|
25 | 25 |
|
26 | 26 | from merino.providers.suggest.finance.backends.protocol import TickerSnapshot |
27 | 27 | from merino.providers.suggest.finance.backends.polygon import PolygonBackend |
| 28 | +from merino.providers.suggest.finance.backends.polygon.utils import generate_cache_key_for_ticker |
28 | 29 |
|
29 | 30 | logger = logging.getLogger(__name__) |
30 | 31 |
|
@@ -82,6 +83,29 @@ def fixture_polygon_parameters( |
82 | 83 | } |
83 | 84 |
|
84 | 85 |
|
| 86 | +@pytest.fixture(name="polygon_factory") |
| 87 | +def fixture_polygon_factory(mocker: MockerFixture, statsd_mock: Any, redis_client: Redis): |
| 88 | + """Return factory fixture to create Polygon backend parameters with overrides.""" |
| 89 | + |
| 90 | + def _polygon_parameters(**overrides: Any) -> dict[str, Any]: |
| 91 | + params = { |
| 92 | + "api_key": "api_key", |
| 93 | + "metrics_client": statsd_mock, |
| 94 | + "http_client": mocker.AsyncMock(spec=AsyncClient), |
| 95 | + "metrics_sample_rate": 1, |
| 96 | + "url_param_api_key": "apiKey", |
| 97 | + "url_single_ticker_snapshot": URL_SINGLE_TICKER_SNAPSHOT, |
| 98 | + "url_single_ticker_overview": URL_SINGLE_TICKER_OVERVIEW, |
| 99 | + "gcs_uploader": mocker.MagicMock(), |
| 100 | + "cache": RedisAdapter(redis_client), |
| 101 | + "ticker_ttl_sec": TICKER_TTL_SEC, |
| 102 | + } |
| 103 | + params.update(overrides) |
| 104 | + return params |
| 105 | + |
| 106 | + return _polygon_parameters |
| 107 | + |
| 108 | + |
85 | 109 | @pytest.fixture(name="polygon") |
86 | 110 | def fixture_polygon( |
87 | 111 | polygon_parameters: dict[str, Any], |
@@ -118,6 +142,14 @@ def fixture_ticker_snapshot_NFLX() -> TickerSnapshot: |
118 | 142 | ) |
119 | 143 |
|
120 | 144 |
|
| 145 | +async def set_redis_key_expiry( |
| 146 | + redis_client: Redis, keys_and_expiry: list[tuple[str, int]] |
| 147 | +) -> None: |
| 148 | + """Set redis cache key expiry (TTL seconds).""" |
| 149 | + for key, ttl in keys_and_expiry: |
| 150 | + await redis_client.expire(key, ttl) |
| 151 | + |
| 152 | + |
121 | 153 | @pytest.mark.asyncio |
122 | 154 | async def test_get_snapshots_from_cache_success( |
123 | 155 | polygon: PolygonBackend, ticker_snapshot_AAPL: TickerSnapshot, ticker_snapshot_NFLX |
@@ -180,3 +212,44 @@ async def test_get_snapshots_from_cache_raises_cache_error( |
180 | 212 | assert len(records) == 1 |
181 | 213 | assert records[0].message.startswith("Failed to fetch snapshots from Redis: test cache error") |
182 | 214 | assert actual == [] |
| 215 | + |
| 216 | + |
| 217 | +@pytest.mark.asyncio |
| 218 | +async def test_refresh_ticker_cache_entries_success( |
| 219 | + polygon_factory, |
| 220 | + ticker_snapshot_AAPL: TickerSnapshot, |
| 221 | + ticker_snapshot_NFLX, |
| 222 | + redis_client: Redis, |
| 223 | + mocker, |
| 224 | +) -> None: |
| 225 | + """Test that refresh_ticker_cache_entries method successfully writes snapshots to cache with new TTL.""" |
| 226 | + polygon = PolygonBackend(**polygon_factory(cache=RedisAdapter(redis_client))) |
| 227 | + |
| 228 | + # Mocking the get_snapshots method to return AAPL and NFLX snapshots fixtures for 2 calls. |
| 229 | + get_snapshots_mock = mocker.patch.object( |
| 230 | + polygon, "get_snapshots", new_callable=mocker.AsyncMock |
| 231 | + ) |
| 232 | + get_snapshots_mock.return_value = [ticker_snapshot_AAPL, ticker_snapshot_NFLX] |
| 233 | + |
| 234 | + expected = [(ticker_snapshot_AAPL, TICKER_TTL_SEC), (ticker_snapshot_NFLX, TICKER_TTL_SEC)] |
| 235 | + |
| 236 | + # write to cache (this method writes with the default 300 sec TTL) |
| 237 | + await polygon.store_snapshots_in_cache([ticker_snapshot_AAPL, ticker_snapshot_NFLX]) |
| 238 | + |
| 239 | + # manually modify the TTL for the above cache entries to 100 instead of 300 |
| 240 | + cache_keys = [] |
| 241 | + for key in ["AAPL", "NFLX"]: |
| 242 | + cache_keys.append(generate_cache_key_for_ticker(key)) |
| 243 | + await set_redis_key_expiry(redis_client, [(cache_keys[0], 100), (cache_keys[1], 100)]) |
| 244 | + |
| 245 | + # refresh the cache entries -- this should reset the TTL to 300 |
| 246 | + # forcing the await here otherwise this task finishes after test execution |
| 247 | + await polygon.refresh_ticker_cache_entries(["AAPL", "NFLX"], await_store=True) |
| 248 | + |
| 249 | + actual = await polygon.get_snapshots_from_cache(["AAPL", "NFLX"]) |
| 250 | + |
| 251 | + assert actual is not None |
| 252 | + assert actual == expected |
| 253 | + |
| 254 | + assert actual[0] == expected[0] |
| 255 | + assert actual[1] == expected[1] |
0 commit comments