chromium/ios/chrome/browser/ui/settings/password/password_checkup/password_checkup_mediator_unittest.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 "ios/chrome/browser/ui/settings/password/password_checkup/password_checkup_mediator.h"

#import "base/test/bind.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/common/password_manager_features.h"
#import "ios/chrome/browser/affiliations/model/ios_chrome_affiliation_service_factory.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/passwords/model/password_checkup_utils.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "ios/chrome/browser/ui/settings/password/password_checkup/password_checkup_consumer.h"
#import "ios/web/public/test/web_task_environment.h"
#import "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#import "third_party/ocmock/gtest_support.h"

namespace {

using password_manager::InsecurePasswordCounts;
using password_manager::InsecureType;
using password_manager::PasswordForm;
using password_manager::TestPasswordStore;

// Creates a saved password form.
PasswordForm CreatePasswordForm() {
  PasswordForm form;
  form.username_value = u"[email protected]";
  form.password_value = u"strongPa55w0rd";
  form.signon_realm = "http://www.example.com/";
  form.in_store = PasswordForm::Store::kProfileStore;
  return form;
}

void AddIssueToForm(PasswordForm* form,
                    InsecureType insecure_type = InsecureType::kLeaked,
                    bool is_muted = false) {
  form->password_issues.insert_or_assign(
      insecure_type, password_manager::InsecurityMetadata(
                         base::Time::Now(), password_manager::IsMuted(is_muted),
                         password_manager::TriggerBackendNotification(false)));
}

}  // namespace

// Test fixture for testing PasswordCheckupMediator class.
class PasswordCheckupMediatorTest : public PlatformTest {
 protected:
  PasswordCheckupMediatorTest() {
    TestChromeBrowserState::Builder builder;
    builder.AddTestingFactory(
        IOSChromeProfilePasswordStoreFactory::GetInstance(),
        base::BindRepeating(
            &password_manager::BuildPasswordStore<web::BrowserState,
                                                  TestPasswordStore>));
    builder.AddTestingFactory(
        IOSChromeAffiliationServiceFactory::GetInstance(),
        base::BindRepeating(base::BindLambdaForTesting([](web::BrowserState*) {
          return std::unique_ptr<KeyedService>(
              std::make_unique<affiliations::FakeAffiliationService>());
        })));

    browser_state_ = std::move(builder).Build();

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

    consumer_ = OCMProtocolMock(@protocol(PasswordCheckupConsumer));

    mediator_ = [[PasswordCheckupMediator alloc]
        initWithPasswordCheckManager:password_check_];
    mediator_.consumer = consumer_;
  }

  PasswordCheckupMediator* mediator() { return mediator_; }

  ChromeBrowserState* browserState() { return browser_state_.get(); }

  id consumer() { return consumer_; }

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

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

 private:
  web::WebTaskEnvironment task_environment_;
  std::unique_ptr<TestChromeBrowserState> browser_state_;
  scoped_refptr<IOSChromePasswordCheckManager> password_check_;
  id consumer_;
  PasswordCheckupMediator* mediator_;
};

// Tests that the consumer is correclty notified when the password check state
// changed.
TEST_F(PasswordCheckupMediatorTest,
       NotifiesConsumerOnPasswordCheckStateChange) {
  EXPECT_TRUE([mediator() conformsToProtocol:@protocol(PasswordCheckObserver)]);

  InsecurePasswordCounts counts{};

  OCMExpect([consumer()
         setPasswordCheckupHomepageState:PasswordCheckupHomepageState::
                                             PasswordCheckupHomepageStateDone
                  insecurePasswordCounts:counts
      formattedElapsedTimeSinceLastCheck:@"Check never run"]);
  OCMExpect([consumer() setAffiliatedGroupCount:0]);

  PasswordCheckupMediator<PasswordCheckObserver>* password_check_observer =
      static_cast<PasswordCheckupMediator<PasswordCheckObserver>*>(mediator());

  [password_check_observer
      passwordCheckStateDidChange:PasswordCheckState::kIdle];

  // Wait for the observer updates to complete.
  RunUntilIdle();

  EXPECT_OCMOCK_VERIFY(consumer());
}

// Tests that the consumer is correctly notified when the saved insecure
// passwords changed.
TEST_F(PasswordCheckupMediatorTest, NotifiesConsumerOnInsecurePasswordChange) {
  InsecurePasswordCounts counts = {1, 0, 0, 1};

  OCMExpect([consumer()
         setPasswordCheckupHomepageState:PasswordCheckupHomepageState::
                                             PasswordCheckupHomepageStateDone
                  insecurePasswordCounts:counts
      formattedElapsedTimeSinceLastCheck:@"Check never run"]);
  OCMExpect([consumer() setAffiliatedGroupCount:1]);

  PasswordCheckupMediator<PasswordCheckObserver>* password_check_observer =
      static_cast<PasswordCheckupMediator<PasswordCheckObserver>*>(mediator());

  [password_check_observer
      passwordCheckStateDidChange:PasswordCheckState::kIdle];

  PasswordForm form = CreatePasswordForm();
  AddIssueToForm(&form, InsecureType::kLeaked);
  AddIssueToForm(&form, InsecureType::kWeak);
  GetTestStore().AddLogin(form);
  RunUntilIdle();

  EXPECT_OCMOCK_VERIFY(consumer());
}

// Tests that the consumer is notified with the results of the last successful
// password check when the current password check fails.
TEST_F(PasswordCheckupMediatorTest,
       NotifiesConsumerAfterPasswordCheckupFailed) {
  PasswordCheckupMediator<PasswordCheckObserver>* password_check_observer =
      static_cast<PasswordCheckupMediator<PasswordCheckObserver>*>(mediator());

  [password_check_observer
      passwordCheckStateDidChange:PasswordCheckState::kIdle];

  // Add a leaked password. This password should be taken into account in the
  // `insecurePasswordCounts` passed to the conusmer.
  PasswordForm form1 = CreatePasswordForm();
  AddIssueToForm(&form1, InsecureType::kLeaked);
  GetTestStore().AddLogin(form1);
  RunUntilIdle();

  InsecurePasswordCounts counts = {1, 0, 0, 0};

  OCMExpect([consumer()
         setPasswordCheckupHomepageState:PasswordCheckupHomepageState::
                                             PasswordCheckupHomepageStateDone
                  insecurePasswordCounts:counts
      formattedElapsedTimeSinceLastCheck:@"Check never run"]);
  OCMExpect([consumer() setAffiliatedGroupCount:1]);

  // Enter an error state of PasswordCheckState.
  [password_check_observer
      passwordCheckStateDidChange:PasswordCheckState::kSignedOut];

  // Add a weak password. This password shouldn't be taken into account in the
  // `insecurePasswordCounts` passed to the conusmer since an error occured.
  PasswordForm form2 = CreatePasswordForm();
  AddIssueToForm(&form2, InsecureType::kWeak);
  GetTestStore().AddLogin(form2);
  RunUntilIdle();

  EXPECT_OCMOCK_VERIFY(consumer());
}