"""ORCH-066: the meaningful Plane status model (layer B) — unit coverage. These tests pin the layer-B behaviour WITHOUT touching layer A (the stage machine). httpx is mocked; no network. * TC-03 (AC-3) — the analyst start/resume indicates `Analysis`, not In Progress. * TC-05 (AC-5) — entering the `review` stage indicates `Code-Review`. * TC-14 (AC-14) — set_issue_needs_input is unchanged (still PATCHes Needs Input). * TC-22 (AC-21) — STAGE_TRANSITIONS (layer A) is byte-identical (explicit pin). * TC-23 (AC-22) — QG_CHECKS registry + check_deploy_status contract unchanged. """ import os os.environ.setdefault("ORCH_PLANE_API_TOKEN", "test-token") os.environ.setdefault("ORCH_GITEA_TOKEN", "test-token") from unittest.mock import patch, MagicMock # noqa: E402 from src import plane_sync as PS # noqa: E402 # A per-project state map that DEFINES the new ORCH-066 statuses with distinct # UUIDs, so we can prove the dedicated status (not the base alias) is used. _STATES_WITH_NEW = { "in_progress": "ip-uuid", "review": "review-uuid", "in_review": "inrev-uuid", "needs_input": "ni-uuid", "done": "done-uuid", "analysis": "analysis-uuid", "code_review": "codereview-uuid", "awaiting_deploy": "awaiting-uuid", "deploying": "deploying-uuid", "monitoring": "monitoring-uuid", } def _patch_resolve(states): """Patch find_issue_id + _resolve_project_id + get_project_states so a set_issue_* helper reaches the PATCH with a known per-project state map.""" return ( patch("src.plane_sync.httpx.patch"), patch("src.plane_sync.find_issue_id", return_value="issue-uuid"), patch("src.plane_sync._resolve_project_id", return_value="proj-1"), patch("src.plane_sync.get_project_states", return_value=states), ) def _run_setter(setter, states): p_patch, p_find, p_res, p_states = _patch_resolve(states) with p_patch as mock_patch, p_find, p_res, p_states: resp = MagicMock() resp.raise_for_status.return_value = None mock_patch.return_value = resp setter("ET-1") return mock_patch # --------------------------------------------------------------------------- # TC-03 (AC-3): analyst start/resume indicates Analysis. # --------------------------------------------------------------------------- def test_tc03_set_issue_analysis_patches_analysis_uuid(): mock_patch = _run_setter(PS.set_issue_analysis, _STATES_WITH_NEW) # The dedicated Analysis UUID is used (NOT the in_progress base alias). assert mock_patch.call_args.kwargs["json"]["state"] == "analysis-uuid" assert mock_patch.call_args.kwargs["json"]["state"] != _STATES_WITH_NEW["in_progress"] def test_tc03_analysis_aliases_in_progress_when_absent(): # A project without the Analysis status -> get_project_states already aliased # 'analysis' onto its in_progress UUID, so the PATCH degrades gracefully. aliased = dict(_STATES_WITH_NEW) aliased["analysis"] = aliased["in_progress"] mock_patch = _run_setter(PS.set_issue_analysis, aliased) assert mock_patch.call_args.kwargs["json"]["state"] == aliased["in_progress"] # --------------------------------------------------------------------------- # TC-05 (AC-5): the review stage indicates Code-Review. # --------------------------------------------------------------------------- def test_tc05_review_stage_maps_to_code_review(): # Both the stage->state-key map and the stage-visibility map point review at # the new code_review logical key (layer B only). assert PS._STAGE_TO_STATE_KEY["review"] == "code_review" assert PS.STAGE_VISIBILITY_STATE["review"] == "code_review" def test_tc05_set_issue_stage_state_review_patches_code_review_uuid(): p_patch, p_find, p_res, p_states = _patch_resolve(_STATES_WITH_NEW) with p_patch as mock_patch, p_find, p_res, p_states: resp = MagicMock() resp.raise_for_status.return_value = None mock_patch.return_value = resp PS.set_issue_stage_state("ET-1", "review") assert mock_patch.call_args.kwargs["json"]["state"] == "codereview-uuid" def test_tc05_set_issue_code_review_helper_patches_code_review_uuid(): mock_patch = _run_setter(PS.set_issue_code_review, _STATES_WITH_NEW) assert mock_patch.call_args.kwargs["json"]["state"] == "codereview-uuid" # --------------------------------------------------------------------------- # TC-14 (AC-14): Needs Input behaviour unchanged. # --------------------------------------------------------------------------- def test_tc14_needs_input_unchanged(): mock_patch = _run_setter(PS.set_issue_needs_input, _STATES_WITH_NEW) assert mock_patch.call_args.kwargs["json"]["state"] == "ni-uuid" # --------------------------------------------------------------------------- # TC-22 (AC-21): STAGE_TRANSITIONS (layer A) is byte-identical. ORCH-066 changes # ONLY layer B — the machine must not move. # --------------------------------------------------------------------------- def test_tc22_stage_transitions_unchanged(): from src.stages import STAGE_TRANSITIONS assert STAGE_TRANSITIONS == { "created": {"next": "analysis", "agent": "analyst", "qg": None}, "analysis": {"next": "architecture", "agent": "architect", "qg": "check_analysis_approved"}, "architecture": {"next": "development", "agent": "developer", "qg": "check_architecture_done"}, "development": {"next": "review", "agent": "reviewer", "qg": "check_ci_green"}, "review": {"next": "testing", "agent": "tester", "qg": "check_reviewer_verdict"}, "testing": {"next": "deploy-staging", "agent": "deployer", "qg": "check_tests_passed"}, "deploy-staging": {"next": "deploy", "agent": "deployer", "qg": "check_staging_status"}, "deploy": {"next": "done", "agent": None, "qg": "check_deploy_status"}, "done": {"next": None, "agent": None, "qg": None}, } # --------------------------------------------------------------------------- # TC-23 (AC-22): QG_CHECKS registry + check_deploy_status contract unchanged. # --------------------------------------------------------------------------- def test_tc23_qg_checks_registry_unchanged(): from src.qg.checks import QG_CHECKS assert set(QG_CHECKS.keys()) == { "check_analysis_approved", "check_analysis_complete", "check_architecture_done", "check_ci_green", "check_review_approved", "check_tests_passed", "check_reviewer_verdict", "check_tests_local", "check_deploy_status", "check_staging_status", "check_branch_mergeable", "check_staging_image_fresh", } def test_tc23_check_deploy_status_signature_unchanged(): import inspect from src.qg.checks import check_deploy_status, QG_CHECKS # Registry still points at the same callable. assert QG_CHECKS["check_deploy_status"] is check_deploy_status # (repo, work_item_id, branch=None) -> tuple[bool, str] contract intact. params = list(inspect.signature(check_deploy_status).parameters) assert params == ["repo", "work_item_id", "branch"]