Completed
Push — master ( 216d34...8ed8a3 )
by Radoslav
01:58 queued 22s
created

WC_Stripe_Subscription_Renewal_Test   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 372
Duplicated Lines 1.08 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
dl 4
loc 372
rs 10
c 0
b 0
f 0
wmc 21
lcom 1
cbo 3

4 Methods

Rating   Name   Duplication   Size   Complexity  
A setUp() 0 24 1
A tearDown() 0 5 1
C test_renewal_successful() 4 187 13
B test_renewal_authorization_required() 0 111 6

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
/**
3
 * These tests assert various things about processing a renewal payment for a WooCommerce Subscription.
4
 *
5
 * The responses from HTTP requests are mocked using the WP filter `pre_http_request`.
6
 *
7
 * There are a few methods that need to be mocked in the class WC_Stripe_Subs_Compat, which is
8
 * why that class is mocked even though the method under test is part of that class.
9
 *
10
 * @package     WooCommerce_Stripe/Classes/WC_Stripe_Subscription_Renewal_Test
11
 */
12
13
/**
14
 * WC_Stripe_Subscription_Renewal_Test
15
 */
16
class WC_Stripe_Subscription_Renewal_Test extends WP_UnitTestCase {
17
	/**
18
	 * System under test, and a mock object with some methods mocked for testing
19
	 *
20
	 * @var PHPUnit_Framework_MockObject_MockObject
21
	 */
22
	private $wc_stripe_subs_compat;
23
24
	/**
25
	 * The statement descriptor we'll use in a test.
26
	 *
27
	 * @var string
28
	 */
29
	private $statement_descriptor;
30
31
	/**
32
	 * Sets up things all tests need.
33
	 */
34
	public function setUp() {
35
		parent::setUp();
36
37
		$this->wc_stripe_subs_compat = $this->getMockBuilder( 'WC_Stripe_Subs_Compat' )
38
			->disableOriginalConstructor()
39
			->setMethods( array( 'prepare_order_source', 'has_subscription' ) )
40
			->getMock();
41
42
		// Mocked in order to get metadata[payment_type] = recurring in the HTTP request.
43
		$this->wc_stripe_subs_compat
44
			->expects( $this->any() )
45
			->method( 'has_subscription' )
46
			->will(
47
				$this->returnValue( true )
48
			);
49
50
		$this->statement_descriptor = 'This is a statement descriptor.';
51
		add_option(
52
			'woocommerce_stripe_settings',
53
			array(
54
				'statement_descriptor' => $this->statement_descriptor,
55
			)
56
		);
57
	}
58
59
	/**
60
	 * Tears down the stuff we set up.
61
	 */
62
	public function tearDown() {
63
		parent::tearDown();
64
65
		delete_option( 'woocommerce_stripe_settings' );
66
	}
67
68
	/**
69
	 * Overall this test works like this:
70
	 *
71
	 * 1. Several things are set up or mocked.
72
	 * 2. A function that will mock an HTTP response for the payment_intents is created.
73
	 * 3. That same function has some assertions about the things we send to the
74
	 * payments_intents endpoint.
75
	 * 4. The function under test - `process_subscription_payment` - is called.
76
	 * 5. More assertions are made.
77
	 */
78
	public function test_renewal_successful() {
79
		// Arrange: Some variables we'll use later.
80
		$renewal_order                 = WC_Helper_Order::create_order();
81
		$amount                        = 20; // WC Subs sends an amount to be used, instead of using the order amount.
82
		$stripe_amount                 = WC_Stripe_Helper::get_stripe_amount( $amount );
83
		$currency                      = strtolower( WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $renewal_order->get_order_currency() : $renewal_order->get_currency() );
84
		$customer                      = 'cus_123abc';
85
		$source                        = 'src_123abc';
86
		$statement_descriptor          = WC_Stripe_Helper::clean_statement_descriptor( $this->statement_descriptor );
87
		$should_retry                  = false;
88
		$previous_error                = false;
89
		$payments_intents_api_endpoint = 'https://api.stripe.com/v1/payment_intents';
90
		$urls_used                     = array();
91
92
		// Arrange: Set payment method to stripe, and not stripe_sepa, for example.
93
		// This needed for testing the statement_descriptor.
94
		if ( WC_Stripe_Helper::is_wc_lt( '3.0' ) ) {
95
			$renewal_order->payment_method = 'stripe';
96
		} else {
97
			$renewal_order->set_payment_method( 'stripe' );
98
		}
99
100
101
		// Arrange: Mock prepare_order_source() so that we have a customer and source.
102
		$this->wc_stripe_subs_compat
103
			->expects( $this->any() )
104
			->method( 'prepare_order_source' )
105
			->will(
106
				$this->returnValue(
107
					(object) array(
108
						'token_id'      => false,
109
						'customer'      => $customer,
110
						'source'        => $source,
111
						'source_object' => (object) array(),
112
					)
113
				)
114
			);
115
116
		// Arrange: Add filter that will return a mocked HTTP response for the payment_intent call.
117
		// Note: There are assertions in the callback function.
118
		$pre_http_request_response_callback = function( $preempt, $request_args, $url ) use (
119
			$renewal_order,
120
			$stripe_amount,
121
			$currency,
122
			$customer,
123
			$source,
124
			$statement_descriptor,
125
			$payments_intents_api_endpoint,
126
			&$urls_used
127
		) {
128
			// Add all urls to array so we can later make assertions about which endpoints were used.
129
			array_push( $urls_used, $url );
130
131
			// Continue without mocking the request if it's not the endpoint we care about.
132
			if ( $payments_intents_api_endpoint !== $url ) {
133
				return false;
134
			}
135
136
			// Assert: the request method is POST.
137
			$this->assertArrayHasKey( 'method', $request_args );
138
			$this->assertSame( 'POST', $request_args['method'] );
139
140
			// Assert: the request has a body.
141
			$this->assertArrayHasKey( 'body', $request_args );
142
143
			// Assert: the request body contains these values.
144
			$expected_request_body_values = array(
145
				'source'               => $source,
146
				'amount'               => $stripe_amount,
147
				'currency'             => $currency,
148
				'payment_method_types' => array( 'card' ),
149
				'customer'             => $customer,
150
				'off_session'          => 'true',
151
				'confirm'              => 'true',
152
				'confirmation_method'  => 'automatic',
153
				'statement_descriptor' => $statement_descriptor,
154
			);
155 View Code Duplication
			foreach ( $expected_request_body_values as $key => $value ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
156
				$this->assertArrayHasKey( $key, $request_args['body'] );
157
				$this->assertSame( $value, $request_args['body'][ $key ] );
158
			}
159
160
			// Assert: the request body contains these keys, without checking for their value.
161
			$expected_request_body_keys = array(
162
				'description',
163
				'metadata',
164
			);
165
			foreach ( $expected_request_body_keys as $key ) {
166
				$this->assertArrayHasKey( $key, $request_args['body'] );
167
			}
168
169
			// Assert: the body metadata has these values.
170
			$order_id                 = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $renewal_order->id : (string) $renewal_order->get_id();
171
			$expected_metadata_values = array(
172
				'order_id'     => $order_id,
173
				'payment_type' => 'recurring',
174
			);
175
			foreach ( $expected_metadata_values as $key => $value ) {
176
				$this->assertArrayHasKey( $key, $request_args['body']['metadata'] );
177
				$this->assertSame( $value, $request_args['body']['metadata'][ $key ] );
178
			}
179
180
			// Assert: the body metadata has these keys, without checking for their value.
181
			$expected_metadata_keys = array(
182
				'customer_name',
183
				'customer_email',
184
				'site_url',
185
			);
186
			foreach ( $expected_metadata_keys as $key ) {
187
				$this->assertArrayHasKey( $key, $request_args['body']['metadata'] );
188
			}
189
190
			// Assert: the request body does not contains these keys.
191
			$expected_missing_request_body_keys = array(
192
				'capture', // No need to capture with a payment intent.
193
				'capture_method', // The default ('automatic') is what we want in this case, so we leave it off.
194
				'expand[]',
195
			);
196
			foreach ( $expected_missing_request_body_keys as $key ) {
197
				$this->assertArrayNotHasKey( $key, $request_args['body'] );
198
			}
199
200
			// Arrange: return dummy content as the response.
201
			return array(
202
				'headers'  => array(),
203
				// Too bad we aren't dynamically setting things 'cus_123abc' when using this file.
204
				'body'     => file_get_contents( 'tests/phpunit/dummy-data/subscription_renewal_response_success.json' ),
205
				'response' => array(
206
					'code'    => 200,
207
					'message' => 'OK',
208
				),
209
				'cookies'  => array(),
210
				'filename' => null,
211
			);
212
		};
213
214
		add_filter( 'pre_http_request', $pre_http_request_response_callback, 10, 3 );
215
216
		// Arrange: Make sure to check that an action we care about was called
217
		// by hooking into it.
218
		$mock_action_process_payment = new MockAction();
219
		add_action(
220
			'wc_gateway_stripe_process_payment',
221
			[ &$mock_action_process_payment, 'action' ]
222
		);
223
224
		// Act: call process_subscription_payment().
225
		// We need to use `wc_stripe_subs_compat` here because we mocked this class earlier.
226
		$result = $this->wc_stripe_subs_compat->process_subscription_payment( 20, $renewal_order, $should_retry, $previous_error );
227
228
		// Assert: nothing was returned.
229
		$this->assertEquals( $result, null );
230
231
		// Assert that we saved the payment intent to the order.
232
		$order_id   = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $renewal_order->id : $renewal_order->get_id();
233
		$order      = wc_get_order( $order_id );
234
		$order_data = (
235
			WC_Stripe_Helper::is_wc_lt( '3.0' )
236
				? get_post_meta( $order_id, '_stripe_intent_id', true )
237
				: $order->get_meta( '_stripe_intent_id' )
238
		);
239
240
		$this->assertEquals( $order_data, 'pi_123abc' );
241
242
		// Transaction ID was saved to order.
243
		$order_transaction_id = (
244
			WC_Stripe_Helper::is_wc_lt( '3.0' )
245
				? get_post_meta( $order_id, '_transaction_id', true )
246
				: $order->get_transaction_id()
247
		);
248
		$this->assertEquals( $order_transaction_id, 'ch_123abc' );
249
250
		// Assert: the order was marked as processing (this is done in process_response()).
251
		$this->assertEquals( $order->get_status(), 'processing' );
252
253
		// Assert: called payment intents.
254
		$this->assertTrue( in_array( $payments_intents_api_endpoint, $urls_used ) );
255
256
		// Assert: Our hook was called once.
257
		$this->assertEquals( 1, $mock_action_process_payment->get_call_count() );
258
259
		// Assert: Only our hook was called.
260
		$this->assertEquals( array( 'wc_gateway_stripe_process_payment' ), $mock_action_process_payment->get_tags() );
261
262
		// Clean up.
263
		remove_filter( 'pre_http_request', array( $this, 'pre_http_request_response_success' ) );
264
	}
265
266
	/**
267
	 * Overall this test works like this:
268
	 *
269
	 * 1. Several things are set up or mocked.
270
	 * 2. A function that will mock an HTTP response for the payment_intents is created.
271
	 * 3. That same function has some assertions about the things we send to the
272
	 * payments_intents endpoint.
273
	 * 4. The function under test - `process_subscription_payment` - is called.
274
	 * 5. More assertions are made.
275
	 */
276
	public function test_renewal_authorization_required() {
277
		// Arrange: Some variables we'll use later.
278
		$renewal_order                 = WC_Helper_Order::create_order();
279
		$amount                        = 20;
280
		$stripe_amount                 = WC_Stripe_Helper::get_stripe_amount( $amount );
281
		$currency                      = strtolower( WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $renewal_order->get_order_currency() : $renewal_order->get_currency() );
282
		$customer                      = 'cus_123abc';
283
		$source                        = 'src_123abc';
284
		$should_retry                  = false;
285
		$previous_error                = false;
286
		$payments_intents_api_endpoint = 'https://api.stripe.com/v1/payment_intents';
287
		$urls_used                     = array();
288
289
		// Arrange: Mock prepare_order_source() so that we have a customer and source.
290
		$this->wc_stripe_subs_compat
291
			->expects( $this->any() )
292
			->method( 'prepare_order_source' )
293
			->will(
294
				$this->returnValue(
295
					(object) array(
296
						'token_id'      => false,
297
						'customer'      => $customer,
298
						'source'        => $source,
299
						'source_object' => (object) array(),
300
					)
301
				)
302
			);
303
304
		// Arrange: Add filter that will return a mocked HTTP response for the payment_intent call.
305
		$pre_http_request_response_callback = function( $preempt, $request_args, $url ) use (
306
			$renewal_order,
307
			$stripe_amount,
308
			$currency,
309
			$customer,
310
			$source,
311
			$payments_intents_api_endpoint,
312
			&$urls_used
313
		) {
314
			// Add all urls to array so we can later make assertions about which endpoints were used.
315
			array_push( $urls_used, $url );
316
317
			// Continue without mocking the request if it's not the endpoint we care about.
318
			if ( $payments_intents_api_endpoint !== $url ) {
319
				return false;
320
			}
321
322
			// Arrange: return dummy content as the response.
323
			return array(
324
				'headers'  => array(),
325
				// Too bad we aren't dynamically setting things 'cus_123abc' when using this file.
326
				'body'     => file_get_contents( 'tests/phpunit/dummy-data/subscription_renewal_response_authentication_required.json' ),
327
				'response' => array(
328
					'code'    => 402,
329
					'message' => 'Payment Required',
330
				),
331
				'cookies'  => array(),
332
				'filename' => null,
333
			);
334
		};
335
		add_filter( 'pre_http_request', $pre_http_request_response_callback, 10, 3 );
336
337
		// Arrange: Make sure to check that an action we care about was called
338
		// by hooking into it.
339
		$mock_action_process_payment = new MockAction();
340
		add_action(
341
			'wc_gateway_stripe_process_payment_authentication_required',
342
			[ &$mock_action_process_payment, 'action' ]
343
		);
344
345
		// Act: call process_subscription_payment().
346
		// We need to use `wc_stripe_subs_compat` here because we mocked this class earlier.
347
		$result = $this->wc_stripe_subs_compat->process_subscription_payment( 20, $renewal_order, $should_retry, $previous_error );
348
349
		// Assert: nothing was returned.
350
		$this->assertEquals( $result, null );
351
352
		// Assert that we saved the payment intent to the order.
353
		$order_id             = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $renewal_order->id : $renewal_order->get_id();
354
		$order                = wc_get_order( $order_id );
355
		$order_data           = (
356
			WC_Stripe_Helper::is_wc_lt( '3.0' )
357
				? get_post_meta( $order_id, '_stripe_intent_id', true )
358
				: $order->get_meta( '_stripe_intent_id' )
359
		);
360
		$order_transaction_id = (
361
			WC_Stripe_Helper::is_wc_lt( '3.0' )
362
				? get_post_meta( $order_id, '_transaction_id', true )
363
				: $order->get_transaction_id()
364
		);
365
366
		// Intent was saved to order even though there was an error in the response body.
367
		$this->assertEquals( $order_data, 'pi_123abc' );
368
369
		// Transaction ID was saved to order.
370
		$this->assertEquals( $order_transaction_id, 'ch_123abc' );
371
372
		// Assert: the order was marked as failed.
373
		$this->assertEquals( $order->get_status(), 'failed' );
374
375
		// Assert: called payment intents.
376
		$this->assertTrue( in_array( $payments_intents_api_endpoint, $urls_used ) );
377
378
		// Assert: Our hook was called once.
379
		$this->assertEquals( 1, $mock_action_process_payment->get_call_count() );
380
381
		// Assert: Only our hook was called.
382
		$this->assertEquals( array( 'wc_gateway_stripe_process_payment_authentication_required' ), $mock_action_process_payment->get_tags() );
383
384
		// Clean up.
385
		remove_filter( 'pre_http_request', array( $this, 'pre_http_request_response_success' ) );
386
	}
387
}
388