From a78398463451ff3dec7c1235aa593e1dd451022b Mon Sep 17 00:00:00 2001 From: demo-bot Date: Sun, 9 Aug 2026 16:03:51 +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 | 91 ++++++++++++++++++++++++----------------------- 1 file changed, 47 insertions(+), 44 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index f65c14d..558f767 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,55 +1,58 @@ -from datetime import datetime, timezone +import httpx +import pytest -from app.checker import UnsafeTarget -from app.models import CheckStatus -from tests.conftest import create_monitor +from app.main import app +from tests.conftest import install_checker + +pytestmark = pytest.mark.anyio -class StubChecker: - def __init__(self, result: CheckStatus | Exception): - self.result = result +async def test_crud_and_status_lifecycle(api_client): + created = await api_client.post("/api/v1/monitors", json={"name": "Example", "url": "https://example.com/path?secret=yes"}) + assert created.status_code == 201 + monitor_id = created.json()["id"] + assert created.json()["status"] == "unknown" + assert (await api_client.get("/api/v1/monitors")).json()[0]["id"] == monitor_id - async def check(self, url: str) -> CheckStatus: - if isinstance(self.result, Exception): - raise self.result - return self.result + updated = await api_client.put(f"/api/v1/monitors/{monitor_id}", json={"name": "New", "url": "https://example.org"}) + assert updated.status_code == 200 + assert updated.json()["name"] == "New" + assert (await api_client.get(f"/api/v1/monitors/{monitor_id}/status")).json()["status"] == "unknown" + assert (await api_client.delete(f"/api/v1/monitors/{monitor_id}")).status_code == 204 + assert (await api_client.get(f"/api/v1/monitors/{monitor_id}")).status_code == 404 -async def test_crud_and_operational_routes(client): - assert (await client.get("/healthz")).json() == {"status": "ok"} - assert (await client.get("/readyz")).json() == {"status": "ready"} - created = await create_monitor(client) - monitor_id = created["id"] - assert created["status"]["state"] == "unknown" - assert len((await client.get("/v1/monitors")).json()) == 1 - assert (await client.get(f"/v1/monitors/{monitor_id}")).json()["name"] == "website" - updated = await client.patch(f"/v1/monitors/{monitor_id}", json={"name": "api"}) - assert updated.status_code == 200 and updated.json()["name"] == "api" - assert (await client.patch(f"/v1/monitors/{monitor_id}", json={})).status_code == 422 - assert (await client.delete(f"/v1/monitors/{monitor_id}")).status_code == 204 - missing = await client.get(f"/v1/monitors/{monitor_id}") - assert missing.status_code == 404 - assert missing.json()["detail"]["code"] == "monitor_not_found" +async def test_validation_and_probes(api_client): + assert (await api_client.post("/api/v1/monitors", json={"name": "", "url": "file:///tmp/x"})).status_code == 422 + assert (await api_client.get("/healthz")).json() == {"status": "ok"} + assert (await api_client.get("/readyz")).json() == {"status": "ready"} -async def test_check_updates_and_status_route(app, client): - created = await create_monitor(client) - result = CheckStatus(state="up", checked_at=datetime.now(timezone.utc), - status_code=204, latency_ms=12.5) - app.state.checker = StubChecker(result) - checked = await client.post(f"/v1/monitors/{created['id']}/checks") +async def test_check_updates_status(api_client): + install_checker(lambda request: httpx.Response(204, request=request)) + item = (await api_client.post("/api/v1/monitors", json={"name": "ok", "url": "https://example.com"})).json() + checked = await api_client.post(f"/api/v1/monitors/{item['id']}/check") assert checked.status_code == 200 - assert checked.json()["state"] == "up" - assert checked.json()["status_applied"] is True - current = await client.get(f"/v1/monitors/{created['id']}/status") - assert current.json()["status_code"] == 204 + assert checked.json()["status"] == "up" + assert checked.json()["http_status"] == 204 + assert checked.json()["latency_ms"] >= 0 + assert (await api_client.get(f"/api/v1/monitors/{item['id']}/status")).json()["status"] == "up" -async def test_unsafe_check_maps_error_and_updates_status(app, client): - created = await create_monitor(client) - app.state.checker = StubChecker(UnsafeTarget("target resolves to a non-public address")) - response = await client.post(f"/v1/monitors/{created['id']}/checks") - assert response.status_code == 400 - assert response.json()["detail"]["code"] == "unsafe_target" - current = await client.get(f"/v1/monitors/{created['id']}/status") - assert current.json()["state"] == "error" +async def test_http_failure_is_down(api_client): + install_checker(lambda request: httpx.Response(503, request=request)) + item = (await api_client.post("/api/v1/monitors", json={"name": "bad", "url": "https://example.com"})).json() + checked = await api_client.post(f"/api/v1/monitors/{item['id']}/check") + assert checked.json()["status"] == "down" + assert checked.json()["http_status"] == 503 + + +async def test_timeout_is_sanitized_error(api_client): + def timeout(request): + raise httpx.ReadTimeout("contains-a-secret", request=request) + install_checker(timeout) + item = (await api_client.post("/api/v1/monitors", json={"name": "slow", "url": "https://example.com"})).json() + checked = await api_client.post(f"/api/v1/monitors/{item['id']}/check") + assert checked.json()["status"] == "error" + assert checked.json()["error"] == "timeout" + assert "contains-a-secret" not in checked.text