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','session_id':'s','status':'completed','response_id':'reply','submission':proof},{'id':'reply','source':'agent','reply_to':'original','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()