"""Quota 配额系统单元测试。""" import pytest from packages.domain.quota import ( QUOTA_TIERS, QuotaChecker, QuotaCheckResult, QuotaDimension, QuotaRegistry, QuotaTier, QuotaWarningLevel, get_warning_level, quota_checker, quota_registry, ) class TestQuotaDimension: def test_core_dimensions_exist(self): assert QuotaDimension.STORAGE_GB.value == "storage_gb" assert QuotaDimension.VIDEOS_PER_MONTH.value == "videos_per_month" assert QuotaDimension.MAX_CONCURRENT.value == "max_concurrent" assert QuotaDimension.MAX_TEMPLATES.value == "max_templates" assert QuotaDimension.MAX_TITLES.value == "max_titles" assert QuotaDimension.MAX_VOICEOVERS.value == "max_voiceovers" assert QuotaDimension.AI_VOICE_ENABLED.value == "ai_voice_enabled" def test_extended_dimensions_exist(self): assert QuotaDimension.AI_VOICE_CREDITS.value == "ai_voice_credits" assert QuotaDimension.BATCH_EXPORT_ENABLED.value == "batch_export_enabled" assert QuotaDimension.MULTI_PLATFORM_ENABLED.value == "multi_platform_enabled" assert QuotaDimension.DEDUP_REPORT_ENABLED.value == "dedup_report_enabled" def test_all_dimensions_are_strings(self): for dim in QuotaDimension: assert isinstance(dim.value, str) class TestQuotaTier: def test_get_limit_defined(self): tier = QuotaTier(name="test", limits={"storage_gb": 10, "videos": 5}) assert tier.get_limit("storage_gb") == 10 assert tier.get_limit("videos") == 5 def test_get_limit_undefined_returns_zero(self): tier = QuotaTier(name="test", limits={"storage_gb": 10}) assert tier.get_limit("unknown_dim") == 0 def test_is_unlimited_false_for_finite(self): tier = QuotaTier(name="test", limits={"storage_gb": 10}) assert tier.is_unlimited("storage_gb") is False def test_is_unlimited_true_for_inf(self): tier = QuotaTier(name="test", limits={"templates": float("inf")}) assert tier.is_unlimited("templates") is True def test_is_unlimited_undefined(self): tier = QuotaTier(name="test", limits={}) # 未定义的维度,limits.get 返回默认 inf,所以 is_unlimited 返回 True assert tier.is_unlimited("unknown") is True def test_empty_limits(self): tier = QuotaTier(name="empty") assert tier.limits == {} assert tier.name == "empty" 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_limits(self): free = QUOTA_TIERS["free"] assert free.get_limit("storage_gb") == 2 assert free.get_limit("videos_per_month") == 5 assert free.get_limit("max_concurrent") == 3 assert free.get_limit("max_templates") == 3 assert free.get_limit("max_titles") == 50 assert free.get_limit("max_voiceovers") == 10 assert free.get_limit("ai_voice_enabled") == 0 def test_basic_tier_limits(self): basic = QUOTA_TIERS["basic"] assert basic.get_limit("storage_gb") == 20 assert basic.get_limit("videos_per_month") == 30 assert basic.get_limit("max_concurrent") == 10 assert basic.get_limit("max_templates") == 15 assert basic.get_limit("max_titles") == 500 assert basic.get_limit("max_voiceovers") == 100 assert basic.get_limit("ai_voice_enabled") == 1 assert basic.get_limit("ai_voice_credits") == 100 assert basic.get_limit("batch_export_enabled") == 1 def test_premium_tier_limits(self): premium = QUOTA_TIERS["premium"] assert premium.get_limit("storage_gb") == 100 assert premium.get_limit("videos_per_month") == 100 assert premium.get_limit("max_concurrent") == 20 assert premium.is_unlimited("max_templates") is True assert premium.get_limit("ai_voice_enabled") == 1 assert premium.get_limit("ai_voice_credits") == 500 assert premium.get_limit("batch_export_enabled") == 1 assert premium.get_limit("multi_platform_enabled") == 1 assert premium.get_limit("dedup_report_enabled") == 1 def test_tier_increase_monotonic(self): free = QUOTA_TIERS["free"] basic = QUOTA_TIERS["basic"] premium = QUOTA_TIERS["premium"] # 高级套餐应该 >= 低级套餐的所有限制 for dim in [ "storage_gb", "videos_per_month", "max_concurrent", "max_titles", "max_voiceovers", "ai_voice_credits", ]: assert basic.get_limit(dim) >= free.get_limit(dim) assert premium.get_limit(dim) >= basic.get_limit(dim) class TestQuotaWarningLevel: def test_levels_exist(self): assert QuotaWarningLevel.NORMAL == "normal" assert QuotaWarningLevel.WARNING == "warning" assert QuotaWarningLevel.CRITICAL == "critical" assert QuotaWarningLevel.EXCEEDED == "exceeded" class TestQuotaCheckResult: def test_usage_percent_normal(self): result = QuotaCheckResult( allowed=True, dimension="storage_gb", limit=100, used=50, remaining=50, warning_level="normal", ) assert result.usage_percent == 50.0 def test_usage_percent_exceeded(self): result = QuotaCheckResult( allowed=False, dimension="d", limit=100, used=150, remaining=0, warning_level="exceeded" ) assert result.usage_percent == 100.0 # min(100, 150%) def test_usage_percent_zero_used(self): result = QuotaCheckResult(allowed=True, dimension="d", limit=100, used=0, remaining=100, warning_level="normal") assert result.usage_percent == 0.0 def test_usage_percent_zero_limit_with_usage(self): result = QuotaCheckResult(allowed=False, dimension="d", limit=0, used=10, remaining=0, warning_level="exceeded") assert result.usage_percent == 100.0 def test_usage_percent_zero_limit_no_usage(self): result = QuotaCheckResult(allowed=True, dimension="d", limit=0, used=0, remaining=0, warning_level="normal") assert result.usage_percent == 0.0 def test_usage_percent_unlimited(self): result = QuotaCheckResult( allowed=True, dimension="d", limit=float("inf"), used=1000, remaining=float("inf"), warning_level="normal", ) assert result.usage_percent == 0.0 class TestQuotaRegistry: def test_initial_dimensions(self): reg = QuotaRegistry() dims = reg.list_dimensions() assert "storage_gb" in dims assert "videos_per_month" in dims assert len(dims) == len(QuotaDimension) def test_list_tiers(self): reg = QuotaRegistry() tiers = reg.list_tiers() assert "free" in tiers assert "basic" in tiers assert "premium" in tiers assert len(tiers) == 3 def test_get_tier_existing(self): reg = QuotaRegistry() tier = reg.get_tier("free") assert tier is not None assert tier.name == "free" def test_get_tier_unknown(self): reg = QuotaRegistry() assert reg.get_tier("unknown_plan") is None def test_get_limit_known(self): reg = QuotaRegistry() assert reg.get_limit("free", "storage_gb") == 2 assert reg.get_limit("premium", "storage_gb") == 100 def test_get_limit_unknown_plan(self): reg = QuotaRegistry() assert reg.get_limit("unknown", "storage_gb") == 0 def test_register_new_dimension(self): reg = QuotaRegistry() reg.register_dimension("new_feature", "新功能", default_limits={"free": 0, "basic": 1, "premium": 5}) dims = reg.list_dimensions() assert "new_feature" in dims assert dims["new_feature"] == "新功能" assert reg.get_limit("free", "new_feature") == 0 assert reg.get_limit("basic", "new_feature") == 1 assert reg.get_limit("premium", "new_feature") == 5 def test_register_dimension_idempotent(self): reg = QuotaRegistry() reg.register_dimension("storage_gb", "should not change", default_limits={"free": 999}) # 已经存在的不覆盖 assert reg.get_limit("free", "storage_gb") == 2 def test_register_without_defaults(self): reg = QuotaRegistry() reg.register_dimension("new_dim", "描述") assert reg.get_limit("free", "new_dim") == 0 assert reg.get_limit("basic", "new_dim") == 0 assert reg.get_limit("premium", "new_dim") == 0 def test_register_partial_limits(self): reg = QuotaRegistry() reg.register_dimension("partial", "partial", default_limits={"premium": 42}) assert reg.get_limit("free", "partial") == 0 # 未设置的保持 0 assert reg.get_limit("premium", "partial") == 42 class TestQuotaChecker: def test_check_within_limit(self): checker = QuotaChecker() result = checker.check("free", "storage_gb", 1) assert result.allowed is True assert result.limit == 2 assert result.used == 1 assert result.remaining == 1 assert result.dimension == "storage_gb" def test_check_exceeded(self): checker = QuotaChecker() result = checker.check("free", "storage_gb", 3) assert result.allowed is False assert result.remaining == 0 assert result.warning_level == "exceeded" def test_check_exact_limit_not_allowed(self): # used < limit 才 allowed,等于不算 checker = QuotaChecker() result = checker.check("free", "storage_gb", 2) assert result.allowed is False assert result.remaining == 0 def test_check_unlimited(self): checker = QuotaChecker() result = checker.check("premium", "max_templates", 999999) assert result.allowed is True assert result.remaining == float("inf") assert result.warning_level == "normal" def test_check_warning_level_normal(self): checker = QuotaChecker() result = checker.check("free", "storage_gb", 1) # 50% assert result.warning_level == "normal" def test_check_warning_level_warning(self): checker = QuotaChecker() # 80% <= used < 95% result = checker.check("free", "max_templates", 2.5) # 2.5/3 = 83% assert result.warning_level == "warning" def test_check_warning_level_critical(self): checker = QuotaChecker() # 95% <= used < 100% result = checker.check("free", "max_templates", 2.9) # 2.9/3 = 97% assert result.warning_level == "critical" def test_check_warning_level_exceeded(self): checker = QuotaChecker() result = checker.check("free", "storage_gb", 5) # 250% assert result.warning_level == "exceeded" def test_check_multiple(self): checker = QuotaChecker() results = checker.check_multiple( "free", {"storage_gb": 1, "max_templates": 2, "max_titles": 10}, ) assert len(results) == 3 assert results[0].dimension == "storage_gb" assert results[1].dimension == "max_templates" assert results[2].dimension == "max_titles" assert all(r.allowed for r in results) def test_check_zero_limit(self): checker = QuotaChecker() result = checker.check("free", "ai_voice_enabled", 0) # limit=0, used=0: used < limit 为 False → allowed=False assert result.allowed is False assert result.remaining == 0 assert result.warning_level == "normal" def test_compute_warning_level_normal(self): assert QuotaChecker._compute_warning_level(50, 100) == "normal" assert QuotaChecker._compute_warning_level(79, 100) == "normal" def test_compute_warning_level_warning_boundary(self): assert QuotaChecker._compute_warning_level(80, 100) == "warning" assert QuotaChecker._compute_warning_level(94, 100) == "warning" def test_compute_warning_level_critical_boundary(self): assert QuotaChecker._compute_warning_level(95, 100) == "critical" assert QuotaChecker._compute_warning_level(99, 100) == "critical" def test_compute_warning_level_exceeded(self): assert QuotaChecker._compute_warning_level(100, 100) == "exceeded" assert QuotaChecker._compute_warning_level(150, 100) == "exceeded" def test_compute_warning_level_unlimited(self): assert QuotaChecker._compute_warning_level(9999, float("inf")) == "normal" def test_compute_warning_level_zero_limit_with_usage(self): assert QuotaChecker._compute_warning_level(1, 0) == "exceeded" def test_compute_warning_level_zero_limit_no_usage(self): assert QuotaChecker._compute_warning_level(0, 0) == "normal" def test_compute_warning_level_negative_limit(self): # limit <= 0 且 used=0 → NORMAL assert QuotaChecker._compute_warning_level(0, -1) == "normal" class TestGetWarningLevel: def test_convenience_function(self): assert get_warning_level(50, 100) == "normal" assert get_warning_level(99, 100) == "critical" assert get_warning_level(100, 100) == "exceeded" assert get_warning_level(0, 0) == "normal" assert get_warning_level(1, 0) == "exceeded" 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_works(self): result = quota_checker.check("free", "storage_gb", 1) assert result.allowed is True