Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(461)

Side by Side Diff: components/ntp_snippets/ntp_snippets_service_unittest.cc

Issue 2285133004: Overhaul ntp_snippets_service_unittest.cc. (Closed) Base URL: https://chromium.googlesource.com/chromium/src@master
Patch Set: No brackets. Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "components/ntp_snippets/ntp_snippets_service.h" 5 #include "components/ntp_snippets/ntp_snippets_service.h"
6 6
7 #include <memory> 7 #include <memory>
8 #include <utility> 8 #include <utility>
9 #include <vector> 9 #include <vector>
10 10
11 #include "base/command_line.h" 11 #include "base/command_line.h"
12 #include "base/files/file_path.h" 12 #include "base/files/file_path.h"
13 #include "base/files/scoped_temp_dir.h" 13 #include "base/files/scoped_temp_dir.h"
14 #include "base/json/json_reader.h" 14 #include "base/json/json_reader.h"
15 #include "base/macros.h" 15 #include "base/macros.h"
16 #include "base/memory/ptr_util.h" 16 #include "base/memory/ptr_util.h"
17 #include "base/message_loop/message_loop.h" 17 #include "base/message_loop/message_loop.h"
18 #include "base/run_loop.h" 18 #include "base/run_loop.h"
19 #include "base/strings/string_number_conversions.h" 19 #include "base/strings/string_number_conversions.h"
20 #include "base/strings/string_util.h" 20 #include "base/strings/string_util.h"
21 #include "base/strings/stringprintf.h" 21 #include "base/strings/stringprintf.h"
22 #include "base/test/histogram_tester.h" 22 #include "base/test/histogram_tester.h"
23 #include "base/threading/thread_task_runner_handle.h" 23 #include "base/threading/thread_task_runner_handle.h"
24 #include "base/time/time.h" 24 #include "base/time/time.h"
25 #include "components/image_fetcher/image_decoder.h" 25 #include "components/image_fetcher/image_decoder.h"
26 #include "components/image_fetcher/image_fetcher.h" 26 #include "components/image_fetcher/image_fetcher.h"
27 #include "components/image_fetcher/image_fetcher_delegate.h" 27 #include "components/image_fetcher/image_fetcher_delegate.h"
28 #include "components/ntp_snippets/category_factory.h" 28 #include "components/ntp_snippets/category_factory.h"
29 #include "components/ntp_snippets/mock_content_suggestions_provider_observer.h"
30 #include "components/ntp_snippets/ntp_snippet.h" 29 #include "components/ntp_snippets/ntp_snippet.h"
30 #include "components/ntp_snippets/ntp_snippets_constants.h"
31 #include "components/ntp_snippets/ntp_snippets_database.h" 31 #include "components/ntp_snippets/ntp_snippets_database.h"
32 #include "components/ntp_snippets/ntp_snippets_fetcher.h" 32 #include "components/ntp_snippets/ntp_snippets_fetcher.h"
33 #include "components/ntp_snippets/ntp_snippets_scheduler.h" 33 #include "components/ntp_snippets/ntp_snippets_scheduler.h"
34 #include "components/ntp_snippets/ntp_snippets_test_utils.h" 34 #include "components/ntp_snippets/ntp_snippets_test_utils.h"
35 #include "components/ntp_snippets/switches.h" 35 #include "components/ntp_snippets/switches.h"
36 #include "components/prefs/testing_pref_service.h" 36 #include "components/prefs/testing_pref_service.h"
37 #include "components/signin/core/browser/fake_profile_oauth2_token_service.h" 37 #include "components/signin/core/browser/fake_profile_oauth2_token_service.h"
38 #include "components/signin/core/browser/fake_signin_manager.h" 38 #include "components/signin/core/browser/fake_signin_manager.h"
39 #include "components/variations/variations_associated_data.h"
39 #include "google_apis/google_api_keys.h" 40 #include "google_apis/google_api_keys.h"
40 #include "net/url_request/test_url_fetcher_factory.h" 41 #include "net/url_request/test_url_fetcher_factory.h"
41 #include "net/url_request/url_request_test_util.h" 42 #include "net/url_request/url_request_test_util.h"
42 #include "testing/gmock/include/gmock/gmock.h" 43 #include "testing/gmock/include/gmock/gmock.h"
43 #include "testing/gtest/include/gtest/gtest.h" 44 #include "testing/gtest/include/gtest/gtest.h"
44 #include "ui/gfx/image/image.h" 45 #include "ui/gfx/image/image.h"
45 #include "ui/gfx/image/image_unittest_util.h" 46 #include "ui/gfx/image/image_unittest_util.h"
46 47
47 using image_fetcher::ImageFetcher; 48 using image_fetcher::ImageFetcher;
48 using image_fetcher::ImageFetcherDelegate; 49 using image_fetcher::ImageFetcherDelegate;
49 using testing::ElementsAre; 50 using testing::ElementsAre;
50 using testing::Eq; 51 using testing::Eq;
52 using testing::InSequence;
51 using testing::Invoke; 53 using testing::Invoke;
52 using testing::IsEmpty; 54 using testing::IsEmpty;
53 using testing::Mock; 55 using testing::Mock;
56 using testing::MockFunction;
57 using testing::NiceMock;
54 using testing::Return; 58 using testing::Return;
59 using testing::SaveArg;
55 using testing::SizeIs; 60 using testing::SizeIs;
56 using testing::StartsWith; 61 using testing::StartsWith;
62 using testing::WithArgs;
57 using testing::_; 63 using testing::_;
58 64
59 namespace ntp_snippets { 65 namespace ntp_snippets {
60 66
61 namespace { 67 namespace {
62 68
63 MATCHER_P(IdEq, value, "") { 69 MATCHER_P(IdEq, value, "") {
64 return arg->id() == value; 70 return arg->id() == value;
65 } 71 }
66 72
67 const base::Time::Exploded kDefaultCreationTime = {2015, 11, 4, 25, 13, 46, 45}; 73 const base::Time::Exploded kDefaultCreationTime = {2015, 11, 4, 25, 13, 46, 45};
68 const char kTestContentSnippetsServerFormat[] = 74 const char kTestContentSuggestionsServerEndpoint[] =
69 "https://chromereader-pa.googleapis.com/v1/fetch?key=%s"; 75 "https://localunittest-chromecontentsuggestions-pa.googleapis.com/v1/"
76 "suggestions/fetch";
77 const char kTestContentSuggestionsServerFormat[] =
78 "https://localunittest-chromecontentsuggestions-pa.googleapis.com/v1/"
79 "suggestions/fetch?key=%s";
70 80
71 const char kSnippetUrl[] = "http://localhost/foobar"; 81 const char kSnippetUrl[] = "http://localhost/foobar";
72 const char kSnippetTitle[] = "Title"; 82 const char kSnippetTitle[] = "Title";
73 const char kSnippetText[] = "Snippet"; 83 const char kSnippetText[] = "Snippet";
74 const char kSnippetSalientImage[] = "http://localhost/salient_image"; 84 const char kSnippetSalientImage[] = "http://localhost/salient_image";
75 const char kSnippetPublisherName[] = "Foo News"; 85 const char kSnippetPublisherName[] = "Foo News";
76 const char kSnippetAmpUrl[] = "http://localhost/amp"; 86 const char kSnippetAmpUrl[] = "http://localhost/amp";
77 const float kSnippetScore = 5.0;
78 87
79 const char kSnippetUrl2[] = "http://foo.com/bar"; 88 const char kSnippetUrl2[] = "http://foo.com/bar";
80 89
81 base::Time GetDefaultCreationTime() { 90 base::Time GetDefaultCreationTime() {
82 base::Time out_time; 91 base::Time out_time;
83 EXPECT_TRUE(base::Time::FromUTCExploded(kDefaultCreationTime, &out_time)); 92 EXPECT_TRUE(base::Time::FromUTCExploded(kDefaultCreationTime, &out_time));
84 return out_time; 93 return out_time;
85 } 94 }
86 95
87 base::Time GetDefaultExpirationTime() { 96 base::Time GetDefaultExpirationTime() {
88 return base::Time::Now() + base::TimeDelta::FromHours(1); 97 return base::Time::Now() + base::TimeDelta::FromHours(1);
89 } 98 }
90 99
91 std::string GetTestJson(const std::vector<std::string>& snippets) { 100 std::string GetTestJson(const std::vector<std::string>& snippets) {
92 return base::StringPrintf("{\"recos\":[%s]}", 101 return base::StringPrintf(
93 base::JoinString(snippets, ", ").c_str()); 102 "{\n"
103 " \"categories\": [{\n"
104 " \"id\": 1,\n"
105 " \"localizedTitle\": \"Articles for You\",\n"
106 " \"suggestions\": [%s]\n"
107 " }]\n"
108 "}\n",
109 base::JoinString(snippets, ", ").c_str());
94 } 110 }
95 111
96 std::string GetSnippetWithUrlAndTimesAndSources( 112 std::string FormatTime(const base::Time& t) {
97 const std::string& url, 113 base::Time::Exploded x;
98 const std::string& content_creation_time_str, 114 t.UTCExplode(&x);
99 const std::string& expiry_time_str, 115 return base::StringPrintf("%04d-%02d-%02dT%02d:%02d:%02dZ", x.year, x.month,
100 const std::vector<std::string>& source_urls, 116 x.day_of_month, x.hour, x.minute, x.second);
101 const std::vector<std::string>& publishers,
102 const std::vector<std::string>& amp_urls) {
103 char json_str_format[] =
104 "{ \"contentInfo\": {"
105 "\"url\" : \"%s\","
106 "\"title\" : \"%s\","
107 "\"snippet\" : \"%s\","
108 "\"thumbnailUrl\" : \"%s\","
109 "\"creationTimestampSec\" : \"%s\","
110 "\"expiryTimestampSec\" : \"%s\","
111 "\"sourceCorpusInfo\" : [%s]"
112 "}, "
113 "\"score\" : %f}";
114
115 char source_corpus_info_format[] =
116 "{\"corpusId\": \"%s\","
117 "\"publisherData\": {"
118 "\"sourceName\": \"%s\""
119 "},"
120 "\"ampUrl\": \"%s\"}";
121
122 std::string source_corpus_info_list_str;
123 for (size_t i = 0; i < source_urls.size(); ++i) {
124 std::string source_corpus_info_str =
125 base::StringPrintf(source_corpus_info_format,
126 source_urls[i].empty() ? "" : source_urls[i].c_str(),
127 publishers[i].empty() ? "" : publishers[i].c_str(),
128 amp_urls[i].empty() ? "" : amp_urls[i].c_str());
129 source_corpus_info_list_str.append(source_corpus_info_str);
130 source_corpus_info_list_str.append(",");
131 }
132 // Remove the last comma
133 source_corpus_info_list_str.erase(source_corpus_info_list_str.size()-1, 1);
134 return base::StringPrintf(json_str_format, url.c_str(), kSnippetTitle,
135 kSnippetText, kSnippetSalientImage,
136 content_creation_time_str.c_str(),
137 expiry_time_str.c_str(),
138 source_corpus_info_list_str.c_str(), kSnippetScore);
139 } 117 }
140 118
141 std::string GetSnippetWithSources(const std::vector<std::string>& source_urls, 119 std::string GetSnippetWithUrlAndTimesAndSource(
142 const std::vector<std::string>& publishers, 120 const std::vector<std::string>& ids,
143 const std::vector<std::string>& amp_urls) { 121 const std::string& url,
144 return GetSnippetWithUrlAndTimesAndSources( 122 const base::Time& creation_time,
145 kSnippetUrl, NTPSnippet::TimeToJsonString(GetDefaultCreationTime()), 123 const base::Time& expiry_time,
146 NTPSnippet::TimeToJsonString(GetDefaultExpirationTime()), source_urls, 124 const std::string& publisher,
147 publishers, amp_urls); 125 const std::string& amp_url) {
126 const std::string ids_string = base::JoinString(ids, "\",\n \"");
127 return base::StringPrintf(
128 "{\n"
129 " \"ids\": [\n"
130 " \"%s\"\n"
131 " ],\n"
132 " \"title\": \"%s\",\n"
133 " \"snippet\": \"%s\",\n"
134 " \"fullPageUrl\": \"%s\",\n"
135 " \"creationTime\": \"%s\",\n"
136 " \"expirationTime\": \"%s\",\n"
137 " \"attribution\": \"%s\",\n"
138 " \"imageUrl\": \"%s\",\n"
139 " \"ampUrl\": \"%s\"\n"
140 " }",
141 ids_string.c_str(), kSnippetTitle, kSnippetText, url.c_str(),
142 FormatTime(creation_time).c_str(), FormatTime(expiry_time).c_str(),
143 publisher.c_str(), kSnippetSalientImage, amp_url.c_str());
148 } 144 }
149 145
150 std::string GetSnippetWithUrlAndTimes( 146 std::string GetSnippetWithSources(const std::string& source_url,
151 const std::string& url, 147 const std::string& publisher,
152 const std::string& content_creation_time_str, 148 const std::string& amp_url) {
153 const std::string& expiry_time_str) { 149 return GetSnippetWithUrlAndTimesAndSource(
154 return GetSnippetWithUrlAndTimesAndSources( 150 {kSnippetUrl}, source_url, GetDefaultCreationTime(),
155 url, content_creation_time_str, expiry_time_str, 151 GetDefaultExpirationTime(), publisher, amp_url);
156 {std::string(url)}, {std::string(kSnippetPublisherName)},
157 {std::string(kSnippetAmpUrl)});
158 } 152 }
159 153
160 std::string GetSnippetWithTimes(const std::string& content_creation_time_str, 154 std::string GetSnippetWithUrlAndTimes(const std::string& url,
161 const std::string& expiry_time_str) { 155 const base::Time& content_creation_time,
162 return GetSnippetWithUrlAndTimes(kSnippetUrl, content_creation_time_str, 156 const base::Time& expiry_time) {
163 expiry_time_str); 157 return GetSnippetWithUrlAndTimesAndSource({url}, url, content_creation_time,
158 expiry_time, kSnippetPublisherName,
159 kSnippetAmpUrl);
160 }
161
162 std::string GetSnippetWithTimes(const base::Time& content_creation_time,
163 const base::Time& expiry_time) {
164 return GetSnippetWithUrlAndTimes(kSnippetUrl, content_creation_time,
165 expiry_time);
164 } 166 }
165 167
166 std::string GetSnippetWithUrl(const std::string& url) { 168 std::string GetSnippetWithUrl(const std::string& url) {
167 return GetSnippetWithUrlAndTimes( 169 return GetSnippetWithUrlAndTimes(url, GetDefaultCreationTime(),
168 url, NTPSnippet::TimeToJsonString(GetDefaultCreationTime()), 170 GetDefaultExpirationTime());
169 NTPSnippet::TimeToJsonString(GetDefaultExpirationTime()));
170 } 171 }
171 172
172 std::string GetSnippet() { 173 std::string GetSnippet() {
173 return GetSnippetWithUrlAndTimes( 174 return GetSnippetWithUrlAndTimes(kSnippetUrl, GetDefaultCreationTime(),
174 kSnippetUrl, NTPSnippet::TimeToJsonString(GetDefaultCreationTime()), 175 GetDefaultExpirationTime());
175 NTPSnippet::TimeToJsonString(GetDefaultExpirationTime()));
176 } 176 }
177 177
178 std::string GetExpiredSnippet() { 178 std::string GetExpiredSnippet() {
179 return GetSnippetWithTimes( 179 return GetSnippetWithTimes(GetDefaultCreationTime(), base::Time::Now());
180 NTPSnippet::TimeToJsonString(GetDefaultCreationTime()),
181 NTPSnippet::TimeToJsonString(base::Time::Now()));
182 } 180 }
183 181
184 std::string GetInvalidSnippet() { 182 std::string GetInvalidSnippet() {
185 std::string json_str = GetSnippet(); 183 std::string json_str = GetSnippet();
186 // Make the json invalid by removing the final closing brace. 184 // Make the json invalid by removing the final closing brace.
187 return json_str.substr(0, json_str.size() - 1); 185 return json_str.substr(0, json_str.size() - 1);
188 } 186 }
189 187
190 std::string GetIncompleteSnippet() { 188 std::string GetIncompleteSnippet() {
191 std::string json_str = GetSnippet(); 189 std::string json_str = GetSnippet();
192 // Rename the "url" entry. The result is syntactically valid json that will 190 // Rename the "url" entry. The result is syntactically valid json that will
193 // fail to parse as snippets. 191 // fail to parse as snippets.
194 size_t pos = json_str.find("\"url\""); 192 size_t pos = json_str.find("\"fullPageUrl\"");
195 if (pos == std::string::npos) { 193 if (pos == std::string::npos) {
196 NOTREACHED(); 194 NOTREACHED();
197 return std::string(); 195 return std::string();
198 } 196 }
199 json_str[pos + 1] = 'x'; 197 json_str[pos + 1] = 'x';
200 return json_str; 198 return json_str;
201 } 199 }
202 200
203 void ServeOneByOneImage( 201 void ServeOneByOneImage(
204 const std::string& id, 202 const std::string& id,
(...skipping 30 matching lines...) Expand all
235 class MockScheduler : public NTPSnippetsScheduler { 233 class MockScheduler : public NTPSnippetsScheduler {
236 public: 234 public:
237 MOCK_METHOD4(Schedule, 235 MOCK_METHOD4(Schedule,
238 bool(base::TimeDelta period_wifi_charging, 236 bool(base::TimeDelta period_wifi_charging,
239 base::TimeDelta period_wifi, 237 base::TimeDelta period_wifi,
240 base::TimeDelta period_fallback, 238 base::TimeDelta period_fallback,
241 base::Time reschedule_time)); 239 base::Time reschedule_time));
242 MOCK_METHOD0(Unschedule, bool()); 240 MOCK_METHOD0(Unschedule, bool());
243 }; 241 };
244 242
245 class WaitForDBLoad {
246 public:
247 WaitForDBLoad(MockContentSuggestionsProviderObserver* observer,
248 NTPSnippetsService* service)
249 : observer_(observer) {
250 EXPECT_CALL(*observer_, OnCategoryStatusChanged(_, _, _))
251 .WillOnce(Invoke(this, &WaitForDBLoad::OnCategoryStatusChanged));
252 if (!service->ready())
253 run_loop_.Run();
254 }
255
256 ~WaitForDBLoad() { Mock::VerifyAndClearExpectations(observer_); }
257
258 private:
259 void OnCategoryStatusChanged(ContentSuggestionsProvider* provider,
260 Category category,
261 CategoryStatus new_status) {
262 EXPECT_EQ(new_status, CategoryStatus::AVAILABLE_LOADING);
263 run_loop_.Quit();
264 }
265
266 MockContentSuggestionsProviderObserver* observer_;
267 base::RunLoop run_loop_;
268
269 DISALLOW_COPY_AND_ASSIGN(WaitForDBLoad);
270 };
271
272 class MockImageFetcher : public ImageFetcher { 243 class MockImageFetcher : public ImageFetcher {
273 public: 244 public:
274 MOCK_METHOD1(SetImageFetcherDelegate, void(ImageFetcherDelegate*)); 245 MOCK_METHOD1(SetImageFetcherDelegate, void(ImageFetcherDelegate*));
275 MOCK_METHOD1(SetDataUseServiceName, void(DataUseServiceName)); 246 MOCK_METHOD1(SetDataUseServiceName, void(DataUseServiceName));
276 MOCK_METHOD3( 247 MOCK_METHOD3(
277 StartOrQueueNetworkRequest, 248 StartOrQueueNetworkRequest,
278 void(const std::string&, 249 void(const std::string&,
279 const GURL&, 250 const GURL&,
280 base::Callback<void(const std::string&, const gfx::Image&)>)); 251 base::Callback<void(const std::string&, const gfx::Image&)>));
281 }; 252 };
282 253
283 class MockDismissedSuggestionsCallback 254 class FakeContentSuggestionsProviderObserver
284 : public ContentSuggestionsProvider::DismissedSuggestionsCallback { 255 : public ContentSuggestionsProvider::Observer {
285 public: 256 public:
286 MOCK_METHOD2(MockRun, 257 FakeContentSuggestionsProviderObserver()
287 void(Category category, 258 : loaded_(base::WaitableEvent::ResetPolicy::MANUAL,
288 std::vector<ContentSuggestion>* dismissed_suggestions)); 259 base::WaitableEvent::InitialState::NOT_SIGNALED) {}
289 void Run(Category category, 260
290 std::vector<ContentSuggestion> dismissed_suggestions) { 261 void OnNewSuggestions(ContentSuggestionsProvider* provider,
291 MockRun(category, &dismissed_suggestions); 262 Category category,
263 std::vector<ContentSuggestion> suggestions) override {
264 suggestions_[category] = std::move(suggestions);
292 } 265 }
266
267 void OnCategoryStatusChanged(ContentSuggestionsProvider* provider,
268 Category category,
269 CategoryStatus new_status) override {
270 loaded_.Signal();
271 statuses_[category] = new_status;
272 }
273
274 void OnSuggestionInvalidated(ContentSuggestionsProvider* provider,
275 Category category,
276 const std::string& suggestion_id) override {}
277
278 CategoryStatus StatusForCategory(Category category) const {
279 auto it = statuses_.find(category);
280 if (it == statuses_.end()) {
281 return CategoryStatus::NOT_PROVIDED;
282 }
283 return it->second;
284 }
285
286 const std::vector<ContentSuggestion>& SuggestionsForCategory(
287 Category category) {
288 return suggestions_[category];
289 }
290
291 void WaitForLoad() { loaded_.Wait(); }
292 bool Loaded() { return loaded_.IsSignaled(); }
293
294 void Reset() {
295 loaded_.Reset();
296 statuses_.clear();
297 }
298
299 private:
300 base::WaitableEvent loaded_;
301 std::map<Category, CategoryStatus, Category::CompareByID> statuses_;
302 std::map<Category, std::vector<ContentSuggestion>, Category::CompareByID>
303 suggestions_;
304
305 DISALLOW_COPY_AND_ASSIGN(FakeContentSuggestionsProviderObserver);
293 }; 306 };
294 307
295 } // namespace 308 } // namespace
296 309
297 class NTPSnippetsServiceTest : public test::NTPSnippetsTestBase { 310 class NTPSnippetsServiceTest : public ::testing::Test {
298 public: 311 public:
299 NTPSnippetsServiceTest() 312 NTPSnippetsServiceTest()
300 : fake_url_fetcher_factory_( 313 : params_manager_(ntp_snippets::kStudyName,
314 {{"content_suggestions_backend",
315 kTestContentSuggestionsServerEndpoint}}),
316 fake_url_fetcher_factory_(
301 /*default_factory=*/&failing_url_fetcher_factory_), 317 /*default_factory=*/&failing_url_fetcher_factory_),
302 test_url_(base::StringPrintf(kTestContentSnippetsServerFormat, 318 test_url_(base::StringPrintf(kTestContentSuggestionsServerFormat,
303 google_apis::GetAPIKey().c_str())) { 319 google_apis::GetAPIKey().c_str())),
304 NTPSnippetsService::RegisterProfilePrefs(pref_service()->registry()); 320
305 RequestThrottler::RegisterProfilePrefs(pref_service()->registry()); 321 observer_(base::MakeUnique<FakeContentSuggestionsProviderObserver>()) {
322 NTPSnippetsService::RegisterProfilePrefs(utils_.pref_service()->registry());
323 RequestThrottler::RegisterProfilePrefs(utils_.pref_service()->registry());
306 324
307 // Since no SuggestionsService is injected in tests, we need to force the 325 // Since no SuggestionsService is injected in tests, we need to force the
308 // service to fetch from all hosts. 326 // service to fetch from all hosts.
309 base::CommandLine::ForCurrentProcess()->AppendSwitch( 327 base::CommandLine::ForCurrentProcess()->AppendSwitch(
310 switches::kDontRestrict); 328 switches::kDontRestrict);
311 EXPECT_TRUE(database_dir_.CreateUniqueTempDir()); 329 EXPECT_TRUE(database_dir_.CreateUniqueTempDir());
312 } 330 }
313 331
314 ~NTPSnippetsServiceTest() override { 332 ~NTPSnippetsServiceTest() override {
315 // We need to run the message loop after deleting the database, because 333 // We need to run the message loop after deleting the database, because
316 // ProtoDatabaseImpl deletes the actual LevelDB asynchronously on the task 334 // ProtoDatabaseImpl deletes the actual LevelDB asynchronously on the task
317 // runner. Without this, we'd get reports of memory leaks. 335 // runner. Without this, we'd get reports of memory leaks.
318 Mock::VerifyAndClear(&mock_scheduler());
319 service_.reset();
320 base::RunLoop().RunUntilIdle(); 336 base::RunLoop().RunUntilIdle();
321 } 337 }
322 338
323 void SetUp() override { 339 std::unique_ptr<NTPSnippetsService> MakeSnippetsService() {
324 test::NTPSnippetsTestBase::SetUp(); 340 CHECK(!observer_->Loaded());
325 EXPECT_CALL(mock_scheduler(), Schedule(_, _, _, _)).Times(1);
326 CreateSnippetsService();
327 }
328
329 void RecreateSnippetsService() {
330 Mock::VerifyAndClear(&mock_scheduler());
331 service_.reset();
332 base::RunLoop().RunUntilIdle();
333 EXPECT_CALL(mock_scheduler(), Schedule(_, _, _, _)).Times(1);
334 CreateSnippetsService();
335 }
336
337 void CreateSnippetsService() {
338 DCHECK(!service_);
339 341
340 scoped_refptr<base::SingleThreadTaskRunner> task_runner( 342 scoped_refptr<base::SingleThreadTaskRunner> task_runner(
341 base::ThreadTaskRunnerHandle::Get()); 343 base::ThreadTaskRunnerHandle::Get());
342 scoped_refptr<net::TestURLRequestContextGetter> request_context_getter = 344 scoped_refptr<net::TestURLRequestContextGetter> request_context_getter =
343 new net::TestURLRequestContextGetter(task_runner.get()); 345 new net::TestURLRequestContextGetter(task_runner.get());
344 346
345 ResetSigninManager(); 347 utils_.ResetSigninManager();
346 std::unique_ptr<NTPSnippetsFetcher> snippets_fetcher = 348 std::unique_ptr<NTPSnippetsFetcher> snippets_fetcher =
347 base::MakeUnique<NTPSnippetsFetcher>( 349 base::MakeUnique<NTPSnippetsFetcher>(
348 fake_signin_manager(), fake_token_service_.get(), 350 utils_.fake_signin_manager(), fake_token_service_.get(),
349 std::move(request_context_getter), pref_service(), 351 std::move(request_context_getter), utils_.pref_service(),
350 &category_factory_, base::Bind(&ParseJson), 352 &category_factory_, base::Bind(&ParseJson),
351 /*is_stable_channel=*/true); 353 /*is_stable_channel=*/true);
352 354
353 fake_signin_manager()->SignIn("foo@bar.com"); 355 utils_.fake_signin_manager()->SignIn("foo@bar.com");
354 snippets_fetcher->SetPersonalizationForTesting( 356 snippets_fetcher->SetPersonalizationForTesting(
355 NTPSnippetsFetcher::Personalization::kNonPersonal); 357 NTPSnippetsFetcher::Personalization::kNonPersonal);
356 358
357 auto image_fetcher = 359 auto image_fetcher = base::MakeUnique<NiceMock<MockImageFetcher>>();
358 base::MakeUnique<testing::NiceMock<MockImageFetcher>>();
359 image_fetcher_ = image_fetcher.get(); 360 image_fetcher_ = image_fetcher.get();
360 361
361 // Add an initial fetch response, as the service tries to fetch when there 362 // Add an initial fetch response, as the service tries to fetch when there
362 // is nothing in the DB. 363 // is nothing in the DB.
363 SetUpFetchResponse(GetTestJson({GetSnippet()})); 364 SetUpFetchResponse(GetTestJson(std::vector<std::string>()));
364 365
365 service_.reset(new NTPSnippetsService( 366 auto service = base::MakeUnique<NTPSnippetsService>(
366 &observer_, &category_factory_, pref_service(), nullptr, nullptr, "fr", 367 observer_.get(), &category_factory_, utils_.pref_service(), nullptr,
367 &scheduler_, std::move(snippets_fetcher), 368 nullptr, "fr", &scheduler_, std::move(snippets_fetcher),
368 std::move(image_fetcher), /*image_decoder=*/nullptr, 369 std::move(image_fetcher), /*image_decoder=*/nullptr,
369 base::MakeUnique<NTPSnippetsDatabase>(database_dir_.path(), 370 base::MakeUnique<NTPSnippetsDatabase>(database_dir_.path(),
370 task_runner), 371 task_runner),
371 base::MakeUnique<NTPSnippetsStatusService>(fake_signin_manager(), 372 base::MakeUnique<NTPSnippetsStatusService>(utils_.fake_signin_manager(),
372 pref_service()))); 373 utils_.pref_service()));
373 374
374 WaitForDBLoad(&observer_, service_.get()); 375 base::RunLoop().RunUntilIdle();
376 observer_->WaitForLoad();
377 return service;
375 } 378 }
376 379
377 std::string MakeUniqueID(const std::string& within_category_id) { 380 void ResetSnippetsService(std::unique_ptr<NTPSnippetsService>* service) {
378 return service()->MakeUniqueID(articles_category(), within_category_id); 381 service->reset();
382 observer_ = base::MakeUnique<FakeContentSuggestionsProviderObserver>();
383 *service = MakeSnippetsService();
384 }
385
386 std::string MakeUniqueID(const NTPSnippetsService& service,
387 const std::string& within_category_id) {
388 return service.MakeUniqueID(articles_category(), within_category_id);
379 } 389 }
380 390
381 Category articles_category() { 391 Category articles_category() {
382 return category_factory_.FromKnownCategory(KnownCategories::ARTICLES); 392 return category_factory_.FromKnownCategory(KnownCategories::ARTICLES);
383 } 393 }
384 394
385 protected: 395 protected:
386 const GURL& test_url() { return test_url_; } 396 const GURL& test_url() { return test_url_; }
387 NTPSnippetsService* service() { return service_.get(); } 397 FakeContentSuggestionsProviderObserver& observer() { return *observer_; }
388 MockContentSuggestionsProviderObserver& observer() { return observer_; }
389 MockScheduler& mock_scheduler() { return scheduler_; } 398 MockScheduler& mock_scheduler() { return scheduler_; }
390 testing::NiceMock<MockImageFetcher>* image_fetcher() { 399 NiceMock<MockImageFetcher>* image_fetcher() { return image_fetcher_; }
391 return image_fetcher_;
392 }
393 400
394 // Provide the json to be returned by the fake fetcher. 401 // Provide the json to be returned by the fake fetcher.
395 void SetUpFetchResponse(const std::string& json) { 402 void SetUpFetchResponse(const std::string& json) {
396 fake_url_fetcher_factory_.SetFakeResponse(test_url_, json, net::HTTP_OK, 403 fake_url_fetcher_factory_.SetFakeResponse(test_url_, json, net::HTTP_OK,
397 net::URLRequestStatus::SUCCESS); 404 net::URLRequestStatus::SUCCESS);
398 } 405 }
399 406
400 void LoadFromJSONString(const std::string& json) { 407 void LoadFromJSONString(NTPSnippetsService* service,
408 const std::string& json) {
401 SetUpFetchResponse(json); 409 SetUpFetchResponse(json);
402 service()->FetchSnippets(true); 410 service->FetchSnippets(true);
403 base::RunLoop().RunUntilIdle(); 411 base::RunLoop().RunUntilIdle();
404 } 412 }
405 413
406 private: 414 private:
415 variations::testing::VariationParamsManager params_manager_;
416 test::NTPSnippetsTestUtils utils_;
407 base::MessageLoop message_loop_; 417 base::MessageLoop message_loop_;
408 FailingFakeURLFetcherFactory failing_url_fetcher_factory_; 418 FailingFakeURLFetcherFactory failing_url_fetcher_factory_;
409 // Instantiation of factory automatically sets itself as URLFetcher's factory. 419 // Instantiation of factory automatically sets itself as URLFetcher's factory.
410 net::FakeURLFetcherFactory fake_url_fetcher_factory_; 420 net::FakeURLFetcherFactory fake_url_fetcher_factory_;
411 const GURL test_url_; 421 const GURL test_url_;
412 std::unique_ptr<OAuth2TokenService> fake_token_service_; 422 std::unique_ptr<OAuth2TokenService> fake_token_service_;
413 MockScheduler scheduler_; 423 NiceMock<MockScheduler> scheduler_;
414 MockContentSuggestionsProviderObserver observer_; 424 std::unique_ptr<FakeContentSuggestionsProviderObserver> observer_;
415 CategoryFactory category_factory_; 425 CategoryFactory category_factory_;
416 testing::NiceMock<MockImageFetcher>* image_fetcher_; 426 NiceMock<MockImageFetcher>* image_fetcher_;
417 // Last so that the dependencies are deleted after the service.
418 std::unique_ptr<NTPSnippetsService> service_;
419 427
420 base::ScopedTempDir database_dir_; 428 base::ScopedTempDir database_dir_;
421 429
422 DISALLOW_COPY_AND_ASSIGN(NTPSnippetsServiceTest); 430 DISALLOW_COPY_AND_ASSIGN(NTPSnippetsServiceTest);
423 }; 431 };
424 432
425 TEST_F(NTPSnippetsServiceTest, ScheduleOnStart) { 433 TEST_F(NTPSnippetsServiceTest, ScheduleOnStart) {
426 // SetUp() checks that Schedule is called. 434 EXPECT_CALL(mock_scheduler(), Schedule(_, _, _, _));
435 auto service = MakeSnippetsService();
427 436
428 // When we have no snippets are all, loading the service initiates a fetch. 437 // When we have no snippets are all, loading the service initiates a fetch.
429 base::RunLoop().RunUntilIdle(); 438 base::RunLoop().RunUntilIdle();
430 ASSERT_EQ("OK", service()->snippets_fetcher()->last_status()); 439 ASSERT_EQ("OK", service->snippets_fetcher()->last_status());
431 } 440 }
432 441
433 TEST_F(NTPSnippetsServiceTest, Full) { 442 TEST_F(NTPSnippetsServiceTest, Full) {
434 std::string json_str(GetTestJson({GetSnippet()})); 443 std::string json_str(GetTestJson({GetSnippet()}));
435 444
436 LoadFromJSONString(json_str); 445 EXPECT_CALL(mock_scheduler(), Schedule(_, _, _, _));
437 ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); 446 auto service = MakeSnippetsService();
438 const NTPSnippet& snippet = *service()->GetSnippetsForTesting().front();
439 447
440 EXPECT_EQ(snippet.id(), kSnippetUrl); 448 LoadFromJSONString(service.get(), json_str);
441 EXPECT_EQ(snippet.title(), kSnippetTitle); 449 ASSERT_THAT(observer().SuggestionsForCategory(articles_category()),
442 EXPECT_EQ(snippet.snippet(), kSnippetText); 450 SizeIs(1));
443 EXPECT_EQ(snippet.salient_image_url(), GURL(kSnippetSalientImage)); 451 ASSERT_THAT(service->GetSnippetsForTesting(), SizeIs(1));
444 EXPECT_EQ(GetDefaultCreationTime(), snippet.publish_date()); 452 const ContentSuggestion& suggestion =
445 EXPECT_EQ(snippet.best_source().publisher_name, kSnippetPublisherName); 453 observer().SuggestionsForCategory(articles_category()).front();
446 EXPECT_EQ(snippet.best_source().amp_url, GURL(kSnippetAmpUrl)); 454
455 EXPECT_EQ(MakeUniqueID(*service, kSnippetUrl), suggestion.id());
456 EXPECT_EQ(kSnippetTitle, base::UTF16ToUTF8(suggestion.title()));
457 EXPECT_EQ(kSnippetText, base::UTF16ToUTF8(suggestion.snippet_text()));
458 // EXPECT_EQ(GURL(kSnippetSalientImage), suggestion.salient_image_url());
459 EXPECT_EQ(GetDefaultCreationTime(), suggestion.publish_date());
460 EXPECT_EQ(kSnippetPublisherName,
461 base::UTF16ToUTF8(suggestion.publisher_name()));
462 EXPECT_EQ(GURL(kSnippetAmpUrl), suggestion.amp_url());
447 } 463 }
448 464
449 TEST_F(NTPSnippetsServiceTest, Clear) { 465 TEST_F(NTPSnippetsServiceTest, Clear) {
466 EXPECT_CALL(mock_scheduler(), Schedule(_, _, _, _));
467 auto service = MakeSnippetsService();
468
450 std::string json_str(GetTestJson({GetSnippet()})); 469 std::string json_str(GetTestJson({GetSnippet()}));
451 470
452 LoadFromJSONString(json_str); 471 LoadFromJSONString(service.get(), json_str);
453 EXPECT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); 472 EXPECT_THAT(service->GetSnippetsForTesting(), SizeIs(1));
454 473
455 service()->ClearCachedSuggestions(articles_category()); 474 service->ClearCachedSuggestions(articles_category());
456 EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); 475 EXPECT_THAT(service->GetSnippetsForTesting(), IsEmpty());
457 } 476 }
458 477
459 TEST_F(NTPSnippetsServiceTest, InsertAtFront) { 478 TEST_F(NTPSnippetsServiceTest, InsertAtFront) {
479 EXPECT_CALL(mock_scheduler(), Schedule(_, _, _, _));
480 auto service = MakeSnippetsService();
481
460 std::string first("http://first"); 482 std::string first("http://first");
461 LoadFromJSONString(GetTestJson({GetSnippetWithUrl(first)})); 483 LoadFromJSONString(service.get(), GetTestJson({GetSnippetWithUrl(first)}));
462 EXPECT_THAT(service()->GetSnippetsForTesting(), ElementsAre(IdEq(first))); 484 EXPECT_THAT(service->GetSnippetsForTesting(), ElementsAre(IdEq(first)));
463 485
464 std::string second("http://second"); 486 std::string second("http://second");
465 LoadFromJSONString(GetTestJson({GetSnippetWithUrl(second)})); 487 LoadFromJSONString(service.get(), GetTestJson({GetSnippetWithUrl(second)}));
466 // The snippet loaded last should be at the first position in the list now. 488 // The snippet loaded last should be at the first position in the list now.
467 EXPECT_THAT(service()->GetSnippetsForTesting(), 489 EXPECT_THAT(service->GetSnippetsForTesting(),
468 ElementsAre(IdEq(second), IdEq(first))); 490 ElementsAre(IdEq(second), IdEq(first)));
469 } 491 }
470 492
471 TEST_F(NTPSnippetsServiceTest, LimitNumSnippets) { 493 TEST_F(NTPSnippetsServiceTest, LimitNumSnippets) {
494 auto service = MakeSnippetsService();
495
472 int max_snippet_count = NTPSnippetsService::GetMaxSnippetCountForTesting(); 496 int max_snippet_count = NTPSnippetsService::GetMaxSnippetCountForTesting();
473 int snippets_per_load = max_snippet_count / 2 + 1; 497 int snippets_per_load = max_snippet_count / 2 + 1;
474 char url_format[] = "http://localhost/%i"; 498 char url_format[] = "http://localhost/%i";
475 499
476 std::vector<std::string> snippets1; 500 std::vector<std::string> snippets1;
477 std::vector<std::string> snippets2; 501 std::vector<std::string> snippets2;
478 for (int i = 0; i < snippets_per_load; i++) { 502 for (int i = 0; i < snippets_per_load; i++) {
479 snippets1.push_back(GetSnippetWithUrl(base::StringPrintf(url_format, i))); 503 snippets1.push_back(GetSnippetWithUrl(base::StringPrintf(url_format, i)));
480 snippets2.push_back(GetSnippetWithUrl( 504 snippets2.push_back(GetSnippetWithUrl(
481 base::StringPrintf(url_format, snippets_per_load + i))); 505 base::StringPrintf(url_format, snippets_per_load + i)));
482 } 506 }
483 507
484 LoadFromJSONString(GetTestJson(snippets1)); 508 LoadFromJSONString(service.get(), GetTestJson(snippets1));
485 ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(snippets1.size())); 509 ASSERT_THAT(service->GetSnippetsForTesting(), SizeIs(snippets1.size()));
486 510
487 LoadFromJSONString(GetTestJson(snippets2)); 511 LoadFromJSONString(service.get(), GetTestJson(snippets2));
488 EXPECT_THAT(service()->GetSnippetsForTesting(), SizeIs(max_snippet_count)); 512 EXPECT_THAT(service->GetSnippetsForTesting(), SizeIs(max_snippet_count));
489 } 513 }
490 514
491 TEST_F(NTPSnippetsServiceTest, LoadInvalidJson) { 515 TEST_F(NTPSnippetsServiceTest, LoadInvalidJson) {
492 LoadFromJSONString(GetTestJson({GetInvalidSnippet()})); 516 auto service = MakeSnippetsService();
493 EXPECT_THAT(service()->snippets_fetcher()->last_status(), 517
518 LoadFromJSONString(service.get(), GetTestJson({GetInvalidSnippet()}));
519 EXPECT_THAT(service->snippets_fetcher()->last_status(),
494 StartsWith("Received invalid JSON")); 520 StartsWith("Received invalid JSON"));
495 EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); 521 EXPECT_THAT(service->GetSnippetsForTesting(), IsEmpty());
496 } 522 }
497 523
498 TEST_F(NTPSnippetsServiceTest, LoadInvalidJsonWithExistingSnippets) { 524 TEST_F(NTPSnippetsServiceTest, LoadInvalidJsonWithExistingSnippets) {
499 LoadFromJSONString(GetTestJson({GetSnippet()})); 525 auto service = MakeSnippetsService();
500 ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1));
501 ASSERT_EQ("OK", service()->snippets_fetcher()->last_status());
502 526
503 LoadFromJSONString(GetTestJson({GetInvalidSnippet()})); 527 LoadFromJSONString(service.get(), GetTestJson({GetSnippet()}));
504 EXPECT_THAT(service()->snippets_fetcher()->last_status(), 528 ASSERT_THAT(service->GetSnippetsForTesting(), SizeIs(1));
529 ASSERT_EQ("OK", service->snippets_fetcher()->last_status());
530
531 LoadFromJSONString(service.get(), GetTestJson({GetInvalidSnippet()}));
532 EXPECT_THAT(service->snippets_fetcher()->last_status(),
505 StartsWith("Received invalid JSON")); 533 StartsWith("Received invalid JSON"));
506 // This should not have changed the existing snippets. 534 // This should not have changed the existing snippets.
507 EXPECT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); 535 EXPECT_THAT(service->GetSnippetsForTesting(), SizeIs(1));
508 } 536 }
509 537
510 TEST_F(NTPSnippetsServiceTest, LoadIncompleteJson) { 538 TEST_F(NTPSnippetsServiceTest, LoadIncompleteJson) {
511 LoadFromJSONString(GetTestJson({GetIncompleteSnippet()})); 539 auto service = MakeSnippetsService();
540
541 LoadFromJSONString(service.get(), GetTestJson({GetIncompleteSnippet()}));
512 EXPECT_EQ("Invalid / empty list.", 542 EXPECT_EQ("Invalid / empty list.",
513 service()->snippets_fetcher()->last_status()); 543 service->snippets_fetcher()->last_status());
514 EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); 544 EXPECT_THAT(service->GetSnippetsForTesting(), IsEmpty());
515 } 545 }
516 546
517 TEST_F(NTPSnippetsServiceTest, LoadIncompleteJsonWithExistingSnippets) { 547 TEST_F(NTPSnippetsServiceTest, LoadIncompleteJsonWithExistingSnippets) {
518 LoadFromJSONString(GetTestJson({GetSnippet()})); 548 auto service = MakeSnippetsService();
519 ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1));
520 549
521 LoadFromJSONString(GetTestJson({GetIncompleteSnippet()})); 550 LoadFromJSONString(service.get(), GetTestJson({GetSnippet()}));
551 ASSERT_THAT(service->GetSnippetsForTesting(), SizeIs(1));
552
553 LoadFromJSONString(service.get(), GetTestJson({GetIncompleteSnippet()}));
522 EXPECT_EQ("Invalid / empty list.", 554 EXPECT_EQ("Invalid / empty list.",
523 service()->snippets_fetcher()->last_status()); 555 service->snippets_fetcher()->last_status());
524 // This should not have changed the existing snippets. 556 // This should not have changed the existing snippets.
525 EXPECT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); 557 EXPECT_THAT(service->GetSnippetsForTesting(), SizeIs(1));
526 } 558 }
527 559
528 TEST_F(NTPSnippetsServiceTest, Dismiss) { 560 TEST_F(NTPSnippetsServiceTest, Dismiss) {
529 std::vector<std::string> source_urls, publishers, amp_urls; 561 EXPECT_CALL(mock_scheduler(), Schedule(_, _, _, _)).Times(2);
530 source_urls.push_back(std::string("http://site.com")); 562 auto service = MakeSnippetsService();
531 publishers.push_back(std::string("Source 1")); 563
532 amp_urls.push_back(std::string());
533 std::string json_str( 564 std::string json_str(
534 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)})); 565 GetTestJson({GetSnippetWithSources("http://site.com", "Source 1", "")}));
535 566
536 LoadFromJSONString(json_str); 567 LoadFromJSONString(service.get(), json_str);
537 568
538 ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); 569 ASSERT_THAT(service->GetSnippetsForTesting(), SizeIs(1));
539 570
540 // Dismissing a non-existent snippet shouldn't do anything. 571 // Dismissing a non-existent snippet shouldn't do anything.
541 service()->DismissSuggestion(MakeUniqueID("http://othersite.com")); 572 service->DismissSuggestion(MakeUniqueID(*service, "http://othersite.com"));
542 EXPECT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); 573 EXPECT_THAT(service->GetSnippetsForTesting(), SizeIs(1));
543 574
544 // Dismiss the snippet. 575 // Dismiss the snippet.
545 service()->DismissSuggestion(MakeUniqueID(kSnippetUrl)); 576 service->DismissSuggestion(MakeUniqueID(*service, kSnippetUrl));
546 EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); 577 EXPECT_THAT(service->GetSnippetsForTesting(), IsEmpty());
547 578
548 // Make sure that fetching the same snippet again does not re-add it. 579 // Make sure that fetching the same snippet again does not re-add it.
549 LoadFromJSONString(json_str); 580 LoadFromJSONString(service.get(), json_str);
550 EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); 581 EXPECT_THAT(service->GetSnippetsForTesting(), IsEmpty());
551 582
552 // The snippet should stay dismissed even after re-creating the service. 583 // The snippet should stay dismissed even after re-creating the service.
553 RecreateSnippetsService(); 584 ResetSnippetsService(&service);
554 LoadFromJSONString(json_str); 585 LoadFromJSONString(service.get(), json_str);
555 EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); 586 EXPECT_THAT(service->GetSnippetsForTesting(), IsEmpty());
556 587
557 // The snippet can be added again after clearing dismissed snippets. 588 // The snippet can be added again after clearing dismissed snippets.
558 service()->ClearDismissedSuggestionsForDebugging(articles_category()); 589 service->ClearDismissedSuggestionsForDebugging(articles_category());
559 EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); 590 EXPECT_THAT(service->GetSnippetsForTesting(), IsEmpty());
560 LoadFromJSONString(json_str); 591 LoadFromJSONString(service.get(), json_str);
561 EXPECT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); 592 EXPECT_THAT(service->GetSnippetsForTesting(), SizeIs(1));
562 } 593 }
563 594
564 TEST_F(NTPSnippetsServiceTest, GetDismissed) { 595 TEST_F(NTPSnippetsServiceTest, GetDismissed) {
565 LoadFromJSONString(GetTestJson({GetSnippet()})); 596 auto service = MakeSnippetsService();
566 597
567 service()->DismissSuggestion(MakeUniqueID(kSnippetUrl)); 598 LoadFromJSONString(service.get(), GetTestJson({GetSnippet()}));
568 599
569 MockDismissedSuggestionsCallback callback; 600 service->DismissSuggestion(MakeUniqueID(*service, kSnippetUrl));
570 601
571 EXPECT_CALL(callback, MockRun(_, _)) 602 service->GetDismissedSuggestionsForDebugging(
572 .WillOnce( 603 articles_category(),
573 Invoke([this](Category category, 604 base::Bind(
574 std::vector<ContentSuggestion>* dismissed_suggestions) { 605 [](NTPSnippetsService* service, NTPSnippetsServiceTest* test,
575 EXPECT_EQ(1u, dismissed_suggestions->size()); 606 std::vector<ContentSuggestion> dismissed_suggestions) {
576 for (auto& suggestion : *dismissed_suggestions) { 607 EXPECT_EQ(1u, dismissed_suggestions.size());
577 EXPECT_EQ(MakeUniqueID(kSnippetUrl), suggestion.id()); 608 for (auto& suggestion : dismissed_suggestions) {
609 EXPECT_EQ(test->MakeUniqueID(*service, kSnippetUrl),
610 suggestion.id());
578 } 611 }
579 })); 612 },
580 service()->GetDismissedSuggestionsForDebugging( 613 service.get(), this));
581 articles_category(), 614 base::RunLoop().RunUntilIdle();
582 base::Bind(&MockDismissedSuggestionsCallback::Run,
583 base::Unretained(&callback), articles_category()));
584 Mock::VerifyAndClearExpectations(&callback);
585 615
586 // There should be no dismissed snippet after clearing the list. 616 // There should be no dismissed snippet after clearing the list.
587 EXPECT_CALL(callback, MockRun(_, _)) 617 service->ClearDismissedSuggestionsForDebugging(articles_category());
588 .WillOnce( 618 service->GetDismissedSuggestionsForDebugging(
589 Invoke([this](Category category,
590 std::vector<ContentSuggestion>* dismissed_suggestions) {
591 EXPECT_EQ(0u, dismissed_suggestions->size());
592 }));
593 service()->ClearDismissedSuggestionsForDebugging(articles_category());
594 service()->GetDismissedSuggestionsForDebugging(
595 articles_category(), 619 articles_category(),
596 base::Bind(&MockDismissedSuggestionsCallback::Run, 620 base::Bind(
597 base::Unretained(&callback), articles_category())); 621 [](NTPSnippetsService* service, NTPSnippetsServiceTest* test,
622 std::vector<ContentSuggestion> dismissed_suggestions) {
623 EXPECT_EQ(0u, dismissed_suggestions.size());
624 },
625 service.get(), this));
626 base::RunLoop().RunUntilIdle();
598 } 627 }
599 628
600 TEST_F(NTPSnippetsServiceTest, CreationTimestampParseFail) { 629 TEST_F(NTPSnippetsServiceTest, CreationTimestampParseFail) {
601 std::string json_str(GetTestJson({GetSnippetWithTimes( 630 auto service = MakeSnippetsService();
602 "aaa1448459205",
603 NTPSnippet::TimeToJsonString(GetDefaultExpirationTime()))}));
604 631
605 LoadFromJSONString(json_str); 632 std::string json =
606 ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); 633 GetSnippetWithTimes(GetDefaultCreationTime(), GetDefaultExpirationTime());
607 const NTPSnippet& snippet = *service()->GetSnippetsForTesting().front(); 634 base::ReplaceFirstSubstringAfterOffset(
608 EXPECT_EQ(snippet.id(), kSnippetUrl); 635 &json, 0, FormatTime(GetDefaultCreationTime()), "aaa1448459205");
609 EXPECT_EQ(snippet.title(), kSnippetTitle); 636 std::string json_str(GetTestJson({json}));
610 EXPECT_EQ(snippet.snippet(), kSnippetText); 637
611 EXPECT_EQ(base::Time::UnixEpoch(), snippet.publish_date()); 638 LoadFromJSONString(service.get(), json_str);
639 EXPECT_THAT(service->GetSnippetsForTesting(), IsEmpty());
612 } 640 }
613 641
614 TEST_F(NTPSnippetsServiceTest, RemoveExpiredContent) { 642 TEST_F(NTPSnippetsServiceTest, RemoveExpiredContent) {
643 auto service = MakeSnippetsService();
644
615 std::string json_str(GetTestJson({GetExpiredSnippet()})); 645 std::string json_str(GetTestJson({GetExpiredSnippet()}));
616 646
617 LoadFromJSONString(json_str); 647 LoadFromJSONString(service.get(), json_str);
618 EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); 648 EXPECT_THAT(service->GetSnippetsForTesting(), IsEmpty());
619 } 649 }
620 650
621 TEST_F(NTPSnippetsServiceTest, TestSingleSource) { 651 TEST_F(NTPSnippetsServiceTest, TestSingleSource) {
622 std::vector<std::string> source_urls, publishers, amp_urls; 652 auto service = MakeSnippetsService();
623 source_urls.push_back(std::string("http://source1.com"));
624 publishers.push_back(std::string("Source 1"));
625 amp_urls.push_back(std::string("http://source1.amp.com"));
626 std::string json_str(
627 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)}));
628 653
629 LoadFromJSONString(json_str); 654 std::string json_str(GetTestJson({GetSnippetWithSources(
630 ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); 655 "http://source1.com", "Source 1", "http://source1.amp.com")}));
631 const NTPSnippet& snippet = *service()->GetSnippetsForTesting().front(); 656
657 LoadFromJSONString(service.get(), json_str);
658 ASSERT_THAT(service->GetSnippetsForTesting(), SizeIs(1));
659 const NTPSnippet& snippet = *service->GetSnippetsForTesting().front();
632 EXPECT_EQ(snippet.sources().size(), 1u); 660 EXPECT_EQ(snippet.sources().size(), 1u);
633 EXPECT_EQ(snippet.id(), kSnippetUrl); 661 EXPECT_EQ(snippet.id(), kSnippetUrl);
634 EXPECT_EQ(snippet.best_source().url, GURL("http://source1.com")); 662 EXPECT_EQ(snippet.best_source().url, GURL("http://source1.com"));
635 EXPECT_EQ(snippet.best_source().publisher_name, std::string("Source 1")); 663 EXPECT_EQ(snippet.best_source().publisher_name, std::string("Source 1"));
636 EXPECT_EQ(snippet.best_source().amp_url, GURL("http://source1.amp.com")); 664 EXPECT_EQ(snippet.best_source().amp_url, GURL("http://source1.amp.com"));
637 } 665 }
638 666
639 TEST_F(NTPSnippetsServiceTest, TestSingleSourceWithMalformedUrl) { 667 TEST_F(NTPSnippetsServiceTest, TestSingleSourceWithMalformedUrl) {
640 std::vector<std::string> source_urls, publishers, amp_urls; 668 auto service = MakeSnippetsService();
641 source_urls.push_back(std::string("aaaa"));
642 publishers.push_back(std::string("Source 1"));
643 amp_urls.push_back(std::string("http://source1.amp.com"));
644 std::string json_str(
645 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)}));
646 669
647 LoadFromJSONString(json_str); 670 std::string json_str(GetTestJson({GetSnippetWithSources(
648 EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); 671 "ceci n'est pas un url", "Source 1", "http://source1.amp.com")}));
672
673 LoadFromJSONString(service.get(), json_str);
674 EXPECT_THAT(service->GetSnippetsForTesting(), IsEmpty());
649 } 675 }
650 676
651 TEST_F(NTPSnippetsServiceTest, TestSingleSourceWithMissingData) { 677 TEST_F(NTPSnippetsServiceTest, TestSingleSourceWithMissingData) {
652 std::vector<std::string> source_urls, publishers, amp_urls; 678 auto service = MakeSnippetsService();
653 source_urls.push_back(std::string("http://source1.com")); 679
654 publishers.push_back(std::string());
655 amp_urls.push_back(std::string());
656 std::string json_str( 680 std::string json_str(
657 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)})); 681 GetTestJson({GetSnippetWithSources("http://source1.com", "", "")}));
658 682
659 LoadFromJSONString(json_str); 683 LoadFromJSONString(service.get(), json_str);
660 EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); 684 EXPECT_THAT(service->GetSnippetsForTesting(), IsEmpty());
661 }
662
663 TEST_F(NTPSnippetsServiceTest, TestMultipleSources) {
664 std::vector<std::string> source_urls, publishers, amp_urls;
665 source_urls.push_back(std::string("http://source1.com"));
666 source_urls.push_back(std::string("http://source2.com"));
667 publishers.push_back(std::string("Source 1"));
668 publishers.push_back(std::string("Source 2"));
669 amp_urls.push_back(std::string("http://source1.amp.com"));
670 amp_urls.push_back(std::string("http://source2.amp.com"));
671 std::string json_str(
672 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)}));
673
674 LoadFromJSONString(json_str);
675 ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1));
676 const NTPSnippet& snippet = *service()->GetSnippetsForTesting().front();
677 // Expect the first source to be chosen
678 EXPECT_EQ(snippet.sources().size(), 2u);
679 EXPECT_EQ(snippet.id(), kSnippetUrl);
680 EXPECT_EQ(snippet.best_source().url, GURL("http://source1.com"));
681 EXPECT_EQ(snippet.best_source().publisher_name, std::string("Source 1"));
682 EXPECT_EQ(snippet.best_source().amp_url, GURL("http://source1.amp.com"));
683 }
684
685 TEST_F(NTPSnippetsServiceTest, TestMultipleIncompleteSources) {
686 // Set Source 2 to have no AMP url, and Source 1 to have no publisher name
687 // Source 2 should win since we favor publisher name over amp url
688 std::vector<std::string> source_urls, publishers, amp_urls;
689 source_urls.push_back(std::string("http://source1.com"));
690 source_urls.push_back(std::string("http://source2.com"));
691 publishers.push_back(std::string());
692 publishers.push_back(std::string("Source 2"));
693 amp_urls.push_back(std::string("http://source1.amp.com"));
694 amp_urls.push_back(std::string());
695 std::string json_str(
696 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)}));
697
698 LoadFromJSONString(json_str);
699 ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1));
700 {
701 const NTPSnippet& snippet = *service()->GetSnippetsForTesting().front();
702 EXPECT_EQ(snippet.sources().size(), 2u);
703 EXPECT_EQ(snippet.id(), kSnippetUrl);
704 EXPECT_EQ(snippet.best_source().url, GURL("http://source2.com"));
705 EXPECT_EQ(snippet.best_source().publisher_name, std::string("Source 2"));
706 EXPECT_EQ(snippet.best_source().amp_url, GURL());
707 }
708
709 service()->ClearCachedSuggestions(articles_category());
710 // Set Source 1 to have no AMP url, and Source 2 to have no publisher name
711 // Source 1 should win in this case since we prefer publisher name to AMP url
712 source_urls.clear();
713 source_urls.push_back(std::string("http://source1.com"));
714 source_urls.push_back(std::string("http://source2.com"));
715 publishers.clear();
716 publishers.push_back(std::string("Source 1"));
717 publishers.push_back(std::string());
718 amp_urls.clear();
719 amp_urls.push_back(std::string());
720 amp_urls.push_back(std::string("http://source2.amp.com"));
721 json_str =
722 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)});
723
724 LoadFromJSONString(json_str);
725 ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1));
726 {
727 const NTPSnippet& snippet = *service()->GetSnippetsForTesting().front();
728 EXPECT_EQ(snippet.sources().size(), 2u);
729 EXPECT_EQ(snippet.id(), kSnippetUrl);
730 EXPECT_EQ(snippet.best_source().url, GURL("http://source1.com"));
731 EXPECT_EQ(snippet.best_source().publisher_name, std::string("Source 1"));
732 EXPECT_EQ(snippet.best_source().amp_url, GURL());
733 }
734
735 service()->ClearCachedSuggestions(articles_category());
736 // Set source 1 to have no AMP url and no source, and source 2 to only have
737 // amp url. There should be no snippets since we only add sources we consider
738 // complete
739 source_urls.clear();
740 source_urls.push_back(std::string("http://source1.com"));
741 source_urls.push_back(std::string("http://source2.com"));
742 publishers.clear();
743 publishers.push_back(std::string());
744 publishers.push_back(std::string());
745 amp_urls.clear();
746 amp_urls.push_back(std::string());
747 amp_urls.push_back(std::string("http://source2.amp.com"));
748 json_str =
749 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)});
750
751 LoadFromJSONString(json_str);
752 EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty());
753 }
754
755 TEST_F(NTPSnippetsServiceTest, TestMultipleCompleteSources) {
756 // Test 2 complete sources, we should choose the first complete source
757 std::vector<std::string> source_urls, publishers, amp_urls;
758 source_urls.push_back(std::string("http://source1.com"));
759 source_urls.push_back(std::string("http://source2.com"));
760 source_urls.push_back(std::string("http://source3.com"));
761 publishers.push_back(std::string("Source 1"));
762 publishers.push_back(std::string());
763 publishers.push_back(std::string("Source 3"));
764 amp_urls.push_back(std::string("http://source1.amp.com"));
765 amp_urls.push_back(std::string("http://source2.amp.com"));
766 amp_urls.push_back(std::string("http://source3.amp.com"));
767 std::string json_str(
768 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)}));
769
770 LoadFromJSONString(json_str);
771 ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1));
772 {
773 const NTPSnippet& snippet = *service()->GetSnippetsForTesting().front();
774 EXPECT_EQ(snippet.sources().size(), 3u);
775 EXPECT_EQ(snippet.id(), kSnippetUrl);
776 EXPECT_EQ(snippet.best_source().url, GURL("http://source1.com"));
777 EXPECT_EQ(snippet.best_source().publisher_name, std::string("Source 1"));
778 EXPECT_EQ(snippet.best_source().amp_url, GURL("http://source1.amp.com"));
779 }
780
781 // Test 2 complete sources, we should choose the first complete source
782 service()->ClearCachedSuggestions(articles_category());
783 source_urls.clear();
784 source_urls.push_back(std::string("http://source1.com"));
785 source_urls.push_back(std::string("http://source2.com"));
786 source_urls.push_back(std::string("http://source3.com"));
787 publishers.clear();
788 publishers.push_back(std::string());
789 publishers.push_back(std::string("Source 2"));
790 publishers.push_back(std::string("Source 3"));
791 amp_urls.clear();
792 amp_urls.push_back(std::string("http://source1.amp.com"));
793 amp_urls.push_back(std::string("http://source2.amp.com"));
794 amp_urls.push_back(std::string("http://source3.amp.com"));
795 json_str =
796 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)});
797
798 LoadFromJSONString(json_str);
799 ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1));
800 {
801 const NTPSnippet& snippet = *service()->GetSnippetsForTesting().front();
802 EXPECT_EQ(snippet.sources().size(), 3u);
803 EXPECT_EQ(snippet.id(), kSnippetUrl);
804 EXPECT_EQ(snippet.best_source().url, GURL("http://source2.com"));
805 EXPECT_EQ(snippet.best_source().publisher_name, std::string("Source 2"));
806 EXPECT_EQ(snippet.best_source().amp_url, GURL("http://source2.amp.com"));
807 }
808
809 // Test 3 complete sources, we should choose the first complete source
810 service()->ClearCachedSuggestions(articles_category());
811 source_urls.clear();
812 source_urls.push_back(std::string("http://source1.com"));
813 source_urls.push_back(std::string("http://source2.com"));
814 source_urls.push_back(std::string("http://source3.com"));
815 publishers.clear();
816 publishers.push_back(std::string("Source 1"));
817 publishers.push_back(std::string("Source 2"));
818 publishers.push_back(std::string("Source 3"));
819 amp_urls.clear();
820 amp_urls.push_back(std::string());
821 amp_urls.push_back(std::string("http://source2.amp.com"));
822 amp_urls.push_back(std::string("http://source3.amp.com"));
823 json_str =
824 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)});
825
826 LoadFromJSONString(json_str);
827 ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1));
828 {
829 const NTPSnippet& snippet = *service()->GetSnippetsForTesting().front();
830 EXPECT_EQ(snippet.sources().size(), 3u);
831 EXPECT_EQ(snippet.id(), kSnippetUrl);
832 EXPECT_EQ(snippet.best_source().url, GURL("http://source2.com"));
833 EXPECT_EQ(snippet.best_source().publisher_name, std::string("Source 2"));
834 EXPECT_EQ(snippet.best_source().amp_url, GURL("http://source2.amp.com"));
835 }
836 } 685 }
837 686
838 TEST_F(NTPSnippetsServiceTest, LogNumArticlesHistogram) { 687 TEST_F(NTPSnippetsServiceTest, LogNumArticlesHistogram) {
688 auto service = MakeSnippetsService();
689
839 base::HistogramTester tester; 690 base::HistogramTester tester;
840 LoadFromJSONString(GetTestJson({GetInvalidSnippet()})); 691 LoadFromJSONString(service.get(), GetTestJson({GetInvalidSnippet()}));
841 692
842 EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"), 693 EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"),
843 ElementsAre(base::Bucket(/*min=*/0, /*count=*/1))); 694 ElementsAre(base::Bucket(/*min=*/0, /*count=*/1)));
695
844 // Invalid JSON shouldn't contribute to NumArticlesFetched. 696 // Invalid JSON shouldn't contribute to NumArticlesFetched.
845 EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticlesFetched"), 697 EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticlesFetched"),
846 IsEmpty()); 698 IsEmpty());
699
847 // Valid JSON with empty list. 700 // Valid JSON with empty list.
848 LoadFromJSONString(GetTestJson(std::vector<std::string>())); 701 LoadFromJSONString(service.get(), GetTestJson(std::vector<std::string>()));
849 EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"), 702 EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"),
850 ElementsAre(base::Bucket(/*min=*/0, /*count=*/2))); 703 ElementsAre(base::Bucket(/*min=*/0, /*count=*/2)));
851 EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticlesFetched"), 704 EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticlesFetched"),
852 ElementsAre(base::Bucket(/*min=*/0, /*count=*/1))); 705 ElementsAre(base::Bucket(/*min=*/0, /*count=*/1)));
706
853 // Snippet list should be populated with size 1. 707 // Snippet list should be populated with size 1.
854 LoadFromJSONString(GetTestJson({GetSnippet()})); 708 LoadFromJSONString(service.get(), GetTestJson({GetSnippet()}));
855 EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"), 709 EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"),
856 ElementsAre(base::Bucket(/*min=*/0, /*count=*/2), 710 ElementsAre(base::Bucket(/*min=*/0, /*count=*/2),
857 base::Bucket(/*min=*/1, /*count=*/1))); 711 base::Bucket(/*min=*/1, /*count=*/1)));
858 EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticlesFetched"), 712 EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticlesFetched"),
859 ElementsAre(base::Bucket(/*min=*/0, /*count=*/1), 713 ElementsAre(base::Bucket(/*min=*/0, /*count=*/1),
860 base::Bucket(/*min=*/1, /*count=*/1))); 714 base::Bucket(/*min=*/1, /*count=*/1)));
715
861 // Duplicate snippet shouldn't increase the list size. 716 // Duplicate snippet shouldn't increase the list size.
862 LoadFromJSONString(GetTestJson({GetSnippet()})); 717 LoadFromJSONString(service.get(), GetTestJson({GetSnippet()}));
863 EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"), 718 EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"),
864 ElementsAre(base::Bucket(/*min=*/0, /*count=*/2), 719 ElementsAre(base::Bucket(/*min=*/0, /*count=*/2),
865 base::Bucket(/*min=*/1, /*count=*/2))); 720 base::Bucket(/*min=*/1, /*count=*/2)));
866 EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticlesFetched"), 721 EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticlesFetched"),
867 ElementsAre(base::Bucket(/*min=*/0, /*count=*/1), 722 ElementsAre(base::Bucket(/*min=*/0, /*count=*/1),
868 base::Bucket(/*min=*/1, /*count=*/2))); 723 base::Bucket(/*min=*/1, /*count=*/2)));
869 EXPECT_THAT( 724 EXPECT_THAT(
870 tester.GetAllSamples("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded"), 725 tester.GetAllSamples("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded"),
871 IsEmpty()); 726 IsEmpty());
727
872 // Dismissing a snippet should decrease the list size. This will only be 728 // Dismissing a snippet should decrease the list size. This will only be
873 // logged after the next fetch. 729 // logged after the next fetch.
874 service()->DismissSuggestion(MakeUniqueID(kSnippetUrl)); 730 service->DismissSuggestion(MakeUniqueID(*service, kSnippetUrl));
875 LoadFromJSONString(GetTestJson({GetSnippet()})); 731 LoadFromJSONString(service.get(), GetTestJson({GetSnippet()}));
876 EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"), 732 EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"),
877 ElementsAre(base::Bucket(/*min=*/0, /*count=*/3), 733 ElementsAre(base::Bucket(/*min=*/0, /*count=*/3),
878 base::Bucket(/*min=*/1, /*count=*/2))); 734 base::Bucket(/*min=*/1, /*count=*/2)));
879 // Dismissed snippets shouldn't influence NumArticlesFetched. 735 // Dismissed snippets shouldn't influence NumArticlesFetched.
880 EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticlesFetched"), 736 EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticlesFetched"),
881 ElementsAre(base::Bucket(/*min=*/0, /*count=*/1), 737 ElementsAre(base::Bucket(/*min=*/0, /*count=*/1),
882 base::Bucket(/*min=*/1, /*count=*/3))); 738 base::Bucket(/*min=*/1, /*count=*/3)));
883 EXPECT_THAT( 739 EXPECT_THAT(
884 tester.GetAllSamples("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded"), 740 tester.GetAllSamples("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded"),
885 ElementsAre(base::Bucket(/*min=*/1, /*count=*/1))); 741 ElementsAre(base::Bucket(/*min=*/1, /*count=*/1)));
886 // Recreating the service and loading from prefs shouldn't count as fetched 742
887 // articles. 743 // There is only a single, dismissed snippet in the database, so recreating
888 RecreateSnippetsService(); 744 // the service will require us to re-fetch.
889 tester.ExpectTotalCount("NewTabPage.Snippets.NumArticlesFetched", 4); 745 tester.ExpectTotalCount("NewTabPage.Snippets.NumArticlesFetched", 4);
746 ResetSnippetsService(&service);
747 EXPECT_EQ(observer().StatusForCategory(articles_category()),
748 CategoryStatus::AVAILABLE);
749 tester.ExpectTotalCount("NewTabPage.Snippets.NumArticlesFetched", 5);
750 EXPECT_THAT(
751 tester.GetAllSamples("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded"),
752 ElementsAre(base::Bucket(/*min=*/1, /*count=*/2)));
753
754 // But if there's a non-dismissed snippet in the database, recreating it
755 // shouldn't trigger a fetch.
756 LoadFromJSONString(
757 service.get(),
758 GetTestJson({GetSnippetWithUrl("http://not-dismissed.com")}));
759 tester.ExpectTotalCount("NewTabPage.Snippets.NumArticlesFetched", 6);
760 ResetSnippetsService(&service);
761 tester.ExpectTotalCount("NewTabPage.Snippets.NumArticlesFetched", 6);
890 } 762 }
891 763
892 TEST_F(NTPSnippetsServiceTest, DismissShouldRespectAllKnownUrls) { 764 TEST_F(NTPSnippetsServiceTest, DismissShouldRespectAllKnownUrls) {
893 const std::string creation = 765 auto service = MakeSnippetsService();
894 NTPSnippet::TimeToJsonString(GetDefaultCreationTime()); 766
895 const std::string expiry = 767 const base::Time creation = GetDefaultCreationTime();
896 NTPSnippet::TimeToJsonString(GetDefaultExpirationTime()); 768 const base::Time expiry = GetDefaultExpirationTime();
897 const std::vector<std::string> source_urls = { 769 const std::vector<std::string> source_urls = {
898 "http://mashable.com/2016/05/11/stolen", 770 "http://mashable.com/2016/05/11/stolen",
899 "http://www.aol.com/article/2016/05/stolen-doggie", 771 "http://www.aol.com/article/2016/05/stolen-doggie"};
900 "http://mashable.com/2016/05/11/stolen?utm_cid=1"}; 772 const std::vector<std::string> publishers = {"Mashable", "AOL"};
901 const std::vector<std::string> publishers = {"Mashable", "AOL", "Mashable"};
902 const std::vector<std::string> amp_urls = { 773 const std::vector<std::string> amp_urls = {
903 "http://mashable-amphtml.googleusercontent.com/1", 774 "http://mashable-amphtml.googleusercontent.com/1",
904 "http://t2.gstatic.com/images?q=tbn:3",
905 "http://t2.gstatic.com/images?q=tbn:3"}; 775 "http://t2.gstatic.com/images?q=tbn:3"};
906 776
907 // Add the snippet from the mashable domain. 777 // Add the snippet from the mashable domain.
908 LoadFromJSONString(GetTestJson({GetSnippetWithUrlAndTimesAndSources( 778 LoadFromJSONString(service.get(),
909 source_urls[0], creation, expiry, source_urls, publishers, amp_urls)})); 779 GetTestJson({GetSnippetWithUrlAndTimesAndSource(
910 ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); 780 source_urls, source_urls[0], creation, expiry,
781 publishers[0], amp_urls[0])}));
782 ASSERT_THAT(service->GetSnippetsForTesting(), SizeIs(1));
911 // Dismiss the snippet via the mashable source corpus ID. 783 // Dismiss the snippet via the mashable source corpus ID.
912 service()->DismissSuggestion(MakeUniqueID(source_urls[0])); 784 service->DismissSuggestion(MakeUniqueID(*service, source_urls[0]));
913 EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); 785 EXPECT_THAT(service->GetSnippetsForTesting(), IsEmpty());
914 786
915 // The same article from the AOL domain should now be detected as dismissed. 787 // The same article from the AOL domain should now be detected as dismissed.
916 LoadFromJSONString(GetTestJson({GetSnippetWithUrlAndTimesAndSources( 788 LoadFromJSONString(service.get(),
917 source_urls[1], creation, expiry, source_urls, publishers, amp_urls)})); 789 GetTestJson({GetSnippetWithUrlAndTimesAndSource(
918 ASSERT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); 790 source_urls, source_urls[1], creation, expiry,
791 publishers[1], amp_urls[1])}));
792 EXPECT_THAT(service->GetSnippetsForTesting(), IsEmpty());
919 } 793 }
920 794
921 TEST_F(NTPSnippetsServiceTest, StatusChanges) { 795 TEST_F(NTPSnippetsServiceTest, StatusChanges) {
796 {
797 InSequence s;
798 EXPECT_CALL(mock_scheduler(), Schedule(_, _, _, _));
799 EXPECT_CALL(mock_scheduler(), Unschedule());
800 EXPECT_CALL(mock_scheduler(), Schedule(_, _, _, _));
801 }
802 auto service = MakeSnippetsService();
803
922 // Simulate user signed out 804 // Simulate user signed out
923 SetUpFetchResponse(GetTestJson({GetSnippet()})); 805 SetUpFetchResponse(GetTestJson({GetSnippet()}));
924 EXPECT_CALL(observer(), 806 service->OnDisabledReasonChanged(DisabledReason::SIGNED_OUT);
925 OnCategoryStatusChanged(_, _, CategoryStatus::SIGNED_OUT)); 807
926 service()->OnDisabledReasonChanged(DisabledReason::SIGNED_OUT);
927 base::RunLoop().RunUntilIdle(); 808 base::RunLoop().RunUntilIdle();
928 EXPECT_EQ(NTPSnippetsService::State::DISABLED, service()->state_); 809 EXPECT_THAT(observer().StatusForCategory(articles_category()),
929 EXPECT_THAT(service()->GetSnippetsForTesting(), 810 Eq(CategoryStatus::SIGNED_OUT));
811 EXPECT_THAT(NTPSnippetsService::State::DISABLED, Eq(service->state_));
812 EXPECT_THAT(service->GetSnippetsForTesting(),
930 IsEmpty()); // No fetch should be made. 813 IsEmpty()); // No fetch should be made.
931 814
932 // Simulate user sign in. The service should be ready again and load snippets. 815 // Simulate user sign in. The service should be ready again and load snippets.
933 SetUpFetchResponse(GetTestJson({GetSnippet()})); 816 SetUpFetchResponse(GetTestJson({GetSnippet()}));
934 EXPECT_CALL(observer(), 817 service->OnDisabledReasonChanged(DisabledReason::NONE);
935 OnCategoryStatusChanged(_, _, CategoryStatus::AVAILABLE_LOADING)); 818 EXPECT_THAT(observer().StatusForCategory(articles_category()),
936 EXPECT_CALL(mock_scheduler(), Schedule(_, _, _, _)).Times(1); 819 Eq(CategoryStatus::AVAILABLE_LOADING));
937 service()->OnDisabledReasonChanged(DisabledReason::NONE); 820
938 EXPECT_CALL(observer(),
939 OnCategoryStatusChanged(_, _, CategoryStatus::AVAILABLE));
940 base::RunLoop().RunUntilIdle(); 821 base::RunLoop().RunUntilIdle();
941 EXPECT_EQ(NTPSnippetsService::State::READY, service()->state_); 822 EXPECT_THAT(observer().StatusForCategory(articles_category()),
942 EXPECT_FALSE(service()->GetSnippetsForTesting().empty()); 823 Eq(CategoryStatus::AVAILABLE));
824 EXPECT_THAT(NTPSnippetsService::State::READY, Eq(service->state_));
825 EXPECT_FALSE(service->GetSnippetsForTesting().empty());
943 } 826 }
944 827
945 TEST_F(NTPSnippetsServiceTest, ImageReturnedWithTheSameId) { 828 TEST_F(NTPSnippetsServiceTest, ImageReturnedWithTheSameId) {
946 LoadFromJSONString(GetTestJson({GetSnippet()})); 829 auto service = MakeSnippetsService();
830
831 LoadFromJSONString(service.get(), GetTestJson({GetSnippet()}));
947 832
948 gfx::Image image; 833 gfx::Image image;
949 EXPECT_CALL(*image_fetcher(), StartOrQueueNetworkRequest(_, _, _)) 834 MockFunction<void(const gfx::Image&)> image_fetched;
950 .WillOnce(testing::WithArgs<0, 2>(Invoke(ServeOneByOneImage))); 835 {
951 testing::MockFunction<void(const gfx::Image&)> image_fetched; 836 InSequence s;
952 EXPECT_CALL(image_fetched, Call(_)).WillOnce(testing::SaveArg<0>(&image)); 837 EXPECT_CALL(*image_fetcher(), StartOrQueueNetworkRequest(_, _, _))
838 .WillOnce(WithArgs<0, 2>(Invoke(ServeOneByOneImage)));
839 EXPECT_CALL(image_fetched, Call(_)).WillOnce(SaveArg<0>(&image));
840 }
953 841
954 service()->FetchSuggestionImage( 842 service->FetchSuggestionImage(
955 MakeUniqueID(kSnippetUrl), 843 MakeUniqueID(*service, kSnippetUrl),
956 base::Bind(&testing::MockFunction<void(const gfx::Image&)>::Call, 844 base::Bind(&MockFunction<void(const gfx::Image&)>::Call,
957 base::Unretained(&image_fetched))); 845 base::Unretained(&image_fetched)));
958 base::RunLoop().RunUntilIdle(); 846 base::RunLoop().RunUntilIdle();
959 // Check that the image by ServeOneByOneImage is really served. 847 // Check that the image by ServeOneByOneImage is really served.
960 EXPECT_EQ(1, image.Width()); 848 EXPECT_EQ(1, image.Width());
961 } 849 }
962 850
963 TEST_F(NTPSnippetsServiceTest, EmptyImageReturnedForNonExistentId) { 851 TEST_F(NTPSnippetsServiceTest, EmptyImageReturnedForNonExistentId) {
852 auto service = MakeSnippetsService();
853
964 // Create a non-empty image so that we can test the image gets updated. 854 // Create a non-empty image so that we can test the image gets updated.
965 gfx::Image image = gfx::test::CreateImage(1, 1); 855 gfx::Image image = gfx::test::CreateImage(1, 1);
966 testing::MockFunction<void(const gfx::Image&)> image_fetched; 856 MockFunction<void(const gfx::Image&)> image_fetched;
967 EXPECT_CALL(image_fetched, Call(_)).WillOnce(testing::SaveArg<0>(&image)); 857 EXPECT_CALL(image_fetched, Call(_)).WillOnce(SaveArg<0>(&image));
968 858
969 service()->FetchSuggestionImage( 859 service->FetchSuggestionImage(
970 MakeUniqueID(kSnippetUrl2), 860 MakeUniqueID(*service, kSnippetUrl2),
971 base::Bind(&testing::MockFunction<void(const gfx::Image&)>::Call, 861 base::Bind(&MockFunction<void(const gfx::Image&)>::Call,
972 base::Unretained(&image_fetched))); 862 base::Unretained(&image_fetched)));
973 863
974 base::RunLoop().RunUntilIdle(); 864 base::RunLoop().RunUntilIdle();
975 EXPECT_TRUE(image.IsEmpty()); 865 EXPECT_TRUE(image.IsEmpty());
976 } 866 }
977 867
978 } // namespace ntp_snippets 868 } // namespace ntp_snippets
OLDNEW
« no previous file with comments | « components/ntp_snippets/ntp_snippet_unittest.cc ('k') | components/ntp_snippets/ntp_snippets_status_service_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698