blob: 2c7743d14d14091ab3f317750148bf9d15bc24b5 [file]
// Copyright 2022 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/repository/fast_pair_repository_impl.h"
#include <optional>
#include "ash/quick_pair/common/device.h"
#include "ash/quick_pair/common/fast_pair/fast_pair_metrics.h"
#include "ash/quick_pair/common/mock_quick_pair_browser_delegate.h"
#include "ash/quick_pair/common/protocol.h"
#include "ash/quick_pair/common/quick_pair_browser_delegate.h"
#include "ash/quick_pair/repository/fake_device_metadata_http_fetcher.h"
#include "ash/quick_pair/repository/fast_pair/device_address_map.h"
#include "ash/quick_pair/repository/fast_pair/device_image_store.h"
#include "ash/quick_pair/repository/fast_pair/device_metadata.h"
#include "ash/quick_pair/repository/fast_pair/device_metadata_fetcher.h"
#include "ash/quick_pair/repository/fast_pair/fake_footprints_fetcher.h"
#include "ash/quick_pair/repository/fast_pair/mock_fast_pair_image_decoder.h"
#include "ash/quick_pair/repository/fast_pair/pending_write_store.h"
#include "ash/quick_pair/repository/fast_pair/proto_conversions.h"
#include "ash/quick_pair/repository/fast_pair/saved_device_registry.h"
#include "ash/quick_pair/repository/fast_pair_repository.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "base/base64.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_view_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "chromeos/ash/components/network/network_handler.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/network/network_state_test_helper.h"
#include "chromeos/ash/services/quick_pair/public/cpp/account_key_filter.h"
#include "components/prefs/testing_pref_service.h"
#include "crypto/hash.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/public/cpp/bluetooth_address.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "device/bluetooth/test/mock_bluetooth_device.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/image/image_unittest_util.h"
namespace {
constexpr int kBluetoothAddressSize = 6;
constexpr char kValidModelId[] = "abc";
constexpr char kInvalidModelId[] = "666";
constexpr char kDeviceDisplayName[] = "test_nickname";
constexpr char kDeviceDisplayName2[] = "second_test_nickname";
constexpr char kTestModelId[] = "test_model_id";
constexpr char kTestDeviceId[] = "test_ble_device_id";
constexpr char kTestBLEAddress[] = "00:11:22:33:45:11";
constexpr char kTestBLEAddress2[] = "00:11:22:33:45:77";
constexpr char kTestClassicAddress1[] = "00:11:22:33:44:55";
constexpr char kTestClassicAddress2[] = "00:11:22:33:44:66";
constexpr char kTestClassicAddress3[] = "04:CB:88:1E:56:19";
constexpr char kBase64ExpectedSha256Hash[] =
"gVzzRtZjwYv8lO8xwWnWW2uw/stV6RdEUhv3cIN3nH4=";
constexpr char kBase64ForgetPatternSha256Hash[] =
"8PDw8NZjwYv8lO8xwWnWW2uw/stV6RdEUhv3cIN3nH4=";
constexpr char kBase64AccountKey[] = "BAcDiEH56/Mq3hW7OKUctA==";
const std::vector<uint8_t> kAccountKey1{0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
0x77, 0x88, 0x99, 0x00, 0xAA, 0xBB,
0xCC, 0xDD, 0xEE, 0xFF};
const std::vector<uint8_t> kAccountKey2{0x11, 0x11, 0x22, 0x22, 0x33, 0x33,
0x44, 0x44, 0x55, 0x55, 0x66, 0x66,
0x77, 0x77, 0x88, 0x88};
const std::vector<uint8_t> kFilterBytes1{0x0A, 0x42, 0x88, 0x10};
const uint8_t salt = 0xC7;
const char kSavedDeviceGetDevicesResultMetricName[] =
"Bluetooth.ChromeOS.FastPair.SavedDevices.GetSavedDevices.Result";
constexpr char kRetroactiveSuccessFunnelMetric[] =
"FastPair.RetroactivePairing";
// Computes and returns the Sha256 of the concatenation of the given
// |account_key| and |mac_address|.
std::string GenerateSha256AccountKeyMacAddress(const std::string& account_key,
const std::string& mac_address) {
std::vector<uint8_t> concat_bytes(account_key.begin(), account_key.end());
std::vector<uint8_t> mac_address_bytes;
mac_address_bytes.resize(kBluetoothAddressSize);
device::ParseBluetoothAddress(mac_address, mac_address_bytes);
concat_bytes.insert(concat_bytes.end(), mac_address_bytes.begin(),
mac_address_bytes.end());
return std::string(base::as_string_view(crypto::hash::Sha256(concat_bytes)));
}
std::string Base64Decode(const std::string& encoded) {
std::string decoded;
base::Base64Decode(encoded, &decoded);
return decoded;
}
} // namespace
namespace ash {
namespace quick_pair {
using ::base::test::RunOnceCallback;
using ::testing::_;
using ::testing::Return;
class FastPairRepositoryImplTest : public AshTestBase {
public:
FastPairRepositoryImplTest()
: AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME),
adapter_(new testing::NiceMock<device::MockBluetoothAdapter>),
ble_bluetooth_device_(adapter_.get(),
0,
"Test ble name",
kTestBLEAddress,
false,
true),
classic_bluetooth_device_(adapter_.get(),
0,
"Test classic name",
kTestClassicAddress1,
false,
true) {
ON_CALL(ble_bluetooth_device_, GetIdentifier)
.WillByDefault(Return(kTestDeviceId));
ON_CALL(classic_bluetooth_device_, GetIdentifier)
.WillByDefault(Return(kTestDeviceId));
ON_CALL(ble_bluetooth_device_, IsPaired)
.WillByDefault(testing::Return(true));
ON_CALL(classic_bluetooth_device_, IsPaired)
.WillByDefault(testing::Return(true));
ON_CALL(*adapter_, GetDevices).WillByDefault(testing::Return(device_list_));
ON_CALL(*adapter_, GetDevice(kTestBLEAddress))
.WillByDefault(Return(&ble_bluetooth_device_));
ON_CALL(*adapter_, GetDevice(kTestClassicAddress1))
.WillByDefault(Return(&classic_bluetooth_device_));
}
void SetUp() override {
AshTestBase::SetUp();
NetworkHandler::Initialize();
device::BluetoothAdapterFactory::SetAdapterForTesting(adapter_);
device_ = base::MakeRefCounted<Device>(kTestModelId, kTestBLEAddress,
Protocol::kFastPairInitial);
device_->set_classic_address(kTestClassicAddress1);
auto http_fetcher = std::make_unique<FakeDeviceMetadataHttpFetcher>();
metadata_http_fetcher_ = http_fetcher.get();
auto device_metadata_fetcher =
std::make_unique<DeviceMetadataFetcher>(std::move(http_fetcher));
device_metadata_fetcher_ = device_metadata_fetcher.get();
auto footprints_fetcher = std::make_unique<FakeFootprintsFetcher>();
footprints_fetcher_ = footprints_fetcher.get();
auto image_decoder = std::make_unique<MockFastPairImageDecoder>();
image_decoder_ = image_decoder.get();
test_image_ = gfx::test::CreateImage(100, 100);
ON_CALL(*image_decoder_, DecodeImage(_, _, _))
.WillByDefault(base::test::RunOnceCallbackRepeatedly<2>(test_image_));
auto device_address_map = std::make_unique<DeviceAddressMap>();
device_address_map_ = device_address_map.get();
auto device_image_store =
std::make_unique<DeviceImageStore>(image_decoder_);
device_image_store_ = device_image_store.get();
auto pending_write_store = std::make_unique<PendingWriteStore>();
pending_write_store_ = pending_write_store.get();
auto saved_device_registry =
std::make_unique<SavedDeviceRegistry>(adapter_.get());
saved_device_registry_ = saved_device_registry.get();
fast_pair_repository_ = std::make_unique<FastPairRepositoryImpl>(
adapter_, std::move(device_metadata_fetcher),
std::move(footprints_fetcher), std::move(image_decoder),
std::move(device_address_map), std::move(device_image_store),
std::move(saved_device_registry), std::move(pending_write_store));
FastPairRepository::SetInstanceForTesting(fast_pair_repository_.get());
}
void TearDown() override {
fast_pair_repository_.reset();
NetworkHandler::Shutdown();
AshTestBase::TearDown();
}
void VerifyMetadata(base::OnceClosure on_complete,
DeviceMetadata* device_metadata,
bool should_retry) {
EXPECT_NE(nullptr, device_metadata);
std::move(on_complete).Run();
}
void VerifyMetadataFailure(base::OnceClosure on_complete,
bool expected_retry,
DeviceMetadata* device_metadata,
bool should_retry) {
EXPECT_EQ(nullptr, device_metadata);
EXPECT_EQ(expected_retry, should_retry);
std::move(on_complete).Run();
}
void VerifyAccountKeyCheck(base::OnceClosure on_complete,
bool expected_result,
std::optional<PairingMetadata> pairing_metadata) {
if (expected_result) {
EXPECT_NE(std::nullopt, pairing_metadata);
} else {
EXPECT_EQ(std::nullopt, pairing_metadata);
}
std::move(on_complete).Run();
}
void GetSavedDevicesCallback(
nearby::fastpair::OptInStatus status,
std::vector<nearby::fastpair::FastPairDevice> devices) {
status_ = status;
devices_ = devices;
}
base::HistogramTester& histogram_tester() { return histogram_tester_; }
protected:
NetworkStateTestHelper helper_{/*use_default_devices_and_services=*/true};
std::unique_ptr<FastPairRepositoryImpl> fast_pair_repository_;
nearby::fastpair::OptInStatus status_;
std::vector<nearby::fastpair::FastPairDevice> devices_;
base::HistogramTester histogram_tester_;
scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>> adapter_;
testing::NiceMock<device::MockBluetoothDevice> ble_bluetooth_device_;
testing::NiceMock<device::MockBluetoothDevice> classic_bluetooth_device_;
device::BluetoothAdapter::ConstDeviceList device_list_{
&ble_bluetooth_device_, &classic_bluetooth_device_};
scoped_refptr<Device> device_;
gfx::Image test_image_;
raw_ptr<DeviceMetadataFetcher, DanglingUntriaged> device_metadata_fetcher_;
raw_ptr<FakeDeviceMetadataHttpFetcher, DanglingUntriaged>
metadata_http_fetcher_;
raw_ptr<FakeFootprintsFetcher, DanglingUntriaged> footprints_fetcher_;
raw_ptr<MockFastPairImageDecoder, DanglingUntriaged> image_decoder_;
raw_ptr<DeviceAddressMap, DanglingUntriaged> device_address_map_;
raw_ptr<DeviceImageStore, DanglingUntriaged> device_image_store_;
raw_ptr<PendingWriteStore, DanglingUntriaged> pending_write_store_;
raw_ptr<SavedDeviceRegistry, DanglingUntriaged> saved_device_registry_;
base::WeakPtrFactory<FastPairRepositoryImplTest> weak_ptr_factory_{this};
};
TEST_F(FastPairRepositoryImplTest, GetDeviceMetadata) {
{
auto run_loop = base::RunLoop();
fast_pair_repository_->GetDeviceMetadata(
kValidModelId,
base::BindOnce(&FastPairRepositoryImplTest::VerifyMetadata,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
}
EXPECT_EQ(1, metadata_http_fetcher_->num_gets());
{
auto run_loop = base::RunLoop();
fast_pair_repository_->GetDeviceMetadata(
kValidModelId,
base::BindOnce(&FastPairRepositoryImplTest::VerifyMetadata,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
}
// Indicates that the cache was used instead of a second GET.
EXPECT_EQ(1, metadata_http_fetcher_->num_gets());
}
TEST_F(FastPairRepositoryImplTest, GetDeviceMetadata_Failed_Retryable) {
base::RunLoop run_loop;
metadata_http_fetcher_->set_network_error(true);
fast_pair_repository_->GetDeviceMetadata(
kInvalidModelId,
base::BindOnce(&FastPairRepositoryImplTest::VerifyMetadataFailure,
base::Unretained(this), run_loop.QuitClosure(),
/*expected_retry=*/true));
run_loop.Run();
}
TEST_F(FastPairRepositoryImplTest, GetDeviceMetadata_Failed_NotRetryable) {
base::RunLoop run_loop;
fast_pair_repository_->GetDeviceMetadata(
kInvalidModelId,
base::BindOnce(&FastPairRepositoryImplTest::VerifyMetadataFailure,
base::Unretained(this), run_loop.QuitClosure(),
/*expected_retry=*/false));
run_loop.Run();
EXPECT_EQ(1, metadata_http_fetcher_->num_gets());
}
TEST_F(FastPairRepositoryImplTest, CheckAccountKeys_NoMatch) {
AccountKeyFilter filter(kFilterBytes1, {salt});
auto run_loop = base::RunLoop();
fast_pair_repository_->CheckAccountKeys(
filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
base::Unretained(this), run_loop.QuitClosure(),
/*expected_result=*/false));
run_loop.Run();
}
TEST_F(FastPairRepositoryImplTest, CheckAccountKeys_Match) {
AccountKeyFilter filter(kFilterBytes1, {salt});
nearby::fastpair::GetObservedDeviceResponse device;
DeviceMetadata metadata(device, gfx::Image());
// FakeFootprintsFetcher APIs are actually synchronous.
footprints_fetcher_->AddUserFastPairInfo(
BuildFastPairInfo(kValidModelId, kAccountKey1, kTestClassicAddress1,
kDeviceDisplayName, &metadata),
base::DoNothing());
auto run_loop = base::RunLoop();
fast_pair_repository_->CheckAccountKeys(
filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
base::Unretained(this), run_loop.QuitClosure(),
/*expected_result=*/true));
run_loop.Run();
}
TEST_F(FastPairRepositoryImplTest, CheckAccountKeys_Match_No_Name) {
AccountKeyFilter filter(kFilterBytes1, {salt});
nearby::fastpair::GetObservedDeviceResponse device;
DeviceMetadata metadata(device, gfx::Image());
// FakeFootprintsFetcher APIs are actually synchronous.
// The |device_| display name is not set so this will mimic when we fail to
// get a |display_name| for the device and have to fall back on using the
// metadata name when creating the proto.
footprints_fetcher_->AddUserFastPairInfo(
BuildFastPairInfo(kValidModelId, kAccountKey1, kTestClassicAddress1,
device_->display_name(), &metadata),
base::DoNothing());
auto run_loop = base::RunLoop();
fast_pair_repository_->CheckAccountKeys(
filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
base::Unretained(this), run_loop.QuitClosure(),
/*expected_result=*/true));
run_loop.Run();
}
TEST_F(FastPairRepositoryImplTest, CheckAccountKeys_SkipForgetPattern) {
AccountKeyFilter filter(kFilterBytes1, {salt});
nearby::fastpair::GetObservedDeviceResponse details;
DeviceMetadata metadata(details, gfx::Image());
// In this test, we use a known "marked as forgotten" hash
// from Android. Note that Android also leaves the account key
// blank; however, we add a matching account key here to test the logic
// of detecting the Forget pattern in the hash, which should be cause
// the device to be skipped in CheckAccountKeys.
nearby::fastpair::FastPairInfo info =
BuildFastPairInfo(kValidModelId, kAccountKey1, kTestClassicAddress1,
kDeviceDisplayName, &metadata);
auto* device = info.mutable_device();
device->set_sha256_account_key_public_address(
Base64Decode(kBase64ForgetPatternSha256Hash));
footprints_fetcher_->AddUserFastPairInfo(info, base::DoNothing());
auto run_loop = std::make_unique<base::RunLoop>();
fast_pair_repository_->CheckAccountKeys(
filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
base::Unretained(this), run_loop->QuitClosure(),
/*expected_result=*/false));
run_loop->Run();
}
TEST_F(FastPairRepositoryImplTest, UpdateStaleUserDeviceCache) {
AccountKeyFilter filter(kFilterBytes1, {salt});
nearby::fastpair::GetObservedDeviceResponse response;
DeviceMetadata metadata(response, gfx::Image());
auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
Protocol::kFastPairInitial);
device->set_classic_address(kTestClassicAddress1);
device->set_display_name(kDeviceDisplayName);
fast_pair_repository_->WriteAccountAssociationToFootprints(device,
kAccountKey1);
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
auto run_loop = base::RunLoop();
// Check for the device, this will also load the device into the cache
fast_pair_repository_->CheckAccountKeys(
filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
base::Unretained(this), run_loop.QuitClosure(),
/*expected_result=*/true));
base::RunLoop().RunUntilIdle();
// Remove the device directly from footprints. This is equivalent to the
// device being removed on an Android phone or another Chromebook
footprints_fetcher_->DeleteUserDevice(base::HexEncode(kAccountKey1),
base::DoNothing());
// 29 minutes later, device is still in the cache
task_environment()->FastForwardBy(base::Minutes(29));
fast_pair_repository_->CheckAccountKeys(
filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
base::Unretained(this), run_loop.QuitClosure(),
/*expected_result=*/true));
base::RunLoop().RunUntilIdle();
// After >30 minutes, cache will have gone stale so device will be removed
task_environment()->FastForwardBy(base::Seconds(61));
fast_pair_repository_->CheckAccountKeys(
filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
base::Unretained(this), run_loop.QuitClosure(),
/*expected_result=*/false));
run_loop.Run();
}
TEST_F(FastPairRepositoryImplTest, UseStaleCache) {
AccountKeyFilter filter(kFilterBytes1, {salt});
nearby::fastpair::GetObservedDeviceResponse response;
DeviceMetadata metadata(response, gfx::Image());
auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
Protocol::kFastPairInitial);
device->set_classic_address(kTestClassicAddress1);
device->set_display_name(kDeviceDisplayName);
fast_pair_repository_->WriteAccountAssociationToFootprints(device,
kAccountKey1);
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
auto run_loop = base::RunLoop();
// Check for the device, this will also load the device into the cache.
fast_pair_repository_->CheckAccountKeys(
filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
base::Unretained(this), run_loop.QuitClosure(),
/*expected_result=*/true));
// Remove the device directly from footprints. This is equivalent to the
// device being removed on an Android phone or another Chromebook.
footprints_fetcher_->DeleteUserDevice(base::HexEncode(kAccountKey1),
base::DoNothing());
// Set the response to replicate an error getting devices from the server.
footprints_fetcher_->SetGetUserDevicesResponse(std::nullopt);
// After >30 minutes, cache is stale but we will fail to get devices from
// the server so we use the stale cache with the device still present.
task_environment()->FastForwardBy(base::Minutes(31));
fast_pair_repository_->CheckAccountKeys(
filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
base::Unretained(this), run_loop.QuitClosure(),
/*expected_result=*/true));
run_loop.Run();
}
TEST_F(FastPairRepositoryImplTest, GetDeviceNameFromCache) {
AccountKeyFilter filter(kFilterBytes1, {salt});
nearby::fastpair::GetObservedDeviceResponse response;
DeviceMetadata metadata(response, gfx::Image());
auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
Protocol::kFastPairSubsequent);
device->set_classic_address(kTestClassicAddress1);
device->set_account_key(kAccountKey1);
device->set_display_name(kDeviceDisplayName);
fast_pair_repository_->WriteAccountAssociationToLocalRegistry(device);
fast_pair_repository_->WriteAccountAssociationToFootprints(device,
kAccountKey1);
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
auto run_loop = base::RunLoop();
// Check for the device, this will load the device into the cache.
fast_pair_repository_->CheckAccountKeys(
filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
base::Unretained(this), run_loop.QuitClosure(),
/*expected_result=*/true));
run_loop.Run();
device->set_display_name(fast_pair_repository_->GetDeviceDisplayNameFromCache(
device->account_key().value()));
ASSERT_EQ(kDeviceDisplayName, device->display_name());
}
TEST_F(FastPairRepositoryImplTest, ChangeDeviceNameFromCache) {
AccountKeyFilter filter(kFilterBytes1, {salt});
nearby::fastpair::GetObservedDeviceResponse response;
DeviceMetadata metadata(response, gfx::Image());
auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
Protocol::kFastPairInitial);
device->set_classic_address(kTestClassicAddress1);
device->set_account_key(kAccountKey1);
device->set_display_name(kDeviceDisplayName);
fast_pair_repository_->WriteAccountAssociationToLocalRegistry(device);
fast_pair_repository_->WriteAccountAssociationToFootprints(device,
kAccountKey1);
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
ASSERT_TRUE(
saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
auto run_loop = base::RunLoop();
// Check for the device, this will load the device into the cache.
fast_pair_repository_->CheckAccountKeys(
filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
base::Unretained(this), run_loop.QuitClosure(),
/*expected_result=*/true));
device->set_display_name(fast_pair_repository_->GetDeviceDisplayNameFromCache(
device->account_key().value()));
ASSERT_EQ(device->display_name(), kDeviceDisplayName);
fast_pair_repository_->UpdateAssociatedDeviceFootprintsName(
kTestClassicAddress1, kDeviceDisplayName2, /*cache_may_be_stale=*/true);
// Check for the device, this will load the device into the cache.
fast_pair_repository_->CheckAccountKeys(
filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
base::Unretained(this), run_loop.QuitClosure(),
/*expected_result=*/true));
device->set_display_name(fast_pair_repository_->GetDeviceDisplayNameFromCache(
device->account_key().value()));
ASSERT_EQ(device->display_name(), kDeviceDisplayName2);
}
TEST_F(FastPairRepositoryImplTest, ChangeDeviceNameNotInCache) {
AccountKeyFilter filter(kFilterBytes1, {salt});
nearby::fastpair::GetObservedDeviceResponse response;
DeviceMetadata metadata(response, gfx::Image());
auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
Protocol::kFastPairInitial);
device->set_classic_address(kTestClassicAddress1);
device->set_account_key(kAccountKey1);
device->set_display_name(kDeviceDisplayName);
fast_pair_repository_->WriteAccountAssociationToLocalRegistry(device);
fast_pair_repository_->WriteAccountAssociationToFootprints(device,
kAccountKey1);
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
ASSERT_TRUE(
saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
auto run_loop = base::RunLoop();
fast_pair_repository_->UpdateAssociatedDeviceFootprintsName(
kTestClassicAddress1, kDeviceDisplayName2, /*cache_may_be_stale=*/true);
base::RunLoop().RunUntilIdle();
// Check for the device, this will load the device into the cache.
fast_pair_repository_->CheckAccountKeys(
filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
base::Unretained(this), run_loop.QuitClosure(),
/*expected_result=*/true));
base::RunLoop().RunUntilIdle();
device->set_display_name(fast_pair_repository_->GetDeviceDisplayNameFromCache(
device->account_key().value()));
ASSERT_EQ(device->display_name(), kDeviceDisplayName2);
}
TEST_F(FastPairRepositoryImplTest,
UpdateAssociatedDeviceFootprintsName_No_Matching_Account_key) {
AccountKeyFilter filter(kFilterBytes1, {salt});
auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
Protocol::kFastPairInitial);
device->set_classic_address(kTestClassicAddress1);
device->set_account_key(kAccountKey1);
fast_pair_repository_->UpdateAssociatedDeviceFootprintsName(
kTestClassicAddress1, kDeviceDisplayName2, /*cache_may_be_stale=*/true);
base::RunLoop().RunUntilIdle();
auto run_loop = base::RunLoop();
// Check for the device, this will load the device into the cache.
fast_pair_repository_->CheckAccountKeys(
filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
base::Unretained(this), run_loop.QuitClosure(),
/*expected_result=*/false));
device->set_display_name(fast_pair_repository_->GetDeviceDisplayNameFromCache(
device->account_key().value()));
}
TEST_F(FastPairRepositoryImplTest, LocalRemoveDeviceUpdatesCache) {
AccountKeyFilter filter(kFilterBytes1, {salt});
nearby::fastpair::GetObservedDeviceResponse response;
DeviceMetadata metadata(response, gfx::Image());
auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
Protocol::kFastPairInitial);
device->set_classic_address(kTestClassicAddress1);
device->set_account_key(kAccountKey1);
device->set_display_name(kDeviceDisplayName);
fast_pair_repository_->WriteAccountAssociationToLocalRegistry(device);
fast_pair_repository_->WriteAccountAssociationToFootprints(device,
kAccountKey1);
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
ASSERT_TRUE(
saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
auto run_loop = base::RunLoop();
// Check for the device, this will also load the device into the cache.
fast_pair_repository_->CheckAccountKeys(
filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
base::Unretained(this), run_loop.QuitClosure(),
/*expected_result=*/true));
// Remove the device as if this chromebook was removing it. This should
// invalidate the cache so the device will be removed there as well.
fast_pair_repository_->DeleteAssociatedDevice(
classic_bluetooth_device_.GetAddress(), base::DoNothing());
// Device should not appear in the cache.
fast_pair_repository_->CheckAccountKeys(
filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
base::Unretained(this), run_loop.QuitClosure(),
/*expected_result=*/false));
run_loop.Run();
}
TEST_F(FastPairRepositoryImplTest,
WriteAccountAssociationToFootprints_InvalidId) {
auto device = base::MakeRefCounted<Device>(kInvalidModelId, kTestBLEAddress,
Protocol::kFastPairInitial);
device->set_classic_address(kTestClassicAddress1);
device->set_display_name(kDeviceDisplayName);
fast_pair_repository_->WriteAccountAssociationToFootprints(device,
kAccountKey1);
base::RunLoop().RunUntilIdle();
ASSERT_FALSE(footprints_fetcher_->ContainsKey(kAccountKey1));
}
TEST_F(FastPairRepositoryImplTest,
WriteAccountAssociationToFootprints_ValidId) {
auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
Protocol::kFastPairInitial);
device->set_classic_address(kTestClassicAddress1);
device->set_display_name(kDeviceDisplayName);
fast_pair_repository_->WriteAccountAssociationToFootprints(device,
kAccountKey1);
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
EXPECT_EQ(histogram_tester().GetBucketCount(
kRetroactiveSuccessFunnelMetric,
FastPairRetroactiveSuccessFunnelEvent::kSaveComplete),
0);
}
TEST_F(FastPairRepositoryImplTest,
WriteAccountAssociationToFootprints_LogRetroactiveSuccessFunnel) {
auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
Protocol::kFastPairRetroactive);
device->set_classic_address(kTestClassicAddress1);
device->set_display_name(kDeviceDisplayName);
fast_pair_repository_->WriteAccountAssociationToFootprints(device,
kAccountKey1);
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
EXPECT_EQ(histogram_tester().GetBucketCount(
kRetroactiveSuccessFunnelMetric,
FastPairRetroactiveSuccessFunnelEvent::kSaveComplete),
1);
}
TEST_F(FastPairRepositoryImplTest, AssociateAccountKeyAndCheckName) {
AccountKeyFilter filter(kFilterBytes1, {salt});
auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
Protocol::kFastPairInitial);
device->set_classic_address(kTestClassicAddress1);
device->set_display_name(kDeviceDisplayName);
fast_pair_repository_->WriteAccountAssociationToFootprints(device,
kAccountKey1);
ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
auto run_loop = base::RunLoop();
// Check for the device, this will load the device into the cache.
fast_pair_repository_->CheckAccountKeys(
filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
base::Unretained(this), run_loop.QuitClosure(),
/*expected_result=*/true));
run_loop.Run();
// Device Account Key is set for testing purposes only.
device->set_account_key(kAccountKey1);
device->set_display_name(fast_pair_repository_->GetDeviceDisplayNameFromCache(
device->account_key().value()));
ASSERT_EQ(device->display_name(), kDeviceDisplayName);
}
TEST_F(FastPairRepositoryImplTest,
WriteAccountAssociationToLocalRegistry_InvalidNoAccountKey) {
auto device = base::MakeRefCounted<Device>(kInvalidModelId, kTestBLEAddress,
Protocol::kFastPairInitial);
device->set_classic_address(kTestClassicAddress1);
ASSERT_FALSE(
fast_pair_repository_->WriteAccountAssociationToLocalRegistry(device));
base::RunLoop().RunUntilIdle();
ASSERT_FALSE(
saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
}
TEST_F(FastPairRepositoryImplTest,
WriteAccountAssociationToLocalRegistry_ValidAccountKey) {
auto device = base::MakeRefCounted<Device>(kInvalidModelId, kTestBLEAddress,
Protocol::kFastPairInitial);
device->set_classic_address(kTestClassicAddress1);
device->set_account_key(kAccountKey1);
ASSERT_TRUE(
fast_pair_repository_->WriteAccountAssociationToLocalRegistry(device));
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(
saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
}
TEST_F(FastPairRepositoryImplTest, DeleteAssociatedDevice_Valid) {
AccountKeyFilter filter(kFilterBytes1, {salt});
nearby::fastpair::GetObservedDeviceResponse response;
DeviceMetadata metadata(response, gfx::Image());
auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
Protocol::kFastPairInitial);
device->set_classic_address(kTestClassicAddress1);
device->set_account_key(kAccountKey1);
device->set_display_name(kDeviceDisplayName);
fast_pair_repository_->WriteAccountAssociationToLocalRegistry(device);
fast_pair_repository_->WriteAccountAssociationToFootprints(device,
kAccountKey1);
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
ASSERT_TRUE(
saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
base::MockCallback<base::OnceCallback<void(bool)>> callback;
EXPECT_CALL(callback, Run(testing::Eq(true))).Times(1);
fast_pair_repository_->DeleteAssociatedDevice(
classic_bluetooth_device_.GetAddress(), callback.Get());
base::RunLoop().RunUntilIdle();
ASSERT_FALSE(footprints_fetcher_->ContainsKey(kAccountKey1));
ASSERT_FALSE(
saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
ASSERT_EQ(0u, pending_write_store_->GetPendingDeletes().size());
}
TEST_F(FastPairRepositoryImplTest, DeleteAssociatedDevice_Invalid) {
base::MockCallback<base::OnceCallback<void(bool)>> callback;
EXPECT_CALL(callback, Run(testing::Eq(false))).Times(1);
fast_pair_repository_->DeleteAssociatedDevice(
classic_bluetooth_device_.GetAddress(), callback.Get());
ASSERT_EQ(0u, pending_write_store_->GetPendingDeletes().size());
}
TEST_F(FastPairRepositoryImplTest, DeleteAssociatedDeviceByAccountKey_Valid) {
AccountKeyFilter filter(kFilterBytes1, {salt});
nearby::fastpair::GetObservedDeviceResponse response;
DeviceMetadata metadata(response, gfx::Image());
auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
Protocol::kFastPairInitial);
device->set_classic_address(kTestClassicAddress1);
device->set_account_key(kAccountKey1);
device->set_display_name(kDeviceDisplayName);
fast_pair_repository_->WriteAccountAssociationToLocalRegistry(device);
fast_pair_repository_->WriteAccountAssociationToFootprints(device,
kAccountKey1);
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
ASSERT_TRUE(
saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
base::MockCallback<base::OnceCallback<void(bool)>> callback;
EXPECT_CALL(callback, Run(testing::Eq(true))).Times(1);
fast_pair_repository_->DeleteAssociatedDeviceByAccountKey(kAccountKey1,
callback.Get());
base::RunLoop().RunUntilIdle();
ASSERT_FALSE(footprints_fetcher_->ContainsKey(kAccountKey1));
ASSERT_FALSE(
saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
ASSERT_EQ(0u, pending_write_store_->GetPendingDeletes().size());
}
TEST_F(FastPairRepositoryImplTest, RetriesForgetDevice_AfterNetworkAvailable) {
auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
Protocol::kFastPairInitial);
device->set_classic_address(kTestClassicAddress1);
device->set_account_key(kAccountKey1);
device->set_display_name(kDeviceDisplayName);
fast_pair_repository_->WriteAccountAssociationToLocalRegistry(device);
fast_pair_repository_->WriteAccountAssociationToFootprints(device,
kAccountKey1);
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
ASSERT_TRUE(
saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
ASSERT_EQ(0u, pending_write_store_->GetPendingDeletes().size());
base::MockCallback<base::OnceCallback<void(bool)>> callback;
EXPECT_CALL(callback, Run(testing::Eq(false))).Times(1);
// Mock an error due to Network failure.
footprints_fetcher_->SetDeleteUserDeviceResult(false);
fast_pair_repository_->DeleteAssociatedDevice(
classic_bluetooth_device_.GetAddress(), callback.Get());
base::RunLoop().RunUntilIdle();
// The failed delete should be saved as a pending delete.
ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
ASSERT_TRUE(
saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
ASSERT_EQ(1u, pending_write_store_->GetPendingDeletes().size());
// Reconnect to the Network, but fail again.
fast_pair_repository_->DefaultNetworkChanged(
helper_.network_state_handler()->DefaultNetwork());
base::RunLoop().RunUntilIdle();
// The delete, after another failed retry, should still be pending.
ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
ASSERT_TRUE(
saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
ASSERT_EQ(1u, pending_write_store_->GetPendingDeletes().size());
// Reconnect to the Network, but within the 1 minute timeout.
footprints_fetcher_->SetDeleteUserDeviceResult(true);
fast_pair_repository_->DefaultNetworkChanged(
helper_.network_state_handler()->DefaultNetwork());
base::RunLoop().RunUntilIdle();
// Since we don't retry within 1 minute, the delete should still be pending.
ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
ASSERT_TRUE(
saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
ASSERT_EQ(1u, pending_write_store_->GetPendingDeletes().size());
// Mock waiting out the 1 minute timeout.
task_environment()->FastForwardBy(base::Minutes(1));
base::RunLoop().RunUntilIdle();
// Reconnect to the Network, but after the 1 minute timeout.
footprints_fetcher_->SetDeleteUserDeviceResult(true);
fast_pair_repository_->DefaultNetworkChanged(
helper_.network_state_handler()->DefaultNetwork());
base::RunLoop().RunUntilIdle();
// The delete, after a successful retry, should no longer be pending.
ASSERT_FALSE(footprints_fetcher_->ContainsKey(kAccountKey1));
ASSERT_FALSE(
saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
ASSERT_EQ(0u, pending_write_store_->GetPendingDeletes().size());
}
// TODO(crbug.com/40264951): Re-enable this test
#if defined(MEMORY_SANITIZER)
#define MAYBE_RetriesForgetDevice_AlreadyDeleted \
DISABLED_RetriesForgetDevice_AlreadyDeleted
#else
#define MAYBE_RetriesForgetDevice_AlreadyDeleted \
RetriesForgetDevice_AlreadyDeleted
#endif
TEST_F(FastPairRepositoryImplTest, MAYBE_RetriesForgetDevice_AlreadyDeleted) {
auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
Protocol::kFastPairInitial);
device->set_classic_address(kTestClassicAddress1);
device->set_account_key(kAccountKey1);
device->set_display_name(kDeviceDisplayName);
fast_pair_repository_->WriteAccountAssociationToLocalRegistry(device);
fast_pair_repository_->WriteAccountAssociationToFootprints(device,
kAccountKey1);
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
ASSERT_TRUE(
saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
ASSERT_EQ(0u, pending_write_store_->GetPendingDeletes().size());
base::MockCallback<base::OnceCallback<void(bool)>> callback;
EXPECT_CALL(callback, Run(testing::Eq(false))).Times(1);
// Mock an error due to Network failure.
footprints_fetcher_->SetDeleteUserDeviceResult(false);
fast_pair_repository_->DeleteAssociatedDevice(
classic_bluetooth_device_.GetAddress(), callback.Get());
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
ASSERT_TRUE(
saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
ASSERT_EQ(1u, pending_write_store_->GetPendingDeletes().size());
// Mock Footprints getting updated by another CB/Android such that the
// saved device is successfully deleted.
footprints_fetcher_->SetDeleteUserDeviceResult(true);
footprints_fetcher_->DeleteUserDevice(
base::HexEncode(
std::vector<uint8_t>(kAccountKey1.begin(), kAccountKey1.end())),
base::DoNothing());
base::RunLoop().RunUntilIdle();
ASSERT_FALSE(footprints_fetcher_->ContainsKey(kAccountKey1));
ASSERT_TRUE(
saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
ASSERT_EQ(1u, pending_write_store_->GetPendingDeletes().size());
// Reconnect to the Network.
fast_pair_repository_->DefaultNetworkChanged(
helper_.network_state_handler()->DefaultNetwork());
base::RunLoop().RunUntilIdle();
// The delete, after a successful retry, should no longer be pending.
ASSERT_FALSE(footprints_fetcher_->ContainsKey(kAccountKey1));
ASSERT_FALSE(
saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
ASSERT_EQ(0u, pending_write_store_->GetPendingDeletes().size());
}
TEST_F(FastPairRepositoryImplTest, RetriesForgetDevice_MultipleDevices) {
auto device1 = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
Protocol::kFastPairInitial);
device1->set_classic_address(kTestClassicAddress1);
device1->set_account_key(kAccountKey1);
device1->set_display_name(kDeviceDisplayName);
fast_pair_repository_->WriteAccountAssociationToFootprints(device1,
kAccountKey1);
fast_pair_repository_->WriteAccountAssociationToLocalRegistry(device1);
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
ASSERT_TRUE(
saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
auto device2 = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress2,
Protocol::kFastPairInitial);
device2->set_classic_address(kTestClassicAddress2);
device2->set_account_key(kAccountKey2);
device2->set_display_name(kDeviceDisplayName);
fast_pair_repository_->WriteAccountAssociationToFootprints(device2,
kAccountKey2);
fast_pair_repository_->WriteAccountAssociationToLocalRegistry(device2);
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey2));
ASSERT_TRUE(
saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey2));
ASSERT_EQ(0u, pending_write_store_->GetPendingDeletes().size());
base::MockCallback<base::OnceCallback<void(bool)>> callback1;
EXPECT_CALL(callback1, Run(testing::Eq(false))).Times(1);
// Mock an error due to Network failure for device1.
footprints_fetcher_->SetDeleteUserDeviceResult(false);
fast_pair_repository_->DeleteAssociatedDevice(kTestClassicAddress1,
callback1.Get());
base::RunLoop().RunUntilIdle();
// The failed delete should be saved as a pending delete.
ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
ASSERT_TRUE(
saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
ASSERT_EQ(1u, pending_write_store_->GetPendingDeletes().size());
base::MockCallback<base::OnceCallback<void(bool)>> callback2;
EXPECT_CALL(callback2, Run(testing::Eq(false))).Times(1);
// Mock an error due to Network failure for device2.
footprints_fetcher_->SetDeleteUserDeviceResult(false);
fast_pair_repository_->DeleteAssociatedDevice(kTestClassicAddress2,
callback2.Get());
base::RunLoop().RunUntilIdle();
// The failed deletes should be saved as pending deletes.
ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey2));
ASSERT_TRUE(
saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey2));
ASSERT_EQ(2u, pending_write_store_->GetPendingDeletes().size());
// Reconnect to the Network.
footprints_fetcher_->SetDeleteUserDeviceResult(true);
fast_pair_repository_->DefaultNetworkChanged(
helper_.network_state_handler()->DefaultNetwork());
base::RunLoop().RunUntilIdle();
// Both deletes should be retried and removed from pending write store.
ASSERT_FALSE(footprints_fetcher_->ContainsKey(kAccountKey1));
ASSERT_FALSE(footprints_fetcher_->ContainsKey(kAccountKey2));
ASSERT_FALSE(
saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
ASSERT_FALSE(
saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey2));
ASSERT_EQ(0u, pending_write_store_->GetPendingDeletes().size());
}
TEST_F(FastPairRepositoryImplTest, FetchDeviceImages) {
ASSERT_FALSE(
device_address_map_->GetModelIdForMacAddress(kTestClassicAddress1));
ASSERT_FALSE(device_image_store_->GetImagesForDeviceModel(kValidModelId));
AccountKeyFilter filter(kFilterBytes1, {salt});
nearby::fastpair::GetObservedDeviceResponse response;
DeviceMetadata metadata(response, gfx::Image());
auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
Protocol::kFastPairInitial);
device->set_classic_address(kTestClassicAddress1);
fast_pair_repository_->FetchDeviceImages(device);
ASSERT_TRUE(
device_address_map_->GetModelIdForMacAddress(kTestClassicAddress1));
ASSERT_TRUE(fast_pair_repository_->GetImagesForDevice(kTestClassicAddress1));
}
TEST_F(FastPairRepositoryImplTest, FetchDeviceImagesNoMacAddress) {
ASSERT_FALSE(
device_address_map_->GetModelIdForMacAddress(kTestClassicAddress1));
ASSERT_FALSE(device_image_store_->GetImagesForDeviceModel(kValidModelId));
AccountKeyFilter filter(kFilterBytes1, {salt});
nearby::fastpair::GetObservedDeviceResponse response;
DeviceMetadata metadata(response, gfx::Image());
// Don't set the classic address of the device, which should result in the
// mac address to model ID record failin to save.
auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
Protocol::kFastPairInitial);
fast_pair_repository_->FetchDeviceImages(device);
// We expect that FetchDeviceImages will first be called while the classic
// address of the device isn't set. Images should still be downloaded and
// linked to the corresponding model ID.
ASSERT_FALSE(
device_address_map_->GetModelIdForMacAddress(kTestClassicAddress1));
ASSERT_FALSE(fast_pair_repository_->GetImagesForDevice(kTestClassicAddress1));
ASSERT_TRUE(device_image_store_->GetImagesForDeviceModel(kValidModelId));
// Mimic the device now completing pairing, which should set the classic
// address and attempt to FetchDeviceImages a second time.
device->set_classic_address(kTestClassicAddress1);
fast_pair_repository_->FetchDeviceImages(device);
ASSERT_TRUE(
device_address_map_->GetModelIdForMacAddress(kTestClassicAddress1));
ASSERT_TRUE(fast_pair_repository_->GetImagesForDevice(kTestClassicAddress1));
}
TEST_F(FastPairRepositoryImplTest, PersistDeviceImages) {
ASSERT_FALSE(
device_address_map_->GetModelIdForMacAddress(kTestClassicAddress1));
ASSERT_FALSE(device_image_store_->GetImagesForDeviceModel(kValidModelId));
AccountKeyFilter filter(kFilterBytes1, {salt});
nearby::fastpair::GetObservedDeviceResponse response;
DeviceMetadata metadata(response, gfx::Image());
auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
Protocol::kFastPairInitial);
device->set_classic_address(kTestClassicAddress1);
fast_pair_repository_->FetchDeviceImages(device);
ASSERT_FALSE(
device_address_map_->HasPersistedRecordsForModelId(kValidModelId));
// Persisting should succeed, which allows the images to be found even after
// the local caches are cleared (i.e. on logout).
ASSERT_TRUE(fast_pair_repository_->PersistDeviceImages(device));
device_address_map_->RefreshCacheForTest();
device_image_store_->RefreshCacheForTest();
ASSERT_TRUE(
device_address_map_->GetModelIdForMacAddress(kTestClassicAddress1));
ASSERT_TRUE(fast_pair_repository_->GetImagesForDevice(kTestClassicAddress1));
ASSERT_TRUE(
device_address_map_->HasPersistedRecordsForModelId(kValidModelId));
}
TEST_F(FastPairRepositoryImplTest, PersistDeviceImagesNoMacAddress) {
ASSERT_FALSE(
device_address_map_->GetModelIdForMacAddress(kTestClassicAddress1));
ASSERT_FALSE(device_image_store_->GetImagesForDeviceModel(kValidModelId));
AccountKeyFilter filter(kFilterBytes1, {salt});
nearby::fastpair::GetObservedDeviceResponse response;
DeviceMetadata metadata(response, gfx::Image());
// Don't set the classic address of the device, which should result in the
// mac address to model ID record failin to save.
auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
Protocol::kFastPairInitial);
fast_pair_repository_->FetchDeviceImages(device);
// We expect that FetchDeviceImages will first be called while the classic
// address of the device isn't set. Images should still be downloaded and
// linked to the corresponding model ID.
ASSERT_FALSE(
device_address_map_->GetModelIdForMacAddress(kTestClassicAddress1));
ASSERT_FALSE(fast_pair_repository_->GetImagesForDevice(kTestClassicAddress1));
ASSERT_TRUE(device_image_store_->GetImagesForDeviceModel(kValidModelId));
// Even though there are images saved that we could persist, we should not
// persist them since we lack the corresponding mac address to model ID
// mapping.
ASSERT_FALSE(fast_pair_repository_->PersistDeviceImages(device));
device_address_map_->RefreshCacheForTest();
device_image_store_->RefreshCacheForTest();
ASSERT_FALSE(
device_address_map_->HasPersistedRecordsForModelId(kValidModelId));
ASSERT_FALSE(device_image_store_->GetImagesForDeviceModel(kValidModelId));
}
TEST_F(FastPairRepositoryImplTest, EvictDeviceImages) {
AccountKeyFilter filter(kFilterBytes1, {salt});
nearby::fastpair::GetObservedDeviceResponse response;
DeviceMetadata metadata(response, gfx::Image());
auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
Protocol::kFastPairInitial);
device->set_classic_address(kTestClassicAddress1);
fast_pair_repository_->FetchDeviceImages(device);
fast_pair_repository_->PersistDeviceImages(device);
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(
device_address_map_->GetModelIdForMacAddress(kTestClassicAddress1));
ASSERT_TRUE(device_image_store_->GetImagesForDeviceModel(kValidModelId));
ASSERT_TRUE(
device_address_map_->HasPersistedRecordsForModelId(kValidModelId));
fast_pair_repository_->EvictDeviceImages(kTestClassicAddress1);
ASSERT_FALSE(
device_address_map_->HasPersistedRecordsForModelId(kValidModelId));
device_address_map_->RefreshCacheForTest();
device_image_store_->RefreshCacheForTest();
ASSERT_FALSE(
device_address_map_->GetModelIdForMacAddress(kTestClassicAddress1));
ASSERT_FALSE(device_image_store_->GetImagesForDeviceModel(kValidModelId));
}
// TODO(crbug.com/40264951): Re-enable this test
#if defined(MEMORY_SANITIZER)
#define MAYBE_UpdateOptInStatus_OptedIn DISABLED_UpdateOptInStatus_OptedIn
#else
#define MAYBE_UpdateOptInStatus_OptedIn UpdateOptInStatus_OptedIn
#endif
TEST_F(FastPairRepositoryImplTest, MAYBE_UpdateOptInStatus_OptedIn) {
base::MockCallback<base::OnceCallback<void(bool)>> callback1;
EXPECT_CALL(callback1, Run(true)).Times(1);
fast_pair_repository_->UpdateOptInStatus(
nearby::fastpair::OptInStatus::STATUS_OPTED_IN, callback1.Get());
base::RunLoop().RunUntilIdle();
base::MockCallback<base::OnceCallback<void(nearby::fastpair::OptInStatus)>>
callback2;
EXPECT_CALL(callback2,
Run(testing::Eq(nearby::fastpair::OptInStatus::STATUS_OPTED_IN)))
.Times(1);
fast_pair_repository_->CheckOptInStatus(callback2.Get());
}
TEST_F(FastPairRepositoryImplTest, UpdateOptInStatus_OptedOut) {
base::MockCallback<base::OnceCallback<void(bool)>> callback1;
EXPECT_CALL(callback1, Run(true)).Times(1);
fast_pair_repository_->UpdateOptInStatus(
nearby::fastpair::OptInStatus::STATUS_OPTED_OUT, callback1.Get());
base::RunLoop().RunUntilIdle();
base::MockCallback<base::OnceCallback<void(nearby::fastpair::OptInStatus)>>
callback2;
EXPECT_CALL(callback2,
Run(testing::Eq(nearby::fastpair::OptInStatus::STATUS_OPTED_OUT)))
.Times(1);
fast_pair_repository_->CheckOptInStatus(callback2.Get());
}
// TODO(crbug.com/40264951): Re-enable this test
#if defined(MEMORY_SANITIZER)
#define MAYBE_UpdateOptInStatus_StatusUnknown \
DISABLED_UpdateOptInStatus_StatusUnknown
#else
#define MAYBE_UpdateOptInStatus_StatusUnknown UpdateOptInStatus_StatusUnknown
#endif
TEST_F(FastPairRepositoryImplTest, MAYBE_UpdateOptInStatus_StatusUnknown) {
base::MockCallback<base::OnceCallback<void(bool)>> callback1;
EXPECT_CALL(callback1, Run(true)).Times(1);
fast_pair_repository_->UpdateOptInStatus(
nearby::fastpair::OptInStatus::STATUS_UNKNOWN, callback1.Get());
base::RunLoop().RunUntilIdle();
base::MockCallback<base::OnceCallback<void(nearby::fastpair::OptInStatus)>>
callback2;
EXPECT_CALL(callback2,
Run(testing::Eq(nearby::fastpair::OptInStatus::STATUS_UNKNOWN)))
.Times(1);
fast_pair_repository_->CheckOptInStatus(callback2.Get());
}
TEST_F(FastPairRepositoryImplTest, UpdateOptInStatus_NoFootprintsResponse) {
footprints_fetcher_->SetGetUserDevicesResponse(std::nullopt);
base::MockCallback<base::OnceCallback<void(nearby::fastpair::OptInStatus)>>
callback;
EXPECT_CALL(callback,
Run(testing::Eq(nearby::fastpair::OptInStatus::STATUS_UNKNOWN)))
.Times(1);
fast_pair_repository_->CheckOptInStatus(callback.Get());
}
// TODO(crbug.com/40264951): Re-enable this test
#if defined(MEMORY_SANITIZER)
#define MAYBE_UpdateOptInStatus_OptedInUpdateFailed \
DISABLED_UpdateOptInStatus_OptedInUpdateFailed
#else
#define MAYBE_UpdateOptInStatus_OptedInUpdateFailed \
UpdateOptInStatus_OptedInUpdateFailed
#endif
TEST_F(FastPairRepositoryImplTest,
MAYBE_UpdateOptInStatus_OptedInUpdateFailed) {
footprints_fetcher_->SetAddUserFastPairInfoResult(/*add_user_result=*/false);
base::MockCallback<base::OnceCallback<void(bool)>> callback1;
EXPECT_CALL(callback1, Run(false)).Times(1);
fast_pair_repository_->UpdateOptInStatus(
nearby::fastpair::OptInStatus::STATUS_OPTED_IN, callback1.Get());
base::RunLoop().RunUntilIdle();
base::MockCallback<base::OnceCallback<void(nearby::fastpair::OptInStatus)>>
callback2;
EXPECT_CALL(callback2,
Run(testing::Eq(nearby::fastpair::OptInStatus::STATUS_UNKNOWN)))
.Times(1);
fast_pair_repository_->CheckOptInStatus(callback2.Get());
}
TEST_F(FastPairRepositoryImplTest, GetSavedDevices_OptedIn) {
histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
/*success=*/true, 0);
histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
/*success=*/false, 0);
fast_pair_repository_->UpdateOptInStatus(
nearby::fastpair::OptInStatus::STATUS_OPTED_IN, base::DoNothing());
base::RunLoop().RunUntilIdle();
AccountKeyFilter filter(kFilterBytes1, {salt});
nearby::fastpair::GetObservedDeviceResponse response;
DeviceMetadata metadata(response, gfx::Image());
auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
Protocol::kFastPairInitial);
device->set_classic_address(kTestClassicAddress1);
device->set_display_name(kDeviceDisplayName);
fast_pair_repository_->WriteAccountAssociationToFootprints(device,
kAccountKey1);
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
base::RunLoop().RunUntilIdle();
fast_pair_repository_->GetSavedDevices(
base::BindOnce(&FastPairRepositoryImplTest::GetSavedDevicesCallback,
weak_ptr_factory_.GetWeakPtr()));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(nearby::fastpair::OptInStatus::STATUS_OPTED_IN, status_);
EXPECT_EQ(1u, devices_.size());
histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
/*success=*/true, 1);
histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
/*success=*/false, 0);
}
TEST_F(FastPairRepositoryImplTest, GetSavedDevices_OptedOut) {
histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
/*success=*/true, 0);
histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
/*success=*/false, 0);
fast_pair_repository_->UpdateOptInStatus(
nearby::fastpair::OptInStatus::STATUS_OPTED_OUT, base::DoNothing());
base::RunLoop().RunUntilIdle();
fast_pair_repository_->GetSavedDevices(
base::BindOnce(&FastPairRepositoryImplTest::GetSavedDevicesCallback,
weak_ptr_factory_.GetWeakPtr()));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(nearby::fastpair::OptInStatus::STATUS_OPTED_OUT, status_);
EXPECT_EQ(0u, devices_.size());
histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
/*success=*/true, 1);
histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
/*success=*/false, 0);
}
TEST_F(FastPairRepositoryImplTest, GetSavedDevices_OptStatusUnknown) {
histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
/*success=*/true, 0);
histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
/*success=*/false, 0);
fast_pair_repository_->UpdateOptInStatus(
nearby::fastpair::OptInStatus::STATUS_UNKNOWN, base::DoNothing());
base::RunLoop().RunUntilIdle();
fast_pair_repository_->GetSavedDevices(
base::BindOnce(&FastPairRepositoryImplTest::GetSavedDevicesCallback,
weak_ptr_factory_.GetWeakPtr()));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(nearby::fastpair::OptInStatus::STATUS_UNKNOWN, status_);
EXPECT_EQ(0u, devices_.size());
histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
/*success=*/true, 1);
histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
/*success=*/false, 0);
}
TEST_F(FastPairRepositoryImplTest, GetSavedDevices_MissingResponse) {
histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
/*success=*/true, 0);
histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
/*success=*/false, 0);
footprints_fetcher_->SetGetUserDevicesResponse(std::nullopt);
fast_pair_repository_->GetSavedDevices(
base::BindOnce(&FastPairRepositoryImplTest::GetSavedDevicesCallback,
weak_ptr_factory_.GetWeakPtr()));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(nearby::fastpair::OptInStatus::
STATUS_ERROR_RETRIEVING_FROM_FOOTPRINTS_SERVER,
status_);
EXPECT_EQ(0u, devices_.size());
histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
/*success=*/true, 0);
histogram_tester().ExpectBucketCount(kSavedDeviceGetDevicesResultMetricName,
/*success=*/false, 1);
}
TEST_F(FastPairRepositoryImplTest,
IsAccountKeyPairedLocally_SavedLocallyNotPaired) {
// Simulate a device already saved to the registry. A Fast Pair device can
// be saved in the registry even if it is not paired locally because
// the SavedDeviceRegistry tracks devices that have been Fast paired in the
// past.
bool success = saved_device_registry_->SaveAccountAssociation(
kTestClassicAddress1, kAccountKey1);
EXPECT_TRUE(success);
EXPECT_TRUE(
saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
EXPECT_TRUE(fast_pair_repository_->IsAccountKeyPairedLocally(kAccountKey1));
EXPECT_FALSE(fast_pair_repository_->IsAccountKeyPairedLocally(kAccountKey2));
}
// TODO(crbug.com/40264951): Re-enable this test
#if defined(MEMORY_SANITIZER)
#define MAYBE_IsAccountKeyPairedLocally_PairedNotSavedLocally \
DISABLED_IsAccountKeyPairedLocally_PairedNotSavedLocally
#else
#define MAYBE_IsAccountKeyPairedLocally_PairedNotSavedLocally \
IsAccountKeyPairedLocally_PairedNotSavedLocally
#endif
TEST_F(FastPairRepositoryImplTest,
MAYBE_IsAccountKeyPairedLocally_PairedNotSavedLocally) {
// Simulate a device saved to a user's account that matches a device paired
// with devices |adapter_| is loaded with. In the init of the |adapter_|, we
// mocked `GetDevices` with |device_list_|.
nearby::fastpair::FastPairInfo info;
auto* device = info.mutable_device();
device->set_account_key(
std::string(kAccountKey1.begin(), kAccountKey1.end()));
device->set_sha256_account_key_public_address(
GenerateSha256AccountKeyMacAddress(
std::string(kAccountKey1.begin(), kAccountKey1.end()),
kTestClassicAddress1));
nearby::fastpair::UserReadDevicesResponse response;
*response.add_fast_pair_info() = info;
footprints_fetcher_->SetGetUserDevicesResponse(response);
// We want to simulate the cache being updated when it is parsing a
// NotDiscoverableAdv, which happens when it is checking an account key.
AccountKeyFilter filter(kFilterBytes1, {salt});
auto run_loop = base::RunLoop();
fast_pair_repository_->CheckAccountKeys(
filter, base::BindOnce(&FastPairRepositoryImplTest::VerifyAccountKeyCheck,
base::Unretained(this), run_loop.QuitClosure(),
/*expected_result=*/false));
run_loop.Run();
// At this point the cache will be updated with any devices Saved to
// Footprints. We can continue now checking if it matches any paired devices.
EXPECT_TRUE(fast_pair_repository_->IsAccountKeyPairedLocally(kAccountKey1));
EXPECT_FALSE(fast_pair_repository_->IsAccountKeyPairedLocally(kAccountKey2));
}
TEST_F(FastPairRepositoryImplTest, IsDeviceSavedToAccount_Match) {
nearby::fastpair::FastPairInfo info;
auto* device = info.mutable_device();
device->set_account_key(
std::string(kAccountKey1.begin(), kAccountKey1.end()));
device->set_sha256_account_key_public_address(
GenerateSha256AccountKeyMacAddress(
std::string(kAccountKey1.begin(), kAccountKey1.end()),
kTestClassicAddress1));
nearby::fastpair::UserReadDevicesResponse response;
*response.add_fast_pair_info() = info;
footprints_fetcher_->SetGetUserDevicesResponse(response);
base::MockCallback<base::OnceCallback<void(bool)>> callback;
EXPECT_CALL(callback, Run(testing::Eq(true))).Times(1);
fast_pair_repository_->IsDeviceSavedToAccount(kTestClassicAddress1,
callback.Get());
base::RunLoop().RunUntilIdle();
}
TEST_F(FastPairRepositoryImplTest,
IsDeviceSavedToAccount_MatchKnownAndroidValues) {
// In this test, we use a known triple from Android to ensure this works
// cross-platform.
nearby::fastpair::FastPairInfo info;
auto* device = info.mutable_device();
device->set_account_key(Base64Decode(kBase64AccountKey));
device->set_sha256_account_key_public_address(
Base64Decode(kBase64ExpectedSha256Hash));
nearby::fastpair::UserReadDevicesResponse response;
*response.add_fast_pair_info() = info;
footprints_fetcher_->SetGetUserDevicesResponse(response);
base::MockCallback<base::OnceCallback<void(bool)>> callback;
EXPECT_CALL(callback, Run(testing::Eq(true))).Times(1);
fast_pair_repository_->IsDeviceSavedToAccount(kTestClassicAddress3,
callback.Get());
base::RunLoop().RunUntilIdle();
}
// TODO(crbug.com/40264951): Re-enable this test
#if defined(MEMORY_SANITIZER)
#define MAYBE_IsDeviceSavedToAccount_IgnoreForgetPattern \
DISABLED_IsDeviceSavedToAccount_IgnoreForgetPattern
#else
#define MAYBE_IsDeviceSavedToAccount_IgnoreForgetPattern \
IsDeviceSavedToAccount_IgnoreForgetPattern
#endif
TEST_F(FastPairRepositoryImplTest,
MAYBE_IsDeviceSavedToAccount_IgnoreForgetPattern) {
// In this test, we use a known "marked as forgotten" hash
// from Android. Note that Android also leaves the account key
// blank; however, we add an account key here to test the logic
// of detecting the Forget pattern in the hash.
nearby::fastpair::FastPairInfo info;
auto* device = info.mutable_device();
device->set_account_key(Base64Decode(kBase64AccountKey));
device->set_sha256_account_key_public_address(
Base64Decode(kBase64ForgetPatternSha256Hash));
nearby::fastpair::UserReadDevicesResponse response;
*response.add_fast_pair_info() = info;
footprints_fetcher_->SetGetUserDevicesResponse(response);
base::MockCallback<base::OnceCallback<void(bool)>> callback;
EXPECT_CALL(callback, Run(testing::Eq(false))).Times(1);
fast_pair_repository_->IsDeviceSavedToAccount(kTestClassicAddress3,
callback.Get());
base::RunLoop().RunUntilIdle();
}
TEST_F(FastPairRepositoryImplTest, IsDeviceSavedToAccount_NoMatch) {
nearby::fastpair::FastPairInfo info;
auto* device = info.mutable_device();
device->set_account_key(
std::string(kAccountKey1.begin(), kAccountKey1.end()));
device->set_sha256_account_key_public_address(
GenerateSha256AccountKeyMacAddress(
std::string(kAccountKey1.begin(), kAccountKey1.end()),
kTestClassicAddress1));
nearby::fastpair::UserReadDevicesResponse response;
*response.add_fast_pair_info() = info;
footprints_fetcher_->SetGetUserDevicesResponse(response);
base::MockCallback<base::OnceCallback<void(bool)>> callback;
EXPECT_CALL(callback, Run(testing::Eq(false))).Times(1);
fast_pair_repository_->IsDeviceSavedToAccount(kTestClassicAddress2,
callback.Get());
base::RunLoop().RunUntilIdle();
}
TEST_F(FastPairRepositoryImplTest, IsDeviceSavedToAccount_MissingResponse) {
footprints_fetcher_->SetGetUserDevicesResponse(std::nullopt);
base::MockCallback<base::OnceCallback<void(bool)>> callback;
EXPECT_CALL(callback, Run(testing::Eq(false))).Times(1);
fast_pair_repository_->IsDeviceSavedToAccount(kTestClassicAddress1,
callback.Get());
base::RunLoop().RunUntilIdle();
}
TEST_F(FastPairRepositoryImplTest, IsDeviceSavedToAccount_MissingAccountKey) {
nearby::fastpair::FastPairInfo info;
auto* device = info.mutable_device();
device->set_sha256_account_key_public_address(
GenerateSha256AccountKeyMacAddress(
std::string(kAccountKey1.begin(), kAccountKey1.end()),
kTestClassicAddress1));
nearby::fastpair::UserReadDevicesResponse response;
*response.add_fast_pair_info() = info;
footprints_fetcher_->SetGetUserDevicesResponse(response);
base::MockCallback<base::OnceCallback<void(bool)>> callback;
EXPECT_CALL(callback, Run(testing::Eq(false))).Times(1);
fast_pair_repository_->IsDeviceSavedToAccount(kTestClassicAddress1,
callback.Get());
base::RunLoop().RunUntilIdle();
}
// TODO(crbug.com/40264951): Re-enable this test
#if defined(MEMORY_SANITIZER)
#define MAYBE_IsDeviceSavedToAccount_MissingSha \
DISABLED_IsDeviceSavedToAccount_MissingSha
#else
#define MAYBE_IsDeviceSavedToAccount_MissingSha \
IsDeviceSavedToAccount_MissingSha
#endif
TEST_F(FastPairRepositoryImplTest, MAYBE_IsDeviceSavedToAccount_MissingSha) {
nearby::fastpair::FastPairInfo info;
auto* device = info.mutable_device();
device->set_account_key(
std::string(kAccountKey1.begin(), kAccountKey1.end()));
nearby::fastpair::UserReadDevicesResponse response;
*response.add_fast_pair_info() = info;
footprints_fetcher_->SetGetUserDevicesResponse(response);
base::MockCallback<base::OnceCallback<void(bool)>> callback;
EXPECT_CALL(callback, Run(testing::Eq(false))).Times(1);
fast_pair_repository_->IsDeviceSavedToAccount(kTestClassicAddress1,
callback.Get());
base::RunLoop().RunUntilIdle();
}
TEST_F(FastPairRepositoryImplTest,
IsDeviceSavedToAccount_MissingShaAccountKey) {
nearby::fastpair::FastPairInfo info;
nearby::fastpair::UserReadDevicesResponse response;
*response.add_fast_pair_info() = info;
footprints_fetcher_->SetGetUserDevicesResponse(response);
base::MockCallback<base::OnceCallback<void(bool)>> callback;
EXPECT_CALL(callback, Run(testing::Eq(false))).Times(1);
fast_pair_repository_->IsDeviceSavedToAccount(kTestClassicAddress1,
callback.Get());
base::RunLoop().RunUntilIdle();
}
TEST_F(FastPairRepositoryImplTest, IsHashCorrect) {
nearby::fastpair::GetObservedDeviceResponse device;
DeviceMetadata metadata(device, gfx::Image());
std::string account_key = Base64Decode(kBase64AccountKey);
std::vector<uint8_t> account_key_bytes(account_key.begin(),
account_key.end());
// FakeFootprintsFetcher APIs are actually synchronous.
footprints_fetcher_->AddUserFastPairInfo(
BuildFastPairInfo(kValidModelId, account_key_bytes, kTestClassicAddress3,
kDeviceDisplayName, &metadata),
base::DoNothing());
base::MockCallback<base::OnceCallback<void(bool)>> callback;
EXPECT_CALL(callback, Run(testing::Eq(true))).Times(1);
fast_pair_repository_->IsDeviceSavedToAccount(kTestClassicAddress3,
callback.Get());
base::RunLoop().RunUntilIdle();
}
TEST_F(FastPairRepositoryImplTest,
WriteAccountAssociationToFootprints_RemoveDeviceFromPendingWriteStore) {
auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
Protocol::kFastPairInitial);
device->set_classic_address(kTestClassicAddress1);
device->set_display_name(kDeviceDisplayName);
fast_pair_repository_->WriteAccountAssociationToFootprints(device,
kAccountKey1);
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
// After a successful Footprints write, pending writes list should be empty.
ASSERT_EQ(0u, pending_write_store_->GetPendingWrites().size());
}
// TODO(crbug.com/40264951): Re-enable this test
#if defined(MEMORY_SANITIZER)
#define MAYBE_RetriesWriteDevice_AfterNetworkAvailable \
DISABLED_RetriesWriteDevice_AfterNetworkAvailable
#else
#define MAYBE_RetriesWriteDevice_AfterNetworkAvailable \
RetriesWriteDevice_AfterNetworkAvailable
#endif
TEST_F(FastPairRepositoryImplTest,
MAYBE_RetriesWriteDevice_AfterNetworkAvailable) {
auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
Protocol::kFastPairInitial);
device->set_classic_address(kTestClassicAddress1);
// Mock an error due to Network failure.
footprints_fetcher_->SetAddUserFastPairInfoResult(false);
device->set_display_name(kDeviceDisplayName);
fast_pair_repository_->WriteAccountAssociationToFootprints(device,
kAccountKey1);
base::RunLoop().RunUntilIdle();
// The failed write should save as pending write.
std::vector<PendingWriteStore::PendingWrite> pending_writes =
pending_write_store_->GetPendingWrites();
ASSERT_EQ(1u, pending_writes.size());
ASSERT_FALSE(footprints_fetcher_->ContainsKey(kAccountKey1));
// Parse device account key from device fast pair info.
const std::string& account_key_str =
pending_writes[0].fast_pair_info.device().account_key();
std::vector<uint8_t> account_key =
std::vector<uint8_t>{account_key_str.begin(), account_key_str.end()};
// Check that account key retrieved from PendingWrite is the same as passed
// into PendingWrite.
ASSERT_EQ(kAccountKey1, account_key);
// Reconnect to the Network, but fail again because |footprints_fetcher_| is
// still stubbed to fail.
fast_pair_repository_->DefaultNetworkChanged(
helper_.network_state_handler()->DefaultNetwork());
base::RunLoop().RunUntilIdle();
// The write should still be pending after a failed retry.
ASSERT_EQ(1u, pending_write_store_->GetPendingWrites().size());
ASSERT_FALSE(footprints_fetcher_->ContainsKey(kAccountKey1));
// Reconnect to the Network, but within the 1 minute timeout.
footprints_fetcher_->SetAddUserFastPairInfoResult(true);
fast_pair_repository_->DefaultNetworkChanged(
helper_.network_state_handler()->DefaultNetwork());
base::RunLoop().RunUntilIdle();
// Since we don't try within 1 minute, the write should still be pending.
ASSERT_EQ(1u, pending_write_store_->GetPendingWrites().size());
ASSERT_FALSE(footprints_fetcher_->ContainsKey(kAccountKey1));
// Mock waiting out the 1 minute timeout.
task_environment()->FastForwardBy(base::Minutes(1));
base::RunLoop().RunUntilIdle();
// Reconnect to the Network, but after the 1 minute timeout.
footprints_fetcher_->SetAddUserFastPairInfoResult(true);
fast_pair_repository_->DefaultNetworkChanged(
helper_.network_state_handler()->DefaultNetwork());
base::RunLoop().RunUntilIdle();
// The write, after a successful retry, should no longer be pending.
ASSERT_EQ(0u, pending_write_store_->GetPendingWrites().size());
ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
}
TEST_F(FastPairRepositoryImplTest,
RetryWriteRetroactivePair_DoesntRecordMetric) {
auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
Protocol::kFastPairRetroactive);
device->set_classic_address(kTestClassicAddress1);
device->set_display_name(kDeviceDisplayName);
// Mock an error due to Network failure.
footprints_fetcher_->SetAddUserFastPairInfoResult(false);
fast_pair_repository_->WriteAccountAssociationToFootprints(device,
kAccountKey1);
base::RunLoop().RunUntilIdle();
// The failed write should save as pending write.
std::vector<PendingWriteStore::PendingWrite> pending_writes =
pending_write_store_->GetPendingWrites();
ASSERT_EQ(1u, pending_writes.size());
ASSERT_FALSE(footprints_fetcher_->ContainsKey(kAccountKey1));
// Mock waiting out the 1 minute timeout.
task_environment()->FastForwardBy(base::Minutes(1));
base::RunLoop().RunUntilIdle();
// Reconnect to the Network, but after the 1 minute timeout.
footprints_fetcher_->SetAddUserFastPairInfoResult(true);
fast_pair_repository_->DefaultNetworkChanged(
helper_.network_state_handler()->DefaultNetwork());
base::RunLoop().RunUntilIdle();
// The write, after a successful retry, should no longer be pending.
ASSERT_EQ(0u, pending_write_store_->GetPendingWrites().size());
ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
// A pending write for retroactive pairing does not log a success in the
// metrics.
EXPECT_EQ(histogram_tester().GetBucketCount(
kRetroactiveSuccessFunnelMetric,
FastPairRetroactiveSuccessFunnelEvent::kSaveComplete),
0);
}
// `WriteAccountAssociationToFootprints()` previously wrote the association
// locally as well. This unit test ensures it does not anymore.
TEST_F(FastPairRepositoryImplTest,
AccountAssociationWriteToFootprints_NoLocalWrite) {
auto device = base::MakeRefCounted<Device>(kValidModelId, kTestBLEAddress,
Protocol::kFastPairRetroactive);
device->set_classic_address(kTestClassicAddress1);
device->set_display_name(kDeviceDisplayName);
fast_pair_repository_->WriteAccountAssociationToFootprints(device,
kAccountKey1);
ASSERT_TRUE(footprints_fetcher_->ContainsKey(kAccountKey1));
ASSERT_FALSE(
saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
}
} // namespace quick_pair
} // namespace ash