From f1a2b5fce165aca97258f1402609606894b81750 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9A=D0=B0?= =?UTF-8?q?=D0=BD?= Date: Sat, 30 Aug 2025 21:29:06 +0300 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20=D0=92=D1=8B=D0=BF=D0=BE=D0=BB?= =?UTF-8?q?=D0=BD=D0=B8=D1=82=D1=8C=20=D0=A4=D0=B7-16.=20=D0=9F=D1=80?= =?UTF-8?q?=D0=BE=D0=B5=D0=BA=D1=82=20=D1=80=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=BD=D0=B0=202=20=D0=BC=D0=BE=D0=B4=D1=83?= =?UTF-8?q?=D0=BB=D1=8F,=20=D1=80=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D1=8B=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8?= =?UTF-8?q?=D0=B8=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81=D0=BE=D0=B2,=20?= =?UTF-8?q?=D0=BD=D0=B0=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=D1=8B=20=D1=82=D0=B5?= =?UTF-8?q?=D1=81=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gateway/pom.xml | 76 ++++ .../ru/practicum/shareit/ShareItGateway.java | 11 + .../shareit/booking/BookingClient.java | 51 +++ .../shareit/booking/BookingController.java | 58 +++ .../booking/dto/BookingDtoRequest.java | 28 ++ .../shareit/booking/dto/BookingState.java | 16 + .../practicum/shareit/client/BaseClient.java | 127 +++++++ .../ru/practicum/shareit/exception/Error.java | 13 + .../shareit/exception/ErrorResponse.java | 12 + .../exception/GatewayErrorHandler.java | 42 ++ .../ru/practicum/shareit/item/ItemClient.java | 62 +++ .../shareit/item/ItemController.java | 79 ++++ .../item/comment/dto/CommentDtoRequest.java | 14 + .../shareit/item/dto/ItemDtoRequest.java | 25 ++ .../item/dto/ItemUpdateDtoRequest.java | 24 ++ .../shareit/request/ItemRequestClient.java | 43 +++ .../request/ItemRequestController.java | 47 +++ .../request/dto/ItemRequestDtoRequest.java | 14 + .../ru/practicum/shareit/user/UserClient.java | 47 +++ .../shareit/user/UserController.java | 51 +++ .../shareit/user/dto/UserDtoRequest.java | 18 + .../user/dto/UserUpdateDtoRequest.java | 15 + .../src/main/resources/application.properties | 6 + .../practicum/shareit/ShareItGatewayTest.java | 8 +- .../booking/BookingControllerTest.java | 341 +++++++++++++++++ .../booking/BookingDtoRequestJsonTest.java | 112 ++++++ .../shareit/item/ItemControllerTest.java | 358 ++++++++++++++++++ .../shareit/item/ItemDtoRequestJsonTest.java | 83 ++++ .../item/ItemUpdateDtoRequestJsonTest.java | 123 ++++++ .../comment/CommentDtoRequestJsonTest.java | 81 ++++ .../request/ItemRequestControllerTest.java | 220 +++++++++++ .../ItemRequestDtoRequestJsonTest.java | 98 +++++ .../shareit/user/UserControllerTest.java | 223 +++++++++++ .../shareit/user/UserDtoRequestJsonTest.java | 78 ++++ .../user/UserUpdateDtoRequestJsonTest.java | 135 +++++++ pom.xml | 187 ++------- server/pom.xml | 87 +++++ .../ru/practicum/shareit/ShareItServer.java | 5 +- .../booking/BookingServerController.java | 14 +- .../shareit/booking/BookingService.java | 0 .../shareit/booking/BookingServiceImpl.java | 11 +- .../booking/dao/BookingRepository.java | 0 .../shareit/booking/dto/BookingDto.java | 7 +- .../shareit/booking/dto/BookingNewDto.java | 0 .../shareit/booking/mapper/BookingMapper.java | 0 .../shareit/booking/model/Booking.java | 5 +- .../shareit/booking/model/BookingState.java | 0 .../shareit/booking/model/BookingStatus.java | 0 .../booking/utils/BookingValidate.java | 0 .../shareit/exception/DuplicateException.java | 0 .../shareit/exception/ErrorHandler.java | 0 .../shareit/exception/ErrorResponse.java | 0 .../shareit/exception/ForbiddenException.java | 0 .../exception/IllegalArgumentException.java | 0 .../exception/ItemNotAvailableException.java | 7 + .../shareit/exception/NotFoundException.java | 0 .../exception/ValidationException.java | 0 .../shareit/item/ItemServerController.java | 18 +- .../practicum/shareit/item/ItemService.java | 10 +- .../shareit/item/ItemServiceImpl.java | 35 +- .../item/comment/dao/CommentRepository.java | 0 .../shareit/item/comment/dto/CommentDto.java | 4 +- .../item/comment/dto/CommentNewDto.java | 0 .../item/comment/mapper/CommentMapper.java | 0 .../shareit/item/comment/model/Comment.java | 0 .../shareit/item/dao/ItemRepository.java | 11 + .../practicum/shareit/item/dto/ItemDto.java | 2 + .../shareit/item/dto/ItemFullDto.java | 1 + .../shareit/item/dto/ItemNewDto.java | 1 + .../shareit/item/mapper/ItemMapper.java | 10 +- .../ru/practicum/shareit/item/model/Item.java | 6 +- .../shareit/item/utils/ItemValidate.java | 0 .../request/ItemRequestServerController.java | 48 +++ .../shareit/request/ItemRequestService.java | 16 + .../request/ItemRequestServiceImpl.java | 110 ++++++ .../request/dao/ItemRequestRepository.java | 13 + .../shareit/request/dto/ItemRequestDto.java | 23 ++ .../request/dto/ItemRequestNewDto.java | 12 + .../request/mapper/ItemRequestMapper.java | 41 ++ .../shareit/request/model/ItemRequest.java | 55 +++ .../request/utils/ItemRequestValidate.java | 33 ++ .../shareit/user/UserServerController.java | 11 +- .../practicum/shareit/user/UserService.java | 0 .../shareit/user/UserServiceImpl.java | 7 +- .../shareit/user/dao/UserRepository.java | 0 .../practicum/shareit/user/dto/UserDto.java | 4 +- .../shareit/user/dto/UserNewDto.java | 0 .../shareit/user/mapper/UserMapper.java | 3 +- .../ru/practicum/shareit/user/model/User.java | 0 .../shareit/user/utils/UserValidate.java | 0 .../main/resources/application.properties | 8 +- {src => server/src}/main/resources/schema.sql | 6 +- .../practicum/shareit/ShareItServerTest.java | 12 + .../booking/BookingServerControllerTest.java | 276 ++++++++++++++ .../BookingServiceIntegrationTest.java | 281 ++++++++++++++ .../item/ItemServerControllerTest.java | 308 +++++++++++++++ .../item/ItemServiceIntegrationTest.java | 355 +++++++++++++++++ .../ItemRequestServerControllerTest.java | 191 ++++++++++ .../ItemRequestServiceIntegrationTest.java | 151 ++++++++ .../user/UserServerControllerTest.java | 187 +++++++++ .../user/UserServerIntegrationTest.java | 203 ++++++++++ .../resources/application-test.properties | 9 +- server/src/test/resources/schema.sql | 45 +++ .../controller/ItemRequestController.java | 12 - .../shareit/request/dto/ItemRequestDto.java | 7 - .../shareit/request/model/ItemRequest.java | 28 -- 106 files changed, 5324 insertions(+), 291 deletions(-) create mode 100644 gateway/pom.xml create mode 100644 gateway/src/main/java/ru/practicum/shareit/ShareItGateway.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/booking/BookingClient.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingDtoRequest.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/exception/Error.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/exception/GatewayErrorHandler.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/item/ItemClient.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/item/ItemController.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/item/comment/dto/CommentDtoRequest.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/item/dto/ItemDtoRequest.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/item/dto/ItemUpdateDtoRequest.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/request/ItemRequestClient.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/request/ItemRequestController.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDtoRequest.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/user/UserClient.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/user/UserController.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/user/dto/UserDtoRequest.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/user/dto/UserUpdateDtoRequest.java create mode 100644 gateway/src/main/resources/application.properties rename src/test/java/ru/practicum/shareit/ShareItTests.java => gateway/src/test/java/ru/practicum/shareit/ShareItGatewayTest.java (65%) create mode 100644 gateway/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java create mode 100644 gateway/src/test/java/ru/practicum/shareit/booking/BookingDtoRequestJsonTest.java create mode 100644 gateway/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java create mode 100644 gateway/src/test/java/ru/practicum/shareit/item/ItemDtoRequestJsonTest.java create mode 100644 gateway/src/test/java/ru/practicum/shareit/item/ItemUpdateDtoRequestJsonTest.java create mode 100644 gateway/src/test/java/ru/practicum/shareit/item/comment/CommentDtoRequestJsonTest.java create mode 100644 gateway/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java create mode 100644 gateway/src/test/java/ru/practicum/shareit/request/ItemRequestDtoRequestJsonTest.java create mode 100644 gateway/src/test/java/ru/practicum/shareit/user/UserControllerTest.java create mode 100644 gateway/src/test/java/ru/practicum/shareit/user/UserDtoRequestJsonTest.java create mode 100644 gateway/src/test/java/ru/practicum/shareit/user/UserUpdateDtoRequestJsonTest.java create mode 100644 server/pom.xml rename src/main/java/ru/practicum/shareit/ShareItApp.java => server/src/main/java/ru/practicum/shareit/ShareItServer.java (72%) rename src/main/java/ru/practicum/shareit/booking/BookingController.java => server/src/main/java/ru/practicum/shareit/booking/BookingServerController.java (89%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/BookingService.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/BookingServiceImpl.java (95%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/dao/BookingRepository.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/dto/BookingDto.java (87%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/dto/BookingNewDto.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/mapper/BookingMapper.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/model/Booking.java (93%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/model/BookingState.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/model/BookingStatus.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/utils/BookingValidate.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/exception/DuplicateException.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/exception/ErrorHandler.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/exception/ErrorResponse.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/exception/ForbiddenException.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/exception/IllegalArgumentException.java (100%) create mode 100644 server/src/main/java/ru/practicum/shareit/exception/ItemNotAvailableException.java rename {src => server/src}/main/java/ru/practicum/shareit/exception/NotFoundException.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/exception/ValidationException.java (100%) rename src/main/java/ru/practicum/shareit/item/ItemController.java => server/src/main/java/ru/practicum/shareit/item/ItemServerController.java (86%) rename {src => server/src}/main/java/ru/practicum/shareit/item/ItemService.java (68%) rename {src => server/src}/main/java/ru/practicum/shareit/item/ItemServiceImpl.java (88%) rename {src => server/src}/main/java/ru/practicum/shareit/item/comment/dao/CommentRepository.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/item/comment/dto/CommentDto.java (85%) rename {src => server/src}/main/java/ru/practicum/shareit/item/comment/dto/CommentNewDto.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/item/comment/mapper/CommentMapper.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/item/comment/model/Comment.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/item/dao/ItemRepository.java (55%) rename {src => server/src}/main/java/ru/practicum/shareit/item/dto/ItemDto.java (89%) rename {src => server/src}/main/java/ru/practicum/shareit/item/dto/ItemFullDto.java (95%) rename {src => server/src}/main/java/ru/practicum/shareit/item/dto/ItemNewDto.java (90%) rename {src => server/src}/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java (83%) rename {src => server/src}/main/java/ru/practicum/shareit/item/model/Item.java (86%) rename {src => server/src}/main/java/ru/practicum/shareit/item/utils/ItemValidate.java (100%) create mode 100644 server/src/main/java/ru/practicum/shareit/request/ItemRequestServerController.java create mode 100644 server/src/main/java/ru/practicum/shareit/request/ItemRequestService.java create mode 100644 server/src/main/java/ru/practicum/shareit/request/ItemRequestServiceImpl.java create mode 100644 server/src/main/java/ru/practicum/shareit/request/dao/ItemRequestRepository.java create mode 100644 server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java create mode 100644 server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestNewDto.java create mode 100644 server/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java create mode 100644 server/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java create mode 100644 server/src/main/java/ru/practicum/shareit/request/utils/ItemRequestValidate.java rename src/main/java/ru/practicum/shareit/user/UserController.java => server/src/main/java/ru/practicum/shareit/user/UserServerController.java (83%) rename {src => server/src}/main/java/ru/practicum/shareit/user/UserService.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/user/UserServiceImpl.java (92%) rename {src => server/src}/main/java/ru/practicum/shareit/user/dao/UserRepository.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/user/dto/UserDto.java (72%) rename {src => server/src}/main/java/ru/practicum/shareit/user/dto/UserNewDto.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/user/mapper/UserMapper.java (87%) rename {src => server/src}/main/java/ru/practicum/shareit/user/model/User.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/user/utils/UserValidate.java (100%) rename {src => server/src}/main/resources/application.properties (84%) rename {src => server/src}/main/resources/schema.sql (91%) create mode 100644 server/src/test/java/ru/practicum/shareit/ShareItServerTest.java create mode 100644 server/src/test/java/ru/practicum/shareit/booking/BookingServerControllerTest.java create mode 100644 server/src/test/java/ru/practicum/shareit/booking/BookingServiceIntegrationTest.java create mode 100644 server/src/test/java/ru/practicum/shareit/item/ItemServerControllerTest.java create mode 100644 server/src/test/java/ru/practicum/shareit/item/ItemServiceIntegrationTest.java create mode 100644 server/src/test/java/ru/practicum/shareit/request/ItemRequestServerControllerTest.java create mode 100644 server/src/test/java/ru/practicum/shareit/request/ItemRequestServiceIntegrationTest.java create mode 100644 server/src/test/java/ru/practicum/shareit/user/UserServerControllerTest.java create mode 100644 server/src/test/java/ru/practicum/shareit/user/UserServerIntegrationTest.java rename {src/main => server/src/test}/resources/application-test.properties (68%) create mode 100644 server/src/test/resources/schema.sql delete mode 100644 src/main/java/ru/practicum/shareit/request/controller/ItemRequestController.java delete mode 100644 src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java delete mode 100644 src/main/java/ru/practicum/shareit/request/model/ItemRequest.java diff --git a/gateway/pom.xml b/gateway/pom.xml new file mode 100644 index 0000000..0917ab7 --- /dev/null +++ b/gateway/pom.xml @@ -0,0 +1,76 @@ + + + 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.hibernate.validator + hibernate-validator + + + + org.apache.httpcomponents.client5 + httpclient5 + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.projectlombok + lombok + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.boot + spring-boot-test-autoconfigure + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/ShareItGateway.java b/gateway/src/main/java/ru/practicum/shareit/ShareItGateway.java new file mode 100644 index 0000000..e14acfb --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/ShareItGateway.java @@ -0,0 +1,11 @@ +package ru.practicum.shareit; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ShareItGateway { + public static void main(String[] args) { + SpringApplication.run(ShareItGateway.class, args); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/BookingClient.java b/gateway/src/main/java/ru/practicum/shareit/booking/BookingClient.java new file mode 100644 index 0000000..d3fff38 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/BookingClient.java @@ -0,0 +1,51 @@ +package ru.practicum.shareit.booking; + +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.BookingDtoRequest; +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(Long userId, BookingDtoRequest bookingDtoRequest) { + return post("", userId, bookingDtoRequest); + } + + public ResponseEntity getBooking(Long userId, Long bookingId) { + return get("/" + bookingId, userId); + } + + public ResponseEntity getBookingByBooker(Long userId, BookingState state) { + return get("?state={state}", userId, Map.of("state", state.name())); + } + + public ResponseEntity getBookingByOwner(Long userId, BookingState state) { + return get("/owner?state={state}", userId, Map.of("state", state.name())); + } + + public ResponseEntity approveBooking(Long userId, Long bookingId, Boolean isApproved) { + return patch("/" + bookingId + "?approved={approved}", userId, + Map.of("approved", isApproved), ""); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java b/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java new file mode 100644 index 0000000..8a6bce9 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java @@ -0,0 +1,58 @@ +package ru.practicum.shareit.booking; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.booking.dto.*; + +@Slf4j +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping(path = "/bookings") +public class BookingController { + + private final BookingClient bookingClient; + + @PostMapping + public ResponseEntity create(@RequestHeader("X-Sharer-User-Id") @Positive Long userId, + @RequestBody @Valid BookingDtoRequest bookingDtoRequest) { + log.info("POST / bookings"); + return bookingClient.createBooking(userId, bookingDtoRequest); + } + + @PatchMapping("/{bookingId}") + public ResponseEntity approve(@RequestHeader("X-Sharer-User-Id") @Positive Long userId, + @PathVariable @Positive Long bookingId, + @RequestParam(name = "approved") Boolean isApproved) { + log.info("PATCH / bookings / {}", bookingId); + return bookingClient.approveBooking(userId, bookingId, isApproved); + } + + @GetMapping("/{bookingId}") + public ResponseEntity getById(@RequestHeader("X-Sharer-User-Id") @Positive Long userId, + @PathVariable @Positive Long bookingId) { + log.info("GET booking {}, userId = {}", bookingId, userId); + return bookingClient.getBooking(userId, bookingId); + } + + @GetMapping("/owner") + public ResponseEntity getByOwnerId(@RequestHeader("X-Sharer-User-Id") @Positive Long ownerId, + @RequestParam(name = "state", defaultValue = "ALL") + BookingState bookingState) { + log.info("GET / ByOwner / {}", ownerId); + return bookingClient.getBookingByOwner(ownerId, bookingState); + } + + @GetMapping + public ResponseEntity getByBookerId(@RequestHeader("X-Sharer-User-Id") @Positive Long bookerId, + @RequestParam(name = "state", defaultValue = "ALL") + BookingState bookingState) { + log.info("GET / ByBooker / {}", bookerId); + return bookingClient.getBookingByBooker(bookerId, bookingState); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingDtoRequest.java b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingDtoRequest.java new file mode 100644 index 0000000..dcedcee --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingDtoRequest.java @@ -0,0 +1,28 @@ +package ru.practicum.shareit.booking.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import jakarta.validation.constraints.*; +import lombok.*; + +import java.time.Instant; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder(toBuilder = true) +public class BookingDtoRequest { + + @NotNull + @Positive + private Long itemId; + + @NotNull + @FutureOrPresent + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss", shape = JsonFormat.Shape.STRING, timezone = "UTC") + private Instant start; + + @NotNull + @Future + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss", shape = JsonFormat.Shape.STRING, timezone = "UTC") + private Instant end; +} \ 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..273c962 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingState.java @@ -0,0 +1,16 @@ +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..66d21fc --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/client/BaseClient.java @@ -0,0 +1,127 @@ +package ru.practicum.shareit.client; + +import jakarta.annotation.Nullable; +import org.springframework.http.*; +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; + + public BaseClient(RestTemplate rest) { + this.rest = rest; + } + + 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) { + return makeAndSendRequest(HttpMethod.GET, path, userId, parameters, null); + } + + protected ResponseEntity post(String path, T body) { + return post(path, null, null, body); + } + + protected ResponseEntity post(String path, long userId, T body) { + return post(path, userId, null, body); + } + + protected ResponseEntity post(String path, Long userId, + @Nullable Map parameters, T body) { + return makeAndSendRequest(HttpMethod.POST, path, userId, parameters, body); + } + + protected ResponseEntity put(String path, long userId, T body) { + return put(path, userId, null, body); + } + + protected ResponseEntity put(String path, long userId, + @Nullable Map parameters, T body) { + return makeAndSendRequest(HttpMethod.PUT, path, userId, parameters, body); + } + + protected ResponseEntity patch(String path, T body) { + return patch(path, null, null, body); + } + + protected ResponseEntity patch(String path, long userId) { + return patch(path, userId, null, null); + } + + 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)); + + try { + ResponseEntity shareitServerResponse; + if (parameters != null) { + shareitServerResponse = rest.exchange(path, method, requestEntity, Object.class, parameters); + } else { + shareitServerResponse = rest.exchange(path, method, requestEntity, Object.class); + } + + return prepareGatewayResponse(shareitServerResponse); + + } catch (HttpStatusCodeException e) { + return ResponseEntity.status(e.getStatusCode()) + .body(e.getResponseBodyAsString()); + } + } + + 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/exception/Error.java b/gateway/src/main/java/ru/practicum/shareit/exception/Error.java new file mode 100644 index 0000000..b26e6a6 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/exception/Error.java @@ -0,0 +1,13 @@ +package ru.practicum.shareit.exception; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Error { + private String fieldName; + private String message; +} \ 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..e43329b --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java @@ -0,0 +1,12 @@ +package ru.practicum.shareit.exception; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ErrorResponse { + private String error; +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/exception/GatewayErrorHandler.java b/gateway/src/main/java/ru/practicum/shareit/exception/GatewayErrorHandler.java new file mode 100644 index 0000000..9d893e1 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/exception/GatewayErrorHandler.java @@ -0,0 +1,42 @@ +package ru.practicum.shareit.exception; + +import jakarta.validation.ConstraintViolationException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@ControllerAdvice +public class GatewayErrorHandler { + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleValidationExceptions( + MethodArgumentNotValidException ex) { + + String errorMessage = ex.getBindingResult().getFieldErrors().stream() + .findFirst() + .map(error -> error.getField() + ": " + error.getDefaultMessage()) + .orElse("Ошибка валидации"); + + return new ResponseEntity<>( + new ErrorResponse(errorMessage), + HttpStatus.BAD_REQUEST + ); + } + + @ExceptionHandler(ConstraintViolationException.class) + public ResponseEntity handleValidationExceptions( + ConstraintViolationException ex) { + + String errorMessage = ex.getConstraintViolations().stream() + .findFirst() + .map(violation -> violation.getPropertyPath() + ": " + violation.getMessage()) + .orElse("Ошибка валидации"); + + return new ResponseEntity<>( + new ErrorResponse(errorMessage), + HttpStatus.BAD_REQUEST + ); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/item/ItemClient.java b/gateway/src/main/java/ru/practicum/shareit/item/ItemClient.java new file mode 100644 index 0000000..865dae1 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/ItemClient.java @@ -0,0 +1,62 @@ +package ru.practicum.shareit.item; + +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.item.comment.dto.CommentDtoRequest; +import ru.practicum.shareit.item.dto.*; + +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) { + super( + builder + .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl + API_PREFIX)) + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) + .build() + ); + } + + public ResponseEntity getAllItemsByOwner(Long userId) { + return get("", userId); + } + + public ResponseEntity getItemById(Long userId, Long itemId) { + return get("/" + itemId, userId); + } + + public ResponseEntity createItem(Long userId, ItemDtoRequest itemDtoRequest) { + return post("", userId, itemDtoRequest); + } + + public ResponseEntity updateItem(Long userId, Long itemId, ItemUpdateDtoRequest itemUpdateDtoRequest) { + return patch("/" + itemId, userId, itemUpdateDtoRequest); + } + + public void deleteItem(Long userId, Long itemId) { + delete("/" + itemId, userId); + } + + public ResponseEntity searchByText(Long userId, String text) { + return get("/search?text={text}", userId, Map.of("text", text)); + } + + public ResponseEntity addComment(Long userId, Long itemId, CommentDtoRequest commentDtoRequest) { + return post("/" + itemId + "/comment", userId, commentDtoRequest); + } + + public ResponseEntity getAllComments(Long userId, Long itemId) { + return get("/" + itemId + "/comment", userId); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/item/ItemController.java b/gateway/src/main/java/ru/practicum/shareit/item/ItemController.java new file mode 100644 index 0000000..c1de550 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -0,0 +1,79 @@ +package ru.practicum.shareit.item; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Positive; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.item.comment.dto.CommentDtoRequest; +import ru.practicum.shareit.item.dto.*; + +@Slf4j +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping(path = "/items") +public class ItemController { + + private final ItemClient itemClient; + + @GetMapping + public ResponseEntity getAllItemsByOwner(@RequestHeader("X-Sharer-User-Id") @Positive Long userId) { + log.info("GET / items / user {}", userId); + return itemClient.getAllItemsByOwner(userId); + } + + @GetMapping("/{itemId}") + public ResponseEntity getItemById(@RequestHeader("X-Sharer-User-Id") @Positive Long userId, + @PathVariable @Positive Long itemId) { + log.info("GET / items {} / user {}", itemId, userId); + return itemClient.getItemById(userId, itemId); + } + + @GetMapping("/search") + public ResponseEntity searchByText(@RequestHeader("X-Sharer-User-Id") @Positive Long userId, + @RequestParam("text") @NotBlank String searchText) { + log.info("GET / search / {}", searchText); + return itemClient.searchByText(userId, searchText); + } + + @PostMapping + public ResponseEntity create(@RequestHeader("X-Sharer-User-Id") @Positive Long userId, + @RequestBody @Valid ItemDtoRequest itemDtoRequest) { + log.info("POST / items {} / user {}", itemDtoRequest.getName(), userId); + return itemClient.createItem(userId, itemDtoRequest); + } + + @PatchMapping("/{itemId}") + public ResponseEntity update(@RequestHeader("X-Sharer-User-Id") @Positive Long ownerId, + @PathVariable @Positive Long itemId, + @RequestBody @Valid ItemUpdateDtoRequest itemUpdateDtoRequest) { + log.info("PATCH / items {} / user {}", itemId, ownerId); + return itemClient.updateItem(ownerId, itemId, itemUpdateDtoRequest); + } + + @DeleteMapping("/{itemId}") + public void delete(@RequestHeader("X-Sharer-User-Id") @Positive Long ownerId, + @PathVariable @Positive Long itemId) { + log.info("DELETE / items {} / user {}", itemId, ownerId); + itemClient.deleteItem(ownerId, itemId); + } + + @PostMapping("/{itemId}/comment") + public ResponseEntity addComment(@RequestHeader("X-Sharer-User-Id") @Positive Long userId, + @PathVariable @Positive Long itemId, + @RequestBody @Valid CommentDtoRequest commentDtoRequest) { + log.info("POST / comment / item {}", itemId); + return itemClient.addComment(userId, itemId, commentDtoRequest); + } + + @GetMapping("/{itemId}/comment") + public ResponseEntity getComments(@RequestHeader("X-Sharer-User-Id") @Positive Long userId, + @PathVariable @Positive Long itemId) { + log.info("GET / comment / item {}", itemId); + return itemClient.getAllComments(userId, itemId); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/item/comment/dto/CommentDtoRequest.java b/gateway/src/main/java/ru/practicum/shareit/item/comment/dto/CommentDtoRequest.java new file mode 100644 index 0000000..57b5e57 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/comment/dto/CommentDtoRequest.java @@ -0,0 +1,14 @@ +package ru.practicum.shareit.item.comment.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.*; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder(toBuilder = true) +public class CommentDtoRequest { + + @NotBlank + String text; +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemDtoRequest.java b/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemDtoRequest.java new file mode 100644 index 0000000..372fd50 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemDtoRequest.java @@ -0,0 +1,25 @@ +package ru.practicum.shareit.item.dto; + +import io.micrometer.common.lang.Nullable; +import jakarta.validation.constraints.*; +import lombok.*; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder(toBuilder = true) +public class ItemDtoRequest { + + @NotBlank + private String name; + + @NotBlank + private String description; + + @NotNull + private Boolean available; + + @Nullable + @Positive + private Long requestId; +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemUpdateDtoRequest.java b/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemUpdateDtoRequest.java new file mode 100644 index 0000000..012202b --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemUpdateDtoRequest.java @@ -0,0 +1,24 @@ +package ru.practicum.shareit.item.dto; + +import io.micrometer.common.lang.Nullable; +import jakarta.validation.constraints.Positive; +import lombok.*; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class ItemUpdateDtoRequest { + + @Nullable + private String name; + + @Nullable + private String description; + + @Nullable + private Boolean available; + + @Nullable + @Positive + private Long requestId; +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestClient.java b/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestClient.java new file mode 100644 index 0000000..c4e6c18 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestClient.java @@ -0,0 +1,43 @@ +package ru.practicum.shareit.request; + +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.ItemRequestDtoRequest; + +@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 createRequest(Long userId, ItemRequestDtoRequest requestDtoRequest) { + return post("", userId, requestDtoRequest); + } + + public ResponseEntity getAllRequests(Long userId) { + return get("/all", userId); + } + + public ResponseEntity getRequestByUser(Long userId) { + return get("", userId); + } + + public ResponseEntity getRequestById(Long userId, Long requestId) { + return get("/" + requestId, userId); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestController.java b/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestController.java new file mode 100644 index 0000000..5a1d397 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestController.java @@ -0,0 +1,47 @@ +package ru.practicum.shareit.request; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.request.dto.ItemRequestDtoRequest; + +@Slf4j +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping(path = "/requests") +public class ItemRequestController { + + private final ItemRequestClient itemRequestClient; + + @PostMapping + public ResponseEntity create(@RequestHeader("X-Sharer-User-Id") @Positive Long userId, + @RequestBody @Valid ItemRequestDtoRequest itemRequestDtoRequest) { + log.info("POST / requests | userId: {} | Создание заявки: '{}'", + userId, itemRequestDtoRequest.getDescription()); + return itemRequestClient.createRequest(userId, itemRequestDtoRequest); + } + + @GetMapping("/all") + public ResponseEntity getAllRequests(@RequestHeader("X-Sharer-User-Id") @Positive Long userId) { + log.info("GET / requests"); + return itemRequestClient.getAllRequests(userId); + } + + @GetMapping + public ResponseEntity getRequestByUser(@RequestHeader("X-Sharer-User-Id") @Positive Long userId) { + log.info("GET / requests / requestor {}", userId); + return itemRequestClient.getRequestByUser(userId); + } + + @GetMapping("/{requestId}") + public ResponseEntity getRequestById(@RequestHeader("X-Sharer-User-Id") @Positive Long userId, + @PathVariable @Positive Long requestId) { + log.info("GET / request {} / user {}", requestId, userId); + return itemRequestClient.getRequestById(userId, requestId); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDtoRequest.java b/gateway/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDtoRequest.java new file mode 100644 index 0000000..754ff4f --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDtoRequest.java @@ -0,0 +1,14 @@ +package ru.practicum.shareit.request.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.*; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder(toBuilder = true) +public class ItemRequestDtoRequest { + + @NotBlank + private String description; +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/user/UserClient.java b/gateway/src/main/java/ru/practicum/shareit/user/UserClient.java new file mode 100644 index 0000000..c8793c7 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/user/UserClient.java @@ -0,0 +1,47 @@ +package ru.practicum.shareit.user; + +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.*; + +@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 getAllUsers() { + return get(""); + } + + public ResponseEntity getUserById(Long userId) { + return get("/" + userId); + } + + public ResponseEntity createUser(UserDtoRequest userDtoRequest) { + return post("", userDtoRequest); + } + + public ResponseEntity updateUser(Long userId, UserUpdateDtoRequest userUpdateDtoRequest) { + return patch("/" + userId, userUpdateDtoRequest); + } + + public void deleteUser(Long userId) { + delete("/" + userId); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/user/UserController.java b/gateway/src/main/java/ru/practicum/shareit/user/UserController.java new file mode 100644 index 0000000..a3dd4e4 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/user/UserController.java @@ -0,0 +1,51 @@ +package ru.practicum.shareit.user; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.user.dto.*; + +@Slf4j +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping(path = "/users") +public class UserController { + + private final UserClient userClient; + + @GetMapping + public ResponseEntity getAllUsers() { + log.info("GET / users"); + return userClient.getAllUsers(); + } + + @GetMapping("/{userId}") + public ResponseEntity getByUserId(@PathVariable @Positive Long userId) { + log.info("GET / users / {}", userId); + return userClient.getUserById(userId); + } + + @PostMapping + public ResponseEntity create(@RequestBody @Valid UserDtoRequest userDtoRequest) { + log.info("POST / users / {} / {}", userDtoRequest.getName(), userDtoRequest.getEmail()); + return userClient.createUser(userDtoRequest); + } + + @PatchMapping("/{userId}") + public ResponseEntity update(@PathVariable @Positive Long userId, + @RequestBody @Valid UserUpdateDtoRequest userUpdateDtoRequest) { + log.info("PATCH / users / {}", userId); + return userClient.updateUser(userId, userUpdateDtoRequest); + } + + @DeleteMapping("/{userId}") + public void delete(@PathVariable @Positive Long userId) { + log.info("DELETE / users / {}", userId); + userClient.deleteUser(userId); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/user/dto/UserDtoRequest.java b/gateway/src/main/java/ru/practicum/shareit/user/dto/UserDtoRequest.java new file mode 100644 index 0000000..7d84883 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/user/dto/UserDtoRequest.java @@ -0,0 +1,18 @@ +package ru.practicum.shareit.user.dto; + +import jakarta.validation.constraints.*; +import lombok.*; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder(toBuilder = true) +public class UserDtoRequest { + + @NotBlank + private String name; + + @NotBlank + @Email + private String email; +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/user/dto/UserUpdateDtoRequest.java b/gateway/src/main/java/ru/practicum/shareit/user/dto/UserUpdateDtoRequest.java new file mode 100644 index 0000000..b14bde3 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/user/dto/UserUpdateDtoRequest.java @@ -0,0 +1,15 @@ +package ru.practicum.shareit.user.dto; + +import jakarta.validation.constraints.Email; +import lombok.*; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class UserUpdateDtoRequest { + + private String name; + + @Email + private String email; +} \ 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..a8507b8 --- /dev/null +++ b/gateway/src/main/resources/application.properties @@ -0,0 +1,6 @@ +logging.level.org.springframework.web.client.RestTemplate=DEBUG +#logging.level.org.apache.http=DEBUG +#logging.level.httpclient.wire=DEBUG + +server.port=8080 +shareit-server.url=http://localhost:9090 \ No newline at end of file diff --git a/src/test/java/ru/practicum/shareit/ShareItTests.java b/gateway/src/test/java/ru/practicum/shareit/ShareItGatewayTest.java similarity index 65% rename from src/test/java/ru/practicum/shareit/ShareItTests.java rename to gateway/src/test/java/ru/practicum/shareit/ShareItGatewayTest.java index 8cb88ed..1fc03a5 100644 --- a/src/test/java/ru/practicum/shareit/ShareItTests.java +++ b/gateway/src/test/java/ru/practicum/shareit/ShareItGatewayTest.java @@ -4,9 +4,9 @@ import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest -class ShareItTests { +class ShareItGatewayTest { - @Test - void contextLoads() { - } + @Test + public void contextLoads() { + } } \ No newline at end of file diff --git a/gateway/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java b/gateway/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java new file mode 100644 index 0000000..10e3654 --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java @@ -0,0 +1,341 @@ +package ru.practicum.shareit.booking; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +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.context.annotation.Import; +import org.springframework.http.*; +import org.springframework.test.web.servlet.MockMvc; +import ru.practicum.shareit.booking.dto.*; +import ru.practicum.shareit.exception.GatewayErrorHandler; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(BookingController.class) +@Import(GatewayErrorHandler.class) +public class BookingControllerTest { + + private final String url = "/bookings"; + private static final String X_SHARER_USER_ID = "X-Sharer-User-Id"; + private static final Long VALID_USER_ID = 1L; + private static final Long VALID_ITEM_ID = 1L; + private static final Long VALID_BOOKING_ID = 1L; + private static final Long INVALID_ID = -1L; + private static final Instant TOMORROW = Instant.now().plus(1, ChronoUnit.DAYS); + private static final Instant DAY_AFTER_TOMORROW = Instant.now().plus(2, ChronoUnit.DAYS); + + @Autowired + private MockMvc mvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private BookingClient bookingClient; + + @BeforeEach + public void setUo() { + Mockito.reset(bookingClient); + setupDefaultSuccessfulMocks(); + } + + @Test + public void createBooking_ShouldReturnCreated_WhenRequestIsValid() throws Exception { + BookingDtoRequest booking = BookingDtoRequest.builder() + .itemId(VALID_ITEM_ID) + .start(TOMORROW) + .end(DAY_AFTER_TOMORROW) + .build(); + + mvc.perform(post(url) + .header(X_SHARER_USER_ID, VALID_USER_ID) + .content(objectMapper.writeValueAsString(booking)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()); + + verify(bookingClient).createBooking(eq(VALID_USER_ID), any(BookingDtoRequest.class)); + } + + @Test + public void createBooking_ShouldReturnBadRequest_WhenItemIdIsNull() throws Exception { + BookingDtoRequest booking = BookingDtoRequest.builder() + .itemId(null) + .start(TOMORROW) + .end(DAY_AFTER_TOMORROW) + .build(); + + mvc.perform(post(url) + .header(X_SHARER_USER_ID, VALID_USER_ID) + .content(objectMapper.writeValueAsString(booking)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + verify(bookingClient, never()).createBooking(anyLong(), any()); + } + + @Test + public void createBooking_ShouldReturnBadRequest_WhenItemIdIsNotPositive() throws Exception { + BookingDtoRequest booking = BookingDtoRequest.builder() + .itemId(0L) + .start(Instant.now().plus(1, ChronoUnit.DAYS)) + .end(Instant.now().plus(2, ChronoUnit.DAYS)) + .build(); + + mvc.perform(post(url) + .header(X_SHARER_USER_ID, VALID_USER_ID) + .content(objectMapper.writeValueAsString(booking)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + verify(bookingClient, never()).createBooking(anyLong(), any()); + } + + @Test + public void createBooking_ShouldReturnBadRequest_WhenStartIsNull() throws Exception { + BookingDtoRequest booking = BookingDtoRequest.builder() + .itemId(VALID_ITEM_ID) + .start(null) + .end(DAY_AFTER_TOMORROW) + .build(); + + mvc.perform(post(url) + .header(X_SHARER_USER_ID, VALID_USER_ID) + .content(objectMapper.writeValueAsString(booking)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + verify(bookingClient, never()).createBooking(anyLong(), any()); + } + + @Test + public void createBooking_ShouldReturnBadRequest_WhenStartIsInPast() throws Exception { + BookingDtoRequest booking = BookingDtoRequest.builder() + .itemId(VALID_ITEM_ID) + .start(Instant.now().minus(1, ChronoUnit.DAYS)) + .end(DAY_AFTER_TOMORROW) + .build(); + + mvc.perform(post(url) + .header(X_SHARER_USER_ID, VALID_USER_ID) + .content(objectMapper.writeValueAsString(booking)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + verify(bookingClient, never()).createBooking(anyLong(), any()); + } + + @Test + public void createBooking_ShouldReturnBadRequest_WhenEndIsNull() throws Exception { + BookingDtoRequest booking = BookingDtoRequest.builder() + .itemId(VALID_ITEM_ID) + .start(TOMORROW) + .end(null) + .build(); + + mvc.perform(post(url) + .header(X_SHARER_USER_ID, VALID_USER_ID) + .content(objectMapper.writeValueAsString(booking)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + verify(bookingClient, never()).createBooking(anyLong(), any()); + } + + @Test + public void createBooking_ShouldReturnBadRequest_WhenEndIsBeforeStart() throws Exception { + BookingDtoRequest booking = BookingDtoRequest.builder() + .itemId(VALID_ITEM_ID) + .start(TOMORROW) + .end(TOMORROW.minus(2, ChronoUnit.DAYS)) + .build(); + + mvc.perform(post(url) + .header(X_SHARER_USER_ID, VALID_USER_ID) + .content(objectMapper.writeValueAsString(booking)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + verify(bookingClient, never()).createBooking(anyLong(), any()); + } + + @Test + public void createBooking_ShouldReturnBadRequest_WhenUserIdIsNotPositive() throws Exception { + BookingDtoRequest booking = BookingDtoRequest.builder() + .itemId(VALID_ITEM_ID) + .start(TOMORROW) + .end(DAY_AFTER_TOMORROW) + .build(); + + mvc.perform(post(url) + .header(X_SHARER_USER_ID, INVALID_ID) + .content(objectMapper.writeValueAsString(booking)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + verify(bookingClient, never()).createBooking(anyLong(), any()); + } + + @Test + public void approveBooking_ShouldReturnOk_WhenRequestIsValid() throws Exception { + mvc.perform(patch("/bookings/{bookingId}", VALID_BOOKING_ID) + .param("approved", "true") + .header(X_SHARER_USER_ID, VALID_USER_ID) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + + verify(bookingClient).approveBooking(eq(VALID_USER_ID), eq(VALID_BOOKING_ID), eq(true)); + } + + @Test + public void approveBooking_ShouldReturnBadRequest_WhenBookingIdIsNotPositive() throws Exception { + mvc.perform(patch("/bookings/{bookingId}", INVALID_ID) + .param("approved", "true") + .header(X_SHARER_USER_ID, VALID_USER_ID) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + verify(bookingClient, never()).approveBooking(anyLong(), anyLong(), anyBoolean()); + } + + @Test + public void approveBooking_ShouldReturnBadRequest_WhenUserIdIsNotPositive() throws Exception { + mvc.perform(patch("/bookings/{bookingId}", VALID_BOOKING_ID) + .param("approved", "true") + .header(X_SHARER_USER_ID, INVALID_ID) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + verify(bookingClient, never()).approveBooking(anyLong(), anyLong(), anyBoolean()); + } + + @Test + public void approveBooking_ShouldReturnBadRequest_WhenApprovedParamIsInvalid() throws Exception { + mvc.perform(patch("/bookings/{bookingId}", VALID_BOOKING_ID) + .param("approved", "invalid") + .header(X_SHARER_USER_ID, VALID_USER_ID) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + verify(bookingClient, never()).approveBooking(anyLong(), anyLong(), anyBoolean()); + } + + @Test + public void getBooking_ShouldReturnOk_WhenRequestIsValid() throws Exception { + mvc.perform(get("/bookings/{bookingId}", VALID_BOOKING_ID) + .header(X_SHARER_USER_ID, VALID_USER_ID) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + + verify(bookingClient).getBooking(eq(VALID_USER_ID), eq(VALID_BOOKING_ID)); + } + + @Test + public void getBooking_ShouldReturnBadRequest_WhenBookingIdIsNotPositive() throws Exception { + mvc.perform(get("/bookings/{bookingId}", INVALID_ID) + .header(X_SHARER_USER_ID, VALID_USER_ID) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + verify(bookingClient, never()).getBooking(anyLong(), anyLong()); + } + + @Test + public void getBooking_ShouldReturnBadRequest_WhenUserIdIsNotPositive() throws Exception { + mvc.perform(get("/bookings/{bookingId}", VALID_BOOKING_ID) + .header(X_SHARER_USER_ID, INVALID_ID) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + verify(bookingClient, never()).getBooking(anyLong(), anyLong()); + } + + @Test + public void getBookingsByOwner_ShouldReturnOk_WhenRequestIsValid() throws Exception { + mvc.perform(get("/bookings/owner") + .param("state", "ALL") + .header(X_SHARER_USER_ID, VALID_USER_ID) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + + verify(bookingClient).getBookingByOwner(eq(VALID_USER_ID), eq(BookingState.ALL)); + } + + @Test + public void getBookingsByOwner_ShouldReturnBadRequest_WhenUserIdIsNotPositive() throws Exception { + mvc.perform(get("/bookings/owner") + .param("state", "ALL") + .header(X_SHARER_USER_ID, INVALID_ID) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + verify(bookingClient, never()).getBookingByOwner(anyLong(), any()); + } + + @Test + public void getBookingsByOwner_ShouldReturnBadRequest_WhenStateIsInvalid() throws Exception { + mvc.perform(get("/bookings/owner") + .param("state", "INVALID_STATE") + .header(X_SHARER_USER_ID, VALID_USER_ID) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + verify(bookingClient, never()).getBookingByOwner(anyLong(), any()); + } + + @Test + public void getBookingsByBooker_ShouldReturnOk_WhenRequestIsValid() throws Exception { + mvc.perform(get(url) + .param("state", "ALL") + .header(X_SHARER_USER_ID, VALID_USER_ID) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + + verify(bookingClient).getBookingByBooker(eq(VALID_USER_ID), eq(BookingState.ALL)); + } + + @Test + public void getBookingsByBooker_ShouldReturnBadRequest_WhenUserIdIsNotPositive() throws Exception { + mvc.perform(get(url) + .param("state", "ALL") + .header(X_SHARER_USER_ID, INVALID_ID) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + verify(bookingClient, never()).getBookingByBooker(anyLong(), any()); + } + + @Test + public void getBookingsByBooker_ShouldReturnBadRequest_WhenStateIsInvalid() throws Exception { + mvc.perform(get(url) + .param("state", "INVALID_STATE") + .header(X_SHARER_USER_ID, VALID_USER_ID) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + verify(bookingClient, never()).getBookingByBooker(anyLong(), any()); + } + + // Вспомогательный метод для настройки мок-объектов + private void setupDefaultSuccessfulMocks() { + when(bookingClient.createBooking(anyLong(), any(BookingDtoRequest.class))) + .thenReturn(ResponseEntity.status(HttpStatus.CREATED).build()); + when(bookingClient.approveBooking(anyLong(), anyLong(), any(Boolean.class))) + .thenReturn(ResponseEntity.status(HttpStatus.OK).build()); + when(bookingClient.getBooking(anyLong(), anyLong())) + .thenReturn(ResponseEntity.status(HttpStatus.OK).build()); + when(bookingClient.getBookingByOwner(anyLong(), any(BookingState.class))) + .thenReturn(ResponseEntity.status(HttpStatus.OK).build()); + when(bookingClient.getBookingByBooker(anyLong(), any(BookingState.class))) + .thenReturn(ResponseEntity.status(HttpStatus.OK).build()); + } +} \ No newline at end of file diff --git a/gateway/src/test/java/ru/practicum/shareit/booking/BookingDtoRequestJsonTest.java b/gateway/src/test/java/ru/practicum/shareit/booking/BookingDtoRequestJsonTest.java new file mode 100644 index 0000000..83936a1 --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/booking/BookingDtoRequestJsonTest.java @@ -0,0 +1,112 @@ +package ru.practicum.shareit.booking; + +import com.fasterxml.jackson.databind.exc.InvalidFormatException; +import org.junit.jupiter.api.BeforeEach; +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 ru.practicum.shareit.booking.dto.BookingDtoRequest; + +import java.time.Instant; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Тесты для проверки сериализации и десериализации BookingDtoRequest + * с использованием аннотации @JsonTest для изолированного тестирования JSON + */ +@JsonTest +public class BookingDtoRequestJsonTest { + + @Autowired + private JacksonTester json; + + private Instant start; + private Instant end; + + @BeforeEach + void setUp() { + start = Instant.parse("2024-01-01T10:00:00Z"); + end = Instant.parse("2024-01-02T10:00:00Z"); + } + + // Тест корректной сериализации объекта в JSON + @Test + public void serialize_shouldProduceCorrectDateFormat() throws Exception { + BookingDtoRequest dto = BookingDtoRequest.builder() + .itemId(1L) + .start(start) + .end(end) + .build(); + + assertThat(json.write(dto)) + .hasJsonPathNumberValue("$.itemId", 1L) + .hasJsonPathStringValue("$.start", "2024-01-01T10:00:00") + .hasJsonPathStringValue("$.end", "2024-01-02T10:00:00"); + } + + // Тест корректной десериализации JSON в объект + @Test + public void deserialize_shouldParseWithoutZ() throws Exception { + String jsonContent = """ + { + "itemId": 1, + "start": "2024-01-01T10:00:00", + "end": "2024-01-02T10:00:00" + } + """; + + BookingDtoRequest result = json.parse(jsonContent).getObject(); + + assertThat(result.getItemId()).isEqualTo(1L); + assertThat(result.getStart()).isEqualTo(Instant.parse("2024-01-01T10:00:00Z")); + assertThat(result.getEnd()).isEqualTo(Instant.parse("2024-01-02T10:00:00Z")); + } + + // Тест проверяет обработку некорректного формата даты(InvalidFormatException) + @Test + public void deserialize_shouldThrowExceptionForInvalidDateFormat() throws Exception { + String jsonContent = """ + { + "itemId": 1, + "start": "2024-01-01T10:00:00Z", + "end": "2024-01-02T10:00:00" + } + """; + + assertThatThrownBy(() -> json.parse(jsonContent)) + .isInstanceOf(InvalidFormatException.class); + } + + // Тест проверяет обработку некорректного формата числового поля(InvalidFormatException) + @Test + public void deserialize_shouldThrowExceptionForInvalidItemIdFormat() throws Exception { + String jsonContent = """ + { + "itemId": "not_a_number", + "start": "2024-01-01T10:00:00", + "end": "2024-01-02T10:00:00" + } + """; + + assertThatThrownBy(() -> json.parse(jsonContent)) + .isInstanceOf(InvalidFormatException.class); + } + + // Тест сериализации при null значениях(Jackson обрабатывает и сериализует их как пустые значения в JSON) + @Test + public void serialize_shouldHandleNullValues() throws Exception { + BookingDtoRequest dto = BookingDtoRequest.builder() + .itemId(null) + .start(null) + .end(null) + .build(); + + assertThat(json.write(dto)) + .hasEmptyJsonPathValue("$.itemId") + .hasEmptyJsonPathValue("$.start") + .hasEmptyJsonPathValue("$.end"); + } +} \ No newline at end of file diff --git a/gateway/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java b/gateway/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java new file mode 100644 index 0000000..93d2560 --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java @@ -0,0 +1,358 @@ +package ru.practicum.shareit.item; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +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.context.annotation.Import; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.web.servlet.MockMvc; +import ru.practicum.shareit.exception.GatewayErrorHandler; +import ru.practicum.shareit.item.comment.dto.CommentDtoRequest; +import ru.practicum.shareit.item.dto.*; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(ItemController.class) +@Import(GatewayErrorHandler.class) +public class ItemControllerTest { + private static final String X_SHARER_USER_ID = "X-Sharer-User-Id"; + private static final Long VALID_USER_ID = 1L; + private static final Long VALID_ITEM_ID = 1L; + private static final Long INVALID_ID = -1L; + private static final Long VALID_REQUEST_ID = 1L; + private final String url = "/items"; + @Autowired + private MockMvc mvc; + @Autowired + private ObjectMapper objectMapper; + @MockBean + private ItemClient itemClient; + + @BeforeEach + void setUp() { + Mockito.reset(itemClient); + setupDefaultSuccessfulMocks(); + } + + @Test + public void getAllItemsByOwner_WithValidParams_ReturnsOk() throws Exception { + mvc.perform(get(url) + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isOk()); + + verify(itemClient).getAllItemsByOwner(eq(VALID_USER_ID)); + } + + @Test + public void getAllItemsByOwner_WithNegativeUserId_ReturnsBadRequest() throws Exception { + mvc.perform(get(url) + .header(X_SHARER_USER_ID, INVALID_ID)) + .andExpect(status().isBadRequest()); + + verify(itemClient, never()).getAllItemsByOwner(anyLong()); + } + + @Test + public void getAllItemsByOwner_WithoutUserIdHeader_ReturnsBadRequest() throws Exception { + mvc.perform(get(url)) + .andExpect(status().isBadRequest()); + + verify(itemClient, never()).getAllItemsByOwner(anyLong()); + } + + @Test + public void getItemById_WithValidParams_ReturnsOk() throws Exception { + mvc.perform(get("/items/{itemId}", VALID_ITEM_ID) + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isOk()); + + verify(itemClient).getItemById(eq(VALID_USER_ID), eq(VALID_ITEM_ID)); + } + + @Test + public void getItemById_WithNegativeItemId_ReturnsBadRequest() throws Exception { + mvc.perform(get("/items/{itemId}", INVALID_ID) + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isBadRequest()); + + verify(itemClient, never()).getItemById(anyLong(), anyLong()); + } + + @Test + public void getItemById_WithNegativeUserId_ReturnsBadRequest() throws Exception { + mvc.perform(get("/items/{itemId}", VALID_ITEM_ID) + .header(X_SHARER_USER_ID, INVALID_ID)) + .andExpect(status().isBadRequest()); + + verify(itemClient, never()).getItemById(anyLong(), anyLong()); + } + + @Test + public void searchByText_WithValidParams_ReturnsOk() throws Exception { + mvc.perform(get("/items/search") + .param("text", "test search") + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isOk()); + + verify(itemClient).searchByText(eq(VALID_USER_ID), eq("test search")); + } + + @Test + public void searchByText_WithoutTextParam_ReturnsBadRequest() throws Exception { + mvc.perform(get("/items/search") + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isBadRequest()); + + verify(itemClient, never()).searchByText(anyLong(), any()); + } + + @Test + public void searchByText_WithEmptyText_ReturnsBadRequest() throws Exception { + mvc.perform(get("/items/search") + .param("text", "") + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isBadRequest()); + + verify(itemClient, never()).searchByText(anyLong(), any()); + } + + @Test + public void createItem_WithValidData_ReturnsCreated() throws Exception { + ItemDtoRequest item = ItemDtoRequest.builder() + .name("Тестовая вещь") + .description("Описание вещи") + .available(true) + .requestId(VALID_REQUEST_ID) + .build(); + + mvc.perform(post(url) + .header(X_SHARER_USER_ID, VALID_USER_ID) + .content(objectMapper.writeValueAsString(item)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()); + + verify(itemClient).createItem(eq(VALID_USER_ID), any(ItemDtoRequest.class)); + } + + @Test + public void createItem_WithNullRequestId_ReturnsCreated() throws Exception { + ItemDtoRequest item = ItemDtoRequest.builder() + .name("Тестовая вещь") + .description("Описание вещи") + .available(true) + .requestId(null) + .build(); + + mvc.perform(post(url) + .header(X_SHARER_USER_ID, VALID_USER_ID) + .content(objectMapper.writeValueAsString(item)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()); + + verify(itemClient).createItem(eq(VALID_USER_ID), any(ItemDtoRequest.class)); + } + + @Test + public void createItem_WithNegativeRequestId_ReturnsBadRequest() throws Exception { + ItemDtoRequest item = ItemDtoRequest.builder() + .name("Тестовая вещь") + .description("Описание вещи") + .available(true) + .requestId(INVALID_ID) + .build(); + + mvc.perform(post(url) + .header(X_SHARER_USER_ID, VALID_USER_ID) + .content(objectMapper.writeValueAsString(item)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + verify(itemClient, never()).createItem(anyLong(), any()); + } + + @Test + public void createItem_WithZeroRequestId_ReturnsBadRequest() throws Exception { + ItemDtoRequest item = ItemDtoRequest.builder() + .name("Тестовая вещь") + .description("Описание вещи") + .available(true) + .requestId(0L) + .build(); + + mvc.perform(post(url) + .header(X_SHARER_USER_ID, VALID_USER_ID) + .content(objectMapper.writeValueAsString(item)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + verify(itemClient, never()).createItem(anyLong(), any()); + } + + @Test + public void createItem_WithMultipleValidationErrors_ReturnsBadRequest() throws Exception { + ItemDtoRequest item = ItemDtoRequest.builder() + .name("") + .description("Описание вещи") + .available(true) + .requestId(INVALID_ID) + .build(); + + mvc.perform(post(url) + .header(X_SHARER_USER_ID, VALID_USER_ID) + .content(objectMapper.writeValueAsString(item)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + verify(itemClient, never()).createItem(anyLong(), any()); + } + + @Test + public void updateItem_WithValidRequestId_ReturnsOk() throws Exception { + ItemDtoRequest item = ItemDtoRequest.builder() + .name("Тестовая вещь") + .description("Описание вещи") + .available(false) + .requestId(2L) + .build(); + + mvc.perform(patch("/items/{itemId}", VALID_ITEM_ID) + .header(X_SHARER_USER_ID, VALID_USER_ID) + .content(objectMapper.writeValueAsString(item)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + + verify(itemClient).updateItem(eq(VALID_USER_ID), eq(VALID_ITEM_ID), any(ItemUpdateDtoRequest.class)); + } + + @Test + public void updateItem_WithNegativeRequestId_ReturnsBadRequest() throws Exception { + ItemDtoRequest item = ItemDtoRequest.builder() + .name("Тестовая вещь") + .description("Описание вещи") + .available(false) + .requestId(INVALID_ID) + .build(); + + mvc.perform(patch("/items/{itemId}", VALID_ITEM_ID) + .header(X_SHARER_USER_ID, VALID_USER_ID) + .content(objectMapper.writeValueAsString(item)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + verify(itemClient, never()).updateItem(anyLong(), anyLong(), any()); + } + + @Test + public void deleteItem_WithValidParams_ReturnsOk() throws Exception { + mvc.perform(delete("/items/{itemId}", VALID_ITEM_ID) + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isOk()); + + verify(itemClient).deleteItem(eq(VALID_USER_ID), eq(VALID_ITEM_ID)); + } + + @Test + public void deleteItem_WithNegativeItemId_ReturnsBadRequest() throws Exception { + mvc.perform(delete("/items/{itemId}", INVALID_ID) + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isBadRequest()); + + verify(itemClient, never()).deleteItem(anyLong(), anyLong()); + } + + @Test + public void deleteItem_WithNegativeUserId_ReturnsBadRequest() throws Exception { + mvc.perform(delete("/items/{itemId}", VALID_ITEM_ID) + .header(X_SHARER_USER_ID, INVALID_ID)) + .andExpect(status().isBadRequest()); + + verify(itemClient, never()).deleteItem(anyLong(), anyLong()); + } + + @Test + public void addComment_WithValidData_ReturnsCreated() throws Exception { + CommentDtoRequest comment = CommentDtoRequest.builder() + .text("Текст отзыва") + .build(); + + mvc.perform(post("/items/{itemId}/comment", VALID_ITEM_ID) + .header(X_SHARER_USER_ID, VALID_USER_ID) + .content(objectMapper.writeValueAsString(comment)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()); + + verify(itemClient).addComment(eq(VALID_USER_ID), eq(VALID_ITEM_ID), any(CommentDtoRequest.class)); + } + + @Test + public void addComment_WithEmptyText_ReturnsBadRequest() throws Exception { + CommentDtoRequest comment = CommentDtoRequest.builder() + .text("") + .build(); + + mvc.perform(post("/items/{itemId}/comment", VALID_ITEM_ID) + .header(X_SHARER_USER_ID, VALID_USER_ID) + .content(objectMapper.writeValueAsString(comment)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + verify(itemClient, never()).addComment(anyLong(), anyLong(), any()); + } + + @Test + public void getComments_WithValidParams_ReturnsOk() throws Exception { + mvc.perform(get("/items/{itemId}/comment", VALID_ITEM_ID) + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isOk()); + + verify(itemClient).getAllComments(eq(VALID_USER_ID), eq(VALID_ITEM_ID)); + } + + @Test + public void getComments_WithNegativeItemId_ReturnsBadRequest() throws Exception { + mvc.perform(get("/items/{itemId}/comment", INVALID_ID) + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isBadRequest()); + + verify(itemClient, never()).getAllComments(anyLong(), anyLong()); + } + + @Test + public void getComments_WithNegativeUserId_ReturnsBadRequest() throws Exception { + mvc.perform(get("/items/{itemId}/comment", VALID_ITEM_ID) + .header(X_SHARER_USER_ID, INVALID_ID)) + .andExpect(status().isBadRequest()); + + verify(itemClient, never()).getAllComments(anyLong(), anyLong()); + } + + // Вспомогательный метод для настройки мок-объектов + private void setupDefaultSuccessfulMocks() { + when(itemClient.getAllItemsByOwner(anyLong())) + .thenReturn(ResponseEntity.status(HttpStatus.OK).build()); + when(itemClient.getItemById(anyLong(), anyLong())) + .thenReturn(ResponseEntity.status(HttpStatus.OK).build()); + when(itemClient.searchByText(anyLong(), any(String.class))) + .thenReturn(ResponseEntity.status(HttpStatus.OK).build()); + when(itemClient.createItem(anyLong(), any(ItemDtoRequest.class))) + .thenReturn(ResponseEntity.status(HttpStatus.CREATED).build()); + when(itemClient.updateItem(anyLong(), anyLong(), any(ItemUpdateDtoRequest.class))) + .thenReturn(ResponseEntity.status(HttpStatus.OK).build()); + when(itemClient.addComment(anyLong(), anyLong(), any(CommentDtoRequest.class))) + .thenReturn(ResponseEntity.status(HttpStatus.CREATED).build()); + when(itemClient.getAllComments(anyLong(), anyLong())) + .thenReturn(ResponseEntity.status(HttpStatus.OK).build()); + + // Для void метода deleteItem + Mockito.doNothing().when(itemClient).deleteItem(anyLong(), anyLong()); + } +} \ No newline at end of file diff --git a/gateway/src/test/java/ru/practicum/shareit/item/ItemDtoRequestJsonTest.java b/gateway/src/test/java/ru/practicum/shareit/item/ItemDtoRequestJsonTest.java new file mode 100644 index 0000000..c41aace --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/item/ItemDtoRequestJsonTest.java @@ -0,0 +1,83 @@ +package ru.practicum.shareit.item; + +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 ru.practicum.shareit.item.dto.ItemDtoRequest; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +/** + * Тесты для проверки сериализации и десериализации ItemDtoRequest + * с использованием аннотации @JsonTest для изолированного тестирования JSON + */ +@JsonTest +public class ItemDtoRequestJsonTest { + + @Autowired + private JacksonTester json; + + // Тест сериализации полного объекта в JSON + @Test + public void serialize_shouldProduceCorrectJsonStructure() throws Exception { + ItemDtoRequest dto = new ItemDtoRequest("Дрель", "Аккумуляторная дрель", + true, 123L); + + assertThat(json.write(dto)) + .hasJsonPathStringValue("$.name", "Дрель") + .hasJsonPathStringValue("$.description", "Аккумуляторная дрель") + .hasJsonPathBooleanValue("$.available", true) + .hasJsonPathNumberValue("$.requestId", 123L); + } + + // Тест десериализации JSON в объект + @Test + public void deserialize_shouldParseJsonCorrectly() throws Exception { + String jsonContent = """ + { + "name": "Дрель", + "description": "Аккумуляторная дрель", + "available": true, + "requestId": 123 + } + """; + + ItemDtoRequest result = json.parseObject(jsonContent); + + assertThat(result) + .hasFieldOrPropertyWithValue("name", "Дрель") + .hasFieldOrPropertyWithValue("description", "Аккумуляторная дрель") + .hasFieldOrPropertyWithValue("available", true) + .hasFieldOrPropertyWithValue("requestId", 123L); + } + + // Тест сериализации без requestId (nullable поле) + @Test + public void serialize_shouldOmitNullRequestId() throws Exception { + ItemDtoRequest dto = new ItemDtoRequest("Дрель", "Аккумуляторная дрель", + true, null); + + assertThat(json.write(dto)) + .hasJsonPathStringValue("$.name", "Дрель") + .doesNotHaveJsonPathValue("$.requestId"); + } + + // Тест десериализации без requestId + @Test + public void deserialize_shouldHandleMissingRequestId() throws Exception { + String jsonContent = """ + { + "name": "Дрель", + "description": "Аккумуляторная дрель", + "available": true + } + """; + + ItemDtoRequest result = json.parseObject(jsonContent); + + assertThat(result) + .hasFieldOrPropertyWithValue("name", "Дрель") + .hasFieldOrPropertyWithValue("requestId", null); + } +} \ No newline at end of file diff --git a/gateway/src/test/java/ru/practicum/shareit/item/ItemUpdateDtoRequestJsonTest.java b/gateway/src/test/java/ru/practicum/shareit/item/ItemUpdateDtoRequestJsonTest.java new file mode 100644 index 0000000..8ad1a44 --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/item/ItemUpdateDtoRequestJsonTest.java @@ -0,0 +1,123 @@ +package ru.practicum.shareit.item; + +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 ru.practicum.shareit.item.dto.ItemUpdateDtoRequest; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Тесты для проверки сериализации и десериализации ItemUpdateDtoRequest + * с использованием аннотации @JsonTest для изолированного тестирования JSON + */ +@JsonTest +public class ItemUpdateDtoRequestJsonTest { + + @Autowired + private JacksonTester json; + + // Тест сериализации частичного обновления + @Test + public void serialize_shouldHandlePartialData() throws Exception { + ItemUpdateDtoRequest dto = new ItemUpdateDtoRequest("Дрель", null, + false, null); + + assertThat(json.write(dto)).isEqualToJson(""" + { + "name": "Дрель", + "available": false + } + """); + } + + // Тест сериализации с полными данными + @Test + public void serialize_shouldHandleAllFields() throws Exception { + ItemUpdateDtoRequest dto = new ItemUpdateDtoRequest("Дрель", "Аккумуляторная дрель", + true, 123L); + + assertThat(json.write(dto)).isEqualToJson(""" + { + "name": "Дрель", + "description": "Аккумуляторная дрель", + "available": true, + "requestId": 123 + } + """); + } + + // Тест сериализации с null значениями + @Test + public void serialize_shouldHandleNullValues() throws Exception { + ItemUpdateDtoRequest dto = new ItemUpdateDtoRequest(null, null, null, null); + + assertThat(json.write(dto)).isEqualToJson(""" + { + "name": null, + "description": null, + "available": null, + "requestId": null + } + """); + } + + // Тест десериализации частичного обновления + @Test + public void deserialize_shouldParsePartialData() throws Exception { + String jsonContent = """ + { + "name": "Дрель", + "available": false + } + """; + + ItemUpdateDtoRequest result = json.parseObject(jsonContent); + + assertThat(result.getName()).isEqualTo("Дрель"); + assertThat(result.getAvailable()).isEqualTo(false); + assertThat(result.getDescription()).isNull(); + assertThat(result.getRequestId()).isNull(); + } + + // Тест десериализации с полными данными + @Test + public void deserialize_shouldParseAllFields() throws Exception { + String jsonContent = """ + { + "name": "Дрель", + "description": "Аккумуляторная дрель", + "available": true, + "requestId": 123 + } + """; + + ItemUpdateDtoRequest result = json.parseObject(jsonContent); + + assertThat(result.getName()).isEqualTo("Дрель"); + assertThat(result.getDescription()).isEqualTo("Аккумуляторная дрель"); + assertThat(result.getAvailable()).isEqualTo(true); + assertThat(result.getRequestId()).isEqualTo(123L); + } + + // Тест десериализации с явными null значениями + @Test + public void deserialize_shouldHandleExplicitNulls() throws Exception { + String jsonContent = """ + { + "name": null, + "description": null, + "available": null, + "requestId": null + } + """; + + ItemUpdateDtoRequest result = json.parseObject(jsonContent); + + assertThat(result.getName()).isNull(); + assertThat(result.getDescription()).isNull(); + assertThat(result.getAvailable()).isNull(); + assertThat(result.getRequestId()).isNull(); + } +} \ No newline at end of file diff --git a/gateway/src/test/java/ru/practicum/shareit/item/comment/CommentDtoRequestJsonTest.java b/gateway/src/test/java/ru/practicum/shareit/item/comment/CommentDtoRequestJsonTest.java new file mode 100644 index 0000000..3eda337 --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/item/comment/CommentDtoRequestJsonTest.java @@ -0,0 +1,81 @@ +package ru.practicum.shareit.item.comment; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.json.JsonTest; +import org.springframework.boot.test.json.JacksonTester; +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.item.comment.dto.CommentDtoRequest; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Тесты для проверки сериализации и десериализации CommentDtoRequest + * с использованием аннотации @JsonTest для изолированного тестирования JSON + */ +@JsonTest +public class CommentDtoRequestJsonTest { + + @Autowired + private JacksonTester json; + + // Тест корректной сериализации объекта в JSON + @Test + public void serialize_shouldProduceCorrectJsonStructure() throws Exception { + CommentDtoRequest dto = CommentDtoRequest.builder() + .text("Тестовый комментарий") + .build(); + + assertThat(json.write(dto)) + .hasJsonPathStringValue("$.text", "Тестовый комментарий"); + } + + // Тест корректной десериализации JSON в объект + @Test + public void deserialize_shouldParseJsonCorrectly() throws Exception { + String jsonContent = "{\"text\": \"Комментарий из JSON\"}"; + + CommentDtoRequest result = json.parseObject(jsonContent); + + assertThat(result.getText()).isEqualTo("Комментарий из JSON"); + } + + // Тест сериализации с пустой строкой + @Test + public void serialize_shouldHandleEmptyString() throws Exception { + CommentDtoRequest dto = CommentDtoRequest.builder() + .text("") + .build(); + + assertThat(json.write(dto)) + .hasJsonPathStringValue("$.text", ""); + } + + // Тест десериализации с пустой строкой + @Test + public void deserialize_shouldParseEmptyString() throws Exception { + String jsonContent = "{\"text\": \"\"}"; + + CommentDtoRequest result = json.parseObject(jsonContent); + + assertThat(result.getText()).isEqualTo(""); + } + + // Тест сериализации при null значении - используем конструктор + @Test + public void serialize_shouldHandleNullValue() throws Exception { + CommentDtoRequest dto = new CommentDtoRequest(null); + + assertThat(json.write(dto)) + .hasEmptyJsonPathValue("$.text"); + } + + // Тест десериализации с null значением + @Test + public void deserialize_shouldParseNullValue() throws Exception { + String jsonContent = "{\"text\": null}"; + + CommentDtoRequest result = json.parseObject(jsonContent); + + assertThat(result.getText()).isNull(); + } +} \ No newline at end of file diff --git a/gateway/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java b/gateway/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java new file mode 100644 index 0000000..cc0956d --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java @@ -0,0 +1,220 @@ +package ru.practicum.shareit.request; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +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.context.annotation.Import; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.web.servlet.MockMvc; +import ru.practicum.shareit.exception.GatewayErrorHandler; +import ru.practicum.shareit.request.dto.ItemRequestDtoRequest; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(ItemRequestController.class) +@Import(GatewayErrorHandler.class) +public class ItemRequestControllerTest { + private final String url = "/requests"; + private static final String X_SHARER_USER_ID = "X-Sharer-User-Id"; + private static final Long VALID_USER_ID = 1L; + private static final Long VALID_REQUEST_ID = 1L; + private static final Long INVALID_ID = -1L; + @Autowired + private MockMvc mvc; + @Autowired + private ObjectMapper objectMapper; + @MockBean + private ItemRequestClient itemRequestClient; + + @BeforeEach + public void setUo() { + Mockito.reset(itemRequestClient); + setupDefaultSuccessfulMocks(); + } + + @Test + public void createRequest_WithValidData_ReturnsCreated() throws Exception { + ItemRequestDtoRequest request = ItemRequestDtoRequest + .builder() + .description("Описание запроса") + .build(); + + mvc.perform(post(url) + .header(X_SHARER_USER_ID, VALID_USER_ID) + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()); + + verify(itemRequestClient).createRequest(eq(VALID_USER_ID), any(ItemRequestDtoRequest.class)); + } + + @Test + public void createRequest_WithEmptyDescription_ReturnsBadRequest() throws Exception { + ItemRequestDtoRequest request = ItemRequestDtoRequest + .builder() + .description("") + .build(); + + mvc.perform(post(url) + .header(X_SHARER_USER_ID, VALID_USER_ID) + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + verify(itemRequestClient, never()).createRequest(anyLong(), any()); + } + + @Test + public void createRequest_WithNullDescription_ReturnsBadRequest() throws Exception { + ItemRequestDtoRequest request = ItemRequestDtoRequest + .builder() + .description(null) + .build(); + + mvc.perform(post(url) + .header(X_SHARER_USER_ID, VALID_USER_ID) + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + verify(itemRequestClient, never()).createRequest(anyLong(), any()); + } + + @Test + public void createRequest_WithNegativeUserId_ReturnsBadRequest() throws Exception { + ItemRequestDtoRequest request = ItemRequestDtoRequest + .builder() + .description("Описание запроса") + .build(); + + mvc.perform(post(url) + .header(X_SHARER_USER_ID, INVALID_ID) + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + verify(itemRequestClient, never()).createRequest(anyLong(), any()); + } + + @Test + public void createRequest_WithoutUserIdHeader_ReturnsBadRequest() throws Exception { + ItemRequestDtoRequest request = ItemRequestDtoRequest + .builder() + .description("Описание запроса") + .build(); + + mvc.perform(post(url) + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + verify(itemRequestClient, never()).createRequest(anyLong(), any()); + } + + @Test + public void getAllRequests_WithValidUserId_ReturnsOk() throws Exception { + mvc.perform(get("/requests/all") + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isOk()); + + verify(itemRequestClient).getAllRequests(eq(VALID_USER_ID)); + } + + @Test + public void getAllRequests_WithNegativeUserId_ReturnsBadRequest() throws Exception { + mvc.perform(get("/requests/all") + .header(X_SHARER_USER_ID, INVALID_ID)) + .andExpect(status().isBadRequest()); + + verify(itemRequestClient, never()).getAllRequests(anyLong()); + } + + @Test + public void getAllRequests_WithoutUserIdHeader_ReturnsBadRequest() throws Exception { + mvc.perform(get("/requests/all")) + .andExpect(status().isBadRequest()); + + verify(itemRequestClient, never()).getAllRequests(anyLong()); + } + + @Test + public void getRequestByUser_WithValidUserId_ReturnsOk() throws Exception { + mvc.perform(get(url) + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isOk()); + + verify(itemRequestClient).getRequestByUser(eq(VALID_USER_ID)); + } + + @Test + public void getRequestByUser_WithNegativeUserId_ReturnsBadRequest() throws Exception { + mvc.perform(get(url) + .header(X_SHARER_USER_ID, INVALID_ID)) + .andExpect(status().isBadRequest()); + + verify(itemRequestClient, never()).getRequestByUser(anyLong()); + } + + @Test + public void getRequestByUser_WithoutUserIdHeader_ReturnsBadRequest() throws Exception { + mvc.perform(get(url)) + .andExpect(status().isBadRequest()); + + verify(itemRequestClient, never()).getRequestByUser(anyLong()); + } + + @Test + public void getRequestById_WithValidParams_ReturnsOk() throws Exception { + mvc.perform(get("/requests/{requestId}", VALID_REQUEST_ID) + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isOk()); + + verify(itemRequestClient).getRequestById(eq(VALID_USER_ID), eq(VALID_REQUEST_ID)); + } + + @Test + public void getRequestById_WithNegativeRequestId_ReturnsBadRequest() throws Exception { + mvc.perform(get("/requests/{requestId}", INVALID_ID) + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isBadRequest()); + + verify(itemRequestClient, never()).getRequestById(anyLong(), anyLong()); + } + + @Test + public void getRequestById_WithNegativeUserId_ReturnsBadRequest() throws Exception { + mvc.perform(get("/requests/{requestId}", VALID_REQUEST_ID) + .header(X_SHARER_USER_ID, INVALID_ID)) + .andExpect(status().isBadRequest()); + + verify(itemRequestClient, never()).getRequestById(anyLong(), anyLong()); + } + + @Test + public void getRequestById_WithoutUserIdHeader_ReturnsBadRequest() throws Exception { + mvc.perform(get("/requests/{requestId}", VALID_REQUEST_ID)) + .andExpect(status().isBadRequest()); + + verify(itemRequestClient, never()).getRequestById(anyLong(), anyLong()); + } + + private void setupDefaultSuccessfulMocks() { + when(itemRequestClient.createRequest(anyLong(), any(ItemRequestDtoRequest.class))) + .thenReturn(ResponseEntity.status(HttpStatus.CREATED).build()); + when(itemRequestClient.getAllRequests(anyLong())) + .thenReturn(ResponseEntity.status(HttpStatus.OK).build()); + when(itemRequestClient.getRequestByUser(anyLong())) + .thenReturn(ResponseEntity.status(HttpStatus.OK).build()); + when(itemRequestClient.getRequestById(anyLong(), anyLong())) + .thenReturn(ResponseEntity.status(HttpStatus.OK).build()); + } +} \ No newline at end of file diff --git a/gateway/src/test/java/ru/practicum/shareit/request/ItemRequestDtoRequestJsonTest.java b/gateway/src/test/java/ru/practicum/shareit/request/ItemRequestDtoRequestJsonTest.java new file mode 100644 index 0000000..8428c7a --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/request/ItemRequestDtoRequestJsonTest.java @@ -0,0 +1,98 @@ +package ru.practicum.shareit.request; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.json.JsonTest; +import org.springframework.boot.test.json.JacksonTester; +import ru.practicum.shareit.request.dto.ItemRequestDtoRequest; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Тесты для проверки сериализации и десериализации ItemRequestDtoRequest + * с использованием аннотации @JsonTest для изолированного тестирования JSON + */ +@JsonTest +public class ItemRequestDtoRequestJsonTest { + + @Autowired + private JacksonTester json; + + // Тест сериализации объекта в JSON + @Test + public void serialize_shouldProduceCorrectJson() throws Exception { + ItemRequestDtoRequest dto = new ItemRequestDtoRequest("Нужна дрель"); + + assertThat(json.write(dto)).isEqualToJson(""" + { + "description": "Нужна дрель" + } + """); + } + + // Тест десериализации JSON в объект + @Test + public void deserialize_shouldParseJsonCorrectly() throws Exception { + String jsonContent = """ + { + "description": "Нужна дрель" + } + """; + + ItemRequestDtoRequest result = json.parseObject(jsonContent); + + assertThat(result.getDescription()).isEqualTo("Нужна дрель"); + } + + // Тест сериализации с пустой строкой + @Test + public void serialize_shouldHandleEmptyString() throws Exception { + ItemRequestDtoRequest dto = new ItemRequestDtoRequest(""); + + assertThat(json.write(dto)).isEqualToJson(""" + { + "description": "" + } + """); + } + + // Тест десериализации с пустой строкой + @Test + public void deserialize_shouldParseEmptyString() throws Exception { + String jsonContent = """ + { + "description": "" + } + """; + + ItemRequestDtoRequest result = json.parseObject(jsonContent); + + assertThat(result.getDescription()).isEqualTo(""); + } + + // Тест сериализации с null значением + @Test + public void serialize_shouldHandleNullValue() throws Exception { + ItemRequestDtoRequest dto = new ItemRequestDtoRequest(null); + + assertThat(json.write(dto)).isEqualToJson(""" + { + "description": null + } + """); + } + + // Тест десериализации с null значением + @Test + public void deserialize_shouldParseNullValue() throws Exception { + String jsonContent = """ + { + "description": null + } + """; + + ItemRequestDtoRequest result = json.parseObject(jsonContent); + + assertThat(result.getDescription()).isNull(); + } +} \ No newline at end of file diff --git a/gateway/src/test/java/ru/practicum/shareit/user/UserControllerTest.java b/gateway/src/test/java/ru/practicum/shareit/user/UserControllerTest.java new file mode 100644 index 0000000..8c8d238 --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/user/UserControllerTest.java @@ -0,0 +1,223 @@ +package ru.practicum.shareit.user; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +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.context.annotation.Import; +import org.springframework.http.*; +import org.springframework.http.ResponseEntity; +import org.springframework.test.web.servlet.MockMvc; +import ru.practicum.shareit.exception.GatewayErrorHandler; +import ru.practicum.shareit.user.dto.*; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(UserController.class) +@Import(GatewayErrorHandler.class) +public class UserControllerTest { + + private final String url = "/users"; + private static final Long VALID_USER_ID = 1L; + private static final Long INVALID_ID = -1L; + @Autowired + private MockMvc mvc; + @Autowired + private ObjectMapper objectMapper; + @MockBean + private UserClient userClient; + + @BeforeEach + public void setUo() { + Mockito.reset(userClient); + setupDefaultSuccessfulMocks(); + } + + @Test + public void getAllUsers_ReturnsOk() throws Exception { + mvc.perform(get(url)) + .andExpect(status().isOk()); + + verify(userClient).getAllUsers(); + } + + @Test + public void getByUserId_WithValidId_ReturnsOk() throws Exception { + mvc.perform(get("/users/{userId}", VALID_USER_ID)) + .andExpect(status().isOk()); + + verify(userClient).getUserById(eq(VALID_USER_ID)); + } + + @Test + public void getByUserId_WithNegativeId_ReturnsBadRequest() throws Exception { + mvc.perform(get("/users/{userId}", INVALID_ID)) + .andExpect(status().isBadRequest()); + + verify(userClient, never()).getUserById(anyLong()); + } + + @Test + public void getByUserId_WithZeroId_ReturnsBadRequest() throws Exception { + mvc.perform(get("/users/{userId}", 0L)) + .andExpect(status().isBadRequest()); + + verify(userClient, never()).getUserById(anyLong()); + } + + @Test + public void createUser_WithValidData_ReturnsCreated() throws Exception { + UserDtoRequest user = UserDtoRequest + .builder() + .name("Имя") + .email("name@yandex.ru") + .build(); + + mvc.perform(post(url) + .content(objectMapper.writeValueAsString(user)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()); + + verify(userClient).createUser(any(UserDtoRequest.class)); + } + + @Test + public void createUser_WithEmptyName_ReturnsBadRequest() throws Exception { + UserDtoRequest user = UserDtoRequest + .builder() + .name("") + .email("name@yandex.ru") + .build(); + + mvc.perform(post(url) + .content(objectMapper.writeValueAsString(user)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + verify(userClient, never()).createUser(any()); + } + + @Test + public void createUser_WithInvalidEmail_ReturnsBadRequest() throws Exception { + UserDtoRequest user = UserDtoRequest + .builder() + .name("Имя") + .email("email-yandex") + .build(); + + mvc.perform(post(url) + .content(objectMapper.writeValueAsString(user)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + verify(userClient, never()).createUser(any()); + } + + @Test + public void createUser_WithoutEmail_ReturnsBadRequest() throws Exception { + UserDtoRequest user = UserDtoRequest + .builder() + .name("Имя") + .email(null) + .build(); + + mvc.perform(post(url) + .content(objectMapper.writeValueAsString(user)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + verify(userClient, never()).createUser(any()); + } + + @Test + public void updateUser_WithValidData_ReturnsOk() throws Exception { + UserDtoRequest user = UserDtoRequest + .builder() + .name("Имя") + .email("name@yandex.ru") + .build(); + + mvc.perform(patch("/users/{userId}", VALID_USER_ID) + .content(objectMapper.writeValueAsString(user)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + + verify(userClient).updateUser(eq(VALID_USER_ID), any(UserUpdateDtoRequest.class)); + } + + @Test + public void updateUser_WithNegativeId_ReturnsBadRequest() throws Exception { + UserDtoRequest user = UserDtoRequest + .builder() + .name("Имя") + .email("name@yandex.ru") + .build(); + + mvc.perform(patch("/users/{userId}", INVALID_ID) + .content(objectMapper.writeValueAsString(user)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + verify(userClient, never()).updateUser(anyLong(), any()); + } + + @Test + public void updateUser_WithInvalidEmail_ReturnsBadRequest() throws Exception { + UserDtoRequest user = UserDtoRequest + .builder() + .name("Имя") + .email("email-yandex") + .build(); + + mvc.perform(patch("/users/{userId}", VALID_USER_ID) + .content(objectMapper.writeValueAsString(user)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + verify(userClient, never()).updateUser(anyLong(), any()); + } + + @Test + public void deleteUser_WithValidId_ReturnsOk() throws Exception { + mvc.perform(delete("/users/{userId}", VALID_USER_ID)) + .andExpect(status().isOk()); + + verify(userClient).deleteUser(eq(VALID_USER_ID)); + } + + @Test + public void deleteUser_WithNegativeId_ReturnsBadRequest() throws Exception { + mvc.perform(delete("/users/{userId}", INVALID_ID)) + .andExpect(status().isBadRequest()); + + verify(userClient, never()).deleteUser(anyLong()); + } + + @Test + public void deleteUser_WithZeroId_ReturnsBadRequest() throws Exception { + mvc.perform(delete("/users/{userId}", 0L)) + .andExpect(status().isBadRequest()); + + verify(userClient, never()).deleteUser(anyLong()); + } + + private void setupDefaultSuccessfulMocks() { + when(userClient.getAllUsers()) + .thenReturn(ResponseEntity.status(HttpStatus.OK).build()); + when(userClient.getUserById(anyLong())) + .thenReturn(ResponseEntity.status(HttpStatus.OK).build()); + when(userClient.createUser(any(UserDtoRequest.class))) + .thenReturn(ResponseEntity.status(HttpStatus.CREATED).build()); + when(userClient.updateUser(anyLong(), any(UserUpdateDtoRequest.class))) + .thenReturn(ResponseEntity.status(HttpStatus.OK).build()); + + // Для void метода deleteUser + Mockito.doNothing().when(userClient).deleteUser(anyLong()); + } +} \ No newline at end of file diff --git a/gateway/src/test/java/ru/practicum/shareit/user/UserDtoRequestJsonTest.java b/gateway/src/test/java/ru/practicum/shareit/user/UserDtoRequestJsonTest.java new file mode 100644 index 0000000..aa32945 --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/user/UserDtoRequestJsonTest.java @@ -0,0 +1,78 @@ +package ru.practicum.shareit.user; + +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 ru.practicum.shareit.user.dto.UserDtoRequest; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Тесты для проверки сериализации и десериализации UserDtoRequest + * с использованием аннотации @JsonTest для изолированного тестирования JSON + */ +@JsonTest +public class UserDtoRequestJsonTest { + + @Autowired + private JacksonTester json; + + // Тест сериализации объекта в JSON + @Test + public void serialize_shouldProduceCorrectJson() throws Exception { + UserDtoRequest dto = new UserDtoRequest("Иван Иванов", "ivan@mail.ru"); + + assertThat(json.write(dto)).isEqualToJson(""" + { + "name": "Иван Иванов", + "email": "ivan@mail.ru" + } + """); + } + + // Тест десериализации JSON в объект + @Test + public void deserialize_shouldParseJsonCorrectly() throws Exception { + String jsonContent = """ + { + "name": "Иван Иванов", + "email": "ivan@mail.ru" + } + """; + + UserDtoRequest result = json.parseObject(jsonContent); + + assertThat(result.getName()).isEqualTo("Иван Иванов"); + assertThat(result.getEmail()).isEqualTo("ivan@mail.ru"); + } + + // Тест сериализации с пустыми строками + @Test + public void serialize_shouldHandleEmptyStrings() throws Exception { + UserDtoRequest dto = new UserDtoRequest("", ""); + + assertThat(json.write(dto)).isEqualToJson(""" + { + "name": "", + "email": "" + } + """); + } + + // Тест десериализации с пустыми строками + @Test + public void deserialize_shouldParseEmptyStrings() throws Exception { + String jsonContent = """ + { + "name": "", + "email": "" + } + """; + + UserDtoRequest result = json.parseObject(jsonContent); + + assertThat(result.getName()).isEqualTo(""); + assertThat(result.getEmail()).isEqualTo(""); + } +} \ No newline at end of file diff --git a/gateway/src/test/java/ru/practicum/shareit/user/UserUpdateDtoRequestJsonTest.java b/gateway/src/test/java/ru/practicum/shareit/user/UserUpdateDtoRequestJsonTest.java new file mode 100644 index 0000000..aea7b58 --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/user/UserUpdateDtoRequestJsonTest.java @@ -0,0 +1,135 @@ +package ru.practicum.shareit.user; + +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 ru.practicum.shareit.user.dto.UserUpdateDtoRequest; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Тесты для проверки сериализации и десериализации UserUpdateDtoRequest + * с использованием аннотации @JsonTest для изолированного тестирования JSON + */ +@JsonTest +public class UserUpdateDtoRequestJsonTest { + + @Autowired + private JacksonTester json; + + // Тест сериализации полного обновления + @Test + public void serialize_shouldHandleFullUpdate() throws Exception { + UserUpdateDtoRequest dto = new UserUpdateDtoRequest("Новое имя", "new@mail.ru"); + + assertThat(json.write(dto)).isEqualToJson(""" + { + "name": "Новое имя", + "email": "new@mail.ru" + } + """); + } + + // Тест сериализации частичного обновления (только имя) + @Test + public void serialize_shouldHandlePartialNameUpdate() throws Exception { + UserUpdateDtoRequest dto = new UserUpdateDtoRequest("Только имя", null); + + assertThat(json.write(dto)).isEqualToJson(""" + { + "name": "Только имя" + } + """); + } + + // Тест сериализации частичного обновления (только email) + @Test + public void serialize_shouldHandlePartialEmailUpdate() throws Exception { + UserUpdateDtoRequest dto = new UserUpdateDtoRequest(null, "only@mail.ru"); + + assertThat(json.write(dto)).isEqualToJson(""" + { + "email": "only@mail.ru" + } + """); + } + + // Тест десериализации полного обновления + @Test + public void deserialize_shouldParseFullUpdate() throws Exception { + String jsonContent = """ + { + "name": "Новое имя", + "email": "new@mail.ru" + } + """; + + UserUpdateDtoRequest result = json.parseObject(jsonContent); + + assertThat(result.getName()).isEqualTo("Новое имя"); + assertThat(result.getEmail()).isEqualTo("new@mail.ru"); + } + + // Тест десериализации частичного обновления (только имя) + @Test + public void deserialize_shouldParsePartialNameUpdate() throws Exception { + String jsonContent = """ + { + "name": "Только имя" + } + """; + + UserUpdateDtoRequest result = json.parseObject(jsonContent); + + assertThat(result.getName()).isEqualTo("Только имя"); + assertThat(result.getEmail()).isNull(); + } + + // Тест десериализации частичного обновления (только email) + @Test + public void deserialize_shouldParsePartialEmailUpdate() throws Exception { + String jsonContent = """ + { + "email": "only@mail.ru" + } + """; + + UserUpdateDtoRequest result = json.parseObject(jsonContent); + + assertThat(result.getName()).isNull(); + assertThat(result.getEmail()).isEqualTo("only@mail.ru"); + } + + // Тест десериализации с null значениями + @Test + public void deserialize_shouldHandleNullValues() throws Exception { + String jsonContent = """ + { + "name": null, + "email": null + } + """; + + UserUpdateDtoRequest result = json.parseObject(jsonContent); + + assertThat(result.getName()).isNull(); + assertThat(result.getEmail()).isNull(); + } + + // Тест десериализации с пустыми полями + @Test + public void deserialize_shouldHandleEmptyFields() throws Exception { + String jsonContent = """ + { + "name": "", + "email": "" + } + """; + + UserUpdateDtoRequest result = json.parseObject(jsonContent); + + assertThat(result.getName()).isEqualTo(""); + assertThat(result.getEmail()).isEqualTo(""); + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 1546b4a..b99dacf 100644 --- a/pom.xml +++ b/pom.xml @@ -12,102 +12,38 @@ ru.practicum shareit 0.0.1-SNAPSHOT + pom ShareIt + + gateway + server + + + 21 - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-actuator - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - org.postgresql - postgresql - runtime - - - - org.projectlombok - lombok - 1.18.38 - provided - - - - com.h2database - h2 - test - - - - org.springframework.boot - spring-boot-starter-test - test - - - org.springframework.boot - spring-boot-starter-validation - - - 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-compiler-plugin - - - - org.projectlombok - lombok - 1.18.38 - - - - - - + + org.springframework.boot + spring-boot-maven-plugin + + + true + + + + org.projectlombok + lombok + + + + org.apache.maven.plugins maven-surefire-plugin @@ -159,74 +95,6 @@ - - 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 - - - - @@ -254,17 +122,6 @@ - - coverage - - - - org.jacoco - jacoco-maven-plugin - - - - \ No newline at end of file diff --git a/server/pom.xml b/server/pom.xml new file mode 100644 index 0000000..e22f84f --- /dev/null +++ b/server/pom.xml @@ -0,0 +1,87 @@ + + + 4.0.0 + + ru.practicum + shareit + 0.0.1-SNAPSHOT + + + 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-actuator + + + + org.postgresql + postgresql + runtime + + + + com.h2database + h2 + test + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.projectlombok + lombok + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + coverage + + + + org.jacoco + jacoco-maven-plugin + + + + + + + \ 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/ShareItServer.java similarity index 72% rename from src/main/java/ru/practicum/shareit/ShareItApp.java rename to server/src/main/java/ru/practicum/shareit/ShareItServer.java index 0151768..ddf75ea 100644 --- a/src/main/java/ru/practicum/shareit/ShareItApp.java +++ b/server/src/main/java/ru/practicum/shareit/ShareItServer.java @@ -4,9 +4,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -public class ShareItApp { - +public class ShareItServer { public static void main(String[] args) { - SpringApplication.run(ShareItApp.class, args); + SpringApplication.run(ShareItServer.class, args); } } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/BookingController.java b/server/src/main/java/ru/practicum/shareit/booking/BookingServerController.java similarity index 89% rename from src/main/java/ru/practicum/shareit/booking/BookingController.java rename to server/src/main/java/ru/practicum/shareit/booking/BookingServerController.java index d107637..29287f9 100644 --- a/src/main/java/ru/practicum/shareit/booking/BookingController.java +++ b/server/src/main/java/ru/practicum/shareit/booking/BookingServerController.java @@ -2,25 +2,23 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; -import ru.practicum.shareit.booking.dto.BookingDto; -import ru.practicum.shareit.booking.dto.BookingNewDto; +import ru.practicum.shareit.booking.dto.*; import ru.practicum.shareit.booking.model.BookingState; import java.util.Collection; -/** - * TODO Sprint add-bookings. - */ @Slf4j @RestController @RequiredArgsConstructor @RequestMapping(path = "/bookings") -public class BookingController { +public class BookingServerController { private final BookingService bookingService; @GetMapping("/{bookingId}") + @ResponseStatus(HttpStatus.OK) public BookingDto findById(@RequestHeader("X-Sharer-User-Id") Long userId, @PathVariable Long bookingId) { log.info("Запрос на получение бронирования с Id:{} от пользователя с Id:{}", bookingId, userId); @@ -28,6 +26,7 @@ public BookingDto findById(@RequestHeader("X-Sharer-User-Id") Long userId, } @PostMapping + @ResponseStatus(HttpStatus.CREATED) public BookingDto create(@RequestHeader("X-Sharer-User-Id") Long userId, @RequestBody BookingNewDto bookingNewDto) { log.info("Запрос на создание бронирования с Id:{} от пользователя с Id:{}", userId, bookingNewDto.getItemId()); @@ -35,6 +34,7 @@ public BookingDto create(@RequestHeader("X-Sharer-User-Id") Long userId, } @PatchMapping("/{bookingId}") + @ResponseStatus(HttpStatus.OK) public BookingDto approve(@RequestHeader("X-Sharer-User-Id") Long userId, @PathVariable Long bookingId, @RequestParam(name = "approved") Boolean isApproved) { @@ -43,6 +43,7 @@ public BookingDto approve(@RequestHeader("X-Sharer-User-Id") Long userId, } @GetMapping("/owner") + @ResponseStatus(HttpStatus.OK) public Collection findByOwnerId(@RequestHeader("X-Sharer-User-Id") Long ownerId, @RequestParam(name = "state", defaultValue = "ALL") BookingState bookingState) { @@ -51,6 +52,7 @@ public Collection findByOwnerId(@RequestHeader("X-Sharer-User-Id") L } @GetMapping + @ResponseStatus(HttpStatus.OK) public Collection findByBookerId(@RequestHeader("X-Sharer-User-Id") Long bookerId, @RequestParam(name = "state", defaultValue = "ALL") BookingState bookingState) { diff --git a/src/main/java/ru/practicum/shareit/booking/BookingService.java b/server/src/main/java/ru/practicum/shareit/booking/BookingService.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/BookingService.java rename to server/src/main/java/ru/practicum/shareit/booking/BookingService.java diff --git a/src/main/java/ru/practicum/shareit/booking/BookingServiceImpl.java b/server/src/main/java/ru/practicum/shareit/booking/BookingServiceImpl.java similarity index 95% rename from src/main/java/ru/practicum/shareit/booking/BookingServiceImpl.java rename to server/src/main/java/ru/practicum/shareit/booking/BookingServiceImpl.java index 26c2589..5d6a0c7 100644 --- a/src/main/java/ru/practicum/shareit/booking/BookingServiceImpl.java +++ b/server/src/main/java/ru/practicum/shareit/booking/BookingServiceImpl.java @@ -4,12 +4,9 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import ru.practicum.shareit.booking.dao.BookingRepository; -import ru.practicum.shareit.booking.dto.BookingDto; -import ru.practicum.shareit.booking.dto.BookingNewDto; +import ru.practicum.shareit.booking.dto.*; import ru.practicum.shareit.booking.mapper.BookingMapper; -import ru.practicum.shareit.booking.model.Booking; -import ru.practicum.shareit.booking.model.BookingState; -import ru.practicum.shareit.booking.model.BookingStatus; +import ru.practicum.shareit.booking.model.*; import ru.practicum.shareit.booking.utils.BookingValidate; import ru.practicum.shareit.exception.*; import ru.practicum.shareit.exception.ValidationException; @@ -75,7 +72,7 @@ public BookingDto create(Long userId, BookingNewDto bookingNewDto) { // Проверяем доступность вещи if (!item.getAvailable()) { - throw new ValidationException("Вещь недоступна для бронирования"); + throw new ItemNotAvailableException("Вещь недоступна для бронирования"); } Booking booking = BookingMapper.mapToBooking(bookingNewDto); @@ -196,7 +193,7 @@ public Collection findByBookerId(Long bookerId, BookingState booking bookings = bookingRepository.findByBookerIdStateCurrent(bookerId); break; case PAST: - bookings = bookingRepository.findByOwnerIdStatePast(bookerId); + bookings = bookingRepository.findByBookerIdStatePast(bookerId); break; case FUTURE: bookings = bookingRepository.findByBookerIdStateFuture(bookerId); diff --git a/src/main/java/ru/practicum/shareit/booking/dao/BookingRepository.java b/server/src/main/java/ru/practicum/shareit/booking/dao/BookingRepository.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/dao/BookingRepository.java rename to server/src/main/java/ru/practicum/shareit/booking/dao/BookingRepository.java 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 87% 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 index bf2e2e1..1271ff1 100644 --- a/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java +++ b/server/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java @@ -1,18 +1,13 @@ package ru.practicum.shareit.booking.dto; import com.fasterxml.jackson.annotation.JsonFormat; -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; +import lombok.*; import ru.practicum.shareit.booking.model.BookingStatus; import ru.practicum.shareit.item.dto.ItemDto; import ru.practicum.shareit.user.dto.UserDto; import java.time.Instant; -/** - * TODO Sprint add-bookings. - */ @Getter @Setter @Builder(toBuilder = true) diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingNewDto.java b/server/src/main/java/ru/practicum/shareit/booking/dto/BookingNewDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/dto/BookingNewDto.java rename to server/src/main/java/ru/practicum/shareit/booking/dto/BookingNewDto.java diff --git a/src/main/java/ru/practicum/shareit/booking/mapper/BookingMapper.java b/server/src/main/java/ru/practicum/shareit/booking/mapper/BookingMapper.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/mapper/BookingMapper.java rename to server/src/main/java/ru/practicum/shareit/booking/mapper/BookingMapper.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 93% 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 index e6d2dea..d65c85d 100644 --- a/src/main/java/ru/practicum/shareit/booking/model/Booking.java +++ b/server/src/main/java/ru/practicum/shareit/booking/model/Booking.java @@ -7,11 +7,8 @@ import java.time.Instant; -/** - * TODO Sprint add-bookings. - */ @Entity -@Table(name = "bookings", schema = "public") +@Table(name = "bookings") @Getter @Setter @ToString diff --git a/src/main/java/ru/practicum/shareit/booking/model/BookingState.java b/server/src/main/java/ru/practicum/shareit/booking/model/BookingState.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/model/BookingState.java rename to server/src/main/java/ru/practicum/shareit/booking/model/BookingState.java diff --git a/src/main/java/ru/practicum/shareit/booking/model/BookingStatus.java b/server/src/main/java/ru/practicum/shareit/booking/model/BookingStatus.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/model/BookingStatus.java rename to server/src/main/java/ru/practicum/shareit/booking/model/BookingStatus.java diff --git a/src/main/java/ru/practicum/shareit/booking/utils/BookingValidate.java b/server/src/main/java/ru/practicum/shareit/booking/utils/BookingValidate.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/utils/BookingValidate.java rename to server/src/main/java/ru/practicum/shareit/booking/utils/BookingValidate.java diff --git a/src/main/java/ru/practicum/shareit/exception/DuplicateException.java b/server/src/main/java/ru/practicum/shareit/exception/DuplicateException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exception/DuplicateException.java rename to server/src/main/java/ru/practicum/shareit/exception/DuplicateException.java diff --git a/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java b/server/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exception/ErrorHandler.java rename to server/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java diff --git a/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java b/server/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exception/ErrorResponse.java rename to server/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java diff --git a/src/main/java/ru/practicum/shareit/exception/ForbiddenException.java b/server/src/main/java/ru/practicum/shareit/exception/ForbiddenException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exception/ForbiddenException.java rename to server/src/main/java/ru/practicum/shareit/exception/ForbiddenException.java diff --git a/src/main/java/ru/practicum/shareit/exception/IllegalArgumentException.java b/server/src/main/java/ru/practicum/shareit/exception/IllegalArgumentException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exception/IllegalArgumentException.java rename to server/src/main/java/ru/practicum/shareit/exception/IllegalArgumentException.java diff --git a/server/src/main/java/ru/practicum/shareit/exception/ItemNotAvailableException.java b/server/src/main/java/ru/practicum/shareit/exception/ItemNotAvailableException.java new file mode 100644 index 0000000..2e32730 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/exception/ItemNotAvailableException.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.exception; + +public class ItemNotAvailableException extends RuntimeException { + public ItemNotAvailableException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/exception/NotFoundException.java b/server/src/main/java/ru/practicum/shareit/exception/NotFoundException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exception/NotFoundException.java rename to server/src/main/java/ru/practicum/shareit/exception/NotFoundException.java diff --git a/src/main/java/ru/practicum/shareit/exception/ValidationException.java b/server/src/main/java/ru/practicum/shareit/exception/ValidationException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exception/ValidationException.java rename to server/src/main/java/ru/practicum/shareit/exception/ValidationException.java diff --git a/src/main/java/ru/practicum/shareit/item/ItemController.java b/server/src/main/java/ru/practicum/shareit/item/ItemServerController.java similarity index 86% rename from src/main/java/ru/practicum/shareit/item/ItemController.java rename to server/src/main/java/ru/practicum/shareit/item/ItemServerController.java index 73c9b62..f6a44fb 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemController.java +++ b/server/src/main/java/ru/practicum/shareit/item/ItemServerController.java @@ -2,12 +2,10 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; -import ru.practicum.shareit.item.comment.dto.CommentDto; -import ru.practicum.shareit.item.comment.dto.CommentNewDto; -import ru.practicum.shareit.item.dto.ItemDto; -import ru.practicum.shareit.item.dto.ItemFullDto; -import ru.practicum.shareit.item.dto.ItemNewDto; +import ru.practicum.shareit.item.comment.dto.*; +import ru.practicum.shareit.item.dto.*; import java.util.Collection; @@ -15,17 +13,19 @@ @RestController @RequiredArgsConstructor @RequestMapping("/items") -public class ItemController { +public class ItemServerController { private final ItemService itemService; @GetMapping + @ResponseStatus(HttpStatus.OK) public Collection findAllByUserId(@RequestHeader("X-Sharer-User-Id") Long userId) { log.info("Запрос на получение всех вещей пользователя c Id:{}", userId); return itemService.findAllByUserId(userId); } @GetMapping("/{itemId}") + @ResponseStatus(HttpStatus.OK) public ItemFullDto findById(@RequestHeader("X-Sharer-User-Id") Long userId, @PathVariable Long itemId) { log.info("Запрос на получение вещи с Id:{}", itemId); @@ -33,12 +33,14 @@ public ItemFullDto findById(@RequestHeader("X-Sharer-User-Id") Long userId, } @GetMapping("/search") + @ResponseStatus(HttpStatus.OK) public Collection searchByText(@RequestParam("text") String searchText) { log.info("Запрос на поиск вещи в названии или в описании"); return itemService.searchByText(searchText); } @PostMapping + @ResponseStatus(HttpStatus.CREATED) public ItemDto create(@RequestHeader("X-Sharer-User-Id") Long userId, @RequestBody ItemNewDto newItem) { log.info("Запрос на добавление новой вещи пользователем с Id:{}", userId); @@ -46,6 +48,7 @@ public ItemDto create(@RequestHeader("X-Sharer-User-Id") Long userId, } @PatchMapping("/{itemId}") + @ResponseStatus(HttpStatus.OK) public ItemDto update(@RequestHeader("X-Sharer-User-Id") Long ownerId, @PathVariable Long itemId, @RequestBody ItemNewDto updatedItem) { @@ -54,6 +57,7 @@ public ItemDto update(@RequestHeader("X-Sharer-User-Id") Long ownerId, } @DeleteMapping("/{itemId}") + @ResponseStatus(HttpStatus.NO_CONTENT) public void delete(@RequestHeader("X-Sharer-User-Id") Long ownerId, @PathVariable Long itemId) { log.info("Запрос на удаление вещи с Id:{} пользователем с Id:{}", itemId, ownerId); @@ -61,6 +65,7 @@ public void delete(@RequestHeader("X-Sharer-User-Id") Long ownerId, } @PostMapping("/{itemId}/comment") + @ResponseStatus(HttpStatus.CREATED) public CommentDto addComment(@RequestHeader("X-Sharer-User-Id") Long userId, @PathVariable Long itemId, @RequestBody CommentNewDto comment) { @@ -69,6 +74,7 @@ public CommentDto addComment(@RequestHeader("X-Sharer-User-Id") Long userId, } @GetMapping("/{itemId}/comment") + @ResponseStatus(HttpStatus.OK) public Collection findAllCommentsByItemId(@PathVariable Long itemId) { log.info("Запрос на получение всех комментариев к вещи с id:{}", itemId); return itemService.findAllCommentsByItemId(itemId); diff --git a/src/main/java/ru/practicum/shareit/item/ItemService.java b/server/src/main/java/ru/practicum/shareit/item/ItemService.java similarity index 68% rename from src/main/java/ru/practicum/shareit/item/ItemService.java rename to server/src/main/java/ru/practicum/shareit/item/ItemService.java index eb9d1bd..5dd04d7 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemService.java +++ b/server/src/main/java/ru/practicum/shareit/item/ItemService.java @@ -1,12 +1,10 @@ package ru.practicum.shareit.item; -import ru.practicum.shareit.item.comment.dto.CommentDto; -import ru.practicum.shareit.item.comment.dto.CommentNewDto; -import ru.practicum.shareit.item.dto.ItemDto; -import ru.practicum.shareit.item.dto.ItemFullDto; -import ru.practicum.shareit.item.dto.ItemNewDto; +import ru.practicum.shareit.item.comment.dto.*; +import ru.practicum.shareit.item.dto.*; import java.util.Collection; +import java.util.List; public interface ItemService { Collection findAllByUserId(Long userId); @@ -24,4 +22,6 @@ public interface ItemService { CommentDto addComment(Long userId, Long itemId, CommentNewDto comment); Collection findAllCommentsByItemId(Long itemId); + + Collection findItemsByRequestIds(List requestIds); } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/ItemServiceImpl.java b/server/src/main/java/ru/practicum/shareit/item/ItemServiceImpl.java similarity index 88% rename from src/main/java/ru/practicum/shareit/item/ItemServiceImpl.java rename to server/src/main/java/ru/practicum/shareit/item/ItemServiceImpl.java index a6cdb97..df8550a 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemServiceImpl.java +++ b/server/src/main/java/ru/practicum/shareit/item/ItemServiceImpl.java @@ -9,24 +9,21 @@ import ru.practicum.shareit.booking.model.Booking; import ru.practicum.shareit.exception.*; import ru.practicum.shareit.item.comment.dao.CommentRepository; -import ru.practicum.shareit.item.comment.dto.CommentDto; -import ru.practicum.shareit.item.comment.dto.CommentNewDto; +import ru.practicum.shareit.item.comment.dto.*; import ru.practicum.shareit.item.comment.mapper.CommentMapper; import ru.practicum.shareit.item.comment.model.Comment; import ru.practicum.shareit.item.dao.ItemRepository; -import ru.practicum.shareit.item.dto.ItemDto; -import ru.practicum.shareit.item.dto.ItemFullDto; -import ru.practicum.shareit.item.dto.ItemNewDto; +import ru.practicum.shareit.item.dto.*; import ru.practicum.shareit.item.mapper.ItemMapper; import ru.practicum.shareit.item.model.Item; import ru.practicum.shareit.item.utils.ItemValidate; +import ru.practicum.shareit.request.dao.ItemRequestRepository; +import ru.practicum.shareit.request.model.ItemRequest; import ru.practicum.shareit.user.dao.UserRepository; import ru.practicum.shareit.user.model.User; import java.time.Instant; -import java.util.Collection; -import java.util.Collections; -import java.util.List; +import java.util.*; import java.util.stream.Collectors; @Slf4j @@ -38,6 +35,7 @@ public class ItemServiceImpl implements ItemService { private final UserRepository userRepository; private final BookingRepository bookingRepository; private final CommentRepository commentRepository; + private final ItemRequestRepository itemRequestRepository; // Получение всех вещей пользователя @Override @@ -83,9 +81,16 @@ public Collection searchByText(String searchText) { @Override public ItemDto create(Long ownerId, ItemNewDto itemNewDto) { ItemValidate.validateForCreate(itemNewDto); + User owner = userRepository.findById(ownerId) .orElseThrow(() -> new NotFoundException("Пользователь с ID" + ownerId + " не найден")); - Item item = ItemMapper.mapToItem(itemNewDto, owner); + + ItemRequest request = null; + if (itemNewDto.getRequestId() != null) { + request = itemRequestRepository.findById(itemNewDto.getRequestId()) + .orElseThrow(() -> new NotFoundException("Запрос с ID" + itemNewDto.getRequestId() + " не найден")); + } + Item item = ItemMapper.mapToItem(itemNewDto, owner, request); Item createdItem = itemRepository.save(item); return ItemMapper.mapToDto(createdItem); } @@ -170,6 +175,18 @@ public Collection findAllCommentsByItemId(Long itemId) { .collect(Collectors.toList()); } + // Получение вещей по id запроса + public Collection findItemsByRequestIds(List requestIds) { + if (requestIds == null || requestIds.isEmpty()) { + return Collections.emptyList(); + } + + Collection items = itemRepository.findByRequestIds(requestIds); + return items.stream() + .map(ItemMapper::mapToDto) + .collect(Collectors.toList()); + } + // Вспомогательный метод для поиска последнего бронирования для вещи private BookingDto findLastBooking(Long itemId, Long userId) { Collection lastBookings = bookingRepository.findByOwnerIdLastBookingsForItem(itemId, userId); diff --git a/src/main/java/ru/practicum/shareit/item/comment/dao/CommentRepository.java b/server/src/main/java/ru/practicum/shareit/item/comment/dao/CommentRepository.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/comment/dao/CommentRepository.java rename to server/src/main/java/ru/practicum/shareit/item/comment/dao/CommentRepository.java diff --git a/src/main/java/ru/practicum/shareit/item/comment/dto/CommentDto.java b/server/src/main/java/ru/practicum/shareit/item/comment/dto/CommentDto.java similarity index 85% rename from src/main/java/ru/practicum/shareit/item/comment/dto/CommentDto.java rename to server/src/main/java/ru/practicum/shareit/item/comment/dto/CommentDto.java index d1623c2..12b8fda 100644 --- a/src/main/java/ru/practicum/shareit/item/comment/dto/CommentDto.java +++ b/server/src/main/java/ru/practicum/shareit/item/comment/dto/CommentDto.java @@ -1,9 +1,7 @@ package ru.practicum.shareit.item.comment.dto; import com.fasterxml.jackson.annotation.JsonFormat; -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; +import lombok.*; import java.time.Instant; diff --git a/src/main/java/ru/practicum/shareit/item/comment/dto/CommentNewDto.java b/server/src/main/java/ru/practicum/shareit/item/comment/dto/CommentNewDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/comment/dto/CommentNewDto.java rename to server/src/main/java/ru/practicum/shareit/item/comment/dto/CommentNewDto.java diff --git a/src/main/java/ru/practicum/shareit/item/comment/mapper/CommentMapper.java b/server/src/main/java/ru/practicum/shareit/item/comment/mapper/CommentMapper.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/comment/mapper/CommentMapper.java rename to server/src/main/java/ru/practicum/shareit/item/comment/mapper/CommentMapper.java diff --git a/src/main/java/ru/practicum/shareit/item/comment/model/Comment.java b/server/src/main/java/ru/practicum/shareit/item/comment/model/Comment.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/comment/model/Comment.java rename to server/src/main/java/ru/practicum/shareit/item/comment/model/Comment.java diff --git a/src/main/java/ru/practicum/shareit/item/dao/ItemRepository.java b/server/src/main/java/ru/practicum/shareit/item/dao/ItemRepository.java similarity index 55% rename from src/main/java/ru/practicum/shareit/item/dao/ItemRepository.java rename to server/src/main/java/ru/practicum/shareit/item/dao/ItemRepository.java index e401e05..8b3f3be 100644 --- a/src/main/java/ru/practicum/shareit/item/dao/ItemRepository.java +++ b/server/src/main/java/ru/practicum/shareit/item/dao/ItemRepository.java @@ -2,6 +2,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import ru.practicum.shareit.item.model.Item; import java.util.Collection; @@ -15,4 +16,14 @@ public interface ItemRepository extends JpaRepository { "OR LOWER(i.description) LIKE LOWER(CONCAT('%', :searchText, '%'))) " + "AND i.available = true") Collection searchByText(String searchText); + + // Для поиска по нескольким requestIds + @Query("SELECT i FROM Item i " + + "WHERE i.request.id IN :requestIds") + Collection findByRequestIds(@Param("requestIds") Collection requestIds); + + // Для поиска по одному requestId + @Query("SELECT i FROM Item i " + + "WHERE i.request.id = :requestId") + Collection findByRequestId(@Param("requestId") Long requestId); } \ No newline at end of file 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 89% 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 bd15707..2470856 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java +++ b/server/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java @@ -6,9 +6,11 @@ @Setter @Builder(toBuilder = true) public class ItemDto { + private Long id; private String name; private String description; private Boolean available; private Long ownerId; + private Long requestId; } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemFullDto.java b/server/src/main/java/ru/practicum/shareit/item/dto/ItemFullDto.java similarity index 95% rename from src/main/java/ru/practicum/shareit/item/dto/ItemFullDto.java rename to server/src/main/java/ru/practicum/shareit/item/dto/ItemFullDto.java index 832f94a..8823e72 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/ItemFullDto.java +++ b/server/src/main/java/ru/practicum/shareit/item/dto/ItemFullDto.java @@ -18,6 +18,7 @@ public class ItemFullDto { private String description; private Boolean available; private Long ownerId; + private Long requestId; private BookingDto lastBooking; private BookingDto nextBooking; private List comments; diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemNewDto.java b/server/src/main/java/ru/practicum/shareit/item/dto/ItemNewDto.java similarity index 90% rename from src/main/java/ru/practicum/shareit/item/dto/ItemNewDto.java rename to server/src/main/java/ru/practicum/shareit/item/dto/ItemNewDto.java index 25c5e82..003eb3a 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/ItemNewDto.java +++ b/server/src/main/java/ru/practicum/shareit/item/dto/ItemNewDto.java @@ -12,4 +12,5 @@ public class ItemNewDto { private String name; private String description; private Boolean available; + private Long requestId; } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java b/server/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java similarity index 83% rename from src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java rename to server/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java index 38fd288..147f63f 100644 --- a/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java +++ b/server/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java @@ -2,10 +2,9 @@ import ru.practicum.shareit.booking.dto.BookingDto; import ru.practicum.shareit.item.comment.dto.CommentDto; -import ru.practicum.shareit.item.dto.ItemDto; -import ru.practicum.shareit.item.dto.ItemFullDto; -import ru.practicum.shareit.item.dto.ItemNewDto; +import ru.practicum.shareit.item.dto.*; import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.request.model.ItemRequest; import ru.practicum.shareit.user.model.User; import java.util.List; @@ -20,16 +19,18 @@ public static ItemDto mapToDto(Item item) { .description(item.getDescription()) .available(item.getAvailable()) .ownerId(item.getOwner().getId()) + .requestId(item.getRequest() != null ? item.getRequest().getId() : null) .build(); } // Преобразование ItemNewDto в Item - public static Item mapToItem(ItemNewDto itemNewDto, User owner) { + public static Item mapToItem(ItemNewDto itemNewDto, User owner, ItemRequest request) { return Item.builder() .name(itemNewDto.getName()) .description(itemNewDto.getDescription()) .available(itemNewDto.getAvailable()) .owner(owner) + .request(request) .build(); } @@ -42,6 +43,7 @@ public static ItemFullDto mapToFullDto(Item item, BookingDto lastBooking, .description(item.getDescription()) .available(item.getAvailable()) .ownerId(item.getOwner().getId()) + .requestId(item.getRequest() != null ? item.getRequest().getId() : null) .lastBooking(lastBooking) .nextBooking(nextBooking) .comments(comments) 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 86% 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 index 8f96f2a..80b2c8e 100644 --- a/src/main/java/ru/practicum/shareit/item/model/Item.java +++ b/server/src/main/java/ru/practicum/shareit/item/model/Item.java @@ -2,9 +2,9 @@ import jakarta.persistence.*; import lombok.*; +import ru.practicum.shareit.request.model.ItemRequest; import ru.practicum.shareit.user.model.User; - @Getter @Setter @ToString @@ -32,6 +32,10 @@ public class Item { @JoinColumn(name = "owner_id", nullable = false) private User owner; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "request_id") + private ItemRequest request; + @Override public boolean equals(Object o) { if (this == o) { diff --git a/src/main/java/ru/practicum/shareit/item/utils/ItemValidate.java b/server/src/main/java/ru/practicum/shareit/item/utils/ItemValidate.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/utils/ItemValidate.java rename to server/src/main/java/ru/practicum/shareit/item/utils/ItemValidate.java diff --git a/server/src/main/java/ru/practicum/shareit/request/ItemRequestServerController.java b/server/src/main/java/ru/practicum/shareit/request/ItemRequestServerController.java new file mode 100644 index 0000000..1131826 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/ItemRequestServerController.java @@ -0,0 +1,48 @@ +package ru.practicum.shareit.request; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.request.dto.*; + +import java.util.Collection; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/requests") +public class ItemRequestServerController { + + private final ItemRequestService itemRequestService; + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public ItemRequestDto create(@RequestHeader("X-Sharer-User-Id") Long userId, + @RequestBody ItemRequestNewDto itemRequestNewDto) { + log.info("Запрос на создание нового запроса пользователем с Id:{}", userId); + return itemRequestService.create(userId, itemRequestNewDto); + } + + @GetMapping + @ResponseStatus(HttpStatus.OK) + public Collection findAllByUserId(@RequestHeader("X-Sharer-User-Id") Long userId) { + log.info("Получение всех запросов пользователя с Id:{}", userId); + return itemRequestService.findAllByUserId(userId); + } + + @GetMapping("/all") + @ResponseStatus(HttpStatus.OK) + public Collection findAllOtherUsersRequests(@RequestHeader("X-Sharer-User-Id") Long userId) { + log.info("Получение всех запросов для пользователя с Id:{} от других пользователей", userId); + return itemRequestService.findAllOtherUsersRequests(userId); + } + + @GetMapping("/{requestId}") + @ResponseStatus(HttpStatus.OK) + public ItemRequestDto findRequestById(@RequestHeader("X-Sharer-User-Id") Long userId, + @PathVariable Long requestId) { + log.info("Запрос на получение запроса с Id:{} пользователем с Id:{}", requestId, userId); + return itemRequestService.findRequestById(userId, requestId); + } +} \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/request/ItemRequestService.java b/server/src/main/java/ru/practicum/shareit/request/ItemRequestService.java new file mode 100644 index 0000000..f6a4464 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/ItemRequestService.java @@ -0,0 +1,16 @@ +package ru.practicum.shareit.request; + +import ru.practicum.shareit.request.dto.*; + +import java.util.Collection; + +public interface ItemRequestService { + + ItemRequestDto create(Long userId, ItemRequestNewDto itemRequestNewDto); + + Collection findAllByUserId(Long userId); + + Collection findAllOtherUsersRequests(Long userId); + + ItemRequestDto findRequestById(Long userId, Long requestId); +} \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/request/ItemRequestServiceImpl.java b/server/src/main/java/ru/practicum/shareit/request/ItemRequestServiceImpl.java new file mode 100644 index 0000000..ab948f3 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/ItemRequestServiceImpl.java @@ -0,0 +1,110 @@ +package ru.practicum.shareit.request; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.item.dao.ItemRepository; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.mapper.ItemMapper; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.request.dao.ItemRequestRepository; +import ru.practicum.shareit.request.dto.*; +import ru.practicum.shareit.request.mapper.ItemRequestMapper; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.request.utils.ItemRequestValidate; +import ru.practicum.shareit.user.dao.UserRepository; +import ru.practicum.shareit.user.model.User; + +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ItemRequestServiceImpl implements ItemRequestService { + + private final ItemRequestRepository itemRequestRepository; + private final UserRepository userRepository; + private final ItemRepository itemRepository; + + // Создание нового запроса + @Override + public ItemRequestDto create(Long userId, ItemRequestNewDto itemRequestNewDto) { + ItemRequestValidate.validateForCreate(itemRequestNewDto); + User requester = userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("Пользователь с ID" + userId + " не найден")); + + ItemRequest itemRequest = ItemRequestMapper.mapToItemRequest(itemRequestNewDto, requester); + ItemRequest createdItemRequest = itemRequestRepository.save(itemRequest); + return ItemRequestMapper.mapToDto(createdItemRequest); + } + + // Получение всех запросов текущего пользователя + @Override + public Collection findAllByUserId(Long userId) { + userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("Пользователь с ID " + userId + " не найден")); + + Collection requests = itemRequestRepository.findAllByRequesterIdOrderByCreatedDesc(userId); + return enrichRequestsWithItems(requests); + } + + // Получение всех запросов от других пользователей + @Override + public Collection findAllOtherUsersRequests(Long userId) { + userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("Пользователь с ID " + userId + " не найден")); + + Collection requests = itemRequestRepository.findAllByRequesterIdNotOrderByCreatedDesc(userId); + return enrichRequestsWithItems(requests); + } + + // Получение запроса по id + @Override + public ItemRequestDto findRequestById(Long userId, Long requestId) { + userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("Пользователь с ID " + userId + " не найден")); + + ItemRequest request = itemRequestRepository.findById(requestId) + .orElseThrow(() -> new NotFoundException("Запрос с ID " + requestId + " не найден")); + + Collection items = itemRepository.findByRequestId(requestId); + Collection itemDtos = items.stream() + .map(ItemMapper::mapToDto) + .collect(Collectors.toList()); + + return ItemRequestMapper.mapToDtoWithItems(request, itemDtos); + } + + // Вспомогательный метод для получения запросов для вещи + private Collection enrichRequestsWithItems(Collection requests) { + if (requests.isEmpty()) { + return Collections.emptyList(); + } + + // Собираем все ID запросов + List requestIds = requests.stream() + .map(ItemRequest::getId) + .collect(Collectors.toList()); + + // Загружаем все items для всех запросов одним запросом + Collection allItems = itemRepository.findByRequestIds(requestIds); + + // Группируем items по requestId + Map> itemsByRequestId = allItems.stream() + .collect(Collectors.groupingBy( + item -> item.getRequest().getId(), + Collectors.mapping(ItemMapper::mapToDto, Collectors.toList()) + )); + + // Обогащаем каждый запрос его items + return requests.stream() + .map(request -> { + Collection itemsForRequest = itemsByRequestId.getOrDefault( + request.getId(), Collections.emptyList()); + return ItemRequestMapper.mapToDtoWithItems(request, itemsForRequest); + }) + .collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/request/dao/ItemRequestRepository.java b/server/src/main/java/ru/practicum/shareit/request/dao/ItemRequestRepository.java new file mode 100644 index 0000000..dae4853 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/dao/ItemRequestRepository.java @@ -0,0 +1,13 @@ +package ru.practicum.shareit.request.dao; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.practicum.shareit.request.model.ItemRequest; + +import java.util.Collection; + +public interface ItemRequestRepository extends JpaRepository { + + Collection findAllByRequesterIdOrderByCreatedDesc(Long userId); + + Collection findAllByRequesterIdNotOrderByCreatedDesc(Long userId); +} \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java b/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java new file mode 100644 index 0000000..ca80871 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java @@ -0,0 +1,23 @@ +package ru.practicum.shareit.request.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.*; +import ru.practicum.shareit.item.dto.ItemDto; + +import java.time.LocalDateTime; +import java.util.Collection; + +@Getter +@Setter +@Builder(toBuilder = true) +public class ItemRequestDto { + + private Long id; + + private String description; + + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss", shape = JsonFormat.Shape.STRING, timezone = "UTC") + private LocalDateTime created; + + private Collection items; +} \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestNewDto.java b/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestNewDto.java new file mode 100644 index 0000000..b9100e4 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestNewDto.java @@ -0,0 +1,12 @@ +package ru.practicum.shareit.request.dto; + +import lombok.*; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder(toBuilder = true) +public class ItemRequestNewDto { + private String description; +} \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java b/server/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java new file mode 100644 index 0000000..537f843 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java @@ -0,0 +1,41 @@ +package ru.practicum.shareit.request.mapper; + +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.request.dto.*; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDateTime; +import java.util.*; + +public class ItemRequestMapper { + + // Преобразование ItemRequestNewDto в ItemRequest + public static ItemRequest mapToItemRequest(ItemRequestNewDto itemRequestNewDto, User requester) { + return ItemRequest.builder() + .description(itemRequestNewDto.getDescription()) + .requester(requester) + .created(LocalDateTime.now()) + .build(); + } + + // Преобразование ItemRequest в Dto (без items) + public static ItemRequestDto mapToDto(ItemRequest itemRequest) { + return ItemRequestDto.builder() + .id(itemRequest.getId()) + .description(itemRequest.getDescription()) + .created(itemRequest.getCreated()) + .items(Collections.emptyList()) + .build(); + } + + // Преобразование ItemRequest в Dto с items (принимает items как параметр) + public static ItemRequestDto mapToDtoWithItems(ItemRequest itemRequest, Collection items) { + return ItemRequestDto.builder() + .id(itemRequest.getId()) + .description(itemRequest.getDescription()) + .created(itemRequest.getCreated()) + .items(items != null ? items : Collections.emptyList()) + .build(); + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..0c2b069 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java @@ -0,0 +1,55 @@ +package ru.practicum.shareit.request.model; + +import jakarta.persistence.*; +import lombok.*; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Objects; + +@Getter +@Setter +@ToString +@Entity +@Table(name = "requests") +@NoArgsConstructor +@AllArgsConstructor +@Builder(toBuilder = true) +public class ItemRequest { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "description", nullable = false) + private String description; + + @Column(name = "created", nullable = false) + private LocalDateTime created; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "requester_id", nullable = false) + private User requester; + + @OneToMany(mappedBy = "request", fetch = FetchType.LAZY) + private List items; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ItemRequest that = (ItemRequest) o; + return Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + return Objects.hashCode(id); + } +} \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/request/utils/ItemRequestValidate.java b/server/src/main/java/ru/practicum/shareit/request/utils/ItemRequestValidate.java new file mode 100644 index 0000000..e5559c1 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/utils/ItemRequestValidate.java @@ -0,0 +1,33 @@ +package ru.practicum.shareit.request.utils; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import ru.practicum.shareit.exception.ValidationException; +import ru.practicum.shareit.request.dto.ItemRequestNewDto; + +@Slf4j +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ItemRequestValidate { + + // Валидация при создании запроса + public static void validateForCreate(ItemRequestNewDto itemRequestNewDto) { + validateNotNull(itemRequestNewDto); + validateDescription(itemRequestNewDto.getDescription()); + } + + // Вспомогательные методы для валидации полей ItemRequest + private static void validateNotNull(ItemRequestNewDto itemRequestNewDto) { + if (itemRequestNewDto == null) { + log.error("Попытка создать null, а не запрос"); + throw new ValidationException("Запрос не может быть null"); + } + } + + private static void validateDescription(String description) { + if (description == null || description.isBlank()) { + log.error("Попытка создать запрос без описания"); + throw new ValidationException("Описание запроса не может быть пустым"); + } + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/UserController.java b/server/src/main/java/ru/practicum/shareit/user/UserServerController.java similarity index 83% rename from src/main/java/ru/practicum/shareit/user/UserController.java rename to server/src/main/java/ru/practicum/shareit/user/UserServerController.java index c1e579c..8e3ded4 100644 --- a/src/main/java/ru/practicum/shareit/user/UserController.java +++ b/server/src/main/java/ru/practicum/shareit/user/UserServerController.java @@ -2,9 +2,9 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; -import ru.practicum.shareit.user.dto.UserDto; -import ru.practicum.shareit.user.dto.UserNewDto; +import ru.practicum.shareit.user.dto.*; import java.util.Collection; @@ -12,29 +12,33 @@ @RestController @RequiredArgsConstructor @RequestMapping("/users") -public class UserController { +public class UserServerController { private final UserService userService; @GetMapping + @ResponseStatus(HttpStatus.OK) public Collection findAll() { log.info("Запрос на получение всех пользователей"); return userService.findAll(); } @GetMapping("/{userId}") + @ResponseStatus(HttpStatus.OK) public UserDto findById(@PathVariable Long userId) { log.info("Запрос на получение пользователя с Id:{}", userId); return userService.findById(userId); } @PostMapping + @ResponseStatus(HttpStatus.CREATED) public UserDto create(@RequestBody UserNewDto newUser) { log.info("Запрос на добавление нового пользователя"); return userService.create(newUser); } @PatchMapping("/{userId}") + @ResponseStatus(HttpStatus.OK) public UserDto update(@PathVariable Long userId, @RequestBody UserNewDto updatedUser) { log.info("Запрос на обновление пользователя с id:{}", userId); @@ -42,6 +46,7 @@ public UserDto update(@PathVariable Long userId, } @DeleteMapping("/{userId}") + @ResponseStatus(HttpStatus.NO_CONTENT) public void delete(@PathVariable Long userId) { log.info("Запрос на удаление пользователя с id:{}", userId); userService.delete(userId); diff --git a/src/main/java/ru/practicum/shareit/user/UserService.java b/server/src/main/java/ru/practicum/shareit/user/UserService.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/UserService.java rename to server/src/main/java/ru/practicum/shareit/user/UserService.java diff --git a/src/main/java/ru/practicum/shareit/user/UserServiceImpl.java b/server/src/main/java/ru/practicum/shareit/user/UserServiceImpl.java similarity index 92% rename from src/main/java/ru/practicum/shareit/user/UserServiceImpl.java rename to server/src/main/java/ru/practicum/shareit/user/UserServiceImpl.java index cd85567..d8e12ee 100644 --- a/src/main/java/ru/practicum/shareit/user/UserServiceImpl.java +++ b/server/src/main/java/ru/practicum/shareit/user/UserServiceImpl.java @@ -5,8 +5,7 @@ import org.springframework.stereotype.Service; import ru.practicum.shareit.exception.*; import ru.practicum.shareit.user.dao.UserRepository; -import ru.practicum.shareit.user.dto.UserDto; -import ru.practicum.shareit.user.dto.UserNewDto; +import ru.practicum.shareit.user.dto.*; import ru.practicum.shareit.user.mapper.UserMapper; import ru.practicum.shareit.user.model.User; import ru.practicum.shareit.user.utils.UserValidate; @@ -40,7 +39,7 @@ public UserDto findById(Long userId) { // Добавление пользователя @Override public UserDto create(UserNewDto userNewDto) { - UserValidate.validateForCreate(userNewDto); +// UserValidate.validateForCreate(userNewDto); checkUniqueEmail(userNewDto.getEmail()); User user = UserMapper.mapToUser(userNewDto); User createdUser = userRepository.save(user); @@ -76,7 +75,7 @@ public UserDto update(Long userId, UserNewDto userNewDto) { @Override public void delete(Long userId) { if (!userRepository.existsById(userId)) { - throw new NotFoundException("Пользователь с Id:" + userId + "не найдена"); + throw new NotFoundException("Пользователь с Id:" + userId + "не найден"); } userRepository.deleteById(userId); diff --git a/src/main/java/ru/practicum/shareit/user/dao/UserRepository.java b/server/src/main/java/ru/practicum/shareit/user/dao/UserRepository.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/dao/UserRepository.java rename to server/src/main/java/ru/practicum/shareit/user/dao/UserRepository.java 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 72% 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 index 93c9e28..ecaf93f 100644 --- a/src/main/java/ru/practicum/shareit/user/dto/UserDto.java +++ b/server/src/main/java/ru/practicum/shareit/user/dto/UserDto.java @@ -1,8 +1,6 @@ package ru.practicum.shareit.user.dto; -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; +import lombok.*; @Getter @Setter diff --git a/src/main/java/ru/practicum/shareit/user/dto/UserNewDto.java b/server/src/main/java/ru/practicum/shareit/user/dto/UserNewDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/dto/UserNewDto.java rename to server/src/main/java/ru/practicum/shareit/user/dto/UserNewDto.java diff --git a/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java b/server/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java similarity index 87% rename from src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java rename to server/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java index 3509daa..94da259 100644 --- a/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java +++ b/server/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java @@ -1,7 +1,6 @@ package ru.practicum.shareit.user.mapper; -import ru.practicum.shareit.user.dto.UserDto; -import ru.practicum.shareit.user.dto.UserNewDto; +import ru.practicum.shareit.user.dto.*; import ru.practicum.shareit.user.model.User; public class UserMapper { 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/utils/UserValidate.java b/server/src/main/java/ru/practicum/shareit/user/utils/UserValidate.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/utils/UserValidate.java rename to server/src/main/java/ru/practicum/shareit/user/utils/UserValidate.java diff --git a/src/main/resources/application.properties b/server/src/main/resources/application.properties similarity index 84% rename from src/main/resources/application.properties rename to server/src/main/resources/application.properties index 713d82b..a202397 100644 --- a/src/main/resources/application.properties +++ b/server/src/main/resources/application.properties @@ -1,7 +1,9 @@ -server.port=8080 -spring.jpa.hibernate.ddl-auto=none +server.port=9090 + +spring.jpa.hibernate.ddl-auto=update spring.jpa.properties.hibernate.format_sql=true -spring.sql.init.mode=always +#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 diff --git a/src/main/resources/schema.sql b/server/src/main/resources/schema.sql similarity index 91% rename from src/main/resources/schema.sql rename to server/src/main/resources/schema.sql index 62c9bd7..f511913 100644 --- a/src/main/resources/schema.sql +++ b/server/src/main/resources/schema.sql @@ -6,7 +6,8 @@ CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS requests ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - description VARCHAR(250), + description VARCHAR(500) NOT NULL, + created TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(), requester_id BIGINT NOT NULL, FOREIGN KEY (requester_id) REFERENCES users (id) ON DELETE CASCADE ); @@ -14,7 +15,7 @@ CREATE TABLE IF NOT EXISTS requests ( CREATE TABLE IF NOT EXISTS items ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name VARCHAR(100) NOT NULL, - description VARCHAR(500), + description VARCHAR(1000) NOT NULL, is_available BOOLEAN NOT NULL, owner_id BIGINT NOT NULL, request_id BIGINT, @@ -29,7 +30,6 @@ CREATE TABLE IF NOT EXISTS bookings ( item_id BIGINT NOT NULL, booker_id BIGINT NOT NULL, status VARCHAR(30) NOT NULL, - FOREIGN KEY (item_id) REFERENCES items (id) ON DELETE CASCADE, FOREIGN KEY (booker_id) REFERENCES users (id) ON DELETE CASCADE ); diff --git a/server/src/test/java/ru/practicum/shareit/ShareItServerTest.java b/server/src/test/java/ru/practicum/shareit/ShareItServerTest.java new file mode 100644 index 0000000..d5b9f2e --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/ShareItServerTest.java @@ -0,0 +1,12 @@ +package ru.practicum.shareit; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class ShareItServerTest { + + @Test + public void contextLoads() { + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/booking/BookingServerControllerTest.java b/server/src/test/java/ru/practicum/shareit/booking/BookingServerControllerTest.java new file mode 100644 index 0000000..f122561 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/booking/BookingServerControllerTest.java @@ -0,0 +1,276 @@ +package ru.practicum.shareit.booking; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +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.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import ru.practicum.shareit.booking.dto.*; +import ru.practicum.shareit.booking.model.*; +import ru.practicum.shareit.exception.*; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.user.dto.UserDto; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.List; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@WebMvcTest(BookingServerController.class) +@Import(ErrorHandler.class) +public class BookingServerControllerTest { + + private final String url = "/bookings"; + private static final String X_SHARER_USER_ID = "X-Sharer-User-Id"; + private static final Long VALID_USER_ID = 1L; + private static final Long VALID_ITEM_ID = 1L; + private static final Long VALID_BOOKING_ID = 1L; + private static final Instant START = Instant.now().plus(1, ChronoUnit.DAYS); + private static final Instant END = Instant.now().plus(2, ChronoUnit.DAYS); + + @Autowired + private MockMvc mvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private BookingService bookingService; + + private BookingDto bookingDto; + + private BookingNewDto bookingNewDto; + + @BeforeEach + public void setUp() { + Mockito.reset(bookingService); + + ItemDto itemDto = ItemDto.builder() + .id(VALID_ITEM_ID) + .name("Тестовая вещь") + .description("Описание") + .available(true) + .build(); + + UserDto userDto = UserDto.builder() + .id(VALID_USER_ID) + .name("Тестовый пользователь") + .email("test@yandex.ru") + .build(); + + bookingDto = BookingDto.builder() + .id(VALID_BOOKING_ID) + .start(START) + .end(END) + .item(itemDto) + .booker(userDto) + .status(BookingStatus.WAITING) + .build(); + + bookingNewDto = new BookingNewDto(VALID_ITEM_ID, START, END); + setupDefaultSuccessfulMocks(); + } + + // Тест получения бронирования по id + @Test + public void findById_shouldReturnBooking() throws Exception { + mvc.perform(get("/bookings/{bookingId}", VALID_BOOKING_ID) + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isOk()); + + verify(bookingService).findById(VALID_USER_ID, VALID_BOOKING_ID); + } + + // Тест получения бронирования с несуществующим id + @Test + public void findById_shouldReturnNotFound_whenBookingNotExists() throws Exception { + when(bookingService.findById(VALID_USER_ID, VALID_BOOKING_ID)) + .thenThrow(new NotFoundException("Бронирование не найдено")); + + mvc.perform(get("/bookings/{bookingId}", VALID_BOOKING_ID) + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isNotFound()); + + verify(bookingService).findById(VALID_USER_ID, VALID_BOOKING_ID); + } + + // Тест успешного создания бронирования + @Test + public void create_shouldCreateBooking() throws Exception { + mvc.perform(post(url) + .header(X_SHARER_USER_ID, VALID_USER_ID) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(bookingNewDto))) + .andExpect(status().isCreated()); + + verify(bookingService).create(eq(VALID_USER_ID), any(BookingNewDto.class)); + } + + // Тест создания брони, если пользователь не существует + @Test + public void create_shouldReturnNotFound_whenUserNotExists() throws Exception { + when(bookingService.create(eq(VALID_USER_ID), any(BookingNewDto.class))) + .thenThrow(new NotFoundException("Пользователь не найден")); + + mvc.perform(post(url) + .header(X_SHARER_USER_ID, VALID_USER_ID) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(bookingNewDto))) + .andExpect(status().isNotFound()); + + verify(bookingService).create(eq(VALID_USER_ID), any(BookingNewDto.class)); + } + + // Тест создания брони для несуществующей вещи + @Test + public void create_shouldReturnNotFound_whenItemNotExists() throws Exception { + when(bookingService.create(eq(VALID_USER_ID), any(BookingNewDto.class))) + .thenThrow(new NotFoundException("Вещь не найдена")); + + mvc.perform(post(url) + .header(X_SHARER_USER_ID, VALID_USER_ID) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(bookingNewDto))) + .andExpect(status().isNotFound()); + + verify(bookingService).create(eq(VALID_USER_ID), any(BookingNewDto.class)); + } + + // Тест подтверждения брони + @Test + public void approve_shouldApproveBooking() throws Exception { + mvc.perform(patch("/bookings/{bookingId}", VALID_BOOKING_ID) + .param("approved", "true") + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.status").value("APPROVED")); + + verify(bookingService).approve(VALID_USER_ID, VALID_BOOKING_ID, true); + } + + // Тест отклонения брони + @Test + public void approve_shouldRejectBooking() throws Exception { + mvc.perform(patch("/bookings/{bookingId}", VALID_BOOKING_ID) + .param("approved", "false") + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.status").value("REJECTED")); + + verify(bookingService).approve(VALID_USER_ID, VALID_BOOKING_ID, false); + } + + // Тест брони с невалидным параметром + @Test + public void approve_shouldReturnBadRequest_whenApprovedParamIsInvalid() throws Exception { + mvc.perform(patch("/bookings/{bookingId}", VALID_BOOKING_ID) + .param("approved", "invalid") + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isBadRequest()); + + verify(bookingService, never()).approve(anyLong(), anyLong(), anyBoolean()); + } + + // Тест бронирования с несуществующим id + @Test + public void approve_shouldReturnNotFound_whenBookingNotExists() throws Exception { + when(bookingService.approve(VALID_USER_ID, VALID_BOOKING_ID, true)) + .thenThrow(new NotFoundException("Бронирование не найдено")); + + mvc.perform(patch("/bookings/{bookingId}", VALID_BOOKING_ID) + .param("approved", "true") + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isNotFound()); + + verify(bookingService).approve(VALID_USER_ID, VALID_BOOKING_ID, true); + } + + // Тест получения бронирований по id владельца + @Test + public void findByOwnerId_shouldReturnOwnerBookings() throws Exception { + mvc.perform(get("/bookings/owner") + .param("state", "ALL") + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isOk()); + + verify(bookingService).findByOwnerId(VALID_USER_ID, BookingState.ALL); + } + + // Тест поиска бронирований владельца, когда у пользователя отсутствуют бронирования + @Test + public void findByOwnerId_shouldReturnEmptyList_whenNoBookings() throws Exception { + setupEmptyListMocks(); + + mvc.perform(get("/bookings/owner") + .param("state", "ALL") + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isOk()); + + verify(bookingService).findByOwnerId(VALID_USER_ID, BookingState.ALL); + } + + // Тест получения брони владельца с некорректным состоянием бронирования + @Test + public void findByOwnerId_shouldReturnBadRequest_whenStateIsInvalid() throws Exception { + mvc.perform(get("/bookings/owner") + .param("state", "INVALID_STATE") + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isBadRequest()); + + verify(bookingService, never()).findByOwnerId(anyLong(), any()); + } + + // Тест получения бронирований по id пользователя + @Test + public void findByBookerId_shouldReturnUserBookings() throws Exception { + mvc.perform(get(url) + .param("state", "ALL") + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isOk()); + + verify(bookingService).findByBookerId(VALID_USER_ID, BookingState.ALL); + } + + // Тест получения брони пользователя с некорректным состоянием бронирования + @Test + public void findByBookerId_shouldReturnBadRequest_whenStateIsInvalid() throws Exception { + mvc.perform(get(url) + .param("state", "INVALID_STATE") + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isBadRequest()); + + verify(bookingService, never()).findByBookerId(anyLong(), any()); + } + + // Вспомогательные методы для настройки мок-объектов + private void setupDefaultSuccessfulMocks() { + when(bookingService.findById(VALID_USER_ID, VALID_BOOKING_ID)) + .thenReturn(bookingDto); + when(bookingService.create(eq(VALID_USER_ID), any(BookingNewDto.class))) + .thenReturn(bookingDto); + when(bookingService.approve(VALID_USER_ID, VALID_BOOKING_ID, true)) + .thenReturn(bookingDto.toBuilder().status(BookingStatus.APPROVED).build()); + when(bookingService.approve(VALID_USER_ID, VALID_BOOKING_ID, false)) + .thenReturn(bookingDto.toBuilder().status(BookingStatus.REJECTED).build()); + when(bookingService.findByOwnerId(eq(VALID_USER_ID), any(BookingState.class))) + .thenReturn(List.of(bookingDto)); + when(bookingService.findByBookerId(eq(VALID_USER_ID), any(BookingState.class))) + .thenReturn(List.of(bookingDto)); + } + + private void setupEmptyListMocks() { + when(bookingService.findByOwnerId(eq(VALID_USER_ID), any(BookingState.class))) + .thenReturn(List.of()); + when(bookingService.findByBookerId(eq(VALID_USER_ID), any(BookingState.class))) + .thenReturn(List.of()); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/booking/BookingServiceIntegrationTest.java b/server/src/test/java/ru/practicum/shareit/booking/BookingServiceIntegrationTest.java new file mode 100644 index 0000000..626340d --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/booking/BookingServiceIntegrationTest.java @@ -0,0 +1,281 @@ +package ru.practicum.shareit.booking; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.shareit.booking.dao.BookingRepository; +import ru.practicum.shareit.booking.dto.*; +import ru.practicum.shareit.booking.model.*; +import ru.practicum.shareit.exception.*; +import ru.practicum.shareit.item.dao.ItemRepository; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.dao.UserRepository; +import ru.practicum.shareit.user.model.User; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Collection; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@ActiveProfiles("test") +@Transactional +public class BookingServiceIntegrationTest { + + @Autowired + private BookingService bookingService; + + @Autowired + private BookingRepository bookingRepository; + + @Autowired + private ItemRepository itemRepository; + + @Autowired + private UserRepository userRepository; + + private User testOwner; + private User testBooker; + private User otherUser; + private Item availableItem; + private Item unavailableItem; + private Booking testBooking; + + @BeforeEach + public void beforeEach() { + itemRepository.deleteAll(); + userRepository.deleteAll(); + bookingRepository.deleteAll(); + + // Создание и сохранение тестового владельца + testOwner = userRepository.save(User.builder() + .name("Тестовый владелец") + .email("owner@yandex.ru") + .build()); + + // Создание и сохранение арендатора + testBooker = userRepository.save(User.builder() + .name("Тестовый арендатор") + .email("booker@gmail.com") + .build()); + + // Создание другого пользователя + otherUser = userRepository.save(User.builder() + .name("Новый пользователь") + .email("other@yahoo.com") + .build()); + + // Создание доступной вещи + availableItem = itemRepository.save(Item.builder() + .name("Доступная вещь") + .description("Описание вещи") + .available(true) + .owner(testOwner) + .build()); + + // Создание недоступной вещи + unavailableItem = itemRepository.save(Item.builder() + .name("Недоступная вещь") + .description("Описание") + .available(false) + .owner(testOwner) + .build()); + + // Создание тестового бронирования + testBooking = bookingRepository.save(Booking.builder() + .start(Instant.now().plus(1, ChronoUnit.DAYS)) + .end(Instant.now().plus(2, ChronoUnit.DAYS)) + .item(availableItem) + .booker(testBooker) + .status(BookingStatus.WAITING) + .build()); + } + + // Тест получения брони по ID для владельца вещи + @Test + public void findById_shouldReturnBookingForOwner() { + BookingDto result = bookingService.findById(testOwner.getId(), testBooking.getId()); + + assertNotNull(result); + assertEquals(testBooking.getId(), result.getId()); + assertEquals(BookingStatus.WAITING, result.getStatus()); + } + + // Тест для получения брони по ID для арендатора + @Test + public void findById_shouldReturnBookingForBooker() { + BookingDto result = bookingService.findById(testBooker.getId(), testBooking.getId()); + + assertNotNull(result); + assertEquals(testBooking.getId(), result.getId()); + } + + // Тест граничного случая получения брони по ID для пользователя без прав доступа(ForbiddenException) + @Test + public void findById_shouldThrowForbiddenExceptionForUnauthorizedUser() { + assertThrows(ForbiddenException.class, () -> { + bookingService.findById(otherUser.getId(), testBooking.getId()); + }); + } + + // Тест граничного случая получения несуществующей брони по ID(NotFoundException) + @Test + public void findById_shouldThrowNotFoundExceptionForNonExistentBooking() { + assertThrows(NotFoundException.class, () -> { + bookingService.findById(testOwner.getId(), 999L); + }); + } + + // Тест создания новой брони + @Test + public void create_shouldCreateNewBooking() { + BookingNewDto newBookingDto = new BookingNewDto( + availableItem.getId(), + Instant.now().plus(3, ChronoUnit.DAYS), + Instant.now().plus(4, ChronoUnit.DAYS) + ); + + BookingDto result = bookingService.create(testBooker.getId(), newBookingDto); + + assertNotNull(result.getId()); + assertEquals(BookingStatus.WAITING, result.getStatus()); + assertEquals(availableItem.getId(), result.getItem().getId()); + } + + // Тест граничного случая создания новой брони для своей вещи(ForbiddenException) + @Test + public void create_shouldThrowForbiddenExceptionForOwnItem() { + BookingNewDto newBookingDto = new BookingNewDto( + availableItem.getId(), + Instant.now().plus(3, ChronoUnit.DAYS), + Instant.now().plus(4, ChronoUnit.DAYS) + ); + + assertThrows(ForbiddenException.class, () -> { + bookingService.create(testOwner.getId(), newBookingDto); + }); + } + + // Тест граничного случая создания новой брони для недоступной вещи(ItemNotAvailableException) + @Test + public void create_shouldThrowItemNotAvailableExceptionForUnavailableItem() { + BookingNewDto newBookingDto = new BookingNewDto( + unavailableItem.getId(), + Instant.now().plus(3, ChronoUnit.DAYS), + Instant.now().plus(4, ChronoUnit.DAYS) + ); + + assertThrows(ItemNotAvailableException.class, () -> { + bookingService.create(testBooker.getId(), newBookingDto); + }); + } + + // Тест граничного случая создания новой брони для несуществующей вещи(NotFoundException) + @Test + public void create_shouldThrowNotFoundExceptionForNonExistentItem() { + BookingNewDto newBookingDto = new BookingNewDto( + 999L, + Instant.now().plus(3, ChronoUnit.DAYS), + Instant.now().plus(4, ChronoUnit.DAYS) + ); + + assertThrows(NotFoundException.class, () -> { + bookingService.create(testBooker.getId(), newBookingDto); + }); + } + + // Тест подтверждения брони владельцем + @Test + public void approve_shouldApproveBooking() { + BookingDto result = bookingService.approve(testOwner.getId(), testBooking.getId(), true); + + assertNotNull(result); + assertEquals(BookingStatus.APPROVED, result.getStatus()); + } + + // Тест отклонения брони владельцем + @Test + public void approve_shouldRejectBooking() { + BookingDto result = bookingService.approve(testOwner.getId(), testBooking.getId(), false); + + assertNotNull(result); + assertEquals(BookingStatus.REJECTED, result.getStatus()); + } + + // Тест граничного случая подтверждения брони не владельцем вещи(ForbiddenException) + @Test + public void approve_shouldThrowForbiddenExceptionForNonOwner() { + assertThrows(ForbiddenException.class, () -> { + bookingService.approve(testBooker.getId(), testBooking.getId(), true); + }); + } + + // Тест получения списка бронирований владельца со статусом ALL + @Test + public void findByOwnerId_shouldReturnAllBookingsForOwner() { + Collection result = bookingService.findByOwnerId(testOwner.getId(), BookingState.ALL); + + assertNotNull(result); + assertEquals(1, result.size()); + } + + // Тест получения списка бронирований владельца со статусом WAITING + @Test + public void findByOwnerId_shouldReturnWaitingBookingsForOwner() { + Collection result = bookingService.findByOwnerId(testOwner.getId(), BookingState.WAITING); + + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(BookingStatus.WAITING, result.iterator().next().getStatus()); + } + + // Тест граничного случая получения списка бронирований для пользователя без вещей(возврат пустого списка) + @Test + public void findByOwnerId_shouldReturnEmptyListForUserWithoutItems() { + Collection result = bookingService.findByOwnerId(otherUser.getId(), BookingState.ALL); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + // Тест получения бронирований арендатора со статусом ALL + @Test + public void findByBookerId_shouldReturnAllBookingsForBooker() { + Collection result = bookingService.findByBookerId(testBooker.getId(), BookingState.ALL); + + assertNotNull(result); + assertEquals(1, result.size()); + } + + // Тест получения бронирований арендатора со статусом FUTURE + @Test + public void findByBookerId_shouldReturnFutureBookingsForBooker() { + // создаем бронирование в будущем + Booking futureBooking = bookingRepository.save(Booking.builder() + .start(Instant.now().plus(3, ChronoUnit.DAYS)) // Явно в будущем + .end(Instant.now().plus(4, ChronoUnit.DAYS)) + .item(availableItem) + .booker(testBooker) + .status(BookingStatus.APPROVED) + .build()); + + Collection result = bookingService.findByBookerId(testBooker.getId(), BookingState.FUTURE); + + assertNotNull(result); + assertEquals(1, result.size()); // Теперь должно найти futureBooking + assertEquals(futureBooking.getId(), result.iterator().next().getId()); + } + + // Тест граничного случая получения пустого списка для пользователя без бронирований + @Test + public void findByBookerId_shouldReturnEmptyListForUserWithoutBookings() { + Collection result = bookingService.findByBookerId(otherUser.getId(), BookingState.ALL); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/item/ItemServerControllerTest.java b/server/src/test/java/ru/practicum/shareit/item/ItemServerControllerTest.java new file mode 100644 index 0000000..32bba8c --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/item/ItemServerControllerTest.java @@ -0,0 +1,308 @@ +package ru.practicum.shareit.item; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +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.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import ru.practicum.shareit.exception.*; +import ru.practicum.shareit.item.comment.dto.*; +import ru.practicum.shareit.item.dto.*; +import ru.practicum.shareit.user.dto.UserDto; + +import java.time.Instant; +import java.util.List; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@WebMvcTest(ItemServerController.class) +@Import(ErrorHandler.class) +public class ItemServerControllerTest { + + private final String url = "/items"; + private static final String X_SHARER_USER_ID = "X-Sharer-User-Id"; + private static final Long VALID_USER_ID = 1L; + private static final Long VALID_ITEM_ID = 1L; + private static final Long VALID_COMMENT_ID = 1L; + private static final Long NON_EXISTENT_ID = 999L; + private static final String SEARCH_TEXT = "test"; + private static final String EMPTY_SEARCH_TEXT = ""; + + @Autowired + private MockMvc mvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private ItemService itemService; + + private ItemDto itemDto; + private ItemFullDto itemFullDto; + private ItemNewDto itemNewDto; + private CommentDto commentDto; + private CommentNewDto commentNewDto; + + @BeforeEach + public void setUp() { + Mockito.reset(itemService); + + UserDto userDto = UserDto.builder() + .id(VALID_USER_ID) + .name("Тестовый пользователь") + .email("test@yandex.ru") + .build(); + + itemDto = ItemDto.builder() + .id(VALID_ITEM_ID) + .name("Тестовая вещь") + .description("Описание") + .available(true) + .build(); + + itemFullDto = ItemFullDto.builder() + .id(VALID_ITEM_ID) + .name("Тестовая вещь") + .description("Описание вещи") + .available(true) + .ownerId(VALID_USER_ID) + .build(); + + itemNewDto = new ItemNewDto("Тестовая вещь", "Описание", true, null); + + commentDto = CommentDto.builder() + .id(VALID_COMMENT_ID) + .text("Текст отзыва") + .authorName("Тестовый пользователь") + .created(Instant.now()) + .build(); + + commentNewDto = new CommentNewDto("Текст отзыва"); + setupDefaultSuccessfulMocks(); + } + + // Тест успешного получения всех вещей пользователя + @Test + public void findAllByUserId_shouldReturnUserItems() throws Exception { + mvc.perform(get(url) + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isOk()); + + verify(itemService).findAllByUserId(VALID_USER_ID); + } + + // Тест получения пустого списка вещей пользователя + @Test + public void findAllByUserId_shouldReturnEmptyList_whenNoItems() throws Exception { + when(itemService.findAllByUserId(VALID_USER_ID)) + .thenReturn(List.of()); + + mvc.perform(get(url) + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$").isEmpty()); + + verify(itemService).findAllByUserId(VALID_USER_ID); + } + + // Тест успешного получения вещи по id + @Test + public void findById_shouldReturnItem() throws Exception { + mvc.perform(get("/items/{itemId}", VALID_ITEM_ID) + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isOk()); + + verify(itemService).findById(VALID_USER_ID, VALID_ITEM_ID); + } + + // Тест получения несуществующей вещи + @Test + public void findById_shouldReturnNotFound_whenItemNotExists() throws Exception { + when(itemService.findById(VALID_USER_ID, NON_EXISTENT_ID)) + .thenThrow(new NotFoundException("Вещь не найдена")); + + mvc.perform(get("/items/{itemId}", NON_EXISTENT_ID) + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isNotFound()); + + verify(itemService).findById(VALID_USER_ID, NON_EXISTENT_ID); + } + + // Тест успешного поиска вещей по тексту + @Test + public void searchByText_shouldReturnFoundItems() throws Exception { + mvc.perform(get("/items/search") + .param("text", SEARCH_TEXT)) + .andExpect(status().isOk()); + + verify(itemService).searchByText(SEARCH_TEXT); + } + + // Тест поиска с пустым текстом + @Test + public void searchByText_shouldReturnEmptyList_whenTextIsEmpty() throws Exception { + when(itemService.searchByText(EMPTY_SEARCH_TEXT)) + .thenReturn(List.of()); + + mvc.perform(get("/items/search") + .param("text", EMPTY_SEARCH_TEXT)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$").isEmpty()); + + verify(itemService).searchByText(EMPTY_SEARCH_TEXT); + } + + // Тест успешного создания новой вещи + @Test + public void create_shouldCreateItem() throws Exception { + mvc.perform(post(url) + .header(X_SHARER_USER_ID, VALID_USER_ID) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(itemNewDto))) + .andExpect(status().isCreated()); + + verify(itemService).create(eq(VALID_USER_ID), any(ItemNewDto.class)); + } + + // Тест создания вещи для несуществующего пользователя + @Test + public void create_shouldReturnNotFound_whenUserNotExists() throws Exception { + when(itemService.create(eq(NON_EXISTENT_ID), any(ItemNewDto.class))) + .thenThrow(new NotFoundException("Пользователь не найден")); + + mvc.perform(post(url) + .header(X_SHARER_USER_ID, NON_EXISTENT_ID) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(itemNewDto))) + .andExpect(status().isNotFound()); + + verify(itemService).create(eq(NON_EXISTENT_ID), any(ItemNewDto.class)); + } + + // Тест успешного обновления вещи + @Test + public void update_shouldUpdateItem() throws Exception { + mvc.perform(patch("/items/{itemId}", VALID_ITEM_ID) + .header(X_SHARER_USER_ID, VALID_USER_ID) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(itemNewDto))) + .andExpect(status().isOk()); + + verify(itemService).update(eq(VALID_USER_ID), eq(VALID_ITEM_ID), any(ItemNewDto.class)); + } + + // Тест обновления несуществующей вещи + @Test + public void update_shouldReturnNotFound_whenItemNotExists() throws Exception { + when(itemService.update(eq(VALID_USER_ID), eq(NON_EXISTENT_ID), any(ItemNewDto.class))) + .thenThrow(new NotFoundException("Вещь не найдена")); + + mvc.perform(patch("/items/{itemId}", NON_EXISTENT_ID) + .header(X_SHARER_USER_ID, VALID_USER_ID) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(itemNewDto))) + .andExpect(status().isNotFound()); + + verify(itemService).update(eq(VALID_USER_ID), eq(NON_EXISTENT_ID), any(ItemNewDto.class)); + } + + // Тест успешного удаления вещи + @Test + public void delete_shouldDeleteItem() throws Exception { + mvc.perform(delete("/items/{itemId}", VALID_ITEM_ID) + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isNoContent()); + + verify(itemService).delete(VALID_USER_ID, VALID_ITEM_ID); + } + + // Тест удаления несуществующей вещи + @Test + public void delete_shouldReturnNotFound_whenItemNotExists() throws Exception { + doThrow(new NotFoundException("Вещь не найдена")) + .when(itemService).delete(VALID_USER_ID, NON_EXISTENT_ID); + + mvc.perform(delete("/items/{itemId}", NON_EXISTENT_ID) + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isNotFound()); + + verify(itemService).delete(VALID_USER_ID, NON_EXISTENT_ID); + } + + // Тест успешного добавления комментария + @Test + public void addComment_shouldAddComment() throws Exception { + mvc.perform(post("/items/{itemId}/comment", VALID_ITEM_ID) + .header(X_SHARER_USER_ID, VALID_USER_ID) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(commentNewDto))) + .andExpect(status().isCreated()); + + verify(itemService).addComment(eq(VALID_USER_ID), eq(VALID_ITEM_ID), any(CommentNewDto.class)); + } + + // Тест добавления комментария к несуществующей вещи + @Test + public void addComment_shouldReturnNotFound_whenItemNotExists() throws Exception { + when(itemService.addComment(eq(VALID_USER_ID), eq(NON_EXISTENT_ID), any(CommentNewDto.class))) + .thenThrow(new NotFoundException("Вещь не найдена")); + + mvc.perform(post("/items/{itemId}/comment", NON_EXISTENT_ID) + .header(X_SHARER_USER_ID, VALID_USER_ID) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(commentNewDto))) + .andExpect(status().isNotFound()); + + verify(itemService).addComment(eq(VALID_USER_ID), eq(NON_EXISTENT_ID), any(CommentNewDto.class)); + } + + // Тест успешного получения всех комментариев вещи + @Test + public void findAllCommentsByItemId_shouldReturnComments() throws Exception { + mvc.perform(get("/items/{itemId}/comment", VALID_ITEM_ID)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()); + + verify(itemService).findAllCommentsByItemId(VALID_ITEM_ID); + } + + // Тест получения комментариев несуществующей вещи + @Test + public void findAllCommentsByItemId_shouldReturnNotFound_whenItemNotExists() throws Exception { + when(itemService.findAllCommentsByItemId(NON_EXISTENT_ID)) + .thenThrow(new NotFoundException("Вещь не найдена")); + + mvc.perform(get("/items/{itemId}/comment", NON_EXISTENT_ID)) + .andExpect(status().isNotFound()); + + verify(itemService).findAllCommentsByItemId(NON_EXISTENT_ID); + } + + // Вспомогательный метод для настройки мок-объектов + private void setupDefaultSuccessfulMocks() { + when(itemService.findAllByUserId(VALID_USER_ID)) + .thenReturn(List.of(itemFullDto)); + when(itemService.findById(VALID_USER_ID, VALID_ITEM_ID)) + .thenReturn(itemFullDto); + when(itemService.searchByText(SEARCH_TEXT)) + .thenReturn(List.of(itemDto)); + when(itemService.create(eq(VALID_USER_ID), any(ItemNewDto.class))) + .thenReturn(itemDto); + when(itemService.update(eq(VALID_USER_ID), eq(VALID_ITEM_ID), any(ItemNewDto.class))) + .thenReturn(itemDto); + when(itemService.addComment(eq(VALID_USER_ID), eq(VALID_ITEM_ID), any(CommentNewDto.class))) + .thenReturn(commentDto); + when(itemService.findAllCommentsByItemId(VALID_ITEM_ID)) + .thenReturn(List.of(commentDto)); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/item/ItemServiceIntegrationTest.java b/server/src/test/java/ru/practicum/shareit/item/ItemServiceIntegrationTest.java new file mode 100644 index 0000000..bb43584 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/item/ItemServiceIntegrationTest.java @@ -0,0 +1,355 @@ +package ru.practicum.shareit.item; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.shareit.booking.dao.BookingRepository; +import ru.practicum.shareit.booking.model.*; +import ru.practicum.shareit.exception.*; +import ru.practicum.shareit.item.comment.dto.*; +import ru.practicum.shareit.item.dao.ItemRepository; +import ru.practicum.shareit.item.dto.*; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.request.dao.ItemRequestRepository; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.user.dao.UserRepository; +import ru.practicum.shareit.user.model.User; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Collection; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@ActiveProfiles("test") +@Transactional +public class ItemServiceIntegrationTest { + + @Autowired + private ItemService itemService; + + @Autowired + private ItemRepository itemRepository; + + @Autowired + private UserRepository userRepository; + + @Autowired + private BookingRepository bookingRepository; + + @Autowired + private ItemRequestRepository itemRequestRepository; + + private User testUser; + private User testBooker; + private User otherUser; + private Item testItem; + private Item unavailableItem; + + @BeforeEach + public void beforeEach() { + // Очистка базы перед каждым тестом + itemRepository.deleteAll(); + userRepository.deleteAll(); + bookingRepository.deleteAll(); + itemRequestRepository.deleteAll(); + + // Создание и сохранение тестового владельца + testUser = userRepository.save(User.builder() + .name("Тестовый владелец") + .email("owner@yandex.ru") + .build()); + + // Создание и сохранение арендатора + testBooker = userRepository.save(User.builder() + .name("Тестовый арендатор") + .email("booker@gmail.com") + .build()); + + // Создание другого пользователя + otherUser = userRepository.save(User.builder() + .name("Новый пользователь") + .email("other@yahoo.com") + .build()); + + // Создание и сохранение тестовой вещи + testItem = itemRepository.save(Item.builder() + .name("Тестовая вещь") + .description("Описание") + .available(true) + .owner(testUser) + .build()); + + // Создание недоступной вещи + unavailableItem = itemRepository.save(Item.builder() + .name("Недоступная вещь") + .description("Описание вещи") + .available(false) + .owner(testUser) + .build()); + } + + // Тест получения всех вещей пользователя с полной информацией + @Test + public void findAllByUserId_shouldReturnUserItemsWithFullInfo() { + Collection result = itemService.findAllByUserId(testUser.getId()); + + assertNotNull(result); + assertEquals(2, result.size()); // 2 вещи: testItem и unavailableItem + } + + // Тест граничного случая получения всех вещей пользователя(возврат пустого списка для пользователя без вещей) + @Test + public void findAllByUserId_shouldReturnEmptyListForUserWithoutItems() { + Collection result = itemService.findAllByUserId(otherUser.getId()); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + // Тест получения вещи по id + @Test + public void findById_shouldReturnItemWithFullInfo() { + ItemFullDto result = itemService.findById(testUser.getId(), testItem.getId()); + + assertNotNull(result); + assertEquals("Тестовая вещь", result.getName()); + assertEquals("Описание", result.getDescription()); + assertTrue(result.getAvailable()); + } + + // Тест граничного случая получения несуществующей вещи по id(NotFoundException) + @Test + public void findById_shouldThrowNotFoundExceptionForNonExistentItem() { + assertThrows(NotFoundException.class, () -> { + itemService.findById(testUser.getId(), 999L); + }); + } + + // Тест поиска вещи по тексту в названии или описании + @Test + public void searchByText_shouldFindItemsByText() { + Collection result = itemService.searchByText("тест"); + + assertEquals(1, result.size()); + assertEquals("Тестовая вещь", result.iterator().next().getName()); + } + + // Тест граничного случая поиска вещи по тексту с пустым запросом + @Test + public void searchByText_shouldReturnEmptyListForEmptyText() { + Collection result = itemService.searchByText(""); + + assertTrue(result.isEmpty()); + } + + // Тест граничного случая поиска вещи по тексту (поиск только доступных вещей) + @Test + public void searchByText_shouldReturnOnlyAvailableItems() { + Collection result = itemService.searchByText("недоступная"); + + assertTrue(result.isEmpty()); // unavailableItem не доступен для бронирования + } + + // Тест добавления новой вещи и сохранения в БД + @Test + public void create_shouldSaveNewItemToDatabase() { + ItemNewDto newItemDto = new ItemNewDto("Новая вещь", "Новое описание", + true, null); + + ItemDto createdItem = itemService.create(testUser.getId(), newItemDto); + + assertNotNull(createdItem.getId()); + assertEquals("Новая вещь", createdItem.getName()); + assertEquals("Новое описание", createdItem.getDescription()); + assertTrue(createdItem.getAvailable()); + + // Проверяем, что вещь сохранилась в БД + assertEquals(3, itemRepository.count()); + } + + // Тест граничного случая добавления новой вещи(создание вещи с запросом) + @Test + public void create_shouldSaveItemWithRequest() { + ItemRequest request = itemRequestRepository.save(ItemRequest.builder() + .description("Нужна тестовая вещь") + .requester(testBooker) + .created(LocalDateTime.now()) + .build()); + + ItemNewDto newItemDto = new ItemNewDto("Вещь для запроса", "Описания вещи для запроса", + true, request.getId()); + + ItemDto createdItem = itemService.create(testUser.getId(), newItemDto); + + assertNotNull(createdItem.getId()); + assertEquals("Вещь для запроса", createdItem.getName()); + } + + // Тест граничного случая добавления новой вещи для несуществующего пользователя(NotFoundException) + @Test + public void create_shouldThrowNotFoundExceptionForNonExistentUser() { + ItemNewDto newItemDto = new ItemNewDto("Тест вещь", "Описание тест вещи", + true, null); + + assertThrows(NotFoundException.class, () -> { + itemService.create(999L, newItemDto); + }); + } + + // Тест обновления данных вещи в БД + @Test + public void update_shouldUpdateItemInDatabase() { + ItemNewDto updateDto = new ItemNewDto("Вещь для обновления", "Описание для обновления", + false, null); + + ItemDto updatedItem = itemService.update(testUser.getId(), testItem.getId(), updateDto); + + assertEquals("Вещь для обновления", updatedItem.getName()); + assertEquals("Описание для обновления", updatedItem.getDescription()); + assertFalse(updatedItem.getAvailable()); + + // Проверяем, что изменения сохранились в БД + Item dbItem = itemRepository.findById(testItem.getId()).orElseThrow(); + assertEquals("Вещь для обновления", dbItem.getName()); + assertEquals("Описание для обновления", dbItem.getDescription()); + assertFalse(dbItem.getAvailable()); + } + + // Тест граничного случая обновления вещи(частичное обновление) + @Test + public void update_shouldPartiallyUpdateItem() { + ItemNewDto updateDto = new ItemNewDto("Название", null, null, null); + + ItemDto updatedItem = itemService.update(testUser.getId(), testItem.getId(), updateDto); + + assertEquals("Название", updatedItem.getName()); + assertEquals("Описание", updatedItem.getDescription()); // Осталось прежним + assertTrue(updatedItem.getAvailable()); // Осталось прежним + } + + // Тест граничного случая при попытке обновления чужой вещи(ForbiddenException) + @Test + public void update_shouldThrowForbiddenExceptionForOtherUser() { + ItemNewDto updateDto = new ItemNewDto("Измененная вещь", "Измененное описание", + false, null); + + assertThrows(ForbiddenException.class, () -> { + itemService.update(otherUser.getId(), testItem.getId(), updateDto); + }); + } + + // Тест удаления вещи из БД + @Test + public void delete_shouldRemoveItemFromDatabase() { + itemService.delete(testUser.getId(), testItem.getId()); + + assertFalse(itemRepository.existsById(testItem.getId())); + assertEquals(1, itemRepository.count()); // Осталась только unavailableItem + } + + // Тест граничного случая при попытке удалить чужую вещь(ForbiddenException) + @Test + public void delete_shouldThrowForbiddenExceptionForOtherUser() { + assertThrows(ForbiddenException.class, () -> { + itemService.delete(otherUser.getId(), testItem.getId()); + }); + } + + // Тест добавления отзыва к вещи после бронирования + @Test + public void addComment_shouldAddCommentAfterBooking() { + // создаем бронирование + Booking booking = bookingRepository.save(Booking.builder() + .start(Instant.now().minus(2, ChronoUnit.DAYS)) + .end(Instant.now().minus(1, ChronoUnit.DAYS)) + .item(testItem) + .booker(testBooker) + .status(BookingStatus.APPROVED) + .build()); + + CommentNewDto commentDto = new CommentNewDto("Все супер!"); + + CommentDto result = itemService.addComment(testBooker.getId(), testItem.getId(), commentDto); + + assertNotNull(result.getId()); + assertEquals("Все супер!", result.getText()); + assertEquals("Тестовый арендатор", result.getAuthorName()); + + // Проверяем, что комментарий сохранился + assertEquals(1, itemService.findAllCommentsByItemId(testItem.getId()).size()); + } + + // Тест граничного случая при попытке добавить отзыв при отсутствии бронирования + @Test + public void addComment_shouldThrowValidationExceptionWithoutBooking() { + CommentNewDto commentDto = new CommentNewDto("Рекомендую!"); + + assertThrows(ValidationException.class, () -> { + itemService.addComment(testBooker.getId(), testItem.getId(), commentDto); + }); + } + + // Тест получения всех отзывов о вещи + @Test + public void findAllCommentsByItemId_shouldReturnItemComments() { + Collection result = itemService.findAllCommentsByItemId(testItem.getId()); + + assertNotNull(result); + assertTrue(result.isEmpty()); // Пока нет комментариев + } + + // Тест граничного случая получения всех отзывов у несуществующей вещи(NotFoundException) + @Test + public void findAllCommentsByItemId_shouldThrowNotFoundExceptionForNonExistentItem() { + // when & then + assertThrows(NotFoundException.class, () -> { + itemService.findAllCommentsByItemId(999L); + }); + } + + // Тест получения вещи по Id запроса + @Test + public void findItemsByRequestIds_shouldReturnItemsForRequests() { + ItemRequest request = itemRequestRepository.save(ItemRequest.builder() + .description("Необходима вещь") + .requester(testBooker) + .created(LocalDateTime.now()) + .build()); + + Item requestedItem = itemRepository.save(Item.builder() + .name("Вещь для запроса") + .description("Для теста") + .available(true) + .owner(testUser) + .request(request) + .build()); + + Collection result = itemService.findItemsByRequestIds(List.of(request.getId())); + + assertEquals(1, result.size()); + assertEquals("Вещь для запроса", result.iterator().next().getName()); + } + + // Тест граничного случая получения вещи по Id запроса(возврат пустого списка для несуществующего запроса) + @Test + public void findItemsByRequestIds_shouldReturnEmptyListForNonExistentRequests() { + Collection result = itemService.findItemsByRequestIds(List.of(999L)); + + assertTrue(result.isEmpty()); + } + + // Тест граничного получения вещи по Id запроса(возврат пустого списка для пустого списка запросов) + @Test + public void findItemsByRequestIds_shouldReturnEmptyListForEmptyRequestList() { + Collection result = itemService.findItemsByRequestIds(List.of()); + + assertTrue(result.isEmpty()); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/request/ItemRequestServerControllerTest.java b/server/src/test/java/ru/practicum/shareit/request/ItemRequestServerControllerTest.java new file mode 100644 index 0000000..e66be3c --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/request/ItemRequestServerControllerTest.java @@ -0,0 +1,191 @@ +package ru.practicum.shareit.request; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +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.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import ru.practicum.shareit.exception.*; +import ru.practicum.shareit.request.dto.*; +import ru.practicum.shareit.user.dto.UserDto; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@WebMvcTest(ItemRequestServerController.class) +@Import(ErrorHandler.class) +public class ItemRequestServerControllerTest { + + private final String url = "/requests"; + private static final String X_SHARER_USER_ID = "X-Sharer-User-Id"; + private static final Long VALID_USER_ID = 1L; + private static final Long VALID_REQUEST_ID = 1L; + private static final Long NON_EXISTENT_ID = 999L; + private static final String REQUEST_DESCRIPTION = "Нужна тестовая вещь"; + + @Autowired + private MockMvc mvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private ItemRequestService itemRequestService; + + private ItemRequestDto itemRequestDto; + private ItemRequestNewDto itemRequestNewDto; + + @BeforeEach + public void setUp() { + Mockito.reset(itemRequestService); + + UserDto userDto = UserDto.builder() + .id(VALID_USER_ID) + .name("Тестовый пользователь") + .email("test@yandex.ru") + .build(); + + itemRequestDto = ItemRequestDto.builder() + .id(VALID_REQUEST_ID) + .description(REQUEST_DESCRIPTION) + .created(LocalDateTime.now()) + .items(List.of()) + .build(); + + itemRequestNewDto = new ItemRequestNewDto(REQUEST_DESCRIPTION); + + setupDefaultSuccessfulMocks(); + } + + // Тест успешного создания нового запроса на вещь + @Test + public void create_shouldCreateItemRequest() throws Exception { + mvc.perform(post(url) + .header(X_SHARER_USER_ID, VALID_USER_ID) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(itemRequestNewDto))) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.description").value(REQUEST_DESCRIPTION)); + + verify(itemRequestService).create(eq(VALID_USER_ID), any(ItemRequestNewDto.class)); + } + + // Тест создания запроса для несуществующего пользователя + @Test + public void create_shouldReturnNotFound_whenUserNotExists() throws Exception { + when(itemRequestService.create(eq(NON_EXISTENT_ID), any(ItemRequestNewDto.class))) + .thenThrow(new NotFoundException("Пользователь не найден")); + + mvc.perform(post(url) + .header(X_SHARER_USER_ID, NON_EXISTENT_ID) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(itemRequestNewDto))) + .andExpect(status().isNotFound()); + + verify(itemRequestService).create(eq(NON_EXISTENT_ID), any(ItemRequestNewDto.class)); + } + + // Тест успешного получения всех запросов пользователя + @Test + public void findAllByUserId_shouldReturnUserRequests() throws Exception { + mvc.perform(get(url) + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()); + + verify(itemRequestService).findAllByUserId(VALID_USER_ID); + } + + // Тест получения пустого списка запросов пользователя + @Test + public void findAllByUserId_shouldReturnEmptyList_whenNoRequests() throws Exception { + when(itemRequestService.findAllByUserId(VALID_USER_ID)) + .thenReturn(List.of()); + + mvc.perform(get(url) + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$").isEmpty()); + + verify(itemRequestService).findAllByUserId(VALID_USER_ID); + } + + // Тест успешного получения всех запросов других пользователей + @Test + public void findAllOtherUsersRequests_shouldReturnOtherUsersRequests() throws Exception { + mvc.perform(get("/requests/all") + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isOk()); + + verify(itemRequestService).findAllOtherUsersRequests(VALID_USER_ID); + } + + // Тест получения пустого списка запросов других пользователей + @Test + public void findAllOtherUsersRequests_shouldReturnEmptyList_whenNoOtherRequests() throws Exception { + when(itemRequestService.findAllOtherUsersRequests(VALID_USER_ID)) + .thenReturn(List.of()); + + mvc.perform(get("/requests/all") + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isOk()); + + verify(itemRequestService).findAllOtherUsersRequests(VALID_USER_ID); + } + + // Тест успешного получения запроса по ID + @Test + public void findRequestById_shouldReturnRequest() throws Exception { + mvc.perform(get("/requests/{requestId}", VALID_REQUEST_ID) + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isOk()); + + verify(itemRequestService).findRequestById(VALID_USER_ID, VALID_REQUEST_ID); + } + + // Тест получения несуществующего запроса + @Test + public void findRequestById_shouldReturnNotFound_whenRequestNotExists() throws Exception { + when(itemRequestService.findRequestById(VALID_USER_ID, NON_EXISTENT_ID)) + .thenThrow(new NotFoundException("Запрос не найден")); + + mvc.perform(get("/requests/{requestId}", NON_EXISTENT_ID) + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isNotFound()); + + verify(itemRequestService).findRequestById(VALID_USER_ID, NON_EXISTENT_ID); + } + + // Тест получения запроса с неверными параметрами пути + @Test + public void findRequestById_shouldReturnBadRequest_whenInvalidPath() throws Exception { + mvc.perform(get("/requests/invalid") + .header(X_SHARER_USER_ID, VALID_USER_ID)) + .andExpect(status().isBadRequest()); + + verify(itemRequestService, never()).findRequestById(anyLong(), anyLong()); + } + + // Вспомогательный метод для настройки мок-объектов + private void setupDefaultSuccessfulMocks() { + when(itemRequestService.create(eq(VALID_USER_ID), any(ItemRequestNewDto.class))) + .thenReturn(itemRequestDto); + when(itemRequestService.findAllByUserId(VALID_USER_ID)) + .thenReturn(List.of(itemRequestDto)); + when(itemRequestService.findAllOtherUsersRequests(VALID_USER_ID)) + .thenReturn(List.of(itemRequestDto)); + when(itemRequestService.findRequestById(VALID_USER_ID, VALID_REQUEST_ID)) + .thenReturn(itemRequestDto); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/request/ItemRequestServiceIntegrationTest.java b/server/src/test/java/ru/practicum/shareit/request/ItemRequestServiceIntegrationTest.java new file mode 100644 index 0000000..42690f8 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/request/ItemRequestServiceIntegrationTest.java @@ -0,0 +1,151 @@ +package ru.practicum.shareit.request; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.item.dao.ItemRepository; +import ru.practicum.shareit.request.dao.ItemRequestRepository; +import ru.practicum.shareit.request.dto.*; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.user.dao.UserRepository; +import ru.practicum.shareit.user.model.User; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.util.Collection; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@ActiveProfiles("test") +@Transactional +public class ItemRequestServiceIntegrationTest { + + @Autowired + private ItemRequestService itemRequestService; + + @Autowired + private ItemRequestRepository itemRequestRepository; + + @Autowired + private ItemRepository itemRepository; + + @Autowired + private UserRepository userRepository; + + private User testUser; + private User otherUser; + private ItemRequest testRequest; + + @BeforeEach + public void beforeEach() { + // Очистка репозиториев + itemRepository.deleteAll(); + itemRequestRepository.deleteAll(); + userRepository.deleteAll(); + + // Создание и сохранение тестового пользователя + testUser = userRepository.save(User.builder() + .name("Тестовый пользователь") + .email("user@yandex.ru") + .build()); + + // Создание другого пользователя + otherUser = userRepository.save(User.builder() + .name("Новый пользователь") + .email("other@yahoo.com") + .build()); + + // Создание тестового запроса + testRequest = itemRequestRepository.save(ItemRequest.builder() + .description("Нужна тестовая вещь") + .requester(testUser) + .created(LocalDateTime.now()) + .build()); + } + + // Тест создания запроса на вещь + @Test + public void create_shouldCreateNewItemRequest() { + ItemRequestNewDto newRequestDto = new ItemRequestNewDto("Нуждаюсь в вещи"); + + ItemRequestDto result = itemRequestService.create(testUser.getId(), newRequestDto); + + assertNotNull(result.getId()); + assertEquals("Нуждаюсь в вещи", result.getDescription()); + assertNotNull(result.getCreated()); + assertTrue(result.getItems().isEmpty()); // Пока нет предложенных вещей + } + + // Тест граничного случая создания запроса на вещь для несуществующего пользователя(NotFoundException) + @Test + public void create_shouldThrowNotFoundExceptionForNonExistentUser() { + ItemRequestNewDto newRequestDto = new ItemRequestNewDto("Нужна тестовая вещь"); + + assertThrows(NotFoundException.class, () -> { + itemRequestService.create(999L, newRequestDto); + }); + } + + // Тест получения всех запросов текущего пользователя + @Test + public void findAllByUserId_shouldReturnUserRequests() { + Collection result = itemRequestService.findAllByUserId(testUser.getId()); + + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals("Нужна тестовая вещь", result.iterator().next().getDescription()); + } + + // Тест граничного случая получения всех запросов (возврат пустого списка для пользователя без запросов) + @Test + public void findAllByUserId_shouldReturnEmptyListForUserWithoutRequests() { + Collection result = itemRequestService.findAllByUserId(otherUser.getId()); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + // Тест получения запросов от других пользователей + @Test + public void findAllOtherUsersRequests_shouldReturnOtherUsersRequests() { + // otherUser запрашивает запросы других пользователей (testUser) + Collection result = itemRequestService.findAllOtherUsersRequests(otherUser.getId()); + + assertNotNull(result); + assertEquals(1, result.size()); // Запрос testUser должен быть виден + assertEquals("Нужна тестовая вещь", result.iterator().next().getDescription()); + } + + // Тест граничного случая получения запросов (пользователь не видит свои собственные запросы) + @Test + public void findAllOtherUsersRequests_shouldNotReturnOwnRequests() { + Collection result = itemRequestService.findAllOtherUsersRequests(testUser.getId()); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + // Тест получения запроса по id + @Test + public void findRequestById_shouldReturnRequestById() { + ItemRequestDto result = itemRequestService.findRequestById(testUser.getId(), testRequest.getId()); + + assertNotNull(result); + assertEquals(testRequest.getId(), result.getId()); + assertEquals("Нужна тестовая вещь", result.getDescription()); + assertTrue(result.getItems().isEmpty()); // Пока нет предложенных вещей + } + + // Тест граничного случая получения несуществующего запроса по id(NotFoundException) + @Test + public void findRequestById_shouldThrowNotFoundExceptionForNonExistentRequest() { + assertThrows(NotFoundException.class, () -> { + itemRequestService.findRequestById(testUser.getId(), 999L); + }); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/user/UserServerControllerTest.java b/server/src/test/java/ru/practicum/shareit/user/UserServerControllerTest.java new file mode 100644 index 0000000..88047b2 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/user/UserServerControllerTest.java @@ -0,0 +1,187 @@ +package ru.practicum.shareit.user; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +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.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import ru.practicum.shareit.exception.*; +import ru.practicum.shareit.user.dto.*; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@WebMvcTest(UserServerController.class) +@Import(ErrorHandler.class) +public class UserServerControllerTest { + + private final String url = "/users"; + private static final Long VALID_USER_ID = 1L; + private static final Long NON_EXISTENT_ID = 999L; + private static final String USER_NAME = "Тестовый пользователь"; + private static final String USER_EMAIL = "test@yandex.ru"; + private static final String UPDATED_NAME = "Обновленное имя"; + private static final String UPDATED_EMAIL = "updated@yandex.ru"; + + @Autowired + private MockMvc mvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private UserService userService; + + private UserDto userDto; + private UserNewDto userNewDto; + private UserNewDto userUpdateDto; + + @BeforeEach + public void setUp() { + Mockito.reset(userService); + + userDto = UserDto.builder() + .id(VALID_USER_ID) + .name(USER_NAME) + .email(USER_EMAIL) + .build(); + + userNewDto = new UserNewDto(USER_NAME, USER_EMAIL); + userUpdateDto = new UserNewDto(UPDATED_NAME, UPDATED_EMAIL); + + setupDefaultSuccessfulMocks(); + } + + //Тест успешного получения всех пользователей + @Test + public void findAll_shouldReturnAllUsers() throws Exception { + mvc.perform(get("/users")) + .andExpect(status().isOk()); + + verify(userService).findAll(); + } + + // Тест получения пустого списка пользователей + @Test + public void findAll_shouldReturnEmptyList_whenNoUsers() throws Exception { + when(userService.findAll()) + .thenReturn(List.of()); + + mvc.perform(get(url)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$").isEmpty()); + + verify(userService).findAll(); + } + + //Тест успешного получения пользователя по ID + @Test + public void findById_shouldReturnUser() throws Exception { + mvc.perform(get("/users/{userId}", VALID_USER_ID)) + .andExpect(status().isOk()); + + verify(userService).findById(VALID_USER_ID); + } + + //Тест получения несуществующего пользователя + @Test + public void findById_shouldReturnNotFound_whenUserNotExists() throws Exception { + when(userService.findById(NON_EXISTENT_ID)) + .thenThrow(new NotFoundException("Пользователь не найден")); + + mvc.perform(get("/users/{userId}", NON_EXISTENT_ID)) + .andExpect(status().isNotFound()); + + verify(userService).findById(NON_EXISTENT_ID); + } + + //Тест успешного создания нового пользователя + @Test + public void create_shouldCreateUser() throws Exception { + mvc.perform(post(url) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(userNewDto))) + .andExpect(status().isCreated()); + + verify(userService).create(any(UserNewDto.class)); + } + + // Тест успешного обновления пользователя + @Test + public void update_shouldUpdateUser() throws Exception { + UserDto updatedUserDto = UserDto.builder() + .id(VALID_USER_ID) + .name(UPDATED_NAME) + .email(UPDATED_EMAIL) + .build(); + + when(userService.update(eq(VALID_USER_ID), any(UserNewDto.class))) + .thenReturn(updatedUserDto); + + mvc.perform(patch("/users/{userId}", VALID_USER_ID) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(userUpdateDto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name").value(UPDATED_NAME)) + .andExpect(jsonPath("$.email").value(UPDATED_EMAIL)); + + verify(userService).update(eq(VALID_USER_ID), any(UserNewDto.class)); + } + + //Тест обновления несуществующего пользователя + @Test + public void update_shouldReturnNotFound_whenUserNotExists() throws Exception { + when(userService.update(eq(NON_EXISTENT_ID), any(UserNewDto.class))) + .thenThrow(new NotFoundException("Пользователь не найден")); + + mvc.perform(patch("/users/{userId}", NON_EXISTENT_ID) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(userUpdateDto))) + .andExpect(status().isNotFound()); + + verify(userService).update(eq(NON_EXISTENT_ID), any(UserNewDto.class)); + } + + // Тест успешного удаления пользователя + @Test + public void delete_shouldDeleteUser() throws Exception { + mvc.perform(delete("/users/{userId}", VALID_USER_ID)) + .andExpect(status().isNoContent()); + + verify(userService).delete(VALID_USER_ID); + } + + // Тест удаления несуществующего пользователя + @Test + public void delete_shouldReturnNotFound_whenUserNotExists() throws Exception { + doThrow(new NotFoundException("Пользователь не найден")) + .when(userService).delete(NON_EXISTENT_ID); + + mvc.perform(delete("/users/{userId}", NON_EXISTENT_ID)) + .andExpect(status().isNotFound()); + + verify(userService).delete(NON_EXISTENT_ID); + } + + // Вспомогательный метод для настройки мок-объектов + private void setupDefaultSuccessfulMocks() { + when(userService.findAll()) + .thenReturn(List.of(userDto)); + when(userService.findById(VALID_USER_ID)) + .thenReturn(userDto); + when(userService.create(any(UserNewDto.class))) + .thenReturn(userDto); + when(userService.update(eq(VALID_USER_ID), any(UserNewDto.class))) + .thenReturn(userDto); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/user/UserServerIntegrationTest.java b/server/src/test/java/ru/practicum/shareit/user/UserServerIntegrationTest.java new file mode 100644 index 0000000..ee90b67 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/user/UserServerIntegrationTest.java @@ -0,0 +1,203 @@ +package ru.practicum.shareit.user; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.shareit.exception.*; +import ru.practicum.shareit.user.dao.UserRepository; +import ru.practicum.shareit.user.dto.*; +import ru.practicum.shareit.user.model.User; + +import java.util.Collection; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@ActiveProfiles("test") +@Transactional +public class UserServerIntegrationTest { + + @Autowired + private UserService userService; + + @Autowired + private UserRepository userRepository; + + private User testUser; + + @BeforeEach + public void beforeEach() { + userRepository.deleteAll(); + + // Создание и сохранение тестового пользователя + testUser = userRepository.save(User.builder() + .name("Тестовый пользователь") + .email("user@yandex.ru") + .build()); + } + + // Тест получения всех пользователей из базы данных + @Test + public void findAll_shouldReturnAllUsers() { + // создаем еще одного пользователя + User secondUser = userRepository.save(User.builder() + .name("Второй пользователь") + .email("second@yandex.ru") + .build()); + + Collection result = userService.findAll(); + + assertNotNull(result); + assertEquals(2, result.size()); + } + + // Тест граничного случая получения всех пользователей(возврат пустого списка когда нет пользователей) + @Test + public void findAll_shouldReturnEmptyListWhenNoUsers() { + userRepository.deleteAll(); + + Collection result = userService.findAll(); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + // Тест получения пользователя по id + @Test + public void findById_shouldReturnUserById() { + UserDto result = userService.findById(testUser.getId()); + + assertNotNull(result); + assertEquals(testUser.getId(), result.getId()); + assertEquals("Тестовый пользователь", result.getName()); + assertEquals("user@yandex.ru", result.getEmail()); + } + + // Тест граничного случая получения несуществующего пользователя по id + @Test + public void findById_shouldThrowNotFoundExceptionForNonExistentUser() { + assertThrows(NotFoundException.class, () -> { + userService.findById(999L); + }); + } + + // Тест создания нового пользователя + @Test + public void create_shouldCreateNewUser() { + UserNewDto newUserDto = new UserNewDto("Новый User", "new@email.com"); + + UserDto result = userService.create(newUserDto); + + assertNotNull(result.getId()); + assertEquals("Новый User", result.getName()); + assertEquals("new@email.com", result.getEmail()); + + // Проверяем, что пользователь сохранился в БД + assertEquals(2, userRepository.count()); + } + + // Тест граничного случая создания нового пользователя для повторяющегося email(DuplicateException) + @Test + public void create_shouldThrowDuplicateExceptionForExistingEmail() { + UserNewDto newUserDto = new UserNewDto("Пользователь 3", "user@yandex.ru"); // Существующий email + + assertThrows(DuplicateException.class, () -> { + userService.create(newUserDto); + }); + } + + // Тест граничного случая создания нового пользователя для email в другом регистре(DuplicateException) + @Test + public void create_shouldThrowDuplicateExceptionForCaseInsensitiveEmail() { + UserNewDto newUserDto = new UserNewDto("Пользователь 4", "USER@yandex.ru"); // Email в верхнем регистре + + assertThrows(DuplicateException.class, () -> { + userService.create(newUserDto); + }); + } + + // Тест обновления пользователя + @Test + public void update_shouldUpdateUser() { + UserNewDto updateDto = new UserNewDto("Пользователь для обновления", "updated@email.com"); + + UserDto result = userService.update(testUser.getId(), updateDto); + + assertEquals("Пользователь для обновления", result.getName()); + assertEquals("updated@email.com", result.getEmail()); + + // Проверяем, что изменения сохранились в БД + User dbUser = userRepository.findById(testUser.getId()).orElseThrow(); + assertEquals("Пользователь для обновления", dbUser.getName()); + assertEquals("updated@email.com", dbUser.getEmail()); + } + + // Тест граничного случая частичного обновления пользователя(только имя) + @Test + public void update_shouldPartiallyUpdateUserName() { + UserNewDto updateDto = new UserNewDto("Имя для обновления", null); + + UserDto result = userService.update(testUser.getId(), updateDto); + + assertEquals("Имя для обновления", result.getName()); + assertEquals("user@yandex.ru", result.getEmail()); // Email остался прежним + } + + // Тест граничного случая частичного обновления пользователя(только email) + @Test + public void update_shouldPartiallyUpdateUserEmail() { + UserNewDto updateDto = new UserNewDto(null, "updated@email.com"); + + UserDto result = userService.update(testUser.getId(), updateDto); + + assertEquals("Тестовый пользователь", result.getName()); // Имя осталось прежним + assertEquals("updated@email.com", result.getEmail()); + } + + // Тест граничного случая при обновлении на существующий email(DuplicateException) + @Test + public void update_shouldThrowDuplicateExceptionForExistingEmail() { + // создаем второго пользователя + User secondUser = userRepository.save(User.builder() + .name("Second User") + .email("second@email.com") + .build()); + + // Пытаемся обновить testUser на email второго пользователя + UserNewDto updateDto = new UserNewDto("Updated User", "second@email.com"); + + assertThrows(DuplicateException.class, () -> { + userService.update(testUser.getId(), updateDto); + }); + } + + // Тест граничного случая обновления несуществующего пользователя(NotFoundException) + @Test + public void update_shouldThrowNotFoundExceptionForNonExistentUser() { + UserNewDto updateDto = new UserNewDto("Updated User", "updated@email.com"); + + assertThrows(NotFoundException.class, () -> { + userService.update(999L, updateDto); + }); + } + + // Тест удаления пользователя + @Test + public void delete_shouldRemoveUserFromDatabase() { + userService.delete(testUser.getId()); + + assertFalse(userRepository.existsById(testUser.getId())); + assertEquals(0, userRepository.count()); + } + + // Тест граничного случая удаления несуществующего пользователя(NotFoundException) + @Test + public void delete_shouldThrowNotFoundExceptionForNonExistentUser() { + assertThrows(NotFoundException.class, () -> { + userService.delete(999L); + }); + } +} \ No newline at end of file diff --git a/src/main/resources/application-test.properties b/server/src/test/resources/application-test.properties similarity index 68% rename from src/main/resources/application-test.properties rename to server/src/test/resources/application-test.properties index 71caf05..fd9ae08 100644 --- a/src/main/resources/application-test.properties +++ b/server/src/test/resources/application-test.properties @@ -1,13 +1,16 @@ +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 -# TODO Append connection to H2 DB spring.datasource.driverClassName=org.h2.Driver -spring.datasource.url=jdbc:h2:mem:shareit;MODE=PostgreSQL +spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE spring.datasource.username=sa -spring.datasource.password=password \ No newline at end of file +spring.datasource.password=password +spring.sql.init.schema-locations=classpath:schema.sql \ No newline at end of file diff --git a/server/src/test/resources/schema.sql b/server/src/test/resources/schema.sql new file mode 100644 index 0000000..f511913 --- /dev/null +++ b/server/src/test/resources/schema.sql @@ -0,0 +1,45 @@ +CREATE TABLE IF NOT EXISTS users ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + name VARCHAR(100) NOT NULL, + email VARCHAR(100) NOT NULL UNIQUE +); + +CREATE TABLE IF NOT EXISTS requests ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + description VARCHAR(500) NOT NULL, + created TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(), + requester_id BIGINT NOT NULL, + FOREIGN KEY (requester_id) REFERENCES users (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS items ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + name VARCHAR(100) NOT NULL, + description VARCHAR(1000) NOT NULL, + is_available BOOLEAN NOT NULL, + owner_id BIGINT NOT NULL, + request_id BIGINT, + FOREIGN KEY (owner_id) REFERENCES users (id) ON DELETE CASCADE, + FOREIGN KEY (request_id) REFERENCES requests (id) ON DELETE SET NULL +); + +CREATE TABLE IF NOT EXISTS bookings ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + start_date TIMESTAMP WITHOUT TIME ZONE NOT NULL, + end_date TIMESTAMP WITHOUT TIME ZONE NOT NULL, + item_id BIGINT NOT NULL, + booker_id BIGINT NOT NULL, + status VARCHAR(30) NOT NULL, + FOREIGN KEY (item_id) REFERENCES items (id) ON DELETE CASCADE, + FOREIGN KEY (booker_id) REFERENCES users (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS comments ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + text VARCHAR(1000) NOT NULL, + item_id BIGINT NOT NULL, + author_id BIGINT NOT NULL, + created TIMESTAMP WITHOUT TIME ZONE NOT NULL, + FOREIGN KEY (item_id) REFERENCES items (id) ON DELETE CASCADE, + FOREIGN KEY (author_id) REFERENCES users (id) ON DELETE CASCADE +); \ 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 02e6a98..0000000 --- a/src/main/java/ru/practicum/shareit/request/controller/ItemRequestController.java +++ /dev/null @@ -1,12 +0,0 @@ -package ru.practicum.shareit.request.controller; - -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * TODO Sprint add-item-requests. - */ -@RestController -@RequestMapping(path = "/requests") -public class ItemRequestController { -} diff --git a/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java b/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java deleted file mode 100644 index 7b3ed54..0000000 --- a/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.practicum.shareit.request.dto; - -/** - * TODO Sprint add-item-requests. - */ -public class ItemRequestDto { -} diff --git a/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java b/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java deleted file mode 100644 index 5e8b8cd..0000000 --- a/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java +++ /dev/null @@ -1,28 +0,0 @@ -package ru.practicum.shareit.request.model; - -import jakarta.persistence.*; -import lombok.*; - -/** - * TODO Sprint add-item-requests. - */ -@Getter -@Setter -@ToString -@Entity -@Table(name = "items") -@NoArgsConstructor -@AllArgsConstructor -@Builder(toBuilder = true) -public class ItemRequest { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(name = "description") - private String description; - - @Column(name = "requestor_id") - private long requestorId; -} \ No newline at end of file From bec9955d390daedf39d8f55b8772dd54ac6e806a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9A=D0=B0?= =?UTF-8?q?=D0=BD?= Date: Wed, 17 Sep 2025 17:23:20 +0300 Subject: [PATCH 2/6] =?UTF-8?q?fix:=20=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20CodyStyle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shareit/item/ItemDtoRequestJsonTest.java | 24 +++--- .../item/ItemUpdateDtoRequestJsonTest.java | 76 +++++++++---------- .../ItemRequestDtoRequestJsonTest.java | 48 ++++++------ .../shareit/user/UserDtoRequestJsonTest.java | 40 +++++----- .../user/UserUpdateDtoRequestJsonTest.java | 72 +++++++++--------- 5 files changed, 129 insertions(+), 131 deletions(-) diff --git a/gateway/src/test/java/ru/practicum/shareit/item/ItemDtoRequestJsonTest.java b/gateway/src/test/java/ru/practicum/shareit/item/ItemDtoRequestJsonTest.java index c41aace..6ce507f 100644 --- a/gateway/src/test/java/ru/practicum/shareit/item/ItemDtoRequestJsonTest.java +++ b/gateway/src/test/java/ru/practicum/shareit/item/ItemDtoRequestJsonTest.java @@ -35,13 +35,12 @@ public void serialize_shouldProduceCorrectJsonStructure() throws Exception { @Test public void deserialize_shouldParseJsonCorrectly() throws Exception { String jsonContent = """ - { - "name": "Дрель", - "description": "Аккумуляторная дрель", - "available": true, - "requestId": 123 - } - """; + { "name": "Дрель", + "description": "Аккумуляторная дрель", + "available": true, + "requestId": 123 + } + """; ItemDtoRequest result = json.parseObject(jsonContent); @@ -67,12 +66,11 @@ public void serialize_shouldOmitNullRequestId() throws Exception { @Test public void deserialize_shouldHandleMissingRequestId() throws Exception { String jsonContent = """ - { - "name": "Дрель", - "description": "Аккумуляторная дрель", - "available": true - } - """; + { "name": "Дрель", + "description": "Аккумуляторная дрель", + "available": true + } + """; ItemDtoRequest result = json.parseObject(jsonContent); diff --git a/gateway/src/test/java/ru/practicum/shareit/item/ItemUpdateDtoRequestJsonTest.java b/gateway/src/test/java/ru/practicum/shareit/item/ItemUpdateDtoRequestJsonTest.java index 8ad1a44..e6018e9 100644 --- a/gateway/src/test/java/ru/practicum/shareit/item/ItemUpdateDtoRequestJsonTest.java +++ b/gateway/src/test/java/ru/practicum/shareit/item/ItemUpdateDtoRequestJsonTest.java @@ -25,11 +25,11 @@ public void serialize_shouldHandlePartialData() throws Exception { false, null); assertThat(json.write(dto)).isEqualToJson(""" - { - "name": "Дрель", - "available": false - } - """); + { + "name": "Дрель", + "available": false + } + """); } // Тест сериализации с полными данными @@ -39,13 +39,13 @@ public void serialize_shouldHandleAllFields() throws Exception { true, 123L); assertThat(json.write(dto)).isEqualToJson(""" - { - "name": "Дрель", - "description": "Аккумуляторная дрель", - "available": true, - "requestId": 123 - } - """); + { + "name": "Дрель", + "description": "Аккумуляторная дрель", + "available": true, + "requestId": 123 + } + """); } // Тест сериализации с null значениями @@ -54,24 +54,24 @@ public void serialize_shouldHandleNullValues() throws Exception { ItemUpdateDtoRequest dto = new ItemUpdateDtoRequest(null, null, null, null); assertThat(json.write(dto)).isEqualToJson(""" - { - "name": null, - "description": null, - "available": null, - "requestId": null - } - """); + { + "name": null, + "description": null, + "available": null, + "requestId": null + } + """); } // Тест десериализации частичного обновления @Test public void deserialize_shouldParsePartialData() throws Exception { String jsonContent = """ - { - "name": "Дрель", - "available": false - } - """; + { + "name": "Дрель", + "available": false + } + """; ItemUpdateDtoRequest result = json.parseObject(jsonContent); @@ -85,13 +85,13 @@ public void deserialize_shouldParsePartialData() throws Exception { @Test public void deserialize_shouldParseAllFields() throws Exception { String jsonContent = """ - { - "name": "Дрель", - "description": "Аккумуляторная дрель", - "available": true, - "requestId": 123 - } - """; + { + "name": "Дрель", + "description": "Аккумуляторная дрель", + "available": true, + "requestId": 123 + } + """; ItemUpdateDtoRequest result = json.parseObject(jsonContent); @@ -105,13 +105,13 @@ public void deserialize_shouldParseAllFields() throws Exception { @Test public void deserialize_shouldHandleExplicitNulls() throws Exception { String jsonContent = """ - { - "name": null, - "description": null, - "available": null, - "requestId": null - } - """; + { + "name": null, + "description": null, + "available": null, + "requestId": null + } + """; ItemUpdateDtoRequest result = json.parseObject(jsonContent); diff --git a/gateway/src/test/java/ru/practicum/shareit/request/ItemRequestDtoRequestJsonTest.java b/gateway/src/test/java/ru/practicum/shareit/request/ItemRequestDtoRequestJsonTest.java index 8428c7a..b43e33c 100644 --- a/gateway/src/test/java/ru/practicum/shareit/request/ItemRequestDtoRequestJsonTest.java +++ b/gateway/src/test/java/ru/practicum/shareit/request/ItemRequestDtoRequestJsonTest.java @@ -24,20 +24,20 @@ public void serialize_shouldProduceCorrectJson() throws Exception { ItemRequestDtoRequest dto = new ItemRequestDtoRequest("Нужна дрель"); assertThat(json.write(dto)).isEqualToJson(""" - { - "description": "Нужна дрель" - } - """); + { + "description": "Нужна дрель" + } + """); } // Тест десериализации JSON в объект @Test public void deserialize_shouldParseJsonCorrectly() throws Exception { String jsonContent = """ - { - "description": "Нужна дрель" - } - """; + { + "description": "Нужна дрель" + } + """; ItemRequestDtoRequest result = json.parseObject(jsonContent); @@ -50,20 +50,20 @@ public void serialize_shouldHandleEmptyString() throws Exception { ItemRequestDtoRequest dto = new ItemRequestDtoRequest(""); assertThat(json.write(dto)).isEqualToJson(""" - { - "description": "" - } - """); + { + "description": "" + } + """); } // Тест десериализации с пустой строкой @Test public void deserialize_shouldParseEmptyString() throws Exception { String jsonContent = """ - { - "description": "" - } - """; + { + "description": "" + } + """; ItemRequestDtoRequest result = json.parseObject(jsonContent); @@ -76,20 +76,20 @@ public void serialize_shouldHandleNullValue() throws Exception { ItemRequestDtoRequest dto = new ItemRequestDtoRequest(null); assertThat(json.write(dto)).isEqualToJson(""" - { - "description": null - } - """); + { + "description": null + } + """); } // Тест десериализации с null значением @Test public void deserialize_shouldParseNullValue() throws Exception { String jsonContent = """ - { - "description": null - } - """; + { + "description": null + } + """; ItemRequestDtoRequest result = json.parseObject(jsonContent); diff --git a/gateway/src/test/java/ru/practicum/shareit/user/UserDtoRequestJsonTest.java b/gateway/src/test/java/ru/practicum/shareit/user/UserDtoRequestJsonTest.java index aa32945..2f44c86 100644 --- a/gateway/src/test/java/ru/practicum/shareit/user/UserDtoRequestJsonTest.java +++ b/gateway/src/test/java/ru/practicum/shareit/user/UserDtoRequestJsonTest.java @@ -24,22 +24,22 @@ public void serialize_shouldProduceCorrectJson() throws Exception { UserDtoRequest dto = new UserDtoRequest("Иван Иванов", "ivan@mail.ru"); assertThat(json.write(dto)).isEqualToJson(""" - { - "name": "Иван Иванов", - "email": "ivan@mail.ru" - } - """); + { + "name": "Иван Иванов", + "email": "ivan@mail.ru" + } + """); } // Тест десериализации JSON в объект @Test public void deserialize_shouldParseJsonCorrectly() throws Exception { String jsonContent = """ - { - "name": "Иван Иванов", - "email": "ivan@mail.ru" - } - """; + { + "name": "Иван Иванов", + "email": "ivan@mail.ru" + } + """; UserDtoRequest result = json.parseObject(jsonContent); @@ -53,22 +53,22 @@ public void serialize_shouldHandleEmptyStrings() throws Exception { UserDtoRequest dto = new UserDtoRequest("", ""); assertThat(json.write(dto)).isEqualToJson(""" - { - "name": "", - "email": "" - } - """); + { + "name": "", + "email": "" + } + """); } // Тест десериализации с пустыми строками @Test public void deserialize_shouldParseEmptyStrings() throws Exception { String jsonContent = """ - { - "name": "", - "email": "" - } - """; + { + "name": "", + "email": "" + } + """; UserDtoRequest result = json.parseObject(jsonContent); diff --git a/gateway/src/test/java/ru/practicum/shareit/user/UserUpdateDtoRequestJsonTest.java b/gateway/src/test/java/ru/practicum/shareit/user/UserUpdateDtoRequestJsonTest.java index aea7b58..0cc46a0 100644 --- a/gateway/src/test/java/ru/practicum/shareit/user/UserUpdateDtoRequestJsonTest.java +++ b/gateway/src/test/java/ru/practicum/shareit/user/UserUpdateDtoRequestJsonTest.java @@ -24,11 +24,11 @@ public void serialize_shouldHandleFullUpdate() throws Exception { UserUpdateDtoRequest dto = new UserUpdateDtoRequest("Новое имя", "new@mail.ru"); assertThat(json.write(dto)).isEqualToJson(""" - { - "name": "Новое имя", - "email": "new@mail.ru" - } - """); + { + "name": "Новое имя", + "email": "new@mail.ru" + } + """); } // Тест сериализации частичного обновления (только имя) @@ -37,10 +37,10 @@ public void serialize_shouldHandlePartialNameUpdate() throws Exception { UserUpdateDtoRequest dto = new UserUpdateDtoRequest("Только имя", null); assertThat(json.write(dto)).isEqualToJson(""" - { - "name": "Только имя" - } - """); + { + "name": "Только имя" + } + """); } // Тест сериализации частичного обновления (только email) @@ -49,21 +49,21 @@ public void serialize_shouldHandlePartialEmailUpdate() throws Exception { UserUpdateDtoRequest dto = new UserUpdateDtoRequest(null, "only@mail.ru"); assertThat(json.write(dto)).isEqualToJson(""" - { - "email": "only@mail.ru" - } - """); + { + "email": "only@mail.ru" + } + """); } // Тест десериализации полного обновления @Test public void deserialize_shouldParseFullUpdate() throws Exception { String jsonContent = """ - { - "name": "Новое имя", - "email": "new@mail.ru" - } - """; + { + "name": "Новое имя", + "email": "new@mail.ru" + } + """; UserUpdateDtoRequest result = json.parseObject(jsonContent); @@ -75,10 +75,10 @@ public void deserialize_shouldParseFullUpdate() throws Exception { @Test public void deserialize_shouldParsePartialNameUpdate() throws Exception { String jsonContent = """ - { - "name": "Только имя" - } - """; + { + "name": "Только имя" + } + """; UserUpdateDtoRequest result = json.parseObject(jsonContent); @@ -90,10 +90,10 @@ public void deserialize_shouldParsePartialNameUpdate() throws Exception { @Test public void deserialize_shouldParsePartialEmailUpdate() throws Exception { String jsonContent = """ - { - "email": "only@mail.ru" - } - """; + { + "email": "only@mail.ru" + } + """; UserUpdateDtoRequest result = json.parseObject(jsonContent); @@ -105,11 +105,11 @@ public void deserialize_shouldParsePartialEmailUpdate() throws Exception { @Test public void deserialize_shouldHandleNullValues() throws Exception { String jsonContent = """ - { - "name": null, - "email": null - } - """; + { + "name": null, + "email": null + } + """; UserUpdateDtoRequest result = json.parseObject(jsonContent); @@ -121,11 +121,11 @@ public void deserialize_shouldHandleNullValues() throws Exception { @Test public void deserialize_shouldHandleEmptyFields() throws Exception { String jsonContent = """ - { - "name": "", - "email": "" - } - """; + { + "name": "", + "email": "" + } + """; UserUpdateDtoRequest result = json.parseObject(jsonContent); From 5790f57ac1b7640cd70fa3137272f61ee62635c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9A=D0=B0?= =?UTF-8?q?=D0=BD?= Date: Wed, 17 Sep 2025 17:47:37 +0300 Subject: [PATCH 3/6] =?UTF-8?q?fix:=20=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20CodyStyle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../booking/BookingDtoRequestJsonTest.java | 30 +++------ .../shareit/item/ItemDtoRequestJsonTest.java | 20 ++---- .../item/ItemUpdateDtoRequestJsonTest.java | 64 ++++++------------- .../ItemRequestDtoRequestJsonTest.java | 36 ++--------- .../shareit/user/UserDtoRequestJsonTest.java | 30 ++------- .../user/UserUpdateDtoRequestJsonTest.java | 54 +++------------- 6 files changed, 58 insertions(+), 176 deletions(-) diff --git a/gateway/src/test/java/ru/practicum/shareit/booking/BookingDtoRequestJsonTest.java b/gateway/src/test/java/ru/practicum/shareit/booking/BookingDtoRequestJsonTest.java index 83936a1..3bfe6a5 100644 --- a/gateway/src/test/java/ru/practicum/shareit/booking/BookingDtoRequestJsonTest.java +++ b/gateway/src/test/java/ru/practicum/shareit/booking/BookingDtoRequestJsonTest.java @@ -50,13 +50,9 @@ public void serialize_shouldProduceCorrectDateFormat() throws Exception { // Тест корректной десериализации JSON в объект @Test public void deserialize_shouldParseWithoutZ() throws Exception { - String jsonContent = """ - { - "itemId": 1, - "start": "2024-01-01T10:00:00", - "end": "2024-01-02T10:00:00" - } - """; + String jsonContent = "{\"itemId\":1," + + "\"start\":\"2024-01-01T10:00:00\"," + + "\"end\":\"2024-01-02T10:00:00\"}"; BookingDtoRequest result = json.parse(jsonContent).getObject(); @@ -68,13 +64,9 @@ public void deserialize_shouldParseWithoutZ() throws Exception { // Тест проверяет обработку некорректного формата даты(InvalidFormatException) @Test public void deserialize_shouldThrowExceptionForInvalidDateFormat() throws Exception { - String jsonContent = """ - { - "itemId": 1, - "start": "2024-01-01T10:00:00Z", - "end": "2024-01-02T10:00:00" - } - """; + String jsonContent = "{\"itemId\":1," + + "\"start\":\"2024-01-01T10:00:00Z\"," + + "\"end\":\"2024-01-02T10:00:00\"}"; assertThatThrownBy(() -> json.parse(jsonContent)) .isInstanceOf(InvalidFormatException.class); @@ -83,13 +75,9 @@ public void deserialize_shouldThrowExceptionForInvalidDateFormat() throws Except // Тест проверяет обработку некорректного формата числового поля(InvalidFormatException) @Test public void deserialize_shouldThrowExceptionForInvalidItemIdFormat() throws Exception { - String jsonContent = """ - { - "itemId": "not_a_number", - "start": "2024-01-01T10:00:00", - "end": "2024-01-02T10:00:00" - } - """; + String jsonContent = "{\"itemId\":\"not_a_number\"," + + "\"start\":\"2024-01-01T10:00:00\"," + + "\"end\":\"2024-01-02T10:00:00\"}"; assertThatThrownBy(() -> json.parse(jsonContent)) .isInstanceOf(InvalidFormatException.class); diff --git a/gateway/src/test/java/ru/practicum/shareit/item/ItemDtoRequestJsonTest.java b/gateway/src/test/java/ru/practicum/shareit/item/ItemDtoRequestJsonTest.java index 6ce507f..472284d 100644 --- a/gateway/src/test/java/ru/practicum/shareit/item/ItemDtoRequestJsonTest.java +++ b/gateway/src/test/java/ru/practicum/shareit/item/ItemDtoRequestJsonTest.java @@ -34,13 +34,10 @@ public void serialize_shouldProduceCorrectJsonStructure() throws Exception { // Тест десериализации JSON в объект @Test public void deserialize_shouldParseJsonCorrectly() throws Exception { - String jsonContent = """ - { "name": "Дрель", - "description": "Аккумуляторная дрель", - "available": true, - "requestId": 123 - } - """; + String jsonContent = "{\"name\":\"Дрель\"," + + "\"description\":\"Аккумуляторная дрель\"," + + "\"available\":true," + + "\"requestId\":123}"; ItemDtoRequest result = json.parseObject(jsonContent); @@ -65,12 +62,9 @@ public void serialize_shouldOmitNullRequestId() throws Exception { // Тест десериализации без requestId @Test public void deserialize_shouldHandleMissingRequestId() throws Exception { - String jsonContent = """ - { "name": "Дрель", - "description": "Аккумуляторная дрель", - "available": true - } - """; + String jsonContent = "{\"name\":\"Дрель\"," + + "\"description\":\"Аккумуляторная дрель\"," + + "\"available\":true}"; ItemDtoRequest result = json.parseObject(jsonContent); diff --git a/gateway/src/test/java/ru/practicum/shareit/item/ItemUpdateDtoRequestJsonTest.java b/gateway/src/test/java/ru/practicum/shareit/item/ItemUpdateDtoRequestJsonTest.java index e6018e9..d513590 100644 --- a/gateway/src/test/java/ru/practicum/shareit/item/ItemUpdateDtoRequestJsonTest.java +++ b/gateway/src/test/java/ru/practicum/shareit/item/ItemUpdateDtoRequestJsonTest.java @@ -24,12 +24,8 @@ public void serialize_shouldHandlePartialData() throws Exception { ItemUpdateDtoRequest dto = new ItemUpdateDtoRequest("Дрель", null, false, null); - assertThat(json.write(dto)).isEqualToJson(""" - { - "name": "Дрель", - "available": false - } - """); + assertThat(json.write(dto)).isEqualToJson("{\"name\":\"Дрель\"," + + "\"available\":false}"); } // Тест сериализации с полными данными @@ -38,14 +34,10 @@ public void serialize_shouldHandleAllFields() throws Exception { ItemUpdateDtoRequest dto = new ItemUpdateDtoRequest("Дрель", "Аккумуляторная дрель", true, 123L); - assertThat(json.write(dto)).isEqualToJson(""" - { - "name": "Дрель", - "description": "Аккумуляторная дрель", - "available": true, - "requestId": 123 - } - """); + assertThat(json.write(dto)).isEqualToJson("{\"name\":\"Дрель\"," + + "\"description\":\"Аккумуляторная дрель\"," + + "\"available\":true," + + "\"requestId\":123}"); } // Тест сериализации с null значениями @@ -53,25 +45,17 @@ public void serialize_shouldHandleAllFields() throws Exception { public void serialize_shouldHandleNullValues() throws Exception { ItemUpdateDtoRequest dto = new ItemUpdateDtoRequest(null, null, null, null); - assertThat(json.write(dto)).isEqualToJson(""" - { - "name": null, - "description": null, - "available": null, - "requestId": null - } - """); + assertThat(json.write(dto)).isEqualToJson("{\"name\":null," + + "\"description\":null," + + "\"available\":null," + + "\"requestId\":null}"); } // Тест десериализации частичного обновления @Test public void deserialize_shouldParsePartialData() throws Exception { - String jsonContent = """ - { - "name": "Дрель", - "available": false - } - """; + String jsonContent = "{\"name\":\"Дрель\"," + + "\"available\":false}"; ItemUpdateDtoRequest result = json.parseObject(jsonContent); @@ -84,14 +68,10 @@ public void deserialize_shouldParsePartialData() throws Exception { // Тест десериализации с полными данными @Test public void deserialize_shouldParseAllFields() throws Exception { - String jsonContent = """ - { - "name": "Дрель", - "description": "Аккумуляторная дрель", - "available": true, - "requestId": 123 - } - """; + String jsonContent = "{\"name\":\"Дрель\"," + + "\"description\":\"Аккумуляторная дрель\"," + + "\"available\":true," + + "\"requestId\":123}"; ItemUpdateDtoRequest result = json.parseObject(jsonContent); @@ -104,14 +84,10 @@ public void deserialize_shouldParseAllFields() throws Exception { // Тест десериализации с явными null значениями @Test public void deserialize_shouldHandleExplicitNulls() throws Exception { - String jsonContent = """ - { - "name": null, - "description": null, - "available": null, - "requestId": null - } - """; + String jsonContent = "{\"name\":null," + + "\"description\":null," + + "\"available\":null," + + "\"requestId\":null}"; ItemUpdateDtoRequest result = json.parseObject(jsonContent); diff --git a/gateway/src/test/java/ru/practicum/shareit/request/ItemRequestDtoRequestJsonTest.java b/gateway/src/test/java/ru/practicum/shareit/request/ItemRequestDtoRequestJsonTest.java index b43e33c..21f1aef 100644 --- a/gateway/src/test/java/ru/practicum/shareit/request/ItemRequestDtoRequestJsonTest.java +++ b/gateway/src/test/java/ru/practicum/shareit/request/ItemRequestDtoRequestJsonTest.java @@ -23,21 +23,13 @@ public class ItemRequestDtoRequestJsonTest { public void serialize_shouldProduceCorrectJson() throws Exception { ItemRequestDtoRequest dto = new ItemRequestDtoRequest("Нужна дрель"); - assertThat(json.write(dto)).isEqualToJson(""" - { - "description": "Нужна дрель" - } - """); + assertThat(json.write(dto)).isEqualToJson("{\"description\":\"Нужна дрель\"}"); } // Тест десериализации JSON в объект @Test public void deserialize_shouldParseJsonCorrectly() throws Exception { - String jsonContent = """ - { - "description": "Нужна дрель" - } - """; + String jsonContent = "{\"description\":\"Нужна дрель\"}"; ItemRequestDtoRequest result = json.parseObject(jsonContent); @@ -49,21 +41,13 @@ public void deserialize_shouldParseJsonCorrectly() throws Exception { public void serialize_shouldHandleEmptyString() throws Exception { ItemRequestDtoRequest dto = new ItemRequestDtoRequest(""); - assertThat(json.write(dto)).isEqualToJson(""" - { - "description": "" - } - """); + assertThat(json.write(dto)).isEqualToJson("{\"description\":\"\"}"); } // Тест десериализации с пустой строкой @Test public void deserialize_shouldParseEmptyString() throws Exception { - String jsonContent = """ - { - "description": "" - } - """; + String jsonContent = "{\"description\":\"\"}"; ItemRequestDtoRequest result = json.parseObject(jsonContent); @@ -75,21 +59,13 @@ public void deserialize_shouldParseEmptyString() throws Exception { public void serialize_shouldHandleNullValue() throws Exception { ItemRequestDtoRequest dto = new ItemRequestDtoRequest(null); - assertThat(json.write(dto)).isEqualToJson(""" - { - "description": null - } - """); + assertThat(json.write(dto)).isEqualToJson("{\"description\":null}"); } // Тест десериализации с null значением @Test public void deserialize_shouldParseNullValue() throws Exception { - String jsonContent = """ - { - "description": null - } - """; + String jsonContent = "{\"description\":null}"; ItemRequestDtoRequest result = json.parseObject(jsonContent); diff --git a/gateway/src/test/java/ru/practicum/shareit/user/UserDtoRequestJsonTest.java b/gateway/src/test/java/ru/practicum/shareit/user/UserDtoRequestJsonTest.java index 2f44c86..620c0c8 100644 --- a/gateway/src/test/java/ru/practicum/shareit/user/UserDtoRequestJsonTest.java +++ b/gateway/src/test/java/ru/practicum/shareit/user/UserDtoRequestJsonTest.java @@ -23,23 +23,15 @@ public class UserDtoRequestJsonTest { public void serialize_shouldProduceCorrectJson() throws Exception { UserDtoRequest dto = new UserDtoRequest("Иван Иванов", "ivan@mail.ru"); - assertThat(json.write(dto)).isEqualToJson(""" - { - "name": "Иван Иванов", - "email": "ivan@mail.ru" - } - """); + assertThat(json.write(dto)).isEqualToJson("{\"name\":\"Иван Иванов\"," + + "\"email\":\"ivan@mail.ru\"}"); } // Тест десериализации JSON в объект @Test public void deserialize_shouldParseJsonCorrectly() throws Exception { - String jsonContent = """ - { - "name": "Иван Иванов", - "email": "ivan@mail.ru" - } - """; + String jsonContent = "{\"name\":\"Иван Иванов\"," + + "\"email\":\"ivan@mail.ru\"}"; UserDtoRequest result = json.parseObject(jsonContent); @@ -52,23 +44,13 @@ public void deserialize_shouldParseJsonCorrectly() throws Exception { public void serialize_shouldHandleEmptyStrings() throws Exception { UserDtoRequest dto = new UserDtoRequest("", ""); - assertThat(json.write(dto)).isEqualToJson(""" - { - "name": "", - "email": "" - } - """); + assertThat(json.write(dto)).isEqualToJson("{\"name\":\"\",\"email\":\"\"}"); } // Тест десериализации с пустыми строками @Test public void deserialize_shouldParseEmptyStrings() throws Exception { - String jsonContent = """ - { - "name": "", - "email": "" - } - """; + String jsonContent = "{\"name\":\"\",\"email\":\"\"}"; UserDtoRequest result = json.parseObject(jsonContent); diff --git a/gateway/src/test/java/ru/practicum/shareit/user/UserUpdateDtoRequestJsonTest.java b/gateway/src/test/java/ru/practicum/shareit/user/UserUpdateDtoRequestJsonTest.java index 0cc46a0..ee35a7d 100644 --- a/gateway/src/test/java/ru/practicum/shareit/user/UserUpdateDtoRequestJsonTest.java +++ b/gateway/src/test/java/ru/practicum/shareit/user/UserUpdateDtoRequestJsonTest.java @@ -23,12 +23,8 @@ public class UserUpdateDtoRequestJsonTest { public void serialize_shouldHandleFullUpdate() throws Exception { UserUpdateDtoRequest dto = new UserUpdateDtoRequest("Новое имя", "new@mail.ru"); - assertThat(json.write(dto)).isEqualToJson(""" - { - "name": "Новое имя", - "email": "new@mail.ru" - } - """); + assertThat(json.write(dto)).isEqualToJson("{\"name\":\"Новое имя\"," + + "\"email\":\"new@mail.ru\"}"); } // Тест сериализации частичного обновления (только имя) @@ -36,11 +32,7 @@ public void serialize_shouldHandleFullUpdate() throws Exception { public void serialize_shouldHandlePartialNameUpdate() throws Exception { UserUpdateDtoRequest dto = new UserUpdateDtoRequest("Только имя", null); - assertThat(json.write(dto)).isEqualToJson(""" - { - "name": "Только имя" - } - """); + assertThat(json.write(dto)).isEqualToJson("{\"name\":\"Только имя\"}"); } // Тест сериализации частичного обновления (только email) @@ -48,22 +40,14 @@ public void serialize_shouldHandlePartialNameUpdate() throws Exception { public void serialize_shouldHandlePartialEmailUpdate() throws Exception { UserUpdateDtoRequest dto = new UserUpdateDtoRequest(null, "only@mail.ru"); - assertThat(json.write(dto)).isEqualToJson(""" - { - "email": "only@mail.ru" - } - """); + assertThat(json.write(dto)).isEqualToJson("{\"email\":\"only@mail.ru\"}"); } // Тест десериализации полного обновления @Test public void deserialize_shouldParseFullUpdate() throws Exception { - String jsonContent = """ - { - "name": "Новое имя", - "email": "new@mail.ru" - } - """; + String jsonContent = "{\"name\":\"Новое имя\"," + + "\"email\":\"new@mail.ru\"}"; UserUpdateDtoRequest result = json.parseObject(jsonContent); @@ -74,11 +58,7 @@ public void deserialize_shouldParseFullUpdate() throws Exception { // Тест десериализации частичного обновления (только имя) @Test public void deserialize_shouldParsePartialNameUpdate() throws Exception { - String jsonContent = """ - { - "name": "Только имя" - } - """; + String jsonContent = "{\"name\":\"Только имя\"}"; UserUpdateDtoRequest result = json.parseObject(jsonContent); @@ -89,11 +69,7 @@ public void deserialize_shouldParsePartialNameUpdate() throws Exception { // Тест десериализации частичного обновления (только email) @Test public void deserialize_shouldParsePartialEmailUpdate() throws Exception { - String jsonContent = """ - { - "email": "only@mail.ru" - } - """; + String jsonContent = "{\"email\":\"only@mail.ru\"}"; UserUpdateDtoRequest result = json.parseObject(jsonContent); @@ -104,12 +80,7 @@ public void deserialize_shouldParsePartialEmailUpdate() throws Exception { // Тест десериализации с null значениями @Test public void deserialize_shouldHandleNullValues() throws Exception { - String jsonContent = """ - { - "name": null, - "email": null - } - """; + String jsonContent = "{\"name\":null,\"email\":null}"; UserUpdateDtoRequest result = json.parseObject(jsonContent); @@ -120,12 +91,7 @@ public void deserialize_shouldHandleNullValues() throws Exception { // Тест десериализации с пустыми полями @Test public void deserialize_shouldHandleEmptyFields() throws Exception { - String jsonContent = """ - { - "name": "", - "email": "" - } - """; + String jsonContent = "{\"name\":\"\",\"email\":\"\"}"; UserUpdateDtoRequest result = json.parseObject(jsonContent); From a34711b1d53dd88bcc36d4f2a23cd477075e0cb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9A=D0=B0?= =?UTF-8?q?=D0=BD?= Date: Wed, 17 Sep 2025 17:50:16 +0300 Subject: [PATCH 4/6] =?UTF-8?q?fix:=20=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20CodeStyle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shareit/request/ItemRequestServiceIntegrationTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/test/java/ru/practicum/shareit/request/ItemRequestServiceIntegrationTest.java b/server/src/test/java/ru/practicum/shareit/request/ItemRequestServiceIntegrationTest.java index 42690f8..e2b512e 100644 --- a/server/src/test/java/ru/practicum/shareit/request/ItemRequestServiceIntegrationTest.java +++ b/server/src/test/java/ru/practicum/shareit/request/ItemRequestServiceIntegrationTest.java @@ -14,7 +14,6 @@ import ru.practicum.shareit.user.dao.UserRepository; import ru.practicum.shareit.user.model.User; -import java.time.Instant; import java.time.LocalDateTime; import java.util.Collection; From 594627482170f60c2442b64ba3908f567f0d8f20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9A=D0=B0?= =?UTF-8?q?=D0=BD?= Date: Fri, 19 Sep 2025 00:01:09 +0300 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=87=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D1=80=D0=B5=D0=B2=D1=8C=D1=8E=D0=B5=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shareit/booking/BookingClient.java | 29 +++-- .../shareit/booking/BookingController.java | 28 +++-- .../booking/dto/BookingDtoResponse.java | 27 +++++ .../practicum/shareit/client/BaseClient.java | 113 +++++++++++------- .../ru/practicum/shareit/item/ItemClient.java | 44 ++++--- .../shareit/item/ItemController.java | 43 ++++--- .../item/comment/dto/CommentDtoRequest.java | 2 +- .../item/comment/dto/CommentDtoResponse.java | 22 ++++ .../shareit/item/dto/ItemDtoResponse.java | 16 +++ .../shareit/item/dto/ItemFullDtoResponse.java | 23 ++++ .../shareit/request/ItemRequestClient.java | 25 ++-- .../request/ItemRequestController.java | 20 ++-- .../request/dto/ItemRequestDtoResponse.java | 22 ++++ .../ru/practicum/shareit/user/UserClient.java | 25 ++-- .../shareit/user/UserController.java | 16 +-- .../shareit/user/dto/UserDtoResponse.java | 12 ++ .../shareit/item/ItemControllerTest.java | 3 +- .../request/ItemRequestControllerTest.java | 1 + .../shareit/user/UserControllerTest.java | 3 +- 19 files changed, 336 insertions(+), 138 deletions(-) create mode 100644 gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingDtoResponse.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/item/comment/dto/CommentDtoResponse.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/item/dto/ItemDtoResponse.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/item/dto/ItemFullDtoResponse.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDtoResponse.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/user/dto/UserDtoResponse.java diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/BookingClient.java b/gateway/src/main/java/ru/practicum/shareit/booking/BookingClient.java index d3fff38..40088d8 100644 --- a/gateway/src/main/java/ru/practicum/shareit/booking/BookingClient.java +++ b/gateway/src/main/java/ru/practicum/shareit/booking/BookingClient.java @@ -3,14 +3,15 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.core.ParameterizedTypeReference; 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.BookingDtoRequest; -import ru.practicum.shareit.booking.dto.BookingState; +import ru.practicum.shareit.booking.dto.*; import ru.practicum.shareit.client.BaseClient; +import java.util.List; import java.util.Map; @Service @@ -28,24 +29,28 @@ public BookingClient(@Value("${shareit-server.url}") String serverUrl, RestTempl ); } - public ResponseEntity createBooking(Long userId, BookingDtoRequest bookingDtoRequest) { - return post("", userId, bookingDtoRequest); + public ResponseEntity createBooking(Long userId, BookingDtoRequest bookingDtoRequest) { + return post("", userId, bookingDtoRequest, BookingDtoResponse.class); } - public ResponseEntity getBooking(Long userId, Long bookingId) { - return get("/" + bookingId, userId); + public ResponseEntity getBooking(Long userId, Long bookingId) { + return get("/" + bookingId, userId, BookingDtoResponse.class); } - public ResponseEntity getBookingByBooker(Long userId, BookingState state) { - return get("?state={state}", userId, Map.of("state", state.name())); + public ResponseEntity> getBookingByBooker(Long userId, BookingState state) { + ParameterizedTypeReference> typeRef = + new ParameterizedTypeReference>() {}; + return get("?state={state}", userId, Map.of("state", state.name()), typeRef); } - public ResponseEntity getBookingByOwner(Long userId, BookingState state) { - return get("/owner?state={state}", userId, Map.of("state", state.name())); + public ResponseEntity> getBookingByOwner(Long userId, BookingState state) { + ParameterizedTypeReference> typeRef = + new ParameterizedTypeReference>() {}; + return get("/owner?state={state}", userId, Map.of("state", state.name()), typeRef); } - public ResponseEntity approveBooking(Long userId, Long bookingId, Boolean isApproved) { + public ResponseEntity approveBooking(Long userId, Long bookingId, Boolean isApproved) { return patch("/" + bookingId + "?approved={approved}", userId, - Map.of("approved", isApproved), ""); + Map.of("approved", isApproved), "", BookingDtoResponse.class); } } \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java b/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java index 8a6bce9..cba3972 100644 --- a/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java +++ b/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java @@ -9,6 +9,8 @@ import org.springframework.web.bind.annotation.*; import ru.practicum.shareit.booking.dto.*; +import java.util.List; + @Slf4j @Validated @RestController @@ -19,39 +21,39 @@ public class BookingController { private final BookingClient bookingClient; @PostMapping - public ResponseEntity create(@RequestHeader("X-Sharer-User-Id") @Positive Long userId, - @RequestBody @Valid BookingDtoRequest bookingDtoRequest) { + public ResponseEntity create(@RequestHeader("X-Sharer-User-Id") @Positive Long userId, + @RequestBody @Valid BookingDtoRequest bookingDtoRequest) { log.info("POST / bookings"); return bookingClient.createBooking(userId, bookingDtoRequest); } @PatchMapping("/{bookingId}") - public ResponseEntity approve(@RequestHeader("X-Sharer-User-Id") @Positive Long userId, - @PathVariable @Positive Long bookingId, - @RequestParam(name = "approved") Boolean isApproved) { + public ResponseEntity approve(@RequestHeader("X-Sharer-User-Id") @Positive Long userId, + @PathVariable @Positive Long bookingId, + @RequestParam(name = "approved") Boolean isApproved) { log.info("PATCH / bookings / {}", bookingId); return bookingClient.approveBooking(userId, bookingId, isApproved); } @GetMapping("/{bookingId}") - public ResponseEntity getById(@RequestHeader("X-Sharer-User-Id") @Positive Long userId, - @PathVariable @Positive Long bookingId) { + public ResponseEntity getById(@RequestHeader("X-Sharer-User-Id") @Positive Long userId, + @PathVariable @Positive Long bookingId) { log.info("GET booking {}, userId = {}", bookingId, userId); return bookingClient.getBooking(userId, bookingId); } @GetMapping("/owner") - public ResponseEntity getByOwnerId(@RequestHeader("X-Sharer-User-Id") @Positive Long ownerId, - @RequestParam(name = "state", defaultValue = "ALL") - BookingState bookingState) { + public ResponseEntity> getByOwnerId( + @RequestHeader("X-Sharer-User-Id") @Positive Long ownerId, + @RequestParam(name = "state", defaultValue = "ALL") BookingState bookingState) { log.info("GET / ByOwner / {}", ownerId); return bookingClient.getBookingByOwner(ownerId, bookingState); } @GetMapping - public ResponseEntity getByBookerId(@RequestHeader("X-Sharer-User-Id") @Positive Long bookerId, - @RequestParam(name = "state", defaultValue = "ALL") - BookingState bookingState) { + public ResponseEntity> getByBookerId( + @RequestHeader("X-Sharer-User-Id") @Positive Long bookerId, + @RequestParam(name = "state", defaultValue = "ALL") BookingState bookingState) { log.info("GET / ByBooker / {}", bookerId); return bookingClient.getBookingByBooker(bookerId, bookingState); } diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingDtoResponse.java b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingDtoResponse.java new file mode 100644 index 0000000..08a2799 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingDtoResponse.java @@ -0,0 +1,27 @@ +package ru.practicum.shareit.booking.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import ru.practicum.shareit.item.dto.ItemDtoResponse; +import ru.practicum.shareit.user.dto.UserDtoResponse; + +import java.time.LocalDateTime; + +@Getter +@Setter +@Builder(toBuilder = true) +public class BookingDtoResponse { + private Long id; + + private LocalDateTime start; + + private LocalDateTime end; + + private ItemDtoResponse item; + + private UserDtoResponse booker; + + private String status; +} \ 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 index 66d21fc..c67b0dc 100644 --- a/gateway/src/main/java/ru/practicum/shareit/client/BaseClient.java +++ b/gateway/src/main/java/ru/practicum/shareit/client/BaseClient.java @@ -1,6 +1,7 @@ package ru.practicum.shareit.client; import jakarta.annotation.Nullable; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.*; import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.RestTemplate; @@ -16,86 +17,117 @@ public BaseClient(RestTemplate rest) { this.rest = rest; } - protected ResponseEntity get(String path) { - return get(path, null, null); + protected ResponseEntity get(String path, Class responseType) { + return get(path, null, null, responseType); } - protected ResponseEntity get(String path, long userId) { - return get(path, userId, null); + protected ResponseEntity get(String path, long userId, Class responseType) { + return get(path, userId, null, responseType); } - protected ResponseEntity get(String path, Long userId, @Nullable Map parameters) { - return makeAndSendRequest(HttpMethod.GET, path, userId, parameters, null); + protected ResponseEntity get(String path, Long userId, + @Nullable Map parameters, Class responseType) { + return makeAndSendRequest(HttpMethod.GET, path, userId, parameters, null, responseType); } - protected ResponseEntity post(String path, T body) { - return post(path, null, null, body); + protected ResponseEntity get(String path, ParameterizedTypeReference responseType) { + return get(path, null, null, responseType); } - protected ResponseEntity post(String path, long userId, T body) { - return post(path, userId, null, body); + protected ResponseEntity get(String path, long userId, ParameterizedTypeReference responseType) { + return get(path, userId, null, responseType); } - protected ResponseEntity post(String path, Long userId, - @Nullable Map parameters, T body) { - return makeAndSendRequest(HttpMethod.POST, path, userId, parameters, body); + protected ResponseEntity get(String path, Long userId, + @Nullable Map parameters, + ParameterizedTypeReference responseType) { + return makeAndSendRequest(HttpMethod.GET, path, userId, parameters, null, responseType); } - protected ResponseEntity put(String path, long userId, T body) { - return put(path, userId, null, body); + protected ResponseEntity post(String path, T body, Class responseType) { + return post(path, null, null, body, responseType); } - protected ResponseEntity put(String path, long userId, - @Nullable Map parameters, T body) { - return makeAndSendRequest(HttpMethod.PUT, path, userId, parameters, body); + protected ResponseEntity post(String path, long userId, T body, Class responseType) { + return post(path, userId, null, body, responseType); } - protected ResponseEntity patch(String path, T body) { - return patch(path, null, null, body); + protected ResponseEntity post(String path, Long userId, + @Nullable Map parameters, + T body, Class responseType) { + return makeAndSendRequest(HttpMethod.POST, path, userId, parameters, body, responseType); } - protected ResponseEntity patch(String path, long userId) { - return patch(path, userId, null, null); + protected ResponseEntity patch(String path, T body, Class responseType) { + return patch(path, null, null, body, responseType); } - protected ResponseEntity patch(String path, long userId, T body) { - return patch(path, userId, null, body); + protected ResponseEntity patch(String path, long userId, T body, Class responseType) { + return patch(path, userId, null, body, responseType); } - protected ResponseEntity patch(String path, Long userId, - @Nullable Map parameters, T body) { - return makeAndSendRequest(HttpMethod.PATCH, path, userId, parameters, body); + protected ResponseEntity patch(String path, Long userId, + @Nullable Map parameters, + T body, Class responseType) { + return makeAndSendRequest(HttpMethod.PATCH, path, userId, parameters, body, responseType); } - protected ResponseEntity delete(String path) { - return delete(path, null, null); + protected ResponseEntity delete(String path, Class responseType) { + return delete(path, null, null, responseType); } - protected ResponseEntity delete(String path, long userId) { - return delete(path, userId, null); + protected ResponseEntity delete(String path, long userId, Class responseType) { + return delete(path, userId, null, responseType); } - protected ResponseEntity delete(String path, Long userId, @Nullable Map parameters) { - return makeAndSendRequest(HttpMethod.DELETE, path, userId, parameters, null); + protected ResponseEntity delete(String path, Long userId, + @Nullable Map parameters, + Class responseType) { + return makeAndSendRequest(HttpMethod.DELETE, path, userId, parameters, null, responseType); } - private ResponseEntity makeAndSendRequest(HttpMethod method, String path, Long userId, - @Nullable Map parameters, @Nullable T body) { + private ResponseEntity makeAndSendRequest(HttpMethod method, String path, Long userId, + @Nullable Map parameters, + @Nullable T body, Class responseType) { HttpEntity requestEntity = new HttpEntity<>(body, defaultHeaders(userId)); try { - ResponseEntity shareitServerResponse; + ResponseEntity shareitServerResponse; if (parameters != null) { - shareitServerResponse = rest.exchange(path, method, requestEntity, Object.class, parameters); + shareitServerResponse = rest.exchange(path, method, requestEntity, responseType, parameters); } else { - shareitServerResponse = rest.exchange(path, method, requestEntity, Object.class); + shareitServerResponse = rest.exchange(path, method, requestEntity, responseType); } return prepareGatewayResponse(shareitServerResponse); } catch (HttpStatusCodeException e) { + // Для всех ошибок возвращаем тело ошибки return ResponseEntity.status(e.getStatusCode()) + .body((R) e.getResponseBodyAsString()); + } + } + + private ResponseEntity makeAndSendRequest(HttpMethod method, String path, Long userId, + @Nullable Map parameters, + @Nullable T body, ParameterizedTypeReference responseType) { + + HttpEntity requestEntity = new HttpEntity<>(body, defaultHeaders(userId)); + + try { + ResponseEntity shareitServerResponse; + if (parameters != null) { + shareitServerResponse = rest.exchange(path, method, requestEntity, responseType, parameters); + } else { + shareitServerResponse = rest.exchange(path, method, requestEntity, responseType); + } + + return prepareGatewayResponse(shareitServerResponse); + + } catch (HttpStatusCodeException e) { + // Для ошибок возвращаем тело как строку + return (ResponseEntity) ResponseEntity.status(e.getStatusCode()) .body(e.getResponseBodyAsString()); } } @@ -110,13 +142,12 @@ private HttpHeaders defaultHeaders(Long userId) { return headers; } - private static ResponseEntity prepareGatewayResponse(ResponseEntity response) { + private static ResponseEntity prepareGatewayResponse(ResponseEntity response) { if (response.getStatusCode().is2xxSuccessful()) { return response; } - ResponseEntity.BodyBuilder responseBuilder = ResponseEntity - .status(response.getStatusCode()); + ResponseEntity.BodyBuilder responseBuilder = ResponseEntity.status(response.getStatusCode()); if (response.hasBody()) { return responseBuilder.body(response.getBody()); diff --git a/gateway/src/main/java/ru/practicum/shareit/item/ItemClient.java b/gateway/src/main/java/ru/practicum/shareit/item/ItemClient.java index 865dae1..5e575a9 100644 --- a/gateway/src/main/java/ru/practicum/shareit/item/ItemClient.java +++ b/gateway/src/main/java/ru/practicum/shareit/item/ItemClient.java @@ -3,14 +3,16 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.core.ParameterizedTypeReference; 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.item.comment.dto.CommentDtoRequest; +import ru.practicum.shareit.item.comment.dto.*; import ru.practicum.shareit.item.dto.*; +import java.util.List; import java.util.Map; @Service @@ -28,35 +30,43 @@ public ItemClient(@Value("${shareit-server.url}") String serverUrl, RestTemplate ); } - public ResponseEntity getAllItemsByOwner(Long userId) { - return get("", userId); + public ResponseEntity> getAllItemsByOwner(Long userId) { + ParameterizedTypeReference> typeRef = + new ParameterizedTypeReference>() {}; + return get("", userId, typeRef); } - public ResponseEntity getItemById(Long userId, Long itemId) { - return get("/" + itemId, userId); + public ResponseEntity getItemById(Long userId, Long itemId) { + return get("/" + itemId, userId, ItemFullDtoResponse.class); } - public ResponseEntity createItem(Long userId, ItemDtoRequest itemDtoRequest) { - return post("", userId, itemDtoRequest); + public ResponseEntity createItem(Long userId, ItemDtoRequest itemDtoRequest) { + return post("", userId, itemDtoRequest, ItemDtoResponse.class); } - public ResponseEntity updateItem(Long userId, Long itemId, ItemUpdateDtoRequest itemUpdateDtoRequest) { - return patch("/" + itemId, userId, itemUpdateDtoRequest); + public ResponseEntity updateItem(Long userId, Long itemId, + ItemUpdateDtoRequest itemUpdateDtoRequest) { + return patch("/" + itemId, userId, itemUpdateDtoRequest, ItemDtoResponse.class); } - public void deleteItem(Long userId, Long itemId) { - delete("/" + itemId, userId); + public ResponseEntity deleteItem(Long userId, Long itemId) { + return delete("/" + itemId, userId, Void.class); } - public ResponseEntity searchByText(Long userId, String text) { - return get("/search?text={text}", userId, Map.of("text", text)); + public ResponseEntity> searchByText(Long userId, String text) { + ParameterizedTypeReference> typeRef = + new ParameterizedTypeReference>() {}; + return get("/search?text={text}", userId, Map.of("text", text), typeRef); } - public ResponseEntity addComment(Long userId, Long itemId, CommentDtoRequest commentDtoRequest) { - return post("/" + itemId + "/comment", userId, commentDtoRequest); + public ResponseEntity addComment(Long userId, Long itemId, + CommentDtoRequest commentDtoRequest) { + return post("/" + itemId + "/comment", userId, commentDtoRequest, CommentDtoResponse.class); } - public ResponseEntity getAllComments(Long userId, Long itemId) { - return get("/" + itemId + "/comment", userId); + public ResponseEntity> getAllComments(Long userId, Long itemId) { + ParameterizedTypeReference> typeRef = + new ParameterizedTypeReference>() {}; + return get("/" + itemId + "/comment", userId, typeRef); } } \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/item/ItemController.java b/gateway/src/main/java/ru/practicum/shareit/item/ItemController.java index c1de550..c03b270 100644 --- a/gateway/src/main/java/ru/practicum/shareit/item/ItemController.java +++ b/gateway/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -8,9 +8,11 @@ import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import ru.practicum.shareit.item.comment.dto.CommentDtoRequest; +import ru.practicum.shareit.item.comment.dto.*; import ru.practicum.shareit.item.dto.*; +import java.util.List; + @Slf4j @Validated @RestController @@ -21,58 +23,61 @@ public class ItemController { private final ItemClient itemClient; @GetMapping - public ResponseEntity getAllItemsByOwner(@RequestHeader("X-Sharer-User-Id") @Positive Long userId) { + public ResponseEntity> getAllItemsByOwner( + @RequestHeader("X-Sharer-User-Id") @Positive Long userId) { log.info("GET / items / user {}", userId); return itemClient.getAllItemsByOwner(userId); } @GetMapping("/{itemId}") - public ResponseEntity getItemById(@RequestHeader("X-Sharer-User-Id") @Positive Long userId, - @PathVariable @Positive Long itemId) { + public ResponseEntity getItemById(@RequestHeader("X-Sharer-User-Id") @Positive Long userId, + @PathVariable @Positive Long itemId) { log.info("GET / items {} / user {}", itemId, userId); return itemClient.getItemById(userId, itemId); } @GetMapping("/search") - public ResponseEntity searchByText(@RequestHeader("X-Sharer-User-Id") @Positive Long userId, - @RequestParam("text") @NotBlank String searchText) { + public ResponseEntity> searchByText( + @RequestHeader("X-Sharer-User-Id") @Positive Long userId, + @RequestParam("text") @NotBlank String searchText) { log.info("GET / search / {}", searchText); return itemClient.searchByText(userId, searchText); } @PostMapping - public ResponseEntity create(@RequestHeader("X-Sharer-User-Id") @Positive Long userId, - @RequestBody @Valid ItemDtoRequest itemDtoRequest) { + public ResponseEntity create(@RequestHeader("X-Sharer-User-Id") @Positive Long userId, + @RequestBody @Valid ItemDtoRequest itemDtoRequest) { log.info("POST / items {} / user {}", itemDtoRequest.getName(), userId); return itemClient.createItem(userId, itemDtoRequest); } @PatchMapping("/{itemId}") - public ResponseEntity update(@RequestHeader("X-Sharer-User-Id") @Positive Long ownerId, - @PathVariable @Positive Long itemId, - @RequestBody @Valid ItemUpdateDtoRequest itemUpdateDtoRequest) { + public ResponseEntity update(@RequestHeader("X-Sharer-User-Id") @Positive Long ownerId, + @PathVariable @Positive Long itemId, + @RequestBody @Valid ItemUpdateDtoRequest itemUpdateDtoRequest) { log.info("PATCH / items {} / user {}", itemId, ownerId); return itemClient.updateItem(ownerId, itemId, itemUpdateDtoRequest); } @DeleteMapping("/{itemId}") - public void delete(@RequestHeader("X-Sharer-User-Id") @Positive Long ownerId, - @PathVariable @Positive Long itemId) { + public ResponseEntity delete(@RequestHeader("X-Sharer-User-Id") @Positive Long ownerId, + @PathVariable @Positive Long itemId) { log.info("DELETE / items {} / user {}", itemId, ownerId); - itemClient.deleteItem(ownerId, itemId); + return itemClient.deleteItem(ownerId, itemId); } @PostMapping("/{itemId}/comment") - public ResponseEntity addComment(@RequestHeader("X-Sharer-User-Id") @Positive Long userId, - @PathVariable @Positive Long itemId, - @RequestBody @Valid CommentDtoRequest commentDtoRequest) { + public ResponseEntity addComment(@RequestHeader("X-Sharer-User-Id") @Positive Long userId, + @PathVariable @Positive Long itemId, + @RequestBody @Valid CommentDtoRequest commentDtoRequest) { log.info("POST / comment / item {}", itemId); return itemClient.addComment(userId, itemId, commentDtoRequest); } @GetMapping("/{itemId}/comment") - public ResponseEntity getComments(@RequestHeader("X-Sharer-User-Id") @Positive Long userId, - @PathVariable @Positive Long itemId) { + public ResponseEntity> getComments( + @RequestHeader("X-Sharer-User-Id") @Positive Long userId, + @PathVariable @Positive Long itemId) { log.info("GET / comment / item {}", itemId); return itemClient.getAllComments(userId, itemId); } diff --git a/gateway/src/main/java/ru/practicum/shareit/item/comment/dto/CommentDtoRequest.java b/gateway/src/main/java/ru/practicum/shareit/item/comment/dto/CommentDtoRequest.java index 57b5e57..03bcdf9 100644 --- a/gateway/src/main/java/ru/practicum/shareit/item/comment/dto/CommentDtoRequest.java +++ b/gateway/src/main/java/ru/practicum/shareit/item/comment/dto/CommentDtoRequest.java @@ -10,5 +10,5 @@ public class CommentDtoRequest { @NotBlank - String text; + private String text; } \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/item/comment/dto/CommentDtoResponse.java b/gateway/src/main/java/ru/practicum/shareit/item/comment/dto/CommentDtoResponse.java new file mode 100644 index 0000000..572b886 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/comment/dto/CommentDtoResponse.java @@ -0,0 +1,22 @@ +package ru.practicum.shareit.item.comment.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.*; + +import java.time.Instant; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CommentDtoResponse { + + private Long id; + + private String text; + + private String authorName; + + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss", shape = JsonFormat.Shape.STRING, timezone = "UTC") + private Instant created; +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemDtoResponse.java b/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemDtoResponse.java new file mode 100644 index 0000000..9595ced --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemDtoResponse.java @@ -0,0 +1,16 @@ +package ru.practicum.shareit.item.dto; + +import lombok.*; + +@Getter +@Setter +@Builder(toBuilder = true) +public class ItemDtoResponse { + + private Long id; + private String name; + private String description; + private Boolean available; + private Long ownerId; + private Long requestId; +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemFullDtoResponse.java b/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemFullDtoResponse.java new file mode 100644 index 0000000..7944471 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemFullDtoResponse.java @@ -0,0 +1,23 @@ +package ru.practicum.shareit.item.dto; + +import lombok.*; +import ru.practicum.shareit.booking.dto.BookingDtoResponse; +import ru.practicum.shareit.item.comment.dto.CommentDtoResponse; + +import java.util.List; + +@Getter +@Setter +@Builder(toBuilder = true) +public class ItemFullDtoResponse { + + private Long id; + private String name; + private String description; + private Boolean available; + private Long ownerId; + private Long requestId; + private BookingDtoResponse lastBooking; + private BookingDtoResponse nextBooking; + private List comments; +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestClient.java b/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestClient.java index c4e6c18..00e416c 100644 --- a/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestClient.java +++ b/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestClient.java @@ -3,12 +3,15 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.core.ParameterizedTypeReference; 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.ItemRequestDtoRequest; +import ru.practicum.shareit.request.dto.*; + +import java.util.List; @Service public class ItemRequestClient extends BaseClient { @@ -25,19 +28,23 @@ public ItemRequestClient(@Value("${shareit-server.url}") String serverUrl, RestT ); } - public ResponseEntity createRequest(Long userId, ItemRequestDtoRequest requestDtoRequest) { - return post("", userId, requestDtoRequest); + public ResponseEntity createRequest(Long userId, ItemRequestDtoRequest requestDtoRequest) { + return post("", userId, requestDtoRequest, ItemRequestDtoResponse.class); } - public ResponseEntity getAllRequests(Long userId) { - return get("/all", userId); + public ResponseEntity> getAllRequests(Long userId) { + ParameterizedTypeReference> typeRef = + new ParameterizedTypeReference>() {}; + return get("/all", userId, typeRef); } - public ResponseEntity getRequestByUser(Long userId) { - return get("", userId); + public ResponseEntity> getRequestByUser(Long userId) { + ParameterizedTypeReference> typeRef = + new ParameterizedTypeReference>() {}; + return get("", userId, typeRef); } - public ResponseEntity getRequestById(Long userId, Long requestId) { - return get("/" + requestId, userId); + public ResponseEntity getRequestById(Long userId, Long requestId) { + return get("/" + requestId, userId, ItemRequestDtoResponse.class); } } \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestController.java b/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestController.java index 5a1d397..f7f5e26 100644 --- a/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestController.java +++ b/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestController.java @@ -7,7 +7,9 @@ import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import ru.practicum.shareit.request.dto.ItemRequestDtoRequest; +import ru.practicum.shareit.request.dto.*; + +import java.util.List; @Slf4j @Validated @@ -19,28 +21,32 @@ public class ItemRequestController { private final ItemRequestClient itemRequestClient; @PostMapping - public ResponseEntity create(@RequestHeader("X-Sharer-User-Id") @Positive Long userId, - @RequestBody @Valid ItemRequestDtoRequest itemRequestDtoRequest) { + public ResponseEntity create( + @RequestHeader("X-Sharer-User-Id") @Positive Long userId, + @RequestBody @Valid ItemRequestDtoRequest itemRequestDtoRequest) { log.info("POST / requests | userId: {} | Создание заявки: '{}'", userId, itemRequestDtoRequest.getDescription()); return itemRequestClient.createRequest(userId, itemRequestDtoRequest); } @GetMapping("/all") - public ResponseEntity getAllRequests(@RequestHeader("X-Sharer-User-Id") @Positive Long userId) { + public ResponseEntity> getAllRequests( + @RequestHeader("X-Sharer-User-Id") @Positive Long userId) { log.info("GET / requests"); return itemRequestClient.getAllRequests(userId); } @GetMapping - public ResponseEntity getRequestByUser(@RequestHeader("X-Sharer-User-Id") @Positive Long userId) { + public ResponseEntity> getRequestByUser( + @RequestHeader("X-Sharer-User-Id") @Positive Long userId) { log.info("GET / requests / requestor {}", userId); return itemRequestClient.getRequestByUser(userId); } @GetMapping("/{requestId}") - public ResponseEntity getRequestById(@RequestHeader("X-Sharer-User-Id") @Positive Long userId, - @PathVariable @Positive Long requestId) { + public ResponseEntity getRequestById( + @RequestHeader("X-Sharer-User-Id") @Positive Long userId, + @PathVariable @Positive Long requestId) { log.info("GET / request {} / user {}", requestId, userId); return itemRequestClient.getRequestById(userId, requestId); } diff --git a/gateway/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDtoResponse.java b/gateway/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDtoResponse.java new file mode 100644 index 0000000..6a8aeec --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDtoResponse.java @@ -0,0 +1,22 @@ +package ru.practicum.shareit.request.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.*; +import ru.practicum.shareit.item.dto.ItemDtoResponse; + +import java.time.LocalDateTime; +import java.util.Collection; + +@Getter +@Setter +@Builder(toBuilder = true) +public class ItemRequestDtoResponse { + private Long id; + + private String description; + + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss", shape = JsonFormat.Shape.STRING, timezone = "UTC") + private LocalDateTime created; + + private Collection items; +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/user/UserClient.java b/gateway/src/main/java/ru/practicum/shareit/user/UserClient.java index c8793c7..9c89c10 100644 --- a/gateway/src/main/java/ru/practicum/shareit/user/UserClient.java +++ b/gateway/src/main/java/ru/practicum/shareit/user/UserClient.java @@ -3,6 +3,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.ResponseEntity; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.stereotype.Service; @@ -10,6 +11,8 @@ import ru.practicum.shareit.client.BaseClient; import ru.practicum.shareit.user.dto.*; +import java.util.List; + @Service public class UserClient extends BaseClient { @@ -25,23 +28,25 @@ public UserClient(@Value("${shareit-server.url}") String serverUrl, RestTemplate ); } - public ResponseEntity getAllUsers() { - return get(""); + public ResponseEntity> getAllUsers() { + ParameterizedTypeReference> typeRef = + new ParameterizedTypeReference>() {}; + return get("", typeRef); } - public ResponseEntity getUserById(Long userId) { - return get("/" + userId); + public ResponseEntity getUserById(Long userId) { + return get("/" + userId, UserDtoResponse.class); } - public ResponseEntity createUser(UserDtoRequest userDtoRequest) { - return post("", userDtoRequest); + public ResponseEntity createUser(UserDtoRequest userDtoRequest) { + return post("", userDtoRequest, UserDtoResponse.class); } - public ResponseEntity updateUser(Long userId, UserUpdateDtoRequest userUpdateDtoRequest) { - return patch("/" + userId, userUpdateDtoRequest); + public ResponseEntity updateUser(Long userId, UserUpdateDtoRequest userUpdateDtoRequest) { + return patch("/" + userId, userUpdateDtoRequest, UserDtoResponse.class); } - public void deleteUser(Long userId) { - delete("/" + userId); + public ResponseEntity deleteUser(Long userId) { + return delete("/" + userId, Void.class); } } \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/user/UserController.java b/gateway/src/main/java/ru/practicum/shareit/user/UserController.java index a3dd4e4..cc84208 100644 --- a/gateway/src/main/java/ru/practicum/shareit/user/UserController.java +++ b/gateway/src/main/java/ru/practicum/shareit/user/UserController.java @@ -9,6 +9,8 @@ import org.springframework.web.bind.annotation.*; import ru.practicum.shareit.user.dto.*; +import java.util.List; + @Slf4j @Validated @RestController @@ -19,33 +21,33 @@ public class UserController { private final UserClient userClient; @GetMapping - public ResponseEntity getAllUsers() { + public ResponseEntity> getAllUsers() { log.info("GET / users"); return userClient.getAllUsers(); } @GetMapping("/{userId}") - public ResponseEntity getByUserId(@PathVariable @Positive Long userId) { + public ResponseEntity getByUserId(@PathVariable @Positive Long userId) { log.info("GET / users / {}", userId); return userClient.getUserById(userId); } @PostMapping - public ResponseEntity create(@RequestBody @Valid UserDtoRequest userDtoRequest) { + public ResponseEntity create(@RequestBody @Valid UserDtoRequest userDtoRequest) { log.info("POST / users / {} / {}", userDtoRequest.getName(), userDtoRequest.getEmail()); return userClient.createUser(userDtoRequest); } @PatchMapping("/{userId}") - public ResponseEntity update(@PathVariable @Positive Long userId, - @RequestBody @Valid UserUpdateDtoRequest userUpdateDtoRequest) { + public ResponseEntity update(@PathVariable @Positive Long userId, + @RequestBody @Valid UserUpdateDtoRequest userUpdateDtoRequest) { log.info("PATCH / users / {}", userId); return userClient.updateUser(userId, userUpdateDtoRequest); } @DeleteMapping("/{userId}") - public void delete(@PathVariable @Positive Long userId) { + public ResponseEntity delete(@PathVariable @Positive Long userId) { log.info("DELETE / users / {}", userId); - userClient.deleteUser(userId); + return userClient.deleteUser(userId); } } \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/user/dto/UserDtoResponse.java b/gateway/src/main/java/ru/practicum/shareit/user/dto/UserDtoResponse.java new file mode 100644 index 0000000..b4621bd --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/user/dto/UserDtoResponse.java @@ -0,0 +1,12 @@ +package ru.practicum.shareit.user.dto; + +import lombok.*; + +@Getter +@Setter +@Builder(toBuilder = true) +public class UserDtoResponse { + private Long id; + private String name; + private String email; +} \ No newline at end of file diff --git a/gateway/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java b/gateway/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java index 93d2560..54d00cc 100644 --- a/gateway/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java +++ b/gateway/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java @@ -353,6 +353,7 @@ private void setupDefaultSuccessfulMocks() { .thenReturn(ResponseEntity.status(HttpStatus.OK).build()); // Для void метода deleteItem - Mockito.doNothing().when(itemClient).deleteItem(anyLong(), anyLong()); + when(itemClient.deleteItem(anyLong(), anyLong())) + .thenReturn(ResponseEntity.status(HttpStatus.OK).build()); } } \ No newline at end of file diff --git a/gateway/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java b/gateway/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java index cc0956d..7c62778 100644 --- a/gateway/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java +++ b/gateway/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java @@ -207,6 +207,7 @@ public void getRequestById_WithoutUserIdHeader_ReturnsBadRequest() throws Except verify(itemRequestClient, never()).getRequestById(anyLong(), anyLong()); } + // Вспомогательный метод для настройки мок-объектов private void setupDefaultSuccessfulMocks() { when(itemRequestClient.createRequest(anyLong(), any(ItemRequestDtoRequest.class))) .thenReturn(ResponseEntity.status(HttpStatus.CREATED).build()); diff --git a/gateway/src/test/java/ru/practicum/shareit/user/UserControllerTest.java b/gateway/src/test/java/ru/practicum/shareit/user/UserControllerTest.java index 8c8d238..c4a6fa2 100644 --- a/gateway/src/test/java/ru/practicum/shareit/user/UserControllerTest.java +++ b/gateway/src/test/java/ru/practicum/shareit/user/UserControllerTest.java @@ -218,6 +218,7 @@ private void setupDefaultSuccessfulMocks() { .thenReturn(ResponseEntity.status(HttpStatus.OK).build()); // Для void метода deleteUser - Mockito.doNothing().when(userClient).deleteUser(anyLong()); + when(userClient.deleteUser(anyLong())) + .thenReturn(ResponseEntity.status(HttpStatus.OK).build()); } } \ No newline at end of file From a168c1df985c1339dc183aefd0476205837b73cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9A=D0=B0?= =?UTF-8?q?=D0=BD?= Date: Fri, 19 Sep 2025 00:04:25 +0300 Subject: [PATCH 6/6] =?UTF-8?q?fix:=20=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=87=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D1=80=D0=B5=D0=B2=D1=8C=D1=8E=D0=B5=D1=80=D0=B0?= =?UTF-8?q?=20=D0=B8=20CodeStyle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ru/practicum/shareit/booking/dto/BookingDtoResponse.java | 1 - 1 file changed, 1 deletion(-) diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingDtoResponse.java b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingDtoResponse.java index 08a2799..65c1b6f 100644 --- a/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingDtoResponse.java +++ b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingDtoResponse.java @@ -1,6 +1,5 @@ package ru.practicum.shareit.booking.dto; -import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Builder; import lombok.Getter; import lombok.Setter;