...

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

Documentation: github.com/go-openapi/spec

     1  package spec
     2  
     3  import (
     4  	"encoding/json"
     5  	"net/http"
     6  	"net/http/httptest"
     7  	"os"
     8  	"path/filepath"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  )
    15  
    16  func TestExpandCircular_Issue3(t *testing.T) {
    17  	jazon, root := expandThisOrDieTrying(t, "fixtures/expansion/overflow.json")
    18  	require.NotEmpty(t, jazon)
    19  
    20  	// circular $ref point to the expanded root document
    21  	assertRefInJSON(t, jazon, "#/definitions")
    22  
    23  	// verify that all $ref can resolved against the new root schema
    24  	assertRefResolve(t, jazon, "", root)
    25  
    26  	// verify that all $ref can be expanded in the new root schema
    27  	assertRefExpand(t, jazon, "", root)
    28  }
    29  
    30  func TestExpandCircular_RefExpansion(t *testing.T) {
    31  	basePath := filepath.Join("fixtures", "expansion", "circularRefs.json")
    32  
    33  	carsDoc, err := jsonDoc(basePath)
    34  	require.NoError(t, err)
    35  
    36  	spec := new(Swagger)
    37  	require.NoError(t, json.Unmarshal(carsDoc, spec))
    38  
    39  	resolver := defaultSchemaLoader(spec, &ExpandOptions{RelativeBase: basePath}, nil, nil)
    40  
    41  	schema := spec.Definitions["car"]
    42  
    43  	_, err = expandSchema(schema, []string{"#/definitions/car"}, resolver, normalizeBase(basePath))
    44  	require.NoError(t, err)
    45  
    46  	jazon := asJSON(t, schema)
    47  
    48  	// circular $ref point to the expanded root document
    49  	// there are only 2 types with circular definitions
    50  	assertRefInJSONRegexp(t, jazon, "#/definitions/(car|category)")
    51  
    52  	// verify that all $ref can resolved against the new root schema
    53  	assertRefResolve(t, jazon, "", spec)
    54  
    55  	// verify that all $ref can be expanded in the new root schema
    56  	assertRefExpand(t, jazon, "", spec)
    57  }
    58  
    59  func TestExpandCircular_Spec2Expansion(t *testing.T) {
    60  	// TODO: assert repeatable results (see commented section below)
    61  
    62  	fixturePath := filepath.Join("fixtures", "expansion", "circular-minimal.json")
    63  	jazon, root := expandThisOrDieTrying(t, fixturePath)
    64  	require.NotEmpty(t, jazon)
    65  
    66  	// circular $ref are not always the same, but they sure are one of the nodes
    67  	assertRefInJSONRegexp(t, jazon, `#/definitions/node\d+`)
    68  
    69  	// circular $ref always resolve against the root
    70  	assertRefResolve(t, jazon, "", root)
    71  
    72  	// assert stripped $ref in result
    73  	assert.NotContainsf(t, jazon, "circular-minimal.json#/",
    74  		"expected %s to be expanded with stripped circular $ref", fixturePath)
    75  
    76  	fixturePath = filepath.Join("fixtures", "expansion", "circularSpec2.json")
    77  	jazon, root = expandThisOrDieTrying(t, fixturePath)
    78  	require.NotEmpty(t, jazon)
    79  
    80  	// circular $ref resolved against the expanded root document
    81  	assertRefInJSON(t, jazon, `#/definitions/`)
    82  
    83  	// circular $ref always resolve against the root
    84  	assertRefResolve(t, jazon, "", root)
    85  
    86  	// circular $ref can always be further expanded against the root
    87  	assertRefExpand(t, jazon, "", root)
    88  
    89  	assert.NotContainsf(t, jazon, "circularSpec.json#/",
    90  		"expected %s to be expanded with stripped circular $ref", fixturePath)
    91  
    92  	/*
    93  
    94  		At the moment, the result of expanding circular references is not stable,
    95  		when several cycles have intersections:
    96  		the spec structure is randomly walked through and mutating as expansion is carried out.
    97  		detected cycles in $ref are not necessarily the shortest matches.
    98  
    99  		This may result in different, functionally correct expanded specs (e.g. with same validations)
   100  
   101  			for i := 0; i < 1; i++ {
   102  				bbb := expandThisOrDieTrying(t, fixturePath)
   103  				t.Log(bbb)
   104  				if !assert.JSONEqf(t, jazon, bbb, "on iteration %d, we should have stable expanded spec", i) {
   105  					t.FailNow()
   106  					return
   107  				}
   108  			}
   109  	*/
   110  }
   111  
   112  func TestExpandCircular_MoreCircular(t *testing.T) {
   113  	// Additional testcase for circular $ref (from go-openapi/validate):
   114  	// - $ref with file = current file
   115  	// - circular is located in remote file
   116  	//
   117  	// There are 4 variants to run:
   118  	// - with/without $ref with local file (so its not really remote)
   119  	// - with circular in a schema in  #/responses
   120  	// - with circular in a schema in  #/parameters
   121  
   122  	fixturePath := filepath.Join("fixtures", "more_circulars", "spec.json")
   123  	jazon, root := expandThisOrDieTrying(t, fixturePath)
   124  	require.NotEmpty(t, jazon)
   125  	assertRefInJSON(t, jazon, "item.json#/item")
   126  	assertRefResolve(t, jazon, "", root, &ExpandOptions{RelativeBase: fixturePath})
   127  
   128  	fixturePath = filepath.Join("fixtures", "more_circulars", "spec2.json")
   129  	jazon, root = expandThisOrDieTrying(t, fixturePath)
   130  	require.NotEmpty(t, jazon)
   131  	assertRefInJSON(t, jazon, "item2.json#/item")
   132  	assertRefResolve(t, jazon, "", root, &ExpandOptions{RelativeBase: fixturePath})
   133  
   134  	fixturePath = filepath.Join("fixtures", "more_circulars", "spec3.json")
   135  	jazon, root = expandThisOrDieTrying(t, fixturePath)
   136  	require.NotEmpty(t, jazon)
   137  	assertRefInJSON(t, jazon, "item.json#/item")
   138  	assertRefResolve(t, jazon, "", root, &ExpandOptions{RelativeBase: fixturePath})
   139  
   140  	fixturePath = filepath.Join("fixtures", "more_circulars", "spec4.json")
   141  	jazon, root = expandThisOrDieTrying(t, fixturePath)
   142  	require.NotEmpty(t, jazon)
   143  	assertRefInJSON(t, jazon, "item4.json#/item")
   144  	assertRefResolve(t, jazon, "", root, &ExpandOptions{RelativeBase: fixturePath})
   145  }
   146  
   147  func TestExpandCircular_Issue957(t *testing.T) {
   148  	fixturePath := filepath.Join("fixtures", "bugs", "957", "fixture-957.json")
   149  	jazon, root := expandThisOrDieTrying(t, fixturePath)
   150  	require.NotEmpty(t, jazon)
   151  
   152  	require.NotContainsf(t, jazon, "fixture-957.json#/",
   153  		"expected %s to be expanded with stripped circular $ref", fixturePath)
   154  
   155  	assertRefInJSON(t, jazon, "#/definitions/")
   156  
   157  	assertRefResolve(t, jazon, "", root)
   158  
   159  	assertRefExpand(t, jazon, "", root)
   160  }
   161  
   162  func TestExpandCircular_Bitbucket(t *testing.T) {
   163  	// Additional testcase for circular $ref (from bitbucket api)
   164  
   165  	fixturePath := filepath.Join("fixtures", "more_circulars", "bitbucket.json")
   166  	jazon, root := expandThisOrDieTrying(t, fixturePath)
   167  	require.NotEmpty(t, jazon)
   168  
   169  	assertRefInJSON(t, jazon, "#/definitions/")
   170  
   171  	assertRefResolve(t, jazon, "", root)
   172  
   173  	assertRefExpand(t, jazon, "", root)
   174  }
   175  
   176  func TestExpandCircular_ResponseWithRoot(t *testing.T) {
   177  	rootDoc := new(Swagger)
   178  	b, err := os.ReadFile(filepath.Join("fixtures", "more_circulars", "resp.json"))
   179  	require.NoError(t, err)
   180  
   181  	require.NoError(t, json.Unmarshal(b, rootDoc))
   182  
   183  	path := rootDoc.Paths.Paths["/api/v1/getx"]
   184  	resp := path.Post.Responses.StatusCodeResponses[200]
   185  
   186  	thisCache := cacheOrDefault(nil)
   187  
   188  	// during the first response expand, refs are getting expanded,
   189  	// so the following expands cannot properly resolve them w/o the document.
   190  	// this happens in validator.Validate() when different validators try to expand the same mutable response.
   191  	require.NoError(t, ExpandResponseWithRoot(&resp, rootDoc, thisCache))
   192  
   193  	jazon := asJSON(t, resp)
   194  	assertRefInJSON(t, jazon, "#/definitions/MyObj")
   195  
   196  	// do it again
   197  	require.NoError(t, ExpandResponseWithRoot(&resp, rootDoc, thisCache))
   198  	jazon = asJSON(t, resp)
   199  	assertRefInJSON(t, jazon, "#/definitions/MyObj")
   200  }
   201  
   202  func TestExpandCircular_Issue415(t *testing.T) {
   203  	jazon, root := expandThisOrDieTrying(t, filepath.Join("fixtures", "expansion", "clickmeter.json"))
   204  	require.NotEmpty(t, jazon)
   205  
   206  	assertRefInJSON(t, jazon, "#/definitions/")
   207  	assertRefResolve(t, jazon, "", root)
   208  	assertRefExpand(t, jazon, "", root)
   209  }
   210  
   211  func TestExpandCircular_SpecExpansion(t *testing.T) {
   212  	jazon, root := expandThisOrDieTrying(t, filepath.Join("fixtures", "expansion", "circularSpec.json"))
   213  	require.NotEmpty(t, jazon)
   214  
   215  	assertRefInJSON(t, jazon, "#/definitions/Book")
   216  	assertRefResolve(t, jazon, "", root)
   217  	assertRefExpand(t, jazon, "", root)
   218  }
   219  
   220  func TestExpandCircular_RemoteCircularID(t *testing.T) {
   221  	go func() {
   222  		err := http.ListenAndServe("localhost:1234", http.FileServer(http.Dir("fixtures/more_circulars/remote"))) //#nosec
   223  		if err != nil {
   224  			panic(err.Error())
   225  		}
   226  	}()
   227  	time.Sleep(100 * time.Millisecond)
   228  
   229  	// from json-schema test suite testcase for remote with circular ID
   230  	fixturePath := "http://localhost:1234/tree"
   231  	jazon, root := expandThisSchemaOrDieTrying(t, fixturePath)
   232  	assertRefResolve(t, jazon, "", root, &ExpandOptions{RelativeBase: fixturePath})
   233  	assertRefExpand(t, jazon, "", root, &ExpandOptions{RelativeBase: fixturePath})
   234  
   235  	require.NoError(t, ExpandSchemaWithBasePath(root, nil, &ExpandOptions{}))
   236  
   237  	jazon = asJSON(t, root)
   238  
   239  	assertRefInJSONRegexp(t, jazon, "^http://localhost:1234/tree$") // $ref now point to the root doc
   240  
   241  	// a spec using the previous circular schema
   242  	fixtureSpecPath := filepath.Join("fixtures", "more_circulars", "with-id.json")
   243  	jazon, doc := expandThisOrDieTrying(t, fixtureSpecPath)
   244  
   245  	assertRefInJSON(t, jazon, fixturePath) // all remaining $ref's point to the circular ID (http://...)
   246  
   247  	// ResolveRef fails, because there are some remote $ref, but ResolveRefWithBasePath is successful
   248  	assertRefResolve(t, jazon, "", doc, &ExpandOptions{})
   249  	assertRefExpand(t, jazon, "", doc)
   250  }
   251  
   252  func TestCircular_RemoteExpandAzure(t *testing.T) {
   253  	// local copy of : https://raw.githubusercontent.com/Azure/azure-rest-api-specs/master/specification/network/resource-manager/Microsoft.Network/stable/2020-04-01/publicIpAddress.json
   254  	server := httptest.NewServer(http.FileServer(http.Dir("fixtures/azure")))
   255  	defer server.Close()
   256  
   257  	basePath := server.URL + "/publicIpAddress.json"
   258  	jazon, sch := expandThisOrDieTrying(t, basePath)
   259  
   260  	// check a pointer with escaped path
   261  	pth1, err := ResolvePathItem(sch, MustCreateRef("#/paths/~1subscriptions~1%7BsubscriptionId%7D~1providers~1Microsoft.Network~1publicIPAddresses"), nil)
   262  	require.NoError(t, err)
   263  	require.NotNil(t, pth1)
   264  
   265  	// check expected remaining $ref
   266  	assertRefInJSONRegexp(t, jazon, `^(#/definitions/)|(networkInterface.json#/definitions/)|(networkSecurityGroup.json#/definitions/)|(network.json#/definitions)|(virtualNetworkTap.json#/definitions/)|(virtualNetwork.json#/definitions/)|(privateEndpoint.json#/definitions/)|(\./examples/)`)
   267  
   268  	// check all $ref resolve in the expanded root
   269  	// (filter out the remaining $ref in x-ms-example extensions, which are not expanded)
   270  	t.Run("resolve $ref azure", func(t *testing.T) {
   271  		assertRefResolve(t, jazon, `\./example`, sch, &ExpandOptions{RelativeBase: basePath})
   272  	})
   273  }
   274  

View as plain text