diff --git a/tests/phpunit/tests/admin/includes/ajax-actions/healthCheckBackgroundUpdates.php b/tests/phpunit/tests/admin/includes/ajax-actions/healthCheckBackgroundUpdates.php new file mode 100644 index 0000000000000..2ceb7a175283f --- /dev/null +++ b/tests/phpunit/tests/admin/includes/ajax-actions/healthCheckBackgroundUpdates.php @@ -0,0 +1,147 @@ +user->create( array( 'role' => 'administrator' ) ); + + if ( is_multisite() ) { + grant_super_admin( self::$admin_id ); + } + } + + public function set_up(): void { + parent::set_up(); + add_action( 'wp_ajax_health-check-background-updates', 'wp_ajax_health_check_background_updates', 1 ); + + // Hook into wp_die to prevent execution from stopping. + add_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ) ); + } + + public function tear_down(): void { + remove_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ) ); + parent::tear_down(); + } + + /** + * Returns our custom die handler. + * + * @return callable + */ + public function getDieHandler() { + return array( $this, 'dieHandler' ); + } + + /** + * Custom die handler that throws an exception. + * + * @param string|WP_Error $message + */ + public function dieHandler( $message ) { + $this->_last_response .= ob_get_clean(); + + if ( '' === $this->_last_response ) { + if ( is_scalar( $message ) ) { + $this->_last_response = (string) $message; + } else { + $this->_last_response = '0'; + } + } + + if ( '-1' === $this->_last_response || ( is_int( $message ) && -1 === $message ) ) { + throw new WPAjaxDieStopException( $this->_last_response ); + } + + throw new WPAjaxDieContinueException( $this->_last_response ); + } + + /** + * Tests success for wp_ajax_health_check_background_updates(). + * + * @ticket 65252 + * @expectedIncorrectUsage wp_ajax_health_check_background_updates + */ + public function test_health_check_background_updates_success(): void { + wp_set_current_user( self::$admin_id ); + + $_POST['_ajax_nonce'] = wp_create_nonce( 'health-check-site-status' ); + + try { + $this->_handleAjax( 'health-check-background-updates' ); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + $this->assertTrue( $response['success'], 'AJAX response should be successful' ); + $this->assertArrayHasKey( 'label', $response['data'], 'Response data should contain a label' ); + $this->assertArrayHasKey( 'status', $response['data'], 'Response data should contain a status' ); + } + + /** + * Tests failure with invalid nonce for wp_ajax_health_check_background_updates(). + * + * @ticket 65252 + * @expectedIncorrectUsage wp_ajax_health_check_background_updates + */ + public function test_health_check_background_updates_invalid_nonce(): void { + wp_set_current_user( self::$admin_id ); + + $_POST['_ajax_nonce'] = 'invalid-nonce'; + + $this->expectException( WPAjaxDieStopException::class ); + $this->expectExceptionMessage( '-1' ); + + $this->_handleAjax( 'health-check-background-updates' ); + } + + /** + * Tests failure with insufficient permissions for wp_ajax_health_check_background_updates(). + * + * @ticket 65252 + * @expectedIncorrectUsage wp_ajax_health_check_background_updates + */ + public function test_health_check_background_updates_insufficient_permissions(): void { + $subscriber_id = self::factory()->user->create( array( 'role' => 'subscriber' ) ); + wp_set_current_user( $subscriber_id ); + + $_POST['_ajax_nonce'] = wp_create_nonce( 'health-check-site-status' ); + + try { + $this->_handleAjax( 'health-check-background-updates' ); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + $this->assertFalse( $response['success'], 'AJAX response should be unsuccessful' ); + } +} diff --git a/tests/phpunit/tests/admin/includes/ajax-actions/healthCheckDotorgCommunication.php b/tests/phpunit/tests/admin/includes/ajax-actions/healthCheckDotorgCommunication.php new file mode 100644 index 0000000000000..443b096c8e32f --- /dev/null +++ b/tests/phpunit/tests/admin/includes/ajax-actions/healthCheckDotorgCommunication.php @@ -0,0 +1,147 @@ +user->create( array( 'role' => 'administrator' ) ); + + if ( is_multisite() ) { + grant_super_admin( self::$admin_id ); + } + } + + public function set_up(): void { + parent::set_up(); + add_action( 'wp_ajax_health-check-dotorg-communication', 'wp_ajax_health_check_dotorg_communication', 1 ); + + // Hook into wp_die to prevent execution from stopping. + add_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ) ); + } + + public function tear_down(): void { + remove_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ) ); + parent::tear_down(); + } + + /** + * Returns our custom die handler. + * + * @return callable + */ + public function getDieHandler() { + return array( $this, 'dieHandler' ); + } + + /** + * Custom die handler that throws an exception. + * + * @param string|WP_Error $message + */ + public function dieHandler( $message ) { + $this->_last_response .= ob_get_clean(); + + if ( '' === $this->_last_response ) { + if ( is_scalar( $message ) ) { + $this->_last_response = (string) $message; + } else { + $this->_last_response = '0'; + } + } + + if ( '-1' === $this->_last_response || ( is_int( $message ) && -1 === $message ) ) { + throw new WPAjaxDieStopException( $this->_last_response ); + } + + throw new WPAjaxDieContinueException( $this->_last_response ); + } + + /** + * Tests success for wp_ajax_health_check_dotorg_communication(). + * + * @ticket 65252 + * @expectedIncorrectUsage wp_ajax_health_check_dotorg_communication + */ + public function test_health_check_dotorg_communication_success(): void { + wp_set_current_user( self::$admin_id ); + + $_POST['_ajax_nonce'] = wp_create_nonce( 'health-check-site-status' ); + + try { + $this->_handleAjax( 'health-check-dotorg-communication' ); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + $this->assertTrue( $response['success'], 'AJAX response should be successful' ); + $this->assertArrayHasKey( 'label', $response['data'], 'Response data should contain a label' ); + $this->assertArrayHasKey( 'status', $response['data'], 'Response data should contain a status' ); + } + + /** + * Tests failure with invalid nonce for wp_ajax_health_check_dotorg_communication(). + * + * @ticket 65252 + * @expectedIncorrectUsage wp_ajax_health_check_dotorg_communication + */ + public function test_health_check_dotorg_communication_invalid_nonce(): void { + wp_set_current_user( self::$admin_id ); + + $_POST['_ajax_nonce'] = 'invalid-nonce'; + + $this->expectException( WPAjaxDieStopException::class ); + $this->expectExceptionMessage( '-1' ); + + $this->_handleAjax( 'health-check-dotorg-communication' ); + } + + /** + * Tests failure with insufficient permissions for wp_ajax_health_check_dotorg_communication(). + * + * @ticket 65252 + * @expectedIncorrectUsage wp_ajax_health_check_dotorg_communication + */ + public function test_health_check_dotorg_communication_insufficient_permissions(): void { + $subscriber_id = self::factory()->user->create( array( 'role' => 'subscriber' ) ); + wp_set_current_user( $subscriber_id ); + + $_POST['_ajax_nonce'] = wp_create_nonce( 'health-check-site-status' ); + + try { + $this->_handleAjax( 'health-check-dotorg-communication' ); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + $this->assertFalse( $response['success'], 'AJAX response should be unsuccessful' ); + } +} diff --git a/tests/phpunit/tests/admin/includes/ajax-actions/healthCheckGetSizes.php b/tests/phpunit/tests/admin/includes/ajax-actions/healthCheckGetSizes.php new file mode 100644 index 0000000000000..89f18c2ec21d3 --- /dev/null +++ b/tests/phpunit/tests/admin/includes/ajax-actions/healthCheckGetSizes.php @@ -0,0 +1,155 @@ +user->create( array( 'role' => 'administrator' ) ); + + if ( is_multisite() ) { + grant_super_admin( self::$admin_id ); + } + } + + public function set_up(): void { + parent::set_up(); + add_action( 'wp_ajax_health-check-get-sizes', 'wp_ajax_health_check_get_sizes', 1 ); + + // Hook into wp_die to prevent execution from stopping. + add_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ) ); + } + + public function tear_down(): void { + remove_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ) ); + parent::tear_down(); + } + + /** + * Returns our custom die handler. + * + * @return callable + */ + public function getDieHandler() { + return array( $this, 'dieHandler' ); + } + + /** + * Custom die handler that throws an exception. + * + * @param string|WP_Error $message + */ + public function dieHandler( $message ) { + $this->_last_response .= ob_get_clean(); + + if ( '' === $this->_last_response ) { + if ( is_scalar( $message ) ) { + $this->_last_response = (string) $message; + } else { + $this->_last_response = '0'; + } + } + + if ( '-1' === $this->_last_response || ( is_int( $message ) && -1 === $message ) ) { + throw new WPAjaxDieStopException( $this->_last_response ); + } + + throw new WPAjaxDieContinueException( $this->_last_response ); + } + + /** + * Tests success for wp_ajax_health_check_get_sizes(). + * + * @ticket 65252 + * @expectedIncorrectUsage wp_ajax_health_check_get_sizes + * @expectedDeprecated WP_Debug_Data::get_sizes + */ + public function test_health_check_get_sizes_success(): void { + if ( is_multisite() ) { + $this->markTestSkipped( 'The get_sizes health check is not available on multisite.' ); + } + + wp_set_current_user( self::$admin_id ); + + $_POST['_ajax_nonce'] = wp_create_nonce( 'health-check-site-status-result' ); + + try { + $this->_handleAjax( 'health-check-get-sizes' ); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + if ( isset( $response['data']['total_size']['debug'] ) && 'not available' === $response['data']['total_size']['debug'] ) { + $this->assertFalse( $response['success'], 'AJAX response should be unsuccessful when sizes are not available' ); + } else { + $this->assertTrue( $response['success'], 'AJAX response should be successful' ); + } + $this->assertIsArray( $response['data'], 'Response data should be an array' ); + } + + /** + * Tests failure with invalid nonce for wp_ajax_health_check_get_sizes(). + * + * @ticket 65252 + * @expectedIncorrectUsage wp_ajax_health_check_get_sizes + */ + public function test_health_check_get_sizes_invalid_nonce(): void { + wp_set_current_user( self::$admin_id ); + + $_POST['_ajax_nonce'] = 'invalid-nonce'; + + $this->expectException( WPAjaxDieStopException::class ); + $this->expectExceptionMessage( '-1' ); + + $this->_handleAjax( 'health-check-get-sizes' ); + } + + /** + * Tests failure with insufficient permissions for wp_ajax_health_check_get_sizes(). + * + * @ticket 65252 + * @expectedIncorrectUsage wp_ajax_health_check_get_sizes + */ + public function test_health_check_get_sizes_insufficient_permissions(): void { + $subscriber_id = self::factory()->user->create( array( 'role' => 'subscriber' ) ); + wp_set_current_user( $subscriber_id ); + + $_POST['_ajax_nonce'] = wp_create_nonce( 'health-check-site-status-result' ); + + try { + $this->_handleAjax( 'health-check-get-sizes' ); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + $this->assertFalse( $response['success'], 'AJAX response should be unsuccessful' ); + } +} diff --git a/tests/phpunit/tests/admin/includes/ajax-actions/healthCheckLoopbackRequests.php b/tests/phpunit/tests/admin/includes/ajax-actions/healthCheckLoopbackRequests.php new file mode 100644 index 0000000000000..73275a2fef784 --- /dev/null +++ b/tests/phpunit/tests/admin/includes/ajax-actions/healthCheckLoopbackRequests.php @@ -0,0 +1,147 @@ +user->create( array( 'role' => 'administrator' ) ); + + if ( is_multisite() ) { + grant_super_admin( self::$admin_id ); + } + } + + public function set_up(): void { + parent::set_up(); + add_action( 'wp_ajax_health-check-loopback-requests', 'wp_ajax_health_check_loopback_requests', 1 ); + + // Hook into wp_die to prevent execution from stopping. + add_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ) ); + } + + public function tear_down(): void { + remove_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ) ); + parent::tear_down(); + } + + /** + * Returns our custom die handler. + * + * @return callable + */ + public function getDieHandler() { + return array( $this, 'dieHandler' ); + } + + /** + * Custom die handler that throws an exception. + * + * @param string|WP_Error $message + */ + public function dieHandler( $message ) { + $this->_last_response .= ob_get_clean(); + + if ( '' === $this->_last_response ) { + if ( is_scalar( $message ) ) { + $this->_last_response = (string) $message; + } else { + $this->_last_response = '0'; + } + } + + if ( '-1' === $this->_last_response || ( is_int( $message ) && -1 === $message ) ) { + throw new WPAjaxDieStopException( $this->_last_response ); + } + + throw new WPAjaxDieContinueException( $this->_last_response ); + } + + /** + * Tests success for wp_ajax_health_check_loopback_requests(). + * + * @ticket 65252 + * @expectedIncorrectUsage wp_ajax_health_check_loopback_requests + */ + public function test_health_check_loopback_requests_success(): void { + wp_set_current_user( self::$admin_id ); + + $_POST['_ajax_nonce'] = wp_create_nonce( 'health-check-site-status' ); + + try { + $this->_handleAjax( 'health-check-loopback-requests' ); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + $this->assertTrue( $response['success'], 'AJAX response should be successful' ); + $this->assertArrayHasKey( 'label', $response['data'], 'Response data should contain a label' ); + $this->assertArrayHasKey( 'status', $response['data'], 'Response data should contain a status' ); + } + + /** + * Tests failure with invalid nonce for wp_ajax_health_check_loopback_requests(). + * + * @ticket 65252 + * @expectedIncorrectUsage wp_ajax_health_check_loopback_requests + */ + public function test_health_check_loopback_requests_invalid_nonce(): void { + wp_set_current_user( self::$admin_id ); + + $_POST['_ajax_nonce'] = 'invalid-nonce'; + + $this->expectException( WPAjaxDieStopException::class ); + $this->expectExceptionMessage( '-1' ); + + $this->_handleAjax( 'health-check-loopback-requests' ); + } + + /** + * Tests failure with insufficient permissions for wp_ajax_health_check_loopback_requests(). + * + * @ticket 65252 + * @expectedIncorrectUsage wp_ajax_health_check_loopback_requests + */ + public function test_health_check_loopback_requests_insufficient_permissions(): void { + $subscriber_id = self::factory()->user->create( array( 'role' => 'subscriber' ) ); + wp_set_current_user( $subscriber_id ); + + $_POST['_ajax_nonce'] = wp_create_nonce( 'health-check-site-status' ); + + try { + $this->_handleAjax( 'health-check-loopback-requests' ); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + $this->assertFalse( $response['success'], 'AJAX response should be unsuccessful' ); + } +} diff --git a/tests/phpunit/tests/admin/includes/ajax-actions/healthCheckSiteStatusResult.php b/tests/phpunit/tests/admin/includes/ajax-actions/healthCheckSiteStatusResult.php new file mode 100644 index 0000000000000..ab563ed125e2a --- /dev/null +++ b/tests/phpunit/tests/admin/includes/ajax-actions/healthCheckSiteStatusResult.php @@ -0,0 +1,151 @@ +user->create( array( 'role' => 'administrator' ) ); + + if ( is_multisite() ) { + grant_super_admin( self::$admin_id ); + } + } + + public function set_up(): void { + parent::set_up(); + add_action( 'wp_ajax_health-check-site-status-result', 'wp_ajax_health_check_site_status_result', 1 ); + + // Hook into wp_die to prevent execution from stopping. + add_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ) ); + } + + public function tear_down(): void { + remove_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ) ); + parent::tear_down(); + } + + /** + * Returns our custom die handler. + * + * @return callable + */ + public function getDieHandler() { + return array( $this, 'dieHandler' ); + } + + /** + * Custom die handler that throws an exception. + * + * @param string|WP_Error $message + */ + public function dieHandler( $message ) { + $this->_last_response .= ob_get_clean(); + + if ( '' === $this->_last_response ) { + if ( is_scalar( $message ) ) { + $this->_last_response = (string) $message; + } else { + $this->_last_response = '0'; + } + } + + if ( '-1' === $this->_last_response || ( is_int( $message ) && -1 === $message ) ) { + throw new WPAjaxDieStopException( $this->_last_response ); + } + + throw new WPAjaxDieContinueException( $this->_last_response ); + } + + /** + * Tests success for wp_ajax_health_check_site_status_result(). + * + * @ticket 65252 + */ + public function test_health_check_site_status_result_success(): void { + wp_set_current_user( self::$admin_id ); + + $_POST['_ajax_nonce'] = wp_create_nonce( 'health-check-site-status-result' ); + $_POST['counts'] = array( + 'good' => 1, + 'recommended' => 2, + 'critical' => 3, + ); + + try { + $this->_handleAjax( 'health-check-site-status-result' ); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + $this->assertTrue( $response['success'], 'AJAX response should be successful' ); + + $transient = get_transient( 'health-check-site-status-result' ); + $this->assertNotFalse( $transient, 'Transient should be set' ); + $this->assertEquals( wp_json_encode( $_POST['counts'] ), $transient, 'Transient value should match POST data' ); + } + + /** + * Tests failure with invalid nonce for wp_ajax_health_check_site_status_result(). + * + * @ticket 65252 + */ + public function test_health_check_site_status_result_invalid_nonce(): void { + wp_set_current_user( self::$admin_id ); + + $_POST['_ajax_nonce'] = 'invalid-nonce'; + + $this->expectException( WPAjaxDieStopException::class ); + $this->expectExceptionMessage( '-1' ); + + $this->_handleAjax( 'health-check-site-status-result' ); + } + + /** + * Tests failure with insufficient permissions for wp_ajax_health_check_site_status_result(). + * + * @ticket 65252 + */ + public function test_health_check_site_status_result_insufficient_permissions(): void { + $subscriber_id = self::factory()->user->create( array( 'role' => 'subscriber' ) ); + wp_set_current_user( $subscriber_id ); + + $_POST['_ajax_nonce'] = wp_create_nonce( 'health-check-site-status-result' ); + + try { + $this->_handleAjax( 'health-check-site-status-result' ); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + $this->assertFalse( $response['success'], 'AJAX response should be unsuccessful' ); + } +} diff --git a/tests/phpunit/tests/admin/includes/ajax-actions/sendPasswordReset.php b/tests/phpunit/tests/admin/includes/ajax-actions/sendPasswordReset.php new file mode 100644 index 0000000000000..fa862135f6177 --- /dev/null +++ b/tests/phpunit/tests/admin/includes/ajax-actions/sendPasswordReset.php @@ -0,0 +1,164 @@ +user->create( array( 'role' => 'administrator' ) ); + self::$user_id = $factory->user->create( array( 'role' => 'subscriber' ) ); + + if ( is_multisite() ) { + grant_super_admin( self::$admin_id ); + } + } + + public function set_up(): void { + parent::set_up(); + add_action( 'wp_ajax_send-password-reset', 'wp_ajax_send_password_reset', 1 ); + + // Hook into wp_die to prevent execution from stopping. + add_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ) ); + + // Clear mock email. + reset_phpmailer_instance(); + } + + public function tear_down(): void { + remove_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ) ); + parent::tear_down(); + } + + /** + * Returns our custom die handler. + * + * @return callable + */ + public function getDieHandler() { + return array( $this, 'dieHandler' ); + } + + /** + * Custom die handler that throws an exception. + * + * @param string|WP_Error $message + */ + public function dieHandler( $message ) { + $this->_last_response .= ob_get_clean(); + + if ( '' === $this->_last_response ) { + if ( is_scalar( $message ) ) { + $this->_last_response = (string) $message; + } else { + $this->_last_response = '0'; + } + } + + if ( '-1' === $this->_last_response || ( is_int( $message ) && -1 === $message ) ) { + throw new WPAjaxDieStopException( $this->_last_response ); + } + + throw new WPAjaxDieContinueException( $this->_last_response ); + } + + /** + * Tests success for sending a password reset email. + * + * @ticket 65252 + */ + public function test_send_password_reset_success(): void { + wp_set_current_user( self::$admin_id ); + + $_POST['user_id'] = self::$user_id; + $_POST['nonce'] = wp_create_nonce( 'reset-password-for-' . self::$user_id ); + + try { + $this->_handleAjax( 'send-password-reset' ); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + $user = get_userdata( self::$user_id ); + + $this->assertTrue( $response['success'], 'AJAX response should be successful' ); + $this->assertStringContainsString( sprintf( 'A password reset link was emailed to %s.', $user->display_name ), $response['data'] ); + + // Verify email was sent. + $mailer = tests_retrieve_phpmailer_instance(); + $this->assertStringContainsString( 'Password Reset', $mailer->get_sent()->subject ); + $this->assertEquals( $user->user_email, $mailer->get_sent()->to[0][0] ); + } + + /** + * Tests failure with invalid nonce for wp_ajax_send_password_reset(). + * + * @ticket 65252 + */ + public function test_send_password_reset_invalid_nonce(): void { + wp_set_current_user( self::$admin_id ); + + $_POST['user_id'] = self::$user_id; + $_POST['nonce'] = 'invalid-nonce'; + + $this->expectException( WPAjaxDieStopException::class ); + $this->expectExceptionMessage( '-1' ); + + $this->_handleAjax( 'send-password-reset' ); + } + + /** + * Tests failure with insufficient permissions for wp_ajax_send_password_reset(). + * + * @ticket 65252 + */ + public function test_send_password_reset_insufficient_permissions(): void { + $subscriber_id = self::factory()->user->create( array( 'role' => 'subscriber' ) ); + wp_set_current_user( $subscriber_id ); + + $_POST['user_id'] = self::$admin_id; + $_POST['nonce'] = wp_create_nonce( 'reset-password-for-' . self::$admin_id ); + + try { + $this->_handleAjax( 'send-password-reset' ); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + $this->assertFalse( $response['success'], 'AJAX response should be unsuccessful' ); + $this->assertEquals( 'Cannot send password reset, permission denied.', $response['data'] ); + } +} diff --git a/tests/phpunit/tests/admin/includes/ajax-actions/toggleAutoUpdates.php b/tests/phpunit/tests/admin/includes/ajax-actions/toggleAutoUpdates.php new file mode 100644 index 0000000000000..ad722ac6c32a0 --- /dev/null +++ b/tests/phpunit/tests/admin/includes/ajax-actions/toggleAutoUpdates.php @@ -0,0 +1,211 @@ +user->create( array( 'role' => 'administrator' ) ); + + if ( is_multisite() ) { + grant_super_admin( self::$admin_id ); + } + } + + public function set_up(): void { + parent::set_up(); + add_action( 'wp_ajax_toggle-auto-updates', 'wp_ajax_toggle_auto_updates', 1 ); + + // Hook into wp_die to prevent execution from stopping. + add_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ) ); + } + + public function tear_down(): void { + remove_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ) ); + parent::tear_down(); + } + + /** + * Returns our custom die handler. + * + * @return callable + */ + public function getDieHandler() { + return array( $this, 'dieHandler' ); + } + + /** + * Custom die handler that throws an exception. + * + * @param string|WP_Error $message + */ + public function dieHandler( $message ) { + $this->_last_response .= ob_get_clean(); + + if ( '' === $this->_last_response ) { + if ( is_scalar( $message ) ) { + $this->_last_response = (string) $message; + } else { + $this->_last_response = '0'; + } + } + + if ( '-1' === $this->_last_response || ( is_int( $message ) && -1 === $message ) ) { + throw new WPAjaxDieStopException( $this->_last_response ); + } + + throw new WPAjaxDieContinueException( $this->_last_response ); + } + + /** + * Tests success for toggling plugin auto-updates. + * + * @ticket 65252 + */ + public function test_toggle_auto_updates_plugin_success(): void { + wp_set_current_user( self::$admin_id ); + + $plugin = 'hello.php'; // Standard WP plugin. + $_POST['_ajax_nonce'] = wp_create_nonce( 'updates' ); + $_POST['type'] = 'plugin'; + $_POST['asset'] = $plugin; + $_POST['state'] = 'enable'; + + try { + $this->_handleAjax( 'toggle-auto-updates' ); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + $this->assertTrue( $response['success'], 'AJAX response should be successful' ); + $this->assertContains( $plugin, get_site_option( 'auto_update_plugins', array() ), 'Plugin should be in auto-update list' ); + + // Now disable it. + $this->_last_response = ''; + $_POST['state'] = 'disable'; + + try { + $this->_handleAjax( 'toggle-auto-updates' ); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + $this->assertTrue( $response['success'], 'AJAX response should be successful' ); + $this->assertNotContains( $plugin, get_site_option( 'auto_update_plugins', array() ), 'Plugin should not be in auto-update list' ); + } + + /** + * Tests success for toggling theme auto-updates. + * + * @ticket 65252 + */ + public function test_toggle_auto_updates_theme_success(): void { + wp_set_current_user( self::$admin_id ); + + $theme = 'twentytwentyone'; + $_POST['_ajax_nonce'] = wp_create_nonce( 'updates' ); + $_POST['type'] = 'theme'; + $_POST['asset'] = $theme; + $_POST['state'] = 'enable'; + + try { + $this->_handleAjax( 'toggle-auto-updates' ); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + $this->assertTrue( $response['success'], 'AJAX response should be successful' ); + $this->assertContains( $theme, get_site_option( 'auto_update_themes', array() ), 'Theme should be in auto-update list' ); + } + + /** + * Tests failure with invalid nonce for wp_ajax_toggle_auto_updates(). + * + * @ticket 65252 + */ + public function test_toggle_auto_updates_invalid_nonce(): void { + wp_set_current_user( self::$admin_id ); + + $_POST['_ajax_nonce'] = 'invalid-nonce'; + + $this->expectException( WPAjaxDieStopException::class ); + $this->expectExceptionMessage( '-1' ); + + $this->_handleAjax( 'toggle-auto-updates' ); + } + + /** + * Tests failure with insufficient permissions for wp_ajax_toggle_auto_updates(). + * + * @ticket 65252 + */ + public function test_toggle_auto_updates_insufficient_permissions(): void { + $subscriber_id = self::factory()->user->create( array( 'role' => 'subscriber' ) ); + wp_set_current_user( $subscriber_id ); + + $_POST['_ajax_nonce'] = wp_create_nonce( 'updates' ); + $_POST['type'] = 'plugin'; + $_POST['asset'] = 'hello.php'; + $_POST['state'] = 'enable'; + + try { + $this->_handleAjax( 'toggle-auto-updates' ); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + $this->assertFalse( $response['success'], 'AJAX response should be unsuccessful' ); + $this->assertEquals( 'Sorry, you are not allowed to modify plugins.', $response['data']['error'] ); + } + + /** + * Tests failure with missing parameters for wp_ajax_toggle_auto_updates(). + * + * @ticket 65252 + */ + public function test_toggle_auto_updates_missing_parameters(): void { + wp_set_current_user( self::$admin_id ); + + $_POST['_ajax_nonce'] = wp_create_nonce( 'updates' ); + // Missing 'type', 'asset', 'state'. + + try { + $this->_handleAjax( 'toggle-auto-updates' ); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + $this->assertFalse( $response['success'], 'AJAX response should be unsuccessful' ); + $this->assertEquals( 'Invalid data. No selected item.', $response['data']['error'] ); + } +}