1+ """Tests for OrderManager core API."""
2+
3+ import pytest
4+ from unittest .mock import AsyncMock , MagicMock , patch
5+
6+ from project_x_py .models import Order , OrderPlaceResponse
7+ from project_x_py .exceptions import ProjectXOrderError
8+
9+
10+ class TestOrderManagerCore :
11+ """Unit tests for OrderManager core public methods."""
12+
13+ @pytest .mark .asyncio
14+ async def test_place_market_order_success (self , order_manager , make_order_response ):
15+ """place_market_order hits /Order/place with correct payload and updates stats."""
16+ # Patch _make_request to return a success response
17+ order_manager .project_x ._make_request = AsyncMock (
18+ return_value = make_order_response (42 )
19+ )
20+ # Should increment orders_placed
21+ start_count = order_manager .stats ["orders_placed" ]
22+ resp = await order_manager .place_market_order ("MGC" , 0 , 2 )
23+ assert isinstance (resp , OrderPlaceResponse )
24+ assert resp .orderId == 42
25+ assert order_manager .project_x ._make_request .call_count == 1
26+ call_args = order_manager .project_x ._make_request .call_args [1 ]["data" ]
27+ assert call_args ["contractId" ] == "MGC"
28+ assert call_args ["type" ] == 2
29+ assert call_args ["side" ] == 0
30+ assert call_args ["size" ] == 2
31+ assert order_manager .stats ["orders_placed" ] == start_count + 1
32+
33+ @pytest .mark .asyncio
34+ async def test_place_order_error_raises (self , order_manager , make_order_response ):
35+ """place_order raises ProjectXOrderError when API fails."""
36+ order_manager .project_x ._make_request = AsyncMock (
37+ return_value = {"success" : False , "errorMessage" : "Test error" }
38+ )
39+ with pytest .raises (ProjectXOrderError ):
40+ await order_manager .place_order ("MGC" , 2 , 0 , 1 )
41+
42+ @pytest .mark .asyncio
43+ async def test_search_open_orders_populates_cache (self , order_manager , make_order_response ):
44+ """search_open_orders converts API dicts to Order objects and populates cache."""
45+ resp_order = {
46+ "id" : 101 , "accountId" : 12345 , "contractId" : "MGC" ,
47+ "creationTimestamp" : "2024-01-01T01:00:00Z" , "updateTimestamp" : None ,
48+ "status" : 1 , "type" : 1 , "side" : 0 , "size" : 2
49+ }
50+ order_manager .project_x .account_info .id = 12345
51+ order_manager .project_x ._make_request = AsyncMock (
52+ return_value = {"success" : True , "orders" : [resp_order ]}
53+ )
54+ orders = await order_manager .search_open_orders ()
55+ assert isinstance (orders [0 ], Order )
56+ assert order_manager .tracked_orders [str (resp_order ["id" ])] == resp_order
57+ assert order_manager .order_status_cache [str (resp_order ["id" ])] == 1
58+
59+ @pytest .mark .asyncio
60+ async def test_is_order_filled_cache_hit (self , order_manager ):
61+ """is_order_filled returns True from cache and does not call _make_request if cached."""
62+ order_manager ._realtime_enabled = True
63+ order_manager .order_status_cache ["77" ] = 2 # 2=Filled
64+ order_manager .project_x ._make_request = AsyncMock ()
65+ result = await order_manager .is_order_filled (77 )
66+ assert result is True
67+ order_manager .project_x ._make_request .assert_not_called ()
68+
69+ @pytest .mark .asyncio
70+ async def test_is_order_filled_fallback (self , order_manager ):
71+ """is_order_filled falls back to get_order_by_id when not cached."""
72+ order_manager ._realtime_enabled = False
73+ dummy_order = Order (
74+ id = 55 , accountId = 12345 , contractId = "CL" ,
75+ creationTimestamp = "2024-01-01T01:00:00Z" , updateTimestamp = None ,
76+ status = 2 , type = 1 , side = 0 , size = 1
77+ )
78+ order_manager .get_order_by_id = AsyncMock (return_value = dummy_order )
79+ result = await order_manager .is_order_filled (55 )
80+ assert result is True
81+
82+ @pytest .mark .asyncio
83+ async def test_cancel_order_success_and_failure (self , order_manager ):
84+ """cancel_order updates caches/stats on success and handles failure."""
85+ # Setup tracked order
86+ order_manager .tracked_orders ["888" ] = {"status" : 1 }
87+ order_manager .order_status_cache ["888" ] = 1
88+ start = order_manager .stats ["orders_cancelled" ]
89+ order_manager .project_x ._make_request = AsyncMock (return_value = {"success" : True })
90+ assert await order_manager .cancel_order (888 ) is True
91+ assert order_manager .tracked_orders ["888" ]["status" ] == 3
92+ assert order_manager .order_status_cache ["888" ] == 3
93+ assert order_manager .stats ["orders_cancelled" ] == start + 1
94+
95+ order_manager .project_x ._make_request = AsyncMock (return_value = {"success" : False , "errorMessage" : "fail" })
96+ assert await order_manager .cancel_order (888 ) is False
97+
98+ @pytest .mark .asyncio
99+ async def test_modify_order_success_and_aligns (self , order_manager ):
100+ """modify_order aligns prices, makes API call, returns True on success."""
101+ dummy_order = Order (
102+ id = 12 , accountId = 12345 , contractId = "MGC" ,
103+ creationTimestamp = "2024-01-01T01:00:00Z" , updateTimestamp = None ,
104+ status = 1 , type = 1 , side = 0 , size = 1
105+ )
106+ order_manager .get_order_by_id = AsyncMock (return_value = dummy_order )
107+ order_manager .project_x ._make_request = AsyncMock (return_value = {"success" : True })
108+ assert await order_manager .modify_order (12 , limit_price = 2000.5 ) is True
109+
110+ order_manager .project_x ._make_request = AsyncMock (return_value = {"success" : False })
111+ assert await order_manager .modify_order (12 , limit_price = 2001.5 ) is False
112+
113+ @pytest .mark .asyncio
114+ async def test_get_order_statistics (self , order_manager ):
115+ """get_order_statistics returns expected health_status key and stats."""
116+ stats = await order_manager .get_order_statistics ()
117+ assert "statistics" in stats
118+ assert "health_status" in stats
119+ assert stats ["health_status" ] in {"healthy" , "degraded" }
0 commit comments