chromium/ios/chrome/browser/autofill/ui_bundled/authentication/card_unmask_authentication_selection_mediator_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 "ios/chrome/browser/autofill/ui_bundled/authentication/card_unmask_authentication_selection_mediator.h"

#import "base/functional/callback_helpers.h"
#import "base/test/mock_callback.h"
#import "components/autofill/core/browser/payments/card_unmask_challenge_option.h"
#import "components/autofill/core/browser/ui/payments/card_unmask_authentication_selection_dialog_controller_impl.h"
#import "components/strings/grit/components_strings.h"
#import "ios/chrome/browser/autofill/ui_bundled/authentication/card_unmask_authentication_selection_consumer.h"
#import "ios/chrome/browser/autofill/ui_bundled/authentication/card_unmask_authentication_selection_mediator_delegate.h"
#import "ios/chrome/browser/autofill/ui_bundled/authentication/card_unmask_authentication_selection_mutator.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#import "third_party/ocmock/gtest_support.h"
#import "ui/base/l10n/l10n_util.h"

using ChallengeOptionId =
    autofill::CardUnmaskChallengeOption::ChallengeOptionId;

class CardUnmaskAuthenticationSelectionMediatorTest : public PlatformTest {
 public:
  void SetUp() override {
    PlatformTest::SetUp();
    consumer_ =
        OCMProtocolMock(@protocol(CardUnmaskAuthenticationSelectionConsumer));
    delegate_ = OCMProtocolMock(
        @protocol(CardUnmaskAuthenticationSelectionMediatorDelegate));
  }

  void TearDown() override {
    if (consumer_) {
      EXPECT_OCMOCK_VERIFY((id)consumer_);
    }
    if (delegate_) {
      EXPECT_OCMOCK_VERIFY((id)delegate_);
    }
  }

  CardUnmaskAuthenticationSelectionMediator* InitializeMediator(
      const std::vector<autofill::CardUnmaskChallengeOption>&
          challenge_options) {
    CHECK(!mediator_) << "Only initialize the mediator once in tests.";
    controller_ = std::make_unique<
        autofill::CardUnmaskAuthenticationSelectionDialogControllerImpl>(
        challenge_options, confirm_unmasking_method_callback_.Get(),
        cancel_unmasking_closure_.Get());
    mediator_ = std::make_unique<CardUnmaskAuthenticationSelectionMediator>(
        controller_->GetWeakPtr(), consumer_);
    mediator_->set_delegate(delegate_);
    return mediator_.get();
  }

  id<CardUnmaskAuthenticationSelectionConsumer> consumer() { return consumer_; }

  id<CardUnmaskAuthenticationSelectionMediatorDelegate> delegate() {
    return delegate_;
  }

  autofill::CardUnmaskAuthenticationSelectionDialogControllerImpl*
  controller() {
    return controller_.get();
  }

  // An SMS autofill challenge option. Matching the IOS version below.
  autofill::CardUnmaskChallengeOption SmsAutofillChallengeOption() {
    return autofill::CardUnmaskChallengeOption(
        ChallengeOptionId("id1"),
        autofill::CardUnmaskChallengeOptionType::kSmsOtp,
        /*challenge_info=*/u"challenge_info1",
        /*challenge_length_input=*/1U);
  }
  // An SMS challenge option. Matching the autofill version above.
  CardUnmaskChallengeOptionIOS* SmsIOSChallengeOption() {
    return [[CardUnmaskChallengeOptionIOS alloc]
           initWithId:ChallengeOptionId("id1")
            modeLabel:l10n_util::GetNSString(
                          IDS_AUTOFILL_AUTHENTICATION_MODE_GET_TEXT_MESSAGE)
        challengeInfo:@"challenge_info1"];
  }

  // A CVC autofill challenge option. Matching the IOS version below.
  autofill::CardUnmaskChallengeOption CvcAutofillChallengeOption() {
    return autofill::CardUnmaskChallengeOption(
        ChallengeOptionId("id2"), autofill::CardUnmaskChallengeOptionType::kCvc,
        /*challenge_info=*/u"challenge_info2",
        /*challenge_length_input=*/2U);
  }
  // A CVC challenge option. Matching the autofill version above.
  CardUnmaskChallengeOptionIOS* CvcIOSChallengeOption() {
    return [[CardUnmaskChallengeOptionIOS alloc]
           initWithId:ChallengeOptionId("id2")
            modeLabel:l10n_util::GetNSString(
                          IDS_AUTOFILL_AUTHENTICATION_MODE_SECURITY_CODE)
        challengeInfo:@"challenge_info2"];
  }

 protected:
  base::MockOnceCallback<void(const std::string&)>
      confirm_unmasking_method_callback_;
  base::MockOnceClosure cancel_unmasking_closure_;

 private:
  id<CardUnmaskAuthenticationSelectionConsumer> consumer_;
  id<CardUnmaskAuthenticationSelectionMediatorDelegate> delegate_;
  // Mediator listed first to destruct the controller (which holds a reference
  // to the mediator) before the mediator.
  std::unique_ptr<CardUnmaskAuthenticationSelectionMediator> mediator_;
  std::unique_ptr<
      autofill::CardUnmaskAuthenticationSelectionDialogControllerImpl>
      controller_;
};

TEST_F(CardUnmaskAuthenticationSelectionMediatorTest,
       SetsHeaderTitleAndHeaderTextAndOptionsAndFooterAndChallengeAcceptance) {
  OCMExpect([consumer()
      setHeaderTitle:
          l10n_util::GetNSString(
              IDS_AUTOFILL_CARD_AUTH_SELECTION_DIALOG_TITLE_MULTIPLE_OPTIONS)]);
  OCMExpect([consumer()
      setHeaderText:
          l10n_util::GetNSString(
              IDS_AUTOFILL_CARD_UNMASK_AUTHENTICATION_SELECTION_DIALOG_ISSUER_CONFIRMATION_TEXT)]);
  NSArray<CardUnmaskChallengeOptionIOS*>* ios_challenge_options =
      @[ SmsIOSChallengeOption(), CvcIOSChallengeOption() ];
  OCMExpect([consumer() setCardUnmaskOptions:ios_challenge_options]);
  OCMExpect([consumer()
      setFooterText:
          l10n_util::GetNSString(
              IDS_AUTOFILL_CARD_UNMASK_AUTHENTICATION_SELECTION_DIALOG_CURRENT_INFO_NOT_SEEN_TEXT)]);
  OCMExpect([consumer()
      setChallengeAcceptanceLabel:
          l10n_util::GetNSString(
              IDS_AUTOFILL_CARD_UNMASK_AUTHENTICATION_SELECTION_DIALOG_OK_BUTTON_LABEL_SEND)]);

  InitializeMediator(
      {SmsAutofillChallengeOption(), CvcAutofillChallengeOption()});
}

TEST_F(CardUnmaskAuthenticationSelectionMediatorTest,
       SetsSendLabelInitiallyWhenSmsIsTheFirstChallengeOption) {
  OCMExpect([consumer()
      setChallengeAcceptanceLabel:
          l10n_util::GetNSString(
              IDS_AUTOFILL_CARD_UNMASK_AUTHENTICATION_SELECTION_DIALOG_OK_BUTTON_LABEL_SEND)]);

  InitializeMediator(
      {SmsAutofillChallengeOption(), CvcAutofillChallengeOption()});
}

TEST_F(CardUnmaskAuthenticationSelectionMediatorTest,
       SetsContinueLabelInitiallyWhenCvcIsTheFirstChallengeOption) {
  OCMExpect([consumer()
      setChallengeAcceptanceLabel:
          l10n_util::GetNSString(
              IDS_AUTOFILL_CARD_UNMASK_AUTHENTICATION_SELECTION_DIALOG_OK_BUTTON_LABEL_CONTINUE)]);

  InitializeMediator(
      {CvcAutofillChallengeOption(), SmsAutofillChallengeOption()});
}

TEST_F(CardUnmaskAuthenticationSelectionMediatorTest,
       OnDidSelectChallengeOption_SetsButtonLabel) {
  CardUnmaskAuthenticationSelectionMediator* mediator =
      InitializeMediator({SmsAutofillChallengeOption()});

  OCMExpect([consumer()
      setChallengeAcceptanceLabel:
          l10n_util::GetNSString(
              IDS_AUTOFILL_CARD_UNMASK_AUTHENTICATION_SELECTION_DIALOG_OK_BUTTON_LABEL_SEND)]);
  [mediator->AsMutator() didSelectChallengeOption:SmsIOSChallengeOption()];
}

TEST_F(CardUnmaskAuthenticationSelectionMediatorTest,
       OnUpdateContent_CallsEnterPendingState) {
  // The interface method CardUnmaskAuthenticationSelectionDialog::UpdateContent
  // is called to set the pending state.
  CardUnmaskAuthenticationSelectionMediator* mediator =
      InitializeMediator({SmsAutofillChallengeOption()});

  OCMExpect([consumer() enterPendingState]);

  mediator->UpdateContent();
}

TEST_F(CardUnmaskAuthenticationSelectionMediatorTest,
       OnAcceptedSelection_CallsConfirmUnmaskingMethodCallback) {
  CardUnmaskAuthenticationSelectionMediator* mediator = InitializeMediator(
      {SmsAutofillChallengeOption(), CvcAutofillChallengeOption()});
  mediator->DidSelectChallengeOption(CvcIOSChallengeOption());

  EXPECT_CALL(confirm_unmasking_method_callback_,
              Run(CvcAutofillChallengeOption().id.value()));
  [mediator->AsMutator() didAcceptSelection];
}

TEST_F(CardUnmaskAuthenticationSelectionMediatorTest,
       OnCancelSelection_CallsCancelUnmaskingClosure) {
  CardUnmaskAuthenticationSelectionMediator* mediator = InitializeMediator(
      {SmsAutofillChallengeOption(), CvcAutofillChallengeOption()});
  mediator->DidSelectChallengeOption(CvcIOSChallengeOption());

  EXPECT_CALL(cancel_unmasking_closure_, Run());
  [mediator->AsMutator() didCancelSelection];
}

TEST_F(CardUnmaskAuthenticationSelectionMediatorTest,
       OnCancelSelection_CallsDelegateToDismiss) {
  CardUnmaskAuthenticationSelectionMediator* mediator =
      InitializeMediator({SmsAutofillChallengeOption()});

  OCMExpect([delegate() dismissAuthenticationSelection]);
  [mediator->AsMutator() didCancelSelection];
}

TEST_F(CardUnmaskAuthenticationSelectionMediatorTest,
       ServerProcessedAuthentication_DoesNotCallCancelUnmaskingClosure) {
  InitializeMediator({SmsAutofillChallengeOption()});

  EXPECT_CALL(cancel_unmasking_closure_, Run()).Times(0);
  controller()->DismissDialogUponServerProcessedAuthenticationMethodRequest(
      /*server_success=*/true);
}

TEST_F(CardUnmaskAuthenticationSelectionMediatorTest,
       ServerProcessedAuthentication_DoesNotDismissAuthenticationSelection) {
  id<CardUnmaskAuthenticationSelectionMediatorDelegate> delegate =
      OCMStrictProtocolMock(
          @protocol(CardUnmaskAuthenticationSelectionMediatorDelegate));
  CardUnmaskAuthenticationSelectionMediator* mediator =
      InitializeMediator({SmsAutofillChallengeOption()});
  mediator->set_delegate(delegate);

  // No calls to delegate() are expected, and OCMStrictProtocolMock will fail
  // this test if [delegate() dismissAuthenticationSelection] is called.
  controller()->DismissDialogUponServerProcessedAuthenticationMethodRequest(
      /*server_success=*/true);
}