910e62b5创建于 1月15日历史提交
// 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