JobForecaster-Agent / tests /test_api.py
lixiaowww
Initial commit: forecaster agent with zero-cost GitHub Pages deploy.
2fc729c
Raw
History Blame Contribute Delete
4.22 kB
"""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