From 89a21580758822d68fdc6d25c6aa9f72176dbdea Mon Sep 17 00:00:00 2001 From: Emil Nikolov Date: Sat, 8 Jan 2022 01:32:27 +0100 Subject: [PATCH] added some tests --- chai/chai_test.go | 100 ++++++++++++++++ chai/generics_test.go | 83 ------------- examples/chi/celler/main.go | 58 +-------- examples/shared/controller/controller.go | 83 +++++++++++++ go.mod | 3 +- go.sum | 3 - .../internal => internal}/tests/testfile.go | 0 internal/tests/testtypes.go | 24 ++++ openapi2/funcinfo_test.go | 10 +- openapi2/internal/tests/testtypes.go | 6 - openapi2/openapi2_test.go | 69 ++++++++++- openapi2/testdata/t1.json | 111 ++++++++++++++++++ 12 files changed, 392 insertions(+), 158 deletions(-) create mode 100644 chai/chai_test.go delete mode 100644 chai/generics_test.go rename {openapi2/internal => internal}/tests/testfile.go (100%) create mode 100644 internal/tests/testtypes.go delete mode 100644 openapi2/internal/tests/testtypes.go create mode 100644 openapi2/testdata/t1.json diff --git a/chai/chai_test.go b/chai/chai_test.go new file mode 100644 index 0000000..f66875b --- /dev/null +++ b/chai/chai_test.go @@ -0,0 +1,100 @@ +package chai_test + +import ( + "bytes" + "encoding/json" + "errors" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/go-chai/chai/chai" + "github.com/go-chai/chai/internal/tests" + "github.com/stretchr/testify/require" +) + +func newRes() *tests.TestResponse { + return &tests.TestResponse{ + Foo: "f", + Bar: "b", + TestInnerResponse: tests.TestInnerResponse{ + FooFoo: 123, + BarBar: 12, + }, + } +} + +func newReq() io.Reader { + buf := new(bytes.Buffer) + + json.NewEncoder(buf).Encode(&tests.TestRequest{ + Foo: "312", + Bar: "31321", + TestInnerResponse: tests.TestInnerResponse{ + FooFoo: 4432, + BarBar: 321, + }, + }) + + return buf +} + +func TestReqResHandler(t *testing.T) { + tests := []struct { + name string + makeHandler func(t *testing.T) http.Handler + response string + }{ + { + name: "req res handler", + makeHandler: func(t *testing.T) http.Handler { + return chai.NewReqResHandler(func(req *tests.TestRequest, w http.ResponseWriter, r *http.Request) (*tests.TestResponse, int, error) { + return newRes(), http.StatusOK, nil + }) + }, + response: `{"foo":"f","bar":"b","test_inner_response":{"foo_foo":123,"bar_bar":12}}`, + }, + { + name: "req res handler with error", + makeHandler: func(t *testing.T) http.Handler { + return chai.NewReqResHandler(func(req *tests.TestRequest, w http.ResponseWriter, r *http.Request) (*tests.TestResponse, int, error) { + return nil, http.StatusInternalServerError, errors.New("zz") + }) + }, + response: `{"error":"zz", "status_code":500}`, + }, + { + name: "res handler", + makeHandler: func(t *testing.T) http.Handler { + return chai.NewResHandler(func(w http.ResponseWriter, r *http.Request) (*tests.TestResponse, int, error) { + return newRes(), http.StatusOK, nil + }) + }, + response: `{"foo":"f","bar":"b","test_inner_response":{"foo_foo":123,"bar_bar":12}}`, + }, + { + name: "req res handler with error", + makeHandler: func(t *testing.T) http.Handler { + return chai.NewResHandler(func(w http.ResponseWriter, r *http.Request) (*tests.TestResponse, int, error) { + return nil, http.StatusInternalServerError, errors.New("zz") + }) + }, + response: `{"error":"zz", "status_code":500}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.NotPanics(t, func() { + h := tt.makeHandler(t) + + w := httptest.NewRecorder() + + h.ServeHTTP(w, httptest.NewRequest(http.MethodPost, "/", newReq())) + + require.JSONEq(t, tt.response, w.Body.String()) + }) + }) + } +} diff --git a/chai/generics_test.go b/chai/generics_test.go deleted file mode 100644 index bd1a6e0..0000000 --- a/chai/generics_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package chai - -import ( - "errors" - "fmt" - "testing" -) - -func err() error { - return errors.New("error!") -} - -func nerr() error { - return nil -} - -func isnil(err error) bool { - return err == nil -} - -type errzzcomp interface { - comparable - ZZ() -} - -type errzz interface { - ZZ() -} - -type errzz2 interface { - error -} - -type errzz2comp interface { - comparable - error -} - -type zzz struct { -} - -func (zzz) ZZ() { -} - -// func gen[T errzz](terr T) { -// fmt.Printf("terr: %+v\n", terr) -// fmt.Printf("terr Is: %+v\n", errors.Is(terr, nil)) - -// if terr == (error)(nil) && isnil(terr) { -// fmt.Printf("terr is nil!\n") -// } else { -// fmt.Printf("terr is NOT nil!\n") -// } -// } - -func comparezz(z errzz) { - if z != nil { - fmt.Println("Not nil") - } else { - fmt.Println("nil!") - } -} - -func gen[T errzz](terr T) { - // if terr == nil { - - // } - - comparezz(terr) -} - -func TestZZ(t *testing.T) { - fmt.Println("----err----") - gen(&zzz{}) - var z *zzz - gen(z) - var zz errzz - gen(zz) - - // gen(err()) - fmt.Println("----nerr----") - // gen(nerr()) -} diff --git a/examples/chi/celler/main.go b/examples/chi/celler/main.go index 312ff94..4335a5e 100644 --- a/examples/chi/celler/main.go +++ b/examples/chi/celler/main.go @@ -4,13 +4,11 @@ import ( "errors" "fmt" "net/http" - "strconv" "github.com/ghodss/yaml" chai "github.com/go-chai/chai/chi" "github.com/go-chai/chai/examples/shared/controller" - "github.com/go-chai/chai/examples/shared/model" _ "github.com/go-chai/chai/examples/docs/celler" // This is required to be able to serve the stored swagger spec in prod "github.com/go-chai/chai/examples/shared/httputil" @@ -25,61 +23,7 @@ func main() { c := controller.NewController() - r.Route("/api/v1", func(r chi.Router) { - r.Route("/accounts", func(r chi.Router) { - chai.Get(r, "/{id}", c.ShowAccount) - chai.Get(r, "/", c.ListAccounts) - chai.Post(r, "/", c.AddAccount) - r.Delete("/{id:[0-9]+}", c.DeleteAccount) - r.Patch("/{id}", c.UpdateAccount) - r.Post("/{id}/images", c.UploadAccountImage) - }) - - r.Route("/bottles", func(r chi.Router) { - // ShowBottle godoc - // @Summary Show a bottle - // @Description get string by ID - // @ID get-string-by-int - // @Tags bottles - // @Accept json - // @Produce json - // @Param id path int true "Bottle ID" - // @Success 200 {object} model.Bottle - // @Failure 400 {object} httputil.Error - // @Failure 404 {object} httputil.Error - // @Failure 500 {object} httputil.Error - chai.Get(r, "/{id}", func(w http.ResponseWriter, r *http.Request) (*model.Bottle, int, error) { - id := chi.URLParam(r, "id") - bid, err := strconv.Atoi(id) - if err != nil { - return nil, http.StatusBadRequest, err - } - bottle, err := model.BottleOne(bid) - if err != nil { - return nil, http.StatusNotFound, err - } - return bottle, http.StatusOK, nil - }) - chai.Get(r, "/", c.ListBottles) - }) - - r.Route("/admin", func(r chi.Router) { - r.Use(auth) - - chai.Post(r, "/auth", c.Auth) - }) - - r.Route("/examples", func(r chi.Router) { - chai.Get(r, "/ping", c.PingExample) - chai.Get(r, "/calc", c.CalcExample) - // chai.Get(r, "/group{s/{gro}up_id}/accounts/{account_id}", c.PathParamsExample) - chai.Get(r, "/groups/{group_id}/accounts/{account_id}", c.PathParamsExample) - chai.Get(r, "/header", c.HeaderExample) - chai.Get(r, "/securities", c.SecuritiesExample) - chai.Get(r, "/attribute", c.AttributeExample) - chai.Post(r, "/attribute", c.PostExample) - }) - }) + r.Mount("/", c.ChiRoutes()) // This must be used only during development to generate the swagger spec docs, err := chai.OpenAPI2(r) diff --git a/examples/shared/controller/controller.go b/examples/shared/controller/controller.go index 9d2db82..dbb2559 100644 --- a/examples/shared/controller/controller.go +++ b/examples/shared/controller/controller.go @@ -1,5 +1,16 @@ package controller +import ( + "errors" + "net/http" + "strconv" + + chai "github.com/go-chai/chai/chi" + "github.com/go-chai/chai/examples/shared/httputil" + "github.com/go-chai/chai/examples/shared/model" + "github.com/go-chi/chi/v5" +) + // Controller example type Controller struct { } @@ -9,6 +20,78 @@ func NewController() *Controller { return &Controller{} } +func (c *Controller) ChiRoutes() chi.Router { + r := chi.NewRouter() + + r.Route("/api/v1", func(r chi.Router) { + r.Route("/accounts", func(r chi.Router) { + chai.Get(r, "/{id}", c.ShowAccount) + chai.Get(r, "/", c.ListAccounts) + chai.Post(r, "/", c.AddAccount) + r.Delete("/{id:[0-9]+}", c.DeleteAccount) + r.Patch("/{id}", c.UpdateAccount) + r.Post("/{id}/images", c.UploadAccountImage) + }) + + r.Route("/bottles", func(r chi.Router) { + // ShowBottle godoc + // @Summary Show a bottle + // @Description get string by ID + // @ID get-string-by-int + // @Tags bottles + // @Accept json + // @Produce json + // @Param id path int true "Bottle ID" + // @Success 200 {object} model.Bottle + // @Failure 400 {object} httputil.Error + // @Failure 404 {object} httputil.Error + // @Failure 500 {object} httputil.Error + chai.Get(r, "/{id}", func(w http.ResponseWriter, r *http.Request) (*model.Bottle, int, error) { + id := chi.URLParam(r, "id") + bid, err := strconv.Atoi(id) + if err != nil { + return nil, http.StatusBadRequest, err + } + bottle, err := model.BottleOne(bid) + if err != nil { + return nil, http.StatusNotFound, err + } + return bottle, http.StatusOK, nil + }) + chai.Get(r, "/", c.ListBottles) + }) + + r.Route("/admin", func(r chi.Router) { + r.Use(auth) + + chai.Post(r, "/auth", c.Auth) + }) + + r.Route("/examples", func(r chi.Router) { + chai.Get(r, "/ping", c.PingExample) + chai.Get(r, "/calc", c.CalcExample) + // chai.Get(r, "/group{s/{gro}up_id}/accounts/{account_id}", c.PathParamsExample) + chai.Get(r, "/groups/{group_id}/accounts/{account_id}", c.PathParamsExample) + chai.Get(r, "/header", c.HeaderExample) + chai.Get(r, "/securities", c.SecuritiesExample) + chai.Get(r, "/attribute", c.AttributeExample) + chai.Post(r, "/attribute", c.PostExample) + }) + }) + + return r +} + +func auth(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if len(r.Header.Get("Authorization")) == 0 { + httputil.NewError(w, http.StatusUnauthorized, errors.New("Authorization is required Header")) + return + } + next.ServeHTTP(w, r) + }) +} + // Message example type Message struct { Message string `json:"message" example:"message"` diff --git a/go.mod b/go.mod index 00200b8..8ed0da2 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,10 @@ module github.com/go-chai/chai go 1.18 require ( - github.com/davecgh/go-spew v1.1.1 github.com/getkin/kin-openapi v0.88.0 github.com/ghodss/yaml v1.0.0 github.com/go-chai/swag v1.7.8-fork2 github.com/go-chi/chi/v5 v5.0.7 - github.com/go-chi/docgen v1.2.0 github.com/go-chi/render v1.0.1 github.com/go-openapi/spec v0.20.4 github.com/gofrs/uuid v4.2.0+incompatible @@ -23,6 +21,7 @@ require ( github.com/KyleBanks/depth v1.2.1 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.19.6 // indirect github.com/go-openapi/swag v0.19.15 // indirect diff --git a/go.sum b/go.sum index 1a4557c..bc6da2c 100644 --- a/go.sum +++ b/go.sum @@ -18,11 +18,8 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-chai/swag v1.7.8-fork2 h1:ijgxBjnbrjRK1XAqLor+FvMLpdST7MEk2MGw3V+Zz0I= github.com/go-chai/swag v1.7.8-fork2/go.mod h1:7osfZ2yWVpvxjKkjueEGIi5GIltebBc7YsfN8JtRURc= -github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-chi/docgen v1.2.0 h1:da0Nq2PKU9W9pSOTUfVrKI1vIgTGpauo9cfh4Iwivek= -github.com/go-chi/docgen v1.2.0/go.mod h1:G9W0G551cs2BFMSn/cnGwX+JBHEloAgo17MBhyrnhPI= github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= diff --git a/openapi2/internal/tests/testfile.go b/internal/tests/testfile.go similarity index 100% rename from openapi2/internal/tests/testfile.go rename to internal/tests/testfile.go diff --git a/internal/tests/testtypes.go b/internal/tests/testtypes.go new file mode 100644 index 0000000..a8cd62c --- /dev/null +++ b/internal/tests/testtypes.go @@ -0,0 +1,24 @@ +package tests + +type TestStruct struct { + Foo string `json:"foo"` + Bar int `json:"bar"` +} + +type TestInnerResponse struct { + FooFoo int `json:"foo_foo"` + BarBar int `json:"bar_bar"` +} +type TestResponse struct { + Foo string `json:"foo"` + Bar string `json:"bar"` + + TestInnerResponse TestInnerResponse `json:"test_inner_response"` +} + +type TestRequest struct { + Foo string `json:"foob"` + Bar string `json:"barb"` + + TestInnerResponse TestInnerResponse `json:"test_inner_responseb"` +} diff --git a/openapi2/funcinfo_test.go b/openapi2/funcinfo_test.go index aab472e..aad8031 100644 --- a/openapi2/funcinfo_test.go +++ b/openapi2/funcinfo_test.go @@ -7,7 +7,7 @@ import ( "go/token" "testing" - "github.com/go-chai/chai/openapi2/internal/tests" + "github.com/go-chai/chai/internal/tests" "github.com/stretchr/testify/require" ) @@ -27,7 +27,7 @@ func TestFixFuncLine(t *testing.T) { { name: "simple", args: args{ - filePath: "internal/tests/testfile.go", + filePath: "../internal/tests/testfile.go", line: 24, }, want: want{fixedLine: 12}, @@ -35,7 +35,7 @@ func TestFixFuncLine(t *testing.T) { { name: "simple2", args: args{ - filePath: "internal/tests/testfile.go", + filePath: "../internal/tests/testfile.go", line: 44, }, want: want{fixedLine: 32}, @@ -43,7 +43,7 @@ func TestFixFuncLine(t *testing.T) { { name: "not simple", args: args{ - filePath: "internal/tests/testfile.go", + filePath: "../internal/tests/testfile.go", line: 52, }, want: want{fixedLine: 52}, @@ -51,7 +51,7 @@ func TestFixFuncLine(t *testing.T) { { name: "not simple 2", args: args{ - filePath: "internal/tests/testfile.go", + filePath: "../internal/tests/testfile.go", line: 74, }, want: want{fixedLine: 74}, diff --git a/openapi2/internal/tests/testtypes.go b/openapi2/internal/tests/testtypes.go deleted file mode 100644 index 6d7c207..0000000 --- a/openapi2/internal/tests/testtypes.go +++ /dev/null @@ -1,6 +0,0 @@ -package tests - -type TestStruct struct { - Foo string `json:"foo"` - Bar int `json:"bar"` -} diff --git a/openapi2/openapi2_test.go b/openapi2/openapi2_test.go index c1b4d35..106aa1f 100644 --- a/openapi2/openapi2_test.go +++ b/openapi2/openapi2_test.go @@ -2,10 +2,13 @@ package openapi2 import ( "encoding/json" + "io/ioutil" "log" + "net/http" "testing" - "github.com/go-chai/chai/openapi2/internal/tests" + "github.com/go-chai/chai/chai" + "github.com/go-chai/chai/internal/tests" "github.com/go-chai/swag" "github.com/go-openapi/spec" "github.com/stretchr/testify/assert" @@ -56,7 +59,7 @@ func TestParseAPIObjectSchema(t *testing.T) { fi := getFuncInfo(RegisterRoute) op := swag.NewOperation(parser) - err := parser.GetAllGoFileInfoAndParseTypes("./") + err := parser.GetAllGoFileInfoAndParseTypes("../internal") require.NoError(t, err) schema, err := op.ParseAPIObjectSchema("object", typeName(tt.args.val), fi.ASTFile) @@ -251,3 +254,65 @@ func TestSortedKeys(t *testing.T) { }) } } + +func TestDocs(t *testing.T) { + type args struct { + routes []*Route + } + tests := []struct { + name string + args args + filePath string + wantErr bool + }{ + { + name: "t1", + args: args{ + routes: []*Route{ + { + Method: "GET", + Path: "/test1/{p1}/{p2}", + Params: []spec.Parameter{ + {ParamProps: spec.ParamProps{Name: "p1", In: "path", Description: "d1", Required: true}}, + {ParamProps: spec.ParamProps{Name: "p2", In: "path", Description: "d2", Required: true}}, + }, + + // ShowBottle godoc + // @Summary Test Handler + // @Description get string by ID + // @ID get-string-by-int + // @Tags bottles + // @Accept json + // @Produce json + // @Success 200 + // @Failure 400,404,500 + Handler: chai.NewReqResHandler(func(req *tests.TestRequest, w http.ResponseWriter, r *http.Request) (*tests.TestResponse, int, error) { + return nil, 0, nil + }), + }, + }, + }, + filePath: "testdata/t1.json", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Docs(tt.args.routes) + + LogJSON(got) + + if (err != nil) != tt.wantErr { + t.Errorf("Docs() error = %v, wantErr %v", err, tt.wantErr) + return + } + require.JSONEq(t, load(t, tt.filePath), js(got)) + }) + } +} + +func load(t *testing.T, path string) string { + b, err := ioutil.ReadFile(path) + require.NoError(t, err) + return string(b) +} diff --git a/openapi2/testdata/t1.json b/openapi2/testdata/t1.json new file mode 100644 index 0000000..fb98493 --- /dev/null +++ b/openapi2/testdata/t1.json @@ -0,0 +1,111 @@ +{ + "info": { + "contact": {} + }, + "paths": { + "/test1/{p1}/{p2}": { + "get": { + "description": "get string by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "bottles" + ], + "summary": "Test Handler", + "operationId": "get-string-by-int", + "parameters": [ + { + "name": "body", + "in": "body", + "schema": { + "$ref": "#/definitions/tests.TestRequest" + } + }, + { + "description": "d1", + "name": "p1", + "in": "path", + "required": true + }, + { + "description": "d2", + "name": "p2", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/tests.TestResponse" + } + }, + "400": { + "description": "", + "schema": { + "type": "string" + } + }, + "404": { + "description": "", + "schema": { + "type": "string" + } + }, + "500": { + "description": "", + "schema": { + "type": "string" + } + } + } + } + } + }, + "definitions": { + "tests.TestInnerResponse": { + "type": "object", + "properties": { + "bar_bar": { + "type": "integer" + }, + "foo_foo": { + "type": "integer" + } + } + }, + "tests.TestRequest": { + "type": "object", + "properties": { + "barb": { + "type": "string" + }, + "foob": { + "type": "string" + }, + "test_inner_responseb": { + "$ref": "#/definitions/tests.TestInnerResponse" + } + } + }, + "tests.TestResponse": { + "type": "object", + "properties": { + "bar": { + "type": "string" + }, + "foo": { + "type": "string" + }, + "test_inner_response": { + "$ref": "#/definitions/tests.TestInnerResponse" + } + } + } + } +}