| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/ntp_snippets/remote/cached_image_fetcher.h" |
| |
| #include <memory> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/files/file_path.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/test/mock_callback.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "components/image_fetcher/core/image_decoder.h" |
| #include "components/image_fetcher/core/image_fetcher.h" |
| #include "components/image_fetcher/core/image_fetcher_impl.h" |
| #include "components/ntp_snippets/remote/proto/ntp_snippets.pb.h" |
| #include "components/ntp_snippets/remote/remote_suggestions_database.h" |
| #include "components/prefs/pref_registry_simple.h" |
| #include "components/prefs/testing_pref_service.h" |
| #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" |
| #include "services/network/test/test_url_loader_factory.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/gfx/image/image.h" |
| #include "ui/gfx/image/image_unittest_util.h" |
| |
| using testing::_; |
| using testing::Eq; |
| using testing::Property; |
| |
| namespace ntp_snippets { |
| |
| namespace { |
| |
| const char kImageData[] = "data"; |
| const char kImageURL[] = "http://image.test/test.png"; |
| const char kSnippetID[] = "http://localhost"; |
| const ContentSuggestion::ID kSuggestionID( |
| Category::FromKnownCategory(KnownCategories::ARTICLES), |
| kSnippetID); |
| |
| // Always decodes a valid image for all non-empty input. |
| class FakeImageDecoder : public image_fetcher::ImageDecoder { |
| public: |
| void DecodeImage( |
| const std::string& image_data, |
| const gfx::Size& desired_image_frame_size, |
| const image_fetcher::ImageDecodedCallback& callback) override { |
| ASSERT_TRUE(enabled_); |
| gfx::Image image; |
| if (!image_data.empty()) { |
| ASSERT_EQ(kImageData, image_data); |
| image = gfx::test::CreateImage(); |
| } |
| base::SequencedTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindRepeating(callback, image)); |
| } |
| void SetEnabled(bool enabled) { enabled_ = enabled; } |
| |
| private: |
| bool enabled_ = true; |
| }; |
| |
| enum class TestType { |
| kImageCallback, |
| kImageDataCallback, |
| kBothCallbacks, |
| }; |
| } // namespace |
| |
| // This test is parameterized to run all tests in the three configurations: |
| // both callbacks used, only image_callback used, only image_data_callback used. |
| class CachedImageFetcherTest : public testing::TestWithParam<TestType> { |
| public: |
| CachedImageFetcherTest() { |
| EXPECT_TRUE(database_dir_.CreateUniqueTempDir()); |
| |
| RequestThrottler::RegisterProfilePrefs(pref_service_.registry()); |
| database_ = |
| std::make_unique<RemoteSuggestionsDatabase>(database_dir_.GetPath()); |
| |
| shared_factory_ = |
| base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( |
| &test_url_loader_factory_); |
| |
| auto decoder = std::make_unique<FakeImageDecoder>(); |
| fake_image_decoder_ = decoder.get(); |
| cached_image_fetcher_ = std::make_unique<ntp_snippets::CachedImageFetcher>( |
| std::make_unique<image_fetcher::ImageFetcherImpl>(std::move(decoder), |
| shared_factory_), |
| &pref_service_, database_.get()); |
| RunUntilIdle(); |
| EXPECT_TRUE(database_->IsInitialized()); |
| } |
| |
| ~CachedImageFetcherTest() override { |
| cached_image_fetcher_.reset(); |
| database_.reset(); |
| // We need to run until idle after deleting the database, because |
| // ProtoDatabaseImpl deletes the actual LevelDB asynchronously. |
| RunUntilIdle(); |
| } |
| |
| void Fetch(std::string expected_image_data, bool expect_image) { |
| fake_image_decoder()->SetEnabled(GetParam() != |
| TestType::kImageDataCallback); |
| base::MockCallback<ImageFetchedCallback> image_callback; |
| base::MockCallback<ImageDataFetchedCallback> image_data_callback; |
| switch (GetParam()) { |
| case TestType::kImageCallback: { |
| EXPECT_CALL(image_callback, |
| Run(Property(&gfx::Image::IsEmpty, Eq(!expect_image)))); |
| cached_image_fetcher()->FetchSuggestionImage( |
| kSuggestionID, GURL(kImageURL), ImageDataFetchedCallback(), |
| image_callback.Get()); |
| |
| } break; |
| case TestType::kImageDataCallback: { |
| EXPECT_CALL(image_data_callback, Run(expected_image_data)); |
| cached_image_fetcher()->FetchSuggestionImage( |
| kSuggestionID, GURL(kImageURL), image_data_callback.Get(), |
| ImageFetchedCallback()); |
| } break; |
| case TestType::kBothCallbacks: { |
| EXPECT_CALL(image_data_callback, Run(expected_image_data)); |
| EXPECT_CALL(image_callback, |
| Run(Property(&gfx::Image::IsEmpty, Eq(!expect_image)))); |
| cached_image_fetcher()->FetchSuggestionImage( |
| kSuggestionID, GURL(kImageURL), image_data_callback.Get(), |
| image_callback.Get()); |
| } break; |
| } |
| RunUntilIdle(); |
| } |
| |
| void RunUntilIdle() { scoped_task_environment_.RunUntilIdle(); } |
| |
| RemoteSuggestionsDatabase* database() { return database_.get(); } |
| FakeImageDecoder* fake_image_decoder() { return fake_image_decoder_; } |
| network::TestURLLoaderFactory* test_url_loader_factory() { |
| return &test_url_loader_factory_; |
| } |
| CachedImageFetcher* cached_image_fetcher() { |
| return cached_image_fetcher_.get(); |
| } |
| |
| private: |
| network::TestURLLoaderFactory test_url_loader_factory_; |
| scoped_refptr<network::SharedURLLoaderFactory> shared_factory_; |
| std::unique_ptr<CachedImageFetcher> cached_image_fetcher_; |
| std::unique_ptr<RemoteSuggestionsDatabase> database_; |
| base::ScopedTempDir database_dir_; |
| FakeImageDecoder* fake_image_decoder_; |
| |
| TestingPrefServiceSimple pref_service_; |
| base::test::ScopedTaskEnvironment scoped_task_environment_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CachedImageFetcherTest); |
| }; |
| |
| TEST_P(CachedImageFetcherTest, FetchImageFromCache) { |
| // Save the image in the database. |
| database()->SaveImage(kSnippetID, kImageData); |
| RunUntilIdle(); |
| |
| // Do not provide any URL responses and expect that the image is fetched (from |
| // cache). |
| Fetch(kImageData, true); |
| } |
| |
| TEST_P(CachedImageFetcherTest, FetchImagePopulatesCache) { |
| // Expect the image to be fetched by URL. |
| { |
| test_url_loader_factory()->AddResponse(kImageURL, kImageData); |
| Fetch(kImageData, true); |
| } |
| // Fetch again. The cache should be populated, no network request is needed. |
| { |
| test_url_loader_factory()->ClearResponses(); |
| Fetch(kImageData, true); |
| } |
| } |
| |
| TEST_P(CachedImageFetcherTest, FetchNonExistingImage) { |
| const std::string kErrorResponse = "error-response"; |
| test_url_loader_factory()->AddResponse(kImageURL, kErrorResponse, |
| net::HTTP_NOT_FOUND); |
| // Expect an empty image is fetched if the URL cannot be requested. |
| Fetch("", false); |
| } |
| |
| INSTANTIATE_TEST_CASE_P(, |
| CachedImageFetcherTest, |
| testing::Values(TestType::kImageCallback, |
| TestType::kImageDataCallback, |
| TestType::kBothCallbacks)); |
| |
| } // namespace ntp_snippets |