diff --git a/backend/src/main/java/com/example/backend/config/SecurityConfig.java b/backend/src/main/java/com/example/backend/config/SecurityConfig.java index 6bfe020..6cc89e8 100644 --- a/backend/src/main/java/com/example/backend/config/SecurityConfig.java +++ b/backend/src/main/java/com/example/backend/config/SecurityConfig.java @@ -9,7 +9,6 @@ import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.HttpStatusEntryPoint; -import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; @Configuration @EnableWebSecurity @@ -21,7 +20,9 @@ public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http - .csrf(AbstractHttpConfigurer::disable) + .csrf((csrf) -> csrf + .ignoringRequestMatchers("/api/**") + ) .authorizeHttpRequests(a -> a .requestMatchers("/api/**").authenticated() .anyRequest().permitAll() diff --git a/backend/src/main/java/com/example/backend/controller/MovieController.java b/backend/src/main/java/com/example/backend/controller/MovieController.java index 8f5531a..da4f5f2 100644 --- a/backend/src/main/java/com/example/backend/controller/MovieController.java +++ b/backend/src/main/java/com/example/backend/controller/MovieController.java @@ -1,12 +1,16 @@ package com.example.backend.controller; import com.example.backend.dto.CreateMovieRequest; +import com.example.backend.dto.MovieRatingResponse; import com.example.backend.dto.MovieResponse; import com.example.backend.model.Movie; +import com.example.backend.model.RatingRepository; import com.example.backend.service.MovieService; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.NonNull; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -17,6 +21,7 @@ @RequestMapping("/api/movie") public class MovieController { private final MovieService movieService; + private final RatingRepository ratingRepository; @PostMapping public MovieResponse save(@RequestBody @NotNull CreateMovieRequest movieRequest) { @@ -48,12 +53,16 @@ public void delete(@PathVariable String id) { } @GetMapping("/watched") - public List getWatchedMovies() { - return movieService.getWatchedMovies().stream().map(MovieResponse::from).collect(Collectors.toList()); + public List getWatchedMovies(@AuthenticationPrincipal OAuth2User user) { + return movieService.getWatchedMovies(user.getAttributes().get("login").toString()).stream() + .map(ratingMoviePair -> MovieRatingResponse.from(ratingMoviePair.getFirst(), ratingMoviePair.getSecond())) + .collect(Collectors.toList()); } @GetMapping("/wishlist") - public List getWishlistedMovies() { - return movieService.getWishlistedMovies().stream().map(MovieResponse::from).collect(Collectors.toList()); + public List getWishlistedMovies(@AuthenticationPrincipal OAuth2User user) { + return movieService.getWishlistedMovies(user.getAttributes().get("login").toString()).stream() + .map(ratingMoviePair -> MovieRatingResponse.from(ratingMoviePair.getFirst(), ratingMoviePair.getSecond())) + .collect(Collectors.toList()); } } diff --git a/backend/src/main/java/com/example/backend/controller/RatingController.java b/backend/src/main/java/com/example/backend/controller/RatingController.java new file mode 100644 index 0000000..1da3776 --- /dev/null +++ b/backend/src/main/java/com/example/backend/controller/RatingController.java @@ -0,0 +1,35 @@ +package com.example.backend.controller; + +import com.example.backend.dto.CreateRatingRequest; +import com.example.backend.dto.RatingResponse; +import com.example.backend.model.Rating; +import com.example.backend.service.RatingService; +import lombok.AllArgsConstructor; +import org.jetbrains.annotations.NotNull; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.web.bind.annotation.*; + +@RestController +@AllArgsConstructor +@RequestMapping("/api/rating") +public class RatingController { + + private final RatingService ratingService; + + @PostMapping + public void save(@RequestBody @NotNull CreateRatingRequest request, @AuthenticationPrincipal OAuth2User user) { + String login = user.getAttributes().get("login").toString(); + Rating rating = request.toRating(login); + + ratingService.save(rating); + } + + @GetMapping("/{movieId}") + public RatingResponse get(@AuthenticationPrincipal OAuth2User user, @PathVariable String movieId) { + String login = user.getAttributes().get("login").toString(); + Rating rating = ratingService.get(login, movieId); + + return RatingResponse.from(rating); + } +} diff --git a/backend/src/main/java/com/example/backend/dto/CreateMovieRequest.java b/backend/src/main/java/com/example/backend/dto/CreateMovieRequest.java index 9416cb8..6aeee2b 100644 --- a/backend/src/main/java/com/example/backend/dto/CreateMovieRequest.java +++ b/backend/src/main/java/com/example/backend/dto/CreateMovieRequest.java @@ -1,19 +1,12 @@ package com.example.backend.dto; -import com.example.backend.model.Actor; import com.example.backend.model.Movie; -import jakarta.validation.constraints.Max; -import jakarta.validation.constraints.Min; import lombok.NonNull; - public record CreateMovieRequest( - @NonNull String name, - boolean isWatched, - @Min(Movie.MIN_RATING) @Max(Movie.MAX_RATING) Integer rating - + @NonNull String name ) { public Movie toMovie() { - return Movie.builder().name(name).isWatched(isWatched).rating(rating).build(); + return Movie.builder().name(name).build(); } } diff --git a/backend/src/main/java/com/example/backend/dto/CreateRatingRequest.java b/backend/src/main/java/com/example/backend/dto/CreateRatingRequest.java new file mode 100644 index 0000000..10fea0c --- /dev/null +++ b/backend/src/main/java/com/example/backend/dto/CreateRatingRequest.java @@ -0,0 +1,18 @@ +package com.example.backend.dto; + +import com.example.backend.model.Movie; +import com.example.backend.model.Rating; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import lombok.NonNull; + + +public record CreateRatingRequest( + @NonNull String movieId, + boolean isWatched, + @Min(Rating.MIN_RATING) @Max(Rating.MAX_RATING) Integer rating +) { + public Rating toRating(String userId) { + return Rating.builder().userId(userId).movieId(movieId).isWatched(isWatched).rating(rating).build(); + } +} diff --git a/backend/src/main/java/com/example/backend/dto/MovieRatingResponse.java b/backend/src/main/java/com/example/backend/dto/MovieRatingResponse.java new file mode 100644 index 0000000..34e1b61 --- /dev/null +++ b/backend/src/main/java/com/example/backend/dto/MovieRatingResponse.java @@ -0,0 +1,18 @@ +package com.example.backend.dto; + +import com.example.backend.model.Movie; +import com.example.backend.model.Rating; +import lombok.Builder; +import lombok.NonNull; + +@Builder +public record MovieRatingResponse(@NonNull String id, @NonNull String movieName, Integer rating, boolean isWatched) { + public static MovieRatingResponse from(Rating rating, Movie movie) { + return MovieRatingResponse.builder() + .id(movie.getId()) + .movieName(movie.getName()) + .rating(rating.getRating()) + .isWatched(rating.isWatched()) + .build(); + } +} diff --git a/backend/src/main/java/com/example/backend/dto/MovieResponse.java b/backend/src/main/java/com/example/backend/dto/MovieResponse.java index 45e9926..42813cf 100644 --- a/backend/src/main/java/com/example/backend/dto/MovieResponse.java +++ b/backend/src/main/java/com/example/backend/dto/MovieResponse.java @@ -1,19 +1,15 @@ package com.example.backend.dto; import com.example.backend.model.Movie; -import jakarta.annotation.Nullable; import lombok.Builder; import lombok.NonNull; @Builder -public record MovieResponse(@NonNull String id, @NonNull String name, boolean isWatched, - @Nullable Integer rating) { +public record MovieResponse(@NonNull String id, @NonNull String name) { public static MovieResponse from(Movie movie) { return MovieResponse.builder() .id(movie.getId()) .name(movie.getName()) - .isWatched(movie.isWatched()) - .rating(movie.getRating()) .build(); } } diff --git a/backend/src/main/java/com/example/backend/dto/RatingResponse.java b/backend/src/main/java/com/example/backend/dto/RatingResponse.java new file mode 100644 index 0000000..032b877 --- /dev/null +++ b/backend/src/main/java/com/example/backend/dto/RatingResponse.java @@ -0,0 +1,19 @@ +package com.example.backend.dto; + +import com.example.backend.model.Rating; +import jakarta.annotation.Nullable; +import lombok.Builder; +import lombok.NonNull; + +@Builder +public record RatingResponse(String userId, String movieId, Integer rating, + boolean isWatched) { + public static RatingResponse from(Rating rating) { + return RatingResponse.builder() + .userId(rating.getUserId()) + .movieId(rating.getMovieId()) + .rating(rating.getRating()) + .isWatched(rating.isWatched()) + .build(); + } +} diff --git a/backend/src/main/java/com/example/backend/model/Movie.java b/backend/src/main/java/com/example/backend/model/Movie.java index b49b876..edd279c 100644 --- a/backend/src/main/java/com/example/backend/model/Movie.java +++ b/backend/src/main/java/com/example/backend/model/Movie.java @@ -11,15 +11,8 @@ @Data @Document("Movie") public class Movie { - public static final int MIN_RATING = 1; - public static final int MAX_RATING = 10; - @Id private String id; - private Integer rating; - - private boolean isWatched; - private String name; } diff --git a/backend/src/main/java/com/example/backend/model/MovieRepository.java b/backend/src/main/java/com/example/backend/model/MovieRepository.java index 46f8f15..a089f80 100644 --- a/backend/src/main/java/com/example/backend/model/MovieRepository.java +++ b/backend/src/main/java/com/example/backend/model/MovieRepository.java @@ -7,7 +7,4 @@ @Repository public interface MovieRepository extends MongoRepository { - List findAllByIsWatchedIsFalse(); - - List findAllByIsWatchedIsTrue(); } diff --git a/backend/src/main/java/com/example/backend/model/Rating.java b/backend/src/main/java/com/example/backend/model/Rating.java new file mode 100644 index 0000000..36b962b --- /dev/null +++ b/backend/src/main/java/com/example/backend/model/Rating.java @@ -0,0 +1,27 @@ +package com.example.backend.model; + +import lombok.*; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@AllArgsConstructor +@NoArgsConstructor +@Builder +@With +@Data +@Document("Rating") +public class Rating { + public static final int MIN_RATING = 1; + public static final int MAX_RATING = 10; + + @Id + private String id; + + private String movieId; + + private String userId; + + private Integer rating; + + private boolean isWatched; +} diff --git a/backend/src/main/java/com/example/backend/model/RatingRepository.java b/backend/src/main/java/com/example/backend/model/RatingRepository.java new file mode 100644 index 0000000..c917707 --- /dev/null +++ b/backend/src/main/java/com/example/backend/model/RatingRepository.java @@ -0,0 +1,18 @@ +package com.example.backend.model; + +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface RatingRepository extends MongoRepository { + Optional findFirstByUserIdAndMovieId(String userId, String movieId); + + List findAllByUserIdAndIsWatchedIsFalse(String userId); + + List findAllByUserIdAndIsWatchedIsTrue(String userId); + + List findAllByMovieIdAndUserId(List movieId, String userId); +} diff --git a/backend/src/main/java/com/example/backend/service/MovieService.java b/backend/src/main/java/com/example/backend/service/MovieService.java index ee5c58a..184abfd 100644 --- a/backend/src/main/java/com/example/backend/service/MovieService.java +++ b/backend/src/main/java/com/example/backend/service/MovieService.java @@ -2,7 +2,11 @@ import com.example.backend.model.Movie; import com.example.backend.model.MovieRepository; +import com.example.backend.model.Rating; +import com.example.backend.model.RatingRepository; +import lombok.NonNull; import lombok.RequiredArgsConstructor; +import org.springframework.data.util.Pair; import org.springframework.stereotype.Service; import java.util.List; @@ -12,9 +16,7 @@ public class MovieService { private final MovieRepository movieRepository; - private final MovieActorService movieActorService; - - private final MovieDirectorService movieDirectorService; + private final RatingRepository ratingRepository; private final IdService idService; @@ -28,12 +30,33 @@ public List getAllMovies() { return movieRepository.findAll(); } - public List getWatchedMovies() { - return movieRepository.findAllByIsWatchedIsTrue(); + public List> getWatchedMovies(@NonNull String userId) { + List ratings = ratingRepository.findAllByUserIdAndIsWatchedIsTrue(userId); + + return getMovieRatings(ratings); + } + + public List> getWishlistedMovies(@NonNull String userId) { + List ratings = ratingRepository.findAllByUserIdAndIsWatchedIsFalse(userId); + + return getMovieRatings(ratings); } - public List getWishlistedMovies() { - return movieRepository.findAllByIsWatchedIsFalse(); + private List> getMovieRatings(List ratings) { + List movieIds = ratings.stream().map(Rating::getMovieId).toList(); + List movies = movieRepository.findAllById(movieIds); + + return ratings.stream().map( + (Rating rating) -> { + return Pair.of( + rating, + movies.stream().filter( + (Movie movie) -> movie.getId().equals(rating.getMovieId()) + ).findFirst().orElse(null) + ); + } + ).filter(ratingMoviePair -> ratingMoviePair.getSecond() != null) + .toList(); } public Movie getMovieById(String movieId) { diff --git a/backend/src/main/java/com/example/backend/service/RatingService.java b/backend/src/main/java/com/example/backend/service/RatingService.java new file mode 100644 index 0000000..382aca5 --- /dev/null +++ b/backend/src/main/java/com/example/backend/service/RatingService.java @@ -0,0 +1,25 @@ +package com.example.backend.service; + +import com.example.backend.model.Rating; +import com.example.backend.model.RatingRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class RatingService { + private final RatingRepository ratingRepository; + + public void save(Rating rating) { + ratingRepository + .findFirstByUserIdAndMovieId(rating.getUserId(), rating.getMovieId()) + .ifPresentOrElse( + (entity) -> ratingRepository.save(entity.withRating(rating.getRating()).withWatched(rating.isWatched())), + () -> ratingRepository.save(rating) + ); + } + + public Rating get(String userId, String movieId) { + return ratingRepository.findFirstByUserIdAndMovieId(userId, movieId).orElseThrow(); + } +} diff --git a/backend/src/test/java/com/example/backend/controller/ActorControllerTest.java b/backend/src/test/java/com/example/backend/controller/ActorControllerTest.java index d99dd89..dd6260d 100644 --- a/backend/src/test/java/com/example/backend/controller/ActorControllerTest.java +++ b/backend/src/test/java/com/example/backend/controller/ActorControllerTest.java @@ -45,12 +45,6 @@ private SecurityMockMvcRequestPostProcessors.OidcLoginRequestPostProcessor mockU @Autowired private ActorRepository actorRepository; - @Autowired - private MovieRepository movieRepository; - - @Autowired - private MovieActorRelationRepository movieActorRelationRepository; - @Test @DirtiesContext void saveTest_correct() throws Exception { diff --git a/backend/src/test/java/com/example/backend/controller/MovieControllerTest.java b/backend/src/test/java/com/example/backend/controller/MovieControllerTest.java index 6a0ca1e..3021450 100644 --- a/backend/src/test/java/com/example/backend/controller/MovieControllerTest.java +++ b/backend/src/test/java/com/example/backend/controller/MovieControllerTest.java @@ -1,12 +1,15 @@ package com.example.backend.controller; -import com.example.backend.model.*; +import com.example.backend.model.Movie; +import com.example.backend.model.MovieRepository; +import com.example.backend.model.Rating; +import com.example.backend.model.RatingRepository; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; -import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors; +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.OidcLoginRequestPostProcessor; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; @@ -14,7 +17,7 @@ import java.util.List; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.hasSize; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oidcLogin; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -26,8 +29,6 @@ class MovieControllerTest { private static final String URL_WATCHED = "/api/movie/watched"; private static final String URL_WISHLIST = "/api/movie/wishlist"; - private static final String ID_FIRST = "1"; - private static final String NAME_FIRST = "Memento"; private static final String NAME_SECOND = "Deadpool"; private static final String NAME_THIRD = "Third"; @@ -35,10 +36,8 @@ class MovieControllerTest { private static final int RATING_ONE = 1; private static final int RATING_TWO = 2; - private SecurityMockMvcRequestPostProcessors.OidcLoginRequestPostProcessor mockUser() { - return oidcLogin().userInfoToken(token -> token - .claim("login", NAME_FIRST)); - } + private static final String USER_FIRST = "First"; + private static final String USER_SECOND = "Second"; @Autowired private MockMvc mockMvc; @@ -46,6 +45,9 @@ private SecurityMockMvcRequestPostProcessors.OidcLoginRequestPostProcessor mockU @Autowired private MovieRepository movieRepository; + @Autowired + private RatingRepository ratingRepository; + @Test @DirtiesContext void saveTest_correct() throws Exception { @@ -54,8 +56,7 @@ void saveTest_correct() throws Exception { .content( """ { - "name": "%s", - "isWatched": true + "name": "%s" } """.formatted(NAME_FIRST) ) @@ -64,7 +65,6 @@ void saveTest_correct() throws Exception { List actualMovies = movieRepository.findAll(); assertEquals(1, actualMovies.size()); assertEquals(NAME_FIRST, actualMovies.getFirst().getName()); - assertEquals(true, actualMovies.getFirst().isWatched()); } @Test @@ -307,9 +307,9 @@ void deleteTest_NonExisting_ID() throws Exception { @Test @DirtiesContext void getWatchedMoviesTest_successful() throws Exception { - Movie movieWatchedFirst = Movie.builder().name(NAME_FIRST).isWatched(true).rating(RATING_ONE).build(); - Movie movieWatchedSecond = Movie.builder().name(NAME_SECOND).isWatched(true).rating(RATING_TWO).build(); - Movie movieWishlisted = Movie.builder().name(NAME_THIRD).isWatched(false).rating(RATING_ONE).build(); + Movie movieWatchedFirst = Movie.builder().name(NAME_FIRST).build(); + Movie movieWatchedSecond = Movie.builder().name(NAME_SECOND).build(); + Movie movieWishlisted = Movie.builder().name(NAME_THIRD).build(); movieRepository.saveAll( List.of( @@ -319,13 +319,24 @@ void getWatchedMoviesTest_successful() throws Exception { ) ); + ratingRepository.saveAll( + List.of( + Rating.builder().movieId(movieWatchedFirst.getId()).userId(USER_FIRST).isWatched(true).rating(RATING_ONE).build(), + Rating.builder().movieId(movieWatchedSecond.getId()).userId(USER_FIRST).isWatched(true).rating(RATING_TWO).build(), + Rating.builder().movieId(movieWishlisted.getId()).userId(USER_FIRST).isWatched(false).rating(RATING_ONE).build(), + Rating.builder().movieId(movieWishlisted.getId()).userId(USER_SECOND).isWatched(true).rating(RATING_TWO).build() + ) + ); + mockMvc.perform(MockMvcRequestBuilders.get(URL_WATCHED) .with(mockUser())) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$", hasSize(2))) - .andExpect(jsonPath("$[0].name").value(NAME_FIRST)) - .andExpect(jsonPath("$[1].name").value(NAME_SECOND)) + .andExpect(jsonPath("$[0].movieName").value(NAME_FIRST)) + .andExpect(jsonPath("$[1].movieName").value(NAME_SECOND)) + .andExpect(jsonPath("$[0].isWatched").value(true)) + .andExpect(jsonPath("$[1].isWatched").value(true)) .andExpect(jsonPath("$[0].rating").value(RATING_ONE)) .andExpect(jsonPath("$[1].rating").value(RATING_TWO)); } @@ -333,18 +344,6 @@ void getWatchedMoviesTest_successful() throws Exception { @Test @DirtiesContext void getWatchedMoviesTest_Unauthorized() throws Exception { - Movie movieWatchedFirst = Movie.builder().name(NAME_FIRST).isWatched(true).rating(RATING_ONE).build(); - Movie movieWatchedSecond = Movie.builder().name(NAME_SECOND).isWatched(true).rating(RATING_TWO).build(); - Movie movieWishlisted = Movie.builder().name(NAME_THIRD).isWatched(false).rating(RATING_ONE).build(); - - movieRepository.saveAll( - List.of( - movieWatchedFirst, - movieWatchedSecond, - movieWishlisted - ) - ); - mockMvc.perform(MockMvcRequestBuilders.get(URL_WATCHED)) .andExpect(MockMvcResultMatchers.status().isUnauthorized()); } @@ -352,15 +351,24 @@ void getWatchedMoviesTest_Unauthorized() throws Exception { @Test @DirtiesContext void getWatchedMoviesTest_empty() throws Exception { - Movie movieWatchedFirst = Movie.builder().name(NAME_FIRST).isWatched(false).rating(RATING_ONE).build(); - Movie movieWatchedSecond = Movie.builder().name(NAME_SECOND).isWatched(false).rating(RATING_TWO).build(); - Movie movieWishlisted = Movie.builder().name(NAME_THIRD).isWatched(false).rating(RATING_ONE).build(); + Movie movieWishlistedFirst = Movie.builder().name(NAME_FIRST).build(); + Movie movieWishlistedSecond = Movie.builder().name(NAME_SECOND).build(); + Movie movieWishlistedThird = Movie.builder().name(NAME_THIRD).build(); movieRepository.saveAll( List.of( - movieWatchedFirst, - movieWatchedSecond, - movieWishlisted + movieWishlistedFirst, + movieWishlistedSecond, + movieWishlistedThird + ) + ); + + ratingRepository.saveAll( + List.of( + Rating.builder().movieId(movieWishlistedFirst.getId()).userId(USER_FIRST).isWatched(false).rating(RATING_ONE).build(), + Rating.builder().movieId(movieWishlistedSecond.getId()).userId(USER_FIRST).isWatched(false).rating(RATING_TWO).build(), + Rating.builder().movieId(movieWishlistedThird.getId()).userId(USER_FIRST).isWatched(false).rating(RATING_ONE).build(), + Rating.builder().movieId(movieWishlistedThird.getId()).userId(USER_SECOND).isWatched(true).rating(RATING_TWO).build() ) ); @@ -374,15 +382,24 @@ void getWatchedMoviesTest_empty() throws Exception { @Test @DirtiesContext void getWishlistedMoviesTest_successful() throws Exception { - Movie movieWatchedFirst = Movie.builder().name(NAME_FIRST).isWatched(false).rating(RATING_ONE).build(); - Movie movieWatchedSecond = Movie.builder().name(NAME_SECOND).isWatched(false).rating(RATING_TWO).build(); - Movie movieWishlisted = Movie.builder().name(NAME_THIRD).isWatched(true).rating(RATING_ONE).build(); + Movie movieWishlistedFirst = Movie.builder().name(NAME_FIRST).build(); + Movie movieWishlistedSecond = Movie.builder().name(NAME_SECOND).build(); + Movie movieWatchedFirst = Movie.builder().name(NAME_THIRD).build(); movieRepository.saveAll( List.of( - movieWatchedFirst, - movieWatchedSecond, - movieWishlisted + movieWishlistedFirst, + movieWishlistedSecond, + movieWatchedFirst + ) + ); + + ratingRepository.saveAll( + List.of( + Rating.builder().movieId(movieWishlistedFirst.getId()).userId(USER_FIRST).isWatched(false).rating(RATING_ONE).build(), + Rating.builder().movieId(movieWishlistedSecond.getId()).userId(USER_FIRST).isWatched(false).rating(RATING_TWO).build(), + Rating.builder().movieId(movieWatchedFirst.getId()).userId(USER_FIRST).isWatched(true).rating(RATING_ONE).build(), + Rating.builder().movieId(movieWatchedFirst.getId()).userId(USER_SECOND).isWatched(false).rating(RATING_TWO).build() ) ); @@ -391,8 +408,10 @@ void getWishlistedMoviesTest_successful() throws Exception { .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$", hasSize(2))) - .andExpect(jsonPath("$[0].name").value(NAME_FIRST)) - .andExpect(jsonPath("$[1].name").value(NAME_SECOND)) + .andExpect(jsonPath("$[0].movieName").value(NAME_FIRST)) + .andExpect(jsonPath("$[1].movieName").value(NAME_SECOND)) + .andExpect(jsonPath("$[0].isWatched").value(false)) + .andExpect(jsonPath("$[1].isWatched").value(false)) .andExpect(jsonPath("$[0].rating").value(RATING_ONE)) .andExpect(jsonPath("$[1].rating").value(RATING_TWO)); } @@ -400,18 +419,6 @@ void getWishlistedMoviesTest_successful() throws Exception { @Test @DirtiesContext void getWishlistedMoviesTest_Unauthorized() throws Exception { - Movie movieWatchedFirst = Movie.builder().name(NAME_FIRST).isWatched(false).rating(RATING_ONE).build(); - Movie movieWatchedSecond = Movie.builder().name(NAME_SECOND).isWatched(false).rating(RATING_TWO).build(); - Movie movieWishlisted = Movie.builder().name(NAME_THIRD).isWatched(true).rating(RATING_ONE).build(); - - movieRepository.saveAll( - List.of( - movieWatchedFirst, - movieWatchedSecond, - movieWishlisted - ) - ); - mockMvc.perform(MockMvcRequestBuilders.get(URL_WISHLIST)) .andExpect(MockMvcResultMatchers.status().isUnauthorized()); } @@ -419,15 +426,24 @@ void getWishlistedMoviesTest_Unauthorized() throws Exception { @Test @DirtiesContext void getWishlistedMoviesTest_empty() throws Exception { - Movie movieWatchedFirst = Movie.builder().name(NAME_FIRST).isWatched(true).rating(RATING_ONE).build(); - Movie movieWatchedSecond = Movie.builder().name(NAME_SECOND).isWatched(true).rating(RATING_TWO).build(); - Movie movieWishlisted = Movie.builder().name(NAME_THIRD).isWatched(true).rating(RATING_ONE).build(); + Movie movieWatchedFirst = Movie.builder().name(NAME_FIRST).build(); + Movie movieWatchedSecond = Movie.builder().name(NAME_SECOND).build(); + Movie movieWatchedThird = Movie.builder().name(NAME_THIRD).build(); movieRepository.saveAll( List.of( movieWatchedFirst, movieWatchedSecond, - movieWishlisted + movieWatchedThird + ) + ); + + ratingRepository.saveAll( + List.of( + Rating.builder().movieId(movieWatchedFirst.getId()).userId(USER_FIRST).isWatched(true).rating(RATING_ONE).build(), + Rating.builder().movieId(movieWatchedSecond.getId()).userId(USER_FIRST).isWatched(true).rating(RATING_TWO).build(), + Rating.builder().movieId(movieWatchedThird.getId()).userId(USER_FIRST).isWatched(true).rating(RATING_ONE).build(), + Rating.builder().movieId(movieWatchedThird.getId()).userId(USER_SECOND).isWatched(false).rating(RATING_TWO).build() ) ); @@ -437,4 +453,9 @@ void getWishlistedMoviesTest_empty() throws Exception { .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$", hasSize(0))); } -} \ No newline at end of file + + private OidcLoginRequestPostProcessor mockUser() { + return oidcLogin().userInfoToken(token -> token + .claim("login", USER_FIRST)); + } +} diff --git a/backend/src/test/java/com/example/backend/controller/RatingControllerTest.java b/backend/src/test/java/com/example/backend/controller/RatingControllerTest.java new file mode 100644 index 0000000..6bb020c --- /dev/null +++ b/backend/src/test/java/com/example/backend/controller/RatingControllerTest.java @@ -0,0 +1,176 @@ +package com.example.backend.controller; + +import com.example.backend.model.Rating; +import com.example.backend.model.RatingRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.OidcLoginRequestPostProcessor; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; + +import java.util.List; +import java.util.Optional; + +import static org.hamcrest.Matchers.hasSize; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oidcLogin; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; + +@SpringBootTest +@AutoConfigureMockMvc +class RatingControllerTest { + private static final String URL_BASE = "/api/rating"; + private static final String URL_WITH_ID = "/api/rating/{id}"; + + private static final String MOVIE_ID_FIRST = "1"; + private static final String MOVIE_ID_SECOND = "2"; + private static final boolean WATCHED_FIRST = true; + private static final boolean WATCHED_SECOND = false; + private static final int RATING_FIRST = 7; + private static final int RATING_SECOND = 8; + private static final String USER_FIRST = "First"; + private static final String USER_SECOND = "Second"; + + @Autowired + private MockMvc mockMvc; + + @Autowired + private RatingRepository ratingRepository; + + @Test + @DirtiesContext + public void saveTest_new() throws Exception { + ratingRepository.saveAll( + List.of( + Rating.builder().movieId(MOVIE_ID_FIRST).userId(USER_SECOND).isWatched(WATCHED_SECOND).rating(RATING_SECOND).build(), + Rating.builder().movieId(MOVIE_ID_SECOND).userId(USER_FIRST).isWatched(WATCHED_SECOND).rating(RATING_SECOND).build(), + Rating.builder().movieId(MOVIE_ID_SECOND).userId(USER_SECOND).isWatched(WATCHED_SECOND).rating(RATING_SECOND).build() + ) + ); + + mockMvc.perform(MockMvcRequestBuilders.post(URL_BASE) + .contentType(MediaType.APPLICATION_JSON) + .content( + """ + { + "movieId": "%s", + "isWatched": %s, + "rating": %s + } + """.formatted(MOVIE_ID_FIRST, WATCHED_FIRST, RATING_FIRST) + ) + .with(mockUser())).andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.status().isOk()); + + Long totalRatings = ratingRepository.count(); + assertEquals(4, totalRatings); + Optional result = ratingRepository.findFirstByUserIdAndMovieId(USER_FIRST, MOVIE_ID_FIRST); + assertTrue(result.isPresent()); + assertEquals(WATCHED_FIRST, result.get().isWatched()); + assertEquals(RATING_FIRST, result.get().getRating()); + } + + @Test + @DirtiesContext + public void saveTest_existent() throws Exception { + ratingRepository.saveAll( + List.of( + Rating.builder().movieId(MOVIE_ID_FIRST).userId(USER_FIRST).isWatched(WATCHED_SECOND).rating(RATING_SECOND).build(), + Rating.builder().movieId(MOVIE_ID_SECOND).userId(USER_FIRST).isWatched(WATCHED_SECOND).rating(RATING_SECOND).build(), + Rating.builder().movieId(MOVIE_ID_SECOND).userId(USER_SECOND).isWatched(WATCHED_SECOND).rating(RATING_SECOND).build() + ) + ); + + mockMvc.perform(MockMvcRequestBuilders.post(URL_BASE) + .contentType(MediaType.APPLICATION_JSON) + .content( + """ + { + "movieId": "%s", + "isWatched": %s, + "rating": %s + } + """.formatted(MOVIE_ID_FIRST, WATCHED_FIRST, RATING_FIRST) + ) + .with(mockUser())).andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.status().isOk()); + + Long totalRatings = ratingRepository.count(); + assertEquals(3, totalRatings); + Optional result = ratingRepository.findFirstByUserIdAndMovieId(USER_FIRST, MOVIE_ID_FIRST); + assertTrue(result.isPresent()); + assertEquals(WATCHED_FIRST, result.get().isWatched()); + assertEquals(RATING_FIRST, result.get().getRating()); + } + + @Test + public void saveTest_unauthorized() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.post(URL_BASE) + .contentType(MediaType.APPLICATION_JSON) + .content( + """ + { + "movieId": "%s", + "isWatched": %s, + "rating": %s + } + """.formatted(MOVIE_ID_FIRST, WATCHED_FIRST, RATING_FIRST) + )) + .andExpect(MockMvcResultMatchers.status().isUnauthorized()); + } + + @Test + @DirtiesContext + void getTest_successful() throws Exception { + ratingRepository.saveAll( + List.of( + Rating.builder().movieId(MOVIE_ID_FIRST).userId(USER_FIRST).isWatched(WATCHED_FIRST).rating(RATING_FIRST).build(), + Rating.builder().movieId(MOVIE_ID_FIRST).userId(USER_SECOND).isWatched(WATCHED_SECOND).rating(RATING_SECOND).build(), + Rating.builder().movieId(MOVIE_ID_SECOND).userId(USER_FIRST).isWatched(WATCHED_SECOND).rating(RATING_SECOND).build(), + Rating.builder().movieId(MOVIE_ID_SECOND).userId(USER_SECOND).isWatched(WATCHED_SECOND).rating(RATING_SECOND).build() + ) + ); + + mockMvc.perform(MockMvcRequestBuilders.get(URL_WITH_ID, MOVIE_ID_FIRST) + .with(mockUser())) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(MockMvcResultMatchers.jsonPath("$.movieId").value(MOVIE_ID_FIRST)) + .andExpect(MockMvcResultMatchers.jsonPath("$.isWatched").value(WATCHED_FIRST)) + .andExpect(MockMvcResultMatchers.jsonPath("$.rating").value(RATING_FIRST)) + .andExpect(MockMvcResultMatchers.jsonPath("$.userId").value(USER_FIRST)); + } + + @Test + @DirtiesContext + void getTest_failed() throws Exception { + ratingRepository.saveAll( + List.of( + Rating.builder().movieId(MOVIE_ID_FIRST).userId(USER_SECOND).isWatched(WATCHED_SECOND).rating(RATING_SECOND).build(), + Rating.builder().movieId(MOVIE_ID_SECOND).userId(USER_FIRST).isWatched(WATCHED_SECOND).rating(RATING_SECOND).build(), + Rating.builder().movieId(MOVIE_ID_SECOND).userId(USER_SECOND).isWatched(WATCHED_SECOND).rating(RATING_SECOND).build() + ) + ); + + mockMvc.perform(MockMvcRequestBuilders.get(URL_WITH_ID, MOVIE_ID_FIRST) + .with(mockUser())) + .andExpect(MockMvcResultMatchers.status().is4xxClientError()); + } + + @Test + void getTest_unauthorized() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get(URL_WITH_ID, MOVIE_ID_FIRST)) + .andExpect(MockMvcResultMatchers.status().isUnauthorized()); + } + + private OidcLoginRequestPostProcessor mockUser() { + return oidcLogin().userInfoToken(token -> token + .claim("login", USER_FIRST)); + } +} \ No newline at end of file