chromium/ios/chrome/browser/ui/settings/google_services/manage_accounts/legacy_accounts_table_view_controller_unittest.mm

// Copyright 2021 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/ui/settings/google_services/manage_accounts/legacy_accounts_table_view_controller.h"

#import "base/apple/foundation_util.h"
#import "base/functional/callback_helpers.h"
#import "base/run_loop.h"
#import "base/strings/sys_string_conversions.h"
#import "components/sync/service/sync_service.h"
#import "components/sync/test/test_sync_service.h"
#import "components/variations/scoped_variations_ids_provider.h"
#import "google_apis/gaia/core_account_id.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/command_dispatcher.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/ui/table_view/legacy_chrome_table_view_controller_test.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.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/sync_service_factory.h"
#import "ios/chrome/browser/ui/settings/google_services/manage_accounts/accounts_mediator.h"
#import "ios/chrome/test/ios_chrome_scoped_testing_local_state.h"
#import "ios/web/public/browser_state.h"
#import "ios/web/public/test/web_task_environment.h"
#import "testing/gmock/include/gmock/gmock.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#import "third_party/ocmock/gtest_support.h"

namespace {

std::unique_ptr<KeyedService> CreateTestSyncService(
    web::BrowserState* context) {
  return std::make_unique<syncer::TestSyncService>();
}

}  // namespace

class LegacyAccountsTableViewControllerTest
    : public LegacyChromeTableViewControllerTest {
 public:
  LegacyAccountsTableViewControllerTest() {
    TestChromeBrowserState::Builder builder;
    builder.AddTestingFactory(
        AuthenticationServiceFactory::GetInstance(),
        AuthenticationServiceFactory::GetDefaultFactory());
    builder.AddTestingFactory(SyncServiceFactory::GetInstance(),
                              base::BindRepeating(&CreateTestSyncService));
    browser_state_ = std::move(builder).Build();
    browser_ = std::make_unique<TestBrowser>(browser_state_.get());

    AuthenticationServiceFactory::CreateAndInitializeForBrowserState(
        browser_state_.get(),
        std::make_unique<FakeAuthenticationServiceDelegate>());
  }

  LegacyChromeTableViewController* InstantiateController() override {
    // Set up ApplicationCommands mock.
    id mock_application_handler =
        OCMProtocolMock(@protocol(ApplicationCommands));
    CommandDispatcher* dispatcher = browser_->GetCommandDispatcher();
    [dispatcher startDispatchingToTarget:mock_application_handler
                             forProtocol:@protocol(ApplicationCommands)];

    AccountsMediator* mediator =
        [[AccountsMediator alloc] initWithSyncService:test_sync_service()
                                accountManagerService:account_manager_service()
                                          authService:authentication_service()
                                      identityManager:identity_manager()];

    LegacyAccountsTableViewController* controller =
        [[LegacyAccountsTableViewController alloc]
                                initWithBrowser:browser_.get()
                      closeSettingsOnAddAccount:NO
                     applicationCommandsHandler:mock_application_handler
            signoutDismissalByParentCoordinator:NO];

    mediator.consumer = controller;
    controller.modelIdentityDataSource = mediator;
    mediator_ = mediator;

    return controller;
  }

  void TearDown() override {
    mediator_ = nil;
    [base::apple::ObjCCast<LegacyAccountsTableViewController>(controller())
        settingsWillBeDismissed];
    LegacyChromeTableViewControllerTest::TearDown();
  }

  // Identity Services
  signin::IdentityManager* identity_manager() {
    return IdentityManagerFactory::GetForBrowserState(browser_state_.get());
  }

  AuthenticationService* authentication_service() {
    return AuthenticationServiceFactory::GetForBrowserState(
        browser_state_.get());
  }

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

  syncer::TestSyncService* test_sync_service() {
    return static_cast<syncer::TestSyncService*>(
        SyncServiceFactory::GetForBrowserState(browser_state_.get()));
  }

  ChromeAccountManagerService* account_manager_service() {
    return ChromeAccountManagerServiceFactory::GetForBrowserState(
        browser_state_.get());
  }

 private:
  web::WebTaskEnvironment task_environment_{
      web::WebTaskEnvironment::MainThreadType::IO};
  IOSChromeScopedTestingLocalState scoped_testing_local_state_;
  std::unique_ptr<TestChromeBrowserState> browser_state_;
  std::unique_ptr<Browser> browser_;
  variations::ScopedVariationsIdsProvider scoped_variations_ids_provider_{
      variations::VariationsIdsProvider::Mode::kUseSignedInState};
  AccountsMediator* mediator_;
};

// Tests that a valid identity is added to the model.
TEST_F(LegacyAccountsTableViewControllerTest, AddChromeIdentity) {
  FakeSystemIdentity* fake_identity = [FakeSystemIdentity fakeIdentity1];
  fake_system_identity_manager()->AddIdentity(fake_identity);

  // Simulates a credential reload.
  authentication_service()->SignIn(
      fake_identity, signin_metrics::AccessPoint::ACCESS_POINT_SETTINGS);
  fake_system_identity_manager()->FireSystemIdentityReloaded();
  base::RunLoop().RunUntilIdle();

  CreateController();
  CheckController();

  EXPECT_EQ(2, NumberOfSections());
  EXPECT_EQ(2, NumberOfItemsInSection(0));
}

// Tests that an invalid identity is not added to the model.
TEST_F(LegacyAccountsTableViewControllerTest, IgnoreMismatchWithAccountInfo) {
  FakeSystemIdentity* fake_identity1 = [FakeSystemIdentity fakeIdentity1];
  fake_system_identity_manager()->AddIdentity(fake_identity1);
  FakeSystemIdentity* fake_identity2 = [FakeSystemIdentity fakeIdentity2];
  fake_system_identity_manager()->AddIdentity(fake_identity2);

  // Simulates a credential reload.
  authentication_service()->SignIn(
      fake_identity1, signin_metrics::AccessPoint::ACCESS_POINT_SETTINGS);
  fake_system_identity_manager()->FireSystemIdentityReloaded();
  base::RunLoop().RunUntilIdle();

  CreateController();
  CheckController();

  EXPECT_EQ(2, NumberOfSections());
  EXPECT_EQ(3, NumberOfItemsInSection(0));

  // Removes identity2 from identity service but not account info storage. This
  // is an asynchronous call, so wait for completion.
  {
    base::RunLoop run_loop;
    fake_system_identity_manager()->ForgetIdentity(
        fake_identity2, base::IgnoreArgs<NSError*>(run_loop.QuitClosure()));
    run_loop.Run();
  }

  [controller() loadModel];

  EXPECT_EQ(2, NumberOfSections());
  EXPECT_EQ(2, NumberOfItemsInSection(0));
}

// Tests that no passphrase error is exposed in the account table view (since
// it's exposed one level up).
TEST_F(LegacyAccountsTableViewControllerTest, DontHoldPassphraseError) {
  FakeSystemIdentity* fake_identity = [FakeSystemIdentity fakeIdentity1];
  fake_system_identity_manager()->AddIdentity(fake_identity);

  // Simulate a credential reload.
  authentication_service()->SignIn(
      fake_identity, signin_metrics::AccessPoint::ACCESS_POINT_SETTINGS);
  fake_system_identity_manager()->FireSystemIdentityReloaded();
  base::RunLoop().RunUntilIdle();

  CoreAccountInfo account;
  account.email = base::SysNSStringToUTF8(fake_identity.userEmail);
  account.gaia = base::SysNSStringToUTF8(fake_identity.gaiaID);
  account.account_id = CoreAccountId::FromGaiaId(account.gaia);
  test_sync_service()->SetSignedIn(signin::ConsentLevel::kSignin, account);
  test_sync_service()->GetUserSettings()->SetPassphraseRequired();

  CreateController();
  CheckController();

  EXPECT_EQ(2, NumberOfSections());
}

// Tests that when eligible account bookmarks don't have the Account Storage
// error when there is no error.
TEST_F(LegacyAccountsTableViewControllerTest,
       DontHoldPassphraseErrorWhenEligibleNoError) {
  FakeSystemIdentity* fake_identity = [FakeSystemIdentity fakeIdentity1];
  fake_system_identity_manager()->AddIdentity(fake_identity);

  // Simulate a credential reload.
  authentication_service()->SignIn(
      fake_identity, signin_metrics::AccessPoint::ACCESS_POINT_SETTINGS);
  fake_system_identity_manager()->FireSystemIdentityReloaded();
  base::RunLoop().RunUntilIdle();

  CoreAccountInfo account;
  account.email = base::SysNSStringToUTF8(fake_identity.userEmail);
  account.gaia = base::SysNSStringToUTF8(fake_identity.gaiaID);
  account.account_id = CoreAccountId::FromGaiaId(account.gaia);
  test_sync_service()->SetSignedIn(signin::ConsentLevel::kSync, account);
  ASSERT_FALSE(test_sync_service()->GetUserSettings()->IsPassphraseRequired());

  CreateController();
  CheckController();

  // Verify that there are only 2 sections, exluding the error section.
  EXPECT_EQ(2, NumberOfSections());
}