// Copyright 2013 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 "chrome/browser/chromeos/extensions/external_cache.h" #include #include #include #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" #include "base/run_loop.h" #include "base/test/sequenced_worker_pool_owner.h" #include "base/values.h" #include "chrome/browser/chromeos/settings/cros_settings.h" #include "chrome/browser/chromeos/settings/device_settings_service.h" #include "chrome/browser/extensions/external_provider_impl.h" #include "content/public/browser/browser_thread.h" #include "content/public/test/test_browser_thread_bundle.h" #include "extensions/common/extension_urls.h" #include "net/url_request/test_url_fetcher_factory.h" #include "net/url_request/url_fetcher_impl.h" #include "net/url_request/url_request_test_util.h" #include "testing/gtest/include/gtest/gtest.h" namespace { const char kTestExtensionId1[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; const char kTestExtensionId2[] = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; const char kTestExtensionId3[] = "cccccccccccccccccccccccccccccccc"; const char kTestExtensionId4[] = "dddddddddddddddddddddddddddddddd"; const char kNonWebstoreUpdateUrl[] = "https://localhost/service/update2/crx"; } // namespace namespace chromeos { class ExternalCacheTest : public testing::Test, public ExternalCache::Delegate { public: ExternalCacheTest() : thread_bundle_(content::TestBrowserThreadBundle::REAL_IO_THREAD) { } ~ExternalCacheTest() override {} scoped_refptr background_task_runner() { return background_task_runner_; } net::URLRequestContextGetter* request_context_getter() { return request_context_getter_.get(); } const base::DictionaryValue* provided_prefs() { return prefs_.get(); } // testing::Test overrides: void SetUp() override { request_context_getter_ = new net::TestURLRequestContextGetter( content::BrowserThread::GetMessageLoopProxyForThread( content::BrowserThread::IO)); fetcher_factory_.reset(new net::TestURLFetcherFactory()); pool_owner_.reset( new base::SequencedWorkerPoolOwner(3, "Background Pool")); background_task_runner_ = pool_owner_->pool()->GetSequencedTaskRunner( pool_owner_->pool()->GetNamedSequenceToken("background")); } void TearDown() override { pool_owner_->pool()->Shutdown(); base::RunLoop().RunUntilIdle(); } // ExternalCache::Delegate: void OnExtensionListsUpdated(const base::DictionaryValue* prefs) override { prefs_.reset(prefs->DeepCopy()); } std::string GetInstalledExtensionVersion(const std::string& id) override { std::map::iterator it = installed_extensions_.find(id); return it != installed_extensions_.end() ? it->second : std::string(); } base::FilePath CreateCacheDir(bool initialized) { EXPECT_TRUE(cache_dir_.CreateUniqueTempDir()); if (initialized) CreateFlagFile(cache_dir_.path()); return cache_dir_.path(); } base::FilePath CreateTempDir() { EXPECT_TRUE(temp_dir_.CreateUniqueTempDir()); return temp_dir_.path(); } void CreateFlagFile(const base::FilePath& dir) { CreateFile(dir.Append( extensions::LocalExtensionCache::kCacheReadyFlagFileName)); } void CreateExtensionFile(const base::FilePath& dir, const std::string& id, const std::string& version) { CreateFile(GetExtensionFile(dir, id, version)); } void CreateFile(const base::FilePath& file) { EXPECT_EQ(base::WriteFile(file, NULL, 0), 0); } base::FilePath GetExtensionFile(const base::FilePath& dir, const std::string& id, const std::string& version) { return dir.Append(id + "-" + version + ".crx"); } base::DictionaryValue* CreateEntryWithUpdateUrl(bool from_webstore) { base::DictionaryValue* entry = new base::DictionaryValue; entry->SetString(extensions::ExternalProviderImpl::kExternalUpdateUrl, from_webstore ? extension_urls::GetWebstoreUpdateUrl().spec() : kNonWebstoreUpdateUrl); return entry; } void WaitForCompletion() { // Wait for background task completion that sends replay to UI thread. pool_owner_->pool()->FlushForTesting(); // Wait for UI thread task completion. base::RunLoop().RunUntilIdle(); } void AddInstalledExtension(const std::string& id, const std::string& version) { installed_extensions_[id] = version; } private: content::TestBrowserThreadBundle thread_bundle_; scoped_refptr request_context_getter_; scoped_ptr fetcher_factory_; scoped_ptr pool_owner_; scoped_refptr background_task_runner_; base::ScopedTempDir cache_dir_; base::ScopedTempDir temp_dir_; scoped_ptr prefs_; std::map installed_extensions_; ScopedTestDeviceSettingsService test_device_settings_service_; ScopedTestCrosSettings test_cros_settings_; DISALLOW_COPY_AND_ASSIGN(ExternalCacheTest); }; TEST_F(ExternalCacheTest, Basic) { base::FilePath cache_dir(CreateCacheDir(false)); ExternalCache external_cache(cache_dir, request_context_getter(), background_task_runner(), this, true, false); scoped_ptr prefs(new base::DictionaryValue); base::DictionaryValue* dict = CreateEntryWithUpdateUrl(true); prefs->Set(kTestExtensionId1, dict); CreateExtensionFile(cache_dir, kTestExtensionId1, "1"); dict = CreateEntryWithUpdateUrl(true); prefs->Set(kTestExtensionId2, dict); prefs->Set(kTestExtensionId3, CreateEntryWithUpdateUrl(false)); CreateExtensionFile(cache_dir, kTestExtensionId3, "3"); prefs->Set(kTestExtensionId4, CreateEntryWithUpdateUrl(false)); external_cache.UpdateExtensionsList(prefs.Pass()); WaitForCompletion(); ASSERT_TRUE(provided_prefs()); EXPECT_EQ(provided_prefs()->size(), 2ul); // File in cache from Webstore. const base::DictionaryValue* entry1 = NULL; ASSERT_TRUE(provided_prefs()->GetDictionary(kTestExtensionId1, &entry1)); EXPECT_FALSE(entry1->HasKey( extensions::ExternalProviderImpl::kExternalUpdateUrl)); EXPECT_TRUE(entry1->HasKey( extensions::ExternalProviderImpl::kExternalCrx)); EXPECT_TRUE(entry1->HasKey( extensions::ExternalProviderImpl::kExternalVersion)); bool from_webstore = false; EXPECT_TRUE(entry1->GetBoolean( extensions::ExternalProviderImpl::kIsFromWebstore, &from_webstore)); EXPECT_TRUE(from_webstore); // File in cache not from Webstore. const base::DictionaryValue* entry3 = NULL; ASSERT_TRUE(provided_prefs()->GetDictionary(kTestExtensionId3, &entry3)); EXPECT_FALSE(entry3->HasKey( extensions::ExternalProviderImpl::kExternalUpdateUrl)); EXPECT_TRUE(entry3->HasKey( extensions::ExternalProviderImpl::kExternalCrx)); EXPECT_TRUE(entry3->HasKey( extensions::ExternalProviderImpl::kExternalVersion)); EXPECT_FALSE(entry3->HasKey( extensions::ExternalProviderImpl::kIsFromWebstore)); // Update from Webstore. base::FilePath temp_dir(CreateTempDir()); base::FilePath temp_file2 = temp_dir.Append("b.crx"); CreateFile(temp_file2); external_cache.OnExtensionDownloadFinished( extensions::CRXFileInfo(kTestExtensionId2, temp_file2), true, GURL(), "2", extensions::ExtensionDownloaderDelegate::PingResult(), std::set(), extensions::ExtensionDownloaderDelegate::InstallCallback()); WaitForCompletion(); EXPECT_EQ(provided_prefs()->size(), 3ul); const base::DictionaryValue* entry2 = NULL; ASSERT_TRUE(provided_prefs()->GetDictionary(kTestExtensionId2, &entry2)); EXPECT_FALSE(entry2->HasKey( extensions::ExternalProviderImpl::kExternalUpdateUrl)); EXPECT_TRUE(entry2->HasKey( extensions::ExternalProviderImpl::kExternalCrx)); EXPECT_TRUE(entry2->HasKey( extensions::ExternalProviderImpl::kExternalVersion)); from_webstore = false; EXPECT_TRUE(entry2->GetBoolean( extensions::ExternalProviderImpl::kIsFromWebstore, &from_webstore)); EXPECT_TRUE(from_webstore); EXPECT_TRUE(base::PathExists( GetExtensionFile(cache_dir, kTestExtensionId2, "2"))); // Update not from Webstore. base::FilePath temp_file4 = temp_dir.Append("d.crx"); CreateFile(temp_file4); external_cache.OnExtensionDownloadFinished( extensions::CRXFileInfo(kTestExtensionId4, temp_file4), true, GURL(), "4", extensions::ExtensionDownloaderDelegate::PingResult(), std::set(), extensions::ExtensionDownloaderDelegate::InstallCallback()); WaitForCompletion(); EXPECT_EQ(provided_prefs()->size(), 4ul); const base::DictionaryValue* entry4 = NULL; ASSERT_TRUE(provided_prefs()->GetDictionary(kTestExtensionId4, &entry4)); EXPECT_FALSE(entry4->HasKey( extensions::ExternalProviderImpl::kExternalUpdateUrl)); EXPECT_TRUE(entry4->HasKey( extensions::ExternalProviderImpl::kExternalCrx)); EXPECT_TRUE(entry4->HasKey( extensions::ExternalProviderImpl::kExternalVersion)); EXPECT_FALSE(entry4->HasKey( extensions::ExternalProviderImpl::kIsFromWebstore)); EXPECT_TRUE(base::PathExists( GetExtensionFile(cache_dir, kTestExtensionId4, "4"))); // Damaged file should be removed from disk. external_cache.OnDamagedFileDetected( GetExtensionFile(cache_dir, kTestExtensionId2, "2")); WaitForCompletion(); EXPECT_EQ(provided_prefs()->size(), 3ul); EXPECT_FALSE(base::PathExists( GetExtensionFile(cache_dir, kTestExtensionId2, "2"))); // Shutdown with callback OnExtensionListsUpdated that clears prefs. scoped_ptr empty(new base::DictionaryValue); external_cache.Shutdown( base::Bind(&ExternalCacheTest::OnExtensionListsUpdated, base::Unretained(this), base::Unretained(empty.get()))); WaitForCompletion(); EXPECT_EQ(provided_prefs()->size(), 0ul); // After Shutdown directory shouldn't be touched. external_cache.OnDamagedFileDetected( GetExtensionFile(cache_dir, kTestExtensionId4, "4")); WaitForCompletion(); EXPECT_TRUE(base::PathExists( GetExtensionFile(cache_dir, kTestExtensionId4, "4"))); } TEST_F(ExternalCacheTest, PreserveInstalled) { base::FilePath cache_dir(CreateCacheDir(false)); ExternalCache external_cache(cache_dir, request_context_getter(), background_task_runner(), this, true, false); scoped_ptr prefs(new base::DictionaryValue); prefs->Set(kTestExtensionId1, CreateEntryWithUpdateUrl(true)); prefs->Set(kTestExtensionId2, CreateEntryWithUpdateUrl(true)); AddInstalledExtension(kTestExtensionId1, "1"); external_cache.UpdateExtensionsList(prefs.Pass()); WaitForCompletion(); ASSERT_TRUE(provided_prefs()); EXPECT_EQ(provided_prefs()->size(), 1ul); // File not in cache but extension installed. const base::DictionaryValue* entry1 = NULL; ASSERT_TRUE(provided_prefs()->GetDictionary(kTestExtensionId1, &entry1)); EXPECT_TRUE(entry1->HasKey( extensions::ExternalProviderImpl::kExternalUpdateUrl)); EXPECT_FALSE(entry1->HasKey( extensions::ExternalProviderImpl::kExternalCrx)); EXPECT_FALSE(entry1->HasKey( extensions::ExternalProviderImpl::kExternalVersion)); } } // namespace chromeos