| 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 |