1package analysis
2
3import (
4	"encoding/json"
5	"fmt"
6	"path"
7	"path/filepath"
8	"testing"
9
10	"net/http"
11	"net/http/httptest"
12
13	"github.com/go-openapi/spec"
14	"github.com/stretchr/testify/assert"
15)
16
17var knownSchemas = []*spec.Schema{
18	spec.BoolProperty(),                  // 0
19	spec.StringProperty(),                // 1
20	spec.Int8Property(),                  // 2
21	spec.Int16Property(),                 // 3
22	spec.Int32Property(),                 // 4
23	spec.Int64Property(),                 // 5
24	spec.Float32Property(),               // 6
25	spec.Float64Property(),               // 7
26	spec.DateProperty(),                  // 8
27	spec.DateTimeProperty(),              // 9
28	(&spec.Schema{}),                     // 10
29	(&spec.Schema{}).Typed("object", ""), // 11
30	(&spec.Schema{}).Typed("", ""),       // 12
31	(&spec.Schema{}).Typed("", "uuid"),   // 13
32}
33
34func newCObj() *spec.Schema {
35	return (&spec.Schema{}).Typed("object", "").SetProperty("id", *spec.Int64Property())
36}
37
38var complexObject = newCObj()
39
40var complexSchemas = []*spec.Schema{
41	complexObject,
42	spec.ArrayProperty(complexObject),
43	spec.MapProperty(complexObject),
44}
45
46func knownRefs(base string) []spec.Ref {
47	urls := []string{"bool", "string", "integer", "float", "date", "object", "format"}
48
49	result := make([]spec.Ref, 0, len(urls))
50	for _, u := range urls {
51		result = append(result, spec.MustCreateRef(fmt.Sprintf("%s/%s", base, path.Join("known", u))))
52	}
53	return result
54}
55
56func complexRefs(base string) []spec.Ref {
57	urls := []string{"object", "array", "map"}
58
59	result := make([]spec.Ref, 0, len(urls))
60	for _, u := range urls {
61		result = append(result, spec.MustCreateRef(fmt.Sprintf("%s/%s", base, path.Join("complex", u))))
62	}
63	return result
64}
65
66func refServer() *httptest.Server {
67	mux := http.NewServeMux()
68	mux.Handle("/known/bool", schemaHandler(knownSchemas[0]))
69	mux.Handle("/known/string", schemaHandler(knownSchemas[1]))
70	mux.Handle("/known/integer", schemaHandler(knownSchemas[5]))
71	mux.Handle("/known/float", schemaHandler(knownSchemas[6]))
72	mux.Handle("/known/date", schemaHandler(knownSchemas[8]))
73	mux.Handle("/known/object", schemaHandler(knownSchemas[11]))
74	mux.Handle("/known/format", schemaHandler(knownSchemas[13]))
75
76	mux.Handle("/complex/object", schemaHandler(complexSchemas[0]))
77	mux.Handle("/complex/array", schemaHandler(complexSchemas[1]))
78	mux.Handle("/complex/map", schemaHandler(complexSchemas[2]))
79
80	return httptest.NewServer(mux)
81}
82
83func refSchema(ref spec.Ref) *spec.Schema {
84	return &spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
85}
86
87func schemaHandler(schema *spec.Schema) http.Handler {
88	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
89		writeJSON(w, schema)
90	})
91}
92
93func writeJSON(w http.ResponseWriter, data interface{}) {
94	w.Header().Add("Content-Type", "application/json")
95	w.WriteHeader(http.StatusOK)
96	enc := json.NewEncoder(w)
97	if err := enc.Encode(data); err != nil {
98		panic(err)
99	}
100}
101
102func TestSchemaAnalysis_KnownTypes(t *testing.T) {
103	for i, v := range knownSchemas {
104		sch, err := Schema(SchemaOpts{Schema: v})
105		if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) {
106			assert.True(t, sch.IsKnownType, "item at %d should be a known type", i)
107		}
108	}
109	for i, v := range complexSchemas {
110		sch, err := Schema(SchemaOpts{Schema: v})
111		if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) {
112			assert.False(t, sch.IsKnownType, "item at %d should not be a known type", i)
113		}
114	}
115
116	serv := refServer()
117	defer serv.Close()
118
119	for i, ref := range knownRefs(serv.URL) {
120		sch, err := Schema(SchemaOpts{Schema: refSchema(ref)})
121		if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) {
122			assert.True(t, sch.IsKnownType, "item at %d should be a known type", i)
123		}
124	}
125	for i, ref := range complexRefs(serv.URL) {
126		sch, err := Schema(SchemaOpts{Schema: refSchema(ref)})
127		if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) {
128			assert.False(t, sch.IsKnownType, "item at %d should not be a known type", i)
129		}
130	}
131}
132
133func TestSchemaAnalysis_Array(t *testing.T) {
134	for i, v := range append(knownSchemas, (&spec.Schema{}).Typed("array", "")) {
135		sch, err := Schema(SchemaOpts{Schema: spec.ArrayProperty(v)})
136		if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) {
137			assert.True(t, sch.IsArray, "item at %d should be an array type", i)
138			assert.True(t, sch.IsSimpleArray, "item at %d should be a simple array type", i)
139		}
140	}
141
142	for i, v := range complexSchemas {
143		sch, err := Schema(SchemaOpts{Schema: spec.ArrayProperty(v)})
144		if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) {
145			assert.True(t, sch.IsArray, "item at %d should be an array type", i)
146			assert.False(t, sch.IsSimpleArray, "item at %d should not be a simple array type", i)
147		}
148	}
149
150	serv := refServer()
151	defer serv.Close()
152
153	for i, ref := range knownRefs(serv.URL) {
154		sch, err := Schema(SchemaOpts{Schema: spec.ArrayProperty(refSchema(ref))})
155		if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) {
156			assert.True(t, sch.IsArray, "item at %d should be an array type", i)
157			assert.True(t, sch.IsSimpleArray, "item at %d should be a simple array type", i)
158		}
159	}
160	for i, ref := range complexRefs(serv.URL) {
161		sch, err := Schema(SchemaOpts{Schema: spec.ArrayProperty(refSchema(ref))})
162		if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) {
163			assert.False(t, sch.IsKnownType, "item at %d should not be a known type", i)
164			assert.True(t, sch.IsArray, "item at %d should be an array type", i)
165			assert.False(t, sch.IsSimpleArray, "item at %d should not be a simple array type", i)
166		}
167	}
168
169	// edge case: unrestricted array (beyond Swagger)
170	at := spec.ArrayProperty(nil)
171	at.Items = nil
172	sch, err := Schema(SchemaOpts{Schema: at})
173	if assert.NoError(t, err) {
174		assert.True(t, sch.IsArray)
175		assert.False(t, sch.IsTuple)
176		assert.False(t, sch.IsKnownType)
177		assert.True(t, sch.IsSimpleSchema)
178	}
179
180	// unrestricted array with explicit empty schema
181	at = spec.ArrayProperty(nil)
182	at.Items = &spec.SchemaOrArray{}
183	sch, err = Schema(SchemaOpts{Schema: at})
184	if assert.NoError(t, err) {
185		assert.True(t, sch.IsArray)
186		assert.False(t, sch.IsTuple)
187		assert.False(t, sch.IsKnownType)
188		assert.True(t, sch.IsSimpleSchema)
189	}
190}
191
192func TestSchemaAnalysis_Map(t *testing.T) {
193	for i, v := range append(knownSchemas, spec.MapProperty(nil)) {
194		sch, err := Schema(SchemaOpts{Schema: spec.MapProperty(v)})
195		if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) {
196			assert.True(t, sch.IsMap, "item at %d should be a map type", i)
197			assert.True(t, sch.IsSimpleMap, "item at %d should be a simple map type", i)
198		}
199	}
200
201	for i, v := range complexSchemas {
202		sch, err := Schema(SchemaOpts{Schema: spec.MapProperty(v)})
203		if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) {
204			assert.True(t, sch.IsMap, "item at %d should be a map type", i)
205			assert.False(t, sch.IsSimpleMap, "item at %d should not be a simple map type", i)
206		}
207	}
208}
209
210func TestSchemaAnalysis_ExtendedObject(t *testing.T) {
211	for i, v := range knownSchemas {
212		wex := spec.MapProperty(v).SetProperty("name", *spec.StringProperty())
213		sch, err := Schema(SchemaOpts{Schema: wex})
214		if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) {
215			assert.True(t, sch.IsExtendedObject, "item at %d should be an extended map object type", i)
216			assert.False(t, sch.IsMap, "item at %d should not be a map type", i)
217			assert.False(t, sch.IsSimpleMap, "item at %d should not be a simple map type", i)
218		}
219	}
220}
221
222func TestSchemaAnalysis_Tuple(t *testing.T) {
223	at := spec.ArrayProperty(nil)
224	at.Items = &spec.SchemaOrArray{}
225	at.Items.Schemas = append(at.Items.Schemas, *spec.StringProperty(), *spec.Int64Property())
226
227	sch, err := Schema(SchemaOpts{Schema: at})
228	if assert.NoError(t, err) {
229		assert.True(t, sch.IsTuple)
230		assert.False(t, sch.IsTupleWithExtra)
231		assert.False(t, sch.IsKnownType)
232		assert.False(t, sch.IsSimpleSchema)
233	}
234
235	// edge case: tuple with a single element
236	at.Items = &spec.SchemaOrArray{}
237	at.Items.Schemas = append(at.Items.Schemas, *spec.StringProperty())
238	sch, err = Schema(SchemaOpts{Schema: at})
239	if assert.NoError(t, err) {
240		assert.True(t, sch.IsTuple)
241		assert.False(t, sch.IsTupleWithExtra)
242		assert.False(t, sch.IsKnownType)
243		assert.False(t, sch.IsSimpleSchema)
244	}
245}
246
247func TestSchemaAnalysis_TupleWithExtra(t *testing.T) {
248	at := spec.ArrayProperty(nil)
249	at.Items = &spec.SchemaOrArray{}
250	at.Items.Schemas = append(at.Items.Schemas, *spec.StringProperty(), *spec.Int64Property())
251	at.AdditionalItems = &spec.SchemaOrBool{Allows: true}
252	at.AdditionalItems.Schema = spec.Int32Property()
253
254	sch, err := Schema(SchemaOpts{Schema: at})
255	if assert.NoError(t, err) {
256		assert.False(t, sch.IsTuple)
257		assert.True(t, sch.IsTupleWithExtra)
258		assert.False(t, sch.IsKnownType)
259		assert.False(t, sch.IsSimpleSchema)
260	}
261}
262
263func TestSchemaAnalysis_BaseType(t *testing.T) {
264	cl := (&spec.Schema{}).Typed("object", "").SetProperty("type", *spec.StringProperty()).WithDiscriminator("type")
265
266	sch, err := Schema(SchemaOpts{Schema: cl})
267	if assert.NoError(t, err) {
268		assert.True(t, sch.IsBaseType)
269		assert.False(t, sch.IsKnownType)
270		assert.False(t, sch.IsSimpleSchema)
271	}
272}
273
274func TestSchemaAnalysis_SimpleSchema(t *testing.T) {
275	for i, v := range append(knownSchemas, spec.ArrayProperty(nil), spec.MapProperty(nil)) {
276		sch, err := Schema(SchemaOpts{Schema: v})
277		if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) {
278			assert.True(t, sch.IsSimpleSchema, "item at %d should be a simple schema", i)
279		}
280
281		asch, err := Schema(SchemaOpts{Schema: spec.ArrayProperty(v)})
282		if assert.NoError(t, err, "failed to analyze array schema at %d: %v", i, err) {
283			assert.True(t, asch.IsSimpleSchema, "array item at %d should be a simple schema", i)
284		}
285
286		msch, err := Schema(SchemaOpts{Schema: spec.MapProperty(v)})
287		if assert.NoError(t, err, "failed to analyze map schema at %d: %v", i, err) {
288			assert.True(t, msch.IsSimpleSchema, "map item at %d should be a simple schema", i)
289		}
290	}
291
292	for i, v := range complexSchemas {
293		sch, err := Schema(SchemaOpts{Schema: v})
294		if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) {
295			assert.False(t, sch.IsSimpleSchema, "item at %d should not be a simple schema", i)
296		}
297	}
298}
299
300func TestSchemaAnalys_InvalidSchema(t *testing.T) {
301	// explore error cases in schema analysis:
302	// the only cause for failure is a wrong $ref at some place
303	bp := filepath.Join("fixtures", "bugs", "1602", "other-invalid-pointers.yaml")
304	sp := loadOrFail(t, bp)
305
306	// invalid ref not detected (no digging further)
307	def := sp.Definitions["invalidRefInObject"]
308	_, err := Schema(SchemaOpts{Schema: &def, Root: sp, BasePath: bp})
309	assert.NoError(t, err, "did not expect an error here, in spite of the underlying invalid $ref")
310
311	def = sp.Definitions["invalidRefInTuple"]
312	_, err = Schema(SchemaOpts{Schema: &def, Root: sp, BasePath: bp})
313	assert.NoError(t, err, "did not expect an error here, in spite of the underlying invalid $ref")
314
315	// invalid ref detected (digging)
316	schema := refSchema(spec.MustCreateRef("#/definitions/noWhere"))
317	_, err = Schema(SchemaOpts{Schema: schema, Root: sp, BasePath: bp})
318	assert.Error(t, err, "expected an error here")
319
320	def = sp.Definitions["invalidRefInMap"]
321	_, err = Schema(SchemaOpts{Schema: &def, Root: sp, BasePath: bp})
322	assert.Error(t, err, "expected an error here")
323
324	def = sp.Definitions["invalidRefInArray"]
325	_, err = Schema(SchemaOpts{Schema: &def, Root: sp, BasePath: bp})
326	assert.Error(t, err, "expected an error here")
327
328	def = sp.Definitions["indirectToInvalidRef"]
329	_, err = Schema(SchemaOpts{Schema: &def, Root: sp, BasePath: bp})
330	assert.Error(t, err, "expected an error here")
331
332	// bbb, _ := json.MarshalIndent(def, "", " ")
333	// log.Printf(string(bbb))
334}
335
336func TestSchemaAnalysis_EdgeCases(t *testing.T) {
337	_, err := Schema(SchemaOpts{Schema: nil})
338	assert.Error(t, err)
339}
340