chromium/chrome/browser/payments/android/payment_app_service_bridge_unittest.cc

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

#include <memory>
#include <vector>

#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "chrome/test/base/testing_profile.h"
#include "components/payments/content/android/payment_app_service_bridge.h"
#include "components/payments/content/payment_app_service.h"
#include "components/payments/content/payment_manifest_web_data_service.h"
#include "components/payments/content/payment_request_spec.h"
#include "components/payments/core/const_csp_checker.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_web_contents_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/payments/payment_app.mojom.h"

namespace payments {

class MockCallback {
 public:
  MockCallback() = default;
  MOCK_METHOD1(NotifyPaymentAppCreated, void(std::unique_ptr<PaymentApp> app));
  MOCK_METHOD1(NotifyCanMakePaymentCalculated, void(bool can_make_payment));
  MOCK_METHOD2(NotifyPaymentAppCreationError,
               void(const std::string& error, AppCreationFailureReason reason));
  MOCK_METHOD0(NotifyDoneCreatingPaymentApps, void(void));
  MOCK_METHOD0(SetCanMakePaymentEvenWithoutApps, void(void));
  MOCK_METHOD0(SetOptOutOffered, void(void));
};

class MockApp : public PaymentApp {
 public:
  MockApp()
      : PaymentApp(/*icon_resource_id=*/0,
                   PaymentApp::Type::SERVICE_WORKER_APP) {}

  ~MockApp() override = default;

  // PaymentApp implementation:
  MOCK_METHOD1(InvokePaymentApp, void(base::WeakPtr<Delegate> delegate));
  MOCK_CONST_METHOD0(IsCompleteForPayment, bool());
  MOCK_CONST_METHOD0(CanPreselect, bool());
  MOCK_CONST_METHOD0(GetMissingInfoLabel, std::u16string());
  MOCK_CONST_METHOD0(HasEnrolledInstrument, bool());
  MOCK_METHOD0(RecordUse, void());
  MOCK_CONST_METHOD0(NeedsInstallation, bool());
  MOCK_CONST_METHOD0(GetId, std::string());
  MOCK_CONST_METHOD0(GetLabel, std::u16string());
  MOCK_CONST_METHOD0(GetSublabel, std::u16string());
  MOCK_CONST_METHOD1(IsValidForModifier, bool(const std::string& method));
  MOCK_METHOD0(AsWeakPtr, base::WeakPtr<PaymentApp>());
  MOCK_CONST_METHOD0(HandlesShippingAddress, bool());
  MOCK_CONST_METHOD0(HandlesPayerName, bool());
  MOCK_CONST_METHOD0(HandlesPayerEmail, bool());
  MOCK_CONST_METHOD0(HandlesPayerPhone, bool());
};

class PaymentAppServiceBridgeUnitTest
    : public ::testing::TestWithParam<std::string> {
 public:
  PaymentAppServiceBridgeUnitTest()
      : top_origin_("https://shop.example"),
        frame_origin_("https://merchant.example") {
    web_contents_ =
        test_web_contents_factory_.CreateWebContents(&browser_context_);
    content::NavigationSimulator::NavigateAndCommitFromBrowser(web_contents_,
                                                               frame_origin_);
  }

  mojom::PaymentMethodDataPtr MakePaymentMethodData(
      const std::string& supported_method) {
    mojom::PaymentMethodDataPtr out = mojom::PaymentMethodData::New();
    out->supported_method = supported_method;
    return out;
  }

 protected:
  content::BrowserTaskEnvironment task_environment_;
  TestingProfile browser_context_;
  content::TestWebContentsFactory test_web_contents_factory_;
  raw_ptr<content::WebContents> web_contents_;
  GURL top_origin_;
  GURL frame_origin_;
  scoped_refptr<PaymentManifestWebDataService> web_data_service_;
};

TEST_P(PaymentAppServiceBridgeUnitTest, Smoke) {
  std::vector<mojom::PaymentMethodDataPtr> method_data;
  method_data.push_back(MakePaymentMethodData("basic-card"));
  method_data.push_back(MakePaymentMethodData("https://ph.example"));
  PaymentRequestSpec spec(mojom::PaymentOptions::New(),
                          mojom::PaymentDetails::New(), std::move(method_data),
                          /*observer=*/nullptr, /*app_locale=*/"en-US");

  ConstCSPChecker const_csp_checker(/*allow=*/true);
  MockCallback mock_callback;
  base::WeakPtr<PaymentAppServiceBridge> bridge =
      PaymentAppServiceBridge::Create(
          std::make_unique<PaymentAppService>(
              web_contents_->GetBrowserContext()),
          web_contents_->GetPrimaryMainFrame(), top_origin_, spec.AsWeakPtr(),
          /*twa_package_name=*/GetParam(), web_data_service_,
          /*is_off_the_record=*/false, const_csp_checker.GetWeakPtr(),
          base::BindRepeating(&MockCallback::NotifyCanMakePaymentCalculated,
                              base::Unretained(&mock_callback)),
          base::BindRepeating(&MockCallback::NotifyPaymentAppCreated,
                              base::Unretained(&mock_callback)),
          base::BindRepeating(&MockCallback::NotifyPaymentAppCreationError,
                              base::Unretained(&mock_callback)),
          base::BindOnce(&MockCallback::NotifyDoneCreatingPaymentApps,
                         base::Unretained(&mock_callback)),
          base::BindRepeating(&MockCallback::SetCanMakePaymentEvenWithoutApps,
                              base::Unretained(&mock_callback)),
          base::BindRepeating(&MockCallback::SetOptOutOffered,
                              base::Unretained(&mock_callback)))
          ->GetWeakPtrForTest();

  EXPECT_EQ(web_contents_, bridge->GetWebContents());
  EXPECT_EQ(top_origin_, bridge->GetTopOrigin());
  EXPECT_EQ(frame_origin_, bridge->GetFrameOrigin());
  EXPECT_EQ("https://merchant.example",
            bridge->GetFrameSecurityOrigin().Serialize());
  EXPECT_EQ(web_contents_->GetPrimaryMainFrame(),
            bridge->GetInitiatorRenderFrameHost());
  EXPECT_EQ(2U, bridge->GetMethodData().size());
  EXPECT_EQ("basic-card", bridge->GetMethodData()[0]->supported_method);
  EXPECT_EQ("https://ph.example", bridge->GetMethodData()[1]->supported_method);

  auto app = std::make_unique<MockApp>();
  EXPECT_CALL(mock_callback, NotifyPaymentAppCreated(::testing::_));
  bridge->OnPaymentAppCreated(std::move(app));

  EXPECT_CALL(mock_callback, SetCanMakePaymentEvenWithoutApps());
  bridge->SetCanMakePaymentEvenWithoutApps();

  EXPECT_CALL(mock_callback, SetOptOutOffered());
  bridge->SetOptOutOffered();

  EXPECT_CALL(mock_callback,
              NotifyPaymentAppCreationError("some error",
                                            AppCreationFailureReason::UNKNOWN));
  bridge->OnPaymentAppCreationError("some error",
                                    AppCreationFailureReason::UNKNOWN);

  // NotifyDoneCreatingPaymentApps() is only called after
  // OnDoneCreatingPaymentApps() is called for each payment factories in
  // |bridge|.
  bridge->OnDoneCreatingPaymentApps();
  bridge->OnDoneCreatingPaymentApps();

  EXPECT_CALL(mock_callback, NotifyDoneCreatingPaymentApps());
  bridge->OnDoneCreatingPaymentApps();

  // |bridge| cleans itself up after NotifyDoneCreatingPaymentApps().
  CHECK_EQ(nullptr, bridge.get());
}

// An empty string indicates running outside of a TWA. A non-empty string is the
// package name of the TWA when running in a TWA mode.
INSTANTIATE_TEST_SUITE_P(WithAndWithoutTwaPackageName,
                         PaymentAppServiceBridgeUnitTest,
                         ::testing::Values("", "com.example.twa.app"));

}  // namespace payments