// Copyright 2017 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/signin_promo_view_mediator.h"

#import "base/functional/callback_helpers.h"
#import "base/run_loop.h"
#import "base/strings/sys_string_conversions.h"
#import "base/test/ios/wait_util.h"
#import "build/branding_buildflags.h"
#import "components/pref_registry/pref_registry_syncable.h"
#import "components/prefs/pref_service.h"
#import "components/signin/public/base/signin_metrics.h"
#import "components/signin/public/base/signin_pref_names.h"
#import "components/signin/public/identity_manager/objc/identity_manager_observer_bridge.h"
#import "components/sync/test/mock_sync_service.h"
#import "components/sync_preferences/pref_service_mock_factory.h"
#import "components/sync_preferences/pref_service_syncable.h"
#import "components/test/ios/test_utils.h"
#import "ios/chrome/browser/authentication/ui_bundled/account_settings_presenter.h"
#import "ios/chrome/browser/authentication/ui_bundled/authentication_test_util.h"
#import "ios/chrome/browser/authentication/ui_bundled/cells/signin_promo_view.h"
#import "ios/chrome/browser/authentication/ui_bundled/cells/signin_promo_view_configurator.h"
#import "ios/chrome/browser/authentication/ui_bundled/cells/signin_promo_view_constants.h"
#import "ios/chrome/browser/authentication/ui_bundled/cells/signin_promo_view_consumer.h"
#import "ios/chrome/browser/authentication/ui_bundled/signin/signin_constants.h"
#import "ios/chrome/browser/authentication/ui_bundled/signin_presenter.h"
#import "ios/chrome/browser/policy/model/policy_util.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/prefs/browser_prefs.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/model/profile/features.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/ui/image/image_names.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/chrome_account_manager_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/identity_manager_factory.h"
#import "ios/chrome/browser/sync/model/mock_sync_service_utils.h"
#import "ios/chrome/browser/sync/model/sync_service_factory.h"
#import "ios/chrome/grit/ios_branded_strings.h"
#import "ios/chrome/grit/ios_strings.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"
#import "third_party/ocmock/ocmock_extensions.h"
#import "ui/base/l10n/l10n_util.h"

using base::SysNSStringToUTF16;
using base::test::ios::kWaitForUIElementTimeout;
using base::test::ios::WaitUntilConditionOrTimeout;
using l10n_util::GetNSString;
using l10n_util::GetNSStringF;
using sync_preferences::PrefServiceMockFactory;
using sync_preferences::PrefServiceSyncable;
using user_prefs::PrefRegistrySyncable;
using web::WebTaskEnvironment;

namespace {

class SigninPromoViewMediatorTest : public PlatformTest {
 protected:
  void SetUp() override {
    identity_ = [FakeSystemIdentity fakeIdentity1];

    TestProfileIOS::Builder builder;
    builder.AddTestingFactory(SyncServiceFactory::GetInstance(),
                              base::BindRepeating(&CreateMockSyncService));
    builder.AddTestingFactory(
        AuthenticationServiceFactory::GetInstance(),
        AuthenticationServiceFactory::GetFactoryWithDelegateForTesting(
            std::make_unique<FakeAuthenticationServiceDelegate>()));
    profile_ = std::move(builder).Build();
  }

  void TearDown() override {
    // All callbacks should be triggered to make sure tests are working
    // correctly.

    fake_system_identity_manager()->WaitForServiceCallbacksToComplete();
    if (mediator_) {
      [mediator_ disconnect];
      EXPECT_EQ(SigninPromoViewState::kDisconnected,
                mediator_.signinPromoViewState);
      EXPECT_EQ(nil, mediator_.consumer);
      mediator_ = nil;
    }
    EXPECT_OCMOCK_VERIFY((id)consumer_);
    EXPECT_OCMOCK_VERIFY((id)signin_promo_view_);
    EXPECT_OCMOCK_VERIFY((id)primary_button_);
    EXPECT_OCMOCK_VERIFY((id)secondary_button_);
    EXPECT_OCMOCK_VERIFY((id)close_button_);
    EXPECT_OCMOCK_VERIFY((id)account_settings_presenter_);
    EXPECT_OCMOCK_VERIFY((id)signin_promo_mediator_delegate_);
  }

  void CreateMediator(signin_metrics::AccessPoint access_point) {
    consumer_ = OCMStrictProtocolMock(@protocol(SigninPromoViewConsumer));
    signin_promo_mediator_delegate_ =
        OCMStrictProtocolMock(@protocol(SigninPromoViewMediatorDelegate));
    account_settings_presenter_ =
        OCMStrictProtocolMock(@protocol(AccountSettingsPresenter));
    mediator_ = [[SigninPromoViewMediator alloc]
                  initWithIdentityManager:IdentityManagerFactory::GetForProfile(
                                              profile_.get())
                    accountManagerService:ChromeAccountManagerServiceFactory::
                                              GetForProfile(profile_.get())
                              authService:GetAuthenticationService()
                              prefService:profile_.get()->GetPrefs()
                              syncService:GetSyncService()
                              accessPoint:access_point
                                 delegate:signin_promo_mediator_delegate_
                 accountSettingsPresenter:account_settings_presenter_
        changeProfileContinuationProvider:NotReachedContinuationProvider()];
    mediator_.consumer = consumer_;

    signin_promo_view_ = OCMStrictClassMock([SigninPromoView class]);
    primary_button_ = OCMStrictClassMock([UIButton class]);
    OCMStub([signin_promo_view_ primaryButton]).andReturn(primary_button_);
    secondary_button_ = OCMStrictClassMock([UIButton class]);
    OCMStub([signin_promo_view_ secondaryButton]).andReturn(secondary_button_);
    close_button_ = OCMStrictClassMock([UIButton class]);
    OCMStub([signin_promo_view_ closeButton]).andReturn(close_button_);
  }

  std::unique_ptr<PrefServiceSyncable> CreatePrefService() {
    PrefServiceMockFactory factory;
    scoped_refptr<PrefRegistrySyncable> registry(new PrefRegistrySyncable);
    std::unique_ptr<PrefServiceSyncable> prefs =
        factory.CreateSyncable(registry.get());
    RegisterProfilePrefs(registry.get());
    return prefs;
  }

  AuthenticationService* GetAuthenticationService() {
    return AuthenticationServiceFactory::GetForProfile(profile_.get());
  }

  syncer::SyncService* GetSyncService() {
    return SyncServiceFactory::GetForProfile(profile_.get());
  }

  // Creates the default identity and adds it into the ChromeIdentityService.
  void AddDefaultIdentity() {
    fake_system_identity_manager()->AddIdentity(identity_);
  }

  PrefService* GetLocalState() {
    return GetApplicationContext()->GetLocalState();
  }

  // Tests the mediator with a new created configurator when no accounts are on
  // the device.
  void TestSigninPromoWithNoAccounts(SigninPromoViewStyle style) {
    EXPECT_EQ(nil, mediator_.displayedIdentity);
    CheckNoAccountsConfigurator([mediator_ createConfigurator], style);
  }

  // Adds an identity and tests the mediator.
  void TestSigninPromoWithAccount(SigninPromoViewStyle style) {
    // Expect to receive an update to the consumer with a configurator.
    ExpectConfiguratorNotification();

    AddDefaultIdentity();
    // Check the configurator received by the consumer.
    CheckSigninWithAccountConfigurator(configurator_, style);
    // Check a new created configurator.
    CheckSigninWithAccountConfigurator([mediator_ createConfigurator], style);
    // The consumer should receive a notification related to the image.
    CheckForImageNotification(style);
  }

  // Expects a notification on the consumer for an identity update, and stores
  // the received configurator into configurator_.
  void ExpectConfiguratorNotification() {
    configurator_ = nil;
    SigninPromoViewConfigurator* configurator_arg =
        AssignValueToVariable(configurator_);
    OCMExpect(
        [consumer_ configureSigninPromoWithConfigurator:configurator_arg]);
  }

  // Expects the signin promo view to be configured with no accounts on the
  // device.
  void ExpectNoAccountsConfiguration(SigninPromoViewStyle style) {
    OCMExpect([signin_promo_view_ setMode:SigninPromoViewModeNoAccounts]);
    NSString* title = nil;
    switch (style) {
      case SigninPromoViewStyleStandard:
        title = GetNSString(IDS_IOS_CONSISTENCY_PROMO_SIGN_IN);
        break;
      case SigninPromoViewStyleCompact:
        title = GetNSString(IDS_IOS_NTP_FEED_SIGNIN_PROMO_CONTINUE);
        break;
      case SigninPromoViewStyleOnlyButton:
        title = GetNSString(IDS_IOS_SIGNIN_PROMO_TURN_ON);
        break;
    }
    OCMExpect([signin_promo_view_ configurePrimaryButtonWithTitle:title]);
    image_view_profile_image_ = nil;
  }

  // Checks a configurator with no accounts on the device.
  void CheckNoAccountsConfigurator(SigninPromoViewConfigurator* configurator,
                                   SigninPromoViewStyle style) {
    EXPECT_NE(nil, configurator);
    ExpectNoAccountsConfiguration(style);
    OCMExpect([close_button_ setHidden:YES]);
    OCMExpect([signin_promo_view_ setPromoViewStyle:style]);
    OCMExpect([signin_promo_view_ stopSignInSpinner]);
    if (style == SigninPromoViewStyleCompact) {
#if BUILDFLAG(IOS_USE_BRANDED_ASSETS)
      UIImage* logo = [UIImage imageNamed:kChromeSigninPromoLogoImage];
#else
      UIImage* logo = [UIImage imageNamed:kChromiumSigninPromoLogoImage];
#endif  // BUILDFLAG(IOS_USE_BRANDED_ASSETS)
      OCMExpect([signin_promo_view_ setNonProfileImage:logo]);
    }
    [configurator configureSigninPromoView:signin_promo_view_ withStyle:style];
    EXPECT_EQ(nil, image_view_profile_image_);
  }

  // Expects the signin promo view to be configured when accounts are on the
  // device.
  void ExpectSigninWithAccountConfiguration(SigninPromoViewStyle style) {
    EXPECT_EQ(identity_, mediator_.displayedIdentity);
    OCMExpect(
        [signin_promo_view_ setMode:SigninPromoViewModeSigninWithAccount]);
    switch (style) {
      case SigninPromoViewStyleStandard: {
        NSString* name = identity_.userGivenName.length
                             ? identity_.userGivenName
                             : identity_.userEmail;
        std::u16string name16 = SysNSStringToUTF16(name);
        OCMExpect([signin_promo_view_
            configurePrimaryButtonWithTitle:
                GetNSStringF(IDS_IOS_SIGNIN_PROMO_CONTINUE_AS, name16)]);
        OCMExpect([secondary_button_
            setTitle:GetNSString(IDS_IOS_SIGNIN_PROMO_CHANGE_ACCOUNT)
            forState:UIControlStateNormal]);
        OCMExpect([signin_promo_view_
            setProfileImage:AssignValueToVariable(image_view_profile_image_)]);

        break;
      }
      case SigninPromoViewStyleCompact: {
        OCMExpect([signin_promo_view_
            configurePrimaryButtonWithTitle:
                GetNSString(IDS_IOS_NTP_FEED_SIGNIN_PROMO_CONTINUE)]);
        OCMExpect([signin_promo_view_
            setProfileImage:AssignValueToVariable(image_view_profile_image_)]);
        break;
      }
      case SigninPromoViewStyleOnlyButton:
        OCMExpect([signin_promo_view_
            configurePrimaryButtonWithTitle:GetNSString(
                                                IDS_IOS_SIGNIN_PROMO_TURN_ON)]);
        break;
    }
  }

  // Checks a configurator with accounts on the device.
  void CheckSigninWithAccountConfigurator(
      SigninPromoViewConfigurator* configurator,
      SigninPromoViewStyle style) {
    EXPECT_NE(nil, configurator);
    ExpectSigninWithAccountConfiguration(style);
    OCMExpect([close_button_ setHidden:YES]);
    OCMExpect([signin_promo_view_ setPromoViewStyle:style]);
    OCMExpect([signin_promo_view_ stopSignInSpinner]);
    [configurator configureSigninPromoView:signin_promo_view_ withStyle:style];
    switch (style) {
      case SigninPromoViewStyleStandard:
      case SigninPromoViewStyleCompact:
        EXPECT_NE(nil, image_view_profile_image_);
        break;
      case SigninPromoViewStyleOnlyButton:
        EXPECT_EQ(nil, image_view_profile_image_);
        break;
    }
  }

  // Expects the sync promo view to be configured
  void ExpectSyncPromoConfiguration() {
    OCMExpect([signin_promo_view_
        setMode:SigninPromoViewModeSignedInWithPrimaryAccount]);
    OCMExpect([signin_promo_view_
        setProfileImage:AssignValueToVariable(image_view_profile_image_)]);
    NSString* name = identity_.userGivenName.length ? identity_.userGivenName
                                                    : identity_.userEmail;
    std::u16string name16 = SysNSStringToUTF16(name);
    OCMExpect([signin_promo_view_
        configurePrimaryButtonWithTitle:GetNSString(
                                            IDS_IOS_SYNC_PROMO_TURN_ON_SYNC)]);
    image_view_profile_image_ = nil;
  }

  // Expects the review account settings promo view to be configured.
  void ExpectReviewAccountSettingsPromoConfiguration() {
    OCMExpect([signin_promo_view_
        setMode:SigninPromoViewModeSignedInWithPrimaryAccount]);
    OCMExpect([signin_promo_view_
        setProfileImage:AssignValueToVariable(image_view_profile_image_)]);
    OCMExpect([signin_promo_view_
        configurePrimaryButtonWithTitle:
            GetNSString(IDS_IOS_SIGNIN_PROMO_REVIEW_SETTINGS_BUTTON)]);
    image_view_profile_image_ = nil;
  }

  // Checks a configurator with accounts on the device.
  void CheckSyncPromoWithAccountConfigurator(
      SigninPromoViewConfigurator* configurator,
      SigninPromoViewStyle style) {
    EXPECT_NE(nil, configurator);
    ExpectSyncPromoConfiguration();
    OCMExpect([close_button_ setHidden:YES]);
    OCMExpect([signin_promo_view_ setPromoViewStyle:style]);
    OCMExpect([signin_promo_view_ stopSignInSpinner]);
    [configurator configureSigninPromoView:signin_promo_view_ withStyle:style];
    EXPECT_NE(nil, image_view_profile_image_);
  }

  // Checks a configurator with a signed-in account and review account settings
  // action.
  void CheckPromoWithReviewAccountSettingsAction(
      SigninPromoViewConfigurator* configurator,
      SigninPromoViewStyle style) {
    EXPECT_NE(nil, configurator);
    ExpectReviewAccountSettingsPromoConfiguration();
    // The close button should exist on the promo when shown on the bookmarks
    // manager UI.
    OCMExpect([close_button_ setHidden:NO]);
    OCMExpect([signin_promo_view_ setPromoViewStyle:style]);
    OCMExpect([signin_promo_view_ stopSignInSpinner]);
    [configurator configureSigninPromoView:signin_promo_view_ withStyle:style];
    EXPECT_NE(nil, image_view_profile_image_);
  }

  // Checks a configurator for recent tabs.
  void CheckPromoForRecentTabs(SigninPromoViewConfigurator* configurator,
                               SigninPromoViewStyle style) {
    EXPECT_NE(nil, configurator);
    // ExpectReviewAccountSettingsPromoConfiguration();
    //  The close button should exist on the promo when shown on the bookmarks
    //  manager UI.
    OCMExpect([close_button_ setHidden:YES]);
    OCMExpect([signin_promo_view_
        setProfileImage:AssignValueToVariable(image_view_profile_image_)]);
    OCMExpect(
        [signin_promo_view_ setMode:SigninPromoViewModeSigninWithAccount]);
    OCMExpect([signin_promo_view_
        configurePrimaryButtonWithTitle:GetNSStringF(
                                            IDS_IOS_SIGNIN_PROMO_CONTINUE_AS,
                                            SysNSStringToUTF16(
                                                identity_.userGivenName))]);
    OCMExpect([secondary_button_
        setTitle:GetNSString(IDS_IOS_SIGNIN_PROMO_CHANGE_ACCOUNT)
        forState:UIControlStateNormal]);
    OCMExpect([signin_promo_view_ setPromoViewStyle:style]);
    OCMExpect([signin_promo_view_ stopSignInSpinner]);
    [configurator configureSigninPromoView:signin_promo_view_ withStyle:style];
    EXPECT_NE(nil, image_view_profile_image_);
  }

  // Checks to receive a notification for the image upate of the current
  // identity.
  void CheckForImageNotification(SigninPromoViewStyle style) {
    configurator_ = nil;
    ExpectConfiguratorNotification();

    fake_system_identity_manager()->UpdateSystemIdentityAvatar(identity_.gaiaId,
                                                               nil);
    fake_system_identity_manager()->WaitForServiceCallbacksToComplete();
    // Check the configurator received by the consumer.
    CheckSigninWithAccountConfigurator(configurator_, style);
  }

  FakeSystemIdentityManager* fake_system_identity_manager() {
    return FakeSystemIdentityManager::FromSystemIdentityManager(
        GetApplicationContext()->GetSystemIdentityManager());
  }

  // Task environment.
  WebTaskEnvironment task_environment_;
  IOSChromeScopedTestingLocalState scoped_testing_local_state_;
  std::unique_ptr<TestProfileIOS> profile_;

  // Mediator used for the tests.
  SigninPromoViewMediator* mediator_;

  // Identity used for sign-in.
  id<SystemIdentity> identity_;

  // Configurator received from the consumer.
  SigninPromoViewConfigurator* configurator_;

  // Mocks.
  id<SigninPromoViewMediatorDelegate> signin_promo_mediator_delegate_;
  id<AccountSettingsPresenter> account_settings_presenter_;
  id<SigninPromoViewConsumer> consumer_;
  SigninPromoView* signin_promo_view_;
  UIButton* primary_button_;
  UIButton* secondary_button_;
  UIButton* close_button_;

  // Value set by -[SigninPromoView setProfileImage:].
  UIImage* image_view_profile_image_;
};

// Tests signin promo view and its configurator with no accounts on the device.
TEST_F(SigninPromoViewMediatorTest, NoAccountsConfigureSigninPromoView) {
  CreateMediator(signin_metrics::AccessPoint::kRecentTabs);
  TestSigninPromoWithNoAccounts(SigninPromoViewStyleStandard);
}

// Tests signin promo view and its configurator with accounts on the device.
TEST_F(SigninPromoViewMediatorTest, SigninWithAccountConfigureSigninPromoView) {
  CreateMediator(signin_metrics::AccessPoint::kRecentTabs);
  TestSigninPromoWithAccount(SigninPromoViewStyleStandard);
}

// Tests signin promo view and its configurator with an identity
// with given name as nil.
TEST_F(SigninPromoViewMediatorTest,
       SigninWithAccountConfigureSigninPromoViewWithMissingGivenName) {
  CreateMediator(signin_metrics::AccessPoint::kRecentTabs);
  identity_ = [FakeSystemIdentity fakeIdentityWithMissingGivenName];
  TestSigninPromoWithAccount(SigninPromoViewStyleStandard);
}

// Tests signin promo view and its configurator with an identity
// with both names as nil.
TEST_F(SigninPromoViewMediatorTest,
       SigninWithAccountConfigureSigninPromoViewWithMissingNames) {
  CreateMediator(signin_metrics::AccessPoint::kRecentTabs);
  identity_ = [FakeSystemIdentity fakeIdentityWithMissingNames];
  TestSigninPromoWithAccount(SigninPromoViewStyleStandard);
}

// Tests the scenario with the sign-in promo when no accounts on the device, and
// then add an identity to update the view.
TEST_F(SigninPromoViewMediatorTest, ConfigureSigninPromoViewWithColdAndWarm) {
  CreateMediator(signin_metrics::AccessPoint::kRecentTabs);
  TestSigninPromoWithNoAccounts(SigninPromoViewStyleStandard);
  TestSigninPromoWithAccount(SigninPromoViewStyleStandard);
}

// Tests the sign-in promo with and without account when the promo style is
// SigninPromoViewStyleOnlyButton.
TEST_F(SigninPromoViewMediatorTest,
       ConfigureOnlyButtonSigninPromoViewWithColdAndWarm) {
  CreateMediator(signin_metrics::AccessPoint::kRecentTabs);
  TestSigninPromoWithNoAccounts(SigninPromoViewStyleOnlyButton);
  TestSigninPromoWithAccount(SigninPromoViewStyleOnlyButton);
}

// Tests the sign-in promo with and without account when the promo style is
// compact vertical.
TEST_F(SigninPromoViewMediatorTest,
       ConfigureCompactSigninPromoViewWithColdAndWarm) {
  CreateMediator(signin_metrics::AccessPoint::kRecentTabs);
  TestSigninPromoWithNoAccounts(SigninPromoViewStyleCompact);
  TestSigninPromoWithAccount(SigninPromoViewStyleCompact);
}

// Tests the scenario with the sign-in promo with accounts on the device, and
// then removing the identity to update the view.
TEST_F(SigninPromoViewMediatorTest, ConfigureSigninPromoViewWithWarmAndCold) {
  CreateMediator(signin_metrics::AccessPoint::kRecentTabs);
  TestSigninPromoWithAccount(SigninPromoViewStyleStandard);
  // Expect to receive a new configuration from -[Consumer
  // configureSigninPromoWithConfigurator:].
  ExpectConfiguratorNotification();

  // Forgetting an identity is an asynchronous operation, so we need to wait
  // before the notification is sent.
  {
    base::RunLoop run_loop;
    fake_system_identity_manager()->ForgetIdentity(
        identity_, base::IgnoreArgs<NSError*>(run_loop.QuitClosure()));
    run_loop.Run();
  }
  identity_ = nil;

  // Check the received configurator.
  CheckNoAccountsConfigurator(configurator_, SigninPromoViewStyleStandard);
}

// Tests the view state before and after calling -[SigninPromoViewMediator
// signinPromoViewIsVisible].
TEST_F(SigninPromoViewMediatorTest, SigninPromoViewStateVisible) {
  CreateMediator(signin_metrics::AccessPoint::kRecentTabs);
  // Test initial state.
  EXPECT_EQ(SigninPromoViewState::kNotYetDisplayed,
            mediator_.signinPromoViewState);
  [mediator_ signinPromoViewIsVisible];
  // Test state once the sign-in promo view is visible.
  EXPECT_EQ(SigninPromoViewState::kHadNoInteraction,
            mediator_.signinPromoViewState);
}

// Tests the view state while signing in.
TEST_F(SigninPromoViewMediatorTest, SigninPromoViewStateSignedin) {
  CreateMediator(signin_metrics::AccessPoint::kRecentTabs);
  [mediator_ signinPromoViewIsVisible];
  __block ShowSigninCommand* command;
  ShowSigninCommand* command_arg = AssignValueToVariable(command);
  // Start sign-in.
  OCMExpect([signin_promo_mediator_delegate_ showSignin:mediator_
                                                command:command_arg]);
  OCMExpect([consumer_ promoProgressStateDidChange]);
  ExpectConfiguratorNotification();
  [mediator_ signinPromoViewDidTapSigninWithNewAccount:signin_promo_view_];
  EXPECT_TRUE(mediator_.showSpinner);
  EXPECT_EQ(SigninPromoViewState::kUserInteracted,
            mediator_.signinPromoViewState);
  // Stop sign-in.
  OCMExpect([consumer_ promoProgressStateDidChange]);
  ExpectConfiguratorNotification();

  [mediator_ signinDidCompleteWithResult:SigninCoordinatorResultSuccess];
  EXPECT_FALSE(mediator_.showSpinner);
  EXPECT_EQ(SigninPromoViewState::kUserInteracted,
            mediator_.signinPromoViewState);
}

// Tests that no update notification is sent by the mediator to its consumer,
// while the sign-in is in progress, when an identity is added.
TEST_F(SigninPromoViewMediatorTest,
       SigninPromoViewNoUpdateNotificationWhileSignin) {
  CreateMediator(signin_metrics::AccessPoint::kRecentTabs);
  [mediator_ signinPromoViewIsVisible];
  __block ShowSigninCommand* command;
  ShowSigninCommand* command_arg = AssignValueToVariable(command);
  OCMExpect([signin_promo_mediator_delegate_ showSignin:mediator_
                                                command:command_arg]);
  OCMExpect([consumer_ promoProgressStateDidChange]);
  ExpectConfiguratorNotification();
  // Starts sign-in without identity.
  [mediator_ signinPromoViewDidTapSigninWithNewAccount:signin_promo_view_];
  // Adds an identity while doing sign-in.
  AddDefaultIdentity();
  // No consumer notification should be expected.
  fake_system_identity_manager()->WaitForServiceCallbacksToComplete();
  // Finishs the sign-in.
  OCMExpect([consumer_ promoProgressStateDidChange]);
  ExpectConfiguratorNotification();
  [mediator_ signinDidCompleteWithResult:SigninCoordinatorResultSuccess];
}

// Tests that no update notification is sent by the mediator to its consumer,
// while the sign-in is in progress.
TEST_F(SigninPromoViewMediatorTest,
       SigninPromoViewNoUpdateNotificationWhileSignin2) {
  AddDefaultIdentity();
  CreateMediator(signin_metrics::AccessPoint::kRecentTabs);
  [mediator_ signinPromoViewIsVisible];
  __block ShowSigninCommand* command;
  ShowSigninCommand* command_arg = AssignValueToVariable(command);
  OCMExpect([signin_promo_mediator_delegate_ showSignin:mediator_
                                                command:command_arg]);
  OCMExpect([consumer_ promoProgressStateDidChange]);
  ExpectConfiguratorNotification();
  // Starts sign-in with an identity.
  [mediator_
      signinPromoViewDidTapPrimaryButtonWithDefaultAccount:signin_promo_view_];
  EXPECT_TRUE(
      [mediator_ conformsToProtocol:@protocol(IdentityManagerObserving)]);
  id<IdentityManagerObserving> identityManagerObserver =
      (id<IdentityManagerObserving>)mediator_;
  // Simulates an identity update.
  [identityManagerObserver extendedAccountInfoDidUpdate:AccountInfo()];
  // Spins the run loop to wait for the profile image update.
  fake_system_identity_manager()->WaitForServiceCallbacksToComplete();
  // Finishs the sign-in.
  OCMExpect([consumer_ promoProgressStateDidChange]);
  ExpectConfiguratorNotification();
  [mediator_ signinDidCompleteWithResult:SigninCoordinatorResultSuccess];
}

// Tests that promos aren't shown if browser sign-in is disabled by policy
TEST_F(SigninPromoViewMediatorTest,
       ShouldNotDisplaySigninPromoViewIfDisabledByPolicy) {
  CreateMediator(signin_metrics::AccessPoint::kRecentTabs);
  TestProfileIOS::Builder builder;
  builder.SetPrefService(CreatePrefService());
  std::unique_ptr<TestProfileIOS> profile = std::move(builder).Build();
  GetLocalState()->SetInteger(prefs::kBrowserSigninPolicy,
                              static_cast<int>(BrowserSigninMode::kDisabled));
  EXPECT_FALSE([SigninPromoViewMediator
      shouldDisplaySigninPromoViewWithAccessPoint:signin_metrics::AccessPoint::
                                                      kRecentTabs
                                signinPromoAction:SigninPromoAction::
                                                      kInstantSignin
                            authenticationService:GetAuthenticationService()
                                      prefService:profile->GetPrefs()]);
}

// Tests that the default identity is the primary account, when the user is
// signed in.
TEST_F(SigninPromoViewMediatorTest, SigninPromoWhileSignedIn) {
  AddDefaultIdentity();
  identity_ = [FakeSystemIdentity fakeIdentity2];
  fake_system_identity_manager()->AddIdentity(identity_);
  GetAuthenticationService()->SignIn(
      identity_, signin_metrics::AccessPoint::kFullscreenSigninPromo);
  CreateMediator(signin_metrics::AccessPoint::kRecentTabs);
  ExpectConfiguratorNotification();
  fake_system_identity_manager()->FireIdentityUpdatedNotification(identity_);
  [mediator_ signinPromoViewIsVisible];
  EXPECT_EQ(identity_, mediator_.displayedIdentity);
  fake_system_identity_manager()->WaitForServiceCallbacksToComplete();
  CheckSyncPromoWithAccountConfigurator(configurator_,
                                        SigninPromoViewStyleStandard);
}

// Tests that the sign-in promo view being removed while the sign-in is in
// progress, and tests the consumer is still called at the end of the sign-in.
TEST_F(SigninPromoViewMediatorTest,
       RemoveSigninPromoAndDeallocMediatorWhileSignedIn) {
  // Setup.
  AddDefaultIdentity();
  CreateMediator(signin_metrics::AccessPoint::kRecentTabs);
  __block ShowSigninCommand* command;
  // This test wants to verify behavior when `mediator_` gets deallocated.
  // OCMock uses autorelease in places, which could result in `mediator_`
  // staying allocated longer than we want without this @autoreleasepool.
  @autoreleasepool {
    [mediator_ signinPromoViewIsVisible];
    ShowSigninCommand* command_arg = AssignValueToVariable(command);
    OCMExpect([signin_promo_mediator_delegate_ showSignin:mediator_
                                                  command:command_arg]);
    OCMExpect([consumer_ promoProgressStateDidChange]);
    ExpectConfiguratorNotification();
    // Start sign-in with an identity.
    [mediator_ signinPromoViewDidTapPrimaryButtonWithDefaultAccount:
                   signin_promo_view_];

    // Finish the sign-in.
    OCMExpect([consumer_ promoProgressStateDidChange]);
    ExpectConfiguratorNotification();
    [mediator_ signinDidCompleteWithResult:SigninCoordinatorResultInterrupted];

    // Remove the sign-in promo.
    [mediator_ disconnect];
    EXPECT_EQ(SigninPromoViewState::kDisconnected,
              mediator_.signinPromoViewState);
    // Dealloc the mediator.
    mediator_ = nil;
    // Also clear all invocations from `consumer_` after verifying them, as the
    // invocations also contain a reference to `mediator_`. Generally this is
    // what `stopMocking` is meant for, but we still want to verify that
    // `signinDidfinish` is called below, so we can't just stop mocking yet.
    EXPECT_OCMOCK_VERIFY(consumer_);
    [(OCMockObject*)consumer_ clearInvocations];
  }
}

// Tests that the sign-in promo view being removed, and tests the consumer is
// still called at the end of the sign-in.
TEST_F(SigninPromoViewMediatorTest, RemoveSigninPromoWhileSignedIn) {
  // Setup.
  AddDefaultIdentity();
  CreateMediator(signin_metrics::AccessPoint::kRecentTabs);
  [mediator_ signinPromoViewIsVisible];
  __block ShowSigninCommand* command;
  ShowSigninCommand* command_arg = AssignValueToVariable(command);
  OCMExpect([signin_promo_mediator_delegate_ showSignin:mediator_
                                                command:command_arg]);
  OCMExpect([consumer_ promoProgressStateDidChange]);
  ExpectConfiguratorNotification();
  // Start sign-in with an identity.
  [mediator_
      signinPromoViewDidTapPrimaryButtonWithDefaultAccount:signin_promo_view_];

  // Finish the sign-in.
  OCMExpect([consumer_ promoProgressStateDidChange]);
  ExpectConfiguratorNotification();
  [mediator_ signinDidCompleteWithResult:SigninCoordinatorResultInterrupted];

  // Remove the sign-in promo.
  [mediator_ disconnect];
  EXPECT_EQ(SigninPromoViewState::kDisconnected,
            mediator_.signinPromoViewState);
  // Set mediator_ to nil to avoid the TearDown doesn't call
  // -[mediator_ disconnect] again.
  mediator_ = nil;
}

// Tests that promo setup with kSigninWithNoDefaultIdentity creates the expected
// configurator and promo.
TEST_F(SigninPromoViewMediatorTest,
       SigninPromoWithSigninWithNoDefaultIdentity) {
  AddDefaultIdentity();
  CreateMediator(signin_metrics::AccessPoint::kRecentTabs);
  [mediator_ signinPromoViewIsVisible];
  ExpectConfiguratorNotification();
  [mediator_
      setSigninPromoAction:SigninPromoAction::kSigninWithNoDefaultIdentity];
  EXPECT_EQ(identity_, mediator_.displayedIdentity);
  fake_system_identity_manager()->WaitForServiceCallbacksToComplete();
  CheckPromoForRecentTabs(configurator_, SigninPromoViewStyleStandard);
}

// Tests that promo setup with review account settings promo action.
TEST_F(SigninPromoViewMediatorTest,
       SigninPromoWithReviewAccountSettingsAction) {
  AddDefaultIdentity();
  identity_ = [FakeSystemIdentity fakeIdentity2];
  fake_system_identity_manager()->AddIdentity(identity_);
  GetAuthenticationService()->SignIn(
      identity_, signin_metrics::AccessPoint::kFullscreenSigninPromo);

  CreateMediator(signin_metrics::AccessPoint::kBookmarkManager);
  [mediator_ signinPromoViewIsVisible];
  ExpectConfiguratorNotification();
  [mediator_ setSigninPromoAction:SigninPromoAction::kReviewAccountSettings];
  EXPECT_EQ(identity_, mediator_.displayedIdentity);
  fake_system_identity_manager()->WaitForServiceCallbacksToComplete();
  CheckPromoWithReviewAccountSettingsAction(configurator_,
                                            SigninPromoViewStyleStandard);

  OCMExpect([account_settings_presenter_ showAccountSettings]);
  [mediator_
      signinPromoViewDidTapPrimaryButtonWithDefaultAccount:signin_promo_view_];
  EXPECT_EQ(SigninPromoViewState::kUserInteracted,
            mediator_.signinPromoViewState);
}

// Tests that review settings promo is not shown if the user has already
// dismissed it, but the signin promo should not be affected.
TEST_F(SigninPromoViewMediatorTest,
       ShouldNotDisplaySigninPromoViewIfAlreadySeen) {
  CreateMediator(signin_metrics::AccessPoint::kBookmarkManager);
  TestProfileIOS::Builder builder;
  builder.SetPrefService(CreatePrefService());
  std::unique_ptr<TestProfileIOS> profile = std::move(builder).Build();
  profile->GetPrefs()->SetBoolean(prefs::kIosBookmarkSettingsPromoAlreadySeen,
                                  true);
  EXPECT_FALSE([SigninPromoViewMediator
      shouldDisplaySigninPromoViewWithAccessPoint:signin_metrics::AccessPoint::
                                                      kBookmarkManager
                                signinPromoAction:SigninPromoAction::
                                                      kReviewAccountSettings
                            authenticationService:GetAuthenticationService()
                                      prefService:profile->GetPrefs()]);
  EXPECT_TRUE([SigninPromoViewMediator
      shouldDisplaySigninPromoViewWithAccessPoint:signin_metrics::AccessPoint::
                                                      kBookmarkManager
                                signinPromoAction:SigninPromoAction::
                                                      kInstantSignin
                            authenticationService:GetAuthenticationService()
                                      prefService:profile->GetPrefs()]);
}

}  // namespace
