Completed
Push — add/e2e-connection-purchase-fl... ( d02cce...68beb5 )
by Yaroslav
14:07 queued 04:35
created

Test_REST_Endpoints::test_remote_authorize()   B

Complexity

Conditions 3
Paths 1

Size

Total Lines 58

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 1
nop 0
dl 0
loc 58
rs 8.9163
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2
3
namespace Automattic\Jetpack\Connection;
4
5
use Automattic\Jetpack\Connection\Plugin as Connection_Plugin;
6
use Automattic\Jetpack\Connection\Plugin_Storage as Connection_Plugin_Storage;
7
use Automattic\Jetpack\Constants;
8
use phpmock\MockBuilder;
9
use PHPUnit\Framework\TestCase;
10
use WorDBless\Options as WorDBless_Options;
11
use WP_REST_Request;
12
use WP_REST_Server;
13
use Requests_Utility_CaseInsensitiveDictionary;
14
use WP_User;
15
16
/**
17
 * Unit tests for the REST API endpoints.
18
 *
19
 * @package automattic/jetpack-connection
20
 * @see \Automattic\Jetpack\Connection\REST_Connector
21
 */
22
class Test_REST_Endpoints extends TestCase {
23
24
	const BLOG_TOKEN = 'new.blogtoken';
25
	const BLOG_ID    = 42;
26
	const USER_ID    = 111;
27
28
	/**
29
	 * REST Server object.
30
	 *
31
	 * @var WP_REST_Server
32
	 */
33
	private $server;
34
35
	/**
36
	 * The original hostname to restore after tests are finished.
37
	 *
38
	 * @var string
39
	 */
40
	private $api_host_original;
41
42
	/**
43
	 * Setting up the test.
44
	 */
45
	public function setUp() {
46
		parent::setUp();
47
48
		global $wp_rest_server;
49
50
		$wp_rest_server = new WP_REST_Server();
51
		$this->server   = $wp_rest_server;
52
53
		do_action( 'rest_api_init' );
54
		new REST_Connector( new Manager() );
55
56
		add_action( 'jetpack_disabled_raw_options', array( $this, 'bypass_raw_options' ) );
57
58
		$user = wp_get_current_user();
59
		$user->add_cap( 'jetpack_reconnect' );
60
61
		$this->api_host_original                                  = Constants::get_constant( 'JETPACK__WPCOM_JSON_API_HOST' );
62
		Constants::$set_constants['JETPACK__WPCOM_JSON_API_HOST'] = 'public-api.wordpress.com';
63
64
		Constants::$set_constants['JETPACK__API_BASE'] = 'https://jetpack.wordpress.com/jetpack.';
65
66
		set_transient( 'jetpack_assumed_site_creation_date', '2020-02-28 01:13:27' );
67
	}
68
69
	/**
70
	 * Returning the environment into its initial state.
71
	 */
72
	public function tearDown() {
73
		parent::tearDown();
74
75
		remove_action( 'jetpack_disabled_raw_options', array( $this, 'bypass_raw_options' ) );
76
77
		$user = wp_get_current_user();
78
		$user->remove_cap( 'jetpack_reconnect' );
79
80
		Constants::$set_constants['JETPACK__WPCOM_JSON_API_HOST'] = $this->api_host_original;
81
82
		delete_transient( 'jetpack_assumed_site_creation_date' );
83
84
		WorDBless_Options::init()->options = array();
85
	}
86
87
	/**
88
	 * Testing the `/jetpack/v4/remote_authorize` endpoint.
89
	 */
90
	public function test_remote_authorize() {
91
		add_filter( 'jetpack_options', array( $this, 'mock_jetpack_options' ), 10, 2 );
92
		add_filter( 'pre_http_request', array( $this, 'intercept_auth_token_request' ), 10, 3 );
93
94
		wp_cache_set(
95
			self::USER_ID,
96
			(object) array(
97
				'ID'         => self::USER_ID,
98
				'user_email' => '[email protected]',
99
			),
100
			'users'
101
		);
102
103
		$secret_1 = 'Az0g39toGWlYiTJ4NnDuAz0g39toGWlY';
104
105
		$secrets = array(
106
			'jetpack_authorize_' . self::USER_ID => array(
107
				'secret_1' => $secret_1,
108
				'secret_2' => 'zfIFcym2Jlzd8AVgzfIFcym2Jlzd8AVg',
109
				'exp'      => time() + 60,
110
			),
111
		);
112
113
		// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
114
		$options_filter = function( $value ) use ( $secrets ) {
115
			return $secrets;
116
		};
117
		add_filter( 'pre_option_' . Manager::SECRETS_OPTION_NAME, $options_filter );
118
119
		$user_caps_filter = function( $allcaps, $caps, $args, $user ) {
120
			if ( $user instanceof WP_User && self::USER_ID === $user->ID ) {
0 ignored issues
show
Bug introduced by
The class WP_User does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
121
				$allcaps['manage_options'] = true;
122
				$allcaps['administrator']  = true;
123
			}
124
125
			return $allcaps;
126
		};
127
		add_filter( 'user_has_cap', $user_caps_filter, 10, 4 );
128
129
		$this->request = new WP_REST_Request( 'POST', '/jetpack/v4/remote_authorize' );
130
		$this->request->set_header( 'Content-Type', 'application/json' );
131
		$this->request->set_body( '{ "state": "' . self::USER_ID . '", "secret": "' . $secret_1 . '", "redirect_uri": "https://example.org", "code": "54321" }' );
132
133
		$response = $this->server->dispatch( $this->request );
134
		$data     = $response->get_data();
135
136
		remove_filter( 'user_has_cap', $user_caps_filter );
137
		remove_filter( 'pre_option_' . Manager::SECRETS_OPTION_NAME, $options_filter );
138
		remove_filter( 'pre_http_request', array( $this, 'intercept_auth_token_request' ) );
139
		remove_filter( 'jetpack_options', array( $this, 'mock_jetpack_options' ) );
140
141
		wp_cache_delete( self::USER_ID, 'users' );
142
143
		wp_set_current_user( 0 );
144
145
		$this->assertEquals( 200, $response->get_status() );
146
		$this->assertEquals( 'authorized', $data['result'] );
147
	}
148
149
	/**
150
	 * Testing the `/jetpack/v4/connection` endpoint.
151
	 */
152
	public function test_connection() {
153
		$builder = new MockBuilder();
154
		$builder->setNamespace( 'Automattic\Jetpack' )
155
				->setName( 'apply_filters' )
156
				->setFunction(
157
					function( $hook, $value ) {
158
						return 'jetpack_offline_mode' === $hook ? true : $value;
159
					}
160
				);
161
162
		$mock = $builder->build();
163
		$mock->enable();
164
165
		$this->request = new WP_REST_Request( 'GET', '/jetpack/v4/connection' );
166
167
		$response = $this->server->dispatch( $this->request );
168
		$data     = $response->get_data();
169
170
		$this->assertFalse( $data['isActive'] );
171
		$this->assertFalse( $data['isRegistered'] );
172
		$this->assertTrue( $data['offlineMode']['isActive'] );
173
	}
174
175
	/**
176
	 * Testing the `/jetpack/v4/connection/plugins` endpoint.
177
	 */
178
	public function test_connection_plugins() {
179
		$user = wp_get_current_user();
180
		$user->add_cap( 'activate_plugins' );
181
182
		$plugins = array(
183
			array(
184
				'name' => 'Plugin Name 1',
185
				'slug' => 'plugin-slug-1',
186
			),
187
			array(
188
				'name' => 'Plugin Name 2',
189
				'slug' => 'plugin-slug-2',
190
			),
191
		);
192
193
		array_walk(
194
			$plugins,
195
			function( $plugin ) {
196
				( new Connection_Plugin( $plugin['slug'] ) )->add( $plugin['name'] );
197
			}
198
		);
199
200
		Connection_Plugin_Storage::configure();
201
202
		$this->request = new WP_REST_Request( 'GET', '/jetpack/v4/connection/plugins' );
203
204
		$response = $this->server->dispatch( $this->request );
205
206
		$user->remove_cap( 'activate_plugins' );
207
208
		$this->assertEquals( $plugins, $response->get_data() );
209
	}
210
211
	/**
212
	 * Testing the `connection/reconnect` endpoint, full reconnect.
213
	 */
214
	public function test_connection_reconnect_full() {
215
		$this->setup_reconnect_test( null );
216
		add_filter( 'jetpack_connection_disconnect_site_wpcom', '__return_false' );
217
		add_filter( 'pre_http_request', array( $this, 'intercept_register_request' ), 10, 3 );
218
219
		$response = $this->server->dispatch( $this->build_reconnect_request() );
220
		$data     = $response->get_data();
221
222
		remove_filter( 'pre_http_request', array( $this, 'intercept_register_request' ), 10 );
223
		remove_filter( 'jetpack_connection_disconnect_site_wpcom', '__return_false' );
224
		$this->shutdown_reconnect_test( null );
225
226
		$this->assertEquals( 200, $response->get_status() );
227
		$this->assertEquals( 'in_progress', $data['status'] );
228
		$this->assertSame( 0, strpos( $data['authorizeUrl'], 'https://jetpack.wordpress.com/jetpack.authorize/' ) );
229
	}
230
231
	/**
232
	 * Testing the `connection/reconnect` endpoint, successful partial reconnect (blog token).
233
	 */
234 View Code Duplication
	public function test_connection_reconnect_partial_blog_token_success() {
235
		$this->setup_reconnect_test( 'blog_token' );
236
		add_filter( 'pre_http_request', array( $this, 'intercept_refresh_blog_token_request' ), 10, 3 );
237
238
		$response = $this->server->dispatch( $this->build_reconnect_request() );
239
		$data     = $response->get_data();
240
241
		remove_filter( 'pre_http_request', array( $this, 'intercept_refresh_blog_token_request' ), 10 );
242
		$this->shutdown_reconnect_test( 'blog_token' );
243
244
		$this->assertEquals( 200, $response->get_status() );
245
		$this->assertEquals( 'completed', $data['status'] );
246
	}
247
248
	/**
249
	 * Testing the `connection/reconnect` endpoint, failed partial reconnect (blog token).
250
	 */
251 View Code Duplication
	public function test_connection_reconnect_partial_blog_token_fail() {
252
		$this->setup_reconnect_test( 'blog_token' );
253
		add_filter( 'pre_http_request', array( $this, 'intercept_refresh_blog_token_request_fail' ), 10, 3 );
254
255
		$response = $this->server->dispatch( $this->build_reconnect_request() );
256
		$data     = $response->get_data();
257
258
		remove_filter( 'pre_http_request', array( $this, 'intercept_refresh_blog_token_request_fail' ), 10 );
259
		$this->shutdown_reconnect_test( 'blog_token' );
260
261
		$this->assertEquals( 500, $response->get_status() );
262
		$this->assertEquals( 'jetpack_secret', $data['code'] );
263
	}
264
265
	/**
266
	 * Testing the `connection/reconnect` endpoint, successful partial reconnect (user token).
267
	 */
268
	public function test_connection_reconnect_partial_user_token_success() {
269
		$this->setup_reconnect_test( 'user_token' );
270
271
		$response = $this->server->dispatch( $this->build_reconnect_request() );
272
		$data     = $response->get_data();
273
274
		$this->shutdown_reconnect_test( 'user_token' );
275
276
		$this->assertEquals( 200, $response->get_status() );
277
		$this->assertEquals( 'in_progress', $data['status'] );
278
		$this->assertSame( 0, strpos( $data['authorizeUrl'], 'https://jetpack.wordpress.com/jetpack.authorize/' ) );
279
	}
280
281
	/**
282
	 * This filter callback allow us to skip the database query by `Jetpack_Options` to retrieve the option.
283
	 *
284
	 * @param array $options List of options already skipping the database request.
285
	 *
286
	 * @return array
287
	 */
288
	public function bypass_raw_options( array $options ) {
289
		$options[ Manager::SECRETS_OPTION_NAME ] = true;
290
291
		return $options;
292
	}
293
294
	/**
295
	 * Intercept the `jetpack.register` API request sent to WP.com, and mock the response.
296
	 *
297
	 * @param bool|array $response The existing response.
298
	 * @param array      $args The request arguments.
299
	 * @param string     $url The request URL.
300
	 *
301
	 * @return array
302
	 */
303
	public function intercept_register_request( $response, $args, $url ) {
304
		if ( false === strpos( $url, 'jetpack.register' ) ) {
305
			return $response;
306
		}
307
308
		return array(
309
			'headers'  => new Requests_Utility_CaseInsensitiveDictionary( array( 'content-type' => 'application/json' ) ),
310
			'body'     => wp_json_encode(
311
				array(
312
					'jetpack_id'     => '12345',
313
					'jetpack_secret' => 'sample_secret',
314
				)
315
			),
316
			'response' => array(
317
				'code'    => 200,
318
				'message' => 'OK',
319
			),
320
		);
321
	}
322
323
	/**
324
	 * Intercept the `jetpack-token-health` API request sent to WP.com, and mock the "invalid blog token" response.
325
	 *
326
	 * @param bool|array $response The existing response.
327
	 * @param array      $args The request arguments.
328
	 * @param string     $url The request URL.
329
	 *
330
	 * @return array
331
	 */
332
	public function intercept_validate_tokens_request_invalid_blog_token( $response, $args, $url ) {
333
		if ( false === strpos( $url, 'jetpack-token-health' ) ) {
334
			return $response;
335
		}
336
337
		return $this->build_validate_tokens_response( 'blog_token' );
338
	}
339
340
	/**
341
	 * Intercept the `jetpack-token-health` API request sent to WP.com, and mock the "invalid user token" response.
342
	 *
343
	 * @param bool|array $response The existing response.
344
	 * @param array      $args The request arguments.
345
	 * @param string     $url The request URL.
346
	 *
347
	 * @return array
348
	 */
349
	public function intercept_validate_tokens_request_invalid_user_token( $response, $args, $url ) {
350
		if ( false === strpos( $url, 'jetpack-token-health' ) ) {
351
			return $response;
352
		}
353
354
		return $this->build_validate_tokens_response( 'user_token' );
355
	}
356
357
	/**
358
	 * Intercept the `jetpack-token-health` API request sent to WP.com, and mock the "valid tokens" response.
359
	 *
360
	 * @param bool|array $response The existing response.
361
	 * @param array      $args The request arguments.
362
	 * @param string     $url The request URL.
363
	 *
364
	 * @return array
365
	 */
366
	public function intercept_validate_tokens_request_valid_tokens( $response, $args, $url ) {
367
		if ( false === strpos( $url, 'jetpack-token-health' ) ) {
368
			return $response;
369
		}
370
371
		return $this->build_validate_tokens_response( null );
372
	}
373
374
	/**
375
	 * Build the response for a tokens validation request
376
	 *
377
	 * @param string $invalid_token Accepted values: 'blog_token', 'user_token'.
378
	 *
379
	 * @return array
380
	 */
381
	private function build_validate_tokens_response( $invalid_token ) {
382
		$body = array(
383
			'blog_token' => array(
384
				'is_healthy' => true,
385
			),
386
			'user_token' => array(
387
				'is_healthy'     => true,
388
				'is_master_user' => true,
389
			),
390
		);
391
392
		switch ( $invalid_token ) {
393
			case 'blog_token':
394
				$body['blog_token'] = array(
395
					'is_healthy' => false,
396
					'code'       => 'unknown_token',
397
				);
398
				break;
399
			case 'user_token':
400
				$body['user_token'] = array(
401
					'is_healthy' => false,
402
					'code'       => 'unknown_token',
403
				);
404
				break;
405
		}
406
407
		return array(
408
			'headers'  => new Requests_Utility_CaseInsensitiveDictionary( array( 'content-type' => 'application/json' ) ),
409
			'body'     => wp_json_encode( $body ),
410
			'response' => array(
411
				'code'    => 200,
412
				'message' => 'OK',
413
			),
414
		);
415
	}
416
417
	/**
418
	 * Intercept the `jetpack-refresh-blog-token` API request sent to WP.com, and mock the success response.
419
	 *
420
	 * @param bool|array $response The existing response.
421
	 * @param array      $args The request arguments.
422
	 * @param string     $url The request URL.
423
	 *
424
	 * @return array
425
	 */
426 View Code Duplication
	public function intercept_refresh_blog_token_request( $response, $args, $url ) {
427
		if ( false === strpos( $url, 'jetpack-refresh-blog-token' ) ) {
428
			return $response;
429
		}
430
431
		return array(
432
			'headers'  => new Requests_Utility_CaseInsensitiveDictionary( array( 'content-type' => 'application/json' ) ),
433
			'body'     => wp_json_encode( array( 'jetpack_secret' => self::BLOG_TOKEN ) ),
434
			'response' => array(
435
				'code'    => 200,
436
				'message' => 'OK',
437
			),
438
		);
439
	}
440
441
	/**
442
	 * Intercept the `jetpack-refresh-blog-token` API request sent to WP.com, and mock the failure response.
443
	 *
444
	 * @param bool|array $response The existing response.
445
	 * @param array      $args The request arguments.
446
	 * @param string     $url The request URL.
447
	 *
448
	 * @return array
449
	 */
450 View Code Duplication
	public function intercept_refresh_blog_token_request_fail( $response, $args, $url ) {
451
		if ( false === strpos( $url, 'jetpack-refresh-blog-token' ) ) {
452
			return $response;
453
		}
454
455
		return array(
456
			'headers'  => new Requests_Utility_CaseInsensitiveDictionary( array( 'content-type' => 'application/json' ) ),
457
			'body'     => wp_json_encode( array( 'jetpack_secret_missing' => true ) ), // Meaningless body.
458
			'response' => array(
459
				'code'    => 200,
460
				'message' => 'OK',
461
			),
462
		);
463
	}
464
465
	/**
466
	 * Intercept the `jetpack-token-health` API request sent to WP.com, and mock the "invalid blog token" response.
467
	 *
468
	 * @param bool|array $response The existing response.
469
	 * @param array      $args The request arguments.
470
	 * @param string     $url The request URL.
471
	 *
472
	 * @return array
473
	 */
474
	public function intercept_auth_token_request( $response, $args, $url ) {
475
		if ( false === strpos( $url, '/jetpack.token/' ) ) {
476
			return $response;
477
		}
478
479
		return array(
480
			'headers'  => new Requests_Utility_CaseInsensitiveDictionary( array( 'content-type' => 'application/json' ) ),
481
			'body'     => wp_json_encode(
482
				array(
483
					'access_token' => 'mock.token',
484
					'token_type'   => 'X_JETPACK',
485
					'scope'        => ( new Manager() )->sign_role( 'administrator' ),
486
				)
487
			),
488
			'response' => array(
489
				'code'    => 200,
490
				'message' => 'OK',
491
			),
492
		);
493
	}
494
495
	/**
496
	 * Intercept the `Jetpack_Options` call and mock the values.
497
	 *
498
	 * @param mixed  $value The current option value.
499
	 * @param string $name Option name.
500
	 *
501
	 * @return mixed
502
	 */
503
	public function mock_jetpack_options( $value, $name ) {
504
		switch ( $name ) {
505
			case 'blog_token':
506
				return self::BLOG_TOKEN;
507
			case 'id':
508
				return self::BLOG_ID;
509
		}
510
511
		return $value;
512
	}
513
514
	/**
515
	 * Build the `connection/reconnect` request object.
516
	 *
517
	 * @return WP_REST_Request
518
	 */
519
	private function build_reconnect_request() {
520
		$this->request = new WP_REST_Request( 'POST', '/jetpack/v4/connection/reconnect' );
521
		$this->request->set_header( 'Content-Type', 'application/json' );
522
523
		return $this->request;
524
	}
525
526
	/**
527
	 * Setup the environment to test the reconnection process.
528
	 *
529
	 * @param string|null $invalid_token The invalid token to be returned in the response. Null if the tokens should be valid.
530
	 */
531 View Code Duplication
	private function setup_reconnect_test( $invalid_token ) {
532
		switch ( $invalid_token ) {
533
			case 'blog_token':
534
				add_filter(
535
					'pre_http_request',
536
					array(
537
						$this,
538
						'intercept_validate_tokens_request_invalid_blog_token',
539
					),
540
					10,
541
					3
542
				);
543
				break;
544
			case 'user_token':
545
				add_filter(
546
					'pre_http_request',
547
					array(
548
						$this,
549
						'intercept_validate_tokens_request_invalid_user_token',
550
					),
551
					10,
552
					3
553
				);
554
				break;
555
			case null:
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $invalid_token of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
556
				add_filter(
557
					'pre_http_request',
558
					array(
559
						$this,
560
						'intercept_validate_tokens_request_valid_tokens',
561
					),
562
					10,
563
					3
564
				);
565
				break;
566
		}
567
568
		add_filter( 'jetpack_options', array( $this, 'mock_jetpack_options' ), 10, 2 );
569
	}
570
571
	/**
572
	 * Restore the environment after the `reconnect` test has been run.
573
	 *
574
	 * @param string|null $invalid_token The invalid token to be returned in the response. Null if the tokens should be valid.
575
	 */
576 View Code Duplication
	private function shutdown_reconnect_test( $invalid_token ) {
577
		switch ( $invalid_token ) {
578
			case 'blog_token':
579
				remove_filter(
580
					'pre_http_request',
581
					array(
582
						$this,
583
						'intercept_validate_tokens_request_invalid_blog_token',
584
					),
585
					10
586
				);
587
				break;
588
			case 'user_token':
589
				remove_filter(
590
					'pre_http_request',
591
					array(
592
						$this,
593
						'intercept_validate_tokens_request_invalid_user_token',
594
					),
595
					10
596
				);
597
				break;
598
			case null:
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $invalid_token of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
599
				remove_filter(
600
					'pre_http_request',
601
					array(
602
						$this,
603
						'intercept_validate_tokens_request_valid_tokens',
604
					),
605
					10
606
				);
607
				break;
608
		}
609
610
		remove_filter( 'jetpack_options', array( $this, 'mock_jetpack_options' ), 10 );
611
		remove_filter( 'pre_http_request', array( $this, 'intercept_validate_tokens_request' ), 10 );
612
	}
613
614
}
615