This is page 5 of 5. Use http://codebase.md/portainer/portainer-mcp?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .cursor
│ └── rules
│ └── integration-test.mdc
├── .github
│ └── workflows
│ ├── ci.yml
│ └── release.yml
├── .gitignore
├── CLAUDE.md
├── cloc.sh
├── cmd
│ ├── portainer-mcp
│ │ └── mcp.go
│ └── token-count
│ └── token.go
├── docs
│ ├── clients_and_models.md
│ ├── design
│ │ ├── 202503-1-external-tools-file.md
│ │ ├── 202503-2-tools-vs-mcp-resources.md
│ │ ├── 202503-3-specific-update-tools.md
│ │ ├── 202504-1-embedded-tools-yaml.md
│ │ ├── 202504-2-tools-yaml-versioning.md
│ │ ├── 202504-3-portainer-version-compatibility.md
│ │ └── 202504-4-read-only-mode.md
│ └── design_summary.md
├── go.mod
├── go.sum
├── internal
│ ├── k8sutil
│ │ ├── stripper_test.go
│ │ └── stripper.go
│ ├── mcp
│ │ ├── access_group_test.go
│ │ ├── access_group.go
│ │ ├── docker_test.go
│ │ ├── docker.go
│ │ ├── environment_test.go
│ │ ├── environment.go
│ │ ├── group_test.go
│ │ ├── group.go
│ │ ├── kubernetes_test.go
│ │ ├── kubernetes.go
│ │ ├── mocks_test.go
│ │ ├── schema_test.go
│ │ ├── schema.go
│ │ ├── server_test.go
│ │ ├── server.go
│ │ ├── settings_test.go
│ │ ├── settings.go
│ │ ├── stack_test.go
│ │ ├── stack.go
│ │ ├── tag_test.go
│ │ ├── tag.go
│ │ ├── team_test.go
│ │ ├── team.go
│ │ ├── testdata
│ │ │ ├── invalid_tools.yaml
│ │ │ └── valid_tools.yaml
│ │ ├── user_test.go
│ │ ├── user.go
│ │ ├── utils_test.go
│ │ └── utils.go
│ └── tooldef
│ ├── tooldef_test.go
│ ├── tooldef.go
│ └── tools.yaml
├── LICENSE
├── Makefile
├── pkg
│ ├── portainer
│ │ ├── client
│ │ │ ├── access_group_test.go
│ │ │ ├── access_group.go
│ │ │ ├── client_test.go
│ │ │ ├── client.go
│ │ │ ├── docker_test.go
│ │ │ ├── docker.go
│ │ │ ├── environment_test.go
│ │ │ ├── environment.go
│ │ │ ├── group_test.go
│ │ │ ├── group.go
│ │ │ ├── kubernetes_test.go
│ │ │ ├── kubernetes.go
│ │ │ ├── mocks_test.go
│ │ │ ├── settings_test.go
│ │ │ ├── settings.go
│ │ │ ├── stack_test.go
│ │ │ ├── stack.go
│ │ │ ├── tag_test.go
│ │ │ ├── tag.go
│ │ │ ├── team_test.go
│ │ │ ├── team.go
│ │ │ ├── user_test.go
│ │ │ ├── user.go
│ │ │ ├── version_test.go
│ │ │ └── version.go
│ │ ├── models
│ │ │ ├── access_group_test.go
│ │ │ ├── access_group.go
│ │ │ ├── access_policy_test.go
│ │ │ ├── access_policy.go
│ │ │ ├── docker.go
│ │ │ ├── environment_test.go
│ │ │ ├── environment.go
│ │ │ ├── group_test.go
│ │ │ ├── group.go
│ │ │ ├── kubernetes.go
│ │ │ ├── settings_test.go
│ │ │ ├── settings.go
│ │ │ ├── stack_test.go
│ │ │ ├── stack.go
│ │ │ ├── tag_test.go
│ │ │ ├── tag.go
│ │ │ ├── team_test.go
│ │ │ ├── team.go
│ │ │ ├── user_test.go
│ │ │ └── user.go
│ │ └── utils
│ │ ├── utils_test.go
│ │ └── utils.go
│ └── toolgen
│ ├── param_test.go
│ ├── param.go
│ ├── yaml_test.go
│ └── yaml.go
├── README.md
├── tests
│ └── integration
│ ├── access_group_test.go
│ ├── containers
│ │ └── portainer.go
│ ├── docker_test.go
│ ├── environment_test.go
│ ├── group_test.go
│ ├── helpers
│ │ └── test_env.go
│ ├── server_test.go
│ ├── settings_test.go
│ ├── stack_test.go
│ ├── tag_test.go
│ ├── team_test.go
│ └── user_test.go
└── token.sh
```
# Files
--------------------------------------------------------------------------------
/internal/k8sutil/stripper_test.go:
--------------------------------------------------------------------------------
```go
1 | package k8sutil
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "io"
7 | "net/http"
8 | "testing"
9 |
10 | "github.com/stretchr/testify/assert"
11 | "github.com/stretchr/testify/require"
12 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
13 | )
14 |
15 | func TestProcessRawKubernetesAPIResponse(t *testing.T) {
16 | tests := []struct {
17 | name string
18 | httpResp *http.Response
19 | expectedResult string
20 | expectedError bool
21 | description string
22 | }{
23 | {
24 | name: "nil response",
25 | httpResp: nil,
26 | expectedError: true,
27 | description: "should return error when http response is nil",
28 | },
29 | {
30 | name: "nil body with 204 status",
31 | httpResp: &http.Response{
32 | StatusCode: http.StatusNoContent,
33 | Body: nil,
34 | ContentLength: 0,
35 | },
36 | expectedResult: "",
37 | expectedError: false,
38 | description: "should handle nil body gracefully for 204 status",
39 | },
40 | {
41 | name: "nil body with 200 status",
42 | httpResp: &http.Response{
43 | StatusCode: http.StatusOK,
44 | Body: nil,
45 | ContentLength: 1,
46 | },
47 | expectedError: true,
48 | description: "should return error when body is nil but content expected",
49 | },
50 | {
51 | name: "empty body",
52 | httpResp: &http.Response{
53 | StatusCode: http.StatusOK,
54 | Body: io.NopCloser(bytes.NewReader([]byte{})),
55 | },
56 | expectedResult: "",
57 | expectedError: false,
58 | description: "should handle empty body gracefully",
59 | },
60 | {
61 | name: "empty JSON object",
62 | httpResp: &http.Response{
63 | StatusCode: http.StatusOK,
64 | Body: io.NopCloser(bytes.NewReader([]byte("{}"))),
65 | },
66 | expectedResult: "{}",
67 | expectedError: false,
68 | description: "should handle empty JSON object",
69 | },
70 | {
71 | name: "empty JSON array",
72 | httpResp: &http.Response{
73 | StatusCode: http.StatusOK,
74 | Body: io.NopCloser(bytes.NewReader([]byte("[]"))),
75 | },
76 | expectedResult: "[]",
77 | expectedError: false,
78 | description: "should handle empty JSON array",
79 | },
80 | {
81 | name: "invalid JSON",
82 | httpResp: &http.Response{
83 | StatusCode: http.StatusOK,
84 | Body: io.NopCloser(bytes.NewReader([]byte("invalid json"))),
85 | },
86 | expectedError: true,
87 | description: "should return error for invalid JSON",
88 | },
89 | {
90 | name: "single object with managedFields",
91 | httpResp: &http.Response{
92 | StatusCode: http.StatusOK,
93 | Body: io.NopCloser(bytes.NewReader([]byte(`{
94 | "apiVersion": "v1",
95 | "kind": "Pod",
96 | "metadata": {
97 | "name": "test-pod",
98 | "namespace": "default",
99 | "managedFields": [
100 | {
101 | "manager": "kubectl-client-side-apply",
102 | "operation": "Update",
103 | "apiVersion": "v1",
104 | "time": "2023-01-01T00:00:00Z"
105 | }
106 | ]
107 | },
108 | "spec": {
109 | "containers": [
110 | {
111 | "name": "test-container",
112 | "image": "nginx"
113 | }
114 | ]
115 | }
116 | }`))),
117 | },
118 | expectedResult: `{"apiVersion":"v1","kind":"Pod","metadata":{"name":"test-pod","namespace":"default"},"spec":{"containers":[{"image":"nginx","name":"test-container"}]}}`,
119 | expectedError: false,
120 | description: "should remove managedFields from single object",
121 | },
122 | {
123 | name: "single object without managedFields",
124 | httpResp: &http.Response{
125 | StatusCode: http.StatusOK,
126 | Body: io.NopCloser(bytes.NewReader([]byte(`{
127 | "apiVersion": "v1",
128 | "kind": "Pod",
129 | "metadata": {
130 | "name": "test-pod",
131 | "namespace": "default"
132 | },
133 | "spec": {
134 | "containers": [
135 | {
136 | "name": "test-container",
137 | "image": "nginx"
138 | }
139 | ]
140 | }
141 | }`))),
142 | },
143 | expectedResult: `{"apiVersion":"v1","kind":"Pod","metadata":{"name":"test-pod","namespace":"default"},"spec":{"containers":[{"image":"nginx","name":"test-container"}]}}`,
144 | expectedError: false,
145 | description: "should handle single object without managedFields",
146 | },
147 | {
148 | name: "list with managedFields",
149 | httpResp: &http.Response{
150 | StatusCode: http.StatusOK,
151 | Body: io.NopCloser(bytes.NewReader([]byte(`{
152 | "apiVersion": "v1",
153 | "kind": "PodList",
154 | "items": [
155 | {
156 | "apiVersion": "v1",
157 | "kind": "Pod",
158 | "metadata": {
159 | "name": "test-pod-1",
160 | "namespace": "default",
161 | "managedFields": [
162 | {
163 | "manager": "kubectl-client-side-apply",
164 | "operation": "Update",
165 | "apiVersion": "v1",
166 | "time": "2023-01-01T00:00:00Z"
167 | }
168 | ]
169 | },
170 | "spec": {
171 | "containers": [
172 | {
173 | "name": "test-container",
174 | "image": "nginx"
175 | }
176 | ]
177 | }
178 | },
179 | {
180 | "apiVersion": "v1",
181 | "kind": "Pod",
182 | "metadata": {
183 | "name": "test-pod-2",
184 | "namespace": "default",
185 | "managedFields": [
186 | {
187 | "manager": "kubectl-client-side-apply",
188 | "operation": "Update",
189 | "apiVersion": "v1",
190 | "time": "2023-01-01T00:00:00Z"
191 | }
192 | ]
193 | },
194 | "spec": {
195 | "containers": [
196 | {
197 | "name": "test-container",
198 | "image": "redis"
199 | }
200 | ]
201 | }
202 | }
203 | ]
204 | }`))),
205 | },
206 | expectedResult: `{"apiVersion":"v1","items":[{"apiVersion":"v1","kind":"Pod","metadata":{"name":"test-pod-1","namespace":"default"},"spec":{"containers":[{"image":"nginx","name":"test-container"}]}},{"apiVersion":"v1","kind":"Pod","metadata":{"name":"test-pod-2","namespace":"default"},"spec":{"containers":[{"image":"redis","name":"test-container"}]}}],"kind":"PodList"}`,
207 | expectedError: false,
208 | description: "should remove managedFields from all items in list",
209 | },
210 | {
211 | name: "object without metadata",
212 | httpResp: &http.Response{
213 | StatusCode: http.StatusOK,
214 | Body: io.NopCloser(bytes.NewReader([]byte(`{
215 | "apiVersion": "v1",
216 | "kind": "Pod",
217 | "spec": {
218 | "containers": [
219 | {
220 | "name": "test-container",
221 | "image": "nginx"
222 | }
223 | ]
224 | }
225 | }`))),
226 | },
227 | expectedResult: `{"apiVersion":"v1","kind":"Pod","spec":{"containers":[{"image":"nginx","name":"test-container"}]}}`,
228 | expectedError: false,
229 | description: "should handle object without metadata",
230 | },
231 | {
232 | name: "empty object with no fields",
233 | httpResp: &http.Response{
234 | StatusCode: http.StatusOK,
235 | Body: io.NopCloser(bytes.NewReader([]byte(`{"apiVersion":"v1","kind":"Pod"}`))),
236 | },
237 | expectedResult: `{"apiVersion":"v1","kind":"Pod"}`,
238 | expectedError: false,
239 | description: "should handle empty object with no fields",
240 | },
241 | {
242 | name: "object with other metadata fields",
243 | httpResp: &http.Response{
244 | StatusCode: http.StatusOK,
245 | Body: io.NopCloser(bytes.NewReader([]byte(`{
246 | "apiVersion": "v1",
247 | "kind": "Service",
248 | "metadata": {
249 | "name": "test-service",
250 | "namespace": "default",
251 | "labels": {"app": "test"},
252 | "annotations": {"key": "value"},
253 | "managedFields": [
254 | {
255 | "manager": "kubectl-client-side-apply",
256 | "operation": "Update",
257 | "apiVersion": "v1",
258 | "time": "2023-01-01T00:00:00Z"
259 | }
260 | ]
261 | },
262 | "spec": {
263 | "ports": [{"port": 80}]
264 | }
265 | }`))),
266 | },
267 | expectedResult: `{"apiVersion":"v1","kind":"Service","metadata":{"annotations":{"key":"value"},"labels":{"app":"test"},"name":"test-service","namespace":"default"},"spec":{"ports":[{"port":80}]}}`,
268 | expectedError: false,
269 | description: "should preserve other metadata fields while removing managedFields",
270 | },
271 | {
272 | name: "list with mixed items",
273 | httpResp: &http.Response{
274 | StatusCode: http.StatusOK,
275 | Body: io.NopCloser(bytes.NewReader([]byte(`{
276 | "apiVersion": "v1",
277 | "kind": "PodList",
278 | "items": [
279 | {
280 | "apiVersion": "v1",
281 | "kind": "Pod",
282 | "metadata": {
283 | "name": "test-pod-1",
284 | "managedFields": [{"manager": "kubectl"}]
285 | }
286 | },
287 | {
288 | "apiVersion": "v1",
289 | "kind": "Pod",
290 | "metadata": {
291 | "name": "test-pod-2"
292 | }
293 | }
294 | ]
295 | }`))),
296 | },
297 | expectedResult: `{"apiVersion":"v1","items":[{"apiVersion":"v1","kind":"Pod","metadata":{"name":"test-pod-1"}},{"apiVersion":"v1","kind":"Pod","metadata":{"name":"test-pod-2"}}],"kind":"PodList"}`,
298 | expectedError: false,
299 | description: "should handle list with items that have and don't have managedFields",
300 | },
301 | {
302 | name: "nil body with 404 status",
303 | httpResp: &http.Response{
304 | StatusCode: http.StatusNotFound,
305 | Body: nil,
306 | ContentLength: 0,
307 | },
308 | expectedResult: "",
309 | expectedError: false,
310 | description: "should handle nil body for non-204 status with zero content length",
311 | },
312 | {
313 | name: "nil body with 500 status",
314 | httpResp: &http.Response{
315 | StatusCode: http.StatusInternalServerError,
316 | Body: nil,
317 | ContentLength: 0,
318 | },
319 | expectedResult: "",
320 | expectedError: false,
321 | description: "should handle nil body for error status with zero content length",
322 | },
323 | {
324 | name: "malformed list JSON",
325 | httpResp: &http.Response{
326 | StatusCode: http.StatusOK,
327 | Body: io.NopCloser(bytes.NewReader([]byte(`{
328 | "apiVersion": "v1",
329 | "kind": "PodList",
330 | "items": "not-an-array"
331 | }`))),
332 | },
333 | expectedResult: `{"apiVersion":"v1","kind":"PodList","items":"not-an-array"}`,
334 | expectedError: false,
335 | description: "should handle malformed list JSON gracefully",
336 | },
337 | {
338 | name: "object with circular reference",
339 | httpResp: &http.Response{
340 | StatusCode: http.StatusOK,
341 | Body: io.NopCloser(bytes.NewReader([]byte(`{
342 | "apiVersion": "v1",
343 | "kind": "Pod",
344 | "metadata": {
345 | "name": "test-pod",
346 | "managedFields": [
347 | {
348 | "manager": "kubectl-client-side-apply",
349 | "operation": "Update",
350 | "apiVersion": "v1",
351 | "time": "2023-01-01T00:00:00Z"
352 | }
353 | ]
354 | },
355 | "spec": {
356 | "containers": [
357 | {
358 | "name": "test-container",
359 | "image": "nginx"
360 | }
361 | ]
362 | }
363 | }`))),
364 | },
365 | expectedResult: `{"apiVersion":"v1","kind":"Pod","metadata":{"name":"test-pod"},"spec":{"containers":[{"image":"nginx","name":"test-container"}]}}`,
366 | expectedError: false,
367 | description: "should handle object with managedFields and preserve other fields",
368 | },
369 | {
370 | name: "list with empty items",
371 | httpResp: &http.Response{
372 | StatusCode: http.StatusOK,
373 | Body: io.NopCloser(bytes.NewReader([]byte(`{
374 | "apiVersion": "v1",
375 | "kind": "PodList",
376 | "items": []
377 | }`))),
378 | },
379 | expectedResult: `{"apiVersion":"v1","items":[],"kind":"PodList"}`,
380 | expectedError: false,
381 | description: "should handle list with empty items array",
382 | },
383 | {
384 | name: "object with deeply nested managedFields",
385 | httpResp: &http.Response{
386 | StatusCode: http.StatusOK,
387 | Body: io.NopCloser(bytes.NewReader([]byte(`{
388 | "apiVersion": "v1",
389 | "kind": "Pod",
390 | "metadata": {
391 | "name": "test-pod",
392 | "managedFields": [
393 | {
394 | "manager": "kubectl-client-side-apply",
395 | "operation": "Update",
396 | "apiVersion": "v1",
397 | "time": "2023-01-01T00:00:00Z",
398 | "fields": {
399 | "f:spec": {
400 | "f:containers": {
401 | "k:{\"name\":\"test-container\"}": {
402 | "f:image": {}
403 | }
404 | }
405 | }
406 | }
407 | }
408 | ]
409 | },
410 | "spec": {
411 | "containers": [
412 | {
413 | "name": "test-container",
414 | "image": "nginx"
415 | }
416 | ]
417 | }
418 | }`))),
419 | },
420 | expectedResult: `{"apiVersion":"v1","kind":"Pod","metadata":{"name":"test-pod"},"spec":{"containers":[{"image":"nginx","name":"test-container"}]}}`,
421 | expectedError: false,
422 | description: "should remove complex managedFields with nested structures",
423 | },
424 | {
425 | name: "different Kubernetes resource type",
426 | httpResp: &http.Response{
427 | StatusCode: http.StatusOK,
428 | Body: io.NopCloser(bytes.NewReader([]byte(`{
429 | "apiVersion": "apps/v1",
430 | "kind": "Deployment",
431 | "metadata": {
432 | "name": "test-deployment",
433 | "namespace": "default",
434 | "managedFields": [
435 | {
436 | "manager": "kubectl-client-side-apply",
437 | "operation": "Update",
438 | "apiVersion": "apps/v1",
439 | "time": "2023-01-01T00:00:00Z"
440 | }
441 | ]
442 | },
443 | "spec": {
444 | "replicas": 3,
445 | "selector": {
446 | "matchLabels": {
447 | "app": "test"
448 | }
449 | }
450 | }
451 | }`))),
452 | },
453 | expectedResult: `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"name":"test-deployment","namespace":"default"},"spec":{"replicas":3,"selector":{"matchLabels":{"app":"test"}}}}`,
454 | expectedError: false,
455 | description: "should handle different Kubernetes resource types",
456 | },
457 | {
458 | name: "custom resource with managedFields",
459 | httpResp: &http.Response{
460 | StatusCode: http.StatusOK,
461 | Body: io.NopCloser(bytes.NewReader([]byte(`{
462 | "apiVersion": "custom.example.com/v1",
463 | "kind": "CustomResource",
464 | "metadata": {
465 | "name": "test-custom",
466 | "managedFields": [
467 | {
468 | "manager": "custom-controller",
469 | "operation": "Apply",
470 | "apiVersion": "custom.example.com/v1",
471 | "time": "2023-01-01T00:00:00Z"
472 | }
473 | ]
474 | },
475 | "spec": {
476 | "customField": "customValue"
477 | }
478 | }`))),
479 | },
480 | expectedResult: `{"apiVersion":"custom.example.com/v1","kind":"CustomResource","metadata":{"name":"test-custom"},"spec":{"customField":"customValue"}}`,
481 | expectedError: false,
482 | description: "should handle custom resources with managedFields",
483 | },
484 | }
485 |
486 | for _, tt := range tests {
487 | t.Run(tt.name, func(t *testing.T) {
488 | result, err := ProcessRawKubernetesAPIResponse(tt.httpResp)
489 |
490 | if tt.expectedError {
491 | assert.Error(t, err, tt.description)
492 | return
493 | }
494 |
495 | require.NoError(t, err, tt.description)
496 | if tt.expectedResult == "" {
497 | assert.Equal(t, tt.expectedResult, string(result), tt.description)
498 | } else {
499 | assert.JSONEq(t, tt.expectedResult, string(result), tt.description)
500 | }
501 | })
502 | }
503 | }
504 |
505 | func TestRemoveManagedFieldsFromUnstructuredObject(t *testing.T) {
506 | tests := []struct {
507 | name string
508 | obj *unstructured.Unstructured
509 | expectedResult *unstructured.Unstructured
510 | expectedError bool
511 | description string
512 | }{
513 | {
514 | name: "nil object",
515 | obj: nil,
516 | expectedResult: nil,
517 | expectedError: false,
518 | description: "should handle nil object gracefully",
519 | },
520 | {
521 | name: "object with nil Object field",
522 | obj: &unstructured.Unstructured{
523 | Object: nil,
524 | },
525 | expectedResult: &unstructured.Unstructured{
526 | Object: nil,
527 | },
528 | expectedError: false,
529 | description: "should handle object with nil Object field",
530 | },
531 | {
532 | name: "object with managedFields",
533 | obj: &unstructured.Unstructured{
534 | Object: map[string]interface{}{
535 | "apiVersion": "v1",
536 | "kind": "Pod",
537 | "metadata": map[string]interface{}{
538 | "name": "test-pod",
539 | "namespace": "default",
540 | "managedFields": []interface{}{
541 | map[string]interface{}{
542 | "manager": "kubectl-client-side-apply",
543 | "operation": "Update",
544 | "apiVersion": "v1",
545 | "time": "2023-01-01T00:00:00Z",
546 | },
547 | },
548 | },
549 | "spec": map[string]interface{}{
550 | "containers": []interface{}{
551 | map[string]interface{}{
552 | "name": "test-container",
553 | "image": "nginx",
554 | },
555 | },
556 | },
557 | },
558 | },
559 | expectedResult: &unstructured.Unstructured{
560 | Object: map[string]interface{}{
561 | "apiVersion": "v1",
562 | "kind": "Pod",
563 | "metadata": map[string]interface{}{
564 | "name": "test-pod",
565 | "namespace": "default",
566 | },
567 | "spec": map[string]interface{}{
568 | "containers": []interface{}{
569 | map[string]interface{}{
570 | "name": "test-container",
571 | "image": "nginx",
572 | },
573 | },
574 | },
575 | },
576 | },
577 | expectedError: false,
578 | description: "should remove managedFields from object metadata",
579 | },
580 | {
581 | name: "object without managedFields",
582 | obj: &unstructured.Unstructured{
583 | Object: map[string]interface{}{
584 | "apiVersion": "v1",
585 | "kind": "Pod",
586 | "metadata": map[string]interface{}{
587 | "name": "test-pod",
588 | "namespace": "default",
589 | },
590 | "spec": map[string]interface{}{
591 | "containers": []interface{}{
592 | map[string]interface{}{
593 | "name": "test-container",
594 | "image": "nginx",
595 | },
596 | },
597 | },
598 | },
599 | },
600 | expectedResult: &unstructured.Unstructured{
601 | Object: map[string]interface{}{
602 | "apiVersion": "v1",
603 | "kind": "Pod",
604 | "metadata": map[string]interface{}{
605 | "name": "test-pod",
606 | "namespace": "default",
607 | },
608 | "spec": map[string]interface{}{
609 | "containers": []interface{}{
610 | map[string]interface{}{
611 | "name": "test-container",
612 | "image": "nginx",
613 | },
614 | },
615 | },
616 | },
617 | },
618 | expectedError: false,
619 | description: "should handle object without managedFields",
620 | },
621 | {
622 | name: "object without metadata",
623 | obj: &unstructured.Unstructured{
624 | Object: map[string]interface{}{
625 | "apiVersion": "v1",
626 | "kind": "Pod",
627 | "spec": map[string]interface{}{
628 | "containers": []interface{}{
629 | map[string]interface{}{
630 | "name": "test-container",
631 | "image": "nginx",
632 | },
633 | },
634 | },
635 | },
636 | },
637 | expectedResult: &unstructured.Unstructured{
638 | Object: map[string]interface{}{
639 | "apiVersion": "v1",
640 | "kind": "Pod",
641 | "spec": map[string]interface{}{
642 | "containers": []interface{}{
643 | map[string]interface{}{
644 | "name": "test-container",
645 | "image": "nginx",
646 | },
647 | },
648 | },
649 | },
650 | },
651 | expectedError: false,
652 | description: "should handle object without metadata",
653 | },
654 | {
655 | name: "object with non-map metadata",
656 | obj: &unstructured.Unstructured{
657 | Object: map[string]interface{}{
658 | "apiVersion": "v1",
659 | "kind": "Pod",
660 | "metadata": "not-a-map",
661 | "spec": map[string]interface{}{
662 | "containers": []interface{}{
663 | map[string]interface{}{
664 | "name": "test-container",
665 | "image": "nginx",
666 | },
667 | },
668 | },
669 | },
670 | },
671 | expectedResult: &unstructured.Unstructured{
672 | Object: map[string]interface{}{
673 | "apiVersion": "v1",
674 | "kind": "Pod",
675 | "metadata": "not-a-map",
676 | "spec": map[string]interface{}{
677 | "containers": []interface{}{
678 | map[string]interface{}{
679 | "name": "test-container",
680 | "image": "nginx",
681 | },
682 | },
683 | },
684 | },
685 | },
686 | expectedError: true,
687 | description: "should return error when metadata is not a map",
688 | },
689 | {
690 | name: "object with complex metadata",
691 | obj: &unstructured.Unstructured{
692 | Object: map[string]interface{}{
693 | "apiVersion": "v1",
694 | "kind": "Service",
695 | "metadata": map[string]interface{}{
696 | "name": "test-service",
697 | "namespace": "default",
698 | "labels": map[string]interface{}{
699 | "app": "test",
700 | "version": "v1",
701 | },
702 | "annotations": map[string]interface{}{
703 | "key1": "value1",
704 | "key2": "value2",
705 | },
706 | "managedFields": []interface{}{
707 | map[string]interface{}{
708 | "manager": "kubectl-client-side-apply",
709 | "operation": "Update",
710 | "apiVersion": "v1",
711 | "time": "2023-01-01T00:00:00Z",
712 | },
713 | map[string]interface{}{
714 | "manager": "controller-manager",
715 | "operation": "Apply",
716 | "apiVersion": "v1",
717 | "time": "2023-01-02T00:00:00Z",
718 | },
719 | },
720 | "ownerReferences": []interface{}{
721 | map[string]interface{}{
722 | "apiVersion": "apps/v1",
723 | "kind": "Deployment",
724 | "name": "test-deployment",
725 | },
726 | },
727 | },
728 | "spec": map[string]interface{}{
729 | "ports": []interface{}{
730 | map[string]interface{}{
731 | "port": 80,
732 | },
733 | },
734 | },
735 | },
736 | },
737 | expectedResult: &unstructured.Unstructured{
738 | Object: map[string]interface{}{
739 | "apiVersion": "v1",
740 | "kind": "Service",
741 | "metadata": map[string]interface{}{
742 | "name": "test-service",
743 | "namespace": "default",
744 | "labels": map[string]interface{}{
745 | "app": "test",
746 | "version": "v1",
747 | },
748 | "annotations": map[string]interface{}{
749 | "key1": "value1",
750 | "key2": "value2",
751 | },
752 | "ownerReferences": []interface{}{
753 | map[string]interface{}{
754 | "apiVersion": "apps/v1",
755 | "kind": "Deployment",
756 | "name": "test-deployment",
757 | },
758 | },
759 | },
760 | "spec": map[string]interface{}{
761 | "ports": []interface{}{
762 | map[string]interface{}{
763 | "port": 80,
764 | },
765 | },
766 | },
767 | },
768 | },
769 | expectedError: false,
770 | description: "should remove managedFields while preserving other metadata fields",
771 | },
772 | {
773 | name: "object with empty managedFields",
774 | obj: &unstructured.Unstructured{
775 | Object: map[string]interface{}{
776 | "apiVersion": "v1",
777 | "kind": "Pod",
778 | "metadata": map[string]interface{}{
779 | "name": "test-pod",
780 | "namespace": "default",
781 | "managedFields": []interface{}{},
782 | },
783 | "spec": map[string]interface{}{
784 | "containers": []interface{}{
785 | map[string]interface{}{
786 | "name": "test-container",
787 | "image": "nginx",
788 | },
789 | },
790 | },
791 | },
792 | },
793 | expectedResult: &unstructured.Unstructured{
794 | Object: map[string]interface{}{
795 | "apiVersion": "v1",
796 | "kind": "Pod",
797 | "metadata": map[string]interface{}{
798 | "name": "test-pod",
799 | "namespace": "default",
800 | },
801 | "spec": map[string]interface{}{
802 | "containers": []interface{}{
803 | map[string]interface{}{
804 | "name": "test-container",
805 | "image": "nginx",
806 | },
807 | },
808 | },
809 | },
810 | },
811 | expectedError: false,
812 | description: "should remove empty managedFields array",
813 | },
814 | {
815 | name: "object with nil managedFields",
816 | obj: &unstructured.Unstructured{
817 | Object: map[string]interface{}{
818 | "apiVersion": "v1",
819 | "kind": "Pod",
820 | "metadata": map[string]interface{}{
821 | "name": "test-pod",
822 | "namespace": "default",
823 | "managedFields": nil,
824 | },
825 | "spec": map[string]interface{}{
826 | "containers": []interface{}{
827 | map[string]interface{}{
828 | "name": "test-container",
829 | "image": "nginx",
830 | },
831 | },
832 | },
833 | },
834 | },
835 | expectedResult: &unstructured.Unstructured{
836 | Object: map[string]interface{}{
837 | "apiVersion": "v1",
838 | "kind": "Pod",
839 | "metadata": map[string]interface{}{
840 | "name": "test-pod",
841 | "namespace": "default",
842 | },
843 | "spec": map[string]interface{}{
844 | "containers": []interface{}{
845 | map[string]interface{}{
846 | "name": "test-container",
847 | "image": "nginx",
848 | },
849 | },
850 | },
851 | },
852 | },
853 | expectedError: false,
854 | description: "should remove nil managedFields",
855 | },
856 | {
857 | name: "object with minimal fields",
858 | obj: &unstructured.Unstructured{
859 | Object: map[string]interface{}{
860 | "apiVersion": "v1",
861 | "kind": "Pod",
862 | "metadata": map[string]interface{}{
863 | "name": "test-pod",
864 | },
865 | },
866 | },
867 | expectedResult: &unstructured.Unstructured{
868 | Object: map[string]interface{}{
869 | "apiVersion": "v1",
870 | "kind": "Pod",
871 | "metadata": map[string]interface{}{
872 | "name": "test-pod",
873 | },
874 | },
875 | },
876 | expectedError: false,
877 | description: "should handle object with minimal metadata",
878 | },
879 | }
880 |
881 | for _, tt := range tests {
882 | t.Run(tt.name, func(t *testing.T) {
883 | err := removeManagedFieldsFromUnstructuredObject(tt.obj)
884 |
885 | if tt.expectedError {
886 | assert.Error(t, err, tt.description)
887 | return
888 | }
889 |
890 | require.NoError(t, err, tt.description)
891 | assert.Equal(t, tt.expectedResult, tt.obj, tt.description)
892 | })
893 | }
894 | }
895 |
896 | // Helper function to create a JSON response for testing
897 | func createJSONResponse(statusCode int, body string) *http.Response {
898 | return &http.Response{
899 | StatusCode: statusCode,
900 | Body: io.NopCloser(bytes.NewReader([]byte(body))),
901 | Header: make(http.Header),
902 | }
903 | }
904 |
905 | // Benchmark tests for performance
906 | func BenchmarkProcessRawKubernetesAPIResponse_SingleObject(b *testing.B) {
907 | jsonBody := `{
908 | "apiVersion": "v1",
909 | "kind": "Pod",
910 | "metadata": {
911 | "name": "test-pod",
912 | "namespace": "default",
913 | "managedFields": [
914 | {
915 | "manager": "kubectl-client-side-apply",
916 | "operation": "Update",
917 | "apiVersion": "v1",
918 | "time": "2023-01-01T00:00:00Z"
919 | }
920 | ]
921 | },
922 | "spec": {
923 | "containers": [
924 | {
925 | "name": "test-container",
926 | "image": "nginx"
927 | }
928 | ]
929 | }
930 | }`
931 |
932 | for i := 0; i < b.N; i++ {
933 | resp := createJSONResponse(http.StatusOK, jsonBody)
934 | _, err := ProcessRawKubernetesAPIResponse(resp)
935 | if err != nil {
936 | b.Fatal(err)
937 | }
938 | }
939 | }
940 |
941 | func BenchmarkProcessRawKubernetesAPIResponse_List(b *testing.B) {
942 | jsonBody := `{
943 | "apiVersion": "v1",
944 | "kind": "PodList",
945 | "items": [
946 | {
947 | "apiVersion": "v1",
948 | "kind": "Pod",
949 | "metadata": {
950 | "name": "test-pod-1",
951 | "namespace": "default",
952 | "managedFields": [
953 | {
954 | "manager": "kubectl-client-side-apply",
955 | "operation": "Update",
956 | "apiVersion": "v1",
957 | "time": "2023-01-01T00:00:00Z"
958 | }
959 | ]
960 | },
961 | "spec": {
962 | "containers": [
963 | {
964 | "name": "test-container",
965 | "image": "nginx"
966 | }
967 | ]
968 | }
969 | }
970 | ]
971 | }`
972 |
973 | for i := 0; i < b.N; i++ {
974 | resp := createJSONResponse(http.StatusOK, jsonBody)
975 | _, err := ProcessRawKubernetesAPIResponse(resp)
976 | if err != nil {
977 | b.Fatal(err)
978 | }
979 | }
980 | }
981 |
982 | // TestErrorConditions tests various error conditions that might occur
983 | func TestErrorConditions(t *testing.T) {
984 | t.Run("body read error", func(t *testing.T) {
985 | // Create a response with a body that will fail to read
986 | resp := &http.Response{
987 | StatusCode: http.StatusOK,
988 | Body: &errorReader{},
989 | }
990 |
991 | _, err := ProcessRawKubernetesAPIResponse(resp)
992 | assert.Error(t, err)
993 | assert.Contains(t, err.Error(), "failed to read response body")
994 | })
995 |
996 | t.Run("json marshal error", func(t *testing.T) {
997 | // This test is difficult to trigger in practice since the unstructured
998 | // library handles most cases, but we can test the structure
999 | resp := createJSONResponse(http.StatusOK, `{
1000 | "apiVersion": "v1",
1001 | "kind": "Pod",
1002 | "metadata": {
1003 | "name": "test-pod",
1004 | "managedFields": [{"manager": "kubectl"}]
1005 | }
1006 | }`)
1007 |
1008 | result, err := ProcessRawKubernetesAPIResponse(resp)
1009 | require.NoError(t, err)
1010 | assert.NotEmpty(t, result)
1011 | })
1012 | }
1013 |
1014 | // errorReader is a reader that always returns an error
1015 | type errorReader struct{}
1016 |
1017 | func (e *errorReader) Read(p []byte) (n int, err error) {
1018 | return 0, fmt.Errorf("simulated read error")
1019 | }
1020 |
1021 | func (e *errorReader) Close() error {
1022 | return nil
1023 | }
1024 |
1025 | // TestEdgeCases tests various edge cases and boundary conditions
1026 | func TestEdgeCases(t *testing.T) {
1027 | t.Run("very large managedFields", func(t *testing.T) {
1028 | // Create a large managedFields array
1029 | largeManagedFields := make([]interface{}, 1000)
1030 | for i := 0; i < 1000; i++ {
1031 | largeManagedFields[i] = map[string]interface{}{
1032 | "manager": fmt.Sprintf("manager-%d", i),
1033 | "operation": "Update",
1034 | "apiVersion": "v1",
1035 | "time": "2023-01-01T00:00:00Z",
1036 | }
1037 | }
1038 |
1039 | obj := &unstructured.Unstructured{
1040 | Object: map[string]interface{}{
1041 | "apiVersion": "v1",
1042 | "kind": "Pod",
1043 | "metadata": map[string]interface{}{
1044 | "name": "test-pod",
1045 | "managedFields": largeManagedFields,
1046 | },
1047 | },
1048 | }
1049 |
1050 | err := removeManagedFieldsFromUnstructuredObject(obj)
1051 | require.NoError(t, err)
1052 |
1053 | // Verify managedFields was removed
1054 | metadata, found, err := unstructured.NestedFieldCopy(obj.Object, "metadata")
1055 | require.NoError(t, err)
1056 | require.True(t, found)
1057 |
1058 | metadataMap, ok := metadata.(map[string]interface{})
1059 | require.True(t, ok)
1060 | _, hasManagedFields := metadataMap["managedFields"]
1061 | assert.False(t, hasManagedFields)
1062 | })
1063 |
1064 | t.Run("metadata with special characters", func(t *testing.T) {
1065 | obj := &unstructured.Unstructured{
1066 | Object: map[string]interface{}{
1067 | "apiVersion": "v1",
1068 | "kind": "Pod",
1069 | "metadata": map[string]interface{}{
1070 | "name": "test-pod-with-special-chars-!@#$%^&*()",
1071 | "namespace": "default-namespace",
1072 | "managedFields": []interface{}{map[string]interface{}{"manager": "kubectl"}},
1073 | },
1074 | },
1075 | }
1076 |
1077 | err := removeManagedFieldsFromUnstructuredObject(obj)
1078 | require.NoError(t, err)
1079 |
1080 | // Verify the object still has the special characters in name
1081 | name, found, err := unstructured.NestedString(obj.Object, "metadata", "name")
1082 | require.NoError(t, err)
1083 | require.True(t, found)
1084 | assert.Equal(t, "test-pod-with-special-chars-!@#$%^&*()", name)
1085 | })
1086 |
1087 | t.Run("empty object after processing", func(t *testing.T) {
1088 | obj := &unstructured.Unstructured{
1089 | Object: map[string]interface{}{
1090 | "apiVersion": "v1",
1091 | "kind": "Pod",
1092 | "metadata": map[string]interface{}{
1093 | "managedFields": []interface{}{map[string]interface{}{"manager": "kubectl"}},
1094 | },
1095 | },
1096 | }
1097 |
1098 | err := removeManagedFieldsFromUnstructuredObject(obj)
1099 | require.NoError(t, err)
1100 |
1101 | // Verify the object still has the basic structure
1102 | assert.Equal(t, "v1", obj.GetAPIVersion())
1103 | assert.Equal(t, "Pod", obj.GetKind())
1104 | })
1105 |
1106 | t.Run("object with only managedFields in metadata", func(t *testing.T) {
1107 | obj := &unstructured.Unstructured{
1108 | Object: map[string]interface{}{
1109 | "apiVersion": "v1",
1110 | "kind": "Pod",
1111 | "metadata": map[string]interface{}{
1112 | "managedFields": []interface{}{
1113 | map[string]interface{}{"manager": "kubectl"},
1114 | map[string]interface{}{"manager": "controller"},
1115 | },
1116 | },
1117 | },
1118 | }
1119 |
1120 | err := removeManagedFieldsFromUnstructuredObject(obj)
1121 | require.NoError(t, err)
1122 |
1123 | // Verify managedFields was removed and metadata is now empty
1124 | metadata, found, err := unstructured.NestedFieldCopy(obj.Object, "metadata")
1125 | require.NoError(t, err)
1126 | require.True(t, found)
1127 |
1128 | metadataMap, ok := metadata.(map[string]interface{})
1129 | require.True(t, ok)
1130 | assert.Empty(t, metadataMap)
1131 | })
1132 |
1133 | t.Run("object with empty Object map", func(t *testing.T) {
1134 | obj := &unstructured.Unstructured{
1135 | Object: map[string]interface{}{},
1136 | }
1137 |
1138 | err := removeManagedFieldsFromUnstructuredObject(obj)
1139 | require.NoError(t, err)
1140 | assert.Empty(t, obj.Object)
1141 | })
1142 |
1143 | t.Run("object with non-map metadata that causes error", func(t *testing.T) {
1144 | obj := &unstructured.Unstructured{
1145 | Object: map[string]interface{}{
1146 | "apiVersion": "v1",
1147 | "kind": "Pod",
1148 | "metadata": "not-a-map",
1149 | },
1150 | }
1151 |
1152 | err := removeManagedFieldsFromUnstructuredObject(obj)
1153 | assert.Error(t, err)
1154 | assert.Contains(t, err.Error(), "metadata for object")
1155 | assert.Contains(t, err.Error(), "is not in the expected map format")
1156 | })
1157 |
1158 | t.Run("object with metadata that causes NestedFieldCopy error", func(t *testing.T) {
1159 | // Create an object with a metadata field that will cause an error
1160 | // This is difficult to trigger in practice, but we can test the error path
1161 | obj := &unstructured.Unstructured{
1162 | Object: map[string]interface{}{
1163 | "apiVersion": "v1",
1164 | "kind": "Pod",
1165 | "metadata": map[string]interface{}{
1166 | "name": "test-pod",
1167 | },
1168 | },
1169 | }
1170 |
1171 | err := removeManagedFieldsFromUnstructuredObject(obj)
1172 | require.NoError(t, err)
1173 | })
1174 |
1175 | t.Run("object with metadata that causes SetNestedField error", func(t *testing.T) {
1176 | // This is difficult to trigger in practice since SetNestedField is quite robust
1177 | // But we can test the structure
1178 | obj := &unstructured.Unstructured{
1179 | Object: map[string]interface{}{
1180 | "apiVersion": "v1",
1181 | "kind": "Pod",
1182 | "metadata": map[string]interface{}{
1183 | "name": "test-pod",
1184 | "managedFields": []interface{}{map[string]interface{}{"manager": "kubectl"}},
1185 | },
1186 | },
1187 | }
1188 |
1189 | err := removeManagedFieldsFromUnstructuredObject(obj)
1190 | require.NoError(t, err)
1191 | })
1192 | }
1193 |
1194 | // TestAdditionalErrorCases tests additional error scenarios that might not be covered
1195 | func TestAdditionalErrorCases(t *testing.T) {
1196 | t.Run("list with ToList error", func(t *testing.T) {
1197 | // Create a malformed list that will cause ToList to fail
1198 | // This is difficult to trigger in practice, but we can test the structure
1199 | resp := &http.Response{
1200 | StatusCode: http.StatusOK,
1201 | Body: io.NopCloser(bytes.NewReader([]byte(`{
1202 | "apiVersion": "v1",
1203 | "kind": "PodList",
1204 | "items": "not-an-array"
1205 | }`))),
1206 | }
1207 |
1208 | result, err := ProcessRawKubernetesAPIResponse(resp)
1209 | // This should not error because the unstructured library handles this gracefully
1210 | require.NoError(t, err)
1211 | assert.NotEmpty(t, result)
1212 | })
1213 |
1214 | t.Run("single object with empty Object map", func(t *testing.T) {
1215 | resp := &http.Response{
1216 | StatusCode: http.StatusOK,
1217 | Body: io.NopCloser(bytes.NewReader([]byte(`{"apiVersion":"v1","kind":"Pod"}`))),
1218 | }
1219 |
1220 | result, err := ProcessRawKubernetesAPIResponse(resp)
1221 | require.NoError(t, err)
1222 | assert.NotEmpty(t, result)
1223 | })
1224 |
1225 | t.Run("list with error during item processing", func(t *testing.T) {
1226 | // Create a list where one item has invalid metadata
1227 | resp := &http.Response{
1228 | StatusCode: http.StatusOK,
1229 | Body: io.NopCloser(bytes.NewReader([]byte(`{
1230 | "apiVersion": "v1",
1231 | "kind": "PodList",
1232 | "items": [
1233 | {
1234 | "apiVersion": "v1",
1235 | "kind": "Pod",
1236 | "metadata": "not-a-map"
1237 | }
1238 | ]
1239 | }`))),
1240 | }
1241 |
1242 | _, err := ProcessRawKubernetesAPIResponse(resp)
1243 | assert.Error(t, err)
1244 | assert.Contains(t, err.Error(), "failed to remove managedFields from item")
1245 | })
1246 |
1247 | t.Run("single object with error during processing", func(t *testing.T) {
1248 | // Create a single object with invalid metadata
1249 | resp := &http.Response{
1250 | StatusCode: http.StatusOK,
1251 | Body: io.NopCloser(bytes.NewReader([]byte(`{
1252 | "apiVersion": "v1",
1253 | "kind": "Pod",
1254 | "metadata": "not-a-map"
1255 | }`))),
1256 | }
1257 |
1258 | _, err := ProcessRawKubernetesAPIResponse(resp)
1259 | assert.Error(t, err)
1260 | assert.Contains(t, err.Error(), "failed to remove managedFields from single object")
1261 | })
1262 |
1263 | t.Run("json marshal error for list", func(t *testing.T) {
1264 | // This is difficult to trigger in practice since json.Marshal is quite robust
1265 | // But we can test the structure
1266 | resp := &http.Response{
1267 | StatusCode: http.StatusOK,
1268 | Body: io.NopCloser(bytes.NewReader([]byte(`{
1269 | "apiVersion": "v1",
1270 | "kind": "PodList",
1271 | "items": [
1272 | {
1273 | "apiVersion": "v1",
1274 | "kind": "Pod",
1275 | "metadata": {
1276 | "name": "test-pod",
1277 | "managedFields": [{"manager": "kubectl"}]
1278 | }
1279 | }
1280 | ]
1281 | }`))),
1282 | }
1283 |
1284 | result, err := ProcessRawKubernetesAPIResponse(resp)
1285 | require.NoError(t, err)
1286 | assert.NotEmpty(t, result)
1287 | })
1288 |
1289 | t.Run("json marshal error for single object", func(t *testing.T) {
1290 | // This is difficult to trigger in practice since json.Marshal is quite robust
1291 | // But we can test the structure
1292 | resp := &http.Response{
1293 | StatusCode: http.StatusOK,
1294 | Body: io.NopCloser(bytes.NewReader([]byte(`{
1295 | "apiVersion": "v1",
1296 | "kind": "Pod",
1297 | "metadata": {
1298 | "name": "test-pod",
1299 | "managedFields": [{"manager": "kubectl"}]
1300 | }
1301 | }`))),
1302 | }
1303 |
1304 | result, err := ProcessRawKubernetesAPIResponse(resp)
1305 | require.NoError(t, err)
1306 | assert.NotEmpty(t, result)
1307 | })
1308 | }
1309 |
1310 | // TestNilBodyWithContentLength tests the specific case where body is nil but content length is not zero
1311 | func TestNilBodyWithContentLength(t *testing.T) {
1312 | t.Run("nil body with positive content length", func(t *testing.T) {
1313 | resp := &http.Response{
1314 | StatusCode: http.StatusOK,
1315 | Body: nil,
1316 | ContentLength: 100,
1317 | }
1318 |
1319 | _, err := ProcessRawKubernetesAPIResponse(resp)
1320 | assert.Error(t, err)
1321 | assert.Contains(t, err.Error(), "http response body is nil but content was expected")
1322 | })
1323 |
1324 | t.Run("nil body with negative content length", func(t *testing.T) {
1325 | resp := &http.Response{
1326 | StatusCode: http.StatusOK,
1327 | Body: nil,
1328 | ContentLength: -1,
1329 | }
1330 |
1331 | _, err := ProcessRawKubernetesAPIResponse(resp)
1332 | assert.Error(t, err)
1333 | assert.Contains(t, err.Error(), "http response body is nil but content was expected")
1334 | })
1335 | }
1336 |
1337 | // TestUnmarshalErrorHandling tests the specific error handling for JSON unmarshaling
1338 | func TestUnmarshalErrorHandling(t *testing.T) {
1339 | t.Run("invalid JSON that is not empty object or array", func(t *testing.T) {
1340 | resp := &http.Response{
1341 | StatusCode: http.StatusOK,
1342 | Body: io.NopCloser(bytes.NewReader([]byte("invalid json content"))),
1343 | }
1344 |
1345 | _, err := ProcessRawKubernetesAPIResponse(resp)
1346 | assert.Error(t, err)
1347 | assert.Contains(t, err.Error(), "failed to unmarshal JSON into Unstructured")
1348 | assert.Contains(t, err.Error(), "Body: invalid json content")
1349 | })
1350 |
1351 | t.Run("empty JSON object string", func(t *testing.T) {
1352 | resp := &http.Response{
1353 | StatusCode: http.StatusOK,
1354 | Body: io.NopCloser(bytes.NewReader([]byte("{}"))),
1355 | }
1356 |
1357 | result, err := ProcessRawKubernetesAPIResponse(resp)
1358 | require.NoError(t, err)
1359 | assert.Equal(t, "{}", string(result))
1360 | })
1361 |
1362 | t.Run("empty JSON array string", func(t *testing.T) {
1363 | resp := &http.Response{
1364 | StatusCode: http.StatusOK,
1365 | Body: io.NopCloser(bytes.NewReader([]byte("[]"))),
1366 | }
1367 |
1368 | result, err := ProcessRawKubernetesAPIResponse(resp)
1369 | require.NoError(t, err)
1370 | assert.Equal(t, "[]", string(result))
1371 | })
1372 | }
1373 |
```