blob: 7eccc57ebbe7457e4e2211359add493afff98f5c [file]
// 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 "components/sharing_message/sharing_device_source_sync.h"
#include <memory>
#include "base/functional/callback.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/test/task_environment.h"
#include "base/time/time.h"
#include "base/uuid.h"
#include "components/send_tab_to_self/features.h"
#include "components/sharing_message/features.h"
#include "components/sharing_message/sharing_constants.h"
#include "components/sharing_message/sharing_utils.h"
#include "components/sync/base/features.h"
#include "components/sync/test/test_sync_service.h"
#include "components/sync_device_info/device_info.h"
#include "components/sync_device_info/device_name_util.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 "components/sync_device_info/test_device_info_builder.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
using SharingFeature = syncer::DeviceInfo::SharingFeature;
const char kSenderIdFCMToken[] = "sharing_fcm_token";
const char kSenderIdP256dh[] = "sharing_p256dh";
const char kSenderIdAuthSecret[] = "sharing_auth_secret";
const char kChimeRepresentativeTargetId[] = "chime_rep_id";
std::unique_ptr<syncer::DeviceInfo> CreateDeviceInfo(
const std::string& client_name,
SharingFeature enabled_feature,
const std::string& manufacturer_name = "manufacturer",
const std::string& model_name = "model",
syncer::DeviceInfo::SharingTargetInfo sender_id_target_info =
{kSenderIdFCMToken, kSenderIdP256dh, kSenderIdAuthSecret},
const std::string& chime_rep_id = kChimeRepresentativeTargetId) {
return syncer::TestDeviceInfoBuilder(syncer::DeviceInfo::OsType::kLinux)
.WithGuid(base::Uuid::GenerateRandomV4().AsLowercaseString())
.WithClientName(client_name)
.WithSharingInfo(
{{std::move(sender_id_target_info), chime_rep_id, {enabled_feature}}})
.WithManufacturerName(manufacturer_name)
.WithModelName(model_name)
.Build();
}
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:
base::test::TaskEnvironment 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);
bool did_run_callback = false;
device_source->AddReadyCallback(base::BindLambdaForTesting(
[&did_run_callback]() { did_run_callback = true; }));
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_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, querying devices should succeed.
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_.SetSignedOut();
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", SharingFeature::kRemoteCopy);
fake_device_info_tracker_.Add(device_info.get());
auto devices =
device_source->GetDeviceCandidates(SharingFeature::kRemoteCopy);
ASSERT_EQ(1u, devices.size());
EXPECT_EQ(device_info->guid(), devices[0].guid());
}
TEST_F(SharingDeviceSourceSyncTest, GetDeviceCandidates_NotReady) {
fake_local_device_info_provider_.SetReady(false);
auto device_source = CreateDeviceSource(/*wait_until_ready=*/false);
auto device_info =
CreateDeviceInfo("client_name", SharingFeature::kRemoteCopy);
fake_device_info_tracker_.Add(device_info.get());
// Local device needs to be ready for deduplication.
EXPECT_TRUE(
device_source->GetDeviceCandidates(SharingFeature::kRemoteCopy)
.empty());
}
enum class DeviceNamingMode {
kLegacyWithDeduplication,
kSimplifiedWithoutDeduplication,
};
class SharingDeviceSourceSyncNamingTest
: public SharingDeviceSourceSyncTest,
public testing::WithParamInterface<DeviceNamingMode> {
public:
SharingDeviceSourceSyncNamingTest() {
if (GetParam() == DeviceNamingMode::kSimplifiedWithoutDeduplication) {
scoped_feature_list_.InitAndEnableFeature(
syncer::kSyncSimplifyDeviceNaming);
} else {
scoped_feature_list_.InitAndDisableFeature(
syncer::kSyncSimplifyDeviceNaming);
}
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// Tests the deduplication and local device filtering behavior of
// GetDeviceCandidates. Parameterized by whether simplified device naming is
// enabled.
TEST_P(SharingDeviceSourceSyncNamingTest,
GetDeviceCandidates_DeduplicationBehavior) {
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", SharingFeature::kRemoteCopy);
fake_device_info_tracker_.Add(device_info_1.get());
task_environment_.FastForwardBy(base::Seconds(10));
auto device_info_2 =
CreateDeviceInfo("client_name_1", SharingFeature::kRemoteCopy);
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", SharingFeature::kRemoteCopy,
"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", SharingFeature::kRemoteCopy,
"manufacturer 1", "model 1");
fake_device_info_tracker_.Add(device_info_4.get());
// Add a device with the same info as the local device (but different GUID).
task_environment_.FastForwardBy(base::Seconds(10));
auto device_info_5 = CreateDeviceInfo(local_device_info_->client_name(),
SharingFeature::kRemoteCopy,
local_device_info_->manufacturer_name(),
local_device_info_->model_name());
fake_device_info_tracker_.Add(device_info_5.get());
auto devices =
device_source->GetDeviceCandidates(SharingFeature::kRemoteCopy);
if (GetParam() == DeviceNamingMode::kSimplifiedWithoutDeduplication) {
// With kSyncSimplifyDeviceNaming enabled:
// 1. No deduplication (device_info_1 and device_info_3 are kept).
// 2. No filtering of same-name-as-local-device (device_info_5 is kept).
// 3. Local device itself is still filtered out (by GUID).
ASSERT_EQ(5u, devices.size());
EXPECT_EQ(device_info_5->guid(), devices[0].guid());
EXPECT_EQ(device_info_4->guid(), devices[1].guid());
EXPECT_EQ(device_info_3->guid(), devices[2].guid());
EXPECT_EQ(device_info_2->guid(), devices[3].guid());
EXPECT_EQ(device_info_1->guid(), devices[4].guid());
} else {
// With kSyncSimplifyDeviceNaming disabled:
// 1. Deduplication active (device_info_4 keeps newer, drops 3.
// device_info_2 keeps newer, drops 1).
// 2. Local device name matching active (device_info_5 is dropped because it
// matches local device name).
ASSERT_EQ(2u, devices.size());
EXPECT_EQ(device_info_4->guid(), devices[0].guid());
EXPECT_EQ(device_info_2->guid(), devices[1].guid());
}
}
// Tests that device names are resolved correctly, including collision
// resolution in legacy mode and preferred names in simplified mode.
TEST_P(SharingDeviceSourceSyncNamingTest, GetDeviceCandidates_DeviceNaming) {
auto device_source = CreateDeviceSource(/*wait_until_ready=*/true);
task_environment_.FastForwardBy(base::Seconds(10));
auto device_info_1 =
CreateDeviceInfo("client_name", SharingFeature::kRemoteCopy);
fake_device_info_tracker_.Add(device_info_1.get());
task_environment_.FastForwardBy(base::Seconds(10));
auto device_info_2 =
CreateDeviceInfo("model 1", SharingFeature::kRemoteCopy,
"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", SharingFeature::kRemoteCopy,
"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", SharingFeature::kRemoteCopy,
"manufacturer 2", "model 1");
fake_device_info_tracker_.Add(device_info_4.get());
auto devices =
device_source->GetDeviceCandidates(SharingFeature::kRemoteCopy);
ASSERT_EQ(4u, devices.size());
if (GetParam() == DeviceNamingMode::kSimplifiedWithoutDeduplication) {
// With kSyncSimplifyDeviceNaming enabled:
// All devices get their preferred name, even if they collide.
EXPECT_EQ(syncer::GetDisplayNameCandidates(device_info_4.get())
.preferred_name_if_unique,
devices[0].client_name());
EXPECT_EQ(syncer::GetDisplayNameCandidates(device_info_3.get())
.preferred_name_if_unique,
devices[1].client_name());
EXPECT_EQ(syncer::GetDisplayNameCandidates(device_info_2.get())
.preferred_name_if_unique,
devices[2].client_name());
EXPECT_EQ(syncer::GetDisplayNameCandidates(device_info_1.get())
.preferred_name_if_unique,
devices[3].client_name());
} else {
// With kSyncSimplifyDeviceNaming disabled:
// Collision resolution is active, so colliding devices get fallback full
// names.
EXPECT_EQ(syncer::GetDisplayNameCandidates(device_info_4.get())
.preferred_name_if_unique,
devices[0].client_name());
EXPECT_EQ(syncer::GetDisplayNameCandidates(device_info_3.get())
.fallback_full_name,
devices[1].client_name());
EXPECT_EQ(syncer::GetDisplayNameCandidates(device_info_2.get())
.fallback_full_name,
devices[2].client_name());
EXPECT_EQ(syncer::GetDisplayNameCandidates(device_info_1.get())
.preferred_name_if_unique,
devices[3].client_name());
}
}
TEST_F(SharingDeviceSourceSyncTest, GetDeviceCandidates_Expired) {
// Create device in advance so time can be forwarded before calling
// GetDeviceCandidates.
auto device_source = CreateDeviceSource(/*wait_until_ready=*/true);
auto device_info =
CreateDeviceInfo("model 1", SharingFeature::kRemoteCopy,
"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<SharingTargetDeviceInfo> candidates =
device_source->GetDeviceCandidates(SharingFeature::kRemoteCopy);
EXPECT_TRUE(candidates.empty());
}
TEST_F(SharingDeviceSourceSyncTest, GetDeviceCandidates_MissingRequirements) {
auto device_source = CreateDeviceSource(/*wait_until_ready=*/true);
// Create device with SMS fetcher feature.
auto device_info =
CreateDeviceInfo("model 1", SharingFeature::kSmsFetcher,
"manufacturer 2", "model 1");
fake_device_info_tracker_.Add(device_info.get());
// Requires remote copy feature.
std::vector<SharingTargetDeviceInfo> candidates =
device_source->GetDeviceCandidates(SharingFeature::kRemoteCopy);
EXPECT_TRUE(candidates.empty());
}
TEST_F(SharingDeviceSourceSyncTest,
GetDeviceCandidates_ExpiredAndMissingRequirements) {
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", SharingFeature::kRemoteCopy,
"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", SharingFeature::kRemoteCopy,
"manufacturer 1", "model 1");
fake_device_info_tracker_.Add(device_info_2.get());
// This device will be filtered out since remote copy is not enabled.
task_environment_.FastForwardBy(base::Seconds(10));
auto device_info_3 = CreateDeviceInfo("model 1", SharingFeature::kSmsFetcher,
"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", SharingFeature::kRemoteCopy,
"manufacturer 2", "model 2");
fake_device_info_tracker_.Add(device_info_4.get());
auto devices =
device_source->GetDeviceCandidates(SharingFeature::kRemoteCopy);
ASSERT_EQ(2u, devices.size());
EXPECT_EQ(device_info_4->guid(), devices[0].guid());
EXPECT_EQ(
syncer::GetDisplayNameCandidates(device_info_4.get()).preferred_name_if_unique,
devices[0].client_name());
EXPECT_EQ(device_info_2->guid(), devices[1].guid());
EXPECT_EQ(
syncer::GetDisplayNameCandidates(device_info_2.get()).preferred_name_if_unique,
devices[1].client_name());
}
TEST_F(SharingDeviceSourceSyncTest, GetDeviceCandidates_NoChannel) {
auto device_source = CreateDeviceSource(/*wait_until_ready=*/true);
auto device_info =
CreateDeviceInfo("client_name", SharingFeature::kRemoteCopy,
"manufacturer", "model",
/*sender_id_target_info=*/{});
fake_device_info_tracker_.Add(device_info.get());
auto devices =
device_source->GetDeviceCandidates(SharingFeature::kRemoteCopy);
EXPECT_TRUE(devices.empty());
}
TEST_F(SharingDeviceSourceSyncTest, GetDeviceCandidates_FCMChannel) {
auto device_source = CreateDeviceSource(/*wait_until_ready=*/true);
auto device_info =
CreateDeviceInfo("client_name", SharingFeature::kRemoteCopy,
"manufacturer", "model",
/*sender_id_target_info=*/{});
fake_device_info_tracker_.Add(device_info.get());
auto devices =
device_source->GetDeviceCandidates(SharingFeature::kRemoteCopy);
// FCM channel is not supported.
ASSERT_EQ(0u, devices.size());
}
TEST_F(SharingDeviceSourceSyncTest, GetDeviceCandidates_SenderIDChannel) {
auto device_source = CreateDeviceSource(/*wait_until_ready=*/true);
auto device_info = CreateDeviceInfo(
"client_name", SharingFeature::kRemoteCopy, "manufacturer",
"model", {kSenderIdFCMToken, kSenderIdP256dh, kSenderIdAuthSecret});
fake_device_info_tracker_.Add(device_info.get());
auto devices =
device_source->GetDeviceCandidates(SharingFeature::kRemoteCopy);
ASSERT_EQ(1u, devices.size());
EXPECT_EQ(device_info->guid(), devices[0].guid());
}
INSTANTIATE_TEST_SUITE_P(
,
SharingDeviceSourceSyncNamingTest,
testing::Values(DeviceNamingMode::kLegacyWithDeduplication,
DeviceNamingMode::kSimplifiedWithoutDeduplication));