1
16
17 package resource
18
19 import (
20 "bytes"
21 "errors"
22 "fmt"
23 "io"
24 "net/http"
25 "reflect"
26 "strings"
27 "testing"
28
29 apierrors "k8s.io/apimachinery/pkg/api/errors"
30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31 "k8s.io/apimachinery/pkg/labels"
32 "k8s.io/apimachinery/pkg/runtime"
33 "k8s.io/client-go/rest/fake"
34
35
36 corev1 "k8s.io/api/core/v1"
37 "k8s.io/client-go/kubernetes/scheme"
38 )
39
40 func objBody(obj runtime.Object) io.ReadCloser {
41 return io.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(corev1Codec, obj))))
42 }
43
44 func header() http.Header {
45 header := http.Header{}
46 header.Set("Content-Type", runtime.ContentTypeJSON)
47 return header
48 }
49
50
51 func splitPath(path string) []string {
52 path = strings.Trim(path, "/")
53 if path == "" {
54 return []string{}
55 }
56 return strings.Split(path, "/")
57 }
58
59
60 func V1DeepEqualSafePodSpec() corev1.PodSpec {
61 grace := int64(30)
62 return corev1.PodSpec{
63 RestartPolicy: corev1.RestartPolicyAlways,
64 DNSPolicy: corev1.DNSClusterFirst,
65 TerminationGracePeriodSeconds: &grace,
66 SecurityContext: &corev1.PodSecurityContext{},
67 }
68 }
69
70 func V1DeepEqualSafePodStatus() corev1.PodStatus {
71 return corev1.PodStatus{
72 Conditions: []corev1.PodCondition{
73 {
74 Status: corev1.ConditionTrue,
75 Type: corev1.PodReady,
76 },
77 },
78 }
79 }
80
81 func TestHelperDelete(t *testing.T) {
82 tests := []struct {
83 name string
84 Err bool
85 Req func(*http.Request) bool
86 Resp *http.Response
87 HttpErr error
88 }{
89 {
90 name: "test1",
91 HttpErr: errors.New("failure"),
92 Err: true,
93 },
94 {
95 name: "test2",
96 Resp: &http.Response{
97 StatusCode: http.StatusNotFound,
98 Header: header(),
99 Body: objBody(&metav1.Status{Status: metav1.StatusFailure}),
100 },
101 Err: true,
102 },
103 {
104 name: "test3pkg/kubectl/genericclioptions/resource/helper_test.go",
105 Resp: &http.Response{
106 StatusCode: http.StatusOK,
107 Header: header(),
108 Body: objBody(&metav1.Status{Status: metav1.StatusSuccess}),
109 },
110 Req: func(req *http.Request) bool {
111 if req.Method != "DELETE" {
112 t.Errorf("unexpected method: %#v", req)
113 return false
114 }
115 parts := splitPath(req.URL.Path)
116 if len(parts) < 3 {
117 t.Errorf("expected URL path to have 3 parts: %s", req.URL.Path)
118 return false
119 }
120 if parts[1] != "bar" {
121 t.Errorf("url doesn't contain namespace: %#v", req)
122 return false
123 }
124 if parts[2] != "foo" {
125 t.Errorf("url doesn't contain name: %#v", req)
126 return false
127 }
128 return true
129 },
130 },
131 }
132 for _, tt := range tests {
133 t.Run(tt.name, func(t *testing.T) {
134 client := &fake.RESTClient{
135 NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
136 Resp: tt.Resp,
137 Err: tt.HttpErr,
138 }
139 modifier := &Helper{
140 RESTClient: client,
141 NamespaceScoped: true,
142 }
143 _, err := modifier.Delete("bar", "foo")
144 if (err != nil) != tt.Err {
145 t.Errorf("unexpected error: %t %v", tt.Err, err)
146 }
147 if err != nil {
148 return
149 }
150 if tt.Req != nil && !tt.Req(client.Req) {
151 t.Errorf("unexpected request: %#v", client.Req)
152 }
153 })
154 }
155 }
156
157 func TestHelperCreate(t *testing.T) {
158 expectPost := func(req *http.Request) bool {
159 if req.Method != "POST" {
160 t.Errorf("unexpected method: %#v", req)
161 return false
162 }
163 parts := splitPath(req.URL.Path)
164 if parts[1] != "bar" {
165 t.Errorf("url doesn't contain namespace: %#v", req)
166 return false
167 }
168 return true
169 }
170
171 tests := []struct {
172 name string
173 Resp *http.Response
174 HttpErr error
175 Modify bool
176 Object runtime.Object
177
178 ExpectObject runtime.Object
179 Err bool
180 Req func(*http.Request) bool
181 }{
182 {
183 name: "test1",
184 HttpErr: errors.New("failure"),
185 Err: true,
186 },
187 {
188 name: "test1",
189 Resp: &http.Response{
190 StatusCode: http.StatusNotFound,
191 Header: header(),
192 Body: objBody(&metav1.Status{Status: metav1.StatusFailure}),
193 },
194 Err: true,
195 },
196 {
197 name: "test1",
198 Resp: &http.Response{
199 StatusCode: http.StatusOK,
200 Header: header(),
201 Body: objBody(&metav1.Status{Status: metav1.StatusSuccess}),
202 },
203 Object: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
204 ExpectObject: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
205 Req: expectPost,
206 },
207 {
208 name: "test1",
209 Modify: false,
210 Object: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}},
211 ExpectObject: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}},
212 Resp: &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&metav1.Status{Status: metav1.StatusSuccess})},
213 Req: expectPost,
214 },
215 {
216 name: "test1",
217 Modify: true,
218 Object: &corev1.Pod{
219 ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"},
220 Spec: V1DeepEqualSafePodSpec(),
221 },
222 ExpectObject: &corev1.Pod{
223 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
224 Spec: V1DeepEqualSafePodSpec(),
225 },
226 Resp: &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&metav1.Status{Status: metav1.StatusSuccess})},
227 Req: expectPost,
228 },
229 }
230 for i, tt := range tests {
231 t.Run(tt.name, func(t *testing.T) {
232 client := &fake.RESTClient{
233 GroupVersion: corev1GV,
234 NegotiatedSerializer: scheme.Codecs,
235 Resp: tt.Resp,
236 Err: tt.HttpErr,
237 }
238 modifier := &Helper{
239 RESTClient: client,
240 NamespaceScoped: true,
241 }
242 _, err := modifier.Create("bar", tt.Modify, tt.Object)
243 if (err != nil) != tt.Err {
244 t.Errorf("%d: unexpected error: %t %v", i, tt.Err, err)
245 }
246 if err != nil {
247 return
248 }
249 if tt.Req != nil && !tt.Req(client.Req) {
250 t.Errorf("%d: unexpected request: %#v", i, client.Req)
251 }
252 body, err := io.ReadAll(client.Req.Body)
253 if err != nil {
254 t.Fatalf("%d: unexpected error: %#v", i, err)
255 }
256 t.Logf("got body: %s", string(body))
257 expect := []byte{}
258 if tt.ExpectObject != nil {
259 expect = []byte(runtime.EncodeOrDie(corev1Codec, tt.ExpectObject))
260 }
261 if !reflect.DeepEqual(expect, body) {
262 t.Errorf("%d: unexpected body: %s (expected %s)", i, string(body), string(expect))
263 }
264 })
265 }
266 }
267
268 func TestHelperGet(t *testing.T) {
269 tests := []struct {
270 name string
271 subresource string
272 Err bool
273 Req func(*http.Request) bool
274 Resp *http.Response
275 HttpErr error
276 }{
277 {
278 name: "test1",
279 HttpErr: errors.New("failure"),
280 Err: true,
281 },
282 {
283 name: "test1",
284 Resp: &http.Response{
285 StatusCode: http.StatusNotFound,
286 Header: header(),
287 Body: objBody(&metav1.Status{Status: metav1.StatusFailure}),
288 },
289 Err: true,
290 },
291 {
292 name: "test1",
293 Resp: &http.Response{
294 StatusCode: http.StatusOK,
295 Header: header(),
296 Body: objBody(&corev1.Pod{TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Pod"}, ObjectMeta: metav1.ObjectMeta{Name: "foo"}}),
297 },
298 Req: func(req *http.Request) bool {
299 if req.Method != "GET" {
300 t.Errorf("unexpected method: %#v", req)
301 return false
302 }
303 parts := splitPath(req.URL.Path)
304 if parts[1] != "bar" {
305 t.Errorf("url doesn't contain namespace: %#v", req)
306 return false
307 }
308 if parts[2] != "foo" {
309 t.Errorf("url doesn't contain name: %#v", req)
310 return false
311 }
312 return true
313 },
314 },
315 {
316 name: "test with subresource",
317 subresource: "status",
318 Resp: &http.Response{
319 StatusCode: http.StatusOK,
320 Header: header(),
321 Body: objBody(&corev1.Pod{TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Pod"}, ObjectMeta: metav1.ObjectMeta{Name: "foo"}}),
322 },
323 Req: func(req *http.Request) bool {
324 if req.Method != "GET" {
325 t.Errorf("unexpected method: %#v", req)
326 return false
327 }
328 parts := splitPath(req.URL.Path)
329 if parts[1] != "bar" {
330 t.Errorf("url doesn't contain namespace: %#v", req)
331 return false
332 }
333 if parts[2] != "foo" {
334 t.Errorf("url doesn't contain name: %#v", req)
335 return false
336 }
337 if parts[3] != "status" {
338 t.Errorf("url doesn't contain subresource: %#v", req)
339 return false
340 }
341 return true
342 },
343 },
344 }
345 for i, tt := range tests {
346 t.Run(tt.name, func(t *testing.T) {
347 client := &fake.RESTClient{
348 GroupVersion: corev1GV,
349 NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
350 Resp: tt.Resp,
351 Err: tt.HttpErr,
352 }
353 modifier := &Helper{
354 RESTClient: client,
355 NamespaceScoped: true,
356 Subresource: tt.subresource,
357 }
358 obj, err := modifier.Get("bar", "foo")
359
360 if (err != nil) != tt.Err {
361 t.Errorf("unexpected error: %d %t %v", i, tt.Err, err)
362 }
363 if err != nil {
364 return
365 }
366 if obj.(*corev1.Pod).Name != "foo" {
367 t.Errorf("unexpected object: %#v", obj)
368 }
369 if tt.Req != nil && !tt.Req(client.Req) {
370 t.Errorf("unexpected request: %#v", client.Req)
371 }
372 })
373 }
374 }
375
376 func TestHelperList(t *testing.T) {
377 tests := []struct {
378 name string
379 Err bool
380 Req func(*http.Request) bool
381 Resp *http.Response
382 HttpErr error
383 }{
384 {
385 name: "test1",
386 HttpErr: errors.New("failure"),
387 Err: true,
388 },
389 {
390 name: "test2",
391 Resp: &http.Response{
392 StatusCode: http.StatusNotFound,
393 Header: header(),
394 Body: objBody(&metav1.Status{Status: metav1.StatusFailure}),
395 },
396 Err: true,
397 },
398 {
399 name: "test3",
400 Resp: &http.Response{
401 StatusCode: http.StatusOK,
402 Header: header(),
403 Body: objBody(&corev1.PodList{
404 Items: []corev1.Pod{{
405 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
406 },
407 },
408 }),
409 },
410 Req: func(req *http.Request) bool {
411 if req.Method != "GET" {
412 t.Errorf("unexpected method: %#v", req)
413 return false
414 }
415 if req.URL.Path != "/namespaces/bar" {
416 t.Errorf("url doesn't contain name: %#v", req.URL)
417 return false
418 }
419 if req.URL.Query().Get(metav1.LabelSelectorQueryParam(corev1GV.String())) != labels.SelectorFromSet(labels.Set{"foo": "baz"}).String() {
420 t.Errorf("url doesn't contain query parameters: %#v", req.URL)
421 return false
422 }
423 return true
424 },
425 },
426 {
427 name: "test with",
428 Resp: &http.Response{
429 StatusCode: http.StatusOK,
430 Header: header(),
431 Body: objBody(&corev1.PodList{
432 Items: []corev1.Pod{{
433 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
434 },
435 },
436 }),
437 },
438 Req: func(req *http.Request) bool {
439 if req.Method != "GET" {
440 t.Errorf("unexpected method: %#v", req)
441 return false
442 }
443 if req.URL.Path != "/namespaces/bar" {
444 t.Errorf("url doesn't contain name: %#v", req.URL)
445 return false
446 }
447 if req.URL.Query().Get(metav1.LabelSelectorQueryParam(corev1GV.String())) != labels.SelectorFromSet(labels.Set{"foo": "baz"}).String() {
448 t.Errorf("url doesn't contain query parameters: %#v", req.URL)
449 return false
450 }
451 return true
452 },
453 },
454 }
455 for _, tt := range tests {
456 t.Run(tt.name, func(t *testing.T) {
457 client := &fake.RESTClient{
458 GroupVersion: corev1GV,
459 NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
460 Resp: tt.Resp,
461 Err: tt.HttpErr,
462 }
463 modifier := &Helper{
464 RESTClient: client,
465 NamespaceScoped: true,
466 }
467 obj, err := modifier.List("bar", corev1GV.String(), &metav1.ListOptions{LabelSelector: "foo=baz"})
468 if (err != nil) != tt.Err {
469 t.Errorf("unexpected error: %t %v", tt.Err, err)
470 }
471 if err != nil {
472 return
473 }
474 if obj.(*corev1.PodList).Items[0].Name != "foo" {
475 t.Errorf("unexpected object: %#v", obj)
476 }
477 if tt.Req != nil && !tt.Req(client.Req) {
478 t.Errorf("unexpected request: %#v", client.Req)
479 }
480 })
481 }
482 }
483
484 func TestHelperListSelectorCombination(t *testing.T) {
485 tests := []struct {
486 Name string
487 Err bool
488 ErrMsg string
489 FieldSelector string
490 LabelSelector string
491 }{
492 {
493 Name: "No selector",
494 Err: false,
495 },
496 {
497 Name: "Only Label Selector",
498 Err: false,
499 LabelSelector: "foo=baz",
500 },
501 {
502 Name: "Only Field Selector",
503 Err: false,
504 FieldSelector: "xyz=zyx",
505 },
506 {
507 Name: "Both Label and Field Selector",
508 Err: false,
509 LabelSelector: "foo=baz",
510 FieldSelector: "xyz=zyx",
511 },
512 }
513
514 resp := &http.Response{
515 StatusCode: http.StatusOK,
516 Header: header(),
517 Body: objBody(&corev1.PodList{
518 Items: []corev1.Pod{{
519 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
520 },
521 },
522 }),
523 }
524 client := &fake.RESTClient{
525 NegotiatedSerializer: scheme.Codecs,
526 Resp: resp,
527 Err: nil,
528 }
529 modifier := &Helper{
530 RESTClient: client,
531 NamespaceScoped: true,
532 }
533
534 for _, tt := range tests {
535 t.Run(tt.Name, func(t *testing.T) {
536 _, err := modifier.List("bar",
537 corev1GV.String(),
538 &metav1.ListOptions{LabelSelector: tt.LabelSelector, FieldSelector: tt.FieldSelector})
539 if tt.Err {
540 if err == nil {
541 t.Errorf("%q expected error: %q", tt.Name, tt.ErrMsg)
542 }
543 if err != nil && err.Error() != tt.ErrMsg {
544 t.Errorf("%q expected error: %q", tt.Name, tt.ErrMsg)
545 }
546 }
547 })
548 }
549 }
550
551 func TestHelperReplace(t *testing.T) {
552 expectPut := func(path string, req *http.Request) bool {
553 if req.Method != "PUT" {
554 t.Errorf("unexpected method: %#v", req)
555 return false
556 }
557 if req.URL.Path != path {
558 t.Errorf("unexpected url: %v", req.URL)
559 return false
560 }
561 return true
562 }
563
564 tests := []struct {
565 Name string
566 Resp *http.Response
567 HTTPClient *http.Client
568 HttpErr error
569 Overwrite bool
570 Object runtime.Object
571 Namespace string
572 NamespaceScoped bool
573 Subresource string
574
575 ExpectPath string
576 ExpectObject runtime.Object
577 Err bool
578 Req func(string, *http.Request) bool
579 }{
580 {
581 Name: "test1",
582 Namespace: "bar",
583 NamespaceScoped: true,
584 HttpErr: errors.New("failure"),
585 Err: true,
586 },
587 {
588 Name: "test2",
589 Namespace: "bar",
590 NamespaceScoped: true,
591 Object: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
592 Resp: &http.Response{
593 StatusCode: http.StatusNotFound,
594 Header: header(),
595 Body: objBody(&metav1.Status{Status: metav1.StatusFailure}),
596 },
597 Err: true,
598 },
599 {
600 Name: "test3",
601 Namespace: "bar",
602 NamespaceScoped: true,
603 Object: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
604 ExpectPath: "/namespaces/bar/foo",
605 ExpectObject: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
606 Resp: &http.Response{
607 StatusCode: http.StatusOK,
608 Header: header(),
609 Body: objBody(&metav1.Status{Status: metav1.StatusSuccess}),
610 },
611 Req: expectPut,
612 },
613
614 {
615 Name: "test4",
616 Namespace: "bar",
617 NamespaceScoped: true,
618 Object: &corev1.Pod{
619 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
620 Spec: V1DeepEqualSafePodSpec(),
621 },
622 ExpectPath: "/namespaces/bar/foo",
623 ExpectObject: &corev1.Pod{
624 ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"},
625 Spec: V1DeepEqualSafePodSpec(),
626 },
627 Overwrite: true,
628 HTTPClient: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
629 if req.Method == "PUT" {
630 return &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&metav1.Status{Status: metav1.StatusSuccess})}, nil
631 }
632 return &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}})}, nil
633 }),
634 Req: expectPut,
635 },
636
637 {
638 Name: "test5",
639 Object: &corev1.Node{
640 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
641 },
642 ExpectObject: &corev1.Node{
643 ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"},
644 },
645 Overwrite: true,
646 ExpectPath: "/foo",
647 HTTPClient: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
648 if req.Method == "PUT" {
649 return &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&metav1.Status{Status: metav1.StatusSuccess})}, nil
650 }
651 return &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}})}, nil
652 }),
653 Req: expectPut,
654 },
655 {
656 Name: "test6",
657 Namespace: "bar",
658 NamespaceScoped: true,
659 Object: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}},
660 ExpectPath: "/namespaces/bar/foo",
661 ExpectObject: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}},
662 Resp: &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&metav1.Status{Status: metav1.StatusSuccess})},
663 Req: expectPut,
664 },
665 {
666 Name: "test7 - with status subresource",
667 Namespace: "bar",
668 NamespaceScoped: true,
669 Subresource: "status",
670 Object: &corev1.Pod{
671 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
672 Status: V1DeepEqualSafePodStatus(),
673 },
674 ExpectPath: "/namespaces/bar/foo/status",
675 ExpectObject: &corev1.Pod{
676 ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"},
677 Status: V1DeepEqualSafePodStatus(),
678 },
679 Overwrite: true,
680 HTTPClient: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
681 if req.Method == "PUT" {
682 return &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&metav1.Status{Status: metav1.StatusSuccess})}, nil
683 }
684 return &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}})}, nil
685 }),
686 Req: expectPut,
687 },
688 }
689 for _, tt := range tests {
690 t.Run(tt.Name, func(t *testing.T) {
691 client := &fake.RESTClient{
692 GroupVersion: corev1GV,
693 NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
694 Client: tt.HTTPClient,
695 Resp: tt.Resp,
696 Err: tt.HttpErr,
697 }
698 modifier := &Helper{
699 RESTClient: client,
700 NamespaceScoped: tt.NamespaceScoped,
701 Subresource: tt.Subresource,
702 }
703 _, err := modifier.Replace(tt.Namespace, "foo", tt.Overwrite, tt.Object)
704 if (err != nil) != tt.Err {
705 t.Fatalf("unexpected error: %t %v", tt.Err, err)
706 }
707 if err != nil {
708 return
709 }
710 if tt.Req != nil && (client.Req == nil || !tt.Req(tt.ExpectPath, client.Req)) {
711 t.Fatalf("unexpected request: %#v", client.Req)
712 }
713 body, err := io.ReadAll(client.Req.Body)
714 if err != nil {
715 t.Fatalf("unexpected error: %#v", err)
716 }
717 expect := []byte{}
718 if tt.ExpectObject != nil {
719 expect = []byte(runtime.EncodeOrDie(corev1Codec, tt.ExpectObject))
720 }
721 if !reflect.DeepEqual(expect, body) {
722 t.Fatalf("unexpected body: %s", string(body))
723 }
724 })
725 }
726 }
727
728 func TestEnhanceListError(t *testing.T) {
729 podGVR := corev1.SchemeGroupVersion.WithResource(corev1.ResourcePods.String())
730 podSubject := podGVR.String()
731 tests := []struct {
732 name string
733 err error
734 opts metav1.ListOptions
735 subj string
736
737 expectedErr string
738 expectStatusErr bool
739 }{
740 {
741 name: "leaves resource expired error as is",
742 err: apierrors.NewResourceExpired("resourceversion too old"),
743 opts: metav1.ListOptions{},
744 subj: podSubject,
745 expectedErr: "resourceversion too old",
746 expectStatusErr: true,
747 }, {
748 name: "leaves unrecognized error as is",
749 err: errors.New("something went wrong"),
750 opts: metav1.ListOptions{},
751 subj: podSubject,
752 expectedErr: "something went wrong",
753 expectStatusErr: false,
754 }, {
755 name: "bad request StatusError without selectors",
756 err: apierrors.NewBadRequest("request is invalid"),
757 opts: metav1.ListOptions{},
758 subj: podSubject,
759 expectedErr: "Unable to list \"/v1, Resource=pods\": request is invalid",
760 expectStatusErr: true,
761 }, {
762 name: "bad request StatusError with selectors",
763 err: apierrors.NewBadRequest("request is invalid"),
764 opts: metav1.ListOptions{
765 LabelSelector: "a=b",
766 FieldSelector: ".spec.nodeName=foo",
767 },
768 subj: podSubject,
769 expectedErr: "Unable to find \"/v1, Resource=pods\" that match label selector \"a=b\", field selector \".spec.nodeName=foo\": request is invalid",
770 expectStatusErr: true,
771 }, {
772 name: "not found without selectors",
773 err: apierrors.NewNotFound(podGVR.GroupResource(), "foo"),
774 opts: metav1.ListOptions{},
775 subj: podSubject,
776 expectedErr: "Unable to list \"/v1, Resource=pods\": pods \"foo\" not found",
777 expectStatusErr: true,
778 }, {
779 name: "not found StatusError with selectors",
780 err: apierrors.NewNotFound(podGVR.GroupResource(), "foo"),
781 opts: metav1.ListOptions{
782 LabelSelector: "a=b",
783 FieldSelector: ".spec.nodeName=foo",
784 },
785 subj: podSubject,
786 expectedErr: "Unable to find \"/v1, Resource=pods\" that match label selector \"a=b\", field selector \".spec.nodeName=foo\": pods \"foo\" not found",
787 expectStatusErr: true,
788 }, {
789 name: "non StatusError without selectors",
790 err: fmt.Errorf("extra info: %w", apierrors.NewNotFound(podGVR.GroupResource(),
791 "foo")),
792 opts: metav1.ListOptions{},
793 subj: podSubject,
794 expectedErr: "Unable to list \"/v1, Resource=pods\": extra info: pods \"foo\" not found",
795 expectStatusErr: false,
796 }, {
797 name: "non StatusError with selectors",
798 err: fmt.Errorf("extra info: %w", apierrors.NewNotFound(podGVR.GroupResource(), "foo")),
799 opts: metav1.ListOptions{
800 LabelSelector: "a=b",
801 FieldSelector: ".spec.nodeName=foo",
802 },
803 subj: podSubject,
804 expectedErr: "Unable to find \"/v1, " +
805 "Resource=pods\" that match label selector \"a=b\", " +
806 "field selector \".spec.nodeName=foo\": extra info: pods \"foo\" not found",
807 expectStatusErr: false,
808 },
809 }
810 for _, tt := range tests {
811 t.Run(tt.name, func(t *testing.T) {
812 err := EnhanceListError(tt.err, tt.opts, tt.subj)
813 if err == nil {
814 t.Errorf("EnhanceListError did not return an error")
815 }
816 if err.Error() != tt.expectedErr {
817 t.Errorf("EnhanceListError() error = %q, expectedErr %q", err, tt.expectedErr)
818 }
819 if tt.expectStatusErr {
820 if _, ok := err.(*apierrors.StatusError); !ok {
821 t.Errorf("EnhanceListError incorrectly returned a non-StatusError: %v", err)
822 }
823 }
824 })
825 }
826 }
827
828 func TestFollowContinue(t *testing.T) {
829 var continueTokens []string
830 tests := []struct {
831 name string
832 initialOpts *metav1.ListOptions
833 tokensSeen []string
834 listFunc func(metav1.ListOptions) (runtime.Object, error)
835
836 expectedTokens []string
837 wantErr string
838 }{
839 {
840 name: "updates list options with continue token until list finished",
841 initialOpts: &metav1.ListOptions{},
842 listFunc: func(options metav1.ListOptions) (runtime.Object, error) {
843 continueTokens = append(continueTokens, options.Continue)
844 obj := corev1.PodList{}
845 switch options.Continue {
846 case "":
847 metadataAccessor.SetContinue(&obj, "abc")
848 case "abc":
849 metadataAccessor.SetContinue(&obj, "def")
850 case "def":
851 metadataAccessor.SetKind(&obj, "ListComplete")
852 }
853 return &obj, nil
854 },
855 expectedTokens: []string{"", "abc", "def"},
856 },
857 {
858 name: "stops looping if listFunc returns an error",
859 initialOpts: &metav1.ListOptions{},
860 listFunc: func(options metav1.ListOptions) (runtime.Object, error) {
861 continueTokens = append(continueTokens, options.Continue)
862 obj := corev1.PodList{}
863 switch options.Continue {
864 case "":
865 metadataAccessor.SetContinue(&obj, "abc")
866 case "abc":
867 return nil, fmt.Errorf("err from list func")
868 case "def":
869 metadataAccessor.SetKind(&obj, "ListComplete")
870 }
871 return &obj, nil
872 },
873 expectedTokens: []string{"", "abc"},
874 wantErr: "err from list func",
875 },
876 }
877 for _, tt := range tests {
878 continueTokens = []string{}
879 t.Run(tt.name, func(t *testing.T) {
880 err := FollowContinue(tt.initialOpts, tt.listFunc)
881 if tt.wantErr != "" {
882 if err == nil {
883 t.Fatalf("FollowContinue was expected to return an error and did not")
884 } else if err.Error() != tt.wantErr {
885 t.Fatalf("wanted error %q, got %q", tt.wantErr, err.Error())
886 }
887 } else {
888 if err != nil {
889 t.Errorf("FollowContinue failed: %v", tt.wantErr)
890 }
891 if !reflect.DeepEqual(continueTokens, tt.expectedTokens) {
892 t.Errorf("got token list %q, wanted %q", continueTokens, tt.expectedTokens)
893 }
894 }
895 })
896 }
897 }
898
View as plain text