Files
ag-bids-mcp/tests/test_client.py
T
justin 831c3e19de
CI / test (push) Successful in 19s
CI / build-push (push) Successful in 5s
latest_prices: include_expired passthrough for historical data (#2)
2026-06-08 19:45:03 -04:00

204 lines
6.1 KiB
Python

"""HTTP client unit tests (no live network)."""
from __future__ import annotations
import importlib
import os
import sys
# Make the package importable when pytest runs from repo root
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
def _reload_client(monkeypatch, key="abc", url="http://test.invalid"):
monkeypatch.setenv("AG_BIDS_API_URL", url)
monkeypatch.setenv("AG_BIDS_API_KEY", key)
from ag_bids_mcp import client
importlib.reload(client)
return client
def test_missing_api_key_raises(monkeypatch):
monkeypatch.setenv("AG_BIDS_API_URL", "http://test.invalid")
monkeypatch.delenv("AG_BIDS_API_KEY", raising=False)
from ag_bids_mcp import client
importlib.reload(client)
import pytest
with pytest.raises(client.AgBidsError):
client.latest()
def test_get_sends_api_key_header(monkeypatch):
client = _reload_client(monkeypatch, key="topsecret")
captured = {}
class FakeResp:
status_code = 200
text = ""
def json(self):
return {"count": 0, "rows": []}
def fake_get(url, params=None, timeout=None, headers=None):
captured["url"] = url
captured["params"] = dict(params or {})
captured["headers"] = dict(headers or {})
return FakeResp()
monkeypatch.setattr(client.httpx, "get", fake_get)
out = client.latest(commodity="corn")
assert captured["headers"]["X-API-Key"] == "topsecret"
assert captured["url"].endswith("/api/data/latest")
assert captured["params"] == {"commodity": "corn"}
assert out == {"count": 0, "rows": []}
def test_get_drops_none_params(monkeypatch):
client = _reload_client(monkeypatch)
captured = {}
class FakeResp:
status_code = 200
text = ""
def json(self): return {"count": 0, "rows": []}
def fake_get(url, params=None, timeout=None, headers=None):
captured["params"] = dict(params or {})
return FakeResp()
monkeypatch.setattr(client.httpx, "get", fake_get)
client.latest(commodity="corn", source=None, delivery="", kind=None)
# None and "" should both be dropped
assert captured["params"] == {"commodity": "corn"}
def test_latest_include_expired_passthrough(monkeypatch):
client = _reload_client(monkeypatch)
captured = {}
class FakeResp:
status_code = 200
text = ""
def json(self): return {"count": 0, "rows": []}
def fake_get(url, params=None, timeout=None, headers=None):
captured["params"] = dict(params or {})
return FakeResp()
monkeypatch.setattr(client.httpx, "get", fake_get)
# Default: not sent, so the API applies its current+future filter.
client.latest(commodity="corn")
assert "include_expired" not in captured["params"]
# Opt in: forwarded so the API returns expired months too.
client.latest(commodity="corn", include_expired=True)
assert captured["params"] == {"commodity": "corn", "include_expired": True}
def test_futures_endpoint_params(monkeypatch):
client = _reload_client(monkeypatch)
captured = {}
class FakeResp:
status_code = 200
text = ""
def json(self): return {"quote": None}
def fake_get(url, params=None, timeout=None, headers=None):
captured["url"] = url
captured["params"] = dict(params or {})
return FakeResp()
monkeypatch.setattr(client.httpx, "get", fake_get)
client.futures(commodity="corn", delivery="Jul 2026")
assert captured["url"].endswith("/api/data/futures")
assert captured["params"] == {"commodity": "corn", "delivery": "Jul 2026"}
# delivery omitted → dropped
client.futures(commodity="corn")
assert captured["params"] == {"commodity": "corn"}
def test_history_without_commodity_drops_param(monkeypatch):
client = _reload_client(monkeypatch)
captured = {}
class FakeResp:
status_code = 200
text = ""
def json(self): return {"rows": []}
def fake_get(url, params=None, timeout=None, headers=None):
captured["params"] = dict(params or {})
return FakeResp()
monkeypatch.setattr(client.httpx, "get", fake_get)
client.history(days=14)
# commodity/source_id/delivery all None → only days survives
assert captured["params"] == {"days": 14}
def test_get_raises_on_non_200(monkeypatch):
client = _reload_client(monkeypatch)
class FakeResp:
status_code = 401
text = "no key"
def json(self): return {}
monkeypatch.setattr(client.httpx, "get", lambda *a, **k: FakeResp())
import pytest
with pytest.raises(client.AgBidsError):
client.best("corn")
def test_get_raises_on_network_error(monkeypatch):
client = _reload_client(monkeypatch)
def boom(*a, **k):
raise client.httpx.ConnectError("network is unreachable")
monkeypatch.setattr(client.httpx, "get", boom)
import pytest
with pytest.raises(client.AgBidsError):
client.sources()
def test_each_endpoint_hits_expected_path(monkeypatch):
client = _reload_client(monkeypatch)
calls = []
class FakeResp:
status_code = 200
text = ""
def json(self): return {}
def fake_get(url, params=None, timeout=None, headers=None):
calls.append((url, dict(params or {})))
return FakeResp()
monkeypatch.setattr(client.httpx, "get", fake_get)
client.latest()
client.history("corn", days=7)
client.best("soy")
client.inputs(product="lime")
client.sources()
client.deliveries("corn")
client.todays_summary()
paths = [u.replace("http://test.invalid", "") for u, _ in calls]
assert paths == [
"/api/data/latest",
"/api/data/history",
"/api/data/best",
"/api/data/inputs",
"/api/data/sources",
"/api/data/deliveries",
"/api/brief/snapshot",
]
# history call sent commodity + days
assert calls[1][1] == {"commodity": "corn", "days": 7}
# best call sent only commodity
assert calls[2][1] == {"commodity": "soy"}
# todays_summary uses kind=morning
assert calls[6][1] == {"kind": "morning"}