| // 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 <map> |
| #include <set> |
| #include <string> |
| |
| #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/extensions/external_provider_impl.h" |
| #include "chrome/common/extensions/extension_constants.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/test/test_browser_thread_bundle.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) { |
| } |
| virtual ~ExternalCacheTest() {} |
| |
| scoped_refptr<base::SequencedTaskRunner> 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: |
| virtual 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")); |
| } |
| |
| virtual void TearDown() OVERRIDE { |
| pool_owner_->pool()->Shutdown(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| // ExternalCache::Delegate: |
| virtual void OnExtensionListsUpdated( |
| const base::DictionaryValue* prefs) OVERRIDE { |
| prefs_.reset(prefs->DeepCopy()); |
| } |
| |
| virtual std::string GetInstalledExtensionVersion( |
| const std::string& id) OVERRIDE { |
| std::map<std::string, std::string>::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<net::URLRequestContextGetter> request_context_getter_; |
| scoped_ptr<net::TestURLFetcherFactory> fetcher_factory_; |
| |
| scoped_ptr<base::SequencedWorkerPoolOwner> pool_owner_; |
| scoped_refptr<base::SequencedTaskRunner> background_task_runner_; |
| |
| base::ScopedTempDir cache_dir_; |
| base::ScopedTempDir temp_dir_; |
| scoped_ptr<base::DictionaryValue> prefs_; |
| std::map<std::string, std::string> installed_extensions_; |
| |
| 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<base::DictionaryValue> 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(kTestExtensionId2, |
| temp_file2, |
| true, |
| GURL(), |
| "2", |
| extensions::ExtensionDownloaderDelegate::PingResult(), |
| std::set<int>()); |
| |
| 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(kTestExtensionId4, |
| temp_file4, |
| true, |
| GURL(), |
| "4", |
| extensions::ExtensionDownloaderDelegate::PingResult(), |
| std::set<int>()); |
| |
| 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<base::DictionaryValue> 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<base::DictionaryValue> 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 |