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/sync_file_system/drive_backend/remote_to_local_syncer.h"
6 
7 #include <map>
8 #include <utility>
9 
10 #include "base/bind.h"
11 #include "base/callback.h"
12 #include "base/files/scoped_temp_dir.h"
13 #include "base/macros.h"
14 #include "base/run_loop.h"
15 #include "base/threading/thread_task_runner_handle.h"
16 #include "chrome/browser/sync_file_system/drive_backend/drive_backend_constants.h"
17 #include "chrome/browser/sync_file_system/drive_backend/drive_backend_test_util.h"
18 #include "chrome/browser/sync_file_system/drive_backend/fake_drive_service_helper.h"
19 #include "chrome/browser/sync_file_system/drive_backend/list_changes_task.h"
20 #include "chrome/browser/sync_file_system/drive_backend/metadata_database.h"
21 #include "chrome/browser/sync_file_system/drive_backend/sync_engine_context.h"
22 #include "chrome/browser/sync_file_system/drive_backend/sync_engine_initializer.h"
23 #include "chrome/browser/sync_file_system/drive_backend/sync_task_manager.h"
24 #include "chrome/browser/sync_file_system/drive_backend/sync_task_token.h"
25 #include "chrome/browser/sync_file_system/fake_remote_change_processor.h"
26 #include "chrome/browser/sync_file_system/sync_file_system_test_util.h"
27 #include "chrome/browser/sync_file_system/syncable_file_system_util.h"
28 #include "components/drive/drive_uploader.h"
29 #include "components/drive/service/fake_drive_service.h"
30 #include "content/public/test/browser_task_environment.h"
31 #include "google_apis/drive/drive_api_error_codes.h"
32 #include "mojo/public/cpp/bindings/pending_remote.h"
33 #include "testing/gtest/include/gtest/gtest.h"
34 #include "third_party/leveldatabase/leveldb_chrome.h"
35 
36 namespace sync_file_system {
37 namespace drive_backend {
38 
39 namespace {
40 
URL(const GURL & origin,const std::string & path)41 storage::FileSystemURL URL(const GURL& origin, const std::string& path) {
42   return CreateSyncableFileSystemURL(
43       origin, base::FilePath::FromUTF8Unsafe(path));
44 }
45 
46 }  // namespace
47 
48 class RemoteToLocalSyncerTest : public testing::Test {
49  public:
50   typedef FakeRemoteChangeProcessor::URLToFileChangesMap URLToFileChangesMap;
51 
RemoteToLocalSyncerTest()52   RemoteToLocalSyncerTest()
53       : task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP) {}
~RemoteToLocalSyncerTest()54   ~RemoteToLocalSyncerTest() override {}
55 
SetUp()56   void SetUp() override {
57     ASSERT_TRUE(database_dir_.CreateUniqueTempDir());
58     in_memory_env_ = leveldb_chrome::NewMemEnv("RemoteToLocalSyncerTest");
59 
60     std::unique_ptr<drive::FakeDriveService> fake_drive_service(
61         new drive::FakeDriveService);
62 
63     std::unique_ptr<drive::DriveUploaderInterface> drive_uploader(
64         new drive::DriveUploader(fake_drive_service.get(),
65                                  base::ThreadTaskRunnerHandle::Get().get(),
66                                  mojo::NullRemote()));
67     fake_drive_helper_.reset(
68         new FakeDriveServiceHelper(fake_drive_service.get(),
69                                    drive_uploader.get(),
70                                    kSyncRootFolderTitle));
71     remote_change_processor_.reset(new FakeRemoteChangeProcessor);
72 
73     context_.reset(new SyncEngineContext(
74         std::move(fake_drive_service), std::move(drive_uploader),
75         nullptr /* task_logger */, base::ThreadTaskRunnerHandle::Get(),
76         base::ThreadTaskRunnerHandle::Get()));
77     context_->SetRemoteChangeProcessor(remote_change_processor_.get());
78 
79     RegisterSyncableFileSystem();
80 
81     sync_task_manager_.reset(new SyncTaskManager(
82         base::WeakPtr<SyncTaskManager::Client>(), 10 /* max_parallel_task */,
83         base::ThreadTaskRunnerHandle::Get()));
84     sync_task_manager_->Initialize(SYNC_STATUS_OK);
85   }
86 
TearDown()87   void TearDown() override {
88     sync_task_manager_.reset();
89     RevokeSyncableFileSystem();
90     fake_drive_helper_.reset();
91     context_.reset();
92     base::RunLoop().RunUntilIdle();
93   }
94 
InitializeMetadataDatabase()95   void InitializeMetadataDatabase() {
96     SyncEngineInitializer* initializer = new SyncEngineInitializer(
97         context_.get(), database_dir_.GetPath(), in_memory_env_.get());
98     SyncStatusCode status = SYNC_STATUS_UNKNOWN;
99     sync_task_manager_->ScheduleSyncTask(
100         FROM_HERE, std::unique_ptr<SyncTask>(initializer),
101         SyncTaskManager::PRIORITY_MED,
102         base::Bind(&RemoteToLocalSyncerTest::DidInitializeMetadataDatabase,
103                    base::Unretained(this), initializer, &status));
104 
105     base::RunLoop().RunUntilIdle();
106     EXPECT_EQ(SYNC_STATUS_OK, status);
107   }
108 
DidInitializeMetadataDatabase(SyncEngineInitializer * initializer,SyncStatusCode * status_out,SyncStatusCode status)109   void DidInitializeMetadataDatabase(SyncEngineInitializer* initializer,
110                                      SyncStatusCode* status_out,
111                                      SyncStatusCode status) {
112     *status_out = status;
113     context_->SetMetadataDatabase(initializer->PassMetadataDatabase());
114   }
115 
116 
RegisterApp(const std::string & app_id,const std::string & app_root_folder_id)117   void RegisterApp(const std::string& app_id,
118                    const std::string& app_root_folder_id) {
119     SyncStatusCode status = context_->GetMetadataDatabase()->RegisterApp(
120         app_id, app_root_folder_id);
121     EXPECT_EQ(SYNC_STATUS_OK, status);
122   }
123 
GetMetadataDatabase()124   MetadataDatabase* GetMetadataDatabase() {
125     return context_->GetMetadataDatabase();
126   }
127 
128  protected:
CreateSyncRoot()129   std::string CreateSyncRoot() {
130     std::string sync_root_folder_id;
131     EXPECT_EQ(google_apis::HTTP_CREATED,
132               fake_drive_helper_->AddOrphanedFolder(
133                   kSyncRootFolderTitle, &sync_root_folder_id));
134     return sync_root_folder_id;
135   }
136 
CreateRemoteFolder(const std::string & parent_folder_id,const std::string & title)137   std::string CreateRemoteFolder(const std::string& parent_folder_id,
138                                  const std::string& title) {
139     std::string folder_id;
140     EXPECT_EQ(google_apis::HTTP_CREATED,
141               fake_drive_helper_->AddFolder(
142                   parent_folder_id, title, &folder_id));
143     return folder_id;
144   }
145 
CreateRemoteFile(const std::string & parent_folder_id,const std::string & title,const std::string & content)146   std::string CreateRemoteFile(const std::string& parent_folder_id,
147                                const std::string& title,
148                                const std::string& content) {
149     std::string file_id;
150     EXPECT_EQ(google_apis::HTTP_SUCCESS,
151               fake_drive_helper_->AddFile(
152                   parent_folder_id, title, content, &file_id));
153     return file_id;
154   }
155 
DeleteRemoteFile(const std::string & file_id)156   void DeleteRemoteFile(const std::string& file_id) {
157     EXPECT_EQ(google_apis::HTTP_NO_CONTENT,
158               fake_drive_helper_->DeleteResource(file_id));
159   }
160 
CreateLocalFolder(const storage::FileSystemURL & url)161   void CreateLocalFolder(const storage::FileSystemURL& url) {
162     remote_change_processor_->UpdateLocalFileMetadata(
163         url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
164                         SYNC_FILE_TYPE_DIRECTORY));
165   }
166 
CreateLocalFile(const storage::FileSystemURL & url)167   void CreateLocalFile(const storage::FileSystemURL& url) {
168     remote_change_processor_->UpdateLocalFileMetadata(
169         url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
170                         SYNC_FILE_TYPE_FILE));
171   }
172 
RunSyncer()173   SyncStatusCode RunSyncer() {
174     SyncStatusCode status = SYNC_STATUS_UNKNOWN;
175     std::unique_ptr<RemoteToLocalSyncer> syncer(
176         new RemoteToLocalSyncer(context_.get()));
177     syncer->RunPreflight(SyncTaskToken::CreateForTesting(
178         CreateResultReceiver(&status)));
179     base::RunLoop().RunUntilIdle();
180     return status;
181   }
182 
RunSyncerUntilIdle()183   SyncStatusCode RunSyncerUntilIdle() {
184     const int kRetryLimit = 100;
185     SyncStatusCode status = SYNC_STATUS_UNKNOWN;
186     int count = 0;
187     do {
188       if (count++ > kRetryLimit)
189         return status;
190       status = RunSyncer();
191     } while (status == SYNC_STATUS_OK ||
192              status == SYNC_STATUS_RETRY);
193     return status;
194   }
195 
RunSyncerAndPromoteUntilIdle()196   SyncStatusCode RunSyncerAndPromoteUntilIdle() {
197     const int kRetryLimit = 100;
198     SyncStatusCode status = SYNC_STATUS_UNKNOWN;
199     MetadataDatabase* metadata_database = context_->GetMetadataDatabase();
200     int count = 0;
201     do {
202       if (count++ > kRetryLimit)
203         return status;
204       status = RunSyncer();
205     } while (status == SYNC_STATUS_OK ||
206              status == SYNC_STATUS_RETRY ||
207              metadata_database->PromoteDemotedTrackers());
208     return status;
209   }
210 
ListChanges()211   SyncStatusCode ListChanges() {
212     SyncStatusCode status = SYNC_STATUS_UNKNOWN;
213     sync_task_manager_->ScheduleSyncTask(
214         FROM_HERE,
215         std::unique_ptr<SyncTask>(new ListChangesTask(context_.get())),
216         SyncTaskManager::PRIORITY_MED, CreateResultReceiver(&status));
217     base::RunLoop().RunUntilIdle();
218     return status;
219   }
220 
AppendExpectedChange(const storage::FileSystemURL & url,FileChange::ChangeType change_type,SyncFileType file_type)221   void AppendExpectedChange(const storage::FileSystemURL& url,
222                             FileChange::ChangeType change_type,
223                             SyncFileType file_type) {
224     expected_changes_[url].push_back(FileChange(change_type, file_type));
225   }
226 
VerifyConsistency()227   void VerifyConsistency() {
228     remote_change_processor_->VerifyConsistency(expected_changes_);
229   }
230 
231  private:
232   content::BrowserTaskEnvironment task_environment_;
233   base::ScopedTempDir database_dir_;
234   std::unique_ptr<leveldb::Env> in_memory_env_;
235 
236   std::unique_ptr<SyncEngineContext> context_;
237   std::unique_ptr<FakeDriveServiceHelper> fake_drive_helper_;
238   std::unique_ptr<FakeRemoteChangeProcessor> remote_change_processor_;
239 
240   std::unique_ptr<SyncTaskManager> sync_task_manager_;
241 
242   URLToFileChangesMap expected_changes_;
243 
244   DISALLOW_COPY_AND_ASSIGN(RemoteToLocalSyncerTest);
245 };
246 
TEST_F(RemoteToLocalSyncerTest,AddNewFile)247 TEST_F(RemoteToLocalSyncerTest, AddNewFile) {
248   const GURL kOrigin("chrome-extension://example");
249   const std::string sync_root = CreateSyncRoot();
250   const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
251   InitializeMetadataDatabase();
252   RegisterApp(kOrigin.host(), app_root);
253 
254   const std::string folder1 = CreateRemoteFolder(app_root, "folder1");
255   const std::string file1 = CreateRemoteFile(app_root, "file1", "data1");
256   const std::string folder2 = CreateRemoteFolder(folder1, "folder2");
257   const std::string file2 = CreateRemoteFile(folder1, "file2", "data2");
258 
259   EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerAndPromoteUntilIdle());
260 
261   // Create expected changes.
262   // TODO(nhiroki): Clean up creating URL part.
263   AppendExpectedChange(URL(kOrigin, "folder1"),
264                        FileChange::FILE_CHANGE_ADD_OR_UPDATE,
265                        SYNC_FILE_TYPE_DIRECTORY);
266   AppendExpectedChange(URL(kOrigin, "file1"),
267                        FileChange::FILE_CHANGE_ADD_OR_UPDATE,
268                        SYNC_FILE_TYPE_FILE);
269   AppendExpectedChange(URL(kOrigin, "folder1/folder2"),
270                        FileChange::FILE_CHANGE_ADD_OR_UPDATE,
271                        SYNC_FILE_TYPE_DIRECTORY);
272   AppendExpectedChange(URL(kOrigin, "folder1/file2"),
273                        FileChange::FILE_CHANGE_ADD_OR_UPDATE,
274                        SYNC_FILE_TYPE_FILE);
275 
276   VerifyConsistency();
277 
278   EXPECT_FALSE(GetMetadataDatabase()->HasDirtyTracker());
279 }
280 
TEST_F(RemoteToLocalSyncerTest,DeleteFile)281 TEST_F(RemoteToLocalSyncerTest, DeleteFile) {
282   const GURL kOrigin("chrome-extension://example");
283   const std::string sync_root = CreateSyncRoot();
284   const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
285   InitializeMetadataDatabase();
286   RegisterApp(kOrigin.host(), app_root);
287 
288   const std::string folder = CreateRemoteFolder(app_root, "folder");
289   const std::string file = CreateRemoteFile(app_root, "file", "data");
290 
291   AppendExpectedChange(URL(kOrigin, "folder"),
292                        FileChange::FILE_CHANGE_ADD_OR_UPDATE,
293                        SYNC_FILE_TYPE_DIRECTORY);
294   AppendExpectedChange(URL(kOrigin, "file"),
295                        FileChange::FILE_CHANGE_ADD_OR_UPDATE,
296                        SYNC_FILE_TYPE_FILE);
297 
298   EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerAndPromoteUntilIdle());
299   VerifyConsistency();
300 
301   DeleteRemoteFile(folder);
302   DeleteRemoteFile(file);
303 
304   AppendExpectedChange(URL(kOrigin, "folder"),
305                        FileChange::FILE_CHANGE_DELETE,
306                        SYNC_FILE_TYPE_UNKNOWN);
307   AppendExpectedChange(URL(kOrigin, "file"),
308                        FileChange::FILE_CHANGE_DELETE,
309                        SYNC_FILE_TYPE_UNKNOWN);
310 
311   EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
312   EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle());
313   VerifyConsistency();
314 
315   EXPECT_FALSE(GetMetadataDatabase()->HasDirtyTracker());
316 }
317 
TEST_F(RemoteToLocalSyncerTest,DeleteNestedFiles)318 TEST_F(RemoteToLocalSyncerTest, DeleteNestedFiles) {
319   const GURL kOrigin("chrome-extension://example");
320   const std::string sync_root = CreateSyncRoot();
321   const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
322   InitializeMetadataDatabase();
323   RegisterApp(kOrigin.host(), app_root);
324 
325   const std::string folder1 = CreateRemoteFolder(app_root, "folder1");
326   const std::string file1 = CreateRemoteFile(app_root, "file1", "data1");
327   const std::string folder2 = CreateRemoteFolder(folder1, "folder2");
328   const std::string file2 = CreateRemoteFile(folder1, "file2", "data2");
329 
330   AppendExpectedChange(URL(kOrigin, "folder1"),
331                        FileChange::FILE_CHANGE_ADD_OR_UPDATE,
332                        SYNC_FILE_TYPE_DIRECTORY);
333   AppendExpectedChange(URL(kOrigin, "file1"),
334                        FileChange::FILE_CHANGE_ADD_OR_UPDATE,
335                        SYNC_FILE_TYPE_FILE);
336   AppendExpectedChange(URL(kOrigin, "folder1/folder2"),
337                        FileChange::FILE_CHANGE_ADD_OR_UPDATE,
338                        SYNC_FILE_TYPE_DIRECTORY);
339   AppendExpectedChange(URL(kOrigin, "folder1/file2"),
340                        FileChange::FILE_CHANGE_ADD_OR_UPDATE,
341                        SYNC_FILE_TYPE_FILE);
342 
343   EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerAndPromoteUntilIdle());
344   VerifyConsistency();
345 
346   DeleteRemoteFile(folder1);
347 
348   AppendExpectedChange(URL(kOrigin, "folder1"),
349                        FileChange::FILE_CHANGE_DELETE,
350                        SYNC_FILE_TYPE_UNKNOWN);
351   // Changes for descendant files ("folder2" and "file2") should be ignored.
352 
353   EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
354   EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle());
355   VerifyConsistency();
356 
357   EXPECT_FALSE(GetMetadataDatabase()->HasDirtyTracker());
358 }
359 
TEST_F(RemoteToLocalSyncerTest,Conflict_CreateFileOnFolder)360 TEST_F(RemoteToLocalSyncerTest, Conflict_CreateFileOnFolder) {
361   const GURL kOrigin("chrome-extension://example");
362   const std::string sync_root = CreateSyncRoot();
363   const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
364   InitializeMetadataDatabase();
365   RegisterApp(kOrigin.host(), app_root);
366 
367   CreateLocalFolder(URL(kOrigin, "folder"));
368   CreateRemoteFile(app_root, "folder", "data");
369 
370   // Folder-File conflict happens. File creation should be ignored.
371 
372   EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
373   EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle());
374   VerifyConsistency();
375 
376   // Tracker for the remote file should has low priority.
377   EXPECT_FALSE(GetMetadataDatabase()->GetDirtyTracker(nullptr));
378   EXPECT_TRUE(GetMetadataDatabase()->HasDemotedDirtyTracker());
379 }
380 
TEST_F(RemoteToLocalSyncerTest,Conflict_CreateFolderOnFile)381 TEST_F(RemoteToLocalSyncerTest, Conflict_CreateFolderOnFile) {
382   const GURL kOrigin("chrome-extension://example");
383   const std::string sync_root = CreateSyncRoot();
384   const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
385   InitializeMetadataDatabase();
386   RegisterApp(kOrigin.host(), app_root);
387 
388   EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle());
389   VerifyConsistency();
390 
391   CreateLocalFile(URL(kOrigin, "file"));
392   CreateRemoteFolder(app_root, "file");
393 
394   // File-Folder conflict happens. Folder should override the existing file.
395   AppendExpectedChange(URL(kOrigin, "file"),
396                        FileChange::FILE_CHANGE_ADD_OR_UPDATE,
397                        SYNC_FILE_TYPE_DIRECTORY);
398 
399   EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
400   EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle());
401   VerifyConsistency();
402 
403   EXPECT_FALSE(GetMetadataDatabase()->HasDirtyTracker());
404 }
405 
TEST_F(RemoteToLocalSyncerTest,Conflict_CreateFolderOnFolder)406 TEST_F(RemoteToLocalSyncerTest, Conflict_CreateFolderOnFolder) {
407   const GURL kOrigin("chrome-extension://example");
408   const std::string sync_root = CreateSyncRoot();
409   const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
410   InitializeMetadataDatabase();
411   RegisterApp(kOrigin.host(), app_root);
412 
413   CreateLocalFolder(URL(kOrigin, "folder"));
414   CreateRemoteFolder(app_root, "folder");
415 
416   // Folder-Folder conflict happens. Folder creation should be ignored.
417 
418   EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
419   EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle());
420   VerifyConsistency();
421 
422   EXPECT_FALSE(GetMetadataDatabase()->HasDirtyTracker());
423 }
424 
TEST_F(RemoteToLocalSyncerTest,Conflict_CreateFileOnFile)425 TEST_F(RemoteToLocalSyncerTest, Conflict_CreateFileOnFile) {
426   const GURL kOrigin("chrome-extension://example");
427   const std::string sync_root = CreateSyncRoot();
428   const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
429   InitializeMetadataDatabase();
430   RegisterApp(kOrigin.host(), app_root);
431 
432   CreateLocalFile(URL(kOrigin, "file"));
433   CreateRemoteFile(app_root, "file", "data");
434 
435   // File-File conflict happens. File creation should be ignored.
436 
437   EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
438   EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle());
439   VerifyConsistency();
440 
441   // Tracker for the remote file should be lowered.
442   EXPECT_FALSE(GetMetadataDatabase()->GetDirtyTracker(nullptr));
443   EXPECT_TRUE(GetMetadataDatabase()->HasDemotedDirtyTracker());
444 }
445 
TEST_F(RemoteToLocalSyncerTest,Conflict_CreateNestedFolderOnFile)446 TEST_F(RemoteToLocalSyncerTest, Conflict_CreateNestedFolderOnFile) {
447   const GURL kOrigin("chrome-extension://example");
448   const std::string sync_root = CreateSyncRoot();
449   const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
450   InitializeMetadataDatabase();
451   RegisterApp(kOrigin.host(), app_root);
452 
453   EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle());
454   VerifyConsistency();
455 
456   const std::string folder = CreateRemoteFolder(app_root, "folder");
457   CreateLocalFile(URL(kOrigin, "/folder"));
458   CreateRemoteFile(folder, "file", "data");
459 
460   // File-Folder conflict happens. Folder should override the existing file.
461   AppendExpectedChange(URL(kOrigin, "/folder"),
462                        FileChange::FILE_CHANGE_ADD_OR_UPDATE,
463                        SYNC_FILE_TYPE_DIRECTORY);
464 
465   EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
466   EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle());
467   VerifyConsistency();
468 }
469 
TEST_F(RemoteToLocalSyncerTest,AppRootDeletion)470 TEST_F(RemoteToLocalSyncerTest, AppRootDeletion) {
471   const GURL kOrigin("chrome-extension://example");
472   const std::string sync_root = CreateSyncRoot();
473   const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
474   InitializeMetadataDatabase();
475   RegisterApp(kOrigin.host(), app_root);
476 
477   EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle());
478   VerifyConsistency();
479 
480   DeleteRemoteFile(app_root);
481 
482   AppendExpectedChange(URL(kOrigin, "/"),
483                        FileChange::FILE_CHANGE_DELETE,
484                        SYNC_FILE_TYPE_UNKNOWN);
485 
486   EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
487   EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle());
488   VerifyConsistency();
489 
490   // SyncEngine will re-register the app and resurrect the app root later.
491 }
492 
493 }  // namespace drive_backend
494 }  // namespace sync_file_system
495