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  }