| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // 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/passwords_table_view_controller.h" |
| |
| #include "base/bind.h" |
| #include "base/compiler_specific.h" |
| #include "base/strings/string_piece.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/bind_test_util.h" |
| #import "base/test/ios/wait_util.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "components/autofill/core/common/password_form.h" |
| #include "components/keyed_service/core/service_access_type.h" |
| #include "components/password_manager/core/browser/mock_bulk_leak_check_service.h" |
| #include "components/password_manager/core/browser/password_manager_test_utils.h" |
| #include "components/password_manager/core/browser/test_password_store.h" |
| #include "components/password_manager/core/common/password_manager_features.h" |
| #include "ios/chrome/browser/main/test_browser.h" |
| #include "ios/chrome/browser/passwords/ios_chrome_bulk_leak_check_service_factory.h" |
| #include "ios/chrome/browser/passwords/ios_chrome_password_check_manager.h" |
| #include "ios/chrome/browser/passwords/ios_chrome_password_store_factory.h" |
| #include "ios/chrome/browser/passwords/password_check_observer_bridge.h" |
| #include "ios/chrome/browser/passwords/save_passwords_consumer.h" |
| #import "ios/chrome/browser/ui/settings/cells/settings_check_item.h" |
| #import "ios/chrome/browser/ui/settings/password/password_details_table_view_controller.h" |
| #import "ios/chrome/browser/ui/settings/password/password_issues_coordinator.h" |
| #import "ios/chrome/browser/ui/settings/password/passwords_consumer.h" |
| #import "ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h" |
| #import "ios/chrome/browser/ui/table_view/cells/table_view_detail_text_item.h" |
| #include "ios/chrome/browser/ui/table_view/chrome_table_view_controller_test.h" |
| #import "ios/chrome/browser/ui/util/uikit_ui_util.h" |
| #import "ios/chrome/common/ui/colors/UIColor+cr_semantic_colors.h" |
| #import "ios/chrome/common/ui/colors/semantic_color_names.h" |
| #include "ios/chrome/grit/ios_chromium_strings.h" |
| #include "ios/chrome/grit/ios_strings.h" |
| #include "ios/web/public/test/web_task_environment.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "testing/gtest_mac.h" |
| #include "testing/platform_test.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/l10n/l10n_util_mac.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| using password_manager::CompromiseType; |
| using password_manager::TestPasswordStore; |
| using password_manager::MockBulkLeakCheckService; |
| using ::testing::Return; |
| |
| // Declaration to conformance to SavePasswordsConsumerDelegate and keep tests in |
| // this file working. |
| @interface PasswordsTableViewController (Test) < |
| UISearchBarDelegate, |
| PasswordIssuesCoordinatorDelegate, |
| PasswordsConsumer> |
| - (void)updateExportPasswordsButton; |
| @end |
| |
| namespace { |
| |
| typedef struct { |
| bool password_check_enabled; |
| } PasswordCheckFeatureStatus; |
| |
| enum PasswordsSections { |
| SavePasswordsSwitch = 0, |
| PasswordCheck, |
| SavedPasswords, |
| Blocked, |
| ExportPasswordsButton, |
| }; |
| |
| class PasswordsTableViewControllerTest |
| : public ChromeTableViewControllerTest, |
| public ::testing::WithParamInterface<PasswordCheckFeatureStatus> { |
| protected: |
| PasswordsTableViewControllerTest() = default; |
| |
| void SetUp() override { |
| // TODO(crbug.com/1096986): Remove parametrized tests once the feature is |
| // enabled. |
| if (GetParam().password_check_enabled) { |
| scoped_feature_list_.InitAndEnableFeature( |
| password_manager::features::kPasswordCheck); |
| } else { |
| scoped_feature_list_.InitAndDisableFeature( |
| password_manager::features::kPasswordCheck); |
| } |
| |
| browser_ = std::make_unique<TestBrowser>(); |
| ChromeTableViewControllerTest::SetUp(); |
| IOSChromePasswordStoreFactory::GetInstance()->SetTestingFactory( |
| browser_->GetBrowserState(), |
| base::BindRepeating( |
| &password_manager::BuildPasswordStore<web::BrowserState, |
| TestPasswordStore>)); |
| |
| IOSChromeBulkLeakCheckServiceFactory::GetInstance() |
| ->SetTestingFactoryAndUse( |
| browser_->GetBrowserState(), |
| base::BindLambdaForTesting([](web::BrowserState*) { |
| return std::unique_ptr<KeyedService>( |
| std::make_unique<MockBulkLeakCheckService>()); |
| })); |
| |
| CreateController(); |
| |
| // Inject some fake passwords to pass the loading state. |
| PasswordsTableViewController* passwords_controller = |
| static_cast<PasswordsTableViewController*>(controller()); |
| [passwords_controller setPasswordsForms:{}]; |
| } |
| |
| int GetSectionIndex(PasswordsSections section) { |
| switch (section) { |
| case SavePasswordsSwitch: |
| case PasswordCheck: |
| return section; |
| case SavedPasswords: |
| return GetParam().password_check_enabled ? 2 : 1; |
| case Blocked: |
| return GetParam().password_check_enabled ? 3 : 2; |
| case ExportPasswordsButton: |
| return GetParam().password_check_enabled ? 3 : 2; |
| } |
| } |
| |
| int SectionsOffset() { return GetParam().password_check_enabled ? 1 : 0; } |
| |
| TestPasswordStore& GetTestStore() { |
| return *static_cast<TestPasswordStore*>( |
| IOSChromePasswordStoreFactory::GetForBrowserState( |
| browser_->GetBrowserState(), ServiceAccessType::EXPLICIT_ACCESS) |
| .get()); |
| } |
| |
| MockBulkLeakCheckService& GetMockPasswordCheckService() { |
| return *static_cast<MockBulkLeakCheckService*>( |
| IOSChromeBulkLeakCheckServiceFactory::GetForBrowserState( |
| browser_->GetBrowserState())); |
| } |
| |
| ChromeTableViewController* InstantiateController() override { |
| return |
| [[PasswordsTableViewController alloc] initWithBrowser:browser_.get()]; |
| } |
| |
| void ChangePasswordCheckState(PasswordCheckUIState state) { |
| PasswordsTableViewController* passwords_controller = |
| static_cast<PasswordsTableViewController*>(controller()); |
| [passwords_controller setPasswordCheckUIState:state]; |
| } |
| |
| // Adds a form to PasswordsTableViewController. |
| void AddPasswordForm(std::unique_ptr<autofill::PasswordForm> form) { |
| if (GetParam().password_check_enabled) { |
| GetTestStore().AddLogin(*form); |
| RunUntilIdle(); |
| } else { |
| PasswordsTableViewController* passwords_controller = |
| static_cast<PasswordsTableViewController*>(controller()); |
| GetTestStore().AddLogin(*form); |
| std::vector<std::unique_ptr<autofill::PasswordForm>> passwords; |
| passwords.push_back(std::move(form)); |
| [passwords_controller setPasswordsForms:std::move(passwords)]; |
| } |
| } |
| |
| // Creates and adds a saved password form. |
| void AddSavedForm1() { |
| auto form = std::make_unique<autofill::PasswordForm>(); |
| form->url = GURL("http://www.example.com/accounts/LoginAuth"); |
| form->action = GURL("http://www.example.com/accounts/Login"); |
| form->username_element = base::ASCIIToUTF16("Email"); |
| form->username_value = base::ASCIIToUTF16("test@egmail.com"); |
| form->password_element = base::ASCIIToUTF16("Passwd"); |
| form->password_value = base::ASCIIToUTF16("test"); |
| form->submit_element = base::ASCIIToUTF16("signIn"); |
| form->signon_realm = "http://www.example.com/"; |
| form->scheme = autofill::PasswordForm::Scheme::kHtml; |
| form->blocked_by_user = false; |
| AddPasswordForm(std::move(form)); |
| } |
| |
| // Creates and adds a saved password form. |
| void AddSavedForm2() { |
| auto form = std::make_unique<autofill::PasswordForm>(); |
| form->url = GURL("http://www.example2.com/accounts/LoginAuth"); |
| form->action = GURL("http://www.example2.com/accounts/Login"); |
| form->username_element = base::ASCIIToUTF16("Email"); |
| form->username_value = base::ASCIIToUTF16("test@egmail.com"); |
| form->password_element = base::ASCIIToUTF16("Passwd"); |
| form->password_value = base::ASCIIToUTF16("test"); |
| form->submit_element = base::ASCIIToUTF16("signIn"); |
| form->signon_realm = "http://www.example2.com/"; |
| form->scheme = autofill::PasswordForm::Scheme::kHtml; |
| form->blocked_by_user = false; |
| AddPasswordForm(std::move(form)); |
| } |
| |
| // Creates and adds a blocked site form to never offer to save |
| // user's password to those sites. |
| void AddBlockedForm1() { |
| auto form = std::make_unique<autofill::PasswordForm>(); |
| form->url = GURL("http://www.secret.com/login"); |
| form->action = GURL("http://www.secret.com/action"); |
| form->username_element = base::ASCIIToUTF16("email"); |
| form->username_value = base::ASCIIToUTF16("test@secret.com"); |
| form->password_element = base::ASCIIToUTF16("password"); |
| form->password_value = base::ASCIIToUTF16("cantsay"); |
| form->submit_element = base::ASCIIToUTF16("signIn"); |
| form->signon_realm = "http://www.secret.com/"; |
| form->scheme = autofill::PasswordForm::Scheme::kHtml; |
| form->blocked_by_user = true; |
| AddPasswordForm(std::move(form)); |
| } |
| |
| // Creates and adds another blocked site form to never offer to save |
| // user's password to those sites. |
| void AddBlockedForm2() { |
| auto form = std::make_unique<autofill::PasswordForm>(); |
| form->url = GURL("http://www.secret2.com/login"); |
| form->action = GURL("http://www.secret2.com/action"); |
| form->username_element = base::ASCIIToUTF16("email"); |
| form->username_value = base::ASCIIToUTF16("test@secret2.com"); |
| form->password_element = base::ASCIIToUTF16("password"); |
| form->password_value = base::ASCIIToUTF16("cantsay"); |
| form->submit_element = base::ASCIIToUTF16("signIn"); |
| form->signon_realm = "http://www.secret2.com/"; |
| form->scheme = autofill::PasswordForm::Scheme::kHtml; |
| form->blocked_by_user = true; |
| AddPasswordForm(std::move(form)); |
| } |
| |
| password_manager::CompromisedCredentials MakeCompromised( |
| base::StringPiece signon_realm, |
| base::StringPiece username) { |
| return { |
| std::string(signon_realm), |
| base::ASCIIToUTF16(username), |
| base::Time::Now(), |
| CompromiseType::kLeaked, |
| }; |
| } |
| |
| void AddCompromisedCredential1() { |
| GetTestStore().AddCompromisedCredentials( |
| MakeCompromised("http://www.example.com/", "test@egmail.com")); |
| RunUntilIdle(); |
| } |
| |
| // Deletes the item at (row, section) and wait util condition returns true or |
| // timeout. |
| bool deleteItemAndWait(int section, int row, ConditionBlock condition) { |
| PasswordsTableViewController* passwords_controller = |
| static_cast<PasswordsTableViewController*>(controller()); |
| [passwords_controller |
| deleteItems:@[ [NSIndexPath indexPathForRow:row inSection:section] ]]; |
| return base::test::ios::WaitUntilConditionOrTimeout( |
| base::test::ios::kWaitForUIElementTimeout, condition); |
| } |
| |
| void CheckDetailItemTextWithPluralIds(int expected_text_id, |
| int expected_detail_text_id, |
| int count, |
| int section, |
| int item) { |
| SettingsCheckItem* cell = |
| static_cast<SettingsCheckItem*>(GetTableViewItem(section, item)); |
| EXPECT_NSEQ(l10n_util::GetNSString(expected_text_id), [cell text]); |
| EXPECT_NSEQ(base::SysUTF16ToNSString(l10n_util::GetPluralStringFUTF16( |
| IDS_IOS_CHECK_PASSWORDS_COMPROMISED_COUNT, count)), |
| [cell detailText]); |
| } |
| |
| void RunUntilIdle() { task_environment_.RunUntilIdle(); } |
| |
| web::WebTaskEnvironment task_environment_; |
| std::unique_ptr<TestBrowser> browser_; |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| // Tests default case has no saved sites and no blocked sites. |
| TEST_P(PasswordsTableViewControllerTest, TestInitialization) { |
| CheckController(); |
| EXPECT_EQ(2 + SectionsOffset(), NumberOfSections()); |
| } |
| |
| // Tests adding one item in saved password section. |
| TEST_P(PasswordsTableViewControllerTest, AddSavedPasswords) { |
| AddSavedForm1(); |
| |
| EXPECT_EQ(3 + SectionsOffset(), NumberOfSections()); |
| EXPECT_EQ(1, NumberOfItemsInSection(GetSectionIndex(SavedPasswords))); |
| } |
| |
| // Tests adding one item in blocked password section. |
| TEST_P(PasswordsTableViewControllerTest, AddBlockedPasswords) { |
| AddBlockedForm1(); |
| |
| EXPECT_EQ(3 + SectionsOffset(), NumberOfSections()); |
| EXPECT_EQ(1, NumberOfItemsInSection(GetSectionIndex(Blocked))); |
| } |
| |
| // Tests adding one item in saved password section, and two items in blocked |
| // password section. |
| TEST_P(PasswordsTableViewControllerTest, AddSavedAndBlocked) { |
| AddSavedForm1(); |
| AddBlockedForm1(); |
| AddBlockedForm2(); |
| |
| // There should be two sections added. |
| EXPECT_EQ(4 + SectionsOffset(), NumberOfSections()); |
| |
| // There should be 1 row in saved password section. |
| EXPECT_EQ(1, NumberOfItemsInSection(GetSectionIndex(SavedPasswords))); |
| // There should be 2 rows in blocked password section. |
| EXPECT_EQ(2, NumberOfItemsInSection(GetSectionIndex(Blocked))); |
| } |
| |
| // Tests the order in which the saved passwords are displayed. |
| TEST_P(PasswordsTableViewControllerTest, TestSavedPasswordsOrder) { |
| AddSavedForm2(); |
| |
| CheckTextCellTextAndDetailText(@"example2.com", @"test@egmail.com", |
| GetSectionIndex(SavedPasswords), 0); |
| |
| AddSavedForm1(); |
| CheckTextCellTextAndDetailText(@"example.com", @"test@egmail.com", |
| GetSectionIndex(SavedPasswords), 0); |
| CheckTextCellTextAndDetailText(@"example2.com", @"test@egmail.com", |
| GetSectionIndex(SavedPasswords), 1); |
| } |
| |
| // Tests the order in which the blocked passwords are displayed. |
| TEST_P(PasswordsTableViewControllerTest, TestBlockedPasswordsOrder) { |
| AddBlockedForm2(); |
| CheckTextCellText(@"secret2.com", GetSectionIndex(SavedPasswords), 0); |
| |
| AddBlockedForm1(); |
| CheckTextCellText(@"secret.com", GetSectionIndex(SavedPasswords), 0); |
| CheckTextCellText(@"secret2.com", GetSectionIndex(SavedPasswords), 1); |
| } |
| |
| // Tests displaying passwords in the saved passwords section when there are |
| // duplicates in the password store. |
| TEST_P(PasswordsTableViewControllerTest, AddSavedDuplicates) { |
| AddSavedForm1(); |
| AddSavedForm1(); |
| |
| EXPECT_EQ(3 + SectionsOffset(), NumberOfSections()); |
| EXPECT_EQ(1, NumberOfItemsInSection(GetSectionIndex(SavedPasswords))); |
| } |
| |
| // Tests displaying passwords in the blocked passwords section when there |
| // are duplicates in the password store. |
| TEST_P(PasswordsTableViewControllerTest, AddBlockedDuplicates) { |
| AddBlockedForm1(); |
| AddBlockedForm1(); |
| |
| EXPECT_EQ(3 + SectionsOffset(), NumberOfSections()); |
| EXPECT_EQ(1, NumberOfItemsInSection(GetSectionIndex(SavedPasswords))); |
| } |
| |
| // Tests deleting items from saved passwords and blocked passwords sections. |
| TEST_P(PasswordsTableViewControllerTest, DeleteItems) { |
| AddSavedForm1(); |
| AddBlockedForm1(); |
| AddBlockedForm2(); |
| |
| // Delete item in save passwords section. |
| ASSERT_TRUE(deleteItemAndWait(GetSectionIndex(SavedPasswords), 0, ^{ |
| return NumberOfSections() == (3 + SectionsOffset()); |
| })); |
| // Section 2 should now be the blocked passwords section, and should still |
| // have both its items. |
| EXPECT_EQ(2, NumberOfItemsInSection(GetSectionIndex(SavedPasswords))); |
| |
| // Delete item in blocked passwords section. |
| ASSERT_TRUE(deleteItemAndWait(GetSectionIndex(SavedPasswords), 0, ^{ |
| return NumberOfItemsInSection(GetSectionIndex(SavedPasswords)) == 1; |
| })); |
| // There should be no password sections remaining and no search bar. |
| EXPECT_TRUE(deleteItemAndWait(GetSectionIndex(SavedPasswords), 0, ^{ |
| return NumberOfSections() == (2 + +SectionsOffset()); |
| })); |
| } |
| |
| // Tests deleting items from saved passwords and blocked passwords sections |
| // when there are duplicates in the store. |
| TEST_P(PasswordsTableViewControllerTest, DeleteItemsWithDuplicates) { |
| AddSavedForm1(); |
| AddSavedForm1(); |
| AddBlockedForm1(); |
| AddBlockedForm1(); |
| AddBlockedForm2(); |
| |
| // Delete item in save passwords section. |
| ASSERT_TRUE(deleteItemAndWait(GetSectionIndex(SavedPasswords), 0, ^{ |
| return NumberOfSections() == (3 + SectionsOffset()); |
| })); |
| // Section 2 should now be the blocked passwords section, and should still |
| // have both its items. |
| EXPECT_EQ(2, NumberOfItemsInSection(GetSectionIndex(Blocked) - 1)); |
| |
| // Delete item in blocked passwords section. |
| ASSERT_TRUE(deleteItemAndWait(GetSectionIndex(Blocked) - 1, 0, ^{ |
| return NumberOfItemsInSection(GetSectionIndex(Blocked) - 1) == 1; |
| })); |
| // There should be no password sections remaining and no search bar. |
| EXPECT_TRUE(deleteItemAndWait(GetSectionIndex(Blocked) - 1, 0, ^{ |
| return NumberOfSections() == (2 + SectionsOffset()); |
| })); |
| } |
| |
| TEST_P(PasswordsTableViewControllerTest, |
| TestExportButtonDisabledNoSavedPasswords) { |
| PasswordsTableViewController* passwords_controller = |
| static_cast<PasswordsTableViewController*>(controller()); |
| [passwords_controller updateExportPasswordsButton]; |
| |
| TableViewDetailTextItem* exportButton = |
| GetTableViewItem(GetSectionIndex(SavedPasswords), 0); |
| CheckTextCellTextWithId(IDS_IOS_EXPORT_PASSWORDS, |
| GetSectionIndex(SavedPasswords), 0); |
| |
| EXPECT_NSEQ(UIColor.cr_labelColor, exportButton.textColor); |
| EXPECT_TRUE(exportButton.accessibilityTraits & |
| UIAccessibilityTraitNotEnabled); |
| |
| // Add blocked form. |
| AddBlockedForm1(); |
| // The export button should still be disabled as exporting blocked forms |
| // is not currently supported. |
| EXPECT_NSEQ(UIColor.cr_labelColor, exportButton.textColor); |
| EXPECT_TRUE(exportButton.accessibilityTraits & |
| UIAccessibilityTraitNotEnabled); |
| } |
| |
| TEST_P(PasswordsTableViewControllerTest, |
| TestExportButtonEnabledWithSavedPasswords) { |
| PasswordsTableViewController* passwords_controller = |
| static_cast<PasswordsTableViewController*>(controller()); |
| AddSavedForm1(); |
| [passwords_controller updateExportPasswordsButton]; |
| |
| TableViewDetailTextItem* exportButton = |
| GetTableViewItem(GetSectionIndex(ExportPasswordsButton), 0); |
| |
| CheckTextCellTextWithId(IDS_IOS_EXPORT_PASSWORDS, |
| GetSectionIndex(ExportPasswordsButton), 0); |
| |
| EXPECT_NSEQ([UIColor colorNamed:kBlueColor], exportButton.textColor); |
| EXPECT_FALSE(exportButton.accessibilityTraits & |
| UIAccessibilityTraitNotEnabled); |
| } |
| |
| // Tests that the "Export Passwords..." button is greyed out in edit mode. |
| TEST_P(PasswordsTableViewControllerTest, TestExportButtonDisabledEditMode) { |
| PasswordsTableViewController* passwords_controller = |
| static_cast<PasswordsTableViewController*>(controller()); |
| AddSavedForm1(); |
| [passwords_controller updateExportPasswordsButton]; |
| |
| TableViewDetailTextItem* exportButton = |
| GetTableViewItem(GetSectionIndex(ExportPasswordsButton), 0); |
| CheckTextCellTextWithId(IDS_IOS_EXPORT_PASSWORDS, |
| GetSectionIndex(ExportPasswordsButton), 0); |
| |
| [passwords_controller setEditing:YES animated:NO]; |
| |
| EXPECT_NSEQ(UIColor.cr_labelColor, exportButton.textColor); |
| EXPECT_TRUE(exportButton.accessibilityTraits & |
| UIAccessibilityTraitNotEnabled); |
| } |
| |
| // Tests that the "Export Passwords..." button is enabled after exiting |
| // edit mode. |
| TEST_P(PasswordsTableViewControllerTest, |
| TestExportButtonEnabledWhenEdittingFinished) { |
| PasswordsTableViewController* passwords_controller = |
| static_cast<PasswordsTableViewController*>(controller()); |
| AddSavedForm1(); |
| [passwords_controller updateExportPasswordsButton]; |
| |
| TableViewDetailTextItem* exportButton = |
| GetTableViewItem(GetSectionIndex(ExportPasswordsButton), 0); |
| CheckTextCellTextWithId(IDS_IOS_EXPORT_PASSWORDS, |
| GetSectionIndex(ExportPasswordsButton), 0); |
| |
| [passwords_controller setEditing:YES animated:NO]; |
| [passwords_controller setEditing:NO animated:NO]; |
| |
| EXPECT_NSEQ([UIColor colorNamed:kBlueColor], exportButton.textColor); |
| EXPECT_FALSE(exportButton.accessibilityTraits & |
| UIAccessibilityTraitNotEnabled); |
| } |
| |
| TEST_P(PasswordsTableViewControllerTest, PropagateDeletionToStore) { |
| PasswordsTableViewController* passwords_controller = |
| static_cast<PasswordsTableViewController*>(controller()); |
| autofill::PasswordForm form; |
| form.url = GURL("http://www.example.com/accounts/LoginAuth"); |
| form.action = GURL("http://www.example.com/accounts/Login"); |
| form.username_element = base::ASCIIToUTF16("Email"); |
| form.username_value = base::ASCIIToUTF16("test@egmail.com"); |
| form.password_element = base::ASCIIToUTF16("Passwd"); |
| form.password_value = base::ASCIIToUTF16("test"); |
| form.submit_element = base::ASCIIToUTF16("signIn"); |
| form.signon_realm = "http://www.example.com/"; |
| form.scheme = autofill::PasswordForm::Scheme::kHtml; |
| form.blocked_by_user = false; |
| |
| AddPasswordForm(std::make_unique<autofill::PasswordForm>(form)); |
| |
| if (GetParam().password_check_enabled) { |
| autofill::PasswordForm formFromStore = |
| GetTestStore().stored_passwords().at("http://www.example.com/")[0]; |
| [passwords_controller passwordDetailsTableViewController:nil |
| deletePassword:formFromStore]; |
| RunUntilIdle(); |
| } else { |
| [passwords_controller passwordDetailsTableViewController:nil |
| deletePassword:form]; |
| } |
| } |
| |
| // Tests filtering of items. |
| TEST_P(PasswordsTableViewControllerTest, FilterItems) { |
| AddSavedForm1(); |
| AddSavedForm2(); |
| AddBlockedForm1(); |
| AddBlockedForm2(); |
| |
| EXPECT_EQ(4 + SectionsOffset(), NumberOfSections()); |
| |
| PasswordsTableViewController* passwords_controller = |
| static_cast<PasswordsTableViewController*>(controller()); |
| UISearchBar* bar = |
| passwords_controller.navigationItem.searchController.searchBar; |
| |
| // Force the initial data to be rendered into view first, before doing any |
| // new filtering (avoids mismatch when reloadSections is called). |
| [passwords_controller searchBar:bar textDidChange:@""]; |
| |
| // Search item in save passwords section. |
| [passwords_controller searchBar:bar textDidChange:@"example.com"]; |
| // Only one item in saved passwords should remain. |
| EXPECT_EQ(1, NumberOfItemsInSection(GetSectionIndex(SavedPasswords))); |
| EXPECT_EQ(0, NumberOfItemsInSection(GetSectionIndex(Blocked))); |
| CheckTextCellTextAndDetailText(@"example.com", @"test@egmail.com", |
| GetSectionIndex(SavedPasswords), 0); |
| |
| [passwords_controller searchBar:bar textDidChange:@"test@egmail.com"]; |
| // Only two items in saved passwords should remain. |
| EXPECT_EQ(2, NumberOfItemsInSection(GetSectionIndex(SavedPasswords))); |
| EXPECT_EQ(0, NumberOfItemsInSection(GetSectionIndex(Blocked))); |
| CheckTextCellTextAndDetailText(@"example.com", @"test@egmail.com", |
| GetSectionIndex(SavedPasswords), 0); |
| CheckTextCellTextAndDetailText(@"example2.com", @"test@egmail.com", |
| GetSectionIndex(SavedPasswords), 1); |
| |
| [passwords_controller searchBar:bar textDidChange:@"secret"]; |
| // Only two blocked items should remain. |
| EXPECT_EQ(0, NumberOfItemsInSection(GetSectionIndex(SavedPasswords))); |
| EXPECT_EQ(2, NumberOfItemsInSection(GetSectionIndex(Blocked))); |
| CheckTextCellText(@"secret.com", GetSectionIndex(Blocked), 0); |
| CheckTextCellText(@"secret2.com", GetSectionIndex(Blocked), 1); |
| |
| [passwords_controller searchBar:bar textDidChange:@""]; |
| // All items should be back. |
| EXPECT_EQ(2, NumberOfItemsInSection(GetSectionIndex(SavedPasswords))); |
| EXPECT_EQ(2, NumberOfItemsInSection(GetSectionIndex(Blocked))); |
| } |
| |
| // Test verifies disabled state of password check cell. |
| TEST_P(PasswordsTableViewControllerTest, PasswordCheckStateDisabled) { |
| if (!GetParam().password_check_enabled) |
| return; |
| ChangePasswordCheckState(PasswordCheckStateDisabled); |
| |
| CheckDetailItemTextWithIds(IDS_IOS_CHECK_PASSWORDS, |
| IDS_IOS_CHECK_PASSWORDS_DESCRIPTION, |
| GetSectionIndex(PasswordCheck), 0); |
| SettingsCheckItem* checkPassword = |
| GetTableViewItem(GetSectionIndex(PasswordCheck), 0); |
| EXPECT_FALSE(checkPassword.enabled); |
| EXPECT_TRUE(checkPassword.indicatorHidden); |
| EXPECT_FALSE(checkPassword.trailingImage); |
| } |
| |
| // Test verifies default state of password check cell. |
| TEST_P(PasswordsTableViewControllerTest, PasswordCheckStateDefault) { |
| if (!GetParam().password_check_enabled) |
| return; |
| |
| ChangePasswordCheckState(PasswordCheckStateDefault); |
| |
| CheckTextCellTextWithId(IDS_IOS_CHECK_PASSWORDS_NOW_BUTTON, |
| GetSectionIndex(PasswordCheck), 1); |
| CheckDetailItemTextWithIds(IDS_IOS_CHECK_PASSWORDS, |
| IDS_IOS_CHECK_PASSWORDS_DESCRIPTION, |
| GetSectionIndex(PasswordCheck), 0); |
| SettingsCheckItem* checkPassword = |
| GetTableViewItem(GetSectionIndex(PasswordCheck), 0); |
| EXPECT_TRUE(checkPassword.enabled); |
| EXPECT_TRUE(checkPassword.indicatorHidden); |
| EXPECT_FALSE(checkPassword.trailingImage); |
| } |
| |
| // Test verifies safe state of password check cell. |
| TEST_P(PasswordsTableViewControllerTest, PasswordCheckStateSafe) { |
| if (!GetParam().password_check_enabled) |
| return; |
| |
| ChangePasswordCheckState(PasswordCheckStateSafe); |
| |
| CheckTextCellTextWithId(IDS_IOS_CHECK_PASSWORDS_NOW_BUTTON, |
| GetSectionIndex(PasswordCheck), 1); |
| CheckDetailItemTextWithPluralIds(IDS_IOS_CHECK_PASSWORDS, |
| IDS_IOS_CHECK_PASSWORDS_COMPROMISED_COUNT, 0, |
| GetSectionIndex(PasswordCheck), 0); |
| SettingsCheckItem* checkPassword = |
| GetTableViewItem(GetSectionIndex(PasswordCheck), 0); |
| EXPECT_TRUE(checkPassword.enabled); |
| EXPECT_TRUE(checkPassword.indicatorHidden); |
| EXPECT_TRUE(checkPassword.trailingImage); |
| } |
| |
| // Test verifies unsafe state of password check cell. |
| TEST_P(PasswordsTableViewControllerTest, PasswordCheckStateUnSafe) { |
| if (!GetParam().password_check_enabled) |
| return; |
| AddSavedForm1(); |
| AddCompromisedCredential1(); |
| ChangePasswordCheckState(PasswordCheckStateUnSafe); |
| |
| CheckTextCellTextWithId(IDS_IOS_CHECK_PASSWORDS_NOW_BUTTON, |
| GetSectionIndex(PasswordCheck), 1); |
| CheckDetailItemTextWithPluralIds(IDS_IOS_CHECK_PASSWORDS, |
| IDS_IOS_CHECK_PASSWORDS_COMPROMISED_COUNT, 1, |
| GetSectionIndex(PasswordCheck), 0); |
| SettingsCheckItem* checkPassword = |
| GetTableViewItem(GetSectionIndex(PasswordCheck), 0); |
| EXPECT_TRUE(checkPassword.enabled); |
| EXPECT_TRUE(checkPassword.indicatorHidden); |
| EXPECT_TRUE(checkPassword.trailingImage); |
| } |
| |
| // Test verifies running state of password check cell. |
| TEST_P(PasswordsTableViewControllerTest, PasswordCheckStateRunning) { |
| if (!GetParam().password_check_enabled) |
| return; |
| |
| ChangePasswordCheckState(PasswordCheckStateRunning); |
| |
| CheckTextCellTextWithId(IDS_IOS_CANCEL_PASSWORD_CHECK_BUTTON, |
| GetSectionIndex(PasswordCheck), 1); |
| CheckDetailItemTextWithIds(IDS_IOS_CHECK_PASSWORDS, |
| IDS_IOS_CHECK_PASSWORDS_DESCRIPTION, |
| GetSectionIndex(PasswordCheck), 0); |
| SettingsCheckItem* checkPassword = |
| GetTableViewItem(GetSectionIndex(PasswordCheck), 0); |
| EXPECT_TRUE(checkPassword.enabled); |
| EXPECT_FALSE(checkPassword.indicatorHidden); |
| EXPECT_FALSE(checkPassword.trailingImage); |
| } |
| |
| // Test verifies error state of password check cell. |
| TEST_P(PasswordsTableViewControllerTest, PasswordCheckStateError) { |
| if (!GetParam().password_check_enabled) |
| return; |
| |
| ChangePasswordCheckState(PasswordCheckStateError); |
| |
| CheckTextCellTextWithId(IDS_IOS_CHECK_PASSWORDS_NOW_BUTTON, |
| GetSectionIndex(PasswordCheck), 1); |
| CheckDetailItemTextWithIds(IDS_IOS_CHECK_PASSWORDS, |
| IDS_IOS_PASSWORD_CHECK_ERROR, |
| GetSectionIndex(PasswordCheck), 0); |
| SettingsCheckItem* checkPassword = |
| GetTableViewItem(GetSectionIndex(PasswordCheck), 0); |
| EXPECT_TRUE(checkPassword.enabled); |
| EXPECT_TRUE(checkPassword.indicatorHidden); |
| EXPECT_FALSE(checkPassword.trailingImage); |
| EXPECT_FALSE(checkPassword.infoButtonHidden); |
| } |
| |
| // Test verifies tapping start with no saved passwords has no effect. |
| TEST_P(PasswordsTableViewControllerTest, DisabledPasswordCheck) { |
| if (!GetParam().password_check_enabled) |
| return; |
| |
| PasswordsTableViewController* passwords_controller = |
| static_cast<PasswordsTableViewController*>(controller()); |
| |
| EXPECT_CALL(GetMockPasswordCheckService(), CheckUsernamePasswordPairs) |
| .Times(0); |
| EXPECT_CALL(GetMockPasswordCheckService(), Cancel).Times(0); |
| |
| [passwords_controller tableView:passwords_controller.tableView |
| didSelectRowAtIndexPath:[NSIndexPath |
| indexPathForItem:1 |
| inSection:GetSectionIndex( |
| PasswordCheck)]]; |
| } |
| |
| // Test verifies tapping start triggers correct function in service. |
| TEST_P(PasswordsTableViewControllerTest, StartPasswordCheck) { |
| if (!GetParam().password_check_enabled) |
| return; |
| AddSavedForm1(); |
| RunUntilIdle(); |
| |
| PasswordsTableViewController* passwords_controller = |
| static_cast<PasswordsTableViewController*>(controller()); |
| |
| EXPECT_CALL(GetMockPasswordCheckService(), CheckUsernamePasswordPairs); |
| |
| [passwords_controller tableView:passwords_controller.tableView |
| didSelectRowAtIndexPath:[NSIndexPath |
| indexPathForItem:1 |
| inSection:GetSectionIndex( |
| PasswordCheck)]]; |
| } |
| |
| // Test verifies tapping cancel triggers correct function in service. |
| TEST_P(PasswordsTableViewControllerTest, StopPasswordCheck) { |
| if (!GetParam().password_check_enabled) |
| return; |
| AddSavedForm1(); |
| RunUntilIdle(); |
| |
| PasswordsTableViewController* passwords_controller = |
| static_cast<PasswordsTableViewController*>(controller()); |
| |
| ON_CALL(GetMockPasswordCheckService(), GetState()) |
| .WillByDefault(Return( |
| password_manager::BulkLeakCheckServiceInterface::State::kRunning)); |
| EXPECT_CALL(GetMockPasswordCheckService(), Cancel); |
| |
| [passwords_controller tableView:passwords_controller.tableView |
| didSelectRowAtIndexPath:[NSIndexPath |
| indexPathForItem:1 |
| inSection:GetSectionIndex( |
| PasswordCheck)]]; |
| } |
| |
| // Test verifies changes to the password store are reflected on UI. |
| TEST_P(PasswordsTableViewControllerTest, PasswordStoreListener) { |
| if (!GetParam().password_check_enabled) |
| return; |
| |
| AddSavedForm1(); |
| EXPECT_EQ(1, NumberOfItemsInSection(GetSectionIndex(SavedPasswords))); |
| AddSavedForm2(); |
| EXPECT_EQ(2, NumberOfItemsInSection(GetSectionIndex(SavedPasswords))); |
| } |
| |
| // Test verifies Passwords View Controller handles deletion of passwords. |
| TEST_P(PasswordsTableViewControllerTest, PasswordIssuesDeletion) { |
| if (!GetParam().password_check_enabled) |
| return; |
| AddSavedForm1(); |
| AddSavedForm2(); |
| EXPECT_EQ(2, NumberOfItemsInSection(GetSectionIndex(SavedPasswords))); |
| |
| PasswordsTableViewController* passwords_controller = |
| static_cast<PasswordsTableViewController*>(controller()); |
| |
| auto password = |
| GetTestStore().stored_passwords().at("http://www.example.com/").at(0); |
| EXPECT_TRUE([passwords_controller willHandlePasswordDeletion:password]); |
| EXPECT_EQ(1, NumberOfItemsInSection(GetSectionIndex(SavedPasswords))); |
| } |
| |
| const std::vector<PasswordCheckFeatureStatus> kPasswordCheckFeatureStatusCases{ |
| // Password check disabled |
| {FALSE}, |
| // Password check enabled |
| {TRUE}}; |
| |
| INSTANTIATE_TEST_SUITE_P(PasswordCheckDisabledAndEnabled, |
| PasswordsTableViewControllerTest, |
| ::testing::ValuesIn(kPasswordCheckFeatureStatusCases)); |
| |
| } // namespace |