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 / quality (push) Has been cancelled
ci / container (push) Has been cancelled

This commit is contained in:
2026-08-09 16:03:52 +00:00
parent a783984634
commit 98dac773bb

View File

@@ -1,109 +1,52 @@
import json
import logging
import httpx import httpx
import pytest import pytest
from app.checker import EndpointChecker, UnsafeTarget from app.checker import EndpointChecker
from app.config import Settings
from app.logging import StructuredJsonFormatter, redact_url pytestmark = pytest.mark.anyio
async def public_resolver(host: str, port: int) -> list[str]: async def test_blocks_private_dns_before_http_call():
return ["93.184.216.34"] called = False
def checker(handler, resolver=public_resolver, **settings):
client = httpx.AsyncClient(transport=httpx.MockTransport(handler))
return EndpointChecker(client, Settings(**settings), resolver), client
async def test_http_status_and_latency():
service, client = checker(lambda request: httpx.Response(503, request=request))
try:
result = await service.check("https://example.com/path")
finally:
await client.aclose()
assert result.state == "down"
assert result.status_code == 503
assert result.latency_ms is not None and result.latency_ms >= 0
async def test_timeout_is_sanitized_error_result():
def timeout(request):
raise httpx.ReadTimeout("secret low-level detail", request=request)
service, client = checker(timeout)
try:
result = await service.check("https://example.com/?token=secret")
finally:
await client.aclose()
assert result.state == "error"
assert result.error == "request timed out"
assert "secret" not in result.error
async def test_dns_blocks_private_answer_before_outbound_request():
calls = 0
def handler(request): def handler(request):
nonlocal calls nonlocal called
calls += 1 called = True
return httpx.Response(200, request=request) return httpx.Response(200, request=request)
async def private_resolver(host: str, port: int) -> list[str]: async def private(host, port):
return ["10.0.0.8"] return ["127.0.0.1"]
service, client = checker(handler, private_resolver) async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client:
try: result = await EndpointChecker(client, 1, 2, 100, private).check("id", "http://public.test")
with pytest.raises(UnsafeTarget, match="non-public"): assert result.status == "error"
await service.check("http://internal.invalid/") assert result.error == "non_public_target"
finally: assert not called
await client.aclose()
assert calls == 0
async def test_mixed_dns_answers_are_blocked(): async def test_blocks_redirect_to_private_target():
async def mixed(host: str, port: int) -> list[str]:
return ["93.184.216.34", "127.0.0.1"]
service, client = checker(lambda request: httpx.Response(200, request=request), mixed)
try:
with pytest.raises(UnsafeTarget):
await service.check("https://example.com")
finally:
await client.aclose()
async def test_redirect_destination_is_resolved_and_blocked():
calls = [] calls = []
def handler(request): def handler(request):
calls.append(str(request.url)) calls.append(str(request.url))
return httpx.Response(302, headers={"Location": "http://127.0.0.1/admin"}, request=request) return httpx.Response(302, headers={"location": "http://internal.test/admin"}, request=request)
async def resolver(host: str, port: int) -> list[str]: async def resolver(host, port):
return ["127.0.0.1"] if host == "127.0.0.1" else ["93.184.216.34"] return ["10.0.0.1"] if host == "internal.test" else ["93.184.216.34"]
service, client = checker(handler, resolver) async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client:
try: result = await EndpointChecker(client, 1, 2, 100, resolver).check("id", "https://example.com")
with pytest.raises(UnsafeTarget): assert result.error == "non_public_target"
await service.check("https://example.com/start") assert calls == ["https://example.com"]
finally:
await client.aclose()
assert calls == ["https://example.com/start"]
async def test_redirect_limit_is_error(): async def test_rejects_if_any_dns_answer_is_private():
async def mixed(host, port):
return ["93.184.216.34", "169.254.169.254"]
async with httpx.AsyncClient(transport=httpx.MockTransport(lambda request: httpx.Response(200))) as client:
result = await EndpointChecker(client, 1, 1, 100, mixed).check("id", "http://example.com")
assert result.error == "non_public_target"
async def test_redirect_limit():
def handler(request): def handler(request):
return httpx.Response(302, headers={"Location": "/again"}, request=request) return httpx.Response(302, headers={"location": "/again"}, request=request)
service, client = checker(handler, max_redirects=1) async def public(host, port):
try: return ["93.184.216.34"]
result = await service.check("https://example.com/start") async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client:
finally: result = await EndpointChecker(client, 1, 1, 100, public).check("id", "https://example.com")
await client.aclose() assert result.error == "too_many_redirects"
assert result.state == "error"
assert result.error == "redirect limit exceeded"
def test_url_redaction_and_structured_log_are_inspectable():
safe = redact_url("https://user:password@example.com/path?token=secret#private")
assert "user" not in safe and "password" not in safe and "secret" not in safe
record = logging.LogRecord("checker", logging.INFO, __file__, 1,
"done", (), None)
record.target = safe
payload = json.loads(StructuredJsonFormatter().format(record))
assert payload["target"] == "https://example.com/path?<redacted>#<redacted>"
assert payload["message"] == "done"