Skip to content

Commit

Permalink
Issue 147 - Padronizar testes usando Mockery (#149)
Browse files Browse the repository at this point in the history
* Padronizar testes usando Mockery (#147)

*refactor tests: breed and pet.

* Issue 54 - Fazer rota de listagem de pets ter autenticacao e listagem diferenciada (#142)

* feat: add validations to dto

feat: implement function find User by email

feat: add missing fields to NewPet and database fields on entity

feat: fix Save function and add List functions

feat: fix call of Save from repository and add List methods

feat: add ListAllPets function

feat: add List functions to interface

feat: routes to list pets

feat: change error message

feat: add offset, fix query and remove adoptionDate

feat: remove unused adoptionDate

feat: remove page from parameter

feat: add validation rule to check if user is authenticated and return list of pets based on result

feat: update autogenerated mocks

feat: add autogenerated repo mocks

feat: add test for ListAllUnauthenticated

feat: add more test cases for ListAllUnauthenticated

feat: remove print statements

feat: fix Save Pet Usecase test and add two authenticated list pets

feat: add test for List pets authenticated with repo error

feat: implement FindByEmail from usecase instead of repo

feat: rename encoder variable

feat: remove parenthesis

feat: remove method for unauthenticated listing

feat: remove ID from list of pets after receiving from repo

feat: call private methods based on user auth and move validation to usecase

feat: validate Authorization token and pass to usecase

* feat: fix usage of boolean to verify authenticated user

* feat: add pet listing to public routes

* feat: update mockery to fix errors

* fix tests

* feat: remove redundant validation

* feat: Reset User Password (#151)

* feat: Reset User Password

* feat: Changing Password functions

* chore: mockery

* fix: Controller logger warnings

* fix: Fixing Password verification

* fix: Message Warning

* fix: Password format validation

* feat: Utils tests

* chore: user test WIP

* feat: Change Password Tests

* fix: CMD Errors

* fix: Renaming hasher and repo to have different names of the pkg

* fix: Initializing empty var with var instead of :=

---------

Co-authored-by: Luan Alves <[email protected]>
Co-authored-by: Patrick Nery Nogueira <[email protected]>
  • Loading branch information
3 people authored Jun 24, 2024
1 parent 7ba70d0 commit ac98426
Show file tree
Hide file tree
Showing 26 changed files with 1,023 additions and 112 deletions.
17 changes: 9 additions & 8 deletions .mockery.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
with-expecter: true
packages:
pet-dex-backend/v2/interfaces:
all: true
interfaces:
Hasher:
PetRepository:
UserRepository:
OngRepository:
AdressRepo:
pet-dex-backend/v2/interfaces:
all: true
interfaces:
Hasher:
PetRepository:
UserRepository:
OngRepository:
BreedRepository:
AdressRepo:
59 changes: 56 additions & 3 deletions api/controllers/pet.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@ import (
"fmt"
"net/http"
"pet-dex-backend/v2/api/errors"
"pet-dex-backend/v2/entity"
"pet-dex-backend/v2/entity/dto"
"pet-dex-backend/v2/infra/config"
"pet-dex-backend/v2/usecase"
"strconv"
"strings"
"time"

"pet-dex-backend/v2/pkg/encoder"
"pet-dex-backend/v2/pkg/uniqueEntityId"

"github.com/go-chi/chi/v5"
Expand Down Expand Up @@ -50,7 +56,7 @@ func (pc *PetController) Update(w http.ResponseWriter, r *http.Request) {
err := errors.ErrInvalidID{
Description: err.Error(),
}

w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(err)
return
Expand Down Expand Up @@ -120,15 +126,14 @@ func (cntrl *PetController) CreatePet(w http.ResponseWriter, r *http.Request) {
}

err = petToSave.Validate()
if err != nil{
if err != nil {
fmt.Printf("Invalid request: could not validate pet data from request body %s", err.Error())

w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(err)
return
}


err = cntrl.Usecase.Save(petToSave)

if err != nil {
Expand All @@ -143,3 +148,51 @@ func (cntrl *PetController) CreatePet(w http.ResponseWriter, r *http.Request) {

w.WriteHeader(http.StatusCreated)
}

func (cntrl *PetController) ListAllPets(w http.ResponseWriter, r *http.Request) {
encoderAdapter := encoder.NewEncoderAdapter(config.GetEnvConfig().JWT_SECRET)
var pageNumber int
var err error
var pets []*entity.Pet
pageStr := r.URL.Query()

if pageStr.Get("page") == "" {
pageNumber = 1
} else {
pageNumber, err = strconv.Atoi(pageStr.Get("page"))
}

if err != nil {
http.Error(w, "Bad Request: Invalid page number", http.StatusBadRequest)
return
}

if pageNumber < 0 {
http.Error(w, "Bad Request: Page number cannot be negative", http.StatusBadRequest)
return
}

authHeader := r.Header.Get("Authorization")
isUnauthorized := true

headerSplited := strings.Split(authHeader, " ")
if len(headerSplited) == 2 {
bearerToken := headerSplited[1]

userclaims := encoderAdapter.ParseAccessToken(bearerToken)
isUnauthorized = userclaims.ExpiresAt != 0 && userclaims.ExpiresAt < time.Now().Unix()
}

pets, err = cntrl.Usecase.ListPetsByPage(pageNumber, isUnauthorized)

if err != nil {
http.Error(w, "Failed to retrieve pets", http.StatusInternalServerError)
return
}

if err = json.NewEncoder(w).Encode(&pets); err != nil {
http.Error(w, "Failed to encode pets", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
41 changes: 37 additions & 4 deletions api/controllers/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,31 +137,64 @@ func (uc *UserController) Delete(w http.ResponseWriter, r *http.Request) {
userIDFromTokenStr := r.Header.Get("UserId")
userIDFromToken, err := uniqueEntityId.ParseID(userIDFromTokenStr)
if err != nil {
logger.Error("[#UserController.Delete] Erro ao tentar receber o ID do token -> Erro: %v", err)
logger.Error("[#UserController.Delete] Erro ao tentar receber o ID do token -> Erro: ", err)
http.Error(w, "Erro ao converter a requisição ", http.StatusBadRequest)
return
}

IDStr := chi.URLParam(r, "id")
ID, err := uniqueEntityId.ParseID(IDStr)
if err != nil {
logger.Error("[#UserController.Delete] Erro ao tentar converter o body da requisição -> Erro: %v", err)
logger.Error("[#UserController.Delete] Erro ao tentar converter o body da requisição -> Erro: ", err)
http.Error(w, "Erro ao converter a requisição ", http.StatusBadRequest)
return
}

if userIDFromToken != ID {
logger.Error("[#UserController.Delete] Erro ao tentar excluir outro usuário -> Erro: %v", err)
logger.Error("[#UserController.Delete] Erro ao tentar excluir outro usuário -> Erro: ", err)
http.Error(w, "Usuário não autorizado a excluir este usuário", http.StatusUnauthorized)
return
}

err = uc.usecase.Delete(ID)
if err != nil {
logger.Error("[#UserController.Delete] Erro ao tentar deletar o usuário -> Erro: %v", err)
logger.Error("[#UserController.Delete] Erro ao tentar deletar o usuário -> Erro: ", err)
http.Error(w, "Erro ao tentar atualizar o usuário ", http.StatusBadRequest)
return
}

w.WriteHeader(http.StatusNoContent)
}

func (uc *UserController) ChangePassword(w http.ResponseWriter, r *http.Request) {
parsedId, err := uniqueEntityId.ParseID(r.Header.Get("UserId"))
if err != nil {
uc.logger.Error("error parsing user id: ", err)
http.Error(w, "Bad Request: Invalid ID", http.StatusBadRequest)
return
}

var userChangePasswordDto dto.UserChangePasswordDto
err = json.NewDecoder(r.Body).Decode(&userChangePasswordDto)
if err != nil {
uc.logger.Error("error decoding request: ", err)
http.Error(w, "Error decoding request ", http.StatusBadRequest)
return
}

err = userChangePasswordDto.Validate()
if err != nil {
uc.logger.Error(err.Error())
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

err = uc.usecase.ChangePassword(userChangePasswordDto, parsedId)
if err != nil {
uc.logger.Error("error changing password: ", err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

w.WriteHeader(http.StatusOK)
}
3 changes: 3 additions & 0 deletions api/routes/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,18 @@ func InitRoutes(controllers Controllers, c *chi.Mux) {

private.Route("/user", func(r chi.Router) {
r.Get("/{id}/my-pets", controllers.PetController.ListUserPets)
r.Post("/change-password", controllers.UserController.ChangePassword)
r.Patch("/{id}", controllers.UserController.Update)
r.Get("/{id}", controllers.UserController.FindByID)
r.Delete("/{id}", controllers.UserController.Delete)

})
})

r.Group(func(public chi.Router) {
public.Post("/user", controllers.UserController.Insert)
public.Post("/user/token", controllers.UserController.GenerateToken)
public.Get("/pets/", controllers.PetController.ListAllPets)
})
})
}
3 changes: 1 addition & 2 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
func main() {
var number string
fmt.Println("Migrations CLI")
fmt.Println("Type the number of the command desired:\n1-Migrations UP\n2-Migrations DOWN\n3-Create a new migration\n")
fmt.Println("Type the number of the command desired:\n1-Migrations UP\n2-Migrations DOWN\n3-Create a new migration")
_, err := fmt.Scan(&number)
if err != nil {
fmt.Println("Error while reading the values", err)
Expand Down Expand Up @@ -40,5 +40,4 @@ func main() {
fmt.Println("Migration created!")
}

return
}
29 changes: 29 additions & 0 deletions entity/dto/user_change_password.dto.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package dto

import (
"errors"
"pet-dex-backend/v2/pkg/utils"
)

type UserChangePasswordDto struct {
OldPassword string `json:"oldPassword"`
NewPassword string `json:"newPassword"`
NewPasswordAgain string `json:"newPasswordAgain"`
}

func (u *UserChangePasswordDto) Validate() error {
if u.NewPassword == "" {
return errors.New("password cannot be empty")
}
if u.OldPassword == u.NewPassword {
return errors.New("old password cannot be the same as new password")
}
if u.NewPassword != u.NewPasswordAgain {
return errors.New("new passwords do not match")
}

if !utils.IsValidPassword(u.NewPassword) {
return errors.New("new password must be at least 6 characters long and contain at least one uppercase letter and one special character")
}
return nil
}
63 changes: 63 additions & 0 deletions entity/dto/user_change_password.dto_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package dto

import (
"errors"
"testing"

"github.com/stretchr/testify/assert"
)

func TestChangePasswordValidate(t *testing.T) {
cases := map[string]struct {
userChangePassword UserChangePasswordDto
expected error
}{
"Valid Password": {
userChangePassword: UserChangePasswordDto{
OldPassword: "oldPassword123!",
NewPassword: "NewPassword123!",
NewPasswordAgain: "NewPassword123!",
},
expected: nil,
},
"Empty New Password": {
userChangePassword: UserChangePasswordDto{
OldPassword: "oldPassword123!",
NewPassword: "",
NewPasswordAgain: "NewPassword123!",
},
expected: errors.New("password cannot be empty"),
},
"New Password is equal to Old Password": {
userChangePassword: UserChangePasswordDto{
OldPassword: "oldPassword123!",
NewPassword: "oldPassword123!",
NewPasswordAgain: "NewPassword123!",
},
expected: errors.New("old password cannot be the same as new password"),
},
"New Passwords does not match": {
userChangePassword: UserChangePasswordDto{
OldPassword: "oldPassword123!",
NewPassword: "NewPassword123!",
NewPasswordAgain: "DifferentNewPassword123!",
},
expected: errors.New("new passwords do not match"),
},
"New Password does not match the security requirements": {
userChangePassword: UserChangePasswordDto{
OldPassword: "oldPassword123!",
NewPassword: "password123!",
NewPasswordAgain: "password123!",
},
expected: errors.New("new password must be at least 6 characters long and contain at least one uppercase letter and one special character"),
},
}

for name, test := range cases {
t.Run(name, func(t *testing.T) {
result := test.userChangePassword.Validate()
assert.Equal(t, test.expected, result, test.expected)
})
}
}
15 changes: 12 additions & 3 deletions entity/dto/user_insert.dto.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ package dto
import (
"fmt"
"net/mail"
"regexp"
"pet-dex-backend/v2/pkg/utils"
"slices"
"time"
)

var regex = regexp.MustCompile(`^[A-Za-z\d\W]{6,}$`)
var userTypes = []string{"juridica", "fisica"}

type UserInsertDto struct {
Name string `json:"name"`
Expand All @@ -33,7 +34,15 @@ func (u *UserInsertDto) Validate() error {
return fmt.Errorf("invalid email")
}

if !regex.MatchString(u.Pass) {
if !slices.Contains(userTypes, u.Type) {
return fmt.Errorf("type can only be 'juridica' or 'fisica'")
}

if u.Pass == "" {
return fmt.Errorf("password cannot be empty")
}

if !utils.IsValidPassword(u.Pass) {
return fmt.Errorf("invalid password format")
}
return nil
Expand Down
8 changes: 5 additions & 3 deletions entity/pet.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import (

type Pet struct {
ID uniqueEntityId.ID `json:"id"`
UserID uniqueEntityId.ID `json:"user_id"`
BreedID uniqueEntityId.ID `json:"breed_id"`
UserID uniqueEntityId.ID `json:"user_id" db:"userId"`
BreedID uniqueEntityId.ID `json:"breed_id" db:"breedId"`
Name string `json:"name"`
Size string `json:"size"`
Weight float64 `json:"weight"`
WeightMeasure string `json:"weight_measure"`
AdoptionDate time.Time `json:"adoption_date"`
AdoptionDate time.Time `json:"adoption_date" db:"adoptionDate"`
Birthdate time.Time `json:"birthdate"`
Comorbidity string `json:"comorbidity"`
Tags string `json:"tags"`
Expand Down Expand Up @@ -52,6 +52,8 @@ func NewPet(userId, breedId uniqueEntityId.ID, size, name string, weight float64
return &Pet{
ID: petId,
UserID: userId,
BreedID: breedId,
Size: size,
Name: name,
Weight: weight,
AdoptionDate: *adoptionDate,
Expand Down
Loading

0 comments on commit ac98426

Please sign in to comment.