fix(qg): gate testing->deploy on machine-readable test verdict, not substring (ET-013)

check_tests_passed did "if PASS in content" over the whole 13-test-report.md
body, so a report explicitly marked verdict: BLOCKED / status: blocked whose
prose mentioned "23 passed" / "PASS" / "All checks passed" passed the gate.
On ET-013 an unfinished feature (P1 AC-19 failed) reached Done.

Now mirrors check_reviewer_verdict (S-5) and check_deploy_status: read ONLY the
YAML frontmatter verdict:/status: fields. Positive tokens (PASS/PASSED/
READY-TO-DEPLOY/GREEN/APPROVED) -> True; negative tokens (BLOCKED/FAILED/...) are
authoritative -> False; missing/empty/no-frontmatter/bad-YAML -> False with reason;
file missing -> not found. Never raises.

Positive token set derived from REAL enduro-trails reports ET-001..ET-014
(inconsistent: PASS, ready-to-deploy+status:PASSED, stage:ready-to-deploy+status:pass,
PASS — ready-to-deploy). Validated: all 9 prior passing WIs stay True, ET-013 -> False.
This commit is contained in:
dev-agent
2026-06-04 16:05:52 +03:00
parent 34894f4684
commit 757745a221
2 changed files with 165 additions and 14 deletions

View File

@@ -167,23 +167,110 @@ class TestCheckReviewApproved:
class TestCheckTestsPassed:
def test_report_with_pass(self, setup_work_item_dir):
repo_dir = setup_work_item_dir
wi_dir = repo_dir / "docs" / "work-items" / "ET-001"
wi_dir.mkdir(parents=True)
(wi_dir / "13-test-report.md").write_text("# Test Report\n\nResult: PASS\n")
"""ET-013 fix: testing -> deploy gate reads the tester's MACHINE-READABLE verdict
in 13-test-report.md frontmatter (verdict:/status:), NOT a substring of the body.
Mirrors check_reviewer_verdict / check_deploy_status. The old `if "PASS" in content`
let a `verdict: BLOCKED` report whose prose said "23 passed"/"✅ PASS" pass the gate,
shipping an unfinished feature to Done."""
def _write(self, repo_dir, content, wi="ET-001"):
wi_dir = repo_dir / "docs" / "work-items" / wi
wi_dir.mkdir(parents=True)
(wi_dir / "13-test-report.md").write_text(content)
def test_verdict_pass_passes(self, setup_work_item_dir):
# Most common real form (ET-001/002/005/009/011/012/014).
self._write(
setup_work_item_dir,
"---\ntype: test-report\nverdict: PASS\nstatus: pass\n---\n\n# Test Report\n",
)
passed, reason = check_tests_passed("enduro-trails", "ET-001")
assert passed is True
assert "PASS" in reason
def test_verdict_pass_ready_to_deploy_passes(self, setup_work_item_dir):
# ET-007 real form: "PASS — ready-to-deploy".
self._write(
setup_work_item_dir,
"---\nverdict: PASS — ready-to-deploy\nstatus: PASS\n---\n\nbody\n",
)
passed, reason = check_tests_passed("enduro-trails", "ET-001")
assert passed is True
def test_report_without_pass(self, setup_work_item_dir):
repo_dir = setup_work_item_dir
wi_dir = repo_dir / "docs" / "work-items" / "ET-001"
wi_dir.mkdir(parents=True)
(wi_dir / "13-test-report.md").write_text("# Test Report\n\nResult: FAIL\n")
def test_verdict_ready_to_deploy_with_status_passed_passes(self, setup_work_item_dir):
# ET-006 real form: verdict has no PASS word, but status: PASSED.
self._write(
setup_work_item_dir,
"---\nverdict: ready-to-deploy\nstatus: PASSED\n---\n\nbody\n",
)
passed, reason = check_tests_passed("enduro-trails", "ET-001")
assert passed is True
def test_verdict_stage_ready_to_deploy_with_status_pass_passes(self, setup_work_item_dir):
# ET-008 real form: verdict: stage:ready-to-deploy, status: pass.
self._write(
setup_work_item_dir,
"---\nverdict: stage:ready-to-deploy\nstatus: pass\n---\n\nbody\n",
)
passed, reason = check_tests_passed("enduro-trails", "ET-001")
assert passed is True
def test_blocked_verdict_with_pass_in_body_fails(self, setup_work_item_dir):
# THE ET-013 BUG: verdict BLOCKED but body is full of "PASS"/"passed".
self._write(
setup_work_item_dir,
"---\ntype: test-report\nstatus: blocked\nverdict: BLOCKED\n---\n\n"
"23 passed\n✅ PASS (часть AC-18)\nAll checks passed\n",
)
passed, reason = check_tests_passed("enduro-trails", "ET-001")
assert passed is False
assert "BLOCKED" in reason
def test_failed_verdict_fails(self, setup_work_item_dir):
self._write(
setup_work_item_dir,
"---\nverdict: FAILED\nstatus: failed\n---\n\nbody\n",
)
passed, reason = check_tests_passed("enduro-trails", "ET-001")
assert passed is False
assert "FAILED" in reason
def test_passed_count_in_body_but_blocked_verdict_fails(self, setup_work_item_dir):
# Body says "23 passed" but frontmatter verdict BLOCKED -> substring no longer fools.
self._write(
setup_work_item_dir,
"---\nverdict: BLOCKED\n---\n\nTests: 23 passed, 0 failed.\n",
)
passed, reason = check_tests_passed("enduro-trails", "ET-001")
assert passed is False
def test_no_frontmatter_fails(self, setup_work_item_dir):
# Old format / prose only -> no machine verdict -> fail.
self._write(
setup_work_item_dir,
"# Test Report\n\nResult: PASS\nAll tests passed.\n",
)
passed, reason = check_tests_passed("enduro-trails", "ET-001")
assert passed is False
def test_no_verdict_field_fails(self, setup_work_item_dir):
# Frontmatter present but neither verdict nor status -> fail.
self._write(
setup_work_item_dir,
"---\ntype: test-report\nversion: 1\n---\n\nResult: PASS\n",
)
passed, reason = check_tests_passed("enduro-trails", "ET-001")
assert passed is False
def test_invalid_yaml_fails_no_exception(self, setup_work_item_dir):
# Broken YAML frontmatter -> False with reason, never raises.
self._write(
setup_work_item_dir,
"---\nverdict: [unclosed\n : : :\n---\n\nbody PASS\n",
)
passed, reason = check_tests_passed("enduro-trails", "ET-001")
assert passed is False
assert "YAML" in reason or "frontmatter" in reason.lower()
def test_no_report(self, setup_work_item_dir):
passed, reason = check_tests_passed("enduro-trails", "ET-001")