diff --git a/.gitignore b/.gitignore index 5fe4b24..cd85e0b 100644 --- a/.gitignore +++ b/.gitignore @@ -59,5 +59,5 @@ docs/_build/ # PyBuilder target/ -ponyapi +# ponyapi nimcache diff --git a/README.md b/README.md index 61393ed..bbf612c 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Clients - [Go](https://godoc.org/github.com/Xe/PonyAPI/client/go) - [Nim](https://github.com/Xe/PonyAPI/blob/master/client/nim/ponyapi.nim) [Docs](http://ponyapi.apps.xeserv.us/static/nim.html) - [Python](https://github.com/Xe/PonyAPI/blob/master/client/python/ponyapi.py) +- [Java](https://github.com/Xe/PonyAPI/client/java) Routes ------ diff --git a/client/java/README.md b/client/java/README.md new file mode 100644 index 0000000..a3113ed --- /dev/null +++ b/client/java/README.md @@ -0,0 +1,62 @@ +PonyAPI Java Client +=================== + +A Java client for accessing PonyAPI. Requires Java 8. + +Example Usage +------------- + +```java +package us.xeserv.examples; + +import us.xeserv.ponyapi.Episode; +import us.xeserv.ponyapi.PonyApiClient; + +import java.io.IOException; +import java.time.Instant; +import java.util.List; + +public class PonyApiExample { + + public static void main(String[] args) throws IOException { + // Initialize a client with a custom API host + PonyApiClient client = new PonyApiClient("some.fqdn.here"); // defaults to port 80 + client = new PonyApiClient("some.fqdn.here", 8080); // with a custom port number + // Initialize a client using http://ponyapi.apps.xeserv.us/ as the API ohst + client = new PonyApiClient(); + + // Get a list of all the episodes + List allEpisodes = client.all(); + + // Get the newest episode + Episode newestEpisode = client.newest(); + + // Get information about an episode + String name = newestEpisode.name; + Instant airDate = newestEpisode.airDate; + int seasonNumber = newestEpisode.season; + int episodeNumber = newestEpisode.episode; + boolean isMovie = newestEpisode.isMovie; + + // Get all episodes in a season + List season = client.getSeason(1); + + // Get all movies + List movies = client.getMovies(); + + // Get a specific episode by season and episode number + Episode specificEpisode = client.getEpisode(1, 13); + + // Get a specific movie by movie number + Episode specificMovie = client.getMovie(1); + + // Get a random movie or movie number + Episode random = client.random(); + + // Get a list of all episodes and movies matching a query + List queryResults = client.search("Owl's Well"); // returns Owl's Well That Ends Well + + } + +} +``` \ No newline at end of file diff --git a/client/java/build.gradle b/client/java/build.gradle new file mode 100644 index 0000000..2196fa9 --- /dev/null +++ b/client/java/build.gradle @@ -0,0 +1,19 @@ +group 'us.xeserv' +version '1.0' + +apply plugin: 'java' + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + compile "com.google.code.gson:gson:+" + compile "org.apache.httpcomponents:httpclient:+" + compile "org.apache.httpcomponents:fluent-hc:+" + compile "com.google.guava:guava:+" + testCompile "junit:junit:+" +} \ No newline at end of file diff --git a/client/java/gradle/wrapper/gradle-wrapper.jar b/client/java/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..fd7e590 Binary files /dev/null and b/client/java/gradle/wrapper/gradle-wrapper.jar differ diff --git a/client/java/gradle/wrapper/gradle-wrapper.properties b/client/java/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..9dd18d3 --- /dev/null +++ b/client/java/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Aug 18 15:13:15 CDT 2015 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.6-bin.zip diff --git a/client/java/gradlew b/client/java/gradlew new file mode 100644 index 0000000..91a7e26 --- /dev/null +++ b/client/java/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/client/java/src/main/java/us/xeserv/ponyapi/Episode.java b/client/java/src/main/java/us/xeserv/ponyapi/Episode.java new file mode 100644 index 0000000..ad1d7dc --- /dev/null +++ b/client/java/src/main/java/us/xeserv/ponyapi/Episode.java @@ -0,0 +1,84 @@ +package us.xeserv.ponyapi; + +import java.time.Instant; +import java.util.Objects; + +/** + * The {@code Episode} class represents My Little Pony episode or movie information returned by PonyAPI. + *

+ * Episodes are immutable; they represent data returned from the API and cannot be changed after they are created. + *

+ * Movie data stored in an {@code Episode} object are referred to by the season number 99. Their episode number refers + * to the order of their release starting with 1. + * + * @author Justin Kaufman + * @since 1.0 + */ +public class Episode { + + /** + * The name of the episode or movie. + */ + public final String name; + + /** + * The season of the episode, or 99 if it is a movie. + */ + public final int season; + + /** + * The episode or movie number. + */ + public final int episode; + + /** + * The air date. + */ + public final Instant airDate; + + /** + * True if this episode represents a movie, false otherwise. + */ + public final boolean isMovie; + + protected Episode(String name, int season, int episode, long airDate, boolean isMovie) { + this.name = name; + this.season = season; + this.episode = episode; + this.airDate = Instant.ofEpochSecond(airDate); + this.isMovie = isMovie; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Episode rhs = (Episode) obj; + return Objects.equals(name, rhs.name) + && Objects.equals(season, rhs.season) + && Objects.equals(episode, rhs.episode) + && Objects.equals(airDate, rhs.airDate) + && Objects.equals(isMovie, rhs.isMovie); + } + + @Override + public int hashCode() { + return Objects.hash(name, season, episode, airDate, isMovie); + } + + @Override + public String toString() { + return "{\n" + + "\t\"name\": \"" + name + "\",\n" + + "\t\"air_date\": " + airDate.getEpochSecond() + ",\n" + + "\t\"season\": " + season + ",\n" + + "\t\"episode\": " + episode + ",\n" + + "\t\"is_movie\": " + isMovie + "\n" + + "}"; + } + +} \ No newline at end of file diff --git a/client/java/src/main/java/us/xeserv/ponyapi/JsonDecoder.java b/client/java/src/main/java/us/xeserv/ponyapi/JsonDecoder.java new file mode 100644 index 0000000..d836929 --- /dev/null +++ b/client/java/src/main/java/us/xeserv/ponyapi/JsonDecoder.java @@ -0,0 +1,42 @@ +package us.xeserv.ponyapi; + +import com.google.gson.*; + +import java.util.ArrayList; +import java.util.List; + +class JsonDecoder { + + private static final Gson gson; + static { + GsonBuilder gsonBuilder = new GsonBuilder(); + gsonBuilder.registerTypeAdapter(Episode.class, (JsonDeserializer) (json, typeOfT, context) -> { + JsonObject jsonObject = json.getAsJsonObject(); + return new Episode( + jsonObject.get("name").getAsString(), + jsonObject.get("season").getAsInt(), + jsonObject.get("episode").getAsInt(), + jsonObject.get("air_date").getAsLong(), + jsonObject.get("is_movie").getAsBoolean() + ); + }); + gson = gsonBuilder.create(); + } + + protected static Episode fromJson(String json) { + JsonObject wrapper = (JsonObject) new JsonParser().parse(json); + JsonObject payload = wrapper.get("episode").getAsJsonObject(); + return gson.fromJson(payload, Episode.class); + } + + protected static List listFromJson(String json) { + List list = new ArrayList<>(); + JsonObject wrapper = (JsonObject) new JsonParser().parse(json); + JsonArray payload = wrapper.get("episodes").getAsJsonArray(); + for (JsonElement episode : payload) { + list.add(gson.fromJson(episode, Episode.class)); + } + return list; + } + +} diff --git a/client/java/src/main/java/us/xeserv/ponyapi/PonyApiClient.java b/client/java/src/main/java/us/xeserv/ponyapi/PonyApiClient.java new file mode 100644 index 0000000..63aaa37 --- /dev/null +++ b/client/java/src/main/java/us/xeserv/ponyapi/PonyApiClient.java @@ -0,0 +1,158 @@ +package us.xeserv.ponyapi; + +import com.google.common.base.Strings; +import org.apache.http.HttpResponse; +import org.apache.http.client.fluent.Request; +import org.apache.http.util.EntityUtils; + +import java.io.IOException; +import java.net.URLEncoder; +import java.util.List; + +/** + * The {@code PonyApiClient} class provides an interface between Java code that uses this library and PonyAPI. + *

+ * This client is able to retrieve information about the show My Little Pony: Friendship is Magic. + * + * @author Justin Kaufman + * @since 1.0 + */ +public class PonyApiClient { + + private final String host; + + /** + * The default constructor initializes a client instance that uses http://ponyapi.apps.xeserv.us/ as the API source. + */ + public PonyApiClient() { + this("ponyapi.apps.xeserv.us"); + } + + /** + * Accepts a FQDN (and optionally port) running the PonyAPI service. + * @param host a FQDN running the PonyAPI service + */ + public PonyApiClient(String host) { + this.host = host; + } + + /** + * Returns a list of all episodes and movies. + * @return a list of all episodes and movies + * @throws IOException if there is an error accessing the service + */ + public List all() throws IOException { + return JsonDecoder.listFromJson(asJson(get("/all"))); + } + + /** + * Returns the newest episode or movie aired. + * @return the newest episode or movie aired + * @throws IOException if there is an error accessing the service + */ + public Episode newest() throws IOException { + return JsonDecoder.fromJson(asJson(get("/newest"))); + } + + /** + * Returns the episode or movie that most recently aired. + * @return the episode or movie that most recently aired + * @throws IOException if there is an error accessing the service + */ + public Episode lastAired() throws IOException { + return JsonDecoder.fromJson(asJson(get("/last_aired"))); + } + + /** + * Returns all information about episodes in a given season number. + * @param season the season number + * @return all information about episodes in a given season number + * @throws IOException if there is an error accessing the service + */ + public List getSeason(int season) throws IOException { + HttpResponse response = get("/season/" + season); + if (statusCode(response) == 404) { + return null; + } + return JsonDecoder.listFromJson(asJson(response)); + } + + /** + * Returns all information about movies. + * @return all information about movies + * @throws IOException if there is an error accessing the service + */ + public List getMovies() throws IOException { + return JsonDecoder.listFromJson(asJson(get("/season/99"))); + } + + /** + * Returns all information about the episode with the given season and episode number. + * @param season the season number + * @param episode the episode number + * @return all information about the episode with the given season and episode number + * @throws IOException if there is an error accessing the service + */ + public Episode getEpisode(int season, int episode) throws IOException { + HttpResponse response = get("/season/" + season + "/episode/" + episode); + if (statusCode(response) == 404) { + return null; + } + return JsonDecoder.fromJson(asJson(response)); + } + + /** + * Returns all information about the movie with the given movie number. + * @param movie the movie number + * @return all information about the movie with the given movie number + * @throws IOException if there is an error accessing the service + */ + public Episode getMovie(int movie) throws IOException { + HttpResponse response = get("/season/99/episode/" + movie); + if (statusCode(response) == 404) { + return null; + } + return JsonDecoder.fromJson(asJson(response)); + } + + /** + * Returns a random episode or movie from the record. + * @return a random episode or movie from the record + * @throws IOException if there is an error accessing the service + */ + public Episode random() throws IOException { + String json = asJson(get("/random")); + return JsonDecoder.fromJson(json); + } + + /** + * Returns a list of episodes which contain the query in its name. + * @param query a case-independent, non-empty {@code String} + * @return a list of episodes which contain the query in its name + * @throws IOException if there is an error accessing the service + * @throws IllegalArgumentException if the query is null or empty + */ + public List search(String query) throws IOException { + if (Strings.isNullOrEmpty(query)) { + throw new IllegalArgumentException("A search query is required."); + } + String json = asJson(get("/search?q=" + URLEncoder.encode(query, "UTF-8"))); + return JsonDecoder.listFromJson(json); + } + + private HttpResponse get(String path) throws IOException { + return Request.Get("http://" + host + path) + .userAgent("PonyApi Java Client 1.0") + .execute() + .returnResponse(); + } + + private String asJson(HttpResponse response) throws IOException { + return EntityUtils.toString(response.getEntity()); + } + + private int statusCode(HttpResponse response) throws IOException { + return response.getStatusLine().getStatusCode(); + } + +} diff --git a/client/java/src/test/java/us/xeserv/ponyapi/JsonDecoderTest.java b/client/java/src/test/java/us/xeserv/ponyapi/JsonDecoderTest.java new file mode 100644 index 0000000..985f685 --- /dev/null +++ b/client/java/src/test/java/us/xeserv/ponyapi/JsonDecoderTest.java @@ -0,0 +1,92 @@ +package us.xeserv.ponyapi; + +import org.junit.Test; + +import java.time.Instant; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +public class JsonDecoderTest { + + @Test + public void episodeTest() { + String json = "{\n" + + "\t\"episode\":\n" + + "\t{\n" + + "\t\t\"name\": \"Friendship is Magic Part 1\",\n" + + "\t\t\"air_date\": 1286735400,\n" + + "\t\t\"season\": 1,\n" + + "\t\t\"episode\": 1,\n" + + "\t\t\"is_movie\": false\n" + + "\t}\n" + + "}"; + + Episode episode = JsonDecoder.fromJson(json); + assertEquals("Friendship is Magic Part 1", episode.name); + assertEquals(Instant.ofEpochSecond(1286735400), episode.airDate); + assertEquals(1, episode.season); + assertEquals(1, episode.episode); + assertEquals(false, episode.isMovie); + } + + @Test + public void movieTest() { + String json = "{\n" + + "\t\"episode\":\n" + + "\t{\n" + + "\t\t\"name\": \"Equestria Girls\",\n" + + "\t\t\"air_date\": 1371340800,\n" + + "\t\t\"season\": 99,\n" + + "\t\t\"episode\": 1,\n" + + "\t\t\"is_movie\": true\n" + + "\t}\n" + + "}"; + + Episode episode = JsonDecoder.fromJson(json); + assertEquals("Equestria Girls", episode.name); + assertEquals(Instant.ofEpochSecond(1371340800), episode.airDate); + assertEquals(99, episode.season); + assertEquals(1, episode.episode); + assertEquals(true, episode.isMovie); + } + + @Test + public void multipleTest() { + String json = "{\n" + + "\t\"episodes\": [\n" + + "\t\t{\n" + + "\t\t\t\"name\": \"Friendship is Magic Part 1\",\n" + + "\t\t\t\"air_date\": 1286735400,\n" + + "\t\t\t\"season\": 1,\n" + + "\t\t\t\"episode\": 1,\n" + + "\t\t\t\"is_movie\": false\n" + + "\t\t},\n" + + "\t\t{\n" + + "\t\t\t\"name\": \"Equestria Girls\",\n" + + "\t\t\t\"air_date\": 1371340800,\n" + + "\t\t\t\"season\": 99,\n" + + "\t\t\t\"episode\": 1,\n" + + "\t\t\t\"is_movie\": true\n" + + "\t\t}\n" + + "\t]\n" + + "}"; + + List episodes = JsonDecoder.listFromJson(json); + assertEquals(2, episodes.size()); + + Episode episode1 = episodes.get(0); + assertEquals("Friendship is Magic Part 1", episode1.name); + assertEquals(Instant.ofEpochSecond(1286735400), episode1.airDate); + assertEquals(1, episode1.season); + assertEquals(1, episode1.episode); + assertEquals(false, episode1.isMovie); + + Episode episode2 = episodes.get(1); + assertEquals("Equestria Girls", episode2.name); + assertEquals(Instant.ofEpochSecond(1371340800), episode2.airDate); + assertEquals(99, episode2.season); + assertEquals(1, episode2.episode); + assertEquals(true, episode2.isMovie); + } +} diff --git a/client/java/src/test/java/us/xeserv/ponyapi/PonyApiClientTest.java b/client/java/src/test/java/us/xeserv/ponyapi/PonyApiClientTest.java new file mode 100644 index 0000000..2eca18e --- /dev/null +++ b/client/java/src/test/java/us/xeserv/ponyapi/PonyApiClientTest.java @@ -0,0 +1,81 @@ +package us.xeserv.ponyapi; + +import org.junit.Test; + +import java.io.IOException; +import java.time.Instant; +import java.util.List; + +import static org.junit.Assert.*; + +public class PonyApiClientTest { + + private PonyApiClient client = new PonyApiClient(); + + @Test + public void allTest() throws Exception { + List list = client.all(); + assertFalse(list.isEmpty()); + } + + @Test + public void newestTest() throws IOException { + Episode episode = client.newest(); + assertNotNull(episode); + } + + @Test + public void lastAiredTest() throws IOException { + Episode episode = client.lastAired(); + assertNotNull(episode); + } + + @Test + public void getSeasonTest() throws IOException { + List list = client.getSeason(98); + assertEquals(null, list); + list = client.getSeason(1); + assertEquals(26, list.size()); + } + + @Test + public void getMoviesTest() throws IOException { + List list = client.getMovies(); + assertFalse(list.isEmpty()); + } + + @Test + public void getEpisodeTest() throws IOException { + Episode episode = client.getEpisode(1, 1); + assertEquals("Friendship is Magic Part 1", episode.name); + assertEquals(Instant.ofEpochSecond(1286735400), episode.airDate); + assertEquals(1, episode.season); + assertEquals(1, episode.episode); + assertEquals(false, episode.isMovie); + } + + @Test + public void getMovieTest() throws IOException { + Episode episode = client.getMovie(1); + assertEquals("Equestria Girls", episode.name); + assertEquals(Instant.ofEpochSecond(1371340800), episode.airDate); + assertEquals(99, episode.season); + assertEquals(1, episode.episode); + assertEquals(true, episode.isMovie); + } + + @Test + public void randomTest() throws IOException { + Episode episode = client.random(); + assertNotNull(episode); + } + + @Test + public void searchTest() throws IOException { + List episodes = client.search("Owl's Well"); + assertEquals(1, episodes.size()); + Episode episode = episodes.get(0); + assertEquals("Owl's Well That Ends Well", episode.name); + } + +} diff --git a/client/java/test.sh b/client/java/test.sh new file mode 100644 index 0000000..9150561 --- /dev/null +++ b/client/java/test.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +set -e + +./gradlew test \ No newline at end of file