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..40088d8
--- /dev/null
+++ b/gateway/src/main/java/ru/practicum/shareit/booking/BookingClient.java
@@ -0,0 +1,56 @@
+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.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.*;
+import ru.practicum.shareit.client.BaseClient;
+
+import java.util.List;
+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, BookingDtoResponse.class);
+ }
+
+ public ResponseEntity getBooking(Long userId, Long bookingId) {
+ return get("/" + bookingId, userId, BookingDtoResponse.class);
+ }
+
+ 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) {
+ 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) {
+ return patch("/" + bookingId + "?approved={approved}", userId,
+ 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
new file mode 100644
index 0000000..cba3972
--- /dev/null
+++ b/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java
@@ -0,0 +1,60 @@
+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.*;
+
+import java.util.List;
+
+@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/BookingDtoResponse.java b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingDtoResponse.java
new file mode 100644
index 0000000..65c1b6f
--- /dev/null
+++ b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingDtoResponse.java
@@ -0,0 +1,26 @@
+package ru.practicum.shareit.booking.dto;
+
+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/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..c67b0dc
--- /dev/null
+++ b/gateway/src/main/java/ru/practicum/shareit/client/BaseClient.java
@@ -0,0 +1,158 @@
+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;
+
+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, Class responseType) {
+ return get(path, null, null, responseType);
+ }
+
+ 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, Class responseType) {
+ return makeAndSendRequest(HttpMethod.GET, path, userId, parameters, null, responseType);
+ }
+
+ protected ResponseEntity get(String path, ParameterizedTypeReference responseType) {
+ return get(path, null, null, responseType);
+ }
+
+ protected ResponseEntity get(String path, long userId, ParameterizedTypeReference responseType) {
+ return get(path, userId, null, responseType);
+ }
+
+ protected ResponseEntity get(String path, Long userId,
+ @Nullable Map parameters,
+ ParameterizedTypeReference responseType) {
+ return makeAndSendRequest(HttpMethod.GET, path, userId, parameters, null, responseType);
+ }
+
+ protected ResponseEntity post(String path, T body, Class responseType) {
+ return post(path, null, null, body, responseType);
+ }
+
+ protected ResponseEntity post(String path, long userId, T body, Class responseType) {
+ return post(path, userId, null, body, responseType);
+ }
+
+ 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, T body, Class responseType) {
+ return patch(path, null, null, body, responseType);
+ }
+
+ 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, Class responseType) {
+ return makeAndSendRequest(HttpMethod.PATCH, path, userId, parameters, body, responseType);
+ }
+
+ protected ResponseEntity delete(String path, Class responseType) {
+ return delete(path, null, null, responseType);
+ }
+
+ 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,
+ 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, Class 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.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());
+ }
+ }
+
+ 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..5e575a9
--- /dev/null
+++ b/gateway/src/main/java/ru/practicum/shareit/item/ItemClient.java
@@ -0,0 +1,72 @@
+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.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.*;
+import ru.practicum.shareit.item.dto.*;
+
+import java.util.List;
+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) {
+ ParameterizedTypeReference> typeRef =
+ new ParameterizedTypeReference>() {};
+ return get("", userId, typeRef);
+ }
+
+ public ResponseEntity getItemById(Long userId, Long itemId) {
+ return get("/" + itemId, userId, ItemFullDtoResponse.class);
+ }
+
+ 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, ItemDtoResponse.class);
+ }
+
+ public ResponseEntity deleteItem(Long userId, Long itemId) {
+ return delete("/" + itemId, userId, Void.class);
+ }
+
+ 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, CommentDtoResponse.class);
+ }
+
+ 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
new file mode 100644
index 0000000..c03b270
--- /dev/null
+++ b/gateway/src/main/java/ru/practicum/shareit/item/ItemController.java
@@ -0,0 +1,84 @@
+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.*;
+import ru.practicum.shareit.item.dto.*;
+
+import java.util.List;
+
+@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 ResponseEntity delete(@RequestHeader("X-Sharer-User-Id") @Positive Long ownerId,
+ @PathVariable @Positive Long itemId) {
+ log.info("DELETE / items {} / user {}", itemId, ownerId);
+ 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) {
+ 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..03bcdf9
--- /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
+ 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/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/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/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..00e416c
--- /dev/null
+++ b/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestClient.java
@@ -0,0 +1,50 @@
+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.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.*;
+
+import java.util.List;
+
+@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, ItemRequestDtoResponse.class);
+ }
+
+ public ResponseEntity> getAllRequests(Long userId) {
+ ParameterizedTypeReference> typeRef =
+ new ParameterizedTypeReference>() {};
+ return get("/all", userId, typeRef);
+ }
+
+ public ResponseEntity> getRequestByUser(Long userId) {
+ ParameterizedTypeReference> typeRef =
+ new ParameterizedTypeReference>() {};
+ return get("", userId, typeRef);
+ }
+
+ 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
new file mode 100644
index 0000000..f7f5e26
--- /dev/null
+++ b/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestController.java
@@ -0,0 +1,53 @@
+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.*;
+
+import java.util.List;
+
+@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/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
new file mode 100644
index 0000000..9c89c10
--- /dev/null
+++ b/gateway/src/main/java/ru/practicum/shareit/user/UserClient.java
@@ -0,0 +1,52 @@
+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.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.user.dto.*;
+
+import java.util.List;
+
+@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() {
+ ParameterizedTypeReference> typeRef =
+ new ParameterizedTypeReference>() {};
+ return get("", typeRef);
+ }
+
+ public ResponseEntity getUserById(Long userId) {
+ return get("/" + userId, UserDtoResponse.class);
+ }
+
+ public ResponseEntity createUser(UserDtoRequest userDtoRequest) {
+ return post("", userDtoRequest, UserDtoResponse.class);
+ }
+
+ public ResponseEntity updateUser(Long userId, UserUpdateDtoRequest userUpdateDtoRequest) {
+ return patch("/" + userId, userUpdateDtoRequest, UserDtoResponse.class);
+ }
+
+ 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
new file mode 100644
index 0000000..cc84208
--- /dev/null
+++ b/gateway/src/main/java/ru/practicum/shareit/user/UserController.java
@@ -0,0 +1,53 @@
+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.*;
+
+import java.util.List;
+
+@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 ResponseEntity delete(@PathVariable @Positive Long userId) {
+ log.info("DELETE / users / {}", userId);
+ return 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/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/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..3bfe6a5
--- /dev/null
+++ b/gateway/src/test/java/ru/practicum/shareit/booking/BookingDtoRequestJsonTest.java
@@ -0,0 +1,100 @@
+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..54d00cc
--- /dev/null
+++ b/gateway/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java
@@ -0,0 +1,359 @@
+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
+ 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/item/ItemDtoRequestJsonTest.java b/gateway/src/test/java/ru/practicum/shareit/item/ItemDtoRequestJsonTest.java
new file mode 100644
index 0000000..472284d
--- /dev/null
+++ b/gateway/src/test/java/ru/practicum/shareit/item/ItemDtoRequestJsonTest.java
@@ -0,0 +1,75 @@
+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..d513590
--- /dev/null
+++ b/gateway/src/test/java/ru/practicum/shareit/item/ItemUpdateDtoRequestJsonTest.java
@@ -0,0 +1,99 @@
+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..7c62778
--- /dev/null
+++ b/gateway/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java
@@ -0,0 +1,221 @@
+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..21f1aef
--- /dev/null
+++ b/gateway/src/test/java/ru/practicum/shareit/request/ItemRequestDtoRequestJsonTest.java
@@ -0,0 +1,74 @@
+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..c4a6fa2
--- /dev/null
+++ b/gateway/src/test/java/ru/practicum/shareit/user/UserControllerTest.java
@@ -0,0 +1,224 @@
+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
+ when(userClient.deleteUser(anyLong()))
+ .thenReturn(ResponseEntity.status(HttpStatus.OK).build());
+ }
+}
\ 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..620c0c8
--- /dev/null
+++ b/gateway/src/test/java/ru/practicum/shareit/user/UserDtoRequestJsonTest.java
@@ -0,0 +1,60 @@
+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..ee35a7d
--- /dev/null
+++ b/gateway/src/test/java/ru/practicum/shareit/user/UserUpdateDtoRequestJsonTest.java
@@ -0,0 +1,101 @@
+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
-
-
-
-
-
- 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 93%
rename from src/main/java/ru/practicum/shareit/user/UserServiceImpl.java
rename to server/src/main/java/ru/practicum/shareit/user/UserServiceImpl.java
index 74c0012..b3ac1cb 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;
@@ -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..e2b512e
--- /dev/null
+++ b/server/src/test/java/ru/practicum/shareit/request/ItemRequestServiceIntegrationTest.java
@@ -0,0 +1,150 @@
+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.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