blob: c0e036c516f4c9913dd0b60029ff54f637c579f5 [file] [log] [blame]
// 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/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<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:
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"));
}
// ExternalCache::Delegate:
void OnExtensionListsUpdated(const base::DictionaryValue* prefs) override {
prefs_.reset(prefs->DeepCopy());
}
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_;
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<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(
extensions::CRXFileInfo(kTestExtensionId2, temp_file2), true, GURL(), "2",
extensions::ExtensionDownloaderDelegate::PingResult(), std::set<int>(),
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<int>(),
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<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