"""Explicit takeover never confirms/retries a possibly sent response.""" import json from pathlib import Path import queue import tempfile from types import SimpleNamespace import unittest from unittest import mock from gui_runtime import BotThread, handoff_pending_reply_file class RuntimeHandoffTests(unittest.TestCase): def test_handoff_during_startup_keeps_takeover_semantics(self): thread = BotThread(queue.Queue(), '合成回复', .2) result = thread.handoff_pending_tasks(['pending-1']) self.assertEqual(result['scheduled'], ['pending-1']) bot = SimpleNamespace(handoff_pending_replies=mock.Mock(return_value={'handed_off': ['pending-1']}), cancel_pending_replies=mock.Mock()) thread._drain_queued_handoffs(bot) bot.handoff_pending_replies.assert_called_once_with({'pending-1'}, confirm_uncertain=False) bot.cancel_pending_replies.assert_not_called() thread._drain_queued_handoffs(bot) bot.handoff_pending_replies.assert_called_once() def test_live_protocol_takeover_uses_distinct_engine_action(self): thread = BotThread(queue.Queue(), '合成回复', .2) bot = SimpleNamespace(handoff_pending_replies=mock.Mock(return_value={'handed_off': ['pending-1']}), cancel_pending_replies=mock.Mock()) thread.bot = bot self.assertEqual(thread.handoff_pending_tasks(['pending-1'])['handed_off'], ['pending-1']) bot.cancel_pending_replies.assert_not_called() def test_visual_engine_falls_back_to_guarded_cancellation(self): thread = BotThread(queue.Queue(), '合成回复', .2) thread.bot = SimpleNamespace(cancel_pending_replies=mock.Mock(return_value={'deleted': ['pending-1'], 'protected': ['sending']})) result = thread.handoff_pending_tasks(['pending-1', 'sending']) self.assertEqual(result['handed_off'], ['pending-1']) self.assertEqual(result['protected'], ['sending']) def test_offline_takeover_preserves_dedup_and_does_not_claim_delivery(self): with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) path = root / 'pending_replies.protocol.json' raw = {'transport': 'protocol', 'pending': { 'unknown': {'send_state': 'uncertain', 'dedup_key': 'event-1', 'account': '100'}, 'sending': {'send_state': 'sending', 'dedup_key': 'event-2', 'account': '100'}, }, 'seen': {'previous': 'event-0'}, 'contactBlocked': {}} path.write_text(json.dumps(raw), encoding='utf-8') journal = root / 'protocol_sends.sqlite3' journal.write_bytes(b'synthetic unchanged journal') with mock.patch('queue_log.application_data_dir', return_value=root): result = handoff_pending_reply_file(['unknown', 'sending', 'absent'], str(path), confirm_uncertain=True) self.assertEqual(result['handed_off'], ['unknown']) self.assertEqual(result['protected'], ['sending']) self.assertEqual(result['missing'], ['absent']) saved = json.loads(path.read_text(encoding='utf-8')) self.assertEqual(saved['seen'], {'previous': 'event-0', 'unknown': 'event-1'}) self.assertEqual(saved['pending'], {'sending': raw['pending']['sending']}) self.assertEqual(journal.read_bytes(), b'synthetic unchanged journal') try: from protocol_engine import ProtocolBot, session_key from test_native_protocol_pipeline import Database, Sender, message, ACCOUNT, CONV except ImportError: ProtocolBot = None @unittest.skipIf(ProtocolBot is None, 'visual-only distribution has no protocol engine') class ProtocolHandoffTests(unittest.TestCase): def setUp(self): self.temp = tempfile.TemporaryDirectory() self.addCleanup(self.temp.cleanup) self.root = Path(self.temp.name) self.db = Database() self.sender = Sender(self.db) self.sender.lookup = mock.Mock(return_value=None) self.generate = mock.Mock(return_value='合成回复') 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), mock.patch('socket.create_connection', side_effect=AssertionError('network forbidden')), ): patcher.start() self.addCleanup(patcher.stop) self.bot = self.make_bot() self.key = session_key(ACCOUNT, CONV) self.bot._pending[self.key] = dict(account=ACCOUNT, conv_id=CONV, send_state='uncertain', stage='receipt_check', dedup_key=message()['dedup_key'], reply_text='可能已发送的草稿') 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=self.generate, log=lambda _: None) def test_explicit_takeover_releases_pause_without_send_or_receipt_change(self): self.sender.lookup.reset_mock() result = self.bot.handoff_pending_replies([self.key], confirm_uncertain=True) self.assertEqual(result['handed_off'], [self.key]) self.assertFalse(self.bot.needs_attention) self.assertNotIn(self.key, self.bot._pending) self.assertEqual(self.bot._seen[self.key], message()['dedup_key']) self.sender.lookup.assert_not_called() self.assertFalse(self.sender.calls) self.generate.assert_not_called() self.db.events = [message()] self.bot.poll_once() self.assertFalse(self.bot._pending) self.assertFalse(self.sender.calls) restored = self.make_bot() self.db.events = [message()] restored.poll_once() self.assertFalse(restored.needs_attention) self.assertFalse(restored._pending) self.assertFalse(self.sender.calls) def test_other_uncertain_tasks_keep_pause(self): other = session_key(ACCOUNT, 'S:100_201') self.bot._pending[other] = dict(self.bot._pending[self.key], conv_id='S:100_201') self.bot.handoff_pending_replies([self.key], confirm_uncertain=True) self.assertTrue(self.bot.needs_attention) self.assertIn(other, self.bot._pending) self.assertFalse(self.sender.calls) def test_active_send_and_other_account_stay_protected(self): self.bot._pending[self.key]['send_state'] = 'sending' self.assertEqual(self.bot.handoff_pending_replies([self.key], confirm_uncertain=True)['protected'], [self.key]) self.bot._pending[self.key].update(send_state='uncertain', account='other-account') self.assertEqual(self.bot.handoff_pending_replies([self.key], confirm_uncertain=True)['protected'], [self.key]) self.assertIn(self.key, self.bot._pending) self.assertFalse(self.sender.calls) def test_failed_send_can_be_taken_over_without_retry(self): self.bot._pending[self.key].update(send_state='failed', stage='failed') self.assertEqual(self.bot.handoff_pending_replies([self.key], confirm_uncertain=True)['handed_off'], [self.key]) self.assertFalse(self.bot._pending) self.assertFalse(self.sender.calls) if __name__ == '__main__': unittest.main()