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

163 lines
6.4 KiB
Python

import json
import threading
import unittest
import sys
from pathlib import Path
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.error import HTTPError
from urllib.request import Request, urlopen
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / 'agents/impl/skills/gitea-issue-implementation/scripts'))
import delivery_response as dr
def make_handler():
class FakeHandler(BaseHTTPRequestHandler):
status = 200
body = b'{"ok":true}'
calls = 0
def do_GET(self): self.respond()
def do_POST(self): self.respond()
def do_PATCH(self): self.respond()
def respond(self):
type(self).calls += 1
self.send_response(type(self).status)
self.end_headers()
self.wfile.write(type(self).body)
def log_message(self, format, *args): pass
return FakeHandler
FakeHandler = make_handler()
class DeliveryResponseTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.server = HTTPServer(('127.0.0.1', 0), FakeHandler)
cls.thread = threading.Thread(target=cls.server.serve_forever, daemon=True)
cls.thread.start()
cls.url = 'http://127.0.0.1:%d/' % cls.server.server_port
@classmethod
def tearDownClass(cls):
cls.server.shutdown()
cls.server.server_close()
def setUp(self):
FakeHandler.calls = 0
FakeHandler.body = b'{"ok":true}'
FakeHandler.status = 200
def request(self, method, expected):
def send():
req = Request(self.url, method=method)
try:
with urlopen(req, timeout=2) as response:
return response.status, response.read(1024 * 1024 + 1)
except HTTPError as e:
return e.code, e.read(1024 * 1024 + 1)
return dr.deliver_once(send, expected_status=expected)
def test_endpoint_specific_success_statuses(self):
for method, status in [('GET', 200), ('POST', 201), ('PATCH', 201)]:
FakeHandler.status = status
self.assertEqual(self.request(method, status), {'ok': True})
def test_rejects_invalid_expected_status_before_send(self):
calls = []
for invalid in (None, True, 0, 700, '201'):
with self.subTest(invalid=invalid):
with self.assertRaises(dr.InvalidExpectationError):
dr.deliver_once(lambda: calls.append(1), expected_status=invalid)
self.assertEqual(calls, [])
def test_unexpected_success_status_uncertain_not_heuristic(self):
FakeHandler.status = 202
with self.assertRaises(dr.AcknowledgementUncertainError):
self.request('POST', 201)
self.assertEqual(FakeHandler.calls, 1)
def test_reconstructed_method_only_heuristic_rejects_valid_patch_ack(self):
# Reconstructed historical predicate, NOT a prior runtime RED run.
old_expected = 201 if 'PATCH' == 'POST' else 200
self.assertNotEqual(201, old_expected)
FakeHandler.status = 201
self.assertEqual(self.request('PATCH', 201), {'ok': True})
self.assertEqual(FakeHandler.calls, 1)
def test_failure_status_cannot_be_configured_as_success(self):
calls = []
for status in (100, 199, 300, 400, 500, 599):
with self.subTest(status=status), self.assertRaises(dr.InvalidExpectationError):
dr.deliver_once(lambda: calls.append(1) or (status, b'{}'), expected_status=status)
self.assertEqual(calls, [])
def test_raised_auth_or_redirect_error_is_rejected_not_uncertain(self):
for status in (300, 302, 304, 399, 401, 403):
calls = []
def send():
calls.append(1)
raise HTTPError('http://example.invalid/private', status, 'sensitive detail', {}, None)
with self.subTest(status=status):
with self.assertRaises(dr.RejectedResponseError) as caught:
dr.deliver_once(send, expected_status=201)
self.assertEqual(calls, [1])
self.assertNotIn('sensitive detail', str(caught.exception))
def test_sender_explicit_rejection_is_not_reclassified_as_uncertain(self):
def send():
raise dr.RejectedResponseError('sensitive detail')
with self.assertRaises(dr.RejectedResponseError) as caught:
dr.deliver_once(send, expected_status=201)
self.assertNotIn('sensitive detail', str(caught.exception))
def test_tls_certificate_rejection_is_not_reclassified_as_uncertain(self):
import ssl
from urllib.error import URLError
for error in (ssl.SSLCertVerificationError('sensitive detail'),
URLError(ssl.SSLCertVerificationError('sensitive detail'))):
def send():
raise error
with self.subTest(error=type(error).__name__), self.assertRaises(dr.RejectedResponseError):
dr.deliver_once(send, expected_status=201)
def test_auth_and_redirect_rejected(self):
for status in (401, 403, 302):
with self.subTest(status=status):
FakeHandler.status = status
with self.assertRaises(dr.RejectedResponseError):
self.request('POST', 201)
def test_bad_json_shape_and_size_are_uncertain(self):
for body in (b'{', b'[]', b'null', b'{"x":NaN}', b'"x"', b' ' * (1024 * 1024 + 1)):
with self.subTest(body=body[:12]):
FakeHandler.body = body
FakeHandler.status = 201
with self.assertRaises(dr.AcknowledgementUncertainError):
self.request('POST', 201)
def test_transport_error_once_and_sanitized_uncertainty(self):
calls = []
def send():
calls.append(1)
raise OSError('secret token should not leak')
with self.assertRaises(dr.AcknowledgementUncertainError) as caught:
dr.deliver_once(send, expected_status=201)
self.assertEqual(len(calls), 1)
self.assertNotIn('secret token', str(caught.exception))
self.assertIn('reconcil', str(caught.exception).lower())
def test_control_exceptions_propagate(self):
for exc in (KeyboardInterrupt(), SystemExit()):
with self.subTest(exc=type(exc).__name__):
with self.assertRaises(type(exc)):
dr.deliver_once(lambda: (_ for _ in ()).throw(exc), expected_status=200)
if __name__ == '__main__':
unittest.main()