Schedule consultation bridges on demand and preserve delivery evidence
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
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 decision_fleet
|
||||
from test_decision_bridge import Process
|
||||
import test_decision_bridge
|
||||
|
||||
class DemandTests(unittest.TestCase):
|
||||
def setup_fleet(self,root):return test_decision_bridge.DecisionBridgeTests().fixture(root)
|
||||
def test_idle_bindings_start_nothing(self):
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
cfg,b=self.setup_fleet(root);b['demand']['queued']=0
|
||||
f=decision_fleet.Fleet(cfg)
|
||||
with patch.object(decision_fleet.subprocess,'Popen') as spawn:
|
||||
self.assertEqual(f.tick([b])['active'],0);spawn.assert_not_called()
|
||||
def test_one_message_cycle_releases_and_old_waiter_wins(self):
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
cfg,b=self.setup_fleet(root);f=decision_fleet.Fleet(cfg)
|
||||
other={**b,'decision_id':'z','session_id':'z','thread_id':'22222222-2222-4222-8222-222222222222'}
|
||||
with patch.object(decision_fleet.subprocess,'Popen',side_effect=lambda *a,**k:Process()) as spawn:
|
||||
with patch.object(decision_fleet.time,'time',return_value=100):f.tick([b,other])
|
||||
self.assertIn('--once',spawn.call_args.args[0])
|
||||
for p in f.children['d']['processes'].values():p.code=0
|
||||
with patch.object(decision_fleet.time,'time',return_value=110):f.tick([b,other])
|
||||
self.assertEqual(set(f.children),{'z'});f.close()
|
||||
def test_old_ordinary_precedes_fresh_human_after_restart(self):
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
cfg,b=self.setup_fleet(root);b['demand']={**b['demand'],'human_queued':0,'oldest_queued_at':1}
|
||||
f=decision_fleet.Fleet(cfg)
|
||||
other={**b,'decision_id':'a','session_id':'a','thread_id':'22222222-2222-4222-8222-222222222222','demand':{'queued':1,'human_queued':1}}
|
||||
with patch.object(decision_fleet.subprocess,'Popen',side_effect=lambda *a,**k:Process()):
|
||||
with patch.object(decision_fleet.time,'time',return_value=200):f.tick([b,other])
|
||||
self.assertEqual(set(f.children),{'d'});f.close()
|
||||
saved=json.loads((f.root/'schedule.json').read_text());self.assertEqual(saved['a']['waiting_since'],200)
|
||||
def test_uncertain_after_restart_only_mirrors(self):
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
cfg,b=self.setup_fleet(root);b['demand']['uncertain']=1
|
||||
f=decision_fleet.Fleet(cfg)
|
||||
with patch.object(decision_fleet.subprocess,'Popen',side_effect=lambda *a,**k:Process()) as spawn:
|
||||
result=f.tick([b]);self.assertEqual(set(f.children['d']['processes']),{'mirror'})
|
||||
self.assertEqual(spawn.call_count,1);self.assertEqual(result['status'],'failed');f.close()
|
||||
def test_fact_reply_waits_for_native_terminal_and_final_mirror(self):
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
cfg,b=self.setup_fleet(root);b['demand']['queued']=0
|
||||
cfg['notification_state_file']=str(Path(root)/'notifier.json')
|
||||
path=Path(cfg['notification_state_file'])
|
||||
record={'decision_id':'d','event_kind':'fact_answered','processing_done':False}
|
||||
decision_fleet.atomic_json(path,{'deliveries':{'receipt':record}})
|
||||
f=decision_fleet.Fleet(cfg)
|
||||
with patch.object(decision_fleet.subprocess,'Popen',side_effect=lambda *a,**k:Process()) as spawn:
|
||||
f.tick([b]);self.assertEqual(set(f.children['d']['processes']),{'mirror'})
|
||||
f.children['d']['processes']['mirror'].code=0
|
||||
record.update(processing_done=True,completed_at=123)
|
||||
decision_fleet.atomic_json(path,{'deliveries':{'receipt':record}})
|
||||
f.tick([b]);self.assertEqual(spawn.call_count,2)
|
||||
f.children['d']['processes']['mirror'].code=0
|
||||
self.assertEqual(f.tick([b])['active'],0)
|
||||
self.assertEqual(f.tick([b])['active'],0);self.assertEqual(spawn.call_count,2)
|
||||
f.close()
|
||||
|
||||
def test_terminal_proof_clears_local_without_replaying(self):
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
path=Path(root)/'state.json';proof={'thread_id':'t','input_sha256':'h','turn_id':'turn'}
|
||||
decision_fleet.atomic_json(path,{'inflight':{'message_id':'original','submission':proof},'needs_reconciliation':True})
|
||||
class Client:
|
||||
def call(self,*args):return {'messages':[{'id':'original','status':'completed','response_id':'reply','submission':proof},{'id':'reply','text':'done'}]}
|
||||
self.assertFalse(decision_fleet.reconcile_completed(path,Client(),'s','different-thread'))
|
||||
self.assertTrue(decision_fleet.busy(path))
|
||||
self.assertTrue(decision_fleet.reconcile_completed(path,Client(),'s','t'))
|
||||
self.assertFalse(decision_fleet.busy(path));self.assertIn('original',json.loads(path.read_text())['delivery_receipts'])
|
||||
|
||||
if __name__=='__main__':unittest.main()
|
||||
Reference in New Issue
Block a user