Files
ag-bids-mcp/tests/test_auth.py
T
justin 875a190983 Initial commit: ag-bids MCP server
Exposes live + historical ag-bids commodity data (from the ag-monitor
service at agbids.paul.farm) as MCP tools, sitting behind MetaMCP at
https://mcp.jpaul.io/metamcp/ag-bids/mcp.

Pattern mirrors zerto-docs-rag with one addition: HTTP Basic auth in
front of the streamable-HTTP transport so namespace guessers can't reach
the tools. Stdio transport is unaffected (used by local Claude Desktop
dev).

Tools (markdown returns, ~15 LOC each):
  best_local_bid(commodity)       — where to sell corn/soy/wheat today,
                                    for the current calendar month only
  current_lime_price()            — latest lime quotes ($/ton)
  current_input_price(product?)   — MAP / Potash / Lime
  latest_prices(...)              — filtered snapshot
  price_history(...)              — per-(source,delivery) trend
  list_sources / list_commodities / list_deliveries
  source_health()                 — healthy / stale / down buckets
  todays_summary()                — same shape as morning brief snapshot

Data path: ag-bids-mcp -> X-API-Key -> /api/data/* on ag-monitor
(reuses BRIEF_API_KEY).

Tests: 24 covering the httpx client, markdown formatters, HTTP Basic
middleware (401/200), and JSONL usage logging.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 11:37:46 -04:00

103 lines
3.3 KiB
Python

"""HTTP Basic middleware tests."""
from __future__ import annotations
import base64
import importlib
import os
import sys
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
def _reload_auth(monkeypatch, user="alice", password="hunter2"):
monkeypatch.setenv("AG_BIDS_MCP_USER", user)
monkeypatch.setenv("AG_BIDS_MCP_PASS", password)
from ag_bids_mcp import auth
importlib.reload(auth)
return auth
def _b64(creds: str) -> str:
return base64.b64encode(creds.encode()).decode()
def test_expected_credentials_requires_both(monkeypatch):
monkeypatch.delenv("AG_BIDS_MCP_USER", raising=False)
monkeypatch.delenv("AG_BIDS_MCP_PASS", raising=False)
from ag_bids_mcp import auth
importlib.reload(auth)
import pytest
with pytest.raises(RuntimeError):
auth.expected_credentials()
monkeypatch.setenv("AG_BIDS_MCP_USER", "alice")
monkeypatch.delenv("AG_BIDS_MCP_PASS", raising=False)
importlib.reload(auth)
with pytest.raises(RuntimeError):
auth.expected_credentials()
def test_middleware_via_starlette_app(monkeypatch):
"""End-to-end: a Starlette app with the middleware returns 401 / 200 correctly."""
auth = _reload_auth(monkeypatch, "alice", "hunter2")
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import PlainTextResponse
from starlette.routing import Route
from starlette.testclient import TestClient
async def hello(request):
return PlainTextResponse("ok")
app = Starlette(
routes=[Route("/x", endpoint=hello)],
middleware=[Middleware(BaseHTTPMiddleware, dispatch=auth.basic_auth_middleware)],
)
c = TestClient(app)
# No header -> 401 + WWW-Authenticate
r = c.get("/x")
assert r.status_code == 401
assert r.headers.get("www-authenticate", "").startswith("Basic")
# Wrong creds -> 401
r = c.get("/x", headers={"Authorization": "Basic " + _b64("alice:wrong")})
assert r.status_code == 401
# Wrong username, right password -> 401
r = c.get("/x", headers={"Authorization": "Basic " + _b64("eve:hunter2")})
assert r.status_code == 401
# Right creds -> 200
r = c.get("/x", headers={"Authorization": "Basic " + _b64("alice:hunter2")})
assert r.status_code == 200
assert r.text == "ok"
def test_malformed_authorization_header(monkeypatch):
auth = _reload_auth(monkeypatch)
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import PlainTextResponse
from starlette.routing import Route
from starlette.testclient import TestClient
async def hello(request):
return PlainTextResponse("ok")
app = Starlette(
routes=[Route("/x", endpoint=hello)],
middleware=[Middleware(BaseHTTPMiddleware, dispatch=auth.basic_auth_middleware)],
)
c = TestClient(app)
# Not even base64
r = c.get("/x", headers={"Authorization": "Basic !!!not_b64!!!"})
assert r.status_code == 401
# Bearer instead of Basic
r = c.get("/x", headers={"Authorization": "Bearer abc"})
assert r.status_code == 401