feat(ocr): add metrics.py factory with test-scoped CollectorRegistry support
Encapsulates every custom OCR metric in an OcrMetrics frozen dataclass and exposes a `build_metrics(registry)` factory. Production main.py binds against the default REGISTRY; tests construct a fresh CollectorRegistry per case and monkeypatch main.metrics, so counter values stay isolated between tests (decision #3 on issue #652, Option A). Refs #652 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,8 +9,19 @@ from unittest.mock import AsyncMock, patch
|
||||
import pytest
|
||||
from httpx import ASGITransport, AsyncClient
|
||||
from PIL import Image
|
||||
from prometheus_client import CollectorRegistry
|
||||
|
||||
from main import app
|
||||
from metrics import build_metrics
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fresh_metrics(monkeypatch):
|
||||
"""Replace the module-level `main.metrics` with one bound to a fresh registry."""
|
||||
registry = CollectorRegistry()
|
||||
test_metrics = build_metrics(registry)
|
||||
monkeypatch.setattr("main.metrics", test_metrics)
|
||||
return test_metrics
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -56,3 +67,27 @@ async def test_metrics_includes_http_request_metrics_after_ocr_call():
|
||||
body = metrics_response.text
|
||||
assert "http_requests_total" in body
|
||||
assert "http_request_duration_seconds" in body
|
||||
|
||||
|
||||
def test_build_metrics_registers_all_custom_metrics_on_given_registry():
|
||||
"""`build_metrics` returns an OcrMetrics bound to the supplied registry."""
|
||||
registry = CollectorRegistry()
|
||||
metrics = build_metrics(registry)
|
||||
|
||||
metric_names = {m.name for m in registry.collect()}
|
||||
expected = {
|
||||
"ocr_jobs",
|
||||
"ocr_pages",
|
||||
"ocr_skipped_pages",
|
||||
"ocr_words",
|
||||
"ocr_illegible_words",
|
||||
"ocr_processing_seconds",
|
||||
"ocr_training_runs",
|
||||
"ocr_model_accuracy",
|
||||
"ocr_models_ready",
|
||||
}
|
||||
assert expected <= metric_names, f"missing: {expected - metric_names}"
|
||||
|
||||
# A second registry yields a separate container — no shared state.
|
||||
other_metrics = build_metrics(CollectorRegistry())
|
||||
assert metrics is not other_metrics
|
||||
|
||||
Reference in New Issue
Block a user