entry_update_performer_unittest.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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/drive/sync/entry_update_performer.h"
6
7#include "base/callback_helpers.h"
8#include "base/file_util.h"
9#include "base/md5.h"
10#include "base/task_runner_util.h"
11#include "chrome/browser/chromeos/drive/file_cache.h"
12#include "chrome/browser/chromeos/drive/file_system/operation_test_base.h"
13#include "chrome/browser/chromeos/drive/job_scheduler.h"
14#include "chrome/browser/chromeos/drive/resource_metadata.h"
15#include "chrome/browser/drive/drive_api_util.h"
16#include "chrome/browser/drive/fake_drive_service.h"
17#include "google_apis/drive/drive_api_parser.h"
18#include "google_apis/drive/gdata_wapi_parser.h"
19#include "google_apis/drive/test_util.h"
20#include "testing/gtest/include/gtest/gtest.h"
21
22namespace drive {
23namespace internal {
24
25class EntryUpdatePerformerTest : public file_system::OperationTestBase {
26 protected:
27  virtual void SetUp() OVERRIDE {
28    OperationTestBase::SetUp();
29    performer_.reset(new EntryUpdatePerformer(blocking_task_runner(),
30                                              observer(),
31                                              scheduler(),
32                                              metadata(),
33                                              cache(),
34                                              loader_controller()));
35  }
36
37  // Stores |content| to the cache and mark it as dirty.
38  FileError StoreAndMarkDirty(const std::string& local_id,
39                              const std::string& content) {
40    base::FilePath path;
41    if (!base::CreateTemporaryFileInDir(temp_dir(), &path) ||
42        !google_apis::test_util::WriteStringToFile(path, content))
43      return FILE_ERROR_FAILED;
44
45    // Store the file to cache.
46    FileError error = FILE_ERROR_FAILED;
47    base::PostTaskAndReplyWithResult(
48        blocking_task_runner(),
49        FROM_HERE,
50        base::Bind(&FileCache::Store,
51                   base::Unretained(cache()),
52                   local_id, std::string(), path,
53                   FileCache::FILE_OPERATION_COPY),
54        google_apis::test_util::CreateCopyResultCallback(&error));
55    test_util::RunBlockingPoolTask();
56    return error;
57  }
58
59  scoped_ptr<EntryUpdatePerformer> performer_;
60};
61
62TEST_F(EntryUpdatePerformerTest, UpdateEntry) {
63  base::FilePath src_path(
64      FILE_PATH_LITERAL("drive/root/Directory 1/SubDirectory File 1.txt"));
65  base::FilePath dest_path(
66      FILE_PATH_LITERAL("drive/root/Directory 1/Sub Directory Folder"));
67
68  ResourceEntry src_entry, dest_entry;
69  EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(src_path, &src_entry));
70  EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(dest_path, &dest_entry));
71
72  // Update local entry.
73  base::Time new_last_modified = base::Time::FromInternalValue(
74      src_entry.file_info().last_modified()) + base::TimeDelta::FromSeconds(1);
75  base::Time new_last_accessed = base::Time::FromInternalValue(
76      src_entry.file_info().last_accessed()) + base::TimeDelta::FromSeconds(2);
77
78  src_entry.set_parent_local_id(dest_entry.local_id());
79  src_entry.set_title("Moved" + src_entry.title());
80  src_entry.mutable_file_info()->set_last_modified(
81      new_last_modified.ToInternalValue());
82  src_entry.mutable_file_info()->set_last_accessed(
83      new_last_accessed.ToInternalValue());
84  src_entry.set_metadata_edit_state(ResourceEntry::DIRTY);
85
86  FileError error = FILE_ERROR_FAILED;
87  base::PostTaskAndReplyWithResult(
88      blocking_task_runner(),
89      FROM_HERE,
90      base::Bind(&ResourceMetadata::RefreshEntry,
91                 base::Unretained(metadata()),
92                 src_entry),
93      google_apis::test_util::CreateCopyResultCallback(&error));
94  test_util::RunBlockingPoolTask();
95  EXPECT_EQ(FILE_ERROR_OK, error);
96
97  // Perform server side update.
98  error = FILE_ERROR_FAILED;
99  performer_->UpdateEntry(
100      src_entry.local_id(),
101      ClientContext(USER_INITIATED),
102      google_apis::test_util::CreateCopyResultCallback(&error));
103  test_util::RunBlockingPoolTask();
104  EXPECT_EQ(FILE_ERROR_OK, error);
105
106  // Verify the file is updated on the server.
107  google_apis::GDataErrorCode gdata_error = google_apis::GDATA_OTHER_ERROR;
108  scoped_ptr<google_apis::ResourceEntry> gdata_entry;
109  fake_service()->GetResourceEntry(
110      src_entry.resource_id(),
111      google_apis::test_util::CreateCopyResultCallback(&gdata_error,
112                                                       &gdata_entry));
113  test_util::RunBlockingPoolTask();
114  EXPECT_EQ(google_apis::HTTP_SUCCESS, gdata_error);
115  ASSERT_TRUE(gdata_entry);
116
117  EXPECT_EQ(src_entry.title(), gdata_entry->title());
118  EXPECT_EQ(new_last_modified, gdata_entry->updated_time());
119  EXPECT_EQ(new_last_accessed, gdata_entry->last_viewed_time());
120
121  const google_apis::Link* parent_link =
122      gdata_entry->GetLinkByType(google_apis::Link::LINK_PARENT);
123  ASSERT_TRUE(parent_link);
124  EXPECT_EQ(dest_entry.resource_id(),
125            util::ExtractResourceIdFromUrl(parent_link->href()));
126}
127
128TEST_F(EntryUpdatePerformerTest, UpdateEntry_NotFound) {
129  const std::string id = "this ID should result in NOT_FOUND";
130  FileError error = FILE_ERROR_FAILED;
131  performer_->UpdateEntry(
132      id, ClientContext(USER_INITIATED),
133      google_apis::test_util::CreateCopyResultCallback(&error));
134  test_util::RunBlockingPoolTask();
135  EXPECT_EQ(FILE_ERROR_NOT_FOUND, error);
136}
137
138TEST_F(EntryUpdatePerformerTest, UpdateEntry_ContentUpdate) {
139  const base::FilePath kFilePath(FILE_PATH_LITERAL("drive/root/File 1.txt"));
140  const std::string kResourceId("file:2_file_resource_id");
141
142  const std::string local_id = GetLocalId(kFilePath);
143  EXPECT_FALSE(local_id.empty());
144
145  const std::string kTestFileContent = "I'm being uploaded! Yay!";
146  EXPECT_EQ(FILE_ERROR_OK, StoreAndMarkDirty(local_id, kTestFileContent));
147
148  int64 original_changestamp =
149      fake_service()->about_resource().largest_change_id();
150
151  // The callback will be called upon completion of UpdateEntry().
152  FileError error = FILE_ERROR_FAILED;
153  performer_->UpdateEntry(
154      local_id,
155      ClientContext(USER_INITIATED),
156      google_apis::test_util::CreateCopyResultCallback(&error));
157  test_util::RunBlockingPoolTask();
158  EXPECT_EQ(FILE_ERROR_OK, error);
159
160  // Check that the server has received an update.
161  EXPECT_LT(original_changestamp,
162            fake_service()->about_resource().largest_change_id());
163
164  // Check that the file size is updated to that of the updated content.
165  google_apis::GDataErrorCode gdata_error = google_apis::GDATA_OTHER_ERROR;
166  scoped_ptr<google_apis::ResourceEntry> server_entry;
167  fake_service()->GetResourceEntry(
168      kResourceId,
169      google_apis::test_util::CreateCopyResultCallback(&gdata_error,
170                                                       &server_entry));
171  test_util::RunBlockingPoolTask();
172  EXPECT_EQ(google_apis::HTTP_SUCCESS, gdata_error);
173  EXPECT_EQ(static_cast<int64>(kTestFileContent.size()),
174            server_entry->file_size());
175
176  // Make sure that the cache is no longer dirty.
177  bool success = false;
178  FileCacheEntry cache_entry;
179  base::PostTaskAndReplyWithResult(
180      blocking_task_runner(),
181      FROM_HERE,
182      base::Bind(&FileCache::GetCacheEntry,
183                 base::Unretained(cache()),
184                 local_id,
185                 &cache_entry),
186      google_apis::test_util::CreateCopyResultCallback(&success));
187  test_util::RunBlockingPoolTask();
188  ASSERT_TRUE(success);
189  EXPECT_FALSE(cache_entry.is_dirty());
190}
191
192TEST_F(EntryUpdatePerformerTest, UpdateEntry_ContentUpdateMd5Check) {
193  const base::FilePath kFilePath(FILE_PATH_LITERAL("drive/root/File 1.txt"));
194  const std::string kResourceId("file:2_file_resource_id");
195
196  const std::string local_id = GetLocalId(kFilePath);
197  EXPECT_FALSE(local_id.empty());
198
199  const std::string kTestFileContent = "I'm being uploaded! Yay!";
200  EXPECT_EQ(FILE_ERROR_OK, StoreAndMarkDirty(local_id, kTestFileContent));
201
202  int64 original_changestamp =
203      fake_service()->about_resource().largest_change_id();
204
205  // The callback will be called upon completion of UpdateEntry().
206  FileError error = FILE_ERROR_FAILED;
207  performer_->UpdateEntry(
208      local_id,
209      ClientContext(USER_INITIATED),
210      google_apis::test_util::CreateCopyResultCallback(&error));
211  test_util::RunBlockingPoolTask();
212  EXPECT_EQ(FILE_ERROR_OK, error);
213
214  // Check that the server has received an update.
215  EXPECT_LT(original_changestamp,
216            fake_service()->about_resource().largest_change_id());
217
218  // Check that the file size is updated to that of the updated content.
219  google_apis::GDataErrorCode gdata_error = google_apis::GDATA_OTHER_ERROR;
220  scoped_ptr<google_apis::ResourceEntry> server_entry;
221  fake_service()->GetResourceEntry(
222      kResourceId,
223      google_apis::test_util::CreateCopyResultCallback(&gdata_error,
224                                                       &server_entry));
225  test_util::RunBlockingPoolTask();
226  EXPECT_EQ(google_apis::HTTP_SUCCESS, gdata_error);
227  EXPECT_EQ(static_cast<int64>(kTestFileContent.size()),
228            server_entry->file_size());
229
230  // Make sure that the cache is no longer dirty.
231  bool success = false;
232  FileCacheEntry cache_entry;
233  base::PostTaskAndReplyWithResult(
234      blocking_task_runner(),
235      FROM_HERE,
236      base::Bind(&FileCache::GetCacheEntry,
237                 base::Unretained(cache()),
238                 local_id,
239                 &cache_entry),
240      google_apis::test_util::CreateCopyResultCallback(&success));
241  test_util::RunBlockingPoolTask();
242  ASSERT_TRUE(success);
243  EXPECT_FALSE(cache_entry.is_dirty());
244
245  // Again mark the cache file dirty.
246  scoped_ptr<base::ScopedClosureRunner> file_closer;
247  error = FILE_ERROR_FAILED;
248  base::PostTaskAndReplyWithResult(
249      blocking_task_runner(),
250      FROM_HERE,
251      base::Bind(&FileCache::OpenForWrite,
252                 base::Unretained(cache()),
253                 local_id,
254                 &file_closer),
255      google_apis::test_util::CreateCopyResultCallback(&error));
256  test_util::RunBlockingPoolTask();
257  EXPECT_EQ(FILE_ERROR_OK, error);
258  file_closer.reset();
259
260  // And call UpdateEntry again.
261  // In this case, although the file is marked as dirty, but the content
262  // hasn't been changed. Thus, the actual uploading should be skipped.
263  original_changestamp = fake_service()->about_resource().largest_change_id();
264  error = FILE_ERROR_FAILED;
265  performer_->UpdateEntry(
266      local_id,
267      ClientContext(USER_INITIATED),
268      google_apis::test_util::CreateCopyResultCallback(&error));
269  test_util::RunBlockingPoolTask();
270  EXPECT_EQ(FILE_ERROR_OK, error);
271
272  EXPECT_EQ(original_changestamp,
273            fake_service()->about_resource().largest_change_id());
274
275  // Make sure that the cache is no longer dirty.
276  success = false;
277  base::PostTaskAndReplyWithResult(
278      blocking_task_runner(),
279      FROM_HERE,
280      base::Bind(&FileCache::GetCacheEntry,
281                 base::Unretained(cache()),
282                 local_id,
283                 &cache_entry),
284      google_apis::test_util::CreateCopyResultCallback(&success));
285  test_util::RunBlockingPoolTask();
286  ASSERT_TRUE(success);
287  EXPECT_FALSE(cache_entry.is_dirty());
288}
289
290TEST_F(EntryUpdatePerformerTest, UpdateEntry_OpenedForWrite) {
291  const base::FilePath kFilePath(FILE_PATH_LITERAL("drive/root/File 1.txt"));
292  const std::string kResourceId("file:2_file_resource_id");
293
294  const std::string local_id = GetLocalId(kFilePath);
295  EXPECT_FALSE(local_id.empty());
296
297  const std::string kTestFileContent = "I'm being uploaded! Yay!";
298  EXPECT_EQ(FILE_ERROR_OK, StoreAndMarkDirty(local_id, kTestFileContent));
299
300  // Emulate a situation where someone is writing to the file.
301  scoped_ptr<base::ScopedClosureRunner> file_closer;
302  FileError error = FILE_ERROR_FAILED;
303  base::PostTaskAndReplyWithResult(
304      blocking_task_runner(),
305      FROM_HERE,
306      base::Bind(&FileCache::OpenForWrite,
307                 base::Unretained(cache()),
308                 local_id,
309                 &file_closer),
310      google_apis::test_util::CreateCopyResultCallback(&error));
311  test_util::RunBlockingPoolTask();
312  EXPECT_EQ(FILE_ERROR_OK, error);
313
314  // Update. This should not clear the dirty bit.
315  error = FILE_ERROR_FAILED;
316  performer_->UpdateEntry(
317      local_id,
318      ClientContext(USER_INITIATED),
319      google_apis::test_util::CreateCopyResultCallback(&error));
320  test_util::RunBlockingPoolTask();
321  EXPECT_EQ(FILE_ERROR_OK, error);
322
323  // Make sure that the cache is still dirty.
324  bool success = false;
325  FileCacheEntry cache_entry;
326  base::PostTaskAndReplyWithResult(
327      blocking_task_runner(),
328      FROM_HERE,
329      base::Bind(&FileCache::GetCacheEntry,
330                 base::Unretained(cache()),
331                 local_id,
332                 &cache_entry),
333      google_apis::test_util::CreateCopyResultCallback(&success));
334  test_util::RunBlockingPoolTask();
335  EXPECT_TRUE(success);
336  EXPECT_TRUE(cache_entry.is_dirty());
337
338  // Close the file.
339  file_closer.reset();
340
341  // Update. This should clear the dirty bit.
342  error = FILE_ERROR_FAILED;
343  performer_->UpdateEntry(
344      local_id,
345      ClientContext(USER_INITIATED),
346      google_apis::test_util::CreateCopyResultCallback(&error));
347  test_util::RunBlockingPoolTask();
348  EXPECT_EQ(FILE_ERROR_OK, error);
349
350  // Make sure that the cache is no longer dirty.
351  base::PostTaskAndReplyWithResult(
352      blocking_task_runner(),
353      FROM_HERE,
354      base::Bind(&FileCache::GetCacheEntry,
355                 base::Unretained(cache()),
356                 local_id,
357                 &cache_entry),
358      google_apis::test_util::CreateCopyResultCallback(&success));
359  test_util::RunBlockingPoolTask();
360  EXPECT_TRUE(success);
361  EXPECT_FALSE(cache_entry.is_dirty());
362}
363
364TEST_F(EntryUpdatePerformerTest, UpdateEntry_UploadNewFile) {
365  // Create a new file locally.
366  const base::FilePath kFilePath(FILE_PATH_LITERAL("drive/root/New File.txt"));
367
368  ResourceEntry parent;
369  EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(kFilePath.DirName(), &parent));
370
371  ResourceEntry entry;
372  entry.set_parent_local_id(parent.local_id());
373  entry.set_title(kFilePath.BaseName().AsUTF8Unsafe());
374  entry.mutable_file_specific_info()->set_content_mime_type("text/plain");
375  entry.set_metadata_edit_state(ResourceEntry::DIRTY);
376
377  FileError error = FILE_ERROR_FAILED;
378  std::string local_id;
379  base::PostTaskAndReplyWithResult(
380      blocking_task_runner(),
381      FROM_HERE,
382      base::Bind(&internal::ResourceMetadata::AddEntry,
383                 base::Unretained(metadata()),
384                 entry,
385                 &local_id),
386      google_apis::test_util::CreateCopyResultCallback(&error));
387  test_util::RunBlockingPoolTask();
388  EXPECT_EQ(FILE_ERROR_OK, error);
389
390  // Update. This should result in creating a new file on the server.
391  error = FILE_ERROR_FAILED;
392  performer_->UpdateEntry(
393      local_id,
394      ClientContext(USER_INITIATED),
395      google_apis::test_util::CreateCopyResultCallback(&error));
396  test_util::RunBlockingPoolTask();
397  EXPECT_EQ(FILE_ERROR_OK, error);
398
399  // The entry got a resource ID.
400  EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(kFilePath, &entry));
401  EXPECT_FALSE(entry.resource_id().empty());
402  EXPECT_EQ(ResourceEntry::CLEAN, entry.metadata_edit_state());
403
404  // Make sure that the cache is no longer dirty.
405  bool success = false;
406  FileCacheEntry cache_entry;
407  base::PostTaskAndReplyWithResult(
408      blocking_task_runner(),
409      FROM_HERE,
410      base::Bind(&FileCache::GetCacheEntry,
411                 base::Unretained(cache()),
412                 local_id,
413                 &cache_entry),
414      google_apis::test_util::CreateCopyResultCallback(&success));
415  test_util::RunBlockingPoolTask();
416  EXPECT_TRUE(success);
417  EXPECT_FALSE(cache_entry.is_dirty());
418
419  // Make sure that we really created a file.
420  google_apis::GDataErrorCode status = google_apis::GDATA_OTHER_ERROR;
421  scoped_ptr<google_apis::ResourceEntry> resource_entry;
422  fake_service()->GetResourceEntry(
423      entry.resource_id(),
424      google_apis::test_util::CreateCopyResultCallback(&status,
425                                                       &resource_entry));
426  test_util::RunBlockingPoolTask();
427  EXPECT_EQ(google_apis::HTTP_SUCCESS, status);
428  ASSERT_TRUE(resource_entry);
429  EXPECT_FALSE(resource_entry->is_folder());
430}
431
432TEST_F(EntryUpdatePerformerTest, UpdateEntry_CreateDirectory) {
433  // Create a new directory locally.
434  const base::FilePath kPath(FILE_PATH_LITERAL("drive/root/New Directory"));
435
436  ResourceEntry parent;
437  EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(kPath.DirName(), &parent));
438
439  ResourceEntry entry;
440  entry.set_parent_local_id(parent.local_id());
441  entry.set_title(kPath.BaseName().AsUTF8Unsafe());
442  entry.mutable_file_info()->set_is_directory(true);
443  entry.set_metadata_edit_state(ResourceEntry::DIRTY);
444
445  FileError error = FILE_ERROR_FAILED;
446  std::string local_id;
447  base::PostTaskAndReplyWithResult(
448      blocking_task_runner(),
449      FROM_HERE,
450      base::Bind(&internal::ResourceMetadata::AddEntry,
451                 base::Unretained(metadata()),
452                 entry,
453                 &local_id),
454      google_apis::test_util::CreateCopyResultCallback(&error));
455  test_util::RunBlockingPoolTask();
456  EXPECT_EQ(FILE_ERROR_OK, error);
457
458  // Update. This should result in creating a new directory on the server.
459  error = FILE_ERROR_FAILED;
460  performer_->UpdateEntry(
461      local_id,
462      ClientContext(USER_INITIATED),
463      google_apis::test_util::CreateCopyResultCallback(&error));
464  test_util::RunBlockingPoolTask();
465  EXPECT_EQ(FILE_ERROR_OK, error);
466
467  // The entry got a resource ID.
468  EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(kPath, &entry));
469  EXPECT_FALSE(entry.resource_id().empty());
470  EXPECT_EQ(ResourceEntry::CLEAN, entry.metadata_edit_state());
471
472  // Make sure that we really created a directory.
473  google_apis::GDataErrorCode status = google_apis::GDATA_OTHER_ERROR;
474  scoped_ptr<google_apis::ResourceEntry> resource_entry;
475  fake_service()->GetResourceEntry(
476      entry.resource_id(),
477      google_apis::test_util::CreateCopyResultCallback(&status,
478                                                       &resource_entry));
479  test_util::RunBlockingPoolTask();
480  EXPECT_EQ(google_apis::HTTP_SUCCESS, status);
481  ASSERT_TRUE(resource_entry);
482  EXPECT_TRUE(resource_entry->is_folder());
483}
484
485}  // namespace internal
486}  // namespace drive
487