Completed
Push — update/add-csstidy-config-sett... ( 30eb4b...0a5859 )
by
unknown
19:15 queued 09:40
created

Test_REST_Endpoints::test_set_user_token_success()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 78

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 78
rs 8.48
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\Connection\Rest_Authentication as Connection_Rest_Authentication;
8
use Automattic\Jetpack\Constants;
9
use Automattic\Jetpack\Redirect;
10
use Jetpack_Options;
11
use PHPUnit\Framework\TestCase;
12
use Requests_Utility_CaseInsensitiveDictionary;
13
use WorDBless\Options as WorDBless_Options;
14
use WP_REST_Request;
15
use WP_REST_Server;
16
use WP_User;
17
18
/**
19
 * Unit tests for the REST API endpoints.
20
 *
21
 * @package automattic/jetpack-connection
22
 * @see \Automattic\Jetpack\Connection\REST_Connector
23
 */
24
class Test_REST_Endpoints extends TestCase {
25
26
	const BLOG_TOKEN = 'new.blogtoken';
27
	const BLOG_ID    = 42;
28
	const USER_ID    = 111;
29
30
	/**
31
	 * REST Server object.
32
	 *
33
	 * @var WP_REST_Server
34
	 */
35
	private $server;
36
37
	/**
38
	 * The original hostname to restore after tests are finished.
39
	 *
40
	 * @var string
41
	 */
42
	private $api_host_original;
43
44
	/**
45
	 * Setting up the test.
46
	 *
47
	 * @before
48
	 */
49
	public function set_up() {
50
		global $wp_rest_server;
51
52
		$wp_rest_server = new WP_REST_Server();
53
		$this->server   = $wp_rest_server;
54
55
		do_action( 'rest_api_init' );
56
		new REST_Connector( new Manager() );
57
58
		add_action( 'jetpack_disabled_raw_options', array( $this, 'bypass_raw_options' ) );
59
60
		$user = wp_get_current_user();
61
		$user->add_cap( 'jetpack_reconnect' );
62
		$user->add_cap( 'jetpack_connect' );
63
64
		$this->api_host_original                                  = Constants::get_constant( 'JETPACK__WPCOM_JSON_API_BASE' );
65
		Constants::$set_constants['JETPACK__WPCOM_JSON_API_BASE'] = 'https://public-api.wordpress.com';
66
67
		Constants::$set_constants['JETPACK__API_BASE'] = 'https://jetpack.wordpress.com/jetpack.';
68
69
		set_transient( 'jetpack_assumed_site_creation_date', '2020-02-28 01:13:27' );
70
	}
71
72
	/**
73
	 * Returning the environment into its initial state.
74
	 *
75
	 * @after
76
	 */
77
	public function tear_down() {
78
		remove_action( 'jetpack_disabled_raw_options', array( $this, 'bypass_raw_options' ) );
79
80
		$user = wp_get_current_user();
81
		$user->remove_cap( 'jetpack_reconnect' );
82
83
		Constants::$set_constants['JETPACK__WPCOM_JSON_API_BASE'] = $this->api_host_original;
84
85
		delete_transient( 'jetpack_assumed_site_creation_date' );
86
87
		WorDBless_Options::init()->clear_options();
88
89
		unset( $_SERVER['REQUEST_METHOD'] );
90
		$_GET = array();
91
	}
92
93
	/**
94
	 * Testing the `/jetpack/v4/remote_authorize` endpoint.
95
	 */
96
	public function test_remote_authorize() {
97
		add_filter( 'jetpack_options', array( $this, 'mock_jetpack_site_connection_options' ), 10, 2 );
98
		add_filter( 'pre_http_request', array( $this, 'intercept_auth_token_request' ), 10, 3 );
99
100
		wp_cache_set(
101
			self::USER_ID,
102
			(object) array(
103
				'ID'         => self::USER_ID,
104
				'user_email' => '[email protected]',
105
			),
106
			'users'
107
		);
108
109
		$secret_1 = 'Az0g39toGWlYiTJ4NnDuAz0g39toGWlY';
110
111
		$secrets = array(
112
			'jetpack_authorize_' . self::USER_ID => array(
113
				'secret_1' => $secret_1,
114
				'secret_2' => 'zfIFcym2Jlzd8AVgzfIFcym2Jlzd8AVg',
115
				'exp'      => time() + 60,
116
			),
117
		);
118
119
		// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
120
		$options_filter = function ( $value ) use ( $secrets ) {
121
			return $secrets;
122
		};
123
		add_filter( 'pre_option_' . Secrets::LEGACY_SECRETS_OPTION_NAME, $options_filter );
124
125
		$user_caps_filter = function ( $allcaps, $caps, $args, $user ) {
126
			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...
127
				$allcaps['manage_options'] = true;
128
				$allcaps['administrator']  = true;
129
			}
130
131
			return $allcaps;
132
		};
133
		add_filter( 'user_has_cap', $user_caps_filter, 10, 4 );
134
135
		$this->request = new WP_REST_Request( 'POST', '/jetpack/v4/remote_authorize' );
136
		$this->request->set_header( 'Content-Type', 'application/json' );
137
		$this->request->set_body( '{ "state": "' . self::USER_ID . '", "secret": "' . $secret_1 . '", "redirect_uri": "https://example.org", "code": "54321" }' );
138
139
		$response = $this->server->dispatch( $this->request );
140
		$data     = $response->get_data();
141
142
		remove_filter( 'user_has_cap', $user_caps_filter );
143
		remove_filter( 'pre_option_' . Secrets::LEGACY_SECRETS_OPTION_NAME, $options_filter );
144
		remove_filter( 'pre_http_request', array( $this, 'intercept_auth_token_request' ) );
145
		remove_filter( 'jetpack_options', array( $this, 'mock_jetpack_site_connection_options' ) );
146
147
		wp_cache_delete( self::USER_ID, 'users' );
148
149
		wp_set_current_user( 0 );
150
151
		$this->assertEquals( 200, $response->get_status() );
152
		$this->assertEquals( 'authorized', $data['result'] );
153
	}
154
155
	/**
156
	 * Testing the `/jetpack/v4/connection` endpoint.
157
	 */
158
	public function test_connection() {
159
		add_filter( 'jetpack_offline_mode', '__return_true' );
160
		try {
161
			$this->request = new WP_REST_Request( 'GET', '/jetpack/v4/connection' );
162
163
			$response = $this->server->dispatch( $this->request );
164
			$data     = $response->get_data();
165
166
			$this->assertFalse( $data['isActive'] );
167
			$this->assertFalse( $data['isRegistered'] );
168
			$this->assertTrue( $data['offlineMode']['isActive'] );
169
		} finally {
170
			remove_filter( 'jetpack_offline_mode', '__return_true' );
171
		}
172
	}
173
174
	/**
175
	 * Testing the `/jetpack/v4/connection` endpoint jetpack_connection_status filter.
176
	 */
177
	public function test_connection_jetpack_connection_status_filter() {
178
		add_filter(
179
			'jetpack_connection_status',
180
			function ( $status_data ) {
181
				$this->assertTrue( is_array( $status_data ) );
182
				return array();
183
			}
184
		);
185
		try {
186
			$this->request = new WP_REST_Request( 'GET', '/jetpack/v4/connection' );
187
188
			$response = $this->server->dispatch( $this->request );
189
			$data     = $response->get_data();
190
191
			$this->assertSame( array(), $data );
192
		} finally {
193
			remove_all_filters( 'jetpack_connection_status' );
194
		}
195
	}
196
197
	/**
198
	 * Testing the `/jetpack/v4/connection/plugins` endpoint.
199
	 */
200
	public function test_connection_plugins() {
201
		$user = wp_get_current_user();
202
		$user->add_cap( 'activate_plugins' );
203
204
		$plugins = array(
205
			array(
206
				'name' => 'Plugin Name 1',
207
				'slug' => 'plugin-slug-1',
208
			),
209
			array(
210
				'name' => 'Plugin Name 2',
211
				'slug' => 'plugin-slug-2',
212
			),
213
		);
214
215
		array_walk(
216
			$plugins,
217
			function ( $plugin ) {
218
				( new Connection_Plugin( $plugin['slug'] ) )->add( $plugin['name'] );
219
			}
220
		);
221
222
		Connection_Plugin_Storage::configure();
223
224
		$this->request = new WP_REST_Request( 'GET', '/jetpack/v4/connection/plugins' );
225
226
		$response = $this->server->dispatch( $this->request );
227
228
		$user->remove_cap( 'activate_plugins' );
229
230
		$this->assertEquals( $plugins, $response->get_data() );
231
	}
232
233
	/**
234
	 * Testing the `connection/reconnect` endpoint, full reconnect.
235
	 */
236
	public function test_connection_reconnect_full() {
237
		$this->setup_reconnect_test( null );
238
		add_filter( 'jetpack_connection_disconnect_site_wpcom', '__return_false' );
239
		add_filter( 'pre_http_request', array( static::class, 'intercept_register_request' ), 10, 3 );
240
241
		$response = $this->server->dispatch( $this->build_reconnect_request() );
242
		$data     = $response->get_data();
243
244
		remove_filter( 'pre_http_request', array( static::class, 'intercept_register_request' ), 10 );
245
		remove_filter( 'jetpack_connection_disconnect_site_wpcom', '__return_false' );
246
		$this->shutdown_reconnect_test( null );
247
248
		$this->assertEquals( 200, $response->get_status() );
249
		$this->assertEquals( 'in_progress', $data['status'] );
250
		$this->assertSame( 0, strpos( $data['authorizeUrl'], 'https://jetpack.wordpress.com/jetpack.authorize/' ) );
251
	}
252
253
	/**
254
	 * Testing the `connection/reconnect` endpoint, successful partial reconnect (blog token).
255
	 */
256 View Code Duplication
	public function test_connection_reconnect_partial_blog_token_success() {
257
		$this->setup_reconnect_test( 'blog_token' );
258
		add_filter( 'pre_http_request', array( $this, 'intercept_refresh_blog_token_request' ), 10, 3 );
259
260
		$response = $this->server->dispatch( $this->build_reconnect_request() );
261
		$data     = $response->get_data();
262
263
		remove_filter( 'pre_http_request', array( $this, 'intercept_refresh_blog_token_request' ), 10 );
264
		$this->shutdown_reconnect_test( 'blog_token' );
265
266
		$this->assertEquals( 200, $response->get_status() );
267
		$this->assertEquals( 'completed', $data['status'] );
268
	}
269
270
	/**
271
	 * Testing the `connection/reconnect` endpoint, failed partial reconnect (blog token).
272
	 */
273 View Code Duplication
	public function test_connection_reconnect_partial_blog_token_fail() {
274
		$this->setup_reconnect_test( 'blog_token' );
275
		add_filter( 'pre_http_request', array( $this, 'intercept_refresh_blog_token_request_fail' ), 10, 3 );
276
277
		$response = $this->server->dispatch( $this->build_reconnect_request() );
278
		$data     = $response->get_data();
279
280
		remove_filter( 'pre_http_request', array( $this, 'intercept_refresh_blog_token_request_fail' ), 10 );
281
		$this->shutdown_reconnect_test( 'blog_token' );
282
283
		$this->assertEquals( 500, $response->get_status() );
284
		$this->assertEquals( 'jetpack_secret', $data['code'] );
285
	}
286
287
	/**
288
	 * Testing the `connection/reconnect` endpoint, successful partial reconnect (user token).
289
	 */
290
	public function test_connection_reconnect_partial_user_token_success() {
291
		$this->setup_reconnect_test( 'user_token' );
292
293
		$response = $this->server->dispatch( $this->build_reconnect_request() );
294
		$data     = $response->get_data();
295
296
		$this->shutdown_reconnect_test( 'user_token' );
297
298
		$this->assertEquals( 200, $response->get_status() );
299
		$this->assertEquals( 'in_progress', $data['status'] );
300
		$this->assertSame( 0, strpos( $data['authorizeUrl'], 'https://jetpack.wordpress.com/jetpack.authorize/' ) );
301
	}
302
303
	/**
304
	 * Testing the `connection/reconnect` endpoint, site_connection (full reconnect).
305
	 */
306
	public function test_connection_reconnect_site_connection() {
307
		add_filter( 'jetpack_options', array( $this, 'mock_jetpack_site_connection_options' ), 10, 2 );
308
		add_filter( 'jetpack_connection_disconnect_site_wpcom', '__return_false' );
309
		add_filter( 'pre_http_request', array( static::class, 'intercept_register_request' ), 10, 3 );
310
311
		$response = $this->server->dispatch( $this->build_reconnect_request() );
312
		$data     = $response->get_data();
313
314
		remove_filter( 'pre_http_request', array( static::class, 'intercept_register_request' ), 10 );
315
		remove_filter( 'jetpack_connection_disconnect_site_wpcom', '__return_false' );
316
		remove_filter( 'jetpack_options', array( $this, 'mock_jetpack_site_connection_options' ) );
317
318
		$this->assertEquals( 200, $response->get_status() );
319
		$this->assertEquals( 'completed', $data['status'] );
320
	}
321
322
	/**
323
	 * Testing the `connection/reconnect` endpoint when the token validation request fails.
324
	 */
325
	public function test_connection_reconnect_when_token_validation_request_fails() {
326
		$this->setup_reconnect_test( 'token_validation_failed' );
327
		add_filter( 'jetpack_connection_disconnect_site_wpcom', '__return_false' );
328
		add_filter( 'pre_http_request', array( static::class, 'intercept_register_request' ), 10, 3 );
329
330
		$response = $this->server->dispatch( $this->build_reconnect_request() );
331
		$data     = $response->get_data();
332
333
		remove_filter( 'pre_http_request', array( static::class, 'intercept_register_request' ), 10 );
334
		remove_filter( 'jetpack_connection_disconnect_site_wpcom', '__return_false' );
335
		$this->shutdown_reconnect_test( 'token_validation_failed' );
336
337
		$this->assertEquals( 200, $response->get_status() );
338
		$this->assertEquals( 'in_progress', $data['status'] );
339
		$this->assertSame( 0, strpos( $data['authorizeUrl'], 'https://jetpack.wordpress.com/jetpack.authorize/' ) );
340
	}
341
342
	/**
343
	 * Testing the `connection/register` endpoint.
344
	 */
345 View Code Duplication
	public function test_connection_register() {
346
		add_filter( 'pre_http_request', array( static::class, 'intercept_register_request' ), 10, 3 );
347
348
		$this->request = new WP_REST_Request( 'POST', '/jetpack/v4/connection/register' );
349
		$this->request->set_header( 'Content-Type', 'application/json' );
350
351
		$this->request->set_body( wp_json_encode( array( 'registration_nonce' => wp_create_nonce( 'jetpack-registration-nonce' ) ) ) );
352
353
		$response = $this->server->dispatch( $this->request );
354
		$data     = $response->get_data();
355
356
		remove_filter( 'pre_http_request', array( static::class, 'intercept_register_request' ), 10 );
357
358
		// Manually clears filter added by Manager::register().
359
		remove_filter( 'jetpack_use_iframe_authorization_flow', '__return_false', 20 );
360
361
		$this->assertEquals( 200, $response->get_status() );
362
		$this->assertSame( 0, strpos( $data['authorizeUrl'], 'https://jetpack.wordpress.com/jetpack.authorize/' ) );
363
364
		// Asserts jetpack_register_site_rest_response filter is being properly hooked to add data from wpcom register endpoint response.
365
		$this->assertFalse( $data['allowInplaceAuthorization'] );
366
		$this->assertSame( '', $data['alternateAuthorizeUrl'] );
367
	}
368
369
	/**
370
	 * Testing the `connection/register` endpoint with allow_inplace_authorization as true.
371
	 */
372
	public function test_connection_register_allow_inplace() {
373
		add_filter( 'pre_http_request', array( static::class, 'intercept_register_request_with_allow_inplace' ), 10, 3 );
374
375
		$this->request = new WP_REST_Request( 'POST', '/jetpack/v4/connection/register' );
376
		$this->request->set_header( 'Content-Type', 'application/json' );
377
378
		$this->request->set_body( wp_json_encode( array( 'registration_nonce' => wp_create_nonce( 'jetpack-registration-nonce' ) ) ) );
379
380
		$response = $this->server->dispatch( $this->request );
381
		$data     = $response->get_data();
382
383
		remove_filter( 'pre_http_request', array( static::class, 'intercept_register_request_with_allow_inplace' ), 10 );
384
385
		$this->assertEquals( 200, $response->get_status() );
386
		$this->assertSame( 0, strpos( $data['authorizeUrl'], 'https://jetpack.wordpress.com/jetpack.authorize_iframe/' ) );
387
388
		// Asserts jetpack_register_site_rest_response filter is being properly hooked to add data from wpcom register endpoint response.
389
		$this->assertTrue( $data['allowInplaceAuthorization'] );
390
		$this->assertSame( '', $data['alternateAuthorizeUrl'] );
391
	}
392
393
	/**
394
	 * Testing the `connection/register` endpoint with alternate_authorization_url
395
	 */
396 View Code Duplication
	public function test_connection_register_with_alternate_auth_url() {
397
		add_filter( 'pre_http_request', array( static::class, 'intercept_register_request_with_alternate_auth_url' ), 10, 3 );
398
399
		$this->request = new WP_REST_Request( 'POST', '/jetpack/v4/connection/register' );
400
		$this->request->set_header( 'Content-Type', 'application/json' );
401
402
		$this->request->set_body( wp_json_encode( array( 'registration_nonce' => wp_create_nonce( 'jetpack-registration-nonce' ) ) ) );
403
404
		$response = $this->server->dispatch( $this->request );
405
		$data     = $response->get_data();
406
407
		remove_filter( 'pre_http_request', array( static::class, 'intercept_register_request_with_alternate_auth_url' ), 10 );
408
409
		// Manually clears filter added by Manager::register().
410
		remove_filter( 'jetpack_use_iframe_authorization_flow', '__return_false', 20 );
411
412
		$this->assertEquals( 200, $response->get_status() );
413
		$this->assertSame( 0, strpos( $data['authorizeUrl'], 'https://jetpack.wordpress.com/jetpack.authorize/' ) );
414
415
		// Asserts jetpack_register_site_rest_response filter is being properly hooked to add data from wpcom register endpoint response.
416
		$this->assertFalse( $data['allowInplaceAuthorization'] );
417
		$this->assertSame( Redirect::get_url( 'https://dummy.com' ), $data['alternateAuthorizeUrl'] );
418
	}
419
420
	/**
421
	 * Testing the `user-token` endpoint using blog token authorization.
422
	 * Response: failed authorization.
423
	 */
424
	public function test_set_user_token_unauthroized() {
425
		$this->request = new WP_REST_Request( 'POST', '/jetpack/v4/user-token' );
426
		$this->request->set_header( 'Content-Type', 'application/json' );
427
428
		$this->request->set_body( wp_json_encode( array( 'user_token' => 'test.test.1' ) ) );
429
430
		$response = $this->server->dispatch( $this->request );
431
		$data     = $response->get_data();
432
433
		static::assertEquals( 'invalid_permission_update_user_token', $data['code'] );
434
		static::assertEquals( 401, $data['data']['status'] );
435
	}
436
437
	/**
438
	 * Testing the `user-token` endpoint using blog token authorization.
439
	 * Response: user token updated.
440
	 */
441
	public function test_set_user_token_success() {
442
		add_filter( 'jetpack_options', array( $this, 'mock_jetpack_site_connection_options' ), 10, 2 );
443
444
		$action_hook_id    = null;
445
		$action_hook_token = null;
446
		$action_hook       = function ( $user_id, $user_token ) use ( &$action_hook_id, &$action_hook_token ) {
447
			$action_hook_id    = $user_id;
448
			$action_hook_token = $user_token;
449
		};
450
451
		add_action( 'jetpack_updated_user_token', $action_hook, 10, 2 );
452
453
		$token     = 'new:1:0';
454
		$timestamp = (string) time();
455
		$nonce     = 'testing123';
456
		$body_hash = '';
457
458
		wp_cache_set(
459
			1,
460
			(object) array(
461
				'ID'         => 1,
462
				'user_email' => '[email protected]',
463
			),
464
			'users'
465
		);
466
467
		$_SERVER['REQUEST_METHOD'] = 'POST';
468
469
		$_GET['_for']      = 'jetpack';
470
		$_GET['token']     = $token;
471
		$_GET['timestamp'] = $timestamp;
472
		$_GET['nonce']     = $nonce;
473
		$_GET['body-hash'] = $body_hash;
474
		// This is intentionally using base64_encode().
475
		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
476
		$_GET['signature'] = base64_encode(
477
			hash_hmac(
478
				'sha1',
479
				implode(
480
					"\n",
481
					$data  = array(
482
						$token,
483
						$timestamp,
484
						$nonce,
485
						$body_hash,
486
						'POST',
487
						'anything.example',
488
						'80',
489
						'',
490
					)
491
				) . "\n",
492
				'blogtoken',
493
				true
494
			)
495
		);
496
497
		Connection_Rest_Authentication::init()->wp_rest_authenticate( false );
498
499
		$this->request = new WP_REST_Request( 'POST', '/jetpack/v4/user-token' );
500
		$this->request->set_header( 'Content-Type', 'application/json' );
501
502
		$user_token = 'test.test.1';
503
504
		$this->request->set_body( wp_json_encode( array( 'user_token' => $user_token ) ) );
505
506
		$response = $this->server->dispatch( $this->request );
507
		$data     = $response->get_data();
508
509
		remove_action( 'jetpack_updated_user_token', $action_hook );
510
		remove_filter( 'jetpack_options', array( $this, 'mock_jetpack_site_connection_options' ) );
511
		wp_cache_delete( 1, 'users' );
512
513
		static::assertTrue( $data['success'] );
514
		static::assertEquals( 200, $response->status );
515
		static::assertEquals( array( 1 => $user_token ), Jetpack_Options::get_option( 'user_tokens' ) );
516
		static::assertSame( 1, $action_hook_id, "The 'jetpack_update_user_token_success' action was not properly executed." );
517
		static::assertEquals( $user_token, $action_hook_token, "The 'jetpack_update_user_token_success' action was not properly executed." );
518
	}
519
520
	/**
521
	 * This filter callback allows us to skip the database query by `Jetpack_Options` to retrieve the option.
522
	 *
523
	 * @param array $options List of options already skipping the database request.
524
	 *
525
	 * @return array
526
	 */
527
	public function bypass_raw_options( array $options ) {
528
		$options[ Secrets::LEGACY_SECRETS_OPTION_NAME ] = true;
529
530
		return $options;
531
	}
532
533
	/**
534
	 * Intercept the `jetpack.register` API request sent to WP.com, and mock the response.
535
	 *
536
	 * @param bool|array $response The existing response.
537
	 * @param array      $args The request arguments.
538
	 * @param string     $url The request URL.
539
	 *
540
	 * @return array
541
	 */
542
	public static function intercept_register_request( $response, $args, $url ) {
543
		if ( false === strpos( $url, 'jetpack.register' ) ) {
544
			return $response;
545
		}
546
547
		return self::get_register_request_mock_response();
548
	}
549
550
	/**
551
	 * Intercept the `jetpack.register` API request sent to WP.com, and mock the response with allow_inplace_authorization as true.
552
	 *
553
	 * @param bool|array $response The existing response.
554
	 * @param array      $args The request arguments.
555
	 * @param string     $url The request URL.
556
	 *
557
	 * @return array
558
	 */
559
	public static function intercept_register_request_with_allow_inplace( $response, $args, $url ) {
560
		if ( false === strpos( $url, 'jetpack.register' ) ) {
561
			return $response;
562
		}
563
564
		return self::get_register_request_mock_response( true );
565
	}
566
567
	/**
568
	 * Intercept the `jetpack.register` API request sent to WP.com, and mock the response with a value in alternate_authorization_url key.
569
	 *
570
	 * @param bool|array $response The existing response.
571
	 * @param array      $args The request arguments.
572
	 * @param string     $url The request URL.
573
	 *
574
	 * @return array
575
	 */
576
	public static function intercept_register_request_with_alternate_auth_url( $response, $args, $url ) {
577
		if ( false === strpos( $url, 'jetpack.register' ) ) {
578
			return $response;
579
		}
580
581
		return self::get_register_request_mock_response( false, 'https://dummy.com' );
582
	}
583
584
	/**
585
	 * Gets a mocked REST response from jetpack.register WPCOM endpoint
586
	 *
587
	 * @param boolean $allow_inplace_authorization the value of allow_inplace_authorization returned by the server.
588
	 * @param string  $alternate_authorization_url the value of alternate_authorization_url returned by the server.
589
	 * @return array
590
	 */
591
	private static function get_register_request_mock_response( $allow_inplace_authorization = false, $alternate_authorization_url = '' ) {
592
		return array(
593
			'headers'  => new Requests_Utility_CaseInsensitiveDictionary( array( 'content-type' => 'application/json' ) ),
594
			'body'     => wp_json_encode(
595
				array(
596
					'jetpack_id'                  => '12345',
597
					'jetpack_secret'              => 'sample_secret',
598
					'allow_inplace_authorization' => $allow_inplace_authorization,
599
					'alternate_authorization_url' => $alternate_authorization_url,
600
				)
601
			),
602
			'response' => array(
603
				'code'    => 200,
604
				'message' => 'OK',
605
			),
606
		);
607
	}
608
609
	/**
610
	 * Intercept the `jetpack-token-health` API request sent to WP.com, and mock the "invalid blog token" response.
611
	 *
612
	 * @param bool|array $response The existing response.
613
	 * @param array      $args The request arguments.
614
	 * @param string     $url The request URL.
615
	 *
616
	 * @return array
617
	 */
618
	public function intercept_validate_tokens_request_invalid_blog_token( $response, $args, $url ) {
619
		if ( false === strpos( $url, 'jetpack-token-health' ) ) {
620
			return $response;
621
		}
622
623
		return $this->build_validate_tokens_response( 'blog_token' );
624
	}
625
626
	/**
627
	 * Intercept the `jetpack-token-health` API request sent to WP.com, and mock the "invalid user token" response.
628
	 *
629
	 * @param bool|array $response The existing response.
630
	 * @param array      $args The request arguments.
631
	 * @param string     $url The request URL.
632
	 *
633
	 * @return array
634
	 */
635
	public function intercept_validate_tokens_request_invalid_user_token( $response, $args, $url ) {
636
		if ( false === strpos( $url, 'jetpack-token-health' ) ) {
637
			return $response;
638
		}
639
640
		return $this->build_validate_tokens_response( 'user_token' );
641
	}
642
643
	/**
644
	 * Intercept the `jetpack-token-health` API request sent to WP.com, and mock the "valid tokens" response.
645
	 *
646
	 * @param bool|array $response The existing response.
647
	 * @param array      $args The request arguments.
648
	 * @param string     $url The request URL.
649
	 *
650
	 * @return array
651
	 */
652
	public function intercept_validate_tokens_request_valid_tokens( $response, $args, $url ) {
653
		if ( false === strpos( $url, 'jetpack-token-health' ) ) {
654
			return $response;
655
		}
656
657
		return $this->build_validate_tokens_response( null );
658
	}
659
660
	/**
661
	 * Intercept the `jetpack-token-health` API request sent to WP.com, and mock failed response.
662
	 *
663
	 * @param bool|array $response The existing response.
664
	 * @param array      $args The request arguments.
665
	 * @param string     $url The request URL.
666
	 *
667
	 * @return array
668
	 */
669 View Code Duplication
	public function intercept_validate_tokens_request_failed( $response, $args, $url ) {
670
		if ( false === strpos( $url, 'jetpack-token-health' ) ) {
671
			return $response;
672
		}
673
674
		return array(
675
			'headers'  => new Requests_Utility_CaseInsensitiveDictionary( array( 'content-type' => 'application/json' ) ),
676
			'body'     => wp_json_encode( array( 'dummy_error' => true ) ),
677
			'response' => array(
678
				'code'    => 500,
679
				'message' => 'failed',
680
			),
681
		);
682
	}
683
684
	/**
685
	 * Build the response for a tokens validation request
686
	 *
687
	 * @param string $invalid_token Accepted values: 'blog_token', 'user_token'.
688
	 *
689
	 * @return array
690
	 */
691
	private function build_validate_tokens_response( $invalid_token ) {
692
		$body = array(
693
			'blog_token' => array(
694
				'is_healthy' => true,
695
			),
696
			'user_token' => array(
697
				'is_healthy'     => true,
698
				'is_master_user' => true,
699
			),
700
		);
701
702
		switch ( $invalid_token ) {
703
			case 'blog_token':
704
				$body['blog_token'] = array(
705
					'is_healthy' => false,
706
					'code'       => 'unknown_token',
707
				);
708
				break;
709
			case 'user_token':
710
				$body['user_token'] = array(
711
					'is_healthy' => false,
712
					'code'       => 'unknown_token',
713
				);
714
				break;
715
		}
716
717
		return array(
718
			'headers'  => new Requests_Utility_CaseInsensitiveDictionary( array( 'content-type' => 'application/json' ) ),
719
			'body'     => wp_json_encode( $body ),
720
			'response' => array(
721
				'code'    => 200,
722
				'message' => 'OK',
723
			),
724
		);
725
	}
726
727
	/**
728
	 * Intercept the `jetpack-refresh-blog-token` API request sent to WP.com, and mock the success response.
729
	 *
730
	 * @param bool|array $response The existing response.
731
	 * @param array      $args The request arguments.
732
	 * @param string     $url The request URL.
733
	 *
734
	 * @return array
735
	 */
736 View Code Duplication
	public function intercept_refresh_blog_token_request( $response, $args, $url ) {
737
		if ( false === strpos( $url, 'jetpack-refresh-blog-token' ) ) {
738
			return $response;
739
		}
740
741
		return array(
742
			'headers'  => new Requests_Utility_CaseInsensitiveDictionary( array( 'content-type' => 'application/json' ) ),
743
			'body'     => wp_json_encode( array( 'jetpack_secret' => self::BLOG_TOKEN ) ),
744
			'response' => array(
745
				'code'    => 200,
746
				'message' => 'OK',
747
			),
748
		);
749
	}
750
751
	/**
752
	 * Intercept the `jetpack-refresh-blog-token` API request sent to WP.com, and mock the failure response.
753
	 *
754
	 * @param bool|array $response The existing response.
755
	 * @param array      $args The request arguments.
756
	 * @param string     $url The request URL.
757
	 *
758
	 * @return array
759
	 */
760 View Code Duplication
	public function intercept_refresh_blog_token_request_fail( $response, $args, $url ) {
761
		if ( false === strpos( $url, 'jetpack-refresh-blog-token' ) ) {
762
			return $response;
763
		}
764
765
		return array(
766
			'headers'  => new Requests_Utility_CaseInsensitiveDictionary( array( 'content-type' => 'application/json' ) ),
767
			'body'     => wp_json_encode( array( 'jetpack_secret_missing' => true ) ), // Meaningless body.
768
			'response' => array(
769
				'code'    => 200,
770
				'message' => 'OK',
771
			),
772
		);
773
	}
774
775
	/**
776
	 * Intercept the `jetpack-token-health` API request sent to WP.com, and mock the "invalid blog token" response.
777
	 *
778
	 * @param bool|array $response The existing response.
779
	 * @param array      $args The request arguments.
780
	 * @param string     $url The request URL.
781
	 *
782
	 * @return array
783
	 */
784
	public function intercept_auth_token_request( $response, $args, $url ) {
785
		if ( false === strpos( $url, '/jetpack.token/' ) ) {
786
			return $response;
787
		}
788
789
		return array(
790
			'headers'  => new Requests_Utility_CaseInsensitiveDictionary( array( 'content-type' => 'application/json' ) ),
791
			'body'     => wp_json_encode(
792
				array(
793
					'access_token' => 'mock.token',
794
					'token_type'   => 'X_JETPACK',
795
					'scope'        => ( new Manager() )->sign_role( 'administrator' ),
796
				)
797
			),
798
			'response' => array(
799
				'code'    => 200,
800
				'message' => 'OK',
801
			),
802
		);
803
	}
804
805
	/**
806
	 * Intercept the `Jetpack_Options` call and mock the values.
807
	 * Site level / user-less connection set-up.
808
	 *
809
	 * @param mixed  $value The current option value.
810
	 * @param string $name Option name.
811
	 *
812
	 * @return mixed
813
	 */
814
	public function mock_jetpack_site_connection_options( $value, $name ) {
815
		switch ( $name ) {
816
			case 'blog_token':
817
				return self::BLOG_TOKEN;
818
			case 'id':
819
				return self::BLOG_ID;
820
		}
821
822
		return $value;
823
	}
824
825
	/**
826
	 * Intercept the `Jetpack_Options` call and mock the values.
827
	 * Full connection set-up.
828
	 *
829
	 * @param mixed  $value The current option value.
830
	 * @param string $name Option name.
831
	 *
832
	 * @return mixed
833
	 */
834
	public function mock_jetpack_options( $value, $name ) {
835
		switch ( $name ) {
836
			case 'blog_token':
837
				return self::BLOG_TOKEN;
838
			case 'id':
839
				return self::BLOG_ID;
840
			case 'master_user':
841
				return self::USER_ID;
842
			case 'user_tokens':
843
				return array(
844
					self::USER_ID => 'new.usertoken.' . self::USER_ID,
845
				);
846
		}
847
848
		return $value;
849
	}
850
851
	/**
852
	 * Build the `connection/reconnect` request object.
853
	 *
854
	 * @return WP_REST_Request
855
	 */
856
	private function build_reconnect_request() {
857
		$this->request = new WP_REST_Request( 'POST', '/jetpack/v4/connection/reconnect' );
858
		$this->request->set_header( 'Content-Type', 'application/json' );
859
860
		return $this->request;
861
	}
862
863
	/**
864
	 * Setup the environment to test the reconnection process.
865
	 *
866
	 * @param string|null $invalid_token The invalid token to be returned in the response. Null if the tokens should be valid.
867
	 */
868
	private function setup_reconnect_test( $invalid_token ) {
869
		switch ( $invalid_token ) {
870
			case 'blog_token':
871
				add_filter(
872
					'pre_http_request',
873
					array(
874
						$this,
875
						'intercept_validate_tokens_request_invalid_blog_token',
876
					),
877
					10,
878
					3
879
				);
880
				break;
881
			case 'user_token':
882
				add_filter(
883
					'pre_http_request',
884
					array(
885
						$this,
886
						'intercept_validate_tokens_request_invalid_user_token',
887
					),
888
					10,
889
					3
890
				);
891
				break;
892
			case 'token_validation_failed':
893
				add_filter(
894
					'pre_http_request',
895
					array(
896
						$this,
897
						'intercept_validate_tokens_request_failed',
898
					),
899
					10,
900
					3
901
				);
902
				break;
903
			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...
904
				add_filter(
905
					'pre_http_request',
906
					array(
907
						$this,
908
						'intercept_validate_tokens_request_valid_tokens',
909
					),
910
					10,
911
					3
912
				);
913
				break;
914
		}
915
916
		add_filter( 'jetpack_options', array( $this, 'mock_jetpack_options' ), 10, 2 );
917
	}
918
919
	/**
920
	 * Restore the environment after the `reconnect` test has been run.
921
	 *
922
	 * @param string|null $invalid_token The invalid token to be returned in the response. Null if the tokens should be valid.
923
	 */
924
	private function shutdown_reconnect_test( $invalid_token ) {
925
		switch ( $invalid_token ) {
926
			case 'blog_token':
927
				remove_filter(
928
					'pre_http_request',
929
					array(
930
						$this,
931
						'intercept_validate_tokens_request_invalid_blog_token',
932
					),
933
					10
934
				);
935
				break;
936
			case 'user_token':
937
				remove_filter(
938
					'pre_http_request',
939
					array(
940
						$this,
941
						'intercept_validate_tokens_request_invalid_user_token',
942
					),
943
					10
944
				);
945
				break;
946
			case 'token_validation_failed':
947
				remove_filter(
948
					'pre_http_request',
949
					array(
950
						$this,
951
						'intercept_validate_tokens_request_failed',
952
					),
953
					10
954
				);
955
				break;
956
			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...
957
				remove_filter(
958
					'pre_http_request',
959
					array(
960
						$this,
961
						'intercept_validate_tokens_request_valid_tokens',
962
					),
963
					10
964
				);
965
				break;
966
		}
967
968
		remove_filter( 'jetpack_options', array( $this, 'mock_jetpack_options' ), 10 );
969
		remove_filter( 'pre_http_request', array( $this, 'intercept_validate_tokens_request' ), 10 );
970
	}
971
972
}
973