"""route.py DTO 单元测试。 覆盖 ``MatchSource`` / ``ConfigMatchSource`` / ``RouteBinding`` / ``MatchTier`` / ``BindingContext`` / ``ConfigFallbackChain`` / ``NestedWhitelistDecision`` / ``RouteBindingRule`` / ``SaveRouteBindingCmd`` / ``UpdateRouteBindingCmd`` / ``RouteBindingFilter`` 的字段赋值、默认值、 不可变语义与 ``__post_init__`` 校验逻辑。 """ from __future__ import annotations import dataclasses from datetime import datetime import pytest from yuxi.channels.contract.dtos.channel import ChannelType from yuxi.channels.contract.dtos.route import ( BindingContext, ConfigFallbackChain, ConfigMatchSource, MatchSource, MatchTier, NestedWhitelistDecision, RouteBinding, RouteBindingFilter, RouteBindingRule, SaveRouteBindingCmd, UpdateRouteBindingCmd, ) from yuxi.channels.contract.errors import ValidationError pytestmark = pytest.mark.unit @pytest.mark.unit class TestMatchSource: """匹配来源枚举测试。""" def test_str_values(self): assert MatchSource.EXPLICIT == "explicit" assert MatchSource.SESSION == "session" assert MatchSource.IDENTITY == "identity" assert MatchSource.DEFAULT == "default" assert MatchSource.CHAT_TYPE == "chat_type" @pytest.mark.unit class TestConfigMatchSource: """配置匹配来源枚举测试。""" def test_str_values(self): assert ConfigMatchSource.DIRECT == "direct" assert ConfigMatchSource.PARENT == "parent" assert ConfigMatchSource.WILDCARD == "wildcard" assert ConfigMatchSource.CHANNEL_BINDING == "channel_binding" @pytest.mark.unit class TestRouteBinding: """路由绑定 DTO 测试。""" def test_fields_are_assigned_with_defaults(self): # Act binding = RouteBinding( channel_account_id="acc-1", agent_binding="agent-slug", match_source=MatchSource.EXPLICIT, ) # Assert assert binding.channel_session_id is None assert binding.matched_layer is None assert binding.config_match_source is None def test_is_frozen(self): # Arrange binding = RouteBinding( channel_account_id="a1", agent_binding="slug", match_source=MatchSource.DEFAULT, ) # Act / Assert with pytest.raises(dataclasses.FrozenInstanceError): binding.agent_binding = "other" # type: ignore[misc] def test_empty_channel_account_id_raises(self): # Act / Assert with pytest.raises(ValidationError) as exc_info: RouteBinding( channel_account_id="", agent_binding="slug", match_source=MatchSource.DEFAULT, ) assert exc_info.value.field == "channel_account_id" def test_empty_agent_binding_raises(self): # Act / Assert with pytest.raises(ValidationError) as exc_info: RouteBinding( channel_account_id="a1", agent_binding="", match_source=MatchSource.DEFAULT, ) assert exc_info.value.field == "agent_binding" @pytest.mark.unit class TestMatchTier: """匹配层级 DTO 测试。""" def test_defaults(self): # Act tier = MatchTier(name="explicit", priority=0, match_method="exact") # Assert assert tier.enabled is True def test_empty_name_raises(self): # Act / Assert with pytest.raises(ValidationError) as exc_info: MatchTier(name="", priority=0, match_method="exact") assert exc_info.value.field == "name" def test_negative_priority_raises(self): # Act / Assert with pytest.raises(ValidationError) as exc_info: MatchTier(name="t", priority=-1, match_method="exact") assert exc_info.value.field == "priority" def test_invalid_match_method_raises(self): # Act / Assert with pytest.raises(ValidationError) as exc_info: MatchTier(name="t", priority=0, match_method="invalid") # type: ignore[arg-type] assert exc_info.value.field == "match_method" @pytest.mark.unit class TestBindingContext: """绑定上下文 DTO 测试。""" def test_valid_construction(self): # Act ctx = BindingContext( session_key="session-key", channel_type=ChannelType("wechat"), account_id="acc-1", chat_type="p2p", peer_id="peer-1", ) # Assert assert ctx.conversation_id is None assert ctx.unified_identity_id is None def test_empty_session_key_raises(self): # Act / Assert with pytest.raises(ValidationError) as exc_info: BindingContext( session_key="", channel_type=ChannelType("wechat"), account_id="a1", chat_type="p2p", peer_id="p1", ) assert exc_info.value.field == "session_key" def test_empty_account_id_raises(self): # Act / Assert with pytest.raises(ValidationError) as exc_info: BindingContext( session_key="sk", channel_type=ChannelType("wechat"), account_id="", chat_type="p2p", peer_id="p1", ) assert exc_info.value.field == "account_id" def test_invalid_chat_type_raises(self): # Act / Assert with pytest.raises(ValidationError) as exc_info: BindingContext( session_key="sk", channel_type=ChannelType("wechat"), account_id="a1", chat_type="invalid", # type: ignore[arg-type] peer_id="p1", ) assert exc_info.value.field == "chat_type" @pytest.mark.unit class TestConfigFallbackChain: """配置回退链 DTO 测试。""" def test_fields_are_assigned(self): # Act chain = ConfigFallbackChain( match_key="wechat:*", match_source=ConfigMatchSource.WILDCARD, ) # Assert assert chain.match_value is None def test_empty_match_key_raises(self): # Act / Assert with pytest.raises(ValidationError) as exc_info: ConfigFallbackChain( match_key="", match_source=ConfigMatchSource.DIRECT, ) assert exc_info.value.field == "match_key" @pytest.mark.unit class TestNestedWhitelistDecision: """嵌套白名单决策 DTO 测试。""" def test_defaults(self): # Act decision = NestedWhitelistDecision( outer_configured=True, outer_matched=False ) # Assert assert decision.inner_decision is None