| """Coverage for CUSIP -> ticker resolution + caching. |
| |
| We don't hit the real OpenFIGI API in tests; the network call is monkeypatched |
| and only the response shape, parsing, ticker selection, and cache behavior |
| are verified. |
| """ |
|
|
| import json |
|
|
| import pytest |
| import requests |
|
|
| from TerraFin.data.providers.corporate import cusip_resolver |
|
|
|
|
| class _StubResponse: |
| def __init__(self, payload, status_code: int = 200) -> None: |
| self._payload = payload |
| self.status_code = status_code |
|
|
| def json(self): |
| if self._payload is _MalformedJsonSentinel: |
| raise ValueError("not json") |
| return self._payload |
|
|
|
|
| _MalformedJsonSentinel = object() |
|
|
|
|
| @pytest.fixture(autouse=True) |
| def _reset_cache(monkeypatch, tmp_path): |
| """Redirect file cache to a temp dir so each test starts clean.""" |
| from TerraFin.data.cache import manager as cache_module |
|
|
| monkeypatch.setattr(cache_module, "_FILE_CACHE_DIR", tmp_path) |
| yield |
|
|
|
|
| def test_resolve_cusip_returns_ticker_from_us_listing(monkeypatch) -> None: |
| captured = {} |
|
|
| def fake_post(url, json=None, timeout=None): |
| captured["body"] = json |
| return _StubResponse( |
| [ |
| { |
| "data": [ |
| {"ticker": "AAPL", "exchCode": "US"}, |
| {"ticker": "AAPL.MX", "exchCode": "MM"}, |
| ] |
| } |
| ] |
| ) |
|
|
| monkeypatch.setattr(cusip_resolver.requests, "post", fake_post) |
|
|
| assert cusip_resolver.resolve_cusip_to_ticker("037833100") == "AAPL" |
| assert captured["body"] == [{"idType": "ID_CUSIP", "idValue": "037833100"}] |
|
|
|
|
| def test_resolve_cusip_caches_misses(monkeypatch) -> None: |
| calls = {"n": 0} |
|
|
| def fake_post(url, json=None, timeout=None): |
| calls["n"] += 1 |
| return _StubResponse([{"warning": "No identifier found."}]) |
|
|
| monkeypatch.setattr(cusip_resolver.requests, "post", fake_post) |
|
|
| assert cusip_resolver.resolve_cusip_to_ticker("999999999") is None |
| |
| assert cusip_resolver.resolve_cusip_to_ticker("999999999") is None |
| assert calls["n"] == 1 |
|
|
|
|
| def test_resolve_cusip_rejects_malformed_input(monkeypatch) -> None: |
| monkeypatch.setattr( |
| cusip_resolver.requests, |
| "post", |
| lambda *a, **k: pytest.fail("network must not be called for invalid CUSIP"), |
| ) |
|
|
| assert cusip_resolver.resolve_cusip_to_ticker("not-a-cusip") is None |
| assert cusip_resolver.resolve_cusip_to_ticker("") is None |
|
|
|
|
| def test_resolve_cusip_swallows_request_exceptions(monkeypatch) -> None: |
| def boom(*a, **k): |
| raise requests.RequestException("network down") |
|
|
| monkeypatch.setattr(cusip_resolver.requests, "post", boom) |
|
|
| assert cusip_resolver.resolve_cusip_to_ticker("037833100") is None |
|
|