2e67be39f7
AI Code Review / AI Code Review (pull_request) Failing after 0s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 6s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m24s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 51s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 1m26s
CI/CD Pipeline / Validate - Code Quality (pull_request) Failing after 1m46s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 43s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 43s
Preview Deploy / Deploy Preview Environment (pull_request) Failing after 32s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 46s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m57s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 4m48s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m47s
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Successful in 2m43s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Failing after 23s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 25s
- module_registry: 新增42个测试 - Module状态转换(6个) - 注册补充场景(5个) - 能力查询补充(9个) - 依赖检查补充(4个) - list_modules补充(3个) - QuotaRule补充(3个) - ModuleCapability补充(3个) - subtitle_domain: 新增19个测试 - merge_short_segments补充(6个) - split_long_segments补充(5个) - _split_text_by_punctuation补充(8个) - voice_library_domain: 新增13个测试 - 各种status状态 - 零值/大值边界 - 标签、元数据、project_id
679 lines
25 KiB
Python
Executable File
679 lines
25 KiB
Python
Executable File
"""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
|