Files
xiaoxia-saas/tests/unit/test_quota_domain.py
T
CI Bot 2df7bc9dc8
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m41s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m45s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m54s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 3m1s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m34s
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 5m49s
CI/CD Pipeline / Frontend Lint (push) Successful in 6m0s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m27s
CI/CD Pipeline / Integration Tests (push) Successful in 2m20s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m11s
CI/CD Pipeline / Unit Tests (push) Failing after 5m52s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 30s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m48s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 5m10s
style: auto-format with black + isort + prettier
2026-07-24 13:35:05 +00:00

425 lines
15 KiB
Python
Executable File
Raw 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.
"""Quota 领域层单元测试 - quota.py"""
import math
import pytest
from packages.domain.quota import (
QUOTA_TIERS,
QuotaChecker,
QuotaCheckResult,
QuotaDimension,
QuotaRegistry,
QuotaTier,
QuotaWarningLevel,
get_warning_level,
quota_checker,
quota_registry,
)
class TestQuotaDimension:
"""QuotaDimension 枚举测试"""
def test_all_dimensions_have_values(self):
"""所有枚举成员都有字符串值"""
for dim in QuotaDimension:
assert isinstance(dim.value, str)
assert dim.value
def test_dimension_count(self):
"""配额维度数量 >= 内置维度"""
# 至少有 storage_gb, videos_per_month, max_concurrent, max_templates 等
assert len(QuotaDimension) >= 7
def test_str_enum_behavior(self):
"""是 str 枚举,可直接当字符串用"""
assert QuotaDimension.STORAGE_GB == "storage_gb"
assert isinstance(QuotaDimension.STORAGE_GB, str)
class TestQuotaTier:
"""QuotaTier 测试"""
def test_get_limit_defined(self):
"""已定义的维度返回正确值"""
tier = QuotaTier(name="test", limits={"storage": 10, "videos": 5})
assert tier.get_limit("storage") == 10
assert tier.get_limit("videos") == 5
def test_get_limit_undefined_returns_zero(self):
"""未定义的维度返回 0"""
tier = QuotaTier(name="test", limits={"storage": 10})
assert tier.get_limit("unknown") == 0
def test_is_unlimited_true(self):
"""不限量判断 - inf"""
tier = QuotaTier(name="test", limits={"templates": float("inf")})
assert tier.is_unlimited("templates") is True
def test_is_unlimited_false(self):
"""限量判断"""
tier = QuotaTier(name="test", limits={"storage": 10})
assert tier.is_unlimited("storage") is False
def test_is_unlimited_undefined_returns_true(self):
"""未定义的维度默认 infis_unlimited 返回 True"""
tier = QuotaTier(name="test", limits={})
# get_limit 用 dict.get 默认 0,但 is_unlimited 用 dict.get 默认 inf
assert tier.is_unlimited("unknown") is True
class TestQuotaTiers:
"""内置套餐配额测试"""
def test_three_tiers_exist(self):
"""三个套餐等级都存在"""
assert "free" in QUOTA_TIERS
assert "basic" in QUOTA_TIERS
assert "premium" in QUOTA_TIERS
def test_free_tier_storage(self):
"""free 套餐 2GB 存储"""
assert QUOTA_TIERS["free"].get_limit(QuotaDimension.STORAGE_GB) == 2
def test_basic_tier_storage(self):
"""basic 套餐 20GB 存储"""
assert QUOTA_TIERS["basic"].get_limit(QuotaDimension.STORAGE_GB) == 20
def test_premium_tier_storage(self):
"""premium 套餐 100GB 存储"""
assert QUOTA_TIERS["premium"].get_limit(QuotaDimension.STORAGE_GB) == 100
def test_free_no_ai_voice(self):
"""free 套餐没有 AI 配音"""
assert QUOTA_TIERS["free"].get_limit(QuotaDimension.AI_VOICE_ENABLED) == 0
def test_basic_has_ai_voice(self):
"""basic 套餐有 AI 配音"""
assert QUOTA_TIERS["basic"].get_limit(QuotaDimension.AI_VOICE_ENABLED) == 1
def test_premium_templates_unlimited(self):
"""premium 套餐模板不限量"""
assert QUOTA_TIERS["premium"].is_unlimited(QuotaDimension.MAX_TEMPLATES) is True
def test_free_videos_per_month(self):
"""free 每月 5 个视频"""
assert QUOTA_TIERS["free"].get_limit(QuotaDimension.VIDEOS_PER_MONTH) == 5
def test_premium_multi_platform_enabled(self):
"""premium 支持多平台发布"""
assert QUOTA_TIERS["premium"].get_limit(QuotaDimension.MULTI_PLATFORM_ENABLED) == 1
class TestQuotaWarningLevel:
"""告警级别常量测试"""
def test_level_values(self):
"""四个告警级别都有定义"""
assert QuotaWarningLevel.NORMAL == "normal"
assert QuotaWarningLevel.WARNING == "warning"
assert QuotaWarningLevel.CRITICAL == "critical"
assert QuotaWarningLevel.EXCEEDED == "exceeded"
class TestQuotaCheckResult:
"""QuotaCheckResult 测试"""
def test_usage_percent_normal(self):
"""正常使用百分比计算"""
result = QuotaCheckResult(
allowed=True,
dimension="storage",
limit=100,
used=30,
remaining=70,
warning_level=QuotaWarningLevel.NORMAL,
)
assert result.usage_percent == 30.0
def test_usage_percent_capped_at_100(self):
"""超过 100% 时截断为 100%"""
result = QuotaCheckResult(
allowed=False,
dimension="storage",
limit=100,
used=150,
remaining=0,
warning_level=QuotaWarningLevel.EXCEEDED,
)
assert result.usage_percent == 100.0
def test_usage_percent_zero_limit_with_usage(self):
"""limit=0 但有使用量,返回 100%"""
result = QuotaCheckResult(
allowed=False,
dimension="storage",
limit=0,
used=5,
remaining=0,
warning_level=QuotaWarningLevel.EXCEEDED,
)
assert result.usage_percent == 100.0
def test_usage_percent_zero_limit_no_usage(self):
"""limit=0 且无使用量,返回 0%"""
result = QuotaCheckResult(
allowed=True,
dimension="storage",
limit=0,
used=0,
remaining=0,
warning_level=QuotaWarningLevel.NORMAL,
)
assert result.usage_percent == 0.0
def test_usage_percent_unlimited(self):
"""不限量时使用百分比为 0"""
result = QuotaCheckResult(
allowed=True,
dimension="templates",
limit=float("inf"),
used=50,
remaining=float("inf"),
warning_level=QuotaWarningLevel.NORMAL,
)
assert result.usage_percent == 0.0
class TestQuotaRegistry:
"""QuotaRegistry 测试"""
def test_initial_dimensions(self):
"""初始化时内置维度已注册"""
registry = QuotaRegistry()
dims = registry.list_dimensions()
assert QuotaDimension.STORAGE_GB in dims
assert QuotaDimension.VIDEOS_PER_MONTH in dims
def test_initial_tiers(self):
"""初始化时三个套餐已注册"""
registry = QuotaRegistry()
tiers = registry.list_tiers()
assert "free" in tiers
assert "basic" in tiers
assert "premium" in tiers
def test_register_new_dimension(self):
"""注册新的配额维度"""
registry = QuotaRegistry()
registry.register_dimension("custom_dim", "自定义维度")
dims = registry.list_dimensions()
assert "custom_dim" in dims
assert dims["custom_dim"] == "自定义维度"
def test_register_dimension_idempotent(self):
"""重复注册是幂等的"""
registry = QuotaRegistry()
registry.register_dimension("custom", "描述1")
registry.register_dimension("custom", "描述2")
# 保留第一次注册的描述
assert registry.list_dimensions()["custom"] == "描述1"
def test_register_with_default_limits(self):
"""注册时指定各套餐的默认限制"""
registry = QuotaRegistry()
registry.register_dimension(
"custom",
"自定义",
default_limits={"free": 1, "basic": 10, "premium": 100},
)
assert registry.get_limit("free", "custom") == 1
assert registry.get_limit("basic", "custom") == 10
assert registry.get_limit("premium", "custom") == 100
def test_register_without_default_limits_defaults_to_zero(self):
"""不指定默认限制时各套餐该维度为 0"""
registry = QuotaRegistry()
registry.register_dimension("custom_no_limit", "自定义")
assert registry.get_limit("free", "custom_no_limit") == 0
assert registry.get_limit("basic", "custom_no_limit") == 0
def test_register_default_limits_ignores_unknown_plan(self):
"""默认限制中未知的套餐名被忽略"""
registry = QuotaRegistry()
registry.register_dimension(
"custom",
"自定义",
default_limits={"nonexistent": 999},
)
# 不报错,但也不会创建新套餐
assert registry.get_tier("nonexistent") is None
def test_get_tier_existing(self):
"""获取存在的套餐"""
registry = QuotaRegistry()
tier = registry.get_tier("free")
assert tier is not None
assert tier.name == "free"
def test_get_tier_nonexistent(self):
"""获取不存在的套餐返回 None"""
registry = QuotaRegistry()
assert registry.get_tier("enterprise") is None
def test_get_limit_existing(self):
"""获取存在的套餐和维度的限制"""
registry = QuotaRegistry()
assert registry.get_limit("free", QuotaDimension.STORAGE_GB) == 2
def test_get_limit_nonexistent_plan(self):
"""不存在的套餐返回 0"""
registry = QuotaRegistry()
assert registry.get_limit("unknown", QuotaDimension.STORAGE_GB) == 0
def test_list_dimensions_returns_copy(self):
"""list_dimensions 返回副本,修改不影响内部"""
registry = QuotaRegistry()
dims = registry.list_dimensions()
dims["fake"] = "fake"
assert "fake" not in registry.list_dimensions()
def test_list_tiers_returns_all_three(self):
"""列出所有套餐"""
registry = QuotaRegistry()
tiers = registry.list_tiers()
assert len(tiers) == 3
assert set(tiers) == {"free", "basic", "premium"}
class TestQuotaChecker:
"""QuotaChecker 测试"""
def test_check_under_limit_allowed(self):
"""使用量低于限制,允许"""
checker = QuotaChecker()
result = checker.check("free", QuotaDimension.STORAGE_GB, 1.0)
assert result.allowed is True
assert result.remaining == 1.0
assert result.warning_level == QuotaWarningLevel.NORMAL
def test_check_at_limit_not_allowed(self):
"""使用量等于限制,不允许(used < limit 判定)"""
checker = QuotaChecker()
result = checker.check("free", QuotaDimension.STORAGE_GB, 2.0)
assert result.allowed is False
assert result.remaining == 0
assert result.warning_level == QuotaWarningLevel.EXCEEDED
def test_check_over_limit(self):
"""使用量超过限制"""
checker = QuotaChecker()
result = checker.check("free", QuotaDimension.STORAGE_GB, 3.0)
assert result.allowed is False
assert result.remaining == 0
assert result.warning_level == QuotaWarningLevel.EXCEEDED
def test_check_warning_level_80_percent(self):
"""80% 触发 WARNING"""
checker = QuotaChecker()
# 100GB 的 80% = 80GB
result = checker.check("premium", QuotaDimension.STORAGE_GB, 80.0)
assert result.warning_level == QuotaWarningLevel.WARNING
def test_check_warning_level_95_percent(self):
"""95% 触发 CRITICAL"""
checker = QuotaChecker()
result = checker.check("premium", QuotaDimension.STORAGE_GB, 95.0)
assert result.warning_level == QuotaWarningLevel.CRITICAL
def test_check_warning_level_exceeded(self):
"""100% 及以上触发 EXCEEDED"""
checker = QuotaChecker()
result = checker.check("premium", QuotaDimension.STORAGE_GB, 100.0)
assert result.warning_level == QuotaWarningLevel.EXCEEDED
def test_check_unlimited_always_allowed(self):
"""不限量的维度始终允许"""
checker = QuotaChecker()
result = checker.check("premium", QuotaDimension.MAX_TEMPLATES, 9999)
assert result.allowed is True
assert math.isinf(result.remaining)
assert result.warning_level == QuotaWarningLevel.NORMAL
def test_check_unknown_plan_zero_limit(self):
"""未知套餐限制为 0used=0 时不允许(0 < 0 为 False"""
checker = QuotaChecker()
result = checker.check("unknown", QuotaDimension.STORAGE_GB, 0)
assert result.limit == 0
assert result.allowed is False
def test_check_multiple(self):
"""批量检查多个维度"""
checker = QuotaChecker()
results = checker.check_multiple(
"free",
{
QuotaDimension.STORAGE_GB: 1.0,
QuotaDimension.VIDEOS_PER_MONTH: 3,
},
)
assert len(results) == 2
assert all(r.allowed for r in results)
dims = {r.dimension for r in results}
assert QuotaDimension.STORAGE_GB in dims
assert QuotaDimension.VIDEOS_PER_MONTH in dims
def test_check_with_custom_registry(self):
"""使用自定义注册表"""
registry = QuotaRegistry()
registry.register_dimension("custom", "自定义", default_limits={"free": 5})
checker = QuotaChecker(registry)
result = checker.check("free", "custom", 3)
assert result.allowed is True
assert result.limit == 5
def test_compute_warning_level_zero_limit_no_usage(self):
"""limit=0, used=0 → NORMAL"""
level = QuotaChecker._compute_warning_level(0, 0)
assert level == QuotaWarningLevel.NORMAL
def test_compute_warning_level_zero_limit_with_usage(self):
"""limit=0, used>0 → EXCEEDED"""
level = QuotaChecker._compute_warning_level(1, 0)
assert level == QuotaWarningLevel.EXCEEDED
def test_compute_warning_level_negative_limit(self):
"""limit<0 视同 0 处理"""
level = QuotaChecker._compute_warning_level(1, -1)
assert level == QuotaWarningLevel.EXCEEDED
class TestGetWarningLevel:
"""get_warning_level 便捷函数测试"""
def test_normal(self):
assert get_warning_level(50, 100) == QuotaWarningLevel.NORMAL
def test_warning(self):
assert get_warning_level(85, 100) == QuotaWarningLevel.WARNING
def test_critical(self):
assert get_warning_level(97, 100) == QuotaWarningLevel.CRITICAL
def test_exceeded(self):
assert get_warning_level(100, 100) == QuotaWarningLevel.EXCEEDED
def test_unlimited(self):
assert get_warning_level(9999, float("inf")) == QuotaWarningLevel.NORMAL
class TestGlobalSingletons:
"""全局单例测试"""
def test_quota_registry_is_instance(self):
assert isinstance(quota_registry, QuotaRegistry)
def test_quota_checker_is_instance(self):
assert isinstance(quota_checker, QuotaChecker)
def test_global_checker_uses_global_registry(self):
"""全局 checker 使用全局 registry"""
# 验证能正常工作
result = quota_checker.check("free", QuotaDimension.STORAGE_GB, 1.0)
assert result.allowed is True