/* * Copyright (c) 2019 Alex aka mailoman * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @author Alex aka mailoman * @copyright Copyright (c) 2019 Alex aka mailoman * @since 18.12.2019 * */ package echoswagger import ( "net/http" "net/http/httptest" "strconv" "testing" "time" "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" ) func prepareApiRoot() ApiRoot { r := New(echo.New(), "doc/", nil) r.SetUI(UISetting{DetachSpec: true}) return r } func prepareApiGroup() ApiGroup { r := prepareApiRoot() return r.Group("G", "/g") } func prepareApi() Api { g := prepareApiGroup() var h func(e echo.Context) error return g.POST("", h) } func TestNew(t *testing.T) { tests := []struct { echo *echo.Echo docPath string info *Info expectPaths []string panic bool name string }{ { echo: echo.New(), docPath: "doc/", info: nil, expectPaths: []string{"/doc/", "/doc/swagger.json"}, panic: false, name: "Normal", }, { echo: echo.New(), docPath: "doc", info: &Info{ Title: "Test project", Contact: &Contact{ URL: "https://github.com/echo-go/echoswagger", }, }, expectPaths: []string{"/doc", "/doc/swagger.json"}, panic: false, name: "Path slash suffix", }, { echo: nil, panic: true, name: "Panic", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.panic { assert.Panics(t, func() { New(tt.echo, tt.docPath, tt.info) }) } else { apiRoot := New(tt.echo, tt.docPath, tt.info) assert.NotNil(t, apiRoot.(*Root)) r := apiRoot.(*Root) assert.NotNil(t, r.spec) if tt.info == nil { assert.Equal(t, r.spec.Info.Title, "Project APIs") } else { assert.Equal(t, r.spec.Info, tt.info) } assert.NotNil(t, r.echo) assert.Len(t, r.echo.Routes(), 2) res := r.echo.Routes() paths := []string{res[0].Path, res[1].Path} assert.ElementsMatch(t, paths, tt.expectPaths) } }) } } func TestPath(t *testing.T) { tests := []struct { docInput string docOutput, specOutput string name string }{ { docInput: "doc/", docOutput: "/doc/", specOutput: "/doc/swagger.json", name: "A", }, { docInput: "", docOutput: "/", specOutput: "/swagger.json", name: "B", }, { docInput: "/doc", docOutput: "/doc", specOutput: "/doc/swagger.json", name: "C", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { apiRoot := New(echo.New(), tt.docInput, nil) r := apiRoot.(*Root) assert.NotNil(t, r.echo) assert.Len(t, r.echo.Routes(), 2) res := r.echo.Routes() paths := []string{res[0].Path, res[1].Path} assert.ElementsMatch(t, paths, []string{tt.docOutput, tt.specOutput}) }) } } func TestGroup(t *testing.T) { r := prepareApiRoot() t.Run("Normal", func(t *testing.T) { g := r.Group("Users", "users") assert.Equal(t, g.(*group).defs, r.(*Root).defs) }) t.Run("Invalid name", func(t *testing.T) { assert.Panics(t, func() { r.Group("", "") }) }) t.Run("Repeat name", func(t *testing.T) { ga := r.Group("Users", "users") assert.Equal(t, ga.(*group).tag.Name, "Users_1") gb := r.Group("Users", "users") assert.Equal(t, gb.(*group).tag.Name, "Users_2") }) } func TestRouters(t *testing.T) { r := prepareApiRoot() var h echo.HandlerFunc r.GET("/:id", h) r.POST("/:id", h) r.PUT("/:id", h) r.DELETE("/:id", h) r.OPTIONS("/:id", h) r.HEAD("/:id", h) r.PATCH("/:id", h) assert.Len(t, r.(*Root).apis, 7) g := prepareApiGroup() g.GET("/:id", h) g.POST("/:id", h) g.PUT("/:id", h) g.DELETE("/:id", h) g.OPTIONS("/:id", h) g.HEAD("/:id", h) g.PATCH("/:id", h) assert.Len(t, g.(*group).apis, 7) } func TestAddParam(t *testing.T) { name := "name" desc := "Param desc" type nested struct { Name string `json:"name" form:"name" query:"name"` Enable bool `json:"-" form:"-" query:"-"` } t.Run("File", func(t *testing.T) { a := prepareApi() a.AddParamFile(name, desc, true) assert.Len(t, a.(*api).operation.Parameters, 1) assert.Equal(t, a.(*api).operation.Parameters[0].Name, name) assert.Equal(t, a.(*api).operation.Parameters[0].In, string(ParamInFormData)) assert.Equal(t, a.(*api).operation.Parameters[0].Description, desc) assert.Equal(t, a.(*api).operation.Parameters[0].Required, true) assert.Equal(t, a.(*api).operation.Parameters[0].Type, "file") }) t.Run("Path", func(t *testing.T) { a := prepareApi() a.AddParamPath(time.Now(), name, desc) assert.Len(t, a.(*api).operation.Parameters, 1) assert.Equal(t, a.(*api).operation.Parameters[0].Name, name) assert.Equal(t, a.(*api).operation.Parameters[0].In, string(ParamInPath)) assert.Equal(t, a.(*api).operation.Parameters[0].Description, desc) assert.Equal(t, a.(*api).operation.Parameters[0].Required, true) assert.Equal(t, a.(*api).operation.Parameters[0].Type, "string") a.AddParamPathNested(&nested{}) assert.Len(t, a.(*api).operation.Parameters, 2) assert.Equal(t, a.(*api).operation.Parameters[1].Name, "name_") assert.Equal(t, a.(*api).operation.Parameters[1].In, string(ParamInPath)) assert.Equal(t, a.(*api).operation.Parameters[1].Type, "string") }) t.Run("Query", func(t *testing.T) { a := prepareApi() a.AddParamQuery(time.Now(), name, desc, true) assert.Len(t, a.(*api).operation.Parameters, 1) assert.Equal(t, a.(*api).operation.Parameters[0].Name, name) assert.Equal(t, a.(*api).operation.Parameters[0].In, string(ParamInQuery)) assert.Equal(t, a.(*api).operation.Parameters[0].Description, desc) assert.Equal(t, a.(*api).operation.Parameters[0].Required, true) assert.Equal(t, a.(*api).operation.Parameters[0].Type, "string") a.AddParamQueryNested(&nested{}) assert.Len(t, a.(*api).operation.Parameters, 2) assert.Equal(t, a.(*api).operation.Parameters[1].Name, "name_") assert.Equal(t, a.(*api).operation.Parameters[1].In, string(ParamInQuery)) assert.Equal(t, a.(*api).operation.Parameters[1].Type, "string") }) t.Run("FormData", func(t *testing.T) { a := prepareApi() a.AddParamForm(time.Now(), name, desc, true) assert.Len(t, a.(*api).operation.Parameters, 1) assert.Equal(t, a.(*api).operation.Parameters[0].Name, name) assert.Equal(t, a.(*api).operation.Parameters[0].In, string(ParamInFormData)) assert.Equal(t, a.(*api).operation.Parameters[0].Description, desc) assert.Equal(t, a.(*api).operation.Parameters[0].Required, true) assert.Equal(t, a.(*api).operation.Parameters[0].Type, "string") a.AddParamFormNested(&nested{}) assert.Len(t, a.(*api).operation.Parameters, 2) assert.Equal(t, a.(*api).operation.Parameters[1].Name, "name_") assert.Equal(t, a.(*api).operation.Parameters[1].In, string(ParamInFormData)) assert.Equal(t, a.(*api).operation.Parameters[1].Type, "string") }) t.Run("Header", func(t *testing.T) { a := prepareApi() a.AddParamHeader(time.Now(), name, desc, true) assert.Len(t, a.(*api).operation.Parameters, 1) assert.Equal(t, a.(*api).operation.Parameters[0].Name, name) assert.Equal(t, a.(*api).operation.Parameters[0].In, string(ParamInHeader)) assert.Equal(t, a.(*api).operation.Parameters[0].Description, desc) assert.Equal(t, a.(*api).operation.Parameters[0].Required, true) assert.Equal(t, a.(*api).operation.Parameters[0].Type, "string") a.AddParamHeaderNested(&nested{}) assert.Len(t, a.(*api).operation.Parameters, 2) assert.Equal(t, a.(*api).operation.Parameters[1].Name, "name_") assert.Equal(t, a.(*api).operation.Parameters[1].In, string(ParamInHeader)) assert.Equal(t, a.(*api).operation.Parameters[1].Type, "string") }) } func TestAddSchema(t *testing.T) { type body struct { Name string `json:"name"` Enable bool `json:"-"` } t.Run("Multiple", func(t *testing.T) { a := prepareApi() a.AddParamBody(&body{}, "body", "body desc", true) assert.Len(t, a.(*api).operation.Parameters, 1) assert.Equal(t, a.(*api).operation.Parameters[0].Name, "body") assert.Equal(t, a.(*api).operation.Parameters[0].In, string(ParamInBody)) assert.Equal(t, a.(*api).operation.Parameters[0].Description, "body desc") assert.Equal(t, a.(*api).operation.Parameters[0].Required, true) assert.Panics(t, func() { a.AddParamBody(body{}, "body", "body desc", true) }) }) t.Run("GetKey", func(t *testing.T) { a := prepareApi() a.AddParamBody(&body{}, "body", "body desc", true) assert.Len(t, a.(*api).operation.Parameters, 1) assert.Equal(t, a.(*api).operation.Parameters[0].Name, "body") assert.Equal(t, a.(*api).operation.Parameters[0].In, string(ParamInBody)) assert.Equal(t, a.(*api).operation.Parameters[0].Description, "body desc") assert.Equal(t, a.(*api).operation.Parameters[0].Required, true) a.AddResponse(http.StatusOK, "response desc", body{}, nil) ca := strconv.Itoa(http.StatusOK) assert.Len(t, a.(*api).operation.Responses, 1) assert.Equal(t, a.(*api).operation.Responses[ca].Description, "response desc") assert.NotNil(t, a.(*api).defs) da := a.(*api).defs assert.Len(t, (*da), 1) assert.NotNil(t, (*da)["body"]) a.AddResponse(http.StatusBadRequest, "response desc", body{Name: "name"}, nil) cb := strconv.Itoa(http.StatusBadRequest) assert.Len(t, a.(*api).operation.Responses, 2) assert.Equal(t, a.(*api).operation.Responses[cb].Description, "response desc") assert.NotNil(t, a.(*api).defs) db := a.(*api).defs assert.Len(t, (*db), 2) assert.NotNil(t, (*db)["body"]) }) } func TestAddResponse(t *testing.T) { a := prepareApi() a.AddResponse(http.StatusOK, "successful", nil, nil) var f = func() {} assert.Panics(t, func() { a.AddResponse(http.StatusBadRequest, "bad request", f, nil) }) assert.Panics(t, func() { a.AddResponse(http.StatusBadRequest, "bad request", nil, time.Now()) }) } func TestUI(t *testing.T) { t.Run("DefaultCDN", func(t *testing.T) { r := New(echo.New(), "doc/", nil) se := r.(*Root) req := httptest.NewRequest(echo.GET, "/doc/", nil) rec := httptest.NewRecorder() c := se.echo.NewContext(req, rec) h := se.docHandler("doc/") if assert.NoError(t, h(c)) { assert.Equal(t, http.StatusOK, rec.Code) assert.Contains(t, rec.Body.String(), DefaultCDN) } }) t.Run("SetUI", func(t *testing.T) { r := New(echo.New(), "doc/", nil) se := r.(*Root) req := httptest.NewRequest(echo.GET, "/doc/", nil) rec := httptest.NewRecorder() c := se.echo.NewContext(req, rec) h := se.docHandler("doc/") cdn := "https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.18.0" r.SetUI(UISetting{ HideTop: true, CDN: cdn, }) if assert.NoError(t, h(c)) { assert.Equal(t, http.StatusOK, rec.Code) assert.Contains(t, rec.Body.String(), cdn) assert.Contains(t, rec.Body.String(), `{\x22swagger\x22:`) assert.Contains(t, rec.Body.String(), "#swagger-ui>.swagger-container>.topbar") } r.SetUI(UISetting{ DetachSpec: true, }) if assert.NoError(t, h(c)) { assert.Equal(t, http.StatusOK, rec.Code) assert.NotContains(t, rec.Body.String(), "{\x22swagger\x22:") assert.Contains(t, rec.Body.String(), "#swagger-ui>.swagger-container>.topbar") } }) } func TestScheme(t *testing.T) { r := prepareApiRoot() schemes := []string{"http", "https"} r.SetScheme(schemes...) assert.ElementsMatch(t, r.(*Root).spec.Schemes, schemes) assert.Panics(t, func() { r.SetScheme("grpc") }) } func TestRaw(t *testing.T) { r := prepareApiRoot() s := r.GetRaw() assert.NotNil(t, s) assert.NotNil(t, s.Info) assert.Equal(t, s.Info.Version, "") s.Info.Version = "1.0" r.SetRaw(s) assert.Equal(t, s.Info.Version, "1.0") } func TestContentType(t *testing.T) { c := []string{"application/x-www-form-urlencoded", "multipart/form-data"} p := []string{"application/vnd.github.v3+json", "application/vnd.github.v3.raw+json", "application/vnd.github.v3.text+json"} t.Run("In Root", func(t *testing.T) { r := prepareApiRoot() r.SetRequestContentType(c...) r.SetResponseContentType(p...) assert.NotNil(t, r.(*Root)) assert.NotNil(t, r.(*Root).spec) assert.Len(t, r.(*Root).spec.Consumes, 2) assert.ElementsMatch(t, r.(*Root).spec.Consumes, c) assert.Len(t, r.(*Root).spec.Produces, 3) assert.ElementsMatch(t, r.(*Root).spec.Produces, p) }) t.Run("In Api", func(t *testing.T) { a := prepareApi() a.SetRequestContentType(c...) a.SetResponseContentType(p...) assert.NotNil(t, a.(*api)) assert.Len(t, a.(*api).operation.Consumes, 2) assert.ElementsMatch(t, a.(*api).operation.Consumes, c) assert.Len(t, a.(*api).operation.Produces, 3) assert.ElementsMatch(t, a.(*api).operation.Produces, p) }) } func TestOperationId(t *testing.T) { id := "TestOperation" a := prepareApi() a.SetOperationId(id) assert.Equal(t, a.(*api).operation.OperationID, id) } func TestDeprecated(t *testing.T) { a := prepareApi() a.SetDeprecated() assert.Equal(t, a.(*api).operation.Deprecated, true) } func TestDescription(t *testing.T) { d := "Test desc" g := prepareApiGroup() g.SetDescription(d) assert.Equal(t, g.(*group).tag.Description, d) a := prepareApi() a.SetDescription(d) assert.Equal(t, a.(*api).operation.Description, d) } func TestExternalDocs(t *testing.T) { e := ExternalDocs{ Description: "Test desc", URL: "http://127.0.0.1/", } r := prepareApiRoot() r.SetExternalDocs(e.Description, e.URL) assert.Equal(t, r.(*Root).spec.ExternalDocs, &e) g := prepareApiGroup() g.SetExternalDocs(e.Description, e.URL) assert.Equal(t, g.(*group).tag.ExternalDocs, &e) a := prepareApi() a.SetExternalDocs(e.Description, e.URL) assert.Equal(t, a.(*api).operation.ExternalDocs, &e) } func TestSummary(t *testing.T) { s := "Test summary" a := prepareApi() a.SetSummary(s) assert.Equal(t, a.(*api).operation.Summary, s) } func TestEcho(t *testing.T) { r := prepareApiRoot() assert.NotNil(t, r.Echo()) g := prepareApiGroup() assert.NotNil(t, g.EchoGroup()) a := prepareApi() assert.NotNil(t, a.Route()) } func TestHandlers(t *testing.T) { t.Run("ErrorGroupSecurity", func(t *testing.T) { r := prepareApiRoot() e := r.(*Root).echo var h echo.HandlerFunc r.Group("G", "/g").SetSecurity("JWT").GET("/", h) req := httptest.NewRequest(echo.GET, "/doc/swagger.json", nil) rec := httptest.NewRecorder() c := e.NewContext(req, rec) if assert.NoError(t, r.(*Root).specHandler("/doc")(c)) { assert.Equal(t, http.StatusInternalServerError, rec.Code) } if assert.NoError(t, r.(*Root).docHandler("/doc")(c)) { assert.Equal(t, http.StatusInternalServerError, rec.Code) } }) t.Run("ErrorApiSecurity", func(t *testing.T) { r := prepareApiRoot() e := r.(*Root).echo var h echo.HandlerFunc r.GET("/", h).SetSecurity("JWT") req := httptest.NewRequest(echo.GET, "/doc/swagger.json", nil) rec := httptest.NewRecorder() c := e.NewContext(req, rec) if assert.NoError(t, r.(*Root).specHandler("/doc")(c)) { assert.Equal(t, http.StatusInternalServerError, rec.Code) } if assert.NoError(t, r.(*Root).docHandler("/doc")(c)) { assert.Equal(t, http.StatusInternalServerError, rec.Code) } }) }