Files
gitea-agent/harness/tests/impl/rework/test_rework_publication.py
T

162 lines
9.1 KiB
Python

"""Regression tests for main-owned rework publication integration."""
import copy
import hashlib
import sys
import unittest
from pathlib import Path
from unittest.mock import Mock
from harness.tests._support.paths import HARNESS as ROOT
SCRIPT = ROOT / "agents/impl/skills/gitea-issue-rework/scripts"
sys.path.insert(0, str(SCRIPT))
sys.path.insert(0, str(ROOT))
import rework_publication as pub
from harness.lib import impl_operation as ops
HEAD = "a" * 40
def expected():
# Generate through the canonical renderer to avoid hand-authored bodies.
import rework_comment as rc
d = dict(owner="o", repository="r", issue=4, pr=5, review=6,
reviewed_head="b"*40, final_head=HEAD, contract_sha256="c"*64,
result="partial", findings=[dict(id="F1", status="resolved", classification="valid_fix",
changes="changed safely", verification="checks pass", reason="verified")],
local_checks="pass", runtime_browser="not run", commit_status="없음", actions_runs="없음")
dd, vals, fs, extras, cs, ar = rc.canon(d)
body = rc.render_body(dd, vals, fs, extras, cs, ar)
rh = hashlib.sha256(body.encode()).hexdigest()
corr = hashlib.sha256("\0".join(["o", "r", "4", "5", "6", "b"*40, HEAD, "c"*64]).encode()).hexdigest()
marker = f"<!-- gitea-agent:implementation-rework/v1 owner=o repository=r issue=4 pr=5 review=6 reviewed-head={'b'*40} head={HEAD} contract-sha256={'c'*64} result-sha256={rh} correlation={corr} -->\n"
return dict(body=body+marker, marker=marker, marker_free_body=body, result_sha256=rh, correlation=corr,
target=dict(owner="o", repository="r", issue=4, pr=5), review=6,
reviewed_head="b"*40, head=HEAD, contract_sha256="c"*64)
def setup_case(op="comment.create", payload=None, approval_flags=None):
if payload is None:
e=expected(); payload={"expected":e,"target":e["target"]}
intent=ops.Intent("v1", op, "o/r", 5, HEAD, payload)
approval=ops.Approval("v1", intent.digest, "approved", **(approval_flags or {}))
declaration={"mode":"none"}
calls=[]
adapter=ops.Adapter(preflight=lambda i: {"fresh":True,"scope":"exact"}, capability=True,
mutate=lambda i: calls.append(copy.deepcopy(i)) or {"acknowledgement":"ok"},
readback=lambda i: {"scope":"exact","head":HEAD}, verify=lambda i,p,o: True)
return intent,approval,declaration,calls,adapter
class PublicationTests(unittest.TestCase):
def test_invalid_body_target_head_approval_do_not_write(self):
cases = []
cases.append(setup_case('pr.patch', {'body': 'body', 'closing': '#4'}))
cases.append(setup_case('comment.create', {'expected': expected(), 'target': {'owner': 'evil', 'repository': 'r', 'issue': 4, 'pr': 5}}))
p = {'head': 'f' * 40}
cases.append(setup_case('push', p))
for intent, approval, decl, calls, adapter in cases:
with self.subTest(intent=intent.operation), self.assertRaises(Exception):
pub.execute_rework_operation(adapter, intent=intent, approval=approval, declaration=decl, coordinator=ops.Coordinator(), on_attempt=lambda x: None)
self.assertEqual(calls, [])
def test_single_mutation_and_fresh_adoption_at_boundary(self):
intent, approval, decl, calls, adapter = setup_case()
old = pub.require_adoption
try:
adopted = Mock(return_value=None)
pub.require_adoption = adopted
attempts = []
result = pub.execute_rework_operation(adapter, intent=intent, approval=approval, declaration=decl, coordinator=ops.Coordinator(), on_attempt=attempts.append)
self.assertEqual(result.status, 'verified')
self.assertEqual(len(calls), 1)
self.assertEqual(len(attempts), 1)
adopted.assert_called_once()
finally:
pub.require_adoption = old
def test_adoption_and_preflight_failure_have_zero_mutations(self):
intent, approval, decl, calls, adapter = setup_case()
old = pub.require_adoption
try:
pub.require_adoption = Mock(side_effect=ValueError('stale'))
r = pub.execute_rework_operation(adapter, intent=intent, approval=approval, declaration=decl, coordinator=ops.Coordinator(), on_attempt=lambda x: None)
self.assertEqual(r.status, 'partial')
self.assertEqual(calls, [])
adapter.preflight = lambda i: {'fresh': False}
r = pub.execute_rework_operation(adapter, intent=intent, approval=approval, declaration=decl, coordinator=ops.Coordinator(), on_attempt=lambda x: None)
self.assertEqual(r.status, 'partial')
self.assertEqual(calls, [])
finally:
pub.require_adoption = old
def test_uncertain_ack_readback_once_literal_true_required(self):
intent, approval, decl, calls, adapter = setup_case()
old = pub.require_adoption
try:
pub.require_adoption = Mock(return_value=None)
mutations = []
reads = []
adapter.mutate = lambda i: mutations.append(i) or (_ for _ in ()).throw(ops.UncertainAcknowledgement())
adapter.readback = lambda i: reads.append(i) or {'head': HEAD}
adapter.verify = lambda i, p, o: 1
result = pub.execute_rework_operation(adapter, intent=intent, approval=approval, declaration=decl, coordinator=ops.Coordinator(), on_attempt=lambda x: None)
self.assertEqual(result.status, 'partial')
self.assertEqual((len(mutations), len(reads)), (1, 1))
finally:
pub.require_adoption = old
def test_reuse_collects_fresh_state_without_any_mutation(self):
intent, approval, declaration, calls, adapter = setup_case()
reads = []
adapter.preflight = lambda value: reads.append('preflight') or {'fresh': True}
adapter.readback = lambda value: reads.append('readback') or {'exact': True}
adapter.verify_reuse = lambda value, before, after: after.get('exact') is True
result = pub.verify_rework_reuse(adapter, intent=intent, approval=approval, declaration=declaration)
self.assertEqual(result['action'], 'reuse')
self.assertEqual(reads, ['preflight', 'readback'])
self.assertEqual(calls, [])
def test_reuse_stale_preflight_blocks_even_when_callback_would_accept(self):
intent, approval, declaration, calls, adapter = setup_case()
reads = []
adapter.preflight = lambda value: {'fresh': False}
adapter.readback = lambda value: reads.append(True)
adapter.verify_reuse = lambda *args: True
with self.assertRaises(ValueError):
pub.verify_rework_reuse(adapter, intent=intent, approval=approval, declaration=declaration)
self.assertEqual(reads, [])
self.assertEqual(calls, [])
def test_canonical_comment_cannot_publish_to_different_intent_scope(self):
intent, approval, declaration, calls, adapter = setup_case()
for target, target_id in [('evil/r', 5), ('o/r', 999)]:
changed = ops.Intent('v1', intent.operation, target, target_id, HEAD, intent.payload)
approved = ops.Approval('v1', changed.digest, 'approved')
with self.subTest(target=target, target_id=target_id), self.assertRaises(ValueError):
pub.execute_rework_operation(adapter, intent=changed, approval=approved, declaration=declaration, coordinator=ops.Coordinator(), on_attempt=lambda record: None)
self.assertEqual(calls, [])
def test_unimplemented_client_cas_correction_blocks_before_write(self):
import rework_comment as rc
new = expected()
old = copy.deepcopy(new)
body = new['marker_free_body']
for label in ('Source Issue', 'Pull Request', 'Source Review'):
body = body.replace('- ' + label + ': ', '- ' + label + ': #')
digest = hashlib.sha256(body.encode()).hexdigest()
marker = new['marker'].replace(new['result_sha256'], digest)
old.update(body=body + marker, marker=marker, marker_free_body=body, result_sha256=digest)
payload = {'old_expected': old, 'new_expected': new, 'comment_id': 7, 'actor_id': 42}
intent = ops.Intent('v1', 'correction', 'o/r', 7, HEAD, payload)
approval = ops.Approval('v1', intent.digest, 'approved', correction=True)
calls = []
adapter = ops.Adapter(lambda value: {'fresh': True}, True, lambda value: calls.append('write'), lambda value: {'exact': True}, lambda *args: True)
with self.assertRaises(ValueError):
pub.execute_rework_operation(adapter, intent=intent, approval=approval, declaration={'mode': 'none'}, coordinator=ops.Coordinator(), on_attempt=lambda record: None)
self.assertEqual(calls, [])
def test_generic_comment_patch_rejected_and_correction_requires_explicit_approval(self):
intent, approval, decl, calls, adapter = setup_case('comment.patch', {'body': 'arbitrary'})
with self.assertRaises(ValueError):
pub.execute_rework_operation(adapter, intent=intent, approval=approval, declaration=decl, coordinator=ops.Coordinator(), on_attempt=lambda x: None)
self.assertEqual(calls, [])