Files
gitea-agent/harness/tests/impl/test_rework_readback_transport.py
T

195 lines
9.2 KiB
Python

import importlib.util
import os
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
SCRIPT = Path(__file__).parents[2] / 'agents/impl/skills/gitea-issue-rework/scripts/rework_comment.py'
class ReworkReadbackTransportTests(unittest.TestCase):
def module(self):
path = SCRIPT.with_name('rework_readback_cli.py')
self.assertTrue(path.is_file(), 'canonical live readback bridge is missing')
spec = importlib.util.spec_from_file_location('transport_under_test', path)
assert spec is not None and spec.loader is not None
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def test_role_token_requires_private_profile_file(self):
module = self.module()
with tempfile.TemporaryDirectory() as directory:
home = Path(directory)
with self.assertRaises(ValueError): module.load_role_token(home)
source = home / '.env'
source.write_text('GITEA_TOKEN=' + 's' * 48 + '\n')
source.chmod(0o644)
with self.assertRaises(ValueError): module.load_role_token(home)
source.chmod(0o600)
self.assertEqual(module.load_role_token(home), 's' * 48)
source.unlink()
external = home / 'external'
external.write_text('GITEA_TOKEN=' + 's' * 48)
external.chmod(0o600)
source.symlink_to(external)
with self.assertRaises(ValueError): module.load_role_token(home)
def test_http_transport_get_only_redirects_and_secret_echo_are_rejected(self):
module = self.module()
self.assertTrue(callable(getattr(module, 'RestTransport', None)), 'read-only HTTP transport missing')
import http.server
import threading
import urllib.request
requests = []
secret = 'z' * 48
class Handler(http.server.BaseHTTPRequestHandler):
def log_message(self, format, *args): pass
def do_GET(self):
requests.append((self.path, self.headers.get('Authorization') == 'token ' + secret))
if self.path == '/api/v1/version':
self.send_response(302)
self.send_header('Location', 'https://outside.invalid/user')
self.end_headers()
return
body = (b'{"credential":"' + secret.encode() + b'"}'
if self.path.endswith('/secret') else b'{"id":42,"login":"gitea-impl-agent"}')
self.send_response(200)
self.send_header('Content-Type', 'application/json')
if self.path.endswith('/duplicate'):
self.send_header('X-Total-Count', '1')
self.send_header('X-Total-Count', '2')
self.end_headers()
self.wfile.write(body)
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, *args, **kwargs): return None
server = http.server.ThreadingHTTPServer(('127.0.0.1', 0), Handler)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
inner = urllib.request.build_opener(NoRedirect())
class Proxy:
def open(self, request, timeout):
self_request = urllib.request.Request(
'http://127.0.0.1:' + str(server.server_port) + request.selector,
headers=dict(request.header_items()), method=request.get_method())
response = inner.open(self_request, timeout=timeout)
original = request.full_url
class Response:
status = response.status
headers = response.headers
def geturl(self): return original
def read1(self, n): return response.read1(n)
def __enter__(self): return self
def __exit__(self, *args): response.close()
return Response()
try:
client = module.RestTransport(secret, opener=Proxy())
data, _, status = client('/user')
self.assertEqual(status, 200)
self.assertEqual(data['id'], 42)
self.assertEqual(client('/version')[2], 302)
with self.assertRaises(ValueError) as raised:
client('/repos/acme/widget/secret')
self.assertNotIn(secret, str(raised.exception))
with self.assertRaises(ValueError): client('/repos/acme/widget/duplicate')
self.assertTrue(all(authenticated for _, authenticated in requests))
self.assertEqual(len(requests), 4)
finally:
server.shutdown()
server.server_close()
thread.join(timeout=3)
def test_output_admission_preserves_receipt_on_failure_and_rejects_links(self):
module = self.module()
self.assertTrue(callable(getattr(module, 'OutputFile', None)), 'pinned output writer missing')
with tempfile.TemporaryDirectory() as directory:
receipt = Path(directory) / 'receipt.json'
receipt.write_text('original')
receipt.chmod(0o600)
with module.OutputFile(receipt): pass
self.assertEqual(receipt.read_text(), 'original')
with module.OutputFile(receipt) as output:
output.write({'status': 'verified'})
self.assertIn('verified', receipt.read_text())
self.assertEqual(receipt.stat().st_mode & 0o777, 0o600)
foreign = Path(directory) / 'foreign'
foreign.write_text('preserve')
receipt.unlink()
receipt.symlink_to(foreign)
with self.assertRaises(ValueError): module.OutputFile(receipt)
self.assertEqual(foreign.read_text(), 'preserve')
def test_http_operation_budget_stops_before_next_get(self):
module = self.module()
from unittest.mock import patch
clock = [0]
calls = []
class Opener:
def open(self, request, timeout):
calls.append(request.full_url)
class Response:
status = 200
headers = {'Content-Type': 'application/json'}
emitted = False
def geturl(self): return request.full_url
def read1(self, n):
if self.emitted: return b''
self.emitted = True
return b'{"id":42}'
def __enter__(self): return self
def __exit__(self, *args): pass
return Response()
with patch.object(module.time, 'monotonic', side_effect=lambda: clock[0]):
client = module.RestTransport('b' * 48, opener=Opener())
self.assertEqual(client('/user')[2], 200)
clock[0] = 121
with self.assertRaises(ValueError): client('/version')
self.assertEqual(len(calls), 1)
def test_live_bridge_rejects_unbound_provider_before_transport(self):
module = self.module()
self.assertTrue(callable(getattr(module, 'run_live', None)), 'live verifier bridge missing')
import hashlib
from types import SimpleNamespace
from unittest.mock import patch
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
expected = root / 'expected.json'
expected.write_text('{}')
contract = root / 'provider.json'
contract.write_text('{"schema":"test-contract"}')
trusted = hashlib.sha256(contract.read_bytes()).hexdigest()
self.assertEqual(module.read_bound_json(contract, trusted)['schema'], 'test-contract')
receipt = root / 'receipt.json'
receipt.write_text('preserve')
receipt.chmod(0o600)
args = SimpleNamespace(cmd='snapshot', expected=str(expected),
provider_contract=str(contract), provider_contract_sha256='0' * 64,
actor_id=42, output=str(receipt))
with patch.object(module, 'RestTransport') as transport:
with patch.object(module, 'read_bound_json', wraps=module.read_bound_json) as reader:
with self.assertRaises(ValueError): module.run_live(args, lambda *a, **k: None)
self.assertGreaterEqual(reader.call_count, 2)
transport.assert_not_called()
self.assertEqual(receipt.read_text(), 'preserve')
def test_existing_command_exposes_readonly_snapshot(self):
result = subprocess.run([sys.executable, '-B', str(SCRIPT), 'snapshot', '--help'],
capture_output=True, text=True, timeout=10)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn('--provider-contract', result.stdout)
self.assertIn('--actor-id', result.stdout)
def test_existing_command_exposes_live_verification(self):
result = subprocess.run([sys.executable, '-B', str(SCRIPT), 'verify-live', '--help'],
capture_output=True, text=True, timeout=10)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn('--baseline-sha256', result.stdout)