generated from coulomb/repo-seed
269 lines
10 KiB
Python
269 lines
10 KiB
Python
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(),
|
|
},
|
|
)
|
|
changed_export = graph.to_export()
|
|
changed_export["nodes"] = [
|
|
node
|
|
for node in changed_export["nodes"]
|
|
if node["id"] != "repo-scoping.scope-generator"
|
|
]
|
|
changed_snapshot = store.add_snapshot(
|
|
"railiance-fabric",
|
|
{
|
|
"commit": "test-commit-2",
|
|
"generated_at": "2026-05-17T00:01:00Z",
|
|
"graph": changed_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 changed_snapshot["commit"] == "test-commit-2"
|
|
assert artifact["artifact_type"] == "openapi"
|
|
assert libraries["component_count"] == 2
|
|
assert store.list_libraries(name="jsonschema")[0]["purl"] == "pkg:pypi/jsonschema@4.18.0"
|
|
inventory = store.repository_inventory("railiance-fabric")
|
|
assert inventory["counts"]["snapshots"] == 2
|
|
assert inventory["counts"]["libraries"] == 2
|
|
diff = store.snapshot_diff("railiance-fabric")
|
|
assert diff["from"]["commit"] == "test-commit"
|
|
assert diff["to"]["commit"] == "test-commit-2"
|
|
assert diff["graph"]["removed_nodes"][0]["id"] == "repo-scoping.scope-generator"
|
|
assert store.search("jsonschema")["libraries"][0]["name"] == "jsonschema"
|
|
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"
|
|
second_export = build_graph([Path(".")]).to_export()
|
|
second_export["nodes"] = second_export["nodes"][:-1]
|
|
_post_json(
|
|
f"{base_url}/repositories/railiance-fabric/snapshots",
|
|
{
|
|
"commit": "test-cli-2",
|
|
"generated_at": "2026-05-17T00:02:00Z",
|
|
"graph": second_export,
|
|
},
|
|
)
|
|
|
|
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}/status", timeout=5) as response:
|
|
status_payload = json.loads(response.read())
|
|
with urllib.request.urlopen(
|
|
f"{base_url}/repositories/railiance-fabric/snapshots",
|
|
timeout=5,
|
|
) as response:
|
|
snapshots_payload = json.loads(response.read())
|
|
with urllib.request.urlopen(
|
|
f"{base_url}/repositories/railiance-fabric/snapshots/diff",
|
|
timeout=5,
|
|
) as response:
|
|
drift_payload = json.loads(response.read())
|
|
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}/repositories/railiance-fabric/inventory",
|
|
timeout=5,
|
|
) as response:
|
|
inventory_payload = json.loads(response.read())
|
|
with urllib.request.urlopen(f"{base_url}/search?q=jsonschema", timeout=5) as response:
|
|
search_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 snapshots_payload[0]["commit"] == "test-cli-2"
|
|
assert status_payload["counts"]["repositories"] == 1
|
|
assert status_payload["counts"]["snapshots"] == 3
|
|
assert drift_payload["graph"]["removed_nodes"]
|
|
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 inventory_payload["counts"]["snapshots"] == 3
|
|
assert search_payload["libraries"][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"],
|
|
}
|
|
],
|
|
}
|