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