Skip to content

Commit 5607108

Browse files
authored
Merge pull request kubernetes#127573 from benluddy/dynamic-golden-response-test
Add test for unintended changes to dynamic client response handling.
2 parents 4c24b93 + c8b1037 commit 5607108

File tree

11 files changed

+202
-0
lines changed

11 files changed

+202
-0
lines changed

staging/src/k8s.io/client-go/dynamic/golden_test.go

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ limitations under the License.
1717
package dynamic_test
1818

1919
import (
20+
"bufio"
2021
"context"
22+
"encoding/json"
2123
"fmt"
2224
"net"
2325
"net/http"
@@ -32,6 +34,7 @@ import (
3234
"github.com/google/go-cmp/cmp"
3335
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3436
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
37+
"k8s.io/apimachinery/pkg/runtime"
3538
"k8s.io/apimachinery/pkg/runtime/schema"
3639
"k8s.io/apimachinery/pkg/types"
3740
"k8s.io/client-go/dynamic"
@@ -246,3 +249,157 @@ func TestGoldenRequest(t *testing.T) {
246249
})
247250
}
248251
}
252+
253+
type RoundTripperFunc func(*http.Request) (*http.Response, error)
254+
255+
func (f RoundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) {
256+
return f(r)
257+
}
258+
259+
// TestGoldenResponse tests that the objects returned from dynamic client methods, given a fixed
260+
// HTTP response, are not changed unintentionally by changes to the client.
261+
func TestGoldenResponse(t *testing.T) {
262+
for _, tc := range []struct {
263+
name string
264+
response string // name of fixture containing a serialized HTTP1.1 response
265+
do func(t *testing.T, client dynamic.ResourceInterface) interface{}
266+
}{
267+
{
268+
name: "create",
269+
response: "nonlist",
270+
do: func(t *testing.T, client dynamic.ResourceInterface) interface{} {
271+
got, err := client.Create(context.Background(), &unstructured.Unstructured{}, metav1.CreateOptions{})
272+
if err != nil {
273+
t.Fatal(err)
274+
}
275+
276+
return got.UnstructuredContent()
277+
},
278+
},
279+
{
280+
name: "update",
281+
response: "nonlist",
282+
do: func(t *testing.T, client dynamic.ResourceInterface) interface{} {
283+
got, err := client.Update(context.Background(), &unstructured.Unstructured{Object: map[string]interface{}{"metadata": map[string]interface{}{"name": "name"}}}, metav1.UpdateOptions{})
284+
if err != nil {
285+
t.Fatal(err)
286+
}
287+
288+
return got.UnstructuredContent()
289+
},
290+
},
291+
{
292+
name: "updatestatus",
293+
response: "nonlist",
294+
do: func(t *testing.T, client dynamic.ResourceInterface) interface{} {
295+
got, err := client.UpdateStatus(context.Background(), &unstructured.Unstructured{Object: map[string]interface{}{"metadata": map[string]interface{}{"name": "name"}}}, metav1.UpdateOptions{})
296+
297+
if err != nil {
298+
t.Fatal(err)
299+
}
300+
301+
return got.UnstructuredContent()
302+
},
303+
},
304+
{
305+
name: "get",
306+
response: "nonlist",
307+
do: func(t *testing.T, client dynamic.ResourceInterface) interface{} {
308+
got, err := client.Get(context.Background(), "name", metav1.GetOptions{})
309+
if err != nil {
310+
t.Fatal(err)
311+
}
312+
313+
return got.UnstructuredContent()
314+
},
315+
},
316+
{
317+
name: "list",
318+
response: "list",
319+
do: func(t *testing.T, client dynamic.ResourceInterface) interface{} {
320+
got, err := client.List(context.Background(), metav1.ListOptions{})
321+
if err != nil {
322+
t.Fatal(err)
323+
}
324+
325+
return got.UnstructuredContent()
326+
327+
},
328+
},
329+
{
330+
name: "watch",
331+
response: "events",
332+
do: func(t *testing.T, client dynamic.ResourceInterface) interface{} {
333+
w, err := client.Watch(context.Background(), metav1.ListOptions{})
334+
if err != nil {
335+
t.Fatal(err)
336+
}
337+
defer w.Stop()
338+
339+
var got []interface{}
340+
for e := range w.ResultChan() {
341+
u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&e)
342+
if err != nil {
343+
t.Fatalf("failed to convert watch event to unstructured content: %v", err)
344+
}
345+
got = append(got, u)
346+
}
347+
348+
return got
349+
},
350+
},
351+
} {
352+
parentTestName := t.Name()
353+
t.Run(tc.name, func(t *testing.T) {
354+
client, err := dynamic.NewForConfig(&rest.Config{
355+
356+
Transport: RoundTripperFunc(func(request *http.Request) (*http.Response, error) {
357+
fd, err := os.Open(filepath.Join("testdata", filepath.FromSlash(parentTestName), "responses", tc.response))
358+
if err != nil {
359+
t.Fatal(err)
360+
}
361+
defer func() {
362+
if err := fd.Close(); err != nil {
363+
t.Fatal(err)
364+
}
365+
}()
366+
367+
response, err := http.ReadResponse(bufio.NewReader(fd), request)
368+
if err != nil {
369+
t.Fatal(err)
370+
}
371+
return response, nil
372+
}),
373+
})
374+
if err != nil {
375+
t.Fatal(err)
376+
}
377+
378+
got := tc.do(t, client.Resource(schema.GroupVersionResource{}))
379+
380+
path := filepath.Join("testdata", filepath.FromSlash(t.Name()))
381+
if os.Getenv("UPDATE_DYNAMIC_CLIENT_FIXTURES") == "true" {
382+
fixture, err := json.Marshal(got)
383+
if err != nil {
384+
t.Fatal(err)
385+
}
386+
if err := os.WriteFile(path, fixture, os.FileMode(0644)); err != nil {
387+
t.Fatalf("failed to update fixture: %v", err)
388+
}
389+
}
390+
fixture, err := os.ReadFile(path)
391+
if err != nil {
392+
t.Fatal(err)
393+
}
394+
395+
var want interface{}
396+
if err := json.Unmarshal(fixture, &want); err != nil {
397+
t.Fatal(err)
398+
}
399+
400+
if diff := cmp.Diff(want, got); diff != "" {
401+
t.Errorf("unexpected diff:\n%s", diff)
402+
}
403+
})
404+
}
405+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# disable end-of-line conversion for fixtures
2+
* -text
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"apiVersion":"v1","kind":"ServiceAccount","metadata":{"creationTimestamp":"2024-09-20T17:16:28Z","name":"foobar","namespace":"default","resourceVersion":"207","uid":"a6bb38b5-67ca-4d0d-bd87-da746a94c8a5"}}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"apiVersion":"v1","kind":"ServiceAccount","metadata":{"creationTimestamp":"2024-09-20T17:16:28Z","name":"foobar","namespace":"default","resourceVersion":"207","uid":"a6bb38b5-67ca-4d0d-bd87-da746a94c8a5"}}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"apiVersion":"v1","items":[{"apiVersion":"v1","kind":"ServiceAccount","metadata":{"creationTimestamp":"2024-09-20T17:16:28Z","name":"foobar","namespace":"default","resourceVersion":"207","uid":"a6bb38b5-67ca-4d0d-bd87-da746a94c8a5"}}],"kind":"ServiceAccountList","metadata":{"resourceVersion":"222"}}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
HTTP/1.1 200 OK
2+
Audit-Id: 1ef157ba-f535-4398-8813-80d86604fa8b
3+
Cache-Control: no-cache, private
4+
Content-Type: application/json
5+
X-Kubernetes-Pf-Flowschema-Uid: 6027abd0-aa72-4d3c-a2cb-42b534068f6d
6+
X-Kubernetes-Pf-Prioritylevel-Uid: f72ba9d7-6df9-4dd9-8720-be5485197492
7+
Date: Wed, 18 Sep 2024 15:17:06 GMT
8+
Transfer-Encoding: chunked
9+
10+
e9
11+
{"type":"ADDED","object":{"kind":"ServiceAccount","apiVersion":"v1","metadata":{"name":"foobar","namespace":"default","uid":"a1453396-7a5c-405a-93f9-ff44af8e7689","resourceVersion":"217","creationTimestamp":"2024-09-18T14:06:40Z"}}}
12+
13+
ba
14+
{"type":"BOOKMARK","object":{"kind":"ServiceAccount","apiVersion":"v1","metadata":{"resourceVersion":"964","creationTimestamp":null,"annotations":{"k8s.io/initial-events-end":"true"}}}}
15+
16+
0
17+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
HTTP/1.1 200 OK
2+
Audit-Id: 683f3c08-8e36-406a-ba2f-e327b7f107f1
3+
Cache-Control: no-cache, private
4+
Content-Type: application/json
5+
X-Kubernetes-Pf-Flowschema-Uid: f04daf25-4159-46d9-8547-499158737500
6+
X-Kubernetes-Pf-Prioritylevel-Uid: a8711f26-ec13-47dd-ae6c-27c345737e12
7+
Date: Fri, 20 Sep 2024 17:17:40 GMT
8+
Content-Length: 260
9+
10+
{"kind":"ServiceAccountList","apiVersion":"v1","metadata":{"resourceVersion":"222"},"items":[{"metadata":{"name":"foobar","namespace":"default","uid":"a6bb38b5-67ca-4d0d-bd87-da746a94c8a5","resourceVersion":"207","creationTimestamp":"2024-09-20T17:16:28Z"}}]}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
HTTP/1.1 200 OK
2+
Audit-Id: a050a5c6-5cc9-40b9-bf0e-7d178e72794a
3+
Cache-Control: no-cache, private
4+
Content-Type: application/json
5+
X-Kubernetes-Pf-Flowschema-Uid: f04daf25-4159-46d9-8547-499158737500
6+
X-Kubernetes-Pf-Prioritylevel-Uid: a8711f26-ec13-47dd-ae6c-27c345737e12
7+
Date: Fri, 20 Sep 2024 17:17:33 GMT
8+
Content-Length: 207
9+
10+
{"kind":"ServiceAccount","apiVersion":"v1","metadata":{"name":"foobar","namespace":"default","uid":"a6bb38b5-67ca-4d0d-bd87-da746a94c8a5","resourceVersion":"207","creationTimestamp":"2024-09-20T17:16:28Z"}}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"apiVersion":"v1","kind":"ServiceAccount","metadata":{"creationTimestamp":"2024-09-20T17:16:28Z","name":"foobar","namespace":"default","resourceVersion":"207","uid":"a6bb38b5-67ca-4d0d-bd87-da746a94c8a5"}}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"apiVersion":"v1","kind":"ServiceAccount","metadata":{"creationTimestamp":"2024-09-20T17:16:28Z","name":"foobar","namespace":"default","resourceVersion":"207","uid":"a6bb38b5-67ca-4d0d-bd87-da746a94c8a5"}}

0 commit comments

Comments
 (0)