diff --git a/src/app/dto/adopt.dto.go b/src/app/dto/adopt.dto.go index 82a453d..56ecaef 100644 --- a/src/app/dto/adopt.dto.go +++ b/src/app/dto/adopt.dto.go @@ -1,10 +1,15 @@ package dto -import ( - "github.com/google/uuid" -) - type AdoptDto struct { - UserID uuid.UUID `json:"user_id" validate:"required"` - PetID uuid.UUID `json:"pet_id" validate:"required"` + UserID string `json:"user_id" validate:"required"` + PetID string `json:"pet_id" validate:"required"` +} + +type AdoptByRequest struct { + UserID string `json:"user_id" validate:"required"` + PetID string `json:"pet_id" validate:"required"` +} + +type AdoptByResponse struct { + Success bool `json:"success"` } diff --git a/src/app/handler/pet/pet.handler.go b/src/app/handler/pet/pet.handler.go index f167a1c..7dae48c 100644 --- a/src/app/handler/pet/pet.handler.go +++ b/src/app/handler/pet/pet.handler.go @@ -298,3 +298,66 @@ func (h *Handler) Delete(c router.IContext) { }) return } + +// Adopt is a function that handles the adoption of a pet in the database +// @Summary Adopt a pet +// @Description Return true if the pet is successfully adopted +// @Param id path string true "Pet ID" +// @Param user_id body string true "User ID" +// @Param pet_id body string true "Pet ID" +// @Tags pet +// @Accept json +// @Produce json +// @Success 201 {object} bool +// @Failure 400 {object} dto.ResponseBadRequestErr "Invalid request body" +// @Failure 500 {object} dto.ResponseInternalErr "Internal service error" +// @Failure 503 {object} dto.ResponseServiceDownErr "Service is down" +// @Router /v1/pets/{id}/adopt [put] +func (h *Handler) Adopt(c router.IContext) { + petId, err := c.Param("id") + if err != nil { + c.JSON(http.StatusBadRequest, &dto.ResponseErr{ + StatusCode: http.StatusBadRequest, + Message: "Invalid ID", + Data: nil, + }) + return + } + + request := &dto.AdoptByRequest{} + err = c.Bind(request) + if err != nil { + c.JSON(http.StatusBadRequest, dto.ResponseErr{ + StatusCode: http.StatusBadRequest, + Message: constant.BindingRequestErrorMessage + err.Error(), + Data: nil, + }) + return + } + + if err := h.validate.Validate(request); err != nil { + var errorMessage []string + for _, reqErr := range err { + errorMessage = append(errorMessage, reqErr.Message) + } + c.JSON(http.StatusBadRequest, dto.ResponseErr{ + StatusCode: http.StatusBadRequest, + Message: constant.InvalidRequestBodyMessage + strings.Join(errorMessage, ", "), + Data: nil, + }) + return + } + + res, errRes := h.service.Adopt(petId, request) + if errRes != nil { + c.JSON(errRes.StatusCode, errRes) + return + } + + c.JSON(http.StatusOK, dto.ResponseSuccess{ + StatusCode: http.StatusOK, + Message: petconst.AdoptPetSuccessMessage, + Data: res, + }) + return +} diff --git a/src/app/handler/pet/pet.handler_test.go b/src/app/handler/pet/pet.handler_test.go index 26e6f5b..659e221 100644 --- a/src/app/handler/pet/pet.handler_test.go +++ b/src/app/handler/pet/pet.handler_test.go @@ -31,6 +31,7 @@ type PetHandlerTest struct { CreatePetRequest *dto.CreatePetRequest ChangeViewPetRequest *dto.ChangeViewPetRequest UpdatePetRequest *dto.UpdatePetRequest + AdoptByRequest *dto.AdoptByRequest BindErr *dto.ResponseErr NotFoundErr *dto.ResponseErr ServiceDownErr *dto.ResponseErr @@ -102,6 +103,8 @@ func (t *PetHandlerTest) SetupTest() { t.ChangeViewPetRequest = &dto.ChangeViewPetRequest{} + t.AdoptByRequest = &dto.AdoptByRequest{} + t.ServiceDownErr = &dto.ResponseErr{ StatusCode: http.StatusServiceUnavailable, Message: "Service is down", @@ -449,3 +452,74 @@ func (t *PetHandlerTest) TestChangeViewGrpcErr() { handler := NewHandler(petSvc, imageSvc, validator) handler.ChangeView(context) } + +func (t *PetHandlerTest) TestAdoptSuccess() { + adoptByResponse := &dto.AdoptByResponse{ + Success: true, + } + expectedResponse := dto.ResponseSuccess{ + StatusCode: http.StatusOK, + Message: petConst.AdoptPetSuccessMessage, + Data: adoptByResponse, + } + + controller := gomock.NewController(t.T()) + + petSvc := petMock.NewMockService(controller) + imageSvc := imageMock.NewMockService(controller) + validator := validatorMock.NewMockIDtoValidator(controller) + context := routerMock.NewMockIContext(controller) + + context.EXPECT().Param("id").Return(t.Pet.Id, nil) + context.EXPECT().Bind(t.AdoptByRequest).Return(nil) + validator.EXPECT().Validate(t.AdoptByRequest).Return(nil) + petSvc.EXPECT().Adopt(t.Pet.Id, t.AdoptByRequest).Return(adoptByResponse, nil) + context.EXPECT().JSON(http.StatusOK, expectedResponse) + + handler := NewHandler(petSvc, imageSvc, validator) + handler.Adopt(context) +} + +func (t *PetHandlerTest) TestAdoptNotFound() { + adoptByResponse := &dto.AdoptByResponse{ + Success: false, + } + + controller := gomock.NewController(t.T()) + + petSvc := petMock.NewMockService(controller) + imageSvc := imageMock.NewMockService(controller) + validator := validatorMock.NewMockIDtoValidator(controller) + context := routerMock.NewMockIContext(controller) + + context.EXPECT().Param("id").Return(t.Pet.Id, nil) + context.EXPECT().Bind(t.AdoptByRequest).Return(nil) + validator.EXPECT().Validate(t.AdoptByRequest).Return(nil) + petSvc.EXPECT().Adopt(t.Pet.Id, t.AdoptByRequest).Return(adoptByResponse, t.NotFoundErr) + context.EXPECT().JSON(http.StatusNotFound, t.NotFoundErr) + + handler := NewHandler(petSvc, imageSvc, validator) + handler.Adopt(context) +} + +func (t *PetHandlerTest) TestAdoptGrpcErr() { + adoptByResponse := &dto.AdoptByResponse{ + Success: false, + } + + controller := gomock.NewController(t.T()) + + petSvc := petMock.NewMockService(controller) + imageSvc := imageMock.NewMockService(controller) + validator := validatorMock.NewMockIDtoValidator(controller) + context := routerMock.NewMockIContext(controller) + + context.EXPECT().Param("id").Return(t.Pet.Id, nil) + context.EXPECT().Bind(t.AdoptByRequest).Return(nil) + validator.EXPECT().Validate(t.AdoptByRequest).Return(nil) + petSvc.EXPECT().Adopt(t.Pet.Id, t.AdoptByRequest).Return(adoptByResponse, t.ServiceDownErr) + context.EXPECT().JSON(http.StatusServiceUnavailable, t.ServiceDownErr) + + handler := NewHandler(petSvc, imageSvc, validator) + handler.Adopt(context) +} diff --git a/src/app/service/pet/pet.service.go b/src/app/service/pet/pet.service.go index 7798ad0..4362e19 100644 --- a/src/app/service/pet/pet.service.go +++ b/src/app/service/pet/pet.service.go @@ -18,11 +18,6 @@ type Service struct { petClient petproto.PetServiceClient } -// Adopt implements pet.Service. -func (*Service) Adopt(*dto.AdoptDto) (bool, *dto.ResponseErr) { - panic("unimplemented") -} - func NewService(petClient petproto.PetServiceClient) *Service { return &Service{ petClient: petClient, @@ -276,3 +271,47 @@ func (s *Service) ChangeView(id string, in *dto.ChangeViewPetRequest) (result *d Success: res.Success, }, nil } + +func (s *Service) Adopt(petId string, in *dto.AdoptByRequest) (result *dto.AdoptByResponse, err *dto.ResponseErr) { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + res, errRes := s.petClient.AdoptPet(ctx, &petproto.AdoptPetRequest{ + UserId: in.UserID, + PetId: in.PetID, + }) + if errRes != nil { + st, _ := status.FromError(errRes) + log.Error(). + Err(errRes). + Str("service", "pet"). + Str("module", "adopt"). + Msg(st.Message()) + switch st.Code() { + case codes.NotFound: + return nil, + &dto.ResponseErr{ + StatusCode: http.StatusNotFound, + Message: constant.PetNotFoundMessage, + Data: nil, + } + case codes.Unavailable: + return nil, + &dto.ResponseErr{ + StatusCode: http.StatusServiceUnavailable, + Message: constant.UnavailableServiceMessage, + Data: nil, + } + default: + return nil, + &dto.ResponseErr{ + StatusCode: http.StatusServiceUnavailable, + Message: constant.InternalErrorMessage, + Data: nil, + } + } + } + return &dto.AdoptByResponse{ + Success: res.Success, + }, nil +} diff --git a/src/app/service/pet/pet.service_test.go b/src/app/service/pet/pet.service_test.go index 87fe217..5705c12 100644 --- a/src/app/service/pet/pet.service_test.go +++ b/src/app/service/pet/pet.service_test.go @@ -37,6 +37,7 @@ type PetServiceTest struct { InvalidArgumentErr *dto.ResponseErr InternalErr *dto.ResponseErr ChangeViewedPetDto *dto.ChangeViewPetRequest + AdoptDto *dto.AdoptByRequest Images []*imgproto.Image ImagesList [][]*imgproto.Image @@ -160,6 +161,11 @@ func (t *PetServiceTest) SetupTest() { UserId: t.Pet.AdoptBy, } + t.AdoptDto = &dto.AdoptByRequest{ + UserID: t.Pet.AdoptBy, + PetID: t.Pet.Id, + } + t.UnavailableServiceErr = &dto.ResponseErr{ StatusCode: http.StatusServiceUnavailable, Message: constant.UnavailableServiceMessage, @@ -514,3 +520,53 @@ func (t *PetServiceTest) TestChangeViewUnavailableServiceError() { assert.Equal(t.T(), &dto.ChangeViewPetResponse{Success: false}, actual) assert.Equal(t.T(), expected, err) } + +func (t *PetServiceTest) TestAdoptSuccess() { + protoReq := t.AdoptReq + protoResp := &petproto.AdoptPetResponse{ + Success: true, + } + + client := &petmock.PetClientMock{} + client.On("AdoptPet", protoReq).Return(protoResp, nil) + + svc := NewService(client) + actual, err := svc.Adopt(t.Pet.Id, t.AdoptDto) + + assert.Nil(t.T(), err) + assert.Equal(t.T(), actual, &dto.AdoptByResponse{Success: true}) +} + +func (t *PetServiceTest) TestAdoptNotFoundError() { + protoReq := t.AdoptReq + + clientErr := status.Error(codes.NotFound, constant.PetNotFoundMessage) + + expected := t.NotFoundErr + + client := &petmock.PetClientMock{} + client.On("AdoptPet", protoReq).Return(nil, clientErr) + + svc := NewService(client) + actual, err := svc.Adopt(t.Pet.Id, t.AdoptDto) + + assert.Nil(t.T(), actual) + assert.Equal(t.T(), expected, err) +} + +func (t *PetServiceTest) TestAdoptUnavailableServiceError() { + protoReq := t.AdoptReq + + clientErr := status.Error(codes.Unavailable, constant.UnavailableServiceMessage) + + expected := t.UnavailableServiceErr + + client := &petmock.PetClientMock{} + client.On("AdoptPet", protoReq).Return(nil, clientErr) + + svc := NewService(client) + actual, err := svc.Adopt(t.Pet.Id, t.AdoptDto) + + assert.Nil(t.T(), actual) + assert.Equal(t.T(), expected, err) +} diff --git a/src/constant/pet/pet.constant.go b/src/constant/pet/pet.constant.go index fe606fe..4ff078c 100644 --- a/src/constant/pet/pet.constant.go +++ b/src/constant/pet/pet.constant.go @@ -64,3 +64,4 @@ const CreatePetSuccessMessage = "create pet success" const UpdatePetSuccessMessage = "update pet success" const ChangeViewPetSuccessMessage = "change view pet success" const DeletePetSuccessMessage = "delete pet success" +const AdoptPetSuccessMessage = "adopt pet success" diff --git a/src/main.go b/src/main.go index cfc31c8..2d354c7 100644 --- a/src/main.go +++ b/src/main.go @@ -133,6 +133,7 @@ func main() { r.GetPet("/:id", petHandler.FindOne) r.PostPet("/create", petHandler.Create) r.PutPet("/:id", petHandler.Update) + r.PutPet("/:id/adopt", petHandler.Adopt) r.PutPet("/:id/visible", petHandler.ChangeView) r.DeletePet("/:id", petHandler.Delete) diff --git a/src/mocks/service/pet/pet.mock.go b/src/mocks/service/pet/pet.mock.go index 1673704..84ba13d 100644 --- a/src/mocks/service/pet/pet.mock.go +++ b/src/mocks/service/pet/pet.mock.go @@ -40,18 +40,18 @@ func (m *MockService) EXPECT() *MockServiceMockRecorder { } // Adopt mocks base method. -func (m *MockService) Adopt(arg0 *dto.AdoptDto) (bool, *dto.ResponseErr) { +func (m *MockService) Adopt(arg0 string, arg1 *dto.AdoptByRequest) (*dto.AdoptByResponse, *dto.ResponseErr) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Adopt", arg0) - ret0, _ := ret[0].(bool) + ret := m.ctrl.Call(m, "Adopt", arg0, arg1) + ret0, _ := ret[0].(*dto.AdoptByResponse) ret1, _ := ret[1].(*dto.ResponseErr) return ret0, ret1 } // Adopt indicates an expected call of Adopt. -func (mr *MockServiceMockRecorder) Adopt(arg0 any) *gomock.Call { +func (mr *MockServiceMockRecorder) Adopt(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Adopt", reflect.TypeOf((*MockService)(nil).Adopt), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Adopt", reflect.TypeOf((*MockService)(nil).Adopt), arg0, arg1) } // ChangeView mocks base method. diff --git a/src/pkg/service/pet/pet.service.go b/src/pkg/service/pet/pet.service.go index e538fb1..9862eee 100644 --- a/src/pkg/service/pet/pet.service.go +++ b/src/pkg/service/pet/pet.service.go @@ -11,5 +11,5 @@ type Service interface { Update(string, *dto.UpdatePetRequest) (*dto.PetResponse, *dto.ResponseErr) Delete(string) (*dto.DeleteResponse, *dto.ResponseErr) ChangeView(string, *dto.ChangeViewPetRequest) (*dto.ChangeViewPetResponse, *dto.ResponseErr) - Adopt(*dto.AdoptDto) (bool, *dto.ResponseErr) + Adopt(string, *dto.AdoptByRequest) (*dto.AdoptByResponse, *dto.ResponseErr) }