49 lines
2.0 KiB
Python
49 lines
2.0 KiB
Python
from __future__ import annotations
|
|
|
|
import unittest
|
|
from http import HTTPStatus
|
|
|
|
from api_access import ROUTES, required_role
|
|
from backend.http import correlation_id, normalize_error_payload
|
|
from tools.build_api_registry import build as build_api_registry
|
|
|
|
|
|
class HttpGovernanceTests(unittest.TestCase):
|
|
def test_runtime_registry_resolves_every_declared_route(self) -> None:
|
|
for item in build_api_registry()["routes"]:
|
|
path = (
|
|
item["path"]
|
|
.replace("(\\d{6})", "000001")
|
|
.replace("(\\d+)", "1")
|
|
.replace("(.+)", "sample")
|
|
)
|
|
resolved = ROUTES.resolve(item["method"], path)
|
|
self.assertIsNotNone(resolved, f"{item['method']} {path}")
|
|
self.assertEqual(resolved.feature, item["feature"])
|
|
self.assertEqual(resolved.access, item["access"])
|
|
|
|
def test_unknown_route_has_no_runtime_match(self) -> None:
|
|
self.assertIsNone(ROUTES.resolve("GET", "/api/not-registered"))
|
|
|
|
def test_compatibility_access_function_uses_runtime_registry(self) -> None:
|
|
self.assertEqual(required_role("GET", "/api/screener/setup"), "member")
|
|
self.assertEqual(required_role("POST", "/api/admin/settings"), "admin")
|
|
self.assertEqual(required_role("GET", "/api/dashboard"), "authenticated")
|
|
|
|
def test_errors_keep_legacy_field_and_add_stable_contract(self) -> None:
|
|
result = normalize_error_payload(
|
|
{"error": "invalid input"}, HTTPStatus.BAD_REQUEST, "request-123"
|
|
)
|
|
self.assertEqual(result["error"], "invalid input")
|
|
self.assertEqual(result["message"], "invalid input")
|
|
self.assertEqual(result["code"], "bad_request")
|
|
self.assertEqual(result["request_id"], "request-123")
|
|
|
|
def test_correlation_id_rejects_header_injection(self) -> None:
|
|
self.assertEqual(correlation_id("client-request-123"), "client-request-123")
|
|
self.assertRegex(correlation_id("bad\r\nheader"), r"^[0-9a-f]{32}$")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|