claire/tests/test_api.py

146 lines
4.7 KiB
Python
Raw Normal View History

"""End-to-end tests against the JSON API using FastAPI's TestClient.
These supersede the old direct-DB CLI smoke tests they exercise the same
service.py paths the CLI now goes through over HTTP.
"""
from __future__ import annotations
import os
from pathlib import Path
import pytest
from fastapi.testclient import TestClient
@pytest.fixture
def client(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> TestClient:
monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path / "data"))
monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path / "config"))
# Force re-import after env vars set so config picks up our tmp paths.
from claire.web.app import create_app
return TestClient(create_app())
def test_health(client: TestClient) -> None:
resp = client.get("/api/v1/health")
assert resp.status_code == 200
body = resp.json()
assert body["status"] == "ok"
assert len(body["machine_id"]) == 36
def test_project_lifecycle(client: TestClient) -> None:
# Empty list to start.
assert client.get("/api/v1/projects").json() == {"projects": []}
# Create.
r = client.post("/api/v1/projects", json={"name": "alpha", "goal": "ship it"})
assert r.status_code == 201, r.text
project = r.json()
assert project["name"] == "alpha"
assert project["goal"] == "ship it"
assert project["status"] == "active"
# Duplicate → 409.
r = client.post("/api/v1/projects", json={"name": "alpha"})
assert r.status_code == 409
# Show.
r = client.get("/api/v1/projects/alpha")
assert r.status_code == 200
assert r.json()["project"]["name"] == "alpha"
assert r.json()["tasks"] == []
# Missing → 404.
assert client.get("/api/v1/projects/zzz").status_code == 404
def test_task_create_and_done(client: TestClient) -> None:
client.post("/api/v1/projects", json={"name": "alpha"})
r = client.post(
"/api/v1/tasks",
json={"project": "alpha", "title": "do thing", "priority": 1},
)
assert r.status_code == 201
task = r.json()
assert task["priority"] == 1
assert task["status"] == "todo"
tid = task["id"]
# Mark done via task update.
r = client.post(f"/api/v1/tasks/{tid}", json={"status": "done"})
assert r.status_code == 200
assert r.json()["status"] == "done"
# task list with status filter.
r = client.get("/api/v1/tasks", params={"status": "done"})
tasks = r.json()["tasks"]
assert len(tasks) == 1 and tasks[0]["id"] == tid
def test_assignment_creation(client: TestClient) -> None:
client.post("/api/v1/projects", json={"name": "alpha"})
tid = client.post(
"/api/v1/tasks", json={"project": "alpha", "title": "t"}
).json()["id"]
sid = "11111111-1111-4111-8111-111111111111"
r = client.post(
"/api/v1/assignments",
json={"task_id": tid, "session_uuid": sid},
)
assert r.status_code == 201
a = r.json()
assert a["task_id"] == tid
assert a["session_uuid"] == sid
# Surfaced in /assignments.
r = client.get("/api/v1/assignments")
assert len(r.json()["assignments"]) == 1
def test_status_rollup(client: TestClient) -> None:
client.post("/api/v1/projects", json={"name": "alpha"})
client.post("/api/v1/tasks", json={"project": "alpha", "title": "t1", "priority": 1})
client.post("/api/v1/tasks", json={"project": "alpha", "title": "t2"})
r = client.get("/api/v1/status")
assert r.status_code == 200
body = r.json()
proj = next(p for p in body["projects"] if p["name"] == "alpha")
assert proj["counts"]["todo"] == 2
assert proj["counts"]["done"] == 0
def test_fleet_considered_shape(client: TestClient) -> None:
"""GET /fleet/considered returns the considered-work shape; an open
task with no free session lands in `remaining_tasks`, not `pairings`."""
client.post("/api/v1/projects", json={"name": "alpha"})
client.post(
"/api/v1/tasks", json={"project": "alpha", "title": "ship hud", "priority": 0}
)
r = client.get("/api/v1/fleet/considered")
assert r.status_code == 200
body = r.json()
assert set(body) == {
"pairings",
"remaining_tasks",
"remaining_sessions",
"capped_out",
}
assert body["pairings"] == [] # no sessions → nothing pairs
titles = [t["title"] for t in body["remaining_tasks"]]
assert "ship hud" in titles
def test_broadcast_dry_run_warns_when_no_assignments(client: TestClient) -> None:
client.post("/api/v1/projects", json={"name": "alpha"})
r = client.post(
"/api/v1/broadcast", json={"target": "alpha", "text": "hello", "yes": False}
)
assert r.status_code == 200
body = r.json()
assert body["sent"] is False
assert body["delivered"] == 0
assert "no active assignments" in (body["error"] or "")