131 lines
5.8 KiB
Python
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()
|