from __future__ import annotations from unittest.mock import patch import pytest from yuxi.channels.adapters.wechat.selector import ( SelectorCard, SelectorItem, SelectorOption, WeChatSelectorAdapter, ) class TestSelectorDataClasses: def test_selector_option_creation(self): opt = SelectorOption(id="1", text="选项一", is_default=True) assert opt.id == "1" assert opt.text == "选项一" assert opt.is_default is True def test_selector_option_defaults(self): opt = SelectorOption(id="2", text="选项二") assert opt.is_default is False def test_selector_item_creation(self): options = [SelectorOption(id="a", text="A"), SelectorOption(id="b", text="B")] item = SelectorItem(question_key="q1", title="问题1", options=options, type="multi") assert item.question_key == "q1" assert item.title == "问题1" assert len(item.options) == 2 assert item.type == "multi" assert item.disable is False def test_selector_card_creation(self): card = SelectorCard(task_id="task1", title="请选择", description="desc") assert card.task_id == "task1" assert card.title == "请选择" assert card.description == "desc" assert card.created_at == 0.0 assert card.expires_at == 0.0 def test_selector_card_defaults(self): card = SelectorCard(task_id="t1") assert card.title == "请选择" assert card.description == "" assert card.items is None class TestWeChatSelectorAdapter: def setup_method(self): self.adapter = WeChatSelectorAdapter(default_ttl_hours=24) def test_build_selector(self): items = [SelectorItem(question_key="q1", title="Q1")] card = WeChatSelectorAdapter.build_selector("task-1", "标题", items, "描述", ttl_hours=1) assert card.task_id == "task-1" assert card.title == "标题" assert card.description == "描述" assert card.expires_at > card.created_at def test_build_instance_option(self): opt = WeChatSelectorAdapter.build_instance_option("1", "选项") assert isinstance(opt, SelectorOption) assert opt.id == "1" assert opt.text == "选项" def test_build_instance_item(self): options = [SelectorOption(id="x", text="X")] item = WeChatSelectorAdapter.build_instance_item("k1", "标题", options, "single") assert isinstance(item, SelectorItem) assert item.question_key == "k1" assert item.type == "single" def test_create_and_get_selector(self): card = WeChatSelectorAdapter.build_selector("task-1", "标题", []) self.adapter.create_selector(card) retrieved = self.adapter.get_selector("task-1") assert retrieved is not None assert retrieved.task_id == "task-1" def test_get_nonexistent_selector(self): assert self.adapter.get_selector("nonexistent") is None def test_is_expired_nonexistent(self): assert self.adapter.is_expired("nonexistent") is True @patch("time.time") def test_is_expired_expired_card(self, mock_time): mock_time.return_value = 1000000.0 card = WeChatSelectorAdapter.build_selector("task-1", "标题", [], ttl_hours=1) self.adapter.create_selector(card) mock_time.return_value = 1000000.0 + 3601.0 assert self.adapter.is_expired("task-1") is True assert self.adapter.get_selector("task-1") is None @patch("time.time") def test_get_selector_expired_returns_none(self, mock_time): mock_time.return_value = 100.0 card = WeChatSelectorAdapter.build_selector("task-e", "标题", [], ttl_hours=0) self.adapter.create_selector(card) mock_time.return_value = 101.0 assert self.adapter.get_selector("task-e") is None def test_resolve_selections_single(self): options = [SelectorOption(id="a", text="A"), SelectorOption(id="b", text="B")] items = [SelectorItem(question_key="q1", title="Q1", options=options, type="single")] card = WeChatSelectorAdapter.build_selector("task-r", "标题", items) self.adapter.create_selector(card) result = self.adapter.resolve_selections("task-r", {"q1": ["a", "b"]}) assert result is not None assert result["answers"]["q1"] == "a" def test_resolve_selections_multi(self): options = [SelectorOption(id="a", text="A"), SelectorOption(id="b", text="B")] items = [SelectorItem(question_key="q1", title="Q1", options=options, type="multi")] card = WeChatSelectorAdapter.build_selector("task-r", "标题", items) self.adapter.create_selector(card) result = self.adapter.resolve_selections("task-r", {"q1": ["a", "b"]}) assert result is not None assert result["answers"]["q1"] == ["a", "b"] def test_resolve_selections_filters_invalid_ids(self): options = [SelectorOption(id="a", text="A")] items = [SelectorItem(question_key="q1", title="Q1", options=options)] card = WeChatSelectorAdapter.build_selector("task-r", "标题", items) self.adapter.create_selector(card) result = self.adapter.resolve_selections("task-r", {"q1": ["invalid", "a"]}) assert result is not None assert result["answers"]["q1"] == "a" def test_resolve_selections_missing_key(self): options = [SelectorOption(id="a", text="A")] items = [SelectorItem(question_key="q1", title="Q1", options=options)] card = WeChatSelectorAdapter.build_selector("task-r", "标题", items) self.adapter.create_selector(card) result = self.adapter.resolve_selections("task-r", {"other": ["a"]}) assert result is not None assert "q1" not in result["answers"] def test_resolve_selections_no_items(self): card = SelectorCard(task_id="task-r", items=None) self.adapter.create_selector(card) result = self.adapter.resolve_selections("task-r", {"q1": ["a"]}) assert result is None def test_render_taskcard_payload(self): options = [SelectorOption(id="a", text="A")] items = [SelectorItem(question_key="q1", title="Q1", options=options)] card = WeChatSelectorAdapter.build_selector("task-card", "选择", items) payload = self.adapter.render_taskcard_payload("user1", "agent1", card) assert payload["touser"] == "user1" assert payload["agentid"] == "agent1" assert payload["msgtype"] == "template_card" assert payload["template_card"]["card_type"] == "button_interaction" assert payload["template_card"]["main_title"]["title"] == "选择" assert len(payload["template_card"]["select_list"]) == 1 assert payload["template_card"]["submit_button"]["text"] == "确认" @patch("time.time") def test_list_active_selectors(self, mock_time): mock_time.return_value = 1000.0 items = [SelectorItem(question_key="q1", title="Q1")] card = WeChatSelectorAdapter.build_selector("task-1", "Card1", items, ttl_hours=24) self.adapter.create_selector(card) mock_time.return_value = 1010.0 active = self.adapter.list_active_selectors() assert len(active) == 1 assert active[0]["task_id"] == "task-1" assert active[0]["title"] == "Card1" @patch("time.time") def test_list_active_selectors_excludes_expired(self, mock_time): mock_time.return_value = 1000.0 items = [SelectorItem(question_key="q1", title="Q1")] card = WeChatSelectorAdapter.build_selector("task-1", "Card1", items, ttl_hours=0) self.adapter.create_selector(card) mock_time.return_value = 2000.0 active = self.adapter.list_active_selectors() assert len(active) == 0