Skip to content

Commit

Permalink
Add subscriptions unit tests (#875)
Browse files Browse the repository at this point in the history
* Add Payment_Information tests

* Add token request key variable to get_token_from_request

* Add subscriptions sign up tests

To avoid coupling the tests with WC Subscriptions code, this commit introduces
a WCS_Mock helper that can be used to mock WCS global functions. It also uses
regular orders, which should behave the same as a subscription one once WCS
methods are mocked.

* Add add_token_to_order tests

* Add update_failing_payment_method tests

* Use gateway add_token_to_order when updating failed payment methods

* Add scheduled_subscription_payment tests

* Use current year + 1 as the test tokens expiry date

The test tokens are never really used, so we shoudln't have any issues if the
token expiry date has passed. However, if WC starts validating the expiry date
before creating an already expired token, they could start failing. This commit
uses the next year for the token expiration, to avoid this if it ever happens.
  • Loading branch information
luizreis authored Sep 4, 2020
1 parent 68f0496 commit 3f1158e
Show file tree
Hide file tree
Showing 12 changed files with 865 additions and 22 deletions.
2 changes: 1 addition & 1 deletion includes/class-wc-payment-gateway-wcpay.php
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,7 @@ public function process_payment_for_order( $order, $cart, $payment_information,
* @param WC_Order $order The order.
* @param WC_Payment_Token $token The token to save.
*/
protected function add_token_to_order( $order, $token ) {
public function add_token_to_order( $order, $token ) {
$order_tokens = $order->get_payment_tokens();

// This could lead to tokens being saved twice in an order's payment tokens, but it is needed so that shoppers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public function update_failing_payment_method( $subscription, $renewal_order ) {
Logger::error( 'Failing subscription could not be updated: there is no saved payment token for order #' . $renewal_order->get_id() );
return;
}
$subscription->add_payment_token( $renewal_token );
$this->add_token_to_order( $subscription, $renewal_token );
}

/**
Expand All @@ -127,7 +127,7 @@ public function update_failing_payment_method( $subscription, $renewal_order ) {
* @param WC_Order $order The order.
* @param WC_Payment_Token $token The token to save.
*/
protected function add_token_to_order( $order, $token ) {
public function add_token_to_order( $order, $token ) {
parent::add_token_to_order( $order, $token );

// Set payment token for subscriptions, so it can be used for renewals.
Expand Down
7 changes: 4 additions & 3 deletions includes/data-types/class-payment-information.php
Original file line number Diff line number Diff line change
Expand Up @@ -156,15 +156,16 @@ public static function get_payment_method_from_request( array $request ): string
* @return \WC_Payment_Token|NULL
*/
public static function get_token_from_request( array $request ) {
$token_request_key = 'wc-' . \WC_Payment_Gateway_WCPay::GATEWAY_ID . '-payment-token';
if (
! isset( $request[ 'wc-' . \WC_Payment_Gateway_WCPay::GATEWAY_ID . '-payment-token' ] ) ||
'new' === $request[ 'wc-' . \WC_Payment_Gateway_WCPay::GATEWAY_ID . '-payment-token' ]
! isset( $request[ $token_request_key ] ) ||
'new' === $request[ $token_request_key ]
) {
return null;
}

//phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
$token = \WC_Payment_Tokens::get( wc_clean( $request[ 'wc-' . \WC_Payment_Gateway_WCPay::GATEWAY_ID . '-payment-token' ] ) );
$token = \WC_Payment_Tokens::get( wc_clean( $request[ $token_request_key ] ) );

// If the token doesn't belong to this gateway or the current user it's invalid.
if ( ! $token || \WC_Payment_Gateway_WCPay::GATEWAY_ID !== $token->get_gateway_id() || $token->get_user_id() !== get_current_user_id() ) {
Expand Down
4 changes: 4 additions & 0 deletions tests/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ function _manually_load_plugin() {
require_once dirname( __FILE__ ) . '/../includes/wc-payment-api/class-wc-payments-api-client.php';
require_once dirname( __FILE__ ) . '/../includes/wc-payment-api/class-wc-payments-http.php';

// Load the gateway files, so subscriptions can be tested.
require_once dirname( __FILE__ ) . '/../includes/class-wc-payment-gateway-wcpay.php';
require_once dirname( __FILE__ ) . '/../includes/compat/subscriptions/class-wc-payment-gateway-wcpay-subscriptions-compat.php';

require_once dirname( __FILE__ ) . '/../includes/exceptions/class-wc-payments-rest-request-exception.php';
require_once dirname( __FILE__ ) . '/../includes/admin/class-wc-payments-rest-controller.php';
require_once dirname( __FILE__ ) . '/../includes/admin/class-wc-rest-payments-webhook-controller.php';
Expand Down
145 changes: 145 additions & 0 deletions tests/data-types/test-class-payment-information.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<?php
/**
* Class Payment_Information_Test
*
* @package WooCommerce\Payments\Tests
*/

use WCPay\DataTypes\Payment_Information;

/**
* Payment_Information unit tests.
*/
class Payment_Information_Test extends WP_UnitTestCase {
const PAYMENT_METHOD_REQUEST_KEY = 'wcpay-payment-method';
const PAYMENT_METHOD = 'pm_mock';
const TOKEN_REQUEST_KEY = 'wc-' . \WC_Payment_Gateway_WCPay::GATEWAY_ID . '-payment-token';
const TOKEN = 'pm_mock_token';

/**
* WC token to be used in tests.
* @var WC_Payment_Token_CC
*/
private $token;

public function setUp() {
parent::setUp();

$this->token = WC_Helper_Token::create_token( self::TOKEN );
}

public function test_requires_payment_method_or_token() {
$this->expectException( Exception::class );
$this->expectExceptionMessage( 'Invalid payment method. Please input a new card number.' );

$payment_information = new Payment_Information( '' );
}

public function test_is_merchant_initiated_returns_off_session() {
$payment_information = new Payment_Information( self::PAYMENT_METHOD, null, true );
$this->assertTrue( $payment_information->is_merchant_initiated() );
}

public function test_get_payment_method_returns_payment_method() {
$payment_information = new Payment_Information( self::PAYMENT_METHOD, null );
$this->assertEquals( self::PAYMENT_METHOD, $payment_information->get_payment_method() );
}

public function test_get_payment_method_returns_token_if_present() {
$payment_information = new Payment_Information( self::PAYMENT_METHOD, $this->token );
$this->assertEquals( self::TOKEN, $payment_information->get_payment_method() );
}

public function test_get_payment_token_returns_token() {
$payment_information = new Payment_Information( self::PAYMENT_METHOD, $this->token );
$this->assertEquals( $this->token, $payment_information->get_payment_token() );
}

public function is_using_saved_payment_method_returns_true_if_token() {
$payment_information = new Payment_Information( self::PAYMENT_METHOD, $this->token );
$this->assertTrue( $payment_information->is_using_saved_payment_method() );
}

public function test_set_token_updates_token() {
$payment_information = new Payment_Information( self::PAYMENT_METHOD );
$this->assertFalse( $payment_information->is_using_saved_payment_method() );

$payment_information->set_token( $this->token );
$this->assertEquals( $this->token, $payment_information->get_payment_token() );
$this->assertTrue( $payment_information->is_using_saved_payment_method() );
}

public function test_get_payment_method_from_request() {
$payment_method = Payment_Information::get_payment_method_from_request(
[ self::PAYMENT_METHOD_REQUEST_KEY => self::PAYMENT_METHOD ]
);
$this->assertEquals( self::PAYMENT_METHOD, $payment_method );
}

public function test_get_token_from_request_returns_null_when_not_set() {
$token = Payment_Information::get_token_from_request( [] );
$this->assertNull( $token );
}

public function test_get_token_from_request_returns_null_when_new() {
$token = Payment_Information::get_token_from_request(
[ self::TOKEN_REQUEST_KEY => 'new' ]
);
$this->assertNull( $token );
}

public function test_get_token_from_request_returns_null_when_invalid() {
$token = Payment_Information::get_token_from_request(
[ self::TOKEN_REQUEST_KEY => $this->token->get_id() + 1 ]
);
$this->assertNull( $token );
}

public function test_get_token_from_request_returns_null_when_wrong_gateway() {
$this->token->set_gateway_id( 'wrong_gateway' );
$this->token->save();
$token = Payment_Information::get_token_from_request(
[ self::TOKEN_REQUEST_KEY => $this->token->get_id() ]
);
$this->assertNull( $token );
}

public function test_get_token_from_request_returns_null_when_wrong_customer() {
$this->token->set_user_id( get_current_user_id() + 1 );
$this->token->save();
$token = Payment_Information::get_token_from_request(
[ self::TOKEN_REQUEST_KEY => $this->token->get_id() ]
);
$this->assertNull( $token );
}

public function test_get_token_from_request_returns_token() {
$token = Payment_Information::get_token_from_request(
[ self::TOKEN_REQUEST_KEY => $this->token->get_id() ]
);
$this->assertEquals( $this->token, $token );
}

public function test_from_payment_request_with_token() {
$payment_information = Payment_Information::from_payment_request(
[
self::PAYMENT_METHOD_REQUEST_KEY => self::PAYMENT_METHOD,
self::TOKEN_REQUEST_KEY => $this->token->get_id(),
],
true
);
$this->assertEquals( self::TOKEN, $payment_information->get_payment_method() );
$this->assertTrue( $payment_information->is_using_saved_payment_method() );
$this->assertEquals( $this->token, $payment_information->get_payment_token() );
$this->assertTrue( $payment_information->is_merchant_initiated() );
}

public function test_from_payment_request_without_token() {
$payment_information = Payment_Information::from_payment_request(
[ self::PAYMENT_METHOD_REQUEST_KEY => self::PAYMENT_METHOD ]
);
$this->assertEquals( self::PAYMENT_METHOD, $payment_information->get_payment_method() );
$this->assertFalse( $payment_information->is_using_saved_payment_method() );
$this->assertFalse( $payment_information->is_merchant_initiated() );
}
}
8 changes: 5 additions & 3 deletions tests/helpers/class-wc-helper-order.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,13 @@ public static function delete_order( $order_id ) {
* @version 3.0 New parameter $product.
*
* @param int $customer_id The ID of the customer the order is for.
* @param WC_Product $product The product to add to the order.
* @param int $total Total cost of the order. Defaults to 50 (4 x $10 simple helper product + $10 shipping)
* and can be modified to test $0 orders.
* @param WC_Product $product The product to add to the order.
*
* @return WC_Order
*/
public static function create_order( $customer_id = 1, $product = null ) {
public static function create_order( $customer_id = 1, $total = 50, $product = null ) {

if ( ! is_a( $product, 'WC_Product' ) ) {
$product = WC_Helper_Product::create_simple_product();
Expand Down Expand Up @@ -114,7 +116,7 @@ public static function create_order( $customer_id = 1, $product = null ) {
$order->set_discount_tax( 0 );
$order->set_cart_tax( 0 );
$order->set_shipping_tax( 0 );
$order->set_total( 50 ); // 4 x $10 simple helper product
$order->set_total( $total );
$order->save();

return $order;
Expand Down
44 changes: 44 additions & 0 deletions tests/helpers/class-wc-helper-subscriptions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php
/**
* Subscription helpers.
*
* @package WooCommerce\Payments\Tests
*/

// Set up subscriptions mocks.
function wcs_order_contains_subscription( $order ) {
return call_user_func( WCS_Mock::$wcs_order_contains_subscription, $order );
}

function wcs_get_subscriptions_for_order( $order ) {
return call_user_func( WCS_Mock::$wcs_get_subscriptions_for_order, $order );
}

/**
* Class WCS_Mock.
*
* This helper class should ONLY be used for unit tests!.
*/
class WCS_Mock {
/**
* wcs_order_contains_subscription mock.
*
* @var function
*/
public static $wcs_order_contains_subscription = null;

/**
* wcs_get_subscriptions_for_order mock.
*
* @var function
*/
public static $wcs_get_subscriptions_for_order = null;

public static function set_wcs_order_contains_subscription( $function ) {
self::$wcs_order_contains_subscription = $function;
}

public static function set_wcs_get_subscriptions_for_order( $function ) {
self::$wcs_get_subscriptions_for_order = $function;
}
}
35 changes: 35 additions & 0 deletions tests/helpers/class-wc-helper-token.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php
/**
* Token helpers.
*
* @package WooCommerce/Tests
*/

/**
* Class WC_Helper_Token.
*
* This helper class should ONLY be used for unit tests!.
*/
class WC_Helper_Token {

/**
* Create a token.
*
* @param string $payment_method Token payment method.
* @param int $user_id ID of the token's user, defaults to get_current_user_id().
* @param string $gateway Token's Gateway ID, default to WC_Payment_Gateway_WCPay::GATEWAY_ID
*/
public static function create_token( $payment_method, $user_id = null, $gateway = WC_Payment_Gateway_WCPay::GATEWAY_ID ) {
$token = new WC_Payment_Token_CC();
$token->set_token( $payment_method );
$token->set_gateway_id( $gateway );
$token->set_user_id( $user_id ?? get_current_user_id() );
$token->set_card_type( 'visa' );
$token->set_last4( '4242' );
$token->set_expiry_month( 6 );
$token->set_expiry_year( intval( gmdate( 'Y' ) ) + 1 );
$token->save();

return WC_Payment_Tokens::get( $token->get_id() );
}
}
10 changes: 1 addition & 9 deletions tests/test-class-wc-payment-gateway-wcpay-process-payment.php
Original file line number Diff line number Diff line change
Expand Up @@ -560,15 +560,7 @@ function( $source_order ) use ( $order ) {
}

private function setup_saved_payment_method() {
$token = new WC_Payment_Token_CC();
$token->set_token( 'pm_mock' );
$token->set_gateway_id( WC_Payment_Gateway_WCPay::GATEWAY_ID );
$token->set_card_type( 'visa' );
$token->set_last4( '4242' );
$token->set_expiry_month( 6 );
$token->set_expiry_year( 2026 );
$token->set_user_id( get_current_user_id() );
$token->save();
$token = WC_Helper_Token::create_token( 'pm_mock' );

return [
'wc-' . WC_Payment_Gateway_WCPay::GATEWAY_ID . '-payment-token' => (string) $token->get_id(),
Expand Down
Loading

0 comments on commit 3f1158e

Please sign in to comment.