chromium/ios/chrome/browser/autofill/model/credit_card/autofill_save_card_infobar_delegate_unittest.mm

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

#import <memory>
#import <string>

#import "base/functional/bind.h"
#import "base/test/scoped_feature_list.h"
#import "components/autofill/core/browser/payments/autofill_save_card_delegate.h"
#import "components/autofill/core/browser/payments/payments_autofill_client.h"
#import "components/autofill/core/common/autofill_payments_features.h"
#import "components/autofill/ios/common/features.h"
#import "ios/chrome/browser/autofill/model/credit_card/autofill_save_card_infobar_delegate_ios.h"
#import "testing/gmock/include/gmock/gmock.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/platform_test.h"
#import "third_party/abseil-cpp/absl/types/variant.h"

namespace autofill {

namespace {
constexpr int kNavEntryId = 10;
}  // namespace

class AutofillSaveCardInfoBarDelegateTest : public PlatformTest {
 public:
  void LocalSaveCardPromptCallbackFn(
      payments::PaymentsAutofillClient::SaveCardOfferUserDecision
          user_decision) {
    last_user_decision_ = user_decision;
  }

  void UploadSaveCardPromptCallbackFn(
      payments::PaymentsAutofillClient::SaveCardOfferUserDecision user_decision,
      const payments::PaymentsAutofillClient::UserProvidedCardDetails&
          user_provided_card_details) {
    last_user_decision_ = user_decision;
    last_user_provided_card_details_ = user_provided_card_details;
  }

  void CreditCardUploadCompletionCallbackFn(bool card_saved) {
    card_saved_ = card_saved;
  }

  void OnConfirmationClosedCallbackFn() {
    ran_on_confirmation_closed_callback_ = true;
  }

 protected:
  ~AutofillSaveCardInfoBarDelegateTest() override = default;

  void SetUp() override {
    delegate_ = CreateDelegate(
        /*callback=*/static_cast<
            payments::PaymentsAutofillClient::LocalSaveCardPromptCallback>(
            base::DoNothing()));
  }

  std::unique_ptr<AutofillSaveCardInfoBarDelegateIOS> CreateDelegate(
      absl::variant<
          payments::PaymentsAutofillClient::LocalSaveCardPromptCallback,
          payments::PaymentsAutofillClient::UploadSaveCardPromptCallback>
          save_card_callback) {
    auto save_card_delegate = std::make_unique<AutofillSaveCardDelegate>(
        std::move(save_card_callback),
        payments::PaymentsAutofillClient::SaveCreditCardOptions());
    return std::make_unique<AutofillSaveCardInfoBarDelegateIOS>(
        AutofillSaveCardUiInfo(), std::move(save_card_delegate));
  }

  base::test::ScopedFeatureList feature_list_;
  infobars::InfoBarDelegate::NavigationDetails nav_details_that_expire_{
      .entry_id = kNavEntryId,
      .is_navigation_to_different_page = true,
      .did_replace_entry = false,
      .is_reload = true,
      .is_redirect = false,
      .is_form_submission = false,
      .has_user_gesture = true};
  std::unique_ptr<AutofillSaveCardInfoBarDelegateIOS> delegate_;
  std::optional<payments::PaymentsAutofillClient::SaveCardOfferUserDecision>
      last_user_decision_;
  std::optional<payments::PaymentsAutofillClient::UserProvidedCardDetails>
      last_user_provided_card_details_;
  std::optional<bool> card_saved_;
  bool ran_on_confirmation_closed_callback_ = false;
};

// Tests that the user decision is propagated when accepting local save.
TEST_F(AutofillSaveCardInfoBarDelegateTest, UpdateAndAccept_Local) {
  payments::PaymentsAutofillClient::LocalSaveCardPromptCallback callback =
      base::BindOnce(
          &AutofillSaveCardInfoBarDelegateTest::LocalSaveCardPromptCallbackFn,
          base::Unretained(this));
  std::unique_ptr<AutofillSaveCardInfoBarDelegateIOS> delegate =
      CreateDelegate(std::move(callback));

  EXPECT_TRUE(delegate->UpdateAndAccept(
      /*cardholder_name=*/u"Test User",
      /*expiration_date_month=*/u"04",
      /*expiration_date_year=*/u"24"));

  ASSERT_TRUE(last_user_decision_);
  EXPECT_EQ(
      payments::PaymentsAutofillClient::SaveCardOfferUserDecision::kAccepted,
      last_user_decision_);
}

// Tests that the user decision is propagated when accepting upload.
TEST_F(AutofillSaveCardInfoBarDelegateTest, UpdateAndAccept_Upload) {
  payments::PaymentsAutofillClient::UploadSaveCardPromptCallback callback =
      base::BindOnce(
          &AutofillSaveCardInfoBarDelegateTest::UploadSaveCardPromptCallbackFn,
          base::Unretained(this));
  std::unique_ptr<AutofillSaveCardInfoBarDelegateIOS> delegate =
      CreateDelegate(std::move(callback));

  const std::u16string cardholder_name = u"Test User";
  const std::u16string expiration_date_month = u"04";
  const std::u16string expiration_date_year = u"24";

  EXPECT_TRUE(delegate->UpdateAndAccept(
      /*cardholder_name=*/cardholder_name,
      /*expiration_date_month=*/expiration_date_month,
      /*expiration_date_year=*/expiration_date_year));

  ASSERT_TRUE(last_user_decision_ && last_user_provided_card_details_);
  EXPECT_EQ(
      payments::PaymentsAutofillClient::SaveCardOfferUserDecision::kAccepted,
      last_user_decision_);
  EXPECT_THAT(
      *last_user_provided_card_details_,
      ::testing::FieldsAre(/*cardholder_name=*/cardholder_name,
                           /*expiration_date_month=*/expiration_date_month,
                           /*expiration_date_year=*/expiration_date_year));
}

// Tests that CreditCardUploadCompleted() runs
// credit_card_upload_completion_callback with card successfully saved.
TEST_F(AutofillSaveCardInfoBarDelegateTest,
       CreditCardUploadCompleted_CardSaved) {
  feature_list_.InitAndEnableFeature(
      autofill::features::kAutofillEnableSaveCardLoadingAndConfirmation);

  payments::PaymentsAutofillClient::UploadSaveCardPromptCallback callback =
      base::BindOnce(
          &AutofillSaveCardInfoBarDelegateTest::UploadSaveCardPromptCallbackFn,
          base::Unretained(this));
  std::unique_ptr<AutofillSaveCardInfoBarDelegateIOS> delegate =
      CreateDelegate(std::move(callback));

  base::OnceCallback<void(bool card_saved)>
      credit_card_upload_completion_callback =
          base::BindOnce(&AutofillSaveCardInfoBarDelegateTest::
                             CreditCardUploadCompletionCallbackFn,
                         base::Unretained(this));

  delegate->SetCreditCardUploadCompletionCallback(
      std::move(credit_card_upload_completion_callback));

  delegate->CreditCardUploadCompleted(
      /*card_saved=*/true, /*on_confirmation_closed_callback=*/std::nullopt);
  EXPECT_TRUE(card_saved_.value());
}

// Tests that CreditCardUploadCompleted() runs
// credit_card_upload_completion_callback with card not successfully saved.
TEST_F(AutofillSaveCardInfoBarDelegateTest,
       CreditCardUploadCompleted_CardNotSaved) {
  feature_list_.InitAndEnableFeature(
      autofill::features::kAutofillEnableSaveCardLoadingAndConfirmation);

  payments::PaymentsAutofillClient::UploadSaveCardPromptCallback callback =
      base::BindOnce(
          &AutofillSaveCardInfoBarDelegateTest::UploadSaveCardPromptCallbackFn,
          base::Unretained(this));
  std::unique_ptr<AutofillSaveCardInfoBarDelegateIOS> delegate =
      CreateDelegate(std::move(callback));

  base::OnceCallback<void(bool card_saved)>
      credit_card_upload_completion_callback =
          base::BindOnce(&AutofillSaveCardInfoBarDelegateTest::
                             CreditCardUploadCompletionCallbackFn,
                         base::Unretained(this));

  delegate->SetCreditCardUploadCompletionCallback(
      std::move(credit_card_upload_completion_callback));

  delegate->CreditCardUploadCompleted(
      /*card_saved=*/false, /*on_confirmation_closed_callback=*/std::nullopt);
  EXPECT_FALSE(card_saved_.value());
}

// Tests that CreditCardUploadCompleted() runs
// `on_confirmation_closed_callback_` when infobar is not presenting.
TEST_F(AutofillSaveCardInfoBarDelegateTest,
       CreditCardUploadCompleted_InfobarNotPresenting) {
  feature_list_.InitAndEnableFeature(
      autofill::features::kAutofillEnableSaveCardLoadingAndConfirmation);

  payments::PaymentsAutofillClient::UploadSaveCardPromptCallback callback =
      base::BindOnce(
          &AutofillSaveCardInfoBarDelegateTest::UploadSaveCardPromptCallbackFn,
          base::Unretained(this));
  std::unique_ptr<AutofillSaveCardInfoBarDelegateIOS> delegate =
      CreateDelegate(std::move(callback));

  delegate->SetInfobarIsPresenting(false);

  delegate->CreditCardUploadCompleted(
      /*card_saved=*/true, /*on_confirmation_closed_callback=*/base::BindOnce(
          &AutofillSaveCardInfoBarDelegateTest::OnConfirmationClosedCallbackFn,
          base::Unretained(this)));

  EXPECT_FALSE(card_saved_.has_value());
  EXPECT_TRUE(ran_on_confirmation_closed_callback_);
}

// Tests that `OnConfirmationClosed()` runs
// `on_confirmation_closed_callback_` when it holds a value.
TEST_F(AutofillSaveCardInfoBarDelegateTest, OnConfirmationClosedCallbackSet) {
  feature_list_.InitAndEnableFeature(
      autofill::features::kAutofillEnableSaveCardLoadingAndConfirmation);

  std::unique_ptr<AutofillSaveCardInfoBarDelegateIOS> delegate = CreateDelegate(
      static_cast<
          payments::PaymentsAutofillClient::UploadSaveCardPromptCallback>(
          base::DoNothing()));

  delegate->CreditCardUploadCompleted(
      /*card_saved=*/true, /*on_confirmation_closed_callback=*/base::BindOnce(
          &AutofillSaveCardInfoBarDelegateTest::OnConfirmationClosedCallbackFn,
          base::Unretained(this)));

  delegate->OnConfirmationClosed();
  EXPECT_TRUE(ran_on_confirmation_closed_callback_);
}

// Tests that `OnConfirmationClosed()` doesn't crash when
// `on_confirmation_closed_callback_` doesn't hold a value.
TEST_F(AutofillSaveCardInfoBarDelegateTest,
       OnConfirmationClosedCallbackNotSet) {
  feature_list_.InitAndEnableFeature(
      autofill::features::kAutofillEnableSaveCardLoadingAndConfirmation);

  std::unique_ptr<AutofillSaveCardInfoBarDelegateIOS> delegate = CreateDelegate(
      static_cast<
          payments::PaymentsAutofillClient::UploadSaveCardPromptCallback>(
          base::DoNothing()));

  // `on_confirmation_closed_callback_` doesn't hold a value when
  // card upload is unsuccessful.
  delegate->CreditCardUploadCompleted(
      /*card_saved=*/false, /*on_confirmation_closed_callback=*/std::nullopt);

  delegate->OnConfirmationClosed();
  EXPECT_FALSE(ran_on_confirmation_closed_callback_);
}

// Tests that the infobar expires when reloading the page.
TEST_F(AutofillSaveCardInfoBarDelegateTest, ShouldExpire_True_WhenReload) {
  nav_details_that_expire_.is_reload = true;
  nav_details_that_expire_.entry_id = kNavEntryId;
  delegate_->set_nav_entry_id(kNavEntryId);
  EXPECT_TRUE(delegate_->ShouldExpire(nav_details_that_expire_));
}

// Tests that the infobar expires when new navigation ID.
TEST_F(AutofillSaveCardInfoBarDelegateTest,
       ShouldExpire_True_WhenDifferentNavEntryId) {
  nav_details_that_expire_.is_reload = false;
  nav_details_that_expire_.entry_id = kNavEntryId;
  const int different_nav_id = kNavEntryId - 1;
  delegate_->set_nav_entry_id(different_nav_id);

  EXPECT_TRUE(delegate_->ShouldExpire(nav_details_that_expire_));
}

// Tests that when the sticky infobar is disabled, having a user gesture isn't
// used as a condition to expire the infobar, hence setting the user gesture bit
// to false shouldn't change the returned value.
TEST_F(AutofillSaveCardInfoBarDelegateTest,
       ShouldExpire_True_WhenNoStickyInfobarAndNoUserGesture) {
  feature_list_.InitAndDisableFeature(kAutofillStickyInfobarIos);
  nav_details_that_expire_.has_user_gesture = false;

  EXPECT_TRUE(delegate_->ShouldExpire(nav_details_that_expire_));
}

// Tests that when the sticky infobar is enabled, having a user gesture is
// used as a condition to expire the infobar, hence setting the user gesture bit
// to true should return true.
TEST_F(AutofillSaveCardInfoBarDelegateTest,
       ShouldExpire_True_WhenStickyInfobarAndUserGesture) {
  nav_details_that_expire_.has_user_gesture = true;
  EXPECT_TRUE(delegate_->ShouldExpire(nav_details_that_expire_));
}

// Tests that the infobar doesn't expire when the page is the same.
TEST_F(AutofillSaveCardInfoBarDelegateTest,
       ShouldExpire_False_WhenNoDifferentPage) {
  nav_details_that_expire_.is_navigation_to_different_page = false;
  EXPECT_FALSE(delegate_->ShouldExpire(nav_details_that_expire_));
}

// Tests that the infobar doesn't expire when the page is the same.
TEST_F(AutofillSaveCardInfoBarDelegateTest,
       ShouldExpire_False_WhenDidReplaceEntry) {
  nav_details_that_expire_.did_replace_entry = true;
  EXPECT_FALSE(delegate_->ShouldExpire(nav_details_that_expire_));
}

// Tests that the infobar doesn't expire when redirect.
TEST_F(AutofillSaveCardInfoBarDelegateTest, ShouldExpire_False_WhenRedirect) {
  nav_details_that_expire_.is_redirect = true;
  EXPECT_FALSE(delegate_->ShouldExpire(nav_details_that_expire_));
}

// Tests that the infobar doesn't expire when form submission.
TEST_F(AutofillSaveCardInfoBarDelegateTest,
       ShouldExpire_False_WhenFormSubmission) {
  nav_details_that_expire_.is_form_submission = true;
  EXPECT_FALSE(delegate_->ShouldExpire(nav_details_that_expire_));
}

// Tests that the infobar expires when no reload and the navigation entry ID
// didn't change.
TEST_F(AutofillSaveCardInfoBarDelegateTest,
       ShouldExpire_False_WhenNoReloadAndSameNavEntryId) {
  nav_details_that_expire_.is_reload = false;
  nav_details_that_expire_.entry_id = kNavEntryId;
  delegate_->set_nav_entry_id(kNavEntryId);
  EXPECT_FALSE(delegate_->ShouldExpire(nav_details_that_expire_));
}

// Tests that when the sticky infobar is enabled, having a user gesture is
// used as a condition to expire the infobar, hence setting the user gesture bit
// to false should return false.
TEST_F(AutofillSaveCardInfoBarDelegateTest,
       ShouldExpire_False_WhenStickyInfobar) {
  nav_details_that_expire_.has_user_gesture = false;
  EXPECT_FALSE(delegate_->ShouldExpire(nav_details_that_expire_));
}

}  // namespace autofill