blob: 695adfad88b178711f500fc88edb0e124e0af474 [file] [log] [blame]
// 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