diff --git a/.run/ShareItGateway.run.xml b/.run/ShareItGateway.run.xml
index 32c8129d..abd21aaa 100644
--- a/.run/ShareItGateway.run.xml
+++ b/.run/ShareItGateway.run.xml
@@ -4,7 +4,7 @@
-
+
diff --git a/.run/ShareItServer.run.xml b/.run/ShareItServer.run.xml
index a8ed9e5f..ddf9dba1 100644
--- a/.run/ShareItServer.run.xml
+++ b/.run/ShareItServer.run.xml
@@ -4,7 +4,7 @@
-
+
diff --git a/README.md b/README.md
index 47a75f04..5c9e2fd7 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,38 @@
# java-shareit
-Template repository for Shareit project.
+[](https://openjdk.org/)
+[](https://spring.io/projects/spring-boot)
+
+Сервис для шеринга вещей позволяет пользователям брать предметы в аренду и предлагать свои вещи в аренду другим.
+
+### Базовый URL
+`http://localhost:8080`
+
+### Пользователи
+| Метод | Путь | Описание |
+|-------|------|----------|
+| POST | `/users` | Создание нового пользователя |
+| GET | `/users/{userId}` | Получение информации о пользователе |
+| PATCH | `/users/{userId}` | Обновление данных пользователя |
+| GET | `/users` | Получение списка всех пользователей |
+
+### Вещи
+| Метод | Путь | Описание | Требуемые заголовки |
+|-------|-----------------------------|-----------------------------------|---------------------|
+| POST | `/items` | Добавление новой вещи | `X-Sharer-User-Id` |
+| POST | `/items/{itemId}/comment` | Создание комменатрия | `X-Sharer-User-Id` |
+| PATCH | `/items/{itemId}` | Обновление вещи | `X-Sharer-User-Id` |
+| GET | `/items/{itemId}` | Получение информации о вещи | - |
+| GET | `/items` | Получение всех вещей пользователя | `X-Sharer-User-Id` |
+| GET | `/items/search?text={text}` | Поиск вещей | - |
+
+### Бронь
+| Метод | Путь | Описание | Требуемые заголовки |
+|-------|---------------------------------|-----------------------------------------------|---------------------|
+| POST | `/bookings` | Добавление новой брони | `X-Sharer-User-Id` |
+| PATCH | `/bookings/{bookingId}?approved` | Изменение состояние брони | `X-Sharer-User-Id` |
+| PATCH | `/bookings/{bookingId}/canceled` | Отмена брони | `X-Sharer-User-Id` |
+| GET | `/bookings/{bookingId}` | Получение информации о брони | `X-Sharer-User-Id` |
+| GET | `/bookings?state={state}` | Получение списка брони в определенном статусе | `X-Sharer-User-Id` |
+| GET | `/bookings/owner?state={state}` | Получение списка броней всех вещей пользователя| `X-Sharer-User-Id` |
+
+
\ No newline at end of file
diff --git a/gateway/pom.xml b/gateway/pom.xml
index f3394c14..1daab9cd 100644
--- a/gateway/pom.xml
+++ b/gateway/pom.xml
@@ -29,11 +29,6 @@
spring-boot-starter-actuator
-
- org.hibernate.validator
- hibernate-validator
-
-
org.apache.httpcomponents.client5
httpclient5
diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/BookingClient.java b/gateway/src/main/java/ru/practicum/shareit/booking/BookingClient.java
index 471c44f5..3e597cdf 100644
--- a/gateway/src/main/java/ru/practicum/shareit/booking/BookingClient.java
+++ b/gateway/src/main/java/ru/practicum/shareit/booking/BookingClient.java
@@ -8,6 +8,7 @@
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Service;
+import org.springframework.web.bind.annotation.*;
import org.springframework.web.util.DefaultUriBuilderFactory;
import ru.practicum.shareit.booking.dto.BookItemRequestDto;
@@ -37,6 +38,16 @@ public ResponseEntity
+
+
+ org.mapstruct
+ mapstruct
+ 1.6.2
+
+
+ org.jetbrains
+ annotations
+ RELEASE
+ compile
+
@@ -66,6 +78,28 @@
org.springframework.boot
spring-boot-maven-plugin
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.11.0
+
+ 17
+ 17
+
+
+ org.projectlombok
+ lombok
+ 1.18.32
+
+
+ org.mapstruct
+ mapstruct-processor
+ 1.6.2
+
+
+
+
diff --git a/server/src/main/java/ru/practicum/shareit/booking/Booking.java b/server/src/main/java/ru/practicum/shareit/booking/Booking.java
new file mode 100644
index 00000000..929903da
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/booking/Booking.java
@@ -0,0 +1,40 @@
+package ru.practicum.shareit.booking;
+
+import jakarta.persistence.*;
+import lombok.*;
+import ru.practicum.shareit.booking.enums.BookingStatus;
+import ru.practicum.shareit.item.Item;
+import ru.practicum.shareit.user.User;
+
+import java.time.LocalDateTime;
+
+@Getter
+@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity
+@Table(name = "bookings")
+public class Booking {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(name = "start_time")
+ private LocalDateTime start;
+
+ @Column(name = "end_time")
+ private LocalDateTime end;
+
+ @ManyToOne
+ @JoinColumn(name = "item_id", referencedColumnName = "id")
+ private Item item;
+
+ @ManyToOne
+ @JoinColumn(name = "booker", referencedColumnName = "id")
+ private User booker;
+
+ @Enumerated(EnumType.STRING)
+ private BookingStatus status;
+}
diff --git a/server/src/main/java/ru/practicum/shareit/booking/BookingController.java b/server/src/main/java/ru/practicum/shareit/booking/BookingController.java
new file mode 100644
index 00000000..5f4d4b50
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/booking/BookingController.java
@@ -0,0 +1,85 @@
+package ru.practicum.shareit.booking;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+import ru.practicum.shareit.booking.dto.BookingDto;
+import ru.practicum.shareit.booking.dto.BookingResponseDto;
+import ru.practicum.shareit.booking.service.BookingService;
+
+import java.util.List;
+
+@RestController
+@RequestMapping(path = "/bookings")
+@RequiredArgsConstructor
+public class BookingController {
+ private final BookingService bookingService;
+
+ /**
+ * Создание брони.
+ * Post /bookings
+ * Headers X-Sharer-User-Id
+ */
+ @PostMapping
+ public BookingResponseDto createBooking(@RequestHeader("X-Sharer-User-Id") Long userId,
+ @RequestBody BookingDto bookingDto) {
+ return bookingService.createBooking(userId, bookingDto);
+ }
+
+ /**
+ * Изменение состояния брони.
+ * Patch /bookings/bookingId?approved
+ * Headers X-Sharer-User-Id
+ */
+ @PatchMapping("/{bookingId}")
+ public BookingResponseDto approveBooking(@PathVariable("bookingId") Long bookingId,
+ @RequestParam("approved") Boolean approved,
+ @RequestHeader("X-Sharer-User-Id") Long userId) {
+ return bookingService.approveBooking(userId, bookingId, approved);
+ }
+
+ /**
+ * Отмена бронирования
+ * Patch /bookings/bookingId/canceled
+ * Headers X-Sharer-User-Id
+ */
+ @PatchMapping("/{bookingId}/canceled")
+ public BookingResponseDto canceledBooking(@PathVariable("bookingId") Long bookingId,
+ @RequestHeader("X-Sharer-User-Id") Long userId) {
+ return bookingService.canceledBooking(userId, bookingId);
+ }
+
+ /**
+ * Получение информации о бронировании.
+ * Get /bookings/bookingId
+ * Headers X-Sharer-User-Id
+ */
+ @GetMapping("/{bookingId}")
+ public BookingResponseDto getBookingById(@PathVariable("bookingId") Long bookingId,
+ @RequestHeader("X-Sharer-User-Id") Long userId) {
+ return bookingService.getBooking(userId, bookingId);
+ }
+
+ /**
+ * Получение списка броней с определенным состоянием,
+ * Список отсортированы по дату в порядке убывания
+ * GET /bookings?state={state}
+ * Headers X-Sharer-User-Id
+ */
+ @GetMapping()
+ public List getBookingsByState(@RequestParam(name = "state", defaultValue = "ALL") String state,
+ @RequestHeader("X-Sharer-User-Id") Long userId) {
+ return bookingService.getBookingByState(userId, state);
+ }
+
+ /**
+ * Получение списка броней всех вещей пользователя,
+ * Список отсортированы по дату в порядке убывания
+ * GET /bookings/owner?state={state}
+ * Headers X-Sharer-User-Id
+ */
+ @GetMapping("/owner")
+ public List getBookingsAllItemsByState(@RequestParam(name = "state", defaultValue = "ALL") String state,
+ @RequestHeader("X-Sharer-User-Id") Long userId) {
+ return bookingService.getBookingsAllItemsByState(state, userId);
+ }
+}
diff --git a/server/src/main/java/ru/practicum/shareit/booking/BookingMapper.java b/server/src/main/java/ru/practicum/shareit/booking/BookingMapper.java
new file mode 100644
index 00000000..8a852dbe
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/booking/BookingMapper.java
@@ -0,0 +1,42 @@
+package ru.practicum.shareit.booking;
+
+import org.mapstruct.Mapper;
+import ru.practicum.shareit.booking.dto.BookingDto;
+import ru.practicum.shareit.booking.dto.BookingResponseDto;
+import ru.practicum.shareit.booking.enums.BookingStatus;
+import ru.practicum.shareit.item.Item;
+import ru.practicum.shareit.item.ItemMapper;
+import ru.practicum.shareit.user.User;
+import ru.practicum.shareit.user.UserMapper;
+
+import java.util.List;
+
+@Mapper(componentModel = "spring", uses = {UserMapper.class, ItemMapper.class})
+public interface BookingMapper {
+
+ default Booking mapBooking(BookingDto bookingDto, User user, Item item) {
+ return Booking.builder()
+ .id(bookingDto.getId())
+ .start(bookingDto.getStart())
+ .end(bookingDto.getEnd())
+ .item(item)
+ .booker(user)
+ .status(BookingStatus.valueOf(bookingDto.getStatus()))
+ .build();
+ }
+
+ default BookingDto mapBookingDto(Booking booking) {
+ return BookingDto.builder()
+ .id(booking.getId())
+ .start(booking.getStart())
+ .end(booking.getEnd())
+ .status(String.valueOf(booking.getStatus()))
+ .booker(booking.getBooker().getId())
+ .itemId(booking.getItem().getId())
+ .build();
+ }
+
+ BookingResponseDto mapBookingResponseDto(Booking booking);
+
+ List mapListBookingResponseDto(List bookings);
+}
diff --git a/server/src/main/java/ru/practicum/shareit/booking/BookingRepository.java b/server/src/main/java/ru/practicum/shareit/booking/BookingRepository.java
new file mode 100644
index 00000000..7e17a89d
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/booking/BookingRepository.java
@@ -0,0 +1,147 @@
+package ru.practicum.shareit.booking;
+
+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.enums.BookingStatus;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Optional;
+
+public interface BookingRepository extends JpaRepository {
+
+ @Query("""
+ SELECT b FROM Booking b
+ WHERE b.item.id = :itemId
+ AND b.end < :now
+ AND b.status = 'APPROVED'
+ ORDER BY b.end DESC
+ LIMIT 1
+ """)
+ Optional findLastBooking(@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
+ LIMIT 1
+ """)
+ Optional findNextBooking(@Param("itemId") Long itemId, @Param("now") LocalDateTime now);
+
+ @Query("SELECT b " +
+ "FROM Booking b " +
+ "JOIN FETCH b.item i " +
+ "JOIN FETCH i.owner " +
+ "WHERE b.booker.id = :id " +
+ "ORDER BY b.start DESC")
+ List getBookingByStateALL(@Param("id") Long id);
+
+ @Query("SELECT b " +
+ "FROM Booking b " +
+ "JOIN FETCH b.item i " +
+ "JOIN FETCH i.owner " +
+ "WHERE b.booker.id = :id " +
+ "AND b.status = :status " +
+ "ORDER BY b.start DESC")
+ List getBookingByStateStatus(@Param("id") Long id, @Param("status") BookingStatus status);
+
+ @Query("SELECT b " +
+ "FROM Booking b " +
+ "JOIN FETCH b.item i " +
+ "JOIN FETCH i.owner " +
+ "WHERE b.booker.id = :id " +
+ "AND b.status = 'APPROVED' " +
+ "AND b.start <= :currentTime " +
+ "AND b.end >= :currentTime " +
+ "ORDER BY b.start DESC")
+ List getBookingByStateCurrent(@Param("id") Long id,
+ @Param("currentTime")LocalDateTime currentTime);
+
+ @Query("SELECT b " +
+ "FROM Booking b " +
+ "JOIN FETCH b.item i " +
+ "JOIN FETCH i.owner " +
+ "WHERE b.booker.id = :id " +
+ "AND b.status = 'APPROVED' " +
+ "AND b.start >= :currentTime " +
+ "ORDER BY b.start DESC")
+ List getBookingByStateFuture(@Param("id") Long id,
+ @Param("currentTime")LocalDateTime currentTime);
+
+ @Query("SELECT b " +
+ "FROM Booking b " +
+ "JOIN FETCH b.item i " +
+ "JOIN FETCH i.owner " +
+ "WHERE b.booker.id = :id " +
+ "AND b.status = 'APPROVED' " +
+ "AND b.end <= :currentTime " +
+ "ORDER BY b.start DESC")
+ List getBookingByStatePast(@Param("id") Long id,
+ @Param("currentTime")LocalDateTime currentTime);
+
+ @Query("SELECT b " +
+ "FROM Booking b " +
+ "JOIN FETCH b.item i " +
+ "JOIN FETCH i.owner " +
+ "WHERE i.owner.id = :id " +
+ "ORDER BY b.start DESC")
+ List getBookingAllItemsByStateALL(@Param("id") Long id);
+
+ @Query("SELECT b " +
+ "FROM Booking b " +
+ "JOIN FETCH b.item i " +
+ "JOIN FETCH i.owner " +
+ "WHERE i.owner.id = :id " +
+ "AND b.status = :status " +
+ "ORDER BY b.start DESC")
+ List getBookingAllItemsByStateStatus(@Param("id") Long id, @Param("status") BookingStatus status);
+
+ @Query("SELECT b " +
+ "FROM Booking b " +
+ "JOIN FETCH b.item i " +
+ "JOIN FETCH i.owner " +
+ "WHERE i.owner.id = :id " +
+ "AND b.status = 'APPROVED' " +
+ "AND b.start <= :currentTime " +
+ "AND b.end >= :currentTime " +
+ "ORDER BY b.start DESC")
+ List getBookingAllItemsByStateCurrent(@Param("id") Long id,
+ @Param("currentTime")LocalDateTime currentTime);
+
+ @Query("SELECT b " +
+ "FROM Booking b " +
+ "JOIN FETCH b.item i " +
+ "JOIN FETCH i.owner " +
+ "WHERE i.owner.id = :id " +
+ "AND b.status = 'APPROVED' " +
+ "AND b.start >= :currentTime " +
+ "ORDER BY b.start DESC")
+ List getBookingAllItemsByStateFuture(@Param("id") Long id,
+ @Param("currentTime")LocalDateTime currentTime);
+
+ @Query("SELECT b " +
+ "FROM Booking b " +
+ "JOIN FETCH b.item i " +
+ "JOIN FETCH i.owner " +
+ "WHERE i.owner.id = :id " +
+ "AND b.status = 'APPROVED' " +
+ "AND b.end <= :currentTime " +
+ "ORDER BY b.start DESC")
+ List getBookingAllItemsByStatePast(@Param("id") Long id,
+ @Param("currentTime")LocalDateTime currentTime);
+
+ @Query("SELECT b " +
+ "FROM Booking b " +
+ "JOIN FETCH b.item i " +
+ "JOIN FETCH i.owner " +
+ "WHERE b.booker.id = :user AND i.id = :item " +
+ "AND b.status = 'APPROVED' " +
+ "AND b.end <= :currentTime " +
+ "ORDER BY b.start DESC")
+ Optional getPostBooking(@Param("item") Long item,
+ @Param("user") Long user,
+ @Param("currentTime")LocalDateTime currentTime);
+}
\ No newline at end of file
diff --git a/server/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java b/server/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java
new file mode 100644
index 00000000..98ef60bf
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java
@@ -0,0 +1,17 @@
+package ru.practicum.shareit.booking.dto;
+
+import lombok.Builder;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@Builder
+public class BookingDto {
+ private Long id;
+ private LocalDateTime start;
+ private LocalDateTime end;
+ private Long itemId;
+ private Long booker;
+ private String status;
+}
diff --git a/server/src/main/java/ru/practicum/shareit/booking/dto/BookingResponseDto.java b/server/src/main/java/ru/practicum/shareit/booking/dto/BookingResponseDto.java
new file mode 100644
index 00000000..801c0203
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/booking/dto/BookingResponseDto.java
@@ -0,0 +1,19 @@
+package ru.practicum.shareit.booking.dto;
+
+import lombok.Builder;
+import lombok.Data;
+import ru.practicum.shareit.item.dto.ItemDto;
+import ru.practicum.shareit.user.UserDto;
+
+import java.time.LocalDateTime;
+
+@Data
+@Builder
+public class BookingResponseDto {
+ private Long id;
+ private LocalDateTime start;
+ private LocalDateTime end;
+ private ItemDto item;
+ private UserDto booker;
+ private String status;
+}
diff --git a/server/src/main/java/ru/practicum/shareit/booking/enums/BookingState.java b/server/src/main/java/ru/practicum/shareit/booking/enums/BookingState.java
new file mode 100644
index 00000000..a4488cd8
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/booking/enums/BookingState.java
@@ -0,0 +1,10 @@
+package ru.practicum.shareit.booking.enums;
+
+public enum BookingState {
+ ALL,
+ CURRENT,
+ PAST,
+ FUTURE,
+ WAITING,
+ REJECTED
+}
diff --git a/server/src/main/java/ru/practicum/shareit/booking/enums/BookingStatus.java b/server/src/main/java/ru/practicum/shareit/booking/enums/BookingStatus.java
new file mode 100644
index 00000000..a61c1e4e
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/booking/enums/BookingStatus.java
@@ -0,0 +1,8 @@
+package ru.practicum.shareit.booking.enums;
+
+public enum BookingStatus {
+ WAITING,
+ APPROVED,
+ REJECTED,
+ CANCELED
+}
diff --git a/server/src/main/java/ru/practicum/shareit/booking/service/BookingService.java b/server/src/main/java/ru/practicum/shareit/booking/service/BookingService.java
new file mode 100644
index 00000000..91b44013
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/booking/service/BookingService.java
@@ -0,0 +1,21 @@
+package ru.practicum.shareit.booking.service;
+
+import ru.practicum.shareit.booking.dto.BookingDto;
+import ru.practicum.shareit.booking.dto.BookingResponseDto;
+
+import java.util.List;
+
+public interface BookingService {
+
+ BookingResponseDto createBooking(Long userId, BookingDto bookingDto);
+
+ BookingResponseDto approveBooking(Long userId, Long bookingId, Boolean approved);
+
+ BookingResponseDto canceledBooking(Long userId, Long bookingId);
+
+ BookingResponseDto getBooking(Long userId, Long bookingId);
+
+ List getBookingByState(Long userId, String state);
+
+ List getBookingsAllItemsByState(String state, Long userId);
+}
diff --git a/server/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java b/server/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java
new file mode 100644
index 00000000..4bc980c8
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java
@@ -0,0 +1,151 @@
+package ru.practicum.shareit.booking.service;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import ru.practicum.shareit.booking.Booking;
+import ru.practicum.shareit.booking.BookingMapper;
+import ru.practicum.shareit.booking.BookingRepository;
+import ru.practicum.shareit.booking.dto.BookingDto;
+import ru.practicum.shareit.booking.dto.BookingResponseDto;
+import ru.practicum.shareit.booking.enums.BookingState;
+import ru.practicum.shareit.booking.enums.BookingStatus;
+import ru.practicum.shareit.exception.ErrorRequestException;
+import ru.practicum.shareit.exception.NotFoundException;
+import ru.practicum.shareit.item.Item;
+import ru.practicum.shareit.item.ItemRepository;
+import ru.practicum.shareit.user.User;
+import ru.practicum.shareit.user.UserRepository;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Service
+@RequiredArgsConstructor
+@Slf4j
+@Transactional
+public class BookingServiceImpl implements BookingService {
+ private final BookingRepository repository;
+ private final UserRepository userRepository;
+ private final ItemRepository itemRepository;
+ private final BookingMapper bookingMapper;
+
+ @Override
+ public BookingResponseDto createBooking(Long userId, BookingDto bookingDto) {
+ log.info("Пользователь с id {} создает бронь", userId);
+ User user = getUser(userId);
+ Item item = checkItem(bookingDto.getItemId());
+ if (!item.getAvailable()) {
+ throw new ErrorRequestException("Вещь с id " + bookingDto.getItemId() + " недоступна для бронирования");
+ }
+ bookingDto.setStatus(String.valueOf(BookingStatus.WAITING));
+ Booking bookingEntity = bookingMapper.mapBooking(bookingDto, user, item);
+ return bookingMapper.mapBookingResponseDto(repository.save(bookingEntity));
+ }
+
+ @Override
+ public BookingResponseDto approveBooking(Long userId, Long bookingId, Boolean approved) {
+ log.info("Пользователь с id {} изменяет статус брони {}", userId, bookingId);
+ Booking booking = checkBooking(bookingId);
+
+ if (booking.getStatus() != BookingStatus.WAITING) {
+ throw new ErrorRequestException("Статус бронирования уже изменен");
+ }
+
+ Item item = checkItem(booking.getItem().getId());
+ if (!item.getOwner().getId().equals(userId)) {
+ throw new ErrorRequestException("Пользователь с id " + userId + " не может редактировать статус этой вещи");
+ }
+
+ booking.setStatus(approved ? BookingStatus.APPROVED : BookingStatus.REJECTED);
+ return bookingMapper.mapBookingResponseDto(repository.save(booking));
+ }
+
+ @Override
+ public BookingResponseDto canceledBooking(Long userId, Long bookingId) {
+ log.info("Пользователь с id {} отменяет бронь {}", userId, bookingId);
+ Booking booking = checkBooking(bookingId);
+
+ if (!booking.getBooker().getId().equals(userId)) {
+ throw new ErrorRequestException("Пользователь с id " + userId + " не может отменить бронь, так как она не " +
+ "принадлежит ему");
+ }
+
+ booking.setStatus(BookingStatus.CANCELED);
+ return bookingMapper.mapBookingResponseDto(repository.save(booking));
+ }
+
+ @Override
+ @Transactional(readOnly = true)
+ public BookingResponseDto getBooking(Long userId, Long bookingId) {
+ Booking booking = checkBooking(bookingId);
+ Item item = checkItem(booking.getItem().getId());
+
+ if (!booking.getBooker().getId().equals(userId) && !item.getOwner().getId().equals(userId)) {
+ throw new ErrorRequestException("Пользователь с id " + userId + " не может просматривать информации о " +
+ "бронировании");
+ }
+
+ return bookingMapper.mapBookingResponseDto(booking);
+ }
+
+ @Override
+ @Transactional(readOnly = true)
+ public List getBookingByState(Long userId, String state) {
+ BookingState bookingState = checkState(state);
+ getUser(userId);
+ LocalDateTime currentTime = LocalDateTime.now();
+ List bookings = switch (bookingState) {
+ case WAITING -> repository.getBookingByStateStatus(userId, BookingStatus.WAITING);
+ case REJECTED -> repository.getBookingByStateStatus(userId, BookingStatus.REJECTED);
+ case CURRENT -> repository.getBookingByStateCurrent(userId, currentTime);
+ case PAST -> repository.getBookingByStatePast(userId, currentTime);
+ case FUTURE -> repository.getBookingByStateFuture(userId, currentTime);
+ default -> repository.getBookingByStateALL(userId);
+ };
+
+ return bookingMapper.mapListBookingResponseDto(bookings);
+ }
+
+ @Override
+ @Transactional(readOnly = true)
+ public List getBookingsAllItemsByState(String state, Long userId) {
+ BookingState bookingState = checkState(state);
+ getUser(userId);
+ LocalDateTime currentTime = LocalDateTime.now();
+ List bookings = switch (bookingState) {
+ case WAITING -> repository.getBookingAllItemsByStateStatus(userId, BookingStatus.WAITING);
+ case REJECTED -> repository.getBookingAllItemsByStateStatus(userId, BookingStatus.REJECTED);
+ case CURRENT -> repository.getBookingAllItemsByStateCurrent(userId, currentTime);
+ case PAST -> repository.getBookingAllItemsByStatePast(userId, currentTime);
+ case FUTURE -> repository.getBookingAllItemsByStateFuture(userId, currentTime);
+ default -> repository.getBookingAllItemsByStateALL(userId);
+ };
+
+ return bookingMapper.mapListBookingResponseDto(bookings);
+ }
+
+ private User getUser(Long userId) {
+ return userRepository.findById(userId)
+ .orElseThrow(() -> new NotFoundException("Не удалось найти пользователя с id:" + userId));
+ }
+
+ private Item checkItem(Long itemId) {
+ return itemRepository.findById(itemId)
+ .orElseThrow(() -> new NotFoundException("Не удалось найти вещь с id:" + itemId));
+ }
+
+ private Booking checkBooking(Long bookingId) {
+ return repository.findById(bookingId)
+ .orElseThrow(() -> new NotFoundException("Не удалось найти бронь с id:" + bookingId));
+ }
+
+ private BookingState checkState(String state) {
+ try {
+ return BookingState.valueOf(state.toUpperCase());
+ } catch (IllegalArgumentException e) {
+ throw new ErrorRequestException("Не существует состояния " + state);
+ }
+ }
+}
diff --git a/server/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java b/server/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java
new file mode 100644
index 00000000..6745151e
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java
@@ -0,0 +1,43 @@
+package ru.practicum.shareit.exception;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import java.util.Map;
+
+@RestControllerAdvice
+@Slf4j
+public class ErrorHandler {
+
+ @ExceptionHandler(NotFoundException.class)
+ @ResponseStatus(HttpStatus.NOT_FOUND)
+ public Map handleNotFoundException(final NotFoundException ex) {
+ log.error("Не найден параметр: {}", ex.getMessage());
+ return Map.of("notFound", ex.getMessage());
+ }
+
+ @ExceptionHandler(ErrorRequestException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public Map handlerValidation(final ErrorRequestException ex) {
+ log.error("Параметр не прошел проверку: {}", ex.getMessage());
+ return Map.of("error validation", ex.getMessage());
+ }
+
+ @ExceptionHandler(MethodArgumentNotValidException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public Map handleMethodArgumentNotValid(MethodArgumentNotValidException ex) {
+ log.error("Ошибка валидации: {}", ex.getMessage());
+ return Map.of("error", ex.getMessage());
+ }
+
+ @ExceptionHandler(Exception.class)
+ @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+ public Map handleAllExceptions(final Exception ex) {
+ log.error("Внутренняя ошибка сервера: {}", ex.getMessage(), ex);
+ return Map.of("Произошла непредвиденная ошибка", ex.getMessage());
+ }
+}
diff --git a/server/src/main/java/ru/practicum/shareit/exception/ErrorRequestException.java b/server/src/main/java/ru/practicum/shareit/exception/ErrorRequestException.java
new file mode 100644
index 00000000..58378452
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/exception/ErrorRequestException.java
@@ -0,0 +1,8 @@
+package ru.practicum.shareit.exception;
+
+public class ErrorRequestException extends RuntimeException {
+
+ public ErrorRequestException(String message) {
+ super(message);
+ }
+}
diff --git a/server/src/main/java/ru/practicum/shareit/exception/NotFoundException.java b/server/src/main/java/ru/practicum/shareit/exception/NotFoundException.java
new file mode 100644
index 00000000..71977f56
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/exception/NotFoundException.java
@@ -0,0 +1,8 @@
+package ru.practicum.shareit.exception;
+
+public class NotFoundException extends RuntimeException {
+
+ public NotFoundException(final String message) {
+ super(message);
+ }
+}
diff --git a/server/src/main/java/ru/practicum/shareit/item/Comment.java b/server/src/main/java/ru/practicum/shareit/item/Comment.java
new file mode 100644
index 00000000..9e0b1a8e
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/item/Comment.java
@@ -0,0 +1,35 @@
+package ru.practicum.shareit.item;
+
+import jakarta.persistence.*;
+import lombok.*;
+import ru.practicum.shareit.user.User;
+
+import java.time.LocalDateTime;
+
+@Getter
+@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity
+@Table(name = "comments")
+public class Comment {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(name = "text")
+ private String text;
+
+ @ManyToOne
+ @JoinColumn(name = "user_id", referencedColumnName = "id")
+ private User author;
+
+ @ManyToOne
+ @JoinColumn(name = "item_id", referencedColumnName = "id")
+ private Item item;
+
+ @Column(name = "created")
+ private LocalDateTime createdAt;
+}
diff --git a/server/src/main/java/ru/practicum/shareit/item/CommentMapper.java b/server/src/main/java/ru/practicum/shareit/item/CommentMapper.java
new file mode 100644
index 00000000..d76a9bc9
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/item/CommentMapper.java
@@ -0,0 +1,33 @@
+package ru.practicum.shareit.item;
+
+import org.mapstruct.Mapper;
+import ru.practicum.shareit.item.dto.CommentDto;
+import ru.practicum.shareit.user.User;
+import ru.practicum.shareit.user.UserMapper;
+
+import java.util.List;
+
+@Mapper(componentModel = "spring", uses = { UserMapper.class })
+public interface CommentMapper {
+
+ default Comment mapComment(CommentDto comment, User user, Item item) {
+ return Comment.builder()
+ .id(comment.getId())
+ .author(user)
+ .text(comment.getText())
+ .item(item)
+ .createdAt(comment.getCreated())
+ .build();
+ }
+
+ default CommentDto mapCommentDto(Comment comment) {
+ return CommentDto.builder()
+ .id(comment.getId())
+ .authorName(comment.getAuthor().getName())
+ .text(comment.getText())
+ .created(comment.getCreatedAt())
+ .build();
+ }
+
+ List mapListCommentDto(List comments);
+}
diff --git a/server/src/main/java/ru/practicum/shareit/item/CommentRepository.java b/server/src/main/java/ru/practicum/shareit/item/CommentRepository.java
new file mode 100644
index 00000000..826a5391
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/item/CommentRepository.java
@@ -0,0 +1,19 @@
+package ru.practicum.shareit.item;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+
+import java.util.Collection;
+import java.util.List;
+
+public interface CommentRepository extends JpaRepository {
+
+ @Query("SELECT c FROM Comment c " +
+ "JOIN FETCH c.author " +
+ "JOIN FETCH c.item " +
+ "WHERE c.item.id = :itemId")
+ List findCommentsByItemId(@Param("itemId") Long itemId);
+
+ List findCommentByItemIdIn(Collection itemIds);
+}
diff --git a/server/src/main/java/ru/practicum/shareit/item/Item.java b/server/src/main/java/ru/practicum/shareit/item/Item.java
new file mode 100644
index 00000000..4b042293
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/item/Item.java
@@ -0,0 +1,37 @@
+package ru.practicum.shareit.item;
+
+import jakarta.persistence.*;
+import lombok.*;
+import ru.practicum.shareit.request.ItemRequest;
+import ru.practicum.shareit.user.User;
+
+@Getter
+@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity
+@Table(name = "items", schema = "public")
+public class Item {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(name = "name", nullable = false)
+ private String name;
+
+ @Column(name = "description", nullable = false)
+ private String description;
+
+ @Column(name = "available")
+ private Boolean available;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "user_id")
+ private User owner;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "request_id")
+ private ItemRequest request;
+}
diff --git a/server/src/main/java/ru/practicum/shareit/item/ItemController.java b/server/src/main/java/ru/practicum/shareit/item/ItemController.java
new file mode 100644
index 00000000..e5c15e4b
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/item/ItemController.java
@@ -0,0 +1,82 @@
+package ru.practicum.shareit.item;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+import ru.practicum.shareit.item.dto.CommentDto;
+import ru.practicum.shareit.item.dto.ItemDto;
+import ru.practicum.shareit.item.dto.ItemWithCommentDto;
+import ru.practicum.shareit.item.service.CommentService;
+import ru.practicum.shareit.item.service.ItemService;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/items")
+@RequiredArgsConstructor
+public class ItemController {
+ private final ItemService itemService;
+ private final CommentService commentService;
+
+ /**
+ * Создание новой вещи.
+ * Post /items
+ * Headers X-Sharer-User-Id
+ */
+ @PostMapping
+ public ItemDto createItem(@RequestHeader("X-Sharer-User-Id") Long userId,
+ @RequestBody ItemDto item) {
+ return itemService.createItem(userId, item);
+ }
+
+ /**
+ * Возвращает вещь по её id.
+ * GET /items/{itemId}
+ */
+ @GetMapping("/{itemId}")
+ public ItemWithCommentDto getItem(@PathVariable Long itemId) {
+ return itemService.getItem(itemId);
+ }
+
+ /**
+ * Обновление данных о вещи.
+ * GET /items/{itemId}
+ * Headers X-Sharer-User-Id
+ */
+ @PatchMapping("/{itemId}")
+ public ItemDto updateItem(@RequestHeader("X-Sharer-User-Id") Long userId,
+ @PathVariable Long itemId,
+ @RequestBody ItemDto item) {
+ return itemService.updateItem(userId, itemId, item);
+ }
+
+ /**
+ * Получает список вещей пользователя.
+ * GET /items
+ * Headers X-Sharer-User-Id
+ */
+ @GetMapping
+ public List getUserItems(@RequestHeader("X-Sharer-User-Id") Long userId) {
+ return itemService.getUserItems(userId);
+ }
+
+ /**
+ * Получает список вещей, которые подходят по поиску.
+ * GET /items/search?text={text}
+ */
+ @GetMapping("/search")
+ public List searchItems(@RequestHeader("X-Sharer-User-Id") Long userId,
+ @RequestParam("text") String text) {
+ return itemService.searchItems(userId, text);
+ }
+
+ /**
+ * Создание комментария
+ * POST /items/itemId
+ */
+ @PostMapping("/{itemId}/comment")
+ public CommentDto createdComment(@RequestHeader("X-Sharer-User-Id") Long userId,
+ @PathVariable Long itemId,
+ @RequestBody CommentDto comment) {
+ return commentService.createdComment(userId, itemId, comment);
+ }
+}
diff --git a/server/src/main/java/ru/practicum/shareit/item/ItemMapper.java b/server/src/main/java/ru/practicum/shareit/item/ItemMapper.java
new file mode 100644
index 00000000..b75f9b5b
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/item/ItemMapper.java
@@ -0,0 +1,65 @@
+package ru.practicum.shareit.item;
+
+import org.mapstruct.Mapper;
+import ru.practicum.shareit.booking.dto.BookingDto;
+import ru.practicum.shareit.item.dto.CommentDto;
+import ru.practicum.shareit.item.dto.ItemDto;
+import ru.practicum.shareit.item.dto.ItemWithCommentDto;
+import ru.practicum.shareit.item.dto.ShortItemDto;
+import ru.practicum.shareit.request.ItemRequest;
+import ru.practicum.shareit.user.User;
+import ru.practicum.shareit.user.UserMapper;
+
+import java.util.List;
+
+@Mapper(componentModel = "spring", uses = {CommentMapper.class, UserMapper.class})
+public interface ItemMapper {
+
+ default Item mapItem(ItemDto itemDto, User user , ItemRequest request) {
+ return Item.builder()
+ .id(itemDto.getId())
+ .name(itemDto.getName())
+ .description(itemDto.getDescription())
+ .available(itemDto.getAvailable())
+ //.request(request)
+ .owner(user)
+ .request(request)
+ .build();
+ }
+
+ default ItemDto mapItemDto(Item item) {
+ return ItemDto.builder()
+ .id(item.getId())
+ .name(item.getName())
+ .description(item.getDescription())
+ .available(item.getAvailable())
+ .owner(item.getOwner() != null ? item.getOwner().getId() : null)
+ .requestId(item.getRequest() != null ? item.getRequest().getId() : null)
+ .build();
+ }
+
+ default ItemWithCommentDto toItemWithCommentDto(Item item, List comments,
+ BookingDto nextBooking, BookingDto lastBooking) {
+ return ItemWithCommentDto.builder()
+ .id(item.getId())
+ .name(item.getName())
+ .description(item.getDescription())
+ .comments(comments)
+ .available(item.getAvailable())
+ .owner(item.getOwner() != null ? item.getOwner().getId() : null)
+ .lastBooking(lastBooking)
+ .nextBooking(nextBooking)
+ .build();
+ }
+
+ default ShortItemDto toShortItemDto(Item item) {
+ return ShortItemDto.builder()
+ .id(item.getId())
+ .name(item.getName())
+ .description(item.getDescription())
+ .userId(item.getOwner() != null ? item.getOwner().getId() : null)
+ .build();
+ }
+
+ List toShortItemDtoList(List- items);
+}
diff --git a/server/src/main/java/ru/practicum/shareit/item/ItemRepository.java b/server/src/main/java/ru/practicum/shareit/item/ItemRepository.java
new file mode 100644
index 00000000..8dc3aede
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/item/ItemRepository.java
@@ -0,0 +1,24 @@
+package ru.practicum.shareit.item;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import ru.practicum.shareit.user.User;
+
+import java.util.List;
+
+
+public interface ItemRepository extends JpaRepository
- {
+
+ List
- findByOwner(User user);
+
+ List
- findByOwnerId(Long id);
+
+ List
- findByRequest_Id(Long id);
+
+ @Modifying
+ @Query("SELECT i FROM Item i JOIN FETCH i.owner WHERE i.available = true " +
+ "AND (UPPER(i.name) LIKE UPPER(CONCAT('%', :text, '%')) " +
+ "OR UPPER(i.description) LIKE UPPER(CONCAT('%', :text, '%')))")
+ List
- searchItems(String text);
+}
diff --git a/server/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java b/server/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java
new file mode 100644
index 00000000..8c9b13e1
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java
@@ -0,0 +1,15 @@
+package ru.practicum.shareit.item.dto;
+
+import lombok.Builder;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@Builder
+public class CommentDto {
+ private Long id;
+ private String text;
+ private String authorName;
+ private LocalDateTime created;
+}
diff --git a/server/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java b/server/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java
new file mode 100644
index 00000000..016b7a95
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java
@@ -0,0 +1,15 @@
+package ru.practicum.shareit.item.dto;
+
+import lombok.Builder;
+import lombok.Data;
+
+@Data
+@Builder
+public class ItemDto {
+ private Long id;
+ private String name;
+ private String description;
+ private Boolean available;
+ private Long owner;
+ private Long requestId;
+}
diff --git a/server/src/main/java/ru/practicum/shareit/item/dto/ItemWithCommentDto.java b/server/src/main/java/ru/practicum/shareit/item/dto/ItemWithCommentDto.java
new file mode 100644
index 00000000..c65892b4
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/item/dto/ItemWithCommentDto.java
@@ -0,0 +1,20 @@
+package ru.practicum.shareit.item.dto;
+
+import lombok.Builder;
+import lombok.Data;
+import ru.practicum.shareit.booking.dto.BookingDto;
+
+import java.util.List;
+
+@Data
+@Builder
+public class ItemWithCommentDto {
+ private Long id;
+ private String name;
+ private String description;
+ private Boolean available;
+ private Long owner;
+ private List comments;
+ private BookingDto lastBooking;
+ private BookingDto nextBooking;
+}
diff --git a/server/src/main/java/ru/practicum/shareit/item/dto/ShortItemDto.java b/server/src/main/java/ru/practicum/shareit/item/dto/ShortItemDto.java
new file mode 100644
index 00000000..32b48527
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/item/dto/ShortItemDto.java
@@ -0,0 +1,13 @@
+package ru.practicum.shareit.item.dto;
+
+import lombok.Builder;
+import lombok.Data;
+
+@Data
+@Builder
+public class ShortItemDto {
+ private Long id;
+ private Long userId;
+ private String name;
+ private String description;
+}
diff --git a/server/src/main/java/ru/practicum/shareit/item/service/CommentService.java b/server/src/main/java/ru/practicum/shareit/item/service/CommentService.java
new file mode 100644
index 00000000..99fd5d94
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/item/service/CommentService.java
@@ -0,0 +1,8 @@
+package ru.practicum.shareit.item.service;
+
+import ru.practicum.shareit.item.dto.CommentDto;
+
+public interface CommentService {
+
+ CommentDto createdComment(Long userId, Long itemId, CommentDto commentDto);
+}
diff --git a/server/src/main/java/ru/practicum/shareit/item/service/CommentServiceImpl.java b/server/src/main/java/ru/practicum/shareit/item/service/CommentServiceImpl.java
new file mode 100644
index 00000000..28784c76
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/item/service/CommentServiceImpl.java
@@ -0,0 +1,55 @@
+package ru.practicum.shareit.item.service;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import ru.practicum.shareit.booking.Booking;
+import ru.practicum.shareit.booking.BookingRepository;
+import ru.practicum.shareit.exception.ErrorRequestException;
+import ru.practicum.shareit.exception.NotFoundException;
+import ru.practicum.shareit.item.*;
+import ru.practicum.shareit.item.dto.CommentDto;
+import ru.practicum.shareit.user.User;
+import ru.practicum.shareit.user.UserRepository;
+
+import java.time.LocalDateTime;
+
+@RequiredArgsConstructor
+@Service
+@Slf4j
+@Transactional
+public class CommentServiceImpl implements CommentService {
+ private final CommentRepository repository;
+ private final UserRepository userRepository;
+ private final ItemRepository itemRepository;
+ private final BookingRepository bookingRepository;
+ private final CommentMapper commentMapper;
+
+ @Override
+ public CommentDto createdComment(Long userId, Long itemId, CommentDto commentDto) {
+ log.info("Создание комментария от пользователя {} на вещь {}", userId, itemId);
+ User user = checkUser(userId);
+ Item item = checkItem(itemId);
+ checkBooking(itemId, user.getId());
+ commentDto.setCreated(LocalDateTime.now());
+ Comment commentEntity = commentMapper.mapComment(commentDto, user, item);
+ return commentMapper.mapCommentDto(repository.save(commentEntity));
+ }
+
+ private User checkUser(Long userId) {
+ return userRepository.findById(userId)
+ .orElseThrow(() -> new NotFoundException("Не удалось найти пользователя с id:" + userId));
+ }
+
+ private Item checkItem(Long itemId) {
+ return itemRepository.findById(itemId)
+ .orElseThrow(() -> new NotFoundException("Не удалось найти вещь с id:" + itemId));
+ }
+
+ private Booking checkBooking(Long itemId, Long userId) {
+ LocalDateTime now = LocalDateTime.now();
+ return bookingRepository.getPostBooking(itemId, userId, now)
+ .orElseThrow(() -> new ErrorRequestException("Нельзя оставить комментарий, если не было брони"));
+ }
+}
diff --git a/server/src/main/java/ru/practicum/shareit/item/service/ItemService.java b/server/src/main/java/ru/practicum/shareit/item/service/ItemService.java
new file mode 100644
index 00000000..38499d09
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/item/service/ItemService.java
@@ -0,0 +1,18 @@
+package ru.practicum.shareit.item.service;
+
+import ru.practicum.shareit.item.dto.ItemDto;
+import ru.practicum.shareit.item.dto.ItemWithCommentDto;
+
+import java.util.List;
+
+public interface ItemService {
+ ItemDto createItem(Long userId, ItemDto item);
+
+ ItemDto updateItem(Long userId, Long itemId, ItemDto item);
+
+ ItemWithCommentDto getItem(Long itemId);
+
+ List getUserItems(Long userId);
+
+ List searchItems(Long userId, String text);
+}
diff --git a/server/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java b/server/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java
new file mode 100644
index 00000000..68c31831
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java
@@ -0,0 +1,152 @@
+package ru.practicum.shareit.item.service;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import ru.practicum.shareit.booking.Booking;
+import ru.practicum.shareit.booking.BookingMapper;
+import ru.practicum.shareit.booking.BookingRepository;
+import ru.practicum.shareit.exception.ErrorRequestException;
+import ru.practicum.shareit.exception.NotFoundException;
+import ru.practicum.shareit.item.*;
+import ru.practicum.shareit.item.dto.CommentDto;
+import ru.practicum.shareit.item.dto.ItemDto;
+import ru.practicum.shareit.item.dto.ItemWithCommentDto;
+import ru.practicum.shareit.request.ItemRequest;
+import ru.practicum.shareit.request.RequestRepository;
+import ru.practicum.shareit.user.User;
+import ru.practicum.shareit.user.UserRepository;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Service
+@RequiredArgsConstructor
+@Slf4j
+@Transactional
+public class ItemServiceImpl implements ItemService {
+ private final ItemRepository itemRepository;
+ private final UserRepository userRepository;
+ private final CommentRepository commentRepository;
+ private final BookingRepository bookingRepository;
+ private final RequestRepository requestRepository;
+
+ private final ItemMapper itemMapper;
+ private final CommentMapper commentMapper;
+ private final BookingMapper bookingMapper;
+
+ @Override
+ public ItemDto createItem(Long userId, ItemDto item) {
+ log.info("Пользователь с id = {} создает вещь {}", userId, item);
+ User user = getOwner(userId);
+ ItemRequest request = checkItemRequest(item.getRequestId());
+ Item itemEntity = itemMapper.mapItem(item, user, request);
+ return itemMapper.mapItemDto(itemRepository.save(itemEntity));
+ }
+
+ @Override
+ public ItemDto updateItem(Long userId, Long itemId, ItemDto item) {
+ log.info("Пользователь с id = {} обновляет вещь с id {}", userId, itemId);
+ User itemOwner = getOwner(userId);
+ Item oldItem = itemRepository.findById(itemId)
+ .orElseThrow(() -> new NotFoundException("Не удается найти вещь с id " + itemId));;
+ if (oldItem.getOwner() == null || !oldItem.getOwner().equals(itemOwner)) {
+ throw new ErrorRequestException("Пользователь с id = " + userId + "не может редактировать эту вещь");
+ }
+
+ if (item.getDescription() != null && !item.getDescription().equals(oldItem.getDescription())) {
+ oldItem.setDescription(item.getDescription());
+ }
+
+ if (item.getName() != null && !item.getName().equals(oldItem.getName())) {
+ oldItem.setName(item.getName());
+ }
+
+ if (item.getAvailable() != null && !item.getAvailable().equals(oldItem.getAvailable())) {
+ oldItem.setAvailable(item.getAvailable());
+ }
+
+ return itemMapper.mapItemDto(itemRepository.save(oldItem));
+ }
+
+
+ @Transactional(readOnly = true)
+ @Override
+ public ItemWithCommentDto getItem(Long itemId) {
+ LocalDateTime now = LocalDateTime.now().minusSeconds(2); //костыль для прохождения теста постмана
+ log.info("Запрос на получение вещи с id {}", itemId);
+ Item item = itemRepository.findById(itemId)
+ .orElseThrow(() -> new NotFoundException("Не удается найти вещь с id " + itemId));
+ List comment = commentMapper.mapListCommentDto(commentRepository.findCommentsByItemId(itemId));
+ log.error("Время: {}", now);
+ Booking lastBooking = bookingRepository.findLastBooking(itemId, now)
+ .orElse(null);
+ Booking nextBooking = bookingRepository.findNextBooking(itemId, now)
+ .orElse(null);
+
+ return itemMapper.toItemWithCommentDto(item, comment,
+ nextBooking == null ? null : bookingMapper.mapBookingDto(nextBooking),
+ lastBooking == null ? null : bookingMapper.mapBookingDto(lastBooking));
+ }
+
+ @Transactional(readOnly = true)
+ @Override
+ public List getUserItems(Long userId) {
+ log.info("Запрос на получение вещей пользователя с id {}", userId);
+ LocalDateTime now = LocalDateTime.now();
+ List itemsId = itemRepository.findByOwnerId(userId).stream()
+ .map(Item::getId)
+ .toList();
+
+ Map> commentsByItem = commentRepository.findCommentByItemIdIn(itemsId).stream()
+ .collect(Collectors.groupingBy(
+ comment -> comment.getItem().getId(),
+ Collectors.mapping(commentMapper::mapCommentDto, Collectors.toList())
+ ));
+
+ return itemRepository.findByOwner(getOwner(userId)).stream()
+ .map(item -> {
+ List comment = commentsByItem.getOrDefault(item.getId(), List.of());
+ Booking nextBooking = bookingRepository.findNextBooking(item.getId(), now)
+ .orElse(null);
+ Booking lastBooking = bookingRepository.findLastBooking(item.getId(), now)
+ .orElse(null);
+
+ return itemMapper
+ .toItemWithCommentDto(item, comment,
+ nextBooking == null ? null : bookingMapper.mapBookingDto(nextBooking),
+ lastBooking == null ? null : bookingMapper.mapBookingDto(lastBooking));
+ })
+ .toList();
+ }
+
+ @Transactional(readOnly = true)
+ @Override
+ public List searchItems(Long userId, String text) {
+ log.info("Пользователь с id {} ищет вещь по запросу text = {}", userId, text);
+ getOwner(userId); //выполняет проверку, существует ли такой пользователь или нет
+ if (text.isEmpty()) {
+ return new ArrayList();
+ }
+ return itemRepository.searchItems(text)
+ .stream().map(itemMapper::mapItemDto)
+ .toList();
+ }
+
+ private User getOwner(Long userId) {
+ return userRepository.findById(userId)
+ .orElseThrow(() -> new NotFoundException("Не удалось найти пользователя с id " + userId));
+ }
+
+ private ItemRequest checkItemRequest(Long requestId) {
+ if (requestId == null) {
+ return null;
+ }
+ return requestRepository.findById(requestId)
+ .orElseThrow(() -> new NotFoundException("Не удалось найти запрос с id " + requestId));
+ }
+}
diff --git a/server/src/main/java/ru/practicum/shareit/request/ItemRequest.java b/server/src/main/java/ru/practicum/shareit/request/ItemRequest.java
new file mode 100644
index 00000000..3324e304
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/request/ItemRequest.java
@@ -0,0 +1,33 @@
+package ru.practicum.shareit.request;
+
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import ru.practicum.shareit.user.User;
+
+import java.time.LocalDateTime;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity
+@Table(name = "requests")
+public class ItemRequest {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(name = "description")
+ private String description;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "user_id")
+ private User requester;
+
+ @Column(name = "created", nullable = false)
+ private LocalDateTime created;
+}
diff --git a/server/src/main/java/ru/practicum/shareit/request/ItemRequestController.java b/server/src/main/java/ru/practicum/shareit/request/ItemRequestController.java
new file mode 100644
index 00000000..cf155d02
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/request/ItemRequestController.java
@@ -0,0 +1,59 @@
+package ru.practicum.shareit.request;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+import ru.practicum.shareit.request.dto.FullRequestDto;
+import ru.practicum.shareit.request.dto.RequestAddDto;
+import ru.practicum.shareit.request.dto.RequestDto;
+import ru.practicum.shareit.request.service.RequestService;
+
+import java.util.List;
+
+@RestController
+@RequiredArgsConstructor
+@RequestMapping(path = "/requests")
+public class ItemRequestController {
+ private final RequestService requestService;
+
+ /**
+ * Создание нового запроса.
+ * Post /requests
+ * Headers X-Sharer-User-Id
+ */
+ @PostMapping
+ public RequestDto createRequest(@RequestBody RequestAddDto request,
+ @RequestHeader("X-Sharer-User-Id") Long userId) {
+ return requestService.createRequest(request, userId);
+ }
+
+ /**
+ * Получение информации о запросах пользователя и ответы на них
+ * Post /requests
+ * Headers X-Sharer-User-Id
+ */
+ @GetMapping
+ public List getRequestsByUser(@RequestHeader("X-Sharer-User-Id") Long userId) {
+ return requestService.getRequestByUser(userId);
+ }
+
+ /**
+ * Получение запросов, созданных другим пользователем
+ * GET /requests/all
+ * Headers X-Sharer-User-Id
+ */
+ @GetMapping("/all")
+ public List getRequests(@RequestHeader("X-Sharer-User-Id") Long userId) {
+ return requestService.getAllRequests(userId);
+ }
+
+ /**
+ * Получение информации о конкретном запросе и ответы на него
+ * GET /requests/{requestId}
+ * Headers X-Sharer-User-Id
+ */
+ @GetMapping("/{requestId}")
+ public FullRequestDto getRequestById(@PathVariable Long requestId,
+ @RequestHeader("X-Sharer-User-Id") Long userId) {
+ return requestService.getRequestById(requestId, userId);
+ }
+}
diff --git a/server/src/main/java/ru/practicum/shareit/request/RequestMapper.java b/server/src/main/java/ru/practicum/shareit/request/RequestMapper.java
new file mode 100644
index 00000000..7db80b27
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/request/RequestMapper.java
@@ -0,0 +1,44 @@
+package ru.practicum.shareit.request;
+
+import org.mapstruct.Mapper;
+import ru.practicum.shareit.item.ItemMapper;
+import ru.practicum.shareit.item.dto.ShortItemDto;
+import ru.practicum.shareit.request.dto.FullRequestDto;
+import ru.practicum.shareit.request.dto.RequestDto;
+import ru.practicum.shareit.user.User;
+
+import java.util.List;
+
+@Mapper(componentModel = "spring", uses = {ItemMapper.class, })
+public interface RequestMapper {
+
+ default RequestDto mapRequestDto(ItemRequest request) {
+ return RequestDto.builder()
+ .id(request.getId())
+ .created(request.getCreated())
+ .description(request.getDescription())
+ .userId(request.getRequester().getId())
+ .build();
+ }
+
+ List mapRequestDto(List items);
+
+ default ItemRequest mapItemRequest(RequestDto requestDto, User user) {
+ return ItemRequest.builder()
+ .id(requestDto.getId())
+ .requester(user)
+ .description(requestDto.getDescription())
+ .created(requestDto.getCreated())
+ .build();
+ }
+
+ default FullRequestDto mapFullRequestDto(ItemRequest request, List items) {
+ return FullRequestDto.builder()
+ .id(request.getId())
+ .created(request.getCreated())
+ .description(request.getDescription())
+ .userId(request.getRequester().getId())
+ .items(items)
+ .build();
+ }
+}
diff --git a/server/src/main/java/ru/practicum/shareit/request/RequestRepository.java b/server/src/main/java/ru/practicum/shareit/request/RequestRepository.java
new file mode 100644
index 00000000..2bc3827a
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/request/RequestRepository.java
@@ -0,0 +1,18 @@
+package ru.practicum.shareit.request;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+
+import java.util.List;
+
+public interface RequestRepository extends JpaRepository {
+
+ @Query("SELECT r FROM ItemRequest r " +
+ "JOIN FETCH r.requester " +
+ "WHERE r.requester.id != :userId " +
+ "ORDER BY r.created DESC")
+ List findByNotUserId(@Param("userId") Long userId);
+
+ List findByRequester_IdOrderByCreatedDesc(Long requesterId);
+}
diff --git a/server/src/main/java/ru/practicum/shareit/request/dto/FullRequestDto.java b/server/src/main/java/ru/practicum/shareit/request/dto/FullRequestDto.java
new file mode 100644
index 00000000..133e067d
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/request/dto/FullRequestDto.java
@@ -0,0 +1,18 @@
+package ru.practicum.shareit.request.dto;
+
+import lombok.Builder;
+import lombok.Data;
+import ru.practicum.shareit.item.dto.ShortItemDto;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Data
+@Builder
+public class FullRequestDto {
+ private Long id;
+ private Long userId;
+ private String description;
+ private LocalDateTime created;
+ private List items;
+}
diff --git a/server/src/main/java/ru/practicum/shareit/request/dto/RequestAddDto.java b/server/src/main/java/ru/practicum/shareit/request/dto/RequestAddDto.java
new file mode 100644
index 00000000..3820708b
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/request/dto/RequestAddDto.java
@@ -0,0 +1,14 @@
+package ru.practicum.shareit.request.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+public class RequestAddDto {
+ private String description;
+}
diff --git a/server/src/main/java/ru/practicum/shareit/request/dto/RequestDto.java b/server/src/main/java/ru/practicum/shareit/request/dto/RequestDto.java
new file mode 100644
index 00000000..e9c7db95
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/request/dto/RequestDto.java
@@ -0,0 +1,17 @@
+package ru.practicum.shareit.request.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@Builder
+@AllArgsConstructor
+public class RequestDto {
+ private Long id;
+ private LocalDateTime created;
+ private Long userId;
+ private String description;
+}
diff --git a/server/src/main/java/ru/practicum/shareit/request/service/RequestService.java b/server/src/main/java/ru/practicum/shareit/request/service/RequestService.java
new file mode 100644
index 00000000..62f4644d
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/request/service/RequestService.java
@@ -0,0 +1,18 @@
+package ru.practicum.shareit.request.service;
+
+import ru.practicum.shareit.request.dto.FullRequestDto;
+import ru.practicum.shareit.request.dto.RequestAddDto;
+import ru.practicum.shareit.request.dto.RequestDto;
+
+import java.util.List;
+
+public interface RequestService {
+
+ RequestDto createRequest(RequestAddDto requestAddDto, Long userId);
+
+ FullRequestDto getRequestById(Long requestId, Long userId);
+
+ List getRequestByUser(Long userId);
+
+ List getAllRequests(Long userId);
+}
diff --git a/server/src/main/java/ru/practicum/shareit/request/service/RequestServiceImpl.java b/server/src/main/java/ru/practicum/shareit/request/service/RequestServiceImpl.java
new file mode 100644
index 00000000..d6c72739
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/request/service/RequestServiceImpl.java
@@ -0,0 +1,88 @@
+package ru.practicum.shareit.request.service;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import ru.practicum.shareit.exception.NotFoundException;
+import ru.practicum.shareit.item.Item;
+import ru.practicum.shareit.item.ItemMapper;
+import ru.practicum.shareit.item.ItemRepository;
+import ru.practicum.shareit.item.dto.ShortItemDto;
+import ru.practicum.shareit.request.ItemRequest;
+import ru.practicum.shareit.request.RequestMapper;
+import ru.practicum.shareit.request.RequestRepository;
+import ru.practicum.shareit.request.dto.FullRequestDto;
+import ru.practicum.shareit.request.dto.RequestAddDto;
+import ru.practicum.shareit.request.dto.RequestDto;
+import ru.practicum.shareit.user.User;
+import ru.practicum.shareit.user.UserRepository;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RequiredArgsConstructor
+@Service
+@Slf4j
+public class RequestServiceImpl implements RequestService {
+ private final RequestRepository requestRepository;
+ private final UserRepository userRepository;
+
+ private final RequestMapper requestMapper;
+ private final ItemRepository itemRepository;
+ private final ItemMapper itemMapper;
+
+ @Override
+ public RequestDto createRequest(RequestAddDto requestAddDto, Long userId) {
+ log.info("Пользователь с id {} создает запрос", userId);
+ RequestDto newRequest = RequestDto.builder()
+ .description(requestAddDto.getDescription())
+ .userId(userId)
+ .created(LocalDateTime.now()).build();
+
+ User user = checkUser(userId);
+ ItemRequest entity = requestMapper.mapItemRequest(newRequest, user);
+ return requestMapper.mapRequestDto(requestRepository.save(entity));
+ }
+
+ @Override
+ public FullRequestDto getRequestById(Long requestId, Long userId) {
+ log.info("Пользователь с id {} запрашивает информацию о запросе {}", userId, requestId);
+ checkUser(userId);
+ return requestMapper.mapFullRequestDto(checkRequest(requestId), getItems(requestId));
+ }
+
+ @Override
+ public List getRequestByUser(Long userId) {
+ log.info("Запрос на получение списка запросов пользователя с id {}", userId);
+
+ List requests = requestRepository.findByRequester_IdOrderByCreatedDesc(userId);
+ return requests.stream()
+ .map(request -> requestMapper.mapFullRequestDto(request, getItems(request.getId())))
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public List getAllRequests(Long userId) {
+ log.info("Пользователь с id {} отправил запрос на получение списка запросов", userId);
+
+ User user = checkUser(userId);
+ List requests = requestRepository.findByNotUserId(user.getId());
+ return requestMapper.mapRequestDto(requests);
+ }
+
+ private User checkUser(Long userId) {
+ return userRepository.findById(userId)
+ .orElseThrow(() -> new NotFoundException("Не удалось найти пользователя с id " + userId));
+ }
+
+ private ItemRequest checkRequest(Long requestId) {
+ return requestRepository.findById(requestId)
+ .orElseThrow(() -> new NotFoundException("Не удалось найти запрос с id " + requestId));
+ }
+
+ private List getItems(Long requestId) {
+ List
- itemsEntity = itemRepository.findByRequest_Id(requestId);
+ return itemMapper.toShortItemDtoList(itemsEntity);
+ }
+}
diff --git a/server/src/main/java/ru/practicum/shareit/user/User.java b/server/src/main/java/ru/practicum/shareit/user/User.java
new file mode 100644
index 00000000..3e7e9970
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/user/User.java
@@ -0,0 +1,24 @@
+package ru.practicum.shareit.user;
+
+import jakarta.persistence.*;
+import lombok.*;
+
+@Getter
+@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity
+@Table(name = "users")
+public class User {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(name = "name", nullable = false)
+ private String name;
+
+ @Column(name = "email", nullable = false, unique = true)
+ private String email;
+}
diff --git a/server/src/main/java/ru/practicum/shareit/user/UserController.java b/server/src/main/java/ru/practicum/shareit/user/UserController.java
new file mode 100644
index 00000000..577332bd
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/user/UserController.java
@@ -0,0 +1,43 @@
+package ru.practicum.shareit.user;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import ru.practicum.shareit.user.service.UserService;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/users")
+@RequiredArgsConstructor
+@Validated
+public class UserController {
+ private final UserService userService;
+
+ @GetMapping
+ public List getAllUsers() {
+ return userService.getAllUsers();
+ }
+
+ @GetMapping("/{userId}")
+ public UserDto getUserById(@PathVariable(name = "userId") Long userId) {
+ return userService.getUserById(userId);
+ }
+
+ @PostMapping
+ public UserDto createUser(@RequestBody UserDto user) {
+ return userService.createUser(user);
+ }
+
+ @PatchMapping("/{userId}")
+ public UserDto updateUser(@RequestBody UserDto user,
+ @PathVariable(name = "userId") Long userId) {
+ user.setId(userId);
+ return userService.updateUser(user);
+ }
+
+ @DeleteMapping("/{userId}")
+ public void deleteUser(@PathVariable(name = "userId") Long userId) {
+ userService.deleteUser(userId);
+ }
+}
diff --git a/server/src/main/java/ru/practicum/shareit/user/UserDto.java b/server/src/main/java/ru/practicum/shareit/user/UserDto.java
new file mode 100644
index 00000000..2c149cec
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/user/UserDto.java
@@ -0,0 +1,15 @@
+package ru.practicum.shareit.user;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+
+@Data
+@Builder
+@AllArgsConstructor
+public class UserDto {
+ private Long id;
+ private String name;
+ private String email;
+}
diff --git a/server/src/main/java/ru/practicum/shareit/user/UserMapper.java b/server/src/main/java/ru/practicum/shareit/user/UserMapper.java
new file mode 100644
index 00000000..a902c788
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/user/UserMapper.java
@@ -0,0 +1,11 @@
+package ru.practicum.shareit.user;
+
+import org.mapstruct.Mapper;
+
+@Mapper(componentModel = "spring")
+public interface UserMapper {
+
+ User mapUser(UserDto userDto);
+
+ UserDto mapUserDto(User user);
+}
diff --git a/server/src/main/java/ru/practicum/shareit/user/UserRepository.java b/server/src/main/java/ru/practicum/shareit/user/UserRepository.java
new file mode 100644
index 00000000..77bcf808
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/user/UserRepository.java
@@ -0,0 +1,7 @@
+package ru.practicum.shareit.user;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface UserRepository extends JpaRepository {
+
+}
diff --git a/server/src/main/java/ru/practicum/shareit/user/service/UserService.java b/server/src/main/java/ru/practicum/shareit/user/service/UserService.java
new file mode 100644
index 00000000..0a18c9b6
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/user/service/UserService.java
@@ -0,0 +1,18 @@
+package ru.practicum.shareit.user.service;
+
+import ru.practicum.shareit.user.UserDto;
+
+import java.util.List;
+
+public interface UserService {
+
+ List getAllUsers();
+
+ UserDto getUserById(Long id);
+
+ UserDto createUser(UserDto user);
+
+ UserDto updateUser(UserDto user);
+
+ void deleteUser(Long id);
+}
diff --git a/server/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java b/server/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java
new file mode 100644
index 00000000..4ce7b4e8
--- /dev/null
+++ b/server/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java
@@ -0,0 +1,74 @@
+package ru.practicum.shareit.user.service;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import ru.practicum.shareit.exception.NotFoundException;
+import ru.practicum.shareit.user.User;
+import ru.practicum.shareit.user.UserDto;
+import ru.practicum.shareit.user.UserMapper;
+import ru.practicum.shareit.user.UserRepository;
+
+import java.util.List;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+@Transactional
+public class UserServiceImpl implements UserService {
+ private final UserRepository repository;
+ private final UserMapper mapper;
+
+ @Transactional(readOnly = true)
+ @Override
+ public List getAllUsers() {
+ log.info("Запрос на получение информации о пользователях");
+ return repository.findAll()
+ .stream().map(mapper::mapUserDto)
+ .toList();
+ }
+
+ @Transactional(readOnly = true)
+ @Override
+ public UserDto getUserById(Long id) {
+ log.info("Запрос на получение информации о пользователе id:" + id);
+ User user = repository.findById(id)
+ .orElseThrow(() -> new NotFoundException("Не удалось нйти пользователя с id:" + id));
+ return mapper.mapUserDto(user);
+ }
+
+ @Override
+ public UserDto createUser(UserDto user) {
+ log.info("Создание пользователя");
+ User userEntity = mapper.mapUser(user);
+ return mapper.mapUserDto(repository.save(userEntity));
+ }
+
+ @Override
+ public UserDto updateUser(UserDto user) {
+ log.info("Обновление информации о пользователе " + user.getId());
+ User oldUser = repository.findById(user.getId())
+ .orElseThrow(() -> new NotFoundException("Не удалось нйти пользователя с id:" + user.getId()));
+
+ if (user.getEmail() != null && !oldUser.getEmail().equals(user.getEmail())) {
+ log.trace("Изменение email пользователя");
+ oldUser.setEmail(user.getEmail());
+ }
+
+ if (user.getName() != null && !oldUser.getName().equals(user.getName())) {
+ log.trace("Изменение имя пользователя");
+ oldUser.setName(user.getName());
+ }
+
+ return mapper.mapUserDto(repository.save(oldUser));
+ }
+
+ @Override
+ public void deleteUser(Long id) {
+ log.info("Удаление пользователя " + id);
+ User user = repository.findById(id)
+ .orElseThrow(() -> new NotFoundException("Не удалось нйти пользователя с id:" + id));
+ repository.delete(user);
+ }
+}
diff --git a/server/src/main/resources/schema.sql b/server/src/main/resources/schema.sql
new file mode 100644
index 00000000..8fc391cf
--- /dev/null
+++ b/server/src/main/resources/schema.sql
@@ -0,0 +1,71 @@
+-- ==========================================
+-- 1. Информация о пользователях
+-- ==========================================
+CREATE TABLE IF NOT EXISTS users
+(
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
+ name VARCHAR(255) NOT NULL,
+ email VARCHAR(512) NOT NULL,
+ CONSTRAINT pk_users PRIMARY KEY (id),
+ CONSTRAINT unique_email UNIQUE (email)
+);
+
+-- ==========================================
+-- 2. Информация о запросах вещи
+-- ==========================================
+CREATE TABLE IF NOT EXISTS requests
+(
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
+ user_id BIGINT NOT NULL,
+ description VARCHAR(512) NOT NULL,
+ created TIMESTAMP WITHOUT TIME ZONE NOT NULL,
+ CONSTRAINT pk_requests PRIMARY KEY (id),
+ CONSTRAINT fk_requests_users FOREIGN KEY (user_id) REFERENCES users (id)
+);
+
+-- ==========================================
+-- 3. Информация о вещах
+-- ==========================================
+CREATE TABLE IF NOT EXISTS items
+(
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
+ user_id BIGINT NOT NULL,
+ name VARCHAR(255) NOT NULL,
+ description VARCHAR(512) NOT NULL,
+ available BOOLEAN NOT NULL,
+ request_id BIGINT,
+ CONSTRAINT pk_items PRIMARY KEY (id),
+ CONSTRAINT fk_items_users FOREIGN KEY (user_id) REFERENCES users (id),
+ CONSTRAINT fk_items_requests FOREIGN KEY (request_id) REFERENCES requests (id)
+);
+
+-- ==========================================
+-- 4. Комментарии
+-- ==========================================
+CREATE TABLE IF NOT EXISTS comments
+(
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
+ text VARCHAR(512) NOT NULL,
+ user_id BIGINT NOT NULL,
+ item_id BIGINT NOT NULL,
+ created TIMESTAMP WITHOUT TIME ZONE NOT NULL,
+ CONSTRAINT pk_comment PRIMARY KEY (id),
+ CONSTRAINT fk_bookings_users FOREIGN KEY (user_id) REFERENCES users (id),
+ CONSTRAINT fk_bookings_items FOREIGN KEY (item_id) REFERENCES items (id)
+);
+
+-- ==========================================
+-- 5. Информация о бронировании
+-- ==========================================
+CREATE TABLE IF NOT EXISTS bookings
+(
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
+ start_time TIMESTAMP WITHOUT TIME ZONE NOT NULL,
+ end_time TIMESTAMP WITHOUT TIME ZONE NOT NULL,
+ booker BIGINT NOT NULL,
+ item_id BIGINT NOT NULL,
+ status VARCHAR(15) NOT NULL,
+ CONSTRAINT pk_bookings PRIMARY KEY (id),
+ CONSTRAINT fk_bookings_users FOREIGN KEY (booker) REFERENCES users (id),
+ CONSTRAINT fk_bookings_items FOREIGN KEY (item_id) REFERENCES items (id)
+);
\ No newline at end of file
diff --git a/server/src/test/java/shareit/booking/BookingControllerTest.java b/server/src/test/java/shareit/booking/BookingControllerTest.java
new file mode 100644
index 00000000..bcfa9d7b
--- /dev/null
+++ b/server/src/test/java/shareit/booking/BookingControllerTest.java
@@ -0,0 +1,170 @@
+package shareit.booking;
+
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import ru.practicum.shareit.ShareItServer;
+import ru.practicum.shareit.booking.Booking;
+import ru.practicum.shareit.booking.dto.BookingDto;
+import ru.practicum.shareit.booking.dto.BookingResponseDto;
+import ru.practicum.shareit.booking.enums.BookingStatus;
+import ru.practicum.shareit.booking.service.BookingService;
+import ru.practicum.shareit.item.Item;
+import ru.practicum.shareit.item.dto.ItemDto;
+import ru.practicum.shareit.user.User;
+import ru.practicum.shareit.user.UserDto;
+
+import java.nio.charset.StandardCharsets;
+import java.time.LocalDateTime;
+import java.util.List;
+
+import static org.hamcrest.Matchers.is;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+@SpringBootTest(classes = ShareItServer.class)
+@AutoConfigureMockMvc
+public class BookingControllerTest {
+
+ @Autowired
+ private MockMvc mvc;
+
+ @Autowired
+ private ObjectMapper mapper;
+
+ @MockBean
+ private BookingService bookingService;
+
+ private final User user1 = User.builder()
+ .id(1L)
+ .name("user1")
+ .email("email1@ya.ru")
+ .build();
+
+ private final UserDto userDto = UserDto.builder()
+ .id(1L)
+ .name("user1")
+ .email("email1@ya.ru")
+ .build();
+
+ private final Item item1 = Item.builder()
+ .id(1L)
+ .owner(user1)
+ .name("item1")
+ .description("desc1")
+ .available(true)
+ .build();
+
+ private final ItemDto itemDto = ItemDto.builder()
+ .id(1L)
+ .owner(user1.getId())
+ .name("item1")
+ .description("desc1")
+ .available(true)
+ .build();
+
+ private final BookingDto bookingDto = BookingDto.builder()
+ .id(1L)
+ .booker(user1.getId())
+ .itemId(item1.getId())
+ .start(LocalDateTime.now().minusDays(1))
+ .end(LocalDateTime.now())
+ .build();
+
+ private final BookingResponseDto responseDto = BookingResponseDto.builder()
+ .id(1L)
+ .item(itemDto)
+ .booker(userDto)
+ .start(LocalDateTime.now().minusDays(1))
+ .end(LocalDateTime.now())
+ .status(BookingStatus.WAITING.toString())
+ .build();
+
+ @Test
+ void createBooking() throws Exception {
+ when(bookingService.createBooking(eq(1L), any(BookingDto.class))).thenReturn(responseDto);
+
+ mvc.perform(post("/bookings")
+ .content(mapper.writeValueAsString(bookingDto))
+ .header("X-Sharer-User-Id", 1L)
+ .characterEncoding(StandardCharsets.UTF_8)
+ .contentType(MediaType.APPLICATION_JSON)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.id", is(1)))
+ .andExpect(jsonPath("$.status", is("WAITING")));
+
+ verify(bookingService).createBooking(eq(1L), any(BookingDto.class));
+ }
+
+ @Test
+ void approveBooking() throws Exception {
+ when(bookingService.approveBooking(1L, 1L, true)).thenReturn(responseDto);
+
+ mvc.perform(patch("/bookings/1")
+ .param("approved", "true")
+ .header("X-Sharer-User-Id", 1L))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.id", is(1)))
+ .andExpect(jsonPath("$.status", is("WAITING")));
+
+ verify(bookingService).approveBooking(1L, 1L, true);
+ }
+
+ @Test
+ void canceledBooking() throws Exception {
+ when(bookingService.canceledBooking(1L, 1L)).thenReturn(responseDto);
+
+ mvc.perform(patch("/bookings/1/canceled")
+ .header("X-Sharer-User-Id", 1L))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.id", is(1)));
+
+ verify(bookingService).canceledBooking(1L, 1L);
+ }
+
+ @Test
+ void getBookingById() throws Exception {
+ when(bookingService.getBooking(1L, 1L)).thenReturn(responseDto);
+
+ mvc.perform(get("/bookings/1")
+ .header("X-Sharer-User-Id", 1L))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.id", is(1)));
+
+ verify(bookingService).getBooking(1L, 1L);
+ }
+
+ @Test
+ void getBookingsByState() throws Exception {
+ when(bookingService.getBookingByState(1L, "ALL")).thenReturn(List.of(responseDto));
+
+ mvc.perform(get("/bookings")
+ .param("state", "ALL")
+ .header("X-Sharer-User-Id", 1L))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$[0].id", is(1)));
+
+ verify(bookingService).getBookingByState(1L, "ALL");
+ }
+
+ @Test
+ void getBookingsAllItemsByState() throws Exception {
+ when(bookingService.getBookingsAllItemsByState("ALL", 1L)).thenReturn(List.of(responseDto));
+
+ mvc.perform(get("/bookings/owner")
+ .param("state", "ALL")
+ .header("X-Sharer-User-Id", 1L))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$[0].id", is(1)));
+
+ verify(bookingService).getBookingsAllItemsByState("ALL", 1L);
+ }
+}
diff --git a/server/src/test/java/shareit/booking/BookingRepositoryTest.java b/server/src/test/java/shareit/booking/BookingRepositoryTest.java
new file mode 100644
index 00000000..cb450d6f
--- /dev/null
+++ b/server/src/test/java/shareit/booking/BookingRepositoryTest.java
@@ -0,0 +1,207 @@
+package shareit.booking;
+
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import ru.practicum.shareit.ShareItServer;
+import ru.practicum.shareit.booking.Booking;
+import ru.practicum.shareit.booking.BookingRepository;
+import ru.practicum.shareit.booking.enums.BookingStatus;
+import ru.practicum.shareit.item.Item;
+import ru.practicum.shareit.item.ItemRepository;
+import ru.practicum.shareit.user.User;
+import ru.practicum.shareit.user.UserRepository;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@Slf4j
+@DataJpaTest
+@ActiveProfiles("test")
+@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
+@ContextConfiguration(classes = ShareItServer.class)
+public class BookingRepositoryTest {
+
+ @Autowired
+ private ItemRepository repository;
+
+ @Autowired
+ private UserRepository userRepository;
+
+ @Autowired
+ private BookingRepository bookingRepository;
+
+ private final LocalDateTime testTime = LocalDateTime.of(2025, 07, 25, 00, 00, 00);
+
+ private final User user1 = User.builder()
+ .id(1L)
+ .name("user1")
+ .email("email1@ya.ru")
+ .build();
+
+ private final User user2 = User.builder()
+ .id(2L)
+ .name("user2")
+ .email("email2@ya.ru")
+ .build();
+
+ private final Item item1 = Item.builder()
+ .id(1L)
+ .name("item1")
+ .description("description1")
+ .available(true)
+ .owner(user1)
+ .build();
+
+ private final Item item2 = Item.builder()
+ .id(2L)
+ .name("item2")
+ .description("description2")
+ .available(true)
+ .owner(user2)
+ .build();
+
+ private final Item item3 = Item.builder()
+ .id(3L)
+ .name("name3")
+ .description("description3")
+ .available(false)
+ .owner(user1)
+ .build();
+
+ private final Booking bookingPast = Booking.builder()
+ .start(testTime.minusYears(10))
+ .end(testTime.minusYears(9))
+ .item(item1)
+ .booker(user1)
+ .status(BookingStatus.APPROVED)
+ .build();
+
+ private final Booking bookingCurrent = Booking.builder()
+ .start(testTime.minusYears(5))
+ .end(testTime.plusYears(5))
+ .item(item1)
+ .booker(user2)
+ .status(BookingStatus.APPROVED)
+ .build();
+
+ private final Booking bookingFuture = Booking.builder()
+ .start(testTime.plusYears(8))
+ .end(testTime.plusYears(9))
+ .item(item1)
+ .booker(user2)
+ .status(BookingStatus.APPROVED)
+ .build();
+
+ private final Booking bookingRejected = Booking.builder()
+ .start(testTime.plusYears(9))
+ .end(testTime.plusYears(10))
+ .item(item1)
+ .booker(user1)
+ .status(BookingStatus.REJECTED)
+ .build();
+
+ private void checkBooking(Booking booking1, Booking booking2) {
+ assertEquals(booking1.getId(), booking2.getId());
+ assertEquals(booking1.getStart(), booking2.getStart());
+ assertEquals(booking1.getEnd(), booking2.getEnd());
+ assertEquals(booking1.getStatus(), booking2.getStatus());
+
+ assertEquals(booking1.getBooker().getId(), booking2.getBooker().getId());
+ assertEquals(booking1.getBooker().getName(), booking2.getBooker().getName());
+
+ assertEquals(booking1.getItem().getId(), booking2.getItem().getId());
+ assertEquals(booking1.getItem().getName(), booking2.getItem().getName());
+ }
+
+ @BeforeEach
+ public void init() {
+ userRepository.save(user1);
+ userRepository.save(user2);
+ repository.save(item1);
+ repository.save(item2);
+ repository.save(item3);
+ bookingRepository.save(bookingCurrent);
+ bookingRepository.save(bookingFuture);
+ bookingRepository.save(bookingRejected);
+ bookingRepository.save(bookingPast);
+ }
+
+ @Test
+ public void getBookingByStateAll() {
+ List bookingsUser = bookingRepository.getBookingByStateALL(user2.getId());
+
+ log.info("Всего бронирований у user2: {}", bookingsUser.size());
+ bookingsUser.forEach(b -> log.info("Booking id = {}, booker = {}", b.getId(), b.getBooker().getName()));
+
+ assertEquals(2, bookingsUser.size(), "Количество бронирований пользователя не совпадает");
+ checkBooking(bookingsUser.get(0), bookingFuture);
+ checkBooking(bookingsUser.get(1), bookingCurrent);
+ }
+
+ @Test
+ public void getBookingByStateStatus() {
+ List bookings = bookingRepository.getBookingByStateStatus(user2.getId(), BookingStatus.APPROVED);
+
+ bookings.stream().forEach(booking -> log.info(booking.getId().toString()));
+ assertEquals(2, bookings.size(), "Количество бронирований пользователя не совпадает");
+ checkBooking(bookings.get(0), bookingFuture);
+ checkBooking(bookings.get(1), bookingCurrent);
+ }
+
+ @Test
+ public void getBookingByStateCurrent() {
+ List bookings = bookingRepository.getBookingByStateCurrent(user2.getId(), testTime);
+
+ bookings.forEach(b -> log.info("Booking id = {}, booker = {}", b.getId(), b.getBooker().getName()));
+
+ assertEquals(1, bookings.size(), "Количество бронирований пользователя не совпадает");
+ checkBooking(bookings.get(0), bookingCurrent);
+ }
+
+ @Test
+ public void getBookingByStateFuture() {
+ List bookings = bookingRepository.getBookingByStateFuture(user2.getId(), testTime);
+
+ assertEquals(1, bookings.size(), "Количество бронирований пользователя не совпадает");
+ checkBooking(bookings.get(0), bookingFuture);
+ }
+
+ @Test
+ public void getBookingByStatePast() {
+ List bookings = bookingRepository.getBookingByStatePast(user1.getId(), testTime);
+
+ assertEquals(1, bookings.size(), "Количество бронирований пользователя не совпадает");
+ checkBooking(bookings.get(0), bookingPast);
+ }
+
+ @Test
+ public void getBookingAllItemsByStateAll() {
+ List bookings = bookingRepository.getBookingAllItemsByStateALL(item1.getId());
+
+ bookings.stream().forEach(booking -> log.info(booking.getId().toString()));
+ assertEquals(4, bookings.size(), "Количество бронирований пользователя не совпадает");
+ checkBooking(bookings.get(0), bookingRejected);
+ checkBooking(bookings.get(1), bookingFuture);
+ checkBooking(bookings.get(2), bookingCurrent);
+ checkBooking(bookings.get(3), bookingPast);
+ }
+
+ @Test
+ public void getBookingAllItemsByStateStatus() {
+ List bookings = bookingRepository.getBookingAllItemsByStateStatus(item1.getId(), BookingStatus.APPROVED);
+
+ bookings.stream().forEach(booking -> log.info(booking.getId().toString()));
+ assertEquals(3, bookings.size(), "Количество бронирований пользователя не совпадает");
+ checkBooking(bookings.get(0), bookingFuture);
+ checkBooking(bookings.get(1), bookingCurrent);
+ checkBooking(bookings.get(2), bookingPast);
+ }
+}
diff --git a/server/src/test/java/shareit/booking/BookingServiceImplTest.java b/server/src/test/java/shareit/booking/BookingServiceImplTest.java
new file mode 100644
index 00000000..b0ab8d5e
--- /dev/null
+++ b/server/src/test/java/shareit/booking/BookingServiceImplTest.java
@@ -0,0 +1,121 @@
+package shareit.booking;
+
+import org.junit.jupiter.api.Test;
+import org.mapstruct.factory.Mappers;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.ComponentScan;
+import ru.practicum.shareit.booking.Booking;
+import ru.practicum.shareit.booking.BookingMapper;
+import ru.practicum.shareit.booking.BookingRepository;
+import ru.practicum.shareit.booking.dto.BookingDto;
+import ru.practicum.shareit.booking.dto.BookingResponseDto;
+import ru.practicum.shareit.booking.enums.BookingStatus;
+import ru.practicum.shareit.booking.service.BookingService;
+import ru.practicum.shareit.booking.service.BookingServiceImpl;
+import ru.practicum.shareit.item.*;
+import ru.practicum.shareit.item.dto.ItemDto;
+import ru.practicum.shareit.user.User;
+import ru.practicum.shareit.user.UserDto;
+import ru.practicum.shareit.user.UserRepository;
+
+import java.time.LocalDateTime;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.*;
+
+@SpringBootTest
+public class BookingServiceImplTest {
+ /*
+
+ @Autowired
+ private BookingServiceImpl service;
+
+ @MockBean
+ private ItemRepository itemRepository;
+
+ @MockBean
+ private UserRepository userRepository;
+
+ @MockBean
+ private BookingRepository bookingRepository;
+
+ private final BookingMapper bookingMapper = Mappers.getMapper(BookingMapper.class);
+ private final ItemMapper itemMapper = Mappers.getMapper(ItemMapper.class);
+
+ private final User user1 = User.builder()
+ .id(1L)
+ .name("user1")
+ .email("email1@ya.ru")
+ .build();
+
+ private final UserDto userDto = UserDto.builder()
+ .id(1L)
+ .name("user1")
+ .email("email1@ya.ru")
+ .build();
+
+ private final Item item1 = Item.builder()
+ .id(1L)
+ .owner(user1)
+ .name("item1")
+ .description("desc1")
+ .available(true)
+ .build();
+
+ private final ItemDto itemDto = ItemDto.builder()
+ .id(1L)
+ .owner(user1.getId())
+ .name("item1")
+ .description("desc1")
+ .available(true)
+ .build();
+
+ private final Booking booking1 = Booking.builder()
+ .id(1L)
+ .item(item1)
+ .booker(user1)
+ .start(LocalDateTime.now().minusDays(1))
+ .end(LocalDateTime.now())
+ .status(BookingStatus.APPROVED)
+ .build();
+
+ private final BookingDto bookingDto = BookingDto.builder()
+ .id(1L)
+ .booker(user1.getId())
+ .itemId(item1.getId())
+ .start(LocalDateTime.now().minusDays(1))
+ .end(LocalDateTime.now())
+ .status(BookingStatus.APPROVED.toString())
+ .build();
+
+ private final BookingResponseDto responseDto = BookingResponseDto.builder()
+ .id(1L)
+ .item(itemDto)
+ .booker(userDto)
+ .start(LocalDateTime.now().minusDays(1))
+ .end(LocalDateTime.now())
+ .status(BookingStatus.APPROVED.toString())
+ .build();
+
+ @Test
+ public void createBooking() {
+ when(userRepository.findById(user1.getId())).thenReturn(Optional.of(user1));
+ when(itemRepository.findById(item1.getId())).thenReturn(Optional.of(item1));
+ when(bookingRepository.save(any(Booking.class))).thenReturn(booking1);
+
+ BookingDto dto = bookingMapper.mapBookingDto(booking1);
+ BookingResponseDto result = service.createBooking(user1.getId(), dto);
+
+ verify(bookingRepository, times(1)).save(any());
+
+ assertEquals(dto.getId(), result.getId());
+ assertEquals(dto.getBooker(), result.getBooker().getId());
+ assertEquals(dto.getStart(), result.getStart());
+ assertEquals(dto.getEnd(), result.getEnd());
+ }
+
+ */
+}
\ No newline at end of file
diff --git a/server/src/test/java/shareit/item/CommentDtoTest.java b/server/src/test/java/shareit/item/CommentDtoTest.java
new file mode 100644
index 00000000..67bda65a
--- /dev/null
+++ b/server/src/test/java/shareit/item/CommentDtoTest.java
@@ -0,0 +1,44 @@
+package shareit.item;
+
+import lombok.RequiredArgsConstructor;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.json.JsonTest;
+import org.springframework.boot.test.json.JacksonTester;
+import org.springframework.boot.test.json.JsonContent;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ContextConfiguration;
+import ru.practicum.shareit.ShareItServer;
+import ru.practicum.shareit.item.dto.CommentDto;
+
+import java.io.IOException;
+import java.time.LocalDateTime;
+
+import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
+
+@JsonTest
+@RequiredArgsConstructor(onConstructor_ = @Autowired)
+@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
+@ContextConfiguration(classes = ShareItServer.class)
+public class CommentDtoTest {
+ private final LocalDateTime testTime = LocalDateTime.of(2025, 07, 25, 00, 00, 00);
+ private final JacksonTester json;
+
+ @Test
+ void testCommentDto() throws IOException {
+ CommentDto dto = CommentDto.builder()
+ .id(1L)
+ .text("text")
+ .authorName("authorName")
+ .created(testTime)
+ .build();
+
+ JsonContent result = json.write(dto);
+
+ assertThat(result).extractingJsonPathNumberValue("$.id").isEqualTo(1);
+ assertThat(result).extractingJsonPathStringValue("$.text").isEqualTo("text");
+ assertThat(result).extractingJsonPathStringValue("$.authorName").isEqualTo("authorName");
+ assertThat(result).extractingJsonPathStringValue("$.created")
+ .isEqualTo("2025-07-25T00:00:00");
+ }
+}
diff --git a/server/src/test/java/shareit/item/CommentServiceTest.java b/server/src/test/java/shareit/item/CommentServiceTest.java
new file mode 100644
index 00000000..8eec2861
--- /dev/null
+++ b/server/src/test/java/shareit/item/CommentServiceTest.java
@@ -0,0 +1,116 @@
+package shareit.item;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mapstruct.factory.Mappers;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.jupiter.MockitoExtension;
+import ru.practicum.shareit.booking.Booking;
+import ru.practicum.shareit.booking.BookingRepository;
+import ru.practicum.shareit.exception.NotFoundException;
+import ru.practicum.shareit.item.*;
+import ru.practicum.shareit.item.dto.CommentDto;
+import ru.practicum.shareit.item.service.CommentServiceImpl;
+import ru.practicum.shareit.user.User;
+import ru.practicum.shareit.user.UserRepository;
+
+import java.time.LocalDateTime;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.never;
+
+@ExtendWith(MockitoExtension.class)
+public class CommentServiceTest {
+ private final LocalDateTime testTime = LocalDateTime.of(2025, 07, 25, 00, 00, 00);
+
+ @Mock
+ private ItemRepository repository;
+
+ @Mock
+ private UserRepository userRepository;
+
+ @Mock
+ private CommentRepository commentRepository;
+
+ @Mock
+ private BookingRepository bookingRepository;
+
+ @Spy
+ private final CommentMapper commentMapper = Mappers.getMapper(CommentMapper.class);
+
+ @InjectMocks
+ private CommentServiceImpl commentService;
+
+ private final User user1 = User.builder()
+ .id(1L)
+ .name("user1")
+ .email("email1@ya.ru")
+ .build();
+
+ private final Item item1 = Item.builder()
+ .id(1L)
+ .owner(user1)
+ .name("item1")
+ .description("desc1")
+ .available(true)
+ .build();
+
+ private final Comment comment1 = Comment.builder()
+ .id(1L)
+ .item(item1)
+ .text("text1")
+ .createdAt(testTime)
+ .author(user1)
+ .build();
+
+ private final Booking booking1 = Booking.builder()
+ .id(1L)
+ .item(item1)
+ .booker(user1)
+ .start(testTime.minusDays(2))
+ .end(testTime.minusDays(1))
+ .build();
+
+ @Nested
+ class CommentTest {
+ @Test
+ public void createComment() {
+ when(userRepository.findById(user1.getId())).thenReturn(Optional.of(user1));
+ when(repository.findById(item1.getId())).thenReturn(Optional.of(item1));
+ when(bookingRepository.getPostBooking(eq(item1.getId()), eq(user1.getId()),
+ any(LocalDateTime.class))).thenReturn(Optional.of(booking1));
+ when(commentRepository.save(any())).thenReturn(comment1);
+
+ CommentDto dto = commentMapper.mapCommentDto(comment1);
+ CommentDto result = commentService.createdComment(user1.getId(), item1.getId(), dto);
+
+ verify(commentRepository, times(1)).save(any());
+
+ assertEquals(comment1.getId(), result.getId());
+ assertEquals(comment1.getCreatedAt(), result.getCreated());
+ assertEquals(comment1.getText(), result.getText());
+ assertEquals(comment1.getAuthor().getName(), result.getAuthorName());
+ }
+
+ @Test
+ public void createdCommentWithFailUser() {
+ Long userId = 99L;
+ when(userRepository.findById(userId)).thenReturn(Optional.empty());
+
+ CommentDto dto = commentMapper.mapCommentDto(comment1);
+
+ NotFoundException ex = assertThrows(NotFoundException.class,
+ () -> commentService.createdComment(userId, item1.getId(), dto));
+
+ assertEquals("Не удалось найти пользователя с id:99", ex.getMessage());
+ verify(commentRepository, never()).save(any());
+ }
+ }
+}
diff --git a/server/src/test/java/shareit/item/ItemControllerTest.java b/server/src/test/java/shareit/item/ItemControllerTest.java
new file mode 100644
index 00000000..eaeae95e
--- /dev/null
+++ b/server/src/test/java/shareit/item/ItemControllerTest.java
@@ -0,0 +1,168 @@
+package shareit.item;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import ru.practicum.shareit.ShareItServer;
+import ru.practicum.shareit.item.dto.CommentDto;
+import ru.practicum.shareit.item.dto.ItemDto;
+import ru.practicum.shareit.item.dto.ItemWithCommentDto;
+import ru.practicum.shareit.item.service.CommentService;
+import ru.practicum.shareit.item.service.ItemService;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import static org.hamcrest.Matchers.is;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@SpringBootTest(classes = ShareItServer.class)
+@AutoConfigureMockMvc
+public class ItemControllerTest {
+ @Autowired
+ private MockMvc mvc;
+
+ @Autowired
+ private ObjectMapper mapper;
+
+ @MockBean
+ private ItemService itemService;
+
+ @MockBean
+ private CommentService commentService;
+
+ private final ItemDto item = ItemDto.builder()
+ .id(1L)
+ .name("name1")
+ .description("description1")
+ .available(true)
+ .build();
+
+ private final ItemWithCommentDto itemWithComment = ItemWithCommentDto.builder()
+ .id(1L)
+ .name("name1")
+ .description("description1")
+ .available(true)
+ .build();
+
+ private final CommentDto comment = CommentDto.builder()
+ .id(1L)
+ .text("text1")
+ .authorName("user1")
+ .build();
+
+ @Test
+ public void createItem() throws Exception {
+ when(itemService.createItem(eq(1L), any(ItemDto.class))).thenReturn(item);
+
+ mvc.perform(post("/items")
+ .header("X-Sharer-User-Id", 1L)
+ .content(mapper.writeValueAsString(item))
+ .characterEncoding(StandardCharsets.UTF_8)
+ .contentType(MediaType.APPLICATION_JSON)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.id", is(item.getId()), Long.class))
+ .andExpect(jsonPath("$.name", is(item.getName())))
+ .andExpect(jsonPath("$.description", is(item.getDescription())))
+ .andExpect(jsonPath("$.available", is(item.getAvailable())));
+
+ verify(itemService, times(1)).createItem(eq(1L), any(ItemDto.class));
+ }
+
+ @Test
+ public void getItem() throws Exception {
+ when(itemService.getItem(1L)).thenReturn(itemWithComment);
+
+ mvc.perform(get("/items/{itemId}", 1L)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.id", is(itemWithComment.getId()), Long.class))
+ .andExpect(jsonPath("$.name", is(itemWithComment.getName())))
+ .andExpect(jsonPath("$.description", is(itemWithComment.getDescription())))
+ .andExpect(jsonPath("$.available", is(itemWithComment.getAvailable())));
+
+ verify(itemService, times(1)).getItem(1L);
+ }
+
+ @Test
+ public void updateItem() throws Exception {
+ when(itemService.updateItem(eq(1L), eq(1L), any(ItemDto.class))).thenReturn(item);
+
+ mvc.perform(patch("/items/{itemId}", 1L)
+ .header("X-Sharer-User-Id", 1L)
+ .content(mapper.writeValueAsString(item))
+ .characterEncoding(StandardCharsets.UTF_8)
+ .contentType(MediaType.APPLICATION_JSON)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.id", is(item.getId()), Long.class))
+ .andExpect(jsonPath("$.name", is(item.getName())))
+ .andExpect(jsonPath("$.description", is(item.getDescription())))
+ .andExpect(jsonPath("$.available", is(item.getAvailable())));
+
+ verify(itemService, times(1)).updateItem(eq(1L), eq(1L), any(ItemDto.class));
+ }
+
+ @Test
+ public void getUserItems() throws Exception {
+ when(itemService.getUserItems(1L)).thenReturn(List.of(itemWithComment));
+
+ mvc.perform(get("/items")
+ .header("X-Sharer-User-Id", 1L)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$[0].id", is(itemWithComment.getId()), Long.class))
+ .andExpect(jsonPath("$[0].name", is(itemWithComment.getName())))
+ .andExpect(jsonPath("$[0].description", is(itemWithComment.getDescription())))
+ .andExpect(jsonPath("$[0].available", is(itemWithComment.getAvailable())));
+
+ verify(itemService, times(1)).getUserItems(1L);
+ }
+
+ @Test
+ public void searchItems() throws Exception {
+ when(itemService.searchItems(eq(1L), eq("drill"))).thenReturn(List.of(item));
+
+ mvc.perform(get("/items/search")
+ .param("text", "drill")
+ .header("X-Sharer-User-Id", 1L)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$[0].id", is(item.getId()), Long.class))
+ .andExpect(jsonPath("$[0].name", is(item.getName())))
+ .andExpect(jsonPath("$[0].description", is(item.getDescription())))
+ .andExpect(jsonPath("$[0].available", is(item.getAvailable())));
+
+ verify(itemService, times(1)).searchItems(1L, "drill");
+ }
+
+ @Test
+ public void createComment() throws Exception {
+ when(commentService.createdComment(eq(1L), eq(1L), any(CommentDto.class))).thenReturn(comment);
+
+ mvc.perform(post("/items/{itemId}/comment", 1L)
+ .header("X-Sharer-User-Id", 1L)
+ .content(mapper.writeValueAsString(comment))
+ .characterEncoding(StandardCharsets.UTF_8)
+ .contentType(MediaType.APPLICATION_JSON)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.id", is(comment.getId()), Long.class))
+ .andExpect(jsonPath("$.text", is(comment.getText())))
+ .andExpect(jsonPath("$.authorName", is(comment.getAuthorName())));
+
+ verify(commentService, times(1)).createdComment(eq(1L), eq(1L), any(CommentDto.class));
+ }
+}
diff --git a/server/src/test/java/shareit/item/ItemDtoTest.java b/server/src/test/java/shareit/item/ItemDtoTest.java
new file mode 100644
index 00000000..07c8bdfa
--- /dev/null
+++ b/server/src/test/java/shareit/item/ItemDtoTest.java
@@ -0,0 +1,47 @@
+package shareit.item;
+
+import lombok.RequiredArgsConstructor;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.json.JsonTest;
+import org.springframework.boot.test.json.JacksonTester;
+import org.springframework.boot.test.json.JsonContent;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ContextConfiguration;
+import ru.practicum.shareit.ShareItServer;
+import ru.practicum.shareit.item.dto.CommentDto;
+import ru.practicum.shareit.item.dto.ItemDto;
+
+import java.io.IOException;
+import java.time.LocalDateTime;
+
+import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
+
+@JsonTest
+@RequiredArgsConstructor(onConstructor_ = @Autowired)
+@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
+@ContextConfiguration(classes = ShareItServer.class)
+public class ItemDtoTest {
+ private final JacksonTester json;
+
+ @Test
+ void testItemDto() throws IOException {
+ ItemDto dto = ItemDto.builder()
+ .id(1L)
+ .name("name")
+ .description("description")
+ .available(true)
+ .owner(1L)
+ .requestId(1L)
+ .build();
+
+ JsonContent result = json.write(dto);
+
+ assertThat(result).extractingJsonPathNumberValue("$.id").isEqualTo(1);
+ assertThat(result).extractingJsonPathStringValue("$.name").isEqualTo("name");
+ assertThat(result).extractingJsonPathStringValue("$.description").isEqualTo("description");
+ assertThat(result).extractingJsonPathBooleanValue("$.available").isEqualTo(true);
+ assertThat(result).extractingJsonPathNumberValue("$.owner").isEqualTo(1);
+ assertThat(result).extractingJsonPathNumberValue("$.requestId").isEqualTo(1);
+ }
+}
diff --git a/server/src/test/java/shareit/item/ItemRepositoryTest.java b/server/src/test/java/shareit/item/ItemRepositoryTest.java
new file mode 100644
index 00000000..adef2622
--- /dev/null
+++ b/server/src/test/java/shareit/item/ItemRepositoryTest.java
@@ -0,0 +1,122 @@
+package shareit.item;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+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.ShareItServer;
+import ru.practicum.shareit.item.Item;
+import ru.practicum.shareit.item.ItemRepository;
+import ru.practicum.shareit.user.User;
+import ru.practicum.shareit.user.UserRepository;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@Slf4j
+@DataJpaTest
+@ActiveProfiles("test")
+@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
+@ContextConfiguration(classes = ShareItServer.class)
+public class ItemRepositoryTest {
+
+ @Autowired
+ private ItemRepository repository;
+
+ @Autowired
+ private UserRepository userRepository;
+
+ private final User user1 = User.builder()
+ .id(1L)
+ .name("user1")
+ .email("email1@ya.ru")
+ .build();
+
+ private final User user2 = User.builder()
+ .id(2L)
+ .name("user2")
+ .email("email2@ya.ru")
+ .build();
+
+ private final Item item1 = Item.builder()
+ .id(1L)
+ .name("item1")
+ .description("description1")
+ .available(true)
+ .owner(user1)
+ .build();
+
+ private final Item item2 = Item.builder()
+ .id(2L)
+ .name("item2")
+ .description("description2")
+ .available(true)
+ .owner(user2)
+ .build();
+
+ private final Item item3 = Item.builder()
+ .id(3L)
+ .name("name3")
+ .description("description3")
+ .available(false)
+ .owner(user1)
+ .build();
+
+ private final List
- items = List.of(item1, item3);
+
+ @BeforeEach
+ public void init() {
+ userRepository.save(user1);
+ userRepository.save(user2);
+ repository.save(item1);
+ repository.save(item2);
+ repository.save(item3);
+ }
+
+ private void checkItem(Item item1, Item item2) {
+ assertEquals(item1.getId(), item2.getId(), "Не совпадает id");
+ assertEquals(item1.getName(), item2.getName(), "Не совпадает наименование");
+ assertEquals(item1.getDescription(), item2.getDescription(), "Не совпадает описание");
+ assertEquals(item1.getAvailable(), item2.getAvailable(), "Не совпадает статус");
+ assertEquals(item1.getOwner().getId(), item2.getOwner().getId(), "Не совпадает id владельца");
+ assertEquals(item1.getOwner().getName(), item2.getOwner().getName(), "Не совпадает имя владельца");
+ assertEquals(item1.getOwner().getEmail(), item2.getOwner().getEmail(), "Не совпадает почта владельца");
+ }
+
+ @Test
+ public void findByOwnerTest() {
+ List
- itemsR = repository.findByOwner(user1); //item1, item 3
+
+ assertEquals(itemsR.size(), 2, "Количество вещей не совпадает");
+ checkItem(itemsR.get(0), items.get(0));
+ checkItem(itemsR.get(1), items.get(1));
+ }
+
+ @Test
+ public void findByOwnerIdTest() {
+ List
- itemsR = repository.findByOwnerId(user1.getId()); //item1, item 3
+
+ assertEquals(itemsR.size(), 2, "Количество вещей не совпадает");
+ checkItem(itemsR.get(0), items.get(0));
+ checkItem(itemsR.get(1), items.get(1));
+ }
+
+ @Test
+ public void findByRequestIdTest() {
+
+ }
+
+ @Test
+ public void searchItemsTest() {
+ List
- itemsR = repository.searchItems("2");
+ assertEquals(itemsR.size(), 1, "Количество вещей не совпадает");
+ checkItem(itemsR.get(0), item2);
+ }
+}
diff --git a/server/src/test/java/shareit/item/ItemServiceImplTest.java b/server/src/test/java/shareit/item/ItemServiceImplTest.java
new file mode 100644
index 00000000..1572fa23
--- /dev/null
+++ b/server/src/test/java/shareit/item/ItemServiceImplTest.java
@@ -0,0 +1,211 @@
+package shareit.item;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mapstruct.factory.Mappers;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.jupiter.MockitoExtension;
+import ru.practicum.shareit.booking.Booking;
+import ru.practicum.shareit.booking.BookingMapper;
+import ru.practicum.shareit.booking.BookingRepository;
+import ru.practicum.shareit.exception.NotFoundException;
+import ru.practicum.shareit.item.*;
+import ru.practicum.shareit.item.dto.CommentDto;
+import ru.practicum.shareit.item.dto.ItemDto;
+import ru.practicum.shareit.item.dto.ItemWithCommentDto;
+import ru.practicum.shareit.item.service.ItemServiceImpl;
+import ru.practicum.shareit.user.User;
+import ru.practicum.shareit.user.UserRepository;
+
+import java.time.LocalDateTime;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+public class ItemServiceImplTest {
+
+ @Mock
+ private ItemRepository repository;
+
+ @Mock
+ private UserRepository userRepository;
+
+ @Mock
+ private CommentRepository commentRepository;
+
+ @Mock
+ private BookingRepository bookingRepository;
+
+ @Spy
+ private final ItemMapper mapper = Mappers.getMapper(ItemMapper.class);
+
+ @Spy
+ private final CommentMapper commentMapper = Mappers.getMapper(CommentMapper.class);
+
+ @Spy
+ private final BookingMapper bookingMapper = Mappers.getMapper(BookingMapper.class);
+
+ @InjectMocks
+ private ItemServiceImpl service;
+
+ private final User user1 = User.builder()
+ .id(1L)
+ .name("user1")
+ .email("email1@ya.ru")
+ .build();
+
+ private final Item item1 = Item.builder()
+ .id(1L)
+ .owner(user1)
+ .name("item1")
+ .description("desc1")
+ .available(true)
+ .build();
+
+ private final Item item2 = Item.builder()
+ .id(2L)
+ .owner(user1)
+ .name("item2")
+ .description("desc2")
+ .available(true)
+ .build();
+
+ private final Comment comment1 = Comment.builder()
+ .id(1L)
+ .item(item1)
+ .text("text1")
+ .createdAt(LocalDateTime.now())
+ .author(user1)
+ .build();
+
+ private final Booking booking1 = Booking.builder()
+ .id(1L)
+ .item(item1)
+ .booker(user1)
+ .start(LocalDateTime.now().minusDays(1))
+ .end(LocalDateTime.now())
+ .build();
+
+ private void checkItem(ItemDto dto1, ItemDto dto2) {
+ assertEquals(dto1.getId(), dto2.getId());
+ assertEquals(dto1.getName(), dto2.getName());
+ assertEquals(dto1.getDescription(), dto2.getDescription());
+ assertEquals(dto1.getAvailable(), dto2.getAvailable());
+ assertEquals(dto1.getOwner(), dto2.getOwner());
+ }
+
+ @Nested
+ class createItem {
+
+ @Test
+ public void createItem() {
+ when(userRepository.findById(user1.getId())).thenReturn(Optional.of(user1));
+ when(repository.save(any())).thenReturn(item1);
+ ItemDto dto = mapper.mapItemDto(item1);
+
+ ItemDto result = service.createItem(user1.getId(), dto);
+
+ verify(repository, times(1)).save(any());
+ checkItem(dto, result);
+ }
+
+ @Test
+ public void createItemWithFailUser() {
+ Long userId = 99L;
+ ItemDto dto = mapper.mapItemDto(item1);
+
+ when(userRepository.findById(userId)).thenReturn(Optional.empty());
+
+ NotFoundException ex = assertThrows(NotFoundException.class,
+ () -> service.createItem(userId, dto));
+
+ assertEquals("Не удалось найти пользователя с id 99", ex.getMessage());
+ verify(repository, never()).save(any());
+ }
+
+ //TODO
+ @Test
+ public void createItemWithRequest() {
+
+ }
+ }
+
+ @Nested
+ class getItem {
+ @Test
+ public void getItemById() {
+ when(repository.findById(item1.getId())).thenReturn(Optional.of(item1));
+ when(commentRepository.findCommentsByItemId(item1.getId())).thenReturn(Collections.EMPTY_LIST);
+ when(bookingRepository.findLastBooking(anyLong(), any())).thenReturn(Optional.empty());
+ when(bookingRepository.findNextBooking(anyLong(), any())).thenReturn(Optional.empty());
+ ItemDto dto = mapper.mapItemDto(item1);
+
+ ItemWithCommentDto result = service.getItem(item1.getId());
+
+ verify(repository, times(1)).findById(item1.getId());
+
+ assertEquals(dto.getId(), result.getId());
+ assertEquals(dto.getName(), result.getName());
+ assertEquals(dto.getDescription(), result.getDescription());
+ assertEquals(dto.getAvailable(), result.getAvailable());
+ assertEquals(dto.getOwner(), result.getOwner());
+ }
+ }
+
+ @Test
+ public void getUserItems() {
+ List
- items = List.of(item1, item2);
+ List itemIds = items.stream().map(Item::getId).toList();
+
+ CommentDto commentDto = commentMapper.mapCommentDto(comment1);
+
+ when(repository.findByOwnerId(user1.getId())).thenReturn(items);
+ when(repository.findByOwner(user1)).thenReturn(items);
+ when(userRepository.findById(user1.getId())).thenReturn(Optional.of(user1));
+ when(commentRepository.findCommentByItemIdIn(itemIds)).thenReturn(List.of(comment1));
+ when(bookingRepository.findLastBooking(eq(item1.getId()), any())).thenReturn(Optional.of(booking1));
+ when(bookingRepository.findNextBooking(eq(item1.getId()), any())).thenReturn(Optional.empty());
+ when(bookingRepository.findLastBooking(eq(item2.getId()), any())).thenReturn(Optional.empty());
+ when(bookingRepository.findNextBooking(eq(item2.getId()), any())).thenReturn(Optional.empty());
+
+ List result = service.getUserItems(user1.getId());
+
+ assertEquals(2, result.size());
+
+ ItemWithCommentDto result1 = result.stream()
+ .filter(dto -> dto.getId().equals(item1.getId()))
+ .findFirst()
+ .orElseThrow();
+
+ assertEquals(item1.getName(), result1.getName());
+ assertEquals(1, result1.getComments().size());
+ assertEquals(comment1.getText(), result1.getComments().get(0).getText());
+
+ assertNotNull(result1.getLastBooking());
+ assertEquals(booking1.getId(), result1.getLastBooking().getId());
+
+ assertNull(result1.getNextBooking());
+
+ ItemWithCommentDto result2 = result.stream()
+ .filter(dto -> dto.getId().equals(item2.getId()))
+ .findFirst()
+ .orElseThrow();
+
+ assertEquals(item2.getName(), result2.getName());
+ assertTrue(result2.getComments().isEmpty());
+ assertNull(result2.getLastBooking());
+ assertNull(result2.getNextBooking());
+
+ verify(repository, times(1)).findByOwnerId(user1.getId());
+ verify(repository, times(1)).findByOwner(user1);
+ verify(commentRepository, times(1)).findCommentByItemIdIn(itemIds);
+ }
+}
diff --git a/server/src/test/java/shareit/request/RequestControllerTest.java b/server/src/test/java/shareit/request/RequestControllerTest.java
new file mode 100644
index 00000000..ebed7655
--- /dev/null
+++ b/server/src/test/java/shareit/request/RequestControllerTest.java
@@ -0,0 +1,136 @@
+package shareit.request;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.test.web.servlet.MockMvc;
+import ru.practicum.shareit.ShareItServer;
+import ru.practicum.shareit.item.dto.ShortItemDto;
+import ru.practicum.shareit.request.ItemRequest;
+import ru.practicum.shareit.request.dto.FullRequestDto;
+import ru.practicum.shareit.request.dto.RequestAddDto;
+import ru.practicum.shareit.request.dto.RequestDto;
+import ru.practicum.shareit.request.service.RequestService;
+import ru.practicum.shareit.user.User;
+
+import static org.mockito.Mockito.*;
+import org.springframework.http.MediaType;
+
+import java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@SpringBootTest(classes = ShareItServer.class)
+@AutoConfigureMockMvc
+public class RequestControllerTest {
+ private final LocalDateTime testTime = LocalDateTime.of(2025, 07, 25, 00, 00, 00);
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @MockBean
+ private RequestService requestService;
+
+ @Autowired
+ private ObjectMapper objectMapper;
+
+ private final User user1 = User.builder()
+ .id(1L)
+ .name("user1")
+ .email("email1@ya.ru")
+ .build();
+
+ private final ItemRequest request = ItemRequest.builder()
+ .id(1L)
+ .requester(user1)
+ .created(testTime)
+ .description("desc1")
+ .build();
+
+ private final RequestDto requestDto = RequestDto.builder()
+ .id(1L)
+ .userId(user1.getId())
+ .created(testTime)
+ .description("desc1")
+ .build();
+
+ private final ShortItemDto item1 = ShortItemDto.builder()
+ .id(1L)
+ .name("item1")
+ .description("desc1")
+ .build();
+
+ private final ShortItemDto item2 = ShortItemDto.builder()
+ .id(2L)
+ .name("item2")
+ .description("desc2")
+ .build();
+
+ private final FullRequestDto fullRequest = FullRequestDto.builder()
+ .id(request.getId())
+ .description(request.getDescription())
+ .created(request.getCreated())
+ .items(Arrays.asList(item1, item2))
+ .build();
+
+ private final RequestAddDto addDto = new RequestAddDto("desc1");
+
+ @Test
+ void createRequest() throws Exception {
+ when(requestService.createRequest(any(RequestAddDto.class), anyLong()))
+ .thenReturn(requestDto);
+
+ mockMvc.perform(post("/requests")
+ .header("X-Sharer-User-Id", 1L)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(addDto)))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.id").value(requestDto.getId()))
+ .andExpect(jsonPath("$.description").value(requestDto.getDescription()));
+ }
+
+ @Test
+ void getRequestsByUser() throws Exception {
+ when(requestService.getRequestByUser(anyLong()))
+ .thenReturn(List.of(fullRequest));
+
+ mockMvc.perform(get("/requests")
+ .header("X-Sharer-User-Id", 1L))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.size()").value(1))
+ .andExpect(jsonPath("$[0].id").value(fullRequest.getId()))
+ .andExpect(jsonPath("$[0].description").value(fullRequest.getDescription()));
+ }
+
+ @Test
+ void getRequests() throws Exception {
+ when(requestService.getAllRequests(anyLong()))
+ .thenReturn(List.of(requestDto));
+
+ mockMvc.perform(get("/requests/all")
+ .header("X-Sharer-User-Id", 1L))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.size()").value(1))
+ .andExpect(jsonPath("$[0].id").value(requestDto.getId()))
+ .andExpect(jsonPath("$[0].description").value(requestDto.getDescription()));
+ }
+
+ @Test
+ void getRequestById() throws Exception {
+ when(requestService.getRequestById(anyLong(), anyLong()))
+ .thenReturn(fullRequest);
+
+ mockMvc.perform(get("/requests/{requestId}", 1L)
+ .header("X-Sharer-User-Id", 1L))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.id").value(fullRequest.getId()))
+ .andExpect(jsonPath("$.description").value(fullRequest.getDescription()));
+ }
+}
diff --git a/server/src/test/java/shareit/request/RequestServiceImplTest.java b/server/src/test/java/shareit/request/RequestServiceImplTest.java
new file mode 100644
index 00000000..94ecfeb1
--- /dev/null
+++ b/server/src/test/java/shareit/request/RequestServiceImplTest.java
@@ -0,0 +1,126 @@
+package shareit.request;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.*;
+import org.mockito.junit.jupiter.MockitoExtension;
+import ru.practicum.shareit.exception.NotFoundException;
+import ru.practicum.shareit.item.Item;
+import ru.practicum.shareit.item.ItemMapper;
+import ru.practicum.shareit.item.ItemRepository;
+import ru.practicum.shareit.item.dto.ShortItemDto;
+import ru.practicum.shareit.request.*;
+import ru.practicum.shareit.request.dto.FullRequestDto;
+import ru.practicum.shareit.request.service.RequestServiceImpl;
+import ru.practicum.shareit.user.User;
+import ru.practicum.shareit.user.UserRepository;
+
+import java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class RequestServiceImplTest {
+ private final LocalDateTime testTime = LocalDateTime.of(2025, 07, 25, 00, 00, 00);
+
+ @Mock
+ private RequestRepository requestRepository;
+
+ @Mock
+ private UserRepository userRepository;
+
+ @Mock
+ private RequestMapper requestMapper;
+
+ @Mock
+ private ItemRepository itemRepository;
+
+ @Mock
+ private ItemMapper itemMapper;
+
+ @InjectMocks
+ private RequestServiceImpl requestService;
+
+ private final User user1 = User.builder()
+ .id(1L)
+ .name("user1")
+ .email("email1@ya.ru")
+ .build();
+
+ private final ItemRequest request = ItemRequest.builder()
+ .id(1L)
+ .requester(user1)
+ .created(testTime)
+ .description("desc1")
+ .build();
+
+ private final ShortItemDto item1 = ShortItemDto.builder()
+ .id(1L)
+ .name("item1")
+ .description("desc1")
+ .build();
+
+ private final ShortItemDto item2 = ShortItemDto.builder()
+ .id(2L)
+ .name("item2")
+ .description("desc2")
+ .build();
+
+ private final FullRequestDto fullRequest = FullRequestDto.builder()
+ .id(request.getId())
+ .description(request.getDescription())
+ .created(request.getCreated())
+ .items(Arrays.asList(item1, item2))
+ .build();
+
+ @Test
+ void getRequestById_success() {
+ when(userRepository.findById(user1.getId())).thenReturn(Optional.of(user1));
+ when(requestRepository.findById(request.getId())).thenReturn(Optional.of(request));
+ when(itemRepository.findByRequest_Id(request.getId())).thenReturn(Collections.emptyList());
+ when(itemMapper.toShortItemDtoList(Collections.emptyList())).thenReturn(Collections.emptyList());
+ when(requestMapper.mapFullRequestDto(request, Collections.emptyList())).thenReturn(fullRequest);
+
+ FullRequestDto result = requestService.getRequestById(request.getId(), user1.getId());
+
+ assertNotNull(result);
+ assertEquals(fullRequest.getId(), result.getId());
+ assertEquals(fullRequest.getDescription(), result.getDescription());
+ assertEquals(fullRequest.getItems().size(), result.getItems().size());
+
+ verify(userRepository).findById(user1.getId());
+ verify(requestRepository).findById(request.getId());
+ verify(itemRepository).findByRequest_Id(request.getId());
+ verify(requestMapper).mapFullRequestDto(request, Collections.emptyList());
+ }
+
+ @Test
+ void getRequestByIdWithFailUser() {
+ when(userRepository.findById(user1.getId())).thenReturn(Optional.empty());
+
+ NotFoundException ex = assertThrows(NotFoundException.class,
+ () -> requestService.getRequestById(request.getId(), user1.getId()));
+
+ assertEquals("Не удалось найти пользователя с id " + request.getId(), ex.getMessage());
+ verify(userRepository).findById(request.getId());
+ verifyNoInteractions(requestRepository);
+ }
+
+ @Test
+ void getRequestById_requestNotFound_throwsException() {
+ when(userRepository.findById(user1.getId())).thenReturn(Optional.of(user1));
+ when(requestRepository.findById(request.getId())).thenReturn(Optional.empty());
+
+ NotFoundException ex = assertThrows(NotFoundException.class,
+ () -> requestService.getRequestById(request.getId(), request.getId()));
+
+ assertEquals("Не удалось найти запрос с id " + request.getId(), ex.getMessage());
+ verify(userRepository).findById(request.getId());
+ verify(requestRepository).findById(request.getId());
+ }
+}
diff --git a/server/src/test/java/shareit/user/UserControllerTest.java b/server/src/test/java/shareit/user/UserControllerTest.java
new file mode 100644
index 00000000..672fb79e
--- /dev/null
+++ b/server/src/test/java/shareit/user/UserControllerTest.java
@@ -0,0 +1,126 @@
+package shareit.user;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentMatchers;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.test.web.servlet.MockMvc;
+import ru.practicum.shareit.ShareItServer;
+import ru.practicum.shareit.user.UserDto;
+import ru.practicum.shareit.user.service.UserService;
+
+import static org.hamcrest.Matchers.is;
+import static org.mockito.Mockito.*;
+import org.springframework.http.MediaType;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@SpringBootTest(classes = ShareItServer.class)
+@AutoConfigureMockMvc
+public class UserControllerTest {
+
+ @Autowired
+ private MockMvc mvc;
+
+ @Autowired
+ private ObjectMapper mapper;
+
+ @MockBean
+ private UserService userService;
+
+ private final UserDto user = UserDto.builder()
+ .id(1L)
+ .name("user1")
+ .email("email1@ya.ru")
+ .build();
+
+ @Test
+ public void createUser() throws Exception {
+ when(userService.createUser(ArgumentMatchers.any(UserDto.class))).thenReturn(user);
+
+ mvc.perform(post("/users")
+ .content(mapper.writeValueAsString(user))
+ .characterEncoding(StandardCharsets.UTF_8)
+ .contentType(MediaType.APPLICATION_JSON)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.id", is(user.getId()), Long.class))
+ .andExpect(jsonPath("$.name", is(user.getName())))
+ .andExpect(jsonPath("$.email", is(user.getEmail())));
+
+ verify(userService, times(1)).createUser(ArgumentMatchers.any(UserDto.class));
+ }
+
+ @Test
+ public void getUserById() throws Exception {
+ when(userService.getUserById(ArgumentMatchers.anyLong())).thenReturn(user);
+
+ mvc.perform(get("/users/{userId}", 1L)
+ .content(mapper.writeValueAsString(user))
+ .characterEncoding(StandardCharsets.UTF_8)
+ .contentType(MediaType.APPLICATION_JSON)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.id", is(user.getId()), Long.class))
+ .andExpect(jsonPath("$.name", is(user.getName())))
+ .andExpect(jsonPath("$.email", is(user.getEmail())));
+
+ verify(userService, times(1)).getUserById(ArgumentMatchers.anyLong());
+ }
+
+ @Test
+ public void getAllUser() throws Exception {
+ when(userService.getAllUsers()).thenReturn(List.of(user));
+
+ mvc.perform(get("/users")
+ .content(mapper.writeValueAsString(user))
+ .characterEncoding(StandardCharsets.UTF_8)
+ .contentType(MediaType.APPLICATION_JSON)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$[0].id", is(user.getId()), Long.class))
+ .andExpect(jsonPath("$[0].name", is(user.getName())))
+ .andExpect(jsonPath("$[0].email", is(user.getEmail())));
+
+ verify(userService, times(1)).getAllUsers();
+ }
+
+ @Test
+ public void deleteUser() throws Exception {
+ mvc.perform(delete("/users/{userId}", 1L))
+ .andExpect(status().isOk());
+
+ verify(userService, times(1)).deleteUser(1L);
+ }
+
+ @Test
+ public void updateUser() throws Exception {
+ UserDto updatedUser = UserDto.builder()
+ .id(1L)
+ .name("updatedName")
+ .email("updated@ya.ru")
+ .build();
+
+ when(userService.updateUser(any(UserDto.class))).thenReturn(updatedUser);
+
+ mvc.perform(patch("/users/{userId}", 1L)
+ .content(mapper.writeValueAsString(updatedUser))
+ .characterEncoding(StandardCharsets.UTF_8)
+ .contentType(MediaType.APPLICATION_JSON)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.id", is(updatedUser.getId()), Long.class))
+ .andExpect(jsonPath("$.name", is(updatedUser.getName())))
+ .andExpect(jsonPath("$.email", is(updatedUser.getEmail())));
+
+ verify(userService, times(1)).updateUser(any(UserDto.class));
+ }
+}
diff --git a/server/src/test/java/shareit/user/UserDtoTest.java b/server/src/test/java/shareit/user/UserDtoTest.java
new file mode 100644
index 00000000..dff98bfb
--- /dev/null
+++ b/server/src/test/java/shareit/user/UserDtoTest.java
@@ -0,0 +1,42 @@
+package shareit.user;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.json.JsonTest;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.boot.test.json.JacksonTester;
+import org.springframework.boot.test.json.JsonContent;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import ru.practicum.shareit.ShareItServer;
+import ru.practicum.shareit.user.UserDto;
+
+import java.io.IOException;
+
+import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
+
+@JsonTest
+@RequiredArgsConstructor(onConstructor_ = @Autowired)
+@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
+@ContextConfiguration(classes = ShareItServer.class)
+public class UserDtoTest {
+ private final JacksonTester json;
+
+ @Test
+ void testUserDto() throws IOException {
+ UserDto dto = UserDto.builder()
+ .id(1L)
+ .name("name")
+ .email("useremail@email.com")
+ .build();
+
+ JsonContent result = json.write(dto);
+
+ assertThat(result).extractingJsonPathNumberValue("$.id").isEqualTo(1);
+ assertThat(result).extractingJsonPathStringValue("$.name").isEqualTo("name");
+ assertThat(result).extractingJsonPathStringValue("$.email").isEqualTo("useremail@email.com");
+ }
+}
\ No newline at end of file
diff --git a/server/src/test/java/shareit/user/UserServiceImplTest.java b/server/src/test/java/shareit/user/UserServiceImplTest.java
new file mode 100644
index 00000000..8901ff6b
--- /dev/null
+++ b/server/src/test/java/shareit/user/UserServiceImplTest.java
@@ -0,0 +1,112 @@
+package shareit.user;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mapstruct.factory.Mappers;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.jupiter.MockitoExtension;
+import ru.practicum.shareit.exception.NotFoundException;
+import ru.practicum.shareit.user.User;
+import ru.practicum.shareit.user.UserDto;
+import ru.practicum.shareit.user.UserMapper;
+import ru.practicum.shareit.user.UserRepository;
+import ru.practicum.shareit.user.service.UserServiceImpl;
+
+import java.util.List;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+public class UserServiceImplTest {
+
+ @Mock
+ private UserRepository repository;
+
+ @Spy
+ private final UserMapper mapper = Mappers.getMapper(UserMapper.class);
+
+ @InjectMocks
+ private UserServiceImpl service;
+
+ private final User user1 = User.builder()
+ .id(1L)
+ .name("user1")
+ .email("email1@ya.ru")
+ .build();
+
+ private final User user2 = User.builder()
+ .id(2L)
+ .name("user2")
+ .email("email2@ya.ru")
+ .build();
+
+ private void checkUserDto(UserDto user, UserDto userDto) {
+ assertEquals(user.getId(), userDto.getId());
+ assertEquals(user.getName(), userDto.getName());
+ assertEquals(user.getEmail(), userDto.getEmail());
+ }
+
+ @Test
+ public void createUser() {
+ when(repository.save(any())).thenReturn(user1);
+ UserDto dto = mapper.mapUserDto(user1);
+
+ UserDto result = service.createUser(dto);
+
+ verify(repository, times(1)).save(any());
+ checkUserDto(dto, result);
+ }
+
+ @Test
+ public void getAllUsers() {
+ when(repository.findAll()).thenReturn(List.of(user1, user2));
+ List result = service.getAllUsers();
+
+ verify(repository, times(1)).findAll();
+ assertEquals(result.size(), 2, "некорректное количество пользователей");
+ }
+
+ @Test
+ public void getUserById() {
+ when(repository.findById(user1.getId())).thenReturn(Optional.of(user1));
+ UserDto dto = mapper.mapUserDto(user1);
+
+ UserDto result = service.getUserById(user1.getId());
+
+ verify(repository, times(1)).findById(user1.getId());
+ checkUserDto(dto, result);
+ }
+
+ @Nested
+ class Delete {
+ @Test
+ public void shouldDelete() {
+ when(repository.findById(1L)).thenReturn(Optional.empty());
+
+ NotFoundException exception = assertThrows(NotFoundException.class,
+ () -> service.deleteUser(1L));
+
+ assertEquals("Не удалось нйти пользователя с id:1", exception.getMessage());
+ verify(repository, never()).deleteById(anyLong());
+ }
+
+ @Test
+ public void shouldDeleteIfUserIdNotFound() {
+ when(repository.findById(99L)).thenReturn(Optional.empty());
+
+ NotFoundException exception = assertThrows(NotFoundException.class,
+ () -> service.deleteUser(99L));
+
+ assertEquals("Не удалось нйти пользователя с id:99", exception.getMessage());
+ verify(repository, never()).deleteById(anyLong());
+ }
+ }
+
+}
diff --git a/server/src/test/resources/application.properties b/server/src/test/resources/application.properties
new file mode 100644
index 00000000..02d52ca2
--- /dev/null
+++ b/server/src/test/resources/application.properties
@@ -0,0 +1,11 @@
+server.port=9090
+
+spring.jpa.hibernate.ddl-auto=create-drop
+spring.jpa.properties.hibernate.format_sql=true
+spring.jpa.properties.hibernate.show_sql=true
+spring.sql.init.mode=never
+
+spring.datasource.driverClassName=org.h2.Driver
+spring.datasource.url=jdbc:h2:mem:shareit
+spring.datasource.username=shareit
+spring.datasource.password=shareit
\ No newline at end of file