package userproduct_test import ( "bytes" "context" "encoding/json" "net/http" "net/http/httptest" "testing" "time" "github.com/food-ai/backend/internal/domain/userproduct" userproductmocks "github.com/food-ai/backend/internal/domain/userproduct/mocks" "github.com/food-ai/backend/internal/infra/middleware" "github.com/go-chi/chi/v5" ) type alwaysAuthValidator struct{ userID string } func (v *alwaysAuthValidator) ValidateAccessToken(_ string) (*middleware.TokenClaims, error) { return &middleware.TokenClaims{UserID: v.userID}, nil } func buildRouter(handler *userproduct.Handler, userID string) *chi.Mux { router := chi.NewRouter() router.Use(middleware.Auth(&alwaysAuthValidator{userID: userID})) router.Get("/user-products", handler.List) router.Post("/user-products", handler.Create) router.Post("/user-products/batch", handler.BatchCreate) router.Put("/user-products/{id}", handler.Update) router.Delete("/user-products/{id}", handler.Delete) return router } func authorizedRequest(method, target string, body []byte) *http.Request { request := httptest.NewRequest(method, target, bytes.NewReader(body)) request.Header.Set("Authorization", "Bearer test-token") request.Header.Set("Content-Type", "application/json") return request } func makeUserProduct(name string) *userproduct.UserProduct { return &userproduct.UserProduct{ ID: "prod-1", UserID: "user-1", Name: name, Quantity: 1, Unit: "pcs", StorageDays: 7, AddedAt: time.Now(), ExpiresAt: time.Now().AddDate(0, 0, 7), DaysLeft: 7, } } func TestList_Success(t *testing.T) { mockRepo := &userproductmocks.MockUserProductRepository{ ListFn: func(ctx context.Context, userID string) ([]*userproduct.UserProduct, error) { return []*userproduct.UserProduct{makeUserProduct("Milk")}, nil }, } handler := userproduct.NewHandler(mockRepo) router := buildRouter(handler, "user-1") recorder := httptest.NewRecorder() router.ServeHTTP(recorder, authorizedRequest(http.MethodGet, "/user-products", nil)) if recorder.Code != http.StatusOK { t.Errorf("expected 200, got %d", recorder.Code) } } func TestCreate_MissingName(t *testing.T) { handler := userproduct.NewHandler(&userproductmocks.MockUserProductRepository{}) router := buildRouter(handler, "user-1") body, _ := json.Marshal(map[string]any{"quantity": 1}) recorder := httptest.NewRecorder() router.ServeHTTP(recorder, authorizedRequest(http.MethodPost, "/user-products", body)) if recorder.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", recorder.Code) } } func TestCreate_Success(t *testing.T) { mockRepo := &userproductmocks.MockUserProductRepository{ CreateFn: func(ctx context.Context, userID string, req userproduct.CreateRequest) (*userproduct.UserProduct, error) { return makeUserProduct(req.Name), nil }, } handler := userproduct.NewHandler(mockRepo) router := buildRouter(handler, "user-1") body, _ := json.Marshal(userproduct.CreateRequest{Name: "Milk", Quantity: 1, Unit: "L"}) recorder := httptest.NewRecorder() router.ServeHTTP(recorder, authorizedRequest(http.MethodPost, "/user-products", body)) if recorder.Code != http.StatusCreated { t.Errorf("expected 201, got %d", recorder.Code) } } func TestBatchCreate_Success(t *testing.T) { mockRepo := &userproductmocks.MockUserProductRepository{ BatchCreateFn: func(ctx context.Context, userID string, items []userproduct.CreateRequest) ([]*userproduct.UserProduct, error) { result := make([]*userproduct.UserProduct, len(items)) for index, item := range items { result[index] = makeUserProduct(item.Name) } return result, nil }, } handler := userproduct.NewHandler(mockRepo) router := buildRouter(handler, "user-1") body, _ := json.Marshal([]userproduct.CreateRequest{ {Name: "Milk", Quantity: 1, Unit: "L"}, {Name: "Eggs", Quantity: 12, Unit: "pcs"}, }) recorder := httptest.NewRecorder() router.ServeHTTP(recorder, authorizedRequest(http.MethodPost, "/user-products/batch", body)) if recorder.Code != http.StatusCreated { t.Errorf("expected 201, got %d", recorder.Code) } } func TestUpdate_NotFound(t *testing.T) { mockRepo := &userproductmocks.MockUserProductRepository{ UpdateFn: func(ctx context.Context, id, userID string, req userproduct.UpdateRequest) (*userproduct.UserProduct, error) { return nil, userproduct.ErrNotFound }, } handler := userproduct.NewHandler(mockRepo) router := buildRouter(handler, "user-1") namePtr := "NewName" body, _ := json.Marshal(userproduct.UpdateRequest{Name: &namePtr}) recorder := httptest.NewRecorder() router.ServeHTTP(recorder, authorizedRequest(http.MethodPut, "/user-products/nonexistent", body)) if recorder.Code != http.StatusNotFound { t.Errorf("expected 404, got %d", recorder.Code) } } func TestUpdate_Success(t *testing.T) { mockRepo := &userproductmocks.MockUserProductRepository{ UpdateFn: func(ctx context.Context, id, userID string, req userproduct.UpdateRequest) (*userproduct.UserProduct, error) { updated := makeUserProduct(*req.Name) return updated, nil }, } handler := userproduct.NewHandler(mockRepo) router := buildRouter(handler, "user-1") namePtr := "Oat Milk" body, _ := json.Marshal(userproduct.UpdateRequest{Name: &namePtr}) recorder := httptest.NewRecorder() router.ServeHTTP(recorder, authorizedRequest(http.MethodPut, "/user-products/prod-1", body)) if recorder.Code != http.StatusOK { t.Errorf("expected 200, got %d", recorder.Code) } } func TestDelete_NotFound(t *testing.T) { mockRepo := &userproductmocks.MockUserProductRepository{ DeleteFn: func(ctx context.Context, id, userID string) error { return userproduct.ErrNotFound }, } handler := userproduct.NewHandler(mockRepo) router := buildRouter(handler, "user-1") recorder := httptest.NewRecorder() router.ServeHTTP(recorder, authorizedRequest(http.MethodDelete, "/user-products/nonexistent", nil)) if recorder.Code != http.StatusNotFound { t.Errorf("expected 404, got %d", recorder.Code) } } func TestDelete_Success(t *testing.T) { mockRepo := &userproductmocks.MockUserProductRepository{ DeleteFn: func(ctx context.Context, id, userID string) error { return nil }, } handler := userproduct.NewHandler(mockRepo) router := buildRouter(handler, "user-1") recorder := httptest.NewRecorder() router.ServeHTTP(recorder, authorizedRequest(http.MethodDelete, "/user-products/prod-1", nil)) if recorder.Code != http.StatusNoContent { t.Errorf("expected 204, got %d", recorder.Code) } }