"""Tests for the remaining transport and router paths."""
from datetime import date
import pytest
from openbb_core.app.model.abstract.error import OpenBBError
from openbb_tmx.utils import cache, helpers, mx
OPTIONS_LIST_HTML = """
Option Symbol Underlying Symbol Name of Company
AC AC Air Canada
BNS BNS Bank of Nova Scotia
"""
QUOTES_HTML = """
Calls Unnamed: 7_level_0 Puts
"""
EOD_CSV = (
"Date,Symbol,Class Symbol,Root Symbol,Underlying Symbol,Ins. Type,Strike,"
"Expiry date,Call/Put,Bid,Ask,Bid size,Ask size,Last price,Volume,"
"Previous price,Net change,Open,High,Low,Total value,Nb. of transaction,"
"Settlement price,Open interest,Implied volatility\n"
"2026-07-24,AC,AC,AC,AC,O,20.0,2026-07-31,0,2.9,3.1,10,10,3.0,5,2.8,0.2,"
"2.9,3.2,2.8,1500,3,3.0,100,40.0\n"
)
class TestCacheRequests:
"""The cached and uncached URL transports."""
async def test_cached_request_reads_json(self, monkeypatch):
class Response:
async def json(self, content_type=None):
return {"ok": True}
def release(self):
return None
class Session:
async def __aenter__(self):
return self
async def __aexit__(self, *args):
return False
async def request(self, method, url, **kwargs):
return Response()
monkeypatch.setattr(
"aiohttp_client_cache.session.CachedSession", lambda **kw: Session()
)
assert await cache.amake_request("https://x") == {"ok": True}
async def test_uncached_request_reads_text(self, monkeypatch):
class Response:
async def text(self):
return "hello"
def release(self):
return None
class Session:
async def __aenter__(self):
return self
async def __aexit__(self, *args):
return False
async def request(self, method, url, **kwargs):
return Response()
monkeypatch.setattr("aiohttp.ClientSession", lambda **kw: Session())
body = await cache.amake_request(
"https://x", use_cache=False, accept_type="text"
)
assert body == "hello"
async def test_binary_read(self, monkeypatch):
class Response:
async def read(self):
return b"\x00"
def release(self):
return None
class Session:
async def __aenter__(self):
return self
async def __aexit__(self, *args):
return False
async def request(self, method, url, **kwargs):
return Response()
monkeypatch.setattr("aiohttp.ClientSession", lambda **kw: Session())
assert (
await cache.amake_request(
"https://x", use_cache=False, accept_type="binary"
)
== b"\x00"
)
def test_release_tolerates_a_cached_response(self):
cache._release(object())
async def test_gql_without_cache(self, monkeypatch):
class Response:
async def json(self, content_type=None):
return {"data": {"ok": 1}}
def release(self):
return None
class Session:
async def __aenter__(self):
return self
async def __aexit__(self, *args):
return False
async def post(self, *args, **kwargs):
return Response()
monkeypatch.setattr("aiohttp.ClientSession", lambda **kw: Session())
assert await cache.amake_gql_request("op", "q", use_cache=False) == {"ok": 1}
class TestMxUniverse:
"""The Montreal Exchange universe."""
@pytest.fixture(autouse=True)
def _no_enrichment(self, monkeypatch):
"""Serve empty listing metadata so the parse is tested in isolation."""
async def empty(use_cache=True):
return {}
monkeypatch.setattr(mx, "get_options_listings", empty)
monkeypatch.setattr(mx, "get_expiry_cycles", empty)
async def test_universe_is_parsed(self, monkeypatch):
async def fake(url, use_cache=True, accept_type="json", **kwargs):
return (
''
'CGB '
)
monkeypatch.setattr("openbb_tmx.utils.cache.amake_request", fake)
mx.get_instrument_universe.cache_clear()
rows = await mx.get_instrument_universe()
assert rows[0]["symbol"] == "CGB"
async def test_empty_page_raises(self, monkeypatch):
async def fake(url, **kwargs):
return ""
monkeypatch.setattr("openbb_tmx.utils.cache.amake_request", fake)
mx.get_instrument_universe.cache_clear()
with pytest.raises(OpenBBError, match="came back empty"):
await mx.get_instrument_universe()
async def test_filter_by_class(self, monkeypatch):
async def fake(url, **kwargs):
return (
''
'CGB '
''
'AC '
)
monkeypatch.setattr("openbb_tmx.utils.cache.amake_request", fake)
mx.get_instrument_universe.cache_clear()
assert len(await mx.get_instruments_by_class()) == 2
mx.get_instrument_universe.cache_clear()
rows = await mx.get_instruments_by_class("interest_rate_derivative")
assert [r["symbol"] for r in rows] == ["CGB"]
class TestOptionsHelpers:
"""The Montreal Exchange option helpers."""
async def test_options_tickers(self, monkeypatch):
async def fake(url, use_cache=True, **kwargs):
return OPTIONS_LIST_HTML
monkeypatch.setattr(helpers, "get_data_from_url", fake)
frame = await helpers.get_all_options_tickers()
assert "AC" in frame.index
async def test_options_tickers_empty(self, monkeypatch):
async def fake(url, use_cache=True, **kwargs):
return None
monkeypatch.setattr(helpers, "get_data_from_url", fake)
with pytest.raises(OpenBBError, match="Error with the request"):
await helpers.get_all_options_tickers()
async def test_eod_chains(self, monkeypatch):
from pandas import DataFrame
async def tickers(use_cache=True):
return DataFrame(
[{"underlying_symbol": "AC", "company": "Air Canada"}], index=["AC"]
).rename_axis("option_symbol")
async def fake(url, use_cache=True, **kwargs):
return EOD_CSV
monkeypatch.setattr(helpers, "get_all_options_tickers", tickers)
monkeypatch.setattr(helpers, "get_data_from_url", fake)
frame = await helpers.download_eod_chains("AC", date(2026, 7, 24))
assert not frame.empty
assert frame["option_type"].iloc[0] == "call"
async def test_eod_chains_rejects_unlisted(self, monkeypatch):
from pandas import DataFrame
async def tickers(use_cache=True):
return DataFrame(
[{"underlying_symbol": "BNS", "company": "Bank"}], index=["BNS"]
).rename_axis("option_symbol")
monkeypatch.setattr(helpers, "get_all_options_tickers", tickers)
with pytest.raises(OpenBBError, match="not a valid listing"):
await helpers.download_eod_chains("ZZZZ")
async def test_eod_chains_empty_download(self, monkeypatch):
from pandas import DataFrame
async def tickers(use_cache=True):
return DataFrame(
[{"underlying_symbol": "AC", "company": "Air Canada"}], index=["AC"]
).rename_axis("option_symbol")
async def fake(url, use_cache=True, **kwargs):
return None
monkeypatch.setattr(helpers, "get_all_options_tickers", tickers)
monkeypatch.setattr(helpers, "get_data_from_url", fake)
with pytest.raises(OpenBBError, match="no data was returned"):
await helpers.download_eod_chains("AC")