Files
xiaoxia-saas/tests/unit/test_module_registry.py
CI Bot 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
test: wave80 - module_registry深度 + subtitle_domain + voice_library补充 (+74)
- 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
2026-07-26 00:25:22 +08:00

679 lines
25 KiB
Python
Executable File
Raw Permalink 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.
"""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不是ACTIVEplugin不会自动激活
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 依赖 bACTIVE),b 依赖 cACTIVE
# 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