"""Offline tests for REST API (TestClient — no live server, no API key).""" import os import sys from pathlib import Path import pytest from fastapi.testclient import TestClient sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) # Ensure open auth for tests os.environ.pop("FORECASTER_API_KEY", None) from api_server import app client = TestClient(app) def test_health_open(): r = client.get("/health") assert r.status_code == 200 assert r.json()["status"] == "ok" assert r.json()["auth"] == "open" def test_scoreboard(): r = client.get("/v1/scoreboard") assert r.status_code == 200 data = r.json() assert "total" in data assert "disclaimer" in data def test_ood_get_fast(): r = client.get("/v1/ood", params={"n_bootstrap": 5}) assert r.status_code == 200 assert "is_ood" in r.json() def test_ood_post_scenario(): r = client.post( "/v1/ood", json={"scenario": {"augmentation_ratio": 0.9}, "n_bootstrap": 5}, ) assert r.status_code == 200 assert "prompt_context" in r.json() def test_jobs_search_get(): r = client.get("/v1/jobs/search", params={"query": "analyst", "industry": "Finance", "limit": 3}) assert r.status_code == 200 data = r.json() assert data["industry"] == "Finance" assert data["count"] <= 3 def test_jobs_search_post(): r = client.post( "/v1/jobs/search", json={"query": "engineer", "industry": "Tech", "limit": 5}, ) assert r.status_code == 200 assert "jobs" in r.json() def test_jobs_search_bad_industry(): r = client.get("/v1/jobs/search", params={"industry": "NotReal"}) assert r.status_code == 400 def test_predictions_open(): r = client.get("/v1/predictions/open", params={"limit": 5}) assert r.status_code == 200 assert "predictions" in r.json() def test_openapi_schema(): r = client.get("/openapi.json") assert r.status_code == 200 schema = r.json() assert schema["info"]["title"] == "forecaster-agent API" assert "/v1/scoreboard" in schema["paths"] def test_api_key_required(monkeypatch): monkeypatch.setenv("FORECASTER_API_KEY", "test-secret-key") # Re-import auth check uses env at call time — configured_api_key reads env each time r = client.get("/health") assert r.status_code == 401 r2 = client.get("/health", headers={"X-API-Key": "test-secret-key"}) assert r2.status_code == 200 monkeypatch.delenv("FORECASTER_API_KEY", raising=False) def test_contribution_flow_api(): from datetime import date, timedelta from sqlmodel import Session, delete from registry import Registry from schemas import Contribution, CrowdSnapshot, Prediction, engine with Session(engine) as session: session.exec(delete(Contribution)) session.exec(delete(CrowdSnapshot)) session.exec(delete(Prediction)) session.commit() p = Prediction( statement="API test prediction for crowd flow", rationale="hidden agent rationale", confidence=0.65, horizon="2027-Q1", resolution_date=date.today() + timedelta(days=90), resolution_criteria="Public filings.", ).assign_id() Registry().add_many([p]) blind = client.get(f"/v1/predictions/{p.id}/contribute") assert blind.status_code == 200 assert "confidence" not in blind.json() post = client.post( f"/v1/predictions/{p.id}/contributions", json={ "contributor_id": "api_tester", "probability": 0.4, "argument": "Because constraints bind however demand grows therefore spending lags since queues persist.", "evidence_urls": ["https://example.com/report"], }, ) assert post.status_code == 200 assert "aggregate_probability" not in post.json() crowd = client.get( f"/v1/predictions/{p.id}/crowd", params={"contributor_id": "api_tester"}, ) assert crowd.status_code == 200 assert "aggregate_probability" in crowd.json() forbidden = client.get( f"/v1/predictions/{p.id}/crowd", params={"contributor_id": "not_submitted"}, ) assert forbidden.status_code == 403