Skip to content

Commit efe8fb3

Browse files
authored
refactored expander and associated unit tests (#136)
* added more guards against panic when continuing on error * removed more debug printing * otherwise, no change in functionality Signed-off-by: Frederic BIDON <[email protected]>
1 parent 9dac817 commit efe8fb3

12 files changed

+515
-605
lines changed

cache.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,8 @@ func (s *simpleCache) ShallowClone() ResolutionCache {
4444

4545
// Get retrieves a cached URI
4646
func (s *simpleCache) Get(uri string) (interface{}, bool) {
47-
debugLog("getting %q from resolution cache", uri)
4847
s.lock.RLock()
4948
v, ok := s.store[uri]
50-
debugLog("got %q from resolution cache: %t", uri, ok)
5149

5250
s.lock.RUnlock()
5351
return v, ok

circular_test.go

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
package spec
2+
3+
import (
4+
"encoding/json"
5+
"io/ioutil"
6+
"net/http"
7+
"path/filepath"
8+
"testing"
9+
"time"
10+
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
func TestExpandCircular_Issue3(t *testing.T) {
16+
jazon := expandThisOrDieTrying(t, "fixtures/expansion/overflow.json")
17+
require.NotEmpty(t, jazon)
18+
19+
// TODO: assert $ref
20+
}
21+
22+
func TestExpandCircular_RefExpansion(t *testing.T) {
23+
carsDoc, err := jsonDoc("fixtures/expansion/circularRefs.json")
24+
require.NoError(t, err)
25+
26+
basePath, _ := absPath("fixtures/expansion/circularRefs.json")
27+
28+
spec := new(Swagger)
29+
require.NoError(t, json.Unmarshal(carsDoc, spec))
30+
31+
resolver := defaultSchemaLoader(spec, &ExpandOptions{RelativeBase: basePath}, nil, nil)
32+
33+
schema := spec.Definitions["car"]
34+
35+
assert.NotPanics(t, func() {
36+
_, err := expandSchema(schema, []string{"#/definitions/car"}, resolver, basePath)
37+
require.NoError(t, err)
38+
}, "Calling expand schema with circular refs, should not panic!")
39+
}
40+
41+
func TestExpandCircular_Spec2Expansion(t *testing.T) {
42+
// TODO: assert repeatable results (see commented section below)
43+
44+
fixturePath := filepath.Join("fixtures", "expansion", "circular-minimal.json")
45+
jazon := expandThisOrDieTrying(t, fixturePath)
46+
require.NotEmpty(t, jazon)
47+
48+
// assert stripped $ref in result
49+
assert.NotContainsf(t, jazon, "circular-minimal.json#/",
50+
"expected %s to be expanded with stripped circular $ref", fixturePath)
51+
52+
fixturePath = "fixtures/expansion/circularSpec2.json"
53+
jazon = expandThisOrDieTrying(t, fixturePath)
54+
assert.NotEmpty(t, jazon)
55+
56+
assert.NotContainsf(t, jazon, "circularSpec.json#/",
57+
"expected %s to be expanded with stripped circular $ref", fixturePath)
58+
59+
/*
60+
61+
At the moment, the result of expanding circular references is not stable,
62+
when several cycles have intersections:
63+
the spec structure is randomly walked through and mutating as expansion is carried out.
64+
detected cycles in $ref are not necessarily the shortest matches.
65+
66+
This may result in different, functionally correct expanded specs (e.g. with same validations)
67+
68+
for i := 0; i < 1; i++ {
69+
bbb := expandThisOrDieTrying(t, fixturePath)
70+
t.Log(bbb)
71+
if !assert.JSONEqf(t, jazon, bbb, "on iteration %d, we should have stable expanded spec", i) {
72+
t.FailNow()
73+
return
74+
}
75+
}
76+
*/
77+
}
78+
79+
func TestExpandCircular_MoreCircular(t *testing.T) {
80+
// Additional testcase for circular $ref (from go-openapi/validate):
81+
// - $ref with file = current file
82+
// - circular is located in remote file
83+
//
84+
// There are 4 variants to run:
85+
// - with/without $ref with local file (so its not really remote)
86+
// - with circular in a schema in #/responses
87+
// - with circular in a schema in #/parameters
88+
89+
fixturePath := "fixtures/more_circulars/spec.json"
90+
jazon := expandThisOrDieTrying(t, fixturePath)
91+
require.NotEmpty(t, jazon)
92+
assertRefInJSON(t, jazon, "item.json#/item")
93+
94+
fixturePath = "fixtures/more_circulars/spec2.json"
95+
jazon = expandThisOrDieTrying(t, fixturePath)
96+
require.NotEmpty(t, jazon)
97+
assertRefInJSON(t, jazon, "item2.json#/item")
98+
99+
fixturePath = "fixtures/more_circulars/spec3.json"
100+
jazon = expandThisOrDieTrying(t, fixturePath)
101+
require.NotEmpty(t, jazon)
102+
assertRefInJSON(t, jazon, "item.json#/item")
103+
104+
fixturePath = "fixtures/more_circulars/spec4.json"
105+
jazon = expandThisOrDieTrying(t, fixturePath)
106+
require.NotEmpty(t, jazon)
107+
assertRefInJSON(t, jazon, "item4.json#/item")
108+
}
109+
110+
func TestExpandCircular_Issue957(t *testing.T) {
111+
fixturePath := "fixtures/bugs/957/fixture-957.json"
112+
jazon := expandThisOrDieTrying(t, fixturePath)
113+
require.NotEmpty(t, jazon)
114+
115+
require.NotContainsf(t, jazon, "fixture-957.json#/",
116+
"expected %s to be expanded with stripped circular $ref", fixturePath)
117+
118+
assertRefInJSON(t, jazon, "#/definitions/")
119+
}
120+
121+
func TestExpandCircular_Bitbucket(t *testing.T) {
122+
// Additional testcase for circular $ref (from bitbucket api)
123+
124+
fixturePath := "fixtures/more_circulars/bitbucket.json"
125+
jazon := expandThisOrDieTrying(t, fixturePath)
126+
require.NotEmpty(t, jazon)
127+
128+
assertRefInJSON(t, jazon, "#/definitions/")
129+
}
130+
131+
func TestExpandCircular_ResponseWithRoot(t *testing.T) {
132+
rootDoc := new(Swagger)
133+
b, err := ioutil.ReadFile("fixtures/more_circulars/resp.json")
134+
require.NoError(t, err)
135+
136+
require.NoError(t, json.Unmarshal(b, rootDoc))
137+
138+
path := rootDoc.Paths.Paths["/api/v1/getx"]
139+
resp := path.Post.Responses.StatusCodeResponses[200]
140+
141+
thisCache := cacheOrDefault(nil)
142+
143+
// during the first response expand, refs are getting expanded,
144+
// so the following expands cannot properly resolve them w/o the document.
145+
// this happens in validator.Validate() when different validators try to expand the same mutable response.
146+
require.NoError(t, ExpandResponseWithRoot(&resp, rootDoc, thisCache))
147+
148+
bbb, _ := json.MarshalIndent(resp, "", " ")
149+
assertRefInJSON(t, string(bbb), "#/definitions/MyObj")
150+
151+
// do it again
152+
require.NoError(t, ExpandResponseWithRoot(&resp, rootDoc, thisCache))
153+
}
154+
155+
func TestExpandCircular_Issue415(t *testing.T) {
156+
jazon := expandThisOrDieTrying(t, "fixtures/expansion/clickmeter.json")
157+
require.NotEmpty(t, jazon)
158+
159+
assertRefInJSON(t, jazon, "#/definitions/")
160+
}
161+
162+
func TestExpandCircular_SpecExpansion(t *testing.T) {
163+
jazon := expandThisOrDieTrying(t, "fixtures/expansion/circularSpec.json")
164+
require.NotEmpty(t, jazon)
165+
166+
assertRefInJSON(t, jazon, "#/definitions/Book")
167+
}
168+
169+
func TestExpandCircular_RemoteCircularID(t *testing.T) {
170+
go func() {
171+
err := http.ListenAndServe("localhost:1234", http.FileServer(http.Dir("fixtures/more_circulars/remote")))
172+
if err != nil {
173+
panic(err.Error())
174+
}
175+
}()
176+
time.Sleep(100 * time.Millisecond)
177+
178+
fixturePath := "http://localhost:1234/tree"
179+
jazon := expandThisSchemaOrDieTrying(t, fixturePath)
180+
181+
sch := new(Schema)
182+
require.NoError(t, json.Unmarshal([]byte(jazon), sch))
183+
184+
require.NotPanics(t, func() {
185+
assert.NoError(t, ExpandSchemaWithBasePath(sch, nil, &ExpandOptions{}))
186+
})
187+
188+
fixturePath = "fixtures/more_circulars/with-id.json"
189+
jazon = expandThisOrDieTrying(t, fixturePath)
190+
191+
// cannot guarantee that the circular will always hook on the same $ref
192+
// but we can assert that there is only one
193+
m := rex.FindAllStringSubmatch(jazon, -1)
194+
require.NotEmpty(t, m)
195+
196+
refs := make(map[string]struct{}, 5)
197+
for _, matched := range m {
198+
subMatch := matched[1]
199+
refs[subMatch] = struct{}{}
200+
}
201+
202+
// TODO(fred): the expansion is incorrect (it was already, with an undetected empty $ref)
203+
// require.Len(t, refs, 1)
204+
}

0 commit comments

Comments
 (0)