diff options
Diffstat (limited to 'components/update_client/test')
9 files changed, 1454 insertions, 24 deletions
diff --git a/components/update_client/test/DEPS b/components/update_client/test/DEPS index 60dbcf4..d0c36c4 100644 --- a/components/update_client/test/DEPS +++ b/components/update_client/test/DEPS @@ -1,3 +1,5 @@ include_rules = [ "+content", + "+courgette", + "+net", ] diff --git a/components/update_client/test/crx_downloader_unittest.cc b/components/update_client/test/crx_downloader_unittest.cc index 5b01b42..4b80f13 100644 --- a/components/update_client/test/crx_downloader_unittest.cc +++ b/components/update_client/test/crx_downloader_unittest.cc @@ -113,10 +113,10 @@ void CrxDownloaderTest::SetUp() { num_progress_calls_ = 0; download_progress_result_ = CrxDownloader::Result(); - crx_downloader_.reset(CrxDownloader::Create( - false, // Do not use the background downloader in these tests. - context_.get(), base::MessageLoopProxy::current(), - NULL)); // No |background_task_runner| because no background downloader. + // Do not use the background downloader in these tests. + crx_downloader_.reset(CrxDownloader::Create(false, context_.get(), + base::MessageLoopProxy::current(), + NULL).release()); crx_downloader_->set_progress_callback(progress_callback_); get_interceptor_.reset(new GetInterceptor(base::MessageLoopProxy::current(), diff --git a/components/update_client/test/ping_manager_unittest.cc b/components/update_client/test/ping_manager_unittest.cc index 95539f03..23308ac 100644 --- a/components/update_client/test/ping_manager_unittest.cc +++ b/components/update_client/test/ping_manager_unittest.cc @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" #include "base/run_loop.h" @@ -29,25 +30,25 @@ class ComponentUpdaterPingManagerTest : public testing::Test { void TearDown() override; protected: - scoped_ptr<TestConfigurator> config_; + scoped_refptr<TestConfigurator> config_; scoped_ptr<PingManager> ping_manager_; private: base::MessageLoopForIO loop_; }; -ComponentUpdaterPingManagerTest::ComponentUpdaterPingManagerTest() - : config_(new TestConfigurator(base::MessageLoopProxy::current(), - base::MessageLoopProxy::current())) { +ComponentUpdaterPingManagerTest::ComponentUpdaterPingManagerTest() { } void ComponentUpdaterPingManagerTest::SetUp() { + config_ = new TestConfigurator(base::MessageLoopProxy::current(), + base::MessageLoopProxy::current()); ping_manager_.reset(new PingManager(*config_)); } void ComponentUpdaterPingManagerTest::TearDown() { ping_manager_.reset(); - config_.reset(); + config_ = nullptr; } void ComponentUpdaterPingManagerTest::RunThreadsUntilIdle() { @@ -65,7 +66,7 @@ TEST_F(ComponentUpdaterPingManagerTest, DISABLED_PingManagerTest) { // Test eventresult="1" is sent for successful updates. CrxUpdateItem item; item.id = "abc"; - item.status = CrxUpdateItem::kUpdated; + item.state = CrxUpdateItem::State::kUpdated; item.previous_version = base::Version("1.0"); item.next_version = base::Version("2.0"); @@ -83,7 +84,7 @@ TEST_F(ComponentUpdaterPingManagerTest, DISABLED_PingManagerTest) { // Test eventresult="0" is sent for failed updates. item = CrxUpdateItem(); item.id = "abc"; - item.status = CrxUpdateItem::kNoUpdate; + item.state = CrxUpdateItem::State::kNoUpdate; item.previous_version = base::Version("1.0"); item.next_version = base::Version("2.0"); @@ -101,7 +102,7 @@ TEST_F(ComponentUpdaterPingManagerTest, DISABLED_PingManagerTest) { // Test the error values and the fingerprints. item = CrxUpdateItem(); item.id = "abc"; - item.status = CrxUpdateItem::kNoUpdate; + item.state = CrxUpdateItem::State::kNoUpdate; item.previous_version = base::Version("1.0"); item.next_version = base::Version("2.0"); item.previous_fp = "prev fp"; @@ -133,7 +134,7 @@ TEST_F(ComponentUpdaterPingManagerTest, DISABLED_PingManagerTest) { // Test the download metrics. item = CrxUpdateItem(); item.id = "abc"; - item.status = CrxUpdateItem::kUpdated; + item.state = CrxUpdateItem::State::kUpdated; item.previous_version = base::Version("1.0"); item.next_version = base::Version("2.0"); diff --git a/components/update_client/test/request_sender_unittest.cc b/components/update_client/test/request_sender_unittest.cc index f8ad042..35d4487 100644 --- a/components/update_client/test/request_sender_unittest.cc +++ b/components/update_client/test/request_sender_unittest.cc @@ -4,6 +4,7 @@ #include "base/compiler_specific.h" #include "base/macros.h" +#include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" #include "base/run_loop.h" @@ -40,7 +41,7 @@ class RequestSenderTest : public testing::Test { void RunThreads(); void RunThreadsUntilIdle(); - scoped_ptr<TestConfigurator> config_; + scoped_refptr<TestConfigurator> config_; scoped_ptr<RequestSender> request_sender_; scoped_ptr<InterceptorFactory> interceptor_factory_; @@ -66,8 +67,8 @@ RequestSenderTest::~RequestSenderTest() { } void RequestSenderTest::SetUp() { - config_.reset(new TestConfigurator(base::MessageLoopProxy::current(), - base::MessageLoopProxy::current())); + config_ = new TestConfigurator(base::MessageLoopProxy::current(), + base::MessageLoopProxy::current()); interceptor_factory_.reset( new InterceptorFactory(base::MessageLoopProxy::current())); post_interceptor_1 = @@ -88,7 +89,7 @@ void RequestSenderTest::TearDown() { interceptor_factory_.reset(); - config_.reset(); + config_ = nullptr; RunThreadsUntilIdle(); } diff --git a/components/update_client/test/test_configurator.cc b/components/update_client/test/test_configurator.cc index 27aaf5f..23eaeff 100644 --- a/components/update_client/test/test_configurator.cc +++ b/components/update_client/test/test_configurator.cc @@ -68,6 +68,10 @@ int TestConfigurator::OnDemandDelay() const { return ondemand_time_; } +int TestConfigurator::UpdateDelay() const { + return 1; +} + std::vector<GURL> TestConfigurator::UpdateUrl() const { return MakeDefaultUrls(); } diff --git a/components/update_client/test/test_configurator.h b/components/update_client/test/test_configurator.h index 4943cca..6c2ed0e 100644 --- a/components/update_client/test/test_configurator.h +++ b/components/update_client/test/test_configurator.h @@ -55,7 +55,6 @@ class TestConfigurator : public Configurator { TestConfigurator( const scoped_refptr<base::SequencedTaskRunner>& worker_task_runner, const scoped_refptr<base::SingleThreadTaskRunner>& network_task_runner); - ~TestConfigurator() override; // Overrrides for Configurator. int InitialDelay() const override; @@ -64,6 +63,7 @@ class TestConfigurator : public Configurator { int StepDelayMedium() override; int MinimumReCheckWait() const override; int OnDemandDelay() const override; + int UpdateDelay() const override; std::vector<GURL> UpdateUrl() const override; std::vector<GURL> PingUrl() const override; base::Version GetBrowserVersion() const override; @@ -88,6 +88,10 @@ class TestConfigurator : public Configurator { void SetInitialDelay(int seconds); private: + friend class base::RefCountedThreadSafe<TestConfigurator>; + + ~TestConfigurator() override; + scoped_refptr<base::SequencedTaskRunner> worker_task_runner_; scoped_refptr<base::SingleThreadTaskRunner> network_task_runner_; diff --git a/components/update_client/test/test_installer.h b/components/update_client/test/test_installer.h index a31c499..e15ae0a 100644 --- a/components/update_client/test/test_installer.h +++ b/components/update_client/test/test_installer.h @@ -17,9 +17,10 @@ class DictionaryValue; namespace update_client { +// TODO(sorin): consider reducing the number of the installer mocks. // A TestInstaller is an installer that does nothing for installation except // increment a counter. -class TestInstaller : public ComponentInstaller { +class TestInstaller : public CrxInstaller { public: TestInstaller(); diff --git a/components/update_client/test/update_checker_unittest.cc b/components/update_client/test/update_checker_unittest.cc index 20d3e79..ce6922a 100644 --- a/components/update_client/test/update_checker_unittest.cc +++ b/components/update_client/test/update_checker_unittest.cc @@ -60,7 +60,7 @@ class UpdateCheckerTest : public testing::Test { CrxUpdateItem BuildCrxUpdateItem(); - scoped_ptr<TestConfigurator> config_; + scoped_refptr<TestConfigurator> config_; scoped_ptr<UpdateChecker> update_checker_; @@ -86,8 +86,8 @@ UpdateCheckerTest::~UpdateCheckerTest() { } void UpdateCheckerTest::SetUp() { - config_.reset(new TestConfigurator(base::MessageLoopProxy::current(), - base::MessageLoopProxy::current())); + config_ = new TestConfigurator(base::MessageLoopProxy::current(), + base::MessageLoopProxy::current()); interceptor_factory_.reset( new InterceptorFactory(base::MessageLoopProxy::current())); post_interceptor_ = interceptor_factory_->CreateInterceptor(); @@ -106,7 +106,7 @@ void UpdateCheckerTest::TearDown() { post_interceptor_ = NULL; interceptor_factory_.reset(); - config_.reset(); + config_ = nullptr; // The PostInterceptor requires the message loop to run to destruct correctly. // TODO(sorin): This is fragile and should be fixed. @@ -155,7 +155,7 @@ CrxUpdateItem UpdateCheckerTest::BuildCrxUpdateItem() { crx_component.fingerprint = "fp1"; CrxUpdateItem crx_update_item; - crx_update_item.status = CrxUpdateItem::kNew; + crx_update_item.state = CrxUpdateItem::State::kNew; crx_update_item.id = "jebgalgnebhfojomionfpkfelancnnkf"; crx_update_item.component = crx_component; diff --git a/components/update_client/test/update_client_unittest.cc b/components/update_client/test/update_client_unittest.cc new file mode 100644 index 0000000..dbff673 --- /dev/null +++ b/components/update_client/test/update_client_unittest.cc @@ -0,0 +1,1417 @@ +// 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 "base/bind.h" +#include "base/bind_helpers.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/location.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/path_service.h" +#include "base/run_loop.h" +#include "base/thread_task_runner_handle.h" +#include "base/values.h" +#include "base/version.h" +#include "components/update_client/crx_update_item.h" +#include "components/update_client/ping_manager.h" +#include "components/update_client/test/test_configurator.h" +#include "components/update_client/test/test_installer.h" +#include "components/update_client/update_checker.h" +#include "components/update_client/update_client_internal.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/test/test_browser_thread_bundle.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 path; + bool result = CreateTemporaryFile(&path); + if (!result) + return false; + + result = CopyFile(from_path, path); + if (!result) { + DeleteFile(path, false); + return result; + } + + *to_path = path; + return true; +} + +using Events = UpdateClient::Observer::Events; + +class MockObserver : public UpdateClient::Observer { + public: + MOCK_METHOD2(OnEvent, void(Events event, const std::string&)); +}; + +class FakePingManagerImpl : public PingManager { + public: + explicit FakePingManagerImpl(const Configurator& config); + ~FakePingManagerImpl() override; + + void OnUpdateComplete(const CrxUpdateItem* item) override; + + const std::vector<CrxUpdateItem>& items() const; + + private: + std::vector<CrxUpdateItem> items_; + DISALLOW_COPY_AND_ASSIGN(FakePingManagerImpl); +}; + +FakePingManagerImpl::FakePingManagerImpl(const Configurator& config) + : PingManager(config) { +} + +FakePingManagerImpl::~FakePingManagerImpl() { +} + +void FakePingManagerImpl::OnUpdateComplete(const CrxUpdateItem* item) { + items_.push_back(*item); +} + +const std::vector<CrxUpdateItem>& FakePingManagerImpl::items() const { + return items_; +} + +} // namespace + +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Mock; +using ::testing::Return; + +using content::BrowserThread; + +using std::string; + +class UpdateClientTest : public testing::Test { + public: + UpdateClientTest(); + ~UpdateClientTest() override; + + void SetUp() override; + void TearDown() override; + + protected: + void RunThreads(); + + // Returns the full path to a test file. + static base::FilePath TestFilePath(const char* file); + + content::TestBrowserThreadBundle thread_bundle_; + + base::RunLoop runloop_; + base::Closure quit_closure_; + + scoped_refptr<update_client::TestConfigurator> config_; + + private: + DISALLOW_COPY_AND_ASSIGN(UpdateClientTest); +}; + +UpdateClientTest::UpdateClientTest() + : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP), + config_(new TestConfigurator( + BrowserThread::GetBlockingPool() + ->GetSequencedTaskRunnerWithShutdownBehavior( + BrowserThread::GetBlockingPool()->GetSequenceToken(), + base::SequencedWorkerPool::SKIP_ON_SHUTDOWN), + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO))) { +} + +UpdateClientTest::~UpdateClientTest() { +} + +void UpdateClientTest::SetUp() { + quit_closure_ = runloop_.QuitClosure(); +} + +void UpdateClientTest::TearDown() { +} + +void UpdateClientTest::RunThreads() { + runloop_.Run(); +} + +base::FilePath UpdateClientTest::TestFilePath(const char* file) { + base::FilePath path; + 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 DataCallbackFake { + public: + static void Callback(const std::vector<std::string>& ids, + std::vector<CrxComponent>* components) { + CrxComponent crx; + crx.name = "test_jebg"; + crx.pk_hash.assign(jebg_hash, jebg_hash + arraysize(jebg_hash)); + crx.version = Version("0.9"); + crx.installer = new TestInstaller; + components->push_back(crx); + } + }; + + class CompletionCallbackFake { + public: + static void Callback(const base::Closure& quit_closure, int error) { + EXPECT_EQ(0, error); + quit_closure.Run(); + } + }; + + class FakeUpdateChecker : public UpdateChecker { + public: + static scoped_ptr<UpdateChecker> Create(const Configurator& config) { + return scoped_ptr<UpdateChecker>(new FakeUpdateChecker()); + } + + bool CheckForUpdates( + const std::vector<CrxUpdateItem*>& items_to_check, + const std::string& additional_attributes, + const UpdateCheckCallback& update_check_callback) override { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(update_check_callback, GURL(), 0, "", + UpdateResponse::Results())); + return true; + } + }; + + class FakeCrxDownloader : public CrxDownloader { + public: + static scoped_ptr<CrxDownloader> Create( + bool is_background_download, + net::URLRequestContextGetter* context_getter, + const scoped_refptr<base::SequencedTaskRunner>& url_fetcher_task_runner, + const scoped_refptr<base::SingleThreadTaskRunner>& + background_task_runner) { + return scoped_ptr<CrxDownloader>(new FakeCrxDownloader()); + } + + private: + FakeCrxDownloader() : CrxDownloader(scoped_ptr<CrxDownloader>().Pass()) {} + ~FakeCrxDownloader() override {} + + void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); } + }; + + class FakePingManager : public FakePingManagerImpl { + public: + explicit FakePingManager(const Configurator& config) + : FakePingManagerImpl(config) {} + ~FakePingManager() override { EXPECT_TRUE(items().empty()); } + }; + + scoped_ptr<PingManager> ping_manager(new FakePingManager(*config_)); + scoped_ptr<UpdateClient> update_client(new UpdateClientImpl( + config_, ping_manager.Pass(), &FakeUpdateChecker::Create, + &FakeCrxDownloader::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); + + std::vector<std::string> ids; + ids.push_back(std::string("jebgalgnebhfojomionfpkfelancnnkf")); + + update_client->Update( + ids, base::Bind(&DataCallbackFake::Callback), + base::Bind(&CompletionCallbackFake::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 DataCallbackFake { + public: + static void Callback(const std::vector<std::string>& ids, + std::vector<CrxComponent>* components) { + CrxComponent crx1; + crx1.name = "test_jebg"; + crx1.pk_hash.assign(jebg_hash, jebg_hash + arraysize(jebg_hash)); + crx1.version = Version("0.9"); + crx1.installer = new TestInstaller; + + CrxComponent crx2; + crx2.name = "test_abag"; + crx2.pk_hash.assign(abag_hash, abag_hash + arraysize(abag_hash)); + crx2.version = Version("2.2"); + crx2.installer = new TestInstaller; + + components->push_back(crx1); + components->push_back(crx2); + } + }; + + class CompletionCallbackFake { + public: + static void Callback(const base::Closure& quit_closure, int error) { + EXPECT_EQ(0, error); + quit_closure.Run(); + } + }; + + class FakeUpdateChecker : public UpdateChecker { + public: + static scoped_ptr<UpdateChecker> Create(const Configurator& config) { + return scoped_ptr<UpdateChecker>(new FakeUpdateChecker()); + } + + bool CheckForUpdates( + const std::vector<CrxUpdateItem*>& items_to_check, + const std::string& additional_attributes, + const UpdateCheckCallback& update_check_callback) override { + /* + Fake the following response: + + <?xml version='1.0' encoding='UTF-8'?> + <response protocol='3.0'> + <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'/> + </packages> + </manifest> + </updatecheck> + </app> + </response> + */ + UpdateResponse::Result::Manifest::Package package; + package.name = "jebgalgnebhfojomionfpkfelancnnkf.crx"; + + UpdateResponse::Result result; + result.extension_id = "jebgalgnebhfojomionfpkfelancnnkf"; + 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); + + UpdateResponse::Results results; + results.list.push_back(result); + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(update_check_callback, GURL(), 0, "", results)); + return true; + } + }; + + class FakeCrxDownloader : public CrxDownloader { + public: + static scoped_ptr<CrxDownloader> Create( + bool is_background_download, + net::URLRequestContextGetter* context_getter, + const scoped_refptr<base::SequencedTaskRunner>& url_fetcher_task_runner, + const scoped_refptr<base::SingleThreadTaskRunner>& + background_task_runner) { + return scoped_ptr<CrxDownloader>(new FakeCrxDownloader()); + } + + private: + FakeCrxDownloader() : CrxDownloader(scoped_ptr<CrxDownloader>().Pass()) {} + ~FakeCrxDownloader() override {} + + 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; + result.downloaded_bytes = 1843; + result.total_bytes = 1843; + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(&FakeCrxDownloader::OnDownloadProgress, + base::Unretained(this), result)); + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&FakeCrxDownloader::OnDownloadComplete, + base::Unretained(this), true, result, download_metrics)); + } + }; + + class FakePingManager : public FakePingManagerImpl { + public: + explicit FakePingManager(const Configurator& config) + : FakePingManagerImpl(config) {} + ~FakePingManager() override { + const auto& ping_items = items(); + EXPECT_EQ(1U, ping_items.size()); + EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_items[0].id); + EXPECT_TRUE(base::Version("0.9").Equals(ping_items[0].previous_version)); + EXPECT_TRUE(base::Version("1.0").Equals(ping_items[0].next_version)); + EXPECT_EQ(0, ping_items[0].error_category); + EXPECT_EQ(0, ping_items[0].error_code); + } + }; + + scoped_ptr<PingManager> ping_manager(new FakePingManager(*config_)); + scoped_ptr<UpdateClient> update_client(new UpdateClientImpl( + config_, ping_manager.Pass(), &FakeUpdateChecker::Create, + &FakeCrxDownloader::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(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); + + std::vector<std::string> ids; + ids.push_back(std::string("jebgalgnebhfojomionfpkfelancnnkf")); + ids.push_back(std::string("abagagagagagagagagagagagagagagag")); + + update_client->Update( + ids, base::Bind(&DataCallbackFake::Callback), + base::Bind(&CompletionCallbackFake::Callback, quit_closure_)); + + RunThreads(); + + update_client->RemoveObserver(&observer); +} + +// Tests the update check for two CRXs scenario. Both CRXs have updates. +TEST_F(UpdateClientTest, TwoCrxUpdate) { + class DataCallbackFake { + public: + static void Callback(const std::vector<std::string>& ids, + std::vector<CrxComponent>* components) { + CrxComponent crx1; + crx1.name = "test_jebg"; + crx1.pk_hash.assign(jebg_hash, jebg_hash + arraysize(jebg_hash)); + crx1.version = Version("0.9"); + crx1.installer = new TestInstaller; + + CrxComponent crx2; + crx2.name = "test_ihfo"; + crx2.pk_hash.assign(ihfo_hash, ihfo_hash + arraysize(ihfo_hash)); + crx2.version = Version("0.8"); + crx2.installer = new TestInstaller; + + components->push_back(crx1); + components->push_back(crx2); + } + }; + + class CompletionCallbackFake { + public: + static void Callback(const base::Closure& quit_closure, int error) { + EXPECT_EQ(0, error); + quit_closure.Run(); + } + }; + + class FakeUpdateChecker : public UpdateChecker { + public: + static scoped_ptr<UpdateChecker> Create(const Configurator& config) { + return scoped_ptr<UpdateChecker>(new FakeUpdateChecker()); + } + + bool CheckForUpdates( + const std::vector<CrxUpdateItem*>& items_to_check, + const std::string& additional_attributes, + const UpdateCheckCallback& update_check_callback) override { + /* + Fake the following response: + + <?xml version='1.0' encoding='UTF-8'?> + <response protocol='3.0'> + <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'/> + </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'/> + </packages> + </manifest> + </updatecheck> + </app> + </response> + */ + UpdateResponse::Result::Manifest::Package package1; + package1.name = "jebgalgnebhfojomionfpkfelancnnkf.crx"; + + UpdateResponse::Result result1; + result1.extension_id = "jebgalgnebhfojomionfpkfelancnnkf"; + result1.crx_urls.push_back(GURL("http://localhost/download/")); + result1.manifest.version = "1.0"; + result1.manifest.browser_min_version = "11.0.1.0"; + result1.manifest.packages.push_back(package1); + + UpdateResponse::Result::Manifest::Package package2; + package2.name = "ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"; + + UpdateResponse::Result result2; + result2.extension_id = "ihfokbkgjpifnbbojhneepfflplebdkc"; + result2.crx_urls.push_back(GURL("http://localhost/download/")); + result2.manifest.version = "1.0"; + result2.manifest.browser_min_version = "11.0.1.0"; + result2.manifest.packages.push_back(package2); + + UpdateResponse::Results results; + results.list.push_back(result1); + results.list.push_back(result2); + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(update_check_callback, GURL(), 0, "", results)); + return true; + } + }; + + class FakeCrxDownloader : public CrxDownloader { + public: + static scoped_ptr<CrxDownloader> Create( + bool is_background_download, + net::URLRequestContextGetter* context_getter, + const scoped_refptr<base::SequencedTaskRunner>& url_fetcher_task_runner, + const scoped_refptr<base::SingleThreadTaskRunner>& + background_task_runner) { + return scoped_ptr<CrxDownloader>(new FakeCrxDownloader()); + } + + private: + FakeCrxDownloader() : CrxDownloader(scoped_ptr<CrxDownloader>().Pass()) {} + ~FakeCrxDownloader() override {} + + 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; + result.downloaded_bytes = 1843; + result.total_bytes = 1843; + } 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; + result.downloaded_bytes = 53638; + result.total_bytes = 53638; + } else { + NOTREACHED(); + } + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(&FakeCrxDownloader::OnDownloadProgress, + base::Unretained(this), result)); + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&FakeCrxDownloader::OnDownloadComplete, + base::Unretained(this), true, result, download_metrics)); + } + }; + + class FakePingManager : public FakePingManagerImpl { + public: + explicit FakePingManager(const Configurator& config) + : FakePingManagerImpl(config) {} + ~FakePingManager() override { + const auto& ping_items = items(); + EXPECT_EQ(2U, ping_items.size()); + EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_items[0].id); + EXPECT_TRUE(base::Version("0.9").Equals(ping_items[0].previous_version)); + EXPECT_TRUE(base::Version("1.0").Equals(ping_items[0].next_version)); + EXPECT_EQ(0, ping_items[0].error_category); + EXPECT_EQ(0, ping_items[0].error_code); + EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", ping_items[1].id); + EXPECT_TRUE(base::Version("0.8").Equals(ping_items[1].previous_version)); + EXPECT_TRUE(base::Version("1.0").Equals(ping_items[1].next_version)); + EXPECT_EQ(0, ping_items[1].error_category); + EXPECT_EQ(0, ping_items[1].error_code); + } + }; + + scoped_ptr<FakePingManager> ping_manager(new FakePingManager(*config_)); + scoped_ptr<UpdateClient> update_client(new UpdateClientImpl( + config_, ping_manager.Pass(), &FakeUpdateChecker::Create, + &FakeCrxDownloader::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(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, + "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(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); + + std::vector<std::string> ids; + ids.push_back(std::string("jebgalgnebhfojomionfpkfelancnnkf")); + ids.push_back(std::string("ihfokbkgjpifnbbojhneepfflplebdkc")); + + update_client->Update( + ids, base::Bind(&DataCallbackFake::Callback), + base::Bind(&CompletionCallbackFake::Callback, quit_closure_)); + + RunThreads(); + + update_client->RemoveObserver(&observer); +} + +// Tests the differential update scenario for one CRX. +TEST_F(UpdateClientTest, OneCrxDiffUpdate) { + class DataCallbackFake { + public: + static void Callback(const std::vector<std::string>& ids, + std::vector<CrxComponent>* components) { + static int num_calls = 0; + + // Must use the same stateful installer object. + static scoped_refptr<CrxInstaller> installer( + new VersionedTestInstaller()); + + ++num_calls; + + CrxComponent crx; + crx.name = "test_ihfo"; + crx.pk_hash.assign(ihfo_hash, ihfo_hash + arraysize(ihfo_hash)); + crx.installer = installer; + if (num_calls == 1) { + crx.version = Version("0.8"); + } else if (num_calls == 2) { + crx.version = Version("1.0"); + } else { + NOTREACHED(); + } + + components->push_back(crx); + } + }; + + class CompletionCallbackFake { + public: + static void Callback(const base::Closure& quit_closure, int error) { + EXPECT_EQ(0, error); + quit_closure.Run(); + } + }; + + class FakeUpdateChecker : public UpdateChecker { + public: + static scoped_ptr<UpdateChecker> Create(const Configurator& config) { + return scoped_ptr<UpdateChecker>(new FakeUpdateChecker()); + } + + bool CheckForUpdates( + const std::vector<CrxUpdateItem*>& items_to_check, + const std::string& additional_attributes, + const UpdateCheckCallback& update_check_callback) override { + static int num_call = 0; + ++num_call; + + UpdateResponse::Results results; + + if (num_call == 1) { + /* + Fake the following response: + <?xml version='1.0' encoding='UTF-8'?> + <response protocol='3.0'> + <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'/> + </packages> + </manifest> + </updatecheck> + </app> + </response> + */ + UpdateResponse::Result::Manifest::Package package; + package.name = "ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"; + package.fingerprint = "1"; + UpdateResponse::Result result; + result.extension_id = "ihfokbkgjpifnbbojhneepfflplebdkc"; + 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) { + /* + Fake the following response: + <?xml version='1.0' encoding='UTF-8'?> + <response protocol='3.0'> + <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' + fp='22'/> + </packages> + </manifest> + </updatecheck> + </app> + </response> + */ + UpdateResponse::Result::Manifest::Package package; + package.name = "ihfokbkgjpifnbbojhneepfflplebdkc_2.crx"; + package.namediff = "ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx"; + package.fingerprint = "22"; + UpdateResponse::Result result; + result.extension_id = "ihfokbkgjpifnbbojhneepfflplebdkc"; + 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::Bind(update_check_callback, GURL(), 0, "", results)); + return true; + } + }; + + class FakeCrxDownloader : public CrxDownloader { + public: + static scoped_ptr<CrxDownloader> Create( + bool is_background_download, + net::URLRequestContextGetter* context_getter, + const scoped_refptr<base::SequencedTaskRunner>& url_fetcher_task_runner, + const scoped_refptr<base::SingleThreadTaskRunner>& + background_task_runner) { + return scoped_ptr<CrxDownloader>(new FakeCrxDownloader()); + } + + private: + FakeCrxDownloader() : CrxDownloader(scoped_ptr<CrxDownloader>().Pass()) {} + ~FakeCrxDownloader() override {} + + 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; + result.downloaded_bytes = 53638; + result.total_bytes = 53638; + } 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; + result.downloaded_bytes = 2105; + result.total_bytes = 2105; + } else { + NOTREACHED(); + } + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(&FakeCrxDownloader::OnDownloadProgress, + base::Unretained(this), result)); + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&FakeCrxDownloader::OnDownloadComplete, + base::Unretained(this), true, result, download_metrics)); + } + }; + + class FakePingManager : public FakePingManagerImpl { + public: + explicit FakePingManager(const Configurator& config) + : FakePingManagerImpl(config) {} + ~FakePingManager() override { + const auto& ping_items = items(); + EXPECT_EQ(2U, ping_items.size()); + EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", ping_items[0].id); + EXPECT_TRUE(base::Version("0.8").Equals(ping_items[0].previous_version)); + EXPECT_TRUE(base::Version("1.0").Equals(ping_items[0].next_version)); + EXPECT_EQ(0, ping_items[0].error_category); + EXPECT_EQ(0, ping_items[0].error_code); + EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", ping_items[1].id); + EXPECT_TRUE(base::Version("1.0").Equals(ping_items[1].previous_version)); + EXPECT_TRUE(base::Version("2.0").Equals(ping_items[1].next_version)); + EXPECT_EQ(0, ping_items[1].diff_error_category); + EXPECT_EQ(0, ping_items[1].diff_error_code); + } + }; + + scoped_ptr<FakePingManager> ping_manager(new FakePingManager(*config_)); + scoped_ptr<UpdateClient> update_client(new UpdateClientImpl( + config_, ping_manager.Pass(), &FakeUpdateChecker::Create, + &FakeCrxDownloader::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(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(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); + + std::vector<std::string> ids; + ids.push_back(std::string("ihfokbkgjpifnbbojhneepfflplebdkc")); + + { + base::RunLoop runloop; + update_client->Update( + ids, base::Bind(&DataCallbackFake::Callback), + base::Bind(&CompletionCallbackFake::Callback, runloop.QuitClosure())); + runloop.Run(); + } + + { + base::RunLoop runloop; + update_client->Update( + ids, base::Bind(&DataCallbackFake::Callback), + base::Bind(&CompletionCallbackFake::Callback, runloop.QuitClosure())); + runloop.Run(); + } + + update_client->RemoveObserver(&observer); +} + +// Tests the update scenario for one CRX where the CRX installer returns +// an error. +TEST_F(UpdateClientTest, OneCrxInstallError) { + class MockInstaller : public CrxInstaller { + public: + MOCK_METHOD1(OnUpdateError, void(int error)); + MOCK_METHOD2(Install, + bool(const base::DictionaryValue& manifest, + const base::FilePath& unpack_path)); + MOCK_METHOD2(GetInstalledFile, + bool(const std::string& file, base::FilePath* installed_file)); + MOCK_METHOD0(Uninstall, bool()); + + static void OnInstall(const base::DictionaryValue& manifest, + const base::FilePath& unpack_path) { + base::DeleteFile(unpack_path, true); + } + + protected: + ~MockInstaller() override {} + }; + + class DataCallbackFake { + public: + static void Callback(const std::vector<std::string>& ids, + std::vector<CrxComponent>* components) { + scoped_refptr<MockInstaller> installer(new MockInstaller()); + + EXPECT_CALL(*installer, OnUpdateError(_)).Times(0); + EXPECT_CALL(*installer, Install(_, _)) + .WillOnce(DoAll(Invoke(MockInstaller::OnInstall), Return(false))); + 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 + arraysize(jebg_hash)); + crx.version = Version("0.9"); + crx.installer = installer; + components->push_back(crx); + } + }; + + class CompletionCallbackFake { + public: + static void Callback(const base::Closure& quit_closure, int error) { + EXPECT_EQ(0, error); + quit_closure.Run(); + } + }; + + class FakeUpdateChecker : public UpdateChecker { + public: + static scoped_ptr<UpdateChecker> Create(const Configurator& config) { + return scoped_ptr<UpdateChecker>(new FakeUpdateChecker()); + } + + bool CheckForUpdates( + const std::vector<CrxUpdateItem*>& items_to_check, + const std::string& additional_attributes, + const UpdateCheckCallback& update_check_callback) override { + /* + Fake the following response: + + <?xml version='1.0' encoding='UTF-8'?> + <response protocol='3.0'> + <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'/> + </packages> + </manifest> + </updatecheck> + </app> + </response> + */ + UpdateResponse::Result::Manifest::Package package; + package.name = "jebgalgnebhfojomionfpkfelancnnkf.crx"; + + UpdateResponse::Result result; + result.extension_id = "jebgalgnebhfojomionfpkfelancnnkf"; + 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); + + UpdateResponse::Results results; + results.list.push_back(result); + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(update_check_callback, GURL(), 0, "", results)); + return true; + } + }; + + class FakeCrxDownloader : public CrxDownloader { + public: + static scoped_ptr<CrxDownloader> Create( + bool is_background_download, + net::URLRequestContextGetter* context_getter, + const scoped_refptr<base::SequencedTaskRunner>& url_fetcher_task_runner, + const scoped_refptr<base::SingleThreadTaskRunner>& + background_task_runner) { + return scoped_ptr<CrxDownloader>(new FakeCrxDownloader()); + } + + private: + FakeCrxDownloader() : CrxDownloader(scoped_ptr<CrxDownloader>().Pass()) {} + ~FakeCrxDownloader() override {} + + 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; + result.downloaded_bytes = 1843; + result.total_bytes = 1843; + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(&FakeCrxDownloader::OnDownloadProgress, + base::Unretained(this), result)); + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&FakeCrxDownloader::OnDownloadComplete, + base::Unretained(this), true, result, download_metrics)); + } + }; + + class FakePingManager : public FakePingManagerImpl { + public: + explicit FakePingManager(const Configurator& config) + : FakePingManagerImpl(config) {} + ~FakePingManager() override { + const auto& ping_items = items(); + EXPECT_EQ(1U, ping_items.size()); + EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_items[0].id); + EXPECT_TRUE(base::Version("0.9").Equals(ping_items[0].previous_version)); + EXPECT_TRUE(base::Version("1.0").Equals(ping_items[0].next_version)); + EXPECT_EQ(3, ping_items[0].error_category); // kInstallError. + EXPECT_EQ(9, ping_items[0].error_code); // kInstallerError. + } + }; + + scoped_ptr<PingManager> ping_manager(new FakePingManager(*config_)); + scoped_ptr<UpdateClient> update_client(new UpdateClientImpl( + config_, ping_manager.Pass(), &FakeUpdateChecker::Create, + &FakeCrxDownloader::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(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY, + "jebgalgnebhfojomionfpkfelancnnkf")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_NOT_UPDATED, + "jebgalgnebhfojomionfpkfelancnnkf")).Times(1); + } + + update_client->AddObserver(&observer); + + std::vector<std::string> ids; + ids.push_back(std::string("jebgalgnebhfojomionfpkfelancnnkf")); + + update_client->Update( + ids, base::Bind(&DataCallbackFake::Callback), + base::Bind(&CompletionCallbackFake::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 DataCallbackFake { + public: + static void Callback(const std::vector<std::string>& ids, + std::vector<CrxComponent>* components) { + static int num_calls = 0; + + // Must use the same stateful installer object. + static scoped_refptr<CrxInstaller> installer( + new VersionedTestInstaller()); + + ++num_calls; + + CrxComponent crx; + crx.name = "test_ihfo"; + crx.pk_hash.assign(ihfo_hash, ihfo_hash + arraysize(ihfo_hash)); + crx.installer = installer; + if (num_calls == 1) { + crx.version = Version("0.8"); + } else if (num_calls == 2) { + crx.version = Version("1.0"); + } else { + NOTREACHED(); + } + + components->push_back(crx); + } + }; + + class CompletionCallbackFake { + public: + static void Callback(const base::Closure& quit_closure, int error) { + EXPECT_EQ(0, error); + quit_closure.Run(); + } + }; + + class FakeUpdateChecker : public UpdateChecker { + public: + static scoped_ptr<UpdateChecker> Create(const Configurator& config) { + return scoped_ptr<UpdateChecker>(new FakeUpdateChecker()); + } + + bool CheckForUpdates( + const std::vector<CrxUpdateItem*>& items_to_check, + const std::string& additional_attributes, + const UpdateCheckCallback& update_check_callback) override { + static int num_call = 0; + ++num_call; + + UpdateResponse::Results results; + + if (num_call == 1) { + /* + Fake the following response: + <?xml version='1.0' encoding='UTF-8'?> + <response protocol='3.0'> + <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'/> + </packages> + </manifest> + </updatecheck> + </app> + </response> + */ + UpdateResponse::Result::Manifest::Package package; + package.name = "ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"; + package.fingerprint = "1"; + UpdateResponse::Result result; + result.extension_id = "ihfokbkgjpifnbbojhneepfflplebdkc"; + 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) { + /* + Fake the following response: + <?xml version='1.0' encoding='UTF-8'?> + <response protocol='3.0'> + <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' + fp='22'/> + </packages> + </manifest> + </updatecheck> + </app> + </response> + */ + UpdateResponse::Result::Manifest::Package package; + package.name = "ihfokbkgjpifnbbojhneepfflplebdkc_2.crx"; + package.namediff = "ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx"; + package.fingerprint = "22"; + UpdateResponse::Result result; + result.extension_id = "ihfokbkgjpifnbbojhneepfflplebdkc"; + 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::Bind(update_check_callback, GURL(), 0, "", results)); + return true; + } + }; + + class FakeCrxDownloader : public CrxDownloader { + public: + static scoped_ptr<CrxDownloader> Create( + bool is_background_download, + net::URLRequestContextGetter* context_getter, + const scoped_refptr<base::SequencedTaskRunner>& url_fetcher_task_runner, + const scoped_refptr<base::SingleThreadTaskRunner>& + background_task_runner) { + return scoped_ptr<CrxDownloader>(new FakeCrxDownloader()); + } + + private: + FakeCrxDownloader() : CrxDownloader(scoped_ptr<CrxDownloader>().Pass()) {} + ~FakeCrxDownloader() override {} + + 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; + result.downloaded_bytes = 53638; + result.total_bytes = 53638; + } 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; + + EXPECT_TRUE(MakeTestFile( + TestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx"), &path)); + + result.error = -1; + result.response = path; + result.downloaded_bytes = 0; + result.total_bytes = 2105; + } 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; + result.downloaded_bytes = 53855; + result.total_bytes = 53855; + } + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(&FakeCrxDownloader::OnDownloadProgress, + base::Unretained(this), result)); + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&FakeCrxDownloader::OnDownloadComplete, + base::Unretained(this), true, result, download_metrics)); + } + }; + + class FakePingManager : public FakePingManagerImpl { + public: + explicit FakePingManager(const Configurator& config) + : FakePingManagerImpl(config) {} + ~FakePingManager() override { + const auto& ping_items = items(); + EXPECT_EQ(2U, ping_items.size()); + EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", ping_items[0].id); + EXPECT_TRUE(base::Version("0.8").Equals(ping_items[0].previous_version)); + EXPECT_TRUE(base::Version("1.0").Equals(ping_items[0].next_version)); + EXPECT_EQ(0, ping_items[0].error_category); + EXPECT_EQ(0, ping_items[0].error_code); + EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", ping_items[1].id); + EXPECT_TRUE(base::Version("1.0").Equals(ping_items[1].previous_version)); + EXPECT_TRUE(base::Version("2.0").Equals(ping_items[1].next_version)); + EXPECT_TRUE(ping_items[1].diff_update_failed); + EXPECT_EQ(1, ping_items[1].diff_error_category); // kNetworkError. + EXPECT_EQ(-1, ping_items[1].diff_error_code); + } + }; + + scoped_ptr<FakePingManager> ping_manager(new FakePingManager(*config_)); + scoped_ptr<UpdateClient> update_client(new UpdateClientImpl( + config_, ping_manager.Pass(), &FakeUpdateChecker::Create, + &FakeCrxDownloader::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(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(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING, + "ihfokbkgjpifnbbojhneepfflplebdkc")).Times(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); + + std::vector<std::string> ids; + ids.push_back(std::string("ihfokbkgjpifnbbojhneepfflplebdkc")); + + { + base::RunLoop runloop; + update_client->Update( + ids, base::Bind(&DataCallbackFake::Callback), + base::Bind(&CompletionCallbackFake::Callback, runloop.QuitClosure())); + runloop.Run(); + } + + { + base::RunLoop runloop; + update_client->Update( + ids, base::Bind(&DataCallbackFake::Callback), + base::Bind(&CompletionCallbackFake::Callback, runloop.QuitClosure())); + runloop.Run(); + } + + update_client->RemoveObserver(&observer); +} + +} // namespace update_client |