fix: resolve conflicts for PR #371 against current master
This commit is contained in:
@@ -62,7 +62,24 @@ class TestPreflightWarnings(unittest.TestCase):
|
||||
self.assertIn("_encode_commit_payload.py", status["preflight_warnings"][0])
|
||||
|
||||
|
||||
# Issue-write tools are profile-gated (#69); gitea_lock_issue requires
|
||||
# gitea.issue.comment (see task_capability_map), so the gate must be
|
||||
# seeded exactly like tests/test_mcp_server.py::TestIssueLocking (#359).
|
||||
ISSUE_WRITE_ENV = {
|
||||
"GITEA_ALLOWED_OPERATIONS": (
|
||||
"gitea.issue.create,gitea.issue.close,gitea.issue.comment"
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class TestIssueLockArtifactWarning(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self._env_patcher = patch.dict(os.environ, ISSUE_WRITE_ENV, clear=True)
|
||||
self._env_patcher.start()
|
||||
|
||||
def tearDown(self):
|
||||
self._env_patcher.stop()
|
||||
|
||||
@patch("mcp_server.api_get_all", return_value=[])
|
||||
@patch("mcp_server._auth", return_value="token x")
|
||||
@patch("mcp_server._resolve", return_value=("h", "o", "r"))
|
||||
@@ -72,6 +89,9 @@ class TestIssueLockArtifactWarning(unittest.TestCase):
|
||||
mock_state.return_value = {
|
||||
"current_branch": "master",
|
||||
"porcelain_status": "?? _emit_payload.py\n",
|
||||
"base_equivalent": True,
|
||||
"inspected_git_root": "/scratch/wt",
|
||||
"base_branch": "origin/master",
|
||||
}
|
||||
result = mcp_server.gitea_lock_issue(
|
||||
issue_number=261,
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
"""Tests for linked-issue consistency verifier (#314).
|
||||
|
||||
Reviewer reports must verify the linked issue live for the selected PR;
|
||||
stale issue numbers from a previous PR must not leak into the final
|
||||
report, and linked-issue status must never be claimed without live proof.
|
||||
"""
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
from review_proofs import assess_linked_issue_consistency # noqa: E402
|
||||
|
||||
|
||||
class TestCorrectLinkedIssue(unittest.TestCase):
|
||||
def test_matching_linked_issue_passes(self):
|
||||
report = (
|
||||
"Selected PR: 280\n"
|
||||
"Linked issue status: Issue #275 open, closes on merge\n"
|
||||
)
|
||||
result = assess_linked_issue_consistency(
|
||||
report, selected_pr=280, linked_issues=[275]
|
||||
)
|
||||
self.assertTrue(result["complete"])
|
||||
self.assertFalse(result["downgraded"])
|
||||
self.assertEqual(result["reasons"], [])
|
||||
|
||||
def test_multiple_linked_issues_all_reported_passes(self):
|
||||
report = (
|
||||
"Linked issue status: Issue #275 and Issue #276 both open\n"
|
||||
)
|
||||
result = assess_linked_issue_consistency(
|
||||
report, selected_pr=280, linked_issues=[275, 276]
|
||||
)
|
||||
self.assertTrue(result["complete"])
|
||||
|
||||
def test_multiple_linked_issues_one_missing_fails(self):
|
||||
report = "Linked issue status: Issue #275 open\n"
|
||||
result = assess_linked_issue_consistency(
|
||||
report, selected_pr=280, linked_issues=[275, 276]
|
||||
)
|
||||
self.assertFalse(result["complete"])
|
||||
self.assertTrue(
|
||||
any("276" in r for r in result["reasons"])
|
||||
)
|
||||
|
||||
|
||||
class TestStaleIssueLeak(unittest.TestCase):
|
||||
def test_stale_issue_in_status_field_fails(self):
|
||||
# #314 observed behavior: PR #280 links Issue #275 but the report
|
||||
# carries Issue #260 from the previous PR.
|
||||
report = "Linked issue status: Issue #260 open\n"
|
||||
result = assess_linked_issue_consistency(
|
||||
report, selected_pr=280, linked_issues=[275]
|
||||
)
|
||||
self.assertFalse(result["complete"])
|
||||
self.assertTrue(any("260" in r for r in result["reasons"]))
|
||||
|
||||
def test_stale_issue_in_diagnostics_fails(self):
|
||||
report = (
|
||||
"Linked issue status: Issue #275 open\n"
|
||||
"Read-only diagnostics: viewed Issue #260 status\n"
|
||||
)
|
||||
result = assess_linked_issue_consistency(
|
||||
report, selected_pr=280, linked_issues=[275]
|
||||
)
|
||||
self.assertFalse(result["complete"])
|
||||
self.assertTrue(
|
||||
any("stale" in r.lower() and "260" in r for r in result["reasons"])
|
||||
)
|
||||
|
||||
def test_selected_pr_number_mention_is_not_stale(self):
|
||||
# "PR #280" mentions must not be confused with issue mentions.
|
||||
report = (
|
||||
"Selected PR: 280\n"
|
||||
"Validation: ran tests at PR #280 head\n"
|
||||
"Linked issue status: Issue #275 open\n"
|
||||
)
|
||||
result = assess_linked_issue_consistency(
|
||||
report, selected_pr=280, linked_issues=[275]
|
||||
)
|
||||
self.assertTrue(result["complete"])
|
||||
|
||||
|
||||
class TestMissingProof(unittest.TestCase):
|
||||
def test_status_claim_without_live_proof_fails(self):
|
||||
report = "Linked issue status: Issue #275 open\n"
|
||||
result = assess_linked_issue_consistency(
|
||||
report, selected_pr=280, linked_issues=None
|
||||
)
|
||||
self.assertFalse(result["complete"])
|
||||
self.assertTrue(
|
||||
any("live proof" in r.lower() for r in result["reasons"])
|
||||
)
|
||||
|
||||
def test_unverified_wording_without_proof_passes(self):
|
||||
report = (
|
||||
"Linked issue status: not verified in this session\n"
|
||||
)
|
||||
result = assess_linked_issue_consistency(
|
||||
report, selected_pr=280, linked_issues=None
|
||||
)
|
||||
self.assertTrue(result["complete"])
|
||||
|
||||
def test_missing_field_fails(self):
|
||||
report = "Selected PR: 280\n"
|
||||
result = assess_linked_issue_consistency(
|
||||
report, selected_pr=280, linked_issues=[275]
|
||||
)
|
||||
self.assertFalse(result["complete"])
|
||||
self.assertTrue(
|
||||
any("linked issue status" in r.lower() for r in result["reasons"])
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -136,6 +136,12 @@ def test_create_issue_final_report_verifier_exported():
|
||||
assert callable(assess_create_issue_final_report)
|
||||
|
||||
|
||||
def test_prior_blocker_skip_verifier_exported():
|
||||
from review_proofs import assess_prior_blocker_skip_proof
|
||||
|
||||
assert callable(assess_prior_blocker_skip_proof)
|
||||
|
||||
|
||||
def test_non_mergeable_skip_verifier_exported():
|
||||
from review_proofs import assess_non_mergeable_skip_proof
|
||||
|
||||
@@ -146,3 +152,9 @@ def test_already_landed_handoff_verifier_exported():
|
||||
from review_proofs import assess_already_landed_handoff_report
|
||||
|
||||
assert callable(assess_already_landed_handoff_report)
|
||||
|
||||
|
||||
def test_worktree_ownership_verifier_exported():
|
||||
from review_proofs import assess_worktree_ownership_report
|
||||
|
||||
assert callable(assess_worktree_ownership_report)
|
||||
@@ -24,6 +24,7 @@ from review_proofs import ( # noqa: E402
|
||||
ISSUE_SELECTION_CONTINUATION_EXPLICIT,
|
||||
ISSUE_SELECTION_REPRESENTED_BY_OPEN_PR,
|
||||
assess_author_pr_report,
|
||||
assess_full_suite_failure_approval_gate,
|
||||
assess_queue_ordering_proof,
|
||||
assess_reviewer_baseline_validation_proof,
|
||||
assess_capability_evidence,
|
||||
@@ -2539,5 +2540,170 @@ class TestReviewerBaselineValidationProof(unittest.TestCase):
|
||||
self.assertFalse(report["baseline_validation_proven"])
|
||||
|
||||
|
||||
class TestFullSuiteFailureApprovalGate(unittest.TestCase):
|
||||
"""Issue #323: full-suite failure blocks approval without baseline proof."""
|
||||
|
||||
ROOT = "/repo/Gitea-Tools"
|
||||
|
||||
def _good_proof(self, **overrides):
|
||||
proof = {
|
||||
"worktree_path": f"{self.ROOT}/branches/baseline-master-pr280",
|
||||
"baseline_target_sha": PINNED,
|
||||
"pr_head_sha": OTHER,
|
||||
"baseline_failures": ["tests/test_foo.py::test_bar"],
|
||||
"pr_failures": ["tests/test_foo.py::test_bar"],
|
||||
"failure_signatures_match": True,
|
||||
"clean_before": True,
|
||||
"clean_after": True,
|
||||
"pr_suite_command": "venv/bin/pytest tests/",
|
||||
"baseline_suite_command": "venv/bin/pytest tests/",
|
||||
"new_tests_passed": True,
|
||||
"unrelated_explanation": (
|
||||
"failures touch agent_temp_artifacts only; PR changes "
|
||||
"review_proofs handoff fields"
|
||||
),
|
||||
}
|
||||
proof.update(overrides)
|
||||
return proof
|
||||
|
||||
def _good_report(self):
|
||||
return (
|
||||
"Full suite failed on PR worktree and on baseline.\n"
|
||||
"- Validation command: venv/bin/pytest tests/\n"
|
||||
f"- Working directory: {self.ROOT}/branches/review-pr-280\n"
|
||||
"Failures are pre-existing on master.\n"
|
||||
)
|
||||
|
||||
def test_full_suite_pass_allows_approval_without_baseline(self):
|
||||
result = assess_full_suite_failure_approval_gate(
|
||||
review_decision="approve",
|
||||
full_suite_passed=True,
|
||||
project_root=self.ROOT,
|
||||
)
|
||||
self.assertTrue(result["approve_allowed"])
|
||||
self.assertFalse(result["block"])
|
||||
self.assertEqual(result["default_action"], "proceed")
|
||||
|
||||
def test_full_suite_failure_defaults_to_request_changes(self):
|
||||
result = assess_full_suite_failure_approval_gate(
|
||||
review_decision="request_changes",
|
||||
full_suite_passed=False,
|
||||
project_root=self.ROOT,
|
||||
)
|
||||
self.assertFalse(result["block"])
|
||||
self.assertEqual(result["default_action"], "request_changes")
|
||||
|
||||
def test_approval_with_failure_and_no_proof_blocks(self):
|
||||
result = assess_full_suite_failure_approval_gate(
|
||||
review_decision="approve",
|
||||
full_suite_passed=False,
|
||||
project_root=self.ROOT,
|
||||
)
|
||||
self.assertFalse(result["approve_allowed"])
|
||||
self.assertTrue(result["block"])
|
||||
self.assertEqual(result["default_action"], "request_changes")
|
||||
|
||||
def test_approval_with_matching_baseline_proof_allowed(self):
|
||||
result = assess_full_suite_failure_approval_gate(
|
||||
review_decision="approve",
|
||||
full_suite_passed=False,
|
||||
baseline_proof=self._good_proof(),
|
||||
report_text=self._good_report(),
|
||||
project_root=self.ROOT,
|
||||
)
|
||||
self.assertTrue(result["approve_allowed"])
|
||||
self.assertFalse(result["block"])
|
||||
|
||||
def test_mismatched_failure_signatures_block_approval(self):
|
||||
result = assess_full_suite_failure_approval_gate(
|
||||
review_decision="approve",
|
||||
full_suite_passed=False,
|
||||
baseline_proof=self._good_proof(
|
||||
failure_signatures_match=False,
|
||||
pr_failures=["tests/test_other.py::test_other"],
|
||||
),
|
||||
report_text=self._good_report(),
|
||||
project_root=self.ROOT,
|
||||
)
|
||||
self.assertFalse(result["approve_allowed"])
|
||||
self.assertTrue(result["block"])
|
||||
|
||||
def test_main_checkout_baseline_blocks_approval(self):
|
||||
result = assess_full_suite_failure_approval_gate(
|
||||
review_decision="approve",
|
||||
full_suite_passed=False,
|
||||
baseline_proof=self._good_proof(worktree_path=self.ROOT),
|
||||
report_text=self._good_report(),
|
||||
project_root=self.ROOT,
|
||||
)
|
||||
self.assertFalse(result["approve_allowed"])
|
||||
self.assertTrue(result["block"])
|
||||
|
||||
def test_missing_new_tests_proof_blocks_approval(self):
|
||||
result = assess_full_suite_failure_approval_gate(
|
||||
review_decision="approve",
|
||||
full_suite_passed=False,
|
||||
baseline_proof=self._good_proof(new_tests_passed=None),
|
||||
report_text=self._good_report(),
|
||||
project_root=self.ROOT,
|
||||
)
|
||||
self.assertFalse(result["approve_allowed"])
|
||||
self.assertTrue(any(
|
||||
"new" in reason.lower() and "test" in reason.lower()
|
||||
for reason in result["reasons"]
|
||||
))
|
||||
|
||||
def test_missing_unrelated_explanation_blocks_approval(self):
|
||||
result = assess_full_suite_failure_approval_gate(
|
||||
review_decision="approve",
|
||||
full_suite_passed=False,
|
||||
baseline_proof=self._good_proof(unrelated_explanation=""),
|
||||
report_text=self._good_report(),
|
||||
project_root=self.ROOT,
|
||||
)
|
||||
self.assertFalse(result["approve_allowed"])
|
||||
|
||||
def test_missing_suite_commands_block_approval(self):
|
||||
result = assess_full_suite_failure_approval_gate(
|
||||
review_decision="approve",
|
||||
full_suite_passed=False,
|
||||
baseline_proof=self._good_proof(
|
||||
pr_suite_command="", baseline_suite_command=""),
|
||||
report_text=self._good_report(),
|
||||
project_root=self.ROOT,
|
||||
)
|
||||
self.assertFalse(result["approve_allowed"])
|
||||
self.assertTrue(any(
|
||||
"command" in reason.lower() for reason in result["reasons"]
|
||||
))
|
||||
|
||||
def test_unknown_suite_result_blocks_approval(self):
|
||||
result = assess_full_suite_failure_approval_gate(
|
||||
review_decision="approve",
|
||||
full_suite_passed=None,
|
||||
project_root=self.ROOT,
|
||||
)
|
||||
self.assertFalse(result["approve_allowed"])
|
||||
self.assertTrue(result["block"])
|
||||
|
||||
def test_vague_same_as_master_claim_without_proof_blocks(self):
|
||||
result = assess_full_suite_failure_approval_gate(
|
||||
review_decision="approve",
|
||||
full_suite_passed=False,
|
||||
report_text="Validation: failures same as master; approving.",
|
||||
project_root=self.ROOT,
|
||||
)
|
||||
self.assertFalse(result["approve_allowed"])
|
||||
self.assertTrue(result["block"])
|
||||
|
||||
def test_non_approve_decision_never_blocks(self):
|
||||
result = assess_full_suite_failure_approval_gate(
|
||||
review_decision="comment",
|
||||
full_suite_passed=None,
|
||||
project_root=self.ROOT,
|
||||
)
|
||||
self.assertFalse(result["block"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
"""Tests for prior-blocker skip proof verifier (#318)."""
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
from reviewer_blocker_skip import assess_prior_blocker_skip_proof # noqa: E402
|
||||
|
||||
HEAD_CURRENT = "0fdc8f582026b72a229d59a172c0a63ac4aaeaf9"
|
||||
HEAD_BLOCKER = "1111111111111111111111111111111111111111"
|
||||
|
||||
|
||||
def _good_blocker_skip_report(pr_number: int) -> str:
|
||||
return "\n".join([
|
||||
f"Skipped PR #{pr_number} due to prior REQUEST_CHANGES.",
|
||||
f"- PR number: #{pr_number}",
|
||||
f"- Current head SHA: {HEAD_CURRENT}",
|
||||
"- Blocking review decision: request_changes",
|
||||
f"- Blocking review head SHA: {HEAD_CURRENT}",
|
||||
"- Head unchanged since blocker",
|
||||
"- Reason remains blocked: unresolved review feedback at same head",
|
||||
"- Blocker revalidated live in this session via gitea_get_pr_review_feedback",
|
||||
])
|
||||
|
||||
|
||||
class TestPriorBlockerSkipProof(unittest.TestCase):
|
||||
def test_no_skips_passes(self):
|
||||
result = assess_prior_blocker_skip_proof(
|
||||
"Selected PR #281 for review.",
|
||||
skipped_prs=[],
|
||||
)
|
||||
self.assertTrue(result["proven"])
|
||||
|
||||
def test_blocked_unchanged_with_live_proof_passes(self):
|
||||
report = _good_blocker_skip_report(280)
|
||||
result = assess_prior_blocker_skip_proof(
|
||||
report,
|
||||
skipped_prs=[{
|
||||
"pr_number": 280,
|
||||
"head_sha": HEAD_CURRENT,
|
||||
"blocking_decision": "request_changes",
|
||||
"blocking_review_head_sha": HEAD_CURRENT,
|
||||
"head_changed_since_blocker": False,
|
||||
"blocker_verified": True,
|
||||
}],
|
||||
)
|
||||
self.assertTrue(result["proven"])
|
||||
|
||||
def test_head_changed_after_blocker_blocks_skip(self):
|
||||
result = assess_prior_blocker_skip_proof(
|
||||
_good_blocker_skip_report(280),
|
||||
skipped_prs=[{
|
||||
"pr_number": 280,
|
||||
"head_sha": HEAD_CURRENT,
|
||||
"blocking_decision": "request_changes",
|
||||
"blocking_review_head_sha": HEAD_BLOCKER,
|
||||
"head_changed_since_blocker": True,
|
||||
"blocker_verified": True,
|
||||
}],
|
||||
)
|
||||
self.assertFalse(result["proven"])
|
||||
self.assertTrue(any("head changed" in r for r in result["reasons"]))
|
||||
|
||||
def test_missing_live_proof_blocks(self):
|
||||
report = _good_blocker_skip_report(280).replace(
|
||||
"- Blocker revalidated live in this session via gitea_get_pr_review_feedback",
|
||||
"",
|
||||
)
|
||||
result = assess_prior_blocker_skip_proof(
|
||||
report,
|
||||
skipped_prs=[{
|
||||
"pr_number": 280,
|
||||
"head_sha": HEAD_CURRENT,
|
||||
"blocking_decision": "request_changes",
|
||||
"blocking_review_head_sha": HEAD_CURRENT,
|
||||
"head_changed_since_blocker": False,
|
||||
"blocker_verified": True,
|
||||
}],
|
||||
)
|
||||
self.assertFalse(result["proven"])
|
||||
self.assertTrue(any("live blocker proof" in r for r in result["reasons"]))
|
||||
|
||||
def test_blocker_unverified_classification_passes(self):
|
||||
report = "\n".join([
|
||||
"Skipped PR #280.",
|
||||
"Classification: BLOCKER_STATUS_UNVERIFIED",
|
||||
"Review feedback could not be fetched live in this session.",
|
||||
])
|
||||
result = assess_prior_blocker_skip_proof(
|
||||
report,
|
||||
skipped_prs=[{
|
||||
"pr_number": 280,
|
||||
"blocking_decision": "request_changes",
|
||||
"blocker_verified": False,
|
||||
}],
|
||||
)
|
||||
self.assertTrue(result["proven"])
|
||||
|
||||
def test_unverified_without_classification_blocks(self):
|
||||
result = assess_prior_blocker_skip_proof(
|
||||
"Skipped PR #280 based on memory from last session.",
|
||||
skipped_prs=[{
|
||||
"pr_number": 280,
|
||||
"blocking_decision": "request_changes",
|
||||
"blocker_verified": False,
|
||||
}],
|
||||
)
|
||||
self.assertFalse(result["proven"])
|
||||
self.assertTrue(any("BLOCKER_STATUS_UNVERIFIED" in r for r in result["reasons"]))
|
||||
|
||||
def test_missing_blocking_head_sha_blocks(self):
|
||||
report = _good_blocker_skip_report(280).replace(
|
||||
f"- Blocking review head SHA: {HEAD_CURRENT}",
|
||||
"",
|
||||
)
|
||||
result = assess_prior_blocker_skip_proof(
|
||||
report,
|
||||
skipped_prs=[{
|
||||
"pr_number": 280,
|
||||
"head_sha": HEAD_CURRENT,
|
||||
"blocking_decision": "request_changes",
|
||||
"blocking_review_head_sha": HEAD_CURRENT,
|
||||
"head_changed_since_blocker": False,
|
||||
"blocker_verified": True,
|
||||
}],
|
||||
)
|
||||
self.assertFalse(result["proven"])
|
||||
|
||||
|
||||
class TestExport(unittest.TestCase):
|
||||
def test_review_proofs_reexport(self):
|
||||
from review_proofs import assess_prior_blocker_skip_proof as exported
|
||||
|
||||
self.assertTrue(callable(exported))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,137 @@
|
||||
"""Tests for reviewer worktree ownership verifier (#312)."""
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
from reviewer_worktree_ownership import assess_worktree_ownership_report # noqa: E402
|
||||
|
||||
|
||||
def _fresh_review_report() -> str:
|
||||
return "\n".join([
|
||||
"Review worktree path: branches/review-pr280-feat-issue-275-claim",
|
||||
"Worktree is inside branches/ and not the main checkout.",
|
||||
"Tracked state: clean; untracked state: clean.",
|
||||
"Official PR-head validation on session-owned worktree.",
|
||||
"Worktree mutations: none",
|
||||
])
|
||||
|
||||
|
||||
def _safe_reuse_report() -> str:
|
||||
return "\n".join([
|
||||
"Safe-reuse proof for existing review worktree.",
|
||||
"Review worktree path: branches/docs-issue-260-forbid-commit-fallbacks",
|
||||
"Worktree inside branches/; not the main checkout.",
|
||||
"Not owned by another active task/session.",
|
||||
"Clean tracked state; clean untracked state.",
|
||||
"Branch/head before reset: feat/issue-260-example",
|
||||
"Reset target SHA: abc123def4567890abcdef1234567890abcdef12",
|
||||
"Project policy allows reuse/reset for this clean review worktree.",
|
||||
"Worktree mutations: git reset --hard FETCH_HEAD",
|
||||
"Destructive reset operations: git reset --hard FETCH_HEAD",
|
||||
])
|
||||
|
||||
|
||||
class TestWorktreeOwnership(unittest.TestCase):
|
||||
def test_fresh_session_owned_passes(self):
|
||||
result = assess_worktree_ownership_report(
|
||||
_fresh_review_report(),
|
||||
ownership_session={
|
||||
"validation_ran": True,
|
||||
"worktree_path": "branches/review-pr280-feat-issue-275-claim",
|
||||
},
|
||||
)
|
||||
self.assertTrue(result["proven"], result["reasons"])
|
||||
self.assertTrue(result["session_owned"])
|
||||
|
||||
def test_safe_reuse_with_reset_passes(self):
|
||||
result = assess_worktree_ownership_report(
|
||||
_safe_reuse_report(),
|
||||
ownership_session={
|
||||
"safe_reuse": True,
|
||||
"worktree_path": "branches/docs-issue-260-forbid-commit-fallbacks",
|
||||
},
|
||||
command_log=["git reset --hard FETCH_HEAD"],
|
||||
)
|
||||
self.assertTrue(result["proven"], result["reasons"])
|
||||
|
||||
def test_destructive_reset_without_proof_blocks(self):
|
||||
report = "\n".join([
|
||||
"Review worktree path: branches/docs-issue-260-forbid-commit-fallbacks",
|
||||
"Worktree mutations: git reset --hard FETCH_HEAD",
|
||||
])
|
||||
result = assess_worktree_ownership_report(
|
||||
report,
|
||||
ownership_session={
|
||||
"validation_ran": True,
|
||||
"worktree_path": "branches/docs-issue-260-forbid-commit-fallbacks",
|
||||
},
|
||||
command_log=["git reset --hard FETCH_HEAD"],
|
||||
)
|
||||
self.assertFalse(result["proven"])
|
||||
self.assertTrue(result["block"])
|
||||
|
||||
def test_reused_dirty_tracked_blocks(self):
|
||||
result = assess_worktree_ownership_report(
|
||||
_fresh_review_report(),
|
||||
ownership_session={
|
||||
"validation_ran": True,
|
||||
"worktree_path": "branches/review-pr280-feat-issue-275-claim",
|
||||
"dirty_tracked": True,
|
||||
},
|
||||
)
|
||||
self.assertFalse(result["proven"])
|
||||
self.assertTrue(any("tracked" in r.lower() for r in result["reasons"]))
|
||||
|
||||
def test_safe_reuse_dirty_untracked_blocks(self):
|
||||
result = assess_worktree_ownership_report(
|
||||
_safe_reuse_report(),
|
||||
ownership_session={
|
||||
"safe_reuse": True,
|
||||
"dirty_untracked": True,
|
||||
"worktree_path": "branches/docs-issue-260-forbid-commit-fallbacks",
|
||||
},
|
||||
command_log=["git reset --hard FETCH_HEAD"],
|
||||
)
|
||||
self.assertFalse(result["proven"])
|
||||
self.assertTrue(any("untracked" in r.lower() for r in result["reasons"]))
|
||||
|
||||
def test_workspace_none_with_reset_blocks(self):
|
||||
report = "\n".join([
|
||||
"Review worktree path: branches/review-pr280-feat-issue-275-claim",
|
||||
"Workspace mutations: none",
|
||||
"Worktree mutations: git reset --hard FETCH_HEAD",
|
||||
])
|
||||
result = assess_worktree_ownership_report(
|
||||
report,
|
||||
ownership_session={
|
||||
"session_owned": True,
|
||||
"worktree_path": "branches/review-pr280-feat-issue-275-claim",
|
||||
},
|
||||
command_log=["git reset --hard FETCH_HEAD"],
|
||||
)
|
||||
self.assertFalse(result["proven"])
|
||||
self.assertTrue(any("workspace mutations" in r.lower() for r in result["reasons"]))
|
||||
|
||||
def test_main_checkout_blocks(self):
|
||||
result = assess_worktree_ownership_report(
|
||||
"Review worktree path: /Users/dev/Gitea-Tools",
|
||||
ownership_session={
|
||||
"validation_ran": True,
|
||||
"main_checkout": True,
|
||||
"worktree_path": "/Users/dev/Gitea-Tools",
|
||||
},
|
||||
)
|
||||
self.assertFalse(result["proven"])
|
||||
|
||||
|
||||
class TestExport(unittest.TestCase):
|
||||
def test_review_proofs_reexport(self):
|
||||
from review_proofs import assess_worktree_ownership_report as exported
|
||||
|
||||
self.assertTrue(callable(exported))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user