Adding more unit and integration tests

This commit is contained in:
jhouse
2019-10-09 15:18:47 +09:00
parent 75eab0f2cf
commit a7ac2cf264
7 changed files with 590 additions and 94 deletions
+6 -4
View File
@@ -10,6 +10,8 @@
package com.jasonhhouse.Gaps;
import okhttp3.HttpUrl;
import java.util.List;
import java.util.Objects;
@@ -29,14 +31,14 @@ public class Gaps {
private Integer readTimeout;
private List<String> movieUrls;
private List<HttpUrl> movieUrls;
private Boolean searchFromFolder;
public Gaps() {
}
public Gaps(String movieDbApiKey, Boolean writeToFile, String movieDbListId, Boolean searchFromPlex, Integer connectTimeout, Integer writeTimeout, Integer readTimeout, List<String> movieUrls, Boolean searchFromFolder) {
public Gaps(String movieDbApiKey, Boolean writeToFile, String movieDbListId, Boolean searchFromPlex, Integer connectTimeout, Integer writeTimeout, Integer readTimeout, List<HttpUrl> movieUrls, Boolean searchFromFolder) {
this.movieDbApiKey = movieDbApiKey;
this.writeToFile = writeToFile;
this.movieDbListId = movieDbListId;
@@ -104,11 +106,11 @@ public class Gaps {
this.readTimeout = readTimeout;
}
public List<String> getMovieUrls() {
public List<HttpUrl> getMovieUrls() {
return movieUrls;
}
public void setMovieUrls(List<String> movieUrls) {
public void setMovieUrls(List<HttpUrl> movieUrls) {
this.movieUrls = movieUrls;
}
@@ -128,11 +128,10 @@ public class GapsControllerImpl {
Exception e = new IllegalArgumentException();
throw new ResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY, reason, e);
} else {
for (String url : gaps.getMovieUrls()) {
if (StringUtils.isEmpty(url)) {
String reason = "Found empty Plex movie collection url. This field is required to search from Plex.";
for (HttpUrl url : gaps.getMovieUrls()) {
if (url == null) {
String reason = "Found null Plex movie collection url. This field is required to search from Plex.";
logger.error(reason);
Exception e = new IllegalArgumentException();
throw new ResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY, reason, e);
}
@@ -20,17 +20,18 @@ import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ResponseStatusException;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.validation.constraints.NotEmpty;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
@@ -64,10 +65,14 @@ public class GapsSearchBean implements GapsSearch {
private final AtomicBoolean cancelSearch;
public GapsSearchBean() {
private final UrlGenerator urlGenerator;
@Autowired
public GapsSearchBean(UrlGenerator urlGenerator) {
this.ownedMovies = new HashSet<>();
this.searched = new HashSet<>();
this.recommended = new ArrayList<>();
this.urlGenerator = urlGenerator;
totalMovieCount = new AtomicInteger();
searchedMovieCount = new AtomicInteger();
@@ -193,7 +198,7 @@ public class GapsSearchBean implements GapsSearch {
try (Response response = client.newCall(request).execute()) {
String body = response.body() != null ? response.body().string() : null;
if (body == null) {
if (StringUtils.isBlank(body)) {
String reason = "Body returned null from Plex. Url: " + url;
logger.error(reason);
throw new IllegalStateException(reason);
@@ -215,15 +220,38 @@ public class GapsSearchBean implements GapsSearch {
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
String type = node.getAttributes().getNamedItem("type").getNodeValue();
NamedNodeMap map = node.getAttributes();
Node namedItem = map.getNamedItem("type");
if (namedItem == null) {
String reason = "Error finding 'type' inside /MediaContainer/Directory";
logger.error(reason);
throw new ResponseStatusException(HttpStatus.NOT_FOUND, reason);
}
String type = namedItem.getNodeValue();
if (type.equals("movie")) {
String title = node.getAttributes().getNamedItem("title").getNodeValue().replaceAll(":", "");
Integer key = Integer.valueOf(node.getAttributes().getNamedItem("key").getNodeValue());
NamedNodeMap attributes = node.getAttributes();
Node titleNode = attributes.getNamedItem("title");
Node keyNode = attributes.getNamedItem("key");
if (titleNode == null) {
String reason = "Error finding 'title' inside /MediaContainer/Directory";
logger.error(reason);
throw new ResponseStatusException(HttpStatus.NOT_FOUND, reason);
}
if (keyNode == null) {
String reason = "Error finding 'key' inside /MediaContainer/Directory";
logger.error(reason);
throw new ResponseStatusException(HttpStatus.NOT_FOUND, reason);
}
String title = titleNode.getNodeValue().replaceAll(":", "");
Integer key = Integer.valueOf(keyNode.getNodeValue().trim());
PlexLibrary plexLibrary = new PlexLibrary(key, title);
plexLibraries.add(plexLibrary);
}
}
@@ -339,11 +367,21 @@ public class GapsSearchBean implements GapsSearch {
// Create the request_token request
OkHttpClient client = new OkHttpClient();
HttpUrl url = new HttpUrl.Builder()
.scheme("http")
.host("api.themoviedb.org")
.addPathSegment("3")
.addPathSegment("authentication")
.addPathSegment("token")
.addPathSegment("new")
.addQueryParameter("api_key", gaps.getMovieDbApiKey())
.build();
MediaType mediaType = MediaType.parse("application/octet-stream");
RequestBody.create("{}", mediaType);
RequestBody body;
Request request = new Request.Builder()
.url("https://api.themoviedb.org/3/authentication/token/new?api_key=" + gaps.getMovieDbApiKey())
.url(url)
.get()
.build();
@@ -366,11 +404,21 @@ public class GapsSearchBean implements GapsSearch {
return null;
}
url = new HttpUrl.Builder()
.scheme("http")
.host("api.themoviedb.org")
.addPathSegment("3")
.addPathSegment("authentication")
.addPathSegment("session")
.addPathSegment("new")
.addQueryParameter("api_key", gaps.getMovieDbApiKey())
.build();
// Create the sesssion ID for MovieDB using the approved token
mediaType = MediaType.parse("application/json");
body = RequestBody.create("{\"request_token\":\"" + request_token + "\"}", mediaType);
request = new Request.Builder()
.url("https://api.themoviedb.org/3/authentication/session/new?api_key=" + gaps.getMovieDbApiKey())
.url(url)
.post(body)
.addHeader("content-type", "application/json")
.build();
@@ -400,8 +448,18 @@ public class GapsSearchBean implements GapsSearch {
client = new OkHttpClient();
body = RequestBody.create("{\"media_id\":" + m.getMedia_id() + "}", mediaType);
String url = "https://api.themoviedb.org/3/list/" + gaps.getMovieDbListId()
+ "/add_item?session_id=" + sessionId + "&api_key=" + gaps.getMovieDbApiKey();
HttpUrl url = new HttpUrl.Builder()
.scheme("https")
.host("api.themoviedb.org")
.addPathSegment("3")
.addPathSegment("list")
.addPathSegment(gaps.getMovieDbListId())
.addPathSegment("add_item")
.addQueryParameter("session_id", sessionId)
.addQueryParameter("api_key", gaps.getMovieDbApiKey())
.build();
Request request = new Request.Builder()
.url(url)
.post(body)
@@ -432,14 +490,14 @@ public class GapsSearchBean implements GapsSearch {
.writeTimeout(gaps.getWriteTimeout(), TimeUnit.SECONDS)
.readTimeout(gaps.getReadTimeout(), TimeUnit.SECONDS)
.build();
List<String> urls = gaps.getMovieUrls();
List<HttpUrl> urls = gaps.getMovieUrls();
if (CollectionUtils.isEmpty(urls)) {
logger.info("No URLs added to plexMovieUrls. Check your application.yaml file if needed.");
return;
}
for (String url : urls) {
for (HttpUrl url : urls) {
//Cancel search if needed
if (cancelSearch.get()) {
throw new SearchCancelledException("Search was cancelled");
@@ -453,9 +511,10 @@ public class GapsSearchBean implements GapsSearch {
try (Response response = client.newCall(request).execute()) {
String body = response.body() != null ? response.body().string() : null;
if (body == null) {
logger.error("Body returned null from Plex");
return;
if (StringUtils.isBlank(body)) {
String reason = "Body returned empty from Plex";
logger.error(reason);
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, reason);
}
InputStream fileIS = new ByteArrayInputStream(body.getBytes());
@@ -469,12 +528,22 @@ public class GapsSearchBean implements GapsSearch {
if (nodeList.getLength() == 0) {
String reason = "No movies found in url: " + url;
logger.warn(reason);
continue;
}
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
Node nodeTitle = node.getAttributes().getNamedItem("title");
if (nodeTitle == null) {
String reason = "Missing title from Video element in Plex";
logger.error(reason);
throw new NullPointerException(reason);
}
//Files can't have : so need to remove to find matches correctly
String title = node.getAttributes().getNamedItem("title").getNodeValue().replaceAll(":", "");
String title = nodeTitle.getNodeValue().replaceAll(":", "");
if (node.getAttributes().getNamedItem("year") == null) {
logger.warn("Year not found for " + title);
continue;
@@ -495,7 +564,7 @@ public class GapsSearchBean implements GapsSearch {
logger.error(reason, e);
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, reason, e);
}
} catch (IllegalArgumentException e) {
} catch (IllegalArgumentException | NullPointerException e) {
String reason = "Error with plex Url: " + url;
logger.error(reason, e);
throw new ResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY, reason, e);
@@ -516,12 +585,11 @@ public class GapsSearchBean implements GapsSearch {
OkHttpClient client = new OkHttpClient();
if (StringUtils.isEmpty(gaps.getMovieDbApiKey())) {
logger.error("No MovieDb Key added to movieDbApiKey. Check your application.yaml file.");
logger.error("No MovieDb Key added to movieDbApiKey. Need to submit movieDbApiKey on each request.");
return;
}
for (Movie movie : ownedMovies) {
//Cancel search if needed
if (cancelSearch.get()) {
throw new SearchCancelledException("Search was cancelled");
@@ -537,14 +605,9 @@ public class GapsSearchBean implements GapsSearch {
continue;
}
String searchMovieUrl;
HttpUrl searchMovieUrl;
try {
searchMovieUrl = "https://api.themoviedb.org/3/search/movie?api_key=" +
gaps.getMovieDbApiKey() +
"&language=en-US&page=1&include_adult=false&query=" +
URLEncoder.encode(movie.getName(), "UTF-8") +
"&year=" +
movie.getYear();
searchMovieUrl = urlGenerator.generateSearchMovieUrl(gaps.getMovieDbApiKey(), URLEncoder.encode(movie.getName(), "UTF-8"), String.valueOf(movie.getYear()));
Request request = new Request.Builder()
.url(searchMovieUrl)
@@ -576,7 +639,7 @@ public class GapsSearchBean implements GapsSearch {
JSONObject result = results.getJSONObject(0);
int id = result.getInt("id");
String movieDetailUrl = "https://api.themoviedb.org/3/movie/" + id + "?api_key=" + gaps.getMovieDbApiKey() + "&language=en-US";
HttpUrl movieDetailUrl = urlGenerator.generateMovieDetailUrl(gaps.getMovieDbApiKey(), String.valueOf(id));
request = new Request.Builder()
.url(movieDetailUrl)
@@ -598,50 +661,7 @@ public class GapsSearchBean implements GapsSearch {
continue;
}
int collectionId = movieDetails.getJSONObject("belongs_to_collection").getInt("id");
String collectionName = movieDetails.getJSONObject("belongs_to_collection").getString("name");
String collectionUrl = "https://api.themoviedb.org/3/collection/" + collectionId + "?api_key=" + gaps.getMovieDbApiKey() + "&language=en-US";
request = new Request.Builder()
.url(collectionUrl)
.build();
try (Response collectionResponse = client.newCall(request).execute()) {
String collectionJson = collectionResponse.body() != null ? collectionResponse.body().string() : null;
if (collectionJson == null) {
logger.error("Body returned null from TheMovieDB for collection information about " + movie.getName());
continue;
}
JSONObject collection = new JSONObject(collectionJson);
JSONArray parts = collection.getJSONArray("parts");
for (int i = 0; i < parts.length(); i++) {
JSONObject part = parts.getJSONObject(i);
int media_id = part.getInt("id");
//Files can't have : so need to remove to find matches correctly
String title = part.getString("original_title").replaceAll(":", "");
int year;
try {
year = Integer.parseInt(part.getString("release_date").substring(0, 4));
} catch (StringIndexOutOfBoundsException | NumberFormatException e) {
logger.warn("No year found for " + title + ". Value returned was '" + part.getString("release_date") + "'. Not adding the movie to recommended list.");
continue;
}
Movie movieFromCollection = new Movie(media_id, title, year, collectionName);
if (ownedMovies.contains(movieFromCollection)) {
searched.add(movieFromCollection);
} else if (!searched.contains(movieFromCollection) && year != 0 && year < 2019) {
recommended.add(movieFromCollection);
}
}
searched.add(movie);
} catch (IOException e) {
logger.error("Error getting collections " + movie, e);
}
handleCollection(movie, gaps, client, movieDetails);
} catch (IOException e) {
logger.error("Error getting movie details " + movie, e);
@@ -660,7 +680,6 @@ public class GapsSearchBean implements GapsSearch {
} catch (InterruptedException e) {
logger.error("Error sleeping", e);
}
}
} catch (UnsupportedEncodingException e) {
logger.error("Error parsing movie URL " + movie, e);
@@ -668,6 +687,54 @@ public class GapsSearchBean implements GapsSearch {
}
}
private void handleCollection(Movie movie, Gaps gaps, OkHttpClient client, JSONObject movieDetails) {
int collectionId = movieDetails.getJSONObject("belongs_to_collection").getInt("id");
String collectionName = movieDetails.getJSONObject("belongs_to_collection").getString("name");
HttpUrl collectionUrl = urlGenerator.generateCollectionUrl(gaps.getMovieDbApiKey(), String.valueOf(collectionId));
Request request = new Request.Builder()
.url(collectionUrl)
.build();
try (Response collectionResponse = client.newCall(request).execute()) {
String collectionJson = collectionResponse.body() != null ? collectionResponse.body().string() : null;
if (collectionJson == null) {
logger.error("Body returned null from TheMovieDB for collection information about " + movie.getName());
return;
}
JSONObject collection = new JSONObject(collectionJson);
JSONArray parts = collection.getJSONArray("parts");
for (int i = 0; i < parts.length(); i++) {
JSONObject part = parts.getJSONObject(i);
int media_id = part.getInt("id");
//Files can't have : so need to remove to find matches correctly
String title = part.getString("original_title").replaceAll(":", "");
int year;
try {
year = Integer.parseInt(part.getString("release_date").substring(0, 4));
} catch (StringIndexOutOfBoundsException | NumberFormatException e) {
logger.warn("No year found for " + title + ". Value returned was '" + part.getString("release_date") + "'. Not adding the movie to recommended list.");
continue;
}
Movie movieFromCollection = new Movie(media_id, title, year, collectionName);
if (ownedMovies.contains(movieFromCollection)) {
searched.add(movieFromCollection);
} else if (!searched.contains(movieFromCollection) && year != 0 && year < 2019) {
recommended.add(movieFromCollection);
}
}
} catch (IOException e) {
logger.error("Error getting collections " + movie, e);
}
searched.add(movie);
}
/**
* Prints out all recommended files to the terminal or command line
*/
@@ -0,0 +1,52 @@
package com.jasonhhouse.Gaps;
import okhttp3.HttpUrl;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;
@Service
public class GapsUrlGenerator implements UrlGenerator {
@Override
public @NotNull HttpUrl generateSearchMovieUrl(String movieDbKey, String query, String year) {
return new HttpUrl.Builder()
.scheme("https")
.host("api.themoviedb.org")
.addPathSegment("3")
.addPathSegment("search")
.addPathSegment("movie")
.addQueryParameter("api_key", movieDbKey)
.addQueryParameter("language", "en-US")
.addQueryParameter("page", "1")
.addQueryParameter("include_adult", "false")
.addQueryParameter("query", query)
.addQueryParameter("year", year)
.build();
}
@Override
public @NotNull HttpUrl generateMovieDetailUrl(String movieDbKey, String movieId) {
return new HttpUrl.Builder()
.scheme("https")
.host("api.themoviedb.org")
.addPathSegment("3")
.addPathSegment("movie")
.addPathSegment(movieId)
.addQueryParameter("api_key", movieDbKey)
.addQueryParameter("language", "en-US")
.build();
}
@Override
public @NotNull HttpUrl generateCollectionUrl(String movieDbKey, String collectionId) {
return new HttpUrl.Builder()
.scheme("https")
.host("api.themoviedb.org")
.addPathSegment("3")
.addPathSegment("collection")
.addPathSegment(collectionId)
.addQueryParameter("api_key", movieDbKey)
.addQueryParameter("language", "en-US")
.build();
}
}
@@ -0,0 +1,12 @@
package com.jasonhhouse.Gaps;
import okhttp3.HttpUrl;
import org.jetbrains.annotations.NotNull;
public interface UrlGenerator {
@NotNull HttpUrl generateSearchMovieUrl(String movieDbKey, String query, String year);
@NotNull HttpUrl generateMovieDetailUrl(String movieDbKey, String movieId);
@NotNull HttpUrl generateCollectionUrl(String movieDbKey, String collectionId);
}
@@ -17,15 +17,17 @@ import org.junit.jupiter.api.Test;
import org.springframework.http.ResponseEntity;
import org.springframework.web.server.ResponseStatusException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Future;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class GapsSearchBeanTest {
private final static String PLEX_XML = "<MediaContainer size=\"3\" allowSync=\"0\" identifier=\"com.plexapp.plugins.library\" mediaTagPrefix=\"/system/bundle/media/flags/\"\n" +
private final static String GOOD_PLEX_XML = "<MediaContainer size=\"3\" allowSync=\"0\" identifier=\"com.plexapp.plugins.library\" mediaTagPrefix=\"/system/bundle/media/flags/\"\n" +
"mediaTagVersion=\"1390169701\" title1=\"Plex Library\">\n" +
" <Directory allowSync=\"0\" art=\"/:/resources/movie-fanart.jpg\" filters=\"1\" refreshing=\"0\" thumb=\"/:/resources/movie.png\"\n" +
" key=\"29\" type=\"movie\" title=\"Movies\" agent=\"com.plexapp.agents.imdb\" scanner=\"Plex Movie Scanner\"\n" +
@@ -33,7 +35,7 @@ public class GapsSearchBeanTest {
" <Location id=\"4\" path=\"/Users/plexuser/Movies/Media/Movies\"/>\n" +
" </Directory>\n" +
" <Directory allowSync=\"0\" art=\"/:/resources/artist-fanart.jpg\" filters=\"1\" refreshing=\"0\" thumb=\"/:/resources/artist.png\"\n" +
" key=\"31\" type=\"artist\" title=\"Music\" agent=\"com.plexapp.agents.lastfm\" scanner=\"Plex Music Scanner\"\n" +
" key=\"31\" type=\"movie\" title=\"Movies HD\" agent=\"com.plexapp.agents.lastfm\" scanner=\"Plex Music Scanner\"\n" +
" language=\"en\" uuid=\"10254ef0-a0a4-481b-ad9c-46ab3db39d0b\" updatedAt=\"1394039950\"\n" +
" createdAt=\"1390440566\">\n" +
" <Location id=\"7\" path=\"/Users/plexuser/Movies/Media/Music\"/>\n" +
@@ -46,7 +48,17 @@ public class GapsSearchBeanTest {
" </Directory>\n" +
"</MediaContainer>";
private final GapsSearchBean gapsSearch = new GapsSearchBean();
private final static String MISSING_TYPE_PLEX_XML = "<MediaContainer><Directory></Directory></MediaContainer>";
private final static String MISSING_TITLE_PLEX_XML = "<MediaContainer><Directory type=\"movie\"></Directory></MediaContainer>";
private final static String MISSING_KEY_PLEX_XML = "<MediaContainer><Directory type=\"movie\" title=\"Movies\"></Directory></MediaContainer>";
private final static String NON_NUMBER_KEY_PLEX_XML = "<MediaContainer><Directory type=\"movie\" title=\"Movies\" key=\"a\" ></Directory></MediaContainer>";
private final static String NO_MOVIE_PLEX_XML = "<MediaContainer></MediaContainer>";
private final static String EMPTY_MOVIE_PLEX_XML = "<MediaContainer><Video></Video></MediaContainer>";
private final static String GOOD_MOVIE_PLEX_XML = "<MediaContainer><Video title=\"Alien\" year=\"1979\"></Video></MediaContainer>";
private final GapsUrlGeneratorTest gapsUrlGeneratorTest = new GapsUrlGeneratorTest();
private final GapsSearchBean gapsSearch = new GapsSearchBean(gapsUrlGeneratorTest);
private final Gaps gaps = new Gaps();
@Test
@@ -79,20 +91,19 @@ public class GapsSearchBeanTest {
ResponseEntity response = completedFuture.get();
if (response.getBody() instanceof List) {
List recommended = (List) response.getBody();
assertEquals(recommended.size(), 0);
assertEquals(recommended.size(), 0, "Shouldn't have found any movies");
}
}
@Test
void emptyXmlFromPlex() throws Exception {
void emptyLibraryXmlFromPlex() throws Exception {
// Create a MockWebServer. These are lean enough that you can create a new
// instance for every unit test.
MockWebServer server = new MockWebServer();
MockResponse response = new MockResponse()
.addHeader("Content-Type", "application/json; charset=utf-8")
.addHeader("Cache-Control", "no-cache")
.setBody("<xml></xml>");
.addHeader("Cache-Control", "no-cache");
// Schedule some responses.
server.enqueue(response);
@@ -103,12 +114,13 @@ public class GapsSearchBeanTest {
// Ask the server for its URL. You'll need this to make HTTP requests.
HttpUrl baseUrl = server.url("library/sections?X-Plex-Token=fake");
Set<PlexLibrary> plexLibraries = gapsSearch.getPlexLibraries(baseUrl);
assertTrue(plexLibraries.isEmpty());
assertThrows(IllegalStateException.class, () -> {
gapsSearch.getPlexLibraries(baseUrl);
}, "Should throw exception with for an empty body");
}
@Test
void validXmlFromPlex() throws Exception {
void validLibraryXmlFromPlex() throws Exception {
// Create a MockWebServer. These are lean enough that you can create a new
// instance for every unit test.
MockWebServer server = new MockWebServer();
@@ -116,7 +128,7 @@ public class GapsSearchBeanTest {
MockResponse response = new MockResponse()
.addHeader("Content-Type", "application/json; charset=utf-8")
.addHeader("Cache-Control", "no-cache")
.setBody(PLEX_XML);
.setBody(GOOD_PLEX_XML);
// Schedule some responses.
server.enqueue(response);
@@ -128,6 +140,266 @@ public class GapsSearchBeanTest {
HttpUrl baseUrl = server.url("library/sections?X-Plex-Token=fake");
Set<PlexLibrary> plexLibraries = gapsSearch.getPlexLibraries(baseUrl);
assertEquals(plexLibraries.size(), 1, "Should have found exactly one library");
assertEquals(plexLibraries.size(), 2, "Should have found exactly two libraries");
}
@Test
void badLibraryXmlFromPlex() throws Exception {
// Create a MockWebServer. These are lean enough that you can create a new
// instance for every unit test.
MockWebServer server = new MockWebServer();
MockResponse response = new MockResponse()
.addHeader("Content-Type", "application/json; charset=utf-8")
.addHeader("Cache-Control", "no-cache")
.setBody(MISSING_TYPE_PLEX_XML);
// Schedule some responses.
server.enqueue(response);
// Start the server.
server.start();
// Ask the server for its URL. You'll need this to make HTTP requests.
HttpUrl baseUrl = server.url("library/sections?X-Plex-Token=fake");
assertThrows(ResponseStatusException.class, () -> {
gapsSearch.getPlexLibraries(baseUrl);
}, "Should throw exception for missing 'type' node inside /MediaContainer/Directory element");
}
@Test
void missingTitleFromPlex() throws Exception {
// Create a MockWebServer. These are lean enough that you can create a new
// instance for every unit test.
MockWebServer server = new MockWebServer();
MockResponse response = new MockResponse()
.addHeader("Content-Type", "application/json; charset=utf-8")
.addHeader("Cache-Control", "no-cache")
.setBody(MISSING_TITLE_PLEX_XML);
// Schedule some responses.
server.enqueue(response);
// Start the server.
server.start();
// Ask the server for its URL. You'll need this to make HTTP requests.
HttpUrl baseUrl = server.url("library/sections?X-Plex-Token=fake");
assertThrows(ResponseStatusException.class, () -> {
gapsSearch.getPlexLibraries(baseUrl);
}, "Should throw exception for missing 'title' node inside /MediaContainer/Directory element");
}
@Test
void missingKeyFromPlex() throws Exception {
// Create a MockWebServer. These are lean enough that you can create a new
// instance for every unit test.
MockWebServer server = new MockWebServer();
MockResponse response = new MockResponse()
.addHeader("Content-Type", "application/json; charset=utf-8")
.addHeader("Cache-Control", "no-cache")
.setBody(MISSING_KEY_PLEX_XML);
// Schedule some responses.
server.enqueue(response);
// Start the server.
server.start();
// Ask the server for its URL. You'll need this to make HTTP requests.
HttpUrl baseUrl = server.url("library/sections?X-Plex-Token=fake");
assertThrows(ResponseStatusException.class, () -> {
gapsSearch.getPlexLibraries(baseUrl);
}, "Should throw exception for missing 'key' node inside /MediaContainer/Directory element");
}
@Test
void nonNumberKeyFromPlex() throws Exception {
// Create a MockWebServer. These are lean enough that you can create a new
// instance for every unit test.
MockWebServer server = new MockWebServer();
MockResponse response = new MockResponse()
.addHeader("Content-Type", "application/json; charset=utf-8")
.addHeader("Cache-Control", "no-cache")
.setBody(NON_NUMBER_KEY_PLEX_XML);
// Schedule some responses.
server.enqueue(response);
// Start the server.
server.start();
// Ask the server for its URL. You'll need this to make HTTP requests.
HttpUrl baseUrl = server.url("library/sections?X-Plex-Token=fake");
assertThrows(ResponseStatusException.class, () -> {
gapsSearch.getPlexLibraries(baseUrl);
}, "Should throw exception for 'key' node not being a number inside /MediaContainer/Directory element");
}
@Test
void noBodyMovieXmlFromPlex() throws Exception {
// Create a MockWebServer. These are lean enough that you can create a new
// instance for every unit test.
MockWebServer server = new MockWebServer();
MockResponse response = new MockResponse()
.addHeader("Content-Type", "application/json; charset=utf-8")
.addHeader("Cache-Control", "no-cache");
// Schedule some responses.
server.enqueue(response);
// Start the server.
server.start();
String movieUrl = "movie/url/1";
// Ask the server for its URL. You'll need this to make HTTP requests.
HttpUrl baseUrl = server.url(movieUrl);
List<HttpUrl> movieUrls = new ArrayList<>();
movieUrls.add(baseUrl);
gaps.setMovieUrls(movieUrls);
gaps.setSearchFromPlex(true);
assertThrows(ResponseStatusException.class, () -> {
gapsSearch.run(gaps);
}, "Should throw exception that the body was empty");
}
@Test
void emptyBodyMovieXmlFromPlex() throws Exception {
// Create a MockWebServer. These are lean enough that you can create a new
// instance for every unit test.
MockWebServer server = new MockWebServer();
MockResponse response = new MockResponse()
.addHeader("Content-Type", "application/json; charset=utf-8")
.addHeader("Cache-Control", "no-cache")
.setBody(NO_MOVIE_PLEX_XML);
// Schedule some responses.
server.enqueue(response);
// Start the server.
server.start();
String movieUrl = "movie/url/1";
// Ask the server for its URL. You'll need this to make HTTP requests.
HttpUrl baseUrl = server.url(movieUrl);
List<HttpUrl> movieUrls = new ArrayList<>();
movieUrls.add(baseUrl);
gaps.setMovieUrls(movieUrls);
gaps.setSearchFromPlex(true);
Future<ResponseEntity> completedFuture = gapsSearch.run(gaps);
ResponseEntity ResponseEntity = completedFuture.get();
if (ResponseEntity.getBody() instanceof List) {
List recommended = (List) ResponseEntity.getBody();
assertEquals(recommended.size(), 0, "Shouldn't have found any movies");
}
}
@Test
void emptyMovieXmlFromPlex() throws Exception {
// Create a MockWebServer. These are lean enough that you can create a new
// instance for every unit test.
MockWebServer server = new MockWebServer();
MockResponse response = new MockResponse()
.addHeader("Content-Type", "application/json; charset=utf-8")
.addHeader("Cache-Control", "no-cache")
.setBody(EMPTY_MOVIE_PLEX_XML);
// Schedule some responses.
server.enqueue(response);
// Start the server.
server.start();
String movieUrl = "movie/url/1";
// Ask the server for its URL. You'll need this to make HTTP requests.
HttpUrl baseUrl = server.url(movieUrl);
List<HttpUrl> movieUrls = new ArrayList<>();
movieUrls.add(baseUrl);
gaps.setMovieUrls(movieUrls);
gaps.setSearchFromPlex(true);
assertThrows(ResponseStatusException.class, () -> {
gapsSearch.run(gaps);
}, "Should throw exception that the title was missing from the video element");
}
@Test
void validYearXmlFromPlex() throws Exception {
// Create a MockWebServer. These are lean enough that you can create a new
// instance for every unit test.
MockWebServer server = new MockWebServer();
MockResponse response = new MockResponse()
.addHeader("Content-Type", "application/json; charset=utf-8")
.addHeader("Cache-Control", "no-cache")
.setBody(GOOD_MOVIE_PLEX_XML);
// Schedule some responses.
server.enqueue(response);
// Start the server.
server.start();
String movieUrl = "movie/url/1";
// Ask the server for its URL. You'll need this to make HTTP requests.
HttpUrl baseUrl = server.url(movieUrl);
List<HttpUrl> movieUrls = new ArrayList<>();
movieUrls.add(baseUrl);
gaps.setMovieUrls(movieUrls);
gaps.setSearchFromPlex(true);
Future<ResponseEntity> completedFuture = gapsSearch.run(gaps);
completedFuture.get();
assertEquals(gapsSearch.getTotalMovieCount(), 1, "Should have found exactly one movie");
}
@Test
void invalidMovieDbFromPlex() throws Exception {
GapsUrlGeneratorTest gapsUrlGeneratorTest = new GapsUrlGeneratorTest();
GapsSearchBean gapsSearch = new GapsSearchBean(gapsUrlGeneratorTest);
// Create a MockWebServer. These are lean enough that you can create a new
// instance for every unit test.
MockWebServer server = new MockWebServer();
gapsUrlGeneratorTest.setMockWebServer(server);
gapsUrlGeneratorTest.setupServer();
server.start();
HttpUrl plexMovieUrl = gapsUrlGeneratorTest.getPlexUrl();
List<HttpUrl> movieUrls = new ArrayList<>();
movieUrls.add(plexMovieUrl);
gaps.setMovieUrls(movieUrls);
gaps.setSearchFromPlex(true);
gaps.setMovieDbApiKey("fake_id");
Future<ResponseEntity> completedFuture = gapsSearch.run(gaps);
completedFuture.get();
assertEquals(gapsSearch.getRecommendedMovies().size(), 3, "Should have found exactly three movies recommended");
}
}
@@ -0,0 +1,92 @@
package com.jasonhhouse.Gaps;
import okhttp3.HttpUrl;
import okhttp3.mockwebserver.Dispatcher;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;
@Service
public class GapsUrlGeneratorTest implements UrlGenerator {
private final static String GOOD_MOVIE_PLEX_XML = "<MediaContainer><Video title=\"Alien\" year=\"1979\"></Video></MediaContainer>";
private final static String GOOD_ALIEN_SEARCH_RESPONSE = "{\"page\":1,\"total_results\":4,\"total_pages\":1,\"results\":[{\"popularity\":33.25,\"vote_count\":8002,\"video\":false,\"poster_path\":\"\\/2h00HrZs89SL3tXB4nbkiM7BKHs.jpg\",\"id\":348,\"adult\":false,\"backdrop_path\":\"\\/dfNrZ82poQ8blHWJreIv6JZQ9JA.jpg\",\"original_language\":\"en\",\"original_title\":\"Alien\",\"genre_ids\":[27,878],\"title\":\"Alien\",\"vote_average\":8.1,\"overview\":\"During its return to the earth, commercial spaceship Nostromo intercepts a distress signal from a distant planet. When a three-member team of the crew discovers a chamber containing thousands of eggs on the planet, a creature inside one of the eggs attacks an explorer. The entire crew is unaware of the impending nightmare set to descend upon them when the alien parasite planted inside its unfortunate host is birthed.\",\"release_date\":\"1979-05-25\"},{\"popularity\":0.975,\"id\":454788,\"video\":false,\"vote_count\":1,\"vote_average\":8,\"title\":\"Giger's Alien\",\"release_date\":\"1979-01-01\",\"original_language\":\"en\",\"original_title\":\"Giger's Alien\",\"genre_ids\":[99],\"backdrop_path\":null,\"adult\":false,\"overview\":\"Documentary about Giger's work for the movie Alien (1979).\",\"poster_path\":\"\\/dnfPMkMbrPBAmhL4jbjDtQg2KWm.jpg\"},{\"popularity\":0.6,\"id\":98401,\"video\":false,\"vote_count\":1,\"vote_average\":1,\"title\":\"The Alien Encounters\",\"release_date\":\"1979-01-01\",\"original_language\":\"en\",\"original_title\":\"The Alien Encounters\",\"genre_ids\":[878],\"backdrop_path\":null,\"adult\":false,\"overview\":\"Investigator searches for probe sent to Earth by aliens.\",\"poster_path\":\"\\/ZTg0qakePuhc4uru8RChsUNXzj.jpg\"},{\"popularity\":2.564,\"id\":42542,\"video\":false,\"vote_count\":6,\"vote_average\":2.3,\"title\":\"Starship Invasions\",\"release_date\":\"1977-10-14\",\"original_language\":\"en\",\"original_title\":\"Starship Invasions\",\"genre_ids\":[878],\"backdrop_path\":\"\\/k2dlgkyIyJAhC5CtRP2I4uVAJxr.jpg\",\"adult\":false,\"overview\":\"Captain Rameses and his Legion of the Winged Serpent brigade are out to claim Earth for their dying race. Out to save Earth is an alien guard patrol located in the Bermuda Triangle, the League of Races. LOR leaders warn Rameses that he's breaking galactic treaty rules. The alien villain responds by launching an invasion which telepathically drives Earthlings to suicide. The LOR implore UFO expert Professor Duncan to help them. Eventually, the two alien forces battle. Will the Earth be saved?\",\"poster_path\":\"\\/9Q0lbq4L7V67AfePhTPRcK9LEz7.jpg\"}]}";
private static final String GOOD_ALIEN_DETAIL_RESPONSE = "{\"adult\":false,\"backdrop_path\":\"/dfNrZ82poQ8blHWJreIv6JZQ9JA.jpg\",\"belongs_to_collection\":{\"id\":8091,\"name\":\"Alien Collection\",\"poster_path\":\"/iVzIeC3PbG9BtDAudpwSNdKAgh6.jpg\",\"backdrop_path\":\"/kB0Y3uGe9ohJa59Lk8UO9cUOxGM.jpg\"},\"budget\":11000000,\"genres\":[{\"id\":27,\"name\":\"Horror\"},{\"id\":878,\"name\":\"Science Fiction\"}],\"homepage\":\"https://www.facebook.com/alienanthology/\",\"id\":348,\"imdb_id\":\"tt0078748\",\"original_language\":\"en\",\"original_title\":\"Alien\",\"overview\":\"During its return to the earth, commercial spaceship Nostromo intercepts a distress signal from a distant planet. When a three-member team of the crew discovers a chamber containing thousands of eggs on the planet, a creature inside one of the eggs attacks an explorer. The entire crew is unaware of the impending nightmare set to descend upon them when the alien parasite planted inside its unfortunate host is birthed.\",\"popularity\":33.25,\"poster_path\":\"/2h00HrZs89SL3tXB4nbkiM7BKHs.jpg\",\"production_companies\":[{\"id\":19747,\"logo_path\":null,\"name\":\"Brandywine Productions\",\"origin_country\":\"\"},{\"id\":25,\"logo_path\":\"/qZCc1lty5FzX30aOCVRBLzaVmcp.png\",\"name\":\"20th Century Fox\",\"origin_country\":\"US\"}],\"production_countries\":[{\"iso_3166_1\":\"US\",\"name\":\"United States of America\"},{\"iso_3166_1\":\"GB\",\"name\":\"United Kingdom\"}],\"release_date\":\"1979-05-25\",\"revenue\":104931801,\"runtime\":117,\"spoken_languages\":[{\"iso_639_1\":\"en\",\"name\":\"English\"},{\"iso_639_1\":\"es\",\"name\":\"Español\"}],\"status\":\"Released\",\"tagline\":\"In space no one can hear you scream.\",\"title\":\"Alien\",\"video\":false,\"vote_average\":8.1,\"vote_count\":8002}";
private static final String GOOD_ALIEN_COLLECTION_RESPONSE = "{\"id\":8091,\"name\":\"Alien Collection\",\"overview\":\"A science fiction horror film franchise, focusing on Lieutenant Ellen Ripley (Sigourney Weaver) and her battle with an extraterrestrial life-form, commonly referred to as \\\"the Alien\\\". Produced by 20th Century Fox, the series started with the 1979 film Alien, then Aliens in 1986, Alien³ in 1992, and Alien: Resurrection in 1997.\",\"poster_path\":\"/iVzIeC3PbG9BtDAudpwSNdKAgh6.jpg\",\"backdrop_path\":\"/kB0Y3uGe9ohJa59Lk8UO9cUOxGM.jpg\",\"parts\":[{\"id\":348,\"video\":false,\"vote_count\":8002,\"vote_average\":8.1,\"title\":\"Alien\",\"release_date\":\"1979-05-25\",\"original_language\":\"en\",\"original_title\":\"Alien\",\"genre_ids\":[27,878],\"backdrop_path\":\"/dfNrZ82poQ8blHWJreIv6JZQ9JA.jpg\",\"adult\":false,\"overview\":\"During its return to the earth, commercial spaceship Nostromo intercepts a distress signal from a distant planet. When a three-member team of the crew discovers a chamber containing thousands of eggs on the planet, a creature inside one of the eggs attacks an explorer. The entire crew is unaware of the impending nightmare set to descend upon them when the alien parasite planted inside its unfortunate host is birthed.\",\"poster_path\":\"/2h00HrZs89SL3tXB4nbkiM7BKHs.jpg\",\"popularity\":33.25},{\"id\":679,\"video\":false,\"vote_count\":5363,\"vote_average\":7.9,\"title\":\"Aliens\",\"release_date\":\"1986-07-18\",\"original_language\":\"en\",\"original_title\":\"Aliens\",\"genre_ids\":[28,878,53],\"backdrop_path\":\"/gXUdQLbo6eqaTby2cEjFQW1jyVV.jpg\",\"adult\":false,\"overview\":\"When Ripley's lifepod is found by a salvage crew over 50 years later, she finds that terra-formers are on the very planet they found the alien species. When the company sends a family of colonists out to investigate her story—all contact is lost with the planet and colonists. They enlist Ripley and the colonial marines to return and search for answers.\",\"poster_path\":\"/nORMXEkYEbzkU5WkMWMgRDJwjSZ.jpg\",\"popularity\":25.819},{\"id\":8077,\"video\":false,\"vote_count\":2970,\"vote_average\":6.3,\"title\":\"Alien³\",\"release_date\":\"1992-05-22\",\"original_language\":\"en\",\"original_title\":\"Alien³\",\"genre_ids\":[28,27,878],\"backdrop_path\":\"/nDPuIjpJyGDOnNEby5ZgkRBxymS.jpg\",\"adult\":false,\"overview\":\"After escaping with Newt and Hicks from the alien planet, Ripley crash lands on Fiorina 161, a prison planet and host to a correctional facility. Unfortunately, although Newt and Hicks do not survive the crash, a more unwelcome visitor does. The prison does not allow weapons of any kind, and with aid being a long time away, the prisoners must simply survive in any way they can.\",\"poster_path\":\"/p7mUd9GpmCYV5qhkKGmiEerFK3i.jpg\",\"popularity\":20.429},{\"adult\":false,\"backdrop_path\":\"/1F8KilfgVwWTB1tIzfwoKUvPqpl.jpg\",\"genre_ids\":[878,27,28],\"id\":8078,\"original_language\":\"en\",\"original_title\":\"Alien Resurrection\",\"overview\":\"Two hundred years after Lt. Ripley died, a group of scientists clone her, hoping to breed the ultimate weapon. But the new Ripley is full of surprises … as are the new aliens. Ripley must team with a band of smugglers to keep the creatures from reaching Earth.\",\"poster_path\":\"/2mavulZivbQa8XZlRA6h3hmesl7.jpg\",\"release_date\":\"1997-11-12\",\"title\":\"Alien Resurrection\",\"video\":false,\"vote_average\":6,\"vote_count\":2523,\"popularity\":17.615}]}";
private static final String plexMovieUrl = "/movie/url/1";
private static final String movieDbSearchUrl = "/movieDbSearch/alien";
private static final String movieDetailUrl = "/movieDetail/alien";
private static final String collectionUrl = "/collection/alien";
private MockWebServer mockWebServer;
public GapsUrlGeneratorTest() {
}
public void setMockWebServer(MockWebServer mockWebServer) {
this.mockWebServer = mockWebServer;
}
public void setupServer() {
MockResponse plexResponse = new MockResponse()
.addHeader("Content-Type", "application/json; charset=utf-8")
.addHeader("Cache-Control", "no-cache")
.setBody(GOOD_MOVIE_PLEX_XML);
MockResponse movieSearchResponse = new MockResponse()
.addHeader("Content-Type", "application/json; charset=utf-8")
.addHeader("Cache-Control", "no-cache")
.setBody(GOOD_ALIEN_SEARCH_RESPONSE);
MockResponse movieDetailResponse = new MockResponse()
.addHeader("Content-Type", "application/json; charset=utf-8")
.addHeader("Cache-Control", "no-cache")
.setBody(GOOD_ALIEN_DETAIL_RESPONSE);
MockResponse collectionResponse = new MockResponse()
.addHeader("Content-Type", "application/json; charset=utf-8")
.addHeader("Cache-Control", "no-cache")
.setBody(GOOD_ALIEN_COLLECTION_RESPONSE);
final Dispatcher dispatcher = new Dispatcher() {
@Override
public MockResponse dispatch(RecordedRequest request) {
switch (request.getPath()) {
case plexMovieUrl:
return plexResponse;
case movieDbSearchUrl:
return movieSearchResponse;
case movieDetailUrl:
return movieDetailResponse;
case collectionUrl:
return collectionResponse;
}
return new MockResponse().setResponseCode(404);
}
};
mockWebServer.setDispatcher(dispatcher);
}
HttpUrl getPlexUrl() {
return mockWebServer.url(plexMovieUrl);
}
@Override
public @NotNull HttpUrl generateSearchMovieUrl(String movieDbKey, String query, String year) {
return mockWebServer.url(movieDbSearchUrl);
}
@Override
public @NotNull HttpUrl generateMovieDetailUrl(String movieDbKey, String movieId) {
return mockWebServer.url(movieDetailUrl);
}
@Override
public @NotNull HttpUrl generateCollectionUrl(String movieDbKey, String collectionId) {
return mockWebServer.url(collectionUrl);
}
}