1// Copyright 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/chromeos/extensions/external_cache.h" 6 7#include <map> 8#include <set> 9#include <string> 10 11#include "base/files/file_path.h" 12#include "base/files/file_util.h" 13#include "base/files/scoped_temp_dir.h" 14#include "base/run_loop.h" 15#include "base/test/sequenced_worker_pool_owner.h" 16#include "base/values.h" 17#include "chrome/browser/extensions/external_provider_impl.h" 18#include "content/public/browser/browser_thread.h" 19#include "content/public/test/test_browser_thread_bundle.h" 20#include "extensions/common/extension_urls.h" 21#include "net/url_request/test_url_fetcher_factory.h" 22#include "net/url_request/url_fetcher_impl.h" 23#include "net/url_request/url_request_test_util.h" 24#include "testing/gtest/include/gtest/gtest.h" 25 26namespace { 27 28const char kTestExtensionId1[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; 29const char kTestExtensionId2[] = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; 30const char kTestExtensionId3[] = "cccccccccccccccccccccccccccccccc"; 31const char kTestExtensionId4[] = "dddddddddddddddddddddddddddddddd"; 32const char kNonWebstoreUpdateUrl[] = "https://localhost/service/update2/crx"; 33 34} // namespace 35 36namespace chromeos { 37 38class ExternalCacheTest : public testing::Test, 39 public ExternalCache::Delegate { 40 public: 41 ExternalCacheTest() 42 : thread_bundle_(content::TestBrowserThreadBundle::REAL_IO_THREAD) { 43 } 44 virtual ~ExternalCacheTest() {} 45 46 scoped_refptr<base::SequencedTaskRunner> background_task_runner() { 47 return background_task_runner_; 48 } 49 50 net::URLRequestContextGetter* request_context_getter() { 51 return request_context_getter_.get(); 52 } 53 54 const base::DictionaryValue* provided_prefs() { 55 return prefs_.get(); 56 } 57 58 // testing::Test overrides: 59 virtual void SetUp() OVERRIDE { 60 request_context_getter_ = new net::TestURLRequestContextGetter( 61 content::BrowserThread::GetMessageLoopProxyForThread( 62 content::BrowserThread::IO)); 63 fetcher_factory_.reset(new net::TestURLFetcherFactory()); 64 65 pool_owner_.reset( 66 new base::SequencedWorkerPoolOwner(3, "Background Pool")); 67 background_task_runner_ = pool_owner_->pool()->GetSequencedTaskRunner( 68 pool_owner_->pool()->GetNamedSequenceToken("background")); 69 } 70 71 virtual void TearDown() OVERRIDE { 72 pool_owner_->pool()->Shutdown(); 73 base::RunLoop().RunUntilIdle(); 74 } 75 76 // ExternalCache::Delegate: 77 virtual void OnExtensionListsUpdated( 78 const base::DictionaryValue* prefs) OVERRIDE { 79 prefs_.reset(prefs->DeepCopy()); 80 } 81 82 virtual std::string GetInstalledExtensionVersion( 83 const std::string& id) OVERRIDE { 84 std::map<std::string, std::string>::iterator it = 85 installed_extensions_.find(id); 86 return it != installed_extensions_.end() ? it->second : std::string(); 87 } 88 89 base::FilePath CreateCacheDir(bool initialized) { 90 EXPECT_TRUE(cache_dir_.CreateUniqueTempDir()); 91 if (initialized) 92 CreateFlagFile(cache_dir_.path()); 93 return cache_dir_.path(); 94 } 95 96 base::FilePath CreateTempDir() { 97 EXPECT_TRUE(temp_dir_.CreateUniqueTempDir()); 98 return temp_dir_.path(); 99 } 100 101 void CreateFlagFile(const base::FilePath& dir) { 102 CreateFile(dir.Append( 103 extensions::LocalExtensionCache::kCacheReadyFlagFileName)); 104 } 105 106 void CreateExtensionFile(const base::FilePath& dir, 107 const std::string& id, 108 const std::string& version) { 109 CreateFile(GetExtensionFile(dir, id, version)); 110 } 111 112 void CreateFile(const base::FilePath& file) { 113 EXPECT_EQ(base::WriteFile(file, NULL, 0), 0); 114 } 115 116 base::FilePath GetExtensionFile(const base::FilePath& dir, 117 const std::string& id, 118 const std::string& version) { 119 return dir.Append(id + "-" + version + ".crx"); 120 } 121 122 base::DictionaryValue* CreateEntryWithUpdateUrl(bool from_webstore) { 123 base::DictionaryValue* entry = new base::DictionaryValue; 124 entry->SetString(extensions::ExternalProviderImpl::kExternalUpdateUrl, 125 from_webstore ? extension_urls::GetWebstoreUpdateUrl().spec() 126 : kNonWebstoreUpdateUrl); 127 return entry; 128 } 129 130 void WaitForCompletion() { 131 // Wait for background task completion that sends replay to UI thread. 132 pool_owner_->pool()->FlushForTesting(); 133 // Wait for UI thread task completion. 134 base::RunLoop().RunUntilIdle(); 135 } 136 137 void AddInstalledExtension(const std::string& id, 138 const std::string& version) { 139 installed_extensions_[id] = version; 140 } 141 142 private: 143 content::TestBrowserThreadBundle thread_bundle_; 144 145 scoped_refptr<net::URLRequestContextGetter> request_context_getter_; 146 scoped_ptr<net::TestURLFetcherFactory> fetcher_factory_; 147 148 scoped_ptr<base::SequencedWorkerPoolOwner> pool_owner_; 149 scoped_refptr<base::SequencedTaskRunner> background_task_runner_; 150 151 base::ScopedTempDir cache_dir_; 152 base::ScopedTempDir temp_dir_; 153 scoped_ptr<base::DictionaryValue> prefs_; 154 std::map<std::string, std::string> installed_extensions_; 155 156 DISALLOW_COPY_AND_ASSIGN(ExternalCacheTest); 157}; 158 159TEST_F(ExternalCacheTest, Basic) { 160 base::FilePath cache_dir(CreateCacheDir(false)); 161 ExternalCache external_cache(cache_dir, request_context_getter(), 162 background_task_runner(), this, true, false); 163 164 scoped_ptr<base::DictionaryValue> prefs(new base::DictionaryValue); 165 base::DictionaryValue* dict = CreateEntryWithUpdateUrl(true); 166 prefs->Set(kTestExtensionId1, dict); 167 CreateExtensionFile(cache_dir, kTestExtensionId1, "1"); 168 dict = CreateEntryWithUpdateUrl(true); 169 prefs->Set(kTestExtensionId2, dict); 170 prefs->Set(kTestExtensionId3, CreateEntryWithUpdateUrl(false)); 171 CreateExtensionFile(cache_dir, kTestExtensionId3, "3"); 172 prefs->Set(kTestExtensionId4, CreateEntryWithUpdateUrl(false)); 173 174 external_cache.UpdateExtensionsList(prefs.Pass()); 175 WaitForCompletion(); 176 177 ASSERT_TRUE(provided_prefs()); 178 EXPECT_EQ(provided_prefs()->size(), 2ul); 179 180 // File in cache from Webstore. 181 const base::DictionaryValue* entry1 = NULL; 182 ASSERT_TRUE(provided_prefs()->GetDictionary(kTestExtensionId1, &entry1)); 183 EXPECT_FALSE(entry1->HasKey( 184 extensions::ExternalProviderImpl::kExternalUpdateUrl)); 185 EXPECT_TRUE(entry1->HasKey( 186 extensions::ExternalProviderImpl::kExternalCrx)); 187 EXPECT_TRUE(entry1->HasKey( 188 extensions::ExternalProviderImpl::kExternalVersion)); 189 bool from_webstore = false; 190 EXPECT_TRUE(entry1->GetBoolean( 191 extensions::ExternalProviderImpl::kIsFromWebstore, &from_webstore)); 192 EXPECT_TRUE(from_webstore); 193 194 // File in cache not from Webstore. 195 const base::DictionaryValue* entry3 = NULL; 196 ASSERT_TRUE(provided_prefs()->GetDictionary(kTestExtensionId3, &entry3)); 197 EXPECT_FALSE(entry3->HasKey( 198 extensions::ExternalProviderImpl::kExternalUpdateUrl)); 199 EXPECT_TRUE(entry3->HasKey( 200 extensions::ExternalProviderImpl::kExternalCrx)); 201 EXPECT_TRUE(entry3->HasKey( 202 extensions::ExternalProviderImpl::kExternalVersion)); 203 EXPECT_FALSE(entry3->HasKey( 204 extensions::ExternalProviderImpl::kIsFromWebstore)); 205 206 // Update from Webstore. 207 base::FilePath temp_dir(CreateTempDir()); 208 base::FilePath temp_file2 = temp_dir.Append("b.crx"); 209 CreateFile(temp_file2); 210 external_cache.OnExtensionDownloadFinished(kTestExtensionId2, 211 temp_file2, 212 true, 213 GURL(), 214 "2", 215 extensions::ExtensionDownloaderDelegate::PingResult(), 216 std::set<int>()); 217 218 WaitForCompletion(); 219 EXPECT_EQ(provided_prefs()->size(), 3ul); 220 221 const base::DictionaryValue* entry2 = NULL; 222 ASSERT_TRUE(provided_prefs()->GetDictionary(kTestExtensionId2, &entry2)); 223 EXPECT_FALSE(entry2->HasKey( 224 extensions::ExternalProviderImpl::kExternalUpdateUrl)); 225 EXPECT_TRUE(entry2->HasKey( 226 extensions::ExternalProviderImpl::kExternalCrx)); 227 EXPECT_TRUE(entry2->HasKey( 228 extensions::ExternalProviderImpl::kExternalVersion)); 229 from_webstore = false; 230 EXPECT_TRUE(entry2->GetBoolean( 231 extensions::ExternalProviderImpl::kIsFromWebstore, &from_webstore)); 232 EXPECT_TRUE(from_webstore); 233 EXPECT_TRUE(base::PathExists( 234 GetExtensionFile(cache_dir, kTestExtensionId2, "2"))); 235 236 // Update not from Webstore. 237 base::FilePath temp_file4 = temp_dir.Append("d.crx"); 238 CreateFile(temp_file4); 239 external_cache.OnExtensionDownloadFinished(kTestExtensionId4, 240 temp_file4, 241 true, 242 GURL(), 243 "4", 244 extensions::ExtensionDownloaderDelegate::PingResult(), 245 std::set<int>()); 246 247 WaitForCompletion(); 248 EXPECT_EQ(provided_prefs()->size(), 4ul); 249 250 const base::DictionaryValue* entry4 = NULL; 251 ASSERT_TRUE(provided_prefs()->GetDictionary(kTestExtensionId4, &entry4)); 252 EXPECT_FALSE(entry4->HasKey( 253 extensions::ExternalProviderImpl::kExternalUpdateUrl)); 254 EXPECT_TRUE(entry4->HasKey( 255 extensions::ExternalProviderImpl::kExternalCrx)); 256 EXPECT_TRUE(entry4->HasKey( 257 extensions::ExternalProviderImpl::kExternalVersion)); 258 EXPECT_FALSE(entry4->HasKey( 259 extensions::ExternalProviderImpl::kIsFromWebstore)); 260 EXPECT_TRUE(base::PathExists( 261 GetExtensionFile(cache_dir, kTestExtensionId4, "4"))); 262 263 // Damaged file should be removed from disk. 264 external_cache.OnDamagedFileDetected( 265 GetExtensionFile(cache_dir, kTestExtensionId2, "2")); 266 WaitForCompletion(); 267 EXPECT_EQ(provided_prefs()->size(), 3ul); 268 EXPECT_FALSE(base::PathExists( 269 GetExtensionFile(cache_dir, kTestExtensionId2, "2"))); 270 271 // Shutdown with callback OnExtensionListsUpdated that clears prefs. 272 scoped_ptr<base::DictionaryValue> empty(new base::DictionaryValue); 273 external_cache.Shutdown( 274 base::Bind(&ExternalCacheTest::OnExtensionListsUpdated, 275 base::Unretained(this), 276 base::Unretained(empty.get()))); 277 WaitForCompletion(); 278 EXPECT_EQ(provided_prefs()->size(), 0ul); 279 280 // After Shutdown directory shouldn't be touched. 281 external_cache.OnDamagedFileDetected( 282 GetExtensionFile(cache_dir, kTestExtensionId4, "4")); 283 WaitForCompletion(); 284 EXPECT_TRUE(base::PathExists( 285 GetExtensionFile(cache_dir, kTestExtensionId4, "4"))); 286} 287 288TEST_F(ExternalCacheTest, PreserveInstalled) { 289 base::FilePath cache_dir(CreateCacheDir(false)); 290 ExternalCache external_cache(cache_dir, request_context_getter(), 291 background_task_runner(), this, true, false); 292 293 scoped_ptr<base::DictionaryValue> prefs(new base::DictionaryValue); 294 prefs->Set(kTestExtensionId1, CreateEntryWithUpdateUrl(true)); 295 prefs->Set(kTestExtensionId2, CreateEntryWithUpdateUrl(true)); 296 297 AddInstalledExtension(kTestExtensionId1, "1"); 298 299 external_cache.UpdateExtensionsList(prefs.Pass()); 300 WaitForCompletion(); 301 302 ASSERT_TRUE(provided_prefs()); 303 EXPECT_EQ(provided_prefs()->size(), 1ul); 304 305 // File not in cache but extension installed. 306 const base::DictionaryValue* entry1 = NULL; 307 ASSERT_TRUE(provided_prefs()->GetDictionary(kTestExtensionId1, &entry1)); 308 EXPECT_TRUE(entry1->HasKey( 309 extensions::ExternalProviderImpl::kExternalUpdateUrl)); 310 EXPECT_FALSE(entry1->HasKey( 311 extensions::ExternalProviderImpl::kExternalCrx)); 312 EXPECT_FALSE(entry1->HasKey( 313 extensions::ExternalProviderImpl::kExternalVersion)); 314} 315 316} // namespace chromeos 317