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.
Some checks failed
ci / validate (push) Has been cancelled
Some checks failed
ci / validate (push) Has been cancelled
This commit is contained in:
@@ -1,77 +1,32 @@
|
||||
import asyncio
|
||||
from collections.abc import Awaitable, Callable
|
||||
from uuid import uuid4
|
||||
|
||||
import httpx
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
||||
async def create(client: httpx.AsyncClient, name: str = "site") -> httpx.Response:
|
||||
return await client.post(
|
||||
"/monitors", json={"name": name, "url": "https://example.com/path?secret=value"}
|
||||
)
|
||||
|
||||
|
||||
async def test_crud_and_error_semantics(client: httpx.AsyncClient) -> None:
|
||||
response = await create(client)
|
||||
def create(client: TestClient, url: str = "https://example.com/health") -> dict[str, object]:
|
||||
response = client.post("/v1/monitors", json={"name": "site", "url": url})
|
||||
assert response.status_code == 201
|
||||
monitor = response.json()
|
||||
assert monitor["current_status"]["state"] == "never_checked"
|
||||
|
||||
response = await client.patch(
|
||||
f"/monitors/{monitor['id']}", json={"name": "renamed", "expected_status": 204}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["name"] == "renamed"
|
||||
assert response.json()["expected_status"] == 204
|
||||
|
||||
assert len((await client.get("/monitors")).json()) == 1
|
||||
assert (await client.delete(f"/monitors/{monitor['id']}")).status_code == 204
|
||||
response = await client.get(f"/monitors/{monitor['id']}")
|
||||
assert response.status_code == 404
|
||||
assert response.json() == {"error": {"code": "not_found", "message": "monitor not found"}}
|
||||
return response.json()
|
||||
|
||||
|
||||
async def test_typed_validation_is_redacted(client: httpx.AsyncClient) -> None:
|
||||
response = await client.post(
|
||||
"/monitors", json={"name": "", "url": "not a URL", "expected_status": 99}
|
||||
)
|
||||
assert response.status_code == 422
|
||||
assert response.json()["error"]["code"] == "invalid_request"
|
||||
assert "not a URL" not in response.text
|
||||
def test_crud_and_operational_routes(client: TestClient) -> None:
|
||||
assert client.get("/healthz").json() == {"status": "ok"}
|
||||
assert client.get("/readyz").json() == {"status": "ready"}
|
||||
item = create(client)
|
||||
monitor_id = item["id"]
|
||||
assert item["current_status"]["state"] == "unknown"
|
||||
assert len(client.get("/v1/monitors").json()) == 1
|
||||
updated = client.patch(f"/v1/monitors/{monitor_id}", json={"name": "new"})
|
||||
assert updated.status_code == 200
|
||||
assert updated.json()["name"] == "new"
|
||||
assert client.get(f"/v1/monitors/{monitor_id}/status").json()["state"] == "unknown"
|
||||
assert client.delete(f"/v1/monitors/{monitor_id}").status_code == 204
|
||||
assert client.get(f"/v1/monitors/{monitor_id}").status_code == 404
|
||||
|
||||
|
||||
async def test_store_survives_concurrent_api_writes(client: httpx.AsyncClient) -> None:
|
||||
responses = await asyncio.gather(*(create(client, f"site-{n}") for n in range(100)))
|
||||
ids = {response.json()["id"] for response in responses}
|
||||
assert len(ids) == 100
|
||||
listed = (await client.get("/monitors")).json()
|
||||
assert {item["id"] for item in listed} == ids
|
||||
|
||||
|
||||
async def test_check_updates_current_status(client: httpx.AsyncClient) -> None:
|
||||
monitor = (await create(client)).json()
|
||||
checked = await client.post(f"/monitors/{monitor['id']}/check")
|
||||
assert checked.status_code == 200
|
||||
assert checked.json()["state"] == "up"
|
||||
status = await client.get(f"/monitors/{monitor['id']}/status")
|
||||
assert status.json() == checked.json()
|
||||
|
||||
|
||||
async def test_operational_routes(client: httpx.AsyncClient) -> None:
|
||||
assert (await client.get("/health/live")).json() == {"status": "ok"}
|
||||
assert (await client.get("/health/ready")).json()["storage"] == "process-local-memory"
|
||||
|
||||
|
||||
async def test_redirect_is_revalidated(
|
||||
client_factory: Callable[[httpx.AsyncBaseTransport], Awaitable[httpx.AsyncClient]],
|
||||
) -> None:
|
||||
async def handler(request: httpx.Request) -> httpx.Response:
|
||||
if request.url.host == "example.com":
|
||||
return httpx.Response(302, headers={"location": "http://127.0.0.1/admin"})
|
||||
raise AssertionError("blocked redirect must not reach transport")
|
||||
|
||||
client = await client_factory(httpx.MockTransport(handler))
|
||||
async with client:
|
||||
monitor = (await create(client)).json()
|
||||
result = await client.post(f"/monitors/{monitor['id']}/check")
|
||||
assert result.json()["state"] == "blocked"
|
||||
assert result.json()["observed_status"] == 302
|
||||
def test_validation_and_missing(client: TestClient) -> None:
|
||||
assert client.post("/v1/monitors", json={"name": "", "url": "file:///tmp/x"}).status_code == 422
|
||||
missing = uuid4()
|
||||
assert client.get(f"/v1/monitors/{missing}").json() == {"detail": "monitor not found"}
|
||||
item = create(client)
|
||||
assert client.patch(f"/v1/monitors/{item['id']}", json={}).status_code == 422
|
||||
|
||||
Reference in New Issue
Block a user