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,35 +1,87 @@
|
|||||||
import httpx
|
from dataclasses import dataclass
|
||||||
import pytest
|
|
||||||
|
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
|
@dataclass
|
||||||
async def test_crud_check_status_and_operations(client: httpx.AsyncClient) -> None:
|
class FakeChecker:
|
||||||
created = await client.post("/monitors", json={"name": "Example", "url": "https://example.com/a?token=secret"})
|
result: object
|
||||||
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"
|
|
||||||
|
|
||||||
changed = await client.patch(f"/monitors/{monitor_id}", json={"name": "Changed"})
|
async def check(self, url: str) -> HttpOutcome:
|
||||||
assert changed.status_code == 200
|
if isinstance(self.result, Exception):
|
||||||
assert changed.json()["name"] == "Changed"
|
raise self.result
|
||||||
|
assert isinstance(self.result, HttpOutcome)
|
||||||
checked = await client.post(f"/monitors/{monitor_id}/check")
|
return self.result
|
||||||
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"
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
def client_with(result: object = HttpOutcome(204, 12.5, "https://example.com/")):
|
||||||
async def test_validation_error_has_contract_shape(client: httpx.AsyncClient) -> None:
|
app = create_app(Settings())
|
||||||
response = await client.post("/monitors", json={"name": "", "url": "file:///etc/passwd"})
|
client = TestClient(app)
|
||||||
assert response.status_code == 422
|
client.__enter__()
|
||||||
assert response.json()["error"]["code"] == "validation_error"
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user