films = new HashMap<>();
- private final AtomicLong idGenerator = new AtomicLong();
+ private final FilmServiceInterface filmService;
/**
- * Private constructor to initialize ID generator with starting ID = 1.
+ * Constructor for dependency injection
*/
- private FilmController() {
- idGenerator.set(1);
+ public FilmController(FilmServiceInterface filmService) {
+ this.filmService = filmService;
}
/**
* Handles GET method.
- * Retrieves all films from the storage.
+ *
Retrieves all filmsStorage from the storage.
*
- * @return Collection of all films.
+ * @return Collection of all filmsStorage.
*/
@GetMapping
public Collection getFilms() {
- log.info("GET /films - returning {} films", films.size());
- return films.values();
+ return filmService.getAllFilms();
}
/**
@@ -46,13 +44,7 @@ public Collection getFilms() {
*/
@PostMapping
public Film addFilm(@Valid @RequestBody Film film) {
-
- film.setId(idGenerator.getAndIncrement());
- films.put(film.getId(), film);
-
- log.info("POST /films - Film created: {}", film);
-
- return film;
+ return filmService.addFilm(film);
}
/**
@@ -75,15 +67,46 @@ public Film updateFilm(@Valid @RequestBody Film newFilm) {
throw new ValidationException("Id должен быть указан");
}
- if (films.containsKey(newFilm.getId())) {
- Film oldFilm = films.get(newFilm.getId());
+ return filmService.updateFilm(newFilm);
+ }
- films.put(newFilm.getId(), newFilm);
- log.info("PUT /films - Film updated: {}", oldFilm);
+ /**
+ * Handles GET method.
+ * Retrieves top {@code count} popular films based on likes.
+ *
+ * @param count must be provided by url params, not required default value is 10.
+ * @return collection of films.
+ */
+ @GetMapping("/popular")
+ public Collection getPopular(@RequestParam(required = false, defaultValue = "10") @Positive Long count) {
+ return filmService.getTopFilms(count);
+ }
- return newFilm;
- }
+ /**
+ * Handles PUT method.
+ * Adds like to specific film by specific user. If like from this user is already set - nothing happens.
+ *
+ * @param filmId Film`s id where likes amount should be increased. Must be positive number.
+ * @param userId User`s id who sets like to the film. Must be positive number.
+ * @return Film where like was set.
+ */
+ @PutMapping("/{filmId}/like/{userId}")
+ public Film addLike(@PathVariable @Positive Long filmId,
+ @PathVariable @Positive Long userId) {
+ return filmService.addLike(filmId, userId);
+ }
- throw new NotFoundException("Фильм с id = " + newFilm.getId() + " не найден");
+ /**
+ * Handles PUT method.
+ *
Deletes like from specific film by specific user. If like was never there - nothing happens.
+ *
+ * @param filmId Film`s id where likes amount should be decreased. Must be positive number.
+ * @param userId User`s id who deletes like to the film. Must be positive number.
+ * @return Film where like was deleted.
+ */
+ @DeleteMapping("/{filmId}/like/{userId}")
+ public Film deleteLike(@PathVariable @Positive Long filmId,
+ @PathVariable @Positive Long userId) {
+ return filmService.removeLike(filmId, userId);
}
}
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 ba11211..975e081 100644
--- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java
+++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java
@@ -1,29 +1,29 @@
package ru.yandex.practicum.filmorate.controller;
import jakarta.validation.Valid;
+import jakarta.validation.constraints.Positive;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import ru.yandex.practicum.filmorate.exception.NotFoundException;
import ru.yandex.practicum.filmorate.exception.ValidationException;
import ru.yandex.practicum.filmorate.model.User;
+import ru.yandex.practicum.filmorate.service.user.UserServiceInterface;
import java.util.Collection;
-import java.util.HashMap;
-import java.util.concurrent.atomic.AtomicLong;
@RestController
@RequestMapping("/users")
@Slf4j
public class UserController {
- private final HashMap users = new HashMap<>();
- private final AtomicLong idGenerator = new AtomicLong();
+ private final UserServiceInterface userService;
/**
- * Private constructor to initialize ID generator with starting ID = 1.
+ * Constructor for dependency injection
*/
- private UserController() {
- idGenerator.set(1);
+ public UserController(UserServiceInterface userService) {
+ this.userService = userService;
}
/**
@@ -34,8 +34,7 @@ private UserController() {
*/
@GetMapping
public Collection getUsers() {
- log.info("GET /users - returning {} users", users.size());
- return users.values();
+ return userService.getAllUsers();
}
/**
@@ -46,17 +45,7 @@ public Collection getUsers() {
*/
@PostMapping
public User addUser(@Valid @RequestBody User user) {
-
- user.setId(idGenerator.getAndIncrement());
-
- if (user.getName() == null) {
- user.setName(user.getLogin());
- }
-
- log.info("POST /users - User created: {}", user);
- users.put(user.getId(), user);
-
- return user;
+ return userService.addUser(user);
}
/**
@@ -71,24 +60,70 @@ public User addUser(@Valid @RequestBody User user) {
*
* @return updated user.
* @throws ValidationException if ID is null or not valid
- * @throws NotFoundException if user is not found
+ * @throws NotFoundException if user is not found
*/
@PutMapping
public User updateUser(@Valid @RequestBody User newUser) {
- if (newUser.getId() == null) {
- throw new ValidationException("Id должен быть указан");
- }
-
- if (users.containsKey(newUser.getId())) {
- User oldUser = users.get(newUser.getId());
+ return userService.updateUser(newUser);
+ }
- users.put(newUser.getId(), newUser);
+ /**
+ * Handles GET method.
+ * Return collection of user`s friends.
+ *
+ * @param id user`s id. Must be positive number.
+ * @return collection of users.
+ * @throws NotFoundException if user is not found
+ */
+ @GetMapping("/{id}/friends")
+ public Collection getFriends(@PathVariable @Positive Long id) {
+ return userService.getFriends(id);
+ }
- log.info("PUT /users - User updated: {}", oldUser);
+ /**
+ * Handles GET method.
+ * Return collection of users that are common between two users.
+ *
+ * @param id user`s id. Must be positive number.
+ * @param otherId other user`s id. Must be positive number.
+ * @return collection of users.
+ * @throws NotFoundException if user is not found
+ */
+ @GetMapping("/{id}/friends/common/{otherId}")
+ public Collection getCommonFriends(@PathVariable @Positive Long id,
+ @PathVariable @Positive Long otherId) {
+ return userService.getCommonFriends(id, otherId);
+ }
- return newUser;
- }
+ /**
+ * Handles PUT method.
+ * Creates friendship between two users.
+ * User`s consent is not required.
+ *
+ * @param id user`s id. Sender. Must be positive number.
+ * @param friendId friend`s id. Receiver. Must be positive number.
+ * @throws NotFoundException if user is not found
+ * @throws ValidationException if id equals friendId
+ */
+ @PutMapping("/{id}/friends/{friendId}")
+ public void addFriend(@PathVariable @Positive Long id,
+ @PathVariable @Positive Long friendId) {
+ userService.addFriend(id, friendId);
+ }
- throw new NotFoundException("Пользователь с id = " + newUser.getId() + " не найден");
+ /**
+ * Handles DELETE method.
+ *
Breaks friendship between two users.
+ * User`s consent is not required.
+ *
+ * @param id user`s id. Sender. Must be positive number.
+ * @param friendId friend`s id. Receiver. Must be positive number.
+ * @throws NotFoundException if user is not found
+ */
+ @DeleteMapping("/{id}/friends/{friendId}")
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ public void deleteFriend(@PathVariable @Positive Long id,
+ @PathVariable @Positive Long friendId) {
+ userService.deleteFriend(id, friendId);
}
}
diff --git a/src/main/java/ru/yandex/practicum/filmorate/exception/GlobalExceptionHandler.java b/src/main/java/ru/yandex/practicum/filmorate/exception/GlobalExceptionHandler.java
index b0d53b7..42ce9ce 100644
--- a/src/main/java/ru/yandex/practicum/filmorate/exception/GlobalExceptionHandler.java
+++ b/src/main/java/ru/yandex/practicum/filmorate/exception/GlobalExceptionHandler.java
@@ -3,6 +3,7 @@
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import jakarta.servlet.http.HttpServletRequest;
+import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@@ -11,6 +12,7 @@
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import java.util.HashMap;
import java.util.Map;
@@ -32,23 +34,16 @@ public ResponseEntity handleValidationExceptions(
MethodArgumentNotValidException ex,
HttpServletRequest request) {
- log.warn("Resolved: [{}]", ex.getClass().getName());
- log.debug("Validation error", ex);
+ logInfo(ex, "Validation error from handleHttpMessageNotReadable");
Map errors = new HashMap<>();
-
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
- return ResponseEntity.badRequest().body(
- new ApiError("Bad Request",
- HttpStatus.BAD_REQUEST.value(),
- request.getRequestURI(),
- errors)
- );
+ return createBadRequest(request.getRequestURI(), errors);
}
/**
@@ -60,25 +55,60 @@ public ResponseEntity handleHttpMessageNotReadable(
HttpMessageNotReadableException ex,
HttpServletRequest request) {
- log.warn("Resolved: [{}]", ex.getClass().getName());
- log.debug("Invalid request format", ex);
+ logInfo(ex, "Invalid request format from handleHttpMessageNotReadable");
+ return createBadRequest(request.getRequestURI(), getErrors(ex));
+ }
- return ResponseEntity.badRequest().body(
- new ApiError("Bad Request",
- HttpStatus.BAD_REQUEST.value(),
- request.getRequestURI(),
- getErrors(ex)
- )
- );
+ /**
+ * Handles business logic "not found" scenarios.
+ * Returns 404 status with descriptive message.
+ */
+ @ExceptionHandler(NotFoundException.class)
+ public ResponseEntity handleNotFoundException(
+ NotFoundException ex,
+ HttpServletRequest request) {
+
+ logInfo(ex, "Not Found from handleNotFoundException");
+
+ return createResponseEntity(
+ HttpStatus.NOT_FOUND,
+ request.getRequestURI(),
+ Map.of("error", ex.getMessage()));
+ }
+
+ /**
+ * Handles custom validation exceptions from service layer.
+ * Returns 400 status with business rule violation details.
+ */
+ @ExceptionHandler(ValidationException.class)
+ public ResponseEntity handleValidationException(
+ ValidationException ex,
+ HttpServletRequest request) {
+
+ logInfo(ex, "Validation Error from handleValidationException");
+ return createBadRequest(request.getRequestURI(), Map.of("error", ex.getMessage()));
+ }
+
+ /**
+ * Handles custom validation exceptions from service layer.
+ * Returns 400 status without details dut to security reasons.
+ */
+ @ExceptionHandler({MethodArgumentTypeMismatchException.class, ConstraintViolationException.class})
+ public ResponseEntity handleMismatchAndConstraintViolation(
+ Exception ex,
+ HttpServletRequest request) {
+
+ logInfo(ex, "Bad Request Error from handleMismatchAndConstraintViolation");
+ return createBadRequest(request.getRequestURI(), Map.of("error", "Invalid request format"));
}
/**
* Helper for HttpMessageNotReadableException handler
- * Extracts meaningful error messages from JSON parsing exceptions.
+ * Extracts meaningful error messages from JSON parsing exceptions.
*
- * @return map with error
+ * @return map with errors
*/
- private static Map getErrors(HttpMessageNotReadableException ex) {
+ private Map getErrors(HttpMessageNotReadableException ex) {
String message = "Invalid request format";
if (ex.getCause() instanceof JsonParseException jpe) {
@@ -95,46 +125,34 @@ private static Map getErrors(HttpMessageNotReadableException ex)
}
/**
- * Handles business logic "not found" scenarios.
- * Returns 404 status with descriptive message.
+ * Helper method to construct a ResponseEntity with ApiError as body
*/
- @ExceptionHandler(NotFoundException.class)
- public ResponseEntity handleNotFoundException(
- NotFoundException ex,
- HttpServletRequest request) {
-
- log.warn("Resolved: [{}]", ex.getClass().getName());
- log.debug("Not Found", ex);
- Map errors = new HashMap<>();
-
- return ResponseEntity.status(HttpStatus.NOT_FOUND).body(
- new ApiError("Not Found",
- HttpStatus.NOT_FOUND.value(),
- request.getRequestURI(),
- Map.of("error", ex.getMessage())
+ private ResponseEntity createResponseEntity(HttpStatus status, String URI, Map errors) {
+ return ResponseEntity.status(status).body(
+ new ApiError(
+ status.getReasonPhrase(),
+ status.value(),
+ URI,
+ errors
)
);
}
/**
- * Handles custom validation exceptions from service layer.
- * Returns 400 status with business rule violation details.
+ * Helper method to construct a BadRequest ResponseEntity.
*/
- @ExceptionHandler(ValidationException.class)
- public ResponseEntity handleValidationException(
- ValidationException ex,
- HttpServletRequest request) {
-
- log.warn("Resolved: [{}]", ex.getClass().getName());
- log.debug("Validation Error", ex);
-
- return ResponseEntity.badRequest().body(
- new ApiError("Bad Request",
- HttpStatus.BAD_REQUEST.value(),
- request.getRequestURI(),
- Map.of("error", ex.getMessage())
+ private ResponseEntity createBadRequest(String path, Map errors) {
+ return createResponseEntity(
+ HttpStatus.BAD_REQUEST,
+ path,
+ errors);
+ }
- )
- );
+ /**
+ * Helper method to log info.
+ */
+ private void logInfo(Throwable ex, String info) {
+ log.info("Resolved: [{}] Info: [{}]", ex.getClass().getName(), info);
+ log.debug(info, ex);
}
}
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 ada7009..6fe8c60 100644
--- a/src/main/java/ru/yandex/practicum/filmorate/model/Film.java
+++ b/src/main/java/ru/yandex/practicum/filmorate/model/Film.java
@@ -1,18 +1,19 @@
package ru.yandex.practicum.filmorate.model;
+import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;
-import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import ru.yandex.practicum.filmorate.validation.ValidReleaseDate;
import java.time.LocalDate;
+
/**
- * User.
- * DTO to represent user
+ * Film.
+ *
DTO to represent film
*
*
Properties:
*
@@ -21,13 +22,22 @@
* - description - Film's description, must not be over 200 characters
* - releaseDate - Film`s release date, must be after 1985-01-28
* - duration - Film's duration, must be positive
+ * - likes - Film's likes, initial value is 0, read only
*
*/
+
@Data
@NoArgsConstructor
-@AllArgsConstructor
public class Film {
+ public Film(Long id, String name, String description, LocalDate releaseDate, Integer duration) {
+ this.id = id;
+ this.name = name;
+ this.description = description;
+ this.releaseDate = releaseDate;
+ this.duration = duration;
+ }
+
private Long id;
@NotBlank(message = "Название не может быть пустым")
@@ -41,4 +51,7 @@ public class Film {
@Positive
private Integer duration;
+
+ @JsonProperty(access = JsonProperty.Access.READ_ONLY)
+ private Long likes = 0L;
}
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 ac664a5..e48cf03 100644
--- a/src/main/java/ru/yandex/practicum/filmorate/model/User.java
+++ b/src/main/java/ru/yandex/practicum/filmorate/model/User.java
@@ -2,10 +2,10 @@
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.PastOrPresent;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
-import ru.yandex.practicum.filmorate.validation.ValidBirthday;
import ru.yandex.practicum.filmorate.validation.ValidLogin;
import java.time.LocalDate;
@@ -39,6 +39,6 @@ public class User {
private String name;
- @ValidBirthday
+ @PastOrPresent
private LocalDate birthday;
}
diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java
new file mode 100644
index 0000000..b53d904
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java
@@ -0,0 +1,169 @@
+package ru.yandex.practicum.filmorate.service.film;
+
+import org.springframework.stereotype.Service;
+import ru.yandex.practicum.filmorate.exception.NotFoundException;
+import ru.yandex.practicum.filmorate.exception.ValidationException;
+import ru.yandex.practicum.filmorate.model.Film;
+import ru.yandex.practicum.filmorate.storage.film.FilmStorage;
+import ru.yandex.practicum.filmorate.storage.film.LikeStorage;
+import ru.yandex.practicum.filmorate.storage.user.UserStorage;
+
+import java.util.Collection;
+import java.util.Comparator;
+
+/**
+ * Main service for film operations and business logic.
+ *
+ * Provides comprehensive film management functionality including CRUD operations, likes management,
+ * and popular films retrieval. Delegates likes-specific operations to FilmLikeService.
+ *
+ *
+ * @see Film
+ * @see FilmStorage
+ */
+@Service
+public class FilmService implements FilmServiceInterface {
+
+ private final FilmStorage filmStorage;
+ private final UserStorage userStorage;
+ private final LikeStorage likeStorage;
+
+ /**
+ * Constructor for dependency injection
+ */
+ public FilmService(FilmStorage filmStorage, UserStorage userStorage, LikeStorage likeStorage) {
+ this.filmStorage = filmStorage;
+ this.userStorage = userStorage;
+ this.likeStorage = likeStorage;
+ }
+
+ /**
+ * Retrieves specific film from the storage.
+ *
+ * @param id Film`s id to get.
+ * @return Film.
+ * @throws NotFoundException if film not found
+ */
+ @Override
+ public Film getFilm(Long id) {
+ throwIfNotFound(id);
+ return filmStorage.get(id);
+ }
+
+ /**
+ * Retrieves all filmsStorage from the storage.
+ *
+ * @return Collection of all filmsStorage.
+ */
+ @Override
+ public Collection getAllFilms() {
+ return filmStorage.getAll();
+ }
+
+ /**
+ * Adds film to the storage.
+ *
+ * @param film Film to add.
+ * @return created Film.
+ */
+ @Override
+ public Film addFilm(Film film) {
+ Film returnFilm = filmStorage.add(film);
+ likeStorage.initializeLikesSet(returnFilm.getId());
+ return returnFilm;
+ }
+
+ /**
+ * Updates film in the storage.
+ *
+ * @param film Film to update.
+ * @return updated Film.
+ * @throws NotFoundException if film not found
+ */
+ @Override
+ public Film updateFilm(Film film) {
+ if (film.getId() == null) {
+ throw new ValidationException("Id должен быть указан");
+ }
+ throwIfNotFound(film.getId());
+
+ return filmStorage.update(film);
+ }
+
+ /**
+ * Deletes film from the storage.
+ *
+ * @param id Film`s id to delete.
+ * @throws NotFoundException if film not found
+ */
+ @Override
+ public void deleteFilm(Long id) {
+ throwIfNotFound(id);
+ filmStorage.remove(id);
+ likeStorage.clearLikesSet(id);
+ }
+
+ /**
+ * Adds like to the film.
+ *
+ * @param filmId Film`s id to add like.
+ * @param userId User`s id to set like.
+ * @return Film where likes where added.
+ * @throws NotFoundException if film or user not found
+ */
+ @Override
+ public Film addLike(Long filmId, Long userId) {
+ throwIfNotFound(filmId);
+ if (!userStorage.contains(userId)) {
+ throw new NotFoundException("Пользователь с id = " + userId + " не найден");
+ }
+
+ Long newLikes = likeStorage.addLike(filmId, userId);
+ filmStorage.get(filmId).setLikes(newLikes);
+
+ return filmStorage.get(filmId);
+ }
+
+ /**
+ * Deletes like from the film.
+ *
+ * @param filmId Film`s id to delete like.
+ * @param userId User`s id to delete like.
+ * @return Film where likes where deleted.
+ * @throws NotFoundException if film or user not found
+ */
+ @Override
+ public Film removeLike(Long filmId, Long userId) {
+ throwIfNotFound(filmId);
+ if (!userStorage.contains(userId)) {
+ throw new NotFoundException("Пользователь с id = " + userId + " не найден");
+ }
+
+ Long newLikes = likeStorage.deleteLike(filmId, userId);
+ filmStorage.get(filmId).setLikes(newLikes);
+
+ return filmStorage.get(filmId);
+ }
+
+ /**
+ * Return top ranked films.
+ *
+ * @param count limit to collection.
+ * @return collection of Films.
+ */
+ @Override
+ public Collection getTopFilms(Long count) {
+
+ return filmStorage.getAll().stream()
+ .sorted(Comparator.comparingLong(Film::getLikes).reversed())
+ .limit(count)
+ .toList();
+ }
+
+ @Override
+ public void throwIfNotFound(Long id) {
+ if (!filmStorage.contains(id)) {
+ throw new NotFoundException("Фильм с id = " + id + " не найден");
+ }
+ }
+}
diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmServiceInterface.java b/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmServiceInterface.java
new file mode 100644
index 0000000..69bf087
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmServiceInterface.java
@@ -0,0 +1,26 @@
+package ru.yandex.practicum.filmorate.service.film;
+
+import ru.yandex.practicum.filmorate.model.Film;
+
+import java.util.Collection;
+
+public interface FilmServiceInterface {
+
+ Film getFilm(Long id);
+
+ Collection getAllFilms();
+
+ Film addFilm(Film film);
+
+ Film updateFilm(Film film);
+
+ void deleteFilm(Long id);
+
+ Film addLike(Long filmId, Long userId);
+
+ Film removeLike(Long filmId, Long userId);
+
+ Collection getTopFilms(Long count);
+
+ void throwIfNotFound(Long id);
+}
diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/user/UserService.java b/src/main/java/ru/yandex/practicum/filmorate/service/user/UserService.java
new file mode 100644
index 0000000..eff4749
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/service/user/UserService.java
@@ -0,0 +1,194 @@
+package ru.yandex.practicum.filmorate.service.user;
+
+import org.springframework.stereotype.Service;
+import ru.yandex.practicum.filmorate.exception.NotFoundException;
+import ru.yandex.practicum.filmorate.exception.ValidationException;
+import ru.yandex.practicum.filmorate.model.User;
+import ru.yandex.practicum.filmorate.storage.user.FriendShipStorage;
+import ru.yandex.practicum.filmorate.storage.user.UserStorage;
+
+import java.util.Collection;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Main service for user operations and business logic.
+ *
+ * Provides comprehensive user management functionality including CRUD operations, friendship management,
+ * and social features. Delegates friendship-specific operations to FriendShipService.
+ *
+ *
+ * @see User
+ * @see UserStorage
+ */
+@Service
+public class UserService implements UserServiceInterface {
+
+ private final UserStorage userStorage;
+ private final FriendShipStorage friendShipStorage;
+
+ /**
+ * Constructor for dependency injection
+ */
+ public UserService(UserStorage userStorage, FriendShipStorage friendShipStorage) {
+ this.userStorage = userStorage;
+ this.friendShipStorage = friendShipStorage;
+ }
+
+ /**
+ * @param id ID of the user to be deleted. Must be positive and exist in the system.
+ * @return User from storage.
+ * @throws NotFoundException if user is not found.
+ */
+ @Override
+ public User getUser(Long id) {
+ throwIfNotFound(id);
+ return userStorage.get(id);
+ }
+
+ /**
+ * @return Collection of all users in storage.
+ */
+ @Override
+ public Collection getAllUsers() {
+ return userStorage.getAll();
+ }
+
+ /**
+ * Add user to the storage.
+ * If user`s name is null, then login is set as a name.
+ *
Initializes set for friends.
+ *
+ * @param user User to create.
+ * @return created User.
+ */
+ @Override
+ public User addUser(User user) {
+
+ if (user.getName() == null || user.getName().isBlank()) {
+ user.setName(user.getLogin());
+ }
+
+ User returnUser = userStorage.add(user);
+ friendShipStorage.initializeFriendsSet(returnUser.getId());
+
+ return returnUser;
+ }
+
+ /**
+ * Updates user in the storage.
+ *
+ * @param user User to replace. User`s id must be in the system.
+ * @return updated User.
+ * @throws NotFoundException if user is not found
+ * @throws ValidationException if ID is null
+ */
+ @Override
+ public User updateUser(User user) {
+ if (user.getId() == null) {
+ throw new ValidationException("Id должен быть указан");
+ }
+ throwIfNotFound(user.getId());
+
+ return userStorage.update(user);
+ }
+
+ /**
+ * Deletes user from the storage.
+ * Deletes user as from friends of all users.
+ *
+ * @param id ID of the user to be deleted. Must be positive and exist in the system.
+ * @throws NotFoundException if user not found
+ */
+ @Override
+ public void deleteUser(Long id) {
+ throwIfNotFound(id);
+
+ Set friendIds = friendShipStorage.getFriends(id);
+ friendIds.forEach(friendId -> friendShipStorage.getFriends(friendId).remove(id));
+ friendShipStorage.clearFriendsSet(id);
+
+ userStorage.remove(id);
+ }
+
+ /**
+ * Create friendship between users. Only one side needs to confirm friendship.
+ *
+ * @param senderId ID of the user that sends friendship request. Must be positive and exist in the system.
+ * @param receiverId ID of the user that gets friendship request. Must be positive and exist in the system.
+ * @throws NotFoundException if user is not found
+ * @throws ValidationException if senderId equals receiverId
+ */
+ @Override
+ public void addFriend(Long senderId, Long receiverId) {
+ if (Objects.equals(receiverId, senderId)) {
+ throw new ValidationException("Сам себя не добавишь - никто не добавит");
+ }
+
+ throwIfNotFound(senderId);
+ throwIfNotFound(receiverId);
+
+ friendShipStorage.addFriend(senderId, receiverId);
+ friendShipStorage.addFriend(receiverId, senderId);
+ }
+
+ /**
+ * Deletes friendship between users without. Only one side needs to confirm deletion of friendship.
+ * FriendShip stops if any of users breaks it.
+ *
+ * @param senderId ID of the user from where friend should be deleted. Must be positive and exist in the system.
+ * @param receiverId ID of the user that is being deleted. Must be positive and exist in the system.
+ * @throws NotFoundException if user is not found
+ */
+ @Override
+ public void deleteFriend(Long senderId, Long receiverId) {
+ throwIfNotFound(senderId);
+ throwIfNotFound(receiverId);
+
+ friendShipStorage.deleteFriend(senderId, receiverId);
+ friendShipStorage.deleteFriend(receiverId, senderId);
+ }
+
+ /**
+ * @param id ID of the user to get friends. Must be positive and exist in the system.
+ * @return Collection of all user`s friends.
+ * @throws NotFoundException if user is not found
+ */
+ @Override
+ public Collection getFriends(Long id) {
+ throwIfNotFound(id);
+
+ return friendShipStorage.getFriends(id).stream()
+ .map(userStorage::get)
+ .toList();
+ }
+
+ /**
+ * Searches for common friends between two users.
+ *
+ * @param id ID of the first user. Must be positive and exist in the system.
+ * @param otherId ID of the second user. Must be positive and exist in the system.
+ * @return Collection of all common friends between users.
+ * @throws NotFoundException if user is not found
+ */
+ @Override
+ public Collection getCommonFriends(Long id, Long otherId) {
+ throwIfNotFound(id);
+ throwIfNotFound(otherId);
+
+ Set friends = friendShipStorage.getFriends(id);
+ Set otherFriends = friendShipStorage.getFriends(otherId);
+
+ return friends.stream()
+ .filter(otherFriends::contains)
+ .map(userStorage::get)
+ .toList();
+ }
+
+ @Override
+ public void throwIfNotFound(Long id) {
+ if (!userStorage.contains(id)) {
+ throw new NotFoundException("Пользователь с id = " + id + " не найден");
+ }
+ }
+}
diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/user/UserServiceInterface.java b/src/main/java/ru/yandex/practicum/filmorate/service/user/UserServiceInterface.java
new file mode 100644
index 0000000..6efaa94
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/service/user/UserServiceInterface.java
@@ -0,0 +1,28 @@
+package ru.yandex.practicum.filmorate.service.user;
+
+import ru.yandex.practicum.filmorate.model.User;
+
+import java.util.Collection;
+
+public interface UserServiceInterface {
+
+ User getUser(Long id);
+
+ Collection getAllUsers();
+
+ User addUser(User user);
+
+ User updateUser(User user);
+
+ void deleteUser(Long id);
+
+ void addFriend(Long senderId, Long receiverId);
+
+ void deleteFriend(Long senderId, Long receiverId);
+
+ Collection getFriends(Long id);
+
+ Collection getCommonFriends(Long id, Long otherId);
+
+ void throwIfNotFound(Long id);
+}
diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/BasicStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/BasicStorage.java
new file mode 100644
index 0000000..0d5f7d7
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/storage/BasicStorage.java
@@ -0,0 +1,21 @@
+package ru.yandex.practicum.filmorate.storage;
+
+import java.util.Collection;
+
+public interface BasicStorage {
+
+ T add(T t);
+
+ T update(T t);
+
+ T remove(Long id);
+
+ T get(Long id);
+
+ Collection getAll();
+
+ boolean contains(Long id);
+
+ int size();
+
+}
diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmStorage.java
new file mode 100644
index 0000000..6e55005
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmStorage.java
@@ -0,0 +1,7 @@
+package ru.yandex.practicum.filmorate.storage.film;
+
+import ru.yandex.practicum.filmorate.model.Film;
+import ru.yandex.practicum.filmorate.storage.BasicStorage;
+
+public interface FilmStorage extends BasicStorage {
+}
diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/InMemoryFilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/film/InMemoryFilmStorage.java
new file mode 100644
index 0000000..82b593c
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/storage/film/InMemoryFilmStorage.java
@@ -0,0 +1,71 @@
+package ru.yandex.practicum.filmorate.films.film;
+
+import org.springframework.stereotype.Component;
+import ru.yandex.practicum.filmorate.model.Film;
+import ru.yandex.practicum.filmorate.storage.film.FilmStorage;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * In-memory implementation of film films.
+ *
+ * Provides thread-safe films for Film objects using ConcurrentHashMap with atomic ID generation.
+ * Suitable for development and testing environments without persistent films requirements.
+ *
+ *
+ * @see Film
+ * @see FilmStorage
+ */
+@Component
+public class InMemoryFilmStorage implements FilmStorage {
+
+ private final Map films;
+ private final AtomicLong idGenerator;
+
+ public InMemoryFilmStorage() {
+ this.films = new ConcurrentHashMap<>();
+ this.idGenerator = new AtomicLong(1);
+ }
+
+ @Override
+ public Film add(Film film) {
+ film.setId(idGenerator.getAndIncrement());
+ films.put(film.getId(), film);
+
+ return films.get(film.getId());
+ }
+
+ @Override
+ public Film update(Film newFilm) {
+ films.put(newFilm.getId(), newFilm);
+
+ return films.get(newFilm.getId());
+ }
+
+ @Override
+ public Film remove(Long id) {
+ return films.remove(id);
+ }
+
+ @Override
+ public Film get(Long id) {
+ return films.get(id);
+ }
+
+ @Override
+ public Collection getAll() {
+ return List.copyOf(films.values());
+ }
+
+ @Override
+ public boolean contains(Long id) {
+ return films.containsKey(id);
+ }
+
+ @Override
+ public int size() {
+ return films.size();
+ }
+}
diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/InMemoryLikeStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/film/InMemoryLikeStorage.java
new file mode 100644
index 0000000..5cf8a84
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/storage/film/InMemoryLikeStorage.java
@@ -0,0 +1,42 @@
+package ru.yandex.practicum.filmorate.storage.film;
+
+import org.springframework.stereotype.Component;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Component
+public class InMemoryLikeStorage implements LikeStorage {
+
+ private final Map> likes;
+
+ public InMemoryLikeStorage() {
+ this.likes = new ConcurrentHashMap<>();
+ }
+
+ @Override
+ public void initializeLikesSet(Long id) {
+ likes.put(id, new HashSet<>());
+ }
+
+ @Override
+ public void clearLikesSet(Long id) {
+ likes.remove(id);
+ }
+
+ @Override
+ public Long addLike(Long filmId, Long userId) {
+ likes.get(filmId).add(userId);
+
+ return (long) likes.get(filmId).size();
+ }
+
+ @Override
+ public Long deleteLike(Long filmId, Long userId) {
+ likes.get(filmId).remove(userId);
+
+ return (long) likes.get(filmId).size();
+ }
+}
diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/LikeStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/film/LikeStorage.java
new file mode 100644
index 0000000..c2cc79e
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/storage/film/LikeStorage.java
@@ -0,0 +1,12 @@
+package ru.yandex.practicum.filmorate.storage.film;
+
+public interface LikeStorage {
+
+ void initializeLikesSet(Long id);
+
+ void clearLikesSet(Long id);
+
+ Long addLike(Long filmId, Long userId);
+
+ Long deleteLike(Long filmId, Long userId);
+}
diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/user/FriendShipStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/user/FriendShipStorage.java
new file mode 100644
index 0000000..d54df19
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/storage/user/FriendShipStorage.java
@@ -0,0 +1,16 @@
+package ru.yandex.practicum.filmorate.storage.user;
+
+import java.util.Set;
+
+public interface FriendShipStorage {
+
+ void initializeFriendsSet(Long id);
+
+ void clearFriendsSet(Long id);
+
+ void addFriend(Long senderId, Long receiverId);
+
+ void deleteFriend(Long senderId, Long receiverId);
+
+ Set getFriends(Long id);
+}
diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/user/InMemoryFriendShipStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/user/InMemoryFriendShipStorage.java
new file mode 100644
index 0000000..88cb81d
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/storage/user/InMemoryFriendShipStorage.java
@@ -0,0 +1,43 @@
+package ru.yandex.practicum.filmorate.storage.user;
+
+import org.springframework.stereotype.Component;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Component
+public class InMemoryFriendShipStorage implements FriendShipStorage {
+
+ private final Map> friendships;
+
+ public InMemoryFriendShipStorage() {
+ this.friendships = new ConcurrentHashMap<>();
+ }
+
+ @Override
+ public void initializeFriendsSet(Long id) {
+ friendships.put(id, new HashSet<>());
+ }
+
+ @Override
+ public void clearFriendsSet(Long id) {
+ friendships.remove(id);
+ }
+
+ @Override
+ public void addFriend(Long senderId, Long receiverId) {
+ friendships.get(senderId).add(receiverId);
+ }
+
+ @Override
+ public void deleteFriend(Long senderId, Long receiverId) {
+ friendships.get(senderId).remove(receiverId);
+ }
+
+ @Override
+ public Set getFriends(Long id) {
+ return friendships.get(id);
+ }
+}
diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/user/InMemoryUserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/user/InMemoryUserStorage.java
new file mode 100644
index 0000000..4b26fae
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/storage/user/InMemoryUserStorage.java
@@ -0,0 +1,72 @@
+package ru.yandex.practicum.filmorate.storage.user;
+
+import org.springframework.stereotype.Component;
+import ru.yandex.practicum.filmorate.model.User;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * In-memory implementation of user users.
+ *
+ * Provides thread-safe users for User objects using ConcurrentHashMap with atomic ID generation.
+ * Suitable for development and testing environments without persistent users requirements.
+ *
+ *
+ * @see User
+ * @see UserStorage
+ */
+@Component
+public class InMemoryUserStorage implements UserStorage {
+
+ private final Map users;
+ private final AtomicLong idGenerator;
+
+ public InMemoryUserStorage() {
+ this.users = new ConcurrentHashMap<>();
+ this.idGenerator = new AtomicLong(1);
+ }
+
+ @Override
+ public User add(User user) {
+ user.setId(idGenerator.getAndIncrement());
+ users.put(user.getId(), user);
+
+ return users.get(user.getId());
+ }
+
+ @Override
+ public User update(User newUser) {
+ users.put(newUser.getId(), newUser);
+
+ return users.get(newUser.getId());
+ }
+
+ @Override
+ public User remove(Long id) {
+ return users.remove(id);
+ }
+
+ @Override
+ public User get(Long id) {
+ return users.get(id);
+ }
+
+ @Override
+ public Collection getAll() {
+ return List.copyOf(users.values());
+ }
+
+ @Override
+ public boolean contains(Long id) {
+ return users.containsKey(id);
+ }
+
+ @Override
+ public int size() {
+ return users.size();
+ }
+}
diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserStorage.java
new file mode 100644
index 0000000..ae4c687
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserStorage.java
@@ -0,0 +1,7 @@
+package ru.yandex.practicum.filmorate.storage.user;
+
+import ru.yandex.practicum.filmorate.model.User;
+import ru.yandex.practicum.filmorate.storage.BasicStorage;
+
+public interface UserStorage extends BasicStorage {
+}
diff --git a/src/main/java/ru/yandex/practicum/filmorate/validation/BirthdayValidator.java b/src/main/java/ru/yandex/practicum/filmorate/validation/BirthdayValidator.java
deleted file mode 100644
index 0dab21a..0000000
--- a/src/main/java/ru/yandex/practicum/filmorate/validation/BirthdayValidator.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package ru.yandex.practicum.filmorate.validation;
-
-import jakarta.validation.ConstraintValidator;
-import jakarta.validation.ConstraintValidatorContext;
-
-import java.time.LocalDate;
-
-/**
- * Custom validator for {@link ValidBirthday}
- */
-public class BirthdayValidator implements ConstraintValidator {
-
- /**
- * Validates that the birthday is not in the future.
- *
- * @param localDate the date to validate
- * @param constraintValidatorContext validation context
- * @return true if date is null or in past or present, false otherwise
- */
- @Override
- public boolean isValid(LocalDate localDate, ConstraintValidatorContext constraintValidatorContext) {
- if (localDate == null) {
- return true;
- }
-
- return !localDate.isAfter(LocalDate.now());
- }
-}
diff --git a/src/main/java/ru/yandex/practicum/filmorate/validation/ValidBirthday.java b/src/main/java/ru/yandex/practicum/filmorate/validation/ValidBirthday.java
deleted file mode 100644
index 836e580..0000000
--- a/src/main/java/ru/yandex/practicum/filmorate/validation/ValidBirthday.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package ru.yandex.practicum.filmorate.validation;
-
-import jakarta.validation.Constraint;
-import jakarta.validation.Payload;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Validates that a birthdate is not in the future.
- *
- * Applied to {@code LocalDate} fields to ensure the date represents a past or present date.
- * Used primarily for user birthdate validation.
- */
-@Target({ElementType.FIELD})
-@Retention(RetentionPolicy.RUNTIME)
-@Constraint(validatedBy = BirthdayValidator.class)
-public @interface ValidBirthday {
- String message() default "Дата рождения не может быть в будущем";
-
- Class>[] groups() default {};
-
- Class extends Payload>[] payload() default {};
-}
\ No newline at end of file
diff --git a/application.properties b/src/main/resources/application-dev.properties
similarity index 68%
rename from application.properties
rename to src/main/resources/application-dev.properties
index f93a4cf..b50b2c2 100644
--- a/application.properties
+++ b/src/main/resources/application-dev.properties
@@ -1,9 +1,12 @@
spring.application.name=Filmorate
-server.port=8080
+server.port=8081
logging.file.path=./logs/
logging.logback.rollingpolicy.file-name-pattern=./logs/spring.%d{yyyy-MM-dd}.%i.log
logging.logback.rollingpolicy.max-file-size=10MB
logging.logback.rollingpolicy.total-size-cap=50MB
logging.logback.rollingpolicy.clean-history-on-start=true
-spring.output.ansi.enabled=ALWAYS
\ No newline at end of file
+spring.output.ansi.enabled=ALWAYS
+
+logging.level.ru.yandex.practicum.filmorate=INFO
+logging.level.org.zalando.logbook=TRACE
\ No newline at end of file
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
new file mode 100644
index 0000000..45001af
--- /dev/null
+++ b/src/main/resources/application.properties
@@ -0,0 +1,12 @@
+spring.application.name=Filmorate
+server.port=8080
+
+logging.file.path=./logs/
+logging.logback.rollingpolicy.file-name-pattern=./logs/spring.%d{yyyy-MM-dd}.%i.log
+logging.logback.rollingpolicy.max-file-size=10MB
+logging.logback.rollingpolicy.total-size-cap=40MB
+logging.logback.rollingpolicy.clean-history-on-start=true
+spring.output.ansi.enabled=ALWAYS
+
+logging.level.ru.yandex.practicum.filmorate=WARN
+logging.level.org.zalando.logbook=WARN
\ No newline at end of file
diff --git a/src/test/java/ru/yandex/practicum/filmorate/controller/ControllerTest.java b/src/test/java/ru/yandex/practicum/filmorate/controller/ControllerTest.java
index e7b1e9f..efcd4f2 100644
--- a/src/test/java/ru/yandex/practicum/filmorate/controller/ControllerTest.java
+++ b/src/test/java/ru/yandex/practicum/filmorate/controller/ControllerTest.java
@@ -2,10 +2,8 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.web.servlet.MockMvc;
-@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class ControllerTest {
@Autowired
@@ -13,7 +11,4 @@ public class ControllerTest {
@Autowired
protected ObjectMapper objectMapper;
-
- protected String json;
-
}
diff --git a/src/test/java/ru/yandex/practicum/filmorate/controller/FilmControllerTest.java b/src/test/java/ru/yandex/practicum/filmorate/controller/FilmControllerTest.java
index 75ad2aa..ce4fdbf 100644
--- a/src/test/java/ru/yandex/practicum/filmorate/controller/FilmControllerTest.java
+++ b/src/test/java/ru/yandex/practicum/filmorate/controller/FilmControllerTest.java
@@ -1,15 +1,19 @@
package ru.yandex.practicum.filmorate.controller;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
+import ru.yandex.practicum.filmorate.exception.NotFoundException;
import ru.yandex.practicum.filmorate.model.Film;
+import ru.yandex.practicum.filmorate.service.film.FilmService;
import java.time.LocalDate;
+import java.util.Collections;
+import java.util.List;
+import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -17,39 +21,125 @@
@WebMvcTest(FilmController.class)
public class FilmControllerTest extends ControllerTest {
+ @MockBean
+ private FilmService filmService;
- private void addTestFilm() throws Exception {
- mockMvc.perform(post("/films")
- .contentType(MediaType.APPLICATION_JSON)
- .content(json));
- }
+ @Nested
+ class FilmControllerGetTest {
- @BeforeEach
- public void getJsonString() throws JsonProcessingException {
+ @Test
+ public void shouldGetFilms() throws Exception {
+ Film film1 = new Film(1L, "Film One", "Description One",
+ LocalDate.of(2020, 1, 1), 120);
+ Film film2 = new Film(2L, "Film Two", "Description Two",
+ LocalDate.of(2021, 1, 1), 130);
- Film film = new Film(2L,
- "TestFilm",
- "TestDescription",
- LocalDate.of(2000, 1, 1),
- 120);
+ List films = List.of(film1, film2);
- json = objectMapper.writeValueAsString(film);
- }
+ // Настраиваем мок сервиса
+ when(filmService.getAllFilms()).thenReturn(films);
- @Nested
- class FilmControllerGetTest {
+ mockMvc.perform(get("/films"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$").isArray())
+ .andExpect(jsonPath("$.length()").value(2))
+ .andExpect(jsonPath("$[0].id").value(1))
+ .andExpect(jsonPath("$[0].name").value("Film One"))
+ .andExpect(jsonPath("$[1].id").value(2))
+ .andExpect(jsonPath("$[1].name").value("Film Two"));
+
+ verify(filmService, times(1)).getAllFilms();
+ }
@Test
- public void shouldGetFilms() throws Exception {
- addTestFilm();
- addTestFilm();
+ public void shouldGetEmptyList() throws Exception {
+
+ when(filmService.getAllFilms()).thenReturn(Collections.emptyList());
mockMvc.perform(get("/films"))
.andExpect(status().isOk())
- .andExpect(jsonPath("$[0].id").value("1"))
- .andExpect(jsonPath("$[0].name").value("TestFilm"))
- .andExpect(jsonPath("$[1].id").value("2"))
- .andExpect(jsonPath("$[1].name").value("TestFilm"));
+ .andExpect(jsonPath("$").isArray())
+ .andExpect(jsonPath("$.length()").value(0));
+
+ verify(filmService, times(1)).getAllFilms();
+ }
+
+ @Test
+ public void shouldGetPopularFilms() throws Exception {
+ Film film1 = new Film(1L, "Film One", "Description One",
+ LocalDate.of(2020, 1, 1), 120);
+ Film film2 = new Film(2L, "Film Two", "Description Two",
+ LocalDate.of(2021, 1, 1), 130);
+ film1.setLikes(2L);
+ film2.setLikes(1L);
+ List films = List.of(film1, film2);
+
+ // Настраиваем мок сервиса
+ when(filmService.getTopFilms(10L)).thenReturn(films);
+
+ mockMvc.perform(get("/films/popular"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$").isArray())
+ .andExpect(jsonPath("$.length()").value(2))
+ .andExpect(jsonPath("$[0].id").value(1))
+ .andExpect(jsonPath("$[0].likes").value(2))
+ .andExpect(jsonPath("$[1].id").value(2))
+ .andExpect(jsonPath("$[1].likes").value(1));
+
+ verify(filmService, times(1)).getTopFilms(10L);
+ }
+
+ @Test
+ public void shouldGetOnlyCountPopularFilms() throws Exception {
+ Film film1 = new Film(1L, "Film One", "Description One",
+ LocalDate.of(2020, 1, 1), 120);
+ Film film2 = new Film(2L, "Film Two", "Description Two",
+ LocalDate.of(2021, 1, 1), 130);
+ film1.setLikes(2L);
+ film2.setLikes(1L);
+
+ List films = List.of(film1, film2);
+
+ // Настраиваем мок сервиса
+ when(filmService.getTopFilms(2L)).thenReturn(films);
+
+ mockMvc.perform(get("/films/popular").param("count", "2"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$").isArray())
+ .andExpect(jsonPath("$.length()").value(2))
+ .andExpect(jsonPath("$[0].id").value(1))
+ .andExpect(jsonPath("$[0].likes").value(2))
+ .andExpect(jsonPath("$[1].id").value(2))
+ .andExpect(jsonPath("$[1].likes").value(1));
+
+ verify(filmService, times(1)).getTopFilms(2L);
+ }
+
+ @Test
+ public void shouldReturn400WhenInvalidCountParam() throws Exception {
+ mockMvc.perform(get("/films/popular")
+ .param("count", "not_a_number"))
+ .andExpect(status().isBadRequest());
+
+ verify(filmService, never()).getTopFilms(any());
+ }
+
+ @Test
+ public void shouldReturn400WhenNegativeCount() throws Exception {
+ mockMvc.perform(get("/films/popular")
+ .param("count", "-5"))
+ .andExpect(status().isBadRequest());
+
+ verify(filmService, never()).getTopFilms(any());
+ }
+
+ @Test
+ public void shouldReturn400WhenZeroCount() throws Exception {
+ mockMvc.perform(get("/films/popular")
+ .param("count", "0"))
+ .andExpect(status().isBadRequest());
+
+ verify(filmService, never()).getTopFilms(any());
}
}
@@ -58,20 +148,96 @@ class FilmControllerPostTest {
@Test
public void shouldCreateFilm() throws Exception {
+ Film filmToCreate = new Film(null, "TestFilm", "TestDescription",
+ LocalDate.of(2000, 1, 1), 120);
+
+ Film createdFilm = new Film(1L, "TestFilm", "TestDescription",
+ LocalDate.of(2000, 1, 1), 120);
+
+ when(filmService.addFilm(any(Film.class))).thenReturn(createdFilm);
+
+ String filmJson = objectMapper.writeValueAsString(filmToCreate);
mockMvc.perform(post("/films")
.contentType(MediaType.APPLICATION_JSON)
- .content(json))
+ .content(filmJson))
.andExpect(status().isOk())
- .andExpect(jsonPath("$.id").value("1"))
+ .andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.name").value("TestFilm"))
.andExpect(jsonPath("$.description").value("TestDescription"))
.andExpect(jsonPath("$.releaseDate").value("2000-01-01"))
- .andExpect(jsonPath("$.duration").value("120"));
+ .andExpect(jsonPath("$.duration").value(120))
+ .andExpect(jsonPath("$.likes").value(0));
+
+ verify(filmService).addFilm(any(Film.class));
}
@Test
- public void shouldNotCreateFilmWhenInvalidJson() throws Exception {
+ public void shouldNotCreateFilmWhenNameIsBlankAndReturnBadRequest() throws Exception {
+ Film filmToCreate = new Film(null, "", "TestDescription",
+ LocalDate.of(2000, 1, 1), 120);
+
+ String invalidJson = objectMapper.writeValueAsString(filmToCreate);
+
+
+ mockMvc.perform(post("/films")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(invalidJson))
+ .andExpect(status().isBadRequest());
+
+ verify(filmService, never()).addFilm(any());
+ }
+
+ @Test
+ public void shouldNotCreateFilmWhenDescriptionIsOverMaxSizeAndReturnBadRequest() throws Exception {
+ Film filmToCreate = new Film(null, "name", "a".repeat(300),
+ LocalDate.of(2000, 1, 1), 120);
+
+ String invalidJson = objectMapper.writeValueAsString(filmToCreate);
+
+
+ mockMvc.perform(post("/films")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(invalidJson))
+ .andExpect(status().isBadRequest());
+
+ verify(filmService, never()).addFilm(any());
+ }
+
+ @Test
+ public void shouldNotCreateFilmWhenReleaseDateIsInvalidAndReturnBadRequest() throws Exception {
+ Film filmToCreate = new Film(null, "name", "description",
+ LocalDate.of(1000, 1, 1), 120);
+
+ String invalidJson = objectMapper.writeValueAsString(filmToCreate);
+
+
+ mockMvc.perform(post("/films")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(invalidJson))
+ .andExpect(status().isBadRequest());
+
+ verify(filmService, never()).addFilm(any());
+ }
+
+ @Test
+ public void shouldNotCreateFilmWhenDurationIsInvalidAndReturnBadRequest() throws Exception {
+ Film filmToCreate = new Film(null, "name", "description",
+ LocalDate.of(2000, 1, 1), -10);
+
+ String invalidJson = objectMapper.writeValueAsString(filmToCreate);
+
+
+ mockMvc.perform(post("/films")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(invalidJson))
+ .andExpect(status().isBadRequest());
+
+ verify(filmService, never()).addFilm(any());
+ }
+
+ @Test
+ public void shouldNotCreateFilmWhenInvalidJsonAndReturnBadRequest() throws Exception {
String json = "{}";
mockMvc.perform(post("/films")
@@ -79,65 +245,249 @@ public void shouldNotCreateFilmWhenInvalidJson() throws Exception {
.content(json))
.andExpect(status().isBadRequest());
}
- }
- @Nested
- class FilmControllerPutTest {
+ @Test
+ public void shouldReturn400WhenInvalidJsonSyntax() throws Exception {
+ String invalidJson = "{ invalid json syntax ;";
+
+ mockMvc.perform(post("/films")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(invalidJson))
+ .andExpect(status().isBadRequest());
+
+ verify(filmService, never()).addFilm(any());
+ }
- @BeforeEach
- public void getNewText() throws JsonProcessingException {
- Film film = new Film(1L,
- "ONLY TODAY",
- "NEW TEXT",
- LocalDate.of(2000, 1, 1),
- 120);
+ @Test
+ public void shouldReturn400WhenInvalidDurationType() throws Exception {
+ String invalidJson =
+ "{\"name\":\"FilmName\",\"description\":\"Description\",\"releaseDate\":\"2000-01-01\",\"duration\":\"not_a_number\"}";
- json = objectMapper.writeValueAsString(film);
+ mockMvc.perform(post("/films")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(invalidJson))
+ .andExpect(status().isBadRequest());
+
+ verify(filmService, never()).addFilm(any());
}
+ @Test
+ public void shouldReturn400WhenInvalidDateFormat() throws Exception {
+ String invalidJson =
+ "{\"name\":\"FilmName\",\"description\":\"Description\",\"releaseDate\":\"2000/01/01\",\"duration\":120}";
+
+ mockMvc.perform(post("/films")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(invalidJson))
+ .andExpect(status().isBadRequest());
+
+ verify(filmService, never()).addFilm(any());
+ }
+ }
+
+ @Nested
+ class FilmControllerPutTest {
+
@Test
public void shouldUpdateFilm() throws Exception {
- addTestFilm();
+ Film filmToUpdate = new Film(1L, "ONLY TODAY", "NEW TEXT",
+ LocalDate.of(2000, 1, 1), 120);
+ filmToUpdate.setLikes(5L);
+
+ Film updatedFilm = new Film(1L, "ONLY TODAY", "NEW TEXT",
+ LocalDate.of(2000, 1, 1), 120);
+ updatedFilm.setLikes(5L);
+
+ when(filmService.updateFilm(any(Film.class))).thenReturn(updatedFilm);
+
+ String json = objectMapper.writeValueAsString(filmToUpdate);
mockMvc.perform(put("/films")
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andExpect(status().isOk())
+ .andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.name").value("ONLY TODAY"))
.andExpect(jsonPath("$.description").value("NEW TEXT"))
.andExpect(jsonPath("$.releaseDate").value("2000-01-01"))
- .andExpect(jsonPath("$.duration").value("120"));
+ .andExpect(jsonPath("$.duration").value(120))
+ .andExpect(jsonPath("$.likes").value(5));
+
+ verify(filmService, times(1)).updateFilm(any(Film.class));
}
@Test
- public void shouldNotUpdateFilmWhenNoId() throws Exception {
- String json =
- "{\"name\":\"test\", \"description\":\"test\", \"releaseDate\":\"2000-01-01\", \"name\":120}";
- addTestFilm();
+ public void shouldNotUpdateFilmWhenNoIdAndReturnBadRequest() throws Exception {
+ Film filmToUpdate = new Film(null, "ONLY TODAY", "NEW TEXT",
+ LocalDate.of(2000, 1, 1), 120);
+
+ String json = objectMapper.writeValueAsString(filmToUpdate);
mockMvc.perform(put("/films")
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andExpect(status().isBadRequest());
+
+ verify(filmService, never()).updateFilm(any());
}
@Test
public void shouldNotUpdateFilmWhenNotFound() throws Exception {
+ Film filmToUpdate = new Film(1000L, "ONLY TODAY", "NEW TEXT",
+ LocalDate.of(2000, 1, 1), 120);
+
+ when(filmService.updateFilm(any(Film.class)))
+ .thenThrow(new NotFoundException("Фильм с id = 1000 не найден"));
+
+ String json = objectMapper.writeValueAsString(filmToUpdate);
+
mockMvc.perform(put("/films")
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andExpect(status().isNotFound());
+
+ verify(filmService, times(1)).updateFilm(any(Film.class));
}
@Test
- public void shouldNotUpdateFilmWhenInvalidJson() throws Exception {
- String json = "{}";
+ public void shouldAddLike() throws Exception {
+ Film film = new Film(1L, "ONLY TODAY", "NEW TEXT",
+ LocalDate.of(2000, 1, 1), 120);
+ film.setLikes(5L);
- mockMvc.perform(put("/films")
+ Film updatedFilm = new Film(1L, "ONLY TODAY", "NEW TEXT",
+ LocalDate.of(2000, 1, 1), 120);
+ updatedFilm.setLikes(6L);
+
+ when(filmService.addLike(any(), any())).thenReturn(updatedFilm);
+
+ String json = objectMapper.writeValueAsString(film);
+
+ mockMvc.perform(put("/films/1/like/1")
.contentType(MediaType.APPLICATION_JSON)
.content(json))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.id").value(1))
+ .andExpect(jsonPath("$.name").value("ONLY TODAY"))
+ .andExpect(jsonPath("$.description").value("NEW TEXT"))
+ .andExpect(jsonPath("$.releaseDate").value("2000-01-01"))
+ .andExpect(jsonPath("$.duration").value(120))
+ .andExpect(jsonPath("$.likes").value(6));
+
+ verify(filmService, times(1)).addLike(any(), any());
+ }
+
+ @Test
+ public void shouldNotAddLikeWhenFilmNotFound() throws Exception {
+ Film filmToUpdate = new Film(1000L, "ONLY TODAY", "NEW TEXT",
+ LocalDate.of(2000, 1, 1), 120);
+
+ when(filmService.addLike(any(), any()))
+ .thenThrow(new NotFoundException("Фильм с id = 1000 не найден"));
+
+ String json = objectMapper.writeValueAsString(filmToUpdate);
+
+ mockMvc.perform(put("/films/1000/like/1")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(json))
+ .andExpect(status().isNotFound());
+
+ verify(filmService, times(1)).addLike(any(), any());
+ }
+
+ @Test
+ public void shouldNotAddLikeWhenUserNotFound() throws Exception {
+ Film filmToUpdate = new Film(1L, "ONLY TODAY", "NEW TEXT",
+ LocalDate.of(2000, 1, 1), 120);
+
+ when(filmService.addLike(any(), any()))
+ .thenThrow(new NotFoundException("Пользователь с id = 1000 не найден"));
+
+ String json = objectMapper.writeValueAsString(filmToUpdate);
+
+ mockMvc.perform(put("/films/1/like/1000")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(json))
+ .andExpect(status().isNotFound());
+
+ verify(filmService, times(1)).addLike(any(), any());
+ }
+
+ @Test
+ public void shouldReturn400WhenInvalidIdsInLike() throws Exception {
+ mockMvc.perform(put("/films/invalid_id/like/also_invalid"))
.andExpect(status().isBadRequest());
+
+ verify(filmService, never()).addLike(any(), any());
+ }
+ }
+
+ @Nested
+ class FilmControllerDeleteTest {
+
+ @Test
+ public void shouldRemoveLike() throws Exception {
+ Film film = new Film(1L, "ONLY TODAY", "NEW TEXT",
+ LocalDate.of(2000, 1, 1), 120);
+ film.setLikes(5L);
+
+ Film updatedFilm = new Film(1L, "ONLY TODAY", "NEW TEXT",
+ LocalDate.of(2000, 1, 1), 120);
+ updatedFilm.setLikes(4L);
+
+ when(filmService.removeLike(any(), any())).thenReturn(updatedFilm);
+
+ String json = objectMapper.writeValueAsString(film);
+
+ mockMvc.perform(delete("/films/1/like/1")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(json))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.id").value(1))
+ .andExpect(jsonPath("$.name").value("ONLY TODAY"))
+ .andExpect(jsonPath("$.description").value("NEW TEXT"))
+ .andExpect(jsonPath("$.releaseDate").value("2000-01-01"))
+ .andExpect(jsonPath("$.duration").value(120))
+ .andExpect(jsonPath("$.likes").value(4));
+
+ verify(filmService, times(1)).removeLike(any(), any());
+ }
+
+ @Test
+ public void shouldNotRemoveLikeWhenFilmNotFound() throws Exception {
+ Film filmToUpdate = new Film(1000L, "ONLY TODAY", "NEW TEXT",
+ LocalDate.of(2000, 1, 1), 120);
+
+ when(filmService.removeLike(any(), any()))
+ .thenThrow(new NotFoundException("Фильм с id = 1000 не найден"));
+
+ String json = objectMapper.writeValueAsString(filmToUpdate);
+
+ mockMvc.perform(delete("/films/1000/like/1")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(json))
+ .andExpect(status().isNotFound());
+
+ verify(filmService, times(1)).removeLike(any(), any());
+ }
+
+ @Test
+ public void shouldNotRemoveLikeWhenUserNotFound() throws Exception {
+ Film filmToUpdate = new Film(1L, "ONLY TODAY", "NEW TEXT",
+ LocalDate.of(2000, 1, 1), 120);
+
+ when(filmService.removeLike(any(), any()))
+ .thenThrow(new NotFoundException("Пользователь с id = 1000 не найден"));
+
+ String json = objectMapper.writeValueAsString(filmToUpdate);
+
+ mockMvc.perform(delete("/films/1/like/1000")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(json))
+ .andExpect(status().isNotFound());
+
+ verify(filmService, times(1)).removeLike(any(), any());
}
}
}
\ No newline at end of file
diff --git a/src/test/java/ru/yandex/practicum/filmorate/controller/UserControllerTest.java b/src/test/java/ru/yandex/practicum/filmorate/controller/UserControllerTest.java
index d9b5cb5..7884b1a 100644
--- a/src/test/java/ru/yandex/practicum/filmorate/controller/UserControllerTest.java
+++ b/src/test/java/ru/yandex/practicum/filmorate/controller/UserControllerTest.java
@@ -1,15 +1,20 @@
package ru.yandex.practicum.filmorate.controller;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
+import ru.yandex.practicum.filmorate.exception.NotFoundException;
+import ru.yandex.practicum.filmorate.exception.ValidationException;
import ru.yandex.practicum.filmorate.model.User;
+import ru.yandex.practicum.filmorate.service.user.UserService;
import java.time.LocalDate;
+import java.util.Collections;
+import java.util.List;
+import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -17,153 +22,406 @@
@WebMvcTest(UserController.class)
public class UserControllerTest extends ControllerTest {
- private void addTestUser() throws Exception {
- mockMvc.perform(post("/users")
- .contentType(MediaType.APPLICATION_JSON)
- .content(json));
- }
+ @MockBean
+ private UserService userService;
- @BeforeEach
- public void getJsonString() throws JsonProcessingException {
+ @Nested
+ class UserControllerGetTest {
- User user = new User(2L,
- "valid@mail.com",
- "login",
- "George",
- LocalDate.of(2000, 1, 1)
- );
+ @Test
+ public void shouldGetUsers() throws Exception {
+ User user1 = new User(1L, "email1", "login1", "name1", LocalDate.now());
+ User user2 = new User(2L, "email2", "login2", "name2", LocalDate.now());
- json = objectMapper.writeValueAsString(user);
+ List users = List.of(user1, user2);
- }
+ // Настраиваем мок сервиса
+ when(userService.getAllUsers()).thenReturn(users);
- @Nested
- class UserGetTest {
+ mockMvc.perform(get("/users"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$").isArray())
+ .andExpect(jsonPath("$.length()").value(2))
+ .andExpect(jsonPath("$[0].id").value(1))
+ .andExpect(jsonPath("$[0].login").value("login1"))
+ .andExpect(jsonPath("$[1].id").value(2))
+ .andExpect(jsonPath("$[1].login").value("login2"));
+
+ verify(userService, times(1)).getAllUsers();
+ }
@Test
- public void shouldGetUsers() throws Exception {
- addTestUser();
- addTestUser();
+ public void shouldGetEmptyList() throws Exception {
+
+ when(userService.getAllUsers()).thenReturn(Collections.emptyList());
mockMvc.perform(get("/users"))
.andExpect(status().isOk())
- .andExpect(jsonPath("$[0].id").value("1"))
- .andExpect(jsonPath("$[0].email").value("valid@mail.com"))
- .andExpect(jsonPath("$[1].id").value("2"))
- .andExpect(jsonPath("$[1].email").value("valid@mail.com"));
+ .andExpect(jsonPath("$").isArray())
+ .andExpect(jsonPath("$.length()").value(0));
+
+ verify(userService, times(1)).getAllUsers();
+ }
+
+ @Test
+ public void shouldGetFriends() throws Exception {
+ User user2 = new User(2L, "email2", "login2", "name2", LocalDate.now());
+ User user3 = new User(3L, "email3", "login3", "name3", LocalDate.now());
+
+ List users = List.of(user2, user3);
+
+ // Настраиваем мок сервиса
+ when(userService.getFriends(1L)).thenReturn(users);
+
+ mockMvc.perform(get("/users/1/friends"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$").isArray())
+ .andExpect(jsonPath("$.length()").value(2))
+ .andExpect(jsonPath("$[0].id").value(2))
+ .andExpect(jsonPath("$[0].login").value("login2"))
+ .andExpect(jsonPath("$[1].id").value(3))
+ .andExpect(jsonPath("$[1].login").value("login3"));
+
+ verify(userService, times(1)).getFriends(1L);
+ }
+
+ @Test
+ public void shouldReturnNotFoundWhenUserIsNotFound() throws Exception {
+
+ // Настраиваем мок сервиса
+ when(userService.getFriends(1000L))
+ .thenThrow(new NotFoundException("Пользователь с id = 1000 не найден"));
+
+
+ mockMvc.perform(get("/users/1000/friends"))
+ .andExpect(status().isNotFound());
+
+ verify(userService, times(1)).getFriends(1000L);
+ }
+
+ @Test
+ public void shouldGetCommonFriends() throws Exception {
+ User user2 = new User(2L, "email2", "login2", "name2", LocalDate.now());
+ User user3 = new User(3L, "email3", "login3", "name3", LocalDate.now());
+
+ List users = List.of(user2, user3);
+
+ // Настраиваем мок сервиса
+ when(userService.getCommonFriends(1L, 4L)).thenReturn(users);
+
+ mockMvc.perform(get("/users/1/friends/common/4"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$").isArray())
+ .andExpect(jsonPath("$.length()").value(2))
+ .andExpect(jsonPath("$[0].id").value(2))
+ .andExpect(jsonPath("$[0].login").value("login2"))
+ .andExpect(jsonPath("$[1].id").value(3))
+ .andExpect(jsonPath("$[1].login").value("login3"));
+
+ verify(userService, times(1)).getCommonFriends(1L, 4L);
+ }
+
+ @Test
+ public void shouldReturnNotFoundWhenUserIsNotFoundWhenCommonFriends() throws Exception {
+
+ // Настраиваем мок сервиса
+ when(userService.getCommonFriends(1000L, 1L))
+ .thenThrow(new NotFoundException("Пользователь с id = 1000 не найден"));
+
+
+ mockMvc.perform(get("/users/1000/friends/common/1"))
+ .andExpect(status().isNotFound());
+
+ verify(userService, times(1)).getCommonFriends(1000L, 1L);
+ }
+
+ @Test
+ public void shouldReturnNotFoundWhenOtherUserIsNotFoundWhenCommonFriends() throws Exception {
+
+ // Настраиваем мок сервиса
+ when(userService.getCommonFriends(1L, 1000L))
+ .thenThrow(new NotFoundException("Пользователь с id = 1000 не найден"));
+
+
+ mockMvc.perform(get("/users/1/friends/common/1000"))
+ .andExpect(status().isNotFound());
+
+ verify(userService, times(1)).getCommonFriends(1L, 1000L);
+ }
+
+ @Test
+ public void shouldReturn400WhenInvalidIdsInFriends() throws Exception {
+ mockMvc.perform(get("/users/invalid_id/friends/common/also_invalid"))
+ .andExpect(status().isBadRequest());
+
+ verify(userService, never()).getCommonFriends(any(), any());
+ }
+
+ @Test
+ public void shouldReturn400WhenNegativeIds() throws Exception {
+ mockMvc.perform(get("/users/-1/friends/common/-2"))
+ .andExpect(status().isBadRequest());
+
+ verify(userService, never()).getFriends(any());
+ }
+
+ @Test
+ public void shouldReturn400WhenZeroUserId() throws Exception {
+ mockMvc.perform(get("/users/0/friends"))
+ .andExpect(status().isBadRequest());
+
+ verify(userService, never()).getFriends(any());
}
}
@Nested
- class UserPostTest {
+ class UserControllerPostTest {
@Test
public void shouldCreateUser() throws Exception {
+ User user = new User(null, "email@mail.org", "login1", "name1", LocalDate.now());
+ User createdUser = new User(1L, "email@mail.org", "login1", "name1", LocalDate.now());
+
+ when(userService.addUser(any(User.class))).thenReturn(createdUser);
+
+ String json = objectMapper.writeValueAsString(user);
mockMvc.perform(post("/users")
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andExpect(status().isOk())
- .andExpect(jsonPath("$.id").value("1"))
- .andExpect(jsonPath("$.email").value("valid@mail.com"))
- .andExpect(jsonPath("$.login").value("login"))
- .andExpect(jsonPath("$.name").value("George"))
- .andExpect(jsonPath("$.birthday").value("2000-01-01"));
+ .andExpect(jsonPath("$.id").value(1))
+ .andExpect(jsonPath("$.email").value("email@mail.org"))
+ .andExpect(jsonPath("$.login").value("login1"))
+ .andExpect(jsonPath("$.name").value("name1"));
+
+ verify(userService).addUser(any(User.class));
}
@Test
- public void shouldCreateUserAndNameShouldBeLoginIfNameNull() throws Exception {
+ public void shouldReturnBadRequestWhenEmailIsNotValid() throws Exception {
+ User user = new User(null, "@email", "login1", "name1", LocalDate.now());
- User user = new User(2L,
- "valid@mail.com",
- "login",
- null,
- LocalDate.of(2000, 1, 1)
- );
+ String json = objectMapper.writeValueAsString(user);
- json = objectMapper.writeValueAsString(user);
+ mockMvc.perform(post("/users")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(json))
+ .andExpect(status().isBadRequest());
+
+ verify(userService, never()).addUser(any(User.class));
+ }
+
+ @Test
+ public void shouldReturnBadRequestWhenEmailIsBlank() throws Exception {
+ User user = new User(null, " ", "login1", "name1", LocalDate.now());
+
+ String json = objectMapper.writeValueAsString(user);
mockMvc.perform(post("/users")
.contentType(MediaType.APPLICATION_JSON)
.content(json))
- .andExpect(status().isOk())
- .andExpect(jsonPath("$.id").value("1"))
- .andExpect(jsonPath("$.email").value("valid@mail.com"))
- .andExpect(jsonPath("$.login").value("login"))
- .andExpect(jsonPath("$.name").value("login"))
- .andExpect(jsonPath("$.birthday").value("2000-01-01"));
+ .andExpect(status().isBadRequest());
+
+ verify(userService, never()).addUser(any(User.class));
}
@Test
- public void shouldNotCreateUserWhenInvalidJson() throws Exception {
- String json = "{}";
+ public void shouldReturnBadRequestWhenLoginContainsWhiteSpaces() throws Exception {
+ User user = new User(null, "email@mail.org", " logi n 1", "name1", LocalDate.now());
+
+ String json = objectMapper.writeValueAsString(user);
mockMvc.perform(post("/users")
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andExpect(status().isBadRequest());
+
+ verify(userService, never()).addUser(any(User.class));
}
- }
- @Nested
- class UserPutTest {
+ @Test
+ public void shouldReturnBadRequestWhenLoginBirthDayInFuture() throws Exception {
+ User user = new User(null, "email@mail.org", "login1", "name1", LocalDate.now().plusDays(1));
+
+ String json = objectMapper.writeValueAsString(user);
+
+ mockMvc.perform(post("/users")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(json))
+ .andExpect(status().isBadRequest());
+
+ verify(userService, never()).addUser(any(User.class));
+ }
+
+ @Test
+ public void shouldReturn400WhenInvalidJsonSyntax() throws Exception {
+ String invalidJson = "{ name: user, email: test@mail.ru }";
+
+ mockMvc.perform(post("/users")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(invalidJson))
+ .andExpect(status().isBadRequest());
+
+ verify(userService, never()).addUser(any());
+ }
+
+ @Test
+ public void shouldReturn400WhenInvalidBirthdayFormat() throws Exception {
+ String invalidJson = "{\"email\":\"test@mail.ru\",\"login\":\"login\",\"name\":\"Name\",\"birthday\":\"2000/01/01\"}";
+
+ mockMvc.perform(post("/users")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(invalidJson))
+ .andExpect(status().isBadRequest());
+
+ verify(userService, never()).addUser(any());
+ }
- @BeforeEach
- public void getNewText() throws JsonProcessingException {
- User user = new User(2L,
- "valid@mail.com",
- "newlogin",
- "same George",
- LocalDate.of(2000, 1, 1)
- );
+ @Test
+ public void shouldReturn400WhenFutureBirthday() throws Exception {
+ String invalidJson = "{\"email\":\"test@mail.ru\",\"login\":\"login\",\"name\":\"Name\",\"birthday\":\"2030-01-01\"}";
- json = objectMapper.writeValueAsString(user);
+ mockMvc.perform(post("/users")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(invalidJson))
+ .andExpect(status().isBadRequest());
+
+ verify(userService, never()).addUser(any());
}
+ }
+
+ @Nested
+ class UserControllerPutTest {
@Test
public void shouldUpdateUser() throws Exception {
- addTestUser();
- addTestUser();
+ User userToUpdate = new User(1L, "email@mail.org", "login1", "name1", LocalDate.now());
+ User updatedUser = new User(1L, "email@mail.org", "login1", "name1", LocalDate.now());
+
+ when(userService.updateUser(any(User.class))).thenReturn(updatedUser);
+
+ String json = objectMapper.writeValueAsString(userToUpdate);
mockMvc.perform(put("/users")
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andExpect(status().isOk())
- .andExpect(jsonPath("$.email").value("valid@mail.com"))
- .andExpect(jsonPath("$.login").value("newlogin"))
- .andExpect(jsonPath("$.name").value("same George"))
- .andExpect(jsonPath("$.birthday").value("2000-01-01"));
+ .andExpect(jsonPath("$.id").value(1))
+ .andExpect(jsonPath("$.id").value(1))
+ .andExpect(jsonPath("$.email").value("email@mail.org"))
+ .andExpect(jsonPath("$.login").value("login1"))
+ .andExpect(jsonPath("$.name").value("name1"));
+
+ verify(userService, times(1)).updateUser(any(User.class));
}
@Test
- public void shouldNotUpdateUserWhenNoId() throws Exception {
- String json =
- "{\"email\":\"valid@mail.com\", \"login\":\"test\", \"name\":\"test\", \"birthday\":2000-01-01}";
- addTestUser();
+ public void shouldNotUpdateUserWhenNoIdAndReturnBadRequest() throws Exception {
+ User userToUpdate = new User(null, "email@mail.org", "login1", "name1", LocalDate.now());
+
+ String json = objectMapper.writeValueAsString(userToUpdate);
+ when(userService.updateUser(userToUpdate)).thenThrow(new ValidationException(""));
mockMvc.perform(put("/users")
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andExpect(status().isBadRequest());
+
+ verify(userService, times(1)).updateUser(any());
}
@Test
public void shouldNotUpdateUserWhenNotFound() throws Exception {
+ User userToUpdate = new User(1000L, "email@mail.org", "login1", "name1", LocalDate.now());
+
+ when(userService.updateUser(any(User.class)))
+ .thenThrow(new NotFoundException("Пользователь с id = 1000 не найден"));
+
+ String json = objectMapper.writeValueAsString(userToUpdate);
+
mockMvc.perform(put("/users")
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andExpect(status().isNotFound());
+
+ verify(userService, times(1)).updateUser(any(User.class));
}
@Test
- public void shouldNotUpdateUserWhenInvalidJson() throws Exception {
- String json = "{}";
+ public void shouldAddFriend() throws Exception {
+ mockMvc.perform(put("/users/3/friends/2"))
+ .andExpect(status().isOk());
- mockMvc.perform(put("/users")
- .contentType(MediaType.APPLICATION_JSON)
- .content(json))
+ verify(userService, times(1)).addFriend(any(), any());
+ }
+
+ @Test
+ public void shouldReturnNotFoundWhenSenderNotFound() throws Exception {
+
+ doThrow(new NotFoundException("Пользователь с id = 1000 не найден"))
+ .when(userService).addFriend(1000L, 1L);
+
+ mockMvc.perform(put("/users/1000/friends/1"))
+ .andExpect(status().isNotFound());
+
+ verify(userService, times(1)).addFriend(1000L, 1L);
+ }
+
+ @Test
+ public void shouldReturnNotFoundWhenReceiverNotFound() throws Exception {
+
+ doThrow(new NotFoundException("Пользователь с id = 1000 не найден"))
+ .when(userService).addFriend(1L, 1000L);
+
+
+ mockMvc.perform(put("/users/1/friends/1000"))
+ .andExpect(status().isNotFound());
+
+ verify(userService, times(1)).addFriend(1L, 1000L);
+ }
+
+ @Test
+ public void shouldReturn400WhenInvalidFriendId() throws Exception {
+ mockMvc.perform(put("/users/1/friends/not_a_number"))
.andExpect(status().isBadRequest());
+
+ verify(userService, never()).addFriend(any(), any());
+ }
+ }
+
+ @Nested
+ class UserControllerDeleteTest {
+
+ @Test
+ public void shouldRemoveFriend() throws Exception {
+ mockMvc.perform(delete("/users/1/friends/2"))
+ .andExpect(status().isNoContent());
+
+ verify(userService, times(1)).deleteFriend(any(), any());
+ }
+
+ @Test
+ public void shouldReturnNotFoundWhenUserNotFound() throws Exception {
+ doThrow(new NotFoundException("Пользователь с id = 1000 не найден"))
+ .when(userService).deleteFriend(1000L, 1L);
+
+ mockMvc.perform(delete("/users/1000/friends/1"))
+ .andExpect(status().isNotFound());
+
+ verify(userService, times(1)).deleteFriend(any(), any());
+ }
+
+ @Test
+ public void shouldReturnNotFoundWhenOtherUserNotFound() throws Exception {
+ doThrow(new NotFoundException("Пользователь с id = 1000 не найден"))
+ .when(userService).deleteFriend(1L, 1000L);
+
+ mockMvc.perform(delete("/users/1/friends/1000"))
+ .andExpect(status().isNotFound());
+
+ verify(userService, times(1)).deleteFriend(any(), any());
}
}
}
\ No newline at end of file
diff --git a/src/test/java/ru/yandex/practicum/filmorate/service/film/FilmServiceTest.java b/src/test/java/ru/yandex/practicum/filmorate/service/film/FilmServiceTest.java
new file mode 100644
index 0000000..d883d1a
--- /dev/null
+++ b/src/test/java/ru/yandex/practicum/filmorate/service/film/FilmServiceTest.java
@@ -0,0 +1,288 @@
+package ru.yandex.practicum.filmorate.service.film;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import ru.yandex.practicum.filmorate.exception.NotFoundException;
+import ru.yandex.practicum.filmorate.exception.ValidationException;
+import ru.yandex.practicum.filmorate.model.Film;
+import ru.yandex.practicum.filmorate.storage.film.FilmStorage;
+import ru.yandex.practicum.filmorate.storage.film.LikeStorage;
+import ru.yandex.practicum.filmorate.storage.user.UserStorage;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+public class FilmServiceTest {
+
+ @Mock
+ private FilmStorage filmStorage;
+
+ @Mock
+ private LikeStorage likeStorage;
+
+ @Mock
+ private UserStorage userStorage;
+
+ private FilmService filmService;
+
+ @BeforeEach
+ public void setUp() {
+ filmService = new FilmService(filmStorage, userStorage, likeStorage);
+ }
+
+ @Test
+ public void shouldGetFilmSuccessfully() {
+ Long filmId = 1L;
+ Film expectedFilm = new Film(filmId, "Film Name", "Description", LocalDate.now(), 120);
+
+ when(filmStorage.contains(filmId)).thenReturn(true);
+ when(filmStorage.get(filmId)).thenReturn(expectedFilm);
+
+ Film actualFilm = filmService.getFilm(filmId);
+
+ assertNotNull(actualFilm);
+ assertEquals(expectedFilm, actualFilm);
+ verify(filmStorage).contains(filmId);
+ verify(filmStorage).get(filmId);
+ }
+
+ @Test
+ public void shouldThrowNotFoundExceptionWhenFilmNotExists() {
+ Long filmId = 999L;
+
+ when(filmStorage.contains(filmId)).thenReturn(false);
+
+ assertThrows(NotFoundException.class, () -> filmService.getFilm(filmId));
+ verify(filmStorage, never()).get(filmId);
+ }
+
+ @Test
+ public void shouldGetAllFilms() {
+ Film film1 = new Film(1L, "Film 1", "Desc 1", LocalDate.now(), 120);
+ Film film2 = new Film(2L, "Film 2", "Desc 2", LocalDate.now(), 150);
+ Collection expectedFilms = List.of(film1, film2);
+
+ when(filmStorage.getAll()).thenReturn(expectedFilms);
+
+ Collection actualFilms = filmService.getAllFilms();
+
+ assertNotNull(actualFilms);
+ assertEquals(2, actualFilms.size());
+ assertEquals(expectedFilms, actualFilms);
+ verify(filmStorage).getAll();
+ }
+
+ @Test
+ public void shouldAddFilmSuccessfully() {
+ Film inputFilm = new Film(null, "New Film", "Description", LocalDate.now(), 120);
+ Film savedFilm = new Film(1L, "New Film", "Description", LocalDate.now(), 120);
+
+ when(filmStorage.add(inputFilm)).thenReturn(savedFilm);
+ doNothing().when(likeStorage).initializeLikesSet(1L);
+
+ Film result = filmService.addFilm(inputFilm);
+
+ assertNotNull(result);
+ assertEquals(1L, result.getId());
+ verify(filmStorage).add(inputFilm);
+ verify(likeStorage).initializeLikesSet(1L);
+ }
+
+ @Test
+ public void shouldUpdateFilmSuccessfully() {
+ Film filmToUpdate = new Film(1L, "Updated Film", "Updated Desc", LocalDate.now(), 130);
+
+ when(filmStorage.contains(1L)).thenReturn(true);
+ when(filmStorage.update(filmToUpdate)).thenReturn(filmToUpdate);
+
+ Film result = filmService.updateFilm(filmToUpdate);
+
+ assertNotNull(result);
+ assertEquals("Updated Film", result.getName());
+ verify(filmStorage).update(filmToUpdate);
+ }
+
+ @Test
+ public void shouldThrowValidationExceptionWhenUpdateFilmWithNullId() {
+ Film filmWithoutId = new Film(null, "Film", "Desc", LocalDate.now(), 120);
+
+ ValidationException exception = assertThrows(ValidationException.class,
+ () -> filmService.updateFilm(filmWithoutId));
+
+ assertEquals("Id должен быть указан", exception.getMessage());
+ verify(filmStorage, never()).update(any());
+ }
+
+ @Test
+ public void shouldThrowNotFoundExceptionWhenUpdateNonExistentFilm() {
+ Film filmToUpdate = new Film(999L, "Film", "Desc", LocalDate.now(), 120);
+
+ when(filmStorage.contains(999L)).thenReturn(false);
+
+ assertThrows(NotFoundException.class, () -> filmService.updateFilm(filmToUpdate));
+ verify(filmStorage, never()).update(any());
+ }
+
+ @Test
+ public void shouldDeleteFilmSuccessfully() {
+ Long filmId = 1L;
+
+ when(filmStorage.contains(filmId)).thenReturn(true);
+
+ filmService.deleteFilm(filmId);
+
+ verify(filmStorage).remove(filmId);
+ verify(likeStorage).clearLikesSet(filmId);
+ }
+
+ @Test
+ public void shouldThrowNotFoundExceptionWhenDeleteNonExistentFilm() {
+ Long filmId = 999L;
+
+ when(filmStorage.contains(filmId)).thenReturn(false);
+
+ assertThrows(NotFoundException.class, () -> filmService.deleteFilm(filmId));
+ verify(filmStorage, never()).remove(filmId);
+ verify(likeStorage, never()).clearLikesSet(filmId);
+ }
+
+ @Test
+ public void shouldAddLikeSuccessfully() {
+ Long filmId = 1L;
+ Long userId = 1L;
+ Film film = new Film(filmId, "Film", "Desc", LocalDate.now(), 120);
+
+ when(filmStorage.contains(filmId)).thenReturn(true);
+ when(userStorage.contains(userId)).thenReturn(true);
+ when(likeStorage.addLike(filmId, userId)).thenReturn(5L);
+ when(filmStorage.get(filmId)).thenReturn(film);
+
+ Film result = filmService.addLike(filmId, userId);
+
+ assertNotNull(result);
+ assertEquals(5L, result.getLikes());
+ verify(likeStorage).addLike(filmId, userId);
+ verify(filmStorage, times(2)).get(filmId);
+ }
+
+ @Test
+ public void shouldThrowNotFoundExceptionWhenAddLikeToNonExistentFilm() {
+ Long filmId = 999L;
+ Long userId = 1L;
+
+ when(filmStorage.contains(filmId)).thenReturn(false);
+
+ assertThrows(NotFoundException.class, () -> filmService.addLike(filmId, userId));
+ verify(likeStorage, never()).addLike(anyLong(), anyLong());
+ }
+
+ @Test
+ public void shouldThrowNotFoundExceptionWhenAddLikeFromNonExistentUser() {
+ Long filmId = 1L;
+ Long userId = 999L;
+
+ when(filmStorage.contains(filmId)).thenReturn(true);
+ when(userStorage.contains(userId)).thenReturn(false);
+
+ assertThrows(NotFoundException.class, () -> filmService.addLike(filmId, userId));
+ verify(likeStorage, never()).addLike(anyLong(), anyLong());
+ }
+
+ @Test
+ public void shouldRemoveLikeSuccessfully() {
+ Long filmId = 1L;
+ Long userId = 1L;
+ Film film = new Film(filmId, "Film", "Desc", LocalDate.now(), 120);
+
+ when(filmStorage.contains(filmId)).thenReturn(true);
+ when(userStorage.contains(userId)).thenReturn(true);
+ when(likeStorage.deleteLike(filmId, userId)).thenReturn(3L);
+ when(filmStorage.get(filmId)).thenReturn(film);
+
+ Film result = filmService.removeLike(filmId, userId);
+
+ assertNotNull(result);
+ assertEquals(3L, result.getLikes());
+ verify(likeStorage).deleteLike(filmId, userId);
+ }
+
+ @Test
+ public void shouldThrowNotFoundExceptionWhenRemoveLikeFromNonExistentFilm() {
+ Long filmId = 999L;
+ Long userId = 1L;
+
+ when(filmStorage.contains(filmId)).thenReturn(false);
+
+ assertThrows(NotFoundException.class, () -> filmService.removeLike(filmId, userId));
+ verify(likeStorage, never()).deleteLike(anyLong(), anyLong());
+ }
+
+ @Test
+ public void shouldThrowNotFoundExceptionWhenRemoveLikeFromNonExistentUser() {
+ Long filmId = 1L;
+ Long userId = 999L;
+
+ when(filmStorage.contains(filmId)).thenReturn(true);
+ when(userStorage.contains(userId)).thenReturn(false);
+
+ assertThrows(NotFoundException.class, () -> filmService.removeLike(filmId, userId));
+ verify(likeStorage, never()).deleteLike(anyLong(), anyLong());
+ }
+
+ @Test
+ public void shouldGetTopFilmsOrderedByLikes() {
+ Film film1 = new Film(1L, "Film 1", "Desc 1", LocalDate.now(), 120);
+ film1.setLikes(10L);
+ Film film2 = new Film(2L, "Film 2", "Desc 2", LocalDate.now(), 150);
+ film2.setLikes(25L);
+ Film film3 = new Film(3L, "Film 3", "Desc 3", LocalDate.now(), 130);
+ film3.setLikes(5L);
+
+ when(filmStorage.getAll()).thenReturn(List.of(film1, film2, film3));
+
+ Collection topFilms = filmService.getTopFilms(2L);
+
+ assertNotNull(topFilms);
+ assertEquals(2, topFilms.size());
+
+ List resultList = new ArrayList<>(topFilms);
+ assertEquals(25L, resultList.get(0).getLikes()); // film2
+ assertEquals(10L, resultList.get(1).getLikes()); // film1
+ }
+
+ @Test
+ public void shouldReturnEmptyListWhenNoFilms() {
+ when(filmStorage.getAll()).thenReturn(List.of());
+
+ // when
+ Collection topFilms = filmService.getTopFilms(10L);
+
+ assertNotNull(topFilms);
+ assertTrue(topFilms.isEmpty());
+ }
+
+ @Test
+ public void shouldReturnAllFilmsWhenCountIsGreaterThanTotal() {
+ Film film1 = new Film(1L, "Film 1", "Desc 1", LocalDate.now(), 120);
+ film1.setLikes(5L);
+ Film film2 = new Film(2L, "Film 2", "Desc 2", LocalDate.now(), 150);
+ film2.setLikes(3L);
+
+ when(filmStorage.getAll()).thenReturn(List.of(film1, film2));
+
+ // when
+ Collection topFilms = filmService.getTopFilms(5L);
+
+ assertNotNull(topFilms);
+ assertEquals(2, topFilms.size());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/yandex/practicum/filmorate/service/user/UserServiceTest.java b/src/test/java/ru/yandex/practicum/filmorate/service/user/UserServiceTest.java
new file mode 100644
index 0000000..a314020
--- /dev/null
+++ b/src/test/java/ru/yandex/practicum/filmorate/service/user/UserServiceTest.java
@@ -0,0 +1,430 @@
+package ru.yandex.practicum.filmorate.service.user;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import ru.yandex.practicum.filmorate.exception.NotFoundException;
+import ru.yandex.practicum.filmorate.exception.ValidationException;
+import ru.yandex.practicum.filmorate.model.User;
+import ru.yandex.practicum.filmorate.storage.user.FriendShipStorage;
+import ru.yandex.practicum.filmorate.storage.user.UserStorage;
+
+import java.time.LocalDate;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+public class UserServiceTest {
+
+ @Mock
+ private UserStorage userStorage;
+
+ @Mock
+ private FriendShipStorage friendShipStorage;
+
+ private UserServiceInterface userService;
+
+ @BeforeEach
+ public void setUp() {
+ userService = new UserService(userStorage, friendShipStorage);
+ }
+
+ @Test
+ public void shouldGetUser() {
+ User user = new User(1L, "", "login", null, LocalDate.now());
+ when(userStorage.contains(1L)).thenReturn(true);
+ when(userStorage.get(1L)).thenReturn(user);
+ User result = userService.getUser(1L);
+
+ assertEquals(user, result);
+
+ verify(userStorage).contains(any());
+ verify(userStorage).get(any());
+ }
+
+ @Test
+ public void shouldThrowNotFoundWhenGetUser() {
+ when(userStorage.contains(1L)).thenReturn(false);
+
+ assertThrows(NotFoundException.class, () -> userService.getUser(1L));
+
+ verify(userStorage).contains(any());
+ verify(userStorage, never()).get(any());
+ }
+
+ @Test
+ public void shouldGetAllUser() {
+ User user1 = new User(1L, "", "login", null, LocalDate.now());
+ User user2 = new User(2L, "", "login", null, LocalDate.now());
+ List list = List.of(user1, user2);
+
+ when(userStorage.getAll()).thenReturn(list);
+
+ assertEquals(2, userService.getAllUsers().size());
+
+ verify(userStorage).getAll();
+ }
+
+ @Test
+ public void shouldAddUser() {
+ User user1 = new User(null, "email@mail.com", "login", "name", LocalDate.now());
+ User returnUser = new User(1L, "email@mail.com", "login", "name", LocalDate.now());
+
+ when(userStorage.add(any(User.class))).thenReturn(returnUser);
+ doNothing().when(friendShipStorage).initializeFriendsSet(1L);
+
+ User result = userService.addUser(user1);
+
+ assertEquals(returnUser, result);
+ verify(userStorage).add(any(User.class));
+ verify(friendShipStorage).initializeFriendsSet(1L);
+ }
+
+ @Test
+ public void shouldSetNameAsLoginWhenNameIsNull() {
+ // входящая информация
+ User user = new User(1L, "", "login", null, LocalDate.now());
+
+ // настраиваем поведение мока
+ when(userStorage.add(user)).thenReturn(user);
+
+ User result = userService.addUser(user);
+
+ // проверки, что метод вызвался
+ verify(userStorage).add(user);
+
+ // проверка, что значения равны
+ assertEquals("login", result.getName());
+ }
+
+ @Test
+ public void shouldSetNameAsLoginWhenNameIsEmpty() {
+ // входящая информация
+ User user = new User(1L, "", "login", "", LocalDate.now());
+
+ // настраиваем поведение мока
+ when(userStorage.add(user)).thenReturn(user);
+
+ User result = userService.addUser(user);
+
+ // проверки, что метод вызвался
+ verify(userStorage).add(user);
+
+ // проверка, что значения равны
+ assertEquals("login", result.getName());
+ }
+
+ @Test
+ public void shouldSetNameAsLoginWhenNameIsBlank() {
+ // входящая информация
+ User user = new User(1L, "", "login", " ", LocalDate.now());
+
+ // настраиваем поведение мока
+ when(userStorage.add(user)).thenReturn(user);
+
+ User result = userService.addUser(user);
+
+ // проверки, что метод вызвался
+ verify(userStorage).add(user);
+ // проверка, что значения равны
+ assertEquals("login", result.getName());
+ }
+
+ @Test
+ public void shouldDoNothingWhenNameIsPresent() {
+ // входящая информация
+ User user = new User(1L, "", "login", "name", LocalDate.now());
+
+ // настраиваем поведение мока
+ when(userStorage.add(user)).thenReturn(user);
+
+ User result = userService.addUser(user);
+
+ // проверки, что метод вызвался
+ verify(userStorage).add(user);
+
+ // проверка, что значения равны
+ assertEquals("name", result.getName());
+ }
+
+ @Test
+ public void shouldUpdateUser() {
+ User user1 = new User(1L, "email@mail.com", "login", "name", LocalDate.now());
+ User returnUser = new User(1L, "email@mail.com", "login", "NAME", LocalDate.now());
+
+ when(userStorage.contains(any())).thenReturn(true);
+ when(userStorage.update(any(User.class))).thenReturn(returnUser);
+
+ User result = userService.updateUser(user1);
+
+ assertEquals(returnUser, result);
+ verify(userStorage).update(any(User.class));
+ }
+
+ @Test
+ public void shouldThrowValidationExceptionWhenUpdateUserWhenIdNull() {
+ User user1 = new User(null, "email@mail.com", "login", "name", LocalDate.now());
+
+ assertThrows(ValidationException.class, () -> userService.updateUser(user1));
+ verify(userStorage, never()).update(any(User.class));
+ }
+
+ @Test
+ public void shouldThrowNotFoundWhenUpdateUserWhenIdNotFound() {
+ User user1 = new User(1L, "email@mail.com", "login", "name", LocalDate.now());
+
+ when(userStorage.contains(any())).thenReturn(false);
+
+ assertThrows(NotFoundException.class, () -> userService.updateUser(user1));
+ verify(userStorage, never()).update(any(User.class));
+ }
+
+ @Test
+ public void shouldDeleteUser() {
+ Long userId = 1L;
+ User user = new User(userId, "email@mail.com", "login", "name", LocalDate.now());
+
+ when(userStorage.contains(userId)).thenReturn(true);
+ when(friendShipStorage.getFriends(userId)).thenReturn(new HashSet<>(Set.of(2L, 3L)));
+ when(friendShipStorage.getFriends(2L)).thenReturn(new HashSet<>(Set.of(1L, 4L)));
+ when(friendShipStorage.getFriends(3L)).thenReturn(new HashSet<>(Set.of(1L, 5L)));
+
+ userService.deleteUser(userId);
+
+ verify(userStorage).remove(userId);
+ verify(friendShipStorage).clearFriendsSet(userId);
+ verify(friendShipStorage).getFriends(2L);
+ verify(friendShipStorage).getFriends(3L);
+ }
+
+ @Test
+ public void shouldThrowNotFoundWhenDeleteUserWhenIdNotFound() {
+ when(userStorage.contains(1000L)).thenReturn(false);
+
+ assertThrows(NotFoundException.class, () -> userService.deleteUser(1000L));
+ verify(userStorage, never()).remove(1000L);
+ verify(friendShipStorage, never()).clearFriendsSet(1000L);
+ }
+
+ @Test
+ public void shouldRemoveUserFromFriendsFriendsLists() {
+ Long userId = 1L;
+ Set friends = Set.of(2L, 3L);
+
+ Set friend2Friends = new HashSet<>(Set.of(1L, 4L, 5L));
+ Set friend3Friends = new HashSet<>(Set.of(1L, 6L));
+
+ when(userStorage.contains(userId)).thenReturn(true);
+ when(friendShipStorage.getFriends(userId)).thenReturn(friends);
+ when(friendShipStorage.getFriends(2L)).thenReturn(friend2Friends);
+ when(friendShipStorage.getFriends(3L)).thenReturn(friend3Friends);
+
+ userService.deleteUser(userId);
+
+ assertFalse(friend2Friends.contains(userId));
+ assertFalse(friend3Friends.contains(userId));
+ verify(userStorage).remove(userId);
+ }
+
+ @Test
+ public void shouldAddFriendSuccessfully() {
+ Long senderId = 1L;
+ Long receiverId = 2L;
+
+ when(userStorage.contains(senderId)).thenReturn(true);
+ when(userStorage.contains(receiverId)).thenReturn(true);
+
+ userService.addFriend(senderId, receiverId);
+
+ verify(friendShipStorage).addFriend(senderId, receiverId);
+ verify(friendShipStorage).addFriend(receiverId, senderId);
+ }
+
+ @Test
+ public void shouldThrowValidationExceptionWhenAddingSelfAsFriend() {
+ Long userId = 1L;
+
+ ValidationException exception = assertThrows(ValidationException.class,
+ () -> userService.addFriend(userId, userId));
+
+ assertEquals("Сам себя не добавишь - никто не добавит", exception.getMessage());
+ verify(friendShipStorage, never()).addFriend(anyLong(), anyLong());
+ }
+
+ @Test
+ public void shouldThrowNotFoundExceptionWhenSenderNotFound() {
+ Long senderId = 999L;
+ Long receiverId = 2L;
+
+ when(userStorage.contains(senderId)).thenReturn(false);
+
+ assertThrows(NotFoundException.class, () -> userService.addFriend(senderId, receiverId));
+ verify(friendShipStorage, never()).addFriend(anyLong(), anyLong());
+ }
+
+ @Test
+ public void shouldThrowNotFoundExceptionWhenReceiverNotFound() {
+ Long senderId = 1L;
+ Long receiverId = 999L;
+
+ when(userStorage.contains(senderId)).thenReturn(true);
+ when(userStorage.contains(receiverId)).thenReturn(false);
+
+ assertThrows(NotFoundException.class, () -> userService.addFriend(senderId, receiverId));
+ verify(friendShipStorage, never()).addFriend(anyLong(), anyLong());
+ }
+
+ @Test
+ public void shouldDeleteFriendSuccessfully() {
+ Long senderId = 1L;
+ Long receiverId = 2L;
+
+ when(userStorage.contains(senderId)).thenReturn(true);
+ when(userStorage.contains(receiverId)).thenReturn(true);
+
+ userService.deleteFriend(senderId, receiverId);
+
+ verify(friendShipStorage).deleteFriend(senderId, receiverId);
+ verify(friendShipStorage).deleteFriend(receiverId, senderId);
+ }
+
+ @Test
+ public void shouldThrowNotFoundExceptionWhenDeletingFriendAndSenderNotFound() {
+ Long senderId = 999L;
+ Long receiverId = 2L;
+
+ when(userStorage.contains(senderId)).thenReturn(false);
+
+ assertThrows(NotFoundException.class, () -> userService.deleteFriend(senderId, receiverId));
+ verify(friendShipStorage, never()).deleteFriend(anyLong(), anyLong());
+ }
+
+ @Test
+ public void shouldThrowNotFoundExceptionWhenDeletingFriendAndReceiverNotFound() {
+ Long senderId = 1L;
+ Long receiverId = 999L;
+
+ when(userStorage.contains(senderId)).thenReturn(true);
+ when(userStorage.contains(receiverId)).thenReturn(false);
+
+ assertThrows(NotFoundException.class, () -> userService.deleteFriend(senderId, receiverId));
+ verify(friendShipStorage, never()).deleteFriend(anyLong(), anyLong());
+ }
+
+ @Test
+ public void shouldGetFriendsSuccessfully() {
+ Long userId = 1L;
+ Set friendIds = Set.of(2L, 3L);
+ User friend1 = new User(2L, "friend1@mail.com", "friend1", "Friend One", LocalDate.now());
+ User friend2 = new User(3L, "friend2@mail.com", "friend2", "Friend Two", LocalDate.now());
+
+ when(userStorage.contains(userId)).thenReturn(true);
+ when(friendShipStorage.getFriends(userId)).thenReturn(friendIds);
+ when(userStorage.get(2L)).thenReturn(friend1);
+ when(userStorage.get(3L)).thenReturn(friend2);
+
+ Collection friends = userService.getFriends(userId);
+
+ assertNotNull(friends);
+ assertEquals(2, friends.size());
+ assertTrue(friends.contains(friend1));
+ assertTrue(friends.contains(friend2));
+ verify(friendShipStorage).getFriends(userId);
+ verify(userStorage).get(2L);
+ verify(userStorage).get(3L);
+ }
+
+ @Test
+ public void shouldReturnEmptyFriendsList() {
+ Long userId = 1L;
+
+ when(userStorage.contains(userId)).thenReturn(true);
+ when(friendShipStorage.getFriends(userId)).thenReturn(Set.of());
+
+ Collection friends = userService.getFriends(userId);
+
+ assertNotNull(friends);
+ assertTrue(friends.isEmpty());
+ }
+
+ @Test
+ public void shouldThrowNotFoundExceptionWhenGettingFriendsOfNonExistentUser() {
+ Long userId = 999L;
+
+ when(userStorage.contains(userId)).thenReturn(false);
+
+ assertThrows(NotFoundException.class, () -> userService.getFriends(userId));
+ verify(friendShipStorage, never()).getFriends(anyLong());
+ }
+
+ @Test
+ public void shouldGetCommonFriendsSuccessfully() {
+ Long user1Id = 1L;
+ Long user2Id = 2L;
+ Set user1Friends = Set.of(3L, 4L, 5L);
+ Set user2Friends = Set.of(4L, 5L, 6L);
+
+ User commonFriend1 = new User(4L, "common1@mail.com", "common1", "Common One", LocalDate.now());
+ User commonFriend2 = new User(5L, "common2@mail.com", "common2", "Common Two", LocalDate.now());
+
+ when(userStorage.contains(user1Id)).thenReturn(true);
+ when(userStorage.contains(user2Id)).thenReturn(true);
+ when(friendShipStorage.getFriends(user1Id)).thenReturn(user1Friends);
+ when(friendShipStorage.getFriends(user2Id)).thenReturn(user2Friends);
+ when(userStorage.get(4L)).thenReturn(commonFriend1);
+ when(userStorage.get(5L)).thenReturn(commonFriend2);
+
+ Collection commonFriends = userService.getCommonFriends(user1Id, user2Id);
+
+ assertNotNull(commonFriends);
+ assertEquals(2, commonFriends.size());
+ assertTrue(commonFriends.contains(commonFriend1));
+ assertTrue(commonFriends.contains(commonFriend2));
+ }
+
+ @Test
+ public void shouldReturnEmptyCommonFriendsWhenNoCommonFriends() {
+ Long user1Id = 1L;
+ Long user2Id = 2L;
+ Set user1Friends = Set.of(3L, 4L);
+ Set user2Friends = Set.of(5L, 6L);
+
+ when(userStorage.contains(user1Id)).thenReturn(true);
+ when(userStorage.contains(user2Id)).thenReturn(true);
+ when(friendShipStorage.getFriends(user1Id)).thenReturn(user1Friends);
+ when(friendShipStorage.getFriends(user2Id)).thenReturn(user2Friends);
+
+ Collection commonFriends = userService.getCommonFriends(user1Id, user2Id);
+
+ assertNotNull(commonFriends);
+ assertTrue(commonFriends.isEmpty());
+ }
+
+ @Test
+ public void shouldThrowNotFoundExceptionWhenFirstUserNotFoundInCommonFriends() {
+ Long user1Id = 999L;
+ Long user2Id = 2L;
+
+ when(userStorage.contains(user1Id)).thenReturn(false);
+
+ assertThrows(NotFoundException.class, () -> userService.getCommonFriends(user1Id, user2Id));
+ }
+
+ @Test
+ public void shouldThrowNotFoundExceptionWhenSecondUserNotFoundInCommonFriends() {
+ Long user1Id = 1L;
+ Long user2Id = 999L;
+
+ when(userStorage.contains(user1Id)).thenReturn(true);
+ when(userStorage.contains(user2Id)).thenReturn(false);
+
+ assertThrows(NotFoundException.class, () -> userService.getCommonFriends(user1Id, user2Id));
+ }
+}
diff --git a/src/test/java/ru/yandex/practicum/filmorate/storage/film/InMemoryFilmStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/storage/film/InMemoryFilmStorageTest.java
new file mode 100644
index 0000000..b7fb5b2
--- /dev/null
+++ b/src/test/java/ru/yandex/practicum/filmorate/storage/film/InMemoryFilmStorageTest.java
@@ -0,0 +1,81 @@
+package ru.yandex.practicum.filmorate.storage.film;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import ru.yandex.practicum.filmorate.films.film.InMemoryFilmStorage;
+import ru.yandex.practicum.filmorate.model.Film;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class InMemoryFilmStorageTest {
+
+ private FilmStorage storage;
+ private Film film;
+
+ @BeforeEach
+ public void setUp() {
+ storage = new InMemoryFilmStorage();
+ film = new Film();
+ film.setId(1L);
+ }
+
+ @Test
+ public void shouldAddObject() {
+ Film user1 = storage.add(film);
+
+ assertEquals(1L, user1.getId());
+ assertEquals(film, user1);
+ }
+
+ @Test
+ public void shouldUpdateObject() {
+ storage.add(film);
+
+ Film user1 = storage.update(film);
+
+ assertEquals(1L, user1.getId());
+ assertEquals(film, user1);
+ }
+
+ @Test
+ public void shouldRemoveObject() {
+ storage.add(film);
+
+ Film user1 = storage.remove(1L);
+
+ assertEquals(1L, user1.getId());
+ assertEquals(film, user1);
+ assertEquals(0, storage.getAll().size());
+ }
+
+ @Test
+ public void shouldGetObject() {
+ storage.add(film);
+ Film user1 = storage.get(1L);
+
+ assertEquals(1L, user1.getId());
+ assertEquals(film, user1);
+ }
+
+ @Test
+ public void shouldThrowNullPointerExceptionWhenGetObjectThatDoesNotExist() {
+ assertNull(storage.get(1L));
+ }
+
+ @Test
+ public void shouldReturnTrueIfContains() {
+ storage.add(film);
+ assertTrue(storage.contains(1L));
+ }
+
+ @Test
+ public void shouldReturnFalseIfNotContains() {
+ assertFalse(storage.contains(1L));
+ }
+
+ @Test
+ public void shouldReturnSize() {
+ storage.add(film);
+ assertEquals(storage.getAll().size(), storage.size());
+ }
+}
diff --git a/src/test/java/ru/yandex/practicum/filmorate/storage/film/InMemoryLikeStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/storage/film/InMemoryLikeStorageTest.java
new file mode 100644
index 0000000..b51076d
--- /dev/null
+++ b/src/test/java/ru/yandex/practicum/filmorate/storage/film/InMemoryLikeStorageTest.java
@@ -0,0 +1,99 @@
+package ru.yandex.practicum.filmorate.storage.film;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Field;
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class InMemoryLikeStorageTest {
+ private LikeStorage storage;
+
+ @BeforeEach
+ public void setUp() {
+ storage = new InMemoryLikeStorage();
+ }
+
+ private Map> getLikesField() {
+
+ try {
+ Field field = storage.getClass().getDeclaredField("likes");
+ field.setAccessible(true);
+ return (Map>) field.get(storage);
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
+ public void shouldCreateLikesSet() {
+ storage.initializeLikesSet(1L);
+ assertNotNull(getLikesField().get(1L));
+ }
+
+ @Test
+ public void shouldRemoveSet() {
+ storage.initializeLikesSet(1L);
+ storage.clearLikesSet(1L);
+ assertNull(getLikesField().get(1L));
+ }
+
+ @Test
+ public void shouldAddLike() {
+ storage.initializeLikesSet(1L);
+
+ storage.addLike(1L, 1L);
+ storage.addLike(1L, 2L);
+
+ assertEquals(2, getLikesField().get(1L).size());
+ }
+
+ @Test
+ public void shouldAddLikeOnlyOnce() {
+ storage.initializeLikesSet(1L);
+
+ storage.addLike(1L, 1L);
+ storage.addLike(1L, 1L);
+
+ assertEquals(1, getLikesField().get(1L).size());
+ }
+
+ @Test
+ public void shouldThrowNullPointerExceptionWhenAddLikeToFilmThatDoesNotExist() {
+ assertThrows(NullPointerException.class, () -> storage.addLike(1L, 1L));
+ }
+
+ @Test
+ public void shouldRemoveLike() {
+ storage.initializeLikesSet(1L);
+
+ storage.addLike(1L, 1L);
+ storage.addLike(1L, 2L);
+
+ storage.deleteLike(1L, 1L);
+
+ assertEquals(1, getLikesField().get(1L).size());
+ }
+
+ @Test
+ public void shouldRemoveLikeOnlyOnce() {
+ storage.initializeLikesSet(1L);
+
+ storage.addLike(1L, 1L);
+ storage.addLike(1L, 2L);
+
+ storage.deleteLike(1L, 1L);
+ storage.deleteLike(1L, 1L);
+
+ assertEquals(1, getLikesField().get(1L).size());
+ }
+
+ @Test
+ public void shouldThrowNullPointerExceptionWhenRemoveLikeToFilmThatDoesNotExist() {
+ assertThrows(NullPointerException.class, () -> storage.deleteLike(1L, 1L));
+ }
+}
diff --git a/src/test/java/ru/yandex/practicum/filmorate/storage/user/InMemoryFriendShipStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/storage/user/InMemoryFriendShipStorageTest.java
new file mode 100644
index 0000000..058ac5b
--- /dev/null
+++ b/src/test/java/ru/yandex/practicum/filmorate/storage/user/InMemoryFriendShipStorageTest.java
@@ -0,0 +1,102 @@
+package ru.yandex.practicum.filmorate.storage.user;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Field;
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class InMemoryFriendShipStorageTest {
+
+ private FriendShipStorage friendShipStorage;
+
+ @BeforeEach
+ public void setUp() {
+ friendShipStorage = new InMemoryFriendShipStorage();
+ }
+
+ private Map> getFriendShips() {
+
+ try {
+ Field field = friendShipStorage.getClass().getDeclaredField("friendships");
+ field.setAccessible(true);
+ return (Map>) field.get(friendShipStorage);
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
+ public void shouldCreateFriendShip() {
+ friendShipStorage.initializeFriendsSet(1L);
+ friendShipStorage.initializeFriendsSet(2L);
+
+ friendShipStorage.addFriend(1L, 2L);
+ friendShipStorage.addFriend(2L, 1L);
+
+ assertTrue(getFriendShips().get(1L).contains(2L));
+ assertTrue(getFriendShips().get(2L).contains(1L));
+ }
+
+ @Test
+ public void shouldThrowNullPointerExceptionWhenSenderIsNotFoundCreateFriendShip() {
+ assertThrows(NullPointerException.class, () -> friendShipStorage.addFriend(1000L, 1L));
+ }
+
+ @Test
+ public void shouldThrowNullPointerExceptionWhenReceiverIsNotFoundCreateFriendShip() {
+ assertThrows(NullPointerException.class, () -> friendShipStorage.addFriend(1L, 1000L));
+ }
+
+ @Test
+ public void shouldClearUserFriendsAndDeleteUser() {
+ friendShipStorage.initializeFriendsSet(1L);
+ friendShipStorage.clearFriendsSet(1L);
+ assertNull(getFriendShips().get(1L));
+ }
+
+ @Test
+ public void shouldBreakFriendShip() {
+ friendShipStorage.initializeFriendsSet(1L);
+ friendShipStorage.initializeFriendsSet(2L);
+
+ friendShipStorage.addFriend(1L, 2L);
+ friendShipStorage.addFriend(2L, 1L);
+
+ friendShipStorage.deleteFriend(1L, 2L);
+ friendShipStorage.deleteFriend(2L, 1L);
+
+ assertFalse(getFriendShips().get(1L).contains(2L));
+ assertFalse(getFriendShips().get(2L).contains(1L));
+ }
+
+ @Test
+ public void shouldThrowNullPointerExceptionWhenSenderIsNotFoundBreakFriendShip() {
+ assertThrows(NullPointerException.class, () -> friendShipStorage.deleteFriend(1000L, 1L));
+ }
+
+ @Test
+ public void shouldThrowNullPointerExceptionWhenReceiverIsNotFoundBreakFriendShip() {
+ assertThrows(NullPointerException.class, () -> friendShipStorage.deleteFriend(1L, 1000L));
+ }
+
+ @Test
+ public void shouldReturnFriends() {
+ friendShipStorage.initializeFriendsSet(1L);
+ friendShipStorage.initializeFriendsSet(2L);
+
+ friendShipStorage.addFriend(1L, 2L);
+ friendShipStorage.addFriend(2L, 1L);
+
+ assertTrue(friendShipStorage.getFriends(2L).contains(1L));
+ }
+
+ @Test
+ public void shouldThrowNullPointerExceptionWhenUserIsNotFoundReturnFriends() {
+ assertNull(friendShipStorage.getFriends(1000L));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/yandex/practicum/filmorate/storage/user/InMemoryUserStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/storage/user/InMemoryUserStorageTest.java
new file mode 100644
index 0000000..cd48557
--- /dev/null
+++ b/src/test/java/ru/yandex/practicum/filmorate/storage/user/InMemoryUserStorageTest.java
@@ -0,0 +1,96 @@
+package ru.yandex.practicum.filmorate.storage.user;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import ru.yandex.practicum.filmorate.model.User;
+
+import java.lang.reflect.Field;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class InMemoryUserStorageTest {
+
+ private UserStorage storage;
+ private User user;
+
+ @BeforeEach
+ public void setUp() {
+ storage = new InMemoryUserStorage();
+ user = new User();
+ user.setId(1L);
+ }
+
+ private Map> getFriendShips() {
+
+ try {
+ Field field = storage.getClass().getDeclaredField("friendships");
+ field.setAccessible(true);
+ return (Map>) field.get(storage);
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ @Test
+ public void shouldAddObject() {
+ User user1 = storage.add(user);
+
+ assertEquals(1L, user1.getId());
+ assertEquals(user, user1);
+ }
+
+ @Test
+ public void shouldUpdateObject() {
+ storage.add(user);
+
+ User user1 = storage.update(user);
+
+ assertEquals(1L, user1.getId());
+ assertEquals(user, user1);
+ }
+
+ @Test
+ public void shouldRemoveObject() {
+ storage.add(user);
+
+ User user1 = storage.remove(1L);
+
+ assertEquals(1L, user1.getId());
+ assertEquals(user, user1);
+ assertEquals(0, storage.getAll().size());
+ }
+
+ @Test
+ public void shouldGetObject() {
+ storage.add(user);
+ User user1 = storage.get(1L);
+
+ assertEquals(1L, user1.getId());
+ assertEquals(user, user1);
+ }
+
+ @Test
+ public void shouldThrowNullPointerExceptionWhenGetObjectThatDoesNotExist() {
+ assertNull(storage.get(1L));
+ }
+
+ @Test
+ public void shouldReturnTrueIfContains() {
+ storage.add(user);
+ assertTrue(storage.contains(1L));
+ }
+
+ @Test
+ public void shouldReturnFalseIfNotContains() {
+ assertFalse(storage.contains(1L));
+ }
+
+ @Test
+ public void shouldReturnSize() {
+ storage.add(user);
+ assertEquals(storage.getAll().size(), storage.size());
+ }
+}
diff --git a/src/test/java/ru/yandex/practicum/filmorate/validation/BirthdayValidatorTest.java b/src/test/java/ru/yandex/practicum/filmorate/validation/BirthdayValidatorTest.java
deleted file mode 100644
index 6dd425f..0000000
--- a/src/test/java/ru/yandex/practicum/filmorate/validation/BirthdayValidatorTest.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package ru.yandex.practicum.filmorate.validation;
-
-import org.junit.jupiter.api.Test;
-
-import java.time.LocalDate;
-
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-public class BirthdayValidatorTest {
- private final BirthdayValidator validator = new BirthdayValidator();
-
- @Test
- public void shouldReturnTrueForValidDate() {
- LocalDate birthday = LocalDate.of(1595, 1, 28);
- assertTrue(validator.isValid(birthday, null));
- }
-
- @Test
- public void shouldReturnTrueForToday() {
- LocalDate future = LocalDate.now();
- assertTrue(validator.isValid(future, null));
- }
-
- @Test
- public void shouldReturnFalseForFutureDate() {
- LocalDate future = LocalDate.now().plusDays(1);
- assertFalse(validator.isValid(future, null));
- }
-
- @Test
- public void shouldReturnTrueForNull() {
- assertTrue(validator.isValid(null, null));
- }
-}
\ No newline at end of file