chromium/chromeos/ash/components/dbus/userdataauth/userdataauth_client_unittest.cc

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

#include "chromeos/ash/components/dbus/userdataauth/userdataauth_client.h"

#include <string>
#include <utility>

#include "base/memory/raw_ptr.h"
#include "base/test/task_environment.h"
#include "chromeos/ash/components/dbus/cryptohome/UserDataAuth.pb.h"
#include "dbus/mock_bus.h"
#include "dbus/mock_object_proxy.h"
#include "dbus/object_path.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/cryptohome/dbus-constants.h"

using ::testing::_;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::SaveArg;

namespace ash {

namespace {

// Runs |callback| with |response|. Needed due to ResponseCallback expecting a
// bare pointer rather than an std::unique_ptr.
void RunResponseCallback(dbus::ObjectProxy::ResponseCallback callback,
                         std::unique_ptr<dbus::Response> response) {
  std::move(callback).Run(response.get());
}

bool ProtobufEquals(const google::protobuf::MessageLite& a,
                    const google::protobuf::MessageLite& b) {
  std::string a_serialized, b_serialized;
  a.SerializeToString(&a_serialized);
  b.SerializeToString(&b_serialized);
  return a_serialized == b_serialized;
}

// Returns a PrepareAuthFactorProgress proto representing a success fingerprint
// enroll scan with the desired |percent_complete|.
::user_data_auth::PrepareAuthFactorProgress PrepareFpAuthFactorForAdd(
    int percent_complete) {
  ::user_data_auth::PrepareAuthFactorProgress progress;
  progress.set_purpose(::user_data_auth::PURPOSE_ADD_AUTH_FACTOR);
  auto* add_progress = progress.mutable_add_progress();
  add_progress->set_auth_factor_type(
      ::user_data_auth::AUTH_FACTOR_TYPE_FINGERPRINT);
  auto* enroll_progress = add_progress->mutable_biometrics_progress();
  enroll_progress->mutable_scan_result()->set_fingerprint_result(
      ::user_data_auth::FINGERPRINT_SCAN_RESULT_SUCCESS);
  enroll_progress->set_done(percent_complete == 100);
  enroll_progress->mutable_fingerprint_progress()->set_percent_complete(
      percent_complete);
  return progress;
}

// Returns a PrepareAuthFactorProgress proto representing a fingerprint auth
// scan.
::user_data_auth::PrepareAuthFactorProgress PrepareFpAuthFactorForAuth(
    ::user_data_auth::FingerprintScanResult result) {
  ::user_data_auth::PrepareAuthFactorProgress progress;
  progress.set_purpose(::user_data_auth::PURPOSE_AUTHENTICATE_AUTH_FACTOR);
  auto* auth_progress = progress.mutable_auth_progress();
  auth_progress->set_auth_factor_type(
      ::user_data_auth::AUTH_FACTOR_TYPE_FINGERPRINT);
  auto* result_progress = auth_progress->mutable_biometrics_progress();
  result_progress->mutable_scan_result()->set_fingerprint_result(result);
  return progress;
}

// Create a callback that would copy the input argument passed to it into |out|.
// This is used mostly to create a callback that would catch the reply from
// dbus.
template <typename T>
base::OnceCallback<void(T)> CreateCopyCallback(T* out) {
  return base::BindOnce([](T* out, T result) { *out = result; }, out);
}

class TestObserver : public UserDataAuthClient::Observer {
 public:
  // UserDataAuthClient::Observer overrides
  void LowDiskSpace(const ::user_data_auth::LowDiskSpace& status) override {
    last_low_disk_space_.CopyFrom(status);
    low_disk_space_count_++;
  }

  void DircryptoMigrationProgress(
      const ::user_data_auth::DircryptoMigrationProgress& progress) override {
    last_dircrypto_progress_.CopyFrom(progress);
    dircrypto_progress_count_++;
  }

  const ::user_data_auth::LowDiskSpace& last_low_disk_space() const {
    return last_low_disk_space_;
  }

  int low_disk_space_count() const { return low_disk_space_count_; }

  const ::user_data_auth::DircryptoMigrationProgress& last_dircrypto_progress()
      const {
    return last_dircrypto_progress_;
  }

  int dircrypto_progress_count() const { return dircrypto_progress_count_; }

 private:
  // The protobuf that came with the signal when the last low disk space event
  // came.
  ::user_data_auth::LowDiskSpace last_low_disk_space_;

  // The number of times the LowDiskSpace signal is triggered.
  int low_disk_space_count_ = 0;

  // The protobuf that came with the signal when the last dircrypto migration
  // progress event came.
  ::user_data_auth::DircryptoMigrationProgress last_dircrypto_progress_;

  // The number of times the DircryptoMigrationProgress signal is triggered.
  int dircrypto_progress_count_ = 0;
};

// The test observer that only observe fingerprint related progress messages.
class TestFingerprintObserver
    : public UserDataAuthClient::PrepareAuthFactorProgressObserver {
 public:
  void OnFingerprintAuthScan(
      const ::user_data_auth::AuthScanDone& result) override {
    last_auth_scan_done_.CopyFrom(result);
    auth_scan_done_count_++;
  }
  void OnFingerprintEnrollProgress(
      const ::user_data_auth::AuthEnrollmentProgress& progress) override {
    last_enroll_progress_.CopyFrom(progress);
    enroll_progress_count_++;
  }

  int auth_scan_done_count() const { return auth_scan_done_count_; }
  int enroll_progress_count() const { return enroll_progress_count_; }
  const ::user_data_auth::AuthScanDone& last_auth_scan_done() const {
    return last_auth_scan_done_;
  }
  const ::user_data_auth::AuthEnrollmentProgress& last_enroll_progress() const {
    return last_enroll_progress_;
  }

 private:
  int auth_scan_done_count_ = 0;
  int enroll_progress_count_ = 0;
  ::user_data_auth::AuthScanDone last_auth_scan_done_;
  ::user_data_auth::AuthEnrollmentProgress last_enroll_progress_;
};

// The test observer that only observe AuthFactorStatusUpdate messages.
class TestAuthFactorStatusUpdateObserver
    : public UserDataAuthClient::AuthFactorStatusUpdateObserver {
 public:
  void OnAuthFactorStatusUpdate(
      const ::user_data_auth::AuthFactorStatusUpdate& update) override {
    last_broadcast_id_ = update.broadcast_id();
    last_auth_factor_with_status_ = update.auth_factor_with_status();
  }
  const std::string& last_broadcast_id() const { return last_broadcast_id_; }
  const ::user_data_auth::AuthFactorWithStatus& last_auth_factor_with_status()
      const {
    return last_auth_factor_with_status_;
  }

 private:
  std::string last_broadcast_id_;
  ::user_data_auth::AuthFactorWithStatus last_auth_factor_with_status_;
};

}  // namespace

class UserDataAuthClientTest : public testing::Test {
 public:
  UserDataAuthClientTest() = default;
  ~UserDataAuthClientTest() override = default;

  void SetUp() override {
    dbus::Bus::Options options;
    options.bus_type = dbus::Bus::SYSTEM;
    bus_ = new dbus::MockBus(options);

    dbus::ObjectPath userdataauth_object_path =
        dbus::ObjectPath(::user_data_auth::kUserDataAuthServicePath);
    proxy_ = new dbus::MockObjectProxy(
        bus_.get(), ::user_data_auth::kUserDataAuthServiceName,
        userdataauth_object_path);

    // Makes sure `GetObjectProxy()` is caled with the correct service name and
    // path.
    EXPECT_CALL(*bus_.get(),
                GetObjectProxy(::user_data_auth::kUserDataAuthServiceName,
                               userdataauth_object_path))
        .WillRepeatedly(Return(proxy_.get()));

    EXPECT_CALL(*proxy_.get(), DoCallMethod(_, _, _))
        .WillRepeatedly(Invoke(this, &UserDataAuthClientTest::OnCallMethod));

    EXPECT_CALL(
        *proxy_,
        DoConnectToSignal(::user_data_auth::kUserDataAuthInterface,
                          ::user_data_auth::kDircryptoMigrationProgress, _, _))
        .WillOnce(SaveArg<2>(&dircrypto_progress_callback_));
    EXPECT_CALL(*proxy_,
                DoConnectToSignal(::user_data_auth::kUserDataAuthInterface,
                                  ::user_data_auth::kLowDiskSpace, _, _))
        .WillOnce(SaveArg<2>(&low_disk_space_callback_));

    EXPECT_CALL(*proxy_,
                DoConnectToSignal(
                    ::user_data_auth::kUserDataAuthInterface,
                    ::user_data_auth::kAuthEnrollmentProgressSignal, _, _))
        .WillOnce(SaveArg<2>(&auth_enrollment_callback_));
    EXPECT_CALL(*proxy_,
                DoConnectToSignal(
                    ::user_data_auth::kUserDataAuthInterface,
                    ::user_data_auth::kPrepareAuthFactorProgressSignal, _, _))
        .WillOnce(SaveArg<2>(&prepare_auth_factor_progress_callback_));

    EXPECT_CALL(*proxy_, DoConnectToSignal(
                             ::user_data_auth::kUserDataAuthInterface,
                             ::user_data_auth::kAuthFactorStatusUpdate, _, _))
        .WillOnce(SaveArg<2>(&auth_factor_status_update_callback_));

    UserDataAuthClient::Initialize(bus_.get());

    // Execute callbacks posted by `client_->Init()`.
    base::RunLoop().RunUntilIdle();

    client_ = UserDataAuthClient::Get();
  }

  void TearDown() override { UserDataAuthClient::Shutdown(); }

 protected:
  void EmitLowDiskSpaceSignal(const ::user_data_auth::LowDiskSpace& status) {
    dbus::Signal signal(::user_data_auth::kUserDataAuthInterface,
                        ::user_data_auth::kLowDiskSpace);
    dbus::MessageWriter writer(&signal);
    writer.AppendProtoAsArrayOfBytes(status);
    // Emit the signal.
    ASSERT_FALSE(low_disk_space_callback_.is_null());
    low_disk_space_callback_.Run(&signal);
  }

  void EmitDircryptoMigrationProgressSignal(
      const ::user_data_auth::DircryptoMigrationProgress& status) {
    dbus::Signal signal(::user_data_auth::kUserDataAuthInterface,
                        ::user_data_auth::kDircryptoMigrationProgress);
    dbus::MessageWriter writer(&signal);
    writer.AppendProtoAsArrayOfBytes(status);
    // Emit the signal.
    ASSERT_FALSE(dircrypto_progress_callback_.is_null());
    dircrypto_progress_callback_.Run(&signal);
  }

  void EmitPrepareAuthFactorProgressSignal(
      const ::user_data_auth::PrepareAuthFactorProgress& progress) {
    dbus::Signal signal(::user_data_auth::kUserDataAuthInterface,
                        ::user_data_auth::kPrepareAuthFactorProgressSignal);
    dbus::MessageWriter writer(&signal);
    writer.AppendProtoAsArrayOfBytes(progress);
    // Emit the signal.
    ASSERT_FALSE(prepare_auth_factor_progress_callback_.is_null());
    prepare_auth_factor_progress_callback_.Run(&signal);
  }

  void EmitAuthFactorStatusUpdateSignal(
      const ::user_data_auth::AuthFactorStatusUpdate& update) {
    dbus::Signal signal(::user_data_auth::kUserDataAuthInterface,
                        ::user_data_auth::kAuthFactorStatusUpdate);
    dbus::MessageWriter writer(&signal);
    writer.AppendProtoAsArrayOfBytes(update);
    // Emit the signal.
    ASSERT_FALSE(auth_factor_status_update_callback_.is_null());
    auth_factor_status_update_callback_.Run(&signal);
  }

  base::test::SingleThreadTaskEnvironment task_environment_;

  // Mock bus and proxy for simulating calls.
  scoped_refptr<dbus::MockBus> bus_;
  scoped_refptr<dbus::MockObjectProxy> proxy_;

  // Convenience pointer to the global instance.
  raw_ptr<UserDataAuthClient, DanglingUntriaged> client_;

  // The expected replies to the respective D-Bus calls.
  ::user_data_auth::IsMountedReply expected_is_mounted_reply_;
  ::user_data_auth::UnmountReply expected_unmount_reply_;
  ::user_data_auth::RemoveReply expected_remove_reply_;
  ::user_data_auth::StartMigrateToDircryptoReply
      expected_start_migrate_to_dircrypto_reply_;
  ::user_data_auth::NeedsDircryptoMigrationReply
      expected_needs_dircrypto_migration_reply_;
  ::user_data_auth::GetSupportedKeyPoliciesReply
      expected_get_supported_key_policies_reply_;
  ::user_data_auth::GetAccountDiskUsageReply
      expected_get_account_disk_usage_reply_;
  ::user_data_auth::StartAuthSessionReply expected_start_auth_session_reply_;

  // When it is set `true`, an invalid array of bytes that cannot be parsed will
  // be the response.
  bool shall_message_parsing_fail_ = false;

 private:
  // Handles calls to |proxy_|'s `CallMethod()`.
  void OnCallMethod(dbus::MethodCall* method_call,
                    int timeout_ms,
                    dbus::ObjectProxy::ResponseCallback* callback) {
    std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());
    dbus::MessageWriter writer(response.get());
    if (shall_message_parsing_fail_) {
      // 0x02 => Field 0, Type String
      // (0xFF)*6 => Varint, the size of the string, it is not terminated and is
      // a very large value so the parsing will fail.
      constexpr uint8_t invalid_protobuf[] = {0x02, 0xFF, 0xFF, 0xFF,
                                              0xFF, 0xFF, 0xFF};
      writer.AppendArrayOfBytes(invalid_protobuf);
    } else if (method_call->GetMember() == ::user_data_auth::kIsMounted) {
      writer.AppendProtoAsArrayOfBytes(expected_is_mounted_reply_);
    } else if (method_call->GetMember() == ::user_data_auth::kIsMounted) {
      writer.AppendProtoAsArrayOfBytes(expected_is_mounted_reply_);
    } else if (method_call->GetMember() == ::user_data_auth::kUnmount) {
      writer.AppendProtoAsArrayOfBytes(expected_unmount_reply_);
    } else if (method_call->GetMember() == ::user_data_auth::kRemove) {
      writer.AppendProtoAsArrayOfBytes(expected_remove_reply_);
    } else if (method_call->GetMember() ==
               ::user_data_auth::kStartMigrateToDircrypto) {
      writer.AppendProtoAsArrayOfBytes(
          expected_start_migrate_to_dircrypto_reply_);
    } else if (method_call->GetMember() ==
               ::user_data_auth::kNeedsDircryptoMigration) {
      writer.AppendProtoAsArrayOfBytes(
          expected_needs_dircrypto_migration_reply_);
    } else if (method_call->GetMember() ==
               ::user_data_auth::kGetSupportedKeyPolicies) {
      writer.AppendProtoAsArrayOfBytes(
          expected_get_supported_key_policies_reply_);
    } else if (method_call->GetMember() ==
               ::user_data_auth::kGetAccountDiskUsage) {
      writer.AppendProtoAsArrayOfBytes(expected_get_account_disk_usage_reply_);
    } else if (method_call->GetMember() ==
               ::user_data_auth::kStartAuthSession) {
      writer.AppendProtoAsArrayOfBytes(expected_start_auth_session_reply_);
    } else {
      ASSERT_FALSE(true) << "Unrecognized member: " << method_call->GetMember();
    }
    task_environment_.GetMainThreadTaskRunner()->PostTask(
        FROM_HERE, base::BindOnce(RunResponseCallback, std::move(*callback),
                                  std::move(response)));
  }

  // Callback that delivers the Low Disk Space signal to the client when called.
  dbus::ObjectProxy::SignalCallback low_disk_space_callback_;

  // Callback that delivers the dircrypto Migration Progress signal to the
  // client when called.
  dbus::ObjectProxy::SignalCallback dircrypto_progress_callback_;

  // Callback that delivers the cryptohome AuthEnrollmentProgress signal to the
  // client when called.
  dbus::ObjectProxy::SignalCallback auth_enrollment_callback_;

  // Callback that delivers the cryptohome AuthEnrollmentProgress signal to the
  // client when called.
  dbus::ObjectProxy::SignalCallback prepare_auth_factor_progress_callback_;

  // Callback that delivers the cryptohome AuthFactorStatusUpdate signal to the
  // client when called.
  dbus::ObjectProxy::SignalCallback auth_factor_status_update_callback_;
};

TEST_F(UserDataAuthClientTest, IsMounted) {
  expected_is_mounted_reply_.set_is_mounted(true);
  expected_is_mounted_reply_.set_is_ephemeral_mount(false);
  std::optional<::user_data_auth::IsMountedReply> result_reply = std::nullopt;
  auto callback = base::BindOnce(
      [](std::optional<::user_data_auth::IsMountedReply>* result_reply,
         std::optional<::user_data_auth::IsMountedReply> reply) {
        *result_reply = reply;
      },
      &result_reply);

  client_->IsMounted(::user_data_auth::IsMountedRequest(), std::move(callback));
  base::RunLoop().RunUntilIdle();
  ASSERT_NE(result_reply, std::nullopt);
  EXPECT_TRUE(result_reply.value().is_mounted());
  EXPECT_FALSE(result_reply.value().is_ephemeral_mount());
}

TEST_F(UserDataAuthClientTest, IsMountedInvalidProtobuf) {
  shall_message_parsing_fail_ = true;
  std::optional<::user_data_auth::IsMountedReply> result_reply =
      ::user_data_auth::IsMountedReply();
  auto callback = base::BindOnce(
      [](std::optional<::user_data_auth::IsMountedReply>* result_reply,
         std::optional<::user_data_auth::IsMountedReply> reply) {
        *result_reply = reply;
      },
      &result_reply);

  client_->IsMounted(::user_data_auth::IsMountedRequest(), std::move(callback));
  base::RunLoop().RunUntilIdle();
  ASSERT_EQ(result_reply, std::nullopt);
}

TEST_F(UserDataAuthClientTest, Unmount) {
  expected_unmount_reply_.set_error(
      user_data_auth::CryptohomeErrorCode::CRYPTOHOME_ERROR_TPM_DEFEND_LOCK);
  std::optional<::user_data_auth::UnmountReply> result_reply;

  client_->Unmount(::user_data_auth::UnmountRequest(),
                   CreateCopyCallback(&result_reply));
  base::RunLoop().RunUntilIdle();
  ASSERT_NE(result_reply, std::nullopt);
  EXPECT_TRUE(ProtobufEquals(result_reply.value(), expected_unmount_reply_));
}

TEST_F(UserDataAuthClientTest, Remove) {
  expected_remove_reply_.set_error(
      user_data_auth::CryptohomeErrorCode::CRYPTOHOME_ERROR_TPM_DEFEND_LOCK);
  std::optional<::user_data_auth::RemoveReply> result_reply;

  client_->Remove(::user_data_auth::RemoveRequest(),
                  CreateCopyCallback(&result_reply));
  base::RunLoop().RunUntilIdle();
  ASSERT_NE(result_reply, std::nullopt);
  EXPECT_TRUE(ProtobufEquals(result_reply.value(), expected_remove_reply_));
}

TEST_F(UserDataAuthClientTest, StartMigrateToDircrypto) {
  expected_start_migrate_to_dircrypto_reply_.set_error(
      user_data_auth::CryptohomeErrorCode::CRYPTOHOME_ERROR_TPM_DEFEND_LOCK);
  std::optional<::user_data_auth::StartMigrateToDircryptoReply> result_reply;

  client_->StartMigrateToDircrypto(
      ::user_data_auth::StartMigrateToDircryptoRequest(),
      CreateCopyCallback(&result_reply));
  base::RunLoop().RunUntilIdle();
  ASSERT_NE(result_reply, std::nullopt);
  EXPECT_TRUE(ProtobufEquals(result_reply.value(),
                             expected_start_migrate_to_dircrypto_reply_));
}

TEST_F(UserDataAuthClientTest, NeedsDircryptoMigration) {
  expected_needs_dircrypto_migration_reply_.set_error(
      user_data_auth::CryptohomeErrorCode::CRYPTOHOME_ERROR_TPM_DEFEND_LOCK);
  std::optional<::user_data_auth::NeedsDircryptoMigrationReply> result_reply;

  client_->NeedsDircryptoMigration(
      ::user_data_auth::NeedsDircryptoMigrationRequest(),
      CreateCopyCallback(&result_reply));
  base::RunLoop().RunUntilIdle();
  ASSERT_NE(result_reply, std::nullopt);
  EXPECT_TRUE(ProtobufEquals(result_reply.value(),
                             expected_needs_dircrypto_migration_reply_));
}

TEST_F(UserDataAuthClientTest, GetSupportedKeyPolicies) {
  expected_get_supported_key_policies_reply_
      .set_low_entropy_credentials_supported(true);
  std::optional<::user_data_auth::GetSupportedKeyPoliciesReply> result_reply;

  client_->GetSupportedKeyPolicies(
      ::user_data_auth::GetSupportedKeyPoliciesRequest(),
      CreateCopyCallback(&result_reply));
  base::RunLoop().RunUntilIdle();
  ASSERT_NE(result_reply, std::nullopt);
  EXPECT_TRUE(ProtobufEquals(result_reply.value(),
                             expected_get_supported_key_policies_reply_));
}

TEST_F(UserDataAuthClientTest, GetAccountDiskUsage) {
  expected_get_account_disk_usage_reply_.set_error(
      user_data_auth::CryptohomeErrorCode::CRYPTOHOME_ERROR_TPM_DEFEND_LOCK);
  std::optional<::user_data_auth::GetAccountDiskUsageReply> result_reply;

  client_->GetAccountDiskUsage(::user_data_auth::GetAccountDiskUsageRequest(),
                               CreateCopyCallback(&result_reply));
  base::RunLoop().RunUntilIdle();
  ASSERT_NE(result_reply, std::nullopt);
  EXPECT_TRUE(ProtobufEquals(result_reply.value(),
                             expected_get_account_disk_usage_reply_));
}

TEST_F(UserDataAuthClientTest, StartAuthSession) {
  expected_start_auth_session_reply_.set_error(
      user_data_auth::CryptohomeErrorCode::CRYPTOHOME_ERROR_TPM_DEFEND_LOCK);
  std::optional<::user_data_auth::StartAuthSessionReply> result_reply;

  client_->StartAuthSession(::user_data_auth::StartAuthSessionRequest(),
                            CreateCopyCallback(&result_reply));
  base::RunLoop().RunUntilIdle();
  ASSERT_NE(result_reply, std::nullopt);
  EXPECT_TRUE(
      ProtobufEquals(result_reply.value(), expected_start_auth_session_reply_));
}

TEST_F(UserDataAuthClientTest, LowDiskSpaceSignal) {
  constexpr uint64_t kFreeSpace1 = 0x1234567890123ULL;
  constexpr uint64_t kFreeSpace2 = 0xFFFF9876ULL;

  TestObserver observer;
  client_->AddObserver(&observer);

  ::user_data_auth::LowDiskSpace status;
  status.set_disk_free_bytes(kFreeSpace1);

  // Basic validity check, emit a signal and check.
  EmitLowDiskSpaceSignal(status);
  EXPECT_EQ(observer.low_disk_space_count(), 1);
  EXPECT_EQ(observer.last_low_disk_space().disk_free_bytes(), kFreeSpace1);

  // Try again to see nothing is stuck.
  status.set_disk_free_bytes(kFreeSpace2);
  EmitLowDiskSpaceSignal(status);
  EXPECT_EQ(observer.low_disk_space_count(), 2);
  EXPECT_EQ(observer.last_low_disk_space().disk_free_bytes(), kFreeSpace2);

  // Remove the observer to check that it no longer gets triggered.
  client_->RemoveObserver(&observer);
  EmitLowDiskSpaceSignal(status);
  EXPECT_EQ(observer.low_disk_space_count(), 2);
}

TEST_F(UserDataAuthClientTest, DircryptoMigrationProgressSignal) {
  // Prepare the test constants.
  constexpr uint64_t kTotalBytes = 0x1234567890123ULL;
  ::user_data_auth::DircryptoMigrationProgress progress1, progress2;
  progress1.set_status(::user_data_auth::DircryptoMigrationStatus::
                           DIRCRYPTO_MIGRATION_IN_PROGRESS);
  progress1.set_current_bytes(12345);
  progress1.set_total_bytes(kTotalBytes);
  progress2.set_status(
      ::user_data_auth::DircryptoMigrationStatus::DIRCRYPTO_MIGRATION_SUCCESS);
  progress2.set_current_bytes(kTotalBytes);
  progress2.set_total_bytes(kTotalBytes);

  TestObserver observer;
  client_->AddObserver(&observer);

  // Basic validity check, emit a signal and check.
  EmitDircryptoMigrationProgressSignal(progress1);
  EXPECT_EQ(observer.dircrypto_progress_count(), 1);
  EXPECT_TRUE(ProtobufEquals(observer.last_dircrypto_progress(), progress1));

  // Try again to see nothing is stuck.
  EmitDircryptoMigrationProgressSignal(progress2);
  EXPECT_EQ(observer.dircrypto_progress_count(), 2);
  EXPECT_TRUE(ProtobufEquals(observer.last_dircrypto_progress(), progress2));

  // Remove the observer to check that it no longer gets triggered.
  client_->RemoveObserver(&observer);
  EmitDircryptoMigrationProgressSignal(progress1);
  EXPECT_EQ(observer.dircrypto_progress_count(), 2);
}

TEST_F(UserDataAuthClientTest, PrepareAuthFactorForAddProgressSignal) {
  TestFingerprintObserver observer;
  client_->AddPrepareAuthFactorProgressObserver(&observer);

  ::user_data_auth::PrepareAuthFactorProgress progress1 =
      PrepareFpAuthFactorForAdd(50);
  ::user_data_auth::PrepareAuthFactorProgress progress2 =
      PrepareFpAuthFactorForAdd(100);

  // Basic validity check, emit a signal and check.
  EmitPrepareAuthFactorProgressSignal(progress1);
  EXPECT_EQ(observer.enroll_progress_count(), 1);
  EXPECT_EQ(
      observer.last_enroll_progress().fingerprint_progress().percent_complete(),
      50);
  EXPECT_EQ(observer.last_enroll_progress().done(), false);

  // Try again to see nothing is stuck.
  EmitPrepareAuthFactorProgressSignal(progress2);
  EXPECT_EQ(observer.enroll_progress_count(), 2);
  EXPECT_EQ(
      observer.last_enroll_progress().fingerprint_progress().percent_complete(),
      100);
  EXPECT_EQ(observer.last_enroll_progress().done(), true);

  // Remove the observer to check that it no longer gets triggered.
  client_->RemovePrepareAuthFactorProgressObserver(&observer);
  EmitPrepareAuthFactorProgressSignal(progress2);
  EXPECT_EQ(observer.enroll_progress_count(), 2);
}

TEST_F(UserDataAuthClientTest, PrepareAuthFactorForAuthenticateProgressSignal) {
  TestFingerprintObserver observer;
  client_->AddPrepareAuthFactorProgressObserver(&observer);

  ::user_data_auth::PrepareAuthFactorProgress progress1 =
      PrepareFpAuthFactorForAuth(
          ::user_data_auth::FINGERPRINT_SCAN_RESULT_SUCCESS);
  ::user_data_auth::PrepareAuthFactorProgress progress2 =
      PrepareFpAuthFactorForAuth(
          ::user_data_auth::FINGERPRINT_SCAN_RESULT_LOCKOUT);

  // Basic validity check, emit a signal and check.
  EmitPrepareAuthFactorProgressSignal(progress1);
  EXPECT_EQ(observer.auth_scan_done_count(), 1);
  EXPECT_EQ(observer.last_auth_scan_done().scan_result().fingerprint_result(),
            ::user_data_auth::FINGERPRINT_SCAN_RESULT_SUCCESS);

  // Try again to see nothing is stuck.
  EmitPrepareAuthFactorProgressSignal(progress2);
  EXPECT_EQ(observer.auth_scan_done_count(), 2);
  EXPECT_EQ(observer.last_auth_scan_done().scan_result().fingerprint_result(),
            ::user_data_auth::FINGERPRINT_SCAN_RESULT_LOCKOUT);

  // Remove the observer to check that it no longer gets triggered.
  client_->RemovePrepareAuthFactorProgressObserver(&observer);
  EmitPrepareAuthFactorProgressSignal(progress2);
  EXPECT_EQ(observer.auth_scan_done_count(), 2);
}

TEST_F(UserDataAuthClientTest, AuthFactorStatusUpdateSignal) {
  TestAuthFactorStatusUpdateObserver observer;
  client_->AddAuthFactorStatusUpdateObserver(&observer);

  ::user_data_auth::AuthFactorStatusUpdate update1;
  update1.set_broadcast_id("id1");
  update1.mutable_auth_factor_with_status()->mutable_auth_factor()->set_type(
      ::user_data_auth::AUTH_FACTOR_TYPE_PASSWORD);
  update1.mutable_auth_factor_with_status()->mutable_auth_factor()->set_label(
      "password");

  // Basic validity check, emit a signal and check.
  EmitAuthFactorStatusUpdateSignal(update1);
  EXPECT_EQ(observer.last_broadcast_id(), "id1");
  EXPECT_EQ(observer.last_auth_factor_with_status().auth_factor().label(),
            "password");
  EXPECT_EQ(observer.last_auth_factor_with_status().auth_factor().type(),
            ::user_data_auth::AUTH_FACTOR_TYPE_PASSWORD);

  // Try again to see nothing is stuck.
  ::user_data_auth::AuthFactorStatusUpdate update2;
  update2.set_broadcast_id("id2");
  update2.mutable_auth_factor_with_status()->mutable_auth_factor()->set_type(
      ::user_data_auth::AUTH_FACTOR_TYPE_PIN);
  update2.mutable_auth_factor_with_status()->mutable_auth_factor()->set_label(
      "pin");
  EmitAuthFactorStatusUpdateSignal(update2);
  EXPECT_EQ(observer.last_broadcast_id(), "id2");
  EXPECT_EQ(observer.last_auth_factor_with_status().auth_factor().label(),
            "pin");
  EXPECT_EQ(observer.last_auth_factor_with_status().auth_factor().type(),
            ::user_data_auth::AUTH_FACTOR_TYPE_PIN);

  // Remove the observer to check that it no longer gets triggered.
  client_->RemoveAuthFactorStatusUpdateObserver(&observer);
  EmitAuthFactorStatusUpdateSignal(update1);
  EXPECT_EQ(observer.last_broadcast_id(), "id2");
}

}  // namespace ash