kubectl-mcp-server 1.16.0__py3-none-any.whl → 1.17.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
tests/test_safety.py ADDED
@@ -0,0 +1,218 @@
1
+ """Tests for safety mode implementation."""
2
+
3
+ import pytest
4
+ from kubectl_mcp_tool.safety import (
5
+ SafetyMode,
6
+ get_safety_mode,
7
+ set_safety_mode,
8
+ is_operation_allowed,
9
+ check_safety_mode,
10
+ get_mode_info,
11
+ WRITE_OPERATIONS,
12
+ DESTRUCTIVE_OPERATIONS,
13
+ )
14
+
15
+
16
+ class TestSafetyMode:
17
+ """Test safety mode enum and state management."""
18
+
19
+ def setup_method(self):
20
+ """Reset safety mode to NORMAL before each test."""
21
+ set_safety_mode(SafetyMode.NORMAL)
22
+
23
+ def test_default_mode_is_normal(self):
24
+ """Test that default safety mode is NORMAL."""
25
+ assert get_safety_mode() == SafetyMode.NORMAL
26
+
27
+ def test_set_read_only_mode(self):
28
+ """Test setting read-only mode."""
29
+ set_safety_mode(SafetyMode.READ_ONLY)
30
+ assert get_safety_mode() == SafetyMode.READ_ONLY
31
+
32
+ def test_set_disable_destructive_mode(self):
33
+ """Test setting disable-destructive mode."""
34
+ set_safety_mode(SafetyMode.DISABLE_DESTRUCTIVE)
35
+ assert get_safety_mode() == SafetyMode.DISABLE_DESTRUCTIVE
36
+
37
+
38
+ class TestOperationAllowed:
39
+ """Test is_operation_allowed function."""
40
+
41
+ def setup_method(self):
42
+ """Reset safety mode to NORMAL before each test."""
43
+ set_safety_mode(SafetyMode.NORMAL)
44
+
45
+ def test_all_operations_allowed_in_normal_mode(self):
46
+ """Test that all operations are allowed in NORMAL mode."""
47
+ for op in WRITE_OPERATIONS | DESTRUCTIVE_OPERATIONS:
48
+ allowed, reason = is_operation_allowed(op)
49
+ assert allowed is True
50
+ assert reason == ""
51
+
52
+ def test_read_operations_allowed_in_all_modes(self):
53
+ """Test that read operations are allowed in all modes."""
54
+ read_ops = ["get_pods", "list_namespaces", "describe_deployment", "get_logs"]
55
+
56
+ for mode in SafetyMode:
57
+ set_safety_mode(mode)
58
+ for op in read_ops:
59
+ allowed, reason = is_operation_allowed(op)
60
+ assert allowed is True
61
+
62
+ def test_write_operations_blocked_in_read_only_mode(self):
63
+ """Test that write operations are blocked in READ_ONLY mode."""
64
+ set_safety_mode(SafetyMode.READ_ONLY)
65
+
66
+ for op in WRITE_OPERATIONS:
67
+ allowed, reason = is_operation_allowed(op)
68
+ assert allowed is False
69
+ assert "read-only mode" in reason
70
+
71
+ def test_destructive_operations_blocked_in_read_only_mode(self):
72
+ """Test that destructive operations are blocked in READ_ONLY mode."""
73
+ set_safety_mode(SafetyMode.READ_ONLY)
74
+
75
+ for op in DESTRUCTIVE_OPERATIONS:
76
+ allowed, reason = is_operation_allowed(op)
77
+ assert allowed is False
78
+ assert "read-only mode" in reason
79
+
80
+ def test_write_operations_allowed_in_disable_destructive_mode(self):
81
+ """Test that non-destructive write operations are allowed in DISABLE_DESTRUCTIVE mode."""
82
+ set_safety_mode(SafetyMode.DISABLE_DESTRUCTIVE)
83
+
84
+ # Operations that are write but not destructive
85
+ non_destructive_writes = WRITE_OPERATIONS - DESTRUCTIVE_OPERATIONS
86
+ for op in non_destructive_writes:
87
+ allowed, reason = is_operation_allowed(op)
88
+ assert allowed is True
89
+
90
+ def test_destructive_operations_blocked_in_disable_destructive_mode(self):
91
+ """Test that destructive operations are blocked in DISABLE_DESTRUCTIVE mode."""
92
+ set_safety_mode(SafetyMode.DISABLE_DESTRUCTIVE)
93
+
94
+ for op in DESTRUCTIVE_OPERATIONS:
95
+ allowed, reason = is_operation_allowed(op)
96
+ assert allowed is False
97
+ assert "destructive operations are disabled" in reason
98
+
99
+
100
+ class TestCheckSafetyModeDecorator:
101
+ """Test the check_safety_mode decorator."""
102
+
103
+ def setup_method(self):
104
+ """Reset safety mode to NORMAL before each test."""
105
+ set_safety_mode(SafetyMode.NORMAL)
106
+
107
+ def test_decorator_allows_in_normal_mode(self):
108
+ """Test that decorated function executes in NORMAL mode."""
109
+ @check_safety_mode
110
+ def delete_pod():
111
+ return {"success": True, "message": "Pod deleted"}
112
+
113
+ result = delete_pod()
114
+ assert result["success"] is True
115
+ assert result["message"] == "Pod deleted"
116
+
117
+ def test_decorator_blocks_write_in_read_only_mode(self):
118
+ """Test that decorated function is blocked in READ_ONLY mode."""
119
+ set_safety_mode(SafetyMode.READ_ONLY)
120
+
121
+ @check_safety_mode
122
+ def run_pod():
123
+ return {"success": True, "message": "Pod created"}
124
+
125
+ result = run_pod()
126
+ assert result["success"] is False
127
+ assert "blocked" in result["error"]
128
+ assert result["blocked_by"] == "read_only"
129
+ assert result["operation"] == "run_pod"
130
+
131
+ def test_decorator_blocks_destructive_in_disable_destructive_mode(self):
132
+ """Test that destructive operations are blocked."""
133
+ set_safety_mode(SafetyMode.DISABLE_DESTRUCTIVE)
134
+
135
+ @check_safety_mode
136
+ def delete_deployment():
137
+ return {"success": True, "message": "Deployment deleted"}
138
+
139
+ result = delete_deployment()
140
+ assert result["success"] is False
141
+ assert "blocked" in result["error"]
142
+ assert result["blocked_by"] == "disable_destructive"
143
+
144
+ def test_decorator_allows_non_destructive_write_in_disable_destructive_mode(self):
145
+ """Test that non-destructive writes are allowed in DISABLE_DESTRUCTIVE mode."""
146
+ set_safety_mode(SafetyMode.DISABLE_DESTRUCTIVE)
147
+
148
+ @check_safety_mode
149
+ def scale_deployment():
150
+ return {"success": True, "message": "Deployment scaled"}
151
+
152
+ result = scale_deployment()
153
+ assert result["success"] is True
154
+
155
+
156
+ class TestGetModeInfo:
157
+ """Test get_mode_info function."""
158
+
159
+ def setup_method(self):
160
+ """Reset safety mode to NORMAL before each test."""
161
+ set_safety_mode(SafetyMode.NORMAL)
162
+
163
+ def test_normal_mode_info(self):
164
+ """Test mode info in NORMAL mode."""
165
+ info = get_mode_info()
166
+ assert info["mode"] == "normal"
167
+ assert "All operations allowed" in info["description"]
168
+ assert info["blocked_operations"] == []
169
+
170
+ def test_read_only_mode_info(self):
171
+ """Test mode info in READ_ONLY mode."""
172
+ set_safety_mode(SafetyMode.READ_ONLY)
173
+ info = get_mode_info()
174
+ assert info["mode"] == "read_only"
175
+ assert "read" in info["description"].lower()
176
+ assert len(info["blocked_operations"]) > 0
177
+ assert "delete_pod" in info["blocked_operations"]
178
+ assert "run_pod" in info["blocked_operations"]
179
+
180
+ def test_disable_destructive_mode_info(self):
181
+ """Test mode info in DISABLE_DESTRUCTIVE mode."""
182
+ set_safety_mode(SafetyMode.DISABLE_DESTRUCTIVE)
183
+ info = get_mode_info()
184
+ assert info["mode"] == "disable_destructive"
185
+ assert "delete" in info["description"].lower()
186
+ assert len(info["blocked_operations"]) > 0
187
+ assert "delete_pod" in info["blocked_operations"]
188
+ # Non-destructive writes should not be blocked
189
+ assert "scale_deployment" not in info["blocked_operations"]
190
+
191
+
192
+ class TestOperationCategories:
193
+ """Test that operations are categorized correctly."""
194
+
195
+ def test_all_destructive_ops_are_write_ops(self):
196
+ """Test that all destructive operations are also write operations."""
197
+ for op in DESTRUCTIVE_OPERATIONS:
198
+ assert op in WRITE_OPERATIONS, f"{op} is destructive but not in WRITE_OPERATIONS"
199
+
200
+ def test_expected_write_operations_exist(self):
201
+ """Test that expected write operations are defined."""
202
+ expected = [
203
+ "run_pod", "delete_pod",
204
+ "scale_deployment", "restart_deployment",
205
+ "install_helm_chart", "uninstall_helm_chart",
206
+ "apply_manifest", "delete_resource",
207
+ ]
208
+ for op in expected:
209
+ assert op in WRITE_OPERATIONS, f"Expected {op} in WRITE_OPERATIONS"
210
+
211
+ def test_expected_destructive_operations_exist(self):
212
+ """Test that expected destructive operations are defined."""
213
+ expected = [
214
+ "delete_pod", "delete_deployment", "delete_namespace",
215
+ "delete_resource", "uninstall_helm_chart",
216
+ ]
217
+ for op in expected:
218
+ assert op in DESTRUCTIVE_OPERATIONS, f"Expected {op} in DESTRUCTIVE_OPERATIONS"