from __future__ import annotations import json import threading import urllib.request from http.server import ThreadingHTTPServer from pathlib import Path from railiance_fabric.cli import main as cli_main from railiance_fabric.graph import build_graph from railiance_fabric.registry import ( RegistryStore, backstage_projection, blast_radius, consumers, library_xregistry_projection, providers, unresolved_dependencies, xregistry_projection, ) from railiance_fabric.server import RegistryHandler def test_registry_accepts_snapshot_and_queries_graph(tmp_path: Path) -> None: store = RegistryStore(tmp_path / "registry.sqlite3") store.init_schema() store.upsert_repository( { "slug": "railiance-fabric", "name": "Railiance Fabric", "remote_url": "https://example.invalid/railiance-fabric.git", } ) graph = build_graph([Path(".")]) snapshot = store.add_snapshot( "railiance-fabric", { "commit": "test-commit", "generated_at": "2026-05-17T00:00:00Z", "graph": graph.to_export(), }, ) combined = store.combined_graph() artifact = store.add_artifact( { "repo_slug": "railiance-fabric", "target_id": "flex-auth.api.http-api", "target_kind": "InterfaceDeclaration", "artifact_type": "openapi", "name": "flex-auth OpenAPI", "uri": "docs/contracts/flex-auth.openapi.yaml", "media_type": "application/vnd.oai.openapi+yaml", "version": "0.1.0", "metadata": {"source": "test"}, } ) libraries = store.ingest_cyclonedx("railiance-fabric", _cyclonedx_bom()) assert snapshot["repo_slug"] == "railiance-fabric" assert artifact["artifact_type"] == "openapi" assert libraries["component_count"] == 2 assert store.list_libraries(name="jsonschema")[0]["purl"] == "pkg:pypi/jsonschema@4.18.0" assert store.graph_node_detail("flex-auth.api.http-api")["artifacts"][0]["name"] == "flex-auth OpenAPI" assert providers(combined, "runtime-secrets")[0]["provider_id"] == "railiance-platform.openbao.runtime-secrets" assert {match["status"] for match in consumers(combined, "railiance-platform.openbao.kv-v2")} >= {"exact"} assert unresolved_dependencies(combined) == [] assert blast_radius(combined, "openbao-kv-v2-mount") assert any(item["kind"] == "Component" for item in backstage_projection(combined)["items"]) assert "services" in xregistry_projection(combined)["groups"] assert "libraries" in library_xregistry_projection(store.list_libraries())["groups"] def test_registry_http_service_serves_queries(tmp_path: Path) -> None: store = RegistryStore(tmp_path / "registry.sqlite3") store.init_schema() store.upsert_repository({"slug": "railiance-fabric", "name": "Railiance Fabric"}) store.add_snapshot( "railiance-fabric", { "commit": "test-commit", "generated_at": "2026-05-17T00:00:00Z", "graph": build_graph([Path(".")]).to_export(), }, ) class Handler(RegistryHandler): pass Handler.store = store server = ThreadingHTTPServer(("127.0.0.1", 0), Handler) thread = threading.Thread(target=server.serve_forever, daemon=True) thread.start() try: base_url = f"http://127.0.0.1:{server.server_port}" assert cli_main( [ "registry", "sync", "--registry-url", base_url, "--repo-slug", "railiance-fabric", "--commit", "test-cli", ".", ] ) == 0 assert store.latest_snapshot("railiance-fabric")["commit"] == "test-cli" with urllib.request.urlopen(f"{base_url}/health", timeout=5) as response: assert json.loads(response.read())["status"] == "ok" with urllib.request.urlopen( f"{base_url}/graph/providers?capability_type=runtime-secrets", timeout=5, ) as response: providers_payload = json.loads(response.read()) artifact_payload = _post_json( f"{base_url}/artifacts", { "repo_slug": "railiance-fabric", "target_id": "railiance-platform.openbao.kv-v2", "target_kind": "InterfaceDeclaration", "artifact_type": "openapi", "name": "OpenBao KV API", "uri": "https://example.invalid/openbao.yaml", }, ) sbom_path = tmp_path / "bom.json" sbom_path.write_text(json.dumps(_cyclonedx_bom()), encoding="utf-8") assert cli_main( [ "registry", "ingest-cyclonedx", str(sbom_path), "--registry-url", base_url, "--repo-slug", "railiance-fabric", ] ) == 0 library_payload = _post_json( f"{base_url}/repositories/railiance-fabric/libraries/cyclonedx", _cyclonedx_bom(), ) with urllib.request.urlopen( f"{base_url}/artifacts?target_id=railiance-platform.openbao.kv-v2", timeout=5, ) as response: artifacts_payload = json.loads(response.read()) with urllib.request.urlopen( f"{base_url}/repositories/railiance-fabric/libraries", timeout=5, ) as response: libraries_payload = json.loads(response.read()) with urllib.request.urlopen(f"{base_url}/exports/backstage", timeout=5) as response: backstage_payload = json.loads(response.read()) with urllib.request.urlopen(f"{base_url}/exports/xregistry", timeout=5) as response: xregistry_payload = json.loads(response.read()) with urllib.request.urlopen(f"{base_url}/exports/libraries/xregistry", timeout=5) as response: library_projection_payload = json.loads(response.read()) assert providers_payload[0]["provider_id"] == "railiance-platform.openbao.runtime-secrets" assert artifact_payload["name"] == "OpenBao KV API" assert artifacts_payload[0]["artifact_type"] == "openapi" assert library_payload["component_count"] == 2 assert libraries_payload[0]["name"] == "jsonschema" assert backstage_payload["kind"] == "BackstageCatalogProjection" assert "interfaces" in xregistry_payload["groups"] assert "libraries" in library_projection_payload["groups"] finally: server.shutdown() server.server_close() thread.join(timeout=5) def _post_json(url: str, payload: dict) -> dict: request = urllib.request.Request( url, data=json.dumps(payload).encode("utf-8"), headers={"Content-Type": "application/json"}, method="POST", ) with urllib.request.urlopen(request, timeout=5) as response: return json.loads(response.read()) def _cyclonedx_bom() -> dict: return { "bomFormat": "CycloneDX", "specVersion": "1.6", "version": 1, "components": [ { "bom-ref": "pkg:pypi/jsonschema@4.18.0", "type": "library", "name": "jsonschema", "version": "4.18.0", "purl": "pkg:pypi/jsonschema@4.18.0", "licenses": [{"license": {"id": "MIT"}}], } ], "services": [ { "bom-ref": "urn:service:state-hub", "name": "state-hub-api", "version": "0.1.0", "endpoints": ["http://127.0.0.1:8000"], } ], }