Skip to content

Commit

Permalink
Tests #3
Browse files Browse the repository at this point in the history
  • Loading branch information
joegasewicz committed May 1, 2023
1 parent f2b4db9 commit e7ba595
Show file tree
Hide file tree
Showing 7 changed files with 317 additions and 67 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}

Expand Down
167 changes: 113 additions & 54 deletions gomek.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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 /<path_name>/ to /<path_name>
// This is because Go's http package swaps out POSTs to GETS with a /<path_name>/ 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 /<path_name>
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()
Expand Down Expand Up @@ -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,
Expand All @@ -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 /<path_name>/ to /<path_name>
// This is because Go's http package swaps out POSTs to GETS with a /<path_name>/ 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 /<path_name>
a.currentRoute = fmt.Sprintf("/%s", storedView.rootName)
a.view.StoreResource(a)
}
}
break
}
}
}
return err
}

// SetHost sets the host. Default is ":"
Expand Down Expand Up @@ -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{}`
Expand Down Expand Up @@ -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
}
16 changes: 16 additions & 0 deletions templates_test.go
Original file line number Diff line number Diff line change
@@ -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])
}
}

}
1 change: 1 addition & 0 deletions test_middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package gomek
35 changes: 35 additions & 0 deletions testing_utils.go
Original file line number Diff line number Diff line change
@@ -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)
}
37 changes: 26 additions & 11 deletions view.go
Original file line number Diff line number Diff line change
Expand Up @@ -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...)
Expand All @@ -31,20 +46,20 @@ 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)
}
}

// 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 {
Expand All @@ -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
}
Expand Down Expand Up @@ -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 (
Expand All @@ -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
Expand All @@ -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")
Expand Down
Loading

0 comments on commit e7ba595

Please sign in to comment.