""" 私聊附件等材料波次:发卡等待、发图/H5 并入、30 分钟时效。 """ 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.attachment_wave import ( WAIT_TTL_SECONDS, AttachWaveBook, reset_attach_wave_book_for_test, ) class AttachWaveTests(unittest.TestCase): def setUp(self) -> None: self.book = reset_attach_wave_book_for_test() def test_wait_ttl_is_30_minutes(self) -> None: self.assertEqual(WAIT_TTL_SECONDS, 1800) def test_open_wait_is_active_without_files(self) -> None: wave = self.book.open_wait("s1", precede_text="海运上海到纽约", token="tok1", now=100.0) self.assertTrue(wave.wave_id) self.assertEqual(wave.precede_text, "海运上海到纽约") self.assertFalse(wave.started) self.assertIsNotNone(self.book.active("s1", now=100.0)) def test_wait_expires_after_30_minutes_if_not_started(self) -> None: self.book.open_wait("s1", precede_text="海运", token="tok1", now=100.0) self.assertIsNone(self.book.active("s1", now=100.0 + 1800.1)) def test_follow_text_while_waiting(self) -> None: self.book.open_wait("s1", precede_text="", token="tok1", now=1.0) self.assertTrue(self.book.append_follow_text("s1", "只要整柜")) self.assertFalse(self.book.append_follow_text("s1", "")) def test_join_image_starts_recognition(self) -> None: self.book.open_wait("s1", precede_text="海运", token="tok1", now=1.0) wave, how = self.book.join_image("s1", "mid-a", filename="a.jpg", now=2.0) self.assertEqual(how, "joined") self.assertTrue(wave.started) self.assertEqual(self.book.pending(wave), 1) def test_add_files_keeps_upload_order(self) -> None: self.book.open_wait("s1", precede_text="", token="tok1", now=1.0) wave = self.book.add_files("s1", ["sea.xlsx", "air.pdf"], now=2.0) self.assertTrue(wave.started) self.assertEqual([m.filename for m in wave.materials], ["sea.xlsx", "air.pdf"]) self.assertEqual(self.book.pending(wave), 2) def test_compose_oral_uses_material_order(self) -> None: self.book.open_wait("s1", precede_text="开口字", token="tok1", now=1.0) self.book.add_files("s1", ["a.xlsx", "b.pdf"], now=2.0) self.book.append_follow_text("s1", "只要40尺") wave = self.book.active("s1", now=2.0) self.assertIsNotNone(wave) self.book.complete_material("s1", 1, text="空运 上海到纽约") self.book.complete_material("s1", 0, text="海运 宁波到汉堡") oral = self.book.compose_oral("s1") self.assertEqual(oral, "开口字\n海运 宁波到汉堡\n空运 上海到纽约\n只要40尺") def test_after_business_done_not_active(self) -> None: self.book.open_wait("s1", precede_text="", token="tok1", now=1.0) self.book.add_files("s1", ["a.xlsx"], now=2.0) self.book.complete_material("s1", 0, text="海运") self.book.mark_business_done("s1") self.assertIsNone(self.book.active("s1", now=3.0)) self.assertFalse(self.book.append_follow_text("s1", "补一句")) def test_image_after_business_rejected(self) -> None: self.book.open_wait("s1", precede_text="", token="tok1", now=1.0) self.book.mark_business_done("s1") wave, how = self.book.join_image("s1", "mid-b", filename="b.jpg", now=2.0) self.assertEqual(how, "inactive") self.assertIsNone(wave) def test_http_and_worker_share_kv(self) -> None: kv = _MemoryWaveKV() http = reset_attach_wave_book_for_test(kv=kv) worker = AttachWaveBook(kv=kv) http.open_wait("s1", precede_text="海运", token="tok", now=1.0) self.assertIsNotNone(worker.active("s1", now=1.0)) worker.add_files("s1", ["a.xlsx"], now=2.0) worker.complete_material("s1", 0, text="宁波到汉堡") worker.mark_business_done("s1") self.assertIsNone(http.active("s1", now=3.0)) class _MemoryWaveKV: """单测假 Redis:两份账本共用同一字典。""" 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()