text: // Copyright (c) 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_file_sync_service.h"
  
  #include <algorithm>
  
  #include "base/format_macros.h"
  #include "base/message_loop.h"
  #include "base/rand_util.h"
  #include "base/strings/stringprintf.h"
  #include "base/strings/utf_string_conversions.h"
  #include "chrome/browser/sync_file_system/drive/fake_api_util.h"
  #include "chrome/browser/sync_file_system/drive/metadata_db_migration_util.h"
  #include "chrome/browser/sync_file_system/drive_file_sync_util.h"
  #include "chrome/browser/sync_file_system/drive_metadata_store.h"
  #include "chrome/browser/sync_file_system/fake_remote_change_processor.h"
  #include "chrome/browser/sync_file_system/sync_file_system.pb.h"
  #include "chrome/test/base/testing_profile.h"
  #include "content/public/test/test_browser_thread.h"
  #include "testing/gtest/include/gtest/gtest.h"
  #include "webkit/browser/fileapi/syncable/syncable_file_system_util.h"
  
  namespace sync_file_system {
  
  namespace {
  
  const char kAppId[] = "app-id";
  const char kAppOrigin[] = "chrome-extension://app-id";
  
  std::string GetSyncRootResourceId() {
    const std::string kSyncRootResourceId("folder:sync_root_resource_id");
    return IsDriveAPIDisabled() ? kSyncRootResourceId
                                : drive::RemoveWapiIdPrefix(kSyncRootResourceId);
  }
  
  std::string GetParentResourceId() {
    const std::string kParentResourceId("folder:parent_resource_id");
    return IsDriveAPIDisabled() ? kParentResourceId
                                : drive::RemoveWapiIdPrefix(kParentResourceId);
  }
  
  void DidInitialize(bool* done, SyncStatusCode status, bool created) {
    EXPECT_EQ(SYNC_STATUS_OK, status);
    *done = true;
  }
  
  void DidProcessRemoteChange(bool* done,
                              SyncStatusCode* status_out,
                              SyncStatusCode status,
                              const fileapi::FileSystemURL& url) {
    *status_out = status;
    *done = true;
  }
  
  }  // namespace
  
  class DriveFileSyncServiceSyncTest : public testing::Test {
   public:
    DriveFileSyncServiceSyncTest()
        : ui_thread_(content::BrowserThread::UI, &message_loop_),
          file_thread_(content::BrowserThread::FILE, &message_loop_),
          fake_api_util_(NULL),
          metadata_store_(NULL),
          resource_count_(0) {}
  
    virtual ~DriveFileSyncServiceSyncTest() {
    }
  
    virtual void SetUp() OVERRIDE {
      SetEnableSyncFSDirectoryOperation(true);
      RegisterSyncableFileSystem();
    }
  
    virtual void TearDown() OVERRIDE {
      SetDisableDriveAPI(false);
      RevokeSyncableFileSystem();
      SetEnableSyncFSDirectoryOperation(false);
    }
  
   protected:
    struct SyncEvent {
      std::string description;
      base::Closure callback;
  
      SyncEvent(const std::string& description,
                base::Closure callback)
          : description(description),
            callback(callback) {
      }
    };
  
    SyncEvent CreateRemoteFileAddOrUpdateEvent(const std::string& title) {
      return SyncEvent(
          "SyncEvent: Add or Update remote file [" + title + "]",
          base::Bind(&DriveFileSyncServiceSyncTest::AddOrUpdateResource,
                     base::Unretained(this), title, SYNC_FILE_TYPE_FILE));
    }
  
    SyncEvent CreateRemoteDirectoryAddEvent(const std::string& title) {
      return SyncEvent(
          "SyncEvent: Add remote directory [" + title + "]",
          base::Bind(&DriveFileSyncServiceSyncTest::AddOrUpdateResource,
                     base::Unretained(this), title, SYNC_FILE_TYPE_DIRECTORY));
    }
  
    SyncEvent CreateRemoteFileDeleteEvent(const std::string& title) {
      return SyncEvent(
          "SyncEvent: Delete remote file [" + title + "]",
          base::Bind(&DriveFileSyncServiceSyncTest::DeleteResource,
                     base::Unretained(this), title));
    }
  
    SyncEvent CreateRelaunchEvent() {
      return SyncEvent(
          "SyncEvent: Relaunch service",
          base::Bind(&DriveFileSyncServiceSyncTest::RelaunchService,
                     base::Unretained(this)));
    }
  
    SyncEvent CreateFetchEvent() {
      return SyncEvent(
          "SyncEvent: Fetch remote changes",
          base::Bind(&DriveFileSyncServiceSyncTest::FetchRemoteChange,
                     base::Unretained(this)));
    }
  
    SyncEvent CreateProcessRemoteChangeEvent() {
      return SyncEvent(
          "SyncEvent: Process remote change",
          base::Bind(
              base::IgnoreResult(
                  &DriveFileSyncServiceSyncTest::ProcessRemoteChange),
              base::Unretained(this)));
    }
  
    template <size_t array_size>
    std::vector<SyncEvent> CreateTestCase(const SyncEvent (&events)[array_size]) {
      return std::vector<SyncEvent>(events, events + array_size);
    }
  
    void ShuffleTestCase(std::vector<SyncEvent>* events) {
      std::random_shuffle(events->begin(), events->end(), base::RandGenerator);
    }
  
    void RunTest(const std::vector<SyncEvent>& events) {
      base::ScopedTempDir scoped_base_dir_;
      ASSERT_TRUE(scoped_base_dir_.CreateUniqueTempDir());
      base_dir_ = scoped_base_dir_.path();
  
      SetUpForTestCase();
  
      typedef std::vector<SyncEvent>::const_iterator iterator;
      std::ostringstream out;
      out << '\n';
      for (iterator itr = events.begin(); itr != events.end(); ++itr)
        out << itr->description << '\n';
      SCOPED_TRACE(out.str());
  
      for (iterator itr = events.begin(); itr != events.end(); ++itr)
        itr->callback.Run();
  
      FetchRemoteChange();
      while (ProcessRemoteChange()) {}
  
      VerifyResult();
      TearDownForTestCase();
  
      base_dir_ = base::FilePath();
    }
  
    void AddFileTest_Body();
    void UpdateFileTest_Body();
    void DeleteFileTest_Body();
    void RelaunchTest_Body();
    void SquashedFileAddTest_Body();
    void RelaunchTestWithSquashedDeletion_Body();
    void CreateDirectoryTest_Body();
    void DeleteDirectoryTest_Body();
  
   private:
    void SetUpForTestCase() {
      fake_api_util_ = new drive::FakeAPIUtil;
      fake_remote_processor_.reset(new FakeRemoteChangeProcessor);
  
      metadata_store_ = new DriveMetadataStore(
          base_dir_, base::MessageLoopProxy::current().get());
  
      bool done = false;
      metadata_store_->Initialize(base::Bind(&DidInitialize, &done));
      message_loop_.RunUntilIdle();
      EXPECT_TRUE(done);
  
      metadata_store_->SetSyncRootDirectory(GetSyncRootResourceId());
      metadata_store_->AddIncrementalSyncOrigin(GURL(kAppOrigin),
                                                GetParentResourceId());
  
      sync_service_ = DriveFileSyncService::CreateForTesting(
          &profile_,
          base_dir_,
          scoped_ptr<drive::APIUtilInterface>(fake_api_util_),
          scoped_ptr<DriveMetadataStore>(metadata_store_)).Pass();
      sync_service_->SetRemoteChangeProcessor(fake_remote_processor_.get());
    }
  
    void TearDownForTestCase() {
      metadata_store_ = NULL;
      fake_api_util_ = NULL;
      sync_service_.reset();
      fake_remote_processor_.reset();
      message_loop_.RunUntilIdle();
    }
  
    void AddOrUpdateResource(const std::string& title,
                             SyncFileType type) {
      typedef ResourceIdByTitle::iterator iterator;
      std::pair<iterator, bool> inserted =
          resources_.insert(std::make_pair(title, std::string()));
      if (inserted.second) {
        switch (type) {
          case SYNC_FILE_TYPE_UNKNOWN:
            NOTREACHED();
            break;
          case SYNC_FILE_TYPE_FILE:
            inserted.first->second = IsDriveAPIDisabled()
                ? base::StringPrintf("file:%" PRId64, ++resource_count_)
                : base::StringPrintf("%" PRId64, ++resource_count_);
            break;
          case SYNC_FILE_TYPE_DIRECTORY:
            inserted.first->second = IsDriveAPIDisabled()
                ? base::StringPrintf("folder:%" PRId64, ++resource_count_)
                : base::StringPrintf("%" PRId64, ++resource_count_);
            break;
        }
      }
      std::string resource_id = inserted.first->second;
      std::string md5_checksum;
      if (type == SYNC_FILE_TYPE_FILE)
        md5_checksum = base::StringPrintf("%" PRIx64, base::RandUint64());
  
      fake_api_util_->PushRemoteChange(GetParentResourceId(),
                                       kAppId,
                                       title,
                                       resource_id,
                                       md5_checksum,
                                       type,
                                       false /* deleted */);
      message_loop_.RunUntilIdle();
    }
  
    void DeleteResource(const std::string& title) {
      ResourceIdByTitle::iterator found = resources_.find(title);
      if (found == resources_.end())
        return;
      std::string resource_id = found->second;
      resources_.erase(found);
      fake_api_util_->PushRemoteChange(GetParentResourceId(),
                                       kAppId,
                                       title,
                                       resource_id,
                                       std::string(),
                                       SYNC_FILE_TYPE_UNKNOWN,
                                       true /* deleted */);
      message_loop_.RunUntilIdle();
    }
  
    void RelaunchService() {
      metadata_store_ = NULL;
      scoped_ptr<drive::APIUtilInterface> api_util =
          DriveFileSyncService::DestroyAndPassAPIUtilForTesting(
              sync_service_.Pass());
      message_loop_.RunUntilIdle();
  
      metadata_store_ = new DriveMetadataStore(
          base_dir_, base::MessageLoopProxy::current().get());
  
      bool done = false;
      metadata_store_->Initialize(base::Bind(&DidInitialize, &done));
      message_loop_.RunUntilIdle();
      EXPECT_TRUE(done);
  
      sync_service_ = DriveFileSyncService::CreateForTesting(
          &profile_,
          base_dir_,
          api_util.Pass(),
          scoped_ptr<DriveMetadataStore>(metadata_store_)).Pass();
      sync_service_->SetRemoteChangeProcessor(fake_remote_processor_.get());
    }
  
    void FetchRemoteChange() {
      sync_service_->may_have_unfetched_changes_ = true;
      sync_service_->MaybeStartFetchChanges();
      message_loop_.RunUntilIdle();
    }
  
    bool ProcessRemoteChange() {
      bool done = false;
      SyncStatusCode status = SYNC_STATUS_UNKNOWN;
      sync_service_->ProcessRemoteChange(
          base::Bind(&DidProcessRemoteChange, &done, &status));
      message_loop_.RunUntilIdle();
      EXPECT_TRUE(done);
      return status != SYNC_STATUS_NO_CHANGE_TO_SYNC;
    }
  
  
    void VerifyResult() {
      typedef drive::FakeAPIUtil::RemoteResourceByResourceId RemoteResourceMap;
      typedef drive::FakeAPIUtil::RemoteResource RemoteResource;
      typedef FakeRemoteChangeProcessor::URLToFileChangesMap
          AppliedRemoteChangeMap;
  
      const RemoteResourceMap& remote_resources =
          fake_api_util_->remote_resources();
      const AppliedRemoteChangeMap applied_changes =
          fake_remote_processor_->GetAppliedRemoteChanges();
  
      std::set<std::string> local_resources;
      for (AppliedRemoteChangeMap::const_iterator itr = applied_changes.begin();
           itr != applied_changes.end(); ++itr) {
        const fileapi::FileSystemURL& url = itr->first;
        const FileChange& applied_change = itr->second.back();
  
        DriveMetadata metadata;
        SyncStatusCode status = metadata_store_->ReadEntry(itr->first, &metadata);
        if (applied_change.IsDelete()) {
          EXPECT_EQ(SYNC_DATABASE_ERROR_NOT_FOUND, status);
          continue;
        }
  
        EXPECT_EQ(SYNC_STATUS_OK, status);
        EXPECT_FALSE(metadata.resource_id().empty());
        EXPECT_FALSE(metadata.conflicted());
        EXPECT_FALSE(metadata.to_be_fetched());
        EXPECT_TRUE(metadata.type() == DriveMetadata::RESOURCE_TYPE_FOLDER ||
                    !metadata.md5_checksum().empty());
  
        RemoteResourceMap::const_iterator found =
            remote_resources.find(metadata.resource_id());
        ASSERT_TRUE(found != remote_resources.end());
        const RemoteResource& remote_resource = found->second;
  
        EXPECT_EQ(base::FilePath::FromUTF8Unsafe(remote_resource.title),
                  url.path());
        EXPECT_EQ(remote_resource.md5_checksum, metadata.md5_checksum());
        EXPECT_FALSE(remote_resource.deleted);
  
        EXPECT_TRUE(local_resources.insert(metadata.resource_id()).second);
      }
  
      for (RemoteResourceMap::const_iterator itr = remote_resources.begin();
           itr != remote_resources.end(); ++itr) {
        if (!itr->second.deleted)
          EXPECT_TRUE(ContainsKey(local_resources, itr->first));
        else
          EXPECT_FALSE(ContainsKey(local_resources, itr->first));
      }
    }
  
    base::MessageLoop message_loop_;
    content::TestBrowserThread ui_thread_;
    content::TestBrowserThread file_thread_;
  
    TestingProfile profile_;
    base::FilePath base_dir_;
  
    drive::FakeAPIUtil* fake_api_util_;  // Owned by |sync_service_|.
    scoped_ptr<FakeRemoteChangeProcessor> fake_remote_processor_;
    DriveMetadataStore* metadata_store_;  // Owned by |sync_service_|.
  
    scoped_ptr<DriveFileSyncService> sync_service_;
  
    typedef std::map<std::string, std::string> ResourceIdByTitle;
    ResourceIdByTitle resources_;
    int64 resource_count_;
  
    DISALLOW_COPY_AND_ASSIGN(DriveFileSyncServiceSyncTest);
  };
  
  void DriveFileSyncServiceSyncTest::AddFileTest_Body() {
    std::string kFile1 = "file title 1";
    SyncEvent sync_event[] = {
      CreateRemoteFileAddOrUpdateEvent(kFile1),
    };
  
    RunTest(CreateTestCase(sync_event));
  }
  
  void DriveFileSyncServiceSyncTest::UpdateFileTest_Body() {
    std::string kFile1 = "file title 1";
    SyncEvent sync_event[] = {
      CreateRemoteFileAddOrUpdateEvent(kFile1),
      CreateRemoteFileAddOrUpdateEvent(kFile1),
    };
  
    RunTest(CreateTestCase(sync_event));
  }
  
  void DriveFileSyncServiceSyncTest::DeleteFileTest_Body() {
    std::string kFile1 = "file title 1";
    SyncEvent sync_event[] = {
      CreateRemoteFileAddOrUpdateEvent(kFile1),
      CreateFetchEvent(),
      CreateProcessRemoteChangeEvent(),
      CreateRemoteFileDeleteEvent(kFile1),
    };
  
    RunTest(CreateTestCase(sync_event));
  }
  
  void DriveFileSyncServiceSyncTest::RelaunchTest_Body() {
    SyncEvent sync_event[] = {
      CreateRelaunchEvent(),
    };
  
    RunTest(CreateTestCase(sync_event));
  }
  
  void DriveFileSyncServiceSyncTest::SquashedFileAddTest_Body() {
    std::string kFile1 = "file title 1";
    SyncEvent sync_event[] = {
      CreateRemoteFileAddOrUpdateEvent(kFile1),
      CreateFetchEvent(),
      CreateRemoteFileDeleteEvent(kFile1),
    };
    RunTest(CreateTestCase(sync_event));
  }
  
  void DriveFileSyncServiceSyncTest::RelaunchTestWithSquashedDeletion_Body() {
    std::string kFile1 = "file title 1";
    std::string kFile2 = "file title 2";
    SyncEvent sync_event[] = {
      CreateRemoteFileAddOrUpdateEvent(kFile1),
      CreateFetchEvent(),
      CreateProcessRemoteChangeEvent(),
  
      CreateRemoteFileDeleteEvent(kFile1),
      CreateRemoteFileAddOrUpdateEvent(kFile2),
      CreateRemoteFileAddOrUpdateEvent(kFile1),
  
      CreateFetchEvent(),
      CreateProcessRemoteChangeEvent(),
      CreateRelaunchEvent(),
    };
    RunTest(CreateTestCase(sync_event));
  }
  
  void DriveFileSyncServiceSyncTest::CreateDirectoryTest_Body() {
    std::string kDir = "dir title";
    SyncEvent sync_event[] = {
      CreateRemoteDirectoryAddEvent(kDir),
    };
    RunTest(CreateTestCase(sync_event));
  }
  
  void DriveFileSyncServiceSyncTest::DeleteDirectoryTest_Body() {
    std::string kDir = "dir title";
    SyncEvent sync_event[] = {
      CreateRemoteDirectoryAddEvent(kDir),
      CreateRemoteFileDeleteEvent(kDir),
    };
    RunTest(CreateTestCase(sync_event));
  }
  
  TEST_F(DriveFileSyncServiceSyncTest, AddFileTest) {
    ASSERT_FALSE(IsDriveAPIDisabled());
    AddFileTest_Body();
  }
  
  TEST_F(DriveFileSyncServiceSyncTest, AddFileTest_WAPI) {
    SetDisableDriveAPI(true);
    AddFileTest_Body();
  }
  
  TEST_F(DriveFileSyncServiceSyncTest, UpdateFileTest) {
    ASSERT_FALSE(IsDriveAPIDisabled());
    UpdateFileTest_Body();
  }
  
  TEST_F(DriveFileSyncServiceSyncTest, UpdateFileTest_WAPI) {
    SetDisableDriveAPI(true);
    UpdateFileTest_Body();
  }
  
  TEST_F(DriveFileSyncServiceSyncTest, DeleteFileTest) {
    ASSERT_FALSE(IsDriveAPIDisabled());
    DeleteFileTest_Body();
  }
  
  TEST_F(DriveFileSyncServiceSyncTest, DeleteFileTest_WAPI) {
    SetDisableDriveAPI(true);
    DeleteFileTest_Body();
  }
  
  TEST_F(DriveFileSyncServiceSyncTest, RelaunchTest) {
    ASSERT_FALSE(IsDriveAPIDisabled());
    RelaunchTest_Body();
  }
  
  TEST_F(DriveFileSyncServiceSyncTest, RelaunchTest_WAPI) {
    SetDisableDriveAPI(true);
    RelaunchTest_Body();
  }
  
  TEST_F(DriveFileSyncServiceSyncTest, SquashedFileAddTest) {
    ASSERT_FALSE(IsDriveAPIDisabled());
    SquashedFileAddTest_Body();
  }
  
  TEST_F(DriveFileSyncServiceSyncTest, SquashedFileAddTest_WAPI) {
    SetDisableDriveAPI(true);
    SquashedFileAddTest_Body();
  }
  
  TEST_F(DriveFileSyncServiceSyncTest, RelaunchTestWithSquashedDeletion) {
    ASSERT_FALSE(IsDriveAPIDisabled());
    RelaunchTestWithSquashedDeletion_Body();
  }
  
  TEST_F(DriveFileSyncServiceSyncTest, RelaunchTestWithSquashedDeletion_WAPI) {
    SetDisableDriveAPI(true);
    RelaunchTestWithSquashedDeletion_Body();
  }
  
  TEST_F(DriveFileSyncServiceSyncTest, CreateDirectoryTest) {
    ASSERT_FALSE(IsDriveAPIDisabled());
    CreateDirectoryTest_Body();
  }
  
  TEST_F(DriveFileSyncServiceSyncTest, CreateDirectoryTest_WAPI) {
    SetDisableDriveAPI(true);
    CreateDirectoryTest_Body();
  }
  
  TEST_F(DriveFileSyncServiceSyncTest, DeleteDirectoryTest) {
    ASSERT_FALSE(IsDriveAPIDisabled());
    DeleteDirectoryTest_Body();
  }
  
  TEST_F(DriveFileSyncServiceSyncTest, DeleteDirectoryTest_WAPI) {
    SetDisableDriveAPI(true);
    DeleteDirectoryTest_Body();
  }
  
  }  // namespace sync_file_system
  