chromium/ios/chrome/browser/ui/settings/autofill/autofill_add_credit_card_mediator_unittest.mm

// 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.

#import "ios/chrome/browser/ui/settings/autofill/autofill_add_credit_card_mediator.h"

#import "base/strings/sys_string_conversions.h"
#import "base/test/metrics/histogram_tester.h"
#import "base/test/metrics/user_action_tester.h"
#import "components/autofill/core/browser/autofill_test_utils.h"
#import "components/autofill/core/browser/payments_data_manager.h"
#import "components/autofill/core/browser/test_personal_data_manager.h"
#import "ios/chrome/browser/ui/settings/autofill/autofill_add_credit_card_mediator_delegate.h"
#import "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"

using autofill::CreditCard;
using testing::AllOf;
using testing::Contains;
using testing::Eq;
using testing::Property;
using testing::SizeIs;

static NSString* const kTestCardName = @"TestName";
static NSString* const kTestCardNumber = @"4111111111111111";
static NSString* const kTestExpirationMonth = @"01";
static NSString* const kTestCardNickname = @"nickname";

class AutofillAddCreditCardMediatorTest : public PlatformTest {
 protected:
  AutofillAddCreditCardMediatorTest() {
    add_credit_card_mediator_delegate_mock_ =
        OCMProtocolMock(@protocol(AddCreditCardMediatorDelegate));

    add_credit_card_mediator_ = [[AutofillAddCreditCardMediator alloc]
           initWithDelegate:add_credit_card_mediator_delegate_mock_
        personalDataManager:&personal_data_manager_];
  }

  NSString* TestExpirationYear() {
    return base::SysUTF8ToNSString(autofill::test::NextYear());
  }

  autofill::TestPersonalDataManager personal_data_manager_;
  AutofillAddCreditCardMediator* add_credit_card_mediator_;
  id add_credit_card_mediator_delegate_mock_;
};

// Test saving a credit card with invalid card number.
TEST_F(AutofillAddCreditCardMediatorTest,
       TestSavingCreditCardWithInvalidNumber) {
  // `creditCardMediatorHasInvalidCardNumber` expected to be called by
  // `add_credit_card_mediator_` if the credit card has invalid number.
  OCMExpect([add_credit_card_mediator_delegate_mock_
      creditCardMediatorHasInvalidCardNumber:[OCMArg any]]);

  [add_credit_card_mediator_
      addCreditCardViewController:nil
      addCreditCardWithHolderName:kTestCardName
                       cardNumber:@"4111111111111112"  // This is an invalid
                                                       // card number.
                  expirationMonth:kTestExpirationMonth
                   expirationYear:TestExpirationYear()
                     cardNickname:kTestCardNickname];

  // A credit card with invalid number shouldn't be saved so the number of
  // credit cards has to equal zero.
  EXPECT_THAT(personal_data_manager_.payments_data_manager().GetCreditCards(),
              SizeIs(0));

  [add_credit_card_mediator_delegate_mock_ verify];
}

// Test saving a credit card with invalid expiration month.
TEST_F(AutofillAddCreditCardMediatorTest,
       TestSavingCreditCardWithInvalidMonth) {
  // `creditCardMediatorHasInvalidExpirationDate` expected to be called by
  // `add_credit_card_mediator_` if the credit card has invalid expiration date.
  OCMExpect([add_credit_card_mediator_delegate_mock_
      creditCardMediatorHasInvalidExpirationDate:[OCMArg any]]);

  [add_credit_card_mediator_
      addCreditCardViewController:nil
      addCreditCardWithHolderName:kTestCardName
                       cardNumber:kTestCardNumber
                  expirationMonth:@"15"  // This is an invalid month.
                   expirationYear:TestExpirationYear()
                     cardNickname:kTestCardNickname];

  //  A credit card with invalid expiration date shouldn't be saved so the
  //  number of credit cards has to equal zero.
  EXPECT_THAT(personal_data_manager_.payments_data_manager().GetCreditCards(),
              SizeIs(0));

  [add_credit_card_mediator_delegate_mock_ verify];
}

// Test saving a credit card with invalid expiration year.
TEST_F(AutofillAddCreditCardMediatorTest, TestSavingCreditCardWithInvalidYear) {
  // `creditCardMediatorHasInvalidExpirationDate` expected to be called by
  // `add_credit_card_mediator_` if the credit card has invalid expiration date.
  OCMExpect([add_credit_card_mediator_delegate_mock_
      creditCardMediatorHasInvalidExpirationDate:[OCMArg any]]);

  [add_credit_card_mediator_
      addCreditCardViewController:nil
      addCreditCardWithHolderName:kTestCardName
                       cardNumber:kTestCardNumber
                  expirationMonth:kTestExpirationMonth
                   expirationYear:base::SysUTF8ToNSString(
                                      autofill::test::LastYear())  // This is an
                                                                   // invalid
                                                                   // year.
                     cardNickname:kTestCardNickname];

  // A credit card with invalid expiration date shouldn't be saved so the number
  // of credit cards has to equal zero.
  EXPECT_THAT(personal_data_manager_.payments_data_manager().GetCreditCards(),
              SizeIs(0));

  [add_credit_card_mediator_delegate_mock_ verify];
}

// Test saving a credit card with invalid nickname.
TEST_F(AutofillAddCreditCardMediatorTest,
       TestSavingCreditCardWithInvalidNickname) {
  // `creditCardMediatorHasInvalidExpirationDate` expected to be called by
  // `add_credit_card_mediator_` if the credit card has invalid expiration date.
  OCMExpect([add_credit_card_mediator_delegate_mock_
      creditCardMediatorHasInvalidNickname:[OCMArg any]]);

  [add_credit_card_mediator_
      addCreditCardViewController:nil
      addCreditCardWithHolderName:kTestCardName
                       cardNumber:kTestCardNumber
                  expirationMonth:kTestExpirationMonth
                   expirationYear:TestExpirationYear()
                     cardNickname:@"cvc123"];  // This is an invalid nickname.

  // A credit card with invalid nickname shouldn't be saved so the number
  // of credit cards has to equal zero.
  EXPECT_THAT(personal_data_manager_.payments_data_manager().GetCreditCards(),
              SizeIs(0));

  [add_credit_card_mediator_delegate_mock_ verify];
}

// Test saving a valid credit card.
TEST_F(AutofillAddCreditCardMediatorTest, TestSavingValidCreditCard) {
  base::UserActionTester user_action_tester;

  // `creditCardMediatorDidFinish` expected to be called by
  // `add_credit_card_mediator_` if the credit card has valid data.
  OCMExpect([add_credit_card_mediator_delegate_mock_
      creditCardMediatorDidFinish:[OCMArg any]]);

  [add_credit_card_mediator_ addCreditCardViewController:nil
                             addCreditCardWithHolderName:kTestCardName
                                              cardNumber:kTestCardNumber
                                         expirationMonth:kTestExpirationMonth
                                          expirationYear:TestExpirationYear()
                                            cardNickname:kTestCardNickname];

  // A valid credit card expected to be savd so the number of credit cards has
  // to equal one.
  EXPECT_THAT(personal_data_manager_.payments_data_manager().GetCreditCards(),
              SizeIs(1));

  EXPECT_EQ(
      user_action_tester.GetActionCount("MobileAddCreditCard.CreditCardAdded"),
      1);

  [add_credit_card_mediator_delegate_mock_ verify];
}

// Test saving duplicated local credit card with the same card number.
TEST_F(AutofillAddCreditCardMediatorTest,
       TestAlreadyExistsLocalCreditCardNumber) {
  // Add an existing local credit card.
  CreditCard existing_credit_card = autofill::test::GetCreditCard();
  personal_data_manager_.payments_data_manager().AddCreditCard(
      existing_credit_card);
  EXPECT_THAT(personal_data_manager_.payments_data_manager().GetCreditCards(),
              SizeIs(1));

  // As long as the card number is the same, the existing card will be updated.
  NSString* card_number =
      base::SysUTF16ToNSString(existing_credit_card.number());

  NSString* updated_card_name = @"Updated Card Name";
  NSString* updated_expiration_month =
      existing_credit_card.expiration_month() == 1 ? @"02" : @"01";
  NSString* updated_expiration_year =
      base::SysUTF8ToNSString(autofill::test::TenYearsFromNow());
  NSString* updated_card_nickname = @"updatednickname";

  // `creditCardMediatorDidFinish` expected to be called by
  // `add_credit_card_mediator_` if the credit card has valid data.
  OCMExpect([add_credit_card_mediator_delegate_mock_
      creditCardMediatorDidFinish:[OCMArg any]]);

  [add_credit_card_mediator_
      addCreditCardViewController:nil
      addCreditCardWithHolderName:updated_card_name
                       cardNumber:card_number
                  expirationMonth:updated_expiration_month
                   expirationYear:updated_expiration_year
                     cardNickname:updated_card_nickname];

  // A duplicated credit card is expected to be updated (not saved) as a new
  // card so the number of credit cards has to remain equal to one.
  EXPECT_THAT(personal_data_manager_.payments_data_manager().GetCreditCards(),
              SizeIs(1));
  CreditCard* credit_card =
      personal_data_manager_.payments_data_manager().GetCreditCards()[0];

  EXPECT_EQ(credit_card->GetRawInfo(autofill::CREDIT_CARD_NUMBER),
            base::SysNSStringToUTF16(card_number));
  EXPECT_EQ(credit_card->GetRawInfo(autofill::CREDIT_CARD_NAME_FULL),
            base::SysNSStringToUTF16(updated_card_name));
  EXPECT_EQ(credit_card->Expiration2DigitMonthAsString(),
            base::SysNSStringToUTF16(updated_expiration_month));
  EXPECT_EQ(credit_card->Expiration4DigitYearAsString(),
            base::SysNSStringToUTF16(updated_expiration_year));
  EXPECT_EQ(credit_card->nickname(),
            base::SysNSStringToUTF16(updated_card_nickname));

  [add_credit_card_mediator_delegate_mock_ verify];
}

// Test saving duplicated credit card with the same card number as an existing
// server card.
TEST_F(AutofillAddCreditCardMediatorTest,
       TestAlreadyExistsServerCreditCardNumber) {
  // Add an existing server credit card.
  CreditCard server_credit_card = autofill::test::GetCreditCard();
  server_credit_card.set_record_type(CreditCard::RecordType::kMaskedServerCard);

  personal_data_manager_.payments_data_manager().AddCreditCard(
      server_credit_card);
  EXPECT_THAT(personal_data_manager_.payments_data_manager().GetCreditCards(),
              SizeIs(1));

  NSString* card_number = base::SysUTF16ToNSString(server_credit_card.number());

  NSString* updated_card_name = @"Updated Card Holder";
  NSString* updated_expiration_month =
      base::SysUTF8ToNSString(autofill::test::NextMonth());
  NSString* updated_expiration_year =
      base::SysUTF8ToNSString(autofill::test::TenYearsFromNow());
  NSString* updated_card_nickname = @"updatednickname";

  // `creditCardMediatorDidFinish` expected to be called by
  // `add_credit_card_mediator_` if the credit card has valid data.
  OCMExpect([add_credit_card_mediator_delegate_mock_
      creditCardMediatorDidFinish:[OCMArg any]]);

  [add_credit_card_mediator_
      addCreditCardViewController:nil
      addCreditCardWithHolderName:updated_card_name
                       cardNumber:card_number
                  expirationMonth:updated_expiration_month
                   expirationYear:updated_expiration_year
                     cardNickname:updated_card_nickname];

  // Server credit cards should not be updated. There should be a new credit
  // card in storage.
  ASSERT_THAT(personal_data_manager_.payments_data_manager().GetCreditCards(),
              SizeIs(2));
  // The existing server card should still be there.
  EXPECT_THAT(personal_data_manager_.payments_data_manager().GetCreditCards(),
              Contains(testing::Pointee(server_credit_card)));

  auto get_card_name_full = [](const CreditCard* card) {
    return card->GetRawInfo(autofill::CREDIT_CARD_NAME_FULL);
  };

  // A new local card should be saved.
  EXPECT_THAT(
      personal_data_manager_.payments_data_manager().GetCreditCards(),
      Contains(AllOf(
          Property(&CreditCard::record_type,
                   Eq(CreditCard::RecordType::kLocalCard)),
          Property(&CreditCard::number, Eq(server_credit_card.number())),
          testing::ResultOf(get_card_name_full,
                            Eq(base::SysNSStringToUTF16(updated_card_name))),
          Property(&CreditCard::number, Eq(server_credit_card.number())),
          Property(&CreditCard::Expiration2DigitMonthAsString,
                   Eq(base::SysNSStringToUTF16(updated_expiration_month))),
          Property(&CreditCard::Expiration4DigitYearAsString,
                   Eq(base::SysNSStringToUTF16(updated_expiration_year))),
          Property(&CreditCard::nickname,
                   Eq(base::SysNSStringToUTF16(updated_card_nickname))))));

  [add_credit_card_mediator_delegate_mock_ verify];
}

// Test that the metrics for saving a credit card are recorded.
TEST_F(AutofillAddCreditCardMediatorTest, TestMetricsWhenSavingCreditCard) {
  base::HistogramTester histogram_tester;

  personal_data_manager_.payments_data_manager().AddCreditCard(
      autofill::test::GetCreditCard2());
  // Required for adding the server card.
  personal_data_manager_.payments_data_manager().SetSyncingForTest(true);
  personal_data_manager_.payments_data_manager().AddServerCreditCardForTest(
      std::make_unique<CreditCard>(autofill::test::GetMaskedServerCard()));

  int number_of_credit_cards =
      personal_data_manager_.payments_data_manager().GetCreditCards().size();
  EXPECT_EQ(number_of_credit_cards, 2);

  [add_credit_card_mediator_ addCreditCardViewController:nil
                             addCreditCardWithHolderName:kTestCardName
                                              cardNumber:kTestCardNumber
                                         expirationMonth:kTestExpirationMonth
                                          expirationYear:TestExpirationYear()
                                            cardNickname:kTestCardNickname];

  // Expect the metric to add a record based on the number of existing cards.
  histogram_tester.ExpectUniqueSample("Autofill.PaymentMethods.SettingsPage."
                                      "StoredCreditCardCountBeforeCardAdded",
                                      number_of_credit_cards, 1);
}

// Test that the metrics for saving a credit card for the first time through the
// settings are recorded accurately.
TEST_F(AutofillAddCreditCardMediatorTest,
       TestMetricsWhenSavingFirstCreditCard) {
  base::HistogramTester histogram_tester;

  // Ensure that there are no existing credit cards.
  EXPECT_THAT(personal_data_manager_.payments_data_manager().GetCreditCards(),
              SizeIs(0));

  [add_credit_card_mediator_ addCreditCardViewController:nil
                             addCreditCardWithHolderName:kTestCardName
                                              cardNumber:kTestCardNumber
                                         expirationMonth:kTestExpirationMonth
                                          expirationYear:TestExpirationYear()
                                            cardNickname:kTestCardNickname];

  // Expect the metric to add a record for a stored credit card count of 0.
  histogram_tester.ExpectUniqueSample("Autofill.PaymentMethods.SettingsPage."
                                      "StoredCreditCardCountBeforeCardAdded",
                                      0, 1);
}