Skip to content

Commit

Permalink
ContactCrudTests pt2
Browse files Browse the repository at this point in the history
  • Loading branch information
ojn03 committed Feb 6, 2024
1 parent 0ab3153 commit 7fb839a
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 51 deletions.
15 changes: 15 additions & 0 deletions backend/src/controllers/club.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ func (l *ClubController) DeleteClub(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusNoContent)
}

// func (l* ClubController) GetContact(c *fiber.Ctx) error {

// }

func (l *ClubController) GetContacts(c *fiber.Ctx) error {
defaultLimit := 10
defaultPage := 1
Expand All @@ -97,6 +101,17 @@ func (l *ClubController) GetClubContacts(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON(contacts)
}

// TODO fix godoc
// PutContact godoc
//
// @Summary Creates a contact for a club, if it does not exist, other wise updates an existing contact
// @Description Returns the updated contact
// @ID put-contact
// @Tags club
// @Produce json
// @Success 200 {object} []models.Contact
// @Failure 400 {string} string "failed to update contact"
// @Router /api/v1/users/ [put]
func (l *ClubController) PutContact(c *fiber.Ctx) error {
var contactBody models.PutContactRequestBody

Expand Down
7 changes: 3 additions & 4 deletions backend/src/models/contact.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,13 @@ type Contact struct {
Model

Type ContactType `gorm:"type:varchar(255);uniqueIndex:idx_contact_type" json:"type" validate:"required,max=255,oneof=facebook instagram twitter linkedin youtube github slack discord email customSite"`
Content string `gorm:"type:varchar(255);" json:"content" validate:"required,max=255"`
Content string `gorm:"type:varchar(255)" json:"content" validate:"required,max=255"`

ClubID uuid.UUID `gorm:"foreignKey:ClubID;uniqueIndex:idx_contact_type" json:"-" validate:"uuid4"`
}

type PutContactRequestBody struct {
Type ContactType `gorm:"type:varchar(255)" json:"type" validate:"required,max=255,oneof=facebook instagram twitter linkedin youtube github slack discord email customSite"`
Type ContactType `gorm:"type:varchar(255)" json:"type" validate:"required,max=255,oneof=facebook instagram twitter linkedin youtube github slack discord email customSite,contact_pointer"`

//TODO content validator
Content string `gorm:"type:varchar(255)" json:"content" validate:"required,s3_url,max=255"`
Content string `gorm:"type:varchar(255)" json:"content" validate:"required,max=255"`
}
10 changes: 10 additions & 0 deletions backend/src/services/club.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type ClubServiceInterface interface {
DeleteClub(id string) *errors.Error
GetContacts(limit string, page string) ([]models.Contact, *errors.Error)
GetClubContacts(id string) ([]models.Contact, *errors.Error)
// GetClubContact(contactId string) (*models.Contact, *errors.Error)
PutContact(clubID string, contactBody models.PutContactRequestBody) (*models.Contact, *errors.Error)
DeleteContact(id string) *errors.Error
}
Expand Down Expand Up @@ -121,6 +122,15 @@ func (c *ClubService) GetClubContacts(id string) ([]models.Contact, *errors.Erro
return transactions.GetClubContacts(c.DB, *idAsUUID)
}

// func (c *ClubService) GetClubContact(contactId string) (*models.Contact, *errors.Error) {
// idAsUUID, err := utilities.ValidateID(contactId)
// if err != nil {
// return nil, &errors.FailedToValidateID
// }

// return transactions.GetClubContact(c.DB, *idAsUUID)
// }

func (c *ClubService) PutContact(clubID string, contactBody models.PutContactRequestBody) (*models.Contact, *errors.Error) {
idAsUUID, idErr := utilities.ValidateID(clubID)
if idErr != nil {
Expand Down
17 changes: 12 additions & 5 deletions backend/src/transactions/club.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"gorm.io/gorm"
"gorm.io/gorm/clause"
)

func GetClubs(db *gorm.DB, limit int, offset int) ([]models.Club, *errors.Error) {
var clubs []models.Club
result := db.Limit(limit).Offset(offset).Find(&clubs)
Expand Down Expand Up @@ -60,8 +61,6 @@ func GetClub(db *gorm.DB, id uuid.UUID) (*models.Club, *errors.Error) {
return &club, nil
}



func GetContacts(db *gorm.DB, limit int, offset int) ([]models.Contact, *errors.Error) {
var contacts []models.Contact
result := db.Limit(limit).Offset(offset).Find(&contacts)
Expand Down Expand Up @@ -90,11 +89,18 @@ func GetClubContacts(db *gorm.DB, id uuid.UUID) ([]models.Contact, *errors.Error
}

func PutContact(db *gorm.DB, clubID uuid.UUID, contact models.Contact) (*models.Contact, *errors.Error) {
// if the club already has a contact of the same type, update the existing contact
err := db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "club_id"}, {Name: "type"}},
DoUpdates: clause.AssignmentColumns([]string{"content"}),
}).Create(&contact).Error

if err != nil {

// if the foreign key (clubID) constraint is violated, return a club not found error
if stdliberrors.Is(err, gorm.ErrForeignKeyViolated) {
return nil, &errors.ClubNotFound
}
return nil, &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.FailedToUpdateContact.Message}
}
return &contact, nil
Expand All @@ -103,10 +109,11 @@ func PutContact(db *gorm.DB, clubID uuid.UUID, contact models.Contact) (*models.
func DeleteContact(db *gorm.DB, id uuid.UUID) *errors.Error {
result := db.Delete(&models.Contact{}, id)
if result.Error != nil {
return &errors.FailedToDeleteClub }

return nil
return &errors.FailedToDeleteClub
}

return nil
}
func UpdateClub(db *gorm.DB, id uuid.UUID, club models.Club) (*models.Club, *errors.Error) {
result := db.Model(&models.User{}).Where("id = ?", id).Updates(club)
if result.Error != nil {
Expand Down
5 changes: 2 additions & 3 deletions backend/src/utilities/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,11 @@ func validateS3URL(fl validator.FieldLevel) bool {
}

func validateContactPointer(validate *validator.Validate, fl validator.FieldLevel) bool {
contact, ok := fl.Parent().Interface().(models.Contact)

contact, ok := fl.Parent().Interface().(models.PutContactRequestBody)
if !ok {
println("Failed to cast to PutContactRequestBody")
return false
}

switch contact.Type {
case models.Email:
return validate.Var(contact.Content, "email") == nil
Expand Down
176 changes: 137 additions & 39 deletions backend/tests/api/club_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -486,27 +486,54 @@ test contact creation fails with invalid data
- type
- missing
- not a valid type
- content
- not a valid url
- missing
- club_id
- inexistent clubId
TODO should a club be allowed multiple links of the same type?
R- test get contacts works
test delete contact works
*/

func SampleContactFactory(clubUUID uuid.UUID) *map[string]interface{} {
func SampleContactFactory() *map[string]interface{} {
return &map[string]interface{}{
"type": "email",
"content": "[email protected]",
"club_id": clubUUID,
}
}

func ManyContactsFactory() *map[string]map[string]interface{} {
arr := make(map[string]map[string]interface{})

arr["email"] = map[string]interface{}{
"type": "email",
"content": "[email protected]",
}

arr["youtube"] = map[string]interface{}{
"type": "youtube",
"content": "youtube.com/cheeseClub",
}

arr["facebook"] = map[string]interface{}{
"type": "facebook",
"content": "facebook.com/cheeseClub",
}

arr["discord"] = map[string]interface{}{
"type": "discord",
"content": "discord.com/cheeseClub",
}

arr["instagram"] = map[string]interface{}{
"type": "instagram",
"content": "instagram.com/cheeseClub",
}
arr["github"]= map[string]interface{}{
"type": "github",
"content": "github.com/cheeseClub",
}

return &arr
}

func AssertContactBodyRespDB(app TestApp, assert *assert.A, resp *http.Response, body *map[string]interface{}) uuid.UUID {
var respContact models.Contact

Expand All @@ -518,11 +545,11 @@ func AssertContactBodyRespDB(app TestApp, assert *assert.A, resp *http.Response,
var dbContacts []models.Contact

// get all contacts from the database ordered by created_at and store them in dbContacts
err = app.Conn.Order("created_at desc").Find(&dbContacts).Error
// err = app.Conn.Order("created_at desc").Find(&dbContacts).Error

assert.NilError(err)

assert.Equal(1, len(dbContacts))
// assert.Equal(1, len(dbContacts))

dbContact := dbContacts[0]

Expand All @@ -534,18 +561,35 @@ func AssertContactBodyRespDB(app TestApp, assert *assert.A, resp *http.Response,
}

func AssertSampleContactBodyRespDB(app TestApp, assert *assert.A, resp *http.Response, clubUUID uuid.UUID) uuid.UUID {
return AssertContactBodyRespDB(app, assert, resp, SampleContactFactory(clubUUID))
return AssertContactBodyRespDB(app, assert, resp, SampleContactFactory())
}

func CreateSampleContact(t *testing.T, existingAppAssert *ExistingAppAssert) (eaa ExistingAppAssert, clubUUID uuid.UUID, contactUUID uuid.UUID) {
appAssert, _, clubUUID := CreateSampleClub(t, nil)

for _, contact := range *ManyContactsFactory() {

appAssert = TestRequest{
Method: fiber.MethodPut,
Path: fmt.Sprintf("/api/v1/clubs/%s/contacts", clubUUID),
Body: &contact,
}.TestOnStatusAndDB(t, &appAssert,
DBTesterWithStatus{
Status: fiber.StatusOK,
DBTester: func(app TestApp, assert *assert.A, resp *http.Response) {
AssertContactBodyRespDB(app, assert, resp, &contact)
},
},
)
}


var sampleContactUUID uuid.UUID

newAppAssert := TestRequest{
Method: fiber.MethodPut,
Path: fmt.Sprintf("/api/v1/clubs/%s/contacts", clubUUID),
Body: SampleContactFactory(clubUUID),
Body: SampleContactFactory(),
}.TestOnStatusAndDB(t, &appAssert,
DBTesterWithStatus{
Status: fiber.StatusOK,
Expand Down Expand Up @@ -585,7 +629,7 @@ func AssertCreateBadContactDataFails(t *testing.T, jsonKey string, badValues []i
appAssert, _, clubUUID := CreateSampleClub(t, nil)

for _, badValue := range badValues {
sampleContactPermutation := *SampleContactFactory(clubUUID)
sampleContactPermutation := *SampleContactFactory()
sampleContactPermutation[jsonKey] = badValue

TestRequest{
Expand Down Expand Up @@ -622,50 +666,104 @@ func TestCreateContactFailsOnInvalidContent(t *testing.T) {
)
}

func TestPutContactFailsOnClubIDNotExist(t *testing.T) {
appAssert, _, _ := CreateSampleContact(t, nil)
func TestPutContactFailsOnClubIdNotExist(t *testing.T) {
appAssert, _, _ := CreateSampleClub(t, nil)

uuid := uuid.New()

TestRequest{
Method: fiber.MethodPut,
Path: fmt.Sprintf("/api/v1/clubs/%s/contacts", uuid),
Body: SampleContactFactory(uuid),
Body: SampleContactFactory(),
}.TestOnErrorAndDB(t, &appAssert,
ErrorWithDBTester{
Error: errors.ClubNotFound,
DBTester: func(app TestApp, assert *assert.A, resp *http.Response) {
var contact models.Contact
var club models.Club

err := app.Conn.Where("id = ?", uuid).First(&contact).Error
err := app.Conn.Where("id = ?", uuid).First(&club).Error

assert.Assert(stdliberrors.Is(err, gorm.ErrRecordNotFound))

AssertNumContactsRemainsAtN(app, assert, resp, 0)
},
},
).Close()
}

func TestPutContactFailsOnClubIdNotExist(t *testing.T) {
appAssert, _, clubUUID := CreateSampleClub(t, nil)
// if a club already has a contact of the same type, the new contact should replace the old one
// func TestPutContactUpdatesExistingContact(t *testing.T){
// appAssert, clubUUID, contactUUID := CreateSampleContact(t, nil)

uuid := uuid.New()
// updatedContact := SampleContactFactory()
// (*updatedContact)["content"] = ""

TestRequest{
Method: fiber.MethodPut,
Path: fmt.Sprintf("/api/v1/clubs/%s/contacts", uuid),
Body: SampleContactFactory(clubUUID),
}.TestOnErrorAndDB(t, &appAssert,
ErrorWithDBTester{
Error: errors.ClubNotFound,
DBTester: func(app TestApp, assert *assert.A, resp *http.Response) {
var club models.Club
// }

err := app.Conn.Where("id = ?", uuid).First(&club).Error
// func TestGetContactByIdWorks(t *testing.T) {
// appAssert, clubUUID, contactUUID := CreateSampleContact(t, nil)

assert.Assert(stdliberrors.Is(err, gorm.ErrRecordNotFound))
},
},
).Close()
}
// TestRequest{
// Method: fiber.MethodGet,
// Path: fmt.Sprintf("/api/v1/clubs/%s/contacts", clubUUID),
// }.TestOnStatusAndDB(t, &appAssert,
// DBTesterWithStatus{
// Status: fiber.StatusOK,
// DBTester: func(app TestApp, assert *assert.A, resp *http.Response) {
// var respContacts []models.Contact

// err := json.NewDecoder(resp.Body).Decode(&respContacts)

// assert.NilError(err)

// assert.Equal(1, len(respContacts))

// respContact := respContacts[0]

// var dbContacts []models.Contact

// err = app.Conn.Order("created_at desc").Find(&dbContacts).Error

// assert.NilError(err)

// assert.Equal(1, len(dbContacts))

// dbContact := dbContacts[0]

// assert.Equal(dbContact.ID, respContact.ID)
// assert.Equal(dbContact.Type, respContact.Type)
// assert.Equal(dbContact.Content, respContact.Content)
// },
// },
// ).Close()
// }

// func TestGetContactsByClubIDWorks(t *testing.T ){
// appAssert, club1, contact1 := CreateSampleContact(t, nil)

// appAssert, club

// appAssert = appTestRequest{
// Method: fiber.MethodGet,
// Path: fmt.Sprintf("/api/v1/clubs/%s/contacts/%s", clubUUID, contactUUID),
// }.TestOnStatusAndDB(t, &appAssert,
// DBTesterWithStatus{
// Status: fiber.StatusOK,
// DBTester: func(app TestApp, assert *assert.A, resp *http.Response) {
// var respContact models.Contact

// err := json.NewDecoder(resp.Body).Decode(&respContact)

// assert.NilError(err)

// var dbContact models.Contact

// err = app.Conn.Order("created_at desc").First(&dbContact).Error

// assert.NilError(err)

// assert.Equal(dbContact.ID, respContact.ID)
// assert.Equal(dbContact.Type, respContact.Type)
// assert.Equal(dbContact.Content, respContact.Content)
// },
// },
// )
// }

0 comments on commit 7fb839a

Please sign in to comment.