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

#include <utility>
#include <vector>

#include "base/command_line.h"
#include "base/macros.h"
#include "base/strings/string_piece.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind_test_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/scoped_task_environment.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/values.h"
#include "components/autofill/core/browser/autofill_experiments.h"
#include "components/autofill/core/browser/autofill_test_utils.h"
#include "components/autofill/core/browser/credit_card_save_manager.h"
#include "components/autofill/core/browser/local_card_migration_manager.h"
#include "components/autofill/core/browser/payments/payments_client.h"
#include "components/autofill/core/browser/test_personal_data_manager.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/autofill/core/common/autofill_switches.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "components/variations/variations_associated_data.h"
#include "components/variations/variations_http_header_provider.h"
#include "services/identity/public/cpp/identity_test_environment.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.h"
#include "services/network/test/test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace autofill {
namespace payments {
namespace {

int kAllDetectableValues =
    CreditCardSaveManager::DetectedValue::CVC |
    CreditCardSaveManager::DetectedValue::CARDHOLDER_NAME |
    CreditCardSaveManager::DetectedValue::ADDRESS_NAME |
    CreditCardSaveManager::DetectedValue::ADDRESS_LINE |
    CreditCardSaveManager::DetectedValue::LOCALITY |
    CreditCardSaveManager::DetectedValue::ADMINISTRATIVE_AREA |
    CreditCardSaveManager::DetectedValue::POSTAL_CODE |
    CreditCardSaveManager::DetectedValue::COUNTRY_CODE |
    CreditCardSaveManager::DetectedValue::HAS_GOOGLE_PAYMENTS_ACCOUNT;

}  // namespace

class PaymentsClientTest : public testing::Test {
 public:
  PaymentsClientTest()
      : result_(AutofillClient::NONE), weak_ptr_factory_(this) {}
  ~PaymentsClientTest() override {}

  void SetUp() override {
    // Silence the warning for mismatching sync and Payments servers.
    base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
        switches::kWalletServiceUseSandbox, "0");

    result_ = AutofillClient::NONE;
    server_id_.clear();
    real_pan_.clear();
    legal_message_.reset();

    factory()->SetInterceptor(base::BindLambdaForTesting(
        [&](const network::ResourceRequest& request) {
          intercepted_headers_ = request.headers;
          intercepted_body_ = network::GetUploadData(request);
        }));
    test_shared_loader_factory_ =
        base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
            &test_url_loader_factory_);
    TestingPrefServiceSimple pref_service_;
    client_ = std::make_unique<PaymentsClient>(
        test_shared_loader_factory_, &pref_service_,
        identity_test_env_.identity_manager(), &test_personal_data_);
    test_personal_data_.SetAccountInfoForPayments(
        identity_test_env_.MakePrimaryAccountAvailable("example@gmail.com"));
  }

  void TearDown() override { client_.reset(); }

  void EnableAutofillSendExperimentIdsInPaymentsRPCs() {
    scoped_feature_list_.InitAndEnableFeature(
        features::kAutofillSendExperimentIdsInPaymentsRPCs);
  }

  void EnableAutofillGetPaymentsIdentityFromSync() {
    scoped_feature_list_.InitAndEnableFeature(
        features::kAutofillGetPaymentsIdentityFromSync);
  }

  void DisableAutofillSendExperimentIdsInPaymentsRPCs() {
    scoped_feature_list_.InitAndDisableFeature(
        features::kAutofillSendExperimentIdsInPaymentsRPCs);
  }

  // Registers a field trial with the specified name and group and an associated
  // google web property variation id.
  void CreateFieldTrialWithId(const std::string& trial_name,
                              const std::string& group_name,
                              int variation_id) {
    variations::AssociateGoogleVariationID(
        variations::GOOGLE_WEB_PROPERTIES, trial_name, group_name,
        static_cast<variations::VariationID>(variation_id));
    base::FieldTrialList::CreateFieldTrial(trial_name, group_name)->group();
  }

  void OnDidGetRealPan(AutofillClient::PaymentsRpcResult result,
                       const std::string& real_pan) {
    result_ = result;
    real_pan_ = real_pan;
  }

  void OnDidGetUploadDetails(AutofillClient::PaymentsRpcResult result,
                             const base::string16& context_token,
                             std::unique_ptr<base::Value> legal_message) {
    result_ = result;
    legal_message_ = std::move(legal_message);
  }

  void OnDidUploadCard(AutofillClient::PaymentsRpcResult result,
                       const std::string& server_id) {
    result_ = result;
    server_id_ = server_id;
  }

  void OnDidMigrateLocalCards(
      AutofillClient::PaymentsRpcResult result,
      std::unique_ptr<std::unordered_map<std::string, std::string>> save_result,
      const std::string& display_text) {
    result_ = result;
    save_result_ = std::move(save_result);
    display_text_ = display_text;
  }

 protected:
  base::test::ScopedFeatureList scoped_feature_list_;

  // Issue an UnmaskCard request. This requires an OAuth token before starting
  // the request.
  void StartUnmasking() {
    PaymentsClient::UnmaskRequestDetails request_details;
    request_details.billing_customer_number = 111222333444;
    request_details.card = test::GetMaskedServerCard();
    request_details.user_response.cvc = base::ASCIIToUTF16("123");
    request_details.risk_data = "some risk data";
    client_->UnmaskCard(request_details,
                        base::BindOnce(&PaymentsClientTest::OnDidGetRealPan,
                                       weak_ptr_factory_.GetWeakPtr()));
  }

  // Issue a GetUploadDetails request.
  void StartGettingUploadDetails(
      PaymentsClient::UploadCardSource upload_card_source =
          PaymentsClient::UploadCardSource::UNKNOWN_UPLOAD_CARD_SOURCE) {
    client_->GetUploadDetails(
        BuildTestProfiles(), kAllDetectableValues, std::vector<const char*>(),
        "language-LOCALE",
        base::BindOnce(&PaymentsClientTest::OnDidGetUploadDetails,
                       weak_ptr_factory_.GetWeakPtr()),
        /*billable_service_number=*/12345, upload_card_source);
  }

  // Issue an UploadCard request. This requires an OAuth token before starting
  // the request.
  void StartUploading(bool include_cvc) {
    PaymentsClient::UploadRequestDetails request_details;
    request_details.billing_customer_number = 111222333444;
    request_details.card = test::GetCreditCard();
    if (include_cvc)
      request_details.cvc = base::ASCIIToUTF16("123");
    request_details.context_token = base::ASCIIToUTF16("context token");
    request_details.risk_data = "some risk data";
    request_details.app_locale = "language-LOCALE";
    request_details.profiles = BuildTestProfiles();
    client_->UploadCard(request_details,
                        base::BindOnce(&PaymentsClientTest::OnDidUploadCard,
                                       weak_ptr_factory_.GetWeakPtr()));
  }

  void StartMigrating(bool has_cardholder_name) {
    PaymentsClient::MigrationRequestDetails request_details;
    request_details.context_token = base::ASCIIToUTF16("context token");
    request_details.risk_data = "some risk data";
    request_details.app_locale = "language-LOCALE";

    migratable_credit_cards_.clear();
    CreditCard card1 = test::GetCreditCard();
    CreditCard card2 = test::GetCreditCard2();
    if (!has_cardholder_name) {
      card1.SetRawInfo(CREDIT_CARD_NAME_FULL, base::UTF8ToUTF16(""));
      card2.SetRawInfo(CREDIT_CARD_NAME_FULL, base::UTF8ToUTF16(""));
    }
    migratable_credit_cards_.push_back(MigratableCreditCard(card1));
    migratable_credit_cards_.push_back(MigratableCreditCard(card2));
    client_->MigrateCards(
        request_details, migratable_credit_cards_,
        base::BindOnce(&PaymentsClientTest::OnDidMigrateLocalCards,
                       weak_ptr_factory_.GetWeakPtr()));
  }

  network::TestURLLoaderFactory* factory() { return &test_url_loader_factory_; }

  const std::string& GetUploadData() { return intercepted_body_; }

  net::HttpRequestHeaders* GetRequestHeaders() { return &intercepted_headers_; }

  // Issues access token in response to any access token request. This will
  // start the Payments Request which requires the authentication.
  void IssueOAuthToken() {
    identity_test_env_.WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
        "totally_real_token",
        base::Time::Now() + base::TimeDelta::FromDays(10));

    // Verify the auth header.
    std::string auth_header_value;
    EXPECT_TRUE(intercepted_headers_.GetHeader(
        net::HttpRequestHeaders::kAuthorization, &auth_header_value))
        << intercepted_headers_.ToString();
    EXPECT_EQ("Bearer totally_real_token", auth_header_value);
  }

  void ReturnResponse(net::HttpStatusCode response_code,
                      const std::string& response_body) {
    client_->OnSimpleLoaderCompleteInternal(response_code, response_body);
  }

  AutofillClient::PaymentsRpcResult result_;
  std::string server_id_;
  std::string real_pan_;
  std::unique_ptr<base::Value> legal_message_;
  std::vector<MigratableCreditCard> migratable_credit_cards_;
  std::unique_ptr<std::unordered_map<std::string, std::string>> save_result_;
  std::string display_text_;

  base::test::ScopedTaskEnvironment scoped_task_environment_;
  network::TestURLLoaderFactory test_url_loader_factory_;
  scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_;
  TestPersonalDataManager test_personal_data_;
  std::unique_ptr<PaymentsClient> client_;
  identity::IdentityTestEnvironment identity_test_env_;

  net::HttpRequestHeaders intercepted_headers_;
  std::string intercepted_body_;
  base::WeakPtrFactory<PaymentsClientTest> weak_ptr_factory_;

 private:
  DISALLOW_COPY_AND_ASSIGN(PaymentsClientTest);

  std::vector<AutofillProfile> BuildTestProfiles() {
    std::vector<AutofillProfile> profiles;
    profiles.push_back(BuildProfile("John", "Smith", "1234 Main St.", "Miami",
                                    "FL", "32006", "212-555-0162"));
    profiles.push_back(BuildProfile("Pat", "Jones", "432 Oak Lane", "Lincoln",
                                    "OH", "43005", "(834)555-0090"));
    return profiles;
  }

  AutofillProfile BuildProfile(base::StringPiece first_name,
                               base::StringPiece last_name,
                               base::StringPiece address_line,
                               base::StringPiece city,
                               base::StringPiece state,
                               base::StringPiece zip,
                               base::StringPiece phone_number) {
    AutofillProfile profile;

    profile.SetInfo(NAME_FIRST, ASCIIToUTF16(first_name), "en-US");
    profile.SetInfo(NAME_LAST, ASCIIToUTF16(last_name), "en-US");
    profile.SetInfo(ADDRESS_HOME_LINE1, ASCIIToUTF16(address_line), "en-US");
    profile.SetInfo(ADDRESS_HOME_CITY, ASCIIToUTF16(city), "en-US");
    profile.SetInfo(ADDRESS_HOME_STATE, ASCIIToUTF16(state), "en-US");
    profile.SetInfo(ADDRESS_HOME_ZIP, ASCIIToUTF16(zip), "en-US");
    profile.SetInfo(PHONE_HOME_WHOLE_NUMBER, ASCIIToUTF16(phone_number),
                    "en-US");

    return profile;
  }
};

TEST_F(PaymentsClientTest, OAuthError) {
  StartUnmasking();
  identity_test_env_.WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
      GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE));
  EXPECT_EQ(AutofillClient::PERMANENT_FAILURE, result_);
  EXPECT_TRUE(real_pan_.empty());
}

TEST_F(PaymentsClientTest,
       UnmaskRequestIncludesBillingCustomerNumberInRequest) {
  StartUnmasking();
  IssueOAuthToken();

  // Verify that the billing customer number is included in the request.
  EXPECT_TRUE(
      GetUploadData().find("%22external_customer_id%22:%22111222333444%22") !=
      std::string::npos);
}

TEST_F(PaymentsClientTest, UnmaskSuccess) {
  StartUnmasking();
  IssueOAuthToken();
  ReturnResponse(net::HTTP_OK, "{ \"pan\": \"1234\" }");
  EXPECT_EQ(AutofillClient::SUCCESS, result_);
  EXPECT_EQ("1234", real_pan_);
}

TEST_F(PaymentsClientTest, UnmaskSuccessAccountFromSyncTest) {
  EnableAutofillGetPaymentsIdentityFromSync();
  StartUnmasking();
  IssueOAuthToken();
  ReturnResponse(net::HTTP_OK, "{ \"pan\": \"1234\" }");
  EXPECT_EQ(AutofillClient::SUCCESS, result_);
  EXPECT_EQ("1234", real_pan_);
}

TEST_F(PaymentsClientTest, UnmaskIncludesChromeUserContext) {
  scoped_feature_list_.InitWithFeatures(
      {features::kAutofillGetPaymentsIdentityFromSync},  // Enabled
      {features::kAutofillEnableAccountWalletStorage});  // Disabled

  StartUnmasking();
  IssueOAuthToken();
  ReturnResponse(net::HTTP_OK, "{}");

  // ChromeUserContext was set.
  EXPECT_TRUE(GetUploadData().find("chrome_user_context") != std::string::npos);
  EXPECT_TRUE(GetUploadData().find("full_sync_enabled") != std::string::npos);
}

TEST_F(PaymentsClientTest,
       UnmaskIncludesChromeUserContextIfWalletStorageFlagEnabled) {
  scoped_feature_list_.InitWithFeatures(
      {features::kAutofillEnableAccountWalletStorage},    // Enabled
      {features::kAutofillGetPaymentsIdentityFromSync});  // Disabled

  StartUnmasking();
  IssueOAuthToken();
  ReturnResponse(net::HTTP_OK, "{}");

  // ChromeUserContext was set.
  EXPECT_TRUE(GetUploadData().find("chrome_user_context") != std::string::npos);
  EXPECT_TRUE(GetUploadData().find("full_sync_enabled") != std::string::npos);
}

TEST_F(PaymentsClientTest, UnmaskExcludesChromeUserContextIfExperimentsOff) {
  scoped_feature_list_.InitWithFeatures(
      {},  // Enabled
      {features::kAutofillEnableAccountWalletStorage,
       features::kAutofillGetPaymentsIdentityFromSync});  // Disabled

  StartUnmasking();
  IssueOAuthToken();
  ReturnResponse(net::HTTP_OK, "{}");

  // ChromeUserContext was not set.
  EXPECT_TRUE(!GetUploadData().empty());
  EXPECT_TRUE(GetUploadData().find("chrome_user_context") == std::string::npos);
  EXPECT_TRUE(GetUploadData().find("full_sync_enabled") == std::string::npos);
}

TEST_F(PaymentsClientTest, GetDetailsSuccess) {
  StartGettingUploadDetails();
  ReturnResponse(
      net::HTTP_OK,
      "{ \"context_token\": \"some_token\", \"legal_message\": {} }");
  EXPECT_EQ(AutofillClient::SUCCESS, result_);
  EXPECT_NE(nullptr, legal_message_.get());
}

TEST_F(PaymentsClientTest, GetDetailsRemovesNonLocationData) {
  StartGettingUploadDetails();

  // Verify that the recipient name field and test names appear nowhere in the
  // upload data.
  EXPECT_TRUE(GetUploadData().find(PaymentsClient::kRecipientName) ==
              std::string::npos);
  EXPECT_TRUE(GetUploadData().find("John") == std::string::npos);
  EXPECT_TRUE(GetUploadData().find("Smith") == std::string::npos);
  EXPECT_TRUE(GetUploadData().find("Pat") == std::string::npos);
  EXPECT_TRUE(GetUploadData().find("Jones") == std::string::npos);

  // Verify that the phone number field and test numbers appear nowhere in the
  // upload data.
  EXPECT_TRUE(GetUploadData().find(PaymentsClient::kPhoneNumber) ==
              std::string::npos);
  EXPECT_TRUE(GetUploadData().find("212") == std::string::npos);
  EXPECT_TRUE(GetUploadData().find("555") == std::string::npos);
  EXPECT_TRUE(GetUploadData().find("0162") == std::string::npos);
  EXPECT_TRUE(GetUploadData().find("834") == std::string::npos);
  EXPECT_TRUE(GetUploadData().find("0090") == std::string::npos);
}

TEST_F(PaymentsClientTest, GetDetailsIncludesDetectedValuesInRequest) {
  StartGettingUploadDetails();

  // Verify that the detected values were included in the request.
  std::string detected_values_string =
      "\"detected_values\":" + std::to_string(kAllDetectableValues);
  EXPECT_TRUE(GetUploadData().find(detected_values_string) !=
              std::string::npos);
}

TEST_F(PaymentsClientTest, GetDetailsIncludesChromeUserContext) {
  scoped_feature_list_.InitWithFeatures(
      {features::kAutofillGetPaymentsIdentityFromSync},  // Enabled
      {features::kAutofillEnableAccountWalletStorage});  // Disabled

  StartGettingUploadDetails();

  // ChromeUserContext was set.
  EXPECT_TRUE(GetUploadData().find("chrome_user_context") != std::string::npos);
  EXPECT_TRUE(GetUploadData().find("full_sync_enabled") != std::string::npos);
}

TEST_F(PaymentsClientTest,
       GetDetailsIncludesChromeUserContextIfWalletStorageFlagEnabled) {
  scoped_feature_list_.InitWithFeatures(
      {features::kAutofillEnableAccountWalletStorage},    // Enabled
      {features::kAutofillGetPaymentsIdentityFromSync});  // Disabled

  StartGettingUploadDetails();

  // ChromeUserContext was set.
  EXPECT_TRUE(GetUploadData().find("chrome_user_context") != std::string::npos);
  EXPECT_TRUE(GetUploadData().find("full_sync_enabled") != std::string::npos);
}

TEST_F(PaymentsClientTest,
       GetDetailsExcludesChromeUserContextIfExperimentsOff) {
  scoped_feature_list_.InitWithFeatures(
      {},  // Enabled
      {features::kAutofillEnableAccountWalletStorage,
       features::kAutofillGetPaymentsIdentityFromSync});  // Disabled

  StartGettingUploadDetails();

  // ChromeUserContext was not set.
  EXPECT_TRUE(!GetUploadData().empty());
  EXPECT_TRUE(GetUploadData().find("chrome_user_context") == std::string::npos);
  EXPECT_TRUE(GetUploadData().find("full_sync_enabled") == std::string::npos);
}

TEST_F(PaymentsClientTest,
       GetDetailsIncludesUpstreamCheckoutFlowUploadCardSourceInRequest) {
  StartGettingUploadDetails(
      PaymentsClient::UploadCardSource::UPSTREAM_CHECKOUT_FLOW);

  // Verify that the correct upload card source was included in the request.
  EXPECT_TRUE(GetUploadData().find("UPSTREAM_CHECKOUT_FLOW") !=
              std::string::npos);
}

TEST_F(PaymentsClientTest,
       GetDetailsIncludesUpstreamSettingsPageUploadCardSourceInRequest) {
  StartGettingUploadDetails(
      PaymentsClient::UploadCardSource::UPSTREAM_SETTINGS_PAGE);

  // Verify that the correct upload card source was included in the request.
  EXPECT_TRUE(GetUploadData().find("UPSTREAM_SETTINGS_PAGE") !=
              std::string::npos);
}

TEST_F(PaymentsClientTest,
       GetDetailsIncludesUpstreamCardOcrUploadCardSourceInRequest) {
  StartGettingUploadDetails(
      PaymentsClient::UploadCardSource::UPSTREAM_CARD_OCR);

  // Verify that the correct upload card source was included in the request.
  EXPECT_TRUE(GetUploadData().find("UPSTREAM_CARD_OCR") != std::string::npos);
}

TEST_F(
    PaymentsClientTest,
    GetDetailsIncludesLocalCardMigrationCheckoutFlowUploadCardSourceInRequest) {
  StartGettingUploadDetails(
      PaymentsClient::UploadCardSource::LOCAL_CARD_MIGRATION_CHECKOUT_FLOW);

  // Verify that the correct upload card source was included in the request.
  EXPECT_TRUE(GetUploadData().find("LOCAL_CARD_MIGRATION_CHECKOUT_FLOW") !=
              std::string::npos);
}

TEST_F(
    PaymentsClientTest,
    GetDetailsIncludesLocalCardMigrationSettingsPageUploadCardSourceInRequest) {
  StartGettingUploadDetails(
      PaymentsClient::UploadCardSource::LOCAL_CARD_MIGRATION_SETTINGS_PAGE);

  // Verify that the correct upload card source was included in the request.
  EXPECT_TRUE(GetUploadData().find("LOCAL_CARD_MIGRATION_SETTINGS_PAGE") !=
              std::string::npos);
}

TEST_F(PaymentsClientTest, GetDetailsIncludesUnknownUploadCardSourceInRequest) {
  StartGettingUploadDetails();

  // Verify that the absence of an upload card source results in UNKNOWN.
  EXPECT_TRUE(GetUploadData().find("UNKNOWN_UPLOAD_CARD_SOURCE") !=
              std::string::npos);
}

TEST_F(PaymentsClientTest, GetUploadAccountFromSyncTest) {
  EnableAutofillGetPaymentsIdentityFromSync();
  // Set up a different account.
  const AccountInfo& secondary_account_info =
      identity_test_env_.MakeAccountAvailable("secondary@gmail.com");
  test_personal_data_.SetAccountInfoForPayments(secondary_account_info);

  StartUploading(/*include_cvc=*/true);
  ReturnResponse(net::HTTP_OK, "{}");

  // Issue a token for the secondary account.
  identity_test_env_.WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      secondary_account_info.account_id, "secondary_account_token",
      base::Time::Now() + base::TimeDelta::FromDays(10));

  // Verify the auth header.
  std::string auth_header_value;
  EXPECT_TRUE(intercepted_headers_.GetHeader(
      net::HttpRequestHeaders::kAuthorization, &auth_header_value))
      << intercepted_headers_.ToString();
  EXPECT_EQ("Bearer secondary_account_token", auth_header_value);
}

TEST_F(PaymentsClientTest, GetUploadDetailsVariationsTest) {
  // Register a trial and variation id, so that there is data in variations
  // headers. Also, the variations header provider may have been registered to
  // observe some other field trial list, so reset it.
  variations::VariationsHttpHeaderProvider::GetInstance()->ResetForTesting();
  base::FieldTrialList field_trial_list_(nullptr);
  CreateFieldTrialWithId("AutofillTest", "Group", 369);
  StartGettingUploadDetails();

  std::string value;
  EXPECT_TRUE(GetRequestHeaders()->GetHeader("X-Client-Data", &value));
  // Note that experiment information is stored in X-Client-Data.
  EXPECT_FALSE(value.empty());

  variations::VariationsHttpHeaderProvider::GetInstance()->ResetForTesting();
}

TEST_F(PaymentsClientTest, GetUploadDetailsVariationsTestExperimentFlagOff) {
  // Register a trial and variation id, so that there is data in variations
  // headers. Also, the variations header provider may have been registered to
  // observe some other field trial list, so reset it.
  DisableAutofillSendExperimentIdsInPaymentsRPCs();
  variations::VariationsHttpHeaderProvider::GetInstance()->ResetForTesting();
  base::FieldTrialList field_trial_list_(nullptr);
  CreateFieldTrialWithId("AutofillTest", "Group", 369);
  StartGettingUploadDetails();

  std::string value;
  EXPECT_FALSE(GetRequestHeaders()->GetHeader("X-Client-Data", &value));
  // Note that experiment information is stored in X-Client-Data.
  EXPECT_TRUE(value.empty());

  variations::VariationsHttpHeaderProvider::GetInstance()->ResetForTesting();
}

TEST_F(PaymentsClientTest, UploadCardVariationsTest) {
  // Register a trial and variation id, so that there is data in variations
  // headers. Also, the variations header provider may have been registered to
  // observe some other field trial list, so reset it.
  variations::VariationsHttpHeaderProvider::GetInstance()->ResetForTesting();
  base::FieldTrialList field_trial_list_(nullptr);
  CreateFieldTrialWithId("AutofillTest", "Group", 369);
  StartUploading(/*include_cvc=*/true);
  IssueOAuthToken();

  std::string value;
  EXPECT_TRUE(GetRequestHeaders()->GetHeader("X-Client-Data", &value));
  // Note that experiment information is stored in X-Client-Data.
  EXPECT_FALSE(value.empty());

  variations::VariationsHttpHeaderProvider::GetInstance()->ResetForTesting();
}

TEST_F(PaymentsClientTest, UploadCardVariationsTestExperimentFlagOff) {
  // Register a trial and variation id, so that there is data in variations
  // headers. Also, the variations header provider may have been registered to
  // observe some other field trial list, so reset it.
  DisableAutofillSendExperimentIdsInPaymentsRPCs();
  variations::VariationsHttpHeaderProvider::GetInstance()->ResetForTesting();
  base::FieldTrialList field_trial_list_(nullptr);
  CreateFieldTrialWithId("AutofillTest", "Group", 369);
  StartUploading(/*include_cvc=*/true);

  std::string value;
  EXPECT_FALSE(GetRequestHeaders()->GetHeader("X-Client-Data", &value));
  // Note that experiment information is stored in X-Client-Data.
  EXPECT_TRUE(value.empty());

  variations::VariationsHttpHeaderProvider::GetInstance()->ResetForTesting();
}

TEST_F(PaymentsClientTest, UnmaskCardVariationsTest) {
  // Register a trial and variation id, so that there is data in variations
  // headers. Also, the variations header provider may have been registered to
  // observe some other field trial list, so reset it.
  variations::VariationsHttpHeaderProvider::GetInstance()->ResetForTesting();
  base::FieldTrialList field_trial_list_(nullptr);
  CreateFieldTrialWithId("AutofillTest", "Group", 369);
  StartUnmasking();
  IssueOAuthToken();

  std::string value;
  EXPECT_TRUE(GetRequestHeaders()->GetHeader("X-Client-Data", &value));
  // Note that experiment information is stored in X-Client-Data.
  EXPECT_FALSE(value.empty());

  variations::VariationsHttpHeaderProvider::GetInstance()->ResetForTesting();
}

TEST_F(PaymentsClientTest, UnmaskCardVariationsTestExperimentOff) {
  // Register a trial and variation id, so that there is data in variations
  // headers. Also, the variations header provider may have been registered to
  // observe some other field trial list, so reset it.
  DisableAutofillSendExperimentIdsInPaymentsRPCs();
  variations::VariationsHttpHeaderProvider::GetInstance()->ResetForTesting();
  base::FieldTrialList field_trial_list_(nullptr);
  CreateFieldTrialWithId("AutofillTest", "Group", 369);
  StartUnmasking();

  std::string value;
  EXPECT_FALSE(GetRequestHeaders()->GetHeader("X-Client-Data", &value));
  // Note that experiment information is stored in X-Client-Data.
  EXPECT_TRUE(value.empty());

  variations::VariationsHttpHeaderProvider::GetInstance()->ResetForTesting();
}

TEST_F(PaymentsClientTest, MigrateCardsVariationsTest) {
  // Register a trial and variation id, so that there is data in variations
  // headers. Also, the variations header provider may have been registered to
  // observe some other field trial list, so reset it.
  EnableAutofillSendExperimentIdsInPaymentsRPCs();
  variations::VariationsHttpHeaderProvider::GetInstance()->ResetForTesting();
  base::FieldTrialList field_trial_list_(nullptr);
  CreateFieldTrialWithId("AutofillTest", "Group", 369);
  StartMigrating(/*has_cardholder_name=*/true);
  IssueOAuthToken();

  std::string value;
  EXPECT_TRUE(GetRequestHeaders()->GetHeader("X-Client-Data", &value));
  // Note that experiment information is stored in X-Client-Data.
  EXPECT_FALSE(value.empty());

  variations::VariationsHttpHeaderProvider::GetInstance()->ResetForTesting();
}

TEST_F(PaymentsClientTest, MigrateCardsVariationsTestExperimentFlagOff) {
  // Register a trial and variation id, so that there is data in variations
  // headers. Also, the variations header provider may have been registered to
  // observe some other field trial list, so reset it.
  DisableAutofillSendExperimentIdsInPaymentsRPCs();
  variations::VariationsHttpHeaderProvider::GetInstance()->ResetForTesting();
  base::FieldTrialList field_trial_list_(nullptr);
  CreateFieldTrialWithId("AutofillTest", "Group", 369);
  StartMigrating(/*has_cardholder_name=*/true);

  std::string value;
  EXPECT_FALSE(GetRequestHeaders()->GetHeader("X-Client-Data", &value));
  // Note that experiment information is stored in X-Client-Data.
  EXPECT_TRUE(value.empty());

  variations::VariationsHttpHeaderProvider::GetInstance()->ResetForTesting();
}

TEST_F(PaymentsClientTest, GetDetailsIncludeBillableServiceNumber) {
  StartGettingUploadDetails();

  // Verify that billable service number was included in the request.
  EXPECT_TRUE(GetUploadData().find("\"billable_service\":12345") !=
              std::string::npos);
}

TEST_F(PaymentsClientTest, UploadSuccessWithoutServerId) {
  StartUploading(/*include_cvc=*/true);
  IssueOAuthToken();
  ReturnResponse(net::HTTP_OK, "{}");
  EXPECT_EQ(AutofillClient::SUCCESS, result_);
  EXPECT_TRUE(server_id_.empty());
}

TEST_F(PaymentsClientTest, UploadSuccessWithServerId) {
  StartUploading(/*include_cvc=*/true);
  IssueOAuthToken();
  ReturnResponse(net::HTTP_OK, "{ \"credit_card_id\": \"InstrumentData:1\" }");
  EXPECT_EQ(AutofillClient::SUCCESS, result_);
  EXPECT_EQ("InstrumentData:1", server_id_);
}

TEST_F(PaymentsClientTest, UploadIncludesNonLocationData) {
  StartUploading(/*include_cvc=*/true);
  IssueOAuthToken();

  // Verify that the recipient name field and test names do appear in the upload
  // data.
  EXPECT_TRUE(GetUploadData().find(PaymentsClient::kRecipientName) !=
              std::string::npos);
  EXPECT_TRUE(GetUploadData().find("John") != std::string::npos);
  EXPECT_TRUE(GetUploadData().find("Smith") != std::string::npos);
  EXPECT_TRUE(GetUploadData().find("Pat") != std::string::npos);
  EXPECT_TRUE(GetUploadData().find("Jones") != std::string::npos);

  // Verify that the phone number field and test numbers do appear in the upload
  // data.
  EXPECT_TRUE(GetUploadData().find(PaymentsClient::kPhoneNumber) !=
              std::string::npos);
  EXPECT_TRUE(GetUploadData().find("212") != std::string::npos);
  EXPECT_TRUE(GetUploadData().find("555") != std::string::npos);
  EXPECT_TRUE(GetUploadData().find("0162") != std::string::npos);
  EXPECT_TRUE(GetUploadData().find("834") != std::string::npos);
  EXPECT_TRUE(GetUploadData().find("0090") != std::string::npos);
}

TEST_F(PaymentsClientTest,
       UploadRequestIncludesBillingCustomerNumberInRequest) {
  StartUploading(/*include_cvc=*/true);
  IssueOAuthToken();

  // Verify that the billing customer number is included in the request.
  EXPECT_TRUE(
      GetUploadData().find("%22external_customer_id%22:%22111222333444%22") !=
      std::string::npos);
}

TEST_F(PaymentsClientTest, UploadIncludesCvcInRequestIfProvided) {
  StartUploading(/*include_cvc=*/true);
  IssueOAuthToken();

  // Verify that the encrypted_cvc and s7e_13_cvc parameters were included in
  // the request.
  EXPECT_TRUE(GetUploadData().find("encrypted_cvc") != std::string::npos);
  EXPECT_TRUE(GetUploadData().find("__param:s7e_13_cvc") != std::string::npos);
  EXPECT_TRUE(GetUploadData().find("&s7e_13_cvc=") != std::string::npos);
}

TEST_F(PaymentsClientTest, UploadIncludesChromeUserContext) {
  scoped_feature_list_.InitWithFeatures(
      {features::kAutofillGetPaymentsIdentityFromSync},  // Enabled
      {features::kAutofillEnableAccountWalletStorage});  // Disabled

  StartUploading(/*include_cvc=*/true);
  IssueOAuthToken();

  // ChromeUserContext was set.
  EXPECT_TRUE(GetUploadData().find("chrome_user_context") != std::string::npos);
  EXPECT_TRUE(GetUploadData().find("full_sync_enabled") != std::string::npos);
}

TEST_F(PaymentsClientTest,
       UploadIncludesChromeUserContextIfWalletStorageFlagEnabled) {
  scoped_feature_list_.InitWithFeatures(
      {features::kAutofillEnableAccountWalletStorage},    // Enabled
      {features::kAutofillGetPaymentsIdentityFromSync});  // Disabled

  StartUploading(/*include_cvc=*/true);
  IssueOAuthToken();

  // ChromeUserContext was set.
  EXPECT_TRUE(GetUploadData().find("chrome_user_context") != std::string::npos);
  EXPECT_TRUE(GetUploadData().find("full_sync_enabled") != std::string::npos);
}

TEST_F(PaymentsClientTest, UploadExcludesChromeUserContextIfExperimentsOff) {
  scoped_feature_list_.InitWithFeatures(
      {},  // Enabled
      {features::kAutofillEnableAccountWalletStorage,
       features::kAutofillGetPaymentsIdentityFromSync});  // Disabled

  StartUploading(/*include_cvc=*/true);
  IssueOAuthToken();

  // ChromeUserContext was not set.
  EXPECT_TRUE(!GetUploadData().empty());
  EXPECT_TRUE(GetUploadData().find("chrome_user_context") == std::string::npos);
  EXPECT_TRUE(GetUploadData().find("full_sync_enabled") == std::string::npos);
}

TEST_F(PaymentsClientTest, UploadDoesNotIncludeCvcInRequestIfNotProvided) {
  StartUploading(/*include_cvc=*/false);
  IssueOAuthToken();

  EXPECT_TRUE(!GetUploadData().empty());
  // Verify that the encrypted_cvc and s7e_13_cvc parameters were not included
  // in the request.
  EXPECT_TRUE(GetUploadData().find("encrypted_cvc") == std::string::npos);
  EXPECT_TRUE(GetUploadData().find("__param:s7e_13_cvc") == std::string::npos);
  EXPECT_TRUE(GetUploadData().find("&s7e_13_cvc=") == std::string::npos);
}

TEST_F(PaymentsClientTest, MigrationRequestIncludesUniqueId) {
  StartMigrating(/*has_cardholder_name=*/true);
  IssueOAuthToken();

  // Verify that the unique id was included in the request.
  EXPECT_TRUE(GetUploadData().find("unique_id") != std::string::npos);
  EXPECT_TRUE(
      GetUploadData().find(migratable_credit_cards_[0].credit_card().guid()) !=
      std::string::npos);
  EXPECT_TRUE(
      GetUploadData().find(migratable_credit_cards_[1].credit_card().guid()) !=
      std::string::npos);
}

TEST_F(PaymentsClientTest, MigrationRequestIncludesEncryptedPan) {
  StartMigrating(/*has_cardholder_name=*/true);
  IssueOAuthToken();

  // Verify that the encrypted_pan and s7e_1_pan parameters were included
  // in the request.
  EXPECT_TRUE(GetUploadData().find("encrypted_pan") != std::string::npos);
  EXPECT_TRUE(GetUploadData().find("__param:s7e_1_pan0") != std::string::npos);
  EXPECT_TRUE(GetUploadData().find("&s7e_1_pan0=4111111111111111") !=
              std::string::npos);
  EXPECT_TRUE(GetUploadData().find("__param:s7e_1_pan1") != std::string::npos);
  EXPECT_TRUE(GetUploadData().find("&s7e_1_pan1=378282246310005") !=
              std::string::npos);
}

TEST_F(PaymentsClientTest, MigrationRequestIncludesCardholderNameWhenItExists) {
  StartMigrating(/*has_cardholder_name=*/true);
  IssueOAuthToken();

  EXPECT_TRUE(!GetUploadData().empty());
  // Verify that the cardholder name is sent if it exists.
  EXPECT_TRUE(GetUploadData().find("cardholder_name") != std::string::npos);
}

TEST_F(PaymentsClientTest,
       MigrationRequestExcludesCardholderNameWhenItDoesNotExist) {
  StartMigrating(/*has_cardholder_name=*/false);
  IssueOAuthToken();

  EXPECT_TRUE(!GetUploadData().empty());
  // Verify that the cardholder name is not sent if it doesn't exist.
  EXPECT_TRUE(GetUploadData().find("cardholder_name") == std::string::npos);
}

TEST_F(PaymentsClientTest, MigrationRequestIncludesChromeUserContext) {
  scoped_feature_list_.InitWithFeatures(
      {features::kAutofillGetPaymentsIdentityFromSync},  // Enabled
      {features::kAutofillEnableAccountWalletStorage});  // Disabled

  StartMigrating(/*has_cardholder_name=*/true);
  IssueOAuthToken();

  // ChromeUserContext was set.
  EXPECT_TRUE(GetUploadData().find("chrome_user_context") != std::string::npos);
  EXPECT_TRUE(GetUploadData().find("full_sync_enabled") != std::string::npos);
}

TEST_F(PaymentsClientTest,
       MigrationRequestIncludesChromeUserContextIfWalletStorageFlagEnabled) {
  scoped_feature_list_.InitWithFeatures(
      {features::kAutofillEnableAccountWalletStorage},    // Enabled
      {features::kAutofillGetPaymentsIdentityFromSync});  // Disabled

  StartMigrating(/*has_cardholder_name=*/true);
  IssueOAuthToken();

  // ChromeUserContext was set.
  EXPECT_TRUE(GetUploadData().find("chrome_user_context") != std::string::npos);
  EXPECT_TRUE(GetUploadData().find("full_sync_enabled") != std::string::npos);
}

TEST_F(PaymentsClientTest,
       MigrationRequestExcludesChromeUserContextIfExperimentsOff) {
  scoped_feature_list_.InitWithFeatures(
      {},  // Enabled
      {features::kAutofillEnableAccountWalletStorage,
       features::kAutofillGetPaymentsIdentityFromSync});  // Disabled

  StartMigrating(/*has_cardholder_name=*/true);
  IssueOAuthToken();

  // ChromeUserContext was not set.
  EXPECT_TRUE(!GetUploadData().empty());
  EXPECT_TRUE(GetUploadData().find("chrome_user_context") == std::string::npos);
  EXPECT_TRUE(GetUploadData().find("full_sync_enabled") == std::string::npos);
}

TEST_F(PaymentsClientTest, MigrationSuccessWithSaveResult) {
  StartMigrating(/*has_cardholder_name=*/true);
  IssueOAuthToken();
  ReturnResponse(net::HTTP_OK,
                 "{\"save_result\":[{\"unique_id\":\"0\",\"status\":"
                 "\"SUCCESS\"},{\"unique_id\":\"1\",\"status\":\"TEMPORARY_"
                 "FAILURE\"}],\"value_prop_display_text\":\"display text\"}");

  EXPECT_EQ(AutofillClient::SUCCESS, result_);
  EXPECT_TRUE(save_result_.get());
  EXPECT_TRUE(save_result_->find("0") != save_result_->end());
  EXPECT_TRUE(save_result_->at("0") == "SUCCESS");
  EXPECT_TRUE(save_result_->find("1") != save_result_->end());
  EXPECT_TRUE(save_result_->at("1") == "TEMPORARY_FAILURE");
}

TEST_F(PaymentsClientTest, MigrationMissingSaveResult) {
  StartMigrating(/*has_cardholder_name=*/true);
  IssueOAuthToken();
  ReturnResponse(net::HTTP_OK,
                 "{\"value_prop_display_text\":\"display text\"}");
  EXPECT_EQ(AutofillClient::PERMANENT_FAILURE, result_);
  EXPECT_EQ(nullptr, save_result_.get());
}

TEST_F(PaymentsClientTest, MigrationSuccessWithDisplayText) {
  StartMigrating(/*has_cardholder_name=*/true);
  IssueOAuthToken();
  ReturnResponse(net::HTTP_OK,
                 "{\"save_result\":[{\"unique_id\":\"0\",\"status\":"
                 "\"SUCCESS\"}],\"value_prop_display_text\":\"display text\"}");
  EXPECT_EQ(AutofillClient::SUCCESS, result_);
  EXPECT_EQ("display text", display_text_);
}

TEST_F(PaymentsClientTest, GetDetailsFollowedByUploadSuccess) {
  StartGettingUploadDetails();
  ReturnResponse(
      net::HTTP_OK,
      "{ \"context_token\": \"some_token\", \"legal_message\": {} }");
  EXPECT_EQ(AutofillClient::SUCCESS, result_);

  result_ = AutofillClient::NONE;

  StartUploading(/*include_cvc=*/true);
  IssueOAuthToken();
  ReturnResponse(net::HTTP_OK, "{}");
  EXPECT_EQ(AutofillClient::SUCCESS, result_);
}

TEST_F(PaymentsClientTest, GetDetailsFollowedByMigrationSuccess) {
  StartGettingUploadDetails();
  ReturnResponse(
      net::HTTP_OK,
      "{ \"context_token\": \"some_token\", \"legal_message\": {} }");
  EXPECT_EQ(AutofillClient::SUCCESS, result_);

  result_ = AutofillClient::NONE;

  StartMigrating(/*has_cardholder_name=*/true);
  IssueOAuthToken();
  ReturnResponse(
      net::HTTP_OK,
      "{\"save_result\":[],\"value_prop_display_text\":\"display text\"}");
  EXPECT_EQ(AutofillClient::SUCCESS, result_);
}

TEST_F(PaymentsClientTest, UnmaskMissingPan) {
  StartUnmasking();
  ReturnResponse(net::HTTP_OK, "{}");
  EXPECT_EQ(AutofillClient::PERMANENT_FAILURE, result_);
}

TEST_F(PaymentsClientTest, GetDetailsMissingContextToken) {
  StartGettingUploadDetails();
  ReturnResponse(net::HTTP_OK, "{ \"legal_message\": {} }");
  EXPECT_EQ(AutofillClient::PERMANENT_FAILURE, result_);
}

TEST_F(PaymentsClientTest, GetDetailsMissingLegalMessage) {
  StartGettingUploadDetails();
  ReturnResponse(net::HTTP_OK, "{ \"context_token\": \"some_token\" }");
  EXPECT_EQ(AutofillClient::PERMANENT_FAILURE, result_);
  EXPECT_EQ(nullptr, legal_message_.get());
}

TEST_F(PaymentsClientTest, RetryFailure) {
  StartUnmasking();
  IssueOAuthToken();
  ReturnResponse(net::HTTP_OK, "{ \"error\": { \"code\": \"INTERNAL\" } }");
  EXPECT_EQ(AutofillClient::TRY_AGAIN_FAILURE, result_);
  EXPECT_EQ("", real_pan_);
}

TEST_F(PaymentsClientTest, PermanentFailure) {
  StartUnmasking();
  IssueOAuthToken();
  ReturnResponse(net::HTTP_OK,
                 "{ \"error\": { \"code\": \"ANYTHING_ELSE\" } }");
  EXPECT_EQ(AutofillClient::PERMANENT_FAILURE, result_);
  EXPECT_EQ("", real_pan_);
}

TEST_F(PaymentsClientTest, MalformedResponse) {
  StartUnmasking();
  IssueOAuthToken();
  ReturnResponse(net::HTTP_OK, "{ \"error_code\": \"WRONG_JSON_FORMAT\" }");
  EXPECT_EQ(AutofillClient::PERMANENT_FAILURE, result_);
  EXPECT_EQ("", real_pan_);
}

TEST_F(PaymentsClientTest, ReauthNeeded) {
  {
    StartUnmasking();
    IssueOAuthToken();
    ReturnResponse(net::HTTP_UNAUTHORIZED, "");
    // No response yet.
    EXPECT_EQ(AutofillClient::NONE, result_);
    EXPECT_EQ("", real_pan_);

    // Second HTTP_UNAUTHORIZED causes permanent failure.
    IssueOAuthToken();
    ReturnResponse(net::HTTP_UNAUTHORIZED, "");
    EXPECT_EQ(AutofillClient::PERMANENT_FAILURE, result_);
    EXPECT_EQ("", real_pan_);
  }

  result_ = AutofillClient::NONE;
  real_pan_.clear();

  {
    StartUnmasking();
    // NOTE: Don't issue an access token here: the issuing of an access token
    // first waits for the access token request to be received, but here there
    // should be no access token request because PaymentsClient should reuse the
    // access token from the previous request.
    ReturnResponse(net::HTTP_UNAUTHORIZED, "");
    // No response yet.
    EXPECT_EQ(AutofillClient::NONE, result_);
    EXPECT_EQ("", real_pan_);

    // HTTP_OK after first HTTP_UNAUTHORIZED results in success.
    IssueOAuthToken();
    ReturnResponse(net::HTTP_OK, "{ \"pan\": \"1234\" }");
    EXPECT_EQ(AutofillClient::SUCCESS, result_);
    EXPECT_EQ("1234", real_pan_);
  }
}

TEST_F(PaymentsClientTest, NetworkError) {
  StartUnmasking();
  IssueOAuthToken();
  ReturnResponse(net::HTTP_REQUEST_TIMEOUT, std::string());
  EXPECT_EQ(AutofillClient::NETWORK_ERROR, result_);
  EXPECT_EQ("", real_pan_);
}

TEST_F(PaymentsClientTest, OtherError) {
  StartUnmasking();
  IssueOAuthToken();
  ReturnResponse(net::HTTP_FORBIDDEN, std::string());
  EXPECT_EQ(AutofillClient::PERMANENT_FAILURE, result_);
  EXPECT_EQ("", real_pan_);
}

}  // namespace payments
}  // namespace autofill
