From 402c995a4e8c9d8e93ea6752e5ce48e050dd0cab Mon Sep 17 00:00:00 2001 From: garrettladley Date: Fri, 2 Feb 2024 15:56:51 -0500 Subject: [PATCH] nested route refactor --- backend/src/controllers/category_tag.go | 24 ++ backend/src/controllers/tag.go | 8 +- backend/src/controllers/user.go | 22 -- backend/src/controllers/user_tag.go | 38 +++ backend/src/models/tag.go | 3 +- backend/src/server/server.go | 32 ++- backend/src/services/category_tag.go | 39 +++ backend/src/services/tag.go | 48 +--- backend/src/services/user.go | 32 --- backend/src/services/user_tag.go | 54 ++++ backend/src/transactions/category_tag.go | 25 ++ backend/src/transactions/tag.go | 8 +- backend/src/transactions/user.go | 27 -- backend/src/transactions/user_tag.go | 35 +++ backend/tests/api/category_tag_test.go | 1 + backend/tests/api/tag_test.go | 225 +++++----------- backend/tests/api/user_tag_test.go | 322 +++++++++++++++++++++++ backend/tests/api/user_test.go | 306 --------------------- 18 files changed, 640 insertions(+), 609 deletions(-) create mode 100644 backend/src/controllers/category_tag.go create mode 100644 backend/src/controllers/user_tag.go create mode 100644 backend/src/services/category_tag.go create mode 100644 backend/src/services/user_tag.go create mode 100644 backend/src/transactions/category_tag.go create mode 100644 backend/src/transactions/user_tag.go create mode 100644 backend/tests/api/category_tag_test.go create mode 100644 backend/tests/api/user_tag_test.go diff --git a/backend/src/controllers/category_tag.go b/backend/src/controllers/category_tag.go new file mode 100644 index 000000000..2cb0d7625 --- /dev/null +++ b/backend/src/controllers/category_tag.go @@ -0,0 +1,24 @@ +package controllers + +import ( + "github.com/GenerateNU/sac/backend/src/services" + + "github.com/gofiber/fiber/v2" +) + +type CategoryTagController struct { + categoryTagService services.CategoryTagServiceInterface +} + +func NewCategoryTagController(categoryTagService services.CategoryTagServiceInterface) *CategoryTagController { + return &CategoryTagController{categoryTagService: categoryTagService} +} + +func (t *CategoryTagController) GetTagByCategory(c *fiber.Ctx) error { + tag, err := t.categoryTagService.GetTagByCategory(c.Params("categoryID"), c.Params("tagID")) + if err != nil { + return err.FiberError(c) + } + + return c.Status(fiber.StatusOK).JSON(&tag) +} diff --git a/backend/src/controllers/tag.go b/backend/src/controllers/tag.go index 78ca83d77..9dccea98c 100644 --- a/backend/src/controllers/tag.go +++ b/backend/src/controllers/tag.go @@ -36,7 +36,7 @@ func (t *TagController) CreateTag(c *fiber.Ctx) error { return errors.FailedToParseRequestBody.FiberError(c) } - dbTag, err := t.tagService.CreateTag(c.Params("categoryID"), tagBody) + dbTag, err := t.tagService.CreateTag(tagBody) if err != nil { return err.FiberError(c) } @@ -58,7 +58,7 @@ func (t *TagController) CreateTag(c *fiber.Ctx) error { // @Failure 500 {string} string "failed to retrieve tag" // @Router /api/v1/tags/{id} [get] func (t *TagController) GetTag(c *fiber.Ctx) error { - tag, err := t.tagService.GetTag(c.Params("categoryID"), c.Params("tagID")) + tag, err := t.tagService.GetTag(c.Params("tagID")) if err != nil { return err.FiberError(c) } @@ -89,7 +89,7 @@ func (t *TagController) UpdateTag(c *fiber.Ctx) error { return errors.FailedToParseRequestBody.FiberError(c) } - tag, err := t.tagService.UpdateTag(c.Params("categoryID"), c.Params("tagID"), tagBody) + tag, err := t.tagService.UpdateTag(c.Params("tagID"), tagBody) if err != nil { return err.FiberError(c) } @@ -110,7 +110,7 @@ func (t *TagController) UpdateTag(c *fiber.Ctx) error { // @Failure 500 {string} string "failed to delete tag" // @Router /api/v1/tags/{id} [delete] func (t *TagController) DeleteTag(c *fiber.Ctx) error { - err := t.tagService.DeleteTag(c.Params("categoryID"), c.Params("tagID")) + err := t.tagService.DeleteTag(c.Params("tagID")) if err != nil { return err.FiberError(c) } diff --git a/backend/src/controllers/user.go b/backend/src/controllers/user.go index 5b4bcfe94..1b509bf3d 100644 --- a/backend/src/controllers/user.go +++ b/backend/src/controllers/user.go @@ -137,25 +137,3 @@ func (u *UserController) DeleteUser(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusNoContent) } - -func (u *UserController) GetUserTags(c *fiber.Ctx) error { - tags, err := u.userService.GetUserTags(c.Params("uid")) - if err != nil { - return err.FiberError(c) - } - return c.Status(fiber.StatusOK).JSON(&tags) -} - -func (u *UserController) CreateUserTags(c *fiber.Ctx) error { - var requestBody models.CreateUserTagsBody - if err := c.BodyParser(&requestBody); err != nil { - return errors.FailedToParseRequestBody.FiberError(c) - } - - tags, err := u.userService.CreateUserTags(c.Params("uid"), requestBody) - if err != nil { - return err.FiberError(c) - } - - return c.Status(fiber.StatusCreated).JSON(&tags) -} diff --git a/backend/src/controllers/user_tag.go b/backend/src/controllers/user_tag.go new file mode 100644 index 000000000..eb1f7bd0b --- /dev/null +++ b/backend/src/controllers/user_tag.go @@ -0,0 +1,38 @@ +package controllers + +import ( + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/models" + "github.com/GenerateNU/sac/backend/src/services" + "github.com/gofiber/fiber/v2" +) + +type UserTagController struct { + userTagService services.UserTagServiceInterface +} + +func NewUserTagController(userTagService services.UserTagServiceInterface) *UserTagController { + return &UserTagController{userTagService: userTagService} +} + +func (u *UserTagController) GetUserTags(c *fiber.Ctx) error { + tags, err := u.userTagService.GetUserTags(c.Params("userID")) + if err != nil { + return err.FiberError(c) + } + return c.Status(fiber.StatusOK).JSON(&tags) +} + +func (u *UserTagController) CreateUserTags(c *fiber.Ctx) error { + var requestBody models.CreateUserTagsBody + if err := c.BodyParser(&requestBody); err != nil { + return errors.FailedToParseRequestBody.FiberError(c) + } + + tags, err := u.userTagService.CreateUserTags(c.Params("userID"), requestBody) + if err != nil { + return err.FiberError(c) + } + + return c.Status(fiber.StatusCreated).JSON(&tags) +} diff --git a/backend/src/models/tag.go b/backend/src/models/tag.go index 15aac5b04..36c71381a 100644 --- a/backend/src/models/tag.go +++ b/backend/src/models/tag.go @@ -15,5 +15,6 @@ type Tag struct { } type TagRequestBody struct { - Name string `json:"name" validate:"required,max=255"` + Name string `json:"name" validate:"required,max=255"` + CategoryID uuid.UUID `json:"category_id" validate:"required,uuid4"` } diff --git a/backend/src/server/server.go b/backend/src/server/server.go index dc5a956a1..8cc5ef224 100644 --- a/backend/src/server/server.go +++ b/backend/src/server/server.go @@ -36,10 +36,12 @@ func Init(db *gorm.DB, settings config.Settings) *fiber.App { utilityRoutes(app) authRoutes(apiv1, services.NewAuthService(db, validate), settings.Auth) - userRoutes(apiv1, services.NewUserService(db, validate), middlewareService) + userRouter := userRoutes(apiv1, services.NewUserService(db, validate), middlewareService) + userTagRouter(userRouter, services.NewUserTagService(db, validate)) clubRoutes(apiv1, services.NewClubService(db, validate), middlewareService) categoryRouter := categoryRoutes(apiv1, services.NewCategoryService(db, validate)) - tagRoutes(categoryRouter, services.NewTagService(db, validate)) + tagRoutes(apiv1, services.NewTagService(db, validate)) + categoryTagRoutes(categoryRouter, services.NewCategoryTagService(db, validate)) return app } @@ -69,7 +71,7 @@ func utilityRoutes(router fiber.Router) { }) } -func userRoutes(router fiber.Router, userService services.UserServiceInterface, middlewareService middleware.MiddlewareInterface) { +func userRoutes(router fiber.Router, userService services.UserServiceInterface, middlewareService middleware.MiddlewareInterface) fiber.Router { userController := controllers.NewUserController(userService) // api/v1/users/* @@ -85,17 +87,21 @@ func userRoutes(router fiber.Router, userService services.UserServiceInterface, usersID.Patch("/", middlewareService.Authorize(types.UserWrite), userController.UpdateUser) usersID.Delete("/", middlewareService.Authorize(types.UserDelete), userController.DeleteUser) - usersID.Post("/tags", userController.CreateUserTags) - usersID.Get("/tags", userController.GetUserTags) users.Get("/", userController.GetUsers) users.Get("/:id", userController.GetUser) users.Patch("/:id", userController.UpdateUser) users.Delete("/:id", userController.DeleteUser) - userTags := users.Group("/:uid/tags") + return users +} + +func userTagRouter(router fiber.Router, userTagService services.UserTagServiceInterface) { + userTagController := controllers.NewUserTagController(userTagService) - userTags.Post("/", userController.CreateUserTags) - userTags.Get("/", userController.GetUserTags) + userTags := router.Group("/:userID/tags") + + userTags.Post("/", userTagController.CreateUserTags) + userTags.Get("/", userTagController.GetUserTags) } func clubRoutes(router fiber.Router, clubService services.ClubServiceInterface, middlewareService middleware.MiddlewareInterface) { @@ -143,10 +149,18 @@ func categoryRoutes(router fiber.Router, categoryService services.CategoryServic func tagRoutes(router fiber.Router, tagService services.TagServiceInterface) { tagController := controllers.NewTagController(tagService) - tags := router.Group("/:categoryID/tags") + tags := router.Group("/tags") tags.Get("/:tagID", tagController.GetTag) tags.Post("/", tagController.CreateTag) tags.Patch("/:tagID", tagController.UpdateTag) tags.Delete("/:tagID", tagController.DeleteTag) } + +func categoryTagRoutes(router fiber.Router, categoryTagService services.CategoryTagServiceInterface) { + categoryTagController := controllers.NewCategoryTagController(categoryTagService) + + categoryTags := router.Group("/:categoryID/tags") + + categoryTags.Get("/:tagID", categoryTagController.GetTagByCategory) +} diff --git a/backend/src/services/category_tag.go b/backend/src/services/category_tag.go new file mode 100644 index 000000000..abafb622d --- /dev/null +++ b/backend/src/services/category_tag.go @@ -0,0 +1,39 @@ +package services + +import ( + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/models" + "github.com/GenerateNU/sac/backend/src/transactions" + "github.com/GenerateNU/sac/backend/src/utilities" + "github.com/go-playground/validator/v10" + "gorm.io/gorm" +) + +type CategoryTagServiceInterface interface { + GetTagByCategory(categoryID string, tagID string) (*models.Tag, *errors.Error) +} + +type CategoryTagService struct { + DB *gorm.DB + Validate *validator.Validate +} + +func NewCategoryTagService(db *gorm.DB, validate *validator.Validate) *CategoryTagService { + return &CategoryTagService{DB: db, Validate: validate} +} + +func (t *CategoryTagService) GetTagByCategory(categoryID string, tagID string) (*models.Tag, *errors.Error) { + categoryIDAsUUID, idErr := utilities.ValidateID(categoryID) + + if idErr != nil { + return nil, idErr + } + + tagIDAsUUID, idErr := utilities.ValidateID(tagID) + + if idErr != nil { + return nil, idErr + } + + return transactions.GetTagByCategory(t.DB, *categoryIDAsUUID, *tagIDAsUUID) +} diff --git a/backend/src/services/tag.go b/backend/src/services/tag.go index 8fda17d26..a7f7523d5 100644 --- a/backend/src/services/tag.go +++ b/backend/src/services/tag.go @@ -10,10 +10,10 @@ import ( ) type TagServiceInterface interface { - CreateTag(categoryID string, tagBody models.TagRequestBody) (*models.Tag, *errors.Error) - GetTag(categoryID string, tagID string) (*models.Tag, *errors.Error) - UpdateTag(categoryID string, tagID string, tagBody models.TagRequestBody) (*models.Tag, *errors.Error) - DeleteTag(categoryID string, tagID string) *errors.Error + CreateTag(tagBody models.TagRequestBody) (*models.Tag, *errors.Error) + GetTag(tagID string) (*models.Tag, *errors.Error) + UpdateTag(tagID string, tagBody models.TagRequestBody) (*models.Tag, *errors.Error) + DeleteTag(tagID string) *errors.Error } type TagService struct { @@ -25,13 +25,7 @@ func NewTagService(db *gorm.DB, validate *validator.Validate) *TagService { return &TagService{DB: db, Validate: validate} } -func (t *TagService) CreateTag(categoryID string, tagBody models.TagRequestBody) (*models.Tag, *errors.Error) { - categoryIDAsUUID, idErr := utilities.ValidateID(categoryID) - - if idErr != nil { - return nil, idErr - } - +func (t *TagService) CreateTag(tagBody models.TagRequestBody) (*models.Tag, *errors.Error) { if err := t.Validate.Struct(tagBody); err != nil { return nil, &errors.FailedToValidateTag } @@ -41,34 +35,20 @@ func (t *TagService) CreateTag(categoryID string, tagBody models.TagRequestBody) return nil, &errors.FailedToMapRequestToModel } - tag.CategoryID = *categoryIDAsUUID - return transactions.CreateTag(t.DB, *tag) } -func (t *TagService) GetTag(categoryID string, tagID string) (*models.Tag, *errors.Error) { - categoryIDAsUUID, idErr := utilities.ValidateID(categoryID) - - if idErr != nil { - return nil, idErr - } - +func (t *TagService) GetTag(tagID string) (*models.Tag, *errors.Error) { tagIDAsUUID, idErr := utilities.ValidateID(tagID) if idErr != nil { return nil, idErr } - return transactions.GetTag(t.DB, *categoryIDAsUUID, *tagIDAsUUID) + return transactions.GetTag(t.DB, *tagIDAsUUID) } -func (t *TagService) UpdateTag(categoryID string, tagID string, tagBody models.TagRequestBody) (*models.Tag, *errors.Error) { - categoryIDAsUUID, idErr := utilities.ValidateID(categoryID) - - if idErr != nil { - return nil, idErr - } - +func (t *TagService) UpdateTag(tagID string, tagBody models.TagRequestBody) (*models.Tag, *errors.Error) { tagIDAsUUID, idErr := utilities.ValidateID(tagID) if idErr != nil { @@ -84,23 +64,15 @@ func (t *TagService) UpdateTag(categoryID string, tagID string, tagBody models.T return nil, &errors.FailedToMapRequestToModel } - tag.CategoryID = *categoryIDAsUUID - return transactions.UpdateTag(t.DB, *tagIDAsUUID, *tag) } -func (t *TagService) DeleteTag(categoryID string, tagID string) *errors.Error { - categoryIDAsUUID, idErr := utilities.ValidateID(categoryID) - - if idErr != nil { - return idErr - } - +func (t *TagService) DeleteTag(tagID string) *errors.Error { tagIDAsUUID, idErr := utilities.ValidateID(tagID) if idErr != nil { return idErr } - return transactions.DeleteTag(t.DB, *categoryIDAsUUID, *tagIDAsUUID) + return transactions.DeleteTag(t.DB, *tagIDAsUUID) } diff --git a/backend/src/services/user.go b/backend/src/services/user.go index 4a985332b..646976e09 100644 --- a/backend/src/services/user.go +++ b/backend/src/services/user.go @@ -19,8 +19,6 @@ type UserServiceInterface interface { GetUser(id string) (*models.User, *errors.Error) UpdateUser(id string, userBody models.UpdateUserRequestBody) (*models.User, *errors.Error) DeleteUser(id string) *errors.Error - GetUserTags(id string) ([]models.Tag, *errors.Error) - CreateUserTags(id string, tagIDs models.CreateUserTagsBody) ([]models.Tag, *errors.Error) } type UserService struct { @@ -111,33 +109,3 @@ func (u *UserService) DeleteUser(id string) *errors.Error { return transactions.DeleteUser(u.DB, *idAsUUID) } - -func (u *UserService) GetUserTags(id string) ([]models.Tag, *errors.Error) { - idAsUUID, err := utilities.ValidateID(id) - if err != nil { - return nil, err - } - - return transactions.GetUserTags(u.DB, *idAsUUID) -} - -func (u *UserService) CreateUserTags(id string, tagIDs models.CreateUserTagsBody) ([]models.Tag, *errors.Error) { - // Validate the id: - idAsUUID, err := utilities.ValidateID(id) - if err != nil { - return nil, err - } - - if err := u.Validate.Struct(tagIDs); err != nil { - return nil, &errors.FailedToValidateUserTags - } - - // Retrieve a list of valid tags from the ids: - tags, err := transactions.GetTagsByIDs(u.DB, tagIDs.Tags) - if err != nil { - return nil, err - } - - // Update the user to reflect the new tags: - return transactions.CreateUserTags(u.DB, *idAsUUID, tags) -} diff --git a/backend/src/services/user_tag.go b/backend/src/services/user_tag.go new file mode 100644 index 000000000..27f979f9d --- /dev/null +++ b/backend/src/services/user_tag.go @@ -0,0 +1,54 @@ +package services + +import ( + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/models" + "github.com/GenerateNU/sac/backend/src/transactions" + "github.com/GenerateNU/sac/backend/src/utilities" + "github.com/go-playground/validator/v10" + "gorm.io/gorm" +) + +type UserTagServiceInterface interface { + GetUserTags(id string) ([]models.Tag, *errors.Error) + CreateUserTags(id string, tagIDs models.CreateUserTagsBody) ([]models.Tag, *errors.Error) +} + +type UserTagService struct { + DB *gorm.DB + Validate *validator.Validate +} + +func NewUserTagService(db *gorm.DB, validate *validator.Validate) *UserTagService { + return &UserTagService{DB: db, Validate: validate} +} + +func (u *UserTagService) GetUserTags(id string) ([]models.Tag, *errors.Error) { + idAsUUID, err := utilities.ValidateID(id) + if err != nil { + return nil, err + } + + return transactions.GetUserTags(u.DB, *idAsUUID) +} + +func (u *UserTagService) CreateUserTags(id string, tagIDs models.CreateUserTagsBody) ([]models.Tag, *errors.Error) { + // Validate the id: + idAsUUID, err := utilities.ValidateID(id) + if err != nil { + return nil, err + } + + if err := u.Validate.Struct(tagIDs); err != nil { + return nil, &errors.FailedToValidateUserTags + } + + // Retrieve a list of valid tags from the ids: + tags, err := transactions.GetTagsByIDs(u.DB, tagIDs.Tags) + if err != nil { + return nil, err + } + + // Update the user to reflect the new tags: + return transactions.CreateUserTags(u.DB, *idAsUUID, tags) +} diff --git a/backend/src/transactions/category_tag.go b/backend/src/transactions/category_tag.go new file mode 100644 index 000000000..b5d1a3ad9 --- /dev/null +++ b/backend/src/transactions/category_tag.go @@ -0,0 +1,25 @@ +package transactions + +import ( + stdliberrors "errors" + + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/google/uuid" + + "github.com/GenerateNU/sac/backend/src/models" + + "gorm.io/gorm" +) + +func GetTagByCategory(db *gorm.DB, categoryID uuid.UUID, tagID uuid.UUID) (*models.Tag, *errors.Error) { + var tag models.Tag + if err := db.Where("category_id = ? AND id = ?", categoryID, tagID).First(&tag).Error; err != nil { + if stdliberrors.Is(err, gorm.ErrRecordNotFound) { + return nil, &errors.TagNotFound + } else { + return nil, &errors.FailedToGetTag + } + } + + return &tag, nil +} diff --git a/backend/src/transactions/tag.go b/backend/src/transactions/tag.go index e2ad6c689..503468f0c 100644 --- a/backend/src/transactions/tag.go +++ b/backend/src/transactions/tag.go @@ -35,9 +35,9 @@ func CreateTag(db *gorm.DB, tag models.Tag) (*models.Tag, *errors.Error) { return &tag, nil } -func GetTag(db *gorm.DB, categoryID uuid.UUID, tagID uuid.UUID) (*models.Tag, *errors.Error) { +func GetTag(db *gorm.DB, tagID uuid.UUID) (*models.Tag, *errors.Error) { var tag models.Tag - if err := db.Where("category_id = ? AND id = ?", categoryID, tagID).First(&tag).Error; err != nil { + if err := db.Where("id = ?", tagID).First(&tag).Error; err != nil { if stdliberrors.Is(err, gorm.ErrRecordNotFound) { return nil, &errors.TagNotFound } else { @@ -60,8 +60,8 @@ func UpdateTag(db *gorm.DB, id uuid.UUID, tag models.Tag) (*models.Tag, *errors. return &tag, nil } -func DeleteTag(db *gorm.DB, categoryID uuid.UUID, tagID uuid.UUID) *errors.Error { - if result := db.Where("category_id = ? AND id = ?", categoryID, tagID).Delete(&models.Tag{}); result.RowsAffected == 0 { +func DeleteTag(db *gorm.DB, tagID uuid.UUID) *errors.Error { + if result := db.Where("id = ?", tagID).Delete(&models.Tag{}); result.RowsAffected == 0 { if result.Error == nil { return &errors.TagNotFound } else { diff --git a/backend/src/transactions/user.go b/backend/src/transactions/user.go index 73d9f07c2..14d52e2f5 100644 --- a/backend/src/transactions/user.go +++ b/backend/src/transactions/user.go @@ -85,30 +85,3 @@ func DeleteUser(db *gorm.DB, id uuid.UUID) *errors.Error { } return nil } - -func GetUserTags(db *gorm.DB, id uuid.UUID) ([]models.Tag, *errors.Error) { - var tags []models.Tag - - user, err := GetUser(db, id) - if err != nil { - return nil, &errors.UserNotFound - } - - if err := db.Model(&user).Association("Tag").Find(&tags); err != nil { - return nil, &errors.FailedToGetTag - } - return tags, nil -} - -func CreateUserTags(db *gorm.DB, id uuid.UUID, tags []models.Tag) ([]models.Tag, *errors.Error) { - user, err := GetUser(db, id) - if err != nil { - return nil, &errors.UserNotFound - } - - if err := db.Model(&user).Association("Tag").Replace(tags); err != nil { - return nil, &errors.FailedToUpdateUser - } - - return tags, nil -} diff --git a/backend/src/transactions/user_tag.go b/backend/src/transactions/user_tag.go new file mode 100644 index 000000000..a8c1dd01d --- /dev/null +++ b/backend/src/transactions/user_tag.go @@ -0,0 +1,35 @@ +package transactions + +import ( + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/models" + "github.com/google/uuid" + "gorm.io/gorm" +) + +func GetUserTags(db *gorm.DB, id uuid.UUID) ([]models.Tag, *errors.Error) { + var tags []models.Tag + + user, err := GetUser(db, id) + if err != nil { + return nil, &errors.UserNotFound + } + + if err := db.Model(&user).Association("Tag").Find(&tags); err != nil { + return nil, &errors.FailedToGetTag + } + return tags, nil +} + +func CreateUserTags(db *gorm.DB, id uuid.UUID, tags []models.Tag) ([]models.Tag, *errors.Error) { + user, err := GetUser(db, id) + if err != nil { + return nil, &errors.UserNotFound + } + + if err := db.Model(&user).Association("Tag").Replace(tags); err != nil { + return nil, &errors.FailedToUpdateUser + } + + return tags, nil +} diff --git a/backend/tests/api/category_tag_test.go b/backend/tests/api/category_tag_test.go new file mode 100644 index 000000000..ca8701d29 --- /dev/null +++ b/backend/tests/api/category_tag_test.go @@ -0,0 +1 @@ +package tests diff --git a/backend/tests/api/tag_test.go b/backend/tests/api/tag_test.go index 17f4f8b1b..eb9174431 100644 --- a/backend/tests/api/tag_test.go +++ b/backend/tests/api/tag_test.go @@ -14,9 +14,10 @@ import ( "github.com/goccy/go-json" ) -func SampleTagFactory() *map[string]interface{} { +func SampleTagFactory(categoryID uuid.UUID) *map[string]interface{} { return &map[string]interface{}{ - "name": "Generate", + "name": "Generate", + "category_id": categoryID, } } @@ -38,29 +39,30 @@ func AssertTagWithBodyRespDB(app TestApp, assert *assert.A, resp *http.Response, assert.Equal(dbTag.CategoryID, respTag.CategoryID) assert.Equal((*body)["name"].(string), dbTag.Name) + assert.Equal((*body)["category_id"].(uuid.UUID), dbTag.CategoryID) return dbTag.ID } func AssertSampleTagBodyRespDB(t *testing.T, app TestApp, assert *assert.A, resp *http.Response) uuid.UUID { - appAssert, _ := CreateSampleCategory(t, &ExistingAppAssert{ + appAssert, uuid := CreateSampleCategory(t, &ExistingAppAssert{ App: app, Assert: assert, }) - return AssertTagWithBodyRespDB(appAssert.App, appAssert.Assert, resp, SampleTagFactory()) + return AssertTagWithBodyRespDB(appAssert.App, appAssert.Assert, resp, SampleTagFactory(uuid)) } func CreateSampleTag(t *testing.T) (appAssert ExistingAppAssert, categoryUUID uuid.UUID, tagUUID uuid.UUID) { appAssert, categoryUUID = CreateSampleCategory(t, nil) AssertSampleTagBodyRespDB := func(app TestApp, assert *assert.A, resp *http.Response) { - tagUUID = AssertTagWithBodyRespDB(app, assert, resp, SampleTagFactory()) + tagUUID = AssertTagWithBodyRespDB(app, assert, resp, SampleTagFactory(categoryUUID)) } TestRequest{ Method: fiber.MethodPost, - Path: fmt.Sprintf("/api/v1/categories/%s/tags", categoryUUID), - Body: SampleTagFactory(), + Path: "/api/v1/tags/", + Body: SampleTagFactory(categoryUUID), }.TestOnStatusAndDB(t, &appAssert, DBTesterWithStatus{ Status: fiber.StatusCreated, @@ -95,65 +97,54 @@ func Assert1Tag(app TestApp, assert *assert.A, resp *http.Response) { } func TestCreateTagFailsBadRequest(t *testing.T) { - appAssert, categoryUUID := CreateSampleCategory(t, nil) - badBodys := []map[string]interface{}{ { - "name": 1, + "name": "Generate", + "category_id": "1", + }, + { + "name": 1, + "category_id": 1, }, } for _, badBody := range badBodys { TestRequest{ Method: fiber.MethodPost, - Path: fmt.Sprintf("/api/v1/categories/%s/tags", categoryUUID), + Path: "/api/v1/tags/", Body: &badBody, - }.TestOnErrorAndDB(t, &appAssert, + }.TestOnErrorAndDB(t, nil, ErrorWithDBTester{ Error: errors.FailedToParseRequestBody, DBTester: AssertNoTags, }, - ) + ).Close() } - - appAssert.Close() -} - -func TestCreateTagFailsCategoryNotFound(t *testing.T) { - uuid := uuid.New() - TestRequest{ - Method: fiber.MethodPost, - Path: fmt.Sprintf("/api/v1/categories/%s/tags", uuid), - Body: SampleTagFactory(), - }.TestOnErrorAndDB(t, nil, - ErrorWithDBTester{ - Error: errors.CategoryNotFound, - DBTester: AssertNoTags, - }, - ).Close() } func TestCreateTagFailsValidation(t *testing.T) { - appAssert, categoryUUID := CreateSampleCategory(t, nil) - badBodys := []map[string]interface{}{ + { + "name": "Generate", + }, + { + "category_id": uuid.New(), + }, {}, } for _, badBody := range badBodys { TestRequest{ Method: fiber.MethodPost, - Path: fmt.Sprintf("/api/v1/categories/%s/tags", categoryUUID), + Path: "/api/v1/tags/", Body: &badBody, - }.TestOnErrorAndDB(t, &appAssert, + }.TestOnErrorAndDB(t, nil, ErrorWithDBTester{ Error: errors.FailedToValidateTag, DBTester: AssertNoTags, }, - ) + ).Close() } - - appAssert.Close() } func TestGetTagWorks(t *testing.T) { @@ -161,41 +152,18 @@ func TestGetTagWorks(t *testing.T) { TestRequest{ Method: fiber.MethodGet, - Path: fmt.Sprintf("/api/v1/categories/%s/tags/%s", categoryUUID, tagUUID), + Path: fmt.Sprintf("/api/v1/tags/%s", tagUUID), }.TestOnStatusAndDB(t, &existingAppAssert, DBTesterWithStatus{ Status: fiber.StatusOK, DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { - AssertTagWithBodyRespDB(app, assert, resp, SampleTagFactory()) + AssertTagWithBodyRespDB(app, assert, resp, SampleTagFactory(categoryUUID)) }, }, ).Close() } -func TestGetTagFailsCategoryBadRequest(t *testing.T) { - appAssert, _, tagUUID := CreateSampleTag(t) - - badRequests := []string{ - "0", - "-1", - "1.1", - "foo", - "null", - } - - for _, badRequest := range badRequests { - TestRequest{ - Method: fiber.MethodGet, - Path: fmt.Sprintf("/api/v1/categories/%s/tags/%s", badRequest, tagUUID), - }.TestOnError(t, &appAssert, errors.FailedToValidateID) - } - - appAssert.Close() -} - -func TestGetTagFailsTagBadRequest(t *testing.T) { - appAssert, categoryUUID := CreateSampleCategory(t, nil) - +func TestGetTagFailsBadRequest(t *testing.T) { badRequests := []string{ "0", "-1", @@ -207,35 +175,22 @@ func TestGetTagFailsTagBadRequest(t *testing.T) { for _, badRequest := range badRequests { TestRequest{ Method: fiber.MethodGet, - Path: fmt.Sprintf("/api/v1/categories/%s/tags/%s", categoryUUID, badRequest), - }.TestOnError(t, &appAssert, errors.FailedToValidateID) + Path: fmt.Sprintf("/api/v1/tags/%s", badRequest), + }.TestOnError(t, nil, errors.FailedToValidateID).Close() } - - appAssert.Close() } -func TestGetTagFailsCategoryNotFound(t *testing.T) { - appAssert, _, tagUUID := CreateSampleTag(t) - +func TestGetTagFailsNotFound(t *testing.T) { TestRequest{ Method: fiber.MethodGet, - Path: fmt.Sprintf("/api/v1/categories/%s/tags/%s", uuid.New(), tagUUID), - }.TestOnError(t, &appAssert, errors.TagNotFound).Close() -} - -func TestGetTagFailsTagNotFound(t *testing.T) { - appAssert, categoryUUID := CreateSampleCategory(t, nil) - - TestRequest{ - Method: fiber.MethodGet, - Path: fmt.Sprintf("/api/v1/categories/%s/tags/%s", categoryUUID, uuid.New()), - }.TestOnError(t, &appAssert, errors.TagNotFound).Close() + Path: fmt.Sprintf("/api/v1/tags/%s", uuid.New()), + }.TestOnError(t, nil, errors.TagNotFound).Close() } func TestUpdateTagWorksUpdateName(t *testing.T) { existingAppAssert, categoryUUID, tagUUID := CreateSampleTag(t) - generateNUTag := *SampleTagFactory() + generateNUTag := *SampleTagFactory(categoryUUID) generateNUTag["name"] = "GenerateNU" AssertUpdatedTagBodyRespDB := func(app TestApp, assert *assert.A, resp *http.Response) { @@ -244,7 +199,7 @@ func TestUpdateTagWorksUpdateName(t *testing.T) { TestRequest{ Method: fiber.MethodPatch, - Path: fmt.Sprintf("/api/v1/categories/%s/tags/%s", categoryUUID, tagUUID), + Path: fmt.Sprintf("/api/v1/tags/%s", tagUUID), Body: &generateNUTag, }.TestOnStatusAndDB(t, &existingAppAssert, DBTesterWithStatus{ @@ -255,13 +210,15 @@ func TestUpdateTagWorksUpdateName(t *testing.T) { } func TestUpdateTagWorksUpdateCategory(t *testing.T) { - existingAppAssert, categoryUUID, tagUUID := CreateSampleTag(t) + existingAppAssert, _, tagUUID := CreateSampleTag(t) technologyCategory := *SampleCategoryFactory() technologyCategory["name"] = "Technology" + var technologyCategoryUUID uuid.UUID + AssertNewCategoryBodyRespDB := func(app TestApp, assert *assert.A, resp *http.Response) { - AssertCategoryWithBodyRespDBMostRecent(app, assert, resp, &technologyCategory) + technologyCategoryUUID = AssertCategoryWithBodyRespDBMostRecent(app, assert, resp, &technologyCategory) } TestRequest{ @@ -275,7 +232,7 @@ func TestUpdateTagWorksUpdateCategory(t *testing.T) { }, ) - technologyTag := *SampleTagFactory() + technologyTag := *SampleTagFactory(technologyCategoryUUID) AssertUpdatedTagBodyRespDB := func(app TestApp, assert *assert.A, resp *http.Response) { AssertTagWithBodyRespDB(app, assert, resp, &technologyTag) @@ -283,7 +240,7 @@ func TestUpdateTagWorksUpdateCategory(t *testing.T) { TestRequest{ Method: fiber.MethodPatch, - Path: fmt.Sprintf("/api/v1/categories/%s/tags/%s", categoryUUID, tagUUID), + Path: fmt.Sprintf("/api/v1/tags/%s", tagUUID), Body: &technologyTag, }.TestOnStatusAndDB(t, &existingAppAssert, DBTesterWithStatus{ @@ -298,42 +255,20 @@ func TestUpdateTagWorksWithSameDetails(t *testing.T) { TestRequest{ Method: fiber.MethodPatch, - Path: fmt.Sprintf("/api/v1/categories/%s/tags/%s", categoryUUID, tagUUID), - Body: SampleTagFactory(), + Path: fmt.Sprintf("/api/v1/tags/%s", tagUUID), + Body: SampleTagFactory(categoryUUID), }.TestOnStatusAndDB(t, &existingAppAssert, DBTesterWithStatus{ Status: fiber.StatusOK, DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { - AssertTagWithBodyRespDB(app, assert, resp, SampleTagFactory()) + AssertTagWithBodyRespDB(app, assert, resp, SampleTagFactory(categoryUUID)) }, }, ).Close() } -func TestUpdateTagFailsCategoryBadRequest(t *testing.T) { - appAssert, _, tagUUID := CreateSampleTag(t) - - badRequests := []string{ - "0", - "-1", - "1.1", - "foo", - "null", - } - - for _, badRequest := range badRequests { - TestRequest{ - Method: fiber.MethodPatch, - Path: fmt.Sprintf("/api/v1/categories/%s/tags/%s", badRequest, tagUUID), - Body: SampleTagFactory(), - }.TestOnError(t, &appAssert, errors.FailedToValidateID) - } - - appAssert.Close() -} - -func TestUpdateTagFailsTagBadRequest(t *testing.T) { - appAssert, categoryUUID := CreateSampleCategory(t, nil) +func TestUpdateTagFailsBadRequest(t *testing.T) { + appAssert, uuid := CreateSampleCategory(t, nil) badRequests := []string{ "0", @@ -346,20 +281,18 @@ func TestUpdateTagFailsTagBadRequest(t *testing.T) { for _, badRequest := range badRequests { TestRequest{ Method: fiber.MethodPatch, - Path: fmt.Sprintf("/api/v1/categories/%s/tags/%s", categoryUUID, badRequest), - Body: SampleTagFactory(), - }.TestOnError(t, &appAssert, errors.FailedToValidateID) + Path: fmt.Sprintf("/api/v1/tags/%s", badRequest), + Body: SampleTagFactory(uuid), + }.TestOnError(t, &appAssert, errors.FailedToValidateID).Close() } - - appAssert.Close() } func TestDeleteTagWorks(t *testing.T) { - existingAppAssert, categoryUUID, tagUUID := CreateSampleTag(t) + existingAppAssert, _, tagUUID := CreateSampleTag(t) TestRequest{ Method: fiber.MethodDelete, - Path: fmt.Sprintf("/api/v1/categories/%s/tags/%s", categoryUUID, tagUUID), + Path: fmt.Sprintf("/api/v1/tags/%s", tagUUID), }.TestOnStatusAndDB(t, &existingAppAssert, DBTesterWithStatus{ Status: fiber.StatusNoContent, @@ -368,34 +301,8 @@ func TestDeleteTagWorks(t *testing.T) { ).Close() } -func TestDeleteTagFailsCategoryBadRequest(t *testing.T) { - appAssert, _, tagUUID := CreateSampleTag(t) - - badRequests := []string{ - "0", - "-1", - "1.1", - "foo", - "null", - } - - for _, badRequest := range badRequests { - TestRequest{ - Method: fiber.MethodDelete, - Path: fmt.Sprintf("/api/v1/categories/%s/tags/%s", badRequest, tagUUID), - }.TestOnErrorAndDB(t, &appAssert, - ErrorWithDBTester{ - Error: errors.FailedToValidateID, - DBTester: Assert1Tag, - }, - ) - } - - appAssert.Close() -} - -func TestDeleteTagFailsTagBadRequest(t *testing.T) { - appAssert, categoryUUID, _ := CreateSampleTag(t) +func TestDeleteTagFailsBadRequest(t *testing.T) { + appAssert, _, _ := CreateSampleTag(t) badRequests := []string{ "0", @@ -408,7 +315,7 @@ func TestDeleteTagFailsTagBadRequest(t *testing.T) { for _, badRequest := range badRequests { TestRequest{ Method: fiber.MethodDelete, - Path: fmt.Sprintf("/api/v1/categories/%s/tags/%s", categoryUUID, badRequest), + Path: fmt.Sprintf("/api/v1/tags/%s", badRequest), }.TestOnErrorAndDB(t, &appAssert, ErrorWithDBTester{ Error: errors.FailedToValidateID, @@ -420,26 +327,12 @@ func TestDeleteTagFailsTagBadRequest(t *testing.T) { appAssert.Close() } -func TestDeleteTagFailsCategoryNotFound(t *testing.T) { - appAssert, _, tagUUID := CreateSampleTag(t) - - TestRequest{ - Method: fiber.MethodDelete, - Path: fmt.Sprintf("/api/v1/categories/%s/tags/%s", uuid.New(), tagUUID), - }.TestOnErrorAndDB(t, &appAssert, - ErrorWithDBTester{ - Error: errors.TagNotFound, - DBTester: Assert1Tag, - }, - ).Close() -} - -func TestDeleteTagFailsTagNotFound(t *testing.T) { - appAssert, categoryUUID, _ := CreateSampleTag(t) +func TestDeleteTagFailsNotFound(t *testing.T) { + appAssert, _, _ := CreateSampleTag(t) TestRequest{ Method: fiber.MethodDelete, - Path: fmt.Sprintf("/api/v1/categories/%s/tags/%s", categoryUUID, uuid.New()), + Path: fmt.Sprintf("/api/v1/tags/%s", uuid.New()), }.TestOnErrorAndDB(t, &appAssert, ErrorWithDBTester{ Error: errors.TagNotFound, diff --git a/backend/tests/api/user_tag_test.go b/backend/tests/api/user_tag_test.go new file mode 100644 index 000000000..a67022a56 --- /dev/null +++ b/backend/tests/api/user_tag_test.go @@ -0,0 +1,322 @@ +package tests + +import ( + "fmt" + "net/http" + "testing" + + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/models" + "github.com/goccy/go-json" + "github.com/gofiber/fiber/v2" + "github.com/google/uuid" + "github.com/huandu/go-assert" +) + +func SampleCategoriesFactory() *[]map[string]interface{} { + return &[]map[string]interface{}{ + { + "name": "Business", + }, + { + "name": "STEM", + }, + } +} + +func SampleTagsFactory(categoryIDs []uuid.UUID) *[]map[string]interface{} { + lenOfIDs := len(categoryIDs) + + return &[]map[string]interface{}{ + { + "name": "Computer Science", + "category_id": categoryIDs[1%lenOfIDs], + }, + { + "name": "Mechanical Engineering", + "category_id": categoryIDs[1%lenOfIDs], + }, + { + "name": "Finance", + "category_id": categoryIDs[0%lenOfIDs], + }, + } +} + +func SampleTagIDsFactory(tagIDs *[]uuid.UUID) *map[string]interface{} { + tags := tagIDs + + if tags == nil { + tags = &[]uuid.UUID{uuid.New()} + } + + return &map[string]interface{}{ + "tags": tags, + } +} + +func CreateSetOfTags(t *testing.T, appAssert ExistingAppAssert) []uuid.UUID { + categories := SampleCategoriesFactory() + + categoryIDs := []uuid.UUID{} + for _, category := range *categories { + TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/categories/", + Body: &category, + }.TestOnStatusAndDB(t, &appAssert, + DBTesterWithStatus{ + Status: fiber.StatusCreated, + DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { + var respCategory models.Category + + err := json.NewDecoder(resp.Body).Decode(&respCategory) + + assert.NilError(err) + + categoryIDs = append(categoryIDs, respCategory.ID) + }, + }, + ) + } + + tags := SampleTagsFactory(categoryIDs) + + tagIDs := []uuid.UUID{} + for _, tag := range *tags { + TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/tags/", + Body: &tag, + }.TestOnStatusAndDB(t, &appAssert, + DBTesterWithStatus{ + Status: fiber.StatusCreated, + DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { + var respTag models.Tag + + err := json.NewDecoder(resp.Body).Decode(&respTag) + + assert.NilError(err) + + tagIDs = append(tagIDs, respTag.ID) + }, + }, + ) + } + + return tagIDs +} + +func AssertUserTagsRespDB(app TestApp, assert *assert.A, resp *http.Response, id uuid.UUID) { + var respTags []models.Tag + + // Retrieve the tags from the response: + err := json.NewDecoder(resp.Body).Decode(&respTags) + + assert.NilError(err) + + // Retrieve the user connected to the tags: + var dbUser models.User + err = app.Conn.First(&dbUser, id).Error + + assert.NilError(err) + + // Retrieve the tags in the bridge table associated with the user: + var dbTags []models.Tag + err = app.Conn.Model(&dbUser).Association("Tag").Find(&dbTags) + + assert.NilError(err) + + // Confirm all the resp tags are equal to the db tags: + for i, respTag := range respTags { + assert.Equal(respTag.ID, dbTags[i].ID) + assert.Equal(respTag.Name, dbTags[i].Name) + assert.Equal(respTag.CategoryID, dbTags[i].CategoryID) + } +} + +func AssertSampleUserTagsRespDB(app TestApp, assert *assert.A, resp *http.Response, uuid uuid.UUID) { + AssertUserTagsRespDB(app, assert, resp, uuid) +} + +func TestCreateUserTagsFailsOnInvalidDataType(t *testing.T) { + // Invalid tag data types: + invalidTags := []interface{}{ + []string{"1", "2", "34"}, + []models.Tag{{Name: "Test", CategoryID: uuid.UUID{}}, {Name: "Test2", CategoryID: uuid.UUID{}}}, + []float32{1.32, 23.5, 35.1}, + } + + // Test each of the invalid tags: + for _, tag := range invalidTags { + malformedTag := *SampleTagIDsFactory(nil) + malformedTag["tags"] = tag + + TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/users/1/tags/", + Body: &malformedTag, + }.TestOnError(t, nil, errors.FailedToParseRequestBody).Close() + } +} + +func TestCreateUserTagsFailsOnInvalidUserID(t *testing.T) { + badRequests := []string{ + "0", + "-1", + "1.1", + "foo", + "null", + } + + for _, badRequest := range badRequests { + TestRequest{ + Method: fiber.MethodPost, + Path: fmt.Sprintf("/api/v1/users/%s/tags", badRequest), + Body: SampleTagIDsFactory(nil), + }.TestOnError(t, nil, errors.FailedToValidateID).Close() + } +} + +type UUIDSlice []uuid.UUID + +var testUUID = uuid.New() + +func TestCreateUserTagsFailsOnInvalidKey(t *testing.T) { + appAssert, uuid := CreateSampleUser(t, nil) + + invalidBody := []map[string]interface{}{ + { + "tag": UUIDSlice{testUUID, testUUID}, + }, + { + "tagIDs": []uint{1, 2, 3}, + }, + } + + for _, body := range invalidBody { + TestRequest{ + Method: fiber.MethodPost, + Path: fmt.Sprintf("/api/v1/users/%s/tags/", uuid), + Body: &body, + }.TestOnError(t, &appAssert, errors.FailedToValidateUserTags) + } + + appAssert.Close() +} + +func TestCreateUserTagsFailsOnNonExistentUser(t *testing.T) { + TestRequest{ + Method: fiber.MethodPost, + Path: fmt.Sprintf("/api/v1/users/%s/tags", uuid.New()), + Body: SampleTagIDsFactory(nil), + }.TestOnError(t, nil, errors.UserNotFound).Close() +} + +func TestCreateUserTagsWorks(t *testing.T) { + appAssert, uuid := CreateSampleUser(t, nil) + + // Create a set of tags: + tagUUIDs := CreateSetOfTags(t, appAssert) + + // Confirm adding real tags adds them to the user: + TestRequest{ + Method: fiber.MethodPost, + Path: fmt.Sprintf("/api/v1/users/%s/tags/", uuid), + Body: SampleTagIDsFactory(&tagUUIDs), + }.TestOnStatusAndDB(t, &appAssert, + DBTesterWithStatus{ + Status: fiber.StatusCreated, + DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { + AssertSampleUserTagsRespDB(app, assert, resp, uuid) + }, + }, + ) + + appAssert.Close() +} + +func TestCreateUserTagsNoneAddedIfInvalid(t *testing.T) { + appAssert, uuid := CreateSampleUser(t, nil) + + TestRequest{ + Method: fiber.MethodPost, + Path: fmt.Sprintf("/api/v1/users/%s/tags/", uuid), + Body: SampleTagIDsFactory(nil), + }.TestOnStatusAndDB(t, &appAssert, + DBTesterWithStatus{ + Status: fiber.StatusCreated, + DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { + var respTags []models.Tag + + err := json.NewDecoder(resp.Body).Decode(&respTags) + + assert.NilError(err) + + assert.Equal(len(respTags), 0) + }, + }, + ) + + appAssert.Close() +} + +func TestGetUserTagsFailsOnNonExistentUser(t *testing.T) { + TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/users/%s/tags/", uuid.New()), + }.TestOnError(t, nil, errors.UserNotFound).Close() +} + +func TestGetUserTagsReturnsEmptyListWhenNoneAdded(t *testing.T) { + appAssert, uuid := CreateSampleUser(t, nil) + + TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/users/%s/tags/", uuid), + }.TestOnStatusAndDB(t, &appAssert, + DBTesterWithStatus{ + Status: 200, + DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { + var respTags []models.Tag + + err := json.NewDecoder(resp.Body).Decode(&respTags) + + assert.NilError(err) + + assert.Equal(len(respTags), 0) + }, + }, + ) + + appAssert.Close() +} + +func TestGetUserTagsReturnsCorrectList(t *testing.T) { + appAssert, uuid := CreateSampleUser(t, nil) + + // Create a set of tags: + tagUUIDs := CreateSetOfTags(t, appAssert) + + // Add the tags: + TestRequest{ + Method: fiber.MethodPost, + Path: fmt.Sprintf("/api/v1/users/%s/tags/", uuid), + Body: SampleTagIDsFactory(&tagUUIDs), + }.TestOnStatus(t, &appAssert, fiber.StatusCreated) + + // Get the tags: + TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/users/%s/tags/", uuid), + }.TestOnStatusAndDB(t, &appAssert, + DBTesterWithStatus{ + Status: fiber.StatusOK, + DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { + AssertSampleUserTagsRespDB(app, assert, resp, uuid) + }, + }, + ) + + appAssert.Close() +} diff --git a/backend/tests/api/user_test.go b/backend/tests/api/user_test.go index acd94abec..a1f74a8c8 100644 --- a/backend/tests/api/user_test.go +++ b/backend/tests/api/user_test.go @@ -550,309 +550,3 @@ func TestCreateUserFailsOnMissingFields(t *testing.T) { } appAssert.Close() } - -func SampleCategoriesFactory() *[]map[string]interface{} { - return &[]map[string]interface{}{ - { - "name": "Business", - }, - { - "name": "STEM", - }, - } -} - -func SampleTagsFactory(categoryUUIDs []uuid.UUID) *[]map[string]interface{} { - lenOfUUIDs := len(categoryUUIDs) - - return &[]map[string]interface{}{ - { - "name": "Computer Science", - "category_id": categoryUUIDs[1%lenOfUUIDs], - }, - { - "name": "Mechanical Engineering", - "category_id": categoryUUIDs[1%lenOfUUIDs], - }, - { - "name": "Finance", - "category_id": categoryUUIDs[0%lenOfUUIDs], - }, - } -} - -func SampleTagUUIDsFactory(tagUUIDs *[]uuid.UUID) *map[string]interface{} { - tags := tagUUIDs - - if tags == nil { - tags = &[]uuid.UUID{uuid.New()} - } - - return &map[string]interface{}{ - "tags": tags, - } -} - -func CreateSetOfTags(t *testing.T, appAssert ExistingAppAssert) []uuid.UUID { - categories := SampleCategoriesFactory() - - categoryUUIDs := []uuid.UUID{} - for _, category := range *categories { - TestRequest{ - Method: fiber.MethodPost, - Path: "/api/v1/categories/", - Body: &category, - }.TestOnStatusAndDB(t, &appAssert, - DBTesterWithStatus{ - Status: fiber.StatusCreated, - DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { - var respCategory models.Category - - err := json.NewDecoder(resp.Body).Decode(&respCategory) - - assert.NilError(err) - - categoryUUIDs = append(categoryUUIDs, respCategory.ID) - }, - }, - ) - } - - tags := SampleTagsFactory(categoryUUIDs) - - tagUUIDs := []uuid.UUID{} - - for _, categoryUUID := range categoryUUIDs { - for _, tag := range *tags { - TestRequest{ - Method: fiber.MethodPost, - Path: fmt.Sprintf("/api/v1/categories/%s/tags/", categoryUUID), - Body: &tag, - }.TestOnStatusAndDB(t, &appAssert, - DBTesterWithStatus{ - Status: fiber.StatusCreated, - DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { - var respTag models.Tag - - err := json.NewDecoder(resp.Body).Decode(&respTag) - - assert.NilError(err) - - tagUUIDs = append(tagUUIDs, respTag.ID) - }, - }, - ) - } - } - - return tagUUIDs -} - -func AssertUserTagsRespDB(app TestApp, assert *assert.A, resp *http.Response, id uuid.UUID) { - var respTags []models.Tag - - // Retrieve the tags from the response: - err := json.NewDecoder(resp.Body).Decode(&respTags) - - assert.NilError(err) - - // Retrieve the user connected to the tags: - var dbUser models.User - err = app.Conn.First(&dbUser, id).Error - - assert.NilError(err) - - // Retrieve the tags in the bridge table associated with the user: - var dbTags []models.Tag - err = app.Conn.Model(&dbUser).Association("Tag").Find(&dbTags) - - assert.NilError(err) - - // Confirm all the resp tags are equal to the db tags: - for i, respTag := range respTags { - assert.Equal(respTag.ID, dbTags[i].ID) - assert.Equal(respTag.Name, dbTags[i].Name) - assert.Equal(respTag.CategoryID, dbTags[i].CategoryID) - } -} - -func AssertSampleUserTagsRespDB(app TestApp, assert *assert.A, resp *http.Response, uuid uuid.UUID) { - AssertUserTagsRespDB(app, assert, resp, uuid) -} - -func TestCreateUserTagsFailsOnInvalidDataType(t *testing.T) { - // Invalid tag data types: - invalidTags := []interface{}{ - []string{"1", "2", "34"}, - []models.Tag{{Name: "Test", CategoryID: uuid.UUID{}}, {Name: "Test2", CategoryID: uuid.UUID{}}}, - []float32{1.32, 23.5, 35.1}, - } - - // Test each of the invalid tags: - for _, tag := range invalidTags { - malformedTag := *SampleTagUUIDsFactory(nil) - malformedTag["tags"] = tag - - TestRequest{ - Method: fiber.MethodPost, - Path: "/api/v1/users/1/tags/", - Body: &malformedTag, - }.TestOnError(t, nil, errors.FailedToParseRequestBody).Close() - } -} - -func TestCreateUserTagsFailsOnInvalidUserID(t *testing.T) { - badRequests := []string{ - "0", - "-1", - "1.1", - "foo", - "null", - } - - for _, badRequest := range badRequests { - TestRequest{ - Method: fiber.MethodPost, - Path: fmt.Sprintf("/api/v1/users/%s/tags", badRequest), - Body: SampleTagUUIDsFactory(nil), - }.TestOnError(t, nil, errors.FailedToValidateID).Close() - } -} - -type UUIDSlice []uuid.UUID - -var testUUID = uuid.New() - -func TestCreateUserTagsFailsOnInvalidKey(t *testing.T) { - appAssert, uuid := CreateSampleUser(t, nil) - - invalidBody := []map[string]interface{}{ - { - "tag": UUIDSlice{testUUID, testUUID}, - }, - { - "tagIDs": []uint{1, 2, 3}, - }, - } - - for _, body := range invalidBody { - TestRequest{ - Method: fiber.MethodPost, - Path: fmt.Sprintf("/api/v1/users/%s/tags/", uuid), - Body: &body, - }.TestOnError(t, &appAssert, errors.FailedToValidateUserTags) - } - - appAssert.Close() -} - -func TestCreateUserTagsFailsOnNonExistentUser(t *testing.T) { - TestRequest{ - Method: fiber.MethodPost, - Path: fmt.Sprintf("/api/v1/users/%s/tags", uuid.New()), - Body: SampleTagUUIDsFactory(nil), - }.TestOnError(t, nil, errors.UserNotFound).Close() -} - -func TestCreateUserTagsWorks(t *testing.T) { - appAssert, uuid := CreateSampleUser(t, nil) - - tagUUIDs := CreateSetOfTags(t, appAssert) - - TestRequest{ - Method: fiber.MethodPost, - Path: fmt.Sprintf("/api/v1/users/%s/tags/", uuid), - Body: SampleTagUUIDsFactory(&tagUUIDs), - }.TestOnStatusAndDB(t, &appAssert, - DBTesterWithStatus{ - Status: fiber.StatusCreated, - DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { - AssertSampleUserTagsRespDB(app, assert, resp, uuid) - }, - }, - ) - - appAssert.Close() -} - -func TestCreateUserTagsNoneAddedIfInvalid(t *testing.T) { - appAssert, uuid := CreateSampleUser(t, nil) - - TestRequest{ - Method: fiber.MethodPost, - Path: fmt.Sprintf("/api/v1/users/%s/tags/", uuid), - Body: SampleTagUUIDsFactory(nil), - }.TestOnStatusAndDB(t, &appAssert, - DBTesterWithStatus{ - Status: fiber.StatusCreated, - DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { - var respTags []models.Tag - - err := json.NewDecoder(resp.Body).Decode(&respTags) - - assert.NilError(err) - - assert.Equal(len(respTags), 0) - }, - }, - ) - - appAssert.Close() -} - -func TestGetUserTagsFailsOnNonExistentUser(t *testing.T) { - TestRequest{ - Method: fiber.MethodGet, - Path: fmt.Sprintf("/api/v1/users/%s/tags/", uuid.New()), - }.TestOnError(t, nil, errors.UserNotFound).Close() -} - -func TestGetUserTagsReturnsEmptyListWhenNoneAdded(t *testing.T) { - appAssert, uuid := CreateSampleUser(t, nil) - - TestRequest{ - Method: fiber.MethodGet, - Path: fmt.Sprintf("/api/v1/users/%s/tags/", uuid), - }.TestOnStatusAndDB(t, &appAssert, - DBTesterWithStatus{ - Status: 200, - DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { - var respTags []models.Tag - - err := json.NewDecoder(resp.Body).Decode(&respTags) - - assert.NilError(err) - - assert.Equal(len(respTags), 0) - }, - }, - ) - - appAssert.Close() -} - -func TestGetUserTagsReturnsCorrectList(t *testing.T) { - appAssert, uuid := CreateSampleUser(t, nil) - - tagUUIDs := CreateSetOfTags(t, appAssert) - - TestRequest{ - Method: fiber.MethodPost, - Path: fmt.Sprintf("/api/v1/users/%s/tags/", uuid), - Body: SampleTagUUIDsFactory(&tagUUIDs), - }.TestOnStatus(t, &appAssert, fiber.StatusCreated) - - TestRequest{ - Method: fiber.MethodGet, - Path: fmt.Sprintf("/api/v1/users/%s/tags/", uuid), - }.TestOnStatusAndDB(t, &appAssert, - DBTesterWithStatus{ - Status: fiber.StatusOK, - DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { - AssertSampleUserTagsRespDB(app, assert, resp, uuid) - }, - }, - ) - - appAssert.Close() -}