ForcePilot/backend/test/unit/channels/infrastructure/test_factory.py
Kris 696f5f44ec chore: 完成多模块迭代优化与测试覆盖
这一批提交包含:
1. 配置项敏感字段标记与测试用例修复
2. 路由绑定乐观锁支持与静态路径校验
3. 微信WOC插件能力适配与新增单元测试
4. 多个适配器的接口对齐与测试补全
5. 新增定时任务清理处理器与依赖注入容器测试
6. 错误码体系扩展与整合测试
7. 删除临时验证脚本与代码清理
2026-07-08 12:58:43 +08:00

693 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.opentelemetry_tracer_adapter import (
OpenTelemetryTracerAdapter,
)
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, OpenTelemetryTracerAdapter)
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),
persistence_db=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),
persistence_db=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,
persistence_db=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()
persistence_db = MagicMock()
# Act
_register_di_singletons(
di_container=di,
core=core,
persistence_port=MagicMock(spec=PersistencePort),
persistence_db=persistence_db,
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),
persistence_db=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_persistence_db_not_registered_as_singleton(self, fake_logger):
"""persistence_db 是请求级资源,不应注册为 AsyncSession 单例。"""
# 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),
persistence_db=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)