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