OLD | NEW |
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/remote/ntp_snippets_service.h" | 5 #include "components/ntp_snippets/remote/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 |
(...skipping 207 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
218 // fail to parse as snippets. | 218 // fail to parse as snippets. |
219 size_t pos = json_str.find("\"fullPageUrl\""); | 219 size_t pos = json_str.find("\"fullPageUrl\""); |
220 if (pos == std::string::npos) { | 220 if (pos == std::string::npos) { |
221 NOTREACHED(); | 221 NOTREACHED(); |
222 return std::string(); | 222 return std::string(); |
223 } | 223 } |
224 json_str[pos + 1] = 'x'; | 224 json_str[pos + 1] = 'x'; |
225 return json_str; | 225 return json_str; |
226 } | 226 } |
227 | 227 |
| 228 using ServeImageCallback = base::Callback<void( |
| 229 const std::string&, |
| 230 base::Callback<void(const std::string&, const gfx::Image&)>)>; |
| 231 |
228 void ServeOneByOneImage( | 232 void ServeOneByOneImage( |
| 233 image_fetcher::ImageFetcherDelegate* notify, |
229 const std::string& id, | 234 const std::string& id, |
230 base::Callback<void(const std::string&, const gfx::Image&)> callback) { | 235 base::Callback<void(const std::string&, const gfx::Image&)> callback) { |
231 base::ThreadTaskRunnerHandle::Get()->PostTask( | 236 base::ThreadTaskRunnerHandle::Get()->PostTask( |
232 FROM_HERE, base::Bind(callback, id, gfx::test::CreateImage(1, 1))); | 237 FROM_HERE, base::Bind(callback, id, gfx::test::CreateImage(1, 1))); |
| 238 notify->OnImageDataFetched(id, "1-by-1-image-data"); |
| 239 } |
| 240 |
| 241 void ServeEmptyImage( |
| 242 const std::string& id, |
| 243 base::Callback<void(const std::string&, const gfx::Image&)> callback) { |
| 244 base::ThreadTaskRunnerHandle::Get()->PostTask( |
| 245 FROM_HERE, base::Bind(callback, id, gfx::Image())); |
233 } | 246 } |
234 | 247 |
235 void ParseJson( | 248 void ParseJson( |
236 const std::string& json, | 249 const std::string& json, |
237 const ntp_snippets::NTPSnippetsFetcher::SuccessCallback& success_callback, | 250 const ntp_snippets::NTPSnippetsFetcher::SuccessCallback& success_callback, |
238 const ntp_snippets::NTPSnippetsFetcher::ErrorCallback& error_callback) { | 251 const ntp_snippets::NTPSnippetsFetcher::ErrorCallback& error_callback) { |
239 base::JSONReader json_reader; | 252 base::JSONReader json_reader; |
240 std::unique_ptr<base::Value> value = json_reader.ReadToValue(json); | 253 std::unique_ptr<base::Value> value = json_reader.ReadToValue(json); |
241 if (value) { | 254 if (value) { |
242 success_callback.Run(std::move(value)); | 255 success_callback.Run(std::move(value)); |
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
331 | 344 |
332 private: | 345 private: |
333 base::WaitableEvent loaded_; | 346 base::WaitableEvent loaded_; |
334 std::map<Category, CategoryStatus, Category::CompareByID> statuses_; | 347 std::map<Category, CategoryStatus, Category::CompareByID> statuses_; |
335 std::map<Category, std::vector<ContentSuggestion>, Category::CompareByID> | 348 std::map<Category, std::vector<ContentSuggestion>, Category::CompareByID> |
336 suggestions_; | 349 suggestions_; |
337 | 350 |
338 DISALLOW_COPY_AND_ASSIGN(FakeContentSuggestionsProviderObserver); | 351 DISALLOW_COPY_AND_ASSIGN(FakeContentSuggestionsProviderObserver); |
339 }; | 352 }; |
340 | 353 |
| 354 class FakeImageDecoder : public image_fetcher::ImageDecoder { |
| 355 public: |
| 356 FakeImageDecoder() {} |
| 357 ~FakeImageDecoder() override {} |
| 358 void DecodeImage( |
| 359 const std::string& image_data, |
| 360 const image_fetcher::ImageDecodedCallback& callback) override { |
| 361 callback.Run(decoded_image_); |
| 362 } |
| 363 |
| 364 void SetDecodedImage(const gfx::Image& image) { decoded_image_ = image; } |
| 365 |
| 366 private: |
| 367 gfx::Image decoded_image_; |
| 368 }; |
| 369 |
341 } // namespace | 370 } // namespace |
342 | 371 |
343 class NTPSnippetsServiceTest : public ::testing::Test { | 372 class NTPSnippetsServiceTest : public ::testing::Test { |
344 public: | 373 public: |
345 NTPSnippetsServiceTest() | 374 NTPSnippetsServiceTest() |
346 : params_manager_(ntp_snippets::kStudyName, | 375 : params_manager_(ntp_snippets::kStudyName, |
347 {{"content_suggestions_backend", | 376 {{"content_suggestions_backend", |
348 kTestContentSuggestionsServerEndpoint}}), | 377 kTestContentSuggestionsServerEndpoint}}), |
349 fake_url_fetcher_factory_( | 378 fake_url_fetcher_factory_( |
350 /*default_factory=*/&failing_url_fetcher_factory_), | 379 /*default_factory=*/&failing_url_fetcher_factory_), |
351 test_url_(kTestContentSuggestionsServerWithAPIKey), | 380 test_url_(kTestContentSuggestionsServerWithAPIKey), |
352 image_fetcher_(nullptr) { | 381 image_fetcher_(nullptr), |
| 382 image_decoder_(nullptr) { |
353 NTPSnippetsService::RegisterProfilePrefs(utils_.pref_service()->registry()); | 383 NTPSnippetsService::RegisterProfilePrefs(utils_.pref_service()->registry()); |
354 RequestThrottler::RegisterProfilePrefs(utils_.pref_service()->registry()); | 384 RequestThrottler::RegisterProfilePrefs(utils_.pref_service()->registry()); |
355 | 385 |
356 // Since no SuggestionsService is injected in tests, we need to force the | 386 // Since no SuggestionsService is injected in tests, we need to force the |
357 // service to fetch from all hosts. | 387 // service to fetch from all hosts. |
358 base::CommandLine::ForCurrentProcess()->AppendSwitch( | 388 base::CommandLine::ForCurrentProcess()->AppendSwitch( |
359 switches::kDontRestrict); | 389 switches::kDontRestrict); |
360 EXPECT_TRUE(database_dir_.CreateUniqueTempDir()); | 390 EXPECT_TRUE(database_dir_.CreateUniqueTempDir()); |
361 } | 391 } |
362 | 392 |
(...skipping 23 matching lines...) Expand all Loading... |
386 base::MakeUnique<NTPSnippetsFetcher>( | 416 base::MakeUnique<NTPSnippetsFetcher>( |
387 utils_.fake_signin_manager(), fake_token_service_.get(), | 417 utils_.fake_signin_manager(), fake_token_service_.get(), |
388 std::move(request_context_getter), utils_.pref_service(), | 418 std::move(request_context_getter), utils_.pref_service(), |
389 &category_factory_, base::Bind(&ParseJson), kAPIKey); | 419 &category_factory_, base::Bind(&ParseJson), kAPIKey); |
390 | 420 |
391 utils_.fake_signin_manager()->SignIn("foo@bar.com"); | 421 utils_.fake_signin_manager()->SignIn("foo@bar.com"); |
392 snippets_fetcher->SetPersonalizationForTesting( | 422 snippets_fetcher->SetPersonalizationForTesting( |
393 NTPSnippetsFetcher::Personalization::kNonPersonal); | 423 NTPSnippetsFetcher::Personalization::kNonPersonal); |
394 | 424 |
395 auto image_fetcher = base::MakeUnique<NiceMock<MockImageFetcher>>(); | 425 auto image_fetcher = base::MakeUnique<NiceMock<MockImageFetcher>>(); |
| 426 |
396 image_fetcher_ = image_fetcher.get(); | 427 image_fetcher_ = image_fetcher.get(); |
| 428 EXPECT_CALL(*image_fetcher, SetImageFetcherDelegate(_)); |
| 429 auto image_decoder = base::MakeUnique<FakeImageDecoder>(); |
| 430 image_decoder_ = image_decoder.get(); |
397 EXPECT_FALSE(observer_); | 431 EXPECT_FALSE(observer_); |
398 observer_ = base::MakeUnique<FakeContentSuggestionsProviderObserver>(); | 432 observer_ = base::MakeUnique<FakeContentSuggestionsProviderObserver>(); |
399 return base::MakeUnique<NTPSnippetsService>( | 433 return base::MakeUnique<NTPSnippetsService>( |
400 observer_.get(), &category_factory_, utils_.pref_service(), nullptr, | 434 observer_.get(), &category_factory_, utils_.pref_service(), nullptr, |
401 "fr", &scheduler_, std::move(snippets_fetcher), | 435 "fr", &scheduler_, std::move(snippets_fetcher), |
402 std::move(image_fetcher), /*image_decoder=*/nullptr, | 436 std::move(image_fetcher), std::move(image_decoder), |
403 base::MakeUnique<NTPSnippetsDatabase>(database_dir_.GetPath(), | 437 base::MakeUnique<NTPSnippetsDatabase>(database_dir_.GetPath(), |
404 task_runner), | 438 task_runner), |
405 base::MakeUnique<NTPSnippetsStatusService>(utils_.fake_signin_manager(), | 439 base::MakeUnique<NTPSnippetsStatusService>(utils_.fake_signin_manager(), |
406 utils_.pref_service())); | 440 utils_.pref_service())); |
407 } | 441 } |
408 | 442 |
409 void WaitForSnippetsServiceInitialization(bool set_empty_response = true) { | 443 void WaitForSnippetsServiceInitialization(bool set_empty_response = true) { |
410 EXPECT_TRUE(observer_); | 444 EXPECT_TRUE(observer_); |
411 EXPECT_FALSE(observer_->Loaded()); | 445 EXPECT_FALSE(observer_->Loaded()); |
412 | 446 |
(...skipping 24 matching lines...) Expand all Loading... |
437 return ContentSuggestion::ID(other_category(), id_within_category); | 471 return ContentSuggestion::ID(other_category(), id_within_category); |
438 } | 472 } |
439 | 473 |
440 Category other_category() { return category_factory_.FromRemoteCategory(2); } | 474 Category other_category() { return category_factory_.FromRemoteCategory(2); } |
441 | 475 |
442 protected: | 476 protected: |
443 const GURL& test_url() { return test_url_; } | 477 const GURL& test_url() { return test_url_; } |
444 FakeContentSuggestionsProviderObserver& observer() { return *observer_; } | 478 FakeContentSuggestionsProviderObserver& observer() { return *observer_; } |
445 MockScheduler& mock_scheduler() { return scheduler_; } | 479 MockScheduler& mock_scheduler() { return scheduler_; } |
446 NiceMock<MockImageFetcher>* image_fetcher() { return image_fetcher_; } | 480 NiceMock<MockImageFetcher>* image_fetcher() { return image_fetcher_; } |
| 481 FakeImageDecoder* image_decoder() { return image_decoder_; } |
447 | 482 |
448 // Provide the json to be returned by the fake fetcher. | 483 // Provide the json to be returned by the fake fetcher. |
449 void SetUpFetchResponse(const std::string& json) { | 484 void SetUpFetchResponse(const std::string& json) { |
450 fake_url_fetcher_factory_.SetFakeResponse(test_url_, json, net::HTTP_OK, | 485 fake_url_fetcher_factory_.SetFakeResponse(test_url_, json, net::HTTP_OK, |
451 net::URLRequestStatus::SUCCESS); | 486 net::URLRequestStatus::SUCCESS); |
452 } | 487 } |
453 | 488 |
454 void LoadFromJSONString(NTPSnippetsService* service, | 489 void LoadFromJSONString(NTPSnippetsService* service, |
455 const std::string& json) { | 490 const std::string& json) { |
456 SetUpFetchResponse(json); | 491 SetUpFetchResponse(json); |
457 service->FetchSnippets(true); | 492 service->FetchSnippets(true); |
458 base::RunLoop().RunUntilIdle(); | 493 base::RunLoop().RunUntilIdle(); |
459 } | 494 } |
460 | 495 |
461 private: | 496 private: |
462 variations::testing::VariationParamsManager params_manager_; | 497 variations::testing::VariationParamsManager params_manager_; |
463 test::NTPSnippetsTestUtils utils_; | 498 test::NTPSnippetsTestUtils utils_; |
464 base::MessageLoop message_loop_; | 499 base::MessageLoop message_loop_; |
465 FailingFakeURLFetcherFactory failing_url_fetcher_factory_; | 500 FailingFakeURLFetcherFactory failing_url_fetcher_factory_; |
466 // Instantiation of factory automatically sets itself as URLFetcher's factory. | 501 // Instantiation of factory automatically sets itself as URLFetcher's factory. |
467 net::FakeURLFetcherFactory fake_url_fetcher_factory_; | 502 net::FakeURLFetcherFactory fake_url_fetcher_factory_; |
468 const GURL test_url_; | 503 const GURL test_url_; |
469 std::unique_ptr<OAuth2TokenService> fake_token_service_; | 504 std::unique_ptr<OAuth2TokenService> fake_token_service_; |
470 NiceMock<MockScheduler> scheduler_; | 505 NiceMock<MockScheduler> scheduler_; |
471 std::unique_ptr<FakeContentSuggestionsProviderObserver> observer_; | 506 std::unique_ptr<FakeContentSuggestionsProviderObserver> observer_; |
472 CategoryFactory category_factory_; | 507 CategoryFactory category_factory_; |
473 NiceMock<MockImageFetcher>* image_fetcher_; | 508 NiceMock<MockImageFetcher>* image_fetcher_; |
| 509 FakeImageDecoder* image_decoder_; |
474 | 510 |
475 base::ScopedTempDir database_dir_; | 511 base::ScopedTempDir database_dir_; |
476 | 512 |
477 DISALLOW_COPY_AND_ASSIGN(NTPSnippetsServiceTest); | 513 DISALLOW_COPY_AND_ASSIGN(NTPSnippetsServiceTest); |
478 }; | 514 }; |
479 | 515 |
480 TEST_F(NTPSnippetsServiceTest, ScheduleOnStart) { | 516 TEST_F(NTPSnippetsServiceTest, ScheduleOnStart) { |
481 // We should get two |Schedule| calls: The first when initialization | 517 // We should get two |Schedule| calls: The first when initialization |
482 // completes, the second one after the automatic (since the service doesn't | 518 // completes, the second one after the automatic (since the service doesn't |
483 // have any data yet) fetch finishes. | 519 // have any data yet) fetch finishes. |
(...skipping 527 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1011 EXPECT_FALSE(service->GetSnippetsForTesting(articles_category()).empty()); | 1047 EXPECT_FALSE(service->GetSnippetsForTesting(articles_category()).empty()); |
1012 } | 1048 } |
1013 | 1049 |
1014 TEST_F(NTPSnippetsServiceTest, ImageReturnedWithTheSameId) { | 1050 TEST_F(NTPSnippetsServiceTest, ImageReturnedWithTheSameId) { |
1015 auto service = MakeSnippetsService(); | 1051 auto service = MakeSnippetsService(); |
1016 | 1052 |
1017 LoadFromJSONString(service.get(), GetTestJson({GetSnippet()})); | 1053 LoadFromJSONString(service.get(), GetTestJson({GetSnippet()})); |
1018 | 1054 |
1019 gfx::Image image; | 1055 gfx::Image image; |
1020 MockFunction<void(const gfx::Image&)> image_fetched; | 1056 MockFunction<void(const gfx::Image&)> image_fetched; |
| 1057 ServeImageCallback cb = base::Bind(&ServeOneByOneImage, service.get()); |
1021 { | 1058 { |
1022 InSequence s; | 1059 InSequence s; |
1023 EXPECT_CALL(*image_fetcher(), StartOrQueueNetworkRequest(_, _, _)) | 1060 EXPECT_CALL(*image_fetcher(), StartOrQueueNetworkRequest(_, _, _)) |
1024 .WillOnce(WithArgs<0, 2>(Invoke(ServeOneByOneImage))); | 1061 .WillOnce(WithArgs<0, 2>(Invoke(&cb, &ServeImageCallback::Run))); |
1025 EXPECT_CALL(image_fetched, Call(_)).WillOnce(SaveArg<0>(&image)); | 1062 EXPECT_CALL(image_fetched, Call(_)).WillOnce(SaveArg<0>(&image)); |
1026 } | 1063 } |
1027 | 1064 |
1028 service->FetchSuggestionImage( | 1065 service->FetchSuggestionImage( |
1029 MakeArticleID(kSnippetUrl), | 1066 MakeArticleID(kSnippetUrl), |
1030 base::Bind(&MockFunction<void(const gfx::Image&)>::Call, | 1067 base::Bind(&MockFunction<void(const gfx::Image&)>::Call, |
1031 base::Unretained(&image_fetched))); | 1068 base::Unretained(&image_fetched))); |
1032 base::RunLoop().RunUntilIdle(); | 1069 base::RunLoop().RunUntilIdle(); |
1033 // Check that the image by ServeOneByOneImage is really served. | 1070 // Check that the image by ServeOneByOneImage is really served. |
1034 EXPECT_EQ(1, image.Width()); | 1071 EXPECT_EQ(1, image.Width()); |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1068 base::Time begin = base::Time::FromTimeT(123), | 1105 base::Time begin = base::Time::FromTimeT(123), |
1069 end = base::Time::FromTimeT(456); | 1106 end = base::Time::FromTimeT(456); |
1070 base::Callback<bool(const GURL& url)> filter; | 1107 base::Callback<bool(const GURL& url)> filter; |
1071 service->ClearHistory(begin, end, filter); | 1108 service->ClearHistory(begin, end, filter); |
1072 | 1109 |
1073 EXPECT_THAT(service->GetSnippetsForTesting(articles_category()), IsEmpty()); | 1110 EXPECT_THAT(service->GetSnippetsForTesting(articles_category()), IsEmpty()); |
1074 EXPECT_THAT(service->GetDismissedSnippetsForTesting(articles_category()), | 1111 EXPECT_THAT(service->GetDismissedSnippetsForTesting(articles_category()), |
1075 IsEmpty()); | 1112 IsEmpty()); |
1076 } | 1113 } |
1077 | 1114 |
| 1115 namespace { |
| 1116 |
| 1117 gfx::Image FetchImage(NTPSnippetsService* service, |
| 1118 const ContentSuggestion::ID& suggestion_id) { |
| 1119 gfx::Image result; |
| 1120 base::RunLoop run_loop; |
| 1121 service->FetchSuggestionImage(suggestion_id, |
| 1122 base::Bind( |
| 1123 [](base::Closure signal, gfx::Image* output, |
| 1124 const gfx::Image& loaded) { |
| 1125 *output = loaded; |
| 1126 signal.Run(); |
| 1127 LOG(INFO) << "signalling loop out"; |
| 1128 }, |
| 1129 run_loop.QuitClosure(), &result)); |
| 1130 run_loop.Run(); |
| 1131 return result; |
| 1132 } |
| 1133 |
| 1134 } // namespace |
| 1135 |
| 1136 TEST_F(NTPSnippetsServiceTest, ShouldClearOrphanedImages) { |
| 1137 auto service = MakeSnippetsService(); |
| 1138 |
| 1139 LoadFromJSONString(service.get(), GetTestJson({GetSnippet()})); |
| 1140 ServeImageCallback cb = base::Bind(&ServeOneByOneImage, service.get()); |
| 1141 |
| 1142 EXPECT_CALL(*image_fetcher(), StartOrQueueNetworkRequest(_, _, _)) |
| 1143 .WillOnce(WithArgs<0, 2>(Invoke(&cb, &ServeImageCallback::Run))); |
| 1144 image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1)); |
| 1145 |
| 1146 gfx::Image image = FetchImage(service.get(), MakeArticleID(kSnippetUrl)); |
| 1147 EXPECT_EQ(1, image.Width()); |
| 1148 EXPECT_FALSE(image.IsEmpty()); |
| 1149 |
| 1150 // Send new suggestion which don't include the snippet referencing the image. |
| 1151 LoadFromJSONString(service.get(), |
| 1152 GetTestJson({GetSnippetWithUrl( |
| 1153 "http://something.com/pletely/unrelated")})); |
| 1154 // The image should still be available until a restart happens. |
| 1155 EXPECT_FALSE(FetchImage(service.get(), MakeArticleID(kSnippetUrl)).IsEmpty()); |
| 1156 ResetSnippetsService(&service); |
| 1157 // After the restart, the image should be garbage collected. |
| 1158 EXPECT_TRUE(FetchImage(service.get(), MakeArticleID(kSnippetUrl)).IsEmpty()); |
| 1159 } |
| 1160 |
1078 } // namespace ntp_snippets | 1161 } // namespace ntp_snippets |
OLD | NEW |