| // 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_device_source_sync.h" |
| |
| #include <memory> |
| |
| #include "base/callback.h" |
| #include "base/guid.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/run_loop.h" |
| #include "base/test/bind.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/sharing/fake_device_info.h" |
| #include "chrome/browser/sharing/features.h" |
| #include "chrome/browser/sharing/sharing_constants.h" |
| #include "chrome/browser/sharing/sharing_utils.h" |
| #include "components/send_tab_to_self/features.h" |
| #include "components/send_tab_to_self/target_device_info.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/fake_device_info_tracker.h" |
| #include "components/sync_device_info/fake_local_device_info_provider.h" |
| #include "components/sync_device_info/local_device_info_util.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace { |
| |
| const char kVapidFCMToken[] = "test_fcm_token"; |
| const char kSenderIdFCMToken[] = "sharing_fcm_token"; |
| const char kDevicep256dh[] = "test_p256_dh"; |
| const char kSenderIdP256dh[] = "sharing_p256dh"; |
| const char kDeviceAuthSecret[] = "test_auth_secret"; |
| const char kSenderIdAuthSecret[] = "sharing_auth_secret"; |
| |
| std::unique_ptr<syncer::DeviceInfo> CreateDeviceInfo( |
| const std::string& client_name, |
| sync_pb::SharingSpecificFields::EnabledFeatures enabled_feature, |
| const std::string& manufacturer_name = "manufacturer", |
| const std::string& model_name = "model", |
| syncer::DeviceInfo::SharingTargetInfo vapid_target_info = |
| {kVapidFCMToken, kDevicep256dh, kDeviceAuthSecret}, |
| syncer::DeviceInfo::SharingTargetInfo sender_id_target_info = { |
| kSenderIdFCMToken, kSenderIdP256dh, kSenderIdAuthSecret}) { |
| syncer::DeviceInfo::SharingInfo sharing_info(std::move(vapid_target_info), |
| std::move(sender_id_target_info), |
| {enabled_feature}); |
| |
| return CreateFakeDeviceInfo( |
| base::GenerateGUID(), client_name, std::move(sharing_info), |
| sync_pb::SyncEnums_DeviceType_TYPE_LINUX, |
| syncer::DeviceInfo::OsType::kLinux, |
| syncer::DeviceInfo::FormFactor::kDesktop, manufacturer_name, model_name); |
| } |
| |
| class SharingDeviceSourceSyncTest : public testing::Test { |
| public: |
| std::unique_ptr<SharingDeviceSourceSync> CreateDeviceSource( |
| bool wait_until_ready) { |
| auto device_source = std::make_unique<SharingDeviceSourceSync>( |
| &test_sync_service_, &fake_local_device_info_provider_, |
| &fake_device_info_tracker_); |
| if (!wait_until_ready) |
| return device_source; |
| |
| if (!fake_device_info_tracker_.IsSyncing()) |
| fake_device_info_tracker_.Add(local_device_info_); |
| fake_local_device_info_provider_.SetReady(true); |
| |
| // Wait until local personalizable device |
| base::RunLoop run_loop; |
| device_source->AddReadyCallback(run_loop.QuitClosure()); |
| run_loop.Run(); |
| |
| return device_source; |
| } |
| |
| protected: |
| content::BrowserTaskEnvironment task_environment_{ |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME}; |
| base::test::ScopedFeatureList scoped_feature_list_; |
| |
| syncer::TestSyncService test_sync_service_; |
| syncer::FakeDeviceInfoSyncService fake_device_info_sync_service_; |
| syncer::FakeLocalDeviceInfoProvider fake_local_device_info_provider_; |
| syncer::FakeDeviceInfoTracker fake_device_info_tracker_; |
| raw_ptr<const syncer::DeviceInfo> local_device_info_ = |
| fake_local_device_info_provider_.GetLocalDeviceInfo(); |
| }; |
| |
| } // namespace |
| |
| TEST_F(SharingDeviceSourceSyncTest, RunsReadyCallback) { |
| fake_local_device_info_provider_.SetReady(false); |
| EXPECT_FALSE(fake_device_info_tracker_.IsSyncing()); |
| EXPECT_FALSE(fake_local_device_info_provider_.GetLocalDeviceInfo()); |
| |
| auto device_source = CreateDeviceSource(/*wait_until_ready=*/false); |
| |
| base::RunLoop run_loop; |
| bool did_run_callback = false; |
| device_source->AddReadyCallback( |
| base::BindLambdaForTesting([&did_run_callback, &run_loop]() { |
| did_run_callback = true; |
| run_loop.Quit(); |
| })); |
| EXPECT_FALSE(did_run_callback); |
| |
| // Make DeviceInfoTracker ready. |
| fake_device_info_tracker_.Add(local_device_info_); |
| EXPECT_FALSE(did_run_callback); |
| |
| // Set LocalDeviceInfoProvider ready. |
| fake_local_device_info_provider_.SetReady(true); |
| EXPECT_FALSE(did_run_callback); |
| |
| // Wait until local device name is ready. |
| run_loop.Run(); |
| EXPECT_TRUE(did_run_callback); |
| } |
| |
| TEST_F(SharingDeviceSourceSyncTest, GetDeviceByGuid_Ready) { |
| auto device_source = CreateDeviceSource(/*wait_until_ready=*/true); |
| EXPECT_TRUE(device_source->GetDeviceByGuid(local_device_info_->guid())); |
| } |
| |
| TEST_F(SharingDeviceSourceSyncTest, GetDeviceByGuid_NotReady) { |
| auto device_source = CreateDeviceSource(/*wait_until_ready=*/false); |
| fake_device_info_tracker_.Add(local_device_info_); |
| // Even if local device is not ready we should be able to query devices. |
| EXPECT_TRUE(device_source->GetDeviceByGuid(local_device_info_->guid())); |
| } |
| |
| TEST_F(SharingDeviceSourceSyncTest, GetDeviceByGuid_UnknownGuid) { |
| auto device_source = CreateDeviceSource(/*wait_until_ready=*/true); |
| EXPECT_FALSE(device_source->GetDeviceByGuid("unknown")); |
| } |
| |
| TEST_F(SharingDeviceSourceSyncTest, GetDeviceByGuid_SyncDisabled) { |
| auto device_source = CreateDeviceSource(/*wait_until_ready=*/true); |
| test_sync_service_.SetTransportState( |
| syncer::SyncService::TransportState::DISABLED); |
| EXPECT_FALSE(device_source->GetDeviceByGuid(local_device_info_->guid())); |
| } |
| |
| TEST_F(SharingDeviceSourceSyncTest, GetDeviceCandidates_Ready) { |
| auto device_source = CreateDeviceSource(/*wait_until_ready=*/true); |
| auto device_info = CreateDeviceInfo( |
| "client_name", sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2); |
| fake_device_info_tracker_.Add(device_info.get()); |
| |
| auto devices = device_source->GetDeviceCandidates( |
| sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2); |
| ASSERT_EQ(1u, devices.size()); |
| EXPECT_EQ(device_info->guid(), devices[0]->guid()); |
| } |
| |
| TEST_F(SharingDeviceSourceSyncTest, GetDeviceCandidates_NotReady) { |
| auto device_source = CreateDeviceSource(/*wait_until_ready=*/false); |
| auto device_info = CreateDeviceInfo( |
| "client_name", sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2); |
| fake_device_info_tracker_.Add(device_info.get()); |
| // Local device needs to be ready for deduplication. |
| EXPECT_TRUE(device_source |
| ->GetDeviceCandidates( |
| sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2) |
| .empty()); |
| } |
| |
| TEST_F(SharingDeviceSourceSyncTest, GetDeviceCandidates_Deduplicated) { |
| auto device_source = CreateDeviceSource(/*wait_until_ready=*/true); |
| |
| // Add two devices with the same |client_name| without hardware info. |
| task_environment_.FastForwardBy(base::Seconds(10)); |
| auto device_info_1 = CreateDeviceInfo( |
| "client_name_1", sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2); |
| fake_device_info_tracker_.Add(device_info_1.get()); |
| task_environment_.FastForwardBy(base::Seconds(10)); |
| auto device_info_2 = CreateDeviceInfo( |
| "client_name_1", sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2); |
| fake_device_info_tracker_.Add(device_info_2.get()); |
| |
| // Add two devices with the same hardware info. |
| task_environment_.FastForwardBy(base::Seconds(10)); |
| auto device_info_3 = CreateDeviceInfo( |
| "model 1", sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2, |
| "manufacturer 1", "model 1"); |
| fake_device_info_tracker_.Add(device_info_3.get()); |
| task_environment_.FastForwardBy(base::Seconds(10)); |
| auto device_info_4 = CreateDeviceInfo( |
| "model 1", sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2, |
| "manufacturer 1", "model 1"); |
| fake_device_info_tracker_.Add(device_info_4.get()); |
| |
| // Add a device with the same info as the local device. |
| task_environment_.FastForwardBy(base::Seconds(10)); |
| auto device_info_5 = |
| CreateDeviceInfo(local_device_info_->client_name(), |
| sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2, |
| local_device_info_->manufacturer_name(), |
| local_device_info_->model_name()); |
| fake_device_info_tracker_.Add(device_info_5.get()); |
| |
| // Add a device with the local personalizable device name as client_name to |
| // simulate old versions without hardware info. |
| task_environment_.FastForwardBy(base::Seconds(10)); |
| auto device_info_6 = |
| CreateDeviceInfo(syncer::GetPersonalizableDeviceNameBlocking(), |
| sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2); |
| fake_device_info_tracker_.Add(device_info_6.get()); |
| |
| auto devices = device_source->GetDeviceCandidates( |
| sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2); |
| ASSERT_EQ(2u, devices.size()); |
| EXPECT_EQ(device_info_4->guid(), devices[0]->guid()); |
| EXPECT_EQ(device_info_2->guid(), devices[1]->guid()); |
| } |
| |
| TEST_F(SharingDeviceSourceSyncTest, GetDeviceCandidates_DeviceNaming) { |
| auto device_source = CreateDeviceSource(/*wait_until_ready=*/true); |
| |
| task_environment_.FastForwardBy(base::Seconds(10)); |
| auto device_info_1 = CreateDeviceInfo( |
| "client_name", sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2); |
| fake_device_info_tracker_.Add(device_info_1.get()); |
| |
| task_environment_.FastForwardBy(base::Seconds(10)); |
| auto device_info_2 = CreateDeviceInfo( |
| "model 1", sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2, |
| "manufacturer 1", "model 1"); |
| fake_device_info_tracker_.Add(device_info_2.get()); |
| |
| task_environment_.FastForwardBy(base::Seconds(10)); |
| auto device_info_3 = CreateDeviceInfo( |
| "model 2", sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2, |
| "manufacturer 1", "model 2"); |
| fake_device_info_tracker_.Add(device_info_3.get()); |
| |
| task_environment_.FastForwardBy(base::Seconds(10)); |
| auto device_info_4 = CreateDeviceInfo( |
| "model 1", sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2, |
| "manufacturer 2", "model 1"); |
| fake_device_info_tracker_.Add(device_info_4.get()); |
| |
| auto devices = device_source->GetDeviceCandidates( |
| sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2); |
| ASSERT_EQ(4u, devices.size()); |
| EXPECT_EQ( |
| send_tab_to_self::GetSharingDeviceNames(device_info_4.get()).short_name, |
| devices[0]->client_name()); |
| EXPECT_EQ( |
| send_tab_to_self::GetSharingDeviceNames(device_info_3.get()).full_name, |
| devices[1]->client_name()); |
| EXPECT_EQ( |
| send_tab_to_self::GetSharingDeviceNames(device_info_2.get()).full_name, |
| devices[2]->client_name()); |
| EXPECT_EQ( |
| send_tab_to_self::GetSharingDeviceNames(device_info_1.get()).short_name, |
| devices[3]->client_name()); |
| } |
| |
| TEST_F(SharingDeviceSourceSyncTest, GetDeviceCandidates_Expired) { |
| // Create device in advance so we can forward time before calling |
| // GetDeviceCandidates. |
| auto device_source = CreateDeviceSource(/*wait_until_ready=*/true); |
| auto device_info = CreateDeviceInfo( |
| "model 1", sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2, |
| "manufacturer 2", "model 1"); |
| fake_device_info_tracker_.Add(device_info.get()); |
| |
| // Forward time until device expires. |
| task_environment_.FastForwardBy(kSharingDeviceExpiration + |
| base::Milliseconds(1)); |
| |
| std::vector<std::unique_ptr<syncer::DeviceInfo>> candidates = |
| device_source->GetDeviceCandidates( |
| sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2); |
| |
| EXPECT_TRUE(candidates.empty()); |
| } |
| |
| TEST_F(SharingDeviceSourceSyncTest, GetDeviceCandidates_MissingRequirements) { |
| auto device_source = CreateDeviceSource(/*wait_until_ready=*/true); |
| // Create device in with Click to call feature. |
| auto device_info = CreateDeviceInfo( |
| "model 1", sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2, |
| "manufacturer 2", "model 1"); |
| fake_device_info_tracker_.Add(device_info.get()); |
| |
| // Requires shared clipboard feature. |
| std::vector<std::unique_ptr<syncer::DeviceInfo>> candidates = |
| device_source->GetDeviceCandidates( |
| sync_pb::SharingSpecificFields::SHARED_CLIPBOARD_V2); |
| |
| EXPECT_TRUE(candidates.empty()); |
| } |
| |
| TEST_F(SharingDeviceSourceSyncTest, |
| GetDeviceCandidates_AlternativeRequirement) { |
| auto device_source = CreateDeviceSource(/*wait_until_ready=*/true); |
| auto device_info = CreateDeviceInfo( |
| "client_name", sync_pb::SharingSpecificFields::CLICK_TO_CALL_VAPID); |
| fake_device_info_tracker_.Add(device_info.get()); |
| |
| auto devices = device_source->GetDeviceCandidates( |
| sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2); |
| ASSERT_EQ(1u, devices.size()); |
| EXPECT_EQ(device_info->guid(), devices[0]->guid()); |
| } |
| |
| TEST_F(SharingDeviceSourceSyncTest, GetDeviceCandidates_RenameAfterFiltering) { |
| auto device_source = CreateDeviceSource(/*wait_until_ready=*/true); |
| |
| // This device will be filtered out because its older than |min_updated_time|. |
| task_environment_.FastForwardBy(base::Seconds(10)); |
| auto device_info_1 = CreateDeviceInfo( |
| "model 3", sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2, |
| "manufacturer 2", "model 3"); |
| fake_device_info_tracker_.Add(device_info_1.get()); |
| |
| // This device will be displayed with its short name. |
| task_environment_.FastForwardBy(kSharingDeviceExpiration); |
| auto device_info_2 = CreateDeviceInfo( |
| "model 1", sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2, |
| "manufacturer 1", "model 1"); |
| fake_device_info_tracker_.Add(device_info_2.get()); |
| |
| // This device will be filtered out since click to call is not enabled. |
| task_environment_.FastForwardBy(base::Seconds(10)); |
| auto device_info_3 = CreateDeviceInfo( |
| "model 1", sync_pb::SharingSpecificFields::SHARED_CLIPBOARD_V2, |
| "manufacturer 1", "model 1"); |
| fake_device_info_tracker_.Add(device_info_3.get()); |
| |
| // This device will be displayed with its short name. |
| task_environment_.FastForwardBy(base::Seconds(10)); |
| auto device_info_4 = CreateDeviceInfo( |
| "model 2", sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2, |
| "manufacturer 2", "model 2"); |
| fake_device_info_tracker_.Add(device_info_4.get()); |
| |
| auto devices = device_source->GetDeviceCandidates( |
| sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2); |
| ASSERT_EQ(2u, devices.size()); |
| EXPECT_EQ(device_info_4->guid(), devices[0]->guid()); |
| EXPECT_EQ( |
| send_tab_to_self::GetSharingDeviceNames(device_info_4.get()).short_name, |
| devices[0]->client_name()); |
| EXPECT_EQ(device_info_2->guid(), devices[1]->guid()); |
| EXPECT_EQ( |
| send_tab_to_self::GetSharingDeviceNames(device_info_2.get()).short_name, |
| devices[1]->client_name()); |
| } |
| |
| TEST_F(SharingDeviceSourceSyncTest, GetDeviceCandidates_NoChannel) { |
| auto device_source = CreateDeviceSource(/*wait_until_ready=*/true); |
| auto device_info = CreateDeviceInfo( |
| "client_name", sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2, |
| "manufacturer", "model", |
| /*vapid_target_info=*/{}, /*sender_id_target_info=*/{}); |
| fake_device_info_tracker_.Add(device_info.get()); |
| |
| auto devices = device_source->GetDeviceCandidates( |
| sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2); |
| EXPECT_TRUE(devices.empty()); |
| } |
| |
| TEST_F(SharingDeviceSourceSyncTest, GetDeviceCandidates_FCMChannel) { |
| scoped_feature_list_.InitAndDisableFeature(kSharingSendViaSync); |
| auto device_source = CreateDeviceSource(/*wait_until_ready=*/true); |
| auto device_info = CreateDeviceInfo( |
| "client_name", sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2, |
| "manufacturer", "model", |
| {kVapidFCMToken, kDevicep256dh, kDeviceAuthSecret}, |
| /*sender_id_target_info=*/{}); |
| fake_device_info_tracker_.Add(device_info.get()); |
| |
| auto devices = device_source->GetDeviceCandidates( |
| sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2); |
| ASSERT_EQ(1u, devices.size()); |
| EXPECT_EQ(device_info->guid(), devices[0]->guid()); |
| } |
| |
| TEST_F(SharingDeviceSourceSyncTest, GetDeviceCandidates_SenderIDChannel) { |
| test_sync_service_.SetActiveDataTypes( |
| {syncer::DEVICE_INFO, syncer::SHARING_MESSAGE}); |
| auto device_source = CreateDeviceSource(/*wait_until_ready=*/true); |
| auto device_info = CreateDeviceInfo( |
| "client_name", sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2, |
| "manufacturer", "model", |
| /*vapid_target_info=*/{}, |
| {kSenderIdFCMToken, kSenderIdP256dh, kSenderIdAuthSecret}); |
| fake_device_info_tracker_.Add(device_info.get()); |
| |
| auto devices = device_source->GetDeviceCandidates( |
| sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2); |
| ASSERT_EQ(1u, devices.size()); |
| EXPECT_EQ(device_info->guid(), devices[0]->guid()); |
| } |