k8s.io/client-go@v0.31.1/dynamic/golden_test.go (about)

     1  /*
     2  Copyright 2024 The Kubernetes 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 dynamic_test
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"net"
    23  	"net/http"
    24  	"net/http/httptest"
    25  	"net/http/httputil"
    26  	"net/url"
    27  	"os"
    28  	"path/filepath"
    29  	"testing"
    30  	"time"
    31  
    32  	"github.com/google/go-cmp/cmp"
    33  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    34  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    35  	"k8s.io/apimachinery/pkg/runtime/schema"
    36  	"k8s.io/apimachinery/pkg/types"
    37  	"k8s.io/client-go/dynamic"
    38  	"k8s.io/client-go/rest"
    39  )
    40  
    41  func TestGoldenRequest(t *testing.T) {
    42  	for _, tc := range []struct {
    43  		name string
    44  		do   func(context.Context, dynamic.Interface) error
    45  	}{
    46  		{
    47  			name: "create",
    48  			do: func(ctx context.Context, client dynamic.Interface) error {
    49  				_, err := client.Resource(schema.GroupVersionResource{Group: "flops", Version: "v1alpha1", Resource: "flips"}).Namespace("mops").Create(
    50  					ctx,
    51  					&unstructured.Unstructured{Object: map[string]interface{}{
    52  						"metadata": map[string]interface{}{"name": "mips"},
    53  					}},
    54  					metav1.CreateOptions{FieldValidation: "warn"},
    55  					"fin",
    56  				)
    57  				return err
    58  			},
    59  		},
    60  		{
    61  			name: "update",
    62  			do: func(ctx context.Context, client dynamic.Interface) error {
    63  				_, err := client.Resource(schema.GroupVersionResource{Group: "flops", Version: "v1alpha1", Resource: "flips"}).Namespace("mops").Update(
    64  					ctx,
    65  					&unstructured.Unstructured{Object: map[string]interface{}{
    66  						"metadata": map[string]interface{}{"name": "mips"},
    67  					}},
    68  					metav1.UpdateOptions{FieldValidation: "warn"},
    69  					"fin",
    70  				)
    71  				return err
    72  			},
    73  		},
    74  		{
    75  			name: "updatestatus",
    76  			do: func(ctx context.Context, client dynamic.Interface) error {
    77  				_, err := client.Resource(schema.GroupVersionResource{Group: "flops", Version: "v1alpha1", Resource: "flips"}).Namespace("mops").UpdateStatus(
    78  					ctx,
    79  					&unstructured.Unstructured{Object: map[string]interface{}{
    80  						"metadata": map[string]interface{}{"name": "mips"},
    81  					}},
    82  					metav1.UpdateOptions{FieldValidation: "warn"},
    83  				)
    84  				return err
    85  			},
    86  		},
    87  		{
    88  			name: "delete",
    89  			do: func(ctx context.Context, client dynamic.Interface) error {
    90  				return client.Resource(schema.GroupVersionResource{Group: "flops", Version: "v1alpha1", Resource: "flips"}).Namespace("mops").Delete(
    91  					ctx,
    92  					"mips",
    93  					metav1.DeleteOptions{DryRun: []string{metav1.DryRunAll}},
    94  					"fin",
    95  				)
    96  			},
    97  		},
    98  		{
    99  			name: "deletecollection",
   100  			do: func(ctx context.Context, client dynamic.Interface) error {
   101  				return client.Resource(schema.GroupVersionResource{Group: "flops", Version: "v1alpha1", Resource: "flips"}).Namespace("mops").DeleteCollection(
   102  					ctx,
   103  					metav1.DeleteOptions{DryRun: []string{metav1.DryRunAll}},
   104  					metav1.ListOptions{ResourceVersion: "42"},
   105  				)
   106  			},
   107  		},
   108  		{
   109  			name: "get",
   110  			do: func(ctx context.Context, client dynamic.Interface) error {
   111  				_, err := client.Resource(schema.GroupVersionResource{Group: "flops", Version: "v1alpha1", Resource: "flips"}).Namespace("mops").Get(
   112  					ctx,
   113  					"mips",
   114  					metav1.GetOptions{ResourceVersion: "42"},
   115  					"fin",
   116  				)
   117  				return err
   118  			},
   119  		},
   120  		{
   121  			name: "list",
   122  			do: func(ctx context.Context, client dynamic.Interface) error {
   123  				_, err := client.Resource(schema.GroupVersionResource{Group: "flops", Version: "v1alpha1", Resource: "flips"}).Namespace("mops").List(
   124  					ctx,
   125  					metav1.ListOptions{ResourceVersion: "42"},
   126  				)
   127  				return err
   128  			},
   129  		},
   130  		{
   131  			name: "watch",
   132  			do: func(ctx context.Context, client dynamic.Interface) error {
   133  				_, err := client.Resource(schema.GroupVersionResource{Group: "flops", Version: "v1alpha1", Resource: "flips"}).Namespace("mops").Watch(
   134  					ctx,
   135  					metav1.ListOptions{ResourceVersion: "42"},
   136  				)
   137  				return err
   138  			},
   139  		},
   140  		{
   141  			name: "patch",
   142  			do: func(ctx context.Context, client dynamic.Interface) error {
   143  				_, err := client.Resource(schema.GroupVersionResource{Group: "flops", Version: "v1alpha1", Resource: "flips"}).Namespace("mops").Patch(
   144  					ctx,
   145  					"mips",
   146  					types.StrategicMergePatchType,
   147  					[]byte("{\"foo\":\"bar\"}\n"),
   148  					metav1.PatchOptions{FieldManager: "baz"},
   149  					"fin",
   150  				)
   151  				return err
   152  			},
   153  		},
   154  		{
   155  			name: "apply",
   156  			do: func(ctx context.Context, client dynamic.Interface) error {
   157  				_, err := client.Resource(schema.GroupVersionResource{Group: "flops", Version: "v1alpha1", Resource: "flips"}).Namespace("mops").Apply(
   158  					ctx,
   159  					"mips",
   160  					&unstructured.Unstructured{Object: map[string]interface{}{
   161  						"metadata": map[string]interface{}{"name": "mips"},
   162  					}},
   163  					metav1.ApplyOptions{Force: true},
   164  					"fin",
   165  				)
   166  				return err
   167  			},
   168  		},
   169  		{
   170  			name: "applystatus",
   171  			do: func(ctx context.Context, client dynamic.Interface) error {
   172  				_, err := client.Resource(schema.GroupVersionResource{Group: "flops", Version: "v1alpha1", Resource: "flips"}).Namespace("mops").ApplyStatus(
   173  					ctx,
   174  					"mips",
   175  					&unstructured.Unstructured{Object: map[string]interface{}{
   176  						"metadata": map[string]interface{}{"name": "mips"},
   177  					}},
   178  					metav1.ApplyOptions{Force: true},
   179  				)
   180  				return err
   181  			},
   182  		},
   183  	} {
   184  		t.Run(tc.name, func(t *testing.T) {
   185  			handled := make(chan struct{})
   186  			srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   187  				defer close(handled)
   188  
   189  				got, err := httputil.DumpRequest(r, true)
   190  				if err != nil {
   191  					t.Fatal(err)
   192  				}
   193  
   194  				path := filepath.Join("testdata", filepath.FromSlash(t.Name()))
   195  
   196  				if os.Getenv("UPDATE_DYNAMIC_CLIENT_FIXTURES") == "true" {
   197  					err := os.WriteFile(path, got, os.FileMode(0755))
   198  					if err != nil {
   199  						t.Fatalf("failed to update fixture: %v", err)
   200  					}
   201  				}
   202  
   203  				want, err := os.ReadFile(path)
   204  				if err != nil {
   205  					t.Fatalf("failed to load fixture: %v", err)
   206  				}
   207  				if diff := cmp.Diff(got, want); diff != "" {
   208  					t.Errorf("unexpected difference from expected bytes:\n%s", diff)
   209  				}
   210  			}))
   211  			defer srv.Close()
   212  
   213  			client, err := dynamic.NewForConfig(&rest.Config{
   214  				Host:      "example.com",
   215  				UserAgent: "TestGoldenRequest",
   216  				Transport: &http.Transport{
   217  					// The client will send a static Host header while always
   218  					// connecting to the test server.
   219  					DialContext: func(ctx context.Context, network string, addr string) (net.Conn, error) {
   220  						u, err := url.Parse(srv.URL)
   221  						if err != nil {
   222  							return nil, fmt.Errorf("failed to parse test server url: %w", err)
   223  						}
   224  						return (&net.Dialer{}).DialContext(ctx, "tcp", u.Host)
   225  					},
   226  				},
   227  			})
   228  			if err != nil {
   229  				t.Fatal(err)
   230  			}
   231  
   232  			ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   233  			defer cancel()
   234  			if err := tc.do(ctx, client); err != nil {
   235  				// This test detects server-perceptible changes to the request. As
   236  				// long as the server receives the expected request, a non-nil error
   237  				// returned from a client method is not a failure.
   238  				t.Logf("client returned non-nil error: %v", err)
   239  			}
   240  
   241  			select {
   242  			case <-handled:
   243  			default:
   244  				t.Fatal("no request received")
   245  			}
   246  		})
   247  	}
   248  }