blob: 59de9319cafffa61bcbe2a9e9d29e601c131a9d8 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/containers/flat_map.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/optional.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/stl_util.h"
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "base/test/scoped_path_override.h"
#include "base/test/scoped_task_environment.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/values.h"
#include "base/version.h"
#include "build/build_config.h"
#include "components/crx_file/crx_verifier.h"
#include "components/prefs/testing_pref_service.h"
#include "components/update_client/component_unpacker.h"
#include "components/update_client/crx_update_item.h"
#include "components/update_client/network.h"
#include "components/update_client/patcher.h"
#include "components/update_client/persisted_data.h"
#include "components/update_client/ping_manager.h"
#include "components/update_client/protocol_handler.h"
#include "components/update_client/test_configurator.h"
#include "components/update_client/test_installer.h"
#include "components/update_client/unzipper.h"
#include "components/update_client/update_checker.h"
#include "components/update_client/update_client_errors.h"
#include "components/update_client/update_client_internal.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace update_client {
namespace {
using base::FilePath;
// Makes a copy of the file specified by |from_path| in a temporary directory
// and returns the path of the copy. Returns true if successful. Cleans up if
// there was an error creating the copy.
bool MakeTestFile(const FilePath& from_path, FilePath* to_path) {
FilePath temp_dir;
bool result =
CreateNewTempDirectory(FILE_PATH_LITERAL("update_client"), &temp_dir);
if (!result)
return false;
FilePath temp_file;
result = CreateTemporaryFileInDir(temp_dir, &temp_file);
if (!result)
return false;
result = CopyFile(from_path, temp_file);
if (!result) {
DeleteFile(temp_file, false);
return false;
}
*to_path = temp_file;
return true;
}
using Events = UpdateClient::Observer::Events;
class MockObserver : public UpdateClient::Observer {
public:
MOCK_METHOD2(OnEvent, void(Events event, const std::string&));
};
} // namespace
using ::testing::_;
using ::testing::AtLeast;
using ::testing::AnyNumber;
using ::testing::DoAll;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::Mock;
using ::testing::Return;
using std::string;
class MockPingManagerImpl : public PingManager {
public:
struct PingData {
std::string id;
base::Version previous_version;
base::Version next_version;
ErrorCategory error_category = ErrorCategory::kNone;
int error_code = 0;
int extra_code1 = 0;
ErrorCategory diff_error_category = ErrorCategory::kNone;
int diff_error_code = 0;
bool diff_update_failed = false;
};
explicit MockPingManagerImpl(scoped_refptr<Configurator> config);
void SendPing(const Component& component, Callback callback) override;
const std::vector<PingData>& ping_data() const;
const std::vector<base::Value>& events() const;
protected:
~MockPingManagerImpl() override;
private:
std::vector<PingData> ping_data_;
std::vector<base::Value> events_;
DISALLOW_COPY_AND_ASSIGN(MockPingManagerImpl);
};
MockPingManagerImpl::MockPingManagerImpl(scoped_refptr<Configurator> config)
: PingManager(config) {}
MockPingManagerImpl::~MockPingManagerImpl() {}
void MockPingManagerImpl::SendPing(const Component& component,
Callback callback) {
PingData ping_data;
ping_data.id = component.id_;
ping_data.previous_version = component.previous_version_;
ping_data.next_version = component.next_version_;
ping_data.error_category = component.error_category_;
ping_data.error_code = component.error_code_;
ping_data.extra_code1 = component.extra_code1_;
ping_data.diff_error_category = component.diff_error_category_;
ping_data.diff_error_code = component.diff_error_code_;
ping_data.diff_update_failed = component.diff_update_failed();
ping_data_.push_back(ping_data);
events_ = component.GetEvents();
std::move(callback).Run(0, "");
}
const std::vector<MockPingManagerImpl::PingData>&
MockPingManagerImpl::ping_data() const {
return ping_data_;
}
const std::vector<base::Value>& MockPingManagerImpl::events() const {
return events_;
}
class UpdateClientTest : public testing::Test {
public:
UpdateClientTest();
~UpdateClientTest() override;
protected:
void RunThreads();
// Returns the full path to a test file.
static base::FilePath TestFilePath(const char* file);
scoped_refptr<update_client::TestConfigurator> config() { return config_; }
update_client::PersistedData* metadata() { return metadata_.get(); }
base::OnceClosure quit_closure() { return runloop_.QuitClosure(); }
private:
static constexpr int kNumWorkerThreads_ = 2;
base::test::ScopedTaskEnvironment scoped_task_environment_;
base::RunLoop runloop_;
#if defined(STARBOARD)
std::unique_ptr<cobalt::network::NetworkModule> network_module_;
#endif
scoped_refptr<update_client::TestConfigurator> config_ =
base::MakeRefCounted<TestConfigurator>();
std::unique_ptr<TestingPrefServiceSimple> pref_ =
std::make_unique<TestingPrefServiceSimple>();
std::unique_ptr<update_client::PersistedData> metadata_;
DISALLOW_COPY_AND_ASSIGN(UpdateClientTest);
};
constexpr int UpdateClientTest::kNumWorkerThreads_;
UpdateClientTest::UpdateClientTest() {
PersistedData::RegisterPrefs(pref_->registry());
metadata_ = std::make_unique<PersistedData>(pref_.get(), nullptr);
}
UpdateClientTest::~UpdateClientTest() {}
void UpdateClientTest::RunThreads() {
runloop_.Run();
scoped_task_environment_.RunUntilIdle();
}
base::FilePath UpdateClientTest::TestFilePath(const char* file) {
base::FilePath path;
base::PathService::Get(base::DIR_SOURCE_ROOT, &path);
return path.AppendASCII("components")
.AppendASCII("test")
.AppendASCII("data")
.AppendASCII("update_client")
.AppendASCII(file);
}
// Tests the scenario where one update check is done for one CRX. The CRX
// has no update.
TEST_F(UpdateClientTest, OneCrxNoUpdate) {
class DataCallbackMock {
public:
static std::vector<base::Optional<CrxComponent>> Callback(
const std::vector<std::string>& ids) {
CrxComponent crx;
crx.name = "test_jebg";
crx.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
crx.version = base::Version("0.9");
crx.installer = base::MakeRefCounted<TestInstaller>();
crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
std::vector<base::Optional<CrxComponent>> component = {crx};
return component;
}
};
class CompletionCallbackMock {
public:
static void Callback(base::OnceClosure quit_closure, Error error) {
EXPECT_EQ(Error::NONE, error);
std::move(quit_closure).Run();
}
};
class MockUpdateChecker : public UpdateChecker {
public:
static std::unique_ptr<UpdateChecker> Create(
scoped_refptr<Configurator> config,
PersistedData* metadata) {
#if defined(STARBOARD)
metadata_ = metadata;
#endif
return std::make_unique<MockUpdateChecker>();
}
void CheckForUpdates(
const std::string& session_id,
const std::vector<std::string>& ids_to_check,
const IdToComponentPtrMap& components,
const base::flat_map<std::string, std::string>& additional_attributes,
bool enabled_component_updates,
UpdateCheckCallback update_check_callback) override {
EXPECT_FALSE(session_id.empty());
EXPECT_TRUE(enabled_component_updates);
EXPECT_EQ(1u, ids_to_check.size());
const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
EXPECT_EQ(id, ids_to_check.front());
EXPECT_EQ(1u, components.count(id));
auto& component = components.at(id);
EXPECT_TRUE(component->is_foreground());
ProtocolParser::Result result;
result.extension_id = id;
result.status = "noupdate";
ProtocolParser::Results results;
results.list.push_back(result);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
ErrorCategory::kNone, 0, 0));
}
#if defined(STARBOARD)
PersistedData* GetPersistedData() override { return metadata_; }
private:
PersistedData* metadata_ = nullptr;
#endif
};
class MockCrxDownloader : public CrxDownloader {
public:
static std::unique_ptr<CrxDownloader> Create(
bool is_background_download,
scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
return std::make_unique<MockCrxDownloader>();
}
MockCrxDownloader() : CrxDownloader(nullptr) {}
private:
void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
};
class MockPingManager : public MockPingManagerImpl {
public:
explicit MockPingManager(scoped_refptr<Configurator> config)
: MockPingManagerImpl(config) {}
protected:
~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); }
};
scoped_refptr<UpdateClient> update_client =
base::MakeRefCounted<UpdateClientImpl>(
config(), base::MakeRefCounted<MockPingManager>(config()),
&MockUpdateChecker::Create, &MockCrxDownloader::Create);
MockObserver observer;
InSequence seq;
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_NOT_UPDATED,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
update_client->AddObserver(&observer);
const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf"};
update_client->Update(
ids, base::BindOnce(&DataCallbackMock::Callback), true,
base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
RunThreads();
update_client->RemoveObserver(&observer);
}
// Tests the scenario where two CRXs are checked for updates. On CRX has
// an update, the other CRX does not.
TEST_F(UpdateClientTest, TwoCrxUpdateNoUpdate) {
class DataCallbackMock {
public:
static std::vector<base::Optional<CrxComponent>> Callback(
const std::vector<std::string>& ids) {
CrxComponent crx1;
crx1.name = "test_jebg";
crx1.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
crx1.version = base::Version("0.9");
crx1.installer = base::MakeRefCounted<TestInstaller>();
crx1.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
CrxComponent crx2;
crx2.name = "test_abag";
crx2.pk_hash.assign(abag_hash, abag_hash + base::size(abag_hash));
crx2.version = base::Version("2.2");
crx2.installer = base::MakeRefCounted<TestInstaller>();
crx2.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
return {crx1, crx2};
}
};
class CompletionCallbackMock {
public:
static void Callback(base::OnceClosure quit_closure, Error error) {
EXPECT_EQ(Error::NONE, error);
std::move(quit_closure).Run();
}
};
class MockUpdateChecker : public UpdateChecker {
public:
static std::unique_ptr<UpdateChecker> Create(
scoped_refptr<Configurator> config,
PersistedData* metadata) {
return std::make_unique<MockUpdateChecker>();
}
void CheckForUpdates(
const std::string& session_id,
const std::vector<std::string>& ids_to_check,
const IdToComponentPtrMap& components,
const base::flat_map<std::string, std::string>& additional_attributes,
bool enabled_component_updates,
UpdateCheckCallback update_check_callback) override {
/*
Mock the following response:
<?xml version='1.0' encoding='UTF-8'?>
<response protocol='3.1'>
<app appid='jebgalgnebhfojomionfpkfelancnnkf'>
<updatecheck status='ok'>
<urls>
<url codebase='http://localhost/download/'/>
</urls>
<manifest version='1.0' prodversionmin='11.0.1.0'>
<packages>
<package name='jebgalgnebhfojomionfpkfelancnnkf.crx'
hash_sha256='6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd
7c9b12cb7cc067667bde87'/>
</packages>
</manifest>
</updatecheck>
</app>
<app appid='abagagagagagagagagagagagagagagag'>
<updatecheck status='noupdate'/>
</app>
</response>
*/
EXPECT_FALSE(session_id.empty());
EXPECT_TRUE(enabled_component_updates);
EXPECT_EQ(2u, ids_to_check.size());
ProtocolParser::Results results;
{
const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
EXPECT_EQ(id, ids_to_check[0]);
EXPECT_EQ(1u, components.count(id));
ProtocolParser::Result::Manifest::Package package;
package.name = "jebgalgnebhfojomionfpkfelancnnkf.crx";
package.hash_sha256 =
"6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd7c9b12cb7cc067667bde87";
ProtocolParser::Result result;
result.extension_id = "jebgalgnebhfojomionfpkfelancnnkf";
result.status = "ok";
result.crx_urls.push_back(GURL("http://localhost/download/"));
result.manifest.version = "1.0";
result.manifest.browser_min_version = "11.0.1.0";
result.manifest.packages.push_back(package);
results.list.push_back(result);
EXPECT_FALSE(components.at(id)->is_foreground());
}
{
const std::string id = "abagagagagagagagagagagagagagagag";
EXPECT_EQ(id, ids_to_check[1]);
EXPECT_EQ(1u, components.count(id));
ProtocolParser::Result result;
result.extension_id = id;
result.status = "noupdate";
results.list.push_back(result);
EXPECT_FALSE(components.at(id)->is_foreground());
}
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
ErrorCategory::kNone, 0, 0));
}
};
class MockCrxDownloader : public CrxDownloader {
public:
static std::unique_ptr<CrxDownloader> Create(
bool is_background_download,
scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
return std::make_unique<MockCrxDownloader>();
}
MockCrxDownloader() : CrxDownloader(nullptr) {}
private:
void DoStartDownload(const GURL& url) override {
DownloadMetrics download_metrics;
download_metrics.url = url;
download_metrics.downloader = DownloadMetrics::kNone;
download_metrics.error = 0;
download_metrics.downloaded_bytes = 1843;
download_metrics.total_bytes = 1843;
download_metrics.download_time_ms = 1000;
FilePath path;
EXPECT_TRUE(MakeTestFile(
TestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"), &path));
Result result;
result.error = 0;
result.response = path;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
base::Unretained(this)));
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
base::Unretained(this), true, result,
download_metrics));
}
};
class MockPingManager : public MockPingManagerImpl {
public:
explicit MockPingManager(scoped_refptr<Configurator> config)
: MockPingManagerImpl(config) {}
protected:
~MockPingManager() override {
const auto ping_data = MockPingManagerImpl::ping_data();
EXPECT_EQ(1u, ping_data.size());
EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
EXPECT_EQ(base::Version("0.9"), ping_data[0].previous_version);
EXPECT_EQ(base::Version("1.0"), ping_data[0].next_version);
EXPECT_EQ(0, static_cast<int>(ping_data[0].error_category));
EXPECT_EQ(0, ping_data[0].error_code);
}
};
scoped_refptr<UpdateClient> update_client =
base::MakeRefCounted<UpdateClientImpl>(
config(), base::MakeRefCounted<MockPingManager>(config()),
&MockUpdateChecker::Create, &MockCrxDownloader::Create);
MockObserver observer;
{
InSequence seq;
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(AtLeast(1));
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
}
{
InSequence seq;
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
"abagagagagagagagagagagagagagagag"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_NOT_UPDATED,
"abagagagagagagagagagagagagagagag"))
.Times(1);
}
update_client->AddObserver(&observer);
const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf",
"abagagagagagagagagagagagagagagag"};
update_client->Update(
ids, base::BindOnce(&DataCallbackMock::Callback), false,
base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
RunThreads();
update_client->RemoveObserver(&observer);
}
// Tests the scenario where two CRXs are checked for updates. One CRX has
// an update but the server ignores the second CRX and returns no response for
// it. The second component gets an |UPDATE_RESPONSE_NOT_FOUND| error and
// transitions to the error state.
TEST_F(UpdateClientTest, TwoCrxUpdateFirstServerIgnoresSecond) {
class DataCallbackMock {
public:
static std::vector<base::Optional<CrxComponent>> Callback(
const std::vector<std::string>& ids) {
CrxComponent crx1;
crx1.name = "test_jebg";
crx1.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
crx1.version = base::Version("0.9");
crx1.installer = base::MakeRefCounted<TestInstaller>();
crx1.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
CrxComponent crx2;
crx2.name = "test_abag";
crx2.pk_hash.assign(abag_hash, abag_hash + base::size(abag_hash));
crx2.version = base::Version("2.2");
crx2.installer = base::MakeRefCounted<TestInstaller>();
crx2.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
return {crx1, crx2};
}
};
class CompletionCallbackMock {
public:
static void Callback(base::OnceClosure quit_closure, Error error) {
EXPECT_EQ(Error::NONE, error);
std::move(quit_closure).Run();
}
};
class MockUpdateChecker : public UpdateChecker {
public:
static std::unique_ptr<UpdateChecker> Create(
scoped_refptr<Configurator> config,
PersistedData* metadata) {
return std::make_unique<MockUpdateChecker>();
}
void CheckForUpdates(
const std::string& session_id,
const std::vector<std::string>& ids_to_check,
const IdToComponentPtrMap& components,
const base::flat_map<std::string, std::string>& additional_attributes,
bool enabled_component_updates,
UpdateCheckCallback update_check_callback) override {
/*
Mock the following response:
<?xml version='1.0' encoding='UTF-8'?>
<response protocol='3.1'>
<app appid='jebgalgnebhfojomionfpkfelancnnkf'>
<updatecheck status='ok'>
<urls>
<url codebase='http://localhost/download/'/>
</urls>
<manifest version='1.0' prodversionmin='11.0.1.0'>
<packages>
<package name='jebgalgnebhfojomionfpkfelancnnkf.crx'
hash_sha256='6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd
7c9b12cb7cc067667bde87'/>
</packages>
</manifest>
</updatecheck>
</app>
</response>
*/
EXPECT_FALSE(session_id.empty());
EXPECT_TRUE(enabled_component_updates);
EXPECT_EQ(2u, ids_to_check.size());
ProtocolParser::Results results;
{
const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
EXPECT_EQ(id, ids_to_check[0]);
EXPECT_EQ(1u, components.count(id));
ProtocolParser::Result::Manifest::Package package;
package.name = "jebgalgnebhfojomionfpkfelancnnkf.crx";
package.hash_sha256 =
"6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd7c9b12cb7cc067667bde87";
ProtocolParser::Result result;
result.extension_id = "jebgalgnebhfojomionfpkfelancnnkf";
result.status = "ok";
result.crx_urls.push_back(GURL("http://localhost/download/"));
result.manifest.version = "1.0";
result.manifest.browser_min_version = "11.0.1.0";
result.manifest.packages.push_back(package);
results.list.push_back(result);
EXPECT_FALSE(components.at(id)->is_foreground());
}
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
ErrorCategory::kNone, 0, 0));
}
};
class MockCrxDownloader : public CrxDownloader {
public:
static std::unique_ptr<CrxDownloader> Create(
bool is_background_download,
scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
return std::make_unique<MockCrxDownloader>();
}
MockCrxDownloader() : CrxDownloader(nullptr) {}
private:
void DoStartDownload(const GURL& url) override {
DownloadMetrics download_metrics;
download_metrics.url = url;
download_metrics.downloader = DownloadMetrics::kNone;
download_metrics.error = 0;
download_metrics.downloaded_bytes = 1843;
download_metrics.total_bytes = 1843;
download_metrics.download_time_ms = 1000;
FilePath path;
EXPECT_TRUE(MakeTestFile(
TestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"), &path));
Result result;
result.error = 0;
result.response = path;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
base::Unretained(this)));
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
base::Unretained(this), true, result,
download_metrics));
}
};
class MockPingManager : public MockPingManagerImpl {
public:
explicit MockPingManager(scoped_refptr<Configurator> config)
: MockPingManagerImpl(config) {}
protected:
~MockPingManager() override {
const auto ping_data = MockPingManagerImpl::ping_data();
EXPECT_EQ(1u, ping_data.size());
EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
EXPECT_EQ(base::Version("0.9"), ping_data[0].previous_version);
EXPECT_EQ(base::Version("1.0"), ping_data[0].next_version);
EXPECT_EQ(0, static_cast<int>(ping_data[0].error_category));
EXPECT_EQ(0, ping_data[0].error_code);
}
};
scoped_refptr<UpdateClient> update_client =
base::MakeRefCounted<UpdateClientImpl>(
config(), base::MakeRefCounted<MockPingManager>(config()),
&MockUpdateChecker::Create, &MockCrxDownloader::Create);
MockObserver observer;
{
InSequence seq;
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(AtLeast(1));
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
}
{
InSequence seq;
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
"abagagagagagagagagagagagagagagag"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
"abagagagagagagagagagagagagagagag"))
.Times(1)
.WillOnce(Invoke([&update_client](Events event, const std::string& id) {
CrxUpdateItem item;
EXPECT_TRUE(update_client->GetCrxUpdateState(id, &item));
EXPECT_EQ(ComponentState::kUpdateError, item.state);
EXPECT_EQ(5, static_cast<int>(item.error_category));
EXPECT_EQ(-10004, item.error_code);
EXPECT_EQ(0, item.extra_code1);
}));
}
update_client->AddObserver(&observer);
const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf",
"abagagagagagagagagagagagagagagag"};
update_client->Update(
ids, base::BindOnce(&DataCallbackMock::Callback), false,
base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
RunThreads();
update_client->RemoveObserver(&observer);
}
// Tests the update check for two CRXs scenario when the second CRX does not
// provide a CrxComponent instance. In this case, the update is handled as
// if only one component were provided as an argument to the |Update| call
// with the exception that the second component still fires an event such as
// |COMPONENT_UPDATE_ERROR|.
TEST_F(UpdateClientTest, TwoCrxUpdateNoCrxComponentData) {
class DataCallbackMock {
public:
static std::vector<base::Optional<CrxComponent>> Callback(
const std::vector<std::string>& ids) {
CrxComponent crx;
crx.name = "test_jebg";
crx.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
crx.version = base::Version("0.9");
crx.installer = base::MakeRefCounted<TestInstaller>();
crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
return {crx, base::nullopt};
}
};
class CompletionCallbackMock {
public:
static void Callback(base::OnceClosure quit_closure, Error error) {
EXPECT_EQ(Error::NONE, error);
std::move(quit_closure).Run();
}
};
class MockUpdateChecker : public UpdateChecker {
public:
static std::unique_ptr<UpdateChecker> Create(
scoped_refptr<Configurator> config,
PersistedData* metadata) {
return std::make_unique<MockUpdateChecker>();
}
void CheckForUpdates(
const std::string& session_id,
const std::vector<std::string>& ids_to_check,
const IdToComponentPtrMap& components,
const base::flat_map<std::string, std::string>& additional_attributes,
bool enabled_component_updates,
UpdateCheckCallback update_check_callback) override {
/*
Mock the following response:
<?xml version='1.0' encoding='UTF-8'?>
<response protocol='3.1'>
<app appid='jebgalgnebhfojomionfpkfelancnnkf'>
<updatecheck status='ok'>
<urls>
<url codebase='http://localhost/download/'/>
</urls>
<manifest version='1.0' prodversionmin='11.0.1.0'>
<packages>
<package name='jebgalgnebhfojomionfpkfelancnnkf.crx'
hash_sha256='6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd
7c9b12cb7cc067667bde87'/>
</packages>
</manifest>
</updatecheck>
</app>
</response>
*/
EXPECT_FALSE(session_id.empty());
EXPECT_TRUE(enabled_component_updates);
EXPECT_EQ(1u, ids_to_check.size());
ProtocolParser::Results results;
{
const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
EXPECT_EQ(id, ids_to_check[0]);
EXPECT_EQ(1u, components.count(id));
ProtocolParser::Result::Manifest::Package package;
package.name = "jebgalgnebhfojomionfpkfelancnnkf.crx";
package.hash_sha256 =
"6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd7c9b12cb7cc067667bde87";
ProtocolParser::Result result;
result.extension_id = id;
result.status = "ok";
result.crx_urls.push_back(GURL("http://localhost/download/"));
result.manifest.version = "1.0";
result.manifest.browser_min_version = "11.0.1.0";
result.manifest.packages.push_back(package);
results.list.push_back(result);
EXPECT_FALSE(components.at(id)->is_foreground());
}
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
ErrorCategory::kNone, 0, 0));
}
};
class MockCrxDownloader : public CrxDownloader {
public:
static std::unique_ptr<CrxDownloader> Create(
bool is_background_download,
scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
return std::make_unique<MockCrxDownloader>();
}
MockCrxDownloader() : CrxDownloader(nullptr) {}
private:
void DoStartDownload(const GURL& url) override {
DownloadMetrics download_metrics;
FilePath path;
Result result;
if (url.path() == "/download/jebgalgnebhfojomionfpkfelancnnkf.crx") {
download_metrics.url = url;
download_metrics.downloader = DownloadMetrics::kNone;
download_metrics.error = 0;
download_metrics.downloaded_bytes = 1843;
download_metrics.total_bytes = 1843;
download_metrics.download_time_ms = 1000;
EXPECT_TRUE(MakeTestFile(
TestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"), &path));
result.error = 0;
result.response = path;
} else {
NOTREACHED();
}
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
base::Unretained(this)));
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
base::Unretained(this), true, result,
download_metrics));
}
};
class MockPingManager : public MockPingManagerImpl {
public:
explicit MockPingManager(scoped_refptr<Configurator> config)
: MockPingManagerImpl(config) {}
protected:
~MockPingManager() override {
const auto ping_data = MockPingManagerImpl::ping_data();
EXPECT_EQ(1u, ping_data.size());
EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
EXPECT_EQ(base::Version("0.9"), ping_data[0].previous_version);
EXPECT_EQ(base::Version("1.0"), ping_data[0].next_version);
EXPECT_EQ(0, static_cast<int>(ping_data[0].error_category));
EXPECT_EQ(0, ping_data[0].error_code);
}
};
scoped_refptr<UpdateClient> update_client =
base::MakeRefCounted<UpdateClientImpl>(
config(), base::MakeRefCounted<MockPingManager>(config()),
&MockUpdateChecker::Create, &MockCrxDownloader::Create);
MockObserver observer;
{
InSequence seq;
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(AtLeast(1));
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
}
{
InSequence seq;
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(1);
}
update_client->AddObserver(&observer);
const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf",
"ihfokbkgjpifnbbojhneepfflplebdkc"};
update_client->Update(
ids, base::BindOnce(&DataCallbackMock::Callback), false,
base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
RunThreads();
update_client->RemoveObserver(&observer);
}
// Tests the update check for two CRXs scenario when no CrxComponent data is
// provided for either component. In this case, no update check occurs, and
// |COMPONENT_UPDATE_ERROR| event fires for both components.
TEST_F(UpdateClientTest, TwoCrxUpdateNoCrxComponentDataAtAll) {
class DataCallbackMock {
public:
static std::vector<base::Optional<CrxComponent>> Callback(
const std::vector<std::string>& ids) {
return {base::nullopt, base::nullopt};
}
};
class CompletionCallbackMock {
public:
static void Callback(base::OnceClosure quit_closure, Error error) {
EXPECT_EQ(Error::NONE, error);
std::move(quit_closure).Run();
}
};
class MockUpdateChecker : public UpdateChecker {
public:
static std::unique_ptr<UpdateChecker> Create(
scoped_refptr<Configurator> config,
PersistedData* metadata) {
return std::make_unique<MockUpdateChecker>();
}
void CheckForUpdates(
const std::string& session_id,
const std::vector<std::string>& ids_to_check,
const IdToComponentPtrMap& components,
const base::flat_map<std::string, std::string>& additional_attributes,
bool enabled_component_updates,
UpdateCheckCallback update_check_callback) override {
NOTREACHED();
}
};
class MockCrxDownloader : public CrxDownloader {
public:
static std::unique_ptr<CrxDownloader> Create(
bool is_background_download,
scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
return std::make_unique<MockCrxDownloader>();
}
MockCrxDownloader() : CrxDownloader(nullptr) {}
private:
void DoStartDownload(const GURL& url) override { NOTREACHED(); }
};
class MockPingManager : public MockPingManagerImpl {
public:
explicit MockPingManager(scoped_refptr<Configurator> config)
: MockPingManagerImpl(config) {}
protected:
~MockPingManager() override {
EXPECT_EQ(0u, MockPingManagerImpl::ping_data().size());
}
};
scoped_refptr<UpdateClient> update_client =
base::MakeRefCounted<UpdateClientImpl>(
config(), base::MakeRefCounted<MockPingManager>(config()),
&MockUpdateChecker::Create, &MockCrxDownloader::Create);
MockObserver observer;
{
InSequence seq;
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(1);
}
update_client->AddObserver(&observer);
const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf",
"ihfokbkgjpifnbbojhneepfflplebdkc"};
update_client->Update(
ids, base::BindOnce(&DataCallbackMock::Callback), false,
base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
RunThreads();
update_client->RemoveObserver(&observer);
}
// Tests the scenario where there is a download timeout for the first
// CRX. The update for the first CRX fails. The update client waits before
// attempting the update for the second CRX. This update succeeds.
TEST_F(UpdateClientTest, TwoCrxUpdateDownloadTimeout) {
class DataCallbackMock {
public:
static std::vector<base::Optional<CrxComponent>> Callback(
const std::vector<std::string>& ids) {
CrxComponent crx1;
crx1.name = "test_jebg";
crx1.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
crx1.version = base::Version("0.9");
crx1.installer = base::MakeRefCounted<TestInstaller>();
crx1.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
CrxComponent crx2;
crx2.name = "test_ihfo";
crx2.pk_hash.assign(ihfo_hash, ihfo_hash + base::size(ihfo_hash));
crx2.version = base::Version("0.8");
crx2.installer = base::MakeRefCounted<TestInstaller>();
crx2.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
return {crx1, crx2};
}
};
class CompletionCallbackMock {
public:
static void Callback(base::OnceClosure quit_closure, Error error) {
EXPECT_EQ(Error::NONE, error);
std::move(quit_closure).Run();
}
};
class MockUpdateChecker : public UpdateChecker {
public:
static std::unique_ptr<UpdateChecker> Create(
scoped_refptr<Configurator> config,
PersistedData* metadata) {
return std::make_unique<MockUpdateChecker>();
}
void CheckForUpdates(
const std::string& session_id,
const std::vector<std::string>& ids_to_check,
const IdToComponentPtrMap& components,
const base::flat_map<std::string, std::string>& additional_attributes,
bool enabled_component_updates,
UpdateCheckCallback update_check_callback) override {
/*
Mock the following response:
<?xml version='1.0' encoding='UTF-8'?>
<response protocol='3.1'>
<app appid='jebgalgnebhfojomionfpkfelancnnkf'>
<updatecheck status='ok'>
<urls>
<url codebase='http://localhost/download/'/>
</urls>
<manifest version='1.0' prodversionmin='11.0.1.0'>
<packages>
<package name='jebgalgnebhfojomionfpkfelancnnkf.crx'
hash_sha256='6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd
7c9b12cb7cc067667bde87'/>
</packages>
</manifest>
</updatecheck>
</app>
<app appid='ihfokbkgjpifnbbojhneepfflplebdkc'>
<updatecheck status='ok'>
<urls>
<url codebase='http://localhost/download/'/>
</urls>
<manifest version='1.0' prodversionmin='11.0.1.0'>
<packages>
<package name='ihfokbkgjpifnbbojhneepfflplebdkc_1.crx'
hash_sha256='813c59747e139a608b3b5fc49633affc6db574373f
309f156ea6d27229c0b3f9'/>
</packages>
</manifest>
</updatecheck>
</app>
</response>
*/
EXPECT_FALSE(session_id.empty());
EXPECT_TRUE(enabled_component_updates);
EXPECT_EQ(2u, ids_to_check.size());
ProtocolParser::Results results;
{
const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
EXPECT_EQ(id, ids_to_check[0]);
EXPECT_EQ(1u, components.count(id));
ProtocolParser::Result::Manifest::Package package;
package.name = "jebgalgnebhfojomionfpkfelancnnkf.crx";
package.hash_sha256 =
"6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd7c9b12cb7cc067667bde87";
ProtocolParser::Result result;
result.extension_id = id;
result.status = "ok";
result.crx_urls.push_back(GURL("http://localhost/download/"));
result.manifest.version = "1.0";
result.manifest.browser_min_version = "11.0.1.0";
result.manifest.packages.push_back(package);
results.list.push_back(result);
}
{
const std::string id = "ihfokbkgjpifnbbojhneepfflplebdkc";
EXPECT_EQ(id, ids_to_check[1]);
EXPECT_EQ(1u, components.count(id));
ProtocolParser::Result::Manifest::Package package;
package.name = "ihfokbkgjpifnbbojhneepfflplebdkc_1.crx";
package.hash_sha256 =
"813c59747e139a608b3b5fc49633affc6db574373f309f156ea6d27229c0b3f9";
ProtocolParser::Result result;
result.extension_id = id;
result.status = "ok";
result.crx_urls.push_back(GURL("http://localhost/download/"));
result.manifest.version = "1.0";
result.manifest.browser_min_version = "11.0.1.0";
result.manifest.packages.push_back(package);
results.list.push_back(result);
}
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
ErrorCategory::kNone, 0, 0));
}
};
class MockCrxDownloader : public CrxDownloader {
public:
static std::unique_ptr<CrxDownloader> Create(
bool is_background_download,
scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
return std::make_unique<MockCrxDownloader>();
}
MockCrxDownloader() : CrxDownloader(nullptr) {}
private:
void DoStartDownload(const GURL& url) override {
DownloadMetrics download_metrics;
FilePath path;
Result result;
if (url.path() == "/download/jebgalgnebhfojomionfpkfelancnnkf.crx") {
download_metrics.url = url;
download_metrics.downloader = DownloadMetrics::kNone;
download_metrics.error = -118;
download_metrics.downloaded_bytes = 0;
download_metrics.total_bytes = 0;
download_metrics.download_time_ms = 1000;
// The result must not include a file path in the case of errors.
result.error = -118;
} else if (url.path() ==
"/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx") {
download_metrics.url = url;
download_metrics.downloader = DownloadMetrics::kNone;
download_metrics.error = 0;
download_metrics.downloaded_bytes = 53638;
download_metrics.total_bytes = 53638;
download_metrics.download_time_ms = 2000;
EXPECT_TRUE(MakeTestFile(
TestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"), &path));
result.error = 0;
result.response = path;
} else {
NOTREACHED();
}
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
base::Unretained(this)));
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
base::Unretained(this), true, result,
download_metrics));
}
};
class MockPingManager : public MockPingManagerImpl {
public:
explicit MockPingManager(scoped_refptr<Configurator> config)
: MockPingManagerImpl(config) {}
protected:
~MockPingManager() override {
const auto ping_data = MockPingManagerImpl::ping_data();
EXPECT_EQ(2u, ping_data.size());
EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
EXPECT_EQ(base::Version("0.9"), ping_data[0].previous_version);
EXPECT_EQ(base::Version("1.0"), ping_data[0].next_version);
EXPECT_EQ(1, static_cast<int>(ping_data[0].error_category));
EXPECT_EQ(-118, ping_data[0].error_code);
EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", ping_data[1].id);
EXPECT_EQ(base::Version("0.8"), ping_data[1].previous_version);
EXPECT_EQ(base::Version("1.0"), ping_data[1].next_version);
EXPECT_EQ(0, static_cast<int>(ping_data[1].error_category));
EXPECT_EQ(0, ping_data[1].error_code);
}
};
scoped_refptr<UpdateClient> update_client =
base::MakeRefCounted<UpdateClientImpl>(
config(), base::MakeRefCounted<MockPingManager>(config()),
&MockUpdateChecker::Create, &MockCrxDownloader::Create);
MockObserver observer;
{
InSequence seq;
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(AtLeast(1));
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1)
.WillOnce(Invoke([&update_client](Events event, const std::string& id) {
CrxUpdateItem item;
EXPECT_TRUE(update_client->GetCrxUpdateState(id, &item));
EXPECT_EQ(ComponentState::kUpdateError, item.state);
EXPECT_EQ(1, static_cast<int>(item.error_category));
EXPECT_EQ(-118, item.error_code);
EXPECT_EQ(0, item.extra_code1);
}));
}
{
InSequence seq;
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_WAIT,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(AtLeast(1));
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(1);
}
update_client->AddObserver(&observer);
const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf",
"ihfokbkgjpifnbbojhneepfflplebdkc"};
update_client->Update(
ids, base::BindOnce(&DataCallbackMock::Callback), false,
base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
RunThreads();
update_client->RemoveObserver(&observer);
}
// Tests the differential update scenario for one CRX.
TEST_F(UpdateClientTest, OneCrxDiffUpdate) {
class DataCallbackMock {
public:
static std::vector<base::Optional<CrxComponent>> Callback(
const std::vector<std::string>& ids) {
static int num_calls = 0;
// Must use the same stateful installer object.
static scoped_refptr<CrxInstaller> installer =
base::MakeRefCounted<VersionedTestInstaller>();
++num_calls;
CrxComponent crx;
crx.name = "test_ihfo";
crx.pk_hash.assign(ihfo_hash, ihfo_hash + base::size(ihfo_hash));
crx.installer = installer;
crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
if (num_calls == 1) {
crx.version = base::Version("0.8");
} else if (num_calls == 2) {
crx.version = base::Version("1.0");
} else {
NOTREACHED();
}
return {crx};
}
};
class CompletionCallbackMock {
public:
static void Callback(base::OnceClosure quit_closure, Error error) {
EXPECT_EQ(Error::NONE, error);
std::move(quit_closure).Run();
}
};
class MockUpdateChecker : public UpdateChecker {
public:
static std::unique_ptr<UpdateChecker> Create(
scoped_refptr<Configurator> config,
PersistedData* metadata) {
return std::make_unique<MockUpdateChecker>();
}
void CheckForUpdates(
const std::string& session_id,
const std::vector<std::string>& ids_to_check,
const IdToComponentPtrMap& components,
const base::flat_map<std::string, std::string>& additional_attributes,
bool enabled_component_updates,
UpdateCheckCallback update_check_callback) override {
EXPECT_FALSE(session_id.empty());
static int num_call = 0;
++num_call;
ProtocolParser::Results results;
if (num_call == 1) {
/*
Mock the following response:
<?xml version='1.0' encoding='UTF-8'?>
<response protocol='3.1'>
<app appid='ihfokbkgjpifnbbojhneepfflplebdkc'>
<updatecheck status='ok'>
<urls>
<url codebase='http://localhost/download/'/>
</urls>
<manifest version='1.0' prodversionmin='11.0.1.0'>
<packages>
<package name='ihfokbkgjpifnbbojhneepfflplebdkc_1.crx'
hash_sha256='813c59747e139a608b3b5fc49633affc6db57437
3f309f156ea6d27229c0b3f9'/>
</packages>
</manifest>
</updatecheck>
</app>
</response>
*/
const std::string id = "ihfokbkgjpifnbbojhneepfflplebdkc";
EXPECT_EQ(id, ids_to_check[0]);
EXPECT_EQ(1u, components.count(id));
ProtocolParser::Result::Manifest::Package package;
package.name = "ihfokbkgjpifnbbojhneepfflplebdkc_1.crx";
package.hash_sha256 =
"813c59747e139a608b3b5fc49633affc6db574373f309f156ea6d27229c0b3f9";
ProtocolParser::Result result;
result.extension_id = id;
result.status = "ok";
result.crx_urls.push_back(GURL("http://localhost/download/"));
result.manifest.version = "1.0";
result.manifest.browser_min_version = "11.0.1.0";
result.manifest.packages.push_back(package);
results.list.push_back(result);
} else if (num_call == 2) {
/*
Mock the following response:
<?xml version='1.0' encoding='UTF-8'?>
<response protocol='3.1'>
<app appid='ihfokbkgjpifnbbojhneepfflplebdkc'>
<updatecheck status='ok'>
<urls>
<url codebase='http://localhost/download/'/>
<url codebasediff='http://localhost/download/'/>
</urls>
<manifest version='2.0' prodversionmin='11.0.1.0'>
<packages>
<package name='ihfokbkgjpifnbbojhneepfflplebdkc_2.crx'
namediff='ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx'
hash_sha256='1af337fbd19c72db0f870753bcd7711c3ae9dcaa
0ecde26c262bad942b112990'
fp='22'
hashdiff_sha256='73c6e2d4f783fc4ca5481e89e0b8bfce7aec
8ead3686290c94792658ec06f2f2'/>
</packages>
</manifest>
</updatecheck>
</app>
</response>
*/
const std::string id = "ihfokbkgjpifnbbojhneepfflplebdkc";
EXPECT_EQ(id, ids_to_check[0]);
EXPECT_EQ(1u, components.count(id));
ProtocolParser::Result::Manifest::Package package;
package.name = "ihfokbkgjpifnbbojhneepfflplebdkc_2.crx";
package.namediff = "ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx";
package.hash_sha256 =
"1af337fbd19c72db0f870753bcd7711c3ae9dcaa0ecde26c262bad942b112990";
package.hashdiff_sha256 =
"73c6e2d4f783fc4ca5481e89e0b8bfce7aec8ead3686290c94792658ec06f2f2";
package.fingerprint = "22";
ProtocolParser::Result result;
result.extension_id = id;
result.status = "ok";
result.crx_urls.push_back(GURL("http://localhost/download/"));
result.crx_diffurls.push_back(GURL("http://localhost/download/"));
result.manifest.version = "2.0";
result.manifest.browser_min_version = "11.0.1.0";
result.manifest.packages.push_back(package);
results.list.push_back(result);
} else {
NOTREACHED();
}
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
ErrorCategory::kNone, 0, 0));
}
};
class MockCrxDownloader : public CrxDownloader {
public:
static std::unique_ptr<CrxDownloader> Create(
bool is_background_download,
scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
return std::make_unique<MockCrxDownloader>();
}
MockCrxDownloader() : CrxDownloader(nullptr) {}
private:
void DoStartDownload(const GURL& url) override {
DownloadMetrics download_metrics;
FilePath path;
Result result;
if (url.path() == "/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx") {
download_metrics.url = url;
download_metrics.downloader = DownloadMetrics::kNone;
download_metrics.error = 0;
download_metrics.downloaded_bytes = 53638;
download_metrics.total_bytes = 53638;
download_metrics.download_time_ms = 2000;
EXPECT_TRUE(MakeTestFile(
TestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"), &path));
result.error = 0;
result.response = path;
} else if (url.path() ==
"/download/ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx") {
download_metrics.url = url;
download_metrics.downloader = DownloadMetrics::kNone;
download_metrics.error = 0;
download_metrics.downloaded_bytes = 2105;
download_metrics.total_bytes = 2105;
download_metrics.download_time_ms = 1000;
EXPECT_TRUE(MakeTestFile(
TestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx"), &path));
result.error = 0;
result.response = path;
} else {
NOTREACHED();
}
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
base::Unretained(this)));
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
base::Unretained(this), true, result,
download_metrics));
}
};
class MockPingManager : public MockPingManagerImpl {
public:
explicit MockPingManager(scoped_refptr<Configurator> config)
: MockPingManagerImpl(config) {}
protected:
~MockPingManager() override {
const auto ping_data = MockPingManagerImpl::ping_data();
EXPECT_EQ(2u, ping_data.size());
EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", ping_data[0].id);
EXPECT_EQ(base::Version("0.8"), ping_data[0].previous_version);
EXPECT_EQ(base::Version("1.0"), ping_data[0].next_version);
EXPECT_EQ(0, static_cast<int>(ping_data[0].error_category));
EXPECT_EQ(0, ping_data[0].error_code);
EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", ping_data[1].id);
EXPECT_EQ(base::Version("1.0"), ping_data[1].previous_version);
EXPECT_EQ(base::Version("2.0"), ping_data[1].next_version);
EXPECT_FALSE(ping_data[1].diff_update_failed);
EXPECT_EQ(0, static_cast<int>(ping_data[1].diff_error_category));
EXPECT_EQ(0, ping_data[1].diff_error_code);
EXPECT_EQ(0, static_cast<int>(ping_data[1].error_category));
EXPECT_EQ(0, ping_data[1].error_code);
}
};
scoped_refptr<UpdateClient> update_client =
base::MakeRefCounted<UpdateClientImpl>(
config(), base::MakeRefCounted<MockPingManager>(config()),
&MockUpdateChecker::Create, &MockCrxDownloader::Create);
MockObserver observer;
{
InSequence seq;
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(AtLeast(1));
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(AtLeast(1));
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(1);
}
update_client->AddObserver(&observer);
const std::vector<std::string> ids = {"ihfokbkgjpifnbbojhneepfflplebdkc"};
{
base::RunLoop runloop;
update_client->Update(ids, base::BindOnce(&DataCallbackMock::Callback),
false,
base::BindOnce(&CompletionCallbackMock::Callback,
runloop.QuitClosure()));
runloop.Run();
}
{
base::RunLoop runloop;
update_client->Update(ids, base::BindOnce(&DataCallbackMock::Callback),
false,
base::BindOnce(&CompletionCallbackMock::Callback,
runloop.QuitClosure()));
runloop.Run();
}
update_client->RemoveObserver(&observer);
}
// Tests the update scenario for one CRX where the CRX installer returns
// an error. Tests that the |unpack_path| argument refers to a valid path
// then |Install| is called, then tests that the |unpack| path is deleted
// by the |update_client| code before the test ends.
TEST_F(UpdateClientTest, OneCrxInstallError) {
class MockInstaller : public CrxInstaller {
public:
MOCK_METHOD1(OnUpdateError, void(int error));
MOCK_METHOD2(DoInstall,
void(const base::FilePath& unpack_path,
const Callback& callback));
MOCK_METHOD2(GetInstalledFile,
bool(const std::string& file, base::FilePath* installed_file));
MOCK_METHOD0(Uninstall, bool());
void Install(const base::FilePath& unpack_path,
const std::string& public_key,
Callback callback) override {
DoInstall(unpack_path, std::move(callback));
unpack_path_ = unpack_path;
EXPECT_TRUE(base::DirectoryExists(unpack_path_));
base::PostTaskWithTraits(
FROM_HERE, {base::MayBlock()},
base::BindOnce(std::move(callback),
CrxInstaller::Result(InstallError::GENERIC_ERROR)));
}
protected:
~MockInstaller() override {
// The unpack path is deleted unconditionally by the component state code,
// which is driving this installer. Therefore, the unpack path must not
// exist when this object is destroyed.
if (!unpack_path_.empty())
EXPECT_FALSE(base::DirectoryExists(unpack_path_));
}
private:
// Contains the |unpack_path| argument of the Install call.
base::FilePath unpack_path_;
};
class DataCallbackMock {
public:
static std::vector<base::Optional<CrxComponent>> Callback(
const std::vector<std::string>& ids) {
scoped_refptr<MockInstaller> installer =
base::MakeRefCounted<MockInstaller>();
EXPECT_CALL(*installer, OnUpdateError(_)).Times(0);
EXPECT_CALL(*installer, DoInstall(_, _)).Times(1);
EXPECT_CALL(*installer, GetInstalledFile(_, _)).Times(0);
EXPECT_CALL(*installer, Uninstall()).Times(0);
CrxComponent crx;
crx.name = "test_jebg";
crx.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
crx.version = base::Version("0.9");
crx.installer = installer;
crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
return {crx};
}
};
class CompletionCallbackMock {
public:
static void Callback(base::OnceClosure quit_closure, Error error) {
EXPECT_EQ(Error::NONE, error);
std::move(quit_closure).Run();
}
};
class MockUpdateChecker : public UpdateChecker {
public:
static std::unique_ptr<UpdateChecker> Create(
scoped_refptr<Configurator> config,
PersistedData* metadata) {
return std::make_unique<MockUpdateChecker>();
}
void CheckForUpdates(
const std::string& session_id,
const std::vector<std::string>& ids_to_check,
const IdToComponentPtrMap& components,
const base::flat_map<std::string, std::string>& additional_attributes,
bool enabled_component_updates,
UpdateCheckCallback update_check_callback) override {
/*
Mock the following response:
<?xml version='1.0' encoding='UTF-8'?>
<response protocol='3.1'>
<app appid='jebgalgnebhfojomionfpkfelancnnkf'>
<updatecheck status='ok'>
<urls>
<url codebase='http://localhost/download/'/>
</urls>
<manifest version='1.0' prodversionmin='11.0.1.0'>
<packages>
<package name='jebgalgnebhfojomionfpkfelancnnkf.crx'
hash_sha256='6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd
7c9b12cb7cc067667bde87'/>
</packages>
</manifest>
</updatecheck>
</app>
</response>
*/
EXPECT_FALSE(session_id.empty());
const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
EXPECT_EQ(id, ids_to_check[0]);
EXPECT_EQ(1u, components.count(id));
ProtocolParser::Result::Manifest::Package package;
package.name = "jebgalgnebhfojomionfpkfelancnnkf.crx";
package.hash_sha256 =
"6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd7c9b12cb7cc067667bde87";
ProtocolParser::Result result;
result.extension_id = id;
result.status = "ok";
result.crx_urls.push_back(GURL("http://localhost/download/"));
result.manifest.version = "1.0";
result.manifest.browser_min_version = "11.0.1.0";
result.manifest.packages.push_back(package);
ProtocolParser::Results results;
results.list.push_back(result);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
ErrorCategory::kNone, 0, 0));
}
};
class MockCrxDownloader : public CrxDownloader {
public:
static std::unique_ptr<CrxDownloader> Create(
bool is_background_download,
scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
return std::make_unique<MockCrxDownloader>();
}
MockCrxDownloader() : CrxDownloader(nullptr) {}
private:
void DoStartDownload(const GURL& url) override {
DownloadMetrics download_metrics;
download_metrics.url = url;
download_metrics.downloader = DownloadMetrics::kNone;
download_metrics.error = 0;
download_metrics.downloaded_bytes = 1843;
download_metrics.total_bytes = 1843;
download_metrics.download_time_ms = 1000;
FilePath path;
EXPECT_TRUE(MakeTestFile(
TestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"), &path));
Result result;
result.error = 0;
result.response = path;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
base::Unretained(this)));
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
base::Unretained(this), true, result,
download_metrics));
}
};
class MockPingManager : public MockPingManagerImpl {
public:
explicit MockPingManager(scoped_refptr<Configurator> config)
: MockPingManagerImpl(config) {}
protected:
~MockPingManager() override {
const auto ping_data = MockPingManagerImpl::ping_data();
EXPECT_EQ(1u, ping_data.size());
EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
EXPECT_EQ(base::Version("0.9"), ping_data[0].previous_version);
EXPECT_EQ(base::Version("1.0"), ping_data[0].next_version);
EXPECT_EQ(3, static_cast<int>(ping_data[0].error_category)); // kInstall.
EXPECT_EQ(9, ping_data[0].error_code); // kInstallerError.
}
};
scoped_refptr<UpdateClient> update_client =
base::MakeRefCounted<UpdateClientImpl>(
config(), base::MakeRefCounted<MockPingManager>(config()),
&MockUpdateChecker::Create, &MockCrxDownloader::Create);
MockObserver observer;
{
InSequence seq;
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(AtLeast(1));
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
}
update_client->AddObserver(&observer);
std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf"};
update_client->Update(
ids, base::BindOnce(&DataCallbackMock::Callback), false,
base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
RunThreads();
update_client->RemoveObserver(&observer);
}
// Tests the fallback from differential to full update scenario for one CRX.
TEST_F(UpdateClientTest, OneCrxDiffUpdateFailsFullUpdateSucceeds) {
class DataCallbackMock {
public:
static std::vector<base::Optional<CrxComponent>> Callback(
const std::vector<std::string>& ids) {
static int num_calls = 0;
// Must use the same stateful installer object.
static scoped_refptr<CrxInstaller> installer =
base::MakeRefCounted<VersionedTestInstaller>();
++num_calls;
CrxComponent crx;
crx.name = "test_ihfo";
crx.pk_hash.assign(ihfo_hash, ihfo_hash + base::size(ihfo_hash));
crx.installer = installer;
crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
if (num_calls == 1) {
crx.version = base::Version("0.8");
} else if (num_calls == 2) {
crx.version = base::Version("1.0");
} else {
NOTREACHED();
}
return {crx};
}
};
class CompletionCallbackMock {
public:
static void Callback(base::OnceClosure quit_closure, Error error) {
EXPECT_EQ(Error::NONE, error);
std::move(quit_closure).Run();
}
};
class MockUpdateChecker : public UpdateChecker {
public:
static std::unique_ptr<UpdateChecker> Create(
scoped_refptr<Configurator> config,
PersistedData* metadata) {
return std::make_unique<MockUpdateChecker>();
}
void CheckForUpdates(
const std::string& session_id,
const std::vector<std::string>& ids_to_check,
const IdToComponentPtrMap& components,
const base::flat_map<std::string, std::string>& additional_attributes,
bool enabled_component_updates,
UpdateCheckCallback update_check_callback) override {
EXPECT_FALSE(session_id.empty());
static int num_call = 0;
++num_call;
ProtocolParser::Results results;
if (num_call == 1) {
/*
Mock the following response:
<?xml version='1.0' encoding='UTF-8'?>
<response protocol='3.1'>
<app appid='ihfokbkgjpifnbbojhneepfflplebdkc'>
<updatecheck status='ok'>
<urls>
<url codebase='http://localhost/download/'/>
</urls>
<manifest version='1.0' prodversionmin='11.0.1.0'>
<packages>
<package name='ihfokbkgjpifnbbojhneepfflplebdkc_1.crx'
hash_sha256='813c59747e139a608b3b5fc49633affc6db57437
3f309f156ea6d27229c0b3f9'
fp='1'/>
</packages>
</manifest>
</updatecheck>
</app>
</response>
*/
const std::string id = "ihfokbkgjpifnbbojhneepfflplebdkc";
EXPECT_EQ(id, ids_to_check[0]);
EXPECT_EQ(1u, components.count(id));
ProtocolParser::Result::Manifest::Package package;
package.name = "ihfokbkgjpifnbbojhneepfflplebdkc_1.crx";
package.hash_sha256 =
"813c59747e139a608b3b5fc49633affc6db574373f309f156ea6d27229c0b3f9";
package.fingerprint = "1";
ProtocolParser::Result result;
result.extension_id = id;
result.status = "ok";
result.crx_urls.push_back(GURL("http://localhost/download/"));
result.manifest.version = "1.0";
result.manifest.browser_min_version = "11.0.1.0";
result.manifest.packages.push_back(package);
results.list.push_back(result);
} else if (num_call == 2) {
/*
Mock the following response:
<?xml version='1.0' encoding='UTF-8'?>
<response protocol='3.1'>
<app appid='ihfokbkgjpifnbbojhneepfflplebdkc'>
<updatecheck status='ok'>
<urls>
<url codebase='http://localhost/download/'/>
<url codebasediff='http://localhost/download/'/>
</urls>
<manifest version='2.0' prodversionmin='11.0.1.0'>
<packages>
<package name='ihfokbkgjpifnbbojhneepfflplebdkc_2.crx'
namediff='ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx'
hash_sha256='1af337fbd19c72db0f870753bcd7711c3ae9dcaa
0ecde26c262bad942b112990'
fp='22'
hashdiff_sha256='73c6e2d4f783fc4ca5481e89e0b8bfce7aec
8ead3686290c94792658ec06f2f2'/>
</packages>
</manifest>
</updatecheck>
</app>
</response>
*/
const std::string id = "ihfokbkgjpifnbbojhneepfflplebdkc";
EXPECT_EQ(id, ids_to_check[0]);
EXPECT_EQ(1u, components.count(id));
ProtocolParser::Result::Manifest::Package package;
package.name = "ihfokbkgjpifnbbojhneepfflplebdkc_2.crx";
package.namediff = "ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx";
package.hash_sha256 =
"1af337fbd19c72db0f870753bcd7711c3ae9dcaa0ecde26c262bad942b112990";
package.hashdiff_sha256 =
"73c6e2d4f783fc4ca5481e89e0b8bfce7aec8ead3686290c94792658ec06f2f2";
package.fingerprint = "22";
ProtocolParser::Result result;
result.extension_id = id;
result.status = "ok";
result.crx_urls.push_back(GURL("http://localhost/download/"));
result.crx_diffurls.push_back(GURL("http://localhost/download/"));
result.manifest.version = "2.0";
result.manifest.browser_min_version = "11.0.1.0";
result.manifest.packages.push_back(package);
results.list.push_back(result);
} else {
NOTREACHED();
}
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
ErrorCategory::kNone, 0, 0));
}
};
class MockCrxDownloader : public CrxDownloader {
public:
static std::unique_ptr<CrxDownloader> Create(
bool is_background_download,
scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
return std::make_unique<MockCrxDownloader>();
}
MockCrxDownloader() : CrxDownloader(nullptr) {}
private:
void DoStartDownload(const GURL& url) override {
DownloadMetrics download_metrics;
FilePath path;
Result result;
if (url.path() == "/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx") {
download_metrics.url = url;
download_metrics.downloader = DownloadMetrics::kNone;
download_metrics.error = 0;
download_metrics.downloaded_bytes = 53638;
download_metrics.total_bytes = 53638;
download_metrics.download_time_ms = 2000;
EXPECT_TRUE(MakeTestFile(
TestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"), &path));
result.error = 0;
result.response = path;
} else if (url.path() ==
"/download/ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx") {
// A download error is injected on this execution path.
download_metrics.url = url;
download_metrics.downloader = DownloadMetrics::kNone;
download_metrics.error = -1;
download_metrics.downloaded_bytes = 0;
download_metrics.total_bytes = 2105;
download_metrics.download_time_ms = 1000;
// The response must not include a file path in the case of errors.
result.error = -1;
} else if (url.path() ==
"/download/ihfokbkgjpifnbbojhneepfflplebdkc_2.crx") {
download_metrics.url = url;
download_metrics.downloader = DownloadMetrics::kNone;
download_metrics.error = 0;
download_metrics.downloaded_bytes = 53855;
download_metrics.total_bytes = 53855;
download_metrics.download_time_ms = 1000;
EXPECT_TRUE(MakeTestFile(
TestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_2.crx"), &path));
result.error = 0;
result.response = path;
}
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
base::Unretained(this)));
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
base::Unretained(this), true, result,
download_metrics));
}
};
class MockPingManager : public MockPingManagerImpl {
public:
explicit MockPingManager(scoped_refptr<Configurator> config)
: MockPingManagerImpl(config) {}
protected:
~MockPingManager() override {
const auto ping_data = MockPingManagerImpl::ping_data();
EXPECT_EQ(2u, ping_data.size());
EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", ping_data[0].id);
EXPECT_EQ(base::Version("0.8"), ping_data[0].previous_version);
EXPECT_EQ(base::Version("1.0"), ping_data[0].next_version);
EXPECT_EQ(0, static_cast<int>(ping_data[0].error_category));
EXPECT_EQ(0, ping_data[0].error_code);
EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", ping_data[1].id);
EXPECT_EQ(base::Version("1.0"), ping_data[1].previous_version);
EXPECT_EQ(base::Version("2.0"), ping_data[1].next_version);
EXPECT_EQ(0, static_cast<int>(ping_data[1].error_category));
EXPECT_EQ(0, ping_data[1].error_code);
EXPECT_TRUE(ping_data[1].diff_update_failed);
EXPECT_EQ(1, static_cast<int>(ping_data[1].diff_error_category));
EXPECT_EQ(-1, ping_data[1].diff_error_code);
}
};
scoped_refptr<UpdateClient> update_client =
base::MakeRefCounted<UpdateClientImpl>(
config(), base::MakeRefCounted<MockPingManager>(config()),
&MockUpdateChecker::Create, &MockCrxDownloader::Create);
MockObserver observer;
{
InSequence seq;
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(AtLeast(1));
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(AtLeast(1));
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(1);
}
update_client->AddObserver(&observer);
const std::vector<std::string> ids = {"ihfokbkgjpifnbbojhneepfflplebdkc"};
{
base::RunLoop runloop;
update_client->Update(ids, base::BindOnce(&DataCallbackMock::Callback),
false,
base::BindOnce(&CompletionCallbackMock::Callback,
runloop.QuitClosure()));
runloop.Run();
}
{
base::RunLoop runloop;
update_client->Update(ids, base::BindOnce(&DataCallbackMock::Callback),
false,
base::BindOnce(&CompletionCallbackMock::Callback,
runloop.QuitClosure()));
runloop.Run();
}
update_client->RemoveObserver(&observer);
}
// Tests the queuing of update checks. In this scenario, two update checks are
// done for one CRX. The second update check call is queued up and will run
// after the first check has completed. The CRX has no updates.
TEST_F(UpdateClientTest, OneCrxNoUpdateQueuedCall) {
class DataCallbackMock {
public:
static std::vector<base::Optional<CrxComponent>> Callback(
const std::vector<std::string>& ids) {
CrxComponent crx;
crx.name = "test_jebg";
crx.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
crx.version = base::Version("0.9");
crx.installer = base::MakeRefCounted<TestInstaller>();
crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
return {crx};
}
};
class CompletionCallbackMock {
public:
static void Callback(base::OnceClosure quit_closure, Error error) {
static int num_call = 0;
++num_call;
EXPECT_EQ(Error::NONE, error);
if (num_call == 2)
std::move(quit_closure).Run();
}
};
class MockUpdateChecker : public UpdateChecker {
public:
static std::unique_ptr<UpdateChecker> Create(
scoped_refptr<Configurator> config,
PersistedData* metadata) {
return std::make_unique<MockUpdateChecker>();
}
void CheckForUpdates(
const std::string& session_id,
const std::vector<std::string>& ids_to_check,
const IdToComponentPtrMap& components,
const base::flat_map<std::string, std::string>& additional_attributes,
bool enabled_component_updates,
UpdateCheckCallback update_check_callback) override {
EXPECT_FALSE(session_id.empty());
EXPECT_TRUE(enabled_component_updates);
EXPECT_EQ(1u, ids_to_check.size());
const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
EXPECT_EQ(id, ids_to_check.front());
EXPECT_EQ(1u, components.count(id));
auto& component = components.at(id);
EXPECT_FALSE(component->is_foreground());
ProtocolParser::Result result;
result.extension_id = id;
result.status = "noupdate";
ProtocolParser::Results results;
results.list.push_back(result);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
ErrorCategory::kNone, 0, 0));
}
};
class MockCrxDownloader : public CrxDownloader {
public:
static std::unique_ptr<CrxDownloader> Create(
bool is_background_download,
scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
return std::make_unique<MockCrxDownloader>();
}
MockCrxDownloader() : CrxDownloader(nullptr) {}
private:
void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
};
class MockPingManager : public MockPingManagerImpl {
public:
explicit MockPingManager(scoped_refptr<Configurator> config)
: MockPingManagerImpl(config) {}
protected:
~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); }
};
scoped_refptr<UpdateClient> update_client =
base::MakeRefCounted<UpdateClientImpl>(
config(), base::MakeRefCounted<MockPingManager>(config()),
&MockUpdateChecker::Create, &MockCrxDownloader::Create);
MockObserver observer;
InSequence seq;
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_NOT_UPDATED,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_NOT_UPDATED,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
update_client->AddObserver(&observer);
const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf"};
update_client->Update(
ids, base::BindOnce(&DataCallbackMock::Callback), false,
base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
update_client->Update(
ids, base::BindOnce(&DataCallbackMock::Callback), false,
base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
RunThreads();
update_client->RemoveObserver(&observer);
}
// Tests the install of one CRX.
TEST_F(UpdateClientTest, OneCrxInstall) {
class DataCallbackMock {
public:
static std::vector<base::Optional<CrxComponent>> Callback(
const std::vector<std::string>& ids) {
CrxComponent crx;
crx.name = "test_jebg";
crx.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
crx.version = base::Version("0.0");
crx.installer = base::MakeRefCounted<TestInstaller>();
crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
return {crx};
}
};
class CompletionCallbackMock {
public:
static void Callback(base::OnceClosure quit_closure, Error error) {
EXPECT_EQ(Error::NONE, error);
std::move(quit_closure).Run();
}
};
class MockUpdateChecker : public UpdateChecker {
public:
static std::unique_ptr<UpdateChecker> Create(
scoped_refptr<Configurator> config,
PersistedData* metadata) {
return std::make_unique<MockUpdateChecker>();
}
void CheckForUpdates(
const std::string& session_id,
const std::vector<std::string>& ids_to_check,
const IdToComponentPtrMap& components,
const base::flat_map<std::string, std::string>& additional_attributes,
bool enabled_component_updates,
UpdateCheckCallback update_check_callback) override {
/*
Mock the following response:
<?xml version='1.0' encoding='UTF-8'?>
<response protocol='3.1'>
<app appid='jebgalgnebhfojomionfpkfelancnnkf'>
<updatecheck status='ok'>
<urls>
<url codebase='http://localhost/download/'/>
</urls>
<manifest version='1.0' prodversionmin='11.0.1.0'>
<packages>
<package name='jebgalgnebhfojomionfpkfelancnnkf.crx'
hash_sha256='6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd
7c9b12cb7cc067667bde87'/>
</packages>
</manifest>
</updatecheck>
</app>
</response>
*/
EXPECT_FALSE(session_id.empty());
EXPECT_TRUE(enabled_component_updates);
EXPECT_EQ(1u, ids_to_check.size());
const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
EXPECT_EQ(id, ids_to_check[0]);
EXPECT_EQ(1u, components.count(id));
ProtocolParser::Result::Manifest::Package package;
package.name = "jebgalgnebhfojomionfpkfelancnnkf.crx";
package.hash_sha256 =
"6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd7c9b12cb7cc067667bde87";
ProtocolParser::Result result;
result.extension_id = id;
result.status = "ok";
result.crx_urls.push_back(GURL("http://localhost/download/"));
result.manifest.version = "1.0";
result.manifest.browser_min_version = "11.0.1.0";
result.manifest.packages.push_back(package);
ProtocolParser::Results results;
results.list.push_back(result);
// Verify that calling Install sets ondemand.
EXPECT_TRUE(components.at(id)->is_foreground());
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
ErrorCategory::kNone, 0, 0));
}
};
class MockCrxDownloader : public CrxDownloader {
public:
static std::unique_ptr<CrxDownloader> Create(
bool is_background_download,
scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
return std::make_unique<MockCrxDownloader>();
}
MockCrxDownloader() : CrxDownloader(nullptr) {}
private:
void DoStartDownload(const GURL& url) override {
DownloadMetrics download_metrics;
FilePath path;
Result result;
if (url.path() == "/download/jebgalgnebhfojomionfpkfelancnnkf.crx") {
download_metrics.url = url;
download_metrics.downloader = DownloadMetrics::kNone;
download_metrics.error = 0;
download_metrics.downloaded_bytes = 1843;
download_metrics.total_bytes = 1843;
download_metrics.download_time_ms = 1000;
EXPECT_TRUE(MakeTestFile(
TestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"), &path));
result.error = 0;
result.response = path;
} else {
NOTREACHED();
}
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
base::Unretained(this)));
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
base::Unretained(this), true, result,
download_metrics));
}
};
class MockPingManager : public MockPingManagerImpl {
public:
explicit MockPingManager(scoped_refptr<Configurator> config)
: MockPingManagerImpl(config) {}
protected:
~MockPingManager() override {
const auto ping_data = MockPingManagerImpl::ping_data();
EXPECT_EQ(1u, ping_data.size());
EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
EXPECT_EQ(base::Version("0.0"), ping_data[0].previous_version);
EXPECT_EQ(base::Version("1.0"), ping_data[0].next_version);
EXPECT_EQ(0, static_cast<int>(ping_data[0].error_category));
EXPECT_EQ(0, ping_data[0].error_code);
}
};
scoped_refptr<UpdateClient> update_client =
base::MakeRefCounted<UpdateClientImpl>(
config(), base::MakeRefCounted<MockPingManager>(config()),
&MockUpdateChecker::Create, &MockCrxDownloader::Create);
MockObserver observer;
InSequence seq;
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(AtLeast(1));
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
update_client->AddObserver(&observer);
update_client->Install(
std::string("jebgalgnebhfojomionfpkfelancnnkf"),
base::BindOnce(&DataCallbackMock::Callback),
base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
RunThreads();
update_client->RemoveObserver(&observer);
}
// Tests the install of one CRX when no component data is provided. This
// results in an install error.
TEST_F(UpdateClientTest, OneCrxInstallNoCrxComponentData) {
class DataCallbackMock {
public:
static std::vector<base::Optional<CrxComponent>> Callback(
const std::vector<std::string>& ids) {
return {base::nullopt};
}
};
class CompletionCallbackMock {
public:
static void Callback(base::OnceClosure quit_closure, Error error) {
EXPECT_EQ(Error::NONE, error);
std::move(quit_closure).Run();
}
};
class MockUpdateChecker : public UpdateChecker {
public:
static std::unique_ptr<UpdateChecker> Create(
scoped_refptr<Configurator> config,
PersistedData* metadata) {
return std::make_unique<MockUpdateChecker>();
}
void CheckForUpdates(
const std::string& session_id,
const std::vector<std::string>& ids_to_check,
const IdToComponentPtrMap& components,
const base::flat_map<std::string, std::string>& additional_attributes,
bool enabled_component_updates,
UpdateCheckCallback update_check_callback) override {
NOTREACHED();
}
};
class MockCrxDownloader : public CrxDownloader {
public:
static std::unique_ptr<CrxDownloader> Create(
bool is_background_download,
scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
return std::make_unique<MockCrxDownloader>();
}
MockCrxDownloader() : CrxDownloader(nullptr) {}
private:
void DoStartDownload(const GURL& url) override { NOTREACHED(); }
};
class MockPingManager : public MockPingManagerImpl {
public:
explicit MockPingManager(scoped_refptr<Configurator> config)
: MockPingManagerImpl(config) {}
protected:
~MockPingManager() override {
EXPECT_EQ(0u, MockPingManagerImpl::ping_data().size());
}
};
scoped_refptr<UpdateClient> update_client =
base::MakeRefCounted<UpdateClientImpl>(
config(), base::MakeRefCounted<MockPingManager>(config()),
&MockUpdateChecker::Create, &MockCrxDownloader::Create);
MockObserver observer;
InSequence seq;
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1)
.WillOnce(Invoke([&update_client](Events event, const std::string& id) {
// Tests that the state of the component when the CrxComponent data
// is not provided. In this case, the optional |item.component| instance
// is not present.
CrxUpdateItem item;
EXPECT_TRUE(update_client->GetCrxUpdateState(id, &item));
EXPECT_EQ(ComponentState::kUpdateError, item.state);
EXPECT_STREQ("jebgalgnebhfojomionfpkfelancnnkf", item.id.c_str());
EXPECT_FALSE(item.component);
EXPECT_EQ(ErrorCategory::kService, item.error_category);
EXPECT_EQ(static_cast<int>(Error::CRX_NOT_FOUND), item.error_code);
EXPECT_EQ(0, item.extra_code1);
}));
update_client->AddObserver(&observer);
update_client->Install(
std::string("jebgalgnebhfojomionfpkfelancnnkf"),
base::BindOnce(&DataCallbackMock::Callback),
base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
RunThreads();
update_client->RemoveObserver(&observer);
}
// Tests that overlapping installs of the same CRX result in an error.
TEST_F(UpdateClientTest, ConcurrentInstallSameCRX) {
class DataCallbackMock {
public:
static std::vector<base::Optional<CrxComponent>> Callback(
const std::vector<std::string>& ids) {
CrxComponent crx;
crx.name = "test_jebg";
crx.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
crx.version = base::Version("0.0");
crx.installer = base::MakeRefCounted<TestInstaller>();
crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
return {crx};
}
};
class CompletionCallbackMock {
public:
static void Callback(base::OnceClosure quit_closure, Error error) {
static int num_call = 0;
++num_call;
EXPECT_LE(num_call, 2);
if (num_call == 1) {
EXPECT_EQ(Error::UPDATE_IN_PROGRESS, error);
return;
}
if (num_call == 2) {
EXPECT_EQ(Error::NONE, error);
std::move(quit_closure).Run();
}
}
};
class MockUpdateChecker : public UpdateChecker {
public:
static std::unique_ptr<UpdateChecker> Create(
scoped_refptr<Configurator> config,
PersistedData* metadata) {
return std::make_unique<MockUpdateChecker>();
}
void CheckForUpdates(
const std::string& session_id,
const std::vector<std::string>& ids_to_check,
const IdToComponentPtrMap& components,
const base::flat_map<std::string, std::string>& additional_attributes,
bool enabled_component_updates,
UpdateCheckCallback update_check_callback) override {
EXPECT_FALSE(session_id.empty());
EXPECT_TRUE(enabled_component_updates);
EXPECT_EQ(1u, ids_to_check.size());
const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
EXPECT_EQ(id, ids_to_check.front());
EXPECT_EQ(1u, components.count(id));
ProtocolParser::Result result;
result.extension_id = id;
result.status = "noupdate";
ProtocolParser::Results results;
results.list.push_back(result);
// Verify that calling Install sets |is_foreground| for the component.
EXPECT_TRUE(components.at(id)->is_foreground());
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
ErrorCategory::kNone, 0, 0));
}
};
class MockCrxDownloader : public CrxDownloader {
public:
static std::unique_ptr<CrxDownloader> Create(
bool is_background_download,
scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
return std::make_unique<MockCrxDownloader>();
}
MockCrxDownloader() : CrxDownloader(nullptr) {}
private:
void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
};
class MockPingManager : public MockPingManagerImpl {
public:
explicit MockPingManager(scoped_refptr<Configurator> config)
: MockPingManagerImpl(config) {}
protected:
~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); }
};
scoped_refptr<UpdateClient> update_client =
base::MakeRefCounted<UpdateClientImpl>(
config(), base::MakeRefCounted<MockPingManager>(config()),
&MockUpdateChecker::Create, &MockCrxDownloader::Create);
MockObserver observer;
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_NOT_UPDATED,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
update_client->AddObserver(&observer);
update_client->Install(
std::string("jebgalgnebhfojomionfpkfelancnnkf"),
base::BindOnce(&DataCallbackMock::Callback),
base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
update_client->Install(
std::string("jebgalgnebhfojomionfpkfelancnnkf"),
base::BindOnce(&DataCallbackMock::Callback),
base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
RunThreads();
update_client->RemoveObserver(&observer);
}
// Tests that UpdateClient::Update returns Error::INVALID_ARGUMENT when
// the |ids| parameter is empty.
TEST_F(UpdateClientTest, EmptyIdList) {
class DataCallbackMock {
public:
static std::vector<base::Optional<CrxComponent>> Callback(
const std::vector<std::string>& ids) {
return {};
}
};
class CompletionCallbackMock {
public:
static void Callback(base::OnceClosure quit_closure, Error error) {
DCHECK_EQ(Error::INVALID_ARGUMENT, error);
std::move(quit_closure).Run();
}
};
class MockUpdateChecker : public UpdateChecker {
public:
static std::unique_ptr<UpdateChecker> Create(
scoped_refptr<Configurator> config,
PersistedData* metadata) {
return std::make_unique<MockUpdateChecker>();
}
void CheckForUpdates(
const std::string& session_id,
const std::vector<std::string>& ids_to_check,
const IdToComponentPtrMap& components,
const base::flat_map<std::string, std::string>& additional_attributes,
bool enabled_component_updates,
UpdateCheckCallback update_check_callback) override {
NOTREACHED();
}
};
class MockCrxDownloader : public CrxDownloader {
public:
static std::unique_ptr<CrxDownloader> Create(
bool is_background_download,
scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
return std::make_unique<MockCrxDownloader>();
}
MockCrxDownloader() : CrxDownloader(nullptr) {}
private:
void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
};
class MockPingManager : public MockPingManagerImpl {
public:
explicit MockPingManager(scoped_refptr<Configurator> config)
: MockPingManagerImpl(config) {}
protected:
~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); }
};
scoped_refptr<UpdateClient> update_client =
base::MakeRefCounted<UpdateClientImpl>(
config(), base::MakeRefCounted<MockPingManager>(config()),
&MockUpdateChecker::Create, &MockCrxDownloader::Create);
const std::vector<std::string> empty_id_list;
update_client->Update(
empty_id_list, base::BindOnce(&DataCallbackMock::Callback), false,
base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
RunThreads();
}
TEST_F(UpdateClientTest, SendUninstallPing) {
class CompletionCallbackMock {
public:
static void Callback(base::OnceClosure quit_closure, Error error) {
std::move(quit_closure).Run();
}
};
class MockUpdateChecker : public UpdateChecker {
public:
static std::unique_ptr<UpdateChecker> Create(
scoped_refptr<Configurator> config,
PersistedData* metadata) {
return nullptr;
}
void CheckForUpdates(
const std::string& session_id,
const std::vector<std::string>& ids_to_check,
const IdToComponentPtrMap& components,
const base::flat_map<std::string, std::string>& additional_attributes,
bool enabled_component_updates,
UpdateCheckCallback update_check_callback) override {
NOTREACHED();
}
};
class MockCrxDownloader : public CrxDownloader {
public:
static std::unique_ptr<CrxDownloader> Create(
bool is_background_download,
scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
return nullptr;
}
private:
MockCrxDownloader() : CrxDownloader(nullptr) {}
~MockCrxDownloader() override {}
void DoStartDownload(const GURL& url) override {}
};
class MockPingManager : public MockPingManagerImpl {
public:
explicit MockPingManager(scoped_refptr<Configurator> config)
: MockPingManagerImpl(config) {}
protected:
~MockPingManager() override {
const auto ping_data = MockPingManagerImpl::ping_data();
EXPECT_EQ(1u, ping_data.size());
EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
EXPECT_EQ(base::Version("1.2.3.4"), ping_data[0].previous_version);
EXPECT_EQ(base::Version("0"), ping_data[0].next_version);
EXPECT_EQ(10, ping_data[0].extra_code1);
}
};
scoped_refptr<UpdateClient> update_client =
base::MakeRefCounted<UpdateClientImpl>(
config(), base::MakeRefCounted<MockPingManager>(config()),
&MockUpdateChecker::Create, &MockCrxDownloader::Create);
update_client->SendUninstallPing(
"jebgalgnebhfojomionfpkfelancnnkf", base::Version("1.2.3.4"), 10,
base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
RunThreads();
}
TEST_F(UpdateClientTest, RetryAfter) {
class DataCallbackMock {
public:
static std::vector<base::Optional<CrxComponent>> Callback(
const std::vector<std::string>& ids) {
CrxComponent crx;
crx.name = "test_jebg";
crx.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
crx.version = base::Version("0.9");
crx.installer = base::MakeRefCounted<TestInstaller>();
crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
return {crx};
}
};
class CompletionCallbackMock {
public:
static void Callback(base::OnceClosure quit_closure, Error error) {
static int num_call = 0;
++num_call;
EXPECT_LE(num_call, 4);
if (num_call == 1) {
EXPECT_EQ(Error::NONE, error);
} else if (num_call == 2) {
// This request is throttled since the update engine received a
// positive |retry_after_sec| value in the update check response.
EXPECT_EQ(Error::RETRY_LATER, error);
} else if (num_call == 3) {
// This request is a foreground Install, which is never throttled.
// The update engine received a |retry_after_sec| value of 0, which
// resets the throttling.
EXPECT_EQ(Error::NONE, error);
} else if (num_call == 4) {
// This request succeeds since there is no throttling in effect.
EXPECT_EQ(Error::NONE, error);
}
std::move(quit_closure).Run();
}
};
class MockUpdateChecker : public UpdateChecker {
public:
static std::unique_ptr<UpdateChecker> Create(
scoped_refptr<Configurator> config,
PersistedData* metadata) {
return std::make_unique<MockUpdateChecker>();
}
void CheckForUpdates(
const std::string& session_id,
const std::vector<std::string>& ids_to_check,
const IdToComponentPtrMap& components,
const base::flat_map<std::string, std::string>& additional_attributes,
bool enabled_component_updates,
UpdateCheckCallback update_check_callback) override {
EXPECT_FALSE(session_id.empty());
static int num_call = 0;
++num_call;
EXPECT_LE(num_call, 3);
int retry_after_sec(0);
if (num_call == 1) {
// Throttle the next call.
retry_after_sec = 60 * 60; // 1 hour.
}
EXPECT_EQ(1u, ids_to_check.size());
const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
EXPECT_EQ(id, ids_to_check.front());
EXPECT_EQ(1u, components.count(id));
ProtocolParser::Result result;
result.extension_id = id;
result.status = "noupdate";
ProtocolParser::Results results;
results.list.push_back(result);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
ErrorCategory::kNone, 0, retry_after_sec));
}
};
class MockCrxDownloader : public CrxDownloader {
public:
static std::unique_ptr<CrxDownloader> Create(
bool is_background_download,
scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
return std::make_unique<MockCrxDownloader>();
}
MockCrxDownloader() : CrxDownloader(nullptr) {}
private:
void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
};
class MockPingManager : public MockPingManagerImpl {
public:
explicit MockPingManager(scoped_refptr<Configurator> config)
: MockPingManagerImpl(config) {}
protected:
~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); }
};
scoped_refptr<UpdateClient> update_client =
base::MakeRefCounted<UpdateClientImpl>(
config(), base::MakeRefCounted<MockPingManager>(config()),
&MockUpdateChecker::Create, &MockCrxDownloader::Create);
MockObserver observer;
InSequence seq;
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_NOT_UPDATED,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_NOT_UPDATED,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_NOT_UPDATED,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
update_client->AddObserver(&observer);
const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf"};
{
// The engine handles this Update call but responds with a valid
// |retry_after_sec|, which causes subsequent calls to fail.
base::RunLoop runloop;
update_client->Update(ids, base::BindOnce(&DataCallbackMock::Callback),
false,
base::BindOnce(&CompletionCallbackMock::Callback,
runloop.QuitClosure()));
runloop.Run();
}
{
// This call will result in a completion callback invoked with
// Error::ERROR_UPDATE_RETRY_LATER.
base::RunLoop runloop;
update_client->Update(ids, base::BindOnce(&DataCallbackMock::Callback),
false,
base::BindOnce(&CompletionCallbackMock::Callback,
runloop.QuitClosure()));
runloop.Run();
}
{
// The Install call is handled, and the throttling is reset due to
// the value of |retry_after_sec| in the completion callback.
base::RunLoop runloop;
update_client->Install(std::string("jebgalgnebhfojomionfpkfelancnnkf"),
base::BindOnce(&DataCallbackMock::Callback),
base::BindOnce(&CompletionCallbackMock::Callback,
runloop.QuitClosure()));
runloop.Run();
}
{
// This call succeeds.
base::RunLoop runloop;
update_client->Update(ids, base::BindOnce(&DataCallbackMock::Callback),
false,
base::BindOnce(&CompletionCallbackMock::Callback,
runloop.QuitClosure()));
runloop.Run();
}
update_client->RemoveObserver(&observer);
}
// Tests the update check for two CRXs scenario. The first component supports
// the group policy to enable updates, and has its updates disabled. The second
// component has an update. The server does not honor the "updatedisabled"
// attribute and returns updates for both components. However, the update for
// the first component is not applied and the client responds with a
// (SERVICE_ERROR, UPDATE_DISABLED)
TEST_F(UpdateClientTest, TwoCrxUpdateOneUpdateDisabled) {
class DataCallbackMock {
public:
static std::vector<base::Optional<CrxComponent>> Callback(
const std::vector<std::string>& ids) {
CrxComponent crx1;
crx1.name = "test_jebg";
crx1.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
crx1.version = base::Version("0.9");
crx1.installer = base::MakeRefCounted<TestInstaller>();
crx1.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
crx1.supports_group_policy_enable_component_updates = true;
CrxComponent crx2;
crx2.name = "test_ihfo";
crx2.pk_hash.assign(ihfo_hash, ihfo_hash + base::size(ihfo_hash));
crx2.version = base::Version("0.8");
crx2.installer = base::MakeRefCounted<TestInstaller>();
crx2.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
return {crx1, crx2};
}
};
class CompletionCallbackMock {
public:
static void Callback(base::OnceClosure quit_closure, Error error) {
EXPECT_EQ(Error::NONE, error);
std::move(quit_closure).Run();
}
};
class MockUpdateChecker : public UpdateChecker {
public:
static std::unique_ptr<UpdateChecker> Create(
scoped_refptr<Configurator> config,
PersistedData* metadata) {
return std::make_unique<MockUpdateChecker>();
}
void CheckForUpdates(
const std::string& session_id,
const std::vector<std::string>& ids_to_check,
const IdToComponentPtrMap& components,
const base::flat_map<std::string, std::string>& additional_attributes,
bool enabled_component_updates,
UpdateCheckCallback update_check_callback) override {
/*
Mock the following response:
<?xml version='1.0' encoding='UTF-8'?>
<response protocol='3.1'>
<app appid='jebgalgnebhfojomionfpkfelancnnkf'>
<updatecheck status='ok'>
<urls>
<url codebase='http://localhost/download/'/>
</urls>
<manifest version='1.0' prodversionmin='11.0.1.0'>
<packages>
<package name='jebgalgnebhfojomionfpkfelancnnkf.crx'
hash_sha256='6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd
7c9b12cb7cc067667bde87'/>
</packages>
</manifest>
</updatecheck>
</app>
<app appid='ihfokbkgjpifnbbojhneepfflplebdkc'>
<updatecheck status='ok'>
<urls>
<url codebase='http://localhost/download/'/>
</urls>
<manifest version='1.0' prodversionmin='11.0.1.0'>
<packages>
<package name='ihfokbkgjpifnbbojhneepfflplebdkc_1.crx'
hash_sha256='813c59747e139a608b3b5fc49633affc6db574373f
309f156ea6d27229c0b3f9'/>
</packages>
</manifest>
</updatecheck>
</app>
</response>
*/
// UpdateClient reads the state of |enabled_component_updates| from the
// configurator instance, persists its value in the corresponding
// update context, and propagates it down to each of the update actions,
// and further down to the UpdateChecker instance.
EXPECT_FALSE(session_id.empty());
EXPECT_FALSE(enabled_component_updates);
EXPECT_EQ(2u, ids_to_check.size());
ProtocolParser::Results results;
{
const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
EXPECT_EQ(id, ids_to_check[0]);
EXPECT_EQ(1u, components.count(id));
ProtocolParser::Result::Manifest::Package package;
package.name = "jebgalgnebhfojomionfpkfelancnnkf.crx";
package.hash_sha256 =
"6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd7c9b12cb7cc067667bde87";
ProtocolParser::Result result;
result.extension_id = id;
result.status = "ok";
result.crx_urls.push_back(GURL("http://localhost/download/"));
result.manifest.version = "1.0";
result.manifest.browser_min_version = "11.0.1.0";
result.manifest.packages.push_back(package);
results.list.push_back(result);
}
{
const std::string id = "ihfokbkgjpifnbbojhneepfflplebdkc";
EXPECT_EQ(id, ids_to_check[1]);
EXPECT_EQ(1u, components.count(id));
ProtocolParser::Result::Manifest::Package package;
package.name = "ihfokbkgjpifnbbojhneepfflplebdkc_1.crx";
package.hash_sha256 =
"813c59747e139a608b3b5fc49633affc6db574373f309f156ea6d27229c0b3f9";
ProtocolParser::Result result;
result.extension_id = id;
result.status = "ok";
result.crx_urls.push_back(GURL("http://localhost/download/"));
result.manifest.version = "1.0";
result.manifest.browser_min_version = "11.0.1.0";
result.manifest.packages.push_back(package);
results.list.push_back(result);
}
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
ErrorCategory::kNone, 0, 0));
}
};
class MockCrxDownloader : public CrxDownloader {
public:
static std::unique_ptr<CrxDownloader> Create(
bool is_background_download,
scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
return std::make_unique<MockCrxDownloader>();
}
MockCrxDownloader() : CrxDownloader(nullptr) {}
private:
void DoStartDownload(const GURL& url) override {
DownloadMetrics download_metrics;
FilePath path;
Result result;
if (url.path() == "/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx") {
download_metrics.url = url;
download_metrics.downloader = DownloadMetrics::kNone;
download_metrics.error = 0;
download_metrics.downloaded_bytes = 53638;
download_metrics.total_bytes = 53638;
download_metrics.download_time_ms = 2000;
EXPECT_TRUE(MakeTestFile(
TestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"), &path));
result.error = 0;
result.response = path;
} else {
NOTREACHED();
}
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
base::Unretained(this)));
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
base::Unretained(this), true, result,
download_metrics));
}
};
class MockPingManager : public MockPingManagerImpl {
public:
explicit MockPingManager(scoped_refptr<Configurator> config)
: MockPingManagerImpl(config) {}
protected:
~MockPingManager() override {
const auto ping_data = MockPingManagerImpl::ping_data();
EXPECT_EQ(2u, ping_data.size());
EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
EXPECT_EQ(base::Version("0.9"), ping_data[0].previous_version);
EXPECT_EQ(base::Version("1.0"), ping_data[0].next_version);
EXPECT_EQ(4, static_cast<int>(ping_data[0].error_category));
EXPECT_EQ(2, ping_data[0].error_code);
EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", ping_data[1].id);
EXPECT_EQ(base::Version("0.8"), ping_data[1].previous_version);
EXPECT_EQ(base::Version("1.0"), ping_data[1].next_version);
EXPECT_EQ(0, static_cast<int>(ping_data[1].error_category));
EXPECT_EQ(0, ping_data[1].error_code);
}
};
// Disables updates for the components declaring support for the group policy.
config()->SetEnabledComponentUpdates(false);
scoped_refptr<UpdateClient> update_client =
base::MakeRefCounted<UpdateClientImpl>(
config(), base::MakeRefCounted<MockPingManager>(config()),
&MockUpdateChecker::Create, &MockCrxDownloader::Create);
MockObserver observer;
{
InSequence seq;
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
}
{
InSequence seq;
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(AtLeast(1));
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(1);
}
update_client->AddObserver(&observer);
const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf",
"ihfokbkgjpifnbbojhneepfflplebdkc"};
update_client->Update(
ids, base::BindOnce(&DataCallbackMock::Callback), false,
base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
RunThreads();
update_client->RemoveObserver(&observer);
}
// Tests the scenario where the update check fails.
TEST_F(UpdateClientTest, OneCrxUpdateCheckFails) {
class DataCallbackMock {
public:
static std::vector<base::Optional<CrxComponent>> Callback(
const std::vector<std::string>& ids) {
CrxComponent crx;
crx.name = "test_jebg";
crx.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
crx.version = base::Version("0.9");
crx.installer = base::MakeRefCounted<TestInstaller>();
crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
return {crx};
}
};
class CompletionCallbackMock {
public:
static void Callback(base::OnceClosure quit_closure, Error error) {
EXPECT_EQ(Error::UPDATE_CHECK_ERROR, error);
std::move(quit_closure).Run();
}
};
class MockUpdateChecker : public UpdateChecker {
public:
static std::unique_ptr<UpdateChecker> Create(
scoped_refptr<Configurator> config,
PersistedData* metadata) {
return std::make_unique<MockUpdateChecker>();
}
void CheckForUpdates(
const std::string& session_id,
const std::vector<std::string>& ids_to_check,
const IdToComponentPtrMap& components,
const base::flat_map<std::string, std::string>& additional_attributes,
bool enabled_component_updates,
UpdateCheckCallback update_check_callback) override {
EXPECT_FALSE(session_id.empty());
EXPECT_TRUE(enabled_component_updates);
EXPECT_EQ(1u, ids_to_check.size());
const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
EXPECT_EQ(id, ids_to_check.front());
EXPECT_EQ(1u, components.count(id));
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(update_check_callback), base::nullopt,
ErrorCategory::kUpdateCheck, -1, 0));
}
};
class MockCrxDownloader : public CrxDownloader {
public:
static std::unique_ptr<CrxDownloader> Create(
bool is_background_download,
scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
return std::make_unique<MockCrxDownloader>();
}
MockCrxDownloader() : CrxDownloader(nullptr) {}
private:
void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
};
class MockPingManager : public MockPingManagerImpl {
public:
explicit MockPingManager(scoped_refptr<Configurator> config)
: MockPingManagerImpl(config) {}
protected:
~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); }
};
scoped_refptr<UpdateClient> update_client =
base::MakeRefCounted<UpdateClientImpl>(
config(), base::MakeRefCounted<MockPingManager>(config()),
&MockUpdateChecker::Create, &MockCrxDownloader::Create);
MockObserver observer;
InSequence seq;
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1)
.WillOnce(Invoke([&update_client](Events event, const std::string& id) {
CrxUpdateItem item;
EXPECT_TRUE(update_client->GetCrxUpdateState(id, &item));
EXPECT_EQ(ComponentState::kUpdateError, item.state);
EXPECT_EQ(5, static_cast<int>(item.error_category));
EXPECT_EQ(-1, item.error_code);
EXPECT_EQ(0, item.extra_code1);
}));
update_client->AddObserver(&observer);
const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf"};
update_client->Update(
ids, base::BindOnce(&DataCallbackMock::Callback), false,
base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
RunThreads();
update_client->RemoveObserver(&observer);
}
// Tests the scenario where the server responds with different values for
// application status.
TEST_F(UpdateClientTest, OneCrxErrorUnknownApp) {
class DataCallbackMock {
public:
static std::vector<base::Optional<CrxComponent>> Callback(
const std::vector<std::string>& ids) {
std::vector<base::Optional<CrxComponent>> component;
{
CrxComponent crx;
crx.name = "test_jebg";
crx.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
crx.version = base::Version("0.9");
crx.installer = base::MakeRefCounted<TestInstaller>();
crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
component.push_back(crx);
}
{
CrxComponent crx;
crx.name = "test_abag";
crx.pk_hash.assign(abag_hash, abag_hash + base::size(abag_hash));
crx.version = base::Version("0.1");
crx.installer = base::MakeRefCounted<TestInstaller>();
crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
component.push_back(crx);
}
{
CrxComponent crx;
crx.name = "test_ihfo";
crx.pk_hash.assign(ihfo_hash, ihfo_hash + base::size(ihfo_hash));
crx.version = base::Version("0.2");
crx.installer = base::MakeRefCounted<TestInstaller>();
crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
component.push_back(crx);
}
{
CrxComponent crx;
crx.name = "test_gjpm";
crx.pk_hash.assign(gjpm_hash, gjpm_hash + base::size(gjpm_hash));
crx.version = base::Version("0.3");
crx.installer = base::MakeRefCounted<TestInstaller>();
crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
component.push_back(crx);
}
return component;
}
};
class CompletionCallbackMock {
public:
static void Callback(base::OnceClosure quit_closure, Error error) {
EXPECT_EQ(Error::NONE, error);
std::move(quit_closure).Run();
}
};
class MockUpdateChecker : public UpdateChecker {
public:
static std::unique_ptr<UpdateChecker> Create(
scoped_refptr<Configurator> config,
PersistedData* metadata) {
return std::make_unique<MockUpdateChecker>();
}
void CheckForUpdates(
const std::string& session_id,
const std::vector<std::string>& ids_to_check,
const IdToComponentPtrMap& components,
const base::flat_map<std::string, std::string>& additional_attributes,
bool enabled_component_updates,
UpdateCheckCallback update_check_callback) override {
EXPECT_FALSE(session_id.empty());
EXPECT_TRUE(enabled_component_updates);
EXPECT_EQ(4u, ids_to_check.size());
const std::string update_response =
")]}'"
R"({"response": {)"
R"( "protocol": "3.1",)"
R"( "app": [)"
R"({"appid": "jebgalgnebhfojomionfpkfelancnnkf",)"
R"( "status": "error-unknownApplication"},)"
R"({"appid": "abagagagagagagagagagagagagagagag",)"
R"( "status": "restricted"},)"
R"({"appid": "ihfokbkgjpifnbbojhneepfflplebdkc",)"
R"( "status": "error-invalidAppId"},)"
R"({"appid": "gjpmebpgbhcamgdgjcmnjfhggjpgcimm",)"
R"( "status": "error-foobarApp"})"
R"(]}})";
const auto parser = ProtocolHandlerFactoryJSON().CreateParser();
EXPECT_TRUE(parser->Parse(update_response));
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(update_check_callback), parser->results(),
ErrorCategory::kNone, 0, 0));
}
};
class MockCrxDownloader : public CrxDownloader {
public:
static std::unique_ptr<CrxDownloader> Create(
bool is_background_download,
scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
return std::make_unique<MockCrxDownloader>();
}
MockCrxDownloader() : CrxDownloader(nullptr) {}
private:
void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
};
class MockPingManager : public MockPingManagerImpl {
public:
explicit MockPingManager(scoped_refptr<Configurator> config)
: MockPingManagerImpl(config) {}
protected:
~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); }
};
scoped_refptr<UpdateClient> update_client =
base::MakeRefCounted<UpdateClientImpl>(
config(), base::MakeRefCounted<MockPingManager>(config()),
&MockUpdateChecker::Create, &MockCrxDownloader::Create);
MockObserver observer;
{
InSequence seq;
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
"jebgalgnebhfojomionfpkfelancnnkf"))
.Times(1)
.WillOnce(Invoke([&update_client](Events event, const std::string& id) {
CrxUpdateItem item;
EXPECT_TRUE(update_client->GetCrxUpdateState(id, &item));
EXPECT_EQ(ComponentState::kUpdateError, item.state);
EXPECT_EQ(5, static_cast<int>(item.error_category));
EXPECT_EQ(-10006, item.error_code); // UNKNOWN_APPPLICATION.
EXPECT_EQ(0, item.extra_code1);
}));
}
{
InSequence seq;
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
"abagagagagagagagagagagagagagagag"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
"abagagagagagagagagagagagagagagag"))
.Times(1)
.WillOnce(Invoke([&update_client](Events event, const std::string& id) {
CrxUpdateItem item;
EXPECT_TRUE(update_client->GetCrxUpdateState(id, &item));
EXPECT_EQ(ComponentState::kUpdateError, item.state);
EXPECT_EQ(5, static_cast<int>(item.error_category));
EXPECT_EQ(-10007, item.error_code); // RESTRICTED_APPLICATION.
EXPECT_EQ(0, item.extra_code1);
}));
}
{
InSequence seq;
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
"ihfokbkgjpifnbbojhneepfflplebdkc"))
.Times(1)
.WillOnce(Invoke([&update_client](Events event, const std::string& id) {
CrxUpdateItem item;
EXPECT_TRUE(update_client->GetCrxUpdateState(id, &item));
EXPECT_EQ(ComponentState::kUpdateError, item.state);
EXPECT_EQ(5, static_cast<int>(item.error_category));
EXPECT_EQ(-10008, item.error_code); // INVALID_APPID.
EXPECT_EQ(0, item.extra_code1);
}));
}
{
InSequence seq;
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
"gjpmebpgbhcamgdgjcmnjfhggjpgcimm"))
.Times(1);
EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
"gjpmebpgbhcamgdgjcmnjfhggjpgcimm"))
.Times(1)
.WillOnce(Invoke([&update_client](Events event, const std::string& id) {
CrxUpdateItem item;
EXPECT_TRUE(update_client->GetCrxUpdateState(id, &item));
EXPECT_EQ(ComponentState::kUpdateError, item.state);
EXPECT_EQ(5, static_cast<int>(item.error_category));
EXPECT_EQ(-10004, item.error_code); // UPDATE_RESPONSE_NOT_FOUND.
EXPECT_EQ(0, item.extra_code1);
}));
}
update_client->AddObserver(&observer);
const std::vector<std::string> ids = {
"jebgalgnebhfojomionfpkfelancnnkf", "abagagagagagagagagagagagagagagag",
"ihfokbkgjpifnbbojhneepfflplebdkc", "gjpmebpgbhcamgdgjcmnjfhggjpgcimm"};
update_client->Update(
ids, base::BindOnce(&DataCallbackMock::Callback), true,
base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
RunThreads();
update_client->RemoveObserver(&observer);
}
#if defined(OS_WIN) // ActionRun is only implemented on Windows.
// Tests that a run action in invoked in the CRX install scenario.
TEST_F(UpdateClientTest, ActionRun_Install) {
class MockUpdateChecker : public UpdateChecker {
public:
static std::unique_ptr<UpdateChecker> Create(
scoped_refptr<Configurator> config,
PersistedData* metadata) {
return std::make_unique<MockUpdateChecker>();
}
void CheckForUpdates(
const std::string& session_id,
const std::vector<std::string>& ids_to_check,
const IdToComponentPtrMap& components,
const base::flat_map<std::string, std::string>& additional_attributes,
bool enabled_component_updates,
UpdateCheckCallback update_check_callback) override {
/*
Mock the following response:
<?xml version='1.0' encoding='UTF-8'?>
<response protocol='3.1'>
<app appid='gjpmebpgbhcamgdgjcmnjfhggjpgcimm'>
<updatecheck status='ok'>
<urls>
<url codebase='http://localhost/download/'/>
</urls>
<manifest version='1.0' prodversionmin='11.0.1.0'>
<packages>
<package name='runaction_test_win.crx3'
hash_sha256='89290a0d2ff21ca5b45e109c6cc859ab5fe294e19c102d54acd321429c372cea'/>
</packages>
</manifest>
<actions>"
<action run='ChromeRecovery.crx3'/>"
</actions>"
</updatecheck>
</app>
</response>
*/
EXPECT_FALSE(session_id.empty());
EXPECT_TRUE(enabled_component_updates);
EXPECT_EQ(1u, ids_to_check.size());
const std::string id = "gjpmebpgbhcamgdgjcmnjfhggjpgcimm";
EXPECT_EQ(id, ids_to_check[0]);
EXPECT_EQ(1u, components.count(id));
ProtocolParser::Result::Manifest::Package package;
package.name = "runaction_test_win.crx3";
package.hash_sha256 =
"89290a0d2ff21ca5b45e109c6cc859ab5fe294e19c102d54acd321429c372cea";
ProtocolParser::Result result;
result.extension_id = id;
result.status = "ok";
result.crx_urls.push_back(GURL("http://localhost/download/"));
result.manifest.version = "1.0";
result.manifest.browser_min_version = "11.0.1.0";
result.manifest.packages.push_back(package);
result.action_run = "ChromeRecovery.crx3";
ProtocolParser::Results results;
results.list.push_back(result);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
ErrorCategory::kNone, 0, 0));
}
};
class MockCrxDownloader : public CrxDownloader {
public:
static std::unique_ptr<CrxDownloader> Create(
bool is_background_download,
scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
return std::make_unique<MockCrxDownloader>();
}
MockCrxDownloader() : CrxDownloader(nullptr) {}
private:
void DoStartDownload(const GURL& url) override {
DownloadMetrics download_metrics;
FilePath path;
Result result;
if (url.path() == "/download/runaction_test_win.crx3") {
download_metrics.url = url;
download_metrics.downloader = DownloadMetrics::kNone;
download_metrics.error = 0;
download_metrics.downloaded_bytes = 1843;
download_metrics.total_bytes = 1843;
download_metrics.download_time_ms = 1000;
EXPECT_TRUE(
MakeTestFile(TestFilePath("runaction_test_win.crx3"), &path));
result.error = 0;
result.response = path;
} else {
NOTREACHED();
}
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
base::Unretained(this), true, result,
download_metrics));
}
};
class MockPingManager : public MockPingManagerImpl {
public:
explicit MockPingManager(scoped_refptr<Configurator> config)
: MockPingManagerImpl(config) {}
protected:
~MockPingManager() override {
EXPECT_EQ(3u, events().size());
/*
"<event eventtype="14" eventresult="1" downloader="unknown" "
"url="http://localhost/download/runaction_test_win.crx3"
"downloaded=1843 "
"total=1843 download_time_ms="1000" previousversion="0.0" "
"nextversion="1.0"/>"
*/
const auto& event0 = events()[0];
EXPECT_EQ(14, event0.FindKey("eventtype")->GetInt());
EXPECT_EQ(1, event0.FindKey("eventresult")->GetInt());
EXPECT_EQ("unknown", event0.FindKey("downloader")->GetString());
EXPECT_EQ("http://localhost/download/runaction_test_win.crx3",
event0.FindKey("url")->GetString());
EXPECT_EQ(1843, event0.FindKey("downloaded")->GetDouble());
EXPECT_EQ(1843, event0.FindKey("total")->GetDouble());
EXPECT_EQ(1000, event0.FindKey("download_time_ms")->GetDouble());
EXPECT_EQ("0.0", event0.FindKey("previousversion")->GetString());
EXPECT_EQ("1.0", event0.FindKey("nextversion")->GetString());
// "<event eventtype="42" eventresult="1" errorcode="1877345072"/>"
const auto& event1 = events()[1];
EXPECT_EQ(42, event1.FindKey("eventtype")->GetInt());
EXPECT_EQ(1, event1.FindKey("eventresult")->GetInt());
EXPECT_EQ(1877345072, event1.FindKey("errorcode")->GetInt());
// "<event eventtype=\"3\" eventresult=\"1\" previousversion=\"0.0\" "
// "nextversion=\"1.0\"/>",
const auto& event2 = events()[2];
EXPECT_EQ(3, event2.FindKey("eventtype")->GetInt());
EXPECT_EQ(1, event1.FindKey("eventresult")->GetInt());
EXPECT_EQ("0.0", event0.FindKey("previousversion")->GetString());
EXPECT_EQ("1.0", event0.FindKey("nextversion")->GetString());
}
};
scoped_refptr<UpdateClient> update_client =
base::MakeRefCounted<UpdateClientImpl>(
config(), base::MakeRefCounted<MockPingManager>(config()),
&MockUpdateChecker::Create, &MockCrxDownloader::Create);
// The action is a program which returns 1877345072 as a hardcoded value.
update_client->Install(
std::string("gjpmebpgbhcamgdgjcmnjfhggjpgcimm"),
base::BindOnce([](const std::vector<std::string>& ids) {
CrxComponent crx;
crx.name = "test_niea";
crx.pk_hash.assign(gjpm_hash, gjpm_hash + base::size(gjpm_hash));
crx.version = base::Version("0.0");
crx.installer = base::MakeRefCounted<VersionedTestInstaller>();
crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
return std::vector<base::Optional<CrxComponent>>{crx};
}),
base::BindOnce(
[](base::OnceClosure quit_closure, Error error) {
EXPECT_EQ(Error::NONE, error);
std::move(quit_closure).Run();
},
quit_closure()));
RunThreads();
}
// Tests that a run action is invoked in an update scenario when there was
// no update.
TEST_F(UpdateClientTest, ActionRun_NoUpdate) {
class MockUpdateChecker : public UpdateChecker {
public:
static std::unique_ptr<UpdateChecker> Create(
scoped_refptr<Configurator> config,
PersistedData* metadata) {
return std::make_unique<MockUpdateChecker>();
}
void CheckForUpdates(
const std::string& session_id,
const std::vector<std::string>& ids_to_check,
const IdToComponentPtrMap& components,
const base::flat_map<std::string, std::string>& additional_attributes,
bool enabled_component_updates,
UpdateCheckCallback update_check_callback) override {
/*
Mock the following response:
<?xml version='1.0' encoding='UTF-8'?>
<response protocol='3.1'>
<app appid='gjpmebpgbhcamgdgjcmnjfhggjpgcimm'>
<updatecheck status='noupdate'>
<actions>"
<action run=ChromeRecovery.crx3'/>"
</actions>"
</updatecheck>
</app>
</response>
*/
EXPECT_FALSE(session_id.empty());
EXPECT_EQ(1u, ids_to_check.size());
const std::string id = "gjpmebpgbhcamgdgjcmnjfhggjpgcimm";
EXPECT_EQ(id, ids_to_check[0]);
EXPECT_EQ(1u, components.count(id));
ProtocolParser::Result result;
result.extension_id = id;
result.status = "noupdate";
result.action_run = "ChromeRecovery.crx3";
ProtocolParser::Results results;
results.list.push_back(result);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
ErrorCategory::kNone, 0, 0));
}
};
class MockCrxDownloader : public CrxDownloader {
public:
static std::unique_ptr<CrxDownloader> Create(
bool is_background_download,
scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
return std::make_unique<MockCrxDownloader>();
}
MockCrxDownloader() : CrxDownloader(nullptr) {}
private:
void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
};
class MockPingManager : public MockPingManagerImpl {
public:
explicit MockPingManager(scoped_refptr<Configurator> config)
: MockPingManagerImpl(config) {}
protected:
~MockPingManager() override {
EXPECT_EQ(1u, events().size());
// "<event eventtype="42" eventresult="1" errorcode="1877345072"/>"
const auto& event = events()[0];
EXPECT_EQ(42, event.FindKey("eventtype")->GetInt());
EXPECT_EQ(1, event.FindKey("eventresult")->GetInt());
EXPECT_EQ(1877345072, event.FindKey("errorcode")->GetInt());
}
};
// Unpack the CRX to mock an existing install to be updated. The payload to
// run is going to be picked up from this directory.
base::FilePath unpack_path;
{
base::RunLoop runloop;
base::OnceClosure quit_closure = runloop.QuitClosure();
auto config = base::MakeRefCounted<TestConfigurator>();
auto component_unpacker = base::MakeRefCounted<ComponentUnpacker>(
std::vector<uint8_t>(std::begin(gjpm_hash), std::end(gjpm_hash)),
TestFilePath("runaction_test_win.crx3"), nullptr,
config->GetUnzipperFactory()->Create(),
config->GetPatcherFactory()->Create(),
crx_file::VerifierFormat::CRX2_OR_CRX3);
component_unpacker->Unpack(base::BindOnce(
[](base::FilePath* unpack_path, base::OnceClosure quit_closure,
const ComponentUnpacker::Result& result) {
EXPECT_EQ(UnpackerError::kNone, result.error);
EXPECT_EQ(0, result.extended_error);
*unpack_path = result.unpack_path;
std::move(quit_closure).Run();
},
&unpack_path, runloop.QuitClosure()));
runloop.Run();
}
EXPECT_FALSE(unpack_path.empty());
EXPECT_TRUE(base::DirectoryExists(unpack_path));
int64_t file_size = 0;
EXPECT_TRUE(base::GetFileSize(unpack_path.AppendASCII("ChromeRecovery.crx3"),
&file_size));
EXPECT_EQ(44582, file_size);
base::ScopedTempDir unpack_path_owner;
EXPECT_TRUE(unpack_path_owner.Set(unpack_path));
scoped_refptr<UpdateClient> update_client =
base::MakeRefCounted<UpdateClientImpl>(
config(), base::MakeRefCounted<MockPingManager>(config()),
&MockUpdateChecker::Create, &MockCrxDownloader::Create);
// The action is a program which returns 1877345072 as a hardcoded value.
const std::vector<std::string> ids = {"gjpmebpgbhcamgdgjcmnjfhggjpgcimm"};
update_client->Update(
ids,
base::BindOnce(
[](const base::FilePath& unpack_path,
const std::vector<std::string>& ids) {
CrxComponent crx;
crx.name = "test_niea";
crx.pk_hash.assign(gjpm_hash, gjpm_hash + base::size(gjpm_hash));
crx.version = base::Version("1.0");
crx.installer =
base::MakeRefCounted<ReadOnlyTestInstaller>(unpack_path);
crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
return std::vector<base::Optional<CrxComponent>>{crx};
},
unpack_path),
false,
base::BindOnce(
[](base::OnceClosure quit_closure, Error error) {
EXPECT_EQ(Error::NONE, error);
std::move(quit_closure).Run();
},
quit_closure()));
RunThreads();
}
#endif // OS_WIN
} // namespace update_client