// roundTrip applies a single round-trip test to the given runtime object // using the given codec. The round-trip test ensures that an object can be // deep-copied and converted from internal -> versioned -> internal without // loss of data. func roundTrip(t *testing.T, codec runtime.Codec, item runtime.Object) { printer := spew.ConfigState{DisableMethods: true} original := item // deep copy the original object copied, err := api.Scheme.DeepCopy(item) if err != nil { panic(fmt.Sprintf("unable to copy: %v", err)) } item = copied.(runtime.Object) name := reflect.TypeOf(item).Elem().Name() // encode (serialize) the deep copy using the provided codec data, err := runtime.Encode(codec, item) if err != nil { if runtime.IsNotRegisteredError(err) { t.Logf("%v: not registered: %v (%s)", name, err, printer.Sprintf("%#v", item)) } else { t.Errorf("%v: %v (%s)", name, err, printer.Sprintf("%#v", item)) } return } // ensure that the deep copy is equal to the original; neither the deep // copy or conversion should alter the object if !api.Semantic.DeepEqual(original, item) { t.Errorf("0: %v: encode altered the object, diff: %v", name, diff.ObjectReflectDiff(original, item)) return } // decode (deserialize) the encoded data back into an object obj2, err := runtime.Decode(codec, data) if err != nil { t.Errorf("0: %v: %v\nCodec: %#v\nData: %s\nSource: %#v", name, err, codec, dataAsString(data), printer.Sprintf("%#v", item)) panic("failed") } // ensure that the object produced from decoding the encoded data is equal // to the original object if !api.Semantic.DeepEqual(original, obj2) { t.Errorf("\n1: %v: diff: %v\nCodec: %#v\nSource:\n\n%#v\n\nEncoded:\n\n%s\n\nFinal:\n\n%#v", name, diff.ObjectReflectDiff(item, obj2), codec, printer.Sprintf("%#v", item), dataAsString(data), printer.Sprintf("%#v", obj2)) return } // decode the encoded data into a new object (instead of letting the codec // create a new object) obj3 := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object) if err := runtime.DecodeInto(codec, data, obj3); err != nil { t.Errorf("2: %v: %v", name, err) return } // ensure that the new runtime object is equal to the original after being // decoded into if !api.Semantic.DeepEqual(item, obj3) { t.Errorf("3: %v: diff: %v\nCodec: %#v", name, diff.ObjectReflectDiff(item, obj3), codec) return } }
func TestDoRequestFailed(t *testing.T) { status := &metav1.Status{ Code: http.StatusNotFound, Status: metav1.StatusFailure, Reason: metav1.StatusReasonNotFound, Message: " \"\" not found", Details: &metav1.StatusDetails{}, } expectedBody, _ := runtime.Encode(testapi.Default.Codec(), status) fakeHandler := utiltesting.FakeHandler{ StatusCode: 404, ResponseBody: string(expectedBody), T: t, } testServer := httptest.NewServer(&fakeHandler) defer testServer.Close() c, err := restClient(testServer) if err != nil { t.Fatalf("unexpected error: %v", err) } err = c.Get().Do().Error() if err == nil { t.Errorf("unexpected non-error") } ss, ok := err.(errors.APIStatus) if !ok { t.Errorf("unexpected error type %v", err) } actual := ss.Status() if !reflect.DeepEqual(status, &actual) { t.Errorf("Unexpected mis-match: %s", diff.ObjectReflectDiff(status, &actual)) } }
func TestDoRawRequestFailed(t *testing.T) { status := &metav1.Status{ Code: http.StatusNotFound, Status: metav1.StatusFailure, Reason: metav1.StatusReasonNotFound, Message: "the server could not find the requested resource", Details: &metav1.StatusDetails{ Causes: []metav1.StatusCause{ {Type: metav1.CauseTypeUnexpectedServerResponse, Message: "unknown"}, }, }, } expectedBody, _ := runtime.Encode(testapi.Default.Codec(), status) fakeHandler := utiltesting.FakeHandler{ StatusCode: 404, ResponseBody: string(expectedBody), T: t, } testServer := httptest.NewServer(&fakeHandler) defer testServer.Close() c, err := restClient(testServer) if err != nil { t.Fatalf("unexpected error: %v", err) } body, err := c.Get().Do().Raw() if err == nil || body == nil { t.Errorf("unexpected non-error: %#v", body) } ss, ok := err.(errors.APIStatus) if !ok { t.Errorf("unexpected error type %v", err) } actual := ss.Status() if !reflect.DeepEqual(status, &actual) { t.Errorf("Unexpected mis-match: %s", diff.ObjectReflectDiff(status, &actual)) } }
func doDeepCopyTest(t *testing.T, kind schema.GroupVersionKind, f *fuzz.Fuzzer) { item, err := api.Scheme.New(kind) if err != nil { t.Fatalf("Could not create a %v: %s", kind, err) } f.Fuzz(item) itemCopy, err := api.Scheme.DeepCopy(item) if err != nil { t.Errorf("Could not deep copy a %v: %s", kind, err) return } if !reflect.DeepEqual(item, itemCopy) { t.Errorf("\nexpected: %#v\n\ngot: %#v\n\ndiff: %v", item, itemCopy, diff.ObjectReflectDiff(item, itemCopy)) } prefuzzData := &bytes.Buffer{} if err := api.Codecs.LegacyCodec(kind.GroupVersion()).Encode(item, prefuzzData); err != nil { t.Errorf("Could not encode a %v: %s", kind, err) return } // Refuzz the copy, which should have no effect on the original f.Fuzz(itemCopy) postfuzzData := &bytes.Buffer{} if err := api.Codecs.LegacyCodec(kind.GroupVersion()).Encode(item, postfuzzData); err != nil { t.Errorf("Could not encode a %v: %s", kind, err) return } if bytes.Compare(prefuzzData.Bytes(), postfuzzData.Bytes()) != 0 { t.Log(diff.StringDiff(prefuzzData.String(), postfuzzData.String())) t.Errorf("Fuzzing copy modified original of %#v", kind) return } }
// TODO: add a reflexive test that verifies that all SetDefaults functions are registered func TestDefaulting(t *testing.T) { // these are the known types with defaulters - you must add to this list if you add a top level defaulter typesWithDefaulting := map[schema.GroupVersionKind]struct{}{ {Group: "", Version: "v1", Kind: "ConfigMap"}: {}, {Group: "", Version: "v1", Kind: "ConfigMapList"}: {}, {Group: "", Version: "v1", Kind: "Endpoints"}: {}, {Group: "", Version: "v1", Kind: "EndpointsList"}: {}, {Group: "", Version: "v1", Kind: "Namespace"}: {}, {Group: "", Version: "v1", Kind: "NamespaceList"}: {}, {Group: "", Version: "v1", Kind: "Node"}: {}, {Group: "", Version: "v1", Kind: "NodeList"}: {}, {Group: "", Version: "v1", Kind: "PersistentVolume"}: {}, {Group: "", Version: "v1", Kind: "PersistentVolumeList"}: {}, {Group: "", Version: "v1", Kind: "PersistentVolumeClaim"}: {}, {Group: "", Version: "v1", Kind: "PersistentVolumeClaimList"}: {}, {Group: "", Version: "v1", Kind: "PodAttachOptions"}: {}, {Group: "", Version: "v1", Kind: "PodExecOptions"}: {}, {Group: "", Version: "v1", Kind: "Pod"}: {}, {Group: "", Version: "v1", Kind: "PodList"}: {}, {Group: "", Version: "v1", Kind: "PodTemplate"}: {}, {Group: "", Version: "v1", Kind: "PodTemplateList"}: {}, {Group: "", Version: "v1", Kind: "ReplicationController"}: {}, {Group: "", Version: "v1", Kind: "ReplicationControllerList"}: {}, {Group: "", Version: "v1", Kind: "Secret"}: {}, {Group: "", Version: "v1", Kind: "SecretList"}: {}, {Group: "", Version: "v1", Kind: "Service"}: {}, {Group: "", Version: "v1", Kind: "ServiceList"}: {}, {Group: "apps", Version: "v1beta1", Kind: "StatefulSet"}: {}, {Group: "apps", Version: "v1beta1", Kind: "StatefulSetList"}: {}, {Group: "autoscaling", Version: "v1", Kind: "HorizontalPodAutoscaler"}: {}, {Group: "autoscaling", Version: "v1", Kind: "HorizontalPodAutoscalerList"}: {}, {Group: "batch", Version: "v1", Kind: "Job"}: {}, {Group: "batch", Version: "v1", Kind: "JobList"}: {}, {Group: "batch", Version: "v2alpha1", Kind: "CronJob"}: {}, {Group: "batch", Version: "v2alpha1", Kind: "CronJobList"}: {}, {Group: "batch", Version: "v2alpha1", Kind: "Job"}: {}, {Group: "batch", Version: "v2alpha1", Kind: "JobList"}: {}, {Group: "batch", Version: "v2alpha1", Kind: "JobTemplate"}: {}, {Group: "batch", Version: "v2alpha1", Kind: "ScheduledJob"}: {}, {Group: "batch", Version: "v2alpha1", Kind: "ScheduledJobList"}: {}, {Group: "certificates.k8s.io", Version: "v1beta1", Kind: "CertificateSigningRequest"}: {}, {Group: "certificates.k8s.io", Version: "v1beta1", Kind: "CertificateSigningRequestList"}: {}, {Group: "componentconfig", Version: "v1alpha1", Kind: "KubeProxyConfiguration"}: {}, {Group: "componentconfig", Version: "v1alpha1", Kind: "KubeSchedulerConfiguration"}: {}, {Group: "componentconfig", Version: "v1alpha1", Kind: "KubeletConfiguration"}: {}, {Group: "kubeadm.k8s.io", Version: "v1alpha1", Kind: "MasterConfiguration"}: {}, // This object contains only int fields which currently breaks the defaulting test because // it's pretty stupid. Once we add non integer fields, we should uncomment this. // {Group: "kubeadm.k8s.io", Version: "v1alpha1", Kind: "NodeConfiguration"}: {}, {Group: "extensions", Version: "v1beta1", Kind: "DaemonSet"}: {}, {Group: "extensions", Version: "v1beta1", Kind: "DaemonSetList"}: {}, {Group: "extensions", Version: "v1beta1", Kind: "Deployment"}: {}, {Group: "extensions", Version: "v1beta1", Kind: "DeploymentList"}: {}, {Group: "extensions", Version: "v1beta1", Kind: "HorizontalPodAutoscaler"}: {}, {Group: "extensions", Version: "v1beta1", Kind: "HorizontalPodAutoscalerList"}: {}, {Group: "extensions", Version: "v1beta1", Kind: "ReplicaSet"}: {}, {Group: "extensions", Version: "v1beta1", Kind: "ReplicaSetList"}: {}, {Group: "rbac.authorization.k8s.io", Version: "v1alpha1", Kind: "ClusterRoleBinding"}: {}, {Group: "rbac.authorization.k8s.io", Version: "v1alpha1", Kind: "ClusterRoleBindingList"}: {}, {Group: "rbac.authorization.k8s.io", Version: "v1alpha1", Kind: "RoleBinding"}: {}, {Group: "rbac.authorization.k8s.io", Version: "v1alpha1", Kind: "RoleBindingList"}: {}, {Group: "rbac.authorization.k8s.io", Version: "v1beta1", Kind: "ClusterRoleBinding"}: {}, {Group: "rbac.authorization.k8s.io", Version: "v1beta1", Kind: "ClusterRoleBindingList"}: {}, {Group: "rbac.authorization.k8s.io", Version: "v1beta1", Kind: "RoleBinding"}: {}, {Group: "rbac.authorization.k8s.io", Version: "v1beta1", Kind: "RoleBindingList"}: {}, } f := fuzz.New().NilChance(.5).NumElements(1, 1).RandSource(rand.NewSource(1)) f.Funcs( func(s *runtime.RawExtension, c fuzz.Continue) {}, func(s *metav1.LabelSelector, c fuzz.Continue) { c.FuzzNoCustom(s) s.MatchExpressions = nil // need to fuzz this specially }, func(s *apiv1.ListOptions, c fuzz.Continue) { c.FuzzNoCustom(s) s.LabelSelector = "" // need to fuzz requirement strings specially s.FieldSelector = "" // need to fuzz requirement strings specially }, func(s *extensionsv1beta1.ScaleStatus, c fuzz.Continue) { c.FuzzNoCustom(s) s.TargetSelector = "" // need to fuzz requirement strings specially }, ) scheme := api.Scheme var testTypes orderedGroupVersionKinds for gvk := range scheme.AllKnownTypes() { if gvk.Version == runtime.APIVersionInternal { continue } testTypes = append(testTypes, gvk) } sort.Sort(testTypes) for _, gvk := range testTypes { _, expectedChanged := typesWithDefaulting[gvk] iter := 0 changedOnce := false for { if iter > *fuzzIters { if !expectedChanged || changedOnce { break } if iter > 300 { t.Errorf("expected %s to trigger defaulting due to fuzzing", gvk) break } // if we expected defaulting, continue looping until the fuzzer gives us one // at worst, we will timeout } iter++ src, err := scheme.New(gvk) if err != nil { t.Fatal(err) } f.Fuzz(src) src.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{}) original, err := scheme.DeepCopy(src) if err != nil { t.Fatal(err) } // get internal withDefaults, _ := scheme.DeepCopy(src) scheme.Default(withDefaults.(runtime.Object)) if !reflect.DeepEqual(original, withDefaults) { changedOnce = true if !expectedChanged { t.Errorf("{Group: \"%s\", Version: \"%s\", Kind: \"%s\"} did not expect defaults to be set - update expected or check defaulter registering: %s", gvk.Group, gvk.Version, gvk.Kind, diff.ObjectReflectDiff(original, withDefaults)) } } } } }
func TestConvertToVersion(t *testing.T) { testCases := []struct { scheme *runtime.Scheme in runtime.Object gv runtime.GroupVersioner same bool out runtime.Object errFn func(error) bool }{ // errors if the type is not registered in the scheme { scheme: GetTestScheme(), in: &UnknownType{}, errFn: func(err error) bool { return err != nil && runtime.IsNotRegisteredError(err) }, }, // errors if the group versioner returns no target { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: testGroupVersioner{}, errFn: func(err error) bool { return err != nil && strings.Contains(err.Error(), "is not suitable for converting") }, }, // converts to internal { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: schema.GroupVersion{Version: "__internal"}, out: &TestType1{A: "test"}, }, // prefers the best match { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: schema.GroupVersions{{Version: "__internal"}, {Version: "v1"}}, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, A: "test", }, }, // unversioned type returned as-is { scheme: GetTestScheme(), in: &UnversionedType{A: "test"}, gv: schema.GroupVersions{{Version: "v1"}}, same: true, out: &UnversionedType{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "UnversionedType"}, A: "test", }, }, // unversioned type returned when not included in the target types { scheme: GetTestScheme(), in: &UnversionedType{A: "test"}, gv: schema.GroupVersions{{Group: "other", Version: "v2"}}, same: true, out: &UnversionedType{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "UnversionedType"}, A: "test", }, }, // detected as already being in the target version { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: schema.GroupVersions{{Version: "v1"}}, same: true, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, A: "test", }, }, // detected as already being in the first target version { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: schema.GroupVersions{{Version: "v1"}, {Version: "__internal"}}, same: true, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, A: "test", }, }, // detected as already being in the first target version { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: schema.GroupVersions{{Version: "v1"}, {Version: "__internal"}}, same: true, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, A: "test", }, }, // the external type is registered in multiple groups, versions, and kinds, and can be targeted to all of them (1/3): different kind { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: testGroupVersioner{ok: true, target: schema.GroupVersionKind{Kind: "TestType3", Version: "v1"}}, same: true, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType3"}, A: "test", }, }, // the external type is registered in multiple groups, versions, and kinds, and can be targeted to all of them (2/3): different gv { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: testGroupVersioner{ok: true, target: schema.GroupVersionKind{Kind: "TestType3", Group: "custom", Version: "v1"}}, same: true, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "custom/v1", ObjectKind: "TestType3"}, A: "test", }, }, // the external type is registered in multiple groups, versions, and kinds, and can be targeted to all of them (3/3): different gvk { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: testGroupVersioner{ok: true, target: schema.GroupVersionKind{Group: "custom", Version: "v1", Kind: "TestType5"}}, same: true, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "custom/v1", ObjectKind: "TestType5"}, A: "test", }, }, // multi group versioner recognizes multiple groups and forces the output to a particular version, copies because version differs { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: "other", Version: "v2"}, schema.GroupKind{Group: "custom", Kind: "TestType3"}, schema.GroupKind{Kind: "TestType1"}), out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "other/v2", ObjectKind: "TestType1"}, A: "test", }, }, // multi group versioner recognizes multiple groups and forces the output to a particular version, copies because version differs { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: "other", Version: "v2"}, schema.GroupKind{Kind: "TestType1"}, schema.GroupKind{Group: "custom", Kind: "TestType3"}), out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "other/v2", ObjectKind: "TestType1"}, A: "test", }, }, // multi group versioner is unable to find a match when kind AND group don't match (there is no TestType1 kind in group "other", and no kind "TestType5" in the default group) { scheme: GetTestScheme(), in: &TestType1{A: "test"}, gv: runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: "custom", Version: "v1"}, schema.GroupKind{Group: "other"}, schema.GroupKind{Kind: "TestType5"}), errFn: func(err error) bool { return err != nil && strings.Contains(err.Error(), "is not suitable for converting") }, }, // multi group versioner recognizes multiple groups and forces the output to a particular version, performs no copy { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: "", Version: "v1"}, schema.GroupKind{Group: "custom", Kind: "TestType3"}, schema.GroupKind{Kind: "TestType1"}), same: true, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, A: "test", }, }, // multi group versioner recognizes multiple groups and forces the output to a particular version, performs no copy { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: "", Version: "v1"}, schema.GroupKind{Kind: "TestType1"}, schema.GroupKind{Group: "custom", Kind: "TestType3"}), same: true, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, A: "test", }, }, // group versioner can choose a particular target kind for a given input when kind is the same across group versions { scheme: GetTestScheme(), in: &TestType1{A: "test"}, gv: testGroupVersioner{ok: true, target: schema.GroupVersionKind{Version: "v1", Kind: "TestType3"}}, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType3"}, A: "test", }, }, // group versioner can choose a different kind { scheme: GetTestScheme(), in: &TestType1{A: "test"}, gv: testGroupVersioner{ok: true, target: schema.GroupVersionKind{Kind: "TestType5", Group: "custom", Version: "v1"}}, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "custom/v1", ObjectKind: "TestType5"}, A: "test", }, }, } for i, test := range testCases { original, _ := test.scheme.DeepCopy(test.in) out, err := test.scheme.ConvertToVersion(test.in, test.gv) switch { case test.errFn != nil: if !test.errFn(err) { t.Errorf("%d: unexpected error: %v", i, err) } continue case err != nil: t.Errorf("%d: unexpected error: %v", i, err) continue } if out == test.in { t.Errorf("%d: ConvertToVersion should always copy out: %#v", i, out) continue } if test.same { if !reflect.DeepEqual(original, test.in) { t.Errorf("%d: unexpected mutation of input: %s", i, diff.ObjectReflectDiff(original, test.in)) continue } if !reflect.DeepEqual(out, test.out) { t.Errorf("%d: unexpected out: %s", i, diff.ObjectReflectDiff(out, test.out)) continue } unsafe, err := test.scheme.UnsafeConvertToVersion(test.in, test.gv) if err != nil { t.Errorf("%d: unexpected error: %v", i, err) continue } if !reflect.DeepEqual(unsafe, test.out) { t.Errorf("%d: unexpected unsafe: %s", i, diff.ObjectReflectDiff(unsafe, test.out)) continue } if unsafe != test.in { t.Errorf("%d: UnsafeConvertToVersion should return same object: %#v", i, unsafe) continue } continue } if !reflect.DeepEqual(out, test.out) { t.Errorf("%d: unexpected out: %s", i, diff.ObjectReflectDiff(out, test.out)) continue } } }
func TestTransformUnstructuredError(t *testing.T) { testCases := []struct { Req *http.Request Res *http.Response Resource string Name string ErrFn func(error) bool Transformed error }{ { Resource: "foo", Name: "bar", Req: &http.Request{ Method: "POST", }, Res: &http.Response{ StatusCode: http.StatusConflict, Body: ioutil.NopCloser(bytes.NewReader(nil)), }, ErrFn: apierrors.IsAlreadyExists, }, { Resource: "foo", Name: "bar", Req: &http.Request{ Method: "PUT", }, Res: &http.Response{ StatusCode: http.StatusConflict, Body: ioutil.NopCloser(bytes.NewReader(nil)), }, ErrFn: apierrors.IsConflict, }, { Resource: "foo", Name: "bar", Req: &http.Request{}, Res: &http.Response{ StatusCode: http.StatusNotFound, Body: ioutil.NopCloser(bytes.NewReader(nil)), }, ErrFn: apierrors.IsNotFound, }, { Req: &http.Request{}, Res: &http.Response{ StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader(nil)), }, ErrFn: apierrors.IsBadRequest, }, { // status in response overrides transformed result Req: &http.Request{}, Res: &http.Response{StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"kind":"Status","apiVersion":"v1","status":"Failure","code":404}`)))}, ErrFn: apierrors.IsBadRequest, Transformed: &apierrors.StatusError{ ErrStatus: metav1.Status{Status: metav1.StatusFailure, Code: http.StatusNotFound}, }, }, { // successful status is ignored Req: &http.Request{}, Res: &http.Response{StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"kind":"Status","apiVersion":"v1","status":"Success","code":404}`)))}, ErrFn: apierrors.IsBadRequest, }, { // empty object does not change result Req: &http.Request{}, Res: &http.Response{StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`)))}, ErrFn: apierrors.IsBadRequest, }, { // we default apiVersion for backwards compatibility with old clients // TODO: potentially remove in 1.7 Req: &http.Request{}, Res: &http.Response{StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"kind":"Status","status":"Failure","code":404}`)))}, ErrFn: apierrors.IsBadRequest, Transformed: &apierrors.StatusError{ ErrStatus: metav1.Status{Status: metav1.StatusFailure, Code: http.StatusNotFound}, }, }, { // we do not default kind Req: &http.Request{}, Res: &http.Response{StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"status":"Failure","code":404}`)))}, ErrFn: apierrors.IsBadRequest, }, } for i, testCase := range testCases { r := &Request{ content: defaultContentConfig(), serializers: defaultSerializers(), resourceName: testCase.Name, resource: testCase.Resource, } result := r.transformResponse(testCase.Res, testCase.Req) err := result.err if !testCase.ErrFn(err) { t.Errorf("unexpected error: %v", err) continue } if !apierrors.IsUnexpectedServerError(err) { t.Errorf("%d: unexpected error type: %v", i, err) } if len(testCase.Name) != 0 && !strings.Contains(err.Error(), testCase.Name) { t.Errorf("unexpected error string: %s", err) } if len(testCase.Resource) != 0 && !strings.Contains(err.Error(), testCase.Resource) { t.Errorf("unexpected error string: %s", err) } // verify Error() properly transforms the error transformed := result.Error() expect := testCase.Transformed if expect == nil { expect = err } if !reflect.DeepEqual(expect, transformed) { t.Errorf("%d: unexpected Error(): %s", i, diff.ObjectReflectDiff(expect, transformed)) } // verify result.Get properly transforms the error if _, err := result.Get(); !reflect.DeepEqual(expect, err) { t.Errorf("%d: unexpected error on Get(): %s", i, diff.ObjectReflectDiff(expect, err)) } // verify result.Into properly handles the error if err := result.Into(&api.Pod{}); !reflect.DeepEqual(expect, err) { t.Errorf("%d: unexpected error on Into(): %s", i, diff.ObjectReflectDiff(expect, err)) } // verify result.Raw leaves the error in the untransformed state if _, err := result.Raw(); !reflect.DeepEqual(result.err, err) { t.Errorf("%d: unexpected error on Raw(): %s", i, diff.ObjectReflectDiff(expect, err)) } } }
func doRoundTrip(t *testing.T, group testapi.TestGroup, kind string) { // We do fuzzing on the internal version of the object, and only then // convert to the external version. This is because custom fuzzing // function are only supported for internal objects. internalObj, err := api.Scheme.New(group.InternalGroupVersion().WithKind(kind)) if err != nil { t.Fatalf("Couldn't create internal object %v: %v", kind, err) } seed := rand.Int63() apitesting.FuzzerFor(t, group.InternalGroupVersion(), rand.NewSource(seed)). // We are explicitly overwriting custom fuzzing functions, to ensure // that InitContainers and their statuses are not generated. This is // because in thise test we are simply doing json operations, in which // those disappear. Funcs( func(s *api.PodSpec, c fuzz.Continue) { c.FuzzNoCustom(s) s.InitContainers = nil }, func(s *api.PodStatus, c fuzz.Continue) { c.FuzzNoCustom(s) s.InitContainerStatuses = nil }, ).Fuzz(internalObj) item, err := api.Scheme.New(group.GroupVersion().WithKind(kind)) if err != nil { t.Fatalf("Couldn't create external object %v: %v", kind, err) } if err := api.Scheme.Convert(internalObj, item, nil); err != nil { t.Fatalf("Conversion for %v failed: %v", kind, err) } data, err := json.Marshal(item) if err != nil { t.Errorf("Error when marshaling object: %v", err) return } unstr := make(map[string]interface{}) err = json.Unmarshal(data, &unstr) if err != nil { t.Errorf("Error when unmarshaling to unstructured: %v", err) return } data, err = json.Marshal(unstr) if err != nil { t.Errorf("Error when marshaling unstructured: %v", err) return } unmarshalledObj := reflect.New(reflect.TypeOf(item).Elem()).Interface() err = json.Unmarshal(data, &unmarshalledObj) if err != nil { t.Errorf("Error when unmarshaling to object: %v", err) return } if !api.Semantic.DeepEqual(item, unmarshalledObj) { t.Errorf("Object changed during JSON operations, diff: %v", diff.ObjectReflectDiff(item, unmarshalledObj)) return } // TODO; Enable the following part of test once to/from unstructured // format conversions are implemented. /* newUnstr := make(map[string]interface{}) err = unstructured.NewConverter().ToUnstructured(item, &newUnstr) if err != nil { t.Errorf("ToUnstructured failed: %v", err) return } newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object) err = unstructured.NewConverter().FromUnstructured(newUnstr, newObj) if err != nil { t.Errorf("FromUnstructured failed: %v", err) return } if !api.Semantic.DeepEqual(item, newObj) { t.Errorf("Object changed, diff: %v", diff.ObjectReflectDiff(item, newObj)) } */ }