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/file_util.h"
12 #include "base/files/file_path.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 "chrome/common/extensions/extension_constants.h"
19 #include "content/public/browser/browser_thread.h"
20 #include "content/public/test/test_browser_thread_bundle.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
26 namespace {
27
28 const char kTestExtensionId1[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
29 const char kTestExtensionId2[] = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
30 const char kTestExtensionId3[] = "cccccccccccccccccccccccccccccccc";
31 const char kTestExtensionId4[] = "dddddddddddddddddddddddddddddddd";
32 const char kNonWebstoreUpdateUrl[] = "https://localhost/service/update2/crx";
33
34 } // namespace
35
36 namespace chromeos {
37
38 class ExternalCacheTest : public testing::Test,
39 public ExternalCache::Delegate {
40 public:
ExternalCacheTest()41 ExternalCacheTest()
42 : thread_bundle_(content::TestBrowserThreadBundle::REAL_IO_THREAD) {
43 }
~ExternalCacheTest()44 virtual ~ExternalCacheTest() {}
45
background_task_runner()46 scoped_refptr<base::SequencedTaskRunner> background_task_runner() {
47 return background_task_runner_;
48 }
49
request_context_getter()50 net::URLRequestContextGetter* request_context_getter() {
51 return request_context_getter_.get();
52 }
53
provided_prefs()54 const base::DictionaryValue* provided_prefs() {
55 return prefs_.get();
56 }
57
58 // testing::Test overrides:
SetUp()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
TearDown()71 virtual void TearDown() OVERRIDE {
72 pool_owner_->pool()->Shutdown();
73 base::RunLoop().RunUntilIdle();
74 }
75
76 // ExternalCache::Delegate:
OnExtensionListsUpdated(const base::DictionaryValue * prefs)77 virtual void OnExtensionListsUpdated(
78 const base::DictionaryValue* prefs) OVERRIDE {
79 prefs_.reset(prefs->DeepCopy());
80 }
81
GetInstalledExtensionVersion(const std::string & id)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
CreateCacheDir(bool initialized)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
CreateTempDir()96 base::FilePath CreateTempDir() {
97 EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
98 return temp_dir_.path();
99 }
100
CreateFlagFile(const base::FilePath & dir)101 void CreateFlagFile(const base::FilePath& dir) {
102 CreateFile(dir.Append(
103 extensions::LocalExtensionCache::kCacheReadyFlagFileName));
104 }
105
CreateExtensionFile(const base::FilePath & dir,const std::string & id,const std::string & version)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
CreateFile(const base::FilePath & file)112 void CreateFile(const base::FilePath& file) {
113 EXPECT_EQ(base::WriteFile(file, NULL, 0), 0);
114 }
115
GetExtensionFile(const base::FilePath & dir,const std::string & id,const std::string & version)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
CreateEntryWithUpdateUrl(bool from_webstore)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
WaitForCompletion()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
AddInstalledExtension(const std::string & id,const std::string & version)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
TEST_F(ExternalCacheTest,Basic)159 TEST_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
TEST_F(ExternalCacheTest,PreserveInstalled)288 TEST_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