From e56b15b1121a4ebe1cf330fb4cd21b0f3de5ee78 Mon Sep 17 00:00:00 2001 From: demo-bot Date: Sun, 9 Aug 2026 15:41:17 +0000 Subject: [PATCH] decomposer: generate deliverable files for Define the service contract and project architecture for the FastAPI endpoint monitoring service.; Implement the typed monitor CRUD API and concurrency-safe in-memory state according to the service design.; Implement secure on-demand endpoint checks with status updates, latency measurement, robust error handling, and redacted structured logs.; Add operational API endpoints and environment-driven runtime configuration to the monitoring service.; Create automated tests for the monitoring service.; Package the service with Docker and developer documentation.; Validate the complete project. --- tests/test_api.py | 112 +++++++++++++++++++++++++++++++++------------- 1 file changed, 82 insertions(+), 30 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index 30c61be..0e5b581 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,35 +1,87 @@ -import httpx -import pytest +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 -@pytest.mark.anyio -async def test_crud_check_status_and_operations(client: httpx.AsyncClient) -> None: - created = await client.post("/monitors", json={"name": "Example", "url": "https://example.com/a?token=secret"}) - assert created.status_code == 201 - monitor_id = created.json()["id"] - assert created.json()["status"]["state"] == "unknown" - assert (await client.get("/monitors")).json()[0]["name"] == "Example" +@dataclass +class FakeChecker: + result: object - changed = await client.patch(f"/monitors/{monitor_id}", json={"name": "Changed"}) - assert changed.status_code == 200 - assert changed.json()["name"] == "Changed" - - checked = await client.post(f"/monitors/{monitor_id}/check") - assert checked.status_code == 200 - assert checked.json()["status"]["state"] == "up" - assert "token" not in checked.json()["url"] - assert (await client.get(f"/monitors/{monitor_id}/status")).json()["http_status"] == 204 - assert (await client.get("/healthz")).json() == {"status": "ok"} - assert (await client.get("/readyz")).status_code == 200 - - assert (await client.delete(f"/monitors/{monitor_id}")).status_code == 204 - missing = await client.get(f"/monitors/{monitor_id}") - assert missing.status_code == 404 - assert missing.json()["error"]["code"] == "http_404" + async def check(self, url: str) -> HttpOutcome: + if isinstance(self.result, Exception): + raise self.result + assert isinstance(self.result, HttpOutcome) + return self.result -@pytest.mark.anyio -async def test_validation_error_has_contract_shape(client: httpx.AsyncClient) -> None: - response = await client.post("/monitors", json={"name": "", "url": "file:///etc/passwd"}) - assert response.status_code == 422 - assert response.json()["error"]["code"] == "validation_error" +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