generated from coulomb/repo-seed
Finish CUST-WP-0011 and implement STATE-WP-0061 suggestion backlog.
Add persisted Suggestion entities with WSJF ranking, relevance bumps, REST/MCP write surfaces, /suggestions dashboard page, and daily triage digest integration. Document the cluster operating model, archive the completed migration workplan, and seed WARDEN-WP-0012 routing scenarios.
This commit is contained in:
158
tests/test_suggestions.py
Normal file
158
tests/test_suggestions.py
Normal file
@@ -0,0 +1,158 @@
|
||||
"""Demand-weighted suggestion backlog tests (STATE-WP-0061)."""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from tests.conftest import create_test_repo, create_test_workplan
|
||||
from tests.test_capability_requests import _create_domain, _create_topic
|
||||
|
||||
|
||||
async def _create_suggestion(client, **kwargs):
|
||||
payload = {
|
||||
"domain": "custodian",
|
||||
"title": "Issue-core ingestion API key path",
|
||||
"description": "OpenBao KV path for issue-core ingestion",
|
||||
"origin_ref": "issue-core-ingestion-api-key",
|
||||
"base_value": 4.0,
|
||||
"job_size": 2.0,
|
||||
}
|
||||
payload.update(kwargs)
|
||||
r = await client.post("/suggestions/", json=payload)
|
||||
assert r.status_code == 201, r.text
|
||||
return r.json()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_list_and_wsjf_ranking(client):
|
||||
await _create_domain(client, "custodian", "Custodian")
|
||||
low = await _create_suggestion(
|
||||
client,
|
||||
title="Low priority path",
|
||||
origin_ref="low-priority",
|
||||
base_value=1.0,
|
||||
job_size=5.0,
|
||||
)
|
||||
high = await _create_suggestion(
|
||||
client,
|
||||
title="High priority path",
|
||||
origin_ref="high-priority",
|
||||
base_value=5.0,
|
||||
job_size=1.0,
|
||||
)
|
||||
|
||||
r = await client.get("/suggestions/?rank=wsjf")
|
||||
assert r.status_code == 200
|
||||
ranked = r.json()
|
||||
assert ranked[0]["id"] == high["id"]
|
||||
assert ranked[0]["wsjf"] > ranked[1]["wsjf"]
|
||||
|
||||
await client.post(
|
||||
f"/suggestions/{low['id']}/bump-relevance",
|
||||
json={"reason": "hit again", "author": "agent-a"},
|
||||
)
|
||||
await client.post(
|
||||
f"/suggestions/{low['id']}/bump-relevance",
|
||||
json={"reason": "hit again", "author": "agent-b"},
|
||||
)
|
||||
r2 = await client.get(f"/suggestions/{low['id']}")
|
||||
assert r2.json()["relevance"] == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bump_relevance_debounces_duplicate_explicit_bumps(client):
|
||||
await _create_domain(client, "custodian", "Custodian")
|
||||
suggestion = await _create_suggestion(client)
|
||||
first = await client.post(
|
||||
f"/suggestions/{suggestion['id']}/bump-relevance",
|
||||
json={"reason": "routing gap", "author": "codex"},
|
||||
)
|
||||
second = await client.post(
|
||||
f"/suggestions/{suggestion['id']}/bump-relevance",
|
||||
json={"reason": "routing gap", "author": "codex"},
|
||||
)
|
||||
assert first.status_code == 200
|
||||
assert second.status_code == 200
|
||||
refreshed = await client.get(f"/suggestions/{suggestion['id']}")
|
||||
assert refreshed.json()["relevance"] == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_vet_decline_and_promote_flow(client):
|
||||
await _create_domain(client, "custodian", "Custodian")
|
||||
topic = await _create_topic(client, "custodian")
|
||||
repo = await create_test_repo(client, domain_slug="custodian", slug="state-hub")
|
||||
workplan = await create_test_workplan(
|
||||
client, repo_id=repo["id"], topic_id=topic["id"], slug="state-wp-0061", title="WP-0061",
|
||||
)
|
||||
|
||||
suggestion = await _create_suggestion(
|
||||
client,
|
||||
workplan_id=workplan["id"],
|
||||
title="Promotable gated need",
|
||||
origin_ref="promote-me",
|
||||
)
|
||||
|
||||
vet = await client.post(
|
||||
f"/suggestions/{suggestion['id']}/vet",
|
||||
json={"note": "Vetted as requirement", "author": "codex"},
|
||||
)
|
||||
assert vet.status_code == 200
|
||||
assert vet.json()["stage"] == "requirement"
|
||||
|
||||
bad_promote = await client.post(
|
||||
f"/suggestions/{suggestion['id']}/promote",
|
||||
json={"note": "too early"},
|
||||
)
|
||||
assert bad_promote.status_code == 200
|
||||
|
||||
promoted = bad_promote.json()
|
||||
assert promoted["stage"] == "promoted"
|
||||
assert promoted["promoted_task_id"] is not None
|
||||
|
||||
task = await client.get(f"/tasks/{promoted['promoted_task_id']}")
|
||||
assert task.status_code == 200
|
||||
assert task.json()["title"] == "Promotable gated need"
|
||||
|
||||
fresh = await _create_suggestion(client, title="Decline me", origin_ref="decline-me")
|
||||
declined = await client.post(
|
||||
f"/suggestions/{fresh['id']}/decline",
|
||||
json={"note": "Not needed", "author": "codex"},
|
||||
)
|
||||
assert declined.status_code == 200
|
||||
assert declined.json()["stage"] == "declined"
|
||||
|
||||
illegal = await client.post(
|
||||
f"/suggestions/{fresh['id']}/vet",
|
||||
json={"note": "too late"},
|
||||
)
|
||||
assert illegal.status_code == 409
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_next_steps_surfaces_and_bumps_suggestions(client):
|
||||
await _create_domain(client, "custodian", "Custodian")
|
||||
await _create_suggestion(client, title="Surfaced need", origin_ref="surfaced-need")
|
||||
before = await client.get("/suggestions/?origin_ref=surfaced-need")
|
||||
# no filter by origin_ref on list - get all and find
|
||||
all_items = await client.get("/suggestions/")
|
||||
item = next(i for i in all_items.json() if i["origin_ref"] == "surfaced-need")
|
||||
assert item["relevance"] == 0
|
||||
|
||||
steps = await client.get("/state/next_steps")
|
||||
assert steps.status_code == 200
|
||||
payload = steps.json()
|
||||
assert any(s["type"] == "open_suggestion" for s in payload)
|
||||
|
||||
after = await client.get(f"/suggestions/{item['id']}")
|
||||
assert after.json()["relevance"] >= 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_summary_includes_ranked_suggestions(client):
|
||||
await _create_domain(client, "custodian", "Custodian")
|
||||
await _create_suggestion(client, title="Summary ranked", origin_ref="summary-ranked")
|
||||
summary = await client.get("/state/summary")
|
||||
assert summary.status_code == 200
|
||||
data = summary.json()
|
||||
assert "ranked_suggestions" in data
|
||||
assert any(s["origin_ref"] == "summary-ranked" for s in data["ranked_suggestions"])
|
||||
Reference in New Issue
Block a user