""" Router tests: technical debt, extension points, contributions, SBOM ingest. """ from __future__ import annotations import pytest # --------------------------------------------------------------------------- # Helpers (reuse domain/topic setup) # --------------------------------------------------------------------------- async def _setup(client): """Create a domain + topic; return (domain_slug, topic_id).""" r = await client.post("/domains/", json={"slug": "testdom", "name": "Test Domain"}) assert r.status_code == 201 r2 = await client.post("/topics/", json={ "slug": "testtopic", "title": "Test Topic", "domain": "testdom", }) assert r2.status_code == 201 return "testdom", r2.json()["id"] async def _create_repo(client, domain_slug, slug="testrepo"): r = await client.post("/repos/", json={ "slug": slug, "name": "Test Repo", "domain_slug": domain_slug, "local_path": "/tmp/testrepo", }) assert r.status_code == 201, r.text return r.json() # --------------------------------------------------------------------------- # Technical Debt # --------------------------------------------------------------------------- class TestTechnicalDebt: async def test_create_and_list(self, client): domain_slug, _ = await _setup(client) r = await client.post("/technical-debt/", json={ "title": "Use UTC datetimes", "domain": domain_slug, }) assert r.status_code == 201 td_id = r.json()["id"] r2 = await client.get(f"/technical-debt/?domain={domain_slug}") assert r2.status_code == 200 ids = [t["id"] for t in r2.json()] assert td_id in ids async def test_status_transition(self, client): domain_slug, _ = await _setup(client) r = await client.post("/technical-debt/", json={ "title": "Old debt", "domain": domain_slug, }) td_id = r.json()["id"] r2 = await client.patch(f"/technical-debt/{td_id}", json={"status": "resolved"}) assert r2.status_code == 200 assert r2.json()["status"] == "resolved" async def test_add_note(self, client): domain_slug, _ = await _setup(client) r = await client.post("/technical-debt/", json={ "title": "Needs note", "domain": domain_slug, }) td_id = r.json()["id"] r2 = await client.post(f"/technical-debt/{td_id}/notes/", json={ "step": "analysis", "content": "Root cause identified.", }) assert r2.status_code == 201 r3 = await client.get(f"/technical-debt/{td_id}/notes/") assert r3.status_code == 200 assert len(r3.json()) == 1 # --------------------------------------------------------------------------- # Extension Points # --------------------------------------------------------------------------- class TestExtensionPoints: async def test_create_and_list(self, client): domain_slug, _ = await _setup(client) r = await client.post("/extension-points/", json={ "domain": domain_slug, "title": "Auth hook", "ep_type": "hook", }) assert r.status_code == 201 ep_id = r.json()["id"] r2 = await client.get(f"/extension-points/?domain={domain_slug}") assert r2.status_code == 200 ids = [e["id"] for e in r2.json()] assert ep_id in ids async def test_status_transition(self, client): domain_slug, _ = await _setup(client) r = await client.post("/extension-points/", json={ "domain": domain_slug, "title": "Webhook", "ep_type": "webhook", }) ep_id = r.json()["id"] r2 = await client.patch(f"/extension-points/{ep_id}", json={"status": "addressed"}) assert r2.status_code == 200 assert r2.json()["status"] == "addressed" # --------------------------------------------------------------------------- # Contributions # --------------------------------------------------------------------------- class TestContributions: async def test_create_draft_and_submit(self, client): r = await client.post("/contributions/", json={ "type": "br", "title": "Fix null pointer", "target_org": "anthropics", "target_repo": "sdk", }) assert r.status_code == 201 c_id = r.json()["id"] assert r.json()["status"] == "draft" r2 = await client.patch(f"/contributions/{c_id}/status", json={ "status": "submitted", }) assert r2.status_code == 200 assert r2.json()["status"] == "submitted" async def test_invalid_transition_returns_422(self, client): r = await client.post("/contributions/", json={ "type": "br", "title": "Fix crash", "target_org": "anthropics", "target_repo": "sdk", }) c_id = r.json()["id"] # starts as draft # draft → merged is not allowed (must go through submitted first) r2 = await client.patch(f"/contributions/{c_id}/status", json={ "status": "merged", }) assert r2.status_code == 422 async def test_list_by_type(self, client): await client.post("/contributions/", json={ "type": "br", "title": "Bug A", "target_org": "org", "target_repo": "repo", }) await client.post("/contributions/", json={ "type": "fr", "title": "Feature B", "target_org": "org", "target_repo": "repo", }) r = await client.get("/contributions/?type=br") assert r.status_code == 200 types = [c["type"] for c in r.json()] assert all(t == "br" for t in types) assert len(types) == 1 # --------------------------------------------------------------------------- # SBOM ingest # --------------------------------------------------------------------------- class TestSBOM: async def test_ingest_and_list_snapshots(self, client): domain_slug, _ = await _setup(client) await _create_repo(client, domain_slug) r = await client.post("/sbom/ingest/", json={ "repo_slug": "testrepo", "entries": [ { "package_name": "fastapi", "package_version": "0.115.0", "ecosystem": "python", "license_spdx": "MIT", "is_direct": True, "is_dev": False, }, { "package_name": "sqlalchemy", "package_version": "2.0.0", "ecosystem": "python", "license_spdx": "MIT", "is_direct": True, "is_dev": False, }, ], }) assert r.status_code == 200 assert r.json()["ingested"] == 2 r2 = await client.get("/sbom/snapshots/?repo_slug=testrepo") assert r2.status_code == 200 assert len(r2.json()) == 1 async def test_ingest_unknown_repo_returns_404(self, client): r = await client.post("/sbom/ingest/", json={ "repo_slug": "doesnotexist", "entries": [], }) assert r.status_code == 404