139 lines
6.7 KiB
Python
139 lines
6.7 KiB
Python
"""Synthetic review persistence: no desktop input, network or live queue access."""
|
|
import json
|
|
from pathlib import Path
|
|
import tempfile
|
|
import threading
|
|
import time
|
|
import unittest
|
|
from unittest import mock
|
|
|
|
from wechat_bot import WeChatBot
|
|
|
|
|
|
class VisualReviewVisibilityTest(unittest.TestCase):
|
|
def setUp(self):
|
|
temporary = tempfile.TemporaryDirectory()
|
|
self.addCleanup(temporary.cleanup)
|
|
self.path = Path(temporary.name) / 'pending.json'
|
|
self.fp = b'v' * 40
|
|
self.key = self.fp.hex()
|
|
self.bot = WeChatBot.__new__(WeChatBot)
|
|
self.bot._pending_lock = threading.RLock()
|
|
self.bot._pending_reply_path = str(self.path)
|
|
self.bot._queue_log_instance = mock.Mock()
|
|
self.bot._forget_uncertain_tracking = mock.Mock()
|
|
self.bot.send_mode = 'auto'
|
|
self.enterContext(mock.patch('review_policy.current_judge_mode', return_value='score_only'))
|
|
self.state = {
|
|
'created_at': time.time(), 'updated_at': time.time(),
|
|
'display_name': '审核测试客户', 'stage': 'manual_review',
|
|
'awaiting_review': True, 'review_origin': 'rule',
|
|
'review_reason': '命中审核规则「测试规则」',
|
|
'staged_user_text': '合成测试问题', 'staged_reply_text': '合成待审草稿',
|
|
'reply_text': '合成待审草稿', 'judge_risk': 'high', 'judge_mode': 'score_only',
|
|
'judge_reason': '合成裁判原因',
|
|
}
|
|
self.bot._pending_reply_sessions = {self.key: self.state}
|
|
for function in ('press', 'hotkey', 'click', 'write'):
|
|
patcher = mock.patch('wechat_bot.pyautogui.' + function,
|
|
side_effect=AssertionError('No real desktop action'))
|
|
patcher.start()
|
|
self.addCleanup(patcher.stop)
|
|
|
|
def restart(self):
|
|
self.assertTrue(self.bot._persist_pending_replies())
|
|
self.bot._pending_reply_sessions = self.bot._load_pending_replies()
|
|
self.state = self.bot._pending_reply_sessions[self.key]
|
|
return self.state
|
|
|
|
def test_rule_reason_and_judge_survive_restart_and_auto_mode(self):
|
|
restored = self.restart()
|
|
self.assertEqual(restored['review_reason'], '命中审核规则「测试规则」')
|
|
self.assertEqual(restored['judge_mode'], 'score_only')
|
|
self.bot._drop_stale_review_flags()
|
|
self.assertTrue(restored['awaiting_review'])
|
|
self.assertEqual(restored['review_reason'], '模型裁判评估为高风险')
|
|
self.assertEqual(restored['judge_risk'], 'high')
|
|
self.assertEqual(restored['judge_reason'], '合成裁判原因')
|
|
self.assertEqual(restored['staged_user_text'], '合成测试问题')
|
|
self.assertEqual(restored['staged_reply_text'], '合成待审草稿')
|
|
|
|
def test_review_without_unread_badge_does_not_expire_during_downtime(self):
|
|
self.state['created_at'] = self.state['updated_at'] = time.time() - 3 * 86400
|
|
self.state['confirmed_unread'] = False
|
|
self.assertTrue(self.restart()['awaiting_review'])
|
|
|
|
def test_legacy_review_with_lost_reason_stays_actionable(self):
|
|
self.state.pop('review_reason')
|
|
self.state.pop('review_origin')
|
|
self.state['stage_detail'] = 'AI 回复已填入输入框,等待人工核对并发送'
|
|
self.path.write_text(json.dumps({self.key: self.state}), encoding='utf-8')
|
|
self.bot._pending_reply_sessions = self.bot._load_pending_replies()
|
|
restored = self.bot._pending_reply_sessions[self.key]
|
|
self.bot._drop_stale_review_flags()
|
|
self.assertTrue(restored['awaiting_review'])
|
|
self.assertIn('历史审核原因', restored['review_reason'])
|
|
|
|
def test_legacy_review_recovers_rule_reason_from_stage_detail(self):
|
|
self.state.pop('review_reason')
|
|
self.state.pop('review_origin')
|
|
self.state['stage_detail'] = '命中审核规则「测试规则」,已停止自动发送'
|
|
self.assertIn('测试规则', self.restart()['review_reason'])
|
|
|
|
def test_legacy_manual_review_stage_without_boolean_is_restored(self):
|
|
self.state.pop('awaiting_review')
|
|
restored = self.restart()
|
|
self.assertTrue(restored['awaiting_review'])
|
|
self.assertEqual(restored['stage'], 'manual_review')
|
|
|
|
def test_known_global_review_still_honors_explicit_mode_change(self):
|
|
self.state.update(review_origin='global', review_reason='', judge_risk='')
|
|
restored = self.restart()
|
|
self.bot._drop_stale_review_flags()
|
|
self.assertFalse(restored.get('awaiting_review'))
|
|
|
|
def test_explicit_approval_survives_restart_but_is_consumed_on_next_turn(self):
|
|
result = self.bot.approve_pending_replies([self.key])
|
|
self.assertEqual(result['approved'], [self.key])
|
|
restored = self.restart()
|
|
self.assertFalse(restored['awaiting_review'])
|
|
self.assertTrue(restored['review_approved'])
|
|
self.assertGreater(restored['review_approved_at'], 0)
|
|
self.assertEqual(restored['stage'], 'queued')
|
|
self.bot._reset_pending_batch(self.fp)
|
|
self.assertFalse(restored.get('review_approved'))
|
|
self.assertNotIn('judge_risk', restored)
|
|
self.assertNotIn('review_origin', restored)
|
|
|
|
def test_next_customer_turn_does_not_inherit_previous_judge_risk(self):
|
|
self.bot._reset_pending_batch(self.fp)
|
|
with mock.patch('ai_config.AI_REVIEW_RULES', []):
|
|
self.assertEqual(self.bot._reply_needs_review(self.state, '新问题', '新回答'), (False, ''))
|
|
|
|
def test_review_outcome_preserves_specific_reason_for_desktop_and_log(self):
|
|
self.bot._log_send_outcome(self.fp, False, '合成待审草稿')
|
|
self.assertIn('测试规则', self.state['stage_detail'])
|
|
self.assertEqual(self.state['stage'], 'manual_review')
|
|
event = self.bot._queue_log_instance.append.call_args
|
|
self.assertIn('测试规则', event.args[-1])
|
|
|
|
def test_user_can_cancel_restored_review_without_sending(self):
|
|
self.restart()
|
|
result = self.bot.cancel_pending_replies([self.key])
|
|
self.assertEqual(result['deleted'], [self.key])
|
|
self.assertNotIn(self.key, self.bot._pending_reply_sessions)
|
|
self.assertEqual(json.loads(self.path.read_text(encoding='utf-8')), {})
|
|
|
|
def test_failed_approval_save_keeps_review_visible_and_unapproved(self):
|
|
with mock.patch.object(self.bot, '_persist_pending_replies_unlocked', return_value=False):
|
|
result = self.bot.approve_pending_replies([self.key])
|
|
self.assertFalse(result['approved'])
|
|
self.assertIn('error', result)
|
|
restored = self.bot._pending_reply_sessions[self.key]
|
|
self.assertTrue(restored['awaiting_review'])
|
|
self.assertFalse(restored.get('review_approved'))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|