"""Synthetic protocol review workflow: local temp state, no network or WeCom I/O.""" import json from pathlib import Path import tempfile import unittest from unittest import mock from protocol_engine import ProtocolBot, session_key, MANUAL_MEDIA_REASON, MANUAL_BATCH_REASON from test_native_protocol_pipeline import Database, Sender, message, ACCOUNT, CONV from wecom_native_sender import DeliveryUnknown class ProtocolReviewVisibilityTests(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.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')), mock.patch('ai_config.AI_REVIEW_RULES', []), ): patcher.start() self.addCleanup(patcher.stop) self.bot = self.make_bot() self.key = session_key(ACCOUNT, CONV) def make_bot(self): bot = ProtocolBot(db=self.db, sender=self.sender, identity={'accountId': ACCOUNT, 'pid': 1}, path=self.root / 'protocol.json', generate=self.generate, log=lambda _: None) bot.message_batch_window_seconds = 0 bot.send_delay_seconds = 0 return bot def enqueue(self, item=None): self.db.add(item or message()) self.bot.poll_once() return self.bot._pending.get(self.key) def saved(self): return json.loads(self.bot.path.read_text(encoding='utf-8'))['pending'][self.key] def test_review_draft_reason_persists_then_explicit_approval_sends_once(self): self.bot.send_mode = 'review' state = self.enqueue() self.assertTrue(state['awaiting_review']) self.assertIn('人工审核', state['review_reason']) self.assertEqual(state['staged_reply_text'], '合成回复草稿') self.assertEqual(self.saved()['review_reason'], state['review_reason']) self.assertFalse(self.sender.calls) self.assertEqual(self.bot.approve_pending_replies([self.key])['approved'], [self.key]) self.assertEqual(self.saved()['stage'], 'ready_to_send') self.assertFalse(self.saved()['awaiting_review']) self.bot.poll_once() self.bot.poll_once() self.assertEqual(len(self.sender.calls), 1) def test_high_risk_reason_available_in_automatic_mode(self): self.bot.generate = lambda _: (self.bot._pending[self.key].update(judge_risk='high') or '高风险草稿') state = self.enqueue() self.assertTrue(state['awaiting_review']) self.assertEqual(state['review_reason'], '模型裁判评估为高风险') self.assertFalse(self.sender.calls) def test_media_batch_is_visible_manual_work_without_approvable_draft(self): self.db.add({**message(2, text='合成图片'), 'content_type': 3}) state = self.enqueue(message(3, text='请看上面的图')) self.assertEqual(state['stage'], 'error') self.assertTrue(state['manual_required']) self.assertEqual(state['manual_reason'], MANUAL_MEDIA_REASON) self.assertIn('合成图片', state['last_lines']) self.assertIn('请看上面的图', state['chat_text']) self.assertFalse(state['awaiting_review']) self.assertEqual(self.bot.approve_pending_replies([self.key])['not_pending'], [self.key]) self.assertFalse(self.sender.calls) self.generate.assert_not_called() def test_overlong_batch_cannot_be_approved_as_a_draft(self): self.db.messages = [message(i) for i in range(1, 102)] self.db.events = [self.db.messages[-1]] self.bot.poll_once() state = self.saved() self.assertTrue(state['manual_required']) self.assertEqual(state['manual_reason'], MANUAL_BATCH_REASON) self.assertEqual(self.bot.approve_pending_replies([self.key])['not_pending'], [self.key]) self.assertFalse(self.sender.calls) def test_retry_clears_old_metadata_then_rechecks_media_without_sending(self): self.enqueue({**message(2), 'content_type': 3}) self.bot._pending[self.key].update(staged_reply_text='旧草稿', review_reason='旧审核原因') self.assertEqual(self.bot.retry_pending_replies([self.key])['retried'], [self.key]) queued = self.saved() for field in ('manual_required', 'manual_reason', 'review_reason', 'staged_reply_text', 'awaiting_review'): self.assertFalse(queued[field], field) self.bot.poll_once() self.assertTrue(self.saved()['manual_required']) self.assertFalse(self.sender.calls) def test_next_valid_batch_discards_old_manual_requirement(self): self.enqueue({**message(2), 'content_type': 3}) self.db.add(message(3, self=True, text='人工已处理图片')) self.bot.send_mode = 'review' state = self.enqueue(message(4, text='新的文本问题')) self.assertFalse(state['manual_required']) self.assertFalse(state['manual_reason']) self.assertTrue(state['awaiting_review']) self.assertFalse(self.sender.calls) def test_old_saved_review_and_manual_tasks_restore_metadata_only(self): self.bot.send_mode = 'review' state = self.enqueue() state.pop('review_reason') self.bot._save() restored = self.make_bot() self.assertIn('人工审核', restored._pending[self.key]['review_reason']) self.assertFalse(restored._pending[self.key].get('approved')) state = restored._pending[self.key] state.update(stage='error', last_error=MANUAL_MEDIA_REASON, awaiting_review=False, reply_text='') state.pop('manual_required', None) state.pop('manual_reason', None) restored._save() restored = self.make_bot() self.assertEqual(restored._pending[self.key]['manual_reason'], MANUAL_MEDIA_REASON) self.assertTrue(restored._pending[self.key]['manual_required']) self.assertFalse(self.sender.calls) def test_unknown_receipt_cannot_be_approved_or_retried(self): self.sender.error = DeliveryUnknown('合成回执超时') self.enqueue() self.assertTrue(self.bot.needs_attention) self.assertEqual(self.bot.approve_pending_replies([self.key])['protected'], [self.key]) self.assertEqual(self.bot.retry_pending_replies([self.key])['protected'], [self.key]) self.bot.poll_once() self.assertEqual(len(self.sender.calls), 1) def test_foreign_account_task_cannot_be_approved_deleted_or_retried(self): self.bot.send_mode = 'review' state = self.enqueue() state['account'] = 'different-account' self.bot._save() for method in (self.bot.approve_pending_replies, self.bot.cancel_pending_replies, self.bot.retry_pending_replies): self.assertEqual(method([self.key])['protected'], [self.key]) self.assertTrue(self.saved()['awaiting_review']) self.assertFalse(self.saved().get('approved')) self.assertFalse(self.sender.calls) if __name__ == '__main__': unittest.main()