chromium/ash/quick_pair/pairing/retroactive_pairing_detector_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 "ash/quick_pair/pairing/retroactive_pairing_detector.h"

#include <memory>
#include <optional>

#include "ash/constants/ash_features.h"
#include "ash/quick_pair/common/constants.h"
#include "ash/quick_pair/common/device.h"
#include "ash/quick_pair/common/fake_bluetooth_adapter.h"
#include "ash/quick_pair/common/logging.h"
#include "ash/quick_pair/common/pair_failure.h"
#include "ash/quick_pair/common/protocol.h"
#include "ash/quick_pair/fast_pair_handshake/fake_fast_pair_gatt_service_client.h"
#include "ash/quick_pair/fast_pair_handshake/fast_pair_gatt_service_client_impl.h"
#include "ash/quick_pair/fast_pair_handshake/fast_pair_gatt_service_client_lookup_impl.h"
#include "ash/quick_pair/message_stream/fake_bluetooth_socket.h"
#include "ash/quick_pair/message_stream/fake_message_stream_lookup.h"
#include "ash/quick_pair/message_stream/message_stream.h"
#include "ash/quick_pair/message_stream/message_stream_lookup.h"
#include "ash/quick_pair/pairing/mock_pairer_broker.h"
#include "ash/quick_pair/pairing/pairer_broker.h"
#include "ash/quick_pair/pairing/retroactive_pairing_detector_impl.h"
#include "ash/quick_pair/proto/fastpair.pb.h"
#include "ash/quick_pair/repository/fake_fast_pair_repository.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "chromeos/ash/services/quick_pair/fast_pair_data_parser.h"
#include "chromeos/ash/services/quick_pair/mock_quick_pair_process_manager.h"
#include "chromeos/ash/services/quick_pair/quick_pair_process.h"
#include "chromeos/ash/services/quick_pair/quick_pair_process_manager.h"
#include "chromeos/ash/services/quick_pair/quick_pair_process_manager_impl.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/floss/floss_features.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "device/bluetooth/test/mock_bluetooth_device.h"
#include "mojo/public/cpp/bindings/shared_remote.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

constexpr base::TimeDelta kRetroactiveDevicePairingTimeout = base::Seconds(60);
constexpr char kTestDeviceAddress[] = "11:12:13:14:15:16";
constexpr char kTestDeviceAddress2[] = "11:12:13:14:15:17";
constexpr char kTestDeviceAddress3[] = "11:12:13:14:15:27";
constexpr char kTestBleDeviceName[] = "Test Device Name";
constexpr char kValidModelId[] = "718c17";
const std::string kUserEmail = "[email protected]";

const std::vector<uint8_t> kModelIdBytes = {
    /*message_group=*/0x03,
    /*message_code=*/0x01,
    /*additional_data_length=*/0x00, 0x03,
    /*additional_data=*/0xAA,        0xBB, 0xCC};
const std::vector<uint8_t> kModelIdBytesNoMetadata = {0xAA, 0xBB, 0xCC};
const std::string kModelId = "AABBCC";

const std::vector<uint8_t> kBleAddressBytes = {
    /*message_group=*/0x03,
    /*message_code=*/0x02,
    /*additional_data_length=*/0x00,
    0x06,
    /*additional_data=*/0xAA,
    0xBB,
    0xCC,
    0xDD,
    0xEE,
    0xFF};
const std::string kBleAddress = "AA:BB:CC:DD:EE:FF";

const std::vector<uint8_t> kModelIdBleAddressBytes = {
    /*mesage_group=*/0x03,
    /*mesage_code=*/0x01,
    /*additional_data_length=*/0x00,
    0x03,
    /*additional_data=*/0xAA,
    0xBB,
    0xCC,
    /*message_group=*/0x03,
    /*message_code=*/0x02,
    /*additional_data_length=*/0x00,
    0x06,
    /*additional_data=*/0xAA,
    0xBB,
    0xCC,
    0xDD,
    0xEE,
    0xFF};

std::unique_ptr<testing::NiceMock<device::MockBluetoothDevice>>
CreateTestBluetoothDevice(std::string address) {
  return std::make_unique<testing::NiceMock<device::MockBluetoothDevice>>(
      /*adapter=*/nullptr, /*bluetooth_class=*/0, kTestBleDeviceName, address,
      /*paired=*/true, /*connected=*/false);
}

class FakeFastPairGattServiceClientImplFactory
    : public ash::quick_pair::FastPairGattServiceClientImpl::Factory {
 public:
  ~FakeFastPairGattServiceClientImplFactory() override = default;

  ash::quick_pair::FakeFastPairGattServiceClient*
  fake_fast_pair_gatt_service_client() {
    return fake_fast_pair_gatt_service_client_;
  }

 private:
  // FastPairGattServiceClientImpl::Factory:
  std::unique_ptr<ash::quick_pair::FastPairGattServiceClient> CreateInstance(
      device::BluetoothDevice* device,
      scoped_refptr<device::BluetoothAdapter> adapter,
      base::OnceCallback<void(std::optional<ash::quick_pair::PairFailure>)>
          on_initialized_callback) override {
    auto fake_fast_pair_gatt_service_client =
        std::make_unique<ash::quick_pair::FakeFastPairGattServiceClient>(
            device, adapter, std::move(on_initialized_callback));
    fake_fast_pair_gatt_service_client_ =
        fake_fast_pair_gatt_service_client.get();
    return fake_fast_pair_gatt_service_client;
  }

  raw_ptr<ash::quick_pair::FakeFastPairGattServiceClient, DanglingUntriaged>
      fake_fast_pair_gatt_service_client_ = nullptr;
};

}  // namespace

namespace ash {
namespace quick_pair {

class RetroactivePairingDetectorTest
    : public AshTestBase,
      public RetroactivePairingDetector::Observer {
 public:
  RetroactivePairingDetectorTest()
      : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}

  void SetUp() override {
    AshTestBase::SetUp();
    FastPairGattServiceClientImpl::Factory::SetFactoryForTesting(
        &fast_pair_gatt_service_factory_);

    adapter_ = base::MakeRefCounted<FakeBluetoothAdapter>();
    device::BluetoothAdapterFactory::SetAdapterForTesting(adapter_);

    fast_pair_repository_ = std::make_unique<FakeFastPairRepository>();
    pairer_broker_ = std::make_unique<MockPairerBroker>();
    mock_pairer_broker_ = static_cast<MockPairerBroker*>(pairer_broker_.get());

    message_stream_lookup_ = std::make_unique<FakeMessageStreamLookup>();
    fake_message_stream_lookup_ =
        static_cast<FakeMessageStreamLookup*>(message_stream_lookup_.get());
    message_stream_ =
        std::make_unique<MessageStream>(kTestDeviceAddress, fake_socket_.get());

    process_manager_ = std::make_unique<MockQuickPairProcessManager>();
    quick_pair_process::SetProcessManager(process_manager_.get());
    data_parser_ = std::make_unique<FastPairDataParser>(
        fast_pair_data_parser_.InitWithNewPipeAndPassReceiver());
    data_parser_remote_.Bind(std::move(fast_pair_data_parser_),
                             task_environment()->GetMainThreadTaskRunner());
    EXPECT_CALL(*mock_process_manager(), GetProcessReference)
        .WillRepeatedly([&](QuickPairProcessManager::ProcessStoppedCallback) {
          return std::make_unique<
              QuickPairProcessManagerImpl::ProcessReferenceImpl>(
              data_parser_remote_, base::DoNothing());
        });
  }

  void TearDown() override {
    fast_pair_repository_.reset();
    retroactive_pairing_detector_.reset();
    ClearLogin();
    AshTestBase::TearDown();
  }

  void CreateRetroactivePairingDetector() {
    retroactive_pairing_detector_ =
        std::make_unique<RetroactivePairingDetectorImpl>(
            pairer_broker_.get(), message_stream_lookup_.get());
    retroactive_pairing_detector_->AddObserver(this);
  }

  MockQuickPairProcessManager* mock_process_manager() {
    return static_cast<MockQuickPairProcessManager*>(process_manager_.get());
  }

  void OnRetroactivePairFound(scoped_refptr<Device> device) override {
    retroactive_pair_found_ = true;
    retroactive_device_ = device;
  }

  void PairFastPairDeviceWithFastPair(std::string address) {
    auto fp_device = base::MakeRefCounted<Device>(kValidModelId, address,
                                                  Protocol::kFastPairInitial);
    fp_device->set_classic_address(address);
    mock_pairer_broker_->NotifyDevicePaired(fp_device);
  }

  void PairFastPairDeviceWithClassicBluetooth(
      bool new_paired_status,
      std::string classic_address,
      bool test_hid_already_connected = false) {
    bluetooth_device_ = CreateTestBluetoothDevice(classic_address);
    bluetooth_device_->AddUUID(ash::quick_pair::kFastPairBluetoothUuid);
    bluetooth_device_->SetType(
        device::BluetoothTransport::BLUETOOTH_TRANSPORT_LE);
    auto* bt_device_ptr = bluetooth_device_.get();
    if (test_hid_already_connected) {
      // Simulate a GATT service client connection already open and connected
      auto gatt_service_client = FastPairGattServiceClientImpl::Factory::Create(
          bt_device_ptr, adapter_.get(), base::DoNothing());
      FastPairGattServiceClientLookup::GetInstance()->InsertFakeForTesting(
          bt_device_ptr, std::move(gatt_service_client));
      SetGattServiceClientConnected(true);
    }
    adapter_->AddMockDevice(std::move(bluetooth_device_));
    adapter_->NotifyDevicePairedChanged(bt_device_ptr, new_paired_status);
  }

  void SetMessageStream(const std::vector<uint8_t>& message_bytes) {
    fake_socket_->SetIOBufferFromBytes(message_bytes);
    message_stream_ =
        std::make_unique<MessageStream>(kTestDeviceAddress, fake_socket_.get());
  }

  void AddMessageStream(const std::vector<uint8_t>& message_bytes) {
    fake_socket_->SetIOBufferFromBytes(message_bytes);
    message_stream_ =
        std::make_unique<MessageStream>(kTestDeviceAddress, fake_socket_.get());
    fake_message_stream_lookup_->AddMessageStream(kTestDeviceAddress,
                                                  message_stream_.get());
  }

  void NotifyMessageStreamConnected(std::string device_address) {
    fake_message_stream_lookup_->NotifyMessageStreamConnected(
        device_address, message_stream_.get());
  }

  void Login(user_manager::UserType user_type) {
    SimulateUserLogin(kUserEmail, user_type);
  }

  void SetGattServiceClientConnected(bool connected) {
    fast_pair_gatt_service_factory_.fake_fast_pair_gatt_service_client()
        ->SetConnected(connected);
  }

  void RunGattClientInitializedCallback(
      std::optional<PairFailure> pair_failure) {
    fast_pair_gatt_service_factory_.fake_fast_pair_gatt_service_client()
        ->RunOnGattClientInitializedCallback(pair_failure);
  }

  void RunReadModelIdCallback(
      std::optional<device::BluetoothGattService::GattErrorCode> error_code,
      const std::vector<uint8_t>& value) {
    fast_pair_gatt_service_factory_.fake_fast_pair_gatt_service_client()
        ->RunReadModelIdCallback(error_code, value);
  }

 protected:
  bool retroactive_pair_found_ = false;
  scoped_refptr<Device> retroactive_device_;

  scoped_refptr<FakeBluetoothAdapter> adapter_;
  std::unique_ptr<PairerBroker> pairer_broker_;
  raw_ptr<MockPairerBroker> mock_pairer_broker_ = nullptr;

  scoped_refptr<FakeBluetoothSocket> fake_socket_ =
      base::MakeRefCounted<FakeBluetoothSocket>();
  std::unique_ptr<MessageStream> message_stream_;
  std::unique_ptr<MessageStreamLookup> message_stream_lookup_;
  raw_ptr<FakeMessageStreamLookup> fake_message_stream_lookup_ = nullptr;
  std::unique_ptr<FakeFastPairRepository> fast_pair_repository_;

  FakeFastPairGattServiceClientImplFactory fast_pair_gatt_service_factory_;

  mojo::SharedRemote<mojom::FastPairDataParser> data_parser_remote_;
  mojo::PendingRemote<mojom::FastPairDataParser> fast_pair_data_parser_;
  std::unique_ptr<FastPairDataParser> data_parser_;
  std::unique_ptr<QuickPairProcessManager> process_manager_;

  scoped_refptr<Device> device_;
  std::unique_ptr<testing::NiceMock<device::MockBluetoothDevice>>
      bluetooth_device_;

  std::unique_ptr<RetroactivePairingDetector> retroactive_pairing_detector_;
};

TEST_F(RetroactivePairingDetectorTest,
       DevicedPaired_FastPair_BluetoothEventFiresFirst) {
  Login(user_manager::UserType::kRegular);
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  PairFastPairDeviceWithFastPair(kTestDeviceAddress);

  EXPECT_FALSE(retroactive_pair_found_);
}

// Regression test for b/261041950
TEST_F(RetroactivePairingDetectorTest,
       FastPairPairingEventCalledDuringBluetoothAdapterPairingEvent) {
  Login(user_manager::UserType::kRegular);
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  SetMessageStream(kModelIdBleAddressBytes);

  // Simulate the Bluetooth Adapter event firing, with the callback to
  // `IsDeviceSavedToAccount` delayed.
  fast_pair_repository_->SetIsDeviceSavedToAccountCallbackDelayed(
      /*is_delayed=*/true);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);

  // Simulate the Fast Pair pairing event firing during the Bluetooth Adapter
  // pairing event call stack. The Bluetooth Adapter system
  // event response has not finished completing because of the delay set in
  // `SetIsDeviceSavedToAccountCallbackDelayed`.
  PairFastPairDeviceWithFastPair(kTestDeviceAddress);

  // Trigger the callback to check the repository after the Fast Pair pairing
  // event fires. This will conclude the BluetoothAdapter pairing event call
  // stack.
  fast_pair_repository_->TriggerIsDeviceSavedToAccountCallback();

  // Simulate data being received via Message Stream for the device. It should
  // not be detected since the Fast Pair event has been fired, removing it
  // as a possible retroactive device.
  fake_socket_->TriggerReceiveCallback();
  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest, DeviceUnpaired) {
  Login(user_manager::UserType::kRegular);
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/false, kTestDeviceAddress);
  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest, NoMessageStream) {
  Login(user_manager::UserType::kRegular);
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest, MessageStream_NoBle) {
  Login(user_manager::UserType::kRegular);
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  SetMessageStream(kModelIdBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);

  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();
  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest, MessageStream_NoModelId) {
  Login(user_manager::UserType::kRegular);
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  SetMessageStream(kBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);

  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest, MessageStream_SocketError) {
  Login(user_manager::UserType::kRegular);
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  std::vector<uint8_t> data;
  SetMessageStream(data);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);

  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();
  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest, MessageStream_NoBytes) {
  Login(user_manager::UserType::kRegular);
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  std::vector<uint8_t> data;
  SetMessageStream(data);
  fake_socket_->SetEmptyBuffer();
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);

  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();
  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest, MessageStream_Ble_ModelId_Lost) {
  Login(user_manager::UserType::kRegular);
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  SetMessageStream(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);

  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();
  adapter_->RemoveMockDevice(kTestDeviceAddress);

  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest, MessageStream_Ble_ModelId_FlagEnabled) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevices,
                            features::kFastPairSavedDevicesStrictOptIn},
      /*disabled_features=*/{});

  // Strict interpretation of opt-in status while opted in means that we
  // expect to be notified of retroactive pairing when the MessageStream
  // connects after pairing is completed. This test is for the scenario
  // when we receive both the model id and the BLE address bytes over the
  // Message Stream.
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  SetMessageStream(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);

  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();
  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(retroactive_pair_found_);
  EXPECT_EQ(retroactive_device_->ble_address(), kBleAddress);
  EXPECT_EQ(retroactive_device_->metadata_id(), kModelId);
}

TEST_F(RetroactivePairingDetectorTest, MessageStream_Ble_ModelId_FlagDisabled) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{},
      /*disabled_features=*/{features::kFastPairSavedDevices,
                             features::kFastPairSavedDevicesStrictOptIn});

  // Without the SavedDevices or StrictOptIn flags, we expect that opt-in
  // status being opted out does not matter and should not impact whether or
  // not we detect a retroactive pairing scenario. We expect to still receive
  // the model id and BLE bytes once the Message Stream connects, and be
  // notified of retroactive pairing found.
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  SetMessageStream(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);

  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();
  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(retroactive_pair_found_);
  EXPECT_EQ(retroactive_device_->ble_address(), kBleAddress);
  EXPECT_EQ(retroactive_device_->metadata_id(), kModelId);
}

TEST_F(RetroactivePairingDetectorTest,
       MessageStream_Ble_ModelId_StrictFlagDisabled) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevices},
      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});

  // With the SavedDevices flag but without the StrictOptIn flag, we expect that
  // opt-in status being opted out does not matter and should not impact whether
  // or not we detect a retroactive pairing scenario. We expect to still receive
  // the model id and BLE bytes once the Message Stream connects, and be
  // notified of retroactive pairing found.
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  SetMessageStream(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);

  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();
  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(retroactive_pair_found_);
  EXPECT_EQ(retroactive_device_->ble_address(), kBleAddress);
  EXPECT_EQ(retroactive_device_->metadata_id(), kModelId);
}

TEST_F(RetroactivePairingDetectorTest,
       MessageStream_Ble_ModelId_SavedFlagDisabled) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevicesStrictOptIn},
      /*disabled_features=*/{features::kFastPairSavedDevices});

  // With the StrictOptIn flag but without the SavedDevices flag, we expect that
  // opt-in status being opted out does not matter and should not impact whether
  // or not we detect a retroactive pairing scenario. We expect to still receive
  // the model id and BLE bytes once the Message Stream connects, and be
  // notified of retroactive pairing found.
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  SetMessageStream(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);

  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();
  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(retroactive_pair_found_);
  EXPECT_EQ(retroactive_device_->ble_address(), kBleAddress);
  EXPECT_EQ(retroactive_device_->metadata_id(), kModelId);
}

TEST_F(RetroactivePairingDetectorTest,
       MessageStream_Ble_ModelId_GuestUserLoggedIn) {
  Login(user_manager::UserType::kGuest);
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  SetMessageStream(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);

  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();
  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest,
       MessageStream_Ble_ModelId_KioskUserLoggedIn) {
  Login(user_manager::UserType::kKioskApp);
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  SetMessageStream(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);

  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();
  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest,
       MessageStream_GetMessageStream_Ble_ModelId_FlagEnabled) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevices,
                            features::kFastPairSavedDevicesStrictOptIn},
      /*disabled_features=*/{});

  // Strict interpretation of opt-in status while opted in means that we
  // expect to be notified of retroactive pairing when the Message Stream
  // connects after pairing is completed. This test is for the scenario
  // when we receive both the model id and the BLE address bytes over the
  // Message Stream. The case where we are not notified when opted out is
  // tested in Notify_OptedOut_* tests below.
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  AddMessageStream(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(retroactive_pair_found_);
  EXPECT_EQ(retroactive_device_->ble_address(), kBleAddress);
  EXPECT_EQ(retroactive_device_->metadata_id(), kModelId);
}

TEST_F(RetroactivePairingDetectorTest,
       MessageStream_GetMessageStream_Ble_ModelId_FlagDisabled) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;

  // With SavedDevices and StrictOptIn flags disabled, the user's opt-in status
  // of opted out should not impact the notification of a retroactive pairing
  // found.
  feature_list.InitWithFeatures(
      /*enabled_features=*/{},
      /*disabled_features=*/{features::kFastPairSavedDevices,
                             features::kFastPairSavedDevicesStrictOptIn});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  AddMessageStream(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(retroactive_pair_found_);
  EXPECT_EQ(retroactive_device_->ble_address(), kBleAddress);
  EXPECT_EQ(retroactive_device_->metadata_id(), kModelId);
}

TEST_F(RetroactivePairingDetectorTest,
       MessageStream_GetMessageStream_Ble_ModelId_StrictFlagDisabled) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;

  // With only one flag enabled and the other disabled, the user's opt-in status
  // of opted out should not impact the notification of a retroactive pairing
  // found.
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevices},
      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  AddMessageStream(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(retroactive_pair_found_);
  EXPECT_EQ(retroactive_device_->ble_address(), kBleAddress);
  EXPECT_EQ(retroactive_device_->metadata_id(), kModelId);
}

TEST_F(RetroactivePairingDetectorTest,
       MessageStream_GetMessageStream_Ble_ModelId_SavedFlagDisabled) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;

  // With only one flag enabled and the other disabled, the user's opt-in status
  // of opted out should not impact the notification of a retroactive pairing
  // found.
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevicesStrictOptIn},
      /*disabled_features=*/{features::kFastPairSavedDevices});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  AddMessageStream(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(retroactive_pair_found_);
  EXPECT_EQ(retroactive_device_->ble_address(), kBleAddress);
  EXPECT_EQ(retroactive_device_->metadata_id(), kModelId);
}

TEST_F(RetroactivePairingDetectorTest,
       EnableScenarioIfLoggedInLater_FlagEnabled) {
  Login(user_manager::UserType::kGuest);
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevices,
                            features::kFastPairSavedDevicesStrictOptIn},
      /*disabled_features=*/{});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
  EXPECT_FALSE(retroactive_pair_found_);

  CreateRetroactivePairingDetector();
  base::RunLoop().RunUntilIdle();

  Login(user_manager::UserType::kRegular);
  AddMessageStream(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(retroactive_pair_found_);
  EXPECT_EQ(retroactive_device_->ble_address(), kBleAddress);
  EXPECT_EQ(retroactive_device_->metadata_id(), kModelId);
}

TEST_F(RetroactivePairingDetectorTest,
       EnableScenarioIfLoggedInLater_FlagDisabled) {
  Login(user_manager::UserType::kGuest);
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{},
      /*disabled_features=*/{features::kFastPairSavedDevices,
                             features::kFastPairSavedDevicesStrictOptIn});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  EXPECT_FALSE(retroactive_pair_found_);

  CreateRetroactivePairingDetector();
  base::RunLoop().RunUntilIdle();

  Login(user_manager::UserType::kRegular);
  AddMessageStream(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(retroactive_pair_found_);
  EXPECT_EQ(retroactive_device_->ble_address(), kBleAddress);
  EXPECT_EQ(retroactive_device_->metadata_id(), kModelId);
}

TEST_F(RetroactivePairingDetectorTest,
       EnableScenarioIfLoggedInLater_StrictFlagDisabled) {
  Login(user_manager::UserType::kGuest);
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevices},
      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  EXPECT_FALSE(retroactive_pair_found_);

  CreateRetroactivePairingDetector();
  base::RunLoop().RunUntilIdle();

  Login(user_manager::UserType::kRegular);
  AddMessageStream(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(retroactive_pair_found_);
  EXPECT_EQ(retroactive_device_->ble_address(), kBleAddress);
  EXPECT_EQ(retroactive_device_->metadata_id(), kModelId);
}

TEST_F(RetroactivePairingDetectorTest,
       EnableScenarioIfLoggedInLater_SavedFlagDisabled) {
  Login(user_manager::UserType::kGuest);
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevicesStrictOptIn},
      /*disabled_features=*/{features::kFastPairSavedDevices});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  EXPECT_FALSE(retroactive_pair_found_);

  CreateRetroactivePairingDetector();
  base::RunLoop().RunUntilIdle();

  Login(user_manager::UserType::kRegular);
  AddMessageStream(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(retroactive_pair_found_);
  EXPECT_EQ(retroactive_device_->ble_address(), kBleAddress);
  EXPECT_EQ(retroactive_device_->metadata_id(), kModelId);
}

TEST_F(RetroactivePairingDetectorTest,
       DontEnableScenarioIfLoggedInLaterAsGuest) {
  Login(user_manager::UserType::kGuest);
  EXPECT_FALSE(retroactive_pair_found_);

  CreateRetroactivePairingDetector();
  base::RunLoop().RunUntilIdle();

  Login(user_manager::UserType::kGuest);
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest,
       MessageStream_GetMessageStream_Ble_ModelId_GuestUser) {
  Login(user_manager::UserType::kGuest);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  AddMessageStream(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  fake_socket_->TriggerReceiveCallback();

  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest,
       MessageStream_GetMessageStream_ModelId_FlagEnabled) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevices,
                            features::kFastPairSavedDevicesStrictOptIn},
      /*disabled_features=*/{});

  // With both SavedDevices and StrictOptIn enabled, we expect to be notified
  // when the Message Stream is connected before we are notified that the
  // pairing is complete, and retrieving the model id and BLE address
  // after the fact by parsing the previous messages received if the user
  // is opted in to saving devices to their account. The case where we are not
  // notified when opted out is tested in Notify_OptedOut_* tests below.
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  AddMessageStream(kModelIdBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest,
       MessageStream_GetMessageStream_ModelId_FlagDisabled) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;

  // With both SavedDevices and StrictOptIn disabled, we expect to be notified
  // when the Message Stream is connected before we are notified that the
  // pairing is complete, and retrieving the model id and BLE address
  // after the fact by parsing the previous messages received even if the
  // user is opted out of saving devices to their account.
  feature_list.InitWithFeatures(
      /*enabled_features=*/{},
      /*disabled_features=*/{features::kFastPairSavedDevices,
                             features::kFastPairSavedDevicesStrictOptIn});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  AddMessageStream(kModelIdBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest,
       MessageStream_GetMessageStream_ModelId_StrictFlagDisabled) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;

  // With the SavedDevices flag enabled but the StrictOptIn disabled, we
  // expect to not consider the user's status of opted out and still be
  // notified when a MessageStream is connected before we are notified of
  // pairing, and successfully parse a model id and BLE address to confirm
  // retroactive pairing has been found.
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevices},
      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  AddMessageStream(kModelIdBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest,
       MessageStream_GetMessageStream_ModelId_SavedFlagDisabled) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;

  // With the StrictOptIn flag enabled but the SavedDevices disabled, we
  // expect to not consider the user's status of opted out and still be
  // notified when a MessageStream is connected before we are notified of
  // pairing, and successfully parse a model id and BLE address to confirm
  // retroactive pairing has been found.
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevicesStrictOptIn},
      /*disabled_features=*/{features::kFastPairSavedDevices});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  AddMessageStream(kModelIdBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest,
       MessageStream_Observer_Ble_ModelId_FlagEnabled) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevices,
                            features::kFastPairSavedDevicesStrictOptIn},
      /*disabled_features=*/{});

  // This test is for the scenario where the Message Stream receives messages
  // for the BLE address and model id after it is connected and paired, and
  // the detector should observe these messages and notify us of the device.
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  fake_socket_->SetIOBufferFromBytes(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(retroactive_pair_found_);
  EXPECT_EQ(retroactive_device_->ble_address(), kBleAddress);
  EXPECT_EQ(retroactive_device_->metadata_id(), kModelId);
}

TEST_F(RetroactivePairingDetectorTest,
       MessageStream_Observer_Ble_ModelId_GuestAccount) {
  Login(user_manager::UserType::kGuest);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  fake_socket_->SetIOBufferFromBytes(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest,
       MessageStream_Observer_ModelId_FlagEnabled) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;

  // With the SavedDevices and StrictOptIn flags enabled, we do not expect
  // to be notified if we only receive the model id (no BLE address) even if
  // the user is opted in.
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevices,
                            features::kFastPairSavedDevicesStrictOptIn},
      /*disabled_features=*/{});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  fake_socket_->SetIOBufferFromBytes(kModelIdBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest,
       MessageStream_Observer_ModelId_FlagDisabled) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;

  // With the SavedDevices and StrictOptIn flags disabled, we do not expect
  // to be notified if we only receive the model id (no BLE address) even if
  // the user is opted out. Opt-in status shouldn't matter for notifying with
  // the flags disabled, but here, since we don't have the information we need
  // from the device for retroactive pairing, then we expect to not be
  // notified.
  feature_list.InitWithFeatures(
      /*enabled_features=*/{},
      /*disabled_features=*/{features::kFastPairSavedDevices,
                             features::kFastPairSavedDevicesStrictOptIn});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  fake_socket_->SetIOBufferFromBytes(kModelIdBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest,
       MessageStream_Observer_ModelId_StrictFlagDisabled) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;

  // With only the StrictOptIn flag disabled, we do not expect
  // to be notified if we only receive the model id (no BLE address) even if
  // the user is opted out. Opt-in status shouldn't matter for notifying with
  // the flags disabled, but here, since we don't have the information we need
  // from the device for retroactive pairing, then we expect to not be
  // notified.
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevices},
      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  fake_socket_->SetIOBufferFromBytes(kModelIdBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest,
       MessageStream_Observer_ModelId_SavedFlagDisabled) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;

  // With only the SavedDevices flag disabled, we do not expect
  // to be notified if we only receive the model id (no BLE address) even if
  // the user is opted out. Opt-in status shouldn't matter for notifying with
  // the flags disabled, but here, since we don't have the information we need
  // from the device for retroactive pairing, then we expect to not be
  // notified.
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevicesStrictOptIn},
      /*disabled_features=*/{features::kFastPairSavedDevices});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  fake_socket_->SetIOBufferFromBytes(kModelIdBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest,
       MessageStreamRemovedOnDestroyed_FlagEnabled) {
  Login(user_manager::UserType::kRegular);
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
  base::test::ScopedFeatureList feature_list;

  // This test is verifying that we are notified when the MessageStream
  // is destroyed, and properly remove the MessageStream. The opt-in status
  // and flags set should not impact this behavior.
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevices,
                            features::kFastPairSavedDevicesStrictOptIn},
      /*disabled_features=*/{});
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  SetMessageStream(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);

  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  message_stream_.reset();
  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest,
       MessageStreamRemovedOnDestroyed_FlagDisabled) {
  Login(user_manager::UserType::kRegular);
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  base::test::ScopedFeatureList feature_list;

  // This test is verifying that we are notified when the MessageStream
  // is destroyed, and properly remove the MessageStream. The opt-in status
  // and flags set should not impact this behavior.
  feature_list.InitWithFeatures(
      /*enabled_features=*/{},
      /*disabled_features=*/{features::kFastPairSavedDevices,
                             features::kFastPairSavedDevicesStrictOptIn});
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  SetMessageStream(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);

  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  message_stream_.reset();
  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest,
       MessageStreamRemovedOnDestroyed_StrictFlagDisabled) {
  Login(user_manager::UserType::kRegular);
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  base::test::ScopedFeatureList feature_list;

  // This test is verifying that we are notified when the MessageStream
  // is destroyed, and properly remove the MessageStream. The opt-in status
  // and flags set should not impact this behavior.
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevices},
      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  SetMessageStream(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);

  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  message_stream_.reset();
  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest,
       MessageStreamRemovedOnDestroyed_SavedFlagDisabled) {
  Login(user_manager::UserType::kRegular);
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  base::test::ScopedFeatureList feature_list;

  // This test is verifying that we are notified when the Message Stream
  // is destroyed, and properly remove the Message Stream. The opt-in status
  // and flags set should not impact this behavior.
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevicesStrictOptIn},
      /*disabled_features=*/{features::kFastPairSavedDevices});
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  SetMessageStream(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);

  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  message_stream_.reset();
  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest,
       MessageStreamRemovedOnDisconnect_FlagEnabled) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;

  // This test is verifying that we are notified when the Message Stream
  // is destroyed, and properly remove the Message Stream. The opt-in status
  // and flags set should not impact this behavior.
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevices,
                            features::kFastPairSavedDevicesStrictOptIn},
      /*disabled_features=*/{});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  fake_socket_->SetErrorReason(
      device::BluetoothSocket::ErrorReason::kDisconnected);
  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  message_stream_ =
      std::make_unique<MessageStream>(kTestDeviceAddress, fake_socket_.get());
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  fake_socket_->SetIOBufferFromBytes(kModelIdBytes);
  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest,
       MessageStreamRemovedOnDisconnect_FlagDisabled) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;

  // This test is verifying that we are notified when the Message Stream
  // is destroyed, and properly remove the Message Stream. The opt-in status
  // and flags set should not impact this behavior.
  feature_list.InitWithFeatures(
      /*enabled_features=*/{},
      /*disabled_features=*/{features::kFastPairSavedDevices,
                             features::kFastPairSavedDevicesStrictOptIn});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  fake_socket_->SetErrorReason(
      device::BluetoothSocket::ErrorReason::kDisconnected);
  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  message_stream_ =
      std::make_unique<MessageStream>(kTestDeviceAddress, fake_socket_.get());
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  fake_socket_->SetIOBufferFromBytes(kModelIdBytes);
  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest,
       MessageStreamRemovedOnDisconnect_StrictFlagDisabled) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;

  // This test is verifying that we are notified when the Message Stream
  // is destroyed, and properly remove the Message Stream. The opt-in status
  // and flags set should not impact this behavior.
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevices},
      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  fake_socket_->SetErrorReason(
      device::BluetoothSocket::ErrorReason::kDisconnected);
  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  message_stream_ =
      std::make_unique<MessageStream>(kTestDeviceAddress, fake_socket_.get());
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  fake_socket_->SetIOBufferFromBytes(kModelIdBytes);
  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest,
       MessageStreamRemovedOnDisconnect_SavedFlagDisabled) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;

  // This test is verifying that we are notified when the Message Stream
  // is destroyed, and properly remove the Message Stream. The opt-in status
  // and flags set should not impact this behavior.
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevicesStrictOptIn},
      /*disabled_features=*/{features::kFastPairSavedDevices});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  fake_socket_->SetErrorReason(
      device::BluetoothSocket::ErrorReason::kDisconnected);
  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  message_stream_ =
      std::make_unique<MessageStream>(kTestDeviceAddress, fake_socket_.get());
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  fake_socket_->SetIOBufferFromBytes(kModelIdBytes);
  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest, DontNotify_OptedOut_FlagEnabled) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;

  // If the SavedDevices and StrictOptIn flags are enabled and the user is
  // opted out, we expect not to be notified for retroactive pairing even if
  // a potential one is found.
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevices,
                            features::kFastPairSavedDevicesStrictOptIn},
      /*disabled_features=*/{});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  fake_socket_->SetIOBufferFromBytes(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest, Notify_OptedOut_FlagDisabled) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;

  // If the SavedDevices and StrictOptIn flags are disabled, we expect to be
  // notified when a retroactive pairing is found even if the user is opted out.
  feature_list.InitWithFeatures(
      /*enabled_features=*/{},
      /*disabled_features=*/{features::kFastPairSavedDevices,
                             features::kFastPairSavedDevicesStrictOptIn});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  fake_socket_->SetIOBufferFromBytes(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest, Notify_OptedOut_StrictFlagDisabled) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;

  // If the SavedDevices flag is enabled but the StrictOptin flag is disabled,
  // then we expect to be notified even if the user is opted out of saving
  // devices to their account.
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevices},
      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  fake_socket_->SetIOBufferFromBytes(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest, Notify_OptedOut_SavedFlagDisabled) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;

  // If the SavedDevices flag is disabled but the StrictOptin flag is enabled,
  // then we expect to be notified even if the user is opted out of saving
  // devices to their account.
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevicesStrictOptIn},
      /*disabled_features=*/{features::kFastPairSavedDevices});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  fake_socket_->SetIOBufferFromBytes(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest, Notify_OptedIn_FlagDisabled) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{},
      /*disabled_features=*/{features::kFastPairSavedDevices,
                             features::kFastPairSavedDevicesStrictOptIn});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  fake_socket_->SetIOBufferFromBytes(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest, Notify_OptedIn_StrictFlagDisabled) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;

  // When the strict interpretation is disabled, we expect to be notified about
  // a retroactive pairing regardless of opt-in status.
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevices},
      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  fake_socket_->SetIOBufferFromBytes(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest, Notify_OptedIn_SavedFlagDisabled) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;

  // When only the SavedDevices flag is disabled, we expect to be notified about
  // a retroactive pairing regardless of opt-in status.
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevicesStrictOptIn},
      /*disabled_features=*/{features::kFastPairSavedDevices});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  fake_socket_->SetIOBufferFromBytes(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest,
       DontNotify_OptedOut_OptedIn_FlagEnabled) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevices,
                            features::kFastPairSavedDevicesStrictOptIn},
      /*disabled_features=*/{});

  // Simulate user is opted out.
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  fake_socket_->SetIOBufferFromBytes(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);

  // Simulate user is opted in. Now we would expect to be notified of a
  // retroactive pairing scenario when the flags are enabled for a
  // strict interpretation of the opt in status.
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);

  fake_socket_->SetIOBufferFromBytes(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(retroactive_pair_found_);
  EXPECT_EQ(retroactive_device_->ble_address(), kBleAddress);
  EXPECT_EQ(retroactive_device_->metadata_id(), kModelId);
}

TEST_F(RetroactivePairingDetectorTest, DontNotifyIfAlreadySavedToAcount) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;
  fast_pair_repository_->SaveMacAddressToAccount(kTestDeviceAddress);

  // If the SavedDevices and StrictOptIn flags are disabled, we may expect to be
  // notified when a retroactive pairing is found even if the user is opted out.
  // However, since the device is already saved the account, we expect to not
  // be notified.
  feature_list.InitWithFeatures(
      /*enabled_features=*/{},
      /*disabled_features=*/{features::kFastPairSavedDevices,
                             features::kFastPairSavedDevicesStrictOptIn});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  fake_socket_->SetIOBufferFromBytes(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

// There are two ways to get to `CheckAndRemoveIfDeviceExpired `. One is by
// `GetModelIdAndAddressFromMessageStream` which is triggered when the
// MessageStream already has the model id and BLE address messages on
// connection. The second way is through `CheckPairingInformation` which is
// triggered when the MessageStream does not have the model id and BLE
// address on connection, and the model id and BLE address are observed later
// on.
TEST_F(RetroactivePairingDetectorTest,
       DontNotify_ExpiryTimeoutReached_GetModelIdAndAddressFromMessageStream) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevices},
      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  // Pair a device with classic Bluetooth pairing and set the MessageStream
  // with model id and BLE address bytes to successfully detect the scenario.
  // At this point, the device is in the `device_pairing_information_` map with
  // an expiry timestamp.
  SetMessageStream(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);

  // Fast forward by |kRetroactiveDevicePairingTimeout| in order to simulate
  // that the device's |expiry_timestamp| has been reached. Because the
  // timeout has been reached, we expect that the retroactive pairing
  // scenario to not be triggered.
  task_environment()->FastForwardBy(kRetroactiveDevicePairingTimeout);
  fake_socket_->TriggerReceiveCallback();
  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest,
       DontNotify_ExpiryTimeoutReached_CheckPairingInformation) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevices},
      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  // Pair a device with classic Bluetooth pairing and set the MessageStream
  // with no bytes to successfully detect the scenario.
  // At this point, the device is in the `device_pairing_information_` map with
  // an expiry timestamp. Because there are no BLE address bytes or model id
  // bytes in the connected MessageStream, the RetroactivePairingDetector adds
  // itself as an observer to wait for these messages.
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  NotifyMessageStreamConnected(kTestDeviceAddress);

  // Fast forward by |kRetroactiveDevicePairingTimeout| in order to simulate
  // that the device's |expiry_timestamp| has been reached. Because the
  // timeout has been reached, we expect that the retroactive pairing
  // scenario to not be triggered.
  task_environment()->FastForwardBy(kRetroactiveDevicePairingTimeout);

  // Set up the socket with the model id bytes and BLE address bytes to
  // successfully detect the scenario, and trigger the bytes being received
  // after the timeout to trigger the check in `CheckPairingInformation`
  // which happens in the overridden observed red functions for
  // `OnModelIdMessage` and `OnBleAddressUpdateMessage`.
  fake_socket_->SetIOBufferFromBytes(kModelIdBleAddressBytes);

  // TODO(b/263391358): Refactor `TriggerReceiveCallback` to take a
  // base::RunLoop parameter and remove `base::RunLoop().RunUntilIdle()`.
  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(
    RetroactivePairingDetectorTest,
    DontNotify_ExpiryTimeoutReached_DifferentDeviceTriggerRemoval_DeviceToBeRemovedHashesToFirstPosition) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevices},
      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  // Simulate a first device being found with classic Bluetooth pairing.
  // At this point, the device is in the `device_pairing_information_` map with
  // an expiry timestamp. This device does not have a MessageStream associated,
  // so `CheckPairingInformation` will never be fired because it doesn't have
  // a model id or BLE event, and thus alone, its expiry event will never be
  // triggered.
  //
  // |kTestDeviceAddress| hashes to the first position in the map, and we
  // expect the removing it from the map of devices will not cause a crash
  // from an invalid iterator when we increment to continue iterating.
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);

  // Fast forward by |kRetroactiveDevicePairingTimeout| in order to simulate
  // that the device's |expiry_timestamp| has been reached.
  task_environment()->FastForwardBy(kRetroactiveDevicePairingTimeout);

  // Simulate another device being found for retroactive pairing. This device
  // will also be added to `device_pairing_information_` map with an expiry
  // timeout, and its addition will trigger
  // `RemoveExpiredDevicesFromStoredDeviceData`, which will remove the
  // first device since it has expired.
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress2);

  // Trigger a Message Stream event for the first device with the model id and
  // BLE address. Although there is a check in `CheckPairingInformation`,
  // the device was already removed in
  // `RemoveExpiredDevicesFromStoredDeviceData`.
  SetMessageStream(kModelIdBleAddressBytes);
  fake_socket_->TriggerReceiveCallback();
  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(
    RetroactivePairingDetectorTest,
    DontNotify_ExpiryTimeoutReached_DifferentDeviceTriggerRemoval_DeviceToBeRemovedHashesToSecondPosition) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevices},
      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  // Simulate a first device being found with classic Bluetooth pairing.
  // At this point, the device is in the `device_pairing_information_` map with
  // an expiry timestamp. This device does not have a MessageStream associated,
  // so `CheckPairingInformation` will never be fired because it doesn't have
  // a model id or BLE event, and thus alone, its expiry event will never be
  // triggered.
  //
  // |kTestDeviceAddress3| hashes to the second position in the map, and we
  // expect the removing it from the map of devices will not cause a crash
  // from an invalid iterator when we increment to continue iterating.
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress3);

  // Fast forward by |kRetroactiveDevicePairingTimeout| in order to simulate
  // that the device's |expiry_timestamp| has been reached.
  task_environment()->FastForwardBy(kRetroactiveDevicePairingTimeout);

  // Simulate another device being found for retroactive pairing. This device
  // will also be added to `device_pairing_information_` map with an expiry
  // timeout, and its addition will trigger
  // `RemoveExpiredDevicesFromStoredDeviceData`, which will remove the
  // first device since it has expired.
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress2);

  // Trigger a Message Stream event for the first device with the model id and
  // BLE address. Although there is a check in `CheckPairingInformation`,
  // the device was already removed in
  // `RemoveExpiredDevicesFromStoredDeviceData`.
  SetMessageStream(kModelIdBleAddressBytes);
  fake_socket_->TriggerReceiveCallback();
  NotifyMessageStreamConnected(kTestDeviceAddress3);
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(
    RetroactivePairingDetectorTest,
    DontNotify_ExpiryTimeoutReached_DifferentDeviceTriggerRemoval_MultipleDevicesPaired) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevices},
      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  // Simulate devices being found with classic Bluetooth pairing.
  // At this point, the devices are in the `device_pairing_information_` map
  // with an expiry timestamp. This device does not have a MessageStream
  // associated, so `CheckPairingInformation` will never be fired because it
  // doesn't have a model id or BLE event, and thus alone, its expiry event will
  // never be triggered.
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress3);

  // Fast forward by |kRetroactiveDevicePairingTimeout| in order to simulate
  // that the device's |expiry_timestamp| has been reached.
  task_environment()->FastForwardBy(kRetroactiveDevicePairingTimeout);

  // Simulate another device being found for retroactive pairing. This device
  // will also be added to `device_pairing_information_` map with an expiry
  // timeout, and its addition will trigger
  // `RemoveExpiredDevicesFromStoredDeviceData`, which will remove the
  // first device since it has expired.
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress2);

  // Trigger a Message Stream event for the first device with the model id and
  // BLE address. Although there is a check in `CheckPairingInformation`,
  // the device was already removed in
  // `RemoveExpiredDevicesFromStoredDeviceData`.
  SetMessageStream(kModelIdBleAddressBytes);
  fake_socket_->TriggerReceiveCallback();
  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest, NotifyAfterDeviceRepairs) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevices},
      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  // Pair a device with classic Bluetooth pairing and set the MessageStream
  // with model id and BLE address bytes to successfully detect the scenario.
  // At this point, the device is in the `device_pairing_information_` map with
  // an expiry timestamp.
  SetMessageStream(kModelIdBleAddressBytes);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);

  // Simulate the device being unpaired, and paired again.
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/false, kTestDeviceAddress);
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kTestDeviceAddress);

  // When the model id and BLE address fire, we expect the retroactive pairing
  // event to still be detected, even if the device was unpaired and repeated.
  fake_socket_->TriggerReceiveCallback();
  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest, NoCrashWhenFootprintsResponseIsSlow) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{},
      /*disabled_features=*/{features::kFastPairSavedDevices,
                             features::kFastPairSavedDevicesStrictOptIn});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();
  EXPECT_FALSE(retroactive_pair_found_);

  // Delay the response so we can trigger it after OnDevicePaired.
  fast_pair_repository_->SetIsDeviceSavedToAccountCallbackDelayed(
      /*is_delayed=*/true);

  // The naming for these are confusing.
  // This calls DevicePairedChanged.
  PairFastPairDeviceWithClassicBluetooth(true, kTestDeviceAddress);

  // This calls OnDevicePaired.
  PairFastPairDeviceWithFastPair(kTestDeviceAddress);

  fake_socket_->TriggerReceiveCallback();
  base::RunLoop().RunUntilIdle();

  // Add a real message stream so the check passes.
  AddMessageStream(kModelIdBleAddressBytes);
  NotifyMessageStreamConnected(kTestDeviceAddress);
  base::RunLoop().RunUntilIdle();

  // Trigger the response.
  fast_pair_repository_->TriggerIsDeviceSavedToAccountCallback();
}

TEST_F(RetroactivePairingDetectorTest, FastPairHID_Success) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevices,
                            features::kFastPairSavedDevicesStrictOptIn,
                            features::kFastPairHID,
                            floss::features::kFlossEnabled},
      /*disabled_features=*/{});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  // Test the normal retroactive pair flow of a BLE HID
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kBleAddress);
  SetGattServiceClientConnected(true);
  RunGattClientInitializedCallback(/*pair_failure=*/std::nullopt);
  RunReadModelIdCallback(/*error_code=*/std::nullopt, kModelIdBytesNoMetadata);

  EXPECT_TRUE(retroactive_pair_found_);
  EXPECT_EQ(retroactive_device_->ble_address(), kBleAddress);
  EXPECT_EQ(retroactive_device_->metadata_id(), kModelId);
}

TEST_F(RetroactivePairingDetectorTest, FastPairHID_GattConnectionOpen_Success) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevices,
                            features::kFastPairSavedDevicesStrictOptIn,
                            features::kFastPairHID,
                            floss::features::kFlossEnabled},
      /*disabled_features=*/{});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  // If GATT connection already open, we expect a read to Model ID
  // immediately after.
  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kBleAddress,
      /*test_hid_already_connected=*/true);
  RunReadModelIdCallback(/*error_code*/ std::nullopt, kModelIdBytesNoMetadata);

  EXPECT_TRUE(retroactive_pair_found_);
  EXPECT_EQ(retroactive_device_->ble_address(), kBleAddress);
  EXPECT_EQ(retroactive_device_->metadata_id(), kModelId);
}

TEST_F(RetroactivePairingDetectorTest, FastPairHID_GattConnectionFailure) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevices,
                            features::kFastPairSavedDevicesStrictOptIn,
                            features::kFastPairHID,
                            floss::features::kFlossEnabled},
      /*disabled_features=*/{});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kBleAddress);
  SetGattServiceClientConnected(true);

  // If we get an error while create the GATT connection, we shouldn't
  // expect a retroactive pairable device to be found.
  RunGattClientInitializedCallback(PairFailure::kCreateGattConnection);
  EXPECT_FALSE(retroactive_pair_found_);
}

TEST_F(RetroactivePairingDetectorTest, FastPairHID_ReadModelIdFailure) {
  Login(user_manager::UserType::kRegular);
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairSavedDevices,
                            features::kFastPairSavedDevicesStrictOptIn,
                            features::kFastPairHID,
                            floss::features::kFlossEnabled},
      /*disabled_features=*/{});
  fast_pair_repository_->SetOptInStatus(
      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
  base::RunLoop().RunUntilIdle();
  CreateRetroactivePairingDetector();

  EXPECT_FALSE(retroactive_pair_found_);

  PairFastPairDeviceWithClassicBluetooth(
      /*new_paired_status=*/true, kBleAddress);
  SetGattServiceClientConnected(true);
  RunGattClientInitializedCallback(/*pair_failure=*/std::nullopt);

  // If we get an error while reading model ID, we shouldn't expect a
  // retroactive pairable device to be found.
  RunReadModelIdCallback(
      /*error_code=*/device::BluetoothGattService::GattErrorCode::kNotSupported,
      kModelIdBytesNoMetadata);
  EXPECT_FALSE(retroactive_pair_found_);
}

}  // namespace quick_pair
}  // namespace ash