"""Quota 配额系统单元测试 — #1894 档位清理后 (free/monthly/quarterly/yearly).""" 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: 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" # MAX_TITLES 保留作为枚举别名(与 MAX_TEMPLATES 同值),但不再在套餐配额中独立配置 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={}) 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_core_tiers_exist(self): """核心四档位存在.""" assert "free" in QUOTA_TIERS assert "monthly" in QUOTA_TIERS assert "quarterly" in QUOTA_TIERS assert "yearly" in QUOTA_TIERS def test_legacy_tiers_are_aliases(self): """旧档位保留为别名以兼容历史数据.""" assert QUOTA_TIERS["basic"] is QUOTA_TIERS["monthly"] assert QUOTA_TIERS["standard"] is QUOTA_TIERS["monthly"] assert QUOTA_TIERS["premium"] is QUOTA_TIERS["quarterly"] assert QUOTA_TIERS["pro"] is QUOTA_TIERS["quarterly"] assert QUOTA_TIERS["enterprise"] is QUOTA_TIERS["quarterly"] 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_voiceovers") == 10 assert free.get_limit("ai_voice_enabled") == 0 assert free.get_limit("ai_voice_credits") == 0 def test_monthly_tier_limits(self): monthly = QUOTA_TIERS["monthly"] assert monthly.get_limit("storage_gb") == 20 assert monthly.get_limit("videos_per_month") == 30 assert monthly.get_limit("max_concurrent") == 10 assert monthly.get_limit("max_templates") == 15 assert monthly.get_limit("max_voiceovers") == 100 assert monthly.get_limit("ai_voice_enabled") == 1 assert monthly.get_limit("ai_voice_credits") == 100 assert monthly.get_limit("batch_export_enabled") == 1 def test_quarterly_tier_limits(self): q = QUOTA_TIERS["quarterly"] assert q.get_limit("storage_gb") == 100 assert q.get_limit("videos_per_month") == 100 assert q.get_limit("max_concurrent") == 20 assert q.is_unlimited("max_templates") is True assert q.get_limit("ai_voice_enabled") == 1 assert q.get_limit("ai_voice_credits") == 500 assert q.get_limit("batch_export_enabled") == 1 assert q.get_limit("multi_platform_enabled") == 1 assert q.get_limit("dedup_report_enabled") == 1 def test_yearly_tier_limits(self): y = QUOTA_TIERS["yearly"] assert y.get_limit("storage_gb") == 100 assert y.is_unlimited("videos_per_month") is True assert y.get_limit("max_concurrent") == 20 assert y.is_unlimited("max_templates") is True assert y.get_limit("max_voiceovers") == 200 assert y.get_limit("ai_voice_credits") == 2000 assert y.get_limit("batch_export_enabled") == 1 assert y.get_limit("multi_platform_enabled") == 1 assert y.get_limit("dedup_report_enabled") == 1 def test_tier_increase_monotonic(self): free = QUOTA_TIERS["free"] monthly = QUOTA_TIERS["monthly"] quarterly = QUOTA_TIERS["quarterly"] for dim in ["storage_gb", "videos_per_month", "max_concurrent", "max_voiceovers", "ai_voice_credits"]: assert monthly.get_limit(dim) >= free.get_limit(dim) assert quarterly.get_limit(dim) >= monthly.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 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 "monthly" in tiers assert "quarterly" in tiers assert "yearly" in tiers # 核心四档位必须存在 assert "free" in tiers and "monthly" in tiers and "quarterly" in tiers and "yearly" in tiers 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("quarterly", "storage_gb") == 100 assert reg.get_limit("yearly", "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, "monthly": 1, "quarterly": 5, "yearly": 10} ) 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("monthly", "new_feature") == 1 assert reg.get_limit("quarterly", "new_feature") == 5 assert reg.get_limit("yearly", "new_feature") == 10 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("monthly", "new_dim") == 0 assert reg.get_limit("yearly", "new_dim") == 0 def test_register_partial_limits(self): reg = QuotaRegistry() reg.register_dimension("partial", "partial", default_limits={"quarterly": 42}) assert reg.get_limit("free", "partial") == 0 assert reg.get_limit("quarterly", "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 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): checker = QuotaChecker() result = checker.check("free", "storage_gb", 2) assert result.allowed is False assert result.remaining == 0 def test_check_unlimited_quarterly_templates(self): checker = QuotaChecker() result = checker.check("quarterly", "max_templates", 999999) assert result.allowed is True assert math.isinf(result.remaining) assert result.warning_level == "normal" def test_check_unlimited_yearly_videos(self): checker = QuotaChecker() result = checker.check("yearly", "videos_per_month", 999999) assert result.allowed is True assert math.isinf(result.remaining) 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() 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() result = checker.check("free", "max_templates", 2.9) # ≈97% assert result.warning_level == "critical" def test_check_warning_level_exceeded(self): checker = QuotaChecker() result = checker.check("free", "storage_gb", 5) assert result.warning_level == "exceeded" def test_check_multiple(self): checker = QuotaChecker() results = checker.check_multiple( "free", {"storage_gb": 1, "max_templates": 2, "max_voiceovers": 5}, ) assert len(results) == 3 assert all(r.allowed for r in results) def test_check_zero_limit(self): checker = QuotaChecker() result = checker.check("free", "ai_voice_enabled", 0) assert result.allowed is False assert result.remaining == 0 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" 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