Skip to content

Commit c02624d

Browse files
committed
add JSONRoundTripTestCase and add a Test*RoundTrip for every spec.* type which needs it
adds subtest `UnmarshalEmbedded` that positively shows that there is at least one property per embedded field also does not use field labels, so compiler errors are thrown in event any are missing
1 parent 6019037 commit c02624d

File tree

14 files changed

+576
-48
lines changed

14 files changed

+576
-48
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
Copyright 2022 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 jsontesting
18+
19+
import (
20+
"encoding/json"
21+
"fmt"
22+
"reflect"
23+
"strings"
24+
25+
"github.com/google/go-cmp/cmp"
26+
)
27+
28+
type RoundTripTestCase struct {
29+
Name string
30+
JSON string
31+
Object json.Marshaler
32+
33+
// An error that is expected when `Object` is marshalled to json
34+
// If `Object` does not exist, then it is inferred from the provided JSON
35+
ExpectedMarshalError string
36+
37+
// An error that is expected when the provided JSON is unmarshalled
38+
// If `JSON` does not exist then this it is inferred from the provided `Object`
39+
ExpectedUnmarshalError string
40+
}
41+
42+
func (t RoundTripTestCase) RoundTripTest(example json.Unmarshaler) error {
43+
var jsonBytes []byte
44+
var err error
45+
46+
// Tests whether the provided error matches the given pattern, and says
47+
// whether the test is finished, and the error to return
48+
expectError := func(e error, name string, expected string) (testFinished bool, err error) {
49+
if len(expected) > 0 {
50+
if e == nil || !strings.Contains(e.Error(), expected) {
51+
return true, fmt.Errorf("expected %v error containing substring: '%s'. but got actual error '%v'", name, expected, e)
52+
}
53+
54+
// If an error was expected and achieved, we stop the test
55+
// since it cannot be continued. But the return nil error since it
56+
// was expected.
57+
return true, nil
58+
} else if e != nil {
59+
return true, fmt.Errorf("unexpected %v error: %w", name, e)
60+
}
61+
62+
return false, nil
63+
}
64+
65+
// If user did not provide JSON and instead provided Object, infer JSON
66+
// from the provided object.
67+
if len(t.JSON) == 0 {
68+
jsonBytes, err = json.Marshal(t.Object)
69+
if testFinished, err := expectError(err, "marshal", t.ExpectedMarshalError); testFinished {
70+
return err
71+
}
72+
} else {
73+
jsonBytes = []byte(t.JSON)
74+
}
75+
76+
err = example.UnmarshalJSON(jsonBytes)
77+
if testFinished, err := expectError(err, "unmarshal", t.ExpectedUnmarshalError); testFinished {
78+
return err
79+
}
80+
81+
if t.Object != nil && !reflect.DeepEqual(t.Object, example) {
82+
return fmt.Errorf("test case expected to unmarshal to specific value: %v", cmp.Diff(t.Object, example))
83+
}
84+
85+
reEncoded, err := json.MarshalIndent(example, "", " ")
86+
if err != nil {
87+
return fmt.Errorf("failed to marshal decoded value: %w", err)
88+
}
89+
90+
// Check expected marshal error if it has not yet been checked
91+
// (for case where JSON is provided, and object is not)
92+
if testFinished, err := expectError(err, "marshal", t.ExpectedMarshalError); testFinished {
93+
return err
94+
}
95+
// Marshal both re-encoded, and original JSON into interface
96+
// to compare them without ordering issues
97+
var expected map[string]interface{}
98+
var actual map[string]interface{}
99+
100+
if err = json.Unmarshal(jsonBytes, &expected); err != nil {
101+
return fmt.Errorf("failed to unmarshal test json: %w", err)
102+
}
103+
104+
if err = json.Unmarshal(reEncoded, &actual); err != nil {
105+
return fmt.Errorf("failed to unmarshal actual data: %w", err)
106+
}
107+
108+
if !reflect.DeepEqual(expected, actual) {
109+
return fmt.Errorf("expected equal values: %v", cmp.Diff(expected, actual))
110+
}
111+
112+
return nil
113+
}

pkg/validation/spec/header_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/google/go-cmp/cmp"
2323
"github.com/stretchr/testify/assert"
2424
"github.com/stretchr/testify/require"
25+
jsontesting "k8s.io/kube-openapi/pkg/util/jsontesting"
2526
)
2627

2728
func float64Ptr(f float64) *float64 {

pkg/validation/spec/info_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import (
1919
"testing"
2020

2121
"github.com/stretchr/testify/assert"
22+
"github.com/stretchr/testify/require"
23+
jsontesting "k8s.io/kube-openapi/pkg/util/jsontesting"
2224
)
2325

2426
const infoJSON = `{
@@ -68,3 +70,35 @@ func TestIntegrationInfo_Deserialize(t *testing.T) {
6870
assert.EqualValues(t, info, actual)
6971
}
7072
}
73+
74+
func TestInfoRoundTrip(t *testing.T) {
75+
cases := []jsontesting.RoundTripTestCase{
76+
{
77+
// Show at least one field from each embededd struct sitll allows
78+
// roundtrips successfully
79+
Name: "UnmarshalEmbedded",
80+
JSON: `{
81+
"x-framework": "swagger-go",
82+
"description": "the description of this object"
83+
}`,
84+
Object: &Info{
85+
VendorExtensible{Extensions{
86+
"x-framework": "swagger-go",
87+
}},
88+
InfoProps{
89+
Description: "the description of this object",
90+
},
91+
},
92+
}, {
93+
Name: "BasicCase",
94+
JSON: infoJSON,
95+
Object: &info,
96+
},
97+
}
98+
99+
for _, tcase := range cases {
100+
t.Run(tcase.Name, func(t *testing.T) {
101+
require.NoError(t, tcase.RoundTripTest(&Info{}))
102+
})
103+
}
104+
}

pkg/validation/spec/items_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import (
1919
"testing"
2020

2121
"github.com/stretchr/testify/assert"
22+
"github.com/stretchr/testify/require"
23+
jsontesting "k8s.io/kube-openapi/pkg/util/jsontesting"
2224
)
2325

2426
var items = Items{
@@ -79,3 +81,41 @@ func TestIntegrationItems(t *testing.T) {
7981

8082
assertParsesJSON(t, itemsJSON, items)
8183
}
84+
85+
func TestItemsRoundTrip(t *testing.T) {
86+
cases := []jsontesting.RoundTripTestCase{
87+
{
88+
// Show at least one field from each embededd struct sitll allows
89+
// roundtrips successfully
90+
Name: "UnmarshalEmbedded",
91+
JSON: `{
92+
"$ref": "/components/my.cool.Schema",
93+
"pattern": "x-^",
94+
"type": "string",
95+
"x-framework": "swagger-go"
96+
}`,
97+
Object: &Items{
98+
Refable{MustCreateRef("/components/my.cool.Schema")},
99+
CommonValidations{
100+
Pattern: "x-^",
101+
},
102+
SimpleSchema{
103+
Type: "string",
104+
},
105+
VendorExtensible{Extensions{
106+
"x-framework": "swagger-go",
107+
}},
108+
},
109+
}, {
110+
Name: "BasicCase",
111+
JSON: itemsJSON,
112+
Object: &items,
113+
},
114+
}
115+
116+
for _, tcase := range cases {
117+
t.Run(tcase.Name, func(t *testing.T) {
118+
require.NoError(t, tcase.RoundTripTest(&Items{}))
119+
})
120+
}
121+
}

pkg/validation/spec/operation_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import (
1919
"testing"
2020

2121
"github.com/stretchr/testify/assert"
22+
"github.com/stretchr/testify/require"
23+
jsontesting "k8s.io/kube-openapi/pkg/util/jsontesting"
2224
)
2325

2426
var operation = Operation{
@@ -83,3 +85,35 @@ func TestIntegrationOperation(t *testing.T) {
8385

8486
assertParsesJSON(t, operationJSON, operation)
8587
}
88+
89+
func TestOperationRoundtrip(t *testing.T) {
90+
cases := []jsontesting.RoundTripTestCase{
91+
{
92+
// Show at least one field from each embededd struct sitll allows
93+
// roundtrips successfully
94+
Name: "UnmarshalEmbedded",
95+
JSON: `{
96+
"description": "a cool description",
97+
"x-framework": "swagger-go"
98+
}`,
99+
Object: &Operation{
100+
VendorExtensible{Extensions{
101+
"x-framework": "swagger-go",
102+
}},
103+
OperationProps{
104+
Description: "a cool description",
105+
},
106+
},
107+
}, {
108+
Name: "BasicCase",
109+
JSON: operationJSON,
110+
Object: &operation,
111+
},
112+
}
113+
114+
for _, tcase := range cases {
115+
t.Run(tcase.Name, func(t *testing.T) {
116+
require.NoError(t, tcase.RoundTripTest(&Operation{}))
117+
})
118+
}
119+
}

pkg/validation/spec/parameters_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import (
1919
"testing"
2020

2121
"github.com/stretchr/testify/assert"
22+
"github.com/stretchr/testify/require"
23+
jsontesting "k8s.io/kube-openapi/pkg/util/jsontesting"
2224
)
2325

2426
var parameter = Parameter{
@@ -97,3 +99,45 @@ func TestIntegrationParameter(t *testing.T) {
9799

98100
assertParsesJSON(t, parameterJSON, parameter)
99101
}
102+
103+
func TestParameterRoundtrip(t *testing.T) {
104+
cases := []jsontesting.RoundTripTestCase{
105+
{
106+
// Show at least one field from each embededd struct sitll allows
107+
// roundtrips successfully
108+
Name: "UnmarshalEmbedded",
109+
JSON: `{
110+
"$ref": "/components/ref/to/something.foo",
111+
"maxLength": 100,
112+
"type": "string",
113+
"x-framework": "swagger-go",
114+
"description": "a really cool description"
115+
}`,
116+
Object: &Parameter{
117+
Refable{MustCreateRef("/components/ref/to/something.foo")},
118+
CommonValidations{
119+
MaxLength: int64Ptr(100),
120+
},
121+
SimpleSchema{
122+
Type: "string",
123+
},
124+
VendorExtensible{Extensions{
125+
"x-framework": "swagger-go",
126+
}},
127+
ParamProps{
128+
Description: "a really cool description",
129+
},
130+
},
131+
}, {
132+
Name: "BasicCase",
133+
JSON: parameterJSON,
134+
Object: &parameter,
135+
},
136+
}
137+
138+
for _, tcase := range cases {
139+
t.Run(tcase.Name, func(t *testing.T) {
140+
require.NoError(t, tcase.RoundTripTest(&Parameter{}))
141+
})
142+
}
143+
}

pkg/validation/spec/path_item_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import (
1919
"testing"
2020

2121
"github.com/stretchr/testify/assert"
22+
"github.com/stretchr/testify/require"
23+
jsontesting "k8s.io/kube-openapi/pkg/util/jsontesting"
2224
)
2325

2426
var pathItem = PathItem{
@@ -79,3 +81,35 @@ func TestIntegrationPathItem(t *testing.T) {
7981

8082
assertParsesJSON(t, pathItemJSON, pathItem)
8183
}
84+
85+
func TestPathItemRoundTrip(t *testing.T) {
86+
cases := []jsontesting.RoundTripTestCase{
87+
{
88+
// Show at least one field from each embededd struct sitll allows
89+
// roundtrips successfully
90+
Name: "UnmarshalEmbedded",
91+
JSON: `{
92+
"$ref": "/components/ref/to/something.foo",
93+
"x-framework": "swagger-go",
94+
"get": {
95+
"description": "a cool operation"
96+
}
97+
}`,
98+
Object: &PathItem{
99+
Refable{MustCreateRef("/components/ref/to/something.foo")},
100+
VendorExtensible{Extensions{"x-framework": "swagger-go"}},
101+
PathItemProps{Get: &Operation{OperationProps: OperationProps{Description: "a cool operation"}}},
102+
},
103+
}, {
104+
Name: "BasicCase",
105+
JSON: pathItemJSON,
106+
Object: &pathItem,
107+
},
108+
}
109+
110+
for _, tcase := range cases {
111+
t.Run(tcase.Name, func(t *testing.T) {
112+
require.NoError(t, tcase.RoundTripTest(&PathItem{}))
113+
})
114+
}
115+
}

0 commit comments

Comments
 (0)