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 }