diff --git a/pom.xml b/pom.xml
index 6ddbe30..5b84756 100644
--- a/pom.xml
+++ b/pom.xml
@@ -33,6 +33,16 @@
spring-boot-configuration-processor
true
+
+ com.h2database
+ h2
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
org.postgresql
@@ -71,6 +81,10 @@
1.5.5.Final
provided
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
diff --git a/src/main/java/ru/practicum/shareit/booking/BookingMapper.java b/src/main/java/ru/practicum/shareit/booking/BookingMapper.java
new file mode 100644
index 0000000..1df9f1a
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/BookingMapper.java
@@ -0,0 +1,30 @@
+package ru.practicum.shareit.booking;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingTarget;
+import org.mapstruct.NullValuePropertyMappingStrategy;
+import ru.practicum.shareit.booking.dto.BookingDto;
+import ru.practicum.shareit.booking.dto.BookingShortDto;
+import ru.practicum.shareit.booking.model.Booking;
+import ru.practicum.shareit.item.ItemMapper;
+import ru.practicum.shareit.user.UserMapper;
+
+@Mapper(componentModel = "spring",
+ uses = {ItemMapper.class, UserMapper.class},
+ nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
+public interface BookingMapper {
+
+ @Mapping(target = "item", source = "item")
+ @Mapping(target = "booker", source = "booker")
+ BookingDto toBookingDto(Booking booking);
+
+ @Mapping(target = "id", ignore = true)
+ Booking toBooking(BookingDto bookingDto);
+
+ Booking updateBookingFields(@MappingTarget Booking targetBooking, BookingDto sourceBookingDto);
+
+ @Mapping(target = "id", source = "id")
+ @Mapping(target = "bookerId", source = "booker.id")
+ BookingShortDto toBookingShortDto(Booking booking);
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/BookingState.java b/src/main/java/ru/practicum/shareit/booking/BookingState.java
new file mode 100644
index 0000000..ecb6b3a
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/BookingState.java
@@ -0,0 +1,10 @@
+package ru.practicum.shareit.booking;
+
+public enum BookingState {
+ ALL,
+ CURRENT,
+ PAST,
+ FUTURE,
+ WAITING,
+ REJECTED
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/Impl/BookingServiceImpl.java b/src/main/java/ru/practicum/shareit/booking/Impl/BookingServiceImpl.java
new file mode 100644
index 0000000..e9d7f03
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/Impl/BookingServiceImpl.java
@@ -0,0 +1,152 @@
+package ru.practicum.shareit.booking.Impl;
+
+import org.springframework.stereotype.Service;
+import ru.practicum.shareit.booking.BookingMapper;
+import ru.practicum.shareit.booking.BookingState;
+import ru.practicum.shareit.booking.BookingStatus;
+import ru.practicum.shareit.booking.dto.BookingDto;
+import ru.practicum.shareit.booking.exception.ShareItException;
+import ru.practicum.shareit.booking.model.Booking;
+import ru.practicum.shareit.booking.repository.BookingRepository;
+import ru.practicum.shareit.booking.service.BookerStateProcessor;
+import ru.practicum.shareit.booking.service.BookingService;
+import ru.practicum.shareit.booking.service.handler.owner.OwnerStateProcessor;
+import ru.practicum.shareit.item.model.Item;
+import ru.practicum.shareit.item.repository.ItemRepository;
+import ru.practicum.shareit.user.model.User;
+import ru.practicum.shareit.user.repository.UserRepository;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Service
+public class BookingServiceImpl implements BookingService {
+ private final BookingRepository bookingRepository;
+ private final UserRepository userRepository;
+ private final ItemRepository itemRepository;
+ private final BookingMapper bookingMapper;
+ private final BookerStateProcessor bookerStateProcessor;
+ private final OwnerStateProcessor ownerStateProcessor;
+
+ public BookingServiceImpl(BookingRepository bookingRepository,
+ UserRepository userRepository,
+ ItemRepository itemRepository,
+ BookingMapper bookingMapper,
+ BookerStateProcessor bookerStateProcessor,
+ OwnerStateProcessor ownerStateProcessor) {
+ this.bookingRepository = bookingRepository;
+ this.userRepository = userRepository;
+ this.itemRepository = itemRepository;
+ this.bookingMapper = bookingMapper;
+ this.bookerStateProcessor = bookerStateProcessor;
+ this.ownerStateProcessor = ownerStateProcessor;
+ }
+
+ @Override
+ public BookingDto create(BookingDto bookingDto, Long userId) {
+ User user = userRepository.findById(userId)
+ .orElseThrow(() -> new ShareItException.NotFoundException("Пользователь с id " + userId + " не найден"));
+
+ if (bookingDto.getItemId() == null) {
+ throw new ShareItException.BadRequestException("ID предмета не указан");
+ }
+
+ Item item = itemRepository.findById(bookingDto.getItemId())
+ .orElseThrow(() -> new ShareItException.NotFoundException("Предмет с id " + bookingDto.getItemId() + " не найден"));
+
+ if (!item.getAvailable()) {
+ throw new ShareItException.BadRequestException("Предмет недоступен для бронирования");
+ }
+
+ if (item.getOwner().getId().equals(userId)) {
+ throw new ShareItException.NotFoundException("Владелец не может бронировать свой предмет");
+ }
+
+ LocalDateTime now = LocalDateTime.now();
+ if (bookingDto.getStart() != null && bookingDto.getEnd() != null) {
+ if (bookingDto.getEnd().isBefore(bookingDto.getStart()) || bookingDto.getEnd().equals(bookingDto.getStart())) {
+ throw new ShareItException.BadRequestException("Дата окончания должна быть позже даты начала");
+ }
+ }
+
+ Booking booking = new Booking();
+ booking.setStart(bookingDto.getStart());
+ booking.setEnd(bookingDto.getEnd());
+ booking.setItem(item);
+ booking.setBooker(user);
+ booking.setStatus(BookingStatus.WAITING);
+
+ Booking savedBooking = bookingRepository.save(booking);
+ return bookingMapper.toBookingDto(savedBooking);
+ }
+
+ @Override
+ public BookingDto approve(Long bookingId, Long userId, Boolean approved) {
+ Booking booking = bookingRepository.findById(bookingId)
+ .orElseThrow(() -> new ShareItException.NotFoundException("Бронирование с id " + bookingId + " не найдено"));
+
+ if (!booking.getItem().getOwner().getId().equals(userId)) {
+ throw new ShareItException.ForbiddenException("Только владелец предмета может подтверждать бронирование");
+ }
+
+ if (booking.getStatus() != BookingStatus.WAITING) {
+ throw new ShareItException.BadRequestException("Бронирование уже обработано");
+ }
+
+ booking.setStatus(approved ? BookingStatus.APPROVED : BookingStatus.REJECTED);
+ return bookingMapper.toBookingDto(bookingRepository.save(booking));
+ }
+
+ @Override
+ public BookingDto getById(Long bookingId, Long userId) {
+ Booking booking = bookingRepository.findById(bookingId)
+ .orElseThrow(() -> new ShareItException.NotFoundException("Бронирование с id " + bookingId + " не найдено"));
+
+ if (!booking.getBooker().getId().equals(userId) && !booking.getItem().getOwner().getId().equals(userId)) {
+ throw new ShareItException.NotFoundException("Доступ запрещен");
+ }
+
+ return bookingMapper.toBookingDto(booking);
+ }
+
+ @Override
+ public List getAllByBooker(Long userId, String stateParam) {
+ userRepository.findById(userId)
+ .orElseThrow(() -> new ShareItException.NotFoundException("Пользователь с id " + userId + " не найден"));
+
+ BookingState state;
+ try {
+ state = BookingState.valueOf(stateParam.toUpperCase());
+ } catch (IllegalArgumentException e) {
+ throw new ShareItException.BadRequestException("Неизвестный статус: " + stateParam);
+ }
+
+ LocalDateTime now = LocalDateTime.now();
+ List bookings = bookerStateProcessor.process(state, userId, now);
+
+ return bookings.stream()
+ .map(bookingMapper::toBookingDto)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public List getAllByOwner(Long userId, String stateParam) {
+ userRepository.findById(userId)
+ .orElseThrow(() -> new ShareItException.NotFoundException("Пользователь с id " + userId + " не найден"));
+
+ BookingState state;
+ try {
+ state = BookingState.valueOf(stateParam.toUpperCase());
+ } catch (IllegalArgumentException e) {
+ throw new ShareItException.BadRequestException("Неизвестный статус: " + stateParam);
+ }
+
+ LocalDateTime now = LocalDateTime.now();
+ List bookings = ownerStateProcessor.process(state, userId, now);
+
+ return bookings.stream()
+ .map(bookingMapper::toBookingDto)
+ .collect(Collectors.toList());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java b/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java
index a3e0ebb..558a388 100644
--- a/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java
+++ b/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java
@@ -1,10 +1,58 @@
package ru.practicum.shareit.booking.controller;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PatchMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
+import ru.practicum.shareit.booking.dto.BookingDto;
+import ru.practicum.shareit.booking.service.BookingService;
+import java.util.List;
@RestController
@RequestMapping(path = "/bookings")
+@RequiredArgsConstructor
public class BookingController {
+ private final BookingService bookingService;
+
+ @PostMapping
+ public ResponseEntity createBooking(@Valid @RequestBody BookingDto bookingDto,
+ @RequestHeader("X-Sharer-User-Id") Long userId) {
+ return ResponseEntity.ok(bookingService.create(bookingDto, userId));
+ }
+
+ @PatchMapping("/{bookingId}")
+ public ResponseEntity approve(@PathVariable Long bookingId,
+ @RequestHeader("X-Sharer-User-Id") Long userId,
+ @RequestParam Boolean approved) {
+ return ResponseEntity.ok(bookingService.approve(bookingId, userId, approved));
+ }
+
+ @GetMapping("/{bookingId}")
+ public ResponseEntity getById(@PathVariable Long bookingId,
+ @RequestHeader("X-Sharer-User-Id") Long userId) {
+ return ResponseEntity.ok(bookingService.getById(bookingId, userId));
+ }
+
+ @GetMapping
+ public ResponseEntity> getAllByBooker(
+ @RequestHeader("X-Sharer-User-Id") Long userId,
+ @RequestParam(defaultValue = "ALL") String state) {
+ return ResponseEntity.ok(bookingService.getAllByBooker(userId, state));
+ }
+
+ @GetMapping("/owner")
+ public ResponseEntity> getAllByOwner(
+ @RequestHeader("X-Sharer-User-Id") Long userId,
+ @RequestParam(defaultValue = "ALL") String state) {
+ return ResponseEntity.ok(bookingService.getAllByOwner(userId, state));
+ }
}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java b/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java
index 393e544..39ccfba 100644
--- a/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java
+++ b/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java
@@ -1,5 +1,7 @@
package ru.practicum.shareit.booking.dto;
+import jakarta.validation.constraints.Future;
+import jakarta.validation.constraints.NotNull;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@@ -9,15 +11,18 @@
import java.time.LocalDateTime;
-
@Getter
@Setter
@Builder(toBuilder = true)
public class BookingDto {
private Long id;
+ @NotNull(message = "Дата начала бронирования не может быть пустой")
+ @Future(message = "Дата начала бронирования должна быть в будущем")
private LocalDateTime start;
+ @NotNull(message = "Дата окончания бронирования не может быть пустой")
+ @Future(message = "Дата окончания бронирования должна быть в будущем")
private LocalDateTime end;
private ItemDto item;
@@ -25,4 +30,6 @@ public class BookingDto {
private UserDto booker;
private BookingStatus status;
+
+ private Long itemId;
}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingInputDto.java b/src/main/java/ru/practicum/shareit/booking/dto/BookingInputDto.java
new file mode 100644
index 0000000..12cd8a0
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/dto/BookingInputDto.java
@@ -0,0 +1,16 @@
+package ru.practicum.shareit.booking.dto;
+
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.time.LocalDateTime;
+
+@Getter
+@Setter
+@Builder
+public class BookingInputDto {
+ private Long itemId;
+ private LocalDateTime start;
+ private LocalDateTime end;
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingShortDto.java b/src/main/java/ru/practicum/shareit/booking/dto/BookingShortDto.java
new file mode 100644
index 0000000..9066ffd
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/dto/BookingShortDto.java
@@ -0,0 +1,18 @@
+package ru.practicum.shareit.booking.dto;
+
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.time.LocalDateTime;
+
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class BookingShortDto {
+ private Long id;
+ private LocalDateTime start;
+ private LocalDateTime end;
+ private Long bookerId;
+ private Long itemId;
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/exception/ShareItException.java b/src/main/java/ru/practicum/shareit/booking/exception/ShareItException.java
index db4ad68..9fe4a12 100644
--- a/src/main/java/ru/practicum/shareit/booking/exception/ShareItException.java
+++ b/src/main/java/ru/practicum/shareit/booking/exception/ShareItException.java
@@ -28,4 +28,18 @@ public ConflictException(String message) {
super(message, HttpStatus.CONFLICT);
}
}
+
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public static class BadRequestException extends ShareItException {
+ public BadRequestException(String message) {
+ super(message, HttpStatus.BAD_REQUEST);
+ }
+ }
+
+ @ResponseStatus(HttpStatus.FORBIDDEN)
+ public static class ForbiddenException extends ShareItException {
+ public ForbiddenException(String message) {
+ super(message, HttpStatus.FORBIDDEN);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/model/Booking.java b/src/main/java/ru/practicum/shareit/booking/model/Booking.java
index 5382265..6ce1971 100644
--- a/src/main/java/ru/practicum/shareit/booking/model/Booking.java
+++ b/src/main/java/ru/practicum/shareit/booking/model/Booking.java
@@ -1,6 +1,20 @@
package ru.practicum.shareit.booking.model;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.EnumType;
+import jakarta.persistence.Enumerated;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.Table;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
import lombok.Getter;
+import lombok.NoArgsConstructor;
import lombok.Setter;
import ru.practicum.shareit.booking.BookingStatus;
import ru.practicum.shareit.item.model.Item;
@@ -8,19 +22,33 @@
import java.time.LocalDateTime;
-
+@Entity
+@Table(name = "bookings")
@Getter
@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
public class Booking {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
+ @Column(name = "start_date", nullable = false)
private LocalDateTime start;
+ @Column(name = "end_date", nullable = false)
private LocalDateTime end;
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "item_id", nullable = false)
private Item item;
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "booker_id", nullable = false)
private User booker;
+ @Enumerated(EnumType.STRING)
+ @Column(name = "status", nullable = false)
private BookingStatus status;
}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java b/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java
new file mode 100644
index 0000000..d613465
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java
@@ -0,0 +1,73 @@
+package ru.practicum.shareit.booking.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import ru.practicum.shareit.booking.BookingStatus;
+import ru.practicum.shareit.booking.model.Booking;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+public interface BookingRepository extends JpaRepository {
+
+ List findAllByBookerId(Long bookerId);
+
+ List findAllByBookerIdAndStartBeforeAndEndAfter(
+ Long bookerId, LocalDateTime start, LocalDateTime end);
+
+ List findAllByBookerIdAndEndBefore(Long bookerId, LocalDateTime end);
+
+ List findAllByBookerIdAndStartAfter(Long bookerId, LocalDateTime start);
+
+ List findAllByBookerIdAndStatus(Long bookerId, BookingStatus status);
+
+ @Query("SELECT b FROM Booking b WHERE b.item.owner.id = :ownerId")
+ List findAllByItemOwnerId(@Param("ownerId") Long ownerId);
+
+ @Query("SELECT b FROM Booking b WHERE b.item.owner.id = :ownerId " +
+ "AND b.start < :start AND b.end > :end")
+ List findAllByItemOwnerIdAndStartBeforeAndEndAfter(
+ @Param("ownerId") Long ownerId,
+ @Param("start") LocalDateTime start,
+ @Param("end") LocalDateTime end);
+
+ @Query("SELECT b FROM Booking b WHERE b.item.owner.id = :ownerId " +
+ "AND b.end < :end")
+ List findAllByItemOwnerIdAndEndBefore(
+ @Param("ownerId") Long ownerId,
+ @Param("end") LocalDateTime end);
+
+ @Query("SELECT b FROM Booking b WHERE b.item.owner.id = :ownerId " +
+ "AND b.start > :start")
+ List findAllByItemOwnerIdAndStartAfter(
+ @Param("ownerId") Long ownerId,
+ @Param("start") LocalDateTime start);
+
+ @Query("SELECT b FROM Booking b WHERE b.item.owner.id = :ownerId " +
+ "AND b.status = :status")
+ List findAllByItemOwnerIdAndStatus(
+ @Param("ownerId") Long ownerId,
+ @Param("status") BookingStatus status);
+
+ @Query("SELECT b FROM Booking b WHERE b.item.id = :itemId " +
+ "AND b.start < :now AND b.status = 'APPROVED' " +
+ "ORDER BY b.start DESC")
+ List findLastBookingForItem(
+ @Param("itemId") Long itemId,
+ @Param("now") LocalDateTime now);
+
+ @Query("SELECT b FROM Booking b WHERE b.item.id = :itemId " +
+ "AND b.start > :now AND b.status = 'APPROVED' " +
+ "ORDER BY b.start ASC")
+ List findNextBookingForItem(
+ @Param("itemId") Long itemId,
+ @Param("now") LocalDateTime now);
+
+ @Query("SELECT COUNT(b) > 0 FROM Booking b WHERE b.booker.id = :userId " +
+ "AND b.item.id = :itemId AND b.end < :now AND b.status = 'APPROVED'")
+ boolean hasUserBookedItem(
+ @Param("userId") Long userId,
+ @Param("itemId") Long itemId,
+ @Param("now") LocalDateTime now);
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/service/BookerStateProcessor.java b/src/main/java/ru/practicum/shareit/booking/service/BookerStateProcessor.java
new file mode 100644
index 0000000..b8281ea
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/service/BookerStateProcessor.java
@@ -0,0 +1,27 @@
+package ru.practicum.shareit.booking.service;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Service;
+import ru.practicum.shareit.booking.BookingState;
+import ru.practicum.shareit.booking.model.Booking;
+import ru.practicum.shareit.booking.service.handler.booker.BookingStateHandler;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Service
+public class BookerStateProcessor {
+ private final List handlers;
+
+ public BookerStateProcessor(@Qualifier("booker") List handlers) {
+ this.handlers = handlers;
+ }
+
+ public List process(BookingState state, Long userId, LocalDateTime now) {
+ return handlers.stream()
+ .filter(handler -> handler.canHandle(state))
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException("Неизвестный статус: " + state))
+ .getBookings(userId, now);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/service/BookingService.java b/src/main/java/ru/practicum/shareit/booking/service/BookingService.java
new file mode 100644
index 0000000..69b502c
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/service/BookingService.java
@@ -0,0 +1,17 @@
+package ru.practicum.shareit.booking.service;
+
+import ru.practicum.shareit.booking.dto.BookingDto;
+
+import java.util.List;
+
+public interface BookingService {
+ BookingDto create(BookingDto bookingDto, Long userId);
+
+ BookingDto approve(Long bookingId, Long userId, Boolean approved);
+
+ BookingDto getById(Long bookingId, Long userId);
+
+ List getAllByBooker(Long userId, String state);
+
+ List getAllByOwner(Long userId, String state);
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/service/handler/AbstractBookingStateHandler.java b/src/main/java/ru/practicum/shareit/booking/service/handler/AbstractBookingStateHandler.java
new file mode 100644
index 0000000..ed87be0
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/service/handler/AbstractBookingStateHandler.java
@@ -0,0 +1,23 @@
+package ru.practicum.shareit.booking.service.handler;
+
+import ru.practicum.shareit.booking.BookingState;
+import ru.practicum.shareit.booking.model.Booking;
+import ru.practicum.shareit.booking.repository.BookingRepository;
+import ru.practicum.shareit.booking.service.handler.booker.BookingStateHandler;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+public abstract class AbstractBookingStateHandler implements BookingStateHandler {
+ protected final BookingRepository bookingRepository;
+
+ public AbstractBookingStateHandler(BookingRepository bookingRepository) {
+ this.bookingRepository = bookingRepository;
+ }
+
+ @Override
+ public abstract boolean canHandle(BookingState state);
+
+ @Override
+ public abstract List getBookings(Long userId, LocalDateTime now);
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/service/handler/booker/AllBookingsHandler.java b/src/main/java/ru/practicum/shareit/booking/service/handler/booker/AllBookingsHandler.java
new file mode 100644
index 0000000..9b567c6
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/service/handler/booker/AllBookingsHandler.java
@@ -0,0 +1,32 @@
+package ru.practicum.shareit.booking.service.handler.booker;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import ru.practicum.shareit.booking.BookingState;
+import ru.practicum.shareit.booking.model.Booking;
+import ru.practicum.shareit.booking.repository.BookingRepository;
+import ru.practicum.shareit.booking.service.handler.AbstractBookingStateHandler;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Component
+@Qualifier("booker")
+@Order(1)
+public class AllBookingsHandler extends AbstractBookingStateHandler {
+
+ public AllBookingsHandler(BookingRepository bookingRepository) {
+ super(bookingRepository);
+ }
+
+ @Override
+ public boolean canHandle(BookingState state) {
+ return state == BookingState.ALL;
+ }
+
+ @Override
+ public List getBookings(Long userId, LocalDateTime now) {
+ return bookingRepository.findAllByBookerId(userId);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/service/handler/booker/BookingStateHandler.java b/src/main/java/ru/practicum/shareit/booking/service/handler/booker/BookingStateHandler.java
new file mode 100644
index 0000000..c1201ea
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/service/handler/booker/BookingStateHandler.java
@@ -0,0 +1,13 @@
+package ru.practicum.shareit.booking.service.handler.booker;
+
+import ru.practicum.shareit.booking.BookingState;
+import ru.practicum.shareit.booking.model.Booking;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+public interface BookingStateHandler {
+ boolean canHandle(BookingState state);
+
+ List getBookings(Long userId, LocalDateTime now);
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/service/handler/booker/CurrentBookingsHandler.java b/src/main/java/ru/practicum/shareit/booking/service/handler/booker/CurrentBookingsHandler.java
new file mode 100644
index 0000000..811a73b
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/service/handler/booker/CurrentBookingsHandler.java
@@ -0,0 +1,32 @@
+package ru.practicum.shareit.booking.service.handler.booker;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import ru.practicum.shareit.booking.BookingState;
+import ru.practicum.shareit.booking.model.Booking;
+import ru.practicum.shareit.booking.repository.BookingRepository;
+import ru.practicum.shareit.booking.service.handler.AbstractBookingStateHandler;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Component
+@Qualifier("booker")
+@Order(2)
+public class CurrentBookingsHandler extends AbstractBookingStateHandler {
+
+ public CurrentBookingsHandler(BookingRepository bookingRepository) {
+ super(bookingRepository);
+ }
+
+ @Override
+ public boolean canHandle(BookingState state) {
+ return state == BookingState.CURRENT;
+ }
+
+ @Override
+ public List getBookings(Long userId, LocalDateTime now) {
+ return bookingRepository.findAllByBookerIdAndStartBeforeAndEndAfter(userId, now, now);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/service/handler/booker/FutureBookingsHandler.java b/src/main/java/ru/practicum/shareit/booking/service/handler/booker/FutureBookingsHandler.java
new file mode 100644
index 0000000..7009733
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/service/handler/booker/FutureBookingsHandler.java
@@ -0,0 +1,32 @@
+package ru.practicum.shareit.booking.service.handler.booker;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import ru.practicum.shareit.booking.BookingState;
+import ru.practicum.shareit.booking.model.Booking;
+import ru.practicum.shareit.booking.repository.BookingRepository;
+import ru.practicum.shareit.booking.service.handler.AbstractBookingStateHandler;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Component
+@Qualifier("booker")
+@Order(4)
+public class FutureBookingsHandler extends AbstractBookingStateHandler {
+
+ public FutureBookingsHandler(BookingRepository bookingRepository) {
+ super(bookingRepository);
+ }
+
+ @Override
+ public boolean canHandle(BookingState state) {
+ return state == BookingState.FUTURE;
+ }
+
+ @Override
+ public List getBookings(Long userId, LocalDateTime now) {
+ return bookingRepository.findAllByBookerIdAndStartAfter(userId, now);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/service/handler/booker/PastBookingsHandler.java b/src/main/java/ru/practicum/shareit/booking/service/handler/booker/PastBookingsHandler.java
new file mode 100644
index 0000000..fc36197
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/service/handler/booker/PastBookingsHandler.java
@@ -0,0 +1,32 @@
+package ru.practicum.shareit.booking.service.handler.booker;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import ru.practicum.shareit.booking.BookingState;
+import ru.practicum.shareit.booking.model.Booking;
+import ru.practicum.shareit.booking.repository.BookingRepository;
+import ru.practicum.shareit.booking.service.handler.AbstractBookingStateHandler;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Component
+@Qualifier("booker")
+@Order(3)
+public class PastBookingsHandler extends AbstractBookingStateHandler {
+
+ public PastBookingsHandler(BookingRepository bookingRepository) {
+ super(bookingRepository);
+ }
+
+ @Override
+ public boolean canHandle(BookingState state) {
+ return state == BookingState.PAST;
+ }
+
+ @Override
+ public List getBookings(Long userId, LocalDateTime now) {
+ return bookingRepository.findAllByBookerIdAndEndBefore(userId, now);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/service/handler/booker/RejectedBookingsHandler.java b/src/main/java/ru/practicum/shareit/booking/service/handler/booker/RejectedBookingsHandler.java
new file mode 100644
index 0000000..5b51d17
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/service/handler/booker/RejectedBookingsHandler.java
@@ -0,0 +1,33 @@
+package ru.practicum.shareit.booking.service.handler.booker;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import ru.practicum.shareit.booking.BookingState;
+import ru.practicum.shareit.booking.BookingStatus;
+import ru.practicum.shareit.booking.model.Booking;
+import ru.practicum.shareit.booking.repository.BookingRepository;
+import ru.practicum.shareit.booking.service.handler.AbstractBookingStateHandler;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Component
+@Qualifier("booker")
+@Order(6)
+public class RejectedBookingsHandler extends AbstractBookingStateHandler {
+
+ public RejectedBookingsHandler(BookingRepository bookingRepository) {
+ super(bookingRepository);
+ }
+
+ @Override
+ public boolean canHandle(BookingState state) {
+ return state == BookingState.REJECTED;
+ }
+
+ @Override
+ public List getBookings(Long userId, LocalDateTime now) {
+ return bookingRepository.findAllByBookerIdAndStatus(userId, BookingStatus.REJECTED);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/service/handler/booker/WaitingBookingsHandler.java b/src/main/java/ru/practicum/shareit/booking/service/handler/booker/WaitingBookingsHandler.java
new file mode 100644
index 0000000..1f22ace
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/service/handler/booker/WaitingBookingsHandler.java
@@ -0,0 +1,33 @@
+package ru.practicum.shareit.booking.service.handler.booker;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import ru.practicum.shareit.booking.BookingState;
+import ru.practicum.shareit.booking.BookingStatus;
+import ru.practicum.shareit.booking.model.Booking;
+import ru.practicum.shareit.booking.repository.BookingRepository;
+import ru.practicum.shareit.booking.service.handler.AbstractBookingStateHandler;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Component
+@Qualifier("booker")
+@Order(5)
+public class WaitingBookingsHandler extends AbstractBookingStateHandler {
+
+ public WaitingBookingsHandler(BookingRepository bookingRepository) {
+ super(bookingRepository);
+ }
+
+ @Override
+ public boolean canHandle(BookingState state) {
+ return state == BookingState.WAITING;
+ }
+
+ @Override
+ public List getBookings(Long userId, LocalDateTime now) {
+ return bookingRepository.findAllByBookerIdAndStatus(userId, BookingStatus.WAITING);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerAllBookingsHandler.java b/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerAllBookingsHandler.java
new file mode 100644
index 0000000..175a8d8
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerAllBookingsHandler.java
@@ -0,0 +1,32 @@
+package ru.practicum.shareit.booking.service.handler.owner;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import ru.practicum.shareit.booking.BookingState;
+import ru.practicum.shareit.booking.model.Booking;
+import ru.practicum.shareit.booking.repository.BookingRepository;
+import ru.practicum.shareit.booking.service.handler.AbstractBookingStateHandler;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Component
+@Qualifier("owner")
+@Order(1)
+public class OwnerAllBookingsHandler extends AbstractBookingStateHandler {
+
+ public OwnerAllBookingsHandler(BookingRepository bookingRepository) {
+ super(bookingRepository);
+ }
+
+ @Override
+ public boolean canHandle(BookingState state) {
+ return state == BookingState.ALL;
+ }
+
+ @Override
+ public List getBookings(Long userId, LocalDateTime now) {
+ return bookingRepository.findAllByItemOwnerId(userId);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerCurrentBookingsHandler.java b/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerCurrentBookingsHandler.java
new file mode 100644
index 0000000..2c8445d
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerCurrentBookingsHandler.java
@@ -0,0 +1,32 @@
+package ru.practicum.shareit.booking.service.handler.owner;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import ru.practicum.shareit.booking.BookingState;
+import ru.practicum.shareit.booking.model.Booking;
+import ru.practicum.shareit.booking.repository.BookingRepository;
+import ru.practicum.shareit.booking.service.handler.AbstractBookingStateHandler;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Component
+@Qualifier("owner")
+@Order(2)
+public class OwnerCurrentBookingsHandler extends AbstractBookingStateHandler {
+
+ public OwnerCurrentBookingsHandler(BookingRepository bookingRepository) {
+ super(bookingRepository);
+ }
+
+ @Override
+ public boolean canHandle(BookingState state) {
+ return state == BookingState.CURRENT;
+ }
+
+ @Override
+ public List getBookings(Long userId, LocalDateTime now) {
+ return bookingRepository.findAllByItemOwnerIdAndStartBeforeAndEndAfter(userId, now, now);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerFutureBookingsHandler.java b/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerFutureBookingsHandler.java
new file mode 100644
index 0000000..9dcdddd
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerFutureBookingsHandler.java
@@ -0,0 +1,32 @@
+package ru.practicum.shareit.booking.service.handler.owner;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import ru.practicum.shareit.booking.BookingState;
+import ru.practicum.shareit.booking.model.Booking;
+import ru.practicum.shareit.booking.repository.BookingRepository;
+import ru.practicum.shareit.booking.service.handler.AbstractBookingStateHandler;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Component
+@Qualifier("owner")
+@Order(4)
+public class OwnerFutureBookingsHandler extends AbstractBookingStateHandler {
+
+ public OwnerFutureBookingsHandler(BookingRepository bookingRepository) {
+ super(bookingRepository);
+ }
+
+ @Override
+ public boolean canHandle(BookingState state) {
+ return state == BookingState.FUTURE;
+ }
+
+ @Override
+ public List getBookings(Long userId, LocalDateTime now) {
+ return bookingRepository.findAllByItemOwnerIdAndStartAfter(userId, now);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerPastBookingsHandler.java b/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerPastBookingsHandler.java
new file mode 100644
index 0000000..668f2d6
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerPastBookingsHandler.java
@@ -0,0 +1,32 @@
+package ru.practicum.shareit.booking.service.handler.owner;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import ru.practicum.shareit.booking.BookingState;
+import ru.practicum.shareit.booking.model.Booking;
+import ru.practicum.shareit.booking.repository.BookingRepository;
+import ru.practicum.shareit.booking.service.handler.AbstractBookingStateHandler;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Component
+@Qualifier("owner")
+@Order(3)
+public class OwnerPastBookingsHandler extends AbstractBookingStateHandler {
+
+ public OwnerPastBookingsHandler(BookingRepository bookingRepository) {
+ super(bookingRepository);
+ }
+
+ @Override
+ public boolean canHandle(BookingState state) {
+ return state == BookingState.PAST;
+ }
+
+ @Override
+ public List getBookings(Long userId, LocalDateTime now) {
+ return bookingRepository.findAllByItemOwnerIdAndEndBefore(userId, now);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerRejectedBookingsHandler.java b/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerRejectedBookingsHandler.java
new file mode 100644
index 0000000..6db61cb
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerRejectedBookingsHandler.java
@@ -0,0 +1,33 @@
+package ru.practicum.shareit.booking.service.handler.owner;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import ru.practicum.shareit.booking.BookingState;
+import ru.practicum.shareit.booking.BookingStatus;
+import ru.practicum.shareit.booking.model.Booking;
+import ru.practicum.shareit.booking.repository.BookingRepository;
+import ru.practicum.shareit.booking.service.handler.AbstractBookingStateHandler;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Component
+@Qualifier("owner")
+@Order(6)
+public class OwnerRejectedBookingsHandler extends AbstractBookingStateHandler {
+
+ public OwnerRejectedBookingsHandler(BookingRepository bookingRepository) {
+ super(bookingRepository);
+ }
+
+ @Override
+ public boolean canHandle(BookingState state) {
+ return state == BookingState.REJECTED;
+ }
+
+ @Override
+ public List getBookings(Long userId, LocalDateTime now) {
+ return bookingRepository.findAllByItemOwnerIdAndStatus(userId, BookingStatus.REJECTED);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerStateProcessor.java b/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerStateProcessor.java
new file mode 100644
index 0000000..e796add
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerStateProcessor.java
@@ -0,0 +1,27 @@
+package ru.practicum.shareit.booking.service.handler.owner;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Service;
+import ru.practicum.shareit.booking.BookingState;
+import ru.practicum.shareit.booking.model.Booking;
+import ru.practicum.shareit.booking.service.handler.booker.BookingStateHandler;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Service
+public class OwnerStateProcessor {
+ private final List handlers;
+
+ public OwnerStateProcessor(@Qualifier("owner") List handlers) {
+ this.handlers = handlers;
+ }
+
+ public List process(BookingState state, Long userId, LocalDateTime now) {
+ return handlers.stream()
+ .filter(handler -> handler.canHandle(state))
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException("Неизвестный статус: " + state))
+ .getBookings(userId, now);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerWaitingBookingsHandler.java b/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerWaitingBookingsHandler.java
new file mode 100644
index 0000000..3ae788a
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/service/handler/owner/OwnerWaitingBookingsHandler.java
@@ -0,0 +1,33 @@
+package ru.practicum.shareit.booking.service.handler.owner;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import ru.practicum.shareit.booking.BookingState;
+import ru.practicum.shareit.booking.BookingStatus;
+import ru.practicum.shareit.booking.model.Booking;
+import ru.practicum.shareit.booking.repository.BookingRepository;
+import ru.practicum.shareit.booking.service.handler.AbstractBookingStateHandler;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Component
+@Qualifier("owner")
+@Order(5)
+public class OwnerWaitingBookingsHandler extends AbstractBookingStateHandler {
+
+ public OwnerWaitingBookingsHandler(BookingRepository bookingRepository) {
+ super(bookingRepository);
+ }
+
+ @Override
+ public boolean canHandle(BookingState state) {
+ return state == BookingState.WAITING;
+ }
+
+ @Override
+ public List getBookings(Long userId, LocalDateTime now) {
+ return bookingRepository.findAllByItemOwnerIdAndStatus(userId, BookingStatus.WAITING);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/item/CommentMapper.java b/src/main/java/ru/practicum/shareit/item/CommentMapper.java
new file mode 100644
index 0000000..25c2dc8
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/item/CommentMapper.java
@@ -0,0 +1,26 @@
+package ru.practicum.shareit.item;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.NullValuePropertyMappingStrategy;
+import ru.practicum.shareit.item.dto.CommentDto;
+import ru.practicum.shareit.item.model.Comment;
+
+import java.time.LocalDateTime;
+
+@Mapper(componentModel = "spring", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
+public interface CommentMapper {
+
+ @Mapping(target = "authorName", source = "author.name")
+ CommentDto toCommentDto(Comment comment);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "author", ignore = true)
+ @Mapping(target = "item", ignore = true)
+ @Mapping(target = "created", expression = "java(getCurrentTime())")
+ Comment toComment(CommentDto commentDto);
+
+ default LocalDateTime getCurrentTime() {
+ return LocalDateTime.now();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/item/Impl/ItemRepositoryImpl.java b/src/main/java/ru/practicum/shareit/item/Impl/ItemRepositoryImpl.java
deleted file mode 100644
index 8bb6974..0000000
--- a/src/main/java/ru/practicum/shareit/item/Impl/ItemRepositoryImpl.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package ru.practicum.shareit.item.Impl;
-
-import org.springframework.stereotype.Component;
-import ru.practicum.shareit.item.model.Item;
-import ru.practicum.shareit.item.repository.AbstractRepository;
-import ru.practicum.shareit.item.repository.ItemRepository;
-
-@Component
-public class ItemRepositoryImpl extends AbstractRepository- implements ItemRepository {
-
- @Override
- public Item create(Item item) {
- setEntityId(item, nextId);
- nextId++;
- entities.put(item.getId(), item);
- return item;
- }
-
- @Override
- public Item update(Item item) {
- entities.put(item.getId(), item);
- return item;
- }
-
- @Override
- protected void setEntityId(Item entity, Long id) {
- entity.setId(id);
- }
-
- @Override
- protected Long getEntityId(Item entity) {
- return entity.getId();
- }
-}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/item/Impl/ItemServiceImpl.java b/src/main/java/ru/practicum/shareit/item/Impl/ItemServiceImpl.java
index 0527ae9..e292c25 100644
--- a/src/main/java/ru/practicum/shareit/item/Impl/ItemServiceImpl.java
+++ b/src/main/java/ru/practicum/shareit/item/Impl/ItemServiceImpl.java
@@ -1,60 +1,127 @@
package ru.practicum.shareit.item.Impl;
+import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import ru.practicum.shareit.booking.BookingMapper;
import ru.practicum.shareit.booking.exception.ShareItException;
+import ru.practicum.shareit.booking.model.Booking;
+import ru.practicum.shareit.booking.repository.BookingRepository;
+import ru.practicum.shareit.item.CommentMapper;
import ru.practicum.shareit.item.ItemMapper;
+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.item.repository.CommentRepository;
import ru.practicum.shareit.item.repository.ItemRepository;
import ru.practicum.shareit.item.service.ItemService;
import ru.practicum.shareit.user.model.User;
import ru.practicum.shareit.user.repository.UserRepository;
+import java.time.LocalDateTime;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.stream.Collectors;
@Service
+@RequiredArgsConstructor
+@Transactional(readOnly = true)
public class ItemServiceImpl implements ItemService {
private final ItemRepository itemRepository;
private final UserRepository userRepository;
+ private final BookingRepository bookingRepository;
+ private final CommentRepository commentRepository;
private final ItemMapper itemMapper;
-
- public ItemServiceImpl(ItemRepository itemRepository, UserRepository userRepository, ItemMapper itemMapper) {
- this.itemRepository = itemRepository;
- this.userRepository = userRepository;
- this.itemMapper = itemMapper;
- }
+ private final CommentMapper commentMapper;
+ private final BookingMapper bookingMapper;
@Override
public List getAll(Long userId) {
- return itemRepository.findAll().stream()
- .filter(item -> item.getOwner().getId().equals(userId))
- .map(itemMapper::toItemDto)
+ List
- items = itemRepository.findByOwnerId(userId);
+ List itemIds = items.stream()
+ .map(Item::getId)
+ .collect(Collectors.toList());
+
+ Map> commentsByItemId = commentRepository.findByItemIdIn(itemIds).stream()
+ .collect(Collectors.groupingBy(comment -> comment.getItem().getId()));
+
+ LocalDateTime now = LocalDateTime.now();
+
+ return items.stream()
+ .map(item -> {
+ ItemDto itemDto = itemMapper.toItemDto(item);
+
+ if (item.getOwner().getId().equals(userId)) {
+ List lastBookings = bookingRepository.findLastBookingForItem(item.getId(), now);
+ if (!lastBookings.isEmpty()) {
+ itemDto.setLastBooking(bookingMapper.toBookingShortDto(lastBookings.get(0)));
+ }
+
+ List nextBookings = bookingRepository.findNextBookingForItem(item.getId(), now);
+ if (!nextBookings.isEmpty()) {
+ itemDto.setNextBooking(bookingMapper.toBookingShortDto(nextBookings.get(0)));
+ }
+ }
+
+ List comments = commentsByItemId.getOrDefault(item.getId(), Collections.emptyList());
+ itemDto.setComments(comments.stream()
+ .map(commentMapper::toCommentDto)
+ .collect(Collectors.toList()));
+
+ return itemDto;
+ })
.collect(Collectors.toList());
}
@Override
- public ItemDto getById(Long id) {
+ public ItemDto getById(Long id, Long userId) {
Item item = itemRepository.findById(id)
.orElseThrow(() -> new ShareItException.NotFoundException("Не найдена вещь с id: " + id));
- return itemMapper.toItemDto(item);
+ ItemDto itemDto = itemMapper.toItemDto(item);
+
+ LocalDateTime now = LocalDateTime.now();
+ if (item.getOwner().getId().equals(userId)) {
+ List lastBookings = bookingRepository.findLastBookingForItem(id, now);
+ if (!lastBookings.isEmpty()) {
+ itemDto.setLastBooking(bookingMapper.toBookingShortDto(lastBookings.get(0)));
+ }
+
+ List nextBookings = bookingRepository.findNextBookingForItem(id, now);
+ if (!nextBookings.isEmpty()) {
+ itemDto.setNextBooking(bookingMapper.toBookingShortDto(nextBookings.get(0)));
+ }
+ }
+
+ List comments = commentRepository.findByItemId(id);
+ itemDto.setComments(comments.stream()
+ .map(commentMapper::toCommentDto)
+ .collect(Collectors.toList()));
+
+ return itemDto;
}
@Override
+ @Transactional
public ItemDto create(ItemDto itemDto, Long userId) {
- User user = userRepository.findById(userId)
- .orElseThrow(() -> new ShareItException.NotFoundException("Невозможно создать вещь - " +
- "не найден пользователь с id: " + userId));
+ User user = getUserById(userId); // Отдельный метод без транзакции
Item item = itemMapper.toItem(itemDto);
item.setOwner(user);
- itemRepository.create(item);
- return itemMapper.toItemDto(item);
+ return itemMapper.toItemDto(itemRepository.save(item));
+ }
+
+ @Transactional(readOnly = true)
+ private User getUserById(Long userId) {
+ return userRepository.findById(userId)
+ .orElseThrow(() -> new ShareItException.NotFoundException("Не найден пользователь с id: " + userId));
}
@Override
+ @Transactional
public ItemDto update(ItemDto itemDto, Long id, Long userId) {
Item item = itemRepository.findById(id)
.orElseThrow(() -> new ShareItException.NotFoundException("Не найдена вещь с id: " + id));
@@ -64,13 +131,15 @@ public ItemDto update(ItemDto itemDto, Long id, Long userId) {
}
Item updatedItem = itemMapper.updateItemFields(item, itemDto);
- return itemMapper.toItemDto(itemRepository.update(updatedItem));
+ return itemMapper.toItemDto(itemRepository.save(updatedItem));
}
@Override
+ @Transactional
public void delete(Long id) {
- getById(id);
- itemRepository.delete(id);
+ itemRepository.findById(id)
+ .orElseThrow(() -> new ShareItException.NotFoundException("Не найдена вещь с id: " + id));
+ itemRepository.deleteById(id);
}
@Override
@@ -79,12 +148,31 @@ public List search(String text) {
return new ArrayList<>();
}
- String searchText = text.toLowerCase();
- return itemRepository.findAll().stream()
- .filter(item -> item.getAvailable() &&
- (item.getName().toLowerCase().contains(searchText) ||
- item.getDescription().toLowerCase().contains(searchText)))
+ return itemRepository.search(text).stream()
.map(itemMapper::toItemDto)
.collect(Collectors.toList());
}
+
+ @Override
+ @Transactional
+ public CommentDto createComment(Long itemId, CommentDto commentDto, Long userId) {
+ Item item = itemRepository.findById(itemId)
+ .orElseThrow(() -> new ShareItException.NotFoundException("Не найдена вещь с id: " + itemId));
+
+ User user = userRepository.findById(userId)
+ .orElseThrow(() -> new ShareItException.NotFoundException("Не найден пользователь с id: " + userId));
+
+ boolean hasBookedItem = bookingRepository.hasUserBookedItem(userId, itemId, LocalDateTime.now());
+
+ if (!hasBookedItem) {
+ throw new ShareItException.BadRequestException("Пользователь не может оставить отзыв, так как не брал вещь в аренду или аренда еще не завершена");
+ }
+
+ Comment comment = commentMapper.toComment(commentDto);
+ comment.setItem(item);
+ comment.setAuthor(user);
+ comment.setCreated(LocalDateTime.now());
+
+ return commentMapper.toCommentDto(commentRepository.save(comment));
+ }
}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/item/ItemMapper.java b/src/main/java/ru/practicum/shareit/item/ItemMapper.java
index ece92a4..d1e0b94 100644
--- a/src/main/java/ru/practicum/shareit/item/ItemMapper.java
+++ b/src/main/java/ru/practicum/shareit/item/ItemMapper.java
@@ -7,11 +7,15 @@
import ru.practicum.shareit.item.dto.ItemDto;
import ru.practicum.shareit.item.model.Item;
-
-@Mapper(componentModel = "spring", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
+@Mapper(componentModel = "spring",
+ nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE,
+ uses = {CommentMapper.class})
public interface ItemMapper {
@Mapping(target = "request", expression = "java(item.getRequest() != null ? item.getRequest() : null)")
+ @Mapping(target = "lastBooking", ignore = true)
+ @Mapping(target = "nextBooking", ignore = true)
+ @Mapping(target = "comments", ignore = true)
ItemDto toItemDto(Item item);
Item toItem(ItemDto itemDto);
diff --git a/src/main/java/ru/practicum/shareit/item/controller/ItemController.java b/src/main/java/ru/practicum/shareit/item/controller/ItemController.java
index d00b6fb..9d2c2ff 100644
--- a/src/main/java/ru/practicum/shareit/item/controller/ItemController.java
+++ b/src/main/java/ru/practicum/shareit/item/controller/ItemController.java
@@ -11,6 +11,7 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
+import ru.practicum.shareit.item.dto.CommentDto;
import ru.practicum.shareit.item.dto.ItemDto;
import ru.practicum.shareit.item.service.ItemService;
import ru.practicum.shareit.item.util.Constants;
@@ -32,8 +33,8 @@ public List getAll(@RequestHeader(Constants.USER_ID_HEADER) Long userId
}
@GetMapping("/{id}")
- public ItemDto getById(@PathVariable Long id) {
- return itemService.getById(id);
+ public ItemDto getById(@PathVariable Long id, @RequestHeader(Constants.USER_ID_HEADER) Long userId) {
+ return itemService.getById(id, userId);
}
@PostMapping
@@ -56,4 +57,11 @@ public void delete(@PathVariable Long id) {
public List search(@RequestParam String text) {
return itemService.search(text);
}
+
+ @PostMapping("/{itemId}/comment")
+ public CommentDto createComment(@PathVariable Long itemId,
+ @Valid @RequestBody CommentDto commentDto,
+ @RequestHeader(Constants.USER_ID_HEADER) Long userId) {
+ return itemService.createComment(itemId, commentDto, userId);
+ }
}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java b/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java
new file mode 100644
index 0000000..66bcda2
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java
@@ -0,0 +1,22 @@
+package ru.practicum.shareit.item.dto;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.time.LocalDateTime;
+
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class CommentDto {
+ private Long id;
+
+ @NotBlank
+ private String text;
+
+ private String authorName;
+
+ private LocalDateTime created;
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java b/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java
index ed1ea71..095dc52 100644
--- a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java
+++ b/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java
@@ -5,8 +5,11 @@
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
+import ru.practicum.shareit.booking.dto.BookingShortDto;
import ru.practicum.shareit.request.model.ItemRequest;
+import java.util.ArrayList;
+import java.util.List;
@Getter
@Setter
@@ -24,4 +27,13 @@ public class ItemDto {
private Boolean available;
private ItemRequest request;
+
+ private BookingShortDto lastBooking;
+
+ private BookingShortDto nextBooking;
+
+ @Builder.Default
+ private List comments = new ArrayList<>();
+
+ private Long requestId;
}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/item/model/Comment.java b/src/main/java/ru/practicum/shareit/item/model/Comment.java
new file mode 100644
index 0000000..4964d29
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/item/model/Comment.java
@@ -0,0 +1,48 @@
+package ru.practicum.shareit.item.model;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.Table;
+import jakarta.validation.constraints.NotBlank;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import ru.practicum.shareit.user.model.User;
+
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "comments")
+@Getter
+@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class Comment {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @NotBlank
+ @Column(name = "text", nullable = false)
+ private String text;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "item_id", nullable = false)
+ private Item item;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "author_id", nullable = false)
+ private User author;
+
+ @Column(name = "created", nullable = false)
+ private LocalDateTime created;
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/item/model/Item.java b/src/main/java/ru/practicum/shareit/item/model/Item.java
index 7021c25..d9a973e 100644
--- a/src/main/java/ru/practicum/shareit/item/model/Item.java
+++ b/src/main/java/ru/practicum/shareit/item/model/Item.java
@@ -1,30 +1,53 @@
package ru.practicum.shareit.item.model;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.Table;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
+import lombok.NoArgsConstructor;
import lombok.Setter;
import ru.practicum.shareit.request.model.ItemRequest;
import ru.practicum.shareit.user.model.User;
-
+@Entity
+@Table(name = "items")
@Getter
@Setter
@Builder
+@NoArgsConstructor
+@AllArgsConstructor
public class Item {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank
+ @Column(name = "name", nullable = false)
private String name;
@NotBlank
+ @Column(name = "description", nullable = false)
private String description;
@NotNull
+ @Column(name = "available", nullable = false)
private Boolean available;
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "owner_id", nullable = false)
private User owner;
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "request_id")
private ItemRequest request;
}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/item/repository/AbstractRepository.java b/src/main/java/ru/practicum/shareit/item/repository/AbstractRepository.java
deleted file mode 100644
index 4f5dd72..0000000
--- a/src/main/java/ru/practicum/shareit/item/repository/AbstractRepository.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package ru.practicum.shareit.item.repository;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-
-public abstract class AbstractRepository {
- protected final Map entities = new HashMap<>();
- protected Long nextId = 1L;
-
- public List findAll() {
- return new ArrayList<>(entities.values());
- }
-
- public Optional findById(K id) {
- return entities.containsKey(id) ? Optional.of(entities.get(id)) : Optional.empty();
- }
-
- public void delete(K id) {
- entities.remove(id);
- }
-
- protected abstract void setEntityId(T entity, Long id);
-
- protected abstract K getEntityId(T entity);
-}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/item/repository/CommentRepository.java b/src/main/java/ru/practicum/shareit/item/repository/CommentRepository.java
new file mode 100644
index 0000000..570a0a2
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/item/repository/CommentRepository.java
@@ -0,0 +1,13 @@
+package ru.practicum.shareit.item.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import ru.practicum.shareit.item.model.Comment;
+
+import java.util.List;
+
+public interface CommentRepository extends JpaRepository {
+
+ List findByItemId(Long itemId);
+
+ List findByItemIdIn(List itemIds);
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java b/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java
index 9bf5801..5f0abcb 100644
--- a/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java
+++ b/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java
@@ -1,18 +1,19 @@
package ru.practicum.shareit.item.repository;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
import ru.practicum.shareit.item.model.Item;
import java.util.List;
-import java.util.Optional;
-public interface ItemRepository {
- List
- findAll();
+public interface ItemRepository extends JpaRepository
- {
- Optional
- findById(Long id);
+ List
- findByOwnerId(Long ownerId);
- Item create(Item item);
-
- Item update(Item item);
-
- void delete(Long id);
+ @Query("SELECT i FROM Item i " +
+ "WHERE i.available = true AND " +
+ "(LOWER(i.name) LIKE LOWER(CONCAT('%', :text, '%')) OR " +
+ "LOWER(i.description) LIKE LOWER(CONCAT('%', :text, '%')))")
+ List
- search(@Param("text") String text);
}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/item/service/ItemService.java b/src/main/java/ru/practicum/shareit/item/service/ItemService.java
index 07f58b1..490ca15 100644
--- a/src/main/java/ru/practicum/shareit/item/service/ItemService.java
+++ b/src/main/java/ru/practicum/shareit/item/service/ItemService.java
@@ -1,5 +1,6 @@
package ru.practicum.shareit.item.service;
+import ru.practicum.shareit.item.dto.CommentDto;
import ru.practicum.shareit.item.dto.ItemDto;
import java.util.List;
@@ -7,7 +8,7 @@
public interface ItemService {
List getAll(Long userId);
- ItemDto getById(Long id);
+ ItemDto getById(Long id, Long userId);
ItemDto create(ItemDto itemDto, Long userId);
@@ -16,4 +17,6 @@ public interface ItemService {
void delete(Long id);
List search(String text);
+
+ CommentDto createComment(Long itemId, CommentDto commentDto, Long userId);
}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java b/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java
index 5612dda..9ee9653 100644
--- a/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java
+++ b/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java
@@ -1,22 +1,44 @@
package ru.practicum.shareit.request.model;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.Table;
import jakarta.validation.constraints.NotBlank;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
import lombok.Getter;
+import lombok.NoArgsConstructor;
import lombok.Setter;
import ru.practicum.shareit.user.model.User;
import java.time.LocalDateTime;
-
+@Entity
+@Table(name = "item_requests")
@Getter
@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
public class ItemRequest {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank
+ @Column(name = "description", nullable = false)
private String description;
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "requestor_id", nullable = false)
private User requestor;
+ @Column(name = "created", nullable = false)
private LocalDateTime created;
}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/user/Impl/UserRepositoryImpl.java b/src/main/java/ru/practicum/shareit/user/Impl/UserRepositoryImpl.java
deleted file mode 100644
index 5f895be..0000000
--- a/src/main/java/ru/practicum/shareit/user/Impl/UserRepositoryImpl.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package ru.practicum.shareit.user.Impl;
-
-import org.springframework.stereotype.Component;
-import ru.practicum.shareit.item.repository.AbstractRepository;
-import ru.practicum.shareit.user.model.User;
-import ru.practicum.shareit.user.repository.UserRepository;
-
-@Component
-public class UserRepositoryImpl extends AbstractRepository implements UserRepository {
-
- @Override
- public User create(User user) {
- setEntityId(user, nextId);
- nextId++;
- entities.put(user.getId(), user);
- return user;
- }
-
- @Override
- public User update(User user) {
- entities.put(user.getId(), user);
- return user;
- }
-
- @Override
- protected void setEntityId(User entity, Long id) {
- entity.setId(id);
- }
-
- @Override
- protected Long getEntityId(User entity) {
- return entity.getId();
- }
-}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/user/Impl/UserServiceImpl.java b/src/main/java/ru/practicum/shareit/user/Impl/UserServiceImpl.java
index ac0e48b..a53beab 100644
--- a/src/main/java/ru/practicum/shareit/user/Impl/UserServiceImpl.java
+++ b/src/main/java/ru/practicum/shareit/user/Impl/UserServiceImpl.java
@@ -1,6 +1,9 @@
package ru.practicum.shareit.user.Impl;
+import lombok.RequiredArgsConstructor;
+import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
import ru.practicum.shareit.booking.exception.ShareItException;
import ru.practicum.shareit.user.UserMapper;
import ru.practicum.shareit.user.dto.UserDto;
@@ -8,21 +11,15 @@
import ru.practicum.shareit.user.repository.UserRepository;
import ru.practicum.shareit.user.service.UserService;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
import java.util.stream.Collectors;
@Service
+@RequiredArgsConstructor
+@Transactional(readOnly = true)
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
private final UserMapper userMapper;
- private final Set emailSet = new HashSet<>();
-
- public UserServiceImpl(UserRepository userRepository, UserMapper userMapper) {
- this.userRepository = userRepository;
- this.userMapper = userMapper;
- }
@Override
public List getAll() {
@@ -35,50 +32,41 @@ public List getAll() {
public UserDto getById(Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ShareItException.NotFoundException("Не найден пользователь с id: " + id));
-
return userMapper.toUserDto(user);
}
@Override
+ @Transactional
public UserDto create(User user) {
- checkEmailUniqueness(user);
- User createdUser = userRepository.create(user);
- emailSet.add(createdUser.getEmail().toLowerCase());
- return userMapper.toUserDto(createdUser);
+ try {
+ User createdUser = userRepository.save(user);
+ return userMapper.toUserDto(createdUser);
+ } catch (DataIntegrityViolationException e) {
+ throw new ShareItException.ConflictException("Пользователь с таким email уже зарегистрирован");
+ }
}
@Override
+ @Transactional
public UserDto update(User user, Long id) {
- User updatedUser = userRepository.findById(id)
+ User existingUser = userRepository.findById(id)
.orElseThrow(() -> new ShareItException.NotFoundException("Невозможно обновить данные пользователя. " +
"Не найден пользователь с id: " + id));
- if (user.getEmail() != null && !user.getEmail().equals(updatedUser.getEmail())) {
- checkEmailUniqueness(user);
- emailSet.remove(updatedUser.getEmail().toLowerCase());
- emailSet.add(user.getEmail().toLowerCase());
+ try {
+ User updated = userMapper.updateUserFields(existingUser, userMapper.toUserDto(user));
+ return userMapper.toUserDto(userRepository.save(updated));
+ } catch (DataIntegrityViolationException e) {
+ throw new ShareItException.ConflictException("Пользователь с таким email уже зарегистрирован");
}
-
- User updated = userMapper.updateUserFields(updatedUser, userMapper.toUserDto(user));
- return userMapper.toUserDto(userRepository.update(updated));
}
@Override
+ @Transactional
public void delete(Long id) {
- User user = userRepository.findById(id)
- .orElseThrow(() -> new ShareItException.NotFoundException("Не найден пользователь с id: " + id));
- emailSet.remove(user.getEmail().toLowerCase());
- userRepository.delete(id);
- }
-
- private void checkEmailUniqueness(User user) {
- if (user.getEmail() == null) {
- return;
- }
-
- String email = user.getEmail().toLowerCase();
- if (emailSet.contains(email)) {
- throw new ShareItException.ConflictException("Пользователь с таким email уже зарегистрирован");
+ if (!userRepository.existsById(id)) {
+ throw new ShareItException.NotFoundException("Не найден пользователь с id: " + id);
}
+ userRepository.deleteById(id);
}
}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/user/model/User.java b/src/main/java/ru/practicum/shareit/user/model/User.java
index 9647105..7c29a66 100644
--- a/src/main/java/ru/practicum/shareit/user/model/User.java
+++ b/src/main/java/ru/practicum/shareit/user/model/User.java
@@ -1,22 +1,37 @@
package ru.practicum.shareit.user.model;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
+import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
+import lombok.NoArgsConstructor;
import lombok.Setter;
-
+@Entity
+@Table(name = "users")
@Getter
@Setter
@Builder
+@NoArgsConstructor
+@AllArgsConstructor
public class User {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank
+ @Column(name = "name", nullable = false)
private String name;
@Email
@NotBlank
+ @Column(name = "email", nullable = false, unique = true)
private String email;
}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java b/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java
index 34465a7..2a1d8c2 100644
--- a/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java
+++ b/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java
@@ -1,18 +1,7 @@
package ru.practicum.shareit.user.repository;
+import org.springframework.data.jpa.repository.JpaRepository;
import ru.practicum.shareit.user.model.User;
-import java.util.List;
-import java.util.Optional;
-
-public interface UserRepository {
- List findAll();
-
- Optional findById(Long id);
-
- User create(User user);
-
- User update(User user);
-
- void delete(Long id);
+public interface UserRepository extends JpaRepository {
}
\ No newline at end of file
diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties
new file mode 100644
index 0000000..6b60a07
--- /dev/null
+++ b/src/main/resources/application-test.properties
@@ -0,0 +1,9 @@
+spring.datasource.driver-class-name=org.h2.Driver
+spring.datasource.url=jdbc:h2:mem:testdb;MODE=PostgreSQL
+spring.datasource.username=test
+spring.datasource.password=test
+spring.jpa.hibernate.ddl-auto=create-drop
+spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
+spring.jpa.properties.hibernate.format_sql=true
+spring.jpa.show-sql=true
+spring.sql.init.mode=never
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index b9e5d4b..929fb7e 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1,14 +1,13 @@
spring.jpa.hibernate.ddl-auto=none
spring.jpa.properties.hibernate.format_sql=true
spring.sql.init.mode=always
-
logging.level.org.springframework.orm.jpa=INFO
logging.level.org.springframework.transaction=INFO
logging.level.org.springframework.transaction.interceptor=TRACE
logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG
+spring.datasource.driverClassName=org.postgresql.Driver
+spring.datasource.url=jdbc:postgresql://localhost:5432/shareit
+spring.datasource.username=postgres
+spring.datasource.password=viktor
-# TODO Append connection to DB
-#spring.datasource.driverClassName
-#spring.datasource.url
-#spring.datasource.username
-#spring.datasource.password
+spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
\ No newline at end of file
diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql
new file mode 100644
index 0000000..df4f228
--- /dev/null
+++ b/src/main/resources/data.sql
@@ -0,0 +1,8 @@
+-- Проверка существования перед вставкой
+INSERT INTO users (name, email)
+SELECT 'User 1', 'user1@example.com'
+WHERE NOT EXISTS (SELECT 1 FROM users WHERE email = 'user1@example.com');
+
+INSERT INTO users (name, email)
+SELECT 'User 2', 'user2@example.com'
+WHERE NOT EXISTS (SELECT 1 FROM users WHERE email = 'user2@example.com');
diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql
new file mode 100644
index 0000000..2d6459a
--- /dev/null
+++ b/src/main/resources/schema.sql
@@ -0,0 +1,54 @@
+CREATE TABLE IF NOT EXISTS users (
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+ name VARCHAR(255) NOT NULL,
+ email VARCHAR(255) NOT NULL UNIQUE
+);
+
+CREATE TABLE IF NOT EXISTS item_requests (
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+ description VARCHAR(1000) NOT NULL,
+ requestor_id BIGINT NOT NULL,
+ created TIMESTAMP WITHOUT TIME ZONE NOT NULL,
+ CONSTRAINT fk_requests_to_users FOREIGN KEY(requestor_id) REFERENCES users(id)
+);
+
+CREATE TABLE IF NOT EXISTS items (
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+ name VARCHAR(255) NOT NULL,
+ description VARCHAR(512) NOT NULL,
+ available BOOLEAN NOT NULL,
+ owner_id BIGINT NOT NULL,
+ request_id BIGINT,
+ CONSTRAINT fk_item_owner FOREIGN KEY (owner_id) REFERENCES users (id),
+ CONSTRAINT fk_item_request FOREIGN KEY (request_id) REFERENCES item_requests (id)
+);
+
+CREATE TABLE IF NOT EXISTS bookings (
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+ start_date TIMESTAMP WITHOUT TIME ZONE NOT NULL,
+ end_date TIMESTAMP WITHOUT TIME ZONE NOT NULL,
+ item_id BIGINT NOT NULL,
+ booker_id BIGINT NOT NULL,
+ status VARCHAR(50) NOT NULL,
+ CONSTRAINT fk_bookings_to_items FOREIGN KEY(item_id) REFERENCES items(id),
+ CONSTRAINT fk_bookings_to_users FOREIGN KEY(booker_id) REFERENCES users(id)
+);
+
+-- Индекс для поиска бронирований по вещи (часто используется в JOIN)
+CREATE INDEX IF NOT EXISTS idx_bookings_item ON bookings(item_id);
+-- Индекс для поиска бронирований по пользователю (используется в WHERE)
+CREATE INDEX IF NOT EXISTS idx_bookings_booker ON bookings(booker_id);
+-- Индекс для поиска бронирований по статусу (используется в WHERE)
+CREATE INDEX IF NOT EXISTS idx_bookings_status ON bookings(status);
+-- Составной индекс для поиска бронирований по дате начала и окончания (используется в WHERE и ORDER BY)
+CREATE INDEX IF NOT EXISTS idx_bookings_dates ON bookings(start_date, end_date);
+
+CREATE TABLE IF NOT EXISTS comments (
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+ text VARCHAR(1000) NOT NULL,
+ item_id BIGINT NOT NULL,
+ author_id BIGINT NOT NULL,
+ created TIMESTAMP WITHOUT TIME ZONE NOT NULL,
+ CONSTRAINT fk_comments_to_items FOREIGN KEY(item_id) REFERENCES items(id),
+ CONSTRAINT fk_comments_to_users FOREIGN KEY(author_id) REFERENCES users(id)
+);
\ No newline at end of file
diff --git a/src/test/java/ru/practicum/shareit/ShareItTests.java b/src/test/java/ru/practicum/shareit/ShareItTests.java
index 4d79052..d53b14f 100644
--- a/src/test/java/ru/practicum/shareit/ShareItTests.java
+++ b/src/test/java/ru/practicum/shareit/ShareItTests.java
@@ -2,12 +2,15 @@
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
@SpringBootTest
+@ActiveProfiles("test")
+@ContextConfiguration(classes = {ShareItApp.class, TestConfig.class})
class ShareItTests {
- @Test
- void contextLoads() {
- }
-
-}
+ @Test
+ void contextLoads() {
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/practicum/shareit/TestConfig.java b/src/test/java/ru/practicum/shareit/TestConfig.java
new file mode 100644
index 0000000..42214b2
--- /dev/null
+++ b/src/test/java/ru/practicum/shareit/TestConfig.java
@@ -0,0 +1,22 @@
+package ru.practicum.shareit;
+
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
+
+import javax.sql.DataSource;
+
+@TestConfiguration
+public class TestConfig {
+
+ @Bean
+ @Primary
+ public DataSource dataSource() {
+ return new EmbeddedDatabaseBuilder()
+ .setType(EmbeddedDatabaseType.H2)
+ .setName("testdb;MODE=PostgreSQL")
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/practicum/shareit/booking/controller/BookingControllerTest.java b/src/test/java/ru/practicum/shareit/booking/controller/BookingControllerTest.java
new file mode 100644
index 0000000..6c49a82
--- /dev/null
+++ b/src/test/java/ru/practicum/shareit/booking/controller/BookingControllerTest.java
@@ -0,0 +1,275 @@
+package ru.practicum.shareit.booking.controller;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.transaction.annotation.Transactional;
+import ru.practicum.shareit.ShareItApp;
+import ru.practicum.shareit.TestConfig;
+import ru.practicum.shareit.booking.BookingStatus;
+import ru.practicum.shareit.booking.dto.BookingDto;
+import ru.practicum.shareit.booking.exception.ShareItException;
+import ru.practicum.shareit.item.controller.ItemController;
+import ru.practicum.shareit.item.dto.ItemDto;
+import ru.practicum.shareit.user.UserMapper;
+import ru.practicum.shareit.user.controller.UserController;
+import ru.practicum.shareit.user.dto.UserDto;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@ActiveProfiles("test")
+@ContextConfiguration(classes = {ShareItApp.class, TestConfig.class})
+@SpringBootTest
+@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
+class BookingControllerTest {
+
+ @Autowired
+ private BookingController bookingController;
+
+ @Autowired
+ private ItemController itemController;
+
+ @Autowired
+ private UserController userController;
+
+ @Autowired
+ private UserMapper userMapper;
+
+ private UserDto ownerDto;
+ private UserDto bookerDto;
+ private ItemDto itemDto;
+ private BookingDto bookingDto;
+
+ @BeforeEach
+ void setUp() {
+ String ownerEmail = "owner" + System.currentTimeMillis() + "@email.com";
+ ownerDto = UserDto.builder()
+ .name("Item Owner")
+ .email(ownerEmail)
+ .build();
+ ownerDto = userController.create(userMapper.toUser(ownerDto));
+
+ String bookerEmail = "booker" + System.currentTimeMillis() + "@email.com";
+ bookerDto = UserDto.builder()
+ .name("Booker User")
+ .email(bookerEmail)
+ .build();
+ bookerDto = userController.create(userMapper.toUser(bookerDto));
+
+ itemDto = ItemDto.builder()
+ .name("Test Item")
+ .description("Test Description")
+ .available(true)
+ .build();
+ itemDto = itemController.create(ownerDto.getId(), itemDto);
+
+ LocalDateTime start = LocalDateTime.now().plusDays(1);
+ LocalDateTime end = LocalDateTime.now().plusDays(2);
+ bookingDto = BookingDto.builder()
+ .itemId(itemDto.getId())
+ .start(start)
+ .end(end)
+ .build();
+ }
+
+ @Nested // Тесты для создания бронирований
+ @DisplayName("Creating bookings")
+ class CreateBookingTests {
+ @Test
+ @Transactional
+ @DisplayName("Successful booking creation")
+ void createBookingTest() {
+ BookingDto createdBooking = bookingController.createBooking(bookingDto, bookerDto.getId()).getBody();
+
+ assertNotNull(createdBooking);
+ assertNotNull(createdBooking.getId());
+ assertEquals(bookingDto.getStart(), createdBooking.getStart());
+ assertEquals(bookingDto.getEnd(), createdBooking.getEnd());
+ assertEquals(itemDto.getId(), createdBooking.getItem().getId());
+ assertEquals(bookerDto.getId(), createdBooking.getBooker().getId());
+ assertEquals(BookingStatus.WAITING, createdBooking.getStatus());
+ }
+
+ @Test
+ @Transactional
+ @DisplayName("Error when owner tries to book their own item")
+ void ownerBookingOwnItemTest() {
+ assertThrows(ShareItException.NotFoundException.class,
+ () -> bookingController.createBooking(bookingDto, ownerDto.getId()));
+ }
+
+ @Test
+ @Transactional
+ @DisplayName("Error when booking unavailable item")
+ void bookingUnavailableItemTest() {
+ ItemDto updatedItem = ItemDto.builder()
+ .available(false)
+ .build();
+ itemController.update(updatedItem, itemDto.getId(), ownerDto.getId());
+
+ assertThrows(ShareItException.BadRequestException.class,
+ () -> bookingController.createBooking(bookingDto, bookerDto.getId()));
+ }
+
+ @Test
+ @Transactional
+ @DisplayName("Error when booking with invalid dates")
+ void bookingWithInvalidDatesTest() {
+ BookingDto invalidBooking = BookingDto.builder()
+ .itemId(itemDto.getId())
+ .start(LocalDateTime.now().plusDays(2))
+ .end(LocalDateTime.now().plusDays(1))
+ .build();
+
+ assertThrows(ShareItException.BadRequestException.class,
+ () -> bookingController.createBooking(invalidBooking, bookerDto.getId()));
+ }
+ }
+
+ @Nested // Тесты для утверждения бронирований
+ @DisplayName("Approving bookings")
+ class ApproveBookingTests {
+ @Test
+ @Transactional
+ @DisplayName("Successful booking approval")
+ void approveBookingTest() {
+ BookingDto createdBooking = bookingController.createBooking(bookingDto, bookerDto.getId()).getBody();
+ BookingDto approvedBooking = bookingController.approve(createdBooking.getId(), ownerDto.getId(), true).getBody();
+
+ assertEquals(BookingStatus.APPROVED, approvedBooking.getStatus());
+ }
+
+ @Test
+ @Transactional
+ @DisplayName("Successful booking rejection")
+ void rejectBookingTest() {
+ BookingDto createdBooking = bookingController.createBooking(bookingDto, bookerDto.getId()).getBody();
+ BookingDto rejectedBooking = bookingController.approve(createdBooking.getId(), ownerDto.getId(), false).getBody();
+
+ assertEquals(BookingStatus.REJECTED, rejectedBooking.getStatus());
+ }
+
+ @Test
+ @Transactional
+ @DisplayName("Error when non-owner tries to approve booking")
+ void nonOwnerApprovingBookingTest() {
+ BookingDto createdBooking = bookingController.createBooking(bookingDto, bookerDto.getId()).getBody();
+
+ String randomEmail = "random" + System.currentTimeMillis() + "@email.com";
+ UserDto randomUser = UserDto.builder()
+ .name("Random User")
+ .email(randomEmail)
+ .build();
+ randomUser = userController.create(userMapper.toUser(randomUser));
+
+ UserDto finalRandomUser = randomUser;
+ assertThrows(ShareItException.ForbiddenException.class,
+ () -> bookingController.approve(createdBooking.getId(), finalRandomUser.getId(), true));
+ }
+
+ @Test
+ @Transactional
+ @DisplayName("Error when approving already processed booking")
+ void approvingProcessedBookingTest() {
+ BookingDto createdBooking = bookingController.createBooking(bookingDto, bookerDto.getId()).getBody();
+ bookingController.approve(createdBooking.getId(), ownerDto.getId(), true).getBody(); // Добавлен .getBody()
+
+ assertThrows(ShareItException.BadRequestException.class,
+ () -> bookingController.approve(createdBooking.getId(), ownerDto.getId(), true));
+ }
+ }
+
+ @Nested // Тесты для получения бронирований
+ @DisplayName("Getting bookings")
+ class GetBookingTests {
+ @Test
+ @Transactional
+ @DisplayName("Getting booking by ID")
+ void getBookingByIdTest() {
+ BookingDto createdBooking = bookingController.createBooking(bookingDto, bookerDto.getId()).getBody();
+ BookingDto retrievedBooking = bookingController.getById(createdBooking.getId(), bookerDto.getId()).getBody();
+
+ assertNotNull(retrievedBooking);
+ assertEquals(createdBooking.getId(), retrievedBooking.getId());
+ assertEquals(createdBooking.getStart(), retrievedBooking.getStart());
+ assertEquals(createdBooking.getEnd(), retrievedBooking.getEnd());
+ }
+
+ @Test
+ @Transactional
+ @DisplayName("Error when unauthorized user tries to get booking")
+ void unauthorizedGetBookingTest() {
+ BookingDto createdBooking = bookingController.createBooking(bookingDto, bookerDto.getId()).getBody();
+
+ String randomEmail = "random" + System.currentTimeMillis() + "@email.com";
+ UserDto randomUser = UserDto.builder()
+ .name("Random User")
+ .email(randomEmail)
+ .build();
+ randomUser = userController.create(userMapper.toUser(randomUser));
+
+ UserDto finalRandomUser = randomUser;
+ assertThrows(ShareItException.NotFoundException.class,
+ () -> bookingController.getById(createdBooking.getId(), finalRandomUser.getId()));
+ }
+
+ @Test
+ @Transactional
+ @DisplayName("Getting all bookings by booker")
+ void getAllBookingsByBookerTest() {
+ bookingController.createBooking(bookingDto, bookerDto.getId()).getBody();
+
+ List bookings = bookingController.getAllByBooker(bookerDto.getId(), "ALL").getBody();
+
+ assertEquals(1, bookings.size());
+ assertEquals(itemDto.getId(), bookings.get(0).getItem().getId());
+ assertEquals(bookerDto.getId(), bookings.get(0).getBooker().getId());
+ }
+
+ @Test
+ @Transactional
+ @DisplayName("Getting all bookings by owner")
+ void getAllBookingsByOwnerTest() {
+ bookingController.createBooking(bookingDto, bookerDto.getId()).getBody();
+
+ List bookings = bookingController.getAllByOwner(ownerDto.getId(), "ALL").getBody();
+
+ assertEquals(1, bookings.size());
+ assertEquals(itemDto.getId(), bookings.get(0).getItem().getId());
+ assertEquals(bookerDto.getId(), bookings.get(0).getBooker().getId());
+ }
+
+ @Test
+ @Transactional
+ @DisplayName("Getting bookings with different states")
+ void getBookingsWithDifferentStatesTest() {
+ BookingDto createdBooking = bookingController.createBooking(bookingDto, bookerDto.getId()).getBody();
+ bookingController.approve(createdBooking.getId(), ownerDto.getId(), true).getBody(); // Добавлен .getBody()
+
+ List waitingBookings = bookingController.getAllByBooker(bookerDto.getId(), "WAITING").getBody();
+ assertEquals(0, waitingBookings.size());
+
+ List futureBookings = bookingController.getAllByBooker(bookerDto.getId(), "FUTURE").getBody();
+ assertEquals(1, futureBookings.size());
+ }
+
+ @Test
+ @Transactional
+ @DisplayName("Error when using invalid state")
+ void invalidStateTest() {
+ assertThrows(ShareItException.BadRequestException.class,
+ () -> bookingController.getAllByBooker(bookerDto.getId(), "INVALID_STATE"));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/practicum/shareit/booking/service/BookingServiceImplTest.java b/src/test/java/ru/practicum/shareit/booking/service/BookingServiceImplTest.java
new file mode 100644
index 0000000..88050f9
--- /dev/null
+++ b/src/test/java/ru/practicum/shareit/booking/service/BookingServiceImplTest.java
@@ -0,0 +1,546 @@
+package ru.practicum.shareit.booking.service;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import ru.practicum.shareit.ShareItApp;
+import ru.practicum.shareit.TestConfig;
+import ru.practicum.shareit.booking.BookingMapper;
+import ru.practicum.shareit.booking.BookingState;
+import ru.practicum.shareit.booking.BookingStatus;
+import ru.practicum.shareit.booking.Impl.BookingServiceImpl;
+import ru.practicum.shareit.booking.dto.BookingDto;
+import ru.practicum.shareit.booking.exception.ShareItException;
+import ru.practicum.shareit.booking.model.Booking;
+import ru.practicum.shareit.booking.repository.BookingRepository;
+import ru.practicum.shareit.booking.service.handler.owner.OwnerStateProcessor;
+import ru.practicum.shareit.item.dto.ItemDto;
+import ru.practicum.shareit.item.model.Item;
+import ru.practicum.shareit.item.repository.ItemRepository;
+import ru.practicum.shareit.user.dto.UserDto;
+import ru.practicum.shareit.user.model.User;
+import ru.practicum.shareit.user.repository.UserRepository;
+
+import java.time.LocalDateTime;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ActiveProfiles("test")
+@ContextConfiguration(classes = {ShareItApp.class, TestConfig.class})
+@ExtendWith(MockitoExtension.class)
+@DisplayName("Booking Service Tests")
+class BookingServiceImplTest {
+ @Mock
+ private BookingRepository bookingRepository;
+ @Mock
+ private UserRepository userRepository;
+ @Mock
+ private ItemRepository itemRepository;
+ @Mock
+ private BookingMapper bookingMapper;
+ @Mock
+ private BookerStateProcessor bookerStateProcessor;
+ @Mock
+ private OwnerStateProcessor ownerStateProcessor;
+ @InjectMocks
+ private BookingServiceImpl bookingService;
+
+ private User owner;
+ private User booker;
+ private Item item;
+ private Booking booking;
+ private BookingDto bookingDto;
+ private LocalDateTime now;
+
+ @BeforeEach
+ void setUp() {
+ now = LocalDateTime.now();
+ owner = User.builder()
+ .id(1L)
+ .name("Owner")
+ .email("owner@test.com")
+ .build();
+ booker = User.builder()
+ .id(2L)
+ .name("Booker")
+ .email("booker@test.com")
+ .build();
+ item = Item.builder()
+ .id(1L)
+ .name("Test Item")
+ .description("Test Description")
+ .available(true)
+ .owner(owner)
+ .build();
+ booking = Booking.builder()
+ .id(1L)
+ .start(now.plusDays(1))
+ .end(now.plusDays(2))
+ .item(item)
+ .booker(booker)
+ .status(BookingStatus.WAITING)
+ .build();
+ bookingDto = BookingDto.builder()
+ .id(1L)
+ .start(now.plusDays(1))
+ .end(now.plusDays(2))
+ .itemId(1L)
+ .item(ItemDto.builder().id(1L).build())
+ .booker(UserDto.builder().id(2L).build())
+ .status(BookingStatus.WAITING)
+ .build();
+ }
+
+ @Nested // Тесты на создание бронирования
+ @DisplayName("Create Booking Tests")
+ class CreateBookingTests {
+ @Test
+ @DisplayName("Should create booking successfully")
+ void createBookingSuccessfully() {
+ when(userRepository.findById(anyLong())).thenReturn(Optional.of(booker));
+ when(itemRepository.findById(anyLong())).thenReturn(Optional.of(item));
+ when(bookingRepository.save(any(Booking.class))).thenReturn(booking);
+ when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto);
+
+ BookingDto result = bookingService.create(bookingDto, booker.getId());
+
+ assertNotNull(result);
+ assertEquals(bookingDto.getId(), result.getId());
+ assertEquals(bookingDto.getStart(), result.getStart());
+ assertEquals(bookingDto.getEnd(), result.getEnd());
+ assertEquals(bookingDto.getStatus(), result.getStatus());
+ verify(bookingRepository).save(any(Booking.class));
+ }
+
+ @Test
+ @DisplayName("Should throw exception when user doesn't exist")
+ void createBookingWithNonExistentUser() {
+ when(userRepository.findById(anyLong())).thenReturn(Optional.empty());
+
+ assertThrows(ShareItException.NotFoundException.class,
+ () -> bookingService.create(bookingDto, 999L));
+ verify(bookingRepository, never()).save(any(Booking.class));
+ }
+
+ @Test
+ @DisplayName("Should throw exception when item doesn't exist")
+ void createBookingWithNonExistentItem() {
+ when(userRepository.findById(anyLong())).thenReturn(Optional.of(booker));
+ when(itemRepository.findById(anyLong())).thenReturn(Optional.empty());
+
+ assertThrows(ShareItException.NotFoundException.class,
+ () -> bookingService.create(bookingDto, booker.getId()));
+ verify(bookingRepository, never()).save(any(Booking.class));
+ }
+
+ @Test
+ @DisplayName("Should throw exception when item is unavailable")
+ void createBookingWithUnavailableItem() {
+ item.setAvailable(false);
+ when(userRepository.findById(anyLong())).thenReturn(Optional.of(booker));
+ when(itemRepository.findById(anyLong())).thenReturn(Optional.of(item));
+
+ assertThrows(ShareItException.BadRequestException.class,
+ () -> bookingService.create(bookingDto, booker.getId()));
+ verify(bookingRepository, never()).save(any(Booking.class));
+ }
+
+ @Test
+ @DisplayName("Should throw exception when owner tries to book own item")
+ void createBookingByOwner() {
+ when(userRepository.findById(anyLong())).thenReturn(Optional.of(owner));
+ when(itemRepository.findById(anyLong())).thenReturn(Optional.of(item));
+
+ assertThrows(ShareItException.NotFoundException.class,
+ () -> bookingService.create(bookingDto, owner.getId()));
+ verify(bookingRepository, never()).save(any(Booking.class));
+ }
+
+ @Test
+ @DisplayName("Should throw exception when booking dates are invalid")
+ void createBookingWithInvalidDates() {
+ bookingDto.setStart(now.plusDays(2));
+ bookingDto.setEnd(now.plusDays(1));
+ when(userRepository.findById(anyLong())).thenReturn(Optional.of(booker));
+ when(itemRepository.findById(anyLong())).thenReturn(Optional.of(item));
+
+ assertThrows(ShareItException.BadRequestException.class,
+ () -> bookingService.create(bookingDto, booker.getId()));
+ verify(bookingRepository, never()).save(any(Booking.class));
+ }
+ }
+
+ @Nested // Тесты на подтверждение бронирования
+ @DisplayName("Approve Booking Tests")
+ class ApproveBookingTests {
+ @Test
+ @DisplayName("Should approve booking successfully")
+ void approveBookingSuccessfully() {
+ booking.setStatus(BookingStatus.WAITING);
+ when(bookingRepository.findById(anyLong())).thenReturn(Optional.of(booking));
+ Booking approvedBooking = Booking.builder()
+ .id(booking.getId())
+ .start(booking.getStart())
+ .end(booking.getEnd())
+ .item(booking.getItem())
+ .booker(booking.getBooker())
+ .status(BookingStatus.APPROVED)
+ .build();
+ when(bookingRepository.save(any(Booking.class))).thenReturn(approvedBooking);
+ BookingDto approvedBookingDto = BookingDto.builder()
+ .id(bookingDto.getId())
+ .start(bookingDto.getStart())
+ .end(bookingDto.getEnd())
+ .item(bookingDto.getItem())
+ .booker(bookingDto.getBooker())
+ .status(BookingStatus.APPROVED)
+ .build();
+ when(bookingMapper.toBookingDto(approvedBooking)).thenReturn(approvedBookingDto);
+
+ BookingDto result = bookingService.approve(booking.getId(), owner.getId(), true);
+
+ assertNotNull(result);
+ assertEquals(BookingStatus.APPROVED, result.getStatus());
+ verify(bookingRepository).save(any(Booking.class));
+ }
+
+ @Test
+ @DisplayName("Should reject booking successfully")
+ void rejectBookingSuccessfully() {
+ booking.setStatus(BookingStatus.WAITING);
+ when(bookingRepository.findById(anyLong())).thenReturn(Optional.of(booking));
+ Booking rejectedBooking = Booking.builder()
+ .id(booking.getId())
+ .start(booking.getStart())
+ .end(booking.getEnd())
+ .item(booking.getItem())
+ .booker(booking.getBooker())
+ .status(BookingStatus.REJECTED)
+ .build();
+ when(bookingRepository.save(any(Booking.class))).thenReturn(rejectedBooking);
+ BookingDto rejectedBookingDto = BookingDto.builder()
+ .id(bookingDto.getId())
+ .start(bookingDto.getStart())
+ .end(bookingDto.getEnd())
+ .item(bookingDto.getItem())
+ .booker(bookingDto.getBooker())
+ .status(BookingStatus.REJECTED)
+ .build();
+ when(bookingMapper.toBookingDto(rejectedBooking)).thenReturn(rejectedBookingDto);
+
+ BookingDto result = bookingService.approve(booking.getId(), owner.getId(), false);
+
+ assertNotNull(result);
+ assertEquals(BookingStatus.REJECTED, result.getStatus());
+ verify(bookingRepository).save(any(Booking.class));
+ }
+
+ @Test
+ @DisplayName("Should throw exception when booking doesn't exist")
+ void approveBookingWithNonExistentBooking() {
+ when(bookingRepository.findById(anyLong())).thenReturn(Optional.empty());
+
+ assertThrows(ShareItException.NotFoundException.class,
+ () -> bookingService.approve(999L, owner.getId(), true));
+ verify(bookingRepository, never()).save(any(Booking.class));
+ }
+
+ @Test
+ @DisplayName("Should throw exception when non-owner tries to approve")
+ void approveBookingByNonOwner() {
+ when(bookingRepository.findById(anyLong())).thenReturn(Optional.of(booking));
+
+ assertThrows(ShareItException.ForbiddenException.class,
+ () -> bookingService.approve(booking.getId(), booker.getId(), true));
+ verify(bookingRepository, never()).save(any(Booking.class));
+ }
+
+ @Test
+ @DisplayName("Should throw exception when booking already processed")
+ void approveAlreadyProcessedBooking() {
+ booking.setStatus(BookingStatus.APPROVED);
+ when(bookingRepository.findById(anyLong())).thenReturn(Optional.of(booking));
+
+ assertThrows(ShareItException.BadRequestException.class,
+ () -> bookingService.approve(booking.getId(), owner.getId(), true));
+ verify(bookingRepository, never()).save(any(Booking.class));
+ }
+ }
+
+ @Nested // Тесты на получение бронирования
+ @DisplayName("Get Booking Tests")
+ class GetBookingTests {
+ @Test
+ @DisplayName("Should get booking by ID successfully")
+ void getBookingByIdSuccessfully() {
+ when(bookingRepository.findById(anyLong())).thenReturn(Optional.of(booking));
+ when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto);
+
+ BookingDto result = bookingService.getById(booking.getId(), booker.getId());
+
+ assertNotNull(result);
+ assertEquals(bookingDto.getId(), result.getId());
+ verify(bookingMapper).toBookingDto(booking);
+ }
+
+ @Test
+ @DisplayName("Should throw exception when booking doesn't exist")
+ void getBookingByIdWithNonExistentBooking() {
+ when(bookingRepository.findById(anyLong())).thenReturn(Optional.empty());
+
+ assertThrows(ShareItException.NotFoundException.class,
+ () -> bookingService.getById(999L, booker.getId()));
+ }
+
+ @Test
+ @DisplayName("Should throw exception when unauthorized user tries to get booking")
+ void getBookingByIdByUnauthorizedUser() {
+ User randomUser = User.builder().id(3L).build();
+ when(bookingRepository.findById(anyLong())).thenReturn(Optional.of(booking));
+
+ assertThrows(ShareItException.NotFoundException.class,
+ () -> bookingService.getById(booking.getId(), randomUser.getId()));
+ }
+ }
+
+ @Nested // Тесты на получение бронирований по статусу
+ @DisplayName("Get Bookings By Booker Tests")
+ class GetBookingsByBookerTests {
+ @Test
+ @DisplayName("Should get all bookings by booker")
+ void getAllBookingsByBooker() {
+ when(userRepository.findById(anyLong())).thenReturn(Optional.of(booker));
+ when(bookerStateProcessor.process(eq(BookingState.ALL), eq(booker.getId()), any(LocalDateTime.class)))
+ .thenReturn(Collections.singletonList(booking));
+ when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto);
+
+ List result = bookingService.getAllByBooker(booker.getId(), "ALL");
+
+ assertNotNull(result);
+ assertEquals(1, result.size());
+ assertEquals(bookingDto.getId(), result.get(0).getId());
+ verify(bookerStateProcessor).process(eq(BookingState.ALL), eq(booker.getId()), any(LocalDateTime.class));
+ }
+
+ @Test
+ @DisplayName("Should get current bookings by booker")
+ void getCurrentBookingsByBooker() {
+ when(userRepository.findById(anyLong())).thenReturn(Optional.of(booker));
+ when(bookerStateProcessor.process(eq(BookingState.CURRENT), eq(booker.getId()), any(LocalDateTime.class)))
+ .thenReturn(Collections.singletonList(booking));
+ when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto);
+
+ List result = bookingService.getAllByBooker(booker.getId(), "CURRENT");
+
+ assertNotNull(result);
+ assertEquals(1, result.size());
+ verify(bookerStateProcessor).process(eq(BookingState.CURRENT), eq(booker.getId()), any(LocalDateTime.class));
+ }
+
+ @Test
+ @DisplayName("Should get past bookings by booker")
+ void getPastBookingsByBooker() {
+ when(userRepository.findById(anyLong())).thenReturn(Optional.of(booker));
+ when(bookerStateProcessor.process(eq(BookingState.PAST), eq(booker.getId()), any(LocalDateTime.class)))
+ .thenReturn(Collections.singletonList(booking));
+ when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto);
+
+ List result = bookingService.getAllByBooker(booker.getId(), "PAST");
+
+ assertNotNull(result);
+ assertEquals(1, result.size());
+ verify(bookerStateProcessor).process(eq(BookingState.PAST), eq(booker.getId()), any(LocalDateTime.class));
+ }
+
+ @Test
+ @DisplayName("Should get future bookings by booker")
+ void getFutureBookingsByBooker() {
+ when(userRepository.findById(anyLong())).thenReturn(Optional.of(booker));
+ when(bookerStateProcessor.process(eq(BookingState.FUTURE), eq(booker.getId()), any(LocalDateTime.class)))
+ .thenReturn(Collections.singletonList(booking));
+ when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto);
+
+ List result = bookingService.getAllByBooker(booker.getId(), "FUTURE");
+
+ assertNotNull(result);
+ assertEquals(1, result.size());
+ verify(bookerStateProcessor).process(eq(BookingState.FUTURE), eq(booker.getId()), any(LocalDateTime.class));
+ }
+
+ @Test
+ @DisplayName("Should get waiting bookings by booker")
+ void getWaitingBookingsByBooker() {
+ when(userRepository.findById(anyLong())).thenReturn(Optional.of(booker));
+ when(bookerStateProcessor.process(eq(BookingState.WAITING), eq(booker.getId()), any(LocalDateTime.class)))
+ .thenReturn(Collections.singletonList(booking));
+ when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto);
+
+ List result = bookingService.getAllByBooker(booker.getId(), "WAITING");
+
+ assertNotNull(result);
+ assertEquals(1, result.size());
+ verify(bookerStateProcessor).process(eq(BookingState.WAITING), eq(booker.getId()), any(LocalDateTime.class));
+ }
+
+ @Test
+ @DisplayName("Should get rejected bookings by booker")
+ void getRejectedBookingsByBooker() {
+ when(userRepository.findById(anyLong())).thenReturn(Optional.of(booker));
+ when(bookerStateProcessor.process(eq(BookingState.REJECTED), eq(booker.getId()), any(LocalDateTime.class)))
+ .thenReturn(Collections.singletonList(booking));
+ when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto);
+
+ List result = bookingService.getAllByBooker(booker.getId(), "REJECTED");
+
+ assertNotNull(result);
+ assertEquals(1, result.size());
+ verify(bookerStateProcessor).process(eq(BookingState.REJECTED), eq(booker.getId()), any(LocalDateTime.class));
+ }
+
+ @Test
+ @DisplayName("Should throw exception when state is invalid")
+ void getBookingsByBookerWithInvalidState() {
+ when(userRepository.findById(anyLong())).thenReturn(Optional.of(booker));
+
+ assertThrows(ShareItException.BadRequestException.class,
+ () -> bookingService.getAllByBooker(booker.getId(), "INVALID_STATE"));
+ }
+ }
+
+ @Nested // Тесты для получения бронирований по владельцу
+ @DisplayName("Get Bookings By Owner Tests")
+ class GetBookingsByOwnerTests {
+ @Test
+ @DisplayName("Should get all bookings by owner")
+ void getAllBookingsByOwner() {
+ when(userRepository.findById(anyLong())).thenReturn(Optional.of(owner));
+ when(ownerStateProcessor.process(eq(BookingState.ALL), eq(owner.getId()), any(LocalDateTime.class)))
+ .thenReturn(Collections.singletonList(booking));
+ when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto);
+
+ List result = bookingService.getAllByOwner(owner.getId(), "ALL");
+
+ assertNotNull(result);
+ assertEquals(1, result.size());
+ assertEquals(bookingDto.getId(), result.get(0).getId());
+ verify(ownerStateProcessor).process(eq(BookingState.ALL), eq(owner.getId()), any(LocalDateTime.class));
+ }
+
+ @Test
+ @DisplayName("Should get current bookings by owner")
+ void getCurrentBookingsByOwner() {
+ when(userRepository.findById(anyLong())).thenReturn(Optional.of(owner));
+ when(ownerStateProcessor.process(eq(BookingState.CURRENT), eq(owner.getId()), any(LocalDateTime.class)))
+ .thenReturn(Collections.singletonList(booking));
+ when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto);
+
+ List result = bookingService.getAllByOwner(owner.getId(), "CURRENT");
+
+ assertNotNull(result);
+ assertEquals(1, result.size());
+ verify(ownerStateProcessor).process(eq(BookingState.CURRENT), eq(owner.getId()), any(LocalDateTime.class));
+ }
+
+ @Test
+ @DisplayName("Should get past bookings by owner")
+ void getPastBookingsByOwner() {
+ when(userRepository.findById(anyLong())).thenReturn(Optional.of(owner));
+ when(ownerStateProcessor.process(eq(BookingState.PAST), eq(owner.getId()), any(LocalDateTime.class)))
+ .thenReturn(Collections.singletonList(booking));
+ when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto);
+
+ List result = bookingService.getAllByOwner(owner.getId(), "PAST");
+
+ assertNotNull(result);
+ assertEquals(1, result.size());
+ verify(ownerStateProcessor).process(eq(BookingState.PAST), eq(owner.getId()), any(LocalDateTime.class));
+ }
+
+ @Test
+ @DisplayName("Should get future bookings by owner")
+ void getFutureBookingsByOwner() {
+ when(userRepository.findById(anyLong())).thenReturn(Optional.of(owner));
+ when(ownerStateProcessor.process(eq(BookingState.FUTURE), eq(owner.getId()), any(LocalDateTime.class)))
+ .thenReturn(Collections.singletonList(booking));
+ when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto);
+
+ List result = bookingService.getAllByOwner(owner.getId(), "FUTURE");
+
+ assertNotNull(result);
+ assertEquals(1, result.size());
+ verify(ownerStateProcessor).process(eq(BookingState.FUTURE), eq(owner.getId()), any(LocalDateTime.class));
+ }
+
+ @Test
+ @DisplayName("Should get waiting bookings by owner")
+ void getWaitingBookingsByOwner() {
+ when(userRepository.findById(anyLong())).thenReturn(Optional.of(owner));
+ when(ownerStateProcessor.process(eq(BookingState.WAITING), eq(owner.getId()), any(LocalDateTime.class)))
+ .thenReturn(Collections.singletonList(booking));
+ when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto);
+
+ List result = bookingService.getAllByOwner(owner.getId(), "WAITING");
+
+ assertNotNull(result);
+ assertEquals(1, result.size());
+ verify(ownerStateProcessor).process(eq(BookingState.WAITING), eq(owner.getId()), any(LocalDateTime.class));
+ }
+
+ @Test
+ @DisplayName("Should get rejected bookings by owner")
+ void getRejectedBookingsByOwner() {
+ when(userRepository.findById(anyLong())).thenReturn(Optional.of(owner));
+ when(ownerStateProcessor.process(eq(BookingState.REJECTED), eq(owner.getId()), any(LocalDateTime.class)))
+ .thenReturn(Collections.singletonList(booking));
+ when(bookingMapper.toBookingDto(any(Booking.class))).thenReturn(bookingDto);
+
+ List result = bookingService.getAllByOwner(owner.getId(), "REJECTED");
+
+ assertNotNull(result);
+ assertEquals(1, result.size());
+ verify(ownerStateProcessor).process(eq(BookingState.REJECTED), eq(owner.getId()), any(LocalDateTime.class));
+ }
+
+ @Test
+ @DisplayName("Should throw exception when state is invalid")
+ void getBookingsByOwnerWithInvalidState() {
+ when(userRepository.findById(anyLong())).thenReturn(Optional.of(owner));
+
+ assertThrows(ShareItException.BadRequestException.class,
+ () -> bookingService.getAllByOwner(owner.getId(), "INVALID_STATE"));
+ }
+ }
+
+ @Nested // Тесты для получения бронирований пользователя
+ @DisplayName("User Validation Tests")
+ class UserValidationTests {
+ @Test
+ @DisplayName("Should throw exception when user doesn't exist")
+ void getBookingsByNonExistentUser() {
+ when(userRepository.findById(anyLong())).thenReturn(Optional.empty());
+
+ assertThrows(ShareItException.NotFoundException.class,
+ () -> bookingService.getAllByBooker(999L, "ALL"));
+ assertThrows(ShareItException.NotFoundException.class,
+ () -> bookingService.getAllByOwner(999L, "ALL"));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/practicum/shareit/item/controller/CommentControllerTest.java b/src/test/java/ru/practicum/shareit/item/controller/CommentControllerTest.java
new file mode 100644
index 0000000..d7e84b3
--- /dev/null
+++ b/src/test/java/ru/practicum/shareit/item/controller/CommentControllerTest.java
@@ -0,0 +1,115 @@
+package ru.practicum.shareit.item.controller;
+
+import jakarta.transaction.Transactional;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import ru.practicum.shareit.ShareItApp;
+import ru.practicum.shareit.TestConfig;
+import ru.practicum.shareit.booking.controller.BookingController;
+import ru.practicum.shareit.booking.dto.BookingDto;
+import ru.practicum.shareit.booking.exception.ShareItException;
+import ru.practicum.shareit.item.dto.CommentDto;
+import ru.practicum.shareit.item.dto.ItemDto;
+import ru.practicum.shareit.user.UserMapper;
+import ru.practicum.shareit.user.controller.UserController;
+import ru.practicum.shareit.user.dto.UserDto;
+
+import java.time.LocalDateTime;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@ActiveProfiles("test")
+@ContextConfiguration(classes = {ShareItApp.class, TestConfig.class})
+@SpringBootTest
+@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
+class CommentControllerTest {
+
+ @Autowired
+ private ItemController itemController;
+
+ @Autowired
+ private UserController userController;
+
+ @Autowired
+ private BookingController bookingController;
+
+ @Autowired
+ private UserMapper userMapper;
+
+ private UserDto ownerDto;
+ private UserDto bookerDto;
+ private ItemDto itemDto;
+ private BookingDto bookingDto;
+
+ @BeforeEach
+ void setUp() {
+ String ownerEmail = "owner" + System.currentTimeMillis() + "@email.com";
+ ownerDto = UserDto.builder()
+ .name("Item Owner")
+ .email(ownerEmail)
+ .build();
+ ownerDto = userController.create(userMapper.toUser(ownerDto));
+
+ String bookerEmail = "booker" + System.currentTimeMillis() + "@email.com";
+ bookerDto = UserDto.builder()
+ .name("Booker User")
+ .email(bookerEmail)
+ .build();
+ bookerDto = userController.create(userMapper.toUser(bookerDto));
+
+ itemDto = ItemDto.builder()
+ .name("Test Item")
+ .description("Test Description")
+ .available(true)
+ .build();
+ itemDto = itemController.create(ownerDto.getId(), itemDto);
+
+ LocalDateTime start = LocalDateTime.now().plusHours(1);
+ LocalDateTime end = LocalDateTime.now().plusHours(2);
+ bookingDto = BookingDto.builder()
+ .itemId(itemDto.getId())
+ .start(start)
+ .end(end)
+ .build();
+ }
+
+ @Test // Тест на успешное добавление комментария без бронирования
+ @DisplayName("Error when adding comment without booking")
+ void errorWhenAddingCommentWithoutBooking() {
+ CommentDto commentDto = CommentDto.builder()
+ .text("I haven't rented this item")
+ .build();
+
+ assertThrows(ShareItException.BadRequestException.class,
+ () -> itemController.createComment(itemDto.getId(), commentDto, bookerDto.getId()));
+ }
+
+ @Test // Тест на успешное добавление комментария с бронированием
+ @Transactional
+ @DisplayName("Error when adding comment with future booking")
+ void errorWhenAddingCommentWithFutureBooking() {
+ LocalDateTime start = LocalDateTime.now().plusDays(1);
+ LocalDateTime end = LocalDateTime.now().plusDays(2);
+ BookingDto futureBooking = BookingDto.builder()
+ .itemId(itemDto.getId())
+ .start(start)
+ .end(end)
+ .build();
+
+ BookingDto createdBooking = bookingController.createBooking(futureBooking, bookerDto.getId()).getBody();
+ bookingController.approve(createdBooking.getId(), ownerDto.getId(), true).getBody();
+
+ CommentDto commentDto = CommentDto.builder()
+ .text("I haven't used this item yet")
+ .build();
+
+ assertThrows(ShareItException.BadRequestException.class,
+ () -> itemController.createComment(itemDto.getId(), commentDto, bookerDto.getId()));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/practicum/shareit/item/controller/ItemBookingInfoTest.java b/src/test/java/ru/practicum/shareit/item/controller/ItemBookingInfoTest.java
new file mode 100644
index 0000000..b5c1e9e
--- /dev/null
+++ b/src/test/java/ru/practicum/shareit/item/controller/ItemBookingInfoTest.java
@@ -0,0 +1,192 @@
+package ru.practicum.shareit.item.controller;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.transaction.annotation.Transactional;
+import ru.practicum.shareit.ShareItApp;
+import ru.practicum.shareit.TestConfig;
+import ru.practicum.shareit.booking.controller.BookingController;
+import ru.practicum.shareit.booking.dto.BookingDto;
+import ru.practicum.shareit.booking.dto.BookingShortDto;
+import ru.practicum.shareit.item.dto.ItemDto;
+import ru.practicum.shareit.user.UserMapper;
+import ru.practicum.shareit.user.controller.UserController;
+import ru.practicum.shareit.user.dto.UserDto;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@ActiveProfiles("test")
+@ContextConfiguration(classes = {ShareItApp.class, TestConfig.class})
+@SpringBootTest
+@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
+class ItemBookingInfoTest {
+
+ @Autowired
+ private ItemController itemController;
+
+ @Autowired
+ private UserController userController;
+
+ @Autowired
+ private BookingController bookingController;
+
+ @Autowired
+ private UserMapper userMapper;
+
+ private UserDto ownerDto;
+ private UserDto bookerDto;
+ private ItemDto itemDto;
+
+ @BeforeEach
+ void setUp() {
+ String ownerEmail = "owner" + System.currentTimeMillis() + "@email.com";
+ ownerDto = UserDto.builder()
+ .name("Item Owner")
+ .email(ownerEmail)
+ .build();
+ ownerDto = userController.create(userMapper.toUser(ownerDto));
+
+ String bookerEmail = "booker" + System.currentTimeMillis() + "@email.com";
+ bookerDto = UserDto.builder()
+ .name("Booker User")
+ .email(bookerEmail)
+ .build();
+ bookerDto = userController.create(userMapper.toUser(bookerDto));
+
+ itemDto = ItemDto.builder()
+ .name("Test Item")
+ .description("Test Description")
+ .available(true)
+ .build();
+ itemDto = itemController.create(ownerDto.getId(), itemDto);
+ }
+
+ @Test // Тесты на получение бронирований для вещи владельца
+ @Transactional
+ @DisplayName("Item should show booking info for owner")
+ void itemShouldShowBookingInfoForOwner() {
+ LocalDateTime nearFutureStart = LocalDateTime.now().plusHours(1);
+ LocalDateTime nearFutureEnd = LocalDateTime.now().plusHours(2);
+ BookingDto nearFutureBooking = BookingDto.builder()
+ .itemId(itemDto.getId())
+ .start(nearFutureStart)
+ .end(nearFutureEnd)
+ .build();
+ BookingDto createdNearFutureBooking = bookingController.createBooking(nearFutureBooking, bookerDto.getId()).getBody();
+ bookingController.approve(createdNearFutureBooking.getId(), ownerDto.getId(), true).getBody(); // Добавлен .getBody()
+
+ LocalDateTime farFutureStart = LocalDateTime.now().plusDays(1);
+ LocalDateTime farFutureEnd = LocalDateTime.now().plusDays(2);
+ BookingDto farFutureBooking = BookingDto.builder()
+ .itemId(itemDto.getId())
+ .start(farFutureStart)
+ .end(farFutureEnd)
+ .build();
+ BookingDto createdFarFutureBooking = bookingController.createBooking(farFutureBooking, bookerDto.getId()).getBody();
+ bookingController.approve(createdFarFutureBooking.getId(), ownerDto.getId(), true).getBody(); // Добавлен .getBody()
+
+ ItemDto itemWithBookings = itemController.getById(itemDto.getId(), ownerDto.getId());
+
+ assertNotNull(itemWithBookings.getNextBooking());
+
+ BookingShortDto nextBooking = itemWithBookings.getNextBooking();
+ assertNotNull(nextBooking.getId());
+ assertEquals(bookerDto.getId(), nextBooking.getBookerId());
+
+ boolean isNearFutureBooking = nextBooking.getStart().equals(nearFutureStart) &&
+ nextBooking.getEnd().equals(nearFutureEnd);
+ boolean isFarFutureBooking = nextBooking.getStart().equals(farFutureStart) &&
+ nextBooking.getEnd().equals(farFutureEnd);
+
+ assertTrue(isNearFutureBooking || isFarFutureBooking,
+ "Next booking should match either near future or far future booking");
+ }
+
+ @Test // Тесты на получение бронирований для вещей кроме вещей владельца
+ @Transactional
+ @DisplayName("Item should not show booking info for non-owner")
+ void itemShouldNotShowBookingInfoForNonOwner() {
+ LocalDateTime start = LocalDateTime.now().plusDays(1);
+ LocalDateTime end = LocalDateTime.now().plusDays(2);
+ BookingDto bookingDto = BookingDto.builder()
+ .itemId(itemDto.getId())
+ .start(start)
+ .end(end)
+ .build();
+ bookingController.createBooking(bookingDto, bookerDto.getId()).getBody();
+
+ ItemDto itemForNonOwner = itemController.getById(itemDto.getId(), bookerDto.getId());
+
+ assertNull(itemForNonOwner.getLastBooking());
+ assertNull(itemForNonOwner.getNextBooking());
+ }
+
+ @Test // Тесты на получение бронирований для всех вещей владельца
+ @Transactional
+ @DisplayName("All owner's items should show booking info")
+ void allOwnerItemsShouldShowBookingInfo() {
+ ItemDto secondItem = ItemDto.builder()
+ .name("Second Item")
+ .description("Another Description")
+ .available(true)
+ .build();
+ secondItem = itemController.create(ownerDto.getId(), secondItem);
+
+ LocalDateTime start1 = LocalDateTime.now().plusDays(1);
+ LocalDateTime end1 = LocalDateTime.now().plusDays(2);
+ BookingDto booking1 = BookingDto.builder()
+ .itemId(itemDto.getId())
+ .start(start1)
+ .end(end1)
+ .build();
+ BookingDto createdBooking1 = bookingController.createBooking(booking1, bookerDto.getId()).getBody();
+ bookingController.approve(createdBooking1.getId(), ownerDto.getId(), true).getBody(); // Добавлен .getBody()
+
+ LocalDateTime start2 = LocalDateTime.now().plusDays(3);
+ LocalDateTime end2 = LocalDateTime.now().plusDays(4);
+ BookingDto booking2 = BookingDto.builder()
+ .itemId(secondItem.getId())
+ .start(start2)
+ .end(end2)
+ .build();
+ BookingDto createdBooking2 = bookingController.createBooking(booking2, bookerDto.getId()).getBody();
+ bookingController.approve(createdBooking2.getId(), ownerDto.getId(), true).getBody(); // Добавлен .getBody()
+
+ List ownerItems = itemController.getAll(ownerDto.getId());
+
+ assertEquals(2, ownerItems.size());
+
+ ItemDto firstItemResult = ownerItems.stream()
+ .filter(item -> item.getId().equals(itemDto.getId()))
+ .findFirst()
+ .orElseThrow();
+
+ ItemDto finalSecondItem = secondItem;
+ ItemDto secondItemResult = ownerItems.stream()
+ .filter(item -> item.getId().equals(finalSecondItem.getId()))
+ .findFirst()
+ .orElseThrow();
+
+ assertNotNull(firstItemResult.getNextBooking());
+ assertEquals(bookerDto.getId(), firstItemResult.getNextBooking().getBookerId());
+ assertEquals(start1, firstItemResult.getNextBooking().getStart());
+ assertEquals(end1, firstItemResult.getNextBooking().getEnd());
+
+ assertNotNull(secondItemResult.getNextBooking());
+ assertEquals(bookerDto.getId(), secondItemResult.getNextBooking().getBookerId());
+ assertEquals(start2, secondItemResult.getNextBooking().getStart());
+ assertEquals(end2, secondItemResult.getNextBooking().getEnd());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/practicum/shareit/item/controller/ItemControllerTest.java b/src/test/java/ru/practicum/shareit/item/controller/ItemControllerTest.java
index ead826a..dc8cc1c 100644
--- a/src/test/java/ru/practicum/shareit/item/controller/ItemControllerTest.java
+++ b/src/test/java/ru/practicum/shareit/item/controller/ItemControllerTest.java
@@ -4,47 +4,51 @@
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.annotation.DirtiesContext;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import ru.practicum.shareit.ShareItApp;
+import ru.practicum.shareit.TestConfig;
import ru.practicum.shareit.booking.exception.ShareItException;
+import ru.practicum.shareit.item.dto.CommentDto;
import ru.practicum.shareit.item.dto.ItemDto;
-import ru.practicum.shareit.user.UserMapper;
-import ru.practicum.shareit.user.controller.UserController;
-import ru.practicum.shareit.user.dto.UserDto;
+import ru.practicum.shareit.item.service.ItemService;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
-
-@SpringBootTest
-@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.when;
+
+@ActiveProfiles("test")
+@ContextConfiguration(classes = {ShareItApp.class, TestConfig.class})
+@ExtendWith(MockitoExtension.class)
class ItemControllerTest {
- @Autowired
- private ItemController itemController;
-
- @Autowired
- private UserController userController;
+ @Mock
+ private ItemService itemService;
- @Autowired
- private UserMapper userMapper;
+ @InjectMocks
+ private ItemController itemController;
private ItemDto itemDto;
- private UserDto userDto;
private Long userId;
@BeforeEach
void setUp() {
- userDto = UserDto.builder()
- .name("Test Owner")
- .email("owner@email.com")
- .build();
- UserDto createdUser = userController.create(userMapper.toUser(userDto));
- userId = createdUser.getId();
-
+ userId = 1L;
itemDto = ItemDto.builder()
+ .id(1L)
.name("Test Item")
.description("Test Description")
.available(true)
@@ -57,10 +61,12 @@ class CreateItemTests {
@Test
@DisplayName("Successful creation of an item")
void createItemTest() {
+ when(itemService.create(any(ItemDto.class), anyLong())).thenReturn(itemDto);
+
ItemDto createdItem = itemController.create(userId, itemDto);
assertNotNull(createdItem);
- assertEquals(1L, createdItem.getId());
+ assertEquals(itemDto.getId(), createdItem.getId());
assertEquals(itemDto.getName(), createdItem.getName());
assertEquals(itemDto.getDescription(), createdItem.getDescription());
assertEquals(itemDto.getAvailable(), createdItem.getAvailable());
@@ -69,6 +75,9 @@ void createItemTest() {
@Test
@DisplayName("Error when creating an item with a non-existent user")
void createItemWithNonExistentUserTest() {
+ when(itemService.create(any(ItemDto.class), eq(999L)))
+ .thenThrow(new ShareItException.NotFoundException("User not found"));
+
assertThrows(ShareItException.NotFoundException.class,
() -> itemController.create(999L, itemDto));
}
@@ -80,40 +89,45 @@ class GetItemTests {
@Test
@DisplayName("Getting an item by ID")
void getItemByIdTest() {
- ItemDto createdItem = itemController.create(userId, itemDto);
- ItemDto retrievedItem = itemController.getById(createdItem.getId());
+ when(itemService.getById(anyLong(), anyLong())).thenReturn(itemDto);
+
+ ItemDto retrievedItem = itemController.getById(1L, userId);
assertNotNull(retrievedItem);
- assertEquals(createdItem.getId(), retrievedItem.getId());
- assertEquals(createdItem.getName(), retrievedItem.getName());
- assertEquals(createdItem.getDescription(), retrievedItem.getDescription());
- assertEquals(createdItem.getAvailable(), retrievedItem.getAvailable());
+ assertEquals(itemDto.getId(), retrievedItem.getId());
+ assertEquals(itemDto.getName(), retrievedItem.getName());
+ assertEquals(itemDto.getDescription(), retrievedItem.getDescription());
+ assertEquals(itemDto.getAvailable(), retrievedItem.getAvailable());
}
@Test
@DisplayName("Getting all the user's items")
void getAllUserItemsTest() {
- itemController.create(userId, itemDto);
-
ItemDto secondItem = ItemDto.builder()
+ .id(2L)
.name("Second Item")
.description("Another Description")
.available(true)
.build();
- itemController.create(userId, secondItem);
+ List items = Arrays.asList(itemDto, secondItem);
+
+ when(itemService.getAll(anyLong())).thenReturn(items);
- List items = itemController.getAll(userId);
+ List retrievedItems = itemController.getAll(userId);
- assertEquals(2, items.size());
- assertEquals("Test Item", items.get(0).getName());
- assertEquals("Second Item", items.get(1).getName());
+ assertEquals(2, retrievedItems.size());
+ assertEquals("Test Item", retrievedItems.get(0).getName());
+ assertEquals("Second Item", retrievedItems.get(1).getName());
}
@Test
@DisplayName("Error when receiving a non-existent item")
void getNonExistentItemTest() {
+ when(itemService.getById(eq(999L), anyLong()))
+ .thenThrow(new ShareItException.NotFoundException("Item not found"));
+
assertThrows(ShareItException.NotFoundException.class,
- () -> itemController.getById(999L));
+ () -> itemController.getById(999L, userId));
}
}
@@ -123,21 +137,29 @@ class UpdateItemTests {
@Test
@DisplayName("Full item update")
void updateItemTest() {
- ItemDto createdItem = itemController.create(userId, itemDto);
-
ItemDto updateRequest = ItemDto.builder()
.name("Updated Item")
.description("Updated Description")
.available(false)
.build();
- ItemDto updatedItem = itemController.update(updateRequest, createdItem.getId(), userId);
+ ItemDto updatedItem = ItemDto.builder()
+ .id(1L)
+ .name("Updated Item")
+ .description("Updated Description")
+ .available(false)
+ .build();
- assertEquals("Updated Item", updatedItem.getName());
- assertEquals("Updated Description", updatedItem.getDescription());
- assertEquals(false, updatedItem.getAvailable());
+ when(itemService.update(any(ItemDto.class), anyLong(), anyLong())).thenReturn(updatedItem);
+ when(itemService.getById(anyLong(), anyLong())).thenReturn(updatedItem);
- ItemDto retrievedItem = itemController.getById(createdItem.getId());
+ ItemDto result = itemController.update(updateRequest, 1L, userId);
+
+ assertEquals("Updated Item", result.getName());
+ assertEquals("Updated Description", result.getDescription());
+ assertEquals(false, result.getAvailable());
+
+ ItemDto retrievedItem = itemController.getById(1L, userId);
assertEquals("Updated Item", retrievedItem.getName());
assertEquals("Updated Description", retrievedItem.getDescription());
assertEquals(false, retrievedItem.getAvailable());
@@ -146,32 +168,47 @@ void updateItemTest() {
@Test
@DisplayName("Partial item update")
void partialUpdateItemTest() {
- ItemDto createdItem = itemController.create(userId, itemDto);
- ItemDto result;
+ ItemDto nameUpdateRequest = ItemDto.builder().name("New Name").build();
+ ItemDto nameUpdatedItem = ItemDto.builder()
+ .id(1L)
+ .name("New Name")
+ .description("Test Description")
+ .available(true)
+ .build();
+
+ when(itemService.update(eq(nameUpdateRequest), anyLong(), anyLong())).thenReturn(nameUpdatedItem);
- result = itemController.update(
- ItemDto.builder().name("New Name").build(),
- createdItem.getId(),
- userId
- );
+ ItemDto result = itemController.update(nameUpdateRequest, 1L, userId);
assertEquals("New Name", result.getName());
- assertEquals(itemDto.getDescription(), result.getDescription());
- assertEquals(itemDto.getAvailable(), result.getAvailable());
-
- result = itemController.update(
- ItemDto.builder().description("New Description").build(),
- createdItem.getId(),
- userId
- );
+ assertEquals("Test Description", result.getDescription());
+ assertEquals(true, result.getAvailable());
+
+ ItemDto descUpdateRequest = ItemDto.builder().description("New Description").build();
+ ItemDto descUpdatedItem = ItemDto.builder()
+ .id(1L)
+ .name("New Name")
+ .description("New Description")
+ .available(true)
+ .build();
+
+ when(itemService.update(eq(descUpdateRequest), anyLong(), anyLong())).thenReturn(descUpdatedItem);
+
+ result = itemController.update(descUpdateRequest, 1L, userId);
assertEquals("New Name", result.getName());
assertEquals("New Description", result.getDescription());
- assertEquals(itemDto.getAvailable(), result.getAvailable());
+ assertEquals(true, result.getAvailable());
+
+ ItemDto availUpdateRequest = ItemDto.builder().available(false).build();
+ ItemDto availUpdatedItem = ItemDto.builder()
+ .id(1L)
+ .name("New Name")
+ .description("New Description")
+ .available(false)
+ .build();
- result = itemController.update(
- ItemDto.builder().available(false).build(),
- createdItem.getId(),
- userId
- );
+ when(itemService.update(eq(availUpdateRequest), anyLong(), anyLong())).thenReturn(availUpdatedItem);
+
+ result = itemController.update(availUpdateRequest, 1L, userId);
assertEquals("New Name", result.getName());
assertEquals("New Description", result.getDescription());
assertEquals(false, result.getAvailable());
@@ -180,20 +217,15 @@ void partialUpdateItemTest() {
@Test
@DisplayName("Error when updating an item by a non-owner")
void updateItemByNonOwnerTest() {
- ItemDto createdItem = itemController.create(userId, itemDto);
-
- UserDto anotherUserDto = UserDto.builder()
- .name("Another User")
- .email("another@email.com")
- .build();
- UserDto anotherUser = userController.create(userMapper.toUser(anotherUserDto));
-
ItemDto updateRequest = ItemDto.builder()
.name("Unauthorized Update")
.build();
+ when(itemService.update(any(ItemDto.class), anyLong(), eq(2L)))
+ .thenThrow(new ShareItException.NotFoundException("Item not found for this user"));
+
assertThrows(ShareItException.NotFoundException.class,
- () -> itemController.update(updateRequest, createdItem.getId(), anotherUser.getId()));
+ () -> itemController.update(updateRequest, 1L, 2L));
}
}
@@ -203,10 +235,15 @@ class DeleteItemTests {
@Test
@DisplayName("Successful removal of an item")
void deleteItemTest() {
- ItemDto createdItem = itemController.create(userId, itemDto);
+ when(itemService.getAll(userId))
+ .thenReturn(Collections.singletonList(itemDto))
+ .thenReturn(Collections.emptyList()); // для второго вызова (после удаления)
+
+ doNothing().when(itemService).delete(anyLong());
+
assertEquals(1, itemController.getAll(userId).size());
- itemController.delete(createdItem.getId());
+ itemController.delete(1L);
assertEquals(0, itemController.getAll(userId).size());
}
@@ -218,32 +255,44 @@ class SearchItemTests {
@Test
@DisplayName("Search for items based on various criteria")
void searchItemsTest() {
- itemController.create(userId, itemDto);
+ ItemDto testItem = ItemDto.builder()
+ .id(1L)
+ .name("Test Item")
+ .description("Test Description")
+ .available(true)
+ .build();
- ItemDto secondItem = ItemDto.builder()
+ ItemDto specialItem = ItemDto.builder()
+ .id(2L)
.name("Special Item")
.description("Unique features")
.available(true)
.build();
- itemController.create(userId, secondItem);
- ItemDto unavailableItem = ItemDto.builder()
- .name("Unavailable Special")
- .description("Not for rent")
- .available(false)
- .build();
- itemController.create(userId, unavailableItem);
+ when(itemService.search("Test"))
+ .thenReturn(Collections.singletonList(testItem));
+
+ when(itemService.search("Unique"))
+ .thenReturn(Collections.singletonList(specialItem));
+
+ when(itemService.search("Item"))
+ .thenReturn(Arrays.asList(testItem, specialItem));
+
+ when(itemService.search("Unavailable"))
+ .thenReturn(Collections.emptyList());
+
+ when(itemService.search(""))
+ .thenReturn(Collections.emptyList());
List results = itemController.search("Test");
- assertEquals(1, results.size());
- assertEquals("Test Item", results.get(0).getName());
+ assertTrue(results.stream().anyMatch(item -> item.getName().equals("Test Item")));
results = itemController.search("Unique");
- assertEquals(1, results.size());
- assertEquals("Special Item", results.get(0).getName());
+ assertTrue(results.stream().anyMatch(item -> item.getName().equals("Special Item")));
results = itemController.search("Item");
- assertEquals(2, results.size());
+ assertTrue(results.stream().anyMatch(item -> item.getName().equals("Test Item")));
+ assertTrue(results.stream().anyMatch(item -> item.getName().equals("Special Item")));
results = itemController.search("Unavailable");
assertEquals(0, results.size());
@@ -252,4 +301,60 @@ void searchItemsTest() {
assertEquals(0, results.size());
}
}
+
+ @Nested // Тесты на комментарии
+ @DisplayName("Comment Tests")
+ class CommentTests {
+ @Test
+ @DisplayName("Create comment successfully")
+ void createCommentTest() {
+ CommentDto commentDto = CommentDto.builder()
+ .text("Great item!")
+ .build();
+
+ CommentDto createdComment = CommentDto.builder()
+ .id(1L)
+ .text("Great item!")
+ .authorName("User")
+ .build();
+
+ when(itemService.createComment(anyLong(), any(CommentDto.class), anyLong()))
+ .thenReturn(createdComment);
+
+ CommentDto result = itemController.createComment(1L, commentDto, userId);
+
+ assertNotNull(result);
+ assertEquals(1L, result.getId());
+ assertEquals("Great item!", result.getText());
+ assertEquals("User", result.getAuthorName());
+ }
+
+ @Test
+ @DisplayName("Error when creating comment for non-existent item")
+ void createCommentForNonExistentItemTest() {
+ CommentDto commentDto = CommentDto.builder()
+ .text("Great item!")
+ .build();
+
+ when(itemService.createComment(eq(999L), any(CommentDto.class), anyLong()))
+ .thenThrow(new ShareItException.NotFoundException("Item not found"));
+
+ assertThrows(ShareItException.NotFoundException.class,
+ () -> itemController.createComment(999L, commentDto, userId));
+ }
+
+ @Test
+ @DisplayName("Error when user hasn't booked the item")
+ void createCommentWithoutBookingTest() {
+ CommentDto commentDto = CommentDto.builder()
+ .text("Great item!")
+ .build();
+
+ when(itemService.createComment(anyLong(), any(CommentDto.class), anyLong()))
+ .thenThrow(new ShareItException.BadRequestException("User hasn't booked this item"));
+
+ assertThrows(ShareItException.BadRequestException.class,
+ () -> itemController.createComment(1L, commentDto, userId));
+ }
+ }
}
\ No newline at end of file
diff --git a/src/test/java/ru/practicum/shareit/user/controller/UserControllerTest.java b/src/test/java/ru/practicum/shareit/user/controller/UserControllerTest.java
index c1c5962..3254097 100644
--- a/src/test/java/ru/practicum/shareit/user/controller/UserControllerTest.java
+++ b/src/test/java/ru/practicum/shareit/user/controller/UserControllerTest.java
@@ -7,6 +7,10 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import ru.practicum.shareit.ShareItApp;
+import ru.practicum.shareit.TestConfig;
import ru.practicum.shareit.booking.exception.ShareItException;
import ru.practicum.shareit.user.UserMapper;
import ru.practicum.shareit.user.dto.UserDto;
@@ -17,7 +21,10 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+@ActiveProfiles("test")
+@ContextConfiguration(classes = {ShareItApp.class, TestConfig.class})
@SpringBootTest
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
class UserControllerTest {
@@ -32,11 +39,12 @@ class UserControllerTest {
@BeforeEach
void setUp() {
+ String uniqueEmail = "test" + System.currentTimeMillis() + "@email.com";
+
userDto = UserDto.builder()
.name("Test User")
- .email("test@email.com")
+ .email(uniqueEmail)
.build();
-
user = userMapper.toUser(userDto);
}
@@ -49,7 +57,7 @@ void createUserTest() {
UserDto createdUser = userController.create(user);
assertNotNull(createdUser);
- assertEquals(1L, createdUser.getId());
+ assertNotNull(createdUser.getId()); // Проверяем, что ID не null, а не конкретное значение
assertEquals(userDto.getName(), createdUser.getName());
assertEquals(userDto.getEmail(), createdUser.getEmail());
}
@@ -61,7 +69,7 @@ void createUserWithDuplicateEmailTest() {
User duplicateUser = User.builder()
.name("Another User")
- .email("test@email.com") // Тот же email
+ .email(user.getEmail()) // Тот же email
.build();
assertThrows(ShareItException.ConflictException.class,
@@ -87,27 +95,35 @@ void getUserByIdTest() {
@Test
@DisplayName("Getting all users")
void getAllUsersTest() {
- userController.create(user);
+ UserDto createdUser = userController.create(user);
- // Создаем второго пользователя
+ String secondEmail = "second" + System.currentTimeMillis() + "@email.com";
UserDto secondUser = UserDto.builder()
.name("Second User")
- .email("second@email.com")
+ .email(secondEmail)
.build();
- userController.create(userMapper.toUser(secondUser));
+ UserDto createdSecondUser = userController.create(userMapper.toUser(secondUser));
List users = userController.getAll();
- assertEquals(2, users.size());
- assertEquals("Test User", users.get(0).getName());
- assertEquals("Second User", users.get(1).getName());
+ assertTrue(users.size() >= 2);
+ assertTrue(users.stream().anyMatch(u -> u.getId().equals(createdUser.getId())));
+ assertTrue(users.stream().anyMatch(u -> u.getId().equals(createdSecondUser.getId())));
}
@Test
@DisplayName("Error when receiving a non-existent user")
void getNonExistentUserTest() {
+ List allUsers = userController.getAll();
+ long maxId = allUsers.stream()
+ .mapToLong(UserDto::getId)
+ .max()
+ .orElse(0);
+
+ long nonExistentId = maxId + 1000;
+
assertThrows(ShareItException.NotFoundException.class,
- () -> userController.getById(999L));
+ () -> userController.getById(nonExistentId));
}
}
@@ -119,19 +135,20 @@ class UpdateUserTests {
void updateUserTest() {
UserDto createdUser = userController.create(user);
+ String updatedEmail = "updated" + System.currentTimeMillis() + "@email.com";
User updatedUser = User.builder()
.name("Updated Name")
- .email("updated@email.com")
+ .email(updatedEmail)
.build();
UserDto result = userController.update(updatedUser, createdUser.getId());
assertEquals("Updated Name", result.getName());
- assertEquals("updated@email.com", result.getEmail());
+ assertEquals(updatedEmail, result.getEmail());
UserDto retrievedUser = userController.getById(createdUser.getId());
assertEquals("Updated Name", retrievedUser.getName());
- assertEquals("updated@email.com", retrievedUser.getEmail());
+ assertEquals(updatedEmail, retrievedUser.getEmail());
}
@Test // Тест на частичное обновление пользователя
@@ -147,12 +164,13 @@ void partialUpdateUserTest() {
assertEquals("New Name", result.getName());
assertEquals(userDto.getEmail(), result.getEmail());
+ String newEmail = "new" + System.currentTimeMillis() + "@email.com";
User emailUpdate = User.builder()
- .email("new@email.com")
+ .email(newEmail)
.build();
result = userController.update(emailUpdate, createdUser.getId());
assertEquals("New Name", result.getName());
- assertEquals("new@email.com", result.getEmail());
+ assertEquals(newEmail, result.getEmail());
}
}
@@ -163,11 +181,12 @@ class DeleteUserTests {
@DisplayName("Successful user deletion")
void deleteUserTest() {
UserDto createdUser = userController.create(user);
- assertEquals(1, userController.getAll().size());
+ int initialSize = userController.getAll().size();
userController.delete(createdUser.getId());
- assertEquals(0, userController.getAll().size());
+ int newSize = userController.getAll().size();
+ assertEquals(initialSize - 1, newSize);
assertThrows(ShareItException.NotFoundException.class,
() -> userController.getById(createdUser.getId()));