blob: 90e04d0a01f9a4d2320085b70cf4a14615c5a8ab [file] [log] [blame]
// Copyright 2023 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/data_sharing/internal/data_sharing_service_impl.h"
#include <memory>
#include <optional>
#include "base/files/scoped_temp_dir.h"
#include "base/memory/raw_ptr.h"
#include "base/test/bind.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "base/version_info/channel.h"
#include "components/data_sharing/public/data_sharing_sdk_delegate.h"
#include "components/data_sharing/public/data_sharing_service.h"
#include "components/data_sharing/public/data_sharing_ui_delegate.h"
#include "components/data_sharing/public/features.h"
#include "components/data_sharing/public/group_data.h"
#include "components/data_sharing/public/protocol/data_sharing_sdk.pb.h"
#include "components/data_sharing/public/protocol/group_data.pb.h"
#include "components/data_sharing/test_support/fake_data_sharing_sdk_delegate.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "components/sync/model/entity_change.h"
#include "components/sync/model/metadata_change_list.h"
#include "components/sync/protocol/collaboration_group_specifics.pb.h"
#include "components/sync/test/data_type_store_test_util.h"
#include "google_apis/gaia/gaia_id.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/status/status.h"
namespace data_sharing {
namespace {
using base::test::RunClosure;
using testing::Eq;
const char kGroupId[] = "/?-group_id";
const char kEncodedGroupId[] = "%2F%3F-group_id";
const char kTokenBlob[] = "/?-_token";
const char kEncodedTokenBlob[] = "%2F%3F-_token";
// Enum cases for parameterizing the tests.
enum Variant {
kDelegateAtCreation,
kDelegateAfter,
};
} // namespace
class DataSharingServiceImplTest : public ::testing::TestWithParam<Variant> {
public:
DataSharingServiceImplTest() = default;
~DataSharingServiceImplTest() override { task_environment_.RunUntilIdle(); }
void SetUp() override {
EXPECT_TRUE(profile_dir_.CreateUniqueTempDir());
scoped_refptr<network::SharedURLLoaderFactory> test_url_loader_factory =
base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
&test_url_loader_factory_);
std::unique_ptr<FakeDataSharingSDKDelegate> sdk_delegate;
if (!ShouldSetSDKLater()) {
sdk_delegate = std::make_unique<FakeDataSharingSDKDelegate>();
not_owned_sdk_delegate_ = sdk_delegate.get();
}
data_sharing_service_ = std::make_unique<DataSharingServiceImpl>(
profile_dir_.GetPath(), std::move(test_url_loader_factory),
identity_test_env_.identity_manager(),
syncer::DataTypeStoreTestUtil::FactoryForInMemoryStoreForTest(),
version_info::Channel::UNKNOWN, std::move(sdk_delegate),
/*ui_delegate=*/nullptr);
if (ShouldSetSDKLater()) {
sdk_delegate = std::make_unique<FakeDataSharingSDKDelegate>();
not_owned_sdk_delegate_ = sdk_delegate.get();
data_sharing_service_->SetSDKDelegate(std::move(sdk_delegate));
}
}
protected:
// Returns whether the SDK delegate should be set after the DataSharingService
// creation.
bool ShouldSetSDKLater() {
switch (GetParam()) {
case kDelegateAtCreation:
return false;
case kDelegateAfter:
return true;
}
}
base::test::TaskEnvironment task_environment_;
base::ScopedTempDir profile_dir_;
signin::IdentityTestEnvironment identity_test_env_;
network::TestURLLoaderFactory test_url_loader_factory_;
std::unique_ptr<DataSharingServiceImpl> data_sharing_service_;
raw_ptr<FakeDataSharingSDKDelegate> not_owned_sdk_delegate_;
};
TEST_P(DataSharingServiceImplTest, ConstructionAndEmptyServiceCheck) {
EXPECT_FALSE(data_sharing_service_->IsEmptyService());
}
TEST_P(DataSharingServiceImplTest, GetDataSharingNetworkLoader) {
EXPECT_TRUE(data_sharing_service_->GetDataSharingNetworkLoader());
}
TEST_P(DataSharingServiceImplTest, ShouldCreateGroup) {
// TODO(crbug.com/301390275): add a version of this test for unhappy path.
const std::string display_name = "display_name";
DataSharingService::GroupDataOrFailureOutcome outcome;
base::RunLoop run_loop;
data_sharing_service_->CreateGroup(
display_name,
base::BindLambdaForTesting(
[&run_loop, &outcome](
const DataSharingService::GroupDataOrFailureOutcome& result) {
outcome = result;
run_loop.Quit();
}));
run_loop.Run();
ASSERT_TRUE(outcome.has_value());
EXPECT_THAT(outcome->display_name, Eq(display_name));
ASSERT_TRUE(not_owned_sdk_delegate_->GetGroup(outcome->group_token.group_id)
.has_value());
EXPECT_THAT(not_owned_sdk_delegate_->GetGroup(outcome->group_token.group_id)
->display_name(),
Eq(display_name));
}
TEST_P(DataSharingServiceImplTest, ShouldDeleteGroup) {
// TODO(crbug.com/301390275): add a version of this test for unhappy path.
const GroupId group_id =
not_owned_sdk_delegate_->AddGroupAndReturnId("display_name");
std::optional<data_sharing_pb::GroupData> group_data_pb =
not_owned_sdk_delegate_->GetGroup(group_id);
ASSERT_TRUE(group_data_pb.has_value());
base::RunLoop run_loop;
base::MockOnceCallback<void(DataSharingService::PeopleGroupActionOutcome)>
callback;
EXPECT_CALL(callback,
Run(DataSharingService::PeopleGroupActionOutcome::kSuccess))
.WillOnce(RunClosure(run_loop.QuitClosure()));
data_sharing_service_->DeleteGroup(group_id, callback.Get());
run_loop.Run();
EXPECT_FALSE(not_owned_sdk_delegate_->GetGroup(group_id).has_value());
// The group should still be available for lookup through a specific API.
// The GroupDataModel informs the service about deletions, so we invoke that
// method here to mimic that behavior.
std::optional<GroupData> group_data = std::make_optional<GroupData>();
group_data->group_token = GroupToken(group_id, "");
group_data->display_name = group_data_pb->display_name();
data_sharing_service_->OnGroupDeleted(group_id, group_data, base::Time());
std::optional<GroupData> retrieved_group_data =
data_sharing_service_->GetPossiblyRemovedGroup(group_id);
ASSERT_TRUE(retrieved_group_data.has_value());
EXPECT_EQ(retrieved_group_data->group_token.group_id,
group_data->group_token.group_id);
EXPECT_EQ(retrieved_group_data->display_name, group_data->display_name);
}
TEST_P(DataSharingServiceImplTest, ShouldReadGroup) {
// TODO(crbug.com/382036119): tested API is deprecated, removed this test once
// there are no callers.
const std::string display_name = "display_name";
const GroupId group_id =
not_owned_sdk_delegate_->AddGroupAndReturnId(display_name);
DataSharingService::GroupDataOrFailureOutcome outcome;
base::RunLoop run_loop;
data_sharing_service_->ReadGroupDeprecated(
group_id,
base::BindLambdaForTesting(
[&run_loop, &outcome](
const DataSharingService::GroupDataOrFailureOutcome& result) {
outcome = result;
run_loop.Quit();
}));
run_loop.Run();
ASSERT_TRUE(outcome.has_value());
EXPECT_THAT(outcome->display_name, Eq(display_name));
EXPECT_THAT(outcome->group_token.group_id, Eq(group_id));
}
TEST_P(DataSharingServiceImplTest, ShouldInviteMember) {
// TODO(crbug.com/301390275): add a version of this test for unhappy paths.
const GroupId group_id =
not_owned_sdk_delegate_->AddGroupAndReturnId("display_name");
const std::string email = "user@gmail.com";
const GaiaId gaia_id("123456789");
not_owned_sdk_delegate_->AddAccount(email, gaia_id);
base::RunLoop run_loop;
base::MockOnceCallback<void(DataSharingService::PeopleGroupActionOutcome)>
callback;
EXPECT_CALL(callback,
Run(DataSharingService::PeopleGroupActionOutcome::kSuccess))
.WillOnce(RunClosure(run_loop.QuitClosure()));
data_sharing_service_->InviteMember(group_id, email, callback.Get());
run_loop.Run();
auto group = not_owned_sdk_delegate_->GetGroup(group_id);
ASSERT_TRUE(group.has_value());
ASSERT_THAT(group->members().size(), Eq(1));
EXPECT_THAT(group->members(0).gaia_id(), Eq(gaia_id.ToString()));
}
TEST_P(DataSharingServiceImplTest, ShouldRemoveMember) {
// TODO(crbug.com/301390275): add a version of this test for unhappy paths.
const GroupId group_id =
not_owned_sdk_delegate_->AddGroupAndReturnId("display_name");
const std::string email = "user@gmail.com";
const GaiaId gaia_id("123456789");
not_owned_sdk_delegate_->AddAccount(email, gaia_id);
not_owned_sdk_delegate_->AddMember(group_id, gaia_id);
base::RunLoop run_loop;
base::MockOnceCallback<void(DataSharingService::PeopleGroupActionOutcome)>
callback;
EXPECT_CALL(callback,
Run(DataSharingService::PeopleGroupActionOutcome::kSuccess))
.WillOnce(RunClosure(run_loop.QuitClosure()));
data_sharing_service_->RemoveMember(group_id, email, callback.Get());
run_loop.Run();
auto group = not_owned_sdk_delegate_->GetGroup(group_id);
ASSERT_TRUE(group.has_value());
EXPECT_TRUE(group->members().empty());
}
TEST_P(DataSharingServiceImplTest, ShouldLeaveGroup) {
const GroupId group_id =
not_owned_sdk_delegate_->AddGroupAndReturnId("display_name");
const std::string email = "user@gmail.com";
const GaiaId gaia_id("123456789");
not_owned_sdk_delegate_->SetUserGaiaId(gaia_id);
not_owned_sdk_delegate_->AddAccount(email, gaia_id);
not_owned_sdk_delegate_->AddMember(group_id, gaia_id);
base::RunLoop run_loop;
base::MockOnceCallback<void(DataSharingService::PeopleGroupActionOutcome)>
callback;
EXPECT_CALL(callback,
Run(DataSharingService::PeopleGroupActionOutcome::kSuccess))
.WillOnce(RunClosure(run_loop.QuitClosure()));
data_sharing_service_->LeaveGroup(group_id, callback.Get());
run_loop.Run();
auto group = not_owned_sdk_delegate_->GetGroup(group_id);
ASSERT_TRUE(group.has_value());
EXPECT_TRUE(group->members().empty());
}
TEST_P(DataSharingServiceImplTest, ParseAndInterceptDataSharingURL) {
GURL url = GURL(data_sharing::features::kDataSharingURL.Get() +
"?group_id=" + kGroupId + "&token_blob=" + kTokenBlob);
DataSharingService::ParseUrlResult result =
data_sharing_service_->ParseDataSharingUrl(url);
// Verify valid path.
ASSERT_TRUE(result.has_value());
EXPECT_EQ(kGroupId, result.value().group_id.value());
EXPECT_EQ(kTokenBlob, result.value().access_token);
EXPECT_TRUE(data_sharing_service_->ShouldInterceptNavigationForShareURL(url));
// Verify host/path error.
std::string invalid = "https://www.test.com/";
url = GURL(invalid + "?group_id=" + kGroupId + "&token_blob=" + kTokenBlob);
result = data_sharing_service_->ParseDataSharingUrl(url);
ASSERT_FALSE(result.has_value());
EXPECT_EQ(result.error(),
DataSharingService::ParseUrlStatus::kHostOrPathMismatchFailure);
EXPECT_FALSE(
data_sharing_service_->ShouldInterceptNavigationForShareURL(url));
// Verify query missing error.
url = GURL(data_sharing::features::kDataSharingURL.Get() +
"?access_token=" + kGroupId);
result = data_sharing_service_->ParseDataSharingUrl(url);
ASSERT_FALSE(result.has_value());
EXPECT_EQ(result.error(),
DataSharingService::ParseUrlStatus::kQueryMissingFailure);
EXPECT_TRUE(data_sharing_service_->ShouldInterceptNavigationForShareURL(url));
// Verify access token missing is ok.
url = GURL(data_sharing::features::kDataSharingURL.Get() +
"?group_id=" + kGroupId);
result = data_sharing_service_->ParseDataSharingUrl(url);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(kGroupId, result.value().group_id.value());
EXPECT_EQ("", result.value().access_token);
EXPECT_TRUE(data_sharing_service_->ShouldInterceptNavigationForShareURL(url));
}
TEST_P(DataSharingServiceImplTest, GetDataSharingUrl) {
GroupData group_data = GroupData();
group_data.group_token =
GroupToken(data_sharing::GroupId(kGroupId), kTokenBlob);
GURL url = GURL(data_sharing::features::kDataSharingURL.Get() + "?group_id=" +
kEncodedGroupId + "&token_blob=" + kEncodedTokenBlob);
std::unique_ptr<GURL> result_url =
data_sharing_service_->GetDataSharingUrl(group_data);
// Verify valid path.
EXPECT_TRUE(result_url);
EXPECT_EQ(url, *result_url);
// Verify invalid group data.
result_url = data_sharing_service_->GetDataSharingUrl(GroupData());
EXPECT_FALSE(result_url);
}
TEST_P(DataSharingServiceImplTest, AddGroupDataForTesting) {
data_sharing::GroupId group_id = data_sharing::GroupId(kGroupId);
const GaiaId gaia_id("gaia_id");
const std::string display_name = "Invitee Display Name";
const std::string email = "invitee@mail.com";
const MemberRole role = MemberRole::kInvitee;
const GURL avatar_url = GURL("chrome://newtab");
const std::string given_name = "Invitee Given Name";
const std::string access_token = "fake_access_token";
GroupMember group_member =
GroupMember(gaia_id, display_name, email, role, avatar_url, given_name);
GroupData group_data =
GroupData(group_id, display_name, {group_member}, access_token);
data_sharing_service_->AddGroupDataForTesting(std::move(group_data));
std::optional<GroupData> returned_group_data =
data_sharing_service_->ReadGroup(group_id);
EXPECT_EQ(returned_group_data->members.size(), 1u);
EXPECT_EQ(returned_group_data->display_name, display_name);
EXPECT_EQ(returned_group_data->group_token.group_id, group_id);
EXPECT_EQ(returned_group_data->group_token.access_token, access_token);
EXPECT_EQ(returned_group_data->members[0].gaia_id, gaia_id);
EXPECT_EQ(returned_group_data->members[0].display_name, display_name);
EXPECT_EQ(returned_group_data->members[0].email, email);
EXPECT_EQ(returned_group_data->members[0].role, role);
EXPECT_EQ(returned_group_data->members[0].avatar_url, avatar_url);
EXPECT_EQ(returned_group_data->members[0].given_name, given_name);
}
INSTANTIATE_TEST_SUITE_P(DataSharingServiceImplTestInstantiation,
DataSharingServiceImplTest,
::testing::Values(Variant::kDelegateAtCreation,
Variant::kDelegateAfter));
} // namespace data_sharing