Files
xiaoxia-saas/tests/unit/test_feature_flags_infra.py
T
xiaoxia df38101bd9
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (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 / Build Staging Web Image (push) Successful in 40s
CI/CD Pipeline / Frontend Lint (push) Successful in 2m26s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 3m35s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m55s
CI/CD Pipeline / Unit Tests (push) Successful in 4m6s
CI/CD Pipeline / Integration Tests (push) Successful in 1m45s
CI/CD Pipeline / Build Staging API Image (push) Successful in 12m21s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 15m52s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
test(unit): P3-1 核心模块单元测试覆盖率提升 - 新增12个模块400+测试 (#661)
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-07-21 00:21:37 +08:00

348 lines
14 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.
"""
Feature Flags 基础设施层单元测试
"""
import pytest
from packages.infrastructure.feature_flags import (
FeatureFlag,
FeatureFlags,
FeatureScope,
feature_flags,
)
class TestFeatureFlag:
"""FeatureFlag 单个开关测试"""
def test_default_values(self):
"""测试默认值"""
flag = FeatureFlag(name="test_flag")
assert flag.name == "test_flag"
assert flag.description == ""
assert flag.global_enabled is True
assert flag.plan_overrides == {}
assert flag.user_overrides == {}
def test_is_enabled_global_true(self):
"""测试全局启用"""
flag = FeatureFlag(name="test", global_enabled=True)
assert flag.is_enabled() is True
def test_is_enabled_global_false(self):
"""测试全局禁用"""
flag = FeatureFlag(name="test", global_enabled=False)
assert flag.is_enabled() is False
def test_is_enabled_plan_override(self):
"""测试套餐级别覆盖"""
flag = FeatureFlag(
name="test",
global_enabled=True,
plan_overrides={"free": False, "premium": True},
)
# free 套餐被覆盖为 False
assert flag.is_enabled(user_plan="free") is False
# premium 套餐覆盖为 True
assert flag.is_enabled(user_plan="premium") is True
# 没有覆盖的套餐用全局值
assert flag.is_enabled(user_plan="basic") is True
def test_is_enabled_user_override_priority(self):
"""测试用户白名单优先级最高"""
flag = FeatureFlag(
name="test",
global_enabled=False,
plan_overrides={"premium": True},
user_overrides={"user-1": True, "user-2": False},
)
# 用户白名单 True → 全局禁用也能启用
assert flag.is_enabled(user_plan="free", user_id="user-1") is True
# 用户白名单 False → premium 套餐也禁用
assert flag.is_enabled(user_plan="premium", user_id="user-2") is False
# 没有用户白名单 → 走套餐级别
assert flag.is_enabled(user_plan="premium", user_id="user-3") is True
def test_is_enabled_no_params(self):
"""测试不传任何参数时使用全局值"""
flag = FeatureFlag(name="test", global_enabled=True)
assert flag.is_enabled() is True
def test_is_enabled_empty_strings_treated_as_none(self):
"""测试空字符串 user_id/user_plan 不触发覆盖"""
flag = FeatureFlag(
name="test",
global_enabled=True,
plan_overrides={"free": False},
user_overrides={"": True}, # 空字符串key
)
# 空字符串 user_id 被当作 falsy,不走用户白名单分支
assert flag.is_enabled(user_id="", user_plan="") is True
def test_plan_override_does_not_affect_other_plans(self):
"""测试套餐覆盖不影响其他套餐"""
flag = FeatureFlag(
name="test",
global_enabled=True,
plan_overrides={"free": False},
)
assert flag.is_enabled(user_plan="free") is False
assert flag.is_enabled(user_plan="basic") is True
assert flag.is_enabled(user_plan="premium") is True
def test_user_override_can_enable_for_disabled_plan(self):
"""测试用户白名单可以为被禁用的套餐用户单独启用"""
flag = FeatureFlag(
name="test",
global_enabled=False,
plan_overrides={"premium": True},
user_overrides={"special-user": True},
)
# free 套餐用户 + 白名单 → 启用
assert flag.is_enabled(user_plan="free", user_id="special-user") is True
def test_user_override_can_disable_for_enabled_plan(self):
"""测试用户白名单可以为启用套餐的用户单独禁用"""
flag = FeatureFlag(
name="test",
global_enabled=True,
user_overrides={"bad-user": False},
)
assert flag.is_enabled(user_id="bad-user") is False
class TestFeatureScope:
"""FeatureScope 常量测试"""
def test_scope_constants(self):
"""测试所有常量存在"""
assert FeatureScope.AI_VOICE_GENERATION == "ai_voice_generation"
assert FeatureScope.DEDUPLICATION_REPORT == "deduplication_report"
assert FeatureScope.BATCH_EXPORT == "batch_export"
assert FeatureScope.MULTI_PLATFORM_OUTPUT == "multi_platform_output"
assert FeatureScope.RECIPE_REUSE == "recipe_reuse"
class TestFeatureFlags:
"""FeatureFlags 管理器测试"""
@pytest.fixture
def flags(self):
"""创建新的 FeatureFlags 实例(不影响全局单例)"""
return FeatureFlags()
# ===== 初始化 =====
def test_default_flags_exist(self, flags):
"""测试默认 flags 已注册"""
all_flags = flags.list_flags()
assert FeatureScope.AI_VOICE_GENERATION in all_flags
assert FeatureScope.DEDUPLICATION_REPORT in all_flags
assert FeatureScope.BATCH_EXPORT in all_flags
assert FeatureScope.MULTI_PLATFORM_OUTPUT in all_flags
assert FeatureScope.RECIPE_REUSE in all_flags
def test_default_ai_voice_generation(self, flags):
"""测试 AI 配音功能默认配置"""
# free 套餐不可用
assert flags.is_enabled("ai_voice_generation", user_plan="free") is False
# basic 套餐可用
assert flags.is_enabled("ai_voice_generation", user_plan="basic") is True
# premium 套餐可用
assert flags.is_enabled("ai_voice_generation", user_plan="premium") is True
def test_default_deduplication_report(self, flags):
"""测试去重报告默认配置(仅 premium"""
assert flags.is_enabled("deduplication_report", user_plan="free") is False
assert flags.is_enabled("deduplication_report", user_plan="basic") is False
assert flags.is_enabled("deduplication_report", user_plan="premium") is True
def test_default_multi_platform_output(self, flags):
"""测试多平台输出默认配置(仅 premium"""
assert flags.is_enabled("multi_platform_output", user_plan="free") is False
assert flags.is_enabled("multi_platform_output", user_plan="basic") is False
assert flags.is_enabled("multi_platform_output", user_plan="premium") is True
def test_default_batch_export(self, flags):
"""测试批量导出默认配置"""
assert flags.is_enabled("batch_export", user_plan="free") is False
assert flags.is_enabled("batch_export", user_plan="basic") is True
assert flags.is_enabled("batch_export", user_plan="premium") is True
def test_default_recipe_reuse(self, flags):
"""测试配方复用默认配置"""
assert flags.is_enabled("recipe_reuse", user_plan="free") is False
assert flags.is_enabled("recipe_reuse", user_plan="basic") is True
assert flags.is_enabled("recipe_reuse", user_plan="premium") is True
# ===== 注册新 flag =====
def test_register_new_flag(self, flags):
"""测试注册新的 feature flag"""
new_flag = FeatureFlag(name="new_feature", description="新功能", global_enabled=False)
flags.register(new_flag)
assert flags.get("new_feature") is not None
assert flags.get("new_feature").description == "新功能"
assert flags.is_enabled("new_feature") is False
def test_register_overwrites_existing(self, flags):
"""测试注册同名 flag 会覆盖"""
flag1 = FeatureFlag(name="test", global_enabled=True, description="v1")
flags.register(flag1)
assert flags.get("test").description == "v1"
flag2 = FeatureFlag(name="test", global_enabled=False, description="v2")
flags.register(flag2)
assert flags.get("test").description == "v2"
assert flags.is_enabled("test") is False
# ===== get 方法 =====
def test_get_existing_flag(self, flags):
"""测试获取存在的 flag"""
flag = flags.get("ai_voice_generation")
assert flag is not None
assert flag.name == "ai_voice_generation"
def test_get_nonexistent_flag(self, flags):
"""测试获取不存在的 flag 返回 None"""
assert flags.get("nonexistent") is None
# ===== is_enabled 方法 =====
def test_is_enabled_nonexistent_flag_returns_false(self, flags):
"""测试不存在的 flag 返回 False"""
assert flags.is_enabled("nonexistent_flag") is False
def test_is_enabled_without_plan_or_user(self, flags):
"""测试不传套餐和用户ID"""
assert flags.is_enabled("ai_voice_generation") is True
# ===== set_global =====
def test_set_global_enable(self, flags):
"""测试设置全局启用"""
flags.set_global("ai_voice_generation", enabled=False)
assert flags.is_enabled("ai_voice_generation", user_plan="premium") is False
def test_set_global_disable(self, flags):
"""测试设置全局禁用"""
flags.set_global("deduplication_report", enabled=False)
assert flags.is_enabled("deduplication_report", user_plan="premium") is False
def test_set_global_nonexistent_raises(self, flags):
"""测试设置不存在的 flag 抛出异常"""
with pytest.raises(KeyError, match="not found"):
flags.set_global("nonexistent", enabled=True)
# ===== set_plan_override =====
def test_set_plan_override(self, flags):
"""测试设置套餐覆盖"""
# 先确认 basic 套餐默认是去重报告禁用
assert flags.is_enabled("deduplication_report", user_plan="basic") is False
flags.set_plan_override("deduplication_report", "basic", True)
assert flags.is_enabled("deduplication_report", user_plan="basic") is True
def test_set_plan_override_nonexistent_raises(self, flags):
"""测试设置不存在 flag 的套餐覆盖抛出异常"""
with pytest.raises(KeyError, match="not found"):
flags.set_plan_override("nonexistent", "free", True)
# ===== set_user_override =====
def test_set_user_override_enable(self, flags):
"""测试设置用户白名单启用"""
assert flags.is_enabled("deduplication_report", user_plan="free", user_id="user-1") is False
flags.set_user_override("deduplication_report", "user-1", True)
assert flags.is_enabled("deduplication_report", user_plan="free", user_id="user-1") is True
def test_set_user_override_disable(self, flags):
"""测试设置用户白名单禁用"""
assert flags.is_enabled("batch_export", user_plan="premium", user_id="user-2") is True
flags.set_user_override("batch_export", "user-2", False)
assert flags.is_enabled("batch_export", user_plan="premium", user_id="user-2") is False
def test_set_user_override_nonexistent_raises(self, flags):
"""测试设置不存在 flag 的用户覆盖抛出异常"""
with pytest.raises(KeyError, match="not found"):
flags.set_user_override("nonexistent", "user-1", True)
# ===== list_flags =====
def test_list_flags_returns_copy(self, flags):
"""测试 list_flags 返回副本"""
all_flags = flags.list_flags()
all_flags["fake"] = FeatureFlag(name="fake")
# 原注册表不应被修改
assert "fake" not in flags.list_flags()
def test_list_flags_count(self, flags):
"""测试默认 flag 数量"""
all_flags = flags.list_flags()
assert len(all_flags) == 5 # 5 个默认 flag
# ===== get_enabled_for_plan =====
def test_get_enabled_for_free_plan(self, flags):
"""测试 free 套餐启用的功能"""
enabled = flags.get_enabled_for_plan("free")
# free 套餐应该只有 0 个默认启用的功能?不对,让我看看...
# 所有5个默认功能 free 套餐都是 False 吗?
# AI_VOICE_GENERATION: free=False
# DEDUPLICATION_REPORT: free=False, basic=False
# BATCH_EXPORT: free=False
# MULTI_PLATFORM_OUTPUT: free=False, basic=False
# RECIPE_REUSE: free=False
# 所以 free 套餐一个都没有?
assert len(enabled) == 0
def test_get_enabled_for_premium_plan(self, flags):
"""测试 premium 套餐启用的功能"""
enabled = flags.get_enabled_for_plan("premium")
# premium 套餐所有功能都应该启用
assert len(enabled) == 5
assert "ai_voice_generation" in enabled
assert "deduplication_report" in enabled
assert "batch_export" in enabled
assert "multi_platform_output" in enabled
assert "recipe_reuse" in enabled
def test_get_enabled_for_basic_plan(self, flags):
"""测试 basic 套餐启用的功能"""
enabled = flags.get_enabled_for_plan("basic")
# basic: ai_voice=True, dedup=False, batch=True, multi=False, recipe=True
assert "ai_voice_generation" in enabled
assert "deduplication_report" not in enabled
assert "batch_export" in enabled
assert "multi_platform_output" not in enabled
assert "recipe_reuse" in enabled
assert len(enabled) == 3
class TestGlobalSingleton:
"""全局单例测试"""
def test_global_singleton_exists(self):
"""测试全局单例存在"""
assert feature_flags is not None
assert isinstance(feature_flags, FeatureFlags)
def test_global_singleton_has_defaults(self):
"""测试全局单例有默认配置"""
assert feature_flags.get("ai_voice_generation") is not None
assert feature_flags.get("deduplication_report") is not None
def test_global_singleton_independent_from_new_instance(self):
"""测试全局单例与新实例相互独立"""
new_flags = FeatureFlags()
new_flags.set_global("ai_voice_generation", False)
# 全局单例不应受影响
assert feature_flags.is_enabled("ai_voice_generation") is True