1. 统一替换多个调度器handler测试用例,移除冗余的session_factory相关代码和fake session上下文 2. 调整ChannelPersistenceAdapter测试,使用模块级patcher管理并统一初始化方式 3. 修正OutboxEntry聚合根操作的版本号逻辑,移除不必要的版本递增 4. 新增UTC时区转换相关测试用例,完善datetime类型映射测试 5. 更新微信WOC适配器测试,适配新的session_type枚举值 6. 优化测试代码的可读性和一致性,统一测试辅助函数的实现方式
679 lines
27 KiB
Python
679 lines
27 KiB
Python
"""yuxi.channels.infrastructure.factory 单元测试。
|
||
|
||
覆盖以下纯函数 / 装配辅助函数:
|
||
- ``_register_default_match_tiers``:注册 8 个默认层级 + 6 个内置 matcher
|
||
- ``_construct_health_dependencies``:构造健康检查子依赖五元组
|
||
- ``_register_builtin_event_subscribers``:注册内置事件订阅者
|
||
- ``_register_di_singletons``:注册全量 DI 单例到容器
|
||
|
||
``create_host_bootstrap`` / ``create_host_shutdown`` / ``load_channel_plugins``
|
||
依赖 Redis / ARQ / DB 等外部资源,不属于单元测试范畴,本文件不覆盖。
|
||
|
||
不依赖运行中的 Docker 服务,纯单元测试。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from unittest.mock import MagicMock
|
||
|
||
import pytest
|
||
from yuxi.channels.application.circuit_breaker.channel_circuit_breaker import (
|
||
ChannelCircuitBreaker,
|
||
)
|
||
from yuxi.channels.application.extension.event_bus import EventBus
|
||
from yuxi.channels.application.health.channel_probe import ChannelProbe
|
||
from yuxi.channels.application.health.diagnostics_exporter import (
|
||
DiagnosticsExporter,
|
||
)
|
||
from yuxi.channels.application.health.health_aggregator import HealthAggregator
|
||
from yuxi.channels.application.lifecycle.plugin_lifecycle_manager import (
|
||
PluginLifecycleManager,
|
||
)
|
||
from yuxi.channels.application.lifecycle.plugin_loader import PluginLoader
|
||
from yuxi.channels.application.pipeline.stage_slot_injector import StageSlotInjector
|
||
from yuxi.channels.application.transport import TransportManager
|
||
from yuxi.channels.adapters.content_review_repository_adapter import (
|
||
ContentReviewRepositoryAdapter,
|
||
)
|
||
from yuxi.channels.adapters.default_content_moderation_adapter import (
|
||
DefaultContentModerationAdapter,
|
||
)
|
||
from yuxi.channels.adapters.inmemory_tracer_adapter import (
|
||
InMemoryTracerAdapter,
|
||
)
|
||
from yuxi.channels.adapters.redis_config_adapter import RedisConfigAdapter
|
||
from yuxi.channels.application.extension.channel_event_broadcaster import (
|
||
ChannelEventBroadcaster,
|
||
)
|
||
from yuxi.channels.application.extension.config_source_registry import (
|
||
ConfigSourceRegistry,
|
||
)
|
||
from yuxi.channels.application.extension.event_subscription_registry import (
|
||
EventSubscriptionRegistry,
|
||
)
|
||
from yuxi.channels.application.extension.stage_slot_registry import StageSlotRegistry
|
||
from yuxi.channels.application.lifecycle.config_scope_registry import (
|
||
ConfigScopeRegistry,
|
||
)
|
||
from yuxi.channels.application.lifecycle.sensitive_field_registry import (
|
||
SensitiveFieldRegistry,
|
||
)
|
||
from yuxi.channels.application.lifecycle.plugin_dependency_resolver import (
|
||
PluginDependencyResolver,
|
||
)
|
||
from yuxi.channels.contract.dtos.outbox import OutboxConfig
|
||
from yuxi.channels.contract.ports.driven.cache_port import CachePort
|
||
from yuxi.channels.contract.ports.driven.config_port import ConfigPort
|
||
from yuxi.channels.contract.ports.driven.event_publisher_port import (
|
||
EventPublisherPort,
|
||
)
|
||
from yuxi.channels.contract.ports.driven.logger_port import LoggerPort
|
||
from yuxi.channels.contract.ports.driven.persistence_port import PersistencePort
|
||
from yuxi.channels.contract.ports.driven.queue_port import QueuePort
|
||
from yuxi.channels.contract.ports.driven.content_review_repository_port import (
|
||
ContentReviewRepositoryPort,
|
||
)
|
||
from yuxi.channels.contract.ports.driven.agent_run_execution_port import (
|
||
AgentRunExecutionPort,
|
||
)
|
||
from yuxi.channels.core.registry.capability_registry import CapabilityRegistry
|
||
from yuxi.channels.core.registry.command_registry import CommandRegistryTable
|
||
from yuxi.channels.core.registry.identity_resolver_registry import (
|
||
IdentityResolverRegistry,
|
||
)
|
||
from yuxi.channels.core.registry.plugin_registry import PluginRegistry
|
||
from yuxi.channels.core.registry.route_match_registry import RouteMatchRegistry
|
||
from yuxi.channels.core.registry.whitelist_registry import WhitelistRegistry
|
||
from yuxi.channels.core.service.degradation_manager import DegradationManager
|
||
from yuxi.channels.infrastructure.dependency_injection import (
|
||
DependencyInjectionContainer,
|
||
)
|
||
from yuxi.channels.infrastructure.factory import (
|
||
_PluginLoadingCore,
|
||
_construct_health_dependencies,
|
||
_register_builtin_event_subscribers,
|
||
_register_default_match_tiers,
|
||
_register_di_singletons,
|
||
)
|
||
|
||
pytestmark = pytest.mark.unit
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# _register_default_match_tiers
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
@pytest.mark.unit
|
||
class TestRegisterDefaultMatchTiers:
|
||
"""``_register_default_match_tiers`` 注册 8 个默认层级 + 6 个内置 matcher。"""
|
||
|
||
def test_registers_eight_default_tiers(self, fake_logger):
|
||
# Arrange
|
||
registry = RouteMatchRegistry()
|
||
|
||
# Act
|
||
_register_default_match_tiers(registry)
|
||
|
||
# Assert: 8 个默认层级
|
||
tiers = registry.listTiers()
|
||
tier_names = [t.name for t in tiers]
|
||
expected_names = [
|
||
"session_key",
|
||
"identity_id",
|
||
"peer_id",
|
||
"chat_type",
|
||
"channel_session",
|
||
"channel_type",
|
||
"account",
|
||
"default",
|
||
]
|
||
assert sorted(tier_names) == sorted(expected_names)
|
||
assert len(tiers) == 8
|
||
|
||
def test_default_tier_priorities_descending_for_route_resolver(self, fake_logger):
|
||
"""优先级数值由高到低:session_key(800) → default(100)。"""
|
||
# Arrange
|
||
registry = RouteMatchRegistry()
|
||
|
||
# Act
|
||
_register_default_match_tiers(registry)
|
||
|
||
# Assert
|
||
tiers_by_name = {t.name: t.priority for t in registry.listTiers()}
|
||
assert tiers_by_name["session_key"] == 800
|
||
assert tiers_by_name["identity_id"] == 700
|
||
assert tiers_by_name["peer_id"] == 600
|
||
assert tiers_by_name["chat_type"] == 500
|
||
assert tiers_by_name["channel_session"] == 400
|
||
assert tiers_by_name["channel_type"] == 300
|
||
assert tiers_by_name["account"] == 200
|
||
assert tiers_by_name["default"] == 100
|
||
|
||
def test_registers_six_builtin_matchers(self, fake_logger):
|
||
"""注册 6 个内置 matcher(session_key/identity_id/peer_id/chat_type/account/default)。
|
||
|
||
channel_session / channel_type 两层无内置 matcher,由插件可注入。
|
||
"""
|
||
# Arrange
|
||
registry = RouteMatchRegistry()
|
||
|
||
# Act
|
||
_register_default_match_tiers(registry)
|
||
|
||
# Assert: 通过尝试注册同名的 matcher 不抛错来间接验证已注册
|
||
# (registerMatcher 内部允许覆盖)。这里仅断言 6 个 matcher 名称
|
||
# 可以重新注册(覆盖),不会因为缺少对应层级而失败。
|
||
builtin_matcher_names = [
|
||
"session_key",
|
||
"identity_id",
|
||
"peer_id",
|
||
"chat_type",
|
||
"account",
|
||
"default",
|
||
]
|
||
for name in builtin_matcher_names:
|
||
# registerMatcher 内部按名查找 tier,未注册时会抛 ValidationError
|
||
# 这里重新注册同名 matcher 不抛错即证明对应 tier 与原 matcher 已存在
|
||
registry.registerMatcher(name, lambda ctx: None)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# _construct_health_dependencies
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
@pytest.mark.unit
|
||
class TestConstructHealthDependencies:
|
||
"""``_construct_health_dependencies`` 构造健康检查子依赖五元组。"""
|
||
|
||
def test_returns_five_tuple_with_expected_types(self, fake_logger):
|
||
# Arrange
|
||
plugin_registry = MagicMock(spec=PluginRegistry)
|
||
persistence_port = MagicMock(spec=PersistencePort)
|
||
cache_port = MagicMock(spec=CachePort)
|
||
queue_port = MagicMock(spec=QueuePort)
|
||
capability_registry = MagicMock(spec=CapabilityRegistry)
|
||
event_publisher = MagicMock(spec=EventBus)
|
||
redis_client = MagicMock()
|
||
|
||
# Act
|
||
(
|
||
config_port,
|
||
channel_circuit_breaker,
|
||
channel_probe,
|
||
tracer_port,
|
||
diagnostics_exporter,
|
||
) = _construct_health_dependencies(
|
||
plugin_registry=plugin_registry,
|
||
persistence_port=persistence_port,
|
||
cache_port=cache_port,
|
||
queue_port=queue_port,
|
||
capability_registry=capability_registry,
|
||
event_publisher=event_publisher,
|
||
redis_client=redis_client,
|
||
logger=fake_logger,
|
||
)
|
||
|
||
# Assert
|
||
assert isinstance(config_port, RedisConfigAdapter)
|
||
assert isinstance(channel_circuit_breaker, ChannelCircuitBreaker)
|
||
assert isinstance(channel_probe, ChannelProbe)
|
||
assert isinstance(tracer_port, InMemoryTracerAdapter)
|
||
assert isinstance(diagnostics_exporter, DiagnosticsExporter)
|
||
|
||
def test_key_to_scope_map_propagated_to_config_port(self, fake_logger):
|
||
"""key_to_scope_map 透传到 RedisConfigAdapter(F-02)。"""
|
||
# Arrange
|
||
from yuxi.channels.contract.dtos.config import ConfigScope
|
||
|
||
key_to_scope_map = {"channel.test_key": ConfigScope.CHANNEL}
|
||
|
||
# Act
|
||
(
|
||
config_port,
|
||
*_rest,
|
||
) = _construct_health_dependencies(
|
||
plugin_registry=MagicMock(spec=PluginRegistry),
|
||
persistence_port=MagicMock(spec=PersistencePort),
|
||
cache_port=MagicMock(spec=CachePort),
|
||
queue_port=MagicMock(spec=QueuePort),
|
||
capability_registry=MagicMock(spec=CapabilityRegistry),
|
||
event_publisher=MagicMock(spec=EventBus),
|
||
redis_client=MagicMock(),
|
||
logger=fake_logger,
|
||
key_to_scope_map=key_to_scope_map,
|
||
)
|
||
|
||
# Assert: RedisConfigAdapter 持有 key_to_scope_map 视图
|
||
assert config_port._key_to_scope_map is key_to_scope_map
|
||
|
||
def test_key_to_scope_map_none_does_not_raise(self, fake_logger):
|
||
"""key_to_scope_map=None 时正常构造(向后兼容)。"""
|
||
# Act
|
||
result = _construct_health_dependencies(
|
||
plugin_registry=MagicMock(spec=PluginRegistry),
|
||
persistence_port=MagicMock(spec=PersistencePort),
|
||
cache_port=MagicMock(spec=CachePort),
|
||
queue_port=MagicMock(spec=QueuePort),
|
||
capability_registry=MagicMock(spec=CapabilityRegistry),
|
||
event_publisher=MagicMock(spec=EventBus),
|
||
redis_client=MagicMock(),
|
||
logger=fake_logger,
|
||
key_to_scope_map=None,
|
||
)
|
||
|
||
# Assert
|
||
assert len(result) == 5
|
||
assert isinstance(result[0], RedisConfigAdapter)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# _register_builtin_event_subscribers
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
@pytest.mark.unit
|
||
class TestRegisterBuiltinEventSubscribers:
|
||
"""``_register_builtin_event_subscribers`` 注册内置事件订阅。"""
|
||
|
||
def _make_event_bus(self, fake_logger) -> EventBus:
|
||
"""构造真实 EventBus 实例(避免 mock 丢失内部状态)。"""
|
||
degradation_manager = MagicMock(spec=DegradationManager)
|
||
return EventBus(logger=fake_logger, degradation_manager=degradation_manager)
|
||
|
||
def test_registers_outbox_state_changed_subscriber(self, fake_logger):
|
||
# Arrange
|
||
event_bus = self._make_event_bus(fake_logger)
|
||
|
||
# Act
|
||
_register_builtin_event_subscribers(
|
||
event_bus=event_bus,
|
||
persistence_port=MagicMock(spec=PersistencePort),
|
||
queue_port=MagicMock(spec=QueuePort),
|
||
whitelist_registry=MagicMock(spec=WhitelistRegistry),
|
||
cache_port=MagicMock(spec=CachePort),
|
||
plugin_registry=MagicMock(spec=PluginRegistry),
|
||
logger=fake_logger,
|
||
)
|
||
|
||
# Assert
|
||
assert "OutboxStateChanged" in event_bus._subscriptions
|
||
assert len(event_bus._subscriptions["OutboxStateChanged"]) == 1
|
||
|
||
def test_registers_eight_plugin_lifecycle_event_subscribers(self, fake_logger):
|
||
"""8 类插件生命周期事件共用同一 PluginLifecycleAuditHandler 实例。"""
|
||
# Arrange
|
||
event_bus = self._make_event_bus(fake_logger)
|
||
plugin_event_types = (
|
||
"PluginDiscovered",
|
||
"PluginLoaded",
|
||
"PluginStarted",
|
||
"PluginPaused",
|
||
"PluginResumed",
|
||
"PluginStopped",
|
||
"PluginUnloaded",
|
||
"PluginFailed",
|
||
)
|
||
|
||
# Act
|
||
_register_builtin_event_subscribers(
|
||
event_bus=event_bus,
|
||
persistence_port=MagicMock(spec=PersistencePort),
|
||
queue_port=MagicMock(spec=QueuePort),
|
||
whitelist_registry=MagicMock(spec=WhitelistRegistry),
|
||
cache_port=MagicMock(spec=CachePort),
|
||
plugin_registry=MagicMock(spec=PluginRegistry),
|
||
logger=fake_logger,
|
||
)
|
||
|
||
# Assert
|
||
for event_type in plugin_event_types:
|
||
assert event_type in event_bus._subscriptions
|
||
assert len(event_bus._subscriptions[event_type]) == 1
|
||
|
||
def test_registers_config_event_pair_for_whitelist_and_route_match(self, fake_logger):
|
||
"""ConfigChanged/ConfigRollback 各注册 WhitelistConfigHandler +
|
||
RouteMatchCacheHandler 共 2 个订阅。"""
|
||
# Arrange
|
||
event_bus = self._make_event_bus(fake_logger)
|
||
|
||
# Act
|
||
_register_builtin_event_subscribers(
|
||
event_bus=event_bus,
|
||
persistence_port=MagicMock(spec=PersistencePort),
|
||
queue_port=MagicMock(spec=QueuePort),
|
||
whitelist_registry=MagicMock(spec=WhitelistRegistry),
|
||
cache_port=MagicMock(spec=CachePort),
|
||
plugin_registry=MagicMock(spec=PluginRegistry),
|
||
logger=fake_logger,
|
||
)
|
||
|
||
# Assert
|
||
for config_event in ("ConfigChanged", "ConfigRollback"):
|
||
assert config_event in event_bus._subscriptions
|
||
assert len(event_bus._subscriptions[config_event]) == 2
|
||
|
||
def test_registers_pairing_approved_subscriber(self, fake_logger):
|
||
# Arrange
|
||
event_bus = self._make_event_bus(fake_logger)
|
||
|
||
# Act
|
||
_register_builtin_event_subscribers(
|
||
event_bus=event_bus,
|
||
persistence_port=MagicMock(spec=PersistencePort),
|
||
queue_port=MagicMock(spec=QueuePort),
|
||
whitelist_registry=MagicMock(spec=WhitelistRegistry),
|
||
cache_port=MagicMock(spec=CachePort),
|
||
plugin_registry=MagicMock(spec=PluginRegistry),
|
||
logger=fake_logger,
|
||
)
|
||
|
||
# Assert
|
||
assert "PairingApproved" in event_bus._subscriptions
|
||
assert len(event_bus._subscriptions["PairingApproved"]) == 1
|
||
|
||
def test_registers_channel_degraded_and_recovered_subscribers(self, fake_logger):
|
||
# Arrange
|
||
event_bus = self._make_event_bus(fake_logger)
|
||
|
||
# Act
|
||
_register_builtin_event_subscribers(
|
||
event_bus=event_bus,
|
||
persistence_port=MagicMock(spec=PersistencePort),
|
||
queue_port=MagicMock(spec=QueuePort),
|
||
whitelist_registry=MagicMock(spec=WhitelistRegistry),
|
||
cache_port=MagicMock(spec=CachePort),
|
||
plugin_registry=MagicMock(spec=PluginRegistry),
|
||
logger=fake_logger,
|
||
)
|
||
|
||
# Assert
|
||
assert len(event_bus._subscriptions["ChannelDegraded"]) == 1
|
||
assert len(event_bus._subscriptions["ChannelRecovered"]) == 1
|
||
|
||
def test_registers_three_channel_event_broadcaster_subscribers(self, fake_logger):
|
||
"""ChannelSessionUpdated/ChannelMessageReceived/ChannelMessageSent → 广播器。"""
|
||
# Arrange
|
||
event_bus = self._make_event_bus(fake_logger)
|
||
|
||
# Act
|
||
_register_builtin_event_subscribers(
|
||
event_bus=event_bus,
|
||
persistence_port=MagicMock(spec=PersistencePort),
|
||
queue_port=MagicMock(spec=QueuePort),
|
||
whitelist_registry=MagicMock(spec=WhitelistRegistry),
|
||
cache_port=MagicMock(spec=CachePort),
|
||
plugin_registry=MagicMock(spec=PluginRegistry),
|
||
logger=fake_logger,
|
||
)
|
||
|
||
# Assert
|
||
for channel_event in (
|
||
"ChannelSessionUpdated",
|
||
"ChannelMessageReceived",
|
||
"ChannelMessageSent",
|
||
):
|
||
assert channel_event in event_bus._subscriptions
|
||
assert len(event_bus._subscriptions[channel_event]) == 1
|
||
|
||
def test_di_container_register_broadcaster_when_provided(self, fake_logger):
|
||
"""di_container 提供时注册 ChannelEventBroadcaster 单例。"""
|
||
# Arrange
|
||
event_bus = self._make_event_bus(fake_logger)
|
||
di_container = DependencyInjectionContainer()
|
||
|
||
# Act
|
||
_register_builtin_event_subscribers(
|
||
event_bus=event_bus,
|
||
persistence_port=MagicMock(spec=PersistencePort),
|
||
queue_port=MagicMock(spec=QueuePort),
|
||
whitelist_registry=MagicMock(spec=WhitelistRegistry),
|
||
cache_port=MagicMock(spec=CachePort),
|
||
plugin_registry=MagicMock(spec=PluginRegistry),
|
||
logger=fake_logger,
|
||
di_container=di_container,
|
||
)
|
||
|
||
# Assert: ChannelEventBroadcaster 单例可解析
|
||
broadcaster = di_container.resolve(ChannelEventBroadcaster)
|
||
assert isinstance(broadcaster, ChannelEventBroadcaster)
|
||
|
||
def test_di_container_none_does_not_raise(self, fake_logger):
|
||
"""di_container=None 时不抛错(仅不注册 broadcaster 单例)。"""
|
||
# Arrange
|
||
event_bus = self._make_event_bus(fake_logger)
|
||
|
||
# Act / Assert: 不抛异常
|
||
_register_builtin_event_subscribers(
|
||
event_bus=event_bus,
|
||
persistence_port=MagicMock(spec=PersistencePort),
|
||
queue_port=MagicMock(spec=QueuePort),
|
||
whitelist_registry=MagicMock(spec=WhitelistRegistry),
|
||
cache_port=MagicMock(spec=CachePort),
|
||
plugin_registry=MagicMock(spec=PluginRegistry),
|
||
logger=fake_logger,
|
||
di_container=None,
|
||
)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# _register_di_singletons
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
def _make_plugin_loading_core() -> _PluginLoadingCore:
|
||
"""构造 _PluginLoadingCore 桩,所有字段使用 MagicMock。"""
|
||
return _PluginLoadingCore(
|
||
plugin_registry=MagicMock(spec=PluginRegistry),
|
||
stage_slot_registry=MagicMock(spec=StageSlotRegistry),
|
||
event_subscription_registry=MagicMock(spec=EventSubscriptionRegistry),
|
||
config_source_registry=MagicMock(spec=ConfigSourceRegistry),
|
||
route_match_registry=MagicMock(spec=RouteMatchRegistry),
|
||
whitelist_registry=MagicMock(spec=WhitelistRegistry),
|
||
capability_registry=MagicMock(spec=CapabilityRegistry),
|
||
event_bus=MagicMock(spec=EventBus),
|
||
cache_port=MagicMock(spec=CachePort),
|
||
degradation_manager=MagicMock(spec=DegradationManager),
|
||
plugin_lifecycle_manager=MagicMock(spec=PluginLifecycleManager),
|
||
plugin_dependency_resolver=MagicMock(spec=PluginDependencyResolver),
|
||
plugin_loader=MagicMock(spec=PluginLoader),
|
||
outbox_config=OutboxConfig(
|
||
ttl_seconds=3600,
|
||
retry_backoff_schedule=(60,),
|
||
max_retry=3,
|
||
),
|
||
redis_client=MagicMock(),
|
||
arq_pool=MagicMock(),
|
||
execution_port_impl=MagicMock(),
|
||
sensitive_registry=MagicMock(spec=SensitiveFieldRegistry),
|
||
config_scope_registry=MagicMock(spec=ConfigScopeRegistry),
|
||
)
|
||
|
||
|
||
@pytest.mark.unit
|
||
class TestRegisterDiSingletons:
|
||
"""``_register_di_singletons`` 注册全量 DI 单例到容器。"""
|
||
|
||
def test_registers_core_registries_and_shared_dependencies(self, fake_logger):
|
||
# Arrange
|
||
di = DependencyInjectionContainer()
|
||
core = _make_plugin_loading_core()
|
||
|
||
# Act
|
||
_register_di_singletons(
|
||
di_container=di,
|
||
core=core,
|
||
persistence_port=MagicMock(spec=PersistencePort),
|
||
session_factory=MagicMock(),
|
||
queue_port=MagicMock(spec=QueuePort),
|
||
config_port=MagicMock(spec=ConfigPort),
|
||
stage_slot_injector=MagicMock(spec=StageSlotInjector),
|
||
health_aggregator=MagicMock(spec=HealthAggregator),
|
||
channel_circuit_breaker=MagicMock(spec=ChannelCircuitBreaker),
|
||
transport_manager=MagicMock(spec=TransportManager),
|
||
logger=fake_logger,
|
||
)
|
||
|
||
# Assert: 注册中心与共享依赖
|
||
assert di.resolve(PluginRegistry) is core.plugin_registry
|
||
assert di.resolve(CapabilityRegistry) is core.capability_registry
|
||
assert di.resolve(RouteMatchRegistry) is core.route_match_registry
|
||
assert di.resolve(WhitelistRegistry) is core.whitelist_registry
|
||
assert di.resolve(EventBus) is core.event_bus
|
||
assert di.resolve(EventPublisherPort) is core.event_bus
|
||
assert di.resolve(StageSlotRegistry) is core.stage_slot_registry
|
||
assert di.resolve(EventSubscriptionRegistry) is core.event_subscription_registry
|
||
assert di.resolve(ConfigSourceRegistry) is core.config_source_registry
|
||
assert di.resolve(PluginLifecycleManager) is core.plugin_lifecycle_manager
|
||
assert di.resolve(PluginDependencyResolver) is core.plugin_dependency_resolver
|
||
assert di.resolve(PluginLoader) is core.plugin_loader
|
||
assert di.resolve(SensitiveFieldRegistry) is core.sensitive_registry
|
||
assert di.resolve(ConfigScopeRegistry) is core.config_scope_registry
|
||
|
||
def test_registers_command_and_identity_resolver_registries(self, fake_logger):
|
||
"""命令注册表与身份解析器注册表由本函数构造并注册。"""
|
||
# Arrange
|
||
di = DependencyInjectionContainer()
|
||
core = _make_plugin_loading_core()
|
||
|
||
# Act
|
||
_register_di_singletons(
|
||
di_container=di,
|
||
core=core,
|
||
persistence_port=MagicMock(spec=PersistencePort),
|
||
session_factory=MagicMock(),
|
||
queue_port=MagicMock(spec=QueuePort),
|
||
config_port=MagicMock(spec=ConfigPort),
|
||
stage_slot_injector=MagicMock(spec=StageSlotInjector),
|
||
health_aggregator=MagicMock(spec=HealthAggregator),
|
||
channel_circuit_breaker=MagicMock(spec=ChannelCircuitBreaker),
|
||
transport_manager=MagicMock(spec=TransportManager),
|
||
logger=fake_logger,
|
||
)
|
||
|
||
# Assert
|
||
cmd_registry = di.resolve(CommandRegistryTable)
|
||
assert isinstance(cmd_registry, CommandRegistryTable)
|
||
identity_registry = di.resolve(IdentityResolverRegistry)
|
||
assert isinstance(identity_registry, IdentityResolverRegistry)
|
||
|
||
def test_registers_app_level_ports_and_orchestration_components(self, fake_logger):
|
||
# Arrange
|
||
di = DependencyInjectionContainer()
|
||
core = _make_plugin_loading_core()
|
||
persistence_port = MagicMock(spec=PersistencePort)
|
||
queue_port = MagicMock(spec=QueuePort)
|
||
config_port = MagicMock(spec=ConfigPort)
|
||
stage_slot_injector = MagicMock(spec=StageSlotInjector)
|
||
health_aggregator = MagicMock(spec=HealthAggregator)
|
||
channel_circuit_breaker = MagicMock(spec=ChannelCircuitBreaker)
|
||
transport_manager = MagicMock(spec=TransportManager)
|
||
|
||
# Act
|
||
_register_di_singletons(
|
||
di_container=di,
|
||
core=core,
|
||
persistence_port=persistence_port,
|
||
session_factory=MagicMock(),
|
||
queue_port=queue_port,
|
||
config_port=config_port,
|
||
stage_slot_injector=stage_slot_injector,
|
||
health_aggregator=health_aggregator,
|
||
channel_circuit_breaker=channel_circuit_breaker,
|
||
transport_manager=transport_manager,
|
||
logger=fake_logger,
|
||
)
|
||
|
||
# Assert
|
||
assert di.resolve(StageSlotInjector) is stage_slot_injector
|
||
assert di.resolve(HealthAggregator) is health_aggregator
|
||
assert di.resolve(ChannelCircuitBreaker) is channel_circuit_breaker
|
||
assert di.resolve(LoggerPort) is fake_logger
|
||
assert di.resolve(CachePort) is core.cache_port
|
||
assert di.resolve(PersistencePort) is persistence_port
|
||
assert di.resolve(QueuePort) is queue_port
|
||
assert di.resolve(ConfigPort) is config_port
|
||
assert di.resolve(TransportManager) is transport_manager
|
||
|
||
def test_registers_content_review_domain_singletons(self, fake_logger):
|
||
"""内容审核域 3 个单例(CR-01~CR-03)。"""
|
||
# Arrange
|
||
di = DependencyInjectionContainer()
|
||
core = _make_plugin_loading_core()
|
||
session_factory = MagicMock()
|
||
|
||
# Act
|
||
_register_di_singletons(
|
||
di_container=di,
|
||
core=core,
|
||
persistence_port=MagicMock(spec=PersistencePort),
|
||
session_factory=session_factory,
|
||
queue_port=MagicMock(spec=QueuePort),
|
||
config_port=MagicMock(spec=ConfigPort),
|
||
stage_slot_injector=MagicMock(spec=StageSlotInjector),
|
||
health_aggregator=MagicMock(spec=HealthAggregator),
|
||
channel_circuit_breaker=MagicMock(spec=ChannelCircuitBreaker),
|
||
transport_manager=MagicMock(spec=TransportManager),
|
||
logger=fake_logger,
|
||
)
|
||
|
||
# Assert
|
||
moderation = di.resolve(DefaultContentModerationAdapter)
|
||
assert isinstance(moderation, DefaultContentModerationAdapter)
|
||
review_adapter = di.resolve(ContentReviewRepositoryAdapter)
|
||
assert isinstance(review_adapter, ContentReviewRepositoryAdapter)
|
||
review_port = di.resolve(ContentReviewRepositoryPort)
|
||
# ContentReviewRepositoryPort 与 ContentReviewRepositoryAdapter 绑定到同一实例
|
||
assert review_port is review_adapter
|
||
|
||
def test_registers_redis_arq_and_execution_port_singletons(self, fake_logger):
|
||
# Arrange
|
||
di = DependencyInjectionContainer()
|
||
core = _make_plugin_loading_core()
|
||
|
||
# Act
|
||
_register_di_singletons(
|
||
di_container=di,
|
||
core=core,
|
||
persistence_port=MagicMock(spec=PersistencePort),
|
||
session_factory=MagicMock(),
|
||
queue_port=MagicMock(spec=QueuePort),
|
||
config_port=MagicMock(spec=ConfigPort),
|
||
stage_slot_injector=MagicMock(spec=StageSlotInjector),
|
||
health_aggregator=MagicMock(spec=HealthAggregator),
|
||
channel_circuit_breaker=MagicMock(spec=ChannelCircuitBreaker),
|
||
transport_manager=MagicMock(spec=TransportManager),
|
||
logger=fake_logger,
|
||
)
|
||
|
||
# Assert: 共享 Redis / ARQ / AgentRunExecutionPort 单例
|
||
# (由 core 直接注册)
|
||
assert di.resolve(AgentRunExecutionPort) is core.execution_port_impl
|
||
|
||
def test_async_session_not_registered_as_singleton(self, fake_logger):
|
||
"""AsyncSession 是请求级资源,不应注册为单例;session_factory 注册为单例供按需创建。"""
|
||
# Arrange
|
||
di = DependencyInjectionContainer()
|
||
core = _make_plugin_loading_core()
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
# Act
|
||
_register_di_singletons(
|
||
di_container=di,
|
||
core=core,
|
||
persistence_port=MagicMock(spec=PersistencePort),
|
||
session_factory=MagicMock(),
|
||
queue_port=MagicMock(spec=QueuePort),
|
||
config_port=MagicMock(spec=ConfigPort),
|
||
stage_slot_injector=MagicMock(spec=StageSlotInjector),
|
||
health_aggregator=MagicMock(spec=HealthAggregator),
|
||
channel_circuit_breaker=MagicMock(spec=ChannelCircuitBreaker),
|
||
transport_manager=MagicMock(spec=TransportManager),
|
||
logger=fake_logger,
|
||
)
|
||
|
||
# Assert: AsyncSession 未注册为单例(避免并发污染)
|
||
with pytest.raises(Exception):
|
||
di.resolve(AsyncSession)
|