这一批提交包含: 1. 配置项敏感字段标记与测试用例修复 2. 路由绑定乐观锁支持与静态路径校验 3. 微信WOC插件能力适配与新增单元测试 4. 多个适配器的接口对齐与测试补全 5. 新增定时任务清理处理器与依赖注入容器测试 6. 错误码体系扩展与整合测试 7. 删除临时验证脚本与代码清理
155 lines
5.2 KiB
Python
155 lines
5.2 KiB
Python
"""yuxi.channels.infrastructure.dependency_injection 单元测试。
|
||
|
||
覆盖 ``DependencyInjectionContainer`` 行为:
|
||
- ``registerSingleton`` 注册后 ``resolve`` 返回原实例(单例语义)
|
||
- 重复注册覆盖旧绑定(last-write-wins)
|
||
- 未注册接口 ``resolve`` 抛 ``InternalError``(装配期漏注册视为编程错误)
|
||
- 线程安全:并发注册与解析不破坏注册表一致性
|
||
|
||
不依赖运行中的 Docker 服务,纯单元测试。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import threading
|
||
from concurrent.futures import ThreadPoolExecutor
|
||
from unittest.mock import MagicMock
|
||
|
||
import pytest
|
||
from yuxi.channels.contract.errors import InternalError
|
||
from yuxi.channels.infrastructure.dependency_injection import (
|
||
DependencyInjectionContainer,
|
||
)
|
||
|
||
pytestmark = pytest.mark.unit
|
||
|
||
|
||
class _FakeInterface:
|
||
"""测试用接口类型占位。"""
|
||
|
||
|
||
class _FakeImpl:
|
||
"""测试用实现类型占位。"""
|
||
|
||
|
||
@pytest.mark.unit
|
||
class TestRegisterAndResolve:
|
||
"""registerSingleton + resolve 基本行为测试。"""
|
||
|
||
def test_register_and_resolve_returns_same_instance(self):
|
||
# Arrange
|
||
container = DependencyInjectionContainer()
|
||
instance = MagicMock()
|
||
|
||
# Act
|
||
container.registerSingleton(_FakeInterface, instance)
|
||
resolved = container.resolve(_FakeInterface)
|
||
|
||
# Assert
|
||
assert resolved is instance
|
||
|
||
def test_resolve_unregistered_interface_raises_internal_error(self):
|
||
# Arrange
|
||
container = DependencyInjectionContainer()
|
||
|
||
# Act / Assert: 未注册接口解析失败,错误码 INTERNAL(装配期漏注册)
|
||
with pytest.raises(InternalError) as exc_info:
|
||
container.resolve(_FakeInterface)
|
||
|
||
assert exc_info.value.error_code == "INTERNAL"
|
||
assert "_FakeInterface" in exc_info.value.message
|
||
|
||
def test_reregister_overrides_previous_binding(self):
|
||
"""重复注册覆盖旧绑定(last-write-wins,幂等覆盖语义)。"""
|
||
# Arrange
|
||
container = DependencyInjectionContainer()
|
||
first = MagicMock(name="first")
|
||
second = MagicMock(name="second")
|
||
|
||
# Act
|
||
container.registerSingleton(_FakeInterface, first)
|
||
container.registerSingleton(_FakeInterface, second)
|
||
resolved = container.resolve(_FakeInterface)
|
||
|
||
# Assert
|
||
assert resolved is second
|
||
assert resolved is not first
|
||
|
||
def test_multiple_interfaces_independent(self):
|
||
"""不同接口的注册互不影响。"""
|
||
# Arrange
|
||
container = DependencyInjectionContainer()
|
||
iface_a = MagicMock()
|
||
impl_b = MagicMock()
|
||
|
||
# Act
|
||
container.registerSingleton(_FakeInterface, iface_a)
|
||
container.registerSingleton(_FakeImpl, impl_b)
|
||
|
||
# Assert
|
||
assert container.resolve(_FakeInterface) is iface_a
|
||
assert container.resolve(_FakeImpl) is impl_b
|
||
|
||
|
||
@pytest.mark.unit
|
||
class TestThreadSafety:
|
||
"""容器线程安全测试。"""
|
||
|
||
def test_concurrent_register_and_resolve_consistent(self):
|
||
"""多线程并发注册与解析不破坏注册表一致性。
|
||
|
||
使用同一接口并发 register + resolve:所有 resolve 必须返回注册过的
|
||
某个实例(不能是 None 或未注册异常),证明锁保护下读写串行化。
|
||
"""
|
||
# Arrange
|
||
container = DependencyInjectionContainer()
|
||
instances = [MagicMock(name=f"inst-{i}") for i in range(20)]
|
||
resolved: list[MagicMock] = []
|
||
errors: list[Exception] = []
|
||
barrier = threading.Barrier(20)
|
||
|
||
def worker(idx: int) -> None:
|
||
barrier.wait()
|
||
instance = instances[idx]
|
||
container.registerSingleton(_FakeInterface, instance)
|
||
try:
|
||
resolved.append(container.resolve(_FakeInterface))
|
||
except Exception as e: # noqa: BLE001 - 测试需捕获全部异常
|
||
errors.append(e)
|
||
|
||
# Act
|
||
with ThreadPoolExecutor(max_workers=20) as executor:
|
||
list(executor.map(worker, range(20)))
|
||
|
||
# Assert
|
||
assert not errors, f"并发 resolve 抛出异常: {errors}"
|
||
# 所有解析结果必须是注册过的某个实例(last-write-wins 下最终为最后一个,
|
||
# 中间并发解析可能拿到任意已注册实例,但必须在 instances 集合中)
|
||
for r in resolved:
|
||
assert r in instances
|
||
|
||
def test_concurrent_resolve_after_register_no_error(self):
|
||
"""单次注册后并发 resolve 不抛异常且返回同一实例。"""
|
||
# Arrange
|
||
container = DependencyInjectionContainer()
|
||
instance = MagicMock()
|
||
container.registerSingleton(_FakeInterface, instance)
|
||
results: list[object] = []
|
||
errors: list[Exception] = []
|
||
barrier = threading.Barrier(50)
|
||
|
||
def worker(_) -> None:
|
||
barrier.wait()
|
||
try:
|
||
results.append(container.resolve(_FakeInterface))
|
||
except Exception as e: # noqa: BLE001
|
||
errors.append(e)
|
||
|
||
# Act
|
||
with ThreadPoolExecutor(max_workers=50) as executor:
|
||
list(executor.map(worker, range(50)))
|
||
|
||
# Assert
|
||
assert not errors
|
||
assert all(r is instance for r in results)
|