Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add site health check to detect blocked REST API and short-circuit optimization when Inaccessible #1762

Open
wants to merge 26 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
88952a5
Add REST API health check for Optimization Detective
b1ink0 Dec 19, 2024
4bff9fe
Improve error message of site health check
b1ink0 Dec 19, 2024
56d769a
Add scheduled health check for Optimization Detective REST API
b1ink0 Dec 19, 2024
ba911e1
Save site health check status for Optimization Detective REST API
b1ink0 Dec 19, 2024
12a229c
Add test for when REST API is available
b1ink0 Dec 19, 2024
a0f460d
Add tests for REST API unauthorized error handling
b1ink0 Dec 19, 2024
c29cb7c
Add test for REST API forbidden error handling
b1ink0 Dec 19, 2024
8d8ae3e
Refactor to move site-health directory to plugin root directory
b1ink0 Dec 20, 2024
422a5bf
Clear site health check data during plugin uninstallation
b1ink0 Dec 20, 2024
0bc74f7
Move REST API availability check to appropriate function
b1ink0 Dec 20, 2024
36042fd
Add error message and error code to option
b1ink0 Dec 20, 2024
ad21df8
Refactor to store status code instead of string, Add fallback else co…
b1ink0 Dec 20, 2024
0bba468
Refactor to only use update_option once
b1ink0 Dec 20, 2024
e1b9004
Add activation hook to initialize option value
b1ink0 Dec 23, 2024
dbede65
Move scheduling to activation hook
b1ink0 Dec 23, 2024
2b2ca3e
Change health check scheduling from hourly to weekly
b1ink0 Dec 23, 2024
e7c1001
Run REST API health check on plugin activation, Display notice if che…
b1ink0 Dec 24, 2024
7e4e057
Move activation logic to separate function
b1ink0 Dec 24, 2024
a2baa65
Refactor activation logic to directly call REST API health check func…
b1ink0 Dec 24, 2024
e1ec10c
Merge branch 'trunk' into add/site-health-check-for-od-rest-api
b1ink0 Dec 24, 2024
48c83a5
Improve site health status messages for clarity and consistency
b1ink0 Dec 25, 2024
8895d35
Refactor site health checks files by combining them into single file
b1ink0 Jan 8, 2025
2fbd530
Move added action and filters for site health checks to plugins's hoo…
b1ink0 Jan 8, 2025
5949bc9
Merge branch 'trunk' into add/site-health-check-for-od-rest-api
b1ink0 Jan 8, 2025
1aa82b4
Merge branch 'trunk' into add/site-health-check-for-od-rest-api
b1ink0 Jan 8, 2025
98fff4d
Merge branch 'trunk' into add/site-health-check-for-od-rest-api
westonruter Jan 8, 2025
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
3 changes: 3 additions & 0 deletions plugins/optimization-detective/hooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@
OD_URL_Metrics_Post_Type::add_hooks();
add_action( 'wp', 'od_maybe_add_template_output_buffer_filter' );
add_action( 'wp_head', 'od_render_generator_meta_tag' );
add_filter( 'site_status_tests', 'od_optimization_detective_add_rest_api_test' );
add_action( 'od_rest_api_health_check_event', 'od_run_scheduled_rest_api_health_check' );
add_action( 'after_plugin_row_meta', 'od_rest_api_health_check_admin_notice', 30 );

Check warning on line 20 in plugins/optimization-detective/hooks.php

View check run for this annotation

Codecov / codecov/patch

plugins/optimization-detective/hooks.php#L18-L20

Added lines #L18 - L20 were not covered by tests
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Why use the after_plugin_row_meta action? If it so happens that the user has many plugins active, which is common, then Optimization Detective may not appear on the initial page and so the error will never be seen. I think the admin_notices action is more appropriate.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or rather there could be an inline notice with the plugin row which displays always, and then there could also be a notice shown at admin_notices for the first time that the logic in https://github.com/WordPress/performance/pull/1762/files#r1907965242 runs. In this way, a user should see the notice after activating the plugin on the plugin list table screen, but then it will go away after reloading to stop nagging them forever. But they'll still be able to see the notice if they scroll down to Optimization Detective as well as when they open Site Health.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A potential solution could be to display a one-time notice upon plugin activation, as suggested in this comment #1762(comment). Additionally, a persistent error message can be shown inline using the after_plugin_row_meta hook.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, a one-time notice upon activation, and then otherwise if this one-time notice is not displayed at admin_notices to then instead show it as an inline notice at after_plugin_row_meta.

15 changes: 15 additions & 0 deletions plugins/optimization-detective/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@
* action), so this is why it gets initialized at priority 9.
*/
add_action( 'init', $bootstrap, 9 );

register_activation_hook(
__FILE__,
static function () use ( $bootstrap ): void {

Check warning on line 57 in plugins/optimization-detective/load.php

View check run for this annotation

Codecov / codecov/patch

plugins/optimization-detective/load.php#L55-L57

Added lines #L55 - L57 were not covered by tests
/*
* The activation hook is called before the init action, so the plugin is not loaded yet. This
* means that the plugin must be bootstrapped here to run the activation logic.
*/
$bootstrap();
od_rest_api_health_check_plugin_activation();
}
);

Check warning on line 65 in plugins/optimization-detective/load.php

View check run for this annotation

Codecov / codecov/patch

plugins/optimization-detective/load.php#L62-L65

Added lines #L62 - L65 were not covered by tests
Comment on lines +54 to +65
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See note below about how the activation hook does not run when network-activating a plugin. I think this should switch to use admin_init. For example, add this to hooks.php:

add_action( 'admin_init', 'od_rest_api_health_check_plugin_activation' );

Copy link
Contributor Author

@b1ink0 b1ink0 Jan 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are the two reasons why I chose the activation hook approach:

  1. Immediate Notice Display:
    With the activation hook approach, when the user activates the plugin, they will see the notice about the REST API during the same page load as the plugin activation.

    • If we use the admin_init hook instead, the user will not see the notice immediately after activating the plugin because the health check is only scheduled during the plugin activation page load. It will execute asynchronously on the next load, and the notice will only appear on the third page load.
    • If a delayed notice is acceptable, we can proceed with the admin_init hook.
  2. Efficient Option Handling:
    As mentioned here, we should add an empty option during plugin activation. If we use the admin_init hook, the code to check whether the option exists and create it if not will run on every admin page load, which is inefficient.

Alternative Approach:
We can also adopt a hybrid approach where the event is scheduled in the admin_init hook, while the logic for adding the option and immediate notice display is handled in the activation hook. This approach would also address the multisite issue where the event may not get scheduled.

Copy link
Member

@westonruter westonruter Jan 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2. If we use the admin_init hook, the code to check whether the option exists and create it if not will run on every admin page load, which is inefficient.

Will this be inefficient? WordPress will remembers options which don't exist in notoptions so that no DB query occurs with the next get_option() call (when an external object cache is being used). Nevertheless, if upon activation the plugin has an admin_init action which will immediately call the Site Health logic to see if the REST API is available, then this will set the option so that no such request happens with the next admin page load. Also, if this request is gated behind admin requests in the first place, then this further reduces the performance impact since frontend users would experience nothing.

So I propose something like this:

/**
 * Plugin activation hook for the REST API health check.
 *
 * @since n.e.x.t
 */
function od_rest_api_health_check_plugin_activation(): void {
	// The option already exists, so do nothing.
	if ( false !== get_option( 'od_rest_api_info' ) ) {
		return;
	}

	// This will populate the od_rest_api_info option so that the function won't execute for the next page load.
	od_run_scheduled_rest_api_health_check();
}

and:

add_action( 'admin_init`, 'od_rest_api_health_check_plugin_activation' );

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed in #1762 (comment), we don't want to schedule any cron events at this time. To ensure our check runs for the first time, we can modify the provided function by directly calling od_optimization_detective_rest_api_test instead of od_run_scheduled_rest_api_health_check.

/**
 * Plugin activation hook for the REST API health check.
 *
 * @since n.e.x.t
 */
function od_rest_api_health_check_plugin_activation(): void {
    // If the option already exists, do nothing.
    if ( false !== get_option( 'od_rest_api_info' ) ) {
        return;
    }

    // This will populate the od_rest_api_info option so that the function won't execute on the next page load.
    od_optimization_detective_rest_api_test();
}

add_action( 'admin_init', 'od_rest_api_health_check_plugin_activation' );

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also for the admin notices which needs to be displayed only once after plugin activation can be included in this function.

/**
 * Plugin activation hook for the REST API health check.
 *
 * @since n.e.x.t
 */
function od_rest_api_health_check_plugin_activation(): void {
	// If the option already exists, do nothing.
	$rest_api_info = get_option( 'od_rest_api_info' );
	if ( false !== $rest_api_info ) {
		return;
	}

	// This will populate the od_rest_api_info option so that the function won't execute on the next page load.
	od_optimization_detective_rest_api_test();

	if (
		isset( $od_rest_api_info['available'] ) &&
		! (bool) $od_rest_api_info['available'] &&
		isset( $od_rest_api_info['error_message'] )
	) {
		wp_admin_notice(
			esc_html( $rest_api_info['error_message'] ),
			array(
				'type'               => 'warning',
				'additional_classes' => array( 'notice-alt' ),
			)
		);
	}
}

add_action( 'admin_init', 'od_rest_api_health_check_plugin_activation' );

}

// Register this copy of the plugin.
Expand Down Expand Up @@ -127,5 +139,8 @@

// Add hooks for the above requires.
require_once __DIR__ . '/hooks.php';

// Load site health checks.
require_once __DIR__ . '/site-health.php';

Check warning on line 144 in plugins/optimization-detective/load.php

View check run for this annotation

Codecov / codecov/patch

plugins/optimization-detective/load.php#L144

Added line #L144 was not covered by tests
}
);
7 changes: 6 additions & 1 deletion plugins/optimization-detective/optimization.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,12 @@ static function ( string $output, ?int $phase ): string {
* @access private
*/
function od_maybe_add_template_output_buffer_filter(): void {
if ( ! od_can_optimize_response() || isset( $_GET['optimization_detective_disabled'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$od_rest_api_info = get_option( 'od_rest_api_info' );
if (
! od_can_optimize_response() ||
isset( $_GET['optimization_detective_disabled'] ) || // phpcs:ignore WordPress.Security.NonceVerification.Recommended
( isset( $od_rest_api_info['available'] ) && ! (bool) $od_rest_api_info['available'] )
) {
return;
}
$callback = 'od_optimize_template_output_buffer';
Expand Down
188 changes: 188 additions & 0 deletions plugins/optimization-detective/site-health.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
<?php
/**
* Site Health checks.
*
* @package optimization-detective
* @since n.e.x.t
*/

if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.

Check warning on line 10 in plugins/optimization-detective/site-health.php

View check run for this annotation

Codecov / codecov/patch

plugins/optimization-detective/site-health.php#L9-L10

Added lines #L9 - L10 were not covered by tests
}

/**
* Adds the Optimization Detective REST API check to site health tests.
*
* @since n.e.x.t
*
* @param array{direct: array<string, array{label: string, test: string}>} $tests Site Health Tests.
* @return array{direct: array<string, array{label: string, test: string}>} Amended tests.
*/
function od_optimization_detective_add_rest_api_test( array $tests ): array {
$tests['direct']['optimization_detective_rest_api'] = array(
'label' => __( 'Optimization Detective REST API Endpoint Availability', 'optimization-detective' ),
'test' => 'od_optimization_detective_rest_api_test',
);

Check warning on line 25 in plugins/optimization-detective/site-health.php

View check run for this annotation

Codecov / codecov/patch

plugins/optimization-detective/site-health.php#L22-L25

Added lines #L22 - L25 were not covered by tests

return $tests;

Check warning on line 27 in plugins/optimization-detective/site-health.php

View check run for this annotation

Codecov / codecov/patch

plugins/optimization-detective/site-health.php#L27

Added line #L27 was not covered by tests
}

/**
* Tests availability of the Optimization Detective REST API endpoint.
*
* @since n.e.x.t
*
* @return array{label: string, status: string, badge: array{label: string, color: string}, description: string, actions: string, test: string} Result.
*/
function od_optimization_detective_rest_api_test(): array {
$result = array(
'label' => __( 'The REST API endpoint is functional.', 'optimization-detective' ),
'status' => 'good',
'badge' => array(
'label' => __( 'Optimization Detective', 'optimization-detective' ),
'color' => 'blue',
),
'description' => sprintf(
'<p>%s</p>',
__( 'Your site can send and receive URL metrics via the REST API endpoint.', 'optimization-detective' )
),
'actions' => '',
'test' => 'optimization_detective_rest_api',
);

$rest_url = get_rest_url( null, OD_REST_API_NAMESPACE . OD_URL_METRICS_ROUTE );
$response = wp_remote_post(
$rest_url,
array(
'headers' => array( 'Content-Type' => 'application/json' ),
'sslverify' => false,
)
);

if ( is_wp_error( $response ) ) {
$result['status'] = 'recommended';
$result['label'] = __( 'Error accessing the REST API endpoint', 'optimization-detective' );
$result['description'] = sprintf(
'<p>%s</p>',
esc_html__( 'There was an issue reaching the REST API endpoint. This might be due to server settings or the REST API being disabled.', 'optimization-detective' )
);
$info = array(
'error_message' => $response->get_error_message(),
'error_code' => $response->get_error_code(),
'available' => false,
);

Check warning on line 73 in plugins/optimization-detective/site-health.php

View check run for this annotation

Codecov / codecov/patch

plugins/optimization-detective/site-health.php#L63-L73

Added lines #L63 - L73 were not covered by tests
} else {
$status_code = wp_remote_retrieve_response_code( $response );
$data = json_decode( wp_remote_retrieve_body( $response ), true );
$expected_params = array( 'slug', 'current_etag', 'hmac', 'url', 'viewport', 'elements' );
$info = array(
'status_code' => $status_code,
'available' => false,
);

if (
400 === $status_code
&& isset( $data['data']['params'] )
&& is_array( $data['data']['params'] )
&& count( $expected_params ) === count( array_intersect( $data['data']['params'], $expected_params ) )
) {
// The REST API endpoint is available.
$info['available'] = true;
} elseif ( 401 === $status_code ) {
$result['status'] = 'recommended';
$result['label'] = __( 'Authorization should not be required to access the REST API endpoint.', 'optimization-detective' );
$result['description'] = sprintf(
'<p>%s</p>',
esc_html__( 'To collect URL metrics, the REST API endpoint should be accessible without requiring authorization.', 'optimization-detective' )
);
} elseif ( 403 === $status_code ) {
$result['status'] = 'recommended';
$result['label'] = __( 'The REST API endpoint should not be forbidden.', 'optimization-detective' );
$result['description'] = sprintf(
'<p>%s</p>',
esc_html__( 'The REST API endpoint is blocked. Please review your server or security settings.', 'optimization-detective' )
);
} else {
$result['status'] = 'recommended';
$result['label'] = __( 'Error accessing the REST API endpoint', 'optimization-detective' );
$result['description'] = sprintf(
'<p>%s</p>',
esc_html__( 'There was an issue reaching the REST API endpoint. This might be due to server settings or the REST API being disabled.', 'optimization-detective' )
);

Check warning on line 111 in plugins/optimization-detective/site-health.php

View check run for this annotation

Codecov / codecov/patch

plugins/optimization-detective/site-health.php#L106-L111

Added lines #L106 - L111 were not covered by tests
}
$info['error_message'] = $result['label'];
}

update_option( 'od_rest_api_info', $info );
return $result;
}

/**
* Periodically runs the Optimization Detective REST API health check.
*
* @since n.e.x.t
*/
function od_schedule_rest_api_health_check(): void {
if ( ! (bool) wp_next_scheduled( 'od_rest_api_health_check_event' ) ) {
wp_schedule_event( time(), 'weekly', 'od_rest_api_health_check_event' );

Check warning on line 127 in plugins/optimization-detective/site-health.php

View check run for this annotation

Codecov / codecov/patch

plugins/optimization-detective/site-health.php#L126-L127

Added lines #L126 - L127 were not covered by tests
}
}

/**
* Hook for the scheduled REST API health check.
*
* @since n.e.x.t
*/
function od_run_scheduled_rest_api_health_check(): void {
od_optimization_detective_rest_api_test();

Check warning on line 137 in plugins/optimization-detective/site-health.php

View check run for this annotation

Codecov / codecov/patch

plugins/optimization-detective/site-health.php#L137

Added line #L137 was not covered by tests
}

/**
* Displays an admin notice if the REST API health check fails.
*
* @since n.e.x.t
*
* @param string $plugin_file Plugin file.
*/
function od_rest_api_health_check_admin_notice( string $plugin_file ): void {
if ( 'optimization-detective/load.php' !== $plugin_file ) {
return;

Check warning on line 149 in plugins/optimization-detective/site-health.php

View check run for this annotation

Codecov / codecov/patch

plugins/optimization-detective/site-health.php#L148-L149

Added lines #L148 - L149 were not covered by tests
}

$od_rest_api_info = get_option( 'od_rest_api_info', array() );

Check warning on line 152 in plugins/optimization-detective/site-health.php

View check run for this annotation

Codecov / codecov/patch

plugins/optimization-detective/site-health.php#L152

Added line #L152 was not covered by tests
if (
isset( $od_rest_api_info['available'] ) &&
! (bool) $od_rest_api_info['available'] &&
isset( $od_rest_api_info['error_message'] )

Check warning on line 156 in plugins/optimization-detective/site-health.php

View check run for this annotation

Codecov / codecov/patch

plugins/optimization-detective/site-health.php#L154-L156

Added lines #L154 - L156 were not covered by tests
) {
wp_admin_notice(
esc_html( $od_rest_api_info['error_message'] ),
array(
'type' => 'warning',
'additional_classes' => array( 'inline', 'notice-alt' ),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to https://github.com/WordPress/performance/pull/1762/files#r1907962460, I think this should not use inline since a user may not scroll down to see the error message with the activated plugin row.

Suggested change
'additional_classes' => array( 'inline', 'notice-alt' ),
'additional_classes' => array( 'notice-alt' ),

)
);

Check warning on line 164 in plugins/optimization-detective/site-health.php

View check run for this annotation

Codecov / codecov/patch

plugins/optimization-detective/site-health.php#L158-L164

Added lines #L158 - L164 were not covered by tests
Comment on lines +158 to +164
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In wp-env, which doesn't support loopback requests, it's expected for there an error message to display. And I am indeed getting one, but it's not telling me what impact the error has:

image

In other words, this error is happening, but it should explain that Optimization Detective will not work because of the error. In other words, the same strings shown on the Site Health screen should be shown here as well:

$result['status'] = 'recommended';
$result['label'] = __( 'Error accessing the REST API endpoint', 'optimization-detective' );
$result['description'] = sprintf(
'<p>%s</p>',
esc_html__( 'There was an issue reaching the REST API endpoint. This might be due to server settings or the REST API being disabled.', 'optimization-detective' )
);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, I am displaying $result['label'] in the notices, but would using the more descriptive $result['description'] make more sense for the inline and also for the one time notice at top?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, where are you displaying $result['label'] in the notices? I only see it in od_optimization_detective_rest_api_test(). I think the same notice can be displayed inline as is what is shown not-inline in admin_notices.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2024-12-24 at 7 28 55 PM

Like this my question is should we display more detailed notice everywhere or this is fine?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Show the more detailed notice. In fact, perhaps only show the notice in admin_notices once (after activating the plugin) but then thereafter only show it inline. This would avoid the notice from appearing duplicated.

}
}

/**
* Plugin activation hook for the REST API health check.
*
* @since n.e.x.t
*/
function od_rest_api_health_check_plugin_activation(): void {
// Add the option if it doesn't exist.
if ( ! (bool) get_option( 'od_rest_api_info' ) ) {
add_option( 'od_rest_api_info', array() );

Check warning on line 176 in plugins/optimization-detective/site-health.php

View check run for this annotation

Codecov / codecov/patch

plugins/optimization-detective/site-health.php#L175-L176

Added lines #L175 - L176 were not covered by tests
}
od_schedule_rest_api_health_check();

Check warning on line 178 in plugins/optimization-detective/site-health.php

View check run for this annotation

Codecov / codecov/patch

plugins/optimization-detective/site-health.php#L178

Added line #L178 was not covered by tests
// Run the check immediately after Optimization Detective is activated.
add_action(
'activated_plugin',
static function ( string $plugin ): void {
if ( 'optimization-detective/load.php' === $plugin ) {
od_optimization_detective_rest_api_test();

Check warning on line 184 in plugins/optimization-detective/site-health.php

View check run for this annotation

Codecov / codecov/patch

plugins/optimization-detective/site-health.php#L180-L184

Added lines #L180 - L184 were not covered by tests
}
}
);

Check warning on line 187 in plugins/optimization-detective/site-health.php

View check run for this annotation

Codecov / codecov/patch

plugins/optimization-detective/site-health.php#L186-L187

Added lines #L186 - L187 were not covered by tests
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php
/**
* Tests for Optimization Detective REST API site health check.
*
* @package optimization-detective
*/

class Test_OD_REST_API_Site_Health_Check extends WP_UnitTestCase {

/**
* Holds mocked response headers for different test scenarios.
*
* @var array<string, array<string, mixed>>
*/
protected $mocked_responses = array();

/**
* Setup each test.
*/
public function setUp(): void {
parent::setUp();

// Clear any filters or mocks.
remove_all_filters( 'pre_http_request' );

// Add the filter to mock HTTP requests.
add_filter( 'pre_http_request', array( $this, 'mock_http_requests' ), 10, 3 );
}

/**
* Test that the site health check is `good` when the REST API is available.
*/
public function test_rest_api_available(): void {
$this->mocked_responses = array(
get_rest_url( null, OD_REST_API_NAMESPACE . OD_URL_METRICS_ROUTE ) => $this->build_mock_response(
400,
'Bad Request',
array(
'data' => array(
'params' => array( 'slug', 'current_etag', 'hmac', 'url', 'viewport', 'elements' ),
),
)
),
);

$result = od_optimization_detective_rest_api_test();
$od_rest_api_info = get_option( 'od_rest_api_info', array() );

$this->assertSame( 'good', $result['status'] );
$this->assertSame( 400, isset( $od_rest_api_info['status_code'] ) ? $od_rest_api_info['status_code'] : '' );
$this->assertTrue( isset( $od_rest_api_info['available'] ) ? $od_rest_api_info['available'] : false );
}

/**
* Test behavior when REST API returns an unauthorized error.
*/
public function test_rest_api_unauthorized(): void {
$this->mocked_responses = array(
get_rest_url( null, OD_REST_API_NAMESPACE . OD_URL_METRICS_ROUTE ) => $this->build_mock_response(
401,
'Unauthorized'
),
);

$result = od_optimization_detective_rest_api_test();
$od_rest_api_info = get_option( 'od_rest_api_info', array() );

$this->assertSame( 'recommended', $result['status'] );
$this->assertSame( 401, isset( $od_rest_api_info['status_code'] ) ? $od_rest_api_info['status_code'] : '' );
$this->assertFalse( isset( $od_rest_api_info['available'] ) ? $od_rest_api_info['available'] : true );
}

/**
* Test behavior when REST API returns an forbidden error.
*/
public function test_rest_api_forbidden(): void {
$this->mocked_responses = array(
get_rest_url( null, OD_REST_API_NAMESPACE . OD_URL_METRICS_ROUTE ) => $this->build_mock_response(
403,
'Forbidden'
),
);

$result = od_optimization_detective_rest_api_test();
$od_rest_api_info = get_option( 'od_rest_api_info', array() );

$this->assertSame( 'recommended', $result['status'] );
$this->assertSame( 403, isset( $od_rest_api_info['status_code'] ) ? $od_rest_api_info['status_code'] : '' );
$this->assertFalse( isset( $od_rest_api_info['available'] ) ? $od_rest_api_info['available'] : true );
}

/**
* Mock HTTP requests for assets to simulate different responses.
*
* @param bool $response A preemptive return value of an HTTP request. Default false.
* @param array<string, mixed> $args Request arguments.
* @param string $url The request URL.
* @return array<string, mixed> Mocked response.
*/
public function mock_http_requests( bool $response, array $args, string $url ): array {
if ( isset( $this->mocked_responses[ $url ] ) ) {
return $this->mocked_responses[ $url ];
}

// If no specific mock set, default to a generic success response.
return array(
'response' => array(
'code' => 200,
'message' => 'OK',
),
);
}

/**
* Build a mock response.
*
* @param int $status_code HTTP status code.
* @param string $message HTTP status message.
* @param array<string, mixed> $body Response body.
* @return array<string, mixed> Mocked response.
*/
protected function build_mock_response( int $status_code, string $message, array $body = array() ): array {
return array(
'response' => array(
'code' => $status_code,
'message' => $message,
),
'body' => wp_json_encode( $body ),
);
}
}
4 changes: 4 additions & 0 deletions plugins/optimization-detective/uninstall.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
// Delete all URL Metrics posts for the current site.
OD_URL_Metrics_Post_Type::delete_all_posts();
wp_unschedule_hook( OD_URL_Metrics_Post_Type::GC_CRON_EVENT_NAME );

// Clear out site health check data.
delete_option( 'od_rest_api_info' );
wp_unschedule_hook( 'od_rest_api_health_check_event' );
};

$od_delete_site_data();
Expand Down
Loading