"""Module Registry 单元测试.""" from __future__ import annotations import pytest from packages.infrastructure.module_registry import ( Module, ModuleCapability, ModuleRegistry, ModuleStatus, QuotaRule, module_registry, ) @pytest.fixture(autouse=True) def clean_registry(): """每个测试前后清空全局单例,避免测试间干扰.""" module_registry.clear() yield module_registry.clear() # ── Module 数据类测试 ────────────────────────────────────────────────── class TestModuleDataclass: """Module 数据类基本行为测试.""" def test_create_module_defaults(self): """创建模块,默认值正确.""" mod = Module(name="test_module") assert mod.name == "test_module" assert mod.version == "1.0.0" assert mod.description == "" assert mod.capabilities == [] assert mod.dependencies == [] assert mod.status == ModuleStatus.REGISTERED assert mod.config == {} def test_create_module_full(self): """创建模块,完整参数.""" mod = Module( name="ai_voice", version="2.0.0", description="AI配音模块", capabilities=[ModuleCapability(name="gen_voice")], dependencies=["core"], status=ModuleStatus.ACTIVE, config={"key": "value"}, ) assert mod.name == "ai_voice" assert mod.version == "2.0.0" assert mod.description == "AI配音模块" assert len(mod.capabilities) == 1 assert mod.dependencies == ["core"] assert mod.status == ModuleStatus.ACTIVE assert mod.config == {"key": "value"} def test_module_activate(self): """激活模块.""" mod = Module(name="m1") assert mod.status == ModuleStatus.REGISTERED mod.activate() assert mod.status == ModuleStatus.ACTIVE def test_module_activate_error_state_ignored(self): """error状态的模块不能激活.""" mod = Module(name="m1", status=ModuleStatus.ERROR) mod.activate() assert mod.status == ModuleStatus.ERROR def test_module_disable(self): """禁用模块.""" mod = Module(name="m1", status=ModuleStatus.ACTIVE) mod.disable() assert mod.status == ModuleStatus.DISABLED class TestQuotaRule: """QuotaRule 测试.""" def test_quota_rule_basic(self): """基本配额规则.""" rule = QuotaRule(dimension="credits", per_operation=1.0, description="每次消耗1积分") assert rule.dimension == "credits" assert rule.per_operation == 1.0 assert rule.description == "每次消耗1积分" def test_quota_rule_default_description(self): """默认描述为空.""" rule = QuotaRule(dimension="storage_gb", per_operation=0.5) assert rule.description == "" class TestModuleCapability: """ModuleCapability 测试.""" def test_capability_basic(self): """基本能力定义.""" cap = ModuleCapability(name="generate_voice", description="文本转配音") assert cap.name == "generate_voice" assert cap.description == "文本转配音" assert cap.quota_rules == [] assert cap.metadata == {} def test_capability_with_quota_rules(self): """带配额规则的能力.""" rules = [ QuotaRule("ai_credits", 1.0, "配音积分"), QuotaRule("storage_gb", 0.1, "存储占用"), ] cap = ModuleCapability( name="generate_voice", quota_rules=rules, metadata={"speed": "fast"}, ) assert len(cap.quota_rules) == 2 assert cap.metadata["speed"] == "fast" # ── ModuleRegistry 核心测试 ──────────────────────────────────────── class TestModuleRegistryRegister: """模块注册测试.""" def test_register_single_module(self): """注册单个模块.""" registry = ModuleRegistry() mod = Module(name="test_mod") registry.register(mod) assert registry.get("test_mod") is mod def test_register_duplicate_raises(self): """重复注册抛异常.""" registry = ModuleRegistry() registry.register(Module(name="m1")) with pytest.raises(ValueError, match="already registered"): registry.register(Module(name="m1")) def test_register_auto_activate_no_deps(self): """无依赖的模块注册后自动激活.""" registry = ModuleRegistry() registry.register(Module(name="m1")) assert registry.get("m1").status == ModuleStatus.ACTIVE def test_register_with_missing_dependency(self): """有未满足依赖的模块保持REGISTERED.""" registry = ModuleRegistry() registry.register(Module(name="m2", dependencies=["m1"])) assert registry.get("m2").status == ModuleStatus.REGISTERED def test_register_with_satisfied_dependency(self): """依赖已满足的模块注册后自动激活.""" registry = ModuleRegistry() registry.register(Module(name="m1")) registry.register(Module(name="m2", dependencies=["m1"])) assert registry.get("m2").status == ModuleStatus.ACTIVE class TestModuleRegistryUnregister: """模块注销测试.""" def test_unregister_existing(self): """注销已存在的模块.""" registry = ModuleRegistry() registry.register(Module(name="m1")) registry.unregister("m1") assert registry.get("m1") is None def test_unregister_nonexistent_raises(self): """注销不存在的模块抛异常.""" registry = ModuleRegistry() with pytest.raises(KeyError, match="not found"): registry.unregister("nonexistent") def test_unregister_with_dependents_raises(self): """被其他模块依赖时不能注销.""" registry = ModuleRegistry() registry.register(Module(name="core")) registry.register(Module(name="plugin", dependencies=["core"])) with pytest.raises(ValueError, match="depended on by"): registry.unregister("core") class TestModuleRegistryQuery: """模块查询测试.""" def test_get_nonexistent_returns_none(self): """获取不存在的模块返回None.""" registry = ModuleRegistry() assert registry.get("nonexistent") is None def test_list_modules_all(self): """列出所有模块.""" registry = ModuleRegistry() registry.register(Module(name="m1")) registry.register(Module(name="m2")) assert len(registry.list_modules()) == 2 def test_list_modules_by_status(self): """按状态过滤模块.""" registry = ModuleRegistry() registry.register(Module(name="m1")) # ACTIVE m2 = Module(name="m2", status=ModuleStatus.DISABLED) registry.register(m2) m2.disable() active = registry.list_modules(status=ModuleStatus.ACTIVE) assert len(active) == 1 assert active[0].name == "m1" def test_list_modules_disabled(self): """列出已禁用模块.""" registry = ModuleRegistry() registry.register(Module(name="m1")) m2 = Module(name="m2") registry.register(m2) m2.disable() disabled = registry.list_modules(status=ModuleStatus.DISABLED) assert len(disabled) == 1 assert disabled[0].name == "m2" class TestModuleRegistryCapabilities: """能力查询测试.""" def test_has_capability_true(self): """检查已存在的能力.""" registry = ModuleRegistry() registry.register( Module( name="ai_mod", capabilities=[ModuleCapability(name="generate_voice")], ) ) assert registry.has_capability("generate_voice") is True def test_has_capability_false(self): """检查不存在的能力.""" registry = ModuleRegistry() registry.register(Module(name="m1")) assert registry.has_capability("nonexistent") is False def test_has_capability_inactive_module(self): """非激活模块的能力不计入.""" registry = ModuleRegistry() m = Module( name="ai_mod", status=ModuleStatus.DISABLED, capabilities=[ModuleCapability(name="generate_voice")], ) registry._modules["ai_mod"] = m assert registry.has_capability("generate_voice") is False def test_get_capability_returns_definition(self): """获取能力定义.""" registry = ModuleRegistry() cap = ModuleCapability(name="gen_voice", description="配音") registry.register(Module(name="ai_mod", capabilities=[cap])) result = registry.get_capability("gen_voice") assert result is not None assert result.name == "gen_voice" assert result.description == "配音" def test_get_capability_nonexistent(self): """获取不存在的能力返回None.""" registry = ModuleRegistry() assert registry.get_capability("nonexistent") is None def test_get_quota_rules_empty(self): """没有配额规则时返回空列表.""" registry = ModuleRegistry() registry.register( Module( name="m1", capabilities=[ModuleCapability(name="do_something")], ) ) rules = registry.get_quota_rules("do_something") assert rules == [] def test_get_quota_rules_with_rules(self): """获取配额规则.""" registry = ModuleRegistry() rules = [QuotaRule("credits", 2.0)] registry.register( Module( name="m1", capabilities=[ModuleCapability(name="do_something", quota_rules=rules)], ) ) result = registry.get_quota_rules("do_something") assert len(result) == 1 assert result[0].dimension == "credits" assert result[0].per_operation == 2.0 def test_get_active_capabilities(self): """获取所有已激活模块的能力.""" registry = ModuleRegistry() registry.register( Module( name="mod_a", capabilities=[ ModuleCapability(name="cap_a1"), ModuleCapability(name="cap_a2"), ], ) ) registry.register( Module( name="mod_b", capabilities=[ModuleCapability(name="cap_b1")], ) ) result = registry.get_active_capabilities() assert "mod_a" in result assert "mod_b" in result assert set(result["mod_a"]) == {"cap_a1", "cap_a2"} assert result["mod_b"] == ["cap_b1"] class TestModuleRegistryDependencies: """依赖检查测试.""" def test_check_dependencies_satisfied(self): """依赖满足.""" registry = ModuleRegistry() registry.register(Module(name="core")) registry.register(Module(name="plugin", dependencies=["core"])) assert registry.check_dependencies("plugin") is True def test_check_dependencies_missing(self): """依赖缺失.""" registry = ModuleRegistry() registry.register(Module(name="plugin", dependencies=["core"])) assert registry.check_dependencies("plugin") is False def test_check_dependencies_module_not_found(self): """模块不存在返回False.""" registry = ModuleRegistry() assert registry.check_dependencies("nonexistent") is False def test_check_dependencies_inactive_dep(self): """依赖模块未激活.""" registry = ModuleRegistry() core = Module(name="core", status=ModuleStatus.DISABLED) registry._modules["core"] = core registry.register(Module(name="plugin", dependencies=["core"])) # 注册plugin时core不是ACTIVE,所以plugin不会自动激活 assert registry.check_dependencies("plugin") is False class TestModuleRegistryClear: """清空注册测试.""" def test_clear_removes_all(self): """清空所有模块.""" registry = ModuleRegistry() registry.register(Module(name="m1")) registry.register(Module(name="m2")) assert len(registry.list_modules()) == 2 registry.clear() assert len(registry.list_modules()) == 0 def test_global_singleton_clear(self): """全局单例清空有效.""" module_registry.register(Module(name="global_test")) assert module_registry.get("global_test") is not None # fixture 会在每个测试前后清空,这里手动验证 module_registry.clear() assert module_registry.get("global_test") is None class TestModuleStatus: """ModuleStatus 枚举测试.""" def test_status_values(self): """状态枚举值正确.""" assert ModuleStatus.REGISTERED.value == "registered" assert ModuleStatus.ACTIVE.value == "active" assert ModuleStatus.DISABLED.value == "disabled" assert ModuleStatus.ERROR.value == "error" # ── Module 更多状态转换测试 ────────────────────────────────────────────────── class TestModuleStateTransitions: """Module 状态转换补充测试.""" def test_activate_twice_idempotent(self): """多次激活不报错.""" mod = Module(name="m1") mod.activate() mod.activate() assert mod.status == ModuleStatus.ACTIVE def test_disable_twice_idempotent(self): """多次禁用不报错.""" mod = Module(name="m1", status=ModuleStatus.ACTIVE) mod.disable() mod.disable() assert mod.status == ModuleStatus.DISABLED def test_disable_from_registered(self): """从registered状态禁用.""" mod = Module(name="m1") mod.disable() assert mod.status == ModuleStatus.DISABLED def test_activate_after_disable(self): """禁用后重新激活.""" mod = Module(name="m1") mod.activate() mod.disable() mod.activate() assert mod.status == ModuleStatus.ACTIVE def test_error_state_cannot_be_activated(self): """error状态不能被激活.""" mod = Module(name="m1", status=ModuleStatus.ERROR) mod.activate() assert mod.status == ModuleStatus.ERROR def test_error_state_can_be_disabled(self): """error状态可以被禁用(disable 不检查状态).""" mod = Module(name="m1", status=ModuleStatus.ERROR) mod.disable() assert mod.status == ModuleStatus.DISABLED # ── ModuleRegistry 注册补充测试 ────────────────────────────────────────────── class TestModuleRegistryRegisterMore: """模块注册补充场景.""" def test_register_multiple_modules(self): """注册多个模块.""" registry = ModuleRegistry() for i in range(5): registry.register(Module(name=f"mod_{i}")) assert len(registry.list_modules()) == 5 def test_register_order_independent_deps(self): """先注册依赖方,后注册被依赖方,依赖方不会自动激活.""" registry = ModuleRegistry() registry.register(Module(name="plugin", dependencies=["core"])) assert registry.get("plugin").status == ModuleStatus.REGISTERED # 注册core后,plugin仍然是REGISTERED(不会自动检查) registry.register(Module(name="core")) assert registry.get("core").status == ModuleStatus.ACTIVE def test_register_with_multiple_deps_all_satisfied(self): """所有依赖都满足时自动激活.""" registry = ModuleRegistry() registry.register(Module(name="dep_a")) registry.register(Module(name="dep_b")) registry.register(Module(name="plugin", dependencies=["dep_a", "dep_b"])) assert registry.get("plugin").status == ModuleStatus.ACTIVE def test_register_with_multiple_deps_partial_missing(self): """部分依赖缺失时不激活.""" registry = ModuleRegistry() registry.register(Module(name="dep_a")) registry.register(Module(name="plugin", dependencies=["dep_a", "dep_b"])) assert registry.get("plugin").status == ModuleStatus.REGISTERED def test_register_self_dependency_handled(self): """自依赖不会导致死循环(依赖检查时找不到自己).""" registry = ModuleRegistry() registry.register(Module(name="self_dep", dependencies=["self_dep"])) # 注册时自己还没加入 _modules,检查依赖时找不到,保持REGISTERED assert registry.get("self_dep").status == ModuleStatus.REGISTERED # ── ModuleRegistry 能力查询补充 ───────────────────────────────────────────── class TestModuleRegistryCapabilitiesMore: """能力查询补充测试.""" def test_multiple_modules_same_capability_returns_first(self): """多个模块提供同一能力,get_capability返回第一个.""" registry = ModuleRegistry() cap1 = ModuleCapability(name="render", description="渲染器A") cap2 = ModuleCapability(name="render", description="渲染器B") registry.register(Module(name="mod_a", capabilities=[cap1])) registry.register(Module(name="mod_b", capabilities=[cap2])) result = registry.get_capability("render") assert result is not None assert result.name == "render" def test_has_capability_case_sensitive(self): """能力名大小写敏感.""" registry = ModuleRegistry() registry.register(Module(name="m1", capabilities=[ModuleCapability(name="Render")])) assert registry.has_capability("Render") is True assert registry.has_capability("render") is False def test_get_quota_rules_nonexistent_capability(self): """不存在的能力返回空配额列表.""" registry = ModuleRegistry() rules = registry.get_quota_rules("nonexistent") assert rules == [] def test_get_active_capabilities_empty_registry(self): """空注册中心返回空字典.""" registry = ModuleRegistry() result = registry.get_active_capabilities() assert result == {} def test_get_active_capabilities_skips_inactive(self): """非激活模块的能力不计入.""" registry = ModuleRegistry() registry.register(Module(name="active_mod", capabilities=[ModuleCapability(name="cap1")])) disabled = Module( name="disabled_mod", status=ModuleStatus.DISABLED, capabilities=[ModuleCapability(name="cap2")], ) registry._modules["disabled_mod"] = disabled result = registry.get_active_capabilities() assert "active_mod" in result assert "disabled_mod" not in result def test_get_active_capabilities_skips_no_cap_modules(self): """无能力的模块不出现在结果中.""" registry = ModuleRegistry() registry.register(Module(name="no_cap_mod")) registry.register(Module(name="has_cap_mod", capabilities=[ModuleCapability(name="cap1")])) result = registry.get_active_capabilities() assert "no_cap_mod" not in result assert "has_cap_mod" in result def test_module_with_multiple_capabilities(self): """单个模块有多个能力.""" registry = ModuleRegistry() caps = [ ModuleCapability(name="cap_a"), ModuleCapability(name="cap_b"), ModuleCapability(name="cap_c"), ] registry.register(Module(name="multi_mod", capabilities=caps)) assert registry.has_capability("cap_a") assert registry.has_capability("cap_b") assert registry.has_capability("cap_c") assert len(registry.get_active_capabilities()["multi_mod"]) == 3 # ── ModuleRegistry 依赖检查补充 ──────────────────────────────────────────── class TestModuleRegistryDependenciesMore: """依赖检查补充测试.""" def test_multiple_dependencies_all_active(self): """多个依赖都激活.""" registry = ModuleRegistry() registry.register(Module(name="dep1")) registry.register(Module(name="dep2")) registry.register(Module(name="dep3")) registry.register(Module(name="plugin", dependencies=["dep1", "dep2", "dep3"])) assert registry.check_dependencies("plugin") is True def test_multiple_dependencies_one_inactive(self): """多个依赖中有一个未激活.""" registry = ModuleRegistry() registry.register(Module(name="dep1")) dep2 = Module(name="dep2", status=ModuleStatus.DISABLED) registry._modules["dep2"] = dep2 registry.register(Module(name="plugin", dependencies=["dep1", "dep2"])) # 注册时dep2不是ACTIVE,plugin不会自动激活 assert registry.check_dependencies("plugin") is False def test_chain_dependencies(self): """链式依赖 A→B→C.""" registry = ModuleRegistry() registry.register(Module(name="c")) registry.register(Module(name="b", dependencies=["c"])) registry.register(Module(name="a", dependencies=["b"])) # a 依赖 b(ACTIVE),b 依赖 c(ACTIVE) # check_dependencies 只检查直接依赖,b 是 ACTIVE 的 assert registry.check_dependencies("a") is True assert registry.get("a").status == ModuleStatus.ACTIVE def test_no_dependencies_always_satisfied(self): """无依赖的模块总是满足依赖检查.""" registry = ModuleRegistry() registry.register(Module(name="standalone")) assert registry.check_dependencies("standalone") is True # ── ModuleRegistry list_modules 补充 ────────────────────────────────────── class TestModuleRegistryListMore: """list_modules 补充测试.""" def test_list_modules_empty(self): """空注册中心.""" registry = ModuleRegistry() assert registry.list_modules() == [] def test_list_modules_registered_status(self): """按registered状态过滤.""" registry = ModuleRegistry() registry.register(Module(name="active_mod")) # auto ACTIVE pending = Module(name="pending_mod") registry._modules["pending_mod"] = pending # REGISTERED registered = registry.list_modules(status=ModuleStatus.REGISTERED) assert len(registered) == 1 assert registered[0].name == "pending_mod" def test_list_modules_error_status(self): """按error状态过滤.""" registry = ModuleRegistry() error_mod = Module(name="err", status=ModuleStatus.ERROR) registry._modules["err"] = error_mod errors = registry.list_modules(status=ModuleStatus.ERROR) assert len(errors) == 1 assert errors[0].name == "err" # ── QuotaRule 补充测试 ────────────────────────────────────────────────────── class TestQuotaRuleMore: """QuotaRule 补充测试.""" def test_zero_per_operation(self): """零消耗配额规则.""" rule = QuotaRule(dimension="free_ops", per_operation=0.0) assert rule.per_operation == 0.0 def test_fractional_per_operation(self): """小数消耗配额规则.""" rule = QuotaRule(dimension="storage", per_operation=0.001) assert rule.per_operation == 0.001 def test_large_per_operation(self): """大数值消耗.""" rule = QuotaRule(dimension="tokens", per_operation=10000.0) assert rule.per_operation == 10000.0 # ── ModuleCapability 补充测试 ────────────────────────────────────────────── class TestModuleCapabilityMore: """ModuleCapability 补充测试.""" def test_empty_metadata(self): """默认metadata为空字典.""" cap = ModuleCapability(name="test") assert cap.metadata == {} def test_metadata_preserved(self): """元数据完整保存.""" meta = {"model": "v1", "speed": 1.5, "enabled": True} cap = ModuleCapability(name="test", metadata=meta) assert cap.metadata["model"] == "v1" assert cap.metadata["speed"] == 1.5 assert cap.metadata["enabled"] is True def test_multiple_quota_rules(self): """多个配额规则.""" rules = [ QuotaRule("dim1", 1.0), QuotaRule("dim2", 2.0), QuotaRule("dim3", 3.0), ] cap = ModuleCapability(name="test", quota_rules=rules) assert len(cap.quota_rules) == 3 assert cap.quota_rules[0].dimension == "dim1" assert cap.quota_rules[2].per_operation == 3.0