diff --git a/backend/src/controllers/club.go b/backend/src/controllers/club.go index e9b3d433d..12b505dce 100644 --- a/backend/src/controllers/club.go +++ b/backend/src/controllers/club.go @@ -1,7 +1,7 @@ package controllers import ( - "strconv" + "fmt" "github.com/GenerateNU/sac/backend/src/errors" "github.com/GenerateNU/sac/backend/src/models" @@ -18,10 +18,17 @@ func NewClubController(clubService services.ClubServiceInterface) *ClubControlle } func (cl *ClubController) GetAllClubs(c *fiber.Ctx) error { - defaultLimit := 10 - defaultPage := 1 + var queryParams models.ClubQueryParams - clubs, err := cl.clubService.GetClubs(c.Query("limit", strconv.Itoa(defaultLimit)), c.Query("page", strconv.Itoa(defaultPage))) + queryParams.Limit = 10 // default limit + queryParams.Page = 1 // default page + + if err := c.QueryParser(&queryParams); err != nil { + fmt.Println(err) + return errors.FailedtoParseQueryParams.FiberError(c) + } + + clubs, err := cl.clubService.GetClubs(&queryParams) if err != nil { return err.FiberError(c) } diff --git a/backend/src/controllers/user_member.go b/backend/src/controllers/user_member.go index 35360e5bb..17a492d23 100644 --- a/backend/src/controllers/user_member.go +++ b/backend/src/controllers/user_member.go @@ -32,7 +32,7 @@ func (um *UserMemberController) DeleteMembership(c *fiber.Ctx) error { } func (um *UserMemberController) GetMembership(c *fiber.Ctx) error { - followers, err := um.clubMemberService.GetMembership(c.Params("clubID")) + followers, err := um.clubMemberService.GetMembership(c.Params("userID")) if err != nil { return err.FiberError(c) } diff --git a/backend/src/errors/common.go b/backend/src/errors/common.go index 343594d5b..5b93b0b14 100644 --- a/backend/src/errors/common.go +++ b/backend/src/errors/common.go @@ -7,6 +7,10 @@ var ( StatusCode: fiber.StatusBadRequest, Message: "failed to parse request body", } + FailedtoParseQueryParams = Error{ + StatusCode: fiber.StatusBadRequest, + Message: "failed to parse query params", + } FailedToValidateID = Error{ StatusCode: fiber.StatusBadRequest, Message: "failed to validate id", diff --git a/backend/src/errors/user.go b/backend/src/errors/user.go index 0ec60ad53..04a52854a 100644 --- a/backend/src/errors/user.go +++ b/backend/src/errors/user.go @@ -51,8 +51,16 @@ var ( StatusCode: fiber.StatusInternalServerError, Message: "failed to get user memberships", } + UserNotMemberOfClub = Error{ + StatusCode: fiber.StatusNotFound, + Message: "user not member of club", + } FailedToGetUserFollowing = Error{ StatusCode: fiber.StatusInternalServerError, Message: "failed to get user following", } + UserNotFollowingClub = Error{ + StatusCode: fiber.StatusNotFound, + Message: "user not following club", + } ) diff --git a/backend/src/models/club.go b/backend/src/models/club.go index 77f1dfc23..64bf85ffd 100644 --- a/backend/src/models/club.go +++ b/backend/src/models/club.go @@ -1,6 +1,9 @@ package models import ( + "fmt" + "strings" + "github.com/google/uuid" "gorm.io/gorm" ) @@ -38,7 +41,7 @@ type Club struct { Logo string `gorm:"type:varchar(255);default:NULL" json:"logo" validate:"omitempty,http_url,s3_url,max=255"` // S3 URL Parent *uuid.UUID `gorm:"foreignKey:Parent" json:"-" validate:"uuid4"` - Tag []Tag `gorm:"many2many:club_tags;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"-"` + Tag []Tag `gorm:"many2many:club_tags;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"tags,omitempty" validate:"-"` // User Admin []User `gorm:"many2many:user_club_admins;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"required"` Member []User `gorm:"many2many:user_club_members;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"required"` @@ -79,6 +82,38 @@ type CreateClubTagsRequestBody struct { Tags []uuid.UUID `json:"tags" validate:"required"` } +type ClubQueryParams struct { + Tags []string `query:"tags"` + MinMembers int `query:"min_members"` + MaxMembers int `query:"max_members"` + RecruitmentCycle *RecruitmentCycle `query:"recruitment_cycle"` + IsRecruiting *bool `query:"is_recruiting"` + Limit int `query:"limit"` + Page int `query:"page"` +} + +func (cqp *ClubQueryParams) IntoWhere() string { + conditions := make([]string, 0) + + if cqp.MinMembers != 0 { + conditions = append(conditions, fmt.Sprintf("num_members >= %d", cqp.MinMembers)) + } + if cqp.MaxMembers != 0 { + conditions = append(conditions, fmt.Sprintf("num_members <= %d", cqp.MaxMembers)) + } + if cqp.RecruitmentCycle != nil { + conditions = append(conditions, fmt.Sprintf("recruitment_cycle = '%s'", *cqp.RecruitmentCycle)) + } + if cqp.IsRecruiting != nil { + conditions = append(conditions, fmt.Sprintf("is_recruiting = %t", *cqp.IsRecruiting)) + } + + if len(conditions) == 0 { + return "" + } + return "WHERE " + strings.Join(conditions, " AND ") +} + func (c *Club) AfterCreate(tx *gorm.DB) (err error) { tx.Model(&c).Update("num_members", c.NumMembers+1) return diff --git a/backend/src/server/routes/club_member.go b/backend/src/server/routes/club_member.go index 2e23ae36e..7096c19fa 100644 --- a/backend/src/server/routes/club_member.go +++ b/backend/src/server/routes/club_member.go @@ -9,8 +9,8 @@ import ( func ClubMember(clubsIDRouter fiber.Router, clubMemberService services.ClubMemberServiceInterface) { clubMemberController := controllers.NewClubMemberController(clubMemberService) - clubMember := clubsIDRouter.Group("/member") + clubMember := clubsIDRouter.Group("/members") - // api/v1/clubs/:clubID/member/* + // api/v1/clubs/:clubID/members/* clubMember.Get("/", clubMemberController.GetClubMembers) } diff --git a/backend/src/server/routes/club_tag.go b/backend/src/server/routes/club_tag.go index 4d570b6b9..5940a5afd 100644 --- a/backend/src/server/routes/club_tag.go +++ b/backend/src/server/routes/club_tag.go @@ -9,7 +9,7 @@ import ( func ClubTag(router fiber.Router, clubTagService services.ClubTagServiceInterface) { clubTagController := controllers.NewClubTagController(clubTagService) - clubTags := router.Group("/:clubID/tags") + clubTags := router.Group("/tags") clubTags.Post("/", clubTagController.CreateClubTags) clubTags.Get("/", clubTagController.GetClubTags) diff --git a/backend/src/server/routes/user_member.go b/backend/src/server/routes/user_member.go index a606dccfa..c52e0519d 100644 --- a/backend/src/server/routes/user_member.go +++ b/backend/src/server/routes/user_member.go @@ -12,7 +12,7 @@ func UserMember(usersRouter fiber.Router, userMembershipService services.UserMem userMember := usersRouter.Group("/:userID/member") // api/v1/users/:userID/member/* - userMember.Post("/", userMemberController.CreateMembership) - userMember.Delete("/", userMemberController.DeleteMembership) + userMember.Post("/:clubID", userMemberController.CreateMembership) + userMember.Delete("/:clubID", userMemberController.DeleteMembership) userMember.Get("/", userMemberController.GetMembership) } diff --git a/backend/src/services/club.go b/backend/src/services/club.go index c05610c1f..ff2c56669 100644 --- a/backend/src/services/club.go +++ b/backend/src/services/club.go @@ -11,7 +11,7 @@ import ( ) type ClubServiceInterface interface { - GetClubs(limit string, page string) ([]models.Club, *errors.Error) + GetClubs(queryParams *models.ClubQueryParams) ([]models.Club, *errors.Error) GetClub(id string) (*models.Club, *errors.Error) CreateClub(clubBody models.CreateClubRequestBody) (*models.Club, *errors.Error) UpdateClub(id string, clubBody models.UpdateClubRequestBody) (*models.Club, *errors.Error) @@ -27,20 +27,16 @@ func NewClubService(db *gorm.DB, validate *validator.Validate) *ClubService { return &ClubService{DB: db, Validate: validate} } -func (c *ClubService) GetClubs(limit string, page string) ([]models.Club, *errors.Error) { - limitAsInt, err := utilities.ValidateNonNegative(limit) - if err != nil { +func (c *ClubService) GetClubs(queryParams *models.ClubQueryParams) ([]models.Club, *errors.Error) { + if queryParams.Limit < 0 { return nil, &errors.FailedToValidateLimit } - pageAsInt, err := utilities.ValidateNonNegative(page) - if err != nil { + if queryParams.Page < 0 { return nil, &errors.FailedToValidatePage } - offset := (*pageAsInt - 1) * *limitAsInt - - return transactions.GetClubs(c.DB, *limitAsInt, offset) + return transactions.GetClubs(c.DB, queryParams) } func (c *ClubService) CreateClub(clubBody models.CreateClubRequestBody) (*models.Club, *errors.Error) { diff --git a/backend/src/transactions/club.go b/backend/src/transactions/club.go index d767adff8..2fa0f0708 100644 --- a/backend/src/transactions/club.go +++ b/backend/src/transactions/club.go @@ -25,9 +25,28 @@ func GetAdminIDs(db *gorm.DB, clubID uuid.UUID) ([]uuid.UUID, *errors.Error) { return adminUUIDs, nil } -func GetClubs(db *gorm.DB, limit int, offset int) ([]models.Club, *errors.Error) { +func GetClubs(db *gorm.DB, queryParams *models.ClubQueryParams) ([]models.Club, *errors.Error) { + query := db.Model(&models.Club{}) + + if queryParams.Tags != nil && len(queryParams.Tags) > 0 { + query = query.Preload("Tags") + } + + for key, value := range queryParams.IntoWhere() { + query = query.Where(key, value) + } + + if queryParams.Tags != nil && len(queryParams.Tags) > 0 { + query = query.Joins("JOIN club_tags ON club_tags.club_id = clubs.id"). + Where("club_tags.tag_id IN ?", queryParams.Tags). + Group("clubs.id") // ensure unique club records + } + var clubs []models.Club - result := db.Limit(limit).Offset(offset).Find(&clubs) + + offset := (queryParams.Page - 1) * queryParams.Limit + + result := query.Limit(queryParams.Limit).Offset(offset).Find(&clubs) if result.Error != nil { return nil, &errors.FailedToGetClubs } @@ -38,7 +57,7 @@ func GetClubs(db *gorm.DB, limit int, offset int) ([]models.Club, *errors.Error) func CreateClub(db *gorm.DB, userId uuid.UUID, club models.Club) (*models.Club, *errors.Error) { user, err := GetUser(db, userId) if err != nil { - return nil, &errors.UserNotFound + return nil, err } tx := db.Begin() @@ -61,9 +80,16 @@ func CreateClub(db *gorm.DB, userId uuid.UUID, club models.Club) (*models.Club, return &club, nil } -func GetClub(db *gorm.DB, id uuid.UUID) (*models.Club, *errors.Error) { +func GetClub(db *gorm.DB, id uuid.UUID, preloads ...OptionalQuery) (*models.Club, *errors.Error) { var club models.Club - if err := db.First(&club, id).Error; err != nil { + + query := db + + for _, preload := range preloads { + query = preload(query) + } + + if err := query.First(&club, id).Error; err != nil { if stdliberrors.Is(err, gorm.ErrRecordNotFound) { return nil, &errors.ClubNotFound } else { diff --git a/backend/src/transactions/club_follower.go b/backend/src/transactions/club_follower.go index 4e6f4c10d..2d59e7363 100644 --- a/backend/src/transactions/club_follower.go +++ b/backend/src/transactions/club_follower.go @@ -10,12 +10,12 @@ import ( func GetClubFollowers(db *gorm.DB, clubID uuid.UUID, limit int, page int) ([]models.User, *errors.Error) { club, err := GetClub(db, clubID) if err != nil { - return nil, &errors.ClubNotFound + return nil, err } var users []models.User - if err := db.Model(&club).Association("Followers").Find(&users); err != nil { + if err := db.Model(&club).Association("Follower").Find(&users); err != nil { return nil, &errors.FailedToGetClubFollowers } diff --git a/backend/src/transactions/club_member.go b/backend/src/transactions/club_member.go index 8b8caf9ad..6c1a87766 100644 --- a/backend/src/transactions/club_member.go +++ b/backend/src/transactions/club_member.go @@ -11,12 +11,12 @@ import ( func GetClubMembers(db *gorm.DB, clubID uuid.UUID, limit int, page int) ([]models.User, *errors.Error) { club, err := GetClub(db, clubID) if err != nil { - return nil, &errors.ClubNotFound + return nil, err } var users []models.User - if err := db.Model(&club).Association("Members").Find(&users); err != nil { + if err := db.Model(&club).Association("Member").Find(&users); err != nil { return nil, &errors.FailedToGetClubMembers } diff --git a/backend/src/transactions/club_tag.go b/backend/src/transactions/club_tag.go index 03127e89b..36ff31951 100644 --- a/backend/src/transactions/club_tag.go +++ b/backend/src/transactions/club_tag.go @@ -8,27 +8,25 @@ import ( "gorm.io/gorm" ) -// Create tags for a club func CreateClubTags(db *gorm.DB, id uuid.UUID, tags []models.Tag) ([]models.Tag, *errors.Error) { - user, err := GetClub(db, id) + user, err := GetClub(db, id, PreloadTag()) if err != nil { - return nil, &errors.UserNotFound + return nil, err } - if err := db.Model(&user).Association("Tag").Replace(tags); err != nil { + if err := db.Model(&user).Association("Tag").Append(tags); err != nil { return nil, &errors.FailedToUpdateUser } return tags, nil } -// Get tags for a club func GetClubTags(db *gorm.DB, id uuid.UUID) ([]models.Tag, *errors.Error) { var tags []models.Tag club, err := GetClub(db, id) if err != nil { - return nil, &errors.ClubNotFound + return nil, err } if err := db.Model(&club).Association("Tag").Find(&tags); err != nil { @@ -37,16 +35,15 @@ func GetClubTags(db *gorm.DB, id uuid.UUID) ([]models.Tag, *errors.Error) { return tags, nil } -// Delete tag for a club func DeleteClubTag(db *gorm.DB, id uuid.UUID, tagId uuid.UUID) *errors.Error { club, err := GetClub(db, id) if err != nil { - return &errors.ClubNotFound + return err } tag, err := GetTag(db, tagId) if err != nil { - return &errors.TagNotFound + return err } if err := db.Model(&club).Association("Tag").Delete(&tag); err != nil { diff --git a/backend/src/transactions/preloaders.go b/backend/src/transactions/preloaders.go new file mode 100644 index 000000000..39f3c88c2 --- /dev/null +++ b/backend/src/transactions/preloaders.go @@ -0,0 +1,23 @@ +package transactions + +import "gorm.io/gorm" + +type OptionalQuery func(*gorm.DB) *gorm.DB + +func PreloadFollwer() OptionalQuery { + return func(db *gorm.DB) *gorm.DB { + return db.Preload("Follower") + } +} + +func PreloadMember() OptionalQuery { + return func(db *gorm.DB) *gorm.DB { + return db.Preload("Member") + } +} + +func PreloadTag() OptionalQuery { + return func(db *gorm.DB) *gorm.DB { + return db.Preload("Tag") + } +} diff --git a/backend/src/transactions/user.go b/backend/src/transactions/user.go index 6ce8eb878..dae4ecd26 100644 --- a/backend/src/transactions/user.go +++ b/backend/src/transactions/user.go @@ -42,9 +42,16 @@ func GetUsers(db *gorm.DB, limit int, offset int) ([]models.User, *errors.Error) return users, nil } -func GetUser(db *gorm.DB, id uuid.UUID) (*models.User, *errors.Error) { +func GetUser(db *gorm.DB, id uuid.UUID, preloads ...OptionalQuery) (*models.User, *errors.Error) { var user models.User - if err := db.Omit("password_hash").First(&user, id).Error; err != nil { + + query := db + + for _, preload := range preloads { + query = preload(query) + } + + if err := query.Omit("password_hash").First(&user, id).Error; err != nil { if stdliberrors.Is(err, gorm.ErrRecordNotFound) { return nil, &errors.UserNotFound } else { diff --git a/backend/src/transactions/user_follower.go b/backend/src/transactions/user_follower.go index 2a1d74a9d..77dde3c69 100644 --- a/backend/src/transactions/user_follower.go +++ b/backend/src/transactions/user_follower.go @@ -1,6 +1,8 @@ package transactions import ( + "slices" + "github.com/GenerateNU/sac/backend/src/errors" "github.com/GenerateNU/sac/backend/src/models" "github.com/google/uuid" @@ -10,15 +12,15 @@ import ( func CreateFollowing(db *gorm.DB, userId uuid.UUID, clubId uuid.UUID) *errors.Error { user, err := GetUser(db, userId) if err != nil { - return &errors.UserNotFound + return err } club, err := GetClub(db, clubId) if err != nil { - return &errors.ClubNotFound + return err } - if err := db.Model(&user).Association("Follower").Append(&club); err != nil { + if err := db.Model(&user).Association("Follower").Append(club); err != nil { return &errors.FailedToUpdateUser } @@ -26,17 +28,30 @@ func CreateFollowing(db *gorm.DB, userId uuid.UUID, clubId uuid.UUID) *errors.Er } func DeleteFollowing(db *gorm.DB, userId uuid.UUID, clubId uuid.UUID) *errors.Error { - user, err := GetUser(db, userId) + user, err := GetUser(db, userId, PreloadFollwer()) if err != nil { - return &errors.UserNotFound + return err } - club, err := GetClub(db, clubId) + + club, err := GetClub(db, clubId, PreloadFollwer()) if err != nil { - return &errors.ClubNotFound + return err + } + + userFollowingClubIDs := make([]uuid.UUID, len(user.Follower)) + + for i, club := range user.Follower { + userFollowingClubIDs[i] = club.ID + } + + if !slices.Contains(userFollowingClubIDs, club.ID) { + return &errors.UserNotFollowingClub } + if err := db.Model(&user).Association("Follower").Delete(club); err != nil { return &errors.FailedToUpdateUser } + return nil } @@ -45,11 +60,12 @@ func GetClubFollowing(db *gorm.DB, userId uuid.UUID) ([]models.Club, *errors.Err user, err := GetUser(db, userId) if err != nil { - return nil, &errors.UserNotFound + return nil, err } if err := db.Model(&user).Association("Follower").Find(&clubs); err != nil { return nil, &errors.FailedToGetUserFollowing } + return clubs, nil } diff --git a/backend/src/transactions/user_member.go b/backend/src/transactions/user_member.go index fe12859b6..6b079862c 100644 --- a/backend/src/transactions/user_member.go +++ b/backend/src/transactions/user_member.go @@ -1,6 +1,8 @@ package transactions import ( + "slices" + "github.com/GenerateNU/sac/backend/src/errors" "github.com/GenerateNU/sac/backend/src/models" "github.com/google/uuid" @@ -10,15 +12,15 @@ import ( func CreateMember(db *gorm.DB, userId uuid.UUID, clubId uuid.UUID) *errors.Error { user, err := GetUser(db, userId) if err != nil { - return &errors.UserNotFound + return err } club, err := GetClub(db, clubId) if err != nil { - return &errors.ClubNotFound + return err } - if err := db.Model(&user).Association("Member").Append(&club); err != nil { + if err := db.Model(&user).Association("Member").Append(club); err != nil { return &errors.FailedToUpdateUser } @@ -26,18 +28,30 @@ func CreateMember(db *gorm.DB, userId uuid.UUID, clubId uuid.UUID) *errors.Error } func DeleteMember(db *gorm.DB, userId uuid.UUID, clubId uuid.UUID) *errors.Error { - user, err := GetUser(db, userId) + user, err := GetUser(db, userId, PreloadMember()) if err != nil { - return &errors.UserNotFound + return err } - club, err := GetClub(db, clubId) + club, err := GetClub(db, clubId, PreloadMember()) if err != nil { - return &errors.ClubNotFound + return err + } + + userMemberClubIDs := make([]uuid.UUID, len(user.Member)) + + for i, club := range user.Member { + userMemberClubIDs[i] = club.ID + } + + if !slices.Contains(userMemberClubIDs, club.ID) { + return &errors.UserNotMemberOfClub } + if err := db.Model(&user).Association("Member").Delete(club); err != nil { return &errors.FailedToUpdateUser } + return nil } @@ -46,7 +60,7 @@ func GetClubMembership(db *gorm.DB, userId uuid.UUID) ([]models.Club, *errors.Er user, err := GetUser(db, userId) if err != nil { - return nil, &errors.UserNotFound + return nil, err } if err := db.Model(&user).Association("Member").Find(&clubs); err != nil { diff --git a/backend/src/transactions/user_tag.go b/backend/src/transactions/user_tag.go index a8c1dd01d..3df46a0b8 100644 --- a/backend/src/transactions/user_tag.go +++ b/backend/src/transactions/user_tag.go @@ -12,7 +12,7 @@ func GetUserTags(db *gorm.DB, id uuid.UUID) ([]models.Tag, *errors.Error) { user, err := GetUser(db, id) if err != nil { - return nil, &errors.UserNotFound + return nil, err } if err := db.Model(&user).Association("Tag").Find(&tags); err != nil { @@ -22,12 +22,12 @@ func GetUserTags(db *gorm.DB, id uuid.UUID) ([]models.Tag, *errors.Error) { } func CreateUserTags(db *gorm.DB, id uuid.UUID, tags []models.Tag) ([]models.Tag, *errors.Error) { - user, err := GetUser(db, id) + user, err := GetUser(db, id, PreloadTag()) if err != nil { - return nil, &errors.UserNotFound + return nil, err } - if err := db.Model(&user).Association("Tag").Replace(tags); err != nil { + if err := db.Model(&user).Association("Tag").Append(tags); err != nil { return nil, &errors.FailedToUpdateUser } diff --git a/backend/tests/api/category_tag_test.go b/backend/tests/api/category_tag_test.go index 888c28147..d7165c885 100644 --- a/backend/tests/api/category_tag_test.go +++ b/backend/tests/api/category_tag_test.go @@ -76,7 +76,7 @@ func TestGetCategoryTagsFailsCategoryBadRequest(t *testing.T) { } for _, badRequest := range badRequests { - appAssert.TestOnError( + appAssert = appAssert.TestOnError( h.TestRequest{ Method: fiber.MethodGet, Path: fmt.Sprintf("/api/v1/categories/%s/tags", badRequest), @@ -140,7 +140,7 @@ func TestGetCategoryTagFailsCategoryBadRequest(t *testing.T) { } for _, badRequest := range badRequests { - appAssert.TestOnError( + appAssert = appAssert.TestOnError( h.TestRequest{ Method: fiber.MethodGet, Path: fmt.Sprintf("/api/v1/categories/%s/tags/%s", badRequest, tagUUID), @@ -164,7 +164,7 @@ func TestGetCategoryTagFailsTagBadRequest(t *testing.T) { } for _, badRequest := range badRequests { - appAssert.TestOnError( + appAssert = appAssert.TestOnError( h.TestRequest{ Method: fiber.MethodGet, Path: fmt.Sprintf("/api/v1/categories/%s/tags/%s", categoryUUID, badRequest), diff --git a/backend/tests/api/category_test.go b/backend/tests/api/category_test.go index 921adf4bf..6bf679046 100644 --- a/backend/tests/api/category_test.go +++ b/backend/tests/api/category_test.go @@ -177,7 +177,7 @@ func TestCreateCategoryFailsIfCategoryWithThatNameAlreadyExists(t *testing.T) { modifiedSampleCategoryBody := *SampleCategoryFactory() modifiedSampleCategoryBody["name"] = permutation - existingAppAssert.TestOnErrorAndTester( + existingAppAssert = existingAppAssert.TestOnErrorAndTester( h.TestRequest{ Method: fiber.MethodPost, Path: "/api/v1/categories/", @@ -224,7 +224,7 @@ func TestGetCategoryFailsBadRequest(t *testing.T) { } for _, badRequest := range badRequests { - appAssert.TestOnError( + appAssert = appAssert.TestOnError( h.TestRequest{ Method: fiber.MethodGet, Path: fmt.Sprintf("/api/v1/categories/%s", badRequest), @@ -370,7 +370,7 @@ func TestUpdateCategoryFailsBadRequest(t *testing.T) { } for _, badRequest := range badRequests { - appAssert.TestOnError( + appAssert = appAssert.TestOnError( h.TestRequest{ Method: fiber.MethodPatch, Path: fmt.Sprintf("/api/v1/categories/%s", badRequest), @@ -412,7 +412,7 @@ func TestDeleteCategoryFailsBadRequest(t *testing.T) { } for _, badRequest := range badRequests { - existingAppAssert.TestOnErrorAndTester( + existingAppAssert = existingAppAssert.TestOnErrorAndTester( h.TestRequest{ Method: fiber.MethodDelete, Path: fmt.Sprintf("/api/v1/categories/%s", badRequest), diff --git a/backend/tests/api/club_follower_test.go b/backend/tests/api/club_follower_test.go index ca8701d29..a7af140db 100644 --- a/backend/tests/api/club_follower_test.go +++ b/backend/tests/api/club_follower_test.go @@ -1 +1,58 @@ package tests + +import ( + "fmt" + "net/http" + "testing" + + "github.com/GenerateNU/sac/backend/src/models" + h "github.com/GenerateNU/sac/backend/tests/api/helpers" + "github.com/goccy/go-json" + "github.com/gofiber/fiber/v2" +) + +func TestClubFollowerWorks(t *testing.T) { + appAssert, _, clubUUID := CreateSampleClub(h.InitTest(t)) + + appAssert.TestOnStatus( + h.TestRequest{ + Method: fiber.MethodPost, + Path: fmt.Sprintf("/api/v1/users/:userID/follower/%s", clubUUID), + Role: &models.Super, + TestUserIDReplaces: h.StringToPointer(":userID"), + }, + fiber.StatusCreated, + ).TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/clubs/%s/followers", clubUUID), + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusOK, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var followers []models.User + + err := json.NewDecoder(resp.Body).Decode(&followers) + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(1, len(followers)) + + var club models.Club + + err = eaa.App.Conn.Where("id = ?", clubUUID).First(&club).Error + + eaa.Assert.NilError(err) + + var dbFollowers []models.User + + err = eaa.App.Conn.Model(&club).Association("Follower").Find(&dbFollowers) + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(len(dbFollowers), len(followers)) + }, + }, + ).Close() +} diff --git a/backend/tests/api/club_member_test.go b/backend/tests/api/club_member_test.go new file mode 100644 index 000000000..acc426e18 --- /dev/null +++ b/backend/tests/api/club_member_test.go @@ -0,0 +1,58 @@ +package tests + +import ( + "fmt" + "net/http" + "testing" + + "github.com/GenerateNU/sac/backend/src/models" + h "github.com/GenerateNU/sac/backend/tests/api/helpers" + "github.com/goccy/go-json" + "github.com/gofiber/fiber/v2" +) + +func TestClubMemberWorks(t *testing.T) { + appAssert, _, clubUUID := CreateSampleClub(h.InitTest(t)) + + appAssert.TestOnStatus( + h.TestRequest{ + Method: fiber.MethodPost, + Path: fmt.Sprintf("/api/v1/users/:userID/member/%s", clubUUID), + Role: &models.Super, + TestUserIDReplaces: h.StringToPointer(":userID"), + }, + fiber.StatusCreated, + ).TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/clubs/%s/members", clubUUID), + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusOK, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var members []models.User + + err := json.NewDecoder(resp.Body).Decode(&members) + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(1, len(members)) + + var club models.Club + + err = eaa.App.Conn.Where("id = ?", clubUUID).First(&club).Error + + eaa.Assert.NilError(err) + + var dbMembers []models.User + + err = eaa.App.Conn.Model(&club).Association("Member").Find(&dbMembers) + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(len(dbMembers), len(members)) + }, + }, + ).Close() +} diff --git a/backend/tests/api/club_tag_test.go b/backend/tests/api/club_tag_test.go index 47764f445..250d335ed 100644 --- a/backend/tests/api/club_tag_test.go +++ b/backend/tests/api/club_tag_test.go @@ -1,201 +1,269 @@ package tests -// func AssertClubTagsRespDB(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 club connected to the tags: -// var dbClub models.Club -// err = app.Conn.First(&dbClub, id).Error - -// assert.NilError(err) - -// // Retrieve the tags in the bridge table associated with the club: -// var dbTags []models.Tag -// err = app.Conn.Model(&dbClub).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 TestCreateClubTagsWorks(t *testing.T) { -// appAssert, _, uuid := CreateSampleClub(t, nil) - -// // Create a set of tags: -// tagUUIDs := CreateSetOfTags(t, appAssert) - -// // Confirm adding real tags adds them to the club: -// TestRequest{ -// Method: fiber.MethodPost, -// Path: fmt.Sprintf("/api/v1/clubs/%s/tags/", uuid), -// Body: SampleTagIDsFactory(&tagUUIDs), -// }.TestOnStatusAndDB(t, &appAssert, -// DBTesterWithStatus{ -// Status: fiber.StatusCreated, -// DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { -// AssertClubTagsRespDB(app, assert, resp, uuid) -// }, -// }, -// ) - -// appAssert.Close() -// } - -// // func TestCreateClubTagsFailsOnInvalidDataType(t *testing.T) { -// // _, _, uuid := CreateSampleClub(t, nil) - -// // // 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: fmt.Sprintf("/api/v1/clubs/%s/tags/", uuid), -// // Body: &malformedTag, -// // }.TestOnError(t, nil, errors.FailedToParseRequestBody).Close() -// // } -// // } - -// func TestCreateClubTagsFailsOnInvalidClubID(t *testing.T) { -// badRequests := []string{ -// "0", -// "-1", -// "1.1", -// "foo", -// "null", -// } - -// for _, badRequest := range badRequests { -// TestRequest{ -// Method: fiber.MethodPost, -// Path: fmt.Sprintf("/api/v1/clubs/%s/tags", badRequest), -// Body: SampleTagIDsFactory(nil), -// }.TestOnError(t, nil, errors.FailedToValidateID).Close() -// } -// } - -// func TestCreateClubTagsFailsOnInvalidKey(t *testing.T) { -// appAssert, _, uuid := CreateSampleClub(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/clubs/%s/tags/", uuid), -// Body: &body, -// }.TestOnError(t, &appAssert, errors.FailedToValidateClubTags) -// } - -// appAssert.Close() -// } - -// func TestCreateClubTagsNoneAddedIfInvalid(t *testing.T) { -// appAssert, _, uuid := CreateSampleClub(t, nil) - -// TestRequest{ -// Method: fiber.MethodPost, -// Path: fmt.Sprintf("/api/v1/clubs/%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 TestGetClubTagsFailsOnNonExistentClub(t *testing.T) { -// TestRequest{ -// Method: fiber.MethodGet, -// Path: fmt.Sprintf("/api/v1/clubs/%s/tags/", uuid.New()), -// }.TestOnError(t, nil, errors.ClubNotFound).Close() -// } - -// func TestGetClubTagsReturnsEmptyListWhenNoneAdded(t *testing.T) { -// appAssert, _, uuid := CreateSampleClub(t, nil) - -// TestRequest{ -// Method: fiber.MethodGet, -// Path: fmt.Sprintf("/api/v1/clubs/%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 TestGetClubTagsReturnsCorrectList(t *testing.T) { -// appAssert, _, uuid := CreateSampleClub(t, nil) - -// // Create a set of tags: -// tagUUIDs := CreateSetOfTags(t, appAssert) - -// // Add the tags: -// TestRequest{ -// Method: fiber.MethodPost, -// Path: fmt.Sprintf("/api/v1/clubs/%s/tags/", uuid), -// Body: SampleTagIDsFactory(&tagUUIDs), -// }.TestOnStatus(t, &appAssert, fiber.StatusCreated) - -// // Get the tags: -// TestRequest{ -// Method: fiber.MethodGet, -// Path: fmt.Sprintf("/api/v1/clubs/%s/tags/", uuid), -// }.TestOnStatusAndDB(t, &appAssert, -// DBTesterWithStatus{ -// Status: fiber.StatusOK, -// DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { -// AssertClubTagsRespDB(app, assert, resp, uuid) -// }, -// }, -// ) - -// appAssert.Close() -// } +import ( + "fmt" + "net/http" + "testing" + + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/models" + h "github.com/GenerateNU/sac/backend/tests/api/helpers" + "github.com/goccy/go-json" + "github.com/gofiber/fiber/v2" + "github.com/google/uuid" +) + +func AssertClubTagsRespDB(eaa h.ExistingAppAssert, resp *http.Response, id uuid.UUID) { + var respTags []models.Tag + + err := json.NewDecoder(resp.Body).Decode(&respTags) + + eaa.Assert.NilError(err) + + var dbClub models.Club + + err = eaa.App.Conn.First(&dbClub, id).Error + + eaa.Assert.NilError(err) + + var dbTags []models.Tag + + err = eaa.App.Conn.Model(&dbClub).Association("Tag").Find(&dbTags) + + eaa.Assert.NilError(err) + + for i, respTag := range respTags { + eaa.Assert.Equal(respTag.ID, dbTags[i].ID) + eaa.Assert.Equal(respTag.Name, dbTags[i].Name) + eaa.Assert.Equal(respTag.CategoryID, dbTags[i].CategoryID) + } +} + +func AssertSampleClubTagsRespDB(eaa h.ExistingAppAssert, resp *http.Response, uuid uuid.UUID) { + AssertClubTagsRespDB(eaa, resp, uuid) +} + +func TestCreateClubTagsFailsOnInvalidDataType(t *testing.T) { + appAssert, _, clubUUID := CreateSampleClub(h.InitTest(t)) + + 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}, + } + + for _, tag := range invalidTags { + malformedTag := *SampleTagIDsFactory(nil) + malformedTag["tags"] = tag + + appAssert = appAssert.TestOnError( + h.TestRequest{ + Method: fiber.MethodPost, + Path: fmt.Sprintf("/api/v1/clubs/%s/tags/", clubUUID), + Body: &malformedTag, + Role: &models.Super, + }, + errors.FailedToParseRequestBody, + ) + } + + appAssert.Close() +} + +func TestCreateClubTagsFailsOnInvalidUserID(t *testing.T) { + appAssert := h.InitTest(t) + + badRequests := []string{ + "0", + "-1", + "1.1", + "foo", + "null", + } + + for _, badRequest := range badRequests { + appAssert = appAssert.TestOnError( + h.TestRequest{ + Method: fiber.MethodPost, + Path: fmt.Sprintf("/api/v1/clubs/%s/tags", badRequest), + Body: SampleTagIDsFactory(nil), + Role: &models.Student, + }, + errors.FailedToValidateID, + ) + } + + appAssert.Close() +} + +func TestCreateClubTagsFailsOnInvalidKey(t *testing.T) { + appAssert, _, clubUUID := CreateSampleClub(h.InitTest(t)) + + invalidBody := []map[string]interface{}{ + { + "tag": UUIDSlice{uuid.New(), uuid.New()}, + }, + { + "tagIDs": []uint{1, 2, 3}, + }, + } + + for _, body := range invalidBody { + body := body + appAssert = appAssert.TestOnError( + h.TestRequest{ + Method: fiber.MethodPost, + Path: fmt.Sprintf("/api/v1/clubs/%s/tags/", clubUUID), + Body: &body, + Role: &models.Student, + }, + errors.FailedToValidateClubTags, + ) + } + + appAssert.Close() +} + +func TestCreateClubTagsFailsOnNonExistentClub(t *testing.T) { + uuid := uuid.New() + + h.InitTest(t).TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodPost, + Path: fmt.Sprintf("/api/v1/clubs/%s/tags/", uuid), + Body: SampleTagIDsFactory(nil), + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.ClubNotFound, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var dbClub models.Club + + err := eaa.App.Conn.First(&dbClub, uuid).Error + + eaa.Assert.Assert(err != nil) + }, + }, + ).Close() +} + +func TestCreateClubTagsWorks(t *testing.T) { + appAssert, _, clubUUID := CreateSampleClub(h.InitTest(t)) + tagUUIDs, appAssert := CreateSetOfTags(appAssert) + + appAssert.TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodPost, + Path: fmt.Sprintf("/api/v1/clubs/%s/tags/", clubUUID), + Body: SampleTagIDsFactory(&tagUUIDs), + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusCreated, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + AssertSampleClubTagsRespDB(eaa, resp, clubUUID) + }, + }, + ) + + appAssert.Close() +} + +func TestCreateClubTagsNoneAddedIfInvalid(t *testing.T) { + appAssert, _, clubUUID := CreateSampleClub(h.InitTest(t)) + + appAssert.TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodPost, + Path: fmt.Sprintf("/api/v1/clubs/%s/tags/", clubUUID), + Body: SampleTagIDsFactory(nil), + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusCreated, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var respTags []models.Tag + + err := json.NewDecoder(resp.Body).Decode(&respTags) + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(len(respTags), 0) + }, + }, + ).Close() +} + +func TestGetClubTagsFailsOnNonExistentClub(t *testing.T) { + uuid := uuid.New() + + h.InitTest(t).TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/clubs/%s/tags/", uuid), + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.ClubNotFound, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var dbClub models.Club + + err := eaa.App.Conn.First(&dbClub, uuid).Error + + eaa.Assert.Assert(err != nil) + }, + }, + ).Close() +} + +func TestGetClubTagsReturnsEmptyListWhenNoneAdded(t *testing.T) { + appAssert, _, clubUUID := CreateSampleClub(h.InitTest(t)) + + appAssert.TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/clubs/%s/tags/", clubUUID), + Role: &models.Student, + }, + h.TesterWithStatus{ + Status: fiber.StatusOK, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var respTags []models.Tag + + err := json.NewDecoder(resp.Body).Decode(&respTags) + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(len(respTags), 0) + }, + }, + ).Close() +} + +func TestGetClubTagsReturnsCorrectList(t *testing.T) { + appAssert, _, clubUUID := CreateSampleClub(h.InitTest(t)) + + tagUUIDs, appAssert := CreateSetOfTags(appAssert) + + appAssert.TestOnStatus( + h.TestRequest{ + Method: fiber.MethodPost, + Path: fmt.Sprintf("/api/v1/clubs/%s/tags/", clubUUID), + Body: SampleTagIDsFactory(&tagUUIDs), + Role: &models.Student, + }, + fiber.StatusCreated, + ).TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/clubs/%s/tags/", clubUUID), + Role: &models.Student, + }, + h.TesterWithStatus{ + Status: fiber.StatusOK, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + AssertSampleClubTagsRespDB(eaa, resp, clubUUID) + }, + }, + ).Close() +} diff --git a/backend/tests/api/club_test.go b/backend/tests/api/club_test.go index b7863fe24..6f6059770 100644 --- a/backend/tests/api/club_test.go +++ b/backend/tests/api/club_test.go @@ -233,7 +233,7 @@ func AssertCreateBadClubDataFails(t *testing.T, jsonKey string, badValues []inte sampleClubPermutation := *SampleClubFactory(&uuid) sampleClubPermutation[jsonKey] = badValue - appAssert.TestOnErrorAndTester( + appAssert = appAssert.TestOnErrorAndTester( h.TestRequest{ Method: fiber.MethodPost, Path: "/api/v1/clubs/", @@ -341,7 +341,7 @@ func TestUpdateClubFailsOnInvalidBody(t *testing.T) { {"logo": "@12394X_2"}, } { invalidData := invalidData - appAssert.TestOnErrorAndTester( + appAssert = appAssert.TestOnErrorAndTester( h.TestRequest{ Method: fiber.MethodPatch, Path: fmt.Sprintf("/api/v1/clubs/%s", clubUUID), @@ -399,7 +399,7 @@ func TestUpdateClubFailsBadRequest(t *testing.T) { sampleStudent, rawPassword := h.SampleStudentFactory() for _, badRequest := range badRequests { - appAssert.TestOnError( + appAssert = appAssert.TestOnError( h.TestRequest{ Method: fiber.MethodPatch, Path: fmt.Sprintf("/api/v1/clubs/%s", badRequest), @@ -487,7 +487,7 @@ func TestDeleteClubBadRequest(t *testing.T) { } for _, badRequest := range badRequests { - appAssert.TestOnError( + appAssert = appAssert.TestOnError( h.TestRequest{ Method: fiber.MethodDelete, Path: fmt.Sprintf("/api/v1/clubs/%s", badRequest), diff --git a/backend/tests/api/helpers/utilities_test.go b/backend/tests/api/helpers/utilities_test.go index e81adb70d..3a0b95bd5 100644 --- a/backend/tests/api/helpers/utilities_test.go +++ b/backend/tests/api/helpers/utilities_test.go @@ -8,6 +8,7 @@ import ( ) func TestThatAllCasingPermutationsWorks(t *testing.T) { + t.Parallel() assert := assert.New(t) expectedPermutations := []string{"foo", "Foo", "fOo", "foO", "FOo", "FoO", "fOO", "FOO"} diff --git a/backend/tests/api/membership_test.go b/backend/tests/api/membership_test.go deleted file mode 100644 index 658583d0b..000000000 --- a/backend/tests/api/membership_test.go +++ /dev/null @@ -1,470 +0,0 @@ -package tests - -// import ( -// "fmt" -// "github.com/GenerateNU/sac/backend/src/auth" -// "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" -// "net/http" -// "testing" -// ) - -// func CreateSampleUser2(t *testing.T, existingAppAssert *ExistingAppAssert) (ExistingAppAssert, uuid.UUID) { -// var uuid uuid.UUID - -// body := SampleUserFactory() -// (*body)["nuid"] = "012820050" -// (*body)["email"] = "brennan.mic@northeastern.edu" - -// newAppAssert := TestRequest{ -// Method: fiber.MethodPost, -// Path: "/api/v1/users/", -// Body: body, -// }.TestOnStatusAndDB(t, existingAppAssert, -// DBTesterWithStatus{ -// Status: fiber.StatusCreated, -// DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { -// var respUser models.User - -// err := json.NewDecoder(resp.Body).Decode(&respUser) - -// assert.NilError(err) - -// var dbUser models.User - -// err = app.Conn.Where("nuid = ?", "012820050").First(&dbUser).Error - -// assert.NilError(err) - -// assert.Equal(dbUser.FirstName, respUser.FirstName) -// assert.Equal(dbUser.LastName, respUser.LastName) -// assert.Equal(dbUser.Email, respUser.Email) -// assert.Equal(dbUser.NUID, respUser.NUID) -// assert.Equal(dbUser.College, respUser.College) -// assert.Equal(dbUser.Year, respUser.Year) - -// match, err := auth.ComparePasswordAndHash((*body)["password"].(string), dbUser.PasswordHash) - -// assert.NilError(err) - -// assert.Assert(match) - -// assert.Equal((*body)["first_name"].(string), dbUser.FirstName) -// assert.Equal((*body)["last_name"].(string), dbUser.LastName) -// assert.Equal((*body)["email"].(string), dbUser.Email) -// assert.Equal((*body)["nuid"].(string), dbUser.NUID) -// assert.Equal(models.College((*body)["college"].(string)), dbUser.College) -// assert.Equal(models.Year((*body)["year"].(int)), dbUser.Year) - -// uuid = dbUser.ID -// }, -// }, -// ) - -// if existingAppAssert == nil { -// return newAppAssert, uuid -// } else { -// return *existingAppAssert, uuid -// } - -// } - -// // Creates a single club with a single member for testing -// func CreateSampleClubWithMembership(t *testing.T, existingAppAssert *ExistingAppAssert) (eaa ExistingAppAssert, userUUID uuid.UUID, clubUUID uuid.UUID) { -// appAssert, _, clubID := CreateSampleClub(t, existingAppAssert) -// appAssert2, userID := CreateSampleUser2(t, &appAssert) - -// newAppAssert := TestRequest{ -// Method: fiber.MethodPost, -// Path: fmt.Sprintf("/api/v1/clubs/%s/membership/%s", clubID, userID), -// Body: &map[string]interface{}{}, -// }.TestOnStatusAndDB(t, &appAssert2, -// DBTesterWithStatus{ -// Status: fiber.StatusNoContent, -// DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { -// club := models.Club{} -// err := app.Conn.Where("id = ?", clubID).First(&club).Error - -// assert.NilError(err) - -// var members []models.User -// err = app.Conn.Model(&club).Association("Member").Find(&members) - -// assert.NilError(err) -// assert.Equal(len(members), 1) -// assert.Equal(members[0].ID, userID) -// }, -// }) - -// if existingAppAssert == nil { -// return newAppAssert, userUUID, clubUUID -// } else { -// return *existingAppAssert, userUUID, clubUUID -// } -// } - -// func AssertNumClubMembersRemainsAtN(app TestApp, assert *assert.A, clubID uuid.UUID, n int) { -// var club models.Club -// var members []models.User -// err := app.Conn.Where("id = ?", clubID).First(&club).Association("Members").Find(&members).Error - -// assert.NilError(err) -// assert.Equal(n, len(members)) -// } - -// func AssertNumClubMembersRemainsAt0(app TestApp, assert *assert.A, clubID uuid.UUID) { -// AssertNumClubMembersRemainsAtN(app, assert, clubID, 0) -// } - -// func AssertNumClubMembersRemainsAt1(app TestApp, assert *assert.A, clubID uuid.UUID) { -// AssertNumClubMembersRemainsAtN(app, assert, clubID, 1) -// } - -// // Create membership by user id - 201 OK -// func TestCreateMembershipWorks(t *testing.T) { -// appAssert, _, _ := CreateSampleClubWithMembership(t, nil) -// appAssert.Close() -// } - -// // Create membership by user id - 404 not found (bad club id) -// func TestCreateMembershipFailsOnInvalidClubId(t *testing.T) { -// appAssert, userID := CreateSampleUser(t, nil) - -// TestRequest{ -// Method: fiber.MethodPost, -// Path: fmt.Sprintf("/api/v1/clubs/%s/membership/%s", "gobbledygook", userID), -// Body: &map[string]interface{}{}, -// }.TestOnError(t, &appAssert, errors.FailedToValidateID) -// } - -// // Create membership by user id - 404 not found (bad user id) -// func TestCreateMembershipFailsOnInvalidUserId(t *testing.T) { -// appAssert, _, clubID := CreateSampleClub(t, nil) - -// TestRequest{ -// Method: fiber.MethodPost, -// Path: fmt.Sprintf("/api/v1/clubs/%s/membership/%s", clubID, "gobbledygook"), -// Body: &map[string]interface{}{}, -// }.TestOnError(t, &appAssert, errors.FailedToValidateID) -// } - -// // Create membership by email lists - 201 OK -// func TestCreateMembershipByEmailListWorks(t *testing.T) { -// appAssert, _, clubID := CreateSampleClub(t, nil) -// appAssert2, userID := CreateSampleUser2(t, &appAssert) - -// TestRequest{ -// Method: fiber.MethodPost, -// Path: fmt.Sprintf("/api/v1/clubs/%s/membership", clubID), -// Body: &map[string]interface{}{ -// "emails": []string{ -// "brennan.mic@northeastern.edu", -// }, -// }, -// }.TestOnStatusAndDB(t, &appAssert2, -// DBTesterWithStatus{ -// Status: fiber.StatusNoContent, -// DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { -// var club models.Club -// var members []models.User -// err := app.Conn.Where("id = ?", clubID).First(&club).Association("Member").Find(&members).Error - -// assert.NilError(err) -// assert.Equal(1, len(members)) -// assert.Equal(members[0].ID, userID) -// }, -// }) -// } - -// // Create membership by email lists - 400 invalid body (bad json) -// func TestCreateMembershipByEmailListFailsOnInvalidBody(t *testing.T) { -// appAssert, _, clubID := CreateSampleClub(t, nil) - -// badBodies := []map[string]interface{}{ -// { -// "foo": "bar", -// "alice": "bob", -// }, -// { -// "x": false, -// }, -// } - -// for i := 0; i < len(badBodies); i += 1 { -// TestRequest{ -// Method: fiber.MethodPost, -// Path: fmt.Sprintf("/api/v1/clubs/%s/membership", clubID), -// Body: &badBodies[i], -// }.TestOnErrorAndDB(t, &appAssert, -// ErrorWithDBTester{ -// Error: errors.FailedToParseRequestBody, -// DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { -// AssertNumClubMembersRemainsAt0(app, assert, clubID) -// }, -// }) -// } - -// } - -// // Create membership by email lists - 404 not found (bad club id) -// func TestCreateMembershipByEmailListFailsOnInvalidClubId(t *testing.T) { -// TestRequest{ -// Method: fiber.MethodPost, -// Path: fmt.Sprintf("/api/v1/clubs/%s/membership", "gobbledygook"), -// Body: &map[string]interface{}{ -// "emails": []string{ -// "doe.jane@northeastern.edu", -// }, -// }, -// }.TestOnError(t, nil, errors.FailedToValidateID) -// } - -// // Create membership by email lists - 404 not found (no user with email) -// func TestCreateMembershipByEmailListFailsOnUserNotFound(t *testing.T) { -// appAssert, _, clubID := CreateSampleClub(t, nil) - -// TestRequest{ -// Method: fiber.MethodPost, -// Path: fmt.Sprintf("/api/v1/clubs/%s/membership", clubID), -// Body: &map[string]interface{}{ -// "emails": []string{ -// "doe.jane@northeastern.edu", -// }, -// }, -// }.TestOnErrorAndDB(t, &appAssert, ErrorWithDBTester{ -// Error: errors.UserNotFound, -// DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { -// AssertNumClubMembersRemainsAt0(app, assert, clubID) -// }, -// }) -// } - -// // Delete membership by user id - 200 OK -// func TestDeleteMembershipWorks(t *testing.T) { -// appAssert, userID, clubID := CreateSampleClubWithMembership(t, nil) - -// TestRequest{ -// Method: fiber.MethodDelete, -// Path: fmt.Sprintf("/api/v1/clubs/%s/membership/%s", clubID, userID), -// Body: &map[string]interface{}{}, -// }.TestOnStatusAndDB(t, &appAssert, -// DBTesterWithStatus{ -// Status: fiber.StatusOK, -// DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { -// AssertNumClubMembersRemainsAt0(app, assert, clubID) -// }, -// }) -// } - -// // Delete membership by user id - 404 not found (bad club id) -// func TestDeleteMembershipFailsOnInvalidClubId(t *testing.T) { -// appAssert, userID := CreateSampleUser(t, nil) - -// TestRequest{ -// Method: fiber.MethodDelete, -// Path: fmt.Sprintf("/api/v1/clubs/%s/membership/%s", "gobbledygook", userID), -// Body: &map[string]interface{}{}, -// }.TestOnError(t, &appAssert, errors.ClubNotFound) -// } - -// // Delete membership by user id - 404 not found (bad user id) -// func TestDeleteMembershipFailsOnInvalidUserId(t *testing.T) { -// appAssert, _, clubID := CreateSampleClub(t, nil) - -// TestRequest{ -// Method: fiber.MethodPost, -// Path: fmt.Sprintf("/api/v1/clubs/%s/membership/%s", clubID, "gobbledygook"), -// Body: &map[string]interface{}{}, -// }.TestOnError(t, &appAssert, errors.UserNotFound) -// } - -// // Delete membership by user ids - 200 OK -// func TestDeleteMembershipByUserIdsWorks(t *testing.T) { -// appAssert, userID, clubID := CreateSampleClubWithMembership(t, nil) - -// TestRequest{ -// Method: fiber.MethodDelete, -// Path: fmt.Sprintf("/api/v1/clubs/%s/membership", clubID), -// Body: &map[string]interface{}{ -// "ids": []string{ -// userID.String(), -// }, -// }, -// }.TestOnStatusAndDB(t, &appAssert, -// DBTesterWithStatus{ -// Status: fiber.StatusOK, -// DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { -// AssertNumClubMembersRemainsAt0(app, assert, clubID) -// }, -// }) -// } - -// // Delete membership by user ids - 400 invalid body (bad json) -// func TestDeleteMembershipByUserIdsFailsOnInvalidBody(t *testing.T) { -// appAssert, _, clubID := CreateSampleClub(t, nil) - -// badBodies := []map[string]interface{}{ -// { -// "ids": []int{ -// 1, -// 2, -// 3, -// 4, -// 890, -// }, -// }, -// { -// "foo": "bar", -// "alice": "bob", -// }, -// { -// "x": false, -// }, -// } - -// for i := 0; i < len(badBodies); i += 1 { -// TestRequest{ -// Method: fiber.MethodDelete, -// Path: fmt.Sprintf("/api/v1/clubs/%s/membership", clubID), -// Body: &badBodies[i], -// }.TestOnErrorAndDB(t, &appAssert, -// ErrorWithDBTester{ -// Error: errors.FailedToParseRequestBody, -// DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { -// AssertNumClubMembersRemainsAt1(app, assert, clubID) -// }, -// }) -// } -// } - -// // Delete membership by user ids - 404 not found (bad club id) -// func TestDeleteMembershipByUserIdsFailsOnInvalidClubId(t *testing.T) { -// appAssert, userID, clubID := CreateSampleClubWithMembership(t, nil) - -// TestRequest{ -// Method: fiber.MethodDelete, -// Path: fmt.Sprintf("/api/v1/clubs/%s/membership", "gobbledygook"), -// Body: &map[string]interface{}{ -// "ids": []string{ -// userID.String(), -// }, -// }, -// }.TestOnErrorAndDB(t, &appAssert, -// ErrorWithDBTester{ -// Error: errors.ClubNotFound, -// DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { -// AssertNumClubMembersRemainsAt1(app, assert, clubID) -// }, -// }) -// } - -// // Delete membership by user ids - 404 not found (bad user id) -// func TestDeleteMembershipByUserIdsFailsOnUserNotFound(t *testing.T) { -// appAssert, _, clubID := CreateSampleClubWithMembership(t, nil) - -// TestRequest{ -// Method: fiber.MethodDelete, -// Path: fmt.Sprintf("/api/v1/clubs/%s/membership", clubID), -// Body: &map[string]interface{}{ -// "ids": []string{ -// "gobbledygook", -// }, -// }, -// }.TestOnErrorAndDB(t, &appAssert, -// ErrorWithDBTester{ -// Error: errors.UserNotFound, -// DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { -// AssertNumClubMembersRemainsAt1(app, assert, clubID) -// }, -// }) -// } - -// // Get all club memberships for 1 user - 200 OK -// func TestGetMembershipsForUserWorks(t *testing.T) { -// appAssert, userID, clubID := CreateSampleClubWithMembership(t, nil) - -// TestRequest{ -// Method: fiber.MethodGet, -// Path: fmt.Sprintf("/api/v1/users/%s/membership", userID), -// Body: &map[string]interface{}{}, -// }.TestOnStatusAndDB(t, &appAssert, -// DBTesterWithStatus{ -// Status: fiber.StatusOK, -// DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { -// var respClubs []models.Club -// err := json.NewDecoder(resp.Body).Decode(&respClubs) -// assert.NilError(err) - -// var respClub = respClubs[0] - -// var dbClub models.Club -// err = app.Conn.Where("id = ?", clubID).First(&dbClub).Error -// assert.NilError(err) - -// assert.Equal(dbClub.ID, respClub.ID) -// assert.Equal(dbClub.Name, respClub.Name) -// assert.Equal(dbClub.Preview, respClub.Preview) -// assert.Equal(dbClub.Description, respClub.Description) -// assert.Equal(dbClub.NumMembers, respClub.NumMembers) -// assert.Equal(dbClub.IsRecruiting, respClub.IsRecruiting) -// assert.Equal(dbClub.RecruitmentCycle, respClub.RecruitmentCycle) -// assert.Equal(dbClub.RecruitmentType, respClub.RecruitmentType) -// assert.Equal(dbClub.ApplicationLink, respClub.ApplicationLink) -// assert.Equal(dbClub.Logo, respClub.Logo) -// }, -// }) -// } - -// // Get all club memberships for 1 user - 404 not found (bad user id) -// func TestGetMembershipsForUserFailsOnInvalidUserId(t *testing.T) { -// TestRequest{ -// Method: fiber.MethodGet, -// Path: fmt.Sprintf("/api/v1/users/%s/membership", "gobbledygook"), -// Body: &map[string]interface{}{}, -// }.TestOnError(t, nil, errors.UserNotFound) -// } - -// // Get all club memberships for 1 club - 200 OK -// func TestGetMembershipsForClubWorks(t *testing.T) { -// appAssert, userID, clubID := CreateSampleClubWithMembership(t, nil) - -// TestRequest{ -// Method: fiber.MethodGet, -// Path: fmt.Sprintf("/api/v1/clubs/%s/membership", clubID), -// Body: &map[string]interface{}{}, -// }.TestOnStatusAndDB(t, &appAssert, -// DBTesterWithStatus{ -// Status: fiber.StatusOK, -// DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { -// var respUsers []models.User -// err := json.NewDecoder(resp.Body).Decode(&respUsers) -// assert.NilError(err) - -// respUser := respUsers[0] - -// var dbUser models.User -// err = app.Conn.Where("id = ?", userID).First(&dbUser).Error -// assert.NilError(err) - -// assert.Equal(dbUser.FirstName, respUser.FirstName) -// assert.Equal(dbUser.LastName, respUser.LastName) -// assert.Equal(dbUser.Email, respUser.Email) -// assert.Equal(dbUser.NUID, respUser.NUID) -// assert.Equal(dbUser.College, respUser.College) -// assert.Equal(dbUser.Year, respUser.Year) -// }, -// }) -// } - -// // Get all club memberships for 1 club - 404 not found (bad club id) -// func TestGetMembershipsForClubFailsOnInvalidClubId(t *testing.T) { -// TestRequest{ -// Method: fiber.MethodGet, -// Path: fmt.Sprintf("/api/v1/clubs/%s/membership", "gobbledygook"), -// Body: &map[string]interface{}{}, -// }.TestOnError(t, nil, errors.FailedToValidateID) -// } diff --git a/backend/tests/api/tag_test.go b/backend/tests/api/tag_test.go index 77c42fea7..6bb186fb4 100644 --- a/backend/tests/api/tag_test.go +++ b/backend/tests/api/tag_test.go @@ -111,7 +111,7 @@ func TestCreateTagFailsBadRequest(t *testing.T) { for _, badBody := range badBodys { badBody := badBody - appAssert.TestOnErrorAndTester( + appAssert = appAssert.TestOnErrorAndTester( h.TestRequest{ Method: fiber.MethodPost, Path: "/api/v1/tags/", @@ -143,7 +143,7 @@ func TestCreateTagFailsValidation(t *testing.T) { for _, badBody := range badBodys { badBody := badBody - appAssert.TestOnErrorAndTester( + appAssert = appAssert.TestOnErrorAndTester( h.TestRequest{ Method: fiber.MethodPost, Path: "/api/v1/tags/", @@ -190,7 +190,7 @@ func TestGetTagFailsBadRequest(t *testing.T) { } for _, badRequest := range badRequests { - appAssert.TestOnError( + appAssert = appAssert.TestOnError( h.TestRequest{ Method: fiber.MethodGet, Path: fmt.Sprintf("/api/v1/tags/%s", badRequest), @@ -314,7 +314,7 @@ func TestUpdateTagFailsBadRequest(t *testing.T) { } for _, badRequest := range badRequests { - appAssert.TestOnError( + appAssert = appAssert.TestOnError( h.TestRequest{ Method: fiber.MethodPatch, Path: fmt.Sprintf("/api/v1/tags/%s", badRequest), @@ -356,7 +356,7 @@ func TestDeleteTagFailsBadRequest(t *testing.T) { } for _, badRequest := range badRequests { - appAssert.TestOnErrorAndTester( + appAssert = appAssert.TestOnErrorAndTester( h.TestRequest{ Method: fiber.MethodDelete, Path: fmt.Sprintf("/api/v1/tags/%s", badRequest), diff --git a/backend/tests/api/user_follower_test.go b/backend/tests/api/user_follower_test.go index 20ec835f9..61c2da3c1 100644 --- a/backend/tests/api/user_follower_test.go +++ b/backend/tests/api/user_follower_test.go @@ -1,36 +1,298 @@ package tests -// func TestCreateFollowingWorks(t *testing.T) { -// appAssert, userUUID, clubUUID := CreateSampleClub(h.InitTest(t)) +import ( + "fmt" + "net/http" + "testing" -// appAssert.TestOnStatusAndTester( -// h.TestRequest{ -// Method: fiber.MethodPost, -// Path: fmt.Sprintf("/api/v1/users/%s/follower/%s", userUUID, clubUUID), -// }, -// h.TesterWithStatus{ -// Status: fiber.StatusCreated, -// Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { -// var user models.User + "github.com/goccy/go-json" -// err := eaa.App.Conn.Where("id = ?", userUUID).Preload("Follower").First(&user) + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/models" + "github.com/GenerateNU/sac/backend/src/transactions" + h "github.com/GenerateNU/sac/backend/tests/api/helpers" + "github.com/gofiber/fiber/v2" + "github.com/google/uuid" +) -// eaa.Assert.NilError(err) +func TestCreateFollowingWorks(t *testing.T) { + appAssert, _, clubUUID := CreateSampleClub(h.InitTest(t)) -// eaa.Assert.Equal(1, len(user.Follower)) + appAssert.TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodPost, + Path: fmt.Sprintf("/api/v1/users/:userID/follower/%s", clubUUID), + Role: &models.Super, + TestUserIDReplaces: h.StringToPointer(":userID"), + }, + h.TesterWithStatus{ + Status: fiber.StatusCreated, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var user models.User -// eaa.Assert.Equal(clubUUID, user.Follower[0].ID) + err := eaa.App.Conn.Where("id = ?", eaa.App.TestUser.UUID).Preload("Follower").First(&user) -// var club models.Club + eaa.Assert.NilError(err) -// err = eaa.App.Conn.Where("id = ?", clubUUID).Preload("Follower").First(&club) + eaa.Assert.Equal(1, len(user.Follower)) -// eaa.Assert.NilError(err) + eaa.Assert.Equal(clubUUID, user.Follower[0].ID) -// eaa.Assert.Equal(1, len(club.Follower)) + var club models.Club -// eaa.Assert.Equal(userUUID, club.Follower[0].ID) -// }, -// }, -// ).Close() -// } + err = eaa.App.Conn.Where("id = ?", clubUUID).Preload("Follower").First(&club) + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(1, len(club.Follower)) + + eaa.Assert.Equal(eaa.App.TestUser.UUID, club.Follower[0].ID) + }, + }, + ).Close() +} + +func TestCreateFollowingFailsClubIdNotExists(t *testing.T) { + appAssert, _, _ := CreateSampleClub(h.InitTest(t)) + + uuid := uuid.New() + + appAssert.TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodPost, + Path: fmt.Sprintf("/api/v1/users/:userID/follower/%s", uuid), + Role: &models.Super, + TestUserIDReplaces: h.StringToPointer(":userID"), + }, + h.ErrorWithTester{ + Error: errors.ClubNotFound, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var club models.Club + + err := eaa.App.Conn.Where("id = ?", uuid).First(&club).Error + + eaa.Assert.Assert(err != nil) + }, + }, + ).Close() +} + +func TestCreateFollowingFailsUserIdNotExists(t *testing.T) { + appAssert, _, clubUUID := CreateSampleClub(h.InitTest(t)) + + uuid := uuid.New() + + appAssert.TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodPost, + Path: fmt.Sprintf("/api/v1/users/%s/follower/%s", uuid, clubUUID), + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.UserNotFound, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var user models.User + + err := eaa.App.Conn.Where("id = ?", uuid).First(&user).Error + + eaa.Assert.Assert(err != nil) + }, + }, + ).Close() +} + +func TestDeleteFollowingWorks(t *testing.T) { + appAssert, _, clubUUID := CreateSampleClub(h.InitTest(t)) + + appAssert.TestOnStatus( + h.TestRequest{ + Method: fiber.MethodPost, + Path: fmt.Sprintf("/api/v1/users/:userID/follower/%s", clubUUID), + Role: &models.Super, + TestUserIDReplaces: h.StringToPointer(":userID"), + }, + fiber.StatusCreated, + ).TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodDelete, + Path: fmt.Sprintf("/api/v1/users/:userID/follower/%s", clubUUID), + Role: &models.Super, + TestUserIDReplaces: h.StringToPointer(":userID"), + }, + h.TesterWithStatus{ + Status: fiber.StatusNoContent, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var user models.User + + err := eaa.App.Conn.Where("id = ?", eaa.App.TestUser.UUID).Preload("Follower").First(&user) + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(0, len(user.Follower)) + + var club models.Club + + err = eaa.App.Conn.Where("id = ?", clubUUID).Preload("Follower").First(&club) + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(0, len(club.Follower)) + }, + }, + ).Close() +} + +func TestDeleteFollwerNotFollower(t *testing.T) { + appAssert, _, clubUUID := CreateSampleClub(h.InitTest(t)) + + userClubsFollowerBefore, err := transactions.GetClubFollowing(appAssert.App.Conn, appAssert.App.TestUser.UUID) + + appAssert.Assert.Assert(err == nil) + + clubUsersFollowerBefore, err := transactions.GetClubFollowers(appAssert.App.Conn, clubUUID, 10, 0) + + appAssert.Assert.Assert(err == nil) + + appAssert.TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodDelete, + Path: fmt.Sprintf("/api/v1/users/:userID/follower/%s", clubUUID), + Role: &models.Super, + TestUserIDReplaces: h.StringToPointer(":userID"), + }, + h.ErrorWithTester{ + Error: errors.UserNotFollowingClub, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var user models.User + + err := eaa.App.Conn.Where("id = ?", eaa.App.TestUser.UUID).Preload("Follower").First(&user) + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(userClubsFollowerBefore, user.Follower) + + var club models.Club + + err = eaa.App.Conn.Where("id = ?", clubUUID).Preload("Follower").First(&club) + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(clubUsersFollowerBefore, club.Follower) + }, + }, + ).Close() +} + +func TestDeleteFollowingFailsClubIdNotExists(t *testing.T) { + appAssert, _, _ := CreateSampleClub(h.InitTest(t)) + + uuid := uuid.New() + + appAssert.TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodDelete, + Path: fmt.Sprintf("/api/v1/users/:userID/follower/%s", uuid), + Role: &models.Super, + TestUserIDReplaces: h.StringToPointer(":userID"), + }, + h.ErrorWithTester{ + Error: errors.ClubNotFound, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var club models.Club + + err := eaa.App.Conn.Where("id = ?", uuid).First(&club).Error + + eaa.Assert.Assert(err != nil) + }, + }, + ).Close() +} + +func TestDeleteFollowingFailsUserIdNotExists(t *testing.T) { + appAssert, _, clubUUID := CreateSampleClub(h.InitTest(t)) + + uuid := uuid.New() + + appAssert.TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodDelete, + Path: fmt.Sprintf("/api/v1/users/%s/follower/%s", uuid, clubUUID), + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.UserNotFound, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var user models.User + + err := eaa.App.Conn.Where("id = ?", uuid).First(&user).Error + + eaa.Assert.Assert(err != nil) + }, + }, + ).Close() +} + +func TestGetFollowingWorks(t *testing.T) { + appAssert, _, clubUUID := CreateSampleClub(h.InitTest(t)) + + appAssert.TestOnStatus( + h.TestRequest{ + Method: fiber.MethodPost, + Path: fmt.Sprintf("/api/v1/users/:userID/follower/%s", clubUUID), + Role: &models.Super, + TestUserIDReplaces: h.StringToPointer(":userID"), + }, + fiber.StatusCreated, + ).TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodGet, + Path: "/api/v1/users/:userID/follower", + Role: &models.Super, + TestUserIDReplaces: h.StringToPointer(":userID"), + }, + h.TesterWithStatus{ + Status: fiber.StatusOK, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var clubs []models.Club + + err := json.NewDecoder(resp.Body).Decode(&clubs) + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(1, len(clubs)) + + var dbClubs []models.Club + + err = eaa.App.Conn.Where("id = ?", clubUUID).Preload("Follower").First(&dbClubs).Error + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(1, len(clubs)) + }, + }, + ).Close() +} + +func TestGetFollowingFailsUserIdNotExists(t *testing.T) { + appAssert, _, _ := CreateSampleClub(h.InitTest(t)) + + uuid := uuid.New() + + appAssert.TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/users/%s/follower", uuid), + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.UserNotFound, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var user models.User + + err := eaa.App.Conn.Where("id = ?", uuid).First(&user).Error + + eaa.Assert.Assert(err != nil) + }, + }, + ).Close() +} diff --git a/backend/tests/api/user_member_test.go b/backend/tests/api/user_member_test.go new file mode 100644 index 000000000..9f41b9e52 --- /dev/null +++ b/backend/tests/api/user_member_test.go @@ -0,0 +1,298 @@ +package tests + +import ( + "fmt" + "net/http" + "testing" + + "github.com/goccy/go-json" + + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/models" + "github.com/GenerateNU/sac/backend/src/transactions" + h "github.com/GenerateNU/sac/backend/tests/api/helpers" + "github.com/gofiber/fiber/v2" + "github.com/google/uuid" +) + +func TestCreateMembershipWorks(t *testing.T) { + appAssert, _, clubUUID := CreateSampleClub(h.InitTest(t)) + + appAssert.TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodPost, + Path: fmt.Sprintf("/api/v1/users/:userID/member/%s", clubUUID), + Role: &models.Super, + TestUserIDReplaces: h.StringToPointer(":userID"), + }, + h.TesterWithStatus{ + Status: fiber.StatusCreated, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var user models.User + + err := eaa.App.Conn.Where("id = ?", eaa.App.TestUser.UUID).Preload("Member").First(&user) + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(2, len(user.Member)) // SAC Super Club and the one just added + + eaa.Assert.Equal(clubUUID, user.Member[1].ID) // second club AKA the one just added + + var club models.Club + + err = eaa.App.Conn.Where("id = ?", clubUUID).Preload("Member").First(&club) + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(1, len(club.Member)) + + eaa.Assert.Equal(eaa.App.TestUser.UUID, club.Member[0].ID) + }, + }, + ).Close() +} + +func TestCreateMembershipFailsClubIdNotExists(t *testing.T) { + appAssert, _, _ := CreateSampleClub(h.InitTest(t)) + + uuid := uuid.New() + + appAssert.TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodPost, + Path: fmt.Sprintf("/api/v1/users/:userID/member/%s", uuid), + Role: &models.Super, + TestUserIDReplaces: h.StringToPointer(":userID"), + }, + h.ErrorWithTester{ + Error: errors.ClubNotFound, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var club models.Club + + err := eaa.App.Conn.Where("id = ?", uuid).First(&club).Error + + eaa.Assert.Assert(err != nil) + }, + }, + ).Close() +} + +func TestCreateMembershipFailsUserIdNotExists(t *testing.T) { + appAssert, _, clubUUID := CreateSampleClub(h.InitTest(t)) + + uuid := uuid.New() + + appAssert.TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodPost, + Path: fmt.Sprintf("/api/v1/users/%s/member/%s", uuid, clubUUID), + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.UserNotFound, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var user models.User + + err := eaa.App.Conn.Where("id = ?", uuid).First(&user).Error + + eaa.Assert.Assert(err != nil) + }, + }, + ).Close() +} + +func TestDeleteMembershipWorks(t *testing.T) { + appAssert, _, clubUUID := CreateSampleClub(h.InitTest(t)) + + appAssert.TestOnStatus( + h.TestRequest{ + Method: fiber.MethodPost, + Path: fmt.Sprintf("/api/v1/users/:userID/member/%s", clubUUID), + Role: &models.Super, + TestUserIDReplaces: h.StringToPointer(":userID"), + }, + fiber.StatusCreated, + ).TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodDelete, + Path: fmt.Sprintf("/api/v1/users/:userID/member/%s", clubUUID), + Role: &models.Super, + TestUserIDReplaces: h.StringToPointer(":userID"), + }, + h.TesterWithStatus{ + Status: fiber.StatusNoContent, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var user models.User + + err := eaa.App.Conn.Where("id = ?", eaa.App.TestUser.UUID).Preload("Member").First(&user) + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(1, len(user.Member)) // SAC Super Club + + var club models.Club + + err = eaa.App.Conn.Where("id = ?", clubUUID).Preload("Member").First(&club) + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(0, len(club.Member)) + }, + }, + ).Close() +} + +func TestDeleteMembershipNotMembership(t *testing.T) { + appAssert, _, clubUUID := CreateSampleClub(h.InitTest(t)) + + userClubsMemberBefore, err := transactions.GetClubMembership(appAssert.App.Conn, appAssert.App.TestUser.UUID) + + appAssert.Assert.Assert(err == nil) + + clubUsersMemberBefore, err := transactions.GetClubMembers(appAssert.App.Conn, clubUUID, 10, 0) + + appAssert.Assert.Assert(err == nil) + + appAssert.TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodDelete, + Path: fmt.Sprintf("/api/v1/users/:userID/member/%s", clubUUID), + Role: &models.Super, + TestUserIDReplaces: h.StringToPointer(":userID"), + }, + h.ErrorWithTester{ + Error: errors.UserNotMemberOfClub, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var user models.User + + err := eaa.App.Conn.Where("id = ?", eaa.App.TestUser.UUID).Preload("Member").First(&user) + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(userClubsMemberBefore, user.Member) + + var club models.Club + + err = eaa.App.Conn.Where("id = ?", clubUUID).Preload("Member").First(&club) + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(clubUsersMemberBefore, club.Member) + }, + }, + ).Close() +} + +func TestDeleteMembershipFailsClubIdNotExists(t *testing.T) { + appAssert, _, _ := CreateSampleClub(h.InitTest(t)) + + uuid := uuid.New() + + appAssert.TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodDelete, + Path: fmt.Sprintf("/api/v1/users/:userID/member/%s", uuid), + Role: &models.Super, + TestUserIDReplaces: h.StringToPointer(":userID"), + }, + h.ErrorWithTester{ + Error: errors.ClubNotFound, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var club models.Club + + err := eaa.App.Conn.Where("id = ?", uuid).First(&club).Error + + eaa.Assert.Assert(err != nil) + }, + }, + ).Close() +} + +func TestDeleteMembershipFailsUserIdNotExists(t *testing.T) { + appAssert, _, clubUUID := CreateSampleClub(h.InitTest(t)) + + uuid := uuid.New() + + appAssert.TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodDelete, + Path: fmt.Sprintf("/api/v1/users/%s/member/%s", uuid, clubUUID), + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.UserNotFound, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var user models.User + + err := eaa.App.Conn.Where("id = ?", uuid).First(&user).Error + + eaa.Assert.Assert(err != nil) + }, + }, + ).Close() +} + +func TestGetMembershipWorks(t *testing.T) { + appAssert, _, clubUUID := CreateSampleClub(h.InitTest(t)) + + appAssert.TestOnStatus( + h.TestRequest{ + Method: fiber.MethodPost, + Path: fmt.Sprintf("/api/v1/users/:userID/member/%s", clubUUID), + Role: &models.Super, + TestUserIDReplaces: h.StringToPointer(":userID"), + }, + fiber.StatusCreated, + ).TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodGet, + Path: "/api/v1/users/:userID/member", + Role: &models.Super, + TestUserIDReplaces: h.StringToPointer(":userID"), + }, + h.TesterWithStatus{ + Status: fiber.StatusOK, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var clubs []models.Club + + err := json.NewDecoder(resp.Body).Decode(&clubs) + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(2, len(clubs)) // SAC Super Club and the one just added + + var dbClubs []models.Club + + err = eaa.App.Conn.Where("id = ?", clubUUID).Preload("Member").First(&dbClubs).Error + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(2, len(clubs)) // SAC Super Club and the one just added + }, + }, + ).Close() +} + +func TestGetMembershipFailsUserIdNotExists(t *testing.T) { + appAssert, _, _ := CreateSampleClub(h.InitTest(t)) + + uuid := uuid.New() + + appAssert.TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/users/%s/member", uuid), + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.UserNotFound, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var user models.User + + err := eaa.App.Conn.Where("id = ?", uuid).First(&user).Error + + eaa.Assert.Assert(err != nil) + }, + }, + ).Close() +} diff --git a/backend/tests/api/user_tag_test.go b/backend/tests/api/user_tag_test.go index a7196e318..e6a200c50 100644 --- a/backend/tests/api/user_tag_test.go +++ b/backend/tests/api/user_tag_test.go @@ -55,18 +55,13 @@ func SampleTagIDsFactory(tagIDs *[]uuid.UUID) *map[string]interface{} { } } -func CreateSetOfTags(t *testing.T, appAssert *h.ExistingAppAssert) ([]uuid.UUID, *h.ExistingAppAssert) { - if appAssert == nil { - newAppAssert := h.InitTest(t) - appAssert = &newAppAssert - } - +func CreateSetOfTags(appAssert h.ExistingAppAssert) ([]uuid.UUID, h.ExistingAppAssert) { categories := SampleCategoriesFactory() categoryIDs := []uuid.UUID{} for _, category := range *categories { category := category - appAssert.TestOnStatusAndTester( + appAssert = appAssert.TestOnStatusAndTester( h.TestRequest{ Method: fiber.MethodPost, Path: "/api/v1/categories/", @@ -93,7 +88,7 @@ func CreateSetOfTags(t *testing.T, appAssert *h.ExistingAppAssert) ([]uuid.UUID, tagIDs := []uuid.UUID{} for _, tag := range *tags { tag := tag - appAssert.TestOnStatusAndTester( + appAssert = appAssert.TestOnStatusAndTester( h.TestRequest{ Method: fiber.MethodPost, Path: "/api/v1/tags/", @@ -121,24 +116,21 @@ func CreateSetOfTags(t *testing.T, appAssert *h.ExistingAppAssert) ([]uuid.UUID, func AssertUserTagsRespDB(eaa h.ExistingAppAssert, resp *http.Response, id uuid.UUID) { var respTags []models.Tag - // Retrieve the tags from the response: err := json.NewDecoder(resp.Body).Decode(&respTags) eaa.Assert.NilError(err) - // Retrieve the user connected to the tags: var dbUser models.User + err = eaa.App.Conn.First(&dbUser, id).Error eaa.Assert.NilError(err) - // Retrieve the tags in the bridge table associated with the user: var dbTags []models.Tag err = eaa.App.Conn.Model(&dbUser).Association("Tag").Find(&dbTags) eaa.Assert.NilError(err) - // Confirm all the resp tags are equal to the db tags: for i, respTag := range respTags { eaa.Assert.Equal(respTag.ID, dbTags[i].ID) eaa.Assert.Equal(respTag.Name, dbTags[i].Name) @@ -151,19 +143,19 @@ func AssertSampleUserTagsRespDB(eaa h.ExistingAppAssert, resp *http.Response, uu } func TestCreateUserTagsFailsOnInvalidDataType(t *testing.T) { - // Invalid tag data types: + appAssert := h.InitTest(t) + 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 - h.InitTest(t).TestOnError( + appAssert = appAssert.TestOnError( h.TestRequest{ Method: fiber.MethodPost, Path: "/api/v1/users/:userID/tags/", @@ -172,11 +164,15 @@ func TestCreateUserTagsFailsOnInvalidDataType(t *testing.T) { TestUserIDReplaces: h.StringToPointer(":userID"), }, errors.FailedToParseRequestBody, - ).Close() + ) } + + appAssert.Close() } func TestCreateUserTagsFailsOnInvalidUserID(t *testing.T) { + appAssert := h.InitTest(t) + badRequests := []string{ "0", "-1", @@ -186,7 +182,7 @@ func TestCreateUserTagsFailsOnInvalidUserID(t *testing.T) { } for _, badRequest := range badRequests { - h.InitTest(t).TestOnError( + appAssert = appAssert.TestOnError( h.TestRequest{ Method: fiber.MethodPost, Path: fmt.Sprintf("/api/v1/users/%s/tags", badRequest), @@ -194,18 +190,20 @@ func TestCreateUserTagsFailsOnInvalidUserID(t *testing.T) { Role: &models.Student, }, errors.FailedToValidateID, - ).Close() + ) } + + appAssert.Close() } type UUIDSlice []uuid.UUID -var testUUID = uuid.New() - func TestCreateUserTagsFailsOnInvalidKey(t *testing.T) { + appAssert := h.InitTest(t) + invalidBody := []map[string]interface{}{ { - "tag": UUIDSlice{testUUID, testUUID}, + "tag": UUIDSlice{uuid.New(), uuid.New()}, }, { "tagIDs": []uint{1, 2, 3}, @@ -214,7 +212,8 @@ func TestCreateUserTagsFailsOnInvalidKey(t *testing.T) { for _, body := range invalidBody { body := body - h.InitTest(t).TestOnError( + + appAssert = appAssert.TestOnError( h.TestRequest{ Method: fiber.MethodPost, Path: "/api/v1/users/:userID/tags/", @@ -223,8 +222,10 @@ func TestCreateUserTagsFailsOnInvalidKey(t *testing.T) { TestUserIDReplaces: h.StringToPointer(":userID"), }, errors.FailedToValidateUserTags, - ).Close() + ) } + + appAssert.Close() } func TestCreateUserTagsFailsOnNonExistentUser(t *testing.T) { @@ -250,10 +251,8 @@ func TestCreateUserTagsFailsOnNonExistentUser(t *testing.T) { } func TestCreateUserTagsWorks(t *testing.T) { - // Create a set of tags: - tagUUIDs, appAssert := CreateSetOfTags(t, nil) + tagUUIDs, appAssert := CreateSetOfTags(h.InitTest(t)) - // Confirm adding real tags adds them to the user: appAssert.TestOnStatusAndTester( h.TestRequest{ Method: fiber.MethodPost, @@ -298,12 +297,24 @@ func TestCreateUserTagsNoneAddedIfInvalid(t *testing.T) { } func TestGetUserTagsFailsOnNonExistentUser(t *testing.T) { - h.InitTest(t).TestOnError( + uuid := uuid.New() + + h.InitTest(t).TestOnErrorAndTester( h.TestRequest{ Method: fiber.MethodGet, - Path: fmt.Sprintf("/api/v1/users/%s/tags/", uuid.New()), + Path: fmt.Sprintf("/api/v1/users/%s/tags/", uuid), Role: &models.Super, - }, errors.UserNotFound, + }, + h.ErrorWithTester{ + Error: errors.UserNotFound, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var dbUser models.User + + err := eaa.App.Conn.First(&dbUser, uuid).Error + + eaa.Assert.Assert(err != nil) + }, + }, ).Close() } @@ -331,11 +342,9 @@ func TestGetUserTagsReturnsEmptyListWhenNoneAdded(t *testing.T) { } func TestGetUserTagsReturnsCorrectList(t *testing.T) { - tagUUIDs, appAssert := CreateSetOfTags(t, nil) - - newAppAssert := *appAssert + tagUUIDs, appAssert := CreateSetOfTags(h.InitTest(t)) - newAppAssert.TestOnStatus( + appAssert.TestOnStatus( h.TestRequest{ Method: fiber.MethodPost, Path: "/api/v1/users/:userID/tags/", diff --git a/backend/tests/api/user_test.go b/backend/tests/api/user_test.go index 7f790c49e..20e1d0ebe 100644 --- a/backend/tests/api/user_test.go +++ b/backend/tests/api/user_test.go @@ -121,7 +121,7 @@ func TestGetUserFailsBadRequest(t *testing.T) { } for _, badRequest := range badRequests { - appAssert.TestOnError( + appAssert = appAssert.TestOnError( h.TestRequest{ Method: fiber.MethodGet, Path: fmt.Sprintf("/api/v1/users/%s", badRequest), @@ -216,6 +216,7 @@ func TestUpdateUserFailsOnInvalidBody(t *testing.T) { {"college": "UT-Austin"}, } { invalidData := invalidData + h.InitTest(t).TestOnErrorAndTester( h.TestRequest{ Method: fiber.MethodPatch, @@ -332,7 +333,7 @@ func TestDeleteUserBadRequest(t *testing.T) { } for _, badRequest := range badRequests { - appAssert.TestOnErrorAndTester( + appAssert = appAssert.TestOnErrorAndTester( h.TestRequest{ Method: fiber.MethodDelete, Path: fmt.Sprintf("/api/v1/users/%s", badRequest), @@ -497,7 +498,7 @@ func AssertCreateBadDataFails(t *testing.T, jsonKey string, badValues []interfac sampleUserPermutation := *h.SampleStudentJSONFactory(sampleStudent, rawPassword) sampleUserPermutation[jsonKey] = badValue - appAssert.TestOnErrorAndTester( + appAssert = appAssert.TestOnErrorAndTester( h.TestRequest{ Method: fiber.MethodPost, Path: "/api/v1/users/", @@ -591,7 +592,7 @@ func TestCreateUserFailsOnMissingFields(t *testing.T) { sampleUserPermutation := *h.SampleStudentJSONFactory(sampleStudent, rawPassword) delete(sampleUserPermutation, missingField) - appAssert.TestOnErrorAndTester( + appAssert = appAssert.TestOnErrorAndTester( h.TestRequest{ Method: fiber.MethodPost, Path: "/api/v1/users/", diff --git a/backend/tests/auth_test.go b/backend/tests/auth_test.go index bb29d577f..9ff2907d8 100644 --- a/backend/tests/auth_test.go +++ b/backend/tests/auth_test.go @@ -31,6 +31,7 @@ func AuthSettings() (*config.AuthSettings, error) { } func TestCreateTokenPairSuccess(t *testing.T) { + t.Parallel() assert := assert.New(t) id := "user123" @@ -48,6 +49,7 @@ func TestCreateTokenPairSuccess(t *testing.T) { } func TestCreateTokenPairFailure(t *testing.T) { + t.Parallel() assert := assert.New(t) id := "user123" @@ -66,6 +68,7 @@ func TestCreateTokenPairFailure(t *testing.T) { } func TestCreateAccessTokenSuccess(t *testing.T) { + t.Parallel() assert := assert.New(t) id := "user123" @@ -83,6 +86,7 @@ func TestCreateAccessTokenSuccess(t *testing.T) { } func TestCreateAccessTokenFailure(t *testing.T) { + t.Parallel() assert := assert.New(t) id := "user123" @@ -100,6 +104,7 @@ func TestCreateAccessTokenFailure(t *testing.T) { } func TestCreateRefreshTokenSuccess(t *testing.T) { + t.Parallel() assert := assert.New(t) id := "user123" @@ -116,6 +121,7 @@ func TestCreateRefreshTokenSuccess(t *testing.T) { } func TestCreateRefreshTokenFailure(t *testing.T) { + t.Parallel() assert := assert.New(t) id := "" @@ -132,6 +138,7 @@ func TestCreateRefreshTokenFailure(t *testing.T) { } func TestSignTokenSuccess(t *testing.T) { + t.Parallel() assert := assert.New(t) token := jwt.New(jwt.SigningMethodHS256) @@ -157,6 +164,7 @@ func TestSignTokenSuccess(t *testing.T) { } func TestSignTokenFailure(t *testing.T) { + t.Parallel() assert := assert.New(t) token := jwt.New(jwt.SigningMethodHS256)