ForcePilot/backend/test/unit/channels/infrastructure/test_factory.py
Kris 361dbe2149 test: 批量修复单元测试与集成测试用例的各类问题
本次提交修复了多个测试文件中的问题:
1.  为各管道测试类添加tracer_port属性初始化
2.  修复QQBot客户端关闭异常的错误类型匹配
3.  修正配对接口查询的状态值小写格式
4.  移除ChannelType枚举值的显式.value调用
5.  新增QQ常量的导出项与Instagram测试桩函数
6.  修复API接口返回值解析,正确访问data字段
7.  调整企业微信富文本消息的断言结构
8.  修正微信iLink的消息类型测试用例
9.  替换废弃的datetime.utc相关导入为UTC常量
10. 修复QQBot白名单适配器的返回值结构
11. 调整outbox工具的channel_msg_id处理逻辑
12. 新增多个适配器与服务的测试用例,覆盖异常降级、缓存处理等场景
13. 重构部分QQBot适配器的辅助函数测试,清理冗余代码
14. 修复微信iLink客户端的上传接口调用参数与缓存清理逻辑
15. 修正配置导出接口的敏感字段过滤与返回值结构
2026-07-09 04:23:07 +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.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),
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)