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