state-hub/tests/test_suggestions.py
tegwick fb363b37d3
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Multi-Context Image / build-and-push (push) Successful in 37s
feat(STATE-WP-0079): retire the suggestion-backlog surface (slice E1)
History is archived fleet-side, so the read surface has no remaining job.

- api/routers/suggestions.py: whole prefix 410s with a pointer to intake and
  to the archive; the router drops from 176 lines to a stub
- mcp_server: the 6 suggestion tools removed outright rather than stubbed --
  a retired tool that still appears in the tool list costs every agent
  session context on every call, which is the opposite of retiring it
- write_idempotency: 5 /suggestions rules dropped
- dashboard: suggestions.md deleted, nav entry removed, reference.md and
  wsjf-triage.md updated; docs/suggestions.md rewritten as archive pointer
- tests: two tests pinned the old read-live behaviour and now pin the
  retirement contract instead

Tables stay: they are retire/archive in SHR-INV-0001 and are captured by the
final dump at T06.

Untouched, and worth knowing during cutover: ui-feedback.md / todo.md
'suggestions' are Shift+click dashboard feedback backed by technical_debt, a
different entity that shares the word. E3 (dashboard-meta) is that page; its
owner is state-hub-until-cutover so it retires at the T06 window, not now.

Full suite 612 passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 08:11:14 +02:00

111 lines
4.8 KiB
Python

"""Suggestion (legacy) tests — CUST-WP-0061-T06, 2026-07-21.
The suggestions table/router is retired to read-only legacy: a fresh
`intake` work-record entity replaces it (canon/standards/
work-record-types_v0.1.md), not a rename. The 10 records still open at
retirement were migrated to file-backed intake records (see
the-custodian/intake-legacy-suggestions-migration.md) and declined here.
These tests verify the retirement itself: every mutation endpoint 410s;
GET/list still works for the historical record. Since creation is
retired, historical rows are seeded directly via the ORM/DB session
(bypassing the retired POST) rather than through the API.
"""
from __future__ import annotations
import uuid
import pytest
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
from tests.test_capability_requests import _create_domain
async def _seed_suggestion(test_engine, *, domain_slug: str, **overrides) -> str:
"""Insert a Suggestion row directly, bypassing the retired POST
endpoint — this is now the only way historical rows get created."""
from api.models.domain import Domain
from api.models.suggestion import Suggestion
from sqlalchemy import select
factory = async_sessionmaker(test_engine, class_=AsyncSession, expire_on_commit=False)
async with factory() as session:
domain_id = (
await session.execute(select(Domain.id).where(Domain.slug == domain_slug))
).scalar_one()
suggestion = Suggestion(
domain_id=domain_id,
title=overrides.get("title", "Legacy suggestion"),
description=overrides.get("description"),
origin=overrides.get("origin"),
origin_ref=overrides.get("origin_ref"),
base_value=overrides.get("base_value", 3.0),
job_size=overrides.get("job_size", 3.0),
)
session.add(suggestion)
await session.commit()
await session.refresh(suggestion)
return str(suggestion.id)
@pytest.mark.asyncio
class TestSuggestionsRetired:
async def test_create_returns_410(self, client):
await _create_domain(client, "custodian", "Custodian")
r = await client.post("/suggestions/", json={
"domain": "custodian",
"title": "New suggestion attempt",
})
assert r.status_code == 410
assert "intake" in r.json()["detail"].lower()
async def test_vet_returns_410(self, client, test_engine):
await _create_domain(client, "custodian", "Custodian")
sid = await _seed_suggestion(test_engine, domain_slug="custodian")
r = await client.post(f"/suggestions/{sid}/vet", json={"note": "x"})
assert r.status_code == 410
async def test_decline_returns_410(self, client, test_engine):
await _create_domain(client, "custodian", "Custodian")
sid = await _seed_suggestion(test_engine, domain_slug="custodian")
r = await client.post(f"/suggestions/{sid}/decline", json={"note": "x"})
assert r.status_code == 410
async def test_promote_returns_410(self, client, test_engine):
await _create_domain(client, "custodian", "Custodian")
sid = await _seed_suggestion(test_engine, domain_slug="custodian")
r = await client.post(f"/suggestions/{sid}/promote", json={})
assert r.status_code == 410
async def test_bump_relevance_returns_410(self, client, test_engine):
await _create_domain(client, "custodian", "Custodian")
sid = await _seed_suggestion(test_engine, domain_slug="custodian")
r = await client.post(f"/suggestions/{sid}/bump-relevance", json={})
assert r.status_code == 410
async def test_reads_are_retired_too(self, client, test_engine):
"""Slice E1 (STATE-WP-0079-T05, 2026-08-20): reads were kept live only
to keep the historical backlog reachable. That history is archived at
the-custodian/docs/archived-suggestion-backlog.md, so the read surface
is retired as well."""
await _create_domain(client, "custodian", "Custodian")
sid = await _seed_suggestion(
test_engine, domain_slug="custodian",
title="Historical gated-need signal",
origin="WARDEN-WP-0012", origin_ref="example",
)
for path in (f"/suggestions/{sid}", "/suggestions/", f"/suggestions/{sid}/notes"):
r = await client.get(path)
assert r.status_code == 410, path
async def test_unknown_id_also_410s(self, client):
"""The whole prefix is retired, so there is nothing left to 404 on."""
r = await client.get(f"/suggestions/{uuid.uuid4()}")
assert r.status_code == 410
async def test_retirement_detail_points_at_intake_and_archive(self, client):
r = await client.get("/suggestions/")
detail = r.json()["detail"]
assert "POST /intakes/" in detail
assert "archived-suggestion-backlog.md" in detail