chromium/ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller_unittest.mm

// Copyright 2020 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/password/password_details/password_details_table_view_controller.h"

#import <memory>

#import "base/apple/foundation_util.h"
#import "base/containers/contains.h"
#import "base/i18n/time_formatting.h"
#import "base/ios/ios_util.h"
#import "base/strings/sys_string_conversions.h"
#import "base/strings/utf_string_conversions.h"
#import "base/test/metrics/histogram_tester.h"
#import "base/test/scoped_feature_list.h"
#import "components/password_manager/core/browser/password_form.h"
#import "components/password_manager/core/browser/password_manager_metrics_util.h"
#import "components/password_manager/core/browser/ui/credential_ui_entry.h"
#import "components/sync/base/features.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/open_new_tab_command.h"
#import "ios/chrome/browser/shared/public/commands/snackbar_commands.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_multi_line_text_edit_item.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_text_edit_item.h"
#import "ios/chrome/browser/shared/ui/table_view/legacy_chrome_table_view_controller_test.h"
#import "ios/chrome/browser/ui/settings/cells/settings_image_detail_text_item.h"
#import "ios/chrome/browser/ui/settings/password/password_details/cells/table_view_stacked_details_item.h"
#import "ios/chrome/browser/ui/settings/password/password_details/credential_details.h"
#import "ios/chrome/browser/ui/settings/password/password_details/password_details_consumer.h"
#import "ios/chrome/browser/ui/settings/password/password_details/password_details_handler.h"
#import "ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_constants.h"
#import "ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller+Testing.h"
#import "ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller_delegate.h"
#import "ios/chrome/common/ui/reauthentication/reauthentication_module.h"
#import "ios/chrome/common/ui/table_view/table_view_cells_constants.h"
#import "ios/chrome/grit/ios_branded_strings.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/chrome/test/app/mock_reauthentication_module.h"
#import "ios/web/public/test/web_task_environment.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#import "third_party/ocmock/gtest_support.h"
#import "ui/base/l10n/l10n_util_mac.h"

namespace {
constexpr char kExampleCom[] = "http://www.example.com/";
constexpr char kAndroid[] = "android://[email protected]";
constexpr char kUsername[] = "[email protected]";
constexpr char kDisplayName[] = "FirstName LastName";
constexpr char kPassword[] = "test";
constexpr char kNote[] = "note";

NSString* HTTPWebsite() {
  return base::SysUTF8ToNSString(kExampleCom);
}

NSString* Username() {
  return base::SysUTF8ToNSString(kUsername);
}

NSString* DisplayName() {
  return base::SysUTF8ToNSString(kDisplayName);
}
}

// Test class that conforms to PasswordDetailsHanler in order to test the
// presenter methods are called correctly.
@interface FakePasswordDetailsHandler : NSObject <PasswordDetailsHandler>

@property(nonatomic, assign) BOOL deletionCalled;

@property(nonatomic, assign) BOOL deletionCalledOnCompromisedPassword;

@property(nonatomic, assign) BOOL editingCalled;

@property(nonatomic, assign) BOOL passwordCopiedByUserCalled;

@end

@implementation FakePasswordDetailsHandler

- (void)passwordDetailsTableViewControllerWasDismissed {
}

- (void)dismissPasswordDetailsTableViewController {
}

- (void)showCredentialDeleteDialogWithCredentialDetails:
            (CredentialDetails*)password
                                             anchorView:(UIView*)anchorView {
  self.deletionCalled = YES;
  self.deletionCalledOnCompromisedPassword = password.isCompromised;
}

- (void)moveCredentialToAccountStore:(CredentialDetails*)password
                          anchorView:(UIView*)anchorView
                     movedCompletion:(void (^)())movedCompletion {
}

- (void)showPasswordEditDialogWithOrigin:(NSString*)origin {
  self.editingCalled = YES;
}

- (void)onAllPasswordsDeleted {
}

- (void)onShareButtonPressed {
}

@end

// Test class that conforms to PasswordDetailsViewControllerDelegate in order
// to test the delegate methods are called correctly.
@interface FakePasswordDetailsDelegate
    : NSObject <PasswordDetailsTableViewControllerDelegate>

@property(nonatomic, strong) CredentialDetails* credential;

@property(nonatomic, assign) BOOL dismissWarningCalled;

@property(nonatomic, assign) BOOL restoreWarningCalled;

@end

@implementation FakePasswordDetailsDelegate

- (void)passwordDetailsViewController:
            (PasswordDetailsTableViewController*)viewController
             didEditCredentialDetails:(CredentialDetails*)credential
                      withOldUsername:(NSString*)oldUsername
                   oldUserDisplayName:(NSString*)oldUserDisplayName
                          oldPassword:(NSString*)oldPassword
                              oldNote:(NSString*)oldNote {
  self.credential = credential;
}

- (void)didFinishEditingPasswordDetails {
}

- (BOOL)isUsernameReused:(NSString*)newUsername forDomain:(NSString*)domain {
  return NO;
}

- (void)passwordDetailsViewController:
            (PasswordDetailsTableViewController*)viewController
                didAddPasswordDetails:(NSString*)username
                             password:(NSString*)password {
}

- (void)checkForDuplicates:(NSString*)username {
}

- (void)showExistingCredential:(NSString*)username {
}

- (void)didCancelAddPasswordDetails {
}

- (void)setWebsiteURL:(NSString*)website {
}

- (BOOL)isURLValid {
  return YES;
}

- (BOOL)isTLDMissing {
  return NO;
}

- (void)dismissWarningForPassword:(CredentialDetails*)password {
  self.dismissWarningCalled = YES;
}

- (void)restoreWarningForCurrentPassword {
  self.restoreWarningCalled = YES;
}

@end

@interface FakeSnackbarImplementation : NSObject <SnackbarCommands>

@property(nonatomic, copy) NSString* snackbarMessage;

@end

@implementation FakeSnackbarImplementation

- (void)showSnackbarMessage:(MDCSnackbarMessage*)message {
}

- (void)showSnackbarMessageOverBrowserToolbar:(MDCSnackbarMessage*)message {
}

- (void)showSnackbarMessage:(MDCSnackbarMessage*)message
             withHapticType:(UINotificationFeedbackType)type {
}

- (void)showSnackbarMessage:(MDCSnackbarMessage*)message
               bottomOffset:(CGFloat)offset {
}

- (void)showSnackbarWithMessage:(NSString*)messageText
                     buttonText:(NSString*)buttonText
                  messageAction:(void (^)(void))messageAction
               completionAction:(void (^)(BOOL))completionAction {
  self.snackbarMessage = messageText;
}

@end

// Unit tests for PasswordIssuesTableViewController.
class PasswordDetailsTableViewControllerTest
    : public LegacyChromeTableViewControllerTest {
 protected:
  PasswordDetailsTableViewControllerTest() {
    feature_list_.InitAndEnableFeature(syncer::kSyncWebauthnCredentials);
    handler_ = [[FakePasswordDetailsHandler alloc] init];
    delegate_ = [[FakePasswordDetailsDelegate alloc] init];
    reauthentication_module_ = [[MockReauthenticationModule alloc] init];
    reauthentication_module_.expectedResult = ReauthenticationResult::kSuccess;
    snack_bar_ = [[FakeSnackbarImplementation alloc] init];
  }

  LegacyChromeTableViewController* InstantiateController() override {
    PasswordDetailsTableViewController* controller =
        [[PasswordDetailsTableViewController alloc] init];
    controller.handler = handler_;
    controller.delegate = delegate_;
    controller.reauthModule = reauthentication_module_;
    controller.snackbarCommandsHandler = snack_bar_;
    return controller;
  }

  void SetPassword(std::string website = kExampleCom,
                   std::string username = kUsername,
                   std::string password = kPassword,
                   std::string note = kNote,
                   bool is_compromised = false,
                   bool is_muted = false,
                   DetailsContext context = DetailsContext::kPasswordSettings) {
    std::vector<std::string> websites = {website};
    SetPassword(websites, username, password, note, is_compromised, is_muted,
                context);
  }

  void SetPassword(const std::vector<std::string>& websites,
                   std::string username = kUsername,
                   std::string password = kPassword,
                   std::string note = kNote,
                   bool is_compromised = false,
                   bool is_muted = false,
                   DetailsContext context = DetailsContext::kPasswordSettings) {
    std::vector<password_manager::PasswordForm> forms;
    for (const auto& website : websites) {
      auto form = password_manager::PasswordForm();
      form.signon_realm = website;
      form.username_value = base::ASCIIToUTF16(username);
      form.password_value = base::ASCIIToUTF16(password);
      form.url = GURL(website);
      form.action = GURL(website + "/action");
      form.username_element = u"email";
      form.scheme = password_manager::PasswordForm::Scheme::kHtml;
      form.notes = {password_manager::PasswordNote(base::ASCIIToUTF16(note),
                                                   base::Time::Now())};
      forms.push_back(std::move(form));
    }

    NSMutableArray<CredentialDetails*>* passwords = [NSMutableArray array];
    CredentialDetails* passwordDetails = [[CredentialDetails alloc]
        initWithCredential:password_manager::CredentialUIEntry(forms)];
    passwordDetails.context = context;
    passwordDetails.compromised = is_compromised;
    passwordDetails.muted = is_muted;
    [passwords addObject:passwordDetails];

    PasswordDetailsTableViewController* passwords_controller =
        static_cast<PasswordDetailsTableViewController*>(controller());
    [passwords_controller setCredentials:passwords andTitle:nil];
  }

  void SetFederatedPassword() {
    SetCredentialType(CredentialTypeFederation);
    auto form = password_manager::PasswordForm();
    form.username_value = u"[email protected]";
    form.url = GURL(u"http://www.example.com/");
    form.signon_realm = form.url.spec();
    form.federation_origin = url::SchemeHostPort(GURL(kExampleCom));
    NSMutableArray<CredentialDetails*>* passwords = [NSMutableArray array];
    CredentialDetails* password = [[CredentialDetails alloc]
        initWithCredential:password_manager::CredentialUIEntry(form)];
    [passwords addObject:password];
    PasswordDetailsTableViewController* passwords_controller =
        static_cast<PasswordDetailsTableViewController*>(controller());
    [passwords_controller setCredentials:passwords andTitle:nil];
  }

  // Creates a passkey, adds it to the view controller and returns the passkey's
  // creation time.
  base::Time SetPasskey(std::string website = "www.example.com/",
                        std::string username = kUsername,
                        std::string display_name = kDisplayName,
                        base::Time creation_time = base::Time::Now()) {
    password_manager::PasskeyCredential::Source source =
        password_manager::PasskeyCredential::Source::kGooglePasswordManager;
    password_manager::PasskeyCredential::RpId rp_id(website);
    password_manager::PasskeyCredential::CredentialId credential_id(
        {'c', 'r', 'e', 'd', 'e', 'n', 't', 'i', 'a', 'l', '_', 'i', 'd', '_',
         '0', '1'});
    password_manager::PasskeyCredential::UserId user_id(
        {'u', 's', 'e', 'r', '_', 'i', 'd', '1'});
    password_manager::PasskeyCredential::Username passkey_username(username);
    password_manager::PasskeyCredential::DisplayName passkey_display_name(
        display_name);
    password_manager::PasskeyCredential passkeyCredential(
        source, rp_id, credential_id, user_id, passkey_username,
        passkey_display_name, creation_time);
    NSMutableArray<CredentialDetails*>* passkeys = [NSMutableArray array];
    CredentialDetails* passkey = [[CredentialDetails alloc]
        initWithCredential:password_manager::CredentialUIEntry(
                               passkeyCredential)];
    [passkeys addObject:passkey];
    PasswordDetailsTableViewController* passwords_controller =
        static_cast<PasswordDetailsTableViewController*>(controller());
    [passwords_controller setCredentials:passkeys andTitle:nil];
    return creation_time;
  }

  void SetBlockedOrigin() {
    SetCredentialType(CredentialTypeBlocked);
    auto form = password_manager::PasswordForm();
    form.url = GURL(kExampleCom);
    form.blocked_by_user = true;
    form.signon_realm = form.url.spec();
    NSMutableArray<CredentialDetails*>* passwords = [NSMutableArray array];
    CredentialDetails* password = [[CredentialDetails alloc]
        initWithCredential:password_manager::CredentialUIEntry(form)];
    [passwords addObject:password];
    PasswordDetailsTableViewController* passwords_controller =
        static_cast<PasswordDetailsTableViewController*>(controller());
    [passwords_controller setCredentials:passwords andTitle:nil];
  }

  void CheckEditCellText(NSString* expected_text, int section, int item) {
    TableViewTextEditItem* cell =
        static_cast<TableViewTextEditItem*>(GetTableViewItem(section, item));
    EXPECT_NSEQ(expected_text, cell.textFieldValue);
  }

  void CheckEditCellMultiLineText(NSString* expected_text,
                                  int section,
                                  int item) {
    TableViewMultiLineTextEditItem* cell =
        static_cast<TableViewMultiLineTextEditItem*>(
            GetTableViewItem(section, item));
    EXPECT_NSEQ(expected_text, cell.text);
  }

  void CheckStackedDetailsCellDetails(NSArray<NSString*>* expected_details,
                                      int section,
                                      int item) {
    TableViewStackedDetailsItem* cell_item =
        static_cast<TableViewStackedDetailsItem*>(
            GetTableViewItem(section, item));

    EXPECT_TRUE([expected_details isEqualToArray:cell_item.detailTexts]);
  }

  void SetEditCellText(NSString* text, int section, int item) {
    TableViewTextEditItem* cell =
        static_cast<TableViewTextEditItem*>(GetTableViewItem(section, item));
    cell.textFieldValue = text;
  }

  void SetEditCellMultiLineText(NSString* text, int section, int item) {
    TableViewMultiLineTextEditItem* cell =
        static_cast<TableViewMultiLineTextEditItem*>(
            GetTableViewItem(section, item));
    cell.text = text;
  }

  void CheckDetailItemTextWithId(int expected_detail_text_id,
                                 int section,
                                 int item) {
    SettingsImageDetailTextItem* cell =
        static_cast<SettingsImageDetailTextItem*>(
            GetTableViewItem(section, item));
    EXPECT_NSEQ(l10n_util::GetNSString(expected_detail_text_id),
                cell.detailText);
  }

  FakePasswordDetailsHandler* handler() { return handler_; }
  FakePasswordDetailsDelegate* delegate() { return delegate_; }
  MockReauthenticationModule* reauth() { return reauthentication_module_; }
  FakeSnackbarImplementation* snack_bar() {
    return (FakeSnackbarImplementation*)snack_bar_;
  }

  void CheckCopyWebsites(const std::vector<std::string>& websites,
                         NSString* expected_pasteboard,
                         NSString* expected_snackbar_message) {
    base::HistogramTester histogram_tester;
    SetPassword(websites);

    PasswordDetailsTableViewController* password_details =
        base::apple::ObjCCastStrict<PasswordDetailsTableViewController>(
            controller());

    [password_details tableView:password_details.tableView
        didSelectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];

    [password_details copyPasswordDetailsHelper:PasswordDetailsItemTypeWebsite];

    UIPasteboard* general_pasteboard = [UIPasteboard generalPasteboard];
    EXPECT_NSEQ(expected_pasteboard, general_pasteboard.string);
    EXPECT_NSEQ(expected_snackbar_message, snack_bar().snackbarMessage);
  }

  void SetCredentialType(CredentialType credentialType) {
    credential_type_ = credentialType;
  }

 private:
  base::test::ScopedFeatureList feature_list_;
  id snack_bar_;
  FakePasswordDetailsHandler* handler_ = nil;
  FakePasswordDetailsDelegate* delegate_ = nil;
  MockReauthenticationModule* reauthentication_module_ = nil;
  CredentialType credential_type_ = CredentialTypeRegularPassword;
};

// Tests that password is displayed properly.
TEST_F(PasswordDetailsTableViewControllerTest, TestPassword) {
  SetPassword();
  EXPECT_EQ(1, NumberOfSections());
  EXPECT_EQ(4, NumberOfItemsInSection(0));
  CheckStackedDetailsCellDetails(@[ HTTPWebsite() ], 0, 0);
  CheckEditCellText(Username(), 0, 1);
  CheckEditCellText(kMaskedPassword, 0, 2);
  CheckEditCellMultiLineText(@"note", 0, 3);
}

// Tests that passkey is displayed properly.
TEST_F(PasswordDetailsTableViewControllerTest, TestPasskey) {
  base::Time creation_time = SetPasskey();
  EXPECT_EQ(1, NumberOfSections());
  EXPECT_EQ(4, NumberOfItemsInSection(0));
  CheckStackedDetailsCellDetails(@[ @"https://www.example.com/" ], 0, 0);
  CheckEditCellText(DisplayName(), 0, 1);
  CheckEditCellText(Username(), 0, 2);
  CheckEditCellText(
      l10n_util::GetNSStringF(IDS_IOS_PASSKEY_CREATION_DATE,
                              base::TimeFormatShortDateNumeric(creation_time)),
      0, 3);
}

// Tests that correct metrics is reported after adding a note.
TEST_F(PasswordDetailsTableViewControllerTest, TestAddingPasswordWithNote) {
  base::HistogramTester histogram_tester;

  SetPassword(kExampleCom, kUsername, kPassword, /*note=*/"");
  PasswordDetailsTableViewController* passwordDetails =
      base::apple::ObjCCastStrict<PasswordDetailsTableViewController>(
          controller());
  [passwordDetails editButtonPressed];
  EXPECT_TRUE(passwordDetails.tableView.editing);

  SetEditCellMultiLineText(@"note", 0, 3);
  [passwordDetails editButtonPressed];

  EXPECT_FALSE(passwordDetails.tableView.editing);
  EXPECT_NSEQ(@"note", delegate().credential.note);
  histogram_tester.ExpectUniqueSample(
      "PasswordManager.PasswordNoteActionInSettings2",
      password_manager::metrics_util::PasswordNoteAction::
          kNoteAddedInEditDialog,
      1);
}

// Tests that correct metrics is reported after editing a note.
TEST_F(PasswordDetailsTableViewControllerTest, TestEditingPasswordWithNote) {
  base::HistogramTester histogram_tester;

  SetPassword();
  PasswordDetailsTableViewController* passwordDetails =
      base::apple::ObjCCastStrict<PasswordDetailsTableViewController>(
          controller());
  [passwordDetails editButtonPressed];
  EXPECT_TRUE(passwordDetails.tableView.editing);

  SetEditCellMultiLineText(@"new_note", 0, 3);
  [passwordDetails editButtonPressed];

  EXPECT_FALSE(passwordDetails.tableView.editing);
  EXPECT_NSEQ(@"new_note", delegate().credential.note);
  histogram_tester.ExpectUniqueSample(
      "PasswordManager.PasswordNoteActionInSettings2",
      password_manager::metrics_util::PasswordNoteAction::
          kNoteEditedInEditDialog,
      1);
}

// Tests that correct metrics is reported after editing a password without a
// note change.
TEST_F(PasswordDetailsTableViewControllerTest, TestRemovingPasswordWithNote) {
  base::HistogramTester histogram_tester;

  SetPassword();
  PasswordDetailsTableViewController* passwordDetails =
      base::apple::ObjCCastStrict<PasswordDetailsTableViewController>(
          controller());
  [passwordDetails editButtonPressed];
  EXPECT_TRUE(passwordDetails.tableView.editing);

  SetEditCellMultiLineText(@"", 0, 3);
  [passwordDetails editButtonPressed];

  EXPECT_FALSE(passwordDetails.tableView.editing);
  EXPECT_NSEQ(@"", delegate().credential.note);
  histogram_tester.ExpectUniqueSample(
      "PasswordManager.PasswordNoteActionInSettings2",
      password_manager::metrics_util::PasswordNoteAction::
          kNoteRemovedInEditDialog,
      1);
}

// Tests that correct metrics is reported after removing a note.
TEST_F(PasswordDetailsTableViewControllerTest,
       TestEditingPasswordWithoutNoteChange) {
  base::HistogramTester histogram_tester;

  SetPassword();
  PasswordDetailsTableViewController* passwordDetails =
      base::apple::ObjCCastStrict<PasswordDetailsTableViewController>(
          controller());
  [passwordDetails editButtonPressed];
  EXPECT_TRUE(passwordDetails.tableView.editing);

  SetEditCellText(@"new_password", 0, 2);
  [passwordDetails editButtonPressed];
  [passwordDetails passwordEditingConfirmed];

  EXPECT_FALSE(passwordDetails.tableView.editing);
  histogram_tester.ExpectUniqueSample(
      "PasswordManager.PasswordNoteActionInSettings2",
      password_manager::metrics_util::PasswordNoteAction::kNoteNotChanged, 1);
}

// Tests that compromised password is displayed properly.
TEST_F(PasswordDetailsTableViewControllerTest, TestCompromisedPassword) {
  SetPassword(kExampleCom, kUsername, kPassword, kNote,
              /*is_compromised=*/true);
  EXPECT_EQ(1, NumberOfSections());
  EXPECT_EQ(7, NumberOfItemsInSection(0));
  CheckStackedDetailsCellDetails(@[ HTTPWebsite() ], 0, 0);
  CheckEditCellText(Username(), 0, 1);
  CheckEditCellText(kMaskedPassword, 0, 2);
  CheckEditCellMultiLineText(@"note", 0, 3);

  CheckDetailItemTextWithId(
      IDS_IOS_CHANGE_COMPROMISED_PASSWORD_DESCRIPTION_BRANDED, 0, 4);
  CheckTextCellTextWithId(IDS_IOS_CHANGE_COMPROMISED_PASSWORD, 0, 5);

  CheckTextCellTextWithId(IDS_IOS_DISMISS_WARNING, 0, 6);
}

// Tests that muted compromised password is displayed properly.
TEST_F(PasswordDetailsTableViewControllerTest, TestMutedCompromisedPassword) {
  SetPassword(kExampleCom, kUsername, kPassword, kNote,
              /*is_compromised=*/false, /*is_muted=*/true,
              DetailsContext::kDismissedWarnings);
  EXPECT_EQ(1, NumberOfSections());
  EXPECT_EQ(7, NumberOfItemsInSection(0));
  CheckStackedDetailsCellDetails(@[ HTTPWebsite() ], 0, 0);
  CheckEditCellText(Username(), 0, 1);
  CheckEditCellText(kMaskedPassword, 0, 2);
  CheckEditCellMultiLineText(@"note", 0, 3);

  CheckDetailItemTextWithId(
      IDS_IOS_CHANGE_COMPROMISED_PASSWORD_DESCRIPTION_BRANDED, 0, 4);
  CheckTextCellTextWithId(IDS_IOS_CHANGE_COMPROMISED_PASSWORD, 0, 5);

  CheckTextCellTextWithId(IDS_IOS_RESTORE_WARNING, 0, 6);
}

// Tests the “Change Password on Website” button.
TEST_F(PasswordDetailsTableViewControllerTest, TestChangePasswordOnWebsite) {
  SetPassword(kExampleCom, kUsername, kPassword, kNote,
              /*is_compromised=*/true);
  PasswordDetailsTableViewController* password_details =
      base::apple::ObjCCastStrict<PasswordDetailsTableViewController>(
          controller());

  id applicationCommandsMock = OCMProtocolMock(@protocol(ApplicationCommands));
  password_details.applicationCommandsHandler = applicationCommandsMock;

  TableViewModel* model = password_details.tableViewModel;
  NSIndexPath* indexPath =
      [model indexPathForItemType:PasswordDetailsItemTypeChangePasswordButton];

  OCMExpect([applicationCommandsMock
      closeSettingsUIAndOpenURL:[OCMArg checkWithBlock:^BOOL(id value) {
        // This block verifies that the closeSettingsUIAndOpenURL function is
        // called with a URL argument which matches the initial URL passed to
        // the password form above. Information may have been appended to the
        // URL argument, so we only make sure it includes the initial URL.
        return base::Contains(((OpenNewTabCommand*)value).URL.spec(),
                              kExampleCom);
      }]]);
  [password_details tableView:password_details.tableView
      didSelectRowAtIndexPath:indexPath];
  EXPECT_OCMOCK_VERIFY(applicationCommandsMock);
}

// Tests the “Dismiss Warning” button.
TEST_F(PasswordDetailsTableViewControllerTest, TestDismissWarning) {
  SetPassword(kExampleCom, kUsername, kPassword, kNote,
              /*is_compromised=*/true);
  PasswordDetailsTableViewController* password_details =
      base::apple::ObjCCastStrict<PasswordDetailsTableViewController>(
          controller());

  EXPECT_FALSE(delegate().dismissWarningCalled);

  TableViewModel* model = password_details.tableViewModel;
  NSIndexPath* indexPath =
      [model indexPathForItemType:PasswordDetailsItemTypeDismissWarningButton];
  [password_details tableView:password_details.tableView
      didSelectRowAtIndexPath:indexPath];

  EXPECT_TRUE(delegate().dismissWarningCalled);
}

// Tests the “Restore Warning” button.
TEST_F(PasswordDetailsTableViewControllerTest, TestRestoreWarning) {
  SetPassword(kExampleCom, kUsername, kPassword, kNote,
              /*is_compromised=*/false, /*is_muted=*/true,
              DetailsContext::kDismissedWarnings);
  PasswordDetailsTableViewController* password_details =
      base::apple::ObjCCastStrict<PasswordDetailsTableViewController>(
          controller());

  EXPECT_FALSE(delegate().restoreWarningCalled);

  TableViewModel* model = password_details.tableViewModel;
  NSIndexPath* indexPath =
      [model indexPathForItemType:PasswordDetailsItemTypeRestoreWarningButton];
  [password_details tableView:password_details.tableView
      didSelectRowAtIndexPath:indexPath];

  EXPECT_TRUE(delegate().restoreWarningCalled);
}

// Tests that password is shown/hidden.
TEST_F(PasswordDetailsTableViewControllerTest, TestShowHidePassword) {
  SetPassword();
  NSIndexPath* indexOfPassword;
  CheckEditCellText(kMaskedPassword, 0, 2);
  indexOfPassword = [NSIndexPath indexPathForRow:2 inSection:0];

  TableViewTextEditCell* textFieldCell =
      base::apple::ObjCCastStrict<TableViewTextEditCell>([controller()
                      tableView:controller().tableView
          cellForRowAtIndexPath:indexOfPassword]);
  EXPECT_TRUE(textFieldCell);
  [textFieldCell.identifyingIconButton
      sendActionsForControlEvents:UIControlEventTouchUpInside];

  CheckEditCellText(@"test", 0, 2);

  [textFieldCell.identifyingIconButton
      sendActionsForControlEvents:UIControlEventTouchUpInside];

  CheckEditCellText(kMaskedPassword, 0, 2);
}

// Tests that password was revealed during editing.
TEST_F(PasswordDetailsTableViewControllerTest, TestPasswordShownDuringEditing) {
  SetPassword();

  CheckEditCellText(kMaskedPassword, 0, 2);

  PasswordDetailsTableViewController* password_details =
      base::apple::ObjCCastStrict<PasswordDetailsTableViewController>(
          controller());
  [password_details editButtonPressed];
  EXPECT_TRUE(password_details.tableView.editing);

  CheckEditCellText(@"test", 0, 2);

  [password_details editButtonPressed];
  EXPECT_FALSE(password_details.tableView.editing);
  CheckEditCellText(kMaskedPassword, 0, 2);
}

// Tests that delete button trigger showing password delete dialog.
TEST_F(PasswordDetailsTableViewControllerTest, TestPasswordDelete) {
  SetPassword();

  EXPECT_FALSE(handler().deletionCalled);
  PasswordDetailsTableViewController* password_details =
      base::apple::ObjCCastStrict<PasswordDetailsTableViewController>(
          controller());
  [password_details editButtonPressed];
  [password_details tableView:password_details.tableView
      didSelectRowAtIndexPath:[NSIndexPath indexPathForRow:4 inSection:0]];
  EXPECT_TRUE(handler().deletionCalled);
  EXPECT_FALSE(handler().deletionCalledOnCompromisedPassword);
}

// Tests compromised password deletion trigger showing password delete dialog.
TEST_F(PasswordDetailsTableViewControllerTest, TestCompromisedPasswordDelete) {
  SetPassword(kExampleCom, kUsername, kPassword, kNote,
              /*is_compromised=*/true);

  EXPECT_FALSE(handler().deletionCalled);
  PasswordDetailsTableViewController* password_details =
      base::apple::ObjCCastStrict<PasswordDetailsTableViewController>(
          controller());
  [password_details editButtonPressed];
  [password_details tableView:password_details.tableView
      didSelectRowAtIndexPath:[NSIndexPath
                                  indexPathForRow:NumberOfItemsInSection(0) - 1
                                        inSection:0]];
  EXPECT_TRUE(handler().deletionCalled);
  EXPECT_TRUE(handler().deletionCalledOnCompromisedPassword);
}

// Tests password editing. User confirmed this action.
TEST_F(PasswordDetailsTableViewControllerTest, TestEditPasswordConfirmed) {
  SetPassword();

  PasswordDetailsTableViewController* password_details =
      base::apple::ObjCCastStrict<PasswordDetailsTableViewController>(
          controller());
  [password_details editButtonPressed];
  EXPECT_FALSE(handler().editingCalled);
  EXPECT_FALSE(delegate().credential);
  EXPECT_TRUE(password_details.tableView.editing);

  SetEditCellText(@"new_password", 0, 2);

  [password_details editButtonPressed];
  EXPECT_TRUE(handler().editingCalled);

  [password_details passwordEditingConfirmed];
  EXPECT_TRUE(delegate().credential);

  EXPECT_NSEQ(@"new_password", delegate().credential.password);
  EXPECT_FALSE(password_details.tableView.editing);
}

// Tests password editing. User cancelled this action.
TEST_F(PasswordDetailsTableViewControllerTest, TestEditPasswordCancel) {
  SetPassword();

  PasswordDetailsTableViewController* password_details =
      base::apple::ObjCCastStrict<PasswordDetailsTableViewController>(
          controller());
  [password_details editButtonPressed];
  EXPECT_FALSE(delegate().credential);
  EXPECT_TRUE(password_details.tableView.editing);

  SetEditCellText(@"new_password", 0, 2);

  [password_details editButtonPressed];
  EXPECT_FALSE(delegate().credential);
  EXPECT_TRUE(password_details.tableView.editing);
}

// Tests android compromised credential is displayed without change password
// button.
TEST_F(PasswordDetailsTableViewControllerTest,
       TestAndroidCompromisedCredential) {
  SetPassword(kAndroid, kUsername, kPassword, kNote, /*is_compromised=*/true);

  EXPECT_EQ(1, NumberOfSections());
  EXPECT_EQ(6, NumberOfItemsInSection(0));

  CheckStackedDetailsCellDetails(@[ @"app.my.example.com" ], 0, 0);
  CheckEditCellText(Username(), 0, 1);
  CheckEditCellText(kMaskedPassword, 0, 2);
  CheckEditCellMultiLineText(@"note", 0, 3);

  CheckDetailItemTextWithId(
      IDS_IOS_CHANGE_COMPROMISED_PASSWORD_DESCRIPTION_BRANDED, 0, 4);

  CheckTextCellTextWithId(IDS_IOS_DISMISS_WARNING, 0, 5);
}

// Tests federated credential is shown without password value and editing
// doesn't require reauth.
TEST_F(PasswordDetailsTableViewControllerTest, TestFederatedCredential) {
  SetFederatedPassword();

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

  CheckStackedDetailsCellDetails(@[ HTTPWebsite() ], 0, 0);
  CheckEditCellText(Username(), 0, 1);
  CheckEditCellText(@"www.example.com", 0, 2);

  reauth().expectedResult = ReauthenticationResult::kFailure;
  PasswordDetailsTableViewController* password_details =
      base::apple::ObjCCastStrict<PasswordDetailsTableViewController>(
          controller());
  [password_details editButtonPressed];
  EXPECT_TRUE(password_details.tableView.editing);
}

// Tests blocked website is shown without password and username values and
// editing doesn't require reauth.
TEST_F(PasswordDetailsTableViewControllerTest, TestBlockedOrigin) {
  SetBlockedOrigin();

  EXPECT_EQ(1, NumberOfSections());
  EXPECT_EQ(1, NumberOfItemsInSection(0));

  CheckStackedDetailsCellDetails(@[ HTTPWebsite() ], 0, 0);

  reauth().expectedResult = ReauthenticationResult::kFailure;
  PasswordDetailsTableViewController* password_details =
      base::apple::ObjCCastStrict<PasswordDetailsTableViewController>(
          controller());
  [password_details editButtonPressed];
  EXPECT_TRUE(password_details.tableView.editing);
}

// Tests copy website works as intended.
TEST_F(PasswordDetailsTableViewControllerTest, CopySite) {
  std::vector<std::string> websites = {kExampleCom};
  NSString* expected_pasteboard = HTTPWebsite();
  CheckCopyWebsites(
      websites, expected_pasteboard,
      l10n_util::GetNSString(IDS_IOS_SETTINGS_SITES_WERE_COPIED_MESSAGE));
}

// Tests copy multiple websites works as intended.
TEST_F(PasswordDetailsTableViewControllerTest, CopySites) {
  std::vector<std::string> websites = {kExampleCom, "http://example.com/"};
  CheckCopyWebsites(
      websites, @"http://www.example.com/ http://example.com/",
      l10n_util::GetNSString(IDS_IOS_SETTINGS_SITES_WERE_COPIED_MESSAGE));
}

// Tests copy username works as intended.
TEST_F(PasswordDetailsTableViewControllerTest, CopyUsername) {
  base::HistogramTester histogram_tester;
  SetPassword();
  PasswordDetailsTableViewController* password_details =
      base::apple::ObjCCastStrict<PasswordDetailsTableViewController>(
          controller());

  [password_details tableView:password_details.tableView
      didSelectRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]];
  [password_details copyPasswordDetailsHelper:PasswordDetailsItemTypeUsername];

  UIPasteboard* generalPasteboard = [UIPasteboard generalPasteboard];
  EXPECT_NSEQ(Username(), generalPasteboard.string);
  EXPECT_NSEQ(
      l10n_util::GetNSString(IDS_IOS_SETTINGS_USERNAME_WAS_COPIED_MESSAGE),
      snack_bar().snackbarMessage);

  EXPECT_FALSE(handler().passwordCopiedByUserCalled);
}

// Tests copy password works as intended when reauth was successful.
TEST_F(PasswordDetailsTableViewControllerTest, CopyPasswordSuccess) {
  base::HistogramTester histogram_tester;
  SetPassword();

  PasswordDetailsTableViewController* password_details =
      base::apple::ObjCCastStrict<PasswordDetailsTableViewController>(
          controller());

  [password_details tableView:password_details.tableView
      didSelectRowAtIndexPath:[NSIndexPath indexPathForRow:2 inSection:0]];
  [password_details copyPasswordDetailsHelper:PasswordDetailsItemTypePassword];

  UIPasteboard* generalPasteboard = [UIPasteboard generalPasteboard];
  EXPECT_NSEQ(@"test", generalPasteboard.string);
  EXPECT_NSEQ(
      l10n_util::GetNSString(IDS_IOS_SETTINGS_PASSWORD_WAS_COPIED_MESSAGE),
      snack_bar().snackbarMessage);
}

// Tests error histogram is emitted when we fail copying a field.
TEST_F(PasswordDetailsTableViewControllerTest, CopyDetailsFailedEmitted) {
  base::HistogramTester histogram_tester;

  PasswordDetailsTableViewController* password_details =
      base::apple::ObjCCastStrict<PasswordDetailsTableViewController>(
          controller());
  [password_details copyPasswordDetailsHelper:NSIntegerMax];

  EXPECT_FALSE(handler().passwordCopiedByUserCalled);
}