"""API smoke tests for plan seed, feedback priors, agent auth, rules reschedule.""" from __future__ import annotations from pathlib import Path import pytest from fastapi.testclient import TestClient from app.config import get_settings from app.schedule_math import ev_score from app.schedule_reschedule import seed_starter_blocks @pytest.fixture() def client(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> TestClient: monkeypatch.setenv("DATA_ROOT", str(tmp_path / "data")) monkeypatch.setenv("AGENT_TOKEN", "test-agent-token") monkeypatch.setenv("APP_SECRET_KEY", "test-secret-key") monkeypatch.setenv("APP_PASSWORD", "test-password") monkeypatch.setenv("ENV", "dev") get_settings.cache_clear() from app.main import create_app c = TestClient(create_app()) assert c.post("/api/auth/login", json={"password": "test-password"}).status_code == 200 return c def test_ev_score_locked_formula() -> None: base = ev_score(p_done=0.8, utility=1.0, fun=0.5, d_hat_min=30, fse=0.2) raised = ev_score( p_done=0.8, utility=1.0, fun=0.5, d_hat_min=30, fse=0.2, raise_eta=True, raise_lambda_f=True, ) assert isinstance(base, float) and isinstance(raised, float) def test_seed_starter_keeps_p0() -> None: blocks = seed_starter_blocks("2026-07-19", capacity_hint=0.8) kinds = {b.kind for b in blocks} assert "food_out" in kinds and "sleep_window" in kinds assert all(b.locked for b in blocks if b.priority == "P0") def test_plan_seed_and_feedback_priors(client: TestClient) -> None: day = "2026-07-19" r = client.post(f"/api/plan/{day}/seed") assert r.status_code == 200, r.text plan = r.json()["data"] assert plan["blocks"] assert "capacity_hint" in plan bid = plan["blocks"][0]["id"] fb = client.post( f"/api/plan/{day}/blocks/{bid}/feedback", json={ "did": "done", "actual_min": 20, "quality": 4, "fun": 3, "energy_after": 1, "would_repeat": "yes", }, ) assert fb.status_code == 200, fb.text priors = client.get("/api/schedule/priors").json()["data"]["kinds"] assert any(p.get("n", 0) >= 1 for p in priors) def test_agent_requires_token(client: TestClient) -> None: bare = TestClient(client.app) r = bare.get("/api/agent/context?day=2026-07-19") assert r.status_code in (401, 403) ok = bare.get( "/api/agent/context?day=2026-07-19", headers={"Authorization": "Bearer test-agent-token"}, ) assert ok.status_code == 200 assert ok.json()["data"]["constraints"]["scope"] == "P1_thin_loop" def test_reschedule_rules_source(client: TestClient) -> None: day = "2026-07-20" client.post(f"/api/plan/{day}/seed") r = client.post(f"/api/plan/{day}/reschedule", json={"reason": "test", "force": True}) assert r.status_code == 200, r.text body = r.json()["data"] assert body["source"] in ("rules", "openrouter") p0 = [b for b in body["plan"]["blocks"] if b["priority"] == "P0"] assert p0