[verified] Support explicit review-only provider counters

This commit is contained in:
2026-10-13 07:48:51 +00:00
parent dbd381a41b
commit e1be0dd526
3 changed files with 88 additions and 3 deletions
@@ -105,6 +105,74 @@ class ReworkReadbackTests(unittest.TestCase):
self.underlying['comments'] += 1
self.pr['updated_at'] = self.underlying['updated_at'] = T1
def test_review_only_projection_preserves_inline_records_without_counting_them(self):
module = self.load('rework_readback')
contract = copy.deepcopy(self.contract)
contract['inline']['components'] = ['reviews']
adapter = module.ReworkReadbackAdapter(self.request, origin=ORIGIN+'/api/v1',
credential_source='$HERMES_HOME/.env:GITEA_TOKEN', actor_id=42, provider_contract=contract)
self.pr['review_comments'] = 1
self.reviews[0]['comments_count'] = 0
snapshot = adapter.snapshot(self.expected)
self.assertEqual(snapshot['normalized_counts']['inline'], 0)
self.reviews[0]['comments_count'] = 1
self.inline = [{'id':801,'body':'inline','created_at':T0,'updated_at':T0,
'user':{'id':33,'login':'gitea-review-agent'}}]
self.pr['review_comments'] = 1
self.pr['comments'] = self.underlying['comments'] = 2
snapshot = adapter.snapshot(self.expected)
self.assertEqual(snapshot['normalized_counts']['inline'], 1)
def test_review_only_projection_rejects_counter_mismatch(self):
module = self.load('rework_readback')
contract = copy.deepcopy(self.contract)
contract['inline']['components'] = ['reviews']
adapter = module.ReworkReadbackAdapter(self.request, origin=ORIGIN+'/api/v1',
credential_source='$HERMES_HOME/.env:GITEA_TOKEN', actor_id=42, provider_contract=contract)
self.reviews[0]['comments_count'] = 1
self.inline = [{'id':801,'body':'inline','created_at':T0,'updated_at':T0,
'user':{'id':33,'login':'gitea-review-agent'}}]
self.pr['review_comments'] = 0
with self.assertRaisesRegex(ValueError, 'inline provider counter crosscheck failed'):
adapter.snapshot(self.expected)
def test_inline_component_contract_validation_remains_strict(self):
module = self.load('rework_readback')
for parts in ([], None, 'reviews', ['inline','inline'], ['reviews','reviews'],
['unknown'], ['reviews','unknown'], ['reviews','inline','reviews']):
contract = copy.deepcopy(self.contract)
contract['inline']['components'] = parts
with self.subTest(components=parts), self.assertRaises(ValueError):
module.ReworkReadbackAdapter(self.request, origin=ORIGIN+'/api/v1',
credential_source='$HERMES_HOME/.env:GITEA_TOKEN', actor_id=42, provider_contract=contract)
def test_review_only_projection_retains_inline_evidence_rejections(self):
module = self.load('rework_readback')
contract = copy.deepcopy(self.contract)
contract['inline']['components'] = ['reviews']
adapter = module.ReworkReadbackAdapter(self.request, origin=ORIGIN+'/api/v1',
credential_source='$HERMES_HOME/.env:GITEA_TOKEN', actor_id=42, provider_contract=contract)
self.reviews[0]['comments_count'] = 1
self.inline = [{'id':801,'body':'inline','created_at':T0,'updated_at':T0,
'user':{'id':33,'login':'gitea-review-agent'}}]
self.pr['review_comments'] = 2
with self.assertRaises(ValueError): adapter.snapshot(self.expected)
self.pr['review_comments'] = 1
self.inline = []
with self.assertRaisesRegex(ValueError, 'per-review inline count mismatch'):
adapter.snapshot(self.expected)
self.inline = [{'id':801,'body':'inline','created_at':T0,'updated_at':T0,
'user':{'id':33,'login':'gitea-review-agent'}},
{'id':801,'body':'duplicate','created_at':T0,'updated_at':T0,
'user':{'id':33,'login':'gitea-review-agent'}}]
with self.assertRaisesRegex(ValueError, 'duplicate'):
adapter.snapshot(self.expected)
self.inline = [{'id':801,'body':'inline','created_at':T0,'updated_at':T0,
'user':{'id':33,'login':'gitea-review-agent'}}]
self.overrides = {'/repos/acme/demo/pulls/9/reviews/301/comments?page=1&limit=50':
(self.inline, {'X-Total-Count':'2'}, 200)}
with self.assertRaises(ValueError): adapter.snapshot(self.expected)
def test_snapshot_rejects_wrong_provider_bindings(self):
cases = [('repo',['id'],True), ('repo',['full_name'],'elsewhere/demo'),
('repo',['html_url'],'https://foreign.invalid/acme/demo'),