mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
112 lines
4.0 KiB
Python
112 lines
4.0 KiB
Python
"""Tier 1 — in-path PR-review gate spec self-tests. Fast (no DB, no network).
|
|||
|
|
|
||
|
|
Covers the lifecycle surface of the assembled-PR review gate: the new
|
||
|
|
awaiting_pr_review status, the submit_for_review / pr_pass / pr_fail actions,
|
||
|
|
the reviewer verbs, and the structural invariant that a gated task cannot reach
|
||
|
|
the PM-merge stage without a pr_pass.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from types import SimpleNamespace
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
from roboco.foundation.policy import lifecycle as spec
|
||
|
|
from roboco.foundation.policy.lifecycle import Role, Status
|
||
|
|
|
||
|
|
|
||
|
|
def _task(status: str, team: str = "backend", **kw: Any) -> SimpleNamespace:
|
||
|
|
return SimpleNamespace(status=status, team=team, **kw)
|
||
|
|
|
||
|
|
|
||
|
|
def test_submit_for_review_enters_the_gate() -> None:
|
||
|
|
assert (
|
||
|
|
spec.status_after("submit_for_review", Status.IN_PROGRESS)
|
||
|
|
== Status.AWAITING_PR_REVIEW
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def test_pr_pass_and_pr_fail_targets() -> None:
|
||
|
|
assert (
|
||
|
|
spec.status_after("pr_pass", Status.AWAITING_PR_REVIEW)
|
||
|
|
== Status.AWAITING_PM_REVIEW
|
||
|
|
)
|
||
|
|
assert (
|
||
|
|
spec.status_after("pr_fail", Status.AWAITING_PR_REVIEW) == Status.NEEDS_REVISION
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def test_reviewer_can_pass_and_fail_from_the_gate() -> None:
|
||
|
|
t = _task("awaiting_pr_review")
|
||
|
|
assert spec.can_invoke_intent(Role.PR_REVIEWER, "pr_pass", t).allowed
|
||
|
|
assert spec.can_invoke_intent(Role.PR_REVIEWER, "pr_fail", t).allowed
|
||
|
|
|
||
|
|
|
||
|
|
def test_non_reviewer_roles_cannot_pass_the_gate() -> None:
|
||
|
|
t = _task("awaiting_pr_review")
|
||
|
|
for role in (Role.DEVELOPER, Role.QA, Role.CELL_PM, Role.MAIN_PM):
|
||
|
|
d = spec.can_invoke_intent(role, "pr_pass", t)
|
||
|
|
assert not d.allowed
|
||
|
|
assert d.rejection_kind == "not_authorized"
|
||
|
|
|
||
|
|
|
||
|
|
def test_reviewer_cannot_pass_from_a_non_gate_state() -> None:
|
||
|
|
d = spec.can_invoke_intent(Role.PR_REVIEWER, "pr_pass", _task("awaiting_pm_review"))
|
||
|
|
assert not d.allowed
|
||
|
|
assert d.rejection_kind == "invalid_state"
|
||
|
|
|
||
|
|
|
||
|
|
def test_reviewer_claims_the_gate_without_transition() -> None:
|
||
|
|
assert spec.can_invoke_intent(
|
||
|
|
Role.PR_REVIEWER, "claim_gate_review", _task("awaiting_pr_review")
|
||
|
|
).allowed
|
||
|
|
|
||
|
|
|
||
|
|
def test_reviewer_cannot_claim_gate_from_another_roles_state() -> None:
|
||
|
|
d = spec.can_invoke_intent(
|
||
|
|
Role.PR_REVIEWER, "claim_gate_review", _task("awaiting_qa")
|
||
|
|
)
|
||
|
|
assert not d.allowed
|
||
|
|
|
||
|
|
|
||
|
|
def test_submit_root_is_main_pm_only_and_opens_a_pr() -> None:
|
||
|
|
iv = spec._INTENT_VERBS["submit_root"]
|
||
|
|
assert iv.allowed_roles == frozenset({Role.MAIN_PM})
|
||
|
|
assert iv.composes == ("submit_for_review",)
|
||
|
|
assert iv.pre_side_effects == ("create_root_pr",)
|
||
|
|
|
||
|
|
|
||
|
|
def test_gate_cannot_skip_straight_to_terminal_or_ceo() -> None:
|
||
|
|
targets = spec.STATUS_GRAPH[Status.AWAITING_PR_REVIEW]
|
||
|
|
assert Status.COMPLETED not in targets
|
||
|
|
assert Status.AWAITING_CEO_APPROVAL not in targets
|
||
|
|
# The only forward exits are pr_pass (to PM review) and pr_fail (back).
|
||
|
|
assert Status.AWAITING_PM_REVIEW in targets
|
||
|
|
assert Status.NEEDS_REVISION in targets
|
||
|
|
|
||
|
|
|
||
|
|
def test_pr_pass_is_the_sole_action_out_of_the_gate_into_pm_review() -> None:
|
||
|
|
movers = [
|
||
|
|
name
|
||
|
|
for name, a in spec._ATOMIC_ACTIONS.items()
|
||
|
|
if a.target_status == Status.AWAITING_PM_REVIEW
|
||
|
|
and Status.AWAITING_PR_REVIEW in a.source_statuses
|
||
|
|
]
|
||
|
|
assert movers == ["pr_pass"]
|
||
|
|
|
||
|
|
|
||
|
|
def test_gate_actions_block_self_review() -> None:
|
||
|
|
assert spec._ATOMIC_ACTIONS["pr_pass"].self_review_block is True
|
||
|
|
assert spec._ATOMIC_ACTIONS["pr_fail"].self_review_block is True
|
||
|
|
|
||
|
|
|
||
|
|
def test_delegate_hint_names_the_role_correct_bubble_up_verb() -> None:
|
||
|
|
"""After delegating, a PM is steered to the right bubble-up verb proactively:
|
||
|
|
a root (no parent) → submit_root; a cell parent → submit_up. Prevents the
|
||
|
|
guess-the-verb flail that deadlocked root closure."""
|
||
|
|
hint = spec._INTENT_VERBS["delegate"].next_hint
|
||
|
|
root = _task("in_progress", parent_task_id=None)
|
||
|
|
cell_parent = _task("in_progress", parent_task_id="parent-id")
|
||
|
|
assert "submit_root" in hint(root)
|
||
|
|
assert "submit_up" in hint(cell_parent)
|