From c9c4d20a1a6a02b54aed381f3308428685a70d49 Mon Sep 17 00:00:00 2001 From: Anton Vlasenko Date: Tue, 17 Dec 2024 19:37:28 +0100 Subject: [PATCH] Move the check for HEAD requests to ::prepare_item_for_response() method to preserve BC. --- .../class-wp-rest-comments-controller.php | 11 +++---- .../class-wp-rest-post-types-controller.php | 10 ++++--- .../class-wp-rest-posts-controller.php | 14 +++++---- .../class-wp-rest-taxonomies-controller.php | 10 ++++--- .../class-wp-rest-terms-controller.php | 10 ++++--- .../class-wp-rest-users-controller.php | 10 ++++--- .../rest-api/rest-categories-controller.php | 25 +++++++++++++--- .../rest-api/rest-comments-controller.php | 24 +++++++++++++-- .../rest-api/rest-post-types-controller.php | 26 +++++++++++++--- .../tests/rest-api/rest-posts-controller.php | 29 +++++++++++++----- .../tests/rest-api/rest-tags-controller.php | 25 +++++++++++++--- .../rest-api/rest-taxonomies-controller.php | 23 ++++++++++++-- .../tests/rest-api/rest-users-controller.php | 30 ++++++++++++++----- 13 files changed, 188 insertions(+), 59 deletions(-) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php index 91a77bd51e04c..7ecf669803fa4 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php @@ -437,11 +437,6 @@ public function get_item( $request ) { return $comment; } - if ( $request->is_method( 'HEAD' ) ) { - // Don't prepare response body for HEAD requests. - return new WP_REST_Response(); - } - $data = $this->prepare_item_for_response( $comment, $request ); $response = rest_ensure_response( $data ); @@ -1056,6 +1051,12 @@ public function prepare_item_for_response( $item, $request ) { // Restores the more descriptive, specific name for use within this method. $comment = $item; + // Don't prepare the response body for HEAD requests. + if ( $request->is_method( 'HEAD' ) ) { + /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php */ + return apply_filters( 'rest_prepare_comment', new WP_REST_Response(), $comment, $request ); + } + $fields = $this->get_fields_for_response( $request ); $data = array(); diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php index bc4d5e8bd18bb..6ead90af59d38 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php @@ -164,10 +164,6 @@ public function get_item( $request ) { ); } - if ( $request->is_method( 'HEAD' ) ) { - return new WP_REST_Response(); - } - $data = $this->prepare_item_for_response( $obj, $request ); return rest_ensure_response( $data ); @@ -187,6 +183,12 @@ public function prepare_item_for_response( $item, $request ) { // Restores the more descriptive, specific name for use within this method. $post_type = $item; + // Don't prepare the response body for HEAD requests. + if ( $request->is_method( 'HEAD' ) ) { + /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php */ + return apply_filters( 'rest_prepare_post_type', new WP_REST_Response(), $post_type, $request ); + } + $taxonomies = wp_list_filter( get_object_taxonomies( $post_type->name, 'objects' ), array( 'show_in_rest' => true ) ); $taxonomies = wp_list_pluck( $taxonomies, 'name' ); $base = ! empty( $post_type->rest_base ) ? $post_type->rest_base : $post_type->name; diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php index 188a05018c148..8c8e793aec592 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php @@ -643,12 +643,8 @@ public function get_item( $request ) { return $post; } - if ( $request->is_method( 'HEAD' ) ) { - $response = new WP_REST_Response(); - } else { - $data = $this->prepare_item_for_response( $post, $request ); - $response = rest_ensure_response( $data ); - } + $data = $this->prepare_item_for_response( $post, $request ); + $response = rest_ensure_response( $data ); if ( is_post_type_viewable( get_post_type_object( $post->post_type ) ) ) { $response->link_header( 'alternate', get_permalink( $post->ID ), array( 'type' => 'text/html' ) ); @@ -1839,6 +1835,12 @@ public function prepare_item_for_response( $item, $request ) { setup_postdata( $post ); + // Don't prepare the response body for HEAD requests. + if ( $request->is_method( 'HEAD' ) ) { + /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */ + return apply_filters( "rest_prepare_{$this->post_type}", new WP_REST_Response(), $post, $request ); + } + $fields = $this->get_fields_for_response( $request ); // Base fields for every post. diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php index 7052b2f39bc4c..b7492c815061b 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php @@ -195,10 +195,6 @@ public function get_item( $request ) { ); } - if ( $request->is_method( 'HEAD' ) ) { - return new WP_REST_Response(); - } - $data = $this->prepare_item_for_response( $tax_obj, $request ); return rest_ensure_response( $data ); @@ -218,6 +214,12 @@ public function prepare_item_for_response( $item, $request ) { // Restores the more descriptive, specific name for use within this method. $taxonomy = $item; + // Don't prepare the response body for HEAD requests. + if ( $request->is_method( 'HEAD' ) ) { + /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php */ + return apply_filters( 'rest_prepare_taxonomy', new WP_REST_Response(), $taxonomy, $request ); + } + $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; $fields = $this->get_fields_for_response( $request ); diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php index 1fa99be6036b9..d927ba5e04c34 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php @@ -477,10 +477,6 @@ public function get_item( $request ) { return $term; } - if ( $request->is_method( 'HEAD' ) ) { - return new WP_REST_Response(); - } - $response = $this->prepare_item_for_response( $term, $request ); return rest_ensure_response( $response ); @@ -900,6 +896,12 @@ public function prepare_item_for_database( $request ) { */ public function prepare_item_for_response( $item, $request ) { + // Don't prepare the response body for HEAD requests. + if ( $request->is_method( 'HEAD' ) ) { + /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */ + return apply_filters( "rest_prepare_{$this->taxonomy}", new WP_REST_Response(), $item, $request ); + } + $fields = $this->get_fields_for_response( $request ); $data = array(); diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php index 4d52a243b2eda..39705e92aa12a 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php @@ -488,10 +488,6 @@ public function get_item( $request ) { return $user; } - if ( $request->is_method( 'HEAD' ) ) { - return new WP_REST_Response(); - } - $user = $this->prepare_item_for_response( $user, $request ); $response = rest_ensure_response( $user ); @@ -1011,6 +1007,12 @@ public function prepare_item_for_response( $item, $request ) { // Restores the more descriptive, specific name for use within this method. $user = $item; + // Don't prepare the response body for HEAD requests. + if ( $request->is_method( 'HEAD' ) ) { + /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php */ + return apply_filters( 'rest_prepare_user', new WP_REST_Response(), $user, $request ); + } + $fields = $this->get_fields_for_response( $request ); $data = array(); diff --git a/tests/phpunit/tests/rest-api/rest-categories-controller.php b/tests/phpunit/tests/rest-api/rest-categories-controller.php index 7a65ec695c1bf..ba921b999b866 100644 --- a/tests/phpunit/tests/rest-api/rest-categories-controller.php +++ b/tests/phpunit/tests/rest-api/rest-categories-controller.php @@ -1325,24 +1325,41 @@ public static function data_readable_http_methods() { } /** + * @dataProvider data_readable_http_methods * @ticket 56481 + * + * @param string $method The HTTP method to use. */ - public function test_get_item_with_head_request_should_not_prepare_category_data() { + public function test_get_item_should_allow_adding_headers_via_filter( $method ) { $category_id = self::factory()->category->create(); - $request = new WP_REST_Request( 'HEAD', sprintf( '/wp/v2/categories/%d', $category_id ) ); + $request = new WP_REST_Request( $method, sprintf( '/wp/v2/categories/%d', $category_id ) ); $hook_name = 'rest_prepare_category'; $filter = new MockAction(); $callback = array( $filter, 'filter' ); add_filter( $hook_name, $callback ); + $header_filter = new class() { + public static function add_custom_header( $response ) { + $response->header( 'X-Test-Header', 'Test' ); + + return $response; + } + }; + add_filter( $hook_name, array( $header_filter, 'add_custom_header' ) ); $response = rest_get_server()->dispatch( $request ); remove_filter( $hook_name, $callback ); + remove_filter( $hook_name, array( $header_filter, 'add_custom_header' ) ); $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); - - $this->assertSame( 0, $filter->get_call_count(), 'The "' . $hook_name . '" filter was called when it should not be for HEAD requests.' ); + $this->assertSame( 1, $filter->get_call_count(), 'The "' . $hook_name . '" filter was called when it should not be for HEAD requests.' ); + $headers = $response->get_headers(); + $this->assertArrayHasKey( 'X-Test-Header', $headers, 'The "X-Test-Header" header should be present in the response.' ); + $this->assertSame( 'Test', $headers['X-Test-Header'], 'The "X-Test-Header" header value should be equal to "Test".' ); + if ( 'HEAD' !== $method ) { + return null; + } $this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' ); } } diff --git a/tests/phpunit/tests/rest-api/rest-comments-controller.php b/tests/phpunit/tests/rest-api/rest-comments-controller.php index 11bcc19f84e7d..f3dbc83dcf3e0 100644 --- a/tests/phpunit/tests/rest-api/rest-comments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-comments-controller.php @@ -3534,21 +3534,39 @@ public function test_get_items_only_fetches_ids_for_head_requests( $method ) { } /** + * @dataProvider data_readable_http_methods * @ticket 56481 + * + * @param string $method The HTTP method to use. */ - public function test_get_item_with_head_request_should_not_prepare_comment_data() { - $request = new WP_REST_Request( 'HEAD', sprintf( '/wp/v2/comments/%d', self::$approved_id ) ); + public function test_get_item_with_head_request_should_not_prepare_comment_data( $method ) { + $request = new WP_REST_Request( $method, sprintf( '/wp/v2/comments/%d', self::$approved_id ) ); $hook_name = 'rest_prepare_comment'; $filter = new MockAction(); $callback = array( $filter, 'filter' ); add_filter( $hook_name, $callback ); + $header_filter = new class() { + public static function add_custom_header( $response ) { + $response->header( 'X-Test-Header', 'Test' ); + + return $response; + } + }; + add_filter( $hook_name, array( $header_filter, 'add_custom_header' ) ); $response = rest_get_server()->dispatch( $request ); remove_filter( $hook_name, $callback ); + remove_filter( $hook_name, array( $header_filter, 'add_custom_header' ) ); $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); - $this->assertSame( 0, $filter->get_call_count(), 'The "' . $hook_name . '" filter was called when it should not be for HEAD requests.' ); + $headers = $response->get_headers(); + $this->assertSame( 1, $filter->get_call_count(), 'The "' . $hook_name . '" filter was called when it should not be for HEAD requests.' ); + $this->assertArrayHasKey( 'X-Test-Header', $headers, 'The "X-Test-Header" header should be present in the response.' ); + $this->assertSame( 'Test', $headers['X-Test-Header'], 'The "X-Test-Header" header value should be equal to "Test".' ); + if ( 'HEAD' !== $method ) { + return null; + } $this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' ); } } diff --git a/tests/phpunit/tests/rest-api/rest-post-types-controller.php b/tests/phpunit/tests/rest-api/rest-post-types-controller.php index be828dc230eae..3b61e0d6a9fc3 100644 --- a/tests/phpunit/tests/rest-api/rest-post-types-controller.php +++ b/tests/phpunit/tests/rest-api/rest-post-types-controller.php @@ -79,20 +79,38 @@ public function test_get_item() { } /** + * @dataProvider data_readable_http_methods * @ticket 56481 + * + * @param string $method The HTTP method to use. */ - public function test_get_item_with_head_request_should_not_prepare_post_type_data() { - $request = new WP_REST_Request( 'HEAD', '/wp/v2/types/post' ); + public function test_get_item_should_allow_adding_headers_via_filter( $method ) { + $request = new WP_REST_Request( $method, '/wp/v2/types/post' ); + $hook_name = 'rest_prepare_post_type'; $filter = new MockAction(); $callback = array( $filter, 'filter' ); add_filter( $hook_name, $callback ); + $header_filter = new class() { + public static function add_custom_header( $response ) { + $response->header( 'X-Test-Header', 'Test' ); + + return $response; + } + }; + add_filter( $hook_name, array( $header_filter, 'add_custom_header' ) ); $response = rest_get_server()->dispatch( $request ); remove_filter( $hook_name, $callback ); + remove_filter( $hook_name, array( $header_filter, 'add_custom_header' ) ); $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); - - $this->assertSame( 0, $filter->get_call_count(), 'The "' . $hook_name . '" filter was called when it should not be for HEAD requests.' ); + $this->assertSame( 1, $filter->get_call_count(), 'The "' . $hook_name . '" filter was called when it should not be for HEAD requests.' ); + $headers = $response->get_headers(); + $this->assertArrayHasKey( 'X-Test-Header', $headers, 'The "X-Test-Header" header should be present in the response.' ); + $this->assertSame( 'Test', $headers['X-Test-Header'], 'The "X-Test-Header" header value should be equal to "Test".' ); + if ( 'HEAD' !== $method ) { + return null; + } $this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' ); } diff --git a/tests/phpunit/tests/rest-api/rest-posts-controller.php b/tests/phpunit/tests/rest-api/rest-posts-controller.php index 4af2c0d98a568..7ab2c665a6414 100644 --- a/tests/phpunit/tests/rest-api/rest-posts-controller.php +++ b/tests/phpunit/tests/rest-api/rest-posts-controller.php @@ -2159,24 +2159,39 @@ public function test_get_item() { } /** + * @dataProvider data_readable_http_methods * @ticket 56481 + * + * @param string $method The HTTP method to use. */ - public function test_get_item_with_head_request_should_not_prepare_post_data() { - $request = new WP_REST_Request( 'HEAD', sprintf( '/wp/v2/posts/%d', self::$post_id ) ); + public function test_get_item_should_allow_adding_headers_via_filter( $method ) { + $request = new WP_REST_Request( $method, sprintf( '/wp/v2/posts/%d', self::$post_id ) ); $hook_name = 'rest_prepare_' . get_post_type( self::$post_id ); - - $filter = new MockAction(); - $callback = array( $filter, 'filter' ); + $filter = new MockAction(); + $callback = array( $filter, 'filter' ); add_filter( $hook_name, $callback ); + $header_filter = new class() { + public static function add_custom_header( $response ) { + $response->header( 'X-Test-Header', 'Test' ); + + return $response; + } + }; + add_filter( $hook_name, array( $header_filter, 'add_custom_header' ) ); $response = rest_get_server()->dispatch( $request ); remove_filter( $hook_name, $callback ); + remove_filter( $hook_name, array( $header_filter, 'add_custom_header' ) ); $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); - + $this->assertSame( 1, $filter->get_call_count(), 'The "' . $hook_name . '" filter was not called when it should be for GET/HEAD requests.' ); $headers = $response->get_headers(); - $this->assertSame( 0, $filter->get_call_count(), 'The "' . $hook_name . '" filter was called when it should not be for HEAD requests.' ); $this->assertArrayHasKey( 'Link', $headers, 'The "Link" header should be present in the response.' ); + $this->assertArrayHasKey( 'X-Test-Header', $headers, 'The "X-Test-Header" header should be present in the response.' ); + $this->assertSame( 'Test', $headers['X-Test-Header'], 'The "X-Test-Header" header value should be equal to "Test".' ); + if ( 'HEAD' !== $method ) { + return null; + } $this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' ); } diff --git a/tests/phpunit/tests/rest-api/rest-tags-controller.php b/tests/phpunit/tests/rest-api/rest-tags-controller.php index 2577bcef2f10b..bf09be061540e 100644 --- a/tests/phpunit/tests/rest-api/rest-tags-controller.php +++ b/tests/phpunit/tests/rest-api/rest-tags-controller.php @@ -1579,24 +1579,41 @@ public static function data_readable_http_methods() { } /** + * @dataProvider data_readable_http_methods * @ticket 56481 + * + * @param string $method The HTTP method to use. */ - public function test_get_item_with_head_request_should_not_prepare_tag_data() { + public function test_get_item_should_allow_adding_headers_via_filter( string $method ) { $tag_id = self::factory()->tag->create(); - $request = new WP_REST_Request( 'HEAD', sprintf( '/wp/v2/tags/%d', $tag_id ) ); + $request = new WP_REST_Request( $method, sprintf( '/wp/v2/tags/%d', $tag_id ) ); $hook_name = 'rest_prepare_post_tag'; $filter = new MockAction(); $callback = array( $filter, 'filter' ); add_filter( $hook_name, $callback ); + $header_filter = new class() { + public static function add_custom_header( $response ) { + $response->header( 'X-Test-Header', 'Test' ); + + return $response; + } + }; + add_filter( $hook_name, array( $header_filter, 'add_custom_header' ) ); $response = rest_get_server()->dispatch( $request ); remove_filter( $hook_name, $callback ); + remove_filter( $hook_name, array( $header_filter, 'add_custom_header' ) ); $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); - - $this->assertSame( 0, $filter->get_call_count(), 'The "' . $hook_name . '" filter was called when it should not be for HEAD requests.' ); + $this->assertSame( 1, $filter->get_call_count(), 'The "' . $hook_name . '" filter was called when it should not be for HEAD requests.' ); + $headers = $response->get_headers(); + $this->assertArrayHasKey( 'X-Test-Header', $headers, 'The "X-Test-Header" header should be present in the response.' ); + $this->assertSame( 'Test', $headers['X-Test-Header'], 'The "X-Test-Header" header value should be equal to "Test".' ); + if ( 'HEAD' !== $method ) { + return null; + } $this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' ); } } diff --git a/tests/phpunit/tests/rest-api/rest-taxonomies-controller.php b/tests/phpunit/tests/rest-api/rest-taxonomies-controller.php index a4da2c4608a2b..8e5542cb37e62 100644 --- a/tests/phpunit/tests/rest-api/rest-taxonomies-controller.php +++ b/tests/phpunit/tests/rest-api/rest-taxonomies-controller.php @@ -154,20 +154,37 @@ public function test_get_item() { } /** + * @dataProvider data_readable_http_methods * @ticket 56481 + * + * @param string $method The HTTP method to use. */ - public function test_get_item_with_head_request_should_not_prepare_taxonomy_data() { + public function test_get_item_should_allow_adding_headers_via_filter( $method ) { $request = new WP_REST_Request( 'HEAD', '/wp/v2/taxonomies/category' ); $hook_name = 'rest_prepare_taxonomy'; $filter = new MockAction(); $callback = array( $filter, 'filter' ); add_filter( $hook_name, $callback ); + $header_filter = new class() { + public static function add_custom_header( $response ) { + $response->header( 'X-Test-Header', 'Test' ); + + return $response; + } + }; + add_filter( $hook_name, array( $header_filter, 'add_custom_header' ) ); $response = rest_get_server()->dispatch( $request ); remove_filter( $hook_name, $callback ); + remove_filter( $hook_name, array( $header_filter, 'add_custom_header' ) ); $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); - - $this->assertSame( 0, $filter->get_call_count(), 'The "' . $hook_name . '" filter was called when it should not be for HEAD requests.' ); + $this->assertSame( 1, $filter->get_call_count(), 'The "' . $hook_name . '" filter was called when it should not be for HEAD requests.' ); + $headers = $response->get_headers(); + $this->assertArrayHasKey( 'X-Test-Header', $headers, 'The "X-Test-Header" header should be present in the response.' ); + $this->assertSame( 'Test', $headers['X-Test-Header'], 'The "X-Test-Header" header value should be equal to "Test".' ); + if ( 'HEAD' !== $method ) { + return null; + } $this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' ); } diff --git a/tests/phpunit/tests/rest-api/rest-users-controller.php b/tests/phpunit/tests/rest-api/rest-users-controller.php index 333c83c498017..f6ba730cdb781 100644 --- a/tests/phpunit/tests/rest-api/rest-users-controller.php +++ b/tests/phpunit/tests/rest-api/rest-users-controller.php @@ -1268,15 +1268,14 @@ public function test_get_current_user( $method ) { $request = new WP_REST_Request( $method, '/wp/v2/users/me' ); $response = rest_get_server()->dispatch( $request ); $this->assertSame( 200, $response->get_status() ); - $this->check_get_user_response( $response, 'view' ); - $headers = $response->get_headers(); $this->assertArrayNotHasKey( 'Location', $headers ); if ( 'HEAD' === $method ) { // HEAD responses only contain headers. Bail. - return; + return null; } + $this->check_get_user_response( $response, 'view' ); $links = $response->get_links(); $this->assertSame( rest_url( 'wp/v2/users/' . self::$user ), $links['self'][0]['href'] ); } @@ -3160,23 +3159,40 @@ public function data_get_default_data() { } /** + * @dataProvider data_readable_http_methods * @ticket 56481 + * + * @param string $method The HTTP method to use. */ - public function test_get_item_with_head_request_should_not_prepare_user_data() { + public function test_get_item_should_allow_adding_headers_via_filter( $method ) { wp_set_current_user( self::$user ); - $request = new WP_REST_Request( 'HEAD', sprintf( '/wp/v2/users/%d', self::$user ) ); + $request = new WP_REST_Request( $method, sprintf( '/wp/v2/users/%d', self::$user ) ); $hook_name = 'rest_prepare_user'; $filter = new MockAction(); $callback = array( $filter, 'filter' ); add_filter( $hook_name, $callback ); + $header_filter = new class() { + public static function add_custom_header( $response ) { + $response->header( 'X-Test-Header', 'Test' ); + + return $response; + } + }; + add_filter( $hook_name, array( $header_filter, 'add_custom_header' ) ); $response = rest_get_server()->dispatch( $request ); remove_filter( $hook_name, $callback ); + remove_filter( $hook_name, array( $header_filter, 'add_custom_header' ) ); $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); - - $this->assertSame( 0, $filter->get_call_count(), 'The "' . $hook_name . '" filter was called when it should not be for HEAD requests.' ); + $this->assertSame( 1, $filter->get_call_count(), 'The "' . $hook_name . '" filter was called when it should not be for HEAD requests.' ); + $headers = $response->get_headers(); + $this->assertArrayHasKey( 'X-Test-Header', $headers, 'The "X-Test-Header" header should be present in the response.' ); + $this->assertSame( 'Test', $headers['X-Test-Header'], 'The "X-Test-Header" header value should be equal to "Test".' ); + if ( 'HEAD' !== $method ) { + return null; + } $this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' ); }