chromium/ios/chrome/browser/ui/settings/sync/sync_encryption_passphrase_table_view_controller_unittest.mm

// Copyright 2012 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/sync/sync_encryption_passphrase_table_view_controller.h"

#import <UIKit/UIKit.h>

#import <memory>

#import "base/functional/bind.h"
#import "base/strings/sys_string_conversions.h"
#import "base/test/ios/wait_util.h"
#import "components/strings/grit/components_strings.h"
#import "components/sync/test/mock_sync_service.h"
#import "ios/chrome/browser/settings/model/sync/utils/sync_util.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_text_item.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/system_identity.h"
#import "ios/chrome/browser/ui/settings/cells/byo_textfield_item.h"
#import "ios/chrome/browser/ui/settings/passphrase_table_view_controller_test.h"
#import "ios/chrome/browser/ui/settings/settings_navigation_controller.h"
#import "ios/chrome/grit/ios_branded_strings.h"
#import "ios/chrome/grit/ios_strings.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"
#import "ui/base/l10n/l10n_util.h"
#import "ui/base/l10n/l10n_util_mac.h"

namespace {

using testing::AtLeast;
using testing::NiceMock;
using testing::Return;

class SyncEncryptionPassphraseTableViewControllerTest
    : public PassphraseTableViewControllerTest {
 public:
  SyncEncryptionPassphraseTableViewControllerTest() = default;

  void TurnSyncPassphraseErrorOn() {
    ON_CALL(*fake_sync_service_, GetUserActionableError())
        .WillByDefault(
            Return(syncer::SyncService::UserActionableError::kNeedsPassphrase));
    ON_CALL(*fake_sync_service_->GetMockUserSettings(), IsPassphraseRequired)
        .WillByDefault(Return(true));
  }

  void TurnSyncOtherErrorOn(syncer::SyncService::UserActionableError error) {
    ON_CALL(*fake_sync_service_, GetUserActionableError())
        .WillByDefault(Return(error));
    ON_CALL(*fake_sync_service_->GetMockUserSettings(), IsPassphraseRequired)
        .WillByDefault(Return(false));
  }

  void TurnSyncErrorOff() {
    ON_CALL(*fake_sync_service_, GetUserActionableError())
        .WillByDefault(Return(syncer::SyncService::UserActionableError::kNone));
    ON_CALL(*fake_sync_service_->GetMockUserSettings(), IsPassphraseRequired)
        .WillByDefault(Return(false));
  }

 protected:
  void SetUp() override {
    PassphraseTableViewControllerTest::SetUp();
    ON_CALL(*fake_sync_service_, GetTransportState)
        .WillByDefault(Return(syncer::SyncService::TransportState::ACTIVE));
    ON_CALL(*fake_sync_service_->GetMockUserSettings(),
            IsUsingExplicitPassphrase)
        .WillByDefault(Return(true));
    TurnSyncErrorOff();
  }

  LegacyChromeTableViewController* InstantiateController() override {
    return [[SyncEncryptionPassphraseTableViewController alloc]
        initWithBrowser:browser_.get()];
  }

  SyncEncryptionPassphraseTableViewController* SyncController() {
    return static_cast<SyncEncryptionPassphraseTableViewController*>(
        controller());
  }
};

TEST_F(SyncEncryptionPassphraseTableViewControllerTest, TestModel) {
  TurnSyncPassphraseErrorOn();
  SyncEncryptionPassphraseTableViewController* controller = SyncController();
  EXPECT_EQ(1, NumberOfSections());
  EXPECT_EQ(2, NumberOfItemsInSection(0));
  // Passphrase message item.
  NSString* userEmail = AuthenticationServiceFactory::GetForBrowserState(
                            chrome_browser_state_.get())
                            ->GetPrimaryIdentity(signin::ConsentLevel::kSignin)
                            .userEmail;
  EXPECT_NSEQ(
      l10n_util::GetNSStringF(IDS_IOS_SYNC_ENTER_PASSPHRASE_BODY_WITH_EMAIL,
                              base::SysNSStringToUTF16(userEmail)),
      [GetTableViewItem(0, 0) text]);
  // Passphrase item.
  EXPECT_NSEQ(controller.passphrase, [GetTableViewItem(0, 1) textField]);
}

TEST_F(SyncEncryptionPassphraseTableViewControllerTest,
       TestConstructorDestructor) {
  CreateController();
  CheckController();
  EXPECT_CALL(*fake_sync_service_->GetMockUserSettings(),
              SetDecryptionPassphrase)
      .Times(0);
  // Simulate the view appearing.
  [controller() viewWillAppear:YES];
  [controller() viewDidAppear:YES];
}

TEST_F(SyncEncryptionPassphraseTableViewControllerTest, TestDecryptButton) {
  SyncEncryptionPassphraseTableViewController* sync_controller =
      SyncController();
  [sync_controller textFieldDidChange:[sync_controller passphrase]];
  EXPECT_FALSE([[sync_controller navigationItem].rightBarButtonItem isEnabled]);
  [[sync_controller passphrase] setText:@"decodeme"];
  [sync_controller textFieldDidChange:[sync_controller passphrase]];
  EXPECT_TRUE([[sync_controller navigationItem].rightBarButtonItem isEnabled]);
}

TEST_F(SyncEncryptionPassphraseTableViewControllerTest,
       TestDecryptWrongPassphrase) {
  SyncEncryptionPassphraseTableViewController* sync_controller =
      SyncController();
  EXPECT_CALL(*fake_sync_service_, AddObserver).Times(AtLeast(1));
  EXPECT_CALL(*fake_sync_service_, RemoveObserver).Times(AtLeast(1));
  EXPECT_CALL(*fake_sync_service_->GetMockUserSettings(),
              SetDecryptionPassphrase);
  [[sync_controller passphrase] setText:@"decodeme"];
  // Set the return value for setting the passphrase to failure.
  ON_CALL(*fake_sync_service_->GetMockUserSettings(), SetDecryptionPassphrase)
      .WillByDefault(Return(false));
  [sync_controller signInPressed];
}

TEST_F(SyncEncryptionPassphraseTableViewControllerTest,
       TestDecryptCorrectPassphrase) {
  SyncEncryptionPassphraseTableViewController* sync_controller =
      SyncController();
  EXPECT_CALL(*fake_sync_service_, AddObserver).Times(AtLeast(1));
  EXPECT_CALL(*fake_sync_service_, RemoveObserver).Times(AtLeast(1));
  EXPECT_CALL(*fake_sync_service_->GetMockUserSettings(),
              SetDecryptionPassphrase);
  [[sync_controller passphrase] setText:@"decodeme"];
  // Set the return value for setting the passphrase to success.
  ON_CALL(*fake_sync_service_->GetMockUserSettings(), SetDecryptionPassphrase)
      .WillByDefault(Return(true));
  [sync_controller signInPressed];
}

TEST_F(SyncEncryptionPassphraseTableViewControllerTest,
       TestOnStateChangedWrongPassphrase) {
  SyncEncryptionPassphraseTableViewController* sync_controller =
      SyncController();
  // Sets up a UINavigationController that has `controller_` as the second view
  // controller on the navigation stack.
  SetUpNavigationController(sync_controller);
  EXPECT_EQ([nav_controller_ topViewController], sync_controller);

  // Set up the fake sync service to require the passphrase.
  TurnSyncPassphraseErrorOn();
  [sync_controller onSyncStateChanged];
  // The controller should only reload. Because there is text in the passphrase
  // field, the 'ok' button should be enabled.
  EXPECT_EQ([nav_controller_ topViewController], sync_controller);
}

TEST_F(SyncEncryptionPassphraseTableViewControllerTest,
       TestOnStateChangedCorrectPassphrase) {
  SyncEncryptionPassphraseTableViewController* sync_controller =
      SyncController();
  // Sets up a UINavigationController that has `controller_` as the second view
  // controller on the navigation stack.
  SetUpNavigationController(sync_controller);
  EXPECT_EQ([nav_controller_ topViewController], sync_controller);

  // Set up the fake sync service to have accepted the passphrase.
  TurnSyncErrorOff();
  [sync_controller onSyncStateChanged];
  // Calling -onStateChanged with an accepted explicit passphrase should
  // cause the controller to be popped off the navigation stack.
  EXPECT_TRUE(base::test::ios::WaitUntilConditionOrTimeout(
      base::test::ios::kWaitForUIElementTimeout, ^bool() {
        return [nav_controller_ topViewController] != sync_controller;
      }));
}

TEST_F(SyncEncryptionPassphraseTableViewControllerTest, TestMessage) {
  SyncEncryptionPassphraseTableViewController* sync_controller =
      SyncController();
  // Default.
  EXPECT_FALSE([sync_controller syncErrorMessage]);

  syncer::SyncService::UserActionableError otherError =
      syncer::SyncService::UserActionableError::kSignInNeedsUpdate;

  // With a custom message.
  [sync_controller setSyncErrorMessage:@"message"];
  EXPECT_NSEQ(@"message", [sync_controller syncErrorMessage]);
  TurnSyncPassphraseErrorOn();
  EXPECT_NSEQ(@"message", [sync_controller syncErrorMessage]);
  TurnSyncOtherErrorOn(otherError);
  EXPECT_NSEQ(@"message", [sync_controller syncErrorMessage]);
  TurnSyncErrorOff();
  EXPECT_NSEQ(@"message", [sync_controller syncErrorMessage]);

  // With no custom message.
  [sync_controller setSyncErrorMessage:nil];
  EXPECT_FALSE([sync_controller syncErrorMessage]);
  TurnSyncPassphraseErrorOn();
  EXPECT_FALSE([sync_controller syncErrorMessage]);
  TurnSyncOtherErrorOn(otherError);
  EXPECT_NSEQ(GetSyncErrorMessageForBrowserState(chrome_browser_state_.get()),
              [sync_controller syncErrorMessage]);
  TurnSyncErrorOff();
  EXPECT_FALSE([sync_controller syncErrorMessage]);
}

}  // namespace