generated from coulomb/repo-seed
feat(tests): pytest-asyncio test suite — 119 tests across 3 modules
Infrastructure (T01): - tests/conftest.py: sync schema setup (psycopg2), per-test table truncation, async ASGI client with get_session override - pyproject.toml: [tool.pytest.ini_options] asyncio_mode=auto - Makefile: make test target with TEST_DATABASE_URL Core router tests (T02): 19 tests - domains, topics, workstreams, tasks, decisions + state summary - Caught real bug: topic router missing duplicate-slug 409 guard (fixed) TD/EP/Contributions/SBOM tests (T03): 10 tests - CRUD + status transitions + lifecycle guard + SBOM ingest MCP smoke tests (T04): 12 tests - get_state_summary, create_task, update_task_status, add_progress_event, flag_for_human HTTP shapes CI gate (T05): make test documented in CLAUDE.md session protocol Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
210
tests/test_routers_td_ep_contrib.py
Normal file
210
tests/test_routers_td_ep_contrib.py
Normal file
@@ -0,0 +1,210 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user