k8s.io/kube-openapi@v0.0.0-20240228011516-70dd3763d340/pkg/validation/validate/jsonschema_test.go (about) 1 // Copyright 2015 go-swagger maintainers 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package validate 16 17 import ( 18 "encoding/json" 19 "net/http" 20 "os" 21 "path/filepath" 22 "strings" 23 "testing" 24 25 "github.com/go-openapi/swag" 26 "github.com/stretchr/testify/assert" 27 "github.com/stretchr/testify/require" 28 "k8s.io/kube-openapi/pkg/validation/spec" 29 "k8s.io/kube-openapi/pkg/validation/strfmt" 30 ) 31 32 // Data structure for jsonschema-suite fixtures 33 type schemaTestT struct { 34 Description string `json:"description"` 35 Schema *spec.Schema `json:"schema"` 36 Tests []struct { 37 Description string `json:"description"` 38 Data interface{} `json:"data"` 39 Valid bool `json:"valid"` 40 } 41 } 42 43 var jsonSchemaFixturesPath = filepath.Join("fixtures", "jsonschema_suite") 44 var schemaFixturesPath = filepath.Join("fixtures", "schemas") 45 var formatFixturesPath = filepath.Join("fixtures", "formats") 46 47 func enabled() []string { 48 // Standard fixtures from JSON schema suite 49 return []string{ 50 "minLength", 51 "maxLength", 52 "pattern", 53 "type", 54 "minimum", 55 "maximum", 56 "multipleOf", 57 "enum", 58 "default", 59 "dependencies", 60 "items", 61 "maxItems", 62 "maxProperties", 63 "minItems", 64 "minProperties", 65 "patternProperties", 66 "required", 67 "additionalItems", 68 "uniqueItems", 69 "properties", 70 "additionalProperties", 71 "allOf", 72 "not", 73 "oneOf", 74 "anyOf", 75 "ref", 76 "definitions", 77 "refRemote", 78 "format", 79 } 80 } 81 82 var optionalFixtures = []string{ 83 // Optional fixtures from JSON schema suite: at the moment, these are disabled 84 //"zeroTerminatedFloats", 85 //"format", /* error on strict URI formatting */ 86 //"bignum", 87 //"ecmascript-regex", 88 } 89 90 var extendedFixtures = []string{ 91 "extended-format", 92 } 93 94 func isEnabled(nm string) bool { 95 return swag.ContainsStringsCI(enabled(), nm) 96 } 97 98 func isOptionalEnabled(nm string) bool { 99 return swag.ContainsStringsCI(optionalFixtures, nm) 100 } 101 102 func isExtendedEnabled(nm string) bool { 103 return swag.ContainsStringsCI(extendedFixtures, nm) 104 } 105 106 func TestJSONSchemaSuite(t *testing.T) { 107 // Internal local server to serve remote $ref 108 go func() { 109 err := http.ListenAndServe("localhost:1234", http.FileServer(http.Dir(jsonSchemaFixturesPath+"/remotes"))) 110 if err != nil { 111 panic(err.Error()) 112 } 113 }() 114 115 files, err := os.ReadDir(jsonSchemaFixturesPath) 116 if err != nil { 117 t.Fatal(err) 118 } 119 120 for _, f := range files { 121 if f.IsDir() { 122 continue 123 } 124 fileName := f.Name() 125 t.Run(fileName, func(t *testing.T) { 126 t.Parallel() 127 specName := strings.TrimSuffix(fileName, filepath.Ext(fileName)) 128 if !isEnabled(specName) { 129 t.Logf("WARNING: fixture from jsonschema-test-suite not enabled: %s", specName) 130 return 131 } 132 t.Log("Running " + specName) 133 b, _ := os.ReadFile(filepath.Join(jsonSchemaFixturesPath, fileName)) 134 doTestSchemaSuite(t, b) 135 }) 136 } 137 } 138 139 func TestSchemaFixtures(t *testing.T) { 140 files, err := os.ReadDir(schemaFixturesPath) 141 if err != nil { 142 t.Fatal(err) 143 } 144 145 for _, f := range files { 146 if f.IsDir() { 147 continue 148 } 149 fileName := f.Name() 150 t.Run(fileName, func(t *testing.T) { 151 t.Parallel() 152 specName := strings.TrimSuffix(fileName, filepath.Ext(fileName)) 153 t.Log("Running " + specName) 154 b, _ := os.ReadFile(filepath.Join(schemaFixturesPath, fileName)) 155 doTestSchemaSuite(t, b) 156 }) 157 } 158 } 159 160 func TestOptionalJSONSchemaSuite(t *testing.T) { 161 jsonOptionalSchemaFixturesPath := filepath.Join(jsonSchemaFixturesPath, "optional") 162 files, err := os.ReadDir(jsonOptionalSchemaFixturesPath) 163 if err != nil { 164 t.Fatal(err) 165 } 166 167 for _, f := range files { 168 if f.IsDir() { 169 continue 170 } 171 fileName := f.Name() 172 t.Run(fileName, func(t *testing.T) { 173 t.Parallel() 174 specName := strings.TrimSuffix(fileName, filepath.Ext(fileName)) 175 if !isOptionalEnabled(specName) { 176 t.Logf("INFO: fixture from jsonschema-test-suite [optional] not enabled: %s", specName) 177 return 178 } 179 t.Log("Running [optional] " + specName) 180 b, _ := os.ReadFile(filepath.Join(jsonOptionalSchemaFixturesPath, fileName)) 181 doTestSchemaSuite(t, b) 182 }) 183 } 184 } 185 186 // Further testing with all formats recognized by strfmt 187 func TestFormat_JSONSchemaExtended(t *testing.T) { 188 jsonFormatSchemaFixturesPath := filepath.Join(formatFixturesPath) 189 files, err := os.ReadDir(jsonFormatSchemaFixturesPath) 190 if err != nil { 191 t.Fatal(err) 192 } 193 194 for _, f := range files { 195 if f.IsDir() { 196 continue 197 } 198 fileName := f.Name() 199 t.Run(fileName, func(t *testing.T) { 200 t.Parallel() 201 specName := strings.TrimSuffix(fileName, filepath.Ext(fileName)) 202 if !isExtendedEnabled(specName) { 203 t.Logf("INFO: fixture from extended tests suite [formats] not enabled: %s", specName) 204 return 205 } 206 t.Log("Running [extended formats] " + specName) 207 b, _ := os.ReadFile(filepath.Join(jsonFormatSchemaFixturesPath, fileName)) 208 doTestSchemaSuite(t, b) 209 }) 210 } 211 } 212 213 func doTestSchemaSuite(t *testing.T, doc []byte) { 214 // run a test formatted as per jsonschema-test-suite 215 var testDescriptions []schemaTestT 216 eru := json.Unmarshal(doc, &testDescriptions) 217 require.NoError(t, eru) 218 219 for _, testDescription := range testDescriptions { 220 b, _ := testDescription.Schema.MarshalJSON() 221 tmpFile, err := os.CreateTemp(os.TempDir(), "validate-test") 222 assert.NoError(t, err) 223 _, _ = tmpFile.Write(b) 224 tmpFile.Close() 225 defer func() { _ = os.Remove(tmpFile.Name()) }() 226 227 validator := NewSchemaValidator(testDescription.Schema, nil, "data", strfmt.Default) 228 for _, test := range testDescription.Tests { 229 result := validator.Validate(test.Data) 230 assert.NotNil(t, result, test.Description+" should validate") 231 232 if test.Valid { 233 assert.Empty(t, result.Errors, test.Description+" should not have errors") 234 } else { 235 assert.NotEmpty(t, result.Errors, test.Description+" should have errors") 236 } 237 } 238 } 239 }