diff --git a/pom.xml b/pom.xml index f81fee6..383688d 100644 --- a/pom.xml +++ b/pom.xml @@ -37,6 +37,11 @@ org.springframework.boot spring-boot-starter-validation + + org.zalando + logbook-spring-boot-starter + 3.7.2 + diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java b/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java new file mode 100644 index 0000000..d5053c7 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java @@ -0,0 +1,31 @@ +package ru.yandex.practicum.filmorate.controller; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.exception.ValidationException; +import ru.yandex.practicum.filmorate.model.ErrorResponse; + +@RestControllerAdvice +public class ErrorHandler { + @ExceptionHandler(value = {MethodArgumentNotValidException.class, ValidationException.class}) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleValidationException(final Exception e) { + return new ErrorResponse("Ошибка валидации", e.getMessage()); + } + + @ExceptionHandler + @ResponseStatus(HttpStatus.NOT_FOUND) + public ErrorResponse handleNotFoundException(final NotFoundException e) { + return new ErrorResponse("Объект не найден", e.getMessage()); + } + + @ExceptionHandler + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ErrorResponse handleThrowable(final Throwable e) { + return new ErrorResponse("Произошла непредвиденная ошибка", e.getMessage()); + } +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java index 8c62826..0e16103 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -1,124 +1,99 @@ package ru.yandex.practicum.filmorate.controller; import jakarta.validation.Valid; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import ru.yandex.practicum.filmorate.exception.ValidationException; import ru.yandex.practicum.filmorate.model.Film; - -import java.time.LocalDate; -import java.time.Month; -import java.util.ArrayList; -import java.util.HashMap; +import ru.yandex.practicum.filmorate.service.FilmService; import java.util.List; @RestController @RequestMapping("/films") public class FilmController { - private static final int FILM_DESCRIPTION_LENGTH = 200; - private static final LocalDate EARLIEST_RELEASE_DATE = LocalDate.of(1895, Month.DECEMBER, 28); - private static final Logger log = LoggerFactory.getLogger(FilmController.class); - private final HashMap films = new HashMap<>(); + private final FilmService filmService; + + @Autowired + public FilmController(FilmService filmService) { + this.filmService = filmService; + } + + /** + * Возвращает фильм по id + */ + @GetMapping("/{id}") + public ResponseEntity getFilmById(@PathVariable int id) { + return ResponseEntity.ok(filmService.getFilmById(id)); + } /** * Возвращает все фильмы в виде списка */ @GetMapping public ResponseEntity> getFilms() { - return ResponseEntity.ok(new ArrayList<>(films.values())); + return ResponseEntity.ok(filmService.getFilms()); } /** * Добавляет новый фильм. - * Проверяет поля фильма на соответствие. - * Если всё хорошо, то создаёт копию переданного фильма, присваивает уникальный ID и сохраняет в контроллер. * * @param newFilm объект фильма, который нужно добавить - * @return копия созданного фильма с присвоенным ID + * @return ответ, содержащий созданный фильм с присвоенным ID */ @PostMapping public ResponseEntity addFilm(@Valid @RequestBody Film newFilm) { - try { - checkIsValidFilm(newFilm); - } catch (ValidationException exception) { - log.error("Ошибка валидации данных фильма {} при добавлении: {}", newFilm.getId(), exception.getMessage()); - throw exception; - } - log.trace("Данные фильма {} прошли валидацию при добавлении", newFilm.getId()); - - newFilm.setId(getNextId()); - films.put(newFilm.getId(), newFilm); - log.info("Фильм {} добавлен", newFilm.getId()); - - return ResponseEntity.status(HttpStatus.CREATED).body(newFilm); + Film createdFilm = filmService.addFilm(newFilm); + return ResponseEntity.status(HttpStatus.CREATED).body(createdFilm); } /** * Обновляет фильм. - * Проверяет поля переданного фильма на соответствие. - * Если всё хорошо и переданный фильм существует в контроллере, то создаёт копию и сохраняет в контроллер. * * @param updatedFilm объект фильма, который нужно обновить - * @return копия обновлённого фильма + * @return ответ, содержащий обновлённый фильм */ @PutMapping public ResponseEntity updateFilm(@Valid @RequestBody Film updatedFilm) { - try { - checkIsValidFilm(updatedFilm); - } catch (ValidationException exception) { - log.error("Ошибка валидации данных фильма {} при обновлении: {}", updatedFilm.getId(), - exception.getMessage()); - throw exception; - } - - log.trace("Фильм {} прошёл валидацию для обновления", updatedFilm.getId()); - if (!films.containsKey(updatedFilm.getId())) { - String filmNotFound = "Фильм " + updatedFilm.getId() + " для обновления не найден"; - log.error(filmNotFound); - throw new ValidationException(filmNotFound); - } - - films.put(updatedFilm.getId(), updatedFilm); - log.info("Фильм {} обновлён", updatedFilm.getId()); - - return ResponseEntity.ok(updatedFilm); + Film savedFilm = filmService.updateFilm(updatedFilm); + return ResponseEntity.ok(savedFilm); } /** - * Генерирует следующий Id. - * Находит максимальный текущй Id и увеличивает его. + * Добавляет лайк к фильму + * + * @param id идентификатор фильма, которому нужно добавить лайк. + * @param userId идентификатор пользователя, кто поставил лайк. + * @return пустой ответ со статусом 200 */ - private int getNextId() { - int currentMaxId = films.keySet() - .stream() - .mapToInt(id -> id) - .max() - .orElse(0); - return ++currentMaxId; + @PutMapping("/{id}/like/{userId}") + public ResponseEntity addLikeByUser(@PathVariable int id, @PathVariable int userId) { + filmService.addLike(id, userId); + return ResponseEntity.ok().build(); } /** - * Проверяет переданный фильм на соответствие условиям. - * Если не удовлетворяет какой-то проверке, то выбрасывается ошибка + * Убирает лайк с фильма + * + * @param id идентификатор фильма, которому нужно убрать лайк. + * @param userId идентификатор пользователя, чей лайк надо убрать. + * @return пустой ответ со статусом 200 */ - private void checkIsValidFilm(Film film) throws ValidationException { - if (film == null) { - throw new ValidationException("Фильм не найден"); - } - - if (film.getDescription().length() > FILM_DESCRIPTION_LENGTH) { - throw new ValidationException("Описание фильма не может быть больше 200 символов"); - } - - if (film.getReleaseDate().isBefore(EARLIEST_RELEASE_DATE)) { - throw new ValidationException("Дата релиза фильма не может быть раньше 28 декабря 1895 года"); - } + @DeleteMapping("/{id}/like/{userId}") + public ResponseEntity removeLikeByUser(@PathVariable int id, @PathVariable int userId) { + filmService.removeLike(id, userId); + return ResponseEntity.ok().build(); + } - if (film.getDuration() <= 0) { - throw new ValidationException("Продолжительность фильма должна быть положительным числом"); - } + /** + * Возвращает самые популярные фильмы в виде списка. + * + * @param count количество фильмов, которое максимально надо вернуть + * @return список самых популярных фильмов + */ + @GetMapping("/popular") + public ResponseEntity> getMostPopularFilms( + @RequestParam(required = false, defaultValue = "10") int count) { + return ResponseEntity.ok(filmService.getMostPopularFilms(count)); } } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java index a742286..a441f3a 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java @@ -1,122 +1,110 @@ package ru.yandex.practicum.filmorate.controller; import jakarta.validation.Valid; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import ru.yandex.practicum.filmorate.exception.ValidationException; import ru.yandex.practicum.filmorate.model.User; - -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.HashMap; +import ru.yandex.practicum.filmorate.service.UserService; import java.util.List; @RestController @RequestMapping("/users") public class UserController { - private static final Logger log = LoggerFactory.getLogger(UserController.class); - private final HashMap users = new HashMap<>(); + private final UserService userService; + + @Autowired + public UserController(UserService userService) { + this.userService = userService; + } + + /** + * Возвращает пользователя по id + */ + @GetMapping("/{id}") + public ResponseEntity getUserById(@PathVariable int id) { + return ResponseEntity.ok(userService.getUserById(id)); + } /** * Возвращает всех пользователей в виде списка */ @GetMapping public ResponseEntity> getUsers() { - return ResponseEntity.ok(new ArrayList<>(users.values())); + return ResponseEntity.ok(userService.getUsers()); } /** * Добавляет нового пользователя. - * Проверяет поля пользователя на соответствие. - * Если всё хорошо, то создаёт копию переданного пользователя, присваивает уникальный ID и сохраняет в контроллер. * * @param newUser объект пользователя, который нужно добавить - * @return копия созданного пользователя с присвоенным ID + * @return ответ, содержащий созданного пользователя с присвоенным ID */ @PostMapping public ResponseEntity addUser(@Valid @RequestBody User newUser) { - try { - checkIsValidUser(newUser); - } catch (ValidationException exception) { - log.error("Ошибка валидации данных пользователя при добавлении: {}", - exception.getMessage()); - throw exception; - } - log.info("Данные пользователя прошли валидацию при добавлении"); - - newUser.setId(getNextId()); - users.put(newUser.getId(), newUser); - log.info("Пользователь {} добавлен", newUser.getId()); - - return ResponseEntity.status(HttpStatus.CREATED).body(newUser); + User createdUser = userService.addUser(newUser); + return ResponseEntity.status(HttpStatus.CREATED).body(createdUser); } /** * Обновляет пользователя. - * Проверяет поля переданного пользователя на соответствие. - * Если всё хорошо и переданный пользователь существует в контроллере, то создаёт копию и сохраняет в контроллер. * * @param updatedUser объект пользователя, который нужно обновить - * @return копия обновлённого пользователя + * @return ответ, содержащий обновлённого пользователя */ @PutMapping public ResponseEntity updateUser(@Valid @RequestBody User updatedUser) { - try { - checkIsValidUser(updatedUser); - } catch (ValidationException exception) { - log.error("Ошибка валидации данных пользователя {} при обновлении: {}", updatedUser.getId(), - exception.getMessage()); - throw exception; - } - log.info("Данные пользователя {} прошли валидацию при обновлении", updatedUser.getId()); - - if (!users.containsKey(updatedUser.getId())) { - String userNotFound = "Пользователь " + updatedUser.getId() + " для обновления не найден"; - log.error(userNotFound); - throw new ValidationException(userNotFound); - } - - users.put(updatedUser.getId(), updatedUser); - log.info("Пользователь {} обновлён", updatedUser.getId()); - - return ResponseEntity.ok(updatedUser); + User savedUser = userService.updateUser(updatedUser); + return ResponseEntity.ok(savedUser); } /** - * Генерирует следующий Id. - * Находит максимальный текущй Id и увеличивает его. + * Добавляет пользователя в друзья к другому пользователю + * + * @param id идентификатор пользователя, которому нужно добавить друга. + * @param friendId идентификатор пользователя, которого нужно добавить в друзья + * @return пустой ответ со статусом 200 */ - private int getNextId() { - int currentMaxId = users.keySet() - .stream() - .mapToInt(id -> id) - .max() - .orElse(0); - return ++currentMaxId; + @PutMapping("/{id}/friends/{friendId}") + public ResponseEntity addFriend(@PathVariable int id, @PathVariable int friendId) { + userService.addFriends(id, friendId); + return ResponseEntity.ok().build(); } /** - * Проверяет переданного пользователя на соответствие условиям. - * Если не удовлетворяет какой-то проверке, то выбрасывается ошибка + * Удаляет пользователя из друзей другого пользователя + * + * @param id идентификатор пользователя, у которого нужно удалить друга. + * @param friendId идентификатор пользователя, которого нужно удалить из друзей + * @return пустой ответ со статусом 200 */ - private void checkIsValidUser(User user) throws ValidationException { - if (user == null) { - throw new ValidationException("Пользователь не найден"); - } - - if (user.getLogin().contains(" ")) { - throw new ValidationException("Логин пользователя не может содержать пробел"); - } + @DeleteMapping("/{id}/friends/{friendId}") + public ResponseEntity removeFriend(@PathVariable int id, @PathVariable int friendId) { + userService.removeFriends(id, friendId); + return ResponseEntity.ok().build(); + } - if (user.getBirthday() != null && user.getBirthday().isAfter(LocalDate.now())) { - throw new ValidationException("Дата рождения не может быть в будущем"); - } + /** + * Возвращает друзей пользователя по его id. + * + * @param id идентификатор пользователя. + * @return список друзей-пользователей указанного пользователя. + */ + @GetMapping("/{id}/friends") + public ResponseEntity> getAllFriends(@PathVariable int id) { + return ResponseEntity.ok(userService.getUserFriendsById(id)); + } - if (user.getName() == null || user.getName().isEmpty()) { - user.setName(user.getLogin()); - } + /** + * Возвращает общих друзей пользователей по их id. + * + * @param id идентификатор пользователя, для которого ищем общих друзей. + * @param otherId идентификатор пользователя, с кем ищем общих друзей. + * @return список общих друзей в виде списка пользователей. + */ + @GetMapping("/{id}/friends/common/{otherId}") + public ResponseEntity> getCommonFriends(@PathVariable int id, @PathVariable int otherId) { + return ResponseEntity.ok(userService.getCommonFriends(id, otherId)); } } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/exception/NotFoundException.java b/src/main/java/ru/yandex/practicum/filmorate/exception/NotFoundException.java new file mode 100644 index 0000000..dd8d262 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/exception/NotFoundException.java @@ -0,0 +1,7 @@ +package ru.yandex.practicum.filmorate.exception; + +public class NotFoundException extends RuntimeException { + public NotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/ErrorResponse.java b/src/main/java/ru/yandex/practicum/filmorate/model/ErrorResponse.java new file mode 100644 index 0000000..9d04f7d --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/ErrorResponse.java @@ -0,0 +1,14 @@ +package ru.yandex.practicum.filmorate.model; + +import lombok.Getter; + +@Getter +public class ErrorResponse { + String error; + String description; + + public ErrorResponse(String error,String description) { + this.description = description; + this.error = error; + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Film.java b/src/main/java/ru/yandex/practicum/filmorate/model/Film.java index a80bbf0..e9b9a17 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Film.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Film.java @@ -5,14 +5,13 @@ import lombok.*; import java.time.LocalDate; +import java.util.Set; -/** - * Film. - */ @Data @Builder(toBuilder = true) @EqualsAndHashCode(of = { "id" }) @AllArgsConstructor +@NoArgsConstructor public class Film { private int id; @@ -26,4 +25,6 @@ public class Film { @NotNull private Integer duration; + + private Set likes; } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/User.java b/src/main/java/ru/yandex/practicum/filmorate/model/User.java index 3f13eab..4f16a1e 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/User.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/User.java @@ -2,12 +2,10 @@ import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.EqualsAndHashCode; +import lombok.*; import java.time.LocalDate; +import java.util.Set; @Data @Builder(toBuilder = true) @@ -25,4 +23,6 @@ public class User { private String login; private LocalDate birthday; + + Set friends; } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java new file mode 100644 index 0000000..5fd80e8 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java @@ -0,0 +1,148 @@ +package ru.yandex.practicum.filmorate.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import ru.yandex.practicum.filmorate.exception.ValidationException; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.storage.FilmStorage; +import ru.yandex.practicum.filmorate.storage.UserStorage; + +import java.time.LocalDate; +import java.time.Month; +import java.util.Comparator; +import java.util.List; + +@Service +public class FilmService { + private static final int FILM_DESCRIPTION_LENGTH = 200; + private static final LocalDate EARLIEST_RELEASE_DATE = LocalDate.of(1895, Month.DECEMBER, 28); + private static final Logger log = LoggerFactory.getLogger(FilmService.class); + private final FilmStorage filmStorage; + private final UserStorage userStorage; + + @Autowired + public FilmService(FilmStorage filmStorage, UserStorage userStorage) { + this.filmStorage = filmStorage; + this.userStorage = userStorage; + } + + /** + * Возвращает фильм по id. + * Вызывает метод хранилища по получению фильма по id. + */ + public Film getFilmById(int id) { + return filmStorage.getFilmById(id); + } + + /** + * Возвращает все фильмы в виде списка. + * Вызывает метод хранилища по получению всех фильмов + */ + public List getFilms() { + return filmStorage.getFilms(); + } + + /** + * Добавляет новый фильм. + * Проверяет поля переданного фильма на соответствие. + * Если всё хорошо, то вызывает соответствующий метод хранилища. + */ + public Film addFilm(Film newFilm) { + try { + checkIsValidFilm(newFilm); + } catch (ValidationException exception) { + log.error("Ошибка валидации данных фильма {} при добавлении: {}", newFilm.getId(), exception.getMessage()); + throw exception; + } + log.trace("Данные фильма {} прошли валидацию при добавлении", newFilm.getId()); + return filmStorage.addFilm(newFilm); + } + + /** + * Обновляет фильм. + * Проверяет поля переданного фильма на соответствие. + * Если всё хорошо, то вызывает соответствующий метод хранилища. + */ + public Film updateFilm(Film updatedFilm) { + try { + checkIsValidFilm(updatedFilm); + } catch (ValidationException exception) { + log.error("Ошибка валидации данных фильма {} при обновлении: {}", updatedFilm.getId(), + exception.getMessage()); + throw exception; + } + log.trace("Фильм {} прошёл валидацию для обновления", updatedFilm.getId()); + return filmStorage.updateFilm(updatedFilm); + } + + /** + * Добавляет лайк к фильму. + * Если пользователь и фильм существуют, то добавляет id пользователя в список тех, кто его лайкнул. + * Вызывает метод хранилища по обновлению фильма. + */ + public void addLike(int filmId, int userId) { + userStorage.getUserById(userId); + log.trace("Пользователь {} найден для добавления лайка", userId); + Film film = filmStorage.getFilmById(filmId); + log.trace("Фильм {} найден для добавления лайка", filmId); + + film.getLikes().add(userId); + log.info("Добавление лайка от пользователя {} для фильма {} выполнено", userId, filmId); + + filmStorage.updateFilm(film); + } + + /** + * Убирает лайк у фильма. + * Если фильм существу, то убирает id пользователя из списка тех, кто его лайкнул. + * Вызывает метод хранилища по обновлению фильма. + */ + public void removeLike(int filmId, int userId) { + userStorage.getUserById(userId); + log.trace("Пользователь {} найден для удаления лайка", userId); + Film film = filmStorage.getFilmById(filmId); + log.trace("Фильм {} найден для удаления лайка", filmId); + + film.getLikes().remove(userId); + log.info("Удаление лайка от пользователя {} для фильма {} выполнено", userId, filmId); + + filmStorage.updateFilm(film); + } + + /** + * Возвращает список самых популярных фильмов в виде списка. + * Вызывает метод хранилища по получению всех фильмов, сортирует их и фильтрует по количеству. + */ + public List getMostPopularFilms(int count) { + return filmStorage.getFilms().stream() + .sorted(Comparator.comparingInt( + (Film film) -> film.getLikes().size()).reversed().thenComparingInt(Film::getId)) + .limit(count) + .toList(); + } + + /** + * Проверяет переданный фильм на соответствие условиям. + * Если не удовлетворяет какой-то проверке, то выбрасывается ошибка + */ + private void checkIsValidFilm(Film film) throws ValidationException { + if (film == null) { + throw new ValidationException("Фильм для валидации входных параметров не найден"); + } + + if (film.getDescription().length() > FILM_DESCRIPTION_LENGTH) { + throw new ValidationException("Описание фильма не может быть больше 200 символов"); + } + + if (film.getReleaseDate().isBefore(EARLIEST_RELEASE_DATE)) { + throw new ValidationException("Дата релиза фильма не может быть раньше 28 декабря 1895 года"); + } + + if (film.getDuration() <= 0) { + throw new ValidationException("Продолжительность фильма должна быть положительным числом"); + } + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java new file mode 100644 index 0000000..5ef5d21 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java @@ -0,0 +1,168 @@ +package ru.yandex.practicum.filmorate.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.exception.ValidationException; +import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.storage.UserStorage; + +import java.time.LocalDate; +import java.util.List; + +@Service +public class UserService { + private final UserStorage userStorage; + private static final Logger log = LoggerFactory.getLogger(UserService.class); + + @Autowired + public UserService(UserStorage userStorage) { + this.userStorage = userStorage; + } + + /** + * Возвращает пользователя по id. + * Вызывает метод хранилища по получению пользователя по id. + */ + public User getUserById(int id) { + return userStorage.getUserById(id); + } + + /** + * Возвращает всех пользователей в виде списка. + * Вызывает соответствующий метод хранилища. + */ + public List getUsers() { + return userStorage.getUsers(); + } + + /** + * Добавляет нового пользователя. + * Проверяет поля пользователя на соответствие. + * Если всё хорошо, то вызывает соответствующий метод хранилища. + * + * @param newUser объект пользователя, который нужно добавить + * @return созданный пользователь с присвоенным ID + */ + public User addUser(User newUser) { + try { + checkIsValidUser(newUser); + } catch (ValidationException exception) { + log.error("Ошибка валидации данных пользователя при добавлении: {}", + exception.getMessage()); + throw exception; + } + log.trace("Данные новго пользователя c логином {} прошли валидацию при добавлении", newUser.getLogin()); + return userStorage.addUser(newUser); + } + + /** + * Обновляет пользователя. + * Проверяет поля переданного пользователя на соответствие. + * Если всё хорошо, то вызывает соответствующий метод хранилища. + * + * @param updatedUser объект пользователя, который нужно обновить + * @return обновлённый пользователь + */ + public User updateUser(User updatedUser) { + try { + checkIsValidUser(updatedUser); + } catch (ValidationException exception) { + log.error("Ошибка валидации данных пользователя {} при обновлении: {}", updatedUser.getId(), + exception.getMessage()); + throw exception; + } + log.info("Данные пользователя {} прошли валидацию при обновлении", updatedUser.getId()); + + return userStorage.updateUser(updatedUser); + } + + /** + * Добавляет пользователей в друзья. + * Если оба пользователя существуют, то обновляет их списки друзей и обновляет пользователей в хранилище. + */ + public void addFriends(int firstUserId, int secondUserId) { + User firstUser = userStorage.getUserById(firstUserId); + log.trace("Пользователь {} найден для добавления в друзья", firstUserId); + User secondUser = userStorage.getUserById(secondUserId); + log.trace("Пользователь {} найден для добавления в друзья", secondUserId); + + firstUser.getFriends().add(secondUserId); + secondUser.getFriends().add(firstUser.getId()); + log.info("Пользователь {} добавлен в друзья пользователя {}.", + secondUserId, firstUserId); + + userStorage.updateUser(firstUser); + userStorage.updateUser(secondUser); + log.trace("Пользователи обновлены в хранилище."); + } + + /** + * Удаляет пользователей из друзей. + * Если оба пользователя существуют, то обновляет их списки друзей и передаёт обновлённых пользователей в мапу. + */ + public void removeFriends(int firstUserId, int secondUserId) { + User firstUser = userStorage.getUserById(firstUserId); + log.trace("Пользователь {} найден для удаления мз друзей", firstUserId); + User secondUser = userStorage.getUserById(secondUserId); + log.trace("Пользователь {} найден для удаления мз друзей", secondUserId); + + firstUser.getFriends().remove(secondUserId); + secondUser.getFriends().remove(firstUserId); + log.info("Пользователь {} удален из друзей пользователя {}.", + secondUserId, firstUserId); + + userStorage.updateUser(firstUser); + userStorage.updateUser(secondUser); + log.trace("Пользователи обновлены в хранилище."); + } + + /** + * Ищет общих друзей у пользователей + * Если оба пользователя существуют, то возвращает список общих друзей. + */ + public List getCommonFriends(int firstUserId, int secondUserId) { + List firstFriends = getUserFriendsById(firstUserId); + List secondFriends = getUserFriendsById(secondUserId); + log.trace("Списки друзей пользователей для поиска общих друзей составлены", secondUserId); + + return firstFriends.stream() + .filter(secondFriends::contains) + .toList(); + } + + /** + * Возвращает всех друзей пользователя + * Если пользователь существуюет, то возвращает список по id + */ + public List getUserFriendsById(int userId) { + User user = userStorage.getUserById(userId); + log.trace("Пользователь {} найден для поиска всех его друзей", userId); + + return user.getFriends().stream().map(id -> userStorage.getUserById(id)).toList(); + } + + /** + * Проверяет переданного пользователя на соответствие условиям. + * Если не удовлетворяет какой-то проверке, то выбрасывается ошибка + */ + private void checkIsValidUser(User user) throws ValidationException { + if (user == null) { + throw new NotFoundException("Пользователь для валидации входных параметров не найден"); + } + + if (user.getLogin().contains(" ")) { + throw new ValidationException("Логин пользователя не может содержать пробел"); + } + + if (user.getBirthday() != null && user.getBirthday().isAfter(LocalDate.now())) { + throw new ValidationException("Дата рождения не может быть в будущем"); + } + + if (user.getName() == null || user.getName().isEmpty()) { + user.setName(user.getLogin()); + } + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java new file mode 100644 index 0000000..8ba57f5 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java @@ -0,0 +1,26 @@ +package ru.yandex.practicum.filmorate.storage; + +import ru.yandex.practicum.filmorate.model.Film; +import java.util.List; + +public interface FilmStorage { + /** + * Возвращает фильм по id + */ + Film getFilmById(int id); + + /** + * Возвращает все фильмы в виде списка + */ + List getFilms(); + + /** + * Добавляет новый фильм. + */ + Film addFilm(Film newFilm); + + /** + * Обновляет фильм. + */ + Film updateFilm(Film updatedFilm); +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java new file mode 100644 index 0000000..e128d75 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java @@ -0,0 +1,110 @@ +package ru.yandex.practicum.filmorate.storage; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.model.Film; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +@Component +public class InMemoryFilmStorage implements FilmStorage { + private final HashMap films = new HashMap<>(); + private static final Logger log = LoggerFactory.getLogger(InMemoryFilmStorage.class); + + /** + * Возвращает фильм по id + */ + @Override + public Film getFilmById(int id) { + if (films.get(id) == null) { + String filmNotFound = "Фильм по указанному id " + id + " не найден."; + log.error(filmNotFound); + throw new NotFoundException(filmNotFound); + } + return films.get(id); + } + + /** + * Возвращает все фильмы в виде списка + */ + @Override + public List getFilms() { + return new ArrayList<>(films.values()); + } + + /** + * Добавляет новый фильм. + * Создаёт копию переданного фильма, присваивает уникальный ID и сохраняет в набор фильмов. + * + * @param newFilm объект фильма, который нужно добавить + * @return созданный фильм с присвоенным ID + */ + @Override + public Film addFilm(Film newFilm) { + if (newFilm == null) { + String filmNotFound = "Фильм для добавления не найден"; + log.error(filmNotFound); + throw new NotFoundException(filmNotFound); + } + + newFilm.setId(getNextId()); + log.info("Фильму {} присвоен id {}", newFilm.getName(), newFilm.getId()); + + if (newFilm.getLikes() == null) { + newFilm.setLikes(new HashSet<>()); + } + + films.put(newFilm.getId(), newFilm); + log.info("Фильм {} добавлен в хранилище", newFilm.getId()); + return newFilm; + } + + /** + * Обновляет фильм. + * Проверяет поля переданного фильма на соответствие. + * Если всё хорошо и переданный фильм существует в контроллере, то создаёт копию и сохраняет в контроллер. + * + * @param updatedFilm объект фильма, который нужно обновить + * @return копия обновлённого фильма + */ + @Override + public Film updateFilm(Film updatedFilm) { + if (updatedFilm == null) { + String filmNotFound = "Фильм для обновления не найден"; + log.error(filmNotFound); + throw new NotFoundException(filmNotFound); + } + + if (!films.containsKey(updatedFilm.getId())) { + String filmNotFoundInStorage = "Фильм " + updatedFilm.getId() + " для обновления не найден в хранилище"; + log.error(filmNotFoundInStorage); + throw new NotFoundException(filmNotFoundInStorage); + } + + if (updatedFilm.getLikes() == null) { + updatedFilm.setLikes(new HashSet<>()); + } + + films.put(updatedFilm.getId(), updatedFilm); + log.info("Фильм {} обновлён в хранилище", updatedFilm.getId()); + return updatedFilm; + } + + /** + * Генерирует следующий Id. + * Находит максимальный текущй Id и увеличивает его. + */ + private int getNextId() { + int currentMaxId = films.keySet() + .stream() + .mapToInt(id -> id) + .max() + .orElse(0); + return ++currentMaxId; + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java new file mode 100644 index 0000000..1af6df2 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java @@ -0,0 +1,108 @@ +package ru.yandex.practicum.filmorate.storage; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.model.User; + +import java.util.*; + +@Component +public class InMemoryUserStorage implements UserStorage { + private static final Logger log = LoggerFactory.getLogger(InMemoryUserStorage.class); + private final HashMap users = new HashMap<>(); + + /** + * Возвращает пользователя по id + */ + @Override + public User getUserById(int id) { + if (users.get(id) == null) { + String userNotFound = "Пользователь с id = " + id + " не найден"; + log.error(userNotFound); + throw new NotFoundException(userNotFound); + } + + return users.get(id); + } + + /** + * Возвращает всех пользователей в виде списка + */ + @Override + public List getUsers() { + return new ArrayList<>(users.values()); + } + + /** + * Добавляет нового пользователя. + * Присваивает уникальный ID и сохраняет в набор пользователей. + * + * @param newUser объект пользователя, который нужно добавить + * @return созданный пользователь с присвоенным ID + */ + @Override + public User addUser(User newUser) { + if (newUser == null) { + String userNotFound = "Пользователь для добавления не найден"; + log.error(userNotFound); + throw new NotFoundException(userNotFound); + } + + newUser.setId(getNextId()); + log.info("Пользователю с логином {} присвоен id {}", newUser.getLogin(), newUser.getId()); + + if (newUser.getFriends() == null) { + newUser.setFriends(new HashSet<>()); + } + + users.put(newUser.getId(), newUser); + log.info("Пользователь {} добавлен в хранилище", newUser.getId()); + return newUser; + } + + /** + * Обновляет пользователя. + * Если переданный пользователь существует, то обновляет пользователя в наборе пользователей. + * + * @param updatedUser объект пользователя, который нужно обновить + * @return обновлённый пользователь + */ + @Override + public User updateUser(User updatedUser) { + if (updatedUser == null) { + String userNotFound = "Пользователь для обновления не найден"; + log.error(userNotFound); + throw new NotFoundException(userNotFound); + } + + if (!users.containsKey(updatedUser.getId())) { + String userNotFound = "Пользователь " + updatedUser.getId() + " для обновления не найден в хранилище"; + log.error(userNotFound); + throw new NotFoundException(userNotFound); + } + + if (updatedUser.getFriends() == null) { + updatedUser.setFriends(new HashSet<>()); + } + + users.put(updatedUser.getId(), updatedUser); + log.info("Пользователь {} обновлён", updatedUser.getId()); + + return updatedUser; + } + + /** + * Генерирует следующий Id. + * Находит максимальный текущй Id и увеличивает его. + */ + private int getNextId() { + int currentMaxId = users.keySet() + .stream() + .mapToInt(id -> id) + .max() + .orElse(0); + return ++currentMaxId; + } +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java new file mode 100644 index 0000000..7a01079 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java @@ -0,0 +1,36 @@ +package ru.yandex.practicum.filmorate.storage; + +import ru.yandex.practicum.filmorate.model.User; +import java.util.List; + +public interface UserStorage { + /** + * Возвращает пользователя по id + */ + User getUserById(int id); + + /** + * Возвращает всех пользователей в виде списка + */ + List getUsers(); + + /** + * Добавляет нового пользователя. + * Проверяет поля пользователя на соответствие. + * Если всё хорошо, то создаёт копию переданного пользователя, присваивает уникальный ID и сохраняет в контроллер. + * + * @param newUser объект пользователя, который нужно добавить + * @return копия созданного пользователя с присвоенным ID + */ + User addUser(User newUser); + + /** + * Обновляет пользователя. + * Проверяет поля переданного пользователя на соответствие. + * Если всё хорошо и переданный пользователь существует в контроллере, то создаёт копию и сохраняет в контроллер. + * + * @param updatedUser объект пользователя, который нужно обновить + * @return копия обновлённого пользователя + */ + User updateUser(User updatedUser); +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8b13789..3eb70b5 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,2 @@ +logging.level.org.zalando.logbook: TRACE diff --git a/src/test/java/ru/yandex/practicum/filmorate/controller/FilmControllerTest.java b/src/test/java/ru/yandex/practicum/filmorate/controller/FilmControllerTest.java index 6ec6c48..4a321e2 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/controller/FilmControllerTest.java +++ b/src/test/java/ru/yandex/practicum/filmorate/controller/FilmControllerTest.java @@ -3,8 +3,18 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.exception.ValidationException; import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.service.FilmService; +import ru.yandex.practicum.filmorate.service.UserService; +import ru.yandex.practicum.filmorate.storage.FilmStorage; +import ru.yandex.practicum.filmorate.storage.InMemoryFilmStorage; +import ru.yandex.practicum.filmorate.storage.InMemoryUserStorage; +import ru.yandex.practicum.filmorate.storage.UserStorage; import java.time.LocalDate; import java.time.Month; @@ -28,12 +38,29 @@ class FilmControllerTest { private static final int FILM_DURATION_2 = 200; private static final int FILM_DURATION_INCORRECT = -100; + private static final int FILM_ID = 100; + private static final int FILM_COUNT_1 = 1; + private static final int FILM_DEFAULT_COUNT = 10; + + private static final String USER_NAME = "Вася"; + private static final String USER_EMAIL = "mail@mail.ru"; + private static final String USER_LOGIN = "user"; + private static final LocalDate USER_BIRTHDAY = LocalDate.of(1992, Month.DECEMBER, 12); + private static final Integer USER_ID = 567; + FilmController filmController; + UserController userController; @BeforeEach @DisplayName("Инициализирует контроллер") void setUp() { - filmController = new FilmController(); + UserStorage userStorage = new InMemoryUserStorage(); + UserService userService = new UserService(userStorage); + userController = new UserController(userService); + + FilmStorage filmStorage = new InMemoryFilmStorage(); + FilmService filmService = new FilmService(filmStorage, userStorage); + filmController = new FilmController(filmService); } @Test @@ -187,4 +214,173 @@ void test_updateFilm_WhenIncorrectReleaseDate_ShouldThrowsError() { "Фильм может быть обновлён с некорректной датой релиза"); } + @Test + @DisplayName("При получении существующего фильма по id контроллер должен вернуть его") + void test_getFilmById_WhenFilmExists_ShouldReturnOne() { + //given + Film film = Film.builder().name(FILM_NAME).description(FILM_DESCRIPTION).releaseDate(FILM_RELEASE_DATE) + .duration(FILM_DURATION).build(); + Film createdFilm = filmController.addFilm(film).getBody(); + + //when + Film findFilm = filmController.getFilmById(createdFilm.getId()).getBody(); + + //then + assertNotNull(findFilm, "В контроллере нет фильмов"); + assertEquals(FILM_NAME, findFilm.getName(), "В контроллере некорректный фильм"); + } + + @Test + @DisplayName("При получении несуществующего фильма по id контроллер должен вернуть ошибку") + void test_getFilmById_WhenFilmNotExists_ShouldThrowsError() { + //given && when && then + assertThrows(NotFoundException.class, + () -> filmController.getFilmById(FILM_ID).getBody(), + "Из контроллера получен несуществующий фильм"); + } + + @Test + @DisplayName("При добавлении лайка существующему фильму от существующего пользователя должен вернуться OK 200") + void test_addLikeByUser_WhenFilmAndUserExist_ShouldReturn200() { + //given + Film film = Film.builder().name(FILM_NAME).description(FILM_DESCRIPTION).releaseDate(FILM_RELEASE_DATE) + .duration(FILM_DURATION).build(); + Film createdFilm = filmController.addFilm(film).getBody(); + + User user = User.builder().name(USER_NAME).email(USER_EMAIL).login(USER_LOGIN).birthday(USER_BIRTHDAY).build(); + User createdUser = userController.addUser(user).getBody(); + + + //when + ResponseEntity response = filmController.addLikeByUser(createdFilm.getId(), createdUser.getId()); + + //then + assertEquals(HttpStatus.OK, response.getStatusCode(), "Вернулся не успешный ответ"); + } + + @Test + @DisplayName("При добавлении лайка существующему фильму от несуществующего пользователя должен вернуть ошибку") + void test_addLikeByUser_WhenUserNotExists_ShouldThrowsError() { + //given && when + Film film = Film.builder().name(FILM_NAME).description(FILM_DESCRIPTION).releaseDate(FILM_RELEASE_DATE) + .duration(FILM_DURATION).build(); + Film createdFilm = filmController.addFilm(film).getBody(); + + //then + assertThrows(NotFoundException.class, () -> filmController.addLikeByUser(createdFilm.getId(), USER_ID), + "Удалось поставить лайк от несуществующего пользователя"); + } + + @Test + @DisplayName("При добавлении лайка несуществующему фильму от существующего пользователя должен вернуть ошибку") + void test_addLikeByUser_WhenFilmNotExists_ShouldThrowsError() { + //given && when + User user = User.builder().name(USER_NAME).email(USER_EMAIL).login(USER_LOGIN).birthday(USER_BIRTHDAY).build(); + User createdUser = userController.addUser(user).getBody(); + + //then + assertThrows(NotFoundException.class, () -> filmController.addLikeByUser(FILM_ID, createdUser.getId()), + "Удалось поставить лайк несуществующему фильму"); + } + + @Test + @DisplayName("При удалении лайка с существующего фильма от существующего пользователя должен вернуть OK 200") + void test_removeLikeByUser_WhenFilmAndUserExist_ShouldReturn200() { + //given + Film film = Film.builder().name(FILM_NAME).description(FILM_DESCRIPTION).releaseDate(FILM_RELEASE_DATE) + .duration(FILM_DURATION).build(); + Film createdFilm = filmController.addFilm(film).getBody(); + + User user = User.builder().name(USER_NAME).email(USER_EMAIL).login(USER_LOGIN).birthday(USER_BIRTHDAY).build(); + User createdUser = userController.addUser(user).getBody(); + + //when + filmController.addLikeByUser(createdFilm.getId(), createdUser.getId()); + ResponseEntity response = filmController.removeLikeByUser(createdFilm.getId(), createdUser.getId()); + + //then + assertEquals(HttpStatus.OK, response.getStatusCode(), "Вернулся не успешный ответ"); + } + + @Test + @DisplayName("При удалении лайка с существующего фильма от несуществующего пользователя должен вернуть ошибку") + void test_removeLikeByUser_WhenUserNotExists_ShouldThrowsError() { + //given && when + Film film = Film.builder().name(FILM_NAME).description(FILM_DESCRIPTION).releaseDate(FILM_RELEASE_DATE) + .duration(FILM_DURATION).build(); + Film createdFilm = filmController.addFilm(film).getBody(); + + //then + assertThrows(NotFoundException.class, () -> filmController.removeLikeByUser(createdFilm.getId(), USER_ID), + "Удалось убрать лайк от несуществующего пользователя"); + } + + @Test + @DisplayName("При удалении лайка с несуществующего фильма от существующего пользователя должен вернуть ошибку") + void test_removeLikeByUser_WhenFilmNotExists_ShouldThrowsError() { + //given && when + User user = User.builder().name(USER_NAME).email(USER_EMAIL).login(USER_LOGIN).birthday(USER_BIRTHDAY).build(); + User createdUser = userController.addUser(user).getBody(); + + //then + assertThrows(NotFoundException.class, () -> filmController.removeLikeByUser(FILM_ID, createdUser.getId()), + "Удалось убрать лайк с несуществующего фильма"); + } + + @Test + @DisplayName("При запросе одного фильма должен вернуть самый популярный") + void test_getMostPopularFilms_WhenRequestOne_ShouldReturnOne() { + //given + Film film = Film.builder().name(FILM_NAME).description(FILM_DESCRIPTION).releaseDate(FILM_RELEASE_DATE) + .duration(FILM_DURATION).build(); + Film film2 = Film.builder().name(FILM_NAME_2).description(FILM_DESCRIPTION_2).releaseDate(FILM_RELEASE_DATE_2) + .duration(FILM_DURATION_2).build(); + Film createdFilm = filmController.addFilm(film).getBody(); + Film createdFilm2 = filmController.addFilm(film2).getBody(); + + User user = User.builder().name(USER_NAME).email(USER_EMAIL).login(USER_LOGIN).birthday(USER_BIRTHDAY).build(); + User createdUser = userController.addUser(user).getBody(); + User user2 = User.builder().name(USER_NAME).email(USER_EMAIL).login(USER_LOGIN).birthday(USER_BIRTHDAY).build(); + User createdUser2 = userController.addUser(user2).getBody(); + + //when + filmController.addLikeByUser(createdFilm.getId(), createdUser.getId()); + filmController.addLikeByUser(createdFilm2.getId(), createdUser.getId()); + filmController.addLikeByUser(createdFilm2.getId(), createdUser2.getId()); + List popularFilms = filmController.getMostPopularFilms(FILM_COUNT_1).getBody(); + + //then + assertNotNull(popularFilms, "В контроллере нет популярных фильмов"); + assertEquals(1, popularFilms.size(), "В контроллере не верное количество фильмов"); + assertEquals(FILM_NAME_2, popularFilms.get(0).getName(), "В контроллере не корректный фильм"); + } + + @Test + @DisplayName("Если передано дефолтное количество фильмов, то должен вернуть все (до дефолтного включительно)") + void test_getMostPopularFilms_WhenRequestNothing_ShouldReturnAll() { + //given + Film film = Film.builder().name(FILM_NAME).description(FILM_DESCRIPTION).releaseDate(FILM_RELEASE_DATE) + .duration(FILM_DURATION).build(); + Film film2 = Film.builder().name(FILM_NAME_2).description(FILM_DESCRIPTION_2).releaseDate(FILM_RELEASE_DATE_2) + .duration(FILM_DURATION_2).build(); + Film createdFilm = filmController.addFilm(film).getBody(); + Film createdFilm2 = filmController.addFilm(film2).getBody(); + + User user = User.builder().name(USER_NAME).email(USER_EMAIL).login(USER_LOGIN).birthday(USER_BIRTHDAY).build(); + User createdUser = userController.addUser(user).getBody(); + User user2 = User.builder().name(USER_NAME).email(USER_EMAIL).login(USER_LOGIN).birthday(USER_BIRTHDAY).build(); + User createdUser2 = userController.addUser(user2).getBody(); + + //when + filmController.addLikeByUser(createdFilm.getId(), createdUser.getId()); + filmController.addLikeByUser(createdFilm2.getId(), createdUser.getId()); + filmController.addLikeByUser(createdFilm2.getId(), createdUser2.getId()); + List popularFilms = filmController.getMostPopularFilms(FILM_DEFAULT_COUNT).getBody(); + + //then + assertNotNull(popularFilms, "В контроллере нет популярных фильмов"); + assertEquals(2, popularFilms.size(), "В контроллере не верное количество фильмов"); + assertEquals(FILM_NAME_2, popularFilms.get(0).getName(), "В контроллере не корректный 1-ый фильм"); + assertEquals(FILM_NAME, popularFilms.get(1).getName(), "В контроллере не корректный 2-ой фильм"); + } } \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/controller/UserControllerTest.java b/src/test/java/ru/yandex/practicum/filmorate/controller/UserControllerTest.java index 7666f1b..3c76ba9 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/controller/UserControllerTest.java +++ b/src/test/java/ru/yandex/practicum/filmorate/controller/UserControllerTest.java @@ -3,8 +3,14 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.exception.ValidationException; import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.service.UserService; +import ru.yandex.practicum.filmorate.storage.InMemoryUserStorage; +import ru.yandex.practicum.filmorate.storage.UserStorage; import java.time.LocalDate; import java.time.Month; @@ -15,24 +21,32 @@ class UserControllerTest { private static final String USER_NAME = "Вася"; private static final String USER_NAME_2 = "Петя"; + private static final String USER_NAME_3 = "Коля"; private static final String USER_EMAIL = "mail@mail.ru"; private static final String USER_EMAIL_2 = "newMail@mail.ru"; + private static final String USER_EMAIL_3 = "newNewMail@mail.ru"; private static final String USER_LOGIN = "user"; private static final String USER_LOGIN_2 = "newUser"; + private static final String USER_LOGIN_3 = "friendUser"; private static final String USER_LOGIN_INCORRECT = "new User"; private static final LocalDate USER_BIRTHDAY = LocalDate.of(1992, Month.DECEMBER, 12); private static final LocalDate USER_BIRTHDAY_2 = LocalDate.of(1982, Month.OCTOBER, 22); + private static final LocalDate USER_BIRTHDAY_3 = LocalDate.of(1968, Month.NOVEMBER, 2); private static final LocalDate USER_BIRTHDAY_INCORRECT = LocalDate.of(3035, Month.DECEMBER, 12); + private static final Integer USER_ID = 567; + UserController userController; @BeforeEach @DisplayName("Инициализирует контроллер") void setUp() { - userController = new UserController(); + UserStorage userStorage = new InMemoryUserStorage(); + UserService userService = new UserService(userStorage); + userController = new UserController(userService); } @Test @@ -68,7 +82,7 @@ void test_addUser_WhenCorrectFields_ShouldAddToController() { List usersByController = userController.getUsers().getBody(); //then - assertNotNull(usersByController,"В контроллере нет пользователей"); + assertNotNull(usersByController, "В контроллере нет пользователей"); assertEquals(1, usersByController.size(), "В контроллере не верное количество пользователей"); assertEquals(USER_NAME, usersByController.get(0).getName(), "В контроллере некорректный пользователь"); } @@ -97,7 +111,7 @@ void test_addUser_WhenNameIsNull_ShouldReturnNameWithLoginValue() { List usersByController = userController.getUsers().getBody(); //then - assertNotNull(usersByController,"В контроллере нет пользователей"); + assertNotNull(usersByController, "В контроллере нет пользователей"); assertEquals(1, usersByController.size(), "В контроллере не верное количество пользователей"); assertEquals(USER_LOGIN, usersByController.get(0).getName(), "Имя пользователя не равно логину"); } @@ -128,7 +142,7 @@ void test_updateUser_WhenCorrectFields_ShouldUpdateInController() { List usersByController = userController.getUsers().getBody(); //then - assertNotNull(usersByController,"В контроллере нет пользователей"); + assertNotNull(usersByController, "В контроллере нет пользователей"); assertEquals(1, usersByController.size(), "В контроллере не верное количество пользователей"); assertEquals(USER_NAME_2, usersByController.get(0).getName(), "В контроллере некорректный пользователь"); } @@ -162,7 +176,7 @@ void test_updateUser_WhenNameIsNull_ShouldReturnNameWithLoginValue() { List usersByController = userController.getUsers().getBody(); //then - assertNotNull(usersByController,"В контроллере нет пользователей"); + assertNotNull(usersByController, "В контроллере нет пользователей"); assertEquals(1, usersByController.size(), "В контроллере не верное количество пользователей"); assertEquals(USER_LOGIN, usersByController.get(0).getName(), "Имя пользователя не равно логину"); } @@ -183,4 +197,170 @@ void test_updateUser_WhenBirthdayInFuture_ShouldThrowsError() { "Пользователь обновлён с датой рождения в будущем"); } + @Test + @DisplayName("При получении существующего пользователя по id контроллер должен вернуть его") + void test_getUserById_WhenUserExists_ShouldReturnOne() { + //given + User user = User.builder().name(USER_NAME).email(USER_EMAIL).login(USER_LOGIN).birthday(USER_BIRTHDAY).build(); + User createdUser = userController.addUser(user).getBody(); + + //when + User findUser = userController.getUserById(createdUser.getId()).getBody(); + + //then + assertNotNull(findUser, "В контроллере нет пользователей"); + assertEquals(USER_NAME, findUser.getName(), "В контроллере некорректный пользователь"); + } + + @Test + @DisplayName("При получении несуществующего пользователя по id контроллер должен выбросить ошибку") + void test_getUserById_WhenUserNotExists_ShouldThrowsError() { + //given && when && then + assertThrows(NotFoundException.class, + () -> userController.getUserById(USER_ID), + "Из контроллера получен несуществующий пользователь"); + } + + @Test + @DisplayName("При добавлении в друзья существующих пользователей ответ должен быть OK 200") + void test_addFriend_WhenUsersExist_ShouldReturn200() { + //given + User user = User.builder().name(USER_NAME).email(USER_EMAIL).login(USER_LOGIN).birthday(USER_BIRTHDAY).build(); + User user2 = User.builder().name(USER_NAME_2).email(USER_EMAIL_2).login(USER_LOGIN_2).birthday(USER_BIRTHDAY_2) + .build(); + User createdUser1 = userController.addUser(user).getBody(); + User createdUser2 = userController.addUser(user2).getBody(); + + //when + ResponseEntity response = userController.addFriend(createdUser1.getId(), createdUser2.getId()); + + //then + assertEquals(HttpStatus.OK, response.getStatusCode(), + "При добавлении в друзья вернулся не успешный ответ"); + } + + @Test + @DisplayName("При добавлении в друзья несуществующего пользователя контроллер должен выбросить ошибку") + void test_addFriend_WhenOneUserNotExists_ShouldThrowsError() { + //given && when + User user = User.builder().name(USER_NAME).email(USER_EMAIL).login(USER_LOGIN).birthday(USER_BIRTHDAY).build(); + User createdUser1 = userController.addUser(user).getBody(); + + //then + assertThrows(NotFoundException.class, + () -> userController.addFriend(createdUser1.getId(), USER_ID), + "Удалось добавить в друзья не существующего пользователя"); + } + + @Test + @DisplayName("При удалении из друзей существующих пользователей ответ должен быть OK 200") + void test_removeFriend_WhenFriendsExist_ShouldReturn200() { + //given + User user = User.builder().name(USER_NAME).email(USER_EMAIL).login(USER_LOGIN).birthday(USER_BIRTHDAY).build(); + User user2 = User.builder().name(USER_NAME_2).email(USER_EMAIL_2).login(USER_LOGIN_2).birthday(USER_BIRTHDAY_2) + .build(); + User createdUser1 = userController.addUser(user).getBody(); + User createdUser2 = userController.addUser(user2).getBody(); + userController.addFriend(createdUser1.getId(), createdUser2.getId()); + + //when + ResponseEntity response = userController.removeFriend(createdUser1.getId(), createdUser2.getId()); + + //then + assertEquals(HttpStatus.OK, response.getStatusCode(), + "При удалении из друзей вернулся не успешный ответ"); + } + + @Test + @DisplayName("При удалении из друзей несуществующего пользователя контроллер должен выбросить ошибку") + void test_removeFriend_WhenOneFriendNotExist_ShouldReturn200() { + //given && when + User user = User.builder().name(USER_NAME).email(USER_EMAIL).login(USER_LOGIN).birthday(USER_BIRTHDAY).build(); + User createdUser1 = userController.addUser(user).getBody(); + + //then + assertThrows(NotFoundException.class, + () -> userController.removeFriend(createdUser1.getId(), USER_ID), + "Удалось удалить из друзей несуществующего пользователя"); + } + + @Test + @DisplayName("При получении всех друзей пользователя должен возвращаться корректный список") + void test_getAllFriends_WhenAddFriends_ShouldReturnOne() { + //given + User user = User.builder().name(USER_NAME).email(USER_EMAIL).login(USER_LOGIN).birthday(USER_BIRTHDAY).build(); + User user2 = User.builder().name(USER_NAME_2).email(USER_EMAIL_2).login(USER_LOGIN_2).birthday(USER_BIRTHDAY_2) + .build(); + User createdUser1 = userController.addUser(user).getBody(); + User createdUser2 = userController.addUser(user2).getBody(); + + //when + userController.addFriend(createdUser1.getId(), createdUser2.getId()); + List userFriends = userController.getAllFriends(createdUser1.getId()).getBody(); + + //then + assertNotNull(userFriends, "У пользователя нет друзей"); + assertEquals(USER_NAME_2, userFriends.get(0).getName(), "В друзьях некорректный пользователь"); + } + + @Test + @DisplayName("При отсутствии друзей у пользователя должен возвращаться пустой список") + void test_getAllFriends_WhenNoFriends_ShouldReturnEmpty() { + //given + User user = User.builder().name(USER_NAME).email(USER_EMAIL).login(USER_LOGIN).birthday(USER_BIRTHDAY).build(); + User createdUser1 = userController.addUser(user).getBody(); + + //when + List userFriends = userController.getAllFriends(createdUser1.getId()).getBody(); + + //then + assertNotNull(userFriends, "Список друзей не проинициализировался"); + assertEquals(0, userFriends.size(), "Список друзей не пустой"); + } + + @Test + @DisplayName("При получении общих друзей пользователей должен возвращаться корректный список") + void test_getCommonFriends_WhenAddSameFriend_ShouldReturnOne() { + //given + User user = User.builder().name(USER_NAME).email(USER_EMAIL).login(USER_LOGIN).birthday(USER_BIRTHDAY).build(); + User user2 = User.builder().name(USER_NAME_2).email(USER_EMAIL_2).login(USER_LOGIN_2).birthday(USER_BIRTHDAY_2) + .build(); + User user3 = User.builder().name(USER_NAME_3).email(USER_EMAIL_3).login(USER_LOGIN_3).birthday(USER_BIRTHDAY_3) + .build(); + User createdUser1 = userController.addUser(user).getBody(); + User createdUser2 = userController.addUser(user2).getBody(); + User createdUser3 = userController.addUser(user3).getBody(); + + //when + userController.addFriend(createdUser1.getId(), createdUser3.getId()); + userController.addFriend(createdUser2.getId(), createdUser3.getId()); + List commonFriends = userController.getCommonFriends(createdUser1.getId(), createdUser2.getId()).getBody(); + + //then + assertNotNull(commonFriends, "У пользователя нет общих друзей"); + assertEquals(USER_NAME_3, commonFriends.get(0).getName(), "В общих друзьях некорректный пользователь"); + } + + @Test + @DisplayName("При отсутствии общих друзей у пользователей должен возвращаться пустой список") + void test_getCommonFriends_WhenAddNotSameFriend_ShouldReturnEmpty() { + //given + User user = User.builder().name(USER_NAME).email(USER_EMAIL).login(USER_LOGIN).birthday(USER_BIRTHDAY).build(); + User user2 = User.builder().name(USER_NAME_2).email(USER_EMAIL_2).login(USER_LOGIN_2).birthday(USER_BIRTHDAY_2) + .build(); + User user3 = User.builder().name(USER_NAME_3).email(USER_EMAIL_3).login(USER_LOGIN_3).birthday(USER_BIRTHDAY_3) + .build(); + User createdUser1 = userController.addUser(user).getBody(); + User createdUser2 = userController.addUser(user2).getBody(); + User createdUser3 = userController.addUser(user3).getBody(); + + //when + userController.addFriend(createdUser1.getId(), createdUser1.getId()); + userController.addFriend(createdUser2.getId(), createdUser3.getId()); + List commonFriends = userController.getCommonFriends(createdUser1.getId(), createdUser2.getId()).getBody(); + + //then + assertNotNull(commonFriends, "Список общих друзей не проинициализировался"); + assertEquals(0, commonFriends.size(), "Список общих друзей не пустой"); + } } \ No newline at end of file