From 54bb79682aa10dbd4810543cc2bb117e4fd8ac5d Mon Sep 17 00:00:00 2001 From: Denis Date: Thu, 11 Jul 2024 21:47:57 +0500 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=D1=80=D0=B5=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=D0=B0=D0=BB=20=D1=82=D0=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/ru/practicum/shareit/ShareItApp.java | 6 +- .../shareit/exception/AccessException.java | 7 + .../exception/DuplicatedDataException.java | 7 + .../ErrorHandlingControllerAdvice.java | 41 +++ .../shareit/exception/ErrorResponse.java | 12 + .../shareit/exception/NotFoundException.java | 7 + .../shareit/item/ItemController.java | 48 +++ .../practicum/shareit/item/dto/ItemDto.java | 17 + .../shareit/item/mappers/ItemMapper.java | 29 ++ .../ru/practicum/shareit/item/model/Item.java | 13 + .../repository/InMemoryItemRepository.java | 56 ++++ .../item/repository/ItemRepository.java | 18 ++ .../shareit/item/service/ItemService.java | 21 ++ .../shareit/item/service/ItemServiceImpl.java | 81 +++++ .../java/ru/practicum/shareit/user/User.java | 9 + .../shareit/user/UserController.java | 44 +++ .../practicum/shareit/user/dto/UserDto.java | 21 ++ .../shareit/user/mappers/UserMapper.java | 24 ++ .../repository/InMemoryUserRepository.java | 66 ++++ .../user/repository/UserRepository.java | 20 ++ .../shareit/user/service/UserService.java | 20 ++ .../shareit/user/service/UserServiceImpl.java | 76 +++++ .../shareit/validationMarker/Marker.java | 9 + src/main/resources/application.properties | 4 +- .../ru/practicum/shareit/ShareItTests.java | 6 +- .../shareit/item/ItemControllerTest.java | 292 ++++++++++++++++++ .../InMemoryItemRepositoryTest.java | 123 ++++++++ .../item/service/ItemServiceImplTest.java | 204 ++++++++++++ .../shareit/matcher/ResponseBodyMatcher.java | 52 ++++ .../shareit/user/UserControllerTest.java | 225 ++++++++++++++ .../InMemoryUserRepositoryTest.java | 103 ++++++ .../user/service/UserServiceImplTest.java | 160 ++++++++++ .../ru/practicum/shareit/utils/DataUtils.java | 72 +++++ 33 files changed, 1885 insertions(+), 8 deletions(-) create mode 100644 src/main/java/ru/practicum/shareit/exception/AccessException.java create mode 100644 src/main/java/ru/practicum/shareit/exception/DuplicatedDataException.java create mode 100644 src/main/java/ru/practicum/shareit/exception/ErrorHandlingControllerAdvice.java create mode 100644 src/main/java/ru/practicum/shareit/exception/ErrorResponse.java create mode 100644 src/main/java/ru/practicum/shareit/exception/NotFoundException.java create mode 100644 src/main/java/ru/practicum/shareit/item/mappers/ItemMapper.java create mode 100644 src/main/java/ru/practicum/shareit/item/repository/InMemoryItemRepository.java create mode 100644 src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java create mode 100644 src/main/java/ru/practicum/shareit/item/service/ItemService.java create mode 100644 src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java create mode 100644 src/main/java/ru/practicum/shareit/user/dto/UserDto.java create mode 100644 src/main/java/ru/practicum/shareit/user/mappers/UserMapper.java create mode 100644 src/main/java/ru/practicum/shareit/user/repository/InMemoryUserRepository.java create mode 100644 src/main/java/ru/practicum/shareit/user/repository/UserRepository.java create mode 100644 src/main/java/ru/practicum/shareit/user/service/UserService.java create mode 100644 src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java create mode 100644 src/main/java/ru/practicum/shareit/validationMarker/Marker.java create mode 100644 src/test/java/ru/practicum/shareit/item/ItemControllerTest.java create mode 100644 src/test/java/ru/practicum/shareit/item/repository/InMemoryItemRepositoryTest.java create mode 100644 src/test/java/ru/practicum/shareit/item/service/ItemServiceImplTest.java create mode 100644 src/test/java/ru/practicum/shareit/matcher/ResponseBodyMatcher.java create mode 100644 src/test/java/ru/practicum/shareit/user/UserControllerTest.java create mode 100644 src/test/java/ru/practicum/shareit/user/repository/InMemoryUserRepositoryTest.java create mode 100644 src/test/java/ru/practicum/shareit/user/service/UserServiceImplTest.java create mode 100644 src/test/java/ru/practicum/shareit/utils/DataUtils.java diff --git a/src/main/java/ru/practicum/shareit/ShareItApp.java b/src/main/java/ru/practicum/shareit/ShareItApp.java index a00ad56..a10a87d 100644 --- a/src/main/java/ru/practicum/shareit/ShareItApp.java +++ b/src/main/java/ru/practicum/shareit/ShareItApp.java @@ -6,8 +6,8 @@ @SpringBootApplication public class ShareItApp { - public static void main(String[] args) { - SpringApplication.run(ShareItApp.class, args); - } + public static void main(String[] args) { + SpringApplication.run(ShareItApp.class, args); + } } diff --git a/src/main/java/ru/practicum/shareit/exception/AccessException.java b/src/main/java/ru/practicum/shareit/exception/AccessException.java new file mode 100644 index 0000000..f354004 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/AccessException.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.exception; + +public class AccessException extends RuntimeException { + public AccessException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/practicum/shareit/exception/DuplicatedDataException.java b/src/main/java/ru/practicum/shareit/exception/DuplicatedDataException.java new file mode 100644 index 0000000..339490c --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/DuplicatedDataException.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.exception; + +public class DuplicatedDataException extends RuntimeException { + public DuplicatedDataException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/practicum/shareit/exception/ErrorHandlingControllerAdvice.java b/src/main/java/ru/practicum/shareit/exception/ErrorHandlingControllerAdvice.java new file mode 100644 index 0000000..15072bc --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/ErrorHandlingControllerAdvice.java @@ -0,0 +1,41 @@ +package ru.practicum.shareit.exception; + +import jakarta.validation.ConstraintViolationException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.util.List; + +@RestControllerAdvice +@Slf4j +public class ErrorHandlingControllerAdvice { + @ExceptionHandler(ConstraintViolationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public List handleMethodArgumentNotValidException(final ConstraintViolationException ex) { + return ex.getConstraintViolations().stream() + .map(constraintViolation -> { + log.warn("{} - {}", constraintViolation.getPropertyPath(), constraintViolation.getMessage()); + return new ErrorResponse(String.format("Invalid value of the %s parameter: %s", + constraintViolation.getPropertyPath().toString(), + constraintViolation.getMessage())); + }) + .toList(); + } + + @ExceptionHandler({NotFoundException.class, AccessException.class}) + @ResponseStatus(HttpStatus.NOT_FOUND) + public ErrorResponse handleNotFoundException(final RuntimeException ex) { + log.warn(ex.getMessage()); + return new ErrorResponse(ex.getMessage()); + } + + @ExceptionHandler(DuplicatedDataException.class) + @ResponseStatus(HttpStatus.CONFLICT) + public ErrorResponse handleDuplicatedDataException(final DuplicatedDataException ex) { + log.warn(ex.getMessage()); + return new ErrorResponse(ex.getMessage()); + } +} diff --git a/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java b/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java new file mode 100644 index 0000000..880210c --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java @@ -0,0 +1,12 @@ +package ru.practicum.shareit.exception; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ErrorResponse { + private String error; +} diff --git a/src/main/java/ru/practicum/shareit/exception/NotFoundException.java b/src/main/java/ru/practicum/shareit/exception/NotFoundException.java new file mode 100644 index 0000000..508b545 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/NotFoundException.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.exception; + +public class NotFoundException extends RuntimeException { + public NotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/practicum/shareit/item/ItemController.java b/src/main/java/ru/practicum/shareit/item/ItemController.java index bb17668..a43b8d6 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemController.java +++ b/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -1,12 +1,60 @@ package ru.practicum.shareit.item; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.service.ItemService; +import ru.practicum.shareit.validationMarker.Marker; + +import java.util.List; /** * TODO Sprint add-controllers. */ @RestController @RequestMapping("/items") +@RequiredArgsConstructor +@Validated public class ItemController { + private final ItemService itemService; + private static final String USER_ID = "X-Sharer-User-Id"; + + @PostMapping + @Validated(Marker.OnCreate.class) + public ItemDto create(@RequestBody @Valid ItemDto itemDto, @RequestHeader(USER_ID) long userId) { + return itemService.create(itemDto, userId); + } + + @PatchMapping("/{id}") + @Validated(Marker.OnUpdate.class) + public ItemDto update(@PathVariable Long id, @RequestBody @Valid ItemDto itemDto, + @RequestHeader(USER_ID) long userId) { + itemDto.setId(id); + return itemService.update(itemDto, userId); + } + + @GetMapping("/{id}") + public ItemDto getById(@PathVariable Long id) { + return itemService.getById(id); + } + + @GetMapping + public List getAllOfOwner(@RequestHeader(USER_ID) long userId) { + return itemService.getAllOfOwner(userId); + } + + @GetMapping("/search") + public List findByNameByDescription(@RequestParam String text) { + return itemService.findByNameByDescription(text); + } } 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 9319d7d..98c14d0 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java +++ b/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java @@ -1,7 +1,24 @@ package ru.practicum.shareit.item.dto; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Null; +import lombok.Data; +import lombok.EqualsAndHashCode; +import ru.practicum.shareit.validationMarker.Marker; + /** * TODO Sprint add-controllers. */ +@Data +@EqualsAndHashCode(of = "id") public class ItemDto { + @Null(message = "The ID must be null", groups = Marker.OnCreate.class) + private Long id; + @NotBlank(message = "The name should not be blank", groups = Marker.OnCreate.class) + private String name; + @NotNull(message = "The description should not be null", groups = Marker.OnCreate.class) + private String description; + @NotNull(message = "The available should not be null", groups = Marker.OnCreate.class) + private Boolean available; } diff --git a/src/main/java/ru/practicum/shareit/item/mappers/ItemMapper.java b/src/main/java/ru/practicum/shareit/item/mappers/ItemMapper.java new file mode 100644 index 0000000..8af7ffb --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/mappers/ItemMapper.java @@ -0,0 +1,29 @@ +package ru.practicum.shareit.item.mappers; + +import org.springframework.stereotype.Component; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.User; + +@Component +public class ItemMapper { + public ItemDto toDto(Item item) { + ItemDto dto = new ItemDto(); + dto.setId(item.getId()); + dto.setName(item.getName()); + dto.setDescription(item.getDescription()); + dto.setAvailable(item.isAvailable()); + return dto; + } + + public Item fromDto(ItemDto dto, User user) { + Item item = new Item(); + item.setName(dto.getName()); + item.setDescription(dto.getDescription()); + item.setOwner(user); + item.setAvailable(dto.getAvailable()); + 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 44eb73d..b55659f 100644 --- a/src/main/java/ru/practicum/shareit/item/model/Item.java +++ b/src/main/java/ru/practicum/shareit/item/model/Item.java @@ -1,7 +1,20 @@ package ru.practicum.shareit.item.model; +import lombok.Data; +import lombok.EqualsAndHashCode; +import ru.practicum.shareit.request.ItemRequest; +import ru.practicum.shareit.user.User; + /** * TODO Sprint add-controllers. */ +@Data +@EqualsAndHashCode(of = "id") public class Item { + private Long id; + private String name; + private String description; + private boolean available; + private User owner; + private ItemRequest request; } diff --git a/src/main/java/ru/practicum/shareit/item/repository/InMemoryItemRepository.java b/src/main/java/ru/practicum/shareit/item/repository/InMemoryItemRepository.java new file mode 100644 index 0000000..058b719 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/repository/InMemoryItemRepository.java @@ -0,0 +1,56 @@ +package ru.practicum.shareit.item.repository; + +import org.springframework.stereotype.Repository; +import ru.practicum.shareit.item.model.Item; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@Repository +public class InMemoryItemRepository implements ItemRepository { + private final Map items = new HashMap<>(); + private final Map> itemsByUser = new HashMap<>(); + private long id = 0; + + @Override + public Item create(Item item) { + item.setId(generateId()); + items.put(item.getId(), item); + itemsByUser.computeIfAbsent(item.getOwner().getId(), k -> new ArrayList<>()).add(item); + return item; + } + + + @Override + public Item update(Item item) { + items.put(item.getId(), item); + itemsByUser.get(item.getOwner().getId()).remove(item); + itemsByUser.get(item.getOwner().getId()).add(item); + return item; + } + + @Override + public Optional getById(Long id) { + return Optional.ofNullable(items.get(id)); + } + + @Override + public List getAllOfOwner(Long userId) { + return new ArrayList<>(itemsByUser.get(userId)); + } + + @Override + public List findByNameByDescription(String text) { + return items.values().stream() + .filter(item -> (item.getDescription().toLowerCase().contains(text.toLowerCase()) + || item.getName().toLowerCase().contains(text.toLowerCase())) && item.isAvailable()) + .toList(); + } + + private Long generateId() { + return ++id; + } +} diff --git a/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java b/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java new file mode 100644 index 0000000..0c16737 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java @@ -0,0 +1,18 @@ +package ru.practicum.shareit.item.repository; + +import ru.practicum.shareit.item.model.Item; + +import java.util.List; +import java.util.Optional; + +public interface ItemRepository { + Item create(Item item); + + Item update(Item item); + + Optional getById(Long id); + + List getAllOfOwner(Long userId); + + List findByNameByDescription(String text); +} diff --git a/src/main/java/ru/practicum/shareit/item/service/ItemService.java b/src/main/java/ru/practicum/shareit/item/service/ItemService.java new file mode 100644 index 0000000..4317afd --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/service/ItemService.java @@ -0,0 +1,21 @@ +package ru.practicum.shareit.item.service; + +import org.springframework.validation.annotation.Validated; +import ru.practicum.shareit.item.dto.ItemDto; + +import java.util.List; + +@Validated +public interface ItemService { + + ItemDto create(ItemDto itemDto, Long userId); + + + ItemDto update(ItemDto itemDto, Long userId); + + ItemDto getById(Long id); + + List getAllOfOwner(Long userId); + + List findByNameByDescription(String text); +} diff --git a/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java b/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java new file mode 100644 index 0000000..701d890 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java @@ -0,0 +1,81 @@ +package ru.practicum.shareit.item.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import ru.practicum.shareit.exception.AccessException; +import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.mappers.ItemMapper; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.repository.ItemRepository; +import ru.practicum.shareit.user.User; +import ru.practicum.shareit.user.repository.UserRepository; + +import java.util.ArrayList; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class ItemServiceImpl implements ItemService { + private final ItemRepository itemRepository; + private final UserRepository userRepository; + private final ItemMapper itemMapper; + + @Override + public ItemDto create(ItemDto itemDto, Long userId) { + User user = userRepository.getById(userId) + .orElseThrow(() -> new NotFoundException("User with id - " + userId + " not found")); + Item item = itemRepository.create(itemMapper.fromDto(itemDto, user)); + return itemMapper.toDto(item); + } + + @Override + public ItemDto update(ItemDto itemDto, Long userId) { + checkIfExistsUser(userId); + Item item = itemRepository.getById(itemDto.getId()).orElseThrow( + () -> new NotFoundException("Item with id - " + itemDto.getId() + " not found")); + if (!item.getOwner().getId().equals(userId)) { + throw new AccessException("The user with the id - " + userId + " is not the owner"); + } + if (itemDto.getDescription() != null && !itemDto.getDescription().isEmpty()) { + item.setDescription(itemDto.getDescription()); + } + if (itemDto.getName() != null && !itemDto.getName().isEmpty()) { + item.setName(itemDto.getName()); + } + if (itemDto.getAvailable() != null) { + item.setAvailable(itemDto.getAvailable()); + } + Item update = itemRepository.update(item); + return itemMapper.toDto(update); + } + + @Override + public ItemDto getById(Long id) { + Item item = itemRepository.getById(id).orElseThrow( + () -> new NotFoundException("Item with id - " + id + " not found")); + return itemMapper.toDto(item); + } + + @Override + public List getAllOfOwner(Long userId) { + checkIfExistsUser(userId); + List items = itemRepository.getAllOfOwner(userId); + return items.stream().map(itemMapper::toDto).toList(); + } + + @Override + public List findByNameByDescription(String text) { + if (text == null || text.isEmpty()) { + return new ArrayList<>(); + } + List items = itemRepository.findByNameByDescription(text); + return items.stream().map(itemMapper::toDto).toList(); + } + + private void checkIfExistsUser(Long userId) { + if (userRepository.getById(userId).isEmpty()) { + throw new NotFoundException("User with id - " + userId + " not found"); + } + } +} diff --git a/src/main/java/ru/practicum/shareit/user/User.java b/src/main/java/ru/practicum/shareit/user/User.java index ae6e7f3..94c66cc 100644 --- a/src/main/java/ru/practicum/shareit/user/User.java +++ b/src/main/java/ru/practicum/shareit/user/User.java @@ -1,7 +1,16 @@ package ru.practicum.shareit.user; +import lombok.Data; +import lombok.EqualsAndHashCode; + /** * TODO Sprint add-controllers. */ +@Data +@EqualsAndHashCode(of = "id") public class User { + private Long id; + private String name; + private String email; + } diff --git a/src/main/java/ru/practicum/shareit/user/UserController.java b/src/main/java/ru/practicum/shareit/user/UserController.java index 03039b9..1376300 100644 --- a/src/main/java/ru/practicum/shareit/user/UserController.java +++ b/src/main/java/ru/practicum/shareit/user/UserController.java @@ -1,12 +1,56 @@ package ru.practicum.shareit.user; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.service.UserService; +import ru.practicum.shareit.validationMarker.Marker; + +import java.util.List; /** * TODO Sprint add-controllers. */ @RestController @RequestMapping(path = "/users") +@RequiredArgsConstructor +@Validated public class UserController { + private final UserService service; + + @GetMapping("/{id}") + public UserDto getUserById(@PathVariable long id) { + return service.getById(id); + } + + @GetMapping + public List getAllUsers() { + return service.getAll(); + } + + @PostMapping + @Validated({Marker.OnCreate.class}) + public UserDto createUser(@RequestBody @Valid UserDto dto) { + return service.create(dto); + } + + @PatchMapping("/{id}") + @Validated({Marker.OnUpdate.class}) + public UserDto updateUser(@PathVariable long id, @RequestBody @Valid UserDto dto) { + return service.update(id, dto); + } + + @DeleteMapping("/{id}") + public void deleteUser(@PathVariable long id) { + service.delete(id); + } } diff --git a/src/main/java/ru/practicum/shareit/user/dto/UserDto.java b/src/main/java/ru/practicum/shareit/user/dto/UserDto.java new file mode 100644 index 0000000..8eabaa4 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/dto/UserDto.java @@ -0,0 +1,21 @@ +package ru.practicum.shareit.user.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Null; +import lombok.Data; +import lombok.EqualsAndHashCode; +import ru.practicum.shareit.validationMarker.Marker; + +@Data +@EqualsAndHashCode(of = "id") +public class UserDto { + @Null(groups = Marker.OnCreate.class) + private Long id; + @NotBlank(message = "name not be blank", groups = Marker.OnCreate.class) + private String name; + @Email(message = "incorrect email", groups = {Marker.OnCreate.class, Marker.OnUpdate.class}) + @NotNull(message = "email not be null", groups = Marker.OnCreate.class) + private String email; +} diff --git a/src/main/java/ru/practicum/shareit/user/mappers/UserMapper.java b/src/main/java/ru/practicum/shareit/user/mappers/UserMapper.java new file mode 100644 index 0000000..3ee7331 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/mappers/UserMapper.java @@ -0,0 +1,24 @@ +package ru.practicum.shareit.user.mappers; + +import org.springframework.stereotype.Component; +import ru.practicum.shareit.user.User; +import ru.practicum.shareit.user.dto.UserDto; + +@Component +public class UserMapper { + public UserDto toUserDto(User user) { + UserDto userDto = new UserDto(); + userDto.setId(user.getId()); + userDto.setName(user.getName()); + userDto.setEmail(user.getEmail()); + return userDto; + } + + public User toNewUser(UserDto userDto) { + User user = new User(); + user.setName(userDto.getName()); + user.setEmail(userDto.getEmail()); + return user; + } + +} diff --git a/src/main/java/ru/practicum/shareit/user/repository/InMemoryUserRepository.java b/src/main/java/ru/practicum/shareit/user/repository/InMemoryUserRepository.java new file mode 100644 index 0000000..04587a7 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/repository/InMemoryUserRepository.java @@ -0,0 +1,66 @@ +package ru.practicum.shareit.user.repository; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; +import ru.practicum.shareit.user.User; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +@Repository +@Slf4j +public class InMemoryUserRepository implements UserRepository { + private final Map users = new HashMap<>(); + private final Set emails = new HashSet<>(); + private long id = 0; + + @Override + public Optional getById(long id) { + return Optional.ofNullable(users.get(id)); + } + + @Override + public List getAll() { + return new ArrayList<>(users.values()); + } + + @Override + public User create(User user) { + user.setId(generatesId()); + users.put(user.getId(), user); + emails.add(user.getEmail()); + return user; + } + + @Override + public User update(long id, User user) { + log.info("Updating user with id {} to {}", id, user); + String email = users.get(id).getEmail(); + if (!email.equals(user.getEmail())) { + emails.remove(email); + } + users.put(user.getId(), user); + emails.add(user.getEmail()); + return user; + } + + @Override + public void delete(long id) { + emails.remove(users.get(id).getEmail()); + users.remove(id); + } + + @Override + public List getEmails() { + return emails.stream().toList(); + } + + private long generatesId() { + return ++id; + } +} diff --git a/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java b/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java new file mode 100644 index 0000000..e208069 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java @@ -0,0 +1,20 @@ +package ru.practicum.shareit.user.repository; + +import ru.practicum.shareit.user.User; + +import java.util.List; +import java.util.Optional; + +public interface UserRepository { + Optional getById(long id); + + List getAll(); + + User create(User user); + + User update(long id, User user); + + void delete(long id); + + List getEmails(); +} diff --git a/src/main/java/ru/practicum/shareit/user/service/UserService.java b/src/main/java/ru/practicum/shareit/user/service/UserService.java new file mode 100644 index 0000000..7977d3b --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/service/UserService.java @@ -0,0 +1,20 @@ +package ru.practicum.shareit.user.service; + +import org.springframework.validation.annotation.Validated; +import ru.practicum.shareit.user.dto.UserDto; + +import java.util.List; + +@Validated +public interface UserService { + UserDto getById(long id); + + UserDto create(UserDto dto); + + List getAll(); + + UserDto update(long id, UserDto dto); + + void delete(long id); + +} diff --git a/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java b/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java new file mode 100644 index 0000000..ca70d92 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java @@ -0,0 +1,76 @@ +package ru.practicum.shareit.user.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import ru.practicum.shareit.exception.DuplicatedDataException; +import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.user.User; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.mappers.UserMapper; +import ru.practicum.shareit.user.repository.UserRepository; + +import java.util.List; + +@Service +@RequiredArgsConstructor +@Slf4j +public class UserServiceImpl implements UserService { + private final UserRepository userRepository; + private final UserMapper userMapper; + + @Override + public UserDto getById(long id) { + User user = userRepository.getById(id) + .orElseThrow(() -> new NotFoundException("User with id " + id + " not found")); + log.info("User with id {} found", id); + return userMapper.toUserDto(user); + } + + @Override + public UserDto create(UserDto dto) { + if (userRepository.getEmails().contains(dto.getEmail())) { + throw new DuplicatedDataException("Email address already exists"); + } + User newUser = userRepository.create(userMapper.toNewUser(dto)); + log.info("User with id {} created. {}", newUser.getId(), newUser); + return userMapper.toUserDto(newUser); + } + + @Override + public List getAll() { + List users = userRepository.getAll(); + return users.stream() + .map(userMapper::toUserDto) + .toList(); + } + + @Override + public UserDto update(long id, UserDto dto) { + log.info("Request to update user with id {} to {}", id, dto); + User oldUser = userRepository.getById(id) + .orElseThrow(() -> new NotFoundException("User with id " + id + " not found")); + User updateUser = new User(); + updateUser.setId(id); + updateUser.setName(oldUser.getName()); + updateUser.setEmail(oldUser.getEmail()); + if (dto.getEmail() != null && !dto.getEmail().equals(updateUser.getEmail())) { + if (userRepository.getEmails().contains(dto.getEmail())) { + throw new DuplicatedDataException("Email address already exists"); + } + updateUser.setEmail(dto.getEmail()); + } + if (dto.getName() != null && !dto.getName().isEmpty()) { + updateUser.setName(dto.getName()); + } + log.info("User with id {} updated {}", id, updateUser); + userRepository.update(id, updateUser); + return userMapper.toUserDto(updateUser); + } + + @Override + public void delete(long id) { + userRepository.delete(id); + log.info("User with id {} deleted", id); + } +} diff --git a/src/main/java/ru/practicum/shareit/validationMarker/Marker.java b/src/main/java/ru/practicum/shareit/validationMarker/Marker.java new file mode 100644 index 0000000..1e0ed70 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/validationMarker/Marker.java @@ -0,0 +1,9 @@ +package ru.practicum.shareit.validationMarker; + +public interface Marker { + interface OnUpdate { + } + + interface OnCreate { + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 318785c..3c15746 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -2,12 +2,12 @@ spring.jpa.hibernate.ddl-auto=none spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQL10Dialect spring.jpa.properties.hibernate.format_sql=true spring.sql.init.mode=always - logging.level.org.springframework.orm.jpa=INFO logging.level.org.springframework.transaction=INFO logging.level.org.springframework.transaction.interceptor=TRACE logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG - +logging.level.root=debug +logging.level.ru.practicum.shareit=trace #--- # TODO Append connection to DB #--- diff --git a/src/test/java/ru/practicum/shareit/ShareItTests.java b/src/test/java/ru/practicum/shareit/ShareItTests.java index 4d79052..8502acd 100644 --- a/src/test/java/ru/practicum/shareit/ShareItTests.java +++ b/src/test/java/ru/practicum/shareit/ShareItTests.java @@ -6,8 +6,8 @@ @SpringBootTest class ShareItTests { - @Test - void contextLoads() { - } + @Test + void contextLoads() { + } } 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..b3700fc --- /dev/null +++ b/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java @@ -0,0 +1,292 @@ +package ru.practicum.shareit.item; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.DisplayName; +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.annotation.DirtiesContext; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import ru.practicum.shareit.exception.AccessException; +import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.service.ItemService; +import ru.practicum.shareit.utils.DataUtils; + +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static ru.practicum.shareit.matcher.ResponseBodyMatcher.responseBody; + +@WebMvcTest(controllers = ItemController.class) +@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) +class ItemControllerTest { + @Autowired + private MockMvc mockMvc; + @Autowired + private ObjectMapper objectMapper; + @MockBean + private ItemService itemService; + private final static String URL = "/items"; + private final static String USER_ID = "X-Sharer-User-Id"; + + @Test + @DisplayName("Test create item functionality") + public void givenItemDto_whenCreateItem_thenCreatedItem() throws Exception { + //given + ItemDto itemDto = DataUtils.getItemDtoTestTransient(1); + String json = objectMapper.writeValueAsString(itemDto); + ItemDto expectedItemDto = DataUtils.getItemDtoTestPersistence(1); + given(itemService.create(any(ItemDto.class), anyLong())).willReturn(expectedItemDto); + + //when + ResultActions result = mockMvc.perform(post(URL) + .contentType(MediaType.APPLICATION_JSON) + .content(json) + .header(USER_ID, 1)); + //then + result.andExpect(status().isOk()) + .andExpect(responseBody().containsObjectAsJson(expectedItemDto, ItemDto.class)); + } + + @Test + @DisplayName("Test create item with non-existent user functionality") + public void givenItemDto_whenCreateItemWithNonExistentUser_thenThrowException() throws Exception { + //given + ItemDto itemDto = DataUtils.getItemDtoTestTransient(1); + String json = objectMapper.writeValueAsString(itemDto); + given(itemService.create(any(ItemDto.class), anyLong())) + .willThrow(new NotFoundException("User with id - 1 not found")); + //when + ResultActions result = mockMvc.perform(post(URL) + .contentType(MediaType.APPLICATION_JSON) + .content(json) + .header(USER_ID, 1)); + //then + result.andExpect(status().isNotFound()) + .andExpect(responseBody().containsError("User with id - 1 not found")); + } + + @Test + @DisplayName("Test create item with null name functionality") + public void givenItemDto_whenCreateItemWithNullName_thenThrowException() throws Exception { + //given + ItemDto itemDto = DataUtils.getItemDtoTestTransient(1); + itemDto.setName(null); + String json = objectMapper.writeValueAsString(itemDto); + //when + ResultActions result = mockMvc.perform(post(URL) + .contentType(MediaType.APPLICATION_JSON) + .content(json) + .header(USER_ID, 1)); + //then + result.andExpect(status().isBadRequest()) + .andExpect(responseBody(). + containsErrorValid("Invalid value of the create.itemDto.name parameter: " + + "The name should not be blank")); + } + + @Test + @DisplayName("Test create item with null description functionality") + public void givenItemDto_whenCreateItemWithNullDescription_thenThrowException() throws Exception { + //given + ItemDto itemDto = DataUtils.getItemDtoTestTransient(1); + itemDto.setDescription(null); + String json = objectMapper.writeValueAsString(itemDto); + //when + ResultActions result = mockMvc.perform(post(URL) + .contentType(MediaType.APPLICATION_JSON) + .content(json) + .header(USER_ID, 1)); + //then + result.andExpect(status().isBadRequest()) + .andExpect(responseBody(). + containsErrorValid("Invalid value of the create.itemDto.description parameter: " + + "The description should not be null")); + } + + @Test + @DisplayName("Test create item with null available functionality") + public void givenItemDto_whenCreateItemWithNullAvailable_thenThrowException() throws Exception { + //given + ItemDto itemDto = DataUtils.getItemDtoTestTransient(1); + itemDto.setAvailable(null); + String json = objectMapper.writeValueAsString(itemDto); + //when + ResultActions result = mockMvc.perform(post(URL) + .contentType(MediaType.APPLICATION_JSON) + .content(json) + .header(USER_ID, 1)); + //then + result.andExpect(status().isBadRequest()) + .andExpect(responseBody(). + containsErrorValid("Invalid value of the create.itemDto.available parameter: " + + "The available should not be null")); + } + + @Test + @DisplayName("Test create item with id functionality") + public void givenItemDto_whenCreateItemWithNotNullID_thenThrowException() throws Exception { + //given + ItemDto itemDto = DataUtils.getItemDtoTestPersistence(1); + String json = objectMapper.writeValueAsString(itemDto); + //when + ResultActions result = mockMvc.perform(post(URL) + .contentType(MediaType.APPLICATION_JSON) + .content(json) + .header(USER_ID, 1)); + //then + result.andExpect(status().isBadRequest()) + .andExpect(responseBody(). + containsErrorValid("Invalid value of the create.itemDto.id parameter: " + + "The ID must be null")); + } + + @Test + @DisplayName("Test get item by id functionality") + public void givenItemDto_whenGetItemById_thenItemDtoReturned() throws Exception { + //given + ItemDto itemDto = DataUtils.getItemDtoTestPersistence(1); + given(itemService.getById(anyLong())).willReturn(itemDto); + //when + ResultActions result = mockMvc.perform(get(URL + "/1") + .contentType(MediaType.APPLICATION_JSON) + .header(USER_ID, 1)); + //then + result.andExpect(status().isOk()) + .andExpect(responseBody().containsObjectAsJson(itemDto, ItemDto.class)); + } + + @Test + @DisplayName("Test get item by id not found functionality") + public void givenItemDto_whenGetItemByIdNotFound_thenThrowException() throws Exception { + //given + given(itemService.getById(anyLong())).willThrow(new NotFoundException("Item not found")); + //when + ResultActions result = mockMvc.perform(get(URL + "/1") + .contentType(MediaType.APPLICATION_JSON) + .header(USER_ID, 1)); + //then + result.andExpect(status().isNotFound()) + .andExpect(responseBody().containsError("Item not found")); + } + + @Test + @DisplayName("Test update item functionality") + public void givenItemDto_whenUpdateItem_thenItemDtoUpdated() throws Exception { + //given + ItemDto itemDto = DataUtils.getItemDtoTestPersistence(1); + itemDto.setName("Updated Name"); + String json = objectMapper.writeValueAsString(itemDto); + given(itemService.update(any(ItemDto.class), anyLong())).willReturn(itemDto); + //when + ResultActions result = mockMvc.perform(patch(URL + "/1") + .contentType(MediaType.APPLICATION_JSON) + .content(json) + .header(USER_ID, 1)); + //given + result.andExpect(status().isOk()) + .andExpect(responseBody().containsObjectAsJson(itemDto, ItemDto.class)); + } + + @Test + @DisplayName("Test update item by not owner functionality") + public void givenItemDto_whenUpdateItemByNotOwner_thenThrowException() throws Exception { + //given + ItemDto itemDto = DataUtils.getItemDtoTestPersistence(1); + itemDto.setName("Updated Name"); + String json = objectMapper.writeValueAsString(itemDto); + given(itemService.update(any(ItemDto.class), anyLong())).willThrow(new AccessException("The user with the id - 1 is not the owner")); + //when + ResultActions result = mockMvc.perform(patch(URL + "/1") + .contentType(MediaType.APPLICATION_JSON) + .content(json) + .header(USER_ID, 1)); + //given + result.andExpect(status().isNotFound()) + .andExpect(responseBody().containsError("The user with the id - 1 is not the owner")); + } + + @Test + @DisplayName("Test update item by user not found functionality") + public void givenItemDto_whenUpdateItemByUserNotFound_thenThrowException() throws Exception { + //given + ItemDto itemDto = DataUtils.getItemDtoTestPersistence(1); + itemDto.setName("Updated Name"); + String json = objectMapper.writeValueAsString(itemDto); + given(itemService.update(any(ItemDto.class), anyLong())) + .willThrow(new NotFoundException("User not found")); + //when + ResultActions result = mockMvc.perform(patch(URL + "/1") + .contentType(MediaType.APPLICATION_JSON) + .content(json) + .header(USER_ID, 1)); + //given + result.andExpect(status().isNotFound()) + .andExpect(responseBody().containsError("User not found")); + } + + @Test + @DisplayName("Test get all items of owner functionality") + public void givenItemDto_whenGetAllItemsOfOwner_thenItemsReturned() throws Exception { + //given + ItemDto itemDto1 = DataUtils.getItemDtoTestPersistence(1); + ItemDto itemDto2 = DataUtils.getItemDtoTestPersistence(2); + ItemDto itemDto3 = DataUtils.getItemDtoTestPersistence(3); + List items = List.of(itemDto1, itemDto2, itemDto3); + given(itemService.getAllOfOwner(anyLong())).willReturn(items); + //when + ResultActions result = mockMvc.perform(get(URL) + .contentType(MediaType.APPLICATION_JSON) + .header(USER_ID, 1)); + //given + result.andExpect(status().isOk()) + .andExpect(responseBody().containsListAsJson(items, new TypeReference>() { + })); + } + + @Test + @DisplayName("Test search item functionality") + public void givenItemDto_whenSearchItems_thenItemsReturned() throws Exception { + //given + ItemDto itemDto1 = DataUtils.getItemDtoTestPersistence(1); + List items = List.of(itemDto1); + given(itemService.getAllOfOwner(anyLong())).willReturn(items); + //when + ResultActions result = mockMvc.perform(get(URL) + .contentType(MediaType.APPLICATION_JSON) + .param("text", "1") + .header(USER_ID, 1)); + //given + result.andExpect(status().isOk()) + .andExpect(responseBody().containsListAsJson(items, new TypeReference>() { + })); + } + + @Test + @DisplayName("Test search item empty query functionality") + public void givenItemDto_whenSearchItemsEmptyQuery_thenItemsNotReturned() throws Exception { + //given + given(itemService.getAllOfOwner(anyLong())).willReturn(new ArrayList<>()); + //when + ResultActions result = mockMvc.perform(get(URL) + .contentType(MediaType.APPLICATION_JSON) + .param("text", "1") + .header(USER_ID, 1)); + //given + result.andExpect(status().isOk()) + .andExpect(responseBody().containsListAsJson(List.of(), new TypeReference>() { + })); + } +} \ No newline at end of file diff --git a/src/test/java/ru/practicum/shareit/item/repository/InMemoryItemRepositoryTest.java b/src/test/java/ru/practicum/shareit/item/repository/InMemoryItemRepositoryTest.java new file mode 100644 index 0000000..af0b209 --- /dev/null +++ b/src/test/java/ru/practicum/shareit/item/repository/InMemoryItemRepositoryTest.java @@ -0,0 +1,123 @@ +package ru.practicum.shareit.item.repository; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.User; +import ru.practicum.shareit.utils.DataUtils; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class InMemoryItemRepositoryTest { + InMemoryItemRepository itemRepository; + + @BeforeEach + public void setUp() { + itemRepository = new InMemoryItemRepository(); + } + + @Test + @DisplayName("Test create item functionality") + public void givenItem_whenCreateItem_thenItemIsCreated() { + //given + Item itemToCreate = DataUtils.getItemTestTransient(1); + User owner = DataUtils.getUserTestPersistence(1); + itemToCreate.setOwner(owner); + //when + Item itemToCreated = itemRepository.create(itemToCreate); + //then + assertThat(itemToCreated).isNotNull(); + assertThat(itemToCreated.getId()).isEqualTo(1); + } + + @Test + @DisplayName("Test update item functionality") + public void givenItem_whenUpdateItem_thenItemIsUpdated() { + //given + User owner = DataUtils.getUserTestPersistence(1); + Item itemUpdateName = DataUtils.getItemTestTransient(1); + itemUpdateName.setOwner(owner); + String updateName = "updated name"; + itemRepository.create(itemUpdateName); + //when + Item itemToUpdate = itemRepository.getById(itemUpdateName.getId()).orElse(null); + itemToUpdate.setName(updateName); + Item itemUpdated = itemRepository.update(itemToUpdate); + //then + assertThat(itemUpdated).isNotNull(); + assertThat(itemUpdated.getName()).isEqualTo(updateName); + } + + @Test + @DisplayName("Test get all of owner item functionality") + public void givenItems_whenGetAllOfOwner_thenItemsReturned() { + //given + User owner1 = DataUtils.getUserTestPersistence(1); + Item item1 = DataUtils.getItemTestTransient(1); + Item item2 = DataUtils.getItemTestTransient(2); + User owner2 = DataUtils.getUserTestPersistence(2); + Item item3 = DataUtils.getItemTestTransient(3); + item1.setOwner(owner1); + item2.setOwner(owner1); + item3.setOwner(owner2); + itemRepository.create(item1); + itemRepository.create(item2); + itemRepository.create(item3); + //when + List allOfOwner = itemRepository.getAllOfOwner(owner1.getId()); + //then + assertThat(allOfOwner).isNotEmpty() + .hasSize(2); + } + + @Test + @DisplayName("Test get item by id functionality") + public void givenItem_whenGetItemById_thenItemIsReturned() { + //given + User owner = DataUtils.getUserTestPersistence(1); + Item item = DataUtils.getItemTestTransient(1); + item.setOwner(owner); + itemRepository.create(item); + //when + Item itemReturned = itemRepository.getById(item.getId()).orElse(null); + //then + assertThat(itemReturned).isNotNull() + .usingRecursiveComparison() + .isEqualTo(item); + } + + @Test + @DisplayName("Test get item incorrect id functionality") + public void givenItem_whenGetItemByInvalidId_thenItemIsNotReturned() { + //given + + //when + Item item = itemRepository.getById(999L).orElse(null); + //then + assertThat(item).isNull(); + } + + @Test + @DisplayName("Test find item by name or description functionality") + public void givenItem_whenFindItemByNameByDescription_thenItemIsReturned() { + //given + User owner = DataUtils.getUserTestPersistence(1); + Item item1 = DataUtils.getItemTestTransient(1); + Item item2 = DataUtils.getItemTestTransient(2); + Item item3 = DataUtils.getItemTestTransient(3); + item1.setOwner(owner); + item2.setOwner(owner); + item3.setOwner(owner); + itemRepository.create(item1); + itemRepository.create(item2); + itemRepository.create(item3); + //when + List itemsFound = itemRepository.findByNameByDescription("test1"); + //then + assertThat(itemsFound).isNotEmpty() + .hasSize(1); + } +} \ No newline at end of file diff --git a/src/test/java/ru/practicum/shareit/item/service/ItemServiceImplTest.java b/src/test/java/ru/practicum/shareit/item/service/ItemServiceImplTest.java new file mode 100644 index 0000000..1d39452 --- /dev/null +++ b/src/test/java/ru/practicum/shareit/item/service/ItemServiceImplTest.java @@ -0,0 +1,204 @@ +package ru.practicum.shareit.item.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import ru.practicum.shareit.exception.AccessException; +import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.mappers.ItemMapper; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.repository.InMemoryItemRepository; +import ru.practicum.shareit.item.repository.ItemRepository; +import ru.practicum.shareit.user.User; +import ru.practicum.shareit.user.repository.InMemoryUserRepository; +import ru.practicum.shareit.user.repository.UserRepository; +import ru.practicum.shareit.utils.DataUtils; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class ItemServiceImplTest { + private ItemRepository itemRepository = Mockito.mock(InMemoryItemRepository.class); + private UserRepository userRepository = Mockito.mock(InMemoryUserRepository.class); + private ItemService itemService; + + @BeforeEach + void setUp() { + itemService = new ItemServiceImpl(itemRepository, userRepository, new ItemMapper()); + } + + @Test + @DisplayName("Test create item functionality") + public void givenItemDto_whenCreateItem_thenReturnItemDto() { + //given + Item item = DataUtils.getItemTestPersistence(1); + User owner = DataUtils.getUserTestPersistence(1); + item.setOwner(owner); + given(itemRepository.create(any(Item.class))).willReturn(item); + given(userRepository.getById(anyLong())).willReturn(Optional.of(owner)); + ItemDto itemDtoCreate = DataUtils.getItemDtoTestTransient(1); + //when + ItemDto itemDtoCreated = itemService.create(itemDtoCreate, owner.getId()); + //then + assertThat(itemDtoCreated).isNotNull(); + assertThat(itemDtoCreated.getId()).isEqualTo(item.getId()); + } + + @Test + @DisplayName("Test create item with non-existent user functionality") + public void givenItemDto_whenCreateItemWithNonExistentUser_thenThrowException() { + //given + given(userRepository.getById(anyLong())).willReturn(Optional.empty()); + ItemDto itemDtoCreate = DataUtils.getItemDtoTestTransient(1); + //when + //then + assertThrows(NotFoundException.class, () -> itemService.create(itemDtoCreate, 1L)); + verify(itemRepository, never()).create(any(Item.class)); + } + + @Test + @DisplayName("Test update item functionality") + public void givenItemDto_whenUpdateItem_thenReturnItemDto() { + //given + String updateName = "updateName"; + Item item = DataUtils.getItemTestPersistence(1); + User owner = DataUtils.getUserTestPersistence(1); + item.setOwner(owner); + given(itemRepository.getById(anyLong())).willReturn(Optional.of(item)); + given(userRepository.getById(anyLong())).willReturn(Optional.of(owner)); + item.setName(updateName); + given(itemRepository.update(any(Item.class))).willReturn(item); + ItemDto itemDtoUpdate = DataUtils.getItemDtoTestPersistence(1); + itemDtoUpdate.setName(updateName); + //when + ItemDto itemDtoUpdated = itemService.update(itemDtoUpdate, owner.getId()); + //then + assertThat(itemDtoUpdated).isNotNull(); + assertThat(itemDtoUpdated.getName()).isEqualTo(updateName); + } + + @Test + @DisplayName("Test update item with user non-existent functionality") + public void givenItemDto_whenUpdateItemWithNonExistentUser_thenThrowException() { + //given + given(userRepository.getById(anyLong())).willReturn(Optional.empty()); + ItemDto itemDtoUpdate = DataUtils.getItemDtoTestPersistence(1); + //when + //then + assertThrows(NotFoundException.class, () -> itemService.update(itemDtoUpdate, 1L)); + verify(itemRepository, never()).update(any(Item.class)); + } + + @Test + @DisplayName("Test update item non existent functionality") + public void givenItemDto_whenUpdateItemNonExistent_thenThrowException() { + //given + given(itemRepository.getById(anyLong())).willReturn(Optional.empty()); + ItemDto itemDtoUpdate = DataUtils.getItemDtoTestPersistence(1); + //when + //then + assertThrows(NotFoundException.class, () -> itemService.update(itemDtoUpdate, 1L)); + verify(itemRepository, never()).update(any(Item.class)); + } + + @Test + @DisplayName("Test update item with user not owner functionality") + public void givenItemDto_whenUpdateItemWithUserNotOwner_thenThrowException() { + //given + User owner = DataUtils.getUserTestPersistence(1); + given(userRepository.getById(anyLong())).willReturn(Optional.of(owner)); + Item item = DataUtils.getItemTestPersistence(1); + item.setOwner(owner); + given(itemRepository.getById(anyLong())).willReturn(Optional.of(item)); + ItemDto itemDtoUpdate = DataUtils.getItemDtoTestPersistence(1); + //when + //then + assertThrows(AccessException.class, () -> itemService.update(itemDtoUpdate, 99L)); + verify(itemRepository, never()).update(any(Item.class)); + } + + @Test + @DisplayName("Test get item by id functionality") + public void givenItemDto_whenGetItemById_thenItemDtoIsReturned() { + //given + User owner = DataUtils.getUserTestPersistence(1); + Item item = DataUtils.getItemTestPersistence(1); + item.setOwner(owner); + given(itemRepository.getById(anyLong())).willReturn(Optional.of(item)); + //when + ItemDto itemReturned = itemService.getById(1L); + //then + assertThat(itemReturned).isNotNull(); + } + + @Test + @DisplayName("Test get item by incorrect id functionality") + public void givenItemDto_whenGetItemByIncorrectId_thenThrowException() { + //given + given(itemRepository.getById(anyLong())).willReturn(Optional.empty()); + //when + //then + assertThrows(NotFoundException.class, () -> itemService.getById(999L)); + } + + @Test + @DisplayName("Test get item all by owner functionality") + public void givenItemDto_whenGetAllByOwner_thenItemDtoIsReturned() { + //given + User owner1 = DataUtils.getUserTestPersistence(1); + given(userRepository.getById(anyLong())).willReturn(Optional.of(owner1)); + User owner2 = DataUtils.getUserTestPersistence(2); + Item item1 = DataUtils.getItemTestPersistence(1); + item1.setOwner(owner1); + Item item2 = DataUtils.getItemTestPersistence(2); + item2.setOwner(owner1); + Item item3 = DataUtils.getItemTestPersistence(3); + item3.setOwner(owner2); + given(itemRepository.getAllOfOwner(owner1.getId())).willReturn(List.of(item1, item2)); + //when + List allOfOwner1Returned = itemService.getAllOfOwner(owner1.getId()); + List allOfOwner2Empty = itemService.getAllOfOwner(owner2.getId()); + //then + assertThat(allOfOwner1Returned).isNotNull() + .hasSize(2); + assertThat(allOfOwner2Empty).isEmpty(); + } + + @Test + @DisplayName("Test find item by name or by description functionality") + public void givenItemDto_whenFindItemByNameByDescription_thenItemsDtoIsReturned() { + //given + Item item = DataUtils.getItemTestPersistence(1); + given(itemRepository.findByNameByDescription(anyString())).willReturn(List.of(item)); + //when + List itemsFound = itemService.findByNameByDescription("test1"); + //then + assertThat(itemsFound).isNotNull() + .hasSize(1); + } + + @Test + @DisplayName("Test find item by name or by description of empty query functionality") + public void givenItemDto_whenFindItemByNameByDescriptionEmptyQuery_thenReturnEmpty() { + //given + //when + List itemsFound = itemService.findByNameByDescription(""); + //then + assertThat(itemsFound).isEmpty(); + verify(itemRepository, never()).findByNameByDescription(anyString()); + } +} \ No newline at end of file diff --git a/src/test/java/ru/practicum/shareit/matcher/ResponseBodyMatcher.java b/src/test/java/ru/practicum/shareit/matcher/ResponseBodyMatcher.java new file mode 100644 index 0000000..df2f4c5 --- /dev/null +++ b/src/test/java/ru/practicum/shareit/matcher/ResponseBodyMatcher.java @@ -0,0 +1,52 @@ +package ru.practicum.shareit.matcher; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.test.web.servlet.ResultMatcher; +import ru.practicum.shareit.exception.ErrorResponse; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ResponseBodyMatcher { + private final ObjectMapper objectMapper = new ObjectMapper(); + + + public ResultMatcher containsObjectAsJson(Object expectedObject, Class targetClass) { + return mvcResult -> { + String json = mvcResult.getResponse().getContentAsString(); + T actualObject = objectMapper.readValue(json, targetClass); + assertThat(actualObject).usingRecursiveComparison().isEqualTo(expectedObject); + }; + } + + public ResultMatcher containsListAsJson(Object expectedObject, TypeReference> targetType) { + return mvcResult -> { + String json = mvcResult.getResponse().getContentAsString(); + List actualObject = objectMapper.readValue(json, targetType); + assertThat(actualObject).usingRecursiveComparison().isEqualTo(expectedObject); + }; + } + + public ResultMatcher containsErrorValid(String expectedMessage) { + return result -> { + String json = result.getResponse().getContentAsString(); + List actualObject = objectMapper.readValue(json, new TypeReference>() { + }); + actualObject.forEach(errorResponse -> assertThat(errorResponse.getError()).isEqualTo(expectedMessage)); + }; + } + + public ResultMatcher containsError(String expectedMessage) { + return result -> { + String json = result.getResponse().getContentAsString(); + ErrorResponse actualObject = objectMapper.readValue(json, ErrorResponse.class); + assertThat(actualObject.getError()).isEqualTo(expectedMessage); + }; + } + + public static ResponseBodyMatcher responseBody() { + return new ResponseBodyMatcher(); + } +} 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..7d4a66e --- /dev/null +++ b/src/test/java/ru/practicum/shareit/user/UserControllerTest.java @@ -0,0 +1,225 @@ +package ru.practicum.shareit.user; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.DisplayName; +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.test.annotation.DirtiesContext; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import ru.practicum.shareit.exception.DuplicatedDataException; +import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.service.UserService; +import ru.practicum.shareit.utils.DataUtils; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static ru.practicum.shareit.matcher.ResponseBodyMatcher.responseBody; + +@WebMvcTest(controllers = UserController.class) +@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) +class UserControllerTest { + @Autowired + private MockMvc mvc; + @Autowired + private ObjectMapper objectMapper; + private static final String URL = "/users"; + @MockBean + private UserService userService; + + @Test + @DisplayName("Test user get by id functionality") + void givenUserDto_whenGetUserById_thenUserDtoReturned() throws Exception { + //given + UserDto userDto = DataUtils.getUserDtoTestPersistence(1); + given(userService.getById(anyLong())).willReturn(userDto); + //when + ResultActions result = mvc.perform(get(URL + "/1") + .contentType("application/json")); + //then + result.andExpect(status().isOk()) + .andExpect(responseBody().containsObjectAsJson(userDto, UserDto.class)); + } + + @Test + @DisplayName("Test user get by id not found functionality") + void givenUserDto_whenGetUserByIdNotFound_then404() throws Exception { + //given + given(userService.getById(anyLong())).willThrow(new NotFoundException("User with id 1 not found")); + //when + ResultActions result = mvc.perform(get(URL + "/1") + .contentType("application/json")); + //then + result.andExpect(status().isNotFound()) + .andExpect(responseBody().containsError("User with id 1 not found")); + } + + + @Test + @DisplayName("Test get all user functionality") + void givenUserDto_whenGetAllUsers_thenUsersReturned() throws Exception { + //given + UserDto userDto1 = DataUtils.getUserDtoTestPersistence(1); + UserDto userDto2 = DataUtils.getUserDtoTestPersistence(2); + UserDto userDto3 = DataUtils.getUserDtoTestPersistence(3); + List usersDto = List.of(userDto1, userDto2, userDto3); + given(userService.getAll()).willReturn(usersDto); + //when + ResultActions result = mvc.perform(get(URL) + .contentType("application/json")); + //then + result.andExpect(status().isOk()) + .andExpect(responseBody().containsListAsJson(usersDto, new TypeReference>() { + })); + } + + + @Test + @DisplayName("Test create user functionality") + void givenUserDto_whenCreateUser_thenUserDtoReturned() throws Exception { + //given + UserDto user = DataUtils.getUserDtoTestTransient(1); + String json = objectMapper.writeValueAsString(user); + UserDto userDtoExpected = DataUtils.getUserDtoTestPersistence(1); + given(userService.create(any(UserDto.class))).willReturn(userDtoExpected); + //when + ResultActions result = mvc.perform(post(URL) + .contentType("application/json") + .content(json)); + //then + result.andExpect(status().isOk()) + .andExpect(responseBody().containsObjectAsJson(userDtoExpected, UserDto.class)); + } + + + @Test + @DisplayName("Test create user with duplicate email functionality") + void givenUserDto_whenCreateUserWithDuplicateEmail_thenThrowException() throws Exception { + //given + UserDto user = DataUtils.getUserDtoTestTransient(1); + String json = objectMapper.writeValueAsString(user); + given(userService.create(any(UserDto.class))) + .willThrow(new DuplicatedDataException("Email address already exists")); + //when + ResultActions result = mvc.perform(post(URL) + .contentType("application/json") + .content(json)); + //then + result.andExpect(status().isConflict()) + .andExpect(responseBody().containsError( + "Email address already exists")); + } + + @Test + @DisplayName("Test create user with blank functionality") + void givenUserDto_whenCreateUserWithBlankName_thenThrowException() throws Exception { + //given + UserDto userTestUser = DataUtils.getUserDtoTestTransient(1); + userTestUser.setName(""); + String json = objectMapper.writeValueAsString(userTestUser); + //when + ResultActions result = mvc.perform(post(URL) + .contentType("application/json") + .content(json)); + //then + result.andExpect(status().isBadRequest()) + .andExpect(responseBody().containsErrorValid( + "Invalid value of the createUser.dto.name parameter: name not be blank")); + } + + @Test + @DisplayName("Test create user with incorrect Email functionality") + void givenUserDto_whenCreateUserWithIncorrectEmail_thenThrowException() throws Exception { + //given + UserDto userTestUser = DataUtils.getUserDtoTestTransient(1); + userTestUser.setEmail("email.email.com"); + String query = objectMapper.writeValueAsString(userTestUser); + //when + ResultActions result = mvc.perform(post(URL) + .contentType("application/json") + .content(query)); + //then + result.andExpect(status().isBadRequest()) + .andExpect(responseBody().containsErrorValid( + "Invalid value of the createUser.dto.email parameter: incorrect email")); + } + + @Test + @DisplayName("Test delete user functionality") + void deleteUser_thenReturn200() throws Exception { + //given + //when + ResultActions result = mvc.perform(delete(URL + "/1")); + //then + result.andExpect(status().isOk()); + + } + + @Test + @DisplayName("Test user update functionality") + public void givenUserDto_whenUpdateUser_thenUserDtoReturned() throws Exception { + //given + UserDto userDto = DataUtils.getUserDtoTestPersistence(1); + String updateName = "update_name"; + userDto.setName(updateName); + String json = objectMapper.writeValueAsString(userDto); + given(userService.update(anyLong(), any(UserDto.class))).willReturn(userDto); + //when + ResultActions result = mvc.perform(patch(URL + "/1") + .contentType("application/json") + .content(json)); + //then + result.andExpect(status().isOk()) + .andExpect(responseBody().containsObjectAsJson(userDto, UserDto.class)); + } + + @Test + @DisplayName("Test update not found user functionality") + public void givenUserDto_whenUpdateUserNotFound_then404() throws Exception { + //given + UserDto userDto = DataUtils.getUserDtoTestPersistence(1); + String updateName = "update_name"; + userDto.setName(updateName); + String json = objectMapper.writeValueAsString(userDto); + given(userService.update(anyLong(), any(UserDto.class))) + .willThrow(new NotFoundException("User with id 1 not found")); + //when + ResultActions result = mvc.perform(patch(URL + "/1") + .contentType("application/json") + .content(json)); + //then + result.andExpect(status().isNotFound()) + .andExpect(responseBody().containsError("User with id 1 not found")); + } + + @Test + @DisplayName("Test update user duplicate email functionality") + public void givenUserDto_whenUpdateUserDuplicateEmail_then409() throws Exception { + //given + UserDto userDto = DataUtils.getUserDtoTestPersistence(1); + String updateName = "update_name"; + userDto.setName(updateName); + String json = objectMapper.writeValueAsString(userDto); + given(userService.update(anyLong(), any(UserDto.class))) + .willThrow(new DuplicatedDataException("Email address already exists")); + //when + ResultActions result = mvc.perform(patch(URL + "/1") + .contentType("application/json") + .content(json)); + //then + result.andExpect(status().isConflict()) + .andExpect(responseBody().containsError("Email address already exists")); + } +} \ No newline at end of file diff --git a/src/test/java/ru/practicum/shareit/user/repository/InMemoryUserRepositoryTest.java b/src/test/java/ru/practicum/shareit/user/repository/InMemoryUserRepositoryTest.java new file mode 100644 index 0000000..a279c0c --- /dev/null +++ b/src/test/java/ru/practicum/shareit/user/repository/InMemoryUserRepositoryTest.java @@ -0,0 +1,103 @@ +package ru.practicum.shareit.user.repository; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.user.User; +import ru.practicum.shareit.utils.DataUtils; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + + +class InMemoryUserRepositoryTest { + private InMemoryUserRepository userRepository; + + @BeforeEach + public void setUp() { + userRepository = new InMemoryUserRepository(); + } + + @Test + @DisplayName("Test create user functionality") + public void givenUser_whenCreateUser_thenUserIsCreated() { + //given + User userToCreate = DataUtils.getUserTestTransient(1); + //when + User userCreated = userRepository.create(userToCreate); + //then + assertThat(userCreated).isNotNull(); + assertThat(userCreated.getId()).isEqualTo(1); + } + + @Test + @DisplayName("Test update user functionality") + public void givenUser_whenUpdateUser_thenUserIsUpdated() { + //given + User userUpdateName = DataUtils.getUserTestTransient(1); + userRepository.create(userUpdateName); + String updateName = "updated Name"; + //when + User userToUpdate = userRepository.getById(userUpdateName.getId()).orElse(null); + userToUpdate.setName(updateName); + User userUpdated = userRepository.update(userToUpdate.getId(), userToUpdate); + //then + assertThat(userUpdated).isNotNull(); + assertThat(userUpdated.getName()).isEqualTo(updateName); + } + + @Test + @DisplayName("Test delete user functionality") + public void givenUser_whenDeleteUser_thenUserIsDeleted() { + //given + User user = DataUtils.getUserTestTransient(1); + userRepository.create(user); + //when + userRepository.delete(user.getId()); + User userDeleted = userRepository.getById(user.getId()).orElse(null); + //then + assertThat(userDeleted).isNull(); + } + + @Test + @DisplayName("Test get user by id functionality") + public void givenUser_whenGetUserById_thenUserIsReturned() { + //given + User user = DataUtils.getUserTestTransient(1); + userRepository.create(user); + //when + User userReturned = userRepository.getById(user.getId()).orElse(null); + //then + assertThat(userReturned).isNotNull() + .usingRecursiveComparison() + .isEqualTo(user); + } + + @Test + @DisplayName("Test get user incorrect id functionality") + public void givenUser_whenGetUserByInvalidId_thenUserIsNotReturned() { + //given + + //when + User userReturned = userRepository.getById(999).orElse(null); + //then + assertThat(userReturned).isNull(); + } + + @Test + @DisplayName("Test get all user functionality") + public void givenUser_whenGetAllUsers_thenUsersIsReturned() { + //given + User user1 = DataUtils.getUserTestTransient(1); + User user2 = DataUtils.getUserTestTransient(2); + User user3 = DataUtils.getUserTestTransient(3); + userRepository.create(user1); + userRepository.create(user2); + userRepository.create(user3); + //when + List users = userRepository.getAll(); + //then + assertThat(users).hasSize(3); + } +} \ No newline at end of file diff --git a/src/test/java/ru/practicum/shareit/user/service/UserServiceImplTest.java b/src/test/java/ru/practicum/shareit/user/service/UserServiceImplTest.java new file mode 100644 index 0000000..72478e8 --- /dev/null +++ b/src/test/java/ru/practicum/shareit/user/service/UserServiceImplTest.java @@ -0,0 +1,160 @@ +package ru.practicum.shareit.user.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import ru.practicum.shareit.exception.DuplicatedDataException; +import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.user.User; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.mappers.UserMapper; +import ru.practicum.shareit.user.repository.InMemoryUserRepository; +import ru.practicum.shareit.utils.DataUtils; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class UserServiceImplTest { + + private InMemoryUserRepository userRepository = Mockito.mock(InMemoryUserRepository.class); + private UserServiceImpl userService; + + @BeforeEach + void setUp() { + userService = new UserServiceImpl(userRepository, new UserMapper()); + } + + @Test + @DisplayName("Test create user functionality") + public void givenUserDto_whenCreteUser_thenReturnUserDto() { + //given + User user = DataUtils.getUserTestPersistence(1); + given(userRepository.create(any(User.class))).willReturn(user); + UserDto userDto = DataUtils.getUserDtoTestTransient(1); + //when + UserDto userDtoCreated = userService.create(userDto); + //then + assertThat(userDtoCreated).isNotNull(); + assertThat(userDtoCreated.getId()).isEqualTo(user.getId()); + } + + @Test + @DisplayName("Test create user with duplicated email functionality") + public void givenUserDto_whenCreteUserWithDuplicateEmail_thenThrowException() { + //given + User user = DataUtils.getUserTestPersistence(1); + given(userRepository.getEmails()).willReturn(List.of(user.getEmail())); + UserDto userDto = DataUtils.getUserDtoTestTransient(1); + //when + assertThrows(DuplicatedDataException.class, () -> userService.create(userDto)); + //then + verify(userRepository, never()).create(any(User.class)); + } + + @Test + @DisplayName("Test get user by id functionality") + public void givenUserDto_whenGetUserById_thenUserDtoIsReturned() { + //given + User user = DataUtils.getUserTestPersistence(1); + given(userRepository.getById(anyLong())).willReturn(Optional.of(user)); + //when + UserDto userReturned = userService.getById(user.getId()); + //then + assertThat(userReturned).isNotNull(); + } + + @Test + @DisplayName("Test get user by incorrect id functionality") + public void givenUserDto_whenGetUserByIncorrectId_thenThrowException() { + //given + given(userRepository.getById(anyLong())).willReturn(Optional.empty()); + //when + //then + assertThrows(NotFoundException.class, () -> userService.getById(999)); + } + + @Test + @DisplayName("Test get all user functionality") + public void givenUserDto_whenGetAllUsers_thenUserDtoIsReturned() { + //given + User user1 = DataUtils.getUserTestPersistence(1); + User user2 = DataUtils.getUserTestPersistence(2); + given(userRepository.getAll()).willReturn(List.of(user1, user2)); + //when + List usersReturned = userService.getAll(); + //then + assertThat(usersReturned).isNotNull() + .hasSize(2); + } + + @Test + @DisplayName("Test user update functionality") + public void givenUserDto_whenUpdateUser_thenUserDtoIsUpdated() { + //given + String updateName = "update Name"; + User userUpdateName = DataUtils.getUserTestPersistence(1); + given(userRepository.getById(anyLong())).willReturn(Optional.of(userUpdateName)); + userUpdateName.setName(updateName); + given(userRepository.update(anyLong(), any(User.class))).willReturn(userUpdateName); + UserDto userDto = DataUtils.getUserDtoTestPersistence(1); + userDto.setName(updateName); + //when + UserDto userUpdated = userService.update(userDto.getId(), userDto); + //then + assertThat(userUpdated).isNotNull(); + assertThat(userUpdated.getName().equals(updateName)).isTrue(); + } + + @Test + @DisplayName("Test user update with incorrect id functionality") + public void givenUserDto_whenUpdateUserWithIncorrectId_thenThrowException() { + //given + given(userRepository.getById(anyLong())).willReturn(Optional.empty()); + //when + //then + assertThrows(NotFoundException.class, + () -> userService.update(1, DataUtils.getUserDtoTestPersistence(1))); + verify(userRepository, never()).update(anyLong(), any(User.class)); + } + + @Test + @DisplayName("Test user update with duplicate email functionality") + public void givenUserDto_whenUpdateUserWithDuplicateEmail_thenThrowException() { + //given + String updateEmail = "update@test.com"; + User user = DataUtils.getUserTestPersistence(1); + given(userRepository.getById(anyLong())).willReturn(Optional.of(user)); + given(userRepository.getEmails()).willReturn(List.of(updateEmail)); + UserDto userDto = DataUtils.getUserDtoTestPersistence(1); + userDto.setEmail(updateEmail); + //when + //then + assertThrows(DuplicatedDataException.class, () -> userService.update(userDto.getId(), userDto)); + verify(userRepository, never()).update(anyLong(), any(User.class)); + } + + @Test + @DisplayName("Test delete user functionality") + public void givenUserDto_whenDeleteUser_thenUserIsDeleted() { + //given + given(userRepository.getById(anyLong())).willReturn(Optional.empty()); + //when + userService.delete(1L); + //then + verify(userRepository, times(1)).delete(anyLong()); + assertThrows(NotFoundException.class, () -> userService.getById(1L)); + } +} \ No newline at end of file diff --git a/src/test/java/ru/practicum/shareit/utils/DataUtils.java b/src/test/java/ru/practicum/shareit/utils/DataUtils.java new file mode 100644 index 0000000..4cf9678 --- /dev/null +++ b/src/test/java/ru/practicum/shareit/utils/DataUtils.java @@ -0,0 +1,72 @@ +package ru.practicum.shareit.utils; + +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.User; +import ru.practicum.shareit.user.dto.UserDto; + +public class DataUtils { + public static User getUserTestTransient(int nameVariable) { + User user = new User(); + user.setName("Test" + nameVariable); + user.setEmail("Test" + nameVariable + "@test.com"); + return user; + } + + public static User getUserTestPersistence(int nameVariable) { + User user = new User(); + user.setId((long) nameVariable); + user.setName("Test" + nameVariable); + user.setEmail("Test" + nameVariable + "@test.com"); + return user; + } + + public static UserDto getUserDtoTestTransient(int nameVariable) { + UserDto userDto = new UserDto(); + userDto.setName("Test" + nameVariable); + userDto.setEmail("Test" + nameVariable + "@test.com"); + return userDto; + } + + public static UserDto getUserDtoTestPersistence(int nameVariable) { + UserDto userDto = new UserDto(); + userDto.setId((long) nameVariable); + userDto.setName("Test" + nameVariable); + userDto.setEmail("Test" + nameVariable + "@test.com"); + return userDto; + } + + + public static Item getItemTestTransient(int nameVariable) { + Item item = new Item(); + item.setName("Test" + nameVariable); + item.setDescription("Test" + nameVariable); + item.setAvailable(true); + return item; + } + + public static Item getItemTestPersistence(int nameVariable) { + Item item = new Item(); + item.setId((long) nameVariable); + item.setName("Test" + nameVariable); + item.setDescription("Test" + nameVariable); + return item; + } + + public static ItemDto getItemDtoTestTransient(int nameVariable) { + ItemDto itemDto = new ItemDto(); + itemDto.setName("Test" + nameVariable); + itemDto.setDescription("Test" + nameVariable); + itemDto.setAvailable(true); + return itemDto; + } + + public static ItemDto getItemDtoTestPersistence(int nameVariable) { + ItemDto itemDto = new ItemDto(); + itemDto.setId((long) nameVariable); + itemDto.setName("Test" + nameVariable); + itemDto.setDescription("Test" + nameVariable); + itemDto.setAvailable(true); + return itemDto; + } +} From 52aba5621f977d2c20a088e3c9d0fcb7429a2408 Mon Sep 17 00:00:00 2001 From: Denis Date: Thu, 18 Jul 2024 22:22:11 +0500 Subject: [PATCH 2/3] fix: reformat code --- .../shareit/item/ItemControllerTest.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java b/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java index b3700fc..ea659d4 100644 --- a/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java +++ b/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java @@ -38,8 +38,8 @@ class ItemControllerTest { private ObjectMapper objectMapper; @MockBean private ItemService itemService; - private final static String URL = "/items"; - private final static String USER_ID = "X-Sharer-User-Id"; + private static final String URL = "/items"; + private static final String USER_ID = "X-Sharer-User-Id"; @Test @DisplayName("Test create item functionality") @@ -92,8 +92,8 @@ public void givenItemDto_whenCreateItemWithNullName_thenThrowException() throws .header(USER_ID, 1)); //then result.andExpect(status().isBadRequest()) - .andExpect(responseBody(). - containsErrorValid("Invalid value of the create.itemDto.name parameter: " + + .andExpect(responseBody() + .containsErrorValid("Invalid value of the create.itemDto.name parameter: " + "The name should not be blank")); } @@ -111,8 +111,8 @@ public void givenItemDto_whenCreateItemWithNullDescription_thenThrowException() .header(USER_ID, 1)); //then result.andExpect(status().isBadRequest()) - .andExpect(responseBody(). - containsErrorValid("Invalid value of the create.itemDto.description parameter: " + + .andExpect(responseBody() + .containsErrorValid("Invalid value of the create.itemDto.description parameter: " + "The description should not be null")); } @@ -130,8 +130,8 @@ public void givenItemDto_whenCreateItemWithNullAvailable_thenThrowException() th .header(USER_ID, 1)); //then result.andExpect(status().isBadRequest()) - .andExpect(responseBody(). - containsErrorValid("Invalid value of the create.itemDto.available parameter: " + + .andExpect(responseBody() + .containsErrorValid("Invalid value of the create.itemDto.available parameter: " + "The available should not be null")); } @@ -148,8 +148,8 @@ public void givenItemDto_whenCreateItemWithNotNullID_thenThrowException() throws .header(USER_ID, 1)); //then result.andExpect(status().isBadRequest()) - .andExpect(responseBody(). - containsErrorValid("Invalid value of the create.itemDto.id parameter: " + + .andExpect(responseBody() + .containsErrorValid("Invalid value of the create.itemDto.id parameter: " + "The ID must be null")); } From a16166fc8c25bc82a569a09c5aac59a94474c701 Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 21 Jul 2024 17:18:17 +0500 Subject: [PATCH 3/3] fix: reformat code --- .../ru/practicum/shareit/item/mappers/ItemMapper.java | 9 ++++++++- .../ru/practicum/shareit/item/service/ItemService.java | 1 - .../practicum/shareit/item/service/ItemServiceImpl.java | 4 ++-- .../ru/practicum/shareit/user/mappers/UserMapper.java | 8 ++++++++ .../practicum/shareit/user/service/UserServiceImpl.java | 4 +--- 5 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/main/java/ru/practicum/shareit/item/mappers/ItemMapper.java b/src/main/java/ru/practicum/shareit/item/mappers/ItemMapper.java index 8af7ffb..4c9e364 100644 --- a/src/main/java/ru/practicum/shareit/item/mappers/ItemMapper.java +++ b/src/main/java/ru/practicum/shareit/item/mappers/ItemMapper.java @@ -5,9 +5,12 @@ import ru.practicum.shareit.item.model.Item; import ru.practicum.shareit.user.User; +import java.util.List; + @Component public class ItemMapper { public ItemDto toDto(Item item) { + if (item == null) return null; ItemDto dto = new ItemDto(); dto.setId(item.getId()); dto.setName(item.getName()); @@ -17,6 +20,7 @@ public ItemDto toDto(Item item) { } public Item fromDto(ItemDto dto, User user) { + if (dto == null) return null; Item item = new Item(); item.setName(dto.getName()); item.setDescription(dto.getDescription()); @@ -25,5 +29,8 @@ public Item fromDto(ItemDto dto, User user) { return item; } - + public List toDtoList(List items) { + if (items == null) return null; + return items.stream().map(this::toDto).toList(); + } } diff --git a/src/main/java/ru/practicum/shareit/item/service/ItemService.java b/src/main/java/ru/practicum/shareit/item/service/ItemService.java index 4317afd..46e3126 100644 --- a/src/main/java/ru/practicum/shareit/item/service/ItemService.java +++ b/src/main/java/ru/practicum/shareit/item/service/ItemService.java @@ -10,7 +10,6 @@ public interface ItemService { ItemDto create(ItemDto itemDto, Long userId); - ItemDto update(ItemDto itemDto, Long userId); ItemDto getById(Long id); 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 701d890..198d528 100644 --- a/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java +++ b/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java @@ -61,7 +61,7 @@ public ItemDto getById(Long id) { public List getAllOfOwner(Long userId) { checkIfExistsUser(userId); List items = itemRepository.getAllOfOwner(userId); - return items.stream().map(itemMapper::toDto).toList(); + return itemMapper.toDtoList(items); } @Override @@ -70,7 +70,7 @@ public List findByNameByDescription(String text) { return new ArrayList<>(); } List items = itemRepository.findByNameByDescription(text); - return items.stream().map(itemMapper::toDto).toList(); + return itemMapper.toDtoList(items); } private void checkIfExistsUser(Long userId) { diff --git a/src/main/java/ru/practicum/shareit/user/mappers/UserMapper.java b/src/main/java/ru/practicum/shareit/user/mappers/UserMapper.java index 3ee7331..f8f407a 100644 --- a/src/main/java/ru/practicum/shareit/user/mappers/UserMapper.java +++ b/src/main/java/ru/practicum/shareit/user/mappers/UserMapper.java @@ -4,9 +4,12 @@ import ru.practicum.shareit.user.User; import ru.practicum.shareit.user.dto.UserDto; +import java.util.List; + @Component public class UserMapper { public UserDto toUserDto(User user) { + if (user == null) return null; UserDto userDto = new UserDto(); userDto.setId(user.getId()); userDto.setName(user.getName()); @@ -15,10 +18,15 @@ public UserDto toUserDto(User user) { } public User toNewUser(UserDto userDto) { + if (userDto == null) return null; User user = new User(); user.setName(userDto.getName()); user.setEmail(userDto.getEmail()); return user; } + public List toUserDtoList(List users) { + if (users == null) return null; + return users.stream().map(this::toUserDto).toList(); + } } diff --git a/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java b/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java index ca70d92..7c05d60 100644 --- a/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java +++ b/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java @@ -40,9 +40,7 @@ public UserDto create(UserDto dto) { @Override public List getAll() { List users = userRepository.getAll(); - return users.stream() - .map(userMapper::toUserDto) - .toList(); + return userMapper.toUserDtoList(users); } @Override