155 lines
4.9 KiB
Python
155 lines
4.9 KiB
Python
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()
|