from dataclasses import dataclass from fastapi.testclient import TestClient from app.checker import CheckNetworkError, CheckTimeoutError, HttpOutcome from app.config import Settings from app.main import create_app @dataclass class FakeChecker: result: object async def check(self, url: str) -> HttpOutcome: if isinstance(self.result, Exception): raise self.result assert isinstance(self.result, HttpOutcome) return self.result def client_with(result: object = HttpOutcome(204, 12.5, "https://example.com/")): app = create_app(Settings()) client = TestClient(app) client.__enter__() app.state.checker = FakeChecker(result) return client def create_monitor(client: TestClient, url: str = "https://example.com/") -> dict: response = client.post("/monitors", json={"name": "site", "url": url}) assert response.status_code == 201 return response.json() def test_crud_and_operational_routes() -> None: with client_with() as client: assert client.get("/healthz").json() == {"status": "ok"} assert client.get("/readyz").json() == {"status": "ok"} item = create_monitor(client) monitor_id = item["id"] assert item["current_status"]["state"] == "unknown" assert len(client.get("/monitors").json()) == 1 updated = client.put(f"/monitors/{monitor_id}", json={ "name": "new", "url": "https://example.org/" }) assert updated.status_code == 200 assert updated.json()["name"] == "new" assert client.delete(f"/monitors/{monitor_id}").status_code == 204 missing = client.get(f"/monitors/{monitor_id}") assert missing.status_code == 404 assert missing.json()["error"]["code"] == "monitor_not_found" def test_check_updates_status() -> None: with client_with(HttpOutcome(503, 8.0, "https://example.com/")) as client: item = create_monitor(client) result = client.post(f"/monitors/{item['id']}/check") assert result.status_code == 200 assert result.json()["state"] == "down" status = client.get(f"/monitors/{item['id']}/status").json() assert status["status_code"] == 503 assert status["latency_ms"] == 8.0 def test_timeout_and_network_errors_are_mapped_and_published() -> None: for failure, expected_status, code in [ (CheckTimeoutError(50.0), 504, "check_timeout"), (CheckNetworkError(20.0), 502, "network_error"), ]: with client_with(failure) as client: item = create_monitor(client) response = client.post(f"/monitors/{item['id']}/check") assert response.status_code == expected_status assert response.json()["error"]["code"] == code current = client.get(f"/monitors/{item['id']}/status").json() assert current["state"] == "error" assert current["error_code"] == code def test_validation_and_capacity() -> None: app = create_app(Settings(max_monitors=1)) with TestClient(app) as client: create_monitor(client) full = client.post("/monitors", json={"name": "other", "url": "https://example.org"}) assert full.status_code == 409 invalid = client.post("/monitors", json={"name": "", "url": "ftp://example.com"}) assert invalid.status_code == 422