18bd0de3fa
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
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 / Validate - Migration (alembic) (push) Successful in 1m42s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m44s
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 / Build Staging Web Image (push) Successful in 1m42s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m26s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 3m13s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 2m22s
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Successful in 4m29s
CI/CD Pipeline / Integration Tests (push) Successful in 2m16s
CI/CD Pipeline / Unit Tests (push) Failing after 5m46s
CI/CD Pipeline / Build Staging API Image (push) Successful in 11m28s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 3m18s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 30s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m13s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 5m58s
244 lines
8.4 KiB
Python
Executable File
244 lines
8.4 KiB
Python
Executable File
"""
|
|
Feature Flags 基础设施测试.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from packages.infrastructure.feature_flags import (
|
|
FeatureFlag,
|
|
FeatureFlags,
|
|
FeatureScope,
|
|
)
|
|
|
|
|
|
class TestFeatureFlagDefaults:
|
|
"""FeatureFlag 默认值."""
|
|
|
|
def test_default_enabled(self):
|
|
flag = FeatureFlag(name="test")
|
|
assert flag.global_enabled is True
|
|
assert flag.plan_overrides == {}
|
|
assert flag.user_overrides == {}
|
|
assert flag.description == ""
|
|
|
|
def test_custom_description(self):
|
|
flag = FeatureFlag(name="test", description="测试功能")
|
|
assert flag.description == "测试功能"
|
|
|
|
def test_global_disabled(self):
|
|
flag = FeatureFlag(name="test", global_enabled=False)
|
|
assert flag.global_enabled is False
|
|
|
|
|
|
class TestFeatureFlagIsEnabled:
|
|
"""is_enabled 优先级逻辑."""
|
|
|
|
def test_global_enabled_no_user_no_plan(self):
|
|
flag = FeatureFlag(name="test", global_enabled=True)
|
|
assert flag.is_enabled() is True
|
|
|
|
def test_global_disabled_no_user_no_plan(self):
|
|
flag = FeatureFlag(name="test", global_enabled=False)
|
|
assert flag.is_enabled() is False
|
|
|
|
def test_plan_override_free_disabled(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="premium") is True # 走全局
|
|
|
|
def test_plan_override_premium_enabled(self):
|
|
flag = FeatureFlag(
|
|
name="test",
|
|
global_enabled=False,
|
|
plan_overrides={"premium": True},
|
|
)
|
|
assert flag.is_enabled(user_plan="premium") is True
|
|
assert flag.is_enabled(user_plan="free") is False # 走全局
|
|
|
|
def test_user_override_highest_priority(self):
|
|
"""用户白名单优先级最高"""
|
|
flag = FeatureFlag(
|
|
name="test",
|
|
global_enabled=False,
|
|
plan_overrides={"premium": True},
|
|
user_overrides={"user_1": True},
|
|
)
|
|
# free 套餐全局禁用,但用户在白名单 → 启用
|
|
assert flag.is_enabled(user_plan="free", user_id="user_1") is True
|
|
|
|
def test_user_override_disable_overrides_plan(self):
|
|
flag = FeatureFlag(
|
|
name="test",
|
|
global_enabled=True,
|
|
plan_overrides={"premium": True},
|
|
user_overrides={"user_1": False},
|
|
)
|
|
# premium 套餐应该启用,但用户在禁用名单 → 禁用
|
|
assert flag.is_enabled(user_plan="premium", user_id="user_1") is False
|
|
|
|
def test_user_not_in_overrides_falls_to_plan(self):
|
|
flag = FeatureFlag(
|
|
name="test",
|
|
global_enabled=True,
|
|
plan_overrides={"free": False},
|
|
user_overrides={"user_x": True},
|
|
)
|
|
assert flag.is_enabled(user_plan="free", user_id="user_other") is False
|
|
|
|
def test_none_user_id_skipped(self):
|
|
flag = FeatureFlag(
|
|
name="test",
|
|
global_enabled=True,
|
|
plan_overrides={"free": False},
|
|
user_overrides={"None": False},
|
|
)
|
|
assert flag.is_enabled(user_plan="premium", user_id=None) is True
|
|
|
|
def test_none_plan_skipped(self):
|
|
flag = FeatureFlag(
|
|
name="test",
|
|
global_enabled=True,
|
|
plan_overrides={"free": False},
|
|
)
|
|
assert flag.is_enabled(user_plan=None) is True
|
|
|
|
def test_empty_user_id_skipped(self):
|
|
flag = FeatureFlag(
|
|
name="test",
|
|
global_enabled=True,
|
|
plan_overrides={"free": False},
|
|
)
|
|
assert flag.is_enabled(user_plan="premium", user_id="") is True
|
|
|
|
def test_empty_plan_skipped(self):
|
|
flag = FeatureFlag(
|
|
name="test",
|
|
global_enabled=True,
|
|
plan_overrides={"free": False},
|
|
)
|
|
assert flag.is_enabled(user_plan="") is True
|
|
|
|
|
|
class TestFeatureScope:
|
|
"""FeatureScope 常量."""
|
|
|
|
def test_constants_exist(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 TestFeatureFlagsManager:
|
|
"""FeatureFlags 管理器."""
|
|
|
|
@pytest.fixture
|
|
def ff(self):
|
|
return FeatureFlags()
|
|
|
|
def test_default_flags_registered(self, ff):
|
|
flags = ff.list_flags()
|
|
assert len(flags) >= 5
|
|
assert FeatureScope.AI_VOICE_GENERATION in flags
|
|
assert FeatureScope.DEDUPLICATION_REPORT in flags
|
|
assert FeatureScope.BATCH_EXPORT in flags
|
|
assert FeatureScope.MULTI_PLATFORM_OUTPUT in flags
|
|
assert FeatureScope.RECIPE_REUSE in flags
|
|
|
|
def test_get_existing_flag(self, ff):
|
|
flag = ff.get(FeatureScope.BATCH_EXPORT)
|
|
assert flag is not None
|
|
assert flag.name == FeatureScope.BATCH_EXPORT
|
|
|
|
def test_get_nonexistent_flag(self, ff):
|
|
assert ff.get("nonexistent") is None
|
|
|
|
def test_is_enabled_global(self, ff):
|
|
assert ff.is_enabled(FeatureScope.BATCH_EXPORT) is True
|
|
|
|
def test_is_enabled_nonexistent_returns_false(self, ff):
|
|
"""未知 flag 默认禁用(安全保守)"""
|
|
assert ff.is_enabled("unknown_feature") is False
|
|
|
|
def test_free_plan_ai_voice_disabled(self, ff):
|
|
"""AI 配音 free 套餐不可用"""
|
|
assert ff.is_enabled(FeatureScope.AI_VOICE_GENERATION, user_plan="free") is False
|
|
|
|
def test_premium_plan_ai_voice_enabled(self, ff):
|
|
assert ff.is_enabled(FeatureScope.AI_VOICE_GENERATION, user_plan="premium") is True
|
|
|
|
def test_deduplication_only_premium(self, ff):
|
|
"""去重报告仅 premium 可用"""
|
|
assert ff.is_enabled(FeatureScope.DEDUPLICATION_REPORT, user_plan="free") is False
|
|
assert ff.is_enabled(FeatureScope.DEDUPLICATION_REPORT, user_plan="basic") is False
|
|
assert ff.is_enabled(FeatureScope.DEDUPLICATION_REPORT, user_plan="premium") is True
|
|
|
|
def test_set_global(self, ff):
|
|
ff.set_global(FeatureScope.BATCH_EXPORT, False)
|
|
assert ff.is_enabled(FeatureScope.BATCH_EXPORT) is False
|
|
# 恢复
|
|
ff.set_global(FeatureScope.BATCH_EXPORT, True)
|
|
assert ff.is_enabled(FeatureScope.BATCH_EXPORT) is True
|
|
|
|
def test_set_global_nonexistent_raises(self, ff):
|
|
with pytest.raises(KeyError):
|
|
ff.set_global("nonexistent", True)
|
|
|
|
def test_set_plan_override(self, ff):
|
|
ff.set_plan_override(FeatureScope.BATCH_EXPORT, "enterprise", False)
|
|
assert ff.is_enabled(FeatureScope.BATCH_EXPORT, user_plan="enterprise") is False
|
|
|
|
def test_set_plan_override_nonexistent_raises(self, ff):
|
|
with pytest.raises(KeyError):
|
|
ff.set_plan_override("nonexistent", "free", True)
|
|
|
|
def test_set_user_override(self, ff):
|
|
ff.set_user_override(FeatureScope.BATCH_EXPORT, "user_42", True)
|
|
assert (
|
|
ff.is_enabled(
|
|
FeatureScope.BATCH_EXPORT,
|
|
user_plan="free",
|
|
user_id="user_42",
|
|
)
|
|
is True
|
|
)
|
|
|
|
def test_set_user_override_nonexistent_raises(self, ff):
|
|
with pytest.raises(KeyError):
|
|
ff.set_user_override("nonexistent", "user_1", True)
|
|
|
|
def test_register_new_flag(self, ff):
|
|
new_flag = FeatureFlag(name="new_feature", description="新功能")
|
|
ff.register(new_flag)
|
|
assert ff.get("new_feature") is not None
|
|
assert ff.is_enabled("new_feature") is True
|
|
|
|
def test_register_overwrites(self, ff):
|
|
flag1 = FeatureFlag(name="test", global_enabled=True)
|
|
flag2 = FeatureFlag(name="test", global_enabled=False)
|
|
ff.register(flag1)
|
|
ff.register(flag2)
|
|
assert ff.is_enabled("test") is False
|
|
|
|
def test_list_flags_returns_copy(self, ff):
|
|
flags = ff.list_flags()
|
|
assert isinstance(flags, dict)
|
|
# 修改返回值不影响内部
|
|
flags["new"] = FeatureFlag(name="new")
|
|
assert ff.get("new") is None
|
|
|
|
def test_get_enabled_for_plan(self, ff):
|
|
free_features = ff.get_enabled_for_plan("free")
|
|
premium_features = ff.get_enabled_for_plan("premium")
|
|
assert len(premium_features) >= len(free_features)
|
|
# free 套餐功能是 premium 的子集
|
|
for f in free_features:
|
|
assert f in premium_features
|