189 lines
7.7 KiB
Python
189 lines
7.7 KiB
Python
|
|
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
|