from __future__ import annotations import ast import importlib.util import json from pathlib import Path import tempfile import unittest SCRIPT = ( Path(__file__).resolve().parents[1] / "tools" / "inventory" / "platform-interface-inventory.py" ) SPEC = importlib.util.spec_from_file_location("platform_interface_inventory", SCRIPT) assert SPEC is not None and SPEC.loader is not None inventory = importlib.util.module_from_spec(SPEC) SPEC.loader.exec_module(inventory) class PlatformInterfaceInventoryTests(unittest.TestCase): def test_canonical_api_path_normalizes_versions_and_parameters(self) -> None: self.assertEqual( inventory.canonical_api_path( "http://localhost/api/v1/campaigns/${campaignId}?limit=10" ), "/campaigns/{}", ) self.assertEqual( inventory.canonical_api_path("/api/v2/campaigns/{campaign_id}"), "/campaigns/{}", ) self.assertEqual( inventory.canonical_api_path("/api/v1/calendar/events/delta${querySuffix}"), "/calendar/events/delta", ) self.assertEqual( inventory.canonical_api_path("/api/v1/calendar/events/${eventId}"), "/calendar/events/{}", ) def test_endpoint_declarations_are_exact_and_require_missing_ui_issue(self) -> None: with tempfile.TemporaryDirectory() as directory: path = Path(directory) / "endpoints.json" path.write_text( json.dumps( { "schema_version": 1, "endpoints": [ { "repository": "govoplan-example", "method": "GET", "path": "/example/items/{}", "category": "public_integration", "rationale": "Published integration API.", } ], } ), encoding="utf-8", ) declarations = inventory._load_endpoint_declarations(path) self.assertIn( ("govoplan-example", "GET", "/example/items/{}"), declarations, ) payload = json.loads(path.read_text(encoding="utf-8")) payload["endpoints"][0]["category"] = "missing_ui" path.write_text(json.dumps(payload), encoding="utf-8") with self.assertRaisesRegex(ValueError, "tracking_issue"): inventory._load_endpoint_declarations(path) def test_inventory_reports_unclassified_and_stale_endpoint_declarations( self, ) -> None: webui = { "frontendApiReferences": [], "translationUsages": [], "translationCatalog": {"en": {}, "de": {}}, "fields": [], "labels": [], "visibleText": [], "routes": [], "navigation": [], "uiCapabilities": [], "dynamicTranslationUsages": [], } endpoint = { "repository": "govoplan-example", "method": "GET", "path": "/api/v1/example/items", "file": "src/example.py", "line": 1, "handler": "items", "router": "router", } stale = { "repository": "govoplan-example", "method": "GET", "path": "/example/removed", "category": "removable", "rationale": "Removal is pending.", } result = inventory._assemble_inventory( webui=webui, backend_endpoints=[endpoint], manifests=[], endpoint_declarations={ ("govoplan-example", "GET", "/example/removed"): stale, }, ) self.assertEqual(1, result["summary"]["unclassified_backend_endpoints"]) self.assertEqual(1, result["summary"]["stale_endpoint_declarations"]) self.assertIsNone(result["api"]["backend_endpoints"][0]["surface"]) def test_fastapi_route_scanner_includes_router_prefix(self) -> None: tree = ast.parse( """ from fastapi import APIRouter router = APIRouter(prefix="/api/v1/items") @router.get("/{item_id}") def read_item(item_id: str): return item_id """ ) prefixes = inventory._router_prefixes(tree) function = next( node for node in ast.walk(tree) if isinstance(node, ast.FunctionDef) ) route = inventory._endpoint_from_decorator( function.decorator_list[0], prefixes=prefixes, ) self.assertEqual( route, { "method": "GET", "path": "/api/v1/items/{item_id}", "router": "router", }, ) if __name__ == "__main__": unittest.main()