From da0b7dd36b683586e47ddfa178a258c8e35a4ee6 Mon Sep 17 00:00:00 2001 From: Evgeniy Dmitriev Date: Sat, 28 Jun 2025 10:44:20 +0300 Subject: [PATCH 01/14] add item requests --- .../exception/RequestNotFoundException.java | 11 +++ .../practicum/shareit/item/dto/ItemDto.java | 2 + .../shareit/item/mapper/ItemMapper.java | 4 +- .../ru/practicum/shareit/item/model/Item.java | 3 + .../shareit/item/service/ItemServiceImpl.java | 14 +++- .../shareit/request/ItemRequest.java | 7 -- .../request/ItemRequestController.java | 38 +++++++-- .../shareit/request/dto/ItemRequestDto.java | 15 +++- .../request/mapper/ItemRequestMapper.java | 27 +++++++ .../shareit/request/model/ItemRequest.java | 30 ++++++++ .../repository/ItemRequestRepository.java | 16 ++++ .../request/service/ItemRequestService.java | 15 ++++ .../service/ItemRequestServiceImpl.java | 77 +++++++++++++++++++ src/main/resources/schema.sql | 12 ++- 14 files changed, 250 insertions(+), 21 deletions(-) create mode 100644 src/main/java/ru/practicum/shareit/exception/RequestNotFoundException.java delete mode 100644 src/main/java/ru/practicum/shareit/request/ItemRequest.java create mode 100644 src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java create mode 100644 src/main/java/ru/practicum/shareit/request/model/ItemRequest.java create mode 100644 src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepository.java create mode 100644 src/main/java/ru/practicum/shareit/request/service/ItemRequestService.java create mode 100644 src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java diff --git a/src/main/java/ru/practicum/shareit/exception/RequestNotFoundException.java b/src/main/java/ru/practicum/shareit/exception/RequestNotFoundException.java new file mode 100644 index 0000000..f3e3fd2 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/RequestNotFoundException.java @@ -0,0 +1,11 @@ +package ru.practicum.shareit.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.NOT_FOUND) +public class RequestNotFoundException extends RuntimeException { + public RequestNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java b/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java index 7c16cc2..800f3d0 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java +++ b/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java @@ -20,4 +20,6 @@ public class ItemDto { @NotNull(message = "Статус доступности не может быть null") private Boolean available; + + private Long requestId; } diff --git a/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java b/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java index 3350446..e8783de 100644 --- a/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java +++ b/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java @@ -10,7 +10,8 @@ public static ItemDto toItemDto(Item item) { item.getId(), item.getName(), item.getDescription(), - item.getAvailable() + item.getAvailable(), + item.getRequestId() != null ? item.getRequestId() : null ); } @@ -20,6 +21,7 @@ public static Item toItem(ItemDto itemDto) { item.setName(itemDto.getName()); item.setDescription(itemDto.getDescription()); item.setAvailable(itemDto.getAvailable()); + item.setRequestId(item.getRequestId()); return item; } diff --git a/src/main/java/ru/practicum/shareit/item/model/Item.java b/src/main/java/ru/practicum/shareit/item/model/Item.java index 7408bcc..b9d53f0 100644 --- a/src/main/java/ru/practicum/shareit/item/model/Item.java +++ b/src/main/java/ru/practicum/shareit/item/model/Item.java @@ -28,4 +28,7 @@ public class Item { @ManyToOne @JoinColumn(name = "owner_id", nullable = false) private User owner; + + @Column(name = "request_id") + private Long requestId; } diff --git a/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java b/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java index 14e6a45..3eeda65 100644 --- a/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java +++ b/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java @@ -8,10 +8,7 @@ import ru.practicum.shareit.booking.dto.BookingDto; import ru.practicum.shareit.booking.repository.BookingRepository; import ru.practicum.shareit.booking.status.BookingStatus; -import ru.practicum.shareit.exception.AccessDeniedException; -import ru.practicum.shareit.exception.ItemNotFoundException; -import ru.practicum.shareit.exception.UserNotFoundException; -import ru.practicum.shareit.exception.ValidationException; +import ru.practicum.shareit.exception.*; import ru.practicum.shareit.item.comment.Comment; import ru.practicum.shareit.item.comment.CommentDto; import ru.practicum.shareit.item.comment.CommentRepository; @@ -20,6 +17,8 @@ import ru.practicum.shareit.item.mapper.ItemMapper; import ru.practicum.shareit.item.model.Item; import ru.practicum.shareit.item.repository.ItemRepository; +import ru.practicum.shareit.request.repository.ItemRequestRepository; +import ru.practicum.shareit.request.model.ItemRequest; import ru.practicum.shareit.user.model.User; import ru.practicum.shareit.user.repository.UserRepository; @@ -38,6 +37,7 @@ public class ItemServiceImpl implements ItemService { private final UserRepository userRepository; private final BookingRepository bookingRepository; private final CommentRepository commentRepository; + private final ItemRequestRepository itemRequestRepository; @Override @@ -48,6 +48,12 @@ public ItemDto createItem(Long userId, ItemDto itemDto) { User owner = userRepository.findById(userId) .orElseThrow(() -> new UserNotFoundException("Собственник не найден")); + if (itemDto.getRequestId() != null) { + ItemRequest request = itemRequestRepository.findById(itemDto.getRequestId()) + .orElseThrow(() -> new RequestNotFoundException( + "Запрос с ID " + itemDto.getRequestId() + " не найден")); + } + Item item = ItemMapper.toItem(itemDto); item.setOwner(owner); diff --git a/src/main/java/ru/practicum/shareit/request/ItemRequest.java b/src/main/java/ru/practicum/shareit/request/ItemRequest.java deleted file mode 100644 index 95d6f23..0000000 --- a/src/main/java/ru/practicum/shareit/request/ItemRequest.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.practicum.shareit.request; - -/** - * TODO Sprint add-item-requests. - */ -public class ItemRequest { -} diff --git a/src/main/java/ru/practicum/shareit/request/ItemRequestController.java b/src/main/java/ru/practicum/shareit/request/ItemRequestController.java index 064e2e9..def75da 100644 --- a/src/main/java/ru/practicum/shareit/request/ItemRequestController.java +++ b/src/main/java/ru/practicum/shareit/request/ItemRequestController.java @@ -1,12 +1,40 @@ package ru.practicum.shareit.request; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.service.ItemRequestService; + +import java.util.List; -/** - * TODO Sprint add-item-requests. - */ @RestController @RequestMapping(path = "/requests") +@RequiredArgsConstructor public class ItemRequestController { + + private final ItemRequestService itemRequestService; + + @PostMapping + public ItemRequestDto createItemRequest(@RequestHeader("X-Sharer-User-Id") Long userId, + @RequestBody ItemRequestDto itemRequestDto) { + return itemRequestService.createItemRequest(userId, itemRequestDto); + } + + @GetMapping + public List getUserRequests(@RequestHeader("X-Sharer-User-Id") Long userId) { + return itemRequestService.getUserRequests(userId); + } + + @GetMapping("/all") + public List getAllRequests(@RequestHeader("X-Sharer-User-Id") Long userId, + @RequestParam(defaultValue = "0") int from, + @RequestParam(defaultValue = "20") int size) { + return itemRequestService.getAllRequests(userId, from, size); + } + + @GetMapping("/{requestId}") + public ItemRequestDto getRequestById(@RequestHeader("X-Sharer-User-Id") Long userId, + @PathVariable Long requestId) { + return itemRequestService.getRequestById(requestId, userId); + } } diff --git a/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java b/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java index 7b3ed54..c74602b 100644 --- a/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java +++ b/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java @@ -1,7 +1,16 @@ package ru.practicum.shareit.request.dto; -/** - * TODO Sprint add-item-requests. - */ +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor public class ItemRequestDto { + private Long id; + private String description; + private LocalDateTime created; } diff --git a/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java b/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java new file mode 100644 index 0000000..1be02cd --- /dev/null +++ b/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java @@ -0,0 +1,27 @@ +package ru.practicum.shareit.request.mapper; + +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.user.model.User; + +public class ItemRequestMapper { + public static ItemRequest toItemRequest(ItemRequestDto itemRequestDto, User requester) { + + if (itemRequestDto == null) { + return null; + } + + return new ItemRequest( + itemRequestDto.getId(), + itemRequestDto.getDescription(), + itemRequestDto.getCreated(), + requester); + } + + public static ItemRequestDto toItemRequestDto(ItemRequest itemRequest) { + return new ItemRequestDto( + itemRequest.getId(), + itemRequest.getDescription(), + itemRequest.getCreated()); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java b/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java new file mode 100644 index 0000000..d7d9577 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java @@ -0,0 +1,30 @@ +package ru.practicum.shareit.request.model; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "item_requests") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ItemRequest { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "description", nullable = false) + private String description; + + @Column(name = "created", nullable = false) + private LocalDateTime created; + + @ManyToOne + @JoinColumn(name = "requester_id", nullable = false) + private User requester; +} diff --git a/src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepository.java b/src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepository.java new file mode 100644 index 0000000..ffa754a --- /dev/null +++ b/src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepository.java @@ -0,0 +1,16 @@ +package ru.practicum.shareit.request.repository; + +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import ru.practicum.shareit.request.model.ItemRequest; + +import java.util.List; + +@Repository +public interface ItemRequestRepository extends JpaRepository { + + List findByRequesterId(Long userId, Sort sort); + + List findByRequesterIdNot(Long userId, Sort sort); +} diff --git a/src/main/java/ru/practicum/shareit/request/service/ItemRequestService.java b/src/main/java/ru/practicum/shareit/request/service/ItemRequestService.java new file mode 100644 index 0000000..7bd3aa8 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/request/service/ItemRequestService.java @@ -0,0 +1,15 @@ +package ru.practicum.shareit.request.service; + +import ru.practicum.shareit.request.dto.ItemRequestDto; +import java.util.List; + +public interface ItemRequestService { + + ItemRequestDto createItemRequest(Long userId, ItemRequestDto itemRequestDto); + + List getUserRequests(Long userId); + + List getAllRequests(Long userId, int from, int size); + + ItemRequestDto getRequestById(Long requestId, Long userId); +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java b/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java new file mode 100644 index 0000000..b2912e1 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java @@ -0,0 +1,77 @@ +package ru.practicum.shareit.request.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.shareit.exception.RequestNotFoundException; +import ru.practicum.shareit.exception.UserNotFoundException; +import ru.practicum.shareit.request.repository.ItemRequestRepository; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.mapper.ItemRequestMapper; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.repository.UserRepository; + +import java.time.LocalDateTime; +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ItemRequestServiceImpl implements ItemRequestService { + private final ItemRequestRepository itemRequestRepository; + private final UserRepository userRepository; + + @Override + @Transactional + public ItemRequestDto createItemRequest(Long userId, ItemRequestDto itemRequestDto) { + User requester = userRepository.findById(userId) + .orElseThrow(() -> new UserNotFoundException("Пользователь не найден")); + + if (itemRequestDto.getDescription() == null || itemRequestDto.getDescription().trim().isEmpty()) { + throw new IllegalArgumentException("Описание запроса не может быть пустым"); + } + + ItemRequest request = ItemRequestMapper.toItemRequest(itemRequestDto, requester); + request.setRequester(requester); + request.setCreated(LocalDateTime.now()); + + ItemRequest savedRequest = itemRequestRepository.save(request); + return ItemRequestMapper.toItemRequestDto(savedRequest); + } + + @Override + public List getUserRequests(Long userId) { + return itemRequestRepository.findByRequesterId(userId, Sort.by("created").descending()) + .stream() + .map(ItemRequestMapper::toItemRequestDto) + .toList(); + } + + @Override + public List getAllRequests(Long userId, int from, int size) { + userRepository.findById(userId) + .orElseThrow(() -> new UserNotFoundException("Пользователь не найден")); + + return itemRequestRepository.findByRequesterIdNot(userId, + Sort.by("created").descending()) + .stream() + .map(ItemRequestMapper::toItemRequestDto) + .toList(); + } + + @Override + public ItemRequestDto getRequestById(Long requestId, Long userId) { + ItemRequest request = itemRequestRepository.findById(requestId) + .orElseThrow(() -> new RequestNotFoundException("Запрос не найден")); + + // Проверяем, что текущий пользователь либо создатель запроса, либо тот, кто отвечает на него + if (!request.getRequester().getId().equals(userId)) { + userRepository.findById(userId) + .orElseThrow(() -> new UserNotFoundException("Пользователь не найден")); + } + + return ItemRequestMapper.toItemRequestDto(request); + } +} \ No newline at end of file diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index efb58f9..6d58500 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -6,6 +6,15 @@ CREATE TABLE IF NOT EXISTS users ( CONSTRAINT UQ_USER_EMAIL UNIQUE (email) ); +CREATE TABLE IF NOT EXISTS item_requests ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + description VARCHAR(512) NOT NULL, + created TIMESTAMP WITHOUT TIME ZONE NOT NULL, + requester_id BIGINT NOT NULL, + CONSTRAINT pk_item_request PRIMARY KEY (id), + CONSTRAINT fk_item_requests_to_users FOREIGN KEY (requester_id) REFERENCES users (id) +); + CREATE TABLE IF NOT EXISTS items ( id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, @@ -14,7 +23,8 @@ CREATE TABLE IF NOT EXISTS items ( owner_id BIGINT NOT NULL, request_id BIGINT, CONSTRAINT pk_item PRIMARY KEY (id), - CONSTRAINT fk_items_to_users FOREIGN KEY (owner_id) REFERENCES users (id) + CONSTRAINT fk_items_to_users FOREIGN KEY (owner_id) REFERENCES users (id), + CONSTRAINT fk_items_to_item_requests FOREIGN KEY (request_id) REFERENCES item_requests (id) ); CREATE TABLE IF NOT EXISTS bookings ( From f9013202463d59120459705622226d002b21a183 Mon Sep 17 00:00:00 2001 From: Evgeniy Dmitriev Date: Sat, 28 Jun 2025 15:27:17 +0300 Subject: [PATCH 02/14] add tests --- pom.xml | 31 ++++++ .../item/repository/ItemRepository.java | 4 + .../shareit/request/dto/ItemRequestDto.java | 3 + .../request/mapper/ItemRequestMapper.java | 15 ++- .../service/ItemRequestServiceImpl.java | 57 ++++++++-- src/main/resources/schema.sql | 1 + .../booking/BookingControllerTest.java | 66 +++++++++++ .../shareit/booking/BookingCreateDtoTest.java | 42 +++++++ .../booking/BookingRepositoryTest.java | 63 +++++++++++ .../shareit/item/ItemControllerTest.java | 105 ++++++++++++++++++ .../practicum/shareit/item/ItemDtoTest.java | 37 ++++++ .../shareit/item/ItemRepositoryTest.java | 61 ++++++++++ .../shareit/item/comment/CommentDtoTest.java | 38 +++++++ .../item/comment/CommentRepositoryTest.java | 57 ++++++++++ .../request/ItemRequestControllerTest.java | 101 +++++++++++++++++ .../request/ItemRequestDtoJsonTest.java | 45 ++++++++ .../ItemRequestRepositoryIntegrationTest.java | 88 +++++++++++++++ .../shareit/user/UserControllerTest.java | 95 ++++++++++++++++ .../practicum/shareit/user/UserDtoTest.java | 36 ++++++ .../shareit/user/UserRepositoryTest.java | 54 +++++++++ 20 files changed, 988 insertions(+), 11 deletions(-) create mode 100644 src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java create mode 100644 src/test/java/ru/practicum/shareit/booking/BookingCreateDtoTest.java create mode 100644 src/test/java/ru/practicum/shareit/booking/BookingRepositoryTest.java create mode 100644 src/test/java/ru/practicum/shareit/item/ItemControllerTest.java create mode 100644 src/test/java/ru/practicum/shareit/item/ItemDtoTest.java create mode 100644 src/test/java/ru/practicum/shareit/item/ItemRepositoryTest.java create mode 100644 src/test/java/ru/practicum/shareit/item/comment/CommentDtoTest.java create mode 100644 src/test/java/ru/practicum/shareit/item/comment/CommentRepositoryTest.java create mode 100644 src/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java create mode 100644 src/test/java/ru/practicum/shareit/request/ItemRequestDtoJsonTest.java create mode 100644 src/test/java/ru/practicum/shareit/request/ItemRequestRepositoryIntegrationTest.java create mode 100644 src/test/java/ru/practicum/shareit/user/UserControllerTest.java create mode 100644 src/test/java/ru/practicum/shareit/user/UserDtoTest.java create mode 100644 src/test/java/ru/practicum/shareit/user/UserRepositoryTest.java diff --git a/pom.xml b/pom.xml index 704411d..7460b3e 100644 --- a/pom.xml +++ b/pom.xml @@ -61,10 +61,41 @@ spring-boot-starter-test test + + + org.mockito + mockito-core + test + + + + org.assertj + assertj-core + 3.24.2 + test + + org.springframework.boot spring-boot-starter-validation + + + com.fasterxml.jackson.core + jackson-databind + 2.15.3 + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.15.3 + + + + org.springframework.boot + spring-boot-test-autoconfigure + test + diff --git a/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java b/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java index bb9f3ac..14d2568 100644 --- a/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java +++ b/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java @@ -12,6 +12,10 @@ public interface ItemRepository extends JpaRepository { List findByOwnerId(Long ownerId); + List findByRequestId(Long requestId); + + List findByRequestIdIn(List requestIds); + @Query("SELECT i FROM Item i " + "WHERE i.available = true AND " + "(UPPER(i.name) LIKE UPPER(CONCAT('%', ?1, '%')) OR " + diff --git a/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java b/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java index c74602b..d2602b6 100644 --- a/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java +++ b/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java @@ -3,8 +3,10 @@ import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; +import ru.practicum.shareit.item.dto.ItemDto; import java.time.LocalDateTime; +import java.util.List; @Data @NoArgsConstructor @@ -13,4 +15,5 @@ public class ItemRequestDto { private Long id; private String description; private LocalDateTime created; + private List items; } diff --git a/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java b/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java index 1be02cd..5386554 100644 --- a/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java +++ b/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java @@ -1,9 +1,13 @@ package ru.practicum.shareit.request.mapper; +import ru.practicum.shareit.item.dto.ItemDto; import ru.practicum.shareit.request.model.ItemRequest; import ru.practicum.shareit.request.dto.ItemRequestDto; import ru.practicum.shareit.user.model.User; +import java.util.Collections; +import java.util.List; + public class ItemRequestMapper { public static ItemRequest toItemRequest(ItemRequestDto itemRequestDto, User requester) { @@ -22,6 +26,15 @@ public static ItemRequestDto toItemRequestDto(ItemRequest itemRequest) { return new ItemRequestDto( itemRequest.getId(), itemRequest.getDescription(), - itemRequest.getCreated()); + itemRequest.getCreated(), + Collections.emptyList()); + } + + public static ItemRequestDto toItemRequestDto(ItemRequest itemRequest, List items) { + return new ItemRequestDto( + itemRequest.getId(), + itemRequest.getDescription(), + itemRequest.getCreated(), + items != null ? items : Collections.emptyList()); } } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java b/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java index b2912e1..604b642 100644 --- a/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java +++ b/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java @@ -6,6 +6,10 @@ import org.springframework.transaction.annotation.Transactional; import ru.practicum.shareit.exception.RequestNotFoundException; import ru.practicum.shareit.exception.UserNotFoundException; +import ru.practicum.shareit.exception.ValidationException; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.mapper.ItemMapper; +import ru.practicum.shareit.item.repository.ItemRepository; import ru.practicum.shareit.request.repository.ItemRequestRepository; import ru.practicum.shareit.request.dto.ItemRequestDto; import ru.practicum.shareit.request.mapper.ItemRequestMapper; @@ -15,6 +19,8 @@ import java.time.LocalDateTime; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -22,6 +28,7 @@ public class ItemRequestServiceImpl implements ItemRequestService { private final ItemRequestRepository itemRequestRepository; private final UserRepository userRepository; + private final ItemRepository itemRepository; @Override @Transactional @@ -30,7 +37,7 @@ public ItemRequestDto createItemRequest(Long userId, ItemRequestDto itemRequestD .orElseThrow(() -> new UserNotFoundException("Пользователь не найден")); if (itemRequestDto.getDescription() == null || itemRequestDto.getDescription().trim().isEmpty()) { - throw new IllegalArgumentException("Описание запроса не может быть пустым"); + throw new ValidationException("Описание запроса не может быть пустым"); } ItemRequest request = ItemRequestMapper.toItemRequest(itemRequestDto, requester); @@ -43,10 +50,25 @@ public ItemRequestDto createItemRequest(Long userId, ItemRequestDto itemRequestD @Override public List getUserRequests(Long userId) { - return itemRequestRepository.findByRequesterId(userId, Sort.by("created").descending()) + userRepository.findById(userId) + .orElseThrow(() -> new UserNotFoundException("Пользователь не найден")); + + List requests = itemRequestRepository + .findByRequesterId(userId, Sort.by("created").descending()); + + List requestIds = requests.stream() + .map(ItemRequest::getId) + .collect(Collectors.toList()); + + Map> itemsByRequestId = itemRepository.findByRequestIdIn(requestIds) .stream() - .map(ItemRequestMapper::toItemRequestDto) - .toList(); + .map(ItemMapper::toItemDto) + .collect(Collectors.groupingBy(ItemDto::getRequestId)); + + return requests.stream() + .map(request -> ItemRequestMapper.toItemRequestDto(request, + itemsByRequestId.getOrDefault(request.getId(), List.of()))) + .collect(Collectors.toList()); } @Override @@ -54,11 +76,22 @@ public List getAllRequests(Long userId, int from, int size) { userRepository.findById(userId) .orElseThrow(() -> new UserNotFoundException("Пользователь не найден")); - return itemRequestRepository.findByRequesterIdNot(userId, - Sort.by("created").descending()) + List requests = itemRequestRepository.findByRequesterIdNot(userId, + Sort.by("created").descending()); + + List requestIds = requests.stream() + .map(ItemRequest::getId) + .collect(Collectors.toList()); + + Map> itemsByRequestId = itemRepository.findByRequestIdIn(requestIds) .stream() - .map(ItemRequestMapper::toItemRequestDto) - .toList(); + .map(ItemMapper::toItemDto) + .collect(Collectors.groupingBy(ItemDto::getRequestId)); + + return requests.stream() + .map(request -> ItemRequestMapper.toItemRequestDto(request, + itemsByRequestId.getOrDefault(request.getId(), List.of()))) + .collect(Collectors.toList()); } @Override @@ -66,12 +99,16 @@ public ItemRequestDto getRequestById(Long requestId, Long userId) { ItemRequest request = itemRequestRepository.findById(requestId) .orElseThrow(() -> new RequestNotFoundException("Запрос не найден")); - // Проверяем, что текущий пользователь либо создатель запроса, либо тот, кто отвечает на него if (!request.getRequester().getId().equals(userId)) { userRepository.findById(userId) .orElseThrow(() -> new UserNotFoundException("Пользователь не найден")); } - return ItemRequestMapper.toItemRequestDto(request); + List items = itemRepository.findByRequestId(requestId) + .stream() + .map(ItemMapper::toItemDto) + .collect(Collectors.toList()); + + return ItemRequestMapper.toItemRequestDto(request, items); } } \ No newline at end of file diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 6d58500..af2a16c 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -8,6 +8,7 @@ CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS item_requests ( id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + name VARCHAR(255) NOT NULL, description VARCHAR(512) NOT NULL, created TIMESTAMP WITHOUT TIME ZONE NOT NULL, requester_id BIGINT NOT NULL, diff --git a/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java b/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java new file mode 100644 index 0000000..3c8615b --- /dev/null +++ b/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java @@ -0,0 +1,66 @@ +package ru.practicum.shareit.booking; + +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import ru.practicum.shareit.booking.dto.BookingCreateDto; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.service.BookingService; +import ru.practicum.shareit.booking.status.BookingStatus; + +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import com.fasterxml.jackson.databind.ObjectMapper; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.user.dto.UserDto; + +import java.time.LocalDateTime; + +@WebMvcTest(controllers = BookingController.class) +public class BookingControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private BookingService bookingService; + + private final ObjectMapper mapper = new ObjectMapper() + .registerModule(new JavaTimeModule()); + + @Test + void createBooking() throws Exception { + BookingCreateDto dto = new BookingCreateDto( + LocalDateTime.now().plusDays(1), + LocalDateTime.now().plusDays(2), + 1L + ); + + BookingDto responseDto = new BookingDto( + 1L, + dto.getStart(), + dto.getEnd(), + new ItemDto(1L, "Name", "Desc", true, null), + new UserDto(2L, "User", "user@mail.ru"), + BookingStatus.WAITING + ); + + when(bookingService.createBooking(anyLong(), any())).thenReturn(responseDto); + + mockMvc.perform(post("/bookings") + .header("X-Sharer-User-Id", 1) + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsString(dto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1L)); + + verify(bookingService, times(1)).createBooking(1L, dto); + } +} diff --git a/src/test/java/ru/practicum/shareit/booking/BookingCreateDtoTest.java b/src/test/java/ru/practicum/shareit/booking/BookingCreateDtoTest.java new file mode 100644 index 0000000..c8ba80c --- /dev/null +++ b/src/test/java/ru/practicum/shareit/booking/BookingCreateDtoTest.java @@ -0,0 +1,42 @@ +package ru.practicum.shareit.booking; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.json.JsonTest; +import org.springframework.boot.test.json.JacksonTester; + +import ru.practicum.shareit.booking.dto.BookingCreateDto; + +import java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.assertThat; + +@JsonTest +public class BookingCreateDtoTest { + + @Autowired + private JacksonTester jsonJacksonTester; + + @Test + void testSerialize() throws Exception { + LocalDateTime start = LocalDateTime.of(2024, 1, 1, 12, 0); + LocalDateTime end = LocalDateTime.of(2024, 1, 2, 12, 0); + BookingCreateDto dto = new BookingCreateDto(start, end, 1L); + + String expectedJson = """ + { + "start": "2024-01-01T12:00:00", + "end": "2024-01-02T12:00:00", + "itemId": 1 + } + """; + + // Сериализация + assertThat(jsonJacksonTester.write(dto)) + .isEqualToJson(expectedJson); + + // Десериализация + assertThat(jsonJacksonTester.parse(expectedJson)) + .isEqualTo(dto); + } +} \ No newline at end of file diff --git a/src/test/java/ru/practicum/shareit/booking/BookingRepositoryTest.java b/src/test/java/ru/practicum/shareit/booking/BookingRepositoryTest.java new file mode 100644 index 0000000..19ad2ec --- /dev/null +++ b/src/test/java/ru/practicum/shareit/booking/BookingRepositoryTest.java @@ -0,0 +1,63 @@ +package ru.practicum.shareit.booking; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.data.domain.Sort; +import ru.practicum.shareit.booking.model.Booking; +import ru.practicum.shareit.booking.repository.BookingRepository; +import ru.practicum.shareit.booking.status.BookingStatus; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.repository.ItemRepository; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.repository.UserRepository; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +@DataJpaTest +public class BookingRepositoryTest { + + @Autowired + private BookingRepository bookingRepository; + + @Autowired + private UserRepository userRepository; + + @Autowired + private ItemRepository itemRepository; + + private User owner; + private User booker; + private Item item; + + @BeforeEach + void setUp() { + owner = new User(null, "Owner", "owner@example.com"); + booker = new User(null, "Booker", "booker@example.com"); + owner = userRepository.save(owner); + booker = userRepository.save(booker); + + item = new Item(null, "Item", "Description", true, owner, null); + item = itemRepository.save(item); + } + + @Test + void testFindByBookerIdAndStatus() { + Booking booking = new Booking(); + booking.setStart(LocalDateTime.now().plusDays(1)); + booking.setEnd(LocalDateTime.now().plusDays(2)); + booking.setItem(item); + booking.setBooker(booker); + booking.setStatus(BookingStatus.WAITING); + bookingRepository.save(booking); + + List result = bookingRepository.findByBookerIdAndStatus(booker.getId(), BookingStatus.WAITING, Sort.by("id")); + + assertThat(result).hasSize(1); + assertThat(result.get(0)).isEqualTo(booking); + } +} diff --git a/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java b/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java new file mode 100644 index 0000000..c0d9522 --- /dev/null +++ b/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java @@ -0,0 +1,105 @@ +package ru.practicum.shareit.item; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.dto.ItemWithBookingDto; +import ru.practicum.shareit.item.service.ItemService; + +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +import java.util.List; + +@WebMvcTest(controllers = ItemController.class) +public class ItemControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private ItemService itemService; + + private final ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule()); + + @Test + void createItem() throws Exception { + ItemDto dto = new ItemDto(null, "Name", "Desc", true, null); + ItemDto response = new ItemDto(1L, "Name", "Desc", true, null); + + when(itemService.createItem(anyLong(), any())).thenReturn(response); + + mockMvc.perform(post("/items") + .header("X-Sharer-User-Id", 1) + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsString(dto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1L)); + + verify(itemService, times(1)).createItem(1L, dto); + } + + @Test + void updateItem() throws Exception { + ItemDto dto = new ItemDto(null, "Updated", "New Desc", false, null); + ItemDto response = new ItemDto(1L, "Updated", "New Desc", false, null); + + when(itemService.updateItem(anyLong(), anyLong(), any())).thenReturn(response); + + mockMvc.perform(patch("/items/1") + .header("X-Sharer-User-Id", 1) + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsString(dto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.description").value("New Desc")); + + verify(itemService, times(1)).updateItem(1L, 1L, dto); + } + + @Test + void getItemById() throws Exception { + ItemWithBookingDto response = new ItemWithBookingDto( + 1L, "Name", "Desc", true, null, null, List.of() + ); + + when(itemService.getItemById(anyLong())).thenReturn(response); + + mockMvc.perform(get("/items/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name").value("Name")); + } + + @Test + void getItemsByOwner() throws Exception { + ItemWithBookingDto response = new ItemWithBookingDto( + 1L, "Name", "Desc", true, null, null, List.of() + ); + + when(itemService.getItemsByOwner(anyLong())).thenReturn(List.of(response)); + + mockMvc.perform(get("/items") + .header("X-Sharer-User-Id", 1)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].name").value("Name")); + } + + @Test + void searchItems() throws Exception { + ItemDto response = new ItemDto(1L, "Name", "Desc", true, null); + + when(itemService.searchItems(anyString())).thenReturn(List.of(response)); + + mockMvc.perform(get("/items/search?text=name")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].name").value("Name")); + } +} diff --git a/src/test/java/ru/practicum/shareit/item/ItemDtoTest.java b/src/test/java/ru/practicum/shareit/item/ItemDtoTest.java new file mode 100644 index 0000000..9e5a79b --- /dev/null +++ b/src/test/java/ru/practicum/shareit/item/ItemDtoTest.java @@ -0,0 +1,37 @@ +package ru.practicum.shareit.item; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.json.JsonTest; +import org.springframework.boot.test.json.JacksonTester; + +import ru.practicum.shareit.item.dto.ItemDto; + +import static org.assertj.core.api.Assertions.assertThat; + +@JsonTest +public class ItemDtoTest { + + @Autowired + private JacksonTester jsonJacksonTester; + + @Test + void testSerializeDeserialize() throws Exception { + ItemDto dto = new ItemDto(1L, "Name", "Description", true, null); + + String expectedJson = """ + { + "id": 1, + "name": "Name", + "description": "Description", + "available": true + } + """; + + assertThat(jsonJacksonTester.write(dto)) + .isEqualToJson(expectedJson); + + assertThat(jsonJacksonTester.parse(expectedJson)) + .isEqualTo(dto); + } +} diff --git a/src/test/java/ru/practicum/shareit/item/ItemRepositoryTest.java b/src/test/java/ru/practicum/shareit/item/ItemRepositoryTest.java new file mode 100644 index 0000000..4f703f4 --- /dev/null +++ b/src/test/java/ru/practicum/shareit/item/ItemRepositoryTest.java @@ -0,0 +1,61 @@ +package ru.practicum.shareit.item; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.item.repository.ItemRepository; +import ru.practicum.shareit.user.repository.UserRepository; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@DataJpaTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +public class ItemRepositoryTest { + + @Autowired + private ItemRepository itemRepository; + + @Autowired + private UserRepository userRepository; + + private User user; + + @BeforeEach + void setUp() { + user = new User(null, "User", "user@example.com"); + user = userRepository.save(user); + } + + @Test + void testFindByOwnerId() { + Item item = new Item(null, "Spoon", "Silver spoon", true, user, null); + itemRepository.save(item); + + List items = itemRepository.findByOwnerId(user.getId()); + + assertThat(items).hasSize(1); + assertThat(items.get(0)).isEqualTo(item); + } + + @Test + void testSearch() { + Item item1 = new Item(null, "Spoon", "Silver spoon", true, user, null); + Item item2 = new Item(null, "Fork", "Stainless steel fork", true, user, null); + Item item3 = new Item(null, "Plate", "Ceramic plate", true, user, null); + + itemRepository.save(item1); + itemRepository.save(item2); + itemRepository.save(item3); + + List result = itemRepository.search("spoon"); + + assertThat(result).hasSize(1); + assertThat(result.get(0)).isEqualTo(item1); + } +} \ No newline at end of file diff --git a/src/test/java/ru/practicum/shareit/item/comment/CommentDtoTest.java b/src/test/java/ru/practicum/shareit/item/comment/CommentDtoTest.java new file mode 100644 index 0000000..9b76493 --- /dev/null +++ b/src/test/java/ru/practicum/shareit/item/comment/CommentDtoTest.java @@ -0,0 +1,38 @@ +package ru.practicum.shareit.item.comment; + +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 java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.assertThat; + +@JsonTest +public class CommentDtoTest { + + @Autowired + private JacksonTester jsonJacksonTester; + + @Test + void testSerializeDeserialize() throws Exception { + LocalDateTime now = LocalDateTime.now(); + CommentDto dto = new CommentDto(1L, "Text", "Author", now); + + String expectedJson = String.format(""" + { + "id": 1, + "text": "Text", + "authorName": "Author", + "created": "%s" + } + """, now.toString()); + + assertThat(jsonJacksonTester.write(dto)) + .isEqualToJson(expectedJson); + + assertThat(jsonJacksonTester.parse(expectedJson)) + .isEqualTo(dto); + } +} diff --git a/src/test/java/ru/practicum/shareit/item/comment/CommentRepositoryTest.java b/src/test/java/ru/practicum/shareit/item/comment/CommentRepositoryTest.java new file mode 100644 index 0000000..57fe157 --- /dev/null +++ b/src/test/java/ru/practicum/shareit/item/comment/CommentRepositoryTest.java @@ -0,0 +1,57 @@ +package ru.practicum.shareit.item.comment; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.repository.ItemRepository; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.repository.UserRepository; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@DataJpaTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +public class CommentRepositoryTest { + + @Autowired + private CommentRepository commentRepository; + + @Autowired + private UserRepository userRepository; + + @Autowired + private ItemRepository itemRepository; + + private User author; + private Item item; + + @BeforeEach + void setUp() { + author = new User(null, "Author", "author@example.com"); + author = userRepository.save(author); + + item = new Item(null, "Item", "Desc", true, author, null); + item = itemRepository.save(item); + } + + @Test + void testFindByItemId() { + Comment comment = new Comment(); + comment.setText("Great item!"); + comment.setItem(item); + comment.setAuthor(author); + comment.setCreated(LocalDateTime.now()); + commentRepository.save(comment); + + List comments = commentRepository.findByItemId(item.getId()); + + assertThat(comments).hasSize(1); + assertThat(comments.get(0).getText()).isEqualTo("Great item!"); + } +} diff --git a/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java b/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java new file mode 100644 index 0000000..abec576 --- /dev/null +++ b/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java @@ -0,0 +1,101 @@ +package ru.practicum.shareit.request; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.service.ItemRequestService; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@WebMvcTest(ItemRequestController.class) +public class ItemRequestControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private ItemRequestService itemRequestService; + + @Autowired + private ObjectMapper objectMapper; + + private ItemRequestDto itemRequestDto; + + @BeforeEach + public void setUp() { + itemRequestDto = new ItemRequestDto(); + itemRequestDto.setId(1L); + itemRequestDto.setDescription("Test request"); + itemRequestDto.setCreated(LocalDateTime.now()); + } + + @Test + public void testCreateItemRequest() throws Exception { + when(itemRequestService.createItemRequest(anyLong(), any(ItemRequestDto.class))) + .thenReturn(itemRequestDto); + + mockMvc.perform(post("/requests") + .header("X-Sharer-User-Id", 1) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(itemRequestDto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.description").value("Test request")); + + verify(itemRequestService, times(1)).createItemRequest(anyLong(), any(ItemRequestDto.class)); + } + + @Test + public void testGetUserRequests() throws Exception { + when(itemRequestService.getUserRequests(anyLong())).thenReturn(List.of(itemRequestDto)); + + mockMvc.perform(get("/requests") + .header("X-Sharer-User-Id", 1)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.length()").value(1)) + .andExpect(jsonPath("$[0].id").value(1)) + .andExpect(jsonPath("$[0].description").value("Test request")); + + verify(itemRequestService, times(1)).getUserRequests(anyLong()); + } + + @Test + public void testGetAllRequests() throws Exception { + when(itemRequestService.getAllRequests(anyLong(), anyInt(), anyInt())) + .thenReturn(List.of(itemRequestDto)); + + mockMvc.perform(get("/requests/all") + .header("X-Sharer-User-Id", 1)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.length()").value(1)) + .andExpect(jsonPath("$[0].id").value(1)) + .andExpect(jsonPath("$[0].description").value("Test request")); + + verify(itemRequestService, times(1)).getAllRequests(anyLong(), anyInt(), anyInt()); + } + + @Test + public void testGetRequestById() throws Exception { + when(itemRequestService.getRequestById(anyLong(), anyLong())) + .thenReturn(itemRequestDto); + + mockMvc.perform(get("/requests/1") + .header("X-Sharer-User-Id", 1)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.description").value("Test request")); + + verify(itemRequestService, times(1)).getRequestById(anyLong(), anyLong()); + } +} diff --git a/src/test/java/ru/practicum/shareit/request/ItemRequestDtoJsonTest.java b/src/test/java/ru/practicum/shareit/request/ItemRequestDtoJsonTest.java new file mode 100644 index 0000000..475d911 --- /dev/null +++ b/src/test/java/ru/practicum/shareit/request/ItemRequestDtoJsonTest.java @@ -0,0 +1,45 @@ +package ru.practicum.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.json.JsonTest; +import ru.practicum.shareit.request.dto.ItemRequestDto; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.regex.Pattern; + +import static org.assertj.core.api.Assertions.assertThat; + +@JsonTest +public class ItemRequestDtoJsonTest { + + @Autowired + private ObjectMapper objectMapper; + + @Test + public void testSerializeItemRequestDto() throws Exception { + LocalDateTime now = LocalDateTime.now(); + ItemRequestDto dto = new ItemRequestDto(1L, "Test request", now, null); + + String json = objectMapper.writeValueAsString(dto); + String expectedTimePrefix = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")); + + assertThat(json).contains("\"id\":1"); + assertThat(json).contains("\"description\":\"Test request\""); + assertThat(json).contains(expectedTimePrefix); + } + + @Test + public void testDeserializeItemRequestDto() throws Exception { + String json = String.format("{\"id\":1,\"description\":\"Test request\",\"created\":\"%s\"}", + LocalDateTime.now().toString()); + + ItemRequestDto dto = objectMapper.readValue(json, ItemRequestDto.class); + + assertThat(dto.getId()).isEqualTo(1L); + assertThat(dto.getDescription()).isEqualTo("Test request"); + assertThat(dto.getCreated()).isNotNull(); + } +} diff --git a/src/test/java/ru/practicum/shareit/request/ItemRequestRepositoryIntegrationTest.java b/src/test/java/ru/practicum/shareit/request/ItemRequestRepositoryIntegrationTest.java new file mode 100644 index 0000000..0df3d84 --- /dev/null +++ b/src/test/java/ru/practicum/shareit/request/ItemRequestRepositoryIntegrationTest.java @@ -0,0 +1,88 @@ +package ru.practicum.shareit.request; + +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.data.domain.Sort; +import org.springframework.test.annotation.DirtiesContext; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.request.repository.ItemRequestRepository; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.repository.UserRepository; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@DataJpaTest +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +public class ItemRequestRepositoryIntegrationTest { + + @Autowired + private ItemRequestRepository itemRequestRepository; + + @Autowired + private UserRepository userRepository; + + @Test + public void testFindUserRequests() { + User user = new User(); + user.setName("John Doe"); + user.setEmail("john@example.com"); + User savedUser = userRepository.save(user); + + ItemRequest request1 = new ItemRequest(); + request1.setDescription("Request 1"); + request1.setCreated(LocalDateTime.now()); + request1.setRequester(savedUser); + + ItemRequest request2 = new ItemRequest(); + request2.setDescription("Request 2"); + request2.setCreated(LocalDateTime.now().plusDays(1)); + request2.setRequester(savedUser); + + itemRequestRepository.save(request1); + itemRequestRepository.save(request2); + + List foundRequests = itemRequestRepository.findByRequesterId( + savedUser.getId(), + Sort.by("created").descending()); + + assertThat(foundRequests).hasSize(2); + assertThat(foundRequests.get(0).getDescription()).isEqualTo("Request 2"); + assertThat(foundRequests.get(1).getDescription()).isEqualTo("Request 1"); + } + + @Test + public void testFindOtherUsersRequests() { + User user1 = new User(); + user1.setName("John Doe"); + user1.setEmail("john@example.com"); + User savedUser1 = userRepository.save(user1); + + ItemRequest request1 = new ItemRequest(); + request1.setDescription("User1 Request"); + request1.setCreated(LocalDateTime.now()); + request1.setRequester(savedUser1); + itemRequestRepository.save(request1); + + User user2 = new User(); + user2.setName("Jane Smith"); + user2.setEmail("jane@example.com"); + User savedUser2 = userRepository.save(user2); + + ItemRequest request2 = new ItemRequest(); + request2.setDescription("User2 Request"); + request2.setCreated(LocalDateTime.now().plusDays(1)); + request2.setRequester(savedUser2); + itemRequestRepository.save(request2); + + List foundRequests = itemRequestRepository.findByRequesterIdNot( + savedUser1.getId(), + Sort.by("created").descending()); + + assertThat(foundRequests).hasSize(1); + assertThat(foundRequests.get(0).getDescription()).isEqualTo("User2 Request"); + } +} diff --git a/src/test/java/ru/practicum/shareit/user/UserControllerTest.java b/src/test/java/ru/practicum/shareit/user/UserControllerTest.java new file mode 100644 index 0000000..4acb4fc --- /dev/null +++ b/src/test/java/ru/practicum/shareit/user/UserControllerTest.java @@ -0,0 +1,95 @@ +package ru.practicum.shareit.user; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.service.UserService; + +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.List; + +@WebMvcTest(controllers = UserController.class) +public class UserControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private UserService userService; + + private final ObjectMapper mapper = new ObjectMapper(); + + @Test + void createUser() throws Exception { + UserDto dto = new UserDto(null, "John", "john@example.com"); + UserDto response = new UserDto(1L, "John", "john@example.com"); + + when(userService.createUser(any())).thenReturn(response); + + mockMvc.perform(post("/users") + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsString(dto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1L)); + + verify(userService, times(1)).createUser(dto); + } + + @Test + void updateUser() throws Exception { + UserDto dto = new UserDto(null, "Updated", "updated@example.com"); + UserDto response = new UserDto(1L, "Updated", "updated@example.com"); + + when(userService.updateUser(anyLong(), any())).thenReturn(response); + + mockMvc.perform(patch("/users/1") + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsString(dto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name").value("Updated")); + + verify(userService, times(1)).updateUser(1L, dto); + } + + @Test + void getUserById() throws Exception { + UserDto response = new UserDto(1L, "John", "john@example.com"); + + when(userService.getUserById(anyLong())).thenReturn(response); + + mockMvc.perform(get("/users/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.email").value("john@example.com")); + } + + @Test + void getAllUsers() throws Exception { + UserDto user1 = new UserDto(1L, "John", "john@example.com"); + UserDto user2 = new UserDto(2L, "Jane", "jane@example.com"); + + when(userService.getAllUsers()).thenReturn(List.of(user1, user2)); + + mockMvc.perform(get("/users")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].name").value("John")) + .andExpect(jsonPath("$[1].name").value("Jane")); + } + + @Test + void deleteUser() throws Exception { + mockMvc.perform(delete("/users/1")) + .andExpect(status().isOk()); + + verify(userService, times(1)).deleteUser(1L); + } +} diff --git a/src/test/java/ru/practicum/shareit/user/UserDtoTest.java b/src/test/java/ru/practicum/shareit/user/UserDtoTest.java new file mode 100644 index 0000000..21ae6e8 --- /dev/null +++ b/src/test/java/ru/practicum/shareit/user/UserDtoTest.java @@ -0,0 +1,36 @@ +package ru.practicum.shareit.user; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.json.JsonTest; +import org.springframework.boot.test.json.JacksonTester; + +import ru.practicum.shareit.user.dto.UserDto; + +import static org.assertj.core.api.Assertions.assertThat; + +@JsonTest +public class UserDtoTest { + + @Autowired + private JacksonTester jsonJacksonTester; + + @Test + void serializeDeserializeTest() throws Exception { + UserDto dto = new UserDto(1L, "John", "john@example.com"); + + String expectedJson = """ + { + "id": 1, + "name": "John", + "email": "john@example.com" + } + """; + + assertThat(jsonJacksonTester.write(dto)) + .isEqualToJson(expectedJson); + + assertThat(jsonJacksonTester.parse(expectedJson)) + .isEqualTo(dto); + } +} diff --git a/src/test/java/ru/practicum/shareit/user/UserRepositoryTest.java b/src/test/java/ru/practicum/shareit/user/UserRepositoryTest.java new file mode 100644 index 0000000..2b5d7e6 --- /dev/null +++ b/src/test/java/ru/practicum/shareit/user/UserRepositoryTest.java @@ -0,0 +1,54 @@ +package ru.practicum.shareit.user; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.repository.UserRepository; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +@DataJpaTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +public class UserRepositoryTest { + + @Autowired + private UserRepository userRepository; + + private User user; + + @BeforeEach + void setUp() { + user = new User(null, "John Doe", "john@example.com"); + } + + @Test + void testSaveAndFindById() { + User saved = userRepository.save(user); + Optional found = userRepository.findById(saved.getId()); + + assertThat(found).isPresent(); + assertThat(found.get()).isEqualTo(saved); + } + + @Test + void testExistsByEmail() { + userRepository.save(user); + boolean exists = userRepository.existsByEmail("john@example.com"); + assertThat(exists).isTrue(); + } + + @Test + void testExistsByEmailAndIdNot() { + User anotherUser = new User(null, "Jane Doe", "jane@example.com"); + userRepository.save(user); + userRepository.save(anotherUser); + + boolean exists = userRepository.existsByEmailAndIdNot("john@example.com", anotherUser.getId()); + assertThat(exists).isTrue(); + } +} From 916fdaa0adf2e28522eb5578b8e5b8260ec58611 Mon Sep 17 00:00:00 2001 From: Evgeniy Dmitriev Date: Sun, 29 Jun 2025 10:49:09 +0300 Subject: [PATCH 03/14] fix item-service --- .../shareit/item/mapper/ItemMapper.java | 2 +- .../shareit/item/service/ItemServiceImpl.java | 5 +++- src/main/resources/schema.sql | 1 - .../shareit/item/comment/CommentDtoTest.java | 26 +++++++------------ 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java b/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java index e8783de..27a3126 100644 --- a/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java +++ b/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java @@ -21,7 +21,7 @@ public static Item toItem(ItemDto itemDto) { item.setName(itemDto.getName()); item.setDescription(itemDto.getDescription()); item.setAvailable(itemDto.getAvailable()); - item.setRequestId(item.getRequestId()); + item.setRequestId(itemDto.getRequestId()); return item; } diff --git a/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java b/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java index 3eeda65..4df3302 100644 --- a/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java +++ b/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java @@ -92,6 +92,10 @@ public ItemDto updateItem(Long userId, Long itemId, ItemDto itemDto) { existingItem.setAvailable(itemDto.getAvailable()); } + if (itemDto.getRequestId() != null) { + existingItem.setRequestId(itemDto.getRequestId()); + } + Item updatedItem = itemRepository.save(existingItem); return ItemMapper.toItemDto(updatedItem); } @@ -160,7 +164,6 @@ public List searchItems(String text) { return new ArrayList<>(); } - String searchText = text.toLowerCase(); return itemRepository.search(text).stream() .map(ItemMapper::toItemDto) .collect(Collectors.toList()); diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index af2a16c..6d58500 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -8,7 +8,6 @@ CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS item_requests ( id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, - name VARCHAR(255) NOT NULL, description VARCHAR(512) NOT NULL, created TIMESTAMP WITHOUT TIME ZONE NOT NULL, requester_id BIGINT NOT NULL, diff --git a/src/test/java/ru/practicum/shareit/item/comment/CommentDtoTest.java b/src/test/java/ru/practicum/shareit/item/comment/CommentDtoTest.java index 9b76493..6ae8eba 100644 --- a/src/test/java/ru/practicum/shareit/item/comment/CommentDtoTest.java +++ b/src/test/java/ru/practicum/shareit/item/comment/CommentDtoTest.java @@ -17,22 +17,16 @@ public class CommentDtoTest { @Test void testSerializeDeserialize() throws Exception { - LocalDateTime now = LocalDateTime.now(); - CommentDto dto = new CommentDto(1L, "Text", "Author", now); - - String expectedJson = String.format(""" - { - "id": 1, - "text": "Text", - "authorName": "Author", - "created": "%s" - } - """, now.toString()); - - assertThat(jsonJacksonTester.write(dto)) - .isEqualToJson(expectedJson); - - assertThat(jsonJacksonTester.parse(expectedJson)) + LocalDateTime fixedTime = LocalDateTime.of(2025, 6, 29, 9, 51, 29, 161745700); + + CommentDto dto = new CommentDto(1L, "Text", "Author", fixedTime); + + String json = jsonJacksonTester.write(dto).getJson(); + + CommentDto parsedDto = jsonJacksonTester.parseObject(json); + + assertThat(parsedDto) + .usingRecursiveComparison() .isEqualTo(dto); } } From 7dfc34ce8443914671ca6ef2b50e66cfcde97177 Mon Sep 17 00:00:00 2001 From: Evgeniy Dmitriev Date: Sun, 29 Jun 2025 17:21:42 +0300 Subject: [PATCH 04/14] add gateway, server modules --- docker-compose.yml | 39 +++++++++ gateway/Dockerfile | 5 ++ gateway/pom.xml | 70 +++++++++++++++ .../ru/practicum/shareit/ShareItGateway.java | 12 +++ .../src/main/resources/application.properties | 7 ++ pom.xml | 7 ++ server/Dockerfile | 5 ++ server/pom.xml | 86 +++++++++++++++++++ .../ru/practicum/shareit/ShareItServer.java | 8 +- .../shareit/booking/BookingController.java | 0 .../shareit/booking/dto/BookingCreateDto.java | 0 .../shareit/booking/dto/BookingDto.java | 0 .../shareit/booking/mapper/BookingMapper.java | 0 .../shareit/booking/model/Booking.java | 0 .../booking/repository/BookingRepository.java | 0 .../booking/service/BookingService.java | 0 .../booking/service/BookingServiceImpl.java | 0 .../shareit/booking/status/BookingStatus.java | 0 .../exception/AccessDeniedException.java | 0 .../exception/BookingNotFoundException.java | 0 .../exception/ItemNotFoundException.java | 0 .../exception/RequestNotFoundException.java | 0 .../exception/UserNotFoundException.java | 0 .../exception/ValidationException.java | 0 .../shareit/item/ItemController.java | 0 .../shareit/item/comment/Comment.java | 0 .../shareit/item/comment/CommentDto.java | 0 .../item/comment/CommentRepository.java | 0 .../practicum/shareit/item/dto/ItemDto.java | 0 .../shareit/item/dto/ItemWithBookingDto.java | 0 .../shareit/item/mapper/ItemMapper.java | 0 .../ru/practicum/shareit/item/model/Item.java | 0 .../item/repository/ItemRepository.java | 0 .../shareit/item/service/ItemService.java | 0 .../shareit/item/service/ItemServiceImpl.java | 0 .../request/ItemRequestController.java | 0 .../shareit/request/dto/ItemRequestDto.java | 0 .../request/mapper/ItemRequestMapper.java | 0 .../shareit/request/model/ItemRequest.java | 0 .../repository/ItemRequestRepository.java | 0 .../request/service/ItemRequestService.java | 0 .../service/ItemRequestServiceImpl.java | 0 .../shareit/user/UserController.java | 0 .../practicum/shareit/user/dto/UserDto.java | 0 .../shareit/user/mapper/UserMapper.java | 0 .../ru/practicum/shareit/user/model/User.java | 0 .../user/repository/UserRepository.java | 0 .../shareit/user/service/UserService.java | 0 .../shareit/user/service/UserServiceImpl.java | 0 .../user/validator/EmailValidator.java | 0 .../main/resources/application.properties | 9 +- {src => server/src}/main/resources/schema.sql | 0 .../ru/practicum/shareit/ShareItTests.java | 0 .../booking/BookingControllerTest.java | 0 .../shareit/booking/BookingCreateDtoTest.java | 0 .../booking/BookingRepositoryTest.java | 0 .../shareit/item/ItemControllerTest.java | 0 .../practicum/shareit/item/ItemDtoTest.java | 0 .../shareit/item/ItemRepositoryTest.java | 0 .../shareit/item/comment/CommentDtoTest.java | 0 .../item/comment/CommentRepositoryTest.java | 0 .../request/ItemRequestControllerTest.java | 0 .../request/ItemRequestDtoJsonTest.java | 0 .../ItemRequestRepositoryIntegrationTest.java | 0 .../shareit/user/UserControllerTest.java | 0 .../practicum/shareit/user/UserDtoTest.java | 0 .../shareit/user/UserRepositoryTest.java | 0 67 files changed, 240 insertions(+), 8 deletions(-) create mode 100644 docker-compose.yml create mode 100644 gateway/Dockerfile create mode 100644 gateway/pom.xml create mode 100644 gateway/src/main/java/ru/practicum/shareit/ShareItGateway.java create mode 100644 gateway/src/main/resources/application.properties create mode 100644 server/Dockerfile create mode 100644 server/pom.xml rename src/main/java/ru/practicum/shareit/ShareItApp.java => server/src/main/java/ru/practicum/shareit/ShareItServer.java (56%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/BookingController.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/dto/BookingCreateDto.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/dto/BookingDto.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/mapper/BookingMapper.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/model/Booking.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/service/BookingService.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/booking/status/BookingStatus.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/exception/AccessDeniedException.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/exception/BookingNotFoundException.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/exception/ItemNotFoundException.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/exception/RequestNotFoundException.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/exception/UserNotFoundException.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/exception/ValidationException.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/item/ItemController.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/item/comment/Comment.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/item/comment/CommentDto.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/item/comment/CommentRepository.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/item/dto/ItemDto.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/item/dto/ItemWithBookingDto.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/item/model/Item.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/item/repository/ItemRepository.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/item/service/ItemService.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/request/ItemRequestController.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/request/model/ItemRequest.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/request/repository/ItemRequestRepository.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/request/service/ItemRequestService.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/user/UserController.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/user/dto/UserDto.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/user/mapper/UserMapper.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/user/model/User.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/user/repository/UserRepository.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/user/service/UserService.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java (100%) rename {src => server/src}/main/java/ru/practicum/shareit/user/validator/EmailValidator.java (100%) rename {src => server/src}/main/resources/application.properties (86%) rename {src => server/src}/main/resources/schema.sql (100%) rename {src => server/src}/test/java/ru/practicum/shareit/ShareItTests.java (100%) rename {src => server/src}/test/java/ru/practicum/shareit/booking/BookingControllerTest.java (100%) rename {src => server/src}/test/java/ru/practicum/shareit/booking/BookingCreateDtoTest.java (100%) rename {src => server/src}/test/java/ru/practicum/shareit/booking/BookingRepositoryTest.java (100%) rename {src => server/src}/test/java/ru/practicum/shareit/item/ItemControllerTest.java (100%) rename {src => server/src}/test/java/ru/practicum/shareit/item/ItemDtoTest.java (100%) rename {src => server/src}/test/java/ru/practicum/shareit/item/ItemRepositoryTest.java (100%) rename {src => server/src}/test/java/ru/practicum/shareit/item/comment/CommentDtoTest.java (100%) rename {src => server/src}/test/java/ru/practicum/shareit/item/comment/CommentRepositoryTest.java (100%) rename {src => server/src}/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java (100%) rename {src => server/src}/test/java/ru/practicum/shareit/request/ItemRequestDtoJsonTest.java (100%) rename {src => server/src}/test/java/ru/practicum/shareit/request/ItemRequestRepositoryIntegrationTest.java (100%) rename {src => server/src}/test/java/ru/practicum/shareit/user/UserControllerTest.java (100%) rename {src => server/src}/test/java/ru/practicum/shareit/user/UserDtoTest.java (100%) rename {src => server/src}/test/java/ru/practicum/shareit/user/UserRepositoryTest.java (100%) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..abe6570 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,39 @@ +services: + gateway: + build: gateway + image: shareit-gateway + container_name: shareit-gateway + ports: + - "8080:8080" + depends_on: + - server + environment: + - SHAREIT_SERVER_URL=http://server:9090 + + server: + build: server + image: shareit-server + container_name: shareit-server + ports: + - "9090:9090" + depends_on: + - db + environment: + - SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/shareit + - SPRING_DATASOURCE_USERNAME=shareit + - SPRING_DATASOURCE_PASSWORD=shareit + + db: + image: postgres:16.1 + container_name: postgres + ports: + - "6541:5432" + environment: + - POSTGRES_PASSWORD=shareit + - POSTGRES_USER=shareit + - POSTGRES_DB=shareit + healthcheck: + test: pg_isready -q -d $$POSTGRES_DB -U $$POSTGRES_USER + timeout: 5s + interval: 5s + retries: 10 \ No newline at end of file diff --git a/gateway/Dockerfile b/gateway/Dockerfile new file mode 100644 index 0000000..0ff1817 --- /dev/null +++ b/gateway/Dockerfile @@ -0,0 +1,5 @@ +FROM eclipse-temurin:21-jre-jammy +VOLUME /tmp +ARG JAR_FILE=target/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"] \ No newline at end of file diff --git a/gateway/pom.xml b/gateway/pom.xml new file mode 100644 index 0000000..f3394c1 --- /dev/null +++ b/gateway/pom.xml @@ -0,0 +1,70 @@ + + + 4.0.0 + + ru.practicum + shareit + 0.0.1-SNAPSHOT + + + shareit-gateway + 0.0.1-SNAPSHOT + + ShareIt Gateway + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.hibernate.validator + hibernate-validator + + + + org.apache.httpcomponents.client5 + httpclient5 + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.projectlombok + lombok + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/ShareItGateway.java b/gateway/src/main/java/ru/practicum/shareit/ShareItGateway.java new file mode 100644 index 0000000..4cbc16c --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/ShareItGateway.java @@ -0,0 +1,12 @@ +package ru.practicum.shareit; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ShareItGateway { + public static void main(String[] args) { + SpringApplication.run(ShareItGateway.class, args); + } + +} \ No newline at end of file diff --git a/gateway/src/main/resources/application.properties b/gateway/src/main/resources/application.properties new file mode 100644 index 0000000..2ee0851 --- /dev/null +++ b/gateway/src/main/resources/application.properties @@ -0,0 +1,7 @@ +logging.level.org.springframework.web.client.RestTemplate=DEBUG +#logging.level.org.apache.http=DEBUG +#logging.level.httpclient.wire=DEBUG + +server.port=8080 + +shareit-server.url=http://localhost:9090 \ No newline at end of file diff --git a/pom.xml b/pom.xml index 7460b3e..4f6792c 100644 --- a/pom.xml +++ b/pom.xml @@ -19,6 +19,13 @@ 21 + pom + + + gateway + server + + org.springframework.boot diff --git a/server/Dockerfile b/server/Dockerfile new file mode 100644 index 0000000..0ff1817 --- /dev/null +++ b/server/Dockerfile @@ -0,0 +1,5 @@ +FROM eclipse-temurin:21-jre-jammy +VOLUME /tmp +ARG JAR_FILE=target/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"] \ No newline at end of file diff --git a/server/pom.xml b/server/pom.xml new file mode 100644 index 0000000..5dd8414 --- /dev/null +++ b/server/pom.xml @@ -0,0 +1,86 @@ + + + 4.0.0 + + ru.practicum + shareit + 0.0.1-SNAPSHOT + + + shareit-server + 0.0.1-SNAPSHOT + + ShareIt Server + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.postgresql + postgresql + runtime + + + + com.h2database + h2 + runtime + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.projectlombok + lombok + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + coverage + + + + org.jacoco + jacoco-maven-plugin + + + + + + + \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/ShareItApp.java b/server/src/main/java/ru/practicum/shareit/ShareItServer.java similarity index 56% rename from src/main/java/ru/practicum/shareit/ShareItApp.java rename to server/src/main/java/ru/practicum/shareit/ShareItServer.java index a00ad56..e814eb2 100644 --- a/src/main/java/ru/practicum/shareit/ShareItApp.java +++ b/server/src/main/java/ru/practicum/shareit/ShareItServer.java @@ -4,10 +4,10 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -public class ShareItApp { +public class ShareItServer { - public static void main(String[] args) { - SpringApplication.run(ShareItApp.class, args); - } + public static void main(String[] args) { + SpringApplication.run(ShareItServer.class, args); + } } diff --git a/src/main/java/ru/practicum/shareit/booking/BookingController.java b/server/src/main/java/ru/practicum/shareit/booking/BookingController.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/BookingController.java rename to server/src/main/java/ru/practicum/shareit/booking/BookingController.java diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingCreateDto.java b/server/src/main/java/ru/practicum/shareit/booking/dto/BookingCreateDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/dto/BookingCreateDto.java rename to server/src/main/java/ru/practicum/shareit/booking/dto/BookingCreateDto.java diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java b/server/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java rename to server/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java diff --git a/src/main/java/ru/practicum/shareit/booking/mapper/BookingMapper.java b/server/src/main/java/ru/practicum/shareit/booking/mapper/BookingMapper.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/mapper/BookingMapper.java rename to server/src/main/java/ru/practicum/shareit/booking/mapper/BookingMapper.java diff --git a/src/main/java/ru/practicum/shareit/booking/model/Booking.java b/server/src/main/java/ru/practicum/shareit/booking/model/Booking.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/model/Booking.java rename to server/src/main/java/ru/practicum/shareit/booking/model/Booking.java diff --git a/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java b/server/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java rename to server/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java diff --git a/src/main/java/ru/practicum/shareit/booking/service/BookingService.java b/server/src/main/java/ru/practicum/shareit/booking/service/BookingService.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/service/BookingService.java rename to server/src/main/java/ru/practicum/shareit/booking/service/BookingService.java diff --git a/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java b/server/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java rename to server/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java diff --git a/src/main/java/ru/practicum/shareit/booking/status/BookingStatus.java b/server/src/main/java/ru/practicum/shareit/booking/status/BookingStatus.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/status/BookingStatus.java rename to server/src/main/java/ru/practicum/shareit/booking/status/BookingStatus.java diff --git a/src/main/java/ru/practicum/shareit/exception/AccessDeniedException.java b/server/src/main/java/ru/practicum/shareit/exception/AccessDeniedException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exception/AccessDeniedException.java rename to server/src/main/java/ru/practicum/shareit/exception/AccessDeniedException.java diff --git a/src/main/java/ru/practicum/shareit/exception/BookingNotFoundException.java b/server/src/main/java/ru/practicum/shareit/exception/BookingNotFoundException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exception/BookingNotFoundException.java rename to server/src/main/java/ru/practicum/shareit/exception/BookingNotFoundException.java diff --git a/src/main/java/ru/practicum/shareit/exception/ItemNotFoundException.java b/server/src/main/java/ru/practicum/shareit/exception/ItemNotFoundException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exception/ItemNotFoundException.java rename to server/src/main/java/ru/practicum/shareit/exception/ItemNotFoundException.java diff --git a/src/main/java/ru/practicum/shareit/exception/RequestNotFoundException.java b/server/src/main/java/ru/practicum/shareit/exception/RequestNotFoundException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exception/RequestNotFoundException.java rename to server/src/main/java/ru/practicum/shareit/exception/RequestNotFoundException.java diff --git a/src/main/java/ru/practicum/shareit/exception/UserNotFoundException.java b/server/src/main/java/ru/practicum/shareit/exception/UserNotFoundException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exception/UserNotFoundException.java rename to server/src/main/java/ru/practicum/shareit/exception/UserNotFoundException.java diff --git a/src/main/java/ru/practicum/shareit/exception/ValidationException.java b/server/src/main/java/ru/practicum/shareit/exception/ValidationException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exception/ValidationException.java rename to server/src/main/java/ru/practicum/shareit/exception/ValidationException.java diff --git a/src/main/java/ru/practicum/shareit/item/ItemController.java b/server/src/main/java/ru/practicum/shareit/item/ItemController.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/ItemController.java rename to server/src/main/java/ru/practicum/shareit/item/ItemController.java diff --git a/src/main/java/ru/practicum/shareit/item/comment/Comment.java b/server/src/main/java/ru/practicum/shareit/item/comment/Comment.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/comment/Comment.java rename to server/src/main/java/ru/practicum/shareit/item/comment/Comment.java diff --git a/src/main/java/ru/practicum/shareit/item/comment/CommentDto.java b/server/src/main/java/ru/practicum/shareit/item/comment/CommentDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/comment/CommentDto.java rename to server/src/main/java/ru/practicum/shareit/item/comment/CommentDto.java diff --git a/src/main/java/ru/practicum/shareit/item/comment/CommentRepository.java b/server/src/main/java/ru/practicum/shareit/item/comment/CommentRepository.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/comment/CommentRepository.java rename to server/src/main/java/ru/practicum/shareit/item/comment/CommentRepository.java diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java b/server/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/dto/ItemDto.java rename to server/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemWithBookingDto.java b/server/src/main/java/ru/practicum/shareit/item/dto/ItemWithBookingDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/dto/ItemWithBookingDto.java rename to server/src/main/java/ru/practicum/shareit/item/dto/ItemWithBookingDto.java diff --git a/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java b/server/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java rename to server/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java diff --git a/src/main/java/ru/practicum/shareit/item/model/Item.java b/server/src/main/java/ru/practicum/shareit/item/model/Item.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/model/Item.java rename to server/src/main/java/ru/practicum/shareit/item/model/Item.java diff --git a/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java b/server/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java rename to server/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java diff --git a/src/main/java/ru/practicum/shareit/item/service/ItemService.java b/server/src/main/java/ru/practicum/shareit/item/service/ItemService.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/service/ItemService.java rename to server/src/main/java/ru/practicum/shareit/item/service/ItemService.java diff --git a/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java b/server/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java rename to server/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java diff --git a/src/main/java/ru/practicum/shareit/request/ItemRequestController.java b/server/src/main/java/ru/practicum/shareit/request/ItemRequestController.java similarity index 100% rename from src/main/java/ru/practicum/shareit/request/ItemRequestController.java rename to server/src/main/java/ru/practicum/shareit/request/ItemRequestController.java diff --git a/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java b/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java rename to server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java diff --git a/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java b/server/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java similarity index 100% rename from src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java rename to server/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java diff --git a/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java b/server/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java similarity index 100% rename from src/main/java/ru/practicum/shareit/request/model/ItemRequest.java rename to server/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java diff --git a/src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepository.java b/server/src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepository.java similarity index 100% rename from src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepository.java rename to server/src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepository.java diff --git a/src/main/java/ru/practicum/shareit/request/service/ItemRequestService.java b/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestService.java similarity index 100% rename from src/main/java/ru/practicum/shareit/request/service/ItemRequestService.java rename to server/src/main/java/ru/practicum/shareit/request/service/ItemRequestService.java diff --git a/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java b/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java similarity index 100% rename from src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java rename to server/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java diff --git a/src/main/java/ru/practicum/shareit/user/UserController.java b/server/src/main/java/ru/practicum/shareit/user/UserController.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/UserController.java rename to server/src/main/java/ru/practicum/shareit/user/UserController.java diff --git a/src/main/java/ru/practicum/shareit/user/dto/UserDto.java b/server/src/main/java/ru/practicum/shareit/user/dto/UserDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/dto/UserDto.java rename to server/src/main/java/ru/practicum/shareit/user/dto/UserDto.java diff --git a/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java b/server/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java rename to server/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java diff --git a/src/main/java/ru/practicum/shareit/user/model/User.java b/server/src/main/java/ru/practicum/shareit/user/model/User.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/model/User.java rename to server/src/main/java/ru/practicum/shareit/user/model/User.java diff --git a/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java b/server/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/repository/UserRepository.java rename to server/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java diff --git a/src/main/java/ru/practicum/shareit/user/service/UserService.java b/server/src/main/java/ru/practicum/shareit/user/service/UserService.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/service/UserService.java rename to server/src/main/java/ru/practicum/shareit/user/service/UserService.java diff --git a/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java b/server/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java rename to server/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java diff --git a/src/main/java/ru/practicum/shareit/user/validator/EmailValidator.java b/server/src/main/java/ru/practicum/shareit/user/validator/EmailValidator.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/validator/EmailValidator.java rename to server/src/main/java/ru/practicum/shareit/user/validator/EmailValidator.java diff --git a/src/main/resources/application.properties b/server/src/main/resources/application.properties similarity index 86% rename from src/main/resources/application.properties rename to server/src/main/resources/application.properties index 8ac3282..04ef538 100644 --- a/src/main/resources/application.properties +++ b/server/src/main/resources/application.properties @@ -1,3 +1,5 @@ +server.port=9090 + spring.jpa.hibernate.ddl-auto=none spring.jpa.properties.hibernate.format_sql=true spring.sql.init.mode=always @@ -8,14 +10,13 @@ logging.level.org.springframework.transaction.interceptor=TRACE logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG #--- +spring.datasource.driverClassName=org.postgresql.Driver spring.datasource.url=jdbc:postgresql://localhost:5432/shareit spring.datasource.username=postgres spring.datasource.password=12345 -spring.datasource.driverClassName=org.postgresql.Driver - #--- spring.config.activate.on-profile=test +spring.datasource.driverClassName=org.h2.Driver spring.datasource.url=jdbc:h2:mem:shareit spring.datasource.username=shareit -spring.datasource.password=shareit -spring.datasource.driverClassName=org.h2.Driver \ No newline at end of file +spring.datasource.password=shareit \ No newline at end of file diff --git a/src/main/resources/schema.sql b/server/src/main/resources/schema.sql similarity index 100% rename from src/main/resources/schema.sql rename to server/src/main/resources/schema.sql diff --git a/src/test/java/ru/practicum/shareit/ShareItTests.java b/server/src/test/java/ru/practicum/shareit/ShareItTests.java similarity index 100% rename from src/test/java/ru/practicum/shareit/ShareItTests.java rename to server/src/test/java/ru/practicum/shareit/ShareItTests.java diff --git a/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java b/server/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java similarity index 100% rename from src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java rename to server/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java diff --git a/src/test/java/ru/practicum/shareit/booking/BookingCreateDtoTest.java b/server/src/test/java/ru/practicum/shareit/booking/BookingCreateDtoTest.java similarity index 100% rename from src/test/java/ru/practicum/shareit/booking/BookingCreateDtoTest.java rename to server/src/test/java/ru/practicum/shareit/booking/BookingCreateDtoTest.java diff --git a/src/test/java/ru/practicum/shareit/booking/BookingRepositoryTest.java b/server/src/test/java/ru/practicum/shareit/booking/BookingRepositoryTest.java similarity index 100% rename from src/test/java/ru/practicum/shareit/booking/BookingRepositoryTest.java rename to server/src/test/java/ru/practicum/shareit/booking/BookingRepositoryTest.java diff --git a/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java b/server/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java similarity index 100% rename from src/test/java/ru/practicum/shareit/item/ItemControllerTest.java rename to server/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java diff --git a/src/test/java/ru/practicum/shareit/item/ItemDtoTest.java b/server/src/test/java/ru/practicum/shareit/item/ItemDtoTest.java similarity index 100% rename from src/test/java/ru/practicum/shareit/item/ItemDtoTest.java rename to server/src/test/java/ru/practicum/shareit/item/ItemDtoTest.java diff --git a/src/test/java/ru/practicum/shareit/item/ItemRepositoryTest.java b/server/src/test/java/ru/practicum/shareit/item/ItemRepositoryTest.java similarity index 100% rename from src/test/java/ru/practicum/shareit/item/ItemRepositoryTest.java rename to server/src/test/java/ru/practicum/shareit/item/ItemRepositoryTest.java diff --git a/src/test/java/ru/practicum/shareit/item/comment/CommentDtoTest.java b/server/src/test/java/ru/practicum/shareit/item/comment/CommentDtoTest.java similarity index 100% rename from src/test/java/ru/practicum/shareit/item/comment/CommentDtoTest.java rename to server/src/test/java/ru/practicum/shareit/item/comment/CommentDtoTest.java diff --git a/src/test/java/ru/practicum/shareit/item/comment/CommentRepositoryTest.java b/server/src/test/java/ru/practicum/shareit/item/comment/CommentRepositoryTest.java similarity index 100% rename from src/test/java/ru/practicum/shareit/item/comment/CommentRepositoryTest.java rename to server/src/test/java/ru/practicum/shareit/item/comment/CommentRepositoryTest.java diff --git a/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java b/server/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java similarity index 100% rename from src/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java rename to server/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java diff --git a/src/test/java/ru/practicum/shareit/request/ItemRequestDtoJsonTest.java b/server/src/test/java/ru/practicum/shareit/request/ItemRequestDtoJsonTest.java similarity index 100% rename from src/test/java/ru/practicum/shareit/request/ItemRequestDtoJsonTest.java rename to server/src/test/java/ru/practicum/shareit/request/ItemRequestDtoJsonTest.java diff --git a/src/test/java/ru/practicum/shareit/request/ItemRequestRepositoryIntegrationTest.java b/server/src/test/java/ru/practicum/shareit/request/ItemRequestRepositoryIntegrationTest.java similarity index 100% rename from src/test/java/ru/practicum/shareit/request/ItemRequestRepositoryIntegrationTest.java rename to server/src/test/java/ru/practicum/shareit/request/ItemRequestRepositoryIntegrationTest.java diff --git a/src/test/java/ru/practicum/shareit/user/UserControllerTest.java b/server/src/test/java/ru/practicum/shareit/user/UserControllerTest.java similarity index 100% rename from src/test/java/ru/practicum/shareit/user/UserControllerTest.java rename to server/src/test/java/ru/practicum/shareit/user/UserControllerTest.java diff --git a/src/test/java/ru/practicum/shareit/user/UserDtoTest.java b/server/src/test/java/ru/practicum/shareit/user/UserDtoTest.java similarity index 100% rename from src/test/java/ru/practicum/shareit/user/UserDtoTest.java rename to server/src/test/java/ru/practicum/shareit/user/UserDtoTest.java diff --git a/src/test/java/ru/practicum/shareit/user/UserRepositoryTest.java b/server/src/test/java/ru/practicum/shareit/user/UserRepositoryTest.java similarity index 100% rename from src/test/java/ru/practicum/shareit/user/UserRepositoryTest.java rename to server/src/test/java/ru/practicum/shareit/user/UserRepositoryTest.java From cb34de3bfac258f8a5ddb0c7b0860242db3fb9c3 Mon Sep 17 00:00:00 2001 From: Evgeniy Dmitriev Date: Sun, 29 Jun 2025 20:14:44 +0300 Subject: [PATCH 05/14] fix modules --- gateway/pom.xml | 7 + .../shareit/booking/BookingClient.java | 48 +++++++ .../shareit/booking/BookingController.java | 55 ++++++++ .../booking/dto/BookItemRequestDto.java | 20 +++ .../shareit/booking/dto/BookingState.java | 27 ++++ .../practicum/shareit/client/BaseClient.java | 121 ++++++++++++++++++ .../exception/AccessDeniedException.java | 11 ++ .../exception/BookingNotFoundException.java | 7 + .../exception/ItemNotFoundException.java | 11 ++ .../exception/RequestNotFoundException.java | 11 ++ .../exception/UserNotFoundException.java | 11 ++ .../exception/ValidationException.java | 7 + .../ru/practicum/shareit/item/ItemClient.java | 51 ++++++++ .../shareit/item/ItemController.java | 64 +++++++++ .../shareit/item/comment/CommentDto.java | 21 +++ .../practicum/shareit/item/dto/ItemDto.java | 25 ++++ .../shareit/request/ItemRequestClient.java | 45 +++++++ .../request/ItemRequestController.java | 49 +++++++ .../shareit/request/dto/ItemRequestDto.java | 20 +++ .../ru/practicum/shareit/user/UserClient.java | 48 +++++++ .../shareit/user/UserController.java | 68 ++++++++++ .../practicum/shareit/user/dto/UserDto.java | 23 ++++ server/pom.xml | 1 + .../shareit/booking/BookingController.java | 49 ------- .../shareit/item/ItemController.java | 53 -------- .../request/ItemRequestController.java | 40 ------ .../service/ItemRequestServiceImpl.java | 7 +- .../shareit/user/UserController.java | 41 ------ .../shareit/user/mapper/UserMapper.java | 6 + .../shareit/user/service/UserServiceImpl.java | 36 +----- 30 files changed, 765 insertions(+), 218 deletions(-) create mode 100644 gateway/src/main/java/ru/practicum/shareit/booking/BookingClient.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/booking/dto/BookItemRequestDto.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingState.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/client/BaseClient.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/exception/AccessDeniedException.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/exception/BookingNotFoundException.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/exception/ItemNotFoundException.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/exception/RequestNotFoundException.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/exception/UserNotFoundException.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/exception/ValidationException.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/item/ItemClient.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/item/ItemController.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/item/comment/CommentDto.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/request/ItemRequestClient.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/request/ItemRequestController.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/user/UserClient.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/user/UserController.java create mode 100644 gateway/src/main/java/ru/practicum/shareit/user/dto/UserDto.java delete mode 100644 server/src/main/java/ru/practicum/shareit/booking/BookingController.java delete mode 100644 server/src/main/java/ru/practicum/shareit/item/ItemController.java delete mode 100644 server/src/main/java/ru/practicum/shareit/request/ItemRequestController.java delete mode 100644 server/src/main/java/ru/practicum/shareit/user/UserController.java diff --git a/gateway/pom.xml b/gateway/pom.xml index f3394c1..3509b6c 100644 --- a/gateway/pom.xml +++ b/gateway/pom.xml @@ -12,6 +12,7 @@ 0.0.1-SNAPSHOT ShareIt Gateway + ShareIt Gateway Application @@ -56,6 +57,12 @@ spring-boot-starter-test test + + ru.practicum + shareit-server + 0.0.1-SNAPSHOT + compile + diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/BookingClient.java b/gateway/src/main/java/ru/practicum/shareit/booking/BookingClient.java new file mode 100644 index 0000000..916528c --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/BookingClient.java @@ -0,0 +1,48 @@ +package ru.practicum.shareit.booking; + +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.util.DefaultUriBuilderFactory; + +import ru.practicum.shareit.booking.dto.BookItemRequestDto; +import ru.practicum.shareit.booking.dto.BookingState; +import ru.practicum.shareit.client.BaseClient; + +@Service +public class BookingClient extends BaseClient { + private static final String API_PREFIX = "/bookings"; + + @Autowired + public BookingClient(@Value("${shareit-server.url}") String serverUrl, RestTemplateBuilder builder) { + super( + builder + .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl + API_PREFIX)) + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) + .build() + ); + } + + public ResponseEntity getBookings(long userId, BookingState state, Integer from, Integer size) { + Map parameters = Map.of( + "state", state.name(), + "from", from, + "size", size + ); + return get("?state={state}&from={from}&size={size}", userId, parameters); + } + + + public ResponseEntity bookItem(long userId, BookItemRequestDto requestDto) { + return post("", userId, requestDto); + } + + public ResponseEntity getBooking(long userId, Long bookingId) { + return get("/" + bookingId, userId); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java b/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java new file mode 100644 index 0000000..7b55542 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java @@ -0,0 +1,55 @@ +package ru.practicum.shareit.booking; + +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import ru.practicum.shareit.booking.dto.BookItemRequestDto; +import ru.practicum.shareit.booking.dto.BookingState; + + +@Controller +@RequestMapping(path = "/bookings") +@RequiredArgsConstructor +@Slf4j +@Validated +public class BookingController { + private final BookingClient bookingClient; + + @GetMapping + public ResponseEntity getBookings(@RequestHeader("X-Sharer-User-Id") long userId, + @RequestParam(name = "state", defaultValue = "all") String stateParam, + @PositiveOrZero @RequestParam(name = "from", defaultValue = "0") Integer from, + @Positive @RequestParam(name = "size", defaultValue = "10") Integer size) { + BookingState state = BookingState.from(stateParam) + .orElseThrow(() -> new IllegalArgumentException("Unknown state: " + stateParam)); + log.info("Get booking with state {}, userId={}, from={}, size={}", stateParam, userId, from, size); + return bookingClient.getBookings(userId, state, from, size); + } + + @PostMapping + public ResponseEntity bookItem(@RequestHeader("X-Sharer-User-Id") long userId, + @RequestBody @Valid BookItemRequestDto requestDto) { + log.info("Creating booking {}, userId={}", requestDto, userId); + return bookingClient.bookItem(userId, requestDto); + } + + @GetMapping("/{bookingId}") + public ResponseEntity getBooking(@RequestHeader("X-Sharer-User-Id") long userId, + @PathVariable Long bookingId) { + log.info("Get booking {}, userId={}", bookingId, userId); + return bookingClient.getBooking(userId, bookingId); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookItemRequestDto.java b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookItemRequestDto.java new file mode 100644 index 0000000..8596ab7 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookItemRequestDto.java @@ -0,0 +1,20 @@ +package ru.practicum.shareit.booking.dto; + +import java.time.LocalDateTime; + +import jakarta.validation.constraints.Future; +import jakarta.validation.constraints.FutureOrPresent; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class BookItemRequestDto { + private long itemId; + @FutureOrPresent + private LocalDateTime start; + @Future + private LocalDateTime end; +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingState.java b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingState.java new file mode 100644 index 0000000..943af85 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingState.java @@ -0,0 +1,27 @@ +package ru.practicum.shareit.booking.dto; + +import java.util.Optional; + +public enum BookingState { + // Все + ALL, + // Текущие + CURRENT, + // Будущие + FUTURE, + // Завершенные + PAST, + // Отклоненные + REJECTED, + // Ожидающие подтверждения + WAITING; + + public static Optional from(String stringState) { + for (BookingState state : values()) { + if (state.name().equalsIgnoreCase(stringState)) { + return Optional.of(state); + } + } + return Optional.empty(); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/client/BaseClient.java b/gateway/src/main/java/ru/practicum/shareit/client/BaseClient.java new file mode 100644 index 0000000..f9781be --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/client/BaseClient.java @@ -0,0 +1,121 @@ +package ru.practicum.shareit.client; + +import java.util.List; +import java.util.Map; + +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.lang.Nullable; +import org.springframework.web.client.HttpStatusCodeException; +import org.springframework.web.client.RestTemplate; + +public class BaseClient { + protected final RestTemplate rest; + + public BaseClient(RestTemplate rest) { + this.rest = rest; + } + + protected ResponseEntity get(String path) { + return get(path, null, null); + } + + protected ResponseEntity get(String path, long userId) { + return get(path, userId, null); + } + + protected ResponseEntity get(String path, Long userId, @Nullable Map parameters) { + return makeAndSendRequest(HttpMethod.GET, path, userId, parameters, null); + } + + protected ResponseEntity post(String path, T body) { + return post(path, null, null, body); + } + + protected ResponseEntity post(String path, long userId, T body) { + return post(path, userId, null, body); + } + + protected ResponseEntity post(String path, Long userId, @Nullable Map parameters, T body) { + return makeAndSendRequest(HttpMethod.POST, path, userId, parameters, body); + } + + protected ResponseEntity put(String path, long userId, T body) { + return put(path, userId, null, body); + } + + protected ResponseEntity put(String path, long userId, @Nullable Map parameters, T body) { + return makeAndSendRequest(HttpMethod.PUT, path, userId, parameters, body); + } + + protected ResponseEntity patch(String path, T body) { + return patch(path, null, null, body); + } + + protected ResponseEntity patch(String path, long userId) { + return patch(path, userId, null, null); + } + + protected ResponseEntity patch(String path, long userId, T body) { + return patch(path, userId, null, body); + } + + protected ResponseEntity patch(String path, Long userId, @Nullable Map parameters, T body) { + return makeAndSendRequest(HttpMethod.PATCH, path, userId, parameters, body); + } + + protected ResponseEntity delete(String path) { + return delete(path, null, null); + } + + protected ResponseEntity delete(String path, long userId) { + return delete(path, userId, null); + } + + protected ResponseEntity delete(String path, Long userId, @Nullable Map parameters) { + return makeAndSendRequest(HttpMethod.DELETE, path, userId, parameters, null); + } + + private ResponseEntity makeAndSendRequest(HttpMethod method, String path, Long userId, @Nullable Map parameters, @Nullable T body) { + HttpEntity requestEntity = new HttpEntity<>(body, defaultHeaders(userId)); + + ResponseEntity shareitServerResponse; + try { + if (parameters != null) { + shareitServerResponse = rest.exchange(path, method, requestEntity, Object.class, parameters); + } else { + shareitServerResponse = rest.exchange(path, method, requestEntity, Object.class); + } + } catch (HttpStatusCodeException e) { + return ResponseEntity.status(e.getStatusCode()).body(e.getResponseBodyAsByteArray()); + } + return prepareGatewayResponse(shareitServerResponse); + } + + private HttpHeaders defaultHeaders(Long userId) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setAccept(List.of(MediaType.APPLICATION_JSON)); + if (userId != null) { + headers.set("X-Sharer-User-Id", String.valueOf(userId)); + } + return headers; + } + + private static ResponseEntity prepareGatewayResponse(ResponseEntity response) { + if (response.getStatusCode().is2xxSuccessful()) { + return response; + } + + ResponseEntity.BodyBuilder responseBuilder = ResponseEntity.status(response.getStatusCode()); + + if (response.hasBody()) { + return responseBuilder.body(response.getBody()); + } + + return responseBuilder.build(); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/exception/AccessDeniedException.java b/gateway/src/main/java/ru/practicum/shareit/exception/AccessDeniedException.java new file mode 100644 index 0000000..d56c89b --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/exception/AccessDeniedException.java @@ -0,0 +1,11 @@ +package ru.practicum.shareit.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.FORBIDDEN) +public class AccessDeniedException extends RuntimeException { + public AccessDeniedException(String message) { + super(message); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/exception/BookingNotFoundException.java b/gateway/src/main/java/ru/practicum/shareit/exception/BookingNotFoundException.java new file mode 100644 index 0000000..cacf9f4 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/exception/BookingNotFoundException.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.exception; + +public class BookingNotFoundException extends RuntimeException { + public BookingNotFoundException(String message) { + super(message); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/exception/ItemNotFoundException.java b/gateway/src/main/java/ru/practicum/shareit/exception/ItemNotFoundException.java new file mode 100644 index 0000000..0c903a8 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/exception/ItemNotFoundException.java @@ -0,0 +1,11 @@ +package ru.practicum.shareit.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.NOT_FOUND) +public class ItemNotFoundException extends RuntimeException { + public ItemNotFoundException(String message) { + super(message); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/exception/RequestNotFoundException.java b/gateway/src/main/java/ru/practicum/shareit/exception/RequestNotFoundException.java new file mode 100644 index 0000000..f3e3fd2 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/exception/RequestNotFoundException.java @@ -0,0 +1,11 @@ +package ru.practicum.shareit.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.NOT_FOUND) +public class RequestNotFoundException extends RuntimeException { + public RequestNotFoundException(String message) { + super(message); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/exception/UserNotFoundException.java b/gateway/src/main/java/ru/practicum/shareit/exception/UserNotFoundException.java new file mode 100644 index 0000000..de91b98 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/exception/UserNotFoundException.java @@ -0,0 +1,11 @@ +package ru.practicum.shareit.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.NOT_FOUND) +public class UserNotFoundException extends RuntimeException { + public UserNotFoundException(String message) { + super(message); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/exception/ValidationException.java b/gateway/src/main/java/ru/practicum/shareit/exception/ValidationException.java new file mode 100644 index 0000000..59043da --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/exception/ValidationException.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.exception; + +public class ValidationException extends RuntimeException { + public ValidationException(String message) { + super(message); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/item/ItemClient.java b/gateway/src/main/java/ru/practicum/shareit/item/ItemClient.java new file mode 100644 index 0000000..24deb2e --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/ItemClient.java @@ -0,0 +1,51 @@ +package ru.practicum.shareit.item; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.util.DefaultUriBuilderFactory; +import ru.practicum.shareit.client.BaseClient; +import ru.practicum.shareit.item.comment.CommentDto; +import ru.practicum.shareit.item.dto.ItemDto; + +import java.util.Map; + +@Service +public class ItemClient extends BaseClient { + + private static final String API_PREFIX = "/items"; + + public ItemClient(RestTemplateBuilder builder, @Value("${shareit-server.url}") String serverUrl) { + super( + builder + .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl + API_PREFIX)) + .build() + ); + } + + public ResponseEntity createItem(Long userId, ItemDto dto) { + return post("", userId, dto); + } + + public ResponseEntity updateItem(Long userId, Long itemId, ItemDto dto) { + return patch("/" + itemId, userId, dto); + } + + public ResponseEntity getItem(Long itemId, Long userId) { + return get("/" + itemId, userId); + } + + public ResponseEntity getItems(Long userId) { + return get("", userId); + } + + public ResponseEntity searchItems(String text) { + Map parameters = Map.of("text", text); + return get("/search", null, parameters); + } + + public ResponseEntity addComment(Long userId, Long itemId, CommentDto dto) { + return post("/" + itemId + "/comment", userId, dto); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/item/ItemController.java b/gateway/src/main/java/ru/practicum/shareit/item/ItemController.java new file mode 100644 index 0000000..2dc0b3f --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -0,0 +1,64 @@ +package ru.practicum.shareit.item; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.exception.ValidationException; +import ru.practicum.shareit.item.comment.CommentDto; +import ru.practicum.shareit.item.dto.ItemDto; + +@RestController +@RequestMapping("/items") +@RequiredArgsConstructor +public class ItemController { + private final ItemClient itemClient; + + @PostMapping + public ResponseEntity createItem(@RequestHeader("X-Sharer-User-Id") Long userId, + @RequestBody @Valid ItemDto itemDto) { + if (itemDto.getName() == null || itemDto.getName().trim().isEmpty()) { + throw new ValidationException("Название предмета не может быть пустым"); + } + if (itemDto.getDescription() == null || itemDto.getDescription().trim().isEmpty()) { + throw new ValidationException("Описание предмета не может быть пустым"); + } + if (itemDto.getAvailable() == null) { + throw new ValidationException("Статус доступности не может быть null"); + } + return itemClient.createItem(userId, itemDto); + } + + @PatchMapping("/{itemId}") + public ResponseEntity updateItem(@RequestHeader("X-Sharer-User-Id") Long userId, + @PathVariable Long itemId, + @RequestBody ItemDto itemDto) { + return itemClient.updateItem(userId, itemId, itemDto); + } + + @GetMapping("/{itemId}") + public ResponseEntity getItem(@PathVariable Long itemId, + @RequestHeader("X-Sharer-User-Id") Long userId) { + return itemClient.getItem(itemId, userId); + } + + @GetMapping + public ResponseEntity getItems(@RequestHeader("X-Sharer-User-Id") Long userId) { + return itemClient.getItems(userId); + } + + @GetMapping("/search") + public ResponseEntity searchItems(@RequestParam String text) { + return itemClient.searchItems(text); + } + + @PostMapping("/{itemId}/comment") + public ResponseEntity addComment(@RequestHeader("X-Sharer-User-Id") Long userId, + @PathVariable Long itemId, + @RequestBody @Valid CommentDto commentDto) { + if (commentDto.getText() == null || commentDto.getText().trim().isEmpty()) { + throw new ValidationException("Комментарий не может быть пустым"); + } + return itemClient.addComment(userId, itemId, commentDto); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/item/comment/CommentDto.java b/gateway/src/main/java/ru/practicum/shareit/item/comment/CommentDto.java new file mode 100644 index 0000000..818fc78 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/comment/CommentDto.java @@ -0,0 +1,21 @@ +package ru.practicum.shareit.item.comment; + +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CommentDto { + private Long id; + + @NotBlank(message = "Комментарий не может быть пустым") + private String text; + + private String authorName; + private LocalDateTime created; +} diff --git a/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java b/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java new file mode 100644 index 0000000..800f3d0 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java @@ -0,0 +1,25 @@ +package ru.practicum.shareit.item.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ItemDto { + private Long id; + + @NotBlank(message = "Название предмета не может быть пустым") + private String name; + + @NotBlank(message = "Описание предмета не может быть пустым") + private String description; + + @NotNull(message = "Статус доступности не может быть null") + private Boolean available; + + private Long requestId; +} diff --git a/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestClient.java b/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestClient.java new file mode 100644 index 0000000..127aaa4 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestClient.java @@ -0,0 +1,45 @@ +package ru.practicum.shareit.request; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.util.DefaultUriBuilderFactory; +import ru.practicum.shareit.client.BaseClient; +import ru.practicum.shareit.request.dto.ItemRequestDto; + +import java.util.Map; + +@Service +public class ItemRequestClient extends BaseClient { + + private static final String API_PREFIX = "/requests"; + + public ItemRequestClient(RestTemplateBuilder builder, @Value("${shareit-server.url}") String serverUrl) { + super( + builder + .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl + API_PREFIX)) + .build() + ); + } + + public ResponseEntity createItemRequest(Long userId, ItemRequestDto dto) { + return post("", userId, dto); + } + + public ResponseEntity getUserRequests(Long userId) { + return get("", userId); + } + + public ResponseEntity getAllRequests(Long userId, int from, int size) { + Map parameters = Map.of( + "from", from, + "size", size + ); + return get("/all", userId, parameters); + } + + public ResponseEntity getRequestById(Long userId, Long requestId) { + return get("/" + requestId, userId); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestController.java b/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestController.java new file mode 100644 index 0000000..3b56477 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestController.java @@ -0,0 +1,49 @@ +package ru.practicum.shareit.request; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.exception.ValidationException; +import ru.practicum.shareit.request.dto.ItemRequestDto; + +@RestController +@RequestMapping(path = "/requests") +@RequiredArgsConstructor +public class ItemRequestController { + + private final ItemRequestClient itemRequestClient; + + @PostMapping + public ResponseEntity createItemRequest(@RequestHeader("X-Sharer-User-Id") Long userId, + @RequestBody @Valid ItemRequestDto itemRequestDto) { + if (itemRequestDto.getDescription() == null || itemRequestDto.getDescription().trim().isEmpty()) { + throw new ValidationException("Описание запроса не может быть пустым"); + } + return itemRequestClient.createItemRequest(userId, itemRequestDto); + } + + @GetMapping + public ResponseEntity getUserRequests(@RequestHeader("X-Sharer-User-Id") Long userId) { + return itemRequestClient.getUserRequests(userId); + } + + @GetMapping("/all") + public ResponseEntity getAllRequests(@RequestHeader("X-Sharer-User-Id") Long userId, + @RequestParam(defaultValue = "0") int from, + @RequestParam(defaultValue = "20") int size) { + if (from < 0) { + throw new ValidationException("Параметр from не может быть отрицательным"); + } + if (size <= 0) { + throw new ValidationException("Параметр size должен быть положительным"); + } + return itemRequestClient.getAllRequests(userId, from, size); + } + + @GetMapping("/{requestId}") + public ResponseEntity getRequestById(@RequestHeader("X-Sharer-User-Id") Long userId, + @PathVariable Long requestId) { + return itemRequestClient.getRequestById(userId, requestId); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java b/gateway/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java new file mode 100644 index 0000000..34b46ff --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java @@ -0,0 +1,20 @@ +package ru.practicum.shareit.request.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ItemRequestDto { + private Long id; + + @NotBlank(message = "Описание запроса не может быть пустым") + private String description; + + private LocalDateTime created; +} diff --git a/gateway/src/main/java/ru/practicum/shareit/user/UserClient.java b/gateway/src/main/java/ru/practicum/shareit/user/UserClient.java new file mode 100644 index 0000000..cb45361 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/user/UserClient.java @@ -0,0 +1,48 @@ +package ru.practicum.shareit.user; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.util.DefaultUriBuilderFactory; +import ru.practicum.shareit.client.BaseClient; +import ru.practicum.shareit.user.dto.UserDto; + +import java.util.Map; + +@Service +public class UserClient extends BaseClient { + private static final String API_PREFIX = "/users"; + + @Autowired + public UserClient(@Value("${shareit-server.url}") String serverUrl, RestTemplateBuilder builder) { + super( + builder + .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl + API_PREFIX)) + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) + .build() + ); + } + + public ResponseEntity createUser(UserDto userDto) { + return post("", userDto); + } + + public ResponseEntity updateUser(long userId, UserDto userDto) { + return patch("/" + userId, userDto); + } + + public ResponseEntity getUser(long userId) { + return get("/" + userId); + } + + public ResponseEntity getAllUsers() { + return get(""); + } + + public ResponseEntity deleteUser(long userId) { + return delete("/" + userId); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/user/UserController.java b/gateway/src/main/java/ru/practicum/shareit/user/UserController.java new file mode 100644 index 0000000..61b180f --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/user/UserController.java @@ -0,0 +1,68 @@ +package ru.practicum.shareit.user; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.user.dto.UserDto; + +@Controller +@RequestMapping(path = "/users") +@RequiredArgsConstructor +@Slf4j +@Validated +public class UserController { + + private final UserClient userClient; + + @PostMapping + public ResponseEntity createUser(@Valid @RequestBody UserDto userDto) { + log.info("Creating user: {}", userDto); + return userClient.createUser(userDto); + } + + @PatchMapping("/{userId}") + public ResponseEntity updateUser(@PathVariable Long userId, + @RequestBody UserDto userDto) { + log.info("Updating user {} with data: {}", userId, userDto); + validateUserForUpdate(userDto); + return userClient.updateUser(userId, userDto); + } + + @GetMapping("/{userId}") + public ResponseEntity getUser(@PathVariable Long userId) { + log.info("Getting user by id: {}", userId); + return userClient.getUser(userId); + } + + @GetMapping + public ResponseEntity getAllUsers() { + log.info("Getting all users"); + return userClient.getAllUsers(); + } + + @DeleteMapping("/{userId}") + public ResponseEntity deleteUser(@PathVariable Long userId) { + log.info("Deleting user by id: {}", userId); + return userClient.deleteUser(userId); + } + + private void validateUserForUpdate(UserDto userDto) { + if (userDto.getName() != null && userDto.getName().trim().isEmpty()) { + throw new IllegalArgumentException("Имя не может быть пустым"); + } + if (userDto.getEmail() != null && userDto.getEmail().trim().isEmpty()) { + throw new IllegalArgumentException("Email не может быть пустым"); + } + if (userDto.getEmail() != null && !isValidEmail(userDto.getEmail())) { + throw new IllegalArgumentException("Email имеет невалидный формат"); + } + } + + private boolean isValidEmail(String email) { + return email != null && email.matches("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/user/dto/UserDto.java b/gateway/src/main/java/ru/practicum/shareit/user/dto/UserDto.java new file mode 100644 index 0000000..9782e1b --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/user/dto/UserDto.java @@ -0,0 +1,23 @@ +package ru.practicum.shareit.user.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class UserDto { + private Long id; + + @NotBlank(message = "Имя не может быть пустым") + private String name; + + @NotNull(message = "Email не может быть null") + @NotBlank(message = "Email не может быть пустым") + @Email(message = "Email должен быть валидным") + private String email; +} diff --git a/server/pom.xml b/server/pom.xml index 5dd8414..172c6ed 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -12,6 +12,7 @@ 0.0.1-SNAPSHOT ShareIt Server + ShareIt Server Application diff --git a/server/src/main/java/ru/practicum/shareit/booking/BookingController.java b/server/src/main/java/ru/practicum/shareit/booking/BookingController.java deleted file mode 100644 index c3353c6..0000000 --- a/server/src/main/java/ru/practicum/shareit/booking/BookingController.java +++ /dev/null @@ -1,49 +0,0 @@ -package ru.practicum.shareit.booking; - -import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.*; -import ru.practicum.shareit.booking.dto.BookingCreateDto; -import ru.practicum.shareit.booking.dto.BookingDto; -import ru.practicum.shareit.booking.service.BookingService; -import ru.practicum.shareit.booking.status.BookingStatus; - -import java.util.List; - -@RestController -@RequestMapping(path = "/bookings") -@RequiredArgsConstructor -public class BookingController { - - private final BookingService bookingService; - - @PostMapping - public BookingDto createBooking(@RequestHeader("X-Sharer-User-Id") Long userId, - @RequestBody BookingCreateDto bookingDto) { - return bookingService.createBooking(userId, bookingDto); - } - - @PatchMapping("/{bookingId}") - public BookingDto updateBookingStatus(@RequestHeader("X-Sharer-User-Id") Long userId, - @PathVariable Long bookingId, - @RequestParam Boolean approved) { - return bookingService.updateBookingStatus(userId, bookingId, approved); - } - - @GetMapping("/{bookingId}") - public BookingDto getBooking(@RequestHeader("X-Sharer-User-Id") Long userId, - @PathVariable Long bookingId) { - return bookingService.getBookingById(userId, bookingId); - } - - @GetMapping - public List getBookings(@RequestHeader("X-Sharer-User-Id") Long userId, - @RequestParam(defaultValue = "ALL") BookingStatus status) { - return bookingService.getBookingsByUser(userId, status); - } - - @GetMapping("/owner") - public List getBookingsByOwner(@RequestHeader("X-Sharer-User-Id") Long userId, - @RequestParam(defaultValue = "ALL") BookingStatus status) { - return bookingService.getBookingsByOwner(userId, status); - } -} diff --git a/server/src/main/java/ru/practicum/shareit/item/ItemController.java b/server/src/main/java/ru/practicum/shareit/item/ItemController.java deleted file mode 100644 index 5bdbb35..0000000 --- a/server/src/main/java/ru/practicum/shareit/item/ItemController.java +++ /dev/null @@ -1,53 +0,0 @@ -package ru.practicum.shareit.item; - -import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.*; -import ru.practicum.shareit.item.comment.CommentDto; -import ru.practicum.shareit.item.dto.ItemDto; -import ru.practicum.shareit.item.dto.ItemWithBookingDto; -import ru.practicum.shareit.item.service.ItemService; - -import java.util.List; - -@RestController -@RequestMapping("/items") -@RequiredArgsConstructor -public class ItemController { - - private final ItemService itemService; - - @PostMapping - public ItemDto createItem(@RequestHeader("X-Sharer-User-Id") Long userId, - @RequestBody ItemDto itemDto) { - return itemService.createItem(userId, itemDto); - } - - @PatchMapping("/{itemId}") - public ItemDto updateItem(@RequestHeader("X-Sharer-User-Id") Long userId, - @PathVariable Long itemId, - @RequestBody ItemDto itemDto) { - return itemService.updateItem(userId, itemId, itemDto); - } - - @GetMapping("/{itemId}") - public ItemWithBookingDto getItem(@PathVariable Long itemId) { - return itemService.getItemById(itemId); - } - - @GetMapping - public List getItems(@RequestHeader("X-Sharer-User-Id") Long userId) { - return itemService.getItemsByOwner(userId); - } - - @GetMapping("/search") - public List searchItems(@RequestParam String text) { - return itemService.searchItems(text); - } - - @PostMapping("/{itemId}/comment") - public CommentDto addComment(@RequestHeader("X-Sharer-User-Id") Long userId, - @PathVariable Long itemId, - @RequestBody CommentDto commentDto) { - return itemService.addComment(userId, itemId, commentDto); - } -} diff --git a/server/src/main/java/ru/practicum/shareit/request/ItemRequestController.java b/server/src/main/java/ru/practicum/shareit/request/ItemRequestController.java deleted file mode 100644 index def75da..0000000 --- a/server/src/main/java/ru/practicum/shareit/request/ItemRequestController.java +++ /dev/null @@ -1,40 +0,0 @@ -package ru.practicum.shareit.request; - -import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.*; -import ru.practicum.shareit.request.dto.ItemRequestDto; -import ru.practicum.shareit.request.service.ItemRequestService; - -import java.util.List; - -@RestController -@RequestMapping(path = "/requests") -@RequiredArgsConstructor -public class ItemRequestController { - - private final ItemRequestService itemRequestService; - - @PostMapping - public ItemRequestDto createItemRequest(@RequestHeader("X-Sharer-User-Id") Long userId, - @RequestBody ItemRequestDto itemRequestDto) { - return itemRequestService.createItemRequest(userId, itemRequestDto); - } - - @GetMapping - public List getUserRequests(@RequestHeader("X-Sharer-User-Id") Long userId) { - return itemRequestService.getUserRequests(userId); - } - - @GetMapping("/all") - public List getAllRequests(@RequestHeader("X-Sharer-User-Id") Long userId, - @RequestParam(defaultValue = "0") int from, - @RequestParam(defaultValue = "20") int size) { - return itemRequestService.getAllRequests(userId, from, size); - } - - @GetMapping("/{requestId}") - public ItemRequestDto getRequestById(@RequestHeader("X-Sharer-User-Id") Long userId, - @PathVariable Long requestId) { - return itemRequestService.getRequestById(requestId, userId); - } -} diff --git a/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java b/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java index 604b642..a421732 100644 --- a/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java +++ b/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java @@ -1,12 +1,13 @@ package ru.practicum.shareit.request.service; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.practicum.shareit.exception.RequestNotFoundException; import ru.practicum.shareit.exception.UserNotFoundException; -import ru.practicum.shareit.exception.ValidationException; import ru.practicum.shareit.item.dto.ItemDto; import ru.practicum.shareit.item.mapper.ItemMapper; import ru.practicum.shareit.item.repository.ItemRepository; @@ -36,10 +37,6 @@ public ItemRequestDto createItemRequest(Long userId, ItemRequestDto itemRequestD User requester = userRepository.findById(userId) .orElseThrow(() -> new UserNotFoundException("Пользователь не найден")); - if (itemRequestDto.getDescription() == null || itemRequestDto.getDescription().trim().isEmpty()) { - throw new ValidationException("Описание запроса не может быть пустым"); - } - ItemRequest request = ItemRequestMapper.toItemRequest(itemRequestDto, requester); request.setRequester(requester); request.setCreated(LocalDateTime.now()); diff --git a/server/src/main/java/ru/practicum/shareit/user/UserController.java b/server/src/main/java/ru/practicum/shareit/user/UserController.java deleted file mode 100644 index c7bd799..0000000 --- a/server/src/main/java/ru/practicum/shareit/user/UserController.java +++ /dev/null @@ -1,41 +0,0 @@ -package ru.practicum.shareit.user; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; -import ru.practicum.shareit.user.dto.UserDto; -import ru.practicum.shareit.user.service.UserService; - -import java.util.List; - -@RestController -@RequestMapping(path = "/users") -public class UserController { - - @Autowired - private UserService userService; - - @PostMapping - public UserDto createUser(@RequestBody UserDto userDto) { - return userService.createUser(userDto); - } - - @PatchMapping("/{userId}") - public UserDto updateUser(@PathVariable Long userId, @RequestBody UserDto userDto) { - return userService.updateUser(userId, userDto); - } - - @GetMapping("/{userId}") - public UserDto getUser(@PathVariable Long userId) { - return userService.getUserById(userId); - } - - @GetMapping - public List getAllUsers() { - return userService.getAllUsers(); - } - - @DeleteMapping("/{userId}") - public void deleteUser(@PathVariable Long userId) { - userService.deleteUser(userId); - } -} diff --git a/server/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java b/server/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java index 14402b9..183f3cf 100644 --- a/server/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java +++ b/server/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java @@ -6,6 +6,9 @@ public class UserMapper { public static UserDto toUserDto(User user) { + if (user == null) { + return null; + } return new UserDto( user.getId(), user.getName(), @@ -14,6 +17,9 @@ public static UserDto toUserDto(User user) { } public static User toUser(UserDto userDto) { + if (userDto == null) { + return null; + } return new User( userDto.getId(), userDto.getName(), 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 index 0b937e8..c6a1420 100644 --- a/server/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java +++ b/server/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java @@ -7,9 +7,8 @@ import ru.practicum.shareit.user.mapper.UserMapper; import ru.practicum.shareit.user.model.User; import ru.practicum.shareit.user.repository.UserRepository; -import ru.practicum.shareit.user.validator.EmailValidator; -import java.util.*; +import java.util.List; import java.util.stream.Collectors; @Service @@ -22,8 +21,6 @@ public class UserServiceImpl implements UserService { @Override @Transactional public UserDto createUser(UserDto userDto) { - validateUserData(userDto, true); - if (userRepository.existsByEmail(userDto.getEmail())) { throw new IllegalArgumentException("Email уже существует"); } @@ -40,9 +37,7 @@ public UserDto updateUser(Long userId, UserDto userDto) { .orElseThrow(() -> new IllegalArgumentException("Пользователь не найден")); if (userDto.getEmail() != null && !userDto.getEmail().equals(existingUser.getEmail())) { - EmailValidator.validateEmail(userDto.getEmail()); String normalizedEmail = userDto.getEmail().trim(); - if (userRepository.existsByEmailAndIdNot(normalizedEmail, userId)) { throw new IllegalArgumentException("Пользователь с таким email уже существует"); } @@ -50,14 +45,11 @@ public UserDto updateUser(Long userId, UserDto userDto) { } if (userDto.getName() != null) { - if (userDto.getName().trim().isEmpty()) { - throw new IllegalArgumentException("Имя не может быть пустым"); - } existingUser.setName(userDto.getName().trim()); } User updatedUser = userRepository.save(existingUser); - return UserMapper.toUserDto(existingUser); + return UserMapper.toUserDto(updatedUser); } @Override @@ -77,25 +69,9 @@ public List getAllUsers() { @Override @Transactional public void deleteUser(Long userId) { - userRepository.deleteById(userId); - } - - private void validateUserData(UserDto userDto, boolean isCreation) { - if (userDto == null) { - throw new IllegalArgumentException("Пользователь не может быть null"); - } - - if (isCreation || userDto.getName() != null) { - if (userDto.getName() == null || userDto.getName().trim().isEmpty()) { - throw new IllegalArgumentException("Имя не может быть пустым или null"); - } - } - - if (isCreation || userDto.getEmail() != null) { - if (userDto.getEmail() == null || userDto.getEmail().trim().isEmpty()) { - throw new IllegalArgumentException("Email не может быть пустым или null"); - } - EmailValidator.validateEmail(userDto.getEmail()); + if (!userRepository.existsById(userId)) { + throw new IllegalArgumentException("Пользователь не найден"); } + userRepository.deleteById(userId); } -} +} \ No newline at end of file From a067940774a4c9da70a9ccdfa1c95c21536ed7a6 Mon Sep 17 00:00:00 2001 From: Evgeniy Dmitriev Date: Sun, 29 Jun 2025 20:23:53 +0300 Subject: [PATCH 06/14] fix checkstyle --- .../src/main/java/ru/practicum/shareit/user/UserClient.java | 2 -- .../shareit/request/service/ItemRequestServiceImpl.java | 2 -- .../ru/practicum/shareit/booking/BookingCreateDtoTest.java | 2 +- .../src/test/java/ru/practicum/shareit/item/ItemDtoTest.java | 4 ++-- .../ru/practicum/shareit/request/ItemRequestDtoJsonTest.java | 1 - .../src/test/java/ru/practicum/shareit/user/UserDtoTest.java | 4 ++-- 6 files changed, 5 insertions(+), 10 deletions(-) diff --git a/gateway/src/main/java/ru/practicum/shareit/user/UserClient.java b/gateway/src/main/java/ru/practicum/shareit/user/UserClient.java index cb45361..9516504 100644 --- a/gateway/src/main/java/ru/practicum/shareit/user/UserClient.java +++ b/gateway/src/main/java/ru/practicum/shareit/user/UserClient.java @@ -10,8 +10,6 @@ import ru.practicum.shareit.client.BaseClient; import ru.practicum.shareit.user.dto.UserDto; -import java.util.Map; - @Service public class UserClient extends BaseClient { private static final String API_PREFIX = "/users"; diff --git a/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java b/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java index a421732..c8c7e3c 100644 --- a/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java +++ b/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java @@ -1,8 +1,6 @@ package ru.practicum.shareit.request.service; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; diff --git a/server/src/test/java/ru/practicum/shareit/booking/BookingCreateDtoTest.java b/server/src/test/java/ru/practicum/shareit/booking/BookingCreateDtoTest.java index c8ba80c..0bd7b9d 100644 --- a/server/src/test/java/ru/practicum/shareit/booking/BookingCreateDtoTest.java +++ b/server/src/test/java/ru/practicum/shareit/booking/BookingCreateDtoTest.java @@ -24,7 +24,7 @@ void testSerialize() throws Exception { BookingCreateDto dto = new BookingCreateDto(start, end, 1L); String expectedJson = """ - { + { "start": "2024-01-01T12:00:00", "end": "2024-01-02T12:00:00", "itemId": 1 diff --git a/server/src/test/java/ru/practicum/shareit/item/ItemDtoTest.java b/server/src/test/java/ru/practicum/shareit/item/ItemDtoTest.java index 9e5a79b..34d5ded 100644 --- a/server/src/test/java/ru/practicum/shareit/item/ItemDtoTest.java +++ b/server/src/test/java/ru/practicum/shareit/item/ItemDtoTest.java @@ -20,13 +20,13 @@ void testSerializeDeserialize() throws Exception { ItemDto dto = new ItemDto(1L, "Name", "Description", true, null); String expectedJson = """ - { + { "id": 1, "name": "Name", "description": "Description", "available": true } - """; + """; assertThat(jsonJacksonTester.write(dto)) .isEqualToJson(expectedJson); diff --git a/server/src/test/java/ru/practicum/shareit/request/ItemRequestDtoJsonTest.java b/server/src/test/java/ru/practicum/shareit/request/ItemRequestDtoJsonTest.java index 475d911..9c7bc60 100644 --- a/server/src/test/java/ru/practicum/shareit/request/ItemRequestDtoJsonTest.java +++ b/server/src/test/java/ru/practicum/shareit/request/ItemRequestDtoJsonTest.java @@ -8,7 +8,6 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.regex.Pattern; import static org.assertj.core.api.Assertions.assertThat; diff --git a/server/src/test/java/ru/practicum/shareit/user/UserDtoTest.java b/server/src/test/java/ru/practicum/shareit/user/UserDtoTest.java index 21ae6e8..8f71da6 100644 --- a/server/src/test/java/ru/practicum/shareit/user/UserDtoTest.java +++ b/server/src/test/java/ru/practicum/shareit/user/UserDtoTest.java @@ -20,12 +20,12 @@ void serializeDeserializeTest() throws Exception { UserDto dto = new UserDto(1L, "John", "john@example.com"); String expectedJson = """ - { + { "id": 1, "name": "John", "email": "john@example.com" } - """; + """; assertThat(jsonJacksonTester.write(dto)) .isEqualToJson(expectedJson); From c9d605a7073889737ac8c83b530a65b7e7dec88d Mon Sep 17 00:00:00 2001 From: Evgeniy Dmitriev Date: Sun, 29 Jun 2025 20:32:10 +0300 Subject: [PATCH 07/14] fix checkstyle Leading braces --- .../shareit/booking/BookingCreateDtoTest.java | 6 ++---- .../ru/practicum/shareit/item/ItemDtoTest.java | 14 +++++++------- .../ru/practicum/shareit/user/UserDtoTest.java | 4 ++-- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/server/src/test/java/ru/practicum/shareit/booking/BookingCreateDtoTest.java b/server/src/test/java/ru/practicum/shareit/booking/BookingCreateDtoTest.java index 0bd7b9d..0127ac1 100644 --- a/server/src/test/java/ru/practicum/shareit/booking/BookingCreateDtoTest.java +++ b/server/src/test/java/ru/practicum/shareit/booking/BookingCreateDtoTest.java @@ -28,14 +28,12 @@ void testSerialize() throws Exception { "start": "2024-01-01T12:00:00", "end": "2024-01-02T12:00:00", "itemId": 1 - } - """; + } + """; - // Сериализация assertThat(jsonJacksonTester.write(dto)) .isEqualToJson(expectedJson); - // Десериализация assertThat(jsonJacksonTester.parse(expectedJson)) .isEqualTo(dto); } diff --git a/server/src/test/java/ru/practicum/shareit/item/ItemDtoTest.java b/server/src/test/java/ru/practicum/shareit/item/ItemDtoTest.java index 34d5ded..bb3ab79 100644 --- a/server/src/test/java/ru/practicum/shareit/item/ItemDtoTest.java +++ b/server/src/test/java/ru/practicum/shareit/item/ItemDtoTest.java @@ -20,13 +20,13 @@ void testSerializeDeserialize() throws Exception { ItemDto dto = new ItemDto(1L, "Name", "Description", true, null); String expectedJson = """ - { - "id": 1, - "name": "Name", - "description": "Description", - "available": true - } - """; + { + "id": 1, + "name": "Name", + "description": "Description", + "available": true + } + """; assertThat(jsonJacksonTester.write(dto)) .isEqualToJson(expectedJson); diff --git a/server/src/test/java/ru/practicum/shareit/user/UserDtoTest.java b/server/src/test/java/ru/practicum/shareit/user/UserDtoTest.java index 8f71da6..991d55e 100644 --- a/server/src/test/java/ru/practicum/shareit/user/UserDtoTest.java +++ b/server/src/test/java/ru/practicum/shareit/user/UserDtoTest.java @@ -24,8 +24,8 @@ void serializeDeserializeTest() throws Exception { "id": 1, "name": "John", "email": "john@example.com" - } - """; + } + """; assertThat(jsonJacksonTester.write(dto)) .isEqualToJson(expectedJson); From 0a700d1aba682dc1b422c7b61fc9cc015bb6e731 Mon Sep 17 00:00:00 2001 From: Evgeniy Dmitriev Date: Mon, 30 Jun 2025 08:45:52 +0300 Subject: [PATCH 08/14] fix checkstyle slashes --- .../java/ru/practicum/shareit/ShareItApp.java | 11 ++++ .../shareit/booking/BookingController.java | 51 ++++++++++++++++ .../booking/dto/BookItemRequestDto.java | 20 +++++++ .../shareit/item/ItemController.java | 60 +++++++++++++++++++ .../request/ItemRequestController.java | 45 ++++++++++++++ .../shareit/user/UserController.java | 41 +++++++++++++ .../shareit/booking/BookingCreateDtoTest.java | 8 +-- .../shareit/item/ItemControllerTest.java | 13 ---- .../practicum/shareit/item/ItemDtoTest.java | 9 +-- .../practicum/shareit/user/UserDtoTest.java | 8 +-- 10 files changed, 231 insertions(+), 35 deletions(-) create mode 100644 server/src/main/java/ru/practicum/shareit/ShareItApp.java create mode 100644 server/src/main/java/ru/practicum/shareit/booking/BookingController.java create mode 100644 server/src/main/java/ru/practicum/shareit/booking/dto/BookItemRequestDto.java create mode 100644 server/src/main/java/ru/practicum/shareit/item/ItemController.java create mode 100644 server/src/main/java/ru/practicum/shareit/request/ItemRequestController.java create mode 100644 server/src/main/java/ru/practicum/shareit/user/UserController.java diff --git a/server/src/main/java/ru/practicum/shareit/ShareItApp.java b/server/src/main/java/ru/practicum/shareit/ShareItApp.java new file mode 100644 index 0000000..4392304 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/ShareItApp.java @@ -0,0 +1,11 @@ +package ru.practicum.shareit; + +import org.springframework.boot.SpringApplication; + +public class ShareItApp { + + public static void main(String[] args) { + SpringApplication.run(ShareItApp.class, args); + } + +} 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 0000000..55b71a2 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/booking/BookingController.java @@ -0,0 +1,51 @@ +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.BookingCreateDto; +import ru.practicum.shareit.booking.service.BookingService; +import ru.practicum.shareit.booking.status.BookingStatus; + +import java.util.List; + +@RestController +@RequestMapping(path = "/bookings") +@RequiredArgsConstructor +public class BookingController { + + private final BookingService bookingService; + + @GetMapping + public List getBookings(@RequestHeader("X-Sharer-User-Id") long userId, + @RequestParam(name = "state", defaultValue = "ALL") String state) { + BookingStatus status = BookingStatus.valueOf(state.toUpperCase()); + return bookingService.getBookingsByUser(userId, status); + } + + @PostMapping + public BookingDto createBooking(@RequestHeader("X-Sharer-User-Id") long userId, + @RequestBody BookingCreateDto bookingDto) { + return bookingService.createBooking(userId, bookingDto); + } + + @GetMapping("/{bookingId}") + public BookingDto getBooking(@RequestHeader("X-Sharer-User-Id") long userId, + @PathVariable Long bookingId) { + return bookingService.getBookingById(userId, bookingId); + } + + @GetMapping("/owner") + public List getBookingsByOwner(@RequestHeader("X-Sharer-User-Id") long userId, + @RequestParam(name = "state", defaultValue = "ALL") String state) { + BookingStatus status = BookingStatus.valueOf(state.toUpperCase()); + return bookingService.getBookingsByOwner(userId, status); + } + + @PatchMapping("/{bookingId}") + public BookingDto updateBookingStatus(@RequestHeader("X-Sharer-User-Id") long userId, + @PathVariable Long bookingId, + @RequestParam Boolean approved) { + return bookingService.updateBookingStatus(userId, bookingId, approved); + } +} \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/booking/dto/BookItemRequestDto.java b/server/src/main/java/ru/practicum/shareit/booking/dto/BookItemRequestDto.java new file mode 100644 index 0000000..efe3980 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/booking/dto/BookItemRequestDto.java @@ -0,0 +1,20 @@ +package ru.practicum.shareit.booking.dto; + +import java.time.LocalDateTime; + +import jakarta.validation.constraints.Future; +import jakarta.validation.constraints.FutureOrPresent; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class BookItemRequestDto { + private long itemId; + @FutureOrPresent + private LocalDateTime start; + @Future + private LocalDateTime end; +} 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 0000000..fcd2ac3 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -0,0 +1,60 @@ +package ru.practicum.shareit.item; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.item.comment.CommentDto; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.dto.ItemWithBookingDto; +import ru.practicum.shareit.item.service.ItemService; + +import java.util.List; + +@RestController +@RequestMapping("/items") +@RequiredArgsConstructor +public class ItemController { + private final ItemService itemService; + + @PostMapping + public ResponseEntity createItem(@RequestHeader("X-Sharer-User-Id") Long userId, + @RequestBody ItemDto itemDto) { + ItemDto createdItem = itemService.createItem(userId, itemDto); + return ResponseEntity.ok(createdItem); + } + + @PatchMapping("/{itemId}") + public ResponseEntity updateItem(@RequestHeader("X-Sharer-User-Id") Long userId, + @PathVariable Long itemId, + @RequestBody ItemDto itemDto) { + ItemDto updatedItem = itemService.updateItem(userId, itemId, itemDto); + return ResponseEntity.ok(updatedItem); + } + + @GetMapping("/{itemId}") + public ResponseEntity getItem(@PathVariable Long itemId, + @RequestHeader("X-Sharer-User-Id") Long userId) { + ItemWithBookingDto item = itemService.getItemById(itemId); + return ResponseEntity.ok(item); + } + + @GetMapping + public ResponseEntity> getItems(@RequestHeader("X-Sharer-User-Id") Long userId) { + List items = itemService.getItemsByOwner(userId); + return ResponseEntity.ok(items); + } + + @GetMapping("/search") + public ResponseEntity> searchItems(@RequestParam String text) { + List items = itemService.searchItems(text); + return ResponseEntity.ok(items); + } + + @PostMapping("/{itemId}/comment") + public ResponseEntity addComment(@RequestHeader("X-Sharer-User-Id") Long userId, + @PathVariable Long itemId, + @RequestBody CommentDto commentDto) { + CommentDto createdComment = itemService.addComment(userId, itemId, commentDto); + return ResponseEntity.ok(createdComment); + } +} 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 0000000..2d7438e --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/ItemRequestController.java @@ -0,0 +1,45 @@ +package ru.practicum.shareit.request; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.service.ItemRequestService; + +import java.util.List; + +@RestController +@RequestMapping(path = "/requests") +@RequiredArgsConstructor +public class ItemRequestController { + + private final ItemRequestService itemRequestService; + + @PostMapping + public ResponseEntity createItemRequest(@RequestHeader("X-Sharer-User-Id") Long userId, + @RequestBody ItemRequestDto itemRequestDto) { + ItemRequestDto createdRequest = itemRequestService.createItemRequest(userId, itemRequestDto); + return ResponseEntity.ok(createdRequest); + } + + @GetMapping + public ResponseEntity> getUserRequests(@RequestHeader("X-Sharer-User-Id") Long userId) { + List requests = itemRequestService.getUserRequests(userId); + return ResponseEntity.ok(requests); + } + + @GetMapping("/all") + public ResponseEntity> getAllRequests(@RequestHeader("X-Sharer-User-Id") Long userId, + @RequestParam(defaultValue = "0") int from, + @RequestParam(defaultValue = "20") int size) { + List requests = itemRequestService.getAllRequests(userId, from, size); + return ResponseEntity.ok(requests); + } + + @GetMapping("/{requestId}") + public ResponseEntity getRequestById(@RequestHeader("X-Sharer-User-Id") Long userId, + @PathVariable Long requestId) { + ItemRequestDto request = itemRequestService.getRequestById(requestId, userId); + return ResponseEntity.ok(request); + } +} 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 0000000..359251e --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/user/UserController.java @@ -0,0 +1,41 @@ +package ru.practicum.shareit.user; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.service.UserService; + +import java.util.List; + +@RestController +@RequestMapping(path = "/users") +@RequiredArgsConstructor +public class UserController { + + private final UserService userService; + + @PostMapping + public UserDto createUser(@RequestBody UserDto userDto) { + return userService.createUser(userDto); + } + + @PatchMapping("/{userId}") + public UserDto updateUser(@PathVariable Long userId, @RequestBody UserDto userDto) { + return userService.updateUser(userId, userDto); + } + + @GetMapping("/{userId}") + public UserDto getUser(@PathVariable Long userId) { + return userService.getUserById(userId); + } + + @GetMapping + public List getAllUsers() { + return userService.getAllUsers(); + } + + @DeleteMapping("/{userId}") + public void deleteUser(@PathVariable Long userId) { + userService.deleteUser(userId); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/booking/BookingCreateDtoTest.java b/server/src/test/java/ru/practicum/shareit/booking/BookingCreateDtoTest.java index 0127ac1..87edc54 100644 --- a/server/src/test/java/ru/practicum/shareit/booking/BookingCreateDtoTest.java +++ b/server/src/test/java/ru/practicum/shareit/booking/BookingCreateDtoTest.java @@ -23,13 +23,7 @@ void testSerialize() throws Exception { LocalDateTime end = LocalDateTime.of(2024, 1, 2, 12, 0); BookingCreateDto dto = new BookingCreateDto(start, end, 1L); - String expectedJson = """ - { - "start": "2024-01-01T12:00:00", - "end": "2024-01-02T12:00:00", - "itemId": 1 - } - """; + String expectedJson = "{\"start\": \"2024-01-01T12:00:00\", \"end\": \"2024-01-02T12:00:00\", \"itemId\": 1}"; assertThat(jsonJacksonTester.write(dto)) .isEqualToJson(expectedJson); diff --git a/server/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java b/server/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java index c0d9522..9b8ea04 100644 --- a/server/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java +++ b/server/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java @@ -65,19 +65,6 @@ void updateItem() throws Exception { verify(itemService, times(1)).updateItem(1L, 1L, dto); } - @Test - void getItemById() throws Exception { - ItemWithBookingDto response = new ItemWithBookingDto( - 1L, "Name", "Desc", true, null, null, List.of() - ); - - when(itemService.getItemById(anyLong())).thenReturn(response); - - mockMvc.perform(get("/items/1")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.name").value("Name")); - } - @Test void getItemsByOwner() throws Exception { ItemWithBookingDto response = new ItemWithBookingDto( diff --git a/server/src/test/java/ru/practicum/shareit/item/ItemDtoTest.java b/server/src/test/java/ru/practicum/shareit/item/ItemDtoTest.java index bb3ab79..c6fc909 100644 --- a/server/src/test/java/ru/practicum/shareit/item/ItemDtoTest.java +++ b/server/src/test/java/ru/practicum/shareit/item/ItemDtoTest.java @@ -19,14 +19,7 @@ public class ItemDtoTest { void testSerializeDeserialize() throws Exception { ItemDto dto = new ItemDto(1L, "Name", "Description", true, null); - String expectedJson = """ - { - "id": 1, - "name": "Name", - "description": "Description", - "available": true - } - """; + String expectedJson = "{\"id\": 1, \"name\": \"Name\", \"description\": \"Description\", \"available\": true}"; assertThat(jsonJacksonTester.write(dto)) .isEqualToJson(expectedJson); diff --git a/server/src/test/java/ru/practicum/shareit/user/UserDtoTest.java b/server/src/test/java/ru/practicum/shareit/user/UserDtoTest.java index 991d55e..2dc60c2 100644 --- a/server/src/test/java/ru/practicum/shareit/user/UserDtoTest.java +++ b/server/src/test/java/ru/practicum/shareit/user/UserDtoTest.java @@ -19,13 +19,7 @@ public class UserDtoTest { void serializeDeserializeTest() throws Exception { UserDto dto = new UserDto(1L, "John", "john@example.com"); - String expectedJson = """ - { - "id": 1, - "name": "John", - "email": "john@example.com" - } - """; + String expectedJson = "{\"id\": 1, \"name\": \"John\", \"email\": \"john@example.com\"}"; assertThat(jsonJacksonTester.write(dto)) .isEqualToJson(expectedJson); From 20827524c105fe9ddadf253fd9dd4e0910559e55 Mon Sep 17 00:00:00 2001 From: Evgeniy Dmitriev Date: Mon, 30 Jun 2025 09:06:12 +0300 Subject: [PATCH 09/14] update tests --- pom.xml | 11 ----------- .../main/java/ru/practicum/shareit/ShareItApp.java | 11 ----------- 2 files changed, 22 deletions(-) delete mode 100644 server/src/main/java/ru/practicum/shareit/ShareItApp.java diff --git a/pom.xml b/pom.xml index 4f6792c..ebd7525 100644 --- a/pom.xml +++ b/pom.xml @@ -274,17 +274,6 @@ - - coverage - - - - org.jacoco - jacoco-maven-plugin - - - - diff --git a/server/src/main/java/ru/practicum/shareit/ShareItApp.java b/server/src/main/java/ru/practicum/shareit/ShareItApp.java deleted file mode 100644 index 4392304..0000000 --- a/server/src/main/java/ru/practicum/shareit/ShareItApp.java +++ /dev/null @@ -1,11 +0,0 @@ -package ru.practicum.shareit; - -import org.springframework.boot.SpringApplication; - -public class ShareItApp { - - public static void main(String[] args) { - SpringApplication.run(ShareItApp.class, args); - } - -} From 7b075091d3cd033f8abc5ccf0ca4e2f14c06c309 Mon Sep 17 00:00:00 2001 From: Evgeniy Dmitriev Date: Mon, 30 Jun 2025 15:41:16 +0300 Subject: [PATCH 10/14] add tests --- .../exception/AccessDeniedException.java | 11 - .../exception/BookingNotFoundException.java | 7 - .../exception/ItemNotFoundException.java | 11 - .../exception/RequestNotFoundException.java | 11 - .../exception/UserNotFoundException.java | 11 - .../exception/BookingNotFoundException.java | 4 + .../BookItemRequestDtoValidationTest.java | 61 ++++ .../booking/BookingControllerTest.java | 198 +++++++++-- .../shareit/booking/BookingCreateDtoTest.java | 34 -- .../shareit/booking/BookingDtoJsonTest.java | 157 +++++++++ .../shareit/booking/BookingMapperTest.java | 73 ++++ .../booking/BookingRepositoryTest.java | 197 +++++++++-- .../BookingServiceImplIntegrationTest.java | 239 +++++++++++++ .../shareit/item/ItemControllerTest.java | 313 +++++++++++++++--- .../item/ItemServiceIntegrationTest.java | 305 +++++++++++++++++ 15 files changed, 1453 insertions(+), 179 deletions(-) delete mode 100644 gateway/src/main/java/ru/practicum/shareit/exception/AccessDeniedException.java delete mode 100644 gateway/src/main/java/ru/practicum/shareit/exception/BookingNotFoundException.java delete mode 100644 gateway/src/main/java/ru/practicum/shareit/exception/ItemNotFoundException.java delete mode 100644 gateway/src/main/java/ru/practicum/shareit/exception/RequestNotFoundException.java delete mode 100644 gateway/src/main/java/ru/practicum/shareit/exception/UserNotFoundException.java create mode 100644 server/src/test/java/ru/practicum/shareit/booking/BookItemRequestDtoValidationTest.java delete mode 100644 server/src/test/java/ru/practicum/shareit/booking/BookingCreateDtoTest.java create mode 100644 server/src/test/java/ru/practicum/shareit/booking/BookingDtoJsonTest.java create mode 100644 server/src/test/java/ru/practicum/shareit/booking/BookingMapperTest.java create mode 100644 server/src/test/java/ru/practicum/shareit/booking/BookingServiceImplIntegrationTest.java create mode 100644 server/src/test/java/ru/practicum/shareit/item/ItemServiceIntegrationTest.java diff --git a/gateway/src/main/java/ru/practicum/shareit/exception/AccessDeniedException.java b/gateway/src/main/java/ru/practicum/shareit/exception/AccessDeniedException.java deleted file mode 100644 index d56c89b..0000000 --- a/gateway/src/main/java/ru/practicum/shareit/exception/AccessDeniedException.java +++ /dev/null @@ -1,11 +0,0 @@ -package ru.practicum.shareit.exception; - -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -@ResponseStatus(HttpStatus.FORBIDDEN) -public class AccessDeniedException extends RuntimeException { - public AccessDeniedException(String message) { - super(message); - } -} diff --git a/gateway/src/main/java/ru/practicum/shareit/exception/BookingNotFoundException.java b/gateway/src/main/java/ru/practicum/shareit/exception/BookingNotFoundException.java deleted file mode 100644 index cacf9f4..0000000 --- a/gateway/src/main/java/ru/practicum/shareit/exception/BookingNotFoundException.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.practicum.shareit.exception; - -public class BookingNotFoundException extends RuntimeException { - public BookingNotFoundException(String message) { - super(message); - } -} diff --git a/gateway/src/main/java/ru/practicum/shareit/exception/ItemNotFoundException.java b/gateway/src/main/java/ru/practicum/shareit/exception/ItemNotFoundException.java deleted file mode 100644 index 0c903a8..0000000 --- a/gateway/src/main/java/ru/practicum/shareit/exception/ItemNotFoundException.java +++ /dev/null @@ -1,11 +0,0 @@ -package ru.practicum.shareit.exception; - -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -@ResponseStatus(HttpStatus.NOT_FOUND) -public class ItemNotFoundException extends RuntimeException { - public ItemNotFoundException(String message) { - super(message); - } -} diff --git a/gateway/src/main/java/ru/practicum/shareit/exception/RequestNotFoundException.java b/gateway/src/main/java/ru/practicum/shareit/exception/RequestNotFoundException.java deleted file mode 100644 index f3e3fd2..0000000 --- a/gateway/src/main/java/ru/practicum/shareit/exception/RequestNotFoundException.java +++ /dev/null @@ -1,11 +0,0 @@ -package ru.practicum.shareit.exception; - -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -@ResponseStatus(HttpStatus.NOT_FOUND) -public class RequestNotFoundException extends RuntimeException { - public RequestNotFoundException(String message) { - super(message); - } -} diff --git a/gateway/src/main/java/ru/practicum/shareit/exception/UserNotFoundException.java b/gateway/src/main/java/ru/practicum/shareit/exception/UserNotFoundException.java deleted file mode 100644 index de91b98..0000000 --- a/gateway/src/main/java/ru/practicum/shareit/exception/UserNotFoundException.java +++ /dev/null @@ -1,11 +0,0 @@ -package ru.practicum.shareit.exception; - -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -@ResponseStatus(HttpStatus.NOT_FOUND) -public class UserNotFoundException extends RuntimeException { - public UserNotFoundException(String message) { - super(message); - } -} diff --git a/server/src/main/java/ru/practicum/shareit/exception/BookingNotFoundException.java b/server/src/main/java/ru/practicum/shareit/exception/BookingNotFoundException.java index cacf9f4..ea09db9 100644 --- a/server/src/main/java/ru/practicum/shareit/exception/BookingNotFoundException.java +++ b/server/src/main/java/ru/practicum/shareit/exception/BookingNotFoundException.java @@ -1,5 +1,9 @@ package ru.practicum.shareit.exception; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.NOT_FOUND) public class BookingNotFoundException extends RuntimeException { public BookingNotFoundException(String message) { super(message); diff --git a/server/src/test/java/ru/practicum/shareit/booking/BookItemRequestDtoValidationTest.java b/server/src/test/java/ru/practicum/shareit/booking/BookItemRequestDtoValidationTest.java new file mode 100644 index 0000000..fa280e3 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/booking/BookItemRequestDtoValidationTest.java @@ -0,0 +1,61 @@ +package ru.practicum.shareit.booking; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; +import ru.practicum.shareit.booking.dto.BookItemRequestDto; + +import java.time.LocalDateTime; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +class BookItemRequestDtoValidationTest { + + private Validator validator; + + @BeforeEach + void setUp() { + ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); + validator = factory.getValidator(); + } + + @Test + void validBookItemRequestDto_ShouldPassValidation() { + LocalDateTime start = LocalDateTime.now().plusHours(1); + LocalDateTime end = LocalDateTime.now().plusHours(2); + BookItemRequestDto dto = new BookItemRequestDto(1L, start, end); + + Set> violations = validator.validate(dto); + + assertThat(violations).isEmpty(); + } + + @Test + void startInPast_ShouldFailValidation() { + LocalDateTime start = LocalDateTime.now().minusHours(1); // прошедшее время + LocalDateTime end = LocalDateTime.now().plusHours(1); + BookItemRequestDto dto = new BookItemRequestDto(1L, start, end); + + Set> violations = validator.validate(dto); + + assertThat(violations).hasSize(1); + assertThat(violations.iterator().next().getPropertyPath().toString()).isEqualTo("start"); + } + + @Test + void endNotInFuture_ShouldFailValidation() { + LocalDateTime start = LocalDateTime.now().plusHours(1); + LocalDateTime end = LocalDateTime.now(); // текущее время, не в будущем + BookItemRequestDto dto = new BookItemRequestDto(1L, start, end); + + Set> violations = validator.validate(dto); + + assertThat(violations).hasSize(1); + assertThat(violations.iterator().next().getPropertyPath().toString()).isEqualTo("end"); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java b/server/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java index 3c8615b..04d73c4 100644 --- a/server/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java +++ b/server/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java @@ -1,30 +1,35 @@ package ru.practicum.shareit.booking; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; - import ru.practicum.shareit.booking.dto.BookingCreateDto; import ru.practicum.shareit.booking.dto.BookingDto; import ru.practicum.shareit.booking.service.BookingService; import ru.practicum.shareit.booking.status.BookingStatus; - -import static org.mockito.Mockito.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -import com.fasterxml.jackson.databind.ObjectMapper; +import ru.practicum.shareit.exception.AccessDeniedException; +import ru.practicum.shareit.exception.BookingNotFoundException; +import ru.practicum.shareit.exception.UserNotFoundException; import ru.practicum.shareit.item.dto.ItemDto; import ru.practicum.shareit.user.dto.UserDto; import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; -@WebMvcTest(controllers = BookingController.class) -public class BookingControllerTest { +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@WebMvcTest(BookingController.class) +class BookingControllerTest { @Autowired private MockMvc mockMvc; @@ -32,35 +37,168 @@ public class BookingControllerTest { @MockBean private BookingService bookingService; - private final ObjectMapper mapper = new ObjectMapper() - .registerModule(new JavaTimeModule()); + @Autowired + private ObjectMapper objectMapper; + + private static final String USER_ID_HEADER = "X-Sharer-User-Id"; + + @Test + void createBooking_ValidRequest_ShouldReturnCreatedBooking() throws Exception { + Long userId = 1L; + LocalDateTime start = LocalDateTime.now().plusHours(1); + LocalDateTime end = LocalDateTime.now().plusHours(2); + BookingCreateDto createDto = new BookingCreateDto(start, end, 1L); + + BookingDto responseDto = createBookingDto(1L, start, end, BookingStatus.WAITING); + + when(bookingService.createBooking(eq(userId), any(BookingCreateDto.class))) + .thenReturn(responseDto); + + mockMvc.perform(post("/bookings") + .header(USER_ID_HEADER, userId) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(createDto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(1))) + .andExpect(jsonPath("$.status", is("WAITING"))) + .andExpect(jsonPath("$.item.id", is(1))) + .andExpect(jsonPath("$.booker.id", is(1))); + } + + @Test + void getBooking_ValidRequest_ShouldReturnBooking() throws Exception { + Long userId = 1L; + Long bookingId = 1L; + BookingDto bookingDto = createBookingDto(bookingId, + LocalDateTime.now().plusHours(1), + LocalDateTime.now().plusHours(2), + BookingStatus.WAITING); + + when(bookingService.getBookingById(userId, bookingId)).thenReturn(bookingDto); + + mockMvc.perform(get("/bookings/{bookingId}", bookingId) + .header(USER_ID_HEADER, userId)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(1))) + .andExpect(jsonPath("$.status", is("WAITING"))); + } + + @Test + void getBooking_BookingNotFound_ShouldReturnNotFound() throws Exception { + Long userId = 1L; + Long bookingId = 999L; + + when(bookingService.getBookingById(userId, bookingId)) + .thenThrow(new BookingNotFoundException("Бронирование не найдено")); + + mockMvc.perform(get("/bookings/{bookingId}", bookingId) + .header(USER_ID_HEADER, userId)) + .andExpect(status().isNotFound()); + } @Test - void createBooking() throws Exception { - BookingCreateDto dto = new BookingCreateDto( - LocalDateTime.now().plusDays(1), - LocalDateTime.now().plusDays(2), - 1L + void getBookings_ValidRequest_ShouldReturnBookingsList() throws Exception { + Long userId = 1L; + List bookings = Arrays.asList( + createBookingDto(1L, LocalDateTime.now().plusHours(1), + LocalDateTime.now().plusHours(2), BookingStatus.WAITING), + createBookingDto(2L, LocalDateTime.now().plusHours(3), + LocalDateTime.now().plusHours(4), BookingStatus.APPROVED) ); - BookingDto responseDto = new BookingDto( - 1L, - dto.getStart(), - dto.getEnd(), - new ItemDto(1L, "Name", "Desc", true, null), - new UserDto(2L, "User", "user@mail.ru"), - BookingStatus.WAITING + when(bookingService.getBookingsByUser(userId, BookingStatus.ALL)).thenReturn(bookings); + + mockMvc.perform(get("/bookings") + .header(USER_ID_HEADER, userId) + .param("state", "ALL")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(2))) + .andExpect(jsonPath("$[0].id", is(1))) + .andExpect(jsonPath("$[1].id", is(2))); + } + + @Test + void getBookingsByOwner_ValidRequest_ShouldReturnBookingsList() throws Exception { + Long userId = 1L; + List bookings = Arrays.asList( + createBookingDto(1L, LocalDateTime.now().plusHours(1), + LocalDateTime.now().plusHours(2), BookingStatus.WAITING) ); - when(bookingService.createBooking(anyLong(), any())).thenReturn(responseDto); + when(bookingService.getBookingsByOwner(userId, BookingStatus.ALL)).thenReturn(bookings); + + mockMvc.perform(get("/bookings/owner") + .header(USER_ID_HEADER, userId) + .param("state", "ALL")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(1))) + .andExpect(jsonPath("$[0].id", is(1))); + } + + @Test + void updateBookingStatus_ValidApproval_ShouldReturnUpdatedBooking() throws Exception { + Long userId = 1L; + Long bookingId = 1L; + BookingDto updatedBooking = createBookingDto(bookingId, + LocalDateTime.now().plusHours(1), + LocalDateTime.now().plusHours(2), + BookingStatus.APPROVED); + + when(bookingService.updateBookingStatus(userId, bookingId, true)) + .thenReturn(updatedBooking); + + mockMvc.perform(patch("/bookings/{bookingId}", bookingId) + .header(USER_ID_HEADER, userId) + .param("approved", "true")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(1))) + .andExpect(jsonPath("$.status", is("APPROVED"))); + } + + @Test + void updateBookingStatus_AccessDenied_ShouldReturnForbidden() throws Exception { + Long userId = 1L; + Long bookingId = 1L; + + when(bookingService.updateBookingStatus(userId, bookingId, true)) + .thenThrow(new AccessDeniedException("Доступ запрещен")); + + mockMvc.perform(patch("/bookings/{bookingId}", bookingId) + .header(USER_ID_HEADER, userId) + .param("approved", "true")) + .andExpect(status().isForbidden()); + } + + @Test + void createBooking_UserNotFound_ShouldReturnNotFound() throws Exception { + Long userId = 999L; + BookingCreateDto createDto = new BookingCreateDto( + LocalDateTime.now().plusHours(1), + LocalDateTime.now().plusHours(2), + 1L); + + when(bookingService.createBooking(eq(userId), any(BookingCreateDto.class))) + .thenThrow(new UserNotFoundException("Пользователь не найден")); mockMvc.perform(post("/bookings") - .header("X-Sharer-User-Id", 1) + .header(USER_ID_HEADER, userId) .contentType(MediaType.APPLICATION_JSON) - .content(mapper.writeValueAsString(dto))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.id").value(1L)); + .content(objectMapper.writeValueAsString(createDto))) + .andExpect(status().isNotFound()); + } + + private BookingDto createBookingDto(Long id, LocalDateTime start, LocalDateTime end, BookingStatus status) { + UserDto booker = new UserDto(); + booker.setId(1L); + booker.setName("Booker"); + booker.setEmail("booker@test.com"); + + ItemDto item = new ItemDto(); + item.setId(1L); + item.setName("Test Item"); + item.setDescription("Test Description"); + item.setAvailable(true); - verify(bookingService, times(1)).createBooking(1L, dto); + return new BookingDto(id, start, end, item, booker, status); } } diff --git a/server/src/test/java/ru/practicum/shareit/booking/BookingCreateDtoTest.java b/server/src/test/java/ru/practicum/shareit/booking/BookingCreateDtoTest.java deleted file mode 100644 index 87edc54..0000000 --- a/server/src/test/java/ru/practicum/shareit/booking/BookingCreateDtoTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package ru.practicum.shareit.booking; - -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.json.JsonTest; -import org.springframework.boot.test.json.JacksonTester; - -import ru.practicum.shareit.booking.dto.BookingCreateDto; - -import java.time.LocalDateTime; - -import static org.assertj.core.api.Assertions.assertThat; - -@JsonTest -public class BookingCreateDtoTest { - - @Autowired - private JacksonTester jsonJacksonTester; - - @Test - void testSerialize() throws Exception { - LocalDateTime start = LocalDateTime.of(2024, 1, 1, 12, 0); - LocalDateTime end = LocalDateTime.of(2024, 1, 2, 12, 0); - BookingCreateDto dto = new BookingCreateDto(start, end, 1L); - - String expectedJson = "{\"start\": \"2024-01-01T12:00:00\", \"end\": \"2024-01-02T12:00:00\", \"itemId\": 1}"; - - assertThat(jsonJacksonTester.write(dto)) - .isEqualToJson(expectedJson); - - assertThat(jsonJacksonTester.parse(expectedJson)) - .isEqualTo(dto); - } -} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/booking/BookingDtoJsonTest.java b/server/src/test/java/ru/practicum/shareit/booking/BookingDtoJsonTest.java new file mode 100644 index 0000000..b59a8de --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/booking/BookingDtoJsonTest.java @@ -0,0 +1,157 @@ +package ru.practicum.shareit.booking; + +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 ru.practicum.shareit.booking.dto.BookItemRequestDto; +import ru.practicum.shareit.booking.dto.BookingCreateDto; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.status.BookingStatus; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.user.dto.UserDto; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +import static org.assertj.core.api.Assertions.assertThat; + +@JsonTest +class BookingDtoJsonTest { + + @Autowired + private JacksonTester json; + + @Autowired + private JacksonTester createJson; + + @Autowired + private JacksonTester requestJson; + + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME; + + @Test + void testBookingDtoSerialization() throws Exception { + LocalDateTime start = LocalDateTime.of(2024, 1, 1, 10, 0, 0); + LocalDateTime end = LocalDateTime.of(2024, 1, 1, 12, 0, 0); + + UserDto booker = new UserDto(); + booker.setId(1L); + booker.setName("John Doe"); + booker.setEmail("john@example.com"); + + ItemDto item = new ItemDto(); + item.setId(1L); + item.setName("Drill"); + item.setDescription("Power drill"); + item.setAvailable(true); + + BookingDto bookingDto = new BookingDto(1L, start, end, item, booker, BookingStatus.WAITING); + + JsonContent result = json.write(bookingDto); + + assertThat(result).extractingJsonPathNumberValue("$.id").isEqualTo(1); + assertThat(result).extractingJsonPathStringValue("$.start") + .isEqualTo(start.format(FORMATTER)); + assertThat(result).extractingJsonPathStringValue("$.end") + .isEqualTo(end.format(FORMATTER)); + assertThat(result).extractingJsonPathStringValue("$.status").isEqualTo("WAITING"); + assertThat(result).extractingJsonPathNumberValue("$.booker.id").isEqualTo(1); + assertThat(result).extractingJsonPathStringValue("$.booker.name").isEqualTo("John Doe"); + assertThat(result).extractingJsonPathNumberValue("$.item.id").isEqualTo(1); + assertThat(result).extractingJsonPathStringValue("$.item.name").isEqualTo("Drill"); + } + + @Test + void testBookingDtoDeserialization() throws Exception { + String jsonContent = "{" + + "\"id\":1," + + "\"start\":\"2024-01-01T10:00:00\"," + + "\"end\":\"2024-01-01T12:00:00\"," + + "\"item\":{" + + " \"id\":1," + + " \"name\":\"Drill\"," + + " \"description\":\"Power drill\"," + + " \"available\":true" + + "}," + + "\"booker\":{" + + " \"id\":1," + + " \"name\":\"John Doe\"," + + " \"email\":\"john@example.com\"" + + "}," + + "\"status\":\"WAITING\"" + + "}"; + + BookingDto result = json.parse(jsonContent).getObject(); + + assertThat(result.getId()).isEqualTo(1L); + assertThat(result.getStart()).isEqualTo(LocalDateTime.of(2024, 1, 1, 10, 0, 0)); + assertThat(result.getEnd()).isEqualTo(LocalDateTime.of(2024, 1, 1, 12, 0, 0)); + assertThat(result.getStatus()).isEqualTo(BookingStatus.WAITING); + assertThat(result.getBooker().getId()).isEqualTo(1L); + assertThat(result.getBooker().getName()).isEqualTo("John Doe"); + assertThat(result.getItem().getId()).isEqualTo(1L); + assertThat(result.getItem().getName()).isEqualTo("Drill"); + } + + @Test + void testBookingCreateDtoSerialization() throws Exception { + LocalDateTime start = LocalDateTime.of(2024, 1, 1, 10, 0, 0); + LocalDateTime end = LocalDateTime.of(2024, 1, 1, 12, 0, 0); + BookingCreateDto createDto = new BookingCreateDto(start, end, 1L); + + JsonContent result = createJson.write(createDto); + + assertThat(result).extractingJsonPathStringValue("$.start") + .isEqualTo(start.format(FORMATTER)); + assertThat(result).extractingJsonPathStringValue("$.end") + .isEqualTo(end.format(FORMATTER)); + assertThat(result).extractingJsonPathNumberValue("$.itemId").isEqualTo(1); + } + + @Test + void testBookingCreateDtoDeserialization() throws Exception { + String jsonContent = "{" + + "\"start\":\"2024-01-01T10:00:00\"," + + "\"end\":\"2024-01-01T12:00:00\"," + + "\"itemId\":1" + + "}"; + + BookingCreateDto result = createJson.parse(jsonContent).getObject(); + + assertThat(result.getStart()).isEqualTo(LocalDateTime.of(2024, 1, 1, 10, 0, 0)); + assertThat(result.getEnd()).isEqualTo(LocalDateTime.of(2024, 1, 1, 12, 0, 0)); + assertThat(result.getItemId()).isEqualTo(1L); + } + + @Test + void testBookItemRequestDtoValidation() throws Exception { + LocalDateTime start = LocalDateTime.of(2024, 1, 1, 10, 0, 0); + LocalDateTime end = LocalDateTime.of(2024, 1, 1, 12, 0, 0); + BookItemRequestDto requestDto = new BookItemRequestDto(1L, start, end); + + JsonContent result = requestJson.write(requestDto); + + assertThat(result).extractingJsonPathNumberValue("$.itemId").isEqualTo(1); + assertThat(result).extractingJsonPathStringValue("$.start") + .isEqualTo(start.format(FORMATTER)); + assertThat(result).extractingJsonPathStringValue("$.end") + .isEqualTo(end.format(FORMATTER)); + } + + @Test + void testBookItemRequestDtoDeserialization() throws Exception { + String jsonContent = "{" + + "\"itemId\":1," + + "\"start\":\"2024-01-01T10:00:00\"," + + "\"end\":\"2024-01-01T12:00:00\"" + + "}"; + + BookItemRequestDto result = requestJson.parse(jsonContent).getObject(); + + assertThat(result.getItemId()).isEqualTo(1L); + assertThat(result.getStart()).isEqualTo(LocalDateTime.of(2024, 1, 1, 10, 0, 0)); + assertThat(result.getEnd()).isEqualTo(LocalDateTime.of(2024, 1, 1, 12, 0, 0)); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/booking/BookingMapperTest.java b/server/src/test/java/ru/practicum/shareit/booking/BookingMapperTest.java new file mode 100644 index 0000000..07a0784 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/booking/BookingMapperTest.java @@ -0,0 +1,73 @@ +package ru.practicum.shareit.booking; + +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.mapper.BookingMapper; +import ru.practicum.shareit.booking.model.Booking; +import ru.practicum.shareit.booking.status.BookingStatus; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +class BookingMapperTest { + + @Test + void toBookingDto_ValidBooking_ShouldReturnCorrectDto() { + User owner = new User(); + owner.setId(1L); + owner.setName("Owner"); + owner.setEmail("owner@test.com"); + + User booker = new User(); + booker.setId(2L); + booker.setName("Booker"); + booker.setEmail("booker@test.com"); + + Item item = new Item(); + item.setId(1L); + item.setName("Test Item"); + item.setDescription("Test Description"); + item.setAvailable(true); + item.setOwner(owner); + + LocalDateTime start = LocalDateTime.of(2024, 1, 1, 10, 0); + LocalDateTime end = LocalDateTime.of(2024, 1, 1, 12, 0); + + Booking booking = new Booking(); + booking.setId(1L); + booking.setStart(start); + booking.setEnd(end); + booking.setItem(item); + booking.setBooker(booker); + booking.setStatus(BookingStatus.WAITING); + + BookingDto result = BookingMapper.toBookingDto(booking); + + assertThat(result).isNotNull(); + assertThat(result.getId()).isEqualTo(1L); + assertThat(result.getStart()).isEqualTo(start); + assertThat(result.getEnd()).isEqualTo(end); + assertThat(result.getStatus()).isEqualTo(BookingStatus.WAITING); + + assertThat(result.getItem()).isNotNull(); + assertThat(result.getItem().getId()).isEqualTo(1L); + assertThat(result.getItem().getName()).isEqualTo("Test Item"); + assertThat(result.getItem().getDescription()).isEqualTo("Test Description"); + assertThat(result.getItem().getAvailable()).isTrue(); + + assertThat(result.getBooker()).isNotNull(); + assertThat(result.getBooker().getId()).isEqualTo(2L); + assertThat(result.getBooker().getName()).isEqualTo("Booker"); + assertThat(result.getBooker().getEmail()).isEqualTo("booker@test.com"); + } + + @Test + void toBookingDto_NullBooking_ShouldThrowException() { + assertThatThrownBy(() -> BookingMapper.toBookingDto(null)) + .isInstanceOf(NullPointerException.class); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/booking/BookingRepositoryTest.java b/server/src/test/java/ru/practicum/shareit/booking/BookingRepositoryTest.java index 19ad2ec..30d4792 100644 --- a/server/src/test/java/ru/practicum/shareit/booking/BookingRepositoryTest.java +++ b/server/src/test/java/ru/practicum/shareit/booking/BookingRepositoryTest.java @@ -4,60 +4,205 @@ 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.boot.test.autoconfigure.orm.jpa.TestEntityManager; import org.springframework.data.domain.Sort; import ru.practicum.shareit.booking.model.Booking; import ru.practicum.shareit.booking.repository.BookingRepository; import ru.practicum.shareit.booking.status.BookingStatus; import ru.practicum.shareit.item.model.Item; -import ru.practicum.shareit.item.repository.ItemRepository; import ru.practicum.shareit.user.model.User; -import ru.practicum.shareit.user.repository.UserRepository; import java.time.LocalDateTime; import java.util.List; -import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; +import static org.assertj.core.api.Assertions.assertThat; @DataJpaTest -public class BookingRepositoryTest { +class BookingRepositoryTest { @Autowired - private BookingRepository bookingRepository; - - @Autowired - private UserRepository userRepository; + private TestEntityManager entityManager; @Autowired - private ItemRepository itemRepository; + private BookingRepository bookingRepository; private User owner; private User booker; private Item item; + private Booking pastBooking; + private Booking currentBooking; + private Booking futureBooking; @BeforeEach void setUp() { - owner = new User(null, "Owner", "owner@example.com"); - booker = new User(null, "Booker", "booker@example.com"); - owner = userRepository.save(owner); - booker = userRepository.save(booker); + owner = new User(); + owner.setName("Owner"); + owner.setEmail("owner@test.com"); + owner = entityManager.persistAndFlush(owner); + + booker = new User(); + booker.setName("Booker"); + booker.setEmail("booker@test.com"); + booker = entityManager.persistAndFlush(booker); + + item = new Item(); + item.setName("Test Item"); + item.setDescription("Test Description"); + item.setAvailable(true); + item.setOwner(owner); + item = entityManager.persistAndFlush(item); + + LocalDateTime now = LocalDateTime.now(); + + pastBooking = new Booking(); + pastBooking.setStart(now.minusHours(3)); + pastBooking.setEnd(now.minusHours(1)); + pastBooking.setItem(item); + pastBooking.setBooker(booker); + pastBooking.setStatus(BookingStatus.APPROVED); + pastBooking = entityManager.persistAndFlush(pastBooking); + + currentBooking = new Booking(); + currentBooking.setStart(now.minusHours(1)); + currentBooking.setEnd(now.plusHours(1)); + currentBooking.setItem(item); + currentBooking.setBooker(booker); + currentBooking.setStatus(BookingStatus.APPROVED); + currentBooking = entityManager.persistAndFlush(currentBooking); + + futureBooking = new Booking(); + futureBooking.setStart(now.plusHours(1)); + futureBooking.setEnd(now.plusHours(3)); + futureBooking.setItem(item); + futureBooking.setBooker(booker); + futureBooking.setStatus(BookingStatus.WAITING); + futureBooking = entityManager.persistAndFlush(futureBooking); + } + + @Test + void findByBookerId_ShouldReturnAllBookingsForBooker() { + List result = bookingRepository.findByBookerId(booker.getId(), + Sort.by(Sort.Direction.DESC, "start")); + + assertThat(result).hasSize(3); + assertThat(result).containsExactly(futureBooking, currentBooking, pastBooking); + } + + @Test + void findByBookerIdAndEndIsBefore_ShouldReturnPastBookings() { + LocalDateTime now = LocalDateTime.now(); + + List result = bookingRepository.findByBookerIdAndEndIsBefore(booker.getId(), now, + Sort.by(Sort.Direction.DESC, "start")); + + assertThat(result).hasSize(1); + assertThat(result).containsExactly(pastBooking); + } + + @Test + void findByBookerIdAndStartIsAfter_ShouldReturnFutureBookings() { + LocalDateTime now = LocalDateTime.now(); + + List result = bookingRepository.findByBookerIdAndStartIsAfter(booker.getId(), now, + Sort.by(Sort.Direction.DESC, "start")); + + assertThat(result).hasSize(1); + assertThat(result).containsExactly(futureBooking); + } + + @Test + void findByBookerIdAndCurrent_ShouldReturnCurrentBookings() { + LocalDateTime now = LocalDateTime.now(); + + List result = bookingRepository.findByBookerIdAndCurrent(booker.getId(), now, + Sort.by(Sort.Direction.DESC, "start")); + + assertThat(result).hasSize(1); + assertThat(result).containsExactly(currentBooking); + } + + @Test + void findByBookerIdAndStatus_ShouldReturnBookingsWithSpecificStatus() { + List waitingBookings = bookingRepository.findByBookerIdAndStatus(booker.getId(), + BookingStatus.WAITING, Sort.by(Sort.Direction.DESC, "start")); + List approvedBookings = bookingRepository.findByBookerIdAndStatus(booker.getId(), + BookingStatus.APPROVED, Sort.by(Sort.Direction.DESC, "start")); + + assertThat(waitingBookings).hasSize(1); + assertThat(waitingBookings).containsExactly(futureBooking); + + assertThat(approvedBookings).hasSize(2); + assertThat(approvedBookings).containsExactly(currentBooking, pastBooking); + } + + @Test + void findByItemOwnerId_ShouldReturnAllBookingsForOwner() { + List result = bookingRepository.findByItemOwnerId(owner.getId(), + Sort.by(Sort.Direction.DESC, "start")); + + assertThat(result).hasSize(3); + assertThat(result).containsExactly(futureBooking, currentBooking, pastBooking); + } + + @Test + void findByItemOwnerIdAndEndIsBefore_ShouldReturnPastBookingsForOwner() { + LocalDateTime now = LocalDateTime.now(); + + List result = bookingRepository.findByItemOwnerIdAndEndIsBefore(owner.getId(), now, + Sort.by(Sort.Direction.DESC, "start")); + + assertThat(result).hasSize(1); + assertThat(result).containsExactly(pastBooking); + } + + @Test + void findByItemOwnerIdAndStartIsAfter_ShouldReturnFutureBookingsForOwner() { + LocalDateTime now = LocalDateTime.now(); + + List result = bookingRepository.findByItemOwnerIdAndStartIsAfter(owner.getId(), now, + Sort.by(Sort.Direction.DESC, "start")); + + assertThat(result).hasSize(1); + assertThat(result).containsExactly(futureBooking); + } + + @Test + void findByItemOwnerIdAndCurrent_ShouldReturnCurrentBookingsForOwner() { + LocalDateTime now = LocalDateTime.now(); + + List result = bookingRepository.findByItemOwnerIdAndCurrent(owner.getId(), now, + Sort.by(Sort.Direction.DESC, "start")); + + assertThat(result).hasSize(1); + assertThat(result).containsExactly(currentBooking); + } + + @Test + void findByItemOwnerIdAndStatus_ShouldReturnBookingsWithSpecificStatusForOwner() { + List waitingBookings = bookingRepository.findByItemOwnerIdAndStatus(owner.getId(), + BookingStatus.WAITING, Sort.by(Sort.Direction.DESC, "start")); + + assertThat(waitingBookings).hasSize(1); + assertThat(waitingBookings).containsExactly(futureBooking); + } + + @Test + void findByItemIdInAndStatus_ShouldReturnBookingsForItemsWithStatus() { + List result = bookingRepository.findByItemIdInAndStatus( + List.of(item.getId()), BookingStatus.APPROVED); - item = new Item(null, "Item", "Description", true, owner, null); - item = itemRepository.save(item); + assertThat(result).hasSize(2); + assertThat(result).contains(pastBooking, currentBooking); } @Test - void testFindByBookerIdAndStatus() { - Booking booking = new Booking(); - booking.setStart(LocalDateTime.now().plusDays(1)); - booking.setEnd(LocalDateTime.now().plusDays(2)); - booking.setItem(item); - booking.setBooker(booker); - booking.setStatus(BookingStatus.WAITING); - bookingRepository.save(booking); + void findByBookerIdAndItemIdAndStatusAndEndBefore_ShouldReturnSpecificBookings() { + LocalDateTime now = LocalDateTime.now(); - List result = bookingRepository.findByBookerIdAndStatus(booker.getId(), BookingStatus.WAITING, Sort.by("id")); + List result = bookingRepository.findByBookerIdAndItemIdAndStatusAndEndBefore( + booker.getId(), item.getId(), BookingStatus.APPROVED, now); assertThat(result).hasSize(1); - assertThat(result.get(0)).isEqualTo(booking); + assertThat(result).containsExactly(pastBooking); } -} +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/booking/BookingServiceImplIntegrationTest.java b/server/src/test/java/ru/practicum/shareit/booking/BookingServiceImplIntegrationTest.java new file mode 100644 index 0000000..c71bd6e --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/booking/BookingServiceImplIntegrationTest.java @@ -0,0 +1,239 @@ +package ru.practicum.shareit.booking; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.TestPropertySource; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.shareit.booking.dto.BookingCreateDto; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.model.Booking; +import ru.practicum.shareit.booking.repository.BookingRepository; +import ru.practicum.shareit.booking.service.BookingService; +import ru.practicum.shareit.booking.status.BookingStatus; +import ru.practicum.shareit.exception.AccessDeniedException; +import ru.practicum.shareit.exception.ValidationException; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.repository.ItemRepository; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.repository.UserRepository; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@TestPropertySource(properties = "db.name=test") +@Transactional +class BookingServiceImplIntegrationTest { + + @Autowired + private BookingService bookingService; + + @Autowired + private BookingRepository bookingRepository; + + @Autowired + private UserRepository userRepository; + + @Autowired + private ItemRepository itemRepository; + + private User owner; + private User booker; + private Item item; + + @BeforeEach + void setUp() { + owner = new User(); + owner.setName("Owner"); + owner.setEmail("owner@test.com"); + owner = userRepository.save(owner); + + booker = new User(); + booker.setName("Booker"); + booker.setEmail("booker@test.com"); + booker = userRepository.save(booker); + + item = new Item(); + item.setName("Test Item"); + item.setDescription("Test Description"); + item.setAvailable(true); + item.setOwner(owner); + item = itemRepository.save(item); + } + + @Test + void createBooking_ValidData_ShouldCreateBooking() { + LocalDateTime start = LocalDateTime.now().plusHours(1); + LocalDateTime end = LocalDateTime.now().plusHours(2); + BookingCreateDto bookingDto = new BookingCreateDto(start, end, item.getId()); + + BookingDto result = bookingService.createBooking(booker.getId(), bookingDto); + + assertThat(result).isNotNull(); + assertThat(result.getId()).isNotNull(); + assertThat(result.getStart()).isEqualTo(start); + assertThat(result.getEnd()).isEqualTo(end); + assertThat(result.getStatus()).isEqualTo(BookingStatus.WAITING); + assertThat(result.getBooker().getId()).isEqualTo(booker.getId()); + assertThat(result.getItem().getId()).isEqualTo(item.getId()); + + Booking savedBooking = bookingRepository.findById(result.getId()).orElse(null); + assertThat(savedBooking).isNotNull(); + assertThat(savedBooking.getStatus()).isEqualTo(BookingStatus.WAITING); + } + + @Test + void createBooking_OwnerTriesToBookOwnItem_ShouldThrowException() { + LocalDateTime start = LocalDateTime.now().plusHours(1); + LocalDateTime end = LocalDateTime.now().plusHours(2); + BookingCreateDto bookingDto = new BookingCreateDto(start, end, item.getId()); + + assertThrows(AccessDeniedException.class, + () -> bookingService.createBooking(owner.getId(), bookingDto)); + } + + @Test + void createBooking_ItemNotAvailable_ShouldThrowException() { + item.setAvailable(false); + itemRepository.save(item); + + LocalDateTime start = LocalDateTime.now().plusHours(1); + LocalDateTime end = LocalDateTime.now().plusHours(2); + BookingCreateDto bookingDto = new BookingCreateDto(start, end, item.getId()); + + assertThrows(RuntimeException.class, + () -> bookingService.createBooking(booker.getId(), bookingDto)); + } + + @Test + void createBooking_InvalidDates_ShouldThrowException() { + LocalDateTime start = LocalDateTime.now().plusHours(2); + LocalDateTime end = LocalDateTime.now().plusHours(1); // end before start + BookingCreateDto bookingDto = new BookingCreateDto(start, end, item.getId()); + + assertThrows(ValidationException.class, + () -> bookingService.createBooking(booker.getId(), bookingDto)); + } + + @Test + void updateBookingStatus_ValidApproval_ShouldUpdateStatus() { + Booking booking = createTestBooking(); + booking = bookingRepository.save(booking); + + BookingDto result = bookingService.updateBookingStatus(owner.getId(), booking.getId(), true); + + assertThat(result.getStatus()).isEqualTo(BookingStatus.APPROVED); + + Booking updatedBooking = bookingRepository.findById(booking.getId()).orElse(null); + assertThat(updatedBooking.getStatus()).isEqualTo(BookingStatus.APPROVED); + } + + @Test + void updateBookingStatus_ValidRejection_ShouldUpdateStatus() { + Booking booking = createTestBooking(); + booking = bookingRepository.save(booking); + + BookingDto result = bookingService.updateBookingStatus(owner.getId(), booking.getId(), false); + + assertThat(result.getStatus()).isEqualTo(BookingStatus.REJECTED); + } + + @Test + void updateBookingStatus_NotOwner_ShouldThrowException() { + Booking booking = createTestBooking(); + booking = bookingRepository.save(booking); + + Booking finalBooking = booking; + assertThrows(AccessDeniedException.class, + () -> bookingService.updateBookingStatus(booker.getId(), finalBooking.getId(), true)); + } + + @Test + void getBookingById_ValidBookerAccess_ShouldReturnBooking() { + Booking booking = createTestBooking(); + booking = bookingRepository.save(booking); + + BookingDto result = bookingService.getBookingById(booker.getId(), booking.getId()); + + assertThat(result).isNotNull(); + assertThat(result.getId()).isEqualTo(booking.getId()); + } + + @Test + void getBookingById_ValidOwnerAccess_ShouldReturnBooking() { + Booking booking = createTestBooking(); + booking = bookingRepository.save(booking); + + BookingDto result = bookingService.getBookingById(owner.getId(), booking.getId()); + + assertThat(result).isNotNull(); + assertThat(result.getId()).isEqualTo(booking.getId()); + } + + @Test + void getBookingById_UnauthorizedAccess_ShouldThrowException() { + Booking booking = createTestBooking(); + booking = bookingRepository.save(booking); + + User anotherUser = new User(); + anotherUser.setName("Another"); + anotherUser.setEmail("another@test.com"); + anotherUser = userRepository.save(anotherUser); + + User finalAnotherUser = anotherUser; + Booking finalBooking = booking; + assertThrows(AccessDeniedException.class, + () -> bookingService.getBookingById(finalAnotherUser.getId(), finalBooking.getId())); + } + + @Test + void getBookingsByUser_AllStatus_ShouldReturnAllBookings() { + Booking booking1 = createTestBooking(); + Booking booking2 = createTestBooking(); + booking2.setStart(LocalDateTime.now().plusHours(3)); + booking2.setEnd(LocalDateTime.now().plusHours(4)); + + bookingRepository.save(booking1); + bookingRepository.save(booking2); + + List result = bookingService.getBookingsByUser(booker.getId(), BookingStatus.ALL); + + assertThat(result).hasSize(2); + } + + @Test + void getBookingsByUser_WaitingStatus_ShouldReturnWaitingBookings() { + Booking booking = createTestBooking(); + booking = bookingRepository.save(booking); + + List result = bookingService.getBookingsByUser(booker.getId(), BookingStatus.WAITING); + + assertThat(result).hasSize(1); + assertThat(result.get(0).getStatus()).isEqualTo(BookingStatus.WAITING); + } + + @Test + void getBookingsByOwner_AllStatus_ShouldReturnAllBookings() { + Booking booking = createTestBooking(); + bookingRepository.save(booking); + + List result = bookingService.getBookingsByOwner(owner.getId(), BookingStatus.ALL); + + assertThat(result).hasSize(1); + } + + private Booking createTestBooking() { + Booking booking = new Booking(); + booking.setStart(LocalDateTime.now().plusHours(1)); + booking.setEnd(LocalDateTime.now().plusHours(2)); + booking.setItem(item); + booking.setBooker(booker); + booking.setStatus(BookingStatus.WAITING); + return booking; + } +} diff --git a/server/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java b/server/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java index 9b8ea04..cd2332c 100644 --- a/server/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java +++ b/server/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java @@ -1,92 +1,329 @@ package ru.practicum.shareit.item; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; - +import ru.practicum.shareit.exception.AccessDeniedException; +import ru.practicum.shareit.exception.ItemNotFoundException; +import ru.practicum.shareit.exception.UserNotFoundException; +import ru.practicum.shareit.item.comment.CommentDto; import ru.practicum.shareit.item.dto.ItemDto; import ru.practicum.shareit.item.dto.ItemWithBookingDto; import ru.practicum.shareit.item.service.ItemService; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; - -import java.util.List; - @WebMvcTest(controllers = ItemController.class) -public class ItemControllerTest { +class ItemControllerTest { @Autowired private MockMvc mockMvc; + @Autowired + private ObjectMapper objectMapper; + @MockBean private ItemService itemService; - private final ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule()); + private ItemDto itemDto; + private ItemWithBookingDto itemWithBookingDto; + private CommentDto commentDto; - @Test - void createItem() throws Exception { - ItemDto dto = new ItemDto(null, "Name", "Desc", true, null); - ItemDto response = new ItemDto(1L, "Name", "Desc", true, null); + @BeforeEach + void setUp() { + itemDto = new ItemDto(); + itemDto.setId(1L); + itemDto.setName("Test Item"); + itemDto.setDescription("Test Description"); + itemDto.setAvailable(true); - when(itemService.createItem(anyLong(), any())).thenReturn(response); + itemWithBookingDto = new ItemWithBookingDto(); + itemWithBookingDto.setId(1L); + itemWithBookingDto.setName("Test Item"); + itemWithBookingDto.setDescription("Test Description"); + itemWithBookingDto.setAvailable(true); + itemWithBookingDto.setComments(Collections.emptyList()); + commentDto = new CommentDto(); + commentDto.setId(1L); + commentDto.setText("Great item!"); + commentDto.setAuthorName("John Doe"); + commentDto.setCreated(LocalDateTime.now()); + } + + @Test + void createItem_ShouldReturnCreatedItem() throws Exception { + // Given + when(itemService.createItem(eq(1L), any(ItemDto.class))).thenReturn(itemDto); + + // When & Then mockMvc.perform(post("/items") - .header("X-Sharer-User-Id", 1) + .header("X-Sharer-User-Id", 1L) .contentType(MediaType.APPLICATION_JSON) - .content(mapper.writeValueAsString(dto))) + .content(objectMapper.writeValueAsString(itemDto))) .andExpect(status().isOk()) - .andExpect(jsonPath("$.id").value(1L)); + .andExpect(jsonPath("$.id", is(1))) + .andExpect(jsonPath("$.name", is("Test Item"))) + .andExpect(jsonPath("$.description", is("Test Description"))) + .andExpect(jsonPath("$.available", is(true))); + + verify(itemService).createItem(eq(1L), any(ItemDto.class)); + } + + @Test + void createItem_WithoutUserHeader_ShouldReturnBadRequest() throws Exception { + // When & Then + mockMvc.perform(post("/items") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(itemDto))) + .andExpect(status().isBadRequest()); + + verify(itemService, never()).createItem(anyLong(), any(ItemDto.class)); + } + + @Test + void createItem_WithNonExistentUser_ShouldReturnNotFound() throws Exception { + // Given + when(itemService.createItem(eq(999L), any(ItemDto.class))) + .thenThrow(new UserNotFoundException("Собственник не найден")); + + // When & Then + mockMvc.perform(post("/items") + .header("X-Sharer-User-Id", 999L) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(itemDto))) + .andExpect(status().isNotFound()); - verify(itemService, times(1)).createItem(1L, dto); + verify(itemService).createItem(eq(999L), any(ItemDto.class)); } @Test - void updateItem() throws Exception { - ItemDto dto = new ItemDto(null, "Updated", "New Desc", false, null); - ItemDto response = new ItemDto(1L, "Updated", "New Desc", false, null); + void updateItem_ShouldReturnUpdatedItem() throws Exception { + // Given + ItemDto updateDto = new ItemDto(); + updateDto.setName("Updated Item"); + updateDto.setDescription("Updated Description"); + updateDto.setAvailable(false); - when(itemService.updateItem(anyLong(), anyLong(), any())).thenReturn(response); + ItemDto updatedItem = new ItemDto(); + updatedItem.setId(1L); + updatedItem.setName("Updated Item"); + updatedItem.setDescription("Updated Description"); + updatedItem.setAvailable(false); + when(itemService.updateItem(eq(1L), eq(1L), any(ItemDto.class))).thenReturn(updatedItem); + + // When & Then mockMvc.perform(patch("/items/1") - .header("X-Sharer-User-Id", 1) + .header("X-Sharer-User-Id", 1L) .contentType(MediaType.APPLICATION_JSON) - .content(mapper.writeValueAsString(dto))) + .content(objectMapper.writeValueAsString(updateDto))) .andExpect(status().isOk()) - .andExpect(jsonPath("$.description").value("New Desc")); + .andExpect(jsonPath("$.id", is(1))) + .andExpect(jsonPath("$.name", is("Updated Item"))) + .andExpect(jsonPath("$.description", is("Updated Description"))) + .andExpect(jsonPath("$.available", is(false))); + + verify(itemService).updateItem(eq(1L), eq(1L), any(ItemDto.class)); + } + + @Test + void updateItem_WithAccessDenied_ShouldReturnForbidden() throws Exception { + // Given + when(itemService.updateItem(eq(2L), eq(1L), any(ItemDto.class))) + .thenThrow(new AccessDeniedException("Только собственник может редактировать предмет")); - verify(itemService, times(1)).updateItem(1L, 1L, dto); + // When & Then + mockMvc.perform(patch("/items/1") + .header("X-Sharer-User-Id", 2L) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(itemDto))) + .andExpect(status().isForbidden()); + + verify(itemService).updateItem(eq(2L), eq(1L), any(ItemDto.class)); } @Test - void getItemsByOwner() throws Exception { - ItemWithBookingDto response = new ItemWithBookingDto( - 1L, "Name", "Desc", true, null, null, List.of() - ); + void updateItem_WithNonExistentItem_ShouldReturnNotFound() throws Exception { + // Given + when(itemService.updateItem(eq(1L), eq(999L), any(ItemDto.class))) + .thenThrow(new ItemNotFoundException("Предмет не найден")); - when(itemService.getItemsByOwner(anyLong())).thenReturn(List.of(response)); + // When & Then + mockMvc.perform(patch("/items/999") + .header("X-Sharer-User-Id", 1L) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(itemDto))) + .andExpect(status().isNotFound()); + + verify(itemService).updateItem(eq(1L), eq(999L), any(ItemDto.class)); + } + @Test + void getItem_ShouldReturnItem() throws Exception { + // Given + when(itemService.getItemById(1L)).thenReturn(itemWithBookingDto); + + // When & Then + mockMvc.perform(get("/items/1") + .header("X-Sharer-User-Id", 1L)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(1))) + .andExpect(jsonPath("$.name", is("Test Item"))) + .andExpect(jsonPath("$.description", is("Test Description"))) + .andExpect(jsonPath("$.available", is(true))) + .andExpect(jsonPath("$.comments", hasSize(0))); + + verify(itemService).getItemById(1L); + } + + @Test + void getItem_WithNonExistentItem_ShouldReturnNotFound() throws Exception { + // Given + when(itemService.getItemById(999L)).thenThrow(new ItemNotFoundException("Предмет не найден")); + + // When & Then + mockMvc.perform(get("/items/999") + .header("X-Sharer-User-Id", 1L)) + .andExpect(status().isNotFound()); + + verify(itemService).getItemById(999L); + } + + @Test + void getItems_ShouldReturnOwnerItems() throws Exception { + // Given + List items = Arrays.asList(itemWithBookingDto); + when(itemService.getItemsByOwner(1L)).thenReturn(items); + + // When & Then mockMvc.perform(get("/items") - .header("X-Sharer-User-Id", 1)) + .header("X-Sharer-User-Id", 1L)) .andExpect(status().isOk()) - .andExpect(jsonPath("$[0].name").value("Name")); + .andExpect(jsonPath("$", hasSize(1))) + .andExpect(jsonPath("$[0].id", is(1))) + .andExpect(jsonPath("$[0].name", is("Test Item"))); + + verify(itemService).getItemsByOwner(1L); + } + + @Test + void getItems_WithNonExistentUser_ShouldReturnNotFound() throws Exception { + // Given + when(itemService.getItemsByOwner(999L)) + .thenThrow(new UserNotFoundException("Собственник не найден")); + + // When & Then + mockMvc.perform(get("/items") + .header("X-Sharer-User-Id", 999L)) + .andExpect(status().isNotFound()); + + verify(itemService).getItemsByOwner(999L); } @Test - void searchItems() throws Exception { - ItemDto response = new ItemDto(1L, "Name", "Desc", true, null); + void searchItems_ShouldReturnMatchingItems() throws Exception { + // Given + List items = Arrays.asList(itemDto); + when(itemService.searchItems("test")).thenReturn(items); - when(itemService.searchItems(anyString())).thenReturn(List.of(response)); + // When & Then + mockMvc.perform(get("/items/search") + .param("text", "test")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(1))) + .andExpect(jsonPath("$[0].id", is(1))) + .andExpect(jsonPath("$[0].name", is("Test Item"))); - mockMvc.perform(get("/items/search?text=name")) + verify(itemService).searchItems("test"); + } + + @Test + void searchItems_WithEmptyText_ShouldReturnEmptyList() throws Exception { + // Given + when(itemService.searchItems("")).thenReturn(Collections.emptyList()); + + // When & Then + mockMvc.perform(get("/items/search") + .param("text", "")) .andExpect(status().isOk()) - .andExpect(jsonPath("$[0].name").value("Name")); + .andExpect(jsonPath("$", hasSize(0))); + + verify(itemService).searchItems(""); + } + + @Test + void addComment_ShouldReturnCreatedComment() throws Exception { + // Given + CommentDto inputComment = new CommentDto(); + inputComment.setText("Great item!"); + + when(itemService.addComment(eq(1L), eq(1L), any(CommentDto.class))).thenReturn(commentDto); + + // When & Then + mockMvc.perform(post("/items/1/comment") + .header("X-Sharer-User-Id", 1L) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(inputComment))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(1))) + .andExpect(jsonPath("$.text", is("Great item!"))) + .andExpect(jsonPath("$.authorName", is("John Doe"))) + .andExpect(jsonPath("$.created").exists()); + + verify(itemService).addComment(eq(1L), eq(1L), any(CommentDto.class)); + } + + @Test + void addComment_WithNonExistentItem_ShouldReturnNotFound() throws Exception { + // Given + CommentDto inputComment = new CommentDto(); + inputComment.setText("Great item!"); + + when(itemService.addComment(eq(1L), eq(999L), any(CommentDto.class))) + .thenThrow(new ItemNotFoundException("Предмет не найден")); + + // When & Then + mockMvc.perform(post("/items/999/comment") + .header("X-Sharer-User-Id", 1L) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(inputComment))) + .andExpect(status().isNotFound()); + + verify(itemService).addComment(eq(1L), eq(999L), any(CommentDto.class)); + } + + @Test + void addComment_WithNonExistentUser_ShouldReturnNotFound() throws Exception { + // Given + CommentDto inputComment = new CommentDto(); + inputComment.setText("Great item!"); + + when(itemService.addComment(eq(999L), eq(1L), any(CommentDto.class))) + .thenThrow(new UserNotFoundException("Пользователь не найден")); + + // When & Then + mockMvc.perform(post("/items/1/comment") + .header("X-Sharer-User-Id", 999L) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(inputComment))) + .andExpect(status().isNotFound()); + + verify(itemService).addComment(eq(999L), eq(1L), any(CommentDto.class)); } -} +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/item/ItemServiceIntegrationTest.java b/server/src/test/java/ru/practicum/shareit/item/ItemServiceIntegrationTest.java new file mode 100644 index 0000000..b334cde --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/item/ItemServiceIntegrationTest.java @@ -0,0 +1,305 @@ +package ru.practicum.shareit.item; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.TestPropertySource; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.shareit.booking.model.Booking; +import ru.practicum.shareit.booking.repository.BookingRepository; +import ru.practicum.shareit.booking.status.BookingStatus; +import ru.practicum.shareit.exception.ItemNotFoundException; +import ru.practicum.shareit.exception.UserNotFoundException; +import ru.practicum.shareit.exception.ValidationException; +import ru.practicum.shareit.item.comment.Comment; +import ru.practicum.shareit.item.comment.CommentDto; +import ru.practicum.shareit.item.comment.CommentRepository; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.dto.ItemWithBookingDto; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.repository.ItemRepository; +import ru.practicum.shareit.item.service.ItemService; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.request.repository.ItemRequestRepository; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.repository.UserRepository; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@SpringBootTest +@TestPropertySource(properties = {"db.name=test"}) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +@Transactional +class ItemServiceIntegrationTest { + + @Autowired + private ItemService itemService; + + @Autowired + private ItemRepository itemRepository; + + @Autowired + private UserRepository userRepository; + + @Autowired + private BookingRepository bookingRepository; + + @Autowired + private CommentRepository commentRepository; + + @Autowired + private ItemRequestRepository itemRequestRepository; + + private User owner; + private User booker; + private ItemRequest itemRequest; + + @BeforeEach + void setUp() { + owner = new User(); + owner.setName("Owner"); + owner.setEmail("owner@example.com"); + owner = userRepository.save(owner); + + booker = new User(); + booker.setName("Booker"); + booker.setEmail("booker@example.com"); + booker = userRepository.save(booker); + + itemRequest = new ItemRequest(); + itemRequest.setDescription("Test request"); + itemRequest.setRequester(booker); + itemRequest.setCreated(LocalDateTime.now()); + itemRequest = itemRequestRepository.save(itemRequest); + } + + @Test + void createItem_ShouldCreateItemSuccessfully() { + ItemDto itemDto = new ItemDto(); + itemDto.setName("Test Item"); + itemDto.setDescription("Test Description"); + itemDto.setAvailable(true); + + ItemDto result = itemService.createItem(owner.getId(), itemDto); + + assertThat(result).isNotNull(); + assertThat(result.getId()).isNotNull(); + assertThat(result.getName()).isEqualTo("Test Item"); + assertThat(result.getDescription()).isEqualTo("Test Description"); + assertThat(result.getAvailable()).isTrue(); + + Item savedItem = itemRepository.findById(result.getId()).orElse(null); + assertThat(savedItem).isNotNull(); + assertThat(savedItem.getOwner().getId()).isEqualTo(owner.getId()); + } + + @Test + void createItem_WithRequestId_ShouldCreateItemWithRequest() { + ItemDto itemDto = new ItemDto(); + itemDto.setName("Test Item"); + itemDto.setDescription("Test Description"); + itemDto.setAvailable(true); + itemDto.setRequestId(itemRequest.getId()); + + ItemDto result = itemService.createItem(owner.getId(), itemDto); + + assertThat(result.getRequestId()).isEqualTo(itemRequest.getId()); + + Item savedItem = itemRepository.findById(result.getId()).orElse(null); + assertThat(savedItem).isNotNull(); + assertThat(savedItem.getRequestId()).isEqualTo(itemRequest.getId()); + } + + @Test + void createItem_WithInvalidUser_ShouldThrowException() { + ItemDto itemDto = new ItemDto(); + itemDto.setName("Test Item"); + itemDto.setDescription("Test Description"); + itemDto.setAvailable(true); + + assertThatThrownBy(() -> itemService.createItem(999L, itemDto)) + .isInstanceOf(UserNotFoundException.class) + .hasMessage("Собственник не найден"); + } + + @Test + void createItem_WithInvalidData_ShouldThrowException() { + ItemDto itemDto = new ItemDto(); + itemDto.setName(""); + itemDto.setDescription("Test Description"); + itemDto.setAvailable(true); + + assertThatThrownBy(() -> itemService.createItem(owner.getId(), itemDto)) + .isInstanceOf(ValidationException.class) + .hasMessage("Название предмета не может быть пустым или null"); + } + + @Test + void updateItem_ShouldUpdateItemSuccessfully() { + Item item = createTestItem(); + + ItemDto updateDto = new ItemDto(); + updateDto.setName("Updated Name"); + updateDto.setDescription("Updated Description"); + updateDto.setAvailable(false); + + ItemDto result = itemService.updateItem(owner.getId(), item.getId(), updateDto); + + assertThat(result.getName()).isEqualTo("Updated Name"); + assertThat(result.getDescription()).isEqualTo("Updated Description"); + assertThat(result.getAvailable()).isFalse(); + + Item updatedItem = itemRepository.findById(item.getId()).orElse(null); + assertThat(updatedItem).isNotNull(); + assertThat(updatedItem.getName()).isEqualTo("Updated Name"); + } + + @Test + void getItemById_ShouldReturnItemWithDetails() { + Item item = createTestItem(); + Comment comment = createTestComment(item, booker); + + ItemWithBookingDto result = itemService.getItemById(item.getId()); + + assertThat(result).isNotNull(); + assertThat(result.getId()).isEqualTo(item.getId()); + assertThat(result.getName()).isEqualTo(item.getName()); + assertThat(result.getComments()).hasSize(1); + assertThat(result.getComments().get(0).getText()).isEqualTo(comment.getText()); + assertThat(result.getComments().get(0).getAuthorName()).isEqualTo(booker.getName()); + } + + @Test + void getItemById_WithNonExistentId_ShouldThrowException() { + assertThatThrownBy(() -> itemService.getItemById(999L)) + .isInstanceOf(ItemNotFoundException.class) + .hasMessage("Предмет не найден"); + } + + @Test + void getItemsByOwner_ShouldReturnItemsWithBookings() { + Item item = createTestItem(); + Booking pastBooking = createTestBooking(item, booker, + LocalDateTime.now().minusDays(2), LocalDateTime.now().minusDays(1)); + Booking futureBooking = createTestBooking(item, booker, + LocalDateTime.now().plusDays(1), LocalDateTime.now().plusDays(2)); + + List result = itemService.getItemsByOwner(owner.getId()); + + assertThat(result).hasSize(1); + ItemWithBookingDto itemDto = result.get(0); + assertThat(itemDto.getId()).isEqualTo(item.getId()); + assertThat(itemDto.getLastBooking()).isNotNull(); + assertThat(itemDto.getLastBooking().getId()).isEqualTo(pastBooking.getId()); + assertThat(itemDto.getNextBooking()).isNotNull(); + assertThat(itemDto.getNextBooking().getId()).isEqualTo(futureBooking.getId()); + } + + @Test + void searchItems_ShouldReturnMatchingItems() { + createTestItemWithName("Bicycle"); + createTestItemWithName("Car"); + createTestItemWithDescription(); + + List result = itemService.searchItems("bicycle"); + + assertThat(result).hasSize(2); + assertThat(result).extracting(ItemDto::getName) + .containsExactlyInAnyOrder("Bicycle", "Another Item"); + } + + @Test + void searchItems_WithEmptyText_ShouldReturnEmptyList() { + createTestItem(); + + List result = itemService.searchItems(""); + + assertThat(result).isEmpty(); + } + + @Test + void addComment_ShouldCreateCommentSuccessfully() { + Item item = createTestItem(); + Booking completedBooking = createTestBooking(item, booker, + LocalDateTime.now().minusDays(2), LocalDateTime.now().minusDays(1)); + + CommentDto commentDto = new CommentDto(); + commentDto.setText("Great item!"); + + CommentDto result = itemService.addComment(booker.getId(), item.getId(), commentDto); + + assertThat(result).isNotNull(); + assertThat(result.getId()).isNotNull(); + assertThat(result.getText()).isEqualTo("Great item!"); + assertThat(result.getAuthorName()).isEqualTo(booker.getName()); + assertThat(result.getCreated()).isNotNull(); + + Comment savedComment = commentRepository.findById(result.getId()).orElse(null); + assertThat(savedComment).isNotNull(); + assertThat(savedComment.getAuthor().getId()).isEqualTo(booker.getId()); + assertThat(savedComment.getItem().getId()).isEqualTo(item.getId()); + } + + @Test + void addComment_WithoutBooking_ShouldThrowException() { + Item item = createTestItem(); + CommentDto commentDto = new CommentDto(); + commentDto.setText("Great item!"); + + assertThatThrownBy(() -> itemService.addComment(booker.getId(), item.getId(), commentDto)) + .isInstanceOf(RuntimeException.class) + .hasMessage("Вы можете комментировать только те предметы, которые бронировали"); + } + + private Item createTestItem() { + Item item = new Item(); + item.setName("Test Item"); + item.setDescription("Test Description"); + item.setAvailable(true); + item.setOwner(owner); + return itemRepository.save(item); + } + + private void createTestItemWithName(String name) { + Item item = new Item(); + item.setName(name); + item.setDescription("Test Description"); + item.setAvailable(true); + item.setOwner(owner); + itemRepository.save(item); + } + + private void createTestItemWithDescription() { + Item item = new Item(); + item.setName("Another Item"); + item.setDescription("A nice bicycle for rent"); + item.setAvailable(true); + item.setOwner(owner); + itemRepository.save(item); + } + + private Comment createTestComment(Item item, User author) { + Comment comment = new Comment(); + comment.setText("Test comment"); + comment.setItem(item); + comment.setAuthor(author); + comment.setCreated(LocalDateTime.now()); + return commentRepository.save(comment); + } + + private Booking createTestBooking(Item item, User booker, LocalDateTime start, LocalDateTime end) { + Booking booking = new Booking(); + booking.setStart(start); + booking.setEnd(end); + booking.setItem(item); + booking.setBooker(booker); + booking.setStatus(BookingStatus.APPROVED); + return bookingRepository.save(booking); + } +} From 892f63fc5defd425119ce2c41c5eac1c076eccef Mon Sep 17 00:00:00 2001 From: Evgeniy Dmitriev Date: Tue, 1 Jul 2025 10:50:27 +0300 Subject: [PATCH 11/14] add service tests --- .../shareit/item/ItemController.java | 1 + server/pom.xml | 25 ++ .../shareit/item/service/ItemServiceImpl.java | 2 +- .../request/mapper/ItemRequestMapper.java | 2 +- .../shareit/user/mapper/UserMapper.java | 8 +- .../user/validator/EmailValidator.java | 6 +- .../BookingServiceImplIntegrationTest.java | 155 ++++++++++ .../item/ItemServiceIntegrationTest.java | 123 ++++++++ .../request/ItemRequestMapperTest.java | 84 ++++++ ...ItemRequestServiceImplIntegrationTest.java | 265 ++++++++++++++++++ .../shareit/user/EmailValidatorTest.java | 222 +++++++++++++++ .../practicum/shareit/user/UserDtoTest.java | 144 ++++++++++ .../user/UserServiceImplIntegrationTest.java | 260 +++++++++++++++++ 13 files changed, 1287 insertions(+), 10 deletions(-) create mode 100644 server/src/test/java/ru/practicum/shareit/request/ItemRequestMapperTest.java create mode 100644 server/src/test/java/ru/practicum/shareit/request/ItemRequestServiceImplIntegrationTest.java create mode 100644 server/src/test/java/ru/practicum/shareit/user/EmailValidatorTest.java create mode 100644 server/src/test/java/ru/practicum/shareit/user/UserServiceImplIntegrationTest.java diff --git a/gateway/src/main/java/ru/practicum/shareit/item/ItemController.java b/gateway/src/main/java/ru/practicum/shareit/item/ItemController.java index 2dc0b3f..a16cfa2 100644 --- a/gateway/src/main/java/ru/practicum/shareit/item/ItemController.java +++ b/gateway/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -33,6 +33,7 @@ public ResponseEntity createItem(@RequestHeader("X-Sharer-User-Id") Long public ResponseEntity updateItem(@RequestHeader("X-Sharer-User-Id") Long userId, @PathVariable Long itemId, @RequestBody ItemDto itemDto) { + return itemClient.updateItem(userId, itemId, itemDto); } diff --git a/server/pom.xml b/server/pom.xml index 172c6ed..230a78a 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -67,9 +67,34 @@ org.springframework.boot spring-boot-maven-plugin + + + + org.jacoco + jacoco-maven-plugin + 0.8.12 + + + + prepare-agent + + prepare-agent + + + + + report + prepare-package + + report + + + + + coverage 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 index 4df3302..0815c7e 100644 --- a/server/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java +++ b/server/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java @@ -51,7 +51,7 @@ public ItemDto createItem(Long userId, ItemDto itemDto) { if (itemDto.getRequestId() != null) { ItemRequest request = itemRequestRepository.findById(itemDto.getRequestId()) .orElseThrow(() -> new RequestNotFoundException( - "Запрос с ID " + itemDto.getRequestId() + " не найден")); + "Запрос не найден")); } Item item = ItemMapper.toItem(itemDto); diff --git a/server/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java b/server/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java index 5386554..35d3ee6 100644 --- a/server/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java +++ b/server/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java @@ -35,6 +35,6 @@ public static ItemRequestDto toItemRequestDto(ItemRequest itemRequest, List bookingService.updateBookingStatus(owner.getId(), finalBooking.getId(), false)); + } + + @Test + void updateBookingStatus_AlreadyRejected_ShouldThrowException() { + Booking booking = createTestBooking(); + booking.setStatus(BookingStatus.REJECTED); + booking = bookingRepository.save(booking); + + Booking finalBooking = booking; + assertThrows(AccessDeniedException.class, + () -> bookingService.updateBookingStatus(owner.getId(), finalBooking.getId(), true)); + } + + @Test + void getBookingsByUser_CurrentStatus_ShouldReturnCurrentBookings() { + Booking currentBooking = new Booking(); + currentBooking.setStart(LocalDateTime.now().minusHours(1)); // started 1 hour ago + currentBooking.setEnd(LocalDateTime.now().plusHours(1)); // ends in 1 hour + currentBooking.setItem(item); + currentBooking.setBooker(booker); + currentBooking.setStatus(BookingStatus.APPROVED); + bookingRepository.save(currentBooking); + + List result = bookingService.getBookingsByUser(booker.getId(), BookingStatus.CURRENT); + + assertThat(result).hasSize(1); + assertThat(result.get(0).getStart()).isBefore(LocalDateTime.now()); + assertThat(result.get(0).getEnd()).isAfter(LocalDateTime.now()); + } + + @Test + void getBookingsByUser_PastStatus_ShouldReturnPastBookings() { + Booking pastBooking = new Booking(); + pastBooking.setStart(LocalDateTime.now().minusHours(3)); + pastBooking.setEnd(LocalDateTime.now().minusHours(1)); + pastBooking.setItem(item); + pastBooking.setBooker(booker); + pastBooking.setStatus(BookingStatus.APPROVED); + bookingRepository.save(pastBooking); + + List result = bookingService.getBookingsByUser(booker.getId(), BookingStatus.PAST); + + assertThat(result).hasSize(1); + assertThat(result.get(0).getEnd()).isBefore(LocalDateTime.now()); + } + + @Test + void getBookingsByUser_FutureStatus_ShouldReturnFutureBookings() { + Booking futureBooking = new Booking(); + futureBooking.setStart(LocalDateTime.now().plusHours(2)); + futureBooking.setEnd(LocalDateTime.now().plusHours(3)); + futureBooking.setItem(item); + futureBooking.setBooker(booker); + futureBooking.setStatus(BookingStatus.APPROVED); + bookingRepository.save(futureBooking); + + List result = bookingService.getBookingsByUser(booker.getId(), BookingStatus.FUTURE); + + assertThat(result).hasSize(1); + assertThat(result.get(0).getStart()).isAfter(LocalDateTime.now()); + } + + @Test + void getBookingsByUser_RejectedStatus_ShouldReturnRejectedBookings() { + Booking rejectedBooking = createTestBooking(); + rejectedBooking.setStatus(BookingStatus.REJECTED); + bookingRepository.save(rejectedBooking); + + List result = bookingService.getBookingsByUser(booker.getId(), BookingStatus.REJECTED); + + assertThat(result).hasSize(1); + assertThat(result.get(0).getStatus()).isEqualTo(BookingStatus.REJECTED); + } + + @Test + void getBookingsByOwner_CurrentStatus_ShouldReturnCurrentBookings() { + Booking currentBooking = new Booking(); + currentBooking.setStart(LocalDateTime.now().minusHours(1)); // started 1 hour ago + currentBooking.setEnd(LocalDateTime.now().plusHours(1)); // ends in 1 hour + currentBooking.setItem(item); + currentBooking.setBooker(booker); + currentBooking.setStatus(BookingStatus.APPROVED); + bookingRepository.save(currentBooking); + + List result = bookingService.getBookingsByOwner(owner.getId(), BookingStatus.CURRENT); + + assertThat(result).hasSize(1); + assertThat(result.get(0).getStart()).isBefore(LocalDateTime.now()); + assertThat(result.get(0).getEnd()).isAfter(LocalDateTime.now()); + } + + @Test + void getBookingsByOwner_PastStatus_ShouldReturnPastBookings() { + Booking pastBooking = new Booking(); + pastBooking.setStart(LocalDateTime.now().minusHours(3)); + pastBooking.setEnd(LocalDateTime.now().minusHours(1)); + pastBooking.setItem(item); + pastBooking.setBooker(booker); + pastBooking.setStatus(BookingStatus.APPROVED); + bookingRepository.save(pastBooking); + + List result = bookingService.getBookingsByOwner(owner.getId(), BookingStatus.PAST); + + assertThat(result).hasSize(1); + assertThat(result.get(0).getEnd()).isBefore(LocalDateTime.now()); + } + + @Test + void getBookingsByOwner_FutureStatus_ShouldReturnFutureBookings() { + Booking futureBooking = new Booking(); + futureBooking.setStart(LocalDateTime.now().plusHours(2)); + futureBooking.setEnd(LocalDateTime.now().plusHours(3)); + futureBooking.setItem(item); + futureBooking.setBooker(booker); + futureBooking.setStatus(BookingStatus.APPROVED); + bookingRepository.save(futureBooking); + + List result = bookingService.getBookingsByOwner(owner.getId(), BookingStatus.FUTURE); + + assertThat(result).hasSize(1); + assertThat(result.get(0).getStart()).isAfter(LocalDateTime.now()); + } + + @Test + void getBookingsByOwner_WaitingStatus_ShouldReturnWaitingBookings() { + Booking waitingBooking = createTestBooking(); + bookingRepository.save(waitingBooking); + + List result = bookingService.getBookingsByOwner(owner.getId(), BookingStatus.WAITING); + + assertThat(result).hasSize(1); + assertThat(result.get(0).getStatus()).isEqualTo(BookingStatus.WAITING); + } + + @Test + void getBookingsByOwner_RejectedStatus_ShouldReturnRejectedBookings() { + Booking rejectedBooking = createTestBooking(); + rejectedBooking.setStatus(BookingStatus.REJECTED); + bookingRepository.save(rejectedBooking); + + List result = bookingService.getBookingsByOwner(owner.getId(), BookingStatus.REJECTED); + + assertThat(result).hasSize(1); + assertThat(result.get(0).getStatus()).isEqualTo(BookingStatus.REJECTED); + } + private Booking createTestBooking() { Booking booking = new Booking(); booking.setStart(LocalDateTime.now().plusHours(1)); diff --git a/server/src/test/java/ru/practicum/shareit/item/ItemServiceIntegrationTest.java b/server/src/test/java/ru/practicum/shareit/item/ItemServiceIntegrationTest.java index b334cde..25d5447 100644 --- a/server/src/test/java/ru/practicum/shareit/item/ItemServiceIntegrationTest.java +++ b/server/src/test/java/ru/practicum/shareit/item/ItemServiceIntegrationTest.java @@ -10,6 +10,7 @@ import ru.practicum.shareit.booking.model.Booking; import ru.practicum.shareit.booking.repository.BookingRepository; import ru.practicum.shareit.booking.status.BookingStatus; +import ru.practicum.shareit.exception.AccessDeniedException; import ru.practicum.shareit.exception.ItemNotFoundException; import ru.practicum.shareit.exception.UserNotFoundException; import ru.practicum.shareit.exception.ValidationException; @@ -257,6 +258,128 @@ void addComment_WithoutBooking_ShouldThrowException() { .hasMessage("Вы можете комментировать только те предметы, которые бронировали"); } + // Тесты для проверки доступа при обновлении предмета + @Test + void updateItem_WithNonOwnerUser_ShouldThrowAccessDeniedException() { + Item item = createTestItem(); + + ItemDto updateDto = new ItemDto(); + updateDto.setName("Updated Name"); + + assertThatThrownBy(() -> itemService.updateItem(booker.getId(), item.getId(), updateDto)) + .isInstanceOf(AccessDeniedException.class) + .hasMessage("Только собственник может редактировать предмет"); + } + + // Тесты для валидации при обновлении предмета + @Test + void updateItem_WithEmptyName_ShouldThrowValidationException() { + Item item = createTestItem(); + + ItemDto updateDto = new ItemDto(); + updateDto.setName(" "); // Пустое название с пробелами + + assertThatThrownBy(() -> itemService.updateItem(owner.getId(), item.getId(), updateDto)) + .isInstanceOf(ValidationException.class) + .hasMessage("Название предмета не может быть пустым"); + } + + @Test + void updateItem_WithEmptyDescription_ShouldThrowValidationException() { + Item item = createTestItem(); + + ItemDto updateDto = new ItemDto(); + updateDto.setDescription(" "); // Пустое описание с пробелами + + assertThatThrownBy(() -> itemService.updateItem(owner.getId(), item.getId(), updateDto)) + .isInstanceOf(ValidationException.class) + .hasMessage("Описание предмета не может быть пустым"); + } + + @Test + void updateItem_WithRequestId_ShouldUpdateRequestId() { + Item item = createTestItem(); + + ItemDto updateDto = new ItemDto(); + updateDto.setRequestId(itemRequest.getId()); + + ItemDto result = itemService.updateItem(owner.getId(), item.getId(), updateDto); + + assertThat(result.getRequestId()).isEqualTo(itemRequest.getId()); + + Item updatedItem = itemRepository.findById(item.getId()).orElse(null); + assertThat(updatedItem).isNotNull(); + assertThat(updatedItem.getRequestId()).isEqualTo(itemRequest.getId()); + } + + // Тесты для валидации при создании предмета + @Test + void createItem_WithNullItemDto_ShouldThrowValidationException() { + assertThatThrownBy(() -> itemService.createItem(owner.getId(), null)) + .isInstanceOf(ValidationException.class) + .hasMessage("Предмет не может быть null"); + } + + @Test + void createItem_WithNullUserId_ShouldThrowUserNotFoundException() { + ItemDto itemDto = new ItemDto(); + itemDto.setName("Test Item"); + itemDto.setDescription("Test Description"); + itemDto.setAvailable(true); + + assertThatThrownBy(() -> itemService.createItem(null, itemDto)) + .isInstanceOf(UserNotFoundException.class) + .hasMessage("Собственник не может быть null"); + } + + @Test + void createItem_WithNullName_ShouldThrowValidationException() { + ItemDto itemDto = new ItemDto(); + itemDto.setName(null); + itemDto.setDescription("Test Description"); + itemDto.setAvailable(true); + + assertThatThrownBy(() -> itemService.createItem(owner.getId(), itemDto)) + .isInstanceOf(ValidationException.class) + .hasMessage("Название предмета не может быть пустым или null"); + } + + @Test + void createItem_WithNullDescription_ShouldThrowValidationException() { + ItemDto itemDto = new ItemDto(); + itemDto.setName("Test Item"); + itemDto.setDescription(null); + itemDto.setAvailable(true); + + assertThatThrownBy(() -> itemService.createItem(owner.getId(), itemDto)) + .isInstanceOf(ValidationException.class) + .hasMessage("Описание предмета не может быть пустым или null"); + } + + @Test + void createItem_WithEmptyDescription_ShouldThrowValidationException() { + ItemDto itemDto = new ItemDto(); + itemDto.setName("Test Item"); + itemDto.setDescription(" "); // Пустое описание с пробелами + itemDto.setAvailable(true); + + assertThatThrownBy(() -> itemService.createItem(owner.getId(), itemDto)) + .isInstanceOf(ValidationException.class) + .hasMessage("Описание предмета не может быть пустым или null"); + } + + @Test + void createItem_WithNullAvailable_ShouldThrowValidationException() { + ItemDto itemDto = new ItemDto(); + itemDto.setName("Test Item"); + itemDto.setDescription("Test Description"); + itemDto.setAvailable(null); + + assertThatThrownBy(() -> itemService.createItem(owner.getId(), itemDto)) + .isInstanceOf(ValidationException.class) + .hasMessage("Статус доступности предмета не может быть null"); + } + private Item createTestItem() { Item item = new Item(); item.setName("Test Item"); diff --git a/server/src/test/java/ru/practicum/shareit/request/ItemRequestMapperTest.java b/server/src/test/java/ru/practicum/shareit/request/ItemRequestMapperTest.java new file mode 100644 index 0000000..6c97785 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/request/ItemRequestMapperTest.java @@ -0,0 +1,84 @@ +package ru.practicum.shareit.request; + +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.mapper.ItemRequestMapper; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +public class ItemRequestMapperTest { + + @Test + void toItemRequest_ShouldMapCorrectly() { + LocalDateTime now = LocalDateTime.now(); + User user = new User(); + user.setId(1L); + user.setName("Test User"); + user.setEmail("test@example.com"); + + ItemRequestDto dto = new ItemRequestDto(1L, "Description", now, List.of()); + + ItemRequest result = ItemRequestMapper.toItemRequest(dto, user); + + assertNotNull(result); + assertEquals(dto.getId(), result.getId()); + assertEquals(dto.getDescription(), result.getDescription()); + assertEquals(now, result.getCreated()); + assertEquals(user, result.getRequester()); + } + + @Test + void toItemRequest_WhenDtoIsNull_ShouldReturnNull() { + ItemRequest result = ItemRequestMapper.toItemRequest(null, new User()); + assertNull(result); + } + + @Test + void toItemRequestDto_ItemRequest_ShouldMapCorrectly() { + LocalDateTime now = LocalDateTime.now(); + ItemRequest request = new ItemRequest(); + request.setId(1L); + request.setDescription("Desc"); + request.setCreated(now); + request.setRequester(new User()); + + ItemRequestDto result = ItemRequestMapper.toItemRequestDto(request); + + assertNotNull(result); + assertEquals(request.getId(), result.getId()); + assertEquals(request.getDescription(), result.getDescription()); + assertEquals(now, result.getCreated()); + assertTrue(result.getItems().isEmpty()); + } + + @Test + void toItemRequestDto_ItemRequestAndItems_ShouldMapCorrectly() { + LocalDateTime now = LocalDateTime.now(); + ItemRequest request = new ItemRequest(); + request.setId(1L); + request.setDescription("Desc"); + request.setCreated(now); + request.setRequester(new User()); + + ItemDto itemDto = new ItemDto(); + itemDto.setId(1L); + itemDto.setName("Item"); + itemDto.setRequestId(1L); + + ItemRequestDto result = ItemRequestMapper.toItemRequestDto(request, List.of(itemDto)); + + assertNotNull(result); + assertEquals(request.getId(), result.getId()); + assertEquals(request.getDescription(), result.getDescription()); + assertEquals(now, result.getCreated()); + assertNotNull(result.getItems()); + assertEquals(1, result.getItems().size()); + assertEquals("Item", result.getItems().get(0).getName()); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/request/ItemRequestServiceImplIntegrationTest.java b/server/src/test/java/ru/practicum/shareit/request/ItemRequestServiceImplIntegrationTest.java new file mode 100644 index 0000000..9a4147d --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/request/ItemRequestServiceImplIntegrationTest.java @@ -0,0 +1,265 @@ +package ru.practicum.shareit.request; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.TestPropertySource; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.shareit.exception.RequestNotFoundException; +import ru.practicum.shareit.exception.UserNotFoundException; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.repository.ItemRepository; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.request.repository.ItemRequestRepository; +import ru.practicum.shareit.request.service.ItemRequestService; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.repository.UserRepository; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@TestPropertySource(properties = { + "spring.datasource.url=jdbc:h2:mem:testdb", + "spring.jpa.hibernate.ddl-auto=create-drop" +}) +@Transactional +class ItemRequestServiceImplIntegrationTest { + + @Autowired + private ItemRequestService itemRequestService; + + @Autowired + private ItemRequestRepository itemRequestRepository; + + @Autowired + private UserRepository userRepository; + + @Autowired + private ItemRepository itemRepository; + + private User requester; + private User otherUser; + private ItemRequest itemRequest1; + private ItemRequest itemRequest2; + private Item item1; + private Item item2; + + @BeforeEach + void setUp() { + requester = new User(); + requester.setName("Requester"); + requester.setEmail("requester@test.com"); + requester = userRepository.save(requester); + + otherUser = new User(); + otherUser.setName("Other User"); + otherUser.setEmail("other@test.com"); + otherUser = userRepository.save(otherUser); + + itemRequest1 = new ItemRequest(); + itemRequest1.setDescription("Need a drill"); + itemRequest1.setCreated(LocalDateTime.now().minusDays(2)); + itemRequest1.setRequester(requester); + itemRequest1 = itemRequestRepository.save(itemRequest1); + + itemRequest2 = new ItemRequest(); + itemRequest2.setDescription("Need a ladder"); + itemRequest2.setCreated(LocalDateTime.now().minusDays(1)); + itemRequest2.setRequester(otherUser); + itemRequest2 = itemRequestRepository.save(itemRequest2); + + item1 = new Item(); + item1.setName("Electric Drill"); + item1.setDescription("Powerful drill"); + item1.setAvailable(true); + item1.setOwner(otherUser); + item1.setRequestId(itemRequest1.getId()); + item1 = itemRepository.save(item1); + + item2 = new Item(); + item2.setName("Step Ladder"); + item2.setDescription("3-step ladder"); + item2.setAvailable(true); + item2.setOwner(requester); + item2.setRequestId(itemRequest2.getId()); + item2 = itemRepository.save(item2); + } + + @Test + void createItemRequest_ShouldCreateAndReturnItemRequest() { + ItemRequestDto requestDto = new ItemRequestDto(); + requestDto.setDescription("Need a hammer"); + + ItemRequestDto result = itemRequestService.createItemRequest(requester.getId(), requestDto); + + assertNotNull(result); + assertNotNull(result.getId()); + assertEquals("Need a hammer", result.getDescription()); + assertNotNull(result.getCreated()); + assertTrue(result.getItems().isEmpty()); + + // Проверяем, что запрос действительно сохранился в базе данных + ItemRequest savedRequest = itemRequestRepository.findById(result.getId()).orElse(null); + assertNotNull(savedRequest); + assertEquals("Need a hammer", savedRequest.getDescription()); + assertEquals(requester.getId(), savedRequest.getRequester().getId()); + } + + @Test + void createItemRequest_WithNonExistentUser_ShouldThrowUserNotFoundException() { + ItemRequestDto requestDto = new ItemRequestDto(); + requestDto.setDescription("Need something"); + + assertThrows(UserNotFoundException.class, + () -> itemRequestService.createItemRequest(999L, requestDto)); + } + + @Test + void getUserRequests_ShouldReturnUserRequestsWithItems() { + List result = itemRequestService.getUserRequests(requester.getId()); + + assertNotNull(result); + assertEquals(1, result.size()); + + ItemRequestDto requestDto = result.get(0); + assertEquals(itemRequest1.getId(), requestDto.getId()); + assertEquals("Need a drill", requestDto.getDescription()); + assertEquals(1, requestDto.getItems().size()); + assertEquals("Electric Drill", requestDto.getItems().get(0).getName()); + } + + @Test + void getUserRequests_WithNonExistentUser_ShouldThrowUserNotFoundException() { + assertThrows(UserNotFoundException.class, + () -> itemRequestService.getUserRequests(999L)); + } + + @Test + void getUserRequests_WithNoRequests_ShouldReturnEmptyList() { + User userWithoutRequests = new User(); + userWithoutRequests.setName("No Requests User"); + userWithoutRequests.setEmail("noreq@test.com"); + userWithoutRequests = userRepository.save(userWithoutRequests); + + List result = itemRequestService.getUserRequests(userWithoutRequests.getId()); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + void getAllRequests_ShouldReturnOtherUsersRequestsWithItems() { + List result = itemRequestService.getAllRequests(requester.getId(), 0, 20); + + assertNotNull(result); + assertEquals(1, result.size()); + + ItemRequestDto requestDto = result.get(0); + assertEquals(itemRequest2.getId(), requestDto.getId()); + assertEquals("Need a ladder", requestDto.getDescription()); + assertEquals(1, requestDto.getItems().size()); + assertEquals("Step Ladder", requestDto.getItems().get(0).getName()); + } + + @Test + void getAllRequests_WithNonExistentUser_ShouldThrowUserNotFoundException() { + assertThrows(UserNotFoundException.class, + () -> itemRequestService.getAllRequests(999L, 0, 20)); + } + + @Test + void getAllRequests_ShouldExcludeCurrentUserRequests() { + ItemRequest additionalRequest = new ItemRequest(); + additionalRequest.setDescription("Another request"); + additionalRequest.setCreated(LocalDateTime.now()); + additionalRequest.setRequester(requester); + itemRequestRepository.save(additionalRequest); + + List result = itemRequestService.getAllRequests(requester.getId(), 0, 20); + + assertEquals(1, result.size()); // Должен вернуть только запрос от otherUser + assertEquals(itemRequest2.getId(), result.get(0).getId()); + } + + @Test + void getRequestById_WithRequesterUser_ShouldReturnRequestWithItems() { + ItemRequestDto result = itemRequestService.getRequestById(itemRequest1.getId(), requester.getId()); + + assertNotNull(result); + assertEquals(itemRequest1.getId(), result.getId()); + assertEquals("Need a drill", result.getDescription()); + assertEquals(1, result.getItems().size()); + assertEquals("Electric Drill", result.getItems().get(0).getName()); + } + + @Test + void getRequestById_WithOtherUser_ShouldReturnRequestWithItems() { + ItemRequestDto result = itemRequestService.getRequestById(itemRequest1.getId(), otherUser.getId()); + + assertNotNull(result); + assertEquals(itemRequest1.getId(), result.getId()); + assertEquals("Need a drill", result.getDescription()); + assertEquals(1, result.getItems().size()); + assertEquals("Electric Drill", result.getItems().get(0).getName()); + } + + @Test + void getRequestById_WithNonExistentRequest_ShouldThrowRequestNotFoundException() { + assertThrows(RequestNotFoundException.class, + () -> itemRequestService.getRequestById(999L, requester.getId())); + } + + @Test + void getRequestById_WithNonExistentUser_ShouldThrowUserNotFoundException() { + assertThrows(UserNotFoundException.class, + () -> itemRequestService.getRequestById(itemRequest2.getId(), 999L)); + } + + @Test + void getRequestById_WithNoItems_ShouldReturnRequestWithEmptyItemsList() { + ItemRequest requestWithoutItems = new ItemRequest(); + requestWithoutItems.setDescription("Request without items"); + requestWithoutItems.setCreated(LocalDateTime.now()); + requestWithoutItems.setRequester(requester); + requestWithoutItems = itemRequestRepository.save(requestWithoutItems); + + ItemRequestDto result = itemRequestService.getRequestById(requestWithoutItems.getId(), requester.getId()); + + assertNotNull(result); + assertEquals(requestWithoutItems.getId(), result.getId()); + assertTrue(result.getItems().isEmpty()); + } + + @Test + void getUserRequests_ShouldReturnRequestsInDescendingOrderByCreated() { + ItemRequest newerRequest = new ItemRequest(); + newerRequest.setDescription("Newer request"); + newerRequest.setCreated(LocalDateTime.now()); + newerRequest.setRequester(requester); + itemRequestRepository.save(newerRequest); + + List result = itemRequestService.getUserRequests(requester.getId()); + + assertEquals(2, result.size()); + assertTrue(result.get(0).getCreated().isAfter(result.get(1).getCreated())); + } + + @Test + void getAllRequests_ShouldReturnRequestsInDescendingOrderByCreated() { + ItemRequest newerRequest = new ItemRequest(); + newerRequest.setDescription("Newer request from other user"); + newerRequest.setCreated(LocalDateTime.now()); + newerRequest.setRequester(otherUser); + itemRequestRepository.save(newerRequest); + + List result = itemRequestService.getAllRequests(requester.getId(), 0, 20); + + assertEquals(2, result.size()); + assertTrue(result.get(0).getCreated().isAfter(result.get(1).getCreated())); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/user/EmailValidatorTest.java b/server/src/test/java/ru/practicum/shareit/user/EmailValidatorTest.java new file mode 100644 index 0000000..e39cb95 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/user/EmailValidatorTest.java @@ -0,0 +1,222 @@ +package ru.practicum.shareit.user; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import ru.practicum.shareit.exception.ValidationException; +import ru.practicum.shareit.user.validator.EmailValidator; + +import static org.junit.jupiter.api.Assertions.*; + +class EmailValidatorTest { + + @Test + void isValidEmail_WithValidEmail_ShouldReturnTrue() { + assertTrue(EmailValidator.isValidEmail("test@example.com")); + assertTrue(EmailValidator.isValidEmail("user.name@domain.org")); + assertTrue(EmailValidator.isValidEmail("email123@test-domain.net")); + assertTrue(EmailValidator.isValidEmail("a@b.co")); + assertTrue(EmailValidator.isValidEmail("user+tag@example.com")); + assertTrue(EmailValidator.isValidEmail("user_name@sub.domain.com")); + assertTrue(EmailValidator.isValidEmail("123@456.ru")); + } + + @ParameterizedTest + @ValueSource(strings = { + "plainaddress", + "@missingdomain.com", + "missing@.com", + "missing@domain", + "spaces @domain.com", + "user@", + "@domain.com", + "user@domain.", + "user@@domain.com", + "user@domain@com", + "user name@domain.com", + "user@domain com", + "user@domain.c", + "" + }) + void isValidEmail_WithInvalidEmail_ShouldReturnFalse(String email) { + assertFalse(EmailValidator.isValidEmail(email)); + } + + @Test + void isValidEmail_WithNullEmail_ShouldReturnFalse() { + assertFalse(EmailValidator.isValidEmail(null)); + } + + @Test + void isValidEmail_WithEmptyString_ShouldReturnFalse() { + assertFalse(EmailValidator.isValidEmail("")); + } + + @Test + void isValidEmail_WithWhitespaceOnlyString_ShouldReturnFalse() { + assertFalse(EmailValidator.isValidEmail(" ")); + assertFalse(EmailValidator.isValidEmail("\t")); + assertFalse(EmailValidator.isValidEmail("\n")); + } + + @Test + void isValidEmail_WithEmailContainingWhitespace_ShouldTrimAndValidate() { + assertTrue(EmailValidator.isValidEmail(" test@example.com ")); + assertTrue(EmailValidator.isValidEmail("\tuser@domain.org\t")); + assertTrue(EmailValidator.isValidEmail("\nvalid@email.net\n")); + } + + @Test + void isValidEmail_WithSpecialCharacters_ShouldHandleCorrectly() { + assertTrue(EmailValidator.isValidEmail("test.email@example.com")); + assertTrue(EmailValidator.isValidEmail("test_email@example.com")); + assertTrue(EmailValidator.isValidEmail("test+tag@example.com")); + assertTrue(EmailValidator.isValidEmail("test-email@example.com")); + assertTrue(EmailValidator.isValidEmail("123@example.com")); + + assertFalse(EmailValidator.isValidEmail("test#email@example.com")); + assertFalse(EmailValidator.isValidEmail("test$email@example.com")); + assertFalse(EmailValidator.isValidEmail("test&email@example.com")); + assertFalse(EmailValidator.isValidEmail("test*email@example.com")); + } + + @Test + void isValidEmail_WithInternationalDomains_ShouldWork() { + assertTrue(EmailValidator.isValidEmail("user@example.ru")); + assertTrue(EmailValidator.isValidEmail("test@domain.org")); + assertTrue(EmailValidator.isValidEmail("email@site.info")); + assertTrue(EmailValidator.isValidEmail("user@test.museum")); + } + + @Test + void isValidEmail_WithSubdomains_ShouldWork() { + assertTrue(EmailValidator.isValidEmail("user@mail.example.com")); + assertTrue(EmailValidator.isValidEmail("test@sub.domain.org")); + assertTrue(EmailValidator.isValidEmail("email@very.long.subdomain.example.net")); + } + + // Тесты для метода validateEmail() + + @Test + void validateEmail_WithValidEmail_ShouldNotThrowException() { + assertDoesNotThrow(() -> EmailValidator.validateEmail("test@example.com")); + assertDoesNotThrow(() -> EmailValidator.validateEmail("user.name@domain.org")); + assertDoesNotThrow(() -> EmailValidator.validateEmail("valid123@test.net")); + } + + @Test + void validateEmail_WithNullEmail_ShouldThrowIllegalArgumentException() { + ValidationException exception = assertThrows( + ValidationException.class, + () -> EmailValidator.validateEmail(null) + ); + assertEquals("Email не может быть пустым или null", exception.getMessage()); + } + + @Test + void validateEmail_WithEmptyEmail_ShouldThrowIllegalArgumentException() { + ValidationException exception = assertThrows( + ValidationException.class, + () -> EmailValidator.validateEmail("") + ); + assertEquals("Email не может быть пустым или null", exception.getMessage()); + } + + @Test + void validateEmail_WithWhitespaceOnlyEmail_ShouldThrowValidationException() { + ValidationException exception = assertThrows( + ValidationException.class, + () -> EmailValidator.validateEmail(" ") + ); + assertEquals("Email не может быть пустым или null", exception.getMessage()); + } + + @ParameterizedTest + @ValueSource(strings = { + "plainaddress", + "@missingdomain.com", + "missing@.com", + "missing@domain", + "user@", + "@domain.com", + "user@domain.", + "user@@domain.com", + "user name@domain.com" + }) + void validateEmail_WithInvalidEmail_ShouldThrowValidationException(String email) { + ValidationException exception = assertThrows( + ValidationException.class, + () -> EmailValidator.validateEmail(email) + ); + assertEquals("Email имеет невалидный формат", exception.getMessage()); + } + + @Test + void validateEmail_WithValidEmailContainingWhitespace_ShouldNotThrowException() { + assertDoesNotThrow(() -> EmailValidator.validateEmail(" test@example.com ")); + assertDoesNotThrow(() -> EmailValidator.validateEmail("\tuser@domain.org\t")); + } + + @Test + void validateEmail_ShouldTrimEmailBeforeValidation() { + // Эти email'ы валидны после обрезки пробелов + assertDoesNotThrow(() -> EmailValidator.validateEmail(" valid@email.com ")); + assertDoesNotThrow(() -> EmailValidator.validateEmail("\ttest@domain.org\n")); + + // Этот email невалиден даже после обрезки пробелов + ValidationException exception = assertThrows( + ValidationException.class, + () -> EmailValidator.validateEmail(" invalid@ ") + ); + assertEquals("Email имеет невалидный формат", exception.getMessage()); + } + + // Тесты граничных случаев + + @Test + void emailValidation_WithMinimumValidEmail_ShouldWork() { + String minEmail = "a@b.co"; // Минимально возможный валидный email + assertTrue(EmailValidator.isValidEmail(minEmail)); + assertDoesNotThrow(() -> EmailValidator.validateEmail(minEmail)); + } + + @Test + void emailValidation_WithVeryLongValidEmail_ShouldWork() { + String longEmail = "very.long.email.address.with.many.dots@very.long.domain.name.example.com"; + assertTrue(EmailValidator.isValidEmail(longEmail)); + assertDoesNotThrow(() -> EmailValidator.validateEmail(longEmail)); + } + + @Test + void emailValidation_WithNumericEmail_ShouldWork() { + String numericEmail = "123456@789.ru"; + assertTrue(EmailValidator.isValidEmail(numericEmail)); + assertDoesNotThrow(() -> EmailValidator.validateEmail(numericEmail)); + } + + @Test + void emailValidation_ConsistencyBetweenMethods() { + String[] testEmails = { + "valid@email.com", + "invalid@", + null, + "", + " ", + "test@example.org", + "bad@email@domain.com" + }; + + for (String email : testEmails) { + if (EmailValidator.isValidEmail(email)) { + // Если isValidEmail возвращает true, validateEmail не должен бросать исключение + assertDoesNotThrow(() -> EmailValidator.validateEmail(email), + "Inconsistency for email: " + email); + } else { + // Если isValidEmail возвращает false, validateEmail должен бросить исключение + assertThrows(ValidationException.class, + () -> EmailValidator.validateEmail(email), + "Inconsistency for email: " + email); + } + } + } +} diff --git a/server/src/test/java/ru/practicum/shareit/user/UserDtoTest.java b/server/src/test/java/ru/practicum/shareit/user/UserDtoTest.java index 2dc60c2..1e265ad 100644 --- a/server/src/test/java/ru/practicum/shareit/user/UserDtoTest.java +++ b/server/src/test/java/ru/practicum/shareit/user/UserDtoTest.java @@ -6,6 +6,8 @@ import org.springframework.boot.test.json.JacksonTester; import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.mapper.UserMapper; +import ru.practicum.shareit.user.model.User; import static org.assertj.core.api.Assertions.assertThat; @@ -27,4 +29,146 @@ void serializeDeserializeTest() throws Exception { assertThat(jsonJacksonTester.parse(expectedJson)) .isEqualTo(dto); } + + @Test + void serializeWithNullIdTest() throws Exception { + UserDto dto = new UserDto(null, "Jane", "jane@example.com"); + + String expectedJson = "{\"id\": null, \"name\": \"Jane\", \"email\": \"jane@example.com\"}"; + + assertThat(jsonJacksonTester.write(dto)) + .isEqualToJson(expectedJson); + + assertThat(jsonJacksonTester.parse(expectedJson)) + .isEqualTo(dto); + } + + @Test + void serializeWithSpecialCharactersTest() throws Exception { + UserDto dto = new UserDto(2L, "Иван Петров", "ivan.petrov@тест.рф"); + + String expectedJson = "{\"id\": 2, \"name\": \"Иван Петров\", \"email\": \"ivan.petrov@тест.рф\"}"; + + assertThat(jsonJacksonTester.write(dto)) + .isEqualToJson(expectedJson); + + assertThat(jsonJacksonTester.parse(expectedJson)) + .isEqualTo(dto); + } + + @Test + void deserializeWithExtraFieldsTest() throws Exception { + UserDto expectedDto = new UserDto(3L, "Bob", "bob@example.com"); + + String jsonWithExtraFields = "{\"id\": 3, \"name\": \"Bob\", \"email\": \"bob@example.com\", \"age\": 25, \"city\": \"Moscow\"}"; + + assertThat(jsonJacksonTester.parse(jsonWithExtraFields)) + .isEqualTo(expectedDto); + } + + @Test + void deserializeWithMissingOptionalFieldsTest() throws Exception { + UserDto expectedDto = new UserDto(null, "Alice", "alice@example.com"); + + String jsonWithMissingId = "{\"name\": \"Alice\", \"email\": \"alice@example.com\"}"; + + assertThat(jsonJacksonTester.parse(jsonWithMissingId)) + .isEqualTo(expectedDto); + } + + @Test + void serializeEmptyStringsTest() throws Exception { + UserDto dto = new UserDto(4L, "", ""); + + String expectedJson = "{\"id\": 4, \"name\": \"\", \"email\": \"\"}"; + + assertThat(jsonJacksonTester.write(dto)) + .isEqualToJson(expectedJson); + + assertThat(jsonJacksonTester.parse(expectedJson)) + .isEqualTo(dto); + } + + @Test + void serializeLongEmailTest() throws Exception { + String longEmail = "very.long.email.address.for.testing.purposes@very.long.domain.name.example.com"; + UserDto dto = new UserDto(5L, "Test User", longEmail); + + String expectedJson = String.format("{\"id\": 5, \"name\": \"Test User\", \"email\": \"%s\"}", longEmail); + + assertThat(jsonJacksonTester.write(dto)) + .isEqualToJson(expectedJson); + + assertThat(jsonJacksonTester.parse(expectedJson)) + .isEqualTo(dto); + } + + // Тесты для UserMapper + @Test + void mapperToUserDtoTest() { + User user = new User(1L, "John", "john@example.com"); + UserDto userDto = UserMapper.toUserDto(user); + + assertThat(userDto.getId()).isEqualTo(user.getId()); + assertThat(userDto.getName()).isEqualTo(user.getName()); + assertThat(userDto.getEmail()).isEqualTo(user.getEmail()); + } + + @Test + void mapperToUserTest() { + UserDto userDto = new UserDto(1L, "John", "john@example.com"); + User user = UserMapper.toUser(userDto); + + assertThat(user.getId()).isEqualTo(userDto.getId()); + assertThat(user.getName()).isEqualTo(userDto.getName()); + assertThat(user.getEmail()).isEqualTo(userDto.getEmail()); + } + + @Test + void mapperToUserDtoWithNullIdTest() { + User user = new User(null, "Jane", "jane@example.com"); + UserDto userDto = UserMapper.toUserDto(user); + + assertThat(userDto.getId()).isNull(); + assertThat(userDto.getName()).isEqualTo(user.getName()); + assertThat(userDto.getEmail()).isEqualTo(user.getEmail()); + } + + @Test + void mapperToUserWithNullIdTest() { + UserDto userDto = new UserDto(null, "Jane", "jane@example.com"); + User user = UserMapper.toUser(userDto); + + assertThat(user.getId()).isNull(); + assertThat(user.getName()).isEqualTo(userDto.getName()); + assertThat(user.getEmail()).isEqualTo(userDto.getEmail()); + } + + @Test + void mapperRoundTripTest() { + User originalUser = new User(1L, "Test User", "test@example.com"); + + // User -> UserDto -> User + UserDto userDto = UserMapper.toUserDto(originalUser); + User mappedUser = UserMapper.toUser(userDto); + + assertThat(mappedUser.getId()).isEqualTo(originalUser.getId()); + assertThat(mappedUser.getName()).isEqualTo(originalUser.getName()); + assertThat(mappedUser.getEmail()).isEqualTo(originalUser.getEmail()); + } + + @Test + void mapperWithSpecialCharactersTest() { + User user = new User(2L, "Анна Смирнова", "anna.smirnova@почта.рф"); + UserDto userDto = UserMapper.toUserDto(user); + + assertThat(userDto.getId()).isEqualTo(user.getId()); + assertThat(userDto.getName()).isEqualTo(user.getName()); + assertThat(userDto.getEmail()).isEqualTo(user.getEmail()); + + User mappedBackUser = UserMapper.toUser(userDto); + assertThat(mappedBackUser.getId()).isEqualTo(user.getId()); + assertThat(mappedBackUser.getName()).isEqualTo(user.getName()); + assertThat(mappedBackUser.getEmail()).isEqualTo(user.getEmail()); + } } diff --git a/server/src/test/java/ru/practicum/shareit/user/UserServiceImplIntegrationTest.java b/server/src/test/java/ru/practicum/shareit/user/UserServiceImplIntegrationTest.java new file mode 100644 index 0000000..7c6aceb --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/user/UserServiceImplIntegrationTest.java @@ -0,0 +1,260 @@ +package ru.practicum.shareit.user; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.repository.UserRepository; +import ru.practicum.shareit.user.service.UserServiceImpl; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@DataJpaTest +@Import(UserServiceImpl.class) +@ActiveProfiles("test") +class UserServiceImplIntegrationTest { + + @Autowired + private UserServiceImpl userService; + + @Autowired + private UserRepository userRepository; + + @Autowired + private TestEntityManager entityManager; + + private User testUser1; + private User testUser2; + + @BeforeEach + void setUp() { + userRepository.deleteAll(); + entityManager.flush(); + entityManager.clear(); + + testUser1 = new User(null, "Иван Иванов", "ivan@example.com"); + testUser2 = new User(null, "Петр Петров", "petr@example.com"); + } + + @Test + void createUser_ShouldSaveUserToDatabase() { + UserDto userDto = new UserDto(null, "Новый Пользователь", "new@example.com"); + + UserDto createdUser = userService.createUser(userDto); + + assertThat(createdUser.getId()).isNotNull(); + assertThat(createdUser.getName()).isEqualTo("Новый Пользователь"); + assertThat(createdUser.getEmail()).isEqualTo("new@example.com"); + + User savedUser = entityManager.find(User.class, createdUser.getId()); + assertThat(savedUser).isNotNull(); + assertThat(savedUser.getName()).isEqualTo("Новый Пользователь"); + assertThat(savedUser.getEmail()).isEqualTo("new@example.com"); + } + + @Test + void createUser_WithExistingEmail_ShouldThrowException() { + entityManager.persistAndFlush(testUser1); + UserDto userDto = new UserDto(null, "Другой Пользователь", "ivan@example.com"); + + assertThatThrownBy(() -> userService.createUser(userDto)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Email уже существует"); + + List users = userRepository.findAll(); + assertThat(users).hasSize(1); + } + + @Test + @Transactional + void updateUser_WithWhitespaceInEmail_ShouldNormalizeEmail() { + User savedUser = entityManager.persistAndFlush(testUser1); + String emailWithWhitespace = " new.email@example.com "; + String expectedNormalizedEmail = emailWithWhitespace.trim(); + UserDto updateDto = new UserDto(null, null, emailWithWhitespace); + + UserDto updatedUser = userService.updateUser(savedUser.getId(), updateDto); + entityManager.flush(); + + assertThat(updatedUser.getEmail()).isEqualTo(expectedNormalizedEmail); + assertThat(updatedUser.getName()).isEqualTo(testUser1.getName()); + + entityManager.clear(); + User userFromDb = entityManager.find(User.class, savedUser.getId()); + + if (userFromDb != null && userFromDb.getEmail() != null) { + System.out.println("DB email length: " + userFromDb.getEmail().length()); + System.out.println("Emails equal: " + expectedNormalizedEmail.equals(userFromDb.getEmail())); + + // Проверяем каждый символ + String dbEmail = userFromDb.getEmail(); + if (!expectedNormalizedEmail.equals(dbEmail)) { + System.out.println("Character comparison:"); + int minLength = Math.min(expectedNormalizedEmail.length(), dbEmail.length()); + for (int i = 0; i < minLength; i++) { + char expected = expectedNormalizedEmail.charAt(i); + char actual = dbEmail.charAt(i); + if (expected != actual) { + System.out.printf("Diff at position %d: expected '%c' (%d), actual '%c' (%d)%n", + i, expected, (int)expected, actual, (int)actual); + } + } + } + } + + assertThat(userFromDb).isNotNull(); + assertThat(userFromDb.getEmail()).isEqualTo(expectedNormalizedEmail); + assertThat(userFromDb.getName()).isEqualTo(testUser1.getName()); +} + + @Test + @Transactional + void updateUser_PartialUpdate_ShouldUpdateOnlyProvidedFields() { + User savedUser = entityManager.persistAndFlush(testUser1); + String newName = "Только Новое Имя"; + UserDto updateDto = new UserDto(null, newName, null); + + UserDto updatedUser = userService.updateUser(savedUser.getId(), updateDto); + entityManager.flush(); + + assertThat(updatedUser.getName()).isEqualTo(newName.trim()); + assertThat(updatedUser.getEmail()).isEqualTo(testUser1.getEmail()); + + entityManager.clear(); + User userFromDb = entityManager.find(User.class, savedUser.getId()); + + assertThat(userFromDb.getName()).isEqualTo(newName.trim()); + assertThat(userFromDb.getEmail()).isEqualTo(testUser1.getEmail()); + } + + @Test + void updateUser_WithExistingEmail_ShouldThrowException() { + User user1 = entityManager.persistAndFlush(testUser1); + User user2 = entityManager.persistAndFlush(testUser2); + + UserDto updateDto = new UserDto(null, "Обновленное Имя", testUser1.getEmail()); + + assertThatThrownBy(() -> userService.updateUser(user2.getId(), updateDto)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Пользователь с таким email уже существует"); + + entityManager.clear(); + User unchangedUser = entityManager.find(User.class, user2.getId()); + assertThat(unchangedUser.getName()).isEqualTo(testUser2.getName()); + assertThat(unchangedUser.getEmail()).isEqualTo(testUser2.getEmail()); + } + + @Test + void updateUser_NonExistentUser_ShouldThrowException() { + Long nonExistentId = 999L; + UserDto updateDto = new UserDto(null, "Новое Имя", "new@example.com"); + + assertThatThrownBy(() -> userService.updateUser(nonExistentId, updateDto)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Пользователь не найден"); + } + + @Test + void getUserById_ShouldReturnUserFromDatabase() { + User savedUser = entityManager.persistAndFlush(testUser1); + + UserDto foundUser = userService.getUserById(savedUser.getId()); + + assertThat(foundUser.getId()).isEqualTo(savedUser.getId()); + assertThat(foundUser.getName()).isEqualTo(testUser1.getName()); + assertThat(foundUser.getEmail()).isEqualTo(testUser1.getEmail()); + } + + @Test + void getUserById_NonExistentUser_ShouldThrowException() { + Long nonExistentId = 999L; + + assertThatThrownBy(() -> userService.getUserById(nonExistentId)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Пользователь не найден"); + } + + @Test + void getAllUsers_ShouldReturnAllUsersFromDatabase() { + entityManager.persistAndFlush(testUser1); + entityManager.persistAndFlush(testUser2); + + List allUsers = userService.getAllUsers(); + + assertThat(allUsers).hasSize(2); + + assertThat(allUsers).extracting("name") + .containsExactlyInAnyOrder("Иван Иванов", "Петр Петров"); + + assertThat(allUsers).extracting("email") + .containsExactlyInAnyOrder("ivan@example.com", "petr@example.com"); + } + + @Test + void getAllUsers_EmptyDatabase_ShouldReturnEmptyList() { + List allUsers = userService.getAllUsers(); + + assertThat(allUsers).isEmpty(); + } + + @Test + @Transactional + void deleteUser_ShouldRemoveUserFromDatabase() { + User savedUser = entityManager.persistAndFlush(testUser1); + Long userId = savedUser.getId(); + + assertThat(entityManager.find(User.class, userId)).isNotNull(); + + userService.deleteUser(userId); + entityManager.flush(); + entityManager.clear(); + + User deletedUser = entityManager.find(User.class, userId); + + System.out.println("User ID: " + userId); + System.out.println("Deleted user: " + deletedUser); + System.out.println("Repository exists check: " + userRepository.existsById(userId)); + + assertThat(deletedUser).isNull(); + + assertThat(userRepository.existsById(userId)).isFalse(); + } + + @Test + void deleteUser_NonExistentUser_ShouldThrowException() { + Long nonExistentId = 999L; + + assertThatThrownBy(() -> userService.deleteUser(nonExistentId)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Пользователь не найден"); + } + + @Test + @Transactional + void deleteUser_WithMultipleUsers_ShouldDeleteOnlySpecifiedUser() { + User user1 = entityManager.persistAndFlush(testUser1); + User user2 = entityManager.persistAndFlush(testUser2); + + userService.deleteUser(user1.getId()); + + entityManager.flush(); + entityManager.clear(); + + User deletedUser = entityManager.find(User.class, user1.getId()); + assertThat(deletedUser).isNull(); + + User remainingUser = entityManager.find(User.class, user2.getId()); + assertThat(remainingUser).isNotNull(); + assertThat(remainingUser.getName()).isEqualTo(testUser2.getName()); + assertThat(remainingUser.getEmail()).isEqualTo(testUser2.getEmail()); + } +} From 32135209119a3e6a96a94d89060936d4cdeee0bb Mon Sep 17 00:00:00 2001 From: Evgeniy Dmitriev Date: Tue, 1 Jul 2025 16:08:15 +0300 Subject: [PATCH 12/14] fix poms --- gateway/pom.xml | 7 +------ .../java/ru/practicum/shareit/ShareItGateway.java | 11 ++++++++++- server/pom.xml | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/gateway/pom.xml b/gateway/pom.xml index 3509b6c..6d21dc6 100644 --- a/gateway/pom.xml +++ b/gateway/pom.xml @@ -57,12 +57,7 @@ spring-boot-starter-test test - - ru.practicum - shareit-server - 0.0.1-SNAPSHOT - compile - + diff --git a/gateway/src/main/java/ru/practicum/shareit/ShareItGateway.java b/gateway/src/main/java/ru/practicum/shareit/ShareItGateway.java index 4cbc16c..0fcc741 100644 --- a/gateway/src/main/java/ru/practicum/shareit/ShareItGateway.java +++ b/gateway/src/main/java/ru/practicum/shareit/ShareItGateway.java @@ -2,8 +2,17 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; -@SpringBootApplication +@SpringBootApplication( + exclude = { + DataSourceAutoConfiguration.class, + DataSourceTransactionManagerAutoConfiguration.class, + HibernateJpaAutoConfiguration.class + } +) public class ShareItGateway { public static void main(String[] args) { SpringApplication.run(ShareItGateway.class, args); diff --git a/server/pom.xml b/server/pom.xml index 230a78a..c9feaf4 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -39,7 +39,7 @@ com.h2database h2 - runtime + test From 33a2b4c0deeecea0518cb81861c882bc4e8873ed Mon Sep 17 00:00:00 2001 From: Evgeniy Dmitriev Date: Tue, 1 Jul 2025 16:41:06 +0300 Subject: [PATCH 13/14] fix booking --- .../shareit/booking/BookingClient.java | 16 +++++++++++ .../shareit/booking/BookingController.java | 27 ++++++++++++++----- 2 files changed, 36 insertions(+), 7 deletions(-) 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 916528c..684215d 100644 --- a/gateway/src/main/java/ru/practicum/shareit/booking/BookingClient.java +++ b/gateway/src/main/java/ru/practicum/shareit/booking/BookingClient.java @@ -45,4 +45,20 @@ public ResponseEntity bookItem(long userId, BookItemRequestDto requestDt public ResponseEntity getBooking(long userId, Long bookingId) { return get("/" + bookingId, userId); } + + public ResponseEntity updateBookingStatus(long userId, Long bookingId, Boolean approved) { + Map parameters = Map.of( + "approved", approved + ); + return patch("/" + bookingId + "?approved={approved}", userId, parameters, null); + } + + public ResponseEntity getBookingsByOwner(long userId, BookingState state, Integer from, Integer size) { + Map parameters = Map.of( + "state", state.name(), + "from", from, + "size", size + ); + return get("/owner?state={state}&from={from}&size={size}", userId, parameters); + } } \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java b/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java index 7b55542..73ae99e 100644 --- a/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java +++ b/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java @@ -3,13 +3,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.*; import jakarta.validation.Valid; import jakarta.validation.constraints.Positive; @@ -52,4 +46,23 @@ public ResponseEntity getBooking(@RequestHeader("X-Sharer-User-Id") long log.info("Get booking {}, userId={}", bookingId, userId); return bookingClient.getBooking(userId, bookingId); } + + @PatchMapping("/{bookingId}") + public ResponseEntity updateBookingStatus(@RequestHeader("X-Sharer-User-Id") long userId, + @PathVariable Long bookingId, + @RequestParam Boolean approved) { + log.info("Update booking status {}, userId={}, approved={}", bookingId, userId, approved); + return bookingClient.updateBookingStatus(userId, bookingId, approved); + } + + @GetMapping("/owner") + public ResponseEntity getBookingsByOwner(@RequestHeader("X-Sharer-User-Id") long userId, + @RequestParam(name = "state", defaultValue = "all") String stateParam, + @PositiveOrZero @RequestParam(name = "from", defaultValue = "0") Integer from, + @Positive @RequestParam(name = "size", defaultValue = "10") Integer size) { + BookingState state = BookingState.from(stateParam) + .orElseThrow(() -> new IllegalArgumentException("Unknown state: " + stateParam)); + log.info("Get bookings by owner with state {}, userId={}, from={}, size={}", stateParam, userId, from, size); + return bookingClient.getBookingsByOwner(userId, state, from, size); + } } \ No newline at end of file From 1a0ce62defdd180cf46b008c2d411f1b44c8e7ba Mon Sep 17 00:00:00 2001 From: Evgeniy Dmitriev Date: Tue, 1 Jul 2025 23:50:02 +0300 Subject: [PATCH 14/14] fix: update comments, add USER_ID_HEADER constants --- .../shareit/booking/BookingController.java | 12 +++-- .../shareit/booking/dto/BookingState.java | 32 ++++++++--- .../shareit/item/ItemController.java | 12 +++-- .../request/ItemRequestController.java | 10 ++-- .../ru/practicum/shareit/util/Constants.java | 12 +++++ .../src/main/resources/application.properties | 4 -- .../shareit/booking/BookingController.java | 12 +++-- .../shareit/item/ItemController.java | 12 +++-- .../request/ItemRequestController.java | 10 ++-- .../ru/practicum/shareit/util/Constants.java | 12 +++++ .../booking/BookingControllerTest.java | 3 +- .../shareit/item/ItemControllerTest.java | 54 +++++-------------- .../request/ItemRequestControllerTest.java | 9 ++-- 13 files changed, 109 insertions(+), 85 deletions(-) create mode 100644 gateway/src/main/java/ru/practicum/shareit/util/Constants.java create mode 100644 server/src/main/java/ru/practicum/shareit/util/Constants.java diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java b/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java index 73ae99e..50d3f79 100644 --- a/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java +++ b/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java @@ -13,6 +13,8 @@ import ru.practicum.shareit.booking.dto.BookItemRequestDto; import ru.practicum.shareit.booking.dto.BookingState; +import static ru.practicum.shareit.util.Constants.USER_ID_HEADER; + @Controller @RequestMapping(path = "/bookings") @@ -23,7 +25,7 @@ public class BookingController { private final BookingClient bookingClient; @GetMapping - public ResponseEntity getBookings(@RequestHeader("X-Sharer-User-Id") long userId, + public ResponseEntity getBookings(@RequestHeader(USER_ID_HEADER) long userId, @RequestParam(name = "state", defaultValue = "all") String stateParam, @PositiveOrZero @RequestParam(name = "from", defaultValue = "0") Integer from, @Positive @RequestParam(name = "size", defaultValue = "10") Integer size) { @@ -34,21 +36,21 @@ public ResponseEntity getBookings(@RequestHeader("X-Sharer-User-Id") lon } @PostMapping - public ResponseEntity bookItem(@RequestHeader("X-Sharer-User-Id") long userId, + public ResponseEntity bookItem(@RequestHeader(USER_ID_HEADER) long userId, @RequestBody @Valid BookItemRequestDto requestDto) { log.info("Creating booking {}, userId={}", requestDto, userId); return bookingClient.bookItem(userId, requestDto); } @GetMapping("/{bookingId}") - public ResponseEntity getBooking(@RequestHeader("X-Sharer-User-Id") long userId, + public ResponseEntity getBooking(@RequestHeader(USER_ID_HEADER) long userId, @PathVariable Long bookingId) { log.info("Get booking {}, userId={}", bookingId, userId); return bookingClient.getBooking(userId, bookingId); } @PatchMapping("/{bookingId}") - public ResponseEntity updateBookingStatus(@RequestHeader("X-Sharer-User-Id") long userId, + public ResponseEntity updateBookingStatus(@RequestHeader(USER_ID_HEADER) long userId, @PathVariable Long bookingId, @RequestParam Boolean approved) { log.info("Update booking status {}, userId={}, approved={}", bookingId, userId, approved); @@ -56,7 +58,7 @@ public ResponseEntity updateBookingStatus(@RequestHeader("X-Sharer-User- } @GetMapping("/owner") - public ResponseEntity getBookingsByOwner(@RequestHeader("X-Sharer-User-Id") long userId, + public ResponseEntity getBookingsByOwner(@RequestHeader(USER_ID_HEADER) long userId, @RequestParam(name = "state", defaultValue = "all") String stateParam, @PositiveOrZero @RequestParam(name = "from", defaultValue = "0") Integer from, @Positive @RequestParam(name = "size", defaultValue = "10") Integer size) { diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingState.java b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingState.java index 943af85..415b321 100644 --- a/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingState.java +++ b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingState.java @@ -2,18 +2,38 @@ import java.util.Optional; +/** + * Перечисление состояний бронирования + */ public enum BookingState { - // Все + /** + * Все бронирования + */ ALL, - // Текущие + + /** + * Текущие бронирования + */ CURRENT, - // Будущие + + /** + * Будущие бронирования + */ FUTURE, - // Завершенные + + /** + * Завершенные бронирования + */ PAST, - // Отклоненные + + /** + * Отклоненные бронирования + */ REJECTED, - // Ожидающие подтверждения + + /** + * Бронирования, ожидающие подтверждения + */ WAITING; public static Optional from(String stringState) { diff --git a/gateway/src/main/java/ru/practicum/shareit/item/ItemController.java b/gateway/src/main/java/ru/practicum/shareit/item/ItemController.java index a16cfa2..c7b5510 100644 --- a/gateway/src/main/java/ru/practicum/shareit/item/ItemController.java +++ b/gateway/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -8,6 +8,8 @@ import ru.practicum.shareit.item.comment.CommentDto; import ru.practicum.shareit.item.dto.ItemDto; +import static ru.practicum.shareit.util.Constants.USER_ID_HEADER; + @RestController @RequestMapping("/items") @RequiredArgsConstructor @@ -15,7 +17,7 @@ public class ItemController { private final ItemClient itemClient; @PostMapping - public ResponseEntity createItem(@RequestHeader("X-Sharer-User-Id") Long userId, + public ResponseEntity createItem(@RequestHeader(USER_ID_HEADER) Long userId, @RequestBody @Valid ItemDto itemDto) { if (itemDto.getName() == null || itemDto.getName().trim().isEmpty()) { throw new ValidationException("Название предмета не может быть пустым"); @@ -30,7 +32,7 @@ public ResponseEntity createItem(@RequestHeader("X-Sharer-User-Id") Long } @PatchMapping("/{itemId}") - public ResponseEntity updateItem(@RequestHeader("X-Sharer-User-Id") Long userId, + public ResponseEntity updateItem(@RequestHeader(USER_ID_HEADER) Long userId, @PathVariable Long itemId, @RequestBody ItemDto itemDto) { @@ -39,12 +41,12 @@ public ResponseEntity updateItem(@RequestHeader("X-Sharer-User-Id") Long @GetMapping("/{itemId}") public ResponseEntity getItem(@PathVariable Long itemId, - @RequestHeader("X-Sharer-User-Id") Long userId) { + @RequestHeader(USER_ID_HEADER) Long userId) { return itemClient.getItem(itemId, userId); } @GetMapping - public ResponseEntity getItems(@RequestHeader("X-Sharer-User-Id") Long userId) { + public ResponseEntity getItems(@RequestHeader(USER_ID_HEADER) Long userId) { return itemClient.getItems(userId); } @@ -54,7 +56,7 @@ public ResponseEntity searchItems(@RequestParam String text) { } @PostMapping("/{itemId}/comment") - public ResponseEntity addComment(@RequestHeader("X-Sharer-User-Id") Long userId, + public ResponseEntity addComment(@RequestHeader(USER_ID_HEADER) Long userId, @PathVariable Long itemId, @RequestBody @Valid CommentDto commentDto) { if (commentDto.getText() == null || commentDto.getText().trim().isEmpty()) { diff --git a/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestController.java b/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestController.java index 3b56477..f60df55 100644 --- a/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestController.java +++ b/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestController.java @@ -7,6 +7,8 @@ import ru.practicum.shareit.exception.ValidationException; import ru.practicum.shareit.request.dto.ItemRequestDto; +import static ru.practicum.shareit.util.Constants.USER_ID_HEADER; + @RestController @RequestMapping(path = "/requests") @RequiredArgsConstructor @@ -15,7 +17,7 @@ public class ItemRequestController { private final ItemRequestClient itemRequestClient; @PostMapping - public ResponseEntity createItemRequest(@RequestHeader("X-Sharer-User-Id") Long userId, + public ResponseEntity createItemRequest(@RequestHeader(USER_ID_HEADER) Long userId, @RequestBody @Valid ItemRequestDto itemRequestDto) { if (itemRequestDto.getDescription() == null || itemRequestDto.getDescription().trim().isEmpty()) { throw new ValidationException("Описание запроса не может быть пустым"); @@ -24,12 +26,12 @@ public ResponseEntity createItemRequest(@RequestHeader("X-Sharer-User-Id } @GetMapping - public ResponseEntity getUserRequests(@RequestHeader("X-Sharer-User-Id") Long userId) { + public ResponseEntity getUserRequests(@RequestHeader(USER_ID_HEADER) Long userId) { return itemRequestClient.getUserRequests(userId); } @GetMapping("/all") - public ResponseEntity getAllRequests(@RequestHeader("X-Sharer-User-Id") Long userId, + public ResponseEntity getAllRequests(@RequestHeader(USER_ID_HEADER) Long userId, @RequestParam(defaultValue = "0") int from, @RequestParam(defaultValue = "20") int size) { if (from < 0) { @@ -42,7 +44,7 @@ public ResponseEntity getAllRequests(@RequestHeader("X-Sharer-User-Id") } @GetMapping("/{requestId}") - public ResponseEntity getRequestById(@RequestHeader("X-Sharer-User-Id") Long userId, + public ResponseEntity getRequestById(@RequestHeader(USER_ID_HEADER) Long userId, @PathVariable Long requestId) { return itemRequestClient.getRequestById(userId, requestId); } diff --git a/gateway/src/main/java/ru/practicum/shareit/util/Constants.java b/gateway/src/main/java/ru/practicum/shareit/util/Constants.java new file mode 100644 index 0000000..0ad7b89 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/util/Constants.java @@ -0,0 +1,12 @@ +package ru.practicum.shareit.util; + +public final class Constants { + + /** + * Заголовок для передачи ID пользователя + */ + public static final String USER_ID_HEADER = "X-Sharer-User-Id"; + + private Constants() { + } +} diff --git a/gateway/src/main/resources/application.properties b/gateway/src/main/resources/application.properties index 2ee0851..d3894a9 100644 --- a/gateway/src/main/resources/application.properties +++ b/gateway/src/main/resources/application.properties @@ -1,7 +1,3 @@ logging.level.org.springframework.web.client.RestTemplate=DEBUG -#logging.level.org.apache.http=DEBUG -#logging.level.httpclient.wire=DEBUG - server.port=8080 - shareit-server.url=http://localhost:9090 \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/booking/BookingController.java b/server/src/main/java/ru/practicum/shareit/booking/BookingController.java index 55b71a2..50f2f51 100644 --- a/server/src/main/java/ru/practicum/shareit/booking/BookingController.java +++ b/server/src/main/java/ru/practicum/shareit/booking/BookingController.java @@ -9,6 +9,8 @@ import java.util.List; +import static ru.practicum.shareit.util.Constants.USER_ID_HEADER; + @RestController @RequestMapping(path = "/bookings") @RequiredArgsConstructor @@ -17,33 +19,33 @@ public class BookingController { private final BookingService bookingService; @GetMapping - public List getBookings(@RequestHeader("X-Sharer-User-Id") long userId, + public List getBookings(@RequestHeader(USER_ID_HEADER) long userId, @RequestParam(name = "state", defaultValue = "ALL") String state) { BookingStatus status = BookingStatus.valueOf(state.toUpperCase()); return bookingService.getBookingsByUser(userId, status); } @PostMapping - public BookingDto createBooking(@RequestHeader("X-Sharer-User-Id") long userId, + public BookingDto createBooking(@RequestHeader(USER_ID_HEADER) long userId, @RequestBody BookingCreateDto bookingDto) { return bookingService.createBooking(userId, bookingDto); } @GetMapping("/{bookingId}") - public BookingDto getBooking(@RequestHeader("X-Sharer-User-Id") long userId, + public BookingDto getBooking(@RequestHeader(USER_ID_HEADER) long userId, @PathVariable Long bookingId) { return bookingService.getBookingById(userId, bookingId); } @GetMapping("/owner") - public List getBookingsByOwner(@RequestHeader("X-Sharer-User-Id") long userId, + public List getBookingsByOwner(@RequestHeader(USER_ID_HEADER) long userId, @RequestParam(name = "state", defaultValue = "ALL") String state) { BookingStatus status = BookingStatus.valueOf(state.toUpperCase()); return bookingService.getBookingsByOwner(userId, status); } @PatchMapping("/{bookingId}") - public BookingDto updateBookingStatus(@RequestHeader("X-Sharer-User-Id") long userId, + public BookingDto updateBookingStatus(@RequestHeader(USER_ID_HEADER) long userId, @PathVariable Long bookingId, @RequestParam Boolean approved) { return bookingService.updateBookingStatus(userId, bookingId, approved); diff --git a/server/src/main/java/ru/practicum/shareit/item/ItemController.java b/server/src/main/java/ru/practicum/shareit/item/ItemController.java index fcd2ac3..596477f 100644 --- a/server/src/main/java/ru/practicum/shareit/item/ItemController.java +++ b/server/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -10,6 +10,8 @@ import java.util.List; +import static ru.practicum.shareit.util.Constants.USER_ID_HEADER; + @RestController @RequestMapping("/items") @RequiredArgsConstructor @@ -17,14 +19,14 @@ public class ItemController { private final ItemService itemService; @PostMapping - public ResponseEntity createItem(@RequestHeader("X-Sharer-User-Id") Long userId, + public ResponseEntity createItem(@RequestHeader(USER_ID_HEADER) Long userId, @RequestBody ItemDto itemDto) { ItemDto createdItem = itemService.createItem(userId, itemDto); return ResponseEntity.ok(createdItem); } @PatchMapping("/{itemId}") - public ResponseEntity updateItem(@RequestHeader("X-Sharer-User-Id") Long userId, + public ResponseEntity updateItem(@RequestHeader(USER_ID_HEADER) Long userId, @PathVariable Long itemId, @RequestBody ItemDto itemDto) { ItemDto updatedItem = itemService.updateItem(userId, itemId, itemDto); @@ -33,13 +35,13 @@ public ResponseEntity updateItem(@RequestHeader("X-Sharer-User-Id") Lon @GetMapping("/{itemId}") public ResponseEntity getItem(@PathVariable Long itemId, - @RequestHeader("X-Sharer-User-Id") Long userId) { + @RequestHeader(USER_ID_HEADER) Long userId) { ItemWithBookingDto item = itemService.getItemById(itemId); return ResponseEntity.ok(item); } @GetMapping - public ResponseEntity> getItems(@RequestHeader("X-Sharer-User-Id") Long userId) { + public ResponseEntity> getItems(@RequestHeader(USER_ID_HEADER) Long userId) { List items = itemService.getItemsByOwner(userId); return ResponseEntity.ok(items); } @@ -51,7 +53,7 @@ public ResponseEntity> searchItems(@RequestParam String text) { } @PostMapping("/{itemId}/comment") - public ResponseEntity addComment(@RequestHeader("X-Sharer-User-Id") Long userId, + public ResponseEntity addComment(@RequestHeader(USER_ID_HEADER) Long userId, @PathVariable Long itemId, @RequestBody CommentDto commentDto) { CommentDto createdComment = itemService.addComment(userId, itemId, commentDto); diff --git a/server/src/main/java/ru/practicum/shareit/request/ItemRequestController.java b/server/src/main/java/ru/practicum/shareit/request/ItemRequestController.java index 2d7438e..4bbaa86 100644 --- a/server/src/main/java/ru/practicum/shareit/request/ItemRequestController.java +++ b/server/src/main/java/ru/practicum/shareit/request/ItemRequestController.java @@ -8,6 +8,8 @@ import java.util.List; +import static ru.practicum.shareit.util.Constants.USER_ID_HEADER; + @RestController @RequestMapping(path = "/requests") @RequiredArgsConstructor @@ -16,20 +18,20 @@ public class ItemRequestController { private final ItemRequestService itemRequestService; @PostMapping - public ResponseEntity createItemRequest(@RequestHeader("X-Sharer-User-Id") Long userId, + public ResponseEntity createItemRequest(@RequestHeader(USER_ID_HEADER) Long userId, @RequestBody ItemRequestDto itemRequestDto) { ItemRequestDto createdRequest = itemRequestService.createItemRequest(userId, itemRequestDto); return ResponseEntity.ok(createdRequest); } @GetMapping - public ResponseEntity> getUserRequests(@RequestHeader("X-Sharer-User-Id") Long userId) { + public ResponseEntity> getUserRequests(@RequestHeader(USER_ID_HEADER) Long userId) { List requests = itemRequestService.getUserRequests(userId); return ResponseEntity.ok(requests); } @GetMapping("/all") - public ResponseEntity> getAllRequests(@RequestHeader("X-Sharer-User-Id") Long userId, + public ResponseEntity> getAllRequests(@RequestHeader(USER_ID_HEADER) Long userId, @RequestParam(defaultValue = "0") int from, @RequestParam(defaultValue = "20") int size) { List requests = itemRequestService.getAllRequests(userId, from, size); @@ -37,7 +39,7 @@ public ResponseEntity> getAllRequests(@RequestHeader("X-Sha } @GetMapping("/{requestId}") - public ResponseEntity getRequestById(@RequestHeader("X-Sharer-User-Id") Long userId, + public ResponseEntity getRequestById(@RequestHeader(USER_ID_HEADER) Long userId, @PathVariable Long requestId) { ItemRequestDto request = itemRequestService.getRequestById(requestId, userId); return ResponseEntity.ok(request); diff --git a/server/src/main/java/ru/practicum/shareit/util/Constants.java b/server/src/main/java/ru/practicum/shareit/util/Constants.java new file mode 100644 index 0000000..0ad7b89 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/util/Constants.java @@ -0,0 +1,12 @@ +package ru.practicum.shareit.util; + +public final class Constants { + + /** + * Заголовок для передачи ID пользователя + */ + public static final String USER_ID_HEADER = "X-Sharer-User-Id"; + + private Constants() { + } +} diff --git a/server/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java b/server/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java index 04d73c4..42f9f22 100644 --- a/server/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java +++ b/server/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static ru.practicum.shareit.util.Constants.USER_ID_HEADER; @WebMvcTest(BookingController.class) class BookingControllerTest { @@ -40,8 +41,6 @@ class BookingControllerTest { @Autowired private ObjectMapper objectMapper; - private static final String USER_ID_HEADER = "X-Sharer-User-Id"; - @Test void createBooking_ValidRequest_ShouldReturnCreatedBooking() throws Exception { Long userId = 1L; diff --git a/server/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java b/server/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java index cd2332c..c1189c3 100644 --- a/server/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java +++ b/server/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static ru.practicum.shareit.util.Constants.USER_ID_HEADER; @WebMvcTest(controllers = ItemController.class) class ItemControllerTest { @@ -68,12 +69,10 @@ void setUp() { @Test void createItem_ShouldReturnCreatedItem() throws Exception { - // Given when(itemService.createItem(eq(1L), any(ItemDto.class))).thenReturn(itemDto); - // When & Then mockMvc.perform(post("/items") - .header("X-Sharer-User-Id", 1L) + .header(USER_ID_HEADER, 1L) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(itemDto))) .andExpect(status().isOk()) @@ -87,7 +86,6 @@ void createItem_ShouldReturnCreatedItem() throws Exception { @Test void createItem_WithoutUserHeader_ShouldReturnBadRequest() throws Exception { - // When & Then mockMvc.perform(post("/items") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(itemDto))) @@ -98,13 +96,11 @@ void createItem_WithoutUserHeader_ShouldReturnBadRequest() throws Exception { @Test void createItem_WithNonExistentUser_ShouldReturnNotFound() throws Exception { - // Given when(itemService.createItem(eq(999L), any(ItemDto.class))) .thenThrow(new UserNotFoundException("Собственник не найден")); - // When & Then mockMvc.perform(post("/items") - .header("X-Sharer-User-Id", 999L) + .header(USER_ID_HEADER, 999L) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(itemDto))) .andExpect(status().isNotFound()); @@ -114,7 +110,6 @@ void createItem_WithNonExistentUser_ShouldReturnNotFound() throws Exception { @Test void updateItem_ShouldReturnUpdatedItem() throws Exception { - // Given ItemDto updateDto = new ItemDto(); updateDto.setName("Updated Item"); updateDto.setDescription("Updated Description"); @@ -128,9 +123,8 @@ void updateItem_ShouldReturnUpdatedItem() throws Exception { when(itemService.updateItem(eq(1L), eq(1L), any(ItemDto.class))).thenReturn(updatedItem); - // When & Then mockMvc.perform(patch("/items/1") - .header("X-Sharer-User-Id", 1L) + .header(USER_ID_HEADER, 1L) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(updateDto))) .andExpect(status().isOk()) @@ -144,13 +138,11 @@ void updateItem_ShouldReturnUpdatedItem() throws Exception { @Test void updateItem_WithAccessDenied_ShouldReturnForbidden() throws Exception { - // Given when(itemService.updateItem(eq(2L), eq(1L), any(ItemDto.class))) .thenThrow(new AccessDeniedException("Только собственник может редактировать предмет")); - // When & Then mockMvc.perform(patch("/items/1") - .header("X-Sharer-User-Id", 2L) + .header(USER_ID_HEADER, 2L) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(itemDto))) .andExpect(status().isForbidden()); @@ -160,13 +152,11 @@ void updateItem_WithAccessDenied_ShouldReturnForbidden() throws Exception { @Test void updateItem_WithNonExistentItem_ShouldReturnNotFound() throws Exception { - // Given when(itemService.updateItem(eq(1L), eq(999L), any(ItemDto.class))) .thenThrow(new ItemNotFoundException("Предмет не найден")); - // When & Then mockMvc.perform(patch("/items/999") - .header("X-Sharer-User-Id", 1L) + .header(USER_ID_HEADER, 1L) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(itemDto))) .andExpect(status().isNotFound()); @@ -176,12 +166,10 @@ void updateItem_WithNonExistentItem_ShouldReturnNotFound() throws Exception { @Test void getItem_ShouldReturnItem() throws Exception { - // Given when(itemService.getItemById(1L)).thenReturn(itemWithBookingDto); - // When & Then mockMvc.perform(get("/items/1") - .header("X-Sharer-User-Id", 1L)) + .header(USER_ID_HEADER, 1L)) .andExpect(status().isOk()) .andExpect(jsonPath("$.id", is(1))) .andExpect(jsonPath("$.name", is("Test Item"))) @@ -194,12 +182,10 @@ void getItem_ShouldReturnItem() throws Exception { @Test void getItem_WithNonExistentItem_ShouldReturnNotFound() throws Exception { - // Given when(itemService.getItemById(999L)).thenThrow(new ItemNotFoundException("Предмет не найден")); - // When & Then mockMvc.perform(get("/items/999") - .header("X-Sharer-User-Id", 1L)) + .header(USER_ID_HEADER, 1L)) .andExpect(status().isNotFound()); verify(itemService).getItemById(999L); @@ -207,13 +193,11 @@ void getItem_WithNonExistentItem_ShouldReturnNotFound() throws Exception { @Test void getItems_ShouldReturnOwnerItems() throws Exception { - // Given List items = Arrays.asList(itemWithBookingDto); when(itemService.getItemsByOwner(1L)).thenReturn(items); - // When & Then mockMvc.perform(get("/items") - .header("X-Sharer-User-Id", 1L)) + .header(USER_ID_HEADER, 1L)) .andExpect(status().isOk()) .andExpect(jsonPath("$", hasSize(1))) .andExpect(jsonPath("$[0].id", is(1))) @@ -224,13 +208,11 @@ void getItems_ShouldReturnOwnerItems() throws Exception { @Test void getItems_WithNonExistentUser_ShouldReturnNotFound() throws Exception { - // Given when(itemService.getItemsByOwner(999L)) .thenThrow(new UserNotFoundException("Собственник не найден")); - // When & Then mockMvc.perform(get("/items") - .header("X-Sharer-User-Id", 999L)) + .header(USER_ID_HEADER, 999L)) .andExpect(status().isNotFound()); verify(itemService).getItemsByOwner(999L); @@ -238,11 +220,9 @@ void getItems_WithNonExistentUser_ShouldReturnNotFound() throws Exception { @Test void searchItems_ShouldReturnMatchingItems() throws Exception { - // Given List items = Arrays.asList(itemDto); when(itemService.searchItems("test")).thenReturn(items); - // When & Then mockMvc.perform(get("/items/search") .param("text", "test")) .andExpect(status().isOk()) @@ -255,10 +235,8 @@ void searchItems_ShouldReturnMatchingItems() throws Exception { @Test void searchItems_WithEmptyText_ShouldReturnEmptyList() throws Exception { - // Given when(itemService.searchItems("")).thenReturn(Collections.emptyList()); - // When & Then mockMvc.perform(get("/items/search") .param("text", "")) .andExpect(status().isOk()) @@ -269,15 +247,13 @@ void searchItems_WithEmptyText_ShouldReturnEmptyList() throws Exception { @Test void addComment_ShouldReturnCreatedComment() throws Exception { - // Given CommentDto inputComment = new CommentDto(); inputComment.setText("Great item!"); when(itemService.addComment(eq(1L), eq(1L), any(CommentDto.class))).thenReturn(commentDto); - // When & Then mockMvc.perform(post("/items/1/comment") - .header("X-Sharer-User-Id", 1L) + .header(USER_ID_HEADER, 1L) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(inputComment))) .andExpect(status().isOk()) @@ -291,16 +267,14 @@ void addComment_ShouldReturnCreatedComment() throws Exception { @Test void addComment_WithNonExistentItem_ShouldReturnNotFound() throws Exception { - // Given CommentDto inputComment = new CommentDto(); inputComment.setText("Great item!"); when(itemService.addComment(eq(1L), eq(999L), any(CommentDto.class))) .thenThrow(new ItemNotFoundException("Предмет не найден")); - // When & Then mockMvc.perform(post("/items/999/comment") - .header("X-Sharer-User-Id", 1L) + .header(USER_ID_HEADER, 1L) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(inputComment))) .andExpect(status().isNotFound()); @@ -310,16 +284,14 @@ void addComment_WithNonExistentItem_ShouldReturnNotFound() throws Exception { @Test void addComment_WithNonExistentUser_ShouldReturnNotFound() throws Exception { - // Given CommentDto inputComment = new CommentDto(); inputComment.setText("Great item!"); when(itemService.addComment(eq(999L), eq(1L), any(CommentDto.class))) .thenThrow(new UserNotFoundException("Пользователь не найден")); - // When & Then mockMvc.perform(post("/items/1/comment") - .header("X-Sharer-User-Id", 999L) + .header(USER_ID_HEADER, 999L) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(inputComment))) .andExpect(status().isNotFound()); diff --git a/server/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java b/server/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java index abec576..2aea04c 100644 --- a/server/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java +++ b/server/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java @@ -17,6 +17,7 @@ import static org.mockito.Mockito.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static ru.practicum.shareit.util.Constants.USER_ID_HEADER; @WebMvcTest(ItemRequestController.class) public class ItemRequestControllerTest { @@ -46,7 +47,7 @@ public void testCreateItemRequest() throws Exception { .thenReturn(itemRequestDto); mockMvc.perform(post("/requests") - .header("X-Sharer-User-Id", 1) + .header(USER_ID_HEADER, 1) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(itemRequestDto))) .andExpect(status().isOk()) @@ -61,7 +62,7 @@ public void testGetUserRequests() throws Exception { when(itemRequestService.getUserRequests(anyLong())).thenReturn(List.of(itemRequestDto)); mockMvc.perform(get("/requests") - .header("X-Sharer-User-Id", 1)) + .header(USER_ID_HEADER, 1)) .andExpect(status().isOk()) .andExpect(jsonPath("$.length()").value(1)) .andExpect(jsonPath("$[0].id").value(1)) @@ -76,7 +77,7 @@ public void testGetAllRequests() throws Exception { .thenReturn(List.of(itemRequestDto)); mockMvc.perform(get("/requests/all") - .header("X-Sharer-User-Id", 1)) + .header(USER_ID_HEADER, 1)) .andExpect(status().isOk()) .andExpect(jsonPath("$.length()").value(1)) .andExpect(jsonPath("$[0].id").value(1)) @@ -91,7 +92,7 @@ public void testGetRequestById() throws Exception { .thenReturn(itemRequestDto); mockMvc.perform(get("/requests/1") - .header("X-Sharer-User-Id", 1)) + .header(USER_ID_HEADER, 1)) .andExpect(status().isOk()) .andExpect(jsonPath("$.id").value(1)) .andExpect(jsonPath("$.description").value("Test request"));