blob: 5faf6a505cb450730ab9da588ce3888ed016a47e [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_controller.h"
#include "base/run_loop.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/gmock_move_support.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "components/collaboration/public/collaboration_controller_delegate.h"
#include "components/collaboration/test_support/mock_collaboration_controller_delegate.h"
#include "components/collaboration/test_support/mock_collaboration_service.h"
#include "components/data_sharing/test_support/mock_data_sharing_service.h"
#include "components/saved_tab_groups/public/saved_tab_group.h"
#include "components/saved_tab_groups/test_support/mock_tab_group_sync_service.h"
#include "components/saved_tab_groups/test_support/saved_tab_group_test_utils.h"
#include "components/sync/test/mock_sync_service.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace collaboration {
namespace {
const char kGroupId[] = "/?-group_id";
const char kAccessToken[] = "/?-access_token";
using StateId = CollaborationController::StateId;
using Outcome = CollaborationControllerDelegate::Outcome;
using ErrorInfo = CollaborationControllerDelegate::ErrorInfo;
using Flow = CollaborationController::Flow;
using base::OnceClosure;
using base::RunLoop;
using base::test::IsNotNullCallback;
using base::test::RunOnceCallback;
using data_sharing::GroupData;
using data_sharing::GroupToken;
using tab_groups::SavedTabGroup;
using ::testing::_;
using ::testing::Return;
using ::testing::SaveArg;
} // namespace
class CollaborationControllerTest : public testing::Test {
public:
CollaborationControllerTest() = default;
~CollaborationControllerTest() override = default;
void SetUp() override {
collaboration_service_ = std::make_unique<MockCollaborationService>();
data_sharing_service_ =
std::make_unique<data_sharing::MockDataSharingService>();
tab_group_sync_service_ =
std::make_unique<tab_groups::MockTabGroupSyncService>();
sync_service_ = std::make_unique<syncer::MockSyncService>();
}
void TearDown() override {}
void InitializeController(OnceClosure run_on_flow_exit, const Flow& flow) {
std::unique_ptr<MockCollaborationControllerDelegate> delegate =
std::make_unique<MockCollaborationControllerDelegate>();
delegate_ = delegate.get();
EXPECT_CALL(*delegate_, PrepareFlowUI(IsNotNullCallback()))
.WillOnce(MoveArg<0>(&prepare_ui_callback_));
controller_ = std::make_unique<CollaborationController>(
flow, collaboration_service_.get(), data_sharing_service_.get(),
tab_group_sync_service_.get(), sync_service_.get(), std::move(delegate),
base::BindOnce(&CollaborationControllerTest::FinishFlow,
weak_ptr_factory_.GetWeakPtr(),
std::move(run_on_flow_exit)));
}
void InitializeJoinController(OnceClosure run_on_flow_exit) {
InitializeController(
std::move(run_on_flow_exit),
Flow(Flow::Type::kJoin,
GroupToken(data_sharing::GroupId(kGroupId), kAccessToken)));
}
void FinishFlow(OnceClosure run_on_flow_exit) {
controller_.reset();
std::move(run_on_flow_exit).Run();
}
protected:
base::test::SingleThreadTaskEnvironment task_environment_;
base::OnceCallback<void(Outcome)> prepare_ui_callback_;
MockCollaborationControllerDelegate* delegate_;
std::unique_ptr<MockCollaborationService> collaboration_service_;
std::unique_ptr<data_sharing::MockDataSharingService> data_sharing_service_;
std::unique_ptr<tab_groups::MockTabGroupSyncService> tab_group_sync_service_;
std::unique_ptr<syncer::MockSyncService> sync_service_;
std::unique_ptr<CollaborationController> controller_;
base::WeakPtrFactory<CollaborationControllerTest> weak_ptr_factory_{this};
};
TEST_F(CollaborationControllerTest, FullFlowAllStates) {
RunLoop run_loop;
// Start Join flow.
InitializeJoinController(run_loop.QuitClosure());
// 1. Pending state.
EXPECT_EQ(controller_->GetStateForTesting(), StateId::kPending);
// Simulate user is signed in, but not syncing.
ServiceStatus status;
status.signin_status = SigninStatus::kSignedIn;
status.sync_status = SyncStatus::kNotSyncing;
ASSERT_FALSE(status.IsAuthenticationValid());
EXPECT_CALL(*collaboration_service_, GetServiceStatus())
.WillOnce(Return(status));
// The user should be shown authentication screens.
base::OnceCallback<void(Outcome)> authentication_ui_calback;
EXPECT_CALL(*delegate_, ShowAuthenticationUi(IsNotNullCallback()))
.WillOnce(MoveArg<0>(&authentication_ui_calback));
// 2. Pending -> Authenticating state.
std::move(prepare_ui_callback_).Run(Outcome::kSuccess);
EXPECT_EQ(controller_->GetStateForTesting(), StateId::kAuthenticating);
// Simulate user successfully completes authentication.
status.sync_status = SyncStatus::kSyncEnabled;
EXPECT_CALL(*collaboration_service_, GetServiceStatus())
.WillOnce(Return(status));
ASSERT_TRUE(status.IsAuthenticationValid());
// Simulate that the user is not already in the tab group.
data_sharing::GroupId group_id(kGroupId);
const GroupToken& token =
GroupToken(data_sharing::GroupId(kGroupId), kAccessToken);
base::OnceCallback<void(
const data_sharing::DataSharingService::GroupDataOrFailureOutcome&)>
group_data_callback;
EXPECT_CALL(*collaboration_service_, GetCurrentUserRoleForGroup(group_id))
.WillOnce(Return(data_sharing::MemberRole::kUnknown));
EXPECT_CALL(*data_sharing_service_, ReadNewGroup(token, IsNotNullCallback()))
.WillOnce(MoveArg<1>(&group_data_callback));
// 3. Authenticating -> CheckingFlowRequirements state.
std::move(authentication_ui_calback).Run(Outcome::kSuccess);
EXPECT_EQ(controller_->GetStateForTesting(),
StateId::kCheckingFlowRequirements);
// The user should be shown invitation screen for joining a collaboration
// group.
GroupData group_data = GroupData(group_id, /*display_name=*/"",
/*members=*/{}, kAccessToken);
base::OnceCallback<void(Outcome)> join_ui_callback;
EXPECT_CALL(*delegate_, ShowJoinDialog(_, _, IsNotNullCallback()))
.WillOnce(MoveArg<2>(&join_ui_callback));
// 4. CheckingFlowRequirements -> AddingUserToGroup state.
std::move(group_data_callback).Run(group_data);
EXPECT_EQ(controller_->GetStateForTesting(), StateId::kAddingUserToGroup);
// Simulate the user accepts the join invitation. Wait for tab group to be
// added in sync.
SavedTabGroup tab_group(std::u16string(u"title"),
tab_groups::TabGroupColorId::kGrey, {});
tab_group.SetCollaborationId(
tab_groups::CollaborationId(std::string(kGroupId)));
std::vector<SavedTabGroup> all_tab_groups;
EXPECT_CALL(*tab_group_sync_service_, GetAllGroups())
.WillOnce(Return(all_tab_groups));
tab_groups::TabGroupSyncService::Observer* sync_observer;
data_sharing::DataSharingService::Observer* data_sharing_observer;
EXPECT_CALL(*sync_service_, TriggerRefresh(_));
EXPECT_CALL(*tab_group_sync_service_, AddObserver(_))
.WillOnce(SaveArg<0>(&sync_observer));
EXPECT_CALL(*data_sharing_service_, AddObserver(_))
.WillOnce(SaveArg<0>(&data_sharing_observer));
// 5. AddingUserToGroup -> WaitingForSyncAndDataSharingGroup state.
std::move(join_ui_callback).Run(Outcome::kSuccess);
EXPECT_EQ(controller_->GetStateForTesting(),
StateId::kWaitingForSyncAndDataSharingGroup);
// Added tab group in sync but not in data sharing should not transition.
EXPECT_CALL(*collaboration_service_, GetCurrentUserRoleForGroup(group_id))
.WillOnce(Return(data_sharing::MemberRole::kUnknown));
sync_observer->OnTabGroupAdded(tab_group, tab_groups::TriggerSource::REMOTE);
EXPECT_EQ(controller_->GetStateForTesting(),
StateId::kWaitingForSyncAndDataSharingGroup);
// Simulate added in both tab group and data_sharing group.
base::OnceCallback<void(Outcome)> promote_ui_callback;
EXPECT_CALL(*collaboration_service_, GetCurrentUserRoleForGroup(group_id))
.WillOnce(Return(data_sharing::MemberRole::kMember));
EXPECT_CALL(*delegate_, PromoteTabGroup(data_sharing::GroupId(kGroupId),
IsNotNullCallback()))
.WillOnce(MoveArg<1>(&promote_ui_callback));
EXPECT_CALL(*tab_group_sync_service_, RemoveObserver(sync_observer));
EXPECT_CALL(*data_sharing_service_, RemoveObserver(data_sharing_observer));
// 5. WaitingForSyncAndDataSharingGroup -> OpeningLocalTabGroup state.
// TODO(crbug.com/373403973): Remove data sharing observer when sync service
// starts observing data sharing.
sync_observer->OnTabGroupAdded(tab_group, tab_groups::TriggerSource::REMOTE);
EXPECT_EQ(controller_->GetStateForTesting(), StateId::kOpeningLocalTabGroup);
// Upon successfully promoting the tab group, the flow ends and exit.
std::move(promote_ui_callback).Run(Outcome::kSuccess);
run_loop.Run();
}
TEST_F(CollaborationControllerTest, UrlHandlingError) {
RunLoop run_loop;
// Start Join flow.
InitializeController(run_loop.QuitClosure(),
Flow(Flow::Type::kJoin, GroupToken()));
EXPECT_EQ(controller_->GetStateForTesting(), StateId::kPending);
// Simulate an error parsing join URL.
base::OnceCallback<void(Outcome)> error_ui_callback;
EXPECT_CALL(*delegate_, ShowError(ErrorInfo(ErrorInfo::Type::kGenericError),
IsNotNullCallback()))
.WillOnce(MoveArg<1>(&error_ui_callback));
std::move(prepare_ui_callback_).Run(Outcome::kSuccess);
EXPECT_EQ(controller_->GetStateForTesting(), StateId::kError);
// Simulate exiting the flow.
std::move(error_ui_callback).Run(Outcome::kSuccess);
run_loop.Run();
}
TEST_F(CollaborationControllerTest, DelegateOutcomeError) {
RunLoop run_loop;
// Start Join flow.
InitializeJoinController(run_loop.QuitClosure());
EXPECT_EQ(controller_->GetStateForTesting(), StateId::kPending);
// Simulate a failure on the UI side.
base::OnceCallback<void(Outcome)> error_ui_callback;
EXPECT_CALL(*delegate_, ShowError(ErrorInfo(ErrorInfo::Type::kGenericError),
IsNotNullCallback()))
.WillOnce(MoveArg<1>(&error_ui_callback));
std::move(prepare_ui_callback_).Run(Outcome::kFailure);
EXPECT_EQ(controller_->GetStateForTesting(), StateId::kError);
// Simulate exiting the flow.
std::move(error_ui_callback).Run(Outcome::kSuccess);
run_loop.Run();
}
TEST_F(CollaborationControllerTest, AuthenticationError) {
RunLoop run_loop;
// Start Join flow with authenticating screens.
base::OnceCallback<void(Outcome)> authentication_ui_calback;
InitializeJoinController(run_loop.QuitClosure());
EXPECT_CALL(*delegate_, ShowAuthenticationUi(IsNotNullCallback()))
.WillOnce(MoveArg<0>(&authentication_ui_calback));
controller_->SetStateForTesting(StateId::kAuthenticating);
// Simulate Authentication finishing successfully on the UI, but getting
// invalid authentication status in the service.
base::OnceCallback<void(Outcome)> error_ui_callback;
EXPECT_CALL(*delegate_, ShowError(ErrorInfo(ErrorInfo::Type::kGenericError),
IsNotNullCallback()))
.WillOnce(MoveArg<1>(&error_ui_callback));
ServiceStatus status;
status.signin_status = SigninStatus::kNotSignedIn;
status.sync_status = SyncStatus::kSyncEnabled;
ASSERT_FALSE(status.IsAuthenticationValid());
EXPECT_CALL(*collaboration_service_, GetServiceStatus())
.WillOnce(Return(status));
std::move(authentication_ui_calback).Run(Outcome::kSuccess);
EXPECT_EQ(controller_->GetStateForTesting(), StateId::kError);
// Simulate exiting the flow.
std::move(error_ui_callback).Run(Outcome::kSuccess);
run_loop.Run();
}
TEST_F(CollaborationControllerTest, CheckingFlowRequirementsShareFlow) {
// Start Share flow.
tab_groups::LocalTabGroupID local_id =
tab_groups::test::GenerateRandomTabGroupID();
InitializeController(base::DoNothing(),
Flow(Flow::Type::kShareOrManage, local_id));
EXPECT_EQ(controller_->GetStateForTesting(), StateId::kPending);
// Simulate that the tab group exist locally, but is not shared.
tab_groups::EitherGroupID either_id = local_id;
SavedTabGroup tab_group(std::u16string(u"title"),
tab_groups::TabGroupColorId::kGrey, {});
tab_group.SetLocalGroupId(local_id);
EXPECT_CALL(*tab_group_sync_service_, GetGroup(either_id))
.WillOnce(Return(tab_group));
EXPECT_CALL(*delegate_, ShowShareDialog(IsNotNullCallback()));
controller_->SetStateForTesting(StateId::kCheckingFlowRequirements);
EXPECT_EQ(controller_->GetStateForTesting(), StateId::kShowingShareScreen);
// Simulate that the tab group exist locally and is a shared tab group.
tab_group.SetCollaborationId(
tab_groups::CollaborationId(std::string(kGroupId)));
EXPECT_CALL(*tab_group_sync_service_, GetGroup(either_id))
.WillOnce(Return(tab_group));
EXPECT_CALL(*delegate_, ShowManageDialog(IsNotNullCallback()));
controller_->SetStateForTesting(StateId::kCheckingFlowRequirements);
EXPECT_EQ(controller_->GetStateForTesting(), StateId::kShowingManageScreen);
}
} // namespace collaboration