| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "extensions/browser/updater/extension_downloader.h" |
| |
| #include "base/containers/contains.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/test/bind.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "content/public/test/test_utils.h" |
| #include "extensions/browser/extensions_test.h" |
| #include "extensions/browser/updater/extension_cache_fake.h" |
| #include "extensions/browser/updater/extension_downloader_test_helper.h" |
| #include "extensions/browser/updater/extension_downloader_types.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/extension_urls.h" |
| #include "services/network/test/test_utils.h" |
| |
| using testing::_; |
| using testing::AnyNumber; |
| using testing::DoAll; |
| using testing::Return; |
| using testing::Sequence; |
| using testing::SetArgPointee; |
| |
| namespace extensions { |
| |
| namespace { |
| |
| const char kTestExtensionId[] = "test_app"; |
| const char kTestExtensionId2[] = "test_app2"; |
| |
| } // namespace |
| |
| class ExtensionDownloaderTest : public ExtensionsTest { |
| protected: |
| using URLStats = ExtensionDownloader::URLStats; |
| |
| ExtensionDownloaderTest() = default; |
| |
| std::unique_ptr<ManifestFetchData> CreateManifestFetchData( |
| const GURL& update_url, |
| DownloadFetchPriority fetch_priority = |
| DownloadFetchPriority::kBackground) { |
| return std::make_unique<ManifestFetchData>( |
| update_url, 0, "", "", ManifestFetchData::PING, fetch_priority); |
| } |
| |
| std::unique_ptr<ManifestFetchData> CreateTestAppFetchData() { |
| GURL kUpdateUrl("http://localhost/manifest1"); |
| std::unique_ptr<ManifestFetchData> fetch( |
| CreateManifestFetchData(kUpdateUrl)); |
| DownloadPingData zero_days(/*rollcall=*/0, /*active=*/0, /*enabled=*/true, |
| /*disable_reasons=*/{}); |
| fetch->AddExtension(kTestExtensionId, "1.0", &zero_days, "", "", |
| mojom::ManifestLocation::kInternal, |
| DownloadFetchPriority::kBackground); |
| fetch->AddAssociatedTask( |
| CreateDownloaderTask(kTestExtensionId, kUpdateUrl)); |
| return fetch; |
| } |
| |
| void AddFetchDataToDownloader(ExtensionDownloaderTestHelper* helper, |
| std::unique_ptr<ManifestFetchData> fetch) { |
| helper->StartUpdateCheck(std::move(fetch)); |
| } |
| |
| const std::vector<ExtensionDownloaderTask>& GetDownloaderPendingTasks( |
| ExtensionDownloaderTestHelper* helper) { |
| return helper->downloader().pending_tasks_; |
| } |
| |
| // Creates an update manifest for several extensions. Provided values should |
| // be tuples of (extension id, version, URL to the CRX file). |
| std::string CreateUpdateManifest( |
| const std::vector<std::tuple<ExtensionId, std::string, std::string>>& |
| extensions) { |
| std::vector<UpdateManifestItem> extensions_raw; |
| for (const auto& [id, version, codebase] : extensions) { |
| extensions_raw.emplace_back(UpdateManifestItem(id) |
| .codebase(codebase) |
| .version(version) |
| .prodversionmin("1.1")); |
| } |
| return extensions::CreateUpdateManifest(extensions_raw); |
| } |
| |
| // Create an update manifest with one test extension. |
| std::string CreateDefaultUpdateManifest() { |
| return CreateUpdateManifest({std::make_tuple( |
| kTestExtensionId, "1.1", "http://example.com/extension_1.2.3.4.crx")}); |
| } |
| |
| void SetHelperHandlers(ExtensionDownloaderTestHelper* helper, |
| const GURL& fetch_url, |
| const std::string& manifest) { |
| helper->test_url_loader_factory().AddResponse(fetch_url.spec(), manifest, |
| net::HTTP_OK); |
| GURL kCrxUrl("http://example.com/extension_1.2.3.4.crx"); |
| helper->test_url_loader_factory().AddResponse(kCrxUrl.spec(), "no data", |
| net::HTTP_OK); |
| } |
| }; |
| |
| // Several tests checking that OnExtensionDownloadStageChanged callback is |
| // called correctly. |
| TEST_F(ExtensionDownloaderTest, TestStageChanges) { |
| ExtensionDownloaderTestHelper helper; |
| |
| std::unique_ptr<ManifestFetchData> fetch(CreateTestAppFetchData()); |
| const std::string manifest = CreateDefaultUpdateManifest(); |
| SetHelperHandlers(&helper, fetch->full_url(), manifest); |
| |
| MockExtensionDownloaderDelegate& delegate = helper.delegate(); |
| EXPECT_CALL(delegate, IsExtensionPending(kTestExtensionId)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(delegate, |
| OnExtensionDownloadCacheStatusRetrieved( |
| kTestExtensionId, |
| ExtensionDownloaderDelegate::CacheStatus::CACHE_DISABLED)); |
| Sequence sequence; |
| EXPECT_CALL(delegate, |
| OnExtensionDownloadStageChanged( |
| kTestExtensionId, |
| ExtensionDownloaderDelegate::Stage::QUEUED_FOR_MANIFEST)) |
| .Times(AnyNumber()); |
| EXPECT_CALL(delegate, OnExtensionDownloadStageChanged( |
| kTestExtensionId, |
| ExtensionDownloaderDelegate::Stage::QUEUED_FOR_CRX)) |
| .Times(AnyNumber()); |
| EXPECT_CALL(delegate, |
| OnExtensionDownloadStageChanged( |
| kTestExtensionId, |
| ExtensionDownloaderDelegate::Stage::DOWNLOADING_MANIFEST)) |
| .InSequence(sequence); |
| EXPECT_CALL(delegate, |
| OnExtensionDownloadStageChanged( |
| kTestExtensionId, |
| ExtensionDownloaderDelegate::Stage::PARSING_MANIFEST)) |
| .InSequence(sequence); |
| EXPECT_CALL(delegate, |
| OnExtensionDownloadStageChanged( |
| kTestExtensionId, |
| ExtensionDownloaderDelegate::Stage::MANIFEST_LOADED)) |
| .InSequence(sequence); |
| EXPECT_CALL(delegate, |
| OnExtensionDownloadStageChanged( |
| kTestExtensionId, |
| ExtensionDownloaderDelegate::Stage::DOWNLOADING_CRX)) |
| .InSequence(sequence); |
| EXPECT_CALL(delegate, OnExtensionDownloadStageChanged( |
| kTestExtensionId, |
| ExtensionDownloaderDelegate::Stage::FINISHED)) |
| .InSequence(sequence); |
| |
| AddFetchDataToDownloader(&helper, std::move(fetch)); |
| |
| content::RunAllTasksUntilIdle(); |
| |
| testing::Mock::VerifyAndClearExpectations(&delegate); |
| } |
| |
| TEST_F(ExtensionDownloaderTest, TestStageChangesNoUpdates) { |
| ExtensionDownloaderTestHelper helper; |
| |
| std::unique_ptr<ManifestFetchData> fetch(CreateTestAppFetchData()); |
| const std::string manifest = CreateDefaultUpdateManifest(); |
| SetHelperHandlers(&helper, fetch->full_url(), manifest); |
| |
| MockExtensionDownloaderDelegate& delegate = helper.delegate(); |
| EXPECT_CALL(delegate, IsExtensionPending(kTestExtensionId)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(delegate, GetExtensionExistingVersion(kTestExtensionId, _)) |
| .WillOnce(DoAll(SetArgPointee<1>("1.1"), Return(true))); |
| Sequence sequence; |
| EXPECT_CALL(delegate, |
| OnExtensionDownloadStageChanged( |
| kTestExtensionId, |
| ExtensionDownloaderDelegate::Stage::QUEUED_FOR_MANIFEST)) |
| .Times(AnyNumber()); |
| EXPECT_CALL(delegate, |
| OnExtensionDownloadStageChanged( |
| kTestExtensionId, |
| ExtensionDownloaderDelegate::Stage::DOWNLOADING_MANIFEST)) |
| .InSequence(sequence); |
| EXPECT_CALL(delegate, |
| OnExtensionDownloadStageChanged( |
| kTestExtensionId, |
| ExtensionDownloaderDelegate::Stage::PARSING_MANIFEST)) |
| .InSequence(sequence); |
| EXPECT_CALL(delegate, |
| OnExtensionDownloadStageChanged( |
| kTestExtensionId, |
| ExtensionDownloaderDelegate::Stage::MANIFEST_LOADED)) |
| .InSequence(sequence); |
| EXPECT_CALL(delegate, |
| OnExtensionDownloadStageChanged( |
| kTestExtensionId, |
| ExtensionDownloaderDelegate::Stage::DOWNLOADING_CRX)) |
| .Times(0); |
| EXPECT_CALL(delegate, OnExtensionDownloadStageChanged( |
| kTestExtensionId, |
| ExtensionDownloaderDelegate::Stage::FINISHED)) |
| .InSequence(sequence); |
| |
| AddFetchDataToDownloader(&helper, std::move(fetch)); |
| |
| content::RunAllTasksUntilIdle(); |
| |
| testing::Mock::VerifyAndClearExpectations(&delegate); |
| } |
| |
| TEST_F(ExtensionDownloaderTest, TestStageChangesBadManifest) { |
| ExtensionDownloaderTestHelper helper; |
| |
| std::unique_ptr<ManifestFetchData> fetch(CreateTestAppFetchData()); |
| GURL fetch_url = fetch->full_url(); |
| |
| const std::string kManifest = "invalid xml"; |
| helper.test_url_loader_factory().AddResponse(fetch_url.spec(), kManifest, |
| net::HTTP_OK); |
| |
| MockExtensionDownloaderDelegate& delegate = helper.delegate(); |
| Sequence sequence; |
| EXPECT_CALL(delegate, |
| OnExtensionDownloadStageChanged( |
| kTestExtensionId, |
| ExtensionDownloaderDelegate::Stage::QUEUED_FOR_MANIFEST)) |
| .Times(AnyNumber()); |
| EXPECT_CALL(delegate, |
| OnExtensionDownloadStageChanged( |
| kTestExtensionId, |
| ExtensionDownloaderDelegate::Stage::DOWNLOADING_MANIFEST)) |
| .InSequence(sequence); |
| EXPECT_CALL(delegate, |
| OnExtensionDownloadStageChanged( |
| kTestExtensionId, |
| ExtensionDownloaderDelegate::Stage::PARSING_MANIFEST)) |
| .InSequence(sequence); |
| EXPECT_CALL(delegate, |
| OnExtensionDownloadStageChanged( |
| kTestExtensionId, |
| ExtensionDownloaderDelegate::Stage::MANIFEST_LOADED)) |
| .Times(0); |
| EXPECT_CALL(delegate, OnExtensionDownloadStageChanged( |
| kTestExtensionId, |
| ExtensionDownloaderDelegate::Stage::FINISHED)) |
| .InSequence(sequence); |
| |
| AddFetchDataToDownloader(&helper, std::move(fetch)); |
| |
| content::RunAllTasksUntilIdle(); |
| |
| testing::Mock::VerifyAndClearExpectations(&delegate); |
| } |
| |
| TEST_F(ExtensionDownloaderTest, TestStageChangesBadQuery) { |
| ExtensionDownloaderTestHelper helper; |
| |
| std::unique_ptr<ManifestFetchData> fetch(CreateTestAppFetchData()); |
| GURL fetch_url = fetch->full_url(); |
| |
| helper.test_url_loader_factory().AddResponse(fetch_url.spec(), "", |
| net::HTTP_BAD_REQUEST); |
| |
| MockExtensionDownloaderDelegate& delegate = helper.delegate(); |
| Sequence sequence; |
| EXPECT_CALL(delegate, |
| OnExtensionDownloadStageChanged( |
| kTestExtensionId, |
| ExtensionDownloaderDelegate::Stage::QUEUED_FOR_MANIFEST)) |
| .Times(AnyNumber()); |
| EXPECT_CALL(delegate, |
| OnExtensionDownloadStageChanged( |
| kTestExtensionId, |
| ExtensionDownloaderDelegate::Stage::DOWNLOADING_MANIFEST)) |
| .InSequence(sequence); |
| EXPECT_CALL(delegate, |
| OnExtensionDownloadStageChanged( |
| kTestExtensionId, |
| ExtensionDownloaderDelegate::Stage::PARSING_MANIFEST)) |
| .Times(0); |
| EXPECT_CALL(delegate, OnExtensionDownloadStageChanged( |
| kTestExtensionId, |
| ExtensionDownloaderDelegate::Stage::FINISHED)) |
| .InSequence(sequence); |
| |
| AddFetchDataToDownloader(&helper, std::move(fetch)); |
| |
| content::RunAllTasksUntilIdle(); |
| |
| testing::Mock::VerifyAndClearExpectations(&delegate); |
| } |
| |
| // Test that failure callback was actually called in case of empty answer from |
| // the update server. Regression for problem described/fixed in |
| // crbug.com/938265. |
| TEST_F(ExtensionDownloaderTest, TestNoUpdatesManifestReports) { |
| ExtensionDownloaderTestHelper helper; |
| |
| std::unique_ptr<ManifestFetchData> fetch(CreateTestAppFetchData()); |
| GURL fetch_url = fetch->full_url(); |
| |
| const std::string kManifest = |
| extensions::CreateUpdateManifest({UpdateManifestItem(kTestExtensionId) |
| .status("noupdate") |
| .info("bandwidth limit")}); |
| helper.test_url_loader_factory().AddResponse(fetch_url.spec(), kManifest, |
| net::HTTP_OK); |
| |
| MockExtensionDownloaderDelegate& delegate = helper.delegate(); |
| EXPECT_CALL(delegate, IsExtensionPending(kTestExtensionId)) |
| .WillOnce(Return(true)); |
| // TODO(burunduk) Also check error (second argument). By now we return |
| // CRX_FETCH_FAILED, but probably we may want to make another one. |
| EXPECT_CALL(delegate, |
| OnExtensionDownloadFailed(kTestExtensionId, _, _, _, _)); |
| |
| AddFetchDataToDownloader(&helper, std::move(fetch)); |
| |
| content::RunAllTasksUntilIdle(); |
| |
| testing::Mock::VerifyAndClearExpectations(&delegate); |
| } |
| |
| // Test that cache status callback is called correctly if there is no such |
| // extension in cache. |
| TEST_F(ExtensionDownloaderTest, TestCacheStatusMiss) { |
| ExtensionDownloaderTestHelper helper; |
| |
| std::unique_ptr<ManifestFetchData> fetch(CreateTestAppFetchData()); |
| const std::string manifest = CreateDefaultUpdateManifest(); |
| SetHelperHandlers(&helper, fetch->full_url(), manifest); |
| |
| // Create cache and provide it to downloader. |
| auto test_extension_cache = std::make_unique<ExtensionCacheFake>(); |
| helper.downloader().StartAllPending(test_extension_cache.get()); |
| |
| MockExtensionDownloaderDelegate& delegate = helper.delegate(); |
| EXPECT_CALL(delegate, IsExtensionPending(kTestExtensionId)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(delegate, |
| OnExtensionDownloadCacheStatusRetrieved( |
| kTestExtensionId, |
| ExtensionDownloaderDelegate::CacheStatus::CACHE_MISS)); |
| |
| AddFetchDataToDownloader(&helper, std::move(fetch)); |
| |
| content::RunAllTasksUntilIdle(); |
| |
| testing::Mock::VerifyAndClearExpectations(&delegate); |
| } |
| |
| // Test that cache status callback is called correctly if there is an extension |
| // archive in cache, but only an old version. |
| TEST_F(ExtensionDownloaderTest, TestCacheStatusOutdated) { |
| ExtensionDownloaderTestHelper helper; |
| |
| std::unique_ptr<ManifestFetchData> fetch(CreateTestAppFetchData()); |
| const std::string manifest = CreateDefaultUpdateManifest(); |
| SetHelperHandlers(&helper, fetch->full_url(), manifest); |
| |
| // Create cache and provide it to downloader. |
| auto test_extension_cache = std::make_unique<ExtensionCacheFake>(); |
| test_extension_cache->AllowCaching(kTestExtensionId); |
| test_extension_cache->PutExtension( |
| kTestExtensionId, "" /* expected hash, ignored by ExtensionCacheFake */, |
| base::FilePath(), "1.0", base::DoNothing()); |
| helper.downloader().StartAllPending(test_extension_cache.get()); |
| |
| MockExtensionDownloaderDelegate& delegate = helper.delegate(); |
| EXPECT_CALL(delegate, IsExtensionPending(kTestExtensionId)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(delegate, |
| OnExtensionDownloadCacheStatusRetrieved( |
| kTestExtensionId, |
| ExtensionDownloaderDelegate::CacheStatus::CACHE_OUTDATED)); |
| |
| AddFetchDataToDownloader(&helper, std::move(fetch)); |
| |
| content::RunAllTasksUntilIdle(); |
| |
| testing::Mock::VerifyAndClearExpectations(&delegate); |
| } |
| |
| // Test that cache status callback is called correctly if there is an extension |
| // archive in cache. |
| TEST_F(ExtensionDownloaderTest, TestCacheStatusHit) { |
| ExtensionDownloaderTestHelper helper; |
| |
| std::unique_ptr<ManifestFetchData> fetch(CreateTestAppFetchData()); |
| const std::string manifest = CreateDefaultUpdateManifest(); |
| SetHelperHandlers(&helper, fetch->full_url(), manifest); |
| |
| // Create cache and provide it to downloader. |
| auto test_extension_cache = std::make_unique<ExtensionCacheFake>(); |
| test_extension_cache->AllowCaching(kTestExtensionId); |
| test_extension_cache->PutExtension( |
| kTestExtensionId, "" /* expected hash, ignored by ExtensionCacheFake */, |
| base::FilePath(FILE_PATH_LITERAL( |
| "file_not_found.crx")) /* We don't need to actually read file. */, |
| "1.1", base::DoNothing()); |
| helper.downloader().StartAllPending(test_extension_cache.get()); |
| |
| MockExtensionDownloaderDelegate& delegate = helper.delegate(); |
| EXPECT_CALL(delegate, IsExtensionPending(kTestExtensionId)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(delegate, |
| OnExtensionDownloadCacheStatusRetrieved( |
| kTestExtensionId, |
| ExtensionDownloaderDelegate::CacheStatus::CACHE_HIT)); |
| |
| AddFetchDataToDownloader(&helper, std::move(fetch)); |
| |
| content::RunAllTasksUntilIdle(); |
| |
| testing::Mock::VerifyAndClearExpectations(&delegate); |
| } |
| |
| // Tests edge-cases related to the update URL. |
| TEST_F(ExtensionDownloaderTest, TestUpdateURLHandle) { |
| ExtensionDownloaderTestHelper helper; |
| const std::vector<ExtensionDownloaderTask>& tasks = |
| GetDownloaderPendingTasks(&helper); |
| |
| // Clear pending queue to check it. |
| helper.downloader().StartAllPending(nullptr); |
| // Invalid update URL, shouldn't be added at all. |
| helper.downloader().AddPendingExtension( |
| CreateDownloaderTask(kTestExtensionId, GURL("http://?invalid=url"))); |
| EXPECT_EQ(0u, tasks.size()); |
| |
| // data: URL, shouldn't be added at all. |
| helper.downloader().AddPendingExtension( |
| CreateDownloaderTask(kTestExtensionId, GURL("data:,"))); |
| EXPECT_EQ(0u, tasks.size()); |
| |
| // Clear pending queue to check it. |
| helper.downloader().StartAllPending(nullptr); |
| // HTTP Webstore URL, should be replaced with HTTPS. |
| helper.downloader().AddPendingExtension(CreateDownloaderTask( |
| kTestExtensionId, |
| GURL("http://clients2.google.com/service/update2/crx"))); |
| ASSERT_EQ(1u, tasks.size()); |
| EXPECT_EQ(GURL("https://clients2.google.com/service/update2/crx"), |
| tasks.rbegin()->update_url); |
| |
| // Clear pending queue to check it. |
| helper.downloader().StartAllPending(nullptr); |
| // Just a custom URL, should be kept as is. |
| helper.downloader().AddPendingExtension( |
| CreateDownloaderTask(kTestExtensionId, GURL("https://example.com"))); |
| ASSERT_EQ(1u, tasks.size()); |
| EXPECT_EQ(GURL("https://example.com"), tasks.rbegin()->update_url); |
| |
| // Clear pending queue to check it. |
| helper.downloader().StartAllPending(nullptr); |
| // Empty URL, should be replaced with Webstore one. |
| helper.downloader().AddPendingExtension( |
| CreateDownloaderTask(kTestExtensionId, GURL(""))); |
| ASSERT_EQ(1u, tasks.size()); |
| EXPECT_EQ(GURL("https://clients2.google.com/service/update2/crx"), |
| tasks.rbegin()->update_url); |
| } |
| |
| // Tests that multiple updates are combined in single requests. |
| TEST_F(ExtensionDownloaderTest, TestMultipleUpdates) { |
| ExtensionDownloaderTestHelper helper; |
| |
| // Add two extensions with different update URLs (to have at least two items |
| // in manifest requests queue). |
| helper.downloader().AddPendingExtension( |
| CreateDownloaderTask(kTestExtensionId, GURL("http://example1.com/"))); |
| helper.downloader().AddPendingExtension( |
| CreateDownloaderTask(kTestExtensionId2, GURL("http://example2.com/"))); |
| |
| helper.downloader().StartAllPending(nullptr); |
| |
| // Add the same two extensions again. |
| helper.downloader().AddPendingExtension( |
| CreateDownloaderTask(kTestExtensionId, GURL("http://example1.com/"))); |
| helper.downloader().AddPendingExtension( |
| CreateDownloaderTask(kTestExtensionId2, GURL("http://example2.com/"))); |
| |
| helper.downloader().StartAllPending(nullptr); |
| |
| int requests_count = 0; |
| network::TestURLLoaderFactory::PendingRequest* request = nullptr; |
| while (helper.test_url_loader_factory().NumPending() > 0) { |
| request = helper.test_url_loader_factory().GetPendingRequest(0); |
| ASSERT_TRUE(request); |
| // Fail all requests with 404 to quickly count them. |
| requests_count++; |
| helper.test_url_loader_factory().AddResponse( |
| request->request.url.spec(), "not found", net::HTTP_NOT_FOUND); |
| } |
| EXPECT_EQ(2, requests_count); |
| } |
| |
| // Tests that extension download is retried if no network found and |
| // extension not found in cache. |
| TEST_F(ExtensionDownloaderTest, TestNoNetworkRetryAfterCacheMiss) { |
| ExtensionDownloaderTestHelper helper; |
| |
| helper.downloader().SetBackoffPolicy(kZeroBackoffPolicy); |
| |
| ExtensionDownloaderTask task = CreateDownloaderTask( |
| kTestExtensionId, extension_urls::GetWebstoreUpdateUrl()); |
| task.install_location = mojom::ManifestLocation::kExternalPolicyDownload, |
| helper.downloader().AddPendingExtension(std::move(task)); |
| auto test_extension_cache = std::make_unique<ExtensionCacheFake>(); |
| helper.downloader().StartAllPending(test_extension_cache.get()); |
| |
| ASSERT_EQ(1, helper.test_url_loader_factory().NumPending()); |
| network::mojom::URLResponseHeadPtr response_head( |
| network::CreateURLResponseHead(net::HTTP_OK)); |
| helper.test_url_loader_factory().SimulateResponseForPendingRequest( |
| helper.test_url_loader_factory().GetPendingRequest(0)->request.url, |
| network::URLLoaderCompletionStatus(net::ERR_INTERNET_DISCONNECTED), |
| std::move(response_head), "" /* content*/); |
| |
| // ExtensionDownloader is expected to retry the request, so number of pending |
| // ones should be one again. |
| EXPECT_EQ(1, helper.test_url_loader_factory().NumPending()); |
| } |
| |
| // Tests that manifest fetch failure is properly reported if extension not found |
| // in cache. |
| TEST_F(ExtensionDownloaderTest, TestManifestFetchFailureAfterCacheMiss) { |
| ExtensionDownloaderTestHelper helper; |
| |
| helper.downloader().SetBackoffPolicy(kZeroBackoffPolicy); |
| |
| ExtensionDownloaderTask task = CreateDownloaderTask( |
| kTestExtensionId, extension_urls::GetWebstoreUpdateUrl()); |
| task.install_location = mojom::ManifestLocation::kExternalPolicyDownload; |
| helper.downloader().AddPendingExtension(std::move(task)); |
| |
| EXPECT_CALL( |
| helper.delegate(), |
| OnExtensionDownloadFailed( |
| kTestExtensionId, |
| ExtensionDownloaderDelegate::Error::MANIFEST_FETCH_FAILED, _, _, _)); |
| |
| auto test_extension_cache = std::make_unique<ExtensionCacheFake>(); |
| helper.downloader().StartAllPending(test_extension_cache.get()); |
| |
| ASSERT_EQ(1, helper.test_url_loader_factory().NumPending()); |
| helper.test_url_loader_factory().SimulateResponseForPendingRequest( |
| helper.test_url_loader_factory().GetPendingRequest(0)->request.url.spec(), |
| "" /* content */, net::HTTP_NOT_FOUND); |
| |
| content::RunAllTasksUntilIdle(); |
| |
| testing::Mock::VerifyAndClearExpectations(&helper.delegate()); |
| } |
| |
| // Tests that multiple requests (with different `request_id`) are handled |
| // correctly. |
| TEST_F(ExtensionDownloaderTest, TestMultipleRequests) { |
| ExtensionDownloaderTestHelper helper; |
| |
| ExtensionDownloaderTask task1 = CreateDownloaderTask( |
| kTestExtensionId, extension_urls::GetWebstoreUpdateUrl()); |
| task1.request_id = 0; |
| ExtensionDownloaderTask task2 = CreateDownloaderTask( |
| kTestExtensionId2, extension_urls::GetWebstoreUpdateUrl()); |
| task2.request_id = 1; |
| |
| helper.test_url_loader_factory().SetInterceptor( |
| base::BindLambdaForTesting([&](const network::ResourceRequest& request) { |
| std::vector<std::tuple<ExtensionId, std::string, std::string>> |
| extensions; |
| if (base::Contains(request.url.spec(), |
| std::string("%3D") + kTestExtensionId + "%26")) { |
| extensions.emplace_back(kTestExtensionId, "1.0", |
| "https://example.com/extension1.crx"); |
| } |
| if (base::Contains(request.url.spec(), |
| std::string("%3D") + kTestExtensionId2 + "%26")) { |
| extensions.emplace_back(kTestExtensionId2, "1.0", |
| "https://example.com/extension2.crx"); |
| } |
| helper.test_url_loader_factory().AddResponse( |
| request.url.spec(), CreateUpdateManifest(extensions), net::HTTP_OK); |
| })); |
| |
| MockExtensionDownloaderDelegate& delegate = helper.delegate(); |
| EXPECT_CALL(delegate, IsExtensionPending(kTestExtensionId)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(delegate, IsExtensionPending(kTestExtensionId2)) |
| .WillOnce(Return(true)); |
| std::map<ExtensionId, std::set<int>> results; |
| EXPECT_CALL(delegate, OnExtensionDownloadFinished_(_, _, _, _, _, _)) |
| .WillRepeatedly( |
| [&](const CRXFileInfo& file, bool file_ownership_passed, |
| const GURL& download_url, |
| const ExtensionDownloaderDelegate::PingResult& ping_result, |
| const std::set<int>& request_ids, |
| ExtensionDownloaderDelegate::InstallCallback& callback) { |
| ASSERT_EQ(results.count(file.extension_id), 0u); |
| results[file.extension_id] = request_ids; |
| }); |
| |
| helper.downloader().AddPendingExtension(std::move(task1)); |
| helper.downloader().AddPendingExtension(std::move(task2)); |
| helper.downloader().StartAllPending(nullptr); |
| |
| content::RunAllTasksUntilIdle(); |
| |
| EXPECT_EQ(results.size(), 2u); |
| EXPECT_EQ(results[kTestExtensionId], std::set<int>({0})); |
| EXPECT_EQ(results[kTestExtensionId2], std::set<int>({1})); |
| |
| testing::Mock::VerifyAndClearExpectations(&delegate); |
| } |
| |
| // Tests that multiple requests (with different `request_id`) are handled |
| // correctly when they are used to fetch the same extension. |
| TEST_F(ExtensionDownloaderTest, TestMultipleRequestsSameExtension) { |
| ExtensionDownloaderTestHelper helper; |
| |
| ExtensionDownloaderTask task1 = CreateDownloaderTask( |
| kTestExtensionId, extension_urls::GetWebstoreUpdateUrl()); |
| task1.request_id = 0; |
| ExtensionDownloaderTask task2 = CreateDownloaderTask( |
| kTestExtensionId, extension_urls::GetWebstoreUpdateUrl()); |
| task2.request_id = 1; |
| |
| helper.test_url_loader_factory().SetInterceptor( |
| base::BindLambdaForTesting([&](const network::ResourceRequest& request) { |
| if (request.url.spec() == "https://example.com/extension1.crx") { |
| helper.test_url_loader_factory().AddResponse(request.url.spec(), "", |
| net::HTTP_OK); |
| return; |
| } |
| ASSERT_TRUE(base::Contains( |
| request.url.spec(), std::string("%3D") + kTestExtensionId + "%26")); |
| std::vector<std::tuple<ExtensionId, std::string, std::string>> |
| extensions; |
| extensions.emplace_back(kTestExtensionId, "1.0", |
| "https://example.com/extension1.crx"); |
| helper.test_url_loader_factory().AddResponse( |
| request.url.spec(), CreateUpdateManifest(extensions), net::HTTP_OK); |
| })); |
| |
| MockExtensionDownloaderDelegate& delegate = helper.delegate(); |
| EXPECT_CALL(delegate, IsExtensionPending(kTestExtensionId)) |
| .WillRepeatedly(Return(true)); |
| std::map<ExtensionId, std::set<int>> results; |
| EXPECT_CALL(delegate, OnExtensionDownloadFinished_(_, _, _, _, _, _)) |
| .WillRepeatedly( |
| [&](const CRXFileInfo& file, bool file_ownership_passed, |
| const GURL& download_url, |
| const ExtensionDownloaderDelegate::PingResult& ping_result, |
| const std::set<int>& request_ids, |
| ExtensionDownloaderDelegate::InstallCallback& callback) { |
| DCHECK(results.count(file.extension_id) == 0); |
| results[file.extension_id] = request_ids; |
| }); |
| |
| helper.downloader().AddPendingExtension(std::move(task1)); |
| helper.downloader().AddPendingExtension(std::move(task2)); |
| helper.downloader().StartAllPending(nullptr); |
| |
| content::RunAllTasksUntilIdle(); |
| |
| EXPECT_EQ(results.size(), 1u); |
| EXPECT_EQ(results[kTestExtensionId], std::set<int>({0, 1})); |
| |
| testing::Mock::VerifyAndClearExpectations(&delegate); |
| } |
| |
| // Tests that update manifest fetches with the same URLs will be actually |
| // merged. |
| TEST_F(ExtensionDownloaderTest, TestUpdateManifestURLMerged) { |
| ExtensionDownloaderTestHelper helper; |
| |
| ExtensionDownloaderTask task1 = CreateDownloaderTask( |
| kTestExtensionId, extension_urls::GetWebstoreUpdateUrl()); |
| task1.request_id = 1; |
| ExtensionDownloaderTask task2 = CreateDownloaderTask( |
| kTestExtensionId, extension_urls::GetWebstoreUpdateUrl()); |
| task2.request_id = 2; |
| |
| int number_of_fetches = 0; |
| helper.test_url_loader_factory().SetInterceptor( |
| base::BindLambdaForTesting([&](const network::ResourceRequest& request) { |
| if (request.url.spec() == "https://example.com/extension1.crx") { |
| helper.test_url_loader_factory().AddResponse(request.url.spec(), "", |
| net::HTTP_OK); |
| return; |
| } |
| number_of_fetches++; |
| helper.test_url_loader_factory().AddResponse( |
| request.url.spec(), |
| CreateUpdateManifest( |
| {std::make_tuple(kTestExtensionId, "1.0", |
| "https://example.com/extension1.crx")}), |
| net::HTTP_OK); |
| })); |
| |
| helper.downloader().AddPendingExtension(std::move(task1)); |
| helper.downloader().StartAllPending(nullptr); |
| // We expect the downloader to merge two requests when the first one will be |
| // processed. For that we ensure that the first one becomes active before we |
| // add the second one. |
| EXPECT_NE(helper.downloader().GetActiveManifestFetchForTesting(), nullptr); |
| helper.downloader().AddPendingExtension(std::move(task2)); |
| helper.downloader().StartAllPending(nullptr); |
| |
| content::RunAllTasksUntilIdle(); |
| |
| EXPECT_EQ(number_of_fetches, 1); |
| } |
| |
| // Tests that extension fetches with the same URLs will be actually merged. |
| TEST_F(ExtensionDownloaderTest, TestExtensionURLMerged) { |
| ExtensionDownloaderTestHelper helper; |
| |
| ExtensionDownloaderTask task1 = CreateDownloaderTask( |
| kTestExtensionId, GURL("https://example.com/update1")); |
| task1.request_id = 1; |
| ExtensionDownloaderTask task2 = CreateDownloaderTask( |
| kTestExtensionId, GURL("https://example.com/update2")); |
| task2.request_id = 2; |
| |
| int number_of_fetches = 0; |
| helper.test_url_loader_factory().SetInterceptor( |
| base::BindLambdaForTesting([&](const network::ResourceRequest& request) { |
| if (request.url.spec() == "https://example.com/extension1.crx") { |
| number_of_fetches++; |
| // Don't reply on this request immediately, make sure that manifest |
| // fetches will happen first. |
| return; |
| } |
| helper.test_url_loader_factory().AddResponse( |
| request.url.spec(), |
| CreateUpdateManifest( |
| {std::make_tuple(kTestExtensionId, "1.0", |
| "https://example.com/extension1.crx")}), |
| net::HTTP_OK); |
| })); |
| |
| MockExtensionDownloaderDelegate& delegate = helper.delegate(); |
| EXPECT_CALL(delegate, IsExtensionPending(kTestExtensionId)) |
| .WillRepeatedly(Return(true)); |
| |
| helper.downloader().AddPendingExtension(std::move(task1)); |
| helper.downloader().AddPendingExtension(std::move(task2)); |
| helper.downloader().StartAllPending(nullptr); |
| |
| content::RunAllTasksUntilIdle(); |
| helper.test_url_loader_factory().AddResponse( |
| "https://example.com/extension1.crx", "", net::HTTP_OK); |
| content::RunAllTasksUntilIdle(); |
| |
| EXPECT_EQ(number_of_fetches, 1); |
| } |
| |
| // Tests how the downloader uses the cache when there is no network. |
| TEST_F(ExtensionDownloaderTest, TestMultipleCacheAccess) { |
| ExtensionDownloaderTestHelper helper; |
| |
| // Two tasks for the same extension ID will end up in two different but |
| // completely identical manifest fetches in the downloader, so when we'll ask |
| // the cache about the extension after network fetch failure, they should be |
| // merged into one fetch and cache should be queried only once. |
| ExtensionDownloaderTask task1 = CreateDownloaderTask( |
| kTestExtensionId, extension_urls::GetWebstoreUpdateUrl()); |
| task1.install_location = mojom::ManifestLocation::kExternalPolicyDownload; |
| task1.request_id = 1; |
| ExtensionDownloaderTask task2 = CreateDownloaderTask( |
| kTestExtensionId, extension_urls::GetWebstoreUpdateUrl()); |
| task2.install_location = mojom::ManifestLocation::kExternalPolicyDownload; |
| task2.request_id = 2; |
| |
| helper.test_url_loader_factory().SetInterceptor( |
| base::BindLambdaForTesting([&](const network::ResourceRequest& request) { |
| network::mojom::URLResponseHeadPtr response_head( |
| network::CreateURLResponseHead(net::HTTP_OK)); |
| helper.test_url_loader_factory().AddResponse( |
| request.url, std::move(response_head), "" /* content*/, |
| network::URLLoaderCompletionStatus(net::ERR_INTERNET_DISCONNECTED)); |
| })); |
| |
| MockExtensionCache mock_cache; |
| |
| EXPECT_CALL(mock_cache, GetExtension(kTestExtensionId, _, _, _)).Times(1); |
| |
| helper.downloader().AddPendingExtension(std::move(task1)); |
| helper.downloader().AddPendingExtension(std::move(task2)); |
| helper.downloader().StartAllPending(&mock_cache); |
| |
| content::RunAllTasksUntilIdle(); |
| |
| testing::Mock::VerifyAndClearExpectations(&mock_cache); |
| } |
| |
| } // namespace extensions |