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