chromium/services/device/fingerprint/fingerprint_chromeos_unittest.cc

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

#include "services/device/fingerprint/fingerprint_chromeos.h"

#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "chromeos/ash/components/dbus/biod/fake_biod_client.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace device {

class FakeFingerprintObserver : public mojom::FingerprintObserver {
 public:
  explicit FakeFingerprintObserver(
      mojo::PendingReceiver<mojom::FingerprintObserver> receiver)
      : receiver_(this, std::move(receiver)) {}

  FakeFingerprintObserver(const FakeFingerprintObserver&) = delete;
  FakeFingerprintObserver& operator=(const FakeFingerprintObserver&) = delete;

  ~FakeFingerprintObserver() override {}

  // mojom::FingerprintObserver
  void OnRestarted() override { restarts_++; }
  void OnStatusChanged(device::mojom::BiometricsManagerStatus status) override {
    DCHECK_EQ(status, device::mojom::BiometricsManagerStatus::INITIALIZED);
    status_changes_++;
  }

  void OnEnrollScanDone(device::mojom::ScanResult scan_result,
                        bool is_complete,
                        int percent_complete) override {
    enroll_scan_dones_++;
  }

  void OnAuthScanDone(
      const device::mojom::FingerprintMessagePtr msg,
      const base::flat_map<std::string, std::vector<std::string>>& matches)
      override {
    auth_scan_dones_++;
    last_message_ = *msg;
  }

  void OnSessionFailed() override { session_failures_++; }

  // Test status counts.
  int enroll_scan_dones() { return enroll_scan_dones_; }
  int auth_scan_dones() { return auth_scan_dones_; }
  int restarts() { return restarts_; }
  int session_failures() { return session_failures_; }

  const device::mojom::FingerprintMessage& last_message() const {
    return last_message_;
  }

 private:
  mojo::Receiver<mojom::FingerprintObserver> receiver_;
  int enroll_scan_dones_ = 0;  // Count of enroll scan done signal received.
  int auth_scan_dones_ = 0;    // Count of auth scan done signal received.
  int restarts_ = 0;           // Count of restart signal received.
  int status_changes_ = 0;     // Count of StatusChanged signal received.
  int session_failures_ = 0;   // Count of session failed signal received.

  device::mojom::FingerprintMessage
      last_message_;  // Last received FingerprintMessage.
};

class FingerprintChromeOSTest : public testing::Test {
 public:
  FingerprintChromeOSTest() = default;

  FingerprintChromeOSTest(const FingerprintChromeOSTest&) = delete;
  FingerprintChromeOSTest& operator=(const FingerprintChromeOSTest&) = delete;

  ~FingerprintChromeOSTest() override = default;

  void SetUp() override {
    ash::BiodClient::InitializeFake();
    fingerprint_ = base::WrapUnique(new FingerprintChromeOS());
  }

  void TearDown() override {
    fingerprint_.reset();
    ash::BiodClient::Shutdown();
  }

  FingerprintChromeOS* fingerprint() { return fingerprint_.get(); }

  void GenerateRestartSignal() { fingerprint_->BiodServiceRestarted(); }

  void GenerateSessionStateSignal() {
    fingerprint_->BiodServiceStatusChanged(
        biod::BiometricsManagerStatus::INITIALIZED);
  }

  void GenerateEnrollScanDoneSignal() {
    std::string fake_fingerprint_data;
    ash::FakeBiodClient::Get()->SendEnrollScanDone(
        fake_fingerprint_data, biod::SCAN_RESULT_SUCCESS, true,
        -1 /* percent_complete */);
  }

  void GenerateAuthScanDoneSignal(const biod::FingerprintMessage& msg) {
    std::string fake_fingerprint_data;
    ash::FakeBiodClient::Get()->SendAuthScanDone(fake_fingerprint_data, msg);
  }

  void GenerateSessionFailedSignal() {
    ash::FakeBiodClient::Get()->SendSessionFailed();
  }

  void onStartSession(const dbus::ObjectPath& path) {}

  void SimulateRequestRunning(bool is_running) {
    fingerprint_->is_request_running_ = is_running;
    if (!is_running)
      fingerprint_->StartNextRequest();
  }

  bool RequestDataIsReset() {
    return fingerprint_->records_path_to_label_.empty() &&
           !fingerprint_->on_get_records_;
  }

  void GenerateGetRecordsForUserRequest(int num_of_request) {
    for (int i = 0; i < num_of_request; i++) {
      fingerprint_->GetRecordsForUser(
          "" /*user_id*/, base::BindOnce(&FingerprintChromeOSTest::OnGetRecords,
                                         base::Unretained(this)));
    }
  }

  void OnGetRecords(
      const base::flat_map<std::string, std::string>& fingerprints_list_mapping,
      bool success) {
    ++get_records_results_;
  }

  int GetPendingRequests() {
    return fingerprint_->get_records_pending_requests_.size();
  }

  bool IsRequestRunning() { return fingerprint_->is_request_running_; }
  int get_records_results() { return get_records_results_; }

 private:
  base::test::SingleThreadTaskEnvironment task_environment_;
  std::unique_ptr<FingerprintChromeOS> fingerprint_;
  int get_records_results_ = 0;
};

TEST_F(FingerprintChromeOSTest, FingerprintObserverTest) {
  mojo::PendingRemote<mojom::FingerprintObserver> pending_observer;
  FakeFingerprintObserver observer(
      pending_observer.InitWithNewPipeAndPassReceiver());
  fingerprint()->AddFingerprintObserver(std::move(pending_observer));

  GenerateRestartSignal();
  GenerateSessionStateSignal();
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(observer.restarts(), 1);

  std::string user_id;
  std::string label;
  ash::FakeBiodClient::Get()->StartEnrollSession(
      user_id, label,
      base::BindOnce(&FingerprintChromeOSTest::onStartSession,
                     base::Unretained(this)));
  base::RunLoop().RunUntilIdle();
  GenerateEnrollScanDoneSignal();
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(observer.enroll_scan_dones(), 1);

  biod::FingerprintMessage msg;
  ash::FakeBiodClient::Get()->StartAuthSession(base::BindOnce(
      &FingerprintChromeOSTest::onStartSession, base::Unretained(this)));
  base::RunLoop().RunUntilIdle();
  msg.set_scan_result(biod::SCAN_RESULT_SUCCESS);
  GenerateAuthScanDoneSignal(msg);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(observer.auth_scan_dones(), 1);

  GenerateSessionFailedSignal();
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(observer.session_failures(), 1);
}

TEST_F(FingerprintChromeOSTest, SimultaneousGetRecordsRequests) {
  EXPECT_EQ(GetPendingRequests(), 0);
  EXPECT_FALSE(IsRequestRunning());

  // Single request.
  GenerateGetRecordsForUserRequest(1);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(get_records_results(), 1);
  EXPECT_FALSE(IsRequestRunning());
  EXPECT_EQ(GetPendingRequests(), 0);
  EXPECT_TRUE(RequestDataIsReset());

  // Multiple requests at the same time.
  SimulateRequestRunning(true);
  GenerateGetRecordsForUserRequest(5);
  EXPECT_EQ(GetPendingRequests(), 5);
  SimulateRequestRunning(false);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(get_records_results(), 6);
  EXPECT_FALSE(IsRequestRunning());
  EXPECT_EQ(GetPendingRequests(), 0);
  EXPECT_TRUE(RequestDataIsReset());
}

TEST_F(FingerprintChromeOSTest, FingerprintScanResultConvertTest) {
  mojo::PendingRemote<mojom::FingerprintObserver> pending_observer;
  FakeFingerprintObserver observer(
      pending_observer.InitWithNewPipeAndPassReceiver());
  fingerprint()->AddFingerprintObserver(std::move(pending_observer));

  ash::FakeBiodClient::Get()->StartAuthSession(base::BindOnce(
      &FingerprintChromeOSTest::onStartSession, base::Unretained(this)));
  base::RunLoop().RunUntilIdle();

  biod::FingerprintMessage msg;
  msg.set_scan_result(biod::SCAN_RESULT_SUCCESS);
  GenerateAuthScanDoneSignal(msg);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(observer.last_message().which(),
            device::mojom::FingerprintMessage::Tag::kScanResult);
  EXPECT_EQ(observer.last_message().get_scan_result(),
            device::mojom::ScanResult::SUCCESS);

  msg.set_scan_result(biod::SCAN_RESULT_PARTIAL);
  GenerateAuthScanDoneSignal(msg);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(observer.last_message().which(),
            device::mojom::FingerprintMessage::Tag::kScanResult);
  EXPECT_EQ(observer.last_message().get_scan_result(),
            device::mojom::ScanResult::PARTIAL);

  msg.set_scan_result(biod::SCAN_RESULT_INSUFFICIENT);
  GenerateAuthScanDoneSignal(msg);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(observer.last_message().which(),
            device::mojom::FingerprintMessage::Tag::kScanResult);
  EXPECT_EQ(observer.last_message().get_scan_result(),
            device::mojom::ScanResult::INSUFFICIENT);

  msg.set_scan_result(biod::SCAN_RESULT_SENSOR_DIRTY);
  GenerateAuthScanDoneSignal(msg);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(observer.last_message().which(),
            device::mojom::FingerprintMessage::Tag::kScanResult);
  EXPECT_EQ(observer.last_message().get_scan_result(),
            device::mojom::ScanResult::SENSOR_DIRTY);

  msg.set_scan_result(biod::SCAN_RESULT_TOO_SLOW);
  GenerateAuthScanDoneSignal(msg);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(observer.last_message().which(),
            device::mojom::FingerprintMessage::Tag::kScanResult);
  EXPECT_EQ(observer.last_message().get_scan_result(),
            device::mojom::ScanResult::TOO_SLOW);

  msg.set_scan_result(biod::SCAN_RESULT_TOO_FAST);
  GenerateAuthScanDoneSignal(msg);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(observer.last_message().which(),
            device::mojom::FingerprintMessage::Tag::kScanResult);
  EXPECT_EQ(observer.last_message().get_scan_result(),
            device::mojom::ScanResult::TOO_FAST);

  msg.set_scan_result(biod::SCAN_RESULT_IMMOBILE);
  GenerateAuthScanDoneSignal(msg);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(observer.last_message().which(),
            device::mojom::FingerprintMessage::Tag::kScanResult);
  EXPECT_EQ(observer.last_message().get_scan_result(),
            device::mojom::ScanResult::IMMOBILE);

  msg.set_scan_result(biod::SCAN_RESULT_NO_MATCH);
  GenerateAuthScanDoneSignal(msg);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(observer.last_message().which(),
            device::mojom::FingerprintMessage::Tag::kScanResult);
  EXPECT_EQ(observer.last_message().get_scan_result(),
            device::mojom::ScanResult::NO_MATCH);
}

// Make sure that compilation fails if a new value is added and this assert is
// not updated. When updating this, please extend unit tests to check newly
// added value.
static_assert(device::mojom::ScanResult::kMaxValue ==
              device::mojom::ScanResult::NO_MATCH);

TEST_F(FingerprintChromeOSTest, FingerprintErrorConvertTest) {
  mojo::PendingRemote<mojom::FingerprintObserver> pending_observer;
  FakeFingerprintObserver observer(
      pending_observer.InitWithNewPipeAndPassReceiver());
  fingerprint()->AddFingerprintObserver(std::move(pending_observer));

  ash::FakeBiodClient::Get()->StartAuthSession(base::BindOnce(
      &FingerprintChromeOSTest::onStartSession, base::Unretained(this)));
  base::RunLoop().RunUntilIdle();

  biod::FingerprintMessage msg;
  msg.set_error(biod::ERROR_HW_UNAVAILABLE);
  GenerateAuthScanDoneSignal(msg);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(observer.last_message().which(),
            device::mojom::FingerprintMessage::Tag::kFingerprintError);
  EXPECT_EQ(observer.last_message().get_fingerprint_error(),
            device::mojom::FingerprintError::HW_UNAVAILABLE);

  msg.set_error(biod::ERROR_UNABLE_TO_PROCESS);
  GenerateAuthScanDoneSignal(msg);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(observer.last_message().which(),
            device::mojom::FingerprintMessage::Tag::kFingerprintError);
  EXPECT_EQ(observer.last_message().get_fingerprint_error(),
            device::mojom::FingerprintError::UNABLE_TO_PROCESS);

  msg.set_error(biod::ERROR_TIMEOUT);
  GenerateAuthScanDoneSignal(msg);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(observer.last_message().which(),
            device::mojom::FingerprintMessage::Tag::kFingerprintError);
  EXPECT_EQ(observer.last_message().get_fingerprint_error(),
            device::mojom::FingerprintError::TIMEOUT);

  msg.set_error(biod::ERROR_NO_SPACE);
  GenerateAuthScanDoneSignal(msg);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(observer.last_message().which(),
            device::mojom::FingerprintMessage::Tag::kFingerprintError);
  EXPECT_EQ(observer.last_message().get_fingerprint_error(),
            device::mojom::FingerprintError::NO_SPACE);

  msg.set_error(biod::ERROR_CANCELED);
  GenerateAuthScanDoneSignal(msg);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(observer.last_message().which(),
            device::mojom::FingerprintMessage::Tag::kFingerprintError);
  EXPECT_EQ(observer.last_message().get_fingerprint_error(),
            device::mojom::FingerprintError::CANCELED);

  msg.set_error(biod::ERROR_UNABLE_TO_REMOVE);
  GenerateAuthScanDoneSignal(msg);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(observer.last_message().which(),
            device::mojom::FingerprintMessage::Tag::kFingerprintError);
  EXPECT_EQ(observer.last_message().get_fingerprint_error(),
            device::mojom::FingerprintError::UNABLE_TO_REMOVE);

  msg.set_error(biod::ERROR_LOCKOUT);
  GenerateAuthScanDoneSignal(msg);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(observer.last_message().which(),
            device::mojom::FingerprintMessage::Tag::kFingerprintError);
  EXPECT_EQ(observer.last_message().get_fingerprint_error(),
            device::mojom::FingerprintError::LOCKOUT);

  msg.set_error(biod::ERROR_NO_TEMPLATES);
  GenerateAuthScanDoneSignal(msg);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(observer.last_message().which(),
            device::mojom::FingerprintMessage::Tag::kFingerprintError);
  EXPECT_EQ(observer.last_message().get_fingerprint_error(),
            device::mojom::FingerprintError::NO_TEMPLATES);
}

// Make sure that compilation fails if a new value is added and this assert is
// not updated. When updating this, please extend unit tests to check newly
// added value.
static_assert(device::mojom::FingerprintError::kMaxValue ==
              device::mojom::FingerprintError::NO_TEMPLATES);

}  // namespace device