generated from coulomb/repo-seed
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.
158 lines
5.4 KiB
Python
158 lines
5.4 KiB
Python
"""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"]) |