Skip to content

Commit

Permalink
Merge pull request #15 from alexferl/custom_error
Browse files Browse the repository at this point in the history
add custom errors and tests
  • Loading branch information
alexferl authored Mar 11, 2024
2 parents 01c7847 + b8a2add commit 4ce19f3
Show file tree
Hide file tree
Showing 17 changed files with 539 additions and 152 deletions.
44 changes: 27 additions & 17 deletions handlers/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@ import (
jwx "github.com/lestrrat-go/jwx/v2/jwt"
"github.com/rs/zerolog/log"
"github.com/spf13/viper"
"go.mongodb.org/mongo-driver/mongo"

"github.com/alexferl/echo-boilerplate/config"
"github.com/alexferl/echo-boilerplate/data"
"github.com/alexferl/echo-boilerplate/models"
"github.com/alexferl/echo-boilerplate/services"
"github.com/alexferl/echo-boilerplate/util/cookie"
)

Expand Down Expand Up @@ -71,8 +70,11 @@ func (h *AuthHandler) login(c echo.Context) error {

user, err := h.svc.FindOneByEmailOrUsername(ctx, body.Email, body.Username)
if err != nil {
if errors.Is(err, data.ErrNoDocuments) {
return h.Validate(c, http.StatusUnauthorized, echo.Map{"message": "invalid email or password"})
var se *services.Error
if errors.As(err, &se) {
if se.Kind == services.NotExist {
return h.Validate(c, http.StatusUnauthorized, echo.Map{"message": "invalid email or password"})
}
}
log.Error().Err(err).Msg("failed finding user")
return err
Expand Down Expand Up @@ -122,8 +124,11 @@ func (h *AuthHandler) logout(c echo.Context) error {

user, err := h.svc.Read(ctx, token.Subject())
if err != nil {
if errors.Is(err, data.ErrNoDocuments) {
return h.Validate(c, http.StatusUnauthorized, echo.Map{"message": "token not found"})
var se *services.Error
if errors.As(err, &se) {
if se.Kind == services.NotExist {
return h.Validate(c, http.StatusUnauthorized, echo.Map{"message": "token not found"})
}
}
log.Error().Err(err).Msg("failed getting user")
return err
Expand Down Expand Up @@ -167,8 +172,11 @@ func (h *AuthHandler) refresh(c echo.Context) error {

user, err := h.svc.Read(ctx, token.Subject())
if err != nil {
if errors.Is(err, data.ErrNoDocuments) {
return h.Validate(c, http.StatusUnauthorized, echo.Map{"message": "token not found"})
var se *services.Error
if errors.As(err, &se) {
if se.Kind == services.NotExist {
return h.Validate(c, http.StatusUnauthorized, echo.Map{"message": "token not found"})
}
}
log.Error().Err(err).Msg("failed getting user")
return err
Expand Down Expand Up @@ -224,14 +232,13 @@ func (h *AuthHandler) signup(c echo.Context) error {

res, err := h.svc.FindOneByEmailOrUsername(ctx, body.Email, body.Username)
if err != nil {
if !errors.Is(err, data.ErrNoDocuments) {
log.Error().Err(err).Msg("failed getting user")
return err
var se *services.Error
if errors.As(err, &se) {
if se.Kind == services.Exist {
return h.Validate(c, http.StatusConflict, echo.Map{"message": se.Message})
}
}
}

if res != nil {
return h.Validate(c, http.StatusConflict, echo.Map{"message": "email or username already in-use"})
log.Error().Err(err).Msg("failed getting user")
}

user := models.NewUser(body.Email, body.Username)
Expand All @@ -247,8 +254,11 @@ func (h *AuthHandler) signup(c echo.Context) error {

res, err = h.svc.Create(ctx, user)
if err != nil {
if mongo.IsDuplicateKeyError(err) {
return h.Validate(c, http.StatusConflict, echo.Map{"message": "email or username already in-use"})
var se *services.Error
if errors.As(err, &se) {
if se.Kind == services.Exist {
return h.Validate(c, http.StatusConflict, echo.Map{"message": se.Message})
}
}
log.Error().Err(err).Msg("failed inserting new user")
return err
Expand Down
18 changes: 9 additions & 9 deletions handlers/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import (

app "github.com/alexferl/echo-boilerplate"
"github.com/alexferl/echo-boilerplate/config"
"github.com/alexferl/echo-boilerplate/data"
"github.com/alexferl/echo-boilerplate/handlers"
"github.com/alexferl/echo-boilerplate/models"
"github.com/alexferl/echo-boilerplate/services"
"github.com/alexferl/echo-boilerplate/util/cookie"
"github.com/alexferl/echo-boilerplate/util/jwt"
)
Expand Down Expand Up @@ -131,7 +131,9 @@ func (s *AuthHandlerTestSuite) TestAuthHandler_Login_401() {

s.svc.EXPECT().
FindOneByEmailOrUsername(mock.Anything, mock.Anything, mock.Anything).
Return(nil, data.ErrNoDocuments)
Return(nil, &services.Error{
Kind: services.NotExist,
})

s.server.ServeHTTP(resp, req)

Expand Down Expand Up @@ -568,20 +570,18 @@ func (s *AuthHandlerTestSuite) TestAuthHandler_Signup_409() {

s.svc.EXPECT().
FindOneByEmailOrUsername(mock.Anything, mock.Anything, mock.Anything).
Return(&models.User{
Model: &models.Model{Id: "1"},
Email: payload.Email,
Username: payload.Username,
Name: payload.Name,
}, nil)
Return(nil, &services.Error{
Kind: services.Exist,
Message: services.ErrUserExist.Error(),
})

s.server.ServeHTTP(resp, req)

var result echo.HTTPError
_ = json.Unmarshal(resp.Body.Bytes(), &result)

assert.Equal(s.T(), http.StatusConflict, resp.Code)
assert.Equal(s.T(), "email or username already in-use", result.Message)
assert.Equal(s.T(), services.ErrUserExist.Error(), result.Message)
}

func (s *AuthHandlerTestSuite) TestAuthHandler_Signup_422() {
Expand Down
11 changes: 7 additions & 4 deletions handlers/oauth2_google.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import (
"golang.org/x/oauth2/google"

"github.com/alexferl/echo-boilerplate/config"
"github.com/alexferl/echo-boilerplate/data"
"github.com/alexferl/echo-boilerplate/models"
"github.com/alexferl/echo-boilerplate/services"
"github.com/alexferl/echo-boilerplate/util/cookie"
"github.com/alexferl/echo-boilerplate/util/rand"
)
Expand Down Expand Up @@ -100,11 +100,14 @@ func (h *AuthHandler) oauth2GoogleCallback(c echo.Context) error {

res, err := h.svc.FindOneByEmailOrUsername(ctx, googleUser.Email, "")
if err != nil {
if !errors.Is(err, data.ErrNoDocuments) {
log.Error().Err(err).Msg("google: failed getting user")
return err
var se *services.Error
if errors.As(err, &se) {
if se.Kind != services.NotExist {
log.Error().Err(err).Msg("failed getting user")
}
}
}

var access, refresh []byte

if res == nil {
Expand Down
28 changes: 17 additions & 11 deletions handlers/personal_access_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,16 @@ import (
jwx "github.com/lestrrat-go/jwx/v2/jwt"
"github.com/rs/zerolog/log"

"github.com/alexferl/echo-boilerplate/data"
"github.com/alexferl/echo-boilerplate/models"
"github.com/alexferl/echo-boilerplate/services"
)

type PersonalAccessTokenService interface {
Create(ctx context.Context, model *models.PersonalAccessToken) (*models.PersonalAccessToken, error)
Read(ctx context.Context, id string) (*models.PersonalAccessToken, error)
Revoke(ctx context.Context, model *models.PersonalAccessToken) error

Find(ctx context.Context, userId string) (models.PersonalAccessTokens, error)
FindOne(ctx context.Context, userId string, name string) (*models.PersonalAccessToken, error)
Revoke(ctx context.Context, model *models.PersonalAccessToken) error
}

type PersonalAccessTokenHandler struct {
Expand Down Expand Up @@ -63,7 +62,8 @@ func (h *PersonalAccessTokenHandler) create(c echo.Context) error {

res, err := h.svc.FindOne(ctx, token.Subject(), body.Name)
if err != nil {
if !errors.Is(err, data.ErrNoDocuments) {
var se *services.Error
if !errors.As(err, &se) {
log.Error().Err(err).Msg("failed getting personal access token")
return err
}
Expand All @@ -77,7 +77,7 @@ func (h *PersonalAccessTokenHandler) create(c echo.Context) error {
if err != nil {
if errors.Is(err, models.ErrExpiresAtPast) {
m := echo.Map{
"message": "Validation error",
"message": "validation error",
"errors": []string{models.ErrExpiresAtPast.Error()},
}
return h.Validate(c, http.StatusUnprocessableEntity, m)
Expand Down Expand Up @@ -126,10 +126,13 @@ func (h *PersonalAccessTokenHandler) get(c echo.Context) error {

pat, err := h.svc.Read(ctx, id)
if err != nil {
if errors.Is(err, data.ErrNoDocuments) {
return h.Validate(c, http.StatusNotFound, echo.Map{"message": "personal access token not found"})
var se *services.Error
if errors.As(err, &se) {
if se.Kind == services.NotExist {
return h.Validate(c, http.StatusNotFound, echo.Map{"message": se.Message})
}
}
log.Error().Err(err).Msg("failed getting user")
log.Error().Err(err).Msg("failed getting personal access token")
return err
}

Expand All @@ -144,10 +147,13 @@ func (h *PersonalAccessTokenHandler) revoke(c echo.Context) error {

pat, err := h.svc.Read(ctx, id)
if err != nil {
if errors.Is(err, data.ErrNoDocuments) {
return h.Validate(c, http.StatusNotFound, echo.Map{"message": "personal access token not found"})
var se *services.Error
if errors.As(err, &se) {
if se.Kind == services.NotExist {
return h.Validate(c, http.StatusNotFound, echo.Map{"message": se.Message})
}
}
log.Error().Err(err).Msg("failed getting user")
log.Error().Err(err).Msg("failed getting personal access token")
return err
}

Expand Down
21 changes: 18 additions & 3 deletions handlers/personal_access_token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@ import (

"github.com/alexferl/echo-openapi"
"github.com/alexferl/golib/http/api/server"
"github.com/labstack/echo/v4"
jwx "github.com/lestrrat-go/jwx/v2/jwt"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"

app "github.com/alexferl/echo-boilerplate"
"github.com/alexferl/echo-boilerplate/data"
"github.com/alexferl/echo-boilerplate/handlers"
"github.com/alexferl/echo-boilerplate/models"
"github.com/alexferl/echo-boilerplate/services"
"github.com/alexferl/echo-boilerplate/util/jwt"
)

Expand Down Expand Up @@ -242,11 +243,18 @@ func (s *PersonalAccessTokenHandlerTestSuite) TestPersonalAccessTokenHandler_Get

s.svc.EXPECT().
Read(mock.Anything, mock.Anything).
Return(nil, data.ErrNoDocuments)
Return(nil, &services.Error{
Kind: services.NotExist,
Message: services.ErrPersonalAccessTokenNotFound.Error(),
})

s.server.ServeHTTP(resp, req)

var result echo.HTTPError
_ = json.Unmarshal(resp.Body.Bytes(), &result)

assert.Equal(s.T(), http.StatusNotFound, resp.Code)
assert.Equal(s.T(), services.ErrPersonalAccessTokenNotFound.Error(), result.Message)
}

func (s *PersonalAccessTokenHandlerTestSuite) TestPersonalAccessTokenHandler_Revoke_204() {
Expand Down Expand Up @@ -294,11 +302,18 @@ func (s *PersonalAccessTokenHandlerTestSuite) TestPersonalAccessTokenHandler_Rev

s.svc.EXPECT().
Read(mock.Anything, mock.Anything).
Return(nil, data.ErrNoDocuments)
Return(nil, &services.Error{
Kind: services.NotExist,
Message: services.ErrPersonalAccessTokenNotFound.Error(),
})

s.server.ServeHTTP(resp, req)

var result echo.HTTPError
_ = json.Unmarshal(resp.Body.Bytes(), &result)

assert.Equal(s.T(), http.StatusNotFound, resp.Code)
assert.Equal(s.T(), services.ErrPersonalAccessTokenNotFound.Error(), result.Message)
}

func (s *PersonalAccessTokenHandlerTestSuite) TestPersonalAccessTokenHandler_Revoke_409() {
Expand Down
43 changes: 23 additions & 20 deletions handlers/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/alexferl/echo-boilerplate/data"
"github.com/alexferl/echo-boilerplate/models"
"github.com/alexferl/echo-boilerplate/services"
"github.com/alexferl/echo-boilerplate/util/jwt"
"github.com/alexferl/echo-boilerplate/util/pagination"
)
Expand All @@ -23,7 +24,6 @@ type TaskService interface {
Read(ctx context.Context, id string) (*models.Task, error)
Update(ctx context.Context, id string, data *models.Task) (*models.Task, error)
Delete(ctx context.Context, id string, data *models.Task) error

Find(ctx context.Context, params *models.TaskSearchParams) (int64, models.Tasks, error)
}

Expand Down Expand Up @@ -109,17 +109,18 @@ func (h *TaskHandler) get(c echo.Context) error {

task, err := h.svc.Read(ctx, id)
if err != nil {
if errors.Is(err, data.ErrNoDocuments) {
return h.Validate(c, http.StatusNotFound, echo.Map{"message": "task not found"})
var se *services.Error
if errors.As(err, &se) {
if se.Kind == services.NotExist {
return h.Validate(c, http.StatusNotFound, echo.Map{"message": se.Message})
} else if se.Kind == services.Deleted {
return h.Validate(c, http.StatusGone, echo.Map{"message": se.Message})
}
}
log.Error().Err(err).Msg("failed getting user")
log.Error().Err(err).Msg("failed getting task")
return err
}

if task.DeletedAt != nil {
return h.Validate(c, http.StatusGone, echo.Map{"message": "task was deleted"})
}

return h.Validate(c, http.StatusOK, task.Response())
}

Expand All @@ -142,17 +143,18 @@ func (h *TaskHandler) update(c echo.Context) error {

task, err := h.svc.Read(ctx, id)
if err != nil {
if errors.Is(err, data.ErrNoDocuments) {
return h.Validate(c, http.StatusNotFound, echo.Map{"message": "task not found"})
var se *services.Error
if errors.As(err, &se) {
if se.Kind == services.NotExist {
return h.Validate(c, http.StatusNotFound, echo.Map{"message": se.Message})
} else if se.Kind == services.Deleted {
return h.Validate(c, http.StatusGone, echo.Map{"message": se.Message})
}
}
log.Error().Err(err).Msg("failed getting task")
return err
}

if task.DeletedAt != nil {
return h.Validate(c, http.StatusGone, echo.Map{"message": "task was deleted"})
}

if token.Subject() != task.CreatedBy.(*models.User).Id && !jwt.HasRoles(token, models.AdminRole.String(), models.SuperRole.String()) {
return h.Validate(c, http.StatusForbidden, echo.Map{"message": "you don't have access"})
}
Expand Down Expand Up @@ -189,17 +191,18 @@ func (h *TaskHandler) transition(c echo.Context) error {

task, err := h.svc.Read(ctx, id)
if err != nil {
if errors.Is(err, data.ErrNoDocuments) {
return h.Validate(c, http.StatusNotFound, echo.Map{"message": "task not found"})
var se *services.Error
if errors.As(err, &se) {
if se.Kind == services.NotExist {
return h.Validate(c, http.StatusNotFound, echo.Map{"message": se.Message})
} else if se.Kind == services.Deleted {
return h.Validate(c, http.StatusGone, echo.Map{"message": se.Message})
}
}
log.Error().Err(err).Msg("failed getting task")
return err
}

if task.DeletedAt != nil {
return h.Validate(c, http.StatusGone, echo.Map{"message": "task was deleted"})
}

if *body.Completed != task.Completed {
if *body.Completed {
task.Complete(token.Subject())
Expand Down
Loading

0 comments on commit 4ce19f3

Please sign in to comment.