diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..5c8152e --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,40 @@ + +services: + gateway: + build: gateway + image: shareit-gateway + container_name: shareit-gateway + ports: + - "8080:8080" + depends_on: + - server + environment: + - SHAREIT_SERVER_URL=http://server:9090 + + server: + build: server + image: shareit-server + container_name: shareit-server + ports: + - "9090:9090" + depends_on: + - db + environment: + - SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/shareit + - SPRING_DATASOURCE_USERNAME=shareit + - SPRING_DATASOURCE_PASSWORD=shareit + + db: + image: postgres:16.1 + container_name: postgres + ports: + - "6541:5432" + environment: + - POSTGRES_PASSWORD=shareit + - POSTGRES_USER=shareit + - POSTGRES_DB=shareit + healthcheck: + test: pg_isready -q -d $$POSTGRES_DB -U $$POSTGRES_USER + timeout: 5s + interval: 5s + retries: 10 diff --git a/gateway/Dockerfile b/gateway/Dockerfile new file mode 100644 index 0000000..0ff1817 --- /dev/null +++ b/gateway/Dockerfile @@ -0,0 +1,5 @@ +FROM eclipse-temurin:21-jre-jammy +VOLUME /tmp +ARG JAR_FILE=target/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"] \ No newline at end of file diff --git a/gateway/pom.xml b/gateway/pom.xml new file mode 100644 index 0000000..dacdef2 --- /dev/null +++ b/gateway/pom.xml @@ -0,0 +1,91 @@ + + + 4.0.0 + + ru.practicum + shareit + 0.0.1-SNAPSHOT + + + 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.apache.maven.plugins + maven-checkstyle-plugin + ${maven-checkstyle-plugin.version} + + + true + true + true + checkstyle.xml + + + + + + check + + compile + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/OnCreate.java b/gateway/src/main/java/ru/practicum/shareit/OnCreate.java new file mode 100644 index 0000000..24698fd --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/OnCreate.java @@ -0,0 +1,4 @@ +package ru.practicum.shareit; + +public interface OnCreate { +} diff --git a/gateway/src/main/java/ru/practicum/shareit/OnUpdate.java b/gateway/src/main/java/ru/practicum/shareit/OnUpdate.java new file mode 100644 index 0000000..4d6a567 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/OnUpdate.java @@ -0,0 +1,4 @@ +package ru.practicum.shareit; + +public interface OnUpdate { +} 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..4cbc16c --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/ShareItGateway.java @@ -0,0 +1,12 @@ +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/client/BaseClient.java b/gateway/src/main/java/ru/practicum/shareit/client/BaseClient.java new file mode 100644 index 0000000..3a3680d --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/client/BaseClient.java @@ -0,0 +1,141 @@ +package ru.practicum.shareit.client; + +import jakarta.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.*; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.lang.Nullable; +import org.springframework.web.client.HttpStatusCodeException; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.DefaultUriBuilderFactory; + +import java.util.List; +import java.util.Map; + +public class BaseClient { + + @Value("${shareit-server.url}") + private String serverUrl; + + private final String prefix; + + protected RestTemplate rest; + + @PostConstruct + public void init() { + RestTemplateBuilder builder = new RestTemplateBuilder(); + + this.rest = builder.uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl + prefix)) + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) + .build(); + } + + public BaseClient(String prefix) { + this.prefix = prefix; + } + + protected ResponseEntity get(String path) { + return get(path, null, null); + } + + protected ResponseEntity get(String path, long userId) { + return get(path, userId, null); + } + + protected ResponseEntity get(String path, Long userId, @Nullable Map parameters) { + return makeAndSendRequest(HttpMethod.GET, path, userId, parameters, null); + } + + protected ResponseEntity post(String path, T body) { + return post(path, null, null, body); + } + + protected ResponseEntity post(String path, long userId, T body) { + return post(path, userId, null, body); + } + + protected ResponseEntity post(String path, Long userId, @Nullable Map parameters, T body) { + return makeAndSendRequest(HttpMethod.POST, path, userId, parameters, body); + } + + protected ResponseEntity put(String path, long userId, T body) { + return put(path, userId, null, body); + } + + protected ResponseEntity put(String path, long userId, @Nullable Map parameters, T body) { + return makeAndSendRequest(HttpMethod.PUT, path, userId, parameters, body); + } + + protected ResponseEntity patch(String path, T body) { + return patch(path, null, null, body); + } + + protected ResponseEntity patch(String path, long userId) { + return patch(path, userId, null, null); + } + + protected ResponseEntity patch(String path, long userId, T body) { + return patch(path, userId, null, body); + } + + protected ResponseEntity patch(String path, long userId, Map parameters) { + return patch(path, userId, parameters, null); + } + + protected ResponseEntity patch(String path, Long userId, @Nullable Map parameters, T body) { + return makeAndSendRequest(HttpMethod.PATCH, path, userId, parameters, body); + } + + protected ResponseEntity delete(String path) { + return delete(path, null, null); + } + + protected ResponseEntity delete(String path, long userId) { + return delete(path, userId, null); + } + + protected ResponseEntity delete(String path, Long userId, @Nullable Map parameters) { + return makeAndSendRequest(HttpMethod.DELETE, path, userId, parameters, null); + } + + private ResponseEntity makeAndSendRequest(HttpMethod method, String path, Long userId, @Nullable Map parameters, @Nullable T body) { + HttpEntity requestEntity = new HttpEntity<>(body, defaultHeaders(userId)); + + ResponseEntity shareitServerResponse; + try { + if (parameters != null) { + shareitServerResponse = rest.exchange(path, method, requestEntity, Object.class, parameters); + } else { + shareitServerResponse = rest.exchange(path, method, requestEntity, Object.class); + } + } catch (HttpStatusCodeException e) { + return ResponseEntity.status(e.getStatusCode()).body(e.getResponseBodyAsByteArray()); + } + return prepareGatewayResponse(shareitServerResponse); + } + + private HttpHeaders defaultHeaders(Long userId) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setAccept(List.of(MediaType.APPLICATION_JSON)); + if (userId != null) { + headers.set("X-Sharer-User-Id", String.valueOf(userId)); + } + return headers; + } + + private static ResponseEntity prepareGatewayResponse(ResponseEntity response) { + if (response.getStatusCode().is2xxSuccessful()) { + return response; + } + + ResponseEntity.BodyBuilder responseBuilder = ResponseEntity.status(response.getStatusCode()); + + if (response.hasBody()) { + return responseBuilder.body(response.getBody()); + } + + return responseBuilder.build(); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/client/BookingClient.java b/gateway/src/main/java/ru/practicum/shareit/client/BookingClient.java new file mode 100644 index 0000000..bddf785 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/client/BookingClient.java @@ -0,0 +1,37 @@ +package ru.practicum.shareit.client; + +import java.util.Collections; +import org.springframework.stereotype.Component; + +import ru.practicum.shareit.dto.BookingDto; +import ru.practicum.shareit.dto.BookingState; + +@Component +public class BookingClient extends BaseClient { + + public BookingClient() { + super("/bookings"); + } + + public Object getBooking(long userId, Long bookingId) { + return get("/" + bookingId, userId); + } + + public Object createBooking(Long userId, BookingDto bookingDto) { + return post("", userId, bookingDto); + } + + public Object updateBookingStatus(Long userId, Long bookingId, Boolean approved) { + return patch("/" + bookingId + "?approved={approved}", + userId, + Collections.singletonMap("approved", approved)); + } + + public Object findByBookerAndState(Long bookerId, BookingState state) { + return get("?state={state}", bookerId, Collections.singletonMap("state", state)); + } + + public Object findByOwnerAndState(Long ownerId, BookingState state) { + return get("/owner?state={state}", ownerId, Collections.singletonMap("state", state)); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/client/ItemClient.java b/gateway/src/main/java/ru/practicum/shareit/client/ItemClient.java new file mode 100644 index 0000000..a9f3b52 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/client/ItemClient.java @@ -0,0 +1,43 @@ +package ru.practicum.shareit.client; + +import org.springframework.stereotype.Component; +import ru.practicum.shareit.dto.CommentDto; +import ru.practicum.shareit.dto.ItemDto; + +import java.util.Collections; + +@Component +public class ItemClient extends BaseClient { + + public ItemClient() { + super("/items"); + } + + public Object addItem(ItemDto itemDto, Long ownerId) { + return post("", ownerId, itemDto); + } + + public Object updateItem(long itemId, ItemDto itemDto, long ownerId) { + return patch("/" + itemId, ownerId, itemDto); + } + + public Object getItem(long itemId) { + return get("/" + itemId); + } + + public Object getAllOwnerItems(long ownerId) { + return get("", ownerId); + } + + public Object getNecessaryItem(String text) { + if (text == null || text.isBlank()) { + return get("/search", null, Collections.emptyMap()); + } + + return get("/search?text={text}", null, Collections.singletonMap("text", text)); + } + + public Object addComment(long itemId, long ownerId, CommentDto comment) { + return post("/" + itemId + "/comment", ownerId, comment); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/client/ItemRequestClient.java b/gateway/src/main/java/ru/practicum/shareit/client/ItemRequestClient.java new file mode 100644 index 0000000..6cc3654 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/client/ItemRequestClient.java @@ -0,0 +1,28 @@ +package ru.practicum.shareit.client; + +import org.springframework.stereotype.Component; +import ru.practicum.shareit.dto.ItemRequestDto; + +@Component +public class ItemRequestClient extends BaseClient { + + public ItemRequestClient() { + super("/requests"); + } + + public Object addItemRequest(ItemRequestDto itemRequest, Long userId) { + return post("", userId, itemRequest); + } + + public Object getAllRequests() { + return get("/all"); + } + + public Object getMyRequests(Long ownerId) { + return get("", ownerId); + } + + public Object getReqestById(Long id) { + return get("/" + id); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/client/UserClient.java b/gateway/src/main/java/ru/practicum/shareit/client/UserClient.java new file mode 100644 index 0000000..9050488 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/client/UserClient.java @@ -0,0 +1,32 @@ +package ru.practicum.shareit.client; + +import org.springframework.stereotype.Component; +import ru.practicum.shareit.dto.UserDto; + +@Component +public class UserClient extends BaseClient { + + public UserClient() { + super("/users"); + } + + public Object addUser(UserDto user) { + return post("", user); + } + + public Object updateUser(Long userId, UserDto userDto) { + return patch("/" + userId, userDto); + } + + public Object getAllUsers() { + return get(""); + } + + public Object getUser(Long userId) { + return get("/" + userId); + } + + public void deleteUser(Long userId) { + delete("/" + userId); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/controller/BookingController.java b/gateway/src/main/java/ru/practicum/shareit/controller/BookingController.java new file mode 100644 index 0000000..9c96518 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/controller/BookingController.java @@ -0,0 +1,63 @@ +package ru.practicum.shareit.controller; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import org.springframework.stereotype.Controller; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import ru.practicum.shareit.dto.BookingDto; +import ru.practicum.shareit.dto.BookingState; +import ru.practicum.shareit.client.BookingClient; + +@Controller +@RequestMapping(path = "/bookings") +@RequiredArgsConstructor +@Slf4j +@Validated +public class BookingController { + + private final BookingClient bookingClient; + + @PostMapping + public Object createBooking(@RequestHeader("X-Sharer-User-Id") @Positive Long userId, + @Valid @RequestBody BookingDto bookingDto) { + + return bookingClient.createBooking(userId, bookingDto); + } + + @PatchMapping("/{bookingId}") + public Object updateBookingStatus( + @RequestHeader("X-Sharer-User-Id") @Positive Long userId, + @Positive @PathVariable Long bookingId, + @RequestParam Boolean approved) { + + return bookingClient.updateBookingStatus(userId, bookingId, approved); + } + + @GetMapping("/{bookingId}") + public Object getBooking( + @RequestHeader("X-Sharer-User-Id") @Positive Long userId, + @PathVariable @Positive Long bookingId) { + + return bookingClient.getBooking(userId, bookingId); + } + + @GetMapping + public Object findByBookerAndState( + @RequestHeader("X-Sharer-User-Id") @Positive Long bookerId, + @RequestParam (defaultValue = "ALL") BookingState state) { + + return bookingClient.findByBookerAndState(bookerId, state); + } + + @GetMapping("/owner") + public Object findByOwnerAndState( + @RequestHeader("X-Sharer-User-Id") @Positive Long ownerId, + @RequestParam (defaultValue = "ALL") BookingState state) { + + return bookingClient.findByOwnerAndState(ownerId, state); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/controller/ItemController.java b/gateway/src/main/java/ru/practicum/shareit/controller/ItemController.java new file mode 100644 index 0000000..d3f2a3d --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/controller/ItemController.java @@ -0,0 +1,63 @@ +package ru.practicum.shareit.controller; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.OnCreate; +import ru.practicum.shareit.OnUpdate; +import ru.practicum.shareit.client.ItemClient; +import ru.practicum.shareit.dto.CommentDto; +import ru.practicum.shareit.dto.ItemDto; + +@Slf4j +@RestController +@RequestMapping("/items") +@RequiredArgsConstructor +public class ItemController { + + private final ItemClient itemClient; + + @PostMapping + public Object addItem(@Validated(OnCreate.class) @RequestBody final ItemDto itemDto, + @RequestHeader("X-Sharer-User-Id") @Positive Long ownerId) { + log.info("Предмет добавлен"); + return itemClient.addItem(itemDto, ownerId); + } + + @PatchMapping("/{itemId}") + public Object updateItem(@Positive @PathVariable("itemId") final long itemId, + @RequestHeader("X-Sharer-User-Id") @Positive long ownerId, + @Validated(OnUpdate.class) @RequestBody final ItemDto itemDto) { + log.info("Предмет обновлен"); + return itemClient.updateItem(itemId, itemDto, ownerId); + } + + @GetMapping("/{itemId}") + public Object getItem(@Positive @PathVariable("itemId") final long itemId) { + log.info("Предмет выведен"); + return itemClient.getItem(itemId); + } + + @GetMapping + public Object getAllOwnerItems(@RequestHeader("X-Sharer-User-Id") @Positive long ownerId) { + log.info("Список всех предметов владельца выведен"); + return itemClient.getAllOwnerItems(ownerId); + } + + @GetMapping("/search") + public Object getNecessaryItem(@RequestParam String text) { + log.info("Список предметов содержащих " + text + " выведен"); + return itemClient.getNecessaryItem(text); + } + + @PostMapping("/{itemId}/comment") + public Object addComment(@Positive @PathVariable("itemId") final long itemId, + @RequestHeader("X-Sharer-User-Id") @Positive long ownerId, + @Valid @RequestBody CommentDto comment) { + log.info("Комментарий добавлен"); + return itemClient.addComment(itemId, ownerId, comment); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/controller/ItemRequestController.java b/gateway/src/main/java/ru/practicum/shareit/controller/ItemRequestController.java new file mode 100644 index 0000000..f5fa1e8 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/controller/ItemRequestController.java @@ -0,0 +1,43 @@ +package ru.practicum.shareit.controller; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.client.ItemRequestClient; +import ru.practicum.shareit.dto.ItemRequestDto; + +@Slf4j +@RestController +@RequestMapping(path = "/requests") +@AllArgsConstructor +public class ItemRequestController { + + private final ItemRequestClient itemRequestClient; + + @PostMapping + public Object addRequest(@RequestHeader("X-Sharer-User-Id") @Positive Long userId, + @Valid @RequestBody final ItemRequestDto itemRequest) { + log.info("Запрос добавлен"); + return itemRequestClient.addItemRequest(itemRequest, userId); + } + + @GetMapping("/all") + public Object getAllRequests() { + log.info("Список запросов выведен"); + return itemRequestClient.getAllRequests(); + } + + @GetMapping + public Object getMyReqests(@RequestHeader("X-Sharer-User-Id") @Positive Long ownerId) { + log.info("Запросы пользователя с id " + ownerId + " выведен"); + return itemRequestClient.getMyRequests(ownerId); + } + + @GetMapping("/{requestId}") + public Object getReqestById(@Positive @PathVariable("requestId") final Long id) { + log.info("Запрос с id " + id + " выведен"); + return itemRequestClient.getReqestById(id); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/controller/UserController.java b/gateway/src/main/java/ru/practicum/shareit/controller/UserController.java new file mode 100644 index 0000000..656403b --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/controller/UserController.java @@ -0,0 +1,51 @@ +package ru.practicum.shareit.controller; + +import jakarta.validation.constraints.Positive; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.OnCreate; +import ru.practicum.shareit.OnUpdate; +import ru.practicum.shareit.client.UserClient; +import ru.practicum.shareit.dto.UserDto; + +@Slf4j +@RestController +@RequestMapping(path = "/users") +@RequiredArgsConstructor +public class UserController { + + private final UserClient userClient; + + @PostMapping + public Object addUser(@Validated(OnCreate.class) @RequestBody final UserDto user) { + log.info("Пользователь добавлен"); + return userClient.addUser(user); + } + + @PatchMapping("/{userId}") + public Object updateUser(@Positive @PathVariable Long userId, + @Validated(OnUpdate.class) @RequestBody final UserDto userDto) { + log.info("Пользователь обновлен"); + return userClient.updateUser(userId, userDto); + } + + @GetMapping + public Object getAllUsers() { + log.info("Список пользователей выведен"); + return userClient.getAllUsers(); + } + + @GetMapping("/{userId}") + public Object getUser(@Positive @PathVariable("userId") final Long userId) { + log.info("Польователь с id " + userId + " выведен"); + return userClient.getUser(userId); + } + + @DeleteMapping("/{userId}") + public void deleteUser(@Positive @PathVariable("userId") final Long userId) { + log.info("Пользователь с id " + userId + " удален"); + userClient.deleteUser(userId); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java b/gateway/src/main/java/ru/practicum/shareit/dto/BookingDto.java similarity index 72% rename from src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java rename to gateway/src/main/java/ru/practicum/shareit/dto/BookingDto.java index 040c09d..c68c6c0 100644 --- a/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java +++ b/gateway/src/main/java/ru/practicum/shareit/dto/BookingDto.java @@ -1,29 +1,26 @@ -package ru.practicum.shareit.booking.dto; +package ru.practicum.shareit.dto; +import java.time.LocalDateTime; + +import jakarta.validation.constraints.Future; import jakarta.validation.constraints.FutureOrPresent; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; import lombok.AllArgsConstructor; import lombok.Getter; -import lombok.Setter; - -import java.time.LocalDateTime; +import lombok.NoArgsConstructor; -/** - * TODO Sprint add-bookings. - */ -@Setter @Getter +@NoArgsConstructor @AllArgsConstructor public class BookingDto { @NotNull @Positive - private Long itemId; + private long itemId; @NotNull @FutureOrPresent private LocalDateTime start; @NotNull - @FutureOrPresent + @Future private LocalDateTime end; - } diff --git a/gateway/src/main/java/ru/practicum/shareit/dto/BookingState.java b/gateway/src/main/java/ru/practicum/shareit/dto/BookingState.java new file mode 100644 index 0000000..b090905 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/dto/BookingState.java @@ -0,0 +1,21 @@ +package ru.practicum.shareit.dto; + +import java.util.Optional; + +public enum BookingState { + ALL, + CURRENT, + FUTURE, + PAST, + REJECTED, + WAITING; + + public static Optional from(String stringState) { + for (BookingState state : values()) { + if (state.name().equalsIgnoreCase(stringState)) { + return Optional.of(state); + } + } + return Optional.empty(); + } +} diff --git a/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java b/gateway/src/main/java/ru/practicum/shareit/dto/CommentDto.java similarity index 85% rename from src/main/java/ru/practicum/shareit/item/dto/CommentDto.java rename to gateway/src/main/java/ru/practicum/shareit/dto/CommentDto.java index 1a228f2..d33c579 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java +++ b/gateway/src/main/java/ru/practicum/shareit/dto/CommentDto.java @@ -1,4 +1,4 @@ -package ru.practicum.shareit.item.dto; +package ru.practicum.shareit.dto; import lombok.Data; diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java b/gateway/src/main/java/ru/practicum/shareit/dto/ItemDto.java similarity index 90% rename from src/main/java/ru/practicum/shareit/item/dto/ItemDto.java rename to gateway/src/main/java/ru/practicum/shareit/dto/ItemDto.java index f8e2967..9578638 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java +++ b/gateway/src/main/java/ru/practicum/shareit/dto/ItemDto.java @@ -1,11 +1,11 @@ -package ru.practicum.shareit.item.dto; +package ru.practicum.shareit.dto; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import ru.practicum.shareit.validation.OnCreate; +import ru.practicum.shareit.OnCreate; import java.time.LocalDateTime; import java.util.Collection; @@ -27,4 +27,5 @@ public class ItemDto { private LocalDateTime lastBooking; private LocalDateTime nextBooking; private Collection comments; + private Long requestId; } diff --git a/gateway/src/main/java/ru/practicum/shareit/dto/ItemRequestDto.java b/gateway/src/main/java/ru/practicum/shareit/dto/ItemRequestDto.java new file mode 100644 index 0000000..618fd76 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/dto/ItemRequestDto.java @@ -0,0 +1,18 @@ +package ru.practicum.shareit.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDate; +import java.util.List; + +@Data +@Builder +@AllArgsConstructor +public class ItemRequestDto { + private Long id; + private String description; + private LocalDate created; + private List items; +} diff --git a/src/main/java/ru/practicum/shareit/user/dto/UserDto.java b/gateway/src/main/java/ru/practicum/shareit/dto/UserDto.java similarity index 81% rename from src/main/java/ru/practicum/shareit/user/dto/UserDto.java rename to gateway/src/main/java/ru/practicum/shareit/dto/UserDto.java index 6bc7abe..7cbb5cf 100644 --- a/src/main/java/ru/practicum/shareit/user/dto/UserDto.java +++ b/gateway/src/main/java/ru/practicum/shareit/dto/UserDto.java @@ -1,4 +1,4 @@ -package ru.practicum.shareit.user.dto; +package ru.practicum.shareit.dto; import jakarta.validation.Valid; import jakarta.validation.constraints.Email; @@ -6,14 +6,13 @@ import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; -import ru.practicum.shareit.validation.OnCreate; -import ru.practicum.shareit.validation.OnUpdate; +import ru.practicum.shareit.OnCreate; +import ru.practicum.shareit.OnUpdate; @Data @Valid @AllArgsConstructor public class UserDto { - private Long id; @NotBlank(groups = OnCreate.class, message = "Имя пользователя не может быть пустым") private String name; @@ -21,4 +20,4 @@ public class UserDto { @NotNull(groups = OnCreate.class, message = "Почта не должна быть пустой") @Email(groups = {OnCreate.class, OnUpdate.class}, message = "Почта должна быть в нужном формате") 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..6706685 --- /dev/null +++ b/gateway/src/main/resources/application.properties @@ -0,0 +1,2 @@ +server.port=8080 +shareit-server.url=http://localhost:9090 \ No newline at end of file diff --git a/pom.xml b/pom.xml index 1b18148..65bb475 100644 --- a/pom.xml +++ b/pom.xml @@ -11,93 +11,39 @@ ru.practicum shareit + pom 0.0.1-SNAPSHOT ShareIt 21 + 3.3.1 - - - 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 - true - - - - 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 - - - - org.postgresql - postgresql - runtime - - + + gateway + server + - - - src/main/resources - true - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - org.projectlombok - lombok - - - - - + + org.springframework.boot + spring-boot-maven-plugin + + + true + + + + org.projectlombok + lombok + + + + org.apache.maven.plugins maven-surefire-plugin @@ -244,17 +190,5 @@ - - coverage - - - - org.jacoco - jacoco-maven-plugin - - - - - - + \ No newline at end of file diff --git a/server/Dockerfile b/server/Dockerfile new file mode 100644 index 0000000..308e738 --- /dev/null +++ b/server/Dockerfile @@ -0,0 +1,5 @@ +FROM amazoncorretto:21 +VOLUME /tmp +ARG JAR_FILE=target/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"] \ No newline at end of file diff --git a/server/pom.xml b/server/pom.xml new file mode 100644 index 0000000..6657d9e --- /dev/null +++ b/server/pom.xml @@ -0,0 +1,108 @@ + + + 4.0.0 + + ru.practicum + shareit + 0.0.1-SNAPSHOT + + + shareit-server + 0.0.1-SNAPSHOT + + ShareIt Server + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.postgresql + postgresql + runtime + + + + com.h2database + h2 + runtime + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.projectlombok + lombok + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${maven-checkstyle-plugin.version} + + + true + true + true + checkstyle.xml + + + + + + check + + compile + + + + + 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/ShareItApp.java similarity index 100% rename from src/main/java/ru/practicum/shareit/ShareItApp.java rename to server/src/main/java/ru/practicum/shareit/ShareItApp.java diff --git a/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java b/server/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java similarity index 72% rename from src/main/java/ru/practicum/shareit/booking/controller/BookingController.java rename to server/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java index 3498754..57907eb 100644 --- a/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java +++ b/server/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java @@ -1,7 +1,5 @@ package ru.practicum.shareit.booking.controller; -import jakarta.validation.Valid; -import jakarta.validation.constraints.Positive; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import ru.practicum.shareit.booking.dto.BookingDto; @@ -22,16 +20,16 @@ public class BookingController { private final BookingService bookingService; @PostMapping - public Booking createBooking(@RequestHeader("X-Sharer-User-Id") @Positive Long userId, - @Valid @RequestBody BookingDto bookingDto) { + public Booking createBooking(@RequestHeader("X-Sharer-User-Id") Long userId, + @RequestBody BookingDto bookingDto) { return bookingService.createBooking(userId, bookingDto); } @PatchMapping("/{bookingId}") public Booking updateBookingStatus( - @RequestHeader("X-Sharer-User-Id") @Positive Long userId, - @PathVariable @Positive Long bookingId, + @RequestHeader("X-Sharer-User-Id") Long userId, + @PathVariable Long bookingId, @RequestParam Boolean approved) { return bookingService.updateBookingStatus(userId, bookingId, approved); @@ -39,15 +37,15 @@ public Booking updateBookingStatus( @GetMapping("/{bookingId}") public Booking getBooking( - @RequestHeader("X-Sharer-User-Id") @Positive Long userId, - @PathVariable @Positive Long bookingId) { + @RequestHeader("X-Sharer-User-Id") Long userId, + @PathVariable Long bookingId) { return bookingService.getBooking(userId, bookingId); } @GetMapping public Collection findByBookerAndState( - @RequestHeader("X-Sharer-User-Id") @Positive Long bookerId, + @RequestHeader("X-Sharer-User-Id") Long bookerId, @RequestParam (defaultValue = "ALL") BookingState state) { return bookingService.findByBookerAndState(bookerId, state); @@ -55,7 +53,7 @@ public Collection findByBookerAndState( @GetMapping("/owner") public Collection findByOwnerAndState( - @RequestHeader("X-Sharer-User-Id") @Positive Long ownerId, + @RequestHeader("X-Sharer-User-Id") Long ownerId, @RequestParam (defaultValue = "ALL") BookingState state) { return bookingService.findByOwnerAndState(ownerId, state); diff --git a/src/main/java/ru/practicum/shareit/booking/dao/BookingMapper.java b/server/src/main/java/ru/practicum/shareit/booking/dao/BookingMapper.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/dao/BookingMapper.java rename to server/src/main/java/ru/practicum/shareit/booking/dao/BookingMapper.java 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 57% 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 index 1e08da0..5c4c642 100644 --- a/src/main/java/ru/practicum/shareit/booking/dao/BookingRepository.java +++ b/server/src/main/java/ru/practicum/shareit/booking/dao/BookingRepository.java @@ -8,33 +8,32 @@ import ru.practicum.shareit.booking.model.LastAndNextDate; import java.time.LocalDateTime; -import java.util.Collection; import java.util.List; @Repository public interface BookingRepository extends JpaRepository { - Collection findByBookerIdOrderByEndDesc(Long bookerId); + List findByBookerIdOrderByEndDesc(Long bookerId); - Collection findByItemOwnerIdOrderByEndDesc(Long ownerId); + List findByItemOwnerIdOrderByEndDesc(Long ownerId); - Collection findByBookerIdAndStartIsBeforeAndEndIsBeforeOrderByEndDesc(Long bookerId, LocalDateTime now, + List findByBookerIdAndStartIsBeforeAndEndIsBeforeOrderByEndDesc(Long bookerId, LocalDateTime now, LocalDateTime now1); - Collection findByBookerIdAndStatusIsOrderByEndDesc(Long bookerId, BookingStatus bookingStatus); + List findByBookerIdAndStatusIsOrderByEndDesc(Long bookerId, BookingStatus bookingStatus); - Collection findByBookerIdAndEndIsBeforeOrderByEndDesc(Long bookerId, LocalDateTime now); + List findByBookerIdAndEndIsBeforeOrderByEndDesc(Long bookerId, LocalDateTime now); - Collection findByBookerIdAndStartIsAfterOrderByEndDesc(Long bookerId, LocalDateTime now); + List findByBookerIdAndStartIsAfterOrderByEndDesc(Long bookerId, LocalDateTime now); - Collection findByItemOwnerIdAndStartIsBeforeAndEndIsBeforeOrderByEndDesc(Long ownerId, LocalDateTime now, + List findByItemOwnerIdAndStartIsBeforeAndEndIsBeforeOrderByEndDesc(Long ownerId, LocalDateTime now, LocalDateTime now1); - Collection findByItemOwnerIdAndStatusIsOrderByEndDesc(Long ownerId, BookingStatus bookingStatus); + List findByItemOwnerIdAndStatusIsOrderByEndDesc(Long ownerId, BookingStatus bookingStatus); - Collection findByItemOwnerIdAndEndIsBeforeOrderByEndDesc(Long ownerId, LocalDateTime now); + List findByItemOwnerIdAndEndIsBeforeOrderByEndDesc(Long ownerId, LocalDateTime now); - Collection findByItemOwnerIdAndStartIsAfterOrderByEndDesc(Long ownerId, LocalDateTime now); + List findByItemOwnerIdAndStartIsAfterOrderByEndDesc(Long ownerId, LocalDateTime now); boolean existsByItemIdAndBookerIdAndStatusIsAndEndBefore(Long itemId, Long userId, BookingStatus bookingStatus, LocalDateTime now); diff --git a/server/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java b/server/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java new file mode 100644 index 0000000..f4ea496 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java @@ -0,0 +1,21 @@ +package ru.practicum.shareit.booking.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDateTime; + +/** + * TODO Sprint add-bookings. + */ +@Setter +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class BookingDto { + private Long itemId; + private LocalDateTime start; + private LocalDateTime end; +} 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 87% 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 33ea486..bbacff4 100644 --- a/src/main/java/ru/practicum/shareit/booking/model/Booking.java +++ b/server/src/main/java/ru/practicum/shareit/booking/model/Booking.java @@ -1,10 +1,7 @@ package ru.practicum.shareit.booking.model; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import lombok.*; import ru.practicum.shareit.item.model.Item; import ru.practicum.shareit.user.model.User; @@ -18,6 +15,7 @@ @NoArgsConstructor @Setter @Getter +@Builder @Table(name = "bookings") public class Booking { 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/model/LastAndNextDate.java b/server/src/main/java/ru/practicum/shareit/booking/model/LastAndNextDate.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/model/LastAndNextDate.java rename to server/src/main/java/ru/practicum/shareit/booking/model/LastAndNextDate.java diff --git a/src/main/java/ru/practicum/shareit/booking/service/BookingService.java b/server/src/main/java/ru/practicum/shareit/booking/service/BookingService.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/service/BookingService.java rename to server/src/main/java/ru/practicum/shareit/booking/service/BookingService.java diff --git a/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java b/server/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java similarity index 98% rename from src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java rename to server/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java index 87396bf..5c75fde 100644 --- a/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java +++ b/server/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java @@ -1,6 +1,5 @@ package ru.practicum.shareit.booking.service; -import jakarta.validation.ValidationException; import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -11,6 +10,7 @@ import ru.practicum.shareit.booking.model.BookingStatus; import ru.practicum.shareit.exceptions.BadRequestException; import ru.practicum.shareit.exceptions.NotFoundException; +import ru.practicum.shareit.exceptions.ValidationException; import ru.practicum.shareit.item.dao.ItemRepository; import ru.practicum.shareit.item.model.Item; import ru.practicum.shareit.user.dao.UserRepository; diff --git a/src/main/java/ru/practicum/shareit/exceptions/BadRequestException.java b/server/src/main/java/ru/practicum/shareit/exceptions/BadRequestException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exceptions/BadRequestException.java rename to server/src/main/java/ru/practicum/shareit/exceptions/BadRequestException.java diff --git a/src/main/java/ru/practicum/shareit/exceptions/ErrorHandler.java b/server/src/main/java/ru/practicum/shareit/exceptions/ErrorHandler.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exceptions/ErrorHandler.java rename to server/src/main/java/ru/practicum/shareit/exceptions/ErrorHandler.java diff --git a/src/main/java/ru/practicum/shareit/exceptions/ErrorResponse.java b/server/src/main/java/ru/practicum/shareit/exceptions/ErrorResponse.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exceptions/ErrorResponse.java rename to server/src/main/java/ru/practicum/shareit/exceptions/ErrorResponse.java diff --git a/src/main/java/ru/practicum/shareit/exceptions/InternalServerException.java b/server/src/main/java/ru/practicum/shareit/exceptions/InternalServerException.java similarity index 89% rename from src/main/java/ru/practicum/shareit/exceptions/InternalServerException.java rename to server/src/main/java/ru/practicum/shareit/exceptions/InternalServerException.java index 3230b48..1af9846 100644 --- a/src/main/java/ru/practicum/shareit/exceptions/InternalServerException.java +++ b/server/src/main/java/ru/practicum/shareit/exceptions/InternalServerException.java @@ -2,5 +2,6 @@ public class InternalServerException extends RuntimeException { public InternalServerException(String s) { + super(s); } -} +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/exceptions/NotFoundException.java b/server/src/main/java/ru/practicum/shareit/exceptions/NotFoundException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exceptions/NotFoundException.java rename to server/src/main/java/ru/practicum/shareit/exceptions/NotFoundException.java diff --git a/server/src/main/java/ru/practicum/shareit/exceptions/ValidationException.java b/server/src/main/java/ru/practicum/shareit/exceptions/ValidationException.java new file mode 100644 index 0000000..fad80fb --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/exceptions/ValidationException.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.exceptions; + +public class ValidationException extends RuntimeException { + public ValidationException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/practicum/shareit/item/controller/ItemController.java b/server/src/main/java/ru/practicum/shareit/item/controller/ItemController.java similarity index 97% rename from src/main/java/ru/practicum/shareit/item/controller/ItemController.java rename to server/src/main/java/ru/practicum/shareit/item/controller/ItemController.java index 5ddfb15..8b8aecc 100644 --- a/src/main/java/ru/practicum/shareit/item/controller/ItemController.java +++ b/server/src/main/java/ru/practicum/shareit/item/controller/ItemController.java @@ -62,6 +62,6 @@ public CommentDto addComment(@PathVariable("itemId") final long itemId, @RequestHeader("X-Sharer-User-Id") long ownerId, @RequestBody Comment comment) { log.info("Комментарий добавлен"); - return itemService.addComment(ownerId, itemId, comment); + return itemService.addComment(itemId, ownerId, comment); } } diff --git a/src/main/java/ru/practicum/shareit/item/dao/CommentMapper.java b/server/src/main/java/ru/practicum/shareit/item/dao/CommentMapper.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/dao/CommentMapper.java rename to server/src/main/java/ru/practicum/shareit/item/dao/CommentMapper.java diff --git a/src/main/java/ru/practicum/shareit/item/dao/CommentRepository.java b/server/src/main/java/ru/practicum/shareit/item/dao/CommentRepository.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/dao/CommentRepository.java rename to server/src/main/java/ru/practicum/shareit/item/dao/CommentRepository.java diff --git a/src/main/java/ru/practicum/shareit/item/dao/ItemMapper.java b/server/src/main/java/ru/practicum/shareit/item/dao/ItemMapper.java similarity index 94% rename from src/main/java/ru/practicum/shareit/item/dao/ItemMapper.java rename to server/src/main/java/ru/practicum/shareit/item/dao/ItemMapper.java index 5829d97..bdce253 100644 --- a/src/main/java/ru/practicum/shareit/item/dao/ItemMapper.java +++ b/server/src/main/java/ru/practicum/shareit/item/dao/ItemMapper.java @@ -12,6 +12,7 @@ public static ItemDto toItemDto(Item item) { itemDto.setName(item.getName()); itemDto.setDescription(item.getDescription()); itemDto.setAvailable(item.getIsAvailable()); + itemDto.setRequestId(item.getRequestId()); return itemDto; } 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 86% 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 f2bc1cf..87bb087 100644 --- a/src/main/java/ru/practicum/shareit/item/dao/ItemRepository.java +++ b/server/src/main/java/ru/practicum/shareit/item/dao/ItemRepository.java @@ -21,4 +21,8 @@ OR LOWER(i.description) LIKE LOWER(%:text%)) Collection searchByNameOrDescription(String text); List findByOwnerId(Long userId); + + Collection findByRequestId(Long requestId); + + List findAllByRequestIdIn(Collection ids); } diff --git a/server/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java b/server/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java new file mode 100644 index 0000000..8d14091 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java @@ -0,0 +1,18 @@ +package ru.practicum.shareit.item.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class CommentDto { + private Long id; + private Long itemId; + private String authorName; + private String text; + private LocalDateTime created; +} diff --git a/server/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java b/server/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java new file mode 100644 index 0000000..fe65596 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java @@ -0,0 +1,25 @@ +package ru.practicum.shareit.item.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.Collection; + +/** + * TODO Sprint add-controllers. + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ItemDto { + private Long id; + private String name; + private String description; + private Boolean available; + private LocalDateTime lastBooking; + private LocalDateTime nextBooking; + private Collection comments; + private Long requestId; +} diff --git a/src/main/java/ru/practicum/shareit/item/model/Comment.java b/server/src/main/java/ru/practicum/shareit/item/model/Comment.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/model/Comment.java rename to server/src/main/java/ru/practicum/shareit/item/model/Comment.java diff --git a/src/main/java/ru/practicum/shareit/item/model/Item.java b/server/src/main/java/ru/practicum/shareit/item/model/Item.java similarity index 83% 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 3dab078..18305b6 100644 --- a/src/main/java/ru/practicum/shareit/item/model/Item.java +++ b/server/src/main/java/ru/practicum/shareit/item/model/Item.java @@ -1,10 +1,7 @@ package ru.practicum.shareit.item.model; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import lombok.*; /** * TODO Sprint add-controllers. @@ -14,6 +11,7 @@ @NoArgsConstructor @Getter @Setter +@Builder @Table(name = "items") public class Item { @Id @@ -31,4 +29,7 @@ public class Item { @Column(name = "owner_id") private Long ownerId; + + @Column(name = "request_id") + private Long requestId; } diff --git a/src/main/java/ru/practicum/shareit/item/service/ItemService.java b/server/src/main/java/ru/practicum/shareit/item/service/ItemService.java similarity index 89% rename from src/main/java/ru/practicum/shareit/item/service/ItemService.java rename to server/src/main/java/ru/practicum/shareit/item/service/ItemService.java index 277a7e9..07af554 100644 --- a/src/main/java/ru/practicum/shareit/item/service/ItemService.java +++ b/server/src/main/java/ru/practicum/shareit/item/service/ItemService.java @@ -19,5 +19,5 @@ public interface ItemService { Collection getNecessaryItem(String text); - CommentDto addComment(Long userId, Long itemId, Comment comment); + CommentDto addComment(Long itemId, Long userId, Comment comment); } diff --git a/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java b/server/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java similarity index 91% rename from src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java rename to server/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java index e25ce6e..106a84c 100644 --- a/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java +++ b/server/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java @@ -16,6 +16,8 @@ import ru.practicum.shareit.item.dto.ItemDto; import ru.practicum.shareit.item.model.Comment; 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; @@ -35,6 +37,7 @@ public class ItemServiceImpl implements ItemService { private final UserRepository userRepository; private final CommentRepository commentRepository; private final BookingRepository bookingRepository; + private final ItemRequestRepository itemRequestRepository; @Override @Transactional @@ -45,6 +48,14 @@ public ItemDto addItem(ItemDto itemDto, Long ownerId) { } Item item = toItem(itemDto); item.setOwnerId(ownerId); + + if (itemDto.getRequestId() != null) { + ItemRequest request = itemRequestRepository.findById(itemDto.getRequestId()) + .orElseThrow(() -> new NotFoundException( + "Запрос с id = " + itemDto.getRequestId() + " не найден")); + item.setRequestId(request.getId()); + } + return toItemDto(itemRepository.save(item)); } @@ -140,7 +151,7 @@ public List getNecessaryItem(String text) { } @Override - public CommentDto addComment(Long userId, Long itemId, Comment comment) { + public CommentDto addComment(Long itemId, Long userId, Comment comment) { User user = userRepository.findById(userId) .orElseThrow(() -> new NotFoundException("Пользователь с id " + userId + " не найден")); diff --git a/server/src/main/java/ru/practicum/shareit/request/controller/ItemRequestController.java b/server/src/main/java/ru/practicum/shareit/request/controller/ItemRequestController.java new file mode 100644 index 0000000..e9c52d9 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/controller/ItemRequestController.java @@ -0,0 +1,47 @@ +package ru.practicum.shareit.request.controller; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.request.service.ItemRequestServiceImpl; + +import java.util.Collection; + +/** + * TODO Sprint add-item-requests. + */ +@Slf4j +@RestController +@RequestMapping(path = "/requests") +@AllArgsConstructor +public class ItemRequestController { + + private final ItemRequestServiceImpl itemRequestServiceImpl; + + @PostMapping + public ItemRequestDto addRequest(@RequestHeader("X-Sharer-User-Id") Long userId, + @RequestBody final ItemRequest itemRequest) { + log.info("Запрос добавлен"); + return itemRequestServiceImpl.addItemRequest(itemRequest, userId); + } + + @GetMapping("/all") + public Collection getAllRequests() { + log.info("Список запросов выведен"); + return itemRequestServiceImpl.getAllRequests(); + } + + @GetMapping + public Collection getMyRequests(@RequestHeader("X-Sharer-User-Id") Long ownerId) { + log.info("Запросы пользователя с id " + ownerId + " выведен"); + return itemRequestServiceImpl.getMyRequests(ownerId); + } + + @GetMapping("/{requestId}") + public ItemRequestDto getRequestById(@PathVariable("requestId") final Long id) { + log.info("Запрос с id " + id + " выведен"); + return itemRequestServiceImpl.getRequestById(id); + } +} diff --git a/server/src/main/java/ru/practicum/shareit/request/dao/ItemRequestMapper.java b/server/src/main/java/ru/practicum/shareit/request/dao/ItemRequestMapper.java new file mode 100644 index 0000000..09eeddf --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/dao/ItemRequestMapper.java @@ -0,0 +1,18 @@ +package ru.practicum.shareit.request.dao; + +import lombok.experimental.UtilityClass; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.model.ItemRequest; + +import java.time.LocalDate; + +@UtilityClass +public class ItemRequestMapper { + public static ItemRequestDto mapToItemRequestDto(ItemRequest itemRequest) { + return ItemRequestDto.builder() + .id(itemRequest.getId()) + .description(itemRequest.getDescription()) + .created(LocalDate.now()) + .build(); + } +} 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..156d8ec --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/dao/ItemRequestRepository.java @@ -0,0 +1,14 @@ +package ru.practicum.shareit.request.dao; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import ru.practicum.shareit.request.model.ItemRequest; + +import java.util.List; + +@Repository +public interface ItemRequestRepository extends JpaRepository { + List findByRequestorIdOrderByCreatedDesc(Long requestorId); + + List findAllByOrderByCreatedDesc(); +} diff --git a/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java b/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java similarity index 58% rename from src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java rename to server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java index e86b372..583df0f 100644 --- a/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java +++ b/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java @@ -1,18 +1,24 @@ package ru.practicum.shareit.request.dto; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; -import ru.practicum.shareit.user.model.User; +import lombok.NoArgsConstructor; +import ru.practicum.shareit.item.dto.ItemDto; import java.time.LocalDate; +import java.util.List; /** * TODO Sprint add-item-requests. */ @Data +@Builder @AllArgsConstructor +@NoArgsConstructor public class ItemRequestDto { + private Long id; private String description; - private User requestor; private LocalDate created; + private List items; } 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..8d21ea5 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java @@ -0,0 +1,33 @@ +package ru.practicum.shareit.request.model; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; + +/** + * TODO Sprint add-item-requests. + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Entity +@Table(name = "item_requests") +public class ItemRequest { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "description") + private String description; + + @Column(name = "requestor_id") + private Long requestorId; + + @Column(name = "created_at") + private LocalDate created; +} diff --git a/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestService.java b/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestService.java new file mode 100644 index 0000000..424a769 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestService.java @@ -0,0 +1,16 @@ +package ru.practicum.shareit.request.service; + +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.model.ItemRequest; + +import java.util.Collection; + +public interface ItemRequestService { + ItemRequestDto addItemRequest(ItemRequest itemRequest, Long userId); + + Collection getAllRequests(); + + Collection getMyRequests(Long ownerId); + + ItemRequestDto getRequestById(Long id); +} diff --git a/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java b/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java new file mode 100644 index 0000000..7c3ea98 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java @@ -0,0 +1,86 @@ +package ru.practicum.shareit.request.service; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.shareit.exceptions.NotFoundException; +import ru.practicum.shareit.item.dao.ItemMapper; +import ru.practicum.shareit.item.dao.ItemRepository; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.request.dao.ItemRequestMapper; +import ru.practicum.shareit.request.dao.ItemRequestRepository; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.model.ItemRequest; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +@Service +@AllArgsConstructor +public class ItemRequestServiceImpl implements ItemRequestService { + + private final ItemRequestRepository itemRequestRepository; + private final ItemRepository itemRepository; + + @Override + public ItemRequestDto addItemRequest(ItemRequest itemRequest, Long userId) { + itemRequest.setRequestorId(userId); + return ItemRequestMapper.mapToItemRequestDto(itemRequestRepository.save(itemRequest)); + } + + @Transactional(readOnly = true) + @Override + public Collection getAllRequests() { + Collection itemRequest = itemRequestRepository.findAllByOrderByCreatedDesc(); + return addItemsToRequest(itemRequest); + } + + @Transactional(readOnly = true) + @Override + public Collection getMyRequests(Long ownerId) { + Collection itemRequest = itemRequestRepository.findByRequestorIdOrderByCreatedDesc(ownerId); + return addItemsToRequest(itemRequest); + } + + @Transactional + @Override + public ItemRequestDto getRequestById(Long id) { + ItemRequest itemRequest = itemRequestRepository.findById(id) + .orElseThrow(() -> new NotFoundException("Запрос с id = " + id + " не найден")); + + List items = itemRepository.findByRequestId(id) + .stream() + .map(ItemMapper::toItemDto) + .toList(); + + ItemRequestDto itemRequestDto = ItemRequestMapper.mapToItemRequestDto(itemRequest); + itemRequestDto.setItems(items); + + return itemRequestDto; + } + + private List addItemsToRequest(Collection itemRequests) { + List requestIds = itemRequests.stream() + .map(ItemRequest::getId) + .toList(); + + List items = itemRepository.findAllByRequestIdIn(requestIds); + + Map> itemsByRequestId = items.stream() + .map(ItemMapper::toItemDto) + .collect(Collectors.groupingBy(ItemDto::getRequestId)); + + return itemRequests.stream() + .map(request -> { + ItemRequestDto dto = ItemRequestMapper.mapToItemRequestDto(request); + dto.setItems(itemsByRequestId.getOrDefault(request.getId(), List.of())); + return dto; + }) + .toList(); + } +} diff --git a/src/main/java/ru/practicum/shareit/user/controller/UserController.java b/server/src/main/java/ru/practicum/shareit/user/controller/UserController.java similarity index 85% rename from src/main/java/ru/practicum/shareit/user/controller/UserController.java rename to server/src/main/java/ru/practicum/shareit/user/controller/UserController.java index d198bf1..a7c38d0 100644 --- a/src/main/java/ru/practicum/shareit/user/controller/UserController.java +++ b/server/src/main/java/ru/practicum/shareit/user/controller/UserController.java @@ -25,21 +25,20 @@ public class UserController { private final UserServiceImpl userServiceImpl; @PostMapping - public UserDto addUser(@Validated(OnCreate.class) @RequestBody final UserDto user) { + public User addUser(@Validated(OnCreate.class) @RequestBody final UserDto user) { log.info("Пользователь добавлен"); return userServiceImpl.addUser(user); } @PatchMapping("/{userId}") - public UserDto updateUser(@PathVariable("userId") final Long userId, + public User updateUser(@PathVariable Long userId, @Validated(OnUpdate.class) @RequestBody final UserDto userDto) { - userDto.setId(userId); log.info("Пользователь обновлен"); - return userServiceImpl.updateUser(userDto); + return userServiceImpl.updateUser(userId, userDto); } @GetMapping - public Collection getAllUsers() { + public Collection getAllUsers() { log.info("Список пользователей выведен"); return userServiceImpl.getAllUsers(); } diff --git a/src/main/java/ru/practicum/shareit/user/dao/UserMapper.java b/server/src/main/java/ru/practicum/shareit/user/dao/UserMapper.java similarity index 72% rename from src/main/java/ru/practicum/shareit/user/dao/UserMapper.java rename to server/src/main/java/ru/practicum/shareit/user/dao/UserMapper.java index 45441cb..e592ed9 100644 --- a/src/main/java/ru/practicum/shareit/user/dao/UserMapper.java +++ b/server/src/main/java/ru/practicum/shareit/user/dao/UserMapper.java @@ -8,17 +8,15 @@ public class UserMapper { public static UserDto toUserDto(User user) { return new UserDto( - user.getId(), user.getName(), user.getEmail() ); } public static User toUser(UserDto userDto) { - return new User( - userDto.getId(), - userDto.getName(), - userDto.getEmail() - ); + User user = new User(); + user.setName(userDto.getName()); + user.setEmail(userDto.getEmail()); + return user; } } \ No newline at end of file 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 87% 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 index 6ee781a..e5bbcaa 100644 --- a/src/main/java/ru/practicum/shareit/user/dao/UserRepository.java +++ b/server/src/main/java/ru/practicum/shareit/user/dao/UserRepository.java @@ -6,4 +6,5 @@ @Repository public interface UserRepository extends JpaRepository { + boolean existsByEmail(String email); } diff --git a/server/src/main/java/ru/practicum/shareit/user/dto/UserDto.java b/server/src/main/java/ru/practicum/shareit/user/dto/UserDto.java new file mode 100644 index 0000000..8ebfb12 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/user/dto/UserDto.java @@ -0,0 +1,14 @@ +package ru.practicum.shareit.user.dto; + + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class UserDto { + private String name; + private String email; +} 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 79% 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 index 8905e5a..3a0b30c 100644 --- a/src/main/java/ru/practicum/shareit/user/model/User.java +++ b/server/src/main/java/ru/practicum/shareit/user/model/User.java @@ -1,10 +1,7 @@ package ru.practicum.shareit.user.model; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import lombok.*; /** * TODO Sprint add-controllers. @@ -14,6 +11,7 @@ @NoArgsConstructor @Getter @Setter +@Builder @Table(name = "users") public class User { @Id diff --git a/src/main/java/ru/practicum/shareit/user/service/UserService.java b/server/src/main/java/ru/practicum/shareit/user/service/UserService.java similarity index 68% rename from src/main/java/ru/practicum/shareit/user/service/UserService.java rename to server/src/main/java/ru/practicum/shareit/user/service/UserService.java index 5f500ad..939a658 100644 --- a/src/main/java/ru/practicum/shareit/user/service/UserService.java +++ b/server/src/main/java/ru/practicum/shareit/user/service/UserService.java @@ -7,11 +7,11 @@ public interface UserService { - UserDto addUser(UserDto userDto); + User addUser(UserDto userDto); - UserDto updateUser(UserDto userDto); + User updateUser(Long userId, UserDto userDto); - List getAllUsers(); + List getAllUsers(); User getUser(Long id); diff --git a/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java b/server/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java similarity index 64% rename from src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java rename to server/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java index 2876ea8..4a1435e 100644 --- a/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java +++ b/server/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java @@ -3,17 +3,15 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; +import ru.practicum.shareit.exceptions.InternalServerException; import ru.practicum.shareit.exceptions.NotFoundException; import ru.practicum.shareit.user.dao.UserRepository; import ru.practicum.shareit.user.dto.UserDto; import ru.practicum.shareit.user.model.User; -import java.util.ArrayList; import java.util.List; import static ru.practicum.shareit.user.dao.UserMapper.toUser; -import static ru.practicum.shareit.user.dao.UserMapper.toUserDto; @Slf4j @Service @@ -22,36 +20,33 @@ public class UserServiceImpl implements UserService { private final UserRepository userRepository; @Override - @Transactional - public UserDto addUser(UserDto userDto) { + public User addUser(UserDto userDto) { + if (userRepository.existsByEmail(userDto.getEmail())) { + throw new InternalServerException("Пользователь с такой почтой уже существует"); + } User user = toUser(userDto); - return toUserDto(userRepository.save(user)); + return userRepository.save(user); } @Override - @Transactional - public UserDto updateUser(UserDto userDto) { - if (!userRepository.existsById(userDto.getId())) { + public User updateUser(Long userId, UserDto userDto) { + if (!userRepository.existsById(userId)) { log.warn("Пользователь с указанным id не найден"); - throw new NotFoundException("Пользователь с id = " + userDto.getId() + " не найден"); + throw new NotFoundException("Пользователь с id = " + userId + " не найден"); } - User newUser = getUser(userDto.getId()); + User newUser = getUser(userId); if (userDto.getName() != null) { newUser.setName(userDto.getName()); } if (userDto.getEmail() != null) { newUser.setEmail(userDto.getEmail()); } - return toUserDto(userRepository.save(newUser)); + return userRepository.save(newUser); } @Override - public List getAllUsers() { - List dtoList = new ArrayList<>(); - for (User user : userRepository.findAll()) { - dtoList.add(toUserDto(user)); - } - return dtoList; + public List getAllUsers() { + return userRepository.findAll(); } @Override diff --git a/src/main/java/ru/practicum/shareit/validation/OnCreate.java b/server/src/main/java/ru/practicum/shareit/validation/OnCreate.java similarity index 100% rename from src/main/java/ru/practicum/shareit/validation/OnCreate.java rename to server/src/main/java/ru/practicum/shareit/validation/OnCreate.java diff --git a/src/main/java/ru/practicum/shareit/validation/OnUpdate.java b/server/src/main/java/ru/practicum/shareit/validation/OnUpdate.java similarity index 100% rename from src/main/java/ru/practicum/shareit/validation/OnUpdate.java rename to server/src/main/java/ru/practicum/shareit/validation/OnUpdate.java diff --git a/src/main/resources/application-test.properties b/server/src/main/resources/application-test.properties similarity index 100% rename from src/main/resources/application-test.properties rename to server/src/main/resources/application-test.properties diff --git a/src/main/resources/application.properties b/server/src/main/resources/application.properties similarity index 97% rename from src/main/resources/application.properties rename to server/src/main/resources/application.properties index d95862f..3f99c67 100644 --- a/src/main/resources/application.properties +++ b/server/src/main/resources/application.properties @@ -1,3 +1,5 @@ +server.port=9090 + spring.jpa.hibernate.ddl-auto=none spring.jpa.properties.hibernate.format_sql=true spring.sql.init.mode=always diff --git a/src/main/resources/schema.sql b/server/src/main/resources/schema.sql similarity index 77% rename from src/main/resources/schema.sql rename to server/src/main/resources/schema.sql index 3d56497..0cc660a 100644 --- a/src/main/resources/schema.sql +++ b/server/src/main/resources/schema.sql @@ -6,12 +6,22 @@ CREATE TABLE IF NOT EXISTS users ( CONSTRAINT UQ_USER_EMAIL UNIQUE (email) ); +CREATE TABLE IF NOT EXISTS item_requests( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + description TEXT NOT NULL, + requestor_id BIGINT NOT NULL REFERENCES users (id) ON DELETE CASCADE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT pk_item_request PRIMARY KEY (id) +); + CREATE TABLE IF NOT EXISTS items ( id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, description VARCHAR(255) NOT NULL, available BOOLEAN NOT NULL, owner_id BIGINT NOT NULL, + request_id BIGINT, + FOREIGN KEY (request_id) REFERENCES item_requests(id) ON DELETE SET NULL, CONSTRAINT pk_item PRIMARY KEY (id), FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE CASCADE ); diff --git a/src/test/java/ru/practicum/shareit/ShareItTests.java b/server/src/test/java/ru/practicum/shareit/ShareItTests.java similarity index 51% rename from src/test/java/ru/practicum/shareit/ShareItTests.java rename to server/src/test/java/ru/practicum/shareit/ShareItTests.java index 4d79052..6ff7d27 100644 --- a/src/test/java/ru/practicum/shareit/ShareItTests.java +++ b/server/src/test/java/ru/practicum/shareit/ShareItTests.java @@ -2,12 +2,17 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import ru.practicum.shareit.exceptions.ErrorHandler; +import ru.practicum.shareit.exceptions.ErrorResponse; @SpringBootTest class ShareItTests { @Test void contextLoads() { + ShareItApp.main(new String[]{}); + new ErrorHandler(); + new ErrorResponse("test"); } } diff --git a/server/src/test/java/ru/practicum/shareit/booking/controller/BookingControllerTest.java b/server/src/test/java/ru/practicum/shareit/booking/controller/BookingControllerTest.java new file mode 100644 index 0000000..620fe55 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/booking/controller/BookingControllerTest.java @@ -0,0 +1,113 @@ +package ru.practicum.shareit.booking.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.model.Booking; +import ru.practicum.shareit.booking.model.BookingStatus; +import ru.practicum.shareit.booking.service.BookingService; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDateTime; +import java.util.Collections; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(BookingController.class) +public class BookingControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private BookingService bookingService; + + @Test + public void shouldCreateBooking() throws Exception { + BookingDto bookingDto = new BookingDto(); + bookingDto.setItemId(1L); + bookingDto.setStart(LocalDateTime.now().plusDays(1)); + bookingDto.setEnd(LocalDateTime.now().plusDays(2)); + + Booking booking = new Booking(); + booking.setId(1L); + booking.setStatus(BookingStatus.WAITING); + when(bookingService.createBooking(anyLong(), any())).thenReturn(booking); + + mockMvc.perform(post("/bookings") + .header("X-Sharer-User-Id", 1L) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(bookingDto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.status").value("WAITING")); + } + + @Test + public void shouldUpdateBookingStatus() throws Exception { + Booking booking = new Booking(); + booking.setId(1L); + booking.setStatus(BookingStatus.APPROVED); + when(bookingService.updateBookingStatus(anyLong(), anyLong(), anyBoolean())).thenReturn(booking); + + mockMvc.perform(patch("/bookings/1") + .header("X-Sharer-User-Id", 1L) + .param("approved", "true")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.status").value("APPROVED")); + } + + @Test + public void shouldGetBooking() throws Exception { + Booking booking = new Booking(); + booking.setId(1L); + booking.setItem(new Item()); + booking.setBooker(new User()); + when(bookingService.getBooking(anyLong(), anyLong())).thenReturn(booking); + + mockMvc.perform(get("/bookings/1") + .header("X-Sharer-User-Id", 1L)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1)); + } + + @Test + public void shouldFindByBookerAndState() throws Exception { + Booking booking = new Booking(); + booking.setId(1L); + when(bookingService.findByBookerAndState(anyLong(), any())).thenReturn(Collections.singletonList(booking)); + + mockMvc.perform(get("/bookings") + .header("X-Sharer-User-Id", 1L) + .param("state", "ALL")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").value(1)); + } + + @Test + public void shouldFindByOwnerAndState() throws Exception { + Booking booking = new Booking(); + booking.setId(1L); + when(bookingService.findByOwnerAndState(anyLong(), any())).thenReturn(Collections.singletonList(booking)); + + mockMvc.perform(get("/bookings/owner") + .header("X-Sharer-User-Id", 1L) + .param("state", "ALL")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").value(1)); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/booking/dao/BookingDtoJsonTest.java b/server/src/test/java/ru/practicum/shareit/booking/dao/BookingDtoJsonTest.java new file mode 100644 index 0000000..1a79af6 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/booking/dao/BookingDtoJsonTest.java @@ -0,0 +1,101 @@ +package ru.practicum.shareit.booking.dao; + +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 org.springframework.boot.test.json.JsonContent; +import com.fasterxml.jackson.databind.ObjectMapper; +import ru.practicum.shareit.booking.dto.BookingDto; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +import static org.assertj.core.api.Assertions.assertThat; + +@JsonTest +class BookingDtoJsonTest { + + @Autowired + private JacksonTester jacksonTester; + + @Autowired + private ObjectMapper objectMapper; + + private BookingDto bookingDto; + private LocalDateTime start; + private LocalDateTime end; + + @BeforeEach + void setUp() { + start = LocalDateTime.of(2022, 2, 2, 10, 0, 0); + end = LocalDateTime.of(2022, 3, 5, 18, 30, 0); + + bookingDto = new BookingDto(); + bookingDto.setItemId(1L); + bookingDto.setStart(start); + bookingDto.setEnd(end); + } + + @Test + void shouldSerializeBookingDto() throws IOException { + JsonContent json = jacksonTester.write(bookingDto); + + assertThat(json).hasJsonPathNumberValue("$.itemId"); + assertThat(json).hasJsonPathStringValue("$.start"); + assertThat(json).hasJsonPathStringValue("$.end"); + + assertThat(json).extractingJsonPathNumberValue("$.itemId") + .isEqualTo(1); + + assertThat(json).extractingJsonPathStringValue("$.start") + .satisfies(startStr -> { + assertThat(startStr).startsWith(start.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); + }); + + assertThat(json).extractingJsonPathStringValue("$.end") + .satisfies(endStr -> { + assertThat(endStr).startsWith(end.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); + }); + } + + @Test + void shouldDeserializeBookingDto() throws IOException { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"); + + String json = String.format( + "{" + + "\"itemId\":1," + + "\"start\":\"%s\"," + + "\"end\":\"%s\"" + + "}", + start.format(formatter), + end.format(formatter) + ); + + BookingDto deserializedBooking = jacksonTester.parseObject(json); + + assertThat(deserializedBooking.getItemId()).isEqualTo(1L); + assertThat(deserializedBooking.getStart().withNano(0)) + .isEqualTo(start.withNano(0)); + assertThat(deserializedBooking.getEnd().withNano(0)) + .isEqualTo(end.withNano(0)); + } + + @Test + void shouldDeserializeBookingDtoWithNullFields() throws IOException { + String json = "{" + + "\"itemId\":null," + + "\"start\":null," + + "\"end\":null" + + "}"; + + BookingDto deserializedBooking = jacksonTester.parseObject(json); + + assertThat(deserializedBooking.getItemId()).isNull(); + assertThat(deserializedBooking.getStart()).isNull(); + assertThat(deserializedBooking.getEnd()).isNull(); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/booking/dao/BookingDtoTest.java b/server/src/test/java/ru/practicum/shareit/booking/dao/BookingDtoTest.java new file mode 100644 index 0000000..a880a45 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/booking/dao/BookingDtoTest.java @@ -0,0 +1,40 @@ +package ru.practicum.shareit.booking.dao; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.booking.dto.BookingDto; + +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.*; + +class BookingDtoTest { + + private BookingDto bookingDto; + + @BeforeEach + void setUp() { + bookingDto = new BookingDto(); + } + + @Test + void testNoArgsConstructor() { + assertNotNull(bookingDto); + assertNull(bookingDto.getItemId()); + assertNull(bookingDto.getStart()); + assertNull(bookingDto.getEnd()); + } + + @Test + void testAllArgsConstructor() { + Long itemId = 1L; + LocalDateTime start = LocalDateTime.now().plusDays(1); + LocalDateTime end = LocalDateTime.now().plusDays(2); + + BookingDto dto = new BookingDto(itemId, start, end); + + assertEquals(itemId, dto.getItemId()); + assertEquals(start, dto.getStart()); + assertEquals(end, dto.getEnd()); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/booking/dao/BookingMapperTest.java b/server/src/test/java/ru/practicum/shareit/booking/dao/BookingMapperTest.java new file mode 100644 index 0000000..d3d01b0 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/booking/dao/BookingMapperTest.java @@ -0,0 +1,26 @@ +package ru.practicum.shareit.booking.dao; + +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.model.Booking; + +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class BookingMapperTest { + + @Test + public void shouldMapBookingDtoToBooking() { + BookingDto bookingDto = new BookingDto(); + bookingDto.setStart(LocalDateTime.of(2033, 1, 2, 10, 10)); + bookingDto.setEnd(LocalDateTime.of(2033, 1, 5, 11, 0)); + + Booking booking = BookingMapper.mapToBooking(bookingDto); + + assertNotNull(booking); + assertEquals(bookingDto.getStart(), booking.getStart()); + assertEquals(bookingDto.getEnd(), booking.getEnd()); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/booking/dao/BookingRepositoryTest.java b/server/src/test/java/ru/practicum/shareit/booking/dao/BookingRepositoryTest.java new file mode 100644 index 0000000..719011e --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/booking/dao/BookingRepositoryTest.java @@ -0,0 +1,270 @@ +package ru.practicum.shareit.booking.dao; + +import lombok.RequiredArgsConstructor; +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.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import ru.practicum.shareit.booking.model.Booking; +import ru.practicum.shareit.booking.model.BookingStatus; +import ru.practicum.shareit.booking.model.LastAndNextDate; +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.LocalDateTime; +import java.util.List; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +@DataJpaTest +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class BookingRepositoryTest { + + private final BookingRepository bookingRepository; + private final UserRepository userRepository; + private final ItemRepository itemRepository; + + private User owner; + private User booker; + private Item item; + + @BeforeEach + void setUp() { + User testUser1 = new User(null, "name1", "email1@test.com"); + User testUser2 = new User(null, "name2", "email2@test.com"); + owner = userRepository.save(testUser1); + booker = userRepository.save(testUser2); + item = itemRepository.save(Item.builder() + .name("item") + .description("desc") + .isAvailable(true) + .ownerId(owner.getId()) + .build()); + } + + @Test + public void shouldFindLastAndNextDatesByOwnerId() { + Booking pastBooking = bookingRepository.save(Booking.builder() + .start(LocalDateTime.now().minusDays(2)) + .end(LocalDateTime.now().minusDays(1)) + .item(item) + .booker(booker) + .status(BookingStatus.APPROVED) + .build()); + + List dates = bookingRepository.findLastAndNextDatesByOwnerId(owner.getId()); + + assertThat(dates).hasSize(1); + assertThat(dates.getFirst().getItemId()).isEqualTo(item.getId()); + } + + @Test + public void shouldExistsByItemIdAndBookerIdAndStatusIsAndEndBefore() { + bookingRepository.save(Booking.builder() + .start(LocalDateTime.now().minusDays(2)) + .end(LocalDateTime.now().minusDays(1)) + .item(item) + .booker(booker) + .status(BookingStatus.APPROVED) + .build()); + + Boolean exists = bookingRepository.existsByItemIdAndBookerIdAndStatusIsAndEndBefore( + item.getId(), + booker.getId(), + BookingStatus.APPROVED, + LocalDateTime.now() + ); + + assertThat(exists).isTrue(); + } + + @Test + public void shouldFindByBookerIdOrderByEndDesc() { + Booking booking1 = bookingRepository.save(Booking.builder() + .start(LocalDateTime.now().plusDays(3)) + .end(LocalDateTime.now().plusDays(4)) + .item(item) + .booker(booker) + .status(BookingStatus.APPROVED) + .build()); + + Booking booking2 = bookingRepository.save(Booking.builder() + .start(LocalDateTime.now().plusDays(5)) + .end(LocalDateTime.now().plusDays(6)) + .item(item) + .booker(booker) + .status(BookingStatus.APPROVED) + .build()); + + List bookings = bookingRepository.findByBookerIdOrderByEndDesc(booker.getId()); + + assertThat(bookings).hasSize(2); + assertThat(bookings.getFirst().getEnd()).isAfter(bookings.get(1).getEnd()); + } + + @Test + public void shouldFindByBookerIdAndStatusIsOrderByEndDesc() { + bookingRepository.save(Booking.builder() + .start(LocalDateTime.now().plusDays(1)) + .end(LocalDateTime.now().plusDays(2)) + .item(item) + .booker(booker) + .status(BookingStatus.WAITING) + .build()); + + List bookings = bookingRepository + .findByBookerIdAndStatusIsOrderByEndDesc(booker.getId(), BookingStatus.WAITING); + + assertThat(bookings).hasSize(1); + assertThat(bookings.getFirst().getStatus()).isEqualTo(BookingStatus.WAITING); + } + + @Test + public void shouldFindByBookerIdAndStartIsBeforeAndEndIsBeforeOrderByEndDesc() { + bookingRepository.save(Booking.builder() + .start(LocalDateTime.now().minusDays(2)) + .end(LocalDateTime.now().minusDays(1)) + .item(item) + .booker(booker) + .status(BookingStatus.APPROVED) + .build()); + + List bookings = bookingRepository + .findByBookerIdAndStartIsBeforeAndEndIsBeforeOrderByEndDesc( + booker.getId(), + LocalDateTime.now(), + LocalDateTime.now() + ); + + assertThat(bookings).hasSize(1); + } + + @Test + public void shouldFindByBookerIdAndStartIsAfterOrderByEndDesc() { + bookingRepository.save(Booking.builder() + .start(LocalDateTime.now().plusDays(1)) + .end(LocalDateTime.now().plusDays(2)) + .item(item) + .booker(booker) + .status(BookingStatus.APPROVED) + .build()); + + List bookings = bookingRepository.findByBookerIdAndStartIsAfterOrderByEndDesc( + booker.getId(), + LocalDateTime.now() + ); + + assertThat(bookings).hasSize(1); + } + + @Test + public void shouldFindByItemOwnerIdAndStartIsAfterOrderByEndDesc() { + bookingRepository.save(Booking.builder() + .start(LocalDateTime.now().plusDays(1)) + .end(LocalDateTime.now().plusDays(2)) + .item(item) + .booker(booker) + .status(BookingStatus.APPROVED) + .build()); + + List bookings = bookingRepository.findByItemOwnerIdAndStartIsAfterOrderByEndDesc( + owner.getId(), + LocalDateTime.now() + ); + + assertThat(bookings).hasSize(1); + } + + @Test + public void shouldFindByBookerIdAndEndIsBeforeOrderByEndDesc() { + bookingRepository.save(Booking.builder() + .start(LocalDateTime.now().minusDays(2)) + .end(LocalDateTime.now().minusDays(1)) + .item(item) + .booker(booker) + .status(BookingStatus.APPROVED) + .build()); + + List bookings = bookingRepository.findByBookerIdAndEndIsBeforeOrderByEndDesc( + booker.getId(), + LocalDateTime.now() + ); + + assertThat(bookings).hasSize(1); + } + + @Test + public void shouldFindByItemOwnerIdOrderByEndDesc() { + bookingRepository.save(Booking.builder() + .start(LocalDateTime.now().plusDays(1)) + .end(LocalDateTime.now().plusDays(2)) + .item(item) + .booker(booker) + .status(BookingStatus.APPROVED) + .build()); + + List bookings = bookingRepository.findByItemOwnerIdOrderByEndDesc(owner.getId()); + + assertThat(bookings).hasSize(1); + } + + @Test + public void shouldFindByItemOwnerIdAndStatusIsOrderByEndDesc() { + bookingRepository.save(Booking.builder() + .start(LocalDateTime.now().plusDays(1)) + .end(LocalDateTime.now().plusDays(2)) + .item(item) + .booker(booker) + .status(BookingStatus.WAITING) + .build()); + + List bookings = bookingRepository.findByItemOwnerIdAndStatusIsOrderByEndDesc( + owner.getId(), + BookingStatus.WAITING + ); + + assertThat(bookings).hasSize(1); + assertThat(bookings.getFirst().getStatus()).isEqualTo(BookingStatus.WAITING); + } + + @Test + public void shouldFindByItemOwnerIdAndStartIsBeforeAndEndIsBeforeOrderByEndDesc() { + bookingRepository.save(Booking.builder() + .start(LocalDateTime.now().minusDays(2)) + .end(LocalDateTime.now().minusDays(1)) + .item(item) + .booker(booker) + .status(BookingStatus.APPROVED) + .build()); + + List bookings = bookingRepository.findByItemOwnerIdAndStartIsBeforeAndEndIsBeforeOrderByEndDesc( + owner.getId(), + LocalDateTime.now(), + LocalDateTime.now() + ); + + assertThat(bookings).hasSize(1); + } + + @Test + public void shouldFindByItemOwnerIdAndEndIsBeforeOrderByEndDesc() { + bookingRepository.save(Booking.builder() + .start(LocalDateTime.now().minusDays(2)) + .end(LocalDateTime.now().minusDays(1)) + .item(item) + .booker(booker) + .status(BookingStatus.APPROVED) + .build()); + + List bookings = bookingRepository.findByItemOwnerIdAndEndIsBeforeOrderByEndDesc( + owner.getId(), + LocalDateTime.now() + ); + + assertThat(bookings).hasSize(1); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/booking/service/BookingServiceImplTest.java b/server/src/test/java/ru/practicum/shareit/booking/service/BookingServiceImplTest.java new file mode 100644 index 0000000..419c94a --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/booking/service/BookingServiceImplTest.java @@ -0,0 +1,318 @@ +package ru.practicum.shareit.booking.service; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import ru.practicum.shareit.booking.dao.BookingRepository; +import ru.practicum.shareit.booking.dto.BookingDto; +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.exceptions.BadRequestException; +import ru.practicum.shareit.exceptions.NotFoundException; +import ru.practicum.shareit.exceptions.ValidationException; +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.LocalDateTime; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class BookingServiceImplTest { + + @Mock + private BookingRepository bookingRepository; + + @Mock + private UserRepository userRepository; + + @Mock + private ItemRepository itemRepository; + + @InjectMocks + private BookingServiceImpl bookingService; + + private final LocalDateTime now = LocalDateTime.now(); + + @Test + public void shouldCreateBooking() { + BookingDto bookingDto = new BookingDto(); + bookingDto.setItemId(1L); + bookingDto.setStart(now.plusDays(1)); + bookingDto.setEnd(now.plusDays(2)); + + User user = new User(1L, "booker", "booker@example.com"); + Item item = new Item(1L, "item", "description", true, 2L, null); + Booking savedBooking = new Booking(); + savedBooking.setId(1L); + savedBooking.setStatus(BookingStatus.WAITING); + + when(userRepository.findById(1L)).thenReturn(Optional.of(user)); + when(itemRepository.findById(1L)).thenReturn(Optional.of(item)); + when(bookingRepository.save(any())).thenReturn(savedBooking); + + Booking result = bookingService.createBooking(1L, bookingDto); + + assertThat(result.getId()).isEqualTo(1L); + assertThat(result.getStatus()).isEqualTo(BookingStatus.WAITING); + verify(bookingRepository).save(any()); + } + + @Test + public void shouldThrowBadRequestExceptionWhenCreateBookingForUnavailableItem() { + BookingDto bookingDto = new BookingDto(); + bookingDto.setItemId(1L); + bookingDto.setStart(now.plusDays(1)); + bookingDto.setEnd(now.plusDays(2)); + + User user = new User(1L, "booker", "booker@example.com"); + Item item = new Item(1L, "item", "description", false, 2L, null); + + when(userRepository.findById(1L)).thenReturn(Optional.of(user)); + when(itemRepository.findById(1L)).thenReturn(Optional.of(item)); + + assertThatThrownBy(() -> bookingService.createBooking(1L, bookingDto)) + .isInstanceOf(BadRequestException.class); + } + + @Test + public void shouldThrowValidationExceptionWhenUserIsNotOwnerOrBooker() { + Long userId = 3L; // пользователь, который не является ни владельцем, ни арендатором + Long bookingId = 1L; + + User booker = new User(1L, "booker", "booker@test.com"); + User owner = new User(2L, "owner", "owner@test.com"); + + Item item = new Item(1L, "item", "desc", true, owner.getId(), null); + Booking booking = new Booking(); + booking.setId(bookingId); + booking.setBooker(booker); + booking.setItem(item); + + when(bookingRepository.findById(bookingId)).thenReturn(Optional.of(booking)); + + assertThatThrownBy(() -> bookingService.getBooking(userId, bookingId)) + .isInstanceOf(ValidationException.class) + .hasMessage("Только владелец или арендатор могут посмотреть бронирование"); + + verify(bookingRepository).findById(bookingId); + } + + @Test + public void shouldThrowNotFoundExceptionWhenUserNotFoundForBooking() { + BookingDto bookingDto = new BookingDto(); + bookingDto.setItemId(1L); + bookingDto.setStart(now.plusDays(1)); + bookingDto.setEnd(now.plusDays(2)); + + when(userRepository.findById(999L)).thenReturn(Optional.empty()); + + assertThatThrownBy(() -> bookingService.createBooking(999L, bookingDto)) + .isInstanceOf(NotFoundException.class); + + verify(itemRepository, never()).findById(any()); + } + + @Test + public void shouldThrowBadRequestExceptionWhenStartIsAfterEnd() { + BookingDto bookingDto = new BookingDto(); + bookingDto.setItemId(1L); + bookingDto.setStart(now.plusDays(2)); + bookingDto.setEnd(now.plusDays(1)); // End before start + + assertThatThrownBy(() -> bookingService.createBooking(1L, bookingDto)) + .isInstanceOf(BadRequestException.class) + .hasMessage("Начало не может быть после конца бронирования"); + + verify(userRepository, never()).findById(any()); + verify(itemRepository, never()).findById(any()); + } + + @Test + public void shouldGetBooking() { + User booker = new User(1L, "booker", "booker@example.com"); + Item item = new Item(1L, "item", "description", true, 2L, null); + Booking booking = new Booking(); + booking.setId(1L); + booking.setItem(item); + booking.setBooker(booker); + + when(bookingRepository.findById(1L)).thenReturn(Optional.of(booking)); // ← убрали пробел после ( + + Booking result = bookingService.getBooking(1L, 1L); + + assertThat(result.getId()).isEqualTo(1L); + verify(bookingRepository).findById(1L); + } + + @Test + public void shouldFindByBookerAndStateFuture() { + when(userRepository.findById(1L)).thenReturn(Optional.of(new User())); + when(bookingRepository.findByBookerIdAndStartIsAfterOrderByEndDesc(any(), any(LocalDateTime.class))) + .thenReturn(List.of(new Booking())); + + Collection result = bookingService.findByBookerAndState(1L, BookingState.FUTURE); + + assertThat(result).hasSize(1); + verify(bookingRepository).findByBookerIdAndStartIsAfterOrderByEndDesc(any(), any(LocalDateTime.class)); + } + + @Test + public void shouldFindByOwnerAndStateFuture() { + when(userRepository.findById(1L)).thenReturn(Optional.of(new User())); + when(bookingRepository.findByItemOwnerIdAndStartIsAfterOrderByEndDesc(any(), any(LocalDateTime.class))) + .thenReturn(List.of(new Booking())); + + Collection result = bookingService.findByOwnerAndState(1L, BookingState.FUTURE); + + assertThat(result).hasSize(1); + verify(bookingRepository).findByItemOwnerIdAndStartIsAfterOrderByEndDesc(any(), any(LocalDateTime.class)); + } + + @Test + public void shouldFindByBookerAndStatePast() { + when(userRepository.findById(1L)).thenReturn(Optional.of(new User())); + when(bookingRepository.findByBookerIdAndEndIsBeforeOrderByEndDesc(any(), any(LocalDateTime.class))) + .thenReturn(List.of(new Booking())); + + Collection result = bookingService.findByBookerAndState(1L, BookingState.PAST); + + assertThat(result).hasSize(1); + verify(bookingRepository).findByBookerIdAndEndIsBeforeOrderByEndDesc(any(), any(LocalDateTime.class)); + } + + @Test + public void shouldFindByOwnerAndStatePast() { + when(userRepository.findById(1L)).thenReturn(Optional.of(new User())); + when(bookingRepository.findByItemOwnerIdAndEndIsBeforeOrderByEndDesc(any(), any(LocalDateTime.class))) + .thenReturn(List.of(new Booking())); + + Collection result = bookingService.findByOwnerAndState(1L, BookingState.PAST); + + assertThat(result).hasSize(1); + verify(bookingRepository).findByItemOwnerIdAndEndIsBeforeOrderByEndDesc(any(), any(LocalDateTime.class)); + } + + @Test + public void shouldFindByBookerAndStateAll() { + when(userRepository.findById(1L)).thenReturn(Optional.of(new User())); + when(bookingRepository.findByBookerIdOrderByEndDesc(1L)).thenReturn(List.of(new Booking())); + + Collection result = bookingService.findByBookerAndState(1L, BookingState.ALL); + + assertThat(result).hasSize(1); + verify(bookingRepository).findByBookerIdOrderByEndDesc(1L); + } + + @Test + public void shouldFindByOwnerAndStateAll() { + when(userRepository.findById(1L)).thenReturn(Optional.of(new User())); + when(bookingRepository.findByItemOwnerIdOrderByEndDesc(1L)).thenReturn(List.of(new Booking())); + + Collection result = bookingService.findByOwnerAndState(1L, BookingState.ALL); + + assertThat(result).hasSize(1); + verify(bookingRepository).findByItemOwnerIdOrderByEndDesc(1L); + } + + @Test + public void shouldFindByOwnerAndStateRejected() { + when(userRepository.findById(1L)).thenReturn(Optional.of(new User())); + when(bookingRepository.findByItemOwnerIdAndStatusIsOrderByEndDesc(1L, BookingStatus.REJECTED)).thenReturn(List.of(new Booking())); + + Collection result = bookingService.findByOwnerAndState(1L, BookingState.REJECTED); + + assertThat(result).hasSize(1); + verify(bookingRepository).findByItemOwnerIdAndStatusIsOrderByEndDesc(1L, BookingStatus.REJECTED); + } + + @Test + public void shouldFindByBookerAndStateRejected() { + when(userRepository.findById(1L)).thenReturn(Optional.of(new User())); + when(bookingRepository.findByBookerIdAndStatusIsOrderByEndDesc(1L, BookingStatus.REJECTED)).thenReturn(List.of(new Booking())); + + Collection result = bookingService.findByBookerAndState(1L, BookingState.REJECTED); + + assertThat(result).hasSize(1); + verify(bookingRepository).findByBookerIdAndStatusIsOrderByEndDesc(1L, BookingStatus.REJECTED); + } + + @Test + public void shouldFindByBookerAndStateWaiting() { + when(userRepository.findById(1L)).thenReturn(Optional.of(new User())); + when(bookingRepository.findByBookerIdAndStatusIsOrderByEndDesc(1L, BookingStatus.WAITING)) + .thenReturn(List.of(new Booking())); + + Collection result = bookingService.findByBookerAndState(1L, BookingState.WAITING); + + assertThat(result).hasSize(1); + verify(bookingRepository).findByBookerIdAndStatusIsOrderByEndDesc(1L, BookingStatus.WAITING); + } + + @Test + public void shouldFindByOwnerAndStateWaiting() { + when(userRepository.findById(1L)).thenReturn(Optional.of(new User())); + when(bookingRepository.findByItemOwnerIdAndStatusIsOrderByEndDesc(1L, BookingStatus.WAITING)) + .thenReturn(List.of(new Booking())); + + Collection result = bookingService.findByOwnerAndState(1L, BookingState.WAITING); + + assertThat(result).hasSize(1); + verify(bookingRepository).findByItemOwnerIdAndStatusIsOrderByEndDesc(1L, BookingStatus.WAITING); + } + + @Test + public void shouldFindByBookerAndStateCurrent() { + when(userRepository.findById(1L)).thenReturn(Optional.of(new User())); + when(bookingRepository.findByBookerIdAndStartIsBeforeAndEndIsBeforeOrderByEndDesc(any(), any(LocalDateTime.class), any(LocalDateTime.class))) + .thenReturn(List.of(new Booking())); + + Collection result = bookingService.findByBookerAndState(1L, BookingState.CURRENT); + + assertThat(result).hasSize(1); + verify(bookingRepository).findByBookerIdAndStartIsBeforeAndEndIsBeforeOrderByEndDesc(any(), any(LocalDateTime.class), any(LocalDateTime.class)); + } + + @Test + public void shouldFindByOwnerAndStateCurrent() { + when(userRepository.findById(1L)).thenReturn(Optional.of(new User())); + + when(bookingRepository.findByItemOwnerIdAndStartIsBeforeAndEndIsBeforeOrderByEndDesc(any(), any(LocalDateTime.class), any(LocalDateTime.class))) + .thenReturn(List.of(new Booking())); + + Collection result = bookingService.findByOwnerAndState(1L, BookingState.CURRENT); + + assertThat(result).hasSize(1); + verify(bookingRepository).findByItemOwnerIdAndStartIsBeforeAndEndIsBeforeOrderByEndDesc(any(), any(LocalDateTime.class), any(LocalDateTime.class)); + } + + @Test + public void shouldUpdateBookingStatus() { + User owner = new User(2L, "owner", "owner@test.com"); + Item item = new Item(1L, "item", "desc", true, 2L, null); + Booking booking = new Booking(); + booking.setId(1L); + booking.setItem(item); + booking.setBooker(new User(1L, "booker", "booker@test.com")); + booking.setStatus(BookingStatus.WAITING); + + when(bookingRepository.findById(1L)).thenReturn(Optional.of(booking)); + when(bookingRepository.save(any())).thenReturn(booking); + + Booking result = bookingService.updateBookingStatus(2L, 1L, true); + + assertThat(result.getStatus()).isEqualTo(BookingStatus.APPROVED); + verify(bookingRepository).save(any()); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/item/controller/ItemControllerTest.java b/server/src/test/java/ru/practicum/shareit/item/controller/ItemControllerTest.java new file mode 100644 index 0000000..823b49c --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/item/controller/ItemControllerTest.java @@ -0,0 +1,127 @@ +package ru.practicum.shareit.item.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.model.Comment; +import ru.practicum.shareit.item.service.ItemServiceImpl; + +import java.util.Collections; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(ItemController.class) +public class ItemControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private ItemServiceImpl itemService; + + @Test + public void shouldGetItem() throws Exception { + ItemDto dto = new ItemDto(); + dto.setId(1L); + dto.setName("item"); + when(itemService.getItem(1L)).thenReturn(dto); + + mockMvc.perform(get("/items/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.name").value("item")); + } + + @Test + public void shouldGetItems() throws Exception { + ItemDto dto = new ItemDto(); + dto.setId(1L); + when(itemService.getAllOwnerItems(1L)).thenReturn(Collections.singletonList(dto)); + + mockMvc.perform(get("/items") + .header("X-Sharer-User-Id", 1L)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").value(1)); + } + + @Test + public void shouldGetNecessaryItems() throws Exception { + ItemDto dto = new ItemDto(); + dto.setId(1L); + when(itemService.getNecessaryItem("testText")).thenReturn(Collections.singletonList(dto)); + + mockMvc.perform(get("/items/search") + .param("text", "testText")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").value(1)); + } + + @Test + public void shouldAddItem() throws Exception { + ItemDto itemDto = new ItemDto(); + itemDto.setName("name"); + itemDto.setDescription("description"); + itemDto.setAvailable(true); + + ItemDto savedDto = new ItemDto(); + savedDto.setId(1L); + when(itemService.addItem(any(), anyLong())).thenReturn(savedDto); + + mockMvc.perform(post("/items") + .header("X-Sharer-User-Id", 1L) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(itemDto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1)); + } + + @Test + public void shouldUpdateItem() throws Exception { + ItemDto itemDto = new ItemDto(); + itemDto.setName("updated"); + + ItemDto updatedDto = new ItemDto(); + updatedDto.setId(1L); + updatedDto.setName("updated"); + when(itemService.updateItem(any(), anyLong(), anyLong())).thenReturn(updatedDto); + + mockMvc.perform(patch("/items/1") + .header("X-Sharer-User-Id", 1L) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(itemDto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.name").value("updated")); + } + + @Test + public void shouldAddComment() throws Exception { + Comment comment = new Comment(); + CommentDto commentDto = new CommentDto(); + commentDto.setId(1L); + commentDto.setText("comment"); + when(itemService.addComment(anyLong(), anyLong(), any())).thenReturn(commentDto); + + mockMvc.perform(post("/items/1/comment") + .header("X-Sharer-User-Id", 1L) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(comment))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.text").value("comment")); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/item/dao/CommentDtoTest.java b/server/src/test/java/ru/practicum/shareit/item/dao/CommentDtoTest.java new file mode 100644 index 0000000..b87657e --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/item/dao/CommentDtoTest.java @@ -0,0 +1,46 @@ +package ru.practicum.shareit.item.dao; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.item.dto.CommentDto; + +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.*; + +class CommentDtoTest { + + private CommentDto commentDto; + + @BeforeEach + void setUp() { + commentDto = new CommentDto(); + } + + @Test + void testNoArgsConstructor() { + assertNotNull(commentDto); + assertNull(commentDto.getId()); + assertNull(commentDto.getItemId()); + assertNull(commentDto.getAuthorName()); + assertNull(commentDto.getText()); + assertNull(commentDto.getCreated()); + } + + @Test + void testAllArgsConstructor() { + Long id = 1L; + Long itemId = 10L; + String authorName = "John Doe"; + String text = "Great item!"; + LocalDateTime created = LocalDateTime.now(); + + CommentDto dto = new CommentDto(id, itemId, authorName, text, created); + + assertEquals(id, dto.getId()); + assertEquals(itemId, dto.getItemId()); + assertEquals(authorName, dto.getAuthorName()); + assertEquals(text, dto.getText()); + assertEquals(created, dto.getCreated()); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/item/dao/CommentMapperTest.java b/server/src/test/java/ru/practicum/shareit/item/dao/CommentMapperTest.java new file mode 100644 index 0000000..e5924e1 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/item/dao/CommentMapperTest.java @@ -0,0 +1,37 @@ +package ru.practicum.shareit.item.dao; + +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.model.Comment; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CommentMapperTest { + + @Test + public void shouldMapCommentToCommentDto() { + User author = new User(); + author.setName("Name"); + Item item = new Item(); + item.setId(1L); + Comment comment = new Comment(); + comment.setId(100L); + comment.setItem(item); + comment.setAuthor(author); + comment.setText("Comment"); + LocalDateTime created = LocalDateTime.now(); + comment.setCreated(created); + + CommentDto commentDto = CommentMapper.toCommentDto(comment); + + assertEquals(comment.getId(), commentDto.getId()); + assertEquals(comment.getItem().getId(), commentDto.getItemId()); + assertEquals(comment.getAuthor().getName(), commentDto.getAuthorName()); + assertEquals(comment.getText(), commentDto.getText()); + assertEquals(comment.getCreated(), commentDto.getCreated()); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/item/dao/CommentRepositoryTest.java b/server/src/test/java/ru/practicum/shareit/item/dao/CommentRepositoryTest.java new file mode 100644 index 0000000..6eba409 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/item/dao/CommentRepositoryTest.java @@ -0,0 +1,85 @@ +package ru.practicum.shareit.item.dao; + +import lombok.RequiredArgsConstructor; +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.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import ru.practicum.shareit.item.model.Comment; +import ru.practicum.shareit.item.model.Item; +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.assertj.core.api.Assertions.assertThat; + +@DataJpaTest +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class CommentRepositoryTest { + + private final CommentRepository commentRepository; + private final ItemRepository itemRepository; + private final UserRepository userRepository; + + private User author; + private Item item; + + @BeforeEach + void setUp() { + User testUser1 = new User(null, "name1", "email1@test.com"); + User user = userRepository.save(testUser1); + + Item testItem1 = new Item(null, "name1", "desc1", true, user.getId(), + null); + item = itemRepository.save(testItem1); + + User testUser2 = new User(null, "name2", "email2@test.com"); + author = userRepository.save(testUser2); + } + + @Test + public void shouldFindByItemId() { + Comment comment = new Comment(); + comment.setAuthor(author); + comment.setItem(item); + comment.setText("ertyuio"); + comment.setCreated(LocalDateTime.now()); + + commentRepository.save(comment); + + Collection comments = commentRepository.findByItemId(item.getId()); + + assertThat(comments).hasSize(1); + + Comment c = comments.iterator().next(); + + assertThat(c.getId()).isEqualTo(comment.getId()); + assertThat(c.getAuthor()).isEqualTo(author); + assertThat(c.getItem()).isEqualTo(item); + } + + @Test + public void shouldFindByItemOwnerId() { + Comment comment = new Comment(); + comment.setAuthor(author); + comment.setItem(item); + comment.setText("wertyu"); + comment.setCreated(LocalDateTime.now()); + + commentRepository.save(comment); + + Collection comments = commentRepository.findByItemOwnerId(item.getOwnerId()); + + assertThat(comments).hasSize(1); + + Comment c = comments.iterator().next(); + + assertThat(c.getId()).isEqualTo(comment.getId()); + assertThat(c.getAuthor()).isEqualTo(author); + assertThat(c.getItem()).isEqualTo(item); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/item/dao/ItemDtoJsonTest.java b/server/src/test/java/ru/practicum/shareit/item/dao/ItemDtoJsonTest.java new file mode 100644 index 0000000..9101e41 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/item/dao/ItemDtoJsonTest.java @@ -0,0 +1,186 @@ +package ru.practicum.shareit.item.dao; + +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 org.springframework.boot.test.json.JsonContent; +import com.fasterxml.jackson.databind.ObjectMapper; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.dto.CommentDto; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Collection; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@JsonTest +class ItemDtoJsonTest { + + @Autowired + private JacksonTester jacksonTester; + + @Autowired + private ObjectMapper objectMapper; + + private ItemDto itemDto; + private LocalDateTime now; + private CommentDto commentDto; + + @BeforeEach + void setUp() { + now = LocalDateTime.of(2025, 3, 3, 12, 0, 0); + + commentDto = new CommentDto(); + commentDto.setId(1L); + commentDto.setText("testItem"); + commentDto.setAuthorName("User"); + commentDto.setCreated(now); + + Collection comments = List.of(commentDto); + + itemDto = new ItemDto(); + itemDto.setId(1L); + itemDto.setName("Item"); + itemDto.setDescription("Description"); + itemDto.setAvailable(true); + itemDto.setLastBooking(now.minusDays(1)); + itemDto.setNextBooking(now.plusDays(1)); + itemDto.setComments(comments); + itemDto.setRequestId(10L); + } + + @Test + void shouldSerializeItemDto() throws IOException { + JsonContent json = jacksonTester.write(itemDto); + + assertThat(json).hasJsonPathNumberValue("$.id"); + assertThat(json).hasJsonPathStringValue("$.name"); + assertThat(json).hasJsonPathStringValue("$.description"); + assertThat(json).hasJsonPathBooleanValue("$.available"); + assertThat(json).hasJsonPathStringValue("$.lastBooking"); + assertThat(json).hasJsonPathStringValue("$.nextBooking"); + assertThat(json).hasJsonPathArrayValue("$.comments"); + assertThat(json).hasJsonPathNumberValue("$.requestId"); + + assertThat(json).extractingJsonPathNumberValue("$.id") + .isEqualTo(1); + assertThat(json).extractingJsonPathStringValue("$.name") + .isEqualTo("Item"); + assertThat(json).extractingJsonPathStringValue("$.description") + .isEqualTo("Description"); + assertThat(json).extractingJsonPathBooleanValue("$.available") + .isTrue(); + assertThat(json).extractingJsonPathStringValue("$.lastBooking") + .satisfies(lastBookingStr -> { + assertThat(lastBookingStr).startsWith(now.minusDays(1).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); + }); + + assertThat(json).extractingJsonPathStringValue("$.nextBooking") + .satisfies(nextBookingStr -> { + assertThat(nextBookingStr).startsWith(now.plusDays(1).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); + }); + + assertThat(json).extractingJsonPathNumberValue("$.requestId") + .isEqualTo(10); + assertThat(json).extractingJsonPathArrayValue("$.comments") + .hasSize(1); + } + + @Test + void shouldDeserializeItemDto() throws IOException { + String json = String.format( + "{" + + "\"id\":1," + + "\"name\":\"Item\"," + + "\"description\":\"Description\"," + + "\"available\":true," + + "\"lastBooking\":\"%s\"," + + "\"nextBooking\":\"%s\"," + + "\"comments\":[" + + "{" + + "\"id\":1," + + "\"text\":\"testItem\"," + + "\"authorName\":\"User\"," + + "\"created\":\"%s\"" + + "}" + + "]," + + "\"requestId\":10" + + "}", + now.minusDays(1).toString(), + now.plusDays(1).toString(), + now.toString() + ); + + ItemDto deserializedItem = jacksonTester.parseObject(json); + + assertThat(deserializedItem.getId()).isEqualTo(1L); + assertThat(deserializedItem.getName()).isEqualTo("Item"); + assertThat(deserializedItem.getDescription()).isEqualTo("Description"); + assertThat(deserializedItem.getAvailable()).isTrue(); + assertThat(deserializedItem.getLastBooking()).isEqualTo(now.minusDays(1)); + assertThat(deserializedItem.getNextBooking()).isEqualTo(now.plusDays(1)); + assertThat(deserializedItem.getRequestId()).isEqualTo(10L); + assertThat(deserializedItem.getComments()).hasSize(1); + + CommentDto deserializedComment = deserializedItem.getComments().iterator().next(); + assertThat(deserializedComment.getId()).isEqualTo(1L); + assertThat(deserializedComment.getText()).isEqualTo("testItem"); + assertThat(deserializedComment.getAuthorName()).isEqualTo("User"); + assertThat(deserializedComment.getCreated()).isEqualTo(now); + } + + @Test + void shouldDeserializeItemDtoWithNullFields() throws IOException { + String json = "{" + + "\"id\":null," + + "\"name\":null," + + "\"description\":null," + + "\"available\":null," + + "\"lastBooking\":null," + + "\"nextBooking\":null," + + "\"comments\":null," + + "\"requestId\":null" + + "}"; + + ItemDto deserializedItem = jacksonTester.parseObject(json); + + assertThat(deserializedItem.getId()).isNull(); + assertThat(deserializedItem.getName()).isNull(); + assertThat(deserializedItem.getDescription()).isNull(); + assertThat(deserializedItem.getAvailable()).isNull(); + assertThat(deserializedItem.getLastBooking()).isNull(); + assertThat(deserializedItem.getNextBooking()).isNull(); + assertThat(deserializedItem.getComments()).isNull(); + assertThat(deserializedItem.getRequestId()).isNull(); + } + + @Test + void shouldDeserializeItemDtoWithEmptyComments() throws IOException { + String json = "{" + + "\"id\":1," + + "\"name\":\"Item\"," + + "\"description\":\"Description\"," + + "\"available\":true," + + "\"lastBooking\":null," + + "\"nextBooking\":null," + + "\"comments\":[]," + + "\"requestId\":null" + + "}"; + + ItemDto deserializedItem = jacksonTester.parseObject(json); + + assertThat(deserializedItem.getId()).isEqualTo(1L); + assertThat(deserializedItem.getName()).isEqualTo("Item"); + assertThat(deserializedItem.getDescription()).isEqualTo("Description"); + assertThat(deserializedItem.getAvailable()).isTrue(); + assertThat(deserializedItem.getLastBooking()).isNull(); + assertThat(deserializedItem.getNextBooking()).isNull(); + assertThat(deserializedItem.getComments()).isEmpty(); + assertThat(deserializedItem.getRequestId()).isNull(); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/item/dao/ItemDtoTest.java b/server/src/test/java/ru/practicum/shareit/item/dao/ItemDtoTest.java new file mode 100644 index 0000000..cc6341b --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/item/dao/ItemDtoTest.java @@ -0,0 +1,59 @@ +package ru.practicum.shareit.item.dao; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.dto.ItemDto; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class ItemDtoTest { + + private ItemDto itemDto; + + @BeforeEach + void setUp() { + itemDto = new ItemDto(); + } + + @Test + void testNoArgsConstructor() { + assertNotNull(itemDto); + assertNull(itemDto.getId()); + assertNull(itemDto.getName()); + assertNull(itemDto.getDescription()); + assertNull(itemDto.getAvailable()); + assertNull(itemDto.getLastBooking()); + assertNull(itemDto.getNextBooking()); + assertNull(itemDto.getComments()); + assertNull(itemDto.getRequestId()); + } + + @Test + void testAllArgsConstructor() { + Long id = 1L; + String name = "Test Item"; + String description = "Test Description"; + Boolean available = true; + LocalDateTime lastBooking = LocalDateTime.now().minusDays(1); + LocalDateTime nextBooking = LocalDateTime.now().plusDays(1); + Collection comments = List.of(new CommentDto()); + Long requestId = 10L; + + ItemDto dto = new ItemDto(id, name, description, available, + lastBooking, nextBooking, comments, requestId); + + assertEquals(id, dto.getId()); + assertEquals(name, dto.getName()); + assertEquals(description, dto.getDescription()); + assertEquals(available, dto.getAvailable()); + assertEquals(lastBooking, dto.getLastBooking()); + assertEquals(nextBooking, dto.getNextBooking()); + assertEquals(comments, dto.getComments()); + assertEquals(requestId, dto.getRequestId()); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/item/dao/ItemMapperTest.java b/server/src/test/java/ru/practicum/shareit/item/dao/ItemMapperTest.java new file mode 100644 index 0000000..eb3d812 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/item/dao/ItemMapperTest.java @@ -0,0 +1,44 @@ +package ru.practicum.shareit.item.dao; + +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.model.Item; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class ItemMapperTest { + + @Test + public void shouldMapItemDtoToItem() { + ItemDto itemDto = new ItemDto(); + itemDto.setId(1L); + itemDto.setName("name"); + itemDto.setDescription("Item description"); + itemDto.setAvailable(true); + + Item item = ItemMapper.toItem(itemDto); + + assertEquals(itemDto.getId(), item.getId()); + assertEquals(itemDto.getName(), item.getName()); + assertEquals(itemDto.getDescription(), item.getDescription()); + assertEquals(itemDto.getAvailable(), item.getIsAvailable()); + } + + @Test + public void shouldMapItemToItemDto() { + Item item = new Item(); + item.setId(2L); + item.setName("name"); + item.setDescription("Description"); + item.setIsAvailable(false); + + ItemDto itemDto = ItemMapper.toItemDto(item); + + assertEquals(item.getId(), itemDto.getId()); + assertEquals(item.getName(), itemDto.getName()); + assertEquals(item.getDescription(), itemDto.getDescription()); + assertEquals(item.getIsAvailable(), itemDto.getAvailable()); + assertNull(itemDto.getRequestId()); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/item/dao/ItemRepositoryTest.java b/server/src/test/java/ru/practicum/shareit/item/dao/ItemRepositoryTest.java new file mode 100644 index 0000000..179a239 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/item/dao/ItemRepositoryTest.java @@ -0,0 +1,94 @@ +package ru.practicum.shareit.item.dao; + +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +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.LocalDate; +import java.util.Collection; +import java.util.List; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +@DataJpaTest +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class ItemRepositoryTest { + + private final ItemRepository itemRepository; + private final UserRepository userRepository; + private final ItemRequestRepository itemRequestRepository; + + @Test + void shouldFindByOwnerId() { + User testUser = new User(null, "name", "email@test.com"); + User savedUser = userRepository.save(testUser); + + Item testItem1 = new Item(null, "name1", "desc1", true, savedUser.getId(), null); + Item item1 = itemRepository.save(testItem1); + + Item testItem2 = new Item(null, "name2", "desc2", true, savedUser.getId(), null); + Item item2 = itemRepository.save(testItem2); + + Collection items = itemRepository.findByOwnerId(savedUser.getId()); + + assertThat(items).hasSize(2); + assertThat(items).extracting(Item::getId).containsExactlyInAnyOrder(item1.getId(), item2.getId()); + } + + @Test + public void shouldFindAllByRequestIdIn() { + User testUser1 = new User(null, "name1", "email1@test.com"); + User savedUser1 = userRepository.save(testUser1); + + ItemRequest request1 = new ItemRequest(); + request1.setRequestorId(savedUser1.getId()); + request1.setDescription("descript1"); + request1.setCreated(LocalDate.now()); + request1 = itemRequestRepository.save(request1); + + ItemRequest request2 = new ItemRequest(); + request2.setRequestorId(savedUser1.getId()); + request2.setDescription("descript2"); + request2.setCreated(LocalDate.now()); + request2 = itemRequestRepository.save(request2); + + User testUser2 = new User(null, "name", "email@test.com"); + User savedUser2 = userRepository.save(testUser2); + + Item testItem1 = new Item(null, "name1", "desc1", true, savedUser2.getId(), + request1.getId()); + Item item1 = itemRepository.save(testItem1); + + Item testItem2 = new Item(null, "name2", "desc2", true, savedUser2.getId(), + request2.getId()); + Item item2 = itemRepository.save(testItem2); + + Collection items = itemRepository.findAllByRequestIdIn(List.of(request1.getId(), request2.getId())); + + assertThat(items).hasSize(2); + assertThat(items).extracting(Item::getId).containsExactlyInAnyOrder(item1.getId(), item2.getId()); + } + + @Test + public void shouldSearchByDescription() { + User testUser = new User(null, "name", "email@test.com"); + User savedUser = userRepository.save(testUser); + + Item testItem1 = new Item(null, "name1", "desc1", true, savedUser.getId(), + null); + Item item1 = itemRepository.save(testItem1); + + Collection items = itemRepository.searchByNameOrDescription(item1.getDescription()); + + assertThat(items).hasSize(1); + assertThat(items.iterator().next().getId()).isEqualTo(item1.getId()); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/item/service/ItemServiceImplTest.java b/server/src/test/java/ru/practicum/shareit/item/service/ItemServiceImplTest.java new file mode 100644 index 0000000..848f659 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/item/service/ItemServiceImplTest.java @@ -0,0 +1,422 @@ +package ru.practicum.shareit.item.service; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import ru.practicum.shareit.booking.dao.BookingRepository; +import ru.practicum.shareit.booking.model.BookingStatus; +import ru.practicum.shareit.booking.model.LastAndNextDate; +import ru.practicum.shareit.exceptions.BadRequestException; +import ru.practicum.shareit.exceptions.NotFoundException; +import ru.practicum.shareit.item.dao.CommentRepository; +import ru.practicum.shareit.item.dao.ItemRepository; +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.model.Comment; +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 ru.practicum.shareit.user.service.UserServiceImpl; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class ItemServiceImplTest { + + @Mock + private ItemRepository itemRepository; + + @Mock + private CommentRepository commentRepository; + + @Mock + private BookingRepository bookingRepository; + + @Mock + private ItemRequestRepository itemRequestRepository; + + @Mock + private UserRepository userRepository; + + @Mock + private UserServiceImpl userService; + + @InjectMocks + private ItemServiceImpl itemService; + + + + @Test + public void shouldGetItem() { + Item item = new Item(1L, "name", "desc", true, 1L, null); + when(itemRepository.findById(1L)).thenReturn(Optional.of(item)); + when(commentRepository.findByItemId(1L)).thenReturn(List.of()); + + ItemDto result = itemService.getItem(1L); + + assertThat(result.getId()).isEqualTo(1L); + assertThat(result.getName()).isEqualTo("name"); + verify(itemRepository).findById(1L); + } + + @Test + public void shouldThrowNotFoundExceptionWhenGetItemWithInvalidId() { + when(itemRepository.findById(999L)).thenReturn(Optional.empty()); + + assertThatThrownBy(() -> itemService.getItem(999L)) + .isInstanceOf(NotFoundException.class); + + verify(itemRepository).findById(999L); + verify(commentRepository, never()).findByItemId(anyLong()); + } + + @Test + public void shouldSearchItems() { + Item item = new Item(1L, "item", "description", true, 1L, null); + when(itemRepository.searchByNameOrDescription("desc")).thenReturn(List.of(item)); + + Collection result = itemService.getNecessaryItem("desc"); + + assertThat(result).hasSize(1); + assertThat(result.iterator().next().getName()).isEqualTo("item"); + verify(itemRepository).searchByNameOrDescription("desc"); + } + + @Test + public void shouldReturnEmptyListWhenSearchTextIsBlank() { + Collection result = itemService.getNecessaryItem(" "); + + assertThat(result).isEmpty(); + verify(itemRepository, never()).searchByNameOrDescription(any()); + } + + @Test + public void shouldGetItems() { + Item item = new Item(1L, "name", "desc", true, 1L, null); + + when(itemRepository.findByOwnerId(1L)).thenReturn(List.of(item)); + when(bookingRepository.findLastAndNextDatesByOwnerId(1L)).thenReturn(List.of()); + when(commentRepository.findByItemOwnerId(1L)).thenReturn(List.of()); + + Collection result = itemService.getAllOwnerItems(1L); + + assertThat(result).hasSize(1); + verify(itemRepository).findByOwnerId(1L); + } + + @Test + public void shouldAddItem() { + ItemDto itemDto = new ItemDto(); + itemDto.setName("item"); + itemDto.setDescription("desc"); + itemDto.setAvailable(true); + itemDto.setRequestId(null); + + Item savedItem = new Item(1L, "item", "desc", true, 1L, null); + + when(userRepository.existsById(1L)).thenReturn(true); + when(itemRepository.save(any(Item.class))).thenReturn(savedItem); + + ItemDto result = itemService.addItem(itemDto, 1L); + + assertThat(result.getId()).isEqualTo(1L); + verify(userRepository).existsById(1L); + verify(itemRepository).save(any()); + } + + @Test + public void shouldAddItemWithRequestId() { + Long userId = 1L; + Long requestId = 10L; + + ItemDto itemDto = new ItemDto(); + itemDto.setName("item"); + itemDto.setDescription("desc"); + itemDto.setAvailable(true); + itemDto.setRequestId(requestId); + + ItemRequest request = new ItemRequest(); + request.setId(requestId); + + Item savedItem = new Item(1L, "item", "desc", true, userId, requestId); + + when(userRepository.existsById(userId)).thenReturn(true); + when(itemRequestRepository.findById(requestId)).thenReturn(Optional.of(request)); + when(itemRepository.save(any(Item.class))).thenReturn(savedItem); + + ItemDto result = itemService.addItem(itemDto, userId); + + assertThat(result.getId()).isEqualTo(1L); + assertThat(result.getRequestId()).isEqualTo(requestId); + verify(itemRequestRepository).findById(requestId); + } + + @Test + public void shouldGetItemWithComments() { + Long itemId = 1L; + Long ownerId = 1L; + Long authorId = 2L; + + Item item = Item.builder() + .id(itemId) + .name("name") + .description("desc") + .isAvailable(true) + .ownerId(ownerId) + .build(); + + User author = User.builder() + .id(authorId) + .name("author") + .email("author@test.com") + .build(); + + Comment comment1 = Comment.builder() + .id(1L) + .text("comment1") + .item(item) + .author(author) // Добавляем автора + .build(); + + Comment comment2 = Comment.builder() + .id(2L) + .text("comment2") + .item(item) + .author(author) // Добавляем автора + .build(); + + List comments = List.of(comment1, comment2); + + when(itemRepository.findById(itemId)).thenReturn(Optional.of(item)); + when(commentRepository.findByItemId(itemId)).thenReturn(comments); + + ItemDto result = itemService.getItem(itemId); + + assertThat(result.getId()).isEqualTo(itemId); + assertThat(result.getComments()).hasSize(2); + assertThat(result.getComments()).extracting("text").containsExactly("comment1", "comment2"); + assertThat(result.getComments()).extracting("authorName").containsExactly("author", "author"); + } + + @Test + public void shouldThrowNotFoundExceptionWhenAddItemWithInvalidRequestId() { + Long userId = 1L; + Long requestId = 999L; + + ItemDto itemDto = new ItemDto(); + itemDto.setName("item"); + itemDto.setDescription("desc"); + itemDto.setAvailable(true); + itemDto.setRequestId(requestId); + + when(userRepository.existsById(userId)).thenReturn(true); + when(itemRequestRepository.findById(requestId)).thenReturn(Optional.empty()); + + assertThatThrownBy(() -> itemService.addItem(itemDto, userId)) + .isInstanceOf(NotFoundException.class); + + verify(itemRepository, never()).save(any()); + } + + @Test + public void shouldReturnEmptyListWhenOwnerHasNoItems() { + Long ownerId = 1L; + + when(itemRepository.findByOwnerId(ownerId)).thenReturn(Collections.emptyList()); + + Collection result = itemService.getAllOwnerItems(ownerId); + + assertThat(result).isEmpty(); + verify(commentRepository, never()).findByItemOwnerId(anyLong()); + verify(bookingRepository, never()).findLastAndNextDatesByOwnerId(anyLong()); + } + + @Test + public void shouldReturnEmptyListWhenSearchTextIsNull() { + Collection result = itemService.getNecessaryItem(null); + + assertThat(result).isEmpty(); + verify(itemRepository, never()).searchByNameOrDescription(any()); + } + + @Test + public void shouldUpdateItem() { + Long userId = 1L; + Long itemId = 1L; + + Item currentItem = new Item(itemId, "old", "old", true, userId, null); + ItemDto updateDto = new ItemDto(); + updateDto.setName("new"); + updateDto.setDescription("new"); + updateDto.setAvailable(false); + + when(userRepository.existsById(userId)).thenReturn(true); + when(itemRepository.findById(itemId)).thenReturn(Optional.of(currentItem)); + when(itemRepository.save(any(Item.class))).thenReturn(currentItem); + + ItemDto result = itemService.updateItem(updateDto, itemId, userId); + + assertThat(result.getName()).isEqualTo("new"); + assertThat(result.getDescription()).isEqualTo("new"); + assertThat(result.getAvailable()).isFalse(); + + verify(userRepository).existsById(userId); + verify(itemRepository).findById(itemId); + verify(itemRepository).save(any(Item.class)); + } + + @Test + public void shouldThrowNotFoundExceptionWhenUpdateNonExistentItem() { + Long userId = 1L; + Long nonExistentItemId = 999L; + ItemDto updateDto = new ItemDto(); + updateDto.setName("new"); + + when(userRepository.existsById(userId)).thenReturn(true); + when(itemRepository.findById(nonExistentItemId)).thenReturn(Optional.empty()); + + assertThatThrownBy(() -> itemService.updateItem(updateDto, nonExistentItemId, userId)) + .isInstanceOf(NotFoundException.class); + + verify(userRepository).existsById(userId); + verify(itemRepository).findById(nonExistentItemId); + verify(itemRepository, never()).save(any()); + } + + @Test + public void shouldAddComment() { + Long userId = 1L; + Long itemId = 1L; + String text = "comment"; + + User user = new User(userId, "name", "email@test.com"); + Item item = new Item(itemId, "item", "description", true, 2L, null); + + Comment commentToSave = Comment.builder() + .text(text) + .build(); + + Comment savedComment = Comment.builder() + .id(1L) + .text(text) + .author(user) + .item(item) + .created(LocalDateTime.now()) + .build(); + + when(userRepository.findById(userId)).thenReturn(Optional.of(user)); + when(itemRepository.findById(itemId)).thenReturn(Optional.of(item)); + when(bookingRepository.existsByItemIdAndBookerIdAndStatusIsAndEndBefore( + eq(itemId), eq(userId), eq(BookingStatus.APPROVED), any(LocalDateTime.class))) + .thenReturn(true); + when(commentRepository.save(any(Comment.class))).thenReturn(savedComment); + + CommentDto result = itemService.addComment(itemId, userId, commentToSave); + + assertThat(result.getText()).isEqualTo(text); + assertThat(result.getId()).isEqualTo(1L); + + verify(userRepository).findById(userId); + verify(itemRepository).findById(itemId); + verify(bookingRepository).existsByItemIdAndBookerIdAndStatusIsAndEndBefore( + eq(itemId), eq(userId), eq(BookingStatus.APPROVED), any(LocalDateTime.class)); + verify(commentRepository).save(any(Comment.class)); + } + + @Test + void shouldThrowBadRequestExceptionWhenUserNeverBookedItem() { + Long itemId = 1L; + Long userId = 1L; + Comment comment = new Comment(); + comment.setText("zwexrciytvuiubh"); + + User user = new User(1L, "oleg", "oleg@test.com"); + Item item = new Item(itemId, "name", "desc", true, 1L, null); + + when(userRepository.findById(userId)).thenReturn(Optional.of(user)); + when(itemRepository.findById(itemId)).thenReturn(Optional.of(item)); + when(bookingRepository.existsByItemIdAndBookerIdAndStatusIsAndEndBefore( + eq(itemId), eq(userId), eq(BookingStatus.APPROVED), any(LocalDateTime.class))) + .thenReturn(false); + + BadRequestException exception = assertThrows(BadRequestException.class, () -> + itemService.addComment(itemId, userId, comment) + ); + + assertThat(exception.getMessage()).isEqualTo("Пользователь никогда не брал предмет в аренду"); + + verify(commentRepository, never()).save(any()); + } + + @Test + public void shouldGetAllOwnerItemsWithBookingsAndComments() { + Long ownerId = 1L; + Long itemId1 = 1L; + Long itemId2 = 2L; + + Item item1 = new Item(itemId1, "item1", "desc1", true, ownerId, null); + Item item2 = new Item(itemId2, "item2", "desc2", true, ownerId, null); + List items = List.of(item1, item2); + + User author = new User(2L, "author", "author@test.com"); + + Comment comment1 = Comment.builder() + .id(1L) + .text("comment1") + .item(item1) + .author(author) + .build(); + Comment comment2 = Comment.builder() + .id(2L) + .text("comment2") + .item(item1) + .author(author) + .build(); + List comments = List.of(comment1, comment2); + + LastAndNextDate date1 = mock(LastAndNextDate.class); + when(date1.getItemId()).thenReturn(itemId1); + when(date1.getLastBooking()).thenReturn(LocalDateTime.now().minusDays(1)); + when(date1.getNextBooking()).thenReturn(LocalDateTime.now().plusDays(1)); + + LastAndNextDate date2 = mock(LastAndNextDate.class); + when(date2.getItemId()).thenReturn(itemId2); + when(date2.getLastBooking()).thenReturn(null); + when(date2.getNextBooking()).thenReturn(LocalDateTime.now().plusDays(2)); + + List dates = List.of(date1, date2); + + when(itemRepository.findByOwnerId(ownerId)).thenReturn(items); + when(commentRepository.findByItemOwnerId(ownerId)).thenReturn(comments); + when(bookingRepository.findLastAndNextDatesByOwnerId(ownerId)).thenReturn(dates); + + Collection result = itemService.getAllOwnerItems(ownerId); + + assertThat(result).hasSize(2); + + ItemDto resultItem1 = result.stream().filter(i -> i.getId().equals(itemId1)).findFirst().get(); + assertThat(resultItem1.getComments()).hasSize(2); + assertThat(resultItem1.getLastBooking()).isNotNull(); + assertThat(resultItem1.getNextBooking()).isNotNull(); + + ItemDto resultItem2 = result.stream().filter(i -> i.getId().equals(itemId2)).findFirst().get(); + assertThat(resultItem2.getComments()).isEmpty(); + assertThat(resultItem2.getLastBooking()).isNull(); + assertThat(resultItem2.getNextBooking()).isNotNull(); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/request/controller/ItemRequestControllerTest.java b/server/src/test/java/ru/practicum/shareit/request/controller/ItemRequestControllerTest.java new file mode 100644 index 0000000..5b92c06 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/request/controller/ItemRequestControllerTest.java @@ -0,0 +1,89 @@ +package ru.practicum.shareit.request.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.request.service.ItemRequestServiceImpl; + +import java.util.Collections; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(ItemRequestController.class) +public class ItemRequestControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private ItemRequestServiceImpl itemRequestService; + + @Test + public void shouldAddItemRequest() throws Exception { + ItemRequestDto requestDto = new ItemRequestDto(); + requestDto.setDescription("description"); + + ItemRequestDto responseDto = new ItemRequestDto(); + responseDto.setId(1L); + responseDto.setDescription("description"); + + when(itemRequestService.addItemRequest(any(ItemRequest.class), anyLong())).thenReturn(responseDto); + + mockMvc.perform(post("/requests") + .header("X-Sharer-User-Id", 1L) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(requestDto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.description").value("description")); + } + + @Test + public void shouldGetMyRequests() throws Exception { + ItemRequestDto dto = new ItemRequestDto(); + dto.setId(1L); + when(itemRequestService.getMyRequests(anyLong())).thenReturn(Collections.singletonList(dto)); + + mockMvc.perform(get("/requests") + .header("X-Sharer-User-Id", 1L)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").value(1)); + } + + @Test + public void shouldGetAllRequests() throws Exception { + ItemRequestDto dto = new ItemRequestDto(); + dto.setId(1L); + when(itemRequestService.getAllRequests()).thenReturn(Collections.singletonList(dto)); + + mockMvc.perform(get("/requests/all")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").value(1)); + } + + @Test + public void shouldGetRequestById() throws Exception { + ItemRequestDto dto = new ItemRequestDto(); + dto.setId(1L); + when(itemRequestService.getRequestById(anyLong())).thenReturn(dto); + + mockMvc.perform(get("/requests/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1)); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/request/dao/ItemRequestDtoTest.java b/server/src/test/java/ru/practicum/shareit/request/dao/ItemRequestDtoTest.java new file mode 100644 index 0000000..aea02f4 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/request/dao/ItemRequestDtoTest.java @@ -0,0 +1,51 @@ +package ru.practicum.shareit.request.dao; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.request.dto.ItemRequestDto; + +import java.time.LocalDate; +import java.util.List; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +public class ItemRequestDtoTest { + + private ItemRequestDto itemRequestDto; + private LocalDate testDate; + private List testItems; + + @BeforeEach + void setUp() { + itemRequestDto = new ItemRequestDto(); + testDate = LocalDate.now(); + + ItemDto item1 = new ItemDto(1L, "name", "desc", true, null, + null, null, null); + ItemDto item2 = new ItemDto(2L, "name", "desc", true, null, + null, null, null); + + testItems = List.of(item1, item2); + } + + @Test + void testNoArgsConstructor() { + assertNotNull(itemRequestDto); + assertNull(itemRequestDto.getId()); + assertNull(itemRequestDto.getItems()); + assertNull(itemRequestDto.getDescription()); + assertNull(itemRequestDto.getCreated()); + } + + @Test + void testAllArgsConstructor() { + ItemRequestDto dto = new ItemRequestDto(1L, "Description", testDate, testItems); + + assertThat(dto.getId()).isEqualTo(1L); + assertThat(dto.getDescription()).isEqualTo("Description"); + assertThat(dto.getCreated()).isEqualTo(testDate); + assertThat(dto.getItems()).isEqualTo(testItems); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/request/dao/ItemRequestMapperTest.java b/server/src/test/java/ru/practicum/shareit/request/dao/ItemRequestMapperTest.java new file mode 100644 index 0000000..58f16cc --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/request/dao/ItemRequestMapperTest.java @@ -0,0 +1,26 @@ +package ru.practicum.shareit.request.dao; + +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.model.ItemRequest; + +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ItemRequestMapperTest { + + @Test + public void shouldMapItemRequestToItemRequestDto() { + ItemRequest itemRequest = new ItemRequest(); + itemRequest.setId(10L); + itemRequest.setDescription("Нужен молоток"); + LocalDate created = LocalDate.of(2026, 4, 13); + itemRequest.setCreated(created); + + ItemRequestDto dto = ItemRequestMapper.mapToItemRequestDto(itemRequest); + + assertEquals(itemRequest.getId(), dto.getId()); + assertEquals(itemRequest.getDescription(), dto.getDescription()); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/request/dao/ItemRequestRepositoryTest.java b/server/src/test/java/ru/practicum/shareit/request/dao/ItemRequestRepositoryTest.java new file mode 100644 index 0000000..ce62f3a --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/request/dao/ItemRequestRepositoryTest.java @@ -0,0 +1,44 @@ +package ru.practicum.shareit.request.dao; + +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.user.dao.UserRepository; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDate; +import java.util.Collection; + +import static org.assertj.core.api.Assertions.assertThat; + +@DataJpaTest +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class ItemRequestRepositoryTest { + + private final ItemRequestRepository itemRequestRepository; + + private final UserRepository userRepository; + + @Test + public void shouldFindByRequestorIdOrderByCreatedDesc() { + User testUser = new User(null, "name", "email@test.com"); + User savedUser = userRepository.save(testUser); + + ItemRequest request = new ItemRequest(); + request.setRequestorId(savedUser.getId()); + request.setDescription("test"); + request.setCreated(LocalDate.now()); + + ItemRequest savedRequest = itemRequestRepository.save(request); + + Collection requests = itemRequestRepository + .findByRequestorIdOrderByCreatedDesc(savedUser.getId()); + + assertThat(requests).hasSize(1); + assertThat(requests.iterator().next().getId()).isEqualTo(savedRequest.getId()); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/request/service/ItemRequestServiceTest.java b/server/src/test/java/ru/practicum/shareit/request/service/ItemRequestServiceTest.java new file mode 100644 index 0000000..2bb0258 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/request/service/ItemRequestServiceTest.java @@ -0,0 +1,106 @@ +package ru.practicum.shareit.request.service; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import ru.practicum.shareit.exceptions.NotFoundException; +import ru.practicum.shareit.item.dao.ItemRepository; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.request.dao.ItemRequestRepository; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.model.ItemRequest; + +import java.time.LocalDate; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class ItemRequestServiceTest { + + @Mock + private ItemRequestRepository requestRepository; + + @Mock + private ItemRepository itemRepository; + + @InjectMocks + private ItemRequestServiceImpl itemRequestService; + + @Test + public void shouldAddItemRequest() { + ItemRequest itemRequest = new ItemRequest(); + ItemRequest savedRequest = new ItemRequest(1L, "description", 1L, LocalDate.now()); + + when(requestRepository.save(any())).thenReturn(savedRequest); + + ItemRequestDto result = itemRequestService.addItemRequest(itemRequest, 1L); + + assertThat(result.getId()).isEqualTo(1L); + verify(requestRepository).save(any()); + } + + @Test + public void shouldThrowNotFoundExceptionWhenGetNonExistentRequest() { + when(requestRepository.findById(999L)).thenReturn(Optional.empty()); + + assertThatThrownBy(() -> itemRequestService.getRequestById(999L)) + .isInstanceOf(NotFoundException.class); + } + + @Test + public void shouldGetRequest() { + ItemRequest request = new ItemRequest(1L, "desc", 1L, LocalDate.now()); + Item item = mock(Item.class); + + when(requestRepository.findById(1L)).thenReturn(Optional.of(request)); + when(itemRepository.findByRequestId(1L)).thenReturn(List.of(item)); + + ItemRequestDto result = itemRequestService.getRequestById(1L); + + assertThat(result.getId()).isEqualTo(1L); + verify(requestRepository).findById(1L); + } + + @Test + public void shouldGetRequests() { + ItemRequest request = new ItemRequest(1L, "ddrftgyhuesc", 1L, LocalDate.now()); + + when(requestRepository.findByRequestorIdOrderByCreatedDesc(1L)).thenReturn(List.of(request)); + when(itemRepository.findAllByRequestIdIn(any())).thenReturn(List.of()); + + Collection result = itemRequestService.getMyRequests(1L); + + assertThat(result).hasSize(1); + verify(requestRepository).findByRequestorIdOrderByCreatedDesc(1L); + } + + @Test + public void shouldGetAllRequests() { + ItemRequest request = new ItemRequest(); + request.setId(1L); + request.setDescription("dedfghsc"); + request.setRequestorId(1L); + request.setCreated(LocalDate.now()); + + when(requestRepository.findAllByOrderByCreatedDesc()) + .thenReturn(List.of(request)); + + when(itemRepository.findAllByRequestIdIn(any())) + .thenReturn(List.of()); + + Collection result = itemRequestService.getAllRequests(); + + assertThat(result).hasSize(1); + + verify(requestRepository).findAllByOrderByCreatedDesc(); + verify(itemRepository).findAllByRequestIdIn(any()); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/user/controller/UserControllerIntegrationTest.java b/server/src/test/java/ru/practicum/shareit/user/controller/UserControllerIntegrationTest.java new file mode 100644 index 0000000..46f7610 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/user/controller/UserControllerIntegrationTest.java @@ -0,0 +1,101 @@ +package ru.practicum.shareit.user.controller; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.shareit.user.dao.UserRepository; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +@Transactional +public class UserControllerIntegrationTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private UserRepository userRepository; + + @Test + public void shouldGetUser() throws Exception { + User testUser = new User(null, "name", "email@test.com"); + User user = userRepository.save(testUser); + + mockMvc.perform(get("/users/{userId}", user.getId())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(user.getId())) + .andExpect(jsonPath("$.name").value(user.getName())); + } + + @Test + public void shouldAddUser() throws Exception { + UserDto dto = new UserDto("name", "email@test.com"); + String inputJson = objectMapper.writeValueAsString(dto);; + + String responseContent = mockMvc.perform(post("/users") + .contentType(MediaType.APPLICATION_JSON) + .content(inputJson)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").exists()) + .andExpect(jsonPath("$.name").value(dto.getName())) + .andReturn() + .getResponse() + .getContentAsString(); + + JsonNode jsonNode = objectMapper.readTree(responseContent); + Long returnedId = jsonNode.get("id").asLong(); + + Optional savedUser = userRepository.findById(returnedId); + assertThat(savedUser).isPresent(); + assertThat(savedUser.get().getName()).isEqualTo(dto.getName()); + } + + @Test + public void shouldUpdateUser() throws Exception { + User testUser = new User(null, "name1", "email1@test.com"); + User user = userRepository.save(testUser); + User newUser = new User(null, "name2", "email2@test.com"); + String json = objectMapper.writeValueAsString(newUser); + + mockMvc.perform(patch("/users/{userId}", user.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(json)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name").value(newUser.getName())); + + Optional updatedUser = userRepository.findById(user.getId()); + assertThat(updatedUser).isPresent(); + assertThat(updatedUser.get().getName()).isEqualTo(newUser.getName()); + assertThat(updatedUser.get().getEmail()).isEqualTo(newUser.getEmail()); + } + + @Test + public void shouldDeleteUser() throws Exception { + User testUser = new User(null, "name1", "email1@test.com"); + User user = userRepository.save(testUser); + + mockMvc.perform(delete("/users/{userId}", user.getId())) + .andExpect(status().isOk()); + + Optional deletedUser = userRepository.findById(user.getId()); + assertThat(deletedUser).isEmpty(); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/user/controller/UserControllerTest.java b/server/src/test/java/ru/practicum/shareit/user/controller/UserControllerTest.java new file mode 100644 index 0000000..de83ce4 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/user/controller/UserControllerTest.java @@ -0,0 +1,102 @@ +package ru.practicum.shareit.user.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.service.UserServiceImpl; + +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(UserController.class) +public class UserControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private UserServiceImpl userService; + + @Test + public void shouldGetUser() throws Exception { + User user = new User(1L, "denis", "email@example.com"); + when(userService.getUser(1L)).thenReturn(user); + + mockMvc.perform(get("/users/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.name").value("denis")); + } + + @Test + public void shouldGetAllUsers() throws Exception { + User user1 = new User(1L, "denis1", "email1@example.com"); + User user2 = new User(2L, "denis2", "email2@example.com"); + + List users = new ArrayList<>(); + users.add(user1); + users.add(user2); + when(userService.getAllUsers()).thenReturn(users); + + mockMvc.perform(get("/users")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").value(1)) + .andExpect(jsonPath("$[0].name").value("denis1")) + .andExpect(jsonPath("$[1].id").value(2)) + .andExpect(jsonPath("$[1].name").value("denis2")); + } + + @Test + public void shouldAddUser() throws Exception { + UserDto userDto = new UserDto(); + userDto.setName("name"); + userDto.setEmail("email@example.com"); + + User user = new User(1L, "name", "email@example.com"); + when(userService.addUser(any())).thenReturn(user); + + mockMvc.perform(post("/users") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(userDto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1)); + } + + @Test + public void shouldUpdateUser() throws Exception { + UserDto userDto = new UserDto(); + userDto.setName("newName"); + userDto.setEmail("new@example.com"); + + User user = new User(1L, "newName", "new@example.com"); + when(userService.updateUser(anyLong(), any())).thenReturn(user); + + mockMvc.perform(patch("/users/1") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(userDto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name").value("newName")); + } + + @Test + public void shouldDeleteUser() throws Exception { + mockMvc.perform(delete("/users/1")) + .andExpect(status().isOk()); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/user/dao/UserDtoJsonTest.java b/server/src/test/java/ru/practicum/shareit/user/dao/UserDtoJsonTest.java new file mode 100644 index 0000000..71204c7 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/user/dao/UserDtoJsonTest.java @@ -0,0 +1,63 @@ +package ru.practicum.shareit.user.dao; + +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 org.springframework.boot.test.json.JsonContent; +import com.fasterxml.jackson.databind.ObjectMapper; +import ru.practicum.shareit.user.dto.UserDto; + +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; + +@JsonTest +class UserDtoJsonTest { + + @Autowired + private JacksonTester jacksonTester; + + @Autowired + private ObjectMapper objectMapper; + + private UserDto userDto; + + @BeforeEach + void setUp() { + userDto = new UserDto("name", "name@example.com"); + } + + @Test + void shouldSerializeUserDto() throws IOException { + JsonContent json = jacksonTester.write(userDto); + + assertThat(json).hasJsonPathStringValue("$.name"); + assertThat(json).hasJsonPathStringValue("$.email"); + assertThat(json).extractingJsonPathStringValue("$.name") + .isEqualTo("name"); + assertThat(json).extractingJsonPathStringValue("$.email") + .isEqualTo("name@example.com"); + } + + @Test + void shouldDeserializeUserDto() throws IOException { + String json = "{\"name\":\"name\",\"email\":\"name@example.com\"}"; + + UserDto deserializedUser = jacksonTester.parseObject(json); + + assertThat(deserializedUser.getName()).isEqualTo("name"); + assertThat(deserializedUser.getEmail()).isEqualTo("name@example.com"); + } + + @Test + void shouldDeserializeUserDtoWithNullFields() throws IOException { + String json = "{\"name\":null,\"email\":null}"; + + UserDto deserializedUser = jacksonTester.parseObject(json); + + assertThat(deserializedUser.getName()).isNull(); + assertThat(deserializedUser.getEmail()).isNull(); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/user/dao/UserDtoTest.java b/server/src/test/java/ru/practicum/shareit/user/dao/UserDtoTest.java new file mode 100644 index 0000000..8fc281e --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/user/dao/UserDtoTest.java @@ -0,0 +1,38 @@ +package ru.practicum.shareit.user.dao; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.user.dto.UserDto; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class UserDtoTest { + + private UserDto userDto; + + @BeforeEach + void setUp() { + userDto = new UserDto(); + } + + @Test + void testNoArgsConstructor() { + assertNotNull(userDto); + assertNull(userDto.getEmail()); + assertNull(userDto.getName()); + } + + @Test + void testAllArgsConstructor() { + Long id = 1L; + String name = "Test"; + String email = "test@example.com"; + + + UserDto dto = new UserDto(name, email); + + assertEquals(name, dto.getName()); + assertEquals(email, dto.getEmail()); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/user/dao/UserMapperTest.java b/server/src/test/java/ru/practicum/shareit/user/dao/UserMapperTest.java new file mode 100644 index 0000000..10cc29b --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/user/dao/UserMapperTest.java @@ -0,0 +1,22 @@ +package ru.practicum.shareit.user.dao; + +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class UserMapperTest { + + @Test + public void shouldMapUserDtoToUser() { + UserDto userDto = new UserDto(); + userDto.setName("User Name"); + userDto.setEmail("user@example.com"); + + User user = UserMapper.toUser(userDto); + + assertEquals(userDto.getName(), user.getName()); + assertEquals(userDto.getEmail(), user.getEmail()); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/user/dao/UserRepositoryTest.java b/server/src/test/java/ru/practicum/shareit/user/dao/UserRepositoryTest.java new file mode 100644 index 0000000..888360d --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/user/dao/UserRepositoryTest.java @@ -0,0 +1,33 @@ +package ru.practicum.shareit.user.dao; + +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import ru.practicum.shareit.user.model.User; + +import java.util.Optional; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +@DataJpaTest +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class UserRepositoryTest { + + private final UserRepository userRepository; + + @Test + void shouldFindByUserId() { + User testUser = new User(null, "name", "email@test.com"); + User savedUser = userRepository.save(testUser); + + Optional foundUser = userRepository.findById(savedUser.getId()); + + assertThat(foundUser).isPresent(); + assertThat(foundUser.get().getId()).isEqualTo(savedUser.getId()); + assertThat(foundUser.get().getName()).isEqualTo("name"); + assertThat(foundUser.get().getEmail()).isEqualTo("email@test.com"); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/user/service/UserServiceImplTest.java b/server/src/test/java/ru/practicum/shareit/user/service/UserServiceImplTest.java new file mode 100644 index 0000000..ef4f916 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/user/service/UserServiceImplTest.java @@ -0,0 +1,120 @@ +package ru.practicum.shareit.user.service; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import ru.practicum.shareit.exceptions.InternalServerException; +import ru.practicum.shareit.exceptions.NotFoundException; +import ru.practicum.shareit.user.dao.UserRepository; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; + +import java.util.Optional; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class UserServiceImplTest { + + @Mock + private UserRepository userRepository; + + @InjectMocks + private UserServiceImpl userService; + + @Test + public void shouldGetUser() { + User user = new User(1L, "name", "email@test.com"); + when(userRepository.findById(1L)).thenReturn(Optional.of(user)); + + User result = userService.getUser(1L); + + assertThat(result.getId()).isEqualTo(1L); + assertThat(result.getName()).isEqualTo("name"); + verify(userRepository).findById(1L); + } + + @Test + public void shouldThrowNotFoundExceptionWhenGetUserWithInvalidId() { + when(userRepository.findById(999L)).thenReturn(Optional.empty()); + + assertThatThrownBy(() -> userService.getUser(999L)) + .isInstanceOf(NotFoundException.class); + } + + @Test + public void shouldAddUser() { + UserDto userDto = new UserDto(); + userDto.setName("name"); + userDto.setEmail("email@test.com"); + + User savedUser = new User(1L, "name", "email@test.com"); + when(userRepository.save(any())).thenReturn(savedUser); + + User result = userService.addUser(userDto); + + assertThat(result.getId()).isEqualTo(1L); + assertThat(result.getName()).isEqualTo("name"); + verify(userRepository).save(any()); + } + + @Test + public void shouldThrowInternalServerExceptionWhenAddUserWithExistingEmail() { + UserDto userDto = new UserDto(); + userDto.setName("name"); + userDto.setEmail("email@test.com"); + + when(userRepository.existsByEmail("email@test.com")).thenReturn(true); + + assertThatThrownBy(() -> userService.addUser(userDto)) + .isInstanceOf(InternalServerException.class) + .hasMessage("Пользователь с такой почтой уже существует"); + + verify(userRepository, never()).save(any()); + } + + @Test + public void shouldUpdateUser() { + User currentUser = new User(1L, "old", "old@test.com"); + UserDto updateDto = new UserDto(); + updateDto.setName("new"); + updateDto.setEmail("new@test.com"); + + when(userRepository.existsById(1L)).thenReturn(true); + + when(userRepository.findById(1L)).thenReturn(Optional.of(currentUser)); + when(userRepository.save(any())).thenReturn(currentUser); + + User result = userService.updateUser(1L, updateDto); + + verify(userRepository).existsById(1L); + verify(userRepository).findById(1L); + verify(userRepository).save(any()); + + assertThat(result.getName()).isEqualTo("new"); + assertThat(result.getEmail()).isEqualTo("new@test.com"); + } + + @Test + public void shouldThrowNotFoundExceptionWhenUpdateNonExistentUser() { + Long nonExistentId = 999L; + UserDto userDto = new UserDto("Updated", "updated@test.com"); + + when(userRepository.existsById(nonExistentId)).thenReturn(false); + + assertThatThrownBy(() -> userService.updateUser(999L, userDto)) + .isInstanceOf(NotFoundException.class); + } + + @Test + public void shouldDeleteUser() { + userService.deleteUser(1L); + + verify(userRepository).deleteById(1L); + } +} diff --git a/server/src/test/resources/application.properties b/server/src/test/resources/application.properties new file mode 100644 index 0000000..71a1491 --- /dev/null +++ b/server/src/test/resources/application.properties @@ -0,0 +1,11 @@ +logging.level.ru.yandex.practicum.shareit=OFF +logging.level.org.springframework=WARN +spring.main.banner-mode=off + +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.url=jdbc:h2:mem:testdb +spring.datasource.username=dbuser +spring.datasource.password=12345 + +spring.sql.init.mode=always +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..33d4401 --- /dev/null +++ b/server/src/test/resources/schema.sql @@ -0,0 +1,50 @@ +CREATE TABLE IF NOT EXISTS users ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + name VARCHAR(255) NOT NULL, + email VARCHAR(512) NOT NULL, + CONSTRAINT pk_user PRIMARY KEY (id), + CONSTRAINT UQ_USER_EMAIL UNIQUE (email) +); + +CREATE TABLE IF NOT EXISTS item_requests( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + description TEXT NOT NULL, + requestor_id BIGINT NOT NULL REFERENCES users (id) ON DELETE CASCADE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT pk_item_request PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS items ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + name VARCHAR(255) NOT NULL, + description VARCHAR(255) NOT NULL, + available BOOLEAN NOT NULL, + owner_id BIGINT NOT NULL, + request_id BIGINT, + FOREIGN KEY (request_id) REFERENCES item_requests(id) ON DELETE SET NULL, + CONSTRAINT pk_item PRIMARY KEY (id), + FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS bookings ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + 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(255) NOT NULL, + CONSTRAINT pk_booking PRIMARY KEY (id), + 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 NOT NULL, + item_id BIGINT NOT NULL, + author_id BIGINT NOT NULL, + content TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT pk_comments PRIMARY KEY (id), + 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/dao/ItemRequestMapper.java b/src/main/java/ru/practicum/shareit/request/dao/ItemRequestMapper.java deleted file mode 100644 index a9eeb20..0000000 --- a/src/main/java/ru/practicum/shareit/request/dao/ItemRequestMapper.java +++ /dev/null @@ -1,16 +0,0 @@ -package ru.practicum.shareit.request.dao; - -import lombok.experimental.UtilityClass; -import ru.practicum.shareit.request.dto.ItemRequestDto; -import ru.practicum.shareit.request.model.ItemRequest; - -@UtilityClass -public class ItemRequestMapper { - public static ItemRequestDto mapToItemRequest(ItemRequest itemRequest) { - return new ItemRequestDto( - itemRequest.getDescription(), - itemRequest.getRequestor(), - itemRequest.getCreated() - ); - } -} 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 f66db0d..0000000 --- a/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java +++ /dev/null @@ -1,21 +0,0 @@ -package ru.practicum.shareit.request.model; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotNull; -import lombok.Data; -import ru.practicum.shareit.user.model.User; - -import java.time.LocalDate; - -/** - * TODO Sprint add-item-requests. - */ -@Data -@Valid -public class ItemRequest { - @NotNull - private Long id; - private String description; - private User requestor; - private LocalDate created; -}