Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a10780-6d41-7952-9718-dfbc1f41236f
149 lines
6.5 KiB
Python
149 lines
6.5 KiB
Python
import json
|
|
from pathlib import Path
|
|
import stat
|
|
import subprocess
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
from api.services.memo_dispatch import Dispatcher, Refused, token_file, transport, ORIGIN
|
|
from tests.test_memo_forwarding import envelope, receipt, Clock
|
|
|
|
|
|
def draft():
|
|
return envelope(memo_class='decision-record')
|
|
|
|
|
|
def test_queue_is_explicit_and_receipt_survives_restart(tmp_path):
|
|
calls = []
|
|
def send(*args):
|
|
calls.append(args)
|
|
return 201, receipt()
|
|
queue = tmp_path/'private'
|
|
worker = Dispatcher(queue, send=send, read_token=lambda: 'synthetic-token')
|
|
assert worker.enqueue(draft()).status == 'pending'
|
|
assert calls == []
|
|
assert worker.once().status == 'delivered'
|
|
assert len(calls) == 1
|
|
assert calls[0][0:2] == ('POST', ORIGIN+'/v1/memos')
|
|
reopened = Dispatcher(queue, send=send, read_token=lambda: 'synthetic-token')
|
|
assert reopened.enqueue(draft()).status == 'replay'
|
|
assert reopened.once() is None
|
|
assert reopened.counts() == {'delivered': 1}
|
|
assert stat.S_IMODE((queue/'forward.sqlite').stat().st_mode) == 0o600
|
|
assert b'synthetic-token' not in (queue/'forward.sqlite').read_bytes()
|
|
|
|
|
|
def test_rotation_retry_and_uncertain_native_create_blocks(tmp_path):
|
|
clock = Clock()
|
|
tokens = tmp_path/'token';tokens.write_text('first')
|
|
seen=[]
|
|
def send(method,url,body,headers):
|
|
seen.append(headers['Authorization'])
|
|
if len(seen)==1: raise OSError('hidden response and token')
|
|
return 409, {'error':'approval_create_unresolved'}
|
|
worker=Dispatcher(tmp_path/'private',send=send,read_token=lambda:token_file(tokens),clock=clock)
|
|
worker.enqueue(draft())
|
|
assert worker.once().status=='pending'
|
|
tokens.write_text('second');clock.now+=10
|
|
assert worker.once().status=='blocked'
|
|
clock.now+=1000
|
|
assert worker.once() is None
|
|
assert seen==['Bearer first','Bearer second']
|
|
assert 'hidden response' not in worker.path.read_bytes().decode(errors='ignore')
|
|
|
|
|
|
def test_lock_serializes_separate_processes(tmp_path):
|
|
worker=Dispatcher(tmp_path/'private')
|
|
with worker.lock():
|
|
result=subprocess.run([sys.executable,'-m','api.services.memo_dispatch','--queue',str(worker.directory),'once'],capture_output=True,text=True)
|
|
assert result.returncode==1
|
|
assert json.loads(result.stdout)=={'error':'dispatch_refused'}
|
|
|
|
|
|
def test_unsafe_custody_and_wrong_routing_refused(tmp_path):
|
|
unsafe=tmp_path/'unsafe';unsafe.mkdir(mode=0o755)
|
|
with pytest.raises(Refused): Dispatcher(unsafe)
|
|
private=tmp_path/'private';private.mkdir(mode=0o700)
|
|
target=tmp_path/'target';target.write_text('secret')
|
|
(private/'forward.sqlite').symlink_to(target)
|
|
with pytest.raises(Refused): Dispatcher(private)
|
|
worker=Dispatcher(tmp_path/'queue')
|
|
with pytest.raises(Refused): worker.enqueue(envelope())
|
|
assert worker.counts()=={}
|
|
|
|
|
|
def test_transport_refuses_arbitrary_destinations_before_io():
|
|
with pytest.raises(Refused): transport('POST','https://other.example/v1/memos',{}, {'Authorization':'Bearer hidden'})
|
|
|
|
|
|
def test_cli_errors_never_emit_input_content(tmp_path):
|
|
path=tmp_path/'bad';path.write_text('synthetic-secret')
|
|
result=subprocess.run([sys.executable,'-m','api.services.memo_dispatch','--queue',str(tmp_path/'private'),'enqueue','--envelope',str(path)],capture_output=True,text=True)
|
|
assert result.returncode==1 and 'synthetic-secret' not in result.stdout+result.stderr
|
|
|
|
|
|
def test_retained_lifecycle_handoff_has_no_invented_hub_relationship(tmp_path):
|
|
from api.services.informed_decision_contract import ContractError, validate_envelope
|
|
body=draft();body.pop('serves')
|
|
body['memo']['id']='review-12345678-1234-1234-1234-123456789abc'
|
|
body['idempotency_key']=body['memo']['id']
|
|
assert validate_envelope(body)==body
|
|
worker=Dispatcher(tmp_path/'private')
|
|
assert worker.enqueue(body).status=='pending'
|
|
assert 'serves' not in json.loads(worker.ledger.pose(body['idempotency_key'])['envelope_json'])
|
|
body['idempotency_key']='changed-key'
|
|
with pytest.raises(ContractError): validate_envelope(body)
|
|
|
|
|
|
def test_transport_suppresses_proxy_redirect_and_oversized_response(monkeypatch):
|
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
import threading
|
|
from api.services import memo_dispatch
|
|
calls=[]
|
|
mode=['redirect']
|
|
class Handler(BaseHTTPRequestHandler):
|
|
def log_message(self,*args): pass
|
|
def do_POST(self):
|
|
calls.append(self.path)
|
|
self.rfile.read(int(self.headers['Content-Length']))
|
|
if mode[0]=='redirect':
|
|
self.send_response(307);self.send_header('Location','/stolen');self.end_headers()
|
|
else:
|
|
self.send_response(200);self.end_headers();self.wfile.write(b'x'*(memo_dispatch.MAX_RESPONSE+1))
|
|
server=ThreadingHTTPServer(('127.0.0.1',0),Handler)
|
|
thread=threading.Thread(target=server.serve_forever,daemon=True);thread.start()
|
|
origin=f'http://127.0.0.1:{server.server_port}'
|
|
monkeypatch.setattr(memo_dispatch,'ORIGIN',origin)
|
|
monkeypatch.setenv('http_proxy','http://127.0.0.1:1')
|
|
try:
|
|
assert transport('POST',origin+'/v1/memos',{}, {'Authorization':'Bearer synthetic'})[0]==307
|
|
assert calls==['/v1/memos']
|
|
mode[0]='oversized'
|
|
with pytest.raises(OSError,match='transport_unavailable'):
|
|
transport('POST',origin+'/v1/memos',{}, {})
|
|
finally:
|
|
server.shutdown();server.server_close();thread.join(timeout=2)
|
|
|
|
|
|
def test_kubernetes_queue_init_handles_setgid_without_taking_over_data(tmp_path):
|
|
manifest = json.loads((Path(__file__).parents[1] / 'deploy/informed-decision/dispatcher.candidate.json').read_text())
|
|
pod = next(item for item in manifest['items'] if item['kind'] == 'Deployment')['spec']['template']['spec']
|
|
init = pod['initContainers'][0]
|
|
assert init['image'] == pod['containers'][0]['image']
|
|
tmp_path.chmod(0o2770)
|
|
private = tmp_path / 'private'
|
|
code = init['command'][2].replace('/data/private', str(private))
|
|
def run():
|
|
return subprocess.run([sys.executable, '-c', code], capture_output=True).returncode
|
|
assert run() == 0
|
|
assert stat.S_IMODE(private.stat().st_mode) == 0o700
|
|
retained = private / 'retained'
|
|
retained.write_text('existing evidence')
|
|
assert run() == 0
|
|
private.chmod(0o2700)
|
|
assert run() != 0 # Existing nonempty custody is never repaired silently.
|
|
retained.unlink()
|
|
assert run() == 0 # Empty directory left by the initial failed startup.
|
|
private.chmod(0o755)
|
|
assert run() != 0
|