chromium/components/password_manager/ios/password_form_helper_unittest.mm

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

#import "components/password_manager/ios/password_form_helper.h"

#import <stddef.h>

#import <string>

#import "base/apple/bundle_locations.h"
#import "base/strings/sys_string_conversions.h"
#import "base/strings/utf_string_conversions.h"
#import "base/test/ios/wait_util.h"
#import "base/test/metrics/histogram_tester.h"
#import "base/values.h"
#import "components/autofill/core/browser/logging/log_manager.h"
#import "components/autofill/core/common/field_data_manager.h"
#import "components/autofill/core/common/form_data.h"
#import "components/autofill/core/common/password_form_fill_data.h"
#import "components/autofill/core/common/unique_ids.h"
#import "components/autofill/ios/browser/autofill_java_script_feature.h"
#import "components/autofill/ios/common/field_data_manager_factory_ios.h"
#import "components/autofill/ios/form_util/autofill_test_with_web_state.h"
#import "components/autofill/ios/form_util/form_handlers_java_script_feature.h"
#import "components/autofill/ios/form_util/form_util_java_script_feature.h"
#import "components/password_manager/core/browser/mock_password_manager.h"
#import "components/password_manager/core/browser/password_manager_driver.h"
#import "components/password_manager/core/browser/password_manager_interface.h"
#import "components/password_manager/ios/account_select_fill_data.h"
#import "components/password_manager/ios/ios_password_manager_driver_factory.h"
#import "components/password_manager/ios/password_manager_java_script_feature.h"
#import "components/password_manager/ios/shared_password_controller.h"
#import "components/password_manager/ios/test_helpers.h"
#import "components/ukm/ios/ukm_url_recorder.h"
#import "components/ukm/test_ukm_recorder.h"
#import "ios/web/public/js_messaging/content_world.h"
#import "ios/web/public/js_messaging/script_message.h"
#import "ios/web/public/js_messaging/web_frame.h"
#import "ios/web/public/js_messaging/web_frames_manager.h"
#import "ios/web/public/test/fakes/fake_navigation_context.h"
#import "ios/web/public/test/fakes/fake_web_client.h"
#import "ios/web/public/test/fakes/fake_web_frame.h"
#import "ios/web/public/test/fakes/fake_web_frames_manager.h"
#import "ios/web/public/test/fakes/fake_web_state.h"
#import "ios/web/public/test/js_test_util.h"
#import "ios/web/public/test/web_test_with_web_state.h"
#import "ios/web/public/web_client.h"
#import "ios/web/public/web_state.h"
#import "services/metrics/public/cpp/ukm_builders.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#import "third_party/ocmock/gtest_support.h"

NS_ASSUME_NONNULL_BEGIN

using autofill::FieldRendererId;
using autofill::FormData;
using autofill::FormRendererId;
using autofill::PasswordFormFillData;
using base::test::ios::kWaitForJSCompletionTimeout;
using base::test::ios::WaitUntilConditionOrTimeout;
using password_manager::FillData;
using test_helpers::SetPasswordFormFillData;
using test_helpers::SetFillData;
using test_helpers::SetFormData;

namespace {

// A FakeWebState that returns nullopt as the last trusted committed URL.
class FakeWebStateWithoutTrustedCommittedUrl : public web::FakeWebState {
 public:
  ~FakeWebStateWithoutTrustedCommittedUrl() override {}

  // WebState implementation.
  std::optional<GURL> GetLastCommittedURLIfTrusted() const override {
    return std::nullopt;
  }
};

class PasswordFormHelperTest : public AutofillTestWithWebState {
 public:
  PasswordFormHelperTest()
      : AutofillTestWithWebState(std::make_unique<web::FakeWebClient>()) {
    web::FakeWebClient* web_client =
        static_cast<web::FakeWebClient*>(GetWebClient());
    web_client->SetJavaScriptFeatures(
        {autofill::FormHandlersJavaScriptFeature::GetInstance(),
         autofill::FormUtilJavaScriptFeature::GetInstance(),
         password_manager::PasswordManagerJavaScriptFeature::GetInstance()});
  }

  PasswordFormHelperTest(const PasswordFormHelperTest&) = delete;
  PasswordFormHelperTest& operator=(const PasswordFormHelperTest&) = delete;

  ~PasswordFormHelperTest() override = default;

  void SetUp() override {
    WebTestWithWebState::SetUp();

    IOSPasswordManagerDriverFactory::CreateForWebState(
        web_state(), OCMStrictClassMock([SharedPasswordController class]),
        &password_manager_);

    helper_ = [[PasswordFormHelper alloc] initWithWebState:web_state()];
    ukm::InitializeSourceUrlRecorderForWebState(web_state());
  }

  void TearDown() override {
    WaitForBackgroundTasks();
    helper_ = nil;
    web::WebTestWithWebState::TearDown();
  }

  web::WebFrame* GetMainFrame() {
    password_manager::PasswordManagerJavaScriptFeature* feature =
        password_manager::PasswordManagerJavaScriptFeature::GetInstance();
    return feature->GetWebFramesManager(web_state())->GetMainWebFrame();
  }

  // Sets up unique form ids and returns true if successful.
  bool SetUpUniqueIDs() {
    __block web::WebFrame* main_frame = nullptr;
    bool success =
        WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^bool {
          main_frame = GetMainFrame();
          return main_frame != nullptr;
        });
    if (!success) {
      return false;
    }
    DCHECK(main_frame);

    // Run password forms search to set up unique IDs.
    __block bool complete = false;
    password_manager::PasswordManagerJavaScriptFeature::GetInstance()
        ->FindPasswordFormsInFrame(main_frame,
                                   base::BindOnce(^(NSString* forms) {
                                     complete = true;
                                   }));

    return WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^bool {
      return complete;
    });
  }

  // Returns a valid form submitted message body.
  std::unique_ptr<base::Value> ValidFormSubmittedMessageBody(
      std::string frame_id) {
    return std::make_unique<base::Value>(
        base::Value::Dict()
            .Set("name", "test_form")
            .Set("origin", BaseUrl())
            .Set("fields", base::Value::List().Append(
                               base::Value::Dict()
                                   .Set("name", "test_field")
                                   .Set("form_control_type", "password")))
            .Set("frame_id", frame_id));
  }

  // Returns a script message that can represent a form submission.
  web::ScriptMessage ScriptMessageForSubmit(std::unique_ptr<base::Value> body) {
    return web::ScriptMessage(std::move(body),
                              /*is_user_interacting=*/true,
                              /*is_main_frame=*/true,
                              /*request_url=*/std::nullopt);
  }

 protected:
  // PasswordFormHelper for testing.
  PasswordFormHelper* helper_;
  password_manager::MockPasswordManager password_manager_;
};

struct FindPasswordFormTestData {
  // HTML String of the form.
  NSString* html_string;
  // True if expected to find the form.
  const bool expected_form_found;
  // Expected number of fields in found form.
  const size_t expected_number_of_fields;
  // Expected form name.
  const char* expected_form_name;
};

// Check that HTML forms are converted correctly into PasswordForms.
TEST_F(PasswordFormHelperTest, FindPasswordFormsInView) {
  // clang-format off
  const FindPasswordFormTestData test_data[] = {
    // Normal form: a username and a password element.
    {
      @"<form name='form1'>"
      "<input type='text' name='user0'>"
      "<input type='password' name='pass0'>"
      "</form>",
      true, 2, "form1"
    },
    // User name is captured as an email address (HTML5).
    {
      @"<form name='form1'>"
      "<input type='email' name='email1'>"
      "<input type='password' name='pass1'>"
      "</form>",
      true, 2, "form1"
    },
    // No form found.
    {
      @"<div>",
      false, 0, nullptr
    },
    // Disabled username element.
    {
      @"<form name='form1'>"
      "<input type='text' name='user2' disabled='disabled'>"
      "<input type='password' name='pass2'>"
      "</form>",
      true, 2, "form1"
    },
    // No password element.
    {
      @"<form name='form1'>"
      "<input type='text' name='user3'>"
      "</form>",
      false, 0, nullptr
    },
    // No <form> tag.
    {
      @"<input type='email' name='email1'>"
      "<input type='password' name='pass1'>",
      true, 2, ""
    },
  };
  // clang-format on

  for (const FindPasswordFormTestData& data : test_data) {
    SCOPED_TRACE(testing::Message()
                 << "for html_string="
                 << base::SysNSStringToUTF8(data.html_string));
    LoadHtml(data.html_string);
    __block std::vector<FormData> forms;
    __block BOOL block_was_called = NO;
    [helper_ findPasswordFormsInFrame:GetMainFrame()
                    completionHandler:^(const std::vector<FormData>& result) {
                      block_was_called = YES;
                      forms = result;
                    }];
    EXPECT_TRUE(
        WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^bool() {
          return block_was_called;
        }));
    if (data.expected_form_found) {
      ASSERT_EQ(1U, forms.size());
      EXPECT_EQ(data.expected_number_of_fields, forms[0].fields().size());
      EXPECT_EQ(data.expected_form_name, base::UTF16ToUTF8(forms[0].name()));
    } else {
      ASSERT_TRUE(forms.empty());
    }
  }
}

// A script that resets all text fields, including those in iframes.
static NSString* kClearInputFieldsScript =
    @"function clearInputFields(win) {"
     "  var inputs = win.document.getElementsByTagName('input');"
     "  for (var i = 0; i < inputs.length; i++) {"
     "    inputs[i].value = '';"
     "  }"
     "  var frames = win.frames;"
     "  for (var i = 0; i < frames.length; i++) {"
     "    clearInputFields(frames[i]);"
     "  }"
     "}"
     "clearInputFields(window);";

// A script that runs after autofilling forms.  It returns ids and values of all
// non-empty fields, including those in iframes.
static NSString* kInputFieldValueVerificationScript =
    @"function findAllInputsInFrame(win, prefix) {"
     "  var result = '';"
     "  var inputs = win.document.getElementsByTagName('input');"
     "  for (var i = 0; i < inputs.length; i++) {"
     "    var input = inputs[i];"
     "    if (input.value) {"
     "      result += prefix + input.id + '=' + input.value + ';';"
     "    }"
     "  }"
     "  var frames = win.frames;"
     "  for (var i = 0; i < frames.length; i++) {"
     "    result += findAllInputsInFrame("
     "        frames[i], prefix + frames[i].name +'.');"
     "  }"
     "  return result;"
     "};"
     "function findAllInputs(win) {"
     "  return findAllInputsInFrame(win, '');"
     "};"
     "findAllInputs(window);";

// Tests that filling password forms with fill data works correctly.
TEST_F(PasswordFormHelperTest, FillPasswordFormWithFillData_Success) {
  ukm::TestAutoSetUkmRecorder test_recorder;
  base::HistogramTester histogram_tester;
  LoadHtml(
      @"<form><input id='u1' type='text' name='un1'>"
       "<input id='p1' type='password' name='pw1'></form>");

  ASSERT_TRUE(SetUpUniqueIDs());
  const std::string base_url = BaseUrl();
  FormRendererId form_id(1);
  FieldRendererId username_field_id(2);
  const std::u16string username_value = u"[email protected]";
  FieldRendererId password_field_id(3);
  const std::u16string password_value = u"super!secret";
  FillData fill_data;
  SetFillData(base_url, form_id.value(), username_field_id.value(),
              base::UTF16ToUTF8(username_value).c_str(),
              password_field_id.value(),
              base::UTF16ToUTF8(password_value).c_str(), &fill_data);

  web::WebFrame* frame = GetMainFrame();

  // Expect calls to the PasswordManager to update its state from the filled
  // inputs.
  IOSPasswordManagerDriver* driver =
      IOSPasswordManagerDriverFactory::FromWebStateAndWebFrame(web_state(),
                                                               frame);
  EXPECT_CALL(password_manager_,
              UpdateStateOnUserInput(
                  driver, std::make_optional<FormRendererId>(form_id),
                  username_field_id, username_value));
  EXPECT_CALL(password_manager_,
              UpdateStateOnUserInput(
                  driver, std::make_optional<FormRendererId>(form_id),
                  password_field_id, password_value));

  __block bool called = false;
  __block BOOL succeeded = false;
  [helper_ fillPasswordFormWithFillData:fill_data
                                inFrame:frame
                       triggeredOnField:username_field_id
                      completionHandler:^(BOOL success) {
                        called = true;
                        succeeded = success;
                      }];

  // Wait on the JS call to be completed.
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
    return called;
  }));

  // Verify that the completion callback is called with success as a result.
  EXPECT_TRUE(succeeded);

  // Verify that the username and password inputs were filled with their
  // respective value.
  id result = ExecuteJavaScript(kInputFieldValueVerificationScript);
  EXPECT_NSEQ(@"[email protected];p1=super!secret;", result);

  // Check that username and password fields were updated as filled in the
  // FieldDataManager.
  autofill::FieldDataManager* fieldDataManager =
      autofill::FieldDataManagerFactoryIOS::FromWebFrame(GetMainFrame());
  EXPECT_TRUE(fieldDataManager->WasAutofilledOnUserTrigger(username_field_id));
  EXPECT_TRUE(fieldDataManager->WasAutofilledOnUserTrigger(password_field_id));

  // Verify that the fill operation was recorded as a success.
  histogram_tester.ExpectUniqueSample("PasswordManager.FillingSuccessIOS", true,
                                      1);

  // Check recorded UKM.
  auto entries = test_recorder.GetEntriesByName(
      ukm::builders::PasswordManager_PasswordFillingIOS::kEntryName);
  // Expect one recorded metric.
  ASSERT_EQ(1u, entries.size());
  test_recorder.ExpectEntryMetric(entries[0], "FillingSuccess", true);
}

// Tests that filling password form data can succeeds while not filling
// anything.
TEST_F(PasswordFormHelperTest, FillPasswordFormWithFillData_Success_NoFill) {
  ukm::TestAutoSetUkmRecorder test_recorder;
  base::HistogramTester histogram_tester;
  // Set the username field as disabled and set a value for the password so it
  // is possible to have a succesful fill operation that returns success but
  // that doesn't fill anything in reality.
  LoadHtml(@"<form><input id='u1' type='text' name='un1' value='test-username' "
           @"disabled>"
            "<input id='p1' type='password' name='pw1' "
            "value='test-password'></form>");

  ASSERT_TRUE(SetUpUniqueIDs());

  // Don't expect calls to the PasswordManager to update its state when failure
  // to fill.
  EXPECT_CALL(password_manager_, UpdateStateOnUserInput).Times(0);

  const std::string base_url = BaseUrl();
  FieldRendererId username_field_id(2);
  FieldRendererId password_field_id(3);
  FillData fill_data;
  // Set fill data with a password that is the same as the one that is already
  // in the password field.
  SetFillData(base_url, 1, username_field_id.value(), "test-username",
              password_field_id.value(), "test-password", &fill_data);

  __block bool called = false;
  __block BOOL succeeded = false;
  [helper_ fillPasswordFormWithFillData:fill_data
                                inFrame:GetMainFrame()
                       triggeredOnField:username_field_id
                      completionHandler:^(BOOL success) {
                        called = true;
                        succeeded = success;
                      }];

  // Wait on the JS call to be completed.
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
    return called;
  }));

  // Verify that the completion callback is called with success as the result.
  EXPECT_TRUE(succeeded);

  // Verify that the username and password inputs still hold their value.
  id result = ExecuteJavaScript(kInputFieldValueVerificationScript);
  EXPECT_NSEQ(@"u1=test-username;p1=test-password;", result);

  // Check that username and password fields were still updated even if they
  // were not filled. This odd behavior is kept to not skew metrics downstream
  // (e.g. PasswordManager.FillingAssistance).
  autofill::FieldDataManager* fieldDataManager =
      autofill::FieldDataManagerFactoryIOS::FromWebFrame(GetMainFrame());
  EXPECT_TRUE(fieldDataManager->WasAutofilledOnUserTrigger(username_field_id));
  EXPECT_TRUE(fieldDataManager->WasAutofilledOnUserTrigger(password_field_id));

  // Verify that the fill operation was recorded as a success.
  histogram_tester.ExpectUniqueSample("PasswordManager.FillingSuccessIOS", true,
                                      1);

  // Check recorded UKM.
  auto entries = test_recorder.GetEntriesByName(
      ukm::builders::PasswordManager_PasswordFillingIOS::kEntryName);
  // Expect one recorded metric.
  ASSERT_EQ(1u, entries.size());
  test_recorder.ExpectEntryMetric(entries[0], "FillingSuccess", true);
}

// Tests that failure in filling password forms with fill data is correctly
// handled.
TEST_F(PasswordFormHelperTest, FillPasswordFormWithFillData_Failure) {
  ukm::TestAutoSetUkmRecorder test_recorder;
  base::HistogramTester histogram_tester;

  LoadHtml(@"<form><input id='p1' type='password' name='pw1'></form>");
  web::WebFrame* frame = GetMainFrame();

  ASSERT_TRUE(SetUpUniqueIDs());

  // Don't expect calls to the PasswordManager to update its state when failure
  // to fill.
  EXPECT_CALL(password_manager_, UpdateStateOnUserInput).Times(0);

  const std::string base_url = BaseUrl();
  FieldRendererId username_field_id(0);
  // The password renderer id does not exist, that's why the filling will fail
  FieldRendererId password_field_id(404);
  FillData fill_data;
  SetFillData(base_url, 1, username_field_id.value(), "",
              password_field_id.value(), "super!secret", &fill_data);

  __block bool called = false;
  __block BOOL succeeded = false;
  [helper_ fillPasswordFormWithFillData:fill_data
                                inFrame:frame
                       triggeredOnField:username_field_id
                      completionHandler:^(BOOL success) {
                        called = true;
                        succeeded = success;
                      }];

  // Wait on the JS call to be completed.
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
    return called;
  }));

  // Verify that the completion callback is called with failure.
  EXPECT_FALSE(succeeded);

  // Check that username and password fields were NOT updated as filled in the
  // FieldDataManager.
  autofill::FieldDataManager* fieldDataManager =
      autofill::FieldDataManagerFactoryIOS::FromWebFrame(frame);
  EXPECT_FALSE(fieldDataManager->WasAutofilledOnUserTrigger(username_field_id));
  EXPECT_FALSE(fieldDataManager->WasAutofilledOnUserTrigger(password_field_id));

  // Verify that the fill operation was recorded as a failure.
  histogram_tester.ExpectUniqueSample("PasswordManager.FillingSuccessIOS",
                                      false, 1);
  // Check recorded UKM.
  auto entries = test_recorder.GetEntriesByName(
      ukm::builders::PasswordManager_PasswordFillingIOS::kEntryName);
  // Expect one recorded metric.
  ASSERT_EQ(1u, entries.size());
  test_recorder.ExpectEntryMetric(entries[0], "FillingSuccess", false);
}

// Tests that a form with username typed by user is not refilled when
// the user selects a filling suggestion on password field.
TEST_F(PasswordFormHelperTest,
       FillPasswordIntoForm_UserTypedUsername_FillFromPassword) {
  LoadHtml(@"<form><input id='u1' type='text' name='un1'>"
            "<input id='p1' type='password' name='pw1'></form>");

  ASSERT_TRUE(SetUpUniqueIDs());

  FormRendererId form_id(1);
  FieldRendererId username_field_id(2);
  const std::u16string username_value = u"[email protected]";
  FieldRendererId password_field_id(3);
  const std::u16string password_value = u"store!pw";

  web::WebFrame* frame = GetMainFrame();

  // Type on username field.
  ExecuteJavaScript(
      @"document.getElementById('u1').value = '[email protected]';");
  [helper_ updateFieldDataOnUserInput:username_field_id
                              inFrame:GetMainFrame()
                           inputValue:@"[email protected]"];

  // Try to autofill the form.
  FillData fill_data;
  SetFillData(BaseUrl(), form_id.value(), username_field_id.value(),
              base::UTF16ToUTF8(username_value).c_str(),
              password_field_id.value(),
              base::UTF16ToUTF8(password_value).c_str(), &fill_data);

  IOSPasswordManagerDriver* driver =
      IOSPasswordManagerDriverFactory::FromWebStateAndWebFrame(web_state(),
                                                               frame);
  // Don't expect to update the state for the username field because it was
  // skipped.
  EXPECT_CALL(password_manager_,
              UpdateStateOnUserInput(
                  driver, std::make_optional<FormRendererId>(form_id),
                  username_field_id, username_value))
      .Times(0);
  // Expect a state update on the password field.
  EXPECT_CALL(password_manager_,
              UpdateStateOnUserInput(
                  driver, std::make_optional<FormRendererId>(form_id),
                  password_field_id, password_value));

  __block bool called = NO;
  __block bool succeeded = NO;
  [helper_ fillPasswordFormWithFillData:fill_data
                                inFrame:GetMainFrame()
                       triggeredOnField:password_field_id
                      completionHandler:^(BOOL success) {
                        called = YES;
                        succeeded = success;
                      }];

  // Wait on the JS call to be completed.
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
    return called;
  }));

  // Verify that the completion callback is called with success as the result.
  EXPECT_TRUE(succeeded);

  // Check that the password field was updated as filled in the
  // FieldDataManager but not the username as it wasn't filled.
  autofill::FieldDataManager* fieldDataManager =
      autofill::FieldDataManagerFactoryIOS::FromWebFrame(GetMainFrame());
  EXPECT_TRUE(fieldDataManager->WasAutofilledOnUserTrigger(username_field_id));
  EXPECT_TRUE(fieldDataManager->WasAutofilledOnUserTrigger(password_field_id));

  // Verify that the password was filled.
  id result = ExecuteJavaScript(kInputFieldValueVerificationScript);
  EXPECT_NSEQ(@"[email protected];p1=store!pw;", result);
}

// Tests that a form with username typed by user is overriden by fill when
// the user selects a filling suggestion on usernmae field.
TEST_F(PasswordFormHelperTest,
       FillPasswordIntoForm_UserTypedUsername_FillFromUsername) {
  LoadHtml(@"<form><input id='u1' type='text' name='un1'>"
            "<input id='p1' type='password' name='pw1'></form>");
  ASSERT_TRUE(SetUpUniqueIDs());

  const std::string base_url = BaseUrl();
  FieldRendererId username_field_id(2);
  FieldRendererId password_field_id(3);

  // Type on username field.
  ExecuteJavaScript(
      @"document.getElementById('u1').value = '[email protected]';");
  [helper_ updateFieldDataOnUserInput:username_field_id
                              inFrame:GetMainFrame()
                           inputValue:@"[email protected]"];

  FillData fill_data;
  SetFillData(base_url, 1, username_field_id.value(), "[email protected]",
              password_field_id.value(), "super!secret", &fill_data);
  __block bool called = false;
  __block BOOL succeeded = false;
  [helper_ fillPasswordFormWithFillData:fill_data
                                inFrame:GetMainFrame()
                       triggeredOnField:username_field_id
                      completionHandler:^(BOOL success) {
                        called = true;
                        succeeded = success;
                      }];

  // Wait on the JS call to be completed.
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
    return called;
  }));

  // Verify that the completion callback is called with success as a result.
  EXPECT_TRUE(succeeded);

  // Verify that the username and password inputs were filled with their
  // respective value.
  id result = ExecuteJavaScript(kInputFieldValueVerificationScript);
  EXPECT_NSEQ(@"[email protected];p1=super!secret;", result);

  // Check that username and password fields were updated as filled in the
  // FieldDataManager.
  autofill::FieldDataManager* fieldDataManager =
      autofill::FieldDataManagerFactoryIOS::FromWebFrame(GetMainFrame());
  EXPECT_TRUE(fieldDataManager->WasAutofilledOnUserTrigger(username_field_id));
  EXPECT_TRUE(fieldDataManager->WasAutofilledOnUserTrigger(password_field_id));
}

//

// Tests that filling is considered as a failure if the returned result blob
// from the js call is missing a field or is of the wrong type. Tests all
// possibilities in the same test to avoid over boilerplating.
TEST_F(PasswordFormHelperTest, FillUsernameAndPassword_MissingFillResultField) {
  const std::string base_url = BaseUrl();
  web::FakeWebState fake_web_state;
  auto* feature =
      password_manager::PasswordManagerJavaScriptFeature::GetInstance();
  web::ContentWorld content_world = feature->GetSupportedContentWorld();

  // Add feature to support the filling of password form data.
  web::test::OverrideJavaScriptFeatures(GetBrowserState(), {feature});

  auto web_frames_manager = std::make_unique<web::FakeWebFramesManager>();
  auto* web_frames_manager_ptr = web_frames_manager.get();
  fake_web_state.SetWebFramesManager(content_world,
                                     std::move(web_frames_manager));
  fake_web_state.SetBrowserState(GetBrowserState());
  fake_web_state.SetContentIsHTML(true);
  fake_web_state.SetCurrentURL(GURL(base_url));

  std::unique_ptr<web::FakeWebFrame> main_frame =
      web::FakeWebFrame::Create("frameID", true, GURL(base_url));
  main_frame->set_browser_state(GetBrowserState());
  auto* main_frame_ptr = main_frame.get();
  web_frames_manager_ptr->AddWebFrame(std::move(main_frame));

  IOSPasswordManagerDriverFactory::CreateForWebState(
      &fake_web_state, OCMStrictClassMock([SharedPasswordController class]),
      &password_manager_);
  // Don't expect calls to the PasswordManager to update its state when failure
  // to fill.
  EXPECT_CALL(password_manager_, UpdateStateOnUserInput).Times(0);

  FieldRendererId username_field_id(2);
  FieldRendererId password_field_id(3);
  FillData fill_data;
  SetFillData(base_url, 1, username_field_id.value(), "test-username",
              password_field_id.value(), "super!secret", &fill_data);

  PasswordFormHelper* helper =
      [[PasswordFormHelper alloc] initWithWebState:&fake_web_state];

  // Test the missing did_fill_username field.
  {
    auto result = base::Value(base::Value::Dict()
                                  .Set("did_fill_password", base::Value(true))
                                  .Set("did_attempt_fill", base::Value(true)));
    main_frame_ptr->AddJsResultForFunctionCall(&result,
                                               "passwords.fillPasswordForm");
    __block bool called = false;
    __block BOOL succeeded = false;
    [helper fillPasswordFormWithFillData:fill_data
                                 inFrame:main_frame_ptr
                        triggeredOnField:username_field_id
                       completionHandler:^(BOOL success) {
                         called = true;
                         succeeded = success;
                       }];
    WaitForBackgroundTasks();
    ASSERT_TRUE(called);
    EXPECT_FALSE(succeeded);
  }

  // Test the missing did_fill_password field.
  {
    auto result = base::Value(base::Value::Dict()
                                  .Set("did_fill_username", base::Value(true))
                                  .Set("did_attempt_fill", base::Value(true)));
    main_frame_ptr->AddJsResultForFunctionCall(&result,
                                               "passwords.fillPasswordForm");
    __block bool called = false;
    __block BOOL succeeded = false;
    [helper fillPasswordFormWithFillData:fill_data
                                 inFrame:main_frame_ptr
                        triggeredOnField:username_field_id
                       completionHandler:^(BOOL success) {
                         called = true;
                         succeeded = success;
                       }];
    WaitForBackgroundTasks();
    ASSERT_TRUE(called);
    EXPECT_FALSE(succeeded);
  }

  // Test the missing did_attempt_fill field.
  {
    auto result = base::Value(base::Value::Dict()
                                  .Set("did_fill_username", base::Value(true))
                                  .Set("did_fill_password", base::Value(true)));
    main_frame_ptr->AddJsResultForFunctionCall(&result,
                                               "passwords.fillPasswordForm");
    __block bool called = false;
    __block BOOL succeeded = false;
    [helper fillPasswordFormWithFillData:fill_data
                                 inFrame:main_frame_ptr
                        triggeredOnField:username_field_id
                       completionHandler:^(BOOL success) {
                         called = true;
                         succeeded = success;
                       }];
    WaitForBackgroundTasks();
    ASSERT_TRUE(called);
    EXPECT_FALSE(succeeded);
  }

  // Test the wrong type of returned result.
  {
    base::Value result("string-result");
    main_frame_ptr->AddJsResultForFunctionCall(&result,
                                               "passwords.fillPasswordForm");
    __block bool called = false;
    __block BOOL succeeded = false;
    [helper fillPasswordFormWithFillData:fill_data
                                 inFrame:main_frame_ptr
                        triggeredOnField:username_field_id
                       completionHandler:^(BOOL success) {
                         called = true;
                         succeeded = success;
                       }];
    WaitForBackgroundTasks();
    ASSERT_TRUE(called);
    EXPECT_FALSE(succeeded);
  }

  // Test a nullptr result.
  {
    main_frame_ptr->AddJsResultForFunctionCall(nullptr,
                                               "passwords.fillPasswordForm");
    __block bool called = false;
    __block BOOL succeeded = false;
    [helper fillPasswordFormWithFillData:fill_data
                                 inFrame:main_frame_ptr
                        triggeredOnField:username_field_id
                       completionHandler:^(BOOL success) {
                         called = true;
                         succeeded = success;
                       }];
    WaitForBackgroundTasks();
    ASSERT_TRUE(called);
    EXPECT_FALSE(succeeded);
  }
}

// Tests that extractPasswordFormData extracts wanted form on page with mutiple
// forms.
TEST_F(PasswordFormHelperTest, ExtractPasswordFormData) {
  LoadHtml(@"<form><input id='u1' type='text' name='un1'>"
            "<input id='p1' type='password' name='pw1'></form>"
            "<form><input id='u2' type='text' name='un2'>"
            "<input id='p2' type='password' name='pw2'></form>"
            "<form><input id='u3' type='text' name='un3'>"
            "<input id='p3' type='password' name='pw3'></form>");

  ASSERT_TRUE(SetUpUniqueIDs());

  __block int call_counter = 0;
  __block int success_counter = 0;
  __block FormData result = FormData();
  [helper_ extractPasswordFormData:FormRendererId(1)
                           inFrame:GetMainFrame()
                 completionHandler:^(BOOL complete, const FormData& form) {
                   ++call_counter;
                   if (complete) {
                     ++success_counter;
                     result = form;
                   }
                 }];
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
    return call_counter == 1;
  }));
  EXPECT_EQ(1, success_counter);
  EXPECT_EQ(result.renderer_id(), FormRendererId(1));

  call_counter = 0;
  success_counter = 0;
  result = FormData();

  [helper_ extractPasswordFormData:FormRendererId(404)
                           inFrame:GetMainFrame()
                 completionHandler:^(BOOL complete, const FormData& form) {
                   ++call_counter;
                   if (complete) {
                     ++success_counter;
                     result = form;
                   }
                 }];
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
    return call_counter == 1;
  }));
  EXPECT_EQ(0, success_counter);
}

// Tests that the form submit message is fully handled when in the correct
// format and with the minimal viable content.
TEST_F(PasswordFormHelperTest, HandleFormSubmittedMessage) {
  id delegate = OCMStrictProtocolMock(@protocol(PasswordFormHelperDelegate));
  OCMExpect([[delegate ignoringNonObjectArgs] formHelper:helper_
                                           didSubmitForm:FormData()
                                                 inFrame:GetMainFrame()]);
  helper_.delegate = delegate;

  LoadHtml(@"<p>");

  // Set a message with the minimal viable body to succeed in form data
  // extraction.
  web::ScriptMessage submit_message = ScriptMessageForSubmit(
      ValidFormSubmittedMessageBody(GetMainFrame()->GetFrameId()));

  HandleSubmittedFormStatus status =
      [helper_ handleFormSubmittedMessage:submit_message];

  EXPECT_EQ(HandleSubmittedFormStatus::kHandled, status);

  EXPECT_OCMOCK_VERIFY(delegate);
}

// Tests that the form submit message isn't handled when the message isn't in
// the correct format.
TEST_F(PasswordFormHelperTest, HandleFormSubmittedMessage_InvalidFormat) {
  // Set the delegate mock in a way that the test will crash if there is any
  // delegate call to handle the message.
  id delegate = OCMStrictProtocolMock(@protocol(PasswordFormHelperDelegate));
  helper_.delegate = delegate;

  LoadHtml(@"<p>");

  // Set the message value content as a string which is an invalid format
  // because a dictionary is expected.
  auto invalid_body =
      std::make_unique<base::Value>(base::Value("invalid_because_expect_dict"));

  web::ScriptMessage submit_message =
      ScriptMessageForSubmit(std::move(invalid_body));

  HandleSubmittedFormStatus status =
      [helper_ handleFormSubmittedMessage:submit_message];

  EXPECT_EQ(HandleSubmittedFormStatus::kRejectedMessageBodyNotADict, status);

  // Verify that the delegate is never called because the message isn't handled
  // because of the early return.
  EXPECT_OCMOCK_VERIFY(delegate);
}

// Tests that the form submit message isn't handled when there is no trusted URL
// loaded in the webstate.
TEST_F(PasswordFormHelperTest, HandleFormSubmittedMessage_NoTrustedUrl) {
  FakeWebStateWithoutTrustedCommittedUrl web_state;
  PasswordFormHelper* helper =
      [[PasswordFormHelper alloc] initWithWebState:&web_state];

  // Set the delegate mock in a way that the test will crash if there is any
  // delegate call to handle the message.
  id delegate = OCMStrictProtocolMock(@protocol(PasswordFormHelperDelegate));
  helper.delegate = delegate;

  // Set a dummy message.
  web::ScriptMessage submit_message =
      ScriptMessageForSubmit(std::make_unique<base::Value>("whatever"));

  HandleSubmittedFormStatus status =
      [helper handleFormSubmittedMessage:submit_message];

  EXPECT_EQ(HandleSubmittedFormStatus::kRejectedNoTrustedUrl, status);

  // Verify that the delegate is never called because the message isn't handled
  // because of the early return.
  EXPECT_OCMOCK_VERIFY(delegate);
}

// Tests that the form submit message isn't handled when there is no webstate.
TEST_F(PasswordFormHelperTest, HandleFormSubmittedMessage_NoWebState) {
  // Set the delegate mock in a way that the test will crash if there is any
  // delegate call to handle the message.
  id delegate = OCMStrictProtocolMock(@protocol(PasswordFormHelperDelegate));
  helper_.delegate = delegate;

  // Set a dummy message.
  web::ScriptMessage submit_message =
      ScriptMessageForSubmit(std::make_unique<base::Value>("whatever"));

  // Destroying the webstate will nullify the webstate pointer in the helper.
  DestroyWebState();

  HandleSubmittedFormStatus status =
      [helper_ handleFormSubmittedMessage:submit_message];

  EXPECT_EQ(HandleSubmittedFormStatus::kRejectedNoWebState, status);

  // Verify that the delegate is never called because the message isn't handled
  // because of the early return.
  EXPECT_OCMOCK_VERIFY(delegate);
}

// Tests that the form submit message isn't handled when there is no frame
// matching the provided frame id.
TEST_F(PasswordFormHelperTest, HandleFormSubmittedMessage_NoFormMatchingId) {
  id delegate = OCMStrictProtocolMock(@protocol(PasswordFormHelperDelegate));
  helper_.delegate = delegate;

  LoadHtml(@"<p>");

  // Set a message with an nonexisting frame id.
  web::ScriptMessage submit_message = ScriptMessageForSubmit(
      ValidFormSubmittedMessageBody("nonexisting_frame_id"));

  HandleSubmittedFormStatus status =
      [helper_ handleFormSubmittedMessage:submit_message];

  EXPECT_EQ(HandleSubmittedFormStatus::kRejectedNoFrameMatchingId, status);

  EXPECT_OCMOCK_VERIFY(delegate);
}

// Tests that the form submit message isn't handled when form data can't be
// extracted from the message's body.
TEST_F(PasswordFormHelperTest, HandleFormSubmittedMessage_CantExtractFormData) {
  id delegate = OCMStrictProtocolMock(@protocol(PasswordFormHelperDelegate));
  helper_.delegate = delegate;

  LoadHtml(@"<p>");

  auto incomplete_message_body = std::make_unique<base::Value>(
      base::Value::Dict().Set("frame_id", GetMainFrame()->GetFrameId()));

  // Set a message with an incomplete body that misses the required keys to be
  // parsed to form data.
  web::ScriptMessage submit_message =
      ScriptMessageForSubmit(std::move(incomplete_message_body));

  HandleSubmittedFormStatus status =
      [helper_ handleFormSubmittedMessage:submit_message];

  EXPECT_EQ(HandleSubmittedFormStatus::kRejectedCantExtractFormData, status);

  EXPECT_OCMOCK_VERIFY(delegate);
}

}  // namespace

NS_ASSUME_NONNULL_END