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 <vector>
6
7#include "base/files/file_enumerator.h"
8#include "base/files/file_util.h"
9#include "base/files/scoped_temp_dir.h"
10#include "base/memory/ref_counted.h"
11#include "base/memory/scoped_ptr.h"
12#include "base/message_loop/message_loop.h"
13#include "base/run_loop.h"
14#include "build/build_config.h"
15#include "chrome/browser/media_galleries/fileapi/media_file_system_backend.h"
16#include "chrome/browser/media_galleries/fileapi/picasa_data_provider.h"
17#include "chrome/common/media_galleries/picasa_test_util.h"
18#include "chrome/common/media_galleries/picasa_types.h"
19#include "chrome/test/base/in_process_browser_test.h"
20#include "content/public/test/test_browser_thread.h"
21
22namespace picasa {
23
24namespace {
25
26void VerifyTestAlbumTable(PicasaDataProvider* data_provider,
27                          base::FilePath test_folder_1_path,
28                          base::FilePath test_folder_2_path) {
29  scoped_ptr<AlbumMap> folders = data_provider->GetFolders();
30  ASSERT_TRUE(folders.get());
31  EXPECT_EQ(2u, folders->size());
32
33  AlbumMap::const_iterator folder_1 = folders->find(
34      test_folder_1_path.BaseName().AsUTF8Unsafe() + " 1899-12-30");
35  EXPECT_NE(folders->end(), folder_1);
36  EXPECT_EQ(test_folder_1_path.BaseName().AsUTF8Unsafe(),
37            folder_1->second.name);
38  EXPECT_EQ(test_folder_1_path, folder_1->second.path);
39  EXPECT_EQ("uid1", folder_1->second.uid);
40
41  AlbumMap::const_iterator folder_2 = folders->find(
42      test_folder_2_path.BaseName().AsUTF8Unsafe() + " 1899-12-30");
43  EXPECT_NE(folders->end(), folder_2);
44  EXPECT_EQ(test_folder_2_path.BaseName().AsUTF8Unsafe(),
45            folder_2->second.name);
46  EXPECT_EQ(test_folder_2_path, folder_2->second.path);
47  EXPECT_EQ("uid4", folder_2->second.uid);
48
49  scoped_ptr<AlbumMap> albums = data_provider->GetAlbums();
50  ASSERT_TRUE(albums.get());
51  EXPECT_EQ(2u, albums->size());
52
53  AlbumMap::const_iterator album_1 = albums->find("Album 1 Name 1899-12-30");
54  EXPECT_NE(albums->end(), album_1);
55  EXPECT_EQ("Album 1 Name", album_1->second.name);
56  EXPECT_EQ(base::FilePath(), album_1->second.path);
57  EXPECT_EQ("uid3", album_1->second.uid);
58
59  AlbumMap::const_iterator album_2 = albums->find("Album 2 Name 1899-12-30");
60  EXPECT_NE(albums->end(), album_2);
61  EXPECT_EQ("Album 2 Name", album_2->second.name);
62  EXPECT_EQ(base::FilePath(), album_2->second.path);
63  EXPECT_EQ("uid5", album_2->second.uid);
64}
65
66void VerifyTestAlbumsImagesIndex(PicasaDataProvider* data_provider,
67                                 base::FilePath test_folder_1_path,
68                                 base::FilePath test_folder_2_path) {
69  base::File::Error error;
70  scoped_ptr<AlbumImages> album_1_images =
71      data_provider->FindAlbumImages("uid3", &error);
72  ASSERT_TRUE(album_1_images);
73  EXPECT_EQ(base::File::FILE_OK, error);
74  EXPECT_EQ(2u, album_1_images->size());
75  EXPECT_NE(album_1_images->end(), album_1_images->find("InBoth.jpg"));
76  EXPECT_EQ(test_folder_1_path.AppendASCII("InBoth.jpg"),
77            (*album_1_images)["InBoth.jpg"]);
78  EXPECT_NE(album_1_images->end(),
79            album_1_images->find("InFirstAlbumOnly.jpg"));
80  EXPECT_EQ(test_folder_2_path.AppendASCII("InFirstAlbumOnly.jpg"),
81            (*album_1_images)["InFirstAlbumOnly.jpg"]);
82
83  scoped_ptr<AlbumImages> album_2_images =
84      data_provider->FindAlbumImages("uid5", &error);
85  ASSERT_TRUE(album_2_images);
86  EXPECT_EQ(base::File::FILE_OK, error);
87  EXPECT_EQ(2u, album_2_images->size());
88  EXPECT_NE(album_2_images->end(), album_2_images->find("InBoth.jpg"));
89  EXPECT_EQ(test_folder_1_path.AppendASCII("InBoth.jpg"),
90            (*album_2_images)["InBoth.jpg"]);
91  EXPECT_NE(album_2_images->end(),
92            album_2_images->find("InSecondAlbumOnly.jpg"));
93  EXPECT_EQ(test_folder_1_path.AppendASCII("InSecondAlbumOnly.jpg"),
94            (*album_2_images)["InSecondAlbumOnly.jpg"]);
95}
96
97}  // namespace
98
99class TestPicasaDataProvider : public PicasaDataProvider {
100 public:
101  explicit TestPicasaDataProvider(const base::FilePath& database_path)
102      : PicasaDataProvider(database_path),
103        file_watch_request_returned_(false)  {
104  }
105
106  virtual ~TestPicasaDataProvider() {}
107
108  // |ready_callback| called with true if and when the file watch is started
109  // successfully. If the file watch fails, it's called with false.
110  void EnsureFileWatchStartedForTesting(const ReadyCallback& ready_callback) {
111    if (!file_watch_request_returned_) {
112      file_watch_started_callbacks_.push_back(ready_callback);
113      return;
114    }
115    ready_callback.Run(temp_dir_watcher_.get() != NULL);
116  }
117
118  // Simulates the actual writing process of moving all the database files
119  // from the temporary directory to the database directory in a loop.
120  void MoveTempFilesToDatabase() {
121    DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
122
123    base::FileEnumerator file_enumerator(
124        database_path_.DirName().AppendASCII(kPicasaTempDirName),
125        false /* recursive */,
126        base::FileEnumerator::FILES);
127
128    for (base::FilePath src_path = file_enumerator.Next(); !src_path.empty();
129         src_path = file_enumerator.Next()) {
130      ASSERT_TRUE(
131          base::Move(src_path, database_path_.Append(src_path.BaseName())));
132    }
133  }
134
135  void SetInvalidateCallback(const base::Closure& callback) {
136    DCHECK(invalidate_callback_.is_null());
137    invalidate_callback_ = callback;
138  }
139
140  virtual void InvalidateData() OVERRIDE {
141    PicasaDataProvider::InvalidateData();
142
143    if (!invalidate_callback_.is_null()) {
144      invalidate_callback_.Run();
145      invalidate_callback_.Reset();
146    }
147  }
148
149  void SetAlbumMapsForTesting(const AlbumMap& album_map,
150                              const AlbumMap& folder_map) {
151    album_map_ = album_map;
152    folder_map_ = folder_map;
153  }
154
155 private:
156  virtual void OnTempDirWatchStarted(
157      scoped_ptr<base::FilePathWatcher> temp_dir_watcher) OVERRIDE {
158    PicasaDataProvider::OnTempDirWatchStarted(temp_dir_watcher.Pass());
159
160    file_watch_request_returned_ = true;
161    for (std::vector<ReadyCallback>::const_iterator it =
162             file_watch_started_callbacks_.begin();
163         it != file_watch_started_callbacks_.end();
164         ++it) {
165      it->Run(temp_dir_watcher_.get() != NULL);
166    }
167    file_watch_started_callbacks_.clear();
168  }
169
170  // Used for test that utilizes file watch
171  bool file_watch_request_returned_;
172  std::vector<ReadyCallback> file_watch_started_callbacks_;
173
174  base::Closure invalidate_callback_;
175};
176
177class PicasaDataProviderTest : public InProcessBrowserTest {
178 public:
179  PicasaDataProviderTest() {}
180  virtual ~PicasaDataProviderTest() {}
181
182 protected:
183  // Runs on the MediaTaskRunner and designed to be overridden by subclasses.
184  virtual void InitializeTestData() {}
185
186  void RunTest() {
187    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
188    base::RunLoop loop;
189    quit_closure_ = loop.QuitClosure();
190    MediaFileSystemBackend::MediaTaskRunner()->PostTask(
191        FROM_HERE,
192        base::Bind(&PicasaDataProviderTest::SetupFoldersAndDataProvider,
193                   base::Unretained(this)));
194    MediaFileSystemBackend::MediaTaskRunner()->PostTask(
195        FROM_HERE,
196        base::Bind(&PicasaDataProviderTest::InitializeTestData,
197                   base::Unretained(this)));
198    MediaFileSystemBackend::MediaTaskRunner()->PostTask(
199        FROM_HERE,
200        base::Bind(&PicasaDataProviderTest::StartTestOnMediaTaskRunner,
201                   base::Unretained(this)));
202    loop.Run();
203  }
204
205  virtual PicasaDataProvider::DataType RequestedDataType() const = 0;
206
207  // Start the test. The data provider is refreshed before calling StartTest
208  // and the result of the refresh is passed in.
209  virtual void VerifyRefreshResults(bool parse_success) {};
210
211  void TestDone() {
212    DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
213
214    // The data provider must be destructed on the MediaTaskRunner. This is done
215    // in a posted task rather than directly because TestDone is called by
216    // PicasaDataProvider. The callee should not destroy the caller.
217    MediaFileSystemBackend::MediaTaskRunner()->PostTask(
218        FROM_HERE,
219        base::Bind(&PicasaDataProviderTest::DestructDataProviderThenQuit,
220                   base::Unretained(this)));
221  }
222
223  const base::FilePath& test_folder_1_path() { return test_folder_1_.path(); }
224  const base::FilePath& test_folder_2_path() { return test_folder_2_.path(); }
225
226  TestPicasaDataProvider* data_provider() const {
227    return picasa_data_provider_.get();
228  }
229
230  const base::FilePath GetTempDirPath() const {
231    return picasa_root_dir_.path().AppendASCII(kPicasaTempDirName);
232  }
233
234  virtual base::FilePath GetColumnFileDestination() const {
235    return picasa_root_dir_.path().AppendASCII(kPicasaDatabaseDirName);
236  }
237
238 private:
239  void SetupFoldersAndDataProvider() {
240    DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
241    ASSERT_TRUE(test_folder_1_.CreateUniqueTempDir());
242    ASSERT_TRUE(test_folder_2_.CreateUniqueTempDir());
243    ASSERT_TRUE(picasa_root_dir_.CreateUniqueTempDir());
244    ASSERT_TRUE(base::CreateDirectory(
245        picasa_root_dir_.path().AppendASCII(kPicasaDatabaseDirName)));
246    ASSERT_TRUE(base::CreateDirectory(
247        picasa_root_dir_.path().AppendASCII(kPicasaTempDirName)));
248
249    picasa_data_provider_.reset(new TestPicasaDataProvider(
250        picasa_root_dir_.path().AppendASCII(kPicasaDatabaseDirName)));
251  }
252
253  virtual void StartTestOnMediaTaskRunner() {
254    DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
255
256    data_provider()->RefreshData(
257        RequestedDataType(),
258        base::Bind(&PicasaDataProviderTest::VerifyRefreshResults,
259                   base::Unretained(this)));
260  }
261
262  void DestructDataProviderThenQuit() {
263    DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
264    picasa_data_provider_.reset();
265    content::BrowserThread::PostTask(
266        content::BrowserThread::UI, FROM_HERE, quit_closure_);
267  }
268
269  base::ScopedTempDir test_folder_1_;
270  base::ScopedTempDir test_folder_2_;
271  base::ScopedTempDir picasa_root_dir_;
272
273  scoped_ptr<TestPicasaDataProvider> picasa_data_provider_;
274
275  base::Closure quit_closure_;
276
277  DISALLOW_COPY_AND_ASSIGN(PicasaDataProviderTest);
278};
279
280class PicasaDataProviderNoDatabaseGetListTest : public PicasaDataProviderTest {
281 protected:
282  virtual PicasaDataProvider::DataType RequestedDataType() const OVERRIDE {
283    return PicasaDataProvider::LIST_OF_ALBUMS_AND_FOLDERS_DATA;
284  }
285  virtual void VerifyRefreshResults(bool parse_success) OVERRIDE {
286    EXPECT_FALSE(parse_success);
287    TestDone();
288  }
289};
290
291IN_PROC_BROWSER_TEST_F(PicasaDataProviderNoDatabaseGetListTest,
292                       NoDatabaseGetList) {
293  RunTest();
294}
295
296class PicasaDataProviderNoDatabaseGetAlbumsImagesTest
297    : public PicasaDataProviderTest {
298 protected:
299  virtual PicasaDataProvider::DataType RequestedDataType() const OVERRIDE {
300    return PicasaDataProvider::ALBUMS_IMAGES_DATA;
301  }
302  virtual void VerifyRefreshResults(bool parse_success) OVERRIDE {
303    EXPECT_FALSE(parse_success);
304    TestDone();
305  }
306};
307
308IN_PROC_BROWSER_TEST_F(PicasaDataProviderNoDatabaseGetAlbumsImagesTest,
309                       NoDatabaseGetAlbumsImages) {
310  RunTest();
311}
312
313class PicasaDataProviderGetListTest : public PicasaDataProviderTest {
314 protected:
315  virtual void InitializeTestData() OVERRIDE {
316    WriteTestAlbumTable(GetColumnFileDestination(), test_folder_1_path(),
317                        test_folder_2_path());
318  }
319
320  virtual PicasaDataProvider::DataType RequestedDataType() const OVERRIDE {
321    return PicasaDataProvider::LIST_OF_ALBUMS_AND_FOLDERS_DATA;
322  }
323
324  virtual void VerifyRefreshResults(bool parse_success) OVERRIDE {
325    ASSERT_TRUE(parse_success);
326    VerifyTestAlbumTable(
327        data_provider(), test_folder_1_path(), test_folder_2_path());
328    TestDone();
329  }
330};
331
332IN_PROC_BROWSER_TEST_F(PicasaDataProviderGetListTest, GetListTest) {
333  RunTest();
334}
335
336class PicasaDataProviderGetAlbumsImagesTest : public PicasaDataProviderTest {
337 protected:
338  virtual void InitializeTestData() OVERRIDE {
339    WriteTestAlbumTable(GetColumnFileDestination(), test_folder_1_path(),
340                        test_folder_2_path());
341    WriteTestAlbumsImagesIndex(test_folder_1_path(), test_folder_2_path());
342  }
343
344  virtual PicasaDataProvider::DataType RequestedDataType() const OVERRIDE {
345    return PicasaDataProvider::ALBUMS_IMAGES_DATA;
346  }
347
348  virtual void VerifyRefreshResults(bool parse_success) OVERRIDE {
349    ASSERT_TRUE(parse_success);
350    VerifyTestAlbumTable(
351        data_provider(), test_folder_1_path(), test_folder_2_path());
352    VerifyTestAlbumsImagesIndex(
353        data_provider(), test_folder_1_path(), test_folder_2_path());
354    TestDone();
355  }
356};
357
358IN_PROC_BROWSER_TEST_F(PicasaDataProviderGetAlbumsImagesTest,
359                       GetAlbumsImagesTest) {
360  RunTest();
361}
362
363class PicasaDataProviderMultipleMixedCallbacksTest
364    : public PicasaDataProviderTest {
365 public:
366  PicasaDataProviderMultipleMixedCallbacksTest()
367      : list_callbacks_called_(0), albums_images_callbacks_called_(0) {}
368
369  virtual void InitializeTestData() OVERRIDE {
370    WriteTestAlbumTable(GetColumnFileDestination(), test_folder_1_path(),
371                        test_folder_2_path());
372    WriteTestAlbumsImagesIndex(test_folder_1_path(), test_folder_2_path());
373  }
374
375  virtual PicasaDataProvider::DataType RequestedDataType() const OVERRIDE {
376    return PicasaDataProvider::ALBUMS_IMAGES_DATA;
377  }
378
379 protected:
380  virtual void ListCallback(int expected_list_callbacks_called,
381                            bool parse_success) {
382    ASSERT_TRUE(parse_success);
383    ASSERT_EQ(expected_list_callbacks_called, ++list_callbacks_called_);
384    VerifyTestAlbumTable(
385        data_provider(), test_folder_1_path(), test_folder_2_path());
386    CheckTestDone();
387  }
388
389  virtual void AlbumsImagesCallback(int expected_albums_images_callbacks_called,
390                                    bool parse_success) {
391    ASSERT_TRUE(parse_success);
392    ASSERT_EQ(expected_albums_images_callbacks_called,
393              ++albums_images_callbacks_called_);
394    VerifyTestAlbumsImagesIndex(
395        data_provider(), test_folder_1_path(), test_folder_2_path());
396    CheckTestDone();
397  }
398
399 private:
400  void CheckTestDone() {
401    ASSERT_LE(list_callbacks_called_, 2);
402    ASSERT_LE(albums_images_callbacks_called_, 2);
403    if (list_callbacks_called_ == 2 && albums_images_callbacks_called_ == 2)
404      TestDone();
405  }
406
407  virtual void StartTestOnMediaTaskRunner() OVERRIDE {
408    DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
409
410    data_provider()->RefreshData(
411        PicasaDataProvider::LIST_OF_ALBUMS_AND_FOLDERS_DATA,
412        base::Bind(&PicasaDataProviderMultipleMixedCallbacksTest::ListCallback,
413                   base::Unretained(this),
414                   1));
415    data_provider()->RefreshData(
416        PicasaDataProvider::ALBUMS_IMAGES_DATA,
417        base::Bind(
418            &PicasaDataProviderMultipleMixedCallbacksTest::AlbumsImagesCallback,
419            base::Unretained(this),
420            1));
421    data_provider()->RefreshData(
422        PicasaDataProvider::LIST_OF_ALBUMS_AND_FOLDERS_DATA,
423        base::Bind(&PicasaDataProviderMultipleMixedCallbacksTest::ListCallback,
424                   base::Unretained(this),
425                   2));
426    data_provider()->RefreshData(
427        PicasaDataProvider::ALBUMS_IMAGES_DATA,
428        base::Bind(
429            &PicasaDataProviderMultipleMixedCallbacksTest::AlbumsImagesCallback,
430            base::Unretained(this),
431            2));
432  }
433
434  int list_callbacks_called_;
435  int albums_images_callbacks_called_;
436};
437
438IN_PROC_BROWSER_TEST_F(PicasaDataProviderMultipleMixedCallbacksTest,
439                       MultipleMixedCallbacks) {
440  RunTest();
441}
442
443class PicasaDataProviderFileWatcherInvalidateTest
444    : public PicasaDataProviderGetListTest {
445 protected:
446  virtual void ListCallback(bool parse_success) {
447    ASSERT_FALSE(parse_success);
448    data_provider()->EnsureFileWatchStartedForTesting(
449        base::Bind(&PicasaDataProviderFileWatcherInvalidateTest::
450                       OnPicasaTempDirWatchStarted,
451                   base::Unretained(this)));
452  }
453
454  void OnPicasaTempDirWatchStarted(bool file_watch_successful) {
455    ASSERT_TRUE(file_watch_successful);
456
457    // Validate the list after the file move triggers an invalidate.
458    data_provider()->SetInvalidateCallback(base::Bind(
459        &PicasaDataProvider::RefreshData,
460        base::Unretained(data_provider()),
461        RequestedDataType(),
462        base::Bind(
463            &PicasaDataProviderFileWatcherInvalidateTest::VerifyRefreshResults,
464            base::Unretained(this))));
465
466    data_provider()->MoveTempFilesToDatabase();
467  }
468
469  virtual base::FilePath GetColumnFileDestination() const OVERRIDE {
470    return GetTempDirPath();
471  }
472
473 private:
474  virtual void StartTestOnMediaTaskRunner() OVERRIDE {
475    DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
476
477    // Refresh before moving album table to database dir, guaranteeing failure.
478    data_provider()->RefreshData(
479        RequestedDataType(),
480        base::Bind(
481            &PicasaDataProviderFileWatcherInvalidateTest::ListCallback,
482            base::Unretained(this)));
483  }
484};
485
486IN_PROC_BROWSER_TEST_F(PicasaDataProviderFileWatcherInvalidateTest,
487                       FileWatcherInvalidateTest) {
488  RunTest();
489}
490
491class PicasaDataProviderInvalidateInflightTableReaderTest
492    : public PicasaDataProviderGetListTest {
493 protected:
494  // Don't write the database files until later.
495  virtual void InitializeTestData() OVERRIDE {}
496
497 private:
498  virtual void StartTestOnMediaTaskRunner() OVERRIDE {
499    DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
500
501    // Refresh before the database files have been written.
502    // This is guaranteed to fail to read the album table.
503    data_provider()->RefreshData(
504        RequestedDataType(),
505        base::Bind(&PicasaDataProviderInvalidateInflightTableReaderTest::
506                       VerifyRefreshResults,
507                   base::Unretained(this)));
508
509    // Now write the album table and invalidate the inflight table reader.
510    PicasaDataProviderGetListTest::InitializeTestData();
511    data_provider()->InvalidateData();
512
513    // VerifyRefreshResults callback should receive correct results now.
514  }
515};
516
517IN_PROC_BROWSER_TEST_F(PicasaDataProviderInvalidateInflightTableReaderTest,
518                       InvalidateInflightTableReaderTest) {
519  RunTest();
520}
521
522class PicasaDataProviderInvalidateInflightAlbumsIndexerTest
523    : public PicasaDataProviderGetAlbumsImagesTest {
524 protected:
525  virtual void ListCallback(bool parse_success) {
526    ASSERT_TRUE(parse_success);
527
528    // Empty the album maps to guarantee that the first utility process will
529    // fail to get the correct albums-images index.
530    data_provider()->SetAlbumMapsForTesting(AlbumMap(), AlbumMap());
531    data_provider()->RefreshData(
532        PicasaDataProvider::ALBUMS_IMAGES_DATA,
533        base::Bind(&PicasaDataProviderInvalidateInflightAlbumsIndexerTest::
534                       VerifyRefreshResults,
535                   base::Unretained(this)));
536
537    // Now invalidate all the data. The album maps will be re-read.
538    data_provider()->InvalidateData();
539
540    // VerifyRefreshResults callback should receive correct results now.
541  }
542
543 private:
544  virtual void StartTestOnMediaTaskRunner() OVERRIDE {
545    DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
546
547    data_provider()->RefreshData(
548        PicasaDataProvider::LIST_OF_ALBUMS_AND_FOLDERS_DATA,
549        base::Bind(&PicasaDataProviderInvalidateInflightAlbumsIndexerTest::
550                       ListCallback,
551                   base::Unretained(this)));
552  }
553};
554
555IN_PROC_BROWSER_TEST_F(PicasaDataProviderInvalidateInflightAlbumsIndexerTest,
556                       InvalidateInflightAlbumsIndexerTest) {
557  RunTest();
558}
559
560}  // namespace picasa
561