package data;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import org.apache.commons.lang3.StringUtils;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Tests whether the ValidatingJsonDeserializer class properly rejects
* incomplete or malformed Tweet or User objects.
*
* @author Peter Wu
*/
public class ValidatingJsonDeserializerTest {
private final static String exampleTweet = "{"
+ " \"retweeted\": false,"
+ " \"in_reply_to_screen_name\": null,"
+ " \"possibly_sensitive\": false,"
+ " \"truncated\": false,"
+ " \"lang\": \"en\","
+ " \"in_reply_to_status_id_str\": null,"
+ " \"id\": 461441338630619136,"
+ " \"in_reply_to_user_id_str\": null,"
+ " \"in_reply_to_status_id\": null,"
+ " \"created_at\": \"Wed Apr 30 09:46:02 +0000 2014\","
+ " \"favorite_count\": 0,"
+ " \"place\": null,"
+ " \"coordinates\": null,"
+ " \"metadata\": {"
+ " \"result_type\": \"recent\","
+ " \"iso_language_code\": \"en\""
+ " },"
+ " \"text\": \"RT @verge: Samsung's Galaxy K zoom is a high-end smartphone and an incredible selfie machine http://t.co/I5WmnHV9HM http://t.co/V8Pm2bxzcH\","
+ " \"contributors\": null,"
+ " \"retweeted_status\": {"
+ " \"contributors\": null,"
+ " \"text\": \"Samsung's Galaxy K zoom is a high-end smartphone and an incredible selfie machine http://t.co/I5WmnHV9HM http://t.co/V8Pm2bxzcH\","
+ " \"geo\": null,"
+ " \"retweeted\": false,"
+ " \"in_reply_to_screen_name\": null,"
+ " \"possibly_sensitive\": false,"
+ " \"truncated\": false,"
+ " \"lang\": \"en\","
+ " \"entities\": {"
+ " \"symbols\": [],"
+ " \"urls\": [{"
+ " \"expanded_url\": \"http://vrge.co/1ivZTla\","
+ " \"indices\": [82, 104],"
+ " \"display_url\": \"vrge.co/1ivZTla\","
+ " \"url\": \"http://t.co/I5WmnHV9HM\""
+ " }],"
+ " \"hashtags\": [],"
+ " \"media\": [{"
+ " \"sizes\": {"
+ " \"small\": {"
+ " \"w\": 340,"
+ " \"resize\": \"fit\","
+ " \"h\": 225"
+ " },"
+ " \"thumb\": {"
+ " \"w\": 150,"
+ " \"resize\": \"crop\","
+ " \"h\": 150"
+ " },"
+ " \"large\": {"
+ " \"w\": 1020,"
+ " \"resize\": \"fit\","
+ " \"h\": 675"
+ " },"
+ " \"medium\": {"
+ " \"w\": 599,"
+ " \"resize\": \"fit\","
+ " \"h\": 397"
+ " }"
+ " },"
+ " \"id\": 461125128559263744,"
+ " \"media_url_https\": \"https://pbs.twimg.com/media/BmY-3HzIAAAR1d6.jpg\","
+ " \"media_url\": \"http://pbs.twimg.com/media/BmY-3HzIAAAR1d6.jpg\","
+ " \"expanded_url\": \"http://twitter.com/verge/status/461125128710262784/photo/1\","
+ " \"indices\": [105, 127],"
+ " \"id_str\": \"461125128559263744\","
+ " \"type\": \"photo\","
+ " \"display_url\": \"pic.twitter.com/V8Pm2bxzcH\","
+ " \"url\": \"http://t.co/V8Pm2bxzcH\""
+ " }],"
+ " \"user_mentions\": []"
+ " },"
+ " \"in_reply_to_status_id_str\": null,"
+ " \"id\": 461125128710262784,"
+ " \"source\": \"Twitter for Mac<\\/a>\","
+ " \"in_reply_to_user_id_str\": null,"
+ " \"favorited\": false,"
+ " \"in_reply_to_status_id\": null,"
+ " \"retweet_count\": 113,"
+ " \"created_at\": \"Tue Apr 29 12:49:32 +0000 2014\","
+ " \"in_reply_to_user_id\": null,"
+ " \"favorite_count\": 79,"
+ " \"id_str\": \"461125128710262784\","
+ " \"place\": null,"
+ " \"user\": {"
+ " \"location\": \"The Universe\","
+ " \"default_profile\": false,"
+ " \"profile_background_tile\": false,"
+ " \"statuses_count\": 45365,"
+ " \"lang\": \"en\","
+ " \"profile_link_color\": \"FA4D2A\","
+ " \"profile_banner_url\": \"https://pbs.twimg.com/profile_banners/275686563/1348153919\","
+ " \"id\": 275686563,"
+ " \"following\": null,"
+ " \"protected\": false,"
+ " \"favourites_count\": 679,"
+ " \"profile_text_color\": \"333333\","
+ " \"description\": \"The culture of now. We tell stories at the intersection of science, technology, art, and the humanities.\","
+ " \"verified\": true,"
+ " \"contributors_enabled\": false,"
+ " \"profile_sidebar_border_color\": \"FFFFFF\","
+ " \"name\": \"The Verge\","
+ " \"profile_background_color\": \"FFFFFF\","
+ " \"created_at\": \"Fri Apr 01 19:54:22 +0000 2011\","
+ " \"is_translation_enabled\": true,"
+ " \"default_profile_image\": false,"
+ " \"followers_count\": 442637,"
+ " \"profile_image_url_https\": \"https://pbs.twimg.com/profile_images/1450127603/TheVerge_Badge_Color1_normal.png\","
+ " \"geo_enabled\": false,"
+ " \"profile_background_image_url\": \"http://pbs.twimg.com/profile_background_images/378800000021313532/66906cdb8cdbfe2de5ac7ada880ca029.png\","
+ " \"profile_background_image_url_https\": \"https://pbs.twimg.com/profile_background_images/378800000021313532/66906cdb8cdbfe2de5ac7ada880ca029.png\","
+ " \"follow_request_sent\": null,"
+ " \"entities\": {"
+ " \"description\": {"
+ " \"urls\": []"
+ " },"
+ " \"url\": {"
+ " \"urls\": [{"
+ " \"expanded_url\": \"http://www.theverge.com\","
+ " \"indices\": [0, 22],"
+ " \"display_url\": \"theverge.com\","
+ " \"url\": \"http://t.co/W2SFxIGhA4\""
+ " }]"
+ " }"
+ " },"
+ " \"url\": \"http://t.co/W2SFxIGhA4\","
+ " \"utc_offset\": -14400,"
+ " \"time_zone\": \"Eastern Time (US & Canada)\","
+ " \"notifications\": null,"
+ " \"profile_use_background_image\": true,"
+ " \"friends_count\": 112,"
+ " \"profile_sidebar_fill_color\": \"EFEFEF\","
+ " \"screen_name\": \"verge\","
+ " \"id_str\": \"275686563\","
+ " \"profile_image_url\": \"http://pbs.twimg.com/profile_images/1450127603/TheVerge_Badge_Color1_normal.png\","
+ " \"listed_count\": 17450,"
+ " \"is_translator\": false"
+ " },"
+ " \"coordinates\": null,"
+ " \"metadata\": {"
+ " \"result_type\": \"recent\","
+ " \"iso_language_code\": \"en\""
+ " }"
+ " },"
+ " \"geo\": null,"
+ " \"entities\": {"
+ " \"symbols\": [],"
+ " \"urls\": [{"
+ " \"expanded_url\": \"http://vrge.co/1ivZTla\","
+ " \"indices\": [93, 115],"
+ " \"display_url\": \"vrge.co/1ivZTla\","
+ " \"url\": \"http://t.co/I5WmnHV9HM\""
+ " }],"
+ " \"hashtags\": [],"
+ " \"media\": [{"
+ " \"sizes\": {"
+ " \"small\": {"
+ " \"w\": 340,"
+ " \"resize\": \"fit\","
+ " \"h\": 225"
+ " },"
+ " \"thumb\": {"
+ " \"w\": 150,"
+ " \"resize\": \"crop\","
+ " \"h\": 150"
+ " },"
+ " \"large\": {"
+ " \"w\": 1020,"
+ " \"resize\": \"fit\","
+ " \"h\": 675"
+ " },"
+ " \"medium\": {"
+ " \"w\": 599,"
+ " \"resize\": \"fit\","
+ " \"h\": 397"
+ " }"
+ " },"
+ " \"id\": 461125128559263744,"
+ " \"media_url_https\": \"https://pbs.twimg.com/media/BmY-3HzIAAAR1d6.jpg\","
+ " \"media_url\": \"http://pbs.twimg.com/media/BmY-3HzIAAAR1d6.jpg\","
+ " \"expanded_url\": \"http://twitter.com/verge/status/461125128710262784/photo/1\","
+ " \"indices\": [116, 138],"
+ " \"id_str\": \"461125128559263744\","
+ " \"type\": \"photo\","
+ " \"display_url\": \"pic.twitter.com/V8Pm2bxzcH\","
+ " \"url\": \"http://t.co/V8Pm2bxzcH\""
+ " }],"
+ " \"user_mentions\": [{"
+ " \"id\": 275686563,"
+ " \"name\": \"The Verge\","
+ " \"indices\": [3, 9],"
+ " \"screen_name\": \"verge\","
+ " \"id_str\": \"275686563\""
+ " }]"
+ " },"
+ " \"source\": \"Twitter for Android Tablets<\\/a>\","
+ " \"favorited\": false,"
+ " \"in_reply_to_user_id\": null,"
+ " \"retweet_count\": 113,"
+ " \"id_str\": \"461441338630619136\","
+ " \"user\": {"
+ " \"location\": \"GURGAON\","
+ " \"default_profile\": false,"
+ " \"profile_background_tile\": true,"
+ " \"statuses_count\": 2208,"
+ " \"lang\": \"en\","
+ " \"profile_link_color\": \"2FC2EF\","
+ " \"profile_banner_url\": \"https://pbs.twimg.com/profile_banners/106707648/1398635609\","
+ " \"id\": 106707648,"
+ " \"following\": null,"
+ " \"protected\": false,"
+ " \"favourites_count\": 547,"
+ " \"profile_text_color\": \"666666\","
+ " \"description\": \"A WANNABE WRITER.....\","
+ " \"verified\": false,"
+ " \"contributors_enabled\": false,"
+ " \"profile_sidebar_border_color\": \"FFFFFF\","
+ " \"name\": \"MANGO PEOPLE\","
+ " \"profile_background_color\": \"1A1B1F\","
+ " \"created_at\": \"Wed Jan 20 12:59:53 +0000 2010\","
+ " \"is_translation_enabled\": false,"
+ " \"default_profile_image\": false,"
+ " \"followers_count\": 573,"
+ " \"profile_image_url_https\": \"https://pbs.twimg.com/profile_images/461180974823272448/RpSM6o2f_normal.jpeg\","
+ " \"geo_enabled\": true,"
+ " \"profile_background_image_url\": \"http://pbs.twimg.com/profile_background_images/378800000157705856/1unyAtzk.jpeg\","
+ " \"profile_background_image_url_https\": \"https://pbs.twimg.com/profile_background_images/378800000157705856/1unyAtzk.jpeg\","
+ " \"follow_request_sent\": null,"
+ " \"entities\": {"
+ " \"description\": {"
+ " \"urls\": []"
+ " },"
+ " \"url\": {"
+ " \"urls\": [{"
+ " \"expanded_url\": \"http://FACEBOOK.COM/BENXROCKS\","
+ " \"indices\": [0, 22],"
+ " \"display_url\": \"FACEBOOK.COM/BENXROCKS\","
+ " \"url\": \"http://t.co/Uwbbg0oN3T\""
+ " }]"
+ " }"
+ " },"
+ " \"url\": \"http://t.co/Uwbbg0oN3T\","
+ " \"utc_offset\": 19800,"
+ " \"time_zone\": \"New Delhi\","
+ " \"notifications\": null,"
+ " \"profile_use_background_image\": true,"
+ " \"friends_count\": 1719,"
+ " \"profile_sidebar_fill_color\": \"252429\","
+ " \"screen_name\": \"BH4NUKALRA\","
+ " \"id_str\": \"106707648\","
+ " \"profile_image_url\": \"http://pbs.twimg.com/profile_images/461180974823272448/RpSM6o2f_normal.jpeg\","
+ " \"listed_count\": 2,"
+ " \"is_translator\": false"
+ " }"
+ "}";
private Object deserializeJson(String json, Class cls) {
Gson gson = new GsonBuilder()
.registerTypeAdapter(cls, new ValidatingJsonDeserializer<>())
.create();
return gson.fromJson(json, cls);
}
@Test
public void testUserEmpty() {
try {
deserializeJson("{}", User.class);
fail("Empty object literal should be rejected.");
} catch (JsonParseException ex) {
// assume id to be the first field
assertEquals("Missing field: id", ex.getMessage());
}
}
@Test
public void testTweetEmpty() {
try {
deserializeJson("{}", Tweet.class);
fail("Empty object literal should be rejected.");
} catch (JsonParseException ex) {
// assume id to be the first field
assertEquals("Missing field: id", ex.getMessage());
}
}
/**
* Test of deserialize method, of class ValidatingJsonDeserializer.
*/
@Test
public void testDeserializeTweet() {
Tweet tweet = (Tweet) deserializeJson(exampleTweet, Tweet.class);
Gson gson = new GsonBuilder().setPrettyPrinting().create();
//System.out.println(gson.toJson(tweet));
assertEquals(461441338630619136L, tweet.id);
assertNotNull(tweet.retweeted_status);
assertEquals("Wed Apr 30 09:46:02 +0000 2014", tweet.created_at);
assertNotNull(tweet.user);
assertEquals("GURGAON", tweet.user.location);
assertNotNull(tweet.user.entities);
assertNotNull(tweet.user.entities.url);
assertEquals(1, tweet.user.entities.url.urls.length);
assertEquals("http://FACEBOOK.COM/BENXROCKS",
tweet.user.entities.url.urls[0].expanded_url);
assertEquals("en", tweet.retweeted_status.user.lang);
assertEquals(1, tweet.entities.user_mentions.length);
assertEquals(275686563, tweet.entities.user_mentions[0].id);
assertEquals(0, tweet.entities.hashtags.length);
assertEquals(1, tweet.entities.urls.length);
assertEquals("http://vrge.co/1ivZTla", tweet.entities.urls[0].expanded_url);
}
private JsonObject buildMinimalUser() {
JsonObject user = new JsonObject();
user.addProperty("id", 4);
user.addProperty("name", "X");
user.addProperty("time_zone", "X");
user.addProperty("statuses_count", 4);
user.addProperty("followers_count", 4);
user.addProperty("friends_count", 4);
user.addProperty("location", "X");
user.addProperty("screen_name", "X");
user.addProperty("created_at", "X");
JsonObject entities = new JsonObject();
JsonObject urlEntity = new JsonObject();
urlEntity.add("urls", new JsonArray());
entities.add("url", urlEntity);
user.add("entities", entities);
user.addProperty("lang", "X");
return user;
}
private JsonObject buildMinimalTweet(JsonObject user) {
JsonObject tweet = new JsonObject();
tweet.addProperty("id", 4);
tweet.addProperty("lang", "X");
tweet.addProperty("in_reply_to_user_id", 4);
tweet.addProperty("created_at", "X");
tweet.addProperty("favorite_count", 4);
tweet.add("place", JsonNull.INSTANCE);
tweet.addProperty("coordinates", "X");
tweet.addProperty("text", "X");
tweet.add("retweeted_status", JsonNull.INSTANCE); // Tweet object
JsonObject entities = new JsonObject();
entities.add("hashtags", new JsonArray());
entities.add("urls", new JsonArray());
entities.add("user_mentions", new JsonArray());
tweet.add("entities", entities);
tweet.addProperty("retweet_count", 4);
tweet.add("user", user);
return tweet;
}
private void removeProperty(JsonObject obj, String... names) {
String prop;
for (int i = 0; i < names.length - 1; i++) {
prop = names[i];
assert obj.has(prop) : "Missing object property: " + prop;
obj = obj.getAsJsonObject(prop);
}
prop = names[names.length - 1];
assert obj.has(prop) : "Cannot remove missing property: " + prop;
obj.remove(prop);
}
private void addProperty(JsonObject obj, JsonElement val, String... names) {
String prop;
for (int i = 0; i < names.length - 1; i++) {
prop = names[i];
if (!obj.has(prop)) {
obj.add(prop, new JsonObject());
}
obj = obj.getAsJsonObject(prop);
}
prop = names[names.length - 1];
obj.add(prop, val);
}
private void checkImpairedTweet(String... names) {
JsonObject tweet = buildMinimalTweet(buildMinimalUser());
removeProperty(tweet, names);
String prop = StringUtils.join(names, ".");
checkTweetFail(tweet, "Missing field: " + prop);
}
private void checkImpairedUser(String... names) {
JsonObject user = buildMinimalUser();
removeProperty(user, names);
JsonObject tweet = buildMinimalTweet(user);
String prop = StringUtils.join(names, ".");
checkTweetFail(tweet, "Missing field: user." + prop);
}
private void checkTweetFail(JsonObject tweet, String exp) {
Gson gson = new Gson();
System.out.println("Checking for failure: " + exp);
String json = gson.toJson(tweet);
try {
deserializeJson(json, Tweet.class);
System.out.println("JSON that should have failed: " + json);
fail("Expected: " + exp);
} catch (JsonParseException ex) {
System.out.println("Failure message: " + ex.getMessage());
assertEquals(exp, ex.getMessage());
}
}
private Tweet checkTweetPass(JsonObject tweet) {
Gson gson = new Gson();
String json = gson.toJson(tweet);
return (Tweet) deserializeJson(json, Tweet.class);
}
@Test
public void testMinimal() {
checkTweetPass(buildMinimalTweet(buildMinimalUser()));
checkTweetFail(buildMinimalTweet(null), "Missing field: user");
}
@Test
public void testTweet() {
checkImpairedTweet("id");
checkImpairedTweet("lang");
checkImpairedTweet("created_at");
checkImpairedTweet("favorite_count");
checkImpairedTweet("text");
checkImpairedTweet("retweet_count");
checkImpairedTweet("user");
}
@Test
public void testTweetNulls() {
// place can be null
JsonObject tweet = buildMinimalTweet(buildMinimalUser());
removeProperty(tweet, "place");
checkTweetPass(tweet);
tweet = buildMinimalTweet(buildMinimalUser());
removeProperty(tweet, "in_reply_to_user_id");
checkTweetPass(tweet);
tweet = buildMinimalTweet(buildMinimalUser());
removeProperty(tweet, "coordinates");
checkTweetPass(tweet);
}
@Test
public void testTweetEntities() {
checkImpairedTweet("entities");
checkImpairedTweet("entities", "hashtags");
checkImpairedTweet("entities", "urls");
}
@Test
public void testUser() {
checkImpairedUser("id");
checkImpairedUser("name");
checkImpairedUser("statuses_count");
checkImpairedUser("followers_count");
checkImpairedUser("friends_count");
checkImpairedUser("location");
checkImpairedUser("screen_name");
checkImpairedUser("created_at");
checkImpairedUser("lang");
}
@Test
public void testNullUserEntities() {
// entities can be null
JsonObject tweet = buildMinimalTweet(buildMinimalUser());
removeProperty(tweet, "user", "entities");
checkTweetPass(tweet);
tweet = buildMinimalTweet(buildMinimalUser());
removeProperty(tweet, "user", "time_zone");
checkTweetPass(tweet);
}
@Test
public void testUserEntities() {
checkImpairedTweet("user", "entities", "url");
checkImpairedTweet("user", "entities", "url", "urls");
}
@Test
public void testRetweetedStatus() {
JsonObject tweet = buildMinimalTweet(buildMinimalUser());
JsonObject rtUser = buildMinimalUser();
String screen_name = "Somebody";
rtUser.addProperty("screen_name", screen_name);
JsonObject retweet = buildMinimalTweet(rtUser);
tweet.add("retweeted_status", retweet);
Tweet parsedTweet = checkTweetPass(tweet);
assertNotNull(parsedTweet.retweeted_status);
assertEquals(screen_name, parsedTweet.retweeted_status.user.screen_name);
}
@Test
public void testRetweetedMissingText() {
JsonObject tweet = buildMinimalTweet(buildMinimalUser());
JsonObject retweet = buildMinimalTweet(buildMinimalUser());
removeProperty(retweet, "text");
tweet.add("retweeted_status", retweet);
checkTweetFail(tweet, "Missing field: retweeted_status.text");
}
@Test
public void testNullPlace() {
JsonObject tweet = buildMinimalTweet(buildMinimalUser());
removeProperty(tweet, "place");
tweet.add("place", null);
checkTweetPass(tweet);
}
@Test
public void testNotATweetObject() {
try {
DebuggingJsonDeserializer.tryValidate("{}", Tweet.class);
fail("DebuggingJsonDeserializer must also check for type.");
} catch (JsonParseException ex) {
assertEquals("Missing field: id", ex.getMessage());
}
}
}