k8s.io/kube-openapi@v0.0.0-20240228011516-70dd3763d340/pkg/builder/parameters_test.go (about) 1 /* 2 Copyright 2023 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 builder 18 19 import ( 20 "encoding/json" 21 "reflect" 22 "testing" 23 24 "github.com/stretchr/testify/require" 25 26 "k8s.io/kube-openapi/pkg/validation/spec" 27 ) 28 29 func TestCollectSharedParameters(t *testing.T) { 30 tests := []struct { 31 name string 32 spec string 33 want map[string]string 34 }{ 35 { 36 name: "empty", 37 spec: "", 38 want: nil, 39 }, 40 { 41 name: "no shared", 42 spec: `{ 43 "parameters": {"pre": {"in": "body", "name": "body", "required": true, "schema": {}}}, 44 "paths": { 45 "/api/v1/a/{name}": {"get": {"parameters": [ 46 {"description": "x","in":"query","name": "x","type":"boolean","uniqueItems":true}, 47 {"description": "y","in":"query","name": "y","type":"boolean","uniqueItems":true} 48 ]}}, 49 "/api/v1/a/{name}/foo": {"get": {"parameters": [ 50 {"description": "z","in":"query","name": "z","type":"boolean","uniqueItems":true} 51 ]}}, 52 "/api/v1/b/{name}": {"get": {"parameters": [ 53 {"description": "x","in":"query","name": "x2","type":"boolean","uniqueItems":true}, 54 {"description": "y","in":"query","name": "y2","type":"boolean","uniqueItems":true} 55 ]}}, 56 "/api/v1/b/{name}/foo": {"get": {"parameters": [ 57 {"description": "z","in":"query","name": "z2","type":"boolean","uniqueItems":true} 58 ]}} 59 } 60 }`, 61 want: map[string]string{ 62 `{"uniqueItems":true,"type":"boolean","description":"x","name":"x","in":"query"}`: "x-yaDSHpi7", 63 `{"uniqueItems":true,"type":"boolean","description":"y","name":"y","in":"query"}`: "y-g6h7lEsz", 64 `{"uniqueItems":true,"type":"boolean","description":"z","name":"z","in":"query"}`: "z--SXYWoM_", 65 `{"uniqueItems":true,"type":"boolean","description":"x","name":"x2","in":"query"}`: "x2-nds6MpS1", 66 `{"uniqueItems":true,"type":"boolean","description":"y","name":"y2","in":"query"}`: "y2-exnalzYE", 67 `{"uniqueItems":true,"type":"boolean","description":"z","name":"z2","in":"query"}`: "z2-8oJfzBQF", 68 }, 69 }, 70 { 71 name: "shared per operation", 72 spec: `{ 73 "parameters": {"pre": {"in": "body", "name": "body", "required": true, "schema": {}}}, 74 "paths": { 75 "/api/v1/a/{name}": {"get": {"parameters": [ 76 {"description": "x","in":"query","name": "x","type":"boolean","uniqueItems":true}, 77 {"description": "y","in":"query","name": "y","type":"boolean","uniqueItems":true} 78 ]}}, 79 "/api/v1/a/{name}/foo": {"get": {"parameters": [ 80 {"description": "z","in":"query","name": "z","type":"boolean","uniqueItems":true}, 81 {"description": "y","in":"query","name": "y","type":"boolean","uniqueItems":true} 82 ]}}, 83 "/api/v1/b/{name}": {"get": {"parameters": [ 84 {"description": "z","in":"query","name": "z","type":"boolean","uniqueItems":true} 85 ]}}, 86 "/api/v1/b/{name}/foo": {"get": {"parameters": [ 87 {"description": "x","in":"query","name": "x","type":"boolean","uniqueItems":true} 88 ]}} 89 } 90 }`, 91 want: map[string]string{ 92 `{"uniqueItems":true,"type":"boolean","description":"x","name":"x","in":"query"}`: "x-yaDSHpi7", 93 `{"uniqueItems":true,"type":"boolean","description":"y","name":"y","in":"query"}`: "y-g6h7lEsz", 94 `{"uniqueItems":true,"type":"boolean","description":"z","name":"z","in":"query"}`: "z--SXYWoM_", 95 }, 96 }, 97 { 98 name: "shared per path", 99 spec: `{ 100 "parameters": {"pre": {"in": "body", "name": "body", "required": true, "schema": {}}}, 101 "paths": { 102 "/api/v1/a/{name}": {"get": {}, 103 "parameters": [ 104 {"description": "x","in":"query","name": "x","type":"boolean","uniqueItems":true}, 105 {"description": "y","in":"query","name": "y","type":"boolean","uniqueItems":true} 106 ] 107 }, 108 "/api/v1/a/{name}/foo": {"get": {"parameters": [ 109 {"description": "z","in":"query","name": "z","type":"boolean","uniqueItems":true}, 110 {"description": "y","in":"query","name": "y","type":"boolean","uniqueItems":true} 111 ]}}, 112 "/api/v1/b/{name}": {"get": {}, 113 "parameters": [ 114 {"description": "z","in":"query","name": "z","type":"boolean","uniqueItems":true} 115 ] 116 }, 117 "/api/v1/b/{name}/foo": {"get": {"parameters": [ 118 {"description": "x","in":"query","name": "x","type":"boolean","uniqueItems":true} 119 ]}} 120 } 121 }`, 122 want: map[string]string{ 123 `{"uniqueItems":true,"type":"boolean","description":"x","name":"x","in":"query"}`: "x-yaDSHpi7", 124 `{"uniqueItems":true,"type":"boolean","description":"y","name":"y","in":"query"}`: "y-g6h7lEsz", 125 `{"uniqueItems":true,"type":"boolean","description":"z","name":"z","in":"query"}`: "z--SXYWoM_", 126 }, 127 }, 128 } 129 130 for _, tt := range tests { 131 t.Run(tt.name, func(t *testing.T) { 132 var sp *spec.Swagger 133 if tt.spec != "" { 134 err := json.Unmarshal([]byte(tt.spec), &sp) 135 require.NoError(t, err) 136 } 137 138 gotNamesByJSON, _, err := collectSharedParameters(sp) 139 require.NoError(t, err) 140 require.Equalf(t, tt.want, gotNamesByJSON, "unexpected shared parameters") 141 }) 142 } 143 } 144 145 func TestReplaceSharedParameters(t *testing.T) { 146 shared := map[string]string{ 147 `{"uniqueItems":true,"type":"boolean","description":"x","name":"x","in":"query"}`: "x", 148 `{"uniqueItems":true,"type":"boolean","description":"y","name":"y","in":"query"}`: "y", 149 `{"uniqueItems":true,"type":"boolean","description":"z","name":"z","in":"query"}`: "z", 150 } 151 152 tests := []struct { 153 name string 154 spec string 155 want string 156 }{ 157 { 158 name: "empty", 159 spec: "{}", 160 want: `{"paths":null}`, 161 }, 162 { 163 name: "existing parameters", 164 spec: `{"parameters": {"a":{"type":"boolean"}}}`, 165 want: `{"parameters": {"a":{"type":"boolean"}},"paths":null}`, 166 }, 167 { 168 name: "replace", 169 spec: `{ 170 "parameters": {"pre": {"in": "body", "name": "body", "required": true, "schema": {}}}, 171 "paths": { 172 "/api/v1/a/{name}": {"get": {"description":"foo"}, 173 "parameters": [ 174 {"description": "x","in":"query","name": "x","type":"boolean","uniqueItems":true}, 175 {"description": "y","in":"query","name": "y","type":"boolean","uniqueItems":true} 176 ] 177 }, 178 "/api/v1/a/{name}/foo": {"get": {"parameters": [ 179 {"description": "z","in":"query","name": "z","type":"boolean","uniqueItems":true}, 180 {"description": "y","in":"query","name": "y","type":"boolean","uniqueItems":true} 181 ]}}, 182 "/api/v1/b/{name}": {"get": {"parameters": [ 183 {"description": "z","in":"query","name": "z","type":"boolean","uniqueItems":true} 184 ]}}, 185 "/api/v1/b/{name}/foo": {"get": {"parameters": [ 186 {"description": "x","in":"query","name": "x","type":"boolean","uniqueItems":true}, 187 {"description": "w","in":"query","name": "w","type":"boolean","uniqueItems":true} 188 ]}} 189 } 190 }`, 191 want: `{ 192 "parameters": {"pre":{"in":"body","name":"body","required":true,"schema":{}}}, 193 "paths": { 194 "/api/v1/a/{name}": {"get": {"description":"foo"}, 195 "parameters": [ 196 {"$ref": "#/parameters/x"}, 197 {"$ref": "#/parameters/y"} 198 ] 199 }, 200 "/api/v1/a/{name}/foo": {"get": {"parameters": [ 201 {"$ref": "#/parameters/z"}, 202 {"$ref": "#/parameters/y"} 203 ]}}, 204 "/api/v1/b/{name}": {"get": {"parameters": [ 205 {"$ref": "#/parameters/z"} 206 ]}}, 207 "/api/v1/b/{name}/foo": {"get": {"parameters": [ 208 {"$ref":"#/parameters/x"}, 209 {"description": "w","in":"query","name": "w","type":"boolean","uniqueItems":true} 210 ]}} 211 } 212 }`, 213 }, 214 } 215 for _, tt := range tests { 216 t.Run(tt.name, func(t *testing.T) { 217 var unmarshalled *spec.Swagger 218 err := json.Unmarshal([]byte(tt.spec), &unmarshalled) 219 require.NoError(t, err) 220 221 got, err := replaceSharedParameters(shared, unmarshalled) 222 require.NoError(t, err) 223 224 require.Equalf(t, normalizeJSON(t, tt.want), normalizeJSON(t, toJSON(t, got)), "unexpected result") 225 }) 226 } 227 } 228 229 func toJSON(t *testing.T, x interface{}) string { 230 bs, err := json.Marshal(x) 231 require.NoError(t, err) 232 233 return string(bs) 234 } 235 236 func normalizeJSON(t *testing.T, j string) string { 237 var obj interface{} 238 err := json.Unmarshal([]byte(j), &obj) 239 require.NoError(t, err) 240 return toJSON(t, obj) 241 } 242 243 func TestOperations(t *testing.T) { 244 t.Log("Ensuring that operations() returns all operations in spec.PathItemProps") 245 path := spec.PathItem{} 246 v := reflect.ValueOf(path.PathItemProps) 247 var rOps []any 248 for i := 0; i < v.NumField(); i++ { 249 if v.Field(i).Kind() == reflect.Ptr { 250 rOps = append(rOps, v.Field(i).Interface()) 251 } 252 } 253 254 ops := operations(&path) 255 require.Equal(t, len(rOps), len(ops), "operations() should return all operations in spec.PathItemProps") 256 }