diff --git a/pom.xml b/pom.xml
index 0cad031..00a0703 100644
--- a/pom.xml
+++ b/pom.xml
@@ -27,11 +27,17 @@
lombok
provided
+
org.springframework.boot
spring-boot-starter-test
test
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
diff --git a/src/main/java/ru/yandex/practicum/filmorate/FilmorateApplication.java b/src/main/java/ru/yandex/practicum/filmorate/FilmorateApplication.java
index dca451b..3f2baca 100644
--- a/src/main/java/ru/yandex/practicum/filmorate/FilmorateApplication.java
+++ b/src/main/java/ru/yandex/practicum/filmorate/FilmorateApplication.java
@@ -5,8 +5,7 @@
@SpringBootApplication
public class FilmorateApplication {
- public static void main(String[] args) {
- SpringApplication.run(FilmorateApplication.class, args);
- }
-
+ public static void main(String[] args) {
+ SpringApplication.run(FilmorateApplication.class, args);
+ }
}
diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java
index 08cf0a1..f51bdc0 100644
--- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java
+++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java
@@ -2,6 +2,68 @@
import org.springframework.web.bind.annotation.RestController;
+import jakarta.validation.Valid;
+import ru.yandex.practicum.filmorate.exception.ValidationException;
+import lombok.extern.slf4j.Slf4j;
+import ru.yandex.practicum.filmorate.model.Film;
+import org.springframework.web.bind.annotation.*;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
@RestController
+@RequestMapping("/films")
+@Slf4j
public class FilmController {
+ private final Map films = new HashMap<>();
+ private Long idCounter = 1L;
+
+ private void validateFilm(Film film) {
+ if (film.getName() == null || film.getName().isBlank()) {
+ log.warn("Название фильма не может быть пустым");
+ throw new ValidationException("Название фильма не может быть пустым");
+ }
+ if (film.getDescription() != null && film.getDescription().length() > 200) {
+ log.warn("Максимальная длина описания — 200 символов");
+ throw new ValidationException("Максимальная длина описания — 200 символов");
+ }
+ if (film.getReleaseDate() == null || film.getReleaseDate().isBefore(LocalDate.of(1895, 12, 28))) {
+ log.warn("Дата релиза должна быть не раньше 28 декабря 1895 года");
+ throw new ValidationException("Дата релиза должна быть не раньше 28 декабря 1895 года");
+ }
+ if (film.getDuration() <= 0) {
+ log.warn("Продолжительность фильма должна быть положительным числом");
+ throw new ValidationException("Продолжительность фильма должна быть положительным числом");
+ }
+ }
+
+ @PostMapping
+ public Film addFilm(@Valid @RequestBody Film film) {
+ validateFilm(film);
+ film.setId(idCounter++);
+ films.put(film.getId(), film);
+ log.info("Добавлен фильм с ID {}: {}", film.getId(), film);
+ return film;
+ }
+
+ @PutMapping
+ public Film updateFilm(@Valid @RequestBody Film film) {
+ if (film.getId() == null || !films.containsKey(film.getId())) {
+ log.warn("Фильм с ID {} не найден", film.getId());
+ throw new ValidationException("Фильм с указанным ID не существует");
+ }
+ validateFilm(film);
+ films.put(film.getId(), film);
+ log.info("Обновлен фильм с ID {}: {}", film.getId(), film);
+ return film;
+ }
+
+ @GetMapping
+ public List getAllFilms() {
+ log.info("Получен запрос всех фильмов. Текущее количество: {}", films.size());
+ return new ArrayList<>(films.values());
+ }
}
diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java
new file mode 100644
index 0000000..acff70f
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java
@@ -0,0 +1,67 @@
+package ru.yandex.practicum.filmorate.controller;
+
+import jakarta.validation.Valid;
+import lombok.extern.slf4j.Slf4j;
+import ru.yandex.practicum.filmorate.exception.ValidationException;
+import ru.yandex.practicum.filmorate.model.User;
+import org.springframework.web.bind.annotation.*;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/users")
+@Slf4j
+public class UserController {
+ private final Map users = new HashMap<>();
+ private Long idCounter = 1L;
+
+ private void validateUser(User user) {
+ if (user.getEmail() == null || user.getEmail().isBlank() || !user.getEmail().contains("@")) {
+ log.warn("Электронная почта не может быть пустой и должна содержать символ @");
+ throw new ValidationException("Электронная почта не может быть пустой и должна содержать символ @");
+ }
+ if (user.getLogin() == null || user.getLogin().isBlank() || user.getLogin().contains(" ")) {
+ log.warn("Логин не может быть пустым и содержать пробелы");
+ throw new ValidationException("Логин не может быть пустым и содержать пробелы");
+ }
+ if (user.getName() == null || user.getName().isBlank()) {
+ user.setName(user.getLogin());
+ log.info("Для пользователя {} установлено имя из логина", user.getLogin());
+ }
+ if (user.getBirthday() == null || user.getBirthday().isAfter(LocalDate.now())) {
+ log.warn("Дата рождения не может быть в будущем");
+ throw new ValidationException("Дата рождения не может быть в будущем");
+ }
+ }
+
+ @PostMapping
+ public User createUser(@Valid @RequestBody User user) {
+ validateUser(user);
+ user.setId(idCounter++);
+ users.put(user.getId(), user);
+ log.info("Создан пользователь: {}", user);
+ return user;
+ }
+
+ @PutMapping
+ public User updateUser(@Valid @RequestBody User user) {
+ if (user.getId() == null || !users.containsKey(user.getId())) {
+ log.warn("Пользователь с id {} не найден", user.getId());
+ throw new ValidationException("Пользователь с указанным id не существует");
+ }
+ validateUser(user);
+ users.put(user.getId(), user);
+ log.info("Обновлен пользователь: {}", user);
+ return user;
+ }
+
+ @GetMapping
+ public List getAllUsers() {
+ log.info("Получен запрос всех пользователей. Текущее количество: {}", users.size());
+ return new ArrayList<>(users.values());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/exception/ValidationException.java b/src/main/java/ru/yandex/practicum/filmorate/exception/ValidationException.java
new file mode 100644
index 0000000..532f9bd
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/exception/ValidationException.java
@@ -0,0 +1,7 @@
+package ru.yandex.practicum.filmorate.exception;
+
+public class ValidationException extends RuntimeException {
+ public ValidationException(String message) {
+ super(message);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Film.java b/src/main/java/ru/yandex/practicum/filmorate/model/Film.java
index 3614a44..ae6de4f 100644
--- a/src/main/java/ru/yandex/practicum/filmorate/model/Film.java
+++ b/src/main/java/ru/yandex/practicum/filmorate/model/Film.java
@@ -1,12 +1,28 @@
package ru.yandex.practicum.filmorate.model;
-import lombok.Getter;
-import lombok.Setter;
-
-/**
- * Film.
- */
-@Getter
-@Setter
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+import java.time.LocalDate;
+
+
+@Data
public class Film {
+ private Long id;
+
+ @NotBlank
+ private String name;
+
+ @NotBlank
+ @Size(min = 1, max = 200)
+ private String description;
+
+ @NotNull
+ private LocalDate releaseDate;
+
+ @Min(1)
+ private int duration;
}
diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/User.java b/src/main/java/ru/yandex/practicum/filmorate/model/User.java
new file mode 100644
index 0000000..15af210
--- /dev/null
+++ b/src/main/java/ru/yandex/practicum/filmorate/model/User.java
@@ -0,0 +1,21 @@
+package ru.yandex.practicum.filmorate.model;
+
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+import java.time.LocalDate;
+
+@Data
+public class User {
+ private Long id;
+
+ @NotBlank
+ @Email
+ private String email;
+
+ @NotBlank
+ private String login;
+ private String name;
+ private LocalDate birthday;
+}
\ No newline at end of file
diff --git a/src/test/java/ru/yandex/practicum/filmorate/BoundaryTests.java b/src/test/java/ru/yandex/practicum/filmorate/BoundaryTests.java
new file mode 100644
index 0000000..48e3c78
--- /dev/null
+++ b/src/test/java/ru/yandex/practicum/filmorate/BoundaryTests.java
@@ -0,0 +1,56 @@
+package ru.yandex.practicum.filmorate;
+
+import ru.yandex.practicum.filmorate.controller.FilmController;
+import ru.yandex.practicum.filmorate.controller.UserController;
+import ru.yandex.practicum.filmorate.model.Film;
+import ru.yandex.practicum.filmorate.model.User;
+import org.junit.jupiter.api.Test;
+import java.time.LocalDate;
+import static org.junit.jupiter.api.Assertions.*;
+
+class BoundaryTests {
+ private FilmController filmController = new FilmController();
+ private UserController userController = new UserController();
+
+ @Test
+ void filmValidation_BoundaryValues() {
+ // Проверка минимально допустимой даты релиза
+ Film film1 = new Film();
+ film1.setName("Boundary Film");
+ film1.setDescription("Desc");
+ film1.setReleaseDate(LocalDate.of(1895, 12, 28));
+ film1.setDuration(1);
+ assertDoesNotThrow(() -> filmController.addFilm(film1));
+
+ // Проверка максимальной длины описания
+ Film film2 = new Film();
+ film2.setName("Boundary Film");
+ film2.setDescription("a".repeat(200));
+ film2.setReleaseDate(LocalDate.of(2000, 1, 1));
+ film2.setDuration(1);
+ assertDoesNotThrow(() -> filmController.addFilm(film2));
+ }
+
+ @Test
+ void userValidation_BoundaryValues() {
+ // Проверка сегодняшней даты рождения
+ User user1 = new User();
+ user1.setEmail("test@example.com");
+ user1.setLogin("testlogin");
+ user1.setBirthday(LocalDate.now());
+ assertDoesNotThrow(() -> userController.createUser(user1));
+
+ // Проверка минимально допустимого логина
+ User user2 = new User();
+ user2.setEmail("test@example.com");
+ user2.setLogin("a"); // Минимальный логин
+ user2.setBirthday(LocalDate.of(1990, 1, 1));
+ assertDoesNotThrow(() -> userController.createUser(user2));
+ }
+
+ @Test
+ void nullRequest_ThrowsException() {
+ assertThrows(Exception.class, () -> filmController.addFilm(null));
+ assertThrows(Exception.class, () -> userController.createUser(null));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/yandex/practicum/filmorate/FilmControllerTest.java b/src/test/java/ru/yandex/practicum/filmorate/FilmControllerTest.java
new file mode 100644
index 0000000..0c95ae8
--- /dev/null
+++ b/src/test/java/ru/yandex/practicum/filmorate/FilmControllerTest.java
@@ -0,0 +1,109 @@
+package ru.yandex.practicum.filmorate;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import ru.yandex.practicum.filmorate.controller.FilmController;
+import ru.yandex.practicum.filmorate.exception.ValidationException;
+import ru.yandex.practicum.filmorate.model.Film;
+
+import java.time.LocalDate;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class FilmControllerTest {
+ private FilmController filmController;
+
+ @BeforeEach
+ void setUp() {
+ filmController = new FilmController();
+ }
+
+ @Test
+ void addValidFilm() {
+ Film film = new Film();
+ film.setName("Valid Film");
+ film.setDescription("Valid Description");
+ film.setReleaseDate(LocalDate.of(2000, 1, 1));
+ film.setDuration(120);
+
+ Film addedFilm = filmController.addFilm(film);
+ assertNotNull(addedFilm.getId());
+ assertEquals(1, filmController.getAllFilms().size());
+ }
+
+ @Test
+ void addFilmWithEmptyName() {
+ Film film = new Film();
+ film.setName("");
+ film.setDescription("Description");
+ film.setReleaseDate(LocalDate.of(2000, 1, 1));
+ film.setDuration(120);
+
+ assertThrows(ValidationException.class, () -> filmController.addFilm(film));
+ }
+
+ @Test
+ void addFilmWithLongDescription() {
+ Film film = new Film();
+ film.setName("Name");
+ film.setDescription("A".repeat(201));
+ film.setReleaseDate(LocalDate.of(2000, 1, 1));
+ film.setDuration(120);
+
+ assertThrows(ValidationException.class, () -> filmController.addFilm(film));
+ }
+
+ @Test
+ void addFilmWithEarlyReleaseDate() {
+ Film film = new Film();
+ film.setName("Name");
+ film.setDescription("Description");
+ film.setReleaseDate(LocalDate.of(1895, 12, 27));
+ film.setDuration(120);
+
+ assertThrows(ValidationException.class, () -> filmController.addFilm(film));
+ }
+
+ @Test
+ void addFilmWithNegativeDuration() {
+ Film film = new Film();
+ film.setName("Name");
+ film.setDescription("Description");
+ film.setReleaseDate(LocalDate.of(2000, 1, 1));
+ film.setDuration(-120);
+
+ assertThrows(ValidationException.class, () -> filmController.addFilm(film));
+ }
+
+ @Test
+ void updateFilmWithInvalidId() {
+ Film film = new Film();
+ film.setId(999L);
+ film.setName("Name");
+ film.setDescription("Description");
+ film.setReleaseDate(LocalDate.of(2000, 1, 1));
+ film.setDuration(120);
+
+ assertThrows(ValidationException.class, () -> filmController.updateFilm(film));
+ }
+
+ @Test
+ void getAllFilms() {
+ Film film1 = createTestFilm("Film 1");
+ Film film2 = createTestFilm("Film 2");
+
+ filmController.addFilm(film1);
+ filmController.addFilm(film2);
+
+ assertEquals(2, filmController.getAllFilms().size());
+ }
+
+ private Film createTestFilm(String name) {
+ Film film = new Film();
+ film.setName(name);
+ film.setDescription("Description");
+ film.setReleaseDate(LocalDate.of(2000, 1, 1));
+ film.setDuration(120);
+ return film;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/yandex/practicum/filmorate/UserControllerTest.java b/src/test/java/ru/yandex/practicum/filmorate/UserControllerTest.java
new file mode 100644
index 0000000..a1a9f16
--- /dev/null
+++ b/src/test/java/ru/yandex/practicum/filmorate/UserControllerTest.java
@@ -0,0 +1,113 @@
+package ru.yandex.practicum.filmorate;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import ru.yandex.practicum.filmorate.controller.UserController;
+import ru.yandex.practicum.filmorate.exception.ValidationException;
+import ru.yandex.practicum.filmorate.model.User;
+
+import java.time.LocalDate;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class UserControllerTest {
+ private UserController userController;
+
+ @BeforeEach
+ void setUp() {
+ userController = new UserController();
+ }
+
+ @Test
+ void createValidUser() {
+ User user = new User();
+ user.setEmail("test@mail.com");
+ user.setLogin("testlogin");
+ user.setBirthday(LocalDate.of(1990, 1, 1));
+
+ User createdUser = userController.createUser(user);
+ assertNotNull(createdUser.getId());
+ assertEquals("testlogin", createdUser.getName());
+ assertEquals(1, userController.getAllUsers().size());
+ }
+
+ @Test
+ void createUserWithEmptyEmail() {
+ User user = new User();
+ user.setEmail("");
+ user.setLogin("login");
+ user.setBirthday(LocalDate.of(1990, 1, 1));
+
+ assertThrows(ValidationException.class, () -> userController.createUser(user));
+ }
+
+ @Test
+ void createUserWithInvalidEmail() {
+ User user = new User();
+ user.setEmail("invalid-email");
+ user.setLogin("login");
+ user.setBirthday(LocalDate.of(1990, 1, 1));
+
+ assertThrows(ValidationException.class, () -> userController.createUser(user));
+ }
+
+ @Test
+ void createUserWithEmptyLogin() {
+ User user = new User();
+ user.setEmail("test@mail.com");
+ user.setLogin("");
+ user.setBirthday(LocalDate.of(1990, 1, 1));
+
+ assertThrows(ValidationException.class, () -> userController.createUser(user));
+ }
+
+ @Test
+ void createUserWithLoginContainingSpaces() {
+ User user = new User();
+ user.setEmail("test@mail.com");
+ user.setLogin("login with spaces");
+ user.setBirthday(LocalDate.of(1990, 1, 1));
+
+ assertThrows(ValidationException.class, () -> userController.createUser(user));
+ }
+
+ @Test
+ void createUserWithFutureBirthday() {
+ User user = new User();
+ user.setEmail("test@mail.com");
+ user.setLogin("login");
+ user.setBirthday(LocalDate.now().plusDays(1));
+
+ assertThrows(ValidationException.class, () -> userController.createUser(user));
+ }
+
+ @Test
+ void updateUserWithInvalidId() {
+ User user = new User();
+ user.setId(999L);
+ user.setEmail("test@mail.com");
+ user.setLogin("login");
+ user.setBirthday(LocalDate.of(1990, 1, 1));
+
+ assertThrows(ValidationException.class, () -> userController.updateUser(user));
+ }
+
+ @Test
+ void getAllUsers() {
+ User user1 = createTestUser("user1@mail.com", "user1");
+ User user2 = createTestUser("user2@mail.com", "user2");
+
+ userController.createUser(user1);
+ userController.createUser(user2);
+
+ assertEquals(2, userController.getAllUsers().size());
+ }
+
+ private User createTestUser(String email, String login) {
+ User user = new User();
+ user.setEmail(email);
+ user.setLogin(login);
+ user.setBirthday(LocalDate.of(1990, 1, 1));
+ return user;
+ }
+}
\ No newline at end of file