diff --git a/tests/__pycache__/__init__.cpython-312.pyc b/tests/__pycache__/__init__.cpython-312.pyc index c0e310b..c6d0d5a 100644 Binary files a/tests/__pycache__/__init__.cpython-312.pyc and b/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/tests/__pycache__/conftest.cpython-312-pytest-9.0.2.pyc b/tests/__pycache__/conftest.cpython-312-pytest-9.0.2.pyc index 4f2086f..93fb7d7 100644 Binary files a/tests/__pycache__/conftest.cpython-312-pytest-9.0.2.pyc and b/tests/__pycache__/conftest.cpython-312-pytest-9.0.2.pyc differ diff --git a/tests/__pycache__/test_propose.cpython-312-pytest-9.0.2.pyc b/tests/__pycache__/test_propose.cpython-312-pytest-9.0.2.pyc index b39cff1..2c985ca 100644 Binary files a/tests/__pycache__/test_propose.cpython-312-pytest-9.0.2.pyc and b/tests/__pycache__/test_propose.cpython-312-pytest-9.0.2.pyc differ diff --git a/tests/__pycache__/test_tasks.cpython-312-pytest-9.0.2.pyc b/tests/__pycache__/test_tasks.cpython-312-pytest-9.0.2.pyc index 3ecf674..eaf0b25 100644 Binary files a/tests/__pycache__/test_tasks.cpython-312-pytest-9.0.2.pyc and b/tests/__pycache__/test_tasks.cpython-312-pytest-9.0.2.pyc differ diff --git a/tests/conftest.py b/tests/conftest.py index 4894da3..f404fc9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -24,6 +24,8 @@ import app.models.task # noqa: F401 import app.models.role_permission # noqa: F401 import app.models.activity # noqa: F401 import app.models.propose # noqa: F401 +import app.models.essential # noqa: F401 +import app.models.proposal # noqa: F401 try: import app.models.apikey # noqa: F401 except ImportError: diff --git a/tests/test_propose.py b/tests/test_propose.py index 97469e2..ca7b0c9 100644 --- a/tests/test_propose.py +++ b/tests/test_propose.py @@ -1,15 +1,16 @@ -"""P13.3 — Propose backend tests. +"""Proposal backend tests (renamed from Propose). Covers: - CRUD: create, list, get, update - propose_code per-project incrementing -- accept → auto-generate feature story task + feat_task_id +- accept → auto-generate story tasks from Essentials (feat_task_id deprecated per BE-PR-010) - accept with non-open milestone → fail - reject → status change - rejected → reopen back to open -- feat_task_id cannot be set manually -- edit restrictions (only open proposes editable) +- feat_task_id cannot be set manually (deprecated, read-only) +- edit restrictions (only open proposals editable) - permission checks for accept/reject/reopen +- Legacy /proposes endpoint still works """ import pytest from app.models.milestone import MilestoneStatus @@ -20,19 +21,47 @@ from app.models.task import TaskStatus # Helpers # --------------------------------------------------------------------------- -def _propose_url(project_id: int, propose_id: int | None = None) -> str: +def _proposal_url(project_id: int, proposal_id: int | None = None) -> str: + """Canonical /proposals URL.""" + base = f"/projects/{project_id}/proposals" + return f"{base}/{proposal_id}" if proposal_id else base + + +def _legacy_propose_url(project_id: int, propose_id: int | None = None) -> str: + """Legacy /proposes URL for backward-compat tests.""" base = f"/projects/{project_id}/proposes" return f"{base}/{propose_id}" if propose_id else base +def _essential_url(project_id: int, proposal_id: int) -> str: + """Essential CRUD URL under a Proposal.""" + return f"/projects/{project_id}/proposals/{proposal_id}/essentials" + + +def _add_essential(client, project_id: int, proposal_id: int, headers, *, + title: str = "Default Essential", type: str = "feature", + description: str | None = None) -> dict: + """Helper: create an Essential under a Proposal (required for accept).""" + body = {"title": title, "type": type} + if description: + body["description"] = description + resp = client.post( + _essential_url(project_id, proposal_id), + json=body, + headers=headers, + ) + assert resp.status_code == 201, f"Failed to create essential: {resp.text}" + return resp.json() + + # =========================================================================== # CRUD # =========================================================================== -class TestProposeCRUD: +class TestProposalCRUD: """Basic create / list / get / update.""" - def test_create_propose( + def test_create_proposal( self, client, db, make_user, make_project, seed_roles_and_permissions, make_member, auth_header, ): admin_role, mgr_role, dev_role = seed_roles_and_permissions @@ -41,7 +70,7 @@ class TestProposeCRUD: make_member(project.id, user.id, dev_role.id) resp = client.post( - _propose_url(project.id), + _proposal_url(project.id), json={"title": "New Feature Idea", "description": "Some details"}, headers=auth_header(user), ) @@ -50,9 +79,9 @@ class TestProposeCRUD: assert data["title"] == "New Feature Idea" assert data["status"] == "open" assert data["propose_code"].startswith("PROJ:P") - assert data["feat_task_id"] is None + assert data["feat_task_id"] is None # DEPRECATED (BE-PR-010): always None for new proposals - def test_list_proposes( + def test_list_proposals( self, client, db, make_user, make_project, seed_roles_and_permissions, make_member, auth_header, ): admin_role, mgr_role, dev_role = seed_roles_and_permissions @@ -60,15 +89,15 @@ class TestProposeCRUD: project = make_project(owner_id=user.id) make_member(project.id, user.id, dev_role.id) - # Create two proposes - client.post(_propose_url(project.id), json={"title": "P1"}, headers=auth_header(user)) - client.post(_propose_url(project.id), json={"title": "P2"}, headers=auth_header(user)) + # Create two proposals + client.post(_proposal_url(project.id), json={"title": "P1"}, headers=auth_header(user)) + client.post(_proposal_url(project.id), json={"title": "P2"}, headers=auth_header(user)) - resp = client.get(_propose_url(project.id), headers=auth_header(user)) + resp = client.get(_proposal_url(project.id), headers=auth_header(user)) assert resp.status_code == 200 assert len(resp.json()) == 2 - def test_get_propose( + def test_get_proposal( self, client, db, make_user, make_project, seed_roles_and_permissions, make_member, auth_header, ): admin_role, mgr_role, dev_role = seed_roles_and_permissions @@ -76,14 +105,14 @@ class TestProposeCRUD: project = make_project(owner_id=user.id) make_member(project.id, user.id, dev_role.id) - create_resp = client.post(_propose_url(project.id), json={"title": "P1"}, headers=auth_header(user)) - propose_id = create_resp.json()["id"] + create_resp = client.post(_proposal_url(project.id), json={"title": "P1"}, headers=auth_header(user)) + proposal_id = create_resp.json()["id"] - resp = client.get(_propose_url(project.id, propose_id), headers=auth_header(user)) + resp = client.get(_proposal_url(project.id, proposal_id), headers=auth_header(user)) assert resp.status_code == 200 assert resp.json()["title"] == "P1" - def test_update_propose_open( + def test_update_proposal_open( self, client, db, make_user, make_project, seed_roles_and_permissions, make_member, auth_header, ): admin_role, mgr_role, dev_role = seed_roles_and_permissions @@ -91,11 +120,11 @@ class TestProposeCRUD: project = make_project(owner_id=user.id) make_member(project.id, user.id, dev_role.id) - create_resp = client.post(_propose_url(project.id), json={"title": "Old"}, headers=auth_header(user)) - propose_id = create_resp.json()["id"] + create_resp = client.post(_proposal_url(project.id), json={"title": "Old"}, headers=auth_header(user)) + proposal_id = create_resp.json()["id"] resp = client.patch( - _propose_url(project.id, propose_id), + _proposal_url(project.id, proposal_id), json={"title": "New Title", "description": "Updated"}, headers=auth_header(user), ) @@ -105,11 +134,11 @@ class TestProposeCRUD: # =========================================================================== -# Propose Code +# Proposal Code # =========================================================================== -class TestProposeCode: - """P1.4 — propose_code increments per project independently.""" +class TestProposalCode: + """propose_code increments per project independently.""" def test_code_increments_per_project( self, client, db, make_user, make_project, seed_roles_and_permissions, make_member, auth_header, @@ -122,11 +151,11 @@ class TestProposeCode: make_member(proj_b.id, user.id, dev_role.id) # Create 2 in ALPHA - r1 = client.post(_propose_url(proj_a.id), json={"title": "A1"}, headers=auth_header(user)) - r2 = client.post(_propose_url(proj_a.id), json={"title": "A2"}, headers=auth_header(user)) + r1 = client.post(_proposal_url(proj_a.id), json={"title": "A1"}, headers=auth_header(user)) + r2 = client.post(_proposal_url(proj_a.id), json={"title": "A2"}, headers=auth_header(user)) # Create 1 in BETA - r3 = client.post(_propose_url(proj_b.id), json={"title": "B1"}, headers=auth_header(user)) + r3 = client.post(_proposal_url(proj_b.id), json={"title": "B1"}, headers=auth_header(user)) code1 = r1.json()["propose_code"] code2 = r2.json()["propose_code"] @@ -144,7 +173,7 @@ class TestProposeCode: # =========================================================================== class TestAccept: - """P6.2 — accept propose → create feature story task.""" + """accept proposal → create feature story task.""" def test_accept_success( self, client, db, make_user, make_project, make_milestone, seed_roles_and_permissions, make_member, auth_header, @@ -157,33 +186,30 @@ class TestAccept: ms = make_milestone(project.id, mgr.id, status=MilestoneStatus.OPEN) create_resp = client.post( - _propose_url(project.id), + _proposal_url(project.id), json={"title": "Cool Feature", "description": "Do something cool"}, headers=auth_header(mgr), ) - propose_id = create_resp.json()["id"] + proposal_id = create_resp.json()["id"] + + # New accept flow requires at least one Essential (BE-PR-007) + _add_essential(client, project.id, proposal_id, auth_header(mgr), + title="Cool Feature", type="feature", description="Do something cool") resp = client.post( - _propose_url(project.id, propose_id) + "/accept", + _proposal_url(project.id, proposal_id) + "/accept", json={"milestone_id": ms.id}, headers=auth_header(mgr), ) assert resp.status_code == 200 data = resp.json() assert data["status"] == "accepted" - assert data["feat_task_id"] is not None + # BE-PR-010: feat_task_id is no longer written by new accept flow + assert data["feat_task_id"] is None - # Verify the generated task exists - from app.models.task import Task - task = db.query(Task).filter(Task.id == int(data["feat_task_id"])).first() - assert task is not None - assert task.title == "Cool Feature" - assert task.description == "Do something cool" - assert task.task_type == "story" - assert task.task_subtype == "feature" - task_status = task.status.value if hasattr(task.status, "value") else task.status - assert task_status == "pending" - assert task.milestone_id == ms.id + # Tasks are tracked via generated_tasks (source_proposal_id) + assert "generated_tasks" in data + assert len(data["generated_tasks"]) >= 1 def test_accept_non_open_milestone_fails( self, client, db, make_user, make_project, make_milestone, seed_roles_and_permissions, make_member, auth_header, @@ -196,14 +222,17 @@ class TestAccept: ms = make_milestone(project.id, mgr.id, status=MilestoneStatus.FREEZE) create_resp = client.post( - _propose_url(project.id), + _proposal_url(project.id), json={"title": "Feature X"}, headers=auth_header(mgr), ) - propose_id = create_resp.json()["id"] + proposal_id = create_resp.json()["id"] + + # Add Essential (required for accept per BE-PR-007) + _add_essential(client, project.id, proposal_id, auth_header(mgr)) resp = client.post( - _propose_url(project.id, propose_id) + "/accept", + _proposal_url(project.id, proposal_id) + "/accept", json={"milestone_id": ms.id}, headers=auth_header(mgr), ) @@ -221,28 +250,36 @@ class TestAccept: ms = make_milestone(project.id, mgr.id, status=MilestoneStatus.OPEN) create_resp = client.post( - _propose_url(project.id), json={"title": "F"}, headers=auth_header(mgr), + _proposal_url(project.id), json={"title": "F"}, headers=auth_header(mgr), ) - propose_id = create_resp.json()["id"] + proposal_id = create_resp.json()["id"] + + # Add Essential (required for accept per BE-PR-007) + _add_essential(client, project.id, proposal_id, auth_header(mgr)) # First accept client.post( - _propose_url(project.id, propose_id) + "/accept", + _proposal_url(project.id, proposal_id) + "/accept", json={"milestone_id": ms.id}, headers=auth_header(mgr), ) # Second accept should fail resp = client.post( - _propose_url(project.id, propose_id) + "/accept", + _proposal_url(project.id, proposal_id) + "/accept", json={"milestone_id": ms.id}, headers=auth_header(mgr), ) assert resp.status_code == 400 - def test_accept_auto_fills_feat_task_id( + def test_accept_does_not_write_feat_task_id( self, client, db, make_user, make_project, make_milestone, seed_roles_and_permissions, make_member, auth_header, ): + """BE-PR-010: new accept flow does NOT populate feat_task_id. + + feat_task_id is deprecated; tasks are now tracked via + Task.source_proposal_id / source_essential_id. + """ admin_role, mgr_role, dev_role = seed_roles_and_permissions mgr = make_user() project = make_project(owner_id=mgr.id) @@ -251,21 +288,25 @@ class TestAccept: ms = make_milestone(project.id, mgr.id, status=MilestoneStatus.OPEN) create_resp = client.post( - _propose_url(project.id), json={"title": "F"}, headers=auth_header(mgr), + _proposal_url(project.id), json={"title": "F"}, headers=auth_header(mgr), ) - propose_id = create_resp.json()["id"] + proposal_id = create_resp.json()["id"] + + # Add Essential (required for accept per BE-PR-007) + _add_essential(client, project.id, proposal_id, auth_header(mgr)) resp = client.post( - _propose_url(project.id, propose_id) + "/accept", + _proposal_url(project.id, proposal_id) + "/accept", json={"milestone_id": ms.id}, headers=auth_header(mgr), ) data = resp.json() - assert data["feat_task_id"] is not None + # feat_task_id should remain None — deprecated field + assert data["feat_task_id"] is None # Re-fetch to confirm persistence - get_resp = client.get(_propose_url(project.id, propose_id), headers=auth_header(mgr)) - assert get_resp.json()["feat_task_id"] == data["feat_task_id"] + get_resp = client.get(_proposal_url(project.id, proposal_id), headers=auth_header(mgr)) + assert get_resp.json()["feat_task_id"] is None def test_accept_no_permission_fails( self, client, db, make_user, make_project, make_milestone, seed_roles_and_permissions, make_member, auth_header, @@ -280,15 +321,18 @@ class TestAccept: ms = make_milestone(project.id, owner.id, status=MilestoneStatus.OPEN) - # Dev creates the propose + # Dev creates the proposal create_resp = client.post( - _propose_url(project.id), json={"title": "F"}, headers=auth_header(dev_user), + _proposal_url(project.id), json={"title": "F"}, headers=auth_header(dev_user), ) - propose_id = create_resp.json()["id"] + proposal_id = create_resp.json()["id"] + + # Add Essential (required for accept per BE-PR-007) + _add_essential(client, project.id, proposal_id, auth_header(dev_user)) # Dev tries to accept — should fail resp = client.post( - _propose_url(project.id, propose_id) + "/accept", + _proposal_url(project.id, proposal_id) + "/accept", json={"milestone_id": ms.id}, headers=auth_header(dev_user), ) @@ -300,7 +344,7 @@ class TestAccept: # =========================================================================== class TestReject: - """P6.3 — reject propose.""" + """reject proposal.""" def test_reject_success( self, client, db, make_user, make_project, seed_roles_and_permissions, make_member, auth_header, @@ -311,12 +355,12 @@ class TestReject: make_member(project.id, mgr.id, mgr_role.id) create_resp = client.post( - _propose_url(project.id), json={"title": "F"}, headers=auth_header(mgr), + _proposal_url(project.id), json={"title": "F"}, headers=auth_header(mgr), ) - propose_id = create_resp.json()["id"] + proposal_id = create_resp.json()["id"] resp = client.post( - _propose_url(project.id, propose_id) + "/reject", + _proposal_url(project.id, proposal_id) + "/reject", json={"reason": "Not needed"}, headers=auth_header(mgr), ) @@ -334,20 +378,23 @@ class TestReject: ms = make_milestone(project.id, mgr.id, status=MilestoneStatus.OPEN) create_resp = client.post( - _propose_url(project.id), json={"title": "F"}, headers=auth_header(mgr), + _proposal_url(project.id), json={"title": "F"}, headers=auth_header(mgr), ) - propose_id = create_resp.json()["id"] + proposal_id = create_resp.json()["id"] + + # Add Essential (required for accept per BE-PR-007) + _add_essential(client, project.id, proposal_id, auth_header(mgr)) # Accept first client.post( - _propose_url(project.id, propose_id) + "/accept", + _proposal_url(project.id, proposal_id) + "/accept", json={"milestone_id": ms.id}, headers=auth_header(mgr), ) # Now reject should fail resp = client.post( - _propose_url(project.id, propose_id) + "/reject", + _proposal_url(project.id, proposal_id) + "/reject", json={"reason": "Changed mind"}, headers=auth_header(mgr), ) @@ -364,12 +411,12 @@ class TestReject: make_member(project.id, dev_user.id, dev_role.id) create_resp = client.post( - _propose_url(project.id), json={"title": "F"}, headers=auth_header(dev_user), + _proposal_url(project.id), json={"title": "F"}, headers=auth_header(dev_user), ) - propose_id = create_resp.json()["id"] + proposal_id = create_resp.json()["id"] resp = client.post( - _propose_url(project.id, propose_id) + "/reject", + _proposal_url(project.id, proposal_id) + "/reject", json={"reason": "nah"}, headers=auth_header(dev_user), ) @@ -381,7 +428,7 @@ class TestReject: # =========================================================================== class TestReopen: - """P6.4 — reopen rejected propose.""" + """reopen rejected proposal.""" def test_reopen_success( self, client, db, make_user, make_project, seed_roles_and_permissions, make_member, auth_header, @@ -392,20 +439,20 @@ class TestReopen: make_member(project.id, mgr.id, mgr_role.id) create_resp = client.post( - _propose_url(project.id), json={"title": "F"}, headers=auth_header(mgr), + _proposal_url(project.id), json={"title": "F"}, headers=auth_header(mgr), ) - propose_id = create_resp.json()["id"] + proposal_id = create_resp.json()["id"] # Reject first client.post( - _propose_url(project.id, propose_id) + "/reject", + _proposal_url(project.id, proposal_id) + "/reject", json={"reason": "wait"}, headers=auth_header(mgr), ) # Reopen resp = client.post( - _propose_url(project.id, propose_id) + "/reopen", + _proposal_url(project.id, proposal_id) + "/reopen", headers=auth_header(mgr), ) assert resp.status_code == 200 @@ -420,13 +467,13 @@ class TestReopen: make_member(project.id, mgr.id, mgr_role.id) create_resp = client.post( - _propose_url(project.id), json={"title": "F"}, headers=auth_header(mgr), + _proposal_url(project.id), json={"title": "F"}, headers=auth_header(mgr), ) - propose_id = create_resp.json()["id"] + proposal_id = create_resp.json()["id"] - # Try reopen on open propose — should fail + # Try reopen on open proposal — should fail resp = client.post( - _propose_url(project.id, propose_id) + "/reopen", + _proposal_url(project.id, proposal_id) + "/reopen", headers=auth_header(mgr), ) assert resp.status_code == 400 @@ -442,31 +489,31 @@ class TestReopen: make_member(project.id, dev_user.id, dev_role.id) create_resp = client.post( - _propose_url(project.id), json={"title": "F"}, headers=auth_header(dev_user), + _proposal_url(project.id), json={"title": "F"}, headers=auth_header(dev_user), ) - propose_id = create_resp.json()["id"] + proposal_id = create_resp.json()["id"] # Owner rejects client.post( - _propose_url(project.id, propose_id) + "/reject", + _proposal_url(project.id, proposal_id) + "/reject", json={"reason": "nah"}, headers=auth_header(owner), ) # Dev tries to reopen — should fail resp = client.post( - _propose_url(project.id, propose_id) + "/reopen", + _proposal_url(project.id, proposal_id) + "/reopen", headers=auth_header(dev_user), ) assert resp.status_code == 403 # =========================================================================== -# feat_task_id protection +# feat_task_id protection (DEPRECATED per BE-PR-010) # =========================================================================== class TestFeatTaskIdProtection: - """P6.5 — feat_task_id is server-side only, cannot be set by client.""" + """feat_task_id is deprecated and read-only; cannot be set by client.""" def test_update_cannot_set_feat_task_id( self, client, db, make_user, make_project, seed_roles_and_permissions, make_member, auth_header, @@ -477,18 +524,18 @@ class TestFeatTaskIdProtection: make_member(project.id, user.id, dev_role.id) create_resp = client.post( - _propose_url(project.id), json={"title": "F"}, headers=auth_header(user), + _proposal_url(project.id), json={"title": "F"}, headers=auth_header(user), ) - propose_id = create_resp.json()["id"] + proposal_id = create_resp.json()["id"] # Try to set feat_task_id via PATCH resp = client.patch( - _propose_url(project.id, propose_id), + _proposal_url(project.id, proposal_id), json={"feat_task_id": "999"}, headers=auth_header(user), ) assert resp.status_code == 200 - # feat_task_id should still be None (server ignores it) + # feat_task_id should still be None — deprecated, read-only (BE-PR-010) assert resp.json()["feat_task_id"] is None @@ -497,9 +544,9 @@ class TestFeatTaskIdProtection: # =========================================================================== class TestEditRestrictions: - """Propose editing is only allowed in open status.""" + """Proposal editing is only allowed in open status.""" - def test_edit_accepted_propose_fails( + def test_edit_accepted_proposal_fails( self, client, db, make_user, make_project, make_milestone, seed_roles_and_permissions, make_member, auth_header, ): admin_role, mgr_role, dev_role = seed_roles_and_permissions @@ -510,27 +557,30 @@ class TestEditRestrictions: ms = make_milestone(project.id, mgr.id, status=MilestoneStatus.OPEN) create_resp = client.post( - _propose_url(project.id), json={"title": "F"}, headers=auth_header(mgr), + _proposal_url(project.id), json={"title": "F"}, headers=auth_header(mgr), ) - propose_id = create_resp.json()["id"] + proposal_id = create_resp.json()["id"] + + # Add Essential (required for accept per BE-PR-007) + _add_essential(client, project.id, proposal_id, auth_header(mgr)) # Accept client.post( - _propose_url(project.id, propose_id) + "/accept", + _proposal_url(project.id, proposal_id) + "/accept", json={"milestone_id": ms.id}, headers=auth_header(mgr), ) # Try to edit resp = client.patch( - _propose_url(project.id, propose_id), + _proposal_url(project.id, proposal_id), json={"title": "Changed"}, headers=auth_header(mgr), ) assert resp.status_code == 400 assert "open" in resp.json()["detail"].lower() - def test_edit_rejected_propose_fails( + def test_edit_rejected_proposal_fails( self, client, db, make_user, make_project, seed_roles_and_permissions, make_member, auth_header, ): admin_role, mgr_role, dev_role = seed_roles_and_permissions @@ -539,21 +589,79 @@ class TestEditRestrictions: make_member(project.id, mgr.id, mgr_role.id) create_resp = client.post( - _propose_url(project.id), json={"title": "F"}, headers=auth_header(mgr), + _proposal_url(project.id), json={"title": "F"}, headers=auth_header(mgr), ) - propose_id = create_resp.json()["id"] + proposal_id = create_resp.json()["id"] # Reject client.post( - _propose_url(project.id, propose_id) + "/reject", + _proposal_url(project.id, proposal_id) + "/reject", json={"reason": "no"}, headers=auth_header(mgr), ) # Try to edit resp = client.patch( - _propose_url(project.id, propose_id), + _proposal_url(project.id, proposal_id), json={"title": "Changed"}, headers=auth_header(mgr), ) assert resp.status_code == 400 + + +# =========================================================================== +# Legacy /proposes endpoint backward compatibility +# =========================================================================== + +class TestLegacyProposeEndpoint: + """Verify the old /proposes URL still works.""" + + def test_legacy_create_and_list( + self, client, db, make_user, make_project, seed_roles_and_permissions, make_member, auth_header, + ): + admin_role, mgr_role, dev_role = seed_roles_and_permissions + user = make_user() + project = make_project(owner_id=user.id, project_code="LEG") + make_member(project.id, user.id, dev_role.id) + + # Create via legacy endpoint + resp = client.post( + _legacy_propose_url(project.id), + json={"title": "Legacy Proposal"}, + headers=auth_header(user), + ) + assert resp.status_code == 201 + assert resp.json()["title"] == "Legacy Proposal" + + # List via legacy endpoint + resp = client.get(_legacy_propose_url(project.id), headers=auth_header(user)) + assert resp.status_code == 200 + assert len(resp.json()) >= 1 + + def test_legacy_get_and_update( + self, client, db, make_user, make_project, seed_roles_and_permissions, make_member, auth_header, + ): + admin_role, mgr_role, dev_role = seed_roles_and_permissions + user = make_user() + project = make_project(owner_id=user.id) + make_member(project.id, user.id, dev_role.id) + + # Create via new endpoint + create_resp = client.post( + _proposal_url(project.id), json={"title": "Cross"}, headers=auth_header(user), + ) + pid = create_resp.json()["id"] + + # Get via legacy + resp = client.get(_legacy_propose_url(project.id, pid), headers=auth_header(user)) + assert resp.status_code == 200 + assert resp.json()["title"] == "Cross" + + # Update via legacy + resp = client.patch( + _legacy_propose_url(project.id, pid), + json={"title": "Updated Cross"}, + headers=auth_header(user), + ) + assert resp.status_code == 200 + assert resp.json()["title"] == "Updated Cross"