136 lines
4.0 KiB
Python
136 lines
4.0 KiB
Python
import httpx2
|
|
import pytest
|
|
from fastapi import FastAPI
|
|
from fastapi.testclient import TestClient
|
|
from prometheus_client import CONTENT_TYPE_LATEST
|
|
|
|
from core.api.observability.metrics import ApiMetrics
|
|
from core.api.observability.routes import create_observability_router
|
|
from core.api.observability.stats import (
|
|
ApiRuntimeStats,
|
|
HttpWorkerStatsClient,
|
|
WorkerStatsUnavailable,
|
|
)
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_http_worker_stats_client_success() -> None:
|
|
async def fake_handler(request: httpx2.Request) -> httpx2.Response:
|
|
assert request.url == "http://test-worker/internal/stats"
|
|
return httpx2.Response(
|
|
status_code=200,
|
|
json={
|
|
"pipeline": {
|
|
"total_processed": 5,
|
|
"total_dlq": 1,
|
|
"in_flight_buffer": 2,
|
|
"last_flush_at": "2026-09-22T12:00:00Z",
|
|
},
|
|
"breakdown": {
|
|
"by_service_type": {"4G_DATA": 5},
|
|
"by_radio_access_type": {"LTE_4G": 5},
|
|
"dlq_by_reason": {"timeout": 1},
|
|
},
|
|
},
|
|
)
|
|
|
|
transport = httpx2.MockTransport(fake_handler)
|
|
client = HttpWorkerStatsClient(
|
|
url="http://test-worker/internal/stats",
|
|
timeout_seconds=2.0,
|
|
)
|
|
# Inject mock transport
|
|
client._client = httpx2.AsyncClient(transport=transport)
|
|
|
|
stats = await client.get_stats()
|
|
assert stats["pipeline"]["total_processed"] == 5
|
|
await client.close()
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_http_worker_stats_client_error() -> None:
|
|
async def failing_handler(request: httpx2.Request) -> httpx2.Response:
|
|
return httpx2.Response(status_code=500)
|
|
|
|
transport = httpx2.MockTransport(failing_handler)
|
|
client = HttpWorkerStatsClient(
|
|
url="http://test-worker/internal/stats",
|
|
timeout_seconds=2.0,
|
|
)
|
|
client._client = httpx2.AsyncClient(transport=transport)
|
|
|
|
with pytest.raises(WorkerStatsUnavailable) as exc_info:
|
|
await client.get_stats()
|
|
|
|
assert "Unable to retrieve worker stats" in str(exc_info.value)
|
|
await client.close()
|
|
|
|
|
|
def test_api_observability_router_metrics() -> None:
|
|
metrics = ApiMetrics()
|
|
stats = ApiRuntimeStats()
|
|
|
|
class DummyWorkerStatsProvider:
|
|
async def get_stats(self) -> dict:
|
|
return {}
|
|
|
|
async def close(self) -> None:
|
|
pass
|
|
|
|
app = FastAPI()
|
|
router = create_observability_router(
|
|
metrics=metrics,
|
|
stats=stats,
|
|
worker_stats_provider=DummyWorkerStatsProvider(),
|
|
)
|
|
app.include_router(router)
|
|
|
|
client = TestClient(app)
|
|
response = client.get("/metrics")
|
|
assert response.status_code == 200
|
|
assert CONTENT_TYPE_LATEST in response.headers["content-type"]
|
|
assert "cdr_api_received_total" in response.text
|
|
|
|
|
|
def test_api_observability_router_stats_aggregation() -> None:
|
|
metrics = ApiMetrics()
|
|
stats = ApiRuntimeStats()
|
|
stats.record_received()
|
|
stats.record_received()
|
|
|
|
class FakeProvider:
|
|
async def get_stats(self) -> dict:
|
|
return {
|
|
"pipeline": {
|
|
"total_processed": 15,
|
|
"total_dlq": 3,
|
|
"in_flight_buffer": 0,
|
|
"last_flush_at": "2026-09-22T10:00:00Z",
|
|
},
|
|
"breakdown": {
|
|
"by_service_type": {"4G_DATA": 15},
|
|
"by_radio_access_type": {"LTE_4G": 15},
|
|
"dlq_by_reason": {},
|
|
},
|
|
}
|
|
|
|
async def close(self) -> None:
|
|
pass
|
|
|
|
app = FastAPI()
|
|
router = create_observability_router(
|
|
metrics=metrics,
|
|
stats=stats,
|
|
worker_stats_provider=FakeProvider(),
|
|
)
|
|
app.include_router(router)
|
|
|
|
client = TestClient(app)
|
|
response = client.get("/stats")
|
|
assert response.status_code == 200
|
|
body = response.json()
|
|
assert body["pipeline"]["total_received"] == 2
|
|
assert body["pipeline"]["total_processed"] == 15
|
|
assert body["pipeline"]["total_dlq"] == 3
|
|
|