text: // Copyright 2013 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "chrome/browser/sync_file_system/drive_backend/conflict_resolver.h"
  
  #include "base/bind.h"
  #include "base/callback.h"
  #include "base/files/scoped_temp_dir.h"
  #include "base/run_loop.h"
  #include "chrome/browser/drive/drive_uploader.h"
  #include "chrome/browser/drive/fake_drive_service.h"
  #include "chrome/browser/sync_file_system/drive_backend/drive_backend_constants.h"
  #include "chrome/browser/sync_file_system/drive_backend/drive_backend_test_util.h"
  #include "chrome/browser/sync_file_system/drive_backend/list_changes_task.h"
  #include "chrome/browser/sync_file_system/drive_backend/metadata_database.h"
  #include "chrome/browser/sync_file_system/drive_backend/remote_to_local_syncer.h"
  #include "chrome/browser/sync_file_system/drive_backend/sync_engine_context.h"
  #include "chrome/browser/sync_file_system/drive_backend/sync_engine_initializer.h"
  #include "chrome/browser/sync_file_system/drive_backend_v1/fake_drive_service_helper.h"
  #include "chrome/browser/sync_file_system/drive_backend_v1/fake_drive_uploader.h"
  #include "chrome/browser/sync_file_system/fake_remote_change_processor.h"
  #include "chrome/browser/sync_file_system/sync_file_system_test_util.h"
  #include "chrome/browser/sync_file_system/syncable_file_system_util.h"
  #include "content/public/test/test_browser_thread_bundle.h"
  #include "google_apis/drive/gdata_errorcode.h"
  #include "testing/gtest/include/gtest/gtest.h"
  
  namespace sync_file_system {
  namespace drive_backend {
  
  class ConflictResolverTest : public testing::Test,
                               public SyncEngineContext {
   public:
    ConflictResolverTest()
        : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {}
    virtual ~ConflictResolverTest() {}
  
    virtual void SetUp() OVERRIDE {
      ASSERT_TRUE(database_dir_.CreateUniqueTempDir());
  
      fake_drive_service_.reset(new FakeDriveServiceWrapper);
      ASSERT_TRUE(fake_drive_service_->LoadAccountMetadataForWapi(
          "sync_file_system/account_metadata.json"));
      ASSERT_TRUE(fake_drive_service_->LoadResourceListForWapi(
          "gdata/empty_feed.json"));
  
      drive_uploader_.reset(new FakeDriveUploader(fake_drive_service_.get()));
      fake_drive_helper_.reset(new FakeDriveServiceHelper(
          fake_drive_service_.get(), drive_uploader_.get()));
      fake_remote_change_processor_.reset(new FakeRemoteChangeProcessor);
  
      RegisterSyncableFileSystem();
    }
  
    virtual void TearDown() OVERRIDE {
      RevokeSyncableFileSystem();
  
      fake_remote_change_processor_.reset();
      metadata_database_.reset();
      fake_drive_helper_.reset();
      drive_uploader_.reset();
      fake_drive_service_.reset();
      base::RunLoop().RunUntilIdle();
    }
  
    void InitializeMetadataDatabase() {
      SyncEngineInitializer initializer(this,
                                        base::MessageLoopProxy::current(),
                                        fake_drive_service_.get(),
                                        database_dir_.path());
      SyncStatusCode status = SYNC_STATUS_UNKNOWN;
      initializer.Run(CreateResultReceiver(&status));
      base::RunLoop().RunUntilIdle();
      EXPECT_EQ(SYNC_STATUS_OK, status);
      metadata_database_ = initializer.PassMetadataDatabase();
    }
  
    void RegisterApp(const std::string& app_id,
                     const std::string& app_root_folder_id) {
      SyncStatusCode status = SYNC_STATUS_FAILED;
      metadata_database_->RegisterApp(app_id, app_root_folder_id,
                                      CreateResultReceiver(&status));
      base::RunLoop().RunUntilIdle();
      EXPECT_EQ(SYNC_STATUS_OK, status);
    }
  
    virtual drive::DriveServiceInterface* GetDriveService() OVERRIDE {
      return fake_drive_service_.get();
    }
  
    virtual drive::DriveUploaderInterface* GetDriveUploader() OVERRIDE {
      return drive_uploader_.get();
    }
  
    virtual MetadataDatabase* GetMetadataDatabase() OVERRIDE {
      return metadata_database_.get();
    }
  
    virtual RemoteChangeProcessor* GetRemoteChangeProcessor() OVERRIDE {
      return fake_remote_change_processor_.get();
    }
  
    virtual base::SequencedTaskRunner* GetBlockingTaskRunner() OVERRIDE {
      return base::MessageLoopProxy::current().get();
    }
  
   protected:
    std::string CreateSyncRoot() {
      std::string sync_root_folder_id;
      EXPECT_EQ(google_apis::HTTP_CREATED,
                fake_drive_helper_->AddOrphanedFolder(
                    kSyncRootFolderTitle, &sync_root_folder_id));
      return sync_root_folder_id;
    }
  
    std::string CreateRemoteFolder(const std::string& parent_folder_id,
                                   const std::string& title) {
      std::string folder_id;
      EXPECT_EQ(google_apis::HTTP_CREATED,
                fake_drive_helper_->AddFolder(
                    parent_folder_id, title, &folder_id));
      return folder_id;
    }
  
    std::string CreateRemoteFile(const std::string& parent_folder_id,
                                 const std::string& title,
                                 const std::string& content) {
      std::string file_id;
      EXPECT_EQ(google_apis::HTTP_SUCCESS,
                fake_drive_helper_->AddFile(
                    parent_folder_id, title, content, &file_id));
      return file_id;
    }
  
    google_apis::GDataErrorCode AddFileToFolder(
        const std::string& parent_folder_id,
        const std::string& file_id) {
      google_apis::GDataErrorCode error = google_apis::GDATA_OTHER_ERROR;
      fake_drive_service_->AddResourceToDirectory(
          parent_folder_id, file_id,
          CreateResultReceiver(&error));
      base::RunLoop().RunUntilIdle();
      return error;
    }
  
    int CountParents(const std::string& file_id) {
      scoped_ptr<google_apis::ResourceEntry> entry;
      EXPECT_EQ(google_apis::HTTP_SUCCESS,
                fake_drive_helper_->GetResourceEntry(file_id, &entry));
      int count = 0;
      const ScopedVector<google_apis::Link>& links = entry->links();
      for (ScopedVector<google_apis::Link>::const_iterator itr = links.begin();
          itr != links.end(); ++itr) {
        if ((*itr)->type() == google_apis::Link::LINK_PARENT)
          ++count;
      }
      return count;
    }
  
    SyncStatusCode RunSyncer() {
      SyncStatusCode status = SYNC_STATUS_UNKNOWN;
      scoped_ptr<RemoteToLocalSyncer> syncer(new RemoteToLocalSyncer(this));
      syncer->Run(CreateResultReceiver(&status));
      base::RunLoop().RunUntilIdle();
      return status;
    }
  
    void RunSyncerUntilIdle() {
      SyncStatusCode status = SYNC_STATUS_UNKNOWN;
      while (status != SYNC_STATUS_NO_CHANGE_TO_SYNC)
        status = RunSyncer();
    }
  
    SyncStatusCode RunConflictResolver() {
      SyncStatusCode status = SYNC_STATUS_UNKNOWN;
      ConflictResolver resolver(this);
      resolver.Run(CreateResultReceiver(&status));
      base::RunLoop().RunUntilIdle();
      return status;
    }
  
    SyncStatusCode ListChanges() {
      ListChangesTask list_changes(this);
      SyncStatusCode status = SYNC_STATUS_UNKNOWN;
      list_changes.Run(CreateResultReceiver(&status));
      base::RunLoop().RunUntilIdle();
      return status;
    }
  
    ScopedVector<google_apis::ResourceEntry>
    GetResourceEntriesForParentAndTitle(const std::string& parent_folder_id,
                                        const std::string& title) {
      ScopedVector<google_apis::ResourceEntry> entries;
      EXPECT_EQ(google_apis::HTTP_SUCCESS,
                fake_drive_helper_->SearchByTitle(
                    parent_folder_id, title, &entries));
      return entries.Pass();
    }
  
    void VerifyConflictResolution(const std::string& parent_folder_id,
                                  const std::string& title,
                                  const std::string& primary_file_id,
                                  google_apis::DriveEntryKind kind) {
      ScopedVector<google_apis::ResourceEntry> entries;
      EXPECT_EQ(google_apis::HTTP_SUCCESS,
                fake_drive_helper_->SearchByTitle(
                    parent_folder_id, title, &entries));
      ASSERT_EQ(1u, entries.size());
      EXPECT_EQ(primary_file_id, entries[0]->resource_id());
      EXPECT_EQ(kind, entries[0]->kind());
    }
  
   private:
    content::TestBrowserThreadBundle thread_bundle_;
    base::ScopedTempDir database_dir_;
  
    scoped_ptr<FakeDriveServiceWrapper> fake_drive_service_;
    scoped_ptr<FakeDriveUploader> drive_uploader_;
    scoped_ptr<FakeDriveServiceHelper> fake_drive_helper_;
    scoped_ptr<MetadataDatabase> metadata_database_;
    scoped_ptr<FakeRemoteChangeProcessor> fake_remote_change_processor_;
  
    DISALLOW_COPY_AND_ASSIGN(ConflictResolverTest);
  };
  
  TEST_F(ConflictResolverTest, NoFileToBeResolved) {
    const GURL kOrigin("chrome-extension://example");
    const std::string sync_root = CreateSyncRoot();
    const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
    InitializeMetadataDatabase();
    RegisterApp(kOrigin.host(), app_root);
    RunSyncerUntilIdle();
  
    EXPECT_EQ(SYNC_STATUS_NO_CONFLICT, RunConflictResolver());
  }
  
  TEST_F(ConflictResolverTest, ResolveConflict_Files) {
    const GURL kOrigin("chrome-extension://example");
    const std::string sync_root = CreateSyncRoot();
    const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
    InitializeMetadataDatabase();
    RegisterApp(kOrigin.host(), app_root);
    RunSyncerUntilIdle();
  
    const std::string kTitle = "foo";
    const std::string primary = CreateRemoteFile(app_root, kTitle, "data1");
    CreateRemoteFile(app_root, kTitle, "data2");
    CreateRemoteFile(app_root, kTitle, "data3");
    CreateRemoteFile(app_root, kTitle, "data4");
    EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
    RunSyncerUntilIdle();
  
    ScopedVector<google_apis::ResourceEntry> entries =
        GetResourceEntriesForParentAndTitle(app_root, kTitle);
    ASSERT_EQ(4u, entries.size());
  
    // Only primary file should survive.
    EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver());
    VerifyConflictResolution(app_root, kTitle, primary,
                             google_apis::ENTRY_KIND_FILE);
  }
  
  TEST_F(ConflictResolverTest, ResolveConflict_Folders) {
    const GURL kOrigin("chrome-extension://example");
    const std::string sync_root = CreateSyncRoot();
    const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
    InitializeMetadataDatabase();
    RegisterApp(kOrigin.host(), app_root);
    RunSyncerUntilIdle();
  
    const std::string kTitle = "foo";
    const std::string primary = CreateRemoteFolder(app_root, kTitle);
    CreateRemoteFolder(app_root, kTitle);
    CreateRemoteFolder(app_root, kTitle);
    CreateRemoteFolder(app_root, kTitle);
    EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
    RunSyncerUntilIdle();
  
    ScopedVector<google_apis::ResourceEntry> entries =
        GetResourceEntriesForParentAndTitle(app_root, kTitle);
    ASSERT_EQ(4u, entries.size());
  
    // Only primary file should survive.
    EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver());
    VerifyConflictResolution(app_root, kTitle, primary,
                             google_apis::ENTRY_KIND_FOLDER);
  }
  
  TEST_F(ConflictResolverTest, ResolveConflict_FilesAndFolders) {
    const GURL kOrigin("chrome-extension://example");
    const std::string sync_root = CreateSyncRoot();
    const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
    InitializeMetadataDatabase();
    RegisterApp(kOrigin.host(), app_root);
    RunSyncerUntilIdle();
  
    const std::string kTitle = "foo";
    CreateRemoteFile(app_root, kTitle, "data");
    const std::string primary = CreateRemoteFolder(app_root, kTitle);
    CreateRemoteFile(app_root, kTitle, "data2");
    CreateRemoteFolder(app_root, kTitle);
    EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
    RunSyncerUntilIdle();
  
    ScopedVector<google_apis::ResourceEntry> entries =
        GetResourceEntriesForParentAndTitle(app_root, kTitle);
    ASSERT_EQ(4u, entries.size());
  
    // Only primary file should survive.
    EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver());
    VerifyConflictResolution(app_root, kTitle, primary,
                             google_apis::ENTRY_KIND_FOLDER);
  }
  
  TEST_F(ConflictResolverTest, ResolveMultiParents_File) {
    const GURL kOrigin("chrome-extension://example");
    const std::string sync_root = CreateSyncRoot();
    const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
    InitializeMetadataDatabase();
    RegisterApp(kOrigin.host(), app_root);
    RunSyncerUntilIdle();
  
    const std::string primary = CreateRemoteFolder(app_root, "primary");
    const std::string file = CreateRemoteFile(primary, "file", "data");
    ASSERT_EQ(google_apis::HTTP_SUCCESS,
              AddFileToFolder(CreateRemoteFolder(app_root, "nonprimary1"), file));
    ASSERT_EQ(google_apis::HTTP_SUCCESS,
              AddFileToFolder(CreateRemoteFolder(app_root, "nonprimary2"), file));
    ASSERT_EQ(google_apis::HTTP_SUCCESS,
              AddFileToFolder(CreateRemoteFolder(app_root, "nonprimary3"), file));
  
    EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
    RunSyncerUntilIdle();
  
    EXPECT_EQ(4, CountParents(file));
  
    EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver());
  
    EXPECT_EQ(1, CountParents(file));
  }
  
  TEST_F(ConflictResolverTest, ResolveMultiParents_Folder) {
    const GURL kOrigin("chrome-extension://example");
    const std::string sync_root = CreateSyncRoot();
    const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
    InitializeMetadataDatabase();
    RegisterApp(kOrigin.host(), app_root);
    RunSyncerUntilIdle();
  
    const std::string primary = CreateRemoteFolder(app_root, "primary");
    const std::string file = CreateRemoteFolder(primary, "folder");
    ASSERT_EQ(google_apis::HTTP_SUCCESS,
              AddFileToFolder(CreateRemoteFolder(app_root, "nonprimary1"), file));
    ASSERT_EQ(google_apis::HTTP_SUCCESS,
              AddFileToFolder(CreateRemoteFolder(app_root, "nonprimary2"), file));
    ASSERT_EQ(google_apis::HTTP_SUCCESS,
              AddFileToFolder(CreateRemoteFolder(app_root, "nonprimary3"), file));
  
    EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
    RunSyncerUntilIdle();
  
    EXPECT_EQ(4, CountParents(file));
  
    EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver());
  
    EXPECT_EQ(1, CountParents(file));
  }
  
  }  // namespace drive_backend
  }  // namespace sync_file_system
  