From 498a888d89195a5a8f919641b52c8cd031874d44 Mon Sep 17 00:00:00 2001 From: Alder Whiteford Date: Fri, 26 Jan 2024 00:51:30 -0500 Subject: [PATCH 01/13] SAC-19 User Tag CRUD --- backend/src/controllers/user.go | 22 ++++++++++++++++++++++ backend/src/models/user.go | 8 +++++++- backend/src/server/server.go | 5 +++++ backend/src/services/user.go | 25 +++++++++++++++++++++++++ backend/src/transactions/tag.go | 12 ++++++++++++ backend/src/transactions/user.go | 27 +++++++++++++++++++++++++++ 6 files changed, 98 insertions(+), 1 deletion(-) diff --git a/backend/src/controllers/user.go b/backend/src/controllers/user.go index 1b509bf3d..c6e7c71eb 100644 --- a/backend/src/controllers/user.go +++ b/backend/src/controllers/user.go @@ -137,3 +137,25 @@ 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.Tags) + if err != nil { + return err.FiberError(c) + } + + return c.Status(fiber.StatusCreated).JSON(&tags) +} diff --git a/backend/src/models/user.go b/backend/src/models/user.go index 1f6b3aa7e..52f04308e 100644 --- a/backend/src/models/user.go +++ b/backend/src/models/user.go @@ -1,6 +1,8 @@ package models -import "github.com/GenerateNU/sac/backend/src/types" +import ( + "github.com/GenerateNU/sac/backend/src/types" +) type UserRole string @@ -76,3 +78,7 @@ type UpdateUserRequestBody struct { College College `json:"college" validate:"omitempty,oneof=CAMD DMSB KCCS CE BCHS SL CPS CS CSSH"` Year Year `json:"year" validate:"omitempty,min=1,max=6"` } + +type CreateUserTagsBody struct { + Tags []uint `json:"tags" validate:"required"` +} diff --git a/backend/src/server/server.go b/backend/src/server/server.go index 4d622d646..82c980ff7 100644 --- a/backend/src/server/server.go +++ b/backend/src/server/server.go @@ -73,6 +73,11 @@ func userRoutes(router fiber.Router, userService services.UserServiceInterface) users.Get("/:id", userController.GetUser) users.Patch("/:id", userController.UpdateUser) users.Delete("/:id", userController.DeleteUser) + + userTags := users.Group("/:uid/tags") + + userTags.Post("/", userController.CreateUserTags) + userTags.Get("/", userController.GetUserTags) } func categoryRoutes(router fiber.Router, categoryService services.CategoryServiceInterface) { diff --git a/backend/src/services/user.go b/backend/src/services/user.go index 324e37d56..e427c40e4 100644 --- a/backend/src/services/user.go +++ b/backend/src/services/user.go @@ -19,6 +19,8 @@ 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 []uint) ([]models.Tag, *errors.Error) } type UserService struct { @@ -107,3 +109,26 @@ func (u *UserService) DeleteUser(id string) *errors.Error { return transactions.DeleteUser(u.DB, *idAsInt) } + +func (u *UserService) GetUserTags(id string) ([]models.Tag, *errors.Error) { + idAsInt, err := utilities.ValidateID(id) + if err != nil { + return nil, err + } + + return transactions.GetUserTags(u.DB, *idAsInt) +} + +func (u *UserService) CreateUserTags(id string, tagIDs []uint) ([]models.Tag, *errors.Error) { + // Validate the id: + idAsInt, err := utilities.ValidateID(id) + if err != nil { + return nil, err + } + + // Retrieve a list of valid tags from the ids: + tags, err := transactions.GetTagsByIDs(u.DB, tagIDs) + + // Update the user to reflect the new tags: + return transactions.CreateUserTags(u.DB, *idAsInt, tags) +} diff --git a/backend/src/transactions/tag.go b/backend/src/transactions/tag.go index 9214ca683..da14ccdd0 100644 --- a/backend/src/transactions/tag.go +++ b/backend/src/transactions/tag.go @@ -55,3 +55,15 @@ func DeleteTag(db *gorm.DB, id uint) *errors.Error { return nil } + +func GetTagsByIDs(db *gorm.DB, selectedTagIDs []uint) ([]models.Tag, *errors.Error) { + if len(selectedTagIDs) != 0 { + var tags []models.Tag + if err := db.Model(models.Tag{}).Where("id IN ?", selectedTagIDs).Find(&tags).Error; err != nil { + return nil, &errors.FailedToGetTag + } + + return tags, nil + } + return []models.Tag{}, nil +} diff --git a/backend/src/transactions/user.go b/backend/src/transactions/user.go index 9fb868ee6..dc666b18b 100644 --- a/backend/src/transactions/user.go +++ b/backend/src/transactions/user.go @@ -74,3 +74,30 @@ func DeleteUser(db *gorm.DB, id uint) *errors.Error { } return nil } + +func GetUserTags(db *gorm.DB, id uint) ([]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 uint, 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 +} From 8a9f004401255217aa8defc02cbb0c01af6c350a Mon Sep 17 00:00:00 2001 From: Alder Whiteford Date: Sat, 27 Jan 2024 15:09:40 -0500 Subject: [PATCH 02/13] SAC-19-User-Tag-Tests --- backend/src/controllers/user.go | 2 +- backend/src/errors/user.go | 4 ++++ backend/src/services/user.go | 10 +++++++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/backend/src/controllers/user.go b/backend/src/controllers/user.go index c6e7c71eb..86b9c6b23 100644 --- a/backend/src/controllers/user.go +++ b/backend/src/controllers/user.go @@ -152,7 +152,7 @@ func (u *UserController) CreateUserTags(c *fiber.Ctx) error { return errors.FailedToParseRequestBody.FiberError(c) } - tags, err := u.userService.CreateUserTags(c.Params("uid"), requestBody.Tags) + tags, err := u.userService.CreateUserTags(c.Params("uid"), requestBody) if err != nil { return err.FiberError(c) } diff --git a/backend/src/errors/user.go b/backend/src/errors/user.go index 3d63b02fc..dd80dbb98 100644 --- a/backend/src/errors/user.go +++ b/backend/src/errors/user.go @@ -7,6 +7,10 @@ var ( StatusCode: fiber.StatusBadRequest, Message: "failed to validate user", } + FailedToValidateUserTags = Error { + StatusCode: fiber.StatusBadRequest, + Message: "failed to validate user tags", + } FailedToCreateUser = Error{ StatusCode: fiber.StatusInternalServerError, Message: "failed to create user", diff --git a/backend/src/services/user.go b/backend/src/services/user.go index e427c40e4..41cad7e0c 100644 --- a/backend/src/services/user.go +++ b/backend/src/services/user.go @@ -20,7 +20,7 @@ type UserServiceInterface interface { 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 []uint) ([]models.Tag, *errors.Error) + CreateUserTags(id string, tagIDs models.CreateUserTagsBody) ([]models.Tag, *errors.Error) } type UserService struct { @@ -119,15 +119,19 @@ func (u *UserService) GetUserTags(id string) ([]models.Tag, *errors.Error) { return transactions.GetUserTags(u.DB, *idAsInt) } -func (u *UserService) CreateUserTags(id string, tagIDs []uint) ([]models.Tag, *errors.Error) { +func (u *UserService) CreateUserTags(id string, tagIDs models.CreateUserTagsBody) ([]models.Tag, *errors.Error) { // Validate the id: idAsInt, 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, err := transactions.GetTagsByIDs(u.DB, tagIDs.Tags) // Update the user to reflect the new tags: return transactions.CreateUserTags(u.DB, *idAsInt, tags) From 9d503b5bee52c9a43a97bc46fdb62a8a6284bbef Mon Sep 17 00:00:00 2001 From: Alder Whiteford Date: Sat, 27 Jan 2024 16:33:22 -0500 Subject: [PATCH 03/13] SAC-19 User Tag Testing --- backend/tests/api/helpers.go | 1 + backend/tests/api/user_test.go | 263 +++++++++++++++++++++++++++++++++ 2 files changed, 264 insertions(+) diff --git a/backend/tests/api/helpers.go b/backend/tests/api/helpers.go index c9fce06dc..091ffa496 100644 --- a/backend/tests/api/helpers.go +++ b/backend/tests/api/helpers.go @@ -159,6 +159,7 @@ func (request TestRequest) Test(t *testing.T, existingAppAssert *ExistingAppAsse var req *http.Request if request.Body == nil { + t.Log("NO REQUEST BODY!") req = httptest.NewRequest(request.Method, address, nil) } else { bodyBytes, err := json.Marshal(request.Body) diff --git a/backend/tests/api/user_test.go b/backend/tests/api/user_test.go index 6ef0883b7..2839c20c0 100644 --- a/backend/tests/api/user_test.go +++ b/backend/tests/api/user_test.go @@ -528,3 +528,266 @@ func TestCreateUserFailsOnMissingFields(t *testing.T) { } appAssert.Close() } + +// Test add user tags: +// Test invalid body: +// - Invalid array data type (non-uint) +// - Invalid key +// Test invalid user: +// - Invalid id +// - Invalid id type +// Test works: +// - Only adds valid IDs + +func SampleCategoriesFactory() *[]map[string]interface{} { + return &[]map[string]interface{}{ + { + "name": "Business", + }, + { + "name": "STEM", + }, + } +} + +func SampleTagsFactory() *[]map[string]interface{} { + return &[]map[string]interface{}{ + { + "name": "Computer Science", + "category_id": 2, + }, + { + "name": "Mechanical Engineering", + "category_id": 2, + }, + { + "name": "Finance", + "category_id": 1, + }, + } +} + +func SampleTagIDsFactory() *map[string]interface{} { + return &map[string]interface{}{ + "tags": []uint{1, 2, 3}, + } +} + +func CreateSetOfTags(t *testing.T, appAssert ExistingAppAssert) { + categories := SampleCategoriesFactory() + tags := SampleTagsFactory() + + for _, category := range *categories { + TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/categories/", + Body: &category, + }.TestOnStatus(t, &appAssert, 201) + } + + for _, tag := range *tags { + TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/tags/", + Body: &tag, + }.TestOnStatus(t, &appAssert, 201) + } +} + +func AssertUserTagsRespDB(app TestApp, assert *assert.A, resp *http.Response, id uint) { + 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) { + AssertUserTagsRespDB(app, assert, resp, 2) +} + +func TestCreateUserTagsFailsOnInvalidDataType(t *testing.T) { + // Invalid tag data types: + invalidTags := []interface{}{ + []string{"1", "2", "34"}, + []models.Tag{{Name: "Test", CategoryID: 1}, {Name: "Test2", CategoryID: 2}}, + []float32{1.32, 23.5, 35.1}, + } + + // Test each of the invalid tags: + for _, tag := range invalidTags { + malformedTag := *SampleTagIDsFactory() + 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(), + }.TestOnError(t, nil, errors.FailedToValidateID).Close() + } +} + +func TestCreateUserTagsFailsOnInvalidKey(t *testing.T) { + appAssert := CreateSampleUser(t) + + invalidBody := []map[string]interface{}{ + { + "tag": []uint{1, 2, 3}, + }, + { + "tagIDs": []uint{1, 2, 3}, + }, + } + + for _, body := range invalidBody { + TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/users/2/tags/", + Body: &body, + }.TestOnError(t, &appAssert, errors.FailedToValidateUserTags) + } + + appAssert.Close() +} + +func TestCreateUserTagsFailsOnNonExistentUser(t *testing.T) { + TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/users/20/tags", + Body: SampleTagIDsFactory(), + }.TestOnError(t, nil, errors.UserNotFound).Close() +} + +func TestCreateUserTagsWorks(t *testing.T) { + appAssert := CreateSampleUser(t) + + // Confirm adding non-existent tags does nothing: + TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/users/2/tags/", + Body: SampleTagIDsFactory(), + }.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) + }, + }, + ) + + // Create a set of tags: + CreateSetOfTags(t, appAssert) + + // Confirm adding real tags adds them to the user: + TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/users/2/tags/", + Body: SampleTagIDsFactory(), + }.TestOnStatusAndDB(t, &appAssert, + DBTesterWithStatus{ + Status: fiber.StatusCreated, + DBTester: AssertSampleUserTagsRespDB, + }, + ) + + appAssert.Close() +} + +func TestGetUserTagsFailsOnNonExistentUser(t *testing.T) { + TestRequest{ + Method: fiber.MethodGet, + Path: "/api/v1/users/2/tags/", + }.TestOnError(t, nil, errors.UserNotFound) +} + +func TestGetUserTagsReturnsEmptyListWhenNoneAdded(t *testing.T) { + appAssert := CreateSampleUser(t) + + TestRequest{ + Method: fiber.MethodGet, + Path: "/api/v1/users/2/tags/", + }.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) + }, + }, + ) +} + +func TestGetUserTagsReturnsCorrectList(t *testing.T) { + appAssert := CreateSampleUser(t) + + // Create a set of tags: + CreateSetOfTags(t, appAssert) + + // Add the tags: + TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/users/2/tags/", + Body: SampleTagIDsFactory(), + }.TestOnStatus(t, &appAssert, 201) + + // Get the tags: + TestRequest{ + Method: fiber.MethodGet, + Path: "/api/v1/users/2/tags/", + }.TestOnStatusAndDB(t, &appAssert, + DBTesterWithStatus{ + Status: fiber.StatusOK, + DBTester: AssertSampleUserTagsRespDB, + }, + ) +} + From ba991d37aaa9283ae8a7aa00daf8e3cf34f080cb Mon Sep 17 00:00:00 2001 From: Alder Whiteford Date: Sat, 27 Jan 2024 18:00:35 -0500 Subject: [PATCH 04/13] SAC-19 Fix UserTag Tests for UUID Update --- backend/src/models/tag.go | 2 +- backend/src/models/user.go | 4 +++- backend/src/services/user.go | 4 ++-- backend/src/transactions/tag.go | 3 +-- backend/src/transactions/user.go | 4 ++-- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/backend/src/models/tag.go b/backend/src/models/tag.go index 471b8b2ae..36c71381a 100644 --- a/backend/src/models/tag.go +++ b/backend/src/models/tag.go @@ -7,7 +7,7 @@ type Tag struct { Name string `gorm:"type:varchar(255)" json:"name" validate:"required,max=255"` - CategoryID uuid.UUID `gorm:"foreignKey:CategoryID" json:"category_id" validate:"required,uuid4"` + CategoryID uuid.UUID `json:"category_id" validate:"required,uuid4"` User []User `gorm:"many2many:user_tags;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"-"` Club []Club `gorm:"many2many:club_tags;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"-"` diff --git a/backend/src/models/user.go b/backend/src/models/user.go index 309bfec2f..ee762ab5f 100644 --- a/backend/src/models/user.go +++ b/backend/src/models/user.go @@ -1,5 +1,7 @@ package models +import "github.com/google/uuid" + type UserRole string const ( @@ -76,5 +78,5 @@ type UpdateUserRequestBody struct { } type CreateUserTagsBody struct { - Tags []uint `json:"tags" validate:"required"` + Tags []uuid.UUID `json:"tags" validate:"required"` } diff --git a/backend/src/services/user.go b/backend/src/services/user.go index bcd44885e..60fe931e7 100644 --- a/backend/src/services/user.go +++ b/backend/src/services/user.go @@ -111,12 +111,12 @@ func (u *UserService) DeleteUser(id string) *errors.Error { } func (u *UserService) GetUserTags(id string) ([]models.Tag, *errors.Error) { - idAsInt, err := utilities.ValidateID(id) + idAsUUID, err := utilities.ValidateID(id) if err != nil { return nil, err } - return transactions.GetUserTags(u.DB, *idAsInt) + return transactions.GetUserTags(u.DB, *idAsUUID) } func (u *UserService) CreateUserTags(id string, tagIDs models.CreateUserTagsBody) ([]models.Tag, *errors.Error) { diff --git a/backend/src/transactions/tag.go b/backend/src/transactions/tag.go index 013f4d46c..2101ae177 100644 --- a/backend/src/transactions/tag.go +++ b/backend/src/transactions/tag.go @@ -2,7 +2,6 @@ package transactions import ( stdliberrors "errors" - "github.com/GenerateNU/sac/backend/src/errors" "github.com/google/uuid" @@ -57,7 +56,7 @@ func DeleteTag(db *gorm.DB, id uuid.UUID) *errors.Error { return nil } -func GetTagsByIDs(db *gorm.DB, selectedTagIDs []uint) ([]models.Tag, *errors.Error) { +func GetTagsByIDs(db *gorm.DB, selectedTagIDs []uuid.UUID) ([]models.Tag, *errors.Error) { if len(selectedTagIDs) != 0 { var tags []models.Tag if err := db.Model(models.Tag{}).Where("id IN ?", selectedTagIDs).Find(&tags).Error; err != nil { diff --git a/backend/src/transactions/user.go b/backend/src/transactions/user.go index 88d1faf38..3f4540b24 100644 --- a/backend/src/transactions/user.go +++ b/backend/src/transactions/user.go @@ -76,7 +76,7 @@ func DeleteUser(db *gorm.DB, id uuid.UUID) *errors.Error { return nil } -func GetUserTags(db *gorm.DB, id uint) ([]models.Tag, *errors.Error) { +func GetUserTags(db *gorm.DB, id uuid.UUID) ([]models.Tag, *errors.Error) { var tags []models.Tag user, err := GetUser(db, id) @@ -90,7 +90,7 @@ func GetUserTags(db *gorm.DB, id uint) ([]models.Tag, *errors.Error) { return tags, nil } -func CreateUserTags(db *gorm.DB, id uint, tags []models.Tag) ([]models.Tag, *errors.Error) { +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 From a56175a48d297b07c5e54af99d0ab45cd9a44369 Mon Sep 17 00:00:00 2001 From: Alder Whiteford Date: Sat, 27 Jan 2024 18:01:42 -0500 Subject: [PATCH 05/13] God damnit I was in /src --- backend/tests/api/user_test.go | 148 +++++++++++++++++++++------------ 1 file changed, 95 insertions(+), 53 deletions(-) diff --git a/backend/tests/api/user_test.go b/backend/tests/api/user_test.go index da5912aa5..8ae65d4fb 100644 --- a/backend/tests/api/user_test.go +++ b/backend/tests/api/user_test.go @@ -547,16 +547,6 @@ func TestCreateUserFailsOnMissingFields(t *testing.T) { appAssert.Close() } -// Test add user tags: -// Test invalid body: -// - Invalid array data type (non-uint) -// - Invalid key -// Test invalid user: -// - Invalid id -// - Invalid id type -// Test works: -// - Only adds valid IDs - func SampleCategoriesFactory() *[]map[string]interface{} { return &[]map[string]interface{}{ { @@ -568,51 +558,92 @@ func SampleCategoriesFactory() *[]map[string]interface{} { } } -func SampleTagsFactory() *[]map[string]interface{} { +func SampleTagsFactory(categoryIDs []uuid.UUID) *[]map[string]interface{} { + lenOfIDs := len(categoryIDs) + return &[]map[string]interface{}{ { "name": "Computer Science", - "category_id": 2, + "category_id": categoryIDs[1%lenOfIDs], }, { "name": "Mechanical Engineering", - "category_id": 2, + "category_id": categoryIDs[1%lenOfIDs], }, { "name": "Finance", - "category_id": 1, + "category_id": categoryIDs[0%lenOfIDs], }, } } -func SampleTagIDsFactory() *map[string]interface{} { +func SampleTagIDsFactory(tagIDs *[]uuid.UUID) *map[string]interface{} { + tags := tagIDs + + if tags == nil { + tags = &[]uuid.UUID{uuid.New()} + } + return &map[string]interface{}{ - "tags": []uint{1, 2, 3}, + "tags": tags, } } -func CreateSetOfTags(t *testing.T, appAssert ExistingAppAssert) { +func CreateSetOfTags(t *testing.T, appAssert ExistingAppAssert) []uuid.UUID { categories := SampleCategoriesFactory() - tags := SampleTagsFactory() + categoryIDs := []uuid.UUID{} for _, category := range *categories { TestRequest{ Method: fiber.MethodPost, Path: "/api/v1/categories/", Body: &category, - }.TestOnStatus(t, &appAssert, 201) + }.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) + + fmt.Printf("\n\n\n\nTAGS: %s \n\n\n\n\n", tags) + + tagIDs := []uuid.UUID{} for _, tag := range *tags { TestRequest{ Method: fiber.MethodPost, Path: "/api/v1/tags/", Body: &tag, - }.TestOnStatus(t, &appAssert, 201) + }.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 uint) { +func AssertUserTagsRespDB(app TestApp, assert *assert.A, resp *http.Response, id uuid.UUID) { var respTags []models.Tag // Retrieve the tags from the response: @@ -640,21 +671,21 @@ func AssertUserTagsRespDB(app TestApp, assert *assert.A, resp *http.Response, id } } -func AssertSampleUserTagsRespDB(app TestApp, assert *assert.A, resp *http.Response) { - AssertUserTagsRespDB(app, assert, resp, 2) +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: 1}, {Name: "Test2", CategoryID: 2}}, + []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() + malformedTag := *SampleTagIDsFactory(nil) malformedTag["tags"] = tag TestRequest{ @@ -678,27 +709,31 @@ func TestCreateUserTagsFailsOnInvalidUserID(t *testing.T) { TestRequest{ Method: fiber.MethodPost, Path: fmt.Sprintf("/api/v1/users/%s/tags", badRequest), - Body: SampleTagIDsFactory(), + Body: SampleTagIDsFactory(nil), }.TestOnError(t, nil, errors.FailedToValidateID).Close() } } +type UUIDSlice []uuid.UUID + +var testUUID = uuid.New() + func TestCreateUserTagsFailsOnInvalidKey(t *testing.T) { - appAssert := CreateSampleUser(t) - + appAssert, uuid := CreateSampleUser(t, nil) + invalidBody := []map[string]interface{}{ { - "tag": []uint{1, 2, 3}, + "tag": UUIDSlice{testUUID, testUUID}, }, { "tagIDs": []uint{1, 2, 3}, }, } - + for _, body := range invalidBody { TestRequest{ Method: fiber.MethodPost, - Path: "/api/v1/users/2/tags/", + Path: fmt.Sprintf("/api/v1/users/%s/tags/", uuid), Body: &body, }.TestOnError(t, &appAssert, errors.FailedToValidateUserTags) } @@ -709,19 +744,19 @@ func TestCreateUserTagsFailsOnInvalidKey(t *testing.T) { func TestCreateUserTagsFailsOnNonExistentUser(t *testing.T) { TestRequest{ Method: fiber.MethodPost, - Path: "/api/v1/users/20/tags", - Body: SampleTagIDsFactory(), + 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 := CreateSampleUser(t) + appAssert, uuid := CreateSampleUser(t, nil) // Confirm adding non-existent tags does nothing: TestRequest{ Method: fiber.MethodPost, - Path: "/api/v1/users/2/tags/", - Body: SampleTagIDsFactory(), + Path: fmt.Sprintf("/api/v1/users/%s/tags/", uuid), + Body: SampleTagIDsFactory(nil), }.TestOnStatusAndDB(t, &appAssert, DBTesterWithStatus{ Status: fiber.StatusCreated, @@ -738,17 +773,19 @@ func TestCreateUserTagsWorks(t *testing.T) { ) // Create a set of tags: - CreateSetOfTags(t, appAssert) + tagUUIDs := CreateSetOfTags(t, appAssert) // Confirm adding real tags adds them to the user: TestRequest{ Method: fiber.MethodPost, - Path: "/api/v1/users/2/tags/", - Body: SampleTagIDsFactory(), + Path: fmt.Sprintf("/api/v1/users/%s/tags/", uuid), + Body: SampleTagIDsFactory(&tagUUIDs), }.TestOnStatusAndDB(t, &appAssert, DBTesterWithStatus{ - Status: fiber.StatusCreated, - DBTester: AssertSampleUserTagsRespDB, + Status: fiber.StatusCreated, + DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { + AssertSampleUserTagsRespDB(app, assert, resp, uuid) + }, }, ) @@ -758,16 +795,16 @@ func TestCreateUserTagsWorks(t *testing.T) { func TestGetUserTagsFailsOnNonExistentUser(t *testing.T) { TestRequest{ Method: fiber.MethodGet, - Path: "/api/v1/users/2/tags/", - }.TestOnError(t, nil, errors.UserNotFound) + Path: fmt.Sprintf("/api/v1/users/%s/tags/", uuid.New()), + }.TestOnError(t, nil, errors.UserNotFound).Close() } func TestGetUserTagsReturnsEmptyListWhenNoneAdded(t *testing.T) { - appAssert := CreateSampleUser(t) + appAssert, uuid := CreateSampleUser(t, nil) TestRequest{ Method: fiber.MethodGet, - Path: "/api/v1/users/2/tags/", + Path: fmt.Sprintf("/api/v1/users/%s/tags/", uuid), }.TestOnStatusAndDB(t, &appAssert, DBTesterWithStatus{ Status: 200, @@ -782,30 +819,35 @@ func TestGetUserTagsReturnsEmptyListWhenNoneAdded(t *testing.T) { }, }, ) + + appAssert.Close() } func TestGetUserTagsReturnsCorrectList(t *testing.T) { - appAssert := CreateSampleUser(t) + appAssert, uuid := CreateSampleUser(t, nil) // Create a set of tags: - CreateSetOfTags(t, appAssert) + tagUUIDs := CreateSetOfTags(t, appAssert) // Add the tags: TestRequest{ Method: fiber.MethodPost, - Path: "/api/v1/users/2/tags/", - Body: SampleTagIDsFactory(), + Path: fmt.Sprintf("/api/v1/users/%s/tags/", uuid), + Body: SampleTagIDsFactory(&tagUUIDs), }.TestOnStatus(t, &appAssert, 201) // Get the tags: TestRequest{ Method: fiber.MethodGet, - Path: "/api/v1/users/2/tags/", + Path: fmt.Sprintf("/api/v1/users/%s/tags/", uuid), }.TestOnStatusAndDB(t, &appAssert, DBTesterWithStatus{ - Status: fiber.StatusOK, - DBTester: AssertSampleUserTagsRespDB, + Status: fiber.StatusOK, + DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { + AssertSampleUserTagsRespDB(app, assert, resp, uuid) + }, }, ) -} + appAssert.Close() +} From f845bf94716c940bdef451917becb26be3759152 Mon Sep 17 00:00:00 2001 From: Alder Whiteford Date: Sat, 27 Jan 2024 18:10:26 -0500 Subject: [PATCH 06/13] Remove log call --- backend/tests/api/helpers.go | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/tests/api/helpers.go b/backend/tests/api/helpers.go index 5c68d9ad3..13a7a95dc 100644 --- a/backend/tests/api/helpers.go +++ b/backend/tests/api/helpers.go @@ -164,7 +164,6 @@ func (request TestRequest) Test(t *testing.T, existingAppAssert *ExistingAppAsse var req *http.Request if request.Body == nil { - t.Log("NO REQUEST BODY!") req = httptest.NewRequest(request.Method, address, nil) } else { bodyBytes, err := json.Marshal(request.Body) From e201bd2751204276d3d27ddc5e13eeaa35856713 Mon Sep 17 00:00:00 2001 From: Alder Whiteford Date: Sat, 27 Jan 2024 18:16:07 -0500 Subject: [PATCH 07/13] SAC-19 Break-up test --- backend/tests/api/user_test.go | 37 +++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/backend/tests/api/user_test.go b/backend/tests/api/user_test.go index 76120e464..720d8be1f 100644 --- a/backend/tests/api/user_test.go +++ b/backend/tests/api/user_test.go @@ -759,39 +759,44 @@ func TestCreateUserTagsFailsOnNonExistentUser(t *testing.T) { func TestCreateUserTagsWorks(t *testing.T) { appAssert, uuid := CreateSampleUser(t, nil) - // Confirm adding non-existent tags does nothing: + // 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(nil), + Body: SampleTagIDsFactory(&tagUUIDs), }.TestOnStatusAndDB(t, &appAssert, DBTesterWithStatus{ - Status: fiber.StatusCreated, + 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) + AssertSampleUserTagsRespDB(app, assert, resp, uuid) }, }, ) - // Create a set of tags: - tagUUIDs := CreateSetOfTags(t, appAssert) + appAssert.Close() +} - // Confirm adding real tags adds them to the user: +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(&tagUUIDs), + Body: SampleTagIDsFactory(nil), }.TestOnStatusAndDB(t, &appAssert, DBTesterWithStatus{ - Status: fiber.StatusCreated, + Status: fiber.StatusCreated, DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { - AssertSampleUserTagsRespDB(app, assert, resp, uuid) + var respTags []models.Tag + + err := json.NewDecoder(resp.Body).Decode(&respTags) + + assert.NilError(err) + + assert.Equal(len(respTags), 0) }, }, ) From d3ac8697e73a3a8e383ab37900aab5d02727c317 Mon Sep 17 00:00:00 2001 From: Alder Whiteford Date: Sat, 27 Jan 2024 18:17:15 -0500 Subject: [PATCH 08/13] Update backend/src/services/user.go Co-authored-by: Garrett Ladley <92384606+garrettladley@users.noreply.github.com> --- backend/src/services/user.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/services/user.go b/backend/src/services/user.go index 60fe931e7..4d9ac8915 100644 --- a/backend/src/services/user.go +++ b/backend/src/services/user.go @@ -121,7 +121,7 @@ func (u *UserService) GetUserTags(id string) ([]models.Tag, *errors.Error) { func (u *UserService) CreateUserTags(id string, tagIDs models.CreateUserTagsBody) ([]models.Tag, *errors.Error) { // Validate the id: - idAsInt, err := utilities.ValidateID(id) + idAsUUID, err := utilities.ValidateID(id) if err != nil { return nil, err } From 94a099dadc0d46208c73d505c617d70e3fadd794 Mon Sep 17 00:00:00 2001 From: Alder Whiteford Date: Sat, 27 Jan 2024 18:17:20 -0500 Subject: [PATCH 09/13] Update backend/src/services/user.go Co-authored-by: Garrett Ladley <92384606+garrettladley@users.noreply.github.com> --- backend/src/services/user.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/services/user.go b/backend/src/services/user.go index 4d9ac8915..678a6fd61 100644 --- a/backend/src/services/user.go +++ b/backend/src/services/user.go @@ -134,5 +134,5 @@ func (u *UserService) CreateUserTags(id string, tagIDs models.CreateUserTagsBody tags, err := transactions.GetTagsByIDs(u.DB, tagIDs.Tags) // Update the user to reflect the new tags: - return transactions.CreateUserTags(u.DB, *idAsInt, tags) + return transactions.CreateUserTags(u.DB, *idAsUUID, tags) } From 5b2dce8249c7ad227c42bac9c02e8edbfaaa4a78 Mon Sep 17 00:00:00 2001 From: Alder Whiteford Date: Sat, 27 Jan 2024 18:17:26 -0500 Subject: [PATCH 10/13] Update backend/tests/api/user_test.go Co-authored-by: Garrett Ladley <92384606+garrettladley@users.noreply.github.com> --- backend/tests/api/user_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/tests/api/user_test.go b/backend/tests/api/user_test.go index 720d8be1f..48e9b409a 100644 --- a/backend/tests/api/user_test.go +++ b/backend/tests/api/user_test.go @@ -623,7 +623,6 @@ func CreateSetOfTags(t *testing.T, appAssert ExistingAppAssert) []uuid.UUID { tags := SampleTagsFactory(categoryIDs) - fmt.Printf("\n\n\n\nTAGS: %s \n\n\n\n\n", tags) tagIDs := []uuid.UUID{} for _, tag := range *tags { From 1eb532f33de6201da539eeef2116f4aabe1962c9 Mon Sep 17 00:00:00 2001 From: Alder Whiteford Date: Sat, 27 Jan 2024 18:17:37 -0500 Subject: [PATCH 11/13] Update backend/tests/api/user_test.go Co-authored-by: Garrett Ladley <92384606+garrettladley@users.noreply.github.com> --- backend/tests/api/user_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/tests/api/user_test.go b/backend/tests/api/user_test.go index 48e9b409a..ff94bfcbe 100644 --- a/backend/tests/api/user_test.go +++ b/backend/tests/api/user_test.go @@ -845,7 +845,7 @@ func TestGetUserTagsReturnsCorrectList(t *testing.T) { Method: fiber.MethodPost, Path: fmt.Sprintf("/api/v1/users/%s/tags/", uuid), Body: SampleTagIDsFactory(&tagUUIDs), - }.TestOnStatus(t, &appAssert, 201) + }.TestOnStatus(t, &appAssert, fiber.StatusOK) // Get the tags: TestRequest{ From 882dd41a459d0de0d79c692b754be2774c47d204 Mon Sep 17 00:00:00 2001 From: Alder Whiteford Date: Sat, 27 Jan 2024 18:22:51 -0500 Subject: [PATCH 12/13] SAC-19 Error Code Bug --- backend/tests/api/user_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/tests/api/user_test.go b/backend/tests/api/user_test.go index ff94bfcbe..f1cd872d9 100644 --- a/backend/tests/api/user_test.go +++ b/backend/tests/api/user_test.go @@ -845,7 +845,7 @@ func TestGetUserTagsReturnsCorrectList(t *testing.T) { Method: fiber.MethodPost, Path: fmt.Sprintf("/api/v1/users/%s/tags/", uuid), Body: SampleTagIDsFactory(&tagUUIDs), - }.TestOnStatus(t, &appAssert, fiber.StatusOK) + }.TestOnStatus(t, &appAssert, fiber.StatusCreated) // Get the tags: TestRequest{ From 8871d5a3074bdf02b12c43a9c1d6d72486c71a52 Mon Sep 17 00:00:00 2001 From: Garrett Ladley <92384606+garrettladley@users.noreply.github.com> Date: Sat, 27 Jan 2024 18:23:59 -0500 Subject: [PATCH 13/13] Update dependabot.yml --- .github/dependabot.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0543f2852..0b40a82c5 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,11 +4,11 @@ updates: directory: ./ schedule: interval: weekly - - package-ecosystem: yarn + - package-ecosystem: npm directory: ./frontend/sac-mobile schedule: interval: weekly - - package-ecosystem: yarn + - package-ecosystem: npm directory: ./frontend/sac-web schedule: interval: weekly