Files
xiaoxia-saas/tests/unit/test_quota.py
T
xiaoxia 67a1ed6430
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 2s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
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 / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 37s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 38s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Successful in 20s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 43s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 3m3s
CI/CD Pipeline / Integration Tests (push) Successful in 3m7s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 1m40s
CI/CD Pipeline / Validate - Style (push) Successful in 3m52s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m17s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 3m6s
CI/CD Pipeline / Validate - Security (push) Successful in 7m14s
CI/CD Pipeline / Unit Tests (push) Successful in 9m33s
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 / CI Gate (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 35m54s
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
feat(#1894): 清理文案库多余字段 / 废弃标题库 API / 旧定价档位清理 (#1968)
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-16 18:58:04 +08:00

417 lines
15 KiB
Python

"""Quota 配额系统单测 — #1894 档位清理后版本 (free/monthly/quarterly/yearly)."""
from __future__ import annotations
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_storage_gb(self):
assert QuotaDimension.STORAGE_GB == "storage_gb"
def test_videos_per_month(self):
assert QuotaDimension.VIDEOS_PER_MONTH == "videos_per_month"
def test_max_concurrent(self):
assert QuotaDimension.MAX_CONCURRENT == "max_concurrent"
def test_max_templates(self):
assert QuotaDimension.MAX_TEMPLATES == "max_templates"
def test_ai_voice_enabled(self):
assert QuotaDimension.AI_VOICE_ENABLED == "ai_voice_enabled"
def test_all_dimensions_count(self):
dims = list(QuotaDimension)
assert len(dims) >= 7
def test_from_string(self):
assert QuotaDimension("storage_gb") == QuotaDimension.STORAGE_GB
class TestQuotaWarningLevel:
def test_normal(self):
assert QuotaWarningLevel.NORMAL == "normal"
def test_warning(self):
assert QuotaWarningLevel.WARNING == "warning"
def test_critical(self):
assert QuotaWarningLevel.CRITICAL == "critical"
def test_exceeded(self):
assert QuotaWarningLevel.EXCEEDED == "exceeded"
# ── QuotaTier ──────────────────────────────────────────────────────────────
class TestQuotaTier:
def test_get_limit_defined(self):
tier = QuotaTier(name="test", limits={"storage_gb": 100})
assert tier.get_limit("storage_gb") == 100
def test_get_limit_undefined_returns_zero(self):
tier = QuotaTier(name="test", limits={})
assert tier.get_limit("unknown_dim") == 0
def test_is_unlimited_true(self):
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_gb": 100})
assert tier.is_unlimited("storage_gb") is False
def test_is_unlimited_undefined_defaults_true(self):
tier = QuotaTier(name="test", limits={})
assert tier.is_unlimited("unknown") is True
# ── QuotaTiers — #1894 新档位: free / monthly / quarterly / yearly ─────────
class TestQuotaTiers:
def test_free_tier_exists(self):
assert "free" in QUOTA_TIERS
free = QUOTA_TIERS["free"]
assert free.name == "free"
assert free.get_limit("storage_gb") == 2
assert free.get_limit("videos_per_month") == 5
def test_monthly_tier_exists(self):
assert "monthly" in QUOTA_TIERS
monthly = QUOTA_TIERS["monthly"]
assert monthly.get_limit("storage_gb") == 20
assert monthly.get_limit("videos_per_month") == 30
assert monthly.get_limit("ai_voice_enabled") == 1
def test_quarterly_tier_exists(self):
assert "quarterly" in QUOTA_TIERS
quarterly = QUOTA_TIERS["quarterly"]
assert quarterly.get_limit("storage_gb") == 100
assert quarterly.get_limit("videos_per_month") == 100
def test_yearly_tier_exists(self):
assert "yearly" in QUOTA_TIERS
yearly = QUOTA_TIERS["yearly"]
assert yearly.get_limit("storage_gb") == 100
assert yearly.is_unlimited("videos_per_month") is True
assert yearly.get_limit("ai_voice_credits") == 2000
def test_quarterly_templates_unlimited(self):
quarterly = QUOTA_TIERS["quarterly"]
assert quarterly.is_unlimited("max_templates") is True
def test_yearly_templates_unlimited(self):
yearly = QUOTA_TIERS["yearly"]
assert yearly.is_unlimited("max_templates") is True
def test_free_ai_voice_disabled(self):
free = QUOTA_TIERS["free"]
assert free.get_limit("ai_voice_enabled") == 0
def test_monthly_ai_voice_enabled(self):
monthly = QUOTA_TIERS["monthly"]
assert monthly.get_limit("ai_voice_enabled") == 1
def test_storage_increases_with_tier(self):
free = QUOTA_TIERS["free"].get_limit("storage_gb")
monthly = QUOTA_TIERS["monthly"].get_limit("storage_gb")
quarterly = QUOTA_TIERS["quarterly"].get_limit("storage_gb")
assert free < monthly <= quarterly
def test_legacy_tiers_are_aliases(self):
"""#1894: old standard/pro/enterprise/basic/premium 保留为别名以兼容历史数据。
basic/standard → monthly; premium/pro/enterprise → quarterly."""
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"]
# ── QuotaCheckResult ───────────────────────────────────────────────────────
class TestQuotaCheckResult:
def test_usage_percent_normal(self):
result = QuotaCheckResult(
allowed=True,
dimension="storage_gb",
limit=100,
used=50,
remaining=50,
warning_level=QuotaWarningLevel.NORMAL,
)
assert result.usage_percent == 50.0
def test_usage_percent_zero_usage(self):
result = QuotaCheckResult(
allowed=True,
dimension="storage_gb",
limit=100,
used=0,
remaining=100,
warning_level=QuotaWarningLevel.NORMAL,
)
assert result.usage_percent == 0.0
def test_usage_percent_exceeded_capped(self):
result = QuotaCheckResult(
allowed=False,
dimension="storage_gb",
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):
result = QuotaCheckResult(
allowed=False,
dimension="storage_gb",
limit=0,
used=10,
remaining=0,
warning_level=QuotaWarningLevel.EXCEEDED,
)
assert result.usage_percent == 100.0
def test_usage_percent_zero_limit_no_usage(self):
result = QuotaCheckResult(
allowed=True,
dimension="storage_gb",
limit=0,
used=0,
remaining=0,
warning_level=QuotaWarningLevel.NORMAL,
)
assert result.usage_percent == 0.0
def test_usage_percent_unlimited(self):
result = QuotaCheckResult(
allowed=True,
dimension="max_templates",
limit=float("inf"),
used=100,
remaining=float("inf"),
warning_level=QuotaWarningLevel.NORMAL,
)
assert result.usage_percent == 0.0
# ── QuotaRegistry ──────────────────────────────────────────────────────────
class TestQuotaRegistry:
def test_init_has_builtin_dimensions(self):
reg = QuotaRegistry()
dims = reg.list_dimensions()
assert "storage_gb" in dims
assert "videos_per_month" in dims
assert "max_concurrent" in dims
def test_init_has_core_four_tiers(self):
reg = QuotaRegistry()
tiers = reg.list_tiers()
assert "free" in tiers
assert "monthly" in tiers
assert "quarterly" in tiers
assert "yearly" in tiers
def test_legacy_tiers_are_aliases(self):
"""旧档位作为别名注册以兼容历史数据."""
reg = QuotaRegistry()
tiers = reg.list_tiers()
for legacy in ("standard", "pro", "enterprise", "basic", "premium"):
assert legacy in tiers
assert reg.get_tier("basic") is reg.get_tier("monthly")
assert reg.get_tier("pro") is reg.get_tier("quarterly")
def test_get_limit_free_storage(self):
reg = QuotaRegistry()
assert reg.get_limit("free", "storage_gb") == 2
def test_get_limit_unknown_plan_returns_zero(self):
reg = QuotaRegistry()
assert reg.get_limit("unknown_plan", "storage_gb") == 0
def test_get_tier_free(self):
reg = QuotaRegistry()
tier = reg.get_tier("free")
assert tier is not None
assert tier.name == "free"
def test_get_tier_unknown_returns_none(self):
reg = QuotaRegistry()
assert reg.get_tier("nonexistent") is None
def test_register_new_dimension(self):
reg = QuotaRegistry()
reg.register_dimension(
"custom_dim", "自定义维度", default_limits={"free": 5, "monthly": 20, "quarterly": 50, "yearly": 100}
)
assert "custom_dim" in reg.list_dimensions()
assert reg.get_limit("free", "custom_dim") == 5
assert reg.get_limit("monthly", "custom_dim") == 20
assert reg.get_limit("yearly", "custom_dim") == 100
def test_register_dimension_idempotent(self):
reg = QuotaRegistry()
reg.register_dimension("custom_dim", "v1", default_limits={"free": 5})
reg.register_dimension("custom_dim", "v2", default_limits={"free": 99})
assert reg.list_dimensions()["custom_dim"] == "v1"
assert reg.get_limit("free", "custom_dim") == 5
def test_register_dimension_no_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_list_dimensions_returns_copy(self):
reg = QuotaRegistry()
dims = reg.list_dimensions()
dims["fake"] = "test"
assert "fake" not in reg.list_dimensions()
# ── QuotaChecker ───────────────────────────────────────────────────────────
class TestQuotaChecker:
def test_check_within_limit(self):
checker = QuotaChecker()
result = checker.check("free", "storage_gb", 1.0)
assert result.allowed is True
assert result.limit == 2
assert result.used == 1.0
assert result.remaining == 1.0
def test_check_exceeds_limit(self):
checker = QuotaChecker()
result = checker.check("free", "storage_gb", 3.0)
assert result.allowed is False
assert result.remaining == 0
def test_check_exactly_at_limit(self):
checker = QuotaChecker()
result = checker.check("free", "storage_gb", 2.0)
assert result.allowed is False
def test_check_unlimited_quarterly_templates(self):
checker = QuotaChecker()
result = checker.check("quarterly", "max_templates", 1000.0)
assert result.allowed is True
assert math.isinf(result.remaining)
def test_check_unlimited_yearly_videos(self):
checker = QuotaChecker()
result = checker.check("yearly", "videos_per_month", 9999.0)
assert result.allowed is True
assert math.isinf(result.remaining)
def test_check_unknown_plan(self):
checker = QuotaChecker()
result = checker.check("unknown", "storage_gb", 1.0)
assert result.limit == 0
assert not result.allowed
def test_check_multiple(self):
checker = QuotaChecker()
results = checker.check_multiple("free", {"storage_gb": 1.0, "videos_per_month": 2})
assert len(results) == 2
assert all(r.allowed for r in results)
def test_warning_level_normal(self):
checker = QuotaChecker()
result = checker.check("free", "storage_gb", 1.0) # 50%
assert result.warning_level == QuotaWarningLevel.NORMAL
def test_warning_level_warning(self):
checker = QuotaChecker()
result = checker.check("free", "storage_gb", 1.7) # 85%
assert result.warning_level == QuotaWarningLevel.WARNING
def test_warning_level_critical(self):
checker = QuotaChecker()
result = checker.check("free", "storage_gb", 1.95) # 97.5%
assert result.warning_level == QuotaWarningLevel.CRITICAL
def test_warning_level_exceeded(self):
checker = QuotaChecker()
result = checker.check("free", "storage_gb", 2.5)
assert result.warning_level == QuotaWarningLevel.EXCEEDED
def test_checker_uses_provided_registry(self):
reg = QuotaRegistry()
reg.register_dimension("custom", "自定义", default_limits={"free": 42})
checker = QuotaChecker(reg)
result = checker.check("free", "custom", 10)
assert result.limit == 42
assert result.allowed is True
# ── get_warning_level ─────────────────────────────────────────────────────
class TestGetWarningLevel:
def test_normal_low_usage(self):
assert get_warning_level(50, 100) == QuotaWarningLevel.NORMAL
def test_warning_threshold(self):
assert get_warning_level(80, 100) == QuotaWarningLevel.WARNING
def test_critical_threshold(self):
assert get_warning_level(95, 100) == QuotaWarningLevel.CRITICAL
def test_exceeded_at_100(self):
assert get_warning_level(100, 100) == QuotaWarningLevel.EXCEEDED
def test_unlimited_always_normal(self):
assert get_warning_level(9999, float("inf")) == QuotaWarningLevel.NORMAL
def test_zero_limit_with_usage_exceeded(self):
assert get_warning_level(1, 0) == QuotaWarningLevel.EXCEEDED
def test_zero_limit_no_usage_normal(self):
assert get_warning_level(0, 0) == QuotaWarningLevel.NORMAL
# ── 全局单例 ─────────────────────────────────────────────────────────────
class TestGlobalSingletons:
def test_quota_registry_exists(self):
assert quota_registry is not None
assert isinstance(quota_registry, QuotaRegistry)
def test_quota_checker_exists(self):
assert quota_checker is not None
assert isinstance(quota_checker, QuotaChecker)
def test_global_checker_works(self):
result = quota_checker.check("free", "storage_gb", 1.0)
assert result.allowed is True
assert result.limit == 2