7c6c906db2
Adds an optional "What will you be using this for?" capture as a sibling to the required justification on both propose surfaces, per roadmap #26. - propose-RFC modal (ProposeModal): optional textarea below the required "Why is this RFC needed?" pitch, labeled "What will you be using this RFC for? (optional)". - propose-PR modal (PRModal): optional textarea below the required description, labeled "What will you be using this change for?". - Backend: ProposeBody / OpenPRBody gain an optional `proposed_use_case` (NULL/omitted accepted, no min, 8000-char cap matching the existing free-text bound). Persisted to a new canonical side table `proposed_use_cases` keyed by PR number, mirrored onto the cache columns added by migration 021. Returned on the proposal list/detail, RFC detail (by slug), and PR detail endpoints. - Display: ProposalView, RFCView (main only), and PRView render the captured use case with a muted "left blank" treatment when NULL. - migration 021: nullable `proposed_use_case` on cached_rfcs/cached_prs plus the reconcile-proof `proposed_use_cases` truth table. - New vertical test_proposed_use_case_vertical: persists+returns when supplied, accepted as NULL/omitted, for both surfaces. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
162 lines
6.7 KiB
Python
162 lines
6.7 KiB
Python
"""End-to-end vertical for roadmap #26 (rfc-app v0.22.0): the optional
|
|
"What will you be using this for?" capture on the two propose surfaces.
|
|
|
|
Reuses the FakeGitea + session helpers from test_propose_vertical.py and
|
|
the active-RFC seed from test_rfc_view_vertical.py. Proves:
|
|
|
|
(a) propose-RFC persists and returns `proposed_use_case` when supplied,
|
|
and the value survives onto the merged super-draft's RFC view;
|
|
(b) propose-RFC accepts a NULL / omitted use case ("left blank");
|
|
(c) propose-PR persists and returns `proposed_use_case` when supplied,
|
|
and accepts a NULL / omitted one.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from test_propose_vertical import ( # noqa: F401
|
|
FakeGitea,
|
|
app_with_fake_gitea,
|
|
provision_user_row,
|
|
sign_in_as,
|
|
tmp_env,
|
|
)
|
|
from test_pr_flow_vertical import _cut_branch_and_accept_change
|
|
from test_rfc_view_vertical import SEED_BODY, seed_active_rfc
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# propose-RFC
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_propose_rfc_persists_and_returns_use_case(app_with_fake_gitea):
|
|
from fastapi.testclient import TestClient
|
|
|
|
app, _fake = app_with_fake_gitea
|
|
with TestClient(app) as client:
|
|
provision_user_row(user_id=2, login="alice", role="contributor")
|
|
provision_user_row(user_id=1, login="ben", role="owner")
|
|
sign_in_as(client, user_id=2, gitea_login="alice", display_name="Alice", role="contributor", email="alice@test")
|
|
|
|
r = client.post("/api/rfcs/propose", json={
|
|
"title": "Open Human Model",
|
|
"slug": "open-human-model",
|
|
"pitch": "A shared definition of what we mean by *human*.",
|
|
"tags": ["identity"],
|
|
"proposed_use_case": "Wiring OHM into the OpenXML consent surface.",
|
|
})
|
|
assert r.status_code == 200, r.text
|
|
pr_number = r.json()["pr_number"]
|
|
|
|
# The pending-idea list carries the use case.
|
|
items = client.get("/api/proposals").json()["items"]
|
|
assert items[0]["proposed_use_case"] == "Wiring OHM into the OpenXML consent surface."
|
|
|
|
# The pending-idea detail view carries it too.
|
|
proposal = client.get(f"/api/proposals/{pr_number}").json()
|
|
assert proposal["proposed_use_case"] == "Wiring OHM into the OpenXML consent surface."
|
|
|
|
# Merge as owner; the use case survives onto the RFC view (looked
|
|
# up by slug from the canonical side table, since the idea PR
|
|
# closes on merge).
|
|
sign_in_as(client, user_id=1, gitea_login="ben", display_name="Ben", role="owner", email="ben@test")
|
|
r = client.post(f"/api/proposals/{pr_number}/merge")
|
|
assert r.status_code == 200, r.text
|
|
|
|
view = client.get("/api/rfcs/open-human-model").json()
|
|
assert view["proposed_use_case"] == "Wiring OHM into the OpenXML consent surface."
|
|
|
|
|
|
def test_propose_rfc_use_case_optional(app_with_fake_gitea):
|
|
from fastapi.testclient import TestClient
|
|
|
|
app, _fake = app_with_fake_gitea
|
|
with TestClient(app) as client:
|
|
provision_user_row(user_id=3, login="carol", role="contributor")
|
|
sign_in_as(client, user_id=3, gitea_login="carol", display_name="Carol", role="contributor")
|
|
|
|
# Omitted entirely.
|
|
r = client.post("/api/rfcs/propose", json={
|
|
"title": "No Use Case", "slug": "no-use-case", "pitch": "p", "tags": [],
|
|
})
|
|
assert r.status_code == 200, r.text
|
|
pr_a = r.json()["pr_number"]
|
|
|
|
# Explicit null.
|
|
r = client.post("/api/rfcs/propose", json={
|
|
"title": "Null Use Case", "slug": "null-use-case", "pitch": "p",
|
|
"tags": [], "proposed_use_case": None,
|
|
})
|
|
assert r.status_code == 200, r.text
|
|
pr_b = r.json()["pr_number"]
|
|
|
|
# Blank/whitespace — treated as "left blank", no row written.
|
|
r = client.post("/api/rfcs/propose", json={
|
|
"title": "Blank Use Case", "slug": "blank-use-case", "pitch": "p",
|
|
"tags": [], "proposed_use_case": " ",
|
|
})
|
|
assert r.status_code == 200, r.text
|
|
pr_c = r.json()["pr_number"]
|
|
|
|
for pr in (pr_a, pr_b, pr_c):
|
|
assert client.get(f"/api/proposals/{pr}").json()["proposed_use_case"] is None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# propose-PR (against an active RFC)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_propose_pr_persists_and_returns_use_case(app_with_fake_gitea):
|
|
from fastapi.testclient import TestClient
|
|
|
|
app, fake = app_with_fake_gitea
|
|
with TestClient(app) as client:
|
|
provision_user_row(user_id=2, login="alice", role="contributor")
|
|
seed_active_rfc(fake, slug="ohm", title="OHM", body=SEED_BODY)
|
|
sign_in_as(client, user_id=2, gitea_login="alice", display_name="Alice", role="contributor")
|
|
|
|
branch, _ = _cut_branch_and_accept_change(
|
|
client, fake, slug="ohm",
|
|
original="Open Human Model is a framework for representing humans.",
|
|
proposed="Open Human Model is a framework for representing humans across systems.",
|
|
)
|
|
r = client.post(
|
|
f"/api/rfcs/ohm/branches/{branch}/open-pr",
|
|
json={
|
|
"title": "Tighten the opening",
|
|
"description": "Scope to systems.",
|
|
"proposed_use_case": "Building a cross-system consent registry.",
|
|
},
|
|
)
|
|
assert r.status_code == 200, r.text
|
|
pr_number = r.json()["pr_number"]
|
|
|
|
pr = client.get(f"/api/rfcs/ohm/prs/{pr_number}").json()
|
|
assert pr["proposed_use_case"] == "Building a cross-system consent registry."
|
|
|
|
|
|
def test_propose_pr_use_case_optional(app_with_fake_gitea):
|
|
from fastapi.testclient import TestClient
|
|
|
|
app, fake = app_with_fake_gitea
|
|
with TestClient(app) as client:
|
|
provision_user_row(user_id=2, login="alice", role="contributor")
|
|
seed_active_rfc(fake, slug="ohm", title="OHM", body=SEED_BODY)
|
|
sign_in_as(client, user_id=2, gitea_login="alice", display_name="Alice", role="contributor")
|
|
|
|
branch, _ = _cut_branch_and_accept_change(
|
|
client, fake, slug="ohm",
|
|
original="It defines consent, trait, and agency in compatible terms.",
|
|
proposed="It defines consent, trait, harm, and agency in compatible terms.",
|
|
)
|
|
# No proposed_use_case key at all.
|
|
r = client.post(
|
|
f"/api/rfcs/ohm/branches/{branch}/open-pr",
|
|
json={"title": "Add harm", "description": "Name harm explicitly."},
|
|
)
|
|
assert r.status_code == 200, r.text
|
|
pr_number = r.json()["pr_number"]
|
|
|
|
pr = client.get(f"/api/rfcs/ohm/prs/{pr_number}").json()
|
|
assert pr["proposed_use_case"] is None
|