From 192d294da5dc8e73de58d32fb891d06d068d1438 Mon Sep 17 00:00:00 2001 From: Baljinder Singh Date: Sun, 16 Aug 2026 21:43:10 +0000 Subject: [PATCH 1/3] Site Health: Add recurring email delivery checks --- src/js/_enqueues/admin/site-health.js | 2 +- .../includes/class-wp-site-health.php | 153 ++++++++++++++++- src/wp-admin/options-general.php | 79 +++++++++ tests/phpunit/tests/admin/wpSiteHealth.php | 156 ++++++++++++++++++ 4 files changed, 386 insertions(+), 4 deletions(-) diff --git a/src/js/_enqueues/admin/site-health.js b/src/js/_enqueues/admin/site-health.js index 57d5c9cbcf289..998b3ae273059 100644 --- a/src/js/_enqueues/admin/site-health.js +++ b/src/js/_enqueues/admin/site-health.js @@ -237,7 +237,7 @@ jQuery( function( $ ) { $circle.css( { strokeDashoffset: pct } ); - if ( 80 <= val && 0 === parseInt( SiteHealth.site_status.issues.critical, 0 ) ) { + if ( 80 <= val && 0 === parseInt( SiteHealth.site_status.issues.critical, 0 ) && ! SiteHealth.site_status.force_improvable ) { $wrapper.addClass( 'green' ).removeClass( 'orange' ); $progressLabel.text( __( 'Good' ) ); diff --git a/src/wp-admin/includes/class-wp-site-health.php b/src/wp-admin/includes/class-wp-site-health.php index 2ddfaadc4b39d..a170df8b3a9e8 100644 --- a/src/wp-admin/includes/class-wp-site-health.php +++ b/src/wp-admin/includes/class-wp-site-health.php @@ -84,6 +84,106 @@ public static function get_instance() { return self::$instance; } + /** + * Handles a request to send an email delivery test. + * + * @since 7.1.0 + */ + public function handle_email_delivery_test() { + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( + '

' . __( 'You need a higher level of permission.' ) . '

' . + '

' . __( 'Sorry, you are not allowed to manage options for this site.' ) . '

', + 403 + ); + } + + check_admin_referer( 'verify-email-delivery' ); + + $result = $this->send_email_delivery_test(); + $status = 'success'; + + if ( is_wp_error( $result ) ) { + $status = $result->get_error_code(); + } + + $redirect = add_query_arg( + 'email-delivery-test', + $status, + admin_url( 'options-general.php' ) + ); + + wp_safe_redirect( $redirect ); + exit; + } + + /** + * Sends a test email to the site administration email address. + * + * A successful result means that WordPress handed the message to the mail + * system without an immediate error. It does not confirm delivery. + * + * @since 7.1.0 + * + * @return true|WP_Error True on success, or a WP_Error on failure. + */ + public function send_email_delivery_test() { + $email = get_option( 'admin_email' ); + + if ( ! is_email( $email ) ) { + return new WP_Error( 'invalid-address' ); + } + + $site_name = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); + $subject = sprintf( + /* translators: %s: Site title. */ + __( '[%s] Email delivery test' ), + $site_name + ); + $message = sprintf( + /* translators: 1: Site title. 2: Site URL. */ + __( "This is a test email sent from your WordPress site, %1\$s.\n\nReceiving this message confirms that it reached this inbox. Review the message headers to confirm that the sender details are correct.\n\nSite URL: %2\$s" ), + $site_name, + home_url( '/' ) + ); + + if ( ! wp_mail( $email, $subject, $message ) ) { + return new WP_Error( 'send-failed' ); + } + + update_option( 'email_delivery_last_tested', time(), false ); + + return true; + } + + /** + * Gets the timestamp of the most recent valid email delivery test. + * + * @since 7.1.0 + * + * @return int The test timestamp, or 0 if no valid timestamp exists. + */ + public function get_email_delivery_last_tested() { + $last_tested = get_option( 'email_delivery_last_tested' ); + + if ( ! is_numeric( $last_tested ) || 0 >= (int) $last_tested || (int) $last_tested > time() ) { + return 0; + } + + return (int) $last_tested; + } + + /** + * Determines whether the most recent email delivery test is current. + * + * @since 7.1.0 + * + * @return bool Whether email delivery was tested within the required period. + */ + private function is_email_delivery_test_current() { + return $this->get_email_delivery_last_tested() > time() - ( 3 * MONTH_IN_SECONDS ); + } + /** * Enqueues the site health scripts. * @@ -102,9 +202,10 @@ public function enqueue_scripts() { 'site_status_result' => wp_create_nonce( 'health-check-site-status-result' ), ), 'site_status' => array( - 'direct' => array(), - 'async' => array(), - 'issues' => array( + 'direct' => array(), + 'async' => array(), + 'force_improvable' => ! $this->is_email_delivery_test_current(), + 'issues' => array( 'good' => 0, 'recommended' => 0, 'critical' => 0, @@ -2791,6 +2892,48 @@ public function get_test_search_engine_visibility() { return $result; } + /** + * Tests whether email delivery has been tested recently. + * + * @since 7.1.0 + * + * @return array The test results. + */ + public function get_test_email_delivery() { + $was_tested_recently = $this->is_email_delivery_test_current(); + + $result = array( + 'label' => __( 'Email delivery was recently tested' ), + 'status' => 'good', + 'badge' => array( + 'label' => __( 'Performance' ), + 'color' => 'blue', + ), + 'description' => sprintf( + '

%s

', + __( 'WordPress recently handed a test email to the mail system without an immediate error. This does not guarantee that the message reached the inbox.' ) + ), + 'actions' => '', + 'test' => 'email_delivery', + ); + + if ( ! $was_tested_recently ) { + $result['label'] = __( 'Email delivery should be tested' ); + $result['status'] = 'recommended'; + $result['description'] = sprintf( + '

%s

', + __( 'WordPress relies on email for important site administration messages. Send a test email, then confirm that it reaches the inbox and that its sender headers are correct.' ) + ); + $result['actions'] = sprintf( + '

%2$s

', + esc_url( admin_url( 'options-general.php' ) ), + __( 'Verify email delivery' ) + ); + } + + return $result; + } + /** * Tests if opcode cache is enabled and available. * @@ -2896,6 +3039,10 @@ public static function get_tests() { 'label' => __( 'HTTP Requests' ), 'test' => 'http_requests', ), + 'email_delivery' => array( + 'label' => __( 'Email delivery' ), + 'test' => 'email_delivery', + ), 'rest_availability' => array( 'label' => __( 'REST API availability' ), 'test' => 'rest_availability', diff --git a/src/wp-admin/options-general.php b/src/wp-admin/options-general.php index 6a9a6d2f3812d..71656756af8c5 100644 --- a/src/wp-admin/options-general.php +++ b/src/wp-admin/options-general.php @@ -16,6 +16,13 @@ wp_die( __( 'Sorry, you are not allowed to manage options for this site.' ) ); } +// The nonce is verified by WP_Site_Health::handle_email_delivery_test(). +// phpcs:ignore WordPress.Security.NonceVerification.Missing +if ( isset( $_POST['action'] ) && 'verify_email_delivery' === $_POST['action'] ) { + require_once ABSPATH . 'wp-admin/includes/class-wp-site-health.php'; + WP_Site_Health::get_instance()->handle_email_delivery_test(); +} + // Used in the HTML title tag. $title = __( 'General Settings' ); $parent_file = 'options-general.php'; @@ -67,6 +74,50 @@

+' . esc_html( get_option( 'admin_email' ) ) . '' + ), + array( + 'type' => 'success', + 'dismissible' => true, + ) + ); +} elseif ( 'invalid-address' === $email_delivery_test_status ) { + wp_admin_notice( + __( 'The Administration Email Address is not valid. Enter a valid address before testing email delivery.' ), + array( + 'type' => 'error', + 'dismissible' => true, + ) + ); +} elseif ( 'send-failed' === $email_delivery_test_status ) { + wp_admin_notice( + __( 'The test email could not be sent. Your site may not be correctly configured to send emails.' ), + array( + 'type' => 'error', + 'dismissible' => true, + ) + ); +} +?> + +
+ + +
+
@@ -265,6 +316,34 @@ class=""

The new address will not become active until confirmed.' ); ?>

+

+ +

+

+ get_email_delivery_last_tested(); + if ( $email_delivery_last_tested ) { + echo ' '; + printf( + /* translators: %s: Date and time of the most recent email delivery test. */ + __( 'Last tested: %s.' ), + esc_html( + wp_date( + get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), + (int) $email_delivery_last_tested + ) + ) + ); + } + ?> +

instance = new WP_Site_Health(); } + /** + * Performs cleanup tasks after every test. + */ + public function tear_down() { + delete_option( 'email_delivery_last_tested' ); + + parent::tear_down(); + } + /** * @ticket 55791 * @covers ::__construct() @@ -649,6 +658,153 @@ public static function set_autoloaded_option( $bytes = 800000 ) { add_option( 'test_set_autoloaded_option', $heavy_option_string, '', true ); } + /** + * Tests the email delivery Site Health status. + * + * @ticket 65891 + * @dataProvider data_email_delivery_status + * @covers ::get_email_delivery_last_tested() + * @covers ::get_test_email_delivery() + * + * @param string $state The stored timestamp state. + * @param string $expected_status The expected Site Health status. + */ + public function test_email_delivery_status( $state, $expected_status ) { + switch ( $state ) { + case 'malformed': + update_option( 'email_delivery_last_tested', 'not-a-timestamp', false ); + break; + case 'future': + update_option( 'email_delivery_last_tested', time() + HOUR_IN_SECONDS, false ); + break; + case 'expired': + update_option( 'email_delivery_last_tested', time() - ( 3 * MONTH_IN_SECONDS ), false ); + break; + case 'recent': + update_option( 'email_delivery_last_tested', time() - DAY_IN_SECONDS, false ); + break; + } + + $result = $this->instance->get_test_email_delivery(); + + $this->assertSame( $expected_status, $result['status'] ); + $this->assertSame( 'email_delivery', $result['test'] ); + $this->assertSame( + array( + 'label' => __( 'Performance' ), + 'color' => 'blue', + ), + $result['badge'] + ); + + if ( 'recommended' === $expected_status ) { + $this->assertStringNotContainsString( '#email-delivery-test', $result['actions'] ); + } + } + + /** + * Data provider for test_email_delivery_status(). + * + * @return array[] Test parameters. + */ + public function data_email_delivery_status() { + return array( + 'missing timestamp' => array( 'missing', 'recommended' ), + 'malformed timestamp' => array( 'malformed', 'recommended' ), + 'future timestamp' => array( 'future', 'recommended' ), + 'expired timestamp' => array( 'expired', 'recommended' ), + 'recent timestamp' => array( 'recent', 'good' ), + ); + } + + /** + * Tests a successful email delivery test. + * + * @ticket 65891 + * @covers ::send_email_delivery_test() + */ + public function test_send_email_delivery_test_success() { + global $wpdb; + + $mail_data = null; + $callback = static function ( $short_circuit, $atts ) use ( &$mail_data ) { + $mail_data = $atts; + return true; + }; + add_filter( 'pre_wp_mail', $callback, 10, 2 ); + + $result = $this->instance->send_email_delivery_test(); + + remove_filter( 'pre_wp_mail', $callback ); + + $this->assertTrue( $result ); + $this->assertSame( get_option( 'admin_email' ), $mail_data['to'] ); + $this->assertStringContainsString( get_option( 'blogname' ), $mail_data['subject'] ); + $this->assertStringContainsString( home_url( '/' ), $mail_data['message'] ); + $this->assertGreaterThan( 0, $this->instance->get_email_delivery_last_tested() ); + $this->assertSame( + 'off', + $wpdb->get_var( + $wpdb->prepare( + "SELECT autoload FROM $wpdb->options WHERE option_name = %s", + 'email_delivery_last_tested' + ) + ) + ); + } + + /** + * Tests an immediate email sending failure. + * + * @ticket 65891 + * @covers ::send_email_delivery_test() + */ + public function test_send_email_delivery_test_failure() { + add_filter( 'pre_wp_mail', '__return_false' ); + + $result = $this->instance->send_email_delivery_test(); + + remove_filter( 'pre_wp_mail', '__return_false' ); + + $this->assertWPError( $result ); + $this->assertSame( 'send-failed', $result->get_error_code() ); + $this->assertFalse( get_option( 'email_delivery_last_tested', false ) ); + } + + /** + * Tests an invalid administration email address. + * + * @ticket 65891 + * @covers ::send_email_delivery_test() + */ + public function test_send_email_delivery_test_invalid_address() { + $callback = static function () { + return 'not-an-email'; + }; + add_filter( 'pre_option_admin_email', $callback ); + + $result = $this->instance->send_email_delivery_test(); + + remove_filter( 'pre_option_admin_email', $callback ); + + $this->assertWPError( $result ); + $this->assertSame( 'invalid-address', $result->get_error_code() ); + $this->assertFalse( get_option( 'email_delivery_last_tested', false ) ); + } + + /** + * Tests that the email delivery check is registered as a direct test. + * + * @ticket 65891 + * @covers ::get_tests() + */ + public function test_email_delivery_test_is_registered() { + $tests = WP_Site_Health::get_tests(); + + $this->assertArrayHasKey( 'email_delivery', $tests['direct'] ); + $this->assertSame( 'email_delivery', $tests['direct']['email_delivery']['test'] ); + } + /** * Tests get_test_opcode_cache() return structure. * From 733bd2ac2828dbb1c70f00ad6d4c4e1407b37d8b Mon Sep 17 00:00:00 2001 From: Baljinder Singh Date: Sun, 16 Aug 2026 22:52:05 +0000 Subject: [PATCH 2/3] Address email delivery review feedback --- src/js/_enqueues/admin/site-health.js | 2 +- .../includes/class-wp-site-health.php | 106 +++++++------- src/wp-admin/options-general.php | 79 ---------- src/wp-admin/site-health.php | 57 ++++++++ tests/phpunit/tests/admin/wpSiteHealth.php | 137 ++++++++++++++++-- 5 files changed, 228 insertions(+), 153 deletions(-) diff --git a/src/js/_enqueues/admin/site-health.js b/src/js/_enqueues/admin/site-health.js index 998b3ae273059..57d5c9cbcf289 100644 --- a/src/js/_enqueues/admin/site-health.js +++ b/src/js/_enqueues/admin/site-health.js @@ -237,7 +237,7 @@ jQuery( function( $ ) { $circle.css( { strokeDashoffset: pct } ); - if ( 80 <= val && 0 === parseInt( SiteHealth.site_status.issues.critical, 0 ) && ! SiteHealth.site_status.force_improvable ) { + if ( 80 <= val && 0 === parseInt( SiteHealth.site_status.issues.critical, 0 ) ) { $wrapper.addClass( 'green' ).removeClass( 'orange' ); $progressLabel.text( __( 'Good' ) ); diff --git a/src/wp-admin/includes/class-wp-site-health.php b/src/wp-admin/includes/class-wp-site-health.php index a170df8b3a9e8..5e9d9c5a73e69 100644 --- a/src/wp-admin/includes/class-wp-site-health.php +++ b/src/wp-admin/includes/class-wp-site-health.php @@ -85,52 +85,20 @@ public static function get_instance() { } /** - * Handles a request to send an email delivery test. - * - * @since 7.1.0 - */ - public function handle_email_delivery_test() { - if ( ! current_user_can( 'manage_options' ) ) { - wp_die( - '

' . __( 'You need a higher level of permission.' ) . '

' . - '

' . __( 'Sorry, you are not allowed to manage options for this site.' ) . '

', - 403 - ); - } - - check_admin_referer( 'verify-email-delivery' ); - - $result = $this->send_email_delivery_test(); - $status = 'success'; - - if ( is_wp_error( $result ) ) { - $status = $result->get_error_code(); - } - - $redirect = add_query_arg( - 'email-delivery-test', - $status, - admin_url( 'options-general.php' ) - ); - - wp_safe_redirect( $redirect ); - exit; - } - - /** - * Sends a test email to the site administration email address. + * Sends a test email to the current user's email address. * * A successful result means that WordPress handed the message to the mail * system without an immediate error. It does not confirm delivery. * - * @since 7.1.0 + * @since 7.2.0 * * @return true|WP_Error True on success, or a WP_Error on failure. */ public function send_email_delivery_test() { - $email = get_option( 'admin_email' ); + $user = wp_get_current_user(); + $email = $user->user_email; - if ( ! is_email( $email ) ) { + if ( ! $user->exists() || ! is_email( $email ) ) { return new WP_Error( 'invalid-address' ); } @@ -151,7 +119,15 @@ public function send_email_delivery_test() { return new WP_Error( 'send-failed' ); } - update_option( 'email_delivery_last_tested', time(), false ); + update_option( + 'email_delivery_last_tested', + array( + 'timestamp' => time(), + 'user_id' => $user->ID, + 'email' => $email, + ), + false + ); return true; } @@ -159,24 +135,40 @@ public function send_email_delivery_test() { /** * Gets the timestamp of the most recent valid email delivery test. * - * @since 7.1.0 + * @since 7.2.0 * * @return int The test timestamp, or 0 if no valid timestamp exists. */ - public function get_email_delivery_last_tested() { + private function get_email_delivery_last_tested() { $last_tested = get_option( 'email_delivery_last_tested' ); - if ( ! is_numeric( $last_tested ) || 0 >= (int) $last_tested || (int) $last_tested > time() ) { + if ( + ! is_array( $last_tested ) || + ! isset( $last_tested['timestamp'], $last_tested['user_id'], $last_tested['email'] ) || + ! is_numeric( $last_tested['timestamp'] ) || + 0 >= (int) $last_tested['timestamp'] || + (int) $last_tested['timestamp'] > time() || + ! is_numeric( $last_tested['user_id'] ) || + 0 >= (int) $last_tested['user_id'] || + ! is_string( $last_tested['email'] ) || + ! is_email( $last_tested['email'] ) + ) { + return 0; + } + + $user = get_userdata( (int) $last_tested['user_id'] ); + + if ( ! $user || 0 !== strcasecmp( $user->user_email, $last_tested['email'] ) ) { return 0; } - return (int) $last_tested; + return (int) $last_tested['timestamp']; } /** * Determines whether the most recent email delivery test is current. * - * @since 7.1.0 + * @since 7.2.0 * * @return bool Whether email delivery was tested within the required period. */ @@ -202,10 +194,9 @@ public function enqueue_scripts() { 'site_status_result' => wp_create_nonce( 'health-check-site-status-result' ), ), 'site_status' => array( - 'direct' => array(), - 'async' => array(), - 'force_improvable' => ! $this->is_email_delivery_test_current(), - 'issues' => array( + 'direct' => array(), + 'async' => array(), + 'issues' => array( 'good' => 0, 'recommended' => 0, 'critical' => 0, @@ -2895,12 +2886,18 @@ public function get_test_search_engine_visibility() { /** * Tests whether email delivery has been tested recently. * - * @since 7.1.0 + * @since 7.2.0 * * @return array The test results. */ public function get_test_email_delivery() { $was_tested_recently = $this->is_email_delivery_test_current(); + $action = sprintf( + '%2$s', + esc_url( admin_url( 'site-health.php' ) ), + wp_nonce_field( 'verify-email-delivery', '_wpnonce', true, false ), + esc_html__( 'Verify email delivery' ) + ); $result = array( 'label' => __( 'Email delivery was recently tested' ), @@ -2910,10 +2907,10 @@ public function get_test_email_delivery() { 'color' => 'blue', ), 'description' => sprintf( - '

%s

', + '

%s

', __( 'WordPress recently handed a test email to the mail system without an immediate error. This does not guarantee that the message reached the inbox.' ) ), - 'actions' => '', + 'actions' => $action, 'test' => 'email_delivery', ); @@ -2921,13 +2918,8 @@ public function get_test_email_delivery() { $result['label'] = __( 'Email delivery should be tested' ); $result['status'] = 'recommended'; $result['description'] = sprintf( - '

%s

', - __( 'WordPress relies on email for important site administration messages. Send a test email, then confirm that it reaches the inbox and that its sender headers are correct.' ) - ); - $result['actions'] = sprintf( - '

%2$s

', - esc_url( admin_url( 'options-general.php' ) ), - __( 'Verify email delivery' ) + '

%s

', + __( 'WordPress relies on email for important site administration messages. Send a test email to your user account, then confirm that it reaches the inbox and that its sender headers are correct.' ) ); } diff --git a/src/wp-admin/options-general.php b/src/wp-admin/options-general.php index 71656756af8c5..6a9a6d2f3812d 100644 --- a/src/wp-admin/options-general.php +++ b/src/wp-admin/options-general.php @@ -16,13 +16,6 @@ wp_die( __( 'Sorry, you are not allowed to manage options for this site.' ) ); } -// The nonce is verified by WP_Site_Health::handle_email_delivery_test(). -// phpcs:ignore WordPress.Security.NonceVerification.Missing -if ( isset( $_POST['action'] ) && 'verify_email_delivery' === $_POST['action'] ) { - require_once ABSPATH . 'wp-admin/includes/class-wp-site-health.php'; - WP_Site_Health::get_instance()->handle_email_delivery_test(); -} - // Used in the HTML title tag. $title = __( 'General Settings' ); $parent_file = 'options-general.php'; @@ -74,50 +67,6 @@

-' . esc_html( get_option( 'admin_email' ) ) . '' - ), - array( - 'type' => 'success', - 'dismissible' => true, - ) - ); -} elseif ( 'invalid-address' === $email_delivery_test_status ) { - wp_admin_notice( - __( 'The Administration Email Address is not valid. Enter a valid address before testing email delivery.' ), - array( - 'type' => 'error', - 'dismissible' => true, - ) - ); -} elseif ( 'send-failed' === $email_delivery_test_status ) { - wp_admin_notice( - __( 'The test email could not be sent. Your site may not be correctly configured to send emails.' ), - array( - 'type' => 'error', - 'dismissible' => true, - ) - ); -} -?> - -
- - -
-
@@ -316,34 +265,6 @@ class=""

The new address will not become active until confirmed.' ); ?>

-

- -

-

- get_email_delivery_last_tested(); - if ( $email_delivery_last_tested ) { - echo ' '; - printf( - /* translators: %s: Date and time of the most recent email delivery test. */ - __( 'Last tested: %s.' ), - esc_html( - wp_date( - get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), - (int) $email_delivery_last_tested - ) - ) - ); - } - ?> -

send_email_delivery_test(); + $status = 'success'; + + if ( is_wp_error( $result ) ) { + $status = $result->get_error_code(); + } + + wp_safe_redirect( + add_query_arg( + 'email-delivery-test', + $status, + admin_url( 'site-health.php' ) + ) + ); + exit; +} + get_current_screen()->add_help_tab( array( 'id' => 'overview', @@ -124,6 +144,43 @@ ); } } + + $email_delivery_test_status = ''; + // The query parameter only selects a notice to display. + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( isset( $_GET['email-delivery-test'] ) && is_string( $_GET['email-delivery-test'] ) ) { + $email_delivery_test_status = sanitize_key( wp_unslash( $_GET['email-delivery-test'] ) ); + } + + if ( 'success' === $email_delivery_test_status ) { + wp_admin_notice( + sprintf( + /* translators: %s: Current user's email address. */ + __( 'WordPress accepted a request to send a test email to %s. Check that it arrived in your inbox and review its headers to confirm that the sender details are correct.' ), + '' . esc_html( wp_get_current_user()->user_email ) . '' + ), + array( + 'type' => 'success', + 'dismissible' => true, + ) + ); + } elseif ( 'invalid-address' === $email_delivery_test_status ) { + wp_admin_notice( + __( 'Your user account does not have a valid email address. Update it before testing email delivery.' ), + array( + 'type' => 'error', + 'dismissible' => true, + ) + ); + } elseif ( 'send-failed' === $email_delivery_test_status ) { + wp_admin_notice( + __( 'The test email could not be sent. Your site may not be correctly configured to send emails.' ), + array( + 'type' => 'error', + 'dismissible' => true, + ) + ); + } ?>
diff --git a/tests/phpunit/tests/admin/wpSiteHealth.php b/tests/phpunit/tests/admin/wpSiteHealth.php index 8e9bfb80cd61e..a642e9a849533 100644 --- a/tests/phpunit/tests/admin/wpSiteHealth.php +++ b/tests/phpunit/tests/admin/wpSiteHealth.php @@ -36,10 +36,29 @@ public function set_up() { */ public function tear_down() { delete_option( 'email_delivery_last_tested' ); + wp_set_current_user( 0 ); parent::tear_down(); } + /** + * Creates and sets the current user for an email delivery test. + * + * @param string $email User email address. + * @return WP_User The current user. + */ + private function set_email_delivery_test_user( $email = 'site-health@example.org' ) { + $user = self::factory()->user->create_and_get( + array( + 'role' => 'administrator', + 'user_email' => $email, + ) + ); + wp_set_current_user( $user->ID ); + + return $user; + } + /** * @ticket 55791 * @covers ::__construct() @@ -670,18 +689,27 @@ public static function set_autoloaded_option( $bytes = 800000 ) { * @param string $expected_status The expected Site Health status. */ public function test_email_delivery_status( $state, $expected_status ) { + $user = $this->set_email_delivery_test_user(); + $test_data = array( + 'timestamp' => time() - DAY_IN_SECONDS, + 'user_id' => $user->ID, + 'email' => $user->user_email, + ); + switch ( $state ) { case 'malformed': update_option( 'email_delivery_last_tested', 'not-a-timestamp', false ); break; case 'future': - update_option( 'email_delivery_last_tested', time() + HOUR_IN_SECONDS, false ); + $test_data['timestamp'] = time() + HOUR_IN_SECONDS; + update_option( 'email_delivery_last_tested', $test_data, false ); break; case 'expired': - update_option( 'email_delivery_last_tested', time() - ( 3 * MONTH_IN_SECONDS ), false ); + $test_data['timestamp'] = time() - ( 3 * MONTH_IN_SECONDS ); + update_option( 'email_delivery_last_tested', $test_data, false ); break; case 'recent': - update_option( 'email_delivery_last_tested', time() - DAY_IN_SECONDS, false ); + update_option( 'email_delivery_last_tested', $test_data, false ); break; } @@ -697,9 +725,8 @@ public function test_email_delivery_status( $state, $expected_status ) { $result['badge'] ); - if ( 'recommended' === $expected_status ) { - $this->assertStringNotContainsString( '#email-delivery-test', $result['actions'] ); - } + $this->assertStringContainsString( 'site-health.php', $result['actions'] ); + $this->assertStringNotContainsString( 'options-general.php', $result['actions'] ); } /** @@ -726,6 +753,7 @@ public function data_email_delivery_status() { public function test_send_email_delivery_test_success() { global $wpdb; + $user = $this->set_email_delivery_test_user(); $mail_data = null; $callback = static function ( $short_circuit, $atts ) use ( &$mail_data ) { $mail_data = $atts; @@ -738,10 +766,15 @@ public function test_send_email_delivery_test_success() { remove_filter( 'pre_wp_mail', $callback ); $this->assertTrue( $result ); - $this->assertSame( get_option( 'admin_email' ), $mail_data['to'] ); + $this->assertSame( $user->user_email, $mail_data['to'] ); $this->assertStringContainsString( get_option( 'blogname' ), $mail_data['subject'] ); $this->assertStringContainsString( home_url( '/' ), $mail_data['message'] ); - $this->assertGreaterThan( 0, $this->instance->get_email_delivery_last_tested() ); + + $test_data = get_option( 'email_delivery_last_tested' ); + $this->assertGreaterThan( 0, $test_data['timestamp'] ); + $this->assertSame( $user->ID, $test_data['user_id'] ); + $this->assertSame( $user->user_email, $test_data['email'] ); + $this->assertSame( 'good', $this->instance->get_test_email_delivery()['status'] ); $this->assertSame( 'off', $wpdb->get_var( @@ -760,6 +793,7 @@ public function test_send_email_delivery_test_success() { * @covers ::send_email_delivery_test() */ public function test_send_email_delivery_test_failure() { + $this->set_email_delivery_test_user(); add_filter( 'pre_wp_mail', '__return_false' ); $result = $this->instance->send_email_delivery_test(); @@ -772,26 +806,97 @@ public function test_send_email_delivery_test_failure() { } /** - * Tests an invalid administration email address. + * Tests a current user without a valid email address. * * @ticket 65891 * @covers ::send_email_delivery_test() */ public function test_send_email_delivery_test_invalid_address() { - $callback = static function () { - return 'not-an-email'; - }; - add_filter( 'pre_option_admin_email', $callback ); - $result = $this->instance->send_email_delivery_test(); - remove_filter( 'pre_option_admin_email', $callback ); - $this->assertWPError( $result ); $this->assertSame( 'invalid-address', $result->get_error_code() ); $this->assertFalse( get_option( 'email_delivery_last_tested', false ) ); } + /** + * Tests that changing the tested user's email invalidates the result. + * + * @ticket 65891 + * @covers ::get_email_delivery_last_tested() + * @covers ::get_test_email_delivery() + */ + public function test_email_delivery_status_is_invalid_after_user_email_changes() { + $user = $this->set_email_delivery_test_user( 'before-change@example.org' ); + update_option( + 'email_delivery_last_tested', + array( + 'timestamp' => time() - DAY_IN_SECONDS, + 'user_id' => $user->ID, + 'email' => $user->user_email, + ), + false + ); + + wp_update_user( + array( + 'ID' => $user->ID, + 'user_email' => 'after-change@example.org', + ) + ); + + $this->assertSame( 'recommended', $this->instance->get_test_email_delivery()['status'] ); + } + + /** + * Tests that deleting the tested user invalidates the result. + * + * @ticket 65891 + * @covers ::get_email_delivery_last_tested() + * @covers ::get_test_email_delivery() + */ + public function test_email_delivery_status_is_invalid_after_user_is_deleted() { + $user = $this->set_email_delivery_test_user(); + update_option( + 'email_delivery_last_tested', + array( + 'timestamp' => time() - DAY_IN_SECONDS, + 'user_id' => $user->ID, + 'email' => $user->user_email, + ), + false + ); + + wp_delete_user( $user->ID ); + wp_set_current_user( 0 ); + + $this->assertSame( 'recommended', $this->instance->get_test_email_delivery()['status'] ); + } + + /** + * Tests that a recent email delivery result applies site-wide. + * + * @ticket 65891 + * @covers ::get_email_delivery_last_tested() + * @covers ::get_test_email_delivery() + */ + public function test_email_delivery_status_applies_to_other_administrators() { + $user = $this->set_email_delivery_test_user(); + update_option( + 'email_delivery_last_tested', + array( + 'timestamp' => time() - DAY_IN_SECONDS, + 'user_id' => $user->ID, + 'email' => $user->user_email, + ), + false + ); + + $this->set_email_delivery_test_user( 'another-admin@example.org' ); + + $this->assertSame( 'good', $this->instance->get_test_email_delivery()['status'] ); + } + /** * Tests that the email delivery check is registered as a direct test. * From 729d6e0d49c255504f8346ecd53e71494a70b246 Mon Sep 17 00:00:00 2001 From: Baljinder Singh Date: Sun, 16 Aug 2026 23:25:33 +0000 Subject: [PATCH 3/3] Tests: Correct email delivery user deletion on multisite --- tests/phpunit/tests/admin/wpSiteHealth.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/phpunit/tests/admin/wpSiteHealth.php b/tests/phpunit/tests/admin/wpSiteHealth.php index a642e9a849533..89df631418695 100644 --- a/tests/phpunit/tests/admin/wpSiteHealth.php +++ b/tests/phpunit/tests/admin/wpSiteHealth.php @@ -867,7 +867,11 @@ public function test_email_delivery_status_is_invalid_after_user_is_deleted() { false ); - wp_delete_user( $user->ID ); + if ( is_multisite() ) { + wpmu_delete_user( $user->ID ); + } else { + wp_delete_user( $user->ID ); + } wp_set_current_user( 0 ); $this->assertSame( 'recommended', $this->instance->get_test_email_delivery()['status'] );