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
15package generator
16
17import (
18	"bytes"
19	"errors"
20	"fmt"
21	"io/ioutil"
22	"log"
23	"os"
24	"path/filepath"
25	"testing"
26
27	"github.com/go-openapi/analysis"
28	"github.com/go-openapi/loads"
29	"github.com/go-openapi/spec"
30	"github.com/stretchr/testify/assert"
31)
32
33func TestUniqueOperationNames(t *testing.T) {
34	doc, err := loads.Spec("../fixtures/codegen/todolist.simple.yml")
35	if assert.NoError(t, err) {
36		sp := doc.Spec()
37		sp.Paths.Paths["/tasks"].Post.ID = "saveTask"
38		sp.Paths.Paths["/tasks"].Post.AddExtension("origName", "createTask")
39		sp.Paths.Paths["/tasks/{id}"].Put.ID = "saveTask"
40		sp.Paths.Paths["/tasks/{id}"].Put.AddExtension("origName", "updateTask")
41		analyzed := analysis.New(sp)
42
43		ops := gatherOperations(analyzed, nil)
44		assert.Len(t, ops, 6)
45		_, exists := ops["saveTask"]
46		assert.True(t, exists)
47		_, exists = ops["PutTasksID"]
48		assert.True(t, exists)
49	}
50}
51
52func TestEmptyOperationNames(t *testing.T) {
53	doc, err := loads.Spec("../fixtures/codegen/todolist.simple.yml")
54	if assert.NoError(t, err) {
55		sp := doc.Spec()
56		sp.Paths.Paths["/tasks"].Post.ID = ""
57		sp.Paths.Paths["/tasks"].Post.AddExtension("origName", "createTask")
58		sp.Paths.Paths["/tasks/{id}"].Put.ID = ""
59		sp.Paths.Paths["/tasks/{id}"].Put.AddExtension("origName", "updateTask")
60		analyzed := analysis.New(sp)
61
62		ops := gatherOperations(analyzed, nil)
63		assert.Len(t, ops, 6)
64		_, exists := ops["PostTasks"]
65		assert.True(t, exists)
66		_, exists = ops["PutTasksID"]
67		assert.True(t, exists)
68	}
69}
70
71func TestMakeResponseHeader(t *testing.T) {
72	b, err := opBuilder("getTasks", "")
73	if assert.NoError(t, err) {
74		hdr := findResponseHeader(&b.Operation, 200, "X-Rate-Limit")
75		gh, er := b.MakeHeader("a", "X-Rate-Limit", *hdr)
76		if assert.NoError(t, er) {
77			assert.True(t, gh.IsPrimitive)
78			assert.Equal(t, "int32", gh.GoType)
79			assert.Equal(t, "X-Rate-Limit", gh.Name)
80		}
81	}
82}
83
84func TestMakeResponseHeaderDefaultValues(t *testing.T) {
85	b, err := opBuilder("getTasks", "")
86	if assert.NoError(t, err) {
87		var testCases = []struct {
88			name         string      // input
89			typeStr      string      // expected type
90			defaultValue interface{} // expected result
91		}{
92			{"Access-Control-Allow-Origin", "string", "*"},
93			{"X-Rate-Limit", "int32", nil},
94			{"X-Rate-Limit-Remaining", "int32", float64(42)},
95			{"X-Rate-Limit-Reset", "int32", "1449875311"},
96			{"X-Rate-Limit-Reset-Human", "string", "3 days"},
97			{"X-Rate-Limit-Reset-Human-Number", "string", float64(3)},
98		}
99
100		for _, tc := range testCases {
101			// t.Logf("tc: %+v", tc)
102			hdr := findResponseHeader(&b.Operation, 200, tc.name)
103			assert.NotNil(t, hdr)
104			gh, er := b.MakeHeader("a", tc.name, *hdr)
105			if assert.NoError(t, er) {
106				assert.True(t, gh.IsPrimitive)
107				assert.Equal(t, tc.typeStr, gh.GoType)
108				assert.Equal(t, tc.name, gh.Name)
109				assert.Exactly(t, tc.defaultValue, gh.Default)
110			}
111		}
112	}
113}
114
115func TestMakeResponse(t *testing.T) {
116	b, err := opBuilder("getTasks", "")
117	if assert.NoError(t, err) {
118		resolver := &typeResolver{ModelsPackage: b.ModelsPackage, Doc: b.Doc}
119		resolver.KnownDefs = make(map[string]struct{})
120		for k := range b.Doc.Spec().Definitions {
121			resolver.KnownDefs[k] = struct{}{}
122		}
123		gO, err := b.MakeResponse("a", "getTasksSuccess", true, resolver, 200, b.Operation.Responses.StatusCodeResponses[200])
124		if assert.NoError(t, err) {
125			assert.Len(t, gO.Headers, 6)
126			assert.NotNil(t, gO.Schema)
127			assert.True(t, gO.Schema.IsArray)
128			assert.NotNil(t, gO.Schema.Items)
129			assert.False(t, gO.Schema.IsAnonymous)
130			assert.Equal(t, "[]*models.Task", gO.Schema.GoType)
131		}
132	}
133}
134
135func TestMakeResponse_WithAllOfSchema(t *testing.T) {
136	b, err := methodPathOpBuilder("get", "/media/search", "../fixtures/codegen/instagram.yml")
137	if assert.NoError(t, err) {
138		resolver := &typeResolver{ModelsPackage: b.ModelsPackage, Doc: b.Doc}
139		resolver.KnownDefs = make(map[string]struct{})
140		for k := range b.Doc.Spec().Definitions {
141			resolver.KnownDefs[k] = struct{}{}
142		}
143		gO, err := b.MakeResponse("a", "get /media/search", true, resolver, 200, b.Operation.Responses.StatusCodeResponses[200])
144		if assert.NoError(t, err) {
145			if assert.NotNil(t, gO.Schema) {
146				assert.Equal(t, "GetMediaSearchBody", gO.Schema.GoType)
147			}
148			if assert.NotEmpty(t, b.ExtraSchemas) {
149				body := b.ExtraSchemas["GetMediaSearchBody"]
150				if assert.NotEmpty(t, body.Properties) {
151					prop := body.Properties[0]
152					assert.Equal(t, "data", prop.Name)
153					assert.Equal(t, "[]*DataItems0", prop.GoType)
154				}
155				items := b.ExtraSchemas["DataItems0"]
156				if assert.NotEmpty(t, items.AllOf) {
157					media := items.AllOf[0]
158					assert.Equal(t, "models.Media", media.GoType)
159				}
160			}
161		}
162	}
163}
164
165func TestMakeOperationParam(t *testing.T) {
166	b, err := opBuilder("getTasks", "")
167	if assert.NoError(t, err) {
168		resolver := &typeResolver{ModelsPackage: b.ModelsPackage, Doc: b.Doc}
169		gO, err := b.MakeParameter("a", resolver, b.Operation.Parameters[0], nil)
170		if assert.NoError(t, err) {
171			assert.Equal(t, "size", gO.Name)
172			assert.True(t, gO.IsPrimitive)
173		}
174	}
175}
176
177func TestMakeOperationParamItem(t *testing.T) {
178	b, err := opBuilder("arrayQueryParams", "../fixtures/codegen/todolist.arrayquery.yml")
179	if assert.NoError(t, err) {
180		resolver := &typeResolver{ModelsPackage: b.ModelsPackage, Doc: b.Doc}
181		gO, err := b.MakeParameterItem("a", "siString", "ii", "siString", "a.SiString", "query", resolver, b.Operation.Parameters[1].Items, nil)
182		if assert.NoError(t, err) {
183			assert.Nil(t, gO.Parent)
184			assert.True(t, gO.IsPrimitive)
185		}
186	}
187}
188
189func TestMakeOperation(t *testing.T) {
190	b, err := opBuilder("getTasks", "")
191	if assert.NoError(t, err) {
192		gO, err := b.MakeOperation()
193		if assert.NoError(t, err) {
194			//pretty.Println(gO)
195			assert.Equal(t, "getTasks", gO.Name)
196			assert.Equal(t, "GET", gO.Method)
197			assert.Equal(t, "/tasks", gO.Path)
198			assert.Len(t, gO.Params, 2)
199			assert.Len(t, gO.Responses, 1)
200			assert.NotNil(t, gO.DefaultResponse)
201			assert.NotNil(t, gO.SuccessResponse)
202		}
203
204		// TODO: validate rendering of a complex operation
205	}
206}
207
208func TestRenderOperation_InstagramSearch(t *testing.T) {
209	b, err := methodPathOpBuilder("get", "/media/search", "../fixtures/codegen/instagram.yml")
210	if assert.NoError(t, err) {
211		gO, err := b.MakeOperation()
212		if assert.NoError(t, err) {
213			buf := bytes.NewBuffer(nil)
214			opts := opts()
215			err := templates.MustGet("serverOperation").Execute(buf, gO)
216			if assert.NoError(t, err) {
217				ff, err := opts.LanguageOpts.FormatContent("operation.go", buf.Bytes())
218				if assert.NoError(t, err) {
219					res := string(ff)
220					// fmt.Println(res)
221					assertInCode(t, "Data []*DataItems0 `json:\"data\"`", res)
222					assertInCode(t, "models.Media", res)
223				} else {
224					fmt.Println(buf.String())
225				}
226			}
227		}
228	}
229}
230
231func methodPathOpBuilder(method, path, fname string) (codeGenOpBuilder, error) {
232	if fname == "" {
233		fname = "../fixtures/codegen/todolist.simple.yml"
234	}
235
236	specDoc, err := loads.Spec(fname)
237	if err != nil {
238		return codeGenOpBuilder{}, err
239	}
240
241	analyzed := analysis.New(specDoc.Spec())
242	op, ok := analyzed.OperationFor(method, path)
243	if !ok {
244		return codeGenOpBuilder{}, errors.New("No operation could be found for " + method + " " + path)
245	}
246
247	return codeGenOpBuilder{
248		Name:          method + " " + path,
249		Method:        method,
250		Path:          path,
251		APIPackage:    "restapi",
252		ModelsPackage: "models",
253		Principal:     "models.User",
254		Target:        ".",
255		Operation:     *op,
256		Doc:           specDoc,
257		Analyzed:      analyzed,
258		Authed:        false,
259		ExtraSchemas:  make(map[string]GenSchema),
260	}, nil
261}
262
263func opBuilder(name, fname string) (codeGenOpBuilder, error) {
264	if fname == "" {
265		fname = "../fixtures/codegen/todolist.simple.yml"
266	}
267
268	specDoc, err := loads.Spec(fname)
269	if err != nil {
270		return codeGenOpBuilder{}, err
271	}
272	analyzed := analysis.New(specDoc.Spec())
273
274	method, path, op, ok := analyzed.OperationForName(name)
275	if !ok {
276		return codeGenOpBuilder{}, errors.New("No operation could be found for " + name)
277	}
278
279	return codeGenOpBuilder{
280		Name:          name,
281		Method:        method,
282		Path:          path,
283		BasePath:      specDoc.BasePath(),
284		APIPackage:    "restapi",
285		ModelsPackage: "models",
286		Principal:     "models.User",
287		Target:        ".",
288		Operation:     *op,
289		Doc:           specDoc,
290		Analyzed:      analyzed,
291		Authed:        false,
292		ExtraSchemas:  make(map[string]GenSchema),
293	}, nil
294}
295
296func findResponseHeader(op *spec.Operation, code int, name string) *spec.Header {
297	resp := op.Responses.Default
298	if code > 0 {
299		bb, ok := op.Responses.StatusCodeResponses[code]
300		if ok {
301			resp = &bb
302		}
303	}
304
305	if resp == nil {
306		return nil
307	}
308
309	hdr, ok := resp.Headers[name]
310	if !ok {
311		return nil
312	}
313
314	return &hdr
315}
316
317func TestDateFormat_Spec1(t *testing.T) {
318	b, err := opBuilder("putTesting", "../fixtures/bugs/193/spec1.json")
319	if assert.NoError(t, err) {
320		op, err := b.MakeOperation()
321		if assert.NoError(t, err) {
322			buf := bytes.NewBuffer(nil)
323			opts := opts()
324			opts.defaultsEnsured = false
325			opts.EnsureDefaults(true)
326			err := templates.MustGet("clientParameter").Execute(buf, op)
327			if assert.NoError(t, err) {
328				ff, err := opts.LanguageOpts.FormatContent("put_testing.go", buf.Bytes())
329				if assert.NoError(t, err) {
330					res := string(ff)
331					assertInCode(t, "frTestingThis.String()", res)
332				} else {
333					fmt.Println(buf.String())
334				}
335			}
336		}
337	}
338}
339
340func TestDateFormat_Spec2(t *testing.T) {
341	b, err := opBuilder("putTesting", "../fixtures/bugs/193/spec2.json")
342	if assert.NoError(t, err) {
343		op, err := b.MakeOperation()
344		if assert.NoError(t, err) {
345			buf := bytes.NewBuffer(nil)
346			opts := opts()
347			opts.defaultsEnsured = false
348			opts.EnsureDefaults(true)
349			err := templates.MustGet("clientParameter").Execute(buf, op)
350			if assert.NoError(t, err) {
351				ff, err := opts.LanguageOpts.FormatContent("put_testing.go", buf.Bytes())
352				if assert.NoError(t, err) {
353					res := string(ff)
354					assertInCode(t, "valuesTestingThis = append(valuesTestingThis, v.String())", res)
355				} else {
356					fmt.Println(buf.String())
357				}
358			}
359		}
360	}
361}
362
363func TestBuilder_Issue287(t *testing.T) {
364	log.SetOutput(ioutil.Discard)
365	defer log.SetOutput(os.Stderr)
366	dr, _ := os.Getwd()
367
368	opts := &GenOpts{
369		Spec:              filepath.FromSlash("../fixtures/bugs/287/swagger.yml"),
370		IncludeModel:      true,
371		IncludeValidator:  true,
372		IncludeHandler:    true,
373		IncludeParameters: true,
374		IncludeResponses:  true,
375		IncludeMain:       true,
376		APIPackage:        "restapi",
377		ModelPackage:      "model",
378		ServerPackage:     "server",
379		ClientPackage:     "client",
380		Target:            dr,
381	}
382	opts.EnsureDefaults(false)
383	appGen, err := newAppGenerator("plainTexter", nil, nil, opts)
384	if assert.NoError(t, err) {
385		op, err := appGen.makeCodegenApp()
386		if assert.NoError(t, err) {
387			buf := bytes.NewBuffer(nil)
388			err := templates.MustGet("serverBuilder").Execute(buf, op)
389			if assert.NoError(t, err) {
390				ff, err := appGen.GenOpts.LanguageOpts.FormatContent("put_testing.go", buf.Bytes())
391				if assert.NoError(t, err) {
392					res := string(ff)
393					assertInCode(t, "case \"text/plain\":", res)
394				} else {
395					fmt.Println(buf.String())
396				}
397			}
398		}
399	}
400}
401
402func TestBuilder_Issue465(t *testing.T) {
403	log.SetOutput(ioutil.Discard)
404	defer log.SetOutput(os.Stderr)
405	dr, _ := os.Getwd()
406	opts := &GenOpts{
407		Spec:              filepath.FromSlash("../fixtures/bugs/465/swagger.yml"),
408		IncludeModel:      true,
409		IncludeValidator:  true,
410		IncludeHandler:    true,
411		IncludeParameters: true,
412		IncludeResponses:  true,
413		IncludeMain:       true,
414		APIPackage:        "restapi",
415		ModelPackage:      "model",
416		ServerPackage:     "server",
417		ClientPackage:     "client",
418		Target:            dr,
419	}
420	opts.EnsureDefaults(true)
421	appGen, err := newAppGenerator("plainTexter", nil, nil, opts)
422	if assert.NoError(t, err) {
423		op, err := appGen.makeCodegenApp()
424		if assert.NoError(t, err) {
425			buf := bytes.NewBuffer(nil)
426			err := templates.MustGet("clientFacade").Execute(buf, op)
427			if assert.NoError(t, err) {
428				ff, err := appGen.GenOpts.LanguageOpts.FormatContent("put_testing.go", buf.Bytes())
429				if assert.NoError(t, err) {
430					res := string(ff)
431					assertInCode(t, "/v1/fancyAPI", res)
432				} else {
433					fmt.Println(buf.String())
434				}
435			}
436		}
437	}
438}
439
440func TestBuilder_Issue500(t *testing.T) {
441	log.SetOutput(ioutil.Discard)
442	defer log.SetOutput(os.Stderr)
443	dr, _ := os.Getwd()
444	opts := &GenOpts{
445		Spec:              filepath.FromSlash("../fixtures/bugs/500/swagger.yml"),
446		IncludeModel:      true,
447		IncludeValidator:  true,
448		IncludeHandler:    true,
449		IncludeParameters: true,
450		IncludeResponses:  true,
451		IncludeMain:       true,
452		APIPackage:        "restapi",
453		ModelPackage:      "model",
454		ServerPackage:     "server",
455		ClientPackage:     "client",
456		Target:            dr,
457	}
458	opts.EnsureDefaults(false)
459	appGen, err := newAppGenerator("multiTags", nil, nil, opts)
460	if assert.NoError(t, err) {
461		op, err := appGen.makeCodegenApp()
462		if assert.NoError(t, err) {
463			buf := bytes.NewBuffer(nil)
464			err := templates.MustGet("serverBuilder").Execute(buf, op)
465			if assert.NoError(t, err) {
466				ff, err := appGen.GenOpts.LanguageOpts.FormatContent("put_testing.go", buf.Bytes())
467				if assert.NoError(t, err) {
468					res := string(ff)
469					assertNotInCode(t, `o.handlers["GET"]["/payment/{invoice_id}/payments/{payment_id}"] = invoices.NewGetPaymentByID(o.context, o.InvoicesGetPaymentByIDHandler)`, res)
470					assertInCode(t, `o.handlers["GET"]["/payment/{invoice_id}/payments/{payment_id}"] = NewGetPaymentByID(o.context, o.GetPaymentByIDHandler)`, res)
471				} else {
472					fmt.Println(buf.String())
473				}
474			}
475		}
476	}
477}
478
479func TestGenClient_IllegalBOM(t *testing.T) {
480	b, err := methodPathOpBuilder("get", "/v3/attachments/{attachmentId}", "../fixtures/bugs/727/swagger.json")
481	if assert.NoError(t, err) {
482		op, err := b.MakeOperation()
483		if assert.NoError(t, err) {
484			buf := bytes.NewBuffer(nil)
485			opts := opts()
486			opts.defaultsEnsured = false
487			opts.EnsureDefaults(true)
488			err := templates.MustGet("clientResponse").Execute(buf, op)
489			assert.NoError(t, err)
490		}
491	}
492}
493
494func TestGenClient_CustomFormatPath(t *testing.T) {
495	b, err := methodPathOpBuilder("get", "/mosaic/experimental/series/{SeriesId}/mosaics", "../fixtures/bugs/789/swagger.yml")
496	if assert.NoError(t, err) {
497		op, err := b.MakeOperation()
498		if assert.NoError(t, err) {
499			buf := bytes.NewBuffer(nil)
500			opts := opts()
501			opts.defaultsEnsured = false
502			opts.EnsureDefaults(true)
503			err := templates.MustGet("clientParameter").Execute(buf, op)
504			if assert.NoError(t, err) {
505				assertInCode(t, `if err := r.SetPathParam("SeriesId", o.SeriesID.String()); err != nil`, buf.String())
506			}
507		}
508	}
509}
510
511func TestGenClient_Issue733(t *testing.T) {
512	b, err := opBuilder("get_characters_character_id_mail_mail_id", "../fixtures/bugs/733/swagger.json")
513	if assert.NoError(t, err) {
514		op, err := b.MakeOperation()
515		if assert.NoError(t, err) {
516			buf := bytes.NewBuffer(nil)
517			opts := opts()
518			opts.defaultsEnsured = false
519			opts.EnsureDefaults(true)
520			err := templates.MustGet("clientResponse").Execute(buf, op)
521			if assert.NoError(t, err) {
522				assertInCode(t, "Labels []*int64 `json:\"labels\"`", buf.String())
523			}
524		}
525	}
526}
527