| // Copyright 2015 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <cstdint> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <string_view> |
| #include <tuple> |
| #include <type_traits> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/barrier_closure.h" |
| #include "base/check_deref.h" |
| #include "base/containers/flat_map.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/functional/bind.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/run_loop.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/task/task_traits.h" |
| #include "base/task/thread_pool.h" |
| #include "base/test/bind.h" |
| #include "base/test/mock_callback.h" |
| #include "base/test/scoped_path_override.h" |
| #include "base/test/task_environment.h" |
| #include "base/types/expected.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/services/unzip/in_process_unzipper.h" |
| #include "components/update_client/activity_data_service.h" |
| #include "components/update_client/crx_downloader_factory.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_definition.h" |
| #include "components/update_client/protocol_handler.h" |
| #include "components/update_client/protocol_parser_json.h" |
| #include "components/update_client/test_configurator.h" |
| #include "components/update_client/test_installer.h" |
| #include "components/update_client/test_utils.h" |
| #include "components/update_client/unpacker.h" |
| #include "components/update_client/unzip/unzip_impl.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 "components/update_client/update_engine.h" |
| #include "components/update_client/utils.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/gurl.h" |
| |
| namespace update_client { |
| namespace { |
| |
| // Makes stateful mock instances for callbacks in tests. These are callback |
| // mocks that retain state between successive invocations of the callback. |
| template <typename Mock> |
| auto MakeMockCallback() { |
| return base::BindRepeating(&Mock::Callback, base::MakeRefCounted<Mock>()); |
| } |
| |
| // Makes factories for creating update checker instances. `UpdateClient` uses |
| // the factory to make one update client checker for each update check. This |
| // factory of factories counts instances of update checkers made. The factory |
| // scope must enclose the scope of its clients since the `num_calls_` member |
| // is captured by the lambda. |
| template <typename MockUpdateChecker> |
| class MockUpdateCheckerFactory { |
| public: |
| typename MockUpdateChecker::Factory GetFactory() { |
| return base::BindLambdaForTesting( |
| [&](scoped_refptr<Configurator>) -> std::unique_ptr<UpdateChecker> { |
| if constexpr (std::is_default_constructible_v<MockUpdateChecker>) { |
| return std::make_unique<MockUpdateChecker>(); |
| } else { |
| return std::make_unique<MockUpdateChecker>(++num_calls_); |
| } |
| }); |
| } |
| |
| private: |
| int num_calls_ = 0; |
| }; |
| |
| // 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 base::FilePath& from_path, base::FilePath* to_path) { |
| base::FilePath temp_dir; |
| bool result = base::CreateNewTempDirectory(FILE_PATH_LITERAL("update_client"), |
| &temp_dir); |
| if (!result) { |
| return false; |
| } |
| |
| base::FilePath temp_file; |
| result = CreateTemporaryFileInDir(temp_dir, &temp_file); |
| if (!result) { |
| return false; |
| } |
| |
| result = CopyFile(from_path, temp_file); |
| if (!result) { |
| base::DeleteFile(temp_file); |
| return false; |
| } |
| |
| *to_path = temp_file; |
| return true; |
| } |
| |
| class MockObserver : public UpdateClient::Observer { |
| public: |
| MockObserver(const MockObserver&) = delete; |
| MockObserver operator=(const MockObserver&) = delete; |
| |
| explicit MockObserver(scoped_refptr<UpdateClient> update_client) |
| : update_client_(update_client) { |
| update_client_->AddObserver(this); |
| } |
| |
| ~MockObserver() override { update_client_->RemoveObserver(this); } |
| |
| MOCK_METHOD1(OnEvent, void(const CrxUpdateItem& item)); |
| |
| private: |
| const scoped_refptr<UpdateClient> update_client_; |
| }; |
| |
| class MockActionHandler : public ActionHandler { |
| public: |
| MockActionHandler() = default; |
| MockActionHandler(const MockActionHandler&) = delete; |
| MockActionHandler& operator=(const MockActionHandler&) = delete; |
| |
| MOCK_METHOD3(Handle, |
| void(const base::FilePath&, const std::string&, Callback)); |
| |
| private: |
| ~MockActionHandler() override = default; |
| }; |
| |
| class MockCrxStateChangeReceiver |
| : public base::RefCountedThreadSafe<MockCrxStateChangeReceiver> { |
| public: |
| MOCK_METHOD(void, Receive, (const CrxUpdateItem&)); |
| |
| private: |
| friend class base::RefCountedThreadSafe<MockCrxStateChangeReceiver>; |
| |
| ~MockCrxStateChangeReceiver() = default; |
| }; |
| |
| class MockCrxDownloaderFactory : public CrxDownloaderFactory { |
| public: |
| explicit MockCrxDownloaderFactory(scoped_refptr<CrxDownloader> crx_downloader) |
| : crx_downloader_(crx_downloader) {} |
| |
| private: |
| ~MockCrxDownloaderFactory() override = default; |
| |
| // Overrides for CrxDownloaderFactory. |
| scoped_refptr<CrxDownloader> MakeCrxDownloader( |
| bool /* background_download_enabled */) const override { |
| return crx_downloader_; |
| } |
| |
| scoped_refptr<CrxDownloader> crx_downloader_; |
| }; |
| |
| // Mocks the completion callback. |
| auto ExpectError(Error expected_error) { |
| return base::BindLambdaForTesting( |
| [=](Error actual_error) { EXPECT_EQ(expected_error, actual_error); }); |
| } |
| |
| auto ExpectErrorThenQuit(base::RunLoop& runloop, Error expected_error) { |
| return ExpectError(expected_error) |
| .Then(base::BindLambdaForTesting([&runloop]() { runloop.Quit(); })); |
| } |
| |
| auto ExpectErrorThenQuit(auto quit, Error expected_error) { |
| return ExpectError(expected_error).Then(std::move(quit)); |
| } |
| |
| struct UpdateCheckerOptionsOneCrxUpdate { |
| static constexpr int64_t kAvailableSpace = 3000; |
| static constexpr size_t kComponentCount = 1; |
| static constexpr std::string_view kJson = R"()]}' |
| { |
| "response": { |
| "protocol": "4.0", |
| "apps": [ |
| { |
| "appid": "jebgalgnebhfojomionfpkfelancnnkf", |
| "status": "ok", |
| "updatecheck": { |
| "status": "ok", |
| "nextversion": "1.0", |
| "pipelines": [ |
| { |
| "operations": [ |
| { |
| "type": "download", |
| "urls": [ |
| { |
| "url": "http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx" |
| } |
| ], |
| "out": { |
| "sha256": "7ab32f071cd9b5ef8e0d7913be161f532d98b3e9fa284a7cd8059c3409ce0498" |
| }, |
| "size": 1015 |
| }, |
| { |
| "type": "crx3", |
| "in": { |
| "sha256": "7ab32f071cd9b5ef8e0d7913be161f532d98b3e9fa284a7cd8059c3409ce0498" |
| } |
| } |
| ] |
| } |
| ] |
| } |
| } |
| ] |
| } |
| })"; |
| }; |
| |
| struct UpdateCheckerOptionsTwoCrxUpdate { |
| static constexpr int64_t kAvailableSpace = 150000; |
| static constexpr size_t kComponentCount = 2; |
| static constexpr std::string_view kJson = R"()]}' |
| { |
| "response": { |
| "protocol": "4.0", |
| "apps": [ |
| { |
| "appid": "jebgalgnebhfojomionfpkfelancnnkf", |
| "status": "ok", |
| "updatecheck": { |
| "status": "ok", |
| "nextversion": "1.0", |
| "pipelines": [ |
| { |
| "operations": [ |
| { |
| "type": "download", |
| "urls": [ |
| { |
| "url": "http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx" |
| } |
| ], |
| "out": { |
| "sha256": "7ab32f071cd9b5ef8e0d7913be161f532d98b3e9fa284a7cd8059c3409ce0498" |
| }, |
| "size": 1015 |
| }, |
| { |
| "type": "crx3", |
| "in": { |
| "sha256": "7ab32f071cd9b5ef8e0d7913be161f532d98b3e9fa284a7cd8059c3409ce0498" |
| } |
| } |
| ] |
| } |
| ] |
| } |
| }, |
| { |
| "appid": "ihfokbkgjpifnbbojhneepfflplebdkc", |
| "status": "ok", |
| "updatecheck": { |
| "status": "ok", |
| "nextversion": "1.0", |
| "pipelines": [ |
| { |
| "operations": [ |
| { |
| "type": "download", |
| "urls": [ |
| { |
| "url": "http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx" |
| } |
| ], |
| "out": { |
| "sha256": "8f5aa190311237cae00675af87ff457f278cd1a05895470ac5d46647d4a3c2ea" |
| }, |
| "size": 54014 |
| }, |
| { |
| "type": "crx3", |
| "in": { |
| "sha256": "8f5aa190311237cae00675af87ff457f278cd1a05895470ac5d46647d4a3c2ea" |
| } |
| } |
| ] |
| } |
| ] |
| } |
| } |
| ] |
| } |
| })"; |
| }; |
| |
| struct UpdateCheckerOptionsOneCrxInstall : UpdateCheckerOptionsOneCrxUpdate { |
| static constexpr std::string_view kJson = R"()]}' |
| { |
| "response": { |
| "protocol": "4.0", |
| "apps": [ |
| { |
| "appid": "jebgalgnebhfojomionfpkfelancnnkf", |
| "status": "ok", |
| "updatecheck": { |
| "status": "ok", |
| "nextversion": "1.0", |
| "pipelines": [ |
| { |
| "pipeline_id": "pipe1", |
| "operations": [ |
| { |
| "type": "download", |
| "urls": [ |
| { |
| "url": "http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx" |
| } |
| ], |
| "out": { |
| "sha256": "7ab32f071cd9b5ef8e0d7913be161f532d98b3e9fa284a7cd8059c3409ce0498" |
| }, |
| "size": 1015 |
| }, |
| { |
| "type": "crx3", |
| "arguments": "--arg1 --arg2", |
| "path": "UpdaterSetup.exe", |
| "in": { |
| "sha256": "7ab32f071cd9b5ef8e0d7913be161f532d98b3e9fa284a7cd8059c3409ce0498" |
| } |
| } |
| ] |
| } |
| ] |
| } |
| } |
| ] |
| } |
| })"; |
| }; |
| |
| struct UpdateCheckerOptionsTwoCrxUpdateServerIgnoresSecond |
| : UpdateCheckerOptionsOneCrxInstall { |
| static constexpr size_t kComponentCount = 2; |
| }; |
| |
| struct UpdateCheckerOptionsTwoCrxUpdateNoUpdate { |
| static constexpr int64_t kAvailableSpace = 3000; |
| static constexpr size_t kComponentCount = 2; |
| static constexpr std::string_view kJson = R"()]}' |
| { |
| "response": { |
| "protocol": "4.0", |
| "apps": [ |
| { |
| "appid": "jebgalgnebhfojomionfpkfelancnnkf", |
| "status": "ok", |
| "updatecheck": { |
| "status": "ok", |
| "nextversion": "1.0", |
| "pipelines": [ |
| { |
| "operations": [ |
| { |
| "type": "download", |
| "urls": [ |
| { |
| "url": "http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx" |
| } |
| ], |
| "out": { |
| "sha256": "7ab32f071cd9b5ef8e0d7913be161f532d98b3e9fa284a7cd8059c3409ce0498" |
| }, |
| "size": 1015 |
| }, |
| { |
| "type": "crx3", |
| "in": { |
| "sha256": "7ab32f071cd9b5ef8e0d7913be161f532d98b3e9fa284a7cd8059c3409ce0498" |
| } |
| } |
| ] |
| } |
| ] |
| } |
| }, |
| { |
| "appid": "abagagagagagagagagagagagagagagag", |
| "status": "ok", |
| "updatecheck": { |
| "status": "noupdate" |
| } |
| } |
| ] |
| } |
| })"; |
| }; |
| |
| struct UpdateCheckerOptionsActionRunNoUpdate { |
| static constexpr int64_t kAvailableSpace = 0; |
| static constexpr size_t kComponentCount = 1; |
| static constexpr std::string_view kJson = R"()]}' |
| { |
| "response": { |
| "protocol": "4.0", |
| "apps": [ |
| { |
| "appid": "gjpmebpgbhcamgdgjcmnjfhggjpgcimm", |
| "status": "ok", |
| "updatecheck": { |
| "status": "ok", |
| "nextversion": "1.0", |
| "pipelines": [ |
| { |
| "operations": [ |
| { |
| "type": "run", |
| "path": "ChromeRecovery.crx3" |
| } |
| ] |
| } |
| ] |
| } |
| } |
| ] |
| } |
| })"; |
| }; |
| |
| struct UpdateCheckerOptionsOneCrxInstallDiskFull |
| : UpdateCheckerOptionsOneCrxInstall { |
| static constexpr int64_t kAvailableSpace = 0; |
| }; |
| |
| struct UpdateCheckerOptionsUnsupportedOperationType |
| : UpdateCheckerOptionsOneCrxUpdate { |
| static constexpr std::string_view kJson = R"()]}' |
| { |
| "response": { |
| "protocol": "4.0", |
| "apps": [ |
| { |
| "appid": "jebgalgnebhfojomionfpkfelancnnkf", |
| "status": "ok", |
| "updatecheck": { |
| "status": "ok", |
| "nextversion": "1.0", |
| "pipelines": [ |
| { |
| "operations": [ |
| { |
| "type": "badoperation" |
| }, |
| { |
| "type": "crx3", |
| "arguments": "--arg1 --arg2", |
| "path": "UpdaterSetup.exe", |
| "in": { |
| "sha256": "7ab32f071cd9b5ef8e0d7913be161f532d98b3e9fa284a7cd8059c3409ce0498" |
| } |
| } |
| ] |
| } |
| ] |
| } |
| } |
| ] |
| } |
| })"; |
| }; |
| |
| template <typename T> |
| concept IsUpdateCheckerOptions = requires(T t) { |
| T::kAvailableSpace; |
| T::kComponentCount; |
| T::kJson; |
| }; |
| |
| template <typename Options> |
| requires IsUpdateCheckerOptions<Options> |
| class MockUpdateCheckerImpl : public UpdateChecker { |
| public: |
| void CheckForUpdates( |
| scoped_refptr<UpdateContext> context, |
| const base::flat_map<std::string, std::string>& additional_attributes, |
| UpdateCheckCallback update_check_callback) override { |
| context->get_available_space = base::BindRepeating( |
| [](const base::FilePath&) { return Options::kAvailableSpace; }); |
| base::expected<ProtocolParser::Results, std::string> results = |
| ProtocolParserJSON::ParseJSON(std::string(Options::kJson)); |
| EXPECT_TRUE(results.has_value()) << results.error(); |
| EXPECT_FALSE(context->session_id.empty()); |
| EXPECT_EQ(context->components_to_check_for_updates.size(), |
| Options::kComponentCount); |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(std::move(update_check_callback), |
| results.value(), ErrorCategory::kNone, 0, 0)); |
| } |
| }; |
| |
| class MockUpdateCheckerAlwaysFails : public UpdateChecker { |
| public: |
| MockUpdateCheckerAlwaysFails() = default; |
| |
| void CheckForUpdates( |
| scoped_refptr<UpdateContext> context, |
| const base::flat_map<std::string, std::string>& additional_attributes, |
| UpdateCheckCallback update_check_callback) override { |
| ADD_FAILURE() << "Check for update failed successfully."; |
| } |
| }; |
| |
| } // namespace |
| |
| using ::testing::_; |
| using ::testing::AnyNumber; |
| using ::testing::AtLeast; |
| using ::testing::DoAll; |
| using ::testing::InSequence; |
| using ::testing::Mock; |
| using ::testing::Return; |
| using ::testing::Truly; |
| using ::testing::Unused; |
| |
| 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; |
| int event_type; |
| int event_result; |
| std::string pipeline_id; |
| }; |
| |
| explicit MockPingManagerImpl(scoped_refptr<Configurator> config); |
| MockPingManagerImpl(const MockPingManagerImpl&) = delete; |
| MockPingManagerImpl& operator=(const MockPingManagerImpl&) = delete; |
| |
| void SendPing(const std::string& session_id, |
| const CrxComponent& component, |
| std::vector<base::Value::Dict> events, |
| base::OnceClosure callback) override; |
| |
| const std::vector<PingData>& ping_data() const; |
| const std::vector<PingData>& terminal_ping_data() const; |
| |
| const std::vector<base::Value::Dict>& events() const; |
| |
| protected: |
| ~MockPingManagerImpl() override; |
| |
| private: |
| std::vector<PingData> ping_data_; |
| std::vector<PingData> terminal_ping_data_; |
| std::vector<base::Value::Dict> events_; |
| }; |
| |
| MockPingManagerImpl::MockPingManagerImpl(scoped_refptr<Configurator> config) |
| : PingManager(config) {} |
| |
| MockPingManagerImpl::~MockPingManagerImpl() = default; |
| |
| void MockPingManagerImpl::SendPing(const std::string& session_id, |
| const CrxComponent& component, |
| std::vector<base::Value::Dict> events, |
| base::OnceClosure callback) { |
| for (const base::Value::Dict& event : events) { |
| PingData ping_data; |
| ping_data.id = component.app_id; |
| int event_type = event.FindInt("eventtype").value_or(0); |
| const std::string* previous_version = event.FindString("previousversion"); |
| if (previous_version) { |
| ping_data.previous_version = base::Version(*previous_version); |
| } |
| ping_data.event_result = event.FindInt("eventresult").value_or(0); |
| const std::string* next_version = event.FindString("nextversion"); |
| if (next_version) { |
| ping_data.next_version = base::Version(*next_version); |
| } |
| std::optional<int> error_category = event.FindInt("errorcat"); |
| if (error_category) { |
| ping_data.error_category = static_cast<ErrorCategory>(*error_category); |
| } |
| std::optional<int> error_code = event.FindInt("errorcode"); |
| if (error_code) { |
| ping_data.error_code = *error_code; |
| } |
| std::optional<int> extra_code1 = event.FindInt("extracode1"); |
| if (extra_code1) { |
| ping_data.extra_code1 = *extra_code1; |
| } |
| const std::string* pipeline_id = event.FindString("pipeline_id"); |
| if (pipeline_id) { |
| ping_data.pipeline_id = *pipeline_id; |
| } |
| if (event_type != 0) { |
| ping_data.event_type = event_type; |
| } |
| ping_data_.push_back(ping_data); |
| if (event_type == protocol_request::kEventInstall || |
| event_type == protocol_request::kEventUpdate || |
| event_type == protocol_request::kEventUninstall) { |
| terminal_ping_data_.push_back(ping_data); |
| } |
| } |
| events_ = std::move(events); |
| |
| std::move(callback).Run(); |
| } |
| |
| const std::vector<MockPingManagerImpl::PingData>& |
| MockPingManagerImpl::ping_data() const { |
| return ping_data_; |
| } |
| |
| const std::vector<MockPingManagerImpl::PingData>& |
| MockPingManagerImpl::terminal_ping_data() const { |
| return terminal_ping_data_; |
| } |
| |
| const std::vector<base::Value::Dict>& MockPingManagerImpl::events() const { |
| return events_; |
| } |
| |
| class UpdateClientTest : public testing::Test { |
| private: |
| // Must be initialized before `runloop_`. |
| base::test::TaskEnvironment task_environment_; |
| |
| protected: |
| UpdateClientTest() { |
| RegisterPersistedDataPrefs(pref_->registry()); |
| config_ = base::MakeRefCounted<TestConfigurator>(pref_.get()); |
| } |
| |
| scoped_refptr<update_client::TestConfigurator> config() { return config_; } |
| |
| // Injects the CrxDownloaderFactory in the test fixture. |
| template <typename MockCrxDownloaderT> |
| void SetMockCrxDownloader() { |
| config()->SetCrxDownloaderFactory( |
| base::MakeRefCounted<MockCrxDownloaderFactory>( |
| base::MakeRefCounted<MockCrxDownloaderT>())); |
| } |
| |
| base::RunLoop runloop_; |
| |
| private: |
| std::unique_ptr<TestingPrefServiceSimple> pref_ = |
| std::make_unique<TestingPrefServiceSimple>(); |
| scoped_refptr<update_client::TestConfigurator> config_; |
| }; |
| |
| // 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 void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| CrxComponent crx; |
| crx.app_id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| crx.name = "test_jebg"; |
| crx.pk_hash.assign(std::begin(jebg_hash), std::end(jebg_hash)); |
| crx.version = base::Version("0.9"); |
| crx.installer = base::MakeRefCounted<TestInstaller>(); |
| crx.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| std::vector<std::optional<CrxComponent>> component = {crx}; |
| std::move(callback).Run({component}); |
| } |
| }; |
| |
| class MockUpdateChecker : public UpdateChecker { |
| public: |
| MockUpdateChecker() = default; |
| |
| void CheckForUpdates( |
| scoped_refptr<UpdateContext> context, |
| const base::flat_map<std::string, std::string>& additional_attributes, |
| UpdateCheckCallback update_check_callback) override { |
| EXPECT_FALSE(context->session_id.empty()); |
| EXPECT_EQ(1u, context->components_to_check_for_updates.size()); |
| const std::string id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| EXPECT_EQ(id, context->components_to_check_for_updates.front()); |
| EXPECT_EQ(1u, context->components.count(id)); |
| |
| auto& component = context->components.at(id); |
| |
| EXPECT_TRUE(component->is_foreground()); |
| |
| ProtocolParser::App result; |
| result.app_id = id; |
| result.status = "noupdate"; |
| |
| ProtocolParser::Results results; |
| results.apps.push_back(result); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(std::move(update_check_callback), results, |
| ErrorCategory::kNone, 0, 0)); |
| } |
| }; |
| MockUpdateCheckerFactory<MockUpdateChecker> mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| ADD_FAILURE(); |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| MockObserver observer(update_client); |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.state == ComponentState::kUpToDate; |
| }))); |
| } |
| |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| update_client->Update( |
| {"jebgalgnebhfojomionfpkfelancnnkf"}, |
| base::BindOnce(&DataCallbackMock::Callback), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), true, |
| ExpectErrorThenQuit(runloop_, Error::NONE)); |
| runloop_.Run(); |
| |
| EXPECT_EQ(2u, items.size()); |
| EXPECT_EQ(ComponentState::kChecking, items[0].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[0].id); |
| EXPECT_EQ(ComponentState::kUpToDate, items[1].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[1].id); |
| } |
| |
| // 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 void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| CrxComponent crx1; |
| crx1.app_id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| crx1.name = "test_jebg"; |
| crx1.pk_hash.assign(std::begin(jebg_hash), std::end(jebg_hash)); |
| crx1.version = base::Version("0.9"); |
| crx1.installer = base::MakeRefCounted<TestInstaller>(); |
| crx1.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| |
| CrxComponent crx2; |
| crx2.app_id = "abagagagagagagagagagagagagagagag"; |
| crx2.name = "test_abag"; |
| crx2.pk_hash.assign(std::begin(abag_hash), std::end(abag_hash)); |
| crx2.version = base::Version("2.2"); |
| crx2.installer = base::MakeRefCounted<TestInstaller>(); |
| crx2.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| |
| std::move(callback).Run({crx1, crx2}); |
| } |
| }; |
| |
| MockUpdateCheckerFactory< |
| MockUpdateCheckerImpl<UpdateCheckerOptionsTwoCrxUpdateNoUpdate>> |
| mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure 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 = 1015; |
| download_metrics.total_bytes = 1015; |
| download_metrics.download_time_ms = 1000; |
| |
| base::FilePath path; |
| EXPECT_TRUE(MakeTestFile( |
| GetTestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"), &path)); |
| |
| Result result; |
| result.error = 0; |
| result.response = path; |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress, |
| base::Unretained(this), |
| download_metrics.downloaded_bytes / 2, |
| download_metrics.total_bytes)); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress, |
| base::Unretained(this), |
| download_metrics.downloaded_bytes, |
| download_metrics.total_bytes)); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete, |
| base::Unretained(this), true, result, |
| download_metrics)); |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { |
| const auto ping_data = MockPingManagerImpl::terminal_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); |
| } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| MockObserver observer(update_client); |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.state == ComponentState::kChecking && |
| item.id == "jebgalgnebhfojomionfpkfelancnnkf"; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.state == ComponentState::kCanUpdate && |
| item.id == "jebgalgnebhfojomionfpkfelancnnkf"; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.state == ComponentState::kDownloading && |
| item.id == "jebgalgnebhfojomionfpkfelancnnkf"; |
| }))) |
| .Times(AtLeast(1)); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.state == ComponentState::kUpdating && |
| item.id == "jebgalgnebhfojomionfpkfelancnnkf"; |
| }))) |
| .Times(AtLeast(1)); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.state == ComponentState::kUpdated && |
| item.id == "jebgalgnebhfojomionfpkfelancnnkf"; |
| }))); |
| } |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.state == ComponentState::kChecking && |
| item.id == "abagagagagagagagagagagagagagagag"; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.state == ComponentState::kUpToDate && |
| item.id == "abagagagagagagagagagagagagagagag"; |
| }))); |
| } |
| |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf", |
| "abagagagagagagagagagagagagagagag"}; |
| update_client->Update( |
| ids, base::BindOnce(&DataCallbackMock::Callback), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| false, ExpectErrorThenQuit(runloop_, Error::NONE)); |
| runloop_.Run(); |
| |
| EXPECT_EQ(9u, items.size()); |
| EXPECT_EQ(ComponentState::kChecking, items[0].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[0].id); |
| EXPECT_EQ(ComponentState::kChecking, items[1].state); |
| EXPECT_EQ("abagagagagagagagagagagagagagagag", items[1].id); |
| EXPECT_EQ(ComponentState::kCanUpdate, items[2].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[2].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[3].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[3].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[4].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[4].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[5].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[5].id); |
| EXPECT_EQ(ComponentState::kUpdating, items[6].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[6].id); |
| EXPECT_EQ(ComponentState::kUpdated, items[7].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[7].id); |
| EXPECT_EQ(ComponentState::kUpToDate, items[8].state); |
| EXPECT_EQ("abagagagagagagagagagagagagagagag", items[8].id); |
| |
| std::vector<std::tuple<int64_t, int64_t>> progress_bytes = { |
| {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {507, 1015}, |
| {1015, 1015}, {1015, 1015}, {1015, 1015}, {-1, -1}}; |
| EXPECT_EQ(items.size(), progress_bytes.size()); |
| for (size_t i{0}; i != items.size(); ++i) { |
| EXPECT_EQ(items[i].downloaded_bytes, std::get<0>(progress_bytes[i])); |
| EXPECT_EQ(items[i].total_bytes, std::get<1>(progress_bytes[i])); |
| } |
| } |
| |
| // 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 void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| CrxComponent crx1; |
| crx1.app_id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| crx1.name = "test_jebg"; |
| crx1.pk_hash.assign(std::begin(jebg_hash), std::end(jebg_hash)); |
| crx1.version = base::Version("0.9"); |
| crx1.installer = base::MakeRefCounted<TestInstaller>(); |
| crx1.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| |
| CrxComponent crx2; |
| crx2.name = "test_abag"; |
| crx2.pk_hash.assign(std::begin(abag_hash), std::end(abag_hash)); |
| crx2.version = base::Version("2.2"); |
| crx2.installer = base::MakeRefCounted<TestInstaller>(); |
| crx2.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| |
| std::move(callback).Run({crx1, crx2}); |
| } |
| }; |
| |
| // Note: even though 2 appid's are requested, we are intentionally only |
| // sending the first. |
| MockUpdateCheckerFactory<MockUpdateCheckerImpl< |
| UpdateCheckerOptionsTwoCrxUpdateServerIgnoresSecond>> |
| mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure 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 = 1015; |
| download_metrics.total_bytes = 1015; |
| download_metrics.download_time_ms = 1000; |
| |
| base::FilePath path; |
| EXPECT_TRUE(MakeTestFile( |
| GetTestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"), &path)); |
| |
| Result result; |
| result.error = 0; |
| result.response = path; |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress, |
| base::Unretained(this), |
| download_metrics.downloaded_bytes, |
| download_metrics.total_bytes)); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete, |
| base::Unretained(this), true, result, |
| download_metrics)); |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { |
| const auto ping_data = MockPingManagerImpl::terminal_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); |
| } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| MockObserver observer(update_client); |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.state == ComponentState::kChecking && |
| item.id == "jebgalgnebhfojomionfpkfelancnnkf"; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.state == ComponentState::kCanUpdate && |
| item.id == "jebgalgnebhfojomionfpkfelancnnkf"; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.state == ComponentState::kDownloading && |
| item.id == "jebgalgnebhfojomionfpkfelancnnkf"; |
| }))) |
| .Times(AtLeast(1)); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.state == ComponentState::kUpdating && |
| item.id == "jebgalgnebhfojomionfpkfelancnnkf"; |
| }))) |
| .Times(AtLeast(1)); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.state == ComponentState::kUpdated && |
| item.id == "jebgalgnebhfojomionfpkfelancnnkf"; |
| }))); |
| } |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.state == ComponentState::kChecking && |
| item.id == "abagagagagagagagagagagagagagagag"; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.state == ComponentState::kUpdateError && |
| item.id == "abagagagagagagagagagagagagagagag"; |
| }))) |
| .WillOnce([](const CrxUpdateItem& 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); |
| }); |
| } |
| |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf", |
| "abagagagagagagagagagagagagagagag"}; |
| update_client->Update( |
| ids, base::BindOnce(&DataCallbackMock::Callback), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| false, ExpectErrorThenQuit(runloop_, Error::NONE)); |
| runloop_.Run(); |
| |
| EXPECT_EQ(8u, items.size()); |
| EXPECT_EQ(ComponentState::kChecking, items[0].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[0].id); |
| EXPECT_EQ(ComponentState::kChecking, items[1].state); |
| EXPECT_EQ("abagagagagagagagagagagagagagagag", items[1].id); |
| EXPECT_EQ(ComponentState::kCanUpdate, items[2].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[2].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[3].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[3].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[4].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[4].id); |
| EXPECT_EQ(ComponentState::kUpdating, items[5].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[5].id); |
| EXPECT_EQ(ComponentState::kUpdated, items[6].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[6].id); |
| EXPECT_EQ(ComponentState::kUpdateError, items[7].state); |
| EXPECT_EQ("abagagagagagagagagagagagagagagag", items[7].id); |
| } |
| |
| // 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 void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| CrxComponent crx; |
| crx.app_id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| crx.name = "test_jebg"; |
| crx.pk_hash.assign(std::begin(jebg_hash), std::end(jebg_hash)); |
| crx.version = base::Version("0.9"); |
| crx.installer = base::MakeRefCounted<TestInstaller>(); |
| crx.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| std::move(callback).Run({crx, std::nullopt}); |
| } |
| }; |
| |
| // Note: even though 2 appid's are requested, since "ihfo..." has no component |
| // data, the response only contains the first appid's update response. |
| MockUpdateCheckerFactory< |
| MockUpdateCheckerImpl<UpdateCheckerOptionsOneCrxUpdate>> |
| mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| DownloadMetrics download_metrics; |
| base::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 = 1015; |
| download_metrics.total_bytes = 1015; |
| download_metrics.download_time_ms = 1000; |
| |
| EXPECT_TRUE(MakeTestFile( |
| GetTestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"), &path)); |
| |
| result.error = 0; |
| result.response = path; |
| } else { |
| ADD_FAILURE(); |
| } |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress, |
| base::Unretained(this), |
| download_metrics.downloaded_bytes, |
| download_metrics.total_bytes)); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete, |
| base::Unretained(this), true, result, |
| download_metrics)); |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { |
| const auto ping_data = MockPingManagerImpl::terminal_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); |
| } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| MockObserver observer(update_client); |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kCanUpdate; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kDownloading; |
| }))) |
| .Times(AtLeast(1)); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpdating; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpdated; |
| }))); |
| } |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kUpdateError; |
| }))); |
| } |
| |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf", |
| "ihfokbkgjpifnbbojhneepfflplebdkc"}; |
| update_client->Update( |
| ids, base::BindOnce(&DataCallbackMock::Callback), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| false, ExpectErrorThenQuit(runloop_, Error::NONE)); |
| runloop_.Run(); |
| |
| EXPECT_EQ(7u, items.size()); |
| EXPECT_EQ(ComponentState::kChecking, items[0].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[0].id); |
| EXPECT_EQ(ComponentState::kUpdateError, items[1].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[1].id); |
| EXPECT_EQ(ComponentState::kCanUpdate, items[2].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[2].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[3].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[3].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[4].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[4].id); |
| EXPECT_EQ(ComponentState::kUpdating, items[5].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[5].id); |
| EXPECT_EQ(ComponentState::kUpdated, items[6].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[6].id); |
| } |
| |
| // 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 void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| std::move(callback).Run({std::nullopt, std::nullopt}); |
| } |
| }; |
| |
| MockUpdateCheckerFactory<MockUpdateCheckerAlwaysFails> |
| mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| ADD_FAILURE(); |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { |
| EXPECT_EQ(0u, MockPingManagerImpl::ping_data().size()); |
| } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| MockObserver observer(update_client); |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpdateError; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kUpdateError; |
| }))); |
| } |
| |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf", |
| "ihfokbkgjpifnbbojhneepfflplebdkc"}; |
| update_client->Update( |
| ids, base::BindOnce(&DataCallbackMock::Callback), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| false, ExpectErrorThenQuit(runloop_, Error::NONE)); |
| runloop_.Run(); |
| |
| EXPECT_EQ(2u, items.size()); |
| EXPECT_EQ(ComponentState::kUpdateError, items[0].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[0].id); |
| EXPECT_EQ(ComponentState::kUpdateError, items[1].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[1].id); |
| } |
| |
| // 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 void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| CrxComponent crx1; |
| crx1.app_id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| crx1.name = "test_jebg"; |
| crx1.pk_hash.assign(std::begin(jebg_hash), std::end(jebg_hash)); |
| crx1.version = base::Version("0.9"); |
| crx1.installer = base::MakeRefCounted<TestInstaller>(); |
| crx1.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| |
| CrxComponent crx2; |
| crx2.app_id = "ihfokbkgjpifnbbojhneepfflplebdkc"; |
| crx2.name = "test_ihfo"; |
| crx2.pk_hash.assign(std::begin(ihfo_hash), std::end(ihfo_hash)); |
| crx2.version = base::Version("0.8"); |
| crx2.installer = base::MakeRefCounted<TestInstaller>(); |
| crx2.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| |
| std::move(callback).Run({crx1, crx2}); |
| } |
| }; |
| |
| MockUpdateCheckerFactory< |
| MockUpdateCheckerImpl<UpdateCheckerOptionsTwoCrxUpdate>> |
| mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| DownloadMetrics download_metrics; |
| base::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 = 54014; |
| download_metrics.total_bytes = 54014; |
| download_metrics.download_time_ms = 2000; |
| |
| EXPECT_TRUE(MakeTestFile( |
| GetTestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"), &path)); |
| |
| result.error = 0; |
| result.response = path; |
| } else { |
| ADD_FAILURE(); |
| } |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress, |
| base::Unretained(this), |
| download_metrics.downloaded_bytes, |
| download_metrics.total_bytes)); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete, |
| base::Unretained(this), true, result, |
| download_metrics)); |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { |
| const auto ping_data = MockPingManagerImpl::terminal_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); |
| } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| MockObserver observer(update_client); |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kCanUpdate; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kDownloading; |
| }))) |
| .Times(AtLeast(1)); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpdateError; |
| }))) |
| .WillOnce([](const CrxUpdateItem& 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(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kCanUpdate; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kDownloading; |
| }))) |
| .Times(AtLeast(1)); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kUpdating; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kUpdated; |
| }))); |
| } |
| |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf", |
| "ihfokbkgjpifnbbojhneepfflplebdkc"}; |
| update_client->Update( |
| ids, base::BindOnce(&DataCallbackMock::Callback), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| false, ExpectErrorThenQuit(runloop_, Error::NONE)); |
| runloop_.Run(); |
| |
| EXPECT_EQ(11u, items.size()); |
| EXPECT_EQ(ComponentState::kChecking, items[0].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[0].id); |
| EXPECT_EQ(ComponentState::kChecking, items[1].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[1].id); |
| EXPECT_EQ(ComponentState::kCanUpdate, items[2].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[2].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[3].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[3].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[4].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[4].id); |
| EXPECT_EQ(ComponentState::kUpdateError, items[5].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[5].id); |
| EXPECT_EQ(ComponentState::kCanUpdate, items[6].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[6].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[7].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[7].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[8].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[8].id); |
| EXPECT_EQ(ComponentState::kUpdating, items[9].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[9].id); |
| EXPECT_EQ(ComponentState::kUpdated, items[10].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[10].id); |
| } |
| |
| // Tests the differential update scenario for one CRX. Tests install progress |
| // for differential and full updates. |
| TEST_F(UpdateClientTest, OneCrxDiffUpdate) { |
| class DataCallbackMock : public base::RefCountedThreadSafe<DataCallbackMock> { |
| public: |
| DataCallbackMock() { |
| installer_->set_installer_progress_samples({-1, 50, 100}); |
| } |
| |
| void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| ++num_calls_; |
| |
| CrxComponent crx; |
| crx.app_id = "ihfokbkgjpifnbbojhneepfflplebdkc"; |
| crx.name = "test_ihfo"; |
| crx.pk_hash.assign(std::begin(ihfo_hash), std::end(ihfo_hash)); |
| crx.installer = installer_; |
| crx.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| if (num_calls_ == 1) { |
| crx.version = base::Version("0.8"); |
| } else if (num_calls_ == 2) { |
| crx.version = base::Version("1.0"); |
| } else { |
| ADD_FAILURE(); |
| } |
| |
| std::move(callback).Run({crx}); |
| } |
| |
| private: |
| friend class base::RefCountedThreadSafe<DataCallbackMock>; |
| ~DataCallbackMock() = default; |
| |
| int num_calls_ = 0; |
| scoped_refptr<VersionedTestInstaller> installer_ = |
| base::MakeRefCounted<VersionedTestInstaller>(); |
| }; |
| auto data_callback_mock = MakeMockCallback<DataCallbackMock>(); |
| |
| class MockUpdateChecker : public UpdateChecker { |
| public: |
| explicit MockUpdateChecker(int num_calls) : num_calls_(num_calls) {} |
| |
| void CheckForUpdates( |
| scoped_refptr<UpdateContext> context, |
| const base::flat_map<std::string, std::string>& additional_attributes, |
| UpdateCheckCallback update_check_callback) override { |
| context->get_available_space = base::BindRepeating( |
| [](const base::FilePath&) -> int64_t { return 200000; }); |
| base::expected<ProtocolParser::Results, std::string> results; |
| if (num_calls_ == 1) { |
| context->get_available_space = base::BindRepeating( |
| [](const base::FilePath&) -> int64_t { return 150000; }); |
| results = ProtocolParserJSON::ParseJSON(R"()]}' |
| { |
| "response": { |
| "protocol": "4.0", |
| "apps": [ |
| { |
| "appid": "ihfokbkgjpifnbbojhneepfflplebdkc", |
| "status": "ok", |
| "updatecheck": { |
| "status": "ok", |
| "nextversion": "1.0", |
| "pipelines": [ |
| { |
| "operations": [ |
| { |
| "type": "download", |
| "urls": [ |
| { |
| "url": "http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx" |
| } |
| ], |
| "out": { |
| "sha256": "8f5aa190311237cae00675af87ff457f278cd1a05895470ac5d46647d4a3c2ea" |
| }, |
| "size": 54014 |
| }, |
| { |
| "type": "crx3", |
| "in": { |
| "sha256": "8f5aa190311237cae00675af87ff457f278cd1a05895470ac5d46647d4a3c2ea" |
| } |
| } |
| ] |
| } |
| ] |
| } |
| } |
| ] |
| } |
| })"); |
| } else if (num_calls_ == 2) { |
| context->get_available_space = base::BindRepeating( |
| [](const base::FilePath&) -> int64_t { return 4000; }); |
| results = ProtocolParserJSON::ParseJSON(R"()]}' |
| { |
| "response": { |
| "protocol": "4.0", |
| "apps": [ |
| { |
| "appid": "ihfokbkgjpifnbbojhneepfflplebdkc", |
| "status": "ok", |
| "updatecheck": { |
| "status": "ok", |
| "nextversion": "2.0", |
| "pipelines": [ |
| { |
| "operations": [ |
| { |
| "type": "download", |
| "urls": [ |
| { |
| "url": "http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_1to2.puff" |
| } |
| ], |
| "out": { |
| "sha256": "f2254da51fa2478a8ba90e58e1c28e24033ec7841015eebf1c82e31b957c44b2" |
| }, |
| "size": 1680 |
| }, |
| { |
| "type": "puff", |
| "previous": { |
| "sha256": "8f5aa190311237cae00675af87ff457f278cd1a05895470ac5d46647d4a3c2ea" |
| }, |
| "out": { |
| "sha256": "c87d8742c3ff3d7a0cb6f3c91aa2fcf3dea63618086a7db1c5be5300e1d4d6b6" |
| } |
| }, |
| { |
| "type": "crx3", |
| "in": { |
| "sha256": "c87d8742c3ff3d7a0cb6f3c91aa2fcf3dea63618086a7db1c5be5300e1d4d6b6" |
| } |
| } |
| ] |
| } |
| ] |
| } |
| } |
| ] |
| } |
| })"); |
| } else { |
| ADD_FAILURE(); |
| } |
| EXPECT_TRUE(results.has_value()) << results.error(); |
| EXPECT_FALSE(context->session_id.empty()); |
| EXPECT_EQ(context->components_to_check_for_updates.size(), 1u); |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(update_check_callback), results.value(), |
| ErrorCategory::kNone, 0, 0)); |
| } |
| |
| private: |
| const int num_calls_; |
| }; |
| MockUpdateCheckerFactory<MockUpdateChecker> mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| DownloadMetrics download_metrics; |
| base::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 = 54014; |
| download_metrics.total_bytes = 54014; |
| download_metrics.download_time_ms = 2000; |
| |
| EXPECT_TRUE(MakeTestFile( |
| GetTestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"), &path)); |
| |
| result.error = 0; |
| result.response = path; |
| } else if (url.path() == |
| "/download/ihfokbkgjpifnbbojhneepfflplebdkc_1to2.puff") { |
| download_metrics.url = url; |
| download_metrics.downloader = DownloadMetrics::kNone; |
| download_metrics.error = 0; |
| download_metrics.downloaded_bytes = 1680; |
| download_metrics.total_bytes = 1680; |
| download_metrics.download_time_ms = 1000; |
| |
| EXPECT_TRUE(MakeTestFile( |
| GetTestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_1to2.puff"), |
| &path)); |
| |
| result.error = 0; |
| result.response = path; |
| } else { |
| ADD_FAILURE() << url.path(); |
| } |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress, |
| base::Unretained(this), |
| download_metrics.downloaded_bytes / 2, |
| download_metrics.total_bytes)); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress, |
| base::Unretained(this), |
| download_metrics.downloaded_bytes, |
| download_metrics.total_bytes)); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete, |
| base::Unretained(this), true, result, |
| download_metrics)); |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { |
| const auto ping_data = MockPingManagerImpl::terminal_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, ping_data[1].error_code); |
| } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| MockObserver observer(update_client); |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kCanUpdate; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kDownloading; |
| }))) |
| .Times(AtLeast(1)); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kUpdating; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kUpdating; |
| }))) |
| .Times(3); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kUpdated; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kCanUpdate; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kDownloading; |
| }))) |
| .Times(AtLeast(1)); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kUpdating; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kUpdating; |
| }))) |
| .Times(3); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kUpdated; |
| }))); |
| } |
| |
| const std::vector<std::string> ids = {"ihfokbkgjpifnbbojhneepfflplebdkc"}; |
| { |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| base::RunLoop runloop; |
| update_client->Update( |
| ids, data_callback_mock, |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| false, ExpectErrorThenQuit(runloop, Error::NONE)); |
| runloop.Run(); |
| EXPECT_EQ(10u, items.size()); |
| EXPECT_EQ(ComponentState::kChecking, items[0].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[0].id); |
| EXPECT_EQ(ComponentState::kCanUpdate, items[1].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[1].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[2].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[2].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[3].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[3].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[4].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[4].id); |
| EXPECT_EQ(ComponentState::kUpdating, items[5].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[5].id); |
| EXPECT_EQ(ComponentState::kUpdating, items[6].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[6].id); |
| EXPECT_EQ(ComponentState::kUpdating, items[7].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[7].id); |
| EXPECT_EQ(ComponentState::kUpdating, items[8].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[8].id); |
| EXPECT_EQ(ComponentState::kUpdated, items[9].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[9].id); |
| |
| std::vector samples = {-1, -1, -1, -1, -1, -1, -1, 50, 100, 100}; |
| EXPECT_EQ(items.size(), samples.size()); |
| for (size_t i = 0; i != items.size(); ++i) { |
| EXPECT_EQ(items[i].install_progress, samples[i]); |
| } |
| } |
| |
| { |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| base::RunLoop runloop; |
| update_client->Update( |
| ids, data_callback_mock, |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| false, ExpectErrorThenQuit(runloop, Error::NONE)); |
| runloop.Run(); |
| |
| EXPECT_EQ(10u, items.size()); |
| EXPECT_EQ(ComponentState::kChecking, items[0].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[0].id); |
| EXPECT_EQ(ComponentState::kCanUpdate, items[1].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[1].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[2].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[2].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[3].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[3].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[4].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[4].id); |
| EXPECT_EQ(ComponentState::kUpdating, items[5].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[5].id); |
| EXPECT_EQ(ComponentState::kUpdating, items[6].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[6].id); |
| EXPECT_EQ(ComponentState::kUpdating, items[7].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[7].id); |
| EXPECT_EQ(ComponentState::kUpdating, items[8].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[8].id); |
| EXPECT_EQ(ComponentState::kUpdated, items[9].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[9].id); |
| |
| std::vector<int> samples = {-1, -1, -1, -1, -1, -1, -1, 50, 100, 100}; |
| EXPECT_EQ(items.size(), samples.size()); |
| for (size_t i = 0; i != items.size(); ++i) { |
| EXPECT_EQ(items[i].install_progress, samples[i]); |
| } |
| } |
| } |
| |
| // 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_METHOD1(DoInstall, void(const base::FilePath& unpack_path)); |
| MOCK_METHOD1(GetInstalledFile, |
| std::optional<base::FilePath>(const std::string& file)); |
| MOCK_METHOD0(Uninstall, bool()); |
| |
| void Install(const base::FilePath& unpack_path, |
| const std::string& public_key, |
| std::unique_ptr<InstallParams> /*install_params*/, |
| ProgressCallback progress_callback, |
| Callback callback) override { |
| DoInstall(unpack_path); |
| |
| unpack_path_ = unpack_path; |
| EXPECT_TRUE(base::DirectoryExists(unpack_path_)); |
| base::ThreadPool::PostTask( |
| FROM_HERE, {base::MayBlock()}, |
| base::BindOnce( |
| std::move(callback), |
| CrxInstaller::Result( |
| {.category = ErrorCategory::kInstaller, |
| .code = static_cast<int>(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 void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| scoped_refptr<MockInstaller> installer = |
| base::MakeRefCounted<MockInstaller>(); |
| |
| EXPECT_CALL(*installer, OnUpdateError(_)).Times(0); |
| EXPECT_CALL(*installer, DoInstall(_)); |
| EXPECT_CALL(*installer, GetInstalledFile(_)).Times(0); |
| EXPECT_CALL(*installer, Uninstall()).Times(0); |
| |
| CrxComponent crx; |
| crx.app_id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| crx.name = "test_jebg"; |
| crx.pk_hash.assign(std::begin(jebg_hash), std::end(jebg_hash)); |
| crx.version = base::Version("0.9"); |
| crx.installer = installer; |
| crx.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| |
| std::move(callback).Run({crx}); |
| } |
| }; |
| |
| MockUpdateCheckerFactory< |
| MockUpdateCheckerImpl<UpdateCheckerOptionsOneCrxInstall>> |
| mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure 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 = 1015; |
| download_metrics.total_bytes = 1015; |
| download_metrics.download_time_ms = 1000; |
| |
| base::FilePath path; |
| EXPECT_TRUE(MakeTestFile( |
| GetTestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"), &path)); |
| |
| Result result; |
| result.error = 0; |
| result.response = path; |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress, |
| base::Unretained(this), |
| download_metrics.downloaded_bytes, |
| download_metrics.total_bytes)); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete, |
| base::Unretained(this), true, result, |
| download_metrics)); |
| return base::DoNothing(); |
| } |
| }; |
| |
| 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(3u, ping_data.size()); |
| // Expect that the download ping carries the pipeline id. |
| EXPECT_EQ(ping_data[0].pipeline_id, "pipe1"); // Download |
| EXPECT_EQ(ping_data[0].error_category, ErrorCategory::kNone); |
| EXPECT_EQ(static_cast<CrxDownloaderError>(ping_data[0].error_code), |
| CrxDownloaderError::NONE); |
| |
| EXPECT_EQ(ping_data[1].pipeline_id, "pipe1"); // crx3 |
| EXPECT_EQ(ping_data[1].error_category, ErrorCategory::kInstaller); |
| EXPECT_EQ(static_cast<InstallError>(ping_data[1].error_code), |
| InstallError::GENERIC_ERROR); |
| |
| EXPECT_EQ(ping_data[2].id, "jebgalgnebhfojomionfpkfelancnnkf"); |
| EXPECT_EQ(ping_data[2].previous_version, base::Version("0.9")); |
| EXPECT_EQ(ping_data[2].next_version, base::Version("1.0")); |
| EXPECT_EQ(ping_data[2].error_category, ErrorCategory::kInstaller); |
| EXPECT_EQ(static_cast<InstallError>(ping_data[2].error_code), |
| InstallError::GENERIC_ERROR); |
| } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| MockObserver observer(update_client); |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kCanUpdate; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kDownloading; |
| }))) |
| .Times(AtLeast(1)); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpdating; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpdateError; |
| }))); |
| } |
| |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| update_client->Update( |
| {"jebgalgnebhfojomionfpkfelancnnkf"}, |
| base::BindOnce(&DataCallbackMock::Callback), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| false, ExpectErrorThenQuit(runloop_, Error::NONE)); |
| runloop_.Run(); |
| |
| EXPECT_EQ(6u, items.size()); |
| EXPECT_EQ(ComponentState::kChecking, items[0].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[0].id); |
| EXPECT_EQ(ComponentState::kCanUpdate, items[1].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[1].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[2].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[2].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[3].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[3].id); |
| EXPECT_EQ(ComponentState::kUpdating, items[4].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[4].id); |
| EXPECT_EQ(ComponentState::kUpdateError, items[5].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[5].id); |
| } |
| |
| // Tests the fallback from differential to full update scenario for one CRX. |
| TEST_F(UpdateClientTest, OneCrxDiffUpdateFailsFullUpdateSucceeds) { |
| class DataCallbackMock : public base::RefCountedThreadSafe<DataCallbackMock> { |
| public: |
| void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| ++num_calls_; |
| |
| CrxComponent crx; |
| crx.app_id = "ihfokbkgjpifnbbojhneepfflplebdkc"; |
| crx.name = "test_ihfo"; |
| crx.pk_hash.assign(std::begin(ihfo_hash), std::end(ihfo_hash)); |
| crx.installer = installer_; |
| crx.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| if (num_calls_ == 1) { |
| crx.version = base::Version("0.8"); |
| } else if (num_calls_ == 2) { |
| crx.version = base::Version("1.0"); |
| } else { |
| ADD_FAILURE(); |
| } |
| |
| std::move(callback).Run({crx}); |
| } |
| |
| private: |
| friend class base::RefCountedThreadSafe<DataCallbackMock>; |
| ~DataCallbackMock() = default; |
| |
| int num_calls_ = 0; |
| scoped_refptr<VersionedTestInstaller> installer_ = |
| base::MakeRefCounted<VersionedTestInstaller>(); |
| }; |
| auto data_callback_mock = MakeMockCallback<DataCallbackMock>(); |
| |
| class MockUpdateChecker : public UpdateChecker { |
| public: |
| explicit MockUpdateChecker(int num_calls) : num_calls_(num_calls) {} |
| |
| void CheckForUpdates( |
| scoped_refptr<UpdateContext> context, |
| const base::flat_map<std::string, std::string>& additional_attributes, |
| UpdateCheckCallback update_check_callback) override { |
| base::expected<ProtocolParser::Results, std::string> results; |
| context->get_available_space = base::BindRepeating( |
| [](const base::FilePath&) -> int64_t { return 150000; }); |
| |
| if (num_calls_ == 1) { |
| results = ProtocolParserJSON::ParseJSON(R"()]}' |
| { |
| "response": { |
| "protocol": "4.0", |
| "apps": [ |
| { |
| "appid": "ihfokbkgjpifnbbojhneepfflplebdkc", |
| "status": "ok", |
| "updatecheck": { |
| "status": "ok", |
| "nextversion": "1.0", |
| "pipelines": [ |
| { |
| "operations": [ |
| { |
| "type": "download", |
| "urls": [ |
| { |
| "url": "http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx" |
| } |
| ], |
| "out": { |
| "sha256": "8f5aa190311237cae00675af87ff457f278cd1a05895470ac5d46647d4a3c2ea" |
| }, |
| "size": 54014 |
| }, |
| { |
| "type": "crx3", |
| "in": { |
| "sha256": "8f5aa190311237cae00675af87ff457f278cd1a05895470ac5d46647d4a3c2ea" |
| } |
| } |
| ] |
| } |
| ] |
| } |
| } |
| ] |
| } |
| })"); |
| } else if (num_calls_ == 2) { |
| results = ProtocolParserJSON::ParseJSON(R"()]}' |
| { |
| "response": { |
| "protocol": "4.0", |
| "apps": [ |
| { |
| "appid": "ihfokbkgjpifnbbojhneepfflplebdkc", |
| "status": "ok", |
| "updatecheck": { |
| "status": "ok", |
| "nextversion": "2.0", |
| "pipelines": [ |
| { |
| "operations": [ |
| { |
| "type": "download", |
| "urls": [ |
| { |
| "url": "http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_1to2.puff" |
| } |
| ], |
| "out": { |
| "sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" |
| }, |
| "size": 1680 |
| }, |
| { |
| "type": "puff", |
| "previous": { |
| "sha256": "8f5aa190311237cae00675af87ff457f278cd1a05895470ac5d46647d4a3c2ea" |
| }, |
| "out": { |
| "sha256": "c87d8742c3ff3d7a0cb6f3c91aa2fcf3dea63618086a7db1c5be5300e1d4d6b6" |
| } |
| }, |
| { |
| "type": "crx3", |
| "in": { |
| "sha256": "c87d8742c3ff3d7a0cb6f3c91aa2fcf3dea63618086a7db1c5be5300e1d4d6b6" |
| } |
| } |
| ] |
| }, |
| { |
| "operations": [ |
| { |
| "type": "download", |
| "urls": [ |
| { |
| "url": "http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_2.crx" |
| } |
| ], |
| "out": { |
| "sha256": "c87d8742c3ff3d7a0cb6f3c91aa2fcf3dea63618086a7db1c5be5300e1d4d6b6" |
| }, |
| "size": 54409 |
| }, |
| { |
| "type": "crx3", |
| "in": { |
| "sha256": "c87d8742c3ff3d7a0cb6f3c91aa2fcf3dea63618086a7db1c5be5300e1d4d6b6" |
| } |
| } |
| ] |
| } |
| ] |
| } |
| } |
| ] |
| } |
| })"); |
| } else { |
| ADD_FAILURE(); |
| } |
| EXPECT_TRUE(results.has_value()) << results.error(); |
| EXPECT_FALSE(context->session_id.empty()); |
| EXPECT_EQ(context->components_to_check_for_updates.size(), 1u); |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(update_check_callback), results.value(), |
| ErrorCategory::kNone, 0, 0)); |
| } |
| |
| private: |
| const int num_calls_; |
| }; |
| MockUpdateCheckerFactory<MockUpdateChecker> mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| DownloadMetrics download_metrics; |
| base::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 = 54014; |
| download_metrics.total_bytes = 54014; |
| download_metrics.download_time_ms = 2000; |
| |
| EXPECT_TRUE(MakeTestFile( |
| GetTestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"), &path)); |
| |
| result.error = 0; |
| result.response = path; |
| } else if (url.path() == |
| "/download/ihfokbkgjpifnbbojhneepfflplebdkc_1to2.puff") { |
| // 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 = 1680; |
| 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 = 54409; |
| download_metrics.total_bytes = 54409; |
| download_metrics.download_time_ms = 1000; |
| |
| EXPECT_TRUE(MakeTestFile( |
| GetTestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_2.crx"), &path)); |
| |
| result.error = 0; |
| result.response = path; |
| } |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress, |
| base::Unretained(this), |
| download_metrics.downloaded_bytes, |
| download_metrics.total_bytes)); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete, |
| base::Unretained(this), true, result, |
| download_metrics)); |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { |
| const auto ping_data = MockPingManagerImpl::terminal_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); |
| } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| MockObserver observer(update_client); |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kCanUpdate; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kDownloading; |
| }))) |
| .Times(AtLeast(1)); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kUpdating; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kUpdated; |
| }))); |
| |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kCanUpdate; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kDownloading; |
| }))) |
| .Times(AtLeast(1)); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kDownloading; |
| }))) |
| .Times(AtLeast(1)); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kUpdating; |
| }))) |
| .Times(AtLeast(1)); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kUpdated; |
| }))); |
| } |
| |
| const std::vector<std::string> ids = {"ihfokbkgjpifnbbojhneepfflplebdkc"}; |
| |
| { |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| base::RunLoop runloop; |
| update_client->Update( |
| ids, data_callback_mock, |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| false, ExpectErrorThenQuit(runloop, Error::NONE)); |
| runloop.Run(); |
| EXPECT_EQ(6u, items.size()); |
| EXPECT_EQ(ComponentState::kChecking, items[0].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[0].id); |
| EXPECT_EQ(ComponentState::kCanUpdate, items[1].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[1].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[2].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[2].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[3].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[3].id); |
| EXPECT_EQ(ComponentState::kUpdating, items[4].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[4].id); |
| EXPECT_EQ(ComponentState::kUpdated, items[5].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[5].id); |
| } |
| |
| { |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| base::RunLoop runloop; |
| update_client->Update( |
| ids, data_callback_mock, |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| false, ExpectErrorThenQuit(runloop, Error::NONE)); |
| runloop.Run(); |
| |
| EXPECT_EQ(8u, items.size()); |
| EXPECT_EQ(ComponentState::kChecking, items[0].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[0].id); |
| EXPECT_EQ(ComponentState::kCanUpdate, items[1].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[1].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[2].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[2].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[3].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[3].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[4].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[4].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[5].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[5].id); |
| EXPECT_EQ(ComponentState::kUpdating, items[6].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[6].id); |
| EXPECT_EQ(ComponentState::kUpdated, items[7].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[7].id); |
| } |
| } |
| |
| // 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 void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| CrxComponent crx; |
| crx.app_id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| crx.name = "test_jebg"; |
| crx.pk_hash.assign(std::begin(jebg_hash), std::end(jebg_hash)); |
| crx.version = base::Version("0.9"); |
| crx.installer = base::MakeRefCounted<TestInstaller>(); |
| crx.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| std::move(callback).Run({crx}); |
| } |
| }; |
| |
| class MockUpdateChecker : public UpdateChecker { |
| public: |
| MockUpdateChecker() = default; |
| |
| void CheckForUpdates( |
| scoped_refptr<UpdateContext> context, |
| const base::flat_map<std::string, std::string>& additional_attributes, |
| UpdateCheckCallback update_check_callback) override { |
| EXPECT_FALSE(context->session_id.empty()); |
| EXPECT_EQ(1u, context->components_to_check_for_updates.size()); |
| const std::string id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| EXPECT_EQ(id, context->components_to_check_for_updates.front()); |
| EXPECT_EQ(1u, context->components.count(id)); |
| |
| auto& component = context->components.at(id); |
| |
| EXPECT_FALSE(component->is_foreground()); |
| |
| ProtocolParser::App result; |
| result.app_id = id; |
| result.status = "noupdate"; |
| ProtocolParser::Results results; |
| results.apps.push_back(result); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(std::move(update_check_callback), results, |
| ErrorCategory::kNone, 0, 0)); |
| } |
| }; |
| MockUpdateCheckerFactory<MockUpdateChecker> mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| ADD_FAILURE(); |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| MockObserver observer(update_client); |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpToDate; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpToDate; |
| }))); |
| } |
| |
| std::vector<CrxUpdateItem> items1; |
| auto receiver1 = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver1, Receive(_)) |
| .WillRepeatedly( |
| [&items1](const CrxUpdateItem& item) { items1.push_back(item); }); |
| |
| std::vector<CrxUpdateItem> items2; |
| auto receiver2 = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver2, Receive(_)) |
| .WillRepeatedly( |
| [&items2](const CrxUpdateItem& item) { items2.push_back(item); }); |
| |
| const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf"}; |
| update_client->Update( |
| ids, base::BindOnce(&DataCallbackMock::Callback), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver1), |
| false, ExpectError(Error::NONE)); |
| update_client->Update( |
| ids, base::BindOnce(&DataCallbackMock::Callback), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver2), |
| false, ExpectErrorThenQuit(runloop_, Error::NONE)); |
| runloop_.Run(); |
| |
| EXPECT_EQ(2u, items1.size()); |
| EXPECT_EQ(ComponentState::kChecking, items1[0].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items1[0].id); |
| EXPECT_EQ(ComponentState::kUpToDate, items1[1].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items1[1].id); |
| |
| EXPECT_EQ(2u, items2.size()); |
| EXPECT_EQ(ComponentState::kChecking, items2[0].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items2[0].id); |
| EXPECT_EQ(ComponentState::kUpToDate, items2[1].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items2[1].id); |
| } |
| |
| // Tests the install of one CRX. Tests the installer is invoked with the |
| // run and arguments values of the manifest object. Tests that "pv" and "fp" |
| // are persisted. |
| TEST_F(UpdateClientTest, OneCrxInstall) { |
| class DataCallbackMock { |
| public: |
| static void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| CrxComponent crx; |
| crx.app_id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| crx.name = "test_jebg"; |
| crx.pk_hash.assign(std::begin(jebg_hash), std::end(jebg_hash)); |
| crx.version = base::Version("0.0"); |
| crx.installer = base::MakeRefCounted<TestInstaller>(); |
| crx.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| std::move(callback).Run({crx}); |
| } |
| }; |
| |
| MockUpdateCheckerFactory< |
| MockUpdateCheckerImpl<UpdateCheckerOptionsOneCrxInstall>> |
| mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| DownloadMetrics download_metrics; |
| base::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 = 1015; |
| download_metrics.total_bytes = 1015; |
| download_metrics.download_time_ms = 1000; |
| |
| EXPECT_TRUE(MakeTestFile( |
| GetTestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"), &path)); |
| |
| result.error = 0; |
| result.response = path; |
| } else { |
| ADD_FAILURE(); |
| } |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress, |
| base::Unretained(this), |
| download_metrics.downloaded_bytes, |
| download_metrics.total_bytes)); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete, |
| base::Unretained(this), true, result, |
| download_metrics)); |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { |
| const auto ping_data = MockPingManagerImpl::terminal_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); |
| } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| { |
| EXPECT_FALSE(config()->GetPrefService()->FindPreference( |
| "updateclientdata.apps.jebgalgnebhfojomionfpkfelancnnkf.pv")); |
| EXPECT_FALSE(config()->GetPrefService()->FindPreference( |
| "updateclientdata.apps.jebgalgnebhfojomionfpkfelancnnkf.fp")); |
| } |
| |
| MockObserver observer(update_client); |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kCanUpdate; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kDownloading; |
| }))) |
| .Times(AtLeast(1)); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpdating; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpdated; |
| }))) |
| |
| .WillOnce([](const CrxUpdateItem& item) { |
| ASSERT_TRUE(item.component); |
| const auto* test_installer = |
| static_cast<TestInstaller*>(item.component->installer.get()); |
| EXPECT_EQ("UpdaterSetup.exe", test_installer->install_params()->run); |
| EXPECT_EQ("--arg1 --arg2", |
| test_installer->install_params()->arguments); |
| }); |
| } |
| |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| update_client->Install( |
| std::string("jebgalgnebhfojomionfpkfelancnnkf"), |
| base::BindOnce(&DataCallbackMock::Callback), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| ExpectErrorThenQuit(runloop_, Error::NONE)); |
| runloop_.Run(); |
| |
| EXPECT_EQ(6u, items.size()); |
| EXPECT_EQ(ComponentState::kChecking, items[0].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[0].id); |
| EXPECT_EQ(ComponentState::kCanUpdate, items[1].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[1].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[2].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[2].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[3].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[3].id); |
| EXPECT_EQ(ComponentState::kUpdating, items[4].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[4].id); |
| EXPECT_EQ(ComponentState::kUpdated, items[5].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[5].id); |
| |
| const base::Value::Dict& dict = |
| config()->GetPrefService()->GetDict("updateclientdata"); |
| EXPECT_EQ("1.0", CHECK_DEREF(dict.FindStringByDottedPath( |
| "apps.jebgalgnebhfojomionfpkfelancnnkf.pv"))); |
| } |
| |
| // 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 void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| std::move(callback).Run({std::nullopt}); |
| } |
| }; |
| |
| MockUpdateCheckerFactory<MockUpdateCheckerAlwaysFails> |
| mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| ADD_FAILURE(); |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { |
| EXPECT_EQ(0u, MockPingManagerImpl::ping_data().size()); |
| } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| MockObserver observer(update_client); |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpdateError; |
| }))) |
| |
| .WillOnce([](const CrxUpdateItem& item) { |
| // 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. |
| EXPECT_EQ(ComponentState::kUpdateError, item.state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", item.id); |
| 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); |
| }); |
| } |
| |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| update_client->Install( |
| std::string("jebgalgnebhfojomionfpkfelancnnkf"), |
| base::BindOnce(&DataCallbackMock::Callback), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| ExpectErrorThenQuit(runloop_, Error::NONE)); |
| runloop_.Run(); |
| |
| EXPECT_EQ(1u, items.size()); |
| EXPECT_EQ(ComponentState::kUpdateError, items[0].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[0].id); |
| } |
| |
| // Tests that overlapping installs of the same CRX result in an error. |
| TEST_F(UpdateClientTest, ConcurrentInstallSameCRX) { |
| class DataCallbackMock { |
| public: |
| static void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| CrxComponent crx; |
| crx.app_id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| crx.name = "test_jebg"; |
| crx.pk_hash.assign(std::begin(jebg_hash), std::end(jebg_hash)); |
| crx.version = base::Version("0.0"); |
| crx.installer = base::MakeRefCounted<TestInstaller>(); |
| crx.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| std::move(callback).Run({crx}); |
| } |
| }; |
| |
| class MockUpdateChecker : public UpdateChecker { |
| public: |
| MockUpdateChecker() = default; |
| |
| void CheckForUpdates( |
| scoped_refptr<UpdateContext> context, |
| const base::flat_map<std::string, std::string>& additional_attributes, |
| UpdateCheckCallback update_check_callback) override { |
| EXPECT_FALSE(context->session_id.empty()); |
| EXPECT_EQ(1u, context->components_to_check_for_updates.size()); |
| const std::string id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| EXPECT_EQ(id, context->components_to_check_for_updates.front()); |
| EXPECT_EQ(1u, context->components.count(id)); |
| |
| ProtocolParser::App result; |
| result.app_id = id; |
| result.status = "noupdate"; |
| |
| ProtocolParser::Results results; |
| results.apps.push_back(result); |
| |
| // Verify that calling Install sets |is_foreground| for the component. |
| EXPECT_TRUE(context->components.at(id)->is_foreground()); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(std::move(update_check_callback), results, |
| ErrorCategory::kNone, 0, 0)); |
| } |
| }; |
| MockUpdateCheckerFactory<MockUpdateChecker> mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| ADD_FAILURE(); |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| MockObserver observer(update_client); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpToDate; |
| }))); |
| |
| std::vector<CrxUpdateItem> items1; |
| auto receiver1 = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver1, Receive(_)) |
| .WillRepeatedly( |
| [&items1](const CrxUpdateItem& item) { items1.push_back(item); }); |
| |
| std::vector<CrxUpdateItem> items2; |
| auto receiver2 = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver2, Receive(_)) |
| .WillRepeatedly( |
| [&items2](const CrxUpdateItem& item) { items2.push_back(item); }); |
| |
| base::RepeatingClosure barrier_quit_closure = |
| BarrierClosure(2, runloop_.QuitClosure()); |
| |
| update_client->Install( |
| std::string("jebgalgnebhfojomionfpkfelancnnkf"), |
| base::BindOnce(&DataCallbackMock::Callback), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver1), |
| ExpectErrorThenQuit(barrier_quit_closure, Error::NONE)); |
| update_client->Install( |
| std::string("jebgalgnebhfojomionfpkfelancnnkf"), |
| base::BindOnce(&DataCallbackMock::Callback), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver2), |
| ExpectErrorThenQuit(barrier_quit_closure, Error::UPDATE_IN_PROGRESS)); |
| runloop_.Run(); |
| |
| EXPECT_EQ(2u, items1.size()); |
| EXPECT_EQ(ComponentState::kChecking, items1[0].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items1[0].id); |
| EXPECT_EQ(ComponentState::kUpToDate, items1[1].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items1[1].id); |
| |
| EXPECT_TRUE(items2.empty()); |
| } |
| |
| // Tests that UpdateClient::Update returns Error::INVALID_ARGUMENT when |
| // the |ids| parameter is empty. |
| TEST_F(UpdateClientTest, EmptyIdList) { |
| class DataCallbackMock { |
| public: |
| static void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| std::move(callback).Run({}); |
| } |
| }; |
| |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| ADD_FAILURE(); |
| return base::DoNothing(); |
| } |
| }; |
| MockUpdateCheckerFactory<MockUpdateCheckerAlwaysFails> |
| mock_update_checker_factory; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| const std::vector<std::string> empty_id_list; |
| update_client->Update(empty_id_list, |
| base::BindOnce(&DataCallbackMock::Callback), {}, false, |
| ExpectErrorThenQuit(runloop_, Error::INVALID_ARGUMENT)); |
| runloop_.Run(); |
| } |
| |
| TEST_F(UpdateClientTest, DiskFull) { |
| class DataCallbackMock { |
| public: |
| static void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| CrxComponent crx1; |
| crx1.app_id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| crx1.name = "test_jebg"; |
| crx1.pk_hash.assign(std::begin(jebg_hash), std::end(jebg_hash)); |
| crx1.version = base::Version("0.9"); |
| crx1.installer = base::MakeRefCounted<TestInstaller>(); |
| crx1.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| |
| std::move(callback).Run({crx1}); |
| } |
| }; |
| |
| MockUpdateCheckerFactory< |
| MockUpdateCheckerImpl<UpdateCheckerOptionsOneCrxInstallDiskFull>> |
| mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| ADD_FAILURE(); |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { |
| const auto ping_data = MockPingManagerImpl::terminal_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(1, static_cast<int>(ping_data[0].error_category)); |
| EXPECT_EQ(static_cast<int>(CrxDownloaderError::DISK_FULL), |
| ping_data[0].error_code); |
| } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| MockObserver observer(update_client); |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kCanUpdate; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kDownloading; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpdateError; |
| }))); |
| } |
| |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf"}; |
| update_client->Update( |
| ids, base::BindOnce(&DataCallbackMock::Callback), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| false, ExpectErrorThenQuit(runloop_, Error::NONE)); |
| runloop_.Run(); |
| |
| EXPECT_EQ(4u, items.size()); |
| EXPECT_EQ(ComponentState::kChecking, items[0].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[0].id); |
| EXPECT_EQ(ComponentState::kCanUpdate, items[1].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[1].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[2].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[2].id); |
| EXPECT_EQ(ComponentState::kUpdateError, items[3].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[3].id); |
| } |
| |
| TEST_F(UpdateClientTest, DiskFullDiff) { |
| class DataCallbackMock : public base::RefCountedThreadSafe<DataCallbackMock> { |
| public: |
| DataCallbackMock() { |
| installer_->set_installer_progress_samples({-1, 50, 100}); |
| } |
| |
| void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| ++num_calls_; |
| |
| CrxComponent crx; |
| crx.app_id = "ihfokbkgjpifnbbojhneepfflplebdkc"; |
| crx.name = "test_ihfo"; |
| crx.pk_hash.assign(std::begin(ihfo_hash), std::end(ihfo_hash)); |
| crx.installer = installer_; |
| crx.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| if (num_calls_ == 1) { |
| crx.version = base::Version("0.8"); |
| } else if (num_calls_ == 2) { |
| crx.version = base::Version("1.0"); |
| } else { |
| ADD_FAILURE(); |
| } |
| |
| std::move(callback).Run({crx}); |
| } |
| |
| private: |
| friend class base::RefCountedThreadSafe<DataCallbackMock>; |
| ~DataCallbackMock() = default; |
| |
| int num_calls_ = 0; |
| scoped_refptr<VersionedTestInstaller> installer_ = |
| base::MakeRefCounted<VersionedTestInstaller>(); |
| }; |
| auto data_callback_mock = MakeMockCallback<DataCallbackMock>(); |
| |
| class MockUpdateChecker : public UpdateChecker { |
| public: |
| explicit MockUpdateChecker(int num_calls) : num_calls_(num_calls) {} |
| |
| void CheckForUpdates( |
| scoped_refptr<UpdateContext> context, |
| const base::flat_map<std::string, std::string>& additional_attributes, |
| UpdateCheckCallback update_check_callback) override { |
| base::expected<ProtocolParser::Results, std::string> results; |
| if (num_calls_ == 1) { |
| results = ProtocolParserJSON::ParseJSON(R"()]}' |
| { |
| "response": { |
| "protocol": "4.0", |
| "apps": [ |
| { |
| "appid": "ihfokbkgjpifnbbojhneepfflplebdkc", |
| "status": "ok", |
| "updatecheck": { |
| "status": "ok", |
| "nextversion": "1.0", |
| "pipelines": [ |
| { |
| "operations": [ |
| { |
| "type": "download", |
| "urls": [ |
| { |
| "url": "http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx" |
| } |
| ], |
| "out": { |
| "sha256": "8f5aa190311237cae00675af87ff457f278cd1a05895470ac5d46647d4a3c2ea" |
| }, |
| "size": 54014 |
| }, |
| { |
| "type": "crx3", |
| "in": { |
| "sha256": "8f5aa190311237cae00675af87ff457f278cd1a05895470ac5d46647d4a3c2ea" |
| } |
| } |
| ] |
| } |
| ] |
| } |
| } |
| ] |
| } |
| })"); |
| } else if (num_calls_ == 2) { |
| context->get_available_space = base::BindRepeating( |
| [](const base::FilePath&) -> int64_t { return 0; }); |
| |
| results = ProtocolParserJSON::ParseJSON(R"()]}' |
| { |
| "response": { |
| "protocol": "4.0", |
| "apps": [ |
| { |
| "appid": "ihfokbkgjpifnbbojhneepfflplebdkc", |
| "status": "ok", |
| "updatecheck": { |
| "status": "ok", |
| "nextversion": "2.0", |
| "pipelines": [ |
| { |
| "operations": [ |
| { |
| "type": "download", |
| "urls": [ |
| { |
| "url": "http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_1to2.puff" |
| } |
| ], |
| "out": { |
| "sha256": "f2254da51fa2478a8ba90e58e1c28e24033ec7841015eebf1c82e31b957c44b2" |
| }, |
| "size": 1680 |
| }, |
| { |
| "type": "puff", |
| "previous": { |
| "sha256": "8f5aa190311237cae00675af87ff457f278cd1a05895470ac5d46647d4a3c2ea" |
| }, |
| "out": { |
| "sha256": "c87d8742c3ff3d7a0cb6f3c91aa2fcf3dea63618086a7db1c5be5300e1d4d6b6" |
| } |
| }, |
| { |
| "type": "crx3", |
| "in": { |
| "sha256": "c87d8742c3ff3d7a0cb6f3c91aa2fcf3dea63618086a7db1c5be5300e1d4d6b6" |
| } |
| } |
| ] |
| }, |
| { |
| "operations": [ |
| { |
| "type": "download", |
| "urls": [ |
| { |
| "url": "http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_2.crx" |
| } |
| ], |
| "out": { |
| "sha256": "c87d8742c3ff3d7a0cb6f3c91aa2fcf3dea63618086a7db1c5be5300e1d4d6b6" |
| }, |
| "size": 54409 |
| }, |
| { |
| "type": "crx3", |
| "in": { |
| "sha256": "c87d8742c3ff3d7a0cb6f3c91aa2fcf3dea63618086a7db1c5be5300e1d4d6b6" |
| } |
| } |
| ] |
| } |
| ] |
| } |
| } |
| ] |
| } |
| })"); |
| } else { |
| ADD_FAILURE(); |
| } |
| EXPECT_TRUE(results.has_value()) << results.error(); |
| EXPECT_FALSE(context->session_id.empty()); |
| EXPECT_EQ(context->components_to_check_for_updates.size(), 1u); |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(update_check_callback), results.value(), |
| ErrorCategory::kNone, 0, 0)); |
| } |
| |
| private: |
| const int num_calls_; |
| }; |
| MockUpdateCheckerFactory<MockUpdateChecker> mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| DownloadMetrics download_metrics; |
| base::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 = 54014; |
| download_metrics.total_bytes = 54014; |
| download_metrics.download_time_ms = 2000; |
| |
| EXPECT_TRUE(MakeTestFile( |
| GetTestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"), &path)); |
| |
| result.error = 0; |
| result.response = path; |
| } else { |
| ADD_FAILURE(); |
| } |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress, |
| base::Unretained(this), |
| download_metrics.downloaded_bytes / 2, |
| download_metrics.total_bytes)); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress, |
| base::Unretained(this), |
| download_metrics.downloaded_bytes, |
| download_metrics.total_bytes)); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete, |
| base::Unretained(this), true, result, |
| download_metrics)); |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { |
| const auto ping_data = MockPingManagerImpl::terminal_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(1, static_cast<int>(ping_data[1].error_category)); |
| EXPECT_EQ(static_cast<int>(CrxDownloaderError::DISK_FULL), |
| ping_data[1].error_code); |
| } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| MockObserver observer(update_client); |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kCanUpdate; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kDownloading; |
| }))) |
| .Times(AtLeast(1)); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kUpdating; |
| }))) |
| .Times(AtLeast(1)); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kUpdated; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kCanUpdate; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kDownloading; |
| }))) |
| .Times(AtLeast(1)); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kDownloading; |
| }))) |
| .Times(AtLeast(1)); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kUpdateError; |
| }))); |
| } |
| |
| const std::vector<std::string> ids = {"ihfokbkgjpifnbbojhneepfflplebdkc"}; |
| { |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| base::RunLoop runloop; |
| update_client->Update( |
| ids, data_callback_mock, |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| false, ExpectErrorThenQuit(runloop, Error::NONE)); |
| runloop.Run(); |
| |
| EXPECT_EQ(10u, items.size()); |
| EXPECT_EQ(ComponentState::kChecking, items[0].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[0].id); |
| EXPECT_EQ(ComponentState::kCanUpdate, items[1].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[1].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[2].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[2].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[3].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[3].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[4].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[4].id); |
| EXPECT_EQ(ComponentState::kUpdating, items[5].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[5].id); |
| EXPECT_EQ(ComponentState::kUpdating, items[6].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[6].id); |
| EXPECT_EQ(ComponentState::kUpdating, items[7].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[7].id); |
| EXPECT_EQ(ComponentState::kUpdating, items[8].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[8].id); |
| EXPECT_EQ(ComponentState::kUpdated, items[9].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[9].id); |
| |
| std::vector samples = {-1, -1, -1, -1, -1, -1, -1, 50, 100, 100}; |
| EXPECT_EQ(items.size(), samples.size()); |
| for (size_t i = 0; i != items.size(); ++i) { |
| EXPECT_EQ(items[i].install_progress, samples[i]); |
| } |
| } |
| |
| { |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| base::RunLoop runloop; |
| update_client->Update( |
| ids, data_callback_mock, |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| false, ExpectErrorThenQuit(runloop, Error::NONE)); |
| runloop.Run(); |
| |
| EXPECT_EQ(5u, items.size()); |
| EXPECT_EQ(ComponentState::kChecking, items[0].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[0].id); |
| EXPECT_EQ(ComponentState::kCanUpdate, items[1].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[1].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[2].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[2].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[3].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[3].id); |
| EXPECT_EQ(ComponentState::kUpdateError, items[4].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[4].id); |
| } |
| } |
| |
| struct SendPingTestCase { |
| const int event_type; |
| const int result; |
| const std::optional<int> error_code; |
| const int extra_code1; |
| const std::optional<base::Version> previous_version; |
| const std::optional<base::Version> next_version; |
| }; |
| |
| class SendPingTest : public ::testing::WithParamInterface<SendPingTestCase>, |
| public UpdateClientTest {}; |
| |
| INSTANTIATE_TEST_SUITE_P(SendPingTestCases, |
| SendPingTest, |
| ::testing::ValuesIn(std::vector<SendPingTestCase>{ |
| // Install ping. |
| {protocol_request::kEventInstall, 1, 2, 3}, |
| |
| // Uninstall ping. |
| {protocol_request::kEventUninstall, |
| 1, |
| {}, |
| 10, |
| base::Version("1.2.3.4")}, |
| })); |
| |
| TEST_P(SendPingTest, SendPingTestCases) { |
| MockUpdateCheckerFactory<MockUpdateCheckerAlwaysFails> |
| mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| static scoped_refptr<CrxDownloader> Create( |
| bool is_background_download, |
| scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) { |
| return nullptr; |
| } |
| |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { |
| EXPECT_EQ(ping_data().size(), 1u); |
| EXPECT_EQ(ping_data()[0].id, "jebgalgnebhfojomionfpkfelancnnkf"); |
| EXPECT_EQ(ping_data()[0].event_type, GetParam().event_type); |
| EXPECT_EQ(ping_data()[0].event_result, GetParam().result); |
| if (GetParam().error_code) { |
| EXPECT_EQ(ping_data()[0].error_code, *GetParam().error_code); |
| } |
| EXPECT_EQ(ping_data()[0].extra_code1, GetParam().extra_code1); |
| if (GetParam().previous_version) { |
| EXPECT_EQ(ping_data()[0].previous_version, |
| *GetParam().previous_version); |
| } |
| if (GetParam().next_version) { |
| EXPECT_EQ(ping_data()[0].next_version, *GetParam().next_version); |
| } |
| } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| CrxComponent crx; |
| crx.app_id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| crx.name = "test_jebg"; |
| crx.version = GetParam().previous_version.value_or(base::Version("1.2.3.4")); |
| update_client->SendPing(crx, |
| {.event_type = GetParam().event_type, |
| .result = GetParam().result, |
| .error_code = GetParam().error_code.value_or(0), |
| .extra_code1 = GetParam().extra_code1}, |
| ExpectErrorThenQuit(runloop_, Error::NONE)); |
| runloop_.Run(); |
| } |
| |
| TEST_F(UpdateClientTest, RetryAfter) { |
| class DataCallbackMock { |
| public: |
| static void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| CrxComponent crx; |
| crx.app_id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| crx.name = "test_jebg"; |
| crx.pk_hash.assign(std::begin(jebg_hash), std::end(jebg_hash)); |
| crx.version = base::Version("0.9"); |
| crx.installer = base::MakeRefCounted<TestInstaller>(); |
| crx.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| std::move(callback).Run({crx}); |
| } |
| }; |
| |
| class MockUpdateChecker : public UpdateChecker { |
| public: |
| explicit MockUpdateChecker(int num_calls) : num_calls_(num_calls) {} |
| |
| private: |
| void CheckForUpdates( |
| scoped_refptr<UpdateContext> context, |
| const base::flat_map<std::string, std::string>& additional_attributes, |
| UpdateCheckCallback update_check_callback) override { |
| EXPECT_FALSE(context->session_id.empty()); |
| |
| EXPECT_LE(num_calls_, 3); |
| |
| int retry_after_sec(0); |
| if (num_calls_ == 1) { |
| // Throttle the next call. |
| retry_after_sec = 60 * 60; // 1 hour. |
| } |
| |
| EXPECT_EQ(1u, context->components_to_check_for_updates.size()); |
| const std::string id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| EXPECT_EQ(id, context->components_to_check_for_updates.front()); |
| EXPECT_EQ(1u, context->components.count(id)); |
| |
| ProtocolParser::App result; |
| result.app_id = id; |
| result.status = "noupdate"; |
| |
| ProtocolParser::Results results; |
| results.apps.push_back(result); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(std::move(update_check_callback), results, |
| ErrorCategory::kNone, 0, retry_after_sec)); |
| } |
| |
| const int num_calls_; |
| }; |
| MockUpdateCheckerFactory<MockUpdateChecker> mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| ADD_FAILURE(); |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| MockObserver observer(update_client); |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpToDate; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpToDate; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpToDate; |
| }))); |
| } |
| |
| 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, ExpectErrorThenQuit(runloop, Error::NONE)); |
| 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, |
| ExpectErrorThenQuit(runloop, Error::RETRY_LATER)); |
| 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), {}, |
| ExpectErrorThenQuit(runloop, Error::NONE)); |
| runloop.Run(); |
| } |
| { |
| // This call succeeds. |
| base::RunLoop runloop; |
| update_client->Update(ids, base::BindOnce(&DataCallbackMock::Callback), {}, |
| false, ExpectErrorThenQuit(runloop, Error::NONE)); |
| runloop.Run(); |
| } |
| } |
| |
| // 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 void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| CrxComponent crx1; |
| crx1.app_id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| crx1.name = "test_jebg"; |
| crx1.pk_hash.assign(std::begin(jebg_hash), std::end(jebg_hash)); |
| crx1.version = base::Version("0.9"); |
| crx1.installer = base::MakeRefCounted<TestInstaller>(); |
| crx1.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| crx1.updates_enabled = false; |
| |
| CrxComponent crx2; |
| crx2.app_id = "ihfokbkgjpifnbbojhneepfflplebdkc"; |
| crx2.name = "test_ihfo"; |
| crx2.pk_hash.assign(std::begin(ihfo_hash), std::end(ihfo_hash)); |
| crx2.version = base::Version("0.8"); |
| crx2.installer = base::MakeRefCounted<TestInstaller>(); |
| crx2.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| |
| std::move(callback).Run({crx1, crx2}); |
| } |
| }; |
| |
| MockUpdateCheckerFactory< |
| MockUpdateCheckerImpl<UpdateCheckerOptionsTwoCrxUpdate>> |
| mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| DownloadMetrics download_metrics; |
| base::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 = 54014; |
| download_metrics.total_bytes = 54014; |
| download_metrics.download_time_ms = 2000; |
| |
| EXPECT_TRUE(MakeTestFile( |
| GetTestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"), &path)); |
| |
| result.error = 0; |
| result.response = path; |
| } else { |
| ADD_FAILURE(); |
| } |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress, |
| base::Unretained(this), |
| download_metrics.downloaded_bytes, |
| download_metrics.total_bytes)); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete, |
| base::Unretained(this), true, result, |
| download_metrics)); |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { |
| const auto ping_data = MockPingManagerImpl::terminal_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. |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| MockObserver observer(update_client); |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kCanUpdate; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpdateError; |
| }))); |
| } |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kCanUpdate; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kDownloading; |
| }))) |
| .Times(AtLeast(1)); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kUpdating; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kUpdated; |
| }))); |
| } |
| |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf", |
| "ihfokbkgjpifnbbojhneepfflplebdkc"}; |
| update_client->Update( |
| ids, base::BindOnce(&DataCallbackMock::Callback), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| false, ExpectErrorThenQuit(runloop_, Error::NONE)); |
| runloop_.Run(); |
| |
| EXPECT_EQ(9u, items.size()); |
| EXPECT_EQ(ComponentState::kChecking, items[0].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[0].id); |
| EXPECT_EQ(ComponentState::kChecking, items[1].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[1].id); |
| EXPECT_EQ(ComponentState::kCanUpdate, items[2].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[2].id); |
| EXPECT_EQ(ComponentState::kUpdateError, items[3].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[3].id); |
| EXPECT_EQ(ComponentState::kCanUpdate, items[4].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[4].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[5].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[5].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[6].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[6].id); |
| EXPECT_EQ(ComponentState::kUpdating, items[7].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[7].id); |
| EXPECT_EQ(ComponentState::kUpdated, items[8].state); |
| EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", items[8].id); |
| } |
| |
| // Tests all ping back events have the correct errorcode and extracode1 set in |
| // the case of a failed download with a valid http status code |
| TEST_F(UpdateClientTest, OneCrxUpdateDownloadTimeout) { |
| class DataCallbackMock { |
| public: |
| static void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| CrxComponent crx; |
| crx.app_id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| crx.name = "test_jebg"; |
| crx.pk_hash.assign(std::begin(jebg_hash), std::end(jebg_hash)); |
| crx.version = base::Version("0.9"); |
| crx.installer = base::MakeRefCounted<TestInstaller>(); |
| crx.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| std::vector<std::optional<CrxComponent>> component = {crx}; |
| std::move(callback).Run({component}); |
| } |
| }; |
| |
| MockUpdateCheckerFactory< |
| MockUpdateCheckerImpl<UpdateCheckerOptionsOneCrxUpdate>> |
| mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| DownloadMetrics download_metrics; |
| download_metrics.url = url; |
| download_metrics.downloader = DownloadMetrics::kNone; |
| download_metrics.error = 200; |
| download_metrics.extra_code1 = -2147012894; |
| download_metrics.downloaded_bytes = 1015 / 2; |
| download_metrics.total_bytes = 1015; |
| download_metrics.download_time_ms = 1000; |
| |
| base::FilePath path; |
| EXPECT_TRUE(MakeTestFile( |
| GetTestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"), &path)); |
| |
| Result result; |
| result.error = 200; |
| result.extra_code1 = -2147012894; |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress, |
| base::Unretained(this), |
| download_metrics.downloaded_bytes / 2, |
| download_metrics.total_bytes)); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete, |
| base::Unretained(this), true, result, |
| download_metrics)); |
| return base::DoNothing(); |
| } |
| }; |
| |
| 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(ping_data.size(), 2u); |
| |
| auto download_ping = ping_data[0]; |
| EXPECT_EQ(download_ping.event_type, 14); |
| EXPECT_EQ(download_ping.event_result, 0); |
| EXPECT_EQ(download_ping.id, "jebgalgnebhfojomionfpkfelancnnkf"); |
| EXPECT_EQ(download_ping.previous_version, base::Version("0.9")); |
| EXPECT_EQ(download_ping.next_version, base::Version("1.0")); |
| EXPECT_EQ(download_ping.error_category, ErrorCategory::kNone); |
| EXPECT_EQ(download_ping.error_code, 200); |
| EXPECT_EQ(download_ping.extra_code1, -2147012894); |
| |
| auto update_ping = ping_data[1]; |
| EXPECT_EQ(update_ping.event_type, 3); |
| EXPECT_EQ(update_ping.event_result, 0); |
| EXPECT_EQ(update_ping.id, "jebgalgnebhfojomionfpkfelancnnkf"); |
| EXPECT_EQ(update_ping.previous_version, base::Version("0.9")); |
| EXPECT_EQ(update_ping.next_version, base::Version("1.0")); |
| EXPECT_EQ(update_ping.error_category, ErrorCategory::kDownload); |
| EXPECT_EQ(update_ping.error_code, 200); |
| EXPECT_EQ(update_ping.extra_code1, -2147012894); |
| } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| MockObserver observer(update_client); |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kCanUpdate; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kDownloading; |
| }))) |
| .Times(AtLeast(2)); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpdateError; |
| }))) |
| .WillOnce([](const CrxUpdateItem& item) { |
| EXPECT_EQ(ComponentState::kUpdateError, item.state); |
| EXPECT_EQ(1, static_cast<int>(item.error_category)); |
| EXPECT_EQ(200, item.error_code); |
| EXPECT_EQ(-2147012894, item.extra_code1); |
| }); |
| } |
| |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf"}; |
| update_client->Update( |
| ids, base::BindOnce(&DataCallbackMock::Callback), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| false, ExpectErrorThenQuit(runloop_, Error::NONE)); |
| runloop_.Run(); |
| |
| EXPECT_EQ(5u, items.size()); |
| EXPECT_EQ(ComponentState::kChecking, items[0].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[0].id); |
| EXPECT_EQ(ComponentState::kCanUpdate, items[1].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[1].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[2].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[2].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[3].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[3].id); |
| EXPECT_EQ(ComponentState::kUpdateError, items[4].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[4].id); |
| } |
| |
| // Tests the scenario where the update check fails. |
| TEST_F(UpdateClientTest, OneCrxUpdateCheckFails) { |
| class DataCallbackMock { |
| public: |
| static void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| CrxComponent crx; |
| crx.name = "test_jebg"; |
| crx.pk_hash.assign(std::begin(jebg_hash), std::end(jebg_hash)); |
| crx.version = base::Version("0.9"); |
| crx.installer = base::MakeRefCounted<TestInstaller>(); |
| crx.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| std::move(callback).Run({crx}); |
| } |
| }; |
| |
| class MockUpdateChecker : public UpdateChecker { |
| public: |
| MockUpdateChecker() = default; |
| |
| void CheckForUpdates( |
| scoped_refptr<UpdateContext> context, |
| const base::flat_map<std::string, std::string>& additional_attributes, |
| UpdateCheckCallback update_check_callback) override { |
| EXPECT_FALSE(context->session_id.empty()); |
| EXPECT_EQ(1u, context->components_to_check_for_updates.size()); |
| const std::string id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| EXPECT_EQ(id, context->components_to_check_for_updates.front()); |
| EXPECT_EQ(1u, context->components.count(id)); |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(update_check_callback), std::nullopt, |
| ErrorCategory::kUpdateCheck, -1, 0)); |
| } |
| }; |
| MockUpdateCheckerFactory<MockUpdateChecker> mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| ADD_FAILURE(); |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| MockObserver observer(update_client); |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpdateError; |
| }))) |
| .WillOnce([](const CrxUpdateItem& 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); |
| }); |
| } |
| |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf"}; |
| update_client->Update( |
| ids, base::BindOnce(&DataCallbackMock::Callback), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| false, ExpectErrorThenQuit(runloop_, Error::UPDATE_CHECK_ERROR)); |
| runloop_.Run(); |
| |
| EXPECT_EQ(2u, items.size()); |
| EXPECT_EQ(ComponentState::kChecking, items[0].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[0].id); |
| EXPECT_EQ(ComponentState::kUpdateError, items[1].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[1].id); |
| } |
| |
| // Tests the scenario where the server responds with different values for |
| // application status. |
| TEST_F(UpdateClientTest, OneCrxErrorUnknownApp) { |
| class DataCallbackMock { |
| public: |
| static void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| std::vector<std::optional<CrxComponent>> component; |
| { |
| CrxComponent crx; |
| crx.app_id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| crx.name = "test_jebg"; |
| crx.pk_hash.assign(std::begin(jebg_hash), std::end(jebg_hash)); |
| crx.version = base::Version("0.9"); |
| crx.installer = base::MakeRefCounted<TestInstaller>(); |
| crx.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| component.push_back(crx); |
| } |
| { |
| CrxComponent crx; |
| crx.app_id = "abagagagagagagagagagagagagagagag"; |
| crx.name = "test_abag"; |
| crx.pk_hash.assign(std::begin(abag_hash), std::end(abag_hash)); |
| crx.version = base::Version("0.1"); |
| crx.installer = base::MakeRefCounted<TestInstaller>(); |
| crx.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| component.push_back(crx); |
| } |
| { |
| CrxComponent crx; |
| crx.app_id = "ihfokbkgjpifnbbojhneepfflplebdkc"; |
| crx.name = "test_ihfo"; |
| crx.pk_hash.assign(std::begin(ihfo_hash), std::end(ihfo_hash)); |
| crx.version = base::Version("0.2"); |
| crx.installer = base::MakeRefCounted<TestInstaller>(); |
| crx.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| component.push_back(crx); |
| } |
| { |
| CrxComponent crx; |
| crx.app_id = "gjpmebpgbhcamgdgjcmnjfhggjpgcimm"; |
| crx.name = "test_gjpm"; |
| crx.pk_hash.assign(std::begin(gjpm_hash), std::end(gjpm_hash)); |
| crx.version = base::Version("0.3"); |
| crx.installer = base::MakeRefCounted<TestInstaller>(); |
| crx.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| component.push_back(crx); |
| } |
| std::move(callback).Run(component); |
| } |
| }; |
| |
| class MockUpdateChecker : public UpdateChecker { |
| public: |
| MockUpdateChecker() = default; |
| |
| void CheckForUpdates( |
| scoped_refptr<UpdateContext> context, |
| const base::flat_map<std::string, std::string>& additional_attributes, |
| UpdateCheckCallback update_check_callback) override { |
| EXPECT_FALSE(context->session_id.empty()); |
| EXPECT_EQ(4u, context->components_to_check_for_updates.size()); |
| |
| const std::string update_response = R"()]}' |
| { |
| "response": { |
| "protocol": "4.0", |
| "apps": [ |
| { |
| "appid": "jebgalgnebhfojomionfpkfelancnnkf", |
| "status": "error-unknownApplication" |
| }, |
| { |
| "appid": "abagagagagagagagagagagagagagagag", |
| "status": "restricted" |
| }, |
| { |
| "appid": "ihfokbkgjpifnbbojhneepfflplebdkc", |
| "status": "error-invalidAppId" |
| }, |
| { |
| "appid": "gjpmebpgbhcamgdgjcmnjfhggjpgcimm", |
| "status": "error-foobarApp" |
| } |
| ] |
| } |
| })"; |
| |
| const auto parser = ProtocolHandlerFactoryJSON().CreateParser(); |
| EXPECT_TRUE(parser->Parse(update_response)); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(update_check_callback), parser->results(), |
| ErrorCategory::kNone, 0, 0)); |
| } |
| }; |
| MockUpdateCheckerFactory<MockUpdateChecker> mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| ADD_FAILURE(); |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| MockObserver observer(update_client); |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpdateError; |
| }))) |
| .WillOnce([](const CrxUpdateItem& 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(Truly([](const CrxUpdateItem& item) { |
| return item.id == "abagagagagagagagagagagagagagagag" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "abagagagagagagagagagagagagagagag" && |
| item.state == ComponentState::kUpdateError; |
| }))) |
| .WillOnce([](const CrxUpdateItem& 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(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "ihfokbkgjpifnbbojhneepfflplebdkc" && |
| item.state == ComponentState::kUpdateError; |
| }))) |
| .WillOnce([](const CrxUpdateItem& 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(Truly([](const CrxUpdateItem& item) { |
| return item.id == "gjpmebpgbhcamgdgjcmnjfhggjpgcimm" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "gjpmebpgbhcamgdgjcmnjfhggjpgcimm" && |
| item.state == ComponentState::kUpdateError; |
| }))) |
| .WillOnce([](const CrxUpdateItem& item) { |
| EXPECT_EQ(ComponentState::kUpdateError, item.state); |
| EXPECT_EQ(5, static_cast<int>(item.error_category)); |
| EXPECT_EQ(-10016, item.error_code); // UNKNOWN_ERROR. |
| EXPECT_EQ(0, item.extra_code1); |
| }); |
| } |
| |
| const std::vector<std::string> ids = { |
| "jebgalgnebhfojomionfpkfelancnnkf", "abagagagagagagagagagagagagagagag", |
| "ihfokbkgjpifnbbojhneepfflplebdkc", "gjpmebpgbhcamgdgjcmnjfhggjpgcimm"}; |
| update_client->Update(ids, base::BindOnce(&DataCallbackMock::Callback), {}, |
| true, ExpectErrorThenQuit(runloop_, Error::NONE)); |
| runloop_.Run(); |
| } |
| |
| // Tests that a run action in invoked in the CRX install scenario. |
| TEST_F(UpdateClientTest, ActionRun_Install) { |
| class MockUpdateChecker : public UpdateChecker { |
| public: |
| MockUpdateChecker() = default; |
| |
| void CheckForUpdates( |
| scoped_refptr<UpdateContext> context, |
| const base::flat_map<std::string, std::string>& additional_attributes, |
| UpdateCheckCallback update_check_callback) override { |
| context->get_available_space = base::BindRepeating( |
| [](const base::FilePath&) -> int64_t { return 200000; }); |
| base::expected<ProtocolParser::Results, std::string> results = |
| ProtocolParserJSON::ParseJSON(R"()]}' |
| { |
| "response": { |
| "protocol": "4.0", |
| "apps": [ |
| { |
| "appid": "gjpmebpgbhcamgdgjcmnjfhggjpgcimm", |
| "status": "ok", |
| "updatecheck": { |
| "status": "ok", |
| "nextversion": "1.0", |
| "pipelines": [ |
| { |
| "operations": [ |
| { |
| "type": "download", |
| "urls": [ |
| { |
| "url": "http://localhost/download/runaction_test_win.crx3" |
| } |
| ], |
| "out": { |
| "sha256": "89290a0d2ff21ca5b45e109c6cc859ab5fe294e19c102d54acd321429c372cea" |
| }, |
| "size": 48141 |
| }, |
| { |
| "type": "crx3", |
| "in": { |
| "sha256": "89290a0d2ff21ca5b45e109c6cc859ab5fe294e19c102d54acd321429c372cea" |
| } |
| }, |
| { |
| "type": "run", |
| "path": "ChromeRecovery.crx3" |
| } |
| ] |
| } |
| ] |
| } |
| } |
| ] |
| } |
| })"); |
| EXPECT_TRUE(results.has_value()) << results.error(); |
| EXPECT_FALSE(context->session_id.empty()); |
| EXPECT_EQ(context->components_to_check_for_updates.size(), 1u); |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(update_check_callback), results.value(), |
| ErrorCategory::kNone, 0, 0)); |
| } |
| }; |
| MockUpdateCheckerFactory<MockUpdateChecker> mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| DownloadMetrics download_metrics; |
| base::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 = 48141; |
| download_metrics.total_bytes = 48141; |
| download_metrics.download_time_ms = 1000; |
| |
| EXPECT_TRUE( |
| MakeTestFile(GetTestFilePath("runaction_test_win.crx3"), &path)); |
| |
| result.error = 0; |
| result.response = path; |
| } else { |
| ADD_FAILURE(); |
| } |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete, |
| base::Unretained(this), true, result, |
| download_metrics)); |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { |
| EXPECT_EQ(4u, events().size()); |
| |
| /* |
| "<event eventtype="14" eventresult="1" downloader="unknown" |
| url="http://localhost/download/runaction_test_win.crx3" |
| downloaded=48141 total=48141 download_time_ms="1000" |
| previousversion="0.0" nextversion="1.0"/> |
| */ |
| const base::Value::Dict& event0 = events()[0]; |
| EXPECT_EQ(14, event0.FindInt("eventtype")); |
| EXPECT_EQ(1, event0.FindInt("eventresult")); |
| EXPECT_EQ("unknown", CHECK_DEREF(event0.FindString("downloader"))); |
| EXPECT_EQ("http://localhost/download/runaction_test_win.crx3", |
| CHECK_DEREF(event0.FindString("url"))); |
| EXPECT_EQ(48141, event0.FindDouble("downloaded")); |
| EXPECT_EQ(48141, event0.FindDouble("total")); |
| EXPECT_EQ(1000, event0.FindDouble("download_time_ms")); |
| EXPECT_EQ("0.0", CHECK_DEREF(event0.FindString("previousversion"))); |
| EXPECT_EQ("1.0", CHECK_DEREF(event0.FindString("nextversion"))); |
| |
| // <event eventtype="63" eventresult="1" previousversion="0.0" |
| // nextversion="1.0"/> |
| const base::Value::Dict& event1 = events()[1]; |
| EXPECT_EQ(63, event1.FindInt("eventtype")); |
| EXPECT_EQ(1, event1.FindInt("eventresult")); |
| EXPECT_EQ("0.0", CHECK_DEREF(event1.FindString("previousversion"))); |
| EXPECT_EQ("1.0", CHECK_DEREF(event1.FindString("nextversion"))); |
| |
| // <event eventtype="42" eventresult="1" errorcode="1877345072"/> |
| const base::Value::Dict& event2 = events()[2]; |
| EXPECT_EQ(42, event2.FindInt("eventtype")); |
| EXPECT_EQ(1, event2.FindInt("eventresult")); |
| EXPECT_EQ(1877345072, event2.FindInt("errorcode")); |
| |
| // <event eventtype="2" eventresult="1" previousversion="0.0" |
| // nextversion="1.0"/> |
| const base::Value::Dict& event3 = events()[3]; |
| EXPECT_EQ(2, event3.FindInt("eventtype")); |
| EXPECT_EQ(1, event3.FindInt("eventresult")); |
| EXPECT_EQ("0.0", CHECK_DEREF(event3.FindString("previousversion"))); |
| EXPECT_EQ("1.0", CHECK_DEREF(event3.FindString("nextversion"))); |
| } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| update_client->Install( |
| std::string("gjpmebpgbhcamgdgjcmnjfhggjpgcimm"), |
| base::BindOnce( |
| [](const std::vector<std::string>& ids, |
| base::OnceCallback<void( |
| const std::vector<std::optional<CrxComponent>>&)> callback) { |
| auto action_handler = base::MakeRefCounted<MockActionHandler>(); |
| EXPECT_CALL(*action_handler, Handle(_, _, _)) |
| .WillOnce([](const base::FilePath& action, |
| const std::string& session_id, |
| ActionHandler::Callback callback) { |
| EXPECT_EQ("ChromeRecovery.crx3", |
| action.BaseName().AsUTF8Unsafe()); |
| EXPECT_TRUE(!session_id.empty()); |
| std::move(callback).Run(true, 1877345072, 0); |
| }); |
| |
| CrxComponent crx; |
| crx.app_id = "gjpmebpgbhcamgdgjcmnjfhggjpgcimm"; |
| crx.name = "test_gjpm"; |
| crx.pk_hash.assign(std::begin(gjpm_hash), std::end(gjpm_hash)); |
| crx.version = base::Version("0.0"); |
| crx.installer = base::MakeRefCounted<VersionedTestInstaller>(); |
| crx.action_handler = action_handler; |
| crx.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| std::move(callback).Run({crx}); |
| }), |
| {}, ExpectErrorThenQuit(runloop_, Error::NONE)); |
| runloop_.Run(); |
| } |
| |
| // Tests that a run action is invoked in an update scenario when there was |
| // no update. |
| TEST_F(UpdateClientTest, ActionRun_NoUpdate) { |
| MockUpdateCheckerFactory< |
| MockUpdateCheckerImpl<UpdateCheckerOptionsActionRunNoUpdate>> |
| mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| ADD_FAILURE(); |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { |
| auto ping_data = MockPingManagerImpl::ping_data(); |
| EXPECT_EQ(ping_data.size(), 2u); |
| |
| // "<event eventtype="42" eventresult="1" errorcode="1877345072"/>" |
| auto download = ping_data[0]; |
| EXPECT_EQ(download.event_type, protocol_request::kEventAction); |
| EXPECT_EQ(download.event_result, protocol_request::kEventResultSuccess); |
| EXPECT_EQ(download.error_code, 1877345072); |
| |
| // "<event eventtype="3" eventresult="1"/>" |
| auto install = ping_data[1]; |
| EXPECT_EQ(install.event_type, protocol_request::kEventUpdate); |
| EXPECT_EQ(install.event_result, protocol_request::kEventResultSuccess); |
| } |
| }; |
| |
| // Unpack the CRX to mock an existing install to be updated. The action to |
| // run is going to be resolved relative to this directory. |
| base::FilePath unpack_path; |
| { |
| base::RunLoop runloop; |
| base::OnceClosure quit_closure = runloop.QuitClosure(); |
| |
| Unpacker::Unpack( |
| std::vector<uint8_t>(std::begin(gjpm_hash), std::end(gjpm_hash)), |
| GetTestFilePath("runaction_test_win.crx3"), |
| base::MakeRefCounted<UnzipChromiumFactory>( |
| base::BindRepeating(&unzip::LaunchInProcessUnzipper)) |
| ->Create(), |
| crx_file::VerifierFormat::CRX3, |
| base::BindOnce( |
| [](base::FilePath* unpack_path, base::OnceClosure quit_closure, |
| const Unpacker::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)); |
| std::optional<int64_t> file_size = base::GetFileSize( |
| unpack_path.Append(FILE_PATH_LITERAL("ChromeRecovery.crx3"))); |
| EXPECT_TRUE(file_size.has_value()); |
| EXPECT_EQ(44582, file_size.value()); |
| |
| base::ScopedTempDir unpack_path_owner; |
| EXPECT_TRUE(unpack_path_owner.Set(unpack_path)); |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| const std::vector<std::string> ids = {"gjpmebpgbhcamgdgjcmnjfhggjpgcimm"}; |
| update_client->Update( |
| ids, |
| base::BindOnce( |
| [](const base::FilePath& unpack_path, |
| const std::vector<std::string>& ids, |
| base::OnceCallback<void( |
| const std::vector<std::optional<CrxComponent>>&)> callback) { |
| auto action_handler = base::MakeRefCounted<MockActionHandler>(); |
| EXPECT_CALL(*action_handler, Handle(_, _, _)) |
| .WillOnce([](const base::FilePath& action, |
| const std::string& session_id, |
| ActionHandler::Callback callback) { |
| EXPECT_EQ("ChromeRecovery.crx3", |
| action.BaseName().AsUTF8Unsafe()); |
| EXPECT_TRUE(!session_id.empty()); |
| std::move(callback).Run(true, 1877345072, 0); |
| }); |
| |
| CrxComponent crx; |
| crx.app_id = "gjpmebpgbhcamgdgjcmnjfhggjpgcimm"; |
| crx.name = "test_gjpm"; |
| crx.pk_hash.assign(std::begin(gjpm_hash), std::end(gjpm_hash)); |
| crx.version = base::Version("1.0"); |
| crx.installer = |
| base::MakeRefCounted<ReadOnlyTestInstaller>(unpack_path); |
| crx.action_handler = action_handler; |
| crx.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| std::move(callback).Run({crx}); |
| }, |
| unpack_path), |
| {}, false, ExpectErrorThenQuit(runloop_, Error::NONE)); |
| |
| runloop_.Run(); |
| } |
| |
| // Tests that custom response attributes are visible to observers. |
| TEST_F(UpdateClientTest, CustomAttributeNoUpdate) { |
| class DataCallbackMock { |
| public: |
| static void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| CrxComponent crx; |
| crx.app_id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| crx.name = "test_jebg"; |
| crx.pk_hash.assign(std::begin(jebg_hash), std::end(jebg_hash)); |
| crx.version = base::Version("0.9"); |
| crx.installer = base::MakeRefCounted<TestInstaller>(); |
| crx.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| std::vector<std::optional<CrxComponent>> component = {crx}; |
| std::move(callback).Run(component); |
| } |
| }; |
| |
| class MockUpdateChecker : public UpdateChecker { |
| public: |
| MockUpdateChecker() = default; |
| |
| void CheckForUpdates( |
| scoped_refptr<UpdateContext> context, |
| const base::flat_map<std::string, std::string>& additional_attributes, |
| UpdateCheckCallback update_check_callback) override { |
| EXPECT_FALSE(context->session_id.empty()); |
| EXPECT_EQ(1u, context->components_to_check_for_updates.size()); |
| const std::string id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| EXPECT_EQ(id, context->components_to_check_for_updates.front()); |
| EXPECT_EQ(1u, context->components.count(id)); |
| |
| auto& component = context->components.at(id); |
| |
| EXPECT_TRUE(component->is_foreground()); |
| |
| ProtocolParser::App result; |
| result.app_id = id; |
| result.status = "noupdate"; |
| result.custom_attributes["_example"] = "example_value"; |
| |
| ProtocolParser::Results results; |
| results.apps.push_back(result); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(std::move(update_check_callback), results, |
| ErrorCategory::kNone, 0, 0)); |
| } |
| }; |
| MockUpdateCheckerFactory<MockUpdateChecker> mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| ADD_FAILURE(); |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| MockObserver observer(update_client); |
| EXPECT_CALL(observer, OnEvent(_)) |
| .WillRepeatedly([](const CrxUpdateItem& item) { |
| if (item.state == ComponentState::kUpToDate) { |
| ASSERT_TRUE(item.custom_updatecheck_data.contains("_example")); |
| EXPECT_EQ("example_value", |
| item.custom_updatecheck_data.at("_example")); |
| } |
| }); |
| |
| update_client->Update({"jebgalgnebhfojomionfpkfelancnnkf"}, |
| base::BindOnce(&DataCallbackMock::Callback), {}, true, |
| ExpectErrorThenQuit(runloop_, Error::NONE)); |
| runloop_.Run(); |
| } |
| |
| // Tests the scenario where `CrxDataCallback` returns a vector whose elements |
| // don't include a value for one of the component ids specified by the `ids` |
| // parameter of the `UpdateClient::Update` function. Expects the completion |
| // callback to include a specific error, and no other events and pings be |
| // generated, since the update engine rejects the UpdateClient::Update call. |
| TEST_F(UpdateClientTest, BadCrxDataCallback) { |
| 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()), |
| UpdateChecker::Factory{}); |
| |
| MockObserver observer(update_client); |
| |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf", |
| "gjpmebpgbhcamgdgjcmnjfhggjpgcimm"}; |
| // The `CrxDataCallback` argument only returns a value for the first |
| // component id. This means that its result is ill formed, and the `Update` |
| // call completes with an error. |
| update_client->Update( |
| ids, |
| base::BindOnce( |
| [](const std::vector<std::string>& ids, |
| base::OnceCallback<void( |
| const std::vector<std::optional<CrxComponent>>&)> callback) { |
| EXPECT_EQ(ids.size(), size_t{2}); |
| std::move(callback).Run({std::nullopt}); |
| }), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), true, |
| ExpectErrorThenQuit(runloop_, Error::BAD_CRX_DATA_CALLBACK)); |
| runloop_.Run(); |
| |
| EXPECT_TRUE(items.empty()); |
| } |
| |
| // Tests cancellation of an install before the task is run. |
| TEST_F(UpdateClientTest, CancelInstallBeforeTaskStart) { |
| class DataCallbackMock { |
| public: |
| static void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| CrxComponent crx; |
| crx.app_id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| crx.name = "test_jebg"; |
| crx.pk_hash.assign(std::begin(jebg_hash), std::end(jebg_hash)); |
| crx.version = base::Version("0.0"); |
| crx.installer = base::MakeRefCounted<TestInstaller>(); |
| crx.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| std::move(callback).Run({crx}); |
| } |
| }; |
| |
| MockUpdateCheckerFactory< |
| MockUpdateCheckerImpl<UpdateCheckerOptionsOneCrxInstall>> |
| mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| DownloadMetrics download_metrics; |
| base::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 = 1015; |
| download_metrics.total_bytes = 1015; |
| download_metrics.download_time_ms = 1000; |
| |
| EXPECT_TRUE(MakeTestFile( |
| GetTestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"), &path)); |
| |
| result.error = 0; |
| result.response = path; |
| } else { |
| ADD_FAILURE(); |
| } |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress, |
| base::Unretained(this), |
| download_metrics.downloaded_bytes, |
| download_metrics.total_bytes)); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete, |
| base::Unretained(this), true, result, |
| download_metrics)); |
| return base::DoNothing(); |
| } |
| }; |
| |
| 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(0u, ping_data.size()); |
| } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| update_client |
| ->Install( |
| std::string("jebgalgnebhfojomionfpkfelancnnkf"), |
| base::BindOnce(&DataCallbackMock::Callback), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| ExpectErrorThenQuit(runloop_, Error::UPDATE_CANCELED)) |
| .Run(); |
| runloop_.Run(); |
| EXPECT_EQ(0u, items.size()); |
| } |
| |
| // Tests cancellation of an install before the component installer runs. |
| TEST_F(UpdateClientTest, CancelInstallBeforeInstall) { |
| class DataCallbackMock { |
| public: |
| static void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| CrxComponent crx; |
| crx.app_id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| crx.name = "test_jebg"; |
| crx.pk_hash.assign(std::begin(jebg_hash), std::end(jebg_hash)); |
| crx.version = base::Version("0.0"); |
| crx.installer = base::MakeRefCounted<TestInstaller>(); |
| crx.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| std::move(callback).Run({crx}); |
| } |
| }; |
| |
| MockUpdateCheckerFactory< |
| MockUpdateCheckerImpl<UpdateCheckerOptionsOneCrxInstall>> |
| mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| DownloadMetrics download_metrics; |
| base::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 = 1015; |
| download_metrics.total_bytes = 1015; |
| download_metrics.download_time_ms = 1000; |
| |
| EXPECT_TRUE(MakeTestFile( |
| GetTestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"), &path)); |
| |
| result.error = 0; |
| result.response = path; |
| } else { |
| ADD_FAILURE(); |
| } |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress, |
| base::Unretained(this), |
| download_metrics.downloaded_bytes, |
| download_metrics.total_bytes)); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete, |
| base::Unretained(this), true, result, |
| download_metrics)); |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { |
| const auto ping_data = MockPingManagerImpl::terminal_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(ErrorCategory::kService, ping_data[0].error_category); |
| EXPECT_EQ(static_cast<int>(ServiceError::CANCELLED), |
| ping_data[0].error_code); |
| } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| base::RepeatingClosure cancel; |
| |
| MockObserver observer(update_client); |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kCanUpdate; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kDownloading; |
| }))) |
| .Times(AtLeast(1)) |
| .WillRepeatedly([&cancel] { cancel.Run(); }); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpdateError; |
| }))); |
| } |
| |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| cancel = update_client->Install( |
| std::string("jebgalgnebhfojomionfpkfelancnnkf"), |
| base::BindOnce(&DataCallbackMock::Callback), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| ExpectErrorThenQuit(runloop_, Error::NONE)); |
| runloop_.Run(); |
| |
| EXPECT_EQ(5u, items.size()); |
| EXPECT_EQ(ComponentState::kChecking, items[0].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[0].id); |
| EXPECT_EQ(ComponentState::kCanUpdate, items[1].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[1].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[2].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[2].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[3].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[3].id); |
| EXPECT_EQ(ComponentState::kUpdateError, items[4].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[4].id); |
| } |
| |
| // Tests cancellation of an install before the download. |
| TEST_F(UpdateClientTest, CancelInstallBeforeDownload) { |
| class DataCallbackMock { |
| public: |
| static void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| CrxComponent crx; |
| crx.name = "test_jebg"; |
| crx.pk_hash.assign(std::begin(jebg_hash), std::end(jebg_hash)); |
| crx.app_id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| crx.version = base::Version("0.0"); |
| crx.installer = base::MakeRefCounted<TestInstaller>(); |
| crx.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| std::move(callback).Run({crx}); |
| } |
| }; |
| |
| MockUpdateCheckerFactory< |
| MockUpdateCheckerImpl<UpdateCheckerOptionsOneCrxInstall>> |
| mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| DownloadMetrics download_metrics; |
| base::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 = 1015; |
| download_metrics.total_bytes = 1015; |
| download_metrics.download_time_ms = 1000; |
| |
| EXPECT_TRUE(MakeTestFile( |
| GetTestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"), &path)); |
| |
| result.error = 0; |
| result.response = path; |
| } else { |
| ADD_FAILURE(); |
| } |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress, |
| base::Unretained(this), |
| download_metrics.downloaded_bytes, |
| download_metrics.total_bytes)); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete, |
| base::Unretained(this), true, result, |
| download_metrics)); |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { |
| const auto ping_data = MockPingManagerImpl::terminal_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(ErrorCategory::kService, ping_data[0].error_category); |
| EXPECT_EQ(static_cast<int>(ServiceError::CANCELLED), |
| ping_data[0].error_code); |
| } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| base::RepeatingClosure cancel; |
| |
| MockObserver observer(update_client); |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kCanUpdate; |
| }))) |
| .WillOnce([&cancel] { cancel.Run(); }); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpdateError; |
| }))); |
| } |
| |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| cancel = update_client->Install( |
| std::string("jebgalgnebhfojomionfpkfelancnnkf"), |
| base::BindOnce(&DataCallbackMock::Callback), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| ExpectErrorThenQuit(runloop_, Error::NONE)); |
| runloop_.Run(); |
| |
| EXPECT_EQ(3u, items.size()); |
| EXPECT_EQ(ComponentState::kChecking, items[0].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[0].id); |
| EXPECT_EQ(ComponentState::kCanUpdate, items[1].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[1].id); |
| EXPECT_EQ(ComponentState::kUpdateError, items[2].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[2].id); |
| } |
| |
| TEST_F(UpdateClientTest, CheckForUpdate_NoUpdate) { |
| class DataCallbackMock { |
| public: |
| static void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| CrxComponent crx; |
| crx.app_id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| crx.name = "test_jebg"; |
| crx.pk_hash.assign(std::begin(jebg_hash), std::end(jebg_hash)); |
| crx.version = base::Version("0.9"); |
| crx.installer = base::MakeRefCounted<TestInstaller>(); |
| crx.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| std::move(callback).Run({crx}); |
| } |
| }; |
| |
| class MockUpdateChecker : public UpdateChecker { |
| public: |
| MockUpdateChecker() = default; |
| |
| void CheckForUpdates( |
| scoped_refptr<UpdateContext> context, |
| const base::flat_map<std::string, std::string>& additional_attributes, |
| UpdateCheckCallback update_check_callback) override { |
| EXPECT_FALSE(context->session_id.empty()); |
| EXPECT_EQ(1u, context->components_to_check_for_updates.size()); |
| const std::string id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| EXPECT_EQ(id, context->components_to_check_for_updates.front()); |
| EXPECT_EQ(1u, context->components.count(id)); |
| |
| auto& component = context->components.at(id); |
| |
| EXPECT_TRUE(component->is_foreground()); |
| |
| ProtocolParser::App result; |
| result.app_id = id; |
| result.status = "noupdate"; |
| |
| ProtocolParser::Results results; |
| results.apps.push_back(result); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(std::move(update_check_callback), results, |
| ErrorCategory::kNone, 0, 0)); |
| } |
| }; |
| MockUpdateCheckerFactory<MockUpdateChecker> mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| ADD_FAILURE(); |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| MockObserver observer(update_client); |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpToDate; |
| }))); |
| } |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| const std::string id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| update_client->CheckForUpdate( |
| id, base::BindOnce(&DataCallbackMock::Callback), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| /*is_foreground=*/true, ExpectErrorThenQuit(runloop_, Error::NONE)); |
| runloop_.Run(); |
| EXPECT_EQ(items.size(), 2u); |
| EXPECT_EQ(items[0].state, ComponentState::kChecking); |
| EXPECT_EQ(items[0].id, "jebgalgnebhfojomionfpkfelancnnkf"); |
| EXPECT_EQ(items[1].state, ComponentState::kUpToDate); |
| EXPECT_EQ(items[1].id, "jebgalgnebhfojomionfpkfelancnnkf"); |
| } |
| |
| TEST_F(UpdateClientTest, CheckForUpdate_UpdateAvailable) { |
| class DataCallbackMock { |
| public: |
| static void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| CrxComponent crx; |
| crx.app_id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| crx.name = "test_jebg"; |
| crx.pk_hash.assign(std::begin(jebg_hash), std::end(jebg_hash)); |
| crx.version = base::Version("0.9"); |
| crx.installer = base::MakeRefCounted<TestInstaller>(); |
| crx.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| std::move(callback).Run({crx}); |
| } |
| }; |
| |
| MockUpdateCheckerFactory< |
| MockUpdateCheckerImpl<UpdateCheckerOptionsOneCrxUpdate>> |
| mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| ADD_FAILURE(); |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { |
| const std::vector<PingData> ping_data = |
| MockPingManagerImpl::terminal_ping_data(); |
| EXPECT_EQ(ping_data.size(), 1u); |
| EXPECT_EQ(ping_data[0].id, "jebgalgnebhfojomionfpkfelancnnkf"); |
| EXPECT_EQ(ping_data[0].previous_version, base::Version("0.9")); |
| EXPECT_EQ(ping_data[0].next_version, base::Version("1.0")); |
| EXPECT_EQ(ping_data[0].error_category, ErrorCategory::kService); |
| EXPECT_EQ(ping_data[0].error_code, |
| static_cast<int>(ServiceError::CHECK_FOR_UPDATE_ONLY)); |
| EXPECT_EQ(ping_data[0].extra_code1, 0); |
| } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| MockObserver observer(update_client); |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kCanUpdate; |
| }))); |
| } |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| const std::string id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| update_client->CheckForUpdate( |
| id, base::BindOnce(&DataCallbackMock::Callback), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| /*is_foreground=*/false, ExpectErrorThenQuit(runloop_, Error::NONE)); |
| runloop_.Run(); |
| EXPECT_EQ(items.size(), 2u); |
| EXPECT_EQ(items[0].state, ComponentState::kChecking); |
| EXPECT_EQ(items[0].id, "jebgalgnebhfojomionfpkfelancnnkf"); |
| EXPECT_EQ(items[1].state, ComponentState::kCanUpdate); |
| EXPECT_EQ(items[1].id, "jebgalgnebhfojomionfpkfelancnnkf"); |
| } |
| |
| TEST_F(UpdateClientTest, CheckForUpdate_QueueChecks) { |
| class DataCallbackMock { |
| public: |
| static void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| CrxComponent crx; |
| crx.app_id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| crx.name = "test_jebg"; |
| crx.pk_hash.assign(std::begin(jebg_hash), std::end(jebg_hash)); |
| crx.version = base::Version("0.9"); |
| crx.installer = base::MakeRefCounted<TestInstaller>(); |
| crx.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| std::move(callback).Run({crx}); |
| } |
| }; |
| |
| class MockUpdateChecker : public UpdateChecker { |
| public: |
| MockUpdateChecker() = default; |
| |
| void CheckForUpdates( |
| scoped_refptr<UpdateContext> context, |
| const base::flat_map<std::string, std::string>& additional_attributes, |
| UpdateCheckCallback update_check_callback) override { |
| EXPECT_FALSE(context->session_id.empty()); |
| EXPECT_EQ(1u, context->components_to_check_for_updates.size()); |
| const std::string id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| EXPECT_EQ(id, context->components_to_check_for_updates.front()); |
| EXPECT_EQ(1u, context->components.count(id)); |
| |
| auto& component = context->components.at(id); |
| |
| EXPECT_TRUE(component->is_foreground()); |
| |
| ProtocolParser::App result; |
| result.app_id = id; |
| result.status = "noupdate"; |
| |
| ProtocolParser::Results results; |
| results.apps.push_back(result); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(std::move(update_check_callback), results, |
| ErrorCategory::kNone, 0, 0)); |
| } |
| }; |
| MockUpdateCheckerFactory<MockUpdateChecker> mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| ADD_FAILURE(); |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| MockObserver observer(update_client); |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpToDate; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpToDate; |
| }))); |
| } |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| // Do two `CheckForUpdate` calls, expect the calls to be done in sequence. |
| base::RepeatingClosure barrier_quit_closure = |
| BarrierClosure(2, runloop_.QuitClosure()); |
| const std::string id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| update_client->CheckForUpdate( |
| id, base::BindOnce(&DataCallbackMock::Callback), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| /*is_foreground=*/true, |
| ExpectErrorThenQuit(barrier_quit_closure, Error::NONE)); |
| update_client->CheckForUpdate( |
| id, base::BindOnce(&DataCallbackMock::Callback), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| /*is_foreground=*/true, |
| ExpectErrorThenQuit(barrier_quit_closure, Error::NONE)); |
| EXPECT_TRUE(update_client->IsUpdating(id)); |
| runloop_.Run(); |
| EXPECT_EQ(items.size(), 4u); |
| EXPECT_EQ(items[0].state, ComponentState::kChecking); |
| EXPECT_EQ(items[0].id, "jebgalgnebhfojomionfpkfelancnnkf"); |
| EXPECT_EQ(items[1].state, ComponentState::kUpToDate); |
| EXPECT_EQ(items[1].id, "jebgalgnebhfojomionfpkfelancnnkf"); |
| EXPECT_EQ(items[2].state, ComponentState::kChecking); |
| EXPECT_EQ(items[2].id, "jebgalgnebhfojomionfpkfelancnnkf"); |
| EXPECT_EQ(items[3].state, ComponentState::kUpToDate); |
| EXPECT_EQ(items[3].id, "jebgalgnebhfojomionfpkfelancnnkf"); |
| } |
| |
| TEST_F(UpdateClientTest, CheckForUpdate_Stop) { |
| class DataCallbackMock { |
| public: |
| static void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| CrxComponent crx; |
| crx.app_id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| crx.name = "test_jebg"; |
| crx.pk_hash.assign(std::begin(jebg_hash), std::end(jebg_hash)); |
| crx.version = base::Version("0.9"); |
| crx.installer = base::MakeRefCounted<TestInstaller>(); |
| crx.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| std::move(callback).Run({crx}); |
| } |
| }; |
| |
| class MockUpdateChecker : public UpdateChecker { |
| public: |
| MockUpdateChecker() = default; |
| |
| void CheckForUpdates( |
| scoped_refptr<UpdateContext> context, |
| const base::flat_map<std::string, std::string>& additional_attributes, |
| UpdateCheckCallback update_check_callback) override { |
| EXPECT_FALSE(context->session_id.empty()); |
| EXPECT_EQ(1u, context->components_to_check_for_updates.size()); |
| const std::string id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| EXPECT_EQ(id, context->components_to_check_for_updates.front()); |
| EXPECT_EQ(1u, context->components.count(id)); |
| |
| auto& component = context->components.at(id); |
| |
| EXPECT_TRUE(component->is_foreground()); |
| |
| ProtocolParser::App result; |
| result.app_id = id; |
| result.status = "noupdate"; |
| |
| ProtocolParser::Results results; |
| results.apps.push_back(result); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(std::move(update_check_callback), results, |
| ErrorCategory::kNone, 0, 0)); |
| } |
| }; |
| MockUpdateCheckerFactory<MockUpdateChecker> mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| ADD_FAILURE(); |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| MockObserver observer(update_client); |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpToDate; |
| }))); |
| } |
| |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| // Do two `CheckForUpdate` calls, expect the second call to be cancelled, |
| // because `Stop` cancels the queued up subsequent call. |
| base::RepeatingClosure barrier_quit_closure = |
| BarrierClosure(2, runloop_.QuitClosure()); |
| const std::string id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| update_client->CheckForUpdate( |
| id, base::BindOnce(&DataCallbackMock::Callback), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| /*is_foreground=*/true, |
| ExpectErrorThenQuit(barrier_quit_closure, Error::NONE)); |
| update_client->CheckForUpdate( |
| id, base::BindOnce(&DataCallbackMock::Callback), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| /*is_foreground=*/true, |
| ExpectErrorThenQuit(barrier_quit_closure, Error::UPDATE_CANCELED)); |
| update_client->Stop(); |
| EXPECT_TRUE(update_client->IsUpdating(id)); |
| runloop_.Run(); |
| EXPECT_EQ(items.size(), 2u); |
| EXPECT_EQ(items[0].state, ComponentState::kChecking); |
| EXPECT_EQ(items[0].id, "jebgalgnebhfojomionfpkfelancnnkf"); |
| EXPECT_EQ(items[1].state, ComponentState::kUpToDate); |
| EXPECT_EQ(items[1].id, "jebgalgnebhfojomionfpkfelancnnkf"); |
| } |
| |
| TEST_F(UpdateClientTest, CheckForUpdate_Errors) { |
| class MockUpdateChecker : public UpdateChecker { |
| public: |
| MockUpdateChecker() = default; |
| |
| void CheckForUpdates( |
| scoped_refptr<UpdateContext> context, |
| const base::flat_map<std::string, std::string>& additional_attributes, |
| UpdateCheckCallback update_check_callback) override {} |
| }; |
| MockUpdateCheckerFactory<MockUpdateChecker> mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| ADD_FAILURE(); |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| MockObserver observer(update_client); |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpdateError; |
| }))); |
| } |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| // Tests some error cases when arguments are incorrect. |
| base::RepeatingClosure barrier_quit_closure = |
| BarrierClosure(2, runloop_.QuitClosure()); |
| const std::string id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| update_client->CheckForUpdate( |
| id, |
| base::BindOnce( |
| [](const std::vector<std::string>& /*ids*/, |
| base::OnceCallback<void( |
| const std::vector<std::optional<CrxComponent>>&)> callback) { |
| std::move(callback).Run({}); |
| }), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| /*is_foreground=*/true, |
| ExpectErrorThenQuit(barrier_quit_closure, Error::BAD_CRX_DATA_CALLBACK)); |
| update_client->CheckForUpdate( |
| id, |
| base::BindLambdaForTesting( |
| [&id]( |
| const std::vector<std::string>& ids, |
| base::OnceCallback<void( |
| const std::vector<std::optional<CrxComponent>>&)> callback) { |
| EXPECT_EQ(ids.size(), 1u); |
| EXPECT_EQ(id, ids[0]); |
| std::move(callback).Run({std::nullopt}); |
| }), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| /*is_foreground=*/true, |
| ExpectErrorThenQuit(barrier_quit_closure, Error::NONE)); |
| EXPECT_TRUE(update_client->IsUpdating(id)); |
| runloop_.Run(); |
| EXPECT_EQ(items.size(), 1u); |
| EXPECT_EQ(items[0].state, ComponentState::kUpdateError); |
| EXPECT_EQ(items[0].id, "jebgalgnebhfojomionfpkfelancnnkf"); |
| EXPECT_EQ(items[0].error_code, static_cast<int>(Error::CRX_NOT_FOUND)); |
| } |
| |
| // Tests `CheckForUpdate` when the updates are disabled but the server ignores |
| // "updatedisabled" attribute and returns on update. In this case, the client |
| // reports an error (SERVICE_ERROR, UPDATE_DISABLED) and pings. |
| TEST_F(UpdateClientTest, UpdateCheck_UpdateDisabled) { |
| class DataCallbackMock { |
| public: |
| static void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| CrxComponent crx; |
| crx.app_id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| crx.name = "test_jebg"; |
| crx.pk_hash.assign(std::begin(jebg_hash), std::end(jebg_hash)); |
| crx.version = base::Version("0.9"); |
| crx.installer = base::MakeRefCounted<TestInstaller>(); |
| crx.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| crx.updates_enabled = false; |
| std::move(callback).Run({crx}); |
| } |
| }; |
| |
| MockUpdateCheckerFactory< |
| MockUpdateCheckerImpl<UpdateCheckerOptionsOneCrxUpdate>> |
| mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| ADD_FAILURE(); |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { |
| const std::vector<PingData>& ping_data = |
| MockPingManagerImpl::terminal_ping_data(); |
| EXPECT_EQ(ping_data.size(), 1u); |
| EXPECT_EQ(ping_data[0].id, "jebgalgnebhfojomionfpkfelancnnkf"); |
| EXPECT_EQ(ping_data[0].previous_version, base::Version("0.9")); |
| EXPECT_EQ(ping_data[0].next_version, base::Version("1.0")); |
| EXPECT_EQ(ping_data[0].error_category, ErrorCategory::kService); |
| EXPECT_EQ(ping_data[0].error_code, |
| static_cast<int>(ServiceError::UPDATE_DISABLED)); |
| } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| MockObserver observer(update_client); |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kCanUpdate; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpdateError; |
| }))); |
| } |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| update_client->CheckForUpdate( |
| "jebgalgnebhfojomionfpkfelancnnkf", |
| base::BindOnce(&DataCallbackMock::Callback), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| false, ExpectErrorThenQuit(runloop_, Error::NONE)); |
| runloop_.Run(); |
| EXPECT_EQ(items.size(), 3u); |
| EXPECT_EQ(items[0].state, ComponentState::kChecking); |
| EXPECT_EQ(items[0].id, "jebgalgnebhfojomionfpkfelancnnkf"); |
| EXPECT_EQ(items[1].state, ComponentState::kCanUpdate); |
| EXPECT_EQ(items[1].id, "jebgalgnebhfojomionfpkfelancnnkf"); |
| EXPECT_EQ(items[2].state, ComponentState::kUpdateError); |
| EXPECT_EQ(items[2].id, "jebgalgnebhfojomionfpkfelancnnkf"); |
| } |
| |
| // Tests the cached update scenario for one CRX to validate that the file is |
| // cached if an install error occurs and re-used when the update is retried. |
| TEST_F(UpdateClientTest, OneCrxCachedUpdate) { |
| class DataCallbackMock : public base::RefCountedThreadSafe<DataCallbackMock> { |
| public: |
| void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| ++num_calls_; |
| |
| CrxComponent crx; |
| crx.app_id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| crx.name = "test_jebg"; |
| crx.pk_hash.assign(std::begin(jebg_hash), std::end(jebg_hash)); |
| crx.version = base::Version("0.9"); |
| crx.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| |
| scoped_refptr<TestInstaller> installer = |
| base::MakeRefCounted<TestInstaller>(); |
| if (num_calls_ == 1) { |
| installer->set_install_error(InstallError::GENERIC_ERROR); |
| installer->set_installer_progress_samples({-1, 25}); |
| } else if (num_calls_ == 2) { |
| installer->set_installer_progress_samples({-1, 50, 100}); |
| } else { |
| ADD_FAILURE(); |
| } |
| crx.installer = installer; |
| |
| std::move(callback).Run({crx}); |
| } |
| |
| private: |
| friend class base::RefCountedThreadSafe<DataCallbackMock>; |
| ~DataCallbackMock() = default; |
| |
| int num_calls_ = 0; |
| }; |
| auto data_callback_mock = MakeMockCallback<DataCallbackMock>(); |
| |
| MockUpdateCheckerFactory< |
| MockUpdateCheckerImpl<UpdateCheckerOptionsOneCrxUpdate>> |
| mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure 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 = 54014; |
| download_metrics.total_bytes = 54014; |
| download_metrics.download_time_ms = 2000; |
| |
| base::FilePath path; |
| EXPECT_TRUE(MakeTestFile( |
| GetTestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"), &path)); |
| |
| Result result; |
| result.error = 0; |
| result.response = path; |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress, |
| base::Unretained(this), |
| download_metrics.downloaded_bytes, |
| download_metrics.total_bytes)); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete, |
| base::Unretained(this), true, result, |
| download_metrics)); |
| return base::DoNothing(); |
| } |
| }; |
| |
| class MockPingManager : public MockPingManagerImpl { |
| public: |
| explicit MockPingManager(scoped_refptr<Configurator> config) |
| : MockPingManagerImpl(config) {} |
| |
| protected: |
| ~MockPingManager() override { |
| const auto ping_data = MockPingManagerImpl::terminal_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(ping_data[0].error_category, ErrorCategory::kInstall); |
| EXPECT_EQ(9, ping_data[0].error_code); // GENERIC_ERROR. |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[1].id); |
| EXPECT_EQ(base::Version("0.9"), 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); |
| } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| MockObserver observer(update_client); |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kCanUpdate; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kDownloading; |
| }))) |
| .Times(2); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpdating; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpdating; |
| }))) |
| .Times(2); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpdateError; |
| }))); |
| |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kCanUpdate; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpdating; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpdating; |
| }))) |
| .Times(3); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpdated; |
| }))); |
| } |
| |
| const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf"}; |
| { |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| base::RunLoop runloop; |
| update_client->Update( |
| ids, data_callback_mock, |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| false, ExpectErrorThenQuit(runloop, Error::NONE)); |
| runloop.Run(); |
| |
| EXPECT_EQ(8u, items.size()); |
| EXPECT_EQ(ComponentState::kChecking, items[0].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[0].id); |
| EXPECT_EQ(ComponentState::kCanUpdate, items[1].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[1].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[2].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[2].id); |
| EXPECT_EQ(ComponentState::kDownloading, items[3].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[3].id); |
| EXPECT_EQ(ComponentState::kUpdating, items[4].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[4].id); |
| EXPECT_EQ(ComponentState::kUpdating, items[5].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[5].id); |
| EXPECT_EQ(ComponentState::kUpdating, items[6].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[6].id); |
| EXPECT_EQ(ComponentState::kUpdateError, items[7].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[7].id); |
| |
| std::vector samples = {-1, -1, -1, -1, -1, -1, 25, 25}; |
| EXPECT_EQ(items.size(), samples.size()); |
| for (size_t i = 0; i != items.size(); ++i) { |
| EXPECT_EQ(items[i].install_progress, samples[i]); |
| } |
| } |
| |
| { |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| base::RunLoop runloop; |
| update_client->Update( |
| ids, data_callback_mock, |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| false, ExpectErrorThenQuit(runloop, Error::NONE)); |
| runloop.Run(); |
| |
| EXPECT_EQ(7u, items.size()); |
| EXPECT_EQ(ComponentState::kChecking, items[0].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[0].id); |
| EXPECT_EQ(ComponentState::kCanUpdate, items[1].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[1].id); |
| EXPECT_EQ(ComponentState::kUpdating, items[2].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[2].id); |
| EXPECT_EQ(ComponentState::kUpdating, items[3].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[3].id); |
| EXPECT_EQ(ComponentState::kUpdating, items[4].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[4].id); |
| EXPECT_EQ(ComponentState::kUpdating, items[5].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[5].id); |
| EXPECT_EQ(ComponentState::kUpdated, items[6].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[6].id); |
| |
| std::vector samples = {-1, -1, -1, -1, 50, 100, 100}; |
| EXPECT_EQ(items.size(), samples.size()); |
| for (size_t i = 0; i != items.size(); ++i) { |
| EXPECT_EQ(items[i].install_progress, samples[i]); |
| } |
| } |
| } |
| |
| TEST_F(UpdateClientTest, UnsupportedOperationType) { |
| class DataCallbackMock { |
| public: |
| static void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| CrxComponent crx1; |
| crx1.app_id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| crx1.name = "test_jebg"; |
| crx1.pk_hash.assign(std::begin(jebg_hash), std::end(jebg_hash)); |
| crx1.version = base::Version("0.9"); |
| crx1.installer = base::MakeRefCounted<TestInstaller>(); |
| crx1.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| |
| std::move(callback).Run({crx1}); |
| } |
| }; |
| |
| MockUpdateCheckerFactory< |
| MockUpdateCheckerImpl<UpdateCheckerOptionsUnsupportedOperationType>> |
| mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| ADD_FAILURE() << "Intentionally forcing download to fail here."; |
| return base::DoNothing(); |
| } |
| }; |
| |
| 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()); |
| |
| // unsupported operation event |
| EXPECT_EQ(ping_data[0].id, "jebgalgnebhfojomionfpkfelancnnkf"); |
| EXPECT_EQ(ping_data[0].previous_version, base::Version("0.9")); |
| EXPECT_EQ(ping_data[0].next_version, base::Version("1.0")); |
| EXPECT_EQ(ping_data[0].error_category, ErrorCategory::kUpdateCheck); |
| EXPECT_EQ(ping_data[0].event_type, protocol_request::kEventUnknown); |
| |
| // update check |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[1].id); |
| EXPECT_EQ(base::Version("0.9"), ping_data[1].previous_version); |
| EXPECT_EQ(base::Version("1.0"), ping_data[1].next_version); |
| EXPECT_EQ(ping_data[1].error_category, ErrorCategory::kUpdateCheck); |
| EXPECT_EQ(ping_data[1].event_type, protocol_request::kEventUpdate); |
| } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| MockObserver observer(update_client); |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kCanUpdate; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpdateError; |
| }))); |
| } |
| |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf"}; |
| update_client->Update( |
| ids, base::BindOnce(&DataCallbackMock::Callback), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| false, ExpectErrorThenQuit(runloop_, Error::NONE)); |
| runloop_.Run(); |
| |
| EXPECT_EQ(3u, items.size()); |
| EXPECT_EQ(ComponentState::kChecking, items[0].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[0].id); |
| EXPECT_EQ(ComponentState::kCanUpdate, items[1].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[1].id); |
| EXPECT_EQ(ComponentState::kUpdateError, items[2].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[2].id); |
| } |
| |
| TEST_F(UpdateClientTest, |
| AllPipelinesContainingOperationsWithInvalidAttributesNoUpdate) { |
| class DataCallbackMock { |
| public: |
| static void Callback( |
| const std::vector<std::string>& ids, |
| base::OnceCallback< |
| void(const std::vector<std::optional<CrxComponent>>&)> callback) { |
| CrxComponent crx1; |
| crx1.app_id = "jebgalgnebhfojomionfpkfelancnnkf"; |
| crx1.name = "test_jebg"; |
| crx1.pk_hash.assign(std::begin(jebg_hash), std::end(jebg_hash)); |
| crx1.version = base::Version("0.9"); |
| crx1.installer = base::MakeRefCounted<TestInstaller>(); |
| crx1.crx_format_requirement = crx_file::VerifierFormat::CRX3; |
| |
| std::move(callback).Run({crx1}); |
| } |
| }; |
| |
| class MockUpdateChecker : public UpdateChecker { |
| public: |
| MockUpdateChecker() = default; |
| |
| void CheckForUpdates( |
| scoped_refptr<UpdateContext> context, |
| const base::flat_map<std::string, std::string>& additional_attributes, |
| UpdateCheckCallback update_check_callback) override { |
| context->get_available_space = base::BindRepeating( |
| [](const base::FilePath&) -> int64_t { return 0; }); |
| // The puff operation is missing previous hash, so it fails. |
| base::expected<ProtocolParser::Results, std::string> results = |
| ProtocolParserJSON::ParseJSON(R"()]}' |
| { |
| "response": { |
| "protocol": "4.0", |
| "apps": [ |
| { |
| "appid": "jebgalgnebhfojomionfpkfelancnnkf", |
| "status": "ok", |
| "updatecheck": { |
| "status": "ok", |
| "nextversion": "1.0", |
| "pipelines": [ |
| { |
| "pipeline_id": "download_missing_urls", |
| "operations": [ |
| { |
| "type": "download", |
| "size": 10, |
| "out": { |
| "sha256": "7ab32f071cd9b5ef8e0d7913be161f532d98b3e9fa284a7cd8059c3409ce0498" |
| } |
| } |
| ] |
| }, |
| { |
| "pipeline_id": "download_missing_out", |
| "operations": [ |
| { |
| "type": "download", |
| "size": 10, |
| "urls": { |
| "url": "http://does.not.matter.com/file.crx" |
| } |
| } |
| ] |
| }, |
| { |
| "pipeline_id": "download_invalid_size", |
| "operations": [ |
| { |
| "type": "download", |
| "size": -10, |
| "urls": { |
| "url": "http://does.not.matter.com/file.crx" |
| }, |
| "out": { |
| "sha256": "7ab32f071cd9b5ef8e0d7913be161f532d98b3e9fa284a7cd8059c3409ce0498" |
| } |
| } |
| ] |
| }, |
| { |
| "pipeline_id": "puff_missing_prev", |
| "operations": [ |
| { |
| "type": "puff", |
| "out": { |
| "sha256": "7ab32f071cd9b5ef8e0d7913be161f532d98b3e9fa284a7cd8059c3409ce0498" |
| } |
| } |
| ] |
| }, |
| { |
| "pipeline_id": "zucc_missing_prev", |
| "operations": [ |
| { |
| "type": "zucc", |
| "out": { |
| "sha256": "7ab32f071cd9b5ef8e0d7913be161f532d98b3e9fa284a7cd8059c3409ce0498" |
| } |
| } |
| ] |
| }, |
| { |
| "pipeline_id": "crx3_missing_in", |
| "operations": [ |
| { |
| "type": "crx3" |
| } |
| ] |
| } |
| ] |
| } |
| } |
| ] |
| } |
| })"); |
| EXPECT_TRUE(results.has_value()) << results.error(); |
| EXPECT_FALSE(context->session_id.empty()); |
| EXPECT_EQ(context->components_to_check_for_updates.size(), 1u); |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(update_check_callback), results.value(), |
| ErrorCategory::kNone, 0, 0)); |
| } |
| }; |
| MockUpdateCheckerFactory<MockUpdateChecker> mock_update_checker_factory; |
| |
| class MockCrxDownloader : public CrxDownloader { |
| public: |
| MockCrxDownloader() = default; |
| |
| private: |
| ~MockCrxDownloader() override = default; |
| |
| base::OnceClosure DoStartDownload(const GURL& url) override { |
| ADD_FAILURE(); |
| return base::DoNothing(); |
| } |
| }; |
| |
| 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(ping_data.size(), 7u); |
| |
| // Download failures: |
| auto noUrls = ping_data[0]; |
| EXPECT_EQ(noUrls.id, "jebgalgnebhfojomionfpkfelancnnkf"); |
| EXPECT_EQ(noUrls.pipeline_id, "download_missing_urls"); |
| EXPECT_EQ(noUrls.error_category, ErrorCategory::kUpdateCheck); |
| EXPECT_EQ(noUrls.event_type, protocol_request::kEventDownload); |
| |
| auto noOutHash = ping_data[1]; |
| EXPECT_EQ(noOutHash.id, "jebgalgnebhfojomionfpkfelancnnkf"); |
| EXPECT_EQ(noOutHash.pipeline_id, "download_missing_out"); |
| EXPECT_EQ(noOutHash.error_category, ErrorCategory::kUpdateCheck); |
| EXPECT_EQ(noOutHash.event_type, protocol_request::kEventDownload); |
| |
| auto badSize = ping_data[2]; |
| EXPECT_EQ(badSize.id, "jebgalgnebhfojomionfpkfelancnnkf"); |
| EXPECT_EQ(badSize.pipeline_id, "download_invalid_size"); |
| EXPECT_EQ(badSize.error_category, ErrorCategory::kUpdateCheck); |
| EXPECT_EQ(badSize.event_type, protocol_request::kEventDownload); |
| |
| auto puff = ping_data[3]; |
| EXPECT_EQ(puff.id, "jebgalgnebhfojomionfpkfelancnnkf"); |
| EXPECT_EQ(puff.pipeline_id, "puff_missing_prev"); |
| EXPECT_EQ(puff.error_category, ErrorCategory::kUpdateCheck); |
| EXPECT_EQ(puff.event_type, protocol_request::kEventPuff); |
| |
| auto zucc = ping_data[4]; |
| EXPECT_EQ(zucc.id, "jebgalgnebhfojomionfpkfelancnnkf"); |
| EXPECT_EQ(zucc.pipeline_id, "zucc_missing_prev"); |
| EXPECT_EQ(zucc.error_category, ErrorCategory::kUpdateCheck); |
| EXPECT_EQ(zucc.event_type, protocol_request::kEventZucchini); |
| |
| auto crx3 = ping_data[5]; |
| EXPECT_EQ(crx3.id, "jebgalgnebhfojomionfpkfelancnnkf"); |
| EXPECT_EQ(crx3.pipeline_id, "crx3_missing_in"); |
| EXPECT_EQ(crx3.error_category, ErrorCategory::kUpdateCheck); |
| EXPECT_EQ(crx3.event_type, protocol_request::kEventCrx3); |
| |
| auto update = ping_data[6]; |
| EXPECT_EQ(update.id, "jebgalgnebhfojomionfpkfelancnnkf"); |
| EXPECT_TRUE(update.pipeline_id.empty()); |
| EXPECT_EQ(update.error_category, ErrorCategory::kUpdateCheck); |
| EXPECT_EQ(update.event_type, protocol_request::kEventUpdate); |
| } |
| }; |
| |
| SetMockCrxDownloader<MockCrxDownloader>(); |
| scoped_refptr<UpdateClient> update_client = |
| base::MakeRefCounted<UpdateClientImpl>( |
| config(), base::MakeRefCounted<MockPingManager>(config()), |
| mock_update_checker_factory.GetFactory()); |
| |
| MockObserver observer(update_client); |
| { |
| InSequence seq; |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kChecking; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kCanUpdate; |
| }))); |
| EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) { |
| return item.id == "jebgalgnebhfojomionfpkfelancnnkf" && |
| item.state == ComponentState::kUpdateError; |
| }))); |
| } |
| |
| std::vector<CrxUpdateItem> items; |
| auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>(); |
| EXPECT_CALL(*receiver, Receive(_)) |
| .WillRepeatedly( |
| [&items](const CrxUpdateItem& item) { items.push_back(item); }); |
| |
| const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf"}; |
| update_client->Update( |
| ids, base::BindOnce(&DataCallbackMock::Callback), |
| base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver), |
| false, ExpectErrorThenQuit(runloop_, Error::NONE)); |
| runloop_.Run(); |
| |
| EXPECT_EQ(3u, items.size()); |
| EXPECT_EQ(ComponentState::kChecking, items[0].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[0].id); |
| EXPECT_EQ(ComponentState::kCanUpdate, items[1].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[1].id); |
| EXPECT_EQ(ComponentState::kUpdateError, items[2].state); |
| EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[2].id); |
| } |
| |
| } // namespace update_client |