From 1075149bb85ec09eade3424538ca8905ddec0dfc Mon Sep 17 00:00:00 2001 From: Aaron Date: Tue, 24 Feb 2015 14:45:37 -0800 Subject: [PATCH] Add router tests. - Rename Endpoint to Location in ErrAndRedirect --- authboss_test.go | 24 ----- config.go | 8 ++ confirm/confirm.go | 4 +- confirm/confirm_test.go | 4 +- errors.go | 4 +- module_test.go | 18 +--- router.go | 37 ++++++-- router_test.go | 200 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 244 insertions(+), 55 deletions(-) create mode 100644 router_test.go diff --git a/authboss_test.go b/authboss_test.go index 709a07a..a90b412 100644 --- a/authboss_test.go +++ b/authboss_test.go @@ -22,30 +22,6 @@ func TestAuthBossInit(t *testing.T) { } } -func TestAuthBossRouter(t *testing.T) { - NewConfig() - Cfg.Storer = mockStorer{} - Cfg.CookieStoreMaker = func(_ http.ResponseWriter, _ *http.Request) ClientStorer { - return mockClientStore{} - } - Cfg.SessionStoreMaker = SessionStoreMaker(Cfg.CookieStoreMaker) - Cfg.MountPath = "/candycanes" - - if err := Init(); err != nil { - t.Error("Unexpected error:", err) - } - router := NewRouter() - - r, _ := http.NewRequest("GET", "/candycanes/testroute", nil) - response := httptest.NewRecorder() - - router.ServeHTTP(response, r) - - if response.Header().Get("testhandler") != "test" { - t.Error("Expected a header to have been set.") - } -} - func TestAuthBossCurrentUser(t *testing.T) { NewConfig() Cfg.Storer = mockStorer{"joe": Attributes{"email": "john@john.com", "password": "lies"}} diff --git a/config.go b/config.go index 6a3d868..6d24cd6 100644 --- a/config.go +++ b/config.go @@ -4,6 +4,7 @@ import ( "html/template" "io" "io/ioutil" + "net/http" "net/smtp" "time" @@ -32,6 +33,13 @@ type Config struct { LayoutEmail *template.Template LayoutDataMaker ViewDataMaker + // ErrorHandler handles would be 500 errors. + ErrorHandler http.Handler + // BadRequestHandler handles would be 400 errors. + BadRequestHandler http.Handler + // NotFoundHandler handles would be 404 errors. + NotFoundHandler http.Handler + AuthLogoutRoute string AuthLoginSuccessRoute string diff --git a/confirm/confirm.go b/confirm/confirm.go index af9f57d..e2038cb 100644 --- a/confirm/confirm.go +++ b/confirm/confirm.go @@ -149,7 +149,7 @@ func (c *Confirm) confirmHandler(ctx *authboss.Context, w http.ResponseWriter, r toHash, err := base64.URLEncoding.DecodeString(token) if err != nil { return authboss.ErrAndRedirect{ - Endpoint: "/", Err: fmt.Errorf("confirm: token failed to decode %q => %v\n", token, err), + Location: "/", Err: fmt.Errorf("confirm: token failed to decode %q => %v\n", token, err), } } @@ -158,7 +158,7 @@ func (c *Confirm) confirmHandler(ctx *authboss.Context, w http.ResponseWriter, r dbTok := base64.StdEncoding.EncodeToString(sum[:]) user, err := authboss.Cfg.Storer.(ConfirmStorer).ConfirmUser(dbTok) if err == authboss.ErrUserNotFound { - return authboss.ErrAndRedirect{Endpoint: "/", Err: errors.New("confirm: token not found")} + return authboss.ErrAndRedirect{Location: "/", Err: errors.New("confirm: token not found")} } else if err != nil { return err } diff --git a/confirm/confirm_test.go b/confirm/confirm_test.go index 92b736b..886665a 100644 --- a/confirm/confirm_test.go +++ b/confirm/confirm_test.go @@ -141,10 +141,10 @@ func TestConfirm_ConfirmHandlerErrors(t *testing.T) { }{ {"http://localhost", false, authboss.ClientDataErr{FormValueConfirm}}, {"http://localhost?cnf=c$ats", false, - authboss.ErrAndRedirect{Endpoint: "/", Err: errors.New("confirm: token failed to decode \"c$ats\" => illegal base64 data at input byte 1\n")}, + authboss.ErrAndRedirect{Location: "/", Err: errors.New("confirm: token failed to decode \"c$ats\" => illegal base64 data at input byte 1\n")}, }, {"http://localhost?cnf=SGVsbG8sIHBsYXlncm91bmQ=", false, - authboss.ErrAndRedirect{Endpoint: "/", Err: errors.New(`confirm: token not found`)}, + authboss.ErrAndRedirect{Location: "/", Err: errors.New(`confirm: token not found`)}, }, } diff --git a/errors.go b/errors.go index 30855da..6c61788 100644 --- a/errors.go +++ b/errors.go @@ -40,13 +40,13 @@ func (c ClientDataErr) Error() string { // be to redirect. type ErrAndRedirect struct { Err error - Endpoint string + Location string FlashSuccess string FlashError string } func (e ErrAndRedirect) Error() string { - return fmt.Sprintf("Error: %v, Redirecting to: %s", e.Err, e.Endpoint) + return fmt.Sprintf("Error: %v, Redirecting to: %s", e.Err, e.Location) } // RenderErr represents an error that occured during rendering diff --git a/module_test.go b/module_test.go index 0138285..54856e1 100644 --- a/module_test.go +++ b/module_test.go @@ -23,21 +23,11 @@ func testHandler(ctx *Context, w http.ResponseWriter, r *http.Request) error { return nil } -func (t *testModule) Initialize() error { - return nil -} - -func (t *testModule) Routes() RouteTable { - return t.r -} - -func (t *testModule) Storage() StorageOptions { - return t.s -} +func (t *testModule) Initialize() error { return nil } +func (t *testModule) Routes() RouteTable { return t.r } +func (t *testModule) Storage() StorageOptions { return t.s } func TestRegister(t *testing.T) { - t.Parallel() - // RegisterModule called by TestMain. if _, ok := modules["testmodule"]; !ok { @@ -50,8 +40,6 @@ func TestRegister(t *testing.T) { } func TestLoadedModules(t *testing.T) { - t.Parallel() - // RegisterModule called by TestMain. loadedMods := LoadedModules() diff --git a/router.go b/router.go index 3b4a81c..fd647ab 100644 --- a/router.go +++ b/router.go @@ -2,6 +2,7 @@ package authboss import ( "fmt" + "io" "net/http" "path" ) @@ -23,6 +24,15 @@ func NewRouter() http.Handler { } } + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + if Cfg.NotFoundHandler != nil { + Cfg.NotFoundHandler.ServeHTTP(w, r) + } else { + w.WriteHeader(http.StatusNotFound) + io.WriteString(w, "404 Page not found") + } + }) + return mux } @@ -46,22 +56,29 @@ func (c contextRoute) ServeHTTP(w http.ResponseWriter, r *http.Request) { } fmt.Fprintf(Cfg.LogWriter, "Error Occurred at %s: %v", r.URL.Path, err) + switch e := err.(type) { - case AttributeErr: - w.WriteHeader(http.StatusInternalServerError) - case ClientDataErr: - w.WriteHeader(http.StatusBadRequest) case ErrAndRedirect: if len(e.FlashSuccess) > 0 { - ctx.CookieStorer.Put(FlashSuccessKey, e.FlashSuccess) + ctx.SessionStorer.Put(FlashSuccessKey, e.FlashSuccess) } if len(e.FlashError) > 0 { - ctx.CookieStorer.Put(FlashErrorKey, e.FlashError) + ctx.SessionStorer.Put(FlashErrorKey, e.FlashError) + } + http.Redirect(w, r, e.Location, http.StatusFound) + case ClientDataErr: + if Cfg.BadRequestHandler != nil { + Cfg.BadRequestHandler.ServeHTTP(w, r) + } else { + w.WriteHeader(http.StatusBadRequest) + io.WriteString(w, "400 Bad request") } - http.Redirect(w, r, e.Endpoint, http.StatusTemporaryRedirect) - case RenderErr: - w.WriteHeader(http.StatusInternalServerError) default: - w.WriteHeader(http.StatusInternalServerError) + if Cfg.ErrorHandler != nil { + Cfg.ErrorHandler.ServeHTTP(w, r) + } else { + w.WriteHeader(http.StatusInternalServerError) + io.WriteString(w, "500 An error has occurred") + } } } diff --git a/router_test.go b/router_test.go new file mode 100644 index 0000000..70b6c76 --- /dev/null +++ b/router_test.go @@ -0,0 +1,200 @@ +package authboss + +import ( + "bytes" + "errors" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +type testRouterMod struct { + handler HandlerFunc + routes RouteTable +} + +func (t testRouterMod) Initialize() error { return nil } +func (t testRouterMod) Routes() RouteTable { return t.routes } +func (t testRouterMod) Storage() StorageOptions { return nil } + +func testRouterSetup() (http.Handler, *bytes.Buffer) { + Cfg = NewConfig() + Cfg.MountPath = "/prefix" + Cfg.SessionStoreMaker = func(w http.ResponseWriter, r *http.Request) ClientStorer { return mockClientStore{} } + Cfg.CookieStoreMaker = func(w http.ResponseWriter, r *http.Request) ClientStorer { return mockClientStore{} } + logger := &bytes.Buffer{} + Cfg.LogWriter = logger + + return NewRouter(), logger +} + +func testRouterCallbackSetup(path string, h HandlerFunc) (w *httptest.ResponseRecorder, r *http.Request) { + modules = map[string]Modularizer{ + "test": testRouterMod{ + routes: map[string]HandlerFunc{ + path: h, + }, + }, + } + + w = httptest.NewRecorder() + r, _ = http.NewRequest("GET", "http://localhost/prefix"+path, nil) + + return w, r +} + +func TestRouter(t *testing.T) { + called := false + + w, r := testRouterCallbackSetup("/called", func(ctx *Context, w http.ResponseWriter, r *http.Request) error { + called = true + return nil + }) + + router, _ := testRouterSetup() + + router.ServeHTTP(w, r) + + if !called { + t.Error("Expected handler to be called.") + } +} + +func TestRouter_NotFound(t *testing.T) { + router, _ := testRouterSetup() + w := httptest.NewRecorder() + r, _ := http.NewRequest("GET", "http://localhost/wat", nil) + + router.ServeHTTP(w, r) + if w.Code != http.StatusNotFound { + t.Error("Wrong code:", w.Code) + } + if body := w.Body.String(); body != "404 Page not found" { + t.Error("Wrong body:", body) + } + + called := false + Cfg.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + called = true + }) + + router.ServeHTTP(w, r) + if !called { + t.Error("Should be called.") + } +} + +func TestRouter_BadRequest(t *testing.T) { + err := ClientDataErr{"what"} + w, r := testRouterCallbackSetup("/badrequest", + func(ctx *Context, w http.ResponseWriter, r *http.Request) error { + return err + }, + ) + + router, logger := testRouterSetup() + logger.Reset() + router.ServeHTTP(w, r) + + if w.Code != http.StatusBadRequest { + t.Error("Wrong code:", w.Code) + } + if body := w.Body.String(); body != "400 Bad request" { + t.Error("Wrong body:", body) + } + + if str := logger.String(); !strings.Contains(str, err.Error()) { + t.Error(str) + } + + called := false + Cfg.BadRequestHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + called = true + }) + + logger.Reset() + router.ServeHTTP(w, r) + if !called { + t.Error("Should be called.") + } + + if str := logger.String(); !strings.Contains(str, err.Error()) { + t.Error(str) + } +} + +func TestRouter_Error(t *testing.T) { + err := errors.New("error") + w, r := testRouterCallbackSetup("/error", + func(ctx *Context, w http.ResponseWriter, r *http.Request) error { + return err + }, + ) + + router, logger := testRouterSetup() + logger.Reset() + router.ServeHTTP(w, r) + + if w.Code != http.StatusInternalServerError { + t.Error("Wrong code:", w.Code) + } + if body := w.Body.String(); body != "500 An error has occurred" { + t.Error("Wrong body:", body) + } + + if str := logger.String(); !strings.Contains(str, err.Error()) { + t.Error(str) + } + + called := false + Cfg.ErrorHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + called = true + }) + + logger.Reset() + router.ServeHTTP(w, r) + if !called { + t.Error("Should be called.") + } + + if str := logger.String(); !strings.Contains(str, err.Error()) { + t.Error(str) + } +} + +func TestRouter_Redirect(t *testing.T) { + err := ErrAndRedirect{ + Err: errors.New("error"), + Location: "/", + FlashSuccess: "yay", + FlashError: "nay", + } + + w, r := testRouterCallbackSetup("/error", + func(ctx *Context, w http.ResponseWriter, r *http.Request) error { + return err + }, + ) + + router, logger := testRouterSetup() + + session := mockClientStore{} + Cfg.SessionStoreMaker = func(w http.ResponseWriter, r *http.Request) ClientStorer { return session } + + logger.Reset() + router.ServeHTTP(w, r) + + if w.Code != http.StatusFound { + t.Error("Wrong code:", w.Code) + } + if loc := w.Header().Get("Location"); loc != err.Location { + t.Error("Wrong location:", loc) + } + if succ, ok := session.Get(FlashSuccessKey); !ok || succ != err.FlashSuccess { + t.Error(succ, ok) + } + if fail, ok := session.Get(FlashErrorKey); !ok || fail != err.FlashError { + t.Error(fail, ok) + } +}