新增功能
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""通用运行设置与连续消息合并窗口测试。"""
|
||||
"""通用运行设置、消息合并窗口与发送前等待测试。"""
|
||||
|
||||
import json
|
||||
import queue
|
||||
@@ -8,7 +8,7 @@ from pathlib import Path
|
||||
from unittest import TestCase, main, mock
|
||||
|
||||
import wechat_gui
|
||||
from gui_runtime import LogQueue, delete_pending_reply_file
|
||||
from gui_runtime import BotThread, LogQueue, delete_pending_reply_file
|
||||
from wechat_bot import WeChatBot
|
||||
|
||||
|
||||
@@ -28,6 +28,9 @@ class RuntimeSettingsTest(TestCase):
|
||||
settings = app._load_runtime_settings()
|
||||
|
||||
self.assertEqual(settings["message_batch_window_seconds"], 20.0)
|
||||
self.assertEqual(settings["send_delay_seconds"], 1.0)
|
||||
self.assertEqual(settings["send_mode"], "auto")
|
||||
self.assertEqual(settings["mouse_idle_seconds"], 5.0)
|
||||
|
||||
def test_invalid_saved_batch_window_falls_back_to_twenty_seconds(self):
|
||||
self.assertEqual(
|
||||
@@ -47,6 +50,26 @@ class RuntimeSettingsTest(TestCase):
|
||||
|
||||
self.assertEqual(settings["message_batch_window_seconds"], 20.0)
|
||||
|
||||
def test_invalid_saved_send_delay_falls_back_to_one_second(self):
|
||||
self.assertEqual(wechat_gui.normalize_send_delay_seconds(True), 1.0)
|
||||
payload = {
|
||||
**wechat_gui.App._runtime_defaults(),
|
||||
"send_delay_seconds": 31,
|
||||
}
|
||||
with tempfile.TemporaryDirectory() as folder:
|
||||
path = Path(folder) / "app_settings.json"
|
||||
path.write_text(json.dumps(payload, ensure_ascii=False), encoding="utf-8")
|
||||
app = wechat_gui.App.__new__(wechat_gui.App)
|
||||
with mock.patch.object(wechat_gui, "APP_SETTINGS_FILE", str(path)):
|
||||
settings = app._load_runtime_settings()
|
||||
|
||||
self.assertEqual(settings["send_delay_seconds"], 1.0)
|
||||
self.assertEqual(settings["mouse_idle_seconds"], 5.0)
|
||||
|
||||
def test_invalid_send_mode_falls_back_to_automatic(self):
|
||||
self.assertEqual(wechat_gui.normalize_send_mode("unknown"), "auto")
|
||||
self.assertEqual(wechat_gui.normalize_send_mode("review"), "review")
|
||||
|
||||
def test_runtime_thread_syncs_updated_batch_window_before_next_poll(self):
|
||||
observed = []
|
||||
fake_bot = mock.Mock()
|
||||
@@ -67,12 +90,20 @@ class RuntimeSettingsTest(TestCase):
|
||||
"在的",
|
||||
0.001,
|
||||
message_batch_window_seconds=7,
|
||||
send_delay_seconds=1.5,
|
||||
send_mode="review",
|
||||
)
|
||||
|
||||
def poll_once():
|
||||
observed.append(fake_bot.message_batch_window_seconds)
|
||||
observed.append((
|
||||
fake_bot.message_batch_window_seconds,
|
||||
fake_bot.send_delay_seconds,
|
||||
fake_bot.send_mode,
|
||||
))
|
||||
if len(observed) == 1:
|
||||
thread.set_message_batch_window_seconds(9)
|
||||
thread.set_send_delay_seconds(2.5)
|
||||
thread.set_send_mode("auto")
|
||||
else:
|
||||
thread.stop_event.set()
|
||||
|
||||
@@ -80,12 +111,19 @@ class RuntimeSettingsTest(TestCase):
|
||||
with mock.patch("wechat_bot.WeChatBot", return_value=fake_bot):
|
||||
thread.run()
|
||||
|
||||
self.assertEqual(observed, [7.0, 9])
|
||||
self.assertEqual(
|
||||
observed,
|
||||
[(7.0, 1.5, "review"), (9, 2.5, "auto")],
|
||||
)
|
||||
|
||||
def test_classic_settings_save_persists_and_updates_live_thread(self):
|
||||
app = wechat_gui.App.__new__(wechat_gui.App)
|
||||
app._runtime_save_job = None
|
||||
app._last_runtime_settings = {}
|
||||
app._runtime_settings = {
|
||||
"send_delay_seconds": 3.5,
|
||||
"send_mode": "review",
|
||||
}
|
||||
app._reply_var = mock.Mock()
|
||||
app._reply_var.get.return_value = "在的"
|
||||
app._poll_var = mock.Mock()
|
||||
@@ -106,7 +144,108 @@ class RuntimeSettingsTest(TestCase):
|
||||
saved = json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
self.assertEqual(saved["message_batch_window_seconds"], 12.5)
|
||||
self.assertEqual(saved["send_delay_seconds"], 3.5)
|
||||
self.assertEqual(saved["send_mode"], "review")
|
||||
app._thread.set_message_batch_window_seconds.assert_called_once_with(12.5)
|
||||
app._thread.set_send_delay_seconds.assert_called_once_with(3.5)
|
||||
app._thread.set_send_mode.assert_called_once_with("review")
|
||||
|
||||
def test_send_delay_wait_is_independent_and_interruptible(self):
|
||||
bot = WeChatBot.__new__(WeChatBot)
|
||||
bot.send_delay_seconds = 1.25
|
||||
bot._stop_check = mock.Mock()
|
||||
bot._stop_check.wait.side_effect = [False, True]
|
||||
|
||||
with mock.patch("builtins.print"):
|
||||
self.assertFalse(bot.wait_before_send())
|
||||
|
||||
self.assertAlmostEqual(bot._stop_check.wait.call_args_list[0].args[0], 0.2)
|
||||
|
||||
def test_review_pending_state_survives_restart(self):
|
||||
fp = b"r" * 40
|
||||
with tempfile.TemporaryDirectory() as folder:
|
||||
path = Path(folder) / "pending_replies.json"
|
||||
bot = WeChatBot.__new__(WeChatBot)
|
||||
bot._pending_reply_path = str(path)
|
||||
bot._pending_reply_sessions = {
|
||||
fp.hex(): {
|
||||
"batch_ready": True,
|
||||
"confirmed_unread": True,
|
||||
"created_at": 1.0,
|
||||
"updated_at": 2.0,
|
||||
"awaiting_review": True,
|
||||
"review_requested_at": 2.0,
|
||||
"reply_text": "请人工确认",
|
||||
"stage": "manual_review",
|
||||
}
|
||||
}
|
||||
self.assertTrue(bot._persist_pending_replies())
|
||||
|
||||
restored_bot = WeChatBot.__new__(WeChatBot)
|
||||
restored_bot._pending_reply_path = str(path)
|
||||
restored = restored_bot._load_pending_replies()
|
||||
|
||||
self.assertTrue(restored[fp.hex()]["awaiting_review"])
|
||||
self.assertEqual(restored[fp.hex()]["reply_text"], "请人工确认")
|
||||
self.assertEqual(restored[fp.hex()]["stage"], "manual_review")
|
||||
|
||||
def test_manual_retry_only_requeues_retry_wait_tasks(self):
|
||||
bot = WeChatBot.__new__(WeChatBot)
|
||||
retry_fp = b"r" * 40
|
||||
normal_fp = b"n" * 40
|
||||
review_fp = b"v" * 40
|
||||
uncertain_fp = b"u" * 40
|
||||
bot._pending_reply_path = ""
|
||||
bot._pending_reply_sessions = {
|
||||
retry_fp.hex(): {
|
||||
"stage": "retry_wait",
|
||||
"stage_started_at": 1.0,
|
||||
"updated_at": 1.0,
|
||||
"last_error": "temporary",
|
||||
"resume_failures": 3,
|
||||
"last_resume_attempt": 999.0,
|
||||
"display_name": "待重试客户",
|
||||
},
|
||||
normal_fp.hex(): {"stage": "generating", "updated_at": 1.0},
|
||||
review_fp.hex(): {
|
||||
"stage": "manual_review",
|
||||
"awaiting_review": True,
|
||||
"updated_at": 1.0,
|
||||
},
|
||||
uncertain_fp.hex(): {
|
||||
"stage": "retry_wait",
|
||||
"send_state": "uncertain",
|
||||
"updated_at": 1.0,
|
||||
},
|
||||
}
|
||||
bot._queue_log_instance = mock.Mock()
|
||||
|
||||
result = bot.retry_pending_replies(
|
||||
[retry_fp.hex(), normal_fp.hex(), review_fp.hex(), uncertain_fp.hex()]
|
||||
)
|
||||
|
||||
self.assertEqual(result["retried"], [retry_fp.hex()])
|
||||
self.assertEqual(result["not_retryable"], [normal_fp.hex()])
|
||||
self.assertEqual(
|
||||
set(result["protected"]), {review_fp.hex(), uncertain_fp.hex()}
|
||||
)
|
||||
retried = bot._pending_reply_sessions[retry_fp.hex()]
|
||||
self.assertEqual(retried["stage"], "queued")
|
||||
self.assertEqual(retried["last_resume_attempt"], 0.0)
|
||||
self.assertNotIn("last_error", retried)
|
||||
self.assertEqual(retried["resume_failures"], 3)
|
||||
bot._queue_log_instance.append.assert_called_once()
|
||||
|
||||
def test_retry_request_queues_until_bot_startup_finishes(self):
|
||||
key = (b"q" * 40).hex()
|
||||
thread = BotThread(queue.Queue(), "在的", 1.0)
|
||||
|
||||
result = thread.retry_pending_tasks([key])
|
||||
|
||||
self.assertEqual(result["scheduled"], [key])
|
||||
fake_bot = mock.Mock()
|
||||
thread._drain_queued_retries(fake_bot)
|
||||
fake_bot.retry_pending_replies.assert_called_once_with({key})
|
||||
|
||||
def test_bot_uses_instance_batch_window_when_call_has_no_override(self):
|
||||
bot = WeChatBot.__new__(WeChatBot)
|
||||
|
||||
Reference in New Issue
Block a user