package com.braintree.method.impl;

import com.braintree.command.request.BrainTreeAddressRequest;
import com.braintree.command.request.BrainTreeAdjustAuthorizationRequest;
import com.braintree.command.request.BrainTreeAuthorizationRequest;
import com.braintree.command.request.BrainTreeCloneTransactionRequest;
import com.braintree.command.request.BrainTreeCreateCreditCardPaymentMethodRequest;
import com.braintree.command.request.BrainTreeCreatePaymentMethodRequest;
import com.braintree.command.request.BrainTreeCustomerRequest;
import com.braintree.command.request.BrainTreeDeletePaymentMethodRequest;
import com.braintree.command.request.BrainTreeFindMerchantAccountRequest;
import com.braintree.command.request.BrainTreeFindTransactionRequest;
import com.braintree.command.request.BrainTreeGenerateClientTokenRequest;
import com.braintree.command.request.BrainTreeRefundTransactionRequest;
import com.braintree.command.request.BrainTreeSaleTransactionRequest;
import com.braintree.command.request.BrainTreeSubmitForSettlementTransactionRequest;
import com.braintree.command.request.BrainTreeTokenizeCreditCardRequest;
import com.braintree.command.request.BrainTreeUpdateCustomerRequest;
import com.braintree.command.request.BrainTreeUpdateCreditCardRequest;
import com.braintree.command.result.BrainTreeAddressResult;
import com.braintree.command.result.BrainTreeAdjustedAuthorizationResult;
import com.braintree.command.result.BrainTreeCloneTransactionResult;
import com.braintree.command.result.BrainTreeCreatePaymentMethodResult;
import com.braintree.command.result.BrainTreeCustomerResult;
import com.braintree.command.result.BrainTreeFindCustomerResult;
import com.braintree.command.result.BrainTreeFindCustomersResult;
import com.braintree.command.result.BrainTreeFindMerchantAccountResult;
import com.braintree.command.result.BrainTreeFindTransactionResult;
import com.braintree.command.result.BrainTreeGenerateClientTokenResult;
import com.braintree.command.result.BrainTreePaymentDetailsResult;
import com.braintree.command.result.BrainTreePaymentMethodNonceResult;
import com.braintree.command.result.BrainTreePaymentMethodResult;
import com.braintree.command.result.BrainTreeRefundTransactionResult;
import com.braintree.command.result.BrainTreeSaleTransactionResult;
import com.braintree.command.result.BrainTreeSubmitForSettlementTransactionResult;
import com.braintree.command.result.BrainTreeTokenizeCardResult;
import com.braintree.command.result.BrainTreeUpdateCustomerResult;
import com.braintree.command.result.BrainTreeUpdateCreditCardBillingAddressResult;
import com.braintree.command.result.BrainTreeVoidResult;
import com.braintree.command.result.BrainTreeBillingAddressResult;
import com.braintree.commands.BrainTreeCloneCommand;
import com.braintree.commands.BrainTreeCreateAddressCommand;
import com.braintree.commands.BrainTreeCreateCreditCardPaymentMethodCommand;
import com.braintree.commands.BrainTreeCreatePaymentMethodCommand;
import com.braintree.commands.BrainTreeCreatePaymentMethodNonceCommand;
import com.braintree.commands.BrainTreeDeletePaymentMethodCommand;
import com.braintree.commands.BrainTreeFindCustomerCommand;
import com.braintree.commands.BrainTreeFindMerchantAccountCommand;
import com.braintree.commands.BrainTreeFindTransactionByIdCommand;
import com.braintree.commands.BrainTreeFindTransactionCommand;
import com.braintree.commands.BrainTreeGenerateClientTokenCommand;
import com.braintree.commands.BrainTreeGetPaymentDetailsByNonce;
import com.braintree.commands.BrainTreeGetPaymentMethodByTokenCommand;
import com.braintree.commands.BrainTreePartialCaptureCommand;
import com.braintree.commands.BrainTreeRefundCommand;
import com.braintree.commands.BrainTreeRemoveAddressCommand;
import com.braintree.commands.BrainTreeRemoveCustomerCommand;
import com.braintree.commands.BrainTreeSaleCommand;
import com.braintree.commands.BrainTreeSubmitForSettlementCommand;
import com.braintree.commands.BrainTreeUpdateAddressCommand;
import com.braintree.commands.BrainTreeUpdateCreditCardBillingAddressCommand;
import com.braintree.commands.BrainTreeUpdateCustomerCommand;
import com.braintree.commands.BrainTreeVoidCommand;
import com.braintree.commands.BrainTreeFindRefundCommand;
import com.braintree.commands.BrainTreeWebhookNotificationCommand;
import com.braintree.commands.BrainTreeCreateUsBankAccountPaymentMethodCommand;
import com.braintree.commands.BrainTreeVerifyCreditCardCommand;
import com.braintree.commands.BrainTreePingCommand;
import com.braintree.commands.BrainTreeSetDefaultPaymentInfoCommand;
import com.braintree.configuration.service.BrainTreeConfigService;
import com.braintree.constants.BraintreeConstants;
import com.braintree.customer.service.BrainTreeCustomerAccountService;
import com.braintree.enums.BrainTreeCardType;
import com.braintree.enums.BrainTreePaymentMethod;
import com.braintree.exceptions.BrainTreeCVVVerificationException;
import com.braintree.exceptions.BraintreeCreatePaymentMethodException;
import com.braintree.graphql.commands.BrainTreeGraphQLExchangeLegacyIdCommand;
import com.braintree.graphql.commands.BrainTreeGraphQLTokenizeCreditCardCommand;
import com.braintree.graphql.commands.BraintreeGraphQLAdjustAuthorizationCommand;
import com.braintree.hybris.data.BrainTreeSubscriptionInfoData;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeExchangeLegacyIdRequest;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeThreeDSecure;
import com.braintree.hybris.data.graphql.commands.response.BraintreePaymentMethodData;
import com.braintree.model.BrainTreePaymentInfoModel;
import com.braintree.payment.info.service.BraintreePaymentInfoService;
import com.braintree.paypal.converters.impl.BraintreeBillingAddressConverter;
import com.braintree.transaction.service.BrainTreePaymentTransactionService;
import com.braintreegateway.WebhookNotification;
import com.braintreegateway.exceptions.NotFoundException;
import de.hybris.bootstrap.annotations.UnitTest;
import de.hybris.platform.braintree.data.BrainTreeWebhookNotificationRequest;
import de.hybris.platform.commerceservices.strategies.CheckoutCustomerStrategy;
import de.hybris.platform.commerceservices.strategies.CustomerNameStrategy;
import de.hybris.platform.core.model.order.CartModel;
import de.hybris.platform.core.model.order.payment.PaymentInfoModel;
import de.hybris.platform.core.model.user.AddressModel;
import de.hybris.platform.core.model.user.CustomerModel;
import de.hybris.platform.order.CartService;
import de.hybris.platform.payment.AdapterException;
import de.hybris.platform.payment.commands.AuthorizationCommand;
import de.hybris.platform.payment.commands.CreateSubscriptionCommand;
import de.hybris.platform.payment.commands.factory.CommandFactory;
import de.hybris.platform.payment.commands.factory.CommandFactoryRegistry;
import de.hybris.platform.payment.commands.factory.CommandNotSupportedException;
import de.hybris.platform.payment.commands.request.AuthorizationRequest;
import de.hybris.platform.payment.commands.request.CreateSubscriptionRequest;
import de.hybris.platform.payment.commands.request.VoidRequest;
import de.hybris.platform.payment.commands.result.AuthorizationResult;
import de.hybris.platform.payment.commands.result.SubscriptionResult;
import de.hybris.platform.payment.dto.BillingInfo;
import de.hybris.platform.payment.enums.PaymentTransactionType;
import de.hybris.platform.payment.model.PaymentTransactionEntryModel;
import de.hybris.platform.payment.model.PaymentTransactionModel;
import de.hybris.platform.servicelayer.config.ConfigurationService;
import de.hybris.platform.servicelayer.model.ModelService;
import de.hybris.platform.servicelayer.user.UserService;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Arrays;
import java.util.Optional;

import org.apache.commons.configuration.Configuration;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;

import static com.braintree.constants.BraintreeConstants.PropertyConfiguration.BRAINTREE_AUTHENTICATION_TOKEN;
import static com.braintree.constants.BraintreeConstants.CARD_NUMBER_MASK;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;


@UnitTest
@RunWith(MockitoJUnitRunner.class)
public class DefaultBrainTreePaymentServiceTest {

    private static final String CUSTOMER_ID = "CUSTOMER_ID";
    private static final String NOT_FOUND_EXCEPTION_MESSAGE = "Problem occurred in Payment Provider configuration. Please contact with store support.";
    private static final String MERCHANT_TRANSACTION_CODE = "MERCHANT_TRANSACTION_CODE";
    private static final String MERCHANT_CURRENCY_CODE = "MerchantCurrencyCode";
    private static final String MERCHANT_ACCOUNT_ID = "MERCHANT_ACCOUNT_ID";
    private static final String ANY_EXCEPTION_MESSAGE = "Any exception";
    private static final String COMMAND_NOT_SUPPORTED_EXCEPTION = "CommandNotSupportedException";
    private static final String TRANSACTION_ID = "transactionId";
    private static final String PAYMENT_ID = "paymentId";
    private static final String GENERATED_NONCE = "GENERATED_NONCE";
    private static final String AUTHENTIFICATION_TOKEN = "AUTHENTIFICATION_TOKEN";
    private static final String CLIENT_TOKEN = "CLIENT_TOKEN";
    private static final String SITE = "SITE";
    private static final String CURRENCY = "CURRENCY";
    private static final String PAYMENT_METHOD_TOKEN = "PAYMENT_METHOD_TOKEN";
    private static final String CARD_TYPE = "CARD_TYPE";
    private static final String CARDTYPE = "CARDTYPE";
    private static final String PAYMENT_METHOD_STRINGS = "PAYMENT_METHOD_STRINGS";
    private static final String VALUE_IMAGE = "VALUE_IMAGE";
    private static final String LAST_4 = "1111";
    private static final String PING = "PING";
    private static final String NONCE = "NONCE";
    private static final String EMAIL = "EMAIL";

    @Mock
    private CustomerModel customerModel;
    @Mock
    private CartModel cartModel;
    @Mock
    private BrainTreePaymentInfoModel paymentInfo;
    @Mock
    private BrainTreeFindCustomerResult customerResult;
    @Mock
    private AddressModel billingAddress;
    @Mock
    private BrainTreeSubscriptionInfoData subscriptionData;
    @Mock
    private BrainTreePaymentDetailsResult detailsResult;
    @Mock
    private SubscriptionResult subscriptionResult;
    @Mock
    private BraintreeGraphQLAdjustAuthorizationCommand braintreeGraphQLAdjustAuthorizationCommand;
    @Mock
    private BrainTreeAdjustAuthorizationRequest brainTreeAdjustAuthorizationRequest;
    @Mock
    private BrainTreeAdjustedAuthorizationResult brainTreeAdjustedAuthorizationResult;

    @Rule
    public ExpectedException exception = ExpectedException.none();
    @Mock
    private CommandFactory commandFactory;
    @Mock
    private CommandFactoryRegistry commandFactoryRegistry;
    @Mock
    private ModelService modelService;
    @Mock
    private UserService userService;
    @Mock
    private CustomerNameStrategy customerNameStrategy;
    @Mock
    private CartService cartService;
    @Mock
    private BraintreePaymentInfoService braintreePaymentInfoService;
    @Mock
    private BrainTreePaymentTransactionService brainTreePaymentTransactionService;
    @Mock
    private BraintreeBillingAddressConverter billingAddressConverter;
    @Mock
    private BrainTreeCustomerAccountService brainTreeCustomerAccountService;
    @Mock
    private CheckoutCustomerStrategy checkoutCustomerStrategy;
    @Mock
    private BrainTreeConfigService brainTreeConfigService;
    @Mock
    private ConfigurationService configurationService;
    @Mock
    private BrainTreeFindTransactionByIdCommand findTransactionByIdCommand;

    @Spy
    @InjectMocks
    private DefaultBrainTreePaymentService unit;

    @Before
    public void init() {
        when(commandFactoryRegistry.getFactory(BraintreeConstants.BRAINTREE_PROVIDER_NAME)).thenReturn(commandFactory);
    }

    @Test
    public void shouldAuthorizeIfCustomerExists() throws CommandNotSupportedException {
        // given
        final AuthorizationCommand authorizationCommand = mock(AuthorizationCommand.class);
        final AuthorizationResult expectedAuthorizationResult = mock(AuthorizationResult.class);
        final AuthorizationRequest authorizationRequest = mock(BrainTreeAuthorizationRequest.class);

        when(checkoutCustomerStrategy.getCurrentUserForCheckout()).thenReturn(customerModel);
        when(commandFactory.createCommand(AuthorizationCommand.class)).thenReturn(authorizationCommand);
        when(authorizationCommand.perform(authorizationRequest)).thenReturn(expectedAuthorizationResult);

        // when
        final AuthorizationResult actualAuthorizationResult = unit.authorize(authorizationRequest);

        // then
        assertEquals(expectedAuthorizationResult, actualAuthorizationResult);
    }

    @Test
    public void shouldThrowNotFoundExceptionOnAuthorize() throws CommandNotSupportedException {
        // given
        exception.expect(AdapterException.class);
        exception.expectMessage(NOT_FOUND_EXCEPTION_MESSAGE);
        final AuthorizationCommand authorizationCommand = mock(AuthorizationCommand.class);
        final BrainTreeAuthorizationRequest authorizationRequest = mock(BrainTreeAuthorizationRequest.class);
        final BrainTreeFindCustomerCommand command = mock(BrainTreeFindCustomerCommand.class);

        when(checkoutCustomerStrategy.getCurrentUserForCheckout()).thenReturn(customerModel);
        when(customerModel.getBraintreeCustomerId()).thenReturn(CUSTOMER_ID);
        when(commandFactory.createCommand(BrainTreeFindCustomerCommand.class)).thenReturn(command);
        when(command.perform((BrainTreeCustomerRequest) anyObject())).thenReturn(customerResult);
        when(Boolean.valueOf(customerResult.isCustomerExist())).thenReturn(Boolean.TRUE);
        when(commandFactory.createCommand(AuthorizationCommand.class)).thenReturn(authorizationCommand);
        when(brainTreeConfigService.isStoreInVault()).thenReturn(Boolean.TRUE);

        when(authorizationCommand.perform(authorizationRequest)).thenThrow(new NotFoundException());

        // when
        unit.authorize(authorizationRequest);
    }

    @Test
    public void shouldThrowExceptionOnAuthorize() {
        // given
        exception.expect(Exception.class);
        final AuthorizationRequest authorizationRequest = mock(AuthorizationRequest.class);

        when(checkoutCustomerStrategy.getCurrentUserForCheckout()).thenReturn(customerModel);

        // then
        unit.authorize(authorizationRequest);
    }

    @Test
    public void shouldSaveBTCustomer() throws CommandNotSupportedException {
        // given
        final AuthorizationCommand authorizationCommand = mock(AuthorizationCommand.class);
        final AuthorizationResult expectedAuthorizationResult = mock(AuthorizationResult.class);
        final BrainTreeAuthorizationRequest authorizationRequest = mock(BrainTreeAuthorizationRequest.class);
        final BrainTreeFindCustomerCommand brainTreeFindCustomerCommand = mock(BrainTreeFindCustomerCommand.class);
        final AddressModel addressModel = mock(AddressModel.class);
        final CreateSubscriptionCommand createSubscriptionCommand = mock(CreateSubscriptionCommand.class);

        when(checkoutCustomerStrategy.getCurrentUserForCheckout()).thenReturn(customerModel);
        when(customerModel.getBraintreeCustomerId()).thenReturn(CUSTOMER_ID);
        when(commandFactory.createCommand(BrainTreeFindCustomerCommand.class)).thenReturn(brainTreeFindCustomerCommand);
        when(brainTreeFindCustomerCommand.perform((BrainTreeCustomerRequest) anyObject()))
            .thenReturn(customerResult);
        when(Boolean.valueOf(customerResult.isCustomerExist())).thenReturn(Boolean.FALSE);
        when(cartService.getSessionCart()).thenReturn(cartModel);
        when(cartModel.getDeliveryAddress()).thenReturn(addressModel);
        when(commandFactory.createCommand(CreateSubscriptionCommand.class)).thenReturn(createSubscriptionCommand);
        when(createSubscriptionCommand.perform((CreateSubscriptionRequest) anyObject())).thenReturn(subscriptionResult);
        when(subscriptionResult.getMerchantTransactionCode()).thenReturn(MERCHANT_TRANSACTION_CODE);

        when(commandFactory.createCommand(AuthorizationCommand.class)).thenReturn(authorizationCommand);
        when(authorizationCommand.perform(authorizationRequest)).thenReturn(expectedAuthorizationResult);
        when(brainTreeConfigService.isStoreInVault()).thenReturn(Boolean.TRUE);

        // when
        final AuthorizationResult actualAuthorizationResult = unit.authorize(authorizationRequest);

        // then
        assertEquals(expectedAuthorizationResult, actualAuthorizationResult);
        verify(customerModel).setBraintreeCustomerId(MERCHANT_TRANSACTION_CODE);
        verify(modelService).save(customerModel);
    }

    @Test
    public void shouldVoidTransaction() throws CommandNotSupportedException {
        // given
        final BrainTreeVoidCommand brainTreeVoidCommand = mock(BrainTreeVoidCommand.class);
        final BrainTreeVoidResult expectedBrainTreeVoidResult = mock(BrainTreeVoidResult.class);
        final VoidRequest voidRequest = mock(VoidRequest.class);

        when(commandFactory.createCommand(BrainTreeVoidCommand.class)).thenReturn(brainTreeVoidCommand);
        when(brainTreeVoidCommand.perform(voidRequest)).thenReturn(expectedBrainTreeVoidResult);

        // when
        final BrainTreeVoidResult actualBrainTreeVoidResult = unit.voidTransaction(voidRequest);

        // then
        assertEquals(expectedBrainTreeVoidResult, actualBrainTreeVoidResult);
    }

    @Test
    public void shouldAdjustAuthorizationTransaction() throws CommandNotSupportedException {
        // given
        when(commandFactory.createCommand(BraintreeGraphQLAdjustAuthorizationCommand.class))
                .thenReturn(braintreeGraphQLAdjustAuthorizationCommand);
        when(braintreeGraphQLAdjustAuthorizationCommand.perform(brainTreeAdjustAuthorizationRequest))
                .thenReturn(brainTreeAdjustedAuthorizationResult);

        // when
        final BrainTreeAdjustedAuthorizationResult actualBraintreeAdjustAuthorizationResult =
                unit.adjustAuthorization(brainTreeAdjustAuthorizationRequest);

        // then
        assertEquals(brainTreeAdjustedAuthorizationResult, actualBraintreeAdjustAuthorizationResult);

        verify(commandFactory).createCommand(BraintreeGraphQLAdjustAuthorizationCommand.class);
        verify(braintreeGraphQLAdjustAuthorizationCommand).perform(brainTreeAdjustAuthorizationRequest);
    }

    @Test
    public void shouldReturnEmptyResultIfExceptionOccurs() throws CommandNotSupportedException {
        // given
        when(commandFactory.createCommand(BraintreeGraphQLAdjustAuthorizationCommand.class))
                .thenThrow(new CommandNotSupportedException());

        // when
        final BrainTreeAdjustedAuthorizationResult actualBraintreeAdjustAuthorizationResult =
                unit.adjustAuthorization(brainTreeAdjustAuthorizationRequest);

        // then
        assertNotNull(actualBraintreeAdjustAuthorizationResult);
        assertFalse(actualBraintreeAdjustAuthorizationResult.isSuccess());
    }

    @Test
    public void shouldThrowExceptionOnVoidTransaction() throws CommandNotSupportedException {
        // given
        exception.expect(AdapterException.class);
        exception.expectMessage(ANY_EXCEPTION_MESSAGE);
        final BrainTreeVoidCommand brainTreeVoidCommand = mock(BrainTreeVoidCommand.class);
        final VoidRequest voidRequest = mock(VoidRequest.class);

        when(commandFactory.createCommand(BrainTreeVoidCommand.class)).thenReturn(brainTreeVoidCommand);
        when(brainTreeVoidCommand.perform(voidRequest)).thenThrow(new RuntimeException(ANY_EXCEPTION_MESSAGE));

        // when
        unit.voidTransaction(voidRequest);
    }

    @Test
    public void shouldCloneTransaction() throws CommandNotSupportedException {
        // given
        final BrainTreeCloneCommand brainTreeCloneCommand = mock(BrainTreeCloneCommand.class);
        final BrainTreeCloneTransactionResult expectedBrainTreeCloneTransactionResult = mock(
            BrainTreeCloneTransactionResult.class);
        final BrainTreeCloneTransactionRequest cloneRequest = mock(BrainTreeCloneTransactionRequest.class);

        when(commandFactory.createCommand(BrainTreeCloneCommand.class)).thenReturn(brainTreeCloneCommand);
        when(brainTreeCloneCommand.perform(cloneRequest)).thenReturn(expectedBrainTreeCloneTransactionResult);

        // when
        final BrainTreeCloneTransactionResult actualBrainTreeCloneTransactionResult = unit
            .cloneTransaction(cloneRequest);

        // then
        assertEquals(expectedBrainTreeCloneTransactionResult, actualBrainTreeCloneTransactionResult);
    }

    @Test
    public void shouldThrowExceptionOnCloneTransaction() throws CommandNotSupportedException {
        // given
        exception.expect(AdapterException.class);
        exception.expectMessage(ANY_EXCEPTION_MESSAGE);
        final BrainTreeCloneCommand brainTreeCloneCommand = mock(BrainTreeCloneCommand.class);
        final BrainTreeCloneTransactionRequest cloneRequest = mock(BrainTreeCloneTransactionRequest.class);

        when(commandFactory.createCommand(BrainTreeCloneCommand.class)).thenReturn(brainTreeCloneCommand);
        when(brainTreeCloneCommand.perform(cloneRequest)).thenThrow(new RuntimeException(ANY_EXCEPTION_MESSAGE));

        // when
        unit.cloneTransaction(cloneRequest);
    }

    @Test
    public void shouldRefundTransaction() throws CommandNotSupportedException {
        // given
        final BrainTreeRefundCommand brainTreeRefundCommand = mock(BrainTreeRefundCommand.class);
        final BrainTreeRefundTransactionResult expectedBrainTreeRefundTransactionResult = mock(
            BrainTreeRefundTransactionResult.class);
        final BrainTreeRefundTransactionRequest refundRequest = mock(BrainTreeRefundTransactionRequest.class);

        when(commandFactory.createCommand(BrainTreeRefundCommand.class)).thenReturn(brainTreeRefundCommand);
        when(brainTreeRefundCommand.perform(refundRequest)).thenReturn(expectedBrainTreeRefundTransactionResult);

        // when
        final BrainTreeRefundTransactionResult actualBrainTreeRefundTransactionResult = unit
            .refundTransaction(refundRequest);

        // then
        assertEquals(expectedBrainTreeRefundTransactionResult, actualBrainTreeRefundTransactionResult);
    }

    @Test
    public void shouldThrowExceptionOnRefundTransaction() throws CommandNotSupportedException {
        // given
        exception.expect(AdapterException.class);
        exception.expectMessage(COMMAND_NOT_SUPPORTED_EXCEPTION);
        final BrainTreeRefundTransactionRequest refundRequest = mock(BrainTreeRefundTransactionRequest.class);

        when(commandFactory.createCommand(BrainTreeRefundCommand.class))
            .thenThrow(new CommandNotSupportedException(COMMAND_NOT_SUPPORTED_EXCEPTION));

        // when
        unit.refundTransaction(refundRequest);
    }

    @Test
    public void shouldSaleTransaction() throws CommandNotSupportedException {
        // given
        final BrainTreeSaleCommand brainTreeSaleCommand = mock(BrainTreeSaleCommand.class);
        final BrainTreeSaleTransactionResult expectedBrainTreeSaleTransactionResult = mock(
            BrainTreeSaleTransactionResult.class);
        final BrainTreeSaleTransactionRequest saleRequest = mock(BrainTreeSaleTransactionRequest.class);

        when(commandFactory.createCommand(BrainTreeSaleCommand.class)).thenReturn(brainTreeSaleCommand);
        when(brainTreeSaleCommand.perform(saleRequest)).thenReturn(expectedBrainTreeSaleTransactionResult);

        // when
        final BrainTreeSaleTransactionResult actualBrainTreeSaleTransactionResult = unit.saleTransaction(saleRequest);

        // then
        assertEquals(expectedBrainTreeSaleTransactionResult, actualBrainTreeSaleTransactionResult);
    }

    @Test
    public void shouldThrowExceptionOnSaleTransaction() throws CommandNotSupportedException {
        // given
        exception.expect(AdapterException.class);
        exception.expectMessage(COMMAND_NOT_SUPPORTED_EXCEPTION);
        final BrainTreeSaleTransactionRequest saleRequest = mock(BrainTreeSaleTransactionRequest.class);

        when(commandFactory.createCommand(BrainTreeSaleCommand.class))
            .thenThrow(new CommandNotSupportedException(COMMAND_NOT_SUPPORTED_EXCEPTION));

        // when
        unit.saleTransaction(saleRequest);
    }

    @Test
    public void updateCustomerShouldPerformCommand() throws CommandNotSupportedException {
        final BrainTreeUpdateCustomerCommand command = mock(BrainTreeUpdateCustomerCommand.class);
        final BrainTreeUpdateCustomerResult result = mock(BrainTreeUpdateCustomerResult.class);
        final BrainTreeUpdateCustomerRequest request = mock(BrainTreeUpdateCustomerRequest.class);

        when(commandFactory.createCommand(BrainTreeUpdateCustomerCommand.class)).thenReturn(command);
        when(command.perform(request)).thenReturn(result);

        assertEquals(result, unit.updateCustomer(request));
    }

    @Test
    public void updateCustomerShouldThrowException() throws CommandNotSupportedException {
        exception.expect(AdapterException.class);
        exception.expectMessage(COMMAND_NOT_SUPPORTED_EXCEPTION);
        final BrainTreeUpdateCustomerRequest request = mock(BrainTreeUpdateCustomerRequest.class);

        when(commandFactory.createCommand(BrainTreeUpdateCustomerCommand.class))
            .thenThrow(new CommandNotSupportedException(COMMAND_NOT_SUPPORTED_EXCEPTION));

        unit.updateCustomer(request);
    }

    @Test
    public void partialCaptureTransactionShouldThroeException() throws CommandNotSupportedException {
        exception.expect(AdapterException.class);
        exception.expectMessage(COMMAND_NOT_SUPPORTED_EXCEPTION);
        final BrainTreeSaleTransactionRequest request = mock(BrainTreeSaleTransactionRequest.class);

        when(commandFactory.createCommand(BrainTreePartialCaptureCommand.class))
            .thenThrow(new CommandNotSupportedException(COMMAND_NOT_SUPPORTED_EXCEPTION));

        unit.partialCaptureTransaction(request);
    }

    @Test
    public void partialCaptureTransactionShouldPerformCommand() throws CommandNotSupportedException {
        final BrainTreePartialCaptureCommand command = mock(BrainTreePartialCaptureCommand.class);
        final BrainTreeSaleTransactionResult result = mock(BrainTreeSaleTransactionResult.class);
        final BrainTreeSaleTransactionRequest request = mock(BrainTreeSaleTransactionRequest.class);

        when(commandFactory.createCommand(BrainTreePartialCaptureCommand.class)).thenReturn(command);
        when(command.perform(request)).thenReturn(result);

        assertEquals(result, unit.partialCaptureTransaction(request));
    }

    @Test
    public void removeCustomerShouldThrowException() throws CommandNotSupportedException {
        exception.expect(AdapterException.class);
        exception.expectMessage(COMMAND_NOT_SUPPORTED_EXCEPTION);
        final BrainTreeCustomerRequest request = mock(BrainTreeCustomerRequest.class);

        when(commandFactory.createCommand(BrainTreeRemoveCustomerCommand.class))
            .thenThrow(new CommandNotSupportedException(COMMAND_NOT_SUPPORTED_EXCEPTION));

        unit.removeCustomer(request);
    }

    @Test
    public void removeCustomerShouldPerformCommand() throws CommandNotSupportedException {
        final BrainTreeRemoveCustomerCommand command = mock(BrainTreeRemoveCustomerCommand.class);
        final BrainTreeCustomerResult result = mock(BrainTreeCustomerResult.class);
        final BrainTreeCustomerRequest request = mock(BrainTreeCustomerRequest.class);

        when(commandFactory.createCommand(BrainTreeRemoveCustomerCommand.class)).thenReturn(command);
        when(command.perform(request)).thenReturn(result);

        assertEquals(result, unit.removeCustomer(request));
    }

    @Test
    public void updatePaymentMethodShouldThrowException() throws CommandNotSupportedException {
        exception.expect(AdapterException.class);
        exception.expectMessage(COMMAND_NOT_SUPPORTED_EXCEPTION);
        final BrainTreeUpdateCreditCardRequest request = mock(
            BrainTreeUpdateCreditCardRequest.class);

        when(commandFactory.createCommand(BrainTreeUpdateCreditCardBillingAddressCommand.class))
            .thenThrow(new CommandNotSupportedException(COMMAND_NOT_SUPPORTED_EXCEPTION));

        unit.updatePaymentMethod(request);
    }

    @Test
    public void updatePaymentMethodShouldPerformCommand() throws CommandNotSupportedException {
        final BrainTreeUpdateCreditCardBillingAddressCommand command = mock(
            BrainTreeUpdateCreditCardBillingAddressCommand.class);
        final BrainTreeUpdateCreditCardBillingAddressResult result = mock(BrainTreeUpdateCreditCardBillingAddressResult.class);
        final BrainTreeUpdateCreditCardRequest request = mock(
            BrainTreeUpdateCreditCardRequest.class);

        when(commandFactory.createCommand(BrainTreeUpdateCreditCardBillingAddressCommand.class)).thenReturn(command);
        when(command.perform(request)).thenReturn(result);

        assertEquals(result, unit.updatePaymentMethod(request));
    }

    @Test
    public void verifyCreditCardShouldThrowException() throws CommandNotSupportedException {
        exception.expect(AdapterException.class);
        exception.expectMessage(COMMAND_NOT_SUPPORTED_EXCEPTION);
        final BrainTreeUpdateCreditCardRequest request = mock(
                BrainTreeUpdateCreditCardRequest.class);

        when(commandFactory.createCommand(BrainTreeVerifyCreditCardCommand.class))
                .thenThrow(new CommandNotSupportedException(COMMAND_NOT_SUPPORTED_EXCEPTION));

        unit.verifyCreditCard(request);
    }

    @Test
    public void verifyCreditCardShouldPerformCommand() throws CommandNotSupportedException {
        final BrainTreeVerifyCreditCardCommand command = mock(
                BrainTreeVerifyCreditCardCommand.class);
        final BrainTreeUpdateCreditCardBillingAddressResult result = mock(BrainTreeUpdateCreditCardBillingAddressResult.class);
        final BrainTreeUpdateCreditCardRequest request = mock(
                BrainTreeUpdateCreditCardRequest.class);

        when(commandFactory.createCommand(BrainTreeVerifyCreditCardCommand.class)).thenReturn(command);
        when(command.perform(request)).thenReturn(result);

        assertEquals(result, unit.verifyCreditCard(request));
    }

    @Test
    public void generateClientTokenShouldThrowException() throws CommandNotSupportedException {
        exception.expect(AdapterException.class);
        exception.expectMessage(COMMAND_NOT_SUPPORTED_EXCEPTION);
        final BrainTreeGenerateClientTokenRequest request = mock(BrainTreeGenerateClientTokenRequest.class);

        when(commandFactory.createCommand(BrainTreeGenerateClientTokenCommand.class))
            .thenThrow(new CommandNotSupportedException(COMMAND_NOT_SUPPORTED_EXCEPTION));

        unit.generateClientToken(request);
    }

    @Test
    public void generateClientTokenShouldPerformCommand() throws CommandNotSupportedException {
        final BrainTreeGenerateClientTokenCommand command = mock(BrainTreeGenerateClientTokenCommand.class);
        final BrainTreeGenerateClientTokenResult result = mock(BrainTreeGenerateClientTokenResult.class);
        final BrainTreeGenerateClientTokenRequest request = mock(BrainTreeGenerateClientTokenRequest.class);

        when(commandFactory.createCommand(BrainTreeGenerateClientTokenCommand.class)).thenReturn(command);
        when(command.perform(request)).thenReturn(result);

        assertEquals(result, unit.generateClientToken(request));
    }

    @Test
    public void findCustomerShouldThrowException() throws CommandNotSupportedException {
        exception.expect(AdapterException.class);
        exception.expectMessage(COMMAND_NOT_SUPPORTED_EXCEPTION);
        final BrainTreeCustomerRequest request = mock(BrainTreeCustomerRequest.class);

        when(commandFactory.createCommand(BrainTreeFindCustomerCommand.class))
            .thenThrow(new CommandNotSupportedException(COMMAND_NOT_SUPPORTED_EXCEPTION));

        unit.findCustomer(request);
    }

    @Test
    public void findCustomersShouldThrowException() throws CommandNotSupportedException {
        exception.expect(AdapterException.class);
        exception.expectMessage(COMMAND_NOT_SUPPORTED_EXCEPTION);
        final BrainTreeCustomerRequest request = mock(BrainTreeCustomerRequest.class);

        when(commandFactory.createCommand(BrainTreeFindCustomerCommand.class))
            .thenThrow(new CommandNotSupportedException(COMMAND_NOT_SUPPORTED_EXCEPTION));

        unit.findCustomers(request);
    }

    @Test
    public void findCustomersShouldPerformCommand() throws CommandNotSupportedException {
        final BrainTreeFindCustomerCommand command = mock(BrainTreeFindCustomerCommand.class);
        final BrainTreeFindCustomersResult result = mock(BrainTreeFindCustomersResult.class);
        final BrainTreeCustomerRequest request = mock(BrainTreeCustomerRequest.class);

        when(commandFactory.createCommand(BrainTreeFindCustomerCommand.class)).thenReturn(command);
        when(command.process(request)).thenReturn(result);

        assertEquals(result, unit.findCustomers(request));
    }

    @Test
    public void submitForSettlementTransactionShouldThrowException() throws CommandNotSupportedException {
        exception.expect(AdapterException.class);
        exception.expectMessage(COMMAND_NOT_SUPPORTED_EXCEPTION);
        final BrainTreeSubmitForSettlementTransactionRequest request = mock(
            BrainTreeSubmitForSettlementTransactionRequest.class);

        when(commandFactory.createCommand(BrainTreeSubmitForSettlementCommand.class))
            .thenThrow(new CommandNotSupportedException(COMMAND_NOT_SUPPORTED_EXCEPTION));

        unit.submitForSettlementTransaction(request);
    }

    @Test
    public void submitForSettlementTransactionShouldPerformCommand() throws CommandNotSupportedException {
        final BrainTreeSubmitForSettlementCommand command = mock(BrainTreeSubmitForSettlementCommand.class);
        final BrainTreeSubmitForSettlementTransactionResult result = mock(
            BrainTreeSubmitForSettlementTransactionResult.class);
        final BrainTreeSubmitForSettlementTransactionRequest request = mock(
            BrainTreeSubmitForSettlementTransactionRequest.class);

        when(commandFactory.createCommand(BrainTreeSubmitForSettlementCommand.class)).thenReturn(command);
        when(command.perform(request)).thenReturn(result);

        assertEquals(result, unit.submitForSettlementTransaction(request));
    }

    @Test
    public void submitForSettlementTransactionShouldPeformCommand() throws CommandNotSupportedException {
        final BrainTreeSubmitForSettlementCommand command = mock(BrainTreeSubmitForSettlementCommand.class);
        final BrainTreeSubmitForSettlementTransactionResult result = mock(
            BrainTreeSubmitForSettlementTransactionResult.class);
        final BrainTreeSubmitForSettlementTransactionRequest request = mock(
            BrainTreeSubmitForSettlementTransactionRequest.class);

        when(request.getTransactionId()).thenReturn(TRANSACTION_ID);
        when(request.getAmount()).thenReturn(BigDecimal.TEN);
        when(commandFactory.createCommand(BrainTreeSubmitForSettlementCommand.class)).thenReturn(command);
        when(command.perform(request)).thenReturn(result);

        List<PaymentTransactionModel> transactions = new ArrayList<>();
        PaymentTransactionModel transactionModel = mock(PaymentTransactionModel.class);
        List<PaymentTransactionEntryModel> entryModels = new ArrayList<>();
        PaymentTransactionEntryModel entryModel = mock(PaymentTransactionEntryModel.class);

        when(entryModel.getType()).thenReturn(PaymentTransactionType.CAPTURE);
        entryModels.add(entryModel);
        when(transactionModel.getEntries()).thenReturn(entryModels);

        transactions.add(transactionModel);

        when(brainTreePaymentTransactionService
            .getTransactionsByRequestIdAndPaymentProvider(TRANSACTION_ID, BraintreeConstants.BRAINTREE_PROVIDER_NAME))
            .thenReturn(transactions);

        assertEquals(result, unit.submitForSettlementTransaction(request));
        verify(entryModel).setAmount(BigDecimal.TEN);
        verify(modelService).save(entryModel);
    }

    @Test
    public void shouldThrowExceptionForGetBraintreePing() throws CommandNotSupportedException {
        exception.expect(AdapterException.class);
        exception.expectMessage(COMMAND_NOT_SUPPORTED_EXCEPTION);

        when(commandFactory.createCommand(BrainTreePingCommand.class))
                .thenThrow(new CommandNotSupportedException(COMMAND_NOT_SUPPORTED_EXCEPTION));

        unit.getBraintreePing();

        verify(commandFactory).createCommand(BrainTreePingCommand.class);
    }

    @Test
    public void shouldGetBraintreePing() throws CommandNotSupportedException {
        final BrainTreePingCommand command = mock(BrainTreePingCommand.class);

        when(commandFactory.createCommand(BrainTreePingCommand.class)).thenReturn(command);
        when(command.perform(null)).thenReturn(PING);

        assertEquals(PING, unit.getBraintreePing());

        verify(commandFactory).createCommand(BrainTreePingCommand.class);
        verify(command).perform(null);
    }

    @Test
    public void createAddressShouldThrowException() throws CommandNotSupportedException {
        final BrainTreeAddressRequest request = mock(BrainTreeAddressRequest.class);

        when(request.getCustomerId()).thenReturn(CUSTOMER_ID);
        when(commandFactory.createCommand(BrainTreeCreateAddressCommand.class))
            .thenThrow(new CommandNotSupportedException(COMMAND_NOT_SUPPORTED_EXCEPTION));

        assertNull(unit.createAddress(request, customerModel));
    }

    @Test
    public void createAddressShouldPerformCommand() throws CommandNotSupportedException {
        final BrainTreeCreateAddressCommand command = mock(BrainTreeCreateAddressCommand.class);
        final CreateSubscriptionCommand subscriptionCommand = mock(CreateSubscriptionCommand.class);
        final BrainTreeAddressResult result = mock(BrainTreeAddressResult.class);
        final BrainTreeAddressRequest request = mock(BrainTreeAddressRequest.class);

        when(commandFactory.createCommand(BrainTreeCreateAddressCommand.class)).thenReturn(command);
        when(commandFactory.createCommand(CreateSubscriptionCommand.class)).thenReturn(subscriptionCommand);
        when(command.perform(request)).thenReturn(result);
        when(subscriptionCommand.perform(any())).thenReturn(subscriptionResult);

        assertEquals(result, unit.createAddress(request, customerModel));
    }

    @Test
    public void updateAddressShouldThrowException() throws CommandNotSupportedException {
        final BrainTreeAddressRequest request = mock(BrainTreeAddressRequest.class);
        when(commandFactory.createCommand(BrainTreeUpdateAddressCommand.class))
            .thenThrow(new CommandNotSupportedException(COMMAND_NOT_SUPPORTED_EXCEPTION));

        assertNull(unit.updateAddress(request));
    }

    @Test
    public void updateAddressShouldPerformCommand() throws CommandNotSupportedException {
        final BrainTreeUpdateAddressCommand command = mock(BrainTreeUpdateAddressCommand.class);
        final BrainTreeAddressResult result = mock(BrainTreeAddressResult.class);
        final BrainTreeAddressRequest request = mock(BrainTreeAddressRequest.class);

        when(commandFactory.createCommand(BrainTreeUpdateAddressCommand.class)).thenReturn(command);
        when(command.perform(request)).thenReturn(result);

        assertEquals(result, unit.updateAddress(request));
    }

    @Test
    public void removeAddressShouldReturnEmptyObjectWhenCommandNotExist() throws CommandNotSupportedException {
        final BrainTreeAddressRequest request = mock(BrainTreeAddressRequest.class);

        when(commandFactory.createCommand(BrainTreeRemoveAddressCommand.class))
            .thenThrow(new CommandNotSupportedException(COMMAND_NOT_SUPPORTED_EXCEPTION));

        assertNotNull(unit.removeAddress(request));
    }

    @Test
    public void removeAddressShouldPerformCommand() throws CommandNotSupportedException {
        final BrainTreeRemoveAddressCommand command = mock(BrainTreeRemoveAddressCommand.class);
        final BrainTreeAddressResult result = mock(BrainTreeAddressResult.class);
        final BrainTreeAddressRequest request = mock(BrainTreeAddressRequest.class);
        when(commandFactory.createCommand(BrainTreeRemoveAddressCommand.class)).thenReturn(command);
        when(command.perform(request)).thenReturn(result);

        assertEquals(result, unit.removeAddress(request));
    }

    @Test
    public void completeCreateSubscription() {
        when(cartService.getSessionCart()).thenReturn(cartModel);
        when(brainTreeCustomerAccountService
            .getBrainTreePaymentInfoForCode(customerModel, PAYMENT_ID)).thenReturn(paymentInfo);
        when(brainTreeConfigService.getMerchantAccountIdForCurrentSiteAndCurrency()).thenReturn(MERCHANT_CURRENCY_CODE);

        unit.completeCreateSubscription(customerModel, PAYMENT_ID);

        verify(paymentInfo).setMerchantAccountIdForCurrentSite(MERCHANT_CURRENCY_CODE);
        verify(cartModel).setPaymentInfo(paymentInfo);
        verify(modelService).save(paymentInfo);
        verify(modelService).save(cartModel);
    }

    @Test
    public void shouldSaveBillingAddressIfNotExistForVenmoPaymentMethod() {
        final String paymentProvider = BrainTreePaymentMethod.VENMOACCOUNT.getCode();
        final AddressModel deliveryAddress = mock(AddressModel.class);
        final AddressModel addressModel = mock(AddressModel.class);

        when(paymentInfo.getPaymentProvider()).thenReturn(paymentProvider);
        when(paymentInfo.getBillingAddress()).thenReturn(null);

        when(cartService.getSessionCart()).thenReturn(cartModel);
        when(cartModel.getDeliveryAddress()).thenReturn(deliveryAddress);
        when(modelService.clone(deliveryAddress)).thenReturn(addressModel);
        doNothing().when(modelService).save(addressModel);

        unit.saveBillingAddressIfNotExistForVenmoPaymentMethod(paymentInfo);

        verify(paymentInfo).getPaymentProvider();
        verify(paymentInfo).getBillingAddress();

        verify(cartService).getSessionCart();
        verify(cartModel).getDeliveryAddress();
        verify(modelService).clone(deliveryAddress);
        verify(addressModel).setShippingAddress(false);
        verify(addressModel).setBillingAddress(true);
        verify(modelService).save(addressModel);
    }

    @Test
    public void shouldNotSaveBecausePaymentProviderIsNotCorrectAndBillingAddressIsNotEmpty() {
        final String paymentProvider = BrainTreePaymentMethod.PAYPAL.getCode();

        when(paymentInfo.getPaymentProvider()).thenReturn(paymentProvider);

        unit.saveBillingAddressIfNotExistForVenmoPaymentMethod(paymentInfo);

        verify(modelService, never()).clone(any(AddressModel.class));
        verify(modelService, never()).save(any(AddressModel.class));
    }

    @Test
    public void shouldNotSaveBecauseBillingAddressIsNotEmpty() {
        final String paymentProvider = BrainTreePaymentMethod.VENMOACCOUNT.getCode();

        when(paymentInfo.getPaymentProvider()).thenReturn(paymentProvider);
        when(paymentInfo.getBillingAddress()).thenReturn(billingAddress);

        unit.saveBillingAddressIfNotExistForVenmoPaymentMethod(paymentInfo);

        verify(modelService, never()).clone(any(AddressModel.class));
        verify(modelService, never()).save(any(AddressModel.class));
    }

    @Test
    public void shouldCompleteCreateSubscriptionWithCvvVerification() {
        final String paymentProvider = BrainTreePaymentMethod.CREDITCARD.getCode();
        final BrainTreeUpdateCreditCardBillingAddressResult result = mock(BrainTreeUpdateCreditCardBillingAddressResult.class);

        doReturn(paymentInfo).when(unit).completeCreateSubscription(customerModel, PAYMENT_ID);
        when(brainTreeConfigService.isBraintreeVerifyFlow()).thenReturn(true);
        when(paymentInfo.getPaymentProvider()).thenReturn(paymentProvider);
        when(paymentInfo.getPaymentMethodToken()).thenReturn(PAYMENT_METHOD_TOKEN);
        doReturn(result).when(unit).verifyCreditCard(any(BrainTreeUpdateCreditCardRequest.class));
        when(result.isSuccess()).thenReturn(true);

        final BrainTreePaymentInfoModel testResult = unit.completeCreateSubscriptionWithCvvVerification(customerModel, PAYMENT_ID, NONCE);

        assertEquals(paymentInfo, testResult);
        verify(unit).completeCreateSubscription(customerModel, PAYMENT_ID);
        verify(brainTreeConfigService).isBraintreeVerifyFlow();
        verify(paymentInfo).getPaymentProvider();
        verify(paymentInfo, times(2)).getPaymentMethodToken();
        verify(unit).verifyCreditCard(any(BrainTreeUpdateCreditCardRequest.class));
        verify(result).isSuccess();
    }

    @Test(expected = BrainTreeCVVVerificationException.class)
    public void shouldThrowBrainTreeCVVVerificationException() {
        final String paymentProvider = BrainTreePaymentMethod.CREDITCARD.getCode();
        final BrainTreeUpdateCreditCardBillingAddressResult result = mock(BrainTreeUpdateCreditCardBillingAddressResult.class);

        doReturn(paymentInfo).when(unit).completeCreateSubscription(customerModel, PAYMENT_ID);
        when(brainTreeConfigService.isBraintreeVerifyFlow()).thenReturn(true);
        when(paymentInfo.getPaymentProvider()).thenReturn(paymentProvider);
        when(paymentInfo.getPaymentMethodToken()).thenReturn(PAYMENT_METHOD_TOKEN);
        doReturn(result).when(unit).verifyCreditCard(any(BrainTreeUpdateCreditCardRequest.class));
        when(result.isSuccess()).thenReturn(false);
        when(cartService.getSessionCart()).thenReturn(cartModel);
        doNothing().when(modelService).save(cartModel);

        unit.completeCreateSubscriptionWithCvvVerification(customerModel, PAYMENT_ID, NONCE);

        verify(unit).completeCreateSubscription(customerModel, PAYMENT_ID);
        verify(brainTreeConfigService).isBraintreeVerifyFlow();
        verify(paymentInfo).getPaymentProvider();
        verify(paymentInfo).getPaymentMethodToken();
        verify(unit).verifyCreditCard(any(BrainTreeUpdateCreditCardRequest.class));
        verify(result).isSuccess();
        verify(cartService).getSessionCart();
        verify(cartModel).setPaymentInfo(null);
        verify(modelService).save(cartModel);
    }

    @Test
    public void shouldNotVerifyCreditCardBecauseVerifyFlowIsDisabledAndPaymentProviderIsIncorrect() {
        doReturn(paymentInfo).when(unit).completeCreateSubscription(customerModel, PAYMENT_ID);
        when(brainTreeConfigService.isBraintreeVerifyFlow()).thenReturn(false);

        final BrainTreePaymentInfoModel result = unit.completeCreateSubscriptionWithCvvVerification(customerModel, PAYMENT_ID, NONCE);

        assertEquals(paymentInfo, result);
        verify(unit).completeCreateSubscription(customerModel, PAYMENT_ID);
        verify(brainTreeConfigService).isBraintreeVerifyFlow();
        verify(paymentInfo, never()).getPaymentProvider();
    }

    @Test
    public void shouldNotVerifyCreditCardBecausePaymentProviderIsIncorrect() {
        final String paymentProvider = BrainTreePaymentMethod.PAYPAL.getCode();

        doReturn(paymentInfo).when(unit).completeCreateSubscription(customerModel, PAYMENT_ID);
        when(brainTreeConfigService.isBraintreeVerifyFlow()).thenReturn(true);
        when(paymentInfo.getPaymentProvider()).thenReturn(paymentProvider);

        final BrainTreePaymentInfoModel result = unit.completeCreateSubscriptionWithCvvVerification(customerModel, PAYMENT_ID, NONCE);

        assertEquals(paymentInfo, result);
        verify(unit).completeCreateSubscription(customerModel, PAYMENT_ID);
        verify(brainTreeConfigService).isBraintreeVerifyFlow();
        verify(paymentInfo).getPaymentProvider();
    }

    @Test
    public void shouldReturnNullBecauseCompleteCreateSubscriptionReturnNull() {
        doReturn(null).when(unit).completeCreateSubscription(customerModel, PAYMENT_ID);

        final BrainTreePaymentInfoModel result = unit.completeCreateSubscriptionWithCvvVerification(customerModel, PAYMENT_ID, NONCE);

        assertNull(result);
        verify(unit).completeCreateSubscription(customerModel, PAYMENT_ID);
    }

    @Test
    public void generateClientTokenByBrainTreeIdShouldReturnAuthentificationTokenIfExist() {
        final Configuration configuration = mock(Configuration.class);

        when(configurationService.getConfiguration()).thenReturn(configuration);
        when(configuration.getString(BRAINTREE_AUTHENTICATION_TOKEN)).thenReturn(AUTHENTIFICATION_TOKEN);
        when(brainTreeConfigService.getMerchantAccountIdForCurrentSiteAndCurrency()).thenReturn(MERCHANT_CURRENCY_CODE);

        assertEquals(AUTHENTIFICATION_TOKEN, unit.generateClientToken(CUSTOMER_ID));
    }

    @Test
    public void generateClientTokenByBrainTreeIdShouldReturnGeneratedClientToken()
        throws CommandNotSupportedException {
        final BrainTreeGenerateClientTokenCommand command = mock(BrainTreeGenerateClientTokenCommand.class);
        final BrainTreeGenerateClientTokenResult result = mock(BrainTreeGenerateClientTokenResult.class);
        final Configuration configuration = mock(Configuration.class);

        when(configurationService.getConfiguration()).thenReturn(configuration);
        when(commandFactory.createCommand(BrainTreeGenerateClientTokenCommand.class)).thenReturn(command);
        when(command.perform(any())).thenReturn(result);
        when(result.getClientToken()).thenReturn(CLIENT_TOKEN);
        when(brainTreeConfigService.getMerchantAccountIdForCurrentSiteAndCurrency()).thenReturn(MERCHANT_CURRENCY_CODE);

        assertEquals(CLIENT_TOKEN, unit.generateClientToken(CUSTOMER_ID));
    }

    @Test
    public void generateClientTokenBySiteAndCurrencyShouldReturnGeneratedClientToken()
        throws CommandNotSupportedException {
        final BrainTreeGenerateClientTokenCommand command = mock(BrainTreeGenerateClientTokenCommand.class);
        final BrainTreeGenerateClientTokenResult result = mock(BrainTreeGenerateClientTokenResult.class);
        final Configuration configuration = mock(Configuration.class);

        when(configurationService.getConfiguration()).thenReturn(configuration);
        when(commandFactory.createCommand(BrainTreeGenerateClientTokenCommand.class)).thenReturn(command);
        when(command.perform(any())).thenReturn(result);
        when(result.getClientToken()).thenReturn(CLIENT_TOKEN);
        when(brainTreeConfigService.getMerchantAccountIdForSiteAndCurrencyIsoCode(SITE, CURRENCY))
            .thenReturn(MERCHANT_ACCOUNT_ID);

        assertEquals(CLIENT_TOKEN, unit.generateClientToken(SITE, CURRENCY));
    }

    @Test(expected = BraintreeCreatePaymentMethodException.class)
    public void createPaymentMethodShouldTrowExceptionWhenCommandNotExist() throws CommandNotSupportedException {
        final BrainTreeCreatePaymentMethodRequest request = mock(BrainTreeCreatePaymentMethodRequest.class);

        when(commandFactory.createCommand(BrainTreeCreatePaymentMethodCommand.class))
            .thenThrow(new CommandNotSupportedException(COMMAND_NOT_SUPPORTED_EXCEPTION));

        unit.createPaymentMethod(request);
    }

    @Test
    public void createPaymentMethodShouldPerformCommand() throws CommandNotSupportedException {
        final BrainTreeCreatePaymentMethodCommand command = mock(BrainTreeCreatePaymentMethodCommand.class);
        final BrainTreeCreatePaymentMethodResult result = mock(BrainTreeCreatePaymentMethodResult.class);
        final BrainTreeCreatePaymentMethodRequest request = mock(BrainTreeCreatePaymentMethodRequest.class);

        when(commandFactory.createCommand(BrainTreeCreatePaymentMethodCommand.class)).thenReturn(command);
        when(command.perform(request)).thenReturn(result);

        assertEquals(result, unit.createPaymentMethod(request));
    }

    @Test
    public void createCreditCardPaymentMethodShouldReturnNullWhenCommandNotExist() throws CommandNotSupportedException {
        final BrainTreeCreateCreditCardPaymentMethodRequest request = mock(
            BrainTreeCreateCreditCardPaymentMethodRequest.class);

        when(commandFactory.createCommand(BrainTreeCreateCreditCardPaymentMethodCommand.class))
            .thenThrow(new CommandNotSupportedException(COMMAND_NOT_SUPPORTED_EXCEPTION));

        assertNull(unit.createCreditCardPaymentMethod(request));
    }

    @Test
    public void createCreditCardPaymentMethodShouldPerformCommand() throws CommandNotSupportedException {
        final BrainTreeCreateCreditCardPaymentMethodCommand command = mock(
            BrainTreeCreateCreditCardPaymentMethodCommand.class);
        final BrainTreePaymentMethodResult result = mock(BrainTreePaymentMethodResult.class);
        final BrainTreeCreateCreditCardPaymentMethodRequest request = mock(
            BrainTreeCreateCreditCardPaymentMethodRequest.class);

        when(commandFactory.createCommand(BrainTreeCreateCreditCardPaymentMethodCommand.class)).thenReturn(command);
        when(command.perform(request)).thenReturn(result);

        assertEquals(result, unit.createCreditCardPaymentMethod(request));
    }

    @Test
    public void createPaymentMethodNonceShouldReturnEmptyResult() throws CommandNotSupportedException {
        when(commandFactory.createCommand(BrainTreeCreatePaymentMethodNonceCommand.class))
            .thenThrow(new CommandNotSupportedException(COMMAND_NOT_SUPPORTED_EXCEPTION));

        final BrainTreePaymentMethodNonceResult actual = unit.createPaymentMethodNonce(MERCHANT_ACCOUNT_ID);

        assertNull(actual.getNonce());
        assertNull(actual.getBin());
        assertNull(actual.getEnvironment());
    }

    @Test
    public void createPaymentMethodNonceShouldPerformCommand() throws CommandNotSupportedException {
        final BrainTreeCreatePaymentMethodNonceCommand command = mock(BrainTreeCreatePaymentMethodNonceCommand.class);
        final BrainTreePaymentMethodNonceResult result = new BrainTreePaymentMethodNonceResult();

        when(commandFactory.createCommand(BrainTreeCreatePaymentMethodNonceCommand.class)).thenReturn(command);
        result.setNonce(GENERATED_NONCE);
        when(command.perform(PAYMENT_ID)).thenReturn(result);

        assertEquals(result.getNonce(), unit.createPaymentMethodNonce(PAYMENT_ID).getNonce());
    }

    @Test
    public void deletePaymentMethodShouldThrowException() throws CommandNotSupportedException {
        exception.expect(AdapterException.class);
        final BrainTreeDeletePaymentMethodRequest request = mock(BrainTreeDeletePaymentMethodRequest.class);

        unit.deletePaymentMethod(request);
    }

    @Test
    public void deletePaymentMethodShouldPerformCommand() throws CommandNotSupportedException {
        final BrainTreeDeletePaymentMethodCommand command = mock(BrainTreeDeletePaymentMethodCommand.class);
        final BrainTreePaymentMethodResult result = mock(BrainTreePaymentMethodResult.class);
        final BrainTreeDeletePaymentMethodRequest request = mock(BrainTreeDeletePaymentMethodRequest.class);

        when(commandFactory.createCommand(BrainTreeDeletePaymentMethodCommand.class)).thenReturn(command);
        when(command.perform(request)).thenReturn(result);

        assertEquals(result, unit.deletePaymentMethod(request));
    }

    @Test
    public void findMerchantAccountShouldThrowException() throws CommandNotSupportedException {
        exception.expect(AdapterException.class);
        final BrainTreeFindMerchantAccountRequest request = mock(BrainTreeFindMerchantAccountRequest.class);

        when(commandFactory.createCommand(BrainTreeFindMerchantAccountCommand.class))
            .thenThrow(new CommandNotSupportedException(COMMAND_NOT_SUPPORTED_EXCEPTION));

        unit.findMerchantAccount(request);
    }

    @Test
    public void findMerchantAccountShouldPerformCommand() throws CommandNotSupportedException {
        final BrainTreeFindMerchantAccountCommand command = mock(BrainTreeFindMerchantAccountCommand.class);
        final BrainTreeFindMerchantAccountResult result = mock(BrainTreeFindMerchantAccountResult.class);
        final BrainTreeFindMerchantAccountRequest request = mock(BrainTreeFindMerchantAccountRequest.class);

        when(commandFactory.createCommand(BrainTreeFindMerchantAccountCommand.class)).thenReturn(command);
        when(command.perform(request)).thenReturn(result);

        assertEquals(result, unit.findMerchantAccount(request));
    }

    @Test
    public void findTransactionsShouldThrowException() throws CommandNotSupportedException {
        exception.expect(AdapterException.class);
        final BrainTreeFindTransactionRequest request = mock(BrainTreeFindTransactionRequest.class);

        when(commandFactory.createCommand(BrainTreeFindTransactionCommand.class))
            .thenThrow(new CommandNotSupportedException(COMMAND_NOT_SUPPORTED_EXCEPTION));

        unit.findTransactions(request);
    }

    @Test
    public void findTransactionsShouldPerformCommand() throws CommandNotSupportedException {
        final BrainTreeFindTransactionCommand command = mock(BrainTreeFindTransactionCommand.class);
        final BrainTreeFindTransactionResult result = mock(BrainTreeFindTransactionResult.class);
        final BrainTreeFindTransactionRequest request = mock(BrainTreeFindTransactionRequest.class);

        when(commandFactory.createCommand(BrainTreeFindTransactionCommand.class)).thenReturn(command);
        when(command.perform(request)).thenReturn(result);

        assertEquals(result, unit.findTransactions(request));
    }

    @Test
    public void shouldFindVenmoPaymentMethod() {
        final List<BrainTreePaymentDetailsResult> paymentMethods = Arrays.asList(detailsResult);
        final String paymentProvider = BrainTreePaymentMethod.VENMOACCOUNT.getCode();

        when(customerResult.getPaymentMethods()).thenReturn(paymentMethods);
        when(detailsResult.getPaymentProvider()).thenReturn(paymentProvider);
        when(subscriptionData.getPaymentProvider()).thenReturn(paymentProvider);

        when(detailsResult.getEmail()).thenReturn(EMAIL);
        when(subscriptionData.getEmail()).thenReturn(EMAIL);

        final Optional<BrainTreePaymentDetailsResult> result = unit.findPaymentMethod(customerResult, billingAddress, subscriptionData);

        assertEquals(detailsResult, result.get());
        verify(customerResult).getPaymentMethods();
        verify(detailsResult).getPaymentProvider();
        verify(subscriptionData, times(2)).getPaymentProvider();

        verify(detailsResult).getEmail();
        verify(subscriptionData).getEmail();
    }

    @Test
    public void shouldFindPayPalPaymentMethod() {
        final List<BrainTreePaymentDetailsResult> paymentMethods = Arrays.asList(detailsResult);
        final String paymentProvider = BrainTreePaymentMethod.PAYPAL.getCode();

        when(customerResult.getPaymentMethods()).thenReturn(paymentMethods);
        when(detailsResult.getPaymentProvider()).thenReturn(paymentProvider);
        when(subscriptionData.getPaymentProvider()).thenReturn(paymentProvider);

        when(detailsResult.getEmail()).thenReturn(EMAIL);
        when(subscriptionData.getEmail()).thenReturn(EMAIL);

        final Optional<BrainTreePaymentDetailsResult> result = unit.findPaymentMethod(customerResult, billingAddress, subscriptionData);

        assertEquals(detailsResult, result.get());
        verify(customerResult).getPaymentMethods();
        verify(detailsResult).getPaymentProvider();
        verify(subscriptionData, times(3)).getPaymentProvider();

        verify(detailsResult).getEmail();
        verify(subscriptionData).getEmail();
    }

    @Test
    public void shouldFindSrcPaymentMethod() {
        final List<BrainTreePaymentDetailsResult> paymentMethods = Arrays.asList(detailsResult);
        final String paymentProvider = BrainTreePaymentMethod.VISACHECKOUTCARD.getCode();

        when(customerResult.getPaymentMethods()).thenReturn(paymentMethods);
        when(detailsResult.getPaymentProvider()).thenReturn(paymentProvider);
        when(subscriptionData.getPaymentProvider()).thenReturn(paymentProvider);

        when(detailsResult.getCardType()).thenReturn(CARD_TYPE);
        when(subscriptionData.getCardType()).thenReturn(CARD_TYPE);

        final Optional<BrainTreePaymentDetailsResult> result = unit.findPaymentMethod(customerResult, billingAddress, subscriptionData);

        assertEquals(detailsResult, result.get());
        verify(customerResult).getPaymentMethods();
        verify(detailsResult).getPaymentProvider();
        verify(subscriptionData, times(4)).getPaymentProvider();

        verify(detailsResult).getCardType();
        verify(subscriptionData).getCardType();
    }

    @Test
    public void shouldFindGooglePaymentMethod() {
        final List<BrainTreePaymentDetailsResult> paymentMethods = Arrays.asList(detailsResult);
        final String paymentProvider = BrainTreePaymentMethod.ANDROIDPAYCARD.getCode();

        when(customerResult.getPaymentMethods()).thenReturn(paymentMethods);
        when(detailsResult.getPaymentProvider()).thenReturn(paymentProvider);
        when(subscriptionData.getPaymentProvider()).thenReturn(paymentProvider);

        when(detailsResult.getCardType()).thenReturn(CARD_TYPE);
        when(subscriptionData.getCardType()).thenReturn(CARD_TYPE);

        final Optional<BrainTreePaymentDetailsResult> result = unit.findPaymentMethod(customerResult, billingAddress, subscriptionData);

        assertEquals(detailsResult, result.get());
        verify(customerResult).getPaymentMethods();
        verify(detailsResult).getPaymentProvider();
        verify(subscriptionData, times(5)).getPaymentProvider();

        verify(detailsResult).getCardType();
        verify(subscriptionData).getCardType();
    }

    @Test
    public void shouldFindCreditCardPaymentMethod() {
        final BrainTreeBillingAddressResult addressResult = mock(BrainTreeBillingAddressResult.class);
        final List<BrainTreePaymentDetailsResult> paymentMethods = Arrays.asList(detailsResult);
        final String paymentProvider = BrainTreePaymentMethod.CREDITCARD.getCode();

        when(customerResult.getPaymentMethods()).thenReturn(paymentMethods);
        when(detailsResult.getPaymentProvider()).thenReturn(paymentProvider);
        when(subscriptionData.getPaymentProvider()).thenReturn(paymentProvider);

        when(detailsResult.getBillingAddress()).thenReturn(addressResult);

        when(detailsResult.getCardType()).thenReturn(CARD_TYPE);
        when(subscriptionData.getCardType()).thenReturn(CARD_TYPE);

        final Optional<BrainTreePaymentDetailsResult> result = unit.findPaymentMethod(customerResult, billingAddress, subscriptionData);

        assertEquals(detailsResult, result.get());
        verify(customerResult).getPaymentMethods();
        verify(detailsResult).getPaymentProvider();
        verify(subscriptionData, times(6)).getPaymentProvider();

        verify(detailsResult).getCardType();
        verify(subscriptionData).getCardType();
    }

    @Test
    public void shouldFindUSBankAccountPaymentMethod() {
        List<BrainTreePaymentDetailsResult> paymentMethods = Arrays.asList(detailsResult);
        final String paymentProvider = BrainTreePaymentMethod.USBANKACCOUNT.getCode();

        when(customerResult.getPaymentMethods()).thenReturn(paymentMethods);
        when(detailsResult.getPaymentProvider()).thenReturn(paymentProvider);
        when(subscriptionData.getPaymentProvider()).thenReturn(paymentProvider);

        when(detailsResult.getLast4()).thenReturn(LAST_4);
        when(subscriptionData.getLast4()).thenReturn(LAST_4);

        final Optional<BrainTreePaymentDetailsResult> result = unit.findPaymentMethod(customerResult, billingAddress, subscriptionData);

        assertEquals(detailsResult, result.get());
        verify(customerResult).getPaymentMethods();
        verify(detailsResult).getPaymentProvider();
        verify(subscriptionData, times(7)).getPaymentProvider();

        verify(detailsResult).getLast4();
        verify(subscriptionData).getLast4();
    }

    @Test
    public void shouldNotFindPaymentMethod() {
        List<BrainTreePaymentDetailsResult> paymentMethods = Arrays.asList(detailsResult);
        final String paymentProvider = "I_DO_NOT_THIS_PAYMENT_METHOD_TYPE";

        when(customerResult.getPaymentMethods()).thenReturn(paymentMethods);
        when(detailsResult.getPaymentProvider()).thenReturn(paymentProvider);
        when(subscriptionData.getPaymentProvider()).thenReturn(paymentProvider);

        final Optional<BrainTreePaymentDetailsResult> result = unit.findPaymentMethod(customerResult, billingAddress, subscriptionData);

        assertTrue(result.isEmpty());
        verify(customerResult).getPaymentMethods();
        verify(detailsResult).getPaymentProvider();
        verify(subscriptionData, times(7)).getPaymentProvider();
    }

    @Test
    public void shouldSetBrainTreeDefaultPaymentInfo() throws CommandNotSupportedException {
        final BrainTreeSetDefaultPaymentInfoCommand command = mock(BrainTreeSetDefaultPaymentInfoCommand.class);
        final BrainTreeUpdateCustomerResult result = mock(BrainTreeUpdateCustomerResult.class);
        final BrainTreeUpdateCustomerRequest request = mock(BrainTreeUpdateCustomerRequest.class);

        when(commandFactory.createCommand(BrainTreeSetDefaultPaymentInfoCommand.class)).thenReturn(command);
        when(command.perform(request)).thenReturn(result);

        assertEquals(result, unit.setBrainTreeDefaultPaymentInfo(request));

        verify(commandFactory).createCommand(BrainTreeSetDefaultPaymentInfoCommand.class);
        verify(command).perform(request);
    }

    @Test
    public void shouldThrowExceptionForSetBrainTreeDefaultPaymentInfo() throws CommandNotSupportedException {
        final BrainTreeUpdateCustomerRequest request = mock(BrainTreeUpdateCustomerRequest.class);

        exception.expect(AdapterException.class);
        exception.expectMessage(COMMAND_NOT_SUPPORTED_EXCEPTION);

        when(commandFactory.createCommand(BrainTreeSetDefaultPaymentInfoCommand.class))
                .thenThrow(new CommandNotSupportedException(COMMAND_NOT_SUPPORTED_EXCEPTION));

        unit.setBrainTreeDefaultPaymentInfo(request);

        verify(commandFactory).createCommand(BrainTreeSetDefaultPaymentInfoCommand.class);
    }

    @Test
    public void createUsBankAccountPaymentMethodShouldPerformCommand() throws CommandNotSupportedException {
        final BrainTreeCreateUsBankAccountPaymentMethodCommand command = mock(BrainTreeCreateUsBankAccountPaymentMethodCommand.class);
        final BrainTreeCreatePaymentMethodResult result = mock(BrainTreeCreatePaymentMethodResult.class);
        final BrainTreeCreatePaymentMethodRequest request = mock(BrainTreeCreatePaymentMethodRequest.class);

        when(commandFactory.createCommand(BrainTreeCreateUsBankAccountPaymentMethodCommand.class)).thenReturn(command);
        when(command.perform(request)).thenReturn(result);

        assertEquals(result, unit.createUsBankAccountPaymentMethod(request));
    }

    @Test
    public void shouldReturnNullForCreateUsBankAccountPaymentMethod() throws CommandNotSupportedException {
        final BrainTreeCreatePaymentMethodRequest request = mock(BrainTreeCreatePaymentMethodRequest.class);

        when(commandFactory.createCommand(BrainTreeCreateUsBankAccountPaymentMethodCommand.class))
                .thenThrow(new CommandNotSupportedException(COMMAND_NOT_SUPPORTED_EXCEPTION));

        assertNull(unit.createUsBankAccountPaymentMethod(request));

        verify(commandFactory).createCommand(BrainTreeCreateUsBankAccountPaymentMethodCommand.class);
    }

    @Test
    public void shouldGetThreeDSDetailsByNonce() throws CommandNotSupportedException {
        final BrainTreeGetPaymentDetailsByNonce command = mock(BrainTreeGetPaymentDetailsByNonce.class);
        final BraintreePaymentMethodData result = mock(BraintreePaymentMethodData.class);

        when(commandFactory.createCommand(BrainTreeGetPaymentDetailsByNonce.class)).thenReturn(command);
        when(command.perform(NONCE)).thenReturn(result);

        assertEquals(result, unit.getPaymentDetailsByNonce(NONCE));

        verify(commandFactory).createCommand(BrainTreeGetPaymentDetailsByNonce.class);
        verify(command).perform(NONCE);
    }

    @Test
    public void shouldThrowExceptionForGetThreeDSDetailsByNonce() throws CommandNotSupportedException {
        exception.expect(AdapterException.class);
        exception.expectMessage(COMMAND_NOT_SUPPORTED_EXCEPTION);

        when(commandFactory.createCommand(BrainTreeGetPaymentDetailsByNonce.class))
                .thenThrow(new CommandNotSupportedException(COMMAND_NOT_SUPPORTED_EXCEPTION));

        unit.getPaymentDetailsByNonce(NONCE);

        verify(commandFactory).createCommand(BrainTreeGetPaymentDetailsByNonce.class);
    }

    @Test
    public void findRefundedTransactionsShouldThrowException() throws CommandNotSupportedException {
        exception.expect(AdapterException.class);
        final BrainTreeFindTransactionRequest request = mock(BrainTreeFindTransactionRequest.class);

        when(commandFactory.createCommand(BrainTreeFindRefundCommand.class))
                .thenThrow(new CommandNotSupportedException(COMMAND_NOT_SUPPORTED_EXCEPTION));

        unit.findRefundedTransactions(request);
    }

    @Test
    public void findRefundedTransactionsShouldPerformCommand() throws CommandNotSupportedException {
        final BrainTreeFindRefundCommand command = mock(BrainTreeFindRefundCommand.class);
        final BrainTreeFindTransactionResult result = mock(BrainTreeFindTransactionResult.class);
        final BrainTreeFindTransactionRequest request = mock(BrainTreeFindTransactionRequest.class);

        when(commandFactory.createCommand(BrainTreeFindRefundCommand.class)).thenReturn(command);
        when(command.perform(request)).thenReturn(result);

        assertEquals(result, unit.findRefundedTransactions(request));
    }

    @Test
    public void createPaymentMethodForCustomerShouldCreateCustomerAndSavePaymentMethod()
        throws CommandNotSupportedException {
        final BrainTreeSubscriptionInfoData subscriptionData = mock(BrainTreeSubscriptionInfoData.class);
        final BillingInfo billingInfo = mock(BillingInfo.class);
        final CreateSubscriptionCommand subscriptionCommand = mock(CreateSubscriptionCommand.class);
        final BrainTreeCreatePaymentMethodCommand createPaymentMethodCommand = mock(
            BrainTreeCreatePaymentMethodCommand.class);
        final BrainTreeCreatePaymentMethodResult createResult = mock(BrainTreeCreatePaymentMethodResult.class);

        when(commandFactory.createCommand(CreateSubscriptionCommand.class)).thenReturn(subscriptionCommand);
        when(commandFactory.createCommand(BrainTreeCreatePaymentMethodCommand.class))
            .thenReturn(createPaymentMethodCommand);
        when(subscriptionCommand.perform(any())).thenReturn(subscriptionResult);
        when(createPaymentMethodCommand.perform(any())).thenReturn(createResult);
        when(billingAddressConverter.convert(billingAddress)).thenReturn(billingInfo);

        assertEquals(createResult, unit.createPaymentMethodOnBraintreeOrGetExisting(customerModel, billingAddress, subscriptionData));
    }

    @Test
    public void shouldCreateCreditCardPaymentMethodForCustomerShouldCreateCustomerAndSavePaymentMethod()
            throws CommandNotSupportedException {
        final BrainTreeSubscriptionInfoData subscriptionData = mock(BrainTreeSubscriptionInfoData.class);
        final BillingInfo billingInfo = mock(BillingInfo.class);
        final CreateSubscriptionCommand subscriptionCommand = mock(CreateSubscriptionCommand.class);
        final BrainTreeCreateCreditCardPaymentMethodCommand command = mock(
                BrainTreeCreateCreditCardPaymentMethodCommand.class);
        final BrainTreePaymentMethodResult result = mock(BrainTreePaymentMethodResult.class);

        when(subscriptionData.getPaymentProvider()).thenReturn(BrainTreePaymentMethod.CREDITCARD.getCode());

        when(commandFactory.createCommand(CreateSubscriptionCommand.class)).thenReturn(subscriptionCommand);
        when(commandFactory.createCommand(BrainTreeCreateCreditCardPaymentMethodCommand.class))
                .thenReturn(command);
        when(subscriptionCommand.perform(any())).thenReturn(subscriptionResult);
        when(command.perform(any())).thenReturn(result);
        when(billingAddressConverter.convert(billingAddress)).thenReturn(billingInfo);

        assertEquals(result, unit.createPaymentMethodOnBraintreeOrGetExisting(customerModel, billingAddress, subscriptionData));

        verify(subscriptionData, times(2)).getPaymentProvider();
    }

    @Test
    public void shouldCreateUsBankAccountPaymentMethodForCustomerShouldCreateCustomerAndSavePaymentMethod()
            throws CommandNotSupportedException {
        final BrainTreeSubscriptionInfoData subscriptionData = mock(BrainTreeSubscriptionInfoData.class);
        final BillingInfo billingInfo = mock(BillingInfo.class);
        final CreateSubscriptionCommand subscriptionCommand = mock(CreateSubscriptionCommand.class);
        final BrainTreeCreateUsBankAccountPaymentMethodCommand command = mock(
                BrainTreeCreateUsBankAccountPaymentMethodCommand.class);
        final BrainTreePaymentMethodResult result = mock(BrainTreePaymentMethodResult.class);

        when(subscriptionData.getPaymentProvider()).thenReturn(BrainTreePaymentMethod.USBANKACCOUNT.getCode());

        when(commandFactory.createCommand(CreateSubscriptionCommand.class)).thenReturn(subscriptionCommand);
        when(commandFactory.createCommand(BrainTreeCreateUsBankAccountPaymentMethodCommand.class))
                .thenReturn(command);
        when(subscriptionCommand.perform(any())).thenReturn(subscriptionResult);
        when(command.perform(any())).thenReturn(result);
        when(billingAddressConverter.convert(billingAddress)).thenReturn(billingInfo);

        assertEquals(result, unit.createPaymentMethodOnBraintreeOrGetExisting(customerModel, billingAddress, subscriptionData));

        verify(subscriptionData, times(4)).getPaymentProvider();

    }

    @Test
    public void createPaymentMethodForCustomerShouldCreatePaymentEvenDuplicateIfPayPalExist()
        throws CommandNotSupportedException {
        final BrainTreeSubscriptionInfoData subscriptionData = mock(BrainTreeSubscriptionInfoData.class);
        final BillingInfo billingInfo = mock(BillingInfo.class);
        final BrainTreeFindCustomerCommand findCustomerCommand = mock(BrainTreeFindCustomerCommand.class);
        final List<BrainTreePaymentDetailsResult> payPalAccounts = new ArrayList<>();
        final List<PaymentInfoModel> customerPaymentInfos = new ArrayList<>();
        final BrainTreeCreatePaymentMethodCommand createPaymentCommand = mock(BrainTreeCreatePaymentMethodCommand.class);

        payPalAccounts.add(detailsResult);
        customerPaymentInfos.add(paymentInfo);
        when(customerModel.getBraintreeCustomerId()).thenReturn(CUSTOMER_ID);
        when(customerResult.isCustomerExist()).thenReturn(true);
        when(commandFactory.createCommand(BrainTreeFindCustomerCommand.class)).thenReturn(findCustomerCommand);
        when(findCustomerCommand.perform(any())).thenReturn(customerResult);
        when(billingAddressConverter.convert(billingAddress)).thenReturn(billingInfo);
        when(subscriptionData.getPaymentProvider()).thenReturn(BrainTreePaymentMethod.PAYPALACCOUNT.getCode());
        when(commandFactory.createCommand(BrainTreeCreatePaymentMethodCommand.class)).thenReturn(createPaymentCommand);

        unit.createPaymentMethodOnBraintreeOrGetExisting(customerModel, billingAddress, subscriptionData);

        verify(createPaymentCommand).perform(any());
    }

    @Test
    public void shouldCreateCustomer() {
        doReturn(subscriptionResult).when(unit).createCustomerSubscription(any(CreateSubscriptionRequest.class));

        unit.createCustomer(customerModel, billingAddress);

        verify(unit).createCustomerSubscription(any(CreateSubscriptionRequest.class));
        verify(subscriptionResult, times(2)).getMerchantTransactionCode();
        verify(modelService).save(customerModel);
    }

    @Test
    public void shouldThrowExceptionForCreateCustomerSubscription() throws CommandNotSupportedException {
        final CreateSubscriptionRequest request = mock(CreateSubscriptionRequest.class);

        exception.expect(AdapterException.class);
        exception.expectMessage(COMMAND_NOT_SUPPORTED_EXCEPTION);

        when(commandFactory.createCommand(CreateSubscriptionCommand.class))
                .thenThrow(new CommandNotSupportedException(COMMAND_NOT_SUPPORTED_EXCEPTION));

        unit.createCustomerSubscription(request);

        verify(commandFactory).createCommand(CreateSubscriptionCommand.class);
    }

    @Test
    public void getWebhookNotificationShouldThrowException() throws CommandNotSupportedException {
        exception.expect(AdapterException.class);
        final BrainTreeWebhookNotificationRequest request = mock(BrainTreeWebhookNotificationRequest.class);

        unit.getWebhookNotification(request);
    }

    @Test
    public void getWebhookNotificationShouldPerformCommand() throws CommandNotSupportedException {
        final BrainTreeWebhookNotificationCommand command = mock(BrainTreeWebhookNotificationCommand.class);
        final WebhookNotification result = mock(WebhookNotification.class);
        final BrainTreeWebhookNotificationRequest request = mock(BrainTreeWebhookNotificationRequest.class);

        when(commandFactory.createCommand(BrainTreeWebhookNotificationCommand.class)).thenReturn(command);
        when(command.perform(request)).thenReturn(result);

        assertEquals(result, unit.getWebhookNotification(request));
    }

    @Test
    public void getPaymentMethodFromBTByTokenShouldThrowException() throws CommandNotSupportedException {
        exception.expect(AdapterException.class);
        when(commandFactory.createCommand(BrainTreeGetPaymentMethodByTokenCommand.class))
            .thenThrow(new CommandNotSupportedException(COMMAND_NOT_SUPPORTED_EXCEPTION));

        unit.getPaymentMethodFromBTByToken(PAYMENT_METHOD_TOKEN);
    }

    @Test
    public void getPaymentMethodFromBTByTokenShouldPerformCommand() throws CommandNotSupportedException {
        final BrainTreeGetPaymentMethodByTokenCommand command = mock(BrainTreeGetPaymentMethodByTokenCommand.class);

        when(commandFactory.createCommand(BrainTreeGetPaymentMethodByTokenCommand.class)).thenReturn(command);
        when(command.perform(PAYMENT_METHOD_TOKEN)).thenReturn(detailsResult);

        assertEquals(detailsResult, unit.getPaymentMethodFromBTByToken(PAYMENT_METHOD_TOKEN));
    }

    @Test
    public void exchangeLegacyIdToGraphQLIdShouldThrowException() throws CommandNotSupportedException {
        exception.expect(AdapterException.class);
        final BrainTreeExchangeLegacyIdRequest request = mock(BrainTreeExchangeLegacyIdRequest.class);

        when(commandFactory.createCommand(BrainTreeGraphQLExchangeLegacyIdCommand.class))
            .thenThrow(new CommandNotSupportedException(COMMAND_NOT_SUPPORTED_EXCEPTION));

        unit.exchangeLegacyIdToGraphQLId(request);
    }

    @Test
    public void exchangeLegacyIdToGraphQLIdShouldPerformCommand() throws CommandNotSupportedException {
        final BrainTreeGraphQLExchangeLegacyIdCommand command = mock(BrainTreeGraphQLExchangeLegacyIdCommand.class);
        final BrainTreeExchangeLegacyIdRequest request = mock(BrainTreeExchangeLegacyIdRequest.class);

        when(commandFactory.createCommand(BrainTreeGraphQLExchangeLegacyIdCommand.class)).thenReturn(command);
        when(command.perform(request)).thenReturn(PAYMENT_METHOD_TOKEN);

        assertEquals(PAYMENT_METHOD_TOKEN, unit.exchangeLegacyIdToGraphQLId(request));
    }

    @Test
    public void tokenizeCreditCardShouldThrowException() throws CommandNotSupportedException {
        exception.expect(AdapterException.class);
        final BrainTreeTokenizeCreditCardRequest request = mock(BrainTreeTokenizeCreditCardRequest.class);

        unit.tokenizeCreditCard(request);
    }

    @Test
    public void tokenizeCreditCardShouldPerformCommand() throws CommandNotSupportedException {
        final BrainTreeGraphQLTokenizeCreditCardCommand command = mock(BrainTreeGraphQLTokenizeCreditCardCommand.class);
        final BrainTreeTokenizeCardResult result = mock(BrainTreeTokenizeCardResult.class);
        final BrainTreeTokenizeCreditCardRequest request = mock(BrainTreeTokenizeCreditCardRequest.class);

        when(commandFactory.createCommand(BrainTreeGraphQLTokenizeCreditCardCommand.class)).thenReturn(command);
        when(command.perform(request)).thenReturn(result);

        assertEquals(result, unit.tokenizeCreditCard(request));
    }

    @Test
    public void getImageSourceForPaymentMethodShouldReturnNull() {
        final Map<String, String> methodImages = new HashMap<>();

        when(brainTreeConfigService.getAcceptedPaymentMethodImages(PAYMENT_METHOD_STRINGS)).thenReturn(methodImages);

        assertNull(unit.getImageSourceForPaymentMethod(CARD_TYPE, PAYMENT_METHOD_STRINGS));
    }

    @Test
    public void getImageSourceForPaymentMethod() {
        final Map<String, String> methodImages = new HashMap<>();
        methodImages.put(CARDTYPE, VALUE_IMAGE);

        when(brainTreeConfigService.getAcceptedPaymentMethodImages(PAYMENT_METHOD_STRINGS)).thenReturn(methodImages);

        assertEquals(VALUE_IMAGE, unit.getImageSourceForPaymentMethod(CARD_TYPE, PAYMENT_METHOD_STRINGS));
    }

    @Test
    public void updatePaymentInfoShouldNotUpdateIfPaymentDetailsNotHaveToken() {
        final PaymentInfoModel paymentInfo = mock(PaymentInfoModel.class);

        unit.updatePaymentInfo(paymentInfo, detailsResult);

        verify(modelService, never()).save(paymentInfo);
    }

    @Test
    public void updatePaymentInfoShouldNotUpdateIfPaymentDetailsHaveNoToken() {
        when(detailsResult.getPaymentMethodToken()).thenReturn(PAYMENT_METHOD_TOKEN);
        when(paymentInfo.isShouldBeSaved()).thenReturn(true);
        when(brainTreeConfigService.isStoreInVault()).thenReturn(true);
        when(detailsResult.getLast4()).thenReturn(LAST_4);
        when(detailsResult.getCardType()).thenReturn(BrainTreeCardType.MAESTRO.getCode());

        unit.updatePaymentInfo(paymentInfo, detailsResult);

        verify(paymentInfo).setSaved(true);
        verify(paymentInfo).setUsePaymentMethodToken(true);
        verify(paymentInfo).setPaymentMethodToken(PAYMENT_METHOD_TOKEN);
        verify(paymentInfo).setCardNumber(String.format(CARD_NUMBER_MASK, LAST_4));
        verify(modelService).save(paymentInfo);
    }

    @Test
    public void shouldFindTransactionByIdWhenItExists() throws CommandNotSupportedException {
        when(commandFactory.createCommand(BrainTreeFindTransactionByIdCommand.class)).thenReturn(findTransactionByIdCommand);

        unit.findTransactionById(TRANSACTION_ID);

        verify(findTransactionByIdCommand).perform(TRANSACTION_ID);
    }

    @Test(expected = AdapterException.class)
    public void shouldThrowExceptionWhenTransactionByIdNotExists() throws CommandNotSupportedException {
        when(commandFactory.createCommand(BrainTreeFindTransactionByIdCommand.class)).thenThrow(AdapterException.class);

        unit.findTransactionById(TRANSACTION_ID);
    }

}
