Files
agent-workbench/tests/test_decision_fleet.py
T

77 lines
5.0 KiB
Python

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()