blob: 5253c20214c730d72fc28279c705bc4f27a8b908 [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.
#import "ios/chrome/browser/authentication/ui_bundled/account_menu/account_menu_coordinator.h"
#import <MaterialComponents/MaterialSnackbar.h>
#import "base/memory/raw_ptr.h"
#import "base/test/scoped_feature_list.h"
#import "components/sync/service/sync_service_utils.h"
#import "components/trusted_vault/trusted_vault_server_constants.h"
#import "ios/chrome/browser/authentication/ui_bundled/account_menu/account_menu_mediator.h"
#import "ios/chrome/browser/authentication/ui_bundled/account_menu/account_menu_mediator_delegate.h"
#import "ios/chrome/browser/authentication/ui_bundled/account_menu/account_menu_view_controller.h"
#import "ios/chrome/browser/authentication/ui_bundled/signin/add_account_signin/add_account_signin_coordinator.h"
#import "ios/chrome/browser/authentication/ui_bundled/signin/signin_constants.h"
#import "ios/chrome/browser/authentication/ui_bundled/signin/signin_coordinator+protected.h"
#import "ios/chrome/browser/authentication/ui_bundled/signout_action_sheet/signout_action_sheet_coordinator.h"
#import "ios/chrome/browser/settings/ui_bundled/sync/sync_encryption_passphrase_table_view_controller.h"
#import "ios/chrome/browser/shared/coordinator/scene/scene_state.h"
#import "ios/chrome/browser/shared/coordinator/scene/test/stub_browser_provider_interface.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/browser/test/test_browser.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "ios/chrome/browser/shared/public/commands/application_commands.h"
#import "ios/chrome/browser/shared/public/commands/browser_commands.h"
#import "ios/chrome/browser/shared/public/commands/browser_coordinator_commands.h"
#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
#import "ios/chrome/browser/shared/public/commands/quick_delete_commands.h"
#import "ios/chrome/browser/shared/public/commands/settings_commands.h"
#import "ios/chrome/browser/shared/public/commands/show_signin_command.h"
#import "ios/chrome/browser/shared/public/commands/snackbar_commands.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/ui/util/identity_snackbar/identity_snackbar_message.h"
#import "ios/chrome/browser/signin/model/authentication_service.h"
#import "ios/chrome/browser/signin/model/authentication_service_factory.h"
#import "ios/chrome/browser/signin/model/fake_authentication_service_delegate.h"
#import "ios/chrome/browser/signin/model/fake_system_identity.h"
#import "ios/chrome/browser/signin/model/fake_system_identity_manager.h"
#import "ios/chrome/browser/signin/model/system_identity_manager.h"
#import "ios/chrome/test/ios_chrome_scoped_testing_local_state.h"
#import "ios/web/public/test/web_task_environment.h"
#import "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#import "third_party/ocmock/gtest_support.h"
namespace {
const FakeSystemIdentity* kPrimaryIdentity = [FakeSystemIdentity fakeIdentity1];
const FakeSystemIdentity* kSecondaryIdentity =
[FakeSystemIdentity fakeIdentity2];
const FakeSystemIdentity* kManagedIdentity =
[FakeSystemIdentity fakeManagedIdentity];
} // namespace
@interface AccountMenuCoordinator (Testing) <
AccountMenuMediatorDelegate,
SignoutActionSheetCoordinatorDelegate,
UIAdaptivePresentationControllerDelegate,
UINavigationControllerDelegate>
@property(nonatomic, weak) AccountMenuViewController* viewController;
@property(nonatomic, weak) AccountMenuMediator* mediator;
@end
// The test param determines whether `kSeparateProfilesForManagedAccounts` is
// enabled.
class AccountMenuCoordinatorTest : public PlatformTest,
public testing::WithParamInterface<bool> {
public:
AccountMenuCoordinatorTest() {
feature_list_.InitWithFeatureState(kSeparateProfilesForManagedAccounts,
GetParam());
}
void SetUp() override {
PlatformTest::SetUp();
scene_state_ = [[SceneState alloc] initWithAppState:nil];
TestProfileIOS::Builder builder;
builder.AddTestingFactory(
AuthenticationServiceFactory::GetInstance(),
AuthenticationServiceFactory::GetFactoryWithDelegate(
std::make_unique<FakeAuthenticationServiceDelegate>()));
profile_ = std::move(builder).Build();
browser_ = std::make_unique<TestBrowser>(profile_.get(), scene_state_);
stub_browser_interface_provider_ =
[[StubBrowserProviderInterface alloc] init];
stub_browser_interface_provider_.currentBrowserProvider.browser =
browser_.get();
scene_state_mock_ = OCMPartialMock(scene_state_);
OCMStub([scene_state_mock_ browserProviderInterface])
.andReturn(stub_browser_interface_provider_);
mock_application_commands_handler_ =
OCMStrictProtocolMock(@protocol(ApplicationCommands));
mock_snackbar_commands_handler_ =
OCMStrictProtocolMock(@protocol(SnackbarCommands));
mock_settings_commands_handler_ =
OCMStrictProtocolMock(@protocol(SettingsCommands));
mock_browser_commands_handler_ =
OCMStrictProtocolMock(@protocol(BrowserCommands));
mock_browser_coordinator_commands_handler_ =
OCMStrictProtocolMock(@protocol(BrowserCoordinatorCommands));
CommandDispatcher* dispatcher = browser_->GetCommandDispatcher();
[dispatcher startDispatchingToTarget:mock_application_commands_handler_
forProtocol:@protocol(ApplicationCommands)];
[dispatcher startDispatchingToTarget:mock_snackbar_commands_handler_
forProtocol:@protocol(SnackbarCommands)];
[dispatcher startDispatchingToTarget:mock_settings_commands_handler_
forProtocol:@protocol(SettingsCommands)];
[dispatcher startDispatchingToTarget:mock_browser_commands_handler_
forProtocol:@protocol(BrowserCommands)];
[dispatcher
startDispatchingToTarget:mock_browser_coordinator_commands_handler_
forProtocol:@protocol(BrowserCoordinatorCommands)];
fake_system_identity_manager_ =
FakeSystemIdentityManager::FromSystemIdentityManager(
GetApplicationContext()->GetSystemIdentityManager());
authentication_service_ =
AuthenticationServiceFactory::GetForProfile(profile_.get());
SigninWithPrimaryIdentity();
AddSecondaryIdentity();
coordinator_ = [[AccountMenuCoordinator alloc]
initWithBaseViewController:nil
browser:browser_.get()];
coordinator_.signinCompletion =
^(SigninCoordinatorResult, id<SystemIdentity>) {
signinCompletion();
};
[coordinator_ start];
// Replacing the view controller and mediator by mock.
view_ = coordinator_.viewController.view;
view_controller_ = OCMStrictClassMock([AccountMenuViewController class]);
OCMStub([view_controller_ view]).andReturn(view_);
coordinator_.viewController = view_controller_;
[coordinator_.mediator disconnect];
mediator_ = OCMStrictClassMock([AccountMenuMediator class]);
coordinator_.mediator = mediator_;
}
void TearDown() override {
VerifyMock();
ASSERT_TRUE(completionCalled_);
PlatformTest::TearDown();
}
virtual const FakeSystemIdentity* primary_identity() = 0;
protected:
void VerifyMock() {
EXPECT_OCMOCK_VERIFY((id)mediator_);
EXPECT_OCMOCK_VERIFY((id)view_controller_);
EXPECT_OCMOCK_VERIFY((id)mock_application_commands_handler_);
EXPECT_OCMOCK_VERIFY((id)mock_browser_commands_handler_);
EXPECT_OCMOCK_VERIFY((id)mock_browser_coordinator_commands_handler_);
EXPECT_OCMOCK_VERIFY((id)mock_settings_commands_handler_);
EXPECT_OCMOCK_VERIFY((id)mock_snackbar_commands_handler_);
}
// Asserts that the coordinator is still open and request it to be closed.
void assertOpenAndInterrupt() {
// `stop` should not be called directly. Instead, the SigninCoordinator is
// closed inderectly through `runCompletion`. We ensure to close it by
// simulating that the mediator request to dismiss the coordinator.
OCMStub(mediator_.signinCompletionIdentity).andReturn(nil);
OCMStub(mediator_.signinCoordinatorResult)
.andReturn(
SigninCoordinatorResult::SigninCoordinatorResultCanceledByUser);
OCMExpect(view_controller_.dataSource = nil);
OCMExpect(mediator_.consumer = nil);
OCMExpect(view_controller_.mutator = nil);
[coordinator_ interruptWithAction:SynchronousStopAction() completion:nil];
}
base::test::ScopedFeatureList feature_list_;
AccountMenuCoordinator<UIAdaptivePresentationControllerDelegate>*
coordinator_;
id<ApplicationCommands> mock_application_commands_handler_;
id<SnackbarCommands> mock_snackbar_commands_handler_;
id<SettingsCommands> mock_settings_commands_handler_;
id<BrowserCommands> mock_browser_commands_handler_;
SceneState* scene_state_;
// Partial mock for stubbing scene_state_'s methods
id scene_state_mock_;
StubBrowserProviderInterface* stub_browser_interface_provider_;
id<BrowserCoordinatorCommands> mock_browser_coordinator_commands_handler_;
AccountMenuViewController* view_controller_;
AccountMenuMediator* mediator_;
raw_ptr<AuthenticationService> authentication_service_;
raw_ptr<FakeSystemIdentityManager> fake_system_identity_manager_;
// The view owned by the view controller.
UIView* view_;
private:
// Stops the coordinator. This is the coordinator’s `signinCompletion`, it is
// called through `interruptWithAction:completion:` and should not be called
// directly.
void signinCompletion() {
completionCalled_ = true;
OCMExpect([mediator_ disconnect]);
OCMExpect(mediator_.delegate = nil);
[coordinator_ stop];
coordinator_ = nil;
}
// Signs in primary_identity() as primary identity.
void SigninWithPrimaryIdentity() {
fake_system_identity_manager_->AddIdentity(primary_identity());
authentication_service_->SignIn(
primary_identity(), signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
}
// Add kSecondaryIdentity as a secondary identity.
void AddSecondaryIdentity() {
fake_system_identity_manager_->AddIdentity(kSecondaryIdentity);
}
web::WebTaskEnvironment task_environment_;
IOSChromeScopedTestingLocalState scoped_testing_local_state_;
std::unique_ptr<TestProfileIOS> profile_;
std::unique_ptr<TestBrowser> browser_;
bool completionCalled_ = false;
};
class AccountMenuCoordinatorNonManagedTest : public AccountMenuCoordinatorTest {
public:
const FakeSystemIdentity* primary_identity() override {
return kPrimaryIdentity;
}
};
class AccountMenuCoordinatorManagedTest : public AccountMenuCoordinatorTest {
public:
const FakeSystemIdentity* primary_identity() override {
return kManagedIdentity;
}
};
#pragma mark - AccountMenuMediatorDelegate
// Tests that `didTapManageYourGoogleAccount` requests the view controller to
// present a view.
TEST_P(AccountMenuCoordinatorNonManagedTest, testManageYourGoogleAccount) {
OCMExpect([view_controller_ presentViewController:[OCMArg any]
animated:YES
completion:nil]);
[coordinator_ didTapManageYourGoogleAccount];
assertOpenAndInterrupt();
}
// Tests that `didTapManageAccounts` has no impact on the view controller and
// mediator.
TEST_P(AccountMenuCoordinatorNonManagedTest, testEditAccountList) {
[coordinator_ didTapManageAccounts];
assertOpenAndInterrupt();
}
// Tests that `signOutFromTargetRect` requests the delegate to be stopped and
// shows a snackbar and calls its completion.
TEST_P(AccountMenuCoordinatorNonManagedTest, testSignOut) {
base::RunLoop run_loop;
base::RepeatingClosure closure = run_loop.QuitClosure();
CGRect rect = CGRect();
OCMExpect([mock_snackbar_commands_handler_
showSnackbarMessageOverBrowserToolbar:[OCMArg isNotNil]]);
[coordinator_ signOutFromTargetRect:rect
forSwitch:NO
completion:^(BOOL success) {
EXPECT_TRUE(success);
assertOpenAndInterrupt();
closure.Run();
}];
run_loop.Run();
EXPECT_EQ(authentication_service_->GetPrimaryIdentity(
signin::ConsentLevel::kSignin),
nil);
}
// Tests that `mediatorWantsToBeDismissed` requests to the delegate to stop the
// coordinator.
TEST_P(AccountMenuCoordinatorNonManagedTest, testMediatorWantsToBeDismissed) {
assertOpenAndInterrupt();
}
// Tests that `triggerSignoutWithTargetRect` calls its
// callback.
TEST_P(AccountMenuCoordinatorNonManagedTest, testTriggerSignout) {
OCMExpect([mock_snackbar_commands_handler_
showSnackbarMessageOverBrowserToolbar:[OCMArg any]]);
base::RunLoop run_loop;
base::RepeatingClosure closure = run_loop.QuitClosure();
CGRect rect = CGRect();
[coordinator_ signOutFromTargetRect:rect
forSwitch:NO
completion:^(BOOL success) {
EXPECT_TRUE(success);
closure.Run();
}];
run_loop.Run();
assertOpenAndInterrupt();
}
// Tests that `triggerSigninWithSystemIdentity` call its completion.
TEST_P(AccountMenuCoordinatorNonManagedTest, testSignin) {
base::RunLoop run_loop;
base::RepeatingClosure closure = run_loop.QuitClosure();
AuthenticationFlow* authentication_flow = [coordinator_
triggerSigninWithSystemIdentity:kSecondaryIdentity
completion:^(SigninCoordinatorResult result) {
EXPECT_EQ(result,
SigninCoordinatorResult::
SigninCoordinatorResultSuccess);
assertOpenAndInterrupt();
closure.Run();
}];
EXPECT_TRUE(authentication_flow);
run_loop.Run();
}
// Tests that `triggerAccountSwitchSnackbarWithIdentity` shows a snackbar.
TEST_P(AccountMenuCoordinatorNonManagedTest, testSnackbar) {
OCMExpect([mock_snackbar_commands_handler_
showSnackbarMessageOverBrowserToolbar:[OCMArg checkWithBlock:^BOOL(
IdentitySnackbarMessage*
msg) {
EXPECT_FALSE(msg.managed);
return YES;
}]]);
[coordinator_ triggerAccountSwitchSnackbarWithIdentity:kPrimaryIdentity];
assertOpenAndInterrupt();
}
// Tests that `triggerAccountSwitchSnackbarWithIdentity` shows a snackbar with
// `managed` set to true.
TEST_P(AccountMenuCoordinatorManagedTest, testSnackbarManaged) {
OCMExpect([mock_snackbar_commands_handler_
showSnackbarMessageOverBrowserToolbar:[OCMArg checkWithBlock:^BOOL(
IdentitySnackbarMessage*
msg) {
EXPECT_TRUE(msg.managed);
return YES;
}]]);
[coordinator_ triggerAccountSwitchSnackbarWithIdentity:kManagedIdentity];
assertOpenAndInterrupt();
}
#pragma mark - SyncErrorSettingsCommandHandler
// Tests that `openPassphraseDialogWithModalPresentation` has no impact on the
// view controller and mediator. Tests also that the
// `SyncEncryptionPassphraseTableViewController` is allocated, and the view is
// correctly closed when the coordinator is stopped.
TEST_P(AccountMenuCoordinatorNonManagedTest, testPassphrase) {
SyncEncryptionPassphraseTableViewController* passphraseViewController =
[SyncEncryptionPassphraseTableViewController alloc];
id classMock =
OCMClassMock([SyncEncryptionPassphraseTableViewController class]);
OCMStub([classMock alloc]).andReturn(passphraseViewController);
[coordinator_ openPassphraseDialogWithModalPresentation:YES];
assertOpenAndInterrupt();
}
// Tests that `openTrustedVaultReauthForFetchKeys` calls
// `showTrustedVaultReauthForFetchKeysFromViewController`.
TEST_P(AccountMenuCoordinatorNonManagedTest, testFetchKeys) {
[coordinator_ openTrustedVaultReauthForFetchKeys];
assertOpenAndInterrupt();
}
// Tests that `openTrustedVaultReauthForDegradedRecoverability` calls
// `showTrustedVaultReauthForDegradedRecoverabilityFromViewController`.
TEST_P(AccountMenuCoordinatorNonManagedTest, testDegradedRecoverability) {
[coordinator_ openTrustedVaultReauthForDegradedRecoverability];
assertOpenAndInterrupt();
}
// Tests that `openMDMErrodDialogWithSystemIdentity` has no effects on the
// mediator and view controller.
TEST_P(AccountMenuCoordinatorNonManagedTest, testMDMError) {
[coordinator_ openMDMErrodDialogWithSystemIdentity:kPrimaryIdentity];
assertOpenAndInterrupt();
}
INSTANTIATE_TEST_SUITE_P(,
AccountMenuCoordinatorNonManagedTest,
testing::Bool(),
[](const testing::TestParamInfo<bool>& info) {
return info.param ? "WithSeparateProfiles"
: "WithoutSeparateProfiles";
});
INSTANTIATE_TEST_SUITE_P(,
AccountMenuCoordinatorManagedTest,
testing::Bool(),
[](const testing::TestParamInfo<bool>& info) {
return info.param ? "WithSeparateProfiles"
: "WithoutSeparateProfiles";
});