knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/apis/duck/typed_test.go (about) 1 /* 2 Copyright 2018 The Knative Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package duck_test 18 19 import ( 20 "context" 21 "encoding/json" 22 "errors" 23 "testing" 24 "time" 25 26 "github.com/google/go-cmp/cmp" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 29 "k8s.io/apimachinery/pkg/runtime" 30 "k8s.io/apimachinery/pkg/runtime/schema" 31 "k8s.io/apimachinery/pkg/watch" 32 "k8s.io/client-go/dynamic" 33 "k8s.io/client-go/dynamic/fake" 34 35 "knative.dev/pkg/apis/duck" 36 duckv1alpha1 "knative.dev/pkg/apis/duck/v1alpha1" 37 . "knative.dev/pkg/testing" 38 ) 39 40 func TestSimpleList(t *testing.T) { 41 scheme := runtime.NewScheme() 42 AddToScheme(scheme) 43 duckv1alpha1.AddToScheme(scheme) 44 45 namespace, name, want := "foo", "bar", "my_hostname" 46 47 // Despite the signature allowing `...runtime.Object`, this method 48 // will not work properly unless the passed objects are `unstructured.Unstructured` 49 client := fake.NewSimpleDynamicClient(scheme, &unstructured.Unstructured{ 50 Object: map[string]interface{}{ 51 "apiVersion": "pkg.knative.dev/v2", 52 "kind": "Resource", 53 "metadata": map[string]interface{}{ 54 "namespace": namespace, 55 "name": name, 56 }, 57 "status": map[string]interface{}{ 58 "address": map[string]interface{}{ 59 "hostname": want, 60 }, 61 }, 62 }, 63 }) 64 65 ctx, cancel := context.WithCancel(context.Background()) 66 defer cancel() 67 68 tif := &duck.TypedInformerFactory{ 69 Client: client, 70 Type: &duckv1alpha1.AddressableType{}, 71 ResyncPeriod: 1 * time.Second, 72 StopChannel: ctx.Done(), 73 } 74 75 // This hangs without: 76 // https://github.com/kubernetes/kubernetes/pull/68552 77 _, lister, err := tif.Get(ctx, SchemeGroupVersion.WithResource("resources")) 78 if err != nil { 79 t.Fatal("Get() =", err) 80 } 81 82 elt, err := lister.ByNamespace(namespace).Get(name) 83 if err != nil { 84 t.Fatal("Get() =", err) 85 } 86 87 got, ok := elt.(*duckv1alpha1.AddressableType) 88 if !ok { 89 t.Fatalf("Get() = %T, wanted *duckv1alpha1.AddressableType", elt) 90 } 91 92 if gotHostname := got.Status.Address.Hostname; gotHostname != want { 93 t.Errorf("Get().Status.Address.Hostname = %v, wanted %v", gotHostname, want) 94 } 95 96 // TODO(mattmoor): Access through informer 97 } 98 99 func TestInvalidResource(t *testing.T) { 100 client := &invalidResourceClient{} 101 stopCh := make(chan struct{}) 102 defer close(stopCh) 103 104 tif := &duck.TypedInformerFactory{ 105 Client: client, 106 Type: &duckv1alpha1.AddressableType{}, 107 ResyncPeriod: 1 * time.Second, 108 StopChannel: stopCh, 109 } 110 111 _, _, got := tif.Get(context.Background(), SchemeGroupVersion.WithResource("resources")) 112 113 if !errors.Is(got, errTest) { 114 t.Errorf("Error = %v, want: %v", got, errTest) 115 } 116 } 117 118 func TestAsStructuredWatcherNestedError(t *testing.T) { 119 want := errors.New("this is what we expect") 120 nwf := func(ctx context.Context, lo metav1.ListOptions) (watch.Interface, error) { 121 return nil, want 122 } 123 124 ctx := context.Background() 125 wf := duck.AsStructuredWatcher(nwf, &duckv1alpha1.AddressableType{}) 126 127 _, got := wf(ctx, metav1.ListOptions{}) 128 if !errors.Is(got, want) { 129 t.Errorf("WatchFunc() = %v, wanted %v", got, want) 130 } 131 } 132 133 func TestAsStructuredWatcherClosedChannel(t *testing.T) { 134 nwf := func(ctx context.Context, lo metav1.ListOptions) (watch.Interface, error) { 135 return watch.NewEmptyWatch(), nil 136 } 137 138 ctx := context.Background() 139 wf := duck.AsStructuredWatcher(nwf, &duckv1alpha1.AddressableType{}) 140 141 wi, err := wf(ctx, metav1.ListOptions{}) 142 if err != nil { 143 t.Error("WatchFunc() =", err) 144 } 145 146 ch := wi.ResultChan() 147 148 x, ok := <-ch 149 if ok { 150 t.Errorf("<-ch = %v, wanted closed", x) 151 } 152 } 153 154 func TestAsStructuredWatcherPassThru(t *testing.T) { 155 unstructuredCh := make(chan watch.Event) 156 nwf := func(ctx context.Context, lo metav1.ListOptions) (watch.Interface, error) { 157 return watch.NewProxyWatcher(unstructuredCh), nil 158 } 159 160 ctx := context.Background() 161 wf := duck.AsStructuredWatcher(nwf, &duckv1alpha1.AddressableType{}) 162 163 wi, err := wf(ctx, metav1.ListOptions{}) 164 if err != nil { 165 t.Error("WatchFunc() =", err) 166 } 167 defer wi.Stop() 168 ch := wi.ResultChan() 169 170 // Don't expect a message yet. 171 select { 172 case x, ok := <-ch: 173 t.Errorf("Saw unexpected message on channel: %v, %v.", x, ok) 174 case <-time.After(100 * time.Millisecond): 175 // Expected path. 176 } 177 178 want := watch.Added 179 unstructuredCh <- watch.Event{ 180 Type: want, 181 Object: &unstructured.Unstructured{}, 182 } 183 184 // Expect a message when we send one though. 185 select { 186 case x, ok := <-ch: 187 if !ok { 188 t.Fatal("<-ch = closed, wanted *duckv1alpha1.AddressableType{}") 189 } 190 if got := x.Type; got != want { 191 t.Errorf("x.Type = %v, wanted %v", got, want) 192 } 193 if _, ok := x.Object.(*duckv1alpha1.AddressableType); !ok { 194 t.Errorf("<-ch = %T, wanted %T", x, &duckv1alpha1.AddressableType{}) 195 } 196 case <-time.After(100 * time.Millisecond): 197 t.Error("Didn't see expected message on channel.") 198 } 199 } 200 201 func TestAsStructuredWatcherPassThruErrors(t *testing.T) { 202 unstructuredCh := make(chan watch.Event) 203 nwf := func(ctx context.Context, lo metav1.ListOptions) (watch.Interface, error) { 204 return watch.NewProxyWatcher(unstructuredCh), nil 205 } 206 207 ctx := context.Background() 208 wf := duck.AsStructuredWatcher(nwf, &duckv1alpha1.AddressableType{}) 209 210 wi, err := wf(ctx, metav1.ListOptions{}) 211 if err != nil { 212 t.Error("WatchFunc() =", err) 213 } 214 defer wi.Stop() 215 ch := wi.ResultChan() 216 217 want := watch.Event{ 218 Type: watch.Error, 219 Object: &metav1.Status{ 220 Code: 42, 221 }, 222 } 223 unstructuredCh <- want 224 225 // Expect a message when we send one though. 226 select { 227 case got, ok := <-ch: 228 if !ok { 229 t.Fatal("<-ch = closed, wanted *metav1.Status{}") 230 } 231 if diff := cmp.Diff(want, got); diff != "" { 232 t.Error("<-ch (-want, +got) =", diff) 233 } 234 case <-time.After(100 * time.Millisecond): 235 t.Error("Didn't see expected message on channel.") 236 } 237 } 238 239 func TestAsStructuredWatcherErrorConverting(t *testing.T) { 240 unstructuredCh := make(chan watch.Event) 241 nwf := func(ctx context.Context, lo metav1.ListOptions) (watch.Interface, error) { 242 return watch.NewProxyWatcher(unstructuredCh), nil 243 } 244 245 ctx := context.Background() 246 wf := duck.AsStructuredWatcher(nwf, &badObject{}) 247 248 wi, err := wf(ctx, metav1.ListOptions{}) 249 if err != nil { 250 t.Error("WatchFunc() =", err) 251 } 252 defer wi.Stop() 253 ch := wi.ResultChan() 254 255 unstructuredCh <- watch.Event{ 256 Type: watch.Added, 257 Object: &unstructured.Unstructured{ 258 Object: map[string]interface{}{ 259 "foo": "bar", 260 }, 261 }, 262 } 263 264 // Expect a message when we send one though. 265 select { 266 case x, ok := <-ch: 267 if !ok { 268 t.Fatal("<-ch = closed, wanted *duckv1alpha1.Generational{}") 269 } 270 if got, want := x.Type, watch.Error; got != want { 271 t.Errorf("<-ch = %v, wanted %v", got, want) 272 } 273 if status, ok := x.Object.(*metav1.Status); !ok { 274 t.Errorf("<-ch = %T, wanted %T", x, &metav1.Status{}) 275 } else if got, want := status.Message, errNoUnmarshal.Error(); got != want { 276 t.Errorf("<-ch = %v, wanted %v", got, want) 277 } 278 case <-time.After(100 * time.Millisecond): 279 t.Error("Didn't see expected message on channel.") 280 } 281 } 282 283 var errNoUnmarshal = errors.New("this cannot be unmarshalled") 284 285 type badObject struct { 286 Foo doNotUnmarshal `json:"foo"` 287 } 288 289 type doNotUnmarshal struct{} 290 291 var _ json.Unmarshaler = (*doNotUnmarshal)(nil) 292 293 func (*doNotUnmarshal) UnmarshalJSON([]byte) error { 294 return errNoUnmarshal 295 } 296 297 func (bo *badObject) GetObjectKind() schema.ObjectKind { 298 return &metav1.TypeMeta{} 299 } 300 301 func (bo *badObject) DeepCopyObject() runtime.Object { 302 return &badObject{} 303 } 304 305 var errTest = errors.New("failed to get list") 306 307 type invalidResourceClient struct { 308 *fake.FakeDynamicClient 309 } 310 311 func (*invalidResourceClient) Resource(resource schema.GroupVersionResource) dynamic.NamespaceableResourceInterface { 312 return &invalidResource{} 313 } 314 315 type invalidResource struct { 316 dynamic.NamespaceableResourceInterface 317 } 318 319 func (*invalidResource) List(ctx context.Context, options metav1.ListOptions) (*unstructured.UnstructuredList, error) { 320 return nil, errTest 321 }