Skip to main content
Glama

metatrader-mcp-server

test_orders_route.py9.21 kB
import sys, os # Add src to path to import metatrader_openapi sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, "src"))) import pytest from fastapi.testclient import TestClient from metatrader_openapi.main import app # Assuming 'app' is your FastAPI instance # import metatrader_openapi.main as main_module # Not strictly needed if 'app' is directly imported from metatrader_client.client import MT5Client # To help with mocking from unittest.mock import MagicMock, patch # Mock for the MT5Client instance and its methods @pytest.fixture def mock_mt5_client_order_methods(monkeypatch): # Renamed fixture for clarity mock_place_market_order = MagicMock(return_value={ "error": False, "message": "Mocked BUY EURUSD 0.01 LOT at 1.1000 success (Position ID: 12345)", "data": MagicMock( request=MagicMock(symbol="EURUSD", sl=1.0990, tp=1.1010, comment="custom_comment_test"), # Added comment for testing volume=0.01, price=1.1000, order=12345 ) }) # Adjust the patching target to where 'place_market_order' is actually called. # This should be the client instance used by your router. # Assuming client is accessed as `request.app.state.client` in the route, # and `place_market_order` is `request.app.state.client.order.place_market_order` # A common way to mock this is to patch the method in the module where it's defined or used. # If 'client' in 'metatrader_openapi.routers.orders' is an instance of MT5Client, # and 'order' is an attribute holding an Order instance (which has place_market_order), # then the target for patching is 'metatrader_openapi.routers.orders.client.order.place_market_order'. # This needs to align with how 'client' is instantiated and used in 'orders.py'. # Let's assume the client is set up in main.py and passed around, or routers.orders directly imports and uses a client instance. # The provided thought process suggests: `monkeypatch.setattr("metatrader_openapi.routers.orders.client.order.place_market_order", mock_place_market_order)` # This implies that in 'metatrader_openapi/routers/orders.py', there's an accessible 'client' object. # If client is globally available in routers.orders (e.g. from a module-level import or app state) # For this example, we'll assume 'client' is an object within 'routers.orders' that has an 'order' attribute. # This is a common pattern if 'client' is initialized in 'main.py' and routers access 'app.state.client'. # The FastAPI TestClient handles app state correctly, so patching the method on the actual client object # used by the app is the most robust approach. # However, the prompt specifically suggests monkeypatch.setattr on the module path. # The prompt's suggested patch: monkeypatch.setattr("metatrader_openapi.routers.orders.client.order.place_market_order", mock_place_market_order) return mock_place_market_order # Return the mock that will be asserted def test_place_market_order_api_with_sl_tp(mock_mt5_client_order_methods): # Inject the patched mock with TestClient(app) as api_client: order_payload = { "symbol": "EURUSD", "volume": 0.01, "type": "BUY", "stop_loss": 1.0990, "take_profit": 1.1010 # "comment": "custom_comment_test" # Not yet part of the task, but good for future } # Ensure endpoint is correct, e.g. /api/v1/orders/market # Based on typical FastAPI structure and if orders_router is prefixed with /api/v1/orders response = api_client.post("/api/v1/orders/market", json=order_payload) assert response.status_code == 200, response.text response_json = response.json() assert response_json["error"] is False # Check message carefully, it might vary based on actual implementation assert "Mocked BUY EURUSD 0.01 LOT at 1.1000 success (Position ID: 12345)" in response_json["message"] # Assert that the underlying client method was called correctly by the API route mock_mt5_client_order_methods.assert_called_once_with( symbol="EURUSD", volume=0.01, type="BUY", stop_loss=1.0990, take_profit=1.1010 # comment="custom_comment_test" # if this parameter were added ) def test_place_market_order_api_no_sl_tp(mock_mt5_client_order_methods): # Test without SL/TP mock_mt5_client_order_methods.reset_mock() # Reset mock for this new test case # Adjust return value for this specific test case if needed mock_mt5_client_order_methods.return_value = { "error": False, "message": "Mocked BUY EURUSD 0.01 LOT at 1.1000 success (Position ID: 67890) (no SL/TP)", "data": MagicMock( request=MagicMock(symbol="EURUSD", sl=0.0, tp=0.0, comment=None), volume=0.01, price=1.1000, order=67890 ) } with TestClient(app) as api_client: order_payload = { "symbol": "EURUSD", "volume": 0.01, "type": "BUY" # No stop_loss or take_profit here, relying on defaults in the endpoint } response = api_client.post("/api/v1/orders/market", json=order_payload) assert response.status_code == 200, response.text response_json = response.json() assert response_json["error"] is False assert "Mocked BUY EURUSD 0.01 LOT at 1.1000 success (Position ID: 67890) (no SL/TP)" in response_json["message"] mock_mt5_client_order_methods.assert_called_once_with( symbol="EURUSD", volume=0.01, type="BUY", stop_loss=0.0, # Default SL value expected by place_market_order in routers/orders.py take_profit=0.0 # Default TP value ) # Example for a test case with invalid input def test_place_market_order_api_invalid_type(mock_mt5_client_order_methods): mock_mt5_client_order_methods.reset_mock() # The client method might not even be called if validation fails at FastAPI level # However, if the call to the MT5 client happens and it returns an error: mock_mt5_client_order_methods.return_value = { "error": True, "message": "Invalid type, should be BUY or SELL.", # This message comes from place_market_order client method "data": None } with TestClient(app) as api_client: order_payload = { "symbol": "EURUSD", "volume": 0.01, "type": "INVALID_TYPE", # Invalid order type "stop_loss": 1.0990, "take_profit": 1.1010 } response = api_client.post("/api/v1/orders/market", json=order_payload) # Depending on where validation happens (FastAPI or client method), status code might differ # If FastAPI handles it via Enum in Pydantic model, it would be 422 # If the error comes from the client method as mocked, it could be 200 with error:true or a mapped HTTP error # The current place_market_order in routers/orders.py calls client.order.place_market_order # and then returns its result. If that result has error:true, it's passed on. # The client's place_market_order has its own "Invalid type" check. assert response.status_code == 200 # Assuming the route itself doesn't throw HTTPException for this response_json = response.json() assert response_json["error"] is True assert "Invalid type, should be BUY or SELL." in response_json["message"] # Check if the mock was called with the invalid type, or not called if validation is earlier # Based on current setup, place_market_order in the client is called. mock_mt5_client_order_methods.assert_called_once_with( symbol="EURUSD", volume=0.01, type="INVALID_TYPE", stop_loss=1.0990, take_profit=1.1010 ) # Add more tests: e.g., missing required fields (FastAPI should catch this with 422) def test_place_market_order_api_missing_fields(): # No mock needed if FastAPI validation catches it with TestClient(app) as api_client: order_payload = { "symbol": "EURUSD", # volume and type are missing } response = api_client.post("/api/v1/orders/market", json=order_payload) assert response.status_code == 422 # FastAPI's Unprocessable Entity for validation errors # Optionally, check the content of response.json()["detail"] for specifics response_json = response.json() assert any(field['msg'] == 'field required' and field['loc'] == ['body', 'volume'] for field in response_json['detail']) assert any(field['msg'] == 'field required' and field['loc'] == ['body', 'type'] for field in response_json['detail']) # Reminder: Ensure that the FastAPI app (`app`) and the router prefix are correctly configured # for the endpoint `/api/v1/orders/market`. # If `main.py` includes `orders.router` with `prefix="/api/v1/orders"`, # and `orders.router` has a POST route at `/market`, then `/api/v1/orders/market` is correct. # To confirm router prefix, I might need to read 'src/metatrader_openapi/main.py' # For now, proceeding with the assumption.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ariadng/metatrader-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server