gtat-tech-career-kickstarte.../solution/tests/test_execution_system.py

140 lines
6.4 KiB
Python

from decimal import Decimal
import logging
import pytest
from connection.tcp_connection_manager import TcpConnectionManager
from proto.common_pb2 import Instrument, Side
from tests.conftest import DEFAULT_PASSWORD
from tests.common.auth_tests import AuthenticationTests
from tests.common.mock_expectations import CallExpectationsManager
from tests.test_client.admin_test_client import (
AdminClientConnectionHandlerFactory, AdminTestClient,
)
from tests.test_client.risk_gateway_test_client import (
RiskGatewayClientConnectionHandlerFactory, RiskGatewayTestClient,
)
logger = logging.getLogger(__name__)
class TestExecutionSystem(AuthenticationTests):
PROTOCOL = "execution"
@pytest.fixture(autouse=True)
def setup(self, request: pytest.FixtureRequest) -> None:
self.test_name = request.node.name
logger.info(f"Setting up test: {self.test_name}")
self.tcp_connection_manager = TcpConnectionManager()
self.call_expectations_manager = CallExpectationsManager()
self.call_expectations_manager.setup_network(self.tcp_connection_manager)
self._client_factory = RiskGatewayClientConnectionHandlerFactory(self.call_expectations_manager)
self._admin_factory = AdminClientConnectionHandlerFactory(self.call_expectations_manager)
self._admin_client: AdminTestClient | None = None
self._next_instrument_id = 1
def _connect_unauthenticated(self) -> RiskGatewayTestClient:
return self.tcp_connection_manager.connect(self.server_address, self._client_factory)
def _connect_and_login(self, username: str | None = None) -> RiskGatewayTestClient:
"""Connect to the execution server, log in (if auth required), and return the client."""
client: RiskGatewayTestClient = self._connect_unauthenticated()
if self.auth_required:
client.test_login(username=username or self.test_name, password=DEFAULT_PASSWORD)
return client
def _get_admin_client(self) -> AdminTestClient:
if self._admin_client is None:
admin_address = self.orchestrator.get_server_address("admin")
self._admin_client = self.tcp_connection_manager.connect(
admin_address, self._admin_factory)
return self._admin_client
def _create_instrument_via_admin(self, tick_size: Decimal = Decimal("0.01")) -> str:
"""Create an instrument via admin and return its symbol."""
symbol = f"TEST.{self._next_instrument_id}"
self._next_instrument_id += 1
instrument = Instrument(
symbol=symbol, description="Test instrument",
currency="USD", multiplier=1.0)
admin = self._get_admin_client()
admin.test_create_instrument(instrument, tick_size)
return symbol
# =========================================================================
# Authentication
# =========================================================================
def test_insert_order_before_login_is_rejected(self) -> None:
"""Operations that require authentication must fail before login."""
if not self.auth_required:
pytest.skip("Component does not require authentication")
symbol = self._create_instrument_via_admin()
client = self._connect_unauthenticated()
client.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 10,
expect_success=False)
# =========================================================================
# Insert order
# =========================================================================
def test_insert_order(self) -> None:
symbol = self._create_instrument_via_admin()
client = self._connect_and_login()
resp = client.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 10)
response = resp.get_response()
assert response.order_id != 0, "InsertOrderResponse must include an order_id"
assert response.timestamp != 0, "InsertOrderResponse must include a timestamp"
def test_insert_order_on_unknown_instrument(self) -> None:
self._create_instrument_via_admin()
client = self._connect_and_login()
client.test_insert_order("UNKNOWN.SYMBOL", Side.BUY, Decimal("100.0"), 10,
expect_success=False)
def test_insert_order_matching_produces_trade(self) -> None:
symbol = self._create_instrument_via_admin()
buyer = self._connect_and_login(f"{self.test_name}_2")
seller = self._connect_and_login(f"{self.test_name}_3")
buyer.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 10)
sell_resp = seller.test_insert_order(symbol, Side.SELL, Decimal("100.0"), 10)
sell_response = sell_resp.get_response()
assert len(sell_response.trade_ids) == 1, "Expected exactly one trade"
assert sell_response.traded_quantity == 10
def test_orders_pass_without_any_limits_configured(self) -> None:
"""When no risk limits are set, orders must flow through without restriction."""
symbol = self._create_instrument_via_admin()
client = self._connect_and_login()
for _ in range(10):
client.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 1000)
# =========================================================================
# Cancel order
# =========================================================================
def test_cancel_resting_order(self) -> None:
symbol = self._create_instrument_via_admin()
client = self._connect_and_login()
client.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 10)
client.test_cancel_order(symbol)
def test_cancel_nonexistent_order(self) -> None:
symbol = self._create_instrument_via_admin()
client = self._connect_and_login()
client.test_cancel_order(symbol, order_id=999999, expect_success=False)
def test_cancel_other_users_order_is_rejected(self) -> None:
"""A user must not be able to cancel another user's order."""
symbol = self._create_instrument_via_admin()
client_a = self._connect_and_login(f"{self.test_name}_2")
client_b = self._connect_and_login(f"{self.test_name}_3")
resp_a = client_a.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 10)
order_id_a = resp_a.get_response().order_id
client_b.test_cancel_order(symbol, order_id=order_id_a, expect_success=False)