Reconcile proven completed inputs without replay and hide internal fact notices
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
sys.path.insert(0,str(Path(__file__).resolve().parents[1]/'scripts'))
|
||||
import bridge_service
|
||||
import worker
|
||||
|
||||
class Process:
|
||||
def __init__(self,code):self.code=code
|
||||
def poll(self):return self.code
|
||||
|
||||
class RecoveryTests(unittest.TestCase):
|
||||
def fixture(self,root):
|
||||
path=Path(root)/'worker.json'
|
||||
cfg={'control':{},'session_id':'session','thread_id':'thread','state_file':str(path)}
|
||||
proof={'input_sha256':'hash','thread_id':'thread','turn_id':'turn'}
|
||||
state={'inflight':{'message_id':'original','submission':proof},'needs_reconciliation':True}
|
||||
worker.atomic_json(path,state)
|
||||
rows=[{'id':'original','session_id':'session','status':'completed','response_id':'reply','submission':proof},
|
||||
{'id':'reply','source':'agent','reply_to':'original','text':'answer'}]
|
||||
return cfg,path,rows
|
||||
def test_stopped_worker_terminal_proof_converges_without_submission(self):
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
cfg,path,rows=self.fixture(root)
|
||||
with patch.object(bridge_service.Client,'call',return_value={'messages':rows}) as call:
|
||||
self.assertTrue(bridge_service.recover_worker(cfg,Process(1)))
|
||||
self.assertEqual(call.call_count,1);self.assertTrue(call.call_args.args[0].startswith('/api/messages?'))
|
||||
state=json.loads(path.read_text());self.assertNotIn('inflight',state)
|
||||
self.assertIn('original',state['delivery_receipts'])
|
||||
def test_running_worker_and_locked_orphan_are_never_touched(self):
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
cfg,path,rows=self.fixture(root);before=path.read_bytes()
|
||||
with patch.object(bridge_service.Client,'call') as call:
|
||||
self.assertFalse(bridge_service.recover_worker(cfg,Process(None)));call.assert_not_called()
|
||||
identity=json.dumps({'control':cfg['control'],'session_id':cfg['session_id']},sort_keys=True)
|
||||
with worker.session_lock(identity):
|
||||
self.assertFalse(bridge_service.recover_worker(cfg,Process(1)))
|
||||
call.assert_not_called()
|
||||
self.assertEqual(path.read_bytes(),before)
|
||||
|
||||
def test_unrelated_reply_cannot_release_the_queue(self):
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
cfg,path,rows=self.fixture(root);before=path.read_bytes()
|
||||
rows[1]['reply_to']='other-input'
|
||||
with patch.object(bridge_service.Client,'call',return_value={'messages':rows}):
|
||||
self.assertFalse(bridge_service.recover_worker(cfg,Process(1)))
|
||||
self.assertEqual(path.read_bytes(),before)
|
||||
def test_offline_or_unmatched_proof_preserves_original(self):
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
cfg,path,rows=self.fixture(root);before=path.read_bytes()
|
||||
with patch.object(bridge_service.Client,'call',side_effect=OSError('offline')):
|
||||
self.assertFalse(bridge_service.recover_worker(cfg,Process(1)))
|
||||
for field in ('input_sha256','thread_id','turn_id'):
|
||||
changed=json.loads(json.dumps(rows));changed[0]['submission'][field]='other'
|
||||
with patch.object(bridge_service.Client,'call',return_value={'messages':changed}):
|
||||
self.assertFalse(bridge_service.recover_worker(cfg,Process(1)))
|
||||
self.assertEqual(path.read_bytes(),before)
|
||||
|
||||
if __name__=='__main__':unittest.main()
|
||||
Reference in New Issue
Block a user