Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
37 changes: 37 additions & 0 deletions api/product/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package productHandler

import (
"context"
"encoding/json"
"net/http"
"products/internal/db/product"
"time"

"github.com/jackc/pgx/v5/pgtype"
)

func (h *ProductHandler) CreateProduct(w http.ResponseWriter, r *http.Request) {
var req product.CreateProductParams
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
if req.Name == "" || req.PlatformID == 0 {
http.Error(w, "Name and platform ID are required", http.StatusBadRequest)
return
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
req.Timestamp = pgtype.Timestamptz{
Valid: true,
Time: time.Now().UTC(),
}

contextWithTimeOut, cancel := context.WithTimeout(r.Context(), 5*time.Second)
defer cancel()
if err := h.queries.CreateProduct(contextWithTimeOut, req); err != nil {
http.Error(w, "Failed to create product", http.StatusInternalServerError)
return
}

w.WriteHeader(http.StatusCreated)

}
6 changes: 6 additions & 0 deletions api/product/product.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@ import "products/internal/db/product"
type ProductHandler struct {
queries product.Querier
}

func NewProductHandler(q product.Querier) *ProductHandler {
return &ProductHandler{
queries: q,
}
}
128 changes: 128 additions & 0 deletions api/product/product_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package productHandler

import (
"bytes"
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"products/internal/db/product"
"testing"
)

type mockProductQuerier struct {
createProductFunc func(ctx context.Context, arg product.CreateProductParams) error
deleteProductFunc func(ctx context.Context, id int32) error
getProductByIdFunc func(ctx context.Context, id int32) (product.Product, error)
getProductsByPlatformFunc func(ctx context.Context, platformID int32) ([]product.Product, error)
updateProductFunc func(ctx context.Context, arg product.UpdateProductParams) error
}

func (m *mockProductQuerier) CreateProduct(ctx context.Context, arg product.CreateProductParams) error {
return m.createProductFunc(ctx, arg)
}

func (m *mockProductQuerier) DeleteProduct(ctx context.Context, id int32) error {
return m.deleteProductFunc(ctx, id)
}

func (m *mockProductQuerier) GetProductById(ctx context.Context, id int32) (product.Product, error) {
return m.getProductByIdFunc(ctx, id)
}

func (m *mockProductQuerier) GetProductsByPlatform(ctx context.Context, platformID int32) ([]product.Product, error) {
return m.getProductsByPlatformFunc(ctx, platformID)
}

func (m *mockProductQuerier) UpdateProduct(ctx context.Context, arg product.UpdateProductParams) error {
return m.updateProductFunc(ctx, arg)
}

func TestCreateProduct(t *testing.T) {
tests := []struct {
name string
requestBody any
mockSetup func(m *mockProductQuerier)
expectedStatus int
}{
{
name: "Success",
requestBody: product.CreateProductParams{
PlatformID: 1,
Name: "Test Product",
},
mockSetup: func(m *mockProductQuerier) {
m.createProductFunc = func(ctx context.Context, arg product.CreateProductParams) error {
if arg.Name != "Test Product" {
return errors.New("unexpected name")
}
if arg.PlatformID != 1 {
return errors.New("unexpected platform id")
}
return nil
}
},
expectedStatus: http.StatusCreated,
},
{
name: "Invalid JSON",
requestBody: "invalid json",
mockSetup: func(m *mockProductQuerier) {},
expectedStatus: http.StatusBadRequest,
},
{
name: "DB Failure",
requestBody: product.CreateProductParams{
PlatformID: 1,
Name: "Fail Product",
},
mockSetup: func(m *mockProductQuerier) {
m.createProductFunc = func(ctx context.Context, arg product.CreateProductParams) error {
return errors.New("db error")
}
},
expectedStatus: http.StatusInternalServerError,
},
{
name: "Missing Name",
requestBody: product.CreateProductParams{
PlatformID: 1,
},
mockSetup: func(m *mockProductQuerier) {},
expectedStatus: http.StatusBadRequest,
},
{
name: "Missing PlatformID",
requestBody: product.CreateProductParams{
Name: "Test Product",
},
mockSetup: func(m *mockProductQuerier) {},
expectedStatus: http.StatusBadRequest,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mock := &mockProductQuerier{}
tt.mockSetup(mock)
h := NewProductHandler(mock)

var body []byte
if s, ok := tt.requestBody.(string); ok {
body = []byte(s)
} else {
body, _ = json.Marshal(tt.requestBody)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

req := httptest.NewRequest(http.MethodPost, "/products", bytes.NewBuffer(body))
rr := httptest.NewRecorder()

h.CreateProduct(rr, req)

if rr.Code != tt.expectedStatus {
t.Errorf("expected status %v, got %v", tt.expectedStatus, rr.Code)
}
})
}
}
8 changes: 3 additions & 5 deletions internal/db/product/products.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion queries/products.sql
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ SELECT id, platform_id, name, description, created_at, updated_at FROM products
SELECT id, platform_id, name, description, created_at, updated_at FROM products WHERE id = @id;

-- name: CreateProduct :exec
INSERT INTO products (platform_id, name, description, created_at, updated_at) VALUES (@platform_id, @name, @description, @created_at, @updated_at);
INSERT INTO products (platform_id, name, description, created_at, updated_at) VALUES (@platform_id, @name, @description, @timestamp, @timestamp);

-- name: UpdateProduct :exec
UPDATE products SET name = @name, description = @description, updated_at = @updated_at WHERE id = @id RETURNING id;
Expand Down
10 changes: 10 additions & 0 deletions router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import (
"log/slog"
"net/http"
platformHandler "products/api/platform"
productHandler "products/api/product"
systemHandler "products/api/system"
"products/internal"
"products/internal/db"
platformDb "products/internal/db/platform"
productDb "products/internal/db/product"

"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
Expand All @@ -33,6 +35,7 @@ func SetupRouter(dbConn db.DBTX) http.Handler {

registerSystemCallHandler(router)
registerPlatformCallHandler(store.Platform, router)
registerProductCallHandler(store.Product, router)
slog.Debug("Router setup complete")
return router
}
Expand All @@ -52,3 +55,10 @@ func registerPlatformCallHandler(q platformDb.Querier, r *chi.Mux) {
u.Put("/{id}", handler.UpdatePlatform)
})
}

func registerProductCallHandler(q productDb.Querier, r *chi.Mux) {
handler := productHandler.NewProductHandler(q)
r.Route("/api/products", func(u chi.Router) {
u.Post("/", handler.CreateProduct)
})
}
Loading