[go: nahoru, domu]

blob: 074af6afa3c8260b5baa645ad8cebb4f345e8026 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stddef.h>
#include <memory>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/containers/contains.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/values.h"
#include "components/crx_file/id_util.h"
#include "components/update_client/crx_update_item.h"
#include "components/update_client/update_client.h"
#include "content/public/browser/notification_service.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/allowlist_state.h"
#include "extensions/browser/blocklist_state.h"
#include "extensions/browser/disable_reason.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extensions_test.h"
#include "extensions/browser/mock_extension_system.h"
#include "extensions/browser/notification_types.h"
#include "extensions/browser/test_extensions_browser_client.h"
#include "extensions/browser/updater/extension_downloader.h"
#include "extensions/browser/updater/extension_update_data.h"
#include "extensions/browser/updater/uninstall_ping_sender.h"
#include "extensions/browser/updater/update_service.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/extension_urls.h"
#include "extensions/common/manifest_url_handlers.h"
#include "extensions/common/value_builder.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace {
using UpdateClientEvents = update_client::UpdateClient::Observer::Events;
class FakeUpdateClient : public update_client::UpdateClient {
public:
FakeUpdateClient();
FakeUpdateClient(const FakeUpdateClient&) = delete;
FakeUpdateClient& operator=(const FakeUpdateClient&) = delete;
// Returns the data we've gotten from the CrxDataCallback for ids passed to
// the Update function.
std::vector<absl::optional<update_client::CrxComponent>>* data() {
return &data_;
}
// Used for tests that uninstall pings get requested properly.
struct UninstallPing {
std::string id;
base::Version version;
int reason;
UninstallPing(const std::string& id,
const base::Version& version,
int reason)
: id(id), version(version), reason(reason) {}
};
std::vector<UninstallPing>& uninstall_pings() { return uninstall_pings_; }
struct UpdateRequest {
std::vector<std::string> extension_ids;
update_client::Callback callback;
};
// update_client::UpdateClient
void AddObserver(Observer* observer) override {
if (observer)
observers_.push_back(observer);
}
void RemoveObserver(Observer* observer) override {}
base::RepeatingClosure Install(
const std::string& id,
CrxDataCallback crx_data_callback,
CrxStateChangeCallback crx_state_change_callback,
update_client::Callback callback) override {
return base::DoNothing();
}
void Update(const std::vector<std::string>& ids,
CrxDataCallback crx_data_callback,
CrxStateChangeCallback crx_state_change_callback,
bool is_foreground,
update_client::Callback callback) override;
bool GetCrxUpdateState(
const std::string& id,
update_client::CrxUpdateItem* update_item) const override {
update_item->next_version = base::Version("2.0");
std::map<std::string, std::string> custom_attributes;
if (is_malware_update_item_)
custom_attributes["_malware"] = "true";
if (allowlist_state == extensions::ALLOWLIST_ALLOWLISTED)
custom_attributes["_esbAllowlist"] = "true";
else if (allowlist_state == extensions::ALLOWLIST_NOT_ALLOWLISTED)
custom_attributes["_esbAllowlist"] = "true";
if (!custom_attributes.empty())
update_item->custom_updatecheck_data = custom_attributes;
return true;
}
bool IsUpdating(const std::string& id) const override { return false; }
void Stop() override {}
void SendUninstallPing(const update_client::CrxComponent& crx_component,
int reason,
update_client::Callback callback) override {
uninstall_pings_.emplace_back(crx_component.app_id, crx_component.version,
reason);
}
void FireEvent(Observer::Events event, const std::string& extension_id) {
for (Observer* observer : observers_)
observer->OnEvent(event, extension_id);
}
void set_delay_update() { delay_update_ = true; }
void set_is_malware_update_item() { is_malware_update_item_ = true; }
void set_allowlist_state(extensions::AllowlistState state) {
allowlist_state = state;
}
bool delay_update() const { return delay_update_; }
UpdateRequest& update_request(int index) { return delayed_requests_[index]; }
int num_update_requests() const {
return static_cast<int>(delayed_requests_.size());
}
void RunDelayedUpdate(
int index,
Observer::Events event = Observer::Events::COMPONENT_UPDATED) {
UpdateRequest& request = update_request(index);
for (const std::string& id : request.extension_ids)
FireEvent(event, id);
std::move(request.callback).Run(update_client::Error::NONE);
}
protected:
~FakeUpdateClient() override = default;
std::vector<absl::optional<update_client::CrxComponent>> data_;
std::vector<UninstallPing> uninstall_pings_;
std::vector<Observer*> observers_;
bool delay_update_;
bool is_malware_update_item_ = false;
extensions::AllowlistState allowlist_state = extensions::ALLOWLIST_UNDEFINED;
std::vector<UpdateRequest> delayed_requests_;
};
FakeUpdateClient::FakeUpdateClient() : delay_update_(false) {}
void FakeUpdateClient::Update(const std::vector<std::string>& ids,
CrxDataCallback crx_data_callback,
CrxStateChangeCallback crx_state_change_callback,
bool is_foreground,
update_client::Callback callback) {
data_ = std::move(crx_data_callback).Run(ids);
if (delay_update()) {
delayed_requests_.push_back({ids, std::move(callback)});
} else {
for (const std::string& id : ids)
FireEvent(Observer::Events::COMPONENT_UPDATED, id);
std::move(callback).Run(update_client::Error::NONE);
}
}
class UpdateFoundNotificationObserver : public content::NotificationObserver {
public:
UpdateFoundNotificationObserver(const std::string& id,
const std::string& version)
: id_(id), version_(version) {
registrar_.Add(this, extensions::NOTIFICATION_EXTENSION_UPDATE_FOUND,
content::NotificationService::AllSources());
}
~UpdateFoundNotificationObserver() override {
registrar_.Remove(this, extensions::NOTIFICATION_EXTENSION_UPDATE_FOUND,
content::NotificationService::AllSources());
}
void reset() { found_notification_ = false; }
bool found_notification() const { return found_notification_; }
private:
void Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) override {
ASSERT_EQ(extensions::NOTIFICATION_EXTENSION_UPDATE_FOUND, type);
EXPECT_EQ(id_, content::Details<extensions::UpdateDetails>(details)->id);
EXPECT_EQ(version_, content::Details<extensions::UpdateDetails>(details)
->version.GetString());
found_notification_ = true;
}
private:
content::NotificationRegistrar registrar_;
bool found_notification_ = false;
std::string id_;
std::string version_;
};
} // namespace
namespace extensions {
namespace {
// A global variable for controlling whether uninstalls should cause uninstall
// pings to be sent.
UninstallPingSender::FilterResult g_should_ping =
UninstallPingSender::DO_NOT_SEND_PING;
// Helper method to serve as an uninstall ping filter.
UninstallPingSender::FilterResult ShouldPing(const Extension* extension,
UninstallReason reason) {
return g_should_ping;
}
// A fake ExtensionSystem that lets us intercept calls to install new
// versions of an extension.
class FakeExtensionSystem : public MockExtensionSystem {
public:
using InstallUpdateCallback = MockExtensionSystem::InstallUpdateCallback;
explicit FakeExtensionSystem(content::BrowserContext* context)
: MockExtensionSystem(context) {}
~FakeExtensionSystem() override = default;
struct InstallUpdateRequest {
InstallUpdateRequest(const std::string& extension_id,
const base::FilePath& temp_dir,
bool install_immediately)
: extension_id(extension_id),
temp_dir(temp_dir),
install_immediately(install_immediately) {}
std::string extension_id;
base::FilePath temp_dir;
bool install_immediately;
};
std::vector<InstallUpdateRequest>* install_requests() {
return &install_requests_;
}
void set_install_callback(base::OnceClosure callback) {
next_install_callback_ = std::move(callback);
}
// ExtensionSystem override
void InstallUpdate(const std::string& extension_id,
const std::string& public_key,
const base::FilePath& temp_dir,
bool install_immediately,
InstallUpdateCallback install_update_callback) override {
base::DeletePathRecursively(temp_dir);
install_requests_.push_back(
InstallUpdateRequest(extension_id, temp_dir, install_immediately));
if (!next_install_callback_.is_null()) {
std::move(next_install_callback_).Run();
}
std::move(install_update_callback).Run(absl::nullopt);
}
void PerformActionBasedOnOmahaAttributes(
const std::string& extension_id,
const base::Value& attributes) override {
ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
scoped_refptr<const Extension> extension1 =
ExtensionBuilder("1").SetVersion("1.2").SetID(extension_id).Build();
const base::Value* malware_value = attributes.FindKey("_malware");
if (malware_value && malware_value->GetBool())
registry->AddDisabled(extension1);
else
registry->AddEnabled(extension1);
const base::Value* allowlist_value = attributes.FindKey("_esbAllowlist");
if (allowlist_value) {
bool is_allowlisted = allowlist_value->GetBool();
extension_allowlist_states_[extension_id] =
is_allowlisted ? ALLOWLIST_ALLOWLISTED : ALLOWLIST_NOT_ALLOWLISTED;
}
}
bool FinishDelayedInstallationIfReady(const std::string& extension_id,
bool install_immediately) override {
return false;
}
AllowlistState GetExtensionAllowlistState(const std::string& extension_id) {
if (!base::Contains(extension_allowlist_states_, extension_id))
return ALLOWLIST_UNDEFINED;
return extension_allowlist_states_[extension_id];
}
private:
std::vector<InstallUpdateRequest> install_requests_;
base::OnceClosure next_install_callback_;
base::flat_map<std::string, AllowlistState> extension_allowlist_states_;
};
class UpdateServiceTest : public ExtensionsTest {
public:
UpdateServiceTest() = default;
~UpdateServiceTest() override = default;
void SetUp() override {
ExtensionsTest::SetUp();
extensions_browser_client()->set_extension_system_factory(
&fake_extension_system_factory_);
extensions_browser_client()->SetUpdateClientFactory(base::BindRepeating(
&UpdateServiceTest::CreateUpdateClient, base::Unretained(this)));
update_service_ = UpdateService::Get(browser_context());
}
protected:
UpdateService* update_service() const { return update_service_; }
FakeUpdateClient* update_client() const { return update_client_.get(); }
update_client::UpdateClient* CreateUpdateClient() {
// We only expect that this will get called once, so consider it an error
// if our update_client_ is already non-null.
EXPECT_EQ(nullptr, update_client_.get());
update_client_ = base::MakeRefCounted<FakeUpdateClient>();
return update_client_.get();
}
// Helper function that creates a file at |relative_path| within |directory|
// and fills it with |content|.
bool AddFileToDirectory(const base::FilePath& directory,
const base::FilePath& relative_path,
const std::string& content) {
base::FilePath full_path = directory.Append(relative_path);
if (!CreateDirectory(full_path.DirName()))
return false;
int result = base::WriteFile(full_path, content.data(), content.size());
return (static_cast<size_t>(result) == content.size());
}
FakeExtensionSystem* extension_system() {
return static_cast<FakeExtensionSystem*>(
fake_extension_system_factory_.GetForBrowserContext(browser_context()));
}
void BasicUpdateOperations(bool install_immediately) {
// Create a temporary directory that a fake extension will live in and fill
// it with some test files.
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
base::FilePath foo_js(FILE_PATH_LITERAL("foo.js"));
base::FilePath bar_html(FILE_PATH_LITERAL("bar/bar.html"));
ASSERT_TRUE(AddFileToDirectory(temp_dir.GetPath(), foo_js, "hello"))
<< "Failed to write " << temp_dir.GetPath().value() << "/"
<< foo_js.value();
ASSERT_TRUE(AddFileToDirectory(temp_dir.GetPath(), bar_html, "world"));
scoped_refptr<const Extension> extension1 =
ExtensionBuilder("Foo")
.SetVersion("1.0")
.SetID(crx_file::id_util::GenerateId("foo_extension"))
.SetPath(temp_dir.GetPath())
.Build();
ExtensionRegistry::Get(browser_context())->AddEnabled(extension1);
ExtensionUpdateCheckParams update_check_params;
update_check_params.update_info[extension1->id()] = ExtensionUpdateData();
update_check_params.install_immediately = install_immediately;
// Start an update check and verify that the UpdateClient was sent the right
// data.
bool executed = false;
update_service()->StartUpdateCheck(
update_check_params,
base::BindOnce([](bool* executed) { *executed = true; }, &executed));
ASSERT_TRUE(executed);
const auto* data = update_client()->data();
ASSERT_NE(nullptr, data);
ASSERT_EQ(1u, data->size());
ASSERT_EQ(data->at(0)->version, extension1->version());
update_client::CrxInstaller* installer = data->at(0)->installer.get();
ASSERT_NE(installer, nullptr);
// The GetInstalledFile method is used when processing differential updates
// to get a path to an existing file in an extension. We want to test a
// number of scenarios to be user we handle invalid relative paths, don't
// accidentally return paths outside the extension's dir, etc.
base::FilePath tmp;
EXPECT_TRUE(installer->GetInstalledFile(foo_js.MaybeAsASCII(), &tmp));
EXPECT_EQ(temp_dir.GetPath().Append(foo_js), tmp) << tmp.value();
EXPECT_TRUE(installer->GetInstalledFile(bar_html.MaybeAsASCII(), &tmp));
EXPECT_EQ(temp_dir.GetPath().Append(bar_html), tmp) << tmp.value();
EXPECT_FALSE(installer->GetInstalledFile("does_not_exist", &tmp));
EXPECT_FALSE(installer->GetInstalledFile("does/not/exist", &tmp));
EXPECT_FALSE(installer->GetInstalledFile("/does/not/exist", &tmp));
EXPECT_FALSE(installer->GetInstalledFile("C:\\tmp", &tmp));
base::FilePath system_temp_dir;
ASSERT_TRUE(base::GetTempDir(&system_temp_dir));
EXPECT_FALSE(
installer->GetInstalledFile(system_temp_dir.MaybeAsASCII(), &tmp));
// Test the install callback.
base::ScopedTempDir new_version_dir;
ASSERT_TRUE(new_version_dir.CreateUniqueTempDir());
bool done = false;
installer->Install(
new_version_dir.GetPath(), std::string(), nullptr, base::DoNothing(),
base::BindOnce(
[](bool* done, const update_client::CrxInstaller::Result& result) {
*done = true;
EXPECT_EQ(0, result.error);
EXPECT_EQ(0, result.extended_error);
},
&done));
base::RunLoop run_loop;
extension_system()->set_install_callback(run_loop.QuitClosure());
run_loop.Run();
std::vector<FakeExtensionSystem::InstallUpdateRequest>* requests =
extension_system()->install_requests();
ASSERT_EQ(1u, requests->size());
const auto& request = requests->at(0);
EXPECT_EQ(request.extension_id, extension1->id());
EXPECT_EQ(request.temp_dir.value(), new_version_dir.GetPath().value());
EXPECT_EQ(install_immediately, request.install_immediately);
EXPECT_TRUE(done);
}
private:
raw_ptr<UpdateService> update_service_ = nullptr;
scoped_refptr<FakeUpdateClient> update_client_;
MockExtensionSystemFactory<FakeExtensionSystem>
fake_extension_system_factory_;
};
TEST_F(UpdateServiceTest, BasicUpdateOperations_InstallImmediately) {
BasicUpdateOperations(true);
}
TEST_F(UpdateServiceTest, BasicUpdateOperations_NotInstallImmediately) {
BasicUpdateOperations(false);
}
TEST_F(UpdateServiceTest, UninstallPings) {
UninstallPingSender sender(ExtensionRegistry::Get(browser_context()),
base::BindRepeating(&ShouldPing));
// Build 3 extensions.
scoped_refptr<const Extension> extension1 =
ExtensionBuilder("1").SetVersion("1.2").Build();
scoped_refptr<const Extension> extension2 =
ExtensionBuilder("2").SetVersion("2.3").Build();
scoped_refptr<const Extension> extension3 =
ExtensionBuilder("3").SetVersion("3.4").Build();
EXPECT_TRUE(extension1->id() != extension2->id() &&
extension1->id() != extension3->id() &&
extension2->id() != extension3->id());
ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
// Run tests for each uninstall reason.
for (int reason_val = static_cast<int>(UNINSTALL_REASON_FOR_TESTING);
reason_val < static_cast<int>(UNINSTALL_REASON_MAX); ++reason_val) {
UninstallReason reason = static_cast<UninstallReason>(reason_val);
// Start with 2 enabled and 1 disabled extensions.
EXPECT_TRUE(registry->AddEnabled(extension1)) << reason;
EXPECT_TRUE(registry->AddEnabled(extension2)) << reason;
EXPECT_TRUE(registry->AddDisabled(extension3)) << reason;
// Uninstall the first extension, instructing our filter not to send pings,
// and verify none were sent.
g_should_ping = UninstallPingSender::DO_NOT_SEND_PING;
EXPECT_TRUE(registry->RemoveEnabled(extension1->id())) << reason;
registry->TriggerOnUninstalled(extension1.get(), reason);
EXPECT_TRUE(update_client()->uninstall_pings().empty()) << reason;
// Uninstall the second and third extensions, instructing the filter to
// send pings, and make sure we got the expected data.
g_should_ping = UninstallPingSender::SEND_PING;
EXPECT_TRUE(registry->RemoveEnabled(extension2->id())) << reason;
registry->TriggerOnUninstalled(extension2.get(), reason);
EXPECT_TRUE(registry->RemoveDisabled(extension3->id())) << reason;
registry->TriggerOnUninstalled(extension3.get(), reason);
std::vector<FakeUpdateClient::UninstallPing>& pings =
update_client()->uninstall_pings();
ASSERT_EQ(2u, pings.size()) << reason;
EXPECT_EQ(extension2->id(), pings[0].id) << reason;
EXPECT_EQ(extension2->version(), pings[0].version) << reason;
EXPECT_EQ(reason, pings[0].reason) << reason;
EXPECT_EQ(extension3->id(), pings[1].id) << reason;
EXPECT_EQ(extension3->version(), pings[1].version) << reason;
EXPECT_EQ(reason, pings[1].reason) << reason;
pings.clear();
}
}
TEST_F(UpdateServiceTest, NoPerformAction) {
std::string extension_id = crx_file::id_util::GenerateId("id");
ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
scoped_refptr<const Extension> extension1 =
ExtensionBuilder("1").SetVersion("1.2").SetID(extension_id).Build();
EXPECT_TRUE(registry->AddEnabled(extension1));
update_client()->set_is_malware_update_item();
update_client()->set_delay_update();
ExtensionUpdateCheckParams update_check_params;
update_check_params.update_info[extension_id] = ExtensionUpdateData();
bool executed = false;
update_service()->StartUpdateCheck(
update_check_params,
base::BindOnce([](bool* executed) { *executed = true; }, &executed));
EXPECT_FALSE(executed);
const auto& request = update_client()->update_request(0);
EXPECT_THAT(request.extension_ids, testing::ElementsAre(extension_id));
update_client()->RunDelayedUpdate(
0, UpdateClientEvents::COMPONENT_CHECKING_FOR_UPDATES);
EXPECT_FALSE(registry->disabled_extensions().GetByID(extension_id));
EXPECT_EQ(extensions::ALLOWLIST_UNDEFINED,
extension_system()->GetExtensionAllowlistState(extension_id));
}
TEST_F(UpdateServiceTest, CheckOmahaMalwareAttributes) {
std::string extension_id = crx_file::id_util::GenerateId("id");
ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
scoped_refptr<const Extension> extension1 =
ExtensionBuilder("1").SetVersion("1.2").SetID(extension_id).Build();
EXPECT_TRUE(registry->AddEnabled(extension1));
update_client()->set_is_malware_update_item();
update_client()->set_delay_update();
ExtensionUpdateCheckParams update_check_params;
update_check_params.update_info[extension_id] = ExtensionUpdateData();
bool executed = false;
update_service()->StartUpdateCheck(
update_check_params,
base::BindOnce([](bool* executed) { *executed = true; }, &executed));
EXPECT_FALSE(executed);
const auto& request = update_client()->update_request(0);
EXPECT_THAT(request.extension_ids, testing::ElementsAre(extension_id));
update_client()->RunDelayedUpdate(
0, UpdateClientEvents::COMPONENT_ALREADY_UP_TO_DATE);
EXPECT_TRUE(registry->disabled_extensions().GetByID(extension_id));
}
TEST_F(UpdateServiceTest, CheckOmahaAllowlistAttributes) {
std::string extension_id = crx_file::id_util::GenerateId("id");
scoped_refptr<const Extension> extension1 =
ExtensionBuilder("1").SetVersion("1.2").SetID(extension_id).Build();
update_client()->set_allowlist_state(extensions::ALLOWLIST_ALLOWLISTED);
update_client()->set_delay_update();
ExtensionUpdateCheckParams update_check_params;
update_check_params.update_info[extension_id] = ExtensionUpdateData();
bool executed = false;
update_service()->StartUpdateCheck(
update_check_params,
base::BindOnce([](bool* executed) { *executed = true; }, &executed));
EXPECT_FALSE(executed);
const auto& request = update_client()->update_request(0);
EXPECT_THAT(request.extension_ids, testing::ElementsAre(extension_id));
update_client()->RunDelayedUpdate(0,
UpdateClientEvents::COMPONENT_UPDATE_FOUND);
EXPECT_EQ(extensions::ALLOWLIST_ALLOWLISTED,
extension_system()->GetExtensionAllowlistState(extension_id));
}
TEST_F(UpdateServiceTest, CheckNoOmahaAttributes) {
std::string extension_id = crx_file::id_util::GenerateId("id");
ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
scoped_refptr<const Extension> extension1 =
ExtensionBuilder("1").SetVersion("1.2").SetID(extension_id).Build();
EXPECT_TRUE(registry->AddDisabled(extension1));
update_client()->set_delay_update();
ExtensionUpdateCheckParams update_check_params;
update_check_params.update_info[extension_id] = ExtensionUpdateData();
bool executed = false;
update_service()->StartUpdateCheck(
update_check_params,
base::BindOnce([](bool* executed) { *executed = true; }, &executed));
EXPECT_FALSE(executed);
const auto& request = update_client()->update_request(0);
EXPECT_THAT(request.extension_ids, testing::ElementsAre(extension_id));
update_client()->RunDelayedUpdate(
0, UpdateClientEvents::COMPONENT_ALREADY_UP_TO_DATE);
EXPECT_TRUE(registry->enabled_extensions().GetByID(extension_id));
EXPECT_EQ(extensions::ALLOWLIST_UNDEFINED,
extension_system()->GetExtensionAllowlistState(extension_id));
}
TEST_F(UpdateServiceTest, UpdateFoundNotification) {
std::string extension_id = crx_file::id_util::GenerateId("id");
UpdateFoundNotificationObserver notification_observer(extension_id, "2.0");
// Fire UpdateClientEvents::COMPONENT_UPDATE_FOUND and verify that
// NOTIFICATION_EXTENSION_UPDATE_FOUND notification is sent.
update_client()->FireEvent(UpdateClientEvents::COMPONENT_UPDATE_FOUND,
extension_id);
EXPECT_TRUE(notification_observer.found_notification());
notification_observer.reset();
update_client()->FireEvent(UpdateClientEvents::COMPONENT_CHECKING_FOR_UPDATES,
extension_id);
EXPECT_FALSE(notification_observer.found_notification());
}
TEST_F(UpdateServiceTest, InProgressUpdate_Successful) {
base::HistogramTester histogram_tester;
update_client()->set_delay_update();
ExtensionUpdateCheckParams update_check_params;
// Extensions with empty IDs will be ignored.
update_check_params.update_info["A"] = ExtensionUpdateData();
update_check_params.update_info["B"] = ExtensionUpdateData();
update_check_params.update_info["C"] = ExtensionUpdateData();
update_check_params.update_info["D"] = ExtensionUpdateData();
update_check_params.update_info["E"] = ExtensionUpdateData();
bool executed = false;
update_service()->StartUpdateCheck(
update_check_params,
base::BindOnce([](bool* executed) { *executed = true; }, &executed));
EXPECT_FALSE(executed);
const auto& request = update_client()->update_request(0);
EXPECT_THAT(request.extension_ids,
testing::ElementsAre("A", "B", "C", "D", "E"));
update_client()->RunDelayedUpdate(0);
EXPECT_TRUE(executed);
}
// Incorrect deduplicating of the same extension ID but with different flags may
// lead to incorrect behaviour: corrupted extension won't be reinstalled.
TEST_F(UpdateServiceTest, InProgressUpdate_DuplicateWithDifferentData) {
base::HistogramTester histogram_tester;
update_client()->set_delay_update();
ExtensionUpdateCheckParams uc1, uc2;
uc1.update_info["A"] = ExtensionUpdateData();
uc2.update_info["A"] = ExtensionUpdateData();
uc2.update_info["A"].install_source = "reinstall";
uc2.update_info["A"].is_corrupt_reinstall = true;
bool executed1 = false;
update_service()->StartUpdateCheck(
uc1,
base::BindOnce([](bool* executed) { *executed = true; }, &executed1));
EXPECT_FALSE(executed1);
bool executed2 = false;
update_service()->StartUpdateCheck(
uc2,
base::BindOnce([](bool* executed) { *executed = true; }, &executed2));
EXPECT_FALSE(executed2);
ASSERT_EQ(2, update_client()->num_update_requests());
{
const auto& request = update_client()->update_request(0);
EXPECT_THAT(request.extension_ids, testing::ElementsAre("A"));
}
{
const auto& request = update_client()->update_request(1);
EXPECT_THAT(request.extension_ids, testing::ElementsAre("A"));
}
update_client()->RunDelayedUpdate(0);
EXPECT_TRUE(executed1);
EXPECT_FALSE(executed2);
update_client()->RunDelayedUpdate(1);
EXPECT_TRUE(executed2);
}
TEST_F(UpdateServiceTest, InProgressUpdate_NonOverlapped) {
// 2 non-overallped update requests.
base::HistogramTester histogram_tester;
update_client()->set_delay_update();
ExtensionUpdateCheckParams uc1, uc2;
uc1.update_info["A"] = ExtensionUpdateData();
uc1.update_info["B"] = ExtensionUpdateData();
uc1.update_info["C"] = ExtensionUpdateData();
uc2.update_info["D"] = ExtensionUpdateData();
uc2.update_info["E"] = ExtensionUpdateData();
bool executed1 = false;
update_service()->StartUpdateCheck(
uc1,
base::BindOnce([](bool* executed) { *executed = true; }, &executed1));
EXPECT_FALSE(executed1);
bool executed2 = false;
update_service()->StartUpdateCheck(
uc2,
base::BindOnce([](bool* executed) { *executed = true; }, &executed2));
EXPECT_FALSE(executed2);
ASSERT_EQ(2, update_client()->num_update_requests());
const auto& request1 = update_client()->update_request(0);
const auto& request2 = update_client()->update_request(1);
EXPECT_THAT(request1.extension_ids, testing::ElementsAre("A", "B", "C"));
EXPECT_THAT(request2.extension_ids, testing::ElementsAre("D", "E"));
update_client()->RunDelayedUpdate(0);
EXPECT_TRUE(executed1);
EXPECT_FALSE(executed2);
update_client()->RunDelayedUpdate(1);
EXPECT_TRUE(executed2);
}
} // namespace
} // namespace extensions