diff --git a/Filmorate.png b/Filmorate.png new file mode 100644 index 0000000..744dd23 Binary files /dev/null and b/Filmorate.png differ diff --git a/README.md b/README.md index c198fee..273776a 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,17 @@ Filmorate - это RESTful API для управления фильмами и пользователями. Сейчас приложение позволяет добавлять, обновлять и просматривать информацию о фильмах и пользователях. +## Схема базы данных +(![Схема базы данных](Filmorate.png)) + +- #### films - хранит все фильмы +- #### users - хранит всех пользователей +- #### mpa - хранит MPA рейтинг +- #### genres - хранит жанры +- #### films_genres - связующая таблица между фильмами и жанрами +- #### likes - хранит лайки фильма +- #### friendships - хранит друзей пользователя + ## Модели данных ### Film diff --git a/pom.xml b/pom.xml index 0ffcf4b..3c38505 100644 --- a/pom.xml +++ b/pom.xml @@ -56,6 +56,16 @@ test + + org.springframework.boot + spring-boot-starter-jdbc + + + + com.h2database + h2 + + 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 e490cb5..a79575f 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -36,6 +36,18 @@ public Collection getFilms() { return filmService.getAllFilms(); } + /** + * Handles GET method. + *

Retrieves film from the storage. + * + * @param id film`s id. Must be positive number + * @return Film. + */ + @GetMapping("/{id}") + public Film getFilm(@PathVariable @Positive Long id) { + return filmService.getFilm(id); + } + /** * Handles POST method. *

Creates film in storage after validation. @@ -70,6 +82,17 @@ public Film updateFilm(@Valid @RequestBody Film newFilm) { return filmService.updateFilm(newFilm); } + /** + * Handles DELETE method. + *

Deletes film from the storage. + * + * @param id film`s id. Must be positive number + */ + @DeleteMapping("/{id}") + public void deleteFilm(@PathVariable @Positive Long id) { + filmService.deleteFilm(id); + } + /** * Handles GET method. *

Retrieves top {@code count} popular films based on likes. diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/GenreController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/GenreController.java new file mode 100644 index 0000000..2ee59a0 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/GenreController.java @@ -0,0 +1,32 @@ +package ru.yandex.practicum.filmorate.controller; + +import jakarta.validation.constraints.Positive; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import ru.yandex.practicum.filmorate.model.Genre; +import ru.yandex.practicum.filmorate.service.film.GenreService; + +import java.util.Collection; + +@RestController +@RequestMapping("/genres") +public class GenreController { + + private final GenreService service; + + public GenreController(GenreService service) { + this.service = service; + } + + @GetMapping("/{id}") + public Genre getGenre(@PathVariable @Positive Long id) { + return service.getGenre(id); + } + + @GetMapping + public Collection getAll() { + return service.getAll(); + } +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/MpaController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/MpaController.java new file mode 100644 index 0000000..8343f8d --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/MpaController.java @@ -0,0 +1,32 @@ +package ru.yandex.practicum.filmorate.controller; + +import jakarta.validation.constraints.Positive; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import ru.yandex.practicum.filmorate.model.Mpa; +import ru.yandex.practicum.filmorate.service.film.MpaService; + +import java.util.Collection; + +@RestController +@RequestMapping("/mpa") +public class MpaController { + + private final MpaService service; + + public MpaController(MpaService service) { + this.service = service; + } + + @GetMapping("/{id}") + public Mpa getMpa(@PathVariable @Positive Long id) { + return service.getMpa(id); + } + + @GetMapping + public Collection getAll() { + return service.getAll(); + } +} 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 975e081..5241b6c 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java @@ -37,6 +37,18 @@ public Collection getUsers() { return userService.getAllUsers(); } + /** + * Handles GET method. + *

Retrieves user from the storage. + * + * @param id user`s id. Must be positive number + * @return User. + */ + @GetMapping("/{id}") + public User getUser(@PathVariable @Positive Long id) { + return userService.getUser(id); + } + /** * Handles POST method. *

Creates user in storage after validation. @@ -67,6 +79,17 @@ public User updateUser(@Valid @RequestBody User newUser) { return userService.updateUser(newUser); } + /** + * Handles DELETE method. + *

Deletes user from the storage. + * + * @param id user`s id. Must be positive number + */ + @DeleteMapping("/{id}") + public void deleteUser(@PathVariable @Positive Long id) { + userService.deleteUser(id); + } + /** * Handles GET method. *

Return collection of user`s friends. diff --git a/src/main/java/ru/yandex/practicum/filmorate/exception/GlobalExceptionHandler.java b/src/main/java/ru/yandex/practicum/filmorate/exception/GlobalExceptionHandler.java index 42ce9ce..ad5a985 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/exception/GlobalExceptionHandler.java +++ b/src/main/java/ru/yandex/practicum/filmorate/exception/GlobalExceptionHandler.java @@ -5,6 +5,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.ConstraintViolationException; import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageNotReadableException; @@ -102,6 +103,33 @@ public ResponseEntity handleMismatchAndConstraintViolation( return createBadRequest(request.getRequestURI(), Map.of("error", "Invalid request format")); } + @ExceptionHandler(DataIntegrityViolationException.class) + public ResponseEntity handleDataIntegrityViolationException( + DataIntegrityViolationException ex, + HttpServletRequest request) { + + logInfo(ex, "Handling DataIntegrityViolationException"); + String msg = ex.getMessage().toLowerCase(); + HttpStatus status = HttpStatus.BAD_REQUEST; + String errorMsg = "Invalid request format"; + + if (msg.contains("foreign key")) { + + if (msg.contains("mpa")) { + errorMsg = "Mpa не найден"; + status = HttpStatus.NOT_FOUND; + } else if (msg.contains("genre")) { + errorMsg = "Один из указанных жанров не найден"; + status = HttpStatus.NOT_FOUND; + } + } + + return createResponseEntity( + status, + request.getRequestURI(), + Map.of("error", errorMsg)); + } + /** * Helper for HttpMessageNotReadableException handler *

Extracts meaningful error messages from JSON parsing exceptions. 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 6fe8c60..6b59539 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Film.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Film.java @@ -9,6 +9,8 @@ import ru.yandex.practicum.filmorate.validation.ValidReleaseDate; import java.time.LocalDate; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; /** @@ -54,4 +56,8 @@ public Film(Long id, String name, String description, LocalDate releaseDate, Int @JsonProperty(access = JsonProperty.Access.READ_ONLY) private Long likes = 0L; + + private Mpa mpa; + + private Set genres = ConcurrentHashMap.newKeySet(); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java b/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java new file mode 100644 index 0000000..b422c45 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java @@ -0,0 +1,16 @@ +package ru.yandex.practicum.filmorate.model; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Genre { + + @NotNull + private Long id; + private String name; +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Mpa.java b/src/main/java/ru/yandex/practicum/filmorate/model/Mpa.java new file mode 100644 index 0000000..bc8733e --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Mpa.java @@ -0,0 +1,16 @@ +package ru.yandex.practicum.filmorate.model; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Mpa { + + @NotNull + private Long id; + private String name; +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java index b53d904..1b98a88 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java @@ -1,15 +1,17 @@ package ru.yandex.practicum.filmorate.service.film; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; 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.storage.film.FilmStorage; +import ru.yandex.practicum.filmorate.storage.film.GenreStorage; import ru.yandex.practicum.filmorate.storage.film.LikeStorage; import ru.yandex.practicum.filmorate.storage.user.UserStorage; import java.util.Collection; -import java.util.Comparator; /** * Main service for film operations and business logic. @@ -22,19 +24,25 @@ * @see FilmStorage */ @Service +@Transactional public class FilmService implements FilmServiceInterface { private final FilmStorage filmStorage; private final UserStorage userStorage; private final LikeStorage likeStorage; + private final GenreStorage genreStorage; /** * Constructor for dependency injection */ - public FilmService(FilmStorage filmStorage, UserStorage userStorage, LikeStorage likeStorage) { + public FilmService(@Qualifier("DbFilmStorage") FilmStorage filmStorage, + @Qualifier("DbUserStorage") UserStorage userStorage, + @Qualifier("DbLikeStorage") LikeStorage likeStorage, + @Qualifier("DbGenreStorage") GenreStorage genreStorage) { this.filmStorage = filmStorage; this.userStorage = userStorage; this.likeStorage = likeStorage; + this.genreStorage = genreStorage; } /** @@ -68,9 +76,13 @@ public Collection getAllFilms() { */ @Override public Film addFilm(Film film) { - Film returnFilm = filmStorage.add(film); - likeStorage.initializeLikesSet(returnFilm.getId()); - return returnFilm; + film = filmStorage.add(film); + + if (film.getGenres() != null && !film.getGenres().isEmpty()) { + genreStorage.addGenresToFilm(film); + } + + return film; } /** @@ -87,7 +99,13 @@ public Film updateFilm(Film film) { } throwIfNotFound(film.getId()); - return filmStorage.update(film); + film = filmStorage.update(film); + + if (film.getGenres() != null && !film.getGenres().isEmpty()) { + genreStorage.updateFilmGenres(film); + } + + return film; } /** @@ -100,7 +118,7 @@ public Film updateFilm(Film film) { public void deleteFilm(Long id) { throwIfNotFound(id); filmStorage.remove(id); - likeStorage.clearLikesSet(id); + likeStorage.clearLikes(id); } /** @@ -119,9 +137,10 @@ public Film addLike(Long filmId, Long userId) { } Long newLikes = likeStorage.addLike(filmId, userId); - filmStorage.get(filmId).setLikes(newLikes); + Film film = filmStorage.get(filmId); + film.setLikes(newLikes); - return filmStorage.get(filmId); + return film; } /** @@ -140,9 +159,10 @@ public Film removeLike(Long filmId, Long userId) { } Long newLikes = likeStorage.deleteLike(filmId, userId); - filmStorage.get(filmId).setLikes(newLikes); + Film film = filmStorage.get(filmId); + film.setLikes(newLikes); - return filmStorage.get(filmId); + return film; } /** @@ -153,11 +173,7 @@ public Film removeLike(Long filmId, Long userId) { */ @Override public Collection getTopFilms(Long count) { - - return filmStorage.getAll().stream() - .sorted(Comparator.comparingLong(Film::getLikes).reversed()) - .limit(count) - .toList(); + return filmStorage.getTopFilms(count); } @Override diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/film/GenreService.java b/src/main/java/ru/yandex/practicum/filmorate/service/film/GenreService.java new file mode 100644 index 0000000..dd774a9 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/film/GenreService.java @@ -0,0 +1,27 @@ +package ru.yandex.practicum.filmorate.service.film; + +import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.model.Genre; +import ru.yandex.practicum.filmorate.storage.film.GenreStorage; + +import java.util.Collection; + +@Service +public class GenreService { + + private final GenreStorage storage; + + public GenreService(GenreStorage storage) { + this.storage = storage; + } + + public Genre getGenre(Long id) { + return storage.getGenre(id) + .orElseThrow(() -> new NotFoundException("Жанр с id = " + id + " не найден")); + } + + public Collection getAll() { + return storage.getAll(); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/film/MpaService.java b/src/main/java/ru/yandex/practicum/filmorate/service/film/MpaService.java new file mode 100644 index 0000000..38353e7 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/film/MpaService.java @@ -0,0 +1,27 @@ +package ru.yandex.practicum.filmorate.service.film; + +import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.model.Mpa; +import ru.yandex.practicum.filmorate.storage.film.MpaStorage; + +import java.util.Collection; + +@Service +public class MpaService { + + private final MpaStorage storage; + + public MpaService(MpaStorage storage) { + this.storage = storage; + } + + public Mpa getMpa(Long id) { + return storage.getMpa(id) + .orElseThrow(() -> new NotFoundException("Mpa с id = " + id + " не найден")); + } + + public Collection getAll() { + return storage.getAll(); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/user/UserService.java b/src/main/java/ru/yandex/practicum/filmorate/service/user/UserService.java index eff4749..a827a79 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/user/UserService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/user/UserService.java @@ -1,6 +1,8 @@ package ru.yandex.practicum.filmorate.service.user; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.exception.ValidationException; import ru.yandex.practicum.filmorate.model.User; @@ -22,6 +24,7 @@ * @see UserStorage */ @Service +@Transactional public class UserService implements UserServiceInterface { private final UserStorage userStorage; @@ -30,7 +33,8 @@ public class UserService implements UserServiceInterface { /** * Constructor for dependency injection */ - public UserService(UserStorage userStorage, FriendShipStorage friendShipStorage) { + public UserService(@Qualifier("DbUserStorage") UserStorage userStorage, + @Qualifier("DbFriendShipStorage") FriendShipStorage friendShipStorage) { this.userStorage = userStorage; this.friendShipStorage = friendShipStorage; } @@ -69,10 +73,7 @@ public User addUser(User user) { user.setName(user.getLogin()); } - User returnUser = userStorage.add(user); - friendShipStorage.initializeFriendsSet(returnUser.getId()); - - return returnUser; + return userStorage.add(user); } /** @@ -103,11 +104,7 @@ public User updateUser(User user) { @Override public void deleteUser(Long id) { throwIfNotFound(id); - - Set friendIds = friendShipStorage.getFriends(id); - friendIds.forEach(friendId -> friendShipStorage.getFriends(friendId).remove(id)); - friendShipStorage.clearFriendsSet(id); - + friendShipStorage.deleteUserFromAllFriends(id); userStorage.remove(id); } @@ -129,7 +126,6 @@ public void addFriend(Long senderId, Long receiverId) { throwIfNotFound(receiverId); friendShipStorage.addFriend(senderId, receiverId); - friendShipStorage.addFriend(receiverId, senderId); } /** @@ -146,7 +142,6 @@ public void deleteFriend(Long senderId, Long receiverId) { throwIfNotFound(receiverId); friendShipStorage.deleteFriend(senderId, receiverId); - friendShipStorage.deleteFriend(receiverId, senderId); } /** @@ -157,10 +152,7 @@ public void deleteFriend(Long senderId, Long receiverId) { @Override public Collection getFriends(Long id) { throwIfNotFound(id); - - return friendShipStorage.getFriends(id).stream() - .map(userStorage::get) - .toList(); + return userStorage.getAllFromCollection(friendShipStorage.getFriends(id)); } /** @@ -176,13 +168,8 @@ public Collection getCommonFriends(Long id, Long otherId) { throwIfNotFound(id); throwIfNotFound(otherId); - Set friends = friendShipStorage.getFriends(id); - Set otherFriends = friendShipStorage.getFriends(otherId); - - return friends.stream() - .filter(otherFriends::contains) - .map(userStorage::get) - .toList(); + Set ids = friendShipStorage.getCommonFriends(id, otherId); + return userStorage.getAllFromCollection(ids); } @Override diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/BaseDao.java b/src/main/java/ru/yandex/practicum/filmorate/storage/BaseDao.java new file mode 100644 index 0000000..19057ce --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/BaseDao.java @@ -0,0 +1,36 @@ +package ru.yandex.practicum.filmorate.storage; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; + +import java.util.Collection; +import java.util.Map; + +public abstract class BaseDao { + protected final JdbcTemplate jdbc; + protected final NamedParameterJdbcTemplate namedJdbc; + protected final RowMapper mapper; + + public BaseDao(JdbcTemplate jdbc, RowMapper mapper) { + this.jdbc = jdbc; + this.namedJdbc = new NamedParameterJdbcTemplate(jdbc); + this.mapper = mapper; + } + + public void update(String sql, Map map) { + namedJdbc.update(sql, map); + } + + public void remove(String sql, Object... params) { + jdbc.update(sql, params); + } + + public T get(String sql, Object... params) { + return jdbc.queryForObject(sql, mapper, params); + } + + public Collection getAll(String sql, Object... params) { + return jdbc.query(sql, mapper, params); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/BasicStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/BasicStorage.java index 0d5f7d7..ca99808 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/BasicStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/BasicStorage.java @@ -16,6 +16,4 @@ public interface BasicStorage { boolean contains(Long id); - int size(); - } diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmStorage.java index 6e55005..ed5cc24 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmStorage.java @@ -3,5 +3,8 @@ import ru.yandex.practicum.filmorate.model.Film; import ru.yandex.practicum.filmorate.storage.BasicStorage; +import java.util.Collection; + public interface FilmStorage extends BasicStorage { + Collection getTopFilms(Long count); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/GenreStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/film/GenreStorage.java new file mode 100644 index 0000000..f30feba --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/film/GenreStorage.java @@ -0,0 +1,18 @@ +package ru.yandex.practicum.filmorate.storage.film; + +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.Genre; + +import java.util.Collection; +import java.util.Optional; + +public interface GenreStorage { + + Optional getGenre(Long id); + + Collection getAll(); + + void addGenresToFilm(Film film); + + void updateFilmGenres(Film film); +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/LikeStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/film/LikeStorage.java index c2cc79e..8ba7338 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/film/LikeStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/film/LikeStorage.java @@ -2,9 +2,8 @@ public interface LikeStorage { - void initializeLikesSet(Long id); - - void clearLikesSet(Long id); + default void clearLikes(Long id) { + } Long addLike(Long filmId, Long userId); diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/MpaStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/film/MpaStorage.java new file mode 100644 index 0000000..01558fc --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/film/MpaStorage.java @@ -0,0 +1,13 @@ +package ru.yandex.practicum.filmorate.storage.film; + +import ru.yandex.practicum.filmorate.model.Mpa; + +import java.util.Collection; +import java.util.Optional; + +public interface MpaStorage { + + Optional getMpa(Long id); + + Collection getAll(); +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/db/DbFilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/film/db/DbFilmStorage.java new file mode 100644 index 0000000..ea83281 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/film/db/DbFilmStorage.java @@ -0,0 +1,120 @@ +package ru.yandex.practicum.filmorate.storage.film.db; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.simple.SimpleJdbcInsert; +import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.storage.BaseDao; +import ru.yandex.practicum.filmorate.storage.film.FilmStorage; +import ru.yandex.practicum.filmorate.storage.mappers.FilmMapper; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +@Repository("DbFilmStorage") +public class DbFilmStorage extends BaseDao implements FilmStorage { + + private static final String SELECT_FIELDS = """ + SELECT f.*, + COUNT(l.film_id) AS likes, + m.mpa_id, + m.name AS mpa_name, + ARRAY_AGG(g.genre_id) AS genre_ids, + ARRAY_AGG(g.name) AS genre_names + """; + + private static final String FROM_JOIN_EVERYTHING = """ + FROM films AS f + LEFT JOIN mpa AS m ON f.mpa_id = m.mpa_id + LEFT JOIN likes AS l ON f.film_id = l.film_id + LEFT JOIN films_genres AS fg ON f.film_id = fg.film_id + LEFT JOIN genres AS g ON fg.genre_id = g.genre_id"""; + + private static final String GROUP_BY = "GROUP BY f.film_id"; + private static final String EXISTS_QUERY = "SELECT EXISTS(SELECT 1 FROM films WHERE film_id = ?)"; + private static final String DELETE_QUERY = "DELETE FROM films WHERE film_id = ?"; + + private static final String SELECT_BY_ID_QUERY = String.format("%s %s WHERE f.film_id = ? %s", + SELECT_FIELDS, FROM_JOIN_EVERYTHING, GROUP_BY); + + private static final String SELECT_ALL_QUERY = String.format("%s %s %s", + SELECT_FIELDS, FROM_JOIN_EVERYTHING, GROUP_BY); + + private static final String UPDATE_QUERY = """ + UPDATE films + SET name = :name, description = :description, + release_date = :release_date, duration = :duration, mpa_id = :mpa_id + WHERE film_id = :film_id"""; + + + private final SimpleJdbcInsert simpleInsert; + + public DbFilmStorage(JdbcTemplate jdbc, FilmMapper mapper) { + super(jdbc, mapper); + this.simpleInsert = new SimpleJdbcInsert(jdbc) + .withTableName("films") + .usingGeneratedKeyColumns("film_id"); + } + + @Override + public Film add(Film film) { + Long id = simpleInsert.executeAndReturnKey(filmToMap(film)).longValue(); + film.setId(id); + + return film; + } + + @Override + public Film update(Film film) { + Map map = filmToMap(film); + map.put("film_id", film.getId()); + + update(UPDATE_QUERY, map); + + return film; + } + + @Override + public Film remove(Long id) { + Film film = get(id); + remove(DELETE_QUERY, id); + return film; + } + + @Override + public Film get(Long id) { + return get(SELECT_BY_ID_QUERY, id); + } + + @Override + public Collection getAll() { + return getAll(SELECT_ALL_QUERY); + } + + @Override + public boolean contains(Long id) { + return jdbc.queryForObject(EXISTS_QUERY, Boolean.class, id); + } + + @Override + public Collection getTopFilms(Long count) { + String sql = SELECT_ALL_QUERY + " ORDER BY likes DESC LIMIT ?"; + + return jdbc.query(sql, mapper, count); + } + + private Map filmToMap(Film film) { + Map map = new HashMap<>(); + map.put("name", film.getName()); + map.put("description", film.getDescription()); + map.put("release_date", film.getReleaseDate()); + map.put("duration", film.getDuration()); + + if (film.getMpa() != null) { + map.put("mpa_id", film.getMpa().getId()); + } + + return map; + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/db/DbGenreStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/film/db/DbGenreStorage.java new file mode 100644 index 0000000..095b7d3 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/film/db/DbGenreStorage.java @@ -0,0 +1,60 @@ +package ru.yandex.practicum.filmorate.storage.film.db; + +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.Genre; +import ru.yandex.practicum.filmorate.storage.BaseDao; +import ru.yandex.practicum.filmorate.storage.film.GenreStorage; +import ru.yandex.practicum.filmorate.storage.mappers.GenreMapper; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +@Repository("DbGenreStorage") +public class DbGenreStorage extends BaseDao implements GenreStorage { + + private static final String SELECT_BY_ID_QUERY = "SELECT * FROM genres WHERE genre_id = ?"; + private static final String SELECT_ALL_QUERY = "SELECT * FROM genres;"; + + public DbGenreStorage(JdbcTemplate jdbc, GenreMapper mapper) { + super(jdbc, mapper); + } + + @Override + public Optional getGenre(Long id) { + try { + Genre genre = get(SELECT_BY_ID_QUERY, id); + return Optional.ofNullable(genre); + } catch (EmptyResultDataAccessException e) { + return Optional.empty(); + } + } + + @Override + public Collection getAll() { + return getAll(SELECT_ALL_QUERY); + } + + @Override + public void addGenresToFilm(Film film) { + String sql = "INSERT INTO films_genres(film_id, genre_id) VALUES(?,?)"; + Long filmId = film.getId(); + + List batchArgs = film.getGenres() + .stream() + .map(genre -> new Object[]{filmId, genre.getId()}) + .toList(); + + jdbc.batchUpdate(sql, batchArgs); + } + + @Override + public void updateFilmGenres(Film film) { + String sql = "DELETE FROM films_genres WHERE film_id = ?"; + jdbc.update(sql, film.getId()); + addGenresToFilm(film); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/db/DbLikeStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/film/db/DbLikeStorage.java new file mode 100644 index 0000000..9221172 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/film/db/DbLikeStorage.java @@ -0,0 +1,33 @@ +package ru.yandex.practicum.filmorate.storage.film.db; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.storage.film.LikeStorage; + +@Repository("DbLikeStorage") +public class DbLikeStorage implements LikeStorage { + + private final JdbcTemplate jdbc; + + public DbLikeStorage(JdbcTemplate jdbc) { + this.jdbc = jdbc; + } + + @Override + public Long addLike(Long filmId, Long userId) { + String insertSql = "INSERT INTO likes(film_id, user_id) VALUES(?, ?)"; + String countSql = "SELECT COUNT(*) FROM likes WHERE film_id = ?"; + jdbc.update(insertSql, filmId, userId); + + return jdbc.queryForObject(countSql, Long.class, filmId); + } + + @Override + public Long deleteLike(Long filmId, Long userId) { + String deleteSql = "DELETE FROM likes WHERE user_id = ?"; + String countSql = "SELECT COUNT(*) FROM likes WHERE film_id = ?"; + + jdbc.update(deleteSql, userId); + return jdbc.queryForObject(countSql, Long.class, filmId); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/db/DbMpaStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/film/db/DbMpaStorage.java new file mode 100644 index 0000000..514224c --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/film/db/DbMpaStorage.java @@ -0,0 +1,38 @@ +package ru.yandex.practicum.filmorate.storage.film.db; + +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.model.Mpa; +import ru.yandex.practicum.filmorate.storage.BaseDao; +import ru.yandex.practicum.filmorate.storage.film.MpaStorage; +import ru.yandex.practicum.filmorate.storage.mappers.MpaMapper; + +import java.util.Collection; +import java.util.Optional; + +@Repository("DbMpaStorage") +public class DbMpaStorage extends BaseDao implements MpaStorage { + + private static final String SELECT_BY_ID_QUERY = "SELECT * FROM mpa WHERE mpa_id = ?"; + private static final String SELECT_ALL_QUERY = "SELECT * FROM mpa;"; + + public DbMpaStorage(JdbcTemplate jdbc, MpaMapper mapper) { + super(jdbc, mapper); + } + + @Override + public Optional getMpa(Long id) { + try { + Mpa mpa = get(SELECT_BY_ID_QUERY, id); + return Optional.ofNullable(mpa); + } catch (EmptyResultDataAccessException e) { + return Optional.empty(); + } + } + + @Override + public Collection getAll() { + return getAll(SELECT_ALL_QUERY); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/InMemoryFilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/film/memory/InMemoryFilmStorage.java similarity index 76% rename from src/main/java/ru/yandex/practicum/filmorate/storage/film/InMemoryFilmStorage.java rename to src/main/java/ru/yandex/practicum/filmorate/storage/film/memory/InMemoryFilmStorage.java index 82b593c..b7183d7 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/film/InMemoryFilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/film/memory/InMemoryFilmStorage.java @@ -1,10 +1,13 @@ -package ru.yandex.practicum.filmorate.films.film; +package ru.yandex.practicum.filmorate.storage.film.memory; -import org.springframework.stereotype.Component; +import org.springframework.stereotype.Repository; import ru.yandex.practicum.filmorate.model.Film; import ru.yandex.practicum.filmorate.storage.film.FilmStorage; -import java.util.*; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; @@ -18,7 +21,7 @@ * @see Film * @see FilmStorage */ -@Component +@Repository("MemFilmStorage") public class InMemoryFilmStorage implements FilmStorage { private final Map films; @@ -33,7 +36,6 @@ public InMemoryFilmStorage() { public Film add(Film film) { film.setId(idGenerator.getAndIncrement()); films.put(film.getId(), film); - return films.get(film.getId()); } @@ -65,7 +67,10 @@ public boolean contains(Long id) { } @Override - public int size() { - return films.size(); + public Collection getTopFilms(Long count) { + return films.values().stream() + .sorted(Comparator.comparingLong(Film::getLikes).reversed()) + .limit(count) + .toList(); } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/InMemoryLikeStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/film/memory/InMemoryLikeStorage.java similarity index 53% rename from src/main/java/ru/yandex/practicum/filmorate/storage/film/InMemoryLikeStorage.java rename to src/main/java/ru/yandex/practicum/filmorate/storage/film/memory/InMemoryLikeStorage.java index 5cf8a84..a254989 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/film/InMemoryLikeStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/film/memory/InMemoryLikeStorage.java @@ -1,13 +1,15 @@ -package ru.yandex.practicum.filmorate.storage.film; +package ru.yandex.practicum.filmorate.storage.film.memory; -import org.springframework.stereotype.Component; +import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.storage.film.LikeStorage; +import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -@Component +@Repository("MemLikeStorage") public class InMemoryLikeStorage implements LikeStorage { private final Map> likes; @@ -16,27 +18,23 @@ public InMemoryLikeStorage() { this.likes = new ConcurrentHashMap<>(); } - @Override - public void initializeLikesSet(Long id) { - likes.put(id, new HashSet<>()); - } @Override - public void clearLikesSet(Long id) { + public void clearLikes(Long id) { likes.remove(id); } @Override public Long addLike(Long filmId, Long userId) { - likes.get(filmId).add(userId); + likes.computeIfAbsent(filmId, id -> new HashSet<>()).add(userId); return (long) likes.get(filmId).size(); } @Override public Long deleteLike(Long filmId, Long userId) { - likes.get(filmId).remove(userId); + likes.getOrDefault(filmId, Collections.emptySet()).remove(userId); - return (long) likes.get(filmId).size(); + return (long) likes.getOrDefault(filmId, Collections.emptySet()).size(); } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/mappers/FilmMapper.java b/src/main/java/ru/yandex/practicum/filmorate/storage/mappers/FilmMapper.java new file mode 100644 index 0000000..d5f8359 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/mappers/FilmMapper.java @@ -0,0 +1,70 @@ +package ru.yandex.practicum.filmorate.storage.mappers; + +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.Genre; +import ru.yandex.practicum.filmorate.model.Mpa; + +import java.sql.Array; +import java.sql.Date; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDate; +import java.util.HashSet; +import java.util.Set; + +@Component +public class FilmMapper implements RowMapper { + + @Override + public Film mapRow(ResultSet resultSet, int rowNum) throws SQLException { + Film film = new Film(); + film.setId(resultSet.getLong("film_id")); + film.setName(resultSet.getString("name")); + film.setDescription(resultSet.getString("description")); + film.setLikes(resultSet.getLong("likes")); + film.setDuration(resultSet.getInt("duration")); + + Date date = resultSet.getDate("release_date"); + LocalDate releaseDate = date == null ? null : date.toLocalDate(); + + film.setReleaseDate(releaseDate); + + mapMpa(film, resultSet); + mapGenres(film, resultSet); + + return film; + } + + private void mapMpa(Film film, ResultSet resultSet) throws SQLException { + Long mpaId = resultSet.getLong("mpa_id"); + if (!resultSet.wasNull()) { + String mpaName = resultSet.getString("mpa_name"); + film.setMpa(new Mpa(mpaId, mpaName)); + } + } + + private void mapGenres(Film film, ResultSet resultSet) throws SQLException { + Array sqlArrayIds = resultSet.getArray("genre_ids"); + if (resultSet.wasNull()) { + return; + } + + Array sqlArrayNames = resultSet.getArray("genre_names"); + + Object[] idsArray = (Object[]) sqlArrayIds.getArray(); + Object[] namesArray = (Object[]) sqlArrayNames.getArray(); + + Set genres = new HashSet<>(); + + for (int i = 0; i < idsArray.length; i++) { + if (idsArray[i] != null && namesArray[i] != null) { + Long id = ((Number) idsArray[i]).longValue(); + String name = namesArray[i].toString(); + genres.add(new Genre(id, name)); + } + } + film.setGenres(genres); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/mappers/GenreMapper.java b/src/main/java/ru/yandex/practicum/filmorate/storage/mappers/GenreMapper.java new file mode 100644 index 0000000..2554b4d --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/mappers/GenreMapper.java @@ -0,0 +1,20 @@ +package ru.yandex.practicum.filmorate.storage.mappers; + +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.model.Genre; + +import java.sql.ResultSet; +import java.sql.SQLException; + +@Component +public class GenreMapper implements RowMapper { + @Override + public Genre mapRow(ResultSet resultSet, int rowNum) throws SQLException { + Genre genre = new Genre(); + genre.setId(resultSet.getLong("genre_id")); + genre.setName(resultSet.getString("name")); + + return genre; + } +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/mappers/MpaMapper.java b/src/main/java/ru/yandex/practicum/filmorate/storage/mappers/MpaMapper.java new file mode 100644 index 0000000..39bc681 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/mappers/MpaMapper.java @@ -0,0 +1,20 @@ +package ru.yandex.practicum.filmorate.storage.mappers; + +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.model.Mpa; + +import java.sql.ResultSet; +import java.sql.SQLException; + +@Component +public class MpaMapper implements RowMapper { + @Override + public Mpa mapRow(ResultSet resultSet, int rowNum) throws SQLException { + Mpa mpa = new Mpa(); + mpa.setId(resultSet.getLong("mpa_id")); + mpa.setName(resultSet.getString("name")); + + return mpa; + } +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/mappers/UserMapper.java b/src/main/java/ru/yandex/practicum/filmorate/storage/mappers/UserMapper.java new file mode 100644 index 0000000..4dc4a93 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/mappers/UserMapper.java @@ -0,0 +1,28 @@ +package ru.yandex.practicum.filmorate.storage.mappers; + +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.model.User; + +import java.sql.Date; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDate; + +@Component +public class UserMapper implements RowMapper { + @Override + public User mapRow(ResultSet resultSet, int rowNum) throws SQLException { + User user = new User(); + user.setId(resultSet.getLong("user_id")); + user.setEmail(resultSet.getString("email")); + user.setLogin(resultSet.getString("login")); + user.setName(resultSet.getString("name")); + + Date date = resultSet.getDate("birthday"); + LocalDate birthday = date == null ? null : date.toLocalDate(); + user.setBirthday(birthday); + + return user; + } +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/user/FriendShipStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/user/FriendShipStorage.java index d54df19..af50ddc 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/user/FriendShipStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/user/FriendShipStorage.java @@ -4,13 +4,14 @@ public interface FriendShipStorage { - void initializeFriendsSet(Long id); - - void clearFriendsSet(Long id); + default void deleteUserFromAllFriends(Long id) { + } void addFriend(Long senderId, Long receiverId); void deleteFriend(Long senderId, Long receiverId); Set getFriends(Long id); + + Set getCommonFriends(Long id, Long otherId); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/user/InMemoryFriendShipStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/user/InMemoryFriendShipStorage.java deleted file mode 100644 index 88cb81d..0000000 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/user/InMemoryFriendShipStorage.java +++ /dev/null @@ -1,43 +0,0 @@ -package ru.yandex.practicum.filmorate.storage.user; - -import org.springframework.stereotype.Component; - -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -@Component -public class InMemoryFriendShipStorage implements FriendShipStorage { - - private final Map> friendships; - - public InMemoryFriendShipStorage() { - this.friendships = new ConcurrentHashMap<>(); - } - - @Override - public void initializeFriendsSet(Long id) { - friendships.put(id, new HashSet<>()); - } - - @Override - public void clearFriendsSet(Long id) { - friendships.remove(id); - } - - @Override - public void addFriend(Long senderId, Long receiverId) { - friendships.get(senderId).add(receiverId); - } - - @Override - public void deleteFriend(Long senderId, Long receiverId) { - friendships.get(senderId).remove(receiverId); - } - - @Override - public Set getFriends(Long id) { - return friendships.get(id); - } -} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserStorage.java index ae4c687..0377f5d 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserStorage.java @@ -3,5 +3,9 @@ import ru.yandex.practicum.filmorate.model.User; import ru.yandex.practicum.filmorate.storage.BasicStorage; +import java.util.Collection; +import java.util.List; + public interface UserStorage extends BasicStorage { + List getAllFromCollection(Collection ids); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/user/db/DbFriendShipStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/user/db/DbFriendShipStorage.java new file mode 100644 index 0000000..728af3e --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/user/db/DbFriendShipStorage.java @@ -0,0 +1,47 @@ +package ru.yandex.practicum.filmorate.storage.user.db; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.storage.user.FriendShipStorage; + +import java.util.Set; + +@Repository("DbFriendShipStorage") +public class DbFriendShipStorage implements FriendShipStorage { + + private static final String SELECT_BY_ID_QUERY = "SELECT friend_id FROM friendships WHERE user_id = ?"; + private static final String INSERT_QUERY = "INSERT INTO friendships(user_id, friend_id) VALUES(?,?)"; + private static final String DELETE_QUERY = "DELETE FROM friendships WHERE user_id = ? AND friend_id = ?"; + private static final String SELECT_COMMON_FRIENDS = """ + SELECT f1.friend_id + FROM friendships f1 + JOIN friendships f2 ON f1.friend_id = f2.friend_id + WHERE f1.user_id = ? AND f2.user_id = ?"""; + + + private final JdbcTemplate jdbc; + + public DbFriendShipStorage(JdbcTemplate jdbc) { + this.jdbc = jdbc; + } + + @Override + public void addFriend(Long senderId, Long receiverId) { + jdbc.update(INSERT_QUERY, senderId, receiverId); + } + + @Override + public void deleteFriend(Long senderId, Long receiverId) { + jdbc.update(DELETE_QUERY, senderId, receiverId); + } + + @Override + public Set getFriends(Long id) { + return Set.copyOf(jdbc.queryForList(SELECT_BY_ID_QUERY, Long.class, id)); + } + + @Override + public Set getCommonFriends(Long id, Long otherId) { + return Set.copyOf(jdbc.queryForList(SELECT_COMMON_FRIENDS, Long.class, id, otherId)); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/user/db/DbUserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/user/db/DbUserStorage.java new file mode 100644 index 0000000..c62e56c --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/user/db/DbUserStorage.java @@ -0,0 +1,91 @@ +package ru.yandex.practicum.filmorate.storage.user.db; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.simple.SimpleJdbcInsert; +import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.storage.BaseDao; +import ru.yandex.practicum.filmorate.storage.mappers.UserMapper; +import ru.yandex.practicum.filmorate.storage.user.UserStorage; + +import java.util.*; + +@Repository("DbUserStorage") +public class DbUserStorage extends BaseDao implements UserStorage { + + private static final String SELECT_BY_ID_QUERY = "SELECT * FROM users WHERE user_id = ?"; + private static final String SELECT_ALL_QUERY = "SELECT * FROM users;"; + private static final String DELETE_QUERY = "DELETE FROM users WHERE user_id = ?"; + private static final String EXISTS_QUERY = "SELECT EXISTS(SELECT 1 FROM users WHERE user_id = ?)"; + private static final String SELECT_ALL_FROM_COLLECTION = "SELECT * FROM users WHERE user_id IN (:ids)"; + private static final String UPDATE_QUERY = """ + UPDATE users SET email = :email, login = :login, name = :name, birthday = :birthday + WHERE user_id = :user_id + """; + + private final SimpleJdbcInsert simpleInsert; + + public DbUserStorage(JdbcTemplate jdbc, UserMapper mapper) { + super(jdbc, mapper); + this.simpleInsert = new SimpleJdbcInsert(jdbc) + .withTableName("users") + .usingGeneratedKeyColumns("user_id"); + } + + @Override + public User add(User user) { + Long id = simpleInsert.executeAndReturnKey(userToMap(user)).longValue(); + user.setId(id); + + return user; + } + + @Override + public User update(User user) { + Map map = userToMap(user); + map.put("user_id", user.getId()); + update(UPDATE_QUERY, map); + return user; + } + + @Override + public User remove(Long id) { + User user = get(id); + remove(DELETE_QUERY, id); + return user; + } + + @Override + public User get(Long id) { + return get(SELECT_BY_ID_QUERY, id); + } + + @Override + public Collection getAll() { + return getAll(SELECT_ALL_QUERY); + } + + @Override + public boolean contains(Long id) { + return jdbc.queryForObject(EXISTS_QUERY, Boolean.class, id); + } + + @Override + public List getAllFromCollection(Collection ids) { + if (ids == null || ids.isEmpty()) { + return Collections.emptyList(); + } + + return namedJdbc.query(SELECT_ALL_FROM_COLLECTION, Collections.singletonMap("ids", ids), mapper); + } + + private Map userToMap(User user) { + Map map = new HashMap<>(); + map.put("email", user.getEmail()); + map.put("login", user.getLogin()); + map.put("name", user.getName()); + map.put("birthday", user.getBirthday()); + + return map; + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/user/memory/InMemoryFriendShipStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/user/memory/InMemoryFriendShipStorage.java new file mode 100644 index 0000000..2a1c7d3 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/user/memory/InMemoryFriendShipStorage.java @@ -0,0 +1,50 @@ +package ru.yandex.practicum.filmorate.storage.user.memory; + +import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.storage.user.FriendShipStorage; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +@Repository("MemFriendShipStorage") +public class InMemoryFriendShipStorage implements FriendShipStorage { + + private final Map> friendships; + + public InMemoryFriendShipStorage() { + this.friendships = new ConcurrentHashMap<>(); + } + + @Override + public void deleteUserFromAllFriends(Long id) { + friendships.get(id) + .forEach(friendId -> friendships.getOrDefault(friendId, Collections.emptySet()).remove(id)); + friendships.remove(id); + } + + @Override + public void addFriend(Long senderId, Long receiverId) { + friendships.computeIfAbsent(senderId, id -> new HashSet<>()).add(receiverId); + } + + @Override + public void deleteFriend(Long senderId, Long receiverId) { + friendships.getOrDefault(senderId, Collections.emptySet()).remove(receiverId); + } + + @Override + public Set getFriends(Long id) { + return friendships.getOrDefault(id, Collections.emptySet()); + } + + @Override + public Set getCommonFriends(Long id, Long otherId) { + Set friends1 = getFriends(id); + Set friends2 = getFriends(otherId); + friends1.retainAll(friends2); + return friends1; + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/user/InMemoryUserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/user/memory/InMemoryUserStorage.java similarity index 85% rename from src/main/java/ru/yandex/practicum/filmorate/storage/user/InMemoryUserStorage.java rename to src/main/java/ru/yandex/practicum/filmorate/storage/user/memory/InMemoryUserStorage.java index 4b26fae..7944882 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/user/InMemoryUserStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/user/memory/InMemoryUserStorage.java @@ -1,7 +1,8 @@ -package ru.yandex.practicum.filmorate.storage.user; +package ru.yandex.practicum.filmorate.storage.user.memory; import org.springframework.stereotype.Component; import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.storage.user.UserStorage; import java.util.Collection; import java.util.List; @@ -19,7 +20,7 @@ * @see User * @see UserStorage */ -@Component +@Component("MemUserStorage") public class InMemoryUserStorage implements UserStorage { private final Map users; @@ -66,7 +67,7 @@ public boolean contains(Long id) { } @Override - public int size() { - return users.size(); + public List getAllFromCollection(Collection ids) { + return ids.stream().map(users::get).toList(); } } diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index b50b2c2..3ac1048 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -1,5 +1,5 @@ spring.application.name=Filmorate -server.port=8081 +server.port=8080 logging.file.path=./logs/ logging.logback.rollingpolicy.file-name-pattern=./logs/spring.%d{yyyy-MM-dd}.%i.log @@ -8,5 +8,16 @@ logging.logback.rollingpolicy.total-size-cap=50MB logging.logback.rollingpolicy.clean-history-on-start=true spring.output.ansi.enabled=ALWAYS -logging.level.ru.yandex.practicum.filmorate=INFO -logging.level.org.zalando.logbook=TRACE \ No newline at end of file +logging.level.ru.yandex.practicum.filmorate=DEBUG +logging.level.org.zalando.logbook=DEBUG +logging.level.org.springframework.jdbc.core.JdbcTemplate=DEBUG +logging.level.org.springframework.jdbc.datasource.DataSourceTransactionManager=DEBUG +logging.level.com.zaxxer.hikari=DEBUG + +spring.sql.init.mode=always +#spring.datasource.url=jdbc:h2:file:./db/filmorate +spring.datasource.url=jdbc:h2:mem:filmorate +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=admin +spring.datasource.password=admin +spring.h2.console.enabled=true \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 45001af..b57abfb 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -9,4 +9,10 @@ logging.logback.rollingpolicy.clean-history-on-start=true spring.output.ansi.enabled=ALWAYS logging.level.ru.yandex.practicum.filmorate=WARN -logging.level.org.zalando.logbook=WARN \ No newline at end of file +logging.level.org.zalando.logbook=WARN + +spring.sql.init.mode=always +spring.datasource.url=jdbc:h2:file:./db/filmorate +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=admin +spring.datasource.password=admin \ No newline at end of file diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql new file mode 100644 index 0000000..2e931bd --- /dev/null +++ b/src/main/resources/data.sql @@ -0,0 +1,20 @@ +DELETE FROM genres; +DELETE FROM mpa; + +ALTER TABLE genres ALTER COLUMN genre_id RESTART WITH 1; +ALTER TABLE mpa ALTER COLUMN mpa_id RESTART WITH 1; + +INSERT INTO mpa (name) VALUES +('G'), +('PG'), +('PG-13'), +('R'), +('NC-17'); + +INSERT INTO genres (name) VALUES +('Комедия'), +('Драма'), +('Мультфильм'), +('Триллер'), +('Документальный'), +('Боевик'); \ No newline at end of file diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 0000000..a1597a0 --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,44 @@ +CREATE TABLE IF NOT EXISTS users ( + user_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + email VARCHAR NOT NULL, + login VARCHAR NOT NULL, + name VARCHAR, + birthday DATE); + +CREATE TABLE IF NOT EXISTS friendships ( + user_id BIGINT, + friend_id BIGINT, + FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE, + FOREIGN KEY (friend_id) REFERENCES users(user_id) ON DELETE CASCADE, + PRIMARY KEY (user_id, friend_id)); + +CREATE TABLE IF NOT EXISTS mpa ( + mpa_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name VARCHAR NOT NULL); + +CREATE TABLE IF NOT EXISTS films ( + film_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name VARCHAR NOT NULL, + description VARCHAR(200), + release_date DATE, + duration INTEGER, + mpa_id BIGINT, + FOREIGN KEY (mpa_id) REFERENCES mpa(mpa_id) ON DELETE SET NULL); + +CREATE TABLE IF NOT EXISTS genres ( + genre_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name VARCHAR NOT NULL); + +CREATE TABLE IF NOT EXISTS films_genres ( + film_id BIGINT, + genre_id BIGINT, + FOREIGN KEY (film_id) REFERENCES films(film_id) ON DELETE CASCADE, + FOREIGN KEY (genre_id) REFERENCES genres(genre_id) ON DELETE CASCADE, + PRIMARY KEY (film_id, genre_id)); + +CREATE TABLE IF NOT EXISTS likes ( + film_id BIGINT, + user_id BIGINT, + FOREIGN KEY (film_id) REFERENCES films(film_id) ON DELETE CASCADE, + FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE, + PRIMARY KEY (film_id, user_id)); \ No newline at end of file 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 ce4fdbf..9939f11 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/controller/FilmControllerTest.java +++ b/src/test/java/ru/yandex/practicum/filmorate/controller/FilmControllerTest.java @@ -4,14 +4,18 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.MediaType; import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.Genre; +import ru.yandex.practicum.filmorate.model.Mpa; import ru.yandex.practicum.filmorate.service.film.FilmService; import java.time.LocalDate; import java.util.Collections; import java.util.List; +import java.util.Set; import static org.mockito.Mockito.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; @@ -141,6 +145,64 @@ public void shouldReturn400WhenZeroCount() throws Exception { verify(filmService, never()).getTopFilms(any()); } + + + @Test + public void shouldGetFilm() throws Exception { + when(filmService.getFilm(1L)) + .thenReturn(new Film(1L, "name", "description", LocalDate.MAX, 100)); + + mockMvc.perform(get("/films/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.name").value("name")); + verify(filmService).getFilm(1L); + } + + @Test + public void shouldReturn404WhenFilmNotFoundAndGetFilm() throws Exception { + when(filmService.getFilm(1000L)) + .thenThrow(new NotFoundException("Not Found")); + mockMvc.perform(get("/films/1000")).andExpect(status().isNotFound()); + verify(filmService).getFilm(any()); + } + + @Test + public void shouldReturn400WhenNegativeIdsAndGetFilm() throws Exception { + mockMvc.perform(get("/films/-1")).andExpect(status().isBadRequest()); + verify(filmService, never()).getFilm(any()); + } + + @Test + public void shouldReturn400WhenZeroFilmIdAndGetFilm() throws Exception { + mockMvc.perform(get("/films/0")).andExpect(status().isBadRequest()); + verify(filmService, never()).getFilm(any()); + } + + @Test + public void shouldDeleteFilm() throws Exception { + mockMvc.perform(delete("/films/1")).andExpect(status().isOk()); + verify(filmService).deleteFilm(any()); + } + + @Test + public void shouldReturn404WhenFilmNotFoundAndDeleteFilm() throws Exception { + doThrow(new NotFoundException("Not Found")).when(filmService).deleteFilm(1000L); + mockMvc.perform(delete("/films/1000")).andExpect(status().isNotFound()); + verify(filmService).deleteFilm(any()); + } + + @Test + public void shouldReturn400WhenNegativeIdsAndDeleteFilm() throws Exception { + mockMvc.perform(delete("/films/-1")).andExpect(status().isBadRequest()); + verify(filmService, never()).deleteFilm(any()); + } + + @Test + public void shouldReturn400WhenZeroFilmIdAndDeleteFilm() throws Exception { + mockMvc.perform(delete("/films/0")).andExpect(status().isBadRequest()); + verify(filmService, never()).deleteFilm(any()); + } } @Nested @@ -283,6 +345,57 @@ public void shouldReturn400WhenInvalidDateFormat() throws Exception { verify(filmService, never()).addFilm(any()); } + + @Test + public void shouldNotCreateFilmWhenMpaIsNotFoundAndReturnNotFound() throws Exception { + Film filmToCreate = new Film(null, "name", "description", + LocalDate.of(2000, 1, 1), 10); + filmToCreate.setMpa(new Mpa(1000L, "name")); + + when(filmService.addFilm(filmToCreate)).thenThrow(new DataIntegrityViolationException("foreign key mpa")); + String invalidJson = objectMapper.writeValueAsString(filmToCreate); + + + mockMvc.perform(post("/films") + .contentType(MediaType.APPLICATION_JSON) + .content(invalidJson)) + .andExpect(status().isNotFound()); + + verify(filmService).addFilm(any()); + } + + @Test + public void shouldNotCreateFilmWhenGenreIsNotFoundAndReturnNotFound() throws Exception { + Film filmToCreate = new Film(null, "name", "description", + LocalDate.of(2000, 1, 1), 10); + filmToCreate.setGenres(Set.of(new Genre(1000L, "bla"))); + + when(filmService.addFilm(filmToCreate)).thenThrow(new DataIntegrityViolationException("foreign key genre")); + String invalidJson = objectMapper.writeValueAsString(filmToCreate); + + mockMvc.perform(post("/films") + .contentType(MediaType.APPLICATION_JSON) + .content(invalidJson)) + .andExpect(status().isNotFound()); + + verify(filmService).addFilm(any()); + } + + @Test + public void shouldNotCreateFilmWhenDataBrokenAndReturnBadRequest() throws Exception { + Film filmToCreate = new Film(null, "name", "description", + LocalDate.of(2000, 1, 1), 10); + + when(filmService.addFilm(filmToCreate)).thenThrow(new DataIntegrityViolationException("something")); + String invalidJson = objectMapper.writeValueAsString(filmToCreate); + + mockMvc.perform(post("/films") + .contentType(MediaType.APPLICATION_JSON) + .content(invalidJson)) + .andExpect(status().isBadRequest()); + + verify(filmService).addFilm(any()); + } } @Nested @@ -379,7 +492,7 @@ public void shouldAddLike() throws Exception { } @Test - public void shouldNotAddLikeWhenFilmNotFound() throws Exception { + public void shouldNotAddLikeWhenUserNotFound() throws Exception { Film filmToUpdate = new Film(1000L, "ONLY TODAY", "NEW TEXT", LocalDate.of(2000, 1, 1), 120); @@ -397,7 +510,7 @@ public void shouldNotAddLikeWhenFilmNotFound() throws Exception { } @Test - public void shouldNotAddLikeWhenUserNotFound() throws Exception { + public void shouldNotAddLikeWhenFilmNotFound() throws Exception { Film filmToUpdate = new Film(1L, "ONLY TODAY", "NEW TEXT", LocalDate.of(2000, 1, 1), 120); diff --git a/src/test/java/ru/yandex/practicum/filmorate/controller/GenreControllerTest.java b/src/test/java/ru/yandex/practicum/filmorate/controller/GenreControllerTest.java new file mode 100644 index 0000000..8497549 --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/controller/GenreControllerTest.java @@ -0,0 +1,77 @@ +package ru.yandex.practicum.filmorate.controller; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.model.Genre; +import ru.yandex.practicum.filmorate.service.film.GenreService; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(GenreController.class) +public class GenreControllerTest extends ControllerTest { + + @MockBean + GenreService genreService; + + @Test + public void shouldGetGenre() throws Exception { + when(genreService.getGenre(1L)) + .thenReturn(new Genre(1L, "name")); + + mockMvc.perform(get("/genres/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.name").value("name")); + verify(genreService).getGenre(1L); + } + + @Test + public void shouldReturn404WhenUserNotFoundAndGetGenre() throws Exception { + when(genreService.getGenre(1000L)) + .thenThrow(new NotFoundException("Not Found")); + mockMvc.perform(get("/genres/1000")).andExpect(status().isNotFound()); + verify(genreService).getGenre(any()); + } + + @Test + public void shouldReturn400WhenNegativeIdsAndGetGenre() throws Exception { + mockMvc.perform(get("/genres/-1")).andExpect(status().isBadRequest()); + verify(genreService, never()).getGenre(any()); + } + + @Test + public void shouldReturn400WhenZeroUserIdAndGetGenre() throws Exception { + mockMvc.perform(get("/genres/0")).andExpect(status().isBadRequest()); + verify(genreService, never()).getGenre(any()); + } + + @Test + public void shouldGetGenres() throws Exception { + Genre mpa1 = new Genre(1L, "name1"); + Genre mpa2 = new Genre(2L, "name2"); + + List genres = List.of(mpa1, mpa2); + + // Настраиваем мок сервиса + when(genreService.getAll()).thenReturn(genres); + + mockMvc.perform(get("/genres")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$.length()").value(2)) + .andExpect(jsonPath("$[0].id").value(1)) + .andExpect(jsonPath("$[0].name").value("name1")) + .andExpect(jsonPath("$[1].id").value(2)) + .andExpect(jsonPath("$[1].name").value("name2")); + + verify(genreService, times(1)).getAll(); + } +} diff --git a/src/test/java/ru/yandex/practicum/filmorate/controller/MpaControllerTest.java b/src/test/java/ru/yandex/practicum/filmorate/controller/MpaControllerTest.java new file mode 100644 index 0000000..f728905 --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/controller/MpaControllerTest.java @@ -0,0 +1,77 @@ +package ru.yandex.practicum.filmorate.controller; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.model.Mpa; +import ru.yandex.practicum.filmorate.service.film.MpaService; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(MpaController.class) +public class MpaControllerTest extends ControllerTest { + + @MockBean + MpaService mpaService; + + @Test + public void shouldGetMpa() throws Exception { + when(mpaService.getMpa(1L)) + .thenReturn(new Mpa(1L, "name")); + + mockMvc.perform(get("/mpa/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.name").value("name")); + verify(mpaService).getMpa(1L); + } + + @Test + public void shouldReturn404WhenUserNotFoundAndGetMpa() throws Exception { + when(mpaService.getMpa(1000L)) + .thenThrow(new NotFoundException("Not Found")); + mockMvc.perform(get("/mpa/1000")).andExpect(status().isNotFound()); + verify(mpaService).getMpa(any()); + } + + @Test + public void shouldReturn400WhenNegativeIdsAndGetMpa() throws Exception { + mockMvc.perform(get("/mpa/-1")).andExpect(status().isBadRequest()); + verify(mpaService, never()).getMpa(any()); + } + + @Test + public void shouldReturn400WhenZeroUserIdAndGetMpa() throws Exception { + mockMvc.perform(get("/mpa/0")).andExpect(status().isBadRequest()); + verify(mpaService, never()).getMpa(any()); + } + + @Test + public void shouldGetMpas() throws Exception { + Mpa mpa1 = new Mpa(1L, "name1"); + Mpa mpa2 = new Mpa(2L, "name2"); + + List mpas = List.of(mpa1, mpa2); + + // Настраиваем мок сервиса + when(mpaService.getAll()).thenReturn(mpas); + + mockMvc.perform(get("/mpa")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$.length()").value(2)) + .andExpect(jsonPath("$[0].id").value(1)) + .andExpect(jsonPath("$[0].name").value("name1")) + .andExpect(jsonPath("$[1].id").value(2)) + .andExpect(jsonPath("$[1].name").value("name2")); + + verify(mpaService, times(1)).getAll(); + } +} 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 7884b1a..540a5f9 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/controller/UserControllerTest.java +++ b/src/test/java/ru/yandex/practicum/filmorate/controller/UserControllerTest.java @@ -172,6 +172,63 @@ public void shouldReturn400WhenZeroUserId() throws Exception { verify(userService, never()).getFriends(any()); } + + @Test + public void shouldGetUser() throws Exception { + when(userService.getUser(1L)) + .thenReturn(new User(1L, "email", "login", "name", LocalDate.MIN)); + + mockMvc.perform(get("/users/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.email").value("email")); + verify(userService).getUser(1L); + } + + @Test + public void shouldReturn404WhenUserNotFoundAndGetUser() throws Exception { + when(userService.getUser(1000L)) + .thenThrow(new NotFoundException("Not Found")); + mockMvc.perform(get("/users/1000")).andExpect(status().isNotFound()); + verify(userService).getUser(any()); + } + + @Test + public void shouldReturn400WhenNegativeIdsAndGetUser() throws Exception { + mockMvc.perform(get("/users/-1")).andExpect(status().isBadRequest()); + verify(userService, never()).getUser(any()); + } + + @Test + public void shouldReturn400WhenZeroUserIdAndGetUser() throws Exception { + mockMvc.perform(get("/users/0")).andExpect(status().isBadRequest()); + verify(userService, never()).getUser(any()); + } + + @Test + public void shouldDeleteUser() throws Exception { + mockMvc.perform(delete("/users/1")).andExpect(status().isOk()); + verify(userService).deleteUser(any()); + } + + @Test + public void shouldReturn404WhenUserNotFoundAndDeleteUser() throws Exception { + doThrow(new NotFoundException("Not Found")).when(userService).deleteUser(1000L); + mockMvc.perform(delete("/users/1000")).andExpect(status().isNotFound()); + verify(userService).deleteUser(any()); + } + + @Test + public void shouldReturn400WhenNegativeIdsAndDeleteUser() throws Exception { + mockMvc.perform(delete("/users/-1")).andExpect(status().isBadRequest()); + verify(userService, never()).deleteUser(any()); + } + + @Test + public void shouldReturn400WhenZeroUserIdAndDeleteUser() throws Exception { + mockMvc.perform(delete("/users/0")).andExpect(status().isBadRequest()); + verify(userService, never()).deleteUser(any()); + } } @Nested diff --git a/src/test/java/ru/yandex/practicum/filmorate/service/film/FilmServiceTest.java b/src/test/java/ru/yandex/practicum/filmorate/service/film/FilmServiceTest.java index d883d1a..9c81fa8 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/service/film/FilmServiceTest.java +++ b/src/test/java/ru/yandex/practicum/filmorate/service/film/FilmServiceTest.java @@ -8,14 +8,14 @@ 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.Genre; import ru.yandex.practicum.filmorate.storage.film.FilmStorage; +import ru.yandex.practicum.filmorate.storage.film.GenreStorage; import ru.yandex.practicum.filmorate.storage.film.LikeStorage; import ru.yandex.practicum.filmorate.storage.user.UserStorage; import java.time.LocalDate; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; +import java.util.*; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @@ -32,11 +32,14 @@ public class FilmServiceTest { @Mock private UserStorage userStorage; + @Mock + private GenreStorage genreStorage; + private FilmService filmService; @BeforeEach public void setUp() { - filmService = new FilmService(filmStorage, userStorage, likeStorage); + filmService = new FilmService(filmStorage, userStorage, likeStorage, genreStorage); } @Test @@ -87,14 +90,64 @@ public void shouldAddFilmSuccessfully() { Film savedFilm = new Film(1L, "New Film", "Description", LocalDate.now(), 120); when(filmStorage.add(inputFilm)).thenReturn(savedFilm); - doNothing().when(likeStorage).initializeLikesSet(1L); Film result = filmService.addFilm(inputFilm); assertNotNull(result); assertEquals(1L, result.getId()); verify(filmStorage).add(inputFilm); - verify(likeStorage).initializeLikesSet(1L); + } + + @Test + public void shouldAddFilmWithGenres() { + Film inputFilm = new Film(null, "New Film", "Description", LocalDate.now(), 120); + inputFilm.setGenres(Set.of(new Genre(1L, "Комедия"), new Genre(2L, "Драма"))); + + Film savedFilm = new Film(1L, "New Film", "Description", LocalDate.now(), 120); + savedFilm.setGenres(inputFilm.getGenres()); + + when(filmStorage.add(inputFilm)).thenReturn(savedFilm); + + Film result = filmService.addFilm(inputFilm); + + assertNotNull(result); + assertEquals(1L, result.getId()); + verify(filmStorage).add(inputFilm); + verify(genreStorage).addGenresToFilm(savedFilm); + } + + @Test + public void shouldAddFilmWithoutGenres() { + Film inputFilm = new Film(null, "New Film", "Description", LocalDate.now(), 120); + inputFilm.setGenres(null); + + Film savedFilm = new Film(1L, "New Film", "Description", LocalDate.now(), 120); + + when(filmStorage.add(inputFilm)).thenReturn(savedFilm); + + Film result = filmService.addFilm(inputFilm); + + assertNotNull(result); + assertEquals(1L, result.getId()); + verify(filmStorage).add(inputFilm); + verify(genreStorage, never()).addGenresToFilm(any()); + } + + @Test + public void shouldAddFilmWithEmptyGenres() { + Film inputFilm = new Film(null, "New Film", "Description", LocalDate.now(), 120); + inputFilm.setGenres(Collections.emptySet()); + + Film savedFilm = new Film(1L, "New Film", "Description", LocalDate.now(), 120); + + when(filmStorage.add(inputFilm)).thenReturn(savedFilm); + + Film result = filmService.addFilm(inputFilm); + + assertNotNull(result); + assertEquals(1L, result.getId()); + verify(filmStorage).add(inputFilm); + verify(genreStorage, never()).addGenresToFilm(any()); } @Test @@ -132,6 +185,43 @@ public void shouldThrowNotFoundExceptionWhenUpdateNonExistentFilm() { verify(filmStorage, never()).update(any()); } + @Test + public void shouldUpdateFilmWithGenres() { + Film inputFilm = new Film(1L, "Updated Film", "Updated Description", LocalDate.now(), 150); + inputFilm.setGenres(Set.of(new Genre(1L, "Комедия"), new Genre(3L, "Мультфильм"))); + + Film updatedFilm = new Film(1L, "Updated Film", "Updated Description", LocalDate.now(), 150); + updatedFilm.setGenres(inputFilm.getGenres()); + + when(filmStorage.contains(1L)).thenReturn(true); + when(filmStorage.update(inputFilm)).thenReturn(updatedFilm); + + Film result = filmService.updateFilm(inputFilm); + + assertNotNull(result); + assertEquals(1L, result.getId()); + verify(filmStorage).update(inputFilm); + verify(genreStorage).updateFilmGenres(updatedFilm); + } + + @Test + public void shouldUpdateFilmWithoutGenres() { + Film inputFilm = new Film(1L, "Updated Film", "Updated Description", LocalDate.now(), 150); + inputFilm.setGenres(null); + + Film updatedFilm = new Film(1L, "Updated Film", "Updated Description", LocalDate.now(), 150); + + when(filmStorage.contains(1L)).thenReturn(true); + when(filmStorage.update(inputFilm)).thenReturn(updatedFilm); + + Film result = filmService.updateFilm(inputFilm); + + assertNotNull(result); + assertEquals(1L, result.getId()); + verify(filmStorage).update(inputFilm); + verify(genreStorage, never()).updateFilmGenres(any()); + } + @Test public void shouldDeleteFilmSuccessfully() { Long filmId = 1L; @@ -141,7 +231,7 @@ public void shouldDeleteFilmSuccessfully() { filmService.deleteFilm(filmId); verify(filmStorage).remove(filmId); - verify(likeStorage).clearLikesSet(filmId); + verify(likeStorage).clearLikes(filmId); } @Test @@ -152,7 +242,7 @@ public void shouldThrowNotFoundExceptionWhenDeleteNonExistentFilm() { assertThrows(NotFoundException.class, () -> filmService.deleteFilm(filmId)); verify(filmStorage, never()).remove(filmId); - verify(likeStorage, never()).clearLikesSet(filmId); + verify(likeStorage, never()).clearLikes(filmId); } @Test @@ -171,7 +261,7 @@ public void shouldAddLikeSuccessfully() { assertNotNull(result); assertEquals(5L, result.getLikes()); verify(likeStorage).addLike(filmId, userId); - verify(filmStorage, times(2)).get(filmId); + verify(filmStorage).get(filmId); } @Test @@ -247,7 +337,7 @@ public void shouldGetTopFilmsOrderedByLikes() { Film film3 = new Film(3L, "Film 3", "Desc 3", LocalDate.now(), 130); film3.setLikes(5L); - when(filmStorage.getAll()).thenReturn(List.of(film1, film2, film3)); + when(filmStorage.getTopFilms(2L)).thenReturn(List.of(film2, film1)); Collection topFilms = filmService.getTopFilms(2L); @@ -257,13 +347,11 @@ public void shouldGetTopFilmsOrderedByLikes() { List resultList = new ArrayList<>(topFilms); assertEquals(25L, resultList.get(0).getLikes()); // film2 assertEquals(10L, resultList.get(1).getLikes()); // film1 + verify(filmStorage).getTopFilms(2L); } @Test public void shouldReturnEmptyListWhenNoFilms() { - when(filmStorage.getAll()).thenReturn(List.of()); - - // when Collection topFilms = filmService.getTopFilms(10L); assertNotNull(topFilms); @@ -272,17 +360,18 @@ public void shouldReturnEmptyListWhenNoFilms() { @Test public void shouldReturnAllFilmsWhenCountIsGreaterThanTotal() { - Film film1 = new Film(1L, "Film 1", "Desc 1", LocalDate.now(), 120); - film1.setLikes(5L); - Film film2 = new Film(2L, "Film 2", "Desc 2", LocalDate.now(), 150); - film2.setLikes(3L); + List allFilms = List.of( + new Film(1L, "Film 1", "Desc 1", LocalDate.now(), 120), + new Film(2L, "Film 2", "Desc 2", LocalDate.now(), 150), + new Film(3L, "Film 3", "Desc 3", LocalDate.now(), 130) + ); - when(filmStorage.getAll()).thenReturn(List.of(film1, film2)); + when(filmStorage.getTopFilms(10L)).thenReturn(allFilms); - // when - Collection topFilms = filmService.getTopFilms(5L); + Collection topFilms = filmService.getTopFilms(10L); assertNotNull(topFilms); - assertEquals(2, topFilms.size()); + assertEquals(3, topFilms.size()); + verify(filmStorage).getTopFilms(10L); } } \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/service/film/GenreServiceTest.java b/src/test/java/ru/yandex/practicum/filmorate/service/film/GenreServiceTest.java new file mode 100644 index 0000000..601fbd0 --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/service/film/GenreServiceTest.java @@ -0,0 +1,71 @@ +package ru.yandex.practicum.filmorate.service.film; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.model.Genre; +import ru.yandex.practicum.filmorate.storage.film.GenreStorage; + +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class GenreServiceTest { + + @Mock + private GenreStorage genreStorage; + + private GenreService mpaService; + + @BeforeEach + public void setUp() { + mpaService = new GenreService(genreStorage); + } + + @Test + public void shouldGetGenre() { + Genre mpa = new Genre(1L, "name1"); + when(genreStorage.getGenre(1L)).thenReturn(Optional.of(mpa)); + + Genre actualGenre = mpaService.getGenre(1L); + + assertNotNull(actualGenre); + assertEquals(actualGenre, mpa); + verify(genreStorage).getGenre(1L); + } + + @Test + public void shouldThrowNotFoundWhenNull() { + when(genreStorage.getGenre(1L)).thenReturn(Optional.ofNullable(null)); + + assertThrows(NotFoundException.class, () -> mpaService.getGenre(1L)); + + verify(genreStorage).getGenre(1L); + } + + @Test + public void shouldThrowNotFound() { + when(genreStorage.getGenre(1L)).thenReturn(Optional.empty()); + + assertThrows(NotFoundException.class, () -> mpaService.getGenre(1L)); + + verify(genreStorage).getGenre(1L); + } + + @Test + public void shouldGetAll() { + when(genreStorage.getAll()).thenReturn(List.of(new Genre(1L, "name1"), new Genre(2L, "name2"))); + + List list = (List) mpaService.getAll(); + + assertEquals(2, list.size()); + verify(genreStorage).getAll(); + } +} \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/service/film/MpaServiceTest.java b/src/test/java/ru/yandex/practicum/filmorate/service/film/MpaServiceTest.java new file mode 100644 index 0000000..5e17a2a --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/service/film/MpaServiceTest.java @@ -0,0 +1,71 @@ +package ru.yandex.practicum.filmorate.service.film; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.model.Mpa; +import ru.yandex.practicum.filmorate.storage.film.MpaStorage; + +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class MpaServiceTest { + + @Mock + private MpaStorage mpaStorage; + + private MpaService mpaService; + + @BeforeEach + public void setUp() { + mpaService = new MpaService(mpaStorage); + } + + @Test + public void shouldGetMpa() { + Mpa mpa = new Mpa(1L, "name1"); + when(mpaStorage.getMpa(1L)).thenReturn(Optional.of(mpa)); + + Mpa actualMpa = mpaService.getMpa(1L); + + assertNotNull(actualMpa); + assertEquals(actualMpa, mpa); + verify(mpaStorage).getMpa(1L); + } + + @Test + public void shouldThrowNotFoundWhenNull() { + when(mpaStorage.getMpa(1L)).thenReturn(Optional.ofNullable(null)); + + assertThrows(NotFoundException.class, () -> mpaService.getMpa(1L)); + + verify(mpaStorage).getMpa(1L); + } + + @Test + public void shouldThrowNotFound() { + when(mpaStorage.getMpa(1L)).thenReturn(Optional.empty()); + + assertThrows(NotFoundException.class, () -> mpaService.getMpa(1L)); + + verify(mpaStorage).getMpa(1L); + } + + @Test + public void shouldGetAll() { + when(mpaStorage.getAll()).thenReturn(List.of(new Mpa(1L, "name1"), new Mpa(2L, "name2"))); + + List list = (List) mpaService.getAll(); + + assertEquals(2, list.size()); + verify(mpaStorage).getAll(); + } +} diff --git a/src/test/java/ru/yandex/practicum/filmorate/service/user/UserServiceTest.java b/src/test/java/ru/yandex/practicum/filmorate/service/user/UserServiceTest.java index a314020..9b22297 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/service/user/UserServiceTest.java +++ b/src/test/java/ru/yandex/practicum/filmorate/service/user/UserServiceTest.java @@ -13,7 +13,6 @@ import java.time.LocalDate; import java.util.Collection; -import java.util.HashSet; import java.util.List; import java.util.Set; @@ -78,13 +77,11 @@ public void shouldAddUser() { User returnUser = new User(1L, "email@mail.com", "login", "name", LocalDate.now()); when(userStorage.add(any(User.class))).thenReturn(returnUser); - doNothing().when(friendShipStorage).initializeFriendsSet(1L); User result = userService.addUser(user1); assertEquals(returnUser, result); verify(userStorage).add(any(User.class)); - verify(friendShipStorage).initializeFriendsSet(1L); } @Test @@ -189,19 +186,12 @@ public void shouldThrowNotFoundWhenUpdateUserWhenIdNotFound() { @Test public void shouldDeleteUser() { Long userId = 1L; - User user = new User(userId, "email@mail.com", "login", "name", LocalDate.now()); when(userStorage.contains(userId)).thenReturn(true); - when(friendShipStorage.getFriends(userId)).thenReturn(new HashSet<>(Set.of(2L, 3L))); - when(friendShipStorage.getFriends(2L)).thenReturn(new HashSet<>(Set.of(1L, 4L))); - when(friendShipStorage.getFriends(3L)).thenReturn(new HashSet<>(Set.of(1L, 5L))); - userService.deleteUser(userId); verify(userStorage).remove(userId); - verify(friendShipStorage).clearFriendsSet(userId); - verify(friendShipStorage).getFriends(2L); - verify(friendShipStorage).getFriends(3L); + verify(friendShipStorage).deleteUserFromAllFriends(1L); } @Test @@ -210,27 +200,6 @@ public void shouldThrowNotFoundWhenDeleteUserWhenIdNotFound() { assertThrows(NotFoundException.class, () -> userService.deleteUser(1000L)); verify(userStorage, never()).remove(1000L); - verify(friendShipStorage, never()).clearFriendsSet(1000L); - } - - @Test - public void shouldRemoveUserFromFriendsFriendsLists() { - Long userId = 1L; - Set friends = Set.of(2L, 3L); - - Set friend2Friends = new HashSet<>(Set.of(1L, 4L, 5L)); - Set friend3Friends = new HashSet<>(Set.of(1L, 6L)); - - when(userStorage.contains(userId)).thenReturn(true); - when(friendShipStorage.getFriends(userId)).thenReturn(friends); - when(friendShipStorage.getFriends(2L)).thenReturn(friend2Friends); - when(friendShipStorage.getFriends(3L)).thenReturn(friend3Friends); - - userService.deleteUser(userId); - - assertFalse(friend2Friends.contains(userId)); - assertFalse(friend3Friends.contains(userId)); - verify(userStorage).remove(userId); } @Test @@ -244,7 +213,7 @@ public void shouldAddFriendSuccessfully() { userService.addFriend(senderId, receiverId); verify(friendShipStorage).addFriend(senderId, receiverId); - verify(friendShipStorage).addFriend(receiverId, senderId); + verify(friendShipStorage, never()).addFriend(receiverId, senderId); } @Test @@ -292,7 +261,7 @@ public void shouldDeleteFriendSuccessfully() { userService.deleteFriend(senderId, receiverId); verify(friendShipStorage).deleteFriend(senderId, receiverId); - verify(friendShipStorage).deleteFriend(receiverId, senderId); + verify(friendShipStorage, never()).deleteFriend(receiverId, senderId); } @Test @@ -327,9 +296,7 @@ public void shouldGetFriendsSuccessfully() { when(userStorage.contains(userId)).thenReturn(true); when(friendShipStorage.getFriends(userId)).thenReturn(friendIds); - when(userStorage.get(2L)).thenReturn(friend1); - when(userStorage.get(3L)).thenReturn(friend2); - + when(userStorage.getAllFromCollection(friendIds)).thenReturn(List.of(friend1, friend2)); Collection friends = userService.getFriends(userId); assertNotNull(friends); @@ -337,8 +304,7 @@ public void shouldGetFriendsSuccessfully() { assertTrue(friends.contains(friend1)); assertTrue(friends.contains(friend2)); verify(friendShipStorage).getFriends(userId); - verify(userStorage).get(2L); - verify(userStorage).get(3L); + verify(userStorage).getAllFromCollection(friendIds); } @Test @@ -368,19 +334,16 @@ public void shouldThrowNotFoundExceptionWhenGettingFriendsOfNonExistentUser() { public void shouldGetCommonFriendsSuccessfully() { Long user1Id = 1L; Long user2Id = 2L; - Set user1Friends = Set.of(3L, 4L, 5L); - Set user2Friends = Set.of(4L, 5L, 6L); + Set common = Set.of(4L, 5L); User commonFriend1 = new User(4L, "common1@mail.com", "common1", "Common One", LocalDate.now()); User commonFriend2 = new User(5L, "common2@mail.com", "common2", "Common Two", LocalDate.now()); + List commonUsers = List.of(commonFriend1, commonFriend2); when(userStorage.contains(user1Id)).thenReturn(true); when(userStorage.contains(user2Id)).thenReturn(true); - when(friendShipStorage.getFriends(user1Id)).thenReturn(user1Friends); - when(friendShipStorage.getFriends(user2Id)).thenReturn(user2Friends); - when(userStorage.get(4L)).thenReturn(commonFriend1); - when(userStorage.get(5L)).thenReturn(commonFriend2); - + when(friendShipStorage.getCommonFriends(user1Id, user2Id)).thenReturn(common); + when(userStorage.getAllFromCollection(common)).thenReturn(commonUsers); Collection commonFriends = userService.getCommonFriends(user1Id, user2Id); assertNotNull(commonFriends); @@ -393,13 +356,10 @@ public void shouldGetCommonFriendsSuccessfully() { public void shouldReturnEmptyCommonFriendsWhenNoCommonFriends() { Long user1Id = 1L; Long user2Id = 2L; - Set user1Friends = Set.of(3L, 4L); - Set user2Friends = Set.of(5L, 6L); when(userStorage.contains(user1Id)).thenReturn(true); when(userStorage.contains(user2Id)).thenReturn(true); - when(friendShipStorage.getFriends(user1Id)).thenReturn(user1Friends); - when(friendShipStorage.getFriends(user2Id)).thenReturn(user2Friends); + when(friendShipStorage.getCommonFriends(user1Id, user2Id)).thenReturn(Set.of()); Collection commonFriends = userService.getCommonFriends(user1Id, user2Id); diff --git a/src/test/java/ru/yandex/practicum/filmorate/storage/film/InMemoryFilmStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/storage/film/InMemoryFilmStorageTest.java index b7fb5b2..e17c170 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/storage/film/InMemoryFilmStorageTest.java +++ b/src/test/java/ru/yandex/practicum/filmorate/storage/film/InMemoryFilmStorageTest.java @@ -2,9 +2,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import ru.yandex.practicum.filmorate.films.film.InMemoryFilmStorage; +import ru.yandex.practicum.filmorate.storage.film.memory.InMemoryFilmStorage; import ru.yandex.practicum.filmorate.model.Film; +import java.time.LocalDate; +import java.util.List; + import static org.junit.jupiter.api.Assertions.*; public class InMemoryFilmStorageTest { @@ -74,8 +77,47 @@ public void shouldReturnFalseIfNotContains() { } @Test - public void shouldReturnSize() { - storage.add(film); - assertEquals(storage.getAll().size(), storage.size()); + public void shouldReturnTopFilms() { + Film film1 = new Film(1L, "Film 1", "Desc 1", LocalDate.now(), 120); + film1.setLikes(10L); + + Film film2 = new Film(2L, "Film 2", "Desc 2", LocalDate.now(), 130); + film2.setLikes(5L); + + Film film3 = new Film(3L, "Film 3", "Desc 3", LocalDate.now(), 140); + film3.setLikes(15L); + + storage.add(film1); + storage.add(film2); + storage.add(film3); + + List top = (List) storage.getTopFilms(5L); + + assertEquals(3,top.size()); + assertEquals(film3, top.get(0)); + assertEquals(film1, top.get(1)); + assertEquals(film2, top.get(2)); + } + + @Test + public void shouldReturnOnlyTwoTopFilms() { + Film film1 = new Film(1L, "Film 1", "Desc 1", LocalDate.now(), 120); + film1.setLikes(10L); + + Film film2 = new Film(2L, "Film 2", "Desc 2", LocalDate.now(), 130); + film2.setLikes(5L); + + Film film3 = new Film(3L, "Film 3", "Desc 3", LocalDate.now(), 140); + film3.setLikes(15L); + + storage.add(film1); + storage.add(film2); + storage.add(film3); + + List top = (List) storage.getTopFilms(2L); + + assertEquals(2,top.size()); + assertEquals(film3, top.get(0)); + assertEquals(film1, top.get(1)); } } diff --git a/src/test/java/ru/yandex/practicum/filmorate/storage/film/InMemoryLikeStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/storage/film/InMemoryLikeStorageTest.java index b51076d..0c9f24c 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/storage/film/InMemoryLikeStorageTest.java +++ b/src/test/java/ru/yandex/practicum/filmorate/storage/film/InMemoryLikeStorageTest.java @@ -2,6 +2,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import ru.yandex.practicum.filmorate.storage.film.memory.InMemoryLikeStorage; import java.lang.reflect.Field; import java.util.Map; @@ -29,23 +30,9 @@ private Map> getLikesField() { } } - @Test - public void shouldCreateLikesSet() { - storage.initializeLikesSet(1L); - assertNotNull(getLikesField().get(1L)); - } - - @Test - public void shouldRemoveSet() { - storage.initializeLikesSet(1L); - storage.clearLikesSet(1L); - assertNull(getLikesField().get(1L)); - } @Test public void shouldAddLike() { - storage.initializeLikesSet(1L); - storage.addLike(1L, 1L); storage.addLike(1L, 2L); @@ -54,7 +41,6 @@ public void shouldAddLike() { @Test public void shouldAddLikeOnlyOnce() { - storage.initializeLikesSet(1L); storage.addLike(1L, 1L); storage.addLike(1L, 1L); @@ -62,14 +48,8 @@ public void shouldAddLikeOnlyOnce() { assertEquals(1, getLikesField().get(1L).size()); } - @Test - public void shouldThrowNullPointerExceptionWhenAddLikeToFilmThatDoesNotExist() { - assertThrows(NullPointerException.class, () -> storage.addLike(1L, 1L)); - } - @Test public void shouldRemoveLike() { - storage.initializeLikesSet(1L); storage.addLike(1L, 1L); storage.addLike(1L, 2L); @@ -81,7 +61,6 @@ public void shouldRemoveLike() { @Test public void shouldRemoveLikeOnlyOnce() { - storage.initializeLikesSet(1L); storage.addLike(1L, 1L); storage.addLike(1L, 2L); @@ -93,7 +72,9 @@ public void shouldRemoveLikeOnlyOnce() { } @Test - public void shouldThrowNullPointerExceptionWhenRemoveLikeToFilmThatDoesNotExist() { - assertThrows(NullPointerException.class, () -> storage.deleteLike(1L, 1L)); + public void shouldClearLikes() { + storage.addLike(1L, 2L); + storage.clearLikes(1L); + assertFalse(getLikesField().containsKey(1L)); } } diff --git a/src/test/java/ru/yandex/practicum/filmorate/storage/film/db/DbFilmStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/storage/film/db/DbFilmStorageTest.java new file mode 100644 index 0000000..1b79354 --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/storage/film/db/DbFilmStorageTest.java @@ -0,0 +1,117 @@ +package ru.yandex.practicum.filmorate.storage.film.db; + +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.context.annotation.Import; +import org.springframework.dao.EmptyResultDataAccessException; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.storage.mappers.FilmMapper; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@JdbcTest +@Import({DbFilmStorage.class, FilmMapper.class}) +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class DbFilmStorageTest { + + private final DbFilmStorage filmStorage; + + @Test + public void shouldGetFilm() { + Film film = filmStorage.get(1L); + assertNotNull(film); + assertEquals("name1", film.getName()); + } + + @Test + public void shouldThrowWhenNotFound() { + assertThrows(EmptyResultDataAccessException.class, () -> filmStorage.get(1000L)); + } + + @Test + public void shouldGetAll() { + List list = (List) filmStorage.getAll(); + + assertEquals(5, list.size()); + + for (int i = 1; i <= list.size(); i++) { + assertEquals("name" + i, list.get(i - 1).getName()); + } + } + + @Test + public void shouldReturnTrue() { + boolean actual = filmStorage.contains(1L); + assertTrue(actual); + } + + @Test + public void shouldReturnFalse() { + boolean actual = filmStorage.contains(1000L); + assertFalse(actual); + } + + @Test + public void shouldAddFilm() { + Film film = new Film(); + film.setName("name6"); + film.setDescription("desc6"); + + Film actual = filmStorage.add(film); + + assertEquals(6, actual.getId()); + + assertTrue(filmStorage.contains(6L)); + } + + @Test + public void shouldUpdateFilm() { + Film film = filmStorage.get(1L); + film.setName("newName1"); + + Film actual = filmStorage.update(film); + + assertEquals("newName1", actual.getName()); + } + + @Test + public void shouldRemoveFilm() { + filmStorage.remove(1L); + assertFalse(filmStorage.contains(1L)); + } + + @Test + public void shouldGetTopFilmsLimit() { + + Collection topFilms = filmStorage.getTopFilms(2L); + + assertNotNull(topFilms); + assertEquals(2, topFilms.size()); + + List resultList = new ArrayList<>(topFilms); + assertEquals(3L, resultList.get(0).getId()); + assertEquals(2L, resultList.get(1).getId()); + } + + @Test + public void shouldGetTopFilmsNoLimit() { + + Collection topFilms = filmStorage.getTopFilms(1000L); + + assertNotNull(topFilms); + assertEquals(5, topFilms.size()); + + List resultList = new ArrayList<>(topFilms); + assertEquals(3L, resultList.get(0).getId()); + assertEquals(2L, resultList.get(1).getId()); + assertEquals(1L, resultList.get(2).getId()); + } +} diff --git a/src/test/java/ru/yandex/practicum/filmorate/storage/film/db/DbGenreStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/storage/film/db/DbGenreStorageTest.java new file mode 100644 index 0000000..9698668 --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/storage/film/db/DbGenreStorageTest.java @@ -0,0 +1,115 @@ +package ru.yandex.practicum.filmorate.storage.film.db; + +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.context.annotation.Import; +import org.springframework.dao.DataIntegrityViolationException; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.Genre; +import ru.yandex.practicum.filmorate.storage.mappers.FilmMapper; +import ru.yandex.practicum.filmorate.storage.mappers.GenreMapper; + +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@JdbcTest +@Import({DbGenreStorage.class, GenreMapper.class, DbFilmStorage.class, FilmMapper.class}) +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class DbGenreStorageTest { + + private final DbGenreStorage storage; + private final DbFilmStorage filmStorage; + + @Test + public void shouldGetGenre() { + + Optional genreOptional = storage.getGenre(1L); + + assertThat(genreOptional) + .isPresent() + .hasValueSatisfying(genre -> + assertThat(genre) + .hasFieldOrPropertyWithValue("id", 1L) + .hasFieldOrPropertyWithValue("name", "Комедия") + ); + } + + @Test + public void shouldGetEmptyGenre() { + Optional genreOptional = storage.getGenre(1000L); + assertThat(genreOptional).isEmpty(); + } + + @Test + public void shouldGetAll() { + List list = (List) storage.getAll(); + assertEquals(6, list.size()); + assertEquals("Боевик", list.getLast().getName()); + } + + @Test + public void shouldAddGenresToFilm() { + Film film = new Film(); + film.setId(1L); + List allGenres = (List) storage.getAll(); + Set genres = new HashSet<>(); + for (int i = 1; i <= 3; i++) { + genres.add(allGenres.get(i - 1)); + } + film.setGenres(genres); + + storage.addGenresToFilm(film); + + Set actual = filmStorage.get(1L).getGenres(); + assertEquals(3, actual.size()); + assertEquals(genres, actual); + } + + @Test + public void shouldThrowIfGenreInvalidAddGenre() { + Film film = new Film(); + film.setId(1L); + Set genres = Set.of(new Genre(1000L, "bla")); + film.setGenres(genres); + + assertThrows(DataIntegrityViolationException.class, () -> storage.addGenresToFilm(film)); + } + + @Test + public void shouldUpdateFilmGenres() { + Film film = new Film(); + film.setId(1L); + List allGenres = (List) storage.getAll(); + Set genres = new HashSet<>(); + for (int i = 1; i <= 3; i++) { + genres.add(allGenres.get(i - 1)); + } + film.setGenres(genres); + + storage.updateFilmGenres(film); + + Set actual = filmStorage.get(1L).getGenres(); + assertEquals(3, actual.size()); + assertEquals(genres, actual); + } + + @Test + public void shouldThrowIfGenreInvalidUpdateGenre() { + Film film = new Film(); + film.setId(1L); + Set genres = Set.of(new Genre(1000L, "bla")); + film.setGenres(genres); + + assertThrows(DataIntegrityViolationException.class, () -> storage.updateFilmGenres(film)); + } +} \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/storage/film/db/DbLikeStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/storage/film/db/DbLikeStorageTest.java new file mode 100644 index 0000000..5750b1b --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/storage/film/db/DbLikeStorageTest.java @@ -0,0 +1,37 @@ +package ru.yandex.practicum.filmorate.storage.film.db; + +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.context.annotation.Import; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@JdbcTest +@Import(DbLikeStorage.class) +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class DbLikeStorageTest { + + private final DbLikeStorage storage; + + @Test + public void shouldAddLikes() { + Long likes = storage.addLike(1L, 5L); + assertEquals(3L, likes); + } + + @Test + public void shouldDeleteLikes() { + Long likes = storage.deleteLike(1L, 1L); + assertEquals(1L, likes); + } + + @Test + public void shouldDoNothing() { + assertDoesNotThrow(() -> storage.clearLikes(1L)); + } +} diff --git a/src/test/java/ru/yandex/practicum/filmorate/storage/film/db/DbMpaStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/storage/film/db/DbMpaStorageTest.java new file mode 100644 index 0000000..c02cd38 --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/storage/film/db/DbMpaStorageTest.java @@ -0,0 +1,52 @@ +package ru.yandex.practicum.filmorate.storage.film.db; + +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.context.annotation.Import; +import ru.yandex.practicum.filmorate.model.Mpa; +import ru.yandex.practicum.filmorate.storage.mappers.MpaMapper; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@JdbcTest +@Import({DbMpaStorage.class, MpaMapper.class}) +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class DbMpaStorageTest { + + private final DbMpaStorage storage; + + @Test + public void shouldGetMpa() { + + Optional mpaOptional = storage.getMpa(1L); + + assertThat(mpaOptional) + .isPresent() + .hasValueSatisfying(mpa -> + assertThat(mpa) + .hasFieldOrPropertyWithValue("id", 1L) + .hasFieldOrPropertyWithValue("name", "G") + ); + } + + @Test + public void shouldGetEmptyMpa() { + Optional mpaOptional = storage.getMpa(1000L); + assertThat(mpaOptional).isEmpty(); + } + + @Test + public void shouldGetAll() { + List list = (List) storage.getAll(); + assertEquals(5, list.size()); + assertEquals("NC-17", list.getLast().getName()); + } +} diff --git a/src/test/java/ru/yandex/practicum/filmorate/storage/user/InMemoryFriendShipStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/storage/user/InMemoryFriendShipStorageTest.java index 058ac5b..802db3a 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/storage/user/InMemoryFriendShipStorageTest.java +++ b/src/test/java/ru/yandex/practicum/filmorate/storage/user/InMemoryFriendShipStorageTest.java @@ -2,6 +2,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import ru.yandex.practicum.filmorate.storage.user.memory.InMemoryFriendShipStorage; import java.lang.reflect.Field; import java.util.Map; @@ -32,8 +33,6 @@ private Map> getFriendShips() { @Test public void shouldCreateFriendShip() { - friendShipStorage.initializeFriendsSet(1L); - friendShipStorage.initializeFriendsSet(2L); friendShipStorage.addFriend(1L, 2L); friendShipStorage.addFriend(2L, 1L); @@ -42,27 +41,15 @@ public void shouldCreateFriendShip() { assertTrue(getFriendShips().get(2L).contains(1L)); } - @Test - public void shouldThrowNullPointerExceptionWhenSenderIsNotFoundCreateFriendShip() { - assertThrows(NullPointerException.class, () -> friendShipStorage.addFriend(1000L, 1L)); - } - - @Test - public void shouldThrowNullPointerExceptionWhenReceiverIsNotFoundCreateFriendShip() { - assertThrows(NullPointerException.class, () -> friendShipStorage.addFriend(1L, 1000L)); - } - @Test public void shouldClearUserFriendsAndDeleteUser() { - friendShipStorage.initializeFriendsSet(1L); - friendShipStorage.clearFriendsSet(1L); + friendShipStorage.addFriend(1L, 2L); + friendShipStorage.deleteUserFromAllFriends(1L); assertNull(getFriendShips().get(1L)); } @Test public void shouldBreakFriendShip() { - friendShipStorage.initializeFriendsSet(1L); - friendShipStorage.initializeFriendsSet(2L); friendShipStorage.addFriend(1L, 2L); friendShipStorage.addFriend(2L, 1L); @@ -74,20 +61,8 @@ public void shouldBreakFriendShip() { assertFalse(getFriendShips().get(2L).contains(1L)); } - @Test - public void shouldThrowNullPointerExceptionWhenSenderIsNotFoundBreakFriendShip() { - assertThrows(NullPointerException.class, () -> friendShipStorage.deleteFriend(1000L, 1L)); - } - - @Test - public void shouldThrowNullPointerExceptionWhenReceiverIsNotFoundBreakFriendShip() { - assertThrows(NullPointerException.class, () -> friendShipStorage.deleteFriend(1L, 1000L)); - } - @Test public void shouldReturnFriends() { - friendShipStorage.initializeFriendsSet(1L); - friendShipStorage.initializeFriendsSet(2L); friendShipStorage.addFriend(1L, 2L); friendShipStorage.addFriend(2L, 1L); @@ -96,7 +71,28 @@ public void shouldReturnFriends() { } @Test - public void shouldThrowNullPointerExceptionWhenUserIsNotFoundReturnFriends() { - assertNull(friendShipStorage.getFriends(1000L)); + public void shouldGetCommonFriends() { + friendShipStorage.addFriend(1L, 2L); + friendShipStorage.addFriend(1L, 3L); + friendShipStorage.addFriend(2L, 3L); + + Set common = friendShipStorage.getCommonFriends(1L, 2L); + + assertTrue(common.contains(3L)); + assertFalse(common.contains(1L)); + assertFalse(common.contains(2L)); + } + + @Test + public void shouldGetCommonFriendsReverseOrder() { + friendShipStorage.addFriend(1L, 2L); + friendShipStorage.addFriend(1L, 3L); + friendShipStorage.addFriend(2L, 3L); + + Set common = friendShipStorage.getCommonFriends(2L, 1L); + + assertTrue(common.contains(3L)); + assertFalse(common.contains(1L)); + assertFalse(common.contains(2L)); } -} \ No newline at end of file +} diff --git a/src/test/java/ru/yandex/practicum/filmorate/storage/user/InMemoryUserStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/storage/user/InMemoryUserStorageTest.java index cd48557..e8ef364 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/storage/user/InMemoryUserStorageTest.java +++ b/src/test/java/ru/yandex/practicum/filmorate/storage/user/InMemoryUserStorageTest.java @@ -3,8 +3,10 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.storage.user.memory.InMemoryUserStorage; import java.lang.reflect.Field; +import java.util.List; import java.util.Map; import static org.junit.jupiter.api.Assertions.*; @@ -89,8 +91,18 @@ public void shouldReturnFalseIfNotContains() { } @Test - public void shouldReturnSize() { - storage.add(user); - assertEquals(storage.getAll().size(), storage.size()); + public void shouldGetAllFromCollection() { + List ids = List.of(1L, 2L, 3L); + for (int i = 0; i < 3; i++) { + User user = new User(); + user.setName(i + ""); + storage.add(user); + } + + List users = storage.getAllFromCollection(ids); + + assertEquals("0", users.get(0).getName()); + assertEquals("1", users.get(1).getName()); + assertEquals("2", users.get(2).getName()); } } diff --git a/src/test/java/ru/yandex/practicum/filmorate/storage/user/db/DbFriendShipStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/storage/user/db/DbFriendShipStorageTest.java new file mode 100644 index 0000000..2fca837 --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/storage/user/db/DbFriendShipStorageTest.java @@ -0,0 +1,59 @@ +package ru.yandex.practicum.filmorate.storage.user.db; + +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.context.annotation.Import; + +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +@JdbcTest +@Import({DbFriendShipStorage.class}) +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class DbFriendShipStorageTest { + + private final DbFriendShipStorage storage; + + @Test + public void shouldGetFriend() { + Set friends = storage.getFriends(3L); + assertTrue(friends.contains(1L)); + assertTrue(friends.contains(4L)); + } + + @Test + public void shouldAddFriend() { + storage.addFriend(1L, 4L); + Set list = storage.getFriends(1L); + assertTrue(list.contains(4L)); + } + + @Test + public void shouldDeleteFriend() { + storage.deleteFriend(3L, 1L); + Set list = storage.getFriends(3L); + assertFalse(list.contains(1L)); + } + + @Test + public void shouldGetCommonFriends() { + Set common = storage.getCommonFriends(2L, 3L); + assertTrue(common.contains(1L)); + } + + @Test + public void shouldGetCommonFriendsReverse() { + Set common = storage.getCommonFriends(3L, 2L); + assertTrue(common.contains(1L)); + } + + @Test + public void shouldDoNothing() { + assertDoesNotThrow(() -> storage.deleteUserFromAllFriends(1L)); + } +} diff --git a/src/test/java/ru/yandex/practicum/filmorate/storage/user/db/DbUserStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/storage/user/db/DbUserStorageTest.java new file mode 100644 index 0000000..cf97adf --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/storage/user/db/DbUserStorageTest.java @@ -0,0 +1,119 @@ +package ru.yandex.practicum.filmorate.storage.user.db; + +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.context.annotation.Import; +import org.springframework.dao.EmptyResultDataAccessException; +import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.storage.mappers.UserMapper; + +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@JdbcTest +@Import({DbUserStorage.class, UserMapper.class}) +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class DbUserStorageTest { + + private final DbUserStorage userStorage; + + @Test + public void shouldGetUser() { + User user = userStorage.get(1L); + assertNotNull(user); + assertEquals("name1", user.getName()); + } + + @Test + public void shouldThrowWhenNotFound() { + assertThrows(EmptyResultDataAccessException.class, () -> userStorage.get(1000L)); + } + + @Test + public void shouldGetAll() { + List list = (List) userStorage.getAll(); + + assertEquals(5, list.size()); + + for (int i = 1; i <= list.size(); i++) { + assertEquals("name" + i, list.get(i - 1).getName()); + } + } + + @Test + public void shouldReturnTrue() { + boolean actual = userStorage.contains(1L); + assertTrue(actual); + } + + @Test + public void shouldReturnFalse() { + boolean actual = userStorage.contains(1000L); + assertFalse(actual); + } + + @Test + public void shouldAddUser() { + User user = new User(); + user.setEmail("login6"); + user.setLogin("login6"); + + User actual = userStorage.add(user); + + assertEquals(6, actual.getId()); + + assertTrue(userStorage.contains(6L)); + } + + @Test + public void shouldUpdateUser() { + User user = userStorage.get(1L); + user.setName("newName1"); + + User actual = userStorage.update(user); + + assertEquals("newName1", actual.getName()); + } + + @Test + public void shouldRemoveUser() { + userStorage.remove(1L); + assertFalse(userStorage.contains(1L)); + } + + @Test + public void shouldGetAllFromCollection() { + List ids = List.of(3L, 4L, 5L); + List users = userStorage.getAllFromCollection(ids); + + assertEquals(3, users.size()); + assertEquals("name3", users.get(0).getName()); + assertEquals("name4", users.get(1).getName()); + assertEquals("name5", users.get(2).getName()); + } + + @Test + public void shouldIgnoreIfNotFound() { + List ids = List.of(4000L, 5L, 6L); + assertDoesNotThrow(() -> userStorage.getAllFromCollection(ids)); + } + + @Test + public void shouldIgnoreIfEmpty() { + List ids = Collections.emptyList(); + List list = userStorage.getAllFromCollection(ids); + assertTrue(list.isEmpty()); + } + + @Test + public void shouldReturnEmptyListIfNull() { + List list = userStorage.getAllFromCollection(null); + assertTrue(list.isEmpty()); + } +} diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index 947989f..6f392d3 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -1,3 +1,12 @@ logging.level.ru.yandex.practicum.filmorate=OFF logging.level.org.springframework=WARN -spring.main.banner-mode=off \ No newline at end of file +spring.main.banner-mode=off + +spring.datasource.url=jdbc:h2:mem:testdb +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= + +spring.sql.init.mode=always +spring.sql.init.schema-locations=classpath:schema.sql +spring.sql.init.data-locations=classpath:data.sql \ No newline at end of file diff --git a/src/test/resources/data.sql b/src/test/resources/data.sql new file mode 100644 index 0000000..4019ea2 --- /dev/null +++ b/src/test/resources/data.sql @@ -0,0 +1,51 @@ +INSERT INTO mpa (name) VALUES +('G'), +('PG'), +('PG-13'), +('R'), +('NC-17'); + +INSERT INTO genres (name) VALUES +('Комедия'), +('Драма'), +('Мультфильм'), +('Триллер'), +('Документальный'), +('Боевик'); + +INSERT INTO users (email, login, name, birthday) VALUES +('email1', 'login1', 'name1', '2000-01-01'), +('email2', 'login2', 'name2', '2000-01-01'), +('email3', 'login3', 'name3', '2000-01-01'), +('email4', 'login4', 'name4', '2000-01-01'), +('email5', 'login5', 'name5', NULL); + +INSERT INTO friendships(user_id, friend_id) VALUES +(1L, 2L), +(2L, 1L), +(3L, 1L), +(3L, 4L); + +INSERT INTO films (name, description, release_date, duration, mpa_id) VALUES +('name1', 'desc1', '2000-01-01', 100, 1), +('name2', 'desc2', '2000-01-01', 100, 2), +('name3', 'desc3', '2000-01-01', 100, 3), +('name4', 'desc4', '2000-01-01', 100, 4), +('name5', 'desc5', '2000-01-01', 100, 5); + +INSERT INTO films_genres (film_id, genre_id) VALUES +(4,1), +(5,1), +(5,2), +(5,3); + +INSERT INTO likes (film_id, user_id) VALUES +(1,1), +(1,2), +(2,1), +(2,3), +(2,4), +(3,1), +(3,2), +(3,3), +(3,4); \ No newline at end of file diff --git a/src/test/resources/schema.sql b/src/test/resources/schema.sql new file mode 100644 index 0000000..a1597a0 --- /dev/null +++ b/src/test/resources/schema.sql @@ -0,0 +1,44 @@ +CREATE TABLE IF NOT EXISTS users ( + user_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + email VARCHAR NOT NULL, + login VARCHAR NOT NULL, + name VARCHAR, + birthday DATE); + +CREATE TABLE IF NOT EXISTS friendships ( + user_id BIGINT, + friend_id BIGINT, + FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE, + FOREIGN KEY (friend_id) REFERENCES users(user_id) ON DELETE CASCADE, + PRIMARY KEY (user_id, friend_id)); + +CREATE TABLE IF NOT EXISTS mpa ( + mpa_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name VARCHAR NOT NULL); + +CREATE TABLE IF NOT EXISTS films ( + film_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name VARCHAR NOT NULL, + description VARCHAR(200), + release_date DATE, + duration INTEGER, + mpa_id BIGINT, + FOREIGN KEY (mpa_id) REFERENCES mpa(mpa_id) ON DELETE SET NULL); + +CREATE TABLE IF NOT EXISTS genres ( + genre_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name VARCHAR NOT NULL); + +CREATE TABLE IF NOT EXISTS films_genres ( + film_id BIGINT, + genre_id BIGINT, + FOREIGN KEY (film_id) REFERENCES films(film_id) ON DELETE CASCADE, + FOREIGN KEY (genre_id) REFERENCES genres(genre_id) ON DELETE CASCADE, + PRIMARY KEY (film_id, genre_id)); + +CREATE TABLE IF NOT EXISTS likes ( + film_id BIGINT, + user_id BIGINT, + FOREIGN KEY (film_id) REFERENCES films(film_id) ON DELETE CASCADE, + FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE, + PRIMARY KEY (film_id, user_id)); \ No newline at end of file