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,51 +1,44 @@
|
||||
from collections.abc import AsyncIterator
|
||||
from datetime import UTC, datetime
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from app.checker import EndpointChecker, Fetcher, HopResponse
|
||||
from app.config import Settings
|
||||
from app.main import create_app
|
||||
from app.security import ValidatedTarget
|
||||
from endpoint_monitor.models import CheckResult, MonitorStatus
|
||||
|
||||
|
||||
class OkayFetcher(Fetcher):
|
||||
async def fetch(self, target: ValidatedTarget, settings: Settings, remaining: float) -> HopResponse:
|
||||
return HopResponse(204, None)
|
||||
|
||||
|
||||
async def public_resolver(host: str, port: int) -> list[tuple[object, ...]]:
|
||||
return [(2, 1, 6, "", ("93.184.216.34", port))]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def client() -> AsyncIterator[httpx.AsyncClient]:
|
||||
settings = Settings(total_timeout_seconds=2, connect_timeout_seconds=1, read_timeout_seconds=1)
|
||||
checker = EndpointChecker(settings, fetcher=OkayFetcher(), resolver=public_resolver)
|
||||
app = create_app(settings=settings, checker=checker)
|
||||
async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://test") as value:
|
||||
yield value
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_crud_check_status_and_operations(client: httpx.AsyncClient) -> None:
|
||||
assert (await client.get("/health")).json() == {"status": "ok"}
|
||||
assert (await client.get("/ready")).json()["storage"] == "process-local-memory"
|
||||
created = await client.post("/monitors", json={"name": "site", "url": "https://example.com/x?secret=yes"})
|
||||
async def test_crud_and_status(client):
|
||||
created = await client.post("/v1/monitors", json={"name": "site", "url": "https://example.com/a?secret=x"})
|
||||
assert created.status_code == 201
|
||||
monitor_id = created.json()["id"]
|
||||
assert len((await client.get("/monitors")).json()) == 1
|
||||
checked = await client.post(f"/monitors/{monitor_id}/check")
|
||||
assert checked.json()["status"]["state"] == "up"
|
||||
assert checked.json()["applied"] is True
|
||||
assert (await client.get(f"/monitors/{monitor_id}/status")).json()["http_status"] == 204
|
||||
updated = await client.put(f"/monitors/{monitor_id}", json={"name": "new", "url": "https://example.com/new"})
|
||||
assert updated.json()["current_status"] is None
|
||||
assert (await client.delete(f"/monitors/{monitor_id}")).status_code == 204
|
||||
assert (await client.get(f"/monitors/{monitor_id}")).status_code == 404
|
||||
assert (await client.get("/v1/monitors")).json()[0]["name"] == "site"
|
||||
updated = await client.patch(f"/v1/monitors/{monitor_id}", json={"name": "renamed"})
|
||||
assert updated.status_code == 200
|
||||
assert updated.json()["revision"] == 2
|
||||
status = await client.get(f"/v1/monitors/{monitor_id}/status")
|
||||
assert status.json()["status"] == "unknown"
|
||||
assert (await client.delete(f"/v1/monitors/{monitor_id}")).status_code == 204
|
||||
assert (await client.get(f"/v1/monitors/{monitor_id}")).status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validation_and_missing(client: httpx.AsyncClient) -> None:
|
||||
assert (await client.post("/monitors", json={"name": "", "url": "ftp://x"})).status_code == 422
|
||||
assert (await client.get("/monitors/00000000-0000-0000-0000-000000000000")).status_code == 404
|
||||
async def test_duplicate_and_validation(client):
|
||||
body = {"name": "same", "url": "https://example.com"}
|
||||
assert (await client.post("/v1/monitors", json=body)).status_code == 201
|
||||
assert (await client.post("/v1/monitors", json=body)).status_code == 409
|
||||
assert (await client.post("/v1/monitors", json={"name": "x", "url": "file:///etc/passwd"})).status_code == 422
|
||||
|
||||
|
||||
async def test_check_updates_status(client, app):
|
||||
class FakeChecker:
|
||||
async def check(self, url, monitor_id):
|
||||
return CheckResult(status=MonitorStatus.UP, checked_at=datetime.now(UTC), latency_ms=1.2, http_status=204, final_url="https://example.com/")
|
||||
|
||||
app.state.checker = FakeChecker()
|
||||
monitor_id = (await client.post("/v1/monitors", json={"name": "site", "url": "https://example.com"})).json()["id"]
|
||||
result = await client.post(f"/v1/monitors/{monitor_id}/check")
|
||||
assert result.status_code == 200
|
||||
assert result.json()["status"] == "up"
|
||||
saved = (await client.get(f"/v1/monitors/{monitor_id}/status")).json()
|
||||
assert saved["status"] == "up"
|
||||
assert saved["last_check"]["http_status"] == 204
|
||||
|
||||
|
||||
async def test_operations(client):
|
||||
assert (await client.get("/healthz")).json() == {"status": "ok"}
|
||||
assert (await client.get("/readyz")).json() == {"status": "ready"}
|
||||
|
||||
Reference in New Issue
Block a user