- All backend routers moved to /api/v1/ prefix
- Frontend BASE_URL updated to /api/v1
- Setup redirect middleware updated to redirect to /api/v1/setup
- Health router path fixed: prefix=/api/v1/health, @router.get('')
- conftest.py: set server_status=online for test fixture
- Created Docs/API_VERSIONING.md with deprecation policy
- Updated Docs/Backend-Development.md with versioning section
- Updated Instructions.md curl examples
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
111 lines
4.0 KiB
Python
111 lines
4.0 KiB
Python
"""Unit tests for correlation ID middleware and distributed tracing."""
|
|
|
|
from typing import Any
|
|
|
|
import pytest
|
|
from httpx import AsyncClient
|
|
from starlette.testclient import TestClient
|
|
|
|
from app.config import Settings
|
|
from app.main import create_app
|
|
from app.middleware.correlation import CORRELATION_ID_CONTEXT_KEY
|
|
|
|
|
|
def test_correlation_middleware_generates_uuid_when_header_absent() -> None:
|
|
"""Correlation middleware generates a UUID4 when X-Correlation-ID header is missing."""
|
|
settings = Settings(
|
|
database_path="/tmp/test.db",
|
|
fail2ban_socket="/tmp/fake_fail2ban.sock",
|
|
fail2ban_config_dir="/tmp/fail2ban",
|
|
session_secret="test-secret-key-do-not-use-in-production",
|
|
session_duration_minutes=60,
|
|
timezone="UTC",
|
|
log_level="debug",
|
|
)
|
|
|
|
app = create_app(settings=settings)
|
|
|
|
# Test with TestClient (synchronous)
|
|
client = TestClient(app)
|
|
response = client.get("/api/v1/health")
|
|
|
|
# Should have correlation ID header in response
|
|
assert "X-Correlation-ID" in response.headers
|
|
correlation_id = response.headers["X-Correlation-ID"]
|
|
# UUID4 format: 8-4-4-4-12 hex digits
|
|
assert len(correlation_id) == 36
|
|
assert correlation_id.count("-") == 4
|
|
|
|
|
|
def test_correlation_middleware_preserves_header_from_request() -> None:
|
|
"""Correlation middleware preserves X-Correlation-ID header from client request."""
|
|
settings = Settings(
|
|
database_path="/tmp/test.db",
|
|
fail2ban_socket="/tmp/fake_fail2ban.sock",
|
|
fail2ban_config_dir="/tmp/fail2ban",
|
|
session_secret="test-secret-key-do-not-use-in-production",
|
|
session_duration_minutes=60,
|
|
timezone="UTC",
|
|
log_level="debug",
|
|
)
|
|
|
|
app = create_app(settings=settings)
|
|
|
|
client = TestClient(app)
|
|
test_correlation_id = "550e8400-e29b-41d4-a716-446655440000"
|
|
response = client.get("/api/v1/health", headers={"X-Correlation-ID": test_correlation_id})
|
|
|
|
# Should return the same correlation ID in response
|
|
assert response.headers["X-Correlation-ID"] == test_correlation_id
|
|
|
|
|
|
def test_correlation_middleware_stores_in_request_state() -> None:
|
|
"""Correlation middleware stores correlation ID in request.state for handlers."""
|
|
settings = Settings(
|
|
database_path="/tmp/test.db",
|
|
fail2ban_socket="/tmp/fake_fail2ban.sock",
|
|
fail2ban_config_dir="/tmp/fail2ban",
|
|
session_secret="test-secret-key-do-not-use-in-production",
|
|
session_duration_minutes=60,
|
|
timezone="UTC",
|
|
log_level="debug",
|
|
)
|
|
|
|
app = create_app(settings=settings)
|
|
client = TestClient(app)
|
|
|
|
# Make a request and verify correlation ID is available to handlers
|
|
test_correlation_id = "550e8400-e29b-41d4-a716-446655440000"
|
|
response = client.get("/api/v1/health", headers={"X-Correlation-ID": test_correlation_id})
|
|
|
|
# The health endpoint should return 200, proving the correlation ID was processed
|
|
assert response.status_code == 200
|
|
# Response should have correlation ID header (proves it was stored and added)
|
|
assert response.headers["X-Correlation-ID"] == test_correlation_id
|
|
|
|
|
|
def test_correlation_id_in_response_headers() -> None:
|
|
"""Correlation ID is included in all response headers."""
|
|
settings = Settings(
|
|
database_path="/tmp/test.db",
|
|
fail2ban_socket="/tmp/fake_fail2ban.sock",
|
|
fail2ban_config_dir="/tmp/fail2ban",
|
|
session_secret="test-secret-key-do-not-use-in-production",
|
|
session_duration_minutes=60,
|
|
timezone="UTC",
|
|
log_level="debug",
|
|
)
|
|
|
|
app = create_app(settings=settings)
|
|
client = TestClient(app)
|
|
|
|
# Test without providing header (should generate one)
|
|
response = client.get("/api/v1/health")
|
|
assert "X-Correlation-ID" in response.headers
|
|
|
|
# Test with providing header (should preserve it)
|
|
test_id = "test-correlation-id-12345"
|
|
response = client.get("/api/v1/health", headers={"X-Correlation-ID": test_id})
|
|
assert response.headers["X-Correlation-ID"] == test_id
|
|
|