Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: get single product #149

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion internal/models/product.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
type Product struct {
ID string `gorm:"type:uuid;primaryKey" json:"product_id"`
Name string `gorm:"column:name; type:varchar(255); not null" json:"name"`
Price float64 `gorm:"column:price; type:decimal(10,2);null" json:"price"`
Price float64 `gorm:"column:price; type:decimal(10,2);not null; default:0" json:"price"`
Description string `gorm:"column:description; type:text" json:"description"`
OwnerID string `gorm:"type:uuid;" json:"owner_id"`
Category []Category `gorm:"many2many:product_categories;;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" json:"category"`
Expand Down Expand Up @@ -42,3 +42,13 @@ func (p *Product) AddProductToCategory(db *gorm.DB, categories []interface{}) er
}
return nil
}

func (p *Product) GetProduct(db *gorm.DB, id string) (Product, error) {
var product Product
err := db.Preload("Category").Model(p).First(&product, "id = ?", id).Error
if err != nil {
return Product{}, err
}

return product, nil
}
31 changes: 31 additions & 0 deletions pkg/controller/product/product.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package product

import (
"net/http"
"regexp"

"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
Expand Down Expand Up @@ -52,3 +53,33 @@ func (base *Controller) CreateProduct(c *gin.Context) {

c.JSON(code, rd)
}

func (base *Controller) GetProduct(c *gin.Context) {
productId := c.Param("product_id")

matched, err := regexp.MatchString("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", productId)
if err != nil {
rd := utility.BuildErrorResponse(http.StatusInternalServerError, "error", err.Error(), "An unexpected error occured", nil)
c.JSON(http.StatusInternalServerError, rd)
return
}

if !matched {
rd := utility.BuildErrorResponse(http.StatusBadRequest, "error", "", "Invalid product ID", nil)
c.JSON(http.StatusBadRequest, rd)
return
}


respData, code, err := product.GetProduct(productId, base.Db.Postgresql)
if err != nil {
rd := utility.BuildErrorResponse(code, "error", err.Error(), "Product not found", nil)
c.JSON(code, rd)
return
}

base.Logger.Info("Product found successfully")
rd := utility.BuildSuccessResponse(http.StatusOK, "Product found successfully", respData)

c.JSON(code, rd)
}
1 change: 1 addition & 0 deletions pkg/router/product.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func Product(r *gin.Engine, ApiVersion string, validator *validator.Validate, db

productUrl := r.Group(fmt.Sprintf("%v", ApiVersion), middleware.Authorize(db.Postgresql))
{
productUrl.GET("/products/:product_id", product.GetProduct)
productUrl.POST("/products", product.CreateProduct)
}
return r
Expand Down
24 changes: 24 additions & 0 deletions services/product/product.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package product

import (
"errors"
"net/http"
"strings"

Expand Down Expand Up @@ -37,10 +38,33 @@ func CreateProduct(req models.CreateProductRequestModel, db *gorm.DB, c *gin.Con
}

responseData = gin.H{
"id": product.ID,
"name": product.Name,
"description": product.Description,
"price": product.Price,
"owner_id": product.OwnerID,
}
return responseData, http.StatusCreated, nil
}

func GetProduct(productId string, db *gorm.DB) (gin.H, int, error) {
product := models.Product{}
product, err := product.GetProduct(db, productId)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, http.StatusNotFound, err
}
return nil, http.StatusInternalServerError, err
}

responseData := gin.H{
"id": product.ID,
"name": product.Name,
"description": product.Description,
"price": product.Price,
"categories": product.Category,
"created_at": product.CreatedAt,
"updated_at": product.UpdatedAt,
}
return responseData, http.StatusOK, nil
}
123 changes: 123 additions & 0 deletions tests/test_product/product_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,126 @@
}

}

func TestProductGet(t *testing.T) {
logger := tst.Setup()
gin.SetMode(gin.TestMode)

validatorRef := validator.New()
db := storage.Connection()
requestURI := url.URL{Path: "/api/v1/products"}
currUUID := utility.GenerateUUID()
userSignUpData := models.CreateUserRequestModel{
Email: fmt.Sprintf("johncarpenter%[email protected]", currUUID),
PhoneNumber: fmt.Sprintf("+234%v", utility.GetRandomNumbersInRange(7000000000, 9099999999)),
FirstName: "test",
LastName: "user",
Password: "password",
UserName: fmt.Sprintf("test_username%v", currUUID),
}
loginData := models.LoginRequestModel{
Email: userSignUpData.Email,
Password: userSignUpData.Password,
}

auth := auth.Controller{Db: db, Validator: validatorRef, Logger: logger}
r := gin.Default()
tst.SignupUser(t, r, auth, userSignUpData)

Check failure on line 159 in tests/test_product/product_test.go

View workflow job for this annotation

GitHub Actions / build_and_test

not enough arguments in call to tst.SignupUser

token := tst.GetLoginToken(t, r, auth, loginData)

testProduct := models.CreateProductRequestModel{
Name: "Nike SB",
Description: "One of the best, common and cloned nike product of all time",
Price: 190.33,
}

product := product.Controller{Db: db, Validator: validatorRef, Logger: logger}
productUrl := r.Group(fmt.Sprintf("%v", "/api/v1"), middleware.Authorize(db.Postgresql))
{
productUrl.POST("/products", product.CreateProduct)

}

var b bytes.Buffer
json.NewEncoder(&b).Encode(testProduct)

req, err := http.NewRequest(http.MethodPost, requestURI.String(), &b)
if err != nil {
t.Fatal(err)
}

req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)

rr := httptest.NewRecorder()
r.ServeHTTP(rr, req)

productCreateResp := tst.ParseResponse(rr)
productId := productCreateResp["data"].(map[string]interface{})["id"].(string) // don't mind this goofy ahh line

tests := []struct {
Name string
productId string
ExpectedCode int
Message string
Headers map[string]string
}{
{
Name: "Get product success",
ExpectedCode: http.StatusOK,
productId: productId,
Message: "",
Headers: map[string]string{
"Content-Type": "application/json",
"Authorization": "Bearer " + token,
},
}, {
Name: "Get product invalid id",
ExpectedCode: http.StatusBadRequest,
productId: "ohio rizzler",
Message: "Invalid product ID",
Headers: map[string]string{
"Content-Type": "application/json",
"Authorization": "Bearer " + token,
},
}, {
Name: "Get product product does not exist",
ExpectedCode: http.StatusNotFound,
productId: utility.GenerateUUID(),
Message: "Product not found",
Headers: map[string]string{
"Content-Type": "application/json",
"Authorization": "Bearer " + token,
},
},
}

for _, test := range tests {
r := gin.Default()

productUrl := r.Group(fmt.Sprintf("%v", "/api/v1"), middleware.Authorize(db.Postgresql))
{
productUrl.GET("/products/:product_id", product.GetProduct)

}

t.Run(test.Name, func(t *testing.T) {
req, err := http.NewRequest(http.MethodGet, requestURI.String()+"/"+test.productId, nil)
if err != nil {
t.Fatal(err)
}

for i, v := range test.Headers {
req.Header.Set(i, v)
}

rr := httptest.NewRecorder()
r.ServeHTTP(rr, req)

tst.AssertStatusCode(t, rr.Code, test.ExpectedCode)
})

}

}
Loading