...

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

Documentation: github.com/go-openapi/spec

     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 spec_test
    16  
    17  import (
    18  	"path/filepath"
    19  	"strings"
    20  	"testing"
    21  
    22  	"github.com/go-openapi/spec"
    23  	"github.com/stretchr/testify/assert"
    24  	"github.com/stretchr/testify/require"
    25  )
    26  
    27  // Test unitary fixture for dev and bug fixing
    28  
    29  func TestSpec_Issue2743(t *testing.T) {
    30  	t.Run("should expand but produce unresolvable $ref", func(t *testing.T) {
    31  		path := filepath.Join("fixtures", "bugs", "2743", "working", "spec.yaml")
    32  		sp := loadOrFail(t, path)
    33  		require.NoError(t,
    34  			spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: true, PathLoader: testLoader}),
    35  		)
    36  
    37  		t.Run("all $ref do not resolve when expanding again", func(t *testing.T) {
    38  			err := spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: false, PathLoader: testLoader})
    39  			require.Error(t, err)
    40  			require.ErrorContains(t, err, filepath.FromSlash("swagger/paths/swagger/user/index.yml"))
    41  		})
    42  	})
    43  
    44  	t.Run("should expand and produce resolvable $ref", func(t *testing.T) {
    45  		path := filepath.Join("fixtures", "bugs", "2743", "not-working", "spec.yaml")
    46  		sp := loadOrFail(t, path)
    47  		require.NoError(t,
    48  			spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: true, PathLoader: testLoader}),
    49  		)
    50  
    51  		t.Run("all $ref properly reolve when expanding again", func(t *testing.T) {
    52  			require.NoError(t,
    53  				spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: false, PathLoader: testLoader}),
    54  			)
    55  			require.NotContainsf(t, asJSON(t, sp), "$ref", "all $ref's should have been expanded properly")
    56  		})
    57  	})
    58  }
    59  
    60  func TestSpec_Issue1429(t *testing.T) {
    61  	path := filepath.Join("fixtures", "bugs", "1429", "swagger.yaml")
    62  
    63  	// load and full expand
    64  	sp := loadOrFail(t, path)
    65  	err := spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: false, PathLoader: testLoader})
    66  	require.NoError(t, err)
    67  
    68  	// assert well expanded
    69  	require.Truef(t, (sp.Paths != nil && sp.Paths.Paths != nil), "expected paths to be available in fixture")
    70  
    71  	assertPaths1429(t, sp)
    72  
    73  	for _, def := range sp.Definitions {
    74  		assert.Empty(t, def.Ref)
    75  	}
    76  
    77  	// reload and SkipSchemas: true
    78  	sp = loadOrFail(t, path)
    79  	err = spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: true, PathLoader: testLoader})
    80  	require.NoError(t, err)
    81  
    82  	// assert well resolved
    83  	require.Truef(t, (sp.Paths != nil && sp.Paths.Paths != nil), "expected paths to be available in fixture")
    84  
    85  	assertPaths1429SkipSchema(t, sp)
    86  
    87  	for _, def := range sp.Definitions {
    88  		assert.Contains(t, def.Ref.String(), "responses.yaml#/definitions/")
    89  	}
    90  }
    91  
    92  func assertPaths1429(t testing.TB, sp *spec.Swagger) {
    93  	for _, pi := range sp.Paths.Paths {
    94  		for _, param := range pi.Get.Parameters {
    95  			require.NotNilf(t, param.Schema, "expected param schema not to be nil")
    96  			// all param fixtures are body param with schema
    97  			// all $ref expanded
    98  			assert.Equal(t, "", param.Schema.Ref.String())
    99  		}
   100  
   101  		for code, response := range pi.Get.Responses.StatusCodeResponses {
   102  			// all response fixtures are with StatusCodeResponses, but 200
   103  			if code == 200 {
   104  				assert.Nilf(t, response.Schema, "expected response schema to be nil")
   105  				continue
   106  			}
   107  			require.NotNilf(t, response.Schema, "expected response schema not to be nil")
   108  			assert.Equal(t, "", response.Schema.Ref.String())
   109  		}
   110  	}
   111  }
   112  
   113  func assertPaths1429SkipSchema(t testing.TB, sp *spec.Swagger) {
   114  	for _, pi := range sp.Paths.Paths {
   115  		for _, param := range pi.Get.Parameters {
   116  			require.NotNilf(t, param.Schema, "expected param schema not to be nil")
   117  
   118  			// all param fixtures are body param with schema
   119  			switch param.Name {
   120  			case "plainRequest":
   121  				// this one is expanded
   122  				assert.Equal(t, "", param.Schema.Ref.String())
   123  				continue
   124  			case "nestedBody":
   125  				// this one is local
   126  				assert.Truef(t, strings.HasPrefix(param.Schema.Ref.String(), "#/definitions/"),
   127  					"expected rooted definitions $ref, got: %s", param.Schema.Ref.String())
   128  				continue
   129  			case "remoteRequest":
   130  				assert.Contains(t, param.Schema.Ref.String(), "remote/remote.yaml#/")
   131  				continue
   132  			}
   133  			assert.Contains(t, param.Schema.Ref.String(), "responses.yaml#/")
   134  
   135  		}
   136  
   137  		for code, response := range pi.Get.Responses.StatusCodeResponses {
   138  			// all response fixtures are with StatusCodeResponses, but 200
   139  			switch code {
   140  			case 200:
   141  				assert.Nilf(t, response.Schema, "expected response schema to be nil")
   142  				continue
   143  			case 204:
   144  				assert.Contains(t, response.Schema.Ref.String(), "remote/remote.yaml#/")
   145  				continue
   146  			case 404:
   147  				assert.Equal(t, "", response.Schema.Ref.String())
   148  				continue
   149  			}
   150  			assert.Containsf(t, response.Schema.Ref.String(), "responses.yaml#/", "expected remote ref at resp. %d", code)
   151  		}
   152  	}
   153  }
   154  
   155  func TestSpec_MoreLocalExpansion(t *testing.T) {
   156  	path := filepath.Join("fixtures", "local_expansion", "spec2.yaml")
   157  
   158  	// load and full expand
   159  	sp := loadOrFail(t, path)
   160  	require.NoError(t, spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: false, PathLoader: testLoader}))
   161  
   162  	// asserts all $ref are expanded
   163  	assert.NotContains(t, asJSON(t, sp), `"$ref"`)
   164  }
   165  
   166  func TestSpec_Issue69(t *testing.T) {
   167  	// this checks expansion for the dapperbox spec (circular ref issues)
   168  
   169  	path := filepath.Join("fixtures", "bugs", "69", "dapperbox.json")
   170  
   171  	// expand with relative path
   172  	// load and expand
   173  	sp := loadOrFail(t, path)
   174  	require.NoError(t, spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: false, PathLoader: testLoader}))
   175  
   176  	// asserts all $ref expanded
   177  	jazon := asJSON(t, sp)
   178  
   179  	// circular $ref are not expanded: however, they point to the expanded root document
   180  
   181  	// assert all $ref match  "$ref": "#/definitions/something"
   182  	assertRefInJSON(t, jazon, "#/definitions")
   183  
   184  	// assert all $ref expand correctly against the spec
   185  	assertRefExpand(t, jazon, "", sp)
   186  }
   187  
   188  func TestSpec_Issue1621(t *testing.T) {
   189  	path := filepath.Join("fixtures", "bugs", "1621", "fixture-1621.yaml")
   190  
   191  	// expand with relative path
   192  	// load and expand
   193  	sp := loadOrFail(t, path)
   194  
   195  	err := spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: false, PathLoader: testLoader})
   196  	require.NoError(t, err)
   197  
   198  	// asserts all $ref expanded
   199  	jazon := asJSON(t, sp)
   200  
   201  	assertNoRef(t, jazon)
   202  }
   203  
   204  func TestSpec_Issue1614(t *testing.T) {
   205  	path := filepath.Join("fixtures", "bugs", "1614", "gitea.json")
   206  
   207  	// expand with relative path
   208  	// load and expand
   209  	sp := loadOrFail(t, path)
   210  	require.NoError(t, spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: false, PathLoader: testLoader}))
   211  
   212  	// asserts all $ref expanded
   213  	jazon := asJSON(t, sp)
   214  
   215  	// assert all $ref match  "$ref": "#/definitions/something"
   216  	assertRefInJSON(t, jazon, "#/definitions")
   217  
   218  	// assert all $ref expand correctly against the spec
   219  	assertRefExpand(t, jazon, "", sp)
   220  
   221  	// now with option CircularRefAbsolute: circular $ref are not denormalized and are kept absolute.
   222  	// This option is essentially for debugging purpose.
   223  	sp = loadOrFail(t, path)
   224  	require.NoError(t, spec.ExpandSpec(sp, &spec.ExpandOptions{
   225  		RelativeBase:        path,
   226  		SkipSchemas:         false,
   227  		AbsoluteCircularRef: true,
   228  		PathLoader:          testLoader,
   229  	}))
   230  
   231  	// asserts all $ref expanded
   232  	jazon = asJSON(t, sp)
   233  
   234  	// assert all $ref match  "$ref": "file://{file}#/definitions/something"
   235  	assertRefInJSONRegexp(t, jazon, `file://.*/gitea.json#/definitions/`)
   236  
   237  	// assert all $ref expand correctly against the spec
   238  	assertRefExpand(t, jazon, "", sp, &spec.ExpandOptions{RelativeBase: path})
   239  }
   240  
   241  func TestSpec_Issue2113(t *testing.T) {
   242  	// this checks expansion with nested specs
   243  	path := filepath.Join("fixtures", "bugs", "2113", "base.yaml")
   244  
   245  	// load and expand
   246  	sp := loadOrFail(t, path)
   247  	err := spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: false, PathLoader: testLoader})
   248  	require.NoError(t, err)
   249  
   250  	// asserts all $ref expanded
   251  	jazon := asJSON(t, sp)
   252  
   253  	// assert all $ref match have been expanded
   254  	assertNoRef(t, jazon)
   255  
   256  	// now trying with SkipSchemas
   257  	sp = loadOrFail(t, path)
   258  	err = spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: true, PathLoader: testLoader})
   259  	require.NoError(t, err)
   260  
   261  	jazon = asJSON(t, sp)
   262  
   263  	// assert all $ref match
   264  	assertRefInJSONRegexp(t, jazon, `^(schemas/dummy/dummy.yaml)|(schemas/example/example.yaml)`)
   265  
   266  	// assert all $ref resolve correctly against the spec
   267  	assertRefResolve(t, jazon, "", sp, &spec.ExpandOptions{RelativeBase: path, PathLoader: testLoader})
   268  
   269  	// assert all $ref expand correctly against the spec
   270  	assertRefExpand(t, jazon, "", sp, &spec.ExpandOptions{RelativeBase: path, PathLoader: testLoader})
   271  }
   272  
   273  func TestSpec_Issue2113_External(t *testing.T) {
   274  	// Exercises the SkipSchema mode (used by spec flattening in go-openapi/analysis).
   275  	// Provides more ground for testing with schemas nested in $refs
   276  
   277  	// this checks expansion with nested specs
   278  	path := filepath.Join("fixtures", "skipschema", "external_definitions_valid.yml")
   279  
   280  	// load and expand, skipping schema expansion
   281  	sp := loadOrFail(t, path)
   282  	require.NoError(t, spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: true, PathLoader: testLoader}))
   283  
   284  	// asserts all $ref are expanded as expected
   285  	jazon := asJSON(t, sp)
   286  
   287  	assertRefInJSONRegexp(t, jazon, `^(external/definitions.yml#/definitions)|(external/errors.yml#/error)|(external/nestedParams.yml#/bodyParam)`)
   288  
   289  	// assert all $ref resolve correctly against the spec
   290  	assertRefResolve(t, jazon, "", sp, &spec.ExpandOptions{RelativeBase: path, PathLoader: testLoader})
   291  
   292  	// assert all $ref in jazon expand correctly against the spec
   293  	assertRefExpand(t, jazon, "", sp, &spec.ExpandOptions{RelativeBase: path, PathLoader: testLoader})
   294  
   295  	// expansion can be iterated again, including schemas
   296  	require.NoError(t, spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: false, PathLoader: testLoader}))
   297  
   298  	jazon = asJSON(t, sp)
   299  	assertNoRef(t, jazon)
   300  
   301  	// load and expand everything
   302  	sp = loadOrFail(t, path)
   303  	require.NoError(t, spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: false, PathLoader: testLoader}))
   304  
   305  	jazon = asJSON(t, sp)
   306  	assertNoRef(t, jazon)
   307  }
   308  
   309  func TestSpec_Issue2113_SkipSchema(t *testing.T) {
   310  	// Exercises the SkipSchema mode from spec flattening in go-openapi/analysis
   311  	// Provides more ground for testing with schemas nested in $refs
   312  
   313  	// this checks expansion with nested specs
   314  	path := filepath.Join("fixtures", "flatten", "flatten.yml")
   315  
   316  	// load and expand, skipping schema expansion
   317  	sp := loadOrFail(t, path)
   318  	require.NoError(t, spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: true, PathLoader: testLoader}))
   319  
   320  	jazon := asJSON(t, sp)
   321  
   322  	// asserts all $ref are expanded as expected
   323  	assertRefInJSONRegexp(t, jazon, `^(external/definitions.yml#/definitions)|(#/definitions/namedAgain)|(external/errors.yml#/error)`)
   324  
   325  	// assert all $ref resolve correctly against the spec
   326  	assertRefResolve(t, jazon, "", sp, &spec.ExpandOptions{RelativeBase: path, PathLoader: testLoader})
   327  
   328  	assertRefExpand(t, jazon, "", sp, &spec.ExpandOptions{RelativeBase: path, PathLoader: testLoader})
   329  
   330  	// load and expand, including schemas
   331  	sp = loadOrFail(t, path)
   332  	require.NoError(t, spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: false, PathLoader: testLoader}))
   333  
   334  	jazon = asJSON(t, sp)
   335  	assertNoRef(t, jazon)
   336  }
   337  
   338  func TestSpec_PointersLoop(t *testing.T) {
   339  	// this a spec that cannot be flattened (self-referencing pointer).
   340  	// however, it should be expanded without errors
   341  
   342  	// this checks expansion with nested specs
   343  	path := filepath.Join("fixtures", "more_circulars", "pointers", "fixture-pointers-loop.yaml")
   344  
   345  	// load and expand, skipping schema expansion
   346  	sp := loadOrFail(t, path)
   347  	require.NoError(t, spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: true, PathLoader: testLoader}))
   348  
   349  	jazon := asJSON(t, sp)
   350  	assertRefExpand(t, jazon, "", sp, &spec.ExpandOptions{RelativeBase: path, PathLoader: testLoader})
   351  
   352  	sp = loadOrFail(t, path)
   353  	require.NoError(t, spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: false, PathLoader: testLoader}))
   354  
   355  	// cannot guarantee which ref will be kept, but only one remains: expand reduces all $ref down
   356  	// to the last self-referencing one (the one picked changes from one run to another, depending
   357  	// on where during the walk the cycle is detected).
   358  	jazon = asJSON(t, sp)
   359  
   360  	m := rex.FindAllStringSubmatch(jazon, -1)
   361  	require.NotEmpty(t, m)
   362  
   363  	refs := make(map[string]struct{}, 5)
   364  	for _, matched := range m {
   365  		subMatch := matched[1]
   366  		refs[subMatch] = struct{}{}
   367  	}
   368  	require.Len(t, refs, 1)
   369  
   370  	assertRefExpand(t, jazon, "", sp, &spec.ExpandOptions{RelativeBase: path, PathLoader: testLoader})
   371  }
   372  
   373  func TestSpec_Issue102(t *testing.T) {
   374  	// go-openapi/validate/issues#102
   375  	path := filepath.Join("fixtures", "bugs", "102", "fixture-102.json")
   376  	sp := loadOrFail(t, path)
   377  
   378  	require.NoError(t, spec.ExpandSpec(sp, nil))
   379  
   380  	jazon := asJSON(t, sp)
   381  	assertRefInJSONRegexp(t, jazon, `^#/definitions/Error$`)
   382  
   383  	sp = loadOrFail(t, path)
   384  	sch := spec.RefSchema("#/definitions/Error")
   385  	require.NoError(t, spec.ExpandSchema(sch, sp, nil))
   386  
   387  	jazon = asJSON(t, sch)
   388  	assertRefInJSONRegexp(t, jazon, "^#/definitions/Error$")
   389  
   390  	sp = loadOrFail(t, path)
   391  	sch = spec.RefSchema("#/definitions/Error")
   392  	resp := spec.NewResponse().WithDescription("ok").WithSchema(sch)
   393  	require.NoError(t, spec.ExpandResponseWithRoot(resp, sp, nil))
   394  
   395  	jazon = asJSON(t, resp)
   396  	assertRefInJSONRegexp(t, jazon, "^#/definitions/Error$")
   397  
   398  	sp = loadOrFail(t, path)
   399  	sch = spec.RefSchema("#/definitions/Error")
   400  	param := spec.BodyParam("error", sch)
   401  	require.NoError(t, spec.ExpandParameterWithRoot(param, sp, nil))
   402  
   403  	jazon = asJSON(t, resp)
   404  	assertRefInJSONRegexp(t, jazon, "^#/definitions/Error$")
   405  }
   406  

View as plain text