""" AIOps Feature Flags 測試 ======================== ADR-080: AI 自主化飛輪 Phase 0 退出條件 測試項目: - 所有 Phase 總開關預設 False - is_sub_flag_enabled() 強制父 Phase 開關 - is_phase_enabled() 邊界條件 - bool cast 正確性(非 bool 型態子開關不洩漏 int) 2026-04-15 Claude Sonnet 4.6 + ogt: Phase 0 初始建立 """ import pytest from src.core.feature_flags import AIOpsFeatureFlags class TestPhaseDefaultsAllFalse: """Phase 0 退出條件:所有 Phase 預設 False""" def test_p1_disabled_by_default(self): flags = AIOpsFeatureFlags() assert flags.AIOPS_P1_ENABLED is False def test_p2_disabled_by_default(self): flags = AIOpsFeatureFlags() assert flags.AIOPS_P2_ENABLED is False def test_p3_disabled_by_default(self): flags = AIOpsFeatureFlags() assert flags.AIOPS_P3_ENABLED is False def test_p4_disabled_by_default(self): flags = AIOpsFeatureFlags() assert flags.AIOPS_P4_ENABLED is False def test_p5_disabled_by_default(self): flags = AIOpsFeatureFlags() assert flags.AIOPS_P5_ENABLED is False def test_p6_disabled_by_default(self): flags = AIOpsFeatureFlags() assert flags.AIOPS_P6_ENABLED is False class TestSubFlagEnforcement: """is_sub_flag_enabled() 必須強制父 Phase 開關""" def test_sub_flag_blocked_when_parent_disabled(self): """父 Phase 關閉時,子開關必須回 False(即使子開關本身 True)""" flags = AIOpsFeatureFlags( AIOPS_P1_ENABLED=False, AIOPS_P1_PRE_DECISION_INVESTIGATOR=True, ) assert flags.is_sub_flag_enabled("AIOPS_P1_PRE_DECISION_INVESTIGATOR") is False def test_sub_flag_allowed_when_parent_enabled(self): """父 Phase 開啟且子開關為 True → 回 True""" flags = AIOpsFeatureFlags( AIOPS_P1_ENABLED=True, AIOPS_P1_PRE_DECISION_INVESTIGATOR=True, ) assert flags.is_sub_flag_enabled("AIOPS_P1_PRE_DECISION_INVESTIGATOR") is True def test_sub_flag_false_when_sub_disabled(self): """父 Phase 開啟但子開關為 False → 回 False""" flags = AIOpsFeatureFlags( AIOPS_P1_ENABLED=True, AIOPS_P1_PRE_DECISION_INVESTIGATOR=False, ) assert flags.is_sub_flag_enabled("AIOPS_P1_PRE_DECISION_INVESTIGATOR") is False def test_sub_flag_returns_bool_not_int(self): """AIOPS_P2_AGENT_TIMEOUT_SEC 是 int — 不得從 is_sub_flag_enabled 洩漏為 truthy int""" flags = AIOpsFeatureFlags(AIOPS_P2_ENABLED=True) result = flags.is_sub_flag_enabled("AIOPS_P2_AGENT_TIMEOUT_SEC") assert isinstance(result, bool), f"Expected bool, got {type(result)}" def test_sub_flag_invalid_name_returns_false(self): flags = AIOpsFeatureFlags() assert flags.is_sub_flag_enabled("INVALID_FLAG_NAME") is False def test_sub_flag_nonexistent_field_returns_false(self): flags = AIOpsFeatureFlags(AIOPS_P1_ENABLED=True) assert flags.is_sub_flag_enabled("AIOPS_P1_NONEXISTENT") is False class TestIsPhaseEnabled: """is_phase_enabled() 邊界條件""" def test_valid_phase_enabled(self): flags = AIOpsFeatureFlags(AIOPS_P3_ENABLED=True) assert flags.is_phase_enabled(3) is True def test_valid_phase_disabled(self): flags = AIOpsFeatureFlags(AIOPS_P3_ENABLED=False) assert flags.is_phase_enabled(3) is False def test_invalid_phase_returns_false(self): flags = AIOpsFeatureFlags() assert flags.is_phase_enabled(0) is False assert flags.is_phase_enabled(7) is False assert flags.is_phase_enabled(99) is False @pytest.mark.parametrize("phase", [1, 2, 3, 4, 5, 6]) def test_all_phases_default_false(self, phase): flags = AIOpsFeatureFlags() assert flags.is_phase_enabled(phase) is False