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

131 lines
5.8 KiB
Python

"""Self-reply regressions; only synthetic records and mocked desktop input."""
import tempfile
import time
import unittest
from pathlib import Path
from unittest import mock
from conversation_store import ConversationStore
from engine_b import DataEngine
from wechat_bot import WeChatBot
class ArchiveFeedbackTest(unittest.TestCase):
def test_packaged_archive_generation_and_send_flow(self):
from packaging_checks import check_reply_feedback
check_reply_feedback()
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.path = Path(self.temp.name) / "conversations.json"
self.store = ConversationStore(str(self.path))
self.bot = mock.Mock()
self.bot.has_active_pending.return_value = False
self.bot.enqueue_detected.return_value = (True, "queued")
self.engine = DataEngine(bot=self.bot, conversations_path=str(self.path))
def test_completed_exchange_does_not_trigger_on_startup_or_repoll(self):
self.store.append_exchange_once("aa", "customer question", "our answer", "exchange1")
self.engine.poll_once()
self.engine.poll_once()
self.bot.enqueue_detected.assert_not_called()
def test_saving_our_reply_does_not_create_another_customer_event(self):
self.store.append("aa", "user", "customer question")
self.store.save()
self.engine.poll_once()
self.assertEqual(self.bot.enqueue_detected.call_count, 1)
self.bot.enqueue_detected.reset_mock()
for i in range(8):
self.store.append_exchange_once("aa", "customer question", f"our answer {i}", f"exchange{i}")
self.engine.poll_once()
self.bot.enqueue_detected.assert_not_called()
def test_genuine_followup_after_reply_is_still_enqueued(self):
self.store.append_exchange_once("aa", "customer question", "our answer", "exchange1")
self.engine.poll_once()
self.bot.enqueue_detected.reset_mock()
self.store.append("aa", "user", "customer question")
self.store.save()
self.engine.poll_once()
self.bot.enqueue_detected.assert_called_once()
def test_meta_records_after_reply_do_not_revive_old_customer_turn(self):
self.store.append_exchange_once("aa", "question", "answer", "exchange1")
self.store.append("aa", "system", "archive metadata")
self.store.save()
self.engine.poll_once()
self.bot.enqueue_detected.assert_not_called()
def test_new_customer_record_with_same_timestamp_as_answer_is_processed(self):
with mock.patch("conversation_store.time.time", return_value=time.time()):
self.store.append_exchange_once("aa", "question", "answer", "exchange1")
self.engine.poll_once()
self.bot.enqueue_detected.reset_mock()
self.store.append("aa", "user", "question")
self.store.save()
self.engine.poll_once()
self.bot.enqueue_detected.assert_called_once()
class OwnMessageGuardTest(unittest.TestCase):
def bot(self):
bot = WeChatBot.__new__(WeChatBot)
bot._active_session_fp = b"a" * 40
bot._pending_reply_sessions = {bot._active_session_fp.hex(): {"confirmed_unread": True, "display_name": "customer"}}
bot._known_outgoing_speakers = mock.Mock(return_value={"configured assistant"})
bot._last_visible_bubble_is_outgoing = mock.Mock(return_value=True)
bot._open_chat_display_name = mock.Mock(return_value="customer")
bot._clear_reply_pending = mock.Mock()
bot._remember_active_surface = mock.Mock()
bot._log_queue_event = mock.Mock()
return bot
def test_unknown_staff_name_is_not_positive_customer_proof(self):
bot = self.bot()
text = "different staff 14:12:01\nour answer"
self.assertFalse(bot._clipboard_tail_is_explicit_customer_text(text))
self.assertTrue(bot._nothing_left_to_answer(bot._active_session_fp, text))
bot._clear_reply_pending.assert_called_once_with(bot._active_session_fp)
def test_verified_customer_name_preserves_short_followup_despite_pixel_error(self):
bot = self.bot()
text = "different staff 14:12:01\nour answer\ncustomer 14:12:02\nthanks"
self.assertTrue(bot._clipboard_tail_is_explicit_customer_text(text))
self.assertFalse(bot._nothing_left_to_answer(bot._active_session_fp, text))
bot._clear_reply_pending.assert_not_called()
def test_known_staff_tail_is_not_answered_even_when_pixel_direction_is_unknown(self):
bot = self.bot()
bot._last_visible_bubble_is_outgoing.return_value = None
text = "configured assistant 14:12:01\nour answer"
self.assertTrue(bot._nothing_left_to_answer(bot._active_session_fp, text))
def test_new_uncopyable_incoming_media_is_not_hidden_by_old_staff_text(self):
bot = self.bot()
bot._last_visible_bubble_is_outgoing.return_value = False
text = "configured assistant 14:12:01\nour older answer"
self.assertFalse(bot._nothing_left_to_answer(bot._active_session_fp, text))
bot._clear_reply_pending.assert_not_called()
def test_stale_unread_task_cannot_call_model_after_our_answer(self):
bot = self.bot()
bot._reply_task_cancelled = mock.Mock(return_value=False)
bot._orchestrated_reply = mock.Mock()
with mock.patch("ai_config.AI_ENABLED", True):
result = bot._generate_ai_reply_impl(
bot._active_session_fp,
chat_text="different staff 14:12:01\nour answer",
confirmed_unread=True,
reliable_text_pending=True,
)
self.assertIsNone(result)
bot._orchestrated_reply.assert_not_called()
bot._clear_reply_pending.assert_called_once_with(bot._active_session_fp)
if __name__ == "__main__":
unittest.main()