From 5bab62fbb1580c0a2a44cb29e3c7fd7cf62bca67 Mon Sep 17 00:00:00 2001 From: Qin Wang Date: Fri, 7 Jul 2023 11:55:15 -0700 Subject: [PATCH] Get/Write/Delete account associated device from/to footprints PiperOrigin-RevId: 546342820 --- fastpair/repository/BUILD | 12 +- .../repository/fake_fast_pair_repository.h | 14 + fastpair/repository/fast_pair_repository.h | 32 ++ .../repository/fast_pair_repository_impl.cc | 136 ++++++++ .../repository/fast_pair_repository_impl.h | 16 +- .../fast_pair_repository_impl_test.cc | 300 ++++++++++++++++++ 6 files changed, 503 insertions(+), 7 deletions(-) diff --git a/fastpair/repository/BUILD b/fastpair/repository/BUILD index 6047ea14..cfa6aaa1 100644 --- a/fastpair/repository/BUILD +++ b/fastpair/repository/BUILD @@ -14,6 +14,7 @@ cc_library( visibility = ["//fastpair:__subpackages__"], deps = [ "//fastpair/common", + "//fastpair/proto:fastpair_cc_proto", "//internal/base:bluetooth_address", "//internal/crypto", "@com_google_absl//absl/functional:any_invocable", @@ -40,13 +41,11 @@ cc_library( "//fastpair/proto:fastpair_cc_proto", "//fastpair/proto:proto_builder", "//fastpair/server_access", - "//internal/base:bluetooth_address", - "//internal/crypto", + "//internal/base", "//internal/platform:logging", "//internal/platform:types", - "@com_google_absl//absl/functional:any_invocable", + "@com_google_absl//absl/status", "@com_google_absl//absl/strings", - "@com_google_absl//absl/time", ], ) @@ -146,14 +145,15 @@ cc_test( "-Ithird_party", ], deps = [ - ":repository", ":repository_impl", - ":test_support", "//fastpair/common", + "//fastpair/proto:fastpair_cc_proto", + "//fastpair/proto:proto_builder", "//fastpair/server_access:test_support", "//internal/platform:types", "//internal/platform/implementation/g3", # build_cleaner: keep "@com_github_protobuf_matchers//protobuf-matchers", + "@com_google_absl//absl/status", "@com_google_googletest//:gtest_main", ], ) diff --git a/fastpair/repository/fake_fast_pair_repository.h b/fastpair/repository/fake_fast_pair_repository.h index 19d4b187..ba387743 100644 --- a/fastpair/repository/fake_fast_pair_repository.h +++ b/fastpair/repository/fake_fast_pair_repository.h @@ -38,10 +38,24 @@ class FakeFastPairRepository : public FastPairRepository { void SetFakeMetadata(absl::string_view hex_model_id, proto::Device metadata); void ClearFakeMetadata(absl::string_view hex_model_id); + // FastPairRepository:: + void AddObserver(Observer* observer) override{}; + void RemoveObserver(Observer* observer) override{}; + void GetDeviceMetadata(absl::string_view hex_model_id, DeviceMetadataCallback callback) override; + void GetUserSavedDevices() override{}; + + void WriteAccountAssociationToFootprints( + FastPairDevice& device, + OperationToFootprintsCallback callback) override{}; + + void DeleteAssociatedDeviceByAccountKey( + const AccountKey& account_key, + OperationToFootprintsCallback callback) override{}; + private: absl::flat_hash_map> data_; SingleThreadExecutor executor_; diff --git a/fastpair/repository/fast_pair_repository.h b/fastpair/repository/fast_pair_repository.h index d7703d4b..10eab502 100644 --- a/fastpair/repository/fast_pair_repository.h +++ b/fastpair/repository/fast_pair_repository.h @@ -18,19 +18,34 @@ #include #include #include +#include #include "absl/functional/any_invocable.h" #include "absl/strings/string_view.h" #include "fastpair/common/account_key.h" #include "fastpair/common/device_metadata.h" +#include "fastpair/common/fast_pair_device.h" +#include "fastpair/proto/data.proto.h" +#include "fastpair/proto/enum.proto.h" namespace nearby { namespace fastpair { using DeviceMetadataCallback = absl::AnyInvocable device_metadata)>; +using OperationToFootprintsCallback = + absl::AnyInvocable; class FastPairRepository { public: + class Observer { + public: + virtual ~Observer() = default; + + virtual void OnGetUserSavedDevices( + const proto::OptInStatus& opt_in_status, + const std::vector& devices) = 0; + }; + static FastPairRepository* Get(); // Computes and returns the SHA256 of the concatenation of the given @@ -40,9 +55,26 @@ class FastPairRepository { FastPairRepository(); virtual ~FastPairRepository(); + + virtual void AddObserver(Observer* observer) = 0; + virtual void RemoveObserver(Observer* observer) = 0; + virtual void GetDeviceMetadata(absl::string_view hex_model_id, DeviceMetadataCallback callback) = 0; + // Gets a list of devices saved to the current user's account and the user's + // opt in status for saving future devices to their account. + virtual void GetUserSavedDevices() = 0; + + // Stores the given |account_key| for a |device| on the Footprints server. + virtual void WriteAccountAssociationToFootprints( + FastPairDevice& device, OperationToFootprintsCallback callback) = 0; + + // Deletes the associated data for a given |account_key|. + virtual void DeleteAssociatedDeviceByAccountKey( + const AccountKey& account_key, + OperationToFootprintsCallback callback) = 0; + protected: static void SetInstance(FastPairRepository* instance); }; diff --git a/fastpair/repository/fast_pair_repository_impl.cc b/fastpair/repository/fast_pair_repository_impl.cc index 135349b5..1ae227ac 100644 --- a/fastpair/repository/fast_pair_repository_impl.cc +++ b/fastpair/repository/fast_pair_repository_impl.cc @@ -18,18 +18,57 @@ #include #include #include +#include +#include "absl/status/status.h" #include "absl/strings/string_view.h" #include "fastpair/common/device_metadata.h" +#include "fastpair/proto/data.proto.h" +#include "fastpair/proto/enum.proto.h" +#include "fastpair/proto/proto_builder.h" #include "internal/platform/logging.h" #include "internal/platform/single_thread_executor.h" namespace nearby { namespace fastpair { +namespace { +// This forget pattern is defined in the Android codebase as FORGET_PREFIX_BYTE +// and FORGET_PREFIX_LENGTH_IN_BYTES. Currently, those values evaluate to the +// string of bytes defined below, which is used as the prefix for the sha256 +// field of the device. +constexpr absl::string_view kForgetPattern = "\xf0\xf0\xf0\xf0"; + +// For all intents and purposes, a device that has the "Forget pattern" is no +// longer associated to the user's account, and should be treated as removed. +bool DoesDeviceHaveForgetPattern(const proto::FastPairDevice& device) { + // The device info is modified to have no account key upon removal from + // Fast Pair Saved Devices + if (device.account_key().empty() || + device.sha256_account_key_public_address().empty()) { + return true; + } + + // To match Android behavior, we check if the SHA256 of a device begins with + // the Forget pattern, defined in Android Fast Pair code. When a device is + // forgotten from Android Bluetooth Settings, the SHA256 hash is modified to + // contain this pattern. + return (device.sha256_account_key_public_address().compare( + 0, kForgetPattern.length(), kForgetPattern) == 0); +} +} // namespace + FastPairRepositoryImpl::FastPairRepositoryImpl(FastPairClient* fast_pair_client) : fast_pair_client_(fast_pair_client) {} +void FastPairRepositoryImpl::AddObserver(Observer* observer) { + observers_.AddObserver(observer); +} + +void FastPairRepositoryImpl::RemoveObserver(Observer* observer) { + observers_.RemoveObserver(observer); +} + void FastPairRepositoryImpl::GetDeviceMetadata( absl::string_view hex_model_id, DeviceMetadataCallback callback) { NEARBY_LOGS(INFO) << __func__ << " with model id= " << hex_model_id; @@ -58,5 +97,102 @@ void FastPairRepositoryImpl::GetDeviceMetadata( }); } +void FastPairRepositoryImpl::WriteAccountAssociationToFootprints( + FastPairDevice& device, OperationToFootprintsCallback callback) { + proto::UserWriteDeviceRequest request; + auto* fast_pair_info = request.mutable_fast_pair_info(); + BuildFastPairInfo(fast_pair_info, device); + executor_.Execute( + "Write associated device", [this, request = std::move(request), + callback = std::move(callback)]() mutable { + NEARBY_LOGS(INFO) + << __func__ + << ": Start to write account associated device to footprints."; + absl::StatusOr response = + fast_pair_client_->UserWriteDevice(request); + if (response.ok()) { + NEARBY_LOGS(INFO) + << __func__ << "Got GetWriteDeviceResponse from backend."; + std::move(callback)(absl::OkStatus()); + } else { + NEARBY_LOGS(WARNING) + << __func__ + << "Failed to get GetWriteDeviceResponse from backend."; + std::move(callback)(response.status()); + } + }); +} + +void FastPairRepositoryImpl::DeleteAssociatedDeviceByAccountKey( + const AccountKey& account_key, OperationToFootprintsCallback callback) { + std::string hex_string = absl::BytesToHexString(account_key.GetAsBytes()); + absl::AsciiStrToUpper(&hex_string); + executor_.Execute( + "Delete associated device", + [this, hex_account_key = std::move(hex_string), + callback = std::move(callback)]() mutable { + NEARBY_LOGS(INFO) + << __func__ + << ": Start to delete account associated device from footprints"; + proto::UserDeleteDeviceRequest request; + request.set_hex_account_key(hex_account_key); + absl::StatusOr response = + fast_pair_client_->UserDeleteDevice(request); + if (response.ok()) { + if (response->success()) { + NEARBY_LOGS(INFO) + << __func__ << "Successfully deleted associated device."; + std::move(callback)(absl::OkStatus()); + } else { + NEARBY_LOGS(WARNING) << __func__ << "Failed to delete device."; + std::move(callback)( + absl::InternalError("Failed to delete device.")); + } + } else { + NEARBY_LOGS(WARNING) + << __func__ + << "Failed to get UserDeleteDeviceResponse from backend."; + std::move(callback)(response.status()); + } + }); +} + +void FastPairRepositoryImpl::GetUserSavedDevices() { + executor_.Execute("Get associated devices", [this]() mutable { + NEARBY_LOGS(INFO) << __func__ + << ": Start to get all account associated devices."; + proto::UserReadDevicesRequest request; + absl::StatusOr response = + fast_pair_client_->UserReadDevices(request); + if (!response.ok()) { + NEARBY_LOGS(WARNING) + << __func__ << "Failed to get UserReadDevicesResponse from backend."; + return; + } + NEARBY_LOGS(INFO) << __func__ + << "Got UserReadDevicesResponse from backend."; + proto::OptInStatus opt_in_status = + proto::OptInStatus::OPT_IN_STATUS_UNKNOWN; + std::vector saved_devices; + for (const auto& info : response->fast_pair_info()) { + if (info.has_opt_in_status()) { + opt_in_status = info.opt_in_status(); + } + // We have to check that the devices in Footprints don't use the + // "forget pattern" which Android uses in some cases to mark a device + // as removed from the user's account. + if (!info.has_device() || DoesDeviceHaveForgetPattern(info.device())) { + continue; + } + saved_devices.push_back(info.device()); + } + NEARBY_LOGS(INFO) << __func__ << ": Got " << saved_devices.size() + << " saved devices."; + for (auto& observer : observers_.GetObservers()) { + observer->OnGetUserSavedDevices(opt_in_status, saved_devices); + } + }); +} + } // namespace fastpair } // namespace nearby diff --git a/fastpair/repository/fast_pair_repository_impl.h b/fastpair/repository/fast_pair_repository_impl.h index 90c4fe4b..5f880785 100644 --- a/fastpair/repository/fast_pair_repository_impl.h +++ b/fastpair/repository/fast_pair_repository_impl.h @@ -20,8 +20,9 @@ #include "absl/strings/string_view.h" #include "fastpair/common/device_metadata.h" -#include "fastpair/server_access/fast_pair_client.h" #include "fastpair/repository/fast_pair_repository.h" +#include "fastpair/server_access/fast_pair_client.h" +#include "internal/base/observer_list.h" #include "internal/platform/single_thread_executor.h" namespace nearby { @@ -35,15 +36,28 @@ class FastPairRepositoryImpl : public FastPairRepository { FastPairRepositoryImpl& operator=(const FastPairRepositoryImpl&) = delete; ~FastPairRepositoryImpl() override = default; + void AddObserver(Observer* observer) override; + void RemoveObserver(Observer* observer) override; + void GetDeviceMetadata(absl::string_view hex_model_id, DeviceMetadataCallback callback) override; + void GetUserSavedDevices() override; + + void WriteAccountAssociationToFootprints( + FastPairDevice& device, OperationToFootprintsCallback callback) override; + + void DeleteAssociatedDeviceByAccountKey( + const AccountKey& account_key, + OperationToFootprintsCallback callback) override; + private: // A thread for running blocking tasks. SingleThreadExecutor executor_; FastPairClient* fast_pair_client_; absl::flat_hash_map> metadata_cache_; + ObserverList observers_; }; } // namespace fastpair } // namespace nearby diff --git a/fastpair/repository/fast_pair_repository_impl_test.cc b/fastpair/repository/fast_pair_repository_impl_test.cc index afad5015..c27edffd 100644 --- a/fastpair/repository/fast_pair_repository_impl_test.cc +++ b/fastpair/repository/fast_pair_repository_impl_test.cc @@ -16,11 +16,17 @@ #include #include +#include +#include #include "gmock/gmock.h" #include "protobuf-matchers/protocol-buffer-matchers.h" #include "gtest/gtest.h" +#include "absl/status/status.h" #include "fastpair/common/device_metadata.h" +#include "fastpair/proto/data.proto.h" +#include "fastpair/proto/fast_pair_string.proto.h" +#include "fastpair/proto/proto_builder.h" #include "fastpair/server_access/fake_fast_pair_client.h" #include "internal/platform/count_down_latch.h" @@ -28,8 +34,15 @@ namespace nearby { namespace fastpair { namespace { constexpr absl::string_view kHexModelId = "718C17"; +constexpr absl::string_view kBleAddress = "11:22:33:44:55:66"; +constexpr absl::string_view kPublicAddress = "20:64:DE:40:F8:93"; +constexpr absl::string_view kDisplayName = "Test Device"; +constexpr absl::string_view kAccountKey = "04b85786180add47fb81a04a8ce6b0de"; constexpr absl::string_view kInitialPairingdescription = "InitialPairingdescription"; +constexpr absl::string_view kExpectedSha256Hash = + "6353c0075a35b7d81bb30a6190ab246da4b8c55a6111d387400579133c090ed8"; +constexpr absl::Duration kWaitTimeout = absl::Milliseconds(200); // A gMock matcher to match proto values. Use this matcher like: // request/response proto, expected_proto; @@ -41,6 +54,29 @@ MATCHER_P( return arg.SerializeAsString() == expected_proto.SerializeAsString(); } +class FastPairRepositoryObserver : public FastPairRepository::Observer { + public: + explicit FastPairRepositoryObserver(CountDownLatch* latch) { + latch_ = latch; + opt_in_status_ = proto::OptInStatus::OPT_IN_STATUS_UNKNOWN; + devices_ = std::vector(); + } + + void OnGetUserSavedDevices( + const proto::OptInStatus& opt_in_status, + const std::vector& devices) override { + for (const auto& device : devices) { + devices_.push_back(device); + } + opt_in_status_ = opt_in_status; + latch_->CountDown(); + } + + CountDownLatch* latch_ = nullptr; + proto::OptInStatus opt_in_status_; + std::vector devices_; +}; + TEST(FastPairRepositoryImplTest, MetadataDownloadSuccess) { FakeFastPairClient fake_fast_pair_client; auto fast_pair_repository = @@ -71,8 +107,272 @@ TEST(FastPairRepositoryImplTest, MetadataDownloadSuccess) { latch.CountDown(); }); latch.Await(); + + // Verifies proto::GetObservedDeviceRequest is as expected. + proto::GetObservedDeviceRequest expected_request = + fake_fast_pair_client.get_observer_device_request(); + EXPECT_EQ(expected_request.device_id(), device_id); + EXPECT_EQ(expected_request.mode(), + proto::GetObservedDeviceRequest::MODE_RELEASE); } +TEST(FastPairRepositoryImplTest, FailedToDownloadMetadata) { + FakeFastPairClient fake_fast_pair_client; + auto fast_pair_repository = + std::make_unique(&fake_fast_pair_client); + + fake_fast_pair_client.SetGetObservedDeviceResponse( + absl::InternalError("No response")); + + CountDownLatch latch(1); + fast_pair_repository->GetDeviceMetadata( + kHexModelId, [&](std::optional device_metadata) { + EXPECT_FALSE(device_metadata.has_value()); + latch.CountDown(); + }); + latch.Await(); +} + +TEST(FastPairRepositoryImplTest, GetUserSavedDevicesSuccess) { + FakeFastPairClient fake_fast_pair_client; + auto fast_pair_repository = + std::make_unique(&fake_fast_pair_client); + + // Sets up two devices to proto::UserReadDevicesResponse. + // Adds device 1. + proto::UserReadDevicesResponse response_proto; + FastPairDevice device_1(kHexModelId, kBleAddress, + Protocol::kFastPairInitialPairing); + AccountKey account_key(absl::HexStringToBytes(kAccountKey)); + device_1.SetAccountKey(account_key); + device_1.SetPublicAddress(kPublicAddress); + device_1.SetDisplayName(kDisplayName); + proto::GetObservedDeviceResponse get_observed_device_response_1; + auto* observed_device_strings_1 = + get_observed_device_response_1.mutable_strings(); + observed_device_strings_1->set_initial_pairing_description( + kInitialPairingdescription); + DeviceMetadata device_metadata_1(get_observed_device_response_1); + device_1.SetMetadata(device_metadata_1); + auto* fast_pair_info_1 = response_proto.add_fast_pair_info(); + BuildFastPairInfo(fast_pair_info_1, device_1); + // Adds device 2. + auto* fast_pair_info_2 = response_proto.add_fast_pair_info(); + fast_pair_info_2->set_opt_in_status( + proto::OptInStatus::OPT_IN_STATUS_OPTED_IN); + fake_fast_pair_client.SetUserReadDevicesResponse(response_proto); + + // Adds FastPairRepository observer. + CountDownLatch latch(1); + FastPairRepositoryObserver observer(&latch); + EXPECT_EQ(observer.devices_.size(), 0); + EXPECT_EQ(observer.opt_in_status_, proto::OptInStatus::OPT_IN_STATUS_UNKNOWN); + fast_pair_repository->AddObserver(&observer); + + // Get user's saved device from footprints. + fast_pair_repository->GetUserSavedDevices(); + latch.Await(); + + // Verifies user's saved devices are as expected. + EXPECT_EQ(observer.opt_in_status_, + proto::OptInStatus::OPT_IN_STATUS_OPTED_IN); + EXPECT_EQ(observer.devices_.size(), 1); + proto::FastPairDevice saved_device = observer.devices_.front(); + EXPECT_EQ(saved_device.account_key(), account_key.GetAsBytes()); + EXPECT_EQ( + absl::BytesToHexString(saved_device.sha256_account_key_public_address()), + kExpectedSha256Hash); + proto::StoredDiscoveryItem stored_discovery_item; + EXPECT_TRUE(stored_discovery_item.ParseFromString( + saved_device.discovery_item_bytes())); + EXPECT_EQ(stored_discovery_item.title(), kDisplayName); + proto::FastPairStrings fast_pair_strings = + stored_discovery_item.fast_pair_strings(); + EXPECT_EQ(fast_pair_strings.initial_pairing_description(), + kInitialPairingdescription); + fast_pair_repository->RemoveObserver(&observer); +} + +TEST(FastPairRepositoryImplTest, FailedToGetUserSavedDevices) { + FakeFastPairClient fake_fast_pair_client; + auto fast_pair_repository = + std::make_unique(&fake_fast_pair_client); + + fake_fast_pair_client.SetUserReadDevicesResponse( + absl::InternalError("No response")); + + // Adds FastPairRepository observer. + CountDownLatch latch(1); + FastPairRepositoryObserver observer(&latch); + fast_pair_repository->AddObserver(&observer); + + // Get user's saved device from footprints. + fast_pair_repository->GetUserSavedDevices(); + EXPECT_FALSE(latch.Await(kWaitTimeout).result()); +} + +TEST(FastPairRepositoryImplTest, WriteAccountAssociationToFootprintsSuccess) { + FakeFastPairClient fake_fast_pair_client; + auto fast_pair_repository = + std::make_unique(&fake_fast_pair_client); + + // Sets up proto::UserWriteDeviceResponse. + proto::UserWriteDeviceResponse response_proto; + fake_fast_pair_client.SetUserWriteDeviceResponse(response_proto); + + // Sets up device info to be saved to footprints. + FastPairDevice fast_pair_device(kHexModelId, kBleAddress, + Protocol::kFastPairInitialPairing); + AccountKey account_key(absl::HexStringToBytes(kAccountKey)); + fast_pair_device.SetAccountKey(account_key); + fast_pair_device.SetPublicAddress(kPublicAddress); + fast_pair_device.SetDisplayName(kDisplayName); + proto::GetObservedDeviceResponse get_observed_device_response; + auto* observed_device_strings = + get_observed_device_response.mutable_strings(); + observed_device_strings->set_initial_pairing_description( + kInitialPairingdescription); + DeviceMetadata device_metadata(get_observed_device_response); + fast_pair_device.SetMetadata(device_metadata); + + CountDownLatch latch(1); + fast_pair_repository->WriteAccountAssociationToFootprints( + fast_pair_device, [&](absl::Status status) { + // Verifies successfully write device to footprints. + EXPECT_OK(status); + latch.CountDown(); + }); + latch.Await(); + + // Verifies proto::UserWriteDeviceRequest is as expected. + proto::UserWriteDeviceRequest expected_request = + fake_fast_pair_client.write_device_request(); + EXPECT_TRUE(expected_request.has_fast_pair_info()); + auto fast_proto_info = expected_request.fast_pair_info(); + EXPECT_TRUE(fast_proto_info.has_device()); + auto device = fast_proto_info.device(); + EXPECT_EQ(device.account_key(), account_key.GetAsBytes()); + EXPECT_EQ(absl::BytesToHexString(device.sha256_account_key_public_address()), + kExpectedSha256Hash); + proto::StoredDiscoveryItem stored_discovery_item; + EXPECT_TRUE( + stored_discovery_item.ParseFromString(device.discovery_item_bytes())); + EXPECT_EQ(stored_discovery_item.title(), kDisplayName); + proto::FastPairStrings fast_pair_strings = + stored_discovery_item.fast_pair_strings(); + EXPECT_EQ(fast_pair_strings.initial_pairing_description(), + kInitialPairingdescription); +} + +TEST(FastPairRepositoryImplTest, FailedToWriteAccountAssociationToFootprints) { + FakeFastPairClient fake_fast_pair_client; + auto fast_pair_repository = + std::make_unique(&fake_fast_pair_client); + + fake_fast_pair_client.SetUserWriteDeviceResponse( + absl::InternalError("No response")); + + // Sets up device info to be saved to footprints. + FastPairDevice fast_pair_device(kHexModelId, kBleAddress, + Protocol::kFastPairInitialPairing); + AccountKey account_key(absl::HexStringToBytes(kAccountKey)); + fast_pair_device.SetAccountKey(account_key); + fast_pair_device.SetPublicAddress(kPublicAddress); + fast_pair_device.SetDisplayName(kDisplayName); + proto::GetObservedDeviceResponse get_observed_device_response; + auto* observed_device_strings = + get_observed_device_response.mutable_strings(); + observed_device_strings->set_initial_pairing_description( + kInitialPairingdescription); + DeviceMetadata device_metadata(get_observed_device_response); + fast_pair_device.SetMetadata(device_metadata); + + CountDownLatch latch(1); + fast_pair_repository->WriteAccountAssociationToFootprints( + fast_pair_device, [&](absl::Status status) { + // Failed to write device to footprints. + EXPECT_FALSE(status.ok()); + EXPECT_EQ(status.message(), "No response"); + latch.CountDown(); + }); + latch.Await(); +} + +TEST(FastPairRepositoryImplTest, DeleteAssociatedDeviceByAccountKeySuccess) { + FakeFastPairClient fake_fast_pair_client; + auto fast_pair_repository = + std::make_unique(&fake_fast_pair_client); + + // Sets up proto::UserDeleteDeviceResponse + proto::UserDeleteDeviceResponse response_proto; + response_proto.set_success(true); + fake_fast_pair_client.SetUserDeleteDeviceResponse(response_proto); + + // AccountKey of the device that will be removed from the footprint. + AccountKey account_key(absl::HexStringToBytes(kAccountKey)); + + CountDownLatch latch(1); + fast_pair_repository->DeleteAssociatedDeviceByAccountKey( + account_key, [&](absl::Status status) { + // Verifies successfully delete device from the footprints. + EXPECT_OK(status); + latch.CountDown(); + }); + latch.Await(); + + // Verifies proto::UserDeleteDeviceRequest is as expected. + proto::UserDeleteDeviceRequest expected_request = + fake_fast_pair_client.delete_device_request(); + std::string hex_account_key = std::string(kAccountKey); + absl::AsciiStrToUpper(&hex_account_key); + EXPECT_EQ(expected_request.hex_account_key(), hex_account_key); +} + +TEST(FastPairRepositoryImplTest, FailedToDeleteAssociatedDeviceWithNoResponse) { + FakeFastPairClient fake_fast_pair_client; + auto fast_pair_repository = + std::make_unique(&fake_fast_pair_client); + + fake_fast_pair_client.SetUserDeleteDeviceResponse( + absl::InternalError("No response")); + + // AccountKey of the device that will be removed from the footprint. + AccountKey account_key(absl::HexStringToBytes(kAccountKey)); + + CountDownLatch latch(1); + fast_pair_repository->DeleteAssociatedDeviceByAccountKey( + account_key, [&](absl::Status status) { + // Failed to delete device from the footprints. + EXPECT_FALSE(status.ok()); + EXPECT_EQ(status.message(), "No response"); + latch.CountDown(); + }); + latch.Await(); +} + +TEST(FastPairRepositoryImplTest, FailedToDeleteAssociatedDeviceWithError) { + FakeFastPairClient fake_fast_pair_client; + auto fast_pair_repository = + std::make_unique(&fake_fast_pair_client); + + // Sets up proto::UserDeleteDeviceResponse + proto::UserDeleteDeviceResponse response_proto; + response_proto.set_success(false); + fake_fast_pair_client.SetUserDeleteDeviceResponse(response_proto); + + // AccountKey of the device that will be removed from the footprint. + AccountKey account_key(absl::HexStringToBytes(kAccountKey)); + + CountDownLatch latch(1); + fast_pair_repository->DeleteAssociatedDeviceByAccountKey( + account_key, [&](absl::Status status) { + // Failed to delete device from the footprints. + EXPECT_FALSE(status.ok()); + EXPECT_EQ(status.message(), "Failed to delete device."); + latch.CountDown(); + }); + latch.Await(); +} } // namespace } // namespace fastpair } // namespace nearby