""" 私聊图片波次:前一句 10 秒、连发图、业务发出前收字。 """ from __future__ import annotations import json import os import sys import unittest ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) if ROOT not in sys.path: sys.path.insert(0, ROOT) from agent.policy.image_wave import ( ImageWaveBook, PRECEDE_SECONDS, reset_image_wave_book_for_test, ) class ImageWaveTests(unittest.TestCase): def setUp(self) -> None: self.book = reset_image_wave_book_for_test() def test_precede_window_is_ten_seconds(self) -> None: self.assertEqual(PRECEDE_SECONDS, 10.0) def test_precede_within_10s(self) -> None: self.book.note_text("s1", "空运 上海到纽约", now=100.0) self.assertEqual(self.book.precede_text("s1", now=109.9), "空运 上海到纽约") def test_precede_after_10s_dropped(self) -> None: self.book.note_text("s1", "空运 上海到纽约", now=100.0) self.assertEqual(self.book.precede_text("s1", now=110.1), "") def test_forget_text_drops_precede(self) -> None: self.book.note_text("s1", "空运 上海到纽约", now=100.0) self.book.forget_text("s1") self.assertEqual(self.book.precede_text("s1", now=100.5), "") def test_second_image_joins_before_business(self) -> None: w1, how = self.book.open_or_join("s1", "mid-a", now=1.0) self.assertEqual(how, "opened") w2, how2 = self.book.open_or_join("s1", "mid-b", now=1.2) self.assertEqual(how2, "joined") self.assertEqual(w1.wave_id, w2.wave_id) self.assertEqual(w2.media_ids, ["mid-a", "mid-b"]) def test_image_after_business_is_new_wave(self) -> None: self.book.open_or_join("s1", "mid-a", now=1.0) self.book.mark_business_done("s1") w2, how = self.book.open_or_join("s1", "mid-b", now=2.0) self.assertEqual(how, "new_after_done") self.assertEqual(w2.media_ids, ["mid-b"]) def test_follow_text_before_business(self) -> None: self.book.open_or_join("s1", "mid-a", now=1.0) self.assertTrue(self.book.append_follow_text("s1", "海运")) self.book.add_vision("s1", "宁波到汉堡") oral = self.book.compose_oral("s1") self.assertIn("海运", oral) self.assertIn("宁波到汉堡", oral) def test_follow_text_after_business_rejected(self) -> None: self.book.open_or_join("s1", "mid-a", now=1.0) self.book.mark_business_done("s1") self.assertFalse(self.book.append_follow_text("s1", "海运")) def test_http_and_worker_share_kv(self) -> None: """复现测服事故:HTTP 收图、Worker 认完必须看到同一份结束标记。""" kv = _MemoryWaveKV() http = reset_image_wave_book_for_test(kv=kv) worker = ImageWaveBook(kv=kv) http.open_or_join("s1", "mid-a", now=1.0) self.assertIsNotNone(worker.active("s1")) worker.add_vision("s1", "SZX-CGK 货交深圳") worker.mark_business_done("s1") self.assertIsNone(http.active("s1")) _w2, how = http.open_or_join("s1", "mid-b", now=2.0) self.assertEqual(how, "new_after_done") class _MemoryWaveKV: """单测假 Redis:两份 ImageWaveBook 共用同一字典。""" def __init__(self) -> None: self._d: dict[str, dict] = {} def get_json(self, kind: str, sender_id: str): row = self._d.get(f"{kind}:{sender_id}") return json.loads(json.dumps(row)) if row is not None else None def set_json(self, kind: str, sender_id: str, data: dict) -> None: self._d[f"{kind}:{sender_id}"] = json.loads(json.dumps(data)) def delete(self, kind: str, sender_id: str) -> None: self._d.pop(f"{kind}:{sender_id}", None) if __name__ == "__main__": unittest.main()