remote_suggestion_unittest.cc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. // Copyright 2016 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "components/ntp_snippets/remote/remote_suggestion.h"
  5. #include <utility>
  6. #include "base/json/json_reader.h"
  7. #include "base/strings/utf_string_conversions.h"
  8. #include "base/time/time.h"
  9. #include "base/values.h"
  10. #include "components/ntp_snippets/remote/proto/ntp_snippets.pb.h"
  11. #include "components/ntp_snippets/time_serialization.h"
  12. #include "testing/gmock/include/gmock/gmock.h"
  13. #include "testing/gtest/include/gtest/gtest.h"
  14. namespace ntp_snippets {
  15. namespace {
  16. using ::testing::ElementsAre;
  17. using ::testing::Eq;
  18. using ::testing::IsNull;
  19. using ::testing::NotNull;
  20. SnippetProto TestSnippetProto() {
  21. SnippetProto proto;
  22. proto.add_ids("foo");
  23. proto.add_ids("bar");
  24. proto.set_title("a suggestion title");
  25. proto.set_snippet("the snippet describing the suggestion.");
  26. proto.set_salient_image_url("http://google.com/logo/");
  27. proto.set_image_dominant_color(4289379276);
  28. proto.set_publish_date(1476095492);
  29. proto.set_expiry_date(1476354691);
  30. proto.set_score(1.5f);
  31. proto.set_dismissed(false);
  32. proto.set_remote_category_id(1);
  33. proto.set_fetch_date(1476364691);
  34. proto.set_content_type(SnippetProto_ContentType_VIDEO);
  35. auto* source = proto.mutable_source();
  36. source->set_url("http://cool-suggestions.com/");
  37. source->set_publisher_name("Great Suggestions Inc.");
  38. source->set_amp_url("http://cdn.ampproject.org/c/foo/");
  39. proto.set_rank(7);
  40. return proto;
  41. }
  42. base::Value::Dict TestSnippetJsonValue() {
  43. const char kJsonStr[] = R"(
  44. {
  45. "ids" : ["foo", "bar"],
  46. "title" : "a suggestion title",
  47. "snippet" : "the snippet describing the suggestion.",
  48. "fullPageUrl" : "http://cool-suggestions.com/",
  49. "creationTime" : "2016-06-30T11:01:37.000Z",
  50. "expirationTime" : "2016-07-01T11:01:37.000Z",
  51. "attribution" : "Great Suggestions Inc.",
  52. "imageUrl" : "http://google.com/logo/",
  53. "ampUrl" : "http://cdn.ampproject.org/c/foo/",
  54. "faviconUrl" : "http://localhost/favicon.ico",
  55. "score": 1.5,
  56. "notificationInfo": {
  57. "shouldNotify": true,
  58. "deadline": "2016-06-30T13:01:37.000Z"
  59. },
  60. "imageDominantColor": 4289379276
  61. }
  62. )";
  63. auto json_parsed = base::JSONReader::ReadAndReturnValueWithError(
  64. kJsonStr, base::JSON_PARSE_RFC);
  65. CHECK(json_parsed.has_value())
  66. << "error_message: " << json_parsed.error().message;
  67. return std::move(json_parsed->GetDict());
  68. }
  69. TEST(RemoteSuggestionTest, FromContentSuggestionsDictionary) {
  70. base::Value::Dict snippet_dict = TestSnippetJsonValue();
  71. const base::Time fetch_date = DeserializeTime(1466634774L);
  72. std::unique_ptr<RemoteSuggestion> snippet =
  73. RemoteSuggestion::CreateFromContentSuggestionsDictionary(
  74. snippet_dict, kArticlesRemoteId, fetch_date);
  75. ASSERT_THAT(snippet, NotNull());
  76. EXPECT_EQ(snippet->id(), "foo");
  77. EXPECT_THAT(snippet->GetAllIDs(), ElementsAre("foo", "bar"));
  78. EXPECT_EQ(snippet->title(), "a suggestion title");
  79. EXPECT_EQ(snippet->snippet(), "the snippet describing the suggestion.");
  80. EXPECT_EQ(snippet->salient_image_url(), GURL("http://google.com/logo/"));
  81. ASSERT_TRUE(snippet->optional_image_dominant_color().has_value());
  82. EXPECT_EQ(*snippet->optional_image_dominant_color(), 4289379276u);
  83. EXPECT_EQ(1.5, snippet->score());
  84. auto unix_publish_date = snippet->publish_date() - base::Time::UnixEpoch();
  85. auto expiry_duration = snippet->expiry_date() - snippet->publish_date();
  86. EXPECT_FLOAT_EQ(unix_publish_date.InSecondsF(), 1467284497.000000f);
  87. EXPECT_FLOAT_EQ(expiry_duration.InSecondsF(), 86400.000000f);
  88. EXPECT_EQ(snippet->publisher_name(), "Great Suggestions Inc.");
  89. EXPECT_EQ(snippet->url(), GURL("http://cool-suggestions.com/"));
  90. EXPECT_EQ(snippet->amp_url(), GURL("http://cdn.ampproject.org/c/foo/"));
  91. EXPECT_TRUE(snippet->should_notify());
  92. auto notification_duration =
  93. snippet->notification_deadline() - snippet->publish_date();
  94. EXPECT_EQ(7200.0f, notification_duration.InSecondsF());
  95. EXPECT_EQ(fetch_date, snippet->fetch_date());
  96. }
  97. TEST(RemoteSuggestionTest,
  98. FromContentSuggestionsDictionaryWithoutImageOrSnippet) {
  99. base::Value::Dict snippet_dict = TestSnippetJsonValue();
  100. ASSERT_TRUE(snippet_dict.Remove("imageUrl"));
  101. ASSERT_TRUE(snippet_dict.Remove("snippet"));
  102. const base::Time fetch_date = DeserializeTime(1466634774L);
  103. std::unique_ptr<RemoteSuggestion> snippet =
  104. RemoteSuggestion::CreateFromContentSuggestionsDictionary(
  105. snippet_dict, kArticlesRemoteId, fetch_date);
  106. ASSERT_THAT(snippet, NotNull());
  107. EXPECT_EQ(GURL(), snippet->salient_image_url());
  108. EXPECT_EQ("", snippet->snippet());
  109. }
  110. TEST(RemoteSuggestionTest, CreateFromProtoToProtoRoundtrip) {
  111. SnippetProto proto = TestSnippetProto();
  112. std::unique_ptr<RemoteSuggestion> snippet =
  113. RemoteSuggestion::CreateFromProto(proto);
  114. ASSERT_THAT(snippet, NotNull());
  115. // The snippet database relies on the fact that the first id in the protocol
  116. // buffer is considered the unique id.
  117. EXPECT_EQ(snippet->id(), "foo");
  118. // Unfortunately, we only have MessageLite protocol buffers in Chrome, so
  119. // comparing via DebugString() or MessageDifferencer is not working.
  120. // So we either need to compare field-by-field (maintenance heavy) or
  121. // compare the binary version (unusable diagnostic). Deciding for the latter.
  122. std::string proto_serialized, round_tripped_serialized;
  123. proto.SerializeToString(&proto_serialized);
  124. snippet->ToProto().SerializeToString(&round_tripped_serialized);
  125. EXPECT_EQ(proto_serialized, round_tripped_serialized);
  126. }
  127. TEST(RemoteSuggestionTest, CreateFromProtoIgnoreMissingFetchDate) {
  128. SnippetProto proto = TestSnippetProto();
  129. proto.clear_fetch_date();
  130. std::unique_ptr<RemoteSuggestion> snippet =
  131. RemoteSuggestion::CreateFromProto(proto);
  132. ASSERT_THAT(snippet, NotNull());
  133. EXPECT_EQ(snippet->fetch_date(), base::Time());
  134. }
  135. TEST(RemoteSuggestionTest, CreateFromProtoIgnoreMissingImageDominantColor) {
  136. SnippetProto proto = TestSnippetProto();
  137. proto.clear_image_dominant_color();
  138. std::unique_ptr<RemoteSuggestion> snippet =
  139. RemoteSuggestion::CreateFromProto(proto);
  140. ASSERT_THAT(snippet, NotNull());
  141. // The snippet database relies on the fact that the first id in the protocol
  142. // buffer is considered the unique id.
  143. EXPECT_EQ(snippet->id(), "foo");
  144. EXPECT_FALSE(snippet->optional_image_dominant_color().has_value());
  145. }
  146. TEST(RemoteSuggestionTest, CreateFromProtoIgnoreMissingSalientImageAndSnippet) {
  147. SnippetProto proto = TestSnippetProto();
  148. proto.clear_salient_image_url();
  149. proto.clear_snippet();
  150. std::unique_ptr<RemoteSuggestion> snippet =
  151. RemoteSuggestion::CreateFromProto(proto);
  152. ASSERT_THAT(snippet, NotNull());
  153. // The snippet database relies on the fact that the first id in the protocol
  154. // buffer is considered the unique id.
  155. EXPECT_EQ(snippet->id(), "foo");
  156. EXPECT_EQ(GURL(), snippet->salient_image_url());
  157. EXPECT_EQ("", snippet->snippet());
  158. }
  159. TEST(RemoteSuggestionTest, NotifcationInfoAllSpecified) {
  160. auto json = TestSnippetJsonValue();
  161. json.SetByDottedPath("notificationInfo.shouldNotify", true);
  162. json.SetByDottedPath("notificationInfo.deadline", "2016-06-30T13:01:37.000Z");
  163. auto snippet = RemoteSuggestion::CreateFromContentSuggestionsDictionary(
  164. json, 0, base::Time());
  165. EXPECT_TRUE(snippet->should_notify());
  166. EXPECT_EQ(7200.0f,
  167. (snippet->notification_deadline() - snippet->publish_date())
  168. .InSecondsF());
  169. }
  170. TEST(RemoteSuggestionTest, NotificationInfoDeadlineInvalid) {
  171. auto json = TestSnippetJsonValue();
  172. json.SetByDottedPath("notificationInfo.shouldNotify", true);
  173. json.SetByDottedPath("notificationInfo.deadline", "abcd");
  174. auto snippet = RemoteSuggestion::CreateFromContentSuggestionsDictionary(
  175. json, 0, base::Time());
  176. EXPECT_TRUE(snippet->should_notify());
  177. EXPECT_EQ(base::Time::Max(), snippet->notification_deadline());
  178. }
  179. TEST(RemoteSuggestionTest, NotificationInfoDeadlineAbsent) {
  180. auto json = TestSnippetJsonValue();
  181. json.SetByDottedPath("notificationInfo.shouldNotify", true);
  182. json.RemoveByDottedPath("notificationInfo.deadline");
  183. auto snippet = RemoteSuggestion::CreateFromContentSuggestionsDictionary(
  184. json, 0, base::Time());
  185. EXPECT_TRUE(snippet->should_notify());
  186. EXPECT_EQ(base::Time::Max(), snippet->notification_deadline());
  187. }
  188. TEST(RemoteSuggestionTest, NotificationInfoShouldNotifyInvalid) {
  189. auto json = TestSnippetJsonValue();
  190. json.SetByDottedPath("notificationInfo.shouldNotify", "non-bool");
  191. auto snippet = RemoteSuggestion::CreateFromContentSuggestionsDictionary(
  192. json, 0, base::Time());
  193. EXPECT_FALSE(snippet->should_notify());
  194. }
  195. TEST(RemoteSuggestionTest, NotificationInfoAbsent) {
  196. auto json = TestSnippetJsonValue();
  197. json.SetByDottedPath("notificationInfo.shouldNotify", false);
  198. auto snippet = RemoteSuggestion::CreateFromContentSuggestionsDictionary(
  199. json, 0, base::Time());
  200. EXPECT_FALSE(snippet->should_notify());
  201. }
  202. TEST(RemoteSuggestionTest, ToContentSuggestionWithoutNotificationInfo) {
  203. auto json = TestSnippetJsonValue();
  204. json.Remove("notificationInfo");
  205. const base::Time fetch_date = DeserializeTime(1466634774L);
  206. auto snippet = RemoteSuggestion::CreateFromContentSuggestionsDictionary(
  207. json, 0, fetch_date);
  208. ASSERT_THAT(snippet, NotNull());
  209. ContentSuggestion sugg = snippet->ToContentSuggestion(
  210. Category::FromKnownCategory(KnownCategories::ARTICLES));
  211. EXPECT_THAT(sugg.id().category(),
  212. Eq(Category::FromKnownCategory(KnownCategories::ARTICLES)));
  213. EXPECT_THAT(sugg.id().id_within_category(), Eq("foo"));
  214. EXPECT_THAT(sugg.url(), Eq(GURL("http://cdn.ampproject.org/c/foo/")));
  215. EXPECT_THAT(sugg.title(), Eq(u"a suggestion title"));
  216. EXPECT_THAT(sugg.snippet_text(),
  217. Eq(u"the snippet describing the suggestion."));
  218. EXPECT_THAT(sugg.publish_date().ToJavaTime(), Eq(1467284497000));
  219. EXPECT_THAT(sugg.publisher_name(), Eq(u"Great Suggestions Inc."));
  220. EXPECT_THAT(sugg.score(), Eq(1.5));
  221. EXPECT_THAT(sugg.salient_image_url(), Eq(GURL("http://google.com/logo/")));
  222. EXPECT_THAT(sugg.notification_extra(), IsNull());
  223. EXPECT_THAT(sugg.fetch_date(), Eq(fetch_date));
  224. }
  225. TEST(RemoteSuggestionTest, ToContentSuggestionWithNotificationInfo) {
  226. auto json = TestSnippetJsonValue();
  227. auto snippet = RemoteSuggestion::CreateFromContentSuggestionsDictionary(
  228. json, 0, base::Time());
  229. ASSERT_THAT(snippet, NotNull());
  230. ContentSuggestion sugg = snippet->ToContentSuggestion(
  231. Category::FromKnownCategory(KnownCategories::ARTICLES));
  232. EXPECT_THAT(sugg.id().category(),
  233. Eq(Category::FromKnownCategory(KnownCategories::ARTICLES)));
  234. EXPECT_THAT(sugg.id().id_within_category(), Eq("foo"));
  235. EXPECT_THAT(sugg.url(), Eq(GURL("http://cdn.ampproject.org/c/foo/")));
  236. EXPECT_THAT(sugg.title(), Eq(u"a suggestion title"));
  237. EXPECT_THAT(sugg.snippet_text(),
  238. Eq(u"the snippet describing the suggestion."));
  239. EXPECT_THAT(sugg.publish_date().ToJavaTime(), Eq(1467284497000));
  240. EXPECT_THAT(sugg.publisher_name(), Eq(u"Great Suggestions Inc."));
  241. EXPECT_THAT(sugg.score(), Eq(1.5));
  242. ASSERT_THAT(sugg.notification_extra(), NotNull());
  243. EXPECT_THAT(sugg.notification_extra()->deadline.ToJavaTime(),
  244. Eq(1467291697000));
  245. }
  246. TEST(RemoteSuggestionTest, ToContentSuggestionWithContentTypeVideo) {
  247. auto json = TestSnippetJsonValue();
  248. json.Set("contentType", "VIDEO");
  249. auto snippet = RemoteSuggestion::CreateFromContentSuggestionsDictionary(
  250. json, 0, base::Time());
  251. ASSERT_THAT(snippet, NotNull());
  252. ContentSuggestion content_suggestion = snippet->ToContentSuggestion(
  253. Category::FromKnownCategory(KnownCategories::ARTICLES));
  254. EXPECT_THAT(content_suggestion.is_video_suggestion(), Eq(true));
  255. }
  256. TEST(RemoteSuggestionTest, ToContentSuggestionWithContentTypeUnknown) {
  257. auto json = TestSnippetJsonValue();
  258. json.Set("contentType", "UNKNOWN");
  259. auto snippet = RemoteSuggestion::CreateFromContentSuggestionsDictionary(
  260. json, 0, base::Time());
  261. ASSERT_THAT(snippet, NotNull());
  262. ContentSuggestion content_suggestion = snippet->ToContentSuggestion(
  263. Category::FromKnownCategory(KnownCategories::ARTICLES));
  264. EXPECT_THAT(content_suggestion.is_video_suggestion(), Eq(false));
  265. }
  266. TEST(RemoteSuggestionTest, ToContentSuggestionWithMissingContentType) {
  267. auto json = TestSnippetJsonValue();
  268. auto snippet = RemoteSuggestion::CreateFromContentSuggestionsDictionary(
  269. json, 0, base::Time());
  270. ASSERT_THAT(snippet, NotNull());
  271. ContentSuggestion content_suggestion = snippet->ToContentSuggestion(
  272. Category::FromKnownCategory(KnownCategories::ARTICLES));
  273. EXPECT_THAT(content_suggestion.is_video_suggestion(), Eq(false));
  274. }
  275. TEST(RemoteSuggestionTest, ToContentSuggestionWithLargeImageDominantColor) {
  276. auto json = TestSnippetJsonValue();
  277. // JSON does not support unsigned types. As a result the value is parsed as
  278. // int if it fits and as double otherwise.
  279. json.Set("imageDominantColor", 4289379276.);
  280. auto snippet = RemoteSuggestion::CreateFromContentSuggestionsDictionary(
  281. json, 0, base::Time());
  282. ASSERT_THAT(snippet, NotNull());
  283. ContentSuggestion content_suggestion = snippet->ToContentSuggestion(
  284. Category::FromKnownCategory(KnownCategories::ARTICLES));
  285. ASSERT_TRUE(content_suggestion.optional_image_dominant_color().has_value());
  286. EXPECT_THAT(*content_suggestion.optional_image_dominant_color(),
  287. Eq(4289379276u));
  288. }
  289. TEST(RemoteSuggestionTest, ToContentSuggestionWithSmallImageDominantColor) {
  290. auto json = TestSnippetJsonValue();
  291. // JSON does not support unsigned types. As a result the value is parsed as
  292. // int if it fits and as double otherwise.
  293. json.Set("imageDominantColor", 16777216 /*=0x1000000*/);
  294. auto snippet = RemoteSuggestion::CreateFromContentSuggestionsDictionary(
  295. json, 0, base::Time());
  296. ASSERT_THAT(snippet, NotNull());
  297. ContentSuggestion content_suggestion = snippet->ToContentSuggestion(
  298. Category::FromKnownCategory(KnownCategories::ARTICLES));
  299. ASSERT_TRUE(content_suggestion.optional_image_dominant_color().has_value());
  300. EXPECT_THAT(*content_suggestion.optional_image_dominant_color(),
  301. Eq(16777216u));
  302. }
  303. TEST(RemoteSuggestionTest, ToContentSuggestionWithoutImageDominantColor) {
  304. auto json = TestSnippetJsonValue();
  305. json.Remove("imageDominantColor");
  306. auto snippet = RemoteSuggestion::CreateFromContentSuggestionsDictionary(
  307. json, 0, base::Time());
  308. ASSERT_THAT(snippet, NotNull());
  309. ContentSuggestion content_suggestion = snippet->ToContentSuggestion(
  310. Category::FromKnownCategory(KnownCategories::ARTICLES));
  311. EXPECT_FALSE(content_suggestion.optional_image_dominant_color().has_value());
  312. }
  313. } // namespace
  314. } // namespace ntp_snippets