Skip to content

Commit cfd0794

Browse files
committed
mock and tests
1 parent cff4d19 commit cfd0794

File tree

2 files changed

+267
-2
lines changed

2 files changed

+267
-2
lines changed

internal/handlers/category_handler_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ func TestCategoryHandler_GetAllCategories(t *testing.T) {
8686
}
8787

8888
for _, tc := range tests {
89+
tc := tc // Capture range variable
8990
t.Run(tc.name, func(t *testing.T) {
9091
mockRepo.On("FindAll", mock.Anything).Return(tc.mockReturn, tc.mockError).Once()
9192

@@ -148,6 +149,7 @@ func TestCategoryHandler_GetCategory(t *testing.T) {
148149
}
149150

150151
for _, tc := range tests {
152+
tc := tc // Capture range variable
151153
t.Run(tc.name, func(t *testing.T) {
152154
// Moved setup inside t.Run for isolation
153155
mockRepo, _, _, _, router := setupCategoryTest(t)
@@ -252,6 +254,7 @@ func TestCategoryHandler_CreateCategory(t *testing.T) {
252254
}
253255

254256
for _, tc := range tests {
257+
tc := tc // Capture range variable
255258
t.Run(tc.name, func(t *testing.T) {
256259
// Moved setup inside t.Run for isolation
257260
mockCategoryRepo, mockUserRepo, _, _, router := setupCategoryTest(t)
@@ -403,6 +406,7 @@ func TestCategoryHandler_UpdateCategory(t *testing.T) {
403406
}
404407

405408
for _, tc := range tests {
409+
tc := tc // Capture range variable
406410
t.Run(tc.name, func(t *testing.T) {
407411
// Moved setup inside t.Run for isolation
408412
mockCategoryRepo, mockUserRepo, _, _, router := setupCategoryTest(t)
@@ -515,6 +519,7 @@ func TestCategoryHandler_DeleteCategory(t *testing.T) {
515519
}
516520

517521
for _, tc := range tests {
522+
tc := tc // Capture range variable
518523
t.Run(tc.name, func(t *testing.T) {
519524
// Moved setup inside t.Run for isolation
520525
mockCategoryRepo, mockUserRepo, _, _, router := setupCategoryTest(t)

internal/handlers/product_handler_test.go

Lines changed: 262 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ func TestProductHandler_GetAllProducts(t *testing.T) {
8989
}
9090

9191
for _, tc := range tests {
92+
tc := tc // Capture range variable
9293
t.Run(tc.name, func(t *testing.T) {
9394
mockRepo.On("FindAll", mock.Anything).Return(tc.mockReturn, tc.mockError).Once()
9495

@@ -152,6 +153,7 @@ func TestProductHandler_GetProduct(t *testing.T) {
152153
}
153154

154155
for _, tc := range tests {
156+
tc := tc // Capture range variable
155157
t.Run(tc.name, func(t *testing.T) {
156158
// Moved setup inside t.Run for isolation
157159
mockRepo, _, _, _, router := setupProductTest(t)
@@ -266,6 +268,7 @@ func TestProductHandler_CreateProduct(t *testing.T) {
266268
}
267269

268270
for _, tc := range tests {
271+
tc := tc // Capture range variable
269272
t.Run(tc.name, func(t *testing.T) {
270273
// Moved setup inside t.Run for isolation
271274
mockProductRepo, mockUserRepo, _, _, router := setupProductTest(t)
@@ -306,5 +309,262 @@ func TestProductHandler_CreateProduct(t *testing.T) {
306309
}
307310
}
308311

309-
// TODO: Add tests for UpdateProduct
310-
// TODO: Add tests for DeleteProduct
312+
func TestProductHandler_UpdateProduct(t *testing.T) {
313+
// Setup inside loop
314+
testUserID := uuid.New()
315+
productToUpdateID := uuid.New()
316+
testJwtSecret := "test-secret-for-jwt-please-change"
317+
testToken := generateTestToken(testUserID, testJwtSecret)
318+
319+
tests := []struct {
320+
name string
321+
productID string
322+
body string
323+
mockUserReturn *models.User
324+
mockUserErr error
325+
mockUpdateErr error
326+
expectedStatus int
327+
expectedBody string
328+
}{
329+
{
330+
name: "Success",
331+
productID: productToUpdateID.String(),
332+
body: `{"name":"Updated Gadget","description":"Better","price":129.99}`, // Include all fields
333+
mockUserReturn: &models.User{ID: testUserID},
334+
mockUserErr: nil,
335+
mockUpdateErr: nil,
336+
expectedStatus: http.StatusOK,
337+
expectedBody: "Updated Gadget",
338+
},
339+
{
340+
name: "Failure - Invalid UUID",
341+
productID: "not-a-uuid",
342+
body: `{"name":"Update Attempt"}`,
343+
mockUserReturn: &models.User{ID: testUserID},
344+
mockUserErr: nil,
345+
mockUpdateErr: nil,
346+
expectedStatus: http.StatusNotFound, // Expect 404 from router
347+
expectedBody: "404 page not found",
348+
},
349+
{
350+
name: "Failure - Invalid JSON",
351+
productID: productToUpdateID.String(),
352+
body: `{"name":}`, // Invalid JSON
353+
mockUserReturn: &models.User{ID: testUserID},
354+
mockUserErr: nil,
355+
mockUpdateErr: nil,
356+
expectedStatus: http.StatusBadRequest,
357+
expectedBody: `{"error":"invalid request body"}`,
358+
},
359+
{
360+
name: "Failure - Missing Name",
361+
productID: productToUpdateID.String(),
362+
body: `{"price":50.0}`, // Missing name
363+
mockUserReturn: &models.User{ID: testUserID},
364+
mockUserErr: nil,
365+
mockUpdateErr: nil,
366+
expectedStatus: http.StatusBadRequest,
367+
expectedBody: `{"error":"product name is required and price must be non-negative"}`,
368+
},
369+
{
370+
name: "Failure - Negative Price",
371+
productID: productToUpdateID.String(),
372+
body: `{"name":"Bad Price Product","price":-1.0}`,
373+
mockUserReturn: &models.User{ID: testUserID},
374+
mockUserErr: nil,
375+
mockUpdateErr: nil,
376+
expectedStatus: http.StatusBadRequest,
377+
expectedBody: `{"error":"product name is required and price must be non-negative"}`,
378+
},
379+
{
380+
name: "Failure - Product Not Found",
381+
productID: productToUpdateID.String(),
382+
body: `{"name":"Update Attempt","price":10.0}`,
383+
mockUserReturn: &models.User{ID: testUserID},
384+
mockUserErr: nil,
385+
mockUpdateErr: products.ErrProductNotFound,
386+
expectedStatus: http.StatusNotFound,
387+
expectedBody: `{"error":"product not found"}`,
388+
},
389+
{
390+
name: "Failure - Repo Update Error",
391+
productID: productToUpdateID.String(),
392+
body: `{"name":"Update Attempt","price":10.0}`,
393+
mockUserReturn: &models.User{ID: testUserID},
394+
mockUserErr: nil,
395+
mockUpdateErr: errors.New("db update failed"),
396+
expectedStatus: http.StatusInternalServerError,
397+
expectedBody: `{"error":"failed to update product"}`,
398+
},
399+
{
400+
name: "Failure - Middleware User Check Fails",
401+
productID: productToUpdateID.String(),
402+
body: `{"name":"Update Attempt","price":10.0}`,
403+
mockUserReturn: nil,
404+
mockUserErr: users.ErrUserNotFound,
405+
mockUpdateErr: nil,
406+
expectedStatus: http.StatusUnauthorized,
407+
expectedBody: `{"error":"user associated with token not found"}`,
408+
},
409+
{
410+
name: "Failure - No Auth Token",
411+
productID: productToUpdateID.String(),
412+
body: `{"name":"Update Attempt","price":10.0}`,
413+
mockUserReturn: nil,
414+
mockUserErr: nil,
415+
mockUpdateErr: nil,
416+
expectedStatus: http.StatusUnauthorized,
417+
expectedBody: `{"error":"authorization header required"}`,
418+
},
419+
}
420+
421+
for _, tc := range tests {
422+
tc := tc // Capture range variable
423+
t.Run(tc.name, func(t *testing.T) {
424+
// Moved setup inside t.Run for isolation
425+
mockProductRepo, mockUserRepo, _, _, router := setupProductTest(t)
426+
427+
// Mock middleware user check
428+
if tc.expectedStatus != http.StatusUnauthorized || tc.expectedBody == `{"error":"user associated with token not found"}` {
429+
mockUserRepo.On("FindByID", mock.Anything, testUserID).Return(tc.mockUserReturn, tc.mockUserErr).Once()
430+
}
431+
432+
// Mock product repo update (only if middleware/validation/parsing passes)
433+
if tc.productID != "not-a-uuid" && tc.mockUserErr == nil && tc.expectedStatus != http.StatusBadRequest && tc.expectedStatus != http.StatusUnauthorized {
434+
parsedID, _ := uuid.Parse(tc.productID)
435+
mockProductRepo.On("Update", mock.Anything, parsedID, mock.AnythingOfType("*models.Product")).
436+
Return(func(ctx context.Context, id uuid.UUID, p *models.Product) *models.Product {
437+
if tc.mockUpdateErr != nil {
438+
return nil
439+
}
440+
p.ID = id
441+
p.UpdatedAt = time.Now() // Simulate update
442+
return p
443+
}, tc.mockUpdateErr).Once()
444+
}
445+
446+
req := httptest.NewRequest(http.MethodPut, "/api/products/"+tc.productID, strings.NewReader(tc.body))
447+
req.Header.Set("Content-Type", "application/json")
448+
if tc.expectedStatus != http.StatusUnauthorized || tc.expectedBody != `{"error":"authorization header required"}` {
449+
req.Header.Set("Authorization", "Bearer "+testToken)
450+
}
451+
452+
rr := httptest.NewRecorder()
453+
router.ServeHTTP(rr, req)
454+
455+
assert.Equal(t, tc.expectedStatus, rr.Code)
456+
assert.Contains(t, rr.Body.String(), tc.expectedBody)
457+
mockUserRepo.AssertExpectations(t)
458+
mockProductRepo.AssertExpectations(t)
459+
})
460+
}
461+
}
462+
463+
func TestProductHandler_DeleteProduct(t *testing.T) {
464+
// Setup inside loop
465+
testUserID := uuid.New()
466+
productToDeleteID := uuid.New()
467+
testJwtSecret := "test-secret-for-jwt-please-change"
468+
testToken := generateTestToken(testUserID, testJwtSecret)
469+
470+
tests := []struct {
471+
name string
472+
productID string
473+
mockUserReturn *models.User
474+
mockUserErr error
475+
mockDeleteErr error
476+
expectedStatus int
477+
expectedBody string
478+
}{
479+
{
480+
name: "Success",
481+
productID: productToDeleteID.String(),
482+
mockUserReturn: &models.User{ID: testUserID},
483+
mockUserErr: nil,
484+
mockDeleteErr: nil,
485+
expectedStatus: http.StatusNoContent,
486+
expectedBody: "", // No body on success
487+
},
488+
{
489+
name: "Failure - Invalid UUID",
490+
productID: "not-a-uuid",
491+
mockUserReturn: &models.User{ID: testUserID},
492+
mockUserErr: nil,
493+
mockDeleteErr: nil,
494+
expectedStatus: http.StatusNotFound, // Expect 404 from router
495+
expectedBody: "404 page not found",
496+
},
497+
{
498+
name: "Failure - Product Not Found",
499+
productID: productToDeleteID.String(),
500+
mockUserReturn: &models.User{ID: testUserID},
501+
mockUserErr: nil,
502+
mockDeleteErr: products.ErrProductNotFound,
503+
expectedStatus: http.StatusNotFound,
504+
expectedBody: `{"error":"product not found"}`,
505+
},
506+
{
507+
name: "Failure - Repo Delete Error",
508+
productID: productToDeleteID.String(),
509+
mockUserReturn: &models.User{ID: testUserID},
510+
mockUserErr: nil,
511+
mockDeleteErr: errors.New("db delete failed"),
512+
expectedStatus: http.StatusInternalServerError,
513+
expectedBody: `{"error":"failed to delete product"}`,
514+
},
515+
{
516+
name: "Failure - Middleware User Check Fails",
517+
productID: productToDeleteID.String(),
518+
mockUserReturn: nil,
519+
mockUserErr: users.ErrUserNotFound,
520+
mockDeleteErr: nil,
521+
expectedStatus: http.StatusUnauthorized,
522+
expectedBody: `{"error":"user associated with token not found"}`,
523+
},
524+
{
525+
name: "Failure - No Auth Token",
526+
productID: productToDeleteID.String(),
527+
mockUserReturn: nil,
528+
mockUserErr: nil,
529+
mockDeleteErr: nil,
530+
expectedStatus: http.StatusUnauthorized,
531+
expectedBody: `{"error":"authorization header required"}`,
532+
},
533+
}
534+
535+
for _, tc := range tests {
536+
tc := tc // Capture range variable
537+
t.Run(tc.name, func(t *testing.T) {
538+
// Moved setup inside t.Run for isolation
539+
mockProductRepo, mockUserRepo, _, _, router := setupProductTest(t)
540+
541+
// Mock middleware user check
542+
if tc.expectedStatus != http.StatusUnauthorized || tc.expectedBody == `{"error":"user associated with token not found"}` {
543+
mockUserRepo.On("FindByID", mock.Anything, testUserID).Return(tc.mockUserReturn, tc.mockUserErr).Once()
544+
}
545+
546+
// Mock product repo delete (only if middleware/parsing passes)
547+
if tc.productID != "not-a-uuid" && tc.mockUserErr == nil && tc.expectedStatus != http.StatusBadRequest && tc.expectedStatus != http.StatusUnauthorized {
548+
parsedID, _ := uuid.Parse(tc.productID)
549+
mockProductRepo.On("Delete", mock.Anything, parsedID).Return(tc.mockDeleteErr).Once()
550+
}
551+
552+
req := httptest.NewRequest(http.MethodDelete, "/api/products/"+tc.productID, nil)
553+
if tc.expectedStatus != http.StatusUnauthorized || tc.expectedBody != `{"error":"authorization header required"}` {
554+
req.Header.Set("Authorization", "Bearer "+testToken)
555+
}
556+
557+
rr := httptest.NewRecorder()
558+
router.ServeHTTP(rr, req)
559+
560+
assert.Equal(t, tc.expectedStatus, rr.Code)
561+
if tc.expectedBody != "" {
562+
assert.Contains(t, rr.Body.String(), tc.expectedBody)
563+
} else {
564+
assert.Empty(t, rr.Body.String())
565+
}
566+
mockUserRepo.AssertExpectations(t)
567+
mockProductRepo.AssertExpectations(t)
568+
})
569+
}
570+
}

0 commit comments

Comments
 (0)