Files
kefu/wechat_rpa/test_review_handoff.py
T
2026-09-21 10:34:06 +08:00

144 lines
7.2 KiB
Python

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