blob: d835b6f2157885d64c2d58a3146872a62dc14db1 [file] [log] [blame]
// Copyright 2024 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/collaboration/internal/collaboration_service_impl.h"
#include <memory>
#include "base/test/run_until.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "components/collaboration/internal/collaboration_controller.h"
#include "components/collaboration/test_support/mock_collaboration_controller_delegate.h"
#include "components/data_sharing/public/data_sharing_service.h"
#include "components/data_sharing/public/features.h"
#include "components/data_sharing/test_support/mock_data_sharing_service.h"
#include "components/saved_tab_groups/test_support/mock_tab_group_sync_service.h"
#include "components/signin/public/base/signin_pref_names.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "components/sync/base/features.h"
#include "components/sync/test/test_sync_service.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "google_apis/gaia/gaia_id.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#if BUILDFLAG(IS_ANDROID)
#include "base/android/build_info.h"
#endif // BUILDFLAG(IS_ANDROID)
using data_sharing::GroupData;
using data_sharing::GroupId;
using data_sharing::GroupMember;
using data_sharing::MemberRole;
using testing::_;
using testing::Invoke;
using testing::Return;
namespace collaboration {
namespace {
constexpr GaiaId::Literal kUserGaia("gaia_id");
constexpr char kConsumerUserEmail[] = "test@email.com";
constexpr char kManagedUserEmail[] = "test@google.com";
constexpr char kGroupId[] = "/?-group_id";
constexpr char kAccessToken[] = "/?-access_token";
} // namespace
class CollaborationServiceImplTest : public testing::Test {
public:
CollaborationServiceImplTest() = default;
~CollaborationServiceImplTest() override = default;
void SetUp() override {
#if BUILDFLAG(IS_ANDROID)
if (base::android::BuildInfo::GetInstance()->is_automotive()) {
// TODO(crbug.com/399444939): Re-enable once automotive is supported.
GTEST_SKIP() << "Test shouldn't run on automotive builders.";
}
#endif
test_sync_service_ = std::make_unique<syncer::TestSyncService>();
pref_service_.registry()->RegisterBooleanPref(prefs::kSigninAllowed, true);
InitService();
}
void TearDown() override { service_.reset(); }
void InitService() {
service_ = std::make_unique<CollaborationServiceImpl>(
&mock_tab_group_sync_service_, &mock_data_sharing_service_,
identity_test_env_.identity_manager(), &pref_service_);
service_->OnSyncServiceInitialized(test_sync_service_.get());
}
protected:
base::test::SingleThreadTaskEnvironment task_environment_;
sync_preferences::TestingPrefServiceSyncable pref_service_;
signin::IdentityTestEnvironment identity_test_env_;
std::unique_ptr<syncer::TestSyncService> test_sync_service_;
tab_groups::MockTabGroupSyncService mock_tab_group_sync_service_;
data_sharing::MockDataSharingService mock_data_sharing_service_;
std::unique_ptr<CollaborationServiceImpl> service_;
};
TEST_F(CollaborationServiceImplTest, ConstructionAndEmptyServiceCheck) {
EXPECT_FALSE(service_->IsEmptyService());
}
TEST_F(CollaborationServiceImplTest, GetCurrentUserRoleForGroup) {
GroupData group_data = GroupData();
GroupMember group_member = GroupMember();
group_member.gaia_id = kUserGaia;
group_member.role = MemberRole::kOwner;
group_data.members.push_back(group_member);
data_sharing::GroupId group_id = data_sharing::GroupId(kGroupId);
// Empty or non existent group should return unknown role.
EXPECT_CALL(mock_data_sharing_service_, ReadGroup(group_id))
.WillOnce(Return(std::nullopt));
EXPECT_EQ(service_->GetCurrentUserRoleForGroup(group_id),
MemberRole::kUnknown);
// No current primary account should return unknown role.
EXPECT_CALL(mock_data_sharing_service_, ReadGroup(group_id))
.WillRepeatedly(Return(group_data));
EXPECT_EQ(service_->GetCurrentUserRoleForGroup(group_id),
MemberRole::kUnknown);
identity_test_env_.MakeAccountAvailable(
kConsumerUserEmail,
{.primary_account_consent_level = signin::ConsentLevel::kSignin,
.gaia_id = kUserGaia});
EXPECT_EQ(service_->GetCurrentUserRoleForGroup(group_id), MemberRole::kOwner);
}
TEST_F(CollaborationServiceImplTest, GetServiceStatus_Disabled) {
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeatures({},
{data_sharing::features::kDataSharingFeature,
data_sharing::features::kDataSharingJoinOnly});
InitService();
EXPECT_EQ(service_->GetServiceStatus().collaboration_status,
CollaborationStatus::kDisabled);
}
TEST_F(CollaborationServiceImplTest, GetServiceStatus_JoinOnly) {
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeatures({data_sharing::features::kDataSharingJoinOnly},
{data_sharing::features::kDataSharingFeature});
InitService();
EXPECT_EQ(service_->GetServiceStatus().collaboration_status,
CollaborationStatus::kAllowedToJoin);
}
TEST_F(CollaborationServiceImplTest, GetServiceStatus_Create) {
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeatures({data_sharing::features::kDataSharingFeature},
{data_sharing::features::kDataSharingJoinOnly});
InitService();
EXPECT_EQ(service_->GetServiceStatus().collaboration_status,
CollaborationStatus::kEnabledCreateAndJoin);
}
TEST_F(CollaborationServiceImplTest, GetServiceStatus_CreateOverridesJoinOnly) {
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeatures({data_sharing::features::kDataSharingJoinOnly,
data_sharing::features::kDataSharingFeature},
{});
InitService();
EXPECT_EQ(service_->GetServiceStatus().collaboration_status,
CollaborationStatus::kEnabledCreateAndJoin);
}
TEST_F(CollaborationServiceImplTest, GetServiceStatus_SigninDisabled) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(
data_sharing::features::kDataSharingFeature);
// Set signin preference to disable signin.
pref_service_.SetBoolean(prefs::kSigninAllowed, false);
InitService();
EXPECT_EQ(service_->GetServiceStatus().signin_status,
SigninStatus::kSigninDisabled);
EXPECT_EQ(service_->GetServiceStatus().collaboration_status,
CollaborationStatus::kEnabledCreateAndJoin);
pref_service_.SetManagedPref(prefs::kSigninAllowed, base::Value(false));
EXPECT_EQ(service_->GetServiceStatus().collaboration_status,
CollaborationStatus::kDisabledForPolicy);
}
TEST_F(CollaborationServiceImplTest, GetServiceStatus_ManagedAccount) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(
data_sharing::features::kDataSharingFeature);
InitService();
EXPECT_EQ(service_->GetServiceStatus().signin_status,
SigninStatus::kNotSignedIn);
EXPECT_EQ(service_->GetServiceStatus().collaboration_status,
CollaborationStatus::kEnabledCreateAndJoin);
// Signin a managed account.
identity_test_env_.MakePrimaryAccountAvailable(kManagedUserEmail,
signin::ConsentLevel::kSignin);
EXPECT_EQ(service_->GetServiceStatus().signin_status,
SigninStatus::kSignedIn);
EXPECT_EQ(service_->GetServiceStatus().collaboration_status,
CollaborationStatus::kDisabledForPolicy);
// Signin a consumer account re-enable the feature.
identity_test_env_.MakePrimaryAccountAvailable(kConsumerUserEmail,
signin::ConsentLevel::kSignin);
EXPECT_EQ(service_->GetServiceStatus().collaboration_status,
CollaborationStatus::kEnabledCreateAndJoin);
}
TEST_F(CollaborationServiceImplTest, StartJoinFlow) {
GURL url("http://www.example.com/");
// Invalid url parsing starts a join flow with empty GroupToken.
std::unique_ptr<MockCollaborationControllerDelegate> mock_delegate_invalid =
std::make_unique<MockCollaborationControllerDelegate>();
MockCollaborationControllerDelegate* delegate_invalid_ptr =
mock_delegate_invalid.get();
EXPECT_CALL(*mock_delegate_invalid, OnFlowFinished());
service_->StartJoinFlow(std::move(mock_delegate_invalid), url);
// Wait for post tasks.
EXPECT_TRUE(base::test::RunUntil(
[&]() { return service_->GetJoinControllersForTesting().size() == 1; }));
const std::map<data_sharing::GroupToken,
std::unique_ptr<CollaborationController>>& join_flows =
service_->GetJoinControllersForTesting();
EXPECT_TRUE(join_flows.find(data_sharing::GroupToken()) != join_flows.end());
// New join flow will be appended with a valid url parsing and will stop all
// conflicting flows.
url = GURL(data_sharing::features::kDataSharingURL.Get() + "?g=" + kGroupId +
"&t=" + kAccessToken);
std::unique_ptr<MockCollaborationControllerDelegate> mock_delegate =
std::make_unique<MockCollaborationControllerDelegate>();
MockCollaborationControllerDelegate* delegate_ptr = mock_delegate.get();
EXPECT_CALL(*mock_delegate, OnFlowFinished());
bool invalid_cancel_called = false;
EXPECT_CALL(*delegate_invalid_ptr, Cancel(_))
.WillOnce([&](CollaborationControllerDelegate::ResultCallback result) {
invalid_cancel_called = true;
std::move(result).Run(
CollaborationControllerDelegate::Outcome::kSuccess);
return true;
});
service_->StartJoinFlow(std::move(mock_delegate), url);
// Wait for post tasks.
EXPECT_TRUE(base::test::RunUntil(
[&]() { return service_->GetJoinControllersForTesting().size() == 1; }));
EXPECT_TRUE(invalid_cancel_called);
bool cancel_called = false;
EXPECT_CALL(*delegate_ptr, Cancel(_))
.WillOnce([&](CollaborationControllerDelegate::ResultCallback result) {
cancel_called = true;
std::move(result).Run(
CollaborationControllerDelegate::Outcome::kSuccess);
return true;
});
// Existing join flow will stop all conflicting flows and will be appended
// similar to a new join flow.
service_->StartJoinFlow(
std::make_unique<MockCollaborationControllerDelegate>(), url);
EXPECT_EQ(service_->GetJoinControllersForTesting().size(), 1u);
EXPECT_TRUE(cancel_called);
}
TEST_F(CollaborationServiceImplTest, SyncStatusChanges) {
// By default the test sync service is signed in with sync and every DataType
// enabled.
EXPECT_EQ(service_->GetServiceStatus().sync_status, SyncStatus::kSyncEnabled);
// Remove user's tab group setting.
test_sync_service_->GetUserSettings()->SetSelectedTypes(
/*sync_everything=*/false,
/*types=*/{});
test_sync_service_->FireStateChanged();
EXPECT_EQ(service_->GetServiceStatus().sync_status,
SyncStatus::kSyncWithoutTabGroup);
if (base::FeatureList::IsEnabled(
syncer::kReplaceSyncPromosWithSignInPromos)) {
// If sync-the-feature is not required, kNotSyncing is never happening.
test_sync_service_->SetSignedOut();
test_sync_service_->FireStateChanged();
EXPECT_EQ(service_->GetServiceStatus().sync_status,
SyncStatus::kSyncWithoutTabGroup);
} else {
// Sign out removes sync consent.
test_sync_service_->SetSignedOut();
test_sync_service_->FireStateChanged();
EXPECT_EQ(service_->GetServiceStatus().sync_status,
SyncStatus::kNotSyncing);
}
}
TEST_F(CollaborationServiceImplTest, SyncStatusChanges_SettingInProgress) {
// By default the test sync service is signed in with sync and every DataType
// enabled.
EXPECT_EQ(service_->GetServiceStatus().sync_status, SyncStatus::kSyncEnabled);
// Setup in progress does not change sync status.
test_sync_service_->SetSetupInProgress();
test_sync_service_->SetSignedOut();
test_sync_service_->FireStateChanged();
EXPECT_EQ(service_->GetServiceStatus().sync_status, SyncStatus::kSyncEnabled);
}
TEST_F(CollaborationServiceImplTest, ConsumerSigninChanges) {
EXPECT_EQ(service_->GetServiceStatus().signin_status,
SigninStatus::kNotSignedIn);
identity_test_env_.SetPrimaryAccount(kConsumerUserEmail,
signin::ConsentLevel::kSignin);
EXPECT_EQ(service_->GetServiceStatus().signin_status,
SigninStatus::kSignedInPaused);
identity_test_env_.SetRefreshTokenForPrimaryAccount();
EXPECT_EQ(service_->GetServiceStatus().signin_status,
SigninStatus::kSignedIn);
}
TEST_F(CollaborationServiceImplTest, DeleteGroup) {
data_sharing::GroupId group_id = data_sharing::GroupId(kGroupId);
EXPECT_CALL(mock_tab_group_sync_service_,
OnCollaborationRemoved(syncer::CollaborationId(kGroupId)));
EXPECT_CALL(mock_data_sharing_service_, DeleteGroup(group_id, _))
.WillOnce(Invoke(
[](const data_sharing::GroupId&,
base::OnceCallback<void(
data_sharing::DataSharingService::PeopleGroupActionOutcome)>
callback) {
std::move(callback).Run(data_sharing::DataSharingService::
PeopleGroupActionOutcome::kSuccess);
}));
base::RunLoop run_loop;
service_->DeleteGroup(group_id,
base::BindOnce(
[](base::RunLoop* run_loop, bool success) {
ASSERT_TRUE(success);
run_loop->Quit();
},
&run_loop));
run_loop.Run();
}
TEST_F(CollaborationServiceImplTest, LeaveGroup) {
data_sharing::GroupId group_id = data_sharing::GroupId(kGroupId);
EXPECT_CALL(mock_tab_group_sync_service_,
OnCollaborationRemoved(syncer::CollaborationId(kGroupId)));
EXPECT_CALL(mock_data_sharing_service_, LeaveGroup(group_id, _))
.WillOnce(Invoke(
[](const data_sharing::GroupId&,
base::OnceCallback<void(
data_sharing::DataSharingService::PeopleGroupActionOutcome)>
callback) {
std::move(callback).Run(data_sharing::DataSharingService::
PeopleGroupActionOutcome::kSuccess);
}));
base::RunLoop run_loop;
service_->LeaveGroup(group_id, base::BindOnce(
[](base::RunLoop* run_loop, bool success) {
ASSERT_TRUE(success);
run_loop->Quit();
},
&run_loop));
run_loop.Run();
}
TEST_F(CollaborationServiceImplTest, CancelAllFlows) {
GURL url = GURL(data_sharing::features::kDataSharingURL.Get() +
"?g=" + kGroupId + "&t=" + kAccessToken);
// New join flow will be appended with a valid url parsing and will stop all
// conflicting flows.
std::unique_ptr<MockCollaborationControllerDelegate> mock_delegate =
std::make_unique<MockCollaborationControllerDelegate>();
MockCollaborationControllerDelegate* delegate_ptr = mock_delegate.get();
EXPECT_CALL(*mock_delegate, OnFlowFinished());
service_->StartJoinFlow(std::move(mock_delegate), url);
// Wait for post tasks.
EXPECT_TRUE(base::test::RunUntil(
[&]() { return service_->GetJoinControllersForTesting().size() == 1; }));
bool cancel_called = false;
EXPECT_CALL(*delegate_ptr, Cancel(_))
.WillOnce([&](CollaborationControllerDelegate::ResultCallback result) {
cancel_called = true;
std::move(result).Run(
CollaborationControllerDelegate::Outcome::kSuccess);
return true;
});
base::RunLoop run_loop;
service_->CancelAllFlows(base::BindOnce(
[](base::RunLoop* run_loop) { run_loop->Quit(); }, &run_loop));
EXPECT_TRUE(cancel_called);
// Wait for post tasks.
EXPECT_TRUE(base::test::RunUntil(
[&]() { return service_->GetJoinControllersForTesting().size() == 0; }));
}
} // namespace collaboration