Skip to content

Commit 1f6fd40

Browse files
committed
update tests
1 parent 5dfaf40 commit 1f6fd40

File tree

7 files changed

+136
-50
lines changed

7 files changed

+136
-50
lines changed

tests/order_manager/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
# Order Manager test subpackage
1+
# Order Manager test subpackage

tests/order_manager/conftest.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
"""OrderManager test-specific fixtures."""
22

3+
from unittest.mock import AsyncMock, patch
4+
35
import pytest
4-
from unittest.mock import AsyncMock, MagicMock, patch
56

6-
from project_x_py.models import Account, OrderPlaceResponse
7+
from project_x_py.models import Account
78
from project_x_py.order_manager.core import OrderManager
89

10+
911
@pytest.fixture
1012
def order_manager(initialized_client):
1113
"""
@@ -46,11 +48,13 @@ def make_order_response():
4648
"""
4749
Helper to build a dict compatible with OrderPlaceResponse.
4850
"""
51+
4952
def _make(order_id, success=True, error_code=0, error_msg=None):
5053
return {
5154
"orderId": order_id,
5255
"success": success,
5356
"errorCode": error_code,
5457
"errorMessage": error_msg,
5558
}
56-
return _make
59+
60+
return _make

tests/order_manager/test_bracket_orders.py

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
"""Tests for BracketOrderMixin (validation and successful flows)."""
22

3+
from unittest.mock import AsyncMock
4+
35
import pytest
4-
from unittest.mock import AsyncMock, patch
56

6-
from project_x_py.models import OrderPlaceResponse, BracketOrderResponse
77
from project_x_py.exceptions import ProjectXOrderError
8+
from project_x_py.models import BracketOrderResponse, OrderPlaceResponse
9+
810

911
@pytest.mark.asyncio
1012
class TestBracketOrderMixin:
@@ -17,16 +19,19 @@ class TestBracketOrderMixin:
1719
(0, 100.0, 99.0, 99.0, "take profit (99.0) must be above entry (100.0)"),
1820
(1, 100.0, 99.0, 98.0, "stop loss (99.0) must be above entry (100.0)"),
1921
(1, 100.0, 101.0, 101.0, "take profit (101.0) must be below entry (100.0)"),
20-
]
22+
],
2123
)
2224
async def test_bracket_order_validation_fails(self, side, entry, stop, target, err):
2325
"""BracketOrderMixin validates stop/take_profit price relationships."""
2426
from project_x_py.order_manager.bracket_orders import BracketOrderMixin
27+
2528
mixin = BracketOrderMixin()
2629
mixin.place_market_order = AsyncMock()
2730
mixin.place_limit_order = AsyncMock()
2831
mixin.place_stop_order = AsyncMock()
29-
mixin.position_orders = {"FOO": {"entry_orders": [], "stop_orders": [], "target_orders": []}}
32+
mixin.position_orders = {
33+
"FOO": {"entry_orders": [], "stop_orders": [], "target_orders": []}
34+
}
3035
mixin.stats = {"bracket_orders_placed": 0}
3136
with pytest.raises(ProjectXOrderError) as exc:
3237
await mixin.place_bracket_order(
@@ -37,14 +42,31 @@ async def test_bracket_order_validation_fails(self, side, entry, stop, target, e
3742
async def test_bracket_order_success_flow(self):
3843
"""Successful bracket order path places all three orders and updates stats/caches."""
3944
from project_x_py.order_manager.bracket_orders import BracketOrderMixin
45+
4046
mixin = BracketOrderMixin()
41-
mixin.place_market_order = AsyncMock(return_value=OrderPlaceResponse(orderId=1, success=True, errorCode=0, errorMessage=None))
42-
mixin.place_limit_order = AsyncMock(side_effect=[
43-
OrderPlaceResponse(orderId=2, success=True, errorCode=0, errorMessage=None),
44-
OrderPlaceResponse(orderId=3, success=True, errorCode=0, errorMessage=None),
45-
])
46-
mixin.place_stop_order = AsyncMock(return_value=OrderPlaceResponse(orderId=4, success=True, errorCode=0, errorMessage=None))
47-
mixin.position_orders = {"BAR": {"entry_orders": [], "stop_orders": [], "target_orders": []}}
47+
mixin.place_market_order = AsyncMock(
48+
return_value=OrderPlaceResponse(
49+
orderId=1, success=True, errorCode=0, errorMessage=None
50+
)
51+
)
52+
mixin.place_limit_order = AsyncMock(
53+
side_effect=[
54+
OrderPlaceResponse(
55+
orderId=2, success=True, errorCode=0, errorMessage=None
56+
),
57+
OrderPlaceResponse(
58+
orderId=3, success=True, errorCode=0, errorMessage=None
59+
),
60+
]
61+
)
62+
mixin.place_stop_order = AsyncMock(
63+
return_value=OrderPlaceResponse(
64+
orderId=4, success=True, errorCode=0, errorMessage=None
65+
)
66+
)
67+
mixin.position_orders = {
68+
"BAR": {"entry_orders": [], "stop_orders": [], "target_orders": []}
69+
}
4870
mixin.stats = {"bracket_orders_placed": 0}
4971

5072
# Entry type = limit
@@ -59,4 +81,4 @@ async def test_bracket_order_success_flow(self):
5981
assert mixin.position_orders["BAR"]["entry_orders"][-1] == 2
6082
assert mixin.position_orders["BAR"]["stop_orders"][-1] == 4
6183
assert mixin.position_orders["BAR"]["target_orders"][-1] == 3
62-
assert mixin.stats["bracket_orders_placed"] == 1
84+
assert mixin.stats["bracket_orders_placed"] == 1

tests/order_manager/test_core.py

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
"""Tests for OrderManager core API."""
22

3+
from unittest.mock import AsyncMock
4+
35
import pytest
4-
from unittest.mock import AsyncMock, MagicMock, patch
56

6-
from project_x_py.models import Order, OrderPlaceResponse
77
from project_x_py.exceptions import ProjectXOrderError
8+
from project_x_py.models import Order, OrderPlaceResponse
89

910

1011
class TestOrderManagerCore:
@@ -40,12 +41,20 @@ async def test_place_order_error_raises(self, order_manager, make_order_response
4041
await order_manager.place_order("MGC", 2, 0, 1)
4142

4243
@pytest.mark.asyncio
43-
async def test_search_open_orders_populates_cache(self, order_manager, make_order_response):
44+
async def test_search_open_orders_populates_cache(
45+
self, order_manager, make_order_response
46+
):
4447
"""search_open_orders converts API dicts to Order objects and populates cache."""
4548
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+
"id": 101,
50+
"accountId": 12345,
51+
"contractId": "MGC",
52+
"creationTimestamp": "2024-01-01T01:00:00Z",
53+
"updateTimestamp": None,
54+
"status": 1,
55+
"type": 1,
56+
"side": 0,
57+
"size": 2,
4958
}
5059
order_manager.project_x.account_info.id = 12345
5160
order_manager.project_x._make_request = AsyncMock(
@@ -71,9 +80,15 @@ async def test_is_order_filled_fallback(self, order_manager):
7180
"""is_order_filled falls back to get_order_by_id when not cached."""
7281
order_manager._realtime_enabled = False
7382
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
83+
id=55,
84+
accountId=12345,
85+
contractId="CL",
86+
creationTimestamp="2024-01-01T01:00:00Z",
87+
updateTimestamp=None,
88+
status=2,
89+
type=1,
90+
side=0,
91+
size=1,
7792
)
7893
order_manager.get_order_by_id = AsyncMock(return_value=dummy_order)
7994
result = await order_manager.is_order_filled(55)
@@ -86,28 +101,42 @@ async def test_cancel_order_success_and_failure(self, order_manager):
86101
order_manager.tracked_orders["888"] = {"status": 1}
87102
order_manager.order_status_cache["888"] = 1
88103
start = order_manager.stats["orders_cancelled"]
89-
order_manager.project_x._make_request = AsyncMock(return_value={"success": True})
104+
order_manager.project_x._make_request = AsyncMock(
105+
return_value={"success": True}
106+
)
90107
assert await order_manager.cancel_order(888) is True
91108
assert order_manager.tracked_orders["888"]["status"] == 3
92109
assert order_manager.order_status_cache["888"] == 3
93110
assert order_manager.stats["orders_cancelled"] == start + 1
94111

95-
order_manager.project_x._make_request = AsyncMock(return_value={"success": False, "errorMessage": "fail"})
112+
order_manager.project_x._make_request = AsyncMock(
113+
return_value={"success": False, "errorMessage": "fail"}
114+
)
96115
assert await order_manager.cancel_order(888) is False
97116

98117
@pytest.mark.asyncio
99118
async def test_modify_order_success_and_aligns(self, order_manager):
100119
"""modify_order aligns prices, makes API call, returns True on success."""
101120
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
121+
id=12,
122+
accountId=12345,
123+
contractId="MGC",
124+
creationTimestamp="2024-01-01T01:00:00Z",
125+
updateTimestamp=None,
126+
status=1,
127+
type=1,
128+
side=0,
129+
size=1,
105130
)
106131
order_manager.get_order_by_id = AsyncMock(return_value=dummy_order)
107-
order_manager.project_x._make_request = AsyncMock(return_value={"success": True})
132+
order_manager.project_x._make_request = AsyncMock(
133+
return_value={"success": True}
134+
)
108135
assert await order_manager.modify_order(12, limit_price=2000.5) is True
109136

110-
order_manager.project_x._make_request = AsyncMock(return_value={"success": False})
137+
order_manager.project_x._make_request = AsyncMock(
138+
return_value={"success": False}
139+
)
111140
assert await order_manager.modify_order(12, limit_price=2001.5) is False
112141

113142
@pytest.mark.asyncio
@@ -116,4 +145,4 @@ async def test_get_order_statistics(self, order_manager):
116145
stats = await order_manager.get_order_statistics()
117146
assert "statistics" in stats
118147
assert "health_status" in stats
119-
assert stats["health_status"] in {"healthy", "degraded"}
148+
assert stats["health_status"] in {"healthy", "degraded"}

tests/order_manager/test_order_types.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
"""Tests for OrderTypesMixin helpers (market/limit/stop/trailing-stop)."""
22

3-
import pytest
43
from unittest.mock import AsyncMock
54

6-
from project_x_py.models import OrderPlaceResponse
5+
import pytest
6+
77

88
class DummyOrderManager:
99
def __init__(self):
1010
self.place_order = AsyncMock()
1111

12+
1213
@pytest.mark.asyncio
1314
class TestOrderTypesMixin:
1415
"""Unit tests for OrderTypesMixin order type wrappers."""
@@ -17,6 +18,7 @@ async def test_place_market_order(self):
1718
"""place_market_order delegates to place_order with order_type=2."""
1819
dummy = DummyOrderManager()
1920
from project_x_py.order_manager.order_types import OrderTypesMixin
21+
2022
mixin = OrderTypesMixin()
2123
mixin.place_order = dummy.place_order
2224
await mixin.place_market_order("MGC", 0, 1)
@@ -28,6 +30,7 @@ async def test_place_limit_order(self):
2830
"""place_limit_order delegates to place_order with order_type=1 and passes limit_price."""
2931
dummy = DummyOrderManager()
3032
from project_x_py.order_manager.order_types import OrderTypesMixin
33+
3134
mixin = OrderTypesMixin()
3235
mixin.place_order = dummy.place_order
3336
await mixin.place_limit_order("MGC", 1, 2, 2040.0)
@@ -39,6 +42,7 @@ async def test_place_stop_order(self):
3942
"""place_stop_order delegates to place_order with order_type=4 and passes stop_price."""
4043
dummy = DummyOrderManager()
4144
from project_x_py.order_manager.order_types import OrderTypesMixin
45+
4246
mixin = OrderTypesMixin()
4347
mixin.place_order = dummy.place_order
4448
await mixin.place_stop_order("MGC", 1, 2, 2030.0)
@@ -50,9 +54,10 @@ async def test_place_trailing_stop_order(self):
5054
"""place_trailing_stop_order delegates to place_order with order_type=5 and passes trail_price."""
5155
dummy = DummyOrderManager()
5256
from project_x_py.order_manager.order_types import OrderTypesMixin
57+
5358
mixin = OrderTypesMixin()
5459
mixin.place_order = dummy.place_order
5560
await mixin.place_trailing_stop_order("MGC", 1, 2, 5.0)
5661
args = dummy.place_order.call_args.kwargs
5762
assert args["order_type"] == 5
58-
assert args["trail_price"] == 5.0
63+
assert args["trail_price"] == 5.0

tests/order_manager/test_position_orders.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
"""Tests for PositionOrderMixin helpers and tracking."""
22

3-
import pytest
43
import asyncio
54
from unittest.mock import AsyncMock, MagicMock
6-
from project_x_py.order_manager.position_orders import PositionOrderMixin
5+
6+
import pytest
7+
78
from project_x_py.models import OrderPlaceResponse
9+
from project_x_py.order_manager.position_orders import PositionOrderMixin
10+
811

912
@pytest.mark.asyncio
1013
class TestPositionOrderMixin:
@@ -32,9 +35,11 @@ async def test_add_stop_loss_success(self):
3235
mixin.project_x = MagicMock()
3336
position = MagicMock(contractId="QWE", size=2)
3437
mixin.project_x.search_open_positions = AsyncMock(return_value=[position])
35-
mixin.place_stop_order = AsyncMock(return_value=OrderPlaceResponse(
36-
orderId=201, success=True, errorCode=0, errorMessage=None
37-
))
38+
mixin.place_stop_order = AsyncMock(
39+
return_value=OrderPlaceResponse(
40+
orderId=201, success=True, errorCode=0, errorMessage=None
41+
)
42+
)
3843
mixin.track_order_for_position = AsyncMock()
3944
resp = await mixin.add_stop_loss("QWE", 99.0)
4045
assert resp.orderId == 201
@@ -57,9 +62,11 @@ async def test_add_take_profit_success(self):
5762
mixin.project_x = MagicMock()
5863
position = MagicMock(contractId="ZXC", size=3)
5964
mixin.project_x.search_open_positions = AsyncMock(return_value=[position])
60-
mixin.place_limit_order = AsyncMock(return_value=OrderPlaceResponse(
61-
orderId=301, success=True, errorCode=0, errorMessage=None
62-
))
65+
mixin.place_limit_order = AsyncMock(
66+
return_value=OrderPlaceResponse(
67+
orderId=301, success=True, errorCode=0, errorMessage=None
68+
)
69+
)
6370
mixin.track_order_for_position = AsyncMock()
6471
resp = await mixin.add_take_profit("ZXC", 120.0)
6572
assert resp.orderId == 301
@@ -74,4 +81,4 @@ async def test_add_take_profit_no_position(self):
7481
mixin.project_x.search_open_positions = AsyncMock(return_value=[])
7582
mixin.place_limit_order = AsyncMock()
7683
resp = await mixin.add_take_profit("TUV", 55.0)
77-
assert resp is None
84+
assert resp is None

0 commit comments

Comments
 (0)