diff --git a/README.md b/README.md
index 05a8e4d..493a848 100644
--- a/README.md
+++ b/README.md
@@ -10,22 +10,30 @@
## Текущий этап работы над проектом:
Спринт 12:
- - Проектирование будущей базы данных;
- - Создание ER-диаграммы базы данных.
+- Переработана архитектура проекта;
+- Добавлены новые модели: MPA-рейтинг, Genre, Likes, Friendship;
+- Добавлен слой DAL для работы с данными из базы данных;
+- Добавлен маппинг для преобразования записей из базы данных в объекты и обратно;
+- Написаны интеграционные тесты;
+- Добавлены тестовые хранилища для unit-тестов;
+- Переработаны unit-тесты для граничных условий с учетом новой архитектуры проекта;
## Этапы работы над проектом
1. Спринт 10:
- - Создание каркаса приложения с использользованием Spring Boot;
- - Вся логика и хранение данных находятся в контроллерах;
- - Разработан базовый функционал: добавление, обновление и получение всех фильмов/пользователей;
- - Написаны unit-тесты для текущего функционала.
+ - Создание каркаса приложения с использользованием Spring Boot;
+ - Вся логика и хранение данных находятся в контроллерах;
+ - Разработан базовый функционал: добавление, обновление и получение всех фильмов/пользователей;
+ - Написаны unit-тесты для текущего функционала.
2. Спринт 11:
- - Добавлены классы-хранилища для работы с данными о фильмах и пользователях;
- - Добавлен новый функционал, позволяющий добавлять друзей и ставить лайки фильмам;
- - Добавлены аннотации @service и @component для внедрения зависимостей;
- - В классы контроллеры добавлена аннотация @ResponseStatus для указания HTTP-статуса ответа, который должен вернуться при обработке запроса;
- - Добавлены классы ErrorHandler и ErrorResponse для централизованной обработки ошибок;
- - Добавлены unit-тесты для нового функционала.
+ - Добавлены классы-хранилища для работы с данными о фильмах и пользователях;
+ - Добавлен новый функционал, позволяющий добавлять друзей и ставить лайки фильмам;
+ - Добавлены аннотации @service и @component для внедрения зависимостей;
+ - В классы контроллеры добавлена аннотация @ResponseStatus для указания HTTP-статуса ответа, который должен вернуться при обработке запроса;
+ - Добавлены классы ErrorHandler и ErrorResponse для централизованной обработки ошибок;
+ - Добавлены unit-тесты для нового функционала.
+3. Спринт 12. Промежуточное задание:
+ - Спроектирована будущая база данных;
+ - Создана ER-диаграмма базы данных.
## ER-диаграмма

@@ -34,7 +42,7 @@
1. Получить всех пользователей, кто родился не раньше 1995 года
```sql
-SELECT *
+SELECT u.user_id, u.email, u.login, u.name, u.birthday
FROM users AS u
WHERE EXTRACT(YEAR
FROM birthday) >= 1995
@@ -43,20 +51,19 @@ ORDER BY birthday
2. Получить фильмы, у которых не меньше двух лайков
```sql
SELECT f.film_id,
- f.name AS film_name,
+ f.name AS title,
f.description,
f.release_date,
+ m.name AS mpa_rating,
COUNT(l.user_id) AS likes_count
FROM films AS f
LEFT JOIN likes l ON f.film_id = l.film_id
-GROUP BY f.film_id,
- f.name,
- f.description,
- f.release_date
+JOIN mpa_rating AS m ON f.rating_id = m.rating_id
+GROUP BY f.film_id, m.name
HAVING COUNT(l.user_id) >= 2
ORDER BY likes_count DESC;
```
-
+
3. Получить пользователей, у которых яндекс почта
```sql
SELECT u.user_id,
@@ -64,5 +71,6 @@ SELECT u.user_id,
u.login,
u.name
FROM users AS u
-WHERE email LIKE '%yandex%'
-```
+WHERE email LIKE '%yandex%' OR email LIKE '%@ya.ru%'
+ORDER BY user_id
+```
\ No newline at end of file
diff --git a/filmorate_diagram.png b/filmorate_diagram.png
index c3812f8..68b563d 100644
Binary files a/filmorate_diagram.png and b/filmorate_diagram.png differ
diff --git a/pom.xml b/pom.xml
index 9c95881..f1eb76c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,59 +1,63 @@
- 4.0.0
-
- org.springframework.boot
- spring-boot-starter-parent
- 3.2.4
-
-
- ru.yandex.practicum
- filmorate
- 0.0.1-SNAPSHOT
- filmorate
- filmorate
-
- 21
-
-
-
- org.springframework.boot
- spring-boot-starter-web
-
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.2.4
+
+
+ ru.yandex.practicum
+ filmorate
+ 0.0.1-SNAPSHOT
+ filmorate
+ filmorate
+
+ 21
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.projectlombok
+ lombok
+ provided
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.zalando
+ logbook-spring-boot-starter
+ 3.7.2
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jdbc
+
+
+ com.h2database
+ h2
+ runtime
+
+
-
- org.projectlombok
- lombok
- provided
-
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
-
- org.springframework.boot
- spring-boot-starter-validation
-
-
-
- org.zalando
- logbook-spring-boot-starter
- 3.7.2
-
-
-
-
-
-
-
- org.springframework.boot
- spring-boot-maven-plugin
-
-
-
-
-
+
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java
index 7829e10..f47eae7 100644
--- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java
+++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java
@@ -4,7 +4,9 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
-import ru.yandex.practicum.filmorate.model.Film;
+import ru.yandex.practicum.filmorate.dto.FilmDto;
+import ru.yandex.practicum.filmorate.dto.NewFilmRequest;
+import ru.yandex.practicum.filmorate.dto.UpdateFilmRequest;
import ru.yandex.practicum.filmorate.service.FilmService;
import java.util.Collection;
@@ -18,22 +20,22 @@ public class FilmController {
private final FilmService filmService;
@GetMapping
- public Collection findAll() {
+ public Collection findAll() {
log.info("Получен запрос на получение списка всех фильмов");
return filmService.findAll();
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
- public Film create(@RequestBody Film newFilm) {
- log.info("Получен запрос на добавление фильма {}", newFilm.getName());
- return filmService.create(newFilm);
+ public FilmDto create(@RequestBody NewFilmRequest newFilmRequest) {
+ log.info("Получен запрос на добавление фильма {}", newFilmRequest.getName());
+ return filmService.create(newFilmRequest);
}
@PutMapping
- public Film update(@RequestBody Film film) {
- log.info("Получен запрос на обновление фильма c Id:{}", film.getId());
- return filmService.update(film);
+ public FilmDto update(@RequestBody UpdateFilmRequest filmRequest) {
+ log.info("Получен запрос на обновление фильма c Id:{}", filmRequest.getId());
+ return filmService.update(filmRequest);
}
@DeleteMapping("/{id}")
@@ -44,9 +46,9 @@ public void delete(@PathVariable("id") Long filmId) {
}
@GetMapping("/{id}")
- public Film getById(@PathVariable("id") Long filmId) {
+ public FilmDto getById(@PathVariable("id") Long filmId) {
log.info("Получен запрос на получение фильма с Id:{}", filmId);
- return filmService.getById(filmId);
+ return filmService.findById(filmId);
}
@PutMapping("/{id}/like/{userId}")
@@ -64,9 +66,9 @@ public void removeLike(@PathVariable("id") Long filmId, @PathVariable("userId")
}
@GetMapping("/popular")
- public Collection getPopularFilms(
+ public Collection getPopularFilms(
@RequestParam(defaultValue = "10") int count) {
log.info("Получен запрос на получение топ-{} фильмов по количеству лайков", count);
- return filmService.getPopularFilms(count);
+ return filmService.findPopularFilms(count);
}
}
\ No newline at end of file
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..c024332
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/controller/GenreController.java
@@ -0,0 +1,33 @@
+package ru.yandex.practicum.filmorate.controller;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+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.GenreService;
+
+import java.util.Collection;
+
+@Slf4j
+@RestController
+@RequestMapping("/genres")
+@RequiredArgsConstructor
+public class GenreController {
+
+ private final GenreService genreService;
+
+ @GetMapping
+ public Collection findAll() {
+ log.info("Получен запрос на получение всех пользователей");
+ return genreService.findAll();
+ }
+
+ @GetMapping("/{id}")
+ public Genre findById(@PathVariable("id") Long genreId) {
+ log.info("Получен запрос на получение жанра с id:{}", genreId);
+ return genreService.findById(genreId);
+ }
+}
\ 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..4f148c0
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/controller/MpaController.java
@@ -0,0 +1,33 @@
+package ru.yandex.practicum.filmorate.controller;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+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.MpaService;
+
+import java.util.Collection;
+
+@Slf4j
+@RestController
+@RequestMapping("/mpa")
+@RequiredArgsConstructor
+public class MpaController {
+
+ private final MpaService mpaService;
+
+ @GetMapping
+ public Collection findAll() {
+ log.info("Получен запрос на получение всех MPA-рейтингов");
+ return mpaService.findAll();
+ }
+
+ @GetMapping("/{id}")
+ public Mpa findById(@PathVariable("id") Long ratingId) {
+ log.info("Получен запрос на получение MPA-рейтинга с id:{}", ratingId);
+ return mpaService.findById(ratingId);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java
index e0f3ebe..80e262f 100644
--- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java
+++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java
@@ -4,7 +4,9 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
-import ru.yandex.practicum.filmorate.model.User;
+import ru.yandex.practicum.filmorate.dto.NewUserRequest;
+import ru.yandex.practicum.filmorate.dto.UpdateUserRequest;
+import ru.yandex.practicum.filmorate.dto.UserDto;
import ru.yandex.practicum.filmorate.service.UserService;
import java.util.*;
@@ -18,22 +20,22 @@ public class UserController {
private final UserService userService;
@GetMapping
- public Collection findAll() {
+ public Collection findAll() {
log.info("Получен запрос на получение всех пользователей");
return userService.findAll();
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
- public User create(@RequestBody User newUser) {
- log.info("Получен запрос на добавление нового пользователя {}", newUser.getLogin());
- return userService.create(newUser);
+ public UserDto create(@RequestBody NewUserRequest newUserRequest) {
+ log.info("Получен запрос на добавление нового пользователя {}", newUserRequest.getLogin());
+ return userService.create(newUserRequest);
}
@PutMapping
- public User update(@RequestBody User user) {
- log.info("Получен запрос на обновление пользователя с id:{}", user.getId());
- return userService.update(user);
+ public UserDto update(@RequestBody UpdateUserRequest userRequest) {
+ log.info("Получен запрос на обновление пользователя с id:{}", userRequest.getId());
+ return userService.update(userRequest);
}
@DeleteMapping("/{id}")
@@ -44,7 +46,7 @@ public void delete(@PathVariable("id") Long userId) {
}
@GetMapping("/{id}")
- public User getById(@PathVariable("id") Long userId) {
+ public UserDto getById(@PathVariable("id") Long userId) {
log.info("Получен запрос на получение пользователя с Id:{}", userId);
return userService.getById(userId);
}
@@ -64,14 +66,14 @@ public void removeFriend(@PathVariable("id") Long userId, @PathVariable("friendI
}
@GetMapping("/{id}/friends")
- public Collection getFriends(@PathVariable("id") Long userId) {
+ public Collection getFriends(@PathVariable("id") Long userId) {
log.info("Получен запрос на получение списка всех друзей пользователя с Id:{}", userId);
- return userService.getFriends(userId);
+ return userService.findAllFriends(userId);
}
@GetMapping("/{id}/friends/common/{otherId}")
- public Collection getCommonFriends(@PathVariable("id") Long userId, @PathVariable("otherId") Long otherId) {
+ public Collection getCommonFriends(@PathVariable("id") Long userId, @PathVariable("otherId") Long otherId) {
log.info("Получен запрос на получение списка общих друзей у пользователей с Id:{} и {}", userId, otherId);
- return userService.getCommonFriends(userId, otherId);
+ return userService.findCommonFriends(userId, otherId);
}
}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/BaseDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/BaseDbStorage.java
new file mode 100644
index 0000000..ba00dc4
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/dal/BaseDbStorage.java
@@ -0,0 +1,64 @@
+package ru.yandex.practicum.filmorate.dal;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.jdbc.support.GeneratedKeyHolder;
+import ru.yandex.practicum.filmorate.exception.InternalServerException;
+
+import java.sql.PreparedStatement;
+import java.sql.Statement;
+import java.util.List;
+import java.util.Optional;
+
+@RequiredArgsConstructor
+public class BaseDbStorage {
+
+ protected final JdbcTemplate jdbc;
+ protected final RowMapper mapper;
+
+ protected Optional findOne(String query, Object... params) {
+ try {
+ T result = jdbc.queryForObject(query, mapper, params);
+ return Optional.ofNullable(result);
+ } catch (EmptyResultDataAccessException ignored) {
+ return Optional.empty();
+ }
+ }
+
+ protected List findMany(String query, Object... params) {
+ return jdbc.query(query, mapper, params);
+ }
+
+ protected boolean delete(String query, long id) {
+ int rowsDeleted = jdbc.update(query, id);
+ return rowsDeleted > 0;
+ }
+
+ protected void update(String query, Object... params) {
+ int rowsUpdated = jdbc.update(query, params);
+ if (rowsUpdated == 0) {
+ throw new InternalServerException("Не удалось обновить данные");
+ }
+ }
+
+ protected long insert(String query, Object... params) {
+ GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
+ jdbc.update(connection -> {
+ PreparedStatement ps = connection
+ .prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
+ for (int idx = 0; idx < params.length; idx++) {
+ ps.setObject(idx + 1, params[idx]);
+ }
+ return ps; }, keyHolder);
+
+ Long id = keyHolder.getKeyAs(Long.class);
+
+ if (id != null) {
+ return id;
+ } else {
+ throw new InternalServerException("Не удалось сохранить данные");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmDbStorage.java
new file mode 100644
index 0000000..811245b
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmDbStorage.java
@@ -0,0 +1,158 @@
+package ru.yandex.practicum.filmorate.dal;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Repository;
+import ru.yandex.practicum.filmorate.model.*;
+import ru.yandex.practicum.filmorate.storage.*;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Repository
+public class FilmDbStorage extends BaseDbStorage implements FilmStorage {
+
+ @Autowired
+ private GenreStorage genreStorage;
+ @Autowired
+ private LikesStorage likesStorage;
+ @Autowired
+ private MpaStorage mpaStorage;
+
+ private static final String INSERT_QUERY = "INSERT INTO films" +
+ "(name, description, release_date, duration, rating_id) " +
+ "VALUES (?, ?, ?, ?, ?)";
+ private static final String DELETE_QUERY = "DELETE FROM films WHERE film_id = ?";
+ private static final String UPDATE_QUERY = "UPDATE films SET " +
+ "name = ?, description = ?, release_date = ?, duration = ?, rating_id = ? WHERE film_id = ?";
+ private static final String FIND_BY_ID_QUERY = "SELECT f.film_id, f.name, f.description, f.release_date, " +
+ "f.duration, m.rating_id, m.name AS mpa_name " +
+ "FROM films AS f " +
+ "INNER JOIN mpa_rating AS m ON f.rating_id = m.rating_id " +
+ "WHERE f.film_id = ?";
+ private static final String FIND_ALL_FILMS_QUERY = "SELECT f.film_id, f.name, f.description, f.release_date, " +
+ "f.duration, m.rating_id, m.name AS mpa_name " +
+ "FROM films AS f " +
+ "INNER JOIN mpa_rating AS m ON f.rating_id = m.rating_id";
+ private static final String FIND_POPULAR_FILMS_QUERY = "SELECT f.film_id, f.name, f.description, " +
+ "f.release_date, f.duration, m.rating_id, m.name AS mpa_name, COUNT(l.user_id) AS likes " +
+ "FROM films As f " +
+ "INNER JOIN mpa_rating AS m ON f.rating_id = m.rating_id " +
+ "LEFT JOIN likes AS l ON f.film_id = l.film_id " +
+ "GROUP BY f.film_id " +
+ "ORDER BY likes DESC " +
+ "LIMIT ?";
+
+ public FilmDbStorage(JdbcTemplate jdbc, RowMapper mapper) {
+ super(jdbc, mapper);
+ }
+
+ // Добавление фильма
+ @Override
+ public Film create(Film film) {
+ long id = insert(
+ INSERT_QUERY,
+ film.getName(),
+ film.getDescription(),
+ film.getReleaseDate(),
+ film.getDuration(),
+ film.getMpa().getId()
+ );
+ film.setId(id);
+
+ if (film.getGenres() != null) {
+ genreStorage.setGenreForFilm(id, film.getGenres());
+ }
+
+ mpaStorage.findMpaFilm(id).ifPresent(film::setMpa);
+
+ return film;
+ }
+
+ // Удаление фильма
+ @Override
+ public void delete(Long id) {
+ delete(DELETE_QUERY, id);
+ }
+
+ // Изменение фильма
+ @Override
+ public Film update(Film film) {
+ update(
+ UPDATE_QUERY,
+ film.getName(),
+ film.getDescription(),
+ film.getReleaseDate(),
+ film.getDuration(),
+ film.getMpa().getId(),
+ film.getId()
+ );
+
+ if (film.getGenres() != null) {
+ genreStorage.setGenreForFilm(film.getId(), film.getGenres());
+ }
+
+ // Возвращаем полный объект из БД
+ return findById(film.getId()).orElseThrow();
+ }
+
+ // Получение фильма по id
+ @Override
+ public Optional findById(Long id) {
+ Optional film = findOne(FIND_BY_ID_QUERY, id);
+ film.ifPresent(this::loadFilmData);
+ return film;
+ }
+
+ // Получение всех фильмов
+ @Override
+ public Collection findAll() {
+ Collection films = findMany(FIND_ALL_FILMS_QUERY);
+ films.forEach(this::loadFilmData);
+ return films;
+ }
+
+ // Получение популярных фильмов по количеству лайков
+ @Override
+ public Collection findPopularFilms(int count) {
+ Collection films = findMany(FIND_POPULAR_FILMS_QUERY, count);
+ films.forEach(this::loadFilmData);
+ return films;
+ }
+
+ // Общий вспомогательный метод для загрузки всех данных фильма
+ private void loadFilmData(Film film) {
+ loadLikes(film);
+ loadGenres(film);
+ loadMpa(film);
+ }
+
+ // Вспомогательный метод для загрузки данных о лайках фильма
+ private void loadLikes(Film film) {
+ Set likes = likesStorage.getLikesOnFilm(film.getId()).stream()
+ .map(Likes::getUserId)
+ .collect(Collectors.toCollection(HashSet::new));
+ film.setLikesByUsers(likes);
+ }
+
+ // Вспомогательный метод для загрузки данных о жанрах фильма
+ private void loadGenres(Film film) {
+ Collection genres = genreStorage.findGenreForFilm(film.getId());
+ film.setGenres(genres != null ?
+ genres.stream()
+ .sorted(Comparator.comparing(Genre::getId))
+ .collect(Collectors.toCollection(LinkedHashSet::new)) :
+ new LinkedHashSet<>());
+ }
+
+ // Вспомогательный метод для загрузки данных о рейтинге фильма
+ private void loadMpa(Film film) {
+ if (film.getMpa() == null || film.getMpa().getName() == null) {
+ mpaStorage.findMpaById(film.getMpa().getId())
+ .ifPresent(film::setMpa);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/FriendshipDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/FriendshipDbStorage.java
new file mode 100644
index 0000000..4cb5c01
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/dal/FriendshipDbStorage.java
@@ -0,0 +1,66 @@
+package ru.yandex.practicum.filmorate.dal;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Repository;
+import ru.yandex.practicum.filmorate.model.*;
+import ru.yandex.practicum.filmorate.storage.FriendshipStorage;
+
+import java.util.Collection;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Repository
+public class FriendshipDbStorage extends BaseDbStorage implements FriendshipStorage {
+
+ private final UserDbStorage userStorage;
+
+ private static final String INSERT_QUERY = "INSERT INTO friendship(user_id, friend_id) VALUES (?, ?)";
+ private static final String DELETE_QUERY = "DELETE FROM friendship WHERE user_id = ? AND friend_id = ?";
+ private static final String FIND_FRIENDS_QUERY =
+ "SELECT f.user_id, f.friend_id FROM friendship f WHERE f.user_id = ?";
+ private static final String FIND_COMMON_FRIENDS_QUERY =
+ "SELECT f1.user_id, f1.friend_id " +
+ "FROM friendship f1 " +
+ "JOIN friendship f2 ON f1.friend_id = f2.friend_id " +
+ "WHERE f1.user_id = ? AND f2.user_id = ?";
+
+ public FriendshipDbStorage(JdbcTemplate jdbc, RowMapper mapper, UserDbStorage userStorage) {
+ super(jdbc, mapper);
+ this.userStorage = userStorage;
+ }
+
+ // Добавление друга
+ @Override
+ public void addFriend(Long userId, Long friendId) {
+ update(INSERT_QUERY, userId, friendId);
+ }
+
+ // Удаление друга
+ @Override
+ public void removeFriend(Long userId, Long friendId) {
+ update(DELETE_QUERY, userId, friendId);
+ }
+
+ // Получение всех друзей пользователя
+ @Override
+ public Collection findAllFriends(Long userId) {
+ return findMany(FIND_FRIENDS_QUERY, userId).stream()
+ .map(friendship -> userStorage.findById(friendship.getFriendId()))
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .collect(Collectors.toList());
+ }
+
+ // Получение общих друзей
+ @Override
+ public Collection findCommonFriends(Long userId, Long otherId) {
+ return findMany(FIND_COMMON_FRIENDS_QUERY, userId, otherId).stream()
+ .map(friendship -> userStorage.findById(friendship.getFriendId()))
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .collect(Collectors.toList());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/GenreDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/GenreDbStorage.java
new file mode 100644
index 0000000..e7aac81
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/dal/GenreDbStorage.java
@@ -0,0 +1,64 @@
+package ru.yandex.practicum.filmorate.dal;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Repository;
+import ru.yandex.practicum.filmorate.model.Genre;
+import ru.yandex.practicum.filmorate.storage.GenreStorage;
+
+import java.util.Collection;
+import java.util.Optional;
+
+@Slf4j
+@Repository
+public class GenreDbStorage extends BaseDbStorage implements GenreStorage {
+
+ private static final String FIND_ALL_GENRES_QUERY = "SELECT genre_id, name FROM genre";
+ private static final String FIND_BY_ID_QUERY = "SELECT genre_id, name FROM genre WHERE genre_id = ?";
+ private static final String FIND_GENRE_FOR_FILM_QUERY = "SELECT g.genre_id, g.name FROM genre As g " +
+ "INNER JOIN film_genres AS fg ON g.genre_id = fg.genre_id " +
+ "WHERE fg.film_id = ?";
+ private static final String INSERT_GENRES_FOR_FILM_QUERY = "INSERT INTO film_genres (film_id, genre_id) " +
+ "VALUES(?, ?)";
+ private static final String DELETE_ALL_GENRES_FOR_FILM_QUERY = "DELETE FROM film_genres WHERE film_id = ?";
+
+ public GenreDbStorage(JdbcTemplate jdbc, RowMapper mapper) {
+ super(jdbc, mapper);
+ }
+
+ // Получение всех жанров
+ @Override
+ public Collection findAllGenres() {
+ return findMany(FIND_ALL_GENRES_QUERY);
+ }
+
+ // Получение жанра по id
+ @Override
+ public Optional findById(Long genreId) {
+ return findOne(FIND_BY_ID_QUERY, genreId);
+ }
+
+ // Получение жанров для указанного фильма
+ @Override
+ public Collection findGenreForFilm(Long filmId) {
+ return findMany(FIND_GENRE_FOR_FILM_QUERY, filmId);
+ }
+
+ // Добавление жанра для фильма
+ @Override
+ public void setGenreForFilm(Long filmId, Collection genres) {
+ if (genres == null) {
+ return;
+ }
+
+ // Удаление всех текущих жанров
+ jdbc.update(DELETE_ALL_GENRES_FOR_FILM_QUERY, filmId);
+
+ // Добавление новых жанров
+ for (Genre genre : genres) {
+ jdbc.update(INSERT_GENRES_FOR_FILM_QUERY,
+ filmId, genre.getId());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/LikesDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/LikesDbStorage.java
new file mode 100644
index 0000000..98fd65b
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/dal/LikesDbStorage.java
@@ -0,0 +1,41 @@
+package ru.yandex.practicum.filmorate.dal;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Repository;
+import ru.yandex.practicum.filmorate.model.Likes;
+import ru.yandex.practicum.filmorate.storage.LikesStorage;
+
+import java.util.Collection;
+
+@Slf4j
+@Repository
+public class LikesDbStorage extends BaseDbStorage implements LikesStorage {
+
+ private static final String INSERT_QUERY = "INSERT INTO likes (film_id, user_id) VALUES (?, ?)";
+ private static final String DELETE_QUERY = "DELETE FROM likes WHERE film_id = ? AND user_id = ?";
+ private static final String FIND_LIKES_ON_FILM_QUERY = "SELECT film_id, user_id FROM likes WHERE film_id = ?";
+
+ public LikesDbStorage(JdbcTemplate jdbc, RowMapper mapper) {
+ super(jdbc, mapper);
+ }
+
+ // Добавление лайка фильму
+ @Override
+ public void addLikeToFilm(Long filmId, Long userId) {
+ update(INSERT_QUERY, filmId, userId);
+ }
+
+ // Удаление лайка у фильма
+ @Override
+ public void removeLikeFromFilm(Long filmId, Long userId) {
+ update(DELETE_QUERY, filmId, userId);
+ }
+
+ // Получение всех лайков фильма
+ @Override
+ public Collection getLikesOnFilm(Long filmId) {
+ return findMany(FIND_LIKES_ON_FILM_QUERY, filmId);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/MpaDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/MpaDbStorage.java
new file mode 100644
index 0000000..0fd1046
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/dal/MpaDbStorage.java
@@ -0,0 +1,44 @@
+package ru.yandex.practicum.filmorate.dal;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Repository;
+import ru.yandex.practicum.filmorate.model.Mpa;
+import ru.yandex.practicum.filmorate.storage.MpaStorage;
+
+import java.util.Collection;
+import java.util.Optional;
+
+@Slf4j
+@Repository
+public class MpaDbStorage extends BaseDbStorage implements MpaStorage {
+
+ private static final String FIND_ALL_MPA_QUERY = "SELECT rating_id, name FROM mpa_rating";
+ private static final String FIND_BY_ID_QUERY = "SELECT rating_id, name FROM mpa_rating WHERE rating_id = ?";
+ private static final String FIND_MPA_FILM_QUERY = "SELECT m.rating_id, m.name FROM films AS f " +
+ "INNER JOIN mpa_rating AS m ON f.rating_id = m.rating_id " +
+ "WHERE f.film_id = ?";
+
+ public MpaDbStorage(JdbcTemplate jdbc, RowMapper mapper) {
+ super(jdbc, mapper);
+ }
+
+ // Получение всех рейтингов
+ @Override
+ public Collection findAll() {
+ return findMany(FIND_ALL_MPA_QUERY);
+ }
+
+ // Получение рейтинга по его id
+ @Override
+ public Optional findMpaById(Long ratingId) {
+ return findOne(FIND_BY_ID_QUERY, ratingId);
+ }
+
+ // Получение рейтинга для фильма
+ @Override
+ public Optional findMpaFilm(Long filmId) {
+ return findOne(FIND_MPA_FILM_QUERY, filmId);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/UserDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/UserDbStorage.java
new file mode 100644
index 0000000..16e3e78
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/dal/UserDbStorage.java
@@ -0,0 +1,101 @@
+package ru.yandex.practicum.filmorate.dal;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Repository;
+import ru.yandex.practicum.filmorate.model.User;
+import ru.yandex.practicum.filmorate.storage.UserStorage;
+
+import java.util.Collection;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Repository
+public class UserDbStorage extends BaseDbStorage implements UserStorage {
+
+ private static final String INSERT_QUERY = "INSERT INTO users(email, login, name, birthday) " +
+ "VALUES(?, ?, ?, ?)";
+ private static final String DELETE_QUERY = "DELETE FROM users WHERE user_id = ?";
+ private static final String UPDATE_QUERY = "UPDATE users SET email = ?, login = ?, name = ?, birthday = ? " +
+ "WHERE user_id = ?";
+ private static final String FIND_BY_ID_QUERY = "SELECT user_id, email, login, name, birthday " +
+ "FROM users " +
+ "WHERE user_id = ?";
+ private static final String FIND_ALL_USERS_QUERY = "SELECT user_id, email, login, name, birthday FROM users";
+ private static final String FIND_FRIENDS_QUERY = "SELECT user_id, email, login, name, birthday FROM users " +
+ "WHERE user_id IN(SELECT friend_id FROM friendship WHERE user_id = ?)";
+
+ public UserDbStorage(JdbcTemplate jdbc, RowMapper mapper) {
+ super(jdbc, mapper);
+ }
+
+ // Добавление пользователя
+ @Override
+ public User create(User user) {
+ long id = insert(
+ INSERT_QUERY,
+ user.getEmail(),
+ user.getLogin(),
+ user.getName(),
+ user.getBirthday()
+ );
+ user.setId(id);
+ return user;
+ }
+
+ // Удаление пользователя
+ @Override
+ public void delete(Long id) {
+ delete(DELETE_QUERY, id);
+ }
+
+ // Изменение пользователя
+ @Override
+ public User update(User user) {
+ update(
+ UPDATE_QUERY,
+ user.getEmail(),
+ user.getLogin(),
+ user.getName(),
+ user.getBirthday(),
+ user.getId()
+ );
+ return user;
+ }
+
+ // Получение пользователя по id
+ @Override
+ public Optional findById(Long id) {
+ Optional user = findOne(FIND_BY_ID_QUERY, id);
+ user.ifPresent(value -> value.setFriends(
+ findMany(FIND_FRIENDS_QUERY, value.getId())
+ .stream()
+ .map(User::getId)
+ .collect(Collectors.toSet())
+ ));
+ return user;
+ }
+
+ // Получение всех пользователей
+ @Override
+ public Collection findAll() {
+ Collection users = findMany(FIND_ALL_USERS_QUERY);
+
+ if (users.isEmpty()) {
+ return users;
+ }
+
+ users.forEach(user -> {
+ user.setFriends(
+ findMany(FIND_FRIENDS_QUERY, user.getId())
+ .stream()
+ .map(User::getId)
+ .collect(Collectors.toSet())
+ );
+ });
+
+ return users;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/mappers/FilmRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dal/mappers/FilmRowMapper.java
new file mode 100644
index 0000000..430d335
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/dal/mappers/FilmRowMapper.java
@@ -0,0 +1,35 @@
+package ru.yandex.practicum.filmorate.dal.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.Mpa;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+
+@Component
+public class FilmRowMapper implements RowMapper {
+
+ @Override
+ public Film mapRow(ResultSet rs, int rowNum) throws SQLException {
+ Film film = new Film();
+ film.setId(rs.getLong("film_id"));
+ film.setName(rs.getString("name"));
+ film.setDescription(rs.getString("description"));
+ film.setReleaseDate(rs.getDate("release_date").toLocalDate());
+ film.setDuration(rs.getInt("duration"));
+
+ Mpa mpa = new Mpa();
+ mpa.setId(rs.getLong("rating_id"));
+ mpa.setName(rs.getString("mpa_name"));
+ film.setMpa(mpa);
+
+ film.setLikesByUsers(new HashSet<>());
+ film.setGenres(new LinkedHashSet<>());
+
+ return film;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/mappers/FriendshipRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dal/mappers/FriendshipRowMapper.java
new file mode 100644
index 0000000..33cb4cc
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/dal/mappers/FriendshipRowMapper.java
@@ -0,0 +1,20 @@
+package ru.yandex.practicum.filmorate.dal.mappers;
+
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Component;
+import ru.yandex.practicum.filmorate.model.Friendship;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+@Component
+public class FriendshipRowMapper implements RowMapper {
+
+ @Override
+ public Friendship mapRow(ResultSet rs, int rowNum) throws SQLException {
+ Friendship friendship = new Friendship();
+ friendship.setUserId(rs.getLong("user_id"));
+ friendship.setFriendId(rs.getLong("friend_id"));
+ return friendship;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/mappers/GenreRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dal/mappers/GenreRowMapper.java
new file mode 100644
index 0000000..a321799
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/dal/mappers/GenreRowMapper.java
@@ -0,0 +1,20 @@
+package ru.yandex.practicum.filmorate.dal.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 GenreRowMapper implements RowMapper {
+
+ @Override
+ public Genre mapRow(ResultSet rs, int rowNum) throws SQLException {
+ Genre genre = new Genre();
+ genre.setId(rs.getLong("genre_id"));
+ genre.setName(rs.getString("name"));
+ return genre;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/mappers/LikesRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dal/mappers/LikesRowMapper.java
new file mode 100644
index 0000000..c5229d1
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/dal/mappers/LikesRowMapper.java
@@ -0,0 +1,20 @@
+package ru.yandex.practicum.filmorate.dal.mappers;
+
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Component;
+import ru.yandex.practicum.filmorate.model.Likes;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+@Component
+public class LikesRowMapper implements RowMapper {
+
+ @Override
+ public Likes mapRow(ResultSet rs, int rowNum) throws SQLException {
+ Likes likes = new Likes();
+ likes.setFilmId(rs.getLong("film_id"));
+ likes.setUserId(rs.getLong("user_id"));
+ return likes;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/mappers/MpaRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dal/mappers/MpaRowMapper.java
new file mode 100644
index 0000000..9ed8a00
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/dal/mappers/MpaRowMapper.java
@@ -0,0 +1,20 @@
+package ru.yandex.practicum.filmorate.dal.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 MpaRowMapper implements RowMapper {
+
+ @Override
+ public Mpa mapRow(ResultSet rs, int rowNum) throws SQLException {
+ Mpa mpa = new Mpa();
+ mpa.setId(rs.getLong("rating_id"));
+ mpa.setName(rs.getString("name"));
+ return mpa;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/mappers/UserRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dal/mappers/UserRowMapper.java
new file mode 100644
index 0000000..36e492c
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/dal/mappers/UserRowMapper.java
@@ -0,0 +1,27 @@
+package ru.yandex.practicum.filmorate.dal.mappers;
+
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Component;
+import ru.yandex.practicum.filmorate.model.User;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashSet;
+
+@Component
+public class UserRowMapper implements RowMapper {
+
+ @Override
+ public User mapRow(ResultSet rs, int rowNum) throws SQLException {
+ User user = new User();
+ user.setId(rs.getLong("user_id"));
+ user.setEmail(rs.getString("email"));
+ user.setLogin(rs.getString("login"));
+ user.setName(rs.getString("name"));
+ user.setBirthday(rs.getDate("birthday").toLocalDate());
+
+ user.setFriends(new HashSet<>());
+
+ return user;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/FilmDto.java b/src/main/java/ru/yandex/practicum/filmorate/dto/FilmDto.java
new file mode 100644
index 0000000..91a3298
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/dto/FilmDto.java
@@ -0,0 +1,24 @@
+package ru.yandex.practicum.filmorate.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import ru.yandex.practicum.filmorate.model.*;
+
+import java.time.LocalDate;
+import java.util.Set;
+
+@Data
+public class FilmDto {
+
+ @JsonProperty(access = JsonProperty.Access.READ_ONLY)
+ private Long id;
+ private String name;
+ private String description;
+ private LocalDate releaseDate;
+ private Integer duration;
+ private Mpa mpa;
+ @JsonProperty(access = JsonProperty.Access.READ_ONLY)
+ private Set likesByUsers;
+ @JsonProperty(access = JsonProperty.Access.READ_ONLY)
+ private Set genres;
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/NewFilmRequest.java b/src/main/java/ru/yandex/practicum/filmorate/dto/NewFilmRequest.java
new file mode 100644
index 0000000..fa23850
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/dto/NewFilmRequest.java
@@ -0,0 +1,21 @@
+package ru.yandex.practicum.filmorate.dto;
+
+import lombok.Data;
+import ru.yandex.practicum.filmorate.model.Genre;
+import ru.yandex.practicum.filmorate.model.Mpa;
+
+import java.time.LocalDate;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+@Data
+public class NewFilmRequest {
+
+ private Long id;
+ private String name;
+ private String description;
+ private LocalDate releaseDate;
+ private Integer duration;
+ private Mpa mpa;
+ private Set genres = new LinkedHashSet<>();
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/NewUserRequest.java b/src/main/java/ru/yandex/practicum/filmorate/dto/NewUserRequest.java
new file mode 100644
index 0000000..8d3d39e
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/dto/NewUserRequest.java
@@ -0,0 +1,18 @@
+package ru.yandex.practicum.filmorate.dto;
+
+import lombok.Data;
+
+import java.time.LocalDate;
+
+@Data
+public class NewUserRequest {
+
+ private String email;
+ private String login;
+ private String name;
+ private LocalDate birthday;
+
+ public String getName() {
+ return (name == null || name.isBlank()) ? login : name;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/UpdateFilmRequest.java b/src/main/java/ru/yandex/practicum/filmorate/dto/UpdateFilmRequest.java
new file mode 100644
index 0000000..2e977a7
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/dto/UpdateFilmRequest.java
@@ -0,0 +1,24 @@
+package ru.yandex.practicum.filmorate.dto;
+
+import lombok.*;
+import ru.yandex.practicum.filmorate.model.Genre;
+import ru.yandex.practicum.filmorate.model.Mpa;
+
+import java.time.LocalDate;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+@Data
+@Builder(toBuilder = true)
+@NoArgsConstructor
+@AllArgsConstructor
+public class UpdateFilmRequest {
+
+ private Long id;
+ private String name;
+ private String description;
+ private LocalDate releaseDate;
+ private Integer duration;
+ private Mpa mpa;
+ private Set genres = new LinkedHashSet<>();
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/UpdateUserRequest.java b/src/main/java/ru/yandex/practicum/filmorate/dto/UpdateUserRequest.java
new file mode 100644
index 0000000..682fba3
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/dto/UpdateUserRequest.java
@@ -0,0 +1,22 @@
+package ru.yandex.practicum.filmorate.dto;
+
+import lombok.*;
+
+import java.time.LocalDate;
+
+@Data
+@Builder(toBuilder = true)
+@NoArgsConstructor
+@AllArgsConstructor
+public class UpdateUserRequest {
+
+ private Long id;
+ private String email;
+ private String login;
+ private String name;
+ private LocalDate birthday;
+
+ public String getName() {
+ return (name == null || name.isBlank()) ? login : name;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/UserDto.java b/src/main/java/ru/yandex/practicum/filmorate/dto/UserDto.java
new file mode 100644
index 0000000..c44827f
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/dto/UserDto.java
@@ -0,0 +1,24 @@
+package ru.yandex.practicum.filmorate.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.time.LocalDate;
+import java.util.Set;
+
+@Data
+public class UserDto {
+
+ @JsonProperty(access = JsonProperty.Access.READ_ONLY)
+ private Long id;
+ private String email;
+ private String login;
+ private String name;
+ private LocalDate birthday;
+ @JsonProperty(access = JsonProperty.Access.READ_ONLY)
+ private Set friends;
+
+ public String getName() {
+ return (name == null || name.isBlank()) ? login : name;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/exception/InternalServerException.java b/src/main/java/ru/yandex/practicum/filmorate/exception/InternalServerException.java
new file mode 100644
index 0000000..8e1a88b
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/exception/InternalServerException.java
@@ -0,0 +1,8 @@
+package ru.yandex.practicum.filmorate.exception;
+
+public class InternalServerException extends RuntimeException {
+
+ public InternalServerException(String message) {
+ super(message);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/mapper/FilmMapper.java b/src/main/java/ru/yandex/practicum/filmorate/mapper/FilmMapper.java
new file mode 100644
index 0000000..1b18769
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/mapper/FilmMapper.java
@@ -0,0 +1,93 @@
+package ru.yandex.practicum.filmorate.mapper;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import ru.yandex.practicum.filmorate.dto.FilmDto;
+import ru.yandex.practicum.filmorate.dto.NewFilmRequest;
+import ru.yandex.practicum.filmorate.dto.UpdateFilmRequest;
+import ru.yandex.practicum.filmorate.model.Film;
+import ru.yandex.practicum.filmorate.model.Genre;
+
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.stream.Collectors;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class FilmMapper {
+
+ // Добавление фильма
+ public static Film mapToFilm(NewFilmRequest request) {
+ Film film = new Film();
+ film.setName(request.getName());
+ film.setDescription(request.getDescription());
+ film.setReleaseDate(request.getReleaseDate());
+ film.setDuration(request.getDuration());
+ film.setMpa(request.getMpa());
+ film.setGenres(request.getGenres().stream()
+ .sorted(Comparator.comparing(Genre::getId))
+ .collect(Collectors.toCollection(LinkedHashSet::new)));
+ film.setLikesByUsers(new HashSet<>());
+ return film;
+ }
+
+ // Изменение фильма
+ public static Film mapToFilm(UpdateFilmRequest request) {
+ Film film = new Film();
+ film.setId(request.getId());
+ film.setName(request.getName());
+ film.setDescription(request.getDescription());
+ film.setReleaseDate(request.getReleaseDate());
+ film.setDuration(request.getDuration());
+ film.setMpa(request.getMpa());
+ film.setGenres(request.getGenres().stream()
+ .sorted(Comparator.comparing(Genre::getId))
+ .collect(Collectors.toCollection(LinkedHashSet::new)));
+ film.setLikesByUsers(new HashSet<>());
+ return film;
+ }
+
+ // Преобразование в DTO
+ public static FilmDto mapToDto(Film film) {
+ FilmDto dto = new FilmDto();
+ dto.setId(film.getId());
+ dto.setName(film.getName());
+ dto.setDescription(film.getDescription());
+ dto.setReleaseDate(film.getReleaseDate());
+ dto.setDuration(film.getDuration());
+ dto.setMpa(film.getMpa());
+
+ dto.setGenres(film.getGenres() != null ?
+ new LinkedHashSet<>(film.getGenres()) :
+ new LinkedHashSet<>());
+
+ dto.setLikesByUsers(film.getLikesByUsers() != null ?
+ new HashSet<>(film.getLikesByUsers()) :
+ new HashSet<>());
+
+ return dto;
+ }
+
+ // Частичное изменение фильма
+ public static Film updateFilmFields(Film film, UpdateFilmRequest request) {
+ if (request.getName() != null) {
+ film.setName(request.getName());
+ }
+ if (request.getDescription() != null) {
+ film.setDescription(request.getDescription());
+ }
+ if (request.getReleaseDate() != null) {
+ film.setReleaseDate(request.getReleaseDate());
+ }
+ if (request.getDuration() != null) {
+ film.setDuration(request.getDuration());
+ }
+ if (request.getMpa() != null) {
+ film.setMpa(request.getMpa());
+ }
+ if (request.getGenres() != null) {
+ film.setGenres(new LinkedHashSet<>(request.getGenres()));
+ }
+ return film;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/mapper/UserMapper.java b/src/main/java/ru/yandex/practicum/filmorate/mapper/UserMapper.java
new file mode 100644
index 0000000..f66adfe
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/mapper/UserMapper.java
@@ -0,0 +1,67 @@
+package ru.yandex.practicum.filmorate.mapper;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import ru.yandex.practicum.filmorate.dto.NewUserRequest;
+import ru.yandex.practicum.filmorate.dto.UpdateUserRequest;
+import ru.yandex.practicum.filmorate.dto.UserDto;
+import ru.yandex.practicum.filmorate.model.User;
+
+import java.util.HashSet;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class UserMapper {
+
+ // Добавление пользователя
+ public static User mapToUser(NewUserRequest request) {
+ User user = new User();
+ user.setEmail(request.getEmail());
+ user.setLogin(request.getLogin());
+ user.setName(request.getName());
+ user.setBirthday(request.getBirthday());
+ user.setFriends(new HashSet<>());
+ return user;
+ }
+
+ // Изменение пользователя
+ public static User mapToUser(UpdateUserRequest request) {
+ User user = new User();
+ user.setId(request.getId());
+ user.setEmail(request.getEmail());
+ user.setLogin(request.getLogin());
+ user.setBirthday(request.getBirthday());
+ user.setFriends(new HashSet<>());
+ return user;
+ }
+
+ // Преобразование в DTO
+ public static UserDto mapToDto(User user) {
+ UserDto dto = new UserDto();
+ dto.setId(user.getId());
+ dto.setEmail(user.getEmail());
+ dto.setLogin(user.getLogin());
+ dto.setName(user.getName());
+ dto.setBirthday(user.getBirthday());
+ dto.setFriends(user.getFriends() != null ?
+ new HashSet<>(user.getFriends()) :
+ new HashSet<>());
+ return dto;
+ }
+
+ // Частичное изменение пользователя
+ public static User updateUserFields(User user, UpdateUserRequest request) {
+ if (request.getEmail() != null) {
+ user.setEmail(request.getEmail());
+ }
+ if (request.getLogin() != null) {
+ user.setLogin(request.getLogin());
+ }
+ if (request.getName() != null) {
+ user.setName(request.getName());
+ }
+ if (request.getBirthday() != null) {
+ user.setBirthday(request.getBirthday());
+ }
+ return user;
+ }
+}
\ No newline at end of file
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 b2514ef..16c9d84 100644
--- a/src/main/java/ru/yandex/practicum/filmorate/model/Film.java
+++ b/src/main/java/ru/yandex/practicum/filmorate/model/Film.java
@@ -14,13 +14,11 @@
public class Film {
private Long id;
- private Set likesByUsers;
-
private String name;
-
private String description;
-
private LocalDate releaseDate;
-
private Integer duration;
+ private Mpa mpa;
+ private Set likesByUsers;
+ private Set genres;
}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Friendship.java b/src/main/java/ru/yandex/practicum/filmorate/model/Friendship.java
new file mode 100644
index 0000000..8376838
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/model/Friendship.java
@@ -0,0 +1,10 @@
+package ru.yandex.practicum.filmorate.model;
+
+import lombok.Data;
+
+@Data
+public class Friendship {
+
+ private Long userId;
+ private Long friendId;
+}
\ No newline at end of file
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..f9aa66e
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java
@@ -0,0 +1,12 @@
+package ru.yandex.practicum.filmorate.model;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(of = {"id"})
+public class Genre {
+
+ private Long id;
+ private String name;
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Likes.java b/src/main/java/ru/yandex/practicum/filmorate/model/Likes.java
new file mode 100644
index 0000000..6b89aca
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/model/Likes.java
@@ -0,0 +1,9 @@
+package ru.yandex.practicum.filmorate.model;
+
+import lombok.Data;
+
+@Data
+public class Likes {
+ private Long filmId;
+ private Long userId;
+}
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..d106e01
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/model/Mpa.java
@@ -0,0 +1,12 @@
+package ru.yandex.practicum.filmorate.model;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(of = {"id"})
+public class Mpa {
+
+ private Long id;
+ private String name;
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/User.java b/src/main/java/ru/yandex/practicum/filmorate/model/User.java
index b51ef9e..1625a49 100644
--- a/src/main/java/ru/yandex/practicum/filmorate/model/User.java
+++ b/src/main/java/ru/yandex/practicum/filmorate/model/User.java
@@ -14,15 +14,11 @@
public class User {
private Long id;
- private Set friends;
-
private String email;
-
private String login;
-
private String name;
-
private LocalDate birthday;
+ private Set friends;
public String getName() {
return (name == null || name.isBlank()) ? login : name;
diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java
index f01a054..c4eca69 100644
--- a/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java
+++ b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java
@@ -3,10 +3,13 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
+import ru.yandex.practicum.filmorate.dto.FilmDto;
+import ru.yandex.practicum.filmorate.dto.NewFilmRequest;
+import ru.yandex.practicum.filmorate.dto.UpdateFilmRequest;
import ru.yandex.practicum.filmorate.exception.*;
+import ru.yandex.practicum.filmorate.mapper.FilmMapper;
import ru.yandex.practicum.filmorate.model.*;
-import ru.yandex.practicum.filmorate.storage.film.FilmStorage;
-import ru.yandex.practicum.filmorate.storage.user.UserStorage;
+import ru.yandex.practicum.filmorate.storage.*;
import ru.yandex.practicum.filmorate.utils.FilmValidate;
import java.util.*;
@@ -19,68 +22,130 @@ public class FilmService {
private final FilmStorage filmStorage;
private final UserStorage userStorage;
private final FilmValidate filmValidate;
+ private final GenreStorage genreStorage;
+ private final MpaStorage mpaStorage;
+ private final LikesStorage likesStorage;
+
+ // Получение всех фильмов
+ public Collection findAll() {
+ return filmStorage.findAll().stream()
+ .map(FilmMapper::mapToDto)
+ .toList();
+ }
- public Collection findAll() {
- return filmStorage.findAll();
+ // Получение фильма по id
+ public FilmDto findById(Long filmId) {
+ return filmStorage.findById(filmId)
+ .map(FilmMapper::mapToDto)
+ .orElseThrow(() -> new NotFoundException("Фильм не найден"));
}
- public Film create(Film film) {
+ // Добавление фильма
+ public FilmDto create(NewFilmRequest request) {
+ Film film = FilmMapper.mapToFilm(request);
filmValidate.validateFilm(film);
- return filmStorage.create(film);
+
+ if (film.getGenres() != null) {
+ film.getGenres().forEach(genre -> {
+ genreStorage.findById(genre.getId())
+ .orElseThrow(() -> {
+ log.warn("Жанр с id:{} не найден", genre.getId());
+ return new NotFoundException("Жанр не найден");
+ });
+ });
+ }
+
+ Long mpaId = film.getMpa().getId();
+ if (mpaId != null) {
+ mpaStorage.findMpaById(mpaId)
+ .orElseThrow(() -> {
+ log.warn("Рейтинг с id:{} не найден", mpaId);
+ return new NotFoundException("Рейтинг MPA не найден");
+ });
+ }
+
+ film = filmStorage.create(film);
+ return FilmMapper.mapToDto(film);
}
- public Film update(Film film) {
+ // Обновление фильма
+ public FilmDto update(UpdateFilmRequest request) {
+ Film film = FilmMapper.mapToFilm(request);
filmValidate.validateFilm(film);
- return filmStorage.update(film);
- }
- public void delete(Long id) {
- filmStorage.delete(id);
+ Film oldFilm = filmStorage.findById(request.getId())
+ .orElseThrow(() -> {
+ log.warn("Фильм с id:{} не найден", request.getId());
+ return new NotFoundException("Фильм не найден");
+ });
+
+ Long mpaId = film.getMpa().getId();
+ if (mpaId != null) {
+ mpaStorage.findMpaById(mpaId)
+ .orElseThrow(() -> {
+ log.warn("Рейтинг с id:{} не найден", mpaId);
+ return new NotFoundException("Рейтинг MPA не найден");
+ });
+ }
+
+ if (film.getGenres() != null) {
+ film.getGenres().forEach(genre -> {
+ genreStorage.findById(genre.getId())
+ .orElseThrow(() -> {
+ log.warn("Жанр с id:{} не найден", genre.getId());
+ return new NotFoundException("Жанр не найден");
+ });
+ });
+ }
+
+ film = FilmMapper.updateFilmFields(oldFilm, request);
+ film.setLikesByUsers(oldFilm.getLikesByUsers());
+ return FilmMapper.mapToDto(filmStorage.update(film));
}
- public Film getById(Long id) {
- return filmStorage.getById(id);
+ // Удаление фильма по id
+ public void delete(Long filmId) {
+ filmStorage.delete(filmId);
}
// Метод добавления лайка фильму(по id) от пользователя c указанным id
public void addLike(Long filmId, Long userId) {
- final Film film = filmStorage.getById(filmId);
- if (film == null) {
- log.warn("Попытка лайкнуть несуществующий фильм Id:{}", filmId);
- throw new NotFoundException("Фильм не найден");
- }
-
- final User user = userStorage.getById(userId);
- if (user == null) {
- log.warn("Попытка лайка от несуществующего пользователя Id:{}", userId);
- throw new NotFoundException("Пользователь не найден");
- }
+ final Film film = filmStorage.findById(filmId)
+ .orElseThrow(() -> {
+ log.warn("Попытка лайкнуть несуществующий фильм Id:{}", filmId);
+ throw new NotFoundException("Фильм не найден");
+ });
+
+ final User user = userStorage.findById(userId)
+ .orElseThrow(() -> {
+ log.warn("Попытка лайка от несуществующего пользователя Id:{}", userId);
+ throw new NotFoundException("Пользователь не найден");
+ });
Set likes = film.getLikesByUsers();
if (likes.contains(userId)) {
log.debug("Пользователь с Id:{} уже ставил лайк фильму с Id:{}", userId, filmId);
return;
}
-
likes.add(userId);
film.setLikesByUsers(likes);
- filmStorage.update(film);
+ likesStorage.addLikeToFilm(filmId, userId);
log.debug("Пользователь с Id:{} поставил лайк фильму c Id:{}", userId, filmId);
}
// Метод удаления лайка у фильма(по id) от пользователя с указанным id
public void removeLike(Long filmId, Long userId) {
- final Film film = filmStorage.getById(filmId);
- if (film == null) {
- log.warn("Попытка удалить лайк у несуществующего фильма с Id:{}", filmId);
- throw new NotFoundException("Фильм не найден");
- }
-
- final User user = userStorage.getById(userId);
- if (user == null) {
- log.warn("Попытка удалить лайк от несуществующего пользователя c Id:{}", userId);
- throw new NotFoundException("Пользователь не найден");
- }
+ final Film film = filmStorage.findById(filmId)
+ .orElseThrow(() -> {
+ log.warn("Попытка удалить лайк у несуществующего фильма с Id:{}", filmId);
+ throw new NotFoundException("Фильм не найден");
+ });
+
+ final User user = userStorage.findById(userId)
+ .orElseThrow(() -> {
+ log.warn("Попытка удалить лайк от несуществующего пользователя c Id:{}", userId);
+ throw new NotFoundException("Пользователь не найден");
+ });
Set likes = film.getLikesByUsers();
if (!likes.contains(userId)) {
@@ -89,20 +154,17 @@ public void removeLike(Long filmId, Long userId) {
}
likes.remove(userId);
film.setLikesByUsers(likes);
- filmStorage.update(film);
+ likesStorage.removeLikeFromFilm(filmId, userId);
log.debug("Пользователь с Id:{} удалил лайк фильму c Id:{}", userId, filmId);
}
- // Метод вывода 10 популярных фильмов по количеству лайков
- public Collection getPopularFilms(int count) {
- if (count <= 0) {
- throw new ValidationException("Количество фильмов должно быть положительным");
- }
-
+ // Метод получения популярных фильмов по количеству лайков
+ public Collection findPopularFilms(int count) {
Collection films = filmStorage.findAll();
return films.stream()
.sorted((f1, f2) -> f2.getLikesByUsers().size() - f1.getLikesByUsers().size())
.limit(count)
+ .map(FilmMapper::mapToDto)
.toList();
}
}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/GenreService.java b/src/main/java/ru/yandex/practicum/filmorate/service/GenreService.java
new file mode 100644
index 0000000..ceb291b
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/service/GenreService.java
@@ -0,0 +1,30 @@
+package ru.yandex.practicum.filmorate.service;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+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.GenreStorage;
+
+import java.util.Collection;
+
+@Slf4j
+@Service
+public class GenreService {
+
+ @Autowired
+ private GenreStorage genreStorage;
+
+ public Collection findAll() {
+ return genreStorage.findAllGenres();
+ }
+
+ public Genre findById(Long genreId) {
+ return genreStorage.findById(genreId)
+ .orElseThrow(() -> {
+ log.warn("Жанр с id:{} не найден", genreId);
+ return new NotFoundException("Жанр не найден");
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/MpaService.java b/src/main/java/ru/yandex/practicum/filmorate/service/MpaService.java
new file mode 100644
index 0000000..a599f88
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/service/MpaService.java
@@ -0,0 +1,30 @@
+package ru.yandex.practicum.filmorate.service;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+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.MpaStorage;
+
+import java.util.Collection;
+
+@Slf4j
+@Service
+public class MpaService {
+
+ @Autowired
+ private MpaStorage mpaStorage;
+
+ public Collection findAll() {
+ return mpaStorage.findAll();
+ }
+
+ public Mpa findById(Long ratingId) {
+ return mpaStorage.findMpaById(ratingId)
+ .orElseThrow(() -> {
+ log.warn("Рейтинг с id:{} не найден", ratingId);
+ return new NotFoundException("Рейтинг MPA не найден");
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java
index c2e60f6..7346bed 100644
--- a/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java
+++ b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java
@@ -3,13 +3,17 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
+import ru.yandex.practicum.filmorate.dto.NewUserRequest;
+import ru.yandex.practicum.filmorate.dto.UpdateUserRequest;
+import ru.yandex.practicum.filmorate.dto.UserDto;
import ru.yandex.practicum.filmorate.exception.*;
+import ru.yandex.practicum.filmorate.mapper.UserMapper;
import ru.yandex.practicum.filmorate.model.User;
-import ru.yandex.practicum.filmorate.storage.user.UserStorage;
+import ru.yandex.practicum.filmorate.storage.FriendshipStorage;
+import ru.yandex.practicum.filmorate.storage.UserStorage;
import ru.yandex.practicum.filmorate.utils.UserValidate;
import java.util.*;
-import java.util.stream.Collectors;
@Slf4j
@Service
@@ -18,134 +22,144 @@ public class UserService {
private final UserStorage userStorage;
private final UserValidate userValidate;
+ private final FriendshipStorage friendshipStorage;
- public Collection findAll() {
- return userStorage.findAll();
+ // Получение всех пользователей
+ public Collection findAll() {
+ return userStorage.findAll().stream()
+ .map(UserMapper::mapToDto)
+ .toList();
}
- public User create(User user) {
- userValidate.validateUser(user);
- return userStorage.create(user);
+ // Получение пользователя по id
+ public UserDto getById(Long userId) {
+ return userStorage.findById(userId)
+ .map(UserMapper::mapToDto)
+ .orElseThrow(() -> new NotFoundException("Пользователь не найден"));
}
- public User update(User user) {
+ // Добавление пользователя
+ public UserDto create(NewUserRequest request) {
+ User user = UserMapper.mapToUser(request);
userValidate.validateUser(user);
- return userStorage.update(user);
+
+ User createdUser = userStorage.create(user);
+ return UserMapper.mapToDto(createdUser);
}
- public void delete(Long id) {
- userStorage.delete(id);
+ // Обновление пользователя
+ public UserDto update(UpdateUserRequest request) {
+ User user = UserMapper.mapToUser(request);
+ userValidate.validateUser(user);
+
+ User oldUser = userStorage.findById(request.getId())
+ .orElseThrow(() -> {
+ log.warn("Пользователь с id:{} не найден", request.getId());
+ return new NotFoundException("Пользователь не найден");
+ });
+ user = UserMapper.updateUserFields(oldUser, request);
+ return UserMapper.mapToDto(userStorage.update(user));
}
- public User getById(Long id) {
- return userStorage.getById(id);
+ // Удаление пользователя
+ public void delete(Long userId) {
+ userStorage.delete(userId);
}
// Метод добавления пользователей в друзья
public void addFriend(Long userId, Long friendId) {
- final User user = userStorage.getById(userId);
- if (user == null) {
- log.warn("Попытка несуществующего пользователя с Id:{} добавить в друзья пользователей", userId);
- throw new NotFoundException("Пользователь не найден");
- }
-
- final User friend = userStorage.getById(friendId);
- if (friend == null) {
- log.warn("Попытка добавить в друзья несуществующего пользователя с Id:{}", friendId);
- throw new NotFoundException("Пользователь не найден");
- }
+ final User user = userStorage.findById(userId)
+ .orElseThrow(() -> {
+ log.warn("Попытка несуществующего пользователя с Id:{} добавить в кого-то в друзья", userId);
+ throw new NotFoundException("Пользователь не найден");
+ });
+
+ final User friend = userStorage.findById(friendId)
+ .orElseThrow(() -> {
+ log.warn("Попытка добавить в друзья несуществующего пользователя с Id:{}", friendId);
+ throw new NotFoundException("Пользователь не найден");
+ });
if (user.equals(friend)) {
log.warn("Попытка добавить самого себя в друзья");
throw new DuplicateException("Нельзя добавить самого себя в друзья");
}
- Set userFriends = user.getFriends();
- if (userFriends.contains(friendId)) {
- log.debug("Пользователь с Id:{} уже добавил в друзья пользователя с Id:{}", userId, friendId);
- return;
+ if (user.getFriends().contains(friendId)) {
+ log.warn("Попытка пользователя с Id:{} снова добавить в друзья пользователя с Id:{}", userId, friendId);
+ throw new DuplicateException("Пользователь уже отправлял запрос на дружбу");
}
-
- userFriends.add(friendId);
- user.setFriends(userFriends);
+ user.getFriends().add(friendId);
userStorage.update(user);
- Set friendFriends = friend.getFriends();
- friendFriends.add(userId);
- friend.setFriends(friendFriends);
- userStorage.update(friend);
+ friendshipStorage.addFriend(userId, friendId);
log.debug("Пользователи с Id:{} и Id:{} теперь друзья", userId, friendId);
}
// Метод удаления пользователей из друзей
public void removeFriend(Long userId, Long friendId) {
- final User user = userStorage.getById(userId);
- if (user == null) {
- log.warn("Попытка несуществующего пользователя с Id:{} удалить кого-то из друзей", userId);
- throw new NotFoundException("Пользователь не найден");
- }
-
- final User friend = userStorage.getById(friendId);
- if (friend == null) {
- log.warn("Попытка удалить из друзей несуществующего пользователя с Id:{}", friendId);
- throw new NotFoundException("Пользователь не найден");
- }
+ final User user = userStorage.findById(userId)
+ .orElseThrow(() -> {
+ log.warn("Попытка несуществующего пользователя с Id:{} удалить кого-то из друзей", userId);
+ throw new NotFoundException("Пользователь не найден");
+ });
+
+ final User friend = userStorage.findById(friendId)
+ .orElseThrow(() -> {
+ log.warn("Попытка удалить из друзей несуществующего пользователя с Id:{}", friendId);
+ throw new NotFoundException("Пользователь не найден");
+ });
if (user.equals(friend)) {
log.warn("При удалении из друзей были переданы одинаковые Id: userId={}, friendId={}", userId, friendId);
throw new DuplicateException("Нельзя удалить самого себя из друзей");
}
- Set userFriends = user.getFriends();
- if (!userFriends.contains(friendId)) {
- log.debug("Пользователь с Id:{} не добавлял в друзья пользователя с Id:{}", userId, friendId);
+ if (!user.getFriends().contains(friendId)) {
+ log.warn("Пользователь с Id:{} не добавлял в друзья пользователя с Id:{}", userId, friendId);
return;
}
- userFriends.remove(friendId);
- user.setFriends(userFriends);
+ user.getFriends().remove(friendId);
userStorage.update(user);
- Set friendFriends = friend.getFriends();
- friendFriends.remove(userId);
- friend.setFriends(friendFriends);
- userStorage.update(friend);
+ friendshipStorage.removeFriend(userId, friendId);
log.debug("Пользователи с Id:{} и Id:{} больше не друзья", userId, friendId);
}
// Метод получения списка всех друзей пользователя
- public Collection getFriends(Long id) {
- final User user = userStorage.getById(id);
-
- if (user == null) {
- log.warn("Попытка получить друзей несуществующего пользователя c Id:{}", id);
- throw new NotFoundException("Пользователь не найден");
- }
+ public Collection findAllFriends(Long userId) {
+ final User user = userStorage.findById(userId)
+ .orElseThrow(() -> {
+ log.warn("Попытка получить друзей несуществующего пользователя c Id:{}", userId);
+ throw new NotFoundException("Пользователь не найден");
+ });
// Возвращаем пустую коллекцию, если у пользователя нет друзей
- if (user.getFriends().isEmpty()) {
- log.debug("У пользователя с Id:{} нет друзей", id);
+ if (user.getFriends() == null || user.getFriends().isEmpty()) {
+ log.debug("У пользователя с Id:{} нет друзей", userId);
return Collections.emptyList();
}
- return user.getFriends().stream()
- .map(userStorage::getById)
- .collect(Collectors.toList());
+ return userStorage.findAll().stream()
+ .filter(u -> user.getFriends().contains(u.getId()))
+ .map(UserMapper::mapToDto)
+ .toList();
}
// Метод получения общих друзей
- public Collection getCommonFriends(Long userId, Long friendId) {
- final User user = getById(userId);
- if (user == null) {
- log.warn("Попытка получить общих друзей у несуществующего пользователя c Id:{}", userId);
- throw new NotFoundException("Пользователь не найден");
- }
-
- final User friend = getById(friendId);
- if (friend == null) {
- log.warn("Попытка получить общих друзей с несуществующим пользователем с Id:{}",friendId);
- throw new NotFoundException("Пользователь не найден");
- }
+ public Collection findCommonFriends(Long userId, Long friendId) {
+ final User user = userStorage.findById(userId)
+ .orElseThrow(() -> {
+ log.warn("Попытка получить общих друзей у несуществующего пользователя c Id:{}", userId);
+ throw new NotFoundException("Пользователь не найден");
+ });
+
+ final User friend = userStorage.findById(friendId)
+ .orElseThrow(() -> {
+ log.warn("Попытка получить общих друзей с несуществующим пользователем с Id:{}",friendId);
+ throw new NotFoundException("Пользователь не найден");
+ });
// Поиск пересечения друзей
Set intersection = new HashSet<>(user.getFriends());
@@ -157,6 +171,7 @@ public Collection getCommonFriends(Long userId, Long friendId) {
return userStorage.findAll().stream()
.filter(u -> intersection.contains(u.getId()))
+ .map(UserMapper::mapToDto)
.toList();
}
}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java
similarity index 51%
rename from src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmStorage.java
rename to src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java
index 51f315e..e5350ef 100644
--- a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmStorage.java
+++ b/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java
@@ -1,18 +1,21 @@
-package ru.yandex.practicum.filmorate.storage.film;
+package ru.yandex.practicum.filmorate.storage;
import ru.yandex.practicum.filmorate.model.Film;
import java.util.Collection;
+import java.util.Optional;
public interface FilmStorage {
Film create(Film film);
- void delete(Long id);
+ void delete(Long filmId);
Film update(Film film);
- Film getById(Long id);
+ Optional findById(Long filmId);
Collection findAll();
+
+ Collection findPopularFilms(int count);
}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/FriendshipStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/FriendshipStorage.java
new file mode 100644
index 0000000..7c30f84
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/storage/FriendshipStorage.java
@@ -0,0 +1,16 @@
+package ru.yandex.practicum.filmorate.storage;
+
+import ru.yandex.practicum.filmorate.model.User;
+
+import java.util.Collection;
+
+public interface FriendshipStorage {
+
+ void addFriend(Long userId, Long friendId);
+
+ void removeFriend(Long userId, Long friendId);
+
+ Collection findAllFriends(Long userId);
+
+ Collection findCommonFriends(Long userId, Long friendId);
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/GenreStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/GenreStorage.java
new file mode 100644
index 0000000..ef6faa9
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/storage/GenreStorage.java
@@ -0,0 +1,17 @@
+package ru.yandex.practicum.filmorate.storage;
+
+import ru.yandex.practicum.filmorate.model.Genre;
+
+import java.util.Collection;
+import java.util.Optional;
+
+public interface GenreStorage {
+
+ Collection findAllGenres();
+
+ Optional findById(Long genreId);
+
+ Collection findGenreForFilm(Long filmId);
+
+ void setGenreForFilm(Long filmId, Collection genres);
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/LikesStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/LikesStorage.java
new file mode 100644
index 0000000..23cfd82
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/storage/LikesStorage.java
@@ -0,0 +1,14 @@
+package ru.yandex.practicum.filmorate.storage;
+
+import ru.yandex.practicum.filmorate.model.Likes;
+
+import java.util.Collection;
+
+public interface LikesStorage {
+
+ void addLikeToFilm(Long filmId, Long userId);
+
+ void removeLikeFromFilm(Long filmId, Long userId);
+
+ Collection getLikesOnFilm(Long filmId);
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/MpaStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/MpaStorage.java
new file mode 100644
index 0000000..21efe32
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/storage/MpaStorage.java
@@ -0,0 +1,15 @@
+package ru.yandex.practicum.filmorate.storage;
+
+import ru.yandex.practicum.filmorate.model.Mpa;
+
+import java.util.Collection;
+import java.util.Optional;
+
+public interface MpaStorage {
+
+ Collection findAll();
+
+ Optional findMpaById(Long ratingId);
+
+ Optional findMpaFilm(Long filmId);
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java
similarity index 58%
rename from src/main/java/ru/yandex/practicum/filmorate/storage/user/UserStorage.java
rename to src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java
index 95a43e2..74b5430 100644
--- a/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserStorage.java
+++ b/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java
@@ -1,18 +1,19 @@
-package ru.yandex.practicum.filmorate.storage.user;
+package ru.yandex.practicum.filmorate.storage;
import ru.yandex.practicum.filmorate.model.User;
import java.util.Collection;
+import java.util.Optional;
public interface UserStorage {
User create(User user);
- void delete(Long id);
+ void delete(Long findId);
User update(User user);
- User getById(Long id);
+ Optional findById(Long userId);
Collection findAll();
}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/utils/FilmValidate.java b/src/main/java/ru/yandex/practicum/filmorate/utils/FilmValidate.java
index 1155956..ddee851 100644
--- a/src/main/java/ru/yandex/practicum/filmorate/utils/FilmValidate.java
+++ b/src/main/java/ru/yandex/practicum/filmorate/utils/FilmValidate.java
@@ -39,5 +39,10 @@ public static void validateFilm(Film film) {
log.error("Попытка добавить фильм с некорректной продолжительностью");
throw new ValidationException("Недопустимая продолжительность фильма");
}
+
+ if (film.getMpa() == null || film.getMpa().getId() == null) {
+ log.error("Попытка добавить фильм без рейтинга");
+ throw new ValidationException("Mpa-рейтинг фильма не может быть пустым");
+ }
}
}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/utils/UserValidate.java b/src/main/java/ru/yandex/practicum/filmorate/utils/UserValidate.java
index a76a93c..5f2ce5b 100644
--- a/src/main/java/ru/yandex/practicum/filmorate/utils/UserValidate.java
+++ b/src/main/java/ru/yandex/practicum/filmorate/utils/UserValidate.java
@@ -28,6 +28,11 @@ public static void validateUser(User user) {
throw new ValidationException("Дата рождения не может быть в будущем");
}
+ if (user.getBirthday() == null) {
+ log.error("Попытка ввести пустую дату рождения");
+ throw new ValidationException("Дата рождения не может быть null");
+ }
+
if (user.getEmail().isEmpty()) {
log.error("Попытка добавить пустой email");
throw new ValidationException("email не может быть пустым");
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 4a23bbc..a91ce0b 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1,2 +1,7 @@
server.port=8080
-logging.level.org.zalando.logbook=TRACE
\ No newline at end of file
+logging.level.org.zalando.logbook=TRACE
+spring.sql.init.mode=always
+spring.datasource.url=jdbc:h2:file:./db/filmorate;MODE=PostgreSQL
+spring.datasource.driverClassName=org.h2.Driver
+spring.datasource.username=sa
+spring.datasource.password=password
\ 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..b121b6a
--- /dev/null
+++ b/src/main/resources/data.sql
@@ -0,0 +1,16 @@
+MERGE INTO genre (genre_id, name)
+VALUES
+ (1, 'Комедия'),
+ (2, 'Драма'),
+ (3, 'Мультфильм'),
+ (4, 'Триллер'),
+ (5, 'Документальный'),
+ (6, 'Боевик');
+
+MERGE INTO mpa_rating (rating_id, name)
+VALUES
+ (1, 'G'),
+ (2, 'PG'),
+ (3, 'PG-13'),
+ (4, 'R'),
+ (5, 'NC-17');
\ 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..afe2d46
--- /dev/null
+++ b/src/main/resources/schema.sql
@@ -0,0 +1,51 @@
+CREATE TABLE IF NOT EXISTS mpa_rating (
+ rating_id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+ name varchar(5) NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS films (
+ film_id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+ name varchar(200) NOT NULL,
+ description varchar(200),
+ release_date date NOT NULL,
+ duration bigint NOT NULL,
+ rating_id bigint NOT NULL,
+ FOREIGN KEY (rating_id) REFERENCES mpa_rating (rating_id)
+);
+
+CREATE TABLE IF NOT EXISTS users (
+ user_id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+ email varchar NOT NULL,
+ login varchar NOT NULL,
+ name varchar,
+ birthday date NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS likes (
+ film_id bigint,
+ user_id bigint,
+ PRIMARY KEY (film_id, user_id),
+ FOREIGN KEY (film_id) REFERENCES films (film_id) ON DELETE CASCADE,
+ FOREIGN KEY (user_id) REFERENCES users (user_id) ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS friendship (
+ user_id bigint,
+ friend_id bigint,
+ PRIMARY KEY (user_id, friend_id),
+ FOREIGN KEY (user_id) REFERENCES users (user_id) ON DELETE CASCADE,
+ FOREIGN KEY (friend_id) REFERENCES users (user_id) ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS genre (
+ genre_id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+ name varchar(50) NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS film_genres (
+ film_id bigint,
+ genre_id bigint,
+ PRIMARY KEY (film_id, genre_id),
+ FOREIGN KEY (film_id) REFERENCES films (film_id) ON DELETE CASCADE,
+ FOREIGN KEY (genre_id) REFERENCES genre (genre_id)
+);
\ No newline at end of file
diff --git a/src/test/java/ru/yandex/practicum/filmorate/FilmorateApplicationTests.java b/src/test/java/ru/yandex/practicum/filmorate/FilmorateApplicationTests.java
index be537be..e188d8d 100644
--- a/src/test/java/ru/yandex/practicum/filmorate/FilmorateApplicationTests.java
+++ b/src/test/java/ru/yandex/practicum/filmorate/FilmorateApplicationTests.java
@@ -3,10 +3,10 @@
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
-@SpringBootTest
+@SpringBootTest(classes = FilmorateApplication.class)
class FilmorateApplicationTests {
@Test
void contextLoads() {
}
-}
+}
\ No newline at end of file
diff --git a/src/test/java/ru/yandex/practicum/filmorate/dal/FilmDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/dal/FilmDbStorageTest.java
new file mode 100644
index 0000000..c3dac0c
--- /dev/null
+++ b/src/test/java/ru/yandex/practicum/filmorate/dal/FilmDbStorageTest.java
@@ -0,0 +1,224 @@
+package ru.yandex.practicum.filmorate.dal;
+
+import lombok.RequiredArgsConstructor;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
+import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest;
+import org.springframework.context.annotation.Import;
+import org.springframework.jdbc.core.JdbcTemplate;
+import ru.yandex.practicum.filmorate.dal.mappers.*;
+import ru.yandex.practicum.filmorate.model.*;
+
+import java.time.LocalDate;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@JdbcTest
+@AutoConfigureTestDatabase
+@RequiredArgsConstructor(onConstructor_ = @Autowired)
+@Import({
+ FilmDbStorage.class,
+ GenreDbStorage.class,
+ LikesDbStorage.class,
+ MpaDbStorage.class,
+ FilmRowMapper.class,
+ GenreRowMapper.class,
+ LikesRowMapper.class,
+ MpaRowMapper.class})
+class FilmDbStorageTest {
+
+ private final FilmDbStorage filmDbStorage;
+ private final JdbcTemplate jdbcTemplate;
+ private final LikesDbStorage likesDbStorage;
+
+ @BeforeEach
+ public void beforeEach() {
+ // Очистка данных
+ jdbcTemplate.execute("DELETE FROM film_genres");
+ jdbcTemplate.execute("DELETE FROM likes");
+ jdbcTemplate.execute("DELETE FROM films");
+ jdbcTemplate.execute("DELETE FROM mpa_rating");
+ jdbcTemplate.execute("DELETE FROM genre");
+
+ // Инициализация mpa и жанров для тестов
+ jdbcTemplate.update("INSERT INTO mpa_rating (rating_id, name) VALUES (1, 'G'), (2, 'PG')");
+ jdbcTemplate.update("INSERT INTO genre (genre_id, name) VALUES (1, 'Комедия'), (2, 'Драма')");
+
+ // Создание тестовых пользователей
+ jdbcTemplate.update("INSERT INTO users (user_id, email, login, name, birthday) VALUES " +
+ "(1, 'user@mail.ru', 'login1', 'user_login', '1995-02-13'), " +
+ "(2, 'user@yandex.ru', 'login2', 'user2_login', '1996-02-14')");
+ }
+
+ // Тест создания фильма с валидными данными и получения его по id
+ @Test
+ public void createFilm_withValidFields_return_filmAddedWithGeneratedId() {
+ Film film = createTestFilm("Фильм", "Описание фильма",
+ LocalDate.of(1995, 2, 13), 120, 1L, Set.of(1L));
+
+ // Сохранение фильма в БД и поиск по id
+ Film newFilm = filmDbStorage.create(film);
+ Optional filmOptional = filmDbStorage.findById(newFilm.getId());
+ assertTrue(filmOptional.isPresent(), "Фильм должен существовать");
+
+ // Получение фильма из Optional
+ Film retrievedFilm = filmOptional.get();
+
+ // Проверка основных полей фильма
+ assertFilmEquals(film, filmOptional.get());
+
+ // Проверка связей фильма и MPA
+ assertNotNull(retrievedFilm.getMpa(), "MPA не должен быть null");
+ assertEquals(1L, retrievedFilm.getMpa().getId(), "id MPA должен совпадать");
+ assertEquals("G", retrievedFilm.getMpa().getName(), "Название MPA должно быть 'G'");
+
+ // Проверка связей фильма и жанров
+ assertNotNull(retrievedFilm.getGenres(), "Жанры не должны быть null");
+ assertEquals(1, retrievedFilm.getGenres().size(), "Должен быть 1 жанр");
+ assertEquals(1L, retrievedFilm.getGenres().iterator().next().getId(),
+ "id жанра должно быть 1");
+ }
+
+ // Тест получения списка всех созданных фильмов
+ @Test
+ public void findAll_afterAddingTwoFilms_returnsCollectionSize2() {
+ Film film = createTestFilm("Фильм", "Описание фильма",
+ LocalDate.of(1995, 2, 13), 120, 1L, Set.of(1L));
+ Film film2 = createTestFilm("Фильм 2", "Описание фильма 2",
+ LocalDate.of(2001, 3, 21), 145, 2L, Set.of(1L, 2L));
+
+ // Сохранение фильмов в БД
+ filmDbStorage.create(film);
+ filmDbStorage.create(film2);
+
+ Collection films = filmDbStorage.findAll();
+ assertEquals(2, films.size(), "Должны быть найдены 2 фильма");
+ }
+
+ // Тест обновления фильма
+ @Test
+ public void update_existingFilmWithValidData_fieldsUpdated() {
+ Film film = createTestFilm("Фильм", "Описание фильма",
+ LocalDate.of(1995, 2, 13), 120, 1L, Collections.emptySet());
+
+ // Сохранение фильма в БД
+ Film createdFilm = filmDbStorage.create(film);
+
+ // Обновление данных фильма
+ Film updatedFilm = createTestFilm("Фильм 2", "Описание фильма 2",
+ LocalDate.of(2001, 3, 21), 145, 2L, Set.of(1L, 2L));
+ updatedFilm.setId(createdFilm.getId());
+
+ //Обновление фильма в БД и получение его из Optional
+ filmDbStorage.update(updatedFilm);
+ Film retrievedFilm = filmDbStorage.findById(createdFilm.getId()).orElseThrow();
+
+ // Проверка основных полей фильма
+ assertFilmEquals(updatedFilm, retrievedFilm);
+
+ // Проверка связей фильма и MPA
+ assertNotNull(retrievedFilm.getMpa(), "MPA не должен быть null");
+ assertEquals(2L, retrievedFilm.getMpa().getId(), "id MPA должен совпадать");
+ assertEquals("PG", retrievedFilm.getMpa().getName(), "Название MPA должно быть 'PG'");
+
+ // Проверка связей фильма и жанров
+ assertNotNull(retrievedFilm.getGenres(), "Жанры не должны быть null");
+ assertEquals(2, retrievedFilm.getGenres().size(), "Должно быть 2 жанра");
+ }
+
+ // Тест удаления фильма по id
+ @Test
+ public void delete_existingFilm_removesFilm() {
+ Film film = createTestFilm("Фильм", "Описание фильма",
+ LocalDate.of(1995, 2, 13), 120, 1L, Collections.emptySet());
+
+ // Сохранение фильма в БД
+ filmDbStorage.create(film);
+
+ // Удаление фильма
+ filmDbStorage.delete(film.getId());
+ Optional filmOptional = filmDbStorage.findById(film.getId());
+ assertEquals(Optional.empty(), filmOptional, "Созданный фильм должен быть удален");
+ }
+
+ // Тест на получение списка популярных фильмов
+ @Test
+ public void getPopularFilms_returnsFilmsOrderedByLikes() {
+ Film film1 = createTestFilm("Фильм 1", "Описание 1",
+ LocalDate.of(2000, 1, 1), 100, 1L, Set.of(1L));
+ Film film2 = createTestFilm("Фильм 2", "Описание 2",
+ LocalDate.of(2001, 1, 1), 120, 2L, Set.of(2L));
+
+ // Сохранение фильмов в БД
+ Film createdFilm = filmDbStorage.create(film1);
+ Film createdFilm2 = filmDbStorage.create(film2);
+
+ // Добавление лайков (film2 - 2 лайка, film1 - 0 лайков)
+ likesDbStorage.addLikeToFilm(createdFilm2.getId(), 1L);
+ likesDbStorage.addLikeToFilm(createdFilm2.getId(), 2L);
+ likesDbStorage.addLikeToFilm(createdFilm.getId(), 2L);
+
+ Collection popularFilms = filmDbStorage.findPopularFilms(2);
+
+ assertNotNull(popularFilms, "Список не должен быть null");
+ assertEquals(2, popularFilms.size(), "Должны вернуться 2 фильма");
+
+ // Преобразование в список для проверки порядка
+ List filmsList = new ArrayList<>(popularFilms);
+
+ // Проверка порядка сортировки (по убыванию лайков)
+ assertEquals(createdFilm2.getId(), filmsList.get(0).getId(),
+ "Первым должен быть фильм с наибольшим количеством лайков");
+ assertEquals(2, filmsList.get(0).getLikesByUsers().size(),
+ "У первого фильма должно быть 2 лайка");
+
+ assertEquals(createdFilm.getId(), filmsList.get(1).getId(),
+ "Вторым должен быть фильм с меньшим количеством лайков");
+ assertEquals(1, filmsList.get(1).getLikesByUsers().size(),
+ "У второго фильма должен быть 1 лайк");
+ }
+
+ // Вспомогательный метод для создания тестового фильма
+ private Film createTestFilm(
+ String name,
+ String description,
+ LocalDate release_date,
+ int duration,
+ Long mpaId,
+ Set genresId) {
+ Film film = new Film();
+ film.setName(name);
+ film.setDescription(description);
+ film.setReleaseDate(release_date);
+ film.setDuration(duration);
+
+ Mpa mpa = new Mpa();
+ mpa.setId(mpaId);
+ film.setMpa(mpa);
+
+
+ Set genres = genresId.stream()
+ .map(id -> {
+ Genre g = new Genre();
+ g.setId(id);
+ return g;
+ })
+ .collect(Collectors.toSet());
+ film.setGenres(genres);
+
+ return film;
+ }
+
+ // Вспомогательный метод для проверки основных полей фильма
+ private void assertFilmEquals(Film expected, Film actual) {
+ assertEquals(expected.getName(), actual.getName(), "Название фильма должно совпадать");
+ assertEquals(expected.getDescription(), actual.getDescription(), "Описание должно совпадать");
+ assertEquals(expected.getReleaseDate(), actual.getReleaseDate(), "Дата релиза должна совпадать");
+ assertEquals(expected.getDuration(), actual.getDuration(),
+ "Продолжительность фильма должна совпадать");
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/yandex/practicum/filmorate/dal/FriendshipDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/dal/FriendshipDbStorageTest.java
new file mode 100644
index 0000000..7c19280
--- /dev/null
+++ b/src/test/java/ru/yandex/practicum/filmorate/dal/FriendshipDbStorageTest.java
@@ -0,0 +1,86 @@
+package ru.yandex.practicum.filmorate.dal;
+
+import lombok.RequiredArgsConstructor;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
+import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest;
+import org.springframework.context.annotation.Import;
+import org.springframework.jdbc.core.JdbcTemplate;
+import ru.yandex.practicum.filmorate.dal.mappers.FriendshipRowMapper;
+import ru.yandex.practicum.filmorate.dal.mappers.UserRowMapper;
+import ru.yandex.practicum.filmorate.model.User;
+
+import java.util.Collection;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@JdbcTest
+@AutoConfigureTestDatabase
+@RequiredArgsConstructor(onConstructor_ = @Autowired)
+@Import({FriendshipDbStorage.class, FriendshipRowMapper.class, UserDbStorage.class, UserRowMapper.class})
+public class FriendshipDbStorageTest {
+
+ private final FriendshipDbStorage friendshipDbStorage;
+ private final JdbcTemplate jdbcTemplate;
+
+ @BeforeEach
+ public void beforeEach() {
+ jdbcTemplate.execute("DELETE FROM friendship");
+ jdbcTemplate.execute("DELETE FROM users");
+
+ // Создание тестовых пользователей
+ jdbcTemplate.update("INSERT INTO users (user_id, email, login, name, birthday) VALUES " +
+ "(1, 'user@yandex.ru', 'Mult1k', 'Дмитрий', '1995-02-13'), " +
+ "(2, 'user@gmail.com.ru', 'Friend', 'Саша', '1995-04-24'), " +
+ "(3, 'user@yahoo.com', 'Friend2', 'Ксения', '1996-02-14')");
+ }
+
+ // Тест добавления в друзья и получение списка всех друзей
+ @Test
+ public void addFriend_addFriendship() {
+ // Добавление пользователем user в друзья пользователей friend и friend2
+ friendshipDbStorage.addFriend(1L, 2L);
+ friendshipDbStorage.addFriend(1L, 3L);
+
+ // Получение списка друзей пользователя user
+ Collection friends = friendshipDbStorage.findAllFriends(1L);
+ assertEquals(2, friends.size(), "У пользователя должно быть 2 друга");
+
+ // Получение списка друзей пользователей friend и friend2 - список должен быть пуст, тк дружба односторонняя
+ Collection friendFriends = friendshipDbStorage.findAllFriends(2L);
+ assertEquals(0, friendFriends.size(), "У пользователя friend не должно быть друзей");
+ Collection friendFriends2 = friendshipDbStorage.findAllFriends(3L);
+ assertEquals(0, friendFriends2.size(), "У пользователя friend2 не должно быть друзей");
+ }
+
+ // Тест удаления пользователя из друзей
+ @Test
+ public void delete_existingFriend_removesFriend() {
+ // Добавление пользователем user в друзья пользователя friend
+ friendshipDbStorage.addFriend(1L, 2L);
+
+ // Удаление пользователем user из друзей пользователя friend
+ friendshipDbStorage.removeFriend(1L, 2L);
+
+ // Получение списка друзей пользователя user
+ Collection friends = friendshipDbStorage.findAllFriends(1L);
+ assertTrue(friends.isEmpty(), "У пользователя не должно быть друзей");
+ }
+
+ // Тест получения общего списка друзей
+ @Test
+ public void getCommonFriends_returnsCommonFriends() {
+ // Добавление пользователями с id 1L и 2L в друзья пользователя с id 3L
+ friendshipDbStorage.addFriend(1L, 3L);
+ friendshipDbStorage.addFriend(2L, 3L);
+
+ // Получение списка общего друга(Friend2) у пользователей User и Friend
+ Collection commonFriends = friendshipDbStorage.findCommonFriends(1L, 2L);
+ assertEquals(1, commonFriends.size(),
+ "У пользователей Mult1k и Friend должен быть 1 общий друг");
+ assertFalse(commonFriends.isEmpty(), "Список общих друзей пуст");
+ assertEquals(3L, commonFriends.iterator().next().getId(), "Неверный id общего друга");
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/yandex/practicum/filmorate/dal/GenreDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/dal/GenreDbStorageTest.java
new file mode 100644
index 0000000..2ad0e7c
--- /dev/null
+++ b/src/test/java/ru/yandex/practicum/filmorate/dal/GenreDbStorageTest.java
@@ -0,0 +1,87 @@
+package ru.yandex.practicum.filmorate.dal;
+
+import lombok.RequiredArgsConstructor;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
+import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest;
+import org.springframework.context.annotation.Import;
+import org.springframework.jdbc.core.JdbcTemplate;
+import ru.yandex.practicum.filmorate.dal.mappers.GenreRowMapper;
+import ru.yandex.practicum.filmorate.model.Genre;
+
+import java.util.*;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@JdbcTest
+@AutoConfigureTestDatabase
+@RequiredArgsConstructor(onConstructor_ = @Autowired)
+@Import({GenreDbStorage.class, GenreRowMapper.class})
+public class GenreDbStorageTest {
+
+ private final GenreDbStorage genreDbStorage;
+ private final JdbcTemplate jdbcTemplate;
+
+ @BeforeEach
+ public void beforeEach() {
+ jdbcTemplate.execute("DELETE FROM film_genres");
+ jdbcTemplate.execute("DELETE FROM films");
+ // Создание тестового фильма
+ jdbcTemplate.update("INSERT INTO films (" +
+ "film_id, " +
+ "name, " +
+ "description, " +
+ "release_date, " +
+ "duration, " +
+ "rating_id) " +
+ "VALUES (1, 'Тестовый фильм', 'Описание фильма', '2025-07-14', 120, 1)");
+ }
+
+ // Тест получения списка всех жанров из БД
+ @Test
+ public void findAllGenres_returnAllGenresFromDataSql() {
+ Collection genres = genreDbStorage.findAllGenres();
+ assertEquals(6, genres.size(), "Должно быть 6 жанров в БД");
+ }
+
+ // Тест получения жанра из БД по id
+ @Test
+ public void findGenreById_returnGenreFromDataSql() {
+ // Получение жанра из Optional
+ Optional genre = genreDbStorage.findById(1L);
+ assertTrue(genre.isPresent(), "Жанр с id:1 должен существовать");
+ assertEquals("Комедия", genre.get().getName(), "Жанр с id:1 должен быть комедией");
+ }
+
+ // Тест получения жанров для указанного фильма
+ @Test
+ public void findGenreForFilm_returnGenresForFilm() {
+ // Добавляем жанры для тестового фильма с id:1
+ jdbcTemplate.update("INSERT INTO film_genres (film_id, genre_id) VALUES (1, 1)");
+ jdbcTemplate.update("INSERT INTO film_genres (film_id, genre_id) VALUES (1, 2)");
+
+ Collection genres = genreDbStorage.findGenreForFilm(1L);
+ assertEquals(2, genres.size(), "У фильма должно быть 2 жанра");
+ assertTrue(genres.stream().anyMatch(g -> g.getId() == 1L && g.getName().equals("Комедия")),
+ "Должен быть жанр 'Комедия' (id=1)");
+ assertTrue(genres.stream().anyMatch(g -> g.getId() == 2L && g.getName().equals("Драма")),
+ "Должен быть жанр 'Драма' (id=2)");
+ }
+
+ // Тест добавления жанров для фильма
+ @Test
+ public void setGenresFilm_addGenresToFilm() {
+ // Добавляем жанры для тестового фильма с id:1
+ jdbcTemplate.update("INSERT INTO film_genres (film_id, genre_id) VALUES (1, 1)");
+ jdbcTemplate.update("INSERT INTO film_genres (film_id, genre_id) VALUES (1, 2)");
+
+ Collection genres = genreDbStorage.findGenreForFilm(1L);
+ assertTrue(genres.stream().anyMatch(g -> g.getId() == 1L && g.getName().equals("Комедия")),
+ "Должен быть жанр 'Комедия' (id=1)");
+ assertTrue(genres.stream().anyMatch(g -> g.getId() == 2L && g.getName().equals("Драма")),
+ "Должен быть жанр 'Драма' (id=2)");
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/yandex/practicum/filmorate/dal/LikesDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/dal/LikesDbStorageTest.java
new file mode 100644
index 0000000..3e74a4a
--- /dev/null
+++ b/src/test/java/ru/yandex/practicum/filmorate/dal/LikesDbStorageTest.java
@@ -0,0 +1,79 @@
+package ru.yandex.practicum.filmorate.dal;
+
+import lombok.RequiredArgsConstructor;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
+import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest;
+import org.springframework.context.annotation.Import;
+import org.springframework.jdbc.core.JdbcTemplate;
+import ru.yandex.practicum.filmorate.dal.mappers.LikesRowMapper;
+import ru.yandex.practicum.filmorate.model.Likes;
+
+import java.util.Collection;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@JdbcTest
+@AutoConfigureTestDatabase
+@RequiredArgsConstructor(onConstructor_ = @Autowired)
+@Import({LikesDbStorage.class, LikesRowMapper.class})
+public class LikesDbStorageTest {
+
+ private final LikesDbStorage likesDbStorage;
+ private final JdbcTemplate jdbcTemplate;
+
+ @BeforeEach
+ public void beforeEach() {
+ jdbcTemplate.execute("DELETE FROM likes");
+ jdbcTemplate.execute("DELETE FROM films");
+ jdbcTemplate.execute("DELETE FROM users");
+
+ // Создание тестового фильма
+ jdbcTemplate.update("INSERT INTO films (" +
+ "film_id, " +
+ "name, " +
+ "description, " +
+ "release_date, " +
+ "duration, " +
+ "rating_id) " +
+ "VALUES (1, 'Тестовый фильм', 'Описание фильма', '2025-07-14', 120, 1)");
+
+ // Создание тестовых пользователей
+ jdbcTemplate.update("INSERT INTO users (user_id, email, login, name, birthday) VALUES " +
+ "(1, 'user@yandex.ru', 'Mult1k', 'Дмитрий', '1995-02-13'), " +
+ "(2, 'user@gmail.com.ru', 'Friend', 'Саша', '1995-04-24')");
+ }
+
+ // Тест добавления лайка фильму
+ @Test
+ public void addLike_shouldAddLike() {
+ likesDbStorage.addLikeToFilm(1L, 2L);
+ Collection likes = likesDbStorage.getLikesOnFilm(1L);
+ assertEquals(1, likes.size(), "У тестового фильма должен быть 1 лайк");
+ }
+
+ // Тест удаления лайка у фильма
+ @Test
+ public void deleteLike_removesLikeFromFilm() {
+ likesDbStorage.addLikeToFilm(1L, 2L);
+ likesDbStorage.removeLikeFromFilm(1L, 2L);
+ Collection likes = likesDbStorage.getLikesOnFilm(1L);
+ assertEquals(0, likes.size(), "У тестового фильма не должно быть лайков");
+ }
+
+ // Тест получения всех лайков у фильма
+ @Test
+ public void getAllLikesFromFilm_returnAllLikesFromFilm() {
+ likesDbStorage.addLikeToFilm(1L, 1L);
+ likesDbStorage.addLikeToFilm(1L, 2L);
+ Collection likes = likesDbStorage.getLikesOnFilm(1L);
+ assertEquals(2, likes.size(), "У тестового фильма должно быть 2 лайка");
+ assertTrue(likes.stream().anyMatch(like -> like.getUserId() == 1L),
+ "Должен быть лайк от Дмитрия");
+ assertTrue(likes.stream().anyMatch(like -> like.getUserId() == 2L),
+ "Должен быть лайк от Саши");
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/yandex/practicum/filmorate/dal/MpaDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/dal/MpaDbStorageTest.java
new file mode 100644
index 0000000..2c342d0
--- /dev/null
+++ b/src/test/java/ru/yandex/practicum/filmorate/dal/MpaDbStorageTest.java
@@ -0,0 +1,67 @@
+package ru.yandex.practicum.filmorate.dal;
+
+import lombok.RequiredArgsConstructor;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
+import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest;
+import org.springframework.context.annotation.Import;
+import org.springframework.jdbc.core.JdbcTemplate;
+import ru.yandex.practicum.filmorate.dal.mappers.MpaRowMapper;
+import ru.yandex.practicum.filmorate.model.Mpa;
+
+import java.util.Collection;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@JdbcTest
+@AutoConfigureTestDatabase
+@RequiredArgsConstructor(onConstructor_ = @Autowired)
+@Import({MpaDbStorage.class, MpaRowMapper.class})
+public class MpaDbStorageTest {
+
+ private final MpaDbStorage mpaDbStorage;
+ private final JdbcTemplate jdbcTemplate;
+
+ @BeforeEach
+ public void beforeEach() {
+ jdbcTemplate.execute("DELETE FROM films");
+ // Создание тестового фильма
+ jdbcTemplate.update("INSERT INTO films (" +
+ "film_id, " +
+ "name, " +
+ "description, " +
+ "release_date, " +
+ "duration, " +
+ "rating_id) " +
+ "VALUES (1, 'Тестовый фильм', 'Описание фильма', '2025-07-14', 120, 5)");
+ }
+
+ // Тест получения всех жанров
+ @Test
+ public void findAllMpa_returnAllMpaFromDataSql() {
+ Collection allMpa = mpaDbStorage.findAll();
+ assertEquals(5, allMpa.size(), "Должно быть 5 MPA-рейтингов в БД");
+ }
+
+ // Тест получения MPA-рейтинга из БД по id
+ @Test
+ public void findMpa_returnMpaFromDataSql() {
+ // Получение MPA-рейтинга из Optional
+ Optional mpa = mpaDbStorage.findMpaById(4L);
+ assertTrue(mpa.isPresent(), "Жанр с id:4 должен существовать");
+ assertEquals("R", mpa.get().getName(), "Жанр с id:4 должен быть R");
+ }
+
+ // Тест получения жанра для указанного фильма
+ @Test
+ public void findMpaForFilm_returnMpaToFilm() {
+ Optional mpaFilm = mpaDbStorage.findMpaFilm(1L);
+ assertTrue(mpaFilm.isPresent(), "Фильм должен иметь рейтинг");
+ assertEquals(5, mpaFilm.get().getId(), "Фильм должен иметь рейтинг с id=5");
+ assertEquals("NC-17", mpaFilm.get().getName(), "Фильм должен иметь рейтинг NC-17");
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/yandex/practicum/filmorate/dal/UserDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/dal/UserDbStorageTest.java
new file mode 100644
index 0000000..3bef5bd
--- /dev/null
+++ b/src/test/java/ru/yandex/practicum/filmorate/dal/UserDbStorageTest.java
@@ -0,0 +1,129 @@
+package ru.yandex.practicum.filmorate.dal;
+
+import lombok.RequiredArgsConstructor;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
+import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest;
+import org.springframework.context.annotation.Import;
+import org.springframework.jdbc.core.JdbcTemplate;
+import ru.yandex.practicum.filmorate.dal.mappers.*;
+import ru.yandex.practicum.filmorate.model.User;
+
+import java.time.LocalDate;
+import java.util.Collection;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@JdbcTest
+@AutoConfigureTestDatabase
+@RequiredArgsConstructor(onConstructor_ = @Autowired)
+@Import({UserDbStorage.class, UserRowMapper.class})
+public class UserDbStorageTest {
+
+ private final UserDbStorage userDbStorage;
+ private final JdbcTemplate jdbcTemplate;
+
+ @BeforeEach
+ public void beforeEach() {
+ // Очистка данных
+ jdbcTemplate.execute("DELETE FROM users");
+ }
+
+ // Тест создания пользователя с валидными данными и получения его по id
+ @Test
+ public void createUser_withValidFields_return_userAddedWithGeneratedId() {
+ User user = createUserTest("user@yandex.ru", "Mult1k", "Дмитрий",
+ LocalDate.of(1995, 2, 13));
+
+ // Сохранение пользователя в БД и поиск по id
+ User newUser = userDbStorage.create(user);
+ Optional userOptional = userDbStorage.findById(newUser.getId());
+ assertTrue(userOptional.isPresent(), "Пользователя должен существовать");
+
+ // Получение пользователя из Optional
+ User retrievedUser = userOptional.get();
+
+ // Проверка основных полей пользователя
+ assertUserEquals(user, retrievedUser);
+ }
+
+ // Тест получения списка всех созданных пользователей
+ @Test
+ public void findAll_afterAddingTwoUsers_returnsCollectionSize2() {
+ User user = createUserTest("user@yandex.ru", "Mult1k", "Дмитрий",
+ LocalDate.of(1995, 2, 13));
+ User user2 = createUserTest("user@gmail.com", "Login2", "name",
+ LocalDate.of(1995, 4, 24));
+
+ // Сохранение пользователей в БД
+ userDbStorage.create(user);
+ userDbStorage.create(user2);
+
+ Collection users = userDbStorage.findAll();
+ assertEquals(2, users.size(), "Должно быть найдено 2 пользователя");
+ }
+
+ // Тест обновления пользователя
+ @Test
+ public void update_existingUserWithValidData_fieldsUpdated() {
+ User user = createUserTest("user@yandex.ru", "Mult1k", "Дмитрий",
+ LocalDate.of(1995, 2, 13));
+
+ // Сохранение пользователя в БД
+ User createdUser = userDbStorage.create(user);
+
+ // Обновление данных пользователя
+ User updatedUser = createUserTest("user@gmail.com", "Login2", "name",
+ LocalDate.of(1995, 4, 24));
+ updatedUser.setId(createdUser.getId());
+
+ // Обновление пользователя в БД и получение его из Optional
+ userDbStorage.update(updatedUser);
+ User retrievedUser = userDbStorage.findById(createdUser.getId()).orElseThrow();
+
+ // Проверка основных полей пользователя
+ assertEquals(updatedUser, retrievedUser);
+ }
+
+ // Тест удаления пользователя по id
+ @Test
+ public void delete_existingUser_removesUser() {
+ User user = createUserTest("user@yandex.ru", "Mult1k", "Дмитрий",
+ LocalDate.of(1995, 2, 13));
+
+ // Сохранение пользователя в БД
+ userDbStorage.create(user);
+
+ // Удаление пользователя
+ userDbStorage.delete(user.getId());
+ Optional userOptional = userDbStorage.findById(user.getId());
+ assertEquals(Optional.empty(), userOptional, "Созданный пользователя должен быть удален");
+ }
+
+ // Вспомогательный метод для создания тестового пользователя
+ private User createUserTest(
+ String email,
+ String login,
+ String name,
+ LocalDate birthday
+ ) {
+ User user = new User();
+ user.setEmail(email);
+ user.setLogin(login);
+ user.setName(name);
+ user.setBirthday(birthday);
+ return user;
+ }
+
+ // Вспомогательный метод для проверки основных полей пользователя
+ private void assertUserEquals(User expected, User actual) {
+ assertEquals(expected.getEmail(), actual.getEmail(), "Email должен совпадать");
+ assertEquals(expected.getLogin(), actual.getLogin(), "Логин пользователя должен совпадать");
+ assertEquals(expected.getName(), actual.getName(), "Имя пользователя должно совпадать");
+ assertEquals(expected.getBirthday(), actual.getBirthday(), "Дата рождения должна совпадать");
+ }
+}
\ 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/service/FilmServiceTest.java
similarity index 62%
rename from src/test/java/ru/yandex/practicum/filmorate/controller/FilmControllerTest.java
rename to src/test/java/ru/yandex/practicum/filmorate/service/FilmServiceTest.java
index 2a46821..79c2c05 100644
--- a/src/test/java/ru/yandex/practicum/filmorate/controller/FilmControllerTest.java
+++ b/src/test/java/ru/yandex/practicum/filmorate/service/FilmServiceTest.java
@@ -1,63 +1,93 @@
-package ru.yandex.practicum.filmorate.controller;
+package ru.yandex.practicum.filmorate.service;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import ru.yandex.practicum.filmorate.controller.*;
+import ru.yandex.practicum.filmorate.dto.*;
import ru.yandex.practicum.filmorate.exception.*;
-import ru.yandex.practicum.filmorate.model.Film;
-import ru.yandex.practicum.filmorate.model.User;
-import ru.yandex.practicum.filmorate.service.FilmService;
-import ru.yandex.practicum.filmorate.storage.film.InMemoryFilmStorage;
-import ru.yandex.practicum.filmorate.storage.user.InMemoryUserStorage;
-import ru.yandex.practicum.filmorate.utils.FilmValidate;
+import ru.yandex.practicum.filmorate.model.*;
+import ru.yandex.practicum.filmorate.storage.*;
+import ru.yandex.practicum.filmorate.utils.*;
import java.time.LocalDate;
import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
-class FilmControllerTest {
+public class FilmServiceTest {
+ private final Map films = new HashMap<>();
private FilmController filmController;
- private InMemoryFilmStorage filmStorage;
- private InMemoryUserStorage userStorage;
+ private FilmTestStorage filmTestStorage;
+ private UserTestStorage userTestStorage;
private FilmValidate filmValidate;
-
+ private GenreTestStorage genreTestStorage;
+ private MpaTestStorage mpaTestStorage;
+ private LikesTestStorage likesTestStorage;
+ private UserController userController;
+ private UserValidate userValidate;
+ private FriendshipTestStorage friendshipTestStorage;
@BeforeEach
public void beforeEach() {
- filmStorage = new InMemoryFilmStorage();
- userStorage = new InMemoryUserStorage();
+ films.clear();
+
+ filmTestStorage = new FilmTestStorage();
+ userTestStorage = new UserTestStorage();
filmValidate = new FilmValidate();
+ genreTestStorage = new GenreTestStorage();
+ mpaTestStorage = new MpaTestStorage();
+ likesTestStorage = new LikesTestStorage();
+
filmController = new FilmController(
- new FilmService(filmStorage, userStorage, filmValidate)
+ new FilmService(filmTestStorage,
+ userTestStorage,
+ filmValidate,
+ genreTestStorage,
+ mpaTestStorage,
+ likesTestStorage)
);
+
+ userController = new UserController(new UserService(userTestStorage, userValidate, friendshipTestStorage));
}
// Тест успешного создания фильма с валидными данными
@Test
public void create_allRequiredFieldsValid_filmAddedWithGeneratedId() {
- Film film = new Film();
+ NewFilmRequest film = new NewFilmRequest();
film.setName("Фильм 1");
film.setDescription("Описание фильма");
film.setReleaseDate(LocalDate.of(1995, 2, 13));
film.setDuration(120);
- Film createdFilm = filmController.create(film);
+ Mpa mpa = new Mpa();
+ mpa.setId(1L);
+ mpa.setName("G");
+ film.setMpa(mpa);
+
+ FilmDto createdFilm = filmController.create(film);
assertNotNull(createdFilm.getId(), "Фильму не был присвоен id");
- assertEquals(1, filmController.findAll().size(),
+ assertEquals(1, filmTestStorage.findAll().size(),
"Неверное количество фильмов после создания");
}
// Тест граничного условия для даты релиза - релиз раньше 28.12.1895 не допустим
@Test
public void create_releaseDateBeforeFirstFilm_throwsValidationException() {
- Film film = new Film();
+ NewFilmRequest film = new NewFilmRequest();
film.setName("Фильм с некорректной датой релиза");
film.setDescription("Описание фильма");
film.setReleaseDate(LocalDate.of(1895, 12, 27));
film.setDuration(120);
+ Mpa mpa = new Mpa();
+ mpa.setId(1L);
+ mpa.setName("G");
+ film.setMpa(mpa);
+
assertThrows(ValidationException.class, () -> filmController.create(film),
"Ожидалось ValidationException при дате релиза раньше 28.12.1895");
}
@@ -65,24 +95,34 @@ public void create_releaseDateBeforeFirstFilm_throwsValidationException() {
// Тест граничного условия для даты релиза - релиз 28.12.1895 должен быть допустим
@Test
public void create_releaseDateEqualsFirstFilm_noValidationErrors() {
- Film film = new Film();
+ NewFilmRequest film = new NewFilmRequest();
film.setName("Старый фильм");
film.setDescription("Описание");
film.setReleaseDate(LocalDate.of(1895, 12, 28));
film.setDuration(15);
+ Mpa mpa = new Mpa();
+ mpa.setId(1L);
+ mpa.setName("G");
+ film.setMpa(mpa);
+
assertDoesNotThrow(() -> filmController.create(film), "Дата релиза 28.12.1895 должна быть допустима");
}
// Тест добавления фильма без даты релиза - релиз не может быть null
@Test
public void create_releaseDateNull_throwsValidationException() {
- Film film = new Film();
+ NewFilmRequest film = new NewFilmRequest();
film.setName("Фильм");
film.setDescription("Описание");
film.setReleaseDate(null);
film.setDuration(15);
+ Mpa mpa = new Mpa();
+ mpa.setId(1L);
+ mpa.setName("G");
+ film.setMpa(mpa);
+
assertThrows(NullPointerException.class, () -> filmController.create(film),
"Дата релиза не может быть null");
}
@@ -90,13 +130,18 @@ public void create_releaseDateNull_throwsValidationException() {
// Тест на обновление несуществующего фильма
@Test
public void update_nonExistentFilmId_throwsNotFoundException() {
- Film film = new Film();
+ UpdateFilmRequest film = new UpdateFilmRequest();
film.setId(14L);
film.setName("Фильм");
film.setDescription("Описание фильма");
film.setReleaseDate(LocalDate.of(1995, 12, 28));
film.setDuration(120);
+ Mpa mpa = new Mpa();
+ mpa.setId(1L);
+ mpa.setName("G");
+ film.setMpa(mpa);
+
assertThrows(NotFoundException.class, () -> filmController.update(film),
"Ожидалось NotFoundException при обновлении несуществующего фильма");
}
@@ -104,13 +149,18 @@ public void update_nonExistentFilmId_throwsNotFoundException() {
// Тест проверяет валидацию Id при обновлении - Id не должен быть null
@Test
public void update_nullId_throwsValidationException() {
- Film film = new Film();
+ UpdateFilmRequest film = new UpdateFilmRequest();
film.setId(null);
film.setName("Фильма с нулевым id");
film.setDescription("Описание фильма");
film.setReleaseDate(LocalDate.of(2025, 1, 14));
film.setDuration(120);
+ Mpa mpa = new Mpa();
+ mpa.setId(1L);
+ mpa.setName("G");
+ film.setMpa(mpa);
+
assertThrows(ValidationException.class, () -> filmController.update(film),
"Ожидалось ValidationException при обновлении с null ID");
}
@@ -119,69 +169,105 @@ public void update_nullId_throwsValidationException() {
@Test
public void update_existingFilmWithValidData_fieldsUpdated() {
// Создание фильма
- Film film = new Film();
+ NewFilmRequest film = new NewFilmRequest();
film.setName("Начало");
film.setDescription("Сон внутри сна");
film.setReleaseDate(LocalDate.of(2010, 7, 22));
film.setDuration(148);
- Film createdFilm = filmController.create(film);
+
+ Mpa mpa = new Mpa();
+ mpa.setId(1L);
+ mpa.setName("G");
+ film.setMpa(mpa);
+
+ FilmDto createdFilm = filmController.create(film);
// Обновление фильма
- Film updatedFilm = new Film();
+ UpdateFilmRequest updatedFilm = new UpdateFilmRequest();
updatedFilm.setId(createdFilm.getId());
updatedFilm.setName("Уже не Начало");
updatedFilm.setDescription("Просто описание");
updatedFilm.setReleaseDate(LocalDate.of(1996, 1, 1));
updatedFilm.setDuration(150);
- Film result = filmController.update(updatedFilm);
+
+ mpa.setId(2L);
+ mpa.setName("PG");
+ updatedFilm.setMpa(mpa);
+
+ FilmDto result = filmController.update(updatedFilm);
assertEquals("Уже не Начало", result.getName(), "Название фильма не обновилось");
assertEquals("Просто описание", result.getDescription(), "Описание фильма не обновилось");
assertEquals(150, result.getDuration(), "Продолжительность фильма не обновилась");
+ assertEquals("PG", result.getMpa().getName(), "MPA-рейтинг фильма не обновился");
}
// Тест на получение списка всех созданных фильмов
@Test
public void findAll_afterAddingTwoFilms_returnsCollectionSize2() {
// Создаем 2 фильма
- Film film1 = new Film();
+ NewFilmRequest film1 = new NewFilmRequest();
film1.setName("Брат");
film1.setDescription("Описание фильма");
film1.setReleaseDate(LocalDate.of(1997, 12, 12));
film1.setDuration(100);
- filmController.create(film1);
- Film film2 = new Film();
+ Mpa mpa = new Mpa();
+ mpa.setId(1L);
+ mpa.setName("G");
+ film1.setMpa(mpa);
+
+ FilmDto createdFilm = filmController.create(film1);
+
+ NewFilmRequest film2 = new NewFilmRequest();
film2.setName("Брат 2");
film2.setDescription("Описание продолжения");
film2.setReleaseDate(LocalDate.of(2000, 5, 11));
film2.setDuration(127);
- filmController.create(film2);
+
+ Mpa mpa2 = new Mpa();
+ mpa2.setId(1L);
+ mpa2.setName("G");
+ film2.setMpa(mpa2);
+
+ FilmDto createdFilm2 = filmController.create(film2);
assertEquals(2, filmController.findAll().size(), "Неверное количество фильмов в списке");
+ assertEquals("Брат", createdFilm.getName(), "Название 1-го фильма должно быть Брат");
+ assertEquals("Брат 2", createdFilm2.getName(), "Название 2-го фильма должно быть Брат 2");
}
// Тест граничного условия для описания - 200 символов должно быть допустимо
@Test
public void create_descriptionExactly200Chars_noValidationErrors() {
- Film film = new Film();
+ NewFilmRequest film = new NewFilmRequest();
film.setName("Фильм");
film.setDescription("A".repeat(200));
film.setReleaseDate(LocalDate.of(2025, 5, 30));
film.setDuration(95);
+ Mpa mpa = new Mpa();
+ mpa.setId(3L);
+ mpa.setName("PG-13");
+ film.setMpa(mpa);
+
assertDoesNotThrow(() -> filmController.create(film), "Описание в 200 символов должно быть допустимо");
}
// Тест граничного условия описания - длина описания не может превышать 200 символов
@Test
public void create_description201Chars_throwsValidationException() {
- Film film = new Film();
+ NewFilmRequest film = new NewFilmRequest();
film.setName("Фильм");
film.setDescription("А".repeat(201));
film.setReleaseDate(LocalDate.of(1995, 12, 28));
film.setDuration(120);
+ Mpa mpa = new Mpa();
+ mpa.setId(4L);
+ mpa.setName("R");
+ film.setMpa(mpa);
+
assertThrows(ValidationException.class, () -> filmController.create(film),
"Ожидалось ValidationException при описании длиной 201 символ ");
}
@@ -189,12 +275,17 @@ public void create_description201Chars_throwsValidationException() {
// Тест граничного условия для продолжительности: минимальная продолжительность (1 минута) должна быть допустима
@Test
public void create_durationOne_noValidationErrors() {
- Film film = new Film();
+ NewFilmRequest film = new NewFilmRequest();
film.setName("Фильм");
film.setDescription("Описание");
film.setReleaseDate(LocalDate.of(2025, 5, 30));
film.setDuration(1);
+ Mpa mpa = new Mpa();
+ mpa.setId(4L);
+ mpa.setName("R");
+ film.setMpa(mpa);
+
assertDoesNotThrow(() -> filmController.create(film),
"Продолжительность фильма в 1 минуту должна быть допустима");
}
@@ -202,12 +293,17 @@ public void create_durationOne_noValidationErrors() {
// Тест граничного условия для продолжительности - продолжительность должна быть больше 0
@Test
public void create_duration0_throwsValidationException() {
- Film film = new Film();
+ NewFilmRequest film = new NewFilmRequest();
film.setName("Фильм");
film.setDescription("Описание");
film.setReleaseDate(LocalDate.of(1995, 12, 28));
film.setDuration(0);
+ Mpa mpa = new Mpa();
+ mpa.setId(4L);
+ mpa.setName("R");
+ film.setMpa(mpa);
+
assertThrows(ValidationException.class, () -> filmController.create(film),
"Ожидалось ValidationException при продолжительности 0");
}
@@ -215,12 +311,17 @@ public void create_duration0_throwsValidationException() {
// Тест граничного условия для продолжительности - продолжительность не может быть меньше 0
@Test
public void create_durationNegative_throwsValidationException() {
- Film film = new Film();
+ NewFilmRequest film = new NewFilmRequest();
film.setName("Фильм");
film.setDescription("Описание");
film.setReleaseDate(LocalDate.of(1995, 12, 28));
film.setDuration(-1);
+ Mpa mpa = new Mpa();
+ mpa.setId(5L);
+ mpa.setName("NC-17");
+ film.setMpa(mpa);
+
assertThrows(ValidationException.class, () -> filmController.create(film),
"Ожидалось ValidationException при отрицательной продолжительности");
}
@@ -228,12 +329,17 @@ public void create_durationNegative_throwsValidationException() {
// Тест проверяет валидацию имени фильма - пустая строка недопустима
@Test
public void create_EmptyName_throwsValidationException() {
- Film film = new Film();
+ NewFilmRequest film = new NewFilmRequest();
film.setName("");
film.setDescription("Описание");
film.setReleaseDate(LocalDate.of(2024, 12, 31));
film.setDuration(160);
+ Mpa mpa = new Mpa();
+ mpa.setId(5L);
+ mpa.setName("NC-17");
+ film.setMpa(mpa);
+
assertThrows(ValidationException.class, () -> filmController.create(film),
"Ожидалось ValidationException при пустом имени фильма");
}
@@ -249,14 +355,19 @@ public void create_nullFilm_throwsValidationException() {
// Тест успешного удаления фильма по Id
@Test
public void delete_existingFilm_removesFilm() {
- Film film = new Film();
+ NewFilmRequest film = new NewFilmRequest();
film.setName("Фильм для удаления");
film.setDescription("Описание");
film.setReleaseDate(LocalDate.of(2025, 6, 14));
film.setDuration(100);
- filmController.create(film);
- filmController.delete(film.getId());
+ Mpa mpa = new Mpa();
+ mpa.setId(5L);
+ mpa.setName("NC-17");
+ film.setMpa(mpa);
+
+ FilmDto filmForDelete = filmController.create(film);
+ filmController.delete(filmForDelete.getId());
assertEquals(0, filmController.findAll().size(), "Фильм должен быть удален");
}
@@ -271,16 +382,21 @@ public void delete_nonExistentFilm_throwsNotFoundException() {
// Тест получения фильма по Id
@Test
public void getById_existingFilm_returnsFilm() {
- Film film = new Film();
+ NewFilmRequest film = new NewFilmRequest();
film.setName("Фильм для поиска");
film.setDescription("Описание");
film.setReleaseDate(LocalDate.of(2025, 6, 14));
film.setDuration(100);
- filmController.create(film);
- Film foundFilm = filmController.getById(film.getId());
+ Mpa mpa = new Mpa();
+ mpa.setId(5L);
+ mpa.setName("NC-17");
+ film.setMpa(mpa);
- assertEquals(film, foundFilm, "Найденный фильм должен соответствовать созданному");
+ FilmDto createdFilm = filmController.create(film);
+ FilmDto foundFilm = filmController.getById(createdFilm.getId());
+
+ assertEquals(foundFilm, createdFilm, "Найденный фильм должен соответствовать созданному");
}
// Тест получения несуществующего фильма по Id
@@ -290,125 +406,165 @@ public void getById_nonExistentFilm_throwsNotFoundException() {
"Ожидалось NotFoundException при поиске несуществующего фильма");
}
- // Тест добавления лайка фильму
+ // Тест добавления лайка
@Test
public void addLike_validUserAndFilm_addsLike() {
- Film film = new Film();
+ NewFilmRequest film = new NewFilmRequest();
film.setName("Фильм для лайка");
film.setDescription("Описание");
film.setReleaseDate(LocalDate.of(2025, 6, 14));
film.setDuration(100);
- filmController.create(film);
- User user = new User();
+ Mpa mpa = new Mpa();
+ mpa.setId(5L);
+ mpa.setName("NC-17");
+ film.setMpa(mpa);
+
+ FilmDto createdFilm = filmController.create(film);
+
+ NewUserRequest user = new NewUserRequest();
user.setEmail("user@mail.ru");
user.setLogin("user_login");
user.setBirthday(LocalDate.of(1995, 2, 13));
- userStorage.create(user);
- filmController.addLike(film.getId(), user.getId());
+ UserDto createdUser = userController.create(user);
+ filmController.addLike(createdFilm.getId(), createdUser.getId());
- Film updatedFilm = filmController.getById(film.getId());
- assertTrue(updatedFilm.getLikesByUsers().contains(user.getId()), "Лайк должен быть добавлен");
+ FilmDto updatedFilm = filmController.getById(createdFilm.getId());
+ assertTrue(updatedFilm.getLikesByUsers().contains(createdUser.getId()), "Лайк должен быть добавлен");
}
//Тест добавления лайка несуществующему фильму
@Test
public void addLike_nonExistentFilm_throwsNotFoundException() {
- User user = new User();
+ NewUserRequest user = new NewUserRequest();
user.setEmail("user@mail.ru");
user.setLogin("user_login");
user.setBirthday(LocalDate.of(1995, 2, 13));
- userStorage.create(user);
- assertThrows(NotFoundException.class, () -> filmController.addLike(999L, user.getId()),
+ UserDto createdUser = userController.create(user);
+
+ assertThrows(NotFoundException.class, () -> filmController.addLike(999L, createdUser.getId()),
"Ожидалось NotFoundException при лайке несуществующего фильма");
}
// Тест добавления лайка от несуществующего пользователя
@Test
public void addLike_nonExistentUser_throwsNotFoundException() {
- Film film = new Film();
+ NewFilmRequest film = new NewFilmRequest();
film.setName("Фильм для лайка");
film.setDescription("Описание");
film.setReleaseDate(LocalDate.of(2025, 6, 14));
film.setDuration(100);
- filmController.create(film);
- assertThrows(NotFoundException.class, () -> filmController.addLike(film.getId(), 999L),
+ Mpa mpa = new Mpa();
+ mpa.setId(5L);
+ mpa.setName("NC-17");
+ film.setMpa(mpa);
+
+ FilmDto createdFilm = filmController.create(film);
+
+ assertThrows(NotFoundException.class, () -> filmController.addLike(createdFilm.getId(), 999L),
"Ожидалось NotFoundException при лайке от несуществующего пользователя");
}
// Тест успешного удаления лайка
@Test
public void removeLike_existingLike_removesLike() {
- Film film = new Film();
+ NewFilmRequest film = new NewFilmRequest();
film.setName("Фильм для лайка");
film.setDescription("Описание");
film.setReleaseDate(LocalDate.of(2025, 6, 14));
film.setDuration(100);
- filmController.create(film);
- User user = new User();
+ Mpa mpa = new Mpa();
+ mpa.setId(5L);
+ mpa.setName("NC-17");
+ film.setMpa(mpa);
+
+ FilmDto createdFilm = filmController.create(film);
+
+ NewUserRequest user = new NewUserRequest();
user.setEmail("user@mail.ru");
user.setLogin("user_login");
user.setBirthday(LocalDate.of(1995, 2, 13));
- userStorage.create(user);
- filmController.addLike(film.getId(), user.getId());
- filmController.removeLike(film.getId(), user.getId());
+ UserDto createdUser = userController.create(user);
- Film updatedFilm = filmController.getById(film.getId());
- assertFalse(updatedFilm.getLikesByUsers().contains(user.getId()), "Лайк должен быть удален");
+ filmController.addLike(createdFilm.getId(), createdUser.getId());
+ filmController.removeLike(createdFilm.getId(), createdFilm.getId());
+
+ FilmDto updatedFilm = filmController.getById(createdFilm.getId());
+ assertFalse(updatedFilm.getLikesByUsers().contains(createdUser.getId()), "Лайк должен быть удален");
}
// Тест получения популярных фильмов
@Test
public void getPopularFilms_returnsFilmsOrderedByLikes() {
- Film film = new Film();
+ // Создаем 3 фильма
+ NewFilmRequest film = new NewFilmRequest();
film.setName("Фильм для 2 лайков");
film.setDescription("Описание");
film.setReleaseDate(LocalDate.of(2025, 6, 14));
film.setDuration(100);
- filmController.create(film);
- Film film2 = new Film();
+ Mpa mpa = new Mpa();
+ mpa.setId(5L);
+ mpa.setName("NC-17");
+ film.setMpa(mpa);
+
+ FilmDto createdFilm1 = filmController.create(film);
+
+ NewFilmRequest film2 = new NewFilmRequest();
film2.setName("Фильм для 1 лайка");
film2.setDescription("Описание");
film2.setReleaseDate(LocalDate.of(2025, 6, 14));
film2.setDuration(120);
- filmController.create(film2);
- Film film3 = new Film();
+ Mpa mpa2 = new Mpa();
+ mpa2.setId(1L);
+ mpa2.setName("G");
+ film2.setMpa(mpa2);
+
+ FilmDto createdFilm2 = filmController.create(film2);
+
+ NewFilmRequest film3 = new NewFilmRequest();
film3.setName("Фильм без лайков");
film3.setDescription("Описание");
film3.setReleaseDate(LocalDate.of(2025, 5, 14));
film3.setDuration(70);
- filmController.create(film3);
- User user = new User();
+ Mpa mpa3 = new Mpa();
+ mpa3.setId(4L);
+ mpa3.setName("R");
+ film3.setMpa(mpa3);
+
+ FilmDto createdFilm3 = filmController.create(film3);
+
+ // Создаем 2 пользователей
+ NewUserRequest user = new NewUserRequest();
user.setEmail("user@mail.ru");
user.setLogin("user_login");
user.setBirthday(LocalDate.of(1995, 2, 13));
- userStorage.create(user);
+ UserDto createdUser = userController.create(user);
- User user2 = new User();
+ NewUserRequest user2 = new NewUserRequest();
user2.setEmail("user@yandex.ru");
user2.setLogin("user2_login");
user2.setBirthday(LocalDate.of(1996, 2, 14));
- userStorage.create(user2);
+ UserDto createdUser2 = userController.create(user2);
- // film получает 2 лайка
- filmController.addLike(film.getId(), user.getId());
- filmController.addLike(film.getId(), user2.getId());
+ // createdFilm1 получает 2 лайка
+ filmController.addLike(createdFilm1.getId(), createdUser.getId());
+ filmController.addLike(createdFilm1.getId(), createdUser2.getId());
- // film2 получает 1 лайк
- filmController.addLike(film2.getId(), user2.getId());
+ // createdFilm2 получает 1 лайк
+ filmController.addLike(createdFilm2.getId(), createdUser2.getId());
- Collection popularFilms = filmController.getPopularFilms(2);
+ Collection popularFilms = filmController.getPopularFilms(2);
assertEquals(2, popularFilms.size(), "Должны вернуться 2 фильма");
- assertEquals(film.getId(), popularFilms.iterator().next().getId(),
+ assertEquals(createdFilm1.getId(), popularFilms.iterator().next().getId(),
"Первый фильм должен быть самым популярным");
}
}
\ No newline at end of file
diff --git a/src/test/java/ru/yandex/practicum/filmorate/controller/UserControllerTest.java b/src/test/java/ru/yandex/practicum/filmorate/service/UserServiceTest.java
similarity index 61%
rename from src/test/java/ru/yandex/practicum/filmorate/controller/UserControllerTest.java
rename to src/test/java/ru/yandex/practicum/filmorate/service/UserServiceTest.java
index ae1ae21..853d5e4 100644
--- a/src/test/java/ru/yandex/practicum/filmorate/controller/UserControllerTest.java
+++ b/src/test/java/ru/yandex/practicum/filmorate/service/UserServiceTest.java
@@ -1,42 +1,51 @@
-package ru.yandex.practicum.filmorate.controller;
+package ru.yandex.practicum.filmorate.service;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import ru.yandex.practicum.filmorate.controller.UserController;
+import ru.yandex.practicum.filmorate.dto.*;
import ru.yandex.practicum.filmorate.exception.*;
import ru.yandex.practicum.filmorate.model.User;
-import ru.yandex.practicum.filmorate.service.UserService;
-import ru.yandex.practicum.filmorate.storage.user.InMemoryUserStorage;
+import ru.yandex.practicum.filmorate.storage.FriendshipTestStorage;
+import ru.yandex.practicum.filmorate.storage.UserTestStorage;
import ru.yandex.practicum.filmorate.utils.UserValidate;
import java.time.LocalDate;
import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
-class UserControllerTest {
+public class UserServiceTest {
+
+ private final Map users = new HashMap<>();
private UserController userController;
- private InMemoryUserStorage userStorage;
+ private UserTestStorage userTestStorage;
private UserValidate userValidate;
+ private FriendshipTestStorage friendshipTestStorage;
@BeforeEach
public void beforeEach() {
- userStorage = new InMemoryUserStorage();
+ users.clear();
+
+ userTestStorage = new UserTestStorage();
userValidate = new UserValidate();
- userController = new UserController(
- new UserService(userStorage, userValidate)
- );
+ friendshipTestStorage = new FriendshipTestStorage();
+
+ userController = new UserController(new UserService(userTestStorage, userValidate, friendshipTestStorage));
}
// Тест успешного создания пользователя с валидными данными
@Test
public void create_allRequiredFieldsValid_userAddedWithGeneratedId() {
- User user = new User();
+ NewUserRequest user = new NewUserRequest();
user.setEmail("user@mail.ru");
user.setLogin("user_login");
user.setBirthday(LocalDate.of(1995, 2, 13));
- User createdUser = userController.create(user);
+ UserDto createdUser = userController.create(user);
assertNotNull(createdUser.getId(), "Пользователю не был присвоен id");
assertEquals(1, userController.findAll().size(),
@@ -46,13 +55,13 @@ public void create_allRequiredFieldsValid_userAddedWithGeneratedId() {
// Тест создания пользователя с пустым именем (должно подставляться значение login)
@Test
public void create_emptyName_nameEqualsLogin() {
- User user = new User();
+ NewUserRequest user = new NewUserRequest();
user.setEmail("user@mail.ru");
user.setLogin("user_login");
user.setName("");
user.setBirthday(LocalDate.of(1995, 2, 13));
- User createdUser = userController.create(user);
+ UserDto createdUser = userController.create(user);
assertEquals("user_login", createdUser.getName(),
"При пустом имени должно подставляться значение login");
@@ -61,13 +70,13 @@ public void create_emptyName_nameEqualsLogin() {
// Тест создания пользователя с null именем (должно подставляться значение login)
@Test
public void create_nullName_nameEqualsLogin() {
- User user = new User();
+ NewUserRequest user = new NewUserRequest();
user.setEmail("user@mail.ru");
user.setLogin("user_login");
user.setName(null);
user.setBirthday(LocalDate.of(1995, 2, 13));
- User createdUser = userController.create(user);
+ UserDto createdUser = userController.create(user);
assertEquals("user_login", createdUser.getName(),
"При null имени должно подставляться значение login");
@@ -76,7 +85,7 @@ public void create_nullName_nameEqualsLogin() {
// Тест валидации email - не может быть пустым
@Test
public void create_emptyEmail_throwsValidationException() {
- User user = new User();
+ NewUserRequest user = new NewUserRequest();
user.setEmail("");
user.setLogin("login");
user.setBirthday(LocalDate.of(1995, 2, 13));
@@ -88,7 +97,7 @@ public void create_emptyEmail_throwsValidationException() {
// Тест валидации email - должен быть корректным форматом
@Test
public void create_invalidEmailFormat_throwsValidationException() {
- User user = new User();
+ NewUserRequest user = new NewUserRequest();
user.setEmail("invalid-email");
user.setLogin("login");
user.setBirthday(LocalDate.of(1995, 2, 13));
@@ -100,13 +109,13 @@ public void create_invalidEmailFormat_throwsValidationException() {
// Тест уникальности email - нельзя создать пользователя с существующим email
@Test
public void create_duplicateEmail_throwsDuplicateException() {
- User user1 = new User();
+ NewUserRequest user1 = new NewUserRequest();
user1.setEmail("user@mail.ru");
user1.setLogin("login1");
user1.setBirthday(LocalDate.of(1996, 2, 14));
userController.create(user1);
- User user2 = new User();
+ NewUserRequest user2 = new NewUserRequest();
user2.setEmail("user@mail.ru");
user2.setLogin("login2");
user2.setBirthday(LocalDate.of(1995, 2, 13));
@@ -118,7 +127,7 @@ public void create_duplicateEmail_throwsDuplicateException() {
// Тест добавления пользователя с email, равным null
@Test
public void create_nullEmail_throwsValidationException() {
- User user = new User();
+ NewUserRequest user = new NewUserRequest();
user.setEmail(null);
user.setLogin("login");
user.setBirthday(LocalDate.of(1995, 2, 13));
@@ -130,7 +139,7 @@ public void create_nullEmail_throwsValidationException() {
// Тест валидации логина - не может быть пустым
@Test
public void create_emptyLogin_throwsValidationException() {
- User user = new User();
+ NewUserRequest user = new NewUserRequest();
user.setEmail("user@mail.ru");
user.setLogin("");
user.setBirthday(LocalDate.of(1995, 2, 13));
@@ -142,7 +151,7 @@ public void create_emptyLogin_throwsValidationException() {
// Тест валидации логина - не может содержать пробелов
@Test
public void create_loginWithSpaces_throwsValidationException() {
- User user = new User();
+ NewUserRequest user = new NewUserRequest();
user.setEmail("user@mail.ru");
user.setLogin("login with spaces");
user.setBirthday(LocalDate.of(1995, 2, 13));
@@ -154,7 +163,7 @@ public void create_loginWithSpaces_throwsValidationException() {
// Тест валидации даты рождения - не может быть в будущем
@Test
public void create_birthdayInFuture_throwsValidationException() {
- User user = new User();
+ NewUserRequest user = new NewUserRequest();
user.setEmail("user@mail.ru");
user.setLogin("login");
user.setBirthday(LocalDate.now().plusDays(1));
@@ -166,7 +175,7 @@ public void create_birthdayInFuture_throwsValidationException() {
// Тест обновления несуществующего пользователя
@Test
public void update_nonExistentUserId_throwsNotFoundException() {
- User user = new User();
+ UpdateUserRequest user = new UpdateUserRequest();
user.setId(999L);
user.setEmail("user@mail.ru");
user.setLogin("login");
@@ -180,19 +189,19 @@ public void update_nonExistentUserId_throwsNotFoundException() {
@Test
public void update_existingUserWithValidData_fieldsUpdated() {
// Создание пользователя
- User user = new User();
+ NewUserRequest user = new NewUserRequest();
user.setEmail("user@mail.ru");
user.setLogin("login");
user.setBirthday(LocalDate.of(1995, 2, 13));
- User createdUser = userController.create(user);
+ UserDto createdUser = userController.create(user);
// Обновление пользователя
- User updatedUser = new User();
+ UpdateUserRequest updatedUser = new UpdateUserRequest();
updatedUser.setId(createdUser.getId());
updatedUser.setEmail("new@mail.ru");
updatedUser.setLogin("new_login");
updatedUser.setBirthday(LocalDate.of(1995, 2, 13));
- User result = userController.update(updatedUser);
+ UserDto result = userController.update(updatedUser);
assertEquals("new@mail.ru", result.getEmail(), "Email не обновился");
assertEquals("new_login", result.getLogin(), "Логин не обновился");
@@ -203,20 +212,24 @@ public void update_existingUserWithValidData_fieldsUpdated() {
// Тест получения списка всех пользователей
@Test
public void findAll_afterAddingTwoUsers_returnsCollectionSize2() {
- User user1 = new User();
+ NewUserRequest user1 = new NewUserRequest();
user1.setEmail("user1@mail.ru");
user1.setLogin("login1");
user1.setBirthday(LocalDate.of(1995, 2, 13));
- userController.create(user1);
+ UserDto createdUser = userController.create(user1);
- User user2 = new User();
+ NewUserRequest user2 = new NewUserRequest();
user2.setEmail("user2@mail.ru");
user2.setLogin("login2");
user2.setBirthday(LocalDate.of(1996, 2, 14));
- userController.create(user2);
+ UserDto createdUser2 = userController.create(user2);
assertEquals(2, userController.findAll().size(),
"Неверное количество пользователей в списке");
+ assertEquals("login1", createdUser.getLogin(),
+ "Логин 1-го пользователя должен быть login1");
+ assertEquals("login2", createdUser2.getLogin(),
+ "Логин 2-го пользователя должен быть login2");
}
// Тест проверяет невозможность добавить пользователя равного null
@@ -230,13 +243,14 @@ public void create_nullUser_throwsValidationException() {
// Тест успешного удаления пользователя
@Test
public void delete_existingUser_removesUser() {
- User user = new User();
+ NewUserRequest user = new NewUserRequest();
user.setEmail("user@mail.ru");
user.setLogin("user_login");
user.setBirthday(LocalDate.of(1995, 2, 13));
- userController.create(user);
- userController.delete(user.getId());
+ UserDto userForDelete = userController.create(user);
+ userController.delete(userForDelete.getId());
+
assertEquals(0, userController.findAll().size(), "Пользователь должен быть удален");
}
@@ -250,15 +264,15 @@ public void delete_nonExistentUser_throwsNotFoundException() {
// Тест получения пользователя по Id
@Test
public void getById_existingUser_returnsFilm() {
- User user = new User();
+ NewUserRequest user = new NewUserRequest();
user.setEmail("user@mail.ru");
user.setLogin("user_login");
user.setBirthday(LocalDate.of(1995, 2, 13));
- userController.create(user);
- User foundUser = userController.getById(user.getId());
+ UserDto createdUser = userController.create(user);
+ UserDto foundUser = userController.getById(createdUser.getId());
- assertEquals(user, foundUser, "Найденный пользователь должен соответствовать созданному");
+ assertEquals(foundUser, createdUser, "Найденный пользователь должен соответствовать созданному");
}
// Тест получения несуществующего пользователя
@@ -268,146 +282,156 @@ public void getById_nonExistentUser_throwsNotFoundException() {
"Ожидалось NotFoundException при поиске несуществующего пользователя");
}
- // Тест добавления в друзья
+ // Тест успешного добавления в друзья
@Test
public void addFriend_validUserAndFriend() {
- User user = new User();
+ // Создаем основного пользователя
+ NewUserRequest user = new NewUserRequest();
user.setEmail("user@mail.ru");
user.setLogin("user_login");
user.setBirthday(LocalDate.of(1995, 2, 13));
- userController.create(user);
+ UserDto createdUser = userController.create(user);
- User friend = new User();
+ // Создаем друга
+ NewUserRequest friend = new NewUserRequest();
friend.setEmail("user@yandex.ru");
friend.setLogin("friend_login");
friend.setBirthday(LocalDate.of(1996, 2, 14));
- userController.create(friend);
-
- userController.addFriend(user.getId(), friend.getId());
-
- User updatedUser = userController.getById(user.getId());
- User updatedFriend = userController.getById(friend.getId());
- assertAll(
- () -> assertTrue(updatedUser.getFriends().contains(friend.getId()),
- "Друг должен быть в списке пользователя"),
- () -> assertTrue(updatedFriend.getFriends().contains(user.getId()),
- "Пользователь должен быть в списке друга"),
- () -> assertEquals(1, updatedUser.getFriends().size(),
- "Должен быть ровно один друг"),
- () -> assertEquals(1, updatedFriend.getFriends().size(),
- "Должен быть ровно один друг")
- );
+ UserDto userForFriend = userController.create(friend);
+
+ userController.addFriend(createdUser.getId(), userForFriend.getId());
+ UserDto updatedUser = userController.getById(createdUser.getId());
+
+ assertTrue(updatedUser.getFriends().contains(userForFriend.getId()),
+ "Друг должен быть в списке пользователя");
+ assertEquals(1, updatedUser.getFriends().size(),
+ "У пользователя должен быть ровно один друг");
+ }
+
+ // Тест, что дружба является односторонней
+ @Test
+ public void addFriend_shouldCreateOneSidedFriendship() {
+ // Создаем основного пользователя
+ NewUserRequest user = new NewUserRequest();
+ user.setEmail("user@mail.ru");
+ user.setLogin("user_login");
+ user.setBirthday(LocalDate.of(1995, 2, 13));
+ UserDto createdUser = userController.create(user);
+
+ // Создаем друга
+ NewUserRequest friend = new NewUserRequest();
+ friend.setEmail("user@yandex.ru");
+ friend.setLogin("friend_login");
+ friend.setBirthday(LocalDate.of(1996, 2, 14));
+ UserDto userForFriend = userController.create(friend);
+
+ userController.addFriend(createdUser.getId(), userForFriend.getId());
+ UserDto updatedUser = userController.getById(createdUser.getId());
+ UserDto updatedFriend = userController.getById(userForFriend.getId());
+
+ // Проверка, что у пользователя появился друг
+ assertTrue(updatedUser.getFriends().contains(userForFriend.getId()),
+ "Друг должен быть в списке друзей пользователя");
+
+ // Проверка, что у друга нет пользователя в друзьях
+ assertFalse(updatedFriend.getFriends().contains(createdUser.getId()),
+ "У друга не должно быть пользователя в друзьях");
+ assertEquals(0, updatedFriend.getFriends().size(),
+ "У друга не должно быть друзей");
}
// Тест на добавление несуществующего друга
@Test
public void addFriend_nonExistentFriend_throwsNotFounderException() {
- User user = new User();
+ NewUserRequest user = new NewUserRequest();
user.setEmail("user@mail.ru");
user.setLogin("user_login");
user.setBirthday(LocalDate.of(1995, 2, 13));
- userController.create(user);
+ UserDto createdUser = userController.create(user);
- assertThrows(NotFoundException.class, () -> userController.addFriend(user.getId(), 999L),
+ assertThrows(NotFoundException.class, () -> userController.addFriend(createdUser.getId(), 999L),
"Ожидалось NotFoundException при добавлении в друзья несуществующего пользователя");
}
// Тест успешного удаления друга
@Test
public void deleteFriend_existingFriend_removesFriend() {
- User user = new User();
+ NewUserRequest user = new NewUserRequest();
user.setEmail("user@mail.ru");
user.setLogin("user_login");
user.setBirthday(LocalDate.of(1995, 2, 13));
- userController.create(user);
+ UserDto createdUser = userController.create(user);
- User friend = new User();
+ NewUserRequest friend = new NewUserRequest();
friend.setEmail("user@yandex.ru");
friend.setLogin("friend_login");
friend.setBirthday(LocalDate.of(1996, 2, 14));
- userController.create(friend);
-
- // добавляем друга и проверяем, что дружба взаимна у двух пользователей
- userController.addFriend(user.getId(), friend.getId());
- User updatedUser = userController.getById(user.getId());
- User updatedFriend = userController.getById(friend.getId());
-
- assertAll(
- () -> assertTrue(updatedUser.getFriends().contains(friend.getId()),
- "Друг должен быть в списке пользователя"),
- () -> assertTrue(updatedFriend.getFriends().contains(user.getId()),
- "Пользователь должен быть в списке друга"),
- () -> assertEquals(1, updatedUser.getFriends().size(),
- "Должен быть ровно один друг"),
- () -> assertEquals(1, updatedFriend.getFriends().size(),
- "Должен быть ровно один друг")
- );
+ UserDto friendForDelete = userController.create(friend);
- // удаляем друга
- userController.removeFriend(updatedUser.getId(), updatedFriend.getId());
- // Получаем обновленные данные
- User userAfterRemove = userController.getById(user.getId());
- User friendAfterRemove = userController.getById(friend.getId());
+ // добавляем друга и проверяем, что у пользователя появился друг
+ userController.addFriend(createdUser.getId(), friendForDelete.getId());
+ UserDto userWithFriend = userController.getById(createdUser.getId());
+
+ assertTrue(userWithFriend.getFriends().contains(friendForDelete.getId()),
+ "Друг должен быть в списке друзей пользователя");
+
+ // удаляем друга и получаем обновленные данные
+ userController.removeFriend(createdUser.getId(), friendForDelete.getId());
+ UserDto userAfterRemove = userController.getById(createdUser.getId());
// проверяем, что дружба удалилась у обоих
- assertAll(
- () -> assertFalse(userAfterRemove.getFriends().contains(friend.getId()),
- "Друг должен быть удален из списка пользователя"),
- () -> assertFalse(friendAfterRemove.getFriends().contains(user.getId()),
- "Пользователь должен быть удален из списка друга"),
- () -> assertEquals(0, userAfterRemove.getFriends().size(),
- "Список друзей пользователя должен быть пустым"),
- () -> assertEquals(0, friendAfterRemove.getFriends().size(),
- "Список друзей друга должен быть пустым")
- );
+ assertFalse(userAfterRemove.getFriends().contains(friendForDelete.getId()),
+ "Друг должен быть удален из списка пользователя");
+ assertEquals(0, userAfterRemove.getFriends().size(),
+ "Список друзей пользователя должен быть пустым");
}
// Тест на получение всех друзей
@Test
public void getFriends_returnsAllFriends() {
- User user = new User();
+ NewUserRequest user = new NewUserRequest();
user.setEmail("user@mail.ru");
user.setLogin("user_login");
user.setBirthday(LocalDate.of(1995, 2, 13));
- userController.create(user);
+ UserDto createdUser = userController.create(user);
- User friend = new User();
+ NewUserRequest friend = new NewUserRequest();
friend.setEmail("user@yandex.ru");
friend.setLogin("friend_login");
friend.setBirthday(LocalDate.of(1996, 2, 14));
- userController.create(friend);
+ UserDto userForFriend = userController.create(friend);
- User friend2 = new User();
+ NewUserRequest friend2 = new NewUserRequest();
friend2.setEmail("friend@gmail.com");
friend2.setLogin("Mult1k3");
friend2.setBirthday(LocalDate.of(1988, 2, 28));
- userController.create(friend2);
+ UserDto userForFriend2 = userController.create(friend2);
// добавляем пользователей в друзья
- userController.addFriend(user.getId(), friend.getId());
- userController.addFriend(user.getId(), friend2.getId());
- User updatedUser = userController.getById(user.getId());
+ userController.addFriend(createdUser.getId(), userForFriend.getId());
+ userController.addFriend(createdUser.getId(), userForFriend2.getId());
+ UserDto updatedUser = userController.getById(createdUser.getId());
// Получаем список друзей
- Collection friends = userController.getFriends(updatedUser.getId());
+ Collection friends = userController.getFriends(updatedUser.getId());
assertEquals(2, friends.size(), "Должно быть 2 друга");
- assertTrue(friends.stream().anyMatch(f -> f.getId().equals(friend.getId())),
+ assertTrue(friends.stream().anyMatch(f -> f.getId().equals(userForFriend.getId())),
"Друг 1 должен быть в списке");
- assertTrue(friends.stream().anyMatch(f -> f.getId().equals(friend2.getId())),
+ assertTrue(friends.stream().anyMatch(f -> f.getId().equals(userForFriend2.getId())),
"Друг 2 должен быть в списке");
}
// Тест на получение пустого списка, если у пользователя нет друзей
@Test
public void getFriends_userWithNoFriends_returnsEmptyList() {
- User user = new User();
+ NewUserRequest user = new NewUserRequest();
user.setEmail("user@mail.ru");
user.setLogin("user_login");
user.setBirthday(LocalDate.of(1995, 2, 13));
- userController.create(user);
+ UserDto createdUser = userController.create(user);
- Collection friends = userController.getFriends(user.getId());
+ Collection friends = userController.getFriends(createdUser.getId());
assertTrue(friends.isEmpty(), "Список друзей должен быть пустым");
}
@@ -415,34 +439,34 @@ public void getFriends_userWithNoFriends_returnsEmptyList() {
// Тест на получение списка общих друзей у двух пользователей
@Test
public void getCommonFriends_returnsCommonFriends() {
- User user = new User();
+ NewUserRequest user = new NewUserRequest();
user.setEmail("user@mail.ru");
user.setLogin("user_login");
user.setBirthday(LocalDate.of(1995, 2, 13));
- userController.create(user);
+ UserDto createdUser = userController.create(user);
- User user2 = new User();
+ NewUserRequest user2 = new NewUserRequest();
user2.setEmail("user@yandex.ru");
user2.setLogin("friend_login");
user2.setBirthday(LocalDate.of(1996, 2, 14));
- userController.create(user2);
+ UserDto createdUser2 = userController.create(user2);
- User commonFriend = new User();
- commonFriend.setEmail("friend@gmail.com");
- commonFriend.setLogin("Mult1k3");
- commonFriend.setBirthday(LocalDate.of(1988, 2, 28));
- userController.create(commonFriend);
+ NewUserRequest request = new NewUserRequest();
+ request.setEmail("friend@gmail.com");
+ request.setLogin("Mult1k3");
+ request.setBirthday(LocalDate.of(1988, 2, 28));
+ UserDto commonFriend = userController.create(request);
// добавляем общего друга
- userController.addFriend(user.getId(), commonFriend.getId());
- userController.addFriend(user2.getId(), commonFriend.getId());
+ userController.addFriend(createdUser.getId(), commonFriend.getId());
+ userController.addFriend(createdUser2.getId(), commonFriend.getId());
// обновляем данные
- User updatedUser = userController.getById(user.getId());
- User updatedUser2 = userController.getById(user2.getId());
+ UserDto updatedUser = userController.getById(createdUser.getId());
+ UserDto updatedUser2 = userController.getById(createdUser2.getId());
// Проверяем общих друзей
- Collection commonFriends = userController.getCommonFriends(
+ Collection commonFriends = userController.getCommonFriends(
updatedUser.getId(),
updatedUser2.getId()
);
diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/InMemoryFilmStorage.java b/src/test/java/ru/yandex/practicum/filmorate/storage/FilmTestStorage.java
similarity index 72%
rename from src/main/java/ru/yandex/practicum/filmorate/storage/film/InMemoryFilmStorage.java
rename to src/test/java/ru/yandex/practicum/filmorate/storage/FilmTestStorage.java
index 6ad50a0..9138ec9 100644
--- a/src/main/java/ru/yandex/practicum/filmorate/storage/film/InMemoryFilmStorage.java
+++ b/src/test/java/ru/yandex/practicum/filmorate/storage/FilmTestStorage.java
@@ -1,25 +1,31 @@
-package ru.yandex.practicum.filmorate.storage.film;
+package ru.yandex.practicum.filmorate.storage;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
+import org.springframework.context.annotation.Primary;
+import org.springframework.stereotype.Repository;
import ru.yandex.practicum.filmorate.exception.*;
import ru.yandex.practicum.filmorate.model.Film;
import java.util.*;
+import java.util.stream.Collectors;
import static ru.yandex.practicum.filmorate.utils.ControllersUtils.getNextId;
+/**
+ * Утилитарный класс - тестовое хранилище для Film
+ */
+
@Slf4j
-@Component
-public class InMemoryFilmStorage implements FilmStorage {
+@Repository
+@Primary
+public class FilmTestStorage implements FilmStorage {
private final Map films = new HashMap<>();
private final Set filmNames = new HashSet<>();
- // Добавление фильма
+ // Создание фильма
@Override
public Film create(Film film) {
-
checkFilmNamesUniqueness(film.getName());
filmNames.add(film.getName().toLowerCase());
film.setId(getNextId(films.keySet()));
@@ -32,12 +38,11 @@ public Film create(Film film) {
@Override
public void delete(Long id) {
validateFilmId(id);
-
filmNames.remove(films.get(id).getName().toLowerCase());
films.remove(id);
}
- // Изменение фильма
+ // Обновление фильма
@Override
public Film update(Film film) {
validateFilmId(film.getId());
@@ -59,15 +64,25 @@ public Film update(Film film) {
// Получение фильма по id
@Override
- public Film getById(Long id) {
+ public Optional findById(Long id) {
validateFilmId(id);
- return films.get(id);
+ return Optional.ofNullable(films.get(id));
}
// Получение всех фильмов
@Override
public Collection findAll() {
- return List.copyOf(films.values());
+ return films.values();
+ }
+
+ // Получения списка популярных фильмов
+ @Override
+ public Collection findPopularFilms(int count) {
+ return films.values().stream()
+ .sorted((f1, f2) -> Integer.compare(f2.getLikesByUsers().size(),
+ f1.getLikesByUsers().size()))
+ .limit(count)
+ .collect(Collectors.toList());
}
// Вспомогательный метод для проверки на наличие дубликата названия фильма
diff --git a/src/test/java/ru/yandex/practicum/filmorate/storage/FriendshipTestStorage.java b/src/test/java/ru/yandex/practicum/filmorate/storage/FriendshipTestStorage.java
new file mode 100644
index 0000000..e3e774e
--- /dev/null
+++ b/src/test/java/ru/yandex/practicum/filmorate/storage/FriendshipTestStorage.java
@@ -0,0 +1,68 @@
+package ru.yandex.practicum.filmorate.storage;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Primary;
+import org.springframework.stereotype.Repository;
+import ru.yandex.practicum.filmorate.model.User;
+
+import java.util.*;
+
+/**
+ * Утилитарный класс - тестовое хранилище Friendship
+ */
+
+@Slf4j
+@Repository
+@Primary
+public class FriendshipTestStorage implements FriendshipStorage {
+
+ private final Map> friendships = new HashMap<>();
+ private UserTestStorage userTestStorage;
+
+ @Override
+ public void addFriend(Long userId, Long friendId) {
+ if (!friendships.containsKey(userId)) {
+ friendships.put(userId, new HashSet<>());
+ }
+ friendships.get(userId).add(friendId);
+ }
+
+ @Override
+ public void removeFriend(Long userId, Long friendId) {
+ // Получаем множество друзей пользователя
+ Set userFriends = friendships.get(userId);
+
+ // Если множество существует — удаляем друга
+ if (userFriends != null) {
+ userFriends.remove(friendId);
+ }
+ }
+
+ @Override
+ public Collection findAllFriends(Long userId) {
+ Set friendIds = friendships.getOrDefault(userId, Set.of());
+ List friends = new ArrayList<>();
+
+ for (Long friendId : friendIds) {
+ userTestStorage.findById(friendId).ifPresent(friends::add);
+ }
+
+ return friends;
+ }
+
+ @Override
+ public Collection findCommonFriends(Long userId, Long otherId) {
+ Set userFriends = friendships.getOrDefault(userId, Set.of());
+ Set otherFriends = friendships.getOrDefault(otherId, Set.of());
+ List commonFriends = new ArrayList<>();
+
+ // Пересечение множеств
+ for (Long friendId : userFriends) {
+ if (otherFriends.contains(friendId)) {
+ userTestStorage.findById(friendId).ifPresent(commonFriends::add);
+ }
+ }
+
+ return commonFriends;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/yandex/practicum/filmorate/storage/GenreTestStorage.java b/src/test/java/ru/yandex/practicum/filmorate/storage/GenreTestStorage.java
new file mode 100644
index 0000000..30fff9d
--- /dev/null
+++ b/src/test/java/ru/yandex/practicum/filmorate/storage/GenreTestStorage.java
@@ -0,0 +1,60 @@
+package ru.yandex.practicum.filmorate.storage;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Primary;
+import org.springframework.stereotype.Repository;
+import ru.yandex.practicum.filmorate.model.Genre;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * Утилитарный класс - тестовое хранилище Genre
+ */
+
+@Slf4j
+@Repository
+@Primary
+public class GenreTestStorage implements GenreStorage {
+ private final Map genres = new HashMap() {{
+ Genre genre1 = new Genre();
+ genre1.setId(1L);
+ genre1.setName("Комедия");
+ put(1L, genre1);
+
+ Genre genre2 = new Genre();
+ genre2.setId(2L);
+ genre2.setName("Драма");
+ put(2L, genre2);
+ }};
+
+ // Хранение связей жанров и фильма
+ private final Map> filmToGenres = new HashMap<>();
+
+ @Override
+ public Collection findAllGenres() {
+ return new ArrayList<>(genres.values());
+ }
+
+ @Override
+ public Optional findById(Long id) {
+ return Optional.ofNullable(genres.get(id));
+ }
+
+ @Override
+ public Collection findGenreForFilm(Long filmId) {
+ return filmToGenres.getOrDefault(filmId, Set.of())
+ .stream()
+ .map(genres::get)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public void setGenreForFilm(Long filmId, Collection genres) {
+ Set genreIds = genres.stream()
+ .map(Genre::getId)
+ .collect(Collectors.toSet());
+ filmToGenres.put(filmId, genreIds);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/yandex/practicum/filmorate/storage/LikesTestStorage.java b/src/test/java/ru/yandex/practicum/filmorate/storage/LikesTestStorage.java
new file mode 100644
index 0000000..250801d
--- /dev/null
+++ b/src/test/java/ru/yandex/practicum/filmorate/storage/LikesTestStorage.java
@@ -0,0 +1,44 @@
+package ru.yandex.practicum.filmorate.storage;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Primary;
+import org.springframework.stereotype.Repository;
+import ru.yandex.practicum.filmorate.model.Likes;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * Утилитарный класс - тестовое хранилище Likes
+ */
+
+@Slf4j
+@Repository
+@Primary
+public class LikesTestStorage implements LikesStorage {
+
+ private final Map> filmLikes = new HashMap<>();
+
+ @Override
+ public void addLikeToFilm(Long filmId, Long userId) {
+ filmLikes.computeIfAbsent(filmId, k -> new HashSet<>()).add(userId);
+ }
+
+ @Override
+ public void removeLikeFromFilm(Long filmId, Long userId) {
+ filmLikes.getOrDefault(filmId, Set.of()).remove(userId);
+ }
+
+ @Override
+ public Collection getLikesOnFilm(Long filmId) {
+ return filmLikes.getOrDefault(filmId, Set.of())
+ .stream()
+ .map(userId -> {
+ Likes like = new Likes();
+ like.setFilmId(filmId);
+ like.setUserId(userId);
+ return like;
+ })
+ .collect(Collectors.toList());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/yandex/practicum/filmorate/storage/MpaTestStorage.java b/src/test/java/ru/yandex/practicum/filmorate/storage/MpaTestStorage.java
new file mode 100644
index 0000000..048a3ce
--- /dev/null
+++ b/src/test/java/ru/yandex/practicum/filmorate/storage/MpaTestStorage.java
@@ -0,0 +1,54 @@
+package ru.yandex.practicum.filmorate.storage;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Primary;
+import org.springframework.stereotype.Repository;
+import ru.yandex.practicum.filmorate.model.Mpa;
+
+import java.util.*;
+
+/**
+ * Утилитарный класс - тестовое хранилище Mpa
+ */
+
+@Slf4j
+@Repository
+@Primary
+public class MpaTestStorage implements MpaStorage {
+
+ // Добавление MPA-рейтингов в тестовое хранилище Mpa
+ private final Map mpas = new HashMap() {{
+ put(1L, createMpa(1L, "G"));
+ put(2L, createMpa(2L, "PG"));
+ put(3L, createMpa(3L, "PG-13"));
+ put(4L, createMpa(4L, "R"));
+ put(5L, createMpa(5L, "NC-17"));
+ }};
+
+ // Хранение связей фильмов и MPA
+ private final Map filmToMpa = new HashMap<>();
+
+ @Override
+ public Collection findAll() {
+ return mpas.values();
+ }
+
+ @Override
+ public Optional findMpaById(Long id) {
+ return Optional.ofNullable(mpas.get(id));
+ }
+
+ @Override
+ public Optional findMpaFilm(Long filmId) {
+ return Optional.ofNullable(filmToMpa.get(filmId))
+ .flatMap(this::findMpaById);
+ }
+
+ // Вспомогательный метод для добавления рейтингов
+ private Mpa createMpa(Long id, String name) {
+ Mpa mpa = new Mpa();
+ mpa.setId(id);
+ mpa.setName(name);
+ return mpa;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/user/InMemoryUserStorage.java b/src/test/java/ru/yandex/practicum/filmorate/storage/UserTestStorage.java
similarity index 87%
rename from src/main/java/ru/yandex/practicum/filmorate/storage/user/InMemoryUserStorage.java
rename to src/test/java/ru/yandex/practicum/filmorate/storage/UserTestStorage.java
index 6eba877..eccd271 100644
--- a/src/main/java/ru/yandex/practicum/filmorate/storage/user/InMemoryUserStorage.java
+++ b/src/test/java/ru/yandex/practicum/filmorate/storage/UserTestStorage.java
@@ -1,7 +1,8 @@
-package ru.yandex.practicum.filmorate.storage.user;
+package ru.yandex.practicum.filmorate.storage;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
+import org.springframework.context.annotation.Primary;
+import org.springframework.stereotype.Repository;
import ru.yandex.practicum.filmorate.exception.*;
import ru.yandex.practicum.filmorate.model.User;
@@ -9,9 +10,14 @@
import static ru.yandex.practicum.filmorate.utils.ControllersUtils.getNextId;
+/**
+ * Утилитарный класс - тестовое хранилище для User
+ */
+
@Slf4j
-@Component
-public class InMemoryUserStorage implements UserStorage {
+@Repository
+@Primary
+public class UserTestStorage implements UserStorage {
private final Map users = new HashMap<>();
private final Set usersEmail = new HashSet<>();
@@ -62,9 +68,9 @@ public User update(User user) {
// Получение пользователя по id
@Override
- public User getById(Long id) {
+ public Optional findById(Long id) {
validateUserId(id);
- return users.get(id);
+ return Optional.ofNullable(users.get(id));
}
// Получение всех пользователей