chromium/ios/chrome/browser/ui/settings/safety_check/safety_check_egtest.mm

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "base/strings/sys_string_conversions.h"
#import "base/time/time.h"
#import "ios/chrome/browser/signin/model/fake_system_identity.h"
#import "ios/chrome/browser/ui/authentication/signin_earl_grey.h"
#import "ios/chrome/browser/ui/authentication/signin_earl_grey_ui_test_util.h"
#import "ios/chrome/browser/ui/settings/password/password_checkup/password_checkup_constants.h"
#import "ios/chrome/browser/ui/settings/password/password_manager_egtest_utils.h"
#import "ios/chrome/browser/ui/settings/password/password_settings_app_interface.h"
#import "ios/chrome/browser/ui/settings/safety_check/safety_check_constants.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey_ui.h"
#import "ios/chrome/test/earl_grey/chrome_matchers.h"
#import "ios/chrome/test/earl_grey/chrome_test_case.h"
#import "ios/testing/earl_grey/earl_grey_test.h"
#import "ui/base/l10n/l10n_util.h"

using password_manager_test_utils::SaveCompromisedPasswordFormToProfileStore;

namespace {

// Matcher for the compromised issues row in the Safety Check module.
// - issue_count: the expected number of issues displayed in the row.
id<GREYMatcher> CompromisedIssuesMatcher(int issue_count) {
  return grey_accessibilityLabel([NSString
      stringWithFormat:@"%@, %@", @"Passwords",
                       base::SysUTF16ToNSString(
                           l10n_util::GetPluralStringFUTF16(
                               IDS_IOS_PASSWORD_CHECKUP_COMPROMISED_COUNT,
                               issue_count))]);
}

// Matcher for the Password Checkup table view.
id<GREYMatcher> PasswordCheckupTableViewMatcher() {
  return grey_accessibilityID(password_manager::kPasswordCheckupTableViewId);
}

// Helper for verifying that the Password Checkup UI is visible.
void VerifyPasswordCheckupIsVisible() {
  [[EarlGrey selectElementWithMatcher:PasswordCheckupTableViewMatcher()]
      assertWithMatcher:grey_sufficientlyVisible()];
}

// Helper for verifying that the Password Checkup UI is not visible.
void VerifyPasswordCheckupIsNotVisible() {
  [[EarlGrey selectElementWithMatcher:PasswordCheckupTableViewMatcher()]
      assertWithMatcher:grey_notVisible()];
}

// Helper for verifying that the Safety Check UI is visible.
void VerifySafetyCheckIsVisible() {
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::SafetyCheckTableViewMatcher()]
      assertWithMatcher:grey_sufficientlyVisible()];
}

// Opens the Safety Check Module.
void OpenSafetyCheck() {
  [ChromeEarlGreyUI openSettingsMenu];
  [ChromeEarlGreyUI
      tapSettingsMenuButton:chrome_test_util::SettingsMenuSafetyCheckButton()];

  VerifySafetyCheckIsVisible();
}

// Helper for tapping the Check Now button in the Safety Check module.
void TapCheckNowButton() {
  [[EarlGrey
      selectElementWithMatcher:grey_accessibilityID(
                                   kSafetyCheckCheckNowButtonAccessibilityID)]
      performAction:grey_tap()];
}

// Waits for the password check to be done and Taps the compromised issues row
// in the Safety Check module.
void TapCompromisedIssues(int issue_count) {
  [ChromeEarlGrey
      waitForUIElementToAppearWithMatcher:CompromisedIssuesMatcher(issue_count)
                                  timeout:base::Seconds(10)];

  [[EarlGrey selectElementWithMatcher:CompromisedIssuesMatcher(issue_count)]
      performAction:grey_tap()];
}

// Opens the Password Checkup UI from the Safety Check module.
// Requires only one saved compromised credential in the Password Store.
void OpenPasswordCheckup() {
  OpenSafetyCheck();

  TapCheckNowButton();

  TapCompromisedIssues(/*issue_count=*/1);
}

// Cleans up the last password check timestamp from User Defaults.
void ResetLastPasswordCheckTimestamp() {
  [ChromeEarlGrey removeUserDefaultsObjectForKey:kTimestampOfLastIssueFoundKey];
}

}  // namespace

// Test case for the Safety Check module.
@interface SafetyCheckTestCase : ChromeTestCase
@end

@implementation SafetyCheckTestCase

- (void)setUp {
  [super setUp];

  // Set the FakeBulkLeakCheckService to return the idle state.
  [PasswordSettingsAppInterface
      setFakeBulkLeakCheckBufferedState:
          password_manager::BulkLeakCheckServiceInterface::State::kIdle];

  FakeSystemIdentity* fakeIdentity = [FakeSystemIdentity fakeIdentity1];
  [SigninEarlGrey signinWithFakeIdentity:fakeIdentity];

  // Mock local authentication for opening Password Checkup.
  [PasswordSettingsAppInterface setUpMockReauthenticationModule];
  [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
                                    ReauthenticationResult::kSuccess];

  ResetLastPasswordCheckTimestamp();
}

- (void)tearDown {
  [PasswordSettingsAppInterface removeMockReauthenticationModule];
  [super tearDown];

  [PasswordSettingsAppInterface
      setFakeBulkLeakCheckBufferedState:
          password_manager::BulkLeakCheckServiceInterface::State::kIdle];

  ResetLastPasswordCheckTimestamp();
}

// Validates that the Safety Check module can be opened from the Settings UI.
- (void)testOpenSafetyCheck {
  OpenSafetyCheck();
}

// Opens the Password Checkup UI from the Safety Check module.
- (void)testOpenPasswordCheckup {
  SaveCompromisedPasswordFormToProfileStore();

  OpenPasswordCheckup();

  VerifyPasswordCheckupIsVisible();
}

// Opens the Password Checkup UI from the Safety Check module and fails local
// authentication. Validates that the Password Checkup content is not revealed
// and it is dismissed after the failed authentication.
- (void)testOpenPasswordCheckupWithFailedAuth {
  SaveCompromisedPasswordFormToProfileStore();

  [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
                                    ReauthenticationResult::kFailure];
  [PasswordSettingsAppInterface
      mockReauthenticationModuleShouldReturnSynchronously:NO];

  OpenPasswordCheckup();

  VerifyPasswordCheckupIsNotVisible();

  [PasswordSettingsAppInterface mockReauthenticationModuleReturnMockedResult];

  // Password checkup should be dismissed and Safety Check module should be
  // visible again.
  VerifyPasswordCheckupIsNotVisible();
  VerifySafetyCheckIsVisible();
}

@end