Add assess_reconciliation_inventory_proof to review_proofs: already-landed reconciliation reports may not claim a complete queue scan or that all already-landed PRs were found unless the report carries the pagination proof fields (Requested page size, Pages fetched, Returned PR count per page, Final page proof) and the per-page gitea_list_prs pagination metadata proves the final page was reached — inventory_complete, is_final_page with has_more=false, or a returned count below the requested page size. Declared page counts and per-page counts must match the evidence. Reports without a completeness claim pass without proof. Fails closed on missing evidence or malformed page metadata. Tests cover first page below limit, first page exactly at the page limit, multi-page traversal to the final page, missing pagination metadata, missing report fields, page-count and per-page-count mismatches, and the no-claim case. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
151 lines
4.9 KiB
Python
151 lines
4.9 KiB
Python
"""Tests for reconciliation PR-inventory pagination proof (#308).
|
|
|
|
Already-landed reconciliation reports may not claim a complete queue
|
|
scan (or that all already-landed PRs were found) unless the report
|
|
carries pagination proof fields and the per-page evidence proves the
|
|
final page was reached or the page limit was not hit.
|
|
"""
|
|
import sys
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
|
|
from review_proofs import assess_reconciliation_inventory_proof # noqa: E402
|
|
|
|
|
|
def _report(claim=True, fields=True, pages="1", counts="12",
|
|
final_proof="is_final_page=true, has_more=false"):
|
|
lines = []
|
|
if claim:
|
|
lines.append(
|
|
"Inventory scope: complete queue scan; all already-landed "
|
|
"PRs were found."
|
|
)
|
|
if fields:
|
|
lines += [
|
|
"- Requested page size: 50",
|
|
f"- Pages fetched: {pages}",
|
|
f"- Returned PR count per page: {counts}",
|
|
f"- Final page proof: {final_proof}",
|
|
]
|
|
return "\n".join(lines) + "\n"
|
|
|
|
|
|
def _page(page, returned, per_page=50, has_more=False, final=True,
|
|
inventory_complete=None):
|
|
d = {
|
|
"page": page,
|
|
"per_page": per_page,
|
|
"returned_count": returned,
|
|
"has_more": has_more,
|
|
"is_final_page": final,
|
|
}
|
|
if inventory_complete is not None:
|
|
d["inventory_complete"] = inventory_complete
|
|
return d
|
|
|
|
|
|
class TestCompleteScanProven(unittest.TestCase):
|
|
def test_first_page_below_limit_passes(self):
|
|
result = assess_reconciliation_inventory_proof(
|
|
_report(),
|
|
pagination_evidence=[_page(1, 12)],
|
|
)
|
|
self.assertTrue(result["complete"])
|
|
self.assertEqual(result["reasons"], [])
|
|
|
|
def test_multiple_pages_to_final_passes(self):
|
|
report = _report(
|
|
pages="3", counts="50, 50, 12",
|
|
final_proof="has_more=false on page 3",
|
|
)
|
|
evidence = [
|
|
_page(1, 50, has_more=True, final=False),
|
|
_page(2, 50, has_more=True, final=False),
|
|
_page(3, 12),
|
|
]
|
|
result = assess_reconciliation_inventory_proof(
|
|
report, pagination_evidence=evidence
|
|
)
|
|
self.assertTrue(result["complete"])
|
|
|
|
def test_no_completeness_claim_needs_no_proof(self):
|
|
result = assess_reconciliation_inventory_proof(
|
|
_report(claim=False, fields=False),
|
|
pagination_evidence=None,
|
|
)
|
|
self.assertTrue(result["complete"])
|
|
|
|
|
|
class TestUnprovenScanFails(unittest.TestCase):
|
|
def test_first_page_exactly_at_limit_fails(self):
|
|
# 50 returned with per_page 50 and no final-page signal: the page
|
|
# limit was hit, so completeness is unproven.
|
|
evidence = [_page(1, 50, has_more=True, final=False)]
|
|
result = assess_reconciliation_inventory_proof(
|
|
_report(counts="50", final_proof="none"),
|
|
pagination_evidence=evidence,
|
|
)
|
|
self.assertFalse(result["complete"])
|
|
self.assertTrue(
|
|
any("final page" in r.lower() for r in result["reasons"])
|
|
)
|
|
|
|
def test_missing_pagination_evidence_fails(self):
|
|
result = assess_reconciliation_inventory_proof(
|
|
_report(), pagination_evidence=None
|
|
)
|
|
self.assertFalse(result["complete"])
|
|
self.assertTrue(
|
|
any("pagination" in r.lower() for r in result["reasons"])
|
|
)
|
|
|
|
def test_missing_report_fields_fails(self):
|
|
result = assess_reconciliation_inventory_proof(
|
|
_report(fields=False),
|
|
pagination_evidence=[_page(1, 12)],
|
|
)
|
|
self.assertFalse(result["complete"])
|
|
self.assertTrue(
|
|
any("requested page size" in r.lower()
|
|
for r in result["reasons"])
|
|
)
|
|
|
|
def test_page_count_mismatch_fails(self):
|
|
# Report claims 1 page; evidence shows 2 were fetched.
|
|
evidence = [
|
|
_page(1, 50, has_more=True, final=False),
|
|
_page(2, 3),
|
|
]
|
|
result = assess_reconciliation_inventory_proof(
|
|
_report(pages="1", counts="50"),
|
|
pagination_evidence=evidence,
|
|
)
|
|
self.assertFalse(result["complete"])
|
|
self.assertTrue(
|
|
any("pages fetched" in r.lower() for r in result["reasons"])
|
|
)
|
|
|
|
def test_per_page_count_mismatch_fails(self):
|
|
evidence = [_page(1, 12)]
|
|
result = assess_reconciliation_inventory_proof(
|
|
_report(counts="11"),
|
|
pagination_evidence=evidence,
|
|
)
|
|
self.assertFalse(result["complete"])
|
|
self.assertTrue(
|
|
any("count per page" in r.lower() for r in result["reasons"])
|
|
)
|
|
|
|
def test_evidence_page_missing_metadata_fails(self):
|
|
result = assess_reconciliation_inventory_proof(
|
|
_report(),
|
|
pagination_evidence=[{"page": 1}],
|
|
)
|
|
self.assertFalse(result["complete"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|