Skip to content

Commit

Permalink
SAC-15 Retrieve Tag GET (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
garrettladley authored Jan 21, 2024
1 parent cde14a8 commit d71fcce
Show file tree
Hide file tree
Showing 11 changed files with 181 additions and 19 deletions.
23 changes: 23 additions & 0 deletions backend/src/controllers/tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
4 changes: 2 additions & 2 deletions backend/src/models/tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ type Tag struct {
}

type CreateTagRequestBody struct {
Name string `json:"name" validate:"required"`
CategoryID uint `json:"category_id" validate:"required"`
Name string `json:"name" validate:"required,max=255"`
CategoryID uint `json:"category_id" validate:"required,min=1"`
}
1 change: 1 addition & 0 deletions backend/src/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
19 changes: 15 additions & 4 deletions backend/src/services/tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,18 @@ import (
)

type TagServiceInterface interface {
CreateTag(partialTag models.CreateTagRequestBody) (*models.Tag, error)
CreateTag(tagBody models.CreateTagRequestBody) (*models.Tag, error)
GetTag(id string) (*models.Tag, error)
}

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 {
Expand All @@ -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)
}
4 changes: 2 additions & 2 deletions backend/src/transactions/tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}

Expand Down
21 changes: 21 additions & 0 deletions backend/src/utilities/validator.go
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
}
10 changes: 5 additions & 5 deletions backend/tests/api/category_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
}.TestOnStatusAndDB(t, nil,
}.TestOnStatusAndDB(t, existingAppAssert,
DBTesterWithStatus{
Status: 201,
DBTester: func(app TestApp, assert *assert.A, resp *http.Response) {
Expand All @@ -41,7 +41,7 @@ func CreateSampleCategory(t *testing.T, categoryName string) ExistingAppAssert {
}

func TestCreateCategoryWorks(t *testing.T) {
CreateSampleCategory(t, "Science")
CreateSampleCategory(t, "Science", nil)
}

func TestCreateCategoryIgnoresid(t *testing.T) {
Expand Down Expand Up @@ -121,9 +121,9 @@ func TestCreateCategoryFailsIfNameIsMissing(t *testing.T) {
}

func TestCreateCategoryFailsIfCategoryWithThatNameAlreadyExists(t *testing.T) {
categoryName := "Science"
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)
Expand Down
4 changes: 3 additions & 1 deletion backend/tests/api/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)))]
Expand Down Expand Up @@ -229,6 +230,7 @@ type DBTesterWithStatus struct {
DBTester
}


func (request TestRequest) TestOnStatusAndDB(t *testing.T, existingAppAssert *ExistingAppAssert, dbTesterStatus DBTesterWithStatus) ExistingAppAssert {
appAssert, resp := request.TestWithJSONBody(t, existingAppAssert)
app, assert := appAssert.App, appAssert.Assert
Expand Down
80 changes: 75 additions & 5 deletions backend/tests/api/tag_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package tests

import (
"fmt"
"net/http"
"testing"

Expand All @@ -11,16 +12,22 @@ 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) 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/",
Body: &map[string]interface{}{
"name": "Generate",
"name": tagName,
"category_id": 1,
},
}.TestOnStatusAndDB(t, &existingAppAssert,
}.TestOnStatusAndDB(t, &appAssert,
DBTesterWithStatus{
Status: 201,
DBTester: func(app TestApp, assert *assert.A, resp *http.Response) {
Expand All @@ -40,6 +47,10 @@ func TestCreateTagWorks(t *testing.T) {
)
}

func TestCreateTagWorks(t *testing.T) {
CreateSampleTag(t, "Generate", "Science", nil)
}

var AssertNoTagCreation = func(app TestApp, assert *assert.A, resp *http.Response) {
var tags []models.Tag

Expand Down Expand Up @@ -105,5 +116,64 @@ func TestCreateTagFailsValidation(t *testing.T) {
},
)
}
}

func TestGetTagWorks(t *testing.T) {
existingAppAssert := CreateSampleTag(t, "Generate", "Science", nil)

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",
},
)
}
17 changes: 17 additions & 0 deletions scripts/clean_old_test_dbs.sh
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions scripts/clean_prefixed_test_dbs.sh
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit d71fcce

Please sign in to comment.