text: // Copyright 2017 The Fuchsia 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 "src/ledger/bin/cloud_sync/impl/user_sync_impl.h"
  
  #include <utility>
  
  #include <lib/backoff/backoff.h>
  #include <lib/backoff/testing/test_backoff.h>
  #include <lib/gtest/test_loop_fixture.h>
  
  #include "peridot/lib/scoped_tmpfs/scoped_tmpfs.h"
  #include "src/ledger/bin/cloud_sync/impl/testing/test_cloud_provider.h"
  #include "src/ledger/bin/encryption/fake/fake_encryption_service.h"
  #include "src/ledger/bin/testing/test_with_environment.h"
  #include "src/lib/files/file.h"
  #include "src/lib/fxl/macros.h"
  
  namespace cloud_sync {
  
  namespace {
  
  class TestSyncStateWatcher : public SyncStateWatcher {
   public:
    TestSyncStateWatcher() {}
    ~TestSyncStateWatcher() override{};
  
    void Notify(SyncStateContainer /*sync_state*/) override {}
  };
  
  class UserSyncImplTest : public ledger::TestWithEnvironment {
   public:
    UserSyncImplTest()
        : cloud_provider_(cloud_provider_ptr_.NewRequest()), encryption_service_(dispatcher()) {
      UserConfig user_config;
      user_config.user_directory = ledger::DetachedPath(tmpfs_.root_fd());
      user_config.cloud_provider = std::move(cloud_provider_ptr_);
  
      auto backoff = std::make_unique<backoff::TestBackoff>();
      backoff->SetOnGetNext([this] {
        // Make RunLoopUntilIdle() return once a backoff is requested, to avoid an
        // infinite loop.
        QuitLoop();
      });
  
      user_sync_ =
          std::make_unique<UserSyncImpl>(&environment_, std::move(user_config), std::move(backoff),
                                         [this] { on_version_mismatch_calls_++; });
      user_sync_->SetSyncWatcher(&sync_state_watcher_);
    }
    ~UserSyncImplTest() override {}
  
   protected:
    bool SetFingerprintFile(std::string content) {
      ledger::DetachedPath fingerprint_path = user_sync_->GetFingerprintPath();
      return files::WriteFileAt(fingerprint_path.root_fd(), fingerprint_path.path(), content.data(),
                                content.size());
    }
  
    scoped_tmpfs::ScopedTmpFS tmpfs_;
    cloud_provider::CloudProviderPtr cloud_provider_ptr_;
    TestCloudProvider cloud_provider_;
    std::unique_ptr<UserSyncImpl> user_sync_;
    encryption::FakeEncryptionService encryption_service_;
    TestSyncStateWatcher sync_state_watcher_;
  
    int on_version_mismatch_calls_ = 0;
  
   private:
    FXL_DISALLOW_COPY_AND_ASSIGN(UserSyncImplTest);
  };
  
  // Verifies that the mismatch callback is called if the fingerprint appears to
  // be erased from the cloud.
  TEST_F(UserSyncImplTest, CloudCheckErased) {
    ASSERT_TRUE(SetFingerprintFile("some-value"));
    cloud_provider_.device_set.status_to_return = cloud_provider::Status::NOT_FOUND;
    EXPECT_EQ(on_version_mismatch_calls_, 0);
    user_sync_->Start();
    RunLoopUntilIdle();
    EXPECT_EQ(on_version_mismatch_calls_, 1);
  }
  
  // Verifies that if the version checker reports that cloud is compatible, upload
  // is enabled in LedgerSync.
  TEST_F(UserSyncImplTest, CloudCheckOk) {
    ASSERT_TRUE(SetFingerprintFile("some-value"));
    cloud_provider_.device_set.status_to_return = cloud_provider::Status::OK;
    EXPECT_EQ(on_version_mismatch_calls_, 0);
    user_sync_->Start();
  
    auto ledger_a = user_sync_->CreateLedgerSync("app-id", &encryption_service_);
    auto ledger_a_ptr = static_cast<LedgerSyncImpl*>(ledger_a.get());
    EXPECT_FALSE(ledger_a_ptr->IsUploadEnabled());
    RunLoopUntilIdle();
    EXPECT_TRUE(ledger_a_ptr->IsUploadEnabled());
    EXPECT_EQ(on_version_mismatch_calls_, 0);
    EXPECT_EQ(cloud_provider_.device_set.checked_fingerprint, "some-value");
  
    // Verify that newly created LedgerSyncs also have the upload enabled.
    auto ledger_b = user_sync_->CreateLedgerSync("app-id", &encryption_service_);
    auto ledger_b_ptr = static_cast<LedgerSyncImpl*>(ledger_b.get());
    EXPECT_TRUE(ledger_b_ptr->IsUploadEnabled());
  }
  
  // Verifies that if there is no fingerprint file, it is created and set in the
  // cloud.
  TEST_F(UserSyncImplTest, CloudCheckSet) {
    auto fingerprint_path = user_sync_->GetFingerprintPath();
    EXPECT_FALSE(files::IsFileAt(fingerprint_path.root_fd(), fingerprint_path.path()));
    cloud_provider_.device_set.status_to_return = cloud_provider::Status::OK;
    EXPECT_EQ(on_version_mismatch_calls_, 0);
    user_sync_->Start();
  
    auto ledger = user_sync_->CreateLedgerSync("app-id", &encryption_service_);
    auto ledger_ptr = static_cast<LedgerSyncImpl*>(ledger.get());
    EXPECT_FALSE(ledger_ptr->IsUploadEnabled());
    RunLoopUntilIdle();
    EXPECT_TRUE(ledger_ptr->IsUploadEnabled());
    EXPECT_EQ(on_version_mismatch_calls_, 0);
    EXPECT_FALSE(cloud_provider_.device_set.set_fingerprint.empty());
  
    // Verify that the fingerprint file was created.
    EXPECT_TRUE(files::IsFileAt(fingerprint_path.root_fd(), fingerprint_path.path()));
  }
  
  // Verifies that the cloud watcher for the fingerprint is set and triggers the
  // mismatch callback when cloud erase is detected.
  TEST_F(UserSyncImplTest, WatchErase) {
    ASSERT_TRUE(SetFingerprintFile("some-value"));
    cloud_provider_.device_set.status_to_return = cloud_provider::Status::OK;
    user_sync_->Start();
  
    RunLoopUntilIdle();
    EXPECT_TRUE(cloud_provider_.device_set.set_watcher.is_bound());
    EXPECT_EQ(cloud_provider_.device_set.watched_fingerprint, "some-value");
    EXPECT_EQ(on_version_mismatch_calls_, 0);
  
    cloud_provider_.device_set.set_watcher->OnCloudErased();
    RunLoopUntilIdle();
    EXPECT_EQ(on_version_mismatch_calls_, 1);
  }
  
  // Verifies that setting the cloud watcher for is retried on network errors.
  TEST_F(UserSyncImplTest, WatchRetry) {
    ASSERT_TRUE(SetFingerprintFile("some-value"));
    cloud_provider_.device_set.set_watcher_status_to_return = cloud_provider::Status::NETWORK_ERROR;
    user_sync_->Start();
  
    RunLoopUntilIdle();
    EXPECT_EQ(cloud_provider_.device_set.set_watcher_calls, 1);
  }
  
  }  // namespace
  
  }  // namespace cloud_sync
  