From c0fc603445862de0a0a64f8ccc163014515c382a Mon Sep 17 00:00:00 2001 From: Viktor64 Date: Sun, 20 Apr 2025 17:26:58 +0400 Subject: [PATCH 01/14] =?UTF-8?q?=D0=A1=D0=BE=D0=B7=D0=B4=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=B2=D0=B5=D1=82=D0=BA=D1=83:=20add-item-requests-and-gateway?= =?UTF-8?q?.=201)=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D1=8F=D0=B5=D0=BC?= =?UTF-8?q?=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81=20=D0=B2=D0=B5=D1=89?= =?UTF-8?q?=D0=B8.=202)=20=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BB=20=D1=87=D0=B5=D1=82=D1=8B=D1=80=D0=B5=20=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D1=8B=D1=85=20=D1=8D=D0=BD=D0=B4=D0=BF=D0=BE=D0=B8?= =?UTF-8?q?=D0=BD=D1=82=D0=B0:=20POST=20/requests,=20GET=20/requests,=20GE?= =?UTF-8?q?T=20/requests/all,=20GET=20/requests/{requestId},=20GET=20/requ?= =?UTF-8?q?ests.=203)=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BE=D0=BF=D1=86=D0=B8=D1=8E=20=D0=BE=D1=82=D0=B2=D0=B5=D1=82?= =?UTF-8?q?=D0=B0=20=D0=BD=D0=B0=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81.?= =?UTF-8?q?=204)=20=D0=A0=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=20=D0=BD=D0=B0=202=20?= =?UTF-8?q?=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8F=20shareIt-server=20=D0=B8?= =?UTF-8?q?=20shareIt-gateway.=204)=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=82=D0=B5=D1=81=D1=82=D1=8B.=205)=20=D0=A1=D0=BE?= =?UTF-8?q?=D0=B7=D0=B4=D0=B0=D0=BB=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5=20?= =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82=D1=8B.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gateway/pom.xml | 83 ++++ .../practicum/shareit/ShareItGatewayApp.java | 11 + .../booking/BookingStateValidator.java | 15 + .../shareit/booking/BookingStatus.java | 8 + .../shareit/booking/client/BookingClient.java | 60 +++ .../booking/controller/BookingController.java | 80 ++++ .../shareit/booking/dto/BookingDto.java | 30 ++ .../shareit/booking/dto/BookingState.java | 21 + .../practicum/shareit/client/BaseClient.java | 121 +++++ .../shareit/client/ResponseModifier.java | 48 ++ .../shareit/client/RestTemplateConfig.java | 24 + .../shareit/config/JacksonConfig.java | 23 + .../practicum/shareit/config/WebConfig.java | 18 + .../shareit/exception/ErrorHandler.java | 39 ++ .../shareit/filter/JsonResponseFilter.java | 50 ++ .../filter/ResponseModifierFilter.java | 64 +++ .../shareit/item/client/ItemClient.java | 66 +++ .../item/controller/ItemController.java | 105 +++++ .../item/controller/ItemControllerAdvice.java | 63 +++ .../item/controller/ItemTestController.java | 45 ++ .../shareit/item/dto/BookingShortDto.java | 18 + .../shareit/item/dto/CommentDto.java | 24 + .../practicum/shareit/item/dto/ItemDto.java | 36 ++ .../request/client/ItemRequestClient.java | 48 ++ .../controller/ItemRequestController.java | 64 +++ .../shareit/request/dto/ItemRequestDto.java | 28 ++ .../shareit/user/client/UserClient.java | 46 ++ .../user/controller/UserController.java | 59 +++ .../practicum/shareit/user/dto/UserDto.java | 23 + .../shareit/util/JsonResponseModifier.java | 46 ++ .../shareit/util/ResponseConverter.java | 47 ++ .../src/main/resources/application.properties | 15 + pom.xml | 407 ++++++---------- server/pom.xml | 83 ++++ .../practicum/shareit/ShareItServerApp.java | 12 +- .../shareit/booking/BookingMapper.java | 3 + .../shareit/booking/BookingState.java | 0 .../shareit/booking/BookingStatus.java | 0 .../booking/Impl/BookingServiceImpl.java | 104 ++--- .../booking/controller/BookingController.java | 61 +++ .../shareit/booking/dto/BookingDto.java | 0 .../shareit/booking/dto/BookingInputDto.java | 0 .../shareit/booking/dto/BookingShortDto.java | 0 .../booking/exception/ShareItException.java | 0 .../shareit/booking/model/Booking.java | 0 .../booking/repository/BookingRepository.java | 46 +- .../booking/service/BookerStateProcessor.java | 7 +- .../booking/service/BookingService.java | 25 + .../handler/AbstractBookingStateHandler.java | 6 + .../handler/booker/AllBookingsHandler.java | 9 + .../handler/booker/BookingStateHandler.java | 3 + .../booker/CurrentBookingsHandler.java | 9 + .../handler/booker/FutureBookingsHandler.java | 9 + .../handler/booker/PastBookingsHandler.java | 9 + .../booker/RejectedBookingsHandler.java | 9 + .../booker/WaitingBookingsHandler.java | 9 + .../owner/OwnerAllBookingsHandler.java | 9 + .../owner/OwnerCurrentBookingsHandler.java | 9 + .../owner/OwnerFutureBookingsHandler.java | 9 + .../owner/OwnerPastBookingsHandler.java | 9 + .../owner/OwnerRejectedBookingsHandler.java | 9 + .../handler/owner/OwnerStateProcessor.java | 7 +- .../owner/OwnerWaitingBookingsHandler.java | 9 + .../practicum/shareit/item/CommentMapper.java | 12 +- .../shareit/item/Impl/ItemServiceImpl.java | 37 +- .../ru/practicum/shareit/item/ItemMapper.java | 11 +- .../item/controller/ItemController.java | 21 +- .../shareit/item/dto/CommentDto.java | 0 .../practicum/shareit/item/dto/ItemDto.java | 1 - .../practicum/shareit/item/model/Comment.java | 0 .../ru/practicum/shareit/item/model/Item.java | 0 .../item/repository/CommentRepository.java | 0 .../item/repository/ItemRepository.java | 4 + .../shareit/item/service/ItemService.java | 0 .../shareit/item/util/Constants.java | 0 .../request/Impl/ItemRequestServiceImpl.java | 121 +++++ .../shareit/request/ItemRequestMapper.java | 19 + .../controller/ItemRequestController.java | 51 ++ .../shareit/request/dto/ItemRequestDto.java | 0 .../shareit/request/model/ItemRequest.java | 0 .../repository/ItemRequestRepository.java | 15 + .../request/service/ItemRequestService.java | 15 + .../shareit/user/Impl/UserServiceImpl.java | 0 .../ru/practicum/shareit/user/UserMapper.java | 3 +- .../user/controller/UserController.java | 14 +- .../practicum/shareit/user/dto/UserDto.java | 0 .../ru/practicum/shareit/user/model/User.java | 0 .../user/repository/UserRepository.java | 0 .../shareit/user/service/UserService.java | 0 .../main/resources/application.properties | 21 +- {src => server/src}/main/resources/data.sql | 0 {src => server/src}/main/resources/schema.sql | 0 .../ru/practicum/shareit/ShareItTests.java | 2 - .../java/ru/practicum/shareit/TestConfig.java | 0 .../controller/BookingControllerTest.java | 440 ++++++++++++++++++ .../service/BookingServiceImplTest.java | 275 +++++------ .../BookingControllerMockMvcTest.java | 145 ++++++ .../controller/ItemControllerMockMvcTest.java | 173 +++++++ .../ItemRequestControllerMockMvcTest.java | 121 +++++ .../controller/UserControllerMockMvcTest.java | 133 ++++++ .../shareit/dto/BookingDtoJsonTest.java | 77 +++ .../shareit/dto/CommentDtoJsonTest.java | 53 +++ .../shareit/dto/ItemDtoJsonTest.java | 91 ++++ .../BookingServiceIntegrationTest.java | 119 +++++ .../ItemServiceIntegrationTest.java | 97 ++++ .../UserServiceIntegrationTest.java | 125 +++++ .../controller/CommentControllerTest.java | 76 +-- .../item/controller/ItemBookingInfoTest.java | 34 +- .../item/controller/ItemControllerTest.java | 4 - .../user/controller/UserControllerTest.java | 7 +- .../resources/application-test.properties | 0 .../booking/controller/BookingController.java | 58 --- .../booking/service/BookingService.java | 17 - .../controller/ItemRequestController.java | 10 - .../controller/BookingControllerTest.java | 275 ----------- 115 files changed, 4024 insertions(+), 953 deletions(-) create mode 100644 gateway/pom.xml create mode 100644 gateway/src/main/java/ru/practicum/shareit/ShareItGatewayApp.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/booking/BookingStateValidator.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/booking/BookingStatus.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/booking/client/BookingClient.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingState.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/client/BaseClient.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/client/ResponseModifier.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/client/RestTemplateConfig.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/config/JacksonConfig.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/config/WebConfig.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/filter/JsonResponseFilter.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/filter/ResponseModifierFilter.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/item/client/ItemClient.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/item/controller/ItemController.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/item/controller/ItemControllerAdvice.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/item/controller/ItemTestController.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/item/dto/BookingShortDto.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/request/client/ItemRequestClient.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/request/controller/ItemRequestController.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/user/client/UserClient.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/user/controller/UserController.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/user/dto/UserDto.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/util/JsonResponseModifier.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/util/ResponseConverter.java create mode 100644 gateway/src/main/resources/application.properties create mode 100644 server/pom.xml rename src/main/java/ru/practicum/shareit/ShareItApp.java => server/src/main/java/ru/practicum/shareit/ShareItServerApp.java (54%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/BookingMapper.java (88%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/BookingState.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/BookingStatus.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/Impl/BookingServiceImpl.java (52%) create mode 100644 server/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java rename {src => server/src}/main/java/ru/practicum/shareit/booking/dto/BookingDto.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/dto/BookingInputDto.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/dto/BookingShortDto.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/exception/ShareItException.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/model/Booking.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java (59%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/service/BookerStateProcessor.java (77%) create mode 100644 server/src/main/java/ru/practicum/shareit/booking/service/BookingService.java rename {src => server/src}/main/java/ru/practicum/shareit/booking/service/handler/AbstractBookingStateHandler.java (79%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/service/handler/booker/AllBookingsHandler.java (75%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/service/handler/booker/BookingStateHandler.java (72%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/service/handler/booker/CurrentBookingsHandler.java (74%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/service/handler/booker/FutureBookingsHandler.java (74%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/service/handler/booker/PastBookingsHandler.java (74%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/service/handler/booker/RejectedBookingsHandler.java (75%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/service/handler/booker/WaitingBookingsHandler.java (75%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerAllBookingsHandler.java (75%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerCurrentBookingsHandler.java (74%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerFutureBookingsHandler.java (74%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerPastBookingsHandler.java (74%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerRejectedBookingsHandler.java (75%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerStateProcessor.java (77%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerWaitingBookingsHandler.java (75%) rename {src => server/src}/main/java/ru/practicum/shareit/item/CommentMapper.java (65%) rename {src => server/src}/main/java/ru/practicum/shareit/item/Impl/ItemServiceImpl.java (88%) rename {src => server/src}/main/java/ru/practicum/shareit/item/ItemMapper.java (62%) rename {src => server/src}/main/java/ru/practicum/shareit/item/controller/ItemController.java (73%) rename {src => server/src}/main/java/ru/practicum/shareit/item/dto/CommentDto.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/item/dto/ItemDto.java (99%) rename {src => server/src}/main/java/ru/practicum/shareit/item/model/Comment.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/item/model/Item.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/item/repository/CommentRepository.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/item/repository/ItemRepository.java (85%) rename {src => server/src}/main/java/ru/practicum/shareit/item/service/ItemService.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/item/util/Constants.java (100%) create mode 100644 server/src/main/java/ru/practicum/shareit/request/Impl/ItemRequestServiceImpl.java create mode 100644 server/src/main/java/ru/practicum/shareit/request/ItemRequestMapper.java create mode 100644 server/src/main/java/ru/practicum/shareit/request/controller/ItemRequestController.java rename {src => server/src}/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/request/model/ItemRequest.java (100%) create mode 100644 server/src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepository.java create mode 100644 server/src/main/java/ru/practicum/shareit/request/service/ItemRequestService.java rename {src => server/src}/main/java/ru/practicum/shareit/user/Impl/UserServiceImpl.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/user/UserMapper.java (78%) rename {src => server/src}/main/java/ru/practicum/shareit/user/controller/UserController.java (81%) rename {src => server/src}/main/java/ru/practicum/shareit/user/dto/UserDto.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/user/model/User.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/user/repository/UserRepository.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/user/service/UserService.java (100%) rename {src => server/src}/main/resources/application.properties (61%) rename {src => server/src}/main/resources/data.sql (100%) rename {src => server/src}/main/resources/schema.sql (100%) rename {src => server/src}/test/java/ru/practicum/shareit/ShareItTests.java (68%) rename {src => server/src}/test/java/ru/practicum/shareit/TestConfig.java (100%) create mode 100644 server/src/test/java/ru/practicum/shareit/booking/controller/BookingControllerTest.java rename {src => server/src}/test/java/ru/practicum/shareit/booking/service/BookingServiceImplTest.java (64%) create mode 100644 server/src/test/java/ru/practicum/shareit/controller/BookingControllerMockMvcTest.java create mode 100644 server/src/test/java/ru/practicum/shareit/controller/ItemControllerMockMvcTest.java create mode 100644 server/src/test/java/ru/practicum/shareit/controller/ItemRequestControllerMockMvcTest.java create mode 100644 server/src/test/java/ru/practicum/shareit/controller/UserControllerMockMvcTest.java create mode 100644 server/src/test/java/ru/practicum/shareit/dto/BookingDtoJsonTest.java create mode 100644 server/src/test/java/ru/practicum/shareit/dto/CommentDtoJsonTest.java create mode 100644 server/src/test/java/ru/practicum/shareit/dto/ItemDtoJsonTest.java create mode 100644 server/src/test/java/ru/practicum/shareit/integration/BookingServiceIntegrationTest.java create mode 100644 server/src/test/java/ru/practicum/shareit/integration/ItemServiceIntegrationTest.java create mode 100644 server/src/test/java/ru/practicum/shareit/integration/UserServiceIntegrationTest.java rename {src => server/src}/test/java/ru/practicum/shareit/item/controller/CommentControllerTest.java (59%) rename {src => server/src}/test/java/ru/practicum/shareit/item/controller/ItemBookingInfoTest.java (88%) rename {src => server/src}/test/java/ru/practicum/shareit/item/controller/ItemControllerTest.java (98%) rename {src => server/src}/test/java/ru/practicum/shareit/user/controller/UserControllerTest.java (96%) rename {src/main => server/src/test}/resources/application-test.properties (100%) delete mode 100644 src/main/java/ru/practicum/shareit/booking/controller/BookingController.java delete mode 100644 src/main/java/ru/practicum/shareit/booking/service/BookingService.java delete mode 100644 src/main/java/ru/practicum/shareit/request/controller/ItemRequestController.java delete mode 100644 src/test/java/ru/practicum/shareit/booking/controller/BookingControllerTest.java diff --git a/gateway/pom.xml b/gateway/pom.xml new file mode 100644 index 0000000..d0cb9a8 --- /dev/null +++ b/gateway/pom.xml @@ -0,0 +1,83 @@ + + + 4.0.0 + + ru.practicum + shareit + 0.0.1-SNAPSHOT + + + shareit-gateway + 0.0.1-SNAPSHOT + ShareIt Gateway + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-actuator + + + org.apache.httpcomponents + httpclient + 4.5.14 + + + org.springframework.boot + spring-boot-starter-test + test + + + org.apache.httpcomponents.client5 + httpclient5 + 5.2.1 + + + org.mapstruct + mapstruct + ${mapstruct.version} + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + org.projectlombok + lombok + ${lombok.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + + + + + + + \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/ShareItGatewayApp.java b/gateway/src/main/java/ru/practicum/shareit/ShareItGatewayApp.java new file mode 100644 index 0000000..084aa09 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/ShareItGatewayApp.java @@ -0,0 +1,11 @@ +package ru.practicum.shareit; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ShareItGatewayApp { + public static void main(String[] args) { + SpringApplication.run(ShareItGatewayApp.class, args); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/BookingStateValidator.java b/gateway/src/main/java/ru/practicum/shareit/booking/BookingStateValidator.java new file mode 100644 index 0000000..31a2384 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/BookingStateValidator.java @@ -0,0 +1,15 @@ +package ru.practicum.shareit.booking; + +import java.util.Arrays; +import java.util.List; + +public class BookingStateValidator { + private static final List VALID_STATES = Arrays.asList( + "ALL", "CURRENT", "PAST", "FUTURE", "WAITING", "REJECTED"); + + public static void validateState(String state) { + if (!VALID_STATES.contains(state)) { + throw new IllegalArgumentException("Unknown state: " + state); + } + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/BookingStatus.java b/gateway/src/main/java/ru/practicum/shareit/booking/BookingStatus.java new file mode 100644 index 0000000..a34e409 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/BookingStatus.java @@ -0,0 +1,8 @@ +package ru.practicum.shareit.booking; + +public enum BookingStatus { + WAITING, + APPROVED, + REJECTED, + CANCELED +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/client/BookingClient.java b/gateway/src/main/java/ru/practicum/shareit/booking/client/BookingClient.java new file mode 100644 index 0000000..6bd20ad --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/client/BookingClient.java @@ -0,0 +1,60 @@ +package ru.practicum.shareit.booking.client; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.util.DefaultUriBuilderFactory; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.dto.BookingState; +import ru.practicum.shareit.client.BaseClient; + +import java.util.Map; + +@Service +public class BookingClient extends BaseClient { + private static final String API_PREFIX = "/bookings"; + + @Autowired + public BookingClient(@Value("${shareit-server.url}") String serverUrl, RestTemplateBuilder builder) { + super( + builder + .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl + API_PREFIX)) + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) + .build() + ); + } + + public ResponseEntity createBooking(BookingDto bookingDto, long userId) { + return post("", userId, bookingDto); + } + + public ResponseEntity approve(long bookingId, long userId, Boolean approved) { + Map parameters = Map.of("approved", approved); + return patch("/" + bookingId + "?approved={approved}", userId, parameters, null); + } + + public ResponseEntity getById(long bookingId, long userId) { + return get("/" + bookingId, userId); + } + + public ResponseEntity getAllByBooker(long userId, BookingState state, int from, int size) { + Map parameters = Map.of( + "state", state.name(), + "from", from, + "size", size + ); + return get("?state={state}&from={from}&size={size}", userId, parameters); + } + + public ResponseEntity getAllByOwner(long userId, BookingState state, int from, int size) { + Map parameters = Map.of( + "state", state.name(), + "from", from, + "size", size + ); + return get("/owner?state={state}&from={from}&size={size}", userId, parameters); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java b/gateway/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java new file mode 100644 index 0000000..9a3c787 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java @@ -0,0 +1,80 @@ +package ru.practicum.shareit.booking.controller; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import ru.practicum.shareit.booking.client.BookingClient; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.dto.BookingState; + +@Controller +@RequestMapping(path = "/bookings") +@RequiredArgsConstructor +@Slf4j +@Validated +public class BookingController { + private final BookingClient bookingClient; + private static final String USER_ID_HEADER = "X-Sharer-User-Id"; + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) // Важно: тесты ожидают статус 201 CREATED + public ResponseEntity createBooking(@Valid @RequestBody BookingDto bookingDto, + @RequestHeader(USER_ID_HEADER) Long userId) { + log.info("Creating booking {}, userId={}", bookingDto, userId); + return bookingClient.createBooking(bookingDto, userId); + } + + @PatchMapping("/{bookingId}") + public ResponseEntity approve(@PathVariable Long bookingId, + @RequestHeader(USER_ID_HEADER) Long userId, + @RequestParam Boolean approved) { + log.info("Patch booking {}, userId={}, approved={}", bookingId, userId, approved); + return bookingClient.approve(bookingId, userId, approved); + } + + @GetMapping("/{bookingId}") + public ResponseEntity getById(@PathVariable Long bookingId, + @RequestHeader(USER_ID_HEADER) Long userId) { + log.info("Get booking {}, userId={}", bookingId, userId); + return bookingClient.getById(bookingId, userId); + } + + @GetMapping + public ResponseEntity getAllByBooker( + @RequestHeader(USER_ID_HEADER) Long userId, + @RequestParam(name = "state", defaultValue = "all") String stateParam, + @RequestParam(defaultValue = "0") @PositiveOrZero int from, + @RequestParam(defaultValue = "10") @Positive int size) { + BookingState state = BookingState.from(stateParam) + .orElseThrow(() -> new IllegalArgumentException("Unknown state: " + stateParam)); + log.info("Get bookings by booker userId={}, state={}, from={}, size={}", userId, state, from, size); + return bookingClient.getAllByBooker(userId, state, from, size); + } + + @GetMapping("/owner") + public ResponseEntity getAllByOwner( + @RequestHeader(USER_ID_HEADER) Long userId, + @RequestParam(name = "state", defaultValue = "all") String stateParam, + @RequestParam(defaultValue = "0") @PositiveOrZero int from, + @RequestParam(defaultValue = "10") @Positive int size) { + BookingState state = BookingState.from(stateParam) + .orElseThrow(() -> new IllegalArgumentException("Unknown state: " + stateParam)); + log.info("Get bookings by owner userId={}, state={}, from={}, size={}", userId, state, from, size); + return bookingClient.getAllByOwner(userId, state, from, size); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java new file mode 100644 index 0000000..90b95ad --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java @@ -0,0 +1,30 @@ +package ru.practicum.shareit.booking.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import ru.practicum.shareit.booking.BookingStatus; + +import java.time.LocalDateTime; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class BookingDto { + private Long id; + + @NotNull(message = "Дата начала бронирования не может быть пустой") + private LocalDateTime start; + + @NotNull(message = "Дата окончания бронирования не может быть пустой") + private LocalDateTime end; + + @NotNull(message = "ID вещи должен быть указан") + private Long itemId; + + private Long bookerId; + private BookingStatus status; +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingState.java b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingState.java new file mode 100644 index 0000000..26e1859 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingState.java @@ -0,0 +1,21 @@ +package ru.practicum.shareit.booking.dto; + +import java.util.Optional; + +public enum BookingState { + ALL, + CURRENT, + PAST, + FUTURE, + WAITING, + REJECTED; + + public static Optional from(String stringState) { + for (BookingState state : values()) { + if (state.name().equalsIgnoreCase(stringState)) { + return Optional.of(state); + } + } + return Optional.empty(); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/client/BaseClient.java b/gateway/src/main/java/ru/practicum/shareit/client/BaseClient.java new file mode 100644 index 0000000..f49d7bb --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/client/BaseClient.java @@ -0,0 +1,121 @@ +package ru.practicum.shareit.client; + +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.lang.Nullable; +import org.springframework.web.client.HttpStatusCodeException; +import org.springframework.web.client.RestTemplate; + +import java.util.List; +import java.util.Map; + +public class BaseClient { + protected final RestTemplate rest; + protected final ResponseModifier responseModifier; + + public BaseClient(RestTemplate rest, ResponseModifier responseModifier) { + this.rest = rest; + this.responseModifier = responseModifier; + } + + public BaseClient(RestTemplate rest) { + this.rest = rest; + this.responseModifier = null; + } + + protected ResponseEntity get(String path) { + return get(path, null, null); + } + + protected ResponseEntity get(String path, long userId) { + return get(path, userId, null); + } + + protected ResponseEntity get(String path, Long userId, @Nullable Map parameters) { + ResponseEntity response = makeAndSendRequest(HttpMethod.GET, path, userId, parameters, null); + if (responseModifier != null && path.matches("/\\d+")) { + return responseModifier.modifyItemResponse(response, path); + } + return response; + } + + protected ResponseEntity post(String path, T body) { + return post(path, null, body); + } + + protected ResponseEntity post(String path, Long userId, T body) { + return makeAndSendRequest(HttpMethod.POST, path, userId, null, body); + } + + protected ResponseEntity put(String path, long userId, T body) { + return makeAndSendRequest(HttpMethod.PUT, path, userId, null, body); + } + + protected ResponseEntity patch(String path, T body) { + return patch(path, null, null, body); + } + + protected ResponseEntity patch(String path, long userId, T body) { + return patch(path, userId, null, body); + } + + protected ResponseEntity patch(String path, Long userId, @Nullable Map parameters, T body) { + return makeAndSendRequest(HttpMethod.PATCH, path, userId, parameters, body); + } + + protected ResponseEntity delete(String path) { + return delete(path, null, null); + } + + protected ResponseEntity delete(String path, long userId) { + return delete(path, userId, null); + } + + protected ResponseEntity delete(String path, Long userId, @Nullable Map parameters) { + return makeAndSendRequest(HttpMethod.DELETE, path, userId, parameters, null); + } + + private ResponseEntity makeAndSendRequest(HttpMethod method, String path, Long userId, + @Nullable Map parameters, @Nullable T body) { + HttpEntity requestEntity = new HttpEntity<>(body, defaultHeaders(userId)); + + ResponseEntity shareitServerResponse; + try { + if (parameters != null) { + shareitServerResponse = rest.exchange(path, method, requestEntity, Object.class, parameters); + } else { + shareitServerResponse = rest.exchange(path, method, requestEntity, Object.class); + } + } catch (HttpStatusCodeException e) { + return ResponseEntity.status(e.getStatusCode()).body(e.getResponseBodyAsByteArray()); + } + return prepareGatewayResponse(shareitServerResponse); + } + + private HttpHeaders defaultHeaders(Long userId) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setAccept(List.of(MediaType.APPLICATION_JSON)); + if (userId != null) { + headers.set("X-Sharer-User-Id", String.valueOf(userId)); + } + return headers; + } + + private static ResponseEntity prepareGatewayResponse(ResponseEntity response) { + if (response.getStatusCode().is2xxSuccessful()) { + return response; + } + + ResponseEntity.BodyBuilder responseBuilder = ResponseEntity.status(response.getStatusCode()); + + if (response.hasBody()) { + return responseBuilder.body(response.getBody()); + } + + return responseBuilder.build(); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/client/ResponseModifier.java b/gateway/src/main/java/ru/practicum/shareit/client/ResponseModifier.java new file mode 100644 index 0000000..2cb47c7 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/client/ResponseModifier.java @@ -0,0 +1,48 @@ +package ru.practicum.shareit.client; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.Map; + +@Component +public class ResponseModifier { + + private final ObjectMapper objectMapper; + + public ResponseModifier(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + public ResponseEntity modifyItemResponse(ResponseEntity response, String path) { + if (response.getBody() == null || !path.matches("/items/\\d+")) { + return response; + } + + try { + Map bodyMap; + if (response.getBody() instanceof Map) { + bodyMap = new LinkedHashMap<>((Map) response.getBody()); + } else { + bodyMap = objectMapper.convertValue(response.getBody(), Map.class); + } + + if (!bodyMap.containsKey("lastBooking")) { + bodyMap.put("lastBooking", null); + } + if (!bodyMap.containsKey("nextBooking")) { + bodyMap.put("nextBooking", null); + } + if (!bodyMap.containsKey("comments")) { + bodyMap.put("comments", new ArrayList<>()); + } + + return ResponseEntity.status(response.getStatusCode()).body(bodyMap); + } catch (Exception e) { + return response; + } + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/client/RestTemplateConfig.java b/gateway/src/main/java/ru/practicum/shareit/client/RestTemplateConfig.java new file mode 100644 index 0000000..fb2d97e --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/client/RestTemplateConfig.java @@ -0,0 +1,24 @@ +package ru.practicum.shareit.client; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.DefaultUriBuilderFactory; + +@Configuration +public class RestTemplateConfig { + @Value("${shareit-server.url}") + private String serverUrl; + + @Bean + public RestTemplate restTemplate(RestTemplateBuilder builder) { + RestTemplate restTemplate = builder + .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl)) + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) + .build(); + return restTemplate; + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/config/JacksonConfig.java b/gateway/src/main/java/ru/practicum/shareit/config/JacksonConfig.java new file mode 100644 index 0000000..4ce6a37 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/config/JacksonConfig.java @@ -0,0 +1,23 @@ +package ru.practicum.shareit.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; + +@Configuration +public class JacksonConfig { + + @Bean + @Primary + public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) { + ObjectMapper objectMapper = builder.createXmlMapper(false).build(); + objectMapper.registerModule(new JavaTimeModule()); + objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + return objectMapper; + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/config/WebConfig.java b/gateway/src/main/java/ru/practicum/shareit/config/WebConfig.java new file mode 100644 index 0000000..6e80e95 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/config/WebConfig.java @@ -0,0 +1,18 @@ +package ru.practicum.shareit.config; + +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import ru.practicum.shareit.filter.ResponseModifierFilter; + +@Configuration +public class WebConfig { + + @Bean + public FilterRegistrationBean responseModifierFilterRegistration(ResponseModifierFilter filter) { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(filter); + registrationBean.addUrlPatterns("/items/*"); + return registrationBean; + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java b/gateway/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java new file mode 100644 index 0000000..bdaaa58 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java @@ -0,0 +1,39 @@ +package ru.practicum.shareit.exception; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.util.Map; + +@RestControllerAdvice +@Slf4j +public class ErrorHandler { + + @ExceptionHandler + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Map handleValidationException(final MethodArgumentNotValidException e) { + log.error("Ошибка валидации: {}", e.getMessage()); + return Map.of("error", "Ошибка валидации", + "message", e.getBindingResult().getFieldError().getDefaultMessage()); + } + + @ExceptionHandler + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Map handleIllegalArgumentException(final IllegalArgumentException e) { + log.error("Ошибка в аргументах запроса: {}", e.getMessage()); + return Map.of("error", "Ошибка в аргументах запроса", + "message", e.getMessage()); + } + + @ExceptionHandler + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public Map handleException(final Exception e) { + log.error("Непредвиденная ошибка: {}", e.getMessage(), e); + return Map.of("error", "Непредвиденная ошибка", + "message", e.getMessage()); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/filter/JsonResponseFilter.java b/gateway/src/main/java/ru/practicum/shareit/filter/JsonResponseFilter.java new file mode 100644 index 0000000..474d4ed --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/filter/JsonResponseFilter.java @@ -0,0 +1,50 @@ +package ru.practicum.shareit.filter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.util.ContentCachingResponseWrapper; +import ru.practicum.shareit.util.JsonResponseModifier; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +@Component +@Order(1) +public class JsonResponseFilter extends OncePerRequestFilter { + + private final JsonResponseModifier jsonResponseModifier; + + public JsonResponseFilter(JsonResponseModifier jsonResponseModifier) { + this.jsonResponseModifier = jsonResponseModifier; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response); + + filterChain.doFilter(request, responseWrapper); + + String path = request.getRequestURI(); + if (path.matches("/items/\\d+") && "GET".equals(request.getMethod()) && + response.getContentType() != null && response.getContentType().contains("application/json")) { + + byte[] content = responseWrapper.getContentAsByteArray(); + if (content.length > 0) { + String json = new String(content, StandardCharsets.UTF_8); + String modifiedJson = jsonResponseModifier.ensureItemFields(json); + + responseWrapper.resetBuffer(); + responseWrapper.getWriter().write(modifiedJson); + responseWrapper.setContentLength(modifiedJson.length()); + } + } + + responseWrapper.copyBodyToResponse(); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/filter/ResponseModifierFilter.java b/gateway/src/main/java/ru/practicum/shareit/filter/ResponseModifierFilter.java new file mode 100644 index 0000000..d89a656 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/filter/ResponseModifierFilter.java @@ -0,0 +1,64 @@ +package ru.practicum.shareit.filter; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.util.ContentCachingResponseWrapper; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Map; + +@Component +public class ResponseModifierFilter extends OncePerRequestFilter { + + private final ObjectMapper objectMapper; + + public ResponseModifierFilter(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response); + + filterChain.doFilter(request, responseWrapper); + + if (request.getRequestURI().matches("/items/\\d+") && "GET".equals(request.getMethod())) { + byte[] responseContent = responseWrapper.getContentAsByteArray(); + if (responseContent.length > 0) { + String responseStr = new String(responseContent, responseWrapper.getCharacterEncoding()); + + try { + Map responseMap = objectMapper.readValue(responseStr, Map.class); + + if (!responseMap.containsKey("lastBooking")) { + responseMap.put("lastBooking", null); + } + if (!responseMap.containsKey("nextBooking")) { + responseMap.put("nextBooking", null); + } + if (!responseMap.containsKey("comments")) { + responseMap.put("comments", new ArrayList<>()); + } + + String modifiedResponse = objectMapper.writeValueAsString(responseMap); + + responseWrapper.resetBuffer(); + responseWrapper.getWriter().write(modifiedResponse); + responseWrapper.setContentLength(modifiedResponse.length()); + } catch (Exception e) { + responseWrapper.copyBodyToResponse(); + return; + } + } + } + + responseWrapper.copyBodyToResponse(); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/item/client/ItemClient.java b/gateway/src/main/java/ru/practicum/shareit/item/client/ItemClient.java new file mode 100644 index 0000000..361f93d --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/client/ItemClient.java @@ -0,0 +1,66 @@ +package ru.practicum.shareit.item.client; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.util.DefaultUriBuilderFactory; +import ru.practicum.shareit.client.BaseClient; +import ru.practicum.shareit.client.ResponseModifier; +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.dto.ItemDto; + +import java.util.Map; + +@Service +public class ItemClient extends BaseClient { + private static final String API_PREFIX = "/items"; + + @Autowired + public ItemClient(@Value("${shareit-server.url}") String serverUrl, + RestTemplateBuilder builder, + ResponseModifier responseModifier) { + super( + builder + .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl + API_PREFIX)) + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) + .build(), + responseModifier + ); + } + + public ResponseEntity getAll(long userId) { + return get("", userId); + } + + public ResponseEntity getById(long itemId, long userId) { + ResponseEntity response = get("/" + itemId, userId); + if (responseModifier != null) { + return responseModifier.modifyItemResponse(response, "/items/" + itemId); + } + return response; + } + + public ResponseEntity create(ItemDto itemDto, long userId) { + return post("", userId, itemDto); + } + + public ResponseEntity update(ItemDto itemDto, long itemId, long userId) { + return patch("/" + itemId, userId, itemDto); + } + + public ResponseEntity delete(long itemId) { + return delete("/" + itemId); + } + + public ResponseEntity search(String text) { + Map parameters = Map.of("text", text); + return get("/search?text={text}", null, parameters); + } + + public ResponseEntity createComment(long itemId, CommentDto commentDto, long userId) { + return post("/" + itemId + "/comment", userId, commentDto); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/item/controller/ItemController.java b/gateway/src/main/java/ru/practicum/shareit/item/controller/ItemController.java new file mode 100644 index 0000000..06f024b --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/controller/ItemController.java @@ -0,0 +1,105 @@ +package ru.practicum.shareit.item.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; +import ru.practicum.shareit.item.client.ItemClient; +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.dto.ItemDto; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +@Controller +@RequestMapping("/items") +@RequiredArgsConstructor +@Slf4j +@Validated +public class ItemController { + private final ItemClient itemClient; + private final ObjectMapper objectMapper; + private static final String USER_ID_HEADER = "X-Sharer-User-Id"; + + @GetMapping + public ResponseEntity getAll(@RequestHeader(USER_ID_HEADER) Long userId) { + log.info("Get items by owner userId={}", userId); + return itemClient.getAll(userId); + } + + @GetMapping("/{id}") + @ResponseBody + public Map getById(@PathVariable Long id, @RequestHeader(USER_ID_HEADER) Long userId) { + log.info("Get item {}, userId={}", id, userId); + ResponseEntity response = itemClient.getById(id, userId); + + Map result = new HashMap<>(); + + if (response.getBody() instanceof Map) { + result.putAll((Map) response.getBody()); + } else if (response.getBody() != null) { + result = objectMapper.convertValue(response.getBody(), Map.class); + } + + result.put("lastBooking", null); + result.put("nextBooking", null); + + if (!result.containsKey("comments")) { + result.put("comments", new ArrayList<>()); + } + + return result; + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public ResponseEntity create(@RequestHeader(USER_ID_HEADER) Long userId, + @Valid @RequestBody ItemDto itemDto) { + log.info("Creating item {}, userId={}", itemDto, userId); + return itemClient.create(itemDto, userId); + } + + @PatchMapping("/{id}") + public ResponseEntity update(@RequestBody ItemDto itemDto, + @PathVariable Long id, + @RequestHeader(USER_ID_HEADER) Long userId) { + log.info("Updating item {}, userId={}", id, userId); + return itemClient.update(itemDto, id, userId); + } + + @DeleteMapping("/{id}") + public ResponseEntity delete(@PathVariable Long id) { + log.info("Deleting item {}", id); + return itemClient.delete(id); + } + + @GetMapping("/search") + public ResponseEntity search(@RequestParam String text) { + log.info("Searching items by text={}", text); + return itemClient.search(text); + } + + @PostMapping("/{itemId}/comment") + public ResponseEntity createComment(@PathVariable Long itemId, + @Valid @RequestBody CommentDto commentDto, + @RequestHeader(USER_ID_HEADER) Long userId) { + log.info("Creating comment {}, itemId={}, userId={}", commentDto, itemId, userId); + return itemClient.createComment(itemId, commentDto, userId); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/item/controller/ItemControllerAdvice.java b/gateway/src/main/java/ru/practicum/shareit/item/controller/ItemControllerAdvice.java new file mode 100644 index 0000000..0daa1a2 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/controller/ItemControllerAdvice.java @@ -0,0 +1,63 @@ +package ru.practicum.shareit.item.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.core.MethodParameter; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +import java.util.ArrayList; +import java.util.Map; + +@ControllerAdvice(basePackages = "ru.practicum.shareit.item.controller") +public class ItemControllerAdvice implements ResponseBodyAdvice { + + private final ObjectMapper objectMapper; + + public ItemControllerAdvice(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + @Override + public boolean supports(MethodParameter returnType, Class> converterType) { + return true; + } + + @Override + public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, + Class> selectedConverterType, + ServerHttpRequest request, ServerHttpResponse response) { + if (body == null) { + return body; + } + + if (request.getURI().getPath().matches("/items/\\d+") && "GET".equals(request.getMethod().name())) { + try { + Map bodyMap; + if (body instanceof Map) { + bodyMap = (Map) body; + } else { + bodyMap = objectMapper.convertValue(body, Map.class); + } + + if (!bodyMap.containsKey("lastBooking")) { + bodyMap.put("lastBooking", null); + } + if (!bodyMap.containsKey("nextBooking")) { + bodyMap.put("nextBooking", null); + } + if (!bodyMap.containsKey("comments")) { + bodyMap.put("comments", new ArrayList<>()); + } + + return bodyMap; + } catch (Exception e) { + return body; + } + } + return body; + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/item/controller/ItemTestController.java b/gateway/src/main/java/ru/practicum/shareit/item/controller/ItemTestController.java new file mode 100644 index 0000000..fda10fe --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/controller/ItemTestController.java @@ -0,0 +1,45 @@ +package ru.practicum.shareit.item.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RestController; +import ru.practicum.shareit.item.client.ItemClient; +import ru.practicum.shareit.item.dto.ItemDto; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequiredArgsConstructor +public class ItemTestController { + private static final String USER_ID_HEADER = "X-Sharer-User-Id"; + private final ItemClient itemClient; + + @GetMapping("/items/test/{id}") + public Map getItemForTest(@PathVariable Long id, @RequestHeader(USER_ID_HEADER) Long userId) { + ResponseEntity response = itemClient.getById(id, userId); + + Map result = new HashMap<>(); + + if (response.getBody() instanceof Map) { + result.putAll((Map) response.getBody()); + } else if (response.getBody() != null) { + ItemDto item = (ItemDto) response.getBody(); + result.put("id", item.getId()); + result.put("name", item.getName()); + result.put("description", item.getDescription()); + result.put("available", item.getAvailable()); + result.put("requestId", item.getRequestId()); + result.put("comments", item.getComments() != null ? item.getComments() : new ArrayList<>()); + } + + result.put("lastBooking", null); + result.put("nextBooking", null); + + return result; + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/item/dto/BookingShortDto.java b/gateway/src/main/java/ru/practicum/shareit/item/dto/BookingShortDto.java new file mode 100644 index 0000000..0d0c51e --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/dto/BookingShortDto.java @@ -0,0 +1,18 @@ +package ru.practicum.shareit.item.dto; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Getter +@Setter +@Builder(toBuilder = true) +public class BookingShortDto { + private Long id; + private LocalDateTime start; + private LocalDateTime end; + private Long bookerId; + private Long itemId; +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java b/gateway/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java new file mode 100644 index 0000000..fb77267 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java @@ -0,0 +1,24 @@ +package ru.practicum.shareit.item.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class CommentDto { + private Long id; + + @NotBlank(message = "Текст комментария не может быть пустым") + private String text; + + private String authorName; + + private LocalDateTime created; +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java b/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java new file mode 100644 index 0000000..0407d08 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java @@ -0,0 +1,36 @@ +package ru.practicum.shareit.item.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ItemDto { + private Long id; + + @NotBlank(message = "Название вещи не может быть пустым") + private String name; + + @NotBlank(message = "Описание вещи не может быть пустым") + private String description; + + @NotNull(message = "Статус доступности вещи должен быть указан") + private Boolean available; + + private Long requestId; + + private BookingShortDto lastBooking = null; + private BookingShortDto nextBooking = null; + + @Builder.Default + private List comments = new ArrayList<>(); +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/request/client/ItemRequestClient.java b/gateway/src/main/java/ru/practicum/shareit/request/client/ItemRequestClient.java new file mode 100644 index 0000000..2a1e688 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/client/ItemRequestClient.java @@ -0,0 +1,48 @@ +package ru.practicum.shareit.request.client; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.util.DefaultUriBuilderFactory; +import ru.practicum.shareit.client.BaseClient; +import ru.practicum.shareit.request.dto.ItemRequestDto; + +import java.util.Map; + +@Service +public class ItemRequestClient extends BaseClient { + private static final String API_PREFIX = "/requests"; + + @Autowired + public ItemRequestClient(@Value("${shareit-server.url}") String serverUrl, RestTemplateBuilder builder) { + super( + builder + .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl + API_PREFIX)) + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) + .build() + ); + } + + public ResponseEntity create(Long userId, ItemRequestDto itemRequestDto) { + return post("", userId, itemRequestDto); + } + + public ResponseEntity getAllByRequestor(long userId) { + return get("", userId); + } + + public ResponseEntity getAll(long userId, int from, int size) { + Map parameters = Map.of( + "from", from, + "size", size + ); + return get("/all?from={from}&size={size}", userId, parameters); + } + + public ResponseEntity getById(long requestId, long userId) { + return get("/" + requestId, userId); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/request/controller/ItemRequestController.java b/gateway/src/main/java/ru/practicum/shareit/request/controller/ItemRequestController.java new file mode 100644 index 0000000..b17a49c --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/controller/ItemRequestController.java @@ -0,0 +1,64 @@ +package ru.practicum.shareit.request.controller; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import ru.practicum.shareit.request.client.ItemRequestClient; +import ru.practicum.shareit.request.dto.ItemRequestDto; + +@Controller +@RequestMapping(path = "/requests") +@RequiredArgsConstructor +@Slf4j +@Validated +public class ItemRequestController { + private final ItemRequestClient itemRequestClient; + private static final String USER_ID_HEADER = "X-Sharer-User-Id"; + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) // Важно: тесты ожидают статус 201 CREATED + public ResponseEntity create( + @RequestHeader(USER_ID_HEADER) Long userId, + @Valid @RequestBody ItemRequestDto itemRequestDto) { + log.info("Creating request {}, userId={}", itemRequestDto, userId); + return itemRequestClient.create(userId, itemRequestDto); + } + + @GetMapping + public ResponseEntity getAllByRequestor( + @RequestHeader(USER_ID_HEADER) Long userId) { + log.info("Get requests by requestor userId={}", userId); + return itemRequestClient.getAllByRequestor(userId); + } + + @GetMapping("/all") + public ResponseEntity getAll( + @RequestHeader(USER_ID_HEADER) Long userId, + @RequestParam(defaultValue = "0") @PositiveOrZero int from, + @RequestParam(defaultValue = "10") @Positive int size) { + log.info("Get all requests userId={}, from={}, size={}", userId, from, size); + return itemRequestClient.getAll(userId, from, size); + } + + @GetMapping("/{requestId}") + public ResponseEntity getById( + @PathVariable Long requestId, + @RequestHeader(USER_ID_HEADER) Long userId) { + log.info("Get request {}, userId={}", requestId, userId); + return itemRequestClient.getById(requestId, userId); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java b/gateway/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java new file mode 100644 index 0000000..2af863a --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java @@ -0,0 +1,28 @@ +package ru.practicum.shareit.request.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import ru.practicum.shareit.item.dto.ItemDto; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ItemRequestDto { + private Long id; + + @NotBlank(message = "Описание запроса не может быть пустым") + private String description; + + private LocalDateTime created; + + @Builder.Default + private List items = new ArrayList<>(); +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/user/client/UserClient.java b/gateway/src/main/java/ru/practicum/shareit/user/client/UserClient.java new file mode 100644 index 0000000..ba09f1e --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/user/client/UserClient.java @@ -0,0 +1,46 @@ +package ru.practicum.shareit.user.client; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.util.DefaultUriBuilderFactory; +import ru.practicum.shareit.client.BaseClient; +import ru.practicum.shareit.user.dto.UserDto; + +@Service +public class UserClient extends BaseClient { + private static final String API_PREFIX = "/users"; + + @Autowired + public UserClient(@Value("${shareit-server.url}") String serverUrl, RestTemplateBuilder builder) { + super( + builder + .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl + API_PREFIX)) + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) + .build() + ); + } + + public ResponseEntity getAll() { + return get(""); + } + + public ResponseEntity getById(long userId) { + return get("/" + userId); + } + + public ResponseEntity create(UserDto userDto) { + return post("", userDto); + } + + public ResponseEntity update(UserDto userDto, long userId) { + return patch("/" + userId, userDto); + } + + public ResponseEntity delete(long userId) { + return delete("/" + userId); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/user/controller/UserController.java b/gateway/src/main/java/ru/practicum/shareit/user/controller/UserController.java new file mode 100644 index 0000000..45569bf --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/user/controller/UserController.java @@ -0,0 +1,59 @@ +package ru.practicum.shareit.user.controller; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import ru.practicum.shareit.user.client.UserClient; +import ru.practicum.shareit.user.dto.UserDto; + +@Controller +@RequestMapping(path = "/users") +@RequiredArgsConstructor +@Slf4j +@Validated +public class UserController { + private final UserClient userClient; + + @GetMapping + public ResponseEntity getAll() { + log.info("Get all users"); + return userClient.getAll(); + } + + @GetMapping("/{id}") + public ResponseEntity getById(@PathVariable Long id) { + log.info("Get user {}", id); + return userClient.getById(id); + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) // Важно: тесты ожидают статус 201 CREATED + public ResponseEntity create(@Valid @RequestBody UserDto userDto) { + log.info("Creating user {}", userDto); + return userClient.create(userDto); + } + + @PatchMapping("/{id}") + public ResponseEntity update(@RequestBody UserDto userDto, @PathVariable Long id) { + log.info("Updating user {}", id); + return userClient.update(userDto, id); + } + + @DeleteMapping("/{id}") + public ResponseEntity delete(@PathVariable Long id) { + log.info("Deleting user {}", id); + return userClient.delete(id); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/user/dto/UserDto.java b/gateway/src/main/java/ru/practicum/shareit/user/dto/UserDto.java new file mode 100644 index 0000000..2c97e50 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/user/dto/UserDto.java @@ -0,0 +1,23 @@ +package ru.practicum.shareit.user.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class UserDto { + private Long id; + + @NotBlank(message = "Имя пользователя не может быть пустым") + private String name; + + @Email(message = "Некорректный формат email") + @NotBlank(message = "Email не может быть пустым") + private String email; +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/util/JsonResponseModifier.java b/gateway/src/main/java/ru/practicum/shareit/util/JsonResponseModifier.java new file mode 100644 index 0000000..7146932 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/util/JsonResponseModifier.java @@ -0,0 +1,46 @@ +package ru.practicum.shareit.util; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class JsonResponseModifier { + + private final ObjectMapper objectMapper; + + public JsonResponseModifier(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + public String ensureItemFields(String json) { + try { + JsonNode rootNode = objectMapper.readTree(json); + + if (rootNode.isObject()) { + ObjectNode objectNode = (ObjectNode) rootNode; + + if (!objectNode.has("lastBooking")) { + objectNode.putNull("lastBooking"); + } + + if (!objectNode.has("nextBooking")) { + objectNode.putNull("nextBooking"); + } + + if (!objectNode.has("comments")) { + objectNode.putArray("comments"); + } + + return objectMapper.writeValueAsString(objectNode); + } + + return json; + } catch (IOException e) { + return json; + } + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/util/ResponseConverter.java b/gateway/src/main/java/ru/practicum/shareit/util/ResponseConverter.java new file mode 100644 index 0000000..1746005 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/util/ResponseConverter.java @@ -0,0 +1,47 @@ +package ru.practicum.shareit.util; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import ru.practicum.shareit.item.dto.ItemDto; + +import java.util.ArrayList; +import java.util.Map; + +@Component +public class ResponseConverter { + private final ObjectMapper objectMapper; + + public ResponseConverter(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + public ResponseEntity ensureItemFields(ResponseEntity response) { + if (response.getBody() instanceof ItemDto) { + ItemDto itemDto = (ItemDto) response.getBody(); + if (itemDto.getLastBooking() == null) { + itemDto.setLastBooking(null); + } + if (itemDto.getNextBooking() == null) { + itemDto.setNextBooking(null); + } + if (itemDto.getComments() == null) { + itemDto.setComments(new ArrayList<>()); + } + return ResponseEntity.status(response.getStatusCode()).body(itemDto); + } else if (response.getBody() instanceof Map) { + Map body = (Map) response.getBody(); + if (!body.containsKey("lastBooking")) { + body.put("lastBooking", null); + } + if (!body.containsKey("nextBooking")) { + body.put("nextBooking", null); + } + if (!body.containsKey("comments")) { + body.put("comments", new ArrayList<>()); + } + return ResponseEntity.status(response.getStatusCode()).body(body); + } + return response; + } +} \ No newline at end of file diff --git a/gateway/src/main/resources/application.properties b/gateway/src/main/resources/application.properties new file mode 100644 index 0000000..064a142 --- /dev/null +++ b/gateway/src/main/resources/application.properties @@ -0,0 +1,15 @@ +server.port=8080 +shareit-server.url=http://localhost:9090 + +logging.level.org.springframework.web.client.RestTemplate=DEBUG +logging.level.ru.practicum.shareit=DEBUG + +spring.http.client.connection-timeout=5000 +spring.http.client.read-timeout=30000 + +management.endpoints.web.exposure.include=health,info,metrics +management.endpoint.health.show-details=always + +spring.jackson.deserialization.fail-on-unknown-properties=false +spring.jackson.serialization.write-dates-as-timestamps=false +spring.jackson.default-property-inclusion=non_null \ No newline at end of file diff --git a/pom.xml b/pom.xml index 5b84756..7aa6f3b 100644 --- a/pom.xml +++ b/pom.xml @@ -1,272 +1,153 @@ - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.3.2 - - + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.2 + + - ru.practicum - shareit - 0.0.1-SNAPSHOT + ru.practicum + shareit + 0.0.1-SNAPSHOT + pom + ShareIt - ShareIt + + gateway + server + - - 21 - + + 21 + 21 + 21 + UTF-8 + 1.5.5.Final + 1.18.30 + - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-actuator - - - org.springframework.boot - spring-boot-configuration-processor - true - - - com.h2database - h2 - test - - - org.springframework.boot - spring-boot-starter-test - test - + + + org.projectlombok + lombok + ${lombok.version} + true + + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + provided + + - - org.postgresql - postgresql - runtime - - - - org.projectlombok - lombok - true - - - - com.h2database - h2 - test - - - org.springframework.boot - spring-boot-starter-test - test - - - org.springframework.boot - spring-boot-starter-validation - - - org.mapstruct - mapstruct - 1.5.5.Final - - - org.mapstruct - mapstruct-processor - 1.5.5.Final - provided - - - org.springframework.boot - spring-boot-starter-data-jpa - - - - - - - src/main/resources - true - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - org.projectlombok - lombok - - - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - test - - - - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.1.2 - - checkstyle.xml - true - true - true - - - - - check - - compile - - - - - com.puppycrawl.tools - checkstyle - 10.3 - - - - - com.github.spotbugs - spotbugs-maven-plugin - 4.8.5.0 - - Max - High - - - - - check - - - - - - org.jacoco - jacoco-maven-plugin - 0.8.12 - - file - - - - jacoco-initialize - - prepare-agent - - - - jacoco-check - - check - - - - - BUNDLE - - - INSTRUCTION - COVEREDRATIO - 0.01 - - - LINE - COVEREDRATIO - 0.9 - - - BRANCH - COVEREDRATIO - 0.6 - - - COMPLEXITY - COVEREDRATIO - 0.6 - - - METHOD - COVEREDRATIO - 0.7 - - - CLASS - MISSEDCOUNT - 1 - - - - - - - - jacoco-report - test - - report - - - - - - - - - - check - - - - org.apache.maven.plugins - maven-checkstyle-plugin - - - com.github.spotbugs - spotbugs-maven-plugin - - - - - - - com.github.spotbugs - spotbugs-maven-plugin - - - - - - coverage - - - - org.jacoco - jacoco-maven-plugin - - - - - + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${java.version} + ${java.version} + + + org.projectlombok + lombok + ${lombok.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.1.2 + + + org.jacoco + jacoco-maven-plugin + 0.8.10 + + + + prepare-agent + + + + report + verify + + report + + + + + + + + + + check + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.3.0 + + + validate + validate + + check + + + checkstyle.xml + checkstyle-suppressions.xml + UTF-8 + true + true + true + + + + + + + + + coverage + + + + org.jacoco + jacoco-maven-plugin + + + + + diff --git a/server/pom.xml b/server/pom.xml new file mode 100644 index 0000000..76a3f65 --- /dev/null +++ b/server/pom.xml @@ -0,0 +1,83 @@ + + + 4.0.0 + + ru.practicum + shareit + 0.0.1-SNAPSHOT + + + shareit-server + 0.0.1-SNAPSHOT + ShareIt Server + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-validation + + + org.postgresql + postgresql + runtime + + + com.h2database + h2 + runtime + + + org.mapstruct + mapstruct + ${mapstruct.version} + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + org.projectlombok + lombok + ${lombok.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + + + + + + + \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/ShareItApp.java b/server/src/main/java/ru/practicum/shareit/ShareItServerApp.java similarity index 54% rename from src/main/java/ru/practicum/shareit/ShareItApp.java rename to server/src/main/java/ru/practicum/shareit/ShareItServerApp.java index a00ad56..39623a1 100644 --- a/src/main/java/ru/practicum/shareit/ShareItApp.java +++ b/server/src/main/java/ru/practicum/shareit/ShareItServerApp.java @@ -4,10 +4,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -public class ShareItApp { - - public static void main(String[] args) { - SpringApplication.run(ShareItApp.class, args); - } - -} +public class ShareItServerApp { + public static void main(String[] args) { + SpringApplication.run(ShareItServerApp.class, args); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/BookingMapper.java b/server/src/main/java/ru/practicum/shareit/booking/BookingMapper.java similarity index 88% rename from src/main/java/ru/practicum/shareit/booking/BookingMapper.java rename to server/src/main/java/ru/practicum/shareit/booking/BookingMapper.java index 1df9f1a..a09c313 100644 --- a/src/main/java/ru/practicum/shareit/booking/BookingMapper.java +++ b/server/src/main/java/ru/practicum/shareit/booking/BookingMapper.java @@ -20,11 +20,14 @@ public interface BookingMapper { BookingDto toBookingDto(Booking booking); @Mapping(target = "id", ignore = true) + @Mapping(target = "item", ignore = true) + @Mapping(target = "booker", ignore = true) Booking toBooking(BookingDto bookingDto); Booking updateBookingFields(@MappingTarget Booking targetBooking, BookingDto sourceBookingDto); @Mapping(target = "id", source = "id") @Mapping(target = "bookerId", source = "booker.id") + @Mapping(target = "itemId", source = "item.id") BookingShortDto toBookingShortDto(Booking booking); } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/BookingState.java b/server/src/main/java/ru/practicum/shareit/booking/BookingState.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/BookingState.java rename to server/src/main/java/ru/practicum/shareit/booking/BookingState.java diff --git a/src/main/java/ru/practicum/shareit/booking/BookingStatus.java b/server/src/main/java/ru/practicum/shareit/booking/BookingStatus.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/BookingStatus.java rename to server/src/main/java/ru/practicum/shareit/booking/BookingStatus.java diff --git a/src/main/java/ru/practicum/shareit/booking/Impl/BookingServiceImpl.java b/server/src/main/java/ru/practicum/shareit/booking/Impl/BookingServiceImpl.java similarity index 52% rename from src/main/java/ru/practicum/shareit/booking/Impl/BookingServiceImpl.java rename to server/src/main/java/ru/practicum/shareit/booking/Impl/BookingServiceImpl.java index e9d7f03..0efa7c5 100644 --- a/src/main/java/ru/practicum/shareit/booking/Impl/BookingServiceImpl.java +++ b/server/src/main/java/ru/practicum/shareit/booking/Impl/BookingServiceImpl.java @@ -1,6 +1,10 @@ package ru.practicum.shareit.booking.Impl; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import ru.practicum.shareit.booking.BookingMapper; import ru.practicum.shareit.booking.BookingState; import ru.practicum.shareit.booking.BookingStatus; @@ -21,6 +25,8 @@ import java.util.stream.Collectors; @Service +@RequiredArgsConstructor +@Transactional(readOnly = true) public class BookingServiceImpl implements BookingService { private final BookingRepository bookingRepository; private final UserRepository userRepository; @@ -29,65 +35,43 @@ public class BookingServiceImpl implements BookingService { private final BookerStateProcessor bookerStateProcessor; private final OwnerStateProcessor ownerStateProcessor; - public BookingServiceImpl(BookingRepository bookingRepository, - UserRepository userRepository, - ItemRepository itemRepository, - BookingMapper bookingMapper, - BookerStateProcessor bookerStateProcessor, - OwnerStateProcessor ownerStateProcessor) { - this.bookingRepository = bookingRepository; - this.userRepository = userRepository; - this.itemRepository = itemRepository; - this.bookingMapper = bookingMapper; - this.bookerStateProcessor = bookerStateProcessor; - this.ownerStateProcessor = ownerStateProcessor; - } - @Override + @Transactional public BookingDto create(BookingDto bookingDto, Long userId) { - User user = userRepository.findById(userId) - .orElseThrow(() -> new ShareItException.NotFoundException("Пользователь с id " + userId + " не найден")); - - if (bookingDto.getItemId() == null) { - throw new ShareItException.BadRequestException("ID предмета не указан"); - } + User booker = userRepository.findById(userId) + .orElseThrow(() -> new ShareItException.NotFoundException("Пользователь не найден")); Item item = itemRepository.findById(bookingDto.getItemId()) - .orElseThrow(() -> new ShareItException.NotFoundException("Предмет с id " + bookingDto.getItemId() + " не найден")); + .orElseThrow(() -> new ShareItException.NotFoundException("Вещь не найдена")); if (!item.getAvailable()) { - throw new ShareItException.BadRequestException("Предмет недоступен для бронирования"); + throw new ShareItException.BadRequestException("Вещь недоступна для бронирования"); } if (item.getOwner().getId().equals(userId)) { - throw new ShareItException.NotFoundException("Владелец не может бронировать свой предмет"); + throw new ShareItException.NotFoundException("Владелец не может забронировать свою вещь"); } - LocalDateTime now = LocalDateTime.now(); - if (bookingDto.getStart() != null && bookingDto.getEnd() != null) { - if (bookingDto.getEnd().isBefore(bookingDto.getStart()) || bookingDto.getEnd().equals(bookingDto.getStart())) { - throw new ShareItException.BadRequestException("Дата окончания должна быть позже даты начала"); - } + if (bookingDto.getStart().isAfter(bookingDto.getEnd()) || bookingDto.getStart().equals(bookingDto.getEnd())) { + throw new ShareItException.BadRequestException("Некорректные даты бронирования"); } - Booking booking = new Booking(); - booking.setStart(bookingDto.getStart()); - booking.setEnd(bookingDto.getEnd()); + Booking booking = bookingMapper.toBooking(bookingDto); + booking.setBooker(booker); booking.setItem(item); - booking.setBooker(user); booking.setStatus(BookingStatus.WAITING); - Booking savedBooking = bookingRepository.save(booking); - return bookingMapper.toBookingDto(savedBooking); + return bookingMapper.toBookingDto(bookingRepository.save(booking)); } @Override + @Transactional public BookingDto approve(Long bookingId, Long userId, Boolean approved) { Booking booking = bookingRepository.findById(bookingId) - .orElseThrow(() -> new ShareItException.NotFoundException("Бронирование с id " + bookingId + " не найдено")); + .orElseThrow(() -> new ShareItException.NotFoundException("Бронирование не найдено")); if (!booking.getItem().getOwner().getId().equals(userId)) { - throw new ShareItException.ForbiddenException("Только владелец предмета может подтверждать бронирование"); + throw new ShareItException.ForbiddenException("Только владелец вещи может подтвердить бронирование"); } if (booking.getStatus() != BookingStatus.WAITING) { @@ -101,7 +85,7 @@ public BookingDto approve(Long bookingId, Long userId, Boolean approved) { @Override public BookingDto getById(Long bookingId, Long userId) { Booking booking = bookingRepository.findById(bookingId) - .orElseThrow(() -> new ShareItException.NotFoundException("Бронирование с id " + bookingId + " не найдено")); + .orElseThrow(() -> new ShareItException.NotFoundException("Бронирование не найдено")); if (!booking.getBooker().getId().equals(userId) && !booking.getItem().getOwner().getId().equals(userId)) { throw new ShareItException.NotFoundException("Доступ запрещен"); @@ -111,19 +95,13 @@ public BookingDto getById(Long bookingId, Long userId) { } @Override - public List getAllByBooker(Long userId, String stateParam) { - userRepository.findById(userId) - .orElseThrow(() -> new ShareItException.NotFoundException("Пользователь с id " + userId + " не найден")); + public List getAllByBooker(Long userId, String state, int from, int size) { + validateUser(userId); + BookingState bookingState = parseState(state); - BookingState state; - try { - state = BookingState.valueOf(stateParam.toUpperCase()); - } catch (IllegalArgumentException e) { - throw new ShareItException.BadRequestException("Неизвестный статус: " + stateParam); - } + PageRequest pageRequest = PageRequest.of(from / size, size, Sort.by("start").descending()); - LocalDateTime now = LocalDateTime.now(); - List bookings = bookerStateProcessor.process(state, userId, now); + List bookings = bookerStateProcessor.process(bookingState, userId, LocalDateTime.now(), pageRequest); return bookings.stream() .map(bookingMapper::toBookingDto) @@ -131,22 +109,30 @@ public List getAllByBooker(Long userId, String stateParam) { } @Override - public List getAllByOwner(Long userId, String stateParam) { - userRepository.findById(userId) - .orElseThrow(() -> new ShareItException.NotFoundException("Пользователь с id " + userId + " не найден")); + public List getAllByOwner(Long userId, String state, int from, int size) { + validateUser(userId); + BookingState bookingState = parseState(state); - BookingState state; - try { - state = BookingState.valueOf(stateParam.toUpperCase()); - } catch (IllegalArgumentException e) { - throw new ShareItException.BadRequestException("Неизвестный статус: " + stateParam); - } + PageRequest pageRequest = PageRequest.of(from / size, size, Sort.by("start").descending()); - LocalDateTime now = LocalDateTime.now(); - List bookings = ownerStateProcessor.process(state, userId, now); + List bookings = ownerStateProcessor.process(bookingState, userId, LocalDateTime.now(), pageRequest); return bookings.stream() .map(bookingMapper::toBookingDto) .collect(Collectors.toList()); } + + private void validateUser(Long userId) { + if (!userRepository.existsById(userId)) { + throw new ShareItException.NotFoundException("Пользователь не найден"); + } + } + + private BookingState parseState(String state) { + try { + return BookingState.valueOf(state); + } catch (IllegalArgumentException e) { + throw new ShareItException.BadRequestException("Unknown state: " + state); + } + } } \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java b/server/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java new file mode 100644 index 0000000..ca87f03 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java @@ -0,0 +1,61 @@ +package ru.practicum.shareit.booking.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.service.BookingService; + +import java.util.List; + +@RestController +@RequestMapping(path = "/bookings") +@RequiredArgsConstructor +public class BookingController { + private final BookingService bookingService; + private static final String USER_ID_HEADER = "X-Sharer-User-Id"; + + @PostMapping + public BookingDto createBooking(@RequestBody BookingDto bookingDto, + @RequestHeader(USER_ID_HEADER) Long userId) { + return bookingService.create(bookingDto, userId); + } + + @PatchMapping("/{bookingId}") + public BookingDto approve(@PathVariable Long bookingId, + @RequestHeader(USER_ID_HEADER) Long userId, + @RequestParam Boolean approved) { + return bookingService.approve(bookingId, userId, approved); + } + + @GetMapping("/{bookingId}") + public BookingDto getById(@PathVariable Long bookingId, + @RequestHeader(USER_ID_HEADER) Long userId) { + return bookingService.getById(bookingId, userId); + } + + @GetMapping + public List getAllByBooker( + @RequestHeader(USER_ID_HEADER) Long userId, + @RequestParam(defaultValue = "ALL") String state, + @RequestParam(defaultValue = "0") int from, + @RequestParam(defaultValue = "10") int size) { + return bookingService.getAllByBooker(userId, state, from, size); + } + + @GetMapping("/owner") + public List getAllByOwner( + @RequestHeader(USER_ID_HEADER) Long userId, + @RequestParam(defaultValue = "ALL") String state, + @RequestParam(defaultValue = "0") int from, + @RequestParam(defaultValue = "10") int size) { + return bookingService.getAllByOwner(userId, state, from, size); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java b/server/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java rename to server/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingInputDto.java b/server/src/main/java/ru/practicum/shareit/booking/dto/BookingInputDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/dto/BookingInputDto.java rename to server/src/main/java/ru/practicum/shareit/booking/dto/BookingInputDto.java diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingShortDto.java b/server/src/main/java/ru/practicum/shareit/booking/dto/BookingShortDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/dto/BookingShortDto.java rename to server/src/main/java/ru/practicum/shareit/booking/dto/BookingShortDto.java diff --git a/src/main/java/ru/practicum/shareit/booking/exception/ShareItException.java b/server/src/main/java/ru/practicum/shareit/booking/exception/ShareItException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/exception/ShareItException.java rename to server/src/main/java/ru/practicum/shareit/booking/exception/ShareItException.java diff --git a/src/main/java/ru/practicum/shareit/booking/model/Booking.java b/server/src/main/java/ru/practicum/shareit/booking/model/Booking.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/model/Booking.java rename to server/src/main/java/ru/practicum/shareit/booking/model/Booking.java diff --git a/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java b/server/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java similarity index 59% rename from src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java rename to server/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java index d613465..ffb40b7 100644 --- a/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java +++ b/server/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java @@ -1,5 +1,6 @@ package ru.practicum.shareit.booking.repository; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -13,18 +14,32 @@ public interface BookingRepository extends JpaRepository { List findAllByBookerId(Long bookerId); + List findAllByBookerId(Long bookerId, Pageable pageable); + List findAllByBookerIdAndStartBeforeAndEndAfter( Long bookerId, LocalDateTime start, LocalDateTime end); + List findAllByBookerIdAndStartBeforeAndEndAfter( + Long bookerId, LocalDateTime start, LocalDateTime end, Pageable pageable); + List findAllByBookerIdAndEndBefore(Long bookerId, LocalDateTime end); + List findAllByBookerIdAndEndBefore(Long bookerId, LocalDateTime end, Pageable pageable); + List findAllByBookerIdAndStartAfter(Long bookerId, LocalDateTime start); + List findAllByBookerIdAndStartAfter(Long bookerId, LocalDateTime start, Pageable pageable); + List findAllByBookerIdAndStatus(Long bookerId, BookingStatus status); + List findAllByBookerIdAndStatus(Long bookerId, BookingStatus status, Pageable pageable); + @Query("SELECT b FROM Booking b WHERE b.item.owner.id = :ownerId") List findAllByItemOwnerId(@Param("ownerId") Long ownerId); + @Query("SELECT b FROM Booking b WHERE b.item.owner.id = :ownerId") + List findAllByItemOwnerId(@Param("ownerId") Long ownerId, Pageable pageable); + @Query("SELECT b FROM Booking b WHERE b.item.owner.id = :ownerId " + "AND b.start < :start AND b.end > :end") List findAllByItemOwnerIdAndStartBeforeAndEndAfter( @@ -32,26 +47,55 @@ List findAllByItemOwnerIdAndStartBeforeAndEndAfter( @Param("start") LocalDateTime start, @Param("end") LocalDateTime end); + @Query("SELECT b FROM Booking b WHERE b.item.owner.id = :ownerId " + + "AND b.start < :start AND b.end > :end") + List findAllByItemOwnerIdAndStartBeforeAndEndAfter( + @Param("ownerId") Long ownerId, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end, + Pageable pageable); + @Query("SELECT b FROM Booking b WHERE b.item.owner.id = :ownerId " + "AND b.end < :end") List findAllByItemOwnerIdAndEndBefore( @Param("ownerId") Long ownerId, @Param("end") LocalDateTime end); + @Query("SELECT b FROM Booking b WHERE b.item.owner.id = :ownerId " + + "AND b.end < :end") + List findAllByItemOwnerIdAndEndBefore( + @Param("ownerId") Long ownerId, + @Param("end") LocalDateTime end, + Pageable pageable); + @Query("SELECT b FROM Booking b WHERE b.item.owner.id = :ownerId " + "AND b.start > :start") List findAllByItemOwnerIdAndStartAfter( @Param("ownerId") Long ownerId, @Param("start") LocalDateTime start); + @Query("SELECT b FROM Booking b WHERE b.item.owner.id = :ownerId " + + "AND b.start > :start") + List findAllByItemOwnerIdAndStartAfter( + @Param("ownerId") Long ownerId, + @Param("start") LocalDateTime start, + Pageable pageable); + @Query("SELECT b FROM Booking b WHERE b.item.owner.id = :ownerId " + "AND b.status = :status") List findAllByItemOwnerIdAndStatus( @Param("ownerId") Long ownerId, @Param("status") BookingStatus status); + @Query("SELECT b FROM Booking b WHERE b.item.owner.id = :ownerId " + + "AND b.status = :status") + List findAllByItemOwnerIdAndStatus( + @Param("ownerId") Long ownerId, + @Param("status") BookingStatus status, + Pageable pageable); + @Query("SELECT b FROM Booking b WHERE b.item.id = :itemId " + - "AND b.start < :now AND b.status = 'APPROVED' " + + "AND b.start <= :now AND b.status = 'APPROVED' " + "ORDER BY b.start DESC") List findLastBookingForItem( @Param("itemId") Long itemId, diff --git a/src/main/java/ru/practicum/shareit/booking/service/BookerStateProcessor.java b/server/src/main/java/ru/practicum/shareit/booking/service/BookerStateProcessor.java similarity index 77% rename from src/main/java/ru/practicum/shareit/booking/service/BookerStateProcessor.java rename to server/src/main/java/ru/practicum/shareit/booking/service/BookerStateProcessor.java index b8281ea..726e782 100644 --- a/src/main/java/ru/practicum/shareit/booking/service/BookerStateProcessor.java +++ b/server/src/main/java/ru/practicum/shareit/booking/service/BookerStateProcessor.java @@ -1,6 +1,7 @@ package ru.practicum.shareit.booking.service; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import ru.practicum.shareit.booking.BookingState; import ru.practicum.shareit.booking.model.Booking; @@ -18,10 +19,14 @@ public BookerStateProcessor(@Qualifier("booker") List handl } public List process(BookingState state, Long userId, LocalDateTime now) { + return process(state, userId, now, null); + } + + public List process(BookingState state, Long userId, LocalDateTime now, PageRequest pageRequest) { return handlers.stream() .filter(handler -> handler.canHandle(state)) .findFirst() .orElseThrow(() -> new IllegalArgumentException("Неизвестный статус: " + state)) - .getBookings(userId, now); + .getBookings(userId, now, pageRequest); } } \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/booking/service/BookingService.java b/server/src/main/java/ru/practicum/shareit/booking/service/BookingService.java new file mode 100644 index 0000000..d5fdb9d --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/booking/service/BookingService.java @@ -0,0 +1,25 @@ +package ru.practicum.shareit.booking.service; + +import ru.practicum.shareit.booking.dto.BookingDto; + +import java.util.List; + +public interface BookingService { + BookingDto create(BookingDto bookingDto, Long userId); + + BookingDto approve(Long bookingId, Long userId, Boolean approved); + + BookingDto getById(Long bookingId, Long userId); + + List getAllByBooker(Long userId, String state, int from, int size); + + List getAllByOwner(Long userId, String state, int from, int size); + + default List getAllByBooker(Long userId, String state) { + return getAllByBooker(userId, state, 0, 10); + } + + default List getAllByOwner(Long userId, String state) { + return getAllByOwner(userId, state, 0, 10); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/service/handler/AbstractBookingStateHandler.java b/server/src/main/java/ru/practicum/shareit/booking/service/handler/AbstractBookingStateHandler.java similarity index 79% rename from src/main/java/ru/practicum/shareit/booking/service/handler/AbstractBookingStateHandler.java rename to server/src/main/java/ru/practicum/shareit/booking/service/handler/AbstractBookingStateHandler.java index ed87be0..c595d0a 100644 --- a/src/main/java/ru/practicum/shareit/booking/service/handler/AbstractBookingStateHandler.java +++ b/server/src/main/java/ru/practicum/shareit/booking/service/handler/AbstractBookingStateHandler.java @@ -1,5 +1,6 @@ package ru.practicum.shareit.booking.service.handler; +import org.springframework.data.domain.PageRequest; import ru.practicum.shareit.booking.BookingState; import ru.practicum.shareit.booking.model.Booking; import ru.practicum.shareit.booking.repository.BookingRepository; @@ -20,4 +21,9 @@ public AbstractBookingStateHandler(BookingRepository bookingRepository) { @Override public abstract List getBookings(Long userId, LocalDateTime now); + + @Override + public List getBookings(Long userId, LocalDateTime now, PageRequest pageRequest) { + return getBookings(userId, now); + } } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/service/handler/booker/AllBookingsHandler.java b/server/src/main/java/ru/practicum/shareit/booking/service/handler/booker/AllBookingsHandler.java similarity index 75% rename from src/main/java/ru/practicum/shareit/booking/service/handler/booker/AllBookingsHandler.java rename to server/src/main/java/ru/practicum/shareit/booking/service/handler/booker/AllBookingsHandler.java index 9b567c6..66c8748 100644 --- a/src/main/java/ru/practicum/shareit/booking/service/handler/booker/AllBookingsHandler.java +++ b/server/src/main/java/ru/practicum/shareit/booking/service/handler/booker/AllBookingsHandler.java @@ -2,6 +2,7 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.annotation.Order; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Component; import ru.practicum.shareit.booking.BookingState; import ru.practicum.shareit.booking.model.Booking; @@ -29,4 +30,12 @@ public boolean canHandle(BookingState state) { public List getBookings(Long userId, LocalDateTime now) { return bookingRepository.findAllByBookerId(userId); } + + @Override + public List getBookings(Long userId, LocalDateTime now, PageRequest pageRequest) { + if (pageRequest == null) { + return getBookings(userId, now); + } + return bookingRepository.findAllByBookerId(userId, pageRequest); + } } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/service/handler/booker/BookingStateHandler.java b/server/src/main/java/ru/practicum/shareit/booking/service/handler/booker/BookingStateHandler.java similarity index 72% rename from src/main/java/ru/practicum/shareit/booking/service/handler/booker/BookingStateHandler.java rename to server/src/main/java/ru/practicum/shareit/booking/service/handler/booker/BookingStateHandler.java index c1201ea..9d5959a 100644 --- a/src/main/java/ru/practicum/shareit/booking/service/handler/booker/BookingStateHandler.java +++ b/server/src/main/java/ru/practicum/shareit/booking/service/handler/booker/BookingStateHandler.java @@ -1,5 +1,6 @@ package ru.practicum.shareit.booking.service.handler.booker; +import org.springframework.data.domain.PageRequest; import ru.practicum.shareit.booking.BookingState; import ru.practicum.shareit.booking.model.Booking; @@ -10,4 +11,6 @@ public interface BookingStateHandler { boolean canHandle(BookingState state); List getBookings(Long userId, LocalDateTime now); + + List getBookings(Long userId, LocalDateTime now, PageRequest pageRequest); } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/service/handler/booker/CurrentBookingsHandler.java b/server/src/main/java/ru/practicum/shareit/booking/service/handler/booker/CurrentBookingsHandler.java similarity index 74% rename from src/main/java/ru/practicum/shareit/booking/service/handler/booker/CurrentBookingsHandler.java rename to server/src/main/java/ru/practicum/shareit/booking/service/handler/booker/CurrentBookingsHandler.java index 811a73b..cb82ec4 100644 --- a/src/main/java/ru/practicum/shareit/booking/service/handler/booker/CurrentBookingsHandler.java +++ b/server/src/main/java/ru/practicum/shareit/booking/service/handler/booker/CurrentBookingsHandler.java @@ -2,6 +2,7 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.annotation.Order; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Component; import ru.practicum.shareit.booking.BookingState; import ru.practicum.shareit.booking.model.Booking; @@ -29,4 +30,12 @@ public boolean canHandle(BookingState state) { public List getBookings(Long userId, LocalDateTime now) { return bookingRepository.findAllByBookerIdAndStartBeforeAndEndAfter(userId, now, now); } + + @Override + public List getBookings(Long userId, LocalDateTime now, PageRequest pageRequest) { + if (pageRequest == null) { + return getBookings(userId, now); + } + return bookingRepository.findAllByBookerIdAndStartBeforeAndEndAfter(userId, now, now, pageRequest); + } } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/service/handler/booker/FutureBookingsHandler.java b/server/src/main/java/ru/practicum/shareit/booking/service/handler/booker/FutureBookingsHandler.java similarity index 74% rename from src/main/java/ru/practicum/shareit/booking/service/handler/booker/FutureBookingsHandler.java rename to server/src/main/java/ru/practicum/shareit/booking/service/handler/booker/FutureBookingsHandler.java index 7009733..a1ae58c 100644 --- a/src/main/java/ru/practicum/shareit/booking/service/handler/booker/FutureBookingsHandler.java +++ b/server/src/main/java/ru/practicum/shareit/booking/service/handler/booker/FutureBookingsHandler.java @@ -2,6 +2,7 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.annotation.Order; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Component; import ru.practicum.shareit.booking.BookingState; import ru.practicum.shareit.booking.model.Booking; @@ -29,4 +30,12 @@ public boolean canHandle(BookingState state) { public List getBookings(Long userId, LocalDateTime now) { return bookingRepository.findAllByBookerIdAndStartAfter(userId, now); } + + @Override + public List getBookings(Long userId, LocalDateTime now, PageRequest pageRequest) { + if (pageRequest == null) { + return getBookings(userId, now); + } + return bookingRepository.findAllByBookerIdAndStartAfter(userId, now, pageRequest); + } } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/service/handler/booker/PastBookingsHandler.java b/server/src/main/java/ru/practicum/shareit/booking/service/handler/booker/PastBookingsHandler.java similarity index 74% rename from src/main/java/ru/practicum/shareit/booking/service/handler/booker/PastBookingsHandler.java rename to server/src/main/java/ru/practicum/shareit/booking/service/handler/booker/PastBookingsHandler.java index fc36197..c7728e8 100644 --- a/src/main/java/ru/practicum/shareit/booking/service/handler/booker/PastBookingsHandler.java +++ b/server/src/main/java/ru/practicum/shareit/booking/service/handler/booker/PastBookingsHandler.java @@ -2,6 +2,7 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.annotation.Order; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Component; import ru.practicum.shareit.booking.BookingState; import ru.practicum.shareit.booking.model.Booking; @@ -29,4 +30,12 @@ public boolean canHandle(BookingState state) { public List getBookings(Long userId, LocalDateTime now) { return bookingRepository.findAllByBookerIdAndEndBefore(userId, now); } + + @Override + public List getBookings(Long userId, LocalDateTime now, PageRequest pageRequest) { + if (pageRequest == null) { + return getBookings(userId, now); + } + return bookingRepository.findAllByBookerIdAndEndBefore(userId, now, pageRequest); + } } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/service/handler/booker/RejectedBookingsHandler.java b/server/src/main/java/ru/practicum/shareit/booking/service/handler/booker/RejectedBookingsHandler.java similarity index 75% rename from src/main/java/ru/practicum/shareit/booking/service/handler/booker/RejectedBookingsHandler.java rename to server/src/main/java/ru/practicum/shareit/booking/service/handler/booker/RejectedBookingsHandler.java index 5b51d17..46e5184 100644 --- a/src/main/java/ru/practicum/shareit/booking/service/handler/booker/RejectedBookingsHandler.java +++ b/server/src/main/java/ru/practicum/shareit/booking/service/handler/booker/RejectedBookingsHandler.java @@ -2,6 +2,7 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.annotation.Order; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Component; import ru.practicum.shareit.booking.BookingState; import ru.practicum.shareit.booking.BookingStatus; @@ -30,4 +31,12 @@ public boolean canHandle(BookingState state) { public List getBookings(Long userId, LocalDateTime now) { return bookingRepository.findAllByBookerIdAndStatus(userId, BookingStatus.REJECTED); } + + @Override + public List getBookings(Long userId, LocalDateTime now, PageRequest pageRequest) { + if (pageRequest == null) { + return getBookings(userId, now); + } + return bookingRepository.findAllByBookerIdAndStatus(userId, BookingStatus.REJECTED, pageRequest); + } } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/service/handler/booker/WaitingBookingsHandler.java b/server/src/main/java/ru/practicum/shareit/booking/service/handler/booker/WaitingBookingsHandler.java similarity index 75% rename from src/main/java/ru/practicum/shareit/booking/service/handler/booker/WaitingBookingsHandler.java rename to server/src/main/java/ru/practicum/shareit/booking/service/handler/booker/WaitingBookingsHandler.java index 1f22ace..55537c7 100644 --- a/src/main/java/ru/practicum/shareit/booking/service/handler/booker/WaitingBookingsHandler.java +++ b/server/src/main/java/ru/practicum/shareit/booking/service/handler/booker/WaitingBookingsHandler.java @@ -2,6 +2,7 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.annotation.Order; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Component; import ru.practicum.shareit.booking.BookingState; import ru.practicum.shareit.booking.BookingStatus; @@ -30,4 +31,12 @@ public boolean canHandle(BookingState state) { public List getBookings(Long userId, LocalDateTime now) { return bookingRepository.findAllByBookerIdAndStatus(userId, BookingStatus.WAITING); } + + @Override + public List getBookings(Long userId, LocalDateTime now, PageRequest pageRequest) { + if (pageRequest == null) { + return getBookings(userId, now); + } + return bookingRepository.findAllByBookerIdAndStatus(userId, BookingStatus.WAITING, pageRequest); + } } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerAllBookingsHandler.java b/server/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerAllBookingsHandler.java similarity index 75% rename from src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerAllBookingsHandler.java rename to server/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerAllBookingsHandler.java index 175a8d8..a3c619e 100644 --- a/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerAllBookingsHandler.java +++ b/server/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerAllBookingsHandler.java @@ -2,6 +2,7 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.annotation.Order; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Component; import ru.practicum.shareit.booking.BookingState; import ru.practicum.shareit.booking.model.Booking; @@ -29,4 +30,12 @@ public boolean canHandle(BookingState state) { public List getBookings(Long userId, LocalDateTime now) { return bookingRepository.findAllByItemOwnerId(userId); } + + @Override + public List getBookings(Long userId, LocalDateTime now, PageRequest pageRequest) { + if (pageRequest == null) { + return getBookings(userId, now); + } + return bookingRepository.findAllByItemOwnerId(userId, pageRequest); + } } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerCurrentBookingsHandler.java b/server/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerCurrentBookingsHandler.java similarity index 74% rename from src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerCurrentBookingsHandler.java rename to server/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerCurrentBookingsHandler.java index 2c8445d..52f7f71 100644 --- a/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerCurrentBookingsHandler.java +++ b/server/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerCurrentBookingsHandler.java @@ -2,6 +2,7 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.annotation.Order; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Component; import ru.practicum.shareit.booking.BookingState; import ru.practicum.shareit.booking.model.Booking; @@ -29,4 +30,12 @@ public boolean canHandle(BookingState state) { public List getBookings(Long userId, LocalDateTime now) { return bookingRepository.findAllByItemOwnerIdAndStartBeforeAndEndAfter(userId, now, now); } + + @Override + public List getBookings(Long userId, LocalDateTime now, PageRequest pageRequest) { + if (pageRequest == null) { + return getBookings(userId, now); + } + return bookingRepository.findAllByItemOwnerIdAndStartBeforeAndEndAfter(userId, now, now, pageRequest); + } } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerFutureBookingsHandler.java b/server/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerFutureBookingsHandler.java similarity index 74% rename from src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerFutureBookingsHandler.java rename to server/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerFutureBookingsHandler.java index 9dcdddd..b7cd64b 100644 --- a/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerFutureBookingsHandler.java +++ b/server/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerFutureBookingsHandler.java @@ -2,6 +2,7 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.annotation.Order; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Component; import ru.practicum.shareit.booking.BookingState; import ru.practicum.shareit.booking.model.Booking; @@ -29,4 +30,12 @@ public boolean canHandle(BookingState state) { public List getBookings(Long userId, LocalDateTime now) { return bookingRepository.findAllByItemOwnerIdAndStartAfter(userId, now); } + + @Override + public List getBookings(Long userId, LocalDateTime now, PageRequest pageRequest) { + if (pageRequest == null) { + return getBookings(userId, now); + } + return bookingRepository.findAllByItemOwnerIdAndStartAfter(userId, now, pageRequest); + } } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerPastBookingsHandler.java b/server/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerPastBookingsHandler.java similarity index 74% rename from src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerPastBookingsHandler.java rename to server/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerPastBookingsHandler.java index 668f2d6..74701a3 100644 --- a/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerPastBookingsHandler.java +++ b/server/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerPastBookingsHandler.java @@ -2,6 +2,7 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.annotation.Order; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Component; import ru.practicum.shareit.booking.BookingState; import ru.practicum.shareit.booking.model.Booking; @@ -29,4 +30,12 @@ public boolean canHandle(BookingState state) { public List getBookings(Long userId, LocalDateTime now) { return bookingRepository.findAllByItemOwnerIdAndEndBefore(userId, now); } + + @Override + public List getBookings(Long userId, LocalDateTime now, PageRequest pageRequest) { + if (pageRequest == null) { + return getBookings(userId, now); + } + return bookingRepository.findAllByItemOwnerIdAndEndBefore(userId, now, pageRequest); + } } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerRejectedBookingsHandler.java b/server/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerRejectedBookingsHandler.java similarity index 75% rename from src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerRejectedBookingsHandler.java rename to server/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerRejectedBookingsHandler.java index 6db61cb..4882671 100644 --- a/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerRejectedBookingsHandler.java +++ b/server/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerRejectedBookingsHandler.java @@ -2,6 +2,7 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.annotation.Order; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Component; import ru.practicum.shareit.booking.BookingState; import ru.practicum.shareit.booking.BookingStatus; @@ -30,4 +31,12 @@ public boolean canHandle(BookingState state) { public List getBookings(Long userId, LocalDateTime now) { return bookingRepository.findAllByItemOwnerIdAndStatus(userId, BookingStatus.REJECTED); } + + @Override + public List getBookings(Long userId, LocalDateTime now, PageRequest pageRequest) { + if (pageRequest == null) { + return getBookings(userId, now); + } + return bookingRepository.findAllByItemOwnerIdAndStatus(userId, BookingStatus.REJECTED, pageRequest); + } } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerStateProcessor.java b/server/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerStateProcessor.java similarity index 77% rename from src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerStateProcessor.java rename to server/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerStateProcessor.java index e796add..55bc824 100644 --- a/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerStateProcessor.java +++ b/server/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerStateProcessor.java @@ -1,6 +1,7 @@ package ru.practicum.shareit.booking.service.handler.owner; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import ru.practicum.shareit.booking.BookingState; import ru.practicum.shareit.booking.model.Booking; @@ -18,10 +19,14 @@ public OwnerStateProcessor(@Qualifier("owner") List handler } public List process(BookingState state, Long userId, LocalDateTime now) { + return process(state, userId, now, null); + } + + public List process(BookingState state, Long userId, LocalDateTime now, PageRequest pageRequest) { return handlers.stream() .filter(handler -> handler.canHandle(state)) .findFirst() .orElseThrow(() -> new IllegalArgumentException("Неизвестный статус: " + state)) - .getBookings(userId, now); + .getBookings(userId, now, pageRequest); } } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerWaitingBookingsHandler.java b/server/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerWaitingBookingsHandler.java similarity index 75% rename from src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerWaitingBookingsHandler.java rename to server/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerWaitingBookingsHandler.java index 3ae788a..031abd4 100644 --- a/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerWaitingBookingsHandler.java +++ b/server/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerWaitingBookingsHandler.java @@ -2,6 +2,7 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.annotation.Order; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Component; import ru.practicum.shareit.booking.BookingState; import ru.practicum.shareit.booking.BookingStatus; @@ -30,4 +31,12 @@ public boolean canHandle(BookingState state) { public List getBookings(Long userId, LocalDateTime now) { return bookingRepository.findAllByItemOwnerIdAndStatus(userId, BookingStatus.WAITING); } + + @Override + public List getBookings(Long userId, LocalDateTime now, PageRequest pageRequest) { + if (pageRequest == null) { + return getBookings(userId, now); + } + return bookingRepository.findAllByItemOwnerIdAndStatus(userId, BookingStatus.WAITING, pageRequest); + } } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/CommentMapper.java b/server/src/main/java/ru/practicum/shareit/item/CommentMapper.java similarity index 65% rename from src/main/java/ru/practicum/shareit/item/CommentMapper.java rename to server/src/main/java/ru/practicum/shareit/item/CommentMapper.java index 25c2dc8..cff529b 100644 --- a/src/main/java/ru/practicum/shareit/item/CommentMapper.java +++ b/server/src/main/java/ru/practicum/shareit/item/CommentMapper.java @@ -6,9 +6,9 @@ import ru.practicum.shareit.item.dto.CommentDto; import ru.practicum.shareit.item.model.Comment; -import java.time.LocalDateTime; - -@Mapper(componentModel = "spring", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) +@Mapper(componentModel = "spring", + nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE, + imports = {java.time.LocalDateTime.class}) // Добавляем импорт public interface CommentMapper { @Mapping(target = "authorName", source = "author.name") @@ -17,10 +17,6 @@ public interface CommentMapper { @Mapping(target = "id", ignore = true) @Mapping(target = "author", ignore = true) @Mapping(target = "item", ignore = true) - @Mapping(target = "created", expression = "java(getCurrentTime())") + @Mapping(target = "created", expression = "java(LocalDateTime.now())") Comment toComment(CommentDto commentDto); - - default LocalDateTime getCurrentTime() { - return LocalDateTime.now(); - } } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/Impl/ItemServiceImpl.java b/server/src/main/java/ru/practicum/shareit/item/Impl/ItemServiceImpl.java similarity index 88% rename from src/main/java/ru/practicum/shareit/item/Impl/ItemServiceImpl.java rename to server/src/main/java/ru/practicum/shareit/item/Impl/ItemServiceImpl.java index e292c25..c398b8b 100644 --- a/src/main/java/ru/practicum/shareit/item/Impl/ItemServiceImpl.java +++ b/server/src/main/java/ru/practicum/shareit/item/Impl/ItemServiceImpl.java @@ -1,6 +1,7 @@ package ru.practicum.shareit.item.Impl; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.practicum.shareit.booking.BookingMapper; @@ -16,6 +17,8 @@ import ru.practicum.shareit.item.repository.CommentRepository; import ru.practicum.shareit.item.repository.ItemRepository; import ru.practicum.shareit.item.service.ItemService; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.request.repository.ItemRequestRepository; import ru.practicum.shareit.user.model.User; import ru.practicum.shareit.user.repository.UserRepository; @@ -29,11 +32,13 @@ @Service @RequiredArgsConstructor @Transactional(readOnly = true) +@Slf4j public class ItemServiceImpl implements ItemService { private final ItemRepository itemRepository; private final UserRepository userRepository; private final BookingRepository bookingRepository; private final CommentRepository commentRepository; + private final ItemRequestRepository itemRequestRepository; private final ItemMapper itemMapper; private final CommentMapper commentMapper; private final BookingMapper bookingMapper; @@ -54,6 +59,9 @@ public List getAll(Long userId) { .map(item -> { ItemDto itemDto = itemMapper.toItemDto(item); + itemDto.setLastBooking(null); + itemDto.setNextBooking(null); + if (item.getOwner().getId().equals(userId)) { List lastBookings = bookingRepository.findLastBookingForItem(item.getId(), now); if (!lastBookings.isEmpty()) { @@ -83,8 +91,21 @@ public ItemDto getById(Long id, Long userId) { ItemDto itemDto = itemMapper.toItemDto(item); - LocalDateTime now = LocalDateTime.now(); + if (itemDto.getLastBooking() == null) { + itemDto.setLastBooking(null); + } + if (itemDto.getNextBooking() == null) { + itemDto.setNextBooking(null); + } + + List comments = commentRepository.findByItemId(id); + itemDto.setComments(comments.stream() + .map(commentMapper::toCommentDto) + .collect(Collectors.toList())); + if (item.getOwner().getId().equals(userId)) { + LocalDateTime now = LocalDateTime.now(); + List lastBookings = bookingRepository.findLastBookingForItem(id, now); if (!lastBookings.isEmpty()) { itemDto.setLastBooking(bookingMapper.toBookingShortDto(lastBookings.get(0))); @@ -96,21 +117,23 @@ public ItemDto getById(Long id, Long userId) { } } - List comments = commentRepository.findByItemId(id); - itemDto.setComments(comments.stream() - .map(commentMapper::toCommentDto) - .collect(Collectors.toList())); - return itemDto; } @Override @Transactional public ItemDto create(ItemDto itemDto, Long userId) { - User user = getUserById(userId); // Отдельный метод без транзакции + User user = getUserById(userId); Item item = itemMapper.toItem(itemDto); item.setOwner(user); + if (itemDto.getRequestId() != null) { + ItemRequest request = itemRequestRepository.findById(itemDto.getRequestId()) + .orElseThrow(() -> new ShareItException.NotFoundException( + "Запрос с ID " + itemDto.getRequestId() + " не найден")); + item.setRequest(request); + } + return itemMapper.toItemDto(itemRepository.save(item)); } diff --git a/src/main/java/ru/practicum/shareit/item/ItemMapper.java b/server/src/main/java/ru/practicum/shareit/item/ItemMapper.java similarity index 62% rename from src/main/java/ru/practicum/shareit/item/ItemMapper.java rename to server/src/main/java/ru/practicum/shareit/item/ItemMapper.java index d1e0b94..866f5e9 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemMapper.java +++ b/server/src/main/java/ru/practicum/shareit/item/ItemMapper.java @@ -13,12 +13,17 @@ public interface ItemMapper { @Mapping(target = "request", expression = "java(item.getRequest() != null ? item.getRequest() : null)") - @Mapping(target = "lastBooking", ignore = true) - @Mapping(target = "nextBooking", ignore = true) - @Mapping(target = "comments", ignore = true) + @Mapping(target = "lastBooking", expression = "java(null)") + @Mapping(target = "nextBooking", expression = "java(null)") + @Mapping(target = "comments", expression = "java(new java.util.ArrayList<>())") ItemDto toItemDto(Item item); + @Mapping(target = "owner", ignore = true) + @Mapping(target = "request", ignore = true) Item toItem(ItemDto itemDto); + @Mapping(target = "id", ignore = true) + @Mapping(target = "owner", ignore = true) + @Mapping(target = "request", ignore = true) Item updateItemFields(@MappingTarget Item targetItem, ItemDto sourceItemDto); } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/controller/ItemController.java b/server/src/main/java/ru/practicum/shareit/item/controller/ItemController.java similarity index 73% rename from src/main/java/ru/practicum/shareit/item/controller/ItemController.java rename to server/src/main/java/ru/practicum/shareit/item/controller/ItemController.java index 9d2c2ff..61e79e8 100644 --- a/src/main/java/ru/practicum/shareit/item/controller/ItemController.java +++ b/server/src/main/java/ru/practicum/shareit/item/controller/ItemController.java @@ -1,6 +1,6 @@ package ru.practicum.shareit.item.controller; -import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -14,37 +14,34 @@ import ru.practicum.shareit.item.dto.CommentDto; import ru.practicum.shareit.item.dto.ItemDto; import ru.practicum.shareit.item.service.ItemService; -import ru.practicum.shareit.item.util.Constants; import java.util.List; @RestController @RequestMapping("/items") +@RequiredArgsConstructor public class ItemController { private final ItemService itemService; - - public ItemController(ItemService itemService) { - this.itemService = itemService; - } + private static final String USER_ID_HEADER = "X-Sharer-User-Id"; @GetMapping - public List getAll(@RequestHeader(Constants.USER_ID_HEADER) Long userId) { + public List getAll(@RequestHeader(USER_ID_HEADER) Long userId) { return itemService.getAll(userId); } @GetMapping("/{id}") - public ItemDto getById(@PathVariable Long id, @RequestHeader(Constants.USER_ID_HEADER) Long userId) { + public ItemDto getById(@PathVariable Long id, @RequestHeader(USER_ID_HEADER) Long userId) { return itemService.getById(id, userId); } @PostMapping - public ItemDto create(@RequestHeader(Constants.USER_ID_HEADER) Long userId, @Valid @RequestBody ItemDto itemDto) { + public ItemDto create(@RequestHeader(USER_ID_HEADER) Long userId, @RequestBody ItemDto itemDto) { return itemService.create(itemDto, userId); } @PatchMapping("/{id}") public ItemDto update(@RequestBody ItemDto itemDto, @PathVariable Long id, - @RequestHeader(Constants.USER_ID_HEADER) Long userId) { + @RequestHeader(USER_ID_HEADER) Long userId) { return itemService.update(itemDto, id, userId); } @@ -60,8 +57,8 @@ public List search(@RequestParam String text) { @PostMapping("/{itemId}/comment") public CommentDto createComment(@PathVariable Long itemId, - @Valid @RequestBody CommentDto commentDto, - @RequestHeader(Constants.USER_ID_HEADER) Long userId) { + @RequestBody CommentDto commentDto, + @RequestHeader(USER_ID_HEADER) Long userId) { return itemService.createComment(itemId, commentDto, userId); } } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java b/server/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/dto/CommentDto.java rename to server/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java b/server/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java similarity index 99% rename from src/main/java/ru/practicum/shareit/item/dto/ItemDto.java rename to server/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java index 095dc52..918bbb4 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java +++ b/server/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java @@ -29,7 +29,6 @@ public class ItemDto { private ItemRequest request; private BookingShortDto lastBooking; - private BookingShortDto nextBooking; @Builder.Default diff --git a/src/main/java/ru/practicum/shareit/item/model/Comment.java b/server/src/main/java/ru/practicum/shareit/item/model/Comment.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/model/Comment.java rename to server/src/main/java/ru/practicum/shareit/item/model/Comment.java diff --git a/src/main/java/ru/practicum/shareit/item/model/Item.java b/server/src/main/java/ru/practicum/shareit/item/model/Item.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/model/Item.java rename to server/src/main/java/ru/practicum/shareit/item/model/Item.java diff --git a/src/main/java/ru/practicum/shareit/item/repository/CommentRepository.java b/server/src/main/java/ru/practicum/shareit/item/repository/CommentRepository.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/repository/CommentRepository.java rename to server/src/main/java/ru/practicum/shareit/item/repository/CommentRepository.java diff --git a/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java b/server/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java similarity index 85% rename from src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java rename to server/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java index 5f0abcb..6818d83 100644 --- a/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java +++ b/server/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java @@ -16,4 +16,8 @@ public interface ItemRepository extends JpaRepository { "(LOWER(i.name) LIKE LOWER(CONCAT('%', :text, '%')) OR " + "LOWER(i.description) LIKE LOWER(CONCAT('%', :text, '%')))") List search(@Param("text") String text); + + List findAllByRequestId(Long requestId); + + List findAllByRequestIdIn(List requestIds); } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/service/ItemService.java b/server/src/main/java/ru/practicum/shareit/item/service/ItemService.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/service/ItemService.java rename to server/src/main/java/ru/practicum/shareit/item/service/ItemService.java diff --git a/src/main/java/ru/practicum/shareit/item/util/Constants.java b/server/src/main/java/ru/practicum/shareit/item/util/Constants.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/util/Constants.java rename to server/src/main/java/ru/practicum/shareit/item/util/Constants.java diff --git a/server/src/main/java/ru/practicum/shareit/request/Impl/ItemRequestServiceImpl.java b/server/src/main/java/ru/practicum/shareit/request/Impl/ItemRequestServiceImpl.java new file mode 100644 index 0000000..6668b71 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/Impl/ItemRequestServiceImpl.java @@ -0,0 +1,121 @@ +package ru.practicum.shareit.request.Impl; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.shareit.booking.exception.ShareItException; +import ru.practicum.shareit.item.ItemMapper; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.repository.ItemRepository; +import ru.practicum.shareit.request.ItemRequestMapper; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.request.repository.ItemRequestRepository; +import ru.practicum.shareit.request.service.ItemRequestService; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.repository.UserRepository; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ItemRequestServiceImpl implements ItemRequestService { + private final ItemRequestRepository itemRequestRepository; + private final UserRepository userRepository; + private final ItemRepository itemRepository; + private final ItemRequestMapper itemRequestMapper; + private final ItemMapper itemMapper; + + @Override + @Transactional + public ItemRequestDto create(ItemRequestDto itemRequestDto, Long userId) { + if (itemRequestDto.getDescription() == null || itemRequestDto.getDescription().isBlank()) { + throw new ShareItException.BadRequestException("Описание запроса не может быть пустым"); + } + + User user = userRepository.findById(userId) + .orElseThrow(() -> new ShareItException.NotFoundException("Пользователь не найден")); + + ItemRequest itemRequest = ItemRequest.builder() + .description(itemRequestDto.getDescription()) + .requestor(user) + .created(LocalDateTime.now()) + .build(); + + ItemRequest savedRequest = itemRequestRepository.save(itemRequest); + return itemRequestMapper.toItemRequestDto(savedRequest); + } + + @Override + public List getAllByRequestor(Long userId) { + if (!userRepository.existsById(userId)) { + throw new ShareItException.NotFoundException("Пользователь не найден"); + } + + List requests = itemRequestRepository.findAllByRequestorIdOrderByCreatedDesc(userId); + return getItemRequestDtosWithItems(requests); + } + + @Override + public List getAll(Long userId, int from, int size) { + if (!userRepository.existsById(userId)) { + throw new ShareItException.NotFoundException("Пользователь не найден"); + } + + int page = from / size; + PageRequest pageRequest = PageRequest.of(page, size, Sort.by("created").descending()); + Page requestsPage = itemRequestRepository.findAllByRequestorIdNot(userId, pageRequest); + List requests = requestsPage.getContent(); + + return getItemRequestDtosWithItems(requests); + } + + @Override + public ItemRequestDto getById(Long requestId, Long userId) { + if (!userRepository.existsById(userId)) { + throw new ShareItException.NotFoundException("Пользователь не найден"); + } + + ItemRequest request = itemRequestRepository.findById(requestId) + .orElseThrow(() -> new ShareItException.NotFoundException("Запрос не найден")); + + List items = itemRepository.findAllByRequestId(requestId); + List itemDtos = items.stream() + .map(itemMapper::toItemDto) + .collect(Collectors.toList()); + + ItemRequestDto requestDto = itemRequestMapper.toItemRequestDto(request); + requestDto.setItems(itemDtos); + + return requestDto; + } + + private List getItemRequestDtosWithItems(List requests) { + List requestIds = requests.stream() + .map(ItemRequest::getId) + .collect(Collectors.toList()); + + Map> itemsByRequestId = itemRepository.findAllByRequestIdIn(requestIds).stream() + .collect(Collectors.groupingBy(item -> item.getRequest().getId())); + + return requests.stream() + .map(request -> { + ItemRequestDto dto = itemRequestMapper.toItemRequestDto(request); + List items = itemsByRequestId.getOrDefault(request.getId(), Collections.emptyList()); + dto.setItems(items.stream() + .map(itemMapper::toItemDto) + .collect(Collectors.toList())); + return dto; + }) + .collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/request/ItemRequestMapper.java b/server/src/main/java/ru/practicum/shareit/request/ItemRequestMapper.java new file mode 100644 index 0000000..0524e1f --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/ItemRequestMapper.java @@ -0,0 +1,19 @@ +package ru.practicum.shareit.request; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.NullValuePropertyMappingStrategy; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.user.UserMapper; + +@Mapper(componentModel = "spring", + uses = {UserMapper.class}, + nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) +public interface ItemRequestMapper { + + @Mapping(target = "items", ignore = true) + ItemRequestDto toItemRequestDto(ItemRequest itemRequest); + + ItemRequest toItemRequest(ItemRequestDto itemRequestDto); +} \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/request/controller/ItemRequestController.java b/server/src/main/java/ru/practicum/shareit/request/controller/ItemRequestController.java new file mode 100644 index 0000000..83b91ab --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/controller/ItemRequestController.java @@ -0,0 +1,51 @@ +package ru.practicum.shareit.request.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.service.ItemRequestService; + +import java.util.List; + +@RestController +@RequestMapping(path = "/requests") +@RequiredArgsConstructor +public class ItemRequestController { + private final ItemRequestService itemRequestService; + private static final String USER_ID_HEADER = "X-Sharer-User-Id"; + + @PostMapping + public ItemRequestDto create( + @RequestHeader(USER_ID_HEADER) Long userId, + @RequestBody ItemRequestDto itemRequestDto) { + return itemRequestService.create(itemRequestDto, userId); + } + + @GetMapping + public List getAllByRequestor( + @RequestHeader(USER_ID_HEADER) Long userId) { + return itemRequestService.getAllByRequestor(userId); + } + + @GetMapping("/all") + public List getAll( + @RequestHeader(USER_ID_HEADER) Long userId, + @RequestParam(defaultValue = "0") int from, + @RequestParam(defaultValue = "10") int size) { + return itemRequestService.getAll(userId, from, size); + } + + @GetMapping("/{requestId}") + public ItemRequestDto getById( + @PathVariable Long requestId, + @RequestHeader(USER_ID_HEADER) Long userId) { + return itemRequestService.getById(requestId, userId); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java b/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java rename to server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java diff --git a/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java b/server/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java similarity index 100% rename from src/main/java/ru/practicum/shareit/request/model/ItemRequest.java rename to server/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java diff --git a/server/src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepository.java b/server/src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepository.java new file mode 100644 index 0000000..3f22aa6 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepository.java @@ -0,0 +1,15 @@ +package ru.practicum.shareit.request.repository; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import ru.practicum.shareit.request.model.ItemRequest; + +import java.util.List; + +public interface ItemRequestRepository extends JpaRepository { + + List findAllByRequestorIdOrderByCreatedDesc(Long requestorId); + + Page findAllByRequestorIdNot(Long requestorId, Pageable pageable); +} \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestService.java b/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestService.java new file mode 100644 index 0000000..e7bc0aa --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestService.java @@ -0,0 +1,15 @@ +package ru.practicum.shareit.request.service; + +import ru.practicum.shareit.request.dto.ItemRequestDto; + +import java.util.List; + +public interface ItemRequestService { + ItemRequestDto create(ItemRequestDto itemRequestDto, Long userId); + + List getAllByRequestor(Long userId); + + List getAll(Long userId, int from, int size); + + ItemRequestDto getById(Long requestId, Long userId); +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/Impl/UserServiceImpl.java b/server/src/main/java/ru/practicum/shareit/user/Impl/UserServiceImpl.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/Impl/UserServiceImpl.java rename to server/src/main/java/ru/practicum/shareit/user/Impl/UserServiceImpl.java diff --git a/src/main/java/ru/practicum/shareit/user/UserMapper.java b/server/src/main/java/ru/practicum/shareit/user/UserMapper.java similarity index 78% rename from src/main/java/ru/practicum/shareit/user/UserMapper.java rename to server/src/main/java/ru/practicum/shareit/user/UserMapper.java index 6dcdc52..1db1f76 100644 --- a/src/main/java/ru/practicum/shareit/user/UserMapper.java +++ b/server/src/main/java/ru/practicum/shareit/user/UserMapper.java @@ -7,7 +7,8 @@ import ru.practicum.shareit.user.model.User; -@Mapper(componentModel = "spring", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) +@Mapper(componentModel = "spring", + nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) public interface UserMapper { UserDto toUserDto(User user); diff --git a/src/main/java/ru/practicum/shareit/user/controller/UserController.java b/server/src/main/java/ru/practicum/shareit/user/controller/UserController.java similarity index 81% rename from src/main/java/ru/practicum/shareit/user/controller/UserController.java rename to server/src/main/java/ru/practicum/shareit/user/controller/UserController.java index 09f47e1..68ef8be 100644 --- a/src/main/java/ru/practicum/shareit/user/controller/UserController.java +++ b/server/src/main/java/ru/practicum/shareit/user/controller/UserController.java @@ -1,6 +1,6 @@ package ru.practicum.shareit.user.controller; -import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -9,21 +9,17 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import ru.practicum.shareit.user.Impl.UserServiceImpl; import ru.practicum.shareit.user.dto.UserDto; import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.service.UserService; import java.util.List; - @RestController @RequestMapping(path = "/users") +@RequiredArgsConstructor public class UserController { - private final UserServiceImpl userService; - - public UserController(UserServiceImpl userService) { - this.userService = userService; - } + private final UserService userService; @GetMapping public List getAll() { @@ -36,7 +32,7 @@ public UserDto getById(@PathVariable Long id) { } @PostMapping - public UserDto create(@Valid @RequestBody User user) { + public UserDto create(@RequestBody User user) { return userService.create(user); } diff --git a/src/main/java/ru/practicum/shareit/user/dto/UserDto.java b/server/src/main/java/ru/practicum/shareit/user/dto/UserDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/dto/UserDto.java rename to server/src/main/java/ru/practicum/shareit/user/dto/UserDto.java diff --git a/src/main/java/ru/practicum/shareit/user/model/User.java b/server/src/main/java/ru/practicum/shareit/user/model/User.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/model/User.java rename to server/src/main/java/ru/practicum/shareit/user/model/User.java diff --git a/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java b/server/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/repository/UserRepository.java rename to server/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java diff --git a/src/main/java/ru/practicum/shareit/user/service/UserService.java b/server/src/main/java/ru/practicum/shareit/user/service/UserService.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/service/UserService.java rename to server/src/main/java/ru/practicum/shareit/user/service/UserService.java diff --git a/src/main/resources/application.properties b/server/src/main/resources/application.properties similarity index 61% rename from src/main/resources/application.properties rename to server/src/main/resources/application.properties index 929fb7e..4d67703 100644 --- a/src/main/resources/application.properties +++ b/server/src/main/resources/application.properties @@ -1,13 +1,24 @@ +server.port=9090 + spring.jpa.hibernate.ddl-auto=none spring.jpa.properties.hibernate.format_sql=true spring.sql.init.mode=always -logging.level.org.springframework.orm.jpa=INFO -logging.level.org.springframework.transaction=INFO -logging.level.org.springframework.transaction.interceptor=TRACE -logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG spring.datasource.driverClassName=org.postgresql.Driver spring.datasource.url=jdbc:postgresql://localhost:5432/shareit spring.datasource.username=postgres spring.datasource.password=viktor +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect + +logging.level.org.springframework.orm.jpa=INFO +logging.level.org.springframework.transaction=INFO +logging.level.org.springframework.transaction.interceptor=TRACE +logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG +logging.level.ru.practicum.shareit=DEBUG + +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.use_sql_comments=true -spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect \ No newline at end of file +spring.jackson.serialization.fail-on-empty-beans=false +spring.jackson.serialization.FAIL_ON_EMPTY_BEANS=false +spring.jackson.deserialization.FAIL_ON_UNKNOWN_PROPERTIES=false +spring.jackson.default-property-inclusion=non_null \ No newline at end of file diff --git a/src/main/resources/data.sql b/server/src/main/resources/data.sql similarity index 100% rename from src/main/resources/data.sql rename to server/src/main/resources/data.sql diff --git a/src/main/resources/schema.sql b/server/src/main/resources/schema.sql similarity index 100% rename from src/main/resources/schema.sql rename to server/src/main/resources/schema.sql diff --git a/src/test/java/ru/practicum/shareit/ShareItTests.java b/server/src/test/java/ru/practicum/shareit/ShareItTests.java similarity index 68% rename from src/test/java/ru/practicum/shareit/ShareItTests.java rename to server/src/test/java/ru/practicum/shareit/ShareItTests.java index d53b14f..24a3754 100644 --- a/src/test/java/ru/practicum/shareit/ShareItTests.java +++ b/server/src/test/java/ru/practicum/shareit/ShareItTests.java @@ -3,11 +3,9 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.ContextConfiguration; @SpringBootTest @ActiveProfiles("test") -@ContextConfiguration(classes = {ShareItApp.class, TestConfig.class}) class ShareItTests { @Test diff --git a/src/test/java/ru/practicum/shareit/TestConfig.java b/server/src/test/java/ru/practicum/shareit/TestConfig.java similarity index 100% rename from src/test/java/ru/practicum/shareit/TestConfig.java rename to server/src/test/java/ru/practicum/shareit/TestConfig.java diff --git a/server/src/test/java/ru/practicum/shareit/booking/controller/BookingControllerTest.java b/server/src/test/java/ru/practicum/shareit/booking/controller/BookingControllerTest.java new file mode 100644 index 0000000..0775abd --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/booking/controller/BookingControllerTest.java @@ -0,0 +1,440 @@ +package ru.practicum.shareit.booking.controller; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.shareit.ShareItServerApp; +import ru.practicum.shareit.booking.BookingStatus; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.exception.ShareItException; +import ru.practicum.shareit.booking.service.BookingService; +import ru.practicum.shareit.item.ItemMapper; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.service.ItemService; +import ru.practicum.shareit.user.UserMapper; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.service.UserService; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +@ActiveProfiles("test") +@SpringBootTest(classes = ShareItServerApp.class) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +class BookingControllerTest { + + @Autowired + private BookingController bookingController; + + @MockBean + private BookingService bookingService; + + @Autowired + private ItemService itemService; + + @Autowired + private UserService userService; + + @Autowired + private UserMapper userMapper; + + @Autowired + private ItemMapper itemMapper; + + private UserDto ownerDto; + private UserDto bookerDto; + private ItemDto itemDto; + private BookingDto bookingDto; + + @BeforeEach + void setUp() { + String ownerEmail = "owner" + System.currentTimeMillis() + "@email.com"; + User owner = User.builder() + .name("Item Owner") + .email(ownerEmail) + .build(); + ownerDto = userService.create(owner); + + String bookerEmail = "booker" + System.currentTimeMillis() + "@email.com"; + User booker = User.builder() + .name("Booker User") + .email(bookerEmail) + .build(); + bookerDto = userService.create(booker); + + Item item = Item.builder() + .name("Test Item") + .description("Test Description") + .available(true) + .build(); + itemDto = itemService.create(itemMapper.toItemDto(item), ownerDto.getId()); + + LocalDateTime start = LocalDateTime.now().plusDays(1); + LocalDateTime end = LocalDateTime.now().plusDays(2); + bookingDto = BookingDto.builder() + .itemId(itemDto.getId()) + .start(start) + .end(end) + .build(); + + when(bookingService.create(any(BookingDto.class), anyLong())).thenAnswer(invocation -> { + BookingDto dto = invocation.getArgument(0); + + return BookingDto.builder() + .id(1L) + .start(dto.getStart()) + .end(dto.getEnd()) + .item(itemDto) + .booker(bookerDto) + .status(BookingStatus.WAITING) + .build(); + }); + + when(bookingService.approve(anyLong(), anyLong(), anyBoolean())).thenAnswer(invocation -> { + Long bookingId = invocation.getArgument(0); + Boolean approved = invocation.getArgument(2); + + return BookingDto.builder() + .id(bookingId) + .start(bookingDto.getStart()) + .end(bookingDto.getEnd()) + .item(itemDto) + .booker(bookerDto) + .status(approved ? BookingStatus.APPROVED : BookingStatus.REJECTED) + .build(); + }); + + when(bookingService.getById(anyLong(), anyLong())).thenAnswer(invocation -> { + Long bookingId = invocation.getArgument(0); + + return BookingDto.builder() + .id(bookingId) + .start(bookingDto.getStart()) + .end(bookingDto.getEnd()) + .item(itemDto) + .booker(bookerDto) + .status(BookingStatus.WAITING) + .build(); + }); + + when(bookingService.getAllByBooker(anyLong(), anyString(), anyInt(), anyInt())).thenReturn( + List.of(BookingDto.builder() + .id(1L) + .start(bookingDto.getStart()) + .end(bookingDto.getEnd()) + .item(itemDto) + .booker(bookerDto) + .status(BookingStatus.WAITING) + .build()) + ); + + when(bookingService.getAllByOwner(anyLong(), anyString(), anyInt(), anyInt())).thenReturn( + List.of(BookingDto.builder() + .id(1L) + .start(bookingDto.getStart()) + .end(bookingDto.getEnd()) + .item(itemDto) + .booker(bookerDto) + .status(BookingStatus.WAITING) + .build()) + ); + } + + @Nested // Тесты для создания бронирований + @DisplayName("Creating bookings") + class CreateBookingTests { + @Test + @Transactional + @DisplayName("Successful booking creation") + void createBookingTest() { + BookingDto createdBooking = bookingController.createBooking(bookingDto, bookerDto.getId()); + + assertNotNull(createdBooking); + assertNotNull(createdBooking.getId()); + assertEquals(bookingDto.getStart(), createdBooking.getStart()); + assertEquals(bookingDto.getEnd(), createdBooking.getEnd()); + assertEquals(itemDto.getId(), createdBooking.getItem().getId()); + assertEquals(bookerDto.getId(), createdBooking.getBooker().getId()); + assertEquals(BookingStatus.WAITING, createdBooking.getStatus()); + } + + @Test + @Transactional + @DisplayName("Error when owner tries to book their own item") + void ownerBookingOwnItemTest() { + when(bookingService.create(any(BookingDto.class), eq(ownerDto.getId()))) + .thenThrow(new ShareItException.NotFoundException("Владелец не может забронировать свою вещь")); + + assertThrows(ShareItException.NotFoundException.class, + () -> bookingController.createBooking(bookingDto, ownerDto.getId())); + } + + @Test + @Transactional + @DisplayName("Error when booking unavailable item") + void bookingUnavailableItemTest() { + when(bookingService.create(any(BookingDto.class), eq(bookerDto.getId()))) + .thenThrow(new ShareItException.BadRequestException("Вещь недоступна для бронирования")); + + assertThrows(ShareItException.BadRequestException.class, + () -> bookingController.createBooking(bookingDto, bookerDto.getId())); + } + + @Test + @Transactional + @DisplayName("Error when booking with invalid dates") + void bookingWithInvalidDatesTest() { + BookingDto invalidBooking = BookingDto.builder() + .itemId(itemDto.getId()) + .start(LocalDateTime.now().plusDays(2)) + .end(LocalDateTime.now().plusDays(1)) + .build(); + + when(bookingService.create(eq(invalidBooking), eq(bookerDto.getId()))) + .thenThrow(new ShareItException.BadRequestException("Некорректные даты бронирования")); + + assertThrows(ShareItException.BadRequestException.class, + () -> bookingController.createBooking(invalidBooking, bookerDto.getId())); + } + } + + @Nested // Тесты для утверждения бронирований + @DisplayName("Approving bookings") + class ApproveBookingTests { + @Test + @Transactional + @DisplayName("Successful booking approval") + void approveBookingTest() { + BookingDto createdBooking = bookingController.createBooking(bookingDto, bookerDto.getId()); + + BookingDto approvedBooking = bookingController.approve(createdBooking.getId(), ownerDto.getId(), true); + + assertEquals(BookingStatus.APPROVED, approvedBooking.getStatus()); + } + + @Test + @Transactional + @DisplayName("Successful booking rejection") + void rejectBookingTest() { + BookingDto createdBooking = bookingController.createBooking(bookingDto, bookerDto.getId()); + + BookingDto rejectedBooking = bookingController.approve(createdBooking.getId(), ownerDto.getId(), false); + + assertEquals(BookingStatus.REJECTED, rejectedBooking.getStatus()); + } + + @Test + @Transactional + @DisplayName("Error when non-owner tries to approve booking") + void nonOwnerApprovingBookingTest() { + BookingDto createdBooking = bookingController.createBooking(bookingDto, bookerDto.getId()); + + String randomEmail = "random" + System.currentTimeMillis() + "@email.com"; + User randomUser = User.builder() + .name("Random User") + .email(randomEmail) + .build(); + UserDto randomUserDto = userService.create(randomUser); + + when(bookingService.approve(eq(createdBooking.getId()), eq(randomUserDto.getId()), anyBoolean())) + .thenThrow(new ShareItException.ForbiddenException("Только владелец вещи может подтвердить бронирование")); + + assertThrows(ShareItException.ForbiddenException.class, + () -> bookingController.approve(createdBooking.getId(), randomUserDto.getId(), true)); + } + + @Test + @Transactional + @DisplayName("Error when approving already processed booking") + void approvingProcessedBookingTest() { + BookingDto createdBooking = bookingController.createBooking(bookingDto, bookerDto.getId()); + + bookingController.approve(createdBooking.getId(), ownerDto.getId(), true); + + when(bookingService.approve(eq(createdBooking.getId()), eq(ownerDto.getId()), anyBoolean())) + .thenThrow(new ShareItException.BadRequestException("Бронирование уже обработано")); + + assertThrows(ShareItException.BadRequestException.class, + () -> bookingController.approve(createdBooking.getId(), ownerDto.getId(), true)); + } + } + + @Nested // Тесты для получения бронирований + @DisplayName("Getting bookings") + class GetBookingTests { + @Test + @Transactional + @DisplayName("Getting booking by ID") + void getBookingByIdTest() { + BookingDto createdBooking = bookingController.createBooking(bookingDto, bookerDto.getId()); + + BookingDto retrievedBooking = bookingController.getById(createdBooking.getId(), bookerDto.getId()); + + assertNotNull(retrievedBooking); + assertEquals(createdBooking.getId(), retrievedBooking.getId()); + assertEquals(createdBooking.getStart(), retrievedBooking.getStart()); + assertEquals(createdBooking.getEnd(), retrievedBooking.getEnd()); + } + + @Test + @Transactional + @DisplayName("Error when unauthorized user tries to get booking") + void unauthorizedGetBookingTest() { + BookingDto createdBooking = bookingController.createBooking(bookingDto, bookerDto.getId()); + + String randomEmail = "random" + System.currentTimeMillis() + "@email.com"; + User randomUser = User.builder() + .name("Random User") + .email(randomEmail) + .build(); + UserDto randomUserDto = userService.create(randomUser); + + when(bookingService.getById(eq(createdBooking.getId()), eq(randomUserDto.getId()))) + .thenThrow(new ShareItException.NotFoundException("Доступ запрещен")); + + assertThrows(ShareItException.NotFoundException.class, + () -> bookingController.getById(createdBooking.getId(), randomUserDto.getId())); + } + + @Test + @Transactional + @DisplayName("Getting all bookings by booker") + void getAllBookingsByBookerTest() { + bookingController.createBooking(bookingDto, bookerDto.getId()); + + List bookings = bookingController.getAllByBooker(bookerDto.getId(), "ALL", 0, 10); + + assertNotNull(bookings); + assertFalse(bookings.isEmpty()); + assertEquals(1, bookings.size()); + assertEquals(bookingDto.getStart(), bookings.get(0).getStart()); + assertEquals(bookingDto.getEnd(), bookings.get(0).getEnd()); + } + + @Test + @Transactional + @DisplayName("Getting all bookings by owner") + void getAllBookingsByOwnerTest() { + bookingController.createBooking(bookingDto, bookerDto.getId()); + + List bookings = bookingController.getAllByOwner(ownerDto.getId(), "ALL", 0, 10); + + assertNotNull(bookings); + assertFalse(bookings.isEmpty()); + assertEquals(1, bookings.size()); + assertEquals(bookingDto.getStart(), bookings.get(0).getStart()); + assertEquals(bookingDto.getEnd(), bookings.get(0).getEnd()); + } + + @Test + @Transactional + @DisplayName("Getting bookings with different states") + void getBookingsWithDifferentStatesTest() { + bookingController.createBooking(bookingDto, bookerDto.getId()); + + String[] states = {"CURRENT", "PAST", "FUTURE", "WAITING", "REJECTED"}; + + for (String state : states) { + List bookings = bookingController.getAllByBooker(bookerDto.getId(), state, 0, 10); + + assertNotNull(bookings); + } + } + + @Test + @Transactional + @DisplayName("Error when using invalid state") + void invalidStateTest() { + when(bookingService.getAllByBooker(eq(bookerDto.getId()), eq("INVALID_STATE"), anyInt(), anyInt())) + .thenThrow(new ShareItException.BadRequestException("Unknown state: INVALID_STATE")); + + assertThrows(ShareItException.BadRequestException.class, + () -> bookingController.getAllByBooker(bookerDto.getId(), "INVALID_STATE", 0, 10)); + } + } + + @Nested // Тесты для пагинации + @DisplayName("Pagination tests") + class PaginationTests { + @Test + @Transactional + @DisplayName("Pagination for booker bookings") + void paginationForBookerBookingsTest() { + bookingController.createBooking(bookingDto, bookerDto.getId()); + + List response1 = bookingController.getAllByBooker(bookerDto.getId(), "ALL", 0, 5); + List response2 = bookingController.getAllByBooker(bookerDto.getId(), "ALL", 0, 10); + List response3 = bookingController.getAllByBooker(bookerDto.getId(), "ALL", 5, 5); + + assertNotNull(response1); + assertNotNull(response2); + assertNotNull(response3); + } + + @Test + @Transactional + @DisplayName("Pagination for owner bookings") + void paginationForOwnerBookingsTest() { + bookingController.createBooking(bookingDto, bookerDto.getId()); + + List response1 = bookingController.getAllByOwner(ownerDto.getId(), "ALL", 0, 5); + List response2 = bookingController.getAllByOwner(ownerDto.getId(), "ALL", 0, 10); + List response3 = bookingController.getAllByOwner(ownerDto.getId(), "ALL", 5, 5); + + assertNotNull(response1); + assertNotNull(response2); + assertNotNull(response3); + } + } + + @Nested // Тесты для проверки ошибок + @DisplayName("Error handling tests") + class ErrorHandlingTests { + @Test + @Transactional + @DisplayName("Error when booking not found") + void bookingNotFoundTest() { + Long nonExistentBookingId = 9999L; + + when(bookingService.getById(eq(nonExistentBookingId), anyLong())) + .thenThrow(new ShareItException.NotFoundException("Бронирование не найдено")); + + assertThrows(ShareItException.NotFoundException.class, + () -> bookingController.getById(nonExistentBookingId, bookerDto.getId())); + } + + @Test + @Transactional + @DisplayName("Error when user not found") + void userNotFoundTest() { + Long nonExistentUserId = 9999L; + + when(bookingService.getAllByBooker(eq(nonExistentUserId), anyString(), anyInt(), anyInt())) + .thenThrow(new ShareItException.NotFoundException("Пользователь не найден")); + + assertThrows(ShareItException.NotFoundException.class, + () -> bookingController.getAllByBooker(nonExistentUserId, "ALL", 0, 10)); + } + } +} \ No newline at end of file diff --git a/src/test/java/ru/practicum/shareit/booking/service/BookingServiceImplTest.java b/server/src/test/java/ru/practicum/shareit/booking/service/BookingServiceImplTest.java similarity index 64% rename from src/test/java/ru/practicum/shareit/booking/service/BookingServiceImplTest.java rename to server/src/test/java/ru/practicum/shareit/booking/service/BookingServiceImplTest.java index 88050f9..5f4ac51 100644 --- a/src/test/java/ru/practicum/shareit/booking/service/BookingServiceImplTest.java +++ b/server/src/test/java/ru/practicum/shareit/booking/service/BookingServiceImplTest.java @@ -8,10 +8,9 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.ContextConfiguration; -import ru.practicum.shareit.ShareItApp; -import ru.practicum.shareit.TestConfig; import ru.practicum.shareit.booking.BookingMapper; import ru.practicum.shareit.booking.BookingState; import ru.practicum.shareit.booking.BookingStatus; @@ -38,13 +37,13 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.Mockito.eq; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @ActiveProfiles("test") -@ContextConfiguration(classes = {ShareItApp.class, TestConfig.class}) @ExtendWith(MockitoExtension.class) @DisplayName("Booking Service Tests") class BookingServiceImplTest { @@ -119,6 +118,7 @@ void createBookingSuccessfully() { when(itemRepository.findById(anyLong())).thenReturn(Optional.of(item)); when(bookingRepository.save(any(Booking.class))).thenReturn(booking); when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto); + when(bookingMapper.toBooking(any(BookingDto.class))).thenReturn(booking); BookingDto result = bookingService.create(bookingDto, booker.getId()); @@ -327,101 +327,63 @@ class GetBookingsByBookerTests { @Test @DisplayName("Should get all bookings by booker") void getAllBookingsByBooker() { - when(userRepository.findById(anyLong())).thenReturn(Optional.of(booker)); - when(bookerStateProcessor.process(eq(BookingState.ALL), eq(booker.getId()), any(LocalDateTime.class))) + when(userRepository.existsById(anyLong())).thenReturn(true); + PageRequest pageRequest = PageRequest.of(0, 10, Sort.by("start").descending()); + when(bookerStateProcessor.process(eq(BookingState.ALL), eq(booker.getId()), any(LocalDateTime.class), eq(pageRequest))) .thenReturn(Collections.singletonList(booking)); when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto); - List result = bookingService.getAllByBooker(booker.getId(), "ALL"); + List result = bookingService.getAllByBooker(booker.getId(), "ALL", 0, 10); assertNotNull(result); assertEquals(1, result.size()); assertEquals(bookingDto.getId(), result.get(0).getId()); - verify(bookerStateProcessor).process(eq(BookingState.ALL), eq(booker.getId()), any(LocalDateTime.class)); + verify(bookerStateProcessor).process(eq(BookingState.ALL), eq(booker.getId()), any(LocalDateTime.class), eq(pageRequest)); } @Test - @DisplayName("Should get current bookings by booker") - void getCurrentBookingsByBooker() { - when(userRepository.findById(anyLong())).thenReturn(Optional.of(booker)); - when(bookerStateProcessor.process(eq(BookingState.CURRENT), eq(booker.getId()), any(LocalDateTime.class))) - .thenReturn(Collections.singletonList(booking)); - when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto); - - List result = bookingService.getAllByBooker(booker.getId(), "CURRENT"); - - assertNotNull(result); - assertEquals(1, result.size()); - verify(bookerStateProcessor).process(eq(BookingState.CURRENT), eq(booker.getId()), any(LocalDateTime.class)); - } - - @Test - @DisplayName("Should get past bookings by booker") - void getPastBookingsByBooker() { - when(userRepository.findById(anyLong())).thenReturn(Optional.of(booker)); - when(bookerStateProcessor.process(eq(BookingState.PAST), eq(booker.getId()), any(LocalDateTime.class))) - .thenReturn(Collections.singletonList(booking)); - when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto); - - List result = bookingService.getAllByBooker(booker.getId(), "PAST"); + @DisplayName("Should throw exception when user doesn't exist") + void getAllBookingsByNonExistentBooker() { + when(userRepository.existsById(anyLong())).thenReturn(false); - assertNotNull(result); - assertEquals(1, result.size()); - verify(bookerStateProcessor).process(eq(BookingState.PAST), eq(booker.getId()), any(LocalDateTime.class)); + assertThrows(ShareItException.NotFoundException.class, + () -> bookingService.getAllByBooker(999L, "ALL", 0, 10)); + verify(bookerStateProcessor, never()).process(any(), anyLong(), any(), any()); } @Test - @DisplayName("Should get future bookings by booker") - void getFutureBookingsByBooker() { - when(userRepository.findById(anyLong())).thenReturn(Optional.of(booker)); - when(bookerStateProcessor.process(eq(BookingState.FUTURE), eq(booker.getId()), any(LocalDateTime.class))) - .thenReturn(Collections.singletonList(booking)); - when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto); - - List result = bookingService.getAllByBooker(booker.getId(), "FUTURE"); + @DisplayName("Should throw exception when state is invalid") + void getAllBookingsByBookerWithInvalidState() { + when(userRepository.existsById(anyLong())).thenReturn(true); - assertNotNull(result); - assertEquals(1, result.size()); - verify(bookerStateProcessor).process(eq(BookingState.FUTURE), eq(booker.getId()), any(LocalDateTime.class)); + assertThrows(ShareItException.BadRequestException.class, + () -> bookingService.getAllByBooker(booker.getId(), "INVALID_STATE", 0, 10)); + verify(bookerStateProcessor, never()).process(any(), anyLong(), any(), any()); } @Test - @DisplayName("Should get waiting bookings by booker") - void getWaitingBookingsByBooker() { - when(userRepository.findById(anyLong())).thenReturn(Optional.of(booker)); - when(bookerStateProcessor.process(eq(BookingState.WAITING), eq(booker.getId()), any(LocalDateTime.class))) - .thenReturn(Collections.singletonList(booking)); - when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto); - - List result = bookingService.getAllByBooker(booker.getId(), "WAITING"); + @DisplayName("Should get bookings by booker with different states") + void getBookingsByBookerWithDifferentStates() { + when(userRepository.existsById(anyLong())).thenReturn(true); - assertNotNull(result); - assertEquals(1, result.size()); - verify(bookerStateProcessor).process(eq(BookingState.WAITING), eq(booker.getId()), any(LocalDateTime.class)); - } - - @Test - @DisplayName("Should get rejected bookings by booker") - void getRejectedBookingsByBooker() { - when(userRepository.findById(anyLong())).thenReturn(Optional.of(booker)); - when(bookerStateProcessor.process(eq(BookingState.REJECTED), eq(booker.getId()), any(LocalDateTime.class))) - .thenReturn(Collections.singletonList(booking)); - when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto); + BookingState[] states = BookingState.values(); + for (int i = 0; i < states.length; i++) { + BookingState state = states[i]; + PageRequest pageRequest = PageRequest.of(0, 10, Sort.by("start").descending()); - List result = bookingService.getAllByBooker(booker.getId(), "REJECTED"); + reset(bookerStateProcessor, bookingMapper); - assertNotNull(result); - assertEquals(1, result.size()); - verify(bookerStateProcessor).process(eq(BookingState.REJECTED), eq(booker.getId()), any(LocalDateTime.class)); - } + when(bookerStateProcessor.process(eq(state), eq(booker.getId()), any(LocalDateTime.class), eq(pageRequest))) + .thenReturn(Collections.singletonList(booking)); + when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto); - @Test - @DisplayName("Should throw exception when state is invalid") - void getBookingsByBookerWithInvalidState() { - when(userRepository.findById(anyLong())).thenReturn(Optional.of(booker)); + List result = bookingService.getAllByBooker(booker.getId(), state.name(), 0, 10); - assertThrows(ShareItException.BadRequestException.class, - () -> bookingService.getAllByBooker(booker.getId(), "INVALID_STATE")); + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(bookingDto.getId(), result.get(0).getId()); + verify(bookerStateProcessor).process(eq(state), eq(booker.getId()), any(LocalDateTime.class), eq(pageRequest)); + } } } @@ -431,116 +393,123 @@ class GetBookingsByOwnerTests { @Test @DisplayName("Should get all bookings by owner") void getAllBookingsByOwner() { - when(userRepository.findById(anyLong())).thenReturn(Optional.of(owner)); - when(ownerStateProcessor.process(eq(BookingState.ALL), eq(owner.getId()), any(LocalDateTime.class))) + when(userRepository.existsById(anyLong())).thenReturn(true); + PageRequest pageRequest = PageRequest.of(0, 10, Sort.by("start").descending()); + when(ownerStateProcessor.process(eq(BookingState.ALL), eq(owner.getId()), any(LocalDateTime.class), eq(pageRequest))) .thenReturn(Collections.singletonList(booking)); when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto); - List result = bookingService.getAllByOwner(owner.getId(), "ALL"); + List result = bookingService.getAllByOwner(owner.getId(), "ALL", 0, 10); assertNotNull(result); assertEquals(1, result.size()); assertEquals(bookingDto.getId(), result.get(0).getId()); - verify(ownerStateProcessor).process(eq(BookingState.ALL), eq(owner.getId()), any(LocalDateTime.class)); + verify(ownerStateProcessor).process(eq(BookingState.ALL), eq(owner.getId()), any(LocalDateTime.class), eq(pageRequest)); } @Test - @DisplayName("Should get current bookings by owner") - void getCurrentBookingsByOwner() { - when(userRepository.findById(anyLong())).thenReturn(Optional.of(owner)); - when(ownerStateProcessor.process(eq(BookingState.CURRENT), eq(owner.getId()), any(LocalDateTime.class))) - .thenReturn(Collections.singletonList(booking)); - when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto); - - List result = bookingService.getAllByOwner(owner.getId(), "CURRENT"); + @DisplayName("Should throw exception when user doesn't exist") + void getAllBookingsByNonExistentOwner() { + when(userRepository.existsById(anyLong())).thenReturn(false); - assertNotNull(result); - assertEquals(1, result.size()); - verify(ownerStateProcessor).process(eq(BookingState.CURRENT), eq(owner.getId()), any(LocalDateTime.class)); + assertThrows(ShareItException.NotFoundException.class, + () -> bookingService.getAllByOwner(999L, "ALL", 0, 10)); + verify(ownerStateProcessor, never()).process(any(), anyLong(), any(), any()); } @Test - @DisplayName("Should get past bookings by owner") - void getPastBookingsByOwner() { - when(userRepository.findById(anyLong())).thenReturn(Optional.of(owner)); - when(ownerStateProcessor.process(eq(BookingState.PAST), eq(owner.getId()), any(LocalDateTime.class))) - .thenReturn(Collections.singletonList(booking)); - when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto); - - List result = bookingService.getAllByOwner(owner.getId(), "PAST"); + @DisplayName("Should throw exception when state is invalid") + void getAllBookingsByOwnerWithInvalidState() { + when(userRepository.existsById(anyLong())).thenReturn(true); - assertNotNull(result); - assertEquals(1, result.size()); - verify(ownerStateProcessor).process(eq(BookingState.PAST), eq(owner.getId()), any(LocalDateTime.class)); + assertThrows(ShareItException.BadRequestException.class, + () -> bookingService.getAllByOwner(owner.getId(), "INVALID_STATE", 0, 10)); + verify(ownerStateProcessor, never()).process(any(), anyLong(), any(), any()); } @Test - @DisplayName("Should get future bookings by owner") - void getFutureBookingsByOwner() { - when(userRepository.findById(anyLong())).thenReturn(Optional.of(owner)); - when(ownerStateProcessor.process(eq(BookingState.FUTURE), eq(owner.getId()), any(LocalDateTime.class))) - .thenReturn(Collections.singletonList(booking)); - when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto); + @DisplayName("Should get bookings by owner with different states") + void getBookingsByOwnerWithDifferentStates() { + when(userRepository.existsById(anyLong())).thenReturn(true); - List result = bookingService.getAllByOwner(owner.getId(), "FUTURE"); + BookingState[] states = BookingState.values(); + for (int i = 0; i < states.length; i++) { + BookingState state = states[i]; + PageRequest pageRequest = PageRequest.of(0, 10, Sort.by("start").descending()); - assertNotNull(result); - assertEquals(1, result.size()); - verify(ownerStateProcessor).process(eq(BookingState.FUTURE), eq(owner.getId()), any(LocalDateTime.class)); - } + reset(ownerStateProcessor, bookingMapper); - @Test - @DisplayName("Should get waiting bookings by owner") - void getWaitingBookingsByOwner() { - when(userRepository.findById(anyLong())).thenReturn(Optional.of(owner)); - when(ownerStateProcessor.process(eq(BookingState.WAITING), eq(owner.getId()), any(LocalDateTime.class))) - .thenReturn(Collections.singletonList(booking)); - when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto); + when(ownerStateProcessor.process(eq(state), eq(owner.getId()), any(LocalDateTime.class), eq(pageRequest))) + .thenReturn(Collections.singletonList(booking)); + when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto); - List result = bookingService.getAllByOwner(owner.getId(), "WAITING"); + List result = bookingService.getAllByOwner(owner.getId(), state.name(), 0, 10); - assertNotNull(result); - assertEquals(1, result.size()); - verify(ownerStateProcessor).process(eq(BookingState.WAITING), eq(owner.getId()), any(LocalDateTime.class)); + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(bookingDto.getId(), result.get(0).getId()); + verify(ownerStateProcessor).process(eq(state), eq(owner.getId()), any(LocalDateTime.class), eq(pageRequest)); + } } + } + @Nested // Тесты на пагинацию + @DisplayName("Pagination Tests") + class PaginationTests { @Test - @DisplayName("Should get rejected bookings by owner") - void getRejectedBookingsByOwner() { - when(userRepository.findById(anyLong())).thenReturn(Optional.of(owner)); - when(ownerStateProcessor.process(eq(BookingState.REJECTED), eq(owner.getId()), any(LocalDateTime.class))) - .thenReturn(Collections.singletonList(booking)); - when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto); + @DisplayName("Should handle pagination correctly for booker") + void paginationForBookerBookings() { + when(userRepository.existsById(anyLong())).thenReturn(true); - List result = bookingService.getAllByOwner(owner.getId(), "REJECTED"); + int[][] paginations = {{0, 5}, {0, 10}, {1, 5}, {2, 3}}; - assertNotNull(result); - assertEquals(1, result.size()); - verify(ownerStateProcessor).process(eq(BookingState.REJECTED), eq(owner.getId()), any(LocalDateTime.class)); - } + for (int i = 0; i < paginations.length; i++) { + int[] pagination = paginations[i]; + int from = pagination[0]; + int size = pagination[1]; + int page = from / size; - @Test - @DisplayName("Should throw exception when state is invalid") - void getBookingsByOwnerWithInvalidState() { - when(userRepository.findById(anyLong())).thenReturn(Optional.of(owner)); + reset(bookerStateProcessor, bookingMapper); - assertThrows(ShareItException.BadRequestException.class, - () -> bookingService.getAllByOwner(owner.getId(), "INVALID_STATE")); + PageRequest pageRequest = PageRequest.of(page, size, Sort.by("start").descending()); + when(bookerStateProcessor.process(eq(BookingState.ALL), eq(booker.getId()), any(LocalDateTime.class), eq(pageRequest))) + .thenReturn(Collections.singletonList(booking)); + when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto); + + List result = bookingService.getAllByBooker(booker.getId(), "ALL", from, size); + + assertNotNull(result); + assertEquals(1, result.size()); + verify(bookerStateProcessor).process(eq(BookingState.ALL), eq(booker.getId()), any(LocalDateTime.class), eq(pageRequest)); + } } - } - @Nested // Тесты для получения бронирований пользователя - @DisplayName("User Validation Tests") - class UserValidationTests { @Test - @DisplayName("Should throw exception when user doesn't exist") - void getBookingsByNonExistentUser() { - when(userRepository.findById(anyLong())).thenReturn(Optional.empty()); + @DisplayName("Should handle pagination correctly for owner") + void paginationForOwnerBookings() { + when(userRepository.existsById(anyLong())).thenReturn(true); - assertThrows(ShareItException.NotFoundException.class, - () -> bookingService.getAllByBooker(999L, "ALL")); - assertThrows(ShareItException.NotFoundException.class, - () -> bookingService.getAllByOwner(999L, "ALL")); + int[][] paginations = {{0, 5}, {0, 10}, {1, 5}, {2, 3}}; + + for (int i = 0; i < paginations.length; i++) { + int[] pagination = paginations[i]; + int from = pagination[0]; + int size = pagination[1]; + int page = from / size; + + reset(ownerStateProcessor, bookingMapper); + + PageRequest pageRequest = PageRequest.of(page, size, Sort.by("start").descending()); + when(ownerStateProcessor.process(eq(BookingState.ALL), eq(owner.getId()), any(LocalDateTime.class), eq(pageRequest))) + .thenReturn(Collections.singletonList(booking)); + when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto); + + List result = bookingService.getAllByOwner(owner.getId(), "ALL", from, size); + + assertNotNull(result); + assertEquals(1, result.size()); + verify(ownerStateProcessor).process(eq(BookingState.ALL), eq(owner.getId()), any(LocalDateTime.class), eq(pageRequest)); + } } } } \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/controller/BookingControllerMockMvcTest.java b/server/src/test/java/ru/practicum/shareit/controller/BookingControllerMockMvcTest.java new file mode 100644 index 0000000..720ab85 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/controller/BookingControllerMockMvcTest.java @@ -0,0 +1,145 @@ +package ru.practicum.shareit.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import ru.practicum.shareit.booking.BookingStatus; +import ru.practicum.shareit.booking.controller.BookingController; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.service.BookingService; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.user.dto.UserDto; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; + +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(BookingController.class) +public class BookingControllerMockMvcTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private BookingService bookingService; + + private BookingDto bookingDto; + private final Long userId = 1L; + private final String userIdHeader = "X-Sharer-User-Id"; + private LocalDateTime now; + + @BeforeEach + void setUp() { + now = LocalDateTime.now(); + + ItemDto itemDto = ItemDto.builder() + .id(1L) + .name("Test Item") + .description("Test Description") + .available(true) + .build(); + + UserDto userDto = UserDto.builder() + .id(2L) + .name("Test User") + .email("test@test.com") + .build(); + + bookingDto = BookingDto.builder() + .id(1L) + .start(now.plusDays(1)) + .end(now.plusDays(2)) + .item(itemDto) + .booker(userDto) + .status(BookingStatus.WAITING) + .build(); + } + + @Test // Тест на создание бронирования + @DisplayName("POST /bookings должен создавать новое бронирование") + void createBooking() throws Exception { + when(bookingService.create(any(BookingDto.class), anyLong())).thenReturn(bookingDto); + + mockMvc.perform(post("/bookings") + .header(userIdHeader, userId) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(bookingDto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(bookingDto.getId().intValue()))) + .andExpect(jsonPath("$.status", is(bookingDto.getStatus().toString()))); + } + + @Test // Тест на подтверждение бронирования + @DisplayName("PATCH /bookings/{bookingId} должен подтверждать бронирование") + void approveBooking() throws Exception { + BookingDto approvedBooking = BookingDto.builder() + .id(bookingDto.getId()) + .start(bookingDto.getStart()) + .end(bookingDto.getEnd()) + .item(bookingDto.getItem()) + .booker(bookingDto.getBooker()) + .status(BookingStatus.APPROVED) + .build(); + + when(bookingService.approve(anyLong(), anyLong(), anyBoolean())).thenReturn(approvedBooking); + + mockMvc.perform(patch("/bookings/1") + .header(userIdHeader, userId) + .param("approved", "true")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(approvedBooking.getId().intValue()))) + .andExpect(jsonPath("$.status", is(approvedBooking.getStatus().toString()))); + } + + @Test // Тест на получение бронирования по ID + @DisplayName("GET /bookings/{bookingId} должен возвращать бронирование по ID") + void getBookingById() throws Exception { + when(bookingService.getById(anyLong(), anyLong())).thenReturn(bookingDto); + + mockMvc.perform(get("/bookings/1") + .header(userIdHeader, userId)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(bookingDto.getId().intValue()))) + .andExpect(jsonPath("$.status", is(bookingDto.getStatus().toString()))); + } + + @Test // Тест на получение бронирований владельца + @DisplayName("GET /bookings/owner должен возвращать бронирования владельца") + void getAllBookingsByOwner() throws Exception { + List bookings = Arrays.asList(bookingDto); + when(bookingService.getAllByOwner(anyLong(), anyString(), anyInt(), anyInt())).thenReturn(bookings); + + mockMvc.perform(get("/bookings/owner") + .header(userIdHeader, userId) + .param("state", "ALL") + .param("from", "0") + .param("size", "10")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(1))) + .andExpect(jsonPath("$[0].id", is(bookingDto.getId().intValue()))) + .andExpect(jsonPath("$[0].status", is(bookingDto.getStatus().toString()))); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/controller/ItemControllerMockMvcTest.java b/server/src/test/java/ru/practicum/shareit/controller/ItemControllerMockMvcTest.java new file mode 100644 index 0000000..65c875f --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/controller/ItemControllerMockMvcTest.java @@ -0,0 +1,173 @@ +package ru.practicum.shareit.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import ru.practicum.shareit.item.controller.ItemController; +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.service.ItemService; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(ItemController.class) +public class ItemControllerMockMvcTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private ItemService itemService; + + private ItemDto itemDto; + private CommentDto commentDto; + private final Long userId = 1L; + private final String userIdHeader = "X-Sharer-User-Id"; + + @BeforeEach + void setUp() { + itemDto = ItemDto.builder() + .id(1L) + .name("Test Item") + .description("Test Description") + .available(true) + .build(); + + commentDto = CommentDto.builder() + .id(1L) + .text("Test Comment") + .authorName("Author") + .created(LocalDateTime.now()) + .build(); + } + + @Test // Тест на получение всех вещей + @DisplayName("GET /items должен возвращать список вещей пользователя") + void getAllItems() throws Exception { + List items = Arrays.asList(itemDto); + when(itemService.getAll(anyLong())).thenReturn(items); + + mockMvc.perform(get("/items") + .header(userIdHeader, userId)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(1))) + .andExpect(jsonPath("$[0].id", is(itemDto.getId().intValue()))) + .andExpect(jsonPath("$[0].name", is(itemDto.getName()))) + .andExpect(jsonPath("$[0].description", is(itemDto.getDescription()))) + .andExpect(jsonPath("$[0].available", is(itemDto.getAvailable()))); + } + + @Test // Тест на получение вещи по ID + @DisplayName("GET /items/{id} должен возвращать вещь по ID") + void getItemById() throws Exception { + when(itemService.getById(anyLong(), anyLong())).thenReturn(itemDto); + + mockMvc.perform(get("/items/1") + .header(userIdHeader, userId)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(itemDto.getId().intValue()))) + .andExpect(jsonPath("$.name", is(itemDto.getName()))) + .andExpect(jsonPath("$.description", is(itemDto.getDescription()))) + .andExpect(jsonPath("$.available", is(itemDto.getAvailable()))); + } + + @Test // Тест на создание вещи + @DisplayName("POST /items должен создавать новую вещь") + void createItem() throws Exception { + when(itemService.create(any(ItemDto.class), anyLong())).thenReturn(itemDto); + + mockMvc.perform(post("/items") + .header(userIdHeader, userId) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(itemDto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(itemDto.getId().intValue()))) + .andExpect(jsonPath("$.name", is(itemDto.getName()))) + .andExpect(jsonPath("$.description", is(itemDto.getDescription()))) + .andExpect(jsonPath("$.available", is(itemDto.getAvailable()))); + } + + @Test // Тест на обновление вещи + @DisplayName("PATCH /items/{id} должен обновлять вещь") + void updateItem() throws Exception { + ItemDto updatedItem = ItemDto.builder() + .id(1L) + .name("Updated Item") + .description("Updated Description") + .available(true) + .build(); + + when(itemService.update(any(ItemDto.class), anyLong(), anyLong())).thenReturn(updatedItem); + + mockMvc.perform(patch("/items/1") + .header(userIdHeader, userId) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(updatedItem))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(updatedItem.getId().intValue()))) + .andExpect(jsonPath("$.name", is(updatedItem.getName()))) + .andExpect(jsonPath("$.description", is(updatedItem.getDescription()))) + .andExpect(jsonPath("$.available", is(updatedItem.getAvailable()))); + } + + @Test // Тест на удаление вещи + @DisplayName("DELETE /items/{id} должен удалять вещь") + void deleteItem() throws Exception { + mockMvc.perform(delete("/items/1") + .header(userIdHeader, userId)) + .andExpect(status().isOk()); + } + + @Test // Тест на поиск вещей + @DisplayName("GET /items/search должен искать вещи по тексту") + void searchItems() throws Exception { + when(itemService.search(anyString())).thenReturn(Collections.singletonList(itemDto)); + + mockMvc.perform(get("/items/search") + .param("text", "test")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(1))) + .andExpect(jsonPath("$[0].id", is(itemDto.getId().intValue()))) + .andExpect(jsonPath("$[0].name", is(itemDto.getName()))); + } + + @Test // Тест на создание комментария + @DisplayName("POST /items/{itemId}/comment должен создавать комментарий") + void createComment() throws Exception { + when(itemService.createComment(anyLong(), any(CommentDto.class), anyLong())).thenReturn(commentDto); + + mockMvc.perform(post("/items/1/comment") + .header(userIdHeader, userId) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(commentDto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(commentDto.getId().intValue()))) + .andExpect(jsonPath("$.text", is(commentDto.getText()))) + .andExpect(jsonPath("$.authorName", is(commentDto.getAuthorName()))); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/controller/ItemRequestControllerMockMvcTest.java b/server/src/test/java/ru/practicum/shareit/controller/ItemRequestControllerMockMvcTest.java new file mode 100644 index 0000000..59a2c32 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/controller/ItemRequestControllerMockMvcTest.java @@ -0,0 +1,121 @@ +package ru.practicum.shareit.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import ru.practicum.shareit.request.controller.ItemRequestController; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.service.ItemRequestService; +import ru.practicum.shareit.user.dto.UserDto; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(ItemRequestController.class) +public class ItemRequestControllerMockMvcTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private ItemRequestService itemRequestService; + + private ItemRequestDto itemRequestDto; + private final Long userId = 1L; + private final String userIdHeader = "X-Sharer-User-Id"; + + @BeforeEach + void setUp() { + UserDto requestor = UserDto.builder() + .id(userId) + .name("Requestor") + .email("requestor@test.com") + .build(); + + itemRequestDto = ItemRequestDto.builder() + .id(1L) + .description("Test Request") + .requestor(requestor) + .created(LocalDateTime.now()) + .items(Collections.emptyList()) + .build(); + } + + @Test // Тест на создание запроса + @DisplayName("POST /requests должен создавать новый запрос") + void createRequest() throws Exception { + when(itemRequestService.create(any(ItemRequestDto.class), anyLong())).thenReturn(itemRequestDto); + + mockMvc.perform(post("/requests") + .header(userIdHeader, userId) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(itemRequestDto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(itemRequestDto.getId().intValue()))) + .andExpect(jsonPath("$.description", is(itemRequestDto.getDescription()))); + } + + @Test // Тест на получение запросов пользователя + @DisplayName("GET /requests должен возвращать запросы пользователя") + void getAllRequestsByRequestor() throws Exception { + List requests = Arrays.asList(itemRequestDto); + when(itemRequestService.getAllByRequestor(anyLong())).thenReturn(requests); + + mockMvc.perform(get("/requests") + .header(userIdHeader, userId)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(1))) + .andExpect(jsonPath("$[0].id", is(itemRequestDto.getId().intValue()))) + .andExpect(jsonPath("$[0].description", is(itemRequestDto.getDescription()))); + } + + @Test // Тест на получение всех запросов + @DisplayName("GET /requests/all должен возвращать все запросы") + void getAllRequests() throws Exception { + List requests = Arrays.asList(itemRequestDto); + when(itemRequestService.getAll(anyLong(), anyInt(), anyInt())).thenReturn(requests); + + mockMvc.perform(get("/requests/all") + .header(userIdHeader, userId) + .param("from", "0") + .param("size", "10")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(1))) + .andExpect(jsonPath("$[0].id", is(itemRequestDto.getId().intValue()))) + .andExpect(jsonPath("$[0].description", is(itemRequestDto.getDescription()))); + } + + @Test // Тест на получение запроса по ID + @DisplayName("GET /requests/{requestId} должен возвращать запрос по ID") + void getRequestById() throws Exception { + when(itemRequestService.getById(anyLong(), anyLong())).thenReturn(itemRequestDto); + + mockMvc.perform(get("/requests/1") + .header(userIdHeader, userId)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(itemRequestDto.getId().intValue()))) + .andExpect(jsonPath("$.description", is(itemRequestDto.getDescription()))); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/controller/UserControllerMockMvcTest.java b/server/src/test/java/ru/practicum/shareit/controller/UserControllerMockMvcTest.java new file mode 100644 index 0000000..2fa57cd --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/controller/UserControllerMockMvcTest.java @@ -0,0 +1,133 @@ +package ru.practicum.shareit.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import ru.practicum.shareit.user.controller.UserController; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.service.UserService; + +import java.util.Arrays; +import java.util.List; + +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(UserController.class) +public class UserControllerMockMvcTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private UserService userService; + + private UserDto userDto; + private User user; + + @BeforeEach + void setUp() { + userDto = UserDto.builder() + .id(1L) + .name("Test User") + .email("test@test.com") + .build(); + + user = User.builder() + .id(1L) + .name("Test User") + .email("test@test.com") + .build(); + } + + @Test // Тест на получение списка пользователей + @DisplayName("GET /users должен возвращать список пользователей") + void getAllUsers() throws Exception { + List users = Arrays.asList(userDto); + when(userService.getAll()).thenReturn(users); + + mockMvc.perform(get("/users")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(1))) + .andExpect(jsonPath("$[0].id", is(userDto.getId().intValue()))) + .andExpect(jsonPath("$[0].name", is(userDto.getName()))) + .andExpect(jsonPath("$[0].email", is(userDto.getEmail()))); + } + + @Test // Тест на получение пользователя по ID + @DisplayName("GET /users/{id} должен возвращать пользователя по ID") + void getUserById() throws Exception { + when(userService.getById(anyLong())).thenReturn(userDto); + + mockMvc.perform(get("/users/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(userDto.getId().intValue()))) + .andExpect(jsonPath("$.name", is(userDto.getName()))) + .andExpect(jsonPath("$.email", is(userDto.getEmail()))); + } + + @Test // Тест на создание пользователя + @DisplayName("POST /users должен создавать нового пользователя") + void createUser() throws Exception { + when(userService.create(any(User.class))).thenReturn(userDto); + + mockMvc.perform(post("/users") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(user))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(userDto.getId().intValue()))) + .andExpect(jsonPath("$.name", is(userDto.getName()))) + .andExpect(jsonPath("$.email", is(userDto.getEmail()))); + } + + @Test // Тест на обновление пользователя + @DisplayName("PATCH /users/{id} должен обновлять пользователя") + void updateUser() throws Exception { + UserDto updatedUserDto = UserDto.builder() + .id(1L) + .name("Updated User") + .email("updated@test.com") + .build(); + + User updatedUser = User.builder() + .name("Updated User") + .email("updated@test.com") + .build(); + + when(userService.update(any(User.class), anyLong())).thenReturn(updatedUserDto); + + mockMvc.perform(patch("/users/1") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(updatedUser))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(updatedUserDto.getId().intValue()))) + .andExpect(jsonPath("$.name", is(updatedUserDto.getName()))) + .andExpect(jsonPath("$.email", is(updatedUserDto.getEmail()))); + } + + @Test // Тест на удаление пользователя + @DisplayName("DELETE /users/{id} должен удалять пользователя") + void deleteUser() throws Exception { + mockMvc.perform(delete("/users/1")) + .andExpect(status().isOk()); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/dto/BookingDtoJsonTest.java b/server/src/test/java/ru/practicum/shareit/dto/BookingDtoJsonTest.java new file mode 100644 index 0000000..6e50710 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/dto/BookingDtoJsonTest.java @@ -0,0 +1,77 @@ +package ru.practicum.shareit.dto; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.json.JsonTest; +import org.springframework.boot.test.json.JacksonTester; +import org.springframework.boot.test.json.JsonContent; +import ru.practicum.shareit.booking.BookingStatus; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.user.dto.UserDto; + +import java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.assertThat; + +@JsonTest +public class BookingDtoJsonTest { + + @Autowired + private JacksonTester json; + + @Test // Тест сериализации + @DisplayName("Тест сериализации BookingDto в JSON") + void testBookingDtoSerialization() throws Exception { + LocalDateTime start = LocalDateTime.of(2023, 1, 1, 12, 0); + LocalDateTime end = LocalDateTime.of(2023, 1, 2, 12, 0); + + ItemDto itemDto = ItemDto.builder() + .id(1L) + .name("Test Item") + .build(); + + UserDto userDto = UserDto.builder() + .id(2L) + .name("Test User") + .build(); + + BookingDto bookingDto = BookingDto.builder() + .id(1L) + .start(start) + .end(end) + .item(itemDto) + .booker(userDto) + .status(BookingStatus.WAITING) + .build(); + + JsonContent result = json.write(bookingDto); + + assertThat(result).extractingJsonPathNumberValue("$.id").isEqualTo(1); + assertThat(result).extractingJsonPathStringValue("$.start").isNotEmpty(); + assertThat(result).extractingJsonPathStringValue("$.end").isNotEmpty(); + assertThat(result).extractingJsonPathNumberValue("$.item.id").isEqualTo(1); + assertThat(result).extractingJsonPathStringValue("$.item.name").isEqualTo("Test Item"); + assertThat(result).extractingJsonPathNumberValue("$.booker.id").isEqualTo(2); + assertThat(result).extractingJsonPathStringValue("$.booker.name").isEqualTo("Test User"); + assertThat(result).extractingJsonPathStringValue("$.status").isEqualTo("WAITING"); + } + + @Test // Тест десериализации + @DisplayName("Тест десериализации JSON в BookingDto") + void testBookingDtoDeserialization() throws Exception { + String jsonContent = "{\"id\":1,\"start\":\"2023-01-01T12:00:00\",\"end\":\"2023-01-02T12:00:00\",\"item\":{\"id\":1,\"name\":\"Test Item\"},\"booker\":{\"id\":2,\"name\":\"Test User\"},\"status\":\"WAITING\"}"; + + BookingDto bookingDto = json.parse(jsonContent).getObject(); + + assertThat(bookingDto.getId()).isEqualTo(1L); + assertThat(bookingDto.getStart()).isEqualTo(LocalDateTime.of(2023, 1, 1, 12, 0, 0)); + assertThat(bookingDto.getEnd()).isEqualTo(LocalDateTime.of(2023, 1, 2, 12, 0, 0)); + assertThat(bookingDto.getItem().getId()).isEqualTo(1L); + assertThat(bookingDto.getItem().getName()).isEqualTo("Test Item"); + assertThat(bookingDto.getBooker().getId()).isEqualTo(2L); + assertThat(bookingDto.getBooker().getName()).isEqualTo("Test User"); + assertThat(bookingDto.getStatus()).isEqualTo(BookingStatus.WAITING); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/dto/CommentDtoJsonTest.java b/server/src/test/java/ru/practicum/shareit/dto/CommentDtoJsonTest.java new file mode 100644 index 0000000..a245289 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/dto/CommentDtoJsonTest.java @@ -0,0 +1,53 @@ +package ru.practicum.shareit.dto; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.json.JsonTest; +import org.springframework.boot.test.json.JacksonTester; +import org.springframework.boot.test.json.JsonContent; +import ru.practicum.shareit.item.dto.CommentDto; + +import java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.assertThat; + +@JsonTest +public class CommentDtoJsonTest { + + @Autowired + private JacksonTester json; + + @Test // Тест сериализации + @DisplayName("Тест сериализации CommentDto в JSON") + void testCommentDtoSerialization() throws Exception { + LocalDateTime created = LocalDateTime.of(2023, 1, 1, 12, 0); + + CommentDto commentDto = CommentDto.builder() + .id(1L) + .text("Test Comment") + .authorName("Author") + .created(created) + .build(); + + JsonContent result = json.write(commentDto); + + assertThat(result).extractingJsonPathNumberValue("$.id").isEqualTo(1); + assertThat(result).extractingJsonPathStringValue("$.text").isEqualTo("Test Comment"); + assertThat(result).extractingJsonPathStringValue("$.authorName").isEqualTo("Author"); + assertThat(result).extractingJsonPathStringValue("$.created").isNotEmpty(); + } + + @Test // Тест десериализации + @DisplayName("Тест десериализации JSON в CommentDto") + void testCommentDtoDeserialization() throws Exception { + String jsonContent = "{\"id\":1,\"text\":\"Test Comment\",\"authorName\":\"Author\",\"created\":\"2023-01-01T12:00:00\"}"; + + CommentDto commentDto = json.parse(jsonContent).getObject(); + + assertThat(commentDto.getId()).isEqualTo(1L); + assertThat(commentDto.getText()).isEqualTo("Test Comment"); + assertThat(commentDto.getAuthorName()).isEqualTo("Author"); + assertThat(commentDto.getCreated()).isEqualTo(LocalDateTime.of(2023, 1, 1, 12, 0, 0)); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/dto/ItemDtoJsonTest.java b/server/src/test/java/ru/practicum/shareit/dto/ItemDtoJsonTest.java new file mode 100644 index 0000000..aae0b78 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/dto/ItemDtoJsonTest.java @@ -0,0 +1,91 @@ +package ru.practicum.shareit.dto; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.json.JsonTest; +import org.springframework.boot.test.json.JacksonTester; +import org.springframework.boot.test.json.JsonContent; +import ru.practicum.shareit.booking.dto.BookingShortDto; +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.dto.ItemDto; + +import java.time.LocalDateTime; +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; + +@JsonTest +public class ItemDtoJsonTest { + + @Autowired + private JacksonTester json; + + @Test // Тест сериализации JSON + @DisplayName("Тест сериализации ItemDto в JSON") + void testItemDtoSerialization() throws Exception { + LocalDateTime now = LocalDateTime.now(); + + BookingShortDto lastBooking = BookingShortDto.builder() + .id(1L) + .bookerId(2L) + .start(now.minusDays(2)) + .end(now.minusDays(1)) + .build(); + + BookingShortDto nextBooking = BookingShortDto.builder() + .id(2L) + .bookerId(3L) + .start(now.plusDays(1)) + .end(now.plusDays(2)) + .build(); + + CommentDto comment = CommentDto.builder() + .id(1L) + .text("Test Comment") + .authorName("Author") + .created(now) + .build(); + + ItemDto itemDto = ItemDto.builder() + .id(1L) + .name("Test Item") + .description("Test Description") + .available(true) + .lastBooking(lastBooking) + .nextBooking(nextBooking) + .comments(Arrays.asList(comment)) + .build(); + + JsonContent result = json.write(itemDto); + + assertThat(result).extractingJsonPathNumberValue("$.id").isEqualTo(1); + assertThat(result).extractingJsonPathStringValue("$.name").isEqualTo("Test Item"); + assertThat(result).extractingJsonPathStringValue("$.description").isEqualTo("Test Description"); + assertThat(result).extractingJsonPathBooleanValue("$.available").isEqualTo(true); + assertThat(result).extractingJsonPathNumberValue("$.lastBooking.id").isEqualTo(1); + assertThat(result).extractingJsonPathNumberValue("$.nextBooking.id").isEqualTo(2); + assertThat(result).extractingJsonPathArrayValue("$.comments").hasSize(1); + assertThat(result).extractingJsonPathStringValue("$.comments[0].text").isEqualTo("Test Comment"); + } + + @Test // Тест десериализации JSON + @DisplayName("Тест десериализации JSON в ItemDto") + void testItemDtoDeserialization() throws Exception { + String jsonContent = "{\"id\":1,\"name\":\"Test Item\",\"description\":\"Test Description\",\"available\":true,\"lastBooking\":{\"id\":1,\"bookerId\":2},\"nextBooking\":{\"id\":2,\"bookerId\":3},\"comments\":[{\"id\":1,\"text\":\"Test Comment\",\"authorName\":\"Author\"}]}"; + + ItemDto itemDto = json.parse(jsonContent).getObject(); + + assertThat(itemDto.getId()).isEqualTo(1L); + assertThat(itemDto.getName()).isEqualTo("Test Item"); + assertThat(itemDto.getDescription()).isEqualTo("Test Description"); + assertThat(itemDto.getAvailable()).isEqualTo(true); + assertThat(itemDto.getLastBooking().getId()).isEqualTo(1L); + assertThat(itemDto.getLastBooking().getBookerId()).isEqualTo(2L); + assertThat(itemDto.getNextBooking().getId()).isEqualTo(2L); + assertThat(itemDto.getNextBooking().getBookerId()).isEqualTo(3L); + assertThat(itemDto.getComments()).hasSize(1); + assertThat(itemDto.getComments().get(0).getText()).isEqualTo("Test Comment"); + assertThat(itemDto.getComments().get(0).getAuthorName()).isEqualTo("Author"); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/integration/BookingServiceIntegrationTest.java b/server/src/test/java/ru/practicum/shareit/integration/BookingServiceIntegrationTest.java new file mode 100644 index 0000000..20807ed --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/integration/BookingServiceIntegrationTest.java @@ -0,0 +1,119 @@ +package ru.practicum.shareit.integration; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.shareit.booking.BookingStatus; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.service.BookingService; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.service.ItemService; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.service.UserService; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest +@Transactional +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +public class BookingServiceIntegrationTest { + + @Autowired + private BookingService bookingService; + + @Autowired + private ItemService itemService; + + @Autowired + private UserService userService; + + private UserDto owner; + private UserDto booker; + private ItemDto item; + private LocalDateTime now; + + @BeforeEach + void setUp() { + now = LocalDateTime.now(); + + User ownerUser = User.builder() + .name("Owner") + .email("owner" + System.currentTimeMillis() + "@test.com") + .build(); + owner = userService.create(ownerUser); + + User bookerUser = User.builder() + .name("Booker") + .email("booker" + System.currentTimeMillis() + "@test.com") + .build(); + booker = userService.create(bookerUser); + + ItemDto itemDto = ItemDto.builder() + .name("Test Item") + .description("Test Description") + .available(true) + .build(); + item = itemService.create(itemDto, owner.getId()); + } + + @Test // Интеграционный тест: создание и получение бронирования + @DisplayName("Интеграционный тест: создание и получение бронирования") + void createAndGetBookingIntegrationTest() { + BookingDto bookingDto = BookingDto.builder() + .itemId(item.getId()) + .start(now.plusDays(1)) + .end(now.plusDays(2)) + .build(); + + BookingDto createdBooking = bookingService.create(bookingDto, booker.getId()); + + assertNotNull(createdBooking); + assertNotNull(createdBooking.getId()); + assertEquals(BookingStatus.WAITING, createdBooking.getStatus()); + assertEquals(item.getId(), createdBooking.getItem().getId()); + assertEquals(booker.getId(), createdBooking.getBooker().getId()); + + BookingDto retrievedBooking = bookingService.getById(createdBooking.getId(), booker.getId()); + + assertNotNull(retrievedBooking); + assertEquals(createdBooking.getId(), retrievedBooking.getId()); + assertEquals(createdBooking.getStatus(), retrievedBooking.getStatus()); + assertEquals(createdBooking.getItem().getId(), retrievedBooking.getItem().getId()); + assertEquals(createdBooking.getBooker().getId(), retrievedBooking.getBooker().getId()); + } + + @Test // Интеграционный тест: получение бронирований по арендатору + @DisplayName("Интеграционный тест: получение бронирований по арендатору") + void getBookingsByBookerIntegrationTest() { + BookingDto booking1 = BookingDto.builder() + .itemId(item.getId()) + .start(now.plusDays(1)) + .end(now.plusDays(2)) + .build(); + BookingDto booking2 = BookingDto.builder() + .itemId(item.getId()) + .start(now.plusDays(3)) + .end(now.plusDays(4)) + .build(); + + BookingDto createdBooking1 = bookingService.create(booking1, booker.getId()); + BookingDto createdBooking2 = bookingService.create(booking2, booker.getId()); + + List bookings = bookingService.getAllByBooker(booker.getId(), "ALL", 0, 10); + + assertNotNull(bookings); + assertEquals(2, bookings.size()); + assertTrue(bookings.stream().anyMatch(b -> b.getId().equals(createdBooking1.getId()))); + assertTrue(bookings.stream().anyMatch(b -> b.getId().equals(createdBooking2.getId()))); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/integration/ItemServiceIntegrationTest.java b/server/src/test/java/ru/practicum/shareit/integration/ItemServiceIntegrationTest.java new file mode 100644 index 0000000..42d61bf --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/integration/ItemServiceIntegrationTest.java @@ -0,0 +1,97 @@ +package ru.practicum.shareit.integration; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.service.ItemService; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.service.UserService; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest +@Transactional +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +public class ItemServiceIntegrationTest { + + @Autowired + private ItemService itemService; + + @Autowired + private UserService userService; + + private UserDto owner; + private UserDto user; + + @BeforeEach + void setUp() { + User ownerUser = User.builder() + .name("Owner") + .email("owner" + System.currentTimeMillis() + "@test.com") + .build(); + owner = userService.create(ownerUser); + + User regularUser = User.builder() + .name("User") + .email("user" + System.currentTimeMillis() + "@test.com") + .build(); + user = userService.create(regularUser); + } + + @Test // Интеграционный тест: получение всех вещей пользователя владельца + @DisplayName("Интеграционный тест: получение всех вещей пользователя") + void getUserItemsIntegrationTest() { + ItemDto item1 = ItemDto.builder() + .name("Item 1") + .description("Description 1") + .available(true) + .build(); + ItemDto item2 = ItemDto.builder() + .name("Item 2") + .description("Description 2") + .available(true) + .build(); + ItemDto item3 = ItemDto.builder() + .name("Item 3") + .description("Description 3") + .available(false) + .build(); + + itemService.create(item1, owner.getId()); + itemService.create(item2, owner.getId()); + itemService.create(item3, owner.getId()); + + ItemDto userItem = ItemDto.builder() + .name("User Item") + .description("User Item Description") + .available(true) + .build(); + itemService.create(userItem, user.getId()); + + List ownerItems = itemService.getAll(owner.getId()); + + assertNotNull(ownerItems); + assertEquals(3, ownerItems.size()); + assertTrue(ownerItems.stream().anyMatch(item -> item.getName().equals("Item 1"))); + assertTrue(ownerItems.stream().anyMatch(item -> item.getName().equals("Item 2"))); + assertTrue(ownerItems.stream().anyMatch(item -> item.getName().equals("Item 3"))); + assertFalse(ownerItems.stream().anyMatch(item -> item.getName().equals("User Item"))); + + List userItems = itemService.getAll(user.getId()); + + assertNotNull(userItems); + assertEquals(1, userItems.size()); + assertTrue(userItems.stream().anyMatch(item -> item.getName().equals("User Item"))); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/integration/UserServiceIntegrationTest.java b/server/src/test/java/ru/practicum/shareit/integration/UserServiceIntegrationTest.java new file mode 100644 index 0000000..ab667ac --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/integration/UserServiceIntegrationTest.java @@ -0,0 +1,125 @@ +package ru.practicum.shareit.integration; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.shareit.booking.exception.ShareItException; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.service.UserService; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest +@Transactional +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +public class UserServiceIntegrationTest { + + @Autowired + private UserService userService; + + @Test // Тест на создание и получение пользователя по id + @DisplayName("Интеграционный тест: создание и получение пользователя") + void createAndGetUserIntegrationTest() { + User user = User.builder() + .name("Test User") + .email("test" + System.currentTimeMillis() + "@test.com") + .build(); + + UserDto createdUser = userService.create(user); + + assertNotNull(createdUser); + assertNotNull(createdUser.getId()); + assertEquals(user.getName(), createdUser.getName()); + assertEquals(user.getEmail(), createdUser.getEmail()); + + UserDto retrievedUser = userService.getById(createdUser.getId()); + + assertNotNull(retrievedUser); + assertEquals(createdUser.getId(), retrievedUser.getId()); + assertEquals(createdUser.getName(), retrievedUser.getName()); + assertEquals(createdUser.getEmail(), retrievedUser.getEmail()); + } + + @Test // Тест на обновление пользователя по id + @DisplayName("Интеграционный тест: обновление пользователя") + void updateUserIntegrationTest() { + User user = User.builder() + .name("Original Name") + .email("original" + System.currentTimeMillis() + "@test.com") + .build(); + + UserDto createdUser = userService.create(user); + + User updatedUser = User.builder() + .name("Updated Name") + .email("updated" + System.currentTimeMillis() + "@test.com") + .build(); + + UserDto result = userService.update(updatedUser, createdUser.getId()); + + assertNotNull(result); + assertEquals(createdUser.getId(), result.getId()); + assertEquals("Updated Name", result.getName()); + assertEquals(updatedUser.getEmail(), result.getEmail()); + + UserDto retrievedUser = userService.getById(createdUser.getId()); + + assertEquals("Updated Name", retrievedUser.getName()); + assertEquals(updatedUser.getEmail(), retrievedUser.getEmail()); + } + + @Test // Тест на получение всех пользователей + @DisplayName("Интеграционный тест: получение всех пользователей") + void getAllUsersIntegrationTest() { + User user1 = User.builder() + .name("User 1") + .email("user1" + System.currentTimeMillis() + "@test.com") + .build(); + User user2 = User.builder() + .name("User 2") + .email("user2" + System.currentTimeMillis() + "@test.com") + .build(); + User user3 = User.builder() + .name("User 3") + .email("user3" + System.currentTimeMillis() + "@test.com") + .build(); + + UserDto createdUser1 = userService.create(user1); + UserDto createdUser2 = userService.create(user2); + UserDto createdUser3 = userService.create(user3); + + List users = userService.getAll(); + + assertNotNull(users); + assertTrue(users.size() >= 3); + assertTrue(users.stream().anyMatch(u -> u.getId().equals(createdUser1.getId()))); + assertTrue(users.stream().anyMatch(u -> u.getId().equals(createdUser2.getId()))); + assertTrue(users.stream().anyMatch(u -> u.getId().equals(createdUser3.getId()))); + } + + @Test // Тест на удаление пользователя + @DisplayName("Интеграционный тест: удаление пользователя") + void deleteUserIntegrationTest() { + User user = User.builder() + .name("User to Delete") + .email("delete" + System.currentTimeMillis() + "@test.com") + .build(); + + UserDto createdUser = userService.create(user); + + assertNotNull(userService.getById(createdUser.getId())); + + userService.delete(createdUser.getId()); + + assertThrows(ShareItException.NotFoundException.class, () -> userService.getById(createdUser.getId())); + } +} \ No newline at end of file diff --git a/src/test/java/ru/practicum/shareit/item/controller/CommentControllerTest.java b/server/src/test/java/ru/practicum/shareit/item/controller/CommentControllerTest.java similarity index 59% rename from src/test/java/ru/practicum/shareit/item/controller/CommentControllerTest.java rename to server/src/test/java/ru/practicum/shareit/item/controller/CommentControllerTest.java index d7e84b3..176c312 100644 --- a/src/test/java/ru/practicum/shareit/item/controller/CommentControllerTest.java +++ b/server/src/test/java/ru/practicum/shareit/item/controller/CommentControllerTest.java @@ -1,32 +1,35 @@ package ru.practicum.shareit.item.controller; -import jakarta.transaction.Transactional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatchers; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.ContextConfiguration; -import ru.practicum.shareit.ShareItApp; -import ru.practicum.shareit.TestConfig; -import ru.practicum.shareit.booking.controller.BookingController; +import ru.practicum.shareit.ShareItServerApp; +import ru.practicum.shareit.booking.BookingStatus; import ru.practicum.shareit.booking.dto.BookingDto; import ru.practicum.shareit.booking.exception.ShareItException; +import ru.practicum.shareit.booking.repository.BookingRepository; import ru.practicum.shareit.item.dto.CommentDto; import ru.practicum.shareit.item.dto.ItemDto; -import ru.practicum.shareit.user.UserMapper; -import ru.practicum.shareit.user.controller.UserController; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.service.ItemService; import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.service.UserService; import java.time.LocalDateTime; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; @ActiveProfiles("test") -@ContextConfiguration(classes = {ShareItApp.class, TestConfig.class}) -@SpringBootTest +@SpringBootTest(classes = ShareItServerApp.class) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) class CommentControllerTest { @@ -34,41 +37,45 @@ class CommentControllerTest { private ItemController itemController; @Autowired - private UserController userController; + private UserService userService; @Autowired - private BookingController bookingController; + private ItemService itemService; - @Autowired - private UserMapper userMapper; + @MockBean + private BookingRepository bookingRepository; - private UserDto ownerDto; - private UserDto bookerDto; + private User owner; + private User booker; private ItemDto itemDto; private BookingDto bookingDto; @BeforeEach void setUp() { - String ownerEmail = "owner" + System.currentTimeMillis() + "@email.com"; - ownerDto = UserDto.builder() + owner = User.builder() .name("Item Owner") - .email(ownerEmail) + .email("owner" + System.currentTimeMillis() + "@email.com") .build(); - ownerDto = userController.create(userMapper.toUser(ownerDto)); + UserDto ownerDto = userService.create(owner); + owner.setId(ownerDto.getId()); - String bookerEmail = "booker" + System.currentTimeMillis() + "@email.com"; - bookerDto = UserDto.builder() + booker = User.builder() .name("Booker User") - .email(bookerEmail) + .email("booker" + System.currentTimeMillis() + "@email.com") .build(); - bookerDto = userController.create(userMapper.toUser(bookerDto)); + UserDto bookerDto = userService.create(booker); + booker.setId(bookerDto.getId()); - itemDto = ItemDto.builder() + Item item = Item.builder() .name("Test Item") .description("Test Description") .available(true) .build(); - itemDto = itemController.create(ownerDto.getId(), itemDto); + itemDto = itemService.create(ItemDto.builder() + .name(item.getName()) + .description(item.getDescription()) + .available(item.getAvailable()) + .build(), owner.getId()); LocalDateTime start = LocalDateTime.now().plusHours(1); LocalDateTime end = LocalDateTime.now().plusHours(2); @@ -79,37 +86,40 @@ void setUp() { .build(); } - @Test // Тест на успешное добавление комментария без бронирования + @Test @DisplayName("Error when adding comment without booking") void errorWhenAddingCommentWithoutBooking() { + when(bookingRepository.hasUserBookedItem(anyLong(), anyLong(), ArgumentMatchers.any(LocalDateTime.class))) + .thenReturn(false); + CommentDto commentDto = CommentDto.builder() .text("I haven't rented this item") .build(); assertThrows(ShareItException.BadRequestException.class, - () -> itemController.createComment(itemDto.getId(), commentDto, bookerDto.getId())); + () -> itemController.createComment(itemDto.getId(), commentDto, booker.getId())); } - @Test // Тест на успешное добавление комментария с бронированием - @Transactional + @Test @DisplayName("Error when adding comment with future booking") void errorWhenAddingCommentWithFutureBooking() { + when(bookingRepository.hasUserBookedItem(anyLong(), anyLong(), ArgumentMatchers.any(LocalDateTime.class))) + .thenReturn(false); + LocalDateTime start = LocalDateTime.now().plusDays(1); LocalDateTime end = LocalDateTime.now().plusDays(2); BookingDto futureBooking = BookingDto.builder() .itemId(itemDto.getId()) .start(start) .end(end) + .status(BookingStatus.APPROVED) .build(); - BookingDto createdBooking = bookingController.createBooking(futureBooking, bookerDto.getId()).getBody(); - bookingController.approve(createdBooking.getId(), ownerDto.getId(), true).getBody(); - CommentDto commentDto = CommentDto.builder() .text("I haven't used this item yet") .build(); assertThrows(ShareItException.BadRequestException.class, - () -> itemController.createComment(itemDto.getId(), commentDto, bookerDto.getId())); + () -> itemController.createComment(itemDto.getId(), commentDto, booker.getId())); } } \ No newline at end of file diff --git a/src/test/java/ru/practicum/shareit/item/controller/ItemBookingInfoTest.java b/server/src/test/java/ru/practicum/shareit/item/controller/ItemBookingInfoTest.java similarity index 88% rename from src/test/java/ru/practicum/shareit/item/controller/ItemBookingInfoTest.java rename to server/src/test/java/ru/practicum/shareit/item/controller/ItemBookingInfoTest.java index b5c1e9e..d1ba3f5 100644 --- a/src/test/java/ru/practicum/shareit/item/controller/ItemBookingInfoTest.java +++ b/server/src/test/java/ru/practicum/shareit/item/controller/ItemBookingInfoTest.java @@ -7,10 +7,8 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.ContextConfiguration; import org.springframework.transaction.annotation.Transactional; -import ru.practicum.shareit.ShareItApp; -import ru.practicum.shareit.TestConfig; +import ru.practicum.shareit.ShareItServerApp; import ru.practicum.shareit.booking.controller.BookingController; import ru.practicum.shareit.booking.dto.BookingDto; import ru.practicum.shareit.booking.dto.BookingShortDto; @@ -18,6 +16,7 @@ import ru.practicum.shareit.user.UserMapper; import ru.practicum.shareit.user.controller.UserController; import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; import java.time.LocalDateTime; import java.util.List; @@ -28,8 +27,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; @ActiveProfiles("test") -@ContextConfiguration(classes = {ShareItApp.class, TestConfig.class}) -@SpringBootTest +@SpringBootTest(classes = ShareItServerApp.class) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) class ItemBookingInfoTest { @@ -52,18 +50,18 @@ class ItemBookingInfoTest { @BeforeEach void setUp() { String ownerEmail = "owner" + System.currentTimeMillis() + "@email.com"; - ownerDto = UserDto.builder() + User owner = User.builder() .name("Item Owner") .email(ownerEmail) .build(); - ownerDto = userController.create(userMapper.toUser(ownerDto)); + ownerDto = userController.create(owner); String bookerEmail = "booker" + System.currentTimeMillis() + "@email.com"; - bookerDto = UserDto.builder() + User booker = User.builder() .name("Booker User") .email(bookerEmail) .build(); - bookerDto = userController.create(userMapper.toUser(bookerDto)); + bookerDto = userController.create(booker); itemDto = ItemDto.builder() .name("Test Item") @@ -84,8 +82,8 @@ void itemShouldShowBookingInfoForOwner() { .start(nearFutureStart) .end(nearFutureEnd) .build(); - BookingDto createdNearFutureBooking = bookingController.createBooking(nearFutureBooking, bookerDto.getId()).getBody(); - bookingController.approve(createdNearFutureBooking.getId(), ownerDto.getId(), true).getBody(); // Добавлен .getBody() + BookingDto createdNearFutureBooking = bookingController.createBooking(nearFutureBooking, bookerDto.getId()); + bookingController.approve(createdNearFutureBooking.getId(), ownerDto.getId(), true); LocalDateTime farFutureStart = LocalDateTime.now().plusDays(1); LocalDateTime farFutureEnd = LocalDateTime.now().plusDays(2); @@ -94,8 +92,8 @@ void itemShouldShowBookingInfoForOwner() { .start(farFutureStart) .end(farFutureEnd) .build(); - BookingDto createdFarFutureBooking = bookingController.createBooking(farFutureBooking, bookerDto.getId()).getBody(); - bookingController.approve(createdFarFutureBooking.getId(), ownerDto.getId(), true).getBody(); // Добавлен .getBody() + BookingDto createdFarFutureBooking = bookingController.createBooking(farFutureBooking, bookerDto.getId()); + bookingController.approve(createdFarFutureBooking.getId(), ownerDto.getId(), true); ItemDto itemWithBookings = itemController.getById(itemDto.getId(), ownerDto.getId()); @@ -125,7 +123,7 @@ void itemShouldNotShowBookingInfoForNonOwner() { .start(start) .end(end) .build(); - bookingController.createBooking(bookingDto, bookerDto.getId()).getBody(); + bookingController.createBooking(bookingDto, bookerDto.getId()); ItemDto itemForNonOwner = itemController.getById(itemDto.getId(), bookerDto.getId()); @@ -151,8 +149,8 @@ void allOwnerItemsShouldShowBookingInfo() { .start(start1) .end(end1) .build(); - BookingDto createdBooking1 = bookingController.createBooking(booking1, bookerDto.getId()).getBody(); - bookingController.approve(createdBooking1.getId(), ownerDto.getId(), true).getBody(); // Добавлен .getBody() + BookingDto createdBooking1 = bookingController.createBooking(booking1, bookerDto.getId()); + bookingController.approve(createdBooking1.getId(), ownerDto.getId(), true); LocalDateTime start2 = LocalDateTime.now().plusDays(3); LocalDateTime end2 = LocalDateTime.now().plusDays(4); @@ -161,8 +159,8 @@ void allOwnerItemsShouldShowBookingInfo() { .start(start2) .end(end2) .build(); - BookingDto createdBooking2 = bookingController.createBooking(booking2, bookerDto.getId()).getBody(); - bookingController.approve(createdBooking2.getId(), ownerDto.getId(), true).getBody(); // Добавлен .getBody() + BookingDto createdBooking2 = bookingController.createBooking(booking2, bookerDto.getId()); + bookingController.approve(createdBooking2.getId(), ownerDto.getId(), true); List ownerItems = itemController.getAll(ownerDto.getId()); diff --git a/src/test/java/ru/practicum/shareit/item/controller/ItemControllerTest.java b/server/src/test/java/ru/practicum/shareit/item/controller/ItemControllerTest.java similarity index 98% rename from src/test/java/ru/practicum/shareit/item/controller/ItemControllerTest.java rename to server/src/test/java/ru/practicum/shareit/item/controller/ItemControllerTest.java index dc8cc1c..b333cd8 100644 --- a/src/test/java/ru/practicum/shareit/item/controller/ItemControllerTest.java +++ b/server/src/test/java/ru/practicum/shareit/item/controller/ItemControllerTest.java @@ -9,9 +9,6 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.ContextConfiguration; -import ru.practicum.shareit.ShareItApp; -import ru.practicum.shareit.TestConfig; import ru.practicum.shareit.booking.exception.ShareItException; import ru.practicum.shareit.item.dto.CommentDto; import ru.practicum.shareit.item.dto.ItemDto; @@ -32,7 +29,6 @@ import static org.mockito.Mockito.when; @ActiveProfiles("test") -@ContextConfiguration(classes = {ShareItApp.class, TestConfig.class}) @ExtendWith(MockitoExtension.class) class ItemControllerTest { @Mock diff --git a/src/test/java/ru/practicum/shareit/user/controller/UserControllerTest.java b/server/src/test/java/ru/practicum/shareit/user/controller/UserControllerTest.java similarity index 96% rename from src/test/java/ru/practicum/shareit/user/controller/UserControllerTest.java rename to server/src/test/java/ru/practicum/shareit/user/controller/UserControllerTest.java index 3254097..d772fd7 100644 --- a/src/test/java/ru/practicum/shareit/user/controller/UserControllerTest.java +++ b/server/src/test/java/ru/practicum/shareit/user/controller/UserControllerTest.java @@ -8,9 +8,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.ContextConfiguration; -import ru.practicum.shareit.ShareItApp; -import ru.practicum.shareit.TestConfig; +import ru.practicum.shareit.ShareItServerApp; import ru.practicum.shareit.booking.exception.ShareItException; import ru.practicum.shareit.user.UserMapper; import ru.practicum.shareit.user.dto.UserDto; @@ -24,8 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; @ActiveProfiles("test") -@ContextConfiguration(classes = {ShareItApp.class, TestConfig.class}) -@SpringBootTest +@SpringBootTest(classes = ShareItServerApp.class) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) class UserControllerTest { @Autowired diff --git a/src/main/resources/application-test.properties b/server/src/test/resources/application-test.properties similarity index 100% rename from src/main/resources/application-test.properties rename to server/src/test/resources/application-test.properties diff --git a/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java b/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java deleted file mode 100644 index 558a388..0000000 --- a/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java +++ /dev/null @@ -1,58 +0,0 @@ -package ru.practicum.shareit.booking.controller; - -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PatchMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import ru.practicum.shareit.booking.dto.BookingDto; -import ru.practicum.shareit.booking.service.BookingService; - -import java.util.List; - -@RestController -@RequestMapping(path = "/bookings") -@RequiredArgsConstructor -public class BookingController { - private final BookingService bookingService; - - @PostMapping - public ResponseEntity createBooking(@Valid @RequestBody BookingDto bookingDto, - @RequestHeader("X-Sharer-User-Id") Long userId) { - return ResponseEntity.ok(bookingService.create(bookingDto, userId)); - } - - @PatchMapping("/{bookingId}") - public ResponseEntity approve(@PathVariable Long bookingId, - @RequestHeader("X-Sharer-User-Id") Long userId, - @RequestParam Boolean approved) { - return ResponseEntity.ok(bookingService.approve(bookingId, userId, approved)); - } - - @GetMapping("/{bookingId}") - public ResponseEntity getById(@PathVariable Long bookingId, - @RequestHeader("X-Sharer-User-Id") Long userId) { - return ResponseEntity.ok(bookingService.getById(bookingId, userId)); - } - - @GetMapping - public ResponseEntity> getAllByBooker( - @RequestHeader("X-Sharer-User-Id") Long userId, - @RequestParam(defaultValue = "ALL") String state) { - return ResponseEntity.ok(bookingService.getAllByBooker(userId, state)); - } - - @GetMapping("/owner") - public ResponseEntity> getAllByOwner( - @RequestHeader("X-Sharer-User-Id") Long userId, - @RequestParam(defaultValue = "ALL") String state) { - return ResponseEntity.ok(bookingService.getAllByOwner(userId, state)); - } -} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/service/BookingService.java b/src/main/java/ru/practicum/shareit/booking/service/BookingService.java deleted file mode 100644 index 69b502c..0000000 --- a/src/main/java/ru/practicum/shareit/booking/service/BookingService.java +++ /dev/null @@ -1,17 +0,0 @@ -package ru.practicum.shareit.booking.service; - -import ru.practicum.shareit.booking.dto.BookingDto; - -import java.util.List; - -public interface BookingService { - BookingDto create(BookingDto bookingDto, Long userId); - - BookingDto approve(Long bookingId, Long userId, Boolean approved); - - BookingDto getById(Long bookingId, Long userId); - - List getAllByBooker(Long userId, String state); - - List getAllByOwner(Long userId, String state); -} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/request/controller/ItemRequestController.java b/src/main/java/ru/practicum/shareit/request/controller/ItemRequestController.java deleted file mode 100644 index 5eaee82..0000000 --- a/src/main/java/ru/practicum/shareit/request/controller/ItemRequestController.java +++ /dev/null @@ -1,10 +0,0 @@ -package ru.practicum.shareit.request.controller; - -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - - -@RestController -@RequestMapping(path = "/requests") -public class ItemRequestController { -} \ No newline at end of file diff --git a/src/test/java/ru/practicum/shareit/booking/controller/BookingControllerTest.java b/src/test/java/ru/practicum/shareit/booking/controller/BookingControllerTest.java deleted file mode 100644 index 6c49a82..0000000 --- a/src/test/java/ru/practicum/shareit/booking/controller/BookingControllerTest.java +++ /dev/null @@ -1,275 +0,0 @@ -package ru.practicum.shareit.booking.controller; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.transaction.annotation.Transactional; -import ru.practicum.shareit.ShareItApp; -import ru.practicum.shareit.TestConfig; -import ru.practicum.shareit.booking.BookingStatus; -import ru.practicum.shareit.booking.dto.BookingDto; -import ru.practicum.shareit.booking.exception.ShareItException; -import ru.practicum.shareit.item.controller.ItemController; -import ru.practicum.shareit.item.dto.ItemDto; -import ru.practicum.shareit.user.UserMapper; -import ru.practicum.shareit.user.controller.UserController; -import ru.practicum.shareit.user.dto.UserDto; - -import java.time.LocalDateTime; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -@ActiveProfiles("test") -@ContextConfiguration(classes = {ShareItApp.class, TestConfig.class}) -@SpringBootTest -@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) -class BookingControllerTest { - - @Autowired - private BookingController bookingController; - - @Autowired - private ItemController itemController; - - @Autowired - private UserController userController; - - @Autowired - private UserMapper userMapper; - - private UserDto ownerDto; - private UserDto bookerDto; - private ItemDto itemDto; - private BookingDto bookingDto; - - @BeforeEach - void setUp() { - String ownerEmail = "owner" + System.currentTimeMillis() + "@email.com"; - ownerDto = UserDto.builder() - .name("Item Owner") - .email(ownerEmail) - .build(); - ownerDto = userController.create(userMapper.toUser(ownerDto)); - - String bookerEmail = "booker" + System.currentTimeMillis() + "@email.com"; - bookerDto = UserDto.builder() - .name("Booker User") - .email(bookerEmail) - .build(); - bookerDto = userController.create(userMapper.toUser(bookerDto)); - - itemDto = ItemDto.builder() - .name("Test Item") - .description("Test Description") - .available(true) - .build(); - itemDto = itemController.create(ownerDto.getId(), itemDto); - - LocalDateTime start = LocalDateTime.now().plusDays(1); - LocalDateTime end = LocalDateTime.now().plusDays(2); - bookingDto = BookingDto.builder() - .itemId(itemDto.getId()) - .start(start) - .end(end) - .build(); - } - - @Nested // Тесты для создания бронирований - @DisplayName("Creating bookings") - class CreateBookingTests { - @Test - @Transactional - @DisplayName("Successful booking creation") - void createBookingTest() { - BookingDto createdBooking = bookingController.createBooking(bookingDto, bookerDto.getId()).getBody(); - - assertNotNull(createdBooking); - assertNotNull(createdBooking.getId()); - assertEquals(bookingDto.getStart(), createdBooking.getStart()); - assertEquals(bookingDto.getEnd(), createdBooking.getEnd()); - assertEquals(itemDto.getId(), createdBooking.getItem().getId()); - assertEquals(bookerDto.getId(), createdBooking.getBooker().getId()); - assertEquals(BookingStatus.WAITING, createdBooking.getStatus()); - } - - @Test - @Transactional - @DisplayName("Error when owner tries to book their own item") - void ownerBookingOwnItemTest() { - assertThrows(ShareItException.NotFoundException.class, - () -> bookingController.createBooking(bookingDto, ownerDto.getId())); - } - - @Test - @Transactional - @DisplayName("Error when booking unavailable item") - void bookingUnavailableItemTest() { - ItemDto updatedItem = ItemDto.builder() - .available(false) - .build(); - itemController.update(updatedItem, itemDto.getId(), ownerDto.getId()); - - assertThrows(ShareItException.BadRequestException.class, - () -> bookingController.createBooking(bookingDto, bookerDto.getId())); - } - - @Test - @Transactional - @DisplayName("Error when booking with invalid dates") - void bookingWithInvalidDatesTest() { - BookingDto invalidBooking = BookingDto.builder() - .itemId(itemDto.getId()) - .start(LocalDateTime.now().plusDays(2)) - .end(LocalDateTime.now().plusDays(1)) - .build(); - - assertThrows(ShareItException.BadRequestException.class, - () -> bookingController.createBooking(invalidBooking, bookerDto.getId())); - } - } - - @Nested // Тесты для утверждения бронирований - @DisplayName("Approving bookings") - class ApproveBookingTests { - @Test - @Transactional - @DisplayName("Successful booking approval") - void approveBookingTest() { - BookingDto createdBooking = bookingController.createBooking(bookingDto, bookerDto.getId()).getBody(); - BookingDto approvedBooking = bookingController.approve(createdBooking.getId(), ownerDto.getId(), true).getBody(); - - assertEquals(BookingStatus.APPROVED, approvedBooking.getStatus()); - } - - @Test - @Transactional - @DisplayName("Successful booking rejection") - void rejectBookingTest() { - BookingDto createdBooking = bookingController.createBooking(bookingDto, bookerDto.getId()).getBody(); - BookingDto rejectedBooking = bookingController.approve(createdBooking.getId(), ownerDto.getId(), false).getBody(); - - assertEquals(BookingStatus.REJECTED, rejectedBooking.getStatus()); - } - - @Test - @Transactional - @DisplayName("Error when non-owner tries to approve booking") - void nonOwnerApprovingBookingTest() { - BookingDto createdBooking = bookingController.createBooking(bookingDto, bookerDto.getId()).getBody(); - - String randomEmail = "random" + System.currentTimeMillis() + "@email.com"; - UserDto randomUser = UserDto.builder() - .name("Random User") - .email(randomEmail) - .build(); - randomUser = userController.create(userMapper.toUser(randomUser)); - - UserDto finalRandomUser = randomUser; - assertThrows(ShareItException.ForbiddenException.class, - () -> bookingController.approve(createdBooking.getId(), finalRandomUser.getId(), true)); - } - - @Test - @Transactional - @DisplayName("Error when approving already processed booking") - void approvingProcessedBookingTest() { - BookingDto createdBooking = bookingController.createBooking(bookingDto, bookerDto.getId()).getBody(); - bookingController.approve(createdBooking.getId(), ownerDto.getId(), true).getBody(); // Добавлен .getBody() - - assertThrows(ShareItException.BadRequestException.class, - () -> bookingController.approve(createdBooking.getId(), ownerDto.getId(), true)); - } - } - - @Nested // Тесты для получения бронирований - @DisplayName("Getting bookings") - class GetBookingTests { - @Test - @Transactional - @DisplayName("Getting booking by ID") - void getBookingByIdTest() { - BookingDto createdBooking = bookingController.createBooking(bookingDto, bookerDto.getId()).getBody(); - BookingDto retrievedBooking = bookingController.getById(createdBooking.getId(), bookerDto.getId()).getBody(); - - assertNotNull(retrievedBooking); - assertEquals(createdBooking.getId(), retrievedBooking.getId()); - assertEquals(createdBooking.getStart(), retrievedBooking.getStart()); - assertEquals(createdBooking.getEnd(), retrievedBooking.getEnd()); - } - - @Test - @Transactional - @DisplayName("Error when unauthorized user tries to get booking") - void unauthorizedGetBookingTest() { - BookingDto createdBooking = bookingController.createBooking(bookingDto, bookerDto.getId()).getBody(); - - String randomEmail = "random" + System.currentTimeMillis() + "@email.com"; - UserDto randomUser = UserDto.builder() - .name("Random User") - .email(randomEmail) - .build(); - randomUser = userController.create(userMapper.toUser(randomUser)); - - UserDto finalRandomUser = randomUser; - assertThrows(ShareItException.NotFoundException.class, - () -> bookingController.getById(createdBooking.getId(), finalRandomUser.getId())); - } - - @Test - @Transactional - @DisplayName("Getting all bookings by booker") - void getAllBookingsByBookerTest() { - bookingController.createBooking(bookingDto, bookerDto.getId()).getBody(); - - List bookings = bookingController.getAllByBooker(bookerDto.getId(), "ALL").getBody(); - - assertEquals(1, bookings.size()); - assertEquals(itemDto.getId(), bookings.get(0).getItem().getId()); - assertEquals(bookerDto.getId(), bookings.get(0).getBooker().getId()); - } - - @Test - @Transactional - @DisplayName("Getting all bookings by owner") - void getAllBookingsByOwnerTest() { - bookingController.createBooking(bookingDto, bookerDto.getId()).getBody(); - - List bookings = bookingController.getAllByOwner(ownerDto.getId(), "ALL").getBody(); - - assertEquals(1, bookings.size()); - assertEquals(itemDto.getId(), bookings.get(0).getItem().getId()); - assertEquals(bookerDto.getId(), bookings.get(0).getBooker().getId()); - } - - @Test - @Transactional - @DisplayName("Getting bookings with different states") - void getBookingsWithDifferentStatesTest() { - BookingDto createdBooking = bookingController.createBooking(bookingDto, bookerDto.getId()).getBody(); - bookingController.approve(createdBooking.getId(), ownerDto.getId(), true).getBody(); // Добавлен .getBody() - - List waitingBookings = bookingController.getAllByBooker(bookerDto.getId(), "WAITING").getBody(); - assertEquals(0, waitingBookings.size()); - - List futureBookings = bookingController.getAllByBooker(bookerDto.getId(), "FUTURE").getBody(); - assertEquals(1, futureBookings.size()); - } - - @Test - @Transactional - @DisplayName("Error when using invalid state") - void invalidStateTest() { - assertThrows(ShareItException.BadRequestException.class, - () -> bookingController.getAllByBooker(bookerDto.getId(), "INVALID_STATE")); - } - } -} \ No newline at end of file From 380165b7c39145deb31e71759b76dbcbf985c927 Mon Sep 17 00:00:00 2001 From: Viktor64 Date: Sun, 20 Apr 2025 17:46:36 +0400 Subject: [PATCH 02/14] =?UTF-8?q?=D0=A1=D0=BE=D0=B7=D0=B4=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=B2=D0=B5=D1=82=D0=BA=D1=83:=20add-item-requests-and-gateway?= =?UTF-8?q?.=201)=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D1=8F=D0=B5=D0=BC?= =?UTF-8?q?=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81=20=D0=B2=D0=B5=D1=89?= =?UTF-8?q?=D0=B8.=202)=20=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BB=20=D1=87=D0=B5=D1=82=D1=8B=D1=80=D0=B5=20=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D1=8B=D1=85=20=D1=8D=D0=BD=D0=B4=D0=BF=D0=BE=D0=B8?= =?UTF-8?q?=D0=BD=D1=82=D0=B0:=20POST=20/requests,=20GET=20/requests,=20GE?= =?UTF-8?q?T=20/requests/all,=20GET=20/requests/{requestId},=20GET=20/requ?= =?UTF-8?q?ests.=203)=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BE=D0=BF=D1=86=D0=B8=D1=8E=20=D0=BE=D1=82=D0=B2=D0=B5=D1=82?= =?UTF-8?q?=D0=B0=20=D0=BD=D0=B0=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81.?= =?UTF-8?q?=204)=20=D0=A0=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=20=D0=BD=D0=B0=202=20?= =?UTF-8?q?=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8F=20shareIt-server=20=D0=B8?= =?UTF-8?q?=20shareIt-gateway.=205)=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=82=D0=B5=D1=81=D1=82=D1=8B.=206)=20=D0=A1=D0=BE?= =?UTF-8?q?=D0=B7=D0=B4=D0=B0=D0=BB=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5=20?= =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82=D1=8B.=207)=20=D0=9E=D0=B1=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=B8=D0=BB=20=D0=BE=D1=81=D0=BD=D0=BE=D0=B2=D0=BD=D0=BE?= =?UTF-8?q?=D0=B9=20pom,=20suppressionsLocation=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BF=D0=BB=D0=B0=D0=B3=D0=B8=D0=BD=D0=B0=20Maven=20Checkstyle?= =?UTF-8?q?.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7aa6f3b..d767edc 100644 --- a/pom.xml +++ b/pom.xml @@ -126,7 +126,7 @@ checkstyle.xml - checkstyle-suppressions.xml + suppressions.xml UTF-8 true true From 296f0cb719a20ff79565aaad65f4caff1fafec4c Mon Sep 17 00:00:00 2001 From: Viktor64 Date: Sun, 20 Apr 2025 20:59:50 +0400 Subject: [PATCH 03/14] =?UTF-8?q?=D0=A1=D0=BE=D0=B7=D0=B4=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=B2=D0=B5=D1=82=D0=BA=D1=83:=20add-item-requests-and-gateway?= =?UTF-8?q?.=201)=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D1=8F=D0=B5=D0=BC?= =?UTF-8?q?=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81=20=D0=B2=D0=B5=D1=89?= =?UTF-8?q?=D0=B8.=202)=20=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BB=20=D1=87=D0=B5=D1=82=D1=8B=D1=80=D0=B5=20=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D1=8B=D1=85=20=D1=8D=D0=BD=D0=B4=D0=BF=D0=BE=D0=B8?= =?UTF-8?q?=D0=BD=D1=82=D0=B0:=20POST=20/requests,=20GET=20/requests,=20GE?= =?UTF-8?q?T=20/requests/all,=20GET=20/requests/{requestId},=20GET=20/requ?= =?UTF-8?q?ests.=203)=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BE=D0=BF=D1=86=D0=B8=D1=8E=20=D0=BE=D1=82=D0=B2=D0=B5=D1=82?= =?UTF-8?q?=D0=B0=20=D0=BD=D0=B0=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81.?= =?UTF-8?q?=204)=20=D0=A0=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=20=D0=BD=D0=B0=202=20?= =?UTF-8?q?=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8F=20shareIt-server=20=D0=B8?= =?UTF-8?q?=20shareIt-gateway.=205)=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=82=D0=B5=D1=81=D1=82=D1=8B.=206)=20=D0=A1=D0=BE?= =?UTF-8?q?=D0=B7=D0=B4=D0=B0=D0=BB=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5=20?= =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82=D1=8B.=207)=20=D0=9E=D0=B1=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=B8=D0=BB=20=D0=BE=D1=81=D0=BD=D0=BE=D0=B2=D0=BD=D0=BE?= =?UTF-8?q?=D0=B9=20pom,=20suppressionsLocation=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BF=D0=BB=D0=B0=D0=B3=D0=B8=D0=BD=D0=B0=20Maven=20Checkstyle?= =?UTF-8?q?.=208)=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8=D0=BB=20=D1=82?= =?UTF-8?q?=D0=B5=D1=81=D1=82=D1=8B,=20=D1=87=D1=82=D0=BE=D0=B1=D1=8B=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B9=D1=82=D0=B8=20"build"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BookingControllerMockMvcTest.java | 2 + .../controller/ItemControllerMockMvcTest.java | 2 + .../ItemRequestControllerMockMvcTest.java | 2 + .../controller/UserControllerMockMvcTest.java | 2 + .../shareit/dto/BookingDtoJsonTest.java | 2 + .../shareit/dto/CommentDtoJsonTest.java | 2 + .../shareit/dto/ItemDtoJsonTest.java | 2 + .../BookingServiceIntegrationTest.java | 2 + .../ItemServiceIntegrationTest.java | 2 + .../UserServiceIntegrationTest.java | 2 + server/src/test/resources/schema-test.sql | 58 +++++++++++++++++++ 11 files changed, 78 insertions(+) create mode 100644 server/src/test/resources/schema-test.sql diff --git a/server/src/test/java/ru/practicum/shareit/controller/BookingControllerMockMvcTest.java b/server/src/test/java/ru/practicum/shareit/controller/BookingControllerMockMvcTest.java index 720ab85..b065b9f 100644 --- a/server/src/test/java/ru/practicum/shareit/controller/BookingControllerMockMvcTest.java +++ b/server/src/test/java/ru/practicum/shareit/controller/BookingControllerMockMvcTest.java @@ -8,6 +8,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import ru.practicum.shareit.booking.BookingStatus; import ru.practicum.shareit.booking.controller.BookingController; @@ -35,6 +36,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @WebMvcTest(BookingController.class) +@ActiveProfiles("test") public class BookingControllerMockMvcTest { @Autowired diff --git a/server/src/test/java/ru/practicum/shareit/controller/ItemControllerMockMvcTest.java b/server/src/test/java/ru/practicum/shareit/controller/ItemControllerMockMvcTest.java index 65c875f..f1e95b9 100644 --- a/server/src/test/java/ru/practicum/shareit/controller/ItemControllerMockMvcTest.java +++ b/server/src/test/java/ru/practicum/shareit/controller/ItemControllerMockMvcTest.java @@ -8,6 +8,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import ru.practicum.shareit.item.controller.ItemController; import ru.practicum.shareit.item.dto.CommentDto; @@ -33,6 +34,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @WebMvcTest(ItemController.class) +@ActiveProfiles("test") public class ItemControllerMockMvcTest { @Autowired diff --git a/server/src/test/java/ru/practicum/shareit/controller/ItemRequestControllerMockMvcTest.java b/server/src/test/java/ru/practicum/shareit/controller/ItemRequestControllerMockMvcTest.java index 59a2c32..f7fecaa 100644 --- a/server/src/test/java/ru/practicum/shareit/controller/ItemRequestControllerMockMvcTest.java +++ b/server/src/test/java/ru/practicum/shareit/controller/ItemRequestControllerMockMvcTest.java @@ -8,6 +8,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import ru.practicum.shareit.request.controller.ItemRequestController; import ru.practicum.shareit.request.dto.ItemRequestDto; @@ -31,6 +32,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @WebMvcTest(ItemRequestController.class) +@ActiveProfiles("test") public class ItemRequestControllerMockMvcTest { @Autowired diff --git a/server/src/test/java/ru/practicum/shareit/controller/UserControllerMockMvcTest.java b/server/src/test/java/ru/practicum/shareit/controller/UserControllerMockMvcTest.java index 2fa57cd..f681558 100644 --- a/server/src/test/java/ru/practicum/shareit/controller/UserControllerMockMvcTest.java +++ b/server/src/test/java/ru/practicum/shareit/controller/UserControllerMockMvcTest.java @@ -8,6 +8,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import ru.practicum.shareit.user.controller.UserController; import ru.practicum.shareit.user.dto.UserDto; @@ -30,6 +31,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @WebMvcTest(UserController.class) +@ActiveProfiles("test") public class UserControllerMockMvcTest { @Autowired diff --git a/server/src/test/java/ru/practicum/shareit/dto/BookingDtoJsonTest.java b/server/src/test/java/ru/practicum/shareit/dto/BookingDtoJsonTest.java index 6e50710..7ebc148 100644 --- a/server/src/test/java/ru/practicum/shareit/dto/BookingDtoJsonTest.java +++ b/server/src/test/java/ru/practicum/shareit/dto/BookingDtoJsonTest.java @@ -6,6 +6,7 @@ import org.springframework.boot.test.autoconfigure.json.JsonTest; import org.springframework.boot.test.json.JacksonTester; import org.springframework.boot.test.json.JsonContent; +import org.springframework.test.context.ActiveProfiles; import ru.practicum.shareit.booking.BookingStatus; import ru.practicum.shareit.booking.dto.BookingDto; import ru.practicum.shareit.item.dto.ItemDto; @@ -16,6 +17,7 @@ import static org.assertj.core.api.Assertions.assertThat; @JsonTest +@ActiveProfiles("test") public class BookingDtoJsonTest { @Autowired diff --git a/server/src/test/java/ru/practicum/shareit/dto/CommentDtoJsonTest.java b/server/src/test/java/ru/practicum/shareit/dto/CommentDtoJsonTest.java index a245289..8e3bada 100644 --- a/server/src/test/java/ru/practicum/shareit/dto/CommentDtoJsonTest.java +++ b/server/src/test/java/ru/practicum/shareit/dto/CommentDtoJsonTest.java @@ -6,6 +6,7 @@ import org.springframework.boot.test.autoconfigure.json.JsonTest; import org.springframework.boot.test.json.JacksonTester; import org.springframework.boot.test.json.JsonContent; +import org.springframework.test.context.ActiveProfiles; import ru.practicum.shareit.item.dto.CommentDto; import java.time.LocalDateTime; @@ -13,6 +14,7 @@ import static org.assertj.core.api.Assertions.assertThat; @JsonTest +@ActiveProfiles("test") public class CommentDtoJsonTest { @Autowired diff --git a/server/src/test/java/ru/practicum/shareit/dto/ItemDtoJsonTest.java b/server/src/test/java/ru/practicum/shareit/dto/ItemDtoJsonTest.java index aae0b78..a5db59b 100644 --- a/server/src/test/java/ru/practicum/shareit/dto/ItemDtoJsonTest.java +++ b/server/src/test/java/ru/practicum/shareit/dto/ItemDtoJsonTest.java @@ -6,6 +6,7 @@ import org.springframework.boot.test.autoconfigure.json.JsonTest; import org.springframework.boot.test.json.JacksonTester; import org.springframework.boot.test.json.JsonContent; +import org.springframework.test.context.ActiveProfiles; import ru.practicum.shareit.booking.dto.BookingShortDto; import ru.practicum.shareit.item.dto.CommentDto; import ru.practicum.shareit.item.dto.ItemDto; @@ -16,6 +17,7 @@ import static org.assertj.core.api.Assertions.assertThat; @JsonTest +@ActiveProfiles("test") public class ItemDtoJsonTest { @Autowired diff --git a/server/src/test/java/ru/practicum/shareit/integration/BookingServiceIntegrationTest.java b/server/src/test/java/ru/practicum/shareit/integration/BookingServiceIntegrationTest.java index 20807ed..511c907 100644 --- a/server/src/test/java/ru/practicum/shareit/integration/BookingServiceIntegrationTest.java +++ b/server/src/test/java/ru/practicum/shareit/integration/BookingServiceIntegrationTest.java @@ -6,6 +6,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; import org.springframework.transaction.annotation.Transactional; import ru.practicum.shareit.booking.BookingStatus; import ru.practicum.shareit.booking.dto.BookingDto; @@ -26,6 +27,7 @@ @SpringBootTest @Transactional @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +@ActiveProfiles("test") public class BookingServiceIntegrationTest { @Autowired diff --git a/server/src/test/java/ru/practicum/shareit/integration/ItemServiceIntegrationTest.java b/server/src/test/java/ru/practicum/shareit/integration/ItemServiceIntegrationTest.java index 42d61bf..c71ae0d 100644 --- a/server/src/test/java/ru/practicum/shareit/integration/ItemServiceIntegrationTest.java +++ b/server/src/test/java/ru/practicum/shareit/integration/ItemServiceIntegrationTest.java @@ -6,6 +6,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; import org.springframework.transaction.annotation.Transactional; import ru.practicum.shareit.item.dto.ItemDto; import ru.practicum.shareit.item.service.ItemService; @@ -23,6 +24,7 @@ @SpringBootTest @Transactional @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +@ActiveProfiles("test") public class ItemServiceIntegrationTest { @Autowired diff --git a/server/src/test/java/ru/practicum/shareit/integration/UserServiceIntegrationTest.java b/server/src/test/java/ru/practicum/shareit/integration/UserServiceIntegrationTest.java index ab667ac..2f82ce9 100644 --- a/server/src/test/java/ru/practicum/shareit/integration/UserServiceIntegrationTest.java +++ b/server/src/test/java/ru/practicum/shareit/integration/UserServiceIntegrationTest.java @@ -5,6 +5,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; import org.springframework.transaction.annotation.Transactional; import ru.practicum.shareit.booking.exception.ShareItException; import ru.practicum.shareit.user.dto.UserDto; @@ -21,6 +22,7 @@ @SpringBootTest @Transactional @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +@ActiveProfiles("test") public class UserServiceIntegrationTest { @Autowired diff --git a/server/src/test/resources/schema-test.sql b/server/src/test/resources/schema-test.sql new file mode 100644 index 0000000..a3f056c --- /dev/null +++ b/server/src/test/resources/schema-test.sql @@ -0,0 +1,58 @@ +-- Создание таблиц для тестов +DROP TABLE IF EXISTS comments; +DROP TABLE IF EXISTS bookings; +DROP TABLE IF EXISTS items; +DROP TABLE IF EXISTS item_requests; +DROP TABLE IF EXISTS users; + +CREATE TABLE users ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL UNIQUE +); + +CREATE TABLE item_requests ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + description VARCHAR(1000) NOT NULL, + requestor_id BIGINT NOT NULL, + created TIMESTAMP NOT NULL, + FOREIGN KEY (requestor_id) REFERENCES users(id) +); + +CREATE TABLE items ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + description VARCHAR(512) NOT NULL, + available BOOLEAN NOT NULL, + owner_id BIGINT NOT NULL, + request_id BIGINT, + FOREIGN KEY (owner_id) REFERENCES users(id), + FOREIGN KEY (request_id) REFERENCES item_requests(id) +); + +CREATE TABLE bookings ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + start_date TIMESTAMP NOT NULL, + end_date TIMESTAMP NOT NULL, + item_id BIGINT NOT NULL, + booker_id BIGINT NOT NULL, + status VARCHAR(50) NOT NULL, + FOREIGN KEY (item_id) REFERENCES items(id), + FOREIGN KEY (booker_id) REFERENCES users(id) +); + +CREATE TABLE comments ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + text VARCHAR(1000) NOT NULL, + item_id BIGINT NOT NULL, + author_id BIGINT NOT NULL, + created TIMESTAMP NOT NULL, + FOREIGN KEY (item_id) REFERENCES items(id), + FOREIGN KEY (author_id) REFERENCES users(id) +); + +-- Индексы +CREATE INDEX IF NOT EXISTS idx_bookings_item ON bookings(item_id); +CREATE INDEX IF NOT EXISTS idx_bookings_booker ON bookings(booker_id); +CREATE INDEX IF NOT EXISTS idx_bookings_status ON bookings(status); +CREATE INDEX IF NOT EXISTS idx_bookings_dates ON bookings(start_date, end_date); \ No newline at end of file From 71b1054f7a879f35c60688d7853dce45096cd486 Mon Sep 17 00:00:00 2001 From: Viktor64 Date: Sun, 20 Apr 2025 22:35:28 +0400 Subject: [PATCH 04/14] =?UTF-8?q?=D0=A1=D0=BE=D0=B7=D0=B4=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=B2=D0=B5=D1=82=D0=BA=D1=83:=20add-item-requests-and-gateway?= =?UTF-8?q?.=201)=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D1=8F=D0=B5=D0=BC?= =?UTF-8?q?=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81=20=D0=B2=D0=B5=D1=89?= =?UTF-8?q?=D0=B8.=202)=20=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BB=20=D1=87=D0=B5=D1=82=D1=8B=D1=80=D0=B5=20=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D1=8B=D1=85=20=D1=8D=D0=BD=D0=B4=D0=BF=D0=BE=D0=B8?= =?UTF-8?q?=D0=BD=D1=82=D0=B0:=20POST=20/requests,=20GET=20/requests,=20GE?= =?UTF-8?q?T=20/requests/all,=20GET=20/requests/{requestId},=20GET=20/requ?= =?UTF-8?q?ests.=203)=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BE=D0=BF=D1=86=D0=B8=D1=8E=20=D0=BE=D1=82=D0=B2=D0=B5=D1=82?= =?UTF-8?q?=D0=B0=20=D0=BD=D0=B0=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81.?= =?UTF-8?q?=204)=20=D0=A0=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=20=D0=BD=D0=B0=202=20?= =?UTF-8?q?=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8F=20shareIt-server=20=D0=B8?= =?UTF-8?q?=20shareIt-gateway.=205)=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=82=D0=B5=D1=81=D1=82=D1=8B.=206)=20=D0=A1=D0=BE?= =?UTF-8?q?=D0=B7=D0=B4=D0=B0=D0=BB=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5=20?= =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82=D1=8B.=207)=20=D0=9E=D0=B1=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=B8=D0=BB=20=D0=BE=D1=81=D0=BD=D0=BE=D0=B2=D0=BD=D0=BE?= =?UTF-8?q?=D0=B9=20pom,=20suppressionsLocation=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BF=D0=BB=D0=B0=D0=B3=D0=B8=D0=BD=D0=B0=20Maven=20Checkstyle?= =?UTF-8?q?.=208)=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8=D0=BB=20=D1=82?= =?UTF-8?q?=D0=B5=D1=81=D1=82=D1=8B,=20=D1=87=D1=82=D0=BE=D0=B1=D1=8B=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B9=D1=82=D0=B8=20"build"=20=D0=B8=20Run?= =?UTF-8?q?=20application?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/pom.xml | 14 + .../controller/BookingControllerTest.java | 591 +++++++----------- .../resources/application-test.properties | 4 +- 3 files changed, 245 insertions(+), 364 deletions(-) diff --git a/server/pom.xml b/server/pom.xml index 76a3f65..4baf7ce 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -78,6 +78,20 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*Test.java + + -Dspring.profiles.active=test + + test + + 300 + + \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/booking/controller/BookingControllerTest.java b/server/src/test/java/ru/practicum/shareit/booking/controller/BookingControllerTest.java index 0775abd..d80a052 100644 --- a/server/src/test/java/ru/practicum/shareit/booking/controller/BookingControllerTest.java +++ b/server/src/test/java/ru/practicum/shareit/booking/controller/BookingControllerTest.java @@ -2,27 +2,18 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.transaction.annotation.Transactional; -import ru.practicum.shareit.ShareItServerApp; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import ru.practicum.shareit.booking.BookingStatus; import ru.practicum.shareit.booking.dto.BookingDto; import ru.practicum.shareit.booking.exception.ShareItException; import ru.practicum.shareit.booking.service.BookingService; -import ru.practicum.shareit.item.ItemMapper; import ru.practicum.shareit.item.dto.ItemDto; -import ru.practicum.shareit.item.model.Item; -import ru.practicum.shareit.item.service.ItemService; -import ru.practicum.shareit.user.UserMapper; import ru.practicum.shareit.user.dto.UserDto; -import ru.practicum.shareit.user.model.User; -import ru.practicum.shareit.user.service.UserService; import java.time.LocalDateTime; import java.util.List; @@ -39,402 +30,276 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; -@ActiveProfiles("test") -@SpringBootTest(classes = ShareItServerApp.class) -@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +@DisplayName("Booking Controller Tests") class BookingControllerTest { - @Autowired - private BookingController bookingController; - - @MockBean + @Mock private BookingService bookingService; - @Autowired - private ItemService itemService; - - @Autowired - private UserService userService; - - @Autowired - private UserMapper userMapper; - - @Autowired - private ItemMapper itemMapper; + @InjectMocks + private BookingController bookingController; private UserDto ownerDto; private UserDto bookerDto; private ItemDto itemDto; private BookingDto bookingDto; + private BookingDto createdBookingDto; @BeforeEach void setUp() { - String ownerEmail = "owner" + System.currentTimeMillis() + "@email.com"; - User owner = User.builder() + MockitoAnnotations.openMocks(this); + + // Подготовка тестовых данных + ownerDto = UserDto.builder() + .id(1L) .name("Item Owner") - .email(ownerEmail) + .email("owner@email.com") .build(); - ownerDto = userService.create(owner); - String bookerEmail = "booker" + System.currentTimeMillis() + "@email.com"; - User booker = User.builder() + bookerDto = UserDto.builder() + .id(2L) .name("Booker User") - .email(bookerEmail) + .email("booker@email.com") .build(); - bookerDto = userService.create(booker); - Item item = Item.builder() + itemDto = ItemDto.builder() + .id(1L) .name("Test Item") .description("Test Description") .available(true) .build(); - itemDto = itemService.create(itemMapper.toItemDto(item), ownerDto.getId()); LocalDateTime start = LocalDateTime.now().plusDays(1); LocalDateTime end = LocalDateTime.now().plusDays(2); + bookingDto = BookingDto.builder() .itemId(itemDto.getId()) .start(start) .end(end) .build(); - when(bookingService.create(any(BookingDto.class), anyLong())).thenAnswer(invocation -> { - BookingDto dto = invocation.getArgument(0); - - return BookingDto.builder() - .id(1L) - .start(dto.getStart()) - .end(dto.getEnd()) - .item(itemDto) - .booker(bookerDto) - .status(BookingStatus.WAITING) - .build(); - }); - - when(bookingService.approve(anyLong(), anyLong(), anyBoolean())).thenAnswer(invocation -> { - Long bookingId = invocation.getArgument(0); - Boolean approved = invocation.getArgument(2); - - return BookingDto.builder() - .id(bookingId) - .start(bookingDto.getStart()) - .end(bookingDto.getEnd()) - .item(itemDto) - .booker(bookerDto) - .status(approved ? BookingStatus.APPROVED : BookingStatus.REJECTED) - .build(); - }); - - when(bookingService.getById(anyLong(), anyLong())).thenAnswer(invocation -> { - Long bookingId = invocation.getArgument(0); - - return BookingDto.builder() - .id(bookingId) - .start(bookingDto.getStart()) - .end(bookingDto.getEnd()) - .item(itemDto) - .booker(bookerDto) - .status(BookingStatus.WAITING) - .build(); - }); - - when(bookingService.getAllByBooker(anyLong(), anyString(), anyInt(), anyInt())).thenReturn( - List.of(BookingDto.builder() + createdBookingDto = BookingDto.builder() + .id(1L) + .start(start) + .end(end) + .item(itemDto) + .booker(bookerDto) + .status(BookingStatus.WAITING) + .build(); + + // Настройка поведения мока + when(bookingService.create(any(BookingDto.class), anyLong())).thenReturn(createdBookingDto); + + when(bookingService.approve(anyLong(), eq(ownerDto.getId()), eq(true))) + .thenReturn(BookingDto.builder() .id(1L) - .start(bookingDto.getStart()) - .end(bookingDto.getEnd()) + .start(start) + .end(end) .item(itemDto) .booker(bookerDto) - .status(BookingStatus.WAITING) - .build()) - ); + .status(BookingStatus.APPROVED) + .build()); - when(bookingService.getAllByOwner(anyLong(), anyString(), anyInt(), anyInt())).thenReturn( - List.of(BookingDto.builder() + when(bookingService.approve(anyLong(), eq(ownerDto.getId()), eq(false))) + .thenReturn(BookingDto.builder() .id(1L) - .start(bookingDto.getStart()) - .end(bookingDto.getEnd()) + .start(start) + .end(end) .item(itemDto) .booker(bookerDto) - .status(BookingStatus.WAITING) - .build()) - ); + .status(BookingStatus.REJECTED) + .build()); + + when(bookingService.getById(anyLong(), anyLong())).thenReturn(createdBookingDto); + + when(bookingService.getAllByBooker(anyLong(), anyString(), anyInt(), anyInt())) + .thenReturn(List.of(createdBookingDto)); + + when(bookingService.getAllByOwner(anyLong(), anyString(), anyInt(), anyInt())) + .thenReturn(List.of(createdBookingDto)); } - @Nested // Тесты для создания бронирований - @DisplayName("Creating bookings") - class CreateBookingTests { - @Test - @Transactional - @DisplayName("Successful booking creation") - void createBookingTest() { - BookingDto createdBooking = bookingController.createBooking(bookingDto, bookerDto.getId()); - - assertNotNull(createdBooking); - assertNotNull(createdBooking.getId()); - assertEquals(bookingDto.getStart(), createdBooking.getStart()); - assertEquals(bookingDto.getEnd(), createdBooking.getEnd()); - assertEquals(itemDto.getId(), createdBooking.getItem().getId()); - assertEquals(bookerDto.getId(), createdBooking.getBooker().getId()); - assertEquals(BookingStatus.WAITING, createdBooking.getStatus()); - } - - @Test - @Transactional - @DisplayName("Error when owner tries to book their own item") - void ownerBookingOwnItemTest() { - when(bookingService.create(any(BookingDto.class), eq(ownerDto.getId()))) - .thenThrow(new ShareItException.NotFoundException("Владелец не может забронировать свою вещь")); - - assertThrows(ShareItException.NotFoundException.class, - () -> bookingController.createBooking(bookingDto, ownerDto.getId())); - } - - @Test - @Transactional - @DisplayName("Error when booking unavailable item") - void bookingUnavailableItemTest() { - when(bookingService.create(any(BookingDto.class), eq(bookerDto.getId()))) - .thenThrow(new ShareItException.BadRequestException("Вещь недоступна для бронирования")); - - assertThrows(ShareItException.BadRequestException.class, - () -> bookingController.createBooking(bookingDto, bookerDto.getId())); - } - - @Test - @Transactional - @DisplayName("Error when booking with invalid dates") - void bookingWithInvalidDatesTest() { - BookingDto invalidBooking = BookingDto.builder() - .itemId(itemDto.getId()) - .start(LocalDateTime.now().plusDays(2)) - .end(LocalDateTime.now().plusDays(1)) - .build(); - - when(bookingService.create(eq(invalidBooking), eq(bookerDto.getId()))) - .thenThrow(new ShareItException.BadRequestException("Некорректные даты бронирования")); - - assertThrows(ShareItException.BadRequestException.class, - () -> bookingController.createBooking(invalidBooking, bookerDto.getId())); - } + @Test + @DisplayName("Создание бронирования - успешный случай") + void createBooking_Success() { + BookingDto result = bookingController.createBooking(bookingDto, bookerDto.getId()); + + assertNotNull(result); + assertEquals(1L, result.getId()); + assertEquals(bookingDto.getStart(), result.getStart()); + assertEquals(bookingDto.getEnd(), result.getEnd()); + assertEquals(itemDto.getId(), result.getItem().getId()); + assertEquals(bookerDto.getId(), result.getBooker().getId()); + assertEquals(BookingStatus.WAITING, result.getStatus()); } - @Nested // Тесты для утверждения бронирований - @DisplayName("Approving bookings") - class ApproveBookingTests { - @Test - @Transactional - @DisplayName("Successful booking approval") - void approveBookingTest() { - BookingDto createdBooking = bookingController.createBooking(bookingDto, bookerDto.getId()); - - BookingDto approvedBooking = bookingController.approve(createdBooking.getId(), ownerDto.getId(), true); - - assertEquals(BookingStatus.APPROVED, approvedBooking.getStatus()); - } - - @Test - @Transactional - @DisplayName("Successful booking rejection") - void rejectBookingTest() { - BookingDto createdBooking = bookingController.createBooking(bookingDto, bookerDto.getId()); - - BookingDto rejectedBooking = bookingController.approve(createdBooking.getId(), ownerDto.getId(), false); - - assertEquals(BookingStatus.REJECTED, rejectedBooking.getStatus()); - } - - @Test - @Transactional - @DisplayName("Error when non-owner tries to approve booking") - void nonOwnerApprovingBookingTest() { - BookingDto createdBooking = bookingController.createBooking(bookingDto, bookerDto.getId()); - - String randomEmail = "random" + System.currentTimeMillis() + "@email.com"; - User randomUser = User.builder() - .name("Random User") - .email(randomEmail) - .build(); - UserDto randomUserDto = userService.create(randomUser); - - when(bookingService.approve(eq(createdBooking.getId()), eq(randomUserDto.getId()), anyBoolean())) - .thenThrow(new ShareItException.ForbiddenException("Только владелец вещи может подтвердить бронирование")); - - assertThrows(ShareItException.ForbiddenException.class, - () -> bookingController.approve(createdBooking.getId(), randomUserDto.getId(), true)); - } - - @Test - @Transactional - @DisplayName("Error when approving already processed booking") - void approvingProcessedBookingTest() { - BookingDto createdBooking = bookingController.createBooking(bookingDto, bookerDto.getId()); - - bookingController.approve(createdBooking.getId(), ownerDto.getId(), true); - - when(bookingService.approve(eq(createdBooking.getId()), eq(ownerDto.getId()), anyBoolean())) - .thenThrow(new ShareItException.BadRequestException("Бронирование уже обработано")); - - assertThrows(ShareItException.BadRequestException.class, - () -> bookingController.approve(createdBooking.getId(), ownerDto.getId(), true)); - } + @Test + @DisplayName("Ошибка при попытке владельца забронировать свою вещь") + void createBooking_OwnerBookingOwnItem() { + when(bookingService.create(any(BookingDto.class), eq(ownerDto.getId()))) + .thenThrow(new ShareItException.NotFoundException("Владелец не может забронировать свою вещь")); + + assertThrows(ShareItException.NotFoundException.class, + () -> bookingController.createBooking(bookingDto, ownerDto.getId())); } - @Nested // Тесты для получения бронирований - @DisplayName("Getting bookings") - class GetBookingTests { - @Test - @Transactional - @DisplayName("Getting booking by ID") - void getBookingByIdTest() { - BookingDto createdBooking = bookingController.createBooking(bookingDto, bookerDto.getId()); - - BookingDto retrievedBooking = bookingController.getById(createdBooking.getId(), bookerDto.getId()); - - assertNotNull(retrievedBooking); - assertEquals(createdBooking.getId(), retrievedBooking.getId()); - assertEquals(createdBooking.getStart(), retrievedBooking.getStart()); - assertEquals(createdBooking.getEnd(), retrievedBooking.getEnd()); - } - - @Test - @Transactional - @DisplayName("Error when unauthorized user tries to get booking") - void unauthorizedGetBookingTest() { - BookingDto createdBooking = bookingController.createBooking(bookingDto, bookerDto.getId()); - - String randomEmail = "random" + System.currentTimeMillis() + "@email.com"; - User randomUser = User.builder() - .name("Random User") - .email(randomEmail) - .build(); - UserDto randomUserDto = userService.create(randomUser); - - when(bookingService.getById(eq(createdBooking.getId()), eq(randomUserDto.getId()))) - .thenThrow(new ShareItException.NotFoundException("Доступ запрещен")); - - assertThrows(ShareItException.NotFoundException.class, - () -> bookingController.getById(createdBooking.getId(), randomUserDto.getId())); - } - - @Test - @Transactional - @DisplayName("Getting all bookings by booker") - void getAllBookingsByBookerTest() { - bookingController.createBooking(bookingDto, bookerDto.getId()); - - List bookings = bookingController.getAllByBooker(bookerDto.getId(), "ALL", 0, 10); - - assertNotNull(bookings); - assertFalse(bookings.isEmpty()); - assertEquals(1, bookings.size()); - assertEquals(bookingDto.getStart(), bookings.get(0).getStart()); - assertEquals(bookingDto.getEnd(), bookings.get(0).getEnd()); - } - - @Test - @Transactional - @DisplayName("Getting all bookings by owner") - void getAllBookingsByOwnerTest() { - bookingController.createBooking(bookingDto, bookerDto.getId()); - - List bookings = bookingController.getAllByOwner(ownerDto.getId(), "ALL", 0, 10); - - assertNotNull(bookings); - assertFalse(bookings.isEmpty()); - assertEquals(1, bookings.size()); - assertEquals(bookingDto.getStart(), bookings.get(0).getStart()); - assertEquals(bookingDto.getEnd(), bookings.get(0).getEnd()); - } - - @Test - @Transactional - @DisplayName("Getting bookings with different states") - void getBookingsWithDifferentStatesTest() { - bookingController.createBooking(bookingDto, bookerDto.getId()); - - String[] states = {"CURRENT", "PAST", "FUTURE", "WAITING", "REJECTED"}; - - for (String state : states) { - List bookings = bookingController.getAllByBooker(bookerDto.getId(), state, 0, 10); - - assertNotNull(bookings); - } - } - - @Test - @Transactional - @DisplayName("Error when using invalid state") - void invalidStateTest() { - when(bookingService.getAllByBooker(eq(bookerDto.getId()), eq("INVALID_STATE"), anyInt(), anyInt())) - .thenThrow(new ShareItException.BadRequestException("Unknown state: INVALID_STATE")); - - assertThrows(ShareItException.BadRequestException.class, - () -> bookingController.getAllByBooker(bookerDto.getId(), "INVALID_STATE", 0, 10)); - } + @Test + @DisplayName("Ошибка при бронировании недоступной вещи") + void createBooking_UnavailableItem() { + when(bookingService.create(any(BookingDto.class), eq(bookerDto.getId()))) + .thenThrow(new ShareItException.BadRequestException("Вещь недоступна для бронирования")); + + assertThrows(ShareItException.BadRequestException.class, + () -> bookingController.createBooking(bookingDto, bookerDto.getId())); } - @Nested // Тесты для пагинации - @DisplayName("Pagination tests") - class PaginationTests { - @Test - @Transactional - @DisplayName("Pagination for booker bookings") - void paginationForBookerBookingsTest() { - bookingController.createBooking(bookingDto, bookerDto.getId()); - - List response1 = bookingController.getAllByBooker(bookerDto.getId(), "ALL", 0, 5); - List response2 = bookingController.getAllByBooker(bookerDto.getId(), "ALL", 0, 10); - List response3 = bookingController.getAllByBooker(bookerDto.getId(), "ALL", 5, 5); - - assertNotNull(response1); - assertNotNull(response2); - assertNotNull(response3); - } - - @Test - @Transactional - @DisplayName("Pagination for owner bookings") - void paginationForOwnerBookingsTest() { - bookingController.createBooking(bookingDto, bookerDto.getId()); - - List response1 = bookingController.getAllByOwner(ownerDto.getId(), "ALL", 0, 5); - List response2 = bookingController.getAllByOwner(ownerDto.getId(), "ALL", 0, 10); - List response3 = bookingController.getAllByOwner(ownerDto.getId(), "ALL", 5, 5); - - assertNotNull(response1); - assertNotNull(response2); - assertNotNull(response3); - } + @Test + @DisplayName("Ошибка при бронировании с некорректными датами") + void createBooking_InvalidDates() { + BookingDto invalidBooking = BookingDto.builder() + .itemId(itemDto.getId()) + .start(LocalDateTime.now().plusDays(2)) + .end(LocalDateTime.now().plusDays(1)) + .build(); + + when(bookingService.create(eq(invalidBooking), eq(bookerDto.getId()))) + .thenThrow(new ShareItException.BadRequestException("Некорректные даты бронирования")); + + assertThrows(ShareItException.BadRequestException.class, + () -> bookingController.createBooking(invalidBooking, bookerDto.getId())); } - @Nested // Тесты для проверки ошибок - @DisplayName("Error handling tests") - class ErrorHandlingTests { - @Test - @Transactional - @DisplayName("Error when booking not found") - void bookingNotFoundTest() { - Long nonExistentBookingId = 9999L; - - when(bookingService.getById(eq(nonExistentBookingId), anyLong())) - .thenThrow(new ShareItException.NotFoundException("Бронирование не найдено")); - - assertThrows(ShareItException.NotFoundException.class, - () -> bookingController.getById(nonExistentBookingId, bookerDto.getId())); - } - - @Test - @Transactional - @DisplayName("Error when user not found") - void userNotFoundTest() { - Long nonExistentUserId = 9999L; - - when(bookingService.getAllByBooker(eq(nonExistentUserId), anyString(), anyInt(), anyInt())) - .thenThrow(new ShareItException.NotFoundException("Пользователь не найден")); - - assertThrows(ShareItException.NotFoundException.class, - () -> bookingController.getAllByBooker(nonExistentUserId, "ALL", 0, 10)); - } + @Test + @DisplayName("Подтверждение бронирования - успешный случай") + void approve_Success() { + BookingDto result = bookingController.approve(1L, ownerDto.getId(), true); + + assertNotNull(result); + assertEquals(BookingStatus.APPROVED, result.getStatus()); + } + + @Test + @DisplayName("Отклонение бронирования - успешный случай") + void reject_Success() { + BookingDto result = bookingController.approve(1L, ownerDto.getId(), false); + + assertNotNull(result); + assertEquals(BookingStatus.REJECTED, result.getStatus()); + } + + @Test + @DisplayName("Ошибка при попытке не владельца подтвердить бронирование") + void approve_NonOwner() { + when(bookingService.approve(eq(1L), eq(bookerDto.getId()), anyBoolean())) + .thenThrow(new ShareItException.ForbiddenException("Только владелец вещи может подтвердить бронирование")); + + assertThrows(ShareItException.ForbiddenException.class, + () -> bookingController.approve(1L, bookerDto.getId(), true)); + } + + @Test + @DisplayName("Ошибка при подтверждении уже обработанного бронирования") + void approve_AlreadyProcessed() { + when(bookingService.approve(eq(1L), eq(ownerDto.getId()), eq(true))) + .thenThrow(new ShareItException.BadRequestException("Бронирование уже обработано")); + + assertThrows(ShareItException.BadRequestException.class, + () -> bookingController.approve(1L, ownerDto.getId(), true)); + } + + @Test + @DisplayName("Получение бронирования по ID - успешный случай") + void getById_Success() { + BookingDto result = bookingController.getById(1L, bookerDto.getId()); + + assertNotNull(result); + assertEquals(1L, result.getId()); + assertEquals(itemDto.getId(), result.getItem().getId()); + assertEquals(bookerDto.getId(), result.getBooker().getId()); + } + + @Test + @DisplayName("Ошибка при попытке неавторизованного пользователя получить бронирование") + void getById_Unauthorized() { + Long randomUserId = 999L; + when(bookingService.getById(eq(1L), eq(randomUserId))) + .thenThrow(new ShareItException.NotFoundException("Доступ запрещен")); + + assertThrows(ShareItException.NotFoundException.class, + () -> bookingController.getById(1L, randomUserId)); + } + + @ParameterizedTest + @ValueSource(strings = {"ALL", "CURRENT", "PAST", "FUTURE", "WAITING", "REJECTED"}) + @DisplayName("Получение всех бронирований арендатора с разными состояниями") + void getAllByBooker_DifferentStates(String state) { + List result = bookingController.getAllByBooker(bookerDto.getId(), state, 0, 10); + + assertNotNull(result); + assertFalse(result.isEmpty()); + assertEquals(1, result.size()); + } + + @ParameterizedTest + @ValueSource(strings = {"ALL", "CURRENT", "PAST", "FUTURE", "WAITING", "REJECTED"}) + @DisplayName("Получение всех бронирований владельца с разными состояниями") + void getAllByOwner_DifferentStates(String state) { + List result = bookingController.getAllByOwner(ownerDto.getId(), state, 0, 10); + + assertNotNull(result); + assertFalse(result.isEmpty()); + assertEquals(1, result.size()); + } + + @Test + @DisplayName("Ошибка при использовании некорректного состояния") + void getBookings_InvalidState() { + when(bookingService.getAllByBooker(eq(bookerDto.getId()), eq("INVALID_STATE"), anyInt(), anyInt())) + .thenThrow(new ShareItException.BadRequestException("Unknown state: INVALID_STATE")); + + assertThrows(ShareItException.BadRequestException.class, + () -> bookingController.getAllByBooker(bookerDto.getId(), "INVALID_STATE", 0, 10)); + } + + @ParameterizedTest + @DisplayName("Пагинация для бронирований арендатора") + @ValueSource(ints = {0, 5, 10}) + void getAllByBooker_Pagination(int from) { + List result = bookingController.getAllByBooker(bookerDto.getId(), "ALL", from, 5); + + assertNotNull(result); + assertFalse(result.isEmpty()); + } + + @ParameterizedTest + @DisplayName("Пагинация для бронирований владельца") + @ValueSource(ints = {0, 5, 10}) + void getAllByOwner_Pagination(int from) { + List result = bookingController.getAllByOwner(ownerDto.getId(), "ALL", from, 5); + + assertNotNull(result); + assertFalse(result.isEmpty()); + } + + @Test + @DisplayName("Ошибка при попытке получить несуществующее бронирование") + void getById_NotFound() { + Long nonExistentBookingId = 9999L; + when(bookingService.getById(eq(nonExistentBookingId), anyLong())) + .thenThrow(new ShareItException.NotFoundException("Бронирование не найдено")); + + assertThrows(ShareItException.NotFoundException.class, + () -> bookingController.getById(nonExistentBookingId, bookerDto.getId())); + } + + @Test + @DisplayName("Ошибка при попытке получить бронирования несуществующего пользователя") + void getAllByBooker_UserNotFound() { + Long nonExistentUserId = 9999L; + when(bookingService.getAllByBooker(eq(nonExistentUserId), anyString(), anyInt(), anyInt())) + .thenThrow(new ShareItException.NotFoundException("Пользователь не найден")); + + assertThrows(ShareItException.NotFoundException.class, + () -> bookingController.getAllByBooker(nonExistentUserId, "ALL", 0, 10)); } } \ No newline at end of file diff --git a/server/src/test/resources/application-test.properties b/server/src/test/resources/application-test.properties index 6b60a07..a45915e 100644 --- a/server/src/test/resources/application-test.properties +++ b/server/src/test/resources/application-test.properties @@ -1,9 +1,11 @@ spring.datasource.driver-class-name=org.h2.Driver -spring.datasource.url=jdbc:h2:mem:testdb;MODE=PostgreSQL +spring.datasource.url=jdbc:h2:mem:shareit;MODE=PostgreSQL spring.datasource.username=test spring.datasource.password=test + spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect spring.jpa.properties.hibernate.format_sql=true spring.jpa.show-sql=true + spring.sql.init.mode=never From 8fe2831d8352018692b0b7074a6b1cd11ebadfcd Mon Sep 17 00:00:00 2001 From: Viktor64 Date: Sun, 20 Apr 2025 23:27:55 +0400 Subject: [PATCH 05/14] =?UTF-8?q?=D0=A1=D0=BE=D0=B7=D0=B4=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=B2=D0=B5=D1=82=D0=BA=D1=83:=20add-item-requests-and-gateway?= =?UTF-8?q?.=201)=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D1=8F=D0=B5=D0=BC?= =?UTF-8?q?=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81=20=D0=B2=D0=B5=D1=89?= =?UTF-8?q?=D0=B8.=202)=20=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BB=20=D1=87=D0=B5=D1=82=D1=8B=D1=80=D0=B5=20=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D1=8B=D1=85=20=D1=8D=D0=BD=D0=B4=D0=BF=D0=BE=D0=B8?= =?UTF-8?q?=D0=BD=D1=82=D0=B0:=20POST=20/requests,=20GET=20/requests,=20GE?= =?UTF-8?q?T=20/requests/all,=20GET=20/requests/{requestId},=20GET=20/requ?= =?UTF-8?q?ests.=203)=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BE=D0=BF=D1=86=D0=B8=D1=8E=20=D0=BE=D1=82=D0=B2=D0=B5=D1=82?= =?UTF-8?q?=D0=B0=20=D0=BD=D0=B0=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81.?= =?UTF-8?q?=204)=20=D0=A0=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=20=D0=BD=D0=B0=202=20?= =?UTF-8?q?=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8F=20shareIt-server=20=D0=B8?= =?UTF-8?q?=20shareIt-gateway.=205)=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=82=D0=B5=D1=81=D1=82=D1=8B.=206)=20=D0=A1=D0=BE?= =?UTF-8?q?=D0=B7=D0=B4=D0=B0=D0=BB=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5=20?= =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82=D1=8B.=207)=20=D0=9E=D0=B1=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=B8=D0=BB=20=D0=BE=D1=81=D0=BD=D0=BE=D0=B2=D0=BD=D0=BE?= =?UTF-8?q?=D0=B9=20pom,=20suppressionsLocation=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BF=D0=BB=D0=B0=D0=B3=D0=B8=D0=BD=D0=B0=20Maven=20Checkstyle?= =?UTF-8?q?.=208)=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8=D0=BB=20=D1=82?= =?UTF-8?q?=D0=B5=D1=81=D1=82=D1=8B,=20=D1=87=D1=82=D0=BE=D0=B1=D1=8B=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B9=D1=82=D0=B8=20"build"=20=D0=B8=20Run?= =?UTF-8?q?=20application?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/api-tests.yml | 6 +++++ docker-compose.yml | 40 +++++++++++++++++++++++++++++++++ gateway/Dockerfile | 4 ++++ server/Dockerfile | 4 ++++ 4 files changed, 54 insertions(+) create mode 100644 docker-compose.yml create mode 100644 gateway/Dockerfile create mode 100644 server/Dockerfile diff --git a/.github/workflows/api-tests.yml b/.github/workflows/api-tests.yml index 51c3d8b..e6e7444 100644 --- a/.github/workflows/api-tests.yml +++ b/.github/workflows/api-tests.yml @@ -5,4 +5,10 @@ on: jobs: build: + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - uses: actions/checkout@v2 + uses: yandex-praktikum/java-shareit/.github/workflows/api-tests.yml@ci \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..4105666 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,40 @@ +version: '3.8' +services: + gateway: + build: ./gateway + container_name: shareit-gateway + ports: + - "8080:8080" + depends_on: + - server + environment: + - SHAREIT_SERVER_URL=http://server:9090 + + server: + build: ./server + container_name: shareit-server + ports: + - "9090:9090" + depends_on: + - db + environment: + - SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/shareit + - SPRING_DATASOURCE_USERNAME=postgres + - SPRING_DATASOURCE_PASSWORD=postgres + + db: + image: postgres:13.7-alpine + container_name: postgres + ports: + - "5432:5432" + environment: + - POSTGRES_PASSWORD=postgres + - POSTGRES_USER=postgres + - POSTGRES_DB=shareit + volumes: + - /var/lib/postgresql/data/ + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 5s + retries: 10 \ No newline at end of file diff --git a/gateway/Dockerfile b/gateway/Dockerfile new file mode 100644 index 0000000..b71999c --- /dev/null +++ b/gateway/Dockerfile @@ -0,0 +1,4 @@ +FROM openjdk:17 +ARG JAR_FILE=target/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["java", "-jar", "/app.jar"] \ No newline at end of file diff --git a/server/Dockerfile b/server/Dockerfile new file mode 100644 index 0000000..b71999c --- /dev/null +++ b/server/Dockerfile @@ -0,0 +1,4 @@ +FROM openjdk:17 +ARG JAR_FILE=target/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["java", "-jar", "/app.jar"] \ No newline at end of file From 3dbe28b81c60aa37f8fe678a62d586ba94bd0f12 Mon Sep 17 00:00:00 2001 From: Viktor64 Date: Sun, 20 Apr 2025 23:29:00 +0400 Subject: [PATCH 06/14] =?UTF-8?q?=D0=A1=D0=BE=D0=B7=D0=B4=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=B2=D0=B5=D1=82=D0=BA=D1=83:=20add-item-requests-and-gateway?= =?UTF-8?q?.=201)=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D1=8F=D0=B5=D0=BC?= =?UTF-8?q?=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81=20=D0=B2=D0=B5=D1=89?= =?UTF-8?q?=D0=B8.=202)=20=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BB=20=D1=87=D0=B5=D1=82=D1=8B=D1=80=D0=B5=20=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D1=8B=D1=85=20=D1=8D=D0=BD=D0=B4=D0=BF=D0=BE=D0=B8?= =?UTF-8?q?=D0=BD=D1=82=D0=B0:=20POST=20/requests,=20GET=20/requests,=20GE?= =?UTF-8?q?T=20/requests/all,=20GET=20/requests/{requestId},=20GET=20/requ?= =?UTF-8?q?ests.=203)=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BE=D0=BF=D1=86=D0=B8=D1=8E=20=D0=BE=D1=82=D0=B2=D0=B5=D1=82?= =?UTF-8?q?=D0=B0=20=D0=BD=D0=B0=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81.?= =?UTF-8?q?=204)=20=D0=A0=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=20=D0=BD=D0=B0=202=20?= =?UTF-8?q?=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8F=20shareIt-server=20=D0=B8?= =?UTF-8?q?=20shareIt-gateway.=205)=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=82=D0=B5=D1=81=D1=82=D1=8B.=206)=20=D0=A1=D0=BE?= =?UTF-8?q?=D0=B7=D0=B4=D0=B0=D0=BB=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5=20?= =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82=D1=8B.=207)=20=D0=9E=D0=B1=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=B8=D0=BB=20=D0=BE=D1=81=D0=BD=D0=BE=D0=B2=D0=BD=D0=BE?= =?UTF-8?q?=D0=B9=20pom,=20suppressionsLocation=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BF=D0=BB=D0=B0=D0=B3=D0=B8=D0=BD=D0=B0=20Maven=20Checkstyle?= =?UTF-8?q?.=208)=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8=D0=BB=20=D1=82?= =?UTF-8?q?=D0=B5=D1=81=D1=82=D1=8B,=20=D1=87=D1=82=D0=BE=D0=B1=D1=8B=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B9=D1=82=D0=B8=20"build"=20=D0=B8=20Run?= =?UTF-8?q?=20application?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/api-tests.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/api-tests.yml b/.github/workflows/api-tests.yml index e6e7444..51c3d8b 100644 --- a/.github/workflows/api-tests.yml +++ b/.github/workflows/api-tests.yml @@ -5,10 +5,4 @@ on: jobs: build: - runs-on: ubuntu-latest - timeout-minutes: 20 - - steps: - - uses: actions/checkout@v2 - uses: yandex-praktikum/java-shareit/.github/workflows/api-tests.yml@ci \ No newline at end of file From d9e273aa74e6105d3733648725b8f2b89e8253b9 Mon Sep 17 00:00:00 2001 From: Viktor64 Date: Mon, 21 Apr 2025 20:06:32 +0400 Subject: [PATCH 07/14] =?UTF-8?q?=D0=A1=D0=BE=D0=B7=D0=B4=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=B2=D0=B5=D1=82=D0=BA=D1=83:=20add-item-requests-and-gateway?= =?UTF-8?q?.=201)=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D1=8F=D0=B5=D0=BC?= =?UTF-8?q?=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81=20=D0=B2=D0=B5=D1=89?= =?UTF-8?q?=D0=B8.=202)=20=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BB=20=D1=87=D0=B5=D1=82=D1=8B=D1=80=D0=B5=20=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D1=8B=D1=85=20=D1=8D=D0=BD=D0=B4=D0=BF=D0=BE=D0=B8?= =?UTF-8?q?=D0=BD=D1=82=D0=B0:=20POST=20/requests,=20GET=20/requests,=20GE?= =?UTF-8?q?T=20/requests/all,=20GET=20/requests/{requestId},=20GET=20/requ?= =?UTF-8?q?ests.=203)=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BE=D0=BF=D1=86=D0=B8=D1=8E=20=D0=BE=D1=82=D0=B2=D0=B5=D1=82?= =?UTF-8?q?=D0=B0=20=D0=BD=D0=B0=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81.?= =?UTF-8?q?=204)=20=D0=A0=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=20=D0=BD=D0=B0=202=20?= =?UTF-8?q?=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8F=20shareIt-server=20=D0=B8?= =?UTF-8?q?=20shareIt-gateway.=205)=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=82=D0=B5=D1=81=D1=82=D1=8B.=206)=20=D0=A1=D0=BE?= =?UTF-8?q?=D0=B7=D0=B4=D0=B0=D0=BB=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5=20?= =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82=D1=8B.=207)=20=D0=9E=D0=B1=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=B8=D0=BB=20=D0=BE=D1=81=D0=BD=D0=BE=D0=B2=D0=BD=D0=BE?= =?UTF-8?q?=D0=B9=20pom,=20suppressionsLocation=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BF=D0=BB=D0=B0=D0=B3=D0=B8=D0=BD=D0=B0=20Maven=20Checkstyle?= =?UTF-8?q?.=208)=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8=D0=BB=20=D1=82?= =?UTF-8?q?=D0=B5=D1=81=D1=82=D1=8B,=20=D1=87=D1=82=D0=BE=D0=B1=D1=8B=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B9=D1=82=D0=B8=20"build"=20=D0=B8=20Run?= =?UTF-8?q?=20application=209)=20=D0=9F=D1=80=D0=BE=D0=B1=D1=83=D1=8E=20?= =?UTF-8?q?=D0=BE=D0=B1=D0=BE=D0=B9=D1=82=D0=B8=20Application=20Run?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .run/ShareItGateway.run.xml | 15 +++++++++++++++ .run/ShareItServer.run.xml | 15 +++++++++++++++ docker-compose.yml | 36 ++++++++++++++++++++++-------------- gateway/Dockerfile | 5 +++-- server/Dockerfile | 5 +++-- 5 files changed, 58 insertions(+), 18 deletions(-) create mode 100644 .run/ShareItGateway.run.xml create mode 100644 .run/ShareItServer.run.xml diff --git a/.run/ShareItGateway.run.xml b/.run/ShareItGateway.run.xml new file mode 100644 index 0000000..32c8129 --- /dev/null +++ b/.run/ShareItGateway.run.xml @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/.run/ShareItServer.run.xml b/.run/ShareItServer.run.xml new file mode 100644 index 0000000..a8ed9e5 --- /dev/null +++ b/.run/ShareItServer.run.xml @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 4105666..10746de 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ -version: '3.8' services: gateway: - build: ./gateway + build: gateway + image: shareit-gateway container_name: shareit-gateway ports: - "8080:8080" @@ -9,9 +9,11 @@ services: - server environment: - SHAREIT_SERVER_URL=http://server:9090 + restart: on-failure server: - build: ./server + build: server + image: shareit-server container_name: shareit-server ports: - "9090:9090" @@ -19,22 +21,28 @@ services: - db environment: - SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/shareit - - SPRING_DATASOURCE_USERNAME=postgres - - SPRING_DATASOURCE_PASSWORD=postgres + - SPRING_DATASOURCE_USERNAME=shareit + - SPRING_DATASOURCE_PASSWORD=shareit + restart: on-failure db: - image: postgres:13.7-alpine + build: server/docker/postgres + image: postgres:16.1 container_name: postgres ports: - - "5432:5432" + - "6541:5432" environment: - - POSTGRES_PASSWORD=postgres - - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=shareit + - POSTGRES_USER=shareit - POSTGRES_DB=shareit - volumes: - - /var/lib/postgresql/data/ healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres"] - interval: 5s + test: pg_isready -q -d $$POSTGRES_DB -U $$POSTGRES_USER timeout: 5s - retries: 10 \ No newline at end of file + interval: 5s + retries: 10 + volumes: + - ./volumes/postgres:/var/lib/postgresql/data + restart: on-failure + +volumes: + postgres: diff --git a/gateway/Dockerfile b/gateway/Dockerfile index b71999c..0ff1817 100644 --- a/gateway/Dockerfile +++ b/gateway/Dockerfile @@ -1,4 +1,5 @@ -FROM openjdk:17 +FROM eclipse-temurin:21-jre-jammy +VOLUME /tmp ARG JAR_FILE=target/*.jar COPY ${JAR_FILE} app.jar -ENTRYPOINT ["java", "-jar", "/app.jar"] \ No newline at end of file +ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"] \ No newline at end of file diff --git a/server/Dockerfile b/server/Dockerfile index b71999c..0ff1817 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -1,4 +1,5 @@ -FROM openjdk:17 +FROM eclipse-temurin:21-jre-jammy +VOLUME /tmp ARG JAR_FILE=target/*.jar COPY ${JAR_FILE} app.jar -ENTRYPOINT ["java", "-jar", "/app.jar"] \ No newline at end of file +ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"] \ No newline at end of file From 0cedba4b981d033a4810712c675d7766af839359 Mon Sep 17 00:00:00 2001 From: Viktor64 Date: Mon, 21 Apr 2025 20:15:41 +0400 Subject: [PATCH 08/14] =?UTF-8?q?=D0=A1=D0=BE=D0=B7=D0=B4=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=B2=D0=B5=D1=82=D0=BA=D1=83:=20add-item-requests-and-gateway?= =?UTF-8?q?.=201)=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D1=8F=D0=B5=D0=BC?= =?UTF-8?q?=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81=20=D0=B2=D0=B5=D1=89?= =?UTF-8?q?=D0=B8.=202)=20=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BB=20=D1=87=D0=B5=D1=82=D1=8B=D1=80=D0=B5=20=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D1=8B=D1=85=20=D1=8D=D0=BD=D0=B4=D0=BF=D0=BE=D0=B8?= =?UTF-8?q?=D0=BD=D1=82=D0=B0:=20POST=20/requests,=20GET=20/requests,=20GE?= =?UTF-8?q?T=20/requests/all,=20GET=20/requests/{requestId},=20GET=20/requ?= =?UTF-8?q?ests.=203)=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BE=D0=BF=D1=86=D0=B8=D1=8E=20=D0=BE=D1=82=D0=B2=D0=B5=D1=82?= =?UTF-8?q?=D0=B0=20=D0=BD=D0=B0=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81.?= =?UTF-8?q?=204)=20=D0=A0=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=20=D0=BD=D0=B0=202=20?= =?UTF-8?q?=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8F=20shareIt-server=20=D0=B8?= =?UTF-8?q?=20shareIt-gateway.=205)=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=82=D0=B5=D1=81=D1=82=D1=8B.=206)=20=D0=A1=D0=BE?= =?UTF-8?q?=D0=B7=D0=B4=D0=B0=D0=BB=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5=20?= =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82=D1=8B.=207)=20=D0=9E=D0=B1=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=B8=D0=BB=20=D0=BE=D1=81=D0=BD=D0=BE=D0=B2=D0=BD=D0=BE?= =?UTF-8?q?=D0=B9=20pom,=20suppressionsLocation=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BF=D0=BB=D0=B0=D0=B3=D0=B8=D0=BD=D0=B0=20Maven=20Checkstyle?= =?UTF-8?q?.=208)=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8=D0=BB=20=D1=82?= =?UTF-8?q?=D0=B5=D1=81=D1=82=D1=8B,=20=D1=87=D1=82=D0=BE=D0=B1=D1=8B=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B9=D1=82=D0=B8=20"build"=20=D0=B8=20Run?= =?UTF-8?q?=20application=209)=20=D0=9F=D1=80=D0=BE=D0=B1=D1=83=D1=8E=20?= =?UTF-8?q?=D0=BE=D0=B1=D0=BE=D0=B9=D1=82=D0=B8=20Application=20Run?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 51 +++++++----- server/pom.xml | 4 + server/wait-for-it.sh | 182 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 216 insertions(+), 21 deletions(-) create mode 100644 server/wait-for-it.sh diff --git a/docker-compose.yml b/docker-compose.yml index 10746de..3ab8885 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,48 +1,57 @@ +version: '3.8' + services: gateway: - build: gateway - image: shareit-gateway + build: ./gateway container_name: shareit-gateway ports: - "8080:8080" depends_on: - - server + server: + condition: service_healthy environment: - SHAREIT_SERVER_URL=http://server:9090 - restart: on-failure + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"] + interval: 10s + timeout: 5s + retries: 10 server: - build: server - image: shareit-server + build: ./server container_name: shareit-server ports: - "9090:9090" depends_on: - - db + db: + condition: service_healthy environment: - SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/shareit - - SPRING_DATASOURCE_USERNAME=shareit - - SPRING_DATASOURCE_PASSWORD=shareit - restart: on-failure + - SPRING_DATASOURCE_USERNAME=postgres + - SPRING_DATASOURCE_PASSWORD=postgres + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9090/actuator/health"] + interval: 10s + timeout: 5s + retries: 10 + command: ["./wait-for-it.sh", "db:5432", "--timeout=120", "--", "java", "-jar", "app.jar"] db: - build: server/docker/postgres - image: postgres:16.1 + image: postgres:13.7-alpine container_name: postgres ports: - - "6541:5432" + - "5432:5432" environment: - - POSTGRES_PASSWORD=shareit - - POSTGRES_USER=shareit + - POSTGRES_PASSWORD=postgres + - POSTGRES_USER=postgres - POSTGRES_DB=shareit + volumes: + - postgres_data:/var/lib/postgresql/data healthcheck: - test: pg_isready -q -d $$POSTGRES_DB -U $$POSTGRES_USER - timeout: 5s + test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s + timeout: 5s retries: 10 - volumes: - - ./volumes/postgres:/var/lib/postgresql/data - restart: on-failure volumes: - postgres: + postgres_data: \ No newline at end of file diff --git a/server/pom.xml b/server/pom.xml index 4baf7ce..de5f2a1 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -45,6 +45,10 @@ spring-boot-starter-test test + + org.springframework.boot + spring-boot-starter-actuator + diff --git a/server/wait-for-it.sh b/server/wait-for-it.sh new file mode 100644 index 0000000..3974640 --- /dev/null +++ b/server/wait-for-it.sh @@ -0,0 +1,182 @@ +#!/usr/bin/env bash +# Use this script to test if a given TCP host/port are available + +WAITFORIT_cmdname=${0##*/} + +echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } + +usage() +{ + cat << USAGE >&2 +Usage: + $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args] + -h HOST | --host=HOST Host or IP under test + -p PORT | --port=PORT TCP port under test + Alternatively, you specify the host and port as host:port + -s | --strict Only execute subcommand if the test succeeds + -q | --quiet Don't output any status messages + -t TIMEOUT | --timeout=TIMEOUT + Timeout in seconds, zero for no timeout + -- COMMAND ARGS Execute command with args after the test finishes +USAGE + exit 1 +} + +wait_for() +{ + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + else + echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout" + fi + WAITFORIT_start_ts=$(date +%s) + while : + do + if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then + nc -z $WAITFORIT_HOST $WAITFORIT_PORT + WAITFORIT_result=$? + else + (echo -n > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1 + WAITFORIT_result=$? + fi + if [[ $WAITFORIT_result -eq 0 ]]; then + WAITFORIT_end_ts=$(date +%s) + echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds" + break + fi + sleep 1 + done + return $WAITFORIT_result +} + +wait_for_wrapper() +{ + # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 + if [[ $WAITFORIT_QUIET -eq 1 ]]; then + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + else + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + fi + WAITFORIT_PID=$! + trap "kill -INT -$WAITFORIT_PID" INT + wait $WAITFORIT_PID + WAITFORIT_RESULT=$? + if [[ $WAITFORIT_RESULT -ne 0 ]]; then + echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + fi + return $WAITFORIT_RESULT +} + +# process arguments +while [[ $# -gt 0 ]] +do + case "$1" in + *:* ) + WAITFORIT_hostport=(${1//:/ }) + WAITFORIT_HOST=${WAITFORIT_hostport[0]} + WAITFORIT_PORT=${WAITFORIT_hostport[1]} + shift 1 + ;; + --child) + WAITFORIT_CHILD=1 + shift 1 + ;; + -q | --quiet) + WAITFORIT_QUIET=1 + shift 1 + ;; + -s | --strict) + WAITFORIT_STRICT=1 + shift 1 + ;; + -h) + WAITFORIT_HOST="$2" + if [[ $WAITFORIT_HOST == "" ]]; then break; fi + shift 2 + ;; + --host=*) + WAITFORIT_HOST="${1#*=}" + shift 1 + ;; + -p) + WAITFORIT_PORT="$2" + if [[ $WAITFORIT_PORT == "" ]]; then break; fi + shift 2 + ;; + --port=*) + WAITFORIT_PORT="${1#*=}" + shift 1 + ;; + -t) + WAITFORIT_TIMEOUT="$2" + if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi + shift 2 + ;; + --timeout=*) + WAITFORIT_TIMEOUT="${1#*=}" + shift 1 + ;; + --) + shift + WAITFORIT_CLI=("$@") + break + ;; + --help) + usage + ;; + *) + echoerr "Unknown argument: $1" + usage + ;; + esac +done + +if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then + echoerr "Error: you need to provide a host and port to test." + usage +fi + +WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15} +WAITFORIT_STRICT=${WAITFORIT_STRICT:-0} +WAITFORIT_CHILD=${WAITFORIT_CHILD:-0} +WAITFORIT_QUIET=${WAITFORIT_QUIET:-0} + +# Check to see if timeout is from busybox? +WAITFORIT_TIMEOUT_PATH=$(type -p timeout) +WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH) + +WAITFORIT_BUSYTIMEFLAG="" +if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then + WAITFORIT_ISBUSY=1 + # Check if busybox timeout uses -t flag + # (recent Alpine versions don't support -t anymore) + if timeout &>/dev/stdout | grep -q -e '-t '; then + WAITFORIT_BUSYTIMEFLAG="-t" + fi +else + WAITFORIT_ISBUSY=0 +fi + +if [[ $WAITFORIT_CHILD -gt 0 ]]; then + wait_for + WAITFORIT_RESULT=$? + exit $WAITFORIT_RESULT +else + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + wait_for_wrapper + WAITFORIT_RESULT=$? + else + wait_for + WAITFORIT_RESULT=$? + fi +fi + +if [[ $WAITFORIT_CLI != "" ]]; then + if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then + echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess" + exit $WAITFORIT_RESULT + fi + exec "${WAITFORIT_CLI[@]}" +else + exit $WAITFORIT_RESULT +fi \ No newline at end of file From 7708b1f39ec7bcdc8228dfac590ddab2a5dc5a5b Mon Sep 17 00:00:00 2001 From: Viktor64 Date: Tue, 22 Apr 2025 20:34:33 +0400 Subject: [PATCH 09/14] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=B0=D0=BB=20=D0=B2=D0=B5=D1=82=D0=BA=D1=83:=20add-item?= =?UTF-8?q?-requests-and-gateway.=201)=20=D0=A3=D0=B4=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BD=D0=B5=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D0=B7=D1=83=D0=B5=D0=BC=D1=8B=D0=B9=20=D0=BA=D0=BB=D0=B0=D1=81?= =?UTF-8?q?=D1=81=20BookingStateValidator.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shareit/booking/BookingStateValidator.java | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 gateway/src/main/java/ru/practicum/shareit/booking/BookingStateValidator.java diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/BookingStateValidator.java b/gateway/src/main/java/ru/practicum/shareit/booking/BookingStateValidator.java deleted file mode 100644 index 31a2384..0000000 --- a/gateway/src/main/java/ru/practicum/shareit/booking/BookingStateValidator.java +++ /dev/null @@ -1,15 +0,0 @@ -package ru.practicum.shareit.booking; - -import java.util.Arrays; -import java.util.List; - -public class BookingStateValidator { - private static final List VALID_STATES = Arrays.asList( - "ALL", "CURRENT", "PAST", "FUTURE", "WAITING", "REJECTED"); - - public static void validateState(String state) { - if (!VALID_STATES.contains(state)) { - throw new IllegalArgumentException("Unknown state: " + state); - } - } -} \ No newline at end of file From 845086664655f107d844ac5ed6c2185bf388c800 Mon Sep 17 00:00:00 2001 From: Viktor64 Date: Tue, 22 Apr 2025 21:14:10 +0400 Subject: [PATCH 10/14] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=B0=D0=BB=20=D0=B2=D0=B5=D1=82=D0=BA=D1=83:=20add-item?= =?UTF-8?q?-requests-and-gateway.=201)=20=D0=A3=D0=B4=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BD=D0=B5=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D0=B7=D1=83=D0=B5=D0=BC=D1=8B=D0=B9=20=D0=BA=D0=BB=D0=B0=D1=81?= =?UTF-8?q?=D1=81=20BookingStateValidator.=202)=20=D0=9F=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D0=BF=D0=B8=D1=81=D0=B0=D0=BB=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4?= =?UTF-8?q?=20from=20=D0=B2=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D0=B5=20Booki?= =?UTF-8?q?ngState=20=D1=81=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=D0=BC=20Stream=20API.=203)?= =?UTF-8?q?=20=D0=97=D0=B0=D0=BC=D0=B5=D0=BD=D0=B8=D0=BB=20Map=20=D0=B2=20ErrorHandler=20=D0=BD=D0=B0=20=D0=BE=D1=82?= =?UTF-8?q?=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20=D0=BA=D0=BB=D0=B0?= =?UTF-8?q?=D1=81=D1=81=20ErrorResponse.=204)=20=D0=97=D0=B0=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=BB=20e=20=D0=BD=D0=B0=20=D0=B1=D0=BE=D0=BB?= =?UTF-8?q?=D0=B5=D0=B5=20=D0=BF=D0=BE=D0=B4=D1=85=D0=BE=D0=B4=D1=8F=D1=89?= =?UTF-8?q?=D1=83=D1=8E=20exception.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shareit/booking/dto/BookingState.java | 10 ++-- .../shareit/exception/ErrorHandler.java | 47 ++++++++++++++----- .../shareit/exception/ErrorResponse.java | 19 ++++++++ 3 files changed, 57 insertions(+), 19 deletions(-) create mode 100644 gateway/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingState.java b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingState.java index 26e1859..c40cc2b 100644 --- a/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingState.java +++ b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingState.java @@ -1,5 +1,6 @@ package ru.practicum.shareit.booking.dto; +import java.util.Arrays; import java.util.Optional; public enum BookingState { @@ -11,11 +12,8 @@ public enum BookingState { REJECTED; public static Optional from(String stringState) { - for (BookingState state : values()) { - if (state.name().equalsIgnoreCase(stringState)) { - return Optional.of(state); - } - } - return Optional.empty(); + return Arrays.stream(values()) + .filter(state -> state.name().equalsIgnoreCase(stringState)) + .findFirst(); } } \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java b/gateway/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java index bdaaa58..31b7034 100644 --- a/gateway/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java +++ b/gateway/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java @@ -1,5 +1,6 @@ package ru.practicum.shareit.exception; +import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.web.bind.MethodArgumentNotValidException; @@ -7,33 +8,53 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; -import java.util.Map; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; @RestControllerAdvice @Slf4j public class ErrorHandler { + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); @ExceptionHandler @ResponseStatus(HttpStatus.BAD_REQUEST) - public Map handleValidationException(final MethodArgumentNotValidException e) { - log.error("Ошибка валидации: {}", e.getMessage()); - return Map.of("error", "Ошибка валидации", - "message", e.getBindingResult().getFieldError().getDefaultMessage()); + public ErrorResponse handleValidationException(final MethodArgumentNotValidException exception, + HttpServletRequest request) { + log.error("Ошибка валидации: {}", exception.getMessage()); + return ErrorResponse.builder() + .error("Ошибка валидации") + .message(exception.getBindingResult().getFieldError().getDefaultMessage()) + .timestamp(LocalDateTime.now().format(FORMATTER)) + .path(request.getRequestURI()) + .status(HttpStatus.BAD_REQUEST.value()) + .build(); } @ExceptionHandler @ResponseStatus(HttpStatus.BAD_REQUEST) - public Map handleIllegalArgumentException(final IllegalArgumentException e) { - log.error("Ошибка в аргументах запроса: {}", e.getMessage()); - return Map.of("error", "Ошибка в аргументах запроса", - "message", e.getMessage()); + public ErrorResponse handleIllegalArgumentException(final IllegalArgumentException exception, + HttpServletRequest request) { + log.error("Ошибка в аргументах запроса: {}", exception.getMessage()); + return ErrorResponse.builder() + .error("Ошибка в аргументах запроса") + .message(exception.getMessage()) + .timestamp(LocalDateTime.now().format(FORMATTER)) + .path(request.getRequestURI()) + .status(HttpStatus.BAD_REQUEST.value()) + .build(); } @ExceptionHandler @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - public Map handleException(final Exception e) { - log.error("Непредвиденная ошибка: {}", e.getMessage(), e); - return Map.of("error", "Непредвиденная ошибка", - "message", e.getMessage()); + public ErrorResponse handleException(final Exception exception, + HttpServletRequest request) { + log.error("Непредвиденная ошибка: {}", exception.getMessage(), exception); + return ErrorResponse.builder() + .error("Непредвиденная ошибка") + .message(exception.getMessage()) + .timestamp(LocalDateTime.now().format(FORMATTER)) + .path(request.getRequestURI()) + .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) + .build(); } } \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java b/gateway/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java new file mode 100644 index 0000000..e5005b7 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java @@ -0,0 +1,19 @@ +package ru.practicum.shareit.exception; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErrorResponse { + private String error; + private String message; + + private String timestamp; + private String path; + private Integer status; +} \ No newline at end of file From 9aebbb7fe433a63b4fb6536dbae4aec266fa035b Mon Sep 17 00:00:00 2001 From: Viktor64 Date: Tue, 22 Apr 2025 22:22:56 +0400 Subject: [PATCH 11/14] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=B0=D0=BB=20=D0=B2=D0=B5=D1=82=D0=BA=D1=83:=20add-item?= =?UTF-8?q?-requests-and-gateway.=201)=20=D0=A3=D0=B4=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BD=D0=B5=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D0=B7=D1=83=D0=B5=D0=BC=D1=8B=D0=B9=20=D0=BA=D0=BB=D0=B0=D1=81?= =?UTF-8?q?=D1=81=20BookingStateValidator.=202)=20=D0=9F=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D0=BF=D0=B8=D1=81=D0=B0=D0=BB=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4?= =?UTF-8?q?=20from=20=D0=B2=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D0=B5=20Booki?= =?UTF-8?q?ngState=20=D1=81=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=D0=BC=20Stream=20API.=203)?= =?UTF-8?q?=20=D0=97=D0=B0=D0=BC=D0=B5=D0=BD=D0=B8=D0=BB=20Map=20=D0=B2=20ErrorHandler=20=D0=BD=D0=B0=20=D0=BE=D1=82?= =?UTF-8?q?=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20=D0=BA=D0=BB=D0=B0?= =?UTF-8?q?=D1=81=D1=81=20ErrorResponse.=204)=20=D0=97=D0=B0=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=BB=20e=20=D0=BD=D0=B0=20=D0=B1=D0=BE=D0=BB?= =?UTF-8?q?=D0=B5=D0=B5=20=D0=BF=D0=BE=D0=B4=D1=85=D0=BE=D0=B4=D1=8F=D1=89?= =?UTF-8?q?=D1=83=D1=8E=20exception.=205)=20=D0=92=D1=8B=D0=BD=D0=B5=D1=81?= =?UTF-8?q?=20USER=5FID=5FHEADER=20=D0=B2=20=D0=BA=D0=BE=D0=BD=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D0=BD=D1=82=D1=83=20=D0=B2=20=D0=BE=D0=B1=D0=BE=D0=B8?= =?UTF-8?q?=D1=85=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8F=D1=85=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=B2=D1=81=D0=B5=D1=85=20=D0=BA=D0=BE=D0=BD?= =?UTF-8?q?=D1=82=D1=80=D0=BE=D0=BB=D0=BB=D0=B5=D1=80=D0=BE=D0=B2.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../booking/controller/BookingController.java | 14 +++++++------- .../shareit/item/controller/ItemController.java | 12 ++++++------ .../request/controller/ItemRequestController.java | 12 ++++++------ .../java/ru/practicum/shareit/util/Constants.java | 11 +++++++++++ .../booking/controller/BookingController.java | 12 ++++++------ .../shareit/item/controller/ItemController.java | 12 ++++++------ .../ru/practicum/shareit/item/util/Constants.java | 5 ----- .../request/controller/ItemRequestController.java | 10 +++++----- .../java/ru/practicum/shareit/util/Constants.java | 11 +++++++++++ 9 files changed, 58 insertions(+), 41 deletions(-) create mode 100644 gateway/src/main/java/ru/practicum/shareit/util/Constants.java delete mode 100644 server/src/main/java/ru/practicum/shareit/item/util/Constants.java create mode 100644 server/src/main/java/ru/practicum/shareit/util/Constants.java diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java b/gateway/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java index 9a3c787..6a77044 100644 --- a/gateway/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java +++ b/gateway/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java @@ -21,6 +21,7 @@ import ru.practicum.shareit.booking.client.BookingClient; import ru.practicum.shareit.booking.dto.BookingDto; import ru.practicum.shareit.booking.dto.BookingState; +import ru.practicum.shareit.util.Constants; @Controller @RequestMapping(path = "/bookings") @@ -29,19 +30,18 @@ @Validated public class BookingController { private final BookingClient bookingClient; - private static final String USER_ID_HEADER = "X-Sharer-User-Id"; @PostMapping - @ResponseStatus(HttpStatus.CREATED) // Важно: тесты ожидают статус 201 CREATED + @ResponseStatus(HttpStatus.CREATED) public ResponseEntity createBooking(@Valid @RequestBody BookingDto bookingDto, - @RequestHeader(USER_ID_HEADER) Long userId) { + @RequestHeader(Constants.USER_ID_HEADER) Long userId) { log.info("Creating booking {}, userId={}", bookingDto, userId); return bookingClient.createBooking(bookingDto, userId); } @PatchMapping("/{bookingId}") public ResponseEntity approve(@PathVariable Long bookingId, - @RequestHeader(USER_ID_HEADER) Long userId, + @RequestHeader(Constants.USER_ID_HEADER) Long userId, @RequestParam Boolean approved) { log.info("Patch booking {}, userId={}, approved={}", bookingId, userId, approved); return bookingClient.approve(bookingId, userId, approved); @@ -49,14 +49,14 @@ public ResponseEntity approve(@PathVariable Long bookingId, @GetMapping("/{bookingId}") public ResponseEntity getById(@PathVariable Long bookingId, - @RequestHeader(USER_ID_HEADER) Long userId) { + @RequestHeader(Constants.USER_ID_HEADER) Long userId) { log.info("Get booking {}, userId={}", bookingId, userId); return bookingClient.getById(bookingId, userId); } @GetMapping public ResponseEntity getAllByBooker( - @RequestHeader(USER_ID_HEADER) Long userId, + @RequestHeader(Constants.USER_ID_HEADER) Long userId, @RequestParam(name = "state", defaultValue = "all") String stateParam, @RequestParam(defaultValue = "0") @PositiveOrZero int from, @RequestParam(defaultValue = "10") @Positive int size) { @@ -68,7 +68,7 @@ public ResponseEntity getAllByBooker( @GetMapping("/owner") public ResponseEntity getAllByOwner( - @RequestHeader(USER_ID_HEADER) Long userId, + @RequestHeader(Constants.USER_ID_HEADER) Long userId, @RequestParam(name = "state", defaultValue = "all") String stateParam, @RequestParam(defaultValue = "0") @PositiveOrZero int from, @RequestParam(defaultValue = "10") @Positive int size) { diff --git a/gateway/src/main/java/ru/practicum/shareit/item/controller/ItemController.java b/gateway/src/main/java/ru/practicum/shareit/item/controller/ItemController.java index 06f024b..b35ef6c 100644 --- a/gateway/src/main/java/ru/practicum/shareit/item/controller/ItemController.java +++ b/gateway/src/main/java/ru/practicum/shareit/item/controller/ItemController.java @@ -22,6 +22,7 @@ import ru.practicum.shareit.item.client.ItemClient; import ru.practicum.shareit.item.dto.CommentDto; import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.util.Constants; import java.util.ArrayList; import java.util.HashMap; @@ -35,17 +36,16 @@ public class ItemController { private final ItemClient itemClient; private final ObjectMapper objectMapper; - private static final String USER_ID_HEADER = "X-Sharer-User-Id"; @GetMapping - public ResponseEntity getAll(@RequestHeader(USER_ID_HEADER) Long userId) { + public ResponseEntity getAll(@RequestHeader(Constants.USER_ID_HEADER) Long userId) { log.info("Get items by owner userId={}", userId); return itemClient.getAll(userId); } @GetMapping("/{id}") @ResponseBody - public Map getById(@PathVariable Long id, @RequestHeader(USER_ID_HEADER) Long userId) { + public Map getById(@PathVariable Long id, @RequestHeader(Constants.USER_ID_HEADER) Long userId) { log.info("Get item {}, userId={}", id, userId); ResponseEntity response = itemClient.getById(id, userId); @@ -69,7 +69,7 @@ public Map getById(@PathVariable Long id, @RequestHeader(USER_ID @PostMapping @ResponseStatus(HttpStatus.CREATED) - public ResponseEntity create(@RequestHeader(USER_ID_HEADER) Long userId, + public ResponseEntity create(@RequestHeader(Constants.USER_ID_HEADER) Long userId, @Valid @RequestBody ItemDto itemDto) { log.info("Creating item {}, userId={}", itemDto, userId); return itemClient.create(itemDto, userId); @@ -78,7 +78,7 @@ public ResponseEntity create(@RequestHeader(USER_ID_HEADER) Long userId, @PatchMapping("/{id}") public ResponseEntity update(@RequestBody ItemDto itemDto, @PathVariable Long id, - @RequestHeader(USER_ID_HEADER) Long userId) { + @RequestHeader(Constants.USER_ID_HEADER) Long userId) { log.info("Updating item {}, userId={}", id, userId); return itemClient.update(itemDto, id, userId); } @@ -98,7 +98,7 @@ public ResponseEntity search(@RequestParam String text) { @PostMapping("/{itemId}/comment") public ResponseEntity createComment(@PathVariable Long itemId, @Valid @RequestBody CommentDto commentDto, - @RequestHeader(USER_ID_HEADER) Long userId) { + @RequestHeader(Constants.USER_ID_HEADER) Long userId) { log.info("Creating comment {}, itemId={}, userId={}", commentDto, itemId, userId); return itemClient.createComment(itemId, commentDto, userId); } diff --git a/gateway/src/main/java/ru/practicum/shareit/request/controller/ItemRequestController.java b/gateway/src/main/java/ru/practicum/shareit/request/controller/ItemRequestController.java index b17a49c..c1472a8 100644 --- a/gateway/src/main/java/ru/practicum/shareit/request/controller/ItemRequestController.java +++ b/gateway/src/main/java/ru/practicum/shareit/request/controller/ItemRequestController.java @@ -19,6 +19,7 @@ import org.springframework.web.bind.annotation.ResponseStatus; import ru.practicum.shareit.request.client.ItemRequestClient; import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.util.Constants; @Controller @RequestMapping(path = "/requests") @@ -27,12 +28,11 @@ @Validated public class ItemRequestController { private final ItemRequestClient itemRequestClient; - private static final String USER_ID_HEADER = "X-Sharer-User-Id"; @PostMapping - @ResponseStatus(HttpStatus.CREATED) // Важно: тесты ожидают статус 201 CREATED + @ResponseStatus(HttpStatus.CREATED) public ResponseEntity create( - @RequestHeader(USER_ID_HEADER) Long userId, + @RequestHeader(Constants.USER_ID_HEADER) Long userId, @Valid @RequestBody ItemRequestDto itemRequestDto) { log.info("Creating request {}, userId={}", itemRequestDto, userId); return itemRequestClient.create(userId, itemRequestDto); @@ -40,14 +40,14 @@ public ResponseEntity create( @GetMapping public ResponseEntity getAllByRequestor( - @RequestHeader(USER_ID_HEADER) Long userId) { + @RequestHeader(Constants.USER_ID_HEADER) Long userId) { log.info("Get requests by requestor userId={}", userId); return itemRequestClient.getAllByRequestor(userId); } @GetMapping("/all") public ResponseEntity getAll( - @RequestHeader(USER_ID_HEADER) Long userId, + @RequestHeader(Constants.USER_ID_HEADER) Long userId, @RequestParam(defaultValue = "0") @PositiveOrZero int from, @RequestParam(defaultValue = "10") @Positive int size) { log.info("Get all requests userId={}, from={}, size={}", userId, from, size); @@ -57,7 +57,7 @@ public ResponseEntity getAll( @GetMapping("/{requestId}") public ResponseEntity getById( @PathVariable Long requestId, - @RequestHeader(USER_ID_HEADER) Long userId) { + @RequestHeader(Constants.USER_ID_HEADER) Long userId) { log.info("Get request {}, userId={}", requestId, userId); return itemRequestClient.getById(requestId, userId); } diff --git a/gateway/src/main/java/ru/practicum/shareit/util/Constants.java b/gateway/src/main/java/ru/practicum/shareit/util/Constants.java new file mode 100644 index 0000000..9f4bf8c --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/util/Constants.java @@ -0,0 +1,11 @@ +package ru.practicum.shareit.util; + + +public class Constants { + + public static final String USER_ID_HEADER = "X-Sharer-User-Id"; + + private Constants() { + throw new IllegalStateException("Utility class"); + } +} \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java b/server/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java index ca87f03..464be92 100644 --- a/server/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java +++ b/server/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java @@ -12,6 +12,7 @@ import org.springframework.web.bind.annotation.RestController; import ru.practicum.shareit.booking.dto.BookingDto; import ru.practicum.shareit.booking.service.BookingService; +import ru.practicum.shareit.util.Constants; import java.util.List; @@ -20,30 +21,29 @@ @RequiredArgsConstructor public class BookingController { private final BookingService bookingService; - private static final String USER_ID_HEADER = "X-Sharer-User-Id"; @PostMapping public BookingDto createBooking(@RequestBody BookingDto bookingDto, - @RequestHeader(USER_ID_HEADER) Long userId) { + @RequestHeader(Constants.USER_ID_HEADER) Long userId) { return bookingService.create(bookingDto, userId); } @PatchMapping("/{bookingId}") public BookingDto approve(@PathVariable Long bookingId, - @RequestHeader(USER_ID_HEADER) Long userId, + @RequestHeader(Constants.USER_ID_HEADER) Long userId, @RequestParam Boolean approved) { return bookingService.approve(bookingId, userId, approved); } @GetMapping("/{bookingId}") public BookingDto getById(@PathVariable Long bookingId, - @RequestHeader(USER_ID_HEADER) Long userId) { + @RequestHeader(Constants.USER_ID_HEADER) Long userId) { return bookingService.getById(bookingId, userId); } @GetMapping public List getAllByBooker( - @RequestHeader(USER_ID_HEADER) Long userId, + @RequestHeader(Constants.USER_ID_HEADER) Long userId, @RequestParam(defaultValue = "ALL") String state, @RequestParam(defaultValue = "0") int from, @RequestParam(defaultValue = "10") int size) { @@ -52,7 +52,7 @@ public List getAllByBooker( @GetMapping("/owner") public List getAllByOwner( - @RequestHeader(USER_ID_HEADER) Long userId, + @RequestHeader(Constants.USER_ID_HEADER) Long userId, @RequestParam(defaultValue = "ALL") String state, @RequestParam(defaultValue = "0") int from, @RequestParam(defaultValue = "10") int size) { diff --git a/server/src/main/java/ru/practicum/shareit/item/controller/ItemController.java b/server/src/main/java/ru/practicum/shareit/item/controller/ItemController.java index 61e79e8..e926bd5 100644 --- a/server/src/main/java/ru/practicum/shareit/item/controller/ItemController.java +++ b/server/src/main/java/ru/practicum/shareit/item/controller/ItemController.java @@ -14,6 +14,7 @@ import ru.practicum.shareit.item.dto.CommentDto; import ru.practicum.shareit.item.dto.ItemDto; import ru.practicum.shareit.item.service.ItemService; +import ru.practicum.shareit.util.Constants; import java.util.List; @@ -22,26 +23,25 @@ @RequiredArgsConstructor public class ItemController { private final ItemService itemService; - private static final String USER_ID_HEADER = "X-Sharer-User-Id"; @GetMapping - public List getAll(@RequestHeader(USER_ID_HEADER) Long userId) { + public List getAll(@RequestHeader(Constants.USER_ID_HEADER) Long userId) { return itemService.getAll(userId); } @GetMapping("/{id}") - public ItemDto getById(@PathVariable Long id, @RequestHeader(USER_ID_HEADER) Long userId) { + public ItemDto getById(@PathVariable Long id, @RequestHeader(Constants.USER_ID_HEADER) Long userId) { return itemService.getById(id, userId); } @PostMapping - public ItemDto create(@RequestHeader(USER_ID_HEADER) Long userId, @RequestBody ItemDto itemDto) { + public ItemDto create(@RequestHeader(Constants.USER_ID_HEADER) Long userId, @RequestBody ItemDto itemDto) { return itemService.create(itemDto, userId); } @PatchMapping("/{id}") public ItemDto update(@RequestBody ItemDto itemDto, @PathVariable Long id, - @RequestHeader(USER_ID_HEADER) Long userId) { + @RequestHeader(Constants.USER_ID_HEADER) Long userId) { return itemService.update(itemDto, id, userId); } @@ -58,7 +58,7 @@ public List search(@RequestParam String text) { @PostMapping("/{itemId}/comment") public CommentDto createComment(@PathVariable Long itemId, @RequestBody CommentDto commentDto, - @RequestHeader(USER_ID_HEADER) Long userId) { + @RequestHeader(Constants.USER_ID_HEADER) Long userId) { return itemService.createComment(itemId, commentDto, userId); } } \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/item/util/Constants.java b/server/src/main/java/ru/practicum/shareit/item/util/Constants.java deleted file mode 100644 index d2d31b8..0000000 --- a/server/src/main/java/ru/practicum/shareit/item/util/Constants.java +++ /dev/null @@ -1,5 +0,0 @@ -package ru.practicum.shareit.item.util; - -public class Constants { - public static final String USER_ID_HEADER = "X-Sharer-User-Id"; -} \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/request/controller/ItemRequestController.java b/server/src/main/java/ru/practicum/shareit/request/controller/ItemRequestController.java index 83b91ab..e9dbbb6 100644 --- a/server/src/main/java/ru/practicum/shareit/request/controller/ItemRequestController.java +++ b/server/src/main/java/ru/practicum/shareit/request/controller/ItemRequestController.java @@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.RestController; import ru.practicum.shareit.request.dto.ItemRequestDto; import ru.practicum.shareit.request.service.ItemRequestService; +import ru.practicum.shareit.util.Constants; import java.util.List; @@ -19,24 +20,23 @@ @RequiredArgsConstructor public class ItemRequestController { private final ItemRequestService itemRequestService; - private static final String USER_ID_HEADER = "X-Sharer-User-Id"; @PostMapping public ItemRequestDto create( - @RequestHeader(USER_ID_HEADER) Long userId, + @RequestHeader(Constants.USER_ID_HEADER) Long userId, @RequestBody ItemRequestDto itemRequestDto) { return itemRequestService.create(itemRequestDto, userId); } @GetMapping public List getAllByRequestor( - @RequestHeader(USER_ID_HEADER) Long userId) { + @RequestHeader(Constants.USER_ID_HEADER) Long userId) { return itemRequestService.getAllByRequestor(userId); } @GetMapping("/all") public List getAll( - @RequestHeader(USER_ID_HEADER) Long userId, + @RequestHeader(Constants.USER_ID_HEADER) Long userId, @RequestParam(defaultValue = "0") int from, @RequestParam(defaultValue = "10") int size) { return itemRequestService.getAll(userId, from, size); @@ -45,7 +45,7 @@ public List getAll( @GetMapping("/{requestId}") public ItemRequestDto getById( @PathVariable Long requestId, - @RequestHeader(USER_ID_HEADER) Long userId) { + @RequestHeader(Constants.USER_ID_HEADER) Long userId) { return itemRequestService.getById(requestId, userId); } } \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/util/Constants.java b/server/src/main/java/ru/practicum/shareit/util/Constants.java new file mode 100644 index 0000000..9f4bf8c --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/util/Constants.java @@ -0,0 +1,11 @@ +package ru.practicum.shareit.util; + + +public class Constants { + + public static final String USER_ID_HEADER = "X-Sharer-User-Id"; + + private Constants() { + throw new IllegalStateException("Utility class"); + } +} \ No newline at end of file From 74d6ddc44a805019492e9b250788a27f6551454f Mon Sep 17 00:00:00 2001 From: Viktor64 Date: Tue, 22 Apr 2025 22:38:43 +0400 Subject: [PATCH 12/14] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=B0=D0=BB=20=D0=B2=D0=B5=D1=82=D0=BA=D1=83:=20add-item?= =?UTF-8?q?-requests-and-gateway.=201)=20=D0=A3=D0=B4=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BD=D0=B5=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D0=B7=D1=83=D0=B5=D0=BC=D1=8B=D0=B9=20=D0=BA=D0=BB=D0=B0=D1=81?= =?UTF-8?q?=D1=81=20BookingStateValidator.=202)=20=D0=9F=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D0=BF=D0=B8=D1=81=D0=B0=D0=BB=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4?= =?UTF-8?q?=20from=20=D0=B2=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D0=B5=20Booki?= =?UTF-8?q?ngState=20=D1=81=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=D0=BC=20Stream=20API.=203)?= =?UTF-8?q?=20=D0=97=D0=B0=D0=BC=D0=B5=D0=BD=D0=B8=D0=BB=20Map=20=D0=B2=20ErrorHandler=20=D0=BD=D0=B0=20=D0=BE=D1=82?= =?UTF-8?q?=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20=D0=BA=D0=BB=D0=B0?= =?UTF-8?q?=D1=81=D1=81=20ErrorResponse.=204)=20=D0=97=D0=B0=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=BB=20e=20=D0=BD=D0=B0=20=D0=B1=D0=BE=D0=BB?= =?UTF-8?q?=D0=B5=D0=B5=20=D0=BF=D0=BE=D0=B4=D1=85=D0=BE=D0=B4=D1=8F=D1=89?= =?UTF-8?q?=D1=83=D1=8E=20exception.=205)=20=D0=92=D1=8B=D0=BD=D0=B5=D1=81?= =?UTF-8?q?=20USER=5FID=5FHEADER=20=D0=B2=20=D0=BA=D0=BE=D0=BD=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D0=BD=D1=82=D1=83=20=D0=B2=20=D0=BE=D0=B1=D0=BE=D0=B8?= =?UTF-8?q?=D1=85=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8F=D1=85=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=B2=D1=81=D0=B5=D1=85=20=D0=BA=D0=BE=D0=BD?= =?UTF-8?q?=D1=82=D1=80=D0=BE=D0=BB=D0=BB=D0=B5=D1=80=D0=BE=D0=B2.=206)=20?= =?UTF-8?q?=D0=98=D0=B7=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=D1=81=D1=8F=20=D0=BE?= =?UTF-8?q?=D1=82=20instanceof.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../item/controller/ItemControllerAdvice.java | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/gateway/src/main/java/ru/practicum/shareit/item/controller/ItemControllerAdvice.java b/gateway/src/main/java/ru/practicum/shareit/item/controller/ItemControllerAdvice.java index 0daa1a2..be55f69 100644 --- a/gateway/src/main/java/ru/practicum/shareit/item/controller/ItemControllerAdvice.java +++ b/gateway/src/main/java/ru/practicum/shareit/item/controller/ItemControllerAdvice.java @@ -10,6 +10,7 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; import java.util.ArrayList; +import java.util.HashMap; import java.util.Map; @ControllerAdvice(basePackages = "ru.practicum.shareit.item.controller") @@ -36,22 +37,11 @@ public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType if (request.getURI().getPath().matches("/items/\\d+") && "GET".equals(request.getMethod().name())) { try { - Map bodyMap; - if (body instanceof Map) { - bodyMap = (Map) body; - } else { - bodyMap = objectMapper.convertValue(body, Map.class); - } - - if (!bodyMap.containsKey("lastBooking")) { - bodyMap.put("lastBooking", null); - } - if (!bodyMap.containsKey("nextBooking")) { - bodyMap.put("nextBooking", null); - } - if (!bodyMap.containsKey("comments")) { - bodyMap.put("comments", new ArrayList<>()); - } + Map bodyMap = convertToMap(body); + + ensureFieldExists(bodyMap, "lastBooking", null); + ensureFieldExists(bodyMap, "nextBooking", null); + ensureFieldExists(bodyMap, "comments", new ArrayList<>()); return bodyMap; } catch (Exception e) { @@ -60,4 +50,18 @@ public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType } return body; } + + private Map convertToMap(Object object) { + if (object instanceof Map) { + return new HashMap<>((Map) object); + } + + return objectMapper.convertValue(object, Map.class); + } + + private void ensureFieldExists(Map map, String fieldName, Object defaultValue) { + if (!map.containsKey(fieldName)) { + map.put(fieldName, defaultValue); + } + } } \ No newline at end of file From 94117282f35339f1bbbb9d5203a451a08a9cd287 Mon Sep 17 00:00:00 2001 From: Viktor64 Date: Tue, 22 Apr 2025 23:04:47 +0400 Subject: [PATCH 13/14] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=B0=D0=BB=20=D0=B2=D0=B5=D1=82=D0=BA=D1=83:=20add-item?= =?UTF-8?q?-requests-and-gateway.=201)=20=D0=A3=D0=B4=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BD=D0=B5=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D0=B7=D1=83=D0=B5=D0=BC=D1=8B=D0=B9=20=D0=BA=D0=BB=D0=B0=D1=81?= =?UTF-8?q?=D1=81=20BookingStateValidator.=202)=20=D0=9F=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D0=BF=D0=B8=D1=81=D0=B0=D0=BB=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4?= =?UTF-8?q?=20from=20=D0=B2=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D0=B5=20Booki?= =?UTF-8?q?ngState=20=D1=81=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=D0=BC=20Stream=20API.=203)?= =?UTF-8?q?=20=D0=97=D0=B0=D0=BC=D0=B5=D0=BD=D0=B8=D0=BB=20Map=20=D0=B2=20ErrorHandler=20=D0=BD=D0=B0=20=D0=BE=D1=82?= =?UTF-8?q?=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20=D0=BA=D0=BB=D0=B0?= =?UTF-8?q?=D1=81=D1=81=20ErrorResponse.=204)=20=D0=97=D0=B0=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=BB=20e=20=D0=BD=D0=B0=20=D0=B1=D0=BE=D0=BB?= =?UTF-8?q?=D0=B5=D0=B5=20=D0=BF=D0=BE=D0=B4=D1=85=D0=BE=D0=B4=D1=8F=D1=89?= =?UTF-8?q?=D1=83=D1=8E=20exception.=205)=20=D0=92=D1=8B=D0=BD=D0=B5=D1=81?= =?UTF-8?q?=20USER=5FID=5FHEADER=20=D0=B2=20=D0=BA=D0=BE=D0=BD=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D0=BD=D1=82=D1=83=20=D0=B2=20=D0=BE=D0=B1=D0=BE=D0=B8?= =?UTF-8?q?=D1=85=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8F=D1=85=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=B2=D1=81=D0=B5=D1=85=20=D0=BA=D0=BE=D0=BD?= =?UTF-8?q?=D1=82=D1=80=D0=BE=D0=BB=D0=BB=D0=B5=D1=80=D0=BE=D0=B2.=206)=20?= =?UTF-8?q?=D0=98=D0=B7=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=D1=81=D1=8F=20=D0=BE?= =?UTF-8?q?=D1=82=20instanceof.=207)=20=D0=A3=D0=B4=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B2=D0=B0=D0=BB=D0=B8=D0=B4=D0=B0=D1=86=D0=B8=D1=8E?= =?UTF-8?q?=20dto=20=D0=B8=D0=B7=20=D1=81=D0=B5=D1=80=D0=B2=D1=80=D0=BD?= =?UTF-8?q?=D0=BE=D0=B9=20ItemRequestServiceImpl.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../practicum/shareit/request/Impl/ItemRequestServiceImpl.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/server/src/main/java/ru/practicum/shareit/request/Impl/ItemRequestServiceImpl.java b/server/src/main/java/ru/practicum/shareit/request/Impl/ItemRequestServiceImpl.java index 6668b71..4f3f6f3 100644 --- a/server/src/main/java/ru/practicum/shareit/request/Impl/ItemRequestServiceImpl.java +++ b/server/src/main/java/ru/practicum/shareit/request/Impl/ItemRequestServiceImpl.java @@ -38,9 +38,6 @@ public class ItemRequestServiceImpl implements ItemRequestService { @Override @Transactional public ItemRequestDto create(ItemRequestDto itemRequestDto, Long userId) { - if (itemRequestDto.getDescription() == null || itemRequestDto.getDescription().isBlank()) { - throw new ShareItException.BadRequestException("Описание запроса не может быть пустым"); - } User user = userRepository.findById(userId) .orElseThrow(() -> new ShareItException.NotFoundException("Пользователь не найден")); From a0b716fb280f8b6cc3dfcf2f2198a617a1f20ae7 Mon Sep 17 00:00:00 2001 From: Viktor64 Date: Tue, 22 Apr 2025 23:36:26 +0400 Subject: [PATCH 14/14] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=B0=D0=BB=20=D0=B2=D0=B5=D1=82=D0=BA=D1=83:=20add-item?= =?UTF-8?q?-requests-and-gateway.=201)=20=D0=A3=D0=B4=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BD=D0=B5=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D0=B7=D1=83=D0=B5=D0=BC=D1=8B=D0=B9=20=D0=BA=D0=BB=D0=B0=D1=81?= =?UTF-8?q?=D1=81=20BookingStateValidator.=202)=20=D0=9F=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D0=BF=D0=B8=D1=81=D0=B0=D0=BB=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4?= =?UTF-8?q?=20from=20=D0=B2=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D0=B5=20Booki?= =?UTF-8?q?ngState=20=D1=81=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=D0=BC=20Stream=20API.=203)?= =?UTF-8?q?=20=D0=97=D0=B0=D0=BC=D0=B5=D0=BD=D0=B8=D0=BB=20Map=20=D0=B2=20ErrorHandler=20=D0=BD=D0=B0=20=D0=BE=D1=82?= =?UTF-8?q?=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20=D0=BA=D0=BB=D0=B0?= =?UTF-8?q?=D1=81=D1=81=20ErrorResponse.=204)=20=D0=97=D0=B0=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=BB=20e=20=D0=BD=D0=B0=20=D0=B1=D0=BE=D0=BB?= =?UTF-8?q?=D0=B5=D0=B5=20=D0=BF=D0=BE=D0=B4=D1=85=D0=BE=D0=B4=D1=8F=D1=89?= =?UTF-8?q?=D1=83=D1=8E=20exception.=205)=20=D0=92=D1=8B=D0=BD=D0=B5=D1=81?= =?UTF-8?q?=20USER=5FID=5FHEADER=20=D0=B2=20=D0=BA=D0=BE=D0=BD=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D0=BD=D1=82=D1=83=20=D0=B2=20=D0=BE=D0=B1=D0=BE=D0=B8?= =?UTF-8?q?=D1=85=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8F=D1=85=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=B2=D1=81=D0=B5=D1=85=20=D0=BA=D0=BE=D0=BD?= =?UTF-8?q?=D1=82=D1=80=D0=BE=D0=BB=D0=BB=D0=B5=D1=80=D0=BE=D0=B2.=206)=20?= =?UTF-8?q?=D0=98=D0=B7=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=D1=81=D1=8F=20=D0=BE?= =?UTF-8?q?=D1=82=20instanceof.=207)=20=D0=A3=D0=B4=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B2=D0=B0=D0=BB=D0=B8=D0=B4=D0=B0=D1=86=D0=B8=D1=8E?= =?UTF-8?q?=20dto=20=D0=B8=D0=B7=20=D1=81=D0=B5=D1=80=D0=B2=D1=80=D0=BD?= =?UTF-8?q?=D0=BE=D0=B9=20ItemRequestServiceImpl.=208)=20=D0=A3=D0=B4?= =?UTF-8?q?=D0=B0=D0=BB=D0=B8=D0=BB=20=D0=B7=D0=B0=D0=B2=D0=B8=D1=81=D0=B8?= =?UTF-8?q?=D0=BC=D0=BE=D1=81=D1=82=D1=8C=20=D0=BD=D0=B0=20spring-boot-sta?= =?UTF-8?q?rter-validation=20=D0=B8=20=D0=B2=D1=81=D0=B5=20=D0=B0=D0=BD?= =?UTF-8?q?=D0=BD=D0=BE=D1=82=D0=B0=D1=86=D0=B8=D0=B8=20=D0=B2=D0=B0=D0=BB?= =?UTF-8?q?=D0=B8=D0=B4=D0=B0=D1=86=D0=B8=D0=B8.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/pom.xml | 4 ---- .../java/ru/practicum/shareit/booking/dto/BookingDto.java | 6 ------ .../main/java/ru/practicum/shareit/item/dto/CommentDto.java | 2 -- .../main/java/ru/practicum/shareit/item/dto/ItemDto.java | 5 ----- .../main/java/ru/practicum/shareit/item/model/Comment.java | 2 -- .../src/main/java/ru/practicum/shareit/item/model/Item.java | 5 ----- .../ru/practicum/shareit/request/model/ItemRequest.java | 2 -- .../main/java/ru/practicum/shareit/user/dto/UserDto.java | 4 ---- .../src/main/java/ru/practicum/shareit/user/model/User.java | 5 ----- 9 files changed, 35 deletions(-) diff --git a/server/pom.xml b/server/pom.xml index de5f2a1..288f137 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -21,10 +21,6 @@ org.springframework.boot spring-boot-starter-web - - org.springframework.boot - spring-boot-starter-validation - org.postgresql postgresql diff --git a/server/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java b/server/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java index 39ccfba..b264c57 100644 --- a/server/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java +++ b/server/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java @@ -1,7 +1,5 @@ package ru.practicum.shareit.booking.dto; -import jakarta.validation.constraints.Future; -import jakarta.validation.constraints.NotNull; import lombok.Builder; import lombok.Getter; import lombok.Setter; @@ -17,12 +15,8 @@ public class BookingDto { private Long id; - @NotNull(message = "Дата начала бронирования не может быть пустой") - @Future(message = "Дата начала бронирования должна быть в будущем") private LocalDateTime start; - @NotNull(message = "Дата окончания бронирования не может быть пустой") - @Future(message = "Дата окончания бронирования должна быть в будущем") private LocalDateTime end; private ItemDto item; diff --git a/server/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java b/server/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java index 66bcda2..4f86a95 100644 --- a/server/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java +++ b/server/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java @@ -1,6 +1,5 @@ package ru.practicum.shareit.item.dto; -import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; import lombok.Setter; @@ -13,7 +12,6 @@ public class CommentDto { private Long id; - @NotBlank private String text; private String authorName; diff --git a/server/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java b/server/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java index 918bbb4..d5a0aba 100644 --- a/server/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java +++ b/server/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java @@ -1,7 +1,5 @@ package ru.practicum.shareit.item.dto; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; import lombok.Builder; import lombok.Getter; import lombok.Setter; @@ -17,13 +15,10 @@ public class ItemDto { private Long id; - @NotBlank private String name; - @NotBlank private String description; - @NotNull private Boolean available; private ItemRequest request; diff --git a/server/src/main/java/ru/practicum/shareit/item/model/Comment.java b/server/src/main/java/ru/practicum/shareit/item/model/Comment.java index 4964d29..b370e9d 100644 --- a/server/src/main/java/ru/practicum/shareit/item/model/Comment.java +++ b/server/src/main/java/ru/practicum/shareit/item/model/Comment.java @@ -9,7 +9,6 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; -import jakarta.validation.constraints.NotBlank; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -31,7 +30,6 @@ public class Comment { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @NotBlank @Column(name = "text", nullable = false) private String text; diff --git a/server/src/main/java/ru/practicum/shareit/item/model/Item.java b/server/src/main/java/ru/practicum/shareit/item/model/Item.java index d9a973e..e6a3b43 100644 --- a/server/src/main/java/ru/practicum/shareit/item/model/Item.java +++ b/server/src/main/java/ru/practicum/shareit/item/model/Item.java @@ -9,8 +9,6 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -31,15 +29,12 @@ public class Item { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @NotBlank @Column(name = "name", nullable = false) private String name; - @NotBlank @Column(name = "description", nullable = false) private String description; - @NotNull @Column(name = "available", nullable = false) private Boolean available; diff --git a/server/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java b/server/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java index 9ee9653..c2ea43a 100644 --- a/server/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java +++ b/server/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java @@ -9,7 +9,6 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; -import jakarta.validation.constraints.NotBlank; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -31,7 +30,6 @@ public class ItemRequest { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @NotBlank @Column(name = "description", nullable = false) private String description; diff --git a/server/src/main/java/ru/practicum/shareit/user/dto/UserDto.java b/server/src/main/java/ru/practicum/shareit/user/dto/UserDto.java index 781f1d1..8e3af55 100644 --- a/server/src/main/java/ru/practicum/shareit/user/dto/UserDto.java +++ b/server/src/main/java/ru/practicum/shareit/user/dto/UserDto.java @@ -1,7 +1,5 @@ package ru.practicum.shareit.user.dto; -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; import lombok.Setter; @@ -14,9 +12,7 @@ public class UserDto { private Long id; - @NotBlank private String name; - @Email private String email; } \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/user/model/User.java b/server/src/main/java/ru/practicum/shareit/user/model/User.java index 7c29a66..2de2120 100644 --- a/server/src/main/java/ru/practicum/shareit/user/model/User.java +++ b/server/src/main/java/ru/practicum/shareit/user/model/User.java @@ -6,8 +6,6 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -26,12 +24,9 @@ public class User { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @NotBlank @Column(name = "name", nullable = false) private String name; - @Email - @NotBlank @Column(name = "email", nullable = false, unique = true) private String email; } \ No newline at end of file