Completed
Push — update/decouple_register_and_a... ( 2206b7...859c0f )
by
unknown
166:22 queued 157:03
created

build_validate_tokens_response()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 1
dl 0
loc 35
rs 9.36
c 0
b 0
f 0
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 PHPUnit\Framework\TestCase;
9
use Requests_Utility_CaseInsensitiveDictionary;
10
use WorDBless\Options as WorDBless_Options;
11
use WP_REST_Request;
12
use WP_REST_Server;
13
use WP_User;
14
15
/**
16
 * Unit tests for the REST API endpoints.
17
 *
18
 * @package automattic/jetpack-connection
19
 * @see \Automattic\Jetpack\Connection\REST_Connector
20
 */
21
class Test_REST_Endpoints extends TestCase {
22
23
	const BLOG_TOKEN = 'new.blogtoken';
24
	const BLOG_ID    = 42;
25
	const USER_ID    = 111;
26
27
	/**
28
	 * REST Server object.
29
	 *
30
	 * @var WP_REST_Server
31
	 */
32
	private $server;
33
34
	/**
35
	 * The original hostname to restore after tests are finished.
36
	 *
37
	 * @var string
38
	 */
39
	private $api_host_original;
40
41
	/**
42
	 * Setting up the test.
43
	 *
44
	 * @before
45
	 */
46
	public function set_up() {
47
		global $wp_rest_server;
48
49
		$wp_rest_server = new WP_REST_Server();
50
		$this->server   = $wp_rest_server;
51
52
		do_action( 'rest_api_init' );
53
		new REST_Connector( new Manager() );
54
55
		add_action( 'jetpack_disabled_raw_options', array( $this, 'bypass_raw_options' ) );
56
57
		$user = wp_get_current_user();
58
		$user->add_cap( 'jetpack_reconnect' );
59
		$user->add_cap( 'jetpack_connect' );
60
61
		$this->api_host_original                                  = Constants::get_constant( 'JETPACK__WPCOM_JSON_API_BASE' );
62
		Constants::$set_constants['JETPACK__WPCOM_JSON_API_BASE'] = 'https://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
	 * @after
73
	 */
74
	public function tear_down() {
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_BASE'] = $this->api_host_original;
81
82
		delete_transient( 'jetpack_assumed_site_creation_date' );
83
84
		WorDBless_Options::init()->clear_options();
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_userless_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_' . Secrets::LEGACY_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_' . Secrets::LEGACY_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_userless_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
		add_filter( 'jetpack_offline_mode', '__return_true' );
154
		try {
155
			$this->request = new WP_REST_Request( 'GET', '/jetpack/v4/connection' );
156
157
			$response = $this->server->dispatch( $this->request );
158
			$data     = $response->get_data();
159
160
			$this->assertFalse( $data['isActive'] );
161
			$this->assertFalse( $data['isRegistered'] );
162
			$this->assertTrue( $data['offlineMode']['isActive'] );
163
		} finally {
164
			remove_filter( 'jetpack_offline_mode', '__return_true' );
165
		}
166
	}
167
168
	/**
169
	 * Testing the `/jetpack/v4/connection` endpoint jetpack_connection_status filter.
170
	 */
171
	public function test_connection_jetpack_connection_status_filter() {
172
		add_filter(
173
			'jetpack_connection_status',
174
			function ( $status_data ) {
175
				$this->assertTrue( is_array( $status_data ) );
176
				return array();
177
			}
178
		);
179
		try {
180
			$this->request = new WP_REST_Request( 'GET', '/jetpack/v4/connection' );
181
182
			$response = $this->server->dispatch( $this->request );
183
			$data     = $response->get_data();
184
185
			$this->assertSame( array(), $data );
186
		} finally {
187
			remove_all_filters( 'jetpack_connection_status' );
188
		}
189
	}
190
191
	/**
192
	 * Testing the `/jetpack/v4/connection/plugins` endpoint.
193
	 */
194
	public function test_connection_plugins() {
195
		$user = wp_get_current_user();
196
		$user->add_cap( 'activate_plugins' );
197
198
		$plugins = array(
199
			array(
200
				'name' => 'Plugin Name 1',
201
				'slug' => 'plugin-slug-1',
202
			),
203
			array(
204
				'name' => 'Plugin Name 2',
205
				'slug' => 'plugin-slug-2',
206
			),
207
		);
208
209
		array_walk(
210
			$plugins,
211
			function ( $plugin ) {
212
				( new Connection_Plugin( $plugin['slug'] ) )->add( $plugin['name'] );
213
			}
214
		);
215
216
		Connection_Plugin_Storage::configure();
217
218
		$this->request = new WP_REST_Request( 'GET', '/jetpack/v4/connection/plugins' );
219
220
		$response = $this->server->dispatch( $this->request );
221
222
		$user->remove_cap( 'activate_plugins' );
223
224
		$this->assertEquals( $plugins, $response->get_data() );
225
	}
226
227
	/**
228
	 * Testing the `connection/reconnect` endpoint, full reconnect.
229
	 */
230
	public function test_connection_reconnect_full() {
231
		$this->setup_reconnect_test( null );
232
		add_filter( 'jetpack_connection_disconnect_site_wpcom', '__return_false' );
233
		add_filter( 'pre_http_request', array( static::class, 'intercept_register_request' ), 10, 3 );
234
235
		$response = $this->server->dispatch( $this->build_reconnect_request() );
236
		$data     = $response->get_data();
237
238
		remove_filter( 'pre_http_request', array( static::class, 'intercept_register_request' ), 10 );
239
		remove_filter( 'jetpack_connection_disconnect_site_wpcom', '__return_false' );
240
		$this->shutdown_reconnect_test( null );
241
242
		$this->assertEquals( 200, $response->get_status() );
243
		$this->assertEquals( 'in_progress', $data['status'] );
244
		$this->assertSame( 0, strpos( $data['authorizeUrl'], 'https://jetpack.wordpress.com/jetpack.authorize/' ) );
245
	}
246
247
	/**
248
	 * Testing the `connection/reconnect` endpoint, successful partial reconnect (blog token).
249
	 */
250 View Code Duplication
	public function test_connection_reconnect_partial_blog_token_success() {
251
		$this->setup_reconnect_test( 'blog_token' );
252
		add_filter( 'pre_http_request', array( $this, 'intercept_refresh_blog_token_request' ), 10, 3 );
253
254
		$response = $this->server->dispatch( $this->build_reconnect_request() );
255
		$data     = $response->get_data();
256
257
		remove_filter( 'pre_http_request', array( $this, 'intercept_refresh_blog_token_request' ), 10 );
258
		$this->shutdown_reconnect_test( 'blog_token' );
259
260
		$this->assertEquals( 200, $response->get_status() );
261
		$this->assertEquals( 'completed', $data['status'] );
262
	}
263
264
	/**
265
	 * Testing the `connection/reconnect` endpoint, failed partial reconnect (blog token).
266
	 */
267 View Code Duplication
	public function test_connection_reconnect_partial_blog_token_fail() {
268
		$this->setup_reconnect_test( 'blog_token' );
269
		add_filter( 'pre_http_request', array( $this, 'intercept_refresh_blog_token_request_fail' ), 10, 3 );
270
271
		$response = $this->server->dispatch( $this->build_reconnect_request() );
272
		$data     = $response->get_data();
273
274
		remove_filter( 'pre_http_request', array( $this, 'intercept_refresh_blog_token_request_fail' ), 10 );
275
		$this->shutdown_reconnect_test( 'blog_token' );
276
277
		$this->assertEquals( 500, $response->get_status() );
278
		$this->assertEquals( 'jetpack_secret', $data['code'] );
279
	}
280
281
	/**
282
	 * Testing the `connection/reconnect` endpoint, successful partial reconnect (user token).
283
	 */
284
	public function test_connection_reconnect_partial_user_token_success() {
285
		$this->setup_reconnect_test( 'user_token' );
286
287
		$response = $this->server->dispatch( $this->build_reconnect_request() );
288
		$data     = $response->get_data();
289
290
		$this->shutdown_reconnect_test( 'user_token' );
291
292
		$this->assertEquals( 200, $response->get_status() );
293
		$this->assertEquals( 'in_progress', $data['status'] );
294
		$this->assertSame( 0, strpos( $data['authorizeUrl'], 'https://jetpack.wordpress.com/jetpack.authorize/' ) );
295
	}
296
297
	/**
298
	 * Testing the `connection/reconnect` endpoint, userless (full reconnect).
299
	 */
300
	public function test_connection_reconnect_userless() {
301
		add_filter( 'jetpack_options', array( $this, 'mock_jetpack_userless_options' ), 10, 2 );
302
		add_filter( 'jetpack_connection_disconnect_site_wpcom', '__return_false' );
303
		add_filter( 'pre_http_request', array( static::class, 'intercept_register_request' ), 10, 3 );
304
305
		$response = $this->server->dispatch( $this->build_reconnect_request() );
306
		$data     = $response->get_data();
307
308
		remove_filter( 'pre_http_request', array( static::class, 'intercept_register_request' ), 10 );
309
		remove_filter( 'jetpack_connection_disconnect_site_wpcom', '__return_false' );
310
		remove_filter( 'jetpack_options', array( $this, 'mock_jetpack_userless_options' ) );
311
312
		$this->assertEquals( 200, $response->get_status() );
313
		$this->assertEquals( 'completed', $data['status'] );
314
	}
315
316
	/**
317
	 * Testing the `connection/reconnect` endpoint when the token validation request fails.
318
	 */
319
	public function test_connection_reconnect_when_token_validation_request_fails() {
320
		$this->setup_reconnect_test( 'token_validation_failed' );
321
		add_filter( 'jetpack_connection_disconnect_site_wpcom', '__return_false' );
322
		add_filter( 'pre_http_request', array( static::class, 'intercept_register_request' ), 10, 3 );
323
324
		$response = $this->server->dispatch( $this->build_reconnect_request() );
325
		$data     = $response->get_data();
326
327
		remove_filter( 'pre_http_request', array( static::class, 'intercept_register_request' ), 10 );
328
		remove_filter( 'jetpack_connection_disconnect_site_wpcom', '__return_false' );
329
		$this->shutdown_reconnect_test( 'token_validation_failed' );
330
331
		$this->assertEquals( 200, $response->get_status() );
332
		$this->assertEquals( 'in_progress', $data['status'] );
333
		$this->assertSame( 0, strpos( $data['authorizeUrl'], 'https://jetpack.wordpress.com/jetpack.authorize/' ) );
334
	}
335
336
	/**
337
	 * Testing the `connection/register` endpoint.
338
	 */
339 View Code Duplication
	public function test_connection_register() {
340
		add_filter( 'pre_http_request', array( static::class, 'intercept_register_request' ), 10, 3 );
341
342
		$this->request = new WP_REST_Request( 'POST', '/jetpack/v4/connection/register' );
343
		$this->request->set_header( 'Content-Type', 'application/json' );
344
345
		$this->request->set_body( wp_json_encode( array( 'registration_nonce' => wp_create_nonce( 'jetpack-registration-nonce' ) ) ) );
346
347
		$response = $this->server->dispatch( $this->request );
348
		$data     = $response->get_data();
349
350
		remove_filter( 'pre_http_request', array( static::class, 'intercept_register_request' ), 10 );
351
352
		// Manually clears filter added by Manager::register().
353
		remove_filter( 'jetpack_use_iframe_authorization_flow', '__return_false', 20 );
354
355
		$this->assertEquals( 200, $response->get_status() );
356
		$this->assertSame( 0, strpos( $data['authorizeUrl'], 'https://jetpack.wordpress.com/jetpack.authorize/' ) );
357
358
		// Asserts jetpack_register_site_rest_response filter is being properly hooked to add data from wpcom register endpoint response.
359
		$this->assertFalse( $data['allowInplaceAuthorization'] );
360
		$this->assertSame( '', $data['alternateAuthorizeUrl'] );
361
	}
362
363
	/**
364
	 * Testing the `connection/register` endpoint with allow_inplace_authorization as true.
365
	 */
366
	public function test_connection_register_allow_inplace() {
367
		add_filter( 'pre_http_request', array( static::class, 'intercept_register_request_with_allow_inplace' ), 10, 3 );
368
369
		$this->request = new WP_REST_Request( 'POST', '/jetpack/v4/connection/register' );
370
		$this->request->set_header( 'Content-Type', 'application/json' );
371
372
		$this->request->set_body( wp_json_encode( array( 'registration_nonce' => wp_create_nonce( 'jetpack-registration-nonce' ) ) ) );
373
374
		$response = $this->server->dispatch( $this->request );
375
		$data     = $response->get_data();
376
377
		remove_filter( 'pre_http_request', array( static::class, 'intercept_register_request_with_allow_inplace' ), 10 );
378
379
		$this->assertEquals( 200, $response->get_status() );
380
		$this->assertSame( 0, strpos( $data['authorizeUrl'], 'https://jetpack.wordpress.com/jetpack.authorize_iframe/' ) );
381
382
		// Asserts jetpack_register_site_rest_response filter is being properly hooked to add data from wpcom register endpoint response.
383
		$this->assertTrue( $data['allowInplaceAuthorization'] );
384
		$this->assertSame( '', $data['alternateAuthorizeUrl'] );
385
	}
386
387
	/**
388
	 * Testing the `connection/register` endpoint with alternate_authorization_url
389
	 */
390 View Code Duplication
	public function test_connection_register_with_alternate_auth_url() {
391
		add_filter( 'pre_http_request', array( static::class, 'intercept_register_request_with_alternate_auth_url' ), 10, 3 );
392
393
		$this->request = new WP_REST_Request( 'POST', '/jetpack/v4/connection/register' );
394
		$this->request->set_header( 'Content-Type', 'application/json' );
395
396
		$this->request->set_body( wp_json_encode( array( 'registration_nonce' => wp_create_nonce( 'jetpack-registration-nonce' ) ) ) );
397
398
		$response = $this->server->dispatch( $this->request );
399
		$data     = $response->get_data();
400
401
		remove_filter( 'pre_http_request', array( static::class, 'intercept_register_request_with_alternate_auth_url' ), 10 );
402
403
		// Manually clears filter added by Manager::register().
404
		remove_filter( 'jetpack_use_iframe_authorization_flow', '__return_false', 20 );
405
406
		$this->assertEquals( 200, $response->get_status() );
407
		$this->assertSame( 0, strpos( $data['authorizeUrl'], 'https://jetpack.wordpress.com/jetpack.authorize/' ) );
408
409
		// Asserts jetpack_register_site_rest_response filter is being properly hooked to add data from wpcom register endpoint response.
410
		$this->assertFalse( $data['allowInplaceAuthorization'] );
411
		$this->assertSame( 'https://dummy.com', $data['alternateAuthorizeUrl'] );
412
	}
413
414
	/**
415
	 * This filter callback allow us to skip the database query by `Jetpack_Options` to retrieve the option.
416
	 *
417
	 * @param array $options List of options already skipping the database request.
418
	 *
419
	 * @return array
420
	 */
421
	public function bypass_raw_options( array $options ) {
422
		$options[ Secrets::LEGACY_SECRETS_OPTION_NAME ] = true;
423
424
		return $options;
425
	}
426
427
	/**
428
	 * Intercept the `jetpack.register` API request sent to WP.com, and mock the response.
429
	 *
430
	 * @param bool|array $response The existing response.
431
	 * @param array      $args The request arguments.
432
	 * @param string     $url The request URL.
433
	 *
434
	 * @return array
435
	 */
436
	public static function intercept_register_request( $response, $args, $url ) {
437
		if ( false === strpos( $url, 'jetpack.register' ) ) {
438
			return $response;
439
		}
440
441
		return self::get_register_request_mock_response();
442
	}
443
444
	/**
445
	 * Intercept the `jetpack.register` API request sent to WP.com, and mock the response with allow_inplace_authorization as true.
446
	 *
447
	 * @param bool|array $response The existing response.
448
	 * @param array      $args The request arguments.
449
	 * @param string     $url The request URL.
450
	 *
451
	 * @return array
452
	 */
453
	public static function intercept_register_request_with_allow_inplace( $response, $args, $url ) {
454
		if ( false === strpos( $url, 'jetpack.register' ) ) {
455
			return $response;
456
		}
457
458
		return self::get_register_request_mock_response( true );
459
	}
460
461
	/**
462
	 * Intercept the `jetpack.register` API request sent to WP.com, and mock the response with a value in alternate_authorization_url key.
463
	 *
464
	 * @param bool|array $response The existing response.
465
	 * @param array      $args The request arguments.
466
	 * @param string     $url The request URL.
467
	 *
468
	 * @return array
469
	 */
470
	public static function intercept_register_request_with_alternate_auth_url( $response, $args, $url ) {
471
		if ( false === strpos( $url, 'jetpack.register' ) ) {
472
			return $response;
473
		}
474
475
		return self::get_register_request_mock_response( false, 'https://dummy.com' );
476
	}
477
478
	/**
479
	 * Gets a mocked REST response from jetpack.register WPCOM endpoint
480
	 *
481
	 * @param boolean $allow_inplace_authorization the value of allow_inplace_authorization returned by the server.
482
	 * @param string  $alternate_authorization_url the value of alternate_authorization_url returned by the server.
483
	 * @return array
484
	 */
485
	private static function get_register_request_mock_response( $allow_inplace_authorization = false, $alternate_authorization_url = '' ) {
486
		return array(
487
			'headers'  => new Requests_Utility_CaseInsensitiveDictionary( array( 'content-type' => 'application/json' ) ),
488
			'body'     => wp_json_encode(
489
				array(
490
					'jetpack_id'                  => '12345',
491
					'jetpack_secret'              => 'sample_secret',
492
					'allow_inplace_authorization' => $allow_inplace_authorization,
493
					'alternate_authorization_url' => $alternate_authorization_url,
494
				)
495
			),
496
			'response' => array(
497
				'code'    => 200,
498
				'message' => 'OK',
499
			),
500
		);
501
	}
502
503
	/**
504
	 * Intercept the `jetpack-token-health` API request sent to WP.com, and mock the "invalid blog token" response.
505
	 *
506
	 * @param bool|array $response The existing response.
507
	 * @param array      $args The request arguments.
508
	 * @param string     $url The request URL.
509
	 *
510
	 * @return array
511
	 */
512
	public function intercept_validate_tokens_request_invalid_blog_token( $response, $args, $url ) {
513
		if ( false === strpos( $url, 'jetpack-token-health' ) ) {
514
			return $response;
515
		}
516
517
		return $this->build_validate_tokens_response( 'blog_token' );
518
	}
519
520
	/**
521
	 * Intercept the `jetpack-token-health` API request sent to WP.com, and mock the "invalid user token" response.
522
	 *
523
	 * @param bool|array $response The existing response.
524
	 * @param array      $args The request arguments.
525
	 * @param string     $url The request URL.
526
	 *
527
	 * @return array
528
	 */
529
	public function intercept_validate_tokens_request_invalid_user_token( $response, $args, $url ) {
530
		if ( false === strpos( $url, 'jetpack-token-health' ) ) {
531
			return $response;
532
		}
533
534
		return $this->build_validate_tokens_response( 'user_token' );
535
	}
536
537
	/**
538
	 * Intercept the `jetpack-token-health` API request sent to WP.com, and mock the "valid tokens" response.
539
	 *
540
	 * @param bool|array $response The existing response.
541
	 * @param array      $args The request arguments.
542
	 * @param string     $url The request URL.
543
	 *
544
	 * @return array
545
	 */
546
	public function intercept_validate_tokens_request_valid_tokens( $response, $args, $url ) {
547
		if ( false === strpos( $url, 'jetpack-token-health' ) ) {
548
			return $response;
549
		}
550
551
		return $this->build_validate_tokens_response( null );
552
	}
553
554
	/**
555
	 * Intercept the `jetpack-token-health` API request sent to WP.com, and mock failed response.
556
	 *
557
	 * @param bool|array $response The existing response.
558
	 * @param array      $args The request arguments.
559
	 * @param string     $url The request URL.
560
	 *
561
	 * @return array
562
	 */
563 View Code Duplication
	public function intercept_validate_tokens_request_failed( $response, $args, $url ) {
564
		if ( false === strpos( $url, 'jetpack-token-health' ) ) {
565
			return $response;
566
		}
567
568
		return array(
569
			'headers'  => new Requests_Utility_CaseInsensitiveDictionary( array( 'content-type' => 'application/json' ) ),
570
			'body'     => wp_json_encode( array( 'dummy_error' => true ) ),
571
			'response' => array(
572
				'code'    => 500,
573
				'message' => 'failed',
574
			),
575
		);
576
	}
577
578
	/**
579
	 * Build the response for a tokens validation request
580
	 *
581
	 * @param string $invalid_token Accepted values: 'blog_token', 'user_token'.
582
	 *
583
	 * @return array
584
	 */
585
	private function build_validate_tokens_response( $invalid_token ) {
586
		$body = array(
587
			'blog_token' => array(
588
				'is_healthy' => true,
589
			),
590
			'user_token' => array(
591
				'is_healthy'     => true,
592
				'is_master_user' => true,
593
			),
594
		);
595
596
		switch ( $invalid_token ) {
597
			case 'blog_token':
598
				$body['blog_token'] = array(
599
					'is_healthy' => false,
600
					'code'       => 'unknown_token',
601
				);
602
				break;
603
			case 'user_token':
604
				$body['user_token'] = array(
605
					'is_healthy' => false,
606
					'code'       => 'unknown_token',
607
				);
608
				break;
609
		}
610
611
		return array(
612
			'headers'  => new Requests_Utility_CaseInsensitiveDictionary( array( 'content-type' => 'application/json' ) ),
613
			'body'     => wp_json_encode( $body ),
614
			'response' => array(
615
				'code'    => 200,
616
				'message' => 'OK',
617
			),
618
		);
619
	}
620
621
	/**
622
	 * Intercept the `jetpack-refresh-blog-token` API request sent to WP.com, and mock the success response.
623
	 *
624
	 * @param bool|array $response The existing response.
625
	 * @param array      $args The request arguments.
626
	 * @param string     $url The request URL.
627
	 *
628
	 * @return array
629
	 */
630 View Code Duplication
	public function intercept_refresh_blog_token_request( $response, $args, $url ) {
631
		if ( false === strpos( $url, 'jetpack-refresh-blog-token' ) ) {
632
			return $response;
633
		}
634
635
		return array(
636
			'headers'  => new Requests_Utility_CaseInsensitiveDictionary( array( 'content-type' => 'application/json' ) ),
637
			'body'     => wp_json_encode( array( 'jetpack_secret' => self::BLOG_TOKEN ) ),
638
			'response' => array(
639
				'code'    => 200,
640
				'message' => 'OK',
641
			),
642
		);
643
	}
644
645
	/**
646
	 * Intercept the `jetpack-refresh-blog-token` API request sent to WP.com, and mock the failure response.
647
	 *
648
	 * @param bool|array $response The existing response.
649
	 * @param array      $args The request arguments.
650
	 * @param string     $url The request URL.
651
	 *
652
	 * @return array
653
	 */
654 View Code Duplication
	public function intercept_refresh_blog_token_request_fail( $response, $args, $url ) {
655
		if ( false === strpos( $url, 'jetpack-refresh-blog-token' ) ) {
656
			return $response;
657
		}
658
659
		return array(
660
			'headers'  => new Requests_Utility_CaseInsensitiveDictionary( array( 'content-type' => 'application/json' ) ),
661
			'body'     => wp_json_encode( array( 'jetpack_secret_missing' => true ) ), // Meaningless body.
662
			'response' => array(
663
				'code'    => 200,
664
				'message' => 'OK',
665
			),
666
		);
667
	}
668
669
	/**
670
	 * Intercept the `jetpack-token-health` API request sent to WP.com, and mock the "invalid blog token" response.
671
	 *
672
	 * @param bool|array $response The existing response.
673
	 * @param array      $args The request arguments.
674
	 * @param string     $url The request URL.
675
	 *
676
	 * @return array
677
	 */
678
	public function intercept_auth_token_request( $response, $args, $url ) {
679
		if ( false === strpos( $url, '/jetpack.token/' ) ) {
680
			return $response;
681
		}
682
683
		return array(
684
			'headers'  => new Requests_Utility_CaseInsensitiveDictionary( array( 'content-type' => 'application/json' ) ),
685
			'body'     => wp_json_encode(
686
				array(
687
					'access_token' => 'mock.token',
688
					'token_type'   => 'X_JETPACK',
689
					'scope'        => ( new Manager() )->sign_role( 'administrator' ),
690
				)
691
			),
692
			'response' => array(
693
				'code'    => 200,
694
				'message' => 'OK',
695
			),
696
		);
697
	}
698
699
	/**
700
	 * Intercept the `Jetpack_Options` call and mock the values.
701
	 * Site level / user-less connection set-up.
702
	 *
703
	 * @param mixed  $value The current option value.
704
	 * @param string $name Option name.
705
	 *
706
	 * @return mixed
707
	 */
708
	public function mock_jetpack_userless_options( $value, $name ) {
709
		switch ( $name ) {
710
			case 'blog_token':
711
				return self::BLOG_TOKEN;
712
			case 'id':
713
				return self::BLOG_ID;
714
		}
715
716
		return $value;
717
	}
718
719
	/**
720
	 * Intercept the `Jetpack_Options` call and mock the values.
721
	 * Full connection set-up.
722
	 *
723
	 * @param mixed  $value The current option value.
724
	 * @param string $name Option name.
725
	 *
726
	 * @return mixed
727
	 */
728
	public function mock_jetpack_options( $value, $name ) {
729
		switch ( $name ) {
730
			case 'blog_token':
731
				return self::BLOG_TOKEN;
732
			case 'id':
733
				return self::BLOG_ID;
734
			case 'master_user':
735
				return self::USER_ID;
736
			case 'user_tokens':
737
				return array(
738
					self::USER_ID => 'new.usertoken.' . self::USER_ID,
739
				);
740
		}
741
742
		return $value;
743
	}
744
745
	/**
746
	 * Build the `connection/reconnect` request object.
747
	 *
748
	 * @return WP_REST_Request
749
	 */
750
	private function build_reconnect_request() {
751
		$this->request = new WP_REST_Request( 'POST', '/jetpack/v4/connection/reconnect' );
752
		$this->request->set_header( 'Content-Type', 'application/json' );
753
754
		return $this->request;
755
	}
756
757
	/**
758
	 * Setup the environment to test the reconnection process.
759
	 *
760
	 * @param string|null $invalid_token The invalid token to be returned in the response. Null if the tokens should be valid.
761
	 */
762
	private function setup_reconnect_test( $invalid_token ) {
763
		switch ( $invalid_token ) {
764
			case 'blog_token':
765
				add_filter(
766
					'pre_http_request',
767
					array(
768
						$this,
769
						'intercept_validate_tokens_request_invalid_blog_token',
770
					),
771
					10,
772
					3
773
				);
774
				break;
775
			case 'user_token':
776
				add_filter(
777
					'pre_http_request',
778
					array(
779
						$this,
780
						'intercept_validate_tokens_request_invalid_user_token',
781
					),
782
					10,
783
					3
784
				);
785
				break;
786
			case 'token_validation_failed':
787
				add_filter(
788
					'pre_http_request',
789
					array(
790
						$this,
791
						'intercept_validate_tokens_request_failed',
792
					),
793
					10,
794
					3
795
				);
796
				break;
797
			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...
798
				add_filter(
799
					'pre_http_request',
800
					array(
801
						$this,
802
						'intercept_validate_tokens_request_valid_tokens',
803
					),
804
					10,
805
					3
806
				);
807
				break;
808
		}
809
810
		add_filter( 'jetpack_options', array( $this, 'mock_jetpack_options' ), 10, 2 );
811
	}
812
813
	/**
814
	 * Restore the environment after the `reconnect` test has been run.
815
	 *
816
	 * @param string|null $invalid_token The invalid token to be returned in the response. Null if the tokens should be valid.
817
	 */
818
	private function shutdown_reconnect_test( $invalid_token ) {
819
		switch ( $invalid_token ) {
820
			case 'blog_token':
821
				remove_filter(
822
					'pre_http_request',
823
					array(
824
						$this,
825
						'intercept_validate_tokens_request_invalid_blog_token',
826
					),
827
					10
828
				);
829
				break;
830
			case 'user_token':
831
				remove_filter(
832
					'pre_http_request',
833
					array(
834
						$this,
835
						'intercept_validate_tokens_request_invalid_user_token',
836
					),
837
					10
838
				);
839
				break;
840
			case 'token_validation_failed':
841
				remove_filter(
842
					'pre_http_request',
843
					array(
844
						$this,
845
						'intercept_validate_tokens_request_failed',
846
					),
847
					10
848
				);
849
				break;
850
			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...
851
				remove_filter(
852
					'pre_http_request',
853
					array(
854
						$this,
855
						'intercept_validate_tokens_request_valid_tokens',
856
					),
857
					10
858
				);
859
				break;
860
		}
861
862
		remove_filter( 'jetpack_options', array( $this, 'mock_jetpack_options' ), 10 );
863
		remove_filter( 'pre_http_request', array( $this, 'intercept_validate_tokens_request' ), 10 );
864
	}
865
866
}
867