ForcePilot/backend/test/unit/channels/infrastructure/test_factory.py
Kris 713fcdcd8e refactor(test): 批量重构测试代码,简化调度器handler测试逻辑
1.  统一替换多个调度器handler测试用例,移除冗余的session_factory相关代码和fake session上下文
2.  调整ChannelPersistenceAdapter测试,使用模块级patcher管理并统一初始化方式
3.  修正OutboxEntry聚合根操作的版本号逻辑,移除不必要的版本递增
4.  新增UTC时区转换相关测试用例,完善datetime类型映射测试
5.  更新微信WOC适配器测试,适配新的session_type枚举值
6.  优化测试代码的可读性和一致性,统一测试辅助函数的实现方式
2026-07-10 14:29:23 +08:00

679 lines
27 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""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 个内置 matchersession_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 透传到 RedisConfigAdapterF-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)