Commit eb3a7786 authored by Mário Štraus's avatar Mário Štraus
Browse files

feature: recommended-movies-according-to-rating-in-same-genre

parent 33d27181
Loading
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -29,7 +29,9 @@ Returns best rated movie in given genre.

Returns recommended movie according to given movie id. 

### `GET /api/v1/movie-recommender/recommended-movies-according-to-rating-in-same-genre/{id}`

Returns recommended movies according to rating in same genre.
## Testing

You can use swagger with url /api/v1/movie-recommender/swagger-ui/index.html to test the endpoints.
+7 −0
Original line number Diff line number Diff line
@@ -33,4 +33,11 @@ public class MovieRecommenderController {
    public Optional<MovieBasicViewDto> getBestRatedMovieInGenre(@PathVariable UUID id) {
        return movieRecommenderService.getBestRatedMovieInGenre(id);
    }

    @GetMapping("/recommended-movies-according-to-rating-in-same-genre/{id}")
    @Operation(summary = "Find recommended movies according to the rating in the same genre as the movie with the given id")
    @ApiResponse(responseCode = "200", description = "Movie found")
    public List<MovieBasicViewDto> getRecommendedMoviesAccordingToRatingInSameGenre(@PathVariable UUID id) {
        return movieRecommenderService.getRecommendedMoviesAccordingToRatingInSameGenre(id);
    }
}
+54 −0
Original line number Diff line number Diff line
@@ -65,6 +65,42 @@ public class MovieRecommenderService {
                .collect(Collectors.toList());
    }

    public List<MovieBasicViewDto> getRecommendedMoviesAccordingToRatingInSameGenre(UUID movieId) {
        ResponseEntity<MovieBasicViewDto> givenMoviesResponse = restTemplate.getForEntity(
                MOVIES_URL + "/" + movieId,
                MovieBasicViewDto.class
        );

        var givenMovie = givenMoviesResponse.getBody();
        if (givenMovie == null) {
            return Collections.emptyList();
        }

        var genreId = givenMovie.genres()[0].getId();

        ResponseEntity<MovieBasicViewDto[]> moviesWithGivenGenreResponse = restTemplate.getForEntity(
                MOVIES_URL + "?genreId=" + genreId,
                MovieBasicViewDto[].class
        );

        var moviesWithGivenGenre = moviesWithGivenGenreResponse.getBody();

        if (moviesWithGivenGenre == null || moviesWithGivenGenre.length == 0) {
            return Collections.emptyList();
        }

        Map<UUID, Double> movieRatings = Arrays.stream(moviesWithGivenGenre)
                .collect(Collectors.toMap(MovieBasicViewDto::id, this::calculateMovieRating));

        return Arrays.stream(moviesWithGivenGenre)
                .map(movie -> new MovieRating(movie, movieRatings.getOrDefault(movie.id(), 0.0)))
                .sorted()
                .limit(5)
                .map(movieRating -> movieRating.movie)
                .collect(Collectors.toList());

    }

    public Optional<MovieBasicViewDto> getBestRatedMovieInGenre(UUID genreId) {
        ResponseEntity<MovieBasicViewDto[]> moviesWithGivenGenreResponse = restTemplate.exchange(
                MOVIES_URL + "?genreId=" + genreId,
@@ -140,5 +176,23 @@ public class MovieRecommenderService {
        return intersection / union;
    }

    private double calculateMovieRating(MovieBasicViewDto movie){
        ResponseEntity<RatingDto[]> ratingsResponse = restTemplate.getForEntity(
                RATINGS_URL + "/movie/" + movie.id(),
                RatingDto[].class
        );

        var ratings = ratingsResponse.getBody();

        if (ratings == null || ratings.length == 0) {
            return 0;
        }

        return Arrays.stream(ratings)
                .mapToDouble(RatingDto::getScore)
                .average()
                .orElse(0);
    }


}
+42 −1
Original line number Diff line number Diff line
@@ -17,7 +17,6 @@ import java.util.Optional;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

@ExtendWith(MockitoExtension.class)
public class MovieRecommenderRestControllerTest {
@@ -94,4 +93,46 @@ public class MovieRecommenderRestControllerTest {
        // assert
        assertThat(response).containsExactly(movie1, movie2);
    }

    @Test
    void getBestRatedMovieInGenre_noMovies_returnsEmptyOptional() {
        // setup
        UUID id = UUID.randomUUID();
        Mockito.when(movieRecommenderService.getBestRatedMovieInGenre(id)).thenReturn(Optional.empty());

        // act
        var response = movieRecommenderController.getBestRatedMovieInGenre(id);

        // assert
        assertThat(response).isEmpty();
    }

    @Test
    void getRecommendedMoviesAccordingToRatingInSameGenre_noMovies_returnsEmptyList() {
        // setup
        UUID id = UUID.randomUUID();
        Mockito.when(movieRecommenderService.getRecommendedMoviesAccordingToRatingInSameGenre(id)).thenReturn(Collections.emptyList());

        // act
        var response = movieRecommenderController.getRecommendedMoviesAccordingToRatingInSameGenre(id);

        // assert
        assertThat(response).isEmpty();
    }

    @Test
    void getRecommendedMoviesAccordingToRatingInSameGenre_singleMovie_returnsThatMovie() {
        // setup
        UUID id = UUID.randomUUID();
        GenreDto genre = new GenreDto();
        genre.setName("Action");
        var movie = new MovieBasicViewDto(id, "title", 1, 1, new GenreDto[] {genre});
        Mockito.when(movieRecommenderService.getRecommendedMoviesAccordingToRatingInSameGenre(id)).thenReturn(Collections.singletonList(movie));

        // act
        var response = movieRecommenderController.getRecommendedMoviesAccordingToRatingInSameGenre(id);

        // assert
        assertThat(response).containsExactly(movie);
    }
}
+83 −0
Original line number Diff line number Diff line
package cz.muni.fi.iamdb.service;

import cz.muni.fi.iamdb.api.RatingDto;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
@@ -20,6 +21,9 @@ public class MovieRecommenderServiceTest {
    @Value("${app.urls.movies}")
    private String MOVIES_URL;

    @Value("${app.urls.ratings}")
    private String RATINGS_URL;

    @Mock
    private RestTemplate restTemplate;

@@ -60,6 +64,85 @@ public class MovieRecommenderServiceTest {
        // assert
        assertThat(response).isEmpty();
    }

    @Test
    void getRecommendedMovies_noGivenMovie() {
        // setup
        UUID id = UUID.randomUUID();
        GenreDto genre = new GenreDto();
        genre.setName("Action");
        var movie = new MovieBasicViewDto(UUID.randomUUID(), "title", 1, 1, new GenreDto[] {genre});

        Mockito.when(restTemplate.getForEntity(MOVIES_URL, MovieBasicViewDto[].class)).thenReturn(ResponseEntity.ok(new MovieBasicViewDto[] {movie}));

        // act
        var response = movieRecommenderService.getRecommendedMovies(id);

        // assert
        assertThat(response).isEmpty();
    }


    @Test
    void getRecommendedMoviesAccordingToRatingInSameGenre_noGivenMovie() {
        // setup
        UUID id = UUID.randomUUID();
        Mockito.when(restTemplate.getForEntity(MOVIES_URL + "/" + id, MovieBasicViewDto.class)).thenReturn(ResponseEntity.ok(null));

        // act
        var response = movieRecommenderService.getRecommendedMoviesAccordingToRatingInSameGenre(id);

        // assert
        assertThat(response).isEmpty();
    }

    @Test
    void getRecommendedMoviesAccordingToRatingInSameGenre_noMoviesWithGivenGenre() {
        // setup
        UUID id = UUID.randomUUID();
        GenreDto genre = new GenreDto();
        genre.setId(UUID.randomUUID());
        genre.setName("Action");
        var movie = new MovieBasicViewDto(id, "title", 1, 1, new GenreDto[] {genre});

        Mockito.when(restTemplate.getForEntity(MOVIES_URL + "/" + id, MovieBasicViewDto.class)).thenReturn(ResponseEntity.ok(movie));
        Mockito.when(restTemplate.getForEntity(MOVIES_URL + "?genreId=" + genre.getId(), MovieBasicViewDto[].class)).thenReturn(ResponseEntity.ok(new MovieBasicViewDto[0]));

        // act
        var response = movieRecommenderService.getRecommendedMoviesAccordingToRatingInSameGenre(id);

        // assert
        assertThat(response).isEmpty();
    }

    @Test
    void getRecommendedMoviesAccordingToRatingInSameGenre() {
        // setup
        UUID id = UUID.randomUUID();
        GenreDto genre = new GenreDto();
        genre.setName("Action");
        var movie = new MovieBasicViewDto(id, "title", 1, 1, new GenreDto[] {genre});

        UUID id_2 = UUID.randomUUID();
        GenreDto genre_2 = new GenreDto();
        genre_2.setName("Action");
        var movie_2 = new MovieBasicViewDto(id_2, "title2", 2, 2, new GenreDto[] {genre_2});

        RatingDto rating = new RatingDto();
        rating.setScore(5.0);
        rating.setMovieId(id_2);

        Mockito.when(restTemplate.getForEntity(MOVIES_URL + "/" + id, MovieBasicViewDto.class)).thenReturn(ResponseEntity.ok(movie));
        Mockito.when(restTemplate.getForEntity(MOVIES_URL + "?genreId=" + genre.getId(), MovieBasicViewDto[].class)).thenReturn(ResponseEntity.ok(new MovieBasicViewDto[] {movie_2}));
        Mockito.when(restTemplate.getForEntity(RATINGS_URL + "/movie/" + id_2, RatingDto[].class))
                .thenReturn(ResponseEntity.ok(new RatingDto[] {rating}));
        // act
        var response = movieRecommenderService.getRecommendedMoviesAccordingToRatingInSameGenre(id);

        // assert
        assertThat(response).containsExactly(movie_2);
    }

}