...

Source file src/github.com/go-openapi/analysis/flatten_test.go

Documentation: github.com/go-openapi/analysis

     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 analysis
    16  
    17  import (
    18  	"bytes"
    19  	"encoding/json"
    20  	"fmt"
    21  	"io"
    22  	"log"
    23  	"os"
    24  	"path"
    25  	"path/filepath"
    26  	"regexp"
    27  	"strings"
    28  	"testing"
    29  
    30  	"github.com/go-openapi/analysis/internal/antest"
    31  	"github.com/go-openapi/analysis/internal/flatten/operations"
    32  	"github.com/go-openapi/jsonpointer"
    33  	"github.com/go-openapi/spec"
    34  	"github.com/stretchr/testify/assert"
    35  	"github.com/stretchr/testify/require"
    36  )
    37  
    38  var (
    39  	rex    = regexp.MustCompile(`"\$ref":\s*"(.+)"`)
    40  	oairex = regexp.MustCompile(`oiagen`)
    41  )
    42  
    43  type refFixture struct {
    44  	Key      string
    45  	Ref      spec.Ref
    46  	Location string
    47  	Expected interface{}
    48  }
    49  
    50  func makeRefFixtures() []refFixture {
    51  	return []refFixture{
    52  		{Key: "#/parameters/someParam/schema", Ref: spec.MustCreateRef("#/definitions/record")},
    53  		{Key: "#/paths/~1some~1where~1{id}/parameters/1/schema", Ref: spec.MustCreateRef("#/definitions/record")},
    54  		{Key: "#/paths/~1some~1where~1{id}/get/parameters/2/schema", Ref: spec.MustCreateRef("#/definitions/record")},
    55  		{Key: "#/responses/someResponse/schema", Ref: spec.MustCreateRef("#/definitions/record")},
    56  		{Key: "#/paths/~1some~1where~1{id}/get/responses/default/schema", Ref: spec.MustCreateRef("#/definitions/record")},
    57  		{Key: "#/paths/~1some~1where~1{id}/get/responses/200/schema", Ref: spec.MustCreateRef("#/definitions/tag")},
    58  		{Key: "#/definitions/namedAgain", Ref: spec.MustCreateRef("#/definitions/named")},
    59  		{Key: "#/definitions/datedTag/allOf/1", Ref: spec.MustCreateRef("#/definitions/tag")},
    60  		{Key: "#/definitions/datedRecords/items/1", Ref: spec.MustCreateRef("#/definitions/record")},
    61  		{Key: "#/definitions/datedTaggedRecords/items/1", Ref: spec.MustCreateRef("#/definitions/record")},
    62  		{Key: "#/definitions/datedTaggedRecords/additionalItems", Ref: spec.MustCreateRef("#/definitions/tag")},
    63  		{Key: "#/definitions/otherRecords/items", Ref: spec.MustCreateRef("#/definitions/record")},
    64  		{Key: "#/definitions/tags/additionalProperties", Ref: spec.MustCreateRef("#/definitions/tag")},
    65  		{Key: "#/definitions/namedThing/properties/name", Ref: spec.MustCreateRef("#/definitions/named")},
    66  	}
    67  }
    68  
    69  func TestFlatten_ImportExternalReferences(t *testing.T) {
    70  	log.SetOutput(io.Discard)
    71  	defer log.SetOutput(os.Stdout)
    72  
    73  	// this fixture is the same as external_definitions.yml, but no more
    74  	// checks if invalid construct is supported (i.e. $ref in parameters items)
    75  	bp := filepath.Join(".", "fixtures", "external_definitions_valid.yml")
    76  	sp := antest.LoadOrFail(t, bp)
    77  
    78  	opts := &FlattenOpts{
    79  		Spec:     New(sp),
    80  		BasePath: bp,
    81  	}
    82  
    83  	// NOTE(fredbi): now we no more expand, but merely resolve and iterate until there is no more ext ref
    84  	// so calling importExternalReferences is not idempotent
    85  	_, erx := importExternalReferences(opts)
    86  	require.NoError(t, erx)
    87  
    88  	require.Len(t, sp.Definitions, 11)
    89  	require.Contains(t, sp.Definitions, "tag")
    90  	require.Contains(t, sp.Definitions, "named")
    91  	require.Contains(t, sp.Definitions, "record")
    92  
    93  	for idx, toPin := range makeRefFixtures() {
    94  		i := idx
    95  		v := toPin
    96  		sp := sp // the pointer passed to Get(node) must be pinned
    97  
    98  		t.Run(fmt.Sprintf("import check ref [%d]: %q", i, v.Key), func(t *testing.T) {
    99  			t.Parallel()
   100  
   101  			ptr, err := jsonpointer.New(v.Key[1:])
   102  			require.NoErrorf(t, err, "error on jsonpointer.New(%q)", v.Key[1:])
   103  
   104  			vv, _, err := ptr.Get(sp)
   105  			require.NoErrorf(t, err, "error on ptr.Get(p for key=%s)", v.Key[1:])
   106  
   107  			switch tv := vv.(type) {
   108  			case *spec.Schema:
   109  				require.Equal(t, v.Ref.String(), tv.Ref.String(), "for %s", v.Key)
   110  
   111  			case spec.Schema:
   112  				require.Equal(t, v.Ref.String(), tv.Ref.String(), "for %s", v.Key)
   113  
   114  			case *spec.SchemaOrBool:
   115  				require.Equal(t, v.Ref.String(), tv.Schema.Ref.String(), "for %s", v.Key)
   116  
   117  			case *spec.SchemaOrArray:
   118  				require.Equal(t, v.Ref.String(), tv.Schema.Ref.String(), "for %s", v.Key)
   119  
   120  			default:
   121  				require.Fail(t, "unknown type", "got %T", vv)
   122  			}
   123  		})
   124  	}
   125  
   126  	// check the complete result for clarity
   127  	jazon := antest.AsJSON(t, sp)
   128  
   129  	expected, err := os.ReadFile(filepath.Join("fixtures", "expected", "external-references-1.json"))
   130  	require.NoError(t, err)
   131  
   132  	assert.JSONEq(t, string(expected), jazon)
   133  
   134  	// iterate again: this time all external schema $ref's should be reinlined
   135  	opts.Spec.reload()
   136  
   137  	_, err = importExternalReferences(&FlattenOpts{
   138  		Spec:     New(sp),
   139  		BasePath: bp,
   140  	})
   141  	require.NoError(t, err)
   142  
   143  	opts.Spec.reload()
   144  	for _, ref := range opts.Spec.references.schemas {
   145  		require.True(t, ref.HasFragmentOnly)
   146  	}
   147  
   148  	// now try complete flatten, with unused definitions removed
   149  	sp = antest.LoadOrFail(t, bp)
   150  	an := New(sp)
   151  
   152  	require.NoError(t, Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: true, Minimal: true, RemoveUnused: true}))
   153  
   154  	jazon = antest.AsJSON(t, an.spec)
   155  
   156  	expected, err = os.ReadFile(filepath.Join("fixtures", "expected", "external-references-2.json"))
   157  	require.NoError(t, err)
   158  
   159  	assert.JSONEq(t, string(expected), jazon)
   160  }
   161  
   162  func makeFlattenFixtures() []refFixture {
   163  	return []refFixture{
   164  		{
   165  			Key:      "#/responses/notFound/schema",
   166  			Location: "#/responses/notFound/schema",
   167  			Ref:      spec.MustCreateRef("#/definitions/error"),
   168  			Expected: nil,
   169  		},
   170  		{
   171  			Key:      "#/paths/~1some~1where~1{id}/parameters/0",
   172  			Location: "#/paths/~1some~1where~1{id}/parameters/0/name",
   173  			Ref:      spec.Ref{},
   174  			Expected: "id",
   175  		},
   176  		{
   177  			Key:      "#/paths/~1other~1place",
   178  			Location: "#/paths/~1other~1place/get/operationId",
   179  			Ref:      spec.Ref{},
   180  			Expected: "modelOp",
   181  		},
   182  		{
   183  			Key:      "#/paths/~1some~1where~1{id}/get/parameters/0",
   184  			Location: "#/paths/~1some~1where~1{id}/get/parameters/0/name",
   185  			Ref:      spec.Ref{},
   186  			Expected: "limit",
   187  		},
   188  		{
   189  			Key:      "#/paths/~1some~1where~1{id}/get/parameters/1",
   190  			Location: "#/paths/~1some~1where~1{id}/get/parameters/1/name",
   191  			Ref:      spec.Ref{},
   192  			Expected: "some",
   193  		},
   194  		{
   195  			Key:      "#/paths/~1some~1where~1{id}/get/parameters/2",
   196  			Location: "#/paths/~1some~1where~1{id}/get/parameters/2/name",
   197  			Ref:      spec.Ref{},
   198  			Expected: "other",
   199  		},
   200  		{
   201  			Key:      "#/paths/~1some~1where~1{id}/get/parameters/3",
   202  			Location: "#/paths/~1some~1where~1{id}/get/parameters/3/schema",
   203  			Ref:      spec.MustCreateRef("#/definitions/getSomeWhereIdParamsBody"),
   204  			Expected: "",
   205  		},
   206  		{
   207  			Key:      "#/paths/~1some~1where~1{id}/get/responses/200",
   208  			Location: "#/paths/~1some~1where~1{id}/get/responses/200/schema",
   209  			Ref:      spec.MustCreateRef("#/definitions/getSomeWhereIdOKBody"),
   210  			Expected: "",
   211  		},
   212  		{
   213  			Key:      "#/definitions/namedAgain",
   214  			Location: "",
   215  			Ref:      spec.MustCreateRef("#/definitions/named"),
   216  			Expected: "",
   217  		},
   218  		{
   219  			Key:      "#/definitions/namedThing/properties/name",
   220  			Location: "",
   221  			Ref:      spec.MustCreateRef("#/definitions/named"),
   222  			Expected: "",
   223  		},
   224  		{
   225  			Key:      "#/definitions/namedThing/properties/namedAgain",
   226  			Location: "",
   227  			Ref:      spec.MustCreateRef("#/definitions/namedAgain"),
   228  			Expected: "",
   229  		},
   230  		{
   231  			Key:      "#/definitions/datedRecords/items/1",
   232  			Location: "",
   233  			Ref:      spec.MustCreateRef("#/definitions/record"),
   234  			Expected: "",
   235  		},
   236  		{
   237  			Key:      "#/definitions/otherRecords/items",
   238  			Location: "",
   239  			Ref:      spec.MustCreateRef("#/definitions/record"),
   240  			Expected: "",
   241  		},
   242  		{
   243  			Key:      "#/definitions/tags/additionalProperties",
   244  			Location: "",
   245  			Ref:      spec.MustCreateRef("#/definitions/tag"),
   246  			Expected: "",
   247  		},
   248  		{
   249  			Key:      "#/definitions/datedTag/allOf/1",
   250  			Location: "",
   251  			Ref:      spec.MustCreateRef("#/definitions/tag"),
   252  			Expected: "",
   253  		},
   254  		{
   255  			Key:      "#/definitions/nestedThingRecord/items/1",
   256  			Location: "",
   257  			Ref:      spec.MustCreateRef("#/definitions/nestedThingRecordItems1"),
   258  			Expected: "",
   259  		},
   260  		{
   261  			Key:      "#/definitions/nestedThingRecord/items/2",
   262  			Location: "",
   263  			Ref:      spec.MustCreateRef("#/definitions/nestedThingRecordItems2"),
   264  			Expected: "",
   265  		},
   266  		{
   267  			Key:      "#/definitions/nestedThing/properties/record",
   268  			Location: "",
   269  			Ref:      spec.MustCreateRef("#/definitions/nestedThingRecord"),
   270  			Expected: "",
   271  		},
   272  		{
   273  			Key:      "#/definitions/named",
   274  			Location: "#/definitions/named/type",
   275  			Ref:      spec.Ref{},
   276  			Expected: spec.StringOrArray{"string"},
   277  		},
   278  		{
   279  			Key:      "#/definitions/error",
   280  			Location: "#/definitions/error/properties/id/type",
   281  			Ref:      spec.Ref{},
   282  			Expected: spec.StringOrArray{"integer"},
   283  		},
   284  		{
   285  			Key:      "#/definitions/record",
   286  			Location: "#/definitions/record/properties/createdAt/format",
   287  			Ref:      spec.Ref{},
   288  			Expected: "date-time",
   289  		},
   290  		{
   291  			Key:      "#/definitions/getSomeWhereIdOKBody",
   292  			Location: "#/definitions/getSomeWhereIdOKBody/properties/record",
   293  			Ref:      spec.MustCreateRef("#/definitions/nestedThing"),
   294  			Expected: nil,
   295  		},
   296  		{
   297  			Key:      "#/definitions/getSomeWhereIdParamsBody",
   298  			Location: "#/definitions/getSomeWhereIdParamsBody/properties/record",
   299  			Ref:      spec.MustCreateRef("#/definitions/getSomeWhereIdParamsBodyRecord"),
   300  			Expected: nil,
   301  		},
   302  		{
   303  			Key:      "#/definitions/getSomeWhereIdParamsBodyRecord",
   304  			Location: "#/definitions/getSomeWhereIdParamsBodyRecord/items/1",
   305  			Ref:      spec.MustCreateRef("#/definitions/getSomeWhereIdParamsBodyRecordItems1"),
   306  			Expected: nil,
   307  		},
   308  		{
   309  			Key:      "#/definitions/getSomeWhereIdParamsBodyRecord",
   310  			Location: "#/definitions/getSomeWhereIdParamsBodyRecord/items/2",
   311  			Ref:      spec.MustCreateRef("#/definitions/getSomeWhereIdParamsBodyRecordItems2"),
   312  			Expected: nil,
   313  		},
   314  		{
   315  			Key:      "#/definitions/getSomeWhereIdParamsBodyRecordItems2",
   316  			Location: "#/definitions/getSomeWhereIdParamsBodyRecordItems2/allOf/0/format",
   317  			Ref:      spec.Ref{},
   318  			Expected: "date",
   319  		},
   320  		{
   321  			Key:      "#/definitions/getSomeWhereIdParamsBodyRecordItems2Name",
   322  			Location: "#/definitions/getSomeWhereIdParamsBodyRecordItems2Name/properties/createdAt/format",
   323  			Ref:      spec.Ref{},
   324  			Expected: "date-time",
   325  		},
   326  		{
   327  			Key:      "#/definitions/getSomeWhereIdParamsBodyRecordItems2",
   328  			Location: "#/definitions/getSomeWhereIdParamsBodyRecordItems2/properties/name",
   329  			Ref:      spec.MustCreateRef("#/definitions/getSomeWhereIdParamsBodyRecordItems2Name"),
   330  			Expected: "date",
   331  		},
   332  	}
   333  }
   334  
   335  func TestFlatten_CheckRef(t *testing.T) {
   336  	bp := filepath.Join("fixtures", "flatten.yml")
   337  	sp := antest.LoadOrFail(t, bp)
   338  
   339  	require.NoError(t, Flatten(FlattenOpts{Spec: New(sp), BasePath: bp}))
   340  
   341  	for idx, toPin := range makeFlattenFixtures() {
   342  		i := idx
   343  		v := toPin
   344  		sp := sp
   345  
   346  		t.Run(fmt.Sprintf("check ref after flatten %q", v.Key), func(t *testing.T) {
   347  			pk := v.Key[1:]
   348  			if v.Location != "" {
   349  				pk = v.Location[1:]
   350  			}
   351  
   352  			ptr, err := jsonpointer.New(pk)
   353  			require.NoError(t, err, "at %d for %s", i, v.Key)
   354  
   355  			d, _, err := ptr.Get(sp)
   356  			require.NoError(t, err)
   357  
   358  			if v.Ref.String() == "" {
   359  				assert.Equal(t, v.Expected, d)
   360  
   361  				return
   362  			}
   363  
   364  			switch s := d.(type) {
   365  			case *spec.Schema:
   366  				assert.Equal(t, v.Ref.String(), s.Ref.String(), "at %d for %s", i, v.Key)
   367  
   368  			case spec.Schema:
   369  				assert.Equal(t, v.Ref.String(), s.Ref.String(), "at %d for %s", i, v.Key)
   370  
   371  			case *spec.SchemaOrArray:
   372  				var sRef spec.Ref
   373  				if s != nil && s.Schema != nil {
   374  					sRef = s.Schema.Ref
   375  				}
   376  				assert.Equal(t, v.Ref.String(), sRef.String(), "at %d for %s", i, v.Key)
   377  
   378  			case *spec.SchemaOrBool:
   379  				var sRef spec.Ref
   380  				if s != nil && s.Schema != nil {
   381  					sRef = s.Schema.Ref
   382  				}
   383  				assert.Equal(t, v.Ref.String(), sRef.String(), "at %d for %s", i, v.Key)
   384  
   385  			default:
   386  				assert.Fail(t, "unknown type", "got %T at %d for %s", d, i, v.Key)
   387  			}
   388  		})
   389  	}
   390  }
   391  
   392  func TestFlatten_FullWithOAIGen(t *testing.T) {
   393  	bp := filepath.Join("fixtures", "oaigen", "fixture-oaigen.yaml")
   394  	sp := antest.LoadOrFail(t, bp)
   395  
   396  	require.NoError(t, Flatten(FlattenOpts{
   397  		Spec: New(sp), BasePath: bp, Verbose: true,
   398  		Minimal: false, RemoveUnused: false,
   399  	}))
   400  
   401  	res := getInPath(t, sp, "/some/where", "/get/responses/204/schema")
   402  	assert.JSONEqf(t, `{"$ref": "#/definitions/uniqueName1"}`, res, "Expected a simple schema for response")
   403  
   404  	res = getInPath(t, sp, "/some/where", "/post/responses/204/schema")
   405  	assert.JSONEqf(t, `{"$ref": "#/definitions/d"}`, res, "Expected a simple schema for response")
   406  
   407  	res = getInPath(t, sp, "/some/where", "/get/responses/206/schema")
   408  	assert.JSONEqf(t, `{"$ref": "#/definitions/a"}`, res, "Expected a simple schema for response")
   409  
   410  	res = getInPath(t, sp, "/some/where", "/get/responses/304/schema")
   411  	assert.JSONEqf(t, `{"$ref": "#/definitions/transitive11"}`, res, "Expected a simple schema for response")
   412  
   413  	res = getInPath(t, sp, "/some/where", "/get/responses/205/schema")
   414  	assert.JSONEqf(t, `{"$ref": "#/definitions/b"}`, res, "Expected a simple schema for response")
   415  
   416  	res = getInPath(t, sp, "/some/where", "/post/responses/200/schema")
   417  	assert.JSONEqf(t, `{"type": "integer"}`, res, "Expected a simple schema for response")
   418  
   419  	res = getInPath(t, sp, "/some/where", "/post/responses/default/schema")
   420  	// pointer expanded
   421  	assert.JSONEqf(t, `{"type": "integer"}`, res, "Expected a simple schema for response")
   422  
   423  	res = getDefinition(t, sp, "a")
   424  	assert.JSONEqf(t,
   425  		`{"type": "object", "properties": { "a": { "$ref": "#/definitions/aAOAIGen" }}}`,
   426  		res, "Expected a simple schema for response")
   427  
   428  	res = getDefinition(t, sp, "aA")
   429  	assert.JSONEqf(t, `{"type": "string", "format": "date"}`, res, "Expected a simple schema for response")
   430  
   431  	res = getDefinition(t, sp, "aAOAIGen")
   432  	assert.JSONEqf(t, `
   433  	{
   434  	  "type": "object",
   435  	  "properties": {
   436  		"b": {
   437  		  "type": "integer"
   438  		}
   439  	  },
   440  	  "x-go-gen-location": "models"
   441      }
   442  	`, res, "Expected a simple schema for response")
   443  
   444  	res = getDefinition(t, sp, "bB")
   445  	assert.JSONEqf(t, `{"type": "string", "format": "date-time"}`, res, "Expected a simple schema for response")
   446  
   447  	_, ok := sp.Definitions["bItems"]
   448  	assert.Falsef(t, ok, "Did not expect a definition for %s", "bItems")
   449  
   450  	res = getDefinition(t, sp, "d")
   451  	assert.JSONEqf(t, `
   452  	{
   453  	  "type": "object",
   454  	  "properties": {
   455  	    "c": {
   456  		  "type": "integer"
   457  		}
   458        }
   459      }
   460     `, res, "Expected a simple schema for response")
   461  
   462  	res = getDefinition(t, sp, "b")
   463  	assert.JSONEqf(t, `
   464  	{
   465  	  "type": "array",
   466  	  "items": {
   467  	    "$ref": "#/definitions/d"
   468  	  }
   469  	}
   470  	`, res, "Expected a ref in response")
   471  
   472  	res = getDefinition(t, sp, "myBody")
   473  	assert.JSONEqf(t, `
   474  	{
   475  	  "type": "object",
   476  	  "properties": {
   477  	    "aA": {
   478  		  "$ref": "#/definitions/aA"
   479  		},
   480  		"prop1": {
   481  		  "type": "integer"
   482  	    }
   483        }
   484  	}
   485  	`, res, "Expected a simple schema for response")
   486  
   487  	res = getDefinition(t, sp, "uniqueName2")
   488  	assert.JSONEqf(t, `{"$ref": "#/definitions/notUniqueName2"}`, res, "Expected a simple schema for response")
   489  
   490  	res = getDefinition(t, sp, "notUniqueName2")
   491  	assert.JSONEqf(t, `
   492  	{
   493  	  "type": "object",
   494  	  "properties": {
   495  		"prop6": {
   496  		  "type": "integer"
   497  		}
   498  	  }
   499  	}
   500  	`, res, "Expected a simple schema for response")
   501  
   502  	res = getDefinition(t, sp, "uniqueName1")
   503  	assert.JSONEqf(t, `{
   504  		   "type": "object",
   505  		   "properties": {
   506  		    "prop5": {
   507  		     "type": "integer"
   508  		    }}}`, res, "Expected a simple schema for response")
   509  
   510  	// allOf container: []spec.Schema
   511  	res = getDefinition(t, sp, "getWithSliceContainerDefaultBody")
   512  	assert.JSONEqf(t, `{
   513  		"allOf": [
   514  		    {
   515  		     "$ref": "#/definitions/uniqueName3"
   516  		    },
   517  		    {
   518  		     "$ref": "#/definitions/getWithSliceContainerDefaultBodyAllOf1"
   519  		    }
   520  		   ],
   521  		   "x-go-gen-location": "operations"
   522  		    }`, res, "Expected a simple schema for response")
   523  
   524  	res = getDefinition(t, sp, "getWithSliceContainerDefaultBodyAllOf1")
   525  	assert.JSONEqf(t, `{
   526  		"type": "object",
   527  		   "properties": {
   528  		    "prop8": {
   529  		     "type": "string"
   530  		    }
   531  		   },
   532  		   "x-go-gen-location": "models"
   533  		    }`, res, "Expected a simple schema for response")
   534  
   535  	res = getDefinition(t, sp, "getWithTupleContainerDefaultBody")
   536  	assert.JSONEqf(t, `{
   537  		   "type": "array",
   538  		   "items": [
   539  		    {
   540  		     "$ref": "#/definitions/uniqueName3"
   541  		    },
   542  		    {
   543  		     "$ref": "#/definitions/getWithSliceContainerDefaultBodyAllOf1"
   544  		    }
   545  		   ],
   546  		   "x-go-gen-location": "operations"
   547  		    }`, res, "Expected a simple schema for response")
   548  
   549  	// with container SchemaOrArray
   550  	res = getDefinition(t, sp, "getWithTupleConflictDefaultBody")
   551  	assert.JSONEqf(t, `{
   552  		   "type": "array",
   553  		   "items": [
   554  		    {
   555  		     "$ref": "#/definitions/uniqueName4"
   556  		    },
   557  		    {
   558  		     "$ref": "#/definitions/getWithTupleConflictDefaultBodyItems1"
   559  		    }
   560  		   ],
   561  		   "x-go-gen-location": "operations"
   562  	}`, res, "Expected a simple schema for response")
   563  
   564  	res = getDefinition(t, sp, "getWithTupleConflictDefaultBodyItems1")
   565  	assert.JSONEqf(t, `{
   566  		   "type": "object",
   567  		   "properties": {
   568  		    "prop10": {
   569  		     "type": "string"
   570  		    }
   571  		   },
   572  		   "x-go-gen-location": "models"
   573  	}`, res, "Expected a simple schema for response")
   574  }
   575  
   576  func TestFlatten_MinimalWithOAIGen(t *testing.T) {
   577  	var sp *spec.Swagger
   578  	defer func() {
   579  		if t.Failed() && sp != nil {
   580  			t.Log(antest.AsJSON(t, sp))
   581  		}
   582  	}()
   583  
   584  	bp := filepath.Join("fixtures", "oaigen", "fixture-oaigen.yaml")
   585  	sp = antest.LoadOrFail(t, bp)
   586  
   587  	var logCapture bytes.Buffer
   588  	log.SetOutput(&logCapture)
   589  	defer log.SetOutput(os.Stdout)
   590  
   591  	require.NoError(t, Flatten(FlattenOpts{Spec: New(sp), BasePath: bp, Verbose: true, Minimal: true, RemoveUnused: false}))
   592  
   593  	msg := logCapture.String()
   594  	if !assert.NotContainsf(t, msg,
   595  		"warning: duplicate flattened definition name resolved as aAOAIGen", "Expected log message") {
   596  		t.Logf("Captured log: %s", msg)
   597  	}
   598  	if !assert.NotContainsf(t, msg,
   599  		"warning: duplicate flattened definition name resolved as uniqueName2OAIGen", "Expected log message") {
   600  		t.Logf("Captured log: %s", msg)
   601  	}
   602  	res := getInPath(t, sp, "/some/where", "/get/responses/204/schema")
   603  	assert.JSONEqf(t, `{"$ref": "#/definitions/uniqueName1"}`, res, "Expected a simple schema for response")
   604  
   605  	res = getInPath(t, sp, "/some/where", "/post/responses/204/schema")
   606  	assert.JSONEqf(t, `{"$ref": "#/definitions/d"}`, res, "Expected a simple schema for response")
   607  
   608  	res = getInPath(t, sp, "/some/where", "/get/responses/206/schema")
   609  	assert.JSONEqf(t, `{"$ref": "#/definitions/a"}`, res, "Expected a simple schema for response")
   610  
   611  	res = getInPath(t, sp, "/some/where", "/get/responses/304/schema")
   612  	assert.JSONEqf(t, `{"$ref": "#/definitions/transitive11"}`, res, "Expected a simple schema for response")
   613  
   614  	res = getInPath(t, sp, "/some/where", "/get/responses/205/schema")
   615  	assert.JSONEqf(t, `{"$ref": "#/definitions/b"}`, res, "Expected a simple schema for response")
   616  
   617  	res = getInPath(t, sp, "/some/where", "/post/responses/200/schema")
   618  	assert.JSONEqf(t, `{"type": "integer"}`, res, "Expected a simple schema for response")
   619  
   620  	res = getInPath(t, sp, "/some/where", "/post/responses/default/schema")
   621  	// This JSON pointer is expanded
   622  	assert.JSONEqf(t, `{"type": "integer"}`, res, "Expected a simple schema for response")
   623  
   624  	res = getDefinition(t, sp, "aA")
   625  	assert.JSONEqf(t, `{"type": "string", "format": "date"}`, res, "Expected a simple schema for response")
   626  
   627  	res = getDefinition(t, sp, "a")
   628  	assert.JSONEqf(t, `{
   629  		   "type": "object",
   630  		   "properties": {
   631  		    "a": {
   632  		     "type": "object",
   633  		     "properties": {
   634  		      "b": {
   635  		       "type": "integer"
   636  		      }
   637  		     }
   638  		    }
   639  		   }
   640  		  }`, res, "Expected a simple schema for response")
   641  
   642  	res = getDefinition(t, sp, "bB")
   643  	assert.JSONEqf(t, `{"type": "string", "format": "date-time"}`, res, "Expected a simple schema for response")
   644  
   645  	_, ok := sp.Definitions["bItems"]
   646  	assert.Falsef(t, ok, "Did not expect a definition for %s", "bItems")
   647  
   648  	res = getDefinition(t, sp, "d")
   649  	assert.JSONEqf(t, `{
   650  		   "type": "object",
   651  		   "properties": {
   652  		    "c": {
   653  		     "type": "integer"
   654  		    }
   655  		   }
   656  	}`, res, "Expected a simple schema for response")
   657  
   658  	res = getDefinition(t, sp, "b")
   659  	assert.JSONEqf(t, `{
   660  		   "type": "array",
   661  		   "items": {
   662  			   "$ref": "#/definitions/d"
   663  		   }
   664  	}`, res, "Expected a ref in response")
   665  
   666  	res = getDefinition(t, sp, "myBody")
   667  	assert.JSONEqf(t, `{
   668  		   "type": "object",
   669  		   "properties": {
   670  		    "aA": {
   671  		     "$ref": "#/definitions/aA"
   672  		    },
   673  		    "prop1": {
   674  		     "type": "integer"
   675  		    }
   676  		   }
   677  	}`, res, "Expected a simple schema for response")
   678  
   679  	res = getDefinition(t, sp, "uniqueName2")
   680  	assert.JSONEqf(t, `{"$ref": "#/definitions/notUniqueName2"}`, res, "Expected a simple schema for response")
   681  
   682  	// with allOf container: []spec.Schema
   683  	res = getInPath(t, sp, "/with/slice/container", "/get/responses/default/schema")
   684  	assert.JSONEqf(t, `{
   685   			"allOf": [
   686  		        {
   687  		         "$ref": "#/definitions/uniqueName3"
   688  		        },
   689  				{
   690  			     "$ref": "#/definitions/getWithSliceContainerDefaultBodyAllOf1"
   691  				}
   692  		       ]
   693  	}`, res, "Expected a simple schema for response")
   694  
   695  	// with tuple container
   696  	res = getInPath(t, sp, "/with/tuple/container", "/get/responses/default/schema")
   697  	assert.JSONEqf(t, `{
   698  		       "type": "array",
   699  		       "items": [
   700  		        {
   701  		         "$ref": "#/definitions/uniqueName3"
   702  		        },
   703  		        {
   704  		         "$ref": "#/definitions/getWithSliceContainerDefaultBodyAllOf1"
   705  		        }
   706  		       ]
   707  	}`, res, "Expected a simple schema for response")
   708  
   709  	// with SchemaOrArray container
   710  	res = getInPath(t, sp, "/with/tuple/conflict", "/get/responses/default/schema")
   711  	assert.JSONEqf(t, `{
   712  		       "type": "array",
   713  		       "items": [
   714  		        {
   715  		         "$ref": "#/definitions/uniqueName4"
   716  		        },
   717  		        {
   718  		         "type": "object",
   719  		         "properties": {
   720  		          "prop10": {
   721  		           "type": "string"
   722  		          }
   723  		         }
   724  		        }
   725  		       ]
   726  	}`, res, "Expected a simple schema for response")
   727  }
   728  
   729  func assertNoOAIGen(t *testing.T, bp string, sp *spec.Swagger) (success bool) {
   730  	var logCapture bytes.Buffer
   731  	log.SetOutput(&logCapture)
   732  	defer log.SetOutput(os.Stdout)
   733  
   734  	defer func() {
   735  		success = !t.Failed()
   736  	}()
   737  
   738  	require.NoError(t, Flatten(FlattenOpts{Spec: New(sp), BasePath: bp, Verbose: true, Minimal: false, RemoveUnused: false}))
   739  
   740  	msg := logCapture.String()
   741  	assert.NotContains(t, msg, "warning")
   742  
   743  	for k := range sp.Definitions {
   744  		require.NotContains(t, k, "OAIGen")
   745  	}
   746  
   747  	return
   748  }
   749  
   750  func TestFlatten_OAIGen(t *testing.T) {
   751  	for _, fixture := range []string{
   752  		filepath.Join("fixtures", "oaigen", "test3-swagger.yaml"),
   753  		filepath.Join("fixtures", "oaigen", "test3-bis-swagger.yaml"),
   754  		filepath.Join("fixtures", "oaigen", "test3-ter-swagger.yaml"),
   755  	} {
   756  		t.Run("flatten_oiagen_1260_"+fixture, func(t *testing.T) {
   757  			t.Parallel()
   758  
   759  			bp := filepath.Join("fixtures", "oaigen", "test3-swagger.yaml")
   760  			sp := antest.LoadOrFail(t, bp)
   761  
   762  			require.Truef(t, assertNoOAIGen(t, bp, sp), "did not expect an OAIGen definition here")
   763  		})
   764  	}
   765  }
   766  
   767  func TestMoreNameInlinedSchemas(t *testing.T) {
   768  	bp := filepath.Join("fixtures", "more_nested_inline_schemas.yml")
   769  	sp := antest.LoadOrFail(t, bp)
   770  
   771  	require.NoError(t, Flatten(FlattenOpts{Spec: New(sp), BasePath: bp, Verbose: true, Minimal: false, RemoveUnused: false}))
   772  
   773  	res := getInPath(t, sp, "/some/where/{id}", "/post/responses/200/schema")
   774  	assert.JSONEqf(t, `
   775  	{
   776  	  "type": "object",
   777  	  "additionalProperties": {
   778  		"type": "object",
   779  		"additionalProperties": {
   780  		  "type": "object", "additionalProperties": {
   781  			"$ref": "#/definitions/postSomeWhereIdOKBodyAdditionalPropertiesAdditionalPropertiesAdditionalProperties"
   782  		  }
   783  	    }
   784        }
   785  	}`,
   786  		res, "Expected a simple schema for response")
   787  
   788  	res = getInPath(t, sp, "/some/where/{id}", "/post/responses/204/schema")
   789  	assert.JSONEqf(t, `
   790  	{
   791  	  "type": "object",
   792  	  "additionalProperties": {
   793  		 "type": "array",
   794  		 "items": {
   795  		   "type": "object",
   796  		   "additionalProperties": {
   797  		     "type": "array",
   798  		     "items": {
   799  		       "type": "object",
   800  		       "additionalProperties": {
   801  		         "type": "array",
   802  		         "items": {
   803  				   "$ref": "#/definitions/postSomeWhereIdNoContentBodyAdditionalPropertiesItemsAdditionalPropertiesItemsAdditionalPropertiesItems"
   804  		         }
   805  		       }
   806  		     }
   807  		   }
   808  		 }
   809  	   }
   810  	 }
   811  `, res, "Expected a simple schema for response")
   812  }
   813  
   814  func TestRemoveUnused(t *testing.T) {
   815  	log.SetOutput(io.Discard)
   816  	defer log.SetOutput(os.Stdout)
   817  
   818  	bp := filepath.Join("fixtures", "oaigen", "fixture-oaigen.yaml")
   819  	sp := antest.LoadOrFail(t, bp)
   820  
   821  	require.NoError(t, Flatten(FlattenOpts{Spec: New(sp), BasePath: bp, Verbose: false, Minimal: true, RemoveUnused: true}))
   822  
   823  	assert.Nil(t, sp.Parameters)
   824  	assert.Nil(t, sp.Responses)
   825  
   826  	bp = filepath.Join("fixtures", "parameters", "fixture-parameters.yaml")
   827  	sp = antest.LoadOrFail(t, bp)
   828  	an := New(sp)
   829  
   830  	require.NoError(t, Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: false, Minimal: true, RemoveUnused: true}))
   831  
   832  	assert.Nil(t, sp.Parameters)
   833  	assert.Nil(t, sp.Responses)
   834  
   835  	op, ok := an.OperationFor("GET", "/some/where")
   836  	assert.True(t, ok)
   837  	assert.Lenf(t, op.Parameters, 4, "Expected 4 parameters expanded for this operation")
   838  	assert.Lenf(t, an.ParamsFor("GET", "/some/where"), 7, "Expected 7 parameters (with default) expanded for this operation")
   839  
   840  	op, ok = an.OperationFor("PATCH", "/some/remote")
   841  	assert.True(t, ok)
   842  	assert.Lenf(t, op.Parameters, 1, "Expected 1 parameter expanded for this operation")
   843  	assert.Lenf(t, an.ParamsFor("PATCH", "/some/remote"), 2, "Expected 2 parameters (with default) expanded for this operation")
   844  
   845  	_, ok = sp.Definitions["unused"]
   846  	assert.False(t, ok, "Did not expect to find #/definitions/unused")
   847  
   848  	bp = filepath.Join("fixtures", "parameters", "fixture-parameters.yaml")
   849  	sp = antest.LoadOrFail(t, bp)
   850  
   851  	require.NoError(t, Flatten(FlattenOpts{Spec: New(sp), BasePath: bp, Verbose: true, Minimal: false, RemoveUnused: true}))
   852  
   853  	assert.Nil(t, sp.Parameters)
   854  	assert.Nil(t, sp.Responses)
   855  	_, ok = sp.Definitions["unused"]
   856  	assert.Falsef(t, ok, "Did not expect to find #/definitions/unused")
   857  }
   858  
   859  func TestOperationIDs(t *testing.T) {
   860  	bp := filepath.Join("fixtures", "operations", "fixture-operations.yaml")
   861  	sp := antest.LoadOrFail(t, bp)
   862  
   863  	an := New(sp)
   864  	require.NoError(t, Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: false, Minimal: false, RemoveUnused: false}))
   865  
   866  	t.Run("should GatherOperations", func(t *testing.T) {
   867  		res := operations.GatherOperations(New(sp), []string{"getSomeWhere", "getSomeWhereElse"})
   868  
   869  		assert.Containsf(t, res, "getSomeWhere", "expected to find operation")
   870  		assert.Containsf(t, res, "getSomeWhereElse", "expected to find operation")
   871  		assert.NotContainsf(t, res, "postSomeWhere", "did not expect to find operation")
   872  	})
   873  
   874  	op, ok := an.OperationFor("GET", "/some/where/else")
   875  	assert.True(t, ok)
   876  	assert.NotNil(t, op)
   877  	assert.Len(t, an.ParametersFor("getSomeWhereElse"), 2)
   878  
   879  	op, ok = an.OperationFor("POST", "/some/where/else")
   880  	assert.True(t, ok)
   881  	assert.NotNil(t, op)
   882  	assert.Len(t, an.ParametersFor("postSomeWhereElse"), 1)
   883  
   884  	op, ok = an.OperationFor("PUT", "/some/where/else")
   885  	assert.True(t, ok)
   886  	assert.NotNil(t, op)
   887  	assert.Len(t, an.ParametersFor("putSomeWhereElse"), 1)
   888  
   889  	op, ok = an.OperationFor("PATCH", "/some/where/else")
   890  	assert.True(t, ok)
   891  	assert.NotNil(t, op)
   892  	assert.Len(t, an.ParametersFor("patchSomeWhereElse"), 1)
   893  
   894  	op, ok = an.OperationFor("DELETE", "/some/where/else")
   895  	assert.True(t, ok)
   896  	assert.NotNil(t, op)
   897  	assert.Len(t, an.ParametersFor("deleteSomeWhereElse"), 1)
   898  
   899  	op, ok = an.OperationFor("HEAD", "/some/where/else")
   900  	assert.True(t, ok)
   901  	assert.NotNil(t, op)
   902  	assert.Len(t, an.ParametersFor("headSomeWhereElse"), 1)
   903  
   904  	op, ok = an.OperationFor("OPTIONS", "/some/where/else")
   905  	assert.True(t, ok)
   906  	assert.NotNil(t, op)
   907  	assert.Len(t, an.ParametersFor("optionsSomeWhereElse"), 1)
   908  
   909  	assert.Empty(t, an.ParametersFor("outOfThisWorld"))
   910  }
   911  
   912  func TestFlatten_Pointers(t *testing.T) {
   913  	log.SetOutput(io.Discard)
   914  	defer log.SetOutput(os.Stdout)
   915  
   916  	bp := filepath.Join("fixtures", "pointers", "fixture-pointers.yaml")
   917  	sp := antest.LoadOrFail(t, bp)
   918  
   919  	an := New(sp)
   920  	require.NoError(t, Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: true, Minimal: true, RemoveUnused: false}))
   921  
   922  	// re-analyse and check all $ref's point to #/definitions
   923  	bn := New(sp)
   924  	for _, r := range bn.AllRefs() {
   925  		assert.Equal(t, definitionsPath, path.Dir(r.String()))
   926  	}
   927  }
   928  
   929  // unit test guards in flatten not easily testable with actual specs
   930  func TestFlatten_ErrorHandling(t *testing.T) {
   931  	log.SetOutput(io.Discard)
   932  	defer log.SetOutput(os.Stdout)
   933  
   934  	const wantedFailure = "Expected a failure"
   935  	bp := filepath.Join("fixtures", "errors", "fixture-unexpandable.yaml")
   936  
   937  	// invalid spec expansion
   938  	sp := antest.LoadOrFail(t, bp)
   939  	require.Errorf(t, Flatten(FlattenOpts{Spec: New(sp), BasePath: bp, Expand: true}), wantedFailure)
   940  
   941  	// reload original spec
   942  	sp = antest.LoadOrFail(t, bp)
   943  	require.Errorf(t, Flatten(FlattenOpts{Spec: New(sp), BasePath: bp, Expand: false}), wantedFailure)
   944  
   945  	bp = filepath.Join("fixtures", "errors", "fixture-unexpandable-2.yaml")
   946  	sp = antest.LoadOrFail(t, bp)
   947  	require.Errorf(t, Flatten(FlattenOpts{Spec: New(sp), BasePath: bp, Expand: false}), wantedFailure)
   948  
   949  	// reload original spec
   950  	sp = antest.LoadOrFail(t, bp)
   951  	require.Errorf(t, Flatten(FlattenOpts{Spec: New(sp), BasePath: bp, Minimal: true, Expand: false}), wantedFailure)
   952  }
   953  
   954  func TestFlatten_PointersLoop(t *testing.T) {
   955  	log.SetOutput(io.Discard)
   956  	defer log.SetOutput(os.Stdout)
   957  
   958  	bp := filepath.Join("fixtures", "pointers", "fixture-pointers-loop.yaml")
   959  	sp := antest.LoadOrFail(t, bp)
   960  
   961  	an := New(sp)
   962  	require.Error(t, Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: true, Minimal: true, RemoveUnused: false}))
   963  }
   964  
   965  func TestFlatten_Bitbucket(t *testing.T) {
   966  	log.SetOutput(io.Discard)
   967  	defer log.SetOutput(os.Stdout)
   968  
   969  	bp := filepath.Join("fixtures", "bugs", "bitbucket.json")
   970  	sp := antest.LoadOrFail(t, bp)
   971  
   972  	an := New(sp)
   973  	require.NoError(t, Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: true, Minimal: true, RemoveUnused: false}))
   974  
   975  	// reload original spec
   976  	sp = antest.LoadOrFail(t, bp)
   977  	an = New(sp)
   978  	require.NoError(t, Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: true, Minimal: false, RemoveUnused: false}))
   979  
   980  	// reload original spec
   981  	sp = antest.LoadOrFail(t, bp)
   982  	an = New(sp)
   983  	require.NoError(t, Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: true, Expand: true, RemoveUnused: false}))
   984  
   985  	// reload original spec
   986  	sp = antest.LoadOrFail(t, bp)
   987  	an = New(sp)
   988  	require.NoError(t, Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: true, Expand: true, RemoveUnused: true}))
   989  
   990  	assert.Len(t, sp.Definitions, 2) // only 2 remaining refs after expansion: circular $ref
   991  	_, ok := sp.Definitions["base_commit"]
   992  	assert.True(t, ok)
   993  	_, ok = sp.Definitions["repository"]
   994  	assert.True(t, ok)
   995  }
   996  
   997  func TestFlatten_Issue_1602(t *testing.T) {
   998  	log.SetOutput(io.Discard)
   999  	defer log.SetOutput(os.Stdout)
  1000  
  1001  	// $ref as schema to #/responses or #/parameters
  1002  
  1003  	// minimal repro test case
  1004  	bp := filepath.Join("fixtures", "bugs", "1602", "fixture-1602-1.yaml")
  1005  	sp := antest.LoadOrFail(t, bp)
  1006  	an := New(sp)
  1007  
  1008  	require.NoError(t, Flatten(FlattenOpts{
  1009  		Spec: an, BasePath: bp, Verbose: true,
  1010  		Minimal:      true,
  1011  		Expand:       false,
  1012  		RemoveUnused: false,
  1013  	}))
  1014  
  1015  	// reload spec
  1016  	sp = antest.LoadOrFail(t, bp)
  1017  	an = New(sp)
  1018  	require.NoError(t, Flatten(FlattenOpts{
  1019  		Spec: an, BasePath: bp, Verbose: false,
  1020  		Minimal:      false,
  1021  		Expand:       false,
  1022  		RemoveUnused: false,
  1023  	}))
  1024  
  1025  	// reload spec
  1026  	// with  prior expansion, a pseudo schema is produced
  1027  	sp = antest.LoadOrFail(t, bp)
  1028  	an = New(sp)
  1029  	require.NoError(t, Flatten(FlattenOpts{
  1030  		Spec: an, BasePath: bp, Verbose: false,
  1031  		Minimal:      false,
  1032  		Expand:       true,
  1033  		RemoveUnused: false,
  1034  	}))
  1035  }
  1036  
  1037  func TestFlatten_Issue_1602_All(t *testing.T) {
  1038  	for _, toPin := range []string{
  1039  		filepath.Join("fixtures", "bugs", "1602", "fixture-1602-full.yaml"),
  1040  		filepath.Join("fixtures", "bugs", "1602", "fixture-1602-1.yaml"),
  1041  		filepath.Join("fixtures", "bugs", "1602", "fixture-1602-2.yaml"),
  1042  		filepath.Join("fixtures", "bugs", "1602", "fixture-1602-3.yaml"),
  1043  		filepath.Join("fixtures", "bugs", "1602", "fixture-1602-4.yaml"),
  1044  		filepath.Join("fixtures", "bugs", "1602", "fixture-1602-5.yaml"),
  1045  		filepath.Join("fixtures", "bugs", "1602", "fixture-1602-6.yaml"),
  1046  	} {
  1047  		fixture := toPin
  1048  		t.Run("issue_1602_all_"+fixture, func(t *testing.T) {
  1049  			t.Parallel()
  1050  			sp := antest.LoadOrFail(t, fixture)
  1051  
  1052  			an := New(sp)
  1053  			require.NoError(t, Flatten(FlattenOpts{
  1054  				Spec: an, BasePath: fixture, Verbose: false, Minimal: true, Expand: false,
  1055  				RemoveUnused: false,
  1056  			}))
  1057  		})
  1058  	}
  1059  }
  1060  
  1061  func TestFlatten_Issue_1614(t *testing.T) {
  1062  	log.SetOutput(io.Discard)
  1063  	defer log.SetOutput(os.Stdout)
  1064  
  1065  	// $ref as schema to #/responses or #/parameters
  1066  	// test warnings
  1067  
  1068  	bp := filepath.Join("fixtures", "bugs", "1614", "gitea.yaml")
  1069  	sp := antest.LoadOrFail(t, bp)
  1070  	an := New(sp)
  1071  	require.NoError(t, Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: true, Minimal: true, Expand: false,
  1072  		RemoveUnused: false}))
  1073  
  1074  	// check that responses subject to warning have been expanded
  1075  	jazon := antest.AsJSON(t, sp)
  1076  	assert.NotContains(t, jazon, `#/responses/forbidden`)
  1077  	assert.NotContains(t, jazon, `#/responses/empty`)
  1078  }
  1079  
  1080  func TestFlatten_Issue_1621(t *testing.T) {
  1081  	// repeated remote refs
  1082  
  1083  	// minimal repro test case
  1084  	bp := filepath.Join("fixtures", "bugs", "1621", "fixture-1621.yaml")
  1085  	sp := antest.LoadOrFail(t, bp)
  1086  	an := New(sp)
  1087  	require.NoError(t, Flatten(FlattenOpts{
  1088  		Spec: an, BasePath: bp, Verbose: true,
  1089  		Minimal:      true,
  1090  		Expand:       false,
  1091  		RemoveUnused: false,
  1092  	}))
  1093  
  1094  	sch1 := sp.Paths.Paths["/v4/users/"].Get.Responses.StatusCodeResponses[200].Schema
  1095  	jazon := antest.AsJSON(t, sch1)
  1096  	assert.JSONEq(t, `{"type": "array", "items": {"$ref": "#/definitions/v4UserListItem" }}`, jazon)
  1097  
  1098  	sch2 := sp.Paths.Paths["/v4/user/"].Get.Responses.StatusCodeResponses[200].Schema
  1099  	jazon = antest.AsJSON(t, sch2)
  1100  	assert.JSONEq(t, `{"$ref": "#/definitions/v4UserListItem"}`, jazon)
  1101  
  1102  	sch3 := sp.Paths.Paths["/v4/users/{email}/"].Get.Responses.StatusCodeResponses[200].Schema
  1103  	jazon = antest.AsJSON(t, sch3)
  1104  	assert.JSONEq(t, `{"$ref": "#/definitions/v4UserListItem"}`, jazon)
  1105  }
  1106  
  1107  func TestFlatten_Issue_1796(t *testing.T) {
  1108  	// remote cyclic ref
  1109  	bp := filepath.Join("fixtures", "bugs", "1796", "queryIssue.json")
  1110  	sp := antest.LoadOrFail(t, bp)
  1111  	an := New(sp)
  1112  
  1113  	require.NoError(t, Flatten(FlattenOpts{
  1114  		Spec: an, BasePath: bp, Verbose: true,
  1115  		Minimal: true, Expand: false,
  1116  		RemoveUnused: false,
  1117  	}))
  1118  
  1119  	// assert all $ref match  "$ref": "#/definitions/something"
  1120  	for _, ref := range an.AllReferences() {
  1121  		assert.True(t, strings.HasPrefix(ref, "#/definitions"))
  1122  	}
  1123  }
  1124  
  1125  func TestFlatten_Issue_1767(t *testing.T) {
  1126  	// remote cyclic ref again
  1127  	bp := filepath.Join("fixtures", "bugs", "1767", "fixture-1767.yaml")
  1128  	sp := antest.LoadOrFail(t, bp)
  1129  	an := New(sp)
  1130  	require.NoError(t, Flatten(FlattenOpts{
  1131  		Spec: an, BasePath: bp, Verbose: true,
  1132  		Minimal: true, Expand: false,
  1133  		RemoveUnused: false,
  1134  	}))
  1135  
  1136  	// assert all $ref match  "$ref": "#/definitions/something"
  1137  	for _, ref := range an.AllReferences() {
  1138  		assert.True(t, strings.HasPrefix(ref, "#/definitions"))
  1139  	}
  1140  }
  1141  
  1142  func TestFlatten_Issue_1774(t *testing.T) {
  1143  	// remote cyclic ref again
  1144  	bp := filepath.Join("fixtures", "bugs", "1774", "def_api.yaml")
  1145  	sp := antest.LoadOrFail(t, bp)
  1146  	an := New(sp)
  1147  
  1148  	require.NoError(t, Flatten(FlattenOpts{
  1149  		Spec: an, BasePath: bp, Verbose: true,
  1150  		Minimal:      false,
  1151  		Expand:       false,
  1152  		RemoveUnused: false,
  1153  	}))
  1154  
  1155  	// assert all $ref match  "$ref": "#/definitions/something"
  1156  	for _, ref := range an.AllReferences() {
  1157  		assert.True(t, strings.HasPrefix(ref, "#/definitions"))
  1158  	}
  1159  }
  1160  
  1161  func TestFlatten_1429(t *testing.T) {
  1162  	// nested / remote $ref in response / param schemas
  1163  	// issue go-swagger/go-swagger#1429
  1164  	bp := filepath.Join("fixtures", "bugs", "1429", "swagger.yaml")
  1165  	sp := antest.LoadOrFail(t, bp)
  1166  
  1167  	an := New(sp)
  1168  	require.NoError(t, Flatten(FlattenOpts{
  1169  		Spec: an, BasePath: bp, Verbose: true,
  1170  		Minimal:      true,
  1171  		RemoveUnused: false,
  1172  	}))
  1173  }
  1174  
  1175  func TestFlatten_1851(t *testing.T) {
  1176  	// nested / remote $ref in response / param schemas
  1177  	// issue go-swagger/go-swagger#1851
  1178  	bp := filepath.Join("fixtures", "bugs", "1851", "fixture-1851.yaml")
  1179  	sp := antest.LoadOrFail(t, bp)
  1180  
  1181  	an := New(sp)
  1182  	require.NoError(t, Flatten(FlattenOpts{
  1183  		Spec: an, BasePath: bp, Verbose: true,
  1184  		Minimal:      true,
  1185  		RemoveUnused: false,
  1186  	}))
  1187  
  1188  	serverDefinition, ok := an.spec.Definitions["server"]
  1189  	assert.True(t, ok)
  1190  
  1191  	serverStatusDefinition, ok := an.spec.Definitions["serverStatus"]
  1192  	assert.True(t, ok)
  1193  
  1194  	serverStatusProperty, ok := serverDefinition.Properties["Status"]
  1195  	assert.True(t, ok)
  1196  
  1197  	jazon := antest.AsJSON(t, serverStatusProperty)
  1198  	assert.JSONEq(t, `{"$ref": "#/definitions/serverStatus"}`, jazon)
  1199  
  1200  	jazon = antest.AsJSON(t, serverStatusDefinition)
  1201  	assert.JSONEq(t, `{"type": "string", "enum": [ "OK", "Not OK" ]}`, jazon)
  1202  
  1203  	// additional test case: this one used to work
  1204  	bp = filepath.Join("fixtures", "bugs", "1851", "fixture-1851-2.yaml")
  1205  	sp = antest.LoadOrFail(t, bp)
  1206  
  1207  	an = New(sp)
  1208  	require.NoError(t, Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: true, Minimal: true, RemoveUnused: false}))
  1209  
  1210  	serverDefinition, ok = an.spec.Definitions["Server"]
  1211  	assert.True(t, ok)
  1212  
  1213  	serverStatusDefinition, ok = an.spec.Definitions["ServerStatus"]
  1214  	assert.True(t, ok)
  1215  
  1216  	serverStatusProperty, ok = serverDefinition.Properties["Status"]
  1217  	assert.True(t, ok)
  1218  
  1219  	jazon = antest.AsJSON(t, serverStatusProperty)
  1220  	assert.JSONEq(t, `{"$ref": "#/definitions/ServerStatus"}`, jazon)
  1221  
  1222  	jazon = antest.AsJSON(t, serverStatusDefinition)
  1223  	assert.JSONEq(t, `{"type": "string", "enum": [ "OK", "Not OK" ]}`, jazon)
  1224  }
  1225  
  1226  func TestFlatten_RemoteAbsolute(t *testing.T) {
  1227  	for _, toPin := range []string{
  1228  		// this one has simple remote ref pattern
  1229  		filepath.Join("fixtures", "bugs", "remote-absolute", "swagger-mini.json"),
  1230  		// this has no remote ref
  1231  		filepath.Join("fixtures", "bugs", "remote-absolute", "swagger.json"),
  1232  		// this one has local ref, no naming conflict (same as previous but with external ref imported)
  1233  		filepath.Join("fixtures", "bugs", "remote-absolute", "swagger-with-local-ref.json"),
  1234  		// this one has remote ref, no naming conflict (same as previous but with external ref imported)
  1235  		filepath.Join("fixtures", "bugs", "remote-absolute", "swagger-with-remote-only-ref.json"),
  1236  	} {
  1237  		fixture := toPin
  1238  		t.Run("remote_absolute_"+fixture, func(t *testing.T) {
  1239  			t.Parallel()
  1240  
  1241  			an := testFlattenWithDefaults(t, fixture)
  1242  			checkRefs(t, an.spec, true)
  1243  		})
  1244  	}
  1245  
  1246  	// This one has both remote and local ref with naming conflict.
  1247  	// This creates some "oiagen" definitions to address naming conflict,
  1248  	// which are removed by the oaigen pruning process (reinlined / merged with parents).
  1249  	an := testFlattenWithDefaults(t, filepath.Join("fixtures", "bugs", "remote-absolute", "swagger-with-ref.json"))
  1250  	checkRefs(t, an.spec, false)
  1251  }
  1252  
  1253  func TestFlatten_2092(t *testing.T) {
  1254  	log.SetOutput(io.Discard)
  1255  	defer log.SetOutput(os.Stdout)
  1256  
  1257  	bp := filepath.Join("fixtures", "bugs", "2092", "swagger.yaml")
  1258  	rexOAIGen := regexp.MustCompile(`(?i)("\$ref":\s*")(.?oaigen.?)"`)
  1259  
  1260  	// #2092 exhibits a stability issue: repeat 100 times the process to make sure it is stable
  1261  	sp := antest.LoadOrFail(t, bp)
  1262  	an := New(sp)
  1263  	require.NoError(t, Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: true, Minimal: true, RemoveUnused: false}))
  1264  	firstJSONMinimal := antest.AsJSON(t, an.spec)
  1265  
  1266  	// verify we don't have dangling oaigen refs
  1267  	require.Falsef(t, rexOAIGen.MatchString(firstJSONMinimal), "unmatched regexp for: %s", firstJSONMinimal)
  1268  
  1269  	sp = antest.LoadOrFail(t, bp)
  1270  	an = New(sp)
  1271  	require.NoError(t, Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: true, Minimal: false, RemoveUnused: false}))
  1272  	firstJSONFull := antest.AsJSON(t, an.spec)
  1273  
  1274  	// verify we don't have dangling oaigen refs
  1275  	require.Falsef(t, rexOAIGen.MatchString(firstJSONFull), "unmatched regexp for: %s", firstJSONFull)
  1276  
  1277  	for i := 0; i < 10; i++ {
  1278  		t.Run(fmt.Sprintf("issue_2092_%d", i), func(t *testing.T) {
  1279  			t.Parallel()
  1280  
  1281  			// verify that we produce a stable result
  1282  			sp := antest.LoadOrFail(t, bp)
  1283  			an := New(sp)
  1284  
  1285  			require.NoError(t, Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: true, Minimal: true, RemoveUnused: false}))
  1286  
  1287  			jazon := antest.AsJSON(t, an.spec)
  1288  			assert.JSONEq(t, firstJSONMinimal, jazon)
  1289  
  1290  			require.NoError(t, Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: true, Minimal: true, RemoveUnused: true}))
  1291  
  1292  			sp = antest.LoadOrFail(t, bp)
  1293  			an = New(sp)
  1294  
  1295  			require.NoError(t, Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: true, Minimal: false, RemoveUnused: false}))
  1296  
  1297  			jazon = antest.AsJSON(t, an.spec)
  1298  			assert.JSONEq(t, firstJSONFull, jazon)
  1299  		})
  1300  	}
  1301  }
  1302  
  1303  func TestFlatten_2113(t *testing.T) {
  1304  	// flatten $ref under path
  1305  	log.SetOutput(io.Discard)
  1306  	defer log.SetOutput(os.Stdout)
  1307  
  1308  	bp := filepath.Join("fixtures", "bugs", "2113", "base.yaml")
  1309  	sp := antest.LoadOrFail(t, bp)
  1310  	an := New(sp)
  1311  
  1312  	require.NoError(t, Flatten(FlattenOpts{
  1313  		Spec: an, BasePath: bp, Verbose: true,
  1314  		Expand:       true,
  1315  		RemoveUnused: false,
  1316  	}))
  1317  
  1318  	sp = antest.LoadOrFail(t, bp)
  1319  	an = New(sp)
  1320  
  1321  	require.NoError(t, Flatten(FlattenOpts{
  1322  		Spec: an, BasePath: bp, Verbose: true,
  1323  		Minimal:      true,
  1324  		RemoveUnused: false,
  1325  	}))
  1326  
  1327  	jazon := antest.AsJSON(t, sp)
  1328  
  1329  	expected, err := os.ReadFile(filepath.Join("fixtures", "expected", "issue-2113.json"))
  1330  	require.NoError(t, err)
  1331  
  1332  	require.JSONEq(t, string(expected), jazon)
  1333  }
  1334  
  1335  func TestFlatten_2334(t *testing.T) {
  1336  	// flatten $ref without altering case
  1337  	log.SetOutput(io.Discard)
  1338  	defer log.SetOutput(os.Stdout)
  1339  
  1340  	bp := filepath.Join("fixtures", "bugs", "2334", "swagger.yaml")
  1341  	sp := antest.LoadOrFail(t, bp)
  1342  	an := New(sp)
  1343  
  1344  	require.NoError(t, Flatten(FlattenOpts{
  1345  		Spec: an, BasePath: bp, Verbose: false,
  1346  		Expand:       false,
  1347  		Minimal:      true,
  1348  		RemoveUnused: true,
  1349  		KeepNames:    true,
  1350  	}))
  1351  
  1352  	jazon := antest.AsJSON(t, sp)
  1353  
  1354  	assert.Contains(t, jazon, `"$ref": "#/definitions/Bar"`)
  1355  	assert.Contains(t, jazon, `"Bar":`)
  1356  	assert.Contains(t, jazon, `"Baz":`)
  1357  }
  1358  
  1359  func TestFlatten_1898(t *testing.T) {
  1360  	log.SetOutput(io.Discard)
  1361  	defer log.SetOutput(os.Stdout)
  1362  
  1363  	bp := filepath.Join("fixtures", "bugs", "1898", "swagger.json")
  1364  	sp := antest.LoadOrFail(t, bp)
  1365  	an := New(sp)
  1366  
  1367  	require.NoError(t, Flatten(FlattenOpts{
  1368  		Spec: an, BasePath: bp, Verbose: true,
  1369  		Expand:       false,
  1370  		RemoveUnused: false,
  1371  	}))
  1372  	op, ok := an.OperationFor("GET", "/example/v2/GetEvents")
  1373  	require.True(t, ok)
  1374  
  1375  	resp, _, ok := op.SuccessResponse()
  1376  	require.True(t, ok)
  1377  
  1378  	require.Equal(t, "#/definitions/xStreamDefinitionsV2EventMsg", resp.Schema.Ref.String())
  1379  	def, ok := sp.Definitions["xStreamDefinitionsV2EventMsg"]
  1380  	require.True(t, ok)
  1381  	require.Len(t, def.Properties, 2)
  1382  	require.Contains(t, def.Properties, "error")
  1383  	require.Contains(t, def.Properties, "result")
  1384  }
  1385  
  1386  func TestFlatten_RemoveUnused_2657(t *testing.T) {
  1387  	log.SetOutput(io.Discard)
  1388  	defer log.SetOutput(os.Stdout)
  1389  
  1390  	bp := filepath.Join("fixtures", "bugs", "2657", "schema.json")
  1391  	sp := antest.LoadOrFail(t, bp)
  1392  	an := New(sp)
  1393  
  1394  	require.NoError(t, Flatten(FlattenOpts{
  1395  		Spec: an, BasePath: bp, Verbose: true,
  1396  		Minimal:      true,
  1397  		Expand:       false,
  1398  		RemoveUnused: true,
  1399  	}))
  1400  	require.Empty(t, sp.Definitions)
  1401  }
  1402  
  1403  func TestFlatten_Relative_2743(t *testing.T) {
  1404  	log.SetOutput(io.Discard)
  1405  	defer log.SetOutput(os.Stdout)
  1406  
  1407  	t.Run("used to work, but should NOT", func(t *testing.T) {
  1408  		bp := filepath.Join("fixtures", "bugs", "2743", "working", "spec.yaml")
  1409  		sp := antest.LoadOrFail(t, bp)
  1410  		an := New(sp)
  1411  
  1412  		require.Error(t, Flatten(FlattenOpts{
  1413  			Spec: an, BasePath: bp, Verbose: true,
  1414  			Minimal: true,
  1415  			Expand:  false,
  1416  		}))
  1417  	})
  1418  
  1419  	t.Run("used not to, but should work", func(t *testing.T) {
  1420  		bp := filepath.Join("fixtures", "bugs", "2743", "not-working", "spec.yaml")
  1421  		sp := antest.LoadOrFail(t, bp)
  1422  		an := New(sp)
  1423  
  1424  		require.NoError(t, Flatten(FlattenOpts{
  1425  			Spec: an, BasePath: bp, Verbose: true,
  1426  			Minimal: true,
  1427  			Expand:  false,
  1428  		}))
  1429  	})
  1430  }
  1431  
  1432  func getDefinition(t testing.TB, sp *spec.Swagger, key string) string {
  1433  	d, ok := sp.Definitions[key]
  1434  	require.Truef(t, ok, "Expected definition for %s", key)
  1435  	res, err := json.Marshal(d)
  1436  	if err != nil {
  1437  		panic(err)
  1438  	}
  1439  
  1440  	return string(res)
  1441  }
  1442  
  1443  func getInPath(t testing.TB, sp *spec.Swagger, path, key string) string {
  1444  	ptr, erp := jsonpointer.New(key)
  1445  	require.NoError(t, erp, "at %s no key", key)
  1446  
  1447  	d, _, erg := ptr.Get(sp.Paths.Paths[path])
  1448  	require.NoError(t, erg, "at %s no value for %s", path, key)
  1449  
  1450  	res, err := json.Marshal(d)
  1451  	if err != nil {
  1452  		panic(err)
  1453  	}
  1454  
  1455  	return string(res)
  1456  }
  1457  
  1458  func checkRefs(t testing.TB, spec *spec.Swagger, expectNoConflict bool) {
  1459  	// all $ref resolve locally
  1460  	jazon := antest.AsJSON(t, spec)
  1461  	m := rex.FindAllStringSubmatch(jazon, -1)
  1462  	require.NotNil(t, m)
  1463  
  1464  	for _, matched := range m {
  1465  		subMatch := matched[1]
  1466  		assert.True(t, strings.HasPrefix(subMatch, "#/definitions/"),
  1467  			"expected $ref to be inlined, got: %s", matched[0])
  1468  	}
  1469  
  1470  	if expectNoConflict {
  1471  		// no naming conflict
  1472  		m := oairex.FindAllStringSubmatch(jazon, -1)
  1473  		assert.Empty(t, m)
  1474  	}
  1475  }
  1476  
  1477  func testFlattenWithDefaults(t *testing.T, bp string) *Spec {
  1478  	sp := antest.LoadOrFail(t, bp)
  1479  	an := New(sp)
  1480  	require.NoError(t, Flatten(FlattenOpts{
  1481  		Spec:         an,
  1482  		BasePath:     bp,
  1483  		Verbose:      true,
  1484  		Minimal:      true,
  1485  		RemoveUnused: false,
  1486  	}))
  1487  
  1488  	return an
  1489  }
  1490  

View as plain text