""" auto_approve.py DESTRUCTIVE_PATTERNS 攔截清單測試 ================================================== 測試策略 (feedback_no_mock_testing.md): - 直接呼叫 AutoApprovePolicy.evaluate(),不 Mock - 驗證破壞性操作被攔截 (should_auto_approve=False, CRITICAL_OPERATION) - 驗證可恢復操作允許通過(不被 pattern 誤攔) - 2026-04-11 Claude Sonnet 4.6: Code Review I4 修補 """ import pytest from src.services.auto_approve import AutoApprovePolicy, _DESTRUCTIVE_PATTERNS class TestDestructivePatternsConstant: """確認模組常量存在且包含關鍵項目""" def test_constant_exists_and_is_list(self): assert isinstance(_DESTRUCTIVE_PATTERNS, list) assert len(_DESTRUCTIVE_PATTERNS) > 0 def test_contains_scale_to_zero(self): assert "--replicas=0" in _DESTRUCTIVE_PATTERNS def test_contains_delete_deployment(self): assert "delete deployment" in _DESTRUCTIVE_PATTERNS def test_contains_kubectl_drain(self): assert "kubectl drain" in _DESTRUCTIVE_PATTERNS def test_contains_docker_rm(self): assert "docker rm" in _DESTRUCTIVE_PATTERNS def test_contains_json_patch_replicas_zero(self): # kubectl patch JSON 形式 assert '"replicas": 0' in _DESTRUCTIVE_PATTERNS def test_contains_rm_rf(self): assert "rm -rf" in _DESTRUCTIVE_PATTERNS class TestDestructivePatternsBlocked: """破壞性操作必須被攔截(無論 risk_level)""" @pytest.fixture def policy(self): return AutoApprovePolicy() def _proposal(self, action: str, risk_level: str = "medium", confidence: float = 0.9) -> dict: return {"action": action, "risk_level": risk_level, "confidence": confidence} def test_scale_to_zero_blocked(self, policy): d = policy.evaluate(self._proposal("kubectl scale deployment api --replicas=0")) assert not d.should_auto_approve assert "parser rejected" in d.reason_detail def test_delete_deployment_blocked(self, policy): d = policy.evaluate(self._proposal("kubectl delete deployment api-server")) assert not d.should_auto_approve assert "parser rejected" in d.reason_detail def test_delete_pod_allowed_by_parser(self, policy): d = policy.evaluate(self._proposal("kubectl delete pod api-server-abc123")) assert d.should_auto_approve assert "Destructive pattern" not in d.reason_detail def test_delete_pod_force_blocked(self, policy): d = policy.evaluate(self._proposal("kubectl delete pod api-server-abc123 --force")) assert not d.should_auto_approve assert "parser rejected" in d.reason_detail def test_delete_pods_plural_blocked(self, policy): d = policy.evaluate(self._proposal("kubectl delete pods --all -n awoooi-prod")) assert not d.should_auto_approve def test_kubectl_drain_blocked(self, policy): d = policy.evaluate(self._proposal("kubectl drain 192.168.0.110 --ignore-daemonsets")) assert not d.should_auto_approve def test_kubectl_rollout_undo_blocked(self, policy): d = policy.evaluate(self._proposal("kubectl rollout undo deployment/api-server")) assert not d.should_auto_approve def test_docker_rm_blocked(self, policy): d = policy.evaluate(self._proposal("docker rm -f sentry")) assert not d.should_auto_approve def test_docker_stop_blocked(self, policy): d = policy.evaluate(self._proposal("docker stop harbor")) assert not d.should_auto_approve def test_drop_table_blocked(self, policy): d = policy.evaluate(self._proposal("drop table incidents")) assert not d.should_auto_approve def test_rm_rf_blocked(self, policy): d = policy.evaluate(self._proposal("ssh 192.168.0.188 'rm -rf /data'")) assert not d.should_auto_approve def test_json_patch_replicas_zero_blocked(self, policy): d = policy.evaluate(self._proposal( 'kubectl patch deployment api -p \'{"spec":{"replicas": 0}}\'' )) assert not d.should_auto_approve def test_destructive_blocked_even_low_risk(self, policy): """破壞性操作即使 low risk 也必須攔截""" d = policy.evaluate(self._proposal("kubectl delete deployment api-server", risk_level="low")) assert not d.should_auto_approve def test_destructive_blocked_even_high_confidence(self, policy): """高信心度也不能繞過破壞性攔截""" d = policy.evaluate(self._proposal("kubectl drain node-1", confidence=0.99)) assert not d.should_auto_approve class TestSafeOperationsAllowed: """可恢復操作不應被誤攔""" @pytest.fixture def policy(self): return AutoApprovePolicy() def _proposal(self, action: str, risk_level: str = "medium", confidence: float = 0.9) -> dict: return {"action": action, "risk_level": risk_level, "confidence": confidence} def test_rollout_restart_allowed(self, policy): """kubectl rollout restart 是可恢復操作,不應被攔截""" d = policy.evaluate(self._proposal("kubectl rollout restart deployment/api-server")) # 不應因 destructive pattern 被攔截 assert "Destructive pattern" not in d.reason_detail def test_docker_restart_allowed(self, policy): """docker restart 是可恢復操作""" d = policy.evaluate(self._proposal("docker restart sentry")) assert "Destructive pattern" not in d.reason_detail def test_scale_up_allowed(self, policy): """scale to 2 replicas 不應被攔截""" d = policy.evaluate(self._proposal("kubectl scale deployment api --replicas=2")) assert "Destructive pattern" not in d.reason_detail def test_kubectl_get_allowed(self, policy): """kubectl get 是只讀操作""" d = policy.evaluate(self._proposal("kubectl get pods -n awoooi-prod")) assert "Destructive pattern" not in d.reason_detail def test_critical_severity_always_blocked(self, policy): """critical risk level 無論操作都需人工""" d = policy.evaluate(self._proposal("kubectl rollout restart deployment/api", risk_level="critical")) assert not d.should_auto_approve assert d.reason.value == "critical_operation"