// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/sync/service/sync_service_crypto.h"

#include <functional>
#include <utility>

#include "base/base64.h"
#include "base/functional/callback.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/test_future.h"
#include "components/os_crypt/async/browser/os_crypt_async.h"
#include "components/os_crypt/async/browser/test_utils.h"
#include "components/os_crypt/async/common/encryptor.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/sync/base/custom_passphrase_bootstrap_token.h"
#include "components/sync/base/time.h"
#include "components/sync/engine/sync_status.h"
#include "components/sync/model/crypto/nigori.h"
#include "components/sync/nigori/required_passphrase_verifier_impl.h"
#include "components/sync/test/mock_sync_engine.h"
#include "components/sync/test/sync_service_crypto_test_utils.h"
#include "components/trusted_vault/test/fake_trusted_vault_client.h"
#include "google_apis/gaia/gaia_id.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace syncer {

namespace {

using testing::_;
using testing::Eq;
using testing::IsEmpty;
using testing::IsNull;
using testing::Ne;
using testing::Not;
using testing::NotNull;
using testing::Return;
using testing::ReturnPointee;
using testing::ReturnRef;
using testing::SaveArg;

sync_pb::EncryptedData MakeEncryptedData(
    const std::string& passphrase,
    const KeyDerivationParams& derivation_params) {
  std::unique_ptr<Nigori> nigori = Nigori::CreateByDerivation(
      NigoriPassKey::ForTesting(), derivation_params, passphrase);

  const std::string unencrypted = "test";
  sync_pb::EncryptedData encrypted;
  encrypted.set_key_name(nigori->GetKeyName());
  encrypted.set_blob(nigori->Encrypt(unencrypted));
  return encrypted;
}

CoreAccountInfo MakeAccountInfoWithGaia(const GaiaId& gaia) {
  CoreAccountInfo result;
  result.gaia = gaia;
  return result;
}

std::unique_ptr<RequiredPassphraseVerifier> CreateVerifier(
    const KeyDerivationParams& params,
    const sync_pb::EncryptedData& pending_keys) {
  return std::make_unique<RequiredPassphraseVerifierImpl>(params, pending_keys);
}

CustomPassphraseBootstrapToken CreateBootstrapToken(
    const std::string& passphrase,
    const KeyDerivationParams& derivation_params) {
  std::unique_ptr<Nigori> nigori = Nigori::CreateByDerivation(
      NigoriPassKey::ForTesting(), derivation_params, passphrase);

  sync_pb::NigoriKey proto;
  proto.set_deprecated_name(nigori->GetKeyName());
  nigori->ExportKeys(proto.mutable_deprecated_user_key(),
                     proto.mutable_encryption_key(), proto.mutable_mac_key());
  return CustomPassphraseBootstrapToken::FromProto(std::move(proto));
}



class MockDelegate : public SyncServiceCrypto::Delegate {
 public:
  MockDelegate() = default;
  ~MockDelegate() override = default;

  MOCK_METHOD(void, CryptoStateChanged, (), (override));
  MOCK_METHOD(void, CryptoRequiredUserActionChanged, (), (override));
  MOCK_METHOD(void, ReconfigureDataTypesDueToCrypto, (), (override));
  MOCK_METHOD(void, PassphraseTypeChanged, (PassphraseType), (override));
  MOCK_METHOD(std::optional<PassphraseType>,
              GetPassphraseType,
              (),
              (const override));
  MOCK_METHOD(void,
              SetEncryptionBootstrapToken,
              (const CustomPassphraseBootstrapToken&,
               const os_crypt_async::Encryptor&),
              (override));
  MOCK_METHOD(CustomPassphraseBootstrapToken,
              GetEncryptionBootstrapToken,
              (const os_crypt_async::Encryptor&),
              (const override));
};

class SyncServiceCryptoTest : public testing::Test {
 protected:
  // Account used in most tests.
  const CoreAccountInfo kSyncingAccount =
      MakeAccountInfoWithGaia(GaiaId("syncingaccount"));

  // Initial trusted vault keys stored on the server for `kSyncingAccount`.
  const std::vector<std::vector<uint8_t>> kInitialTrustedVaultKeys = {
      {0, 1, 2, 3, 4}};

  SyncServiceCryptoTest() : crypto_(&delegate_, &trusted_vault_client_) {
    trusted_vault_client_.server()->StoreKeysOnServer(kSyncingAccount.gaia,
                                                      kInitialTrustedVaultKeys);

    crypto_.SetEncryptor(GetEncryptorForTest());

    ON_CALL(delegate_, GetPassphraseType())
        .WillByDefault(ReturnPointee(&passphrase_type_));
    ON_CALL(delegate_, PassphraseTypeChanged(_))
        .WillByDefault(SaveArg<0>(&passphrase_type_));
  }

  ~SyncServiceCryptoTest() override = default;

  void SetUp() override {
    os_crypt_async_ = os_crypt_async::GetTestOSCryptAsyncForTesting(
        /*is_sync_for_unittests=*/true);
    crypto_.SetEncryptor(GetEncryptor());
    encryptor_ = GetEncryptor();
  }

  scoped_refptr<os_crypt_async::Encryptor> GetEncryptor() {
    base::test::TestFuture<scoped_refptr<os_crypt_async::Encryptor>> future;
    os_crypt_async_->GetInstance(future.GetCallback());
    return future.Take();
  }

  bool VerifyAndClearExpectations() {
    return testing::Mock::VerifyAndClearExpectations(&delegate_) &&
           testing::Mock::VerifyAndClearExpectations(&trusted_vault_client_) &&
           testing::Mock::VerifyAndClearExpectations(&engine_);
  }

  void MimicKeyRetrievalByUser(
      std::optional<trusted_vault::TrustedVaultUserActionTriggerForUMA>
          trigger = std::nullopt) {
    trusted_vault_client_.server()->MimicKeyRetrievalByUser(
        kSyncingAccount.gaia, &trusted_vault_client_, trigger);
  }

  std::optional<PassphraseType> passphrase_type_;

  testing::NiceMock<MockDelegate> delegate_;
  trusted_vault::FakeTrustedVaultClient trusted_vault_client_;
  testing::NiceMock<MockSyncEngine> engine_;
  SyncServiceCrypto crypto_;
  std::unique_ptr<os_crypt_async::OSCryptAsync> os_crypt_async_;
  scoped_refptr<os_crypt_async::Encryptor> encryptor_;
};

// Happy case where no user action is required upon startup.
TEST_F(SyncServiceCryptoTest, ShouldRequireNoUserAction) {
  crypto_.SetSyncEngine(CoreAccountInfo(), &engine_);
  EXPECT_FALSE(crypto_.IsPassphraseRequired());
  EXPECT_FALSE(crypto_.IsTrustedVaultKeyRequired());
  EXPECT_TRUE(crypto_.IsTrustedVaultKeyRequiredStateKnown());
  EXPECT_FALSE(crypto_.IsTrustedVaultRecoverabilityDegraded());
}

TEST_F(SyncServiceCryptoTest, ShouldSetUpNewCustomPassphrase) {
  const std::string kTestPassphrase = "somepassphrase";

  crypto_.SetSyncEngine(CoreAccountInfo(), &engine_);
  ASSERT_FALSE(crypto_.IsPassphraseRequired());
  ASSERT_FALSE(crypto_.IsEncryptEverythingEnabled());
  ASSERT_THAT(crypto_.GetPassphraseType(),
              Ne(PassphraseType::kCustomPassphrase));

  EXPECT_CALL(delegate_, SetEncryptionBootstrapToken(_, _));
  EXPECT_CALL(engine_, SetEncryptionPassphrase(kTestPassphrase));
  crypto_.SetEncryptionPassphrase(kTestPassphrase);

  // Mimic completion of the procedure in the sync engine.
  EXPECT_CALL(delegate_, CryptoStateChanged());
  crypto_.OnPassphraseTypeChanged(PassphraseType::kCustomPassphrase,
                                  base::Time::Now());
  // The current implementation notifies observers again upon
  // crypto_.OnEncryptedTypesChanged(). This may change in the future.
  EXPECT_CALL(delegate_, CryptoStateChanged());
  crypto_.OnEncryptedTypesChanged(syncer::EncryptableUserTypes(),
                                  /*encrypt_everything=*/true);
  EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto());
  crypto_.OnPassphraseAccepted(
      CustomPassphraseBootstrapToken::CreateFakeForTesting(1));

  EXPECT_FALSE(crypto_.IsPassphraseRequired());
  EXPECT_TRUE(crypto_.IsEncryptEverythingEnabled());
  EXPECT_THAT(crypto_.GetPassphraseType(),
              Eq(PassphraseType::kCustomPassphrase));
}

TEST_F(SyncServiceCryptoTest, ShouldExposePassphraseRequired) {
  const std::string kTestPassphrase = "somepassphrase";

  crypto_.SetSyncEngine(CoreAccountInfo(), &engine_);
  ASSERT_FALSE(crypto_.IsPassphraseRequired());
  ASSERT_THAT(trusted_vault_client_.fetch_count(), Eq(0));

  // Mimic the engine determining that a passphrase is required.
  EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto());
  crypto_.OnPassphraseRequired(CreateVerifier(
      KeyDerivationParams::CreateForPbkdf2(),
      MakeEncryptedData(kTestPassphrase,
                        KeyDerivationParams::CreateForPbkdf2())));
  EXPECT_TRUE(crypto_.IsPassphraseRequired());
  VerifyAndClearExpectations();

  // Entering the wrong passphrase should be rejected.
  EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto()).Times(0);
  EXPECT_CALL(engine_, SetDecryptionPassphrase).Times(0);
  EXPECT_FALSE(crypto_.SetDecryptionPassphrase("wrongpassphrase"));
  EXPECT_TRUE(crypto_.IsPassphraseRequired());

  // Entering the correct passphrase should be accepted.
  EXPECT_CALL(engine_, SetDecryptionPassphrase(Eq(kTestPassphrase)))
      .WillOnce([&](const std::string&) {
        crypto_.OnPassphraseAccepted(
            CustomPassphraseBootstrapToken::CreateFakeForTesting(1));
      });
  // The current implementation issues two reconfigurations: one immediately
  // after checking the passphrase in the UI thread and a second time later when
  // the engine confirms with OnPassphraseAccepted().
  EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto()).Times(2);
  EXPECT_CALL(delegate_, SetEncryptionBootstrapToken(_, _));
  EXPECT_TRUE(crypto_.SetDecryptionPassphrase(kTestPassphrase));
  EXPECT_FALSE(crypto_.IsPassphraseRequired());
}

TEST_F(SyncServiceCryptoTest, ShouldHandleKeystoreKeysRequired) {
  crypto_.SetSyncEngine(CoreAccountInfo(), &engine_);
  ASSERT_FALSE(crypto_.IsPassphraseRequired());
  ASSERT_FALSE(crypto_.HasCryptoError());

  // Mimic the engine determining that keystore keys are required.
  EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto());
  crypto_.OnKeystoreKeysRequired();

  EXPECT_FALSE(crypto_.IsPassphraseRequired());
  EXPECT_TRUE(crypto_.HasCryptoError());
  VerifyAndClearExpectations();

  // Mimic the engine confirming that keystore keys are accepted.
  EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto());
  crypto_.OnKeystoreKeysAccepted();

  EXPECT_FALSE(crypto_.IsPassphraseRequired());
  EXPECT_FALSE(crypto_.HasCryptoError());
}

TEST_F(SyncServiceCryptoTest,
       ShouldHandleKeystoreKeysAcceptedAfterPassphraseRequired) {
  crypto_.SetSyncEngine(CoreAccountInfo(), &engine_);
  ASSERT_FALSE(crypto_.IsPassphraseRequired());
  ASSERT_FALSE(crypto_.HasCryptoError());

  // Mimic the engine determining that a passphrase is required (half-migrated
  // keystore state).
  EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto());
  crypto_.OnPassphraseRequired(CreateVerifier(
      KeyDerivationParams::CreateForPbkdf2(), sync_pb::EncryptedData()));

  EXPECT_TRUE(crypto_.IsPassphraseRequired());
  EXPECT_TRUE(crypto_.HasCryptoError());
  VerifyAndClearExpectations();

  // Mimic the engine confirming that keystore keys are accepted (resolving the
  // encryption). This shouldn't happen in real scenarios where the user was
  // actually in a half-migrated state, but it is technially reachable so it is
  // reasonable to expect that the crypto error would be resolved in this case
  // too.
  EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto());
  crypto_.OnKeystoreKeysAccepted();

  EXPECT_FALSE(crypto_.IsPassphraseRequired());
  EXPECT_FALSE(crypto_.HasCryptoError());
}

// Regression test for crbug.com/1306831.
TEST_F(SyncServiceCryptoTest,
       ShouldStoreBootstrapTokenBeforeReconfiguringDataTypes) {
  const std::string kTestPassphrase = "somepassphrase";

  crypto_.SetSyncEngine(CoreAccountInfo(), &engine_);
  ASSERT_FALSE(crypto_.IsPassphraseRequired());

  crypto_.OnPassphraseRequired(CreateVerifier(
      KeyDerivationParams::CreateForPbkdf2(),
      MakeEncryptedData(kTestPassphrase,
                        KeyDerivationParams::CreateForPbkdf2())));
  ASSERT_TRUE(crypto_.IsPassphraseRequired());

  // Entering the correct passphrase should be accepted.
  EXPECT_CALL(engine_, SetDecryptionPassphrase(Eq(kTestPassphrase)))
      .WillOnce([&](const std::string&) {
        crypto_.OnPassphraseAccepted(
            CustomPassphraseBootstrapToken::CreateFakeForTesting(1));
      });

  // Order of SetEncryptionBootstrapToken() and
  // ReconfigureDataTypesDueToCrypto() (assuming passphrase is not required upon
  // reconfiguration) is important as clients rely on this to detect whether
  // GetExplicitPassphraseDecryptionNigoriKey() can be called.
  testing::InSequence seq;
  EXPECT_CALL(delegate_, SetEncryptionBootstrapToken(_, _));
  // The current implementation issues two reconfigurations: one immediately
  // after checking the passphrase in the UI thread and a second time later when
  // the engine confirms with OnPassphraseAccepted().
  EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto()).Times(2);
  ASSERT_TRUE(crypto_.SetDecryptionPassphrase(kTestPassphrase));
}

TEST_F(SyncServiceCryptoTest, ShouldSetupDecryptionWithBootstrapToken) {
  const std::string kTestPassphrase = "somepassphrase";

  crypto_.SetSyncEngine(CoreAccountInfo(), &engine_);
  ASSERT_FALSE(crypto_.IsPassphraseRequired());

  // Mimic passphrase stored in bootstrap token.
  ON_CALL(delegate_, GetEncryptionBootstrapToken(_))
      .WillByDefault(Return(CreateBootstrapToken(
          kTestPassphrase, KeyDerivationParams::CreateForPbkdf2())));

  // Expect setting decryption key without waiting till user enters the
  // passphrase.
  EXPECT_CALL(engine_, SetDecryptionBootstrapToken(_))
      .WillOnce([&](const CustomPassphraseBootstrapToken&) {
        crypto_.OnPassphraseAccepted(
            CustomPassphraseBootstrapToken::CreateFakeForTesting(1));
      });

  // Mimic the engine determining that a passphrase is required.
  crypto_.OnPassphraseRequired(CreateVerifier(
      KeyDerivationParams::CreateForPbkdf2(),
      MakeEncryptedData(kTestPassphrase,
                        KeyDerivationParams::CreateForPbkdf2())));
  // The passphrase-required state should have been automatically resolved via
  // the bootstrap token.
  EXPECT_FALSE(crypto_.IsPassphraseRequired());
}

TEST_F(SyncServiceCryptoTest,
       ShouldSetupDecryptionWithBootstrapTokenUponEngineInitialization) {
  const std::string kTestPassphrase = "somepassphrase";

  ASSERT_FALSE(crypto_.IsPassphraseRequired());

  // Mimic passphrase stored in bootstrap token.
  ON_CALL(delegate_, GetEncryptionBootstrapToken(_))
      .WillByDefault(Return(CreateBootstrapToken(
          kTestPassphrase, KeyDerivationParams::CreateForPbkdf2())));

  // Mimic the engine determining that a passphrase is required. Note that
  // `crypto_` isn't yet aware of engine initialization - this is a legitimate
  // scenario.
  crypto_.OnPassphraseRequired(CreateVerifier(
      KeyDerivationParams::CreateForPbkdf2(),
      MakeEncryptedData(kTestPassphrase,
                        KeyDerivationParams::CreateForPbkdf2())));
  EXPECT_TRUE(crypto_.IsPassphraseRequired());

  // Expect setting decryption key without waiting till user enters the
  // passphrase.
  EXPECT_CALL(engine_, SetDecryptionBootstrapToken(_))
      .WillOnce([&](const CustomPassphraseBootstrapToken&) {
        crypto_.OnPassphraseAccepted(
            CustomPassphraseBootstrapToken::CreateFakeForTesting(1));
      });

  // Mimic completion of engine initialization, now decryption key from
  // bootstrap token should be populated to the engine.
  crypto_.SetSyncEngine(CoreAccountInfo(), &engine_);
  EXPECT_FALSE(crypto_.IsPassphraseRequired());
}

TEST_F(SyncServiceCryptoTest, ShouldIgnoreNotMatchingBootstrapToken) {
  const std::string kTestPassphrase = "somepassphrase";

  crypto_.SetSyncEngine(CoreAccountInfo(), &engine_);
  ASSERT_FALSE(crypto_.IsPassphraseRequired());

  ON_CALL(delegate_, GetEncryptionBootstrapToken(_))
      .WillByDefault(Return(CreateBootstrapToken(
          "wrongpassphrase", KeyDerivationParams::CreateForPbkdf2())));

  // Mimic the engine determining that a passphrase is required.
  EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto());
  // There should be no attempt to populate wrong key to the `engine_`.
  EXPECT_CALL(engine_, SetDecryptionPassphrase).Times(0);
  crypto_.OnPassphraseRequired(CreateVerifier(
      KeyDerivationParams::CreateForPbkdf2(),
      MakeEncryptedData(kTestPassphrase,
                        KeyDerivationParams::CreateForPbkdf2())));
  EXPECT_TRUE(crypto_.IsPassphraseRequired());
  VerifyAndClearExpectations();

  // Entering the correct passphrase should be accepted.
  EXPECT_CALL(engine_, SetDecryptionPassphrase(Eq(kTestPassphrase)))
      .WillOnce([&](const std::string&) {
        crypto_.OnPassphraseAccepted(
            CustomPassphraseBootstrapToken::CreateFakeForTesting(1));
      });
  // The current implementation issues two reconfigurations: one immediately
  // after checking the passphrase in the UI thread and a second time later when
  // the engine confirms with OnPassphraseAccepted().
  EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto()).Times(2);
  EXPECT_CALL(delegate_, SetEncryptionBootstrapToken(_, _));
  EXPECT_TRUE(crypto_.SetDecryptionPassphrase(kTestPassphrase));
  EXPECT_FALSE(crypto_.IsPassphraseRequired());
}

TEST_F(SyncServiceCryptoTest, ShouldIgnoreCorruptedBootstrapToken) {
  const std::string kTestPassphrase = "somepassphrase";

  crypto_.SetSyncEngine(CoreAccountInfo(), &engine_);
  ASSERT_FALSE(crypto_.IsPassphraseRequired());

  ON_CALL(delegate_, GetEncryptionBootstrapToken(_))
      .WillByDefault(Return(CustomPassphraseBootstrapToken()));

  // Mimic the engine determining that a passphrase is required.
  EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto());
  // There should be no attempt to populate wrong key to the `engine_`.
  EXPECT_CALL(engine_, SetDecryptionPassphrase).Times(0);
  crypto_.OnPassphraseRequired(CreateVerifier(
      KeyDerivationParams::CreateForPbkdf2(),
      MakeEncryptedData(kTestPassphrase,
                        KeyDerivationParams::CreateForPbkdf2())));
  EXPECT_TRUE(crypto_.IsPassphraseRequired());
  VerifyAndClearExpectations();

  // Entering the correct passphrase should be accepted.
  EXPECT_CALL(engine_, SetDecryptionPassphrase(Eq(kTestPassphrase)))
      .WillOnce([&](const std::string&) {
        crypto_.OnPassphraseAccepted(
            CustomPassphraseBootstrapToken::CreateFakeForTesting(1));
      });
  // The current implementation issues two reconfigurations: one immediately
  // after checking the passphrase in the UI thread and a second time later when
  // the engine confirms with OnPassphraseAccepted().
  EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto()).Times(2);
  EXPECT_CALL(delegate_, SetEncryptionBootstrapToken(_, _));
  EXPECT_TRUE(crypto_.SetDecryptionPassphrase(kTestPassphrase));
  EXPECT_FALSE(crypto_.IsPassphraseRequired());
}



TEST_F(SyncServiceCryptoTest,
       ShouldReadValidTrustedVaultKeysFromClientBeforeInitialization) {
  // Cache `kInitialTrustedVaultKeys` into `trusted_vault_client_` prior to
  // engine initialization.
  MimicKeyRetrievalByUser();

  EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto()).Times(0);
  ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());

  // OnTrustedVaultKeyRequired() called during initialization of the sync
  // engine (i.e. before SetSyncEngine()).
  crypto_.OnTrustedVaultKeyRequired();

  // Trusted vault keys should be fetched only after the engine initialization
  // is completed.
  ASSERT_THAT(trusted_vault_client_.fetch_count(), Eq(0));
  crypto_.SetSyncEngine(kSyncingAccount, &engine_);

  // While there is an ongoing fetch, there should be no user action required.
  EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(1));
  EXPECT_FALSE(crypto_.IsTrustedVaultKeyRequired());

  base::OnceClosure add_keys_cb;
  EXPECT_CALL(engine_,
              AddTrustedVaultDecryptionKeys(kInitialTrustedVaultKeys, _))
      .WillOnce(
          [&](const std::vector<std::vector<uint8_t>>& keys,
              base::OnceClosure done_cb) { add_keys_cb = std::move(done_cb); });

  // Mimic completion of the fetch.
  ASSERT_TRUE(trusted_vault_client_.CompleteAllPendingRequests());
  ASSERT_TRUE(add_keys_cb);
  EXPECT_FALSE(crypto_.IsTrustedVaultKeyRequired());

  // Mimic completion of the engine.
  EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto());
  crypto_.OnTrustedVaultKeyAccepted();
  std::move(add_keys_cb).Run();
  EXPECT_FALSE(crypto_.IsTrustedVaultKeyRequired());
  EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(1));
  EXPECT_THAT(trusted_vault_client_.keys_marked_as_stale_count(), Eq(0));
  EXPECT_THAT(trusted_vault_client_.server_request_count(), Eq(0));
}

TEST_F(SyncServiceCryptoTest,
       ShouldRecordTrustedVaultAddKeysSuccessfullyHistogram) {
  const std::vector<std::vector<uint8_t>> kNewKeys = {{2, 3, 4, 5}};
  base::HistogramTester histogram_tester;

  // Cache `kInitialTrustedVaultKeys` into `trusted_vault_client_` prior to
  // engine initialization. In this test, `kInitialTrustedVaultKeys` does not
  // match the Nigori keys (i.e. the engine continues to think trusted vault
  // keys are required until `kNewKeys` are provided).
  MimicKeyRetrievalByUser();

  // The engine replies with OnTrustedVaultKeyAccepted() only if `kNewKeys` are
  // provided.
  ON_CALL(engine_, AddTrustedVaultDecryptionKeys)
      .WillByDefault([&](const std::vector<std::vector<uint8_t>>& keys,
                         base::OnceClosure done_cb) {
        if (keys == kNewKeys) {
          crypto_.OnTrustedVaultKeyAccepted();
        }
        std::move(done_cb).Run();
      });

  // Mimic initialization of the engine where trusted vault keys are needed and
  // `kInitialTrustedVaultKeys` are fetched, which are insufficient, and hence
  // IsTrustedVaultKeyRequired() is exposed.
  crypto_.SetSyncEngine(kSyncingAccount, &engine_);
  crypto_.OnTrustedVaultKeyRequired();
  // Note that this initial attempt involves two fetches, where both return
  // `kInitialTrustedVaultKeys`.
  ASSERT_TRUE(trusted_vault_client_.CompleteAllPendingRequests());
  ASSERT_TRUE(trusted_vault_client_.CompleteAllPendingRequests());
  ASSERT_TRUE(crypto_.IsTrustedVaultKeyRequired());

  // Mimic server-side key reset and a new retrieval with a user action trigger.
  trusted_vault_client_.server()->StoreKeysOnServer(kSyncingAccount.gaia,
                                                    kNewKeys);
  MimicKeyRetrievalByUser(
      trusted_vault::TrustedVaultUserActionTriggerForUMA::kSettings);

  EXPECT_TRUE(trusted_vault_client_.CompleteAllPendingRequests());

  // Now, the relevant histogram for the specified trigger should have been
  // emitted.
  histogram_tester.ExpectUniqueSample(
      "Sync.TrustedVaultAddKeysSuccessfully",
      /*sample=*/
      trusted_vault::TrustedVaultUserActionTriggerForUMA::kSettings,
      /*expected_bucket_count=*/1);
}

TEST_F(SyncServiceCryptoTest,
       ShouldReadValidTrustedVaultKeysFromClientAfterInitialization) {
  // Cache `kInitialTrustedVaultKeys` into `trusted_vault_client_` prior to
  // engine initialization.
  MimicKeyRetrievalByUser();

  EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto()).Times(0);
  ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());

  // Mimic the initialization of the sync engine, without trusted vault keys
  // being required.
  crypto_.SetSyncEngine(kSyncingAccount, &engine_);
  ASSERT_THAT(trusted_vault_client_.fetch_count(), Eq(0));

  // Later on, mimic trusted vault keys being required (e.g. remote Nigori
  // update), which should trigger a fetch.
  crypto_.OnTrustedVaultKeyRequired();
  EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(1));

  // While there is an ongoing fetch, there should be no user action required.
  EXPECT_FALSE(crypto_.IsTrustedVaultKeyRequired());

  base::OnceClosure add_keys_cb;
  EXPECT_CALL(engine_,
              AddTrustedVaultDecryptionKeys(kInitialTrustedVaultKeys, _))
      .WillOnce(
          [&](const std::vector<std::vector<uint8_t>>& keys,
              base::OnceClosure done_cb) { add_keys_cb = std::move(done_cb); });

  // Mimic completion of the fetch.
  ASSERT_TRUE(trusted_vault_client_.CompleteAllPendingRequests());
  ASSERT_TRUE(add_keys_cb);
  EXPECT_FALSE(crypto_.IsTrustedVaultKeyRequired());

  // Mimic completion of the engine.
  EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto());
  crypto_.OnTrustedVaultKeyAccepted();
  std::move(add_keys_cb).Run();
  EXPECT_FALSE(crypto_.IsTrustedVaultKeyRequired());
  EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(1));
  EXPECT_THAT(trusted_vault_client_.keys_marked_as_stale_count(), Eq(0));
  EXPECT_THAT(trusted_vault_client_.server_request_count(), Eq(0));
}

TEST_F(SyncServiceCryptoTest,
       ShouldReadNoTrustedVaultKeysFromClientAfterInitialization) {
  EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto()).Times(0);
  EXPECT_CALL(engine_, AddTrustedVaultDecryptionKeys).Times(0);

  ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());

  // Mimic the initialization of the sync engine, without trusted vault keys
  // being required.
  crypto_.SetSyncEngine(kSyncingAccount, &engine_);
  ASSERT_THAT(trusted_vault_client_.fetch_count(), Eq(0));
  ASSERT_THAT(trusted_vault_client_.server_request_count(), Eq(0));

  // Later on, mimic trusted vault keys being required (e.g. remote Nigori
  // update), which should trigger a fetch.
  crypto_.OnTrustedVaultKeyRequired();
  EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(1));

  // While there is an ongoing fetch, there should be no user action required.
  ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());

  // Mimic completion of the fetch, which should lead to a reconfiguration.
  EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto());
  ASSERT_TRUE(trusted_vault_client_.CompleteAllPendingRequests());
  EXPECT_TRUE(crypto_.IsTrustedVaultKeyRequired());
  EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(1));

  EXPECT_THAT(trusted_vault_client_.server_request_count(), Eq(0));
  EXPECT_THAT(trusted_vault_client_.keys_marked_as_stale_count(), Eq(0));
}

TEST_F(SyncServiceCryptoTest, ShouldReadInvalidTrustedVaultKeysFromClient) {
  // Cache `kInitialTrustedVaultKeys` into `trusted_vault_client_` prior to
  // engine initialization. In this test, `kInitialTrustedVaultKeys` does not
  // match the Nigori keys (i.e. the engine continues to think trusted vault
  // keys are required).
  MimicKeyRetrievalByUser();

  base::OnceClosure add_keys_cb;
  ON_CALL(engine_, AddTrustedVaultDecryptionKeys)
      .WillByDefault(
          [&](const std::vector<std::vector<uint8_t>>& keys,
              base::OnceClosure done_cb) { add_keys_cb = std::move(done_cb); });

  ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());

  // Mimic the initialization of the sync engine, without trusted vault keys
  // being required.
  crypto_.SetSyncEngine(kSyncingAccount, &engine_);
  ASSERT_THAT(trusted_vault_client_.fetch_count(), Eq(0));
  ASSERT_THAT(trusted_vault_client_.server_request_count(), Eq(0));

  // Later on, mimic trusted vault keys being required (e.g. remote Nigori
  // update), which should trigger a fetch.
  crypto_.OnTrustedVaultKeyRequired();
  EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(1));

  // While there is an ongoing fetch, there should be no user action required.
  EXPECT_FALSE(crypto_.IsTrustedVaultKeyRequired());

  // Mimic completion of the client.
  EXPECT_CALL(engine_,
              AddTrustedVaultDecryptionKeys(kInitialTrustedVaultKeys, _));
  ASSERT_TRUE(trusted_vault_client_.CompleteAllPendingRequests());
  ASSERT_TRUE(add_keys_cb);
  EXPECT_FALSE(crypto_.IsTrustedVaultKeyRequired());

  // Mimic completion of the engine, without OnTrustedVaultKeyAccepted().
  std::move(add_keys_cb).Run();

  // The keys should be marked as stale, and a second fetch attempt started.
  EXPECT_FALSE(crypto_.IsTrustedVaultKeyRequired());
  EXPECT_THAT(trusted_vault_client_.keys_marked_as_stale_count(), Eq(1));
  EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(2));

  // Mimic completion of the client for the second pass.
  EXPECT_CALL(engine_,
              AddTrustedVaultDecryptionKeys(kInitialTrustedVaultKeys, _));
  ASSERT_TRUE(trusted_vault_client_.CompleteAllPendingRequests());
  ASSERT_TRUE(add_keys_cb);

  // Mimic completion of the engine, without OnTrustedVaultKeyAccepted(), for
  // the second pass.
  EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto());
  std::move(add_keys_cb).Run();

  EXPECT_TRUE(crypto_.IsTrustedVaultKeyRequired());
  EXPECT_THAT(trusted_vault_client_.keys_marked_as_stale_count(), Eq(1));
  EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(2));
}

// Similar to ShouldReadInvalidTrustedVaultKeysFromClient but in this case the
// client is able to follow a key rotation as part of the second fetch attempt.
TEST_F(SyncServiceCryptoTest, ShouldFollowKeyRotationDueToSecondFetch) {
  const std::vector<std::vector<uint8_t>> kRotatedKeys = {
      kInitialTrustedVaultKeys[0], {2, 3, 4, 5}};

  // Cache `kInitialTrustedVaultKeys` into `trusted_vault_client_` prior to
  // engine initialization. In this test, `kInitialTrustedVaultKeys` does not
  // match the Nigori keys (i.e. the engine continues to think trusted vault
  // keys are required until `kRotatedKeys` are provided).
  MimicKeyRetrievalByUser();

  // Mimic server-side key rotation which the keys, in a way that the rotated
  // keys are a continuation of kInitialTrustedVaultKeys, such that
  // FakeTrustedVaultClient::server() will allow the client to silently follow
  // key rotation.
  trusted_vault_client_.server()->StoreKeysOnServer(kSyncingAccount.gaia,
                                                    kRotatedKeys);

  // The engine replies with OnTrustedVaultKeyAccepted() only if `kRotatedKeys`
  // are provided.
  ON_CALL(engine_, AddTrustedVaultDecryptionKeys)
      .WillByDefault([&](const std::vector<std::vector<uint8_t>>& keys,
                         base::OnceClosure done_cb) {
        if (keys == kRotatedKeys) {
          crypto_.OnTrustedVaultKeyAccepted();
        }
        std::move(done_cb).Run();
      });

  // Mimic initialization of the engine where trusted vault keys are needed and
  // `kInitialTrustedVaultKeys` are fetched as part of the first fetch.
  crypto_.SetSyncEngine(kSyncingAccount, &engine_);
  crypto_.OnTrustedVaultKeyRequired();
  ASSERT_THAT(trusted_vault_client_.fetch_count(), Eq(1));

  // While there is an ongoing fetch (first attempt), there should be no user
  // action required.
  ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());

  // The keys fetched in the first attempt (`kInitialTrustedVaultKeys`) are
  // insufficient and should be marked as stale. In addition, a second fetch
  // should be triggered.
  ASSERT_TRUE(trusted_vault_client_.CompleteAllPendingRequests());
  ASSERT_THAT(trusted_vault_client_.keys_marked_as_stale_count(), Eq(1));
  ASSERT_THAT(trusted_vault_client_.fetch_count(), Eq(2));

  // While there is an ongoing fetch (second attempt), there should be no user
  // action required.
  ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());

  // Because of `kRotatedKeys` is a continuation of `kInitialTrustedVaultKeys`,
  // TrustedVaultServer should successfully deliver the new keys `kRotatedKeys`
  // to the client.
  EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto());
  ASSERT_TRUE(trusted_vault_client_.CompleteAllPendingRequests());
  EXPECT_FALSE(crypto_.IsTrustedVaultKeyRequired());
  ASSERT_THAT(trusted_vault_client_.keys_marked_as_stale_count(), Eq(1));
}

// Similar to ShouldReadInvalidTrustedVaultKeysFromClient: the vault
// initially has no valid keys, leading to IsTrustedVaultKeyRequired().
// Later, the vault gets populated with the keys, which should trigger
// a fetch and eventually resolve the encryption issue.
TEST_F(SyncServiceCryptoTest, ShouldRefetchTrustedVaultKeysWhenChangeObserved) {
  const std::vector<std::vector<uint8_t>> kNewKeys = {{2, 3, 4, 5}};

  // Cache `kInitialTrustedVaultKeys` into `trusted_vault_client_` prior to
  // engine initialization. In this test, `kInitialTrustedVaultKeys` does not
  // match the Nigori keys (i.e. the engine continues to think trusted vault
  // keys are required until `kNewKeys` are provided).
  MimicKeyRetrievalByUser();

  // The engine replies with OnTrustedVaultKeyAccepted() only if `kNewKeys` are
  // provided.
  ON_CALL(engine_, AddTrustedVaultDecryptionKeys)
      .WillByDefault([&](const std::vector<std::vector<uint8_t>>& keys,
                         base::OnceClosure done_cb) {
        if (keys == kNewKeys) {
          crypto_.OnTrustedVaultKeyAccepted();
        }
        std::move(done_cb).Run();
      });

  // Mimic initialization of the engine where trusted vault keys are needed and
  // `kInitialTrustedVaultKeys` are fetched, which are insufficient, and hence
  // IsTrustedVaultKeyRequired() is exposed.
  crypto_.SetSyncEngine(kSyncingAccount, &engine_);
  crypto_.OnTrustedVaultKeyRequired();
  ASSERT_THAT(trusted_vault_client_.fetch_count(), Eq(1));
  ASSERT_TRUE(trusted_vault_client_.CompleteAllPendingRequests());
  // Note that this initial attempt involves two fetches, where both return
  // `kInitialTrustedVaultKeys`.
  ASSERT_THAT(trusted_vault_client_.keys_marked_as_stale_count(), Eq(1));
  ASSERT_THAT(trusted_vault_client_.fetch_count(), Eq(2));
  ASSERT_TRUE(trusted_vault_client_.CompleteAllPendingRequests());
  ASSERT_THAT(trusted_vault_client_.keys_marked_as_stale_count(), Eq(1));
  ASSERT_TRUE(crypto_.IsTrustedVaultKeyRequired());

  // Mimic server-side key reset and a new retrieval.
  trusted_vault_client_.server()->StoreKeysOnServer(kSyncingAccount.gaia,
                                                    kNewKeys);
  MimicKeyRetrievalByUser();

  // Key retrieval should have initiated a third fetch.
  EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(3));
  EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto());
  EXPECT_TRUE(trusted_vault_client_.CompleteAllPendingRequests());
  EXPECT_FALSE(crypto_.IsTrustedVaultKeyRequired());
  EXPECT_THAT(trusted_vault_client_.keys_marked_as_stale_count(), Eq(1));
}

// Same as above but the new keys become available during an ongoing FetchKeys()
// request.
TEST_F(SyncServiceCryptoTest,
       ShouldDeferTrustedVaultKeyFetchingWhenChangeObservedWhileOngoingFetch) {
  const std::vector<std::vector<uint8_t>> kNewKeys = {{2, 3, 4, 5}};

  // Cache `kInitialTrustedVaultKeys` into `trusted_vault_client_` prior to
  // engine initialization. In this test, `kInitialTrustedVaultKeys` does not
  // match the Nigori keys (i.e. the engine continues to think trusted vault
  // keys are required until `kNewKeys` are provided).
  MimicKeyRetrievalByUser();

  // The engine replies with OnTrustedVaultKeyAccepted() only if `kNewKeys` are
  // provided.
  ON_CALL(engine_, AddTrustedVaultDecryptionKeys)
      .WillByDefault([&](const std::vector<std::vector<uint8_t>>& keys,
                         base::OnceClosure done_cb) {
        if (keys == kNewKeys) {
          crypto_.OnTrustedVaultKeyAccepted();
        }
        std::move(done_cb).Run();
      });

  // Mimic initialization of the engine where trusted vault keys are needed and
  // `kInitialTrustedVaultKeys` are in the process of being fetched.
  crypto_.SetSyncEngine(kSyncingAccount, &engine_);
  crypto_.OnTrustedVaultKeyRequired();
  ASSERT_THAT(trusted_vault_client_.fetch_count(), Eq(1));
  ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());

  // While there is an ongoing fetch, mimic server-side key reset and a new
  // retrieval.
  trusted_vault_client_.server()->StoreKeysOnServer(kSyncingAccount.gaia,
                                                    kNewKeys);
  MimicKeyRetrievalByUser();

  // Because there's already an ongoing fetch, a second one should not have been
  // triggered yet and should be deferred instead.
  EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(1));

  // As soon as the first fetch completes, the second one (deferred) should be
  // started.
  EXPECT_TRUE(trusted_vault_client_.CompleteAllPendingRequests());
  EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(2));
  EXPECT_FALSE(crypto_.IsTrustedVaultKeyRequired());

  // The completion of the second fetch should resolve the encryption issue.
  EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto());
  EXPECT_TRUE(trusted_vault_client_.CompleteAllPendingRequests());
  EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(2));
  EXPECT_FALSE(crypto_.IsTrustedVaultKeyRequired());
}

// The engine gets initialized and the vault initially has insufficient keys,
// leading to IsTrustedVaultKeyRequired(). Later, keys are added to the vault
// *twice*, where the later event should be handled as a deferred fetch.
TEST_F(
    SyncServiceCryptoTest,
    ShouldDeferTrustedVaultKeyFetchingWhenChangeObservedWhileOngoingRefetch) {
  const std::vector<std::vector<uint8_t>> kLatestKeys = {{2, 2, 2, 2, 2}};

  // The engine replies with OnTrustedVaultKeyAccepted() only if `kLatestKeys`
  // are provided.
  ON_CALL(engine_, AddTrustedVaultDecryptionKeys)
      .WillByDefault([&](const std::vector<std::vector<uint8_t>>& keys,
                         base::OnceClosure done_cb) {
        if (keys == kLatestKeys) {
          crypto_.OnTrustedVaultKeyAccepted();
        }
        std::move(done_cb).Run();
      });

  // Mimic initialization of the engine where trusted vault keys are needed and
  // no keys are fetched from the client, hence IsTrustedVaultKeyRequired() is
  // exposed.
  crypto_.SetSyncEngine(kSyncingAccount, &engine_);
  crypto_.OnTrustedVaultKeyRequired();
  ASSERT_THAT(trusted_vault_client_.fetch_count(), Eq(1));
  ASSERT_TRUE(trusted_vault_client_.CompleteAllPendingRequests());
  ASSERT_THAT(trusted_vault_client_.fetch_count(), Eq(1));
  ASSERT_THAT(trusted_vault_client_.keys_marked_as_stale_count(), Eq(0));
  ASSERT_TRUE(crypto_.IsTrustedVaultKeyRequired());

  // Mimic retrieval of keys, leading to a second fetch that returns
  // `kInitialTrustedVaultKeys`, which are insufficient and should be marked as
  // stale as soon as the fetch completes (later below).
  MimicKeyRetrievalByUser();
  EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(2));

  // While the second fetch is ongoing, mimic additional keys being retrieved.
  // Because there's already an ongoing fetch, a third one should not have been
  // triggered yet and should be deferred instead.
  trusted_vault_client_.server()->StoreKeysOnServer(kSyncingAccount.gaia,
                                                    kLatestKeys);
  MimicKeyRetrievalByUser();
  EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(2));

  // As soon as the second fetch completes, the keys should be marked as stale
  // and a third fetch attempt triggered.
  EXPECT_TRUE(trusted_vault_client_.CompleteAllPendingRequests());
  EXPECT_THAT(trusted_vault_client_.keys_marked_as_stale_count(), Eq(1));
  EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(3));

  // As soon as the third fetch completes, the fourth one (deferred) should be
  // started.
  EXPECT_TRUE(trusted_vault_client_.CompleteAllPendingRequests());
  EXPECT_THAT(trusted_vault_client_.fetch_count(), Eq(3));
}

TEST_F(SyncServiceCryptoTest,
       ShouldNotGetRecoverabilityIfKeystorePassphraseUsed) {
  trusted_vault_client_.SetIsRecoveryMethodRequired(true);
  crypto_.OnPassphraseTypeChanged(PassphraseType::kKeystorePassphrase,
                                  base::Time::Now());
  crypto_.SetSyncEngine(CoreAccountInfo(), &engine_);
  ASSERT_THAT(crypto_.GetPassphraseType(),
              Eq(PassphraseType::kKeystorePassphrase));
  ASSERT_TRUE(crypto_.IsTrustedVaultKeyRequiredStateKnown());
  ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());

  EXPECT_THAT(trusted_vault_client_.get_is_recoverablity_degraded_call_count(),
              Eq(0));
  EXPECT_FALSE(crypto_.IsTrustedVaultRecoverabilityDegraded());
}

TEST_F(SyncServiceCryptoTest,
       ShouldNotReportDegradedRecoverabilityUponInitialization) {
  const SyncStatus kEmptySyncStatus;
  ON_CALL(engine_, GetDetailedStatus())
      .WillByDefault(ReturnRef(kEmptySyncStatus));

  base::HistogramTester histogram_tester;
  crypto_.OnPassphraseTypeChanged(PassphraseType::kTrustedVaultPassphrase,
                                  base::Time::Now());
  crypto_.SetSyncEngine(CoreAccountInfo(), &engine_);
  ASSERT_TRUE(trusted_vault_client_.CompleteAllPendingRequests());
  ASSERT_THAT(crypto_.GetPassphraseType(),
              Eq(PassphraseType::kTrustedVaultPassphrase));
  ASSERT_TRUE(crypto_.IsTrustedVaultKeyRequiredStateKnown());
  ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());

  EXPECT_FALSE(crypto_.IsTrustedVaultRecoverabilityDegraded());
  histogram_tester.ExpectUniqueSample(
      "Sync.TrustedVaultRecoverabilityDegradedOnStartup",
      /*sample=*/false, /*expected_bucket_count=*/1);
}

TEST_F(SyncServiceCryptoTest,
       ShouldReportDegradedRecoverabilityUponInitialization) {
  // Use a very recent migration time to verify all histograms.
  SyncStatus sync_status;
  sync_status.trusted_vault_debug_info.set_migration_time(
      TimeToProtoTime(base::Time::Now() - base::Hours(1)));
  ON_CALL(engine_, GetDetailedStatus()).WillByDefault(ReturnRef(sync_status));

  base::HistogramTester histogram_tester;
  trusted_vault_client_.SetIsRecoveryMethodRequired(true);
  crypto_.OnPassphraseTypeChanged(PassphraseType::kTrustedVaultPassphrase,
                                  base::Time::Now());
  crypto_.SetSyncEngine(CoreAccountInfo(), &engine_);
  ASSERT_TRUE(trusted_vault_client_.CompleteAllPendingRequests());
  ASSERT_THAT(crypto_.GetPassphraseType(),
              Eq(PassphraseType::kTrustedVaultPassphrase));
  ASSERT_TRUE(crypto_.IsTrustedVaultKeyRequiredStateKnown());
  ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());

  EXPECT_TRUE(crypto_.IsTrustedVaultRecoverabilityDegraded());
  histogram_tester.ExpectUniqueSample(
      "Sync.TrustedVaultRecoverabilityDegradedOnStartup",
      /*sample=*/true, /*expected_bucket_count=*/1);
  histogram_tester.ExpectUniqueSample(
      "Sync.TrustedVaultRecoverabilityDegradedOnStartup.MigratedLast28Days",
      /*sample=*/true, /*expected_bucket_count=*/1);
  histogram_tester.ExpectUniqueSample(
      "Sync.TrustedVaultRecoverabilityDegradedOnStartup.MigratedLast7Days",
      /*sample=*/true, /*expected_bucket_count=*/1);
  histogram_tester.ExpectUniqueSample(
      "Sync.TrustedVaultRecoverabilityDegradedOnStartup.MigratedLast3Days",
      /*sample=*/true, /*expected_bucket_count=*/1);
  histogram_tester.ExpectUniqueSample(
      "Sync.TrustedVaultRecoverabilityDegradedOnStartup.MigratedLastDay",
      /*sample=*/true, /*expected_bucket_count=*/1);
}

TEST_F(SyncServiceCryptoTest, ShouldReportDegradedRecoverabilityUponChange) {
  const SyncStatus kEmptySyncStatus;
  ON_CALL(engine_, GetDetailedStatus())
      .WillByDefault(ReturnRef(kEmptySyncStatus));

  base::HistogramTester histogram_tester;
  crypto_.OnPassphraseTypeChanged(PassphraseType::kTrustedVaultPassphrase,
                                  base::Time::Now());
  crypto_.SetSyncEngine(CoreAccountInfo(), &engine_);
  ASSERT_TRUE(trusted_vault_client_.CompleteAllPendingRequests());
  ASSERT_THAT(crypto_.GetPassphraseType(),
              Eq(PassphraseType::kTrustedVaultPassphrase));
  ASSERT_TRUE(crypto_.IsTrustedVaultKeyRequiredStateKnown());
  ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());
  ASSERT_FALSE(crypto_.IsTrustedVaultRecoverabilityDegraded());

  // Changing the state notifies observers and should lead to a change in
  // IsTrustedVaultRecoverabilityDegraded().
  EXPECT_CALL(delegate_, CryptoStateChanged());
  trusted_vault_client_.SetIsRecoveryMethodRequired(true);
  EXPECT_TRUE(trusted_vault_client_.CompleteAllPendingRequests());
  EXPECT_TRUE(crypto_.IsTrustedVaultRecoverabilityDegraded());

  // For UMA purposes, only the initial value counts (false).
  histogram_tester.ExpectUniqueSample(
      "Sync.TrustedVaultRecoverabilityDegradedOnStartup",
      /*sample=*/false, /*expected_bucket_count=*/1);
}

TEST_F(SyncServiceCryptoTest,
       ShouldStopReportingDegradedRecoverabilityUponChange) {
  const SyncStatus kEmptySyncStatus;
  ON_CALL(engine_, GetDetailedStatus())
      .WillByDefault(ReturnRef(kEmptySyncStatus));

  base::HistogramTester histogram_tester;
  trusted_vault_client_.SetIsRecoveryMethodRequired(true);
  crypto_.OnPassphraseTypeChanged(PassphraseType::kTrustedVaultPassphrase,
                                  base::Time::Now());
  crypto_.SetSyncEngine(CoreAccountInfo(), &engine_);
  ASSERT_TRUE(trusted_vault_client_.CompleteAllPendingRequests());
  ASSERT_THAT(crypto_.GetPassphraseType(),
              Eq(PassphraseType::kTrustedVaultPassphrase));
  ASSERT_TRUE(crypto_.IsTrustedVaultKeyRequiredStateKnown());
  ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());
  ASSERT_TRUE(crypto_.IsTrustedVaultRecoverabilityDegraded());

  // Changing the state notifies observers and should lead to a change in
  // IsTrustedVaultRecoverabilityDegraded().
  EXPECT_CALL(delegate_, CryptoStateChanged());
  trusted_vault_client_.SetIsRecoveryMethodRequired(false);
  EXPECT_TRUE(trusted_vault_client_.CompleteAllPendingRequests());
  EXPECT_FALSE(crypto_.IsTrustedVaultRecoverabilityDegraded());

  // For UMA purposes, only the initial value counts (true).
  histogram_tester.ExpectUniqueSample(
      "Sync.TrustedVaultRecoverabilityDegradedOnStartup",
      /*sample=*/true, /*expected_bucket_count=*/1);
}

TEST_F(SyncServiceCryptoTest, ShouldReportDegradedRecoverabilityUponRetrieval) {
  const SyncStatus kEmptySyncStatus;
  ON_CALL(engine_, GetDetailedStatus())
      .WillByDefault(ReturnRef(kEmptySyncStatus));

  base::HistogramTester histogram_tester;
  trusted_vault_client_.SetIsRecoveryMethodRequired(true);

  // Mimic startup with trusted vault keys being required.
  crypto_.OnTrustedVaultKeyRequired();
  crypto_.OnPassphraseTypeChanged(PassphraseType::kTrustedVaultPassphrase,
                                  base::Time::Now());
  crypto_.SetSyncEngine(kSyncingAccount, &engine_);
  ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequiredStateKnown());
  ASSERT_FALSE(crypto_.IsTrustedVaultRecoverabilityDegraded());

  // Complete the fetching of initial keys (no keys) from the client.
  ASSERT_TRUE(trusted_vault_client_.CompleteAllPendingRequests());
  ASSERT_TRUE(crypto_.IsTrustedVaultKeyRequiredStateKnown());
  ASSERT_TRUE(crypto_.IsTrustedVaultKeyRequired());
  ASSERT_FALSE(crypto_.IsTrustedVaultRecoverabilityDegraded());

  // Mimic a successful key retrieval.
  ON_CALL(engine_, AddTrustedVaultDecryptionKeys)
      .WillByDefault([&](const std::vector<std::vector<uint8_t>>& keys,
                         base::OnceClosure done_cb) {
        crypto_.OnTrustedVaultKeyAccepted();
        std::move(done_cb).Run();
      });
  MimicKeyRetrievalByUser();
  ASSERT_TRUE(trusted_vault_client_.CompleteAllPendingRequests());
  ASSERT_TRUE(crypto_.IsTrustedVaultKeyRequiredStateKnown());
  ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());

  // Complete degraded recoverability refresh, that should be triggered upon
  // successful key retrieval.
  EXPECT_TRUE(trusted_vault_client_.CompleteAllPendingRequests());

  // The recoverability state should be exposed.
  EXPECT_TRUE(crypto_.IsTrustedVaultRecoverabilityDegraded());
  histogram_tester.ExpectUniqueSample(
      "Sync.TrustedVaultRecoverabilityDegradedOnStartup",
      /*sample=*/true, /*expected_bucket_count=*/1);
}

TEST_F(SyncServiceCryptoTest,
       ShouldClearDegradedRecoverabilityIfCustomPassphraseIsSet) {
  const SyncStatus kEmptySyncStatus;
  ON_CALL(engine_, GetDetailedStatus())
      .WillByDefault(ReturnRef(kEmptySyncStatus));

  const std::string kTestPassphrase = "somepassphrase";

  // Mimic a browser startup in `kTrustedVaultPassphrase` with no additional
  // keys required and degraded recoverability state.
  trusted_vault_client_.SetIsRecoveryMethodRequired(true);
  crypto_.OnPassphraseTypeChanged(PassphraseType::kTrustedVaultPassphrase,
                                  base::Time::Now());
  crypto_.SetSyncEngine(CoreAccountInfo(), &engine_);
  ASSERT_TRUE(trusted_vault_client_.CompleteAllPendingRequests());
  ASSERT_THAT(crypto_.GetPassphraseType(),
              Eq(PassphraseType::kTrustedVaultPassphrase));
  ASSERT_TRUE(crypto_.IsTrustedVaultKeyRequiredStateKnown());
  ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());
  ASSERT_FALSE(crypto_.IsPassphraseRequired());
  ASSERT_TRUE(crypto_.IsTrustedVaultRecoverabilityDegraded());

  // Mimic the user setting up a new custom passphrase.
  crypto_.SetEncryptionPassphrase(kTestPassphrase);

  // Mimic completion of the procedure in the sync engine.
  EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto());
  EXPECT_CALL(delegate_, CryptoStateChanged());
  crypto_.OnPassphraseTypeChanged(PassphraseType::kCustomPassphrase,
                                  base::Time::Now());
  crypto_.OnPassphraseAccepted(
      CustomPassphraseBootstrapToken::CreateFakeForTesting(1));

  ASSERT_THAT(crypto_.GetPassphraseType(),
              Eq(PassphraseType::kCustomPassphrase));

  // Recoverability should no longer be considered degraded.
  EXPECT_FALSE(crypto_.IsTrustedVaultRecoverabilityDegraded());
}

// Regression test for crbug.com/1475589.
TEST_F(SyncServiceCryptoTest,
       ShouldIgnoreDegradedRecoverabilityRequestCompletionAfterReset) {
  crypto_.OnPassphraseTypeChanged(PassphraseType::kTrustedVaultPassphrase,
                                  base::Time::Now());
  crypto_.SetSyncEngine(CoreAccountInfo(), &engine_);
  ASSERT_THAT(crypto_.GetPassphraseType(),
              Eq(PassphraseType::kTrustedVaultPassphrase));
  ASSERT_TRUE(crypto_.IsTrustedVaultKeyRequiredStateKnown());
  ASSERT_FALSE(crypto_.IsTrustedVaultKeyRequired());
  ASSERT_FALSE(crypto_.IsPassphraseRequired());

  // Reset all in-memory `crypto_` state, including engine pointer. Passphrase
  // type will remain kTrustedVaultPassphrase, because it is cached by delegate.
  crypto_.Reset();
  ASSERT_THAT(crypto_.GetPassphraseType(),
              Eq(PassphraseType::kTrustedVaultPassphrase));

  // There is an ongoing GetIsRecoverabilityRequest(), mimic its completion.
  // Main expectation: no crashes.
  EXPECT_TRUE(trusted_vault_client_.CompleteAllPendingRequests());
}

}  // namespace

}  // namespace syncer
