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
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
348 lines
14 KiB
Python
Executable File
348 lines
14 KiB
Python
Executable File
"""
|
||
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
|