"""Handoff records remain durable evidence, not a delivery confirmation.""" import json from pathlib import Path import tempfile import unittest from unittest import mock from gui_runtime import handoff_pending_reply_file class OfflineEvidenceTests(unittest.TestCase): def test_legacy_visual_queue_upgrades_to_envelope_and_retains_full_evidence(self): with tempfile.TemporaryDirectory() as tmp: root=Path(tmp) path=root/'pending_replies.json' state={'stage':'receipt_check','send_state':'sent_uncommitted','reply_text':'合成发送正文','send_dispatched_at':123.0} path.write_text(json.dumps({'task':state,'other':{'stage':'queued'}}),encoding='utf-8') with mock.patch('queue_log.application_data_dir',return_value=root): result=handoff_pending_reply_file(['task'],str(path),confirm_uncertain=True) self.assertEqual(result['handed_off'],['task']) saved=json.loads(path.read_text(encoding='utf-8')) self.assertEqual(saved['pending'],{'other':{'stage':'queued'}}) record=saved['manual_handoffs']['task'] self.assertEqual(record['state'],state) self.assertTrue(record['receipt_checked_by_user']) self.assertFalse(record['delivery_confirmed']) self.assertGreater(record['handed_off_at'],0) def test_write_failure_preserves_original_queue_and_evidence(self): with tempfile.TemporaryDirectory() as tmp: root=Path(tmp) path=root/'pending_replies.json' original=json.dumps({'pending':{'task':{'send_state':'uncertain'}},'manual_handoffs':{'old':{'state':{}}}}) path.write_text(original,encoding='utf-8') with mock.patch('gui_runtime.os.replace',side_effect=OSError('synthetic disk error')): result=handoff_pending_reply_file(['task'],str(path),confirm_uncertain=True) self.assertIn('error',result) self.assertFalse(result['handed_off']) self.assertEqual(path.read_text(encoding='utf-8'),original) try: from protocol_engine import ProtocolBot,session_key from test_native_protocol_pipeline import Database,Sender,ACCOUNT,CONV,message except ImportError: ProtocolBot=None @unittest.skipIf(ProtocolBot is None,'visual-only distribution has no protocol engine') class ProtocolEvidenceTests(unittest.TestCase): def setUp(self): self.tmp=tempfile.TemporaryDirectory() self.addCleanup(self.tmp.cleanup) self.root=Path(self.tmp.name) self.db=Database();self.sender=Sender(self.db) for patcher in (mock.patch('protocol_engine.application_data_dir',return_value=self.root), mock.patch('queue_log.application_data_dir',return_value=self.root)): patcher.start();self.addCleanup(patcher.stop) self.bot=self.make_bot() self.key=session_key(ACCOUNT,CONV) self.state={'account':ACCOUNT,'conv_id':CONV,'stage':'receipt_check','send_state':'uncertain', 'reply_text':'可能已发送的回复','dedup_key':message()['dedup_key']} self.bot._pending[self.key]=dict(self.state) self.bot.needs_attention=True self.bot._save() def make_bot(self): return ProtocolBot(db=self.db,sender=self.sender,identity={'accountId':ACCOUNT,'pid':1}, path=self.root/'queue.json',generate=lambda _: '合成回复',log=lambda _:None) def test_takeover_record_survives_restart_and_poll_without_becoming_pending(self): self.bot.handoff_pending_replies([self.key],confirm_uncertain=True) restored=self.make_bot() restored.poll_once() self.assertFalse(restored._pending) record=restored._manual_handoffs[self.key] self.assertEqual(record['state'],self.state) self.assertTrue(record['receipt_checked_by_user']) self.assertFalse(record['delivery_confirmed']) self.assertFalse(self.sender.calls) saved=json.loads(restored.path.read_text(encoding='utf-8')) self.assertEqual(saved['manual_handoffs'][self.key],record) def test_failed_persist_rolls_back_memory_and_keeps_pause(self): original=self.bot.path.read_text(encoding='utf-8') with mock.patch.object(self.bot,'_save',side_effect=OSError('synthetic disk error')): with self.assertRaises(OSError): self.bot.handoff_pending_replies([self.key],confirm_uncertain=True) self.assertEqual(self.bot._pending[self.key],self.state) self.assertTrue(self.bot.needs_attention) self.assertFalse(self.bot._manual_handoffs) self.assertEqual(self.bot.path.read_text(encoding='utf-8'),original) self.assertFalse(self.sender.calls) if __name__=='__main__': unittest.main()