Skip to content

Commit ffd8a2e

Browse files
TexasCodingclaude
andcommitted
fix(tests): Fix all failing tests after recent refactoring
- Fixed cache expiration tests by adding cache_ttl property setter that recreates TTL caches when TTL value changes - Updated optimized cache tests to match Arrow IPC serialization - Fixed trading workflow test to handle both list and dict API responses - Added is_closed attribute to mock httpx client for health status test - Fixed timezone handling in market data tests (use America/Chicago) - Removed obsolete phase4 comprehensive test example - All 76 client tests now passing successfully 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 7c69848 commit ffd8a2e

File tree

9 files changed

+120
-472
lines changed

9 files changed

+120
-472
lines changed

examples/14_phase4_comprehensive_test.py

Lines changed: 0 additions & 439 deletions
This file was deleted.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ dependencies = [
4747
"lz4>=4.4.4",
4848
"cachetools>=6.1.0",
4949
"plotly>=6.3.0",
50+
"deprecated>=1.2.18",
5051
]
5152

5253
[project.optional-dependencies]

src/project_x_py/client/cache.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,15 @@ def __init__(self) -> None:
5050
super().__init__()
5151

5252
# Cache settings (set early so they can be overridden)
53-
self.cache_ttl = 300 # 5 minutes default
53+
self._cache_ttl = 300 # 5 minutes default
5454
self.cache_hit_count = 0
5555

5656
# Internal optimized caches with time-to-live eviction
5757
self._opt_instrument_cache: TTLCache[str, Instrument] = TTLCache(
58-
maxsize=1000, ttl=self.cache_ttl
58+
maxsize=1000, ttl=self._cache_ttl
5959
)
6060
self._opt_market_data_cache: TTLCache[str, bytes] = TTLCache(
61-
maxsize=10000, ttl=self.cache_ttl
61+
maxsize=10000, ttl=self._cache_ttl
6262
)
6363

6464
# Compression settings (configurable)
@@ -69,6 +69,24 @@ def __init__(self) -> None:
6969
"compression_level", 3
7070
) # lz4 compression level (0-16)
7171

72+
@property
73+
def cache_ttl(self) -> float:
74+
"""Get cache TTL value."""
75+
return self._cache_ttl
76+
77+
@cache_ttl.setter
78+
def cache_ttl(self, value: float) -> None:
79+
"""
80+
Set cache TTL and recreate caches with new TTL.
81+
82+
Args:
83+
value: New TTL value in seconds
84+
"""
85+
self._cache_ttl = value
86+
# Recreate caches with new TTL
87+
self._opt_instrument_cache = TTLCache(maxsize=1000, ttl=value)
88+
self._opt_market_data_cache = TTLCache(maxsize=10000, ttl=value)
89+
7290
def _serialize_dataframe(self, df: pl.DataFrame) -> bytes:
7391
"""
7492
Serialize Polars DataFrame efficiently using the Arrow IPC format.

src/project_x_py/client/trading.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,10 +155,21 @@ async def search_open_positions(
155155
"POST", "/Position/searchOpen", data=payload
156156
)
157157

158-
if not response or not response.get("success", False):
158+
# Handle both list response (new API) and dict response (legacy)
159+
if response is None:
160+
return []
161+
162+
# If response is a list, use it directly
163+
if isinstance(response, list):
164+
positions_data = response
165+
# If response is a dict with success/positions structure
166+
elif isinstance(response, dict):
167+
if not response.get("success", False):
168+
return []
169+
positions_data = response.get("positions", [])
170+
else:
159171
return []
160172

161-
positions_data = response.get("positions", [])
162173
return [Position(**pos) for pos in positions_data]
163174

164175
async def search_trades(

tests/client/test_cache.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,12 @@ async def test_cache_cleanup(self, mock_project_x, mock_instrument):
117117
# Wait for expiry
118118
time.sleep(0.2)
119119

120-
# Force cleanup
121-
await client._cleanup_cache()
122-
123-
# Cache should be empty
124-
assert len(client._opt_instrument_cache) == 0
125-
assert len(client._opt_instrument_cache_time) == 0
126-
assert len(client._opt_market_data_cache) == 0
127-
assert len(client._opt_market_data_cache_time) == 0
120+
# TTLCache automatically expires items - no manual cleanup needed
121+
# Check that items have expired
122+
assert client.get_cached_instrument("MGC") is None
123+
assert client.get_cached_instrument("MNQ") is None
124+
assert client.get_cached_market_data("key1") is None
125+
assert client.get_cached_market_data("key2") is None
128126

129127
@pytest.mark.asyncio
130128
async def test_clear_all_caches(self, mock_project_x, mock_instrument):
@@ -145,9 +143,7 @@ async def test_clear_all_caches(self, mock_project_x, mock_instrument):
145143

146144
# Cache should be empty
147145
assert len(client._opt_instrument_cache) == 0
148-
assert len(client._opt_instrument_cache_time) == 0
149146
assert len(client._opt_market_data_cache) == 0
150-
assert len(client._opt_market_data_cache_time) == 0
151147

152148
@pytest.mark.asyncio
153149
async def test_cache_hit_tracking(self, mock_project_x, mock_instrument):

tests/client/test_cache_optimized.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ async def mock_project_x(self, mock_httpx_client):
2121

2222
@pytest.mark.asyncio
2323
async def test_msgpack_serialization(self, mock_project_x):
24-
"""Test that DataFrames are serialized with msgpack."""
24+
"""Test that DataFrames are serialized with Arrow IPC format."""
2525
client = mock_project_x
2626

2727
# Create test data
@@ -38,7 +38,6 @@ async def test_msgpack_serialization(self, mock_project_x):
3838

3939
# Check that data is stored in optimized cache (not compatibility cache)
4040
assert "test_key" in client._opt_market_data_cache
41-
assert "test_key" in client._opt_market_data_cache_time
4241

4342
# Retrieve and verify data integrity
4443
cached_data = client.get_cached_market_data("test_key")
@@ -140,7 +139,7 @@ async def test_cache_statistics(self, mock_project_x, mock_instrument):
140139

141140
# Verify values
142141
assert stats["compression_enabled"] is True
143-
assert stats["serialization"] == "msgpack"
142+
assert stats["serialization"] == "arrow-ipc"
144143
assert stats["compression"] == "lz4"
145144
assert stats["instrument_cache_size"] == 1
146145
assert stats["market_data_cache_size"] == 1

tests/client/test_market_data.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -540,12 +540,12 @@ async def test_get_bars_with_start_and_end_time(
540540
assert "timestamp" in bars.columns
541541
assert "open" in bars.columns
542542

543-
# Should use time-based cache key
544-
start_utc = pytz.UTC.localize(start)
545-
end_utc = pytz.UTC.localize(end)
546-
cache_key = (
547-
f"MGC_{start_utc.isoformat()}_{end_utc.isoformat()}_15_2_True"
548-
)
543+
# Should use time-based cache key with market timezone
544+
# Client uses America/Chicago by default
545+
market_tz = pytz.timezone("America/Chicago")
546+
start_tz = market_tz.localize(start)
547+
end_tz = market_tz.localize(end)
548+
cache_key = f"MGC_{start_tz.isoformat()}_{end_tz.isoformat()}_15_2_True"
549549
assert cache_key in client._opt_market_data_cache
550550

551551
@pytest.mark.asyncio
@@ -677,12 +677,8 @@ async def test_get_bars_with_timezone_aware_times(
677677
assert not bars.is_empty()
678678
assert "timestamp" in bars.columns
679679

680-
# Cache key should use UTC times
681-
start_utc = start.astimezone(pytz.UTC)
682-
end_utc = end.astimezone(pytz.UTC)
683-
cache_key = (
684-
f"MGC_{start_utc.isoformat()}_{end_utc.isoformat()}_30_2_True"
685-
)
680+
# Cache key should use the same timezone as provided (Chicago)
681+
cache_key = f"MGC_{start.isoformat()}_{end.isoformat()}_30_2_True"
686682
assert cache_key in client._opt_market_data_cache
687683

688684
@pytest.mark.asyncio
@@ -729,10 +725,12 @@ async def test_get_bars_time_params_override_days(
729725
)
730726

731727
# Verify that the cache key uses the time range, not days
732-
start_utc = pytz.UTC.localize(start)
733-
end_utc = pytz.UTC.localize(end)
728+
# Client uses America/Chicago by default
729+
market_tz = pytz.timezone("America/Chicago")
730+
start_tz = market_tz.localize(start)
731+
end_tz = market_tz.localize(end)
734732
time_based_key = (
735-
f"MGC_{start_utc.isoformat()}_{end_utc.isoformat()}_15_2_True"
733+
f"MGC_{start_tz.isoformat()}_{end_tz.isoformat()}_15_2_True"
736734
)
737735
days_based_key = "MGC_100_15_2_True"
738736

tests/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ def mock_httpx_client():
226226
mock_client = AsyncMock()
227227
mock_client.request = AsyncMock()
228228
mock_client.aclose = AsyncMock()
229+
mock_client.is_closed = False # Add is_closed attribute
229230
return mock_client
230231

231232

0 commit comments

Comments
 (0)