blob: 01b0921a06f9874d98cfe18df0fcc12bcc864dd1 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/sharing/sharing_service.h"
#include <memory>
#include <optional>
#include <vector>
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/test/task_environment.h"
#include "base/uuid.h"
#include "chrome/browser/sharing/mock_sharing_device_source.h"
#include "chrome/browser/sharing/mock_sharing_message_sender.h"
#include "chrome/browser/sharing/proto/sharing_message.pb.h"
#include "chrome/browser/sharing/sharing_constants.h"
#include "chrome/browser/sharing/sharing_device_registration.h"
#include "chrome/browser/sharing/sharing_device_registration_result.h"
#include "chrome/browser/sharing/sharing_fcm_handler.h"
#include "chrome/browser/sharing/sharing_fcm_sender.h"
#include "chrome/browser/sharing/sharing_handler_registry.h"
#include "chrome/browser/sharing/sharing_message_handler.h"
#include "chrome/browser/sharing/sharing_sync_preference.h"
#include "chrome/browser/sharing/vapid_key_manager.h"
#include "components/gcm_driver/crypto/gcm_encryption_provider.h"
#include "components/gcm_driver/instance_id/instance_id_driver.h"
#include "components/sharing_message/features.h"
#include "components/sync/test/test_sync_service.h"
#include "components/sync_device_info/device_info.h"
#include "components/sync_device_info/fake_device_info_sync_service.h"
#include "components/sync_device_info/local_device_info_provider.h"
#include "components/sync_device_info/local_device_info_util.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "content/public/test/browser_task_environment.h"
#include "crypto/ec_private_key.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
const char kDeviceName[] = "other_name";
const char kAuthorizedEntity[] = "authorized_entity";
constexpr base::TimeDelta kTimeout = base::Seconds(15);
SharingTargetDeviceInfo CreateFakeSharingTargetDeviceInfo(
const std::string& guid,
const std::string& client_name) {
return SharingTargetDeviceInfo(guid, client_name,
SharingDevicePlatform::kUnknown,
/*pulse_interval=*/base::TimeDelta(),
syncer::DeviceInfo::FormFactor::kUnknown,
/*last_updated_timestamp=*/base::Time());
}
class MockInstanceIDDriver : public instance_id::InstanceIDDriver {
public:
MockInstanceIDDriver() : InstanceIDDriver(/*gcm_driver=*/nullptr) {}
MockInstanceIDDriver(const MockInstanceIDDriver&) = delete;
MockInstanceIDDriver& operator=(const MockInstanceIDDriver&) = delete;
~MockInstanceIDDriver() override = default;
};
class MockSharingHandlerRegistry : public SharingHandlerRegistry {
public:
MockSharingHandlerRegistry() = default;
~MockSharingHandlerRegistry() override = default;
MOCK_METHOD1(
GetSharingHandler,
SharingMessageHandler*(
chrome_browser_sharing::SharingMessage::PayloadCase payload_case));
MOCK_METHOD2(
RegisterSharingHandler,
void(std::unique_ptr<SharingMessageHandler> handler,
chrome_browser_sharing::SharingMessage::PayloadCase payload_case));
MOCK_METHOD1(
UnregisterSharingHandler,
void(chrome_browser_sharing::SharingMessage::PayloadCase payload_case));
};
class MockSharingFCMHandler : public SharingFCMHandler {
using SharingMessage = chrome_browser_sharing::SharingMessage;
public:
MockSharingFCMHandler()
: SharingFCMHandler(/*gcm_driver=*/nullptr,
/*sharing_fcm_sender=*/nullptr,
/*sync_preference=*/nullptr,
/*handler_registry=*/nullptr) {}
~MockSharingFCMHandler() override = default;
MOCK_METHOD0(StartListening, void());
MOCK_METHOD0(StopListening, void());
};
class FakeSharingDeviceRegistration : public SharingDeviceRegistration {
public:
FakeSharingDeviceRegistration(
PrefService* pref_service,
SharingSyncPreference* prefs,
VapidKeyManager* vapid_key_manager,
instance_id::InstanceIDDriver* instance_id_driver,
syncer::SyncService* sync_service)
: SharingDeviceRegistration(pref_service,
prefs,
vapid_key_manager,
instance_id_driver,
sync_service),
vapid_key_manager_(vapid_key_manager) {}
~FakeSharingDeviceRegistration() override = default;
void RegisterDevice(
SharingDeviceRegistration::RegistrationCallback callback) override {
registration_attempts_++;
// Simulate SharingDeviceRegistration calling GetOrCreateKey.
vapid_key_manager_->GetOrCreateKey();
std::move(callback).Run(result_);
}
void UnregisterDevice(
SharingDeviceRegistration::RegistrationCallback callback) override {
unregistration_attempts_++;
std::move(callback).Run(result_);
}
void SetResult(SharingDeviceRegistrationResult result) { result_ = result; }
int registration_attempts() { return registration_attempts_; }
int unregistration_attempts() { return unregistration_attempts_; }
private:
raw_ptr<VapidKeyManager> vapid_key_manager_;
SharingDeviceRegistrationResult result_ =
SharingDeviceRegistrationResult::kSuccess;
int registration_attempts_ = 0;
int unregistration_attempts_ = 0;
};
class SharingServiceTest : public testing::Test {
public:
SharingServiceTest() {
sync_prefs_ =
new SharingSyncPreference(&prefs_, &fake_device_info_sync_service);
vapid_key_manager_ = new VapidKeyManager(sync_prefs_, &test_sync_service_);
sharing_device_registration_ = new FakeSharingDeviceRegistration(
/* pref_service= */ nullptr, sync_prefs_, vapid_key_manager_,
&mock_instance_id_driver_, &test_sync_service_);
handler_registry_ = new testing::NiceMock<MockSharingHandlerRegistry>();
fcm_handler_ = new testing::NiceMock<MockSharingFCMHandler>();
device_source_ = new testing::NiceMock<MockSharingDeviceSource>();
sharing_message_sender_ = new testing::NiceMock<MockSharingMessageSender>();
SharingSyncPreference::RegisterProfilePrefs(prefs_.registry());
ON_CALL(*device_source_, IsReady()).WillByDefault(testing::Return(true));
}
~SharingServiceTest() override {
// Make sure we're creating a SharingService so it can take ownership of the
// local objects.
GetSharingService();
}
void OnMessageSent(
SharingSendMessageResult result,
std::unique_ptr<chrome_browser_sharing::ResponseMessage> response) {
send_message_result_ = std::make_optional(result);
send_message_response_ = std::move(response);
}
const std::optional<SharingSendMessageResult>& send_message_result() {
return send_message_result_;
}
const chrome_browser_sharing::ResponseMessage* send_message_response() {
return send_message_response_.get();
}
void OnDeviceCandidatesInitialized() {
device_candidates_initialized_ = true;
}
protected:
// Lazily initialized so we can test the constructor.
SharingService* GetSharingService() {
if (!sharing_service_) {
sharing_service_ = std::make_unique<SharingService>(
base::WrapUnique(sync_prefs_.get()),
base::WrapUnique(vapid_key_manager_.get()),
base::WrapUnique(sharing_device_registration_.get()),
base::WrapUnique(sharing_message_sender_.get()),
base::WrapUnique(device_source_.get()),
base::WrapUnique(handler_registry_.get()),
base::WrapUnique(fcm_handler_.get()), &test_sync_service_);
}
task_environment_.RunUntilIdle();
return sharing_service_.get();
}
content::BrowserTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
syncer::FakeDeviceInfoSyncService fake_device_info_sync_service;
syncer::TestSyncService test_sync_service_;
sync_preferences::TestingPrefServiceSyncable prefs_;
private:
// `sharing_service_` must outlive the raw_ptrs below.
std::unique_ptr<SharingService> sharing_service_;
protected:
testing::NiceMock<MockInstanceIDDriver> mock_instance_id_driver_;
raw_ptr<testing::NiceMock<MockSharingHandlerRegistry>> handler_registry_;
raw_ptr<testing::NiceMock<MockSharingFCMHandler>> fcm_handler_;
raw_ptr<testing::NiceMock<MockSharingDeviceSource>> device_source_;
raw_ptr<SharingSyncPreference> sync_prefs_;
raw_ptr<VapidKeyManager> vapid_key_manager_;
raw_ptr<FakeSharingDeviceRegistration> sharing_device_registration_;
raw_ptr<testing::NiceMock<MockSharingMessageSender>> sharing_message_sender_;
bool device_candidates_initialized_ = false;
private:
std::optional<SharingSendMessageResult> send_message_result_;
std::unique_ptr<chrome_browser_sharing::ResponseMessage>
send_message_response_;
};
bool ProtoEquals(const google::protobuf::MessageLite& expected,
const google::protobuf::MessageLite& actual) {
std::string expected_serialized, actual_serialized;
expected.SerializeToString(&expected_serialized);
actual.SerializeToString(&actual_serialized);
return expected_serialized == actual_serialized;
}
} // namespace
TEST_F(SharingServiceTest, GetDeviceCandidates_Empty) {
EXPECT_CALL(*device_source_, GetDeviceCandidates(::testing::_))
.WillOnce(
[](sync_pb::SharingSpecificFields::EnabledFeatures required_feature)
-> std::vector<SharingTargetDeviceInfo> { return {}; });
std::vector<SharingTargetDeviceInfo> candidates =
GetSharingService()->GetDeviceCandidates(
sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2);
EXPECT_TRUE(candidates.empty());
}
TEST_F(SharingServiceTest, GetDeviceCandidates_Tracked) {
EXPECT_CALL(*device_source_, GetDeviceCandidates(::testing::_))
.WillOnce(
[](sync_pb::SharingSpecificFields::EnabledFeatures required_feature) {
std::vector<SharingTargetDeviceInfo> device_candidates;
device_candidates.push_back(CreateFakeSharingTargetDeviceInfo(
base::Uuid::GenerateRandomV4().AsLowercaseString(),
kDeviceName));
return device_candidates;
});
std::vector<SharingTargetDeviceInfo> candidates =
GetSharingService()->GetDeviceCandidates(
sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2);
ASSERT_EQ(1u, candidates.size());
}
TEST_F(SharingServiceTest, SendMessageToDeviceSuccess) {
SharingTargetDeviceInfo device_info = CreateFakeSharingTargetDeviceInfo(
base::Uuid::GenerateRandomV4().AsLowercaseString(), kDeviceName);
chrome_browser_sharing::ResponseMessage expected_response_message;
auto run_callback = [&](const SharingTargetDeviceInfo& device_info,
base::TimeDelta response_timeout,
chrome_browser_sharing::SharingMessage message,
SharingMessageSender::DelegateType delegate_type,
SharingMessageSender::ResponseCallback callback) {
std::unique_ptr<chrome_browser_sharing::ResponseMessage> response_message =
std::make_unique<chrome_browser_sharing::ResponseMessage>();
response_message->CopyFrom(expected_response_message);
std::move(callback).Run(SharingSendMessageResult::kSuccessful,
std::move(response_message));
return base::DoNothing();
};
ON_CALL(*sharing_message_sender_,
SendMessageToDevice(testing::_, testing::_, testing::_, testing::_,
testing::_))
.WillByDefault(testing::Invoke(run_callback));
GetSharingService()->SendMessageToDevice(
device_info, kTimeout, chrome_browser_sharing::SharingMessage(),
base::BindOnce(&SharingServiceTest::OnMessageSent,
base::Unretained(this)));
EXPECT_EQ(SharingSendMessageResult::kSuccessful, send_message_result());
ASSERT_TRUE(send_message_response());
EXPECT_TRUE(ProtoEquals(expected_response_message, *send_message_response()));
}
TEST_F(SharingServiceTest, DeviceRegistration) {
test_sync_service_.GetUserSettings()->SetSelectedTypes(
/*sync_everything=*/false,
/*types=*/{syncer::UserSelectableType::kPreferences});
EXPECT_EQ(SharingService::State::DISABLED,
GetSharingService()->GetStateForTesting());
// Expect registration to be successful on sync state changed.
sharing_device_registration_->SetResult(
SharingDeviceRegistrationResult::kSuccess);
EXPECT_CALL(*fcm_handler_, StartListening()).Times(1);
test_sync_service_.FireStateChanged();
EXPECT_EQ(1, sharing_device_registration_->registration_attempts());
EXPECT_EQ(SharingService::State::ACTIVE,
GetSharingService()->GetStateForTesting());
// As device is already registered, won't attempt registration anymore.
EXPECT_CALL(*fcm_handler_, StartListening()).Times(0);
test_sync_service_.FireStateChanged();
EXPECT_EQ(1, sharing_device_registration_->registration_attempts());
EXPECT_EQ(SharingService::State::ACTIVE,
GetSharingService()->GetStateForTesting());
auto vapid_key = crypto::ECPrivateKey::Create();
ASSERT_TRUE(vapid_key);
std::vector<uint8_t> vapid_key_info;
ASSERT_TRUE(vapid_key->ExportPrivateKey(&vapid_key_info));
// Registration will be attempeted as VAPID key has changed.
EXPECT_CALL(*fcm_handler_, StartListening()).Times(0);
sync_prefs_->SetVapidKey(vapid_key_info);
EXPECT_EQ(2, sharing_device_registration_->registration_attempts());
EXPECT_EQ(SharingService::State::ACTIVE,
GetSharingService()->GetStateForTesting());
}
TEST_F(SharingServiceTest, DeviceRegistrationPreferenceNotAvailable) {
test_sync_service_.GetUserSettings()->SetSelectedTypes(
/*sync_everything=*/false,
/*types=*/syncer::UserSelectableTypeSet());
test_sync_service_.SetFailedDataTypes({syncer::SHARING_MESSAGE});
EXPECT_EQ(SharingService::State::DISABLED,
GetSharingService()->GetStateForTesting());
// As sync preferences is not available, registration shouldn't start.
EXPECT_CALL(*fcm_handler_, StartListening()).Times(0);
test_sync_service_.FireStateChanged();
EXPECT_EQ(0, sharing_device_registration_->registration_attempts());
EXPECT_EQ(SharingService::State::DISABLED,
GetSharingService()->GetStateForTesting());
}
TEST_F(SharingServiceTest, DeviceRegistrationTransportMode) {
EXPECT_EQ(SharingService::State::DISABLED,
GetSharingService()->GetStateForTesting());
// Expect registration to be successful on sync state changed.
sharing_device_registration_->SetResult(
SharingDeviceRegistrationResult::kSuccess);
EXPECT_CALL(*fcm_handler_, StartListening()).Times(1);
test_sync_service_.FireStateChanged();
EXPECT_EQ(1, sharing_device_registration_->registration_attempts());
EXPECT_EQ(SharingService::State::ACTIVE,
GetSharingService()->GetStateForTesting());
}
TEST_F(SharingServiceTest, DeviceRegistrationTransientError) {
test_sync_service_.GetUserSettings()->SetSelectedTypes(
/*sync_everything=*/false,
/*types=*/{syncer::UserSelectableType::kPreferences});
EXPECT_EQ(SharingService::State::DISABLED,
GetSharingService()->GetStateForTesting());
// Retry will be scheduled on transient error received.
sharing_device_registration_->SetResult(
SharingDeviceRegistrationResult::kFcmTransientError);
test_sync_service_.FireStateChanged();
EXPECT_EQ(1, sharing_device_registration_->registration_attempts());
EXPECT_EQ(SharingService::State::REGISTERING,
GetSharingService()->GetStateForTesting());
// Retry should be scheduled by now. Next retry after 5 minutes will be
// successful.
sharing_device_registration_->SetResult(
SharingDeviceRegistrationResult::kSuccess);
EXPECT_CALL(*fcm_handler_, StartListening()).Times(1);
task_environment_.FastForwardBy(
base::Milliseconds(kRetryBackoffPolicy.initial_delay_ms));
EXPECT_EQ(2, sharing_device_registration_->registration_attempts());
EXPECT_EQ(SharingService::State::ACTIVE,
GetSharingService()->GetStateForTesting());
}
TEST_F(SharingServiceTest, DeviceUnregistrationSyncDisabled) {
test_sync_service_.SetSignedOut();
// Create new SharingService instance with sync disabled at constructor.
GetSharingService();
EXPECT_EQ(1, sharing_device_registration_->unregistration_attempts());
EXPECT_EQ(SharingService::State::DISABLED,
GetSharingService()->GetStateForTesting());
}
TEST_F(SharingServiceTest, DeviceRegisterAndUnregister) {
test_sync_service_.GetUserSettings()->SetSelectedTypes(
/*sync_everything=*/false,
/*types=*/{syncer::UserSelectableType::kPreferences});
// Create new SharingService instance with feature enabled at constructor.
GetSharingService();
EXPECT_EQ(SharingService::State::DISABLED,
GetSharingService()->GetStateForTesting());
// Expect registration to be successful on sync state changed.
sharing_device_registration_->SetResult(
SharingDeviceRegistrationResult::kSuccess);
EXPECT_CALL(*fcm_handler_, StartListening()).Times(1);
test_sync_service_.FireStateChanged();
EXPECT_EQ(1, sharing_device_registration_->registration_attempts());
EXPECT_EQ(0, sharing_device_registration_->unregistration_attempts());
EXPECT_EQ(SharingService::State::ACTIVE,
GetSharingService()->GetStateForTesting());
// Further state changes do nothing.
EXPECT_CALL(*fcm_handler_, StartListening()).Times(0);
test_sync_service_.FireStateChanged();
EXPECT_EQ(1, sharing_device_registration_->registration_attempts());
EXPECT_EQ(0, sharing_device_registration_->unregistration_attempts());
EXPECT_EQ(SharingService::State::ACTIVE,
GetSharingService()->GetStateForTesting());
// Change sync to configuring, which will be ignored.
test_sync_service_.SetTransportState(
syncer::SyncService::TransportState::CONFIGURING);
test_sync_service_.FireStateChanged();
EXPECT_EQ(1, sharing_device_registration_->registration_attempts());
EXPECT_EQ(0, sharing_device_registration_->unregistration_attempts());
EXPECT_EQ(SharingService::State::ACTIVE,
GetSharingService()->GetStateForTesting());
// Disable sync and un-registration should happen.
test_sync_service_.SetSignedOut();
EXPECT_CALL(*fcm_handler_, StopListening()).Times(1);
test_sync_service_.FireStateChanged();
EXPECT_EQ(1, sharing_device_registration_->registration_attempts());
EXPECT_EQ(1, sharing_device_registration_->unregistration_attempts());
EXPECT_EQ(SharingService::State::DISABLED,
GetSharingService()->GetStateForTesting());
// Further state changes do nothing.
EXPECT_CALL(*fcm_handler_, StopListening()).Times(0);
test_sync_service_.FireStateChanged();
EXPECT_EQ(1, sharing_device_registration_->registration_attempts());
EXPECT_EQ(1, sharing_device_registration_->unregistration_attempts());
EXPECT_EQ(SharingService::State::DISABLED,
GetSharingService()->GetStateForTesting());
// Should be able to register once again when sync is back on.
test_sync_service_.SetSignedInWithSyncFeatureOn();
EXPECT_CALL(*fcm_handler_, StartListening()).Times(1);
test_sync_service_.FireStateChanged();
EXPECT_EQ(2, sharing_device_registration_->registration_attempts());
EXPECT_EQ(1, sharing_device_registration_->unregistration_attempts());
EXPECT_EQ(SharingService::State::ACTIVE,
GetSharingService()->GetStateForTesting());
// Disable syncing of preference and un-registration should happen.
test_sync_service_.GetUserSettings()->SetSelectedTypes(
/*sync_everything=*/false,
/*types=*/syncer::UserSelectableTypeSet());
test_sync_service_.SetFailedDataTypes({syncer::SHARING_MESSAGE});
EXPECT_CALL(*fcm_handler_, StopListening()).Times(1);
test_sync_service_.FireStateChanged();
EXPECT_EQ(2, sharing_device_registration_->registration_attempts());
EXPECT_EQ(2, sharing_device_registration_->unregistration_attempts());
EXPECT_EQ(SharingService::State::DISABLED,
GetSharingService()->GetStateForTesting());
}
TEST_F(SharingServiceTest, StartListeningToFCMAtConstructor) {
test_sync_service_.GetUserSettings()->SetSelectedTypes(
/*sync_everything=*/false,
/*types=*/{syncer::UserSelectableType::kPreferences});
// Create new SharingService instance with FCM already registered at
// constructor.
sync_prefs_->SetFCMRegistration(SharingSyncPreference::FCMRegistration(
kAuthorizedEntity, base::Time::Now()));
EXPECT_CALL(*fcm_handler_, StartListening()).Times(1);
GetSharingService();
}
TEST_F(SharingServiceTest, GetDeviceByGuid) {
std::string guid = base::Uuid::GenerateRandomV4().AsLowercaseString();
EXPECT_CALL(*device_source_, GetDeviceByGuid(guid))
.WillOnce([](const std::string& guid)
-> std::optional<SharingTargetDeviceInfo> {
return CreateFakeSharingTargetDeviceInfo(guid, "Dell Computer sno one");
});
std::optional<SharingTargetDeviceInfo> device_info =
GetSharingService()->GetDeviceByGuid(guid);
ASSERT_TRUE(device_info.has_value());
EXPECT_EQ("Dell Computer sno one", device_info->client_name());
}
TEST_F(SharingServiceTest, AddSharingHandler) {
EXPECT_CALL(*handler_registry_,
RegisterSharingHandler(testing::_, testing::_))
.Times(1);
GetSharingService()->RegisterSharingHandler(
nullptr, chrome_browser_sharing::SharingMessage::kSharedClipboardMessage);
}
TEST_F(SharingServiceTest, RemoveSharingHandler) {
EXPECT_CALL(*handler_registry_, UnregisterSharingHandler(testing::_))
.Times(1);
GetSharingService()->UnregisterSharingHandler(
chrome_browser_sharing::SharingMessage::kSharedClipboardMessage);
}