chromium/chrome/browser/password_manager/android/generated_password_saved_message_delegate_unittest.cc

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/password_manager/android/generated_password_saved_message_delegate.h"

#include <algorithm>
#include <memory>

#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/functional/bind.h"
#include "base/functional/callback_forward.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/mock_callback.h"
#include "base/test/with_feature_override.h"
#include "chrome/browser/android/android_theme_resources.h"
#include "chrome/browser/android/resource_mapper.h"
#include "chrome/browser/password_manager/android/add_username_dialog/add_username_dialog_bridge.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "components/messages/android/mock_message_dispatcher_bridge.h"
#include "components/password_manager/core/browser/mock_password_form_manager_for_ui.h"
#include "components/password_manager/core/browser/password_form.h"
#include "components/password_manager/core/common/password_manager_features.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/test/web_contents_tester.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/l10n/l10n_util.h"

using password_manager::MockPasswordFormManagerForUI;
using password_manager::PasswordFormManagerForUI;

namespace {
constexpr char16_t kDefaultUrl[] = u"http://example.com";
constexpr char16_t kUsername[] = u"username_42";
constexpr char16_t kPassword[] = u"password_42";

using testing::_;
using testing::AtMost;
using testing::ByMove;
using testing::InvokeWithoutArgs;
using testing::Return;
using testing::Sequence;

class MockJniDelegate : public AddUsernameDialogBridge::JniDelegate {
 public:
  MockJniDelegate() = default;
  ~MockJniDelegate() override = default;

  MOCK_METHOD((void),
              Create,
              (const gfx::NativeWindow, AddUsernameDialogBridge*),
              (override));
  MOCK_METHOD((void),
              ShowAddUsernameDialog,
              (const std::u16string&),
              (override));
  MOCK_METHOD((void), Dismiss, (), (override));
};

}  // namespace

class GeneratedPasswordSavedMessageDelegateTest
    : public ChromeRenderViewHostTestHarness {
 public:
  GeneratedPasswordSavedMessageDelegateTest();

 protected:
  void SetUp() override;
  void TearDown() override;

  std::unique_ptr<MockPasswordFormManagerForUI> CreateFormManager(
      const GURL& url);

  void SetUsernameAndPassword(std::u16string username, std::u16string password);
  void DismissMessage();
  std::unique_ptr<AddUsernameDialogBridge> CreateAddUsernameBridge(
      std::unique_ptr<MockJniDelegate> mock_jni);
  messages::MessageWrapper* GetMessageWrapper() {
    return delegate_->message_.get();
  }

  messages::MockMessageDispatcherBridge message_dispatcher_bridge_;
  std::unique_ptr<GeneratedPasswordSavedMessageDelegate> delegate_;
  base::MockCallback<
      GeneratedPasswordSavedMessageDelegate::CreateAddUsernameDialogBridge>
      mock_create_add_username_bridge_factory_;

 private:
  password_manager::PasswordForm form_;
  GURL password_form_url_;
};

GeneratedPasswordSavedMessageDelegateTest::
    GeneratedPasswordSavedMessageDelegateTest() {
  delegate_ = std::make_unique<GeneratedPasswordSavedMessageDelegate>(
      base::PassKey<class GeneratedPasswordSavedMessageDelegateTest>(),
      mock_create_add_username_bridge_factory_.Get());
}

void GeneratedPasswordSavedMessageDelegateTest::SetUp() {
  ChromeRenderViewHostTestHarness::SetUp();
  NavigateAndCommit(GURL(kDefaultUrl));
  messages::MessageDispatcherBridge::SetInstanceForTesting(
      &message_dispatcher_bridge_);
}

void GeneratedPasswordSavedMessageDelegateTest::TearDown() {
  messages::MessageDispatcherBridge::SetInstanceForTesting(nullptr);
  ChromeRenderViewHostTestHarness::TearDown();
}

std::unique_ptr<MockPasswordFormManagerForUI>
GeneratedPasswordSavedMessageDelegateTest::CreateFormManager(
    const GURL& password_form_url) {
  password_form_url_ = password_form_url;
  auto form_manager =
      std::make_unique<testing::NiceMock<MockPasswordFormManagerForUI>>();
  ON_CALL(*form_manager, GetPendingCredentials())
      .WillByDefault(testing::ReturnRef(form_));
  ON_CALL(*form_manager, GetURL())
      .WillByDefault(testing::ReturnRef(password_form_url_));
  return form_manager;
}

void GeneratedPasswordSavedMessageDelegateTest::SetUsernameAndPassword(
    std::u16string username,
    std::u16string password) {
  form_.username_value = std::move(username);
  form_.password_value = std::move(password);
}

void GeneratedPasswordSavedMessageDelegateTest::DismissMessage() {
  EXPECT_CALL(message_dispatcher_bridge_, DismissMessage)
      .WillOnce([](messages::MessageWrapper* message,
                   messages::DismissReason dismiss_reason) {
        message->HandleDismissCallback(base::android::AttachCurrentThread(),
                                       static_cast<int>(dismiss_reason));
      });
  delegate_->DismissPromptInternal();
  EXPECT_EQ(nullptr, GetMessageWrapper());
}

std::unique_ptr<AddUsernameDialogBridge>
GeneratedPasswordSavedMessageDelegateTest::CreateAddUsernameBridge(
    std::unique_ptr<MockJniDelegate> mock_jni) {
  return std::make_unique<AddUsernameDialogBridge>(
      base::PassKey<class GeneratedPasswordSavedMessageDelegateTest>(),
      std::move(mock_jni));
}

TEST_F(GeneratedPasswordSavedMessageDelegateTest, TestUsernameAddedCallback) {
  auto username_bridge =
      CreateAddUsernameBridge(std::make_unique<MockJniDelegate>());
  auto* username_bridge_raw_ptr = username_bridge.get();
  EXPECT_CALL(mock_create_add_username_bridge_factory_, Run)
      .Times(AtMost(1))
      .WillOnce(Return(ByMove(std::move(username_bridge))));

  SetUsernameAndPassword(u"", kPassword);
  auto form_manager = CreateFormManager(GURL(kDefaultUrl));
  auto* form_manager_raw_ptr = form_manager.get();

  delegate_->ShowPrompt(web_contents(), std::move(form_manager));

  const std::u16string username = u"test username";
  JNIEnv* env = base::android::AttachCurrentThread();
  auto j_string = base::android::ConvertUTF16ToJavaString(env, username);

  {
    testing::InSequence s;
    EXPECT_CALL(*form_manager_raw_ptr, OnUpdateUsernameFromPrompt(username));
    EXPECT_CALL(*form_manager_raw_ptr, Save);
  }
  username_bridge_raw_ptr->OnDialogAccepted(
      env, base::android::JavaParamRef<jstring>(env, j_string.obj()));
}

// Tests that message properties (title, description, icon, button text) are
// set correctly.
TEST_F(GeneratedPasswordSavedMessageDelegateTest, MessagePropertyValues) {
  SetUsernameAndPassword(kUsername, kPassword);
  auto form_manager = CreateFormManager(GURL(kDefaultUrl));

  EXPECT_CALL(message_dispatcher_bridge_, EnqueueMessage);
  delegate_->ShowPrompt(web_contents(), std::move(form_manager));

  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_CONFIRM_SAVED_TITLE),
            GetMessageWrapper()->GetTitle());
  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_OK),
            GetMessageWrapper()->GetPrimaryButtonText());
  EXPECT_EQ(
      l10n_util::GetStringUTF16(
          IDS_PASSWORD_MANAGER_GENERATED_PASSWORD_SAVED_MESSAGE_DESCRIPTION),
      GetMessageWrapper()->GetDescription());
  EXPECT_EQ(ResourceMapper::MapToJavaDrawableId(
                IDR_ANDROID_PASSWORD_MANAGER_LOGO_24DP),
            GetMessageWrapper()->GetIconResourceId());
  DismissMessage();
}

TEST_F(GeneratedPasswordSavedMessageDelegateTest,
       AddUsernameDialogIsDisplayedWhenEmptyUsername) {
  auto username_bridge_jni = std::make_unique<MockJniDelegate>();
  auto* username_bridge_jni_raw_ptr = username_bridge_jni.get();
  auto username_bridge =
      CreateAddUsernameBridge(std::move(username_bridge_jni));
  EXPECT_CALL(mock_create_add_username_bridge_factory_, Run)
      .Times(AtMost(1))
      .WillOnce(Return(ByMove(std::move(username_bridge))));

  SetUsernameAndPassword(u"", kPassword);
  auto form_manager = CreateFormManager(GURL(kDefaultUrl));

  EXPECT_CALL(*username_bridge_jni_raw_ptr, ShowAddUsernameDialog);
  delegate_->ShowPrompt(web_contents(), std::move(form_manager));
}

TEST_F(GeneratedPasswordSavedMessageDelegateTest,
       DialogIsDismissedOnDelegateDestruction) {
  auto username_bridge_jni = std::make_unique<MockJniDelegate>();
  auto* username_bridge_jni_raw_ptr = username_bridge_jni.get();
  auto username_bridge =
      CreateAddUsernameBridge(std::move(username_bridge_jni));
  EXPECT_CALL(mock_create_add_username_bridge_factory_, Run)
      .Times(AtMost(1))
      .WillOnce(Return(ByMove(std::move(username_bridge))));

  SetUsernameAndPassword(u"", kPassword);
  auto form_manager = CreateFormManager(GURL(kDefaultUrl));
  delegate_->ShowPrompt(web_contents(), std::move(form_manager));

  EXPECT_CALL(*username_bridge_jni_raw_ptr, Dismiss);
  delegate_.reset();
}