...

Source file src/github.com/go-openapi/validate/spec_test.go

Documentation: github.com/go-openapi/validate

     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  	"flag"
    20  	"os"
    21  	"path/filepath"
    22  	"strings"
    23  	"testing"
    24  
    25  	"github.com/davecgh/go-spew/spew"
    26  	"github.com/go-openapi/analysis"
    27  	"github.com/go-openapi/loads"
    28  	"github.com/go-openapi/loads/fmts"
    29  	"github.com/go-openapi/spec"
    30  	"github.com/go-openapi/strfmt"
    31  	"github.com/stretchr/testify/assert"
    32  	"github.com/stretchr/testify/require"
    33  )
    34  
    35  const testID = "id"
    36  
    37  // Enable long running tests by using cmd line arg,
    38  // Usage: go test ... -args [-enable-long|-enable-go-swagger]
    39  //
    40  // -enable-long:       enable spec_test.go:TestIssue18 and messages_test.go:Test_Quality*
    41  // -enable-go-swagger: enable non-regression tests against go-swagger fixtures (validation status) in swagger_test.go:Test_GoSwagger  (running about 110 specs...)
    42  //
    43  // If none enabled, these tests are skipped
    44  // NOTE: replacing with go test -short and testing.Short() means that
    45  // by default, every test is launched. With -enable-long, we just get the
    46  // opposite...
    47  var enableLongTests bool
    48  var enableGoSwaggerTests bool
    49  
    50  func init() {
    51  	loads.AddLoader(fmts.YAMLMatcher, fmts.YAMLDoc)
    52  	flag.BoolVar(&enableLongTests, "enable-long", false, "enable long runnning tests")
    53  	flag.BoolVar(&enableGoSwaggerTests, "enable-go-swagger", false, "enable go-swagger non-regression test")
    54  }
    55  
    56  func skipNotify(t *testing.T) {
    57  	t.Log("To enable this long running test, use -args -enable-long in your go test command line")
    58  }
    59  
    60  func debugTest(t *testing.T, path string, res *Result) {
    61  	if DebugTest && t.Failed() {
    62  		verifiedErrors := verifiedTestErrors(res)
    63  		if len(verifiedErrors) > 0 {
    64  			t.Logf("DEVMODE:Returned error messages validating %s ", path)
    65  			for _, v := range verifiedErrors {
    66  				t.Logf("%s", v)
    67  			}
    68  		}
    69  		verifiedWarnings := verifiedTestWarnings(res)
    70  		if len(verifiedWarnings) > 0 {
    71  			t.Logf("DEVMODE: Returned warnings for %s:", path)
    72  			for _, e := range res.Warnings {
    73  				t.Logf("%v", e)
    74  			}
    75  		}
    76  	}
    77  }
    78  
    79  func verifiedTestErrors(res *Result) []string {
    80  	verifiedErrors := make([]string, 0, 50)
    81  	for _, e := range res.Errors {
    82  		verifiedErrors = append(verifiedErrors, e.Error())
    83  	}
    84  	return verifiedErrors
    85  }
    86  
    87  func verifiedTestWarnings(res *Result) []string {
    88  	verifiedWarnings := make([]string, 0, 50)
    89  	for _, e := range res.Warnings {
    90  		verifiedWarnings = append(verifiedWarnings, e.Error())
    91  	}
    92  	return verifiedWarnings
    93  }
    94  
    95  func TestSpec_ExpandResponseLocalFile(t *testing.T) {
    96  	res, _ := loadAndValidate(t, filepath.Join("fixtures", "local_expansion", "spec.yaml"))
    97  	assert.True(t, res.IsValid())
    98  	assert.Empty(t, res.Errors)
    99  }
   100  
   101  func TestSpec_ExpandResponseRecursive(t *testing.T) {
   102  	res, _ := loadAndValidate(t, filepath.Join("fixtures", "recursive_expansion", "spec.yaml"))
   103  	assert.True(t, res.IsValid())
   104  	assert.Empty(t, res.Errors)
   105  }
   106  
   107  // Spec with no path
   108  func TestSpec_Issue52(t *testing.T) {
   109  	fp := filepath.Join("fixtures", "bugs", "52", "swagger.json")
   110  	jstext, _ := os.ReadFile(fp)
   111  
   112  	// as json schema
   113  	var sch spec.Schema
   114  	require.NoError(t, json.Unmarshal(jstext, &sch))
   115  
   116  	schemaValidator := NewSchemaValidator(spec.MustLoadSwagger20Schema(), nil, "", strfmt.Default)
   117  	res := schemaValidator.Validate(&sch)
   118  	assert.False(t, res.IsValid())
   119  	require.NotEmpty(t, res.Errors)
   120  	require.EqualError(t, res.Errors[0], ".paths in body is required")
   121  
   122  	// as swagger spec: path is set to nil
   123  	// Here, validation stops as paths is initialized to empty
   124  	res, _ = loadAndValidate(t, fp)
   125  	assert.False(t, res.IsValid())
   126  
   127  	verifiedErrors := verifiedTestErrors(res)
   128  	assert.Len(t, verifiedErrors, 2, "Unexpected number of error messages returned")
   129  	assert.Contains(t, verifiedErrors, ".paths in body is required")
   130  	assert.Contains(t, verifiedErrors, "spec has no valid path defined")
   131  }
   132  
   133  func TestSpec_Issue53(t *testing.T) {
   134  	fp := filepath.Join("fixtures", "bugs", "53", "noswagger.json")
   135  	jstext, _ := os.ReadFile(fp)
   136  
   137  	// as json schema
   138  	var sch spec.Schema
   139  	require.NoError(t, json.Unmarshal(jstext, &sch))
   140  
   141  	schemaValidator := NewSchemaValidator(spec.MustLoadSwagger20Schema(), nil, "", strfmt.Default)
   142  	res := schemaValidator.Validate(&sch)
   143  	assert.False(t, res.IsValid())
   144  	require.NotEmpty(t, res.Errors)
   145  	require.EqualError(t, res.Errors[0], ".swagger in body is required")
   146  
   147  	// as swagger despec
   148  	res, _ = loadAndValidate(t, fp, false)
   149  	require.False(t, res.IsValid())
   150  	require.NotEmpty(t, res.Errors)
   151  	require.EqualError(t, res.Errors[0], ".swagger in body is required")
   152  }
   153  
   154  func TestSpec_Issue62(t *testing.T) {
   155  	fp := filepath.Join("fixtures", "bugs", "62", "swagger.json")
   156  
   157  	// as swagger spec
   158  	doc, err := loads.Spec(fp)
   159  	require.NoError(t, err)
   160  
   161  	validator := NewSpecValidator(spec.MustLoadSwagger20Schema(), strfmt.Default)
   162  	res, _ := validator.Validate(doc)
   163  	assert.NotEmpty(t, res.Errors)
   164  	assert.True(t, res.HasErrors())
   165  }
   166  
   167  func TestSpec_Issue63(t *testing.T) {
   168  	res, _ := loadAndValidate(t, filepath.Join("fixtures", "bugs", "63", "swagger.json"))
   169  	assert.True(t, res.IsValid())
   170  }
   171  
   172  func TestSpec_Issue61_MultipleRefs(t *testing.T) {
   173  	res, _ := loadAndValidate(t, filepath.Join("fixtures", "bugs", "61", "multiple-refs.json"))
   174  	assert.Empty(t, res.Errors)
   175  	assert.True(t, res.IsValid())
   176  }
   177  
   178  func TestSpec_Issue61_ResolvedRef(t *testing.T) {
   179  	res, _ := loadAndValidate(t, filepath.Join("fixtures", "bugs", "61", "unresolved-ref-for-name.json"))
   180  	assert.Empty(t, res.Errors)
   181  	assert.True(t, res.IsValid())
   182  }
   183  
   184  // No error with this one
   185  func TestSpec_Issue123(t *testing.T) {
   186  	fp := filepath.Join("fixtures", "bugs", "123", "swagger.yml")
   187  	res, _ := loadAndValidate(t, fp)
   188  	assert.True(t, res.IsValid())
   189  	assert.Empty(t, res.Errors)
   190  
   191  	debugTest(t, fp, res)
   192  }
   193  
   194  func TestSpec_Issue6(t *testing.T) {
   195  	files, _ := filepath.Glob(filepath.Join("fixtures", "bugs", "6", "*.json"))
   196  	for _, path := range files {
   197  		t.Logf("Tested spec=%s", path)
   198  		res, _ := loadAndValidate(t, path)
   199  		assert.False(t, res.IsValid())
   200  
   201  		verifiedErrors := verifiedTestErrors(res)
   202  		switch {
   203  		case strings.Contains(path, "empty-responses.json"):
   204  			assert.Contains(t, verifiedErrors, "\"paths./foo.get.responses\" must not validate the schema (not)")
   205  			assert.Contains(t, verifiedErrors, "paths./foo.get.responses in body should have at least 1 properties")
   206  		case strings.Contains(path, "no-responses.json"):
   207  			assert.Contains(t, verifiedErrors, "paths./foo.get.responses in body is required")
   208  		default:
   209  			t.Logf("Returned error messages: %v", verifiedErrors)
   210  			t.Fatal("fixture not tested. Please add assertions for messages")
   211  		}
   212  
   213  		debugTest(t, path, res)
   214  	}
   215  }
   216  
   217  // check if invalid patterns are indeed invalidated
   218  func TestSpec_Issue18(t *testing.T) {
   219  	files, _ := filepath.Glob(filepath.Join("fixtures", "bugs", "18", "*.json"))
   220  	for _, path := range files {
   221  		t.Logf("Tested spec=%s", path)
   222  		res, _ := loadAndValidate(t, path)
   223  		assert.False(t, res.IsValid())
   224  
   225  		verifiedErrors := verifiedTestErrors(res)
   226  		switch {
   227  		case strings.Contains(path, "headerItems.json"):
   228  			assert.Contains(t, verifiedErrors, "X-Foo in header has invalid pattern: \")<-- bad pattern\"")
   229  		case strings.Contains(path, "headers.json"):
   230  			assert.Contains(t, verifiedErrors, "in operation \"\", header X-Foo for default response has invalid pattern \")<-- bad pattern\": error parsing regexp: unexpected ): `)<-- bad pattern`")
   231  			//  in operation \"\", header X-Foo for default response has invalid pattern \")<-- bad pattern\": error parsing regexp: unexpected ): `)<-- bad pattern`
   232  			assert.Contains(t, verifiedErrors, "in operation \"\", header X-Foo for response 402 has invalid pattern \")<-- bad pattern\": error parsing regexp: unexpected ): `)<-- bad pattern`")
   233  			//  in operation "", header X-Foo for response 402 has invalid pattern ")<-- bad pattern": error parsing regexp: unexpected ): `)<-- bad pattern`
   234  
   235  		case strings.Contains(path, "paramItems.json"):
   236  			assert.Contains(t, verifiedErrors, "body param \"user\" for \"\" has invalid items pattern: \")<-- bad pattern\"")
   237  			// Updated message: from "user.items in body has invalid pattern: \")<-- bad pattern\"" to:
   238  			assert.Contains(t, verifiedErrors, "default value for user in body does not validate its schema")
   239  			assert.Contains(t, verifiedErrors, "user.items.default in body has invalid pattern: \")<-- bad pattern\"")
   240  		case strings.Contains(path, "parameters.json"):
   241  			assert.Contains(t, verifiedErrors, "operation \"\" has invalid pattern in param \"userId\": \")<-- bad pattern\"")
   242  		case strings.Contains(path, "schema.json"):
   243  			// TODO: strange that the text does not say response "200"...
   244  			assert.Contains(t, verifiedErrors, "200 in response has invalid pattern: \")<-- bad pattern\"")
   245  		default:
   246  			t.Logf("Returned error messages: %v", verifiedErrors)
   247  			t.Fatal("fixture not tested. Please add assertions for messages")
   248  		}
   249  
   250  		debugTest(t, path, res)
   251  	}
   252  }
   253  
   254  // check if a fragment path parameter is recognized, without error
   255  func TestSpec_Issue39(t *testing.T) {
   256  	fp := filepath.Join("fixtures", "bugs", "39", "swagger.yml")
   257  	res, _ := loadAndValidate(t, fp)
   258  	assert.True(t, res.IsValid())
   259  	assert.Empty(t, res.Errors)
   260  	debugTest(t, fp, res)
   261  }
   262  
   263  func TestSpec_ValidateDuplicatePropertyNames(t *testing.T) {
   264  	// simple allOf
   265  	doc, err := loads.Spec(filepath.Join("fixtures", "validation", "duplicateprops.json"))
   266  	require.NoError(t, err)
   267  
   268  	validator := NewSpecValidator(spec.MustLoadSwagger20Schema(), strfmt.Default)
   269  	validator.spec = doc
   270  	res := validator.validateDuplicatePropertyNames()
   271  	assert.NotEmpty(t, res.Errors)
   272  	assert.Len(t, res.Errors, 1)
   273  
   274  	// nested allOf
   275  	doc, err = loads.Spec(filepath.Join("fixtures", "validation", "nestedduplicateprops.json"))
   276  	require.NoError(t, err)
   277  
   278  	validator = NewSpecValidator(spec.MustLoadSwagger20Schema(), strfmt.Default)
   279  	validator.spec = doc
   280  	res = validator.validateDuplicatePropertyNames()
   281  	assert.NotEmpty(t, res.Errors)
   282  	assert.Len(t, res.Errors, 1)
   283  }
   284  
   285  func TestSpec_ValidateNonEmptyPathParameterNames(t *testing.T) {
   286  	doc, err := loads.Spec(filepath.Join("fixtures", "validation", "empty-path-param-name.json"))
   287  	require.NoError(t, err)
   288  
   289  	validator := NewSpecValidator(spec.MustLoadSwagger20Schema(), strfmt.Default)
   290  	validator.spec = doc
   291  	res := validator.validateNonEmptyPathParamNames()
   292  	assert.NotEmpty(t, res.Errors)
   293  	assert.Len(t, res.Errors, 1)
   294  }
   295  
   296  func TestSpec_ValidateCircularAncestry(t *testing.T) {
   297  	doc, err := loads.Spec(filepath.Join("fixtures", "validation", "direct-circular-ancestor.json"))
   298  	require.NoError(t, err)
   299  
   300  	validator := NewSpecValidator(spec.MustLoadSwagger20Schema(), strfmt.Default)
   301  	validator.spec = doc
   302  	res := validator.validateDuplicatePropertyNames()
   303  	assert.NotEmpty(t, res.Errors)
   304  	assert.Len(t, res.Errors, 1)
   305  
   306  	doc, err = loads.Spec(filepath.Join("fixtures", "validation", "indirect-circular-ancestor.json"))
   307  	require.NoError(t, err)
   308  
   309  	validator = NewSpecValidator(spec.MustLoadSwagger20Schema(), strfmt.Default)
   310  	validator.spec = doc
   311  	res = validator.validateDuplicatePropertyNames()
   312  	assert.NotEmpty(t, res.Errors)
   313  	assert.Len(t, res.Errors, 1)
   314  
   315  	doc, err = loads.Spec(filepath.Join("fixtures", "validation", "recursive-circular-ancestor.json"))
   316  	require.NoError(t, err)
   317  
   318  	validator = NewSpecValidator(spec.MustLoadSwagger20Schema(), strfmt.Default)
   319  	validator.spec = doc
   320  	res = validator.validateDuplicatePropertyNames()
   321  	assert.NotEmpty(t, res.Errors)
   322  	assert.Len(t, res.Errors, 1)
   323  }
   324  
   325  func TestSpec_ValidateReferenced(t *testing.T) {
   326  	doc, err := loads.Spec(filepath.Join("fixtures", "validation", "valid-referenced.yml"))
   327  	require.NoError(t, err)
   328  
   329  	validator := NewSpecValidator(spec.MustLoadSwagger20Schema(), strfmt.Default)
   330  	validator.spec = doc
   331  	validator.analyzer = analysis.New(doc.Spec())
   332  	res := validator.validateReferenced()
   333  	assert.Empty(t, res.Errors)
   334  
   335  	doc, err = loads.Spec(filepath.Join("fixtures", "validation", "invalid-referenced.yml"))
   336  	require.NoError(t, err)
   337  
   338  	validator = NewSpecValidator(spec.MustLoadSwagger20Schema(), strfmt.Default)
   339  	validator.spec = doc
   340  	validator.analyzer = analysis.New(doc.Spec())
   341  	res = validator.validateReferenced()
   342  	assert.Empty(t, res.Errors)
   343  	assert.NotEmpty(t, res.Warnings)
   344  	assert.Len(t, res.Warnings, 3)
   345  }
   346  
   347  func TestSpec_ValidateReferencesValid(t *testing.T) {
   348  	doc, err := loads.Spec(filepath.Join("fixtures", "validation", "valid-ref.json"))
   349  	require.NoError(t, err)
   350  
   351  	validator := NewSpecValidator(spec.MustLoadSwagger20Schema(), strfmt.Default)
   352  	validator.spec = doc
   353  	validator.analyzer = analysis.New(doc.Spec())
   354  	res := validator.validateReferencesValid()
   355  	assert.Empty(t, res.Errors)
   356  
   357  	doc, err = loads.Spec(filepath.Join("fixtures", "validation", "invalid-ref.json"))
   358  	require.NoError(t, err)
   359  
   360  	validator = NewSpecValidator(spec.MustLoadSwagger20Schema(), strfmt.Default)
   361  	validator.spec = doc
   362  	validator.analyzer = analysis.New(doc.Spec())
   363  	res = validator.validateReferencesValid()
   364  	assert.NotEmpty(t, res.Errors)
   365  }
   366  
   367  func TestSpec_ValidateRequiredDefinitions(t *testing.T) {
   368  	doc, _ := loads.Analyzed(PetStoreJSONMessage, "")
   369  	validator := NewSpecValidator(spec.MustLoadSwagger20Schema(), strfmt.Default)
   370  	validator.spec = doc
   371  	validator.analyzer = analysis.New(doc.Spec())
   372  	res := validator.validateRequiredDefinitions()
   373  	assert.Empty(t, res.Errors)
   374  
   375  	// properties
   376  	sw := doc.Spec()
   377  	def := sw.Definitions["Tag"]
   378  	def.Required = append(def.Required, "type")
   379  	sw.Definitions["Tag"] = def
   380  	res = validator.validateRequiredDefinitions()
   381  	assert.NotEmpty(t, res.Errors)
   382  
   383  	// pattern properties
   384  	def.PatternProperties = make(map[string]spec.Schema)
   385  	def.PatternProperties["ty.*"] = *spec.StringProperty()
   386  	sw.Definitions["Tag"] = def
   387  	res = validator.validateRequiredDefinitions()
   388  	assert.Empty(t, res.Errors)
   389  
   390  	def.PatternProperties = make(map[string]spec.Schema)
   391  	def.PatternProperties["^ty.$"] = *spec.StringProperty()
   392  	sw.Definitions["Tag"] = def
   393  	res = validator.validateRequiredDefinitions()
   394  	assert.NotEmpty(t, res.Errors)
   395  
   396  	// additional properties
   397  	def.PatternProperties = nil
   398  	def.AdditionalProperties = &spec.SchemaOrBool{Allows: true}
   399  	sw.Definitions["Tag"] = def
   400  	res = validator.validateRequiredDefinitions()
   401  	assert.Empty(t, res.Errors)
   402  
   403  	def.AdditionalProperties = &spec.SchemaOrBool{Allows: false}
   404  	sw.Definitions["Tag"] = def
   405  	res = validator.validateRequiredDefinitions()
   406  	assert.NotEmpty(t, res.Errors)
   407  }
   408  
   409  func TestSpec_ValidateParameters(t *testing.T) {
   410  	validatorForDoc := func(doc *loads.Document) *SpecValidator {
   411  		// build a spec validator for some doc
   412  		validator := NewSpecValidator(spec.MustLoadSwagger20Schema(), strfmt.Default)
   413  		validator.spec = doc
   414  		validator.analyzer = analysis.New(doc.Spec())
   415  
   416  		return validator
   417  	}
   418  
   419  	t.Run("should validate classic PetStore", func(t *testing.T) {
   420  		doc, err := loads.Analyzed(PetStoreJSONMessage, "")
   421  		require.NoError(t, err)
   422  		validator := validatorForDoc(doc)
   423  
   424  		res := validator.validateParameters()
   425  		require.Empty(t, res.Errors)
   426  	})
   427  
   428  	t.Run("should detect duplicate parameters", func(t *testing.T) {
   429  		doc, err := loads.Analyzed(PetStoreJSONMessage, "")
   430  		require.NoError(t, err)
   431  
   432  		sw := doc.Spec()
   433  		sw.Paths.Paths["/pets"].Get.Parameters = append(sw.Paths.Paths["/pets"].Get.Parameters, *spec.QueryParam("limit").Typed(stringType, ""))
   434  		validator := validatorForDoc(doc)
   435  
   436  		res := validator.validateParameters()
   437  		require.NotEmpty(t, res.Errors)
   438  		assert.Contains(t, res.Errors[0].Error(),
   439  			`duplicate parameter name "limit" for "query" in operation "getAllPets"`,
   440  		)
   441  	})
   442  
   443  	t.Run("should detect multiple parameters in body", func(t *testing.T) {
   444  		doc, err := loads.Analyzed(PetStoreJSONMessage, "")
   445  		require.NoError(t, err)
   446  
   447  		sw := doc.Spec()
   448  		sw.Paths.Paths["/pets"].Post.Parameters = append(sw.Paths.Paths["/pets"].Post.Parameters, *spec.BodyParam("fake", spec.RefProperty("#/definitions/Pet")))
   449  		validator := validatorForDoc(doc)
   450  
   451  		res := validator.validateParameters()
   452  		assert.NotEmpty(t, res.Errors)
   453  		require.Len(t, res.Errors, 1)
   454  		assert.Contains(t, res.Errors[0].Error(), "has more than 1 body param")
   455  	})
   456  
   457  	t.Run("should detect invalid parameter schema in (modified) classic PetStore", func(t *testing.T) {
   458  		fixture := filepath.Join("fixtures", "petstore", "swagger-invalid.json")
   459  
   460  		t.Run("with raw JSON", func(t *testing.T) {
   461  			// loading with full root document
   462  			jazon, err := os.ReadFile(fixture)
   463  			require.NoError(t, err)
   464  			doc, err := loads.Analyzed(jazon, "")
   465  			require.NoError(t, err)
   466  			validator := validatorForDoc(doc)
   467  
   468  			res := validator.validateParameters()
   469  			require.Len(t, res.Errors, 2)
   470  			assert.Contains(t, res.Errors[0].Error(),
   471  				`"/pets.POST.parameters.pet" must validate one and only one schema (oneOf). Found none valid`,
   472  			)
   473  			assert.Contains(t, res.Errors[1].Error(),
   474  				`/pets.POST.parameters.pet.schema.anyOf in body is a forbidden property`,
   475  			)
   476  		})
   477  		t.Run("with loads.Spec", func(t *testing.T) {
   478  			// loading like a regular user of this library
   479  			doc, err := loads.Spec(fixture)
   480  			require.NoError(t, err)
   481  
   482  			err = Spec(doc, strfmt.Default)
   483  			require.Error(t, err)
   484  			require.ErrorContains(t, err,
   485  				"definitions.newPet.anyOf in body is a forbidden property",
   486  			)
   487  		})
   488  
   489  		t.Run("with invalid Swagger schema", func(t *testing.T) {
   490  			doc, err := loads.Analyzed(PetStoreJSONMessage, "")
   491  			require.NoError(t, err)
   492  			validator := validatorForDoc(doc)
   493  			delete(validator.schema.Definitions, "parameter")
   494  
   495  			require.Panics(t, func() {
   496  				_ = validator.validateParameters()
   497  			})
   498  		})
   499  	})
   500  
   501  	t.Run("should detect duplicate parameters", func(t *testing.T) {
   502  		doc, err := loads.Analyzed(PetStoreJSONMessage, "")
   503  		require.NoError(t, err)
   504  
   505  		sw := doc.Spec()
   506  		pp := sw.Paths.Paths["/pets/{id}"]
   507  		pp.Delete = nil
   508  		var nameParams []spec.Parameter
   509  		for _, p := range pp.Parameters {
   510  			if p.Name == testID {
   511  				p.Name = "name"
   512  				nameParams = append(nameParams, p)
   513  			}
   514  		}
   515  		pp.Parameters = nameParams
   516  		sw.Paths.Paths["/pets/{name}"] = pp
   517  		validator := validatorForDoc(doc)
   518  
   519  		res := validator.validateParameters()
   520  		assert.NotEmpty(t, res.Errors)
   521  		require.Len(t, res.Errors, 1)
   522  		assert.Contains(t, res.Errors[0].Error(), "overlaps with")
   523  
   524  		t.Run("should tolerate duplicate parameters, on option", func(t *testing.T) {
   525  			// Disable strict path param uniqueness and ensure there is no error
   526  			validator.Options.StrictPathParamUniqueness = false
   527  			res := validator.validateParameters()
   528  			require.Empty(t, res.Errors)
   529  		})
   530  	})
   531  
   532  	t.Run("should detect mismatch with path parameter", func(t *testing.T) {
   533  		doc, err := loads.Analyzed(PetStoreJSONMessage, "")
   534  		require.NoError(t, err)
   535  
   536  		sw := doc.Spec()
   537  		pp := sw.Paths.Paths["/pets/{id}"]
   538  		pp.Delete = nil
   539  		var nameParams []spec.Parameter
   540  		for _, p := range pp.Parameters {
   541  			if p.Name == testID {
   542  				p.Name = "name"
   543  				nameParams = append(nameParams, p)
   544  			}
   545  		}
   546  		pp.Get.Parameters = nameParams
   547  		pp.Parameters = nil
   548  		sw.Paths.Paths["/pets/{id}"] = pp
   549  		validator := validatorForDoc(doc)
   550  
   551  		res := validator.validateParameters()
   552  		require.NotEmpty(t, res.Errors)
   553  		require.Len(t, res.Errors, 2)
   554  		assert.Contains(t, res.Errors[1].Error(),
   555  			`is not present in path "/pets/{id}"`,
   556  		)
   557  		assert.Contains(t, res.Errors[0].Error(),
   558  			"has no parameter definition",
   559  		)
   560  	})
   561  
   562  	t.Run("with issue go-swagger/go-swagger#2527", func(t *testing.T) {
   563  		basePath := filepath.Join("fixtures", "bugs", "2527")
   564  
   565  		t.Run("should detect mismatch between parameter and schema", func(t *testing.T) {
   566  			doc, err := loads.Spec(filepath.Join(basePath, "swagger.yml"))
   567  			require.NoError(t, err)
   568  
   569  			err = Spec(doc, strfmt.Default)
   570  			require.Error(t, err)
   571  			require.ErrorContains(t, err,
   572  				`/deposits.GET.parameters..enum in body is a forbidden property`,
   573  			)
   574  			require.ErrorContains(t, err,
   575  				`deposits.GET.parameters..type in body is a forbidden property`,
   576  			)
   577  			require.ErrorContains(t, err,
   578  				`/deposits.GET.parameters..name in body is required`,
   579  			)
   580  			require.ErrorContains(t, err,
   581  				`/deposits.GET.parameters..in in body is required`,
   582  			)
   583  		})
   584  
   585  		t.Run("should validate fixed spec", func(t *testing.T) {
   586  			doc, err := loads.Spec(filepath.Join(basePath, "swagger-fixed.yml"))
   587  			require.NoError(t, err)
   588  
   589  			require.NoError(t, Spec(doc, strfmt.Default))
   590  		})
   591  
   592  		t.Run("should detect missing name and in from refed parameter", func(t *testing.T) {
   593  			doc, err := loads.Spec(filepath.Join(basePath, "swagger-other.yml"))
   594  			require.NoError(t, err)
   595  
   596  			err = Spec(doc, strfmt.Default)
   597  			require.ErrorContains(t, err,
   598  				`"parameters.missingName" must validate one and only one schema (oneOf). Found none valid`,
   599  			)
   600  			require.ErrorContains(t, err,
   601  				`parameters.missingName.name in body is required`,
   602  			)
   603  			require.ErrorContains(t, err,
   604  				`"parameters.missingIn" must validate one and only one schema (oneOf). Found none valid`,
   605  			)
   606  			require.ErrorContains(t, err,
   607  				`parameters.missingIn.in in body is required`,
   608  			)
   609  		})
   610  
   611  		t.Run("extra parameter JSONSchema validation should not result in duplicate errors", func(t *testing.T) {
   612  			t.Run("with spec validator", func(t *testing.T) {
   613  				doc, err := loads.Spec(filepath.Join(basePath, "swagger-schema-error.yml"))
   614  				require.NoError(t, err)
   615  
   616  				errs, warns := NewSpecValidator(doc.Schema(), strfmt.Default).Validate(doc)
   617  				require.Len(t, errs.Errors, 3)
   618  				require.Empty(t, warns.Errors)
   619  
   620  				var found1, found2, found3 int
   621  				for _, err := range errs.Errors {
   622  					switch {
   623  					case strings.Contains(err.Error(), `definitions.WrongSchema.descriptions in body is a forbidden property`):
   624  						found1++
   625  					case strings.Contains(err.Error(), `"definitions.WrongSchema.type" must validate at least one schema (anyOf)`):
   626  						found2++
   627  					case strings.Contains(err.Error(), `definitions.WrongSchema.type in body should be one of [array boolean integer null number object string]`):
   628  						found3++
   629  					}
   630  				}
   631  
   632  				t.Run("each message should appear exactly once", func(t *testing.T) {
   633  					require.Equal(t, 1, found1)
   634  					require.Equal(t, 1, found2)
   635  					require.Equal(t, 1, found3)
   636  				})
   637  			})
   638  		})
   639  	})
   640  }
   641  
   642  func TestSpec_ValidateItems(t *testing.T) {
   643  	doc, _ := loads.Analyzed(PetStoreJSONMessage, "")
   644  	validator := NewSpecValidator(spec.MustLoadSwagger20Schema(), strfmt.Default)
   645  	validator.spec = doc
   646  	validator.analyzer = analysis.New(doc.Spec())
   647  	res := validator.validateItems()
   648  	assert.Empty(t, res.Errors)
   649  
   650  	// in operation parameters
   651  	sw := doc.Spec()
   652  	sw.Paths.Paths["/pets"].Get.Parameters[0].Type = arrayType
   653  	res = validator.validateItems()
   654  	assert.NotEmpty(t, res.Errors)
   655  
   656  	sw.Paths.Paths["/pets"].Get.Parameters[0].Items = spec.NewItems().Typed(stringType, "")
   657  	res = validator.validateItems()
   658  	assert.Empty(t, res.Errors)
   659  
   660  	sw.Paths.Paths["/pets"].Get.Parameters[0].Items = spec.NewItems().Typed(arrayType, "")
   661  	res = validator.validateItems()
   662  	assert.NotEmpty(t, res.Errors)
   663  
   664  	sw.Paths.Paths["/pets"].Get.Parameters[0].Items.Items = spec.NewItems().Typed(stringType, "")
   665  	res = validator.validateItems()
   666  	assert.Empty(t, res.Errors)
   667  
   668  	// in global parameters
   669  	sw.Parameters = make(map[string]spec.Parameter)
   670  	sw.Parameters["other"] = *spec.SimpleArrayParam("other", arrayType, "csv")
   671  	res = validator.validateItems()
   672  	assert.Empty(t, res.Errors)
   673  
   674  	// pp := spec.SimpleArrayParam("other", arrayType, "")
   675  	// pp.Items = nil
   676  	// sw.Parameters["other"] = *pp
   677  	// res = validator.validateItems()
   678  	// assert.NotEmpty(t, res.Errors)
   679  
   680  	// in shared path object parameters
   681  	doc, _ = loads.Analyzed(PetStoreJSONMessage, "")
   682  	validator = NewSpecValidator(spec.MustLoadSwagger20Schema(), strfmt.Default)
   683  	validator.spec = doc
   684  	validator.analyzer = analysis.New(doc.Spec())
   685  	sw = doc.Spec()
   686  
   687  	pa := sw.Paths.Paths["/pets"]
   688  	pa.Parameters = []spec.Parameter{*spec.SimpleArrayParam("another", arrayType, "csv")}
   689  	sw.Paths.Paths["/pets"] = pa
   690  	res = validator.validateItems()
   691  	assert.Empty(t, res.Errors)
   692  
   693  	pa = sw.Paths.Paths["/pets"]
   694  	pp := spec.SimpleArrayParam("other", arrayType, "")
   695  	pp.Items = nil
   696  	pa.Parameters = []spec.Parameter{*pp}
   697  	sw.Paths.Paths["/pets"] = pa
   698  	res = validator.validateItems()
   699  	assert.NotEmpty(t, res.Errors)
   700  
   701  	// in body param schema
   702  	doc, _ = loads.Analyzed(PetStoreJSONMessage, "")
   703  	validator = NewSpecValidator(spec.MustLoadSwagger20Schema(), strfmt.Default)
   704  	validator.spec = doc
   705  	validator.analyzer = analysis.New(doc.Spec())
   706  	sw = doc.Spec()
   707  	pa = sw.Paths.Paths["/pets"]
   708  	pa.Post.Parameters[0].Schema = spec.ArrayProperty(nil)
   709  	res = validator.validateItems()
   710  	assert.NotEmpty(t, res.Errors)
   711  
   712  	// in response headers
   713  	doc, _ = loads.Analyzed(PetStoreJSONMessage, "")
   714  	validator = NewSpecValidator(spec.MustLoadSwagger20Schema(), strfmt.Default)
   715  	validator.spec = doc
   716  	validator.analyzer = analysis.New(doc.Spec())
   717  	sw = doc.Spec()
   718  	pa = sw.Paths.Paths["/pets"]
   719  	rp := pa.Post.Responses.StatusCodeResponses[200]
   720  	var hdr spec.Header
   721  	hdr.Type = arrayType
   722  	rp.Headers = make(map[string]spec.Header)
   723  	rp.Headers["X-YADA"] = hdr
   724  	pa.Post.Responses.StatusCodeResponses[200] = rp
   725  	res = validator.validateItems()
   726  	assert.NotEmpty(t, res.Errors)
   727  
   728  	// in response schema
   729  	doc, _ = loads.Analyzed(PetStoreJSONMessage, "")
   730  	validator = NewSpecValidator(spec.MustLoadSwagger20Schema(), strfmt.Default)
   731  	validator.spec = doc
   732  	validator.analyzer = analysis.New(doc.Spec())
   733  	sw = doc.Spec()
   734  	pa = sw.Paths.Paths["/pets"]
   735  	rp = pa.Post.Responses.StatusCodeResponses[200]
   736  	rp.Schema = spec.ArrayProperty(nil)
   737  	pa.Post.Responses.StatusCodeResponses[200] = rp
   738  	res = validator.validateItems()
   739  	assert.NotEmpty(t, res.Errors)
   740  }
   741  
   742  // Reuse known validated cases through the higher level Spec() call
   743  func TestSpec_ValidDoc(t *testing.T) {
   744  	fp := filepath.Join("fixtures", "local_expansion", "spec.yaml")
   745  	doc2, err := loads.Spec(fp)
   746  	require.NoError(t, err)
   747  	err = Spec(doc2, strfmt.Default)
   748  	require.NoError(t, err)
   749  }
   750  
   751  // Check higher level behavior on invalid spec doc
   752  func TestSpec_InvalidDoc(t *testing.T) {
   753  	doc, err := loads.Spec(filepath.Join("fixtures", "validation", "default", "invalid-default-value-parameter.json"))
   754  	require.NoError(t, err)
   755  	err = Spec(doc, strfmt.Default)
   756  	require.Error(t, err)
   757  }
   758  
   759  func TestSpec_Validate_InvalidInterface(t *testing.T) {
   760  	fp := filepath.Join("fixtures", "local_expansion", "spec.yaml")
   761  	doc2, err := loads.Spec(fp)
   762  	require.NoError(t, err)
   763  	require.NotNil(t, doc2)
   764  
   765  	validator := NewSpecValidator(doc2.Schema(), strfmt.Default)
   766  	bug := "bzzz"
   767  	res, _ := validator.Validate(bug)
   768  	assert.NotEmpty(t, res.Errors)
   769  	assert.Contains(t, res.Errors[0].Error(), "can only validate spec.Document objects")
   770  }
   771  
   772  func TestSpec_ValidateBodyFormDataParams(t *testing.T) {
   773  	res, _ := loadAndValidate(t, filepath.Join("fixtures", "validation", "invalid-formdata-body-params.json"))
   774  	assert.NotEmpty(t, res.Errors)
   775  	assert.Len(t, res.Errors, 1)
   776  }
   777  
   778  func TestSpec_Issue73(t *testing.T) {
   779  	res, _ := loadAndValidate(t, filepath.Join("fixtures", "bugs", "73", "fixture-swagger.yaml"))
   780  	assert.Empty(t, res.Errors, " in fixture-swagger.yaml")
   781  
   782  	res, _ = loadAndValidate(t, filepath.Join("fixtures", "bugs", "73", "fixture-swagger-2.yaml"))
   783  	assert.Empty(t, res.Errors, "in fixture-swagger-2.yaml")
   784  
   785  	res, _ = loadAndValidate(t, filepath.Join("fixtures", "bugs", "73", "fixture-swagger-3.yaml"))
   786  	assert.Empty(t, res.Errors, "in fixture-swagger-3.yaml")
   787  
   788  	res, _ = loadAndValidate(t, filepath.Join("fixtures", "bugs", "73", "fixture-swagger-good.yaml"))
   789  	assert.Empty(t, res.Errors, " in fixture-swagger-good.yaml")
   790  }
   791  
   792  func TestSpec_Issue1341(t *testing.T) {
   793  	// testing recursive walk with defaults and examples
   794  	res, _ := loadAndValidate(t, filepath.Join("fixtures", "bugs", "1341", "fixture-1341-good.yaml"))
   795  	assert.Empty(t, res.Errors, " in fixture-1341-good.yaml")
   796  	assert.Len(t, res.Warnings, 1, " in fixture-1341-good.yaml")
   797  
   798  	res, _ = loadAndValidate(t, filepath.Join("fixtures", "bugs", "1341", "fixture-1341.yaml"))
   799  	assert.Empty(t, res.Errors, "in fixture-1341.yaml")
   800  	assert.Empty(t, res.Warnings, "in fixture-1341.yaml")
   801  
   802  	res, _ = loadAndValidate(t, filepath.Join("fixtures", "bugs", "1341", "fixture-1341-2.yaml"))
   803  	assert.Empty(t, res.Errors, "in fixture-1341-2.yaml")
   804  	assert.Empty(t, res.Warnings, "in fixture-1341-2.yaml")
   805  
   806  	res, _ = loadAndValidate(t, filepath.Join("fixtures", "bugs", "1341", "fixture-1341-3.yaml"))
   807  	assert.Empty(t, res.Errors, "in fixture-1341-3.yaml")
   808  	assert.Len(t, res.Warnings, 4, "in fixture-1341-3.yaml")
   809  
   810  	res, _ = loadAndValidate(t, filepath.Join("fixtures", "bugs", "1341", "fixture-1341-4.yaml"))
   811  	assert.Empty(t, res.Errors, "in fixture-1341-4.yaml")
   812  	assert.Empty(t, res.Warnings, "in fixture-1341-4.yaml")
   813  
   814  	res, _ = loadAndValidate(t, filepath.Join("fixtures", "bugs", "1341", "fixture-1341-5.yaml"))
   815  	assert.Len(t, res.Errors, 4, "in fixture-1341-5.yaml")
   816  	assert.Empty(t, res.Warnings, "in fixture-1341-5.yaml")
   817  }
   818  
   819  // test go-swagger/go-swagger#1614 (circular refs)
   820  func Test_Issue1614(t *testing.T) {
   821  	path := filepath.Join("fixtures", "bugs", "1614", "gitea.json")
   822  	testIssue(t, path, 0, 3)
   823  }
   824  
   825  // Test go-swagger/go-swagger#1621 (remote $ref)
   826  func Test_Issue1621(t *testing.T) {
   827  	path := filepath.Join("fixtures", "bugs", "1621", "fixture-1621.yaml")
   828  	testIssue(t, path, 0, 0)
   829  }
   830  
   831  // Test go-swagger/go-swagger#1429 (remote $ref)
   832  func Test_Issue1429(t *testing.T) {
   833  	path := filepath.Join("fixtures", "bugs", "1429", "swagger.yaml")
   834  	testIssue(t, path, 0, 0)
   835  }
   836  
   837  func TestSpec_ValidationTypeMismatch(t *testing.T) {
   838  	doc, err := loads.Spec(filepath.Join("fixtures", "validation", "type-keyword-mismatch.yaml"))
   839  	require.NoError(t, err)
   840  	validator := NewSpecValidator(doc.Schema(), strfmt.Default)
   841  	validator.spec = doc
   842  	validator.analyzer = analysis.New(doc.Spec())
   843  	res := validator.validateParameters()
   844  	assert.NotEmpty(t, res.Warnings)
   845  	assert.Len(t, res.Warnings, 3)
   846  
   847  	warnings := verifiedTestWarnings(res)
   848  	assert.Contains(t, warnings, `validation keywords of parameter "id" in path "/test/{id}/string" don't match its type string`)
   849  	assert.Contains(t, warnings, `validation keywords of parameter "id" in path "/test/{id}/integer" don't match its type integer`)
   850  	assert.Contains(t, warnings, `validation keywords of parameter "id" in path "/test/{id}/array" don't match its type array`)
   851  }
   852  
   853  func loadAndValidate(t testing.TB, fp string, early ...bool) (*Result, *Result) {
   854  	doc, err := loads.Spec(fp)
   855  	require.NoError(t, err)
   856  	require.NotNil(t, doc)
   857  	validator := NewSpecValidator(doc.Schema(), strfmt.Default)
   858  	// for testing, we enable "ContinueOnErrors" by default
   859  	if len(early) == 0 {
   860  		validator.Options = Opts{ContinueOnErrors: true}
   861  	} else {
   862  		for _, flag := range early {
   863  			validator.Options = Opts{ContinueOnErrors: flag}
   864  		}
   865  	}
   866  	return validator.Validate(doc)
   867  }
   868  
   869  func TestItemsProperty_Issue43(t *testing.T) {
   870  	for _, fixture := range []string{
   871  		"fixture-43.yaml",
   872  		"fixture-43-variants.yaml",
   873  		"fixture-1456.yaml",
   874  	} {
   875  		fp := filepath.Join("fixtures", "bugs", "43", fixture)
   876  		res, warnings := loadAndValidate(t, fp)
   877  		assert.Truef(t, res.IsValid(), "expected spec from %s to be valid", fixture)
   878  		assert.Emptyf(t, res.Errors, "expected no error in %s", fixture)
   879  		assert.Emptyf(t, res.Warnings, "expected no warning in %s", fixture)
   880  		assert.Emptyf(t, warnings, "expected no warning in %s", fixture)
   881  	}
   882  
   883  	fp := filepath.Join("fixtures", "bugs", "43", "fixture-43-fail.yaml")
   884  	res, _ := loadAndValidate(t, fp)
   885  	assert.Falsef(t, res.IsValid(), "expected spec to be invalid")
   886  	assert.Greater(t, len(res.Errors), 3)
   887  
   888  	fp = filepath.Join("fixtures", "validation", "fixture-1171.yaml")
   889  	res, _ = loadAndValidate(t, fp)
   890  	assert.Falsef(t, res.IsValid(), "expected spec to be invalid")
   891  	assert.Greater(t, len(res.Errors), 3)
   892  	found := false
   893  	for _, e := range res.Errors {
   894  		found = strings.Contains(e.Error(), "array requires items definition")
   895  		if found {
   896  			break
   897  		}
   898  	}
   899  	assert.True(t, found)
   900  }
   901  
   902  func Test_Issue2137(t *testing.T) {
   903  	fp := filepath.Join("fixtures", "bugs", "2137", "fixture-2137.yaml")
   904  	res, _ := loadAndValidate(t, fp)
   905  	assert.Falsef(t, res.IsValid(), "expected spec to be invalid")
   906  	found := false
   907  	for _, e := range res.Errors {
   908  		found = strings.Contains(e.Error(), `"test" is defined 2 times`)
   909  		if found {
   910  			break
   911  		}
   912  	}
   913  	assert.True(t, found)
   914  }
   915  
   916  func Test_Examples(t *testing.T) {
   917  	fp := filepath.Join("fixtures", "bugs", "2649", "swagger.yaml")
   918  
   919  	doc, err := loads.Spec(fp)
   920  	require.NoError(t, err)
   921  	require.NotNil(t, doc)
   922  
   923  	validator := NewSpecValidator(doc.Schema(), strfmt.Default)
   924  	validator.Options.SkipSchemataResult = true
   925  
   926  	res, _ := validator.Validate(doc)
   927  	if !assert.Truef(t, res.IsValid(), "expected spec to be valid") {
   928  		spew.Dump(res.Errors)
   929  	}
   930  }
   931  
   932  func Test_2866(t *testing.T) {
   933  	// exercises fixture from go-swagger/go-swagger#2866, a test in go-swagger
   934  	// that used to be problematic when using memory pools.
   935  
   936  	fp := filepath.Join("fixtures", "bugs", "2866", "2866.yaml")
   937  
   938  	doc, err := loads.Spec(fp)
   939  	require.NoError(t, err)
   940  	require.NotNil(t, doc)
   941  
   942  	require.NoError(t, Spec(doc, strfmt.Default))
   943  }
   944  

View as plain text