From 28131104bde0bcfabfcd217f710f08919f5806a8 Mon Sep 17 00:00:00 2001 From: mevain Date: Wed, 27 Nov 2024 05:42:55 +0300 Subject: [PATCH 1/2] added tests for trips --- internal/pkg/trips/delivery/grpc/grpc_test.go | 568 ++++++++ .../pkg/trips/delivery/http/handler_test.go | 1236 +++++++++++------ internal/pkg/trips/mocks/mock_grpc_trips.go | 392 ++++++ internal/pkg/trips/mocks/mock_trips.go | 65 +- .../pkg/trips/repo/trips_repository_test.go | 570 +++++--- .../pkg/trips/usecase/trips_usecase_test.go | 797 +++++++---- internal/pkg/user/mocks/mock_jwt.go | 2 +- 7 files changed, 2704 insertions(+), 926 deletions(-) create mode 100644 internal/pkg/trips/delivery/grpc/grpc_test.go create mode 100644 internal/pkg/trips/mocks/mock_grpc_trips.go diff --git a/internal/pkg/trips/delivery/grpc/grpc_test.go b/internal/pkg/trips/delivery/grpc/grpc_test.go new file mode 100644 index 0000000..1a83213 --- /dev/null +++ b/internal/pkg/trips/delivery/grpc/grpc_test.go @@ -0,0 +1,568 @@ +package grpc + +import ( + "2024_2_ThereWillBeName/internal/models" + tripsGen "2024_2_ThereWillBeName/internal/pkg/trips/delivery/grpc/gen" + mock "2024_2_ThereWillBeName/internal/pkg/trips/mocks" + "context" + "errors" + "fmt" + "log/slog" + "os" + "path/filepath" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" +) + +func TestCreateTrip(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockUsecase := mock.NewMockTripsUsecase(ctrl) + opts := &slog.HandlerOptions{ + Level: slog.LevelDebug, + } + handl := slog.NewJSONHandler(os.Stdout, opts) + logger := slog.New(handl) + + handler := NewGrpcTripHandler(mockUsecase, logger) + + tests := []struct { + name string + input *tripsGen.CreateTripRequest + mockBehavior func() + expectedErr error + }{ + { + name: "Success", + input: &tripsGen.CreateTripRequest{ + Trip: &tripsGen.Trip{ + UserId: 1, + Name: "Trip to Paris", + Description: "A nice trip", + CityId: 2, + StartDate: "2024-12-01", + EndDate: "2024-12-10", + Private: false, + }, + }, + mockBehavior: func() { + mockUsecase.EXPECT().CreateTrip(gomock.Any(), models.Trip{ + UserID: 1, + Name: "Trip to Paris", + Description: "A nice trip", + CityID: 2, + StartDate: "2024-12-01", + EndDate: "2024-12-10", + Private: false, + }).Return(nil) + }, + expectedErr: nil, + }, + { + name: "Usecase Error", + input: &tripsGen.CreateTripRequest{ + Trip: &tripsGen.Trip{ + UserId: 1, + Name: "Trip to Paris", + Description: "A nice trip", + CityId: 2, + StartDate: "2024-12-01", + EndDate: "2024-12-10", + Private: false, + }, + }, + mockBehavior: func() { + mockUsecase.EXPECT().CreateTrip(gomock.Any(), models.Trip{ + UserID: 1, + Name: "Trip to Paris", + Description: "A nice trip", + CityID: 2, + StartDate: "2024-12-01", + EndDate: "2024-12-10", + Private: false, + }).Return(errors.New("usecase error")) + }, + expectedErr: errors.New("usecase error"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.mockBehavior() + + _, err := handler.CreateTrip(context.Background(), tt.input) + + if tt.expectedErr != nil { + assert.Error(t, err) + assert.Contains(t, err.Error(), tt.expectedErr.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestUpdateTrip(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockUsecase := mock.NewMockTripsUsecase(ctrl) + opts := &slog.HandlerOptions{ + Level: slog.LevelDebug, + } + handl := slog.NewJSONHandler(os.Stdout, opts) + logger := slog.New(handl) + + handler := NewGrpcTripHandler(mockUsecase, logger) + + tests := []struct { + name string + input *tripsGen.UpdateTripRequest + mockBehavior func() + expectedErr error + }{ + { + name: "Success", + input: &tripsGen.UpdateTripRequest{ + Trip: &tripsGen.Trip{ + UserId: 1, + Name: "Updated Trip", + Description: "Updated description", + CityId: 2, + StartDate: "2024-12-01", + EndDate: "2024-12-10", + Private: false, + }, + }, + mockBehavior: func() { + mockUsecase.EXPECT().UpdateTrip(gomock.Any(), models.Trip{ + UserID: 1, + Name: "Updated Trip", + Description: "Updated description", + CityID: 2, + StartDate: "2024-12-01", + EndDate: "2024-12-10", + Private: false, + }).Return(nil) + }, + expectedErr: nil, + }, + { + name: "Usecase Error", + input: &tripsGen.UpdateTripRequest{ + Trip: &tripsGen.Trip{ + UserId: 1, + Name: "Updated Trip", + Description: "Updated description", + CityId: 2, + StartDate: "2024-12-01", + EndDate: "2024-12-10", + Private: false, + }, + }, + mockBehavior: func() { + mockUsecase.EXPECT().UpdateTrip(gomock.Any(), models.Trip{ + UserID: 1, + Name: "Updated Trip", + Description: "Updated description", + CityID: 2, + StartDate: "2024-12-01", + EndDate: "2024-12-10", + Private: false, + }).Return(errors.New("usecase error")) + }, + expectedErr: errors.New("usecase error"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.mockBehavior() + + _, err := handler.UpdateTrip(context.Background(), tt.input) + + if tt.expectedErr != nil { + assert.Error(t, err) + assert.Contains(t, err.Error(), tt.expectedErr.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} +func TestDeleteTrip(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockUsecase := mock.NewMockTripsUsecase(ctrl) + opts := &slog.HandlerOptions{ + Level: slog.LevelDebug, + } + handl := slog.NewJSONHandler(os.Stdout, opts) + logger := slog.New(handl) + + handler := NewGrpcTripHandler(mockUsecase, logger) + + tests := []struct { + name string + input *tripsGen.DeleteTripRequest + mockBehavior func() + expectedErr error + }{ + { + name: "Success", + input: &tripsGen.DeleteTripRequest{ + Id: 123, + }, + mockBehavior: func() { + mockUsecase.EXPECT().DeleteTrip(gomock.Any(), uint(123)).Return(nil) + }, + expectedErr: nil, + }, + { + name: "Usecase Error", + input: &tripsGen.DeleteTripRequest{ + Id: 123, + }, + mockBehavior: func() { + mockUsecase.EXPECT().DeleteTrip(gomock.Any(), uint(123)).Return(errors.New("usecase error")) + }, + expectedErr: errors.New("usecase error"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.mockBehavior() + + _, err := handler.DeleteTrip(context.Background(), tt.input) + + if tt.expectedErr != nil { + assert.Error(t, err) + assert.Contains(t, err.Error(), tt.expectedErr.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestGetTripsByUserID(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockUsecase := mock.NewMockTripsUsecase(ctrl) + opts := &slog.HandlerOptions{ + Level: slog.LevelDebug, + } + handl := slog.NewJSONHandler(os.Stdout, opts) + logger := slog.New(handl) + + handler := NewGrpcTripHandler(mockUsecase, logger) + + tests := []struct { + name string + input *tripsGen.GetTripsByUserIDRequest + mockBehavior func() + expectedResp *tripsGen.GetTripsByUserIDResponse + expectedErr error + }{ + { + name: "Success", + input: &tripsGen.GetTripsByUserIDRequest{ + UserId: 1, + Limit: 10, + Offset: 0, + }, + mockBehavior: func() { + mockUsecase.EXPECT().GetTripsByUserID(gomock.Any(), uint(1), 10, 0).Return([]models.Trip{ + { + ID: 1, + UserID: 1, + Name: "Trip 1", + Description: "Description 1", + CityID: 2, + StartDate: "2024-12-01", + EndDate: "2024-12-10", + Photos: []string{"photo1.jpg", "photo2.jpg"}, + Private: false, + }, + }, nil) + }, + expectedResp: &tripsGen.GetTripsByUserIDResponse{ + Trips: []*tripsGen.Trip{ + { + Id: 1, + UserId: 1, + Name: "Trip 1", + Description: "Description 1", + CityId: 2, + StartDate: "2024-12-01", + EndDate: "2024-12-10", + Photos: []string{"photo1.jpg", "photo2.jpg"}, + Private: false, + }, + }, + }, + expectedErr: nil, + }, + { + name: "Usecase Error", + input: &tripsGen.GetTripsByUserIDRequest{ + UserId: 1, + Limit: 10, + Offset: 0, + }, + mockBehavior: func() { + mockUsecase.EXPECT().GetTripsByUserID(gomock.Any(), uint(1), 10, 0).Return(nil, errors.New("usecase error")) + }, + expectedResp: nil, + expectedErr: errors.New("usecase error"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.mockBehavior() + + resp, err := handler.GetTripsByUserID(context.Background(), tt.input) + + if tt.expectedErr != nil { + assert.Error(t, err) + assert.Contains(t, err.Error(), tt.expectedErr.Error()) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.expectedResp, resp) + } + }) + } +} +func TestGetTrip(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockUsecase := mock.NewMockTripsUsecase(ctrl) + opts := &slog.HandlerOptions{ + Level: slog.LevelDebug, + } + handl := slog.NewJSONHandler(os.Stdout, opts) + logger := slog.New(handl) + + handler := NewGrpcTripHandler(mockUsecase, logger) + + tests := []struct { + name string + input *tripsGen.GetTripRequest + mockBehavior func() + expectedResp *tripsGen.GetTripResponse + expectedErr error + }{ + { + name: "Success", + input: &tripsGen.GetTripRequest{ + TripId: 1, + }, + mockBehavior: func() { + mockUsecase.EXPECT().GetTrip(gomock.Any(), uint(1)).Return(models.Trip{ + ID: 1, + UserID: 1, + Name: "Trip to Paris", + Description: "A nice trip", + CityID: 2, + StartDate: "2024-12-01", + EndDate: "2024-12-10", + Private: false, + Photos: []string{"photo1.jpg", "photo2.jpg"}, + }, nil) + }, + expectedResp: &tripsGen.GetTripResponse{ + Trip: &tripsGen.Trip{ + Id: 1, + UserId: 1, + Name: "Trip to Paris", + Description: "A nice trip", + CityId: 2, + StartDate: "2024-12-01", + EndDate: "2024-12-10", + Private: false, + Photos: []string{"photo1.jpg", "photo2.jpg"}, + }, + }, + expectedErr: nil, + }, + { + name: "Usecase Error", + input: &tripsGen.GetTripRequest{ + TripId: 1, + }, + mockBehavior: func() { + mockUsecase.EXPECT().GetTrip(gomock.Any(), uint(1)).Return(models.Trip{}, errors.New("usecase error")) + }, + expectedResp: nil, + expectedErr: errors.New("usecase error"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.mockBehavior() + + resp, err := handler.GetTrip(context.Background(), tt.input) + + if tt.expectedErr != nil { + assert.Error(t, err) + assert.Contains(t, err.Error(), tt.expectedErr.Error()) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.expectedResp, resp) + } + }) + } +} + +func setupTestStorage() (string, func()) { + dir, err := os.MkdirTemp("", "photo_storage_test") + if err != nil { + panic(fmt.Sprintf("Failed to create temp dir: %v", err)) + } + + os.Setenv("PHOTO_STORAGE_PATH", dir) + + return dir, func() { + os.RemoveAll(dir) + } +} + +func TestAddPlaceToTrip(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockUsecase := mock.NewMockTripsUsecase(ctrl) + opts := &slog.HandlerOptions{ + Level: slog.LevelDebug, + } + handl := slog.NewJSONHandler(os.Stdout, opts) + logger := slog.New(handl) + + handler := NewGrpcTripHandler(mockUsecase, logger) + + tests := []struct { + name string + input *tripsGen.AddPlaceToTripRequest + mockBehavior func() + expectedResp *tripsGen.EmptyResponse + expectedErr error + }{ + { + name: "Success", + input: &tripsGen.AddPlaceToTripRequest{ + TripId: 1, + PlaceId: 2, + }, + mockBehavior: func() { + mockUsecase.EXPECT().AddPlaceToTrip(gomock.Any(), uint(1), uint(2)).Return(nil) + }, + expectedResp: &tripsGen.EmptyResponse{}, + expectedErr: nil, + }, + { + name: "Usecase Error", + input: &tripsGen.AddPlaceToTripRequest{ + TripId: 1, + PlaceId: 2, + }, + mockBehavior: func() { + mockUsecase.EXPECT().AddPlaceToTrip(gomock.Any(), uint(1), uint(2)).Return(errors.New("usecase error")) + }, + expectedResp: nil, + expectedErr: errors.New("usecase error"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.mockBehavior() + + resp, err := handler.AddPlaceToTrip(context.Background(), tt.input) + + if tt.expectedErr != nil { + assert.Error(t, err) + assert.Contains(t, err.Error(), tt.expectedErr.Error()) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.expectedResp, resp) + } + }) + } +} +func TestAddPhotosToTrip(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + dir, cleanup := setupTestStorage() + defer cleanup() + + mockUsecase := mock.NewMockTripsUsecase(ctrl) + opts := &slog.HandlerOptions{ + Level: slog.LevelDebug, + } + handl := slog.NewJSONHandler(os.Stdout, opts) + logger := slog.New(handl) + + handler := NewGrpcTripHandler(mockUsecase, logger) + + tests := []struct { + name string + input *tripsGen.AddPhotosToTripRequest + mockBehavior func() + expectedResp *tripsGen.AddPhotosToTripResponse + expectedErr error + }{ + { + name: "Success", + input: &tripsGen.AddPhotosToTripRequest{ + TripId: 1, + Photos: []string{"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEA"}, + }, + mockBehavior: func() { + mockUsecase.EXPECT().AddPhotosToTrip(gomock.Any(), uint(1), gomock.Any()).Return(nil) + }, + expectedResp: &tripsGen.AddPhotosToTripResponse{ + Photos: []*tripsGen.Photo{ + {PhotoPath: filepath.Join(dir, "trip_1")}, + }, + }, + expectedErr: nil, + }, + { + name: "Invalid Base64 Data", + input: &tripsGen.AddPhotosToTripRequest{ + TripId: 1, + Photos: []string{"invalid-base64"}, + }, + mockBehavior: func() { + }, + expectedResp: nil, + expectedErr: errors.New("invalid base64 data"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.mockBehavior() + + resp, err := handler.AddPhotosToTrip(context.Background(), tt.input) + + if tt.expectedErr != nil { + assert.Error(t, err) + assert.Contains(t, err.Error(), tt.expectedErr.Error()) + } else { + assert.NoError(t, err) + assert.NotNil(t, resp) + } + }) + } +} diff --git a/internal/pkg/trips/delivery/http/handler_test.go b/internal/pkg/trips/delivery/http/handler_test.go index fab2487..e481ee3 100644 --- a/internal/pkg/trips/delivery/http/handler_test.go +++ b/internal/pkg/trips/delivery/http/handler_test.go @@ -1,441 +1,799 @@ package http -// import ( -// "bytes" -// "encoding/json" -// "errors" -// "log/slog" -// "net/http" -// "net/http/httptest" -// "os" - -// "strconv" -// "testing" - -// "2024_2_ThereWillBeName/internal/models" -// httpresponse "2024_2_ThereWillBeName/internal/pkg/httpresponses" -// mocks "2024_2_ThereWillBeName/internal/pkg/trips/mocks" - -// "github.com/gorilla/mux" - -// "github.com/golang/mock/gomock" -// "github.com/stretchr/testify/assert" -// ) - -// func TestCreateTripHandler(t *testing.T) { -// ctrl := gomock.NewController(t) -// defer ctrl.Finish() - -// opts := &slog.HandlerOptions{ -// Level: slog.LevelDebug, -// } - -// handl := slog.NewJSONHandler(os.Stdout, opts) - -// logger := slog.New(handl) - -// mockUsecase := mocks.NewMockTripsUsecase(ctrl) -// handler := NewTripHandler(mockUsecase, logger) - -// tests := []struct { -// name string -// inputTrip models.Trip -// usecaseErr error -// expectedStatus int -// expectedBody httpresponse.ErrorResponse -// }{ -// { -// name: "successful creation", -// inputTrip: models.Trip{ - -// ID: 0, -// UserID: 100, -// Name: "Test Trip", -// Description: "A trip for testing", -// CityID: 1, -// StartDate: "2024-12-01", -// EndDate: "2024-12-15", -// Private: false, -// }, -// usecaseErr: nil, -// expectedStatus: http.StatusCreated, -// }, -// { -// name: "invalid input data", -// inputTrip: models.Trip{ - -// ID: 0, -// UserID: 101, -// StartDate: "invalid-date", -// EndDate: "2024-12-15", -// }, -// usecaseErr: errors.New("validation error"), -// expectedStatus: http.StatusInternalServerError, -// expectedBody: httpresponse.ErrorResponse{Message: "Failed to create trip"}, -// }, -// { -// name: "internal server error", -// inputTrip: models.Trip{ - -// ID: 0, -// UserID: 102, -// Name: "Error Trip", -// Description: "This trip causes an error", -// CityID: 1, -// StartDate: "2024-12-01", -// EndDate: "2024-12-15", -// Private: true, -// }, -// usecaseErr: models.ErrNotFound, -// expectedStatus: http.StatusNotFound, -// expectedBody: httpresponse.ErrorResponse{Message: "Invalid request"}, -// }, -// } - -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// mockUsecase.EXPECT().CreateTrip(gomock.Any(), tt.inputTrip).Return(tt.usecaseErr) - -// reqBody, _ := json.Marshal(tt.inputTrip) -// req := httptest.NewRequest("POST", "/trips", bytes.NewReader(reqBody)) -// rec := httptest.NewRecorder() - -// handler.CreateTripHandler(rec, req) - -// assert.Equal(t, tt.expectedStatus, rec.Code) - -// if tt.expectedStatus != http.StatusCreated { -// var response httpresponse.ErrorResponse -// _ = json.NewDecoder(rec.Body).Decode(&response) -// assert.Equal(t, tt.expectedBody.Message, response.Message) -// } -// }) -// } -// } - -// func TestUpdateTripHandler(t *testing.T) { -// ctrl := gomock.NewController(t) -// defer ctrl.Finish() - -// opts := &slog.HandlerOptions{ -// Level: slog.LevelDebug, -// } - -// handl := slog.NewJSONHandler(os.Stdout, opts) - -// logger := slog.New(handl) -// mockUsecase := mocks.NewMockTripsUsecase(ctrl) -// handler := NewTripHandler(mockUsecase, logger) - -// tests := []struct { -// name string -// inputTrip models.Trip -// usecaseErr error -// expectedStatus int -// expectedBody httpresponse.ErrorResponse -// }{ -// { -// name: "successful update", -// inputTrip: models.Trip{ -// ID: 1, -// Name: "Updated Trip", -// UserID: 100, -// Description: "Updated description", -// CityID: 1, -// StartDate: "2024-12-01", -// EndDate: "2024-12-15"}, -// usecaseErr: nil, -// expectedStatus: http.StatusOK, -// }, -// { -// name: "invalid request", -// inputTrip: models.Trip{ID: 10000}, -// usecaseErr: models.ErrNotFound, -// expectedStatus: http.StatusNotFound, -// expectedBody: httpresponse.ErrorResponse{Message: "Invalid request"}, -// }, -// } - -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// mockUsecase.EXPECT().UpdateTrip(gomock.Any(), tt.inputTrip).Return(tt.usecaseErr) - -// reqBody, _ := json.Marshal(tt.inputTrip) -// req := httptest.NewRequest("PUT", "/trips/"+strconv.FormatUint(uint64(tt.inputTrip.ID), 10), bytes.NewReader(reqBody)) -// rec := httptest.NewRecorder() - -// r := mux.NewRouter() -// r.HandleFunc("/trips/{id}", handler.UpdateTripHandler).Methods("PUT") -// r.ServeHTTP(rec, req) - -// assert.Equal(t, tt.expectedStatus, rec.Code) -// if tt.expectedStatus != http.StatusOK { -// var response httpresponse.ErrorResponse -// _ = json.NewDecoder(rec.Body).Decode(&response) -// assert.Equal(t, tt.expectedBody.Message, response.Message) -// } -// }) -// } -// } - -// func TestDeleteTripHandler(t *testing.T) { -// ctrl := gomock.NewController(t) -// defer ctrl.Finish() -// opts := &slog.HandlerOptions{ -// Level: slog.LevelDebug, -// } - -// handl := slog.NewJSONHandler(os.Stdout, opts) - -// logger := slog.New(handl) - -// mockUsecase := mocks.NewMockTripsUsecase(ctrl) -// handler := NewTripHandler(mockUsecase, logger) - -// tests := []struct { -// name string -// tripID uint -// usecaseErr error -// expectedStatus int -// expectedBody httpresponse.ErrorResponse -// }{ -// { -// name: "successful deletion", -// tripID: 1, -// usecaseErr: nil, -// expectedStatus: http.StatusNoContent, -// }, -// { -// name: "invalid request", -// tripID: 10000, -// usecaseErr: models.ErrNotFound, -// expectedStatus: http.StatusNotFound, -// expectedBody: httpresponse.ErrorResponse{Message: "Invalid request"}, -// }, -// } - -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// mockUsecase.EXPECT().DeleteTrip(gomock.Any(), tt.tripID).Return(tt.usecaseErr) - -// req := httptest.NewRequest("DELETE", "/trips/"+strconv.FormatUint(uint64(tt.tripID), 10), nil) -// rec := httptest.NewRecorder() - -// r := mux.NewRouter() -// r.HandleFunc("/trips/{id}", handler.DeleteTripHandler).Methods("DELETE") -// r.ServeHTTP(rec, req) - -// assert.Equal(t, tt.expectedStatus, rec.Code) -// if tt.expectedStatus != http.StatusNoContent { -// var response httpresponse.ErrorResponse -// _ = json.NewDecoder(rec.Body).Decode(&response) -// assert.Equal(t, tt.expectedBody.Message, response.Message) -// } -// }) -// } -// } - -// func TestGetTripsByUserIDHandler(t *testing.T) { -// ctrl := gomock.NewController(t) -// defer ctrl.Finish() - -// opts := &slog.HandlerOptions{ -// Level: slog.LevelDebug, -// } - -// handl := slog.NewJSONHandler(os.Stdout, opts) - -// logger := slog.New(handl) -// mockUsecase := mocks.NewMockTripsUsecase(ctrl) -// handler := NewTripHandler(mockUsecase, logger) - -// tests := []struct { -// name string -// userID uint -// expectedTrips []models.Trip -// usecaseErr error -// expectedStatus int -// expectedBody httpresponse.ErrorResponse -// }{ -// { -// name: "successful retrieval", -// userID: 100, -// expectedTrips: []models.Trip{{ -// ID: 1, -// UserID: 100, -// Name: "Test Trip", -// Description: "A trip for testing"}}, -// usecaseErr: nil, -// expectedStatus: http.StatusOK, -// }, -// { -// name: "invalid request", -// userID: 100, -// expectedTrips: nil, -// usecaseErr: models.ErrNotFound, -// expectedStatus: http.StatusNotFound, -// expectedBody: httpresponse.ErrorResponse{Message: "Invalid request"}, -// }, -// } - -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// mockUsecase.EXPECT().GetTripsByUserID(gomock.Any(), tt.userID, gomock.Any(), gomock.Any()).Return(tt.expectedTrips, tt.usecaseErr) - -// req := httptest.NewRequest("GET", "/users/"+strconv.FormatUint(uint64(tt.userID), 10)+"/trips", nil) -// rec := httptest.NewRecorder() - -// r := mux.NewRouter() -// r.HandleFunc("/users/{userID}/trips", handler.GetTripsByUserIDHandler).Methods("GET") -// r.ServeHTTP(rec, req) - -// assert.Equal(t, tt.expectedStatus, rec.Code) -// if tt.expectedStatus == http.StatusOK { -// var trips []models.Trip -// _ = json.NewDecoder(rec.Body).Decode(&trips) -// assert.Equal(t, tt.expectedTrips, trips) -// } else { -// var response httpresponse.ErrorResponse -// _ = json.NewDecoder(rec.Body).Decode(&response) -// assert.Equal(t, tt.expectedBody.Message, response.Message) -// } -// }) -// } -// } - -// func TestGetTripHandler(t *testing.T) { -// ctrl := gomock.NewController(t) -// defer ctrl.Finish() -// opts := &slog.HandlerOptions{ -// Level: slog.LevelDebug, -// } - -// handl := slog.NewJSONHandler(os.Stdout, opts) - -// logger := slog.New(handl) -// mockUsecase := mocks.NewMockTripsUsecase(ctrl) -// handler := NewTripHandler(mockUsecase, logger) - -// tests := []struct { -// name string -// tripID uint -// expectedTrip models.Trip -// usecaseErr error -// expectedStatus int -// expectedBody httpresponse.ErrorResponse -// }{ -// { -// name: "successful retrieval", -// tripID: 1, -// expectedTrip: models.Trip{ -// ID: 1, -// UserID: 100, -// Name: "Test Trip", -// Description: "A trip for testing"}, -// usecaseErr: nil, -// expectedStatus: http.StatusOK, -// }, -// { -// name: "invalid request", -// tripID: 10000, -// usecaseErr: models.ErrNotFound, -// expectedStatus: http.StatusNotFound, -// expectedBody: httpresponse.ErrorResponse{Message: "Invalid request"}, -// }, -// } - -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// mockUsecase.EXPECT().GetTrip(gomock.Any(), tt.tripID).Return(tt.expectedTrip, tt.usecaseErr) - -// req := httptest.NewRequest("GET", "/trips/"+strconv.Itoa(int(tt.tripID)), nil) -// rec := httptest.NewRecorder() - -// r := mux.NewRouter() -// r.HandleFunc("/trips/{id}", handler.GetTripHandler).Methods("GET") -// r.ServeHTTP(rec, req) - -// assert.Equal(t, tt.expectedStatus, rec.Code) -// if tt.expectedStatus == http.StatusOK { -// var trip models.Trip -// _ = json.NewDecoder(rec.Body).Decode(&trip) -// assert.Equal(t, tt.expectedTrip, trip) -// } else { -// var response httpresponse.ErrorResponse -// _ = json.NewDecoder(rec.Body).Decode(&response) -// assert.Equal(t, tt.expectedBody.Message, response.Message) -// } -// }) -// } -// } - -// // func TestAddPlaceToTripHandler(t *testing.T) { -// // ctrl := gomock.NewController(t) -// // defer ctrl.Finish() -// // opts := &slog.HandlerOptions{ -// // Level: slog.LevelDebug, -// // } - -// // opts := &slog.HandlerOptions{ -// // Level: slog.LevelDebug, -// // } - -// // handl := slog.NewJSONHandler(os.Stdout, opts) - -// // logger := slog.New(handl) - -// // mockUsecase := mocks.NewMockTripsUsecase(ctrl) -// // handler := NewTripHandler(mockUsecase, logger) - -// // tests := []struct { -// // name string -// // ID uint -// // requestBody string -// // usecaseErr error -// // expectedStatus int -// // expectedBody httpresponse.ErrorResponse -// // }{ -// // { -// // name: "successful addition of place", -// // ID: 1, -// // requestBody: `{"place_id": 2}`, -// // usecaseErr: nil, -// // expectedStatus: http.StatusCreated, -// // }, -// // { -// // name: "invalid request body", -// // ID: 2, -// // requestBody: `{"place_id": "invalid"}`, -// // usecaseErr: nil, -// // expectedStatus: http.StatusBadRequest, -// // expectedBody: httpresponse.ErrorResponse{Message: "Invalid place ID"}, -// // }, -// // { -// // name: "error from usecase", -// // ID: 3, -// // requestBody: `{"place_id": 2}`, -// // usecaseErr: errors.New("usecase error"), -// // expectedStatus: http.StatusBadRequest, -// // expectedBody: httpresponse.ErrorResponse{Message: "Invalid trip ID"}, -// // }, -// // } - -// // for _, tt := range tests { -// // t.Run(tt.name, func(t *testing.T) { -// // mockUsecase.EXPECT().AddPlaceToTrip(gomock.Any(), gomock.Any(), gomock.Any()).Return(tt.usecaseErr) -// // req := httptest.NewRequest("POST", "/trips/"+strconv.Itoa(int(tt.ID)), bytes.NewReader([]byte(tt.requestBody))) -// // rec := httptest.NewRecorder() - -// // handler.AddPlaceToTripHandler(rec, req) - -// // assert.Equal(t, tt.expectedStatus, rec.Code) - -// // if tt.expectedStatus != http.StatusCreated { -// // var response httpresponse.ErrorResponse -// // _ = json.NewDecoder(rec.Body).Decode(&response) -// // fmt.Println(response) -// // assert.Equal(t, tt.expectedBody.Message, response.Message) -// // } -// // }) -// // } -// // } +import ( + "bytes" + "context" + "encoding/json" + "errors" + "io" + "log/slog" + "net/http" + "net/http/httptest" + "os" + "testing" + + "2024_2_ThereWillBeName/internal/pkg/middleware" + "2024_2_ThereWillBeName/internal/pkg/trips/delivery/grpc/gen" + mock "2024_2_ThereWillBeName/internal/pkg/trips/mocks" + + "github.com/golang/mock/gomock" + "github.com/gorilla/mux" + "github.com/stretchr/testify/assert" +) + +func TestCreateTripHandler(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockClient := mock.NewMockTripsClient(ctrl) + opts := &slog.HandlerOptions{ + Level: slog.LevelDebug, + } + handl := slog.NewJSONHandler(os.Stdout, opts) + logger := slog.New(handl) + + handler := NewTripHandler(mockClient, logger) + + tests := []struct { + name string + requestBody interface{} + mockBehavior func() + expectedCode int + expectedBody string + }{ + { + name: "Success", + requestBody: TripData{ + UserID: 1, + Name: "Trip to Paris", + CityID: 2, + Description: "A nice trip", + StartDate: "2024-12-01", + EndDate: "2024-12-10", + Private: false, + }, + mockBehavior: func() { + mockClient.EXPECT().CreateTrip(gomock.Any(), gomock.Any()).Return(&gen.EmptyResponse{}, nil) + }, + expectedCode: http.StatusCreated, + expectedBody: `"Trip created successfully"`, + }, + { + name: "Invalid JSON", + requestBody: `invalid-json`, + mockBehavior: func() {}, + expectedCode: http.StatusBadRequest, + expectedBody: `{"message":"Invalid request"}`, + }, + { + name: "Failed Usecase", + requestBody: TripData{ + UserID: 1, + Name: "Trip to Paris", + CityID: 2, + Description: "A nice trip", + StartDate: "2024-12-01", + EndDate: "2024-12-10", + Private: false, + }, + mockBehavior: func() { + mockClient.EXPECT().CreateTrip(gomock.Any(), gomock.Any()).Return(nil, errors.New("usecase error")) + }, + expectedCode: http.StatusInternalServerError, + expectedBody: `{"message":"Failed to create trip"}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + body, _ := json.Marshal(tt.requestBody) + req := httptest.NewRequest(http.MethodPost, "/trips", bytes.NewReader(body)) + req = req.WithContext(context.WithValue(req.Context(), middleware.IdKey, uint(1))) + + w := httptest.NewRecorder() + + tt.mockBehavior() + + handler.CreateTripHandler(w, req) + + resp := w.Result() + assert.Equal(t, tt.expectedCode, resp.StatusCode) + + respBody, _ := io.ReadAll(resp.Body) + assert.Contains(t, string(respBody), tt.expectedBody) + }) + } +} +func TestUpdateTripHandler(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockClient := mock.NewMockTripsClient(ctrl) + opts := &slog.HandlerOptions{ + Level: slog.LevelDebug, + } + handl := slog.NewJSONHandler(os.Stdout, opts) + logger := slog.New(handl) + + handler := NewTripHandler(mockClient, logger) + + router := mux.NewRouter() + router.HandleFunc("/trips/{id}", handler.UpdateTripHandler).Methods(http.MethodPut) + + tests := []struct { + name string + requestPath string + requestBody interface{} + mockBehavior func() + expectedCode int + expectedBody string + }{ + { + name: "Success", + requestPath: "/trips/123", + requestBody: TripData{ + UserID: 1, + Name: "Updated Trip", + CityID: 2, + Description: "Updated description", + StartDate: "2024-12-01", + EndDate: "2024-12-10", + Private: false, + }, + mockBehavior: func() { + mockClient.EXPECT().UpdateTrip(gomock.Any(), gomock.Any()).Return(&gen.EmptyResponse{}, nil) + }, + expectedCode: http.StatusOK, + expectedBody: `"Trip updated successfully"`, + }, + { + name: "Invalid ID", + requestPath: "/trips/abc", + requestBody: TripData{ + UserID: 1, + Name: "Updated Trip", + CityID: 2, + Description: "Updated description", + StartDate: "2024-12-01", + EndDate: "2024-12-10", + Private: false, + }, + mockBehavior: func() {}, + expectedCode: http.StatusBadRequest, + expectedBody: `{"message":"Invalid trip ID"}`, + }, + { + name: "Invalid Body", + requestPath: "/trips/123", + requestBody: `invalid-json`, + mockBehavior: func() {}, + expectedCode: http.StatusBadRequest, + expectedBody: `{"message":"Invalid trip data"}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var body []byte + if b, ok := tt.requestBody.([]byte); ok { + body = b + } else { + body, _ = json.Marshal(tt.requestBody) + } + + req := httptest.NewRequest(http.MethodPut, tt.requestPath, bytes.NewReader(body)) + req = req.WithContext(context.WithValue(req.Context(), middleware.IdKey, uint(1))) + + w := httptest.NewRecorder() + + tt.mockBehavior() + + router.ServeHTTP(w, req) + + resp := w.Result() + assert.Equal(t, tt.expectedCode, resp.StatusCode) + + respBody, _ := io.ReadAll(resp.Body) + assert.Contains(t, string(respBody), tt.expectedBody) + }) + } +} + +func TestDeleteTripHandler(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockClient := mock.NewMockTripsClient(ctrl) + opts := &slog.HandlerOptions{ + Level: slog.LevelDebug, + } + handl := slog.NewJSONHandler(os.Stdout, opts) + logger := slog.New(handl) + + handler := NewTripHandler(mockClient, logger) + + router := mux.NewRouter() + router.HandleFunc("/trips/{id}", handler.DeleteTripHandler).Methods(http.MethodDelete) + + tests := []struct { + name string + tripID string + authUserID uint + mockBehavior func() + expectedCode int + expectedBody string + }{ + { + name: "Success", + tripID: "123", + authUserID: 1, + mockBehavior: func() { + mockClient.EXPECT().DeleteTrip(gomock.Any(), &gen.DeleteTripRequest{Id: 123}).Return(&gen.EmptyResponse{}, nil) + }, + expectedCode: http.StatusNoContent, + expectedBody: `"Trip deleted successfully"`, + }, + { + name: "Invalid Trip ID", + tripID: "invalid-id", + authUserID: 1, + mockBehavior: func() {}, + expectedCode: http.StatusBadRequest, + expectedBody: `{"message":"Invalid trip ID"}`, + }, + { + name: "Unauthorized", + tripID: "123", + authUserID: 0, + mockBehavior: func() {}, + expectedCode: http.StatusUnauthorized, + expectedBody: `{"message":"User is not authorized"}`, + }, + { + name: "GRPC Error", + tripID: "123", + authUserID: 1, + mockBehavior: func() { + mockClient.EXPECT().DeleteTrip(gomock.Any(), &gen.DeleteTripRequest{Id: 123}).Return(nil, errors.New("grpc error")) + }, + expectedCode: http.StatusInternalServerError, + expectedBody: `{"message":"Failed to delete trip"}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := httptest.NewRequest(http.MethodDelete, "/trips/"+tt.tripID, nil) + + if tt.authUserID > 0 { + req = req.WithContext(context.WithValue(req.Context(), middleware.IdKey, tt.authUserID)) + } + + w := httptest.NewRecorder() + + tt.mockBehavior() + + router.ServeHTTP(w, req) + + resp := w.Result() + assert.Equal(t, tt.expectedCode, resp.StatusCode) + + respBody, _ := io.ReadAll(resp.Body) + assert.Contains(t, string(respBody), tt.expectedBody) + }) + } +} + +func TestGetTripsByUserIDHandler(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockClient := mock.NewMockTripsClient(ctrl) + opts := &slog.HandlerOptions{ + Level: slog.LevelDebug, + } + handl := slog.NewJSONHandler(os.Stdout, opts) + logger := slog.New(handl) + handler := NewTripHandler(mockClient, logger) + + tests := []struct { + name string + authUserID uint + page string + mockBehavior func() + expectedCode int + expectedBody string + }{ + { + name: "Success", + authUserID: 1, + page: "1", + mockBehavior: func() { + mockClient.EXPECT().GetTripsByUserID(gomock.Any(), &gen.GetTripsByUserIDRequest{ + UserId: 1, + Limit: 10, + Offset: 0, + }).Return(&gen.GetTripsByUserIDResponse{ + Trips: []*gen.Trip{ + {Id: 1, UserId: 1, Name: "Trip 1", Description: "Description 1"}, + }, + }, nil) + }, + expectedCode: http.StatusOK, + expectedBody: `[{"id":1,"user_id":1,"name":"Trip 1","description":"Description 1"}]`, + }, + { + name: "Unauthorized", + authUserID: 0, + page: "1", + mockBehavior: func() {}, + expectedCode: http.StatusUnauthorized, + expectedBody: `{"message":"User is not authorized"}`, + }, + { + name: "Invalid Page Number", + authUserID: 1, + page: "invalid", + mockBehavior: func() {}, + expectedCode: http.StatusBadRequest, + expectedBody: `{"message":"Invalid page number"}`, + }, + { + name: "GRPC Error", + authUserID: 1, + page: "1", + mockBehavior: func() { + mockClient.EXPECT().GetTripsByUserID(gomock.Any(), gomock.Any()).Return(nil, errors.New("grpc error")) + }, + expectedCode: http.StatusInternalServerError, + expectedBody: `{"message":"Failed to retrieve trip"}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "/trips?page="+tt.page, nil) + + if tt.authUserID > 0 { + req = req.WithContext(context.WithValue(req.Context(), middleware.IdKey, tt.authUserID)) + } + + w := httptest.NewRecorder() + + tt.mockBehavior() + + handler.GetTripsByUserIDHandler(w, req) + + resp := w.Result() + assert.Equal(t, tt.expectedCode, resp.StatusCode) + + respBody, _ := io.ReadAll(resp.Body) + assert.JSONEq(t, tt.expectedBody, string(respBody), "Response body does not match") + }) + } +} + +func TestGetTripHandler(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockClient := mock.NewMockTripsClient(ctrl) + opts := &slog.HandlerOptions{ + Level: slog.LevelDebug, + } + handl := slog.NewJSONHandler(os.Stdout, opts) + logger := slog.New(handl) + handler := NewTripHandler(mockClient, logger) + + tests := []struct { + name string + tripID string + authUserID uint + mockBehavior func() + expectedCode int + expectedBody map[string]interface{} + }{ + { + name: "Success", + tripID: "1", + authUserID: 1, + mockBehavior: func() { + mockClient.EXPECT().GetTrip(gomock.Any(), &gen.GetTripRequest{TripId: 1}).Return(&gen.GetTripResponse{ + Trip: &gen.Trip{ + Id: 1, + UserId: 1, + Name: "Trip to Paris", + Description: "A nice trip", + CityId: 2, + StartDate: "2024-12-01", + EndDate: "2024-12-10", + Private: false, + }, + }, nil) + }, + expectedCode: http.StatusOK, + expectedBody: map[string]interface{}{ + "id": float64(1), + "user_id": float64(1), + "name": "Trip to Paris", + "description": "A nice trip", + "city_id": float64(2), + "start_date": "2024-12-01", + "end_date": "2024-12-10", + }, + }, + + { + name: "Invalid Trip ID", + tripID: "abc", + authUserID: 1, + mockBehavior: func() { + }, + expectedCode: http.StatusBadRequest, + expectedBody: map[string]interface{}{ + "message": "Invalid trip ID", + }, + }, + { + name: "Trip Not Found", + tripID: "1", + authUserID: 1, + mockBehavior: func() { + mockClient.EXPECT().GetTrip(gomock.Any(), &gen.GetTripRequest{TripId: 1}).Return(nil, errors.New("not found")) + }, + expectedCode: http.StatusInternalServerError, + expectedBody: map[string]interface{}{ + "message": "Failed to retrieve trip", + }, + }, + { + name: "Internal Server Error", + tripID: "1", + authUserID: 1, + mockBehavior: func() { + mockClient.EXPECT().GetTrip(gomock.Any(), &gen.GetTripRequest{TripId: 1}).Return(nil, errors.New("internal error")) + }, + expectedCode: http.StatusInternalServerError, + expectedBody: map[string]interface{}{ + "message": "Failed to retrieve trip", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + tt.mockBehavior() + + router := mux.NewRouter() + router.HandleFunc("/trips/{id}", func(w http.ResponseWriter, r *http.Request) { + mux.SetURLVars(r, map[string]string{"id": tt.tripID}) + handler.GetTripHandler(w, r) + }) + + req := httptest.NewRequest(http.MethodGet, "/trips/"+tt.tripID, nil) + req = req.WithContext(context.WithValue(req.Context(), middleware.IdKey, tt.authUserID)) + + w := httptest.NewRecorder() + + router.ServeHTTP(w, req) + + resp := w.Result() + assert.Equal(t, tt.expectedCode, resp.StatusCode) + + var responseBody map[string]interface{} + json.NewDecoder(resp.Body).Decode(&responseBody) + + assert.Equal(t, tt.expectedBody, responseBody) + }) + } +} + +func TestAddPlaceToTripHandler(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockClient := mock.NewMockTripsClient(ctrl) + opts := &slog.HandlerOptions{ + Level: slog.LevelDebug, + } + handl := slog.NewJSONHandler(os.Stdout, opts) + logger := slog.New(handl) + + handler := NewTripHandler(mockClient, logger) + + tests := []struct { + name string + tripID string + authUserID uint + requestBody interface{} + mockBehavior func() + expectedCode int + expectedBody string + }{ + { + name: "Success", + tripID: "1", + authUserID: 1, + requestBody: AddPlaceRequest{ + PlaceID: 123, + }, + mockBehavior: func() { + mockClient.EXPECT().AddPlaceToTrip(gomock.Any(), &gen.AddPlaceToTripRequest{ + TripId: 1, + PlaceId: 123, + }).Return(&gen.EmptyResponse{}, nil) + }, + expectedCode: http.StatusCreated, + expectedBody: `"Place added to trip successfully"`, + }, + { + name: "Unauthorized", + tripID: "1", + authUserID: 0, + requestBody: AddPlaceRequest{PlaceID: 123}, + mockBehavior: func() {}, + expectedCode: http.StatusUnauthorized, + expectedBody: `{"message":"User is not authorized"}`, + }, + { + name: "Invalid Trip ID Format", + tripID: "invalid", + authUserID: 1, + requestBody: AddPlaceRequest{PlaceID: 123}, + mockBehavior: func() {}, + expectedCode: http.StatusBadRequest, + expectedBody: `{"message":"Invalid trip ID"}`, + }, + { + name: "Invalid Request Body", + tripID: "1", + authUserID: 1, + requestBody: "invalid-json", + mockBehavior: func() {}, + expectedCode: http.StatusBadRequest, + expectedBody: `{"message":"Invalid place ID"}`, + }, + { + name: "GRPC Error", + tripID: "1", + authUserID: 1, + requestBody: AddPlaceRequest{ + PlaceID: 123, + }, + mockBehavior: func() { + mockClient.EXPECT().AddPlaceToTrip(gomock.Any(), &gen.AddPlaceToTripRequest{ + TripId: 1, + PlaceId: 123, + }).Return(nil, errors.New("grpc error")) + }, + expectedCode: http.StatusInternalServerError, + expectedBody: `{"message":"Failed to add place trip"}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + router := mux.NewRouter() + router.HandleFunc("/trips/{id}/places", handler.AddPlaceToTripHandler).Methods(http.MethodPost) + + var body []byte + if b, ok := tt.requestBody.([]byte); ok { + body = b + } else { + body, _ = json.Marshal(tt.requestBody) + } + + req := httptest.NewRequest(http.MethodPost, "/trips/"+tt.tripID+"/places", bytes.NewReader(body)) + if tt.authUserID > 0 { + req = req.WithContext(context.WithValue(req.Context(), middleware.IdKey, tt.authUserID)) + } + + w := httptest.NewRecorder() + + tt.mockBehavior() + + router.ServeHTTP(w, req) + + resp := w.Result() + assert.Equal(t, tt.expectedCode, resp.StatusCode) + + respBody, _ := io.ReadAll(resp.Body) + assert.Contains(t, string(respBody), tt.expectedBody) + }) + } +} + +func TestAddPhotosToTripHandler(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockClient := mock.NewMockTripsClient(ctrl) + logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})) + handler := NewTripHandler(mockClient, logger) + + tests := []struct { + name string + tripID string + authUserID uint + requestBody interface{} + mockBehavior func() + expectedCode int + expectedBody string + }{ + { + name: "Success", + tripID: "1", + authUserID: 1, + requestBody: map[string][]string{ + "photos": {"photo1_base64", "photo2_base64"}, + }, + mockBehavior: func() { + mockClient.EXPECT().AddPhotosToTrip(gomock.Any(), &gen.AddPhotosToTripRequest{ + TripId: 1, + Photos: []string{"photo1_base64", "photo2_base64"}, + }).Return(&gen.AddPhotosToTripResponse{ + Photos: []*gen.Photo{ + {PhotoPath: "photo1_path"}, + {PhotoPath: "photo2_path"}, + }, + }, nil) + }, + expectedCode: http.StatusCreated, + expectedBody: `[{"photoPath":"photo1_path"},{"photoPath":"photo2_path"}]`, + }, + { + name: "Invalid Trip ID", + tripID: "abc", + authUserID: 1, + requestBody: map[string][]string{"photos": {"photo1_base64"}}, + mockBehavior: func() {}, + expectedCode: http.StatusBadRequest, + expectedBody: `{"message":"Invalid trip ID"}`, + }, + { + name: "Invalid Request Body", + tripID: "1", + authUserID: 1, + requestBody: `invalid-body`, + mockBehavior: func() {}, + expectedCode: http.StatusBadRequest, + expectedBody: `{"message":"Invalid request body"}`, + }, + { + name: "GRPC Error", + tripID: "1", + authUserID: 1, + requestBody: map[string][]string{ + "photos": {"photo1_base64"}, + }, + mockBehavior: func() { + mockClient.EXPECT().AddPhotosToTrip(gomock.Any(), gomock.Any()).Return(nil, errors.New("grpc error")) + }, + expectedCode: http.StatusInternalServerError, + expectedBody: `{"message":"Failed to add photos trip"}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + router := mux.NewRouter() + router.HandleFunc("/trips/{id}/photos", handler.AddPhotosToTripHandler).Methods(http.MethodPost) + + var body []byte + if b, ok := tt.requestBody.([]byte); ok { + body = b + } else { + body, _ = json.Marshal(tt.requestBody) + } + + req := httptest.NewRequest(http.MethodPost, "/trips/"+tt.tripID+"/photos", bytes.NewReader(body)) + req = req.WithContext(context.WithValue(req.Context(), middleware.IdKey, tt.authUserID)) + + w := httptest.NewRecorder() + tt.mockBehavior() + router.ServeHTTP(w, req) + + resp := w.Result() + assert.Equal(t, tt.expectedCode, resp.StatusCode) + + respBody, _ := io.ReadAll(resp.Body) + assert.JSONEq(t, tt.expectedBody, string(respBody)) + }) + } +} + +func TestDeletePhotoHandler(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockClient := mock.NewMockTripsClient(ctrl) + logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})) + handler := NewTripHandler(mockClient, logger) + + tests := []struct { + name string + tripID string + authUserID uint + requestBody interface{} + mockBehavior func() + expectedCode int + expectedBody string + }{ + { + name: "Success", + tripID: "1", + authUserID: 1, + requestBody: map[string]string{ + "photo_path": "photo_path_1", + }, + mockBehavior: func() { + mockClient.EXPECT().DeletePhotoFromTrip(gomock.Any(), &gen.DeletePhotoRequest{ + TripId: 1, + PhotoPath: "photo_path_1", + }).Return(&gen.EmptyResponse{}, nil) + }, + expectedCode: http.StatusOK, + expectedBody: `{"message":"Photo deleted successfully"}`, + }, + { + name: "Invalid Trip ID", + tripID: "abc", + authUserID: 1, + requestBody: map[string]string{"photo_path": "photo_path_1"}, + mockBehavior: func() {}, + expectedCode: http.StatusBadRequest, + expectedBody: `{"message":"Invalid trip ID"}`, + }, + { + name: "Invalid Request Body", + tripID: "1", + authUserID: 1, + requestBody: `invalid-body`, + mockBehavior: func() {}, + expectedCode: http.StatusBadRequest, + expectedBody: `{"message":"Invalid request body"}`, + }, + { + name: "GRPC Error", + tripID: "1", + authUserID: 1, + requestBody: map[string]string{ + "photo_path": "photo_path_1", + }, + mockBehavior: func() { + mockClient.EXPECT().DeletePhotoFromTrip(gomock.Any(), gomock.Any()).Return(nil, errors.New("grpc error")) + }, + expectedCode: http.StatusInternalServerError, + expectedBody: `{"message":"Failed to delete photo"}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + router := mux.NewRouter() + router.HandleFunc("/trips/{id}/photos", handler.DeletePhotoHandler).Methods(http.MethodDelete) + + var body []byte + if b, ok := tt.requestBody.([]byte); ok { + body = b + } else { + body, _ = json.Marshal(tt.requestBody) + } + + req := httptest.NewRequest(http.MethodDelete, "/trips/"+tt.tripID+"/photos", bytes.NewReader(body)) + req = req.WithContext(context.WithValue(req.Context(), middleware.IdKey, tt.authUserID)) + + w := httptest.NewRecorder() + tt.mockBehavior() + router.ServeHTTP(w, req) + + resp := w.Result() + assert.Equal(t, tt.expectedCode, resp.StatusCode) + + respBody, _ := io.ReadAll(resp.Body) + assert.JSONEq(t, tt.expectedBody, string(respBody)) + }) + } +} diff --git a/internal/pkg/trips/mocks/mock_grpc_trips.go b/internal/pkg/trips/mocks/mock_grpc_trips.go new file mode 100644 index 0000000..6f93a70 --- /dev/null +++ b/internal/pkg/trips/mocks/mock_grpc_trips.go @@ -0,0 +1,392 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: internal/pkg/trips/delivery/grpc/gen/trips_grpc.pb.go +// +// Generated by this command: +// +// mockgen -source=internal/pkg/trips/delivery/grpc/gen/trips_grpc.pb.go -destination=internal/pkg/trips/mocks/mock_grpc_trips.go -package=repo +// + +// Package repo is a generated GoMock package. +package mock + +import ( + gen "2024_2_ThereWillBeName/internal/pkg/trips/delivery/grpc/gen" + context "context" + reflect "reflect" + + "github.com/golang/mock/gomock" + grpc "google.golang.org/grpc" +) + +// MockTripsClient is a mock of TripsClient interface. +type MockTripsClient struct { + ctrl *gomock.Controller + recorder *MockTripsClientMockRecorder +} + +// MockTripsClientMockRecorder is the mock recorder for MockTripsClient. +type MockTripsClientMockRecorder struct { + mock *MockTripsClient +} + +// NewMockTripsClient creates a new mock instance. +func NewMockTripsClient(ctrl *gomock.Controller) *MockTripsClient { + mock := &MockTripsClient{ctrl: ctrl} + mock.recorder = &MockTripsClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockTripsClient) EXPECT() *MockTripsClientMockRecorder { + return m.recorder +} + +// AddPhotosToTrip mocks base method. +func (m *MockTripsClient) AddPhotosToTrip(ctx context.Context, in *gen.AddPhotosToTripRequest, opts ...grpc.CallOption) (*gen.AddPhotosToTripResponse, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, in} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "AddPhotosToTrip", varargs...) + ret0, _ := ret[0].(*gen.AddPhotosToTripResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AddPhotosToTrip indicates an expected call of AddPhotosToTrip. +func (mr *MockTripsClientMockRecorder) AddPhotosToTrip(ctx, in any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, in}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddPhotosToTrip", reflect.TypeOf((*MockTripsClient)(nil).AddPhotosToTrip), varargs...) +} + +// AddPlaceToTrip mocks base method. +func (m *MockTripsClient) AddPlaceToTrip(ctx context.Context, in *gen.AddPlaceToTripRequest, opts ...grpc.CallOption) (*gen.EmptyResponse, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, in} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "AddPlaceToTrip", varargs...) + ret0, _ := ret[0].(*gen.EmptyResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AddPlaceToTrip indicates an expected call of AddPlaceToTrip. +func (mr *MockTripsClientMockRecorder) AddPlaceToTrip(ctx, in any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, in}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddPlaceToTrip", reflect.TypeOf((*MockTripsClient)(nil).AddPlaceToTrip), varargs...) +} + +// CreateTrip mocks base method. +func (m *MockTripsClient) CreateTrip(ctx context.Context, in *gen.CreateTripRequest, opts ...grpc.CallOption) (*gen.EmptyResponse, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, in} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CreateTrip", varargs...) + ret0, _ := ret[0].(*gen.EmptyResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateTrip indicates an expected call of CreateTrip. +func (mr *MockTripsClientMockRecorder) CreateTrip(ctx, in any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, in}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateTrip", reflect.TypeOf((*MockTripsClient)(nil).CreateTrip), varargs...) +} + +// DeletePhotoFromTrip mocks base method. +func (m *MockTripsClient) DeletePhotoFromTrip(ctx context.Context, in *gen.DeletePhotoRequest, opts ...grpc.CallOption) (*gen.EmptyResponse, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, in} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DeletePhotoFromTrip", varargs...) + ret0, _ := ret[0].(*gen.EmptyResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeletePhotoFromTrip indicates an expected call of DeletePhotoFromTrip. +func (mr *MockTripsClientMockRecorder) DeletePhotoFromTrip(ctx, in any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, in}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePhotoFromTrip", reflect.TypeOf((*MockTripsClient)(nil).DeletePhotoFromTrip), varargs...) +} + +// DeleteTrip mocks base method. +func (m *MockTripsClient) DeleteTrip(ctx context.Context, in *gen.DeleteTripRequest, opts ...grpc.CallOption) (*gen.EmptyResponse, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, in} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DeleteTrip", varargs...) + ret0, _ := ret[0].(*gen.EmptyResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeleteTrip indicates an expected call of DeleteTrip. +func (mr *MockTripsClientMockRecorder) DeleteTrip(ctx, in any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, in}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteTrip", reflect.TypeOf((*MockTripsClient)(nil).DeleteTrip), varargs...) +} + +// GetTrip mocks base method. +func (m *MockTripsClient) GetTrip(ctx context.Context, in *gen.GetTripRequest, opts ...grpc.CallOption) (*gen.GetTripResponse, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, in} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetTrip", varargs...) + ret0, _ := ret[0].(*gen.GetTripResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTrip indicates an expected call of GetTrip. +func (mr *MockTripsClientMockRecorder) GetTrip(ctx, in any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, in}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTrip", reflect.TypeOf((*MockTripsClient)(nil).GetTrip), varargs...) +} + +// GetTripsByUserID mocks base method. +func (m *MockTripsClient) GetTripsByUserID(ctx context.Context, in *gen.GetTripsByUserIDRequest, opts ...grpc.CallOption) (*gen.GetTripsByUserIDResponse, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, in} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetTripsByUserID", varargs...) + ret0, _ := ret[0].(*gen.GetTripsByUserIDResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTripsByUserID indicates an expected call of GetTripsByUserID. +func (mr *MockTripsClientMockRecorder) GetTripsByUserID(ctx, in any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, in}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTripsByUserID", reflect.TypeOf((*MockTripsClient)(nil).GetTripsByUserID), varargs...) +} + +// UpdateTrip mocks base method. +func (m *MockTripsClient) UpdateTrip(ctx context.Context, in *gen.UpdateTripRequest, opts ...grpc.CallOption) (*gen.EmptyResponse, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, in} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "UpdateTrip", varargs...) + ret0, _ := ret[0].(*gen.EmptyResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateTrip indicates an expected call of UpdateTrip. +func (mr *MockTripsClientMockRecorder) UpdateTrip(ctx, in any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, in}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateTrip", reflect.TypeOf((*MockTripsClient)(nil).UpdateTrip), varargs...) +} + +// MockTripsServer is a mock of TripsServer interface. +type MockTripsServer struct { + ctrl *gomock.Controller + recorder *MockTripsServerMockRecorder +} + +// MockTripsServerMockRecorder is the mock recorder for MockTripsServer. +type MockTripsServerMockRecorder struct { + mock *MockTripsServer +} + +// NewMockTripsServer creates a new mock instance. +func NewMockTripsServer(ctrl *gomock.Controller) *MockTripsServer { + mock := &MockTripsServer{ctrl: ctrl} + mock.recorder = &MockTripsServerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockTripsServer) EXPECT() *MockTripsServerMockRecorder { + return m.recorder +} + +// AddPhotosToTrip mocks base method. +func (m *MockTripsServer) AddPhotosToTrip(arg0 context.Context, arg1 *gen.AddPhotosToTripRequest) (*gen.AddPhotosToTripResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddPhotosToTrip", arg0, arg1) + ret0, _ := ret[0].(*gen.AddPhotosToTripResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AddPhotosToTrip indicates an expected call of AddPhotosToTrip. +func (mr *MockTripsServerMockRecorder) AddPhotosToTrip(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddPhotosToTrip", reflect.TypeOf((*MockTripsServer)(nil).AddPhotosToTrip), arg0, arg1) +} + +// AddPlaceToTrip mocks base method. +func (m *MockTripsServer) AddPlaceToTrip(arg0 context.Context, arg1 *gen.AddPlaceToTripRequest) (*gen.EmptyResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddPlaceToTrip", arg0, arg1) + ret0, _ := ret[0].(*gen.EmptyResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AddPlaceToTrip indicates an expected call of AddPlaceToTrip. +func (mr *MockTripsServerMockRecorder) AddPlaceToTrip(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddPlaceToTrip", reflect.TypeOf((*MockTripsServer)(nil).AddPlaceToTrip), arg0, arg1) +} + +// CreateTrip mocks base method. +func (m *MockTripsServer) CreateTrip(arg0 context.Context, arg1 *gen.CreateTripRequest) (*gen.EmptyResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateTrip", arg0, arg1) + ret0, _ := ret[0].(*gen.EmptyResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateTrip indicates an expected call of CreateTrip. +func (mr *MockTripsServerMockRecorder) CreateTrip(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateTrip", reflect.TypeOf((*MockTripsServer)(nil).CreateTrip), arg0, arg1) +} + +// DeletePhotoFromTrip mocks base method. +func (m *MockTripsServer) DeletePhotoFromTrip(arg0 context.Context, arg1 *gen.DeletePhotoRequest) (*gen.EmptyResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeletePhotoFromTrip", arg0, arg1) + ret0, _ := ret[0].(*gen.EmptyResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeletePhotoFromTrip indicates an expected call of DeletePhotoFromTrip. +func (mr *MockTripsServerMockRecorder) DeletePhotoFromTrip(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePhotoFromTrip", reflect.TypeOf((*MockTripsServer)(nil).DeletePhotoFromTrip), arg0, arg1) +} + +// DeleteTrip mocks base method. +func (m *MockTripsServer) DeleteTrip(arg0 context.Context, arg1 *gen.DeleteTripRequest) (*gen.EmptyResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteTrip", arg0, arg1) + ret0, _ := ret[0].(*gen.EmptyResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeleteTrip indicates an expected call of DeleteTrip. +func (mr *MockTripsServerMockRecorder) DeleteTrip(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteTrip", reflect.TypeOf((*MockTripsServer)(nil).DeleteTrip), arg0, arg1) +} + +// GetTrip mocks base method. +func (m *MockTripsServer) GetTrip(arg0 context.Context, arg1 *gen.GetTripRequest) (*gen.GetTripResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTrip", arg0, arg1) + ret0, _ := ret[0].(*gen.GetTripResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTrip indicates an expected call of GetTrip. +func (mr *MockTripsServerMockRecorder) GetTrip(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTrip", reflect.TypeOf((*MockTripsServer)(nil).GetTrip), arg0, arg1) +} + +// GetTripsByUserID mocks base method. +func (m *MockTripsServer) GetTripsByUserID(arg0 context.Context, arg1 *gen.GetTripsByUserIDRequest) (*gen.GetTripsByUserIDResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTripsByUserID", arg0, arg1) + ret0, _ := ret[0].(*gen.GetTripsByUserIDResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTripsByUserID indicates an expected call of GetTripsByUserID. +func (mr *MockTripsServerMockRecorder) GetTripsByUserID(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTripsByUserID", reflect.TypeOf((*MockTripsServer)(nil).GetTripsByUserID), arg0, arg1) +} + +// UpdateTrip mocks base method. +func (m *MockTripsServer) UpdateTrip(arg0 context.Context, arg1 *gen.UpdateTripRequest) (*gen.EmptyResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateTrip", arg0, arg1) + ret0, _ := ret[0].(*gen.EmptyResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateTrip indicates an expected call of UpdateTrip. +func (mr *MockTripsServerMockRecorder) UpdateTrip(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateTrip", reflect.TypeOf((*MockTripsServer)(nil).UpdateTrip), arg0, arg1) +} + +// mustEmbedUnimplementedTripsServer mocks base method. +func (m *MockTripsServer) mustEmbedUnimplementedTripsServer() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "mustEmbedUnimplementedTripsServer") +} + +// mustEmbedUnimplementedTripsServer indicates an expected call of mustEmbedUnimplementedTripsServer. +func (mr *MockTripsServerMockRecorder) mustEmbedUnimplementedTripsServer() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "mustEmbedUnimplementedTripsServer", reflect.TypeOf((*MockTripsServer)(nil).mustEmbedUnimplementedTripsServer)) +} + +// MockUnsafeTripsServer is a mock of UnsafeTripsServer interface. +type MockUnsafeTripsServer struct { + ctrl *gomock.Controller + recorder *MockUnsafeTripsServerMockRecorder +} + +// MockUnsafeTripsServerMockRecorder is the mock recorder for MockUnsafeTripsServer. +type MockUnsafeTripsServerMockRecorder struct { + mock *MockUnsafeTripsServer +} + +// NewMockUnsafeTripsServer creates a new mock instance. +func NewMockUnsafeTripsServer(ctrl *gomock.Controller) *MockUnsafeTripsServer { + mock := &MockUnsafeTripsServer{ctrl: ctrl} + mock.recorder = &MockUnsafeTripsServerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockUnsafeTripsServer) EXPECT() *MockUnsafeTripsServerMockRecorder { + return m.recorder +} + +// mustEmbedUnimplementedTripsServer mocks base method. +func (m *MockUnsafeTripsServer) mustEmbedUnimplementedTripsServer() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "mustEmbedUnimplementedTripsServer") +} + +// mustEmbedUnimplementedTripsServer indicates an expected call of mustEmbedUnimplementedTripsServer. +func (mr *MockUnsafeTripsServerMockRecorder) mustEmbedUnimplementedTripsServer() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "mustEmbedUnimplementedTripsServer", reflect.TypeOf((*MockUnsafeTripsServer)(nil).mustEmbedUnimplementedTripsServer)) +} diff --git a/internal/pkg/trips/mocks/mock_trips.go b/internal/pkg/trips/mocks/mock_trips.go index b8bee2b..a83a910 100644 --- a/internal/pkg/trips/mocks/mock_trips.go +++ b/internal/pkg/trips/mocks/mock_trips.go @@ -3,18 +3,18 @@ // // Generated by this command: // -// mockgen -source=internal/pkg/trips/interfaces.go -destination=internal/pkg/trips/mocks/mock_trips.go -package=trips +// mockgen -source=internal/pkg/trips/interfaces.go -destination=internal/pkg/trips/mocks/mock_trips.go -package=repo // -// Package trips is a generated GoMock package. -package trips +// Package repo is a generated GoMock package. +package mock import ( models "2024_2_ThereWillBeName/internal/models" context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" + "github.com/golang/mock/gomock" ) // MockTripsUsecase is a mock of TripsUsecase interface. @@ -40,6 +40,19 @@ func (m *MockTripsUsecase) EXPECT() *MockTripsUsecaseMockRecorder { return m.recorder } +// AddPhotosToTrip mocks base method. +func (m *MockTripsUsecase) AddPhotosToTrip(ctx context.Context, tripID uint, photos []string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddPhotosToTrip", ctx, tripID, photos) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddPhotosToTrip indicates an expected call of AddPhotosToTrip. +func (mr *MockTripsUsecaseMockRecorder) AddPhotosToTrip(ctx, tripID, photos any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddPhotosToTrip", reflect.TypeOf((*MockTripsUsecase)(nil).AddPhotosToTrip), ctx, tripID, photos) +} // AddPlaceToTrip mocks base method. func (m *MockTripsUsecase) AddPlaceToTrip(ctx context.Context, tripID, placeID uint) error { @@ -55,7 +68,6 @@ func (mr *MockTripsUsecaseMockRecorder) AddPlaceToTrip(ctx, tripID, placeID any) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddPlaceToTrip", reflect.TypeOf((*MockTripsUsecase)(nil).AddPlaceToTrip), ctx, tripID, placeID) } - // CreateTrip mocks base method. func (m *MockTripsUsecase) CreateTrip(ctx context.Context, trip models.Trip) error { m.ctrl.T.Helper() @@ -70,6 +82,20 @@ func (mr *MockTripsUsecaseMockRecorder) CreateTrip(ctx, trip any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateTrip", reflect.TypeOf((*MockTripsUsecase)(nil).CreateTrip), ctx, trip) } +// DeletePhotoFromTrip mocks base method. +func (m *MockTripsUsecase) DeletePhotoFromTrip(ctx context.Context, tripID uint, photoPath string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeletePhotoFromTrip", ctx, tripID, photoPath) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeletePhotoFromTrip indicates an expected call of DeletePhotoFromTrip. +func (mr *MockTripsUsecaseMockRecorder) DeletePhotoFromTrip(ctx, tripID, photoPath any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePhotoFromTrip", reflect.TypeOf((*MockTripsUsecase)(nil).DeletePhotoFromTrip), ctx, tripID, photoPath) +} + // DeleteTrip mocks base method. func (m *MockTripsUsecase) DeleteTrip(ctx context.Context, id uint) error { m.ctrl.T.Helper() @@ -151,6 +177,19 @@ func (m *MockTripsRepo) EXPECT() *MockTripsRepoMockRecorder { return m.recorder } +// AddPhotoToTrip mocks base method. +func (m *MockTripsRepo) AddPhotoToTrip(ctx context.Context, tripID uint, photoPath string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddPhotoToTrip", ctx, tripID, photoPath) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddPhotoToTrip indicates an expected call of AddPhotoToTrip. +func (mr *MockTripsRepoMockRecorder) AddPhotoToTrip(ctx, tripID, photoPath any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddPhotoToTrip", reflect.TypeOf((*MockTripsRepo)(nil).AddPhotoToTrip), ctx, tripID, photoPath) +} // AddPlaceToTrip mocks base method. func (m *MockTripsRepo) AddPlaceToTrip(ctx context.Context, tripID, placeID uint) error { @@ -180,6 +219,20 @@ func (mr *MockTripsRepoMockRecorder) CreateTrip(ctx, user any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateTrip", reflect.TypeOf((*MockTripsRepo)(nil).CreateTrip), ctx, user) } +// DeletePhotoFromTrip mocks base method. +func (m *MockTripsRepo) DeletePhotoFromTrip(ctx context.Context, tripID uint, photoPath string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeletePhotoFromTrip", ctx, tripID, photoPath) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeletePhotoFromTrip indicates an expected call of DeletePhotoFromTrip. +func (mr *MockTripsRepoMockRecorder) DeletePhotoFromTrip(ctx, tripID, photoPath any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePhotoFromTrip", reflect.TypeOf((*MockTripsRepo)(nil).DeletePhotoFromTrip), ctx, tripID, photoPath) +} + // DeleteTrip mocks base method. func (m *MockTripsRepo) DeleteTrip(ctx context.Context, id uint) error { m.ctrl.T.Helper() @@ -236,6 +289,4 @@ func (m *MockTripsRepo) UpdateTrip(ctx context.Context, user models.Trip) error func (mr *MockTripsRepoMockRecorder) UpdateTrip(ctx, user any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateTrip", reflect.TypeOf((*MockTripsRepo)(nil).UpdateTrip), ctx, user) - } - diff --git a/internal/pkg/trips/repo/trips_repository_test.go b/internal/pkg/trips/repo/trips_repository_test.go index 8c28e07..fad0780 100644 --- a/internal/pkg/trips/repo/trips_repository_test.go +++ b/internal/pkg/trips/repo/trips_repository_test.go @@ -3,329 +3,497 @@ package repo import ( "2024_2_ThereWillBeName/internal/models" "context" - "database/sql" "errors" "fmt" - "time" - "testing" + "time" "github.com/DATA-DOG/go-sqlmock" + "github.com/lib/pq" "github.com/stretchr/testify/assert" ) func TestCreateTrip(t *testing.T) { - db, mock, err := sqlmock.New() - if err != nil { - t.Fatalf("failed to create mock: %s", err) - } - defer db.Close() - - repo := NewTripRepository(db) - tests := []struct { - name string - trip models.Trip - mockSetup func() - expectedErr error + name string + trip models.Trip + mockBehavior func(mock sqlmock.Sqlmock) + expectedError error }{ { - name: "successful creation", - trip: models.Trip{UserID: 1, Name: "Test trip", Description: "A trip for testing", CityID: 1}, - mockSetup: func() { - mock.ExpectExec(`INSERT INTO trip`).WithArgs(1, "Test trip", "A trip for testing", 1, sqlmock.AnyArg(), sqlmock.AnyArg(), false). + name: "Success", + trip: models.Trip{ + UserID: 1, + Name: "Trip to Paris", + Description: "A great trip", + CityID: 2, + StartDate: "2024-12-01", + EndDate: "2024-12-10", + Private: false, + }, + mockBehavior: func(mock sqlmock.Sqlmock) { + mock.ExpectExec(`INSERT INTO trip`). + WithArgs(1, "Trip to Paris", "A great trip", 2, "2024-12-01", "2024-12-10", false). WillReturnResult(sqlmock.NewResult(1, 1)) }, - expectedErr: nil, + expectedError: nil, }, { - name: "error on exec", - trip: models.Trip{UserID: 1, Name: "Test trip", Description: "A trip for testing", CityID: 1}, - mockSetup: func() { - mock.ExpectExec(`INSERT INTO trip`).WithArgs(1, "Test trip", "A trip for testing", 1, sqlmock.AnyArg(), sqlmock.AnyArg(), false). - WillReturnError(errors.New("exec error")) + name: "Insert Failed", + trip: models.Trip{ + UserID: 1, + Name: "Trip to Paris", + Description: "A great trip", + CityID: 2, + StartDate: "2024-12-01", + EndDate: "2024-12-10", + Private: false, }, - expectedErr: fmt.Errorf("failed to create a trip: %w", models.ErrInternal), + mockBehavior: func(mock sqlmock.Sqlmock) { + mock.ExpectExec(`INSERT INTO trip`). + WithArgs(1, "Trip to Paris", "A great trip", 2, "2024-12-01", "2024-12-10", false). + WillReturnError(errors.New("insert failed")) + }, + expectedError: models.ErrInternal, }, { - name: "no rows created", - trip: models.Trip{UserID: 1, Name: "Test trip", Description: "A trip for testing", CityID: 1}, - mockSetup: func() { - mock.ExpectExec(`INSERT INTO trip`).WithArgs(1, "Test trip", "A trip for testing", 1, sqlmock.AnyArg(), sqlmock.AnyArg(), false). - WillReturnResult(sqlmock.NewResult(0, 0)) + name: "No Rows Affected", + trip: models.Trip{ + UserID: 1, + Name: "Trip to Paris", + Description: "A great trip", + CityID: 2, + StartDate: "2024-12-01", + EndDate: "2024-12-10", + Private: false, + }, + mockBehavior: func(mock sqlmock.Sqlmock) { + mock.ExpectExec(`INSERT INTO trip`). + WithArgs(1, "Trip to Paris", "A great trip", 2, "2024-12-01", "2024-12-10", false). + WillReturnResult(sqlmock.NewResult(1, 0)) }, - expectedErr: fmt.Errorf("no rows were created: %w", models.ErrNotFound), + expectedError: models.ErrNotFound, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tt.mockSetup() + db, mock, _ := sqlmock.New() + defer db.Close() + + repo := NewTripRepository(db) + + tt.mockBehavior(mock) + err := repo.CreateTrip(context.Background(), tt.trip) - if tt.expectedErr != nil { - assert.EqualError(t, err, tt.expectedErr.Error()) + if tt.expectedError != nil { + assert.ErrorIs(t, err, tt.expectedError) } else { assert.NoError(t, err) } - assert.NoError(t, mock.ExpectationsWereMet()) }) } } func TestUpdateTrip(t *testing.T) { - db, mock, err := sqlmock.New() - if err != nil { - t.Fatalf("failed to create mock: %s", err) - } - defer db.Close() - - repo := NewTripRepository(db) - tests := []struct { - name string - trip models.Trip - mockSetup func() - expectedErr error + name string + trip models.Trip + mockBehavior func(mock sqlmock.Sqlmock) + expectedError error }{ { - name: "successful update", - trip: models.Trip{ID: 1, Name: "Updated Trip", Description: "Updated description", CityID: 2}, - mockSetup: func() { - mock.ExpectExec(`UPDATE trip`).WithArgs("Updated Trip", "Updated description", 2, sqlmock.AnyArg(), sqlmock.AnyArg(), false, 1). + name: "Success", + trip: models.Trip{ + ID: 1, + Name: "Updated Trip", + Description: "Updated Description", + CityID: 2, + StartDate: "2024-12-01", + EndDate: "2024-12-10", + Private: false, + }, + mockBehavior: func(mock sqlmock.Sqlmock) { + mock.ExpectExec(`UPDATE trip`). + WithArgs("Updated Trip", "Updated Description", 2, "2024-12-01", "2024-12-10", false, 1). WillReturnResult(sqlmock.NewResult(1, 1)) }, - expectedErr: nil, + expectedError: nil, }, { - name: "error on exec", - trip: models.Trip{ID: 1, Name: "Updated Trip", Description: "Updated description", CityID: 2}, - mockSetup: func() { - mock.ExpectExec(`UPDATE trip`).WithArgs("Updated Trip", "Updated description", 2, sqlmock.AnyArg(), sqlmock.AnyArg(), false, 1). - WillReturnError(errors.New("exec error")) + name: "Update Failed", + trip: models.Trip{ + ID: 1, + Name: "Updated Trip", + Description: "Updated Description", + CityID: 2, + StartDate: "2024-12-01", + EndDate: "2024-12-10", + Private: false, }, - expectedErr: fmt.Errorf("failed to execute update query: %w", models.ErrInternal), + mockBehavior: func(mock sqlmock.Sqlmock) { + mock.ExpectExec(`UPDATE trip`). + WithArgs("Updated Trip", "Updated Description", 2, "2024-12-01", "2024-12-10", false, 1). + WillReturnError(errors.New("update failed")) + }, + expectedError: models.ErrInternal, }, { - name: "no rows updated", - trip: models.Trip{ID: 1, Name: "Updated Trip", Description: "Updated description", CityID: 2}, - mockSetup: func() { - mock.ExpectExec(`UPDATE trip`).WithArgs("Updated Trip", "Updated description", 2, sqlmock.AnyArg(), sqlmock.AnyArg(), false, 1). - WillReturnResult(sqlmock.NewResult(0, 0)) + name: "No Rows Affected", + trip: models.Trip{ + ID: 1, + Name: "Updated Trip", + Description: "Updated Description", + CityID: 2, + StartDate: "2024-12-01", + EndDate: "2024-12-10", + Private: false, + }, + mockBehavior: func(mock sqlmock.Sqlmock) { + mock.ExpectExec(`UPDATE trip`). + WithArgs("Updated Trip", "Updated Description", 2, "2024-12-01", "2024-12-10", false, 1). + WillReturnResult(sqlmock.NewResult(1, 0)) }, - expectedErr: fmt.Errorf("no rows were updated: %w", models.ErrNotFound), + expectedError: models.ErrNotFound, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tt.mockSetup() + db, mock, _ := sqlmock.New() + defer db.Close() + + repo := NewTripRepository(db) + + tt.mockBehavior(mock) + err := repo.UpdateTrip(context.Background(), tt.trip) - if tt.expectedErr != nil { - assert.EqualError(t, err, tt.expectedErr.Error()) + if tt.expectedError != nil { + assert.ErrorIs(t, err, tt.expectedError) } else { assert.NoError(t, err) } - assert.NoError(t, mock.ExpectationsWereMet()) }) } } func TestDeleteTrip(t *testing.T) { - db, mock, err := sqlmock.New() - if err != nil { - t.Fatalf("failed to create mock: %s", err) - } - defer db.Close() - - repo := NewTripRepository(db) - tests := []struct { - name string - tripID uint - mockSetup func() - expectedErr error + name string + tripID uint + mockBehavior func(mock sqlmock.Sqlmock) + expectedError error }{ { - name: "successful deletion", + name: "Success", tripID: 1, - mockSetup: func() { - mock.ExpectExec(`DELETE FROM trip`).WithArgs(1).WillReturnResult(sqlmock.NewResult(1, 1)) + mockBehavior: func(mock sqlmock.Sqlmock) { + mock.ExpectExec(`DELETE FROM trip`). + WithArgs(1). + WillReturnResult(sqlmock.NewResult(1, 1)) }, - expectedErr: nil, + expectedError: nil, }, { - name: "error on exec", + name: "Delete Failed", tripID: 1, - mockSetup: func() { - mock.ExpectExec(`DELETE FROM trip`).WithArgs(1).WillReturnError(errors.New("exec error")) + mockBehavior: func(mock sqlmock.Sqlmock) { + mock.ExpectExec(`DELETE FROM trip`). + WithArgs(1). + WillReturnError(errors.New("delete failed")) }, - expectedErr: fmt.Errorf("failed to delete trip: %w", models.ErrInternal), + expectedError: models.ErrInternal, }, { - name: "no rows deleted", + name: "No Rows Affected", tripID: 1, - mockSetup: func() { - mock.ExpectExec(`DELETE FROM trip`).WithArgs(1).WillReturnResult(sqlmock.NewResult(0, 0)) + mockBehavior: func(mock sqlmock.Sqlmock) { + mock.ExpectExec(`DELETE FROM trip`). + WithArgs(1). + WillReturnResult(sqlmock.NewResult(1, 0)) }, - expectedErr: fmt.Errorf("no rows were deleted: %w", models.ErrNotFound), + expectedError: models.ErrNotFound, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tt.mockSetup() + db, mock, _ := sqlmock.New() + defer db.Close() + + repo := NewTripRepository(db) + + tt.mockBehavior(mock) + err := repo.DeleteTrip(context.Background(), tt.tripID) - if tt.expectedErr != nil { - assert.EqualError(t, err, tt.expectedErr.Error()) + if tt.expectedError != nil { + assert.ErrorIs(t, err, tt.expectedError) } else { assert.NoError(t, err) } - assert.NoError(t, mock.ExpectationsWereMet()) }) } } - func TestGetTripsByUserID(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { - t.Fatalf("failed to create mock: %s", err) + t.Fatalf("unexpected error when opening stub database connection: %v", err) } defer db.Close() - ctx := context.Background() + repo := NewTripRepository(db) - userID := uint(1) - limit, offset := 10, 0 - createdAt := time.Now() - tests := []struct { - name string - mockSetup func() - expectedTrips []models.Trip - expectedError error - }{ - { - name: "successful retrieval", - mockSetup: func() { - rows := sqlmock.NewRows([]string{"id", "user_id", "name", "description", "city_id", "start_date", "end_date", "private", "created_at"}). - AddRow(1, userID, "Test trip 1", "A trip for testing", 1, "2024-01-01", "2024-01-05", false, createdAt). - AddRow(2, userID, "Test trip 2", "A trip for testing", 2, "2024-02-01", "2024-02-10", true, createdAt) - - mock.ExpectQuery(`SELECT id, user_id, name, description, city_id, start_date, end_date, private, created_at FROM trip WHERE user_id = \$1 ORDER BY created_at DESC LIMIT \$2 OFFSET \$3`). - WithArgs(userID, limit, offset). - WillReturnRows(rows) - }, - expectedTrips: []models.Trip{ - {ID: 1, UserID: userID, Name: "Test trip 1", Description: "A trip for testing", CityID: 1, StartDate: "2024-01-01", EndDate: "2024-01-05", Private: false, CreatedAt: createdAt}, - {ID: 2, UserID: userID, Name: "Test trip 2", Description: "A trip for testing", CityID: 2, StartDate: "2024-02-01", EndDate: "2024-02-10", Private: true, CreatedAt: createdAt}, - }, - expectedError: nil, - }, - { - name: "query execution error", - mockSetup: func() { - mock.ExpectQuery(`SELECT id, user_id, name, description, city_id, start_date, end_date, private, created_at FROM trip WHERE user_id = \$1 ORDER BY created_at DESC LIMIT \$2 OFFSET \$3`). - WithArgs(userID, limit, offset). - WillReturnError(models.ErrInternal) - }, - expectedTrips: nil, - expectedError: fmt.Errorf("failed to retrieve trips: %w", models.ErrInternal), - }, - { - name: "no trips found", - mockSetup: func() { - rows := sqlmock.NewRows([]string{"id", "user_id", "name", "description", "city_id", "start_date", "end_date", "private", "created_at"}) - mock.ExpectQuery(`SELECT id, user_id, name, description, city_id, start_date, end_date, private, created_at FROM trip WHERE user_id = \$1 ORDER BY created_at DESC LIMIT \$2 OFFSET \$3`). - WithArgs(userID, limit, offset). - WillReturnRows(rows) + t.Run("Success", func(t *testing.T) { + createdAt := time.Date(2024, time.November, 25, 0, 0, 0, 0, time.UTC) + rows := sqlmock.NewRows([]string{ + "id", "user_id", "name", "description", "city_id", + "start_date", "end_date", "private", "created_at", "photos", + }).AddRow( + 1, 1, "Trip 1", "Description 1", 2, + "2024-12-01", "2024-12-10", false, createdAt, pq.Array([]string{"photo1.jpg", "photo2.jpg"}), + ) + + mock.ExpectQuery(`SELECT (.+) FROM trip t LEFT JOIN trip_photo tp`). + WithArgs(1, 10, 0). + WillReturnRows(rows) + + expected := []models.Trip{ + { + ID: 1, + UserID: 1, + Name: "Trip 1", + Description: "Description 1", + CityID: 2, + StartDate: "2024-12-01", + EndDate: "2024-12-10", + Private: false, + Photos: []string{"photo1.jpg", "photo2.jpg"}, + CreatedAt: createdAt, }, - expectedTrips: nil, - expectedError: fmt.Errorf("no trips found: %w", models.ErrNotFound), - }, - } + } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.mockSetup() + result, err := repo.GetTripsByUserID(context.Background(), 1, 10, 0) + + assert.NoError(t, err) + assert.Equal(t, expected, result) + + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("there were unfulfilled expectations: %s", err) + } + }) - trips, err := repo.GetTripsByUserID(ctx, userID, limit, offset) - assert.Equal(t, tt.expectedError, err) - assert.Equal(t, tt.expectedTrips, trips) + t.Run("No Trips Found", func(t *testing.T) { + rows := sqlmock.NewRows([]string{ + "id", "user_id", "name", "description", "city_id", + "start_date", "end_date", "private", "created_at", "photos", }) - } - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("there were unfulfilled expectations: %s", err) - } + mock.ExpectQuery(`SELECT (.+) FROM trip t LEFT JOIN trip_photo tp`). + WithArgs(1, 10, 0). + WillReturnRows(rows) + + result, err := repo.GetTripsByUserID(context.Background(), 1, 10, 0) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "no trips found") + assert.Nil(t, result) + + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("there were unfulfilled expectations: %s", err) + } + }) + + t.Run("Database Error", func(t *testing.T) { + mock.ExpectQuery(`SELECT (.+) FROM trip t LEFT JOIN trip_photo tp`). + WithArgs(1, 10, 0). + WillReturnError(fmt.Errorf("database error")) + + result, err := repo.GetTripsByUserID(context.Background(), 1, 10, 0) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to retrieve trips") + assert.Nil(t, result) + + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("there were unfulfilled expectations: %s", err) + } + }) } -func TestGetTrip(t *testing.T) { +func TestAddPlaceToTrip(t *testing.T) { tests := []struct { name string tripID uint - mockSetup func(sqlmock.Sqlmock) - expectedTrip models.Trip + placeID uint + mockBehavior func(mock sqlmock.Sqlmock) expectedError error }{ { - name: "successful retrieval", - tripID: 1, - mockSetup: func(mock sqlmock.Sqlmock) { - rows := sqlmock.NewRows([]string{"id", "user_id", "name", "description", "city_id", "start_date", "end_date", "private", "created_at"}). - AddRow(1, 1, "Test trip", "A trip for testing", 1, "2024-01-01", "2024-01-05", false, time.Now()) - mock.ExpectQuery(`SELECT id, user_id, name, description, city_id, start_date, end_date, private, created_at FROM trip WHERE id = \$1`). - WithArgs(1). - WillReturnRows(rows) - }, - expectedTrip: models.Trip{ - ID: 1, - UserID: 1, - Name: "Test trip", - Description: "A trip for testing", - CityID: 1, - StartDate: "2024-01-01", - EndDate: "2024-01-05", - Private: false, + name: "Success", + tripID: 1, + placeID: 2, + mockBehavior: func(mock sqlmock.Sqlmock) { + mock.ExpectExec(`INSERT INTO trip_place`). + WithArgs(1, 2). + WillReturnResult(sqlmock.NewResult(1, 1)) }, expectedError: nil, }, { - name: "trip not found", - tripID: 2, - mockSetup: func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(`SELECT id, user_id, name, description, city_id, start_date, end_date, private, created_at FROM trip WHERE id = \$1`). - WithArgs(2). - WillReturnError(sql.ErrNoRows) + name: "Insert Failed", + tripID: 1, + placeID: 2, + mockBehavior: func(mock sqlmock.Sqlmock) { + mock.ExpectExec(`INSERT INTO trip_place`). + WithArgs(1, 2). + WillReturnError(errors.New("insert failed")) }, - expectedTrip: models.Trip{}, - expectedError: fmt.Errorf("trip not found: %w", models.ErrNotFound), + expectedError: models.ErrInternal, + }, + { + name: "No Rows Affected", + tripID: 1, + placeID: 2, + mockBehavior: func(mock sqlmock.Sqlmock) { + mock.ExpectExec(`INSERT INTO trip_place`). + WithArgs(1, 2). + WillReturnResult(sqlmock.NewResult(1, 0)) + }, + expectedError: models.ErrNotFound, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - db, mock, err := sqlmock.New() - assert.NoError(t, err) + db, mock, _ := sqlmock.New() defer db.Close() repo := NewTripRepository(db) - tt.mockSetup(mock) - trip, err := repo.GetTrip(context.Background(), tt.tripID) + tt.mockBehavior(mock) + + err := repo.AddPlaceToTrip(context.Background(), tt.tripID, tt.placeID) if tt.expectedError != nil { - assert.Error(t, err) - assert.EqualError(t, err, tt.expectedError.Error()) + assert.ErrorIs(t, err, tt.expectedError) } else { assert.NoError(t, err) - assert.Equal(t, tt.expectedTrip.ID, trip.ID) - assert.Equal(t, tt.expectedTrip.UserID, trip.UserID) - assert.Equal(t, tt.expectedTrip.Name, trip.Name) - assert.Equal(t, tt.expectedTrip.Description, trip.Description) - assert.Equal(t, tt.expectedTrip.CityID, trip.CityID) - assert.Equal(t, tt.expectedTrip.StartDate, trip.StartDate) - assert.Equal(t, tt.expectedTrip.EndDate, trip.EndDate) - assert.Equal(t, tt.expectedTrip.Private, trip.Private) } - assert.NoError(t, mock.ExpectationsWereMet()) }) } } +func TestAddPhotoToTrip(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("unexpected error when opening stub database connection: %v", err) + } + defer db.Close() + + repo := NewTripRepository(db) + + t.Run("Success", func(t *testing.T) { + tripID := uint(1) + photoPath := "photo1.jpg" + + query := ` + INSERT INTO trip_photo \(trip_id, photo_path\) + VALUES \(\$1, \$2\)` + + mock.ExpectExec(query). + WithArgs(tripID, photoPath). + WillReturnResult(sqlmock.NewResult(1, 1)) + + err := repo.AddPhotoToTrip(context.Background(), tripID, photoPath) + assert.NoError(t, err) + + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("there were unfulfilled expectations: %s", err) + } + }) + + t.Run("Database Error", func(t *testing.T) { + tripID := uint(1) + photoPath := "photo1.jpg" + + query := ` + INSERT INTO trip_photo \(trip_id, photo_path\) + VALUES \(\$1, \$2\)` + + mock.ExpectExec(query). + WithArgs(tripID, photoPath). + WillReturnError(fmt.Errorf("database error")) + + err := repo.AddPhotoToTrip(context.Background(), tripID, photoPath) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to insert photo into database") + + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("there were unfulfilled expectations: %s", err) + } + }) +} + +func TestDeletePhotoFromTrip(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("unexpected error when opening stub database connection: %v", err) + } + defer db.Close() + + repo := NewTripRepository(db) + + t.Run("Success", func(t *testing.T) { + tripID := uint(1) + photoPath := "photo1.jpg" + + query := `DELETE FROM trip_photo WHERE trip_id = \$1 AND photo_path = \$2` + + mock.ExpectExec(query). + WithArgs(tripID, photoPath). + WillReturnResult(sqlmock.NewResult(1, 1)) + + err := repo.DeletePhotoFromTrip(context.Background(), tripID, photoPath) + assert.NoError(t, err) + + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("there were unfulfilled expectations: %s", err) + } + }) + + t.Run("Photo Not Found", func(t *testing.T) { + tripID := uint(1) + photoPath := "photo1.jpg" + + query := `DELETE FROM trip_photo WHERE trip_id = \$1 AND photo_path = \$2` + + mock.ExpectExec(query). + WithArgs(tripID, photoPath). + WillReturnResult(sqlmock.NewResult(0, 0)) + + err := repo.DeletePhotoFromTrip(context.Background(), tripID, photoPath) + assert.Error(t, err) + assert.Contains(t, err.Error(), "photo not found in trip") + + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("there were unfulfilled expectations: %s", err) + } + }) + + t.Run("Database Error", func(t *testing.T) { + tripID := uint(1) + photoPath := "photo1.jpg" + + query := `DELETE FROM trip_photo WHERE trip_id = \$1 AND photo_path = \$2` + + mock.ExpectExec(query). + WithArgs(tripID, photoPath). + WillReturnError(fmt.Errorf("database error")) + + err := repo.DeletePhotoFromTrip(context.Background(), tripID, photoPath) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to delete photo from database") + + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("there were unfulfilled expectations: %s", err) + } + }) +} diff --git a/internal/pkg/trips/usecase/trips_usecase_test.go b/internal/pkg/trips/usecase/trips_usecase_test.go index 6c375b5..3d07475 100644 --- a/internal/pkg/trips/usecase/trips_usecase_test.go +++ b/internal/pkg/trips/usecase/trips_usecase_test.go @@ -1,280 +1,521 @@ package usecase -// import ( -// "2024_2_ThereWillBeName/internal/models" -// mocks "2024_2_ThereWillBeName/internal/pkg/trips/mocks" -// "context" -// "errors" -// "testing" - -// "github.com/golang/mock/gomock" -// "github.com/stretchr/testify/assert" -// ) - -// func TestCreateTrip(t *testing.T) { -// ctrl := gomock.NewController(t) -// defer ctrl.Finish() - -// mockRepo := mocks.NewMockTripsRepo(ctrl) -// usecase := NewTripsUsecase(mockRepo) - -// tests := []struct { -// name string -// inputTrip models.Trip -// usecaseErr error -// expectedErr error -// }{ -// { -// name: "successful_creation", -// inputTrip: models.Trip{Name: "Test Trip", UserID: 100}, -// usecaseErr: nil, -// expectedErr: nil, -// }, -// { -// name: "repository error", -// inputTrip: models.Trip{Name: "Test Trip", UserID: 100}, -// usecaseErr: errors.New("internal error"), -// expectedErr: errors.New("internal error: internal repository error"), -// }, -// { -// name: "not found error", -// inputTrip: models.Trip{Name: "Test Trip", UserID: 100}, -// usecaseErr: models.ErrNotFound, -// expectedErr: errors.New("invalid request: not found"), -// }, -// } - -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// mockRepo.EXPECT().CreateTrip(gomock.Any(), tt.inputTrip).Return(tt.usecaseErr) - -// err := usecase.CreateTrip(context.Background(), tt.inputTrip) - -// if tt.expectedErr != nil { -// assert.EqualError(t, err, tt.expectedErr.Error()) -// } else { -// assert.NoError(t, err) -// } -// }) -// } -// } - -// func TestUpdateTrip(t *testing.T) { -// ctrl := gomock.NewController(t) -// defer ctrl.Finish() - -// mockRepo := mocks.NewMockTripsRepo(ctrl) -// usecase := NewTripsUsecase(mockRepo) - -// tests := []struct { -// name string -// inputTrip models.Trip -// usecaseErr error -// expectedErr error -// }{ -// { -// name: "successful update", -// inputTrip: models.Trip{ID: 1, Name: "Updated Trip"}, -// usecaseErr: nil, -// expectedErr: nil, -// }, -// { -// name: "repository error", -// inputTrip: models.Trip{ID: 1, Name: "Updated Trip"}, -// usecaseErr: errors.New("internal error"), -// expectedErr: errors.New("internal error: internal repository error"), -// }, -// { -// name: "not found error", -// inputTrip: models.Trip{ID: 1, Name: "Updated Trip"}, -// usecaseErr: models.ErrNotFound, -// expectedErr: errors.New("invalid request: not found"), -// }, -// } - -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// mockRepo.EXPECT().UpdateTrip(gomock.Any(), tt.inputTrip).Return(tt.usecaseErr) - -// err := usecase.UpdateTrip(context.Background(), tt.inputTrip) - -// if tt.expectedErr != nil { -// assert.EqualError(t, err, tt.expectedErr.Error()) -// } else { -// assert.NoError(t, err) -// } -// }) -// } -// } - -// func TestDeleteTrip(t *testing.T) { -// ctrl := gomock.NewController(t) -// defer ctrl.Finish() - -// mockRepo := mocks.NewMockTripsRepo(ctrl) -// usecase := NewTripsUsecase(mockRepo) - -// tests := []struct { -// name string -// tripID uint -// usecaseErr error -// expectedErr error -// }{ -// { -// name: "successful deletion", -// tripID: 1, -// usecaseErr: nil, -// expectedErr: nil, -// }, -// { -// name: "repository error", -// tripID: 1, -// usecaseErr: errors.New("internal error"), -// expectedErr: errors.New("internal error: internal repository error"), -// }, -// { -// name: "not found error", -// tripID: 1, -// usecaseErr: models.ErrNotFound, -// expectedErr: errors.New("invalid request: not found"), -// }, -// } - -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// mockRepo.EXPECT().DeleteTrip(gomock.Any(), tt.tripID).Return(tt.usecaseErr) - -// err := usecase.DeleteTrip(context.Background(), tt.tripID) - -// if tt.expectedErr != nil { -// assert.EqualError(t, err, tt.expectedErr.Error()) -// } else { -// assert.NoError(t, err) -// } -// }) -// } -// } - -// func TestGetTripsByUserID(t *testing.T) { -// ctrl := gomock.NewController(t) -// defer ctrl.Finish() - -// mockRepo := mocks.NewMockTripsRepo(ctrl) -// usecase := NewTripsUsecase(mockRepo) - -// tests := []struct { -// name string -// userID uint -// limit int -// offset int -// repoTrips []models.Trip -// usecaseErr error -// expectedTrips []models.Trip -// expectedErr error -// }{ -// { -// name: "successful retrieval", -// userID: 1, -// limit: 10, -// offset: 0, -// repoTrips: []models.Trip{{ID: 1, Name: "Test trip"}}, -// usecaseErr: nil, -// expectedTrips: []models.Trip{{ID: 1, Name: "Test trip"}}, -// expectedErr: nil, -// }, -// { -// name: "repository error", -// userID: 1, -// limit: 10, -// offset: 0, -// repoTrips: nil, -// usecaseErr: errors.New("internal error"), -// expectedTrips: nil, -// expectedErr: errors.New("internal error: internal repository error"), -// }, -// { -// name: "not found error", -// userID: 1, -// limit: 10, -// offset: 0, -// repoTrips: nil, -// usecaseErr: models.ErrNotFound, -// expectedTrips: nil, -// expectedErr: errors.New("invalid request: not found"), -// }, -// } - -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// mockRepo.EXPECT().GetTripsByUserID(gomock.Any(), tt.userID, tt.limit, tt.offset).Return(tt.repoTrips, tt.usecaseErr) - -// trips, err := usecase.GetTripsByUserID(context.Background(), tt.userID, tt.limit, tt.offset) - -// assert.ElementsMatch(t, tt.expectedTrips, trips) - -// if tt.expectedErr != nil { -// assert.EqualError(t, err, tt.expectedErr.Error()) -// } else { -// assert.NoError(t, err) -// } -// }) -// } -// } - -// func TestGetTrip(t *testing.T) { -// ctrl := gomock.NewController(t) -// defer ctrl.Finish() - -// mockRepo := mocks.NewMockTripsRepo(ctrl) -// usecase := NewTripsUsecase(mockRepo) - -// tests := []struct { -// name string -// tripID uint -// repoTrip models.Trip -// usecaseErr error -// expectedTrip models.Trip -// expectedErr error -// }{ -// { -// name: "successful retrieval", -// tripID: 1, -// repoTrip: models.Trip{ID: 1, Name: "Test trip"}, -// usecaseErr: nil, -// expectedTrip: models.Trip{ID: 1, Name: "Test trip"}, -// expectedErr: nil, -// }, -// { -// name: "repository error", -// tripID: 1, -// repoTrip: models.Trip{}, -// usecaseErr: errors.New("internal error"), -// expectedTrip: models.Trip{}, -// expectedErr: errors.New("internal error: internal repository error"), -// }, -// { -// name: "not found error", -// tripID: 1, -// repoTrip: models.Trip{}, -// usecaseErr: models.ErrNotFound, -// expectedTrip: models.Trip{}, -// expectedErr: errors.New("invalid request: not found"), -// }, -// } - -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// mockRepo.EXPECT().GetTrip(gomock.Any(), tt.tripID).Return(tt.repoTrip, tt.usecaseErr) - -// trip, err := usecase.GetTrip(context.Background(), tt.tripID) - -// assert.Equal(t, tt.expectedTrip, trip) - -// if tt.expectedErr != nil { -// assert.EqualError(t, err, tt.expectedErr.Error()) -// } else { -// assert.NoError(t, err) -// } -// }) -// } -// } +import ( + "2024_2_ThereWillBeName/internal/models" + mock "2024_2_ThereWillBeName/internal/pkg/trips/mocks" + "context" + "errors" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" +) + +func TestTripsUsecaseImpl_CreateTrip(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockRepo := mock.NewMockTripsRepo(ctrl) + usecase := NewTripsUsecase(mockRepo) + + tests := []struct { + name string + inputTrip models.Trip + mockBehavior func() + expectedErr error + }{ + { + name: "Success", + inputTrip: models.Trip{ + UserID: 1, + Name: "Trip to Paris", + Description: "A nice trip", + CityID: 2, + StartDate: "2024-12-01", + EndDate: "2024-12-10", + }, + mockBehavior: func() { + mockRepo.EXPECT().CreateTrip(gomock.Any(), gomock.Any()).Return(nil) + }, + expectedErr: nil, + }, + { + name: "Repository Error - Not Found", + inputTrip: models.Trip{ + UserID: 1, + Name: "Trip to Paris", + Description: "A nice trip", + CityID: 2, + StartDate: "2024-12-01", + EndDate: "2024-12-10", + }, + mockBehavior: func() { + mockRepo.EXPECT().CreateTrip(gomock.Any(), gomock.Any()).Return(models.ErrNotFound) + }, + expectedErr: models.ErrNotFound, + }, + { + name: "Repository Error - Internal", + inputTrip: models.Trip{ + UserID: 1, + Name: "Trip to Paris", + Description: "A nice trip", + CityID: 2, + StartDate: "2024-12-01", + EndDate: "2024-12-10", + }, + mockBehavior: func() { + mockRepo.EXPECT().CreateTrip(gomock.Any(), gomock.Any()).Return(errors.New("internal error")) + }, + expectedErr: models.ErrInternal, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.mockBehavior() + + err := usecase.CreateTrip(context.Background(), tt.inputTrip) + + if tt.expectedErr != nil { + assert.Error(t, err) + assert.True(t, errors.Is(err, tt.expectedErr)) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestTripsUsecaseImpl_UpdateTrip(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockRepo := mock.NewMockTripsRepo(ctrl) + usecase := NewTripsUsecase(mockRepo) + + tests := []struct { + name string + inputTrip models.Trip + mockBehavior func() + expectedErr error + }{ + { + name: "Success", + inputTrip: models.Trip{ + ID: 1, + UserID: 1, + Name: "Updated Trip", + Description: "Updated Description", + CityID: 2, + StartDate: "2024-12-01", + EndDate: "2024-12-10", + }, + mockBehavior: func() { + mockRepo.EXPECT().UpdateTrip(gomock.Any(), gomock.Any()).Return(nil) + }, + expectedErr: nil, + }, + { + name: "Repository Error - Not Found", + inputTrip: models.Trip{ + ID: 1, + UserID: 1, + Name: "Updated Trip", + Description: "Updated Description", + CityID: 2, + StartDate: "2024-12-01", + EndDate: "2024-12-10", + }, + mockBehavior: func() { + mockRepo.EXPECT().UpdateTrip(gomock.Any(), gomock.Any()).Return(models.ErrNotFound) + }, + expectedErr: models.ErrNotFound, + }, + { + name: "Repository Error - Internal", + inputTrip: models.Trip{ + ID: 1, + UserID: 1, + Name: "Updated Trip", + Description: "Updated Description", + CityID: 2, + StartDate: "2024-12-01", + EndDate: "2024-12-10", + }, + mockBehavior: func() { + mockRepo.EXPECT().UpdateTrip(gomock.Any(), gomock.Any()).Return(errors.New("internal error")) + }, + expectedErr: models.ErrInternal, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.mockBehavior() + + err := usecase.UpdateTrip(context.Background(), tt.inputTrip) + + if tt.expectedErr != nil { + assert.Error(t, err) + assert.True(t, errors.Is(err, tt.expectedErr)) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestTripsUsecaseImpl_DeleteTrip(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockRepo := mock.NewMockTripsRepo(ctrl) + usecase := NewTripsUsecase(mockRepo) + + tests := []struct { + name string + inputID uint + mockBehavior func() + expectedErr error + }{ + { + name: "Success", + inputID: 1, + mockBehavior: func() { + mockRepo.EXPECT().DeleteTrip(gomock.Any(), uint(1)).Return(nil) + }, + expectedErr: nil, + }, + { + name: "Repository Error - Not Found", + inputID: 1, + mockBehavior: func() { + mockRepo.EXPECT().DeleteTrip(gomock.Any(), uint(1)).Return(models.ErrNotFound) + }, + expectedErr: models.ErrNotFound, + }, + { + name: "Repository Error - Internal", + inputID: 1, + mockBehavior: func() { + mockRepo.EXPECT().DeleteTrip(gomock.Any(), uint(1)).Return(errors.New("internal error")) + }, + expectedErr: models.ErrInternal, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.mockBehavior() + + err := usecase.DeleteTrip(context.Background(), tt.inputID) + + if tt.expectedErr != nil { + assert.Error(t, err) + assert.True(t, errors.Is(err, tt.expectedErr)) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestTripsUsecaseImpl_GetTripsByUserID(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockRepo := mock.NewMockTripsRepo(ctrl) + usecase := NewTripsUsecase(mockRepo) + + tests := []struct { + name string + userID uint + limit int + offset int + mockBehavior func() + expectedTrips []models.Trip + expectedErr error + }{ + { + name: "Success", + userID: 1, + limit: 10, + offset: 0, + mockBehavior: func() { + mockRepo.EXPECT().GetTripsByUserID(gomock.Any(), uint(1), 10, 0).Return([]models.Trip{ + {ID: 1, UserID: 1, Name: "Trip 1"}, + {ID: 2, UserID: 1, Name: "Trip 2"}, + }, nil) + }, + expectedTrips: []models.Trip{ + {ID: 1, UserID: 1, Name: "Trip 1"}, + {ID: 2, UserID: 1, Name: "Trip 2"}, + }, + expectedErr: nil, + }, + { + name: "Repository Error - Not Found", + userID: 1, + limit: 10, + offset: 0, + mockBehavior: func() { + mockRepo.EXPECT().GetTripsByUserID(gomock.Any(), uint(1), 10, 0).Return(nil, models.ErrNotFound) + }, + expectedTrips: nil, + expectedErr: models.ErrNotFound, + }, + { + name: "Repository Error - Internal", + userID: 1, + limit: 10, + offset: 0, + mockBehavior: func() { + mockRepo.EXPECT().GetTripsByUserID(gomock.Any(), uint(1), 10, 0).Return(nil, errors.New("internal error")) + }, + expectedTrips: nil, + expectedErr: models.ErrInternal, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.mockBehavior() + + trips, err := usecase.GetTripsByUserID(context.Background(), tt.userID, tt.limit, tt.offset) + + if tt.expectedErr != nil { + assert.Error(t, err) + assert.True(t, errors.Is(err, tt.expectedErr)) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.expectedTrips, trips) + } + }) + } +} + +func TestTripsUsecaseImpl_GetTrip(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockRepo := mock.NewMockTripsRepo(ctrl) + usecase := NewTripsUsecase(mockRepo) + + tests := []struct { + name string + tripID uint + mockBehavior func() + expectedTrip models.Trip + expectedErr error + }{ + { + name: "Success", + tripID: 1, + mockBehavior: func() { + mockRepo.EXPECT().GetTrip(gomock.Any(), uint(1)).Return(models.Trip{ + ID: 1, + Name: "Trip 1", + }, nil) + }, + expectedTrip: models.Trip{ + ID: 1, + Name: "Trip 1", + }, + expectedErr: nil, + }, + { + name: "Repository Error - Not Found", + tripID: 1, + mockBehavior: func() { + mockRepo.EXPECT().GetTrip(gomock.Any(), uint(1)).Return(models.Trip{}, models.ErrNotFound) + }, + expectedTrip: models.Trip{}, + expectedErr: models.ErrNotFound, + }, + { + name: "Repository Error - Internal", + tripID: 1, + mockBehavior: func() { + mockRepo.EXPECT().GetTrip(gomock.Any(), uint(1)).Return(models.Trip{}, errors.New("internal error")) + }, + expectedTrip: models.Trip{}, + expectedErr: models.ErrInternal, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.mockBehavior() + + trip, err := usecase.GetTrip(context.Background(), tt.tripID) + + if tt.expectedErr != nil { + assert.Error(t, err) + assert.True(t, errors.Is(err, tt.expectedErr)) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.expectedTrip, trip) + } + }) + } +} +func TestTripsUsecaseImpl_AddPlaceToTrip(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockRepo := mock.NewMockTripsRepo(ctrl) + usecase := NewTripsUsecase(mockRepo) + + tests := []struct { + name string + tripID uint + placeID uint + mockBehavior func() + expectedErr error + }{ + { + name: "Success", + tripID: 1, + placeID: 2, + mockBehavior: func() { + mockRepo.EXPECT().AddPlaceToTrip(gomock.Any(), uint(1), uint(2)).Return(nil) + }, + expectedErr: nil, + }, + { + name: "Repository Error - Not Found", + tripID: 1, + placeID: 2, + mockBehavior: func() { + mockRepo.EXPECT().AddPlaceToTrip(gomock.Any(), uint(1), uint(2)).Return(models.ErrNotFound) + }, + expectedErr: models.ErrNotFound, + }, + { + name: "Repository Error - Internal", + tripID: 1, + placeID: 2, + mockBehavior: func() { + mockRepo.EXPECT().AddPlaceToTrip(gomock.Any(), uint(1), uint(2)).Return(errors.New("internal error")) + }, + expectedErr: models.ErrInternal, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.mockBehavior() + + err := usecase.AddPlaceToTrip(context.Background(), tt.tripID, tt.placeID) + + if tt.expectedErr != nil { + assert.Error(t, err) + assert.True(t, errors.Is(err, tt.expectedErr)) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestTripsUsecaseImpl_AddPhotosToTrip(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockRepo := mock.NewMockTripsRepo(ctrl) + usecase := NewTripsUsecase(mockRepo) + + tests := []struct { + name string + tripID uint + photoPaths []string + mockBehavior func() + expectedErr error + }{ + { + name: "Success", + tripID: 1, + photoPaths: []string{"path/to/photo1.jpg", "path/to/photo2.jpg"}, + mockBehavior: func() { + mockRepo.EXPECT().AddPhotoToTrip(gomock.Any(), uint(1), "photo1.jpg").Return(nil) + mockRepo.EXPECT().AddPhotoToTrip(gomock.Any(), uint(1), "photo2.jpg").Return(nil) + }, + expectedErr: nil, + }, + { + name: "Repository Error - AddPhotoToTrip Fails", + tripID: 1, + photoPaths: []string{"path/to/photo1.jpg", "path/to/photo2.jpg"}, + mockBehavior: func() { + mockRepo.EXPECT().AddPhotoToTrip(gomock.Any(), uint(1), "photo1.jpg").Return(errors.New("internal error")) + }, + expectedErr: errors.New("failed to add photo to trip: internal error"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.mockBehavior() + + err := usecase.AddPhotosToTrip(context.Background(), tt.tripID, tt.photoPaths) + + if tt.expectedErr != nil { + assert.Error(t, err) + assert.Contains(t, err.Error(), tt.expectedErr.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestTripsUsecaseImpl_DeletePhotoFromTrip(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockRepo := mock.NewMockTripsRepo(ctrl) + usecase := NewTripsUsecase(mockRepo) + + tests := []struct { + name string + tripID uint + photoPath string + mockBehavior func() + expectedErr error + }{ + { + name: "Success", + tripID: 1, + photoPath: "photo.jpg", + mockBehavior: func() { + mockRepo.EXPECT().DeletePhotoFromTrip(gomock.Any(), uint(1), "photo.jpg").Return(nil) + }, + expectedErr: nil, + }, + { + name: "Repository Error - DeletePhotoFromTrip Fails", + tripID: 1, + photoPath: "photo.jpg", + mockBehavior: func() { + mockRepo.EXPECT().DeletePhotoFromTrip(gomock.Any(), uint(1), "photo.jpg").Return(errors.New("internal error")) + }, + expectedErr: errors.New("failed to delete photo from database: internal error"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.mockBehavior() + + err := usecase.DeletePhotoFromTrip(context.Background(), tt.tripID, tt.photoPath) + + if tt.expectedErr != nil { + assert.Error(t, err) + assert.Contains(t, err.Error(), tt.expectedErr.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/internal/pkg/user/mocks/mock_jwt.go b/internal/pkg/user/mocks/mock_jwt.go index 21698e4..c98484c 100644 --- a/internal/pkg/user/mocks/mock_jwt.go +++ b/internal/pkg/user/mocks/mock_jwt.go @@ -1,4 +1,4 @@ -package user +package mock_user import ( reflect "reflect" From 16522148964de087a0b0aff6b0d7855f200cbe34 Mon Sep 17 00:00:00 2001 From: mevain Date: Wed, 27 Nov 2024 05:56:40 +0300 Subject: [PATCH 2/2] fixed makefile --- Makefile | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index c43e391..449a01b 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,22 @@ PKG=./... MOCKGEN=mockgen COVERAGE_FILE=coverage.out -MOCK_SRC_PLACES=internal/pkg/places/interfaces.go -MOCK_DST_PLACES=internal/pkg/places/mocks/mock.go +MOCK_SRC_PLACES=internal/pkg/attractions/interfaces.go +MOCK_DST_PLACES=internal/pkg/attractions/mocks/mock.go MOCK_SRC_USER=internal/pkg/user/interfaces.go MOCK_DST_USER=internal/pkg/user/mocks/mock.go +MOCK_SRC_TRIPS=internal/pkg/trips/interfaces.go +MOCK_DST_TRIPS=internal/pkg/trips/mocks/mock_trips.go PACKAGE_NAME_USER=user -PACKAGE_NAME_PLACES=places - +PACKAGE_NAME_PLACES=attractions +PACKAGE_NAME_trips=trips all: test mocks: $(MOCKGEN) -source=$(MOCK_SRC_PLACES) -destination=$(MOCK_DST_PLACES) -package=$(PACKAGE_NAME_PLACES) $(MOCKGEN) -source=$(MOCK_SRC_USER) -destination=$(MOCK_DST_USER) -package=$(PACKAGE_NAME_USER) + $(MOCKGEN) -source=$(MOCK_SRC_TRIPS) -destination=$(MOCK_DST_TRIPS) -package=$(PACKAGE_NAME_TRIPS) test: mocks go test $(PKG) -coverprofile=$(COVERAGE_FILE)