Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: changed

Include the upstream status and error in the uptime route's failure response, so a timeout, a 401, and a 500 can be told apart.
33 changes: 25 additions & 8 deletions projects/packages/protect/src/sections/class-monitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public function get_uptime() {
if ( ! in_array( $history['code'], array( 401, 403 ), true ) ) {
set_transient( self::UPTIME_TRANSIENT, 'failed', MINUTE_IN_SECONDS );
}
return $this->unavailable_error();
return $this->unavailable_error( $history );
}

$uptime = array(
Expand Down Expand Up @@ -187,7 +187,8 @@ private function fetch_is_up() {
* GET a wpcom/v2 site endpoint as the current user.
*
* @param string $path Path after `/sites/<id>`.
* @return array The HTTP `code` (0 when the request failed) and the decoded `body`, null unless the code is 200.
* @return array The HTTP `code` (0 when the request failed), the decoded `body` (null unless the code is 200),
* and an `error` string describing a transport failure (null otherwise).
*/
private function request( $path ) {
$response = Client::wpcom_json_api_request_as_user(
Expand All @@ -197,19 +198,35 @@ private function request( $path ) {
null,
'wpcom'
);
$code = is_wp_error( $response ) ? 0 : (int) wp_remote_retrieve_response_code( $response );
if ( is_wp_error( $response ) ) {
return array(
'code' => 0,
'body' => null,
'error' => $response->get_error_code() . ': ' . $response->get_error_message(),
);
}
$code = (int) wp_remote_retrieve_response_code( $response );
return array(
'code' => $code,
'body' => 200 === $code ? json_decode( wp_remote_retrieve_body( $response ), true ) : null,
'code' => $code,
'body' => 200 === $code ? json_decode( wp_remote_retrieve_body( $response ), true ) : null,
'error' => null,
);
}

/**
* The error returned when WordPress.com can't be reached.
* The error returned when WordPress.com can't be reached, carrying the upstream detail for debugging.
*
* @param array $request The failing request from `request()`; empty for a cached failure with no detail to hand.
* @return WP_Error
*/
private function unavailable_error() {
return new WP_Error( 'uptime_unavailable', __( 'Uptime history is unavailable right now.', 'jetpack-protect-pkg' ), array( 'status' => 502 ) );
private function unavailable_error( $request = array() ) {
$data = array( 'status' => 502 );
if ( isset( $request['code'] ) ) {
$data['upstreamStatus'] = $request['code'];
}
if ( ! empty( $request['error'] ) ) {
$data['upstreamError'] = $request['error'];
}
return new WP_Error( 'uptime_unavailable', __( 'Uptime history is unavailable right now.', 'jetpack-protect-pkg' ), $data );
}
}
60 changes: 52 additions & 8 deletions projects/packages/protect/tests/php/Monitor_Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -217,12 +217,55 @@ public function test_get_uptime_reports_the_current_status_and_caches_an_unknown
*/
public static function provide_unusable_histories() {
return array(
'request failed' => array( new WP_Error( 'http_request_failed', 'Timed out' ), 1 ),
'WordPress.com 500' => array( self::json_response( array(), 500 ), 1 ),
'no days' => array( self::json_response( array() ), 1 ),
'no valid days' => array( self::json_response( array( 'total' => array( 'status' => 'up' ) ) ), 1 ),
'this user is refused' => array( self::json_response( array(), 403 ), 2 ),
'this user is not known' => array( self::json_response( array(), 401 ), 2 ),
'request failed' => array(
new WP_Error( 'http_request_failed', 'Timed out' ),
1,
array(
'status' => 502,
'upstreamStatus' => 0,
'upstreamError' => 'http_request_failed: Timed out',
),
),
'WordPress.com 500' => array(
self::json_response( array(), 500 ),
1,
array(
'status' => 502,
'upstreamStatus' => 500,
),
),
'no days' => array(
self::json_response( array() ),
1,
array(
'status' => 502,
'upstreamStatus' => 200,
),
),
'no valid days' => array(
self::json_response( array( 'total' => array( 'status' => 'up' ) ) ),
1,
array(
'status' => 502,
'upstreamStatus' => 200,
),
),
'this user is refused' => array(
self::json_response( array(), 403 ),
2,
array(
'status' => 502,
'upstreamStatus' => 403,
),
),
'this user is not known' => array(
self::json_response( array(), 401 ),
2,
array(
'status' => 502,
'upstreamStatus' => 401,
),
),
);
}

Expand All @@ -231,9 +274,10 @@ public static function provide_unusable_histories() {
*
* @param array|WP_Error $uptime_response WordPress.com's answer for the uptime history.
* @param int $requests Requests made by two calls: 1 when the failure is cached for everyone.
* @param array $error_data The error data the 502 should carry, telling a timeout from a 401 from a 500.
*/
#[DataProvider( 'provide_unusable_histories' )]
public function test_get_uptime_is_a_502_when_the_history_is_unusable_and_caches_only_shared_failures( $uptime_response, $requests ) {
public function test_get_uptime_is_a_502_when_the_history_is_unusable_and_caches_only_shared_failures( $uptime_response, $requests, $error_data ) {
$this->uptime_response = $uptime_response;
$monitor = new Monitor();

Expand All @@ -242,7 +286,7 @@ public function test_get_uptime_is_a_502_when_the_history_is_unusable_and_caches

$this->assertInstanceOf( WP_Error::class, $first );
$this->assertSame( 'uptime_unavailable', $first->get_error_code() );
$this->assertSame( array( 'status' => 502 ), $first->get_error_data() );
$this->assertSame( $error_data, $first->get_error_data() );
$this->assertInstanceOf( WP_Error::class, $second );
$this->assertCount( $requests, $this->requests );
}
Expand Down
Loading