"""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)