45d12b2811
update_service grows explicit URL validation on the redirect chain so a hostile mirror can't bounce the updater to a private IP. restart.ps1 gets stricter argument handling and clearer log lines. default_config.yaml exposes the new toggles. test_system_routes pins the new behaviour.
95 lines
3.1 KiB
Python
95 lines
3.1 KiB
Python
"""Tests for system routes — health, version.
|
|
|
|
These tests use the FastAPI TestClient against the real app. The health
|
|
and version endpoints do NOT require authentication, so we can test them
|
|
without setting up the full dependency injection.
|
|
"""
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
from ledgrab import __version__
|
|
from ledgrab.config import get_config
|
|
|
|
|
|
def _auth_headers() -> dict[str, str]:
|
|
"""Resolve the configured API key lazily so test ordering doesn't matter.
|
|
|
|
Evaluating ``get_config()`` at module import time produced a stale empty
|
|
key when other tests mutated the global config before this module ran.
|
|
"""
|
|
api_key = next(iter(get_config().auth.api_keys.values()), "")
|
|
return {"Authorization": f"Bearer {api_key}"} if api_key else {}
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def client():
|
|
"""Provide a test client for the main app.
|
|
|
|
The app module initializes stores from the default config on import,
|
|
which is acceptable for read-only endpoints tested here.
|
|
"""
|
|
from ledgrab.main import app
|
|
|
|
return TestClient(app, raise_server_exceptions=False)
|
|
|
|
|
|
class TestHealthEndpoint:
|
|
def test_health_returns_200(self, client):
|
|
resp = client.get("/health")
|
|
assert resp.status_code == 200
|
|
|
|
def test_health_response_structure(self, client):
|
|
data = client.get("/health").json()
|
|
assert data["status"] == "healthy"
|
|
assert data["version"] == __version__
|
|
assert "timestamp" in data
|
|
|
|
def test_health_no_auth_required(self, client):
|
|
"""Health endpoint should work without Authorization header."""
|
|
resp = client.get("/health")
|
|
assert resp.status_code == 200
|
|
|
|
|
|
class TestVersionEndpoint:
|
|
def test_version_returns_200(self, client):
|
|
resp = client.get("/api/v1/version")
|
|
assert resp.status_code == 200
|
|
|
|
def test_version_response_fields(self, client):
|
|
data = client.get("/api/v1/version").json()
|
|
assert data["version"] == __version__
|
|
assert "python_version" in data
|
|
assert data["api_version"] == "v1"
|
|
assert "demo_mode" in data
|
|
|
|
|
|
class TestOpenAPIEndpoint:
|
|
def test_openapi_available(self, client):
|
|
resp = client.get("/openapi.json", headers=_auth_headers())
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert "info" in data
|
|
assert data["info"]["version"] == __version__
|
|
|
|
def test_swagger_ui(self, client):
|
|
resp = client.get("/docs", headers=_auth_headers())
|
|
assert resp.status_code == 200
|
|
assert "text/html" in resp.headers["content-type"]
|
|
|
|
def test_openapi_requires_auth(self, client):
|
|
"""Without a valid bearer token, the OpenAPI surface is unreachable."""
|
|
resp = client.get("/openapi.json")
|
|
assert resp.status_code == 401
|
|
|
|
def test_swagger_requires_auth(self, client):
|
|
resp = client.get("/docs")
|
|
assert resp.status_code == 401
|
|
|
|
|
|
class TestRootEndpoint:
|
|
def test_root_returns_html(self, client):
|
|
resp = client.get("/")
|
|
assert resp.status_code == 200
|
|
assert "text/html" in resp.headers["content-type"]
|