From e7ba5957941440f6e9a5d3f4425161bb29266990 Mon Sep 17 00:00:00 2001 From: joegasewicz Date: Mon, 1 May 2023 20:19:55 +0100 Subject: [PATCH] Tests #3 --- README.md | 4 +- gomek.go | 167 ++++++++++++++++++++++++++++++--------------- templates_test.go | 16 +++++ test_middleware.go | 1 + testing_utils.go | 35 ++++++++++ view.go | 37 +++++++--- view_test.go | 124 +++++++++++++++++++++++++++++++++ 7 files changed, 317 insertions(+), 67 deletions(-) create mode 100644 templates_test.go create mode 100644 test_middleware.go create mode 100644 testing_utils.go create mode 100644 view_test.go diff --git a/README.md b/README.md index 9177bc7..4a78c7a 100644 --- a/README.md +++ b/README.md @@ -138,11 +138,11 @@ app.Use(gomek.Authorize(whiteList, func(r *http.Request) (bool, context.Context) type Notice struct { } // Implement the `Resource` interface -func (n *Notice) Post(w http.ResponseWriter, request *http.Request, data *gomek.Data) { +func (n *Notice) Post(w http.ResponseWriter, request *http.Request, d *gomek.Data) { panic("implement me") } -func (n *Notice) Put(w http.ResponseWriter, request *http.Request, data *gomek.Data) { +func (n *Notice) Put(w http.ResponseWriter, request *http.Request, d *gomek.Data) { panic("implement me") } diff --git a/gomek.go b/gomek.go index a09d68a..0ac704f 100644 --- a/gomek.go +++ b/gomek.go @@ -30,6 +30,8 @@ type CurrentView func(http.ResponseWriter, *http.Request, *Data) type Handle func(pattern string, handler http.Handler) +type Middleware []func(http.Handler) http.HandlerFunc + // Config type that should be passed to `gomek.New` type Config struct { BaseTemplateName string @@ -43,7 +45,24 @@ type Resource interface { Put(http.ResponseWriter, *http.Request, *Data) } -// App +type IApp interface { + resetCurrentView() + cloneRoute() + Start() error + SetHost(host string) + Listen(port int) + Methods(methods ...string) *App + Route(route string) *App + Templates(templates ...string) + BaseTemplates(templates ...string) + View(view CurrentView) *App + Resource(m Resource) *App + Use(h func(http.Handler) http.HandlerFunc) + Shutdown() + GetView() *View + GetConfig() *Config +} + type App struct { baseTemplateName string baseTemplates []string @@ -59,7 +78,7 @@ type App struct { Protocol string view View Handle Handle - middleware []func(http.Handler) http.HandlerFunc + middleware Middleware rootCtx context.Context authCtx context.Context server *http.Server @@ -69,38 +88,15 @@ func createAddr(a *App) string { return fmt.Sprintf("%s:%d", a.Host, a.Port) } -func (a *App) resetCurrentView() { - a.currentRoute = "" - a.currentMethods = nil - a.baseTemplates = nil - a.currentView = nil +func (a *App) GetView() *View { + return &a.view } -func (a *App) cloneRoute() { - // Duplicate the resource methods for // to / - // This is because Go's http package swaps out POSTs to GETS with a // path. - if a.currentRoute[len(a.currentRoute)-1:] == ">" { - for _, m := range a.currentMethods { - if m == "POST" { - // Construct a path name from the stored `registeredRoute` value - for _, storedView := range a.view.StoredViews { - if storedView.registeredRoute == a.currentRoute { - // Create a route without the slash at the parth end e.g / - a.currentRoute = fmt.Sprintf("/%s", storedView.rootName) - a.view.StoreResource(a) - } - } - break - } - } - } +func (a *App) GetConfig() *Config { + return &a.Config } -// Start sets up all the registered views, templates & middleware -// -// app = gomek.New(gomek.Config{}) -// app.Start() -func (a *App) Start() error { +func (a *App) setup() *http.Server { var auth = map[string]string{} // Set app context a.rootCtx = context.Background() @@ -135,13 +131,13 @@ func (a *App) Start() error { //a.Use(Logging) // Create views for _, v := range a.view.StoredViews { - a.view.Create(a, v) + a.view.Create(&a.Config, &a.middleware, a.mux, v) } // Create the origin address := createAddr(a) // Server - server := &http.Server{ + return &http.Server{ Addr: address, Handler: a.mux, TLSConfig: nil, @@ -156,16 +152,33 @@ func (a *App) Start() error { BaseContext: nil, ConnContext: nil, } - log.Printf("Starting server on %s://%s", a.Protocol, server.Addr) - // Start server... - a.server = server - err := server.ListenAndServe() - if err != nil { - log.Println("error starting gomek server", err) - } else { - log.Printf("Starting server on %s://%s", a.Protocol, address) +} + +func (a *App) resetCurrentView() { + a.currentRoute = "" + a.currentMethods = nil + a.baseTemplates = nil + a.currentView = nil +} + +func (a *App) cloneRoute() { + // Duplicate the resource methods for // to / + // This is because Go's http package swaps out POSTs to GETS with a // path. + if a.currentRoute[len(a.currentRoute)-1:] == ">" { + for _, m := range a.currentMethods { + if m == "POST" { + // Construct a path name from the stored `registeredRoute` value + for _, storedView := range a.view.StoredViews { + if storedView.registeredRoute == a.currentRoute { + // Create a route without the slash at the parth end e.g / + a.currentRoute = fmt.Sprintf("/%s", storedView.rootName) + a.view.StoreResource(a) + } + } + break + } + } } - return err } // SetHost sets the host. Default is ":" @@ -247,19 +260,6 @@ func (a *App) BaseTemplates(templates ...string) { a.Config.BaseTemplates = templates } -// New creates a new gomek application -// -// app := gomek.New(gomek.Config{}) -func New(config Config) App { - mux := http.NewServeMux() - - return App{ - Config: config, - mux: mux, - Handle: mux.Handle, - } -} - // View is called if the Route request URL is matched. // handler arg is your View function.Create a View - template data needs to be passed // by value to `data *map[string]interface{}` @@ -359,3 +359,62 @@ func GetParams(r *http.Request, name string) ([]string, error) { } return paramValue, nil } + +// App +type _App struct { + *App +} + +// Start sets up all the registered views, templates & middleware +// +// app = gomek.New(gomek.Config{}) +// app.Start() +func (a *App) Start() error { + server := a.setup() + log.Printf("Starting server on %s://%s", a.Protocol, server.Addr) + // Start server... + a.server = a.setup() + err := server.ListenAndServe() + if err != nil { + log.Println("error starting gomek server", err) + } else { + log.Printf("Starting server on %s://%s", a.Protocol, server.Addr) + } + return err +} + +// New creates a new gomek application +// +// app := gomek.New(gomek.Config{}) +func New(config Config) *App { + mux := http.NewServeMux() + return &App{ + Config: config, + mux: mux, + Handle: mux.Handle, + } +} + +type TestApp struct { + App +} + +// NewTestApp creates a new gomek application +// +// app := gomek.NewTestApp(gomek.Config{}) +func NewTestApp(config Config) IApp { + mux := http.NewServeMux() + app := TestApp{ + App{ + Config: config, + mux: mux, + Handle: mux.Handle, + }, + } + return &app +} + +func (a *TestApp) Start() error { + a.App.setup() + return nil +} diff --git a/templates_test.go b/templates_test.go new file mode 100644 index 0000000..3b4636e --- /dev/null +++ b/templates_test.go @@ -0,0 +1,16 @@ +package gomek + +import "testing" + +func TestTemplate_Run(t *testing.T) { + template := Template{base: []string{"base.html"}} + routeTemplates := []string{"/index.gohtml", "/news.gohtml"} + result := template.Run(routeTemplates...) + expected := []string{"base.html", "/index.gohtml", "/news.gohtml"} + for i, _ := range result { + if result[i] != expected[i] { + t.Errorf("expected %v got %v", result[i], expected[i]) + } + } + +} diff --git a/test_middleware.go b/test_middleware.go new file mode 100644 index 0000000..0fdfb23 --- /dev/null +++ b/test_middleware.go @@ -0,0 +1 @@ +package gomek diff --git a/testing_utils.go b/testing_utils.go new file mode 100644 index 0000000..54732d4 --- /dev/null +++ b/testing_utils.go @@ -0,0 +1,35 @@ +package gomek + +import "net/http" + +// CreateTestHandler this function provides an easy way to access the +// `http.HandlerFunc` func. Testing gomek views is thus the same as testing +// any regular HandlerFunc handlers. +// +// c := Config{} +// mockApp := NewTestApp(c) +// mockApp.Route("/blogs").Methods("POST").Resource(&Notice{}) +// mockApp.Start() +// +// notice := Notice{} +// +// handler := CreateTestHandler(mockApp, notice.Post) +// +// req := httptest.NewRequest(http.MethodPost, "/blogs", nil) +// w := httptest.NewRecorder() +// handler(w, req) // regular HandlerFunc +// resp := w.Result() +// +// defer resp.Body.Close() +// data, err := io.ReadAll(resp.Body) +// if err != nil { +// t.Errorf("Error: %v", err) +// } +// expected := `{"name":"Joe"}` +// if string(data) != expected { +// t.Errorf("Expected %s got '%v'", expected, string(data)) +func CreateTestHandler(testApp IApp, view CurrentView) http.HandlerFunc { + v := testApp.GetView() + var templates []string + return v.handleFuncWrapper(templates, testApp.GetConfig(), *testApp.GetView(), view) +} diff --git a/view.go b/view.go index fd1c4ad..f5a9b60 100644 --- a/view.go +++ b/view.go @@ -20,9 +20,24 @@ type View struct { StoredViews []View } -func (v *View) Create(a *App, view View) { +// NewTestView view testing util. Enables the testing of individual + +func NewTestView(views []View) View { + return View{ + registeredRoute: "", + routePaths: nil, + rootName: "", + CurrentRoute: "/blogs", + CurrentMethods: []string{"POST"}, + CurrentTemplates: nil, + CurrentView: nil, + StoredViews: views, + } +} + +func (v *View) Create(config *Config, middleware *Middleware, mux *http.ServeMux, view View) { t := Template{ - base: a.Config.BaseTemplates, + base: config.BaseTemplates, } // Add templates finalTemplates := t.Run(view.CurrentTemplates...) @@ -31,9 +46,9 @@ func (v *View) Create(a *App, view View) { } // Add middleware var wrappedHandler http.HandlerFunc - wrappedHandler = v.handleFuncWrapper(finalTemplates, a, view.CurrentView) + wrappedHandler = v.handleFuncWrapper(finalTemplates, config, view, view.CurrentView) - for _, m := range a.middleware { + for _, m := range *middleware { if m != nil { wrappedHandler = m(wrappedHandler) } @@ -41,10 +56,10 @@ func (v *View) Create(a *App, view View) { // In case there is an option to turn off all gomek default middleware if wrappedHandler == nil { - wrappedHandler = v.handleFuncWrapper(finalTemplates, a, view.CurrentView) + wrappedHandler = v.handleFuncWrapper(finalTemplates, config, view, view.CurrentView) } // Create handler - a.mux.HandleFunc(view.CurrentRoute, wrappedHandler) + mux.HandleFunc(view.CurrentRoute, wrappedHandler) } func stripTokens(pathSegment string) string { @@ -53,8 +68,8 @@ func stripTokens(pathSegment string) string { return path[0] } -func getView(r *http.Request, a *App) (*View, map[string]string, bool) { - for _, v := range a.view.StoredViews { +func getView(r *http.Request, view View) (*View, map[string]string, bool) { + for _, v := range view.StoredViews { if r.URL.Path == v.CurrentRoute { return &v, nil, true } @@ -88,7 +103,7 @@ func testMethod(r *http.Request, v View) bool { return false } -func (v *View) handleFuncWrapper(templates []string, a *App, currentView CurrentView) http.HandlerFunc { +func (v *View) handleFuncWrapper(templates []string, config *Config, view View, currentView CurrentView) http.HandlerFunc { var data Data return func(w http.ResponseWriter, r *http.Request) { var ( @@ -97,7 +112,7 @@ func (v *View) handleFuncWrapper(templates []string, a *App, currentView Current vars map[string]string ) // Route - if v, viewVars, ok := getView(r, a); ok { + if v, viewVars, ok := getView(r, view); ok { vars = viewVars routeMatches = true // Methods @@ -120,7 +135,7 @@ func (v *View) handleFuncWrapper(templates []string, a *App, currentView Current if err != nil { log.Fatalf("Error parsing templates: %v", err.Error()) } - te.ExecuteTemplate(w, a.Config.BaseTemplateName, data) + te.ExecuteTemplate(w, config.BaseTemplateName, data) } else { // No templates so treat as JSON / TEXT r.Header.Set("Content-Type", "application/json") diff --git a/view_test.go b/view_test.go new file mode 100644 index 0000000..e7999ca --- /dev/null +++ b/view_test.go @@ -0,0 +1,124 @@ +package gomek + +import ( + "io" + "net/http" + "net/http/httptest" + "testing" +) + +type Notice struct{} + +func (n *Notice) Post(w http.ResponseWriter, request *http.Request, d *Data) { + data := struct { + Name string `json:"name"` + }{ + Name: "Joe", + } + JSON(w, data, http.StatusOK) +} + +func (n *Notice) Put(w http.ResponseWriter, request *http.Request, d *Data) { + data := struct { + Name string `json:"name"` + }{ + Name: "Cosmo", + } + JSON(w, data, http.StatusOK) +} + +func (n *Notice) Delete(w http.ResponseWriter, r *http.Request, d *Data) { + data := struct { + Name string `json:"name"` + }{ + Name: "Alex", + } + JSON(w, data, http.StatusOK) +} + +func (n *Notice) Get(w http.ResponseWriter, r *http.Request, d *Data) { + data := struct { + Name string `json:"name"` + }{ + Name: "Ram", + } + JSON(w, data, http.StatusOK) +} + +func TestViewGet(t *testing.T) { + c := Config{} + mockApp := NewTestApp(c) + mockApp.Route("/blogs").Methods("POST").Resource(&Notice{}) + mockApp.Start() + + notice := Notice{} + + handler := CreateTestHandler(mockApp, notice.Get) + + req := httptest.NewRequest(http.MethodPost, "/blogs", nil) + w := httptest.NewRecorder() + handler(w, req) + resp := w.Result() + + defer resp.Body.Close() + data, err := io.ReadAll(resp.Body) + if err != nil { + t.Errorf("Error: %v", err) + } + expected := `{"name":"Joe"}` + if string(data) != expected { + t.Errorf("Expected %s got '%v'", expected, string(data)) + } +} + +func TestViewPost(t *testing.T) { + c := Config{} + mockApp := NewTestApp(c) + mockApp.Route("/blogs").Methods("GET", "POST").Resource(&Notice{}) + mockApp.Start() + + notice := Notice{} + + handler := CreateTestHandler(mockApp, notice.Get) + + req := httptest.NewRequest(http.MethodGet, "/blogs", nil) + w := httptest.NewRecorder() + handler(w, req) + resp := w.Result() + + defer resp.Body.Close() + data, err := io.ReadAll(resp.Body) + if err != nil { + t.Errorf("Error: %v", err) + } + expected := `{"name":"Ram"}` + if string(data) != expected { + t.Errorf("Expected %s got '%v'", expected, string(data)) + } +} + +func TestViewDelete(t *testing.T) { + c := Config{} + mockApp := NewTestApp(c) + mockApp.Route("/blogs").Methods("GET", "POST", "PUT", "DELETE").Resource(&Notice{}) + mockApp.Start() + + notice := Notice{} + + handler := CreateTestHandler(mockApp, notice.Put) + + req := httptest.NewRequest(http.MethodPut, "/blogs", nil) + w := httptest.NewRecorder() + handler(w, req) + resp := w.Result() + + defer resp.Body.Close() + data, err := io.ReadAll(resp.Body) + if err != nil { + t.Errorf("Error: %v", err) + } + expected := `{"name":"Cosmo"}` + if string(data) != expected { + t.Errorf("Expected %s got '%v'", expected, string(data)) + } +}