From ba564eacc75bb6d5be24a30396bb717ab175758a Mon Sep 17 00:00:00 2001 From: garrettladley Date: Mon, 15 Jan 2024 20:36:59 -0500 Subject: [PATCH 01/15] init --- backend/src/controllers/tag.go | 49 +++++++++++++++++++++++++++++++++ backend/src/server/server.go | 9 ++++++ backend/src/services/tag.go | 20 ++++++++++++++ backend/src/transactions/tag.go | 25 +++++++++++++++++ backend/tests/api/tag_test.go | 46 +++++++++++++++++++++++++++++++ 5 files changed, 149 insertions(+) create mode 100644 backend/src/controllers/tag.go create mode 100644 backend/src/services/tag.go create mode 100644 backend/src/transactions/tag.go create mode 100644 backend/tests/api/tag_test.go diff --git a/backend/src/controllers/tag.go b/backend/src/controllers/tag.go new file mode 100644 index 000000000..0cefe25e8 --- /dev/null +++ b/backend/src/controllers/tag.go @@ -0,0 +1,49 @@ +package controllers + +import ( + "backend/src/models" + "backend/src/services" + "backend/src/utilities" + + "github.com/gofiber/fiber/v2" +) + +type TagController struct { + tagService services.TagServiceInterface +} + +func NewTagController(tagService services.TagServiceInterface) *TagController { + return &TagController{tagService: tagService} +} + +// CreateTag godoc +// +// @Summary Creates a tag +// @Description Creates a tag +// @ID create-tag +// @Tags tag +// @Accept json +// @Produce json +// @Success 200 {object} string +// @Failure 400 {string} string "Failed to process the request" +// @Failure 400 {string} string "Failed to validate the data" +// @Failure 400 {string} string "Failed to create tag" +// @Router /api/v1/tags/create [post] +func (t *TagController) CreateTag(c *fiber.Ctx) error { + var tag models.Tag + + if err := c.BodyParser(&tag); err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Failed to process the request") + } + + if err := utilities.ValidateData(c, tag); err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Failed to validate the data") + } + + tag, err := t.tagService.CreateTag(tag) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Failed to create tag") + } + + return c.Status(fiber.StatusOK).JSON(tag) +} diff --git a/backend/src/server/server.go b/backend/src/server/server.go index 7785d1e98..ff6add9ef 100644 --- a/backend/src/server/server.go +++ b/backend/src/server/server.go @@ -30,6 +30,7 @@ func Init(db *gorm.DB) *fiber.App { apiv1 := app.Group("/api/v1") userRoutes(apiv1, &services.UserService{DB: db}) + tagRoutes(apiv1, &services.TagService{DB: db}) return app } @@ -63,3 +64,11 @@ func userRoutes(router fiber.Router, userService services.UserServiceInterface) users.Get("/", userController.GetAllUsers) } + +func tagRoutes(router fiber.Router, tagService services.TagServiceInterface) { + tagController := controllers.NewTagController(tagService) + + tags := router.Group("/tags") + + tags.Post("/create", tagController.CreateTag) +} diff --git a/backend/src/services/tag.go b/backend/src/services/tag.go new file mode 100644 index 000000000..9529f6355 --- /dev/null +++ b/backend/src/services/tag.go @@ -0,0 +1,20 @@ +package services + +import ( + "backend/src/models" + "backend/src/transactions" + + "gorm.io/gorm" +) + +type TagServiceInterface interface { + CreateTag(tag models.Tag) (models.Tag, error) +} + +type TagService struct { + DB *gorm.DB +} + +func (t *TagService) CreateTag(tag models.Tag) (models.Tag, error) { + return transactions.CreateTag(t.DB, tag) +} diff --git a/backend/src/transactions/tag.go b/backend/src/transactions/tag.go new file mode 100644 index 000000000..ac2175a58 --- /dev/null +++ b/backend/src/transactions/tag.go @@ -0,0 +1,25 @@ +package transactions + +import ( + "backend/src/models" + + "gorm.io/gorm" +) + +func CreateTag(db *gorm.DB, tag models.Tag) (models.Tag, error) { + if err := db.Create(&tag).Error; err != nil { + return models.Tag{}, err + } + + return tag, nil +} + +func GetTag(db *gorm.DB, id uint) (models.Tag, error) { + var tag models.Tag + + if err := db.First(&tag, id).Error; err != nil { + return models.Tag{}, err + } + + return tag, nil +} diff --git a/backend/tests/api/tag_test.go b/backend/tests/api/tag_test.go new file mode 100644 index 000000000..8c6cac745 --- /dev/null +++ b/backend/tests/api/tag_test.go @@ -0,0 +1,46 @@ +package tests + +import ( + "backend/src/models" + "backend/src/transactions" + "bytes" + "fmt" + "net/http/httptest" + "testing" + + "github.com/goccy/go-json" +) + +func TestCreateTagWorks(t *testing.T) { + app, assert := InitTest(t) + + data := map[string]interface{}{ + "name": "Generate", + "category_id": 1, + } + + body, err := json.Marshal(data) + + assert.NilError(err) + + req := httptest.NewRequest("POST", fmt.Sprintf("%s/api/v1/tags/create", app.Address), bytes.NewBuffer(body)) + req.Header.Set("Content-Type", "application/json") + + resp, err := app.App.Test(req) + + assert.NilError(err) + + assert.Equal(200, resp.StatusCode) + + var respTag models.Tag + + err = json.NewDecoder(resp.Body).Decode(&respTag) + + assert.NilError(err) + + dbTag, err := transactions.GetTag(app.Conn, respTag.ID) + + assert.NilError(err) + + assert.Equal(dbTag, respTag) +} From 6127a86dd124d25c865dbbbc282fad975ee10ca7 Mon Sep 17 00:00:00 2001 From: garrettladley Date: Mon, 15 Jan 2024 20:51:40 -0500 Subject: [PATCH 02/15] failure tests | misc fixes --- backend/src/controllers/tag.go | 4 +- backend/src/models/tag.go | 2 +- backend/tests/api/tag_test.go | 72 ++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 3 deletions(-) diff --git a/backend/src/controllers/tag.go b/backend/src/controllers/tag.go index 0cefe25e8..7c41a7081 100644 --- a/backend/src/controllers/tag.go +++ b/backend/src/controllers/tag.go @@ -27,7 +27,7 @@ func NewTagController(tagService services.TagServiceInterface) *TagController { // @Success 200 {object} string // @Failure 400 {string} string "Failed to process the request" // @Failure 400 {string} string "Failed to validate the data" -// @Failure 400 {string} string "Failed to create tag" +// @Failure 500 {string} string "Failed to create tag" // @Router /api/v1/tags/create [post] func (t *TagController) CreateTag(c *fiber.Ctx) error { var tag models.Tag @@ -42,7 +42,7 @@ func (t *TagController) CreateTag(c *fiber.Ctx) error { tag, err := t.tagService.CreateTag(tag) if err != nil { - return fiber.NewError(fiber.StatusBadRequest, "Failed to create tag") + return fiber.NewError(fiber.StatusInternalServerError, "Failed to create tag") } return c.Status(fiber.StatusOK).JSON(tag) diff --git a/backend/src/models/tag.go b/backend/src/models/tag.go index 142b04727..54b01b792 100644 --- a/backend/src/models/tag.go +++ b/backend/src/models/tag.go @@ -9,7 +9,7 @@ type Tag struct { Name string `gorm:"type:varchar(255)" json:"name" validate:"required"` - CategoryID uint `gorm:"foreignKey:CategoryID" json:"category_id" validate:"-"` + CategoryID uint `gorm:"foreignKey:CategoryID" json:"category_id" validate:"required"` User []User `gorm:"many2many:user_tags;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"users" validate:"-"` Club []Club `gorm:"many2many:club_tags;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"clubs" validate:"-"` diff --git a/backend/tests/api/tag_test.go b/backend/tests/api/tag_test.go index 8c6cac745..9ab3405d9 100644 --- a/backend/tests/api/tag_test.go +++ b/backend/tests/api/tag_test.go @@ -5,6 +5,7 @@ import ( "backend/src/transactions" "bytes" "fmt" + "io" "net/http/httptest" "testing" @@ -44,3 +45,74 @@ func TestCreateTagWorks(t *testing.T) { assert.Equal(dbTag, respTag) } + +func TestCreateTagFailsBadRequest(t *testing.T) { + app, assert := InitTest(t) + + badReqs := []map[string]interface{}{ + { + "name": "Generate", + "category_id": "1", + }, + { + "name": 1, + "category_id": 1, + }, + } + + for _, badReq := range badReqs { + body, err := json.Marshal(badReq) + + assert.NilError(err) + + req := httptest.NewRequest("POST", fmt.Sprintf("%s/api/v1/tags/create", app.Address), bytes.NewBuffer(body)) + req.Header.Set("Content-Type", "application/json") + + resp, err := app.App.Test(req) + + assert.NilError(err) + + assert.Equal(400, resp.StatusCode) + + body, err = io.ReadAll(resp.Body) + + assert.NilError(err) + + assert.Equal("Failed to process the request", string(body)) + } +} + +func TestCreateTagFailsValidation(t *testing.T) { + app, assert := InitTest(t) + + badReqs := []map[string]interface{}{ + { + "name": "Generate", + }, + { + "category_id": 1, + }, + {}, + } + + for _, badReq := range badReqs { + body, err := json.Marshal(badReq) + + assert.NilError(err) + + req := httptest.NewRequest("POST", fmt.Sprintf("%s/api/v1/tags/create", app.Address), bytes.NewBuffer(body)) + req.Header.Set("Content-Type", "application/json") + + resp, err := app.App.Test(req) + + assert.NilError(err) + + assert.Equal(400, resp.StatusCode) + + body, err = io.ReadAll(resp.Body) + + assert.NilError(err) + + assert.Equal("Failed to validate the data", string(body)) + } +} From 1f7eedfa849ac090548499a05ae1ef7830455278 Mon Sep 17 00:00:00 2001 From: garrettladley Date: Tue, 16 Jan 2024 22:43:50 -0500 Subject: [PATCH 03/15] addressed review --- backend/src/controllers/tag.go | 8 ++++---- backend/src/server/server.go | 2 +- backend/src/services/tag.go | 4 ++-- backend/src/transactions/tag.go | 6 +++--- backend/tests/api/tag_test.go | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/backend/src/controllers/tag.go b/backend/src/controllers/tag.go index 7c41a7081..234c92e0f 100644 --- a/backend/src/controllers/tag.go +++ b/backend/src/controllers/tag.go @@ -24,11 +24,11 @@ func NewTagController(tagService services.TagServiceInterface) *TagController { // @Tags tag // @Accept json // @Produce json -// @Success 200 {object} string +// @Success 201 {object} models.Tag // @Failure 400 {string} string "Failed to process the request" // @Failure 400 {string} string "Failed to validate the data" // @Failure 500 {string} string "Failed to create tag" -// @Router /api/v1/tags/create [post] +// @Router /api/v1/tags/ [post] func (t *TagController) CreateTag(c *fiber.Ctx) error { var tag models.Tag @@ -40,10 +40,10 @@ func (t *TagController) CreateTag(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusBadRequest, "Failed to validate the data") } - tag, err := t.tagService.CreateTag(tag) + dbTag, err := t.tagService.CreateTag(tag) if err != nil { return fiber.NewError(fiber.StatusInternalServerError, "Failed to create tag") } - return c.Status(fiber.StatusOK).JSON(tag) + return c.Status(fiber.StatusCreated).JSON(&dbTag) } diff --git a/backend/src/server/server.go b/backend/src/server/server.go index ff6add9ef..b0d3b7214 100644 --- a/backend/src/server/server.go +++ b/backend/src/server/server.go @@ -70,5 +70,5 @@ func tagRoutes(router fiber.Router, tagService services.TagServiceInterface) { tags := router.Group("/tags") - tags.Post("/create", tagController.CreateTag) + tags.Post("/", tagController.CreateTag) } diff --git a/backend/src/services/tag.go b/backend/src/services/tag.go index 9529f6355..9aba5e773 100644 --- a/backend/src/services/tag.go +++ b/backend/src/services/tag.go @@ -8,13 +8,13 @@ import ( ) type TagServiceInterface interface { - CreateTag(tag models.Tag) (models.Tag, error) + CreateTag(tag models.Tag) (*models.Tag, error) } type TagService struct { DB *gorm.DB } -func (t *TagService) CreateTag(tag models.Tag) (models.Tag, error) { +func (t *TagService) CreateTag(tag models.Tag) (*models.Tag, error) { return transactions.CreateTag(t.DB, tag) } diff --git a/backend/src/transactions/tag.go b/backend/src/transactions/tag.go index ac2175a58..d7597fe39 100644 --- a/backend/src/transactions/tag.go +++ b/backend/src/transactions/tag.go @@ -6,12 +6,12 @@ import ( "gorm.io/gorm" ) -func CreateTag(db *gorm.DB, tag models.Tag) (models.Tag, error) { +func CreateTag(db *gorm.DB, tag models.Tag) (*models.Tag, error) { if err := db.Create(&tag).Error; err != nil { - return models.Tag{}, err + return nil, err } - return tag, nil + return &tag, nil } func GetTag(db *gorm.DB, id uint) (models.Tag, error) { diff --git a/backend/tests/api/tag_test.go b/backend/tests/api/tag_test.go index 9ab3405d9..27bdb5609 100644 --- a/backend/tests/api/tag_test.go +++ b/backend/tests/api/tag_test.go @@ -31,7 +31,7 @@ func TestCreateTagWorks(t *testing.T) { assert.NilError(err) - assert.Equal(200, resp.StatusCode) + assert.Equal(201, resp.StatusCode) var respTag models.Tag From 4659deb0d5526abfb9489d26f86d782da8b22bef Mon Sep 17 00:00:00 2001 From: garrettladley Date: Tue, 16 Jan 2024 22:49:45 -0500 Subject: [PATCH 04/15] update tests --- backend/tests/api/tag_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/tests/api/tag_test.go b/backend/tests/api/tag_test.go index 27bdb5609..ac57c7cbe 100644 --- a/backend/tests/api/tag_test.go +++ b/backend/tests/api/tag_test.go @@ -24,7 +24,7 @@ func TestCreateTagWorks(t *testing.T) { assert.NilError(err) - req := httptest.NewRequest("POST", fmt.Sprintf("%s/api/v1/tags/create", app.Address), bytes.NewBuffer(body)) + req := httptest.NewRequest("POST", fmt.Sprintf("%s/api/v1/tags/", app.Address), bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") resp, err := app.App.Test(req) @@ -65,7 +65,7 @@ func TestCreateTagFailsBadRequest(t *testing.T) { assert.NilError(err) - req := httptest.NewRequest("POST", fmt.Sprintf("%s/api/v1/tags/create", app.Address), bytes.NewBuffer(body)) + req := httptest.NewRequest("POST", fmt.Sprintf("%s/api/v1/tags/", app.Address), bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") resp, err := app.App.Test(req) @@ -100,7 +100,7 @@ func TestCreateTagFailsValidation(t *testing.T) { assert.NilError(err) - req := httptest.NewRequest("POST", fmt.Sprintf("%s/api/v1/tags/create", app.Address), bytes.NewBuffer(body)) + req := httptest.NewRequest("POST", fmt.Sprintf("%s/api/v1/tags/", app.Address), bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") resp, err := app.App.Test(req) From ae5e6bdda2efb94eb24fc1d85f0e7c1a81ca1aad Mon Sep 17 00:00:00 2001 From: garrettladley Date: Thu, 18 Jan 2024 15:01:19 -0500 Subject: [PATCH 05/15] defer expensive transaction creation until necessary --- backend/src/database/db.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/backend/src/database/db.go b/backend/src/database/db.go index 1f91265a7..48954e10d 100644 --- a/backend/src/database/db.go +++ b/backend/src/database/db.go @@ -55,12 +55,6 @@ func MigrateDB(settings config.Settings, db *gorm.DB) error { return err } - tx := db.Begin() - - if err := tx.Error; err != nil { - return err - } - superUser := models.User{ Role: models.Super, NUID: "000000000", @@ -74,7 +68,13 @@ func MigrateDB(settings config.Settings, db *gorm.DB) error { var user models.User - if err := tx.Where("nuid = ?", superUser.NUID).First(&user).Error; err != nil { + if err := db.Where("nuid = ?", superUser.NUID).First(&user).Error; err != nil { + tx := db.Begin() + + if err := tx.Error; err != nil { + return err + } + if err := tx.Create(&superUser).Error; err != nil { tx.Rollback() return err @@ -99,7 +99,9 @@ func MigrateDB(settings config.Settings, db *gorm.DB) error { tx.Rollback() return err } - } - return tx.Commit().Error + return tx.Commit().Error + + } + return nil } From 5e70ee7e682d133c016777f4722132cc1389ef34 Mon Sep 17 00:00:00 2001 From: garrettladley Date: Thu, 18 Jan 2024 19:56:27 -0500 Subject: [PATCH 06/15] ALDER + GML CHANGES --- backend/src/controllers/tag.go | 21 +++++++++++---------- backend/src/models/tag.go | 5 +++++ backend/src/services/tag.go | 6 ++++++ backend/src/transactions/tag.go | 15 ++++++++++----- backend/src/utilities/validator.go | 3 +-- 5 files changed, 33 insertions(+), 17 deletions(-) diff --git a/backend/src/controllers/tag.go b/backend/src/controllers/tag.go index 234c92e0f..6047883bf 100644 --- a/backend/src/controllers/tag.go +++ b/backend/src/controllers/tag.go @@ -3,7 +3,6 @@ package controllers import ( "backend/src/models" "backend/src/services" - "backend/src/utilities" "github.com/gofiber/fiber/v2" ) @@ -25,24 +24,26 @@ func NewTagController(tagService services.TagServiceInterface) *TagController { // @Accept json // @Produce json // @Success 201 {object} models.Tag -// @Failure 400 {string} string "Failed to process the request" -// @Failure 400 {string} string "Failed to validate the data" -// @Failure 500 {string} string "Failed to create tag" +// @Failure 400 {string} string "failed to process the request" +// @Failure 400 {string} string "failed to validate the data" +// @Failure 500 {string} string "failed to create tag" // @Router /api/v1/tags/ [post] func (t *TagController) CreateTag(c *fiber.Ctx) error { - var tag models.Tag + var tag models.TagCreateRequestBody if err := c.BodyParser(&tag); err != nil { - return fiber.NewError(fiber.StatusBadRequest, "Failed to process the request") + return fiber.NewError(fiber.StatusBadRequest, "failed to process the request") } - if err := utilities.ValidateData(c, tag); err != nil { - return fiber.NewError(fiber.StatusBadRequest, "Failed to validate the data") + partialTag := models.Tag{ + Name: tag.Name, + CategoryID: tag.CategoryID, } - dbTag, err := t.tagService.CreateTag(tag) + dbTag, err := t.tagService.CreateTag(partialTag) + if err != nil { - return fiber.NewError(fiber.StatusInternalServerError, "Failed to create tag") + return err } return c.Status(fiber.StatusCreated).JSON(&dbTag) diff --git a/backend/src/models/tag.go b/backend/src/models/tag.go index 54b01b792..4aa48fbcf 100644 --- a/backend/src/models/tag.go +++ b/backend/src/models/tag.go @@ -15,3 +15,8 @@ type Tag struct { Club []Club `gorm:"many2many:club_tags;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"clubs" validate:"-"` Event []Event `gorm:"many2many:event_tags;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"events" validate:"-"` } + +type TagCreateRequestBody struct { + Name string `json:"name" validate:"required"` + CategoryID uint `json:"category_id" validate:"required"` +} diff --git a/backend/src/services/tag.go b/backend/src/services/tag.go index 9aba5e773..99fd7eed0 100644 --- a/backend/src/services/tag.go +++ b/backend/src/services/tag.go @@ -3,7 +3,9 @@ package services import ( "backend/src/models" "backend/src/transactions" + "backend/src/utilities" + "github.com/gofiber/fiber/v2" "gorm.io/gorm" ) @@ -16,5 +18,9 @@ type TagService struct { } func (t *TagService) CreateTag(tag models.Tag) (*models.Tag, error) { + if err := utilities.ValidateData(tag); err != nil { + return nil, fiber.NewError(fiber.StatusBadRequest, "failed to validate the data") + } + return transactions.CreateTag(t.DB, tag) } diff --git a/backend/src/transactions/tag.go b/backend/src/transactions/tag.go index d7597fe39..9b90b21fa 100644 --- a/backend/src/transactions/tag.go +++ b/backend/src/transactions/tag.go @@ -2,24 +2,29 @@ package transactions import ( "backend/src/models" + "errors" + "github.com/gofiber/fiber/v2" "gorm.io/gorm" ) func CreateTag(db *gorm.DB, tag models.Tag) (*models.Tag, error) { if err := db.Create(&tag).Error; err != nil { - return nil, err + return nil, fiber.NewError(fiber.StatusInternalServerError, "failed to create tag") } - return &tag, nil } -func GetTag(db *gorm.DB, id uint) (models.Tag, error) { +func GetTag(db *gorm.DB, id uint) (*models.Tag, error) { var tag models.Tag if err := db.First(&tag, id).Error; err != nil { - return models.Tag{}, err + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fiber.NewError(fiber.StatusBadRequest, "invalid tag id") + } else { + return nil, fiber.NewError(fiber.StatusInternalServerError, "unable to retrieve tag") + } } - return tag, nil + return &tag, nil } diff --git a/backend/src/utilities/validator.go b/backend/src/utilities/validator.go index a6dd65bbe..770097268 100644 --- a/backend/src/utilities/validator.go +++ b/backend/src/utilities/validator.go @@ -2,11 +2,10 @@ package utilities import ( "github.com/go-playground/validator/v10" - "github.com/gofiber/fiber/v2" ) // Validate the data sent to the server if the data is invalid, return an error otherwise, return nil -func ValidateData(c *fiber.Ctx, model interface{}) error { +func ValidateData(model interface{}) error { validate := validator.New(validator.WithRequiredStructEnabled()) if err := validate.Struct(model); err != nil { return err From f7622e825f40adf3c838537b8970af12efaebd09 Mon Sep 17 00:00:00 2001 From: garrettladley Date: Thu, 18 Jan 2024 20:05:45 -0500 Subject: [PATCH 07/15] defer resp.Body.Close() --- backend/tests/api/tag_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/tests/api/tag_test.go b/backend/tests/api/tag_test.go index ac57c7cbe..21bd1ba8b 100644 --- a/backend/tests/api/tag_test.go +++ b/backend/tests/api/tag_test.go @@ -74,6 +74,8 @@ func TestCreateTagFailsBadRequest(t *testing.T) { assert.Equal(400, resp.StatusCode) + defer resp.Body.Close() + body, err = io.ReadAll(resp.Body) assert.NilError(err) @@ -109,6 +111,8 @@ func TestCreateTagFailsValidation(t *testing.T) { assert.Equal(400, resp.StatusCode) + defer resp.Body.Close() + body, err = io.ReadAll(resp.Body) assert.NilError(err) From 3e9945a1234f21e87071715c55ea67397908b48d Mon Sep 17 00:00:00 2001 From: garrettladley Date: Sat, 20 Jan 2024 15:12:00 -0500 Subject: [PATCH 08/15] clean up | testing revolution --- backend/src/controllers/tag.go | 15 ++-- backend/src/models/tag.go | 2 +- backend/src/services/tag.go | 16 ++-- backend/src/transactions/tag.go | 3 +- backend/tests/api/category_test.go | 54 +++++++++--- backend/tests/api/helpers.go | 22 ++++- backend/tests/api/tag_test.go | 137 +++++++++++++---------------- 7 files changed, 139 insertions(+), 110 deletions(-) diff --git a/backend/src/controllers/tag.go b/backend/src/controllers/tag.go index 6047883bf..3d47bdac0 100644 --- a/backend/src/controllers/tag.go +++ b/backend/src/controllers/tag.go @@ -1,8 +1,8 @@ package controllers import ( - "backend/src/models" - "backend/src/services" + "github.com/GenerateNU/sac/backend/src/models" + "github.com/GenerateNU/sac/backend/src/services" "github.com/gofiber/fiber/v2" ) @@ -29,18 +29,13 @@ func NewTagController(tagService services.TagServiceInterface) *TagController { // @Failure 500 {string} string "failed to create tag" // @Router /api/v1/tags/ [post] func (t *TagController) CreateTag(c *fiber.Ctx) error { - var tag models.TagCreateRequestBody + var tagBody models.CreateTagRequestBody - if err := c.BodyParser(&tag); err != nil { + if err := c.BodyParser(&tagBody); err != nil { return fiber.NewError(fiber.StatusBadRequest, "failed to process the request") } - partialTag := models.Tag{ - Name: tag.Name, - CategoryID: tag.CategoryID, - } - - dbTag, err := t.tagService.CreateTag(partialTag) + dbTag, err := t.tagService.CreateTag(tagBody) if err != nil { return err diff --git a/backend/src/models/tag.go b/backend/src/models/tag.go index d38246bdd..bcb48a96d 100644 --- a/backend/src/models/tag.go +++ b/backend/src/models/tag.go @@ -16,7 +16,7 @@ type Tag struct { Event []Event `gorm:"many2many:event_tags;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"-"` } -type TagCreateRequestBody struct { +type CreateTagRequestBody struct { Name string `json:"name" validate:"required"` CategoryID uint `json:"category_id" validate:"required"` } diff --git a/backend/src/services/tag.go b/backend/src/services/tag.go index 99fd7eed0..a6093bdb5 100644 --- a/backend/src/services/tag.go +++ b/backend/src/services/tag.go @@ -1,23 +1,27 @@ package services import ( - "backend/src/models" - "backend/src/transactions" - "backend/src/utilities" - + "github.com/GenerateNU/sac/backend/src/models" + "github.com/GenerateNU/sac/backend/src/transactions" + "github.com/GenerateNU/sac/backend/src/utilities" "github.com/gofiber/fiber/v2" "gorm.io/gorm" ) type TagServiceInterface interface { - CreateTag(tag models.Tag) (*models.Tag, error) + CreateTag(partialTag models.CreateTagRequestBody) (*models.Tag, error) } type TagService struct { DB *gorm.DB } -func (t *TagService) CreateTag(tag models.Tag) (*models.Tag, error) { +func (t *TagService) CreateTag(partialTag models.CreateTagRequestBody) (*models.Tag, error) { + tag := models.Tag{ + Name: partialTag.Name, + CategoryID: partialTag.CategoryID, + } + if err := utilities.ValidateData(tag); err != nil { return nil, fiber.NewError(fiber.StatusBadRequest, "failed to validate the data") } diff --git a/backend/src/transactions/tag.go b/backend/src/transactions/tag.go index 9b90b21fa..ec261aa7e 100644 --- a/backend/src/transactions/tag.go +++ b/backend/src/transactions/tag.go @@ -1,9 +1,10 @@ package transactions import ( - "backend/src/models" "errors" + "github.com/GenerateNU/sac/backend/src/models" + "github.com/gofiber/fiber/v2" "gorm.io/gorm" ) diff --git a/backend/tests/api/category_test.go b/backend/tests/api/category_test.go index 4119dc3cb..b552fa244 100644 --- a/backend/tests/api/category_test.go +++ b/backend/tests/api/category_test.go @@ -73,6 +73,20 @@ func TestCreateCategoryIgnoresid(t *testing.T) { ) } +func AssertNoCategoryCreation(app TestApp, assert *assert.A, resp *http.Response) { + AssertNumCategoriesRemainsAtN(app, assert, resp, 0) +} + +func AssertNumCategoriesRemainsAtN(app TestApp, assert *assert.A, resp *http.Response, n int) { + var categories []models.Category + + err := app.Conn.Find(&categories).Error + + assert.NilError(err) + + assert.Equal(n, len(categories)) +} + func TestCreateCategoryFailsIfNameIsNotString(t *testing.T) { TestRequest{ Method: "POST", @@ -80,11 +94,15 @@ func TestCreateCategoryFailsIfNameIsNotString(t *testing.T) { Body: &map[string]interface{}{ "category_name": 1231, }, - }.TestOnStatusAndMessage(t, nil, - MessageWithStatus{ - Status: 400, - Message: "failed to process the request", - }) + }.TestOnStatusMessageAndDB(t, nil, + StatusMessageDBTester{ + MessageWithStatus: MessageWithStatus{ + Status: 400, + Message: "failed to process the request", + }, + DBTester: AssertNoCategoryCreation, + }, + ) } func TestCreateCategoryFailsIfNameIsMissing(t *testing.T) { @@ -92,10 +110,13 @@ func TestCreateCategoryFailsIfNameIsMissing(t *testing.T) { Method: "POST", Path: "/api/v1/categories/", Body: &map[string]interface{}{}, - }.TestOnStatusAndMessage(t, nil, - MessageWithStatus{ - Status: 400, - Message: "failed to validate the data", + }.TestOnStatusMessageAndDB(t, nil, + StatusMessageDBTester{ + MessageWithStatus: MessageWithStatus{ + Status: 400, + Message: "failed to validate the data", + }, + DBTester: AssertNoCategoryCreation, }, ) } @@ -105,6 +126,10 @@ func TestCreateCategoryFailsIfCategoryWithThatNameAlreadyExists(t *testing.T) { existingAppAssert := CreateSampleCategory(t, categoryName) + var TestNumCategoriesRemainsAt1 = func(app TestApp, assert *assert.A, resp *http.Response) { + AssertNumCategoriesRemainsAtN(app, assert, resp, 1) + } + for _, permutation := range AllCasingPermutations(categoryName) { fmt.Println(permutation) TestRequest{ @@ -113,10 +138,13 @@ func TestCreateCategoryFailsIfCategoryWithThatNameAlreadyExists(t *testing.T) { Body: &map[string]interface{}{ "category_name": permutation, }, - }.TestOnStatusAndMessageKeepDB(t, &existingAppAssert, - MessageWithStatus{ - Status: 400, - Message: "category with that name already exists", + }.TestOnStatusMessageAndDBKeepDB(t, &existingAppAssert, + StatusMessageDBTester{ + MessageWithStatus: MessageWithStatus{ + Status: 400, + Message: "category with that name already exists", + }, + DBTester: TestNumCategoriesRemainsAt1, }, ) } diff --git a/backend/tests/api/helpers.go b/backend/tests/api/helpers.go index 534f00f4e..144904269 100644 --- a/backend/tests/api/helpers.go +++ b/backend/tests/api/helpers.go @@ -213,10 +213,9 @@ type MessageWithStatus struct { Message string } -func (request TestRequest) TestOnStatusAndMessage(t *testing.T, existingAppAssert *ExistingAppAssert, messagedStatus MessageWithStatus) ExistingAppAssert { +func (request TestRequest) TestOnStatusAndMessage(t *testing.T, existingAppAssert *ExistingAppAssert, messagedStatus MessageWithStatus) { appAssert := request.TestOnStatusAndMessageKeepDB(t, existingAppAssert, messagedStatus) appAssert.App.DropDB() - return appAssert } func (request TestRequest) TestOnStatusAndMessageKeepDB(t *testing.T, existingAppAssert *ExistingAppAssert, messagedStatus MessageWithStatus) ExistingAppAssert { @@ -238,6 +237,22 @@ func (request TestRequest) TestOnStatusAndMessageKeepDB(t *testing.T, existingAp return appAssert } +type StatusMessageDBTester struct { + MessageWithStatus MessageWithStatus + DBTester DBTester +} + +func (request TestRequest) TestOnStatusMessageAndDB(t *testing.T, existingAppAssert *ExistingAppAssert, statusMessageDBTester StatusMessageDBTester) { + appAssert := request.TestOnStatusMessageAndDBKeepDB(t, existingAppAssert, statusMessageDBTester) + appAssert.App.DropDB() +} + +func (request TestRequest) TestOnStatusMessageAndDBKeepDB(t *testing.T, existingAppAssert *ExistingAppAssert, statusMessageDBTester StatusMessageDBTester) ExistingAppAssert { + appAssert := request.TestOnStatusAndMessageKeepDB(t, existingAppAssert, statusMessageDBTester.MessageWithStatus) + statusMessageDBTester.DBTester(appAssert.App, appAssert.Assert, nil) + return appAssert +} + type DBTester func(app TestApp, assert *assert.A, resp *http.Response) type DBTesterWithStatus struct { @@ -245,10 +260,9 @@ type DBTesterWithStatus struct { DBTester } -func (request TestRequest) TestOnStatusAndDB(t *testing.T, existingAppAssert *ExistingAppAssert, dbTesterStatus DBTesterWithStatus) ExistingAppAssert { +func (request TestRequest) TestOnStatusAndDB(t *testing.T, existingAppAssert *ExistingAppAssert, dbTesterStatus DBTesterWithStatus) { appAssert := request.TestOnStatusAndDBKeepDB(t, existingAppAssert, dbTesterStatus) appAssert.App.DropDB() - return appAssert } func (request TestRequest) TestOnStatusAndDBKeepDB(t *testing.T, existingAppAssert *ExistingAppAssert, dbTesterStatus DBTesterWithStatus) ExistingAppAssert { diff --git a/backend/tests/api/tag_test.go b/backend/tests/api/tag_test.go index 21bd1ba8b..5a0ab27c1 100644 --- a/backend/tests/api/tag_test.go +++ b/backend/tests/api/tag_test.go @@ -1,55 +1,57 @@ package tests import ( - "backend/src/models" - "backend/src/transactions" - "bytes" - "fmt" - "io" - "net/http/httptest" + "net/http" "testing" + "github.com/GenerateNU/sac/backend/src/models" + "github.com/GenerateNU/sac/backend/src/transactions" + "github.com/huandu/go-assert" + "github.com/goccy/go-json" ) func TestCreateTagWorks(t *testing.T) { - app, assert := InitTest(t) - - data := map[string]interface{}{ - "name": "Generate", - "category_id": 1, - } - - body, err := json.Marshal(data) - - assert.NilError(err) - - req := httptest.NewRequest("POST", fmt.Sprintf("%s/api/v1/tags/", app.Address), bytes.NewBuffer(body)) - req.Header.Set("Content-Type", "application/json") + existingAppAssert := CreateSampleCategory(t, "Science") + TestRequest{ + Method: "POST", + Path: "/api/v1/tags/", + Body: &map[string]interface{}{ + "name": "Generate", + "category_id": 1, + }, + }.TestOnStatusAndDB(t, &existingAppAssert, + DBTesterWithStatus{ + Status: 201, + DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { + var respTag models.Tag - resp, err := app.App.Test(req) + err := json.NewDecoder(resp.Body).Decode(&respTag) - assert.NilError(err) + assert.NilError(err) - assert.Equal(201, resp.StatusCode) + dbTag, err := transactions.GetTag(app.Conn, respTag.ID) - var respTag models.Tag + assert.NilError(err) - err = json.NewDecoder(resp.Body).Decode(&respTag) + assert.Equal(dbTag, &respTag) + }, + }, + ) +} - assert.NilError(err) +var AssertNoTagCreation = func(app TestApp, assert *assert.A, resp *http.Response) { + var tags []models.Tag - dbTag, err := transactions.GetTag(app.Conn, respTag.ID) + err := app.Conn.Find(&tags).Error assert.NilError(err) - assert.Equal(dbTag, respTag) + assert.Equal(0, len(tags)) } func TestCreateTagFailsBadRequest(t *testing.T) { - app, assert := InitTest(t) - - badReqs := []map[string]interface{}{ + badBodys := []map[string]interface{}{ { "name": "Generate", "category_id": "1", @@ -60,34 +62,25 @@ func TestCreateTagFailsBadRequest(t *testing.T) { }, } - for _, badReq := range badReqs { - body, err := json.Marshal(badReq) - - assert.NilError(err) - - req := httptest.NewRequest("POST", fmt.Sprintf("%s/api/v1/tags/", app.Address), bytes.NewBuffer(body)) - req.Header.Set("Content-Type", "application/json") - - resp, err := app.App.Test(req) - - assert.NilError(err) - - assert.Equal(400, resp.StatusCode) - - defer resp.Body.Close() - - body, err = io.ReadAll(resp.Body) - - assert.NilError(err) - - assert.Equal("Failed to process the request", string(body)) + for _, badBody := range badBodys { + TestRequest{ + Method: "POST", + Path: "/api/v1/tags/", + Body: &badBody, + }.TestOnStatusMessageAndDB(t, nil, + StatusMessageDBTester{ + MessageWithStatus: MessageWithStatus{ + Status: 400, + Message: "failed to process the request", + }, + DBTester: AssertNoTagCreation, + }, + ) } } func TestCreateTagFailsValidation(t *testing.T) { - app, assert := InitTest(t) - - badReqs := []map[string]interface{}{ + badBodys := []map[string]interface{}{ { "name": "Generate", }, @@ -97,26 +90,20 @@ func TestCreateTagFailsValidation(t *testing.T) { {}, } - for _, badReq := range badReqs { - body, err := json.Marshal(badReq) - - assert.NilError(err) - - req := httptest.NewRequest("POST", fmt.Sprintf("%s/api/v1/tags/", app.Address), bytes.NewBuffer(body)) - req.Header.Set("Content-Type", "application/json") - - resp, err := app.App.Test(req) - - assert.NilError(err) - - assert.Equal(400, resp.StatusCode) - - defer resp.Body.Close() - - body, err = io.ReadAll(resp.Body) - - assert.NilError(err) - - assert.Equal("Failed to validate the data", string(body)) + for _, badBody := range badBodys { + TestRequest{ + Method: "POST", + Path: "/api/v1/tags/", + Body: &badBody, + }.TestOnStatusMessageAndDB(t, nil, + StatusMessageDBTester{ + MessageWithStatus: MessageWithStatus{ + Status: 400, + Message: "failed to validate the data", + }, + DBTester: AssertNoTagCreation, + }, + ) } + } From fa44d3bca2afe28ffe06b73f59f66723d26a63d9 Mon Sep 17 00:00:00 2001 From: garrettladley Date: Sat, 20 Jan 2024 15:54:29 -0500 Subject: [PATCH 09/15] get route | utility scripts --- backend/src/controllers/tag.go | 23 +++++++++ backend/src/server/server.go | 1 + backend/src/services/tag.go | 11 +++++ backend/src/transactions/tag.go | 4 +- backend/src/utilities/validator.go | 21 +++++++++ backend/tests/api/category_test.go | 2 +- backend/tests/api/helpers.go | 3 +- backend/tests/api/tag_test.go | 75 ++++++++++++++++++++++++++++-- scripts/clean_old_test_dbs.sh | 17 +++++++ scripts/clean_prefixed_test_dbs.sh | 17 +++++++ 10 files changed, 165 insertions(+), 9 deletions(-) create mode 100755 scripts/clean_old_test_dbs.sh create mode 100755 scripts/clean_prefixed_test_dbs.sh diff --git a/backend/src/controllers/tag.go b/backend/src/controllers/tag.go index 3d47bdac0..15ef076d6 100644 --- a/backend/src/controllers/tag.go +++ b/backend/src/controllers/tag.go @@ -43,3 +43,26 @@ func (t *TagController) CreateTag(c *fiber.Ctx) error { return c.Status(fiber.StatusCreated).JSON(&dbTag) } + +// GetTag godoc +// +// @Summary Gets a tag +// @Description Returns a tag +// @ID get-tag +// @Tags tag +// @Produce json +// @Param id path int true "Tag ID" +// @Success 200 {object} models.Tag +// @Failure 400 {string} string "failed to validate id" +// @Failure 404 {string} string "faied to find tag" +// @Failure 500 {string} string "failed to retrieve tag" +// @Router /api/v1/tags/{id} [get] +func (t *TagController) GetTag(c *fiber.Ctx) error { + tag, err := t.tagService.GetTag(c.Params("id")) + + if err != nil { + return err + } + + return c.Status(fiber.StatusOK).JSON(&tag) +} diff --git a/backend/src/server/server.go b/backend/src/server/server.go index 7711bf788..3f8682d8d 100644 --- a/backend/src/server/server.go +++ b/backend/src/server/server.go @@ -81,4 +81,5 @@ func tagRoutes(router fiber.Router, tagService services.TagServiceInterface) { tags := router.Group("/tags") tags.Post("/", tagController.CreateTag) + tags.Get("/:id", tagController.GetTag) } diff --git a/backend/src/services/tag.go b/backend/src/services/tag.go index a6093bdb5..12457ed14 100644 --- a/backend/src/services/tag.go +++ b/backend/src/services/tag.go @@ -10,6 +10,7 @@ import ( type TagServiceInterface interface { CreateTag(partialTag models.CreateTagRequestBody) (*models.Tag, error) + GetTag(id string) (*models.Tag, error) } type TagService struct { @@ -28,3 +29,13 @@ func (t *TagService) CreateTag(partialTag models.CreateTagRequestBody) (*models. return transactions.CreateTag(t.DB, tag) } + +func (t *TagService) GetTag(id string) (*models.Tag, error) { + idAsUint, err := utilities.ValidateID(id) + + if err != nil { + return nil, err + } + + return transactions.GetTag(t.DB, *idAsUint) +} diff --git a/backend/src/transactions/tag.go b/backend/src/transactions/tag.go index ec261aa7e..ea844ecb5 100644 --- a/backend/src/transactions/tag.go +++ b/backend/src/transactions/tag.go @@ -21,9 +21,9 @@ func GetTag(db *gorm.DB, id uint) (*models.Tag, error) { if err := db.First(&tag, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, fiber.NewError(fiber.StatusBadRequest, "invalid tag id") + return nil, fiber.NewError(fiber.StatusNotFound, "failed to find tag") } else { - return nil, fiber.NewError(fiber.StatusInternalServerError, "unable to retrieve tag") + return nil, fiber.NewError(fiber.StatusInternalServerError, "failed to retrieve tag") } } diff --git a/backend/src/utilities/validator.go b/backend/src/utilities/validator.go index 770097268..a161587c6 100644 --- a/backend/src/utilities/validator.go +++ b/backend/src/utilities/validator.go @@ -1,7 +1,10 @@ package utilities import ( + "strconv" + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" ) // Validate the data sent to the server if the data is invalid, return an error otherwise, return nil @@ -13,3 +16,21 @@ func ValidateData(model interface{}) error { return nil } + +func ValidateID(id string) (*uint, error) { + idAsInt, err := strconv.Atoi(id) + + errMsg := "failed to validate id" + + if err != nil { + return nil, fiber.NewError(fiber.StatusBadRequest, errMsg) + } + + if idAsInt < 1 { // postgres ids start at 1 + return nil, fiber.NewError(fiber.StatusBadRequest, errMsg) + } + + idAsUint := uint(idAsInt) + + return &idAsUint, nil +} diff --git a/backend/tests/api/category_test.go b/backend/tests/api/category_test.go index b552fa244..27e74d0fa 100644 --- a/backend/tests/api/category_test.go +++ b/backend/tests/api/category_test.go @@ -122,7 +122,7 @@ func TestCreateCategoryFailsIfNameIsMissing(t *testing.T) { } func TestCreateCategoryFailsIfCategoryWithThatNameAlreadyExists(t *testing.T) { - categoryName := "Science" + categoryName := "foo" existingAppAssert := CreateSampleCategory(t, categoryName) diff --git a/backend/tests/api/helpers.go b/backend/tests/api/helpers.go index 144904269..9a4e838d7 100644 --- a/backend/tests/api/helpers.go +++ b/backend/tests/api/helpers.go @@ -78,8 +78,9 @@ func generateRandomInt(max int64) int64 { } func generateRandomDBName() string { + prefix := "sac_test_" letterBytes := "abcdefghijklmnopqrstuvwxyz" - length := 36 + length := len(prefix) + 36 result := make([]byte, length) for i := 0; i < length; i++ { result[i] = letterBytes[generateRandomInt(int64(len(letterBytes)))] diff --git a/backend/tests/api/tag_test.go b/backend/tests/api/tag_test.go index 5a0ab27c1..fd6206d20 100644 --- a/backend/tests/api/tag_test.go +++ b/backend/tests/api/tag_test.go @@ -1,6 +1,7 @@ package tests import ( + "fmt" "net/http" "testing" @@ -11,16 +12,16 @@ import ( "github.com/goccy/go-json" ) -func TestCreateTagWorks(t *testing.T) { - existingAppAssert := CreateSampleCategory(t, "Science") - TestRequest{ +func CreateSampleTag(t *testing.T, tagName string, categoryName string) ExistingAppAssert { + existingAppAssert := CreateSampleCategory(t, categoryName) + return TestRequest{ Method: "POST", Path: "/api/v1/tags/", Body: &map[string]interface{}{ - "name": "Generate", + "name": tagName, "category_id": 1, }, - }.TestOnStatusAndDB(t, &existingAppAssert, + }.TestOnStatusAndDBKeepDB(t, &existingAppAssert, DBTesterWithStatus{ Status: 201, DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { @@ -40,6 +41,11 @@ func TestCreateTagWorks(t *testing.T) { ) } +func TestCreateTagWorks(t *testing.T) { + appAssert := CreateSampleTag(t, "Generate", "Science") + appAssert.App.DropDB() +} + var AssertNoTagCreation = func(app TestApp, assert *assert.A, resp *http.Response) { var tags []models.Tag @@ -105,5 +111,64 @@ func TestCreateTagFailsValidation(t *testing.T) { }, ) } +} + +func TestGetTagWorks(t *testing.T) { + existingAppAssert := CreateSampleTag(t, "Generate", "Science") + + TestRequest{ + Method: "GET", + Path: "/api/v1/tags/1", + }.TestOnStatusAndDB(t, &existingAppAssert, + DBTesterWithStatus{ + Status: 200, + DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { + var respTag models.Tag + + err := json.NewDecoder(resp.Body).Decode(&respTag) + + assert.NilError(err) + + dbTag, err := transactions.GetTag(app.Conn, respTag.ID) + + assert.NilError(err) + + assert.Equal(dbTag, &respTag) + }, + }, + ) +} + +func TestGetTagFailsBadRequest(t *testing.T) { + badRequests := []string{ + "0", + "-1", + "1.1", + "foo", + "null", + } + + for _, badRequest := range badRequests { + TestRequest{ + Method: "GET", + Path: fmt.Sprintf("/api/v1/tags/%s", badRequest), + }.TestOnStatusAndMessage(t, nil, + MessageWithStatus{ + Status: 400, + Message: "failed to validate id", + }, + ) + } +} +func TestGetTagFailsNotFound(t *testing.T) { + TestRequest{ + Method: "GET", + Path: "/api/v1/tags/1", + }.TestOnStatusAndMessage(t, nil, + MessageWithStatus{ + Status: 404, + Message: "failed to find tag", + }, + ) } diff --git a/scripts/clean_old_test_dbs.sh b/scripts/clean_old_test_dbs.sh new file mode 100755 index 000000000..04c97262b --- /dev/null +++ b/scripts/clean_old_test_dbs.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +PGHOST="127.0.0.1" +PGPORT="5432" +PGUSER="postgres" +PGPASSWORD="postgres" +PGDATABASE="sac" + +DATABASES=$(psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d "$PGDATABASE" -t -c "SELECT datname FROM pg_database WHERE datistemplate = false AND datname != 'postgres' AND datname != 'template0' AND datname != '$PGDATABASE' AND datname !='$(whoami)';") + + +for db in $DATABASES; do + echo "Dropping database $db" + psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d "$PGDATABASE" -c "DROP DATABASE $db;" +done diff --git a/scripts/clean_prefixed_test_dbs.sh b/scripts/clean_prefixed_test_dbs.sh new file mode 100755 index 000000000..61797e999 --- /dev/null +++ b/scripts/clean_prefixed_test_dbs.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +PGHOST="127.0.0.1" +PGPORT="5432" +PGUSER="postgres" +PGPASSWORD="postgres" +PGDATABASE="sac" +PREFIX="sac_test_" + +DATABASES=$(psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d "$PGDATABASE" -t -c "SELECT datname FROM pg_database WHERE datistemplate = false AND datname like '$PREFIX%';") + +for db in $DATABASES; do + echo "Dropping database $db" + psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d "$PGDATABASE" -c "DROP DATABASE $db;" +done From ed18a94e0e9cb2b74018073345b8acd15084fae0 Mon Sep 17 00:00:00 2001 From: garrettladley Date: Sat, 20 Jan 2024 16:12:58 -0500 Subject: [PATCH 10/15] done --- backend/src/controllers/tag.go | 22 +++++++++++++ backend/src/server/server.go | 1 + backend/src/services/tag.go | 11 +++++++ backend/src/transactions/tag.go | 12 +++++++ backend/tests/api/helpers.go | 2 +- backend/tests/api/tag_test.go | 58 +++++++++++++++++++++++++++++++-- 6 files changed, 102 insertions(+), 4 deletions(-) diff --git a/backend/src/controllers/tag.go b/backend/src/controllers/tag.go index 15ef076d6..58e55afc8 100644 --- a/backend/src/controllers/tag.go +++ b/backend/src/controllers/tag.go @@ -66,3 +66,25 @@ func (t *TagController) GetTag(c *fiber.Ctx) error { return c.Status(fiber.StatusOK).JSON(&tag) } + +// DeleteTag godoc +// +// @Summary Deletes a tag +// @Description Deletes a tag +// @ID delete-tag +// @Tags tag +// @Param id path int true "Tag ID" +// @Success 204 {string} string "No Content" +// @Failure 400 {string} string "failed to validate id" +// @Failure 404 {string} string "failed to find tag" +// @Failure 500 {string} string "failed to delete tag" +// @Router /api/v1/tags/{id} [delete] +func (t *TagController) DeleteTag(c *fiber.Ctx) error { + err := t.tagService.DeleteTag(c.Params("id")) + + if err != nil { + return err + } + + return c.SendStatus(fiber.StatusNoContent) +} diff --git a/backend/src/server/server.go b/backend/src/server/server.go index 3f8682d8d..4f02f90a3 100644 --- a/backend/src/server/server.go +++ b/backend/src/server/server.go @@ -82,4 +82,5 @@ func tagRoutes(router fiber.Router, tagService services.TagServiceInterface) { tags.Post("/", tagController.CreateTag) tags.Get("/:id", tagController.GetTag) + tags.Delete("/:id", tagController.DeleteTag) } diff --git a/backend/src/services/tag.go b/backend/src/services/tag.go index 12457ed14..41d9ffdad 100644 --- a/backend/src/services/tag.go +++ b/backend/src/services/tag.go @@ -11,6 +11,7 @@ import ( type TagServiceInterface interface { CreateTag(partialTag models.CreateTagRequestBody) (*models.Tag, error) GetTag(id string) (*models.Tag, error) + DeleteTag(id string) error } type TagService struct { @@ -39,3 +40,13 @@ func (t *TagService) GetTag(id string) (*models.Tag, error) { return transactions.GetTag(t.DB, *idAsUint) } + +func (t *TagService) DeleteTag(id string) error { + idAsUint, err := utilities.ValidateID(id) + + if err != nil { + return err + } + + return transactions.DeleteTag(t.DB, *idAsUint) +} diff --git a/backend/src/transactions/tag.go b/backend/src/transactions/tag.go index ea844ecb5..6f033babe 100644 --- a/backend/src/transactions/tag.go +++ b/backend/src/transactions/tag.go @@ -29,3 +29,15 @@ func GetTag(db *gorm.DB, id uint) (*models.Tag, error) { return &tag, nil } + +func DeleteTag(db *gorm.DB, id uint) error { + if result := db.Delete(&models.Tag{}, id); result.RowsAffected == 0 { + if result.Error != nil { + return fiber.NewError(fiber.StatusInternalServerError, "failed to delete tag") + } else { + return fiber.NewError(fiber.StatusNotFound, "failed to find tag") + } + } + + return nil +} diff --git a/backend/tests/api/helpers.go b/backend/tests/api/helpers.go index 9a4e838d7..8fb641801 100644 --- a/backend/tests/api/helpers.go +++ b/backend/tests/api/helpers.go @@ -86,7 +86,7 @@ func generateRandomDBName() string { result[i] = letterBytes[generateRandomInt(int64(len(letterBytes)))] } - return string(result) + return fmt.Sprintf("%s%s", prefix, string(result)) } func configureDatabase(config config.Settings) (*gorm.DB, error) { diff --git a/backend/tests/api/tag_test.go b/backend/tests/api/tag_test.go index fd6206d20..706183be2 100644 --- a/backend/tests/api/tag_test.go +++ b/backend/tests/api/tag_test.go @@ -46,7 +46,7 @@ func TestCreateTagWorks(t *testing.T) { appAssert.App.DropDB() } -var AssertNoTagCreation = func(app TestApp, assert *assert.A, resp *http.Response) { +var AssertNoTags = func(app TestApp, assert *assert.A, resp *http.Response) { var tags []models.Tag err := app.Conn.Find(&tags).Error @@ -79,7 +79,7 @@ func TestCreateTagFailsBadRequest(t *testing.T) { Status: 400, Message: "failed to process the request", }, - DBTester: AssertNoTagCreation, + DBTester: AssertNoTags, }, ) } @@ -107,7 +107,7 @@ func TestCreateTagFailsValidation(t *testing.T) { Status: 400, Message: "failed to validate the data", }, - DBTester: AssertNoTagCreation, + DBTester: AssertNoTags, }, ) } @@ -137,6 +137,8 @@ func TestGetTagWorks(t *testing.T) { }, }, ) + + existingAppAssert.App.DropDB() } func TestGetTagFailsBadRequest(t *testing.T) { @@ -172,3 +174,53 @@ func TestGetTagFailsNotFound(t *testing.T) { }, ) } + +func TestDeleteTagWorks(t *testing.T) { + existingAppAssert := CreateSampleTag(t, "Generate", "Science") + + TestRequest{ + Method: "DELETE", + Path: "/api/v1/tags/1", + }.TestOnStatusAndDB(t, &existingAppAssert, + DBTesterWithStatus{ + Status: 204, + DBTester: AssertNoTags, + }, + ) + + existingAppAssert.App.DropDB() +} + +func TestDeleteTagFailsBadRequest(t *testing.T) { + badRequests := []string{ + "0", + "-1", + "1.1", + "foo", + "null", + } + + for _, badRequest := range badRequests { + TestRequest{ + Method: "DELETE", + Path: fmt.Sprintf("/api/v1/tags/%s", badRequest), + }.TestOnStatusAndMessage(t, nil, + MessageWithStatus{ + Status: 400, + Message: "failed to validate id", + }, + ) + } +} + +func TestDeleteTagFailsNotFound(t *testing.T) { + TestRequest{ + Method: "DELETE", + Path: "/api/v1/tags/1", + }.TestOnStatusAndMessage(t, nil, + MessageWithStatus{ + Status: 404, + Message: "failed to find tag", + }, + ) +} From 71ec057054aadcfa7a2a46046ca9c09d9b609431 Mon Sep 17 00:00:00 2001 From: garrettladley Date: Sat, 20 Jan 2024 16:54:22 -0500 Subject: [PATCH 11/15] done --- backend/src/controllers/tag.go | 29 ++++++- backend/src/models/tag.go | 14 ++- backend/src/server/server.go | 1 + backend/src/services/tag.go | 20 +++++ backend/src/transactions/tag.go | 12 +++ backend/tests/api/category_test.go | 8 +- backend/tests/api/tag_test.go | 134 +++++++++++++++++++++++++++-- 7 files changed, 204 insertions(+), 14 deletions(-) diff --git a/backend/src/controllers/tag.go b/backend/src/controllers/tag.go index 58e55afc8..05c2540b2 100644 --- a/backend/src/controllers/tag.go +++ b/backend/src/controllers/tag.go @@ -67,6 +67,33 @@ func (t *TagController) GetTag(c *fiber.Ctx) error { return c.Status(fiber.StatusOK).JSON(&tag) } +// UpdateTag godoc +// +// @Summary Updates a tag +// @Description Updates a tag +// @ID update-tag +// @Tags tag +// @Accept json +// @Produce json +// @Param id path int true "Tag ID" +// @Success 204 +// @Failure 400 {string} string "failed to process the request" +func (t *TagController) UpdateTag(c *fiber.Ctx) error { + var tagBody models.UpdateTagRequestBody + + if err := c.BodyParser(&tagBody); err != nil { + return fiber.NewError(fiber.StatusBadRequest, "failed to process the request") + } + + err := t.tagService.UpdateTag(c.Params("id"), tagBody) + + if err != nil { + return err + } + + return c.SendStatus(fiber.StatusNoContent) +} + // DeleteTag godoc // // @Summary Deletes a tag @@ -74,7 +101,7 @@ func (t *TagController) GetTag(c *fiber.Ctx) error { // @ID delete-tag // @Tags tag // @Param id path int true "Tag ID" -// @Success 204 {string} string "No Content" +// @Success 204 // @Failure 400 {string} string "failed to validate id" // @Failure 404 {string} string "failed to find tag" // @Failure 500 {string} string "failed to delete tag" diff --git a/backend/src/models/tag.go b/backend/src/models/tag.go index bcb48a96d..767b3d37b 100644 --- a/backend/src/models/tag.go +++ b/backend/src/models/tag.go @@ -9,14 +9,22 @@ type Tag struct { Name string `gorm:"type:varchar(255)" json:"name" validate:"required,max=255"` - CategoryID uint `gorm:"foreignKey:CategoryID" json:"category_id" validate:"required"` + CategoryID uint `gorm:"foreignKey:CategoryID" json:"category_id" validate:"required,min=1"` User []User `gorm:"many2many:user_tags;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"-"` Club []Club `gorm:"many2many:club_tags;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"-"` Event []Event `gorm:"many2many:event_tags;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"-"` } +type PartialTag struct { + Name string `json:"name" validate:"required,max=255"` + CategoryID uint `json:"category_id" validate:"required,min=1"` +} + type CreateTagRequestBody struct { - Name string `json:"name" validate:"required"` - CategoryID uint `json:"category_id" validate:"required"` + PartialTag +} + +type UpdateTagRequestBody struct { + PartialTag } diff --git a/backend/src/server/server.go b/backend/src/server/server.go index 4f02f90a3..a59011369 100644 --- a/backend/src/server/server.go +++ b/backend/src/server/server.go @@ -82,5 +82,6 @@ func tagRoutes(router fiber.Router, tagService services.TagServiceInterface) { tags.Post("/", tagController.CreateTag) tags.Get("/:id", tagController.GetTag) + tags.Patch("/:id", tagController.UpdateTag) tags.Delete("/:id", tagController.DeleteTag) } diff --git a/backend/src/services/tag.go b/backend/src/services/tag.go index 41d9ffdad..915ded92a 100644 --- a/backend/src/services/tag.go +++ b/backend/src/services/tag.go @@ -11,6 +11,7 @@ import ( type TagServiceInterface interface { CreateTag(partialTag models.CreateTagRequestBody) (*models.Tag, error) GetTag(id string) (*models.Tag, error) + UpdateTag(id string, partialTag models.UpdateTagRequestBody) error DeleteTag(id string) error } @@ -41,6 +42,25 @@ func (t *TagService) GetTag(id string) (*models.Tag, error) { return transactions.GetTag(t.DB, *idAsUint) } +func (t *TagService) UpdateTag(id string, partialTag models.UpdateTagRequestBody) error { + idAsUint, err := utilities.ValidateID(id) + + if err != nil { + return err + } + + tag := models.Tag{ + Name: partialTag.Name, + CategoryID: partialTag.CategoryID, + } + + if err := utilities.ValidateData(tag); err != nil { + return fiber.NewError(fiber.StatusBadRequest, "failed to validate the data") + } + + return transactions.UpdateTag(t.DB, *idAsUint, tag) +} + func (t *TagService) DeleteTag(id string) error { idAsUint, err := utilities.ValidateID(id) diff --git a/backend/src/transactions/tag.go b/backend/src/transactions/tag.go index 6f033babe..609275a15 100644 --- a/backend/src/transactions/tag.go +++ b/backend/src/transactions/tag.go @@ -30,6 +30,18 @@ func GetTag(db *gorm.DB, id uint) (*models.Tag, error) { return &tag, nil } +func UpdateTag(db *gorm.DB, id uint, tag models.Tag) error { + if err := db.Model(&models.Tag{}).Where("id = ?", id).Updates(tag).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return fiber.NewError(fiber.StatusNotFound, "failed to find tag") + } else { + return fiber.NewError(fiber.StatusInternalServerError, "failed to update tag") + } + } + + return nil +} + func DeleteTag(db *gorm.DB, id uint) error { if result := db.Delete(&models.Tag{}, id); result.RowsAffected == 0 { if result.Error != nil { diff --git a/backend/tests/api/category_test.go b/backend/tests/api/category_test.go index 27e74d0fa..e8d9f92b8 100644 --- a/backend/tests/api/category_test.go +++ b/backend/tests/api/category_test.go @@ -12,14 +12,14 @@ import ( "github.com/goccy/go-json" ) -func CreateSampleCategory(t *testing.T, categoryName string) ExistingAppAssert { +func CreateSampleCategory(t *testing.T, categoryName string, existingAppAssert *ExistingAppAssert) ExistingAppAssert { return TestRequest{ Method: "POST", Path: "/api/v1/categories/", Body: &map[string]interface{}{ "category_name": categoryName, }, - }.TestOnStatusAndDBKeepDB(t, nil, + }.TestOnStatusAndDBKeepDB(t, existingAppAssert, DBTesterWithStatus{ Status: 201, DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { @@ -41,7 +41,7 @@ func CreateSampleCategory(t *testing.T, categoryName string) ExistingAppAssert { } func TestCreateCategoryWorks(t *testing.T) { - appAssert := CreateSampleCategory(t, "Science") + appAssert := CreateSampleCategory(t, "Science", nil) appAssert.App.DropDB() } @@ -124,7 +124,7 @@ func TestCreateCategoryFailsIfNameIsMissing(t *testing.T) { func TestCreateCategoryFailsIfCategoryWithThatNameAlreadyExists(t *testing.T) { categoryName := "foo" - existingAppAssert := CreateSampleCategory(t, categoryName) + existingAppAssert := CreateSampleCategory(t, categoryName, nil) var TestNumCategoriesRemainsAt1 = func(app TestApp, assert *assert.A, resp *http.Response) { AssertNumCategoriesRemainsAtN(app, assert, resp, 1) diff --git a/backend/tests/api/tag_test.go b/backend/tests/api/tag_test.go index 706183be2..3cdf72f8c 100644 --- a/backend/tests/api/tag_test.go +++ b/backend/tests/api/tag_test.go @@ -12,8 +12,14 @@ import ( "github.com/goccy/go-json" ) -func CreateSampleTag(t *testing.T, tagName string, categoryName string) ExistingAppAssert { - existingAppAssert := CreateSampleCategory(t, categoryName) +func CreateSampleTag(t *testing.T, tagName string, categoryName string, existingAppAssert *ExistingAppAssert) ExistingAppAssert { + var appAssert ExistingAppAssert + + if existingAppAssert == nil { + appAssert = CreateSampleCategory(t, categoryName, existingAppAssert) + } else { + appAssert = CreateSampleCategory(t, categoryName, existingAppAssert) + } return TestRequest{ Method: "POST", Path: "/api/v1/tags/", @@ -21,7 +27,7 @@ func CreateSampleTag(t *testing.T, tagName string, categoryName string) Existing "name": tagName, "category_id": 1, }, - }.TestOnStatusAndDBKeepDB(t, &existingAppAssert, + }.TestOnStatusAndDBKeepDB(t, &appAssert, DBTesterWithStatus{ Status: 201, DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { @@ -42,7 +48,7 @@ func CreateSampleTag(t *testing.T, tagName string, categoryName string) Existing } func TestCreateTagWorks(t *testing.T) { - appAssert := CreateSampleTag(t, "Generate", "Science") + appAssert := CreateSampleTag(t, "Generate", "Science", nil) appAssert.App.DropDB() } @@ -114,7 +120,7 @@ func TestCreateTagFailsValidation(t *testing.T) { } func TestGetTagWorks(t *testing.T) { - existingAppAssert := CreateSampleTag(t, "Generate", "Science") + existingAppAssert := CreateSampleTag(t, "Generate", "Science", nil) TestRequest{ Method: "GET", @@ -175,8 +181,124 @@ func TestGetTagFailsNotFound(t *testing.T) { ) } +func TestUpdateTagWorksUpdateName(t *testing.T) { + existingAppAssert := CreateSampleTag(t, "Generate", "Science", nil) + + TestRequest{ + Method: "PATCH", + Path: "/api/v1/tags/1", + Body: &map[string]interface{}{ + "name": "GenerateNU", + "category_id": 1, + }, + }.TestOnStatusAndDB(t, &existingAppAssert, + DBTesterWithStatus{ + Status: 204, + DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { + var dbTag models.Tag + + err := app.Conn.First(&dbTag).Error + + assert.NilError(err) + + assert.Equal("GenerateNU", dbTag.Name) + }, + }, + ) + + existingAppAssert.App.DropDB() +} + +func TestUpdateTagWorksUpdateCategory(t *testing.T) { + existingAppAssert := CreateSampleTag(t, "Generate", "Science", nil) + existingAppAssert = CreateSampleCategory(t, "Technology", &existingAppAssert) + + TestRequest{ + Method: "PATCH", + Path: "/api/v1/tags/1", + Body: &map[string]interface{}{ + "name": "Generate", + "category_id": 2, + }, + }.TestOnStatusAndDB(t, &existingAppAssert, + DBTesterWithStatus{ + Status: 204, + DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { + var dbTag models.Tag + + err := app.Conn.First(&dbTag).Error + + assert.NilError(err) + + assert.Equal("Generate", dbTag.Name) + assert.Equal(uint(2), dbTag.CategoryID) + }, + }, + ) + + existingAppAssert.App.DropDB() +} + +func TestUpdateTagWorksWithSameDetails(t *testing.T) { + existingAppAssert := CreateSampleTag(t, "Generate", "Science", nil) + + TestRequest{ + Method: "PATCH", + Path: "/api/v1/tags/1", + Body: &map[string]interface{}{ + "name": "Generate", + "category_id": 1, + }, + }.TestOnStatusAndDB(t, &existingAppAssert, + DBTesterWithStatus{ + Status: 204, + DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { + var dbTag models.Tag + + err := app.Conn.First(&dbTag).Error + + assert.NilError(err) + + assert.Equal("Generate", dbTag.Name) + assert.Equal(uint(1), dbTag.CategoryID) + }, + }, + ) + + existingAppAssert.App.DropDB() +} + +func TestUpdateTagFailsBadRequest(t *testing.T) { + badBodys := []map[string]interface{}{ + { + "name": "Generate", + "category_id": "1", + }, + { + "name": 1, + "category_id": 1, + }, + } + + for _, badBody := range badBodys { + TestRequest{ + Method: "PATCH", + Path: "/api/v1/tags/1", + Body: &badBody, + }.TestOnStatusMessageAndDB(t, nil, + StatusMessageDBTester{ + MessageWithStatus: MessageWithStatus{ + Status: 400, + Message: "failed to process the request", + }, + DBTester: AssertNoTags, + }, + ) + } +} + func TestDeleteTagWorks(t *testing.T) { - existingAppAssert := CreateSampleTag(t, "Generate", "Science") + existingAppAssert := CreateSampleTag(t, "Generate", "Science", nil) TestRequest{ Method: "DELETE", From bff3a71bdb83cd0fcb9be11aa7c5b815e9b2ee4f Mon Sep 17 00:00:00 2001 From: garrettladley Date: Sat, 20 Jan 2024 23:20:24 -0500 Subject: [PATCH 12/15] addressed review --- backend/src/controllers/tag.go | 5 +++++ backend/src/services/tag.go | 16 ++++++++-------- backend/src/utilities/validator.go | 1 + 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/backend/src/controllers/tag.go b/backend/src/controllers/tag.go index 05c2540b2..53b7a799d 100644 --- a/backend/src/controllers/tag.go +++ b/backend/src/controllers/tag.go @@ -78,6 +78,11 @@ func (t *TagController) GetTag(c *fiber.Ctx) error { // @Param id path int true "Tag ID" // @Success 204 // @Failure 400 {string} string "failed to process the request" +// @Failure 400 {string} string "failed to validate id" +// @Failure 400 {string} string "failed to validate the data" +// @Failure 404 {string} string "failed to find tag" +// @Failure 500 {string} string "failed to update tag" +// @Router /api/v1/tags/{id} [patch] func (t *TagController) UpdateTag(c *fiber.Ctx) error { var tagBody models.UpdateTagRequestBody diff --git a/backend/src/services/tag.go b/backend/src/services/tag.go index 915ded92a..fb693f499 100644 --- a/backend/src/services/tag.go +++ b/backend/src/services/tag.go @@ -9,9 +9,9 @@ import ( ) type TagServiceInterface interface { - CreateTag(partialTag models.CreateTagRequestBody) (*models.Tag, error) + CreateTag(tagBody models.CreateTagRequestBody) (*models.Tag, error) GetTag(id string) (*models.Tag, error) - UpdateTag(id string, partialTag models.UpdateTagRequestBody) error + UpdateTag(id string, tagBody models.UpdateTagRequestBody) error DeleteTag(id string) error } @@ -19,10 +19,10 @@ type TagService struct { DB *gorm.DB } -func (t *TagService) CreateTag(partialTag models.CreateTagRequestBody) (*models.Tag, error) { +func (t *TagService) CreateTag(tagBody models.CreateTagRequestBody) (*models.Tag, error) { tag := models.Tag{ - Name: partialTag.Name, - CategoryID: partialTag.CategoryID, + Name: tagBody.Name, + CategoryID: tagBody.CategoryID, } if err := utilities.ValidateData(tag); err != nil { @@ -42,7 +42,7 @@ func (t *TagService) GetTag(id string) (*models.Tag, error) { return transactions.GetTag(t.DB, *idAsUint) } -func (t *TagService) UpdateTag(id string, partialTag models.UpdateTagRequestBody) error { +func (t *TagService) UpdateTag(id string, tagBody models.UpdateTagRequestBody) error { idAsUint, err := utilities.ValidateID(id) if err != nil { @@ -50,8 +50,8 @@ func (t *TagService) UpdateTag(id string, partialTag models.UpdateTagRequestBody } tag := models.Tag{ - Name: partialTag.Name, - CategoryID: partialTag.CategoryID, + Name: tagBody.Name, + CategoryID: tagBody.CategoryID, } if err := utilities.ValidateData(tag); err != nil { diff --git a/backend/src/utilities/validator.go b/backend/src/utilities/validator.go index a161587c6..07ef87d87 100644 --- a/backend/src/utilities/validator.go +++ b/backend/src/utilities/validator.go @@ -17,6 +17,7 @@ func ValidateData(model interface{}) error { return nil } +// Validates that an id follows postgres uint format, returns a uint otherwise returns an error func ValidateID(id string) (*uint, error) { idAsInt, err := strconv.Atoi(id) From 377330b15bf4582b1ff276ca89a775675802cdac Mon Sep 17 00:00:00 2001 From: garrettladley Date: Sun, 21 Jan 2024 00:21:04 -0500 Subject: [PATCH 13/15] rename and clean up --- backend/tests/api/category_test.go | 6 +++--- backend/tests/api/tag_test.go | 8 +------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/backend/tests/api/category_test.go b/backend/tests/api/category_test.go index a43039a8c..c9dbea0a2 100644 --- a/backend/tests/api/category_test.go +++ b/backend/tests/api/category_test.go @@ -72,7 +72,7 @@ func TestCreateCategoryIgnoresid(t *testing.T) { ) } -func AssertNoCategoryCreation(app TestApp, assert *assert.A, resp *http.Response) { +func AssertNoCategories(app TestApp, assert *assert.A, resp *http.Response) { AssertNumCategoriesRemainsAtN(app, assert, resp, 0) } @@ -99,7 +99,7 @@ func TestCreateCategoryFailsIfNameIsNotString(t *testing.T) { Status: 400, Message: "failed to process the request", }, - DBTester: AssertNoCategoryCreation, + DBTester: AssertNoCategories, }, ) } @@ -115,7 +115,7 @@ func TestCreateCategoryFailsIfNameIsMissing(t *testing.T) { Status: 400, Message: "failed to validate the data", }, - DBTester: AssertNoCategoryCreation, + DBTester: AssertNoCategories, }, ) } diff --git a/backend/tests/api/tag_test.go b/backend/tests/api/tag_test.go index c08e8d92f..080fa8025 100644 --- a/backend/tests/api/tag_test.go +++ b/backend/tests/api/tag_test.go @@ -48,7 +48,7 @@ func CreateSampleTag(t *testing.T, tagName string, categoryName string, existing } func TestCreateTagWorks(t *testing.T) { - appAssert := CreateSampleTag(t, "Generate", "Science", nil) + CreateSampleTag(t, "Generate", "Science", nil) } var AssertNoTags = func(app TestApp, assert *assert.A, resp *http.Response) { @@ -202,8 +202,6 @@ func TestUpdateTagWorksUpdateName(t *testing.T) { }, }, ) - - existingAppAssert.App.DropDB() } func TestUpdateTagWorksUpdateCategory(t *testing.T) { @@ -232,8 +230,6 @@ func TestUpdateTagWorksUpdateCategory(t *testing.T) { }, }, ) - - existingAppAssert.App.DropDB() } func TestUpdateTagWorksWithSameDetails(t *testing.T) { @@ -261,8 +257,6 @@ func TestUpdateTagWorksWithSameDetails(t *testing.T) { }, }, ) - - existingAppAssert.App.DropDB() } func TestUpdateTagFailsBadRequest(t *testing.T) { From 1d0d1c8eb8c483acc50ec370b1f5b703ad2e46e4 Mon Sep 17 00:00:00 2001 From: garrettladley Date: Sun, 21 Jan 2024 11:48:52 -0500 Subject: [PATCH 14/15] update tag success 204 -> 200 w/ models.Tag --- backend/src/controllers/tag.go | 6 +- backend/src/services/tag.go | 8 +-- backend/src/transactions/tag.go | 11 ++-- backend/tests/api/category_test.go | 2 - backend/tests/api/tag_test.go | 94 +++++++++--------------------- 5 files changed, 41 insertions(+), 80 deletions(-) diff --git a/backend/src/controllers/tag.go b/backend/src/controllers/tag.go index ecf8da9e6..fda5186f9 100644 --- a/backend/src/controllers/tag.go +++ b/backend/src/controllers/tag.go @@ -76,7 +76,7 @@ func (t *TagController) GetTag(c *fiber.Ctx) error { // @Accept json // @Produce json // @Param id path int true "Tag ID" -// @Success 204 +// @Success 200 {object} models.Tag // @Failure 400 {string} string "failed to process the request" // @Failure 400 {string} string "failed to validate id" // @Failure 400 {string} string "failed to validate the data" @@ -90,13 +90,13 @@ func (t *TagController) UpdateTag(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusBadRequest, "failed to process the request") } - err := t.tagService.UpdateTag(c.Params("id"), tagBody) + tag, err := t.tagService.UpdateTag(c.Params("id"), tagBody) if err != nil { return err } - return c.SendStatus(fiber.StatusNoContent) + return c.Status(fiber.StatusOK).JSON(&tag) } // DeleteTag godoc diff --git a/backend/src/services/tag.go b/backend/src/services/tag.go index fb693f499..dac2203b3 100644 --- a/backend/src/services/tag.go +++ b/backend/src/services/tag.go @@ -11,7 +11,7 @@ import ( type TagServiceInterface interface { CreateTag(tagBody models.CreateTagRequestBody) (*models.Tag, error) GetTag(id string) (*models.Tag, error) - UpdateTag(id string, tagBody models.UpdateTagRequestBody) error + UpdateTag(id string, tagBody models.UpdateTagRequestBody) (*models.Tag, error) DeleteTag(id string) error } @@ -42,11 +42,11 @@ func (t *TagService) GetTag(id string) (*models.Tag, error) { return transactions.GetTag(t.DB, *idAsUint) } -func (t *TagService) UpdateTag(id string, tagBody models.UpdateTagRequestBody) error { +func (t *TagService) UpdateTag(id string, tagBody models.UpdateTagRequestBody) (*models.Tag, error) { idAsUint, err := utilities.ValidateID(id) if err != nil { - return err + return nil, err } tag := models.Tag{ @@ -55,7 +55,7 @@ func (t *TagService) UpdateTag(id string, tagBody models.UpdateTagRequestBody) e } if err := utilities.ValidateData(tag); err != nil { - return fiber.NewError(fiber.StatusBadRequest, "failed to validate the data") + return nil, fiber.NewError(fiber.StatusBadRequest, "failed to validate the data") } return transactions.UpdateTag(t.DB, *idAsUint, tag) diff --git a/backend/src/transactions/tag.go b/backend/src/transactions/tag.go index 609275a15..b1f51f7cb 100644 --- a/backend/src/transactions/tag.go +++ b/backend/src/transactions/tag.go @@ -30,16 +30,17 @@ func GetTag(db *gorm.DB, id uint) (*models.Tag, error) { return &tag, nil } -func UpdateTag(db *gorm.DB, id uint, tag models.Tag) error { - if err := db.Model(&models.Tag{}).Where("id = ?", id).Updates(tag).Error; err != nil { +func UpdateTag(db *gorm.DB, id uint, tag models.Tag) (*models.Tag, error) { + if err := db.Model(&models.Tag{}).Where("id = ?", id).Updates(tag).First(&tag).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - return fiber.NewError(fiber.StatusNotFound, "failed to find tag") + return nil, fiber.NewError(fiber.StatusNotFound, "failed to find tag") } else { - return fiber.NewError(fiber.StatusInternalServerError, "failed to update tag") + return nil, fiber.NewError(fiber.StatusInternalServerError, "failed to update tag") } } - return nil + return &tag, nil + } func DeleteTag(db *gorm.DB, id uint) error { diff --git a/backend/tests/api/category_test.go b/backend/tests/api/category_test.go index c9dbea0a2..83e6e0b2d 100644 --- a/backend/tests/api/category_test.go +++ b/backend/tests/api/category_test.go @@ -1,7 +1,6 @@ package tests import ( - "fmt" "net/http" "testing" @@ -130,7 +129,6 @@ func TestCreateCategoryFailsIfCategoryWithThatNameAlreadyExists(t *testing.T) { } for _, permutation := range AllCasingPermutations(categoryName) { - fmt.Println(permutation) TestRequest{ Method: "POST", Path: "/api/v1/categories/", diff --git a/backend/tests/api/tag_test.go b/backend/tests/api/tag_test.go index 080fa8025..2b35f7820 100644 --- a/backend/tests/api/tag_test.go +++ b/backend/tests/api/tag_test.go @@ -12,14 +12,26 @@ import ( "github.com/goccy/go-json" ) +var AssertRespTagSameAsDBTag = func(app TestApp, assert *assert.A, resp *http.Response) { + var respTag models.Tag + + err := json.NewDecoder(resp.Body).Decode(&respTag) + + assert.NilError(err) + + fmt.Printf("respTag: %+v\n", respTag) + fmt.Printf("respTag.ID: %+v\n", respTag.ID) + + dbTag, err := transactions.GetTag(app.Conn, respTag.ID) + + assert.NilError(err) + + assert.Equal(dbTag, &respTag) +} + func CreateSampleTag(t *testing.T, tagName string, categoryName string, existingAppAssert *ExistingAppAssert) ExistingAppAssert { - var appAssert ExistingAppAssert + appAssert := CreateSampleCategory(t, categoryName, existingAppAssert) - if existingAppAssert == nil { - appAssert = CreateSampleCategory(t, categoryName, existingAppAssert) - } else { - appAssert = CreateSampleCategory(t, categoryName, existingAppAssert) - } return TestRequest{ Method: "POST", Path: "/api/v1/tags/", @@ -29,20 +41,8 @@ func CreateSampleTag(t *testing.T, tagName string, categoryName string, existing }, }.TestOnStatusAndDB(t, &appAssert, DBTesterWithStatus{ - Status: 201, - DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { - var respTag models.Tag - - err := json.NewDecoder(resp.Body).Decode(&respTag) - - assert.NilError(err) - - dbTag, err := transactions.GetTag(app.Conn, respTag.ID) - - assert.NilError(err) - - assert.Equal(dbTag, &respTag) - }, + Status: 201, + DBTester: AssertRespTagSameAsDBTag, }, ) } @@ -126,20 +126,8 @@ func TestGetTagWorks(t *testing.T) { Path: "/api/v1/tags/1", }.TestOnStatusAndDB(t, &existingAppAssert, DBTesterWithStatus{ - Status: 200, - DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { - var respTag models.Tag - - err := json.NewDecoder(resp.Body).Decode(&respTag) - - assert.NilError(err) - - dbTag, err := transactions.GetTag(app.Conn, respTag.ID) - - assert.NilError(err) - - assert.Equal(dbTag, &respTag) - }, + Status: 200, + DBTester: AssertRespTagSameAsDBTag, }, ) } @@ -190,16 +178,8 @@ func TestUpdateTagWorksUpdateName(t *testing.T) { }, }.TestOnStatusAndDB(t, &existingAppAssert, DBTesterWithStatus{ - Status: 204, - DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { - var dbTag models.Tag - - err := app.Conn.First(&dbTag).Error - - assert.NilError(err) - - assert.Equal("GenerateNU", dbTag.Name) - }, + Status: 200, + DBTester: AssertRespTagSameAsDBTag, }, ) } @@ -217,17 +197,8 @@ func TestUpdateTagWorksUpdateCategory(t *testing.T) { }, }.TestOnStatusAndDB(t, &existingAppAssert, DBTesterWithStatus{ - Status: 204, - DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { - var dbTag models.Tag - - err := app.Conn.First(&dbTag).Error - - assert.NilError(err) - - assert.Equal("Generate", dbTag.Name) - assert.Equal(uint(2), dbTag.CategoryID) - }, + Status: 200, + DBTester: AssertRespTagSameAsDBTag, }, ) } @@ -244,17 +215,8 @@ func TestUpdateTagWorksWithSameDetails(t *testing.T) { }, }.TestOnStatusAndDB(t, &existingAppAssert, DBTesterWithStatus{ - Status: 204, - DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { - var dbTag models.Tag - - err := app.Conn.First(&dbTag).Error - - assert.NilError(err) - - assert.Equal("Generate", dbTag.Name) - assert.Equal(uint(1), dbTag.CategoryID) - }, + Status: 200, + DBTester: AssertRespTagSameAsDBTag, }, ) } From 4bb96ef9d84d03b06512d00bd8150ce3bb0431e7 Mon Sep 17 00:00:00 2001 From: garrettladley Date: Sun, 21 Jan 2024 11:49:55 -0500 Subject: [PATCH 15/15] fix update gorm statement --- backend/src/transactions/tag.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/transactions/tag.go b/backend/src/transactions/tag.go index b1f51f7cb..8ba91210a 100644 --- a/backend/src/transactions/tag.go +++ b/backend/src/transactions/tag.go @@ -31,7 +31,7 @@ func GetTag(db *gorm.DB, id uint) (*models.Tag, error) { } func UpdateTag(db *gorm.DB, id uint, tag models.Tag) (*models.Tag, error) { - if err := db.Model(&models.Tag{}).Where("id = ?", id).Updates(tag).First(&tag).Error; err != nil { + if err := db.Model(&models.Tag{}).Where("id = ?", id).Updates(tag).First(&tag, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "failed to find tag") } else {