blob: f8d6fad510792fa855ff1341f336a319a259c571 [file] [log] [blame]
// Copyright 2019 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/keyboard_accessory/android/payment_method_accessory_controller_impl.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/mock_callback.h"
#include "chrome/browser/keyboard_accessory/android/accessory_controller.h"
#include "chrome/browser/keyboard_accessory/test_utils/android/mock_manual_filling_controller.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/base/testing_profile.h"
#include "components/autofill/content/browser/test_autofill_client_injector.h"
#include "components/autofill/content/browser/test_autofill_driver_injector.h"
#include "components/autofill/content/browser/test_autofill_manager_injector.h"
#include "components/autofill/content/browser/test_content_autofill_client.h"
#include "components/autofill/content/browser/test_content_autofill_driver.h"
#include "components/autofill/content/common/mojom/autofill_agent.mojom.h"
#include "components/autofill/core/browser/autofill_test_utils.h"
#include "components/autofill/core/browser/browser_autofill_manager.h"
#include "components/autofill/core/browser/browser_autofill_manager_test_api.h"
#include "components/autofill/core/browser/data_model/credit_card.h"
#include "components/autofill/core/browser/payments/constants.h"
#include "components/autofill/core/browser/test_autofill_client.h"
#include "components/autofill/core/browser/test_autofill_driver.h"
#include "components/autofill/core/browser/test_browser_autofill_manager.h"
#include "components/autofill/core/browser/test_payments_data_manager.h"
#include "components/autofill/core/browser/test_personal_data_manager.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/autofill/core/common/autofill_payments_features.h"
#include "components/autofill/core/common/form_data.h"
#include "components/autofill/core/common/unique_ids.h"
#include "components/strings/grit/components_strings.h"
#include "components/sync/test/test_sync_service.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/l10n/l10n_util.h"
using testing::_;
using testing::SaveArg;
using IsFillingSourceAvailable = AccessoryController::IsFillingSourceAvailable;
constexpr char kExampleSite[] = "https://example.com";
namespace autofill {
namespace {
AccessorySheetData::Builder PaymentMethodAccessorySheetDataBuilder() {
return AccessorySheetData::Builder(
AccessoryTabType::CREDIT_CARDS,
l10n_util::GetStringUTF16(
IDS_MANUAL_FILLING_CREDIT_CARD_SHEET_TITLE))
.AppendFooterCommand(
l10n_util::GetStringUTF16(
IDS_MANUAL_FILLING_CREDIT_CARD_SHEET_ALL_ADDRESSES_LINK),
AccessoryAction::MANAGE_CREDIT_CARDS);
}
} // namespace
class TestAccessManager : public CreditCardAccessManager {
public:
TestAccessManager(AutofillDriver* driver,
AutofillClient* client,
PersonalDataManager* personal_data)
: CreditCardAccessManager(driver,
client,
personal_data,
/*credit_card_form_event_logger=*/nullptr) {}
void FetchCreditCard(
const CreditCard* card,
OnCreditCardFetchedCallback on_credit_card_fetched) override {
std::move(on_credit_card_fetched)
.Run(CreditCardFetchResult::kSuccess, card);
}
};
class MockAutofillDriver : public TestContentAutofillDriver {
public:
using TestContentAutofillDriver::TestContentAutofillDriver;
MOCK_METHOD(void,
ApplyFieldAction,
(mojom::FieldActionType action_type,
mojom::ActionPersistence action_persistence,
const FieldGlobalId& field,
const std::u16string&),
(override));
};
class PaymentMethodAccessoryControllerTest
: public ChromeRenderViewHostTestHarness {
public:
void SetUp() override {
ChromeRenderViewHostTestHarness::SetUp();
NavigateAndCommit(GURL(kExampleSite));
SetFormOrigin(GURL(kExampleSite));
FocusWebContentsOnMainFrame();
test_api(autofill_manager())
.set_credit_card_access_manager(std::make_unique<TestAccessManager>(
&autofill_driver(), &autofill_client(), &data_manager_));
PaymentMethodAccessoryControllerImpl::CreateForWebContentsForTesting(
web_contents(), mock_mf_controller_.AsWeakPtr(), &data_manager_,
&autofill_manager(), &autofill_driver());
data_manager_.SetPrefService(profile()->GetPrefs());
data_manager_.SetSyncServiceForTest(&sync_service_);
}
void TearDown() override {
data_manager_.SetSyncServiceForTest(nullptr);
data_manager_.SetPrefService(nullptr);
data_manager_.test_payments_data_manager().ClearCreditCards();
data_manager_.test_payments_data_manager().ClearCreditCardOfferData();
ChromeRenderViewHostTestHarness::TearDown();
}
PaymentMethodAccessoryController* controller() {
return PaymentMethodAccessoryControllerImpl::FromWebContents(web_contents());
}
void SetFormOrigin(GURL origin) {
FormData form;
form.renderer_id = FormRendererId(1);
form.action = origin;
form.main_frame_origin = url::Origin::Create(origin);
autofill_client().set_form_origin(origin);
// Promo codes are filtered by the last_committed_primary_main_frame_url.
autofill_client().set_last_committed_primary_main_frame_url(
GURL(kExampleSite));
}
protected:
TestContentAutofillClient& autofill_client() {
return *autofill_client_injector_[web_contents()];
}
testing::NiceMock<MockAutofillDriver>& autofill_driver() {
return *autofill_driver_injector_[web_contents()];
}
TestBrowserAutofillManager& autofill_manager() {
return *autofill_manager_injector_[web_contents()];
}
syncer::TestSyncService sync_service_;
TestPersonalDataManager data_manager_;
testing::NiceMock<MockManualFillingController> mock_mf_controller_;
base::MockCallback<AccessoryController::FillingSourceObserver>
filling_source_observer_;
private:
TestAutofillClientInjector<TestContentAutofillClient>
autofill_client_injector_;
TestAutofillDriverInjector<testing::NiceMock<MockAutofillDriver>>
autofill_driver_injector_;
TestAutofillManagerInjector<TestBrowserAutofillManager>
autofill_manager_injector_;
};
class PaymentMethodAccessoryControllerTestSupportingPromoCodeOffers
: public PaymentMethodAccessoryControllerTest {
public:
PaymentMethodAccessoryControllerTestSupportingPromoCodeOffers() = default;
};
TEST_F(PaymentMethodAccessoryControllerTest, RefreshSuggestions) {
CreditCard card = test::GetCreditCard();
data_manager_.payments_data_manager().AddCreditCard(card);
AccessorySheetData result(AccessoryTabType::CREDIT_CARDS, std::u16string());
EXPECT_CALL(mock_mf_controller_, RefreshSuggestions(_))
.WillOnce(SaveArg<0>(&result));
ASSERT_TRUE(controller());
controller()->RefreshSuggestions();
EXPECT_EQ(result, controller()->GetSheetData());
EXPECT_EQ(result,
PaymentMethodAccessorySheetDataBuilder()
.AddUserInfo(kVisaCard)
.AppendField(card.ObfuscatedNumberWithVisibleLastFourDigits(),
/*text_to_fill=*/std::u16string(),
card.ObfuscatedNumberWithVisibleLastFourDigits(),
card.guid(),
/*is_obfuscated=*/false,
/*selectable=*/true)
.AppendSimpleField(card.Expiration2DigitMonthAsString())
.AppendSimpleField(card.Expiration4DigitYearAsString())
.AppendSimpleField(card.GetRawInfo(CREDIT_CARD_NAME_FULL))
.AppendSimpleField(std::u16string())
.Build());
}
TEST_F(PaymentMethodAccessoryControllerTest, PreventsFillingInsecureContexts) {
CreditCard card = test::GetCreditCard();
data_manager_.payments_data_manager().AddCreditCard(card);
AccessorySheetData result(autofill::AccessoryTabType::CREDIT_CARDS,
std::u16string());
SetFormOrigin(GURL("http://insecure.http-site.com"));
EXPECT_CALL(mock_mf_controller_, RefreshSuggestions(_))
.WillOnce(SaveArg<0>(&result));
ASSERT_TRUE(controller());
controller()->RefreshSuggestions();
EXPECT_EQ(result, controller()->GetSheetData());
EXPECT_EQ(result,
PaymentMethodAccessorySheetDataBuilder()
.SetWarning(l10n_util::GetStringUTF16(
IDS_AUTOFILL_WARNING_INSECURE_CONNECTION))
.AddUserInfo(kVisaCard)
.AppendField(card.ObfuscatedNumberWithVisibleLastFourDigits(),
/*text_to_fill=*/std::u16string(),
card.ObfuscatedNumberWithVisibleLastFourDigits(),
card.guid(),
/*is_obfuscated=*/false,
/*selectable=*/false)
.AppendField(card.Expiration2DigitMonthAsString(),
card.Expiration2DigitMonthAsString(),
/*is_obfuscated=*/false,
/*selectable=*/false)
.AppendField(card.Expiration4DigitYearAsString(),
card.Expiration4DigitYearAsString(),
/*is_obfuscated=*/false,
/*selectable=*/false)
.AppendField(card.GetRawInfo(CREDIT_CARD_NAME_FULL),
card.GetRawInfo(CREDIT_CARD_NAME_FULL),
/*is_obfuscated=*/false,
/*selectable=*/false)
.AppendField(std::u16string(), std::u16string(),
/*is_obfuscated=*/false,
/*selectable=*/false)
.Build());
}
class PaymentMethodAccessoryControllerCardUnmaskTest
: public PaymentMethodAccessoryControllerTest,
public testing::WithParamInterface<CreditCard::RecordType> {
public:
PaymentMethodAccessoryControllerCardUnmaskTest() = default;
~PaymentMethodAccessoryControllerCardUnmaskTest() override = default;
CreditCard GetCreditCard() {
switch (GetParam()) {
case CreditCard::RecordType::kLocalCard:
return test::GetCreditCard();
case CreditCard::RecordType::kMaskedServerCard:
return test::GetMaskedServerCard();
case CreditCard::RecordType::kFullServerCard:
return test::GetFullServerCard();
case CreditCard::RecordType::kVirtualCard: {
// The PaymentMethodAccessoryController will automatically create a virtual
// card for this masked server card.
CreditCard card = test::GetMaskedServerCard();
card.set_virtual_card_enrollment_state(
CreditCard::VirtualCardEnrollmentState::kEnrolled);
return card;
}
}
}
bool IsMaskedServerCard() {
return GetParam() == CreditCard::RecordType::kMaskedServerCard;
}
};
TEST_P(PaymentMethodAccessoryControllerCardUnmaskTest, CardUnmask) {
// TODO(crbug.com/40165275): Move this into setup once controllers don't push
// updated sheets proactively anymore.
controller()->RegisterFillingSourceObserver(filling_source_observer_.Get());
CreditCard card = GetCreditCard();
data_manager_.payments_data_manager().AddCreditCard(card);
EXPECT_CALL(filling_source_observer_,
Run(controller(), IsFillingSourceAvailable(true)));
ASSERT_TRUE(controller());
controller()->RefreshSuggestions();
AccessorySheetField field(card.ObfuscatedNumberWithVisibleLastFourDigits(),
/*text_to_fill=*/std::u16string(),
card.ObfuscatedNumberWithVisibleLastFourDigits(),
card.guid(),
/*is_obfuscated=*/false,
/*selectable=*/true);
CreditCard card_to_unmask;
content::RenderFrameHost* rfh = web_contents()->GetFocusedFrame();
ASSERT_TRUE(rfh);
FieldGlobalId field_id{.frame_token = LocalFrameToken(*rfh->GetFrameToken()),
.renderer_id = FieldRendererId(123)};
EXPECT_CALL(autofill_driver(),
ApplyFieldAction(mojom::FieldActionType::kReplaceAll,
mojom::ActionPersistence::kFill, field_id,
card.number()));
controller()->OnFillingTriggered(field_id, field);
}
INSTANTIATE_TEST_SUITE_P(
,
PaymentMethodAccessoryControllerCardUnmaskTest,
testing::Values(CreditCard::RecordType::kLocalCard,
CreditCard::RecordType::kMaskedServerCard,
CreditCard::RecordType::kFullServerCard,
CreditCard::RecordType::kVirtualCard));
TEST_F(PaymentMethodAccessoryControllerTest,
RefreshSuggestionsUnmaskedCachedCardNotAdded) {
// Store a full server card in the credit_card_access_manager's
// unmasked_cards_cache.
CreditCard card = test::GetCreditCard();
card.set_record_type(CreditCard::RecordType::kFullServerCard);
data_manager_.payments_data_manager().AddCreditCard(card);
std::u16string cvc = u"123";
autofill_manager().GetCreditCardAccessManager().CacheUnmaskedCardInfo(card,
cvc);
AccessorySheetData result(autofill::AccessoryTabType::CREDIT_CARDS,
std::u16string());
EXPECT_CALL(mock_mf_controller_, RefreshSuggestions)
.WillOnce(SaveArg<0>(&result));
ASSERT_TRUE(controller());
controller()->RefreshSuggestions();
EXPECT_EQ(result, controller()->GetSheetData());
// Verify that the only the obfuscated last four and no cvc is added to the
// accessory sheet data.
EXPECT_EQ(result,
PaymentMethodAccessorySheetDataBuilder()
.AddUserInfo(kVisaCard)
.AppendField(card.ObfuscatedNumberWithVisibleLastFourDigits(),
/*text_to_fill=*/std::u16string(),
card.ObfuscatedNumberWithVisibleLastFourDigits(),
card.guid(),
/*is_obfuscated=*/false,
/*selectable=*/true)
.AppendSimpleField(card.Expiration2DigitMonthAsString())
.AppendSimpleField(card.Expiration4DigitYearAsString())
.AppendSimpleField(card.GetRawInfo(CREDIT_CARD_NAME_FULL))
.AppendSimpleField(std::u16string())
.Build());
}
TEST_F(PaymentMethodAccessoryControllerTest,
RefreshSuggestionsAddsCachedVirtualCards) {
// Add a masked card to PersonalDataManager.
CreditCard unmasked_card = test::GetCreditCard();
data_manager_.payments_data_manager().AddCreditCard(unmasked_card);
// Update the record type to kVirtualCard and add it to the unmasked cards
// cache.
unmasked_card.set_record_type(CreditCard::RecordType::kVirtualCard);
std::u16string cvc = u"123";
autofill_manager().GetCreditCardAccessManager().CacheUnmaskedCardInfo(
unmasked_card, cvc);
AccessorySheetData result(autofill::AccessoryTabType::CREDIT_CARDS,
std::u16string());
EXPECT_CALL(mock_mf_controller_, RefreshSuggestions)
.WillOnce(SaveArg<0>(&result));
ASSERT_TRUE(controller());
controller()->RefreshSuggestions();
std::u16string card_number_for_display = unmasked_card.FullDigitsForDisplay();
std::u16string card_number_for_fill =
unmasked_card.GetRawInfo(CREDIT_CARD_NUMBER);
EXPECT_EQ(result, controller()->GetSheetData());
// Verify that the unmasked virtual card is at the top followed by the masked
// card.
EXPECT_EQ(
result,
PaymentMethodAccessorySheetDataBuilder()
.AddUserInfo(kVisaCard)
.AppendField(/*display_text=*/card_number_for_display,
/*text_to_fill=*/card_number_for_fill,
/*a11y_description=*/card_number_for_fill,
/*id=*/std::string(),
/*is_obfuscated=*/false,
/*selectable=*/true)
.AppendSimpleField(unmasked_card.Expiration2DigitMonthAsString())
.AppendSimpleField(unmasked_card.Expiration4DigitYearAsString())
.AppendSimpleField(unmasked_card.GetRawInfo(CREDIT_CARD_NAME_FULL))
.AppendSimpleField(cvc)
.AddUserInfo(kVisaCard)
.AppendField(
unmasked_card.ObfuscatedNumberWithVisibleLastFourDigits(),
/*text_to_fill=*/std::u16string(),
unmasked_card.ObfuscatedNumberWithVisibleLastFourDigits(),
unmasked_card.guid(),
/*is_obfuscated=*/false,
/*selectable=*/true)
.AppendSimpleField(unmasked_card.Expiration2DigitMonthAsString())
.AppendSimpleField(unmasked_card.Expiration4DigitYearAsString())
.AppendSimpleField(unmasked_card.GetRawInfo(CREDIT_CARD_NAME_FULL))
.AppendSimpleField(std::u16string())
.Build());
}
TEST_F(
PaymentMethodAccessoryControllerTest,
RefreshSuggestionsAddsVirtualCardWhenOriginalCardIsEnrolledForVirtualCards) {
// Add a masked card to PersonalDataManager.
CreditCard masked_card = test::GetMaskedServerCard();
masked_card.set_virtual_card_enrollment_state(
CreditCard::VirtualCardEnrollmentState::kEnrolled);
data_manager_.payments_data_manager().AddCreditCard(masked_card);
AccessorySheetData result(autofill::AccessoryTabType::CREDIT_CARDS,
std::u16string());
EXPECT_CALL(mock_mf_controller_, RefreshSuggestions)
.WillOnce(SaveArg<0>(&result));
ASSERT_TRUE(controller());
controller()->RefreshSuggestions();
std::u16string virtual_card_label =
u"Virtual card " +
masked_card.ObfuscatedNumberWithVisibleLastFourDigits();
EXPECT_EQ(result, controller()->GetSheetData());
// Verify that a virtual card is inserted before the actual masked card.
EXPECT_EQ(
result,
PaymentMethodAccessorySheetDataBuilder()
.AddUserInfo(kMasterCard)
.AppendField(virtual_card_label, /*text_to_fill*/ std::u16string(),
virtual_card_label, masked_card.guid() + "_vcn",
/*is_obfuscated=*/false,
/*selectable=*/true)
.AppendSimpleField(masked_card.Expiration2DigitMonthAsString())
.AppendSimpleField(masked_card.Expiration4DigitYearAsString())
.AppendSimpleField(masked_card.GetRawInfo(CREDIT_CARD_NAME_FULL))
.AppendSimpleField(std::u16string())
.AddUserInfo(kMasterCard)
.AppendField(masked_card.ObfuscatedNumberWithVisibleLastFourDigits(),
/*text_to_fill*/ std::u16string(),
masked_card.ObfuscatedNumberWithVisibleLastFourDigits(),
masked_card.guid(),
/*is_obfuscated=*/false,
/*selectable=*/true)
.AppendSimpleField(masked_card.Expiration2DigitMonthAsString())
.AppendSimpleField(masked_card.Expiration4DigitYearAsString())
.AppendSimpleField(masked_card.GetRawInfo(CREDIT_CARD_NAME_FULL))
.AppendSimpleField(std::u16string())
.Build());
}
TEST_F(PaymentMethodAccessoryControllerTest,
CardArtIsNotShownEvenWhenMetadataIsAvailableAndEnabled) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
features::kAutofillEnableCardArtImage);
// Add a masked card to PersonalDataManager.
CreditCard masked_card = test::GetMaskedServerCard();
masked_card.set_card_art_url(GURL("http://www.example.com/image.png"));
masked_card.set_virtual_card_enrollment_state(
CreditCard::VirtualCardEnrollmentState::kEnrolled);
data_manager_.payments_data_manager().AddCreditCard(masked_card);
AccessorySheetData result(autofill::AccessoryTabType::CREDIT_CARDS,
std::u16string());
EXPECT_CALL(mock_mf_controller_, RefreshSuggestions)
.WillOnce(SaveArg<0>(&result));
ASSERT_TRUE(controller());
controller()->RefreshSuggestions();
EXPECT_EQ(result, controller()->GetSheetData());
// Verify both the virtual card and the masked server card are in the
// suggestions.
EXPECT_EQ(result.user_info_list().size(), 2u);
// Verify card art is not shown for the virtual card.
EXPECT_EQ(result.user_info_list()[0].icon_url(), GURL());
// Verify card art is not shown for the masked server card.
EXPECT_EQ(result.user_info_list()[1].icon_url(), GURL());
}
TEST_F(
PaymentMethodAccessoryControllerTest,
CapitalOneVirtualCardIconIsShownForVirtualCardsEvenWhenMetadataIsNotEnabled) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndDisableFeature(
features::kAutofillEnableCardArtImage);
// Add a masked card to PersonalDataManager.
CreditCard masked_card = test::GetMaskedServerCard();
masked_card.set_card_art_url(GURL(kCapitalOneCardArtUrl));
masked_card.set_virtual_card_enrollment_state(
CreditCard::VirtualCardEnrollmentState::kEnrolled);
data_manager_.payments_data_manager().AddCreditCard(masked_card);
AccessorySheetData result(autofill::AccessoryTabType::CREDIT_CARDS,
std::u16string());
EXPECT_CALL(mock_mf_controller_, RefreshSuggestions)
.WillOnce(SaveArg<0>(&result));
ASSERT_TRUE(controller());
controller()->RefreshSuggestions();
EXPECT_EQ(result, controller()->GetSheetData());
// Verify both the virtual card and the masked server card are in the
// suggestions.
EXPECT_EQ(result.user_info_list().size(), 2u);
// Verify the the Capital One virtual card icon is shown for the virtual card.
EXPECT_EQ(result.user_info_list()[0].icon_url(), GURL(kCapitalOneCardArtUrl));
// Verify card art is not shown for the masked server card.
EXPECT_EQ(result.user_info_list()[1].icon_url(), GURL());
}
// Tests that promo codes are shown.
TEST_F(PaymentMethodAccessoryControllerTestSupportingPromoCodeOffers,
RefreshSuggestionsWithPromoCodeOffers) {
CreditCard card = test::GetCreditCard();
data_manager_.payments_data_manager().AddCreditCard(card);
// Getting a promo code whose |merchant_origins| contains AutofillClient's
// |last_committed_url_|.
AutofillOfferData promo_code_valid = test::GetPromoCodeOfferData(
/*merchant_origin=*/GURL(kExampleSite),
/*is_expired=*/false);
AutofillOfferData promo_code_origin_mismatch = test::GetPromoCodeOfferData(
/*merchant_origin=*/GURL("https://someorigin.com"),
/*is_expired=*/false);
AutofillOfferData promo_code_expired = test::GetPromoCodeOfferData(
/*merchant_origin=*/GURL(kExampleSite),
/*is_expired=*/true);
data_manager_.test_payments_data_manager().AddAutofillOfferData(
promo_code_valid);
data_manager_.test_payments_data_manager().AddAutofillOfferData(
promo_code_origin_mismatch);
data_manager_.test_payments_data_manager().AddAutofillOfferData(
promo_code_expired);
AccessorySheetData result(autofill::AccessoryTabType::CREDIT_CARDS,
std::u16string());
EXPECT_CALL(mock_mf_controller_, RefreshSuggestions(_))
.WillOnce(SaveArg<0>(&result));
ASSERT_TRUE(controller());
controller()->RefreshSuggestions();
EXPECT_EQ(result, controller()->GetSheetData());
// Only valid promo code should appear in the AccessorySheet.
EXPECT_EQ(result,
PaymentMethodAccessorySheetDataBuilder()
.AddUserInfo(kVisaCard)
.AppendField(card.ObfuscatedNumberWithVisibleLastFourDigits(),
/*text_to_fill=*/std::u16string(),
card.ObfuscatedNumberWithVisibleLastFourDigits(),
card.guid(),
/*is_obfuscated=*/false,
/*selectable=*/true)
.AppendSimpleField(card.Expiration2DigitMonthAsString())
.AppendSimpleField(card.Expiration4DigitYearAsString())
.AppendSimpleField(card.GetRawInfo(CREDIT_CARD_NAME_FULL))
.AppendSimpleField(std::u16string())
.AddPromoCodeInfo(
base::ASCIIToUTF16(promo_code_valid.GetPromoCode()),
base::ASCIIToUTF16(
promo_code_valid.GetDisplayStrings().value_prop_text))
.Build());
}
} // namespace autofill