chromium/ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_password_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/autofill/ui_bundled/manual_fill/manual_fill_password_mediator.h"

#import "base/memory/raw_ptr.h"
#import "base/test/bind.h"
#import "components/affiliations/core/browser/fake_affiliation_service.h"
#import "components/autofill/core/common/autofill_test_utils.h"
#import "components/keyed_service/core/service_access_type.h"
#import "components/password_manager/core/browser/fake_form_fetcher.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/saved_passwords_presenter.h"
#import "ios/chrome/browser/affiliations/model/ios_chrome_affiliation_service_factory.h"
#import "ios/chrome/browser/autofill/ui_bundled/manual_fill/form_fetcher_consumer_bridge.h"
#import "ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_password_cell.h"
#import "ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_password_mediator+Testing.h"
#import "ios/chrome/browser/autofill/ui_bundled/manual_fill/password_consumer.h"
#import "ios/chrome/browser/favicon/model/ios_chrome_favicon_loader_factory.h"
#import "ios/chrome/browser/passwords/model/ios_chrome_profile_password_store_factory.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "ios/chrome/browser/sync/model/sync_service_factory.h"
#import "ios/chrome/browser/ui/settings/password/saved_passwords_presenter_observer.h"
#import "ios/web/public/test/fakes/fake_web_state.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"
#import "url/gurl.h"

using password_manager::PasswordForm;
using password_manager::SavedPasswordsPresenter;
using password_manager::TestPasswordStore;

namespace {

// Creates a 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;
}

}  // namespace

// Test fixture for testing the ManualFillPasswordMediator class.
class ManualFillPasswordMediatorTest : public PlatformTest {
 protected:
  void SetUp() override {
    fake_web_state_ = std::make_unique<web::FakeWebState>();

    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();

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

    affiliation_service_ = static_cast<affiliations::FakeAffiliationService*>(
        IOSChromeAffiliationServiceFactory::GetForBrowserState(
            browser_state_.get()));

    presenter_ = std::make_unique<SavedPasswordsPresenter>(
        affiliation_service_, store_, /*accont_store=*/nullptr);
    presenter_->Init();

    mediator_ = [[ManualFillPasswordMediator alloc]
           initWithFaviconLoader:IOSChromeFaviconLoaderFactory::
                                     GetForBrowserState(browser_state_.get())
                        webState:fake_web_state_.get()
                     syncService:SyncServiceFactory::GetForBrowserState(
                                     browser_state_.get())
                             URL:GURL("http://www.example.com/")
        invokedOnObfuscatedField:NO
            profilePasswordStore:store_
            accountPasswordStore:nil
          showAutofillFormButton:NO];

    consumer_ = OCMProtocolMock(@protocol(ManualFillPasswordConsumer));
    mediator_.consumer = consumer_;
  }

  void TearDown() override { [mediator_ disconnect]; }

  SavedPasswordsPresenter* presenter() { return presenter_.get(); }

  ManualFillPasswordMediator* mediator() { return mediator_; }

  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<web::FakeWebState> fake_web_state_;
  scoped_refptr<TestPasswordStore> store_;
  std::unique_ptr<TestChromeBrowserState> browser_state_;
  std::unique_ptr<SavedPasswordsPresenter> presenter_;
  id consumer_;
  raw_ptr<affiliations::FakeAffiliationService> affiliation_service_;
  ManualFillPasswordMediator* mediator_;
};

// Tests that the consumer is notified when the passwords related to the current
// site are fetched.
TEST_F(ManualFillPasswordMediatorTest, NotifiesConsumerOnFetchDidComplete) {
  // Set the mediator's form fetcher.
  [mediator()
      setFormFetcher:std::make_unique<password_manager::FakeFormFetcher>()];
  EXPECT_TRUE([mediator() conformsToProtocol:@protocol(FormFetcherConsumer)]);

  OCMExpect([consumer() presentCredentials:[OCMArg isNotNil]]);

  ManualFillPasswordMediator<FormFetcherConsumer>* form_fetcher_consumer =
      static_cast<ManualFillPasswordMediator<FormFetcherConsumer>*>(mediator());

  [form_fetcher_consumer fetchDidComplete];

  EXPECT_OCMOCK_VERIFY(consumer());
}

// Tests that the consumer is notified through `fetchAllPasswords` when the
// saved passwords change.
TEST_F(ManualFillPasswordMediatorTest, NotifiesConsumerOnFetchAllPasswords) {
  // Set the mediator's saved passwords presenter.
  mediator().savedPasswordsPresenter = presenter();
  EXPECT_TRUE([mediator()
      conformsToProtocol:@protocol(SavedPasswordsPresenterObserver)]);

  // Add password form to store so we get a result when getting the passwords
  // from the saved passwords presenter.
  PasswordForm form = CreatePasswordForm();
  GetTestStore().AddLogin(form);
  RunUntilIdle();

  OCMExpect([consumer()
      presentCredentials:[OCMArg checkWithBlock:^(
                                     NSArray<ManualFillCredentialItem*>*
                                         credential_items) {
        EXPECT_EQ([credential_items count], 1u);
        return YES;
      }]]);

  ManualFillPasswordMediator<
      SavedPasswordsPresenterObserver>* saved_passwords_presenter_observer =
      static_cast<ManualFillPasswordMediator<SavedPasswordsPresenterObserver>*>(
          mediator());

  // Call `savedPasswordsDidChange` to trigger a call to `fetchAllPasswords`.
  [saved_passwords_presenter_observer savedPasswordsDidChange];

  EXPECT_OCMOCK_VERIFY(consumer());
}