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  }