chromium/ios/chrome/browser/ui/settings/safety_check/safety_check_mediator_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/safety_check/safety_check_mediator.h"

#import <memory>

#import "base/memory/ptr_util.h"
#import "base/memory/raw_ptr.h"
#import "base/strings/string_util.h"
#import "base/strings/sys_string_conversions.h"
#import "base/strings/utf_string_conversions.h"
#import "base/test/bind.h"
#import "base/test/ios/wait_util.h"
#import "base/test/metrics/histogram_tester.h"
#import "base/test/scoped_feature_list.h"
#import "components/affiliations/core/browser/fake_affiliation_service.h"
#import "components/keyed_service/core/service_access_type.h"
#import "components/password_manager/core/browser/password_form.h"
#import "components/password_manager/core/browser/password_manager_test_utils.h"
#import "components/password_manager/core/browser/password_store/test_password_store.h"
#import "components/password_manager/core/browser/ui/password_check_referrer.h"
#import "components/prefs/pref_service.h"
#import "components/prefs/testing_pref_service.h"
#import "components/safe_browsing/core/common/features.h"
#import "components/safe_browsing/core/common/safe_browsing_prefs.h"
#import "components/safety_check/safety_check.h"
#import "components/strings/grit/components_strings.h"
#import "components/sync/test/mock_sync_service.h"
#import "components/sync_preferences/pref_service_mock_factory.h"
#import "ios/chrome/browser/affiliations/model/ios_chrome_affiliation_service_factory.h"
#import "ios/chrome/browser/passwords/model/ios_chrome_password_check_manager.h"
#import "ios/chrome/browser/passwords/model/ios_chrome_password_check_manager_factory.h"
#import "ios/chrome/browser/passwords/model/ios_chrome_profile_password_store_factory.h"
#import "ios/chrome/browser/passwords/model/password_check_observer_bridge.h"
#import "ios/chrome/browser/shared/model/browser/test/test_browser.h"
#import "ios/chrome/browser/shared/model/prefs/pref_backed_boolean.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_text_item.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/fake_authentication_service_delegate.h"
#import "ios/chrome/browser/sync/model/sync_service_factory.h"
#import "ios/chrome/browser/ui/settings/cells/settings_check_item.h"
#import "ios/chrome/browser/ui/settings/safety_check/safety_check_constants.h"
#import "ios/chrome/browser/ui/settings/safety_check/safety_check_consumer.h"
#import "ios/chrome/browser/ui/settings/safety_check/safety_check_mediator+Testing.h"
#import "ios/chrome/browser/upgrade/model/upgrade_constants.h"
#import "ios/chrome/browser/upgrade/model/upgrade_recommended_details.h"
#import "ios/chrome/common/string_util.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/grit/ios_branded_strings.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/chrome/test/ios_chrome_scoped_testing_local_state.h"
#import "ios/chrome/test/testing_application_context.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 "testing/gtest_mac.h"
#import "testing/platform_test.h"
#import "ui/base/l10n/l10n_util.h"
#import "ui/base/l10n/time_format.h"

namespace {

using l10n_util::GetNSString;
using password_manager::InsecureType;
using password_manager::TestPasswordStore;

typedef NS_ENUM(NSInteger, SafetyCheckItemType) {
  // CheckTypes section.
  UpdateItemType = kItemTypeEnumZero,
  PasswordItemType,
  SafeBrowsingItemType,
  HeaderItem,
  // CheckStart section.
  CheckStartItemType,
  TimestampFooterItem,
};

// The size of trailing symbol icons.
NSInteger kTrailingSymbolImagePointSize = 22;

// Registers account preference that will be used for Safe Browsing.
PrefService* SetPrefService() {
  TestingPrefServiceSimple* prefs = new TestingPrefServiceSimple();
  PrefRegistrySimple* registry = prefs->registry();
  registry->RegisterBooleanPref(prefs::kSafeBrowsingEnabled, true);
  registry->RegisterBooleanPref(prefs::kSafeBrowsingEnhanced, true);
  return prefs;
}

// The image when the state is safe.
UIImage* SafeImage() {
  return DefaultSymbolTemplateWithPointSize(kCheckmarkCircleFillSymbol,
                                            kTrailingSymbolImagePointSize);
}

// The image when the state is unsafe.
UIImage* UnsafeImage() {
  return DefaultSymbolTemplateWithPointSize(kErrorCircleFillSymbol,
                                            kTrailingSymbolImagePointSize);
}

// The color when the state is safe.
UIColor* GreenColor() {
  return [UIColor colorNamed:kGreen500Color];
}

// The color when the state is unsafe.
UIColor* RedColor() {
  return [UIColor colorNamed:kRed500Color];
}

}  // namespace

class SafetyCheckMediatorTest : public PlatformTest {
 public:
  SafetyCheckMediatorTest() {
    TestChromeBrowserState::Builder test_cbs_builder;
    test_cbs_builder.AddTestingFactory(
        AuthenticationServiceFactory::GetInstance(),
        AuthenticationServiceFactory::GetDefaultFactory());
    test_cbs_builder.AddTestingFactory(
        SyncServiceFactory::GetInstance(),
        base::BindRepeating(
            [](web::BrowserState*) -> std::unique_ptr<KeyedService> {
              return std::make_unique<syncer::MockSyncService>();
            }));
    test_cbs_builder.AddTestingFactory(
        IOSChromeProfilePasswordStoreFactory::GetInstance(),
        base::BindRepeating(
            &password_manager::BuildPasswordStore<web::BrowserState,
                                                  TestPasswordStore>));
    test_cbs_builder.AddTestingFactory(
        IOSChromeAffiliationServiceFactory::GetInstance(),
        base::BindRepeating(base::BindLambdaForTesting([](web::BrowserState*) {
          return std::unique_ptr<KeyedService>(
              std::make_unique<affiliations::FakeAffiliationService>());
        })));
    browser_state_ = std::move(test_cbs_builder).Build();
    AuthenticationServiceFactory::CreateAndInitializeForBrowserState(
        browser_state_.get(),
        std::make_unique<FakeAuthenticationServiceDelegate>());
    auth_service_ = static_cast<AuthenticationService*>(
        AuthenticationServiceFactory::GetInstance()->GetForBrowserState(
            browser_state_.get()));

    store_ =
        base::WrapRefCounted(static_cast<password_manager::TestPasswordStore*>(
            IOSChromeProfilePasswordStoreFactory::GetForBrowserState(
                browser_state_.get(), ServiceAccessType::EXPLICIT_ACCESS)
                .get()));

    password_check_ = IOSChromePasswordCheckManagerFactory::GetForBrowserState(
        browser_state_.get());

    pref_service_ = SetPrefService();
    local_pref_service_ =
        TestingApplicationContext::GetGlobal()->GetLocalState();

    mediator_ = [[SafetyCheckMediator alloc]
        initWithUserPrefService:pref_service_
               localPrefService:local_pref_service_
           passwordCheckManager:password_check_
                    authService:auth_service_
                    syncService:syncService()
                       referrer:password_manager::PasswordCheckReferrer::
                                    kSafetyCheck];
  }

  syncer::SyncService* syncService() {
    return SyncServiceFactory::GetForBrowserState(browser_state_.get());
  }

  void RunUntilIdle() { environment_.RunUntilIdle(); }

  void AddPasswordForm(std::unique_ptr<password_manager::PasswordForm> form) {
    GetTestStore().AddLogin(*form);
    RunUntilIdle();
  }

  void ResetNSUserDefaultsForTesting() {
    NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
    [defaults removeObjectForKey:kTimestampOfLastIssueFoundKey];
    [defaults removeObjectForKey:kIOSChromeUpToDateKey];
  }

  void ResetLocalPrefsForTesting() {
    local_pref_service_->ClearPref(prefs::kIosSettingsSafetyCheckLastRunTime);
    local_pref_service_->ClearPref(kIOSChromeNextVersionKey);
    local_pref_service_->ClearPref(kIOSChromeUpgradeURLKey);
  }

  // Creates a form.
  std::unique_ptr<password_manager::PasswordForm> CreateForm(
      std::string signon_realm = "http://www.example.com/") {
    auto form = std::make_unique<password_manager::PasswordForm>();
    form->url = GURL(signon_realm + "accounts/LoginAuth");
    form->action = GURL(signon_realm + "accounts/Login");
    form->username_element = u"Email";
    form->username_value = u"[email protected]";
    form->password_element = u"Passwd";
    form->password_value = u"fnlsr4@cm^mdls@fkspnsg3d";
    form->submit_element = u"signIn";
    form->signon_realm = signon_realm;
    form->scheme = password_manager::PasswordForm::Scheme::kHtml;
    form->blocked_by_user = false;
    return form;
  }

  // Creates and adds a saved password form. If `is_leaked` is true it marks the
  // credential as leaked.
  void AddSavedForm() {
    auto form = CreateForm();
    AddPasswordForm(std::move(form));
  }

  // Creates and adds a saved insecure password form.
  void AddSavedInsecureForm(
      InsecureType insecure_type,
      bool is_muted = false,
      std::string signon_realm = "http://www.example.com/") {
    auto form = CreateForm(signon_realm);
    form->password_issues = {
        {insecure_type,
         password_manager::InsecurityMetadata(
             base::Time::Now(), password_manager::IsMuted(is_muted),
             password_manager::TriggerBackendNotification(false))}};
    AddPasswordForm(std::move(form));
  }

  TestPasswordStore& GetTestStore() {
    return *static_cast<TestPasswordStore*>(
        IOSChromeProfilePasswordStoreFactory::GetForBrowserState(
            browser_state_.get(), ServiceAccessType::EXPLICIT_ACCESS)
            .get());
  }

  // Tests that only having a leaked password results in an umuted compromised
  // password row state.
  void CheckCompromisedRowState() {
    base::HistogramTester histogram_tester;

    AddSavedInsecureForm(InsecureType::kLeaked);
    mediator_.currentPasswordCheckState = PasswordCheckState::kRunning;
    [mediator_ passwordCheckStateDidChange:PasswordCheckState::kIdle];

    histogram_tester.ExpectUniqueSample(
        kSafetyCheckMetricsPasswords,
        safety_check::PasswordsStatus::kCompromisedExist, 1);

    EXPECT_EQ(mediator_.passwordCheckRowState,
              PasswordCheckRowStateUnmutedCompromisedPasswords);
  }

 protected:
  base::test::ScopedFeatureList feature_list_;
  web::WebTaskEnvironment environment_;
  IOSChromeScopedTestingLocalState scoped_testing_local_state_;
  std::unique_ptr<TestChromeBrowserState> browser_state_;
  scoped_refptr<TestPasswordStore> store_;
  raw_ptr<AuthenticationService> auth_service_;
  scoped_refptr<IOSChromePasswordCheckManager> password_check_;
  SafetyCheckMediator* mediator_;
  raw_ptr<PrefService> pref_service_;
  raw_ptr<PrefService> local_pref_service_;
  PrefBackedBoolean* safe_browsing_preference_;
};

#pragma mark - Check start button tests

TEST_F(SafetyCheckMediatorTest, StartingCheckPutsChecksInRunningState) {
  TableViewItem* start =
      [[TableViewItem alloc] initWithType:CheckStartItemType];
  [mediator_ didSelectItem:start];
  EXPECT_EQ(mediator_.updateCheckRowState, UpdateCheckRowStateRunning);
  EXPECT_EQ(mediator_.passwordCheckRowState, PasswordCheckRowStateRunning);
  EXPECT_EQ(mediator_.safeBrowsingCheckRowState,
            SafeBrowsingCheckRowStateRunning);
  EXPECT_EQ(mediator_.checkStartState, CheckStartStateCancel);
}

TEST_F(SafetyCheckMediatorTest, CheckStartButtonDefaultUI) {
  mediator_.checkStartState = CheckStartStateDefault;
  [mediator_ reconfigureCheckStartSection];
  EXPECT_NSEQ(mediator_.checkStartItem.text,
              GetNSString(IDS_IOS_CHECK_PASSWORDS_NOW_BUTTON));
}

TEST_F(SafetyCheckMediatorTest, ClickingCancelTakesChecksToPrevious) {
  TableViewItem* start =
      [[TableViewItem alloc] initWithType:CheckStartItemType];
  [mediator_ didSelectItem:start];
  EXPECT_EQ(mediator_.updateCheckRowState, UpdateCheckRowStateRunning);
  EXPECT_EQ(mediator_.passwordCheckRowState, PasswordCheckRowStateRunning);
  EXPECT_EQ(mediator_.safeBrowsingCheckRowState,
            SafeBrowsingCheckRowStateRunning);
  EXPECT_EQ(mediator_.checkStartState, CheckStartStateCancel);
  [mediator_ didSelectItem:start];
  EXPECT_EQ(mediator_.updateCheckRowState,
            mediator_.previousUpdateCheckRowState);
  EXPECT_EQ(mediator_.passwordCheckRowState,
            mediator_.previousPasswordCheckRowState);
  EXPECT_EQ(mediator_.safeBrowsingCheckRowState,
            mediator_.previousSafeBrowsingCheckRowState);
  EXPECT_EQ(mediator_.checkStartState, CheckStartStateDefault);
}

TEST_F(SafetyCheckMediatorTest, CheckStartButtonCancelUI) {
  mediator_.checkStartState = CheckStartStateCancel;
  [mediator_ reconfigureCheckStartSection];
  EXPECT_NSEQ(mediator_.checkStartItem.text,
              GetNSString(IDS_IOS_CANCEL_PASSWORD_CHECK_BUTTON));
}

#pragma mark - Timestamp tests

TEST_F(SafetyCheckMediatorTest, TimestampSetIfIssueFound) {
  mediator_.checkDidRun = true;
  mediator_.passwordCheckRowState =
      PasswordCheckRowStateUnmutedCompromisedPasswords;
  [mediator_ resetsCheckStartItemIfNeeded];

  base::Time lastCompletedCheck = base::Time::FromSecondsSinceUnixEpoch(
      [[NSUserDefaults standardUserDefaults]
          doubleForKey:kTimestampOfLastIssueFoundKey]);
  EXPECT_GE(lastCompletedCheck, base::Time::Now() - base::Seconds(1));
  EXPECT_LE(lastCompletedCheck, base::Time::Now() + base::Seconds(1));

  ResetNSUserDefaultsForTesting();
}

TEST_F(SafetyCheckMediatorTest, TimestampResetIfNoIssuesInCheck) {
  mediator_.checkDidRun = true;
  mediator_.passwordCheckRowState =
      PasswordCheckRowStateUnmutedCompromisedPasswords;
  [mediator_ resetsCheckStartItemIfNeeded];

  base::Time lastCompletedCheck = base::Time::FromSecondsSinceUnixEpoch(
      [[NSUserDefaults standardUserDefaults]
          doubleForKey:kTimestampOfLastIssueFoundKey]);
  EXPECT_GE(lastCompletedCheck, base::Time::Now() - base::Seconds(1));
  EXPECT_LE(lastCompletedCheck, base::Time::Now() + base::Seconds(1));

  mediator_.checkDidRun = true;
  mediator_.passwordCheckRowState = PasswordCheckRowStateSafe;
  [mediator_ resetsCheckStartItemIfNeeded];

  lastCompletedCheck = base::Time::FromSecondsSinceUnixEpoch(
      [[NSUserDefaults standardUserDefaults]
          doubleForKey:kTimestampOfLastIssueFoundKey]);
  EXPECT_EQ(base::Time(), lastCompletedCheck);

  ResetNSUserDefaultsForTesting();
}

// Checks the timestamp of the latest run is set after the Safety Check
// completes its run.
TEST_F(SafetyCheckMediatorTest, TimestampSetForLatestRun) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures({kSafetyCheckMagicStack}, {});

  mediator_.checkDidRun = true;

  mediator_.passwordCheckRowState =
      PasswordCheckRowStateUnmutedCompromisedPasswords;

  [mediator_ resetsCheckStartItemIfNeeded];

  base::Time lastRunTime =
      local_pref_service_->GetTime(prefs::kIosSettingsSafetyCheckLastRunTime);

  EXPECT_GE(lastRunTime, base::Time::Now() - base::Seconds(1));
  EXPECT_LE(lastRunTime, base::Time::Now() + base::Seconds(1));

  ResetLocalPrefsForTesting();
}

#pragma mark - Safe Browsing check tests

TEST_F(SafetyCheckMediatorTest, SafeBrowsingEnabledReturnsSafeState) {
  mediator_.safeBrowsingPreference.value = true;
  RunUntilIdle();
  [mediator_ checkAndReconfigureSafeBrowsingState];
  RunUntilIdle();
  EXPECT_EQ(mediator_.safeBrowsingCheckRowState, SafeBrowsingCheckRowStateSafe);
}

TEST_F(SafetyCheckMediatorTest, SafeBrowsingSafeUI) {
  mediator_.safeBrowsingCheckRowState = SafeBrowsingCheckRowStateSafe;
  [mediator_ reconfigureSafeBrowsingCheckItem];
  EXPECT_NSEQ(
      mediator_.safeBrowsingCheckItem.detailText,
      GetNSString(
          IDS_IOS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_ENHANCED_PROTECTION_ENABLED_DESC));

  // Change from Enhanced Protection to Standard Protection.
  mediator_.enhancedSafeBrowsingPreference.value = false;
  [mediator_ reconfigureSafeBrowsingCheckItem];
  EXPECT_NSEQ(
      mediator_.safeBrowsingCheckItem.detailText,
      GetNSString(
          IDS_IOS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_STANDARD_PROTECTION_ENABLED_DESC_WITH_ENHANCED_PROTECTION));

  EXPECT_EQ(mediator_.safeBrowsingCheckItem.trailingImage, SafeImage());
  EXPECT_TRUE([mediator_.safeBrowsingCheckItem.trailingImageTintColor
      isEqual:GreenColor()]);
}

// Tests UI for Safe Browsing row in Safety Check settings.
TEST_F(SafetyCheckMediatorTest,
       SafeBrowsingSafeUIStandardAndEnhancedProtection) {
  // Check UI when Safe Browsing protection choice is "Enhanced Protection".
  mediator_.safeBrowsingCheckRowState = SafeBrowsingCheckRowStateSafe;
  [mediator_ reconfigureSafeBrowsingCheckItem];
  EXPECT_NSEQ(
      mediator_.safeBrowsingCheckItem.detailText,
      GetNSString(
          IDS_IOS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_ENHANCED_PROTECTION_ENABLED_DESC));
  EXPECT_EQ(mediator_.safeBrowsingCheckItem.accessoryType,
            UITableViewCellAccessoryNone);
  EXPECT_EQ(mediator_.safeBrowsingCheckItem.trailingImage, SafeImage());
  EXPECT_TRUE([mediator_.safeBrowsingCheckItem.trailingImageTintColor
      isEqual:GreenColor()]);

  // Check UI when Safe Browsing protection choice is "Standard Protection".
  mediator_.enhancedSafeBrowsingPreference.value = false;
  [mediator_ reconfigureSafeBrowsingCheckItem];
  EXPECT_NSEQ(
      mediator_.safeBrowsingCheckItem.detailText,
      GetNSString(
          IDS_IOS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_STANDARD_PROTECTION_ENABLED_DESC_WITH_ENHANCED_PROTECTION));
  EXPECT_EQ(mediator_.safeBrowsingCheckItem.accessoryType,
            UITableViewCellAccessoryDisclosureIndicator);
  EXPECT_EQ(mediator_.safeBrowsingCheckItem.trailingImage, SafeImage());
  EXPECT_TRUE([mediator_.safeBrowsingCheckItem.trailingImageTintColor
      isEqual:GreenColor()]);

  // Check UI when Safe Browsing protection choice is "No Protection".
  mediator_.safeBrowsingPreference.value = false;
  mediator_.safeBrowsingCheckRowState = SafeBrowsingCheckRowStateUnsafe;
  [mediator_ reconfigureSafeBrowsingCheckItem];
  EXPECT_EQ(mediator_.safeBrowsingCheckItem.accessoryType,
            UITableViewCellAccessoryDisclosureIndicator);
  EXPECT_EQ(mediator_.safeBrowsingCheckItem.trailingImage, UnsafeImage());
  EXPECT_TRUE([mediator_.safeBrowsingCheckItem.trailingImageTintColor
      isEqual:RedColor()]);
}

TEST_F(SafetyCheckMediatorTest, SafeBrowsingDisabledReturnsInfoState) {
  mediator_.safeBrowsingPreference.value = false;
  RunUntilIdle();
  [mediator_ checkAndReconfigureSafeBrowsingState];
  EXPECT_EQ(mediator_.safeBrowsingCheckRowState,
            SafeBrowsingCheckRowStateUnsafe);
}

TEST_F(SafetyCheckMediatorTest, SafeBrowsingUnSafeUI) {
  mediator_.safeBrowsingCheckRowState = SafeBrowsingCheckRowStateUnsafe;
  [mediator_ reconfigureSafeBrowsingCheckItem];
  EXPECT_NSEQ(
      mediator_.safeBrowsingCheckItem.detailText,
      GetNSString(IDS_IOS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_DISABLED_DESC));
  EXPECT_TRUE(mediator_.safeBrowsingCheckItem.infoButtonHidden);
}

TEST_F(SafetyCheckMediatorTest, SafeBrowsingManagedUI) {
  mediator_.safeBrowsingCheckRowState = SafeBrowsingCheckRowStateManaged;
  [mediator_ reconfigureSafeBrowsingCheckItem];
  EXPECT_NSEQ(
      mediator_.safeBrowsingCheckItem.detailText,
      GetNSString(IDS_IOS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_MANAGED_DESC));
  EXPECT_FALSE(mediator_.safeBrowsingCheckItem.infoButtonHidden);
}

#pragma mark - Password check tests

// Tests that only having a safe password results in a safe password row state.
TEST_F(SafetyCheckMediatorTest, PasswordCheckSafeCheck) {
  AddSavedForm();
  mediator_.currentPasswordCheckState = PasswordCheckState::kRunning;
  [mediator_ passwordCheckStateDidChange:PasswordCheckState::kIdle];
  EXPECT_EQ(mediator_.passwordCheckRowState, PasswordCheckRowStateSafe);
}

// Tests that the content of the `passwordCheckItem` is as expected when in safe
// state.
TEST_F(SafetyCheckMediatorTest, PasswordCheckSafeUI) {
  mediator_.passwordCheckRowState = PasswordCheckRowStateSafe;
  [mediator_ reconfigurePasswordCheckItem];
  EXPECT_NSEQ(mediator_.passwordCheckItem.detailText,
              base::SysUTF16ToNSString(l10n_util::GetPluralStringFUTF16(
                  IDS_IOS_PASSWORD_CHECKUP_COMPROMISED_COUNT, 0)));
  EXPECT_EQ(mediator_.passwordCheckItem.trailingImage, SafeImage());
  EXPECT_TRUE([mediator_.passwordCheckItem.trailingImageTintColor
      isEqual:GreenColor()]);
  EXPECT_EQ(mediator_.passwordCheckItem.accessoryType,
            UITableViewCellAccessoryDisclosureIndicator);
}

// Tests that only having a leaked password results in an umuted compromised
// password row state.
TEST_F(SafetyCheckMediatorTest, PasswordCheckUnmutedCompromisedPasswordsCheck) {
  CheckCompromisedRowState();
}

// Tests that the content of the `passwordCheckItem` is as expected when in
// compromised state.
TEST_F(SafetyCheckMediatorTest, PasswordCheckUnmutedCompromisedPasswordsUI) {
  AddSavedInsecureForm(InsecureType::kLeaked);
  mediator_.passwordCheckRowState =
      PasswordCheckRowStateUnmutedCompromisedPasswords;
  [mediator_ reconfigurePasswordCheckItem];
  EXPECT_NSEQ(mediator_.passwordCheckItem.detailText,
              base::SysUTF16ToNSString(l10n_util::GetPluralStringFUTF16(
                  IDS_IOS_PASSWORD_CHECKUP_COMPROMISED_COUNT, 1)));
  EXPECT_EQ(mediator_.passwordCheckItem.trailingImage, UnsafeImage());
  EXPECT_TRUE(
      [mediator_.passwordCheckItem.trailingImageTintColor isEqual:RedColor()]);
  EXPECT_EQ(mediator_.passwordCheckItem.accessoryType,
            UITableViewCellAccessoryDisclosureIndicator);
}

// Tests that only having a reused password results in a reused password row
// state.
TEST_F(SafetyCheckMediatorTest, PasswordCheckReusedPasswordsCheck) {
  base::HistogramTester histogram_tester;

  AddSavedInsecureForm(InsecureType::kReused);
  AddSavedInsecureForm(InsecureType::kReused, /*is_muted=*/false,
                       /*signon_realm=*/"http://www.example1.com/");
  mediator_.currentPasswordCheckState = PasswordCheckState::kRunning;
  [mediator_ passwordCheckStateDidChange:PasswordCheckState::kIdle];

  histogram_tester.ExpectUniqueSample(
      kSafetyCheckMetricsPasswords,
      safety_check::PasswordsStatus::kReusedPasswordsExist, 1);

  EXPECT_EQ(mediator_.passwordCheckRowState,
            PasswordCheckRowStateReusedPasswords);
}

// Tests that the content of the `passwordCheckItem` is as expected when in
// reused state.
TEST_F(SafetyCheckMediatorTest, PasswordCheckReusedPasswordsUI) {
  AddSavedInsecureForm(InsecureType::kReused);
  AddSavedInsecureForm(InsecureType::kReused, /*is_muted=*/false,
                       /*signon_realm=*/"http://www.example1.com/");
  mediator_.passwordCheckRowState = PasswordCheckRowStateReusedPasswords;
  [mediator_ reconfigurePasswordCheckItem];
  EXPECT_NSEQ(
      mediator_.passwordCheckItem.detailText,
      l10n_util::GetNSStringF(IDS_IOS_PASSWORD_CHECKUP_REUSED_COUNT, u"2"));
  EXPECT_EQ(mediator_.passwordCheckItem.trailingImage, nil);
  EXPECT_EQ(mediator_.passwordCheckItem.accessoryType,
            UITableViewCellAccessoryDisclosureIndicator);
}

// Tests that only having a weak password results in a weak password row state.
TEST_F(SafetyCheckMediatorTest, PasswordCheckWeakPasswordsCheck) {
  base::HistogramTester histogram_tester;

  AddSavedInsecureForm(InsecureType::kWeak);
  mediator_.currentPasswordCheckState = PasswordCheckState::kRunning;
  [mediator_ passwordCheckStateDidChange:PasswordCheckState::kIdle];

  histogram_tester.ExpectUniqueSample(
      kSafetyCheckMetricsPasswords,
      safety_check::PasswordsStatus::kWeakPasswordsExist, 1);

  EXPECT_EQ(mediator_.passwordCheckRowState,
            PasswordCheckRowStateWeakPasswords);
}

// Tests that the content of the `passwordCheckItem` is as expected when in weak
// state.
TEST_F(SafetyCheckMediatorTest, PasswordCheckWeakPasswordsUI) {
  AddSavedInsecureForm(InsecureType::kWeak);
  mediator_.passwordCheckRowState = PasswordCheckRowStateWeakPasswords;
  [mediator_ reconfigurePasswordCheckItem];
  EXPECT_NSEQ(mediator_.passwordCheckItem.detailText,
              base::SysUTF16ToNSString(l10n_util::GetPluralStringFUTF16(
                  IDS_IOS_PASSWORD_CHECKUP_WEAK_COUNT, 1)));
  EXPECT_EQ(mediator_.passwordCheckItem.trailingImage, nil);
  EXPECT_EQ(mediator_.passwordCheckItem.accessoryType,
            UITableViewCellAccessoryDisclosureIndicator);
}

// Tests that only having a dismissed compromsied warning results in a dismissed
// warning row state.
TEST_F(SafetyCheckMediatorTest, PasswordCheckDismissedWarningsCheck) {
  base::HistogramTester histogram_tester;

  AddSavedInsecureForm(InsecureType::kLeaked, /*is_muted=*/true);
  mediator_.currentPasswordCheckState = PasswordCheckState::kRunning;
  [mediator_ passwordCheckStateDidChange:PasswordCheckState::kIdle];

  histogram_tester.ExpectUniqueSample(
      kSafetyCheckMetricsPasswords,
      safety_check::PasswordsStatus::kMutedCompromisedExist, 1);

  EXPECT_EQ(mediator_.passwordCheckRowState,
            PasswordCheckRowStateDismissedWarnings);
}

// Tests that the content of the `passwordCheckItem` is as expected when in
// dismissed warning state.
TEST_F(SafetyCheckMediatorTest, PasswordCheckDismissedWarningsUI) {
  AddSavedInsecureForm(InsecureType::kLeaked, /*is_muted=*/true);
  mediator_.passwordCheckRowState = PasswordCheckRowStateDismissedWarnings;
  [mediator_ reconfigurePasswordCheckItem];
  EXPECT_NSEQ(mediator_.passwordCheckItem.detailText,
              base::SysUTF16ToNSString(l10n_util::GetPluralStringFUTF16(
                  IDS_IOS_PASSWORD_CHECKUP_DISMISSED_COUNT, 1)));
  EXPECT_EQ(mediator_.passwordCheckItem.trailingImage, nil);
  EXPECT_EQ(mediator_.passwordCheckItem.accessoryType,
            UITableViewCellAccessoryDisclosureIndicator);
}

// Tests that exceeding the quota limit results in an error row state.
TEST_F(SafetyCheckMediatorTest, PasswordCheckErrorCheck) {
  AddSavedForm();
  mediator_.currentPasswordCheckState = PasswordCheckState::kRunning;
  [mediator_ passwordCheckStateDidChange:PasswordCheckState::kQuotaLimit];
  EXPECT_EQ(mediator_.passwordCheckRowState, PasswordCheckRowStateError);
}

// Tests that the content of the `passwordCheckItem` is as expected when in
// error state.
TEST_F(SafetyCheckMediatorTest, PasswordCheckErrorUI) {
  mediator_.passwordCheckRowState = PasswordCheckRowStateError;
  [mediator_ reconfigurePasswordCheckItem];
  EXPECT_NSEQ(mediator_.passwordCheckItem.detailText,
              GetNSString(IDS_IOS_PASSWORD_CHECK_ERROR));
  EXPECT_FALSE(mediator_.passwordCheckItem.infoButtonHidden);
  EXPECT_EQ(mediator_.passwordCheckItem.accessoryType,
            UITableViewCellAccessoryNone);
}

#pragma mark - Update check tests

TEST_F(SafetyCheckMediatorTest, OmahaRespondsUpToDate) {
  mediator_.updateCheckRowState = UpdateCheckRowStateRunning;
  UpgradeRecommendedDetails details;
  details.is_up_to_date = true;
  details.next_version = "9999.9999.9999.9999";
  details.upgrade_url = GURL("http://foobar.org");
  [mediator_ handleOmahaResponse:details];
  EXPECT_EQ(mediator_.updateCheckRowState, UpdateCheckRowStateUpToDate);
  ResetNSUserDefaultsForTesting();
}

TEST_F(SafetyCheckMediatorTest, UpdateCheckUpToDateUI) {
  mediator_.updateCheckRowState = UpdateCheckRowStateUpToDate;
  [mediator_ reconfigureUpdateCheckItem];
  EXPECT_NSEQ(
      mediator_.updateCheckItem.detailText,
      GetNSString(IDS_IOS_SETTINGS_SAFETY_CHECK_UPDATES_UP_TO_DATE_DESC));
  EXPECT_EQ(mediator_.updateCheckItem.trailingImage, SafeImage());
  EXPECT_TRUE(
      [mediator_.updateCheckItem.trailingImageTintColor isEqual:GreenColor()]);
}

TEST_F(SafetyCheckMediatorTest, OmahaRespondsOutOfDateAndUpdatesInfobarTime) {
  mediator_.updateCheckRowState = UpdateCheckRowStateRunning;
  UpgradeRecommendedDetails details;
  details.is_up_to_date = false;
  details.next_version = "9999.9999.9999.9999";
  details.upgrade_url = GURL("http://foobar.org");
  [mediator_ handleOmahaResponse:details];
  EXPECT_EQ(mediator_.updateCheckRowState, UpdateCheckRowStateOutOfDate);

  PrefService* pref_service = GetApplicationContext()->GetLocalState();
  const base::Time last_display =
      pref_service->GetTime(kLastInfobarDisplayTimeKey);
  EXPECT_GE((base::Time::Now() - last_display).InSeconds(), -1);
  EXPECT_LE((base::Time::Now() - last_display).InSeconds(), 1);
  ResetNSUserDefaultsForTesting();
  ResetLocalPrefsForTesting();
}

TEST_F(SafetyCheckMediatorTest, UpdateCheckOutOfDateUI) {
  mediator_.updateCheckRowState = UpdateCheckRowStateOutOfDate;
  [mediator_ reconfigureUpdateCheckItem];
  EXPECT_NSEQ(
      mediator_.updateCheckItem.detailText,
      GetNSString(IDS_IOS_SETTINGS_SAFETY_CHECK_UPDATES_OUT_OF_DATE_DESC));
  EXPECT_EQ(mediator_.updateCheckItem.trailingImage, UnsafeImage());
  EXPECT_TRUE(
      [mediator_.updateCheckItem.trailingImageTintColor isEqual:RedColor()]);
}

TEST_F(SafetyCheckMediatorTest, OmahaRespondsError) {
  mediator_.updateCheckRowState = UpdateCheckRowStateRunning;
  UpgradeRecommendedDetails details;
  details.is_up_to_date = false;
  details.next_version = "";
  details.upgrade_url = GURL("7");
  [mediator_ handleOmahaResponse:details];
  EXPECT_EQ(mediator_.updateCheckRowState, UpdateCheckRowStateOmahaError);
}

TEST_F(SafetyCheckMediatorTest, UpdateCheckOmahaErrorUI) {
  mediator_.updateCheckRowState = UpdateCheckRowStateOmahaError;
  [mediator_ reconfigureUpdateCheckItem];
  EXPECT_NSEQ(mediator_.updateCheckItem.detailText,
              GetNSString(IDS_IOS_SETTINGS_SAFETY_CHECK_UPDATES_ERROR_DESC));
  EXPECT_FALSE(mediator_.updateCheckItem.infoButtonHidden);
}

TEST_F(SafetyCheckMediatorTest, UpdateCheckNetErrorUI) {
  mediator_.updateCheckRowState = UpdateCheckRowStateNetError;
  [mediator_ reconfigureUpdateCheckItem];
  EXPECT_NSEQ(mediator_.updateCheckItem.detailText,
              GetNSString(IDS_IOS_SETTINGS_SAFETY_CHECK_UPDATES_OFFLINE_DESC));
  EXPECT_FALSE(mediator_.updateCheckItem.infoButtonHidden);
}

TEST_F(SafetyCheckMediatorTest, UpdateCheckManagedUI) {
  mediator_.updateCheckRowState = UpdateCheckRowStateManaged;
  [mediator_ reconfigureUpdateCheckItem];
  EXPECT_NSEQ(mediator_.updateCheckItem.detailText,
              GetNSString(IDS_IOS_SETTINGS_SAFETY_CHECK_UPDATES_MANAGED_DESC));
  EXPECT_FALSE(mediator_.updateCheckItem.infoButtonHidden);
}

TEST_F(SafetyCheckMediatorTest, UpdateCheckChannelUI) {
  mediator_.updateCheckRowState = UpdateCheckRowStateChannel;
  [mediator_ reconfigureUpdateCheckItem];
  EXPECT_NSEQ(
      mediator_.updateCheckItem.detailText,
      GetNSString(IDS_IOS_SETTINGS_SAFETY_CHECK_UPDATES_CHANNEL_CANARY_DESC));
  EXPECT_TRUE(mediator_.updateCheckItem.infoButtonHidden);
}

#pragma mark - Clickable tests

TEST_F(SafetyCheckMediatorTest, UpdateClickableOutOfDate) {
  mediator_.updateCheckRowState = UpdateCheckRowStateOutOfDate;
  [mediator_ reconfigureUpdateCheckItem];
  TableViewItem* updateItem =
      [[TableViewItem alloc] initWithType:SafetyCheckItemType::UpdateItemType];
  EXPECT_TRUE([mediator_ isItemClickable:updateItem]);
}

TEST_F(SafetyCheckMediatorTest, UpdateNonclickableUpToDate) {
  mediator_.updateCheckRowState = UpdateCheckRowStateUpToDate;
  [mediator_ reconfigureUpdateCheckItem];
  TableViewItem* updateItem =
      [[TableViewItem alloc] initWithType:SafetyCheckItemType::UpdateItemType];
  EXPECT_FALSE([mediator_ isItemClickable:updateItem]);
}

TEST_F(SafetyCheckMediatorTest, PasswordClickableUnmutedCompromisedPasswords) {
  mediator_.passwordCheckRowState =
      PasswordCheckRowStateUnmutedCompromisedPasswords;
  [mediator_ reconfigurePasswordCheckItem];
  TableViewItem* passwordItem = [[TableViewItem alloc]
      initWithType:SafetyCheckItemType::PasswordItemType];
  EXPECT_TRUE([mediator_ isItemClickable:passwordItem]);
}

// When in safe state, the password check item is clickable.
TEST_F(SafetyCheckMediatorTest, PasswordClickableSafe) {
  mediator_.passwordCheckRowState = PasswordCheckRowStateSafe;
  [mediator_ reconfigurePasswordCheckItem];
  TableViewItem* passwordItem = [[TableViewItem alloc]
      initWithType:SafetyCheckItemType::PasswordItemType];
  EXPECT_TRUE([mediator_ isItemClickable:passwordItem]);
}

// When in reused state, the password check item is clickable.
TEST_F(SafetyCheckMediatorTest, PasswordClickableReusedPasswords) {
  mediator_.passwordCheckRowState = PasswordCheckRowStateReusedPasswords;
  [mediator_ reconfigurePasswordCheckItem];
  TableViewItem* passwordItem = [[TableViewItem alloc]
      initWithType:SafetyCheckItemType::PasswordItemType];
  EXPECT_TRUE([mediator_ isItemClickable:passwordItem]);
}

// When in weak state, the password check item is clickable.
TEST_F(SafetyCheckMediatorTest, PasswordClickableWeakPasswords) {
  mediator_.passwordCheckRowState = PasswordCheckRowStateWeakPasswords;
  [mediator_ reconfigurePasswordCheckItem];
  TableViewItem* passwordItem = [[TableViewItem alloc]
      initWithType:SafetyCheckItemType::PasswordItemType];
  EXPECT_TRUE([mediator_ isItemClickable:passwordItem]);
}

// When in dismissed warnings state, the password check item is clickable.
TEST_F(SafetyCheckMediatorTest, PasswordClickableDismissedWarnings) {
  mediator_.passwordCheckRowState = PasswordCheckRowStateDismissedWarnings;
  [mediator_ reconfigurePasswordCheckItem];
  TableViewItem* passwordItem = [[TableViewItem alloc]
      initWithType:SafetyCheckItemType::PasswordItemType];
  EXPECT_TRUE([mediator_ isItemClickable:passwordItem]);
}

TEST_F(SafetyCheckMediatorTest, SafeBrowsingNonClickableDefault) {
  mediator_.safeBrowsingCheckRowState = SafeBrowsingCheckRowStateDefault;
  [mediator_ reconfigureSafeBrowsingCheckItem];
  TableViewItem* safeBrowsingItem = [[TableViewItem alloc]
      initWithType:SafetyCheckItemType::SafeBrowsingItemType];
  EXPECT_FALSE([mediator_ isItemClickable:safeBrowsingItem]);
}

TEST_F(SafetyCheckMediatorTest, CheckNowClickableAll) {
  mediator_.checkStartState = CheckStartStateCancel;
  [mediator_ reconfigureCheckStartSection];
  TableViewItem* checkStartItem = [[TableViewItem alloc]
      initWithType:SafetyCheckItemType::CheckStartItemType];
  EXPECT_TRUE([mediator_ isItemClickable:checkStartItem]);

  mediator_.checkStartState = CheckStartStateDefault;
  [mediator_ reconfigureCheckStartSection];
  EXPECT_TRUE([mediator_ isItemClickable:checkStartItem]);
}