REST API: Improve performance for HEAD requests.

By default, the REST API responds to HEAD rqeuests by calling the GET handler and omitting the body from the response. While convenient, this ends up performing needless work that slows down the API response time.

This commit adjusts the Core controllers to specifically handle HEAD requests by not preparing the response body.

Fixes #56481.
Props antonvlasenko, janusdev, ironprogrammer, swissspidy, spacedmonkey, mukesh27, mamaduka, timothyblynjacobs.



git-svn-id: https://develop.svn.wordpress.org/trunk@59899 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Timothy Jacobs 2025-03-02 22:05:08 +00:00
parent c19e4a45c2
commit 1c2a87b294
43 changed files with 2685 additions and 311 deletions

View File

@ -161,6 +161,18 @@ class WP_REST_Request implements ArrayAccess {
return $this->headers;
}
/**
* Determines if the request is the given method.
*
* @since 6.8.0
*
* @param string $method HTTP method.
* @return bool Whether the request is of the given method.
*/
public function is_method( $method ) {
return $this->get_method() === strtoupper( $method );
}
/**
* Canonicalizes the header name.
*

View File

@ -305,6 +305,10 @@ class WP_REST_Autosaves_Controller extends WP_REST_Revisions_Controller {
return $parent;
}
if ( $request->is_method( 'HEAD' ) ) {
// Return early as this handler doesn't add any response headers.
return new WP_REST_Response();
}
$response = array();
$parent_id = $parent->ID;
$revisions = wp_get_post_revisions( $parent_id, array( 'check_enabled' => false ) );
@ -448,6 +452,11 @@ class WP_REST_Autosaves_Controller extends WP_REST_Revisions_Controller {
// Restores the more descriptive, specific name for use within this method.
$post = $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-autosaves-controller.php */
return apply_filters( 'rest_prepare_autosave', new WP_REST_Response(), $post, $request );
}
$response = $this->revisions_controller->prepare_item_for_response( $post, $request );
$fields = $this->get_fields_for_response( $request );

View File

@ -81,6 +81,11 @@ class WP_REST_Block_Pattern_Categories_Controller extends WP_REST_Controller {
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_items( $request ) {
if ( $request->is_method( 'HEAD' ) ) {
// Return early as this handler doesn't add any response headers.
return new WP_REST_Response();
}
$response = array();
$categories = WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered();
foreach ( $categories as $category ) {

View File

@ -131,6 +131,11 @@ class WP_REST_Block_Types_Controller extends WP_REST_Controller {
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_items( $request ) {
if ( $request->is_method( 'HEAD' ) ) {
// Return early as this handler doesn't add any response headers.
return new WP_REST_Response();
}
$data = array();
$block_types = $this->block_registry->get_all_registered();
@ -250,6 +255,12 @@ class WP_REST_Block_Types_Controller extends WP_REST_Controller {
// Restores the more descriptive, specific name for use within this method.
$block_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-block-types-controller.php */
return apply_filters( 'rest_prepare_block_type', new WP_REST_Response(), $block_type, $request );
}
$fields = $this->get_fields_for_response( $request );
$data = array();

View File

@ -262,6 +262,14 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
$prepared_args['offset'] = $prepared_args['number'] * ( absint( $request['page'] ) - 1 );
}
$is_head_request = $request->is_method( 'HEAD' );
if ( $is_head_request ) {
// Force the 'fields' argument. For HEAD requests, only post IDs are required to calculate pagination.
$prepared_args['fields'] = 'ids';
// Disable priming comment meta for HEAD requests to improve performance.
$prepared_args['update_comment_meta_cache'] = false;
}
/**
* Filters WP_Comment_Query arguments when querying comments via the REST API.
*
@ -277,15 +285,17 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
$query = new WP_Comment_Query();
$query_result = $query->query( $prepared_args );
$comments = array();
if ( ! $is_head_request ) {
$comments = array();
foreach ( $query_result as $comment ) {
if ( ! $this->check_read_permission( $comment, $request ) ) {
continue;
foreach ( $query_result as $comment ) {
if ( ! $this->check_read_permission( $comment, $request ) ) {
continue;
}
$data = $this->prepare_item_for_response( $comment, $request );
$comments[] = $this->prepare_response_for_collection( $data );
}
$data = $this->prepare_item_for_response( $comment, $request );
$comments[] = $this->prepare_response_for_collection( $data );
}
$total_comments = (int) $query->found_comments;
@ -303,7 +313,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
$max_pages = (int) ceil( $total_comments / $request['per_page'] );
}
$response = rest_ensure_response( $comments );
$response = $is_head_request ? new WP_REST_Response() : rest_ensure_response( $comments );
$response->header( 'X-WP-Total', $total_comments );
$response->header( 'X-WP-TotalPages', $max_pages );
@ -1041,6 +1051,12 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
// 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();

View File

@ -89,6 +89,8 @@ class WP_REST_Font_Collections_Controller extends WP_REST_Controller {
$collections_page = array_slice( $collections_all, ( $page - 1 ) * $per_page, $per_page );
$is_head_request = $request->is_method( 'HEAD' );
$items = array();
foreach ( $collections_page as $collection ) {
$item = $this->prepare_item_for_response( $collection, $request );
@ -97,11 +99,21 @@ class WP_REST_Font_Collections_Controller extends WP_REST_Controller {
if ( is_wp_error( $item ) ) {
continue;
}
/*
* Skip preparing the response body for HEAD requests.
* Cannot exit earlier due to backward compatibility reasons,
* as validation occurs in the prepare_item_for_response method.
*/
if ( $is_head_request ) {
continue;
}
$item = $this->prepare_response_for_collection( $item );
$items[] = $item;
}
$response = rest_ensure_response( $items );
$response = $is_head_request ? new WP_REST_Response() : rest_ensure_response( $items );
$response->header( 'X-WP-Total', (int) $total_items );
$response->header( 'X-WP-TotalPages', $max_pages );
@ -175,6 +187,15 @@ class WP_REST_Font_Collections_Controller extends WP_REST_Controller {
return $collection_data;
}
/**
* Don't prepare the response body for HEAD requests.
* Can't exit at the beginning of the method due to the potential need to return a WP_Error object.
*/
if ( $request->is_method( 'HEAD' ) ) {
/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php */
return apply_filters( 'rest_prepare_font_collection', new WP_REST_Response(), $item, $request );
}
foreach ( $data_fields as $field ) {
if ( rest_is_field_included( $field, $fields ) ) {
$data[ $field ] = $collection_data[ $field ];
@ -182,6 +203,15 @@ class WP_REST_Font_Collections_Controller extends WP_REST_Controller {
}
}
/**
* Don't prepare the response body for HEAD requests.
* Can't exit at the beginning of the method due to the potential need to return a WP_Error object.
*/
if ( $request->is_method( 'HEAD' ) ) {
/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php */
return apply_filters( 'rest_prepare_font_collection', new WP_REST_Response(), $item, $request );
}
$response = rest_ensure_response( $data );
if ( rest_is_field_included( '_links', $fields ) ) {

View File

@ -163,6 +163,8 @@ class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Revisions_Contr
return $global_styles_config;
}
$is_head_request = $request->is_method( 'HEAD' );
if ( wp_revisions_enabled( $parent ) ) {
$registered = $this->get_collection_params();
$query_args = array(
@ -186,6 +188,14 @@ class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Revisions_Contr
}
}
if ( $is_head_request ) {
// Force the 'fields' argument. For HEAD requests, only post IDs are required to calculate pagination.
$query_args['fields'] = 'ids';
// Disable priming post meta for HEAD requests to improve performance.
$query_args['update_post_term_cache'] = false;
$query_args['update_post_meta_cache'] = false;
}
$revisions_query = new WP_Query();
$revisions = $revisions_query->query( $query_args );
$offset = isset( $query_args['offset'] ) ? (int) $query_args['offset'] : 0;
@ -228,15 +238,19 @@ class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Revisions_Contr
$page = (int) $request['page'];
}
$response = array();
if ( ! $is_head_request ) {
$response = array();
foreach ( $revisions as $revision ) {
$data = $this->prepare_item_for_response( $revision, $request );
$response[] = $this->prepare_response_for_collection( $data );
foreach ( $revisions as $revision ) {
$data = $this->prepare_item_for_response( $revision, $request );
$response[] = $this->prepare_response_for_collection( $data );
}
$response = rest_ensure_response( $response );
} else {
$response = new WP_REST_Response();
}
$response = rest_ensure_response( $response );
$response->header( 'X-WP-Total', (int) $total_revisions );
$response->header( 'X-WP-TotalPages', (int) $max_pages );
@ -275,6 +289,11 @@ class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Revisions_Contr
* @return WP_REST_Response|WP_Error Response object.
*/
public function prepare_item_for_response( $post, $request ) {
// Don't prepare the response body for HEAD requests.
if ( $request->is_method( 'HEAD' ) ) {
return new WP_REST_Response();
}
$parent = $this->get_parent( $request['parent'] );
$global_styles_config = $this->get_decoded_global_styles_json( $post->post_content );

View File

@ -161,6 +161,11 @@ class WP_REST_Pattern_Directory_Controller extends WP_REST_Controller {
return $raw_patterns;
}
if ( $request->is_method( 'HEAD' ) ) {
// Return early as this handler doesn't add any response headers.
return new WP_REST_Response();
}
$response = array();
if ( $raw_patterns ) {

View File

@ -109,6 +109,11 @@ class WP_REST_Post_Types_Controller extends WP_REST_Controller {
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_items( $request ) {
if ( $request->is_method( 'HEAD' ) ) {
// Return early as this handler doesn't add any response headers.
return new WP_REST_Response();
}
$data = array();
$types = get_post_types( array( 'show_in_rest' => true ), 'objects' );
@ -178,6 +183,12 @@ class WP_REST_Post_Types_Controller extends WP_REST_Controller {
// 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;

View File

@ -411,6 +411,15 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
// Force the post_type argument, since it's not a user input variable.
$args['post_type'] = $this->post_type;
$is_head_request = $request->is_method( 'HEAD' );
if ( $is_head_request ) {
// Force the 'fields' argument. For HEAD requests, only post IDs are required to calculate pagination.
$args['fields'] = 'ids';
// Disable priming post meta for HEAD requests to improve performance.
$args['update_post_term_cache'] = false;
$args['update_post_meta_cache'] = false;
}
/**
* Filters WP_Query arguments when querying posts via the REST API.
*
@ -443,22 +452,24 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
add_filter( 'post_password_required', array( $this, 'check_password_required' ), 10, 2 );
}
$posts = array();
if ( ! $is_head_request ) {
$posts = array();
update_post_author_caches( $query_result );
update_post_parent_caches( $query_result );
update_post_author_caches( $query_result );
update_post_parent_caches( $query_result );
if ( post_type_supports( $this->post_type, 'thumbnail' ) ) {
update_post_thumbnail_cache( $posts_query );
}
foreach ( $query_result as $post ) {
if ( ! $this->check_read_permission( $post ) ) {
continue;
if ( post_type_supports( $this->post_type, 'thumbnail' ) ) {
update_post_thumbnail_cache( $posts_query );
}
$data = $this->prepare_item_for_response( $post, $request );
$posts[] = $this->prepare_response_for_collection( $data );
foreach ( $query_result as $post ) {
if ( ! $this->check_read_permission( $post ) ) {
continue;
}
$data = $this->prepare_item_for_response( $post, $request );
$posts[] = $this->prepare_response_for_collection( $data );
}
}
// Reset filter.
@ -488,7 +499,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
);
}
$response = rest_ensure_response( $posts );
$response = $is_head_request ? new WP_REST_Response() : rest_ensure_response( $posts );
$response->header( 'X-WP-Total', (int) $total_posts );
$response->header( 'X-WP-TotalPages', (int) $max_pages );
@ -1833,6 +1844,12 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
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.

View File

@ -253,6 +253,8 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
);
}
$is_head_request = $request->is_method( 'HEAD' );
if ( wp_revisions_enabled( $parent ) ) {
$registered = $this->get_collection_params();
$args = array(
@ -287,6 +289,14 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
$args['orderby'] = 'date ID';
}
if ( $is_head_request ) {
// Force the 'fields' argument. For HEAD requests, only post IDs are required to calculate pagination.
$args['fields'] = 'ids';
// Disable priming post meta for HEAD requests to improve performance.
$args['update_post_term_cache'] = false;
$args['update_post_meta_cache'] = false;
}
/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */
$args = apply_filters( 'rest_revision_query', $args, $request );
$query_args = $this->prepare_items_query( $args, $request );
@ -335,15 +345,19 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
$page = (int) $request['page'];
}
$response = array();
if ( ! $is_head_request ) {
$response = array();
foreach ( $revisions as $revision ) {
$data = $this->prepare_item_for_response( $revision, $request );
$response[] = $this->prepare_response_for_collection( $data );
foreach ( $revisions as $revision ) {
$data = $this->prepare_item_for_response( $revision, $request );
$response[] = $this->prepare_response_for_collection( $data );
}
$response = rest_ensure_response( $response );
} else {
$response = new WP_REST_Response();
}
$response = rest_ensure_response( $response );
$response->header( 'X-WP-Total', (int) $total_revisions );
$response->header( 'X-WP-TotalPages', (int) $max_pages );
@ -574,6 +588,12 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
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-revisions-controller.php */
return apply_filters( 'rest_prepare_revision', new WP_REST_Response(), $post, $request );
}
$fields = $this->get_fields_for_response( $request );
$data = array();

View File

@ -142,11 +142,14 @@ class WP_REST_Search_Controller extends WP_REST_Controller {
$ids = $result[ WP_REST_Search_Handler::RESULT_IDS ];
$results = array();
$is_head_request = $request->is_method( 'HEAD' );
if ( ! $is_head_request ) {
$results = array();
foreach ( $ids as $id ) {
$data = $this->prepare_item_for_response( $id, $request );
$results[] = $this->prepare_response_for_collection( $data );
foreach ( $ids as $id ) {
$data = $this->prepare_item_for_response( $id, $request );
$results[] = $this->prepare_response_for_collection( $data );
}
}
$total = (int) $result[ WP_REST_Search_Handler::RESULT_TOTAL ];
@ -162,7 +165,7 @@ class WP_REST_Search_Controller extends WP_REST_Controller {
);
}
$response = rest_ensure_response( $results );
$response = $is_head_request ? new WP_REST_Response() : rest_ensure_response( $results );
$response->header( 'X-WP-Total', $total );
$response->header( 'X-WP-TotalPages', $max_pages );

View File

@ -119,6 +119,11 @@ class WP_REST_Sidebars_Controller extends WP_REST_Controller {
* @return WP_REST_Response Response object on success.
*/
public function get_items( $request ) {
if ( $request->is_method( 'HEAD' ) ) {
// Return early as this handler doesn't add any response headers.
return new WP_REST_Response();
}
$this->retrieve_widgets();
$data = array();
@ -321,6 +326,12 @@ class WP_REST_Sidebars_Controller extends WP_REST_Controller {
// Restores the more descriptive, specific name for use within this method.
$raw_sidebar = $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-sidebars-controller.php */
return apply_filters( 'rest_prepare_sidebar', new WP_REST_Response(), $raw_sidebar, $request );
}
$id = $raw_sidebar['id'];
$sidebar = array( 'id' => $id );

View File

@ -113,6 +113,10 @@ class WP_REST_Taxonomies_Controller extends WP_REST_Controller {
* @return WP_REST_Response Response object on success, or WP_Error object on failure.
*/
public function get_items( $request ) {
if ( $request->is_method( 'HEAD' ) ) {
// Return early as this handler doesn't add any response headers.
return new WP_REST_Response();
}
// Retrieve the list of registered collection query parameters.
$registered = $this->get_collection_params();
@ -210,6 +214,12 @@ class WP_REST_Taxonomies_Controller extends WP_REST_Controller {
// 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 );

View File

@ -175,6 +175,11 @@ class WP_REST_Template_Autosaves_Controller extends WP_REST_Autosaves_Controller
$template = _build_block_template_result_from_post( $item );
$response = $this->parent_controller->prepare_item_for_response( $template, $request );
// Don't prepare the response body for HEAD requests.
if ( $request->is_method( 'HEAD' ) ) {
return $response;
}
$fields = $this->get_fields_for_response( $request );
$data = $response->get_data();

View File

@ -200,6 +200,11 @@ class WP_REST_Template_Revisions_Controller extends WP_REST_Revisions_Controller
$template = _build_block_template_result_from_post( $item );
$response = $this->parent_controller->prepare_item_for_response( $template, $request );
// Don't prepare the response body for HEAD requests.
if ( $request->is_method( 'HEAD' ) ) {
return $response;
}
$fields = $this->get_fields_for_response( $request );
$data = $response->get_data();

View File

@ -269,6 +269,11 @@ class WP_REST_Templates_Controller extends WP_REST_Controller {
* @return WP_REST_Response
*/
public function get_items( $request ) {
if ( $request->is_method( 'HEAD' ) ) {
// Return early as this handler doesn't add any response headers.
return new WP_REST_Response();
}
$query = array();
if ( isset( $request['wp_id'] ) ) {
$query['wp_id'] = $request['wp_id'];
@ -668,6 +673,11 @@ class WP_REST_Templates_Controller extends WP_REST_Controller {
* @return WP_REST_Response Response object.
*/
public function prepare_item_for_response( $item, $request ) {
// Don't prepare the response body for HEAD requests.
if ( $request->is_method( 'HEAD' ) ) {
return new WP_REST_Response();
}
/*
* Resolve pattern blocks so they don't need to be resolved client-side
* in the editor, improving performance.

View File

@ -312,6 +312,14 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
$prepared_args = array_merge( $prepared_args, $taxonomy_obj->args );
}
$is_head_request = $request->is_method( 'HEAD' );
if ( $is_head_request ) {
// Force the 'fields' argument. For HEAD requests, only term IDs are required.
$prepared_args['fields'] = 'ids';
// Disable priming term meta for HEAD requests to improve performance.
$prepared_args['update_term_meta_cache'] = false;
}
/**
* Filters get_terms() arguments when querying terms via the REST API.
*
@ -354,14 +362,15 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
$total_terms = 0;
}
$response = array();
foreach ( $query_result as $term ) {
$data = $this->prepare_item_for_response( $term, $request );
$response[] = $this->prepare_response_for_collection( $data );
if ( ! $is_head_request ) {
$response = array();
foreach ( $query_result as $term ) {
$data = $this->prepare_item_for_response( $term, $request );
$response[] = $this->prepare_response_for_collection( $data );
}
}
$response = rest_ensure_response( $response );
$response = $is_head_request ? new WP_REST_Response() : rest_ensure_response( $response );
// Store pagination values for headers.
$per_page = (int) $prepared_args['number'];
@ -887,6 +896,12 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
*/
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();

View File

@ -355,6 +355,12 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
}
$prepared_args['search'] = '*' . $prepared_args['search'] . '*';
}
$is_head_request = $request->is_method( 'HEAD' );
if ( $is_head_request ) {
// Force the 'fields' argument. For HEAD requests, only user IDs are required.
$prepared_args['fields'] = 'id';
}
/**
* Filters WP_User_Query arguments when querying users via the REST API.
*
@ -369,14 +375,16 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
$query = new WP_User_Query( $prepared_args );
$users = array();
if ( ! $is_head_request ) {
$users = array();
foreach ( $query->get_results() as $user ) {
$data = $this->prepare_item_for_response( $user, $request );
$users[] = $this->prepare_response_for_collection( $data );
foreach ( $query->get_results() as $user ) {
$data = $this->prepare_item_for_response( $user, $request );
$users[] = $this->prepare_response_for_collection( $data );
}
}
$response = rest_ensure_response( $users );
$response = $is_head_request ? new WP_REST_Response() : rest_ensure_response( $users );
// Store pagination values for headers then unset for count query.
$per_page = (int) $prepared_args['number'];
@ -1021,6 +1029,12 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
// 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();

View File

@ -145,6 +145,11 @@ class WP_REST_Widget_Types_Controller extends WP_REST_Controller {
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_items( $request ) {
if ( $request->is_method( 'HEAD' ) ) {
// Return early as this handler doesn't add any response headers.
return new WP_REST_Response();
}
$data = array();
foreach ( $this->get_widgets() as $widget ) {
$widget_type = $this->prepare_item_for_response( $widget, $request );
@ -298,6 +303,12 @@ class WP_REST_Widget_Types_Controller extends WP_REST_Controller {
// Restores the more descriptive, specific name for use within this method.
$widget_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-widget-types-controller.php */
return apply_filters( 'rest_prepare_widget_type', new WP_REST_Response(), $widget_type, $request );
}
$fields = $this->get_fields_for_response( $request );
$data = array(
'id' => $widget_type['id'],

View File

@ -136,6 +136,11 @@ class WP_REST_Widgets_Controller extends WP_REST_Controller {
* @return WP_REST_Response Response object.
*/
public function get_items( $request ) {
if ( $request->is_method( 'HEAD' ) ) {
// Return early as this handler doesn't add any response headers.
return new WP_REST_Response();
}
$this->retrieve_widgets();
$prepared = array();
@ -678,6 +683,12 @@ class WP_REST_Widgets_Controller extends WP_REST_Controller {
}
$widget = $wp_registered_widgets[ $widget_id ];
// 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-widgets-controller.php */
return apply_filters( 'rest_prepare_widget', new WP_REST_Response(), $widget, $request );
}
$parsed_id = wp_parse_widget_id( $widget_id );
$fields = $this->get_fields_for_response( $request );

View File

@ -78,9 +78,13 @@ class Tests_REST_WpRestFontCollectionsController extends WP_Test_REST_Controller
}
/**
* @dataProvider data_readable_http_methods
* @covers WP_REST_Font_Collections_Controller::get_items
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_should_only_return_valid_collections() {
public function test_get_items_should_only_return_valid_collections( $method ) {
$this->setExpectedIncorrectUsage( 'WP_Font_Collection::load_from_json' );
wp_set_current_user( self::$admin_id );
@ -92,14 +96,23 @@ class Tests_REST_WpRestFontCollectionsController extends WP_Test_REST_Controller
)
);
$request = new WP_REST_Request( 'GET', '/wp/v2/font-collections' );
$request = new WP_REST_Request( $method, '/wp/v2/font-collections' );
$response = rest_get_server()->dispatch( $request );
$content = $response->get_data();
wp_unregister_font_collection( 'invalid-collection' );
$this->assertSame( 200, $response->get_status(), 'The response status should be 200.' );
$this->assertCount( 1, $content, 'The response should only contain valid collections.' );
if ( 'HEAD' !== $method ) {
$this->assertCount( 1, $content, 'The response should only contain valid collections.' );
return null;
}
$this->assertNull( $content, 'The response should be empty.' );
$headers = $response->get_headers();
$this->assertArrayHasKey( 'X-WP-Total', $headers, 'The "X-WP-Total" header should be present in the response.' );
// Includes non-valid collections.
$this->assertSame( 2, $headers['X-WP-Total'], 'The "X-WP-Total" header value should be equal to 1.' );
}
/**
@ -127,19 +140,75 @@ class Tests_REST_WpRestFontCollectionsController extends WP_Test_REST_Controller
}
/**
* @covers WP_REST_Font_Collections_Controller::get_item
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_item_invalid_slug() {
public function test_get_item_should_allow_adding_headers_via_filter( $method ) {
$hook_name = 'rest_prepare_font_collection';
$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' ) );
wp_set_current_user( self::$admin_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/font-collections/non-existing-collection' );
$request = new WP_REST_Request( $method, '/wp/v2/font-collections/mock-col-slug' );
$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->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.' );
}
/**
* Data provider intended to provide HTTP method names for testing GET and HEAD requests.
*
* @return array
*/
public static function data_readable_http_methods() {
return array(
'GET request' => array( 'GET' ),
'HEAD request' => array( 'HEAD' ),
);
}
/**
* @dataProvider data_readable_http_methods
* @covers WP_REST_Font_Collections_Controller::get_item
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_item_invalid_slug( $method ) {
wp_set_current_user( self::$admin_id );
$request = new WP_REST_Request( $method, '/wp/v2/font-collections/non-existing-collection' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_font_collection_not_found', $response, 404 );
}
/**
* @dataProvider data_readable_http_methods
* @covers WP_REST_Font_Collections_Controller::get_item
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_item_invalid_collection() {
public function test_get_item_invalid_collection( $method ) {
$this->setExpectedIncorrectUsage( 'WP_Font_Collection::load_from_json' );
wp_set_current_user( self::$admin_id );
@ -152,7 +221,7 @@ class Tests_REST_WpRestFontCollectionsController extends WP_Test_REST_Controller
)
);
$request = new WP_REST_Request( 'GET', '/wp/v2/font-collections/' . $slug );
$request = new WP_REST_Request( $method, '/wp/v2/font-collections/' . $slug );
$response = rest_get_server()->dispatch( $request );
wp_unregister_font_collection( $slug );
@ -161,10 +230,14 @@ class Tests_REST_WpRestFontCollectionsController extends WP_Test_REST_Controller
}
/**
* @dataProvider data_readable_http_methods
* @covers WP_REST_Font_Collections_Controller::get_item
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_item_invalid_id_permission() {
$request = new WP_REST_Request( 'GET', '/wp/v2/font-collections/mock-col-slug' );
public function test_get_item_invalid_id_permission( $method ) {
$request = new WP_REST_Request( $method, '/wp/v2/font-collections/mock-col-slug' );
wp_set_current_user( 0 );
$response = rest_get_server()->dispatch( $request );

View File

@ -179,9 +179,35 @@ class WP_Test_REST_Autosaves_Controller extends WP_Test_REST_Post_Type_Controlle
$this->check_get_autosave_response( $data[0], $this->post_autosave );
}
public function test_get_items_no_permission() {
/**
* @ticket 56481
*/
public function test_get_items_with_head_request_should_not_prepare_autosaves_data() {
$request = new WP_REST_Request( 'HEAD', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
$hook_name = 'rest_prepare_autosave';
$filter = new MockAction();
$callback = array( $filter, 'filter' );
add_filter( $hook_name, $callback );
$response = rest_get_server()->dispatch( $request );
remove_filter( $hook_name, $callback );
$this->assertNotWPError( $response );
$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->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' );
}
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_no_permission( $method ) {
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
$request = new WP_REST_Request( $method, '/wp/v2/posts/' . self::$post_id . '/autosaves' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_read', $response, 401 );
wp_set_current_user( self::$contributor_id );
@ -189,16 +215,40 @@ class WP_Test_REST_Autosaves_Controller extends WP_Test_REST_Post_Type_Controlle
$this->assertErrorResponse( 'rest_cannot_read', $response, 403 );
}
public function test_get_items_missing_parent() {
/**
* Data provider intended to provide HTTP method names for testing GET and HEAD requests.
*
* @return array
*/
public static function data_readable_http_methods() {
return array(
'GET request' => array( 'GET' ),
'HEAD request' => array( 'HEAD' ),
);
}
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_missing_parent( $method ) {
wp_set_current_user( self::$editor_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/autosaves' );
$request = new WP_REST_Request( $method, '/wp/v2/posts/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/autosaves' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 );
}
public function test_get_items_invalid_parent_post_type() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_invalid_parent_post_type( $method ) {
wp_set_current_user( self::$editor_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$page_id . '/autosaves' );
$request = new WP_REST_Request( $method, '/wp/v2/posts/' . self::$page_id . '/autosaves' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 );
}
@ -230,6 +280,43 @@ class WP_Test_REST_Autosaves_Controller extends WP_Test_REST_Post_Type_Controlle
$this->assertSame( self::$editor_id, $data['author'] );
}
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_item_should_allow_adding_headers_via_filter( $method ) {
wp_set_current_user( self::$editor_id );
$request = new WP_REST_Request( $method, '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id );
$hook_name = 'rest_prepare_autosave';
$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->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.' );
}
public function test_get_item_embed_context() {
wp_set_current_user( self::$editor_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id );
@ -248,23 +335,41 @@ class WP_Test_REST_Autosaves_Controller extends WP_Test_REST_Post_Type_Controlle
$this->assertSameSets( $fields, array_keys( $data ) );
}
public function test_get_item_no_permission() {
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id );
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_item_no_permission( $method ) {
$request = new WP_REST_Request( $method, '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id );
wp_set_current_user( self::$contributor_id );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_read', $response, 403 );
}
public function test_get_item_missing_parent() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_item_missing_parent( $method ) {
wp_set_current_user( self::$editor_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/autosaves/' . self::$autosave_post_id );
$request = new WP_REST_Request( $method, '/wp/v2/posts/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/autosaves/' . self::$autosave_post_id );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 );
}
public function test_get_item_invalid_parent_post_type() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_item_invalid_parent_post_type( $method ) {
wp_set_current_user( self::$editor_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$page_id . '/autosaves' );
$request = new WP_REST_Request( $method, '/wp/v2/posts/' . self::$page_id . '/autosaves' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 );
}

View File

@ -602,47 +602,128 @@ class REST_Block_Type_Controller_Test extends WP_Test_REST_Controller_Testcase {
}
/**
* @ticket 47620
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_wrong_permission() {
public function test_get_item_should_allow_adding_headers_via_filter( $method ) {
$block_name = 'fake/test';
wp_set_current_user( self::$admin_id );
$hook_name = 'rest_prepare_block_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' ) );
$request = new WP_REST_Request( $method, '/wp/v2/block-types/' . $block_name );
$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 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.' );
}
/**
* Data provider intended to provide HTTP method names for testing GET and HEAD requests.
*
* @return array
*/
public static function data_readable_http_methods() {
return array(
'GET request' => array( 'GET' ),
'HEAD request' => array( 'HEAD' ),
);
}
/**
* @ticket 56481
*/
public function test_get_items_with_head_request_should_not_prepare_block_type_data() {
wp_set_current_user( self::$admin_id );
$request = new WP_REST_Request( 'HEAD', '/wp/v2/block-types' );
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 200, $response->get_status(), 'The response status should be 200.' );
$this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' );
}
/**
* @dataProvider data_readable_http_methods
* @ticket 47620
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_items_wrong_permission( $method ) {
wp_set_current_user( self::$subscriber_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/block-types' );
$request = new WP_REST_Request( $method, '/wp/v2/block-types' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_block_type_cannot_view', $response, 403 );
}
/**
* @dataProvider data_readable_http_methods
* @ticket 47620
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_item_wrong_permission() {
public function test_get_item_wrong_permission( $method ) {
wp_set_current_user( self::$subscriber_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/block-types/fake/test' );
$request = new WP_REST_Request( $method, '/wp/v2/block-types/fake/test' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_block_type_cannot_view', $response, 403 );
}
/**
* @dataProvider data_readable_http_methods
* @ticket 47620
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_items_no_permission() {
public function test_get_items_no_permission( $method ) {
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'GET', '/wp/v2/block-types' );
$request = new WP_REST_Request( $method, '/wp/v2/block-types' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_block_type_cannot_view', $response, 401 );
}
/**
* @dataProvider data_readable_http_methods
* @ticket 47620
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_item_no_permission() {
public function test_get_item_no_permission( $method ) {
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'GET', '/wp/v2/block-types/fake/test' );
$request = new WP_REST_Request( $method, '/wp/v2/block-types/fake/test' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_block_type_cannot_view', $response, 401 );
}
/**
* @dataProvider data_readable_http_methods
* @ticket 47620
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_prepare_item() {
$registry = new WP_Block_Type_Registry();

View File

@ -576,8 +576,14 @@ class WP_Test_REST_Categories_Controller extends WP_Test_REST_Controller_Testcas
$this->assertSame( 'Child', $data[0]['name'] );
}
public function test_get_terms_invalid_parent_arg() {
$request = new WP_REST_Request( 'GET', '/wp/v2/categories' );
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_terms_invalid_parent_arg( $method ) {
$request = new WP_REST_Request( $method, '/wp/v2/categories' );
$request->set_param( 'parent', 'invalid-parent' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
@ -609,17 +615,25 @@ class WP_Test_REST_Categories_Controller extends WP_Test_REST_Controller_Testcas
$this->assertErrorResponse( 'rest_no_route', $response, 404 );
}
public function test_get_terms_pagination_headers() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_terms_pagination_headers( $method ) {
$total_categories = self::$total_categories;
$total_pages = (int) ceil( $total_categories / 10 );
// Start of the index + Uncategorized default term.
$request = new WP_REST_Request( 'GET', '/wp/v2/categories' );
$request = new WP_REST_Request( $method, '/wp/v2/categories' );
$response = rest_get_server()->dispatch( $request );
$headers = $response->get_headers();
$this->assertSame( $total_categories, $headers['X-WP-Total'] );
$this->assertSame( $total_pages, $headers['X-WP-TotalPages'] );
$this->assertCount( 10, $response->get_data() );
if ( 'HEAD' !== $method ) {
$this->assertCount( 10, $response->get_data() );
}
$next_link = add_query_arg(
array(
'page' => 2,
@ -662,7 +676,9 @@ class WP_Test_REST_Categories_Controller extends WP_Test_REST_Controller_Testcas
$headers = $response->get_headers();
$this->assertSame( $total_categories, $headers['X-WP-Total'] );
$this->assertSame( $total_pages, $headers['X-WP-TotalPages'] );
$this->assertCount( 1, $response->get_data() );
if ( 'HEAD' !== $method ) {
$this->assertCount( 1, $response->get_data() );
}
$prev_link = add_query_arg(
array(
'page' => $total_pages - 1,
@ -679,7 +695,9 @@ class WP_Test_REST_Categories_Controller extends WP_Test_REST_Controller_Testcas
$headers = $response->get_headers();
$this->assertSame( $total_categories, $headers['X-WP-Total'] );
$this->assertSame( $total_pages, $headers['X-WP-TotalPages'] );
$this->assertCount( 0, $response->get_data() );
if ( 'HEAD' !== $method ) {
$this->assertCount( 0, $response->get_data() );
}
$prev_link = add_query_arg(
array(
'page' => $total_pages,
@ -1236,4 +1254,112 @@ class WP_Test_REST_Categories_Controller extends WP_Test_REST_Controller_Testcas
$category = get_term( 1, 'category' );
$this->check_taxonomy_term( $category, $data, $response->get_links() );
}
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_items_only_fetches_ids_for_head_requests( $method ) {
$is_head_request = 'HEAD' === $method;
$request = new WP_REST_Request( $method, '/wp/v2/categories' );
$filter = new MockAction();
add_filter( 'terms_pre_query', array( $filter, 'filter' ), 10, 2 );
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 200, $response->get_status() );
if ( $is_head_request ) {
$this->assertEmpty( $response->get_data() );
} else {
$this->assertNotEmpty( $response->get_data() );
}
$args = $filter->get_args();
$this->assertTrue( isset( $args[0][1] ), 'Query parameters were not captured.' );
$this->assertInstanceOf( WP_Term_Query::class, $args[0][1], 'Query parameters were not captured.' );
/** @var WP_Term_Query $query */
$query = $args[0][1];
if ( $is_head_request ) {
$this->assertArrayHasKey( 'fields', $query->query_vars, 'The fields parameter is not set in the query vars.' );
$this->assertSame( 'ids', $query->query_vars['fields'], 'The query must fetch only term IDs.' );
$this->assertArrayHasKey( 'update_term_meta_cache', $query->query_vars, 'The update_term_meta_cache key is missing in the query vars.' );
$this->assertFalse( $query->query_vars['update_term_meta_cache'], 'The update_term_meta_cache value should be false for HEAD requests.' );
} else {
$this->assertTrue(
! array_key_exists( 'fields', $query->query_vars ) || 'ids' !== $query->query_vars['fields'],
'The fields parameter should not be forced to "ids" for non-HEAD requests.'
);
$this->assertArrayHasKey( 'update_term_meta_cache', $query->query_vars, 'The update_term_meta_cache key is missing in the query vars.' );
$this->assertTrue( $query->query_vars['update_term_meta_cache'], 'The update_term_meta_cache value should be true for HEAD requests.' );
}
if ( ! $is_head_request ) {
return;
}
global $wpdb;
$terms_table = preg_quote( $wpdb->terms, '/' );
$pattern = '/SELECT\s+t\.term_id.+FROM\s+' . $terms_table . '\s+AS\s+t\s+INNER\s+JOIN/is';
// Assert that the SQL query only fetches the term_id column.
$this->assertMatchesRegularExpression( $pattern, $query->request, 'The SQL query does not match the expected string.' );
}
/**
* Data provider intended to provide HTTP method names for testing GET and HEAD requests.
*
* @return array
*/
public static function data_readable_http_methods() {
return array(
'GET request' => array( 'GET' ),
'HEAD request' => array( 'HEAD' ),
);
}
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_item_should_allow_adding_headers_via_filter( $method ) {
$category_id = self::factory()->category->create();
$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( 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.' );
}
}

View File

@ -432,19 +432,43 @@ class WP_Test_REST_Comments_Controller extends WP_Test_REST_Controller_Testcase
$this->assertCount( 2, $comments );
}
public function test_get_items_no_permission_for_no_post() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_items_no_permission_for_no_post( $method ) {
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'GET', '/wp/v2/comments' );
$request = new WP_REST_Request( $method, '/wp/v2/comments' );
$request->set_param( 'post', 0 );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_read', $response, 401 );
}
public function test_get_items_edit_context() {
/**
* Data provider intended to provide HTTP method names for testing GET and HEAD requests.
*
* @return array
*/
public static function data_readable_http_methods() {
return array(
'GET request' => array( 'GET' ),
'HEAD request' => array( 'HEAD' ),
);
}
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_items_edit_context( $method ) {
wp_set_current_user( self::$admin_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/comments' );
$request = new WP_REST_Request( $method, '/wp/v2/comments' );
$request->set_param( 'context', 'edit' );
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 200, $response->get_status() );
@ -593,12 +617,18 @@ class WP_Test_REST_Comments_Controller extends WP_Test_REST_Controller_Testcase
$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
}
public function test_get_items_private_post_no_permissions() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_items_private_post_no_permissions( $method ) {
wp_set_current_user( 0 );
$post_id = self::factory()->post->create( array( 'post_status' => 'private' ) );
$request = new WP_REST_Request( 'GET', '/wp/v2/comments' );
$request = new WP_REST_Request( $method, '/wp/v2/comments' );
$request->set_param( 'post', $post_id );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_read_post', $response, 401 );
@ -801,14 +831,20 @@ class WP_Test_REST_Comments_Controller extends WP_Test_REST_Controller_Testcase
$this->assertSame( $id, $data[0]['id'] );
}
public function test_get_comments_pagination_headers() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_comments_pagination_headers( $method ) {
$total_comments = self::$total_comments;
$total_pages = (int) ceil( $total_comments / 10 );
wp_set_current_user( self::$admin_id );
// Start of the index.
$request = new WP_REST_Request( 'GET', '/wp/v2/comments' );
$request = new WP_REST_Request( $method, '/wp/v2/comments' );
$response = rest_get_server()->dispatch( $request );
$headers = $response->get_headers();
$this->assertSame( $total_comments, $headers['X-WP-Total'] );
@ -884,8 +920,14 @@ class WP_Test_REST_Comments_Controller extends WP_Test_REST_Controller_Testcase
$this->assertStringNotContainsString( 'rel="next"', $headers['Link'] );
}
public function test_get_comments_invalid_date() {
$request = new WP_REST_Request( 'GET', '/wp/v2/comments' );
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_comments_invalid_date( $method ) {
$request = new WP_REST_Request( $method, '/wp/v2/comments' );
$request->set_param( 'after', 'foo' );
$request->set_param( 'before', 'bar' );
$response = rest_get_server()->dispatch( $request );
@ -997,23 +1039,41 @@ class WP_Test_REST_Comments_Controller extends WP_Test_REST_Controller_Testcase
$this->assertSame( substr( get_avatar_url( $comment->comment_author_email ), 9 ), substr( $data['author_avatar_urls'][96], 9 ) );
}
public function test_get_comment_invalid_id() {
$request = new WP_REST_Request( 'GET', '/wp/v2/comments/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER );
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_comment_invalid_id( $method ) {
$request = new WP_REST_Request( $method, '/wp/v2/comments/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_comment_invalid_id', $response, 404 );
}
public function test_get_comment_invalid_context() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_comment_invalid_context( $method ) {
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%s', self::$approved_id ) );
$request = new WP_REST_Request( $method, sprintf( '/wp/v2/comments/%s', self::$approved_id ) );
$request->set_param( 'context', 'edit' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_forbidden_context', $response, 401 );
}
public function test_get_comment_invalid_post_id() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_comment_invalid_post_id( $method ) {
wp_set_current_user( 0 );
$comment_id = self::factory()->comment->create(
@ -1023,12 +1083,18 @@ class WP_Test_REST_Comments_Controller extends WP_Test_REST_Controller_Testcase
)
);
$request = new WP_REST_Request( 'GET', '/wp/v2/comments/' . $comment_id );
$request = new WP_REST_Request( $method, '/wp/v2/comments/' . $comment_id );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_post_invalid_id', $response, 404 );
}
public function test_get_comment_invalid_post_id_as_admin() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_comment_invalid_post_id_as_admin( $method ) {
wp_set_current_user( self::$admin_id );
$comment_id = self::factory()->comment->create(
@ -1038,23 +1104,35 @@ class WP_Test_REST_Comments_Controller extends WP_Test_REST_Controller_Testcase
)
);
$request = new WP_REST_Request( 'GET', '/wp/v2/comments/' . $comment_id );
$request = new WP_REST_Request( $method, '/wp/v2/comments/' . $comment_id );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_post_invalid_id', $response, 404 );
}
public function test_get_comment_not_approved() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_comment_not_approved( $method ) {
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%d', self::$hold_id ) );
$request = new WP_REST_Request( $method, sprintf( '/wp/v2/comments/%d', self::$hold_id ) );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_read', $response, 401 );
}
public function test_get_comment_not_approved_same_user() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_comment_not_approved_same_user( $method ) {
wp_set_current_user( self::$admin_id );
$request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%d', self::$hold_id ) );
$request = new WP_REST_Request( $method, sprintf( '/wp/v2/comments/%d', self::$hold_id ) );
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 200, $response->get_status() );
}
@ -1098,7 +1176,13 @@ class WP_Test_REST_Comments_Controller extends WP_Test_REST_Controller_Testcase
$this->assertArrayNotHasKey( 'children', $response->get_links() );
}
public function test_get_comment_with_password_without_edit_post_permission() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_comment_with_password_without_edit_post_permission( $method ) {
wp_set_current_user( self::$subscriber_id );
$args = array(
@ -1108,15 +1192,19 @@ class WP_Test_REST_Comments_Controller extends WP_Test_REST_Controller_Testcase
$password_comment = self::factory()->comment->create( $args );
$request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%s', $password_comment ) );
$request = new WP_REST_Request( $method, sprintf( '/wp/v2/comments/%s', $password_comment ) );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_read', $response, 403 );
}
/**
* @dataProvider data_readable_http_methods
* @ticket 38692
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_comment_with_password_with_valid_password() {
public function test_get_comment_with_password_with_valid_password( $method ) {
wp_set_current_user( self::$subscriber_id );
$args = array(
@ -1126,7 +1214,7 @@ class WP_Test_REST_Comments_Controller extends WP_Test_REST_Controller_Testcase
$password_comment = self::factory()->comment->create( $args );
$request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%s', $password_comment ) );
$request = new WP_REST_Request( $method, sprintf( '/wp/v2/comments/%s', $password_comment ) );
$request->set_param( 'password', 'toomanysecrets' );
$response = rest_get_server()->dispatch( $request );
@ -3365,9 +3453,13 @@ class WP_Test_REST_Comments_Controller extends WP_Test_REST_Controller_Testcase
}
/**
* @dataProvider data_readable_http_methods
* @ticket 42238
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_check_read_post_permission_with_invalid_post_type() {
public function test_check_read_post_permission_with_invalid_post_type( $method ) {
register_post_type(
'bug-post',
array(
@ -3386,8 +3478,95 @@ class WP_Test_REST_Comments_Controller extends WP_Test_REST_Controller_Testcase
$this->setExpectedIncorrectUsage( 'map_meta_cap' );
wp_set_current_user( self::$admin_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/comments/' . $comment_id );
$request = new WP_REST_Request( $method, '/wp/v2/comments/' . $comment_id );
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 403, $response->get_status() );
}
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_items_only_fetches_ids_for_head_requests( $method ) {
$is_head_request = 'HEAD' === $method;
$request = new WP_REST_Request( $method, '/wp/v2/comments' );
$filter = new MockAction();
add_filter( 'comments_pre_query', array( $filter, 'filter' ), 10, 2 );
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 200, $response->get_status() );
if ( $is_head_request ) {
$this->assertEmpty( $response->get_data() );
} else {
$this->assertNotEmpty( $response->get_data() );
}
$args = $filter->get_args();
$this->assertTrue( isset( $args[0][1] ), 'Query parameters were not captured.' );
$this->assertInstanceOf( WP_Comment_Query::class, $args[0][1], 'Query parameters were not captured.' );
/** @var WP_Comment_Query $query */
$query = $args[0][1];
if ( $is_head_request ) {
$this->assertArrayHasKey( 'fields', $query->query_vars, 'The fields parameter is not set in the query vars.' );
$this->assertSame( 'ids', $query->query_vars['fields'], 'The query must fetch only post IDs.' );
$this->assertArrayHasKey( 'update_comment_meta_cache', $query->query_vars, 'The update_comment_meta_cache key is missing in the query vars.' );
$this->assertFalse( $query->query_vars['update_comment_meta_cache'], 'The update_comment_meta_cache value should be false for HEAD requests.' );
} else {
$this->assertTrue( ! array_key_exists( 'fields', $query->query_vars ) || 'ids' !== $query->query_vars['fields'], 'The fields parameter should not be forced to "ids" for non-HEAD requests.' );
$this->assertArrayHasKey( 'update_comment_meta_cache', $query->query_vars, 'The update_comment_meta_cache key is missing in the query vars.' );
$this->assertTrue( $query->query_vars['update_comment_meta_cache'], 'The update_comment_meta_cache value should be true for non-HEAD requests.' );
return;
}
global $wpdb;
$comments_table = preg_quote( $wpdb->comments, '/' );
$pattern = '/^SELECT\s+SQL_CALC_FOUND_ROWS\s+' . $comments_table . '\.comment_ID\s+FROM\s+' . $comments_table . '\s+WHERE/i';
// Assert that the SQL query only fetches the ID column.
$this->assertMatchesRegularExpression( $pattern, $query->request, 'The SQL query does not match the expected string.' );
}
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_item_should_allow_adding_headers_via_filter( $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.' );
$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.' );
}
}

View File

@ -245,17 +245,33 @@ class WP_REST_Global_Styles_Revisions_Controller_Test extends WP_Test_REST_Contr
}
/**
* @dataProvider data_readable_http_methods
* @ticket 58524
* @ticket 56481
*
* @covers WP_REST_Global_Styles_Controller::get_items
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_missing_parent() {
public function test_get_items_missing_parent( $method ) {
wp_set_current_user( self::$admin_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/global-styles/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/revisions' );
$request = new WP_REST_Request( $method, '/wp/v2/global-styles/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/revisions' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 );
}
/**
* Data provider intended to provide HTTP method names for testing GET and HEAD requests.
*
* @return array
*/
public static function data_readable_http_methods() {
return array(
'GET request' => array( 'GET' ),
'HEAD request' => array( 'HEAD' ),
);
}
/**
* Utility function to check the items in WP_REST_Global_Styles_Controller::get_items
* against the expected values.
@ -310,6 +326,19 @@ class WP_REST_Global_Styles_Revisions_Controller_Test extends WP_Test_REST_Contr
$this->check_get_revision_response( $data[2], $this->revision_1 );
}
/**
* @ticket 56481
*
* @covers WP_REST_Global_Styles_Controller::prepare_item_for_response
*/
public function test_get_items_should_return_no_response_body_for_head_requests() {
wp_set_current_user( self::$admin_id );
$request = new WP_REST_Request( 'HEAD', '/wp/v2/global-styles/' . self::$global_styles_id . '/revisions' );
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 200, $response->get_status(), 'Response status is 200.' );
$this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' );
}
/**
* @ticket 59810
*
@ -327,16 +356,34 @@ class WP_REST_Global_Styles_Revisions_Controller_Test extends WP_Test_REST_Contr
}
/**
* @ticket 56481
*
* @covers WP_REST_Global_Styles_Controller::get_item
* @covers WP_REST_Global_Styles_Controller::prepare_item_for_response
*/
public function test_get_item_should_return_no_response_body_for_head_requests() {
wp_set_current_user( self::$admin_id );
$request = new WP_REST_Request( 'HEAD', '/wp/v2/global-styles/' . self::$global_styles_id . '/revisions/' . $this->revision_1_id );
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 200, $response->get_status(), 'Response status is 200.' );
$this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' );
}
/**
* @dataProvider data_readable_http_methods
* @ticket 59810
* @ticket 56481
*
* @covers WP_REST_Global_Styles_Controller::get_revision
*
* @param string $method The HTTP method to use.
*/
public function test_get_item_invalid_revision_id_should_error() {
public function test_get_item_invalid_revision_id_should_error( $method ) {
wp_set_current_user( self::$admin_id );
$expected_error = 'rest_post_invalid_id';
$expected_status = 404;
$request = new WP_REST_Request( 'GET', '/wp/v2/global-styles/' . self::$global_styles_id . '/revisions/20000001' );
$request = new WP_REST_Request( $method, '/wp/v2/global-styles/' . self::$global_styles_id . '/revisions/20000001' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( $expected_error, $response, $expected_status );
@ -419,14 +466,18 @@ class WP_REST_Global_Styles_Revisions_Controller_Test extends WP_Test_REST_Contr
}
/**
* @dataProvider data_readable_http_methods
* @ticket 58524
* @ticket 60131
* @ticket 56481
*
* @covers WP_REST_Global_Styles_Controller::get_item_permissions_check
*
* @param string $method The HTTP method to use.
*/
public function test_get_item_permissions_check() {
public function test_get_item_permissions_check( $method ) {
wp_set_current_user( self::$author_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/global-styles/' . self::$global_styles_id . '/revisions' );
$request = new WP_REST_Request( $method, '/wp/v2/global-styles/' . self::$global_styles_id . '/revisions' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_read', $response, 403 );
@ -435,13 +486,15 @@ class WP_REST_Global_Styles_Revisions_Controller_Test extends WP_Test_REST_Contr
/**
* Tests the pagination header of the first page.
*
* Duplicate of WP_Test_REST_Revisions_Controller::test_get_items_pagination_header_of_the_first_page
*
* @dataProvider data_readable_http_methods
* @ticket 58524
* @ticket 56481
*
* @covers WP_REST_Global_Styles_Controller::get_items
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_pagination_header_of_the_first_page() {
public function test_get_items_pagination_header_of_the_first_page( $method ) {
wp_set_current_user( self::$admin_id );
$rest_route = '/wp/v2/global-styles/' . self::$global_styles_id . '/revisions';
@ -449,7 +502,7 @@ class WP_REST_Global_Styles_Revisions_Controller_Test extends WP_Test_REST_Contr
$total_pages = (int) ceil( $this->total_revisions / $per_page );
$page = 1; // First page.
$request = new WP_REST_Request( 'GET', $rest_route );
$request = new WP_REST_Request( $method, $rest_route );
$request->set_query_params(
array(
'per_page' => $per_page,
@ -476,11 +529,15 @@ class WP_REST_Global_Styles_Revisions_Controller_Test extends WP_Test_REST_Contr
*
* Duplicate of WP_Test_REST_Revisions_Controller::test_get_items_pagination_header_of_the_last_page
*
* @dataProvider data_readable_http_methods
* @ticket 58524
* @ticket 56481
*
* @covers WP_REST_Global_Styles_Controller::get_items
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_pagination_header_of_the_last_page() {
public function test_get_items_pagination_header_of_the_last_page( $method ) {
wp_set_current_user( self::$admin_id );
$rest_route = '/wp/v2/global-styles/' . self::$global_styles_id . '/revisions';
@ -488,7 +545,7 @@ class WP_REST_Global_Styles_Revisions_Controller_Test extends WP_Test_REST_Contr
$total_pages = (int) ceil( $this->total_revisions / $per_page );
$page = 2; // Last page.
$request = new WP_REST_Request( 'GET', $rest_route );
$request = new WP_REST_Request( $method, $rest_route );
$request->set_query_params(
array(
'per_page' => $per_page,
@ -514,18 +571,22 @@ class WP_REST_Global_Styles_Revisions_Controller_Test extends WP_Test_REST_Contr
*
* Duplicate of WP_Test_REST_Revisions_Controller::test_get_items_invalid_per_page_should_error
*
* @dataProvider data_readable_http_methods
* @ticket 58524
* @ticket 56481
*
* @covers WP_REST_Global_Styles_Controller::get_items
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_invalid_per_page_should_error() {
public function test_get_items_invalid_per_page_should_error( $method ) {
wp_set_current_user( self::$admin_id );
$per_page = -1; // Invalid number.
$expected_error = 'rest_invalid_param';
$expected_status = 400;
$request = new WP_REST_Request( 'GET', '/wp/v2/global-styles/' . self::$global_styles_id . '/revisions' );
$request = new WP_REST_Request( $method, '/wp/v2/global-styles/' . self::$global_styles_id . '/revisions' );
$request->set_param( 'per_page', $per_page );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( $expected_error, $response, $expected_status );
@ -536,11 +597,15 @@ class WP_REST_Global_Styles_Revisions_Controller_Test extends WP_Test_REST_Contr
*
* Duplicate of WP_Test_REST_Revisions_Controller::test_get_items_out_of_bounds_page_should_error
*
* @dataProvider data_readable_http_methods
* @ticket 58524
* @ticket 56481
*
* @covers WP_REST_Global_Styles_Controller::get_items
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_out_of_bounds_page_should_error() {
public function test_get_items_out_of_bounds_page_should_error( $method ) {
wp_set_current_user( self::$admin_id );
$per_page = 2;
@ -549,7 +614,7 @@ class WP_REST_Global_Styles_Revisions_Controller_Test extends WP_Test_REST_Contr
$expected_error = 'rest_revision_invalid_page_number';
$expected_status = 400;
$request = new WP_REST_Request( 'GET', '/wp/v2/global-styles/' . self::$global_styles_id . '/revisions' );
$request = new WP_REST_Request( $method, '/wp/v2/global-styles/' . self::$global_styles_id . '/revisions' );
$request->set_query_params(
array(
'per_page' => $per_page,
@ -565,11 +630,15 @@ class WP_REST_Global_Styles_Revisions_Controller_Test extends WP_Test_REST_Contr
*
* Duplicate of WP_Test_REST_Revisions_Controller::test_get_items_invalid_max_pages_should_error
*
* @dataProvider data_readable_http_methods
* @ticket 58524
* @ticket 56481
*
* @covers WP_REST_Global_Styles_Controller::get_items
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_invalid_max_pages_should_error() {
public function test_get_items_invalid_max_pages_should_error( $method ) {
wp_set_current_user( self::$admin_id );
$per_page = 2;
@ -577,7 +646,7 @@ class WP_REST_Global_Styles_Revisions_Controller_Test extends WP_Test_REST_Contr
$expected_error = 'rest_revision_invalid_page_number';
$expected_status = 400;
$request = new WP_REST_Request( 'GET', '/wp/v2/global-styles/' . self::$global_styles_id . '/revisions' );
$request = new WP_REST_Request( $method, '/wp/v2/global-styles/' . self::$global_styles_id . '/revisions' );
$request->set_query_params(
array(
'per_page' => $per_page,
@ -689,11 +758,15 @@ class WP_REST_Global_Styles_Revisions_Controller_Test extends WP_Test_REST_Contr
*
* Duplicate of WP_Test_REST_Revisions_Controller::test_get_items_total_revisions_offset_should_return_empty_data
*
* @dataProvider data_readable_http_methods
* @ticket 58524
* @ticket 56481
*
* @covers WP_REST_Global_Styles_Controller::get_items
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_total_revisions_offset_should_return_empty_data() {
public function test_get_items_total_revisions_offset_should_return_empty_data( $method ) {
wp_set_current_user( self::$admin_id );
$per_page = 2;
@ -701,7 +774,7 @@ class WP_REST_Global_Styles_Revisions_Controller_Test extends WP_Test_REST_Contr
$expected_error = 'rest_revision_invalid_offset_number';
$expected_status = 400;
$request = new WP_REST_Request( 'GET', '/wp/v2/global-styles/' . self::$global_styles_id . '/revisions' );
$request = new WP_REST_Request( $method, '/wp/v2/global-styles/' . self::$global_styles_id . '/revisions' );
$request->set_query_params(
array(
'offset' => $offset,
@ -717,11 +790,15 @@ class WP_REST_Global_Styles_Revisions_Controller_Test extends WP_Test_REST_Contr
*
* Duplicate of WP_Test_REST_Revisions_Controller::test_get_items_out_of_bound_offset_should_error
*
* @dataProvider data_readable_http_methods
* @ticket 58524
* @ticket 56481
*
* @covers WP_REST_Global_Styles_Controller::get_items
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_out_of_bound_offset_should_error() {
public function test_get_items_out_of_bound_offset_should_error( $method ) {
wp_set_current_user( self::$admin_id );
$per_page = 2;
@ -729,7 +806,7 @@ class WP_REST_Global_Styles_Revisions_Controller_Test extends WP_Test_REST_Contr
$expected_error = 'rest_revision_invalid_offset_number';
$expected_status = 400;
$request = new WP_REST_Request( 'GET', '/wp/v2/global-styles/' . self::$global_styles_id . '/revisions' );
$request = new WP_REST_Request( $method, '/wp/v2/global-styles/' . self::$global_styles_id . '/revisions' );
$request->set_query_params(
array(
'offset' => $offset,
@ -745,11 +822,15 @@ class WP_REST_Global_Styles_Revisions_Controller_Test extends WP_Test_REST_Contr
*
* Duplicate of WP_Test_REST_Revisions_Controller::test_get_items_impossible_high_number_offset_should_error
*
* @dataProvider data_readable_http_methods
* @ticket 58524
* @ticket 56481
*
* @covers WP_REST_Global_Styles_Controller::get_items
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_impossible_high_number_offset_should_error() {
public function test_get_items_impossible_high_number_offset_should_error( $method ) {
wp_set_current_user( self::$admin_id );
$per_page = 2;
@ -757,7 +838,7 @@ class WP_REST_Global_Styles_Revisions_Controller_Test extends WP_Test_REST_Contr
$expected_error = 'rest_revision_invalid_offset_number';
$expected_status = 400;
$request = new WP_REST_Request( 'GET', '/wp/v2/global-styles/' . self::$global_styles_id . '/revisions' );
$request = new WP_REST_Request( $method, '/wp/v2/global-styles/' . self::$global_styles_id . '/revisions' );
$request->set_query_params(
array(
'offset' => $offset,
@ -773,11 +854,15 @@ class WP_REST_Global_Styles_Revisions_Controller_Test extends WP_Test_REST_Contr
*
* Duplicate of WP_Test_REST_Revisions_Controller::test_get_items_invalid_offset_should_error
*
* @dataProvider data_readable_http_methods
* @ticket 58524
* @ticket 56481
*
* @covers WP_REST_Global_Styles_Controller::get_items
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_invalid_offset_should_error() {
public function test_get_items_invalid_offset_should_error( $method ) {
wp_set_current_user( self::$admin_id );
$per_page = 2;
@ -785,7 +870,7 @@ class WP_REST_Global_Styles_Revisions_Controller_Test extends WP_Test_REST_Contr
$expected_error = 'rest_invalid_param';
$expected_status = 400;
$request = new WP_REST_Request( 'GET', '/wp/v2/global-styles/' . self::$global_styles_id . '/revisions' );
$request = new WP_REST_Request( $method, '/wp/v2/global-styles/' . self::$global_styles_id . '/revisions' );
$request->set_query_params(
array(
'offset' => $offset,

View File

@ -144,6 +144,32 @@ class WP_REST_Pattern_Directory_Controller_Test extends WP_Test_REST_Controller_
$this->assertSame( array( 'call to action', 'hero section' ), $patterns[2]['keywords'] );
}
/**
* @ticket 56481
*/
public function test_get_items_with_head_request_should_not_prepare_block_patterns_data() {
wp_set_current_user( self::$contributor_id );
self::mock_successful_response( 'browse-all', true );
$request = new WP_REST_Request( 'HEAD', '/wp/v2/pattern-directory/patterns' );
$hook_name = 'rest_prepare_block_pattern';
$filter = new MockAction();
$callback = array( $filter, 'filter' );
add_filter( $hook_name, $callback );
$response = rest_get_server()->dispatch( $request );
remove_filter( $hook_name, $callback );
$this->assertNotWPError( $response );
$response = rest_ensure_response( $response );
$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->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' );
}
/**
* @covers WP_REST_Pattern_Directory_Controller::get_items
*
@ -219,27 +245,49 @@ class WP_REST_Pattern_Directory_Controller_Test extends WP_Test_REST_Controller_
}
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @covers WP_REST_Pattern_Directory_Controller::get_items
*
* @since 5.8.0
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_wdotorg_unavailable() {
public function test_get_items_wdotorg_unavailable( $method ) {
wp_set_current_user( self::$contributor_id );
self::prevent_requests_to_host( 'api.wordpress.org' );
$request = new WP_REST_Request( 'GET', '/wp/v2/pattern-directory/patterns' );
$request = new WP_REST_Request( $method, '/wp/v2/pattern-directory/patterns' );
$response = rest_do_request( $request );
$this->assertErrorResponse( 'patterns_api_failed', $response, 500 );
}
/**
* Data provider intended to provide HTTP method names for testing GET and HEAD requests.
*
* @return array
*/
public static function data_readable_http_methods() {
return array(
'GET request' => array( 'GET' ),
'HEAD request' => array( 'HEAD' ),
);
}
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @covers WP_REST_Pattern_Directory_Controller::get_items
*
* @since 5.8.0
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_logged_out() {
$request = new WP_REST_Request( 'GET', '/wp/v2/pattern-directory/patterns' );
public function test_get_items_logged_out( $method ) {
$request = new WP_REST_Request( $method, '/wp/v2/pattern-directory/patterns' );
$request->set_query_params( array( 'search' => 'button' ) );
$response = rest_do_request( $request );
@ -283,15 +331,20 @@ class WP_REST_Pattern_Directory_Controller_Test extends WP_Test_REST_Controller_
}
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @covers WP_REST_Pattern_Directory_Controller::get_items
*
* @since 5.8.0
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_invalid_response_data() {
public function test_get_items_invalid_response_data( $method ) {
wp_set_current_user( self::$contributor_id );
self::mock_successful_response( 'invalid-data', true );
$request = new WP_REST_Request( 'GET', '/wp/v2/pattern-directory/patterns' );
$request = new WP_REST_Request( $method, '/wp/v2/pattern-directory/patterns' );
$response = rest_do_request( $request );
$this->assertSame( 500, $response->status );

View File

@ -44,14 +44,32 @@ class WP_Test_REST_Post_Types_Controller extends WP_Test_REST_Controller_Testcas
$this->assertArrayNotHasKey( 'revision', $data );
}
public function test_get_items_invalid_permission_for_context() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_items_invalid_permission_for_context( $method ) {
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'GET', '/wp/v2/types' );
$request = new WP_REST_Request( $method, '/wp/v2/types' );
$request->set_param( 'context', 'edit' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_view', $response, 401 );
}
/**
* Data provider intended to provide HTTP method names for testing GET and HEAD requests.
*
* @return array
*/
public static function data_readable_http_methods() {
return array(
'GET request' => array( 'GET' ),
'HEAD request' => array( 'HEAD' ),
);
}
public function test_get_item() {
$request = new WP_REST_Request( 'GET', '/wp/v2/types/post' );
$response = rest_get_server()->dispatch( $request );
@ -60,6 +78,42 @@ class WP_Test_REST_Post_Types_Controller extends WP_Test_REST_Controller_Testcas
$this->assertSame( array( 'category', 'post_tag' ), $data['taxonomies'] );
}
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
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( 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.' );
}
/**
* @ticket 53656
*/
@ -106,8 +160,14 @@ class WP_Test_REST_Post_Types_Controller extends WP_Test_REST_Controller_Testcas
$this->assertSame( array(), $data['taxonomies'] );
}
public function test_get_item_invalid_type() {
$request = new WP_REST_Request( 'GET', '/wp/v2/types/invalid' );
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_item_invalid_type( $method ) {
$request = new WP_REST_Request( $method, '/wp/v2/types/invalid' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_type_invalid', $response, 404 );
}
@ -121,9 +181,15 @@ class WP_Test_REST_Post_Types_Controller extends WP_Test_REST_Controller_Testcas
$this->check_post_type_object_response( 'edit', $response );
}
public function test_get_item_invalid_permission_for_context() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_item_invalid_permission_for_context( $method ) {
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'GET', '/wp/v2/types/post' );
$request = new WP_REST_Request( $method, '/wp/v2/types/post' );
$request->set_param( 'context', 'edit' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_forbidden_context', $response, 401 );
@ -245,6 +311,22 @@ class WP_Test_REST_Post_Types_Controller extends WP_Test_REST_Controller_Testcas
return 123;
}
/**
* @ticket 56481
*/
public function test_get_items_with_head_request_should_not_prepare_post_types_data() {
$request = new WP_REST_Request( 'HEAD', '/wp/v2/types' );
$hook_name = 'rest_prepare_post_type';
$filter = new MockAction();
$callback = array( $filter, 'filter' );
add_filter( $hook_name, $callback );
$response = rest_get_server()->dispatch( $request );
remove_filter( $hook_name, $callback );
$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->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' );
}
protected function check_post_type_obj( $context, $post_type_obj, $data, $links ) {
$this->assertSame( $post_type_obj->label, $data['name'] );
$this->assertSame( $post_type_obj->name, $data['slug'] );

View File

@ -274,12 +274,44 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te
}
/**
* A valid query that returns 0 results should return an empty JSON list.
*
* @link https://github.com/WP-API/WP-API/issues/862
* @ticket 56481
*/
public function test_get_items_empty_query() {
$request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
public function test_get_items_with_head_request_should_not_prepare_post_data() {
$request = new WP_REST_Request( 'HEAD', '/wp/v2/posts' );
$hook_name = 'rest_prepare_post';
$filter = new MockAction();
$callback = array( $filter, 'filter' );
add_filter( $hook_name, $callback );
$response = rest_get_server()->dispatch( $request );
remove_filter( $hook_name, $callback );
$this->assertNotWPError( $response );
$response = rest_ensure_response( $response );
$this->assertSame( 200, $response->get_status(), 'The response status should be 200.' );
$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->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' );
}
/**
* A valid query that returns 0 results should return an empty JSON list.
* In case of a HEAD request, the response should not contain a body.
*
* @dataProvider data_readable_http_methods
* @link https://github.com/WP-API/WP-API/issues/862
* @ticket 56481
*
* @covers WP_REST_Posts_Controller::get_items
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_empty_query( $method ) {
$request = new WP_REST_Request( $method, '/wp/v2/posts' );
$request->set_query_params(
array(
'author' => REST_TESTS_IMPOSSIBLY_HIGH_NUMBER,
@ -287,85 +319,147 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te
);
$response = rest_get_server()->dispatch( $request );
$this->assertEmpty( $response->get_data() );
$this->assertSame( 200, $response->get_status() );
if ( $request->is_method( 'HEAD' ) ) {
$this->assertNull( $response->get_data(), 'Failed asserting that response data is null for HEAD request.' );
} else {
$this->assertSame( array(), $response->get_data(), 'Failed asserting that response data is an empty array for GET request.' );
}
$headers = $response->get_headers();
$this->assertSame( 0, $headers['X-WP-Total'], 'Failed asserting that X-WP-Total header is 0.' );
$this->assertSame( 0, $headers['X-WP-TotalPages'], 'Failed asserting that X-WP-TotalPages header is 0.' );
}
public function test_get_items_author_query() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_author_query( $method ) {
self::factory()->post->create( array( 'post_author' => self::$editor_id ) );
self::factory()->post->create( array( 'post_author' => self::$author_id ) );
$total_posts = self::$total_posts + 2;
// All posts in the database.
$request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
$request = new WP_REST_Request( $method, '/wp/v2/posts' );
$request->set_param( 'per_page', self::$per_page );
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 200, $response->get_status() );
$this->assertCount( $total_posts, $response->get_data() );
if ( $request->is_method( 'get' ) ) {
$this->assertCount( $total_posts, $response->get_data() );
} else {
$this->assertNull( $response->get_data(), 'Failed asserting that response data is null for HEAD request.' );
$headers = $response->get_headers();
$this->assertSame( $total_posts, $headers['X-WP-Total'] );
}
// Limit to editor and author.
$request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
$request = new WP_REST_Request( $method, '/wp/v2/posts' );
$request->set_param( 'author', array( self::$editor_id, self::$author_id ) );
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 200, $response->get_status() );
$data = $response->get_data();
$this->assertCount( 2, $data );
$this->assertSameSets( array( self::$editor_id, self::$author_id ), wp_list_pluck( $data, 'author' ) );
if ( $request->is_method( 'get' ) ) {
$this->assertCount( 2, $data );
$this->assertSameSets( array( self::$editor_id, self::$author_id ), wp_list_pluck( $data, 'author' ) );
} else {
$this->assertNull( $data, 'Failed asserting that response data is null for HEAD request.' );
$headers = $response->get_headers();
$this->assertSame( 2, $headers['X-WP-Total'], 'Failed asserting that X-WP-Total header is 2.' );
}
// Limit to editor.
$request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
$request = new WP_REST_Request( $method, '/wp/v2/posts' );
$request->set_param( 'author', self::$editor_id );
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 200, $response->get_status() );
$data = $response->get_data();
$this->assertCount( 1, $data );
$this->assertSame( self::$editor_id, $data[0]['author'] );
if ( $request->is_method( 'get' ) ) {
$this->assertCount( 1, $data );
$this->assertSame( self::$editor_id, $data[0]['author'] );
} else {
$this->assertNull( $data, 'Failed asserting that response data is null for HEAD request.' );
$headers = $response->get_headers();
$this->assertSame( 1, $headers['X-WP-Total'], 'Failed asserting that X-WP-Total header is 1.' );
}
}
public function test_get_items_author_exclude_query() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_author_exclude_query( $method ) {
self::factory()->post->create( array( 'post_author' => self::$editor_id ) );
self::factory()->post->create( array( 'post_author' => self::$author_id ) );
$total_posts = self::$total_posts + 2;
// All posts in the database.
$request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
$request = new WP_REST_Request( $method, '/wp/v2/posts' );
$request->set_param( 'per_page', self::$per_page );
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 200, $response->get_status() );
$this->assertCount( $total_posts, $response->get_data() );
if ( $request->is_method( 'get' ) ) {
$this->assertCount( $total_posts, $response->get_data() );
} else {
$this->assertNull( $response->get_data(), 'Failed asserting that response data is null for HEAD request.' );
$headers = $response->get_headers();
$this->assertSame( $total_posts, $headers['X-WP-Total'], 'Failed asserting that the number of posts is correct.' );
}
// Exclude editor and author.
$request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
$request = new WP_REST_Request( $method, '/wp/v2/posts' );
$request->set_param( 'per_page', self::$per_page );
$request->set_param( 'author_exclude', array( self::$editor_id, self::$author_id ) );
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 200, $response->get_status() );
$data = $response->get_data();
$this->assertCount( $total_posts - 2, $data );
$this->assertNotEquals( self::$editor_id, $data[0]['author'] );
$this->assertNotEquals( self::$author_id, $data[0]['author'] );
if ( $request->is_method( 'get' ) ) {
$this->assertCount( $total_posts - 2, $data );
$this->assertNotEquals( self::$editor_id, $data[0]['author'] );
$this->assertNotEquals( self::$author_id, $data[0]['author'] );
} else {
$this->assertNull( $response->get_data(), 'Failed asserting that response data is null for HEAD request.' );
$headers = $response->get_headers();
$this->assertSame( $total_posts - 2, $headers['X-WP-Total'], 'Failed asserting that the number of posts is correct.' );
}
// Exclude editor.
$request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
$request = new WP_REST_Request( $method, '/wp/v2/posts' );
$request->set_param( 'per_page', self::$per_page );
$request->set_param( 'author_exclude', self::$editor_id );
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 200, $response->get_status() );
$data = $response->get_data();
$this->assertCount( $total_posts - 1, $data );
$this->assertNotEquals( self::$editor_id, $data[0]['author'] );
$this->assertNotEquals( self::$editor_id, $data[1]['author'] );
if ( $request->is_method( 'get' ) ) {
$this->assertCount( $total_posts - 1, $data );
$this->assertNotEquals( self::$editor_id, $data[0]['author'] );
$this->assertNotEquals( self::$editor_id, $data[1]['author'] );
} else {
$this->assertNull( $response->get_data(), 'Failed asserting that response data is null for HEAD request.' );
$headers = $response->get_headers();
$this->assertSame( $total_posts - 1, $headers['X-WP-Total'], 'Failed asserting that the number of posts is correct.' );
}
// Invalid 'author_exclude' should error.
$request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
$request = new WP_REST_Request( $method, '/wp/v2/posts' );
$request->set_param( 'author_exclude', 'invalid' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
}
public function test_get_items_include_query() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_include_query( $method ) {
$id1 = self::factory()->post->create(
array(
'post_status' => 'publish',
@ -379,26 +473,40 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te
)
);
$request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
$request = new WP_REST_Request( $method, '/wp/v2/posts' );
// Order defaults to date descending.
$request->set_param( 'include', array( $id1, $id2 ) );
$response = rest_get_server()->dispatch( $request );
$data = $response->get_data();
$this->assertCount( 2, $data );
$this->assertSame( $id2, $data[0]['id'] );
if ( $request->is_method( 'get' ) ) {
$this->assertCount( 2, $data );
$this->assertSame( $id2, $data[0]['id'] );
} else {
$this->assertNull( $data, 'Failed asserting that response data is null for HEAD request.' );
$headers = $response->get_headers();
$this->assertSame( 2, $headers['X-WP-Total'], 'Failed asserting that the number of posts is correct.' );
}
$this->assertPostsOrderedBy( '{posts}.post_date DESC' );
// 'orderby' => 'include'.
$request->set_param( 'orderby', 'include' );
$response = rest_get_server()->dispatch( $request );
$data = $response->get_data();
$this->assertCount( 2, $data );
$this->assertSame( $id1, $data[0]['id'] );
if ( $request->is_method( 'get' ) ) {
$this->assertCount( 2, $data );
$this->assertSame( $id1, $data[0]['id'] );
} else {
$this->assertNull( $data, 'Failed asserting that response data is null for HEAD request.' );
$headers = $response->get_headers();
$this->assertSame( 2, $headers['X-WP-Total'], 'Failed asserting that the number of posts is correct.' );
}
$this->assertPostsOrderedBy( "FIELD({posts}.ID,$id1,$id2)" );
// Invalid 'include' should error.
$request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
$request = new WP_REST_Request( $method, '/wp/v2/posts' );
$request->set_param( 'include', 'invalid' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
@ -1729,12 +1837,18 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te
$this->assertSameSets( $parent_ids, $primed[1] );
}
public function test_get_items_pagination_headers() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_items_pagination_headers( $method ) {
$total_posts = self::$total_posts;
$total_pages = (int) ceil( $total_posts / 10 );
// Start of the index.
$request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
$request = new WP_REST_Request( $method, '/wp/v2/posts' );
$response = rest_get_server()->dispatch( $request );
$headers = $response->get_headers();
$this->assertSame( $total_posts, $headers['X-WP-Total'] );
@ -1752,7 +1866,7 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te
self::factory()->post->create();
++$total_posts;
++$total_pages;
$request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
$request = new WP_REST_Request( $method, '/wp/v2/posts' );
$request->set_param( 'page', 3 );
$response = rest_get_server()->dispatch( $request );
$headers = $response->get_headers();
@ -1774,7 +1888,7 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te
$this->assertStringContainsString( '<' . $next_link . '>; rel="next"', $headers['Link'] );
// Last page.
$request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
$request = new WP_REST_Request( $method, '/wp/v2/posts' );
$request->set_param( 'page', $total_pages );
$response = rest_get_server()->dispatch( $request );
$headers = $response->get_headers();
@ -1790,7 +1904,7 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te
$this->assertStringNotContainsString( 'rel="next"', $headers['Link'] );
// Out of bounds.
$request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
$request = new WP_REST_Request( $method, '/wp/v2/posts' );
$request->set_param( 'page', 100 );
$response = rest_get_server()->dispatch( $request );
$headers = $response->get_headers();
@ -1798,7 +1912,7 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te
// With query params.
$total_pages = (int) ceil( $total_posts / 5 );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
$request = new WP_REST_Request( $method, '/wp/v2/posts' );
$request->set_query_params(
array(
'per_page' => 5,
@ -1827,6 +1941,78 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te
$this->assertStringContainsString( '<' . $next_link . '>; rel="next"', $headers['Link'] );
}
/**
* Data provider intended to provide HTTP method names for testing GET and HEAD requests.
*
* @return array
*/
public static function data_readable_http_methods() {
return array(
'GET request' => array( 'GET' ),
'HEAD request' => array( 'HEAD' ),
);
}
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_items_only_fetches_ids_for_head_requests( $method ) {
$is_head_request = 'HEAD' === $method;
$request = new WP_REST_Request( $method, '/wp/v2/posts' );
$filter = new MockAction();
add_filter( 'posts_pre_query', array( $filter, 'filter' ), 10, 2 );
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 200, $response->get_status() );
if ( $is_head_request ) {
$this->assertEmpty( $response->get_data() );
} else {
$this->assertNotEmpty( $response->get_data() );
}
$args = $filter->get_args();
$this->assertTrue( isset( $args[0][1] ), 'Query parameters were not captured.' );
$this->assertInstanceOf( WP_Query::class, $args[0][1], 'Query parameters were not captured.' );
/** @var WP_Query $query */
$query = $args[0][1];
if ( $is_head_request ) {
$this->assertArrayHasKey( 'fields', $query->query, 'The fields parameter is not set in the query vars.' );
$this->assertSame( 'ids', $query->query['fields'], 'The query must fetch only post IDs.' );
$this->assertArrayHasKey( 'fields', $query->query_vars, 'The fields parameter is not set in the query vars.' );
$this->assertSame( 'ids', $query->query_vars['fields'], 'The query must fetch only post IDs.' );
$this->assertArrayHasKey( 'update_post_term_cache', $query->query_vars, 'The "update_post_term_cache" parameter is missing in the query vars.' );
$this->assertFalse( $query->query_vars['update_post_term_cache'], 'The "update_post_term_cache" parameter must be false for HEAD requests.' );
$this->assertArrayHasKey( 'update_post_meta_cache', $query->query_vars, 'The "update_post_meta_cache" parameter is missing in the query vars.' );
$this->assertFalse( $query->query_vars['update_post_meta_cache'], 'The "update_post_meta_cache" parameter must be false for HEAD requests.' );
} else {
$this->assertTrue( ! array_key_exists( 'fields', $query->query ) || 'ids' !== $query->query['fields'], 'The fields parameter should not be forced to "ids" for non-HEAD requests.' );
$this->assertTrue( ! array_key_exists( 'fields', $query->query_vars ) || 'ids' !== $query->query_vars['fields'], 'The fields parameter should not be forced to "ids" for non-HEAD requests.' );
$this->assertArrayHasKey( 'update_post_term_cache', $query->query_vars, 'The "update_post_term_cache" parameter is missing in the query vars.' );
$this->assertTrue( $query->query_vars['update_post_term_cache'], 'The "update_post_term_cache" parameter must be true for non-HEAD requests.' );
$this->assertArrayHasKey( 'update_post_meta_cache', $query->query_vars, 'The "update_post_meta_cache" parameter is missing in the query vars.' );
$this->assertTrue( $query->query_vars['update_post_meta_cache'], 'The "update_post_meta_cache" parameter must be true for non-HEAD requests.' );
}
if ( ! $is_head_request ) {
return;
}
global $wpdb;
$posts_table = preg_quote( $wpdb->posts, '/' );
$pattern = '/^SELECT\s+SQL_CALC_FOUND_ROWS\s+' . $posts_table . '\.ID\s+FROM\s+' . $posts_table . '\s+WHERE/i';
// Assert that the SQL query only fetches the ID column.
$this->assertMatchesRegularExpression( $pattern, $query->request, 'The SQL query does not match the expected string.' );
}
public function test_get_items_status_draft_permissions() {
$draft_id = self::factory()->post->create( array( 'post_status' => 'draft' ) );
@ -1973,6 +2159,43 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te
$this->check_get_post_response( $response, 'view' );
}
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
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' );
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->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.' );
}
public function test_get_item_links() {
$request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
$response = rest_get_server()->dispatch( $request );

View File

@ -1081,4 +1081,49 @@ class Tests_REST_Request extends WP_UnitTestCase {
$this->assertWPError( $valid );
$this->assertSame( 'rest_invalid_param', $valid->get_error_code() );
}
/**
* Tests that WP_REST_Request::is_method() correctly detects the request method,
* regardless of case sensitivity.
*
* @dataProvider data_is_method_should_detect_method_ignoring_case
* @ticket 56481
*
* @param string $method The expected HTTP method of the request.
* @param string $input_method The HTTP method to check against.
* @param bool $expected The expected result of the is_method() check.
*/
public function test_is_method_should_detect_method_ignoring_case( $method, $input_method, $expected ) {
$request = new WP_REST_Request();
$request->set_method( $method );
$result = $request->is_method( $input_method );
$this->assertSame( $expected, $result, 'Failed asserting that the WP_REST_Request::is_method() method correctly detects the request method.' );
}
/**
* Provides test cases for verifying HTTP method comparison is case-insensitive.
*
* @return array
*/
public function data_is_method_should_detect_method_ignoring_case() {
return array(
// GET.
'GET same case' => array( 'GET', 'GET', true ),
'GET different case' => array( 'GET', 'get', true ),
'GET different case #2' => array( 'GET', 'get', true ),
'GET different case #3' => array( 'GET', 'gEt', true ),
'GET wrong method' => array( 'GET', 'POST', false ),
// POST.
'POST same case' => array( 'POST', 'POST', true ),
'POST different case' => array( 'POST', 'post', true ),
'POST different case #2' => array( 'POST', 'pOsT', true ),
'POST wrong method' => array( 'POST', 'GET', false ),
// HEAD.
'HEAD same case' => array( 'HEAD', 'HEAD', true ),
'HEAD different case' => array( 'HEAD', 'head', true ),
'HEAD different case #2' => array( 'HEAD', 'HeAd', true ),
'HEAD wrong method' => array( 'HEAD', 'GET', false ),
);
}
}

View File

@ -162,9 +162,38 @@ class WP_Test_REST_Revisions_Controller extends WP_Test_REST_Controller_Testcase
$this->check_get_revision_response( $data[2], $this->revision_1 );
}
public function test_get_items_no_permission() {
/**
* @ticket 56481
*/
public function test_get_items_with_head_request_should_not_prepare_revisions_data() {
wp_set_current_user( self::$editor_id );
$hook_name = 'rest_prepare_revision';
$filter = new MockAction();
$callback = array( $filter, 'filter' );
add_filter( $hook_name, $callback );
$request = new WP_REST_Request( 'HEAD', '/wp/v2/posts/' . self::$post_id . '/revisions' );
$response = rest_get_server()->dispatch( $request );
remove_filter( $hook_name, $callback );
$this->assertNotWPError( $response );
$response = rest_ensure_response( $response );
$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->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' );
}
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_no_permission( $method ) {
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/revisions' );
$request = new WP_REST_Request( $method, '/wp/v2/posts/' . self::$post_id . '/revisions' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_read', $response, 401 );
@ -173,16 +202,40 @@ class WP_Test_REST_Revisions_Controller extends WP_Test_REST_Controller_Testcase
$this->assertErrorResponse( 'rest_cannot_read', $response, 403 );
}
public function test_get_items_missing_parent() {
/**
* Data provider intended to provide HTTP method names for testing GET and HEAD requests.
*
* @return array
*/
public static function data_readable_http_methods() {
return array(
'GET request' => array( 'GET' ),
'HEAD request' => array( 'HEAD' ),
);
}
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_missing_parent( $method ) {
wp_set_current_user( self::$editor_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/revisions' );
$request = new WP_REST_Request( $method, '/wp/v2/posts/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/revisions' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 );
}
public function test_get_items_invalid_parent_post_type() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_invalid_parent_post_type( $method ) {
wp_set_current_user( self::$editor_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$page_id . '/revisions' );
$request = new WP_REST_Request( $method, '/wp/v2/posts/' . self::$page_id . '/revisions' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 );
}
@ -213,6 +266,43 @@ class WP_Test_REST_Revisions_Controller extends WP_Test_REST_Controller_Testcase
$this->assertSame( self::$editor_id, $data['author'] );
}
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_item_should_allow_adding_headers_via_filter( $method ) {
wp_set_current_user( self::$editor_id );
$request = new WP_REST_Request( $method, '/wp/v2/posts/' . self::$post_id . '/revisions/' . $this->revision_id1 );
$hook_name = 'rest_prepare_revision';
$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->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 ( 'GET' === $method ) {
return null;
}
$this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' );
}
public function test_get_item_embed_context() {
wp_set_current_user( self::$editor_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/revisions/' . $this->revision_id1 );
@ -231,9 +321,15 @@ class WP_Test_REST_Revisions_Controller extends WP_Test_REST_Controller_Testcase
$this->assertSameSets( $fields, array_keys( $data ) );
}
public function test_get_item_no_permission() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_item_no_permission( $method ) {
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/revisions/' . $this->revision_id1 );
$request = new WP_REST_Request( $method, '/wp/v2/posts/' . self::$post_id . '/revisions/' . $this->revision_id1 );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_read', $response, 401 );
@ -242,16 +338,28 @@ class WP_Test_REST_Revisions_Controller extends WP_Test_REST_Controller_Testcase
$this->assertErrorResponse( 'rest_cannot_read', $response, 403 );
}
public function test_get_item_missing_parent() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_item_missing_parent( $method ) {
wp_set_current_user( self::$editor_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/revisions/' . $this->revision_id1 );
$request = new WP_REST_Request( $method, '/wp/v2/posts/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/revisions/' . $this->revision_id1 );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 );
}
public function test_get_item_invalid_parent_post_type() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_item_invalid_parent_post_type( $method ) {
wp_set_current_user( self::$editor_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$page_id . '/revisions/' . $this->revision_id1 );
$request = new WP_REST_Request( $method, '/wp/v2/posts/' . self::$page_id . '/revisions/' . $this->revision_id1 );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 );
}
@ -269,11 +377,15 @@ class WP_Test_REST_Revisions_Controller extends WP_Test_REST_Controller_Testcase
}
/**
* @dataProvider data_readable_http_methods
* @ticket 59875
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_item_invalid_parent_id() {
public function test_get_item_invalid_parent_id( $method ) {
wp_set_current_user( self::$editor_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/revisions/' . $this->revision_2_1_id );
$request = new WP_REST_Request( $method, '/wp/v2/posts/' . self::$post_id . '/revisions/' . $this->revision_2_1_id );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_revision_parent_id_mismatch', $response, 404 );
@ -510,9 +622,13 @@ class WP_Test_REST_Revisions_Controller extends WP_Test_REST_Controller_Testcase
/**
* Test the pagination header of the first page.
*
* @dataProvider data_readable_http_methods
* @ticket 40510
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_pagination_header_of_the_first_page() {
public function test_get_items_pagination_header_of_the_first_page( $method ) {
wp_set_current_user( self::$editor_id );
$rest_route = '/wp/v2/posts/' . self::$post_id . '/revisions';
@ -520,7 +636,7 @@ class WP_Test_REST_Revisions_Controller extends WP_Test_REST_Controller_Testcase
$total_pages = (int) ceil( $this->total_revisions / $per_page );
$page = 1; // First page.
$request = new WP_REST_Request( 'GET', $rest_route );
$request = new WP_REST_Request( $method, $rest_route );
$request->set_query_params(
array(
'per_page' => $per_page,
@ -545,9 +661,13 @@ class WP_Test_REST_Revisions_Controller extends WP_Test_REST_Controller_Testcase
/**
* Test the pagination header of the last page.
*
* @dataProvider data_readable_http_methods
* @ticket 40510
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_pagination_header_of_the_last_page() {
public function test_get_items_pagination_header_of_the_last_page( $method ) {
wp_set_current_user( self::$editor_id );
$rest_route = '/wp/v2/posts/' . self::$post_id . '/revisions';
@ -555,7 +675,7 @@ class WP_Test_REST_Revisions_Controller extends WP_Test_REST_Controller_Testcase
$total_pages = (int) ceil( $this->total_revisions / $per_page );
$page = 2; // Last page.
$request = new WP_REST_Request( 'GET', $rest_route );
$request = new WP_REST_Request( $method, $rest_route );
$request->set_query_params(
array(
'per_page' => $per_page,
@ -576,19 +696,24 @@ class WP_Test_REST_Revisions_Controller extends WP_Test_REST_Controller_Testcase
$this->assertStringContainsString( '<' . $prev_link . '>; rel="prev"', $headers['Link'] );
}
/**
* Test that invalid 'per_page' query should error.
*
* @dataProvider data_readable_http_methods
* @ticket 40510
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_invalid_per_page_should_error() {
public function test_get_items_invalid_per_page_should_error( $method ) {
wp_set_current_user( self::$editor_id );
$per_page = -1; // Invalid number.
$expected_error = 'rest_invalid_param';
$expected_status = 400;
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/revisions' );
$request = new WP_REST_Request( $method, '/wp/v2/posts/' . self::$post_id . '/revisions' );
$request->set_param( 'per_page', $per_page );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( $expected_error, $response, $expected_status );
@ -597,9 +722,13 @@ class WP_Test_REST_Revisions_Controller extends WP_Test_REST_Controller_Testcase
/**
* Test that out of bounds 'page' query should error.
*
* @dataProvider data_readable_http_methods
* @ticket 40510
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_out_of_bounds_page_should_error() {
public function test_get_items_out_of_bounds_page_should_error( $method ) {
wp_set_current_user( self::$editor_id );
$per_page = 2;
@ -608,7 +737,7 @@ class WP_Test_REST_Revisions_Controller extends WP_Test_REST_Controller_Testcase
$expected_error = 'rest_revision_invalid_page_number';
$expected_status = 400;
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/revisions' );
$request = new WP_REST_Request( $method, '/wp/v2/posts/' . self::$post_id . '/revisions' );
$request->set_query_params(
array(
'per_page' => $per_page,
@ -622,9 +751,13 @@ class WP_Test_REST_Revisions_Controller extends WP_Test_REST_Controller_Testcase
/**
* Test that impossibly high 'page' query should error.
*
* @dataProvider data_readable_http_methods
* @ticket 40510
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_invalid_max_pages_should_error() {
public function test_get_items_invalid_max_pages_should_error( $method ) {
wp_set_current_user( self::$editor_id );
$per_page = 2;
@ -632,7 +765,7 @@ class WP_Test_REST_Revisions_Controller extends WP_Test_REST_Controller_Testcase
$expected_error = 'rest_revision_invalid_page_number';
$expected_status = 400;
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/revisions' );
$request = new WP_REST_Request( $method, '/wp/v2/posts/' . self::$post_id . '/revisions' );
$request->set_query_params(
array(
'per_page' => $per_page,
@ -770,9 +903,13 @@ class WP_Test_REST_Revisions_Controller extends WP_Test_REST_Controller_Testcase
/**
* Test that out of bound 'offset' query should error.
*
* @dataProvider data_readable_http_methods
* @ticket 40510
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_out_of_bound_offset_should_error() {
public function test_get_items_out_of_bound_offset_should_error( $method ) {
wp_set_current_user( self::$editor_id );
$per_page = 2;
@ -780,7 +917,7 @@ class WP_Test_REST_Revisions_Controller extends WP_Test_REST_Controller_Testcase
$expected_error = 'rest_revision_invalid_offset_number';
$expected_status = 400;
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/revisions' );
$request = new WP_REST_Request( $method, '/wp/v2/posts/' . self::$post_id . '/revisions' );
$request->set_query_params(
array(
'offset' => $offset,
@ -794,9 +931,13 @@ class WP_Test_REST_Revisions_Controller extends WP_Test_REST_Controller_Testcase
/**
* Test that impossible high number for 'offset' query should error.
*
* @dataProvider data_readable_http_methods
* @ticket 40510
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_impossible_high_number_offset_should_error() {
public function test_get_items_impossible_high_number_offset_should_error( $method ) {
wp_set_current_user( self::$editor_id );
$per_page = 2;
@ -804,7 +945,7 @@ class WP_Test_REST_Revisions_Controller extends WP_Test_REST_Controller_Testcase
$expected_error = 'rest_revision_invalid_offset_number';
$expected_status = 400;
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/revisions' );
$request = new WP_REST_Request( $method, '/wp/v2/posts/' . self::$post_id . '/revisions' );
$request->set_query_params(
array(
'offset' => $offset,
@ -818,9 +959,13 @@ class WP_Test_REST_Revisions_Controller extends WP_Test_REST_Controller_Testcase
/**
* Test that invalid 'offset' query should error.
*
* @dataProvider data_readable_http_methods
* @ticket 40510
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_invalid_offset_should_error() {
public function test_get_items_invalid_offset_should_error( $method ) {
wp_set_current_user( self::$editor_id );
$per_page = 2;
@ -828,7 +973,7 @@ class WP_Test_REST_Revisions_Controller extends WP_Test_REST_Controller_Testcase
$expected_error = 'rest_invalid_param';
$expected_status = 400;
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/revisions' );
$request = new WP_REST_Request( $method, '/wp/v2/posts/' . self::$post_id . '/revisions' );
$request->set_query_params(
array(
'offset' => $offset,

View File

@ -160,6 +160,101 @@ class WP_Test_REST_Search_Controller extends WP_Test_REST_Controller_Testcase {
);
}
/**
* Test pagination headers.
*
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_items_pagination_headers( $method ) {
$total_posts = count( self::$my_title_post_ids ) + count( self::$my_title_page_ids ) + count( self::$my_content_post_ids );
$per_page = 3;
$total_pages = (int) ceil( $total_posts / $per_page );
// Start of the index.
$response = $this->do_request_with_params(
array(
'per_page' => $per_page,
),
$method
);
$headers = $response->get_headers();
$this->assertSame( $total_posts, $headers['X-WP-Total'] );
$this->assertSame( $total_pages, $headers['X-WP-TotalPages'] );
$next_link = add_query_arg(
array(
'per_page' => $per_page,
'page' => 2,
),
rest_url( '/wp/v2/search' )
);
$this->assertStringNotContainsString( 'rel="prev"', $headers['Link'] );
$this->assertStringContainsString( '<' . $next_link . '>; rel="next"', $headers['Link'] );
$response = $this->do_request_with_params(
array(
'per_page' => $per_page,
'page' => 3,
),
$method
);
$headers = $response->get_headers();
$this->assertSame( $total_posts, $headers['X-WP-Total'] );
$this->assertSame( $total_pages, $headers['X-WP-TotalPages'] );
$prev_link = add_query_arg(
array(
'per_page' => $per_page,
'page' => 2,
),
rest_url( '/wp/v2/search' )
);
$this->assertStringContainsString( '<' . $prev_link . '>; rel="prev"', $headers['Link'] );
$next_link = add_query_arg(
array(
'per_page' => $per_page,
'page' => 4,
),
rest_url( '/wp/v2/search' )
);
$this->assertStringContainsString( '<' . $next_link . '>; rel="next"', $headers['Link'] );
// Last page.
$response = $this->do_request_with_params(
array(
'per_page' => $per_page,
'page' => $total_pages,
),
$method
);
$headers = $response->get_headers();
$this->assertSame( $total_posts, $headers['X-WP-Total'] );
$this->assertSame( $total_pages, $headers['X-WP-TotalPages'] );
$prev_link = add_query_arg(
array(
'per_page' => $per_page,
'page' => $total_pages - 1,
),
rest_url( '/wp/v2/search' )
);
$this->assertStringContainsString( '<' . $prev_link . '>; rel="prev"', $headers['Link'] );
$this->assertStringNotContainsString( 'rel="next"', $headers['Link'] );
}
/**
* Data provider intended to provide HTTP method names for testing GET and HEAD requests.
*
* @return array
*/
public static function data_readable_http_methods() {
return array(
'GET request' => array( 'GET' ),
'HEAD request' => array( 'HEAD' ),
);
}
/**
* Search through all content with a low limit.
*/
@ -239,13 +334,19 @@ class WP_Test_REST_Search_Controller extends WP_Test_REST_Controller_Testcase {
/**
* Search through an invalid type
*
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_items_search_type_invalid() {
public function test_get_items_search_type_invalid( $method ) {
$response = $this->do_request_with_params(
array(
'per_page' => 100,
'type' => 'invalid',
)
),
$method
);
$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
@ -253,6 +354,11 @@ class WP_Test_REST_Search_Controller extends WP_Test_REST_Controller_Testcase {
/**
* Search through posts of an invalid post type.
*
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_items_search_type_post_subtype_invalid() {
$response = $this->do_request_with_params(
@ -462,13 +568,19 @@ class WP_Test_REST_Search_Controller extends WP_Test_REST_Controller_Testcase {
/**
* Tests that non-public post types are not allowed.
*
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_non_public_post_type() {
public function test_non_public_post_type( $method ) {
$response = $this->do_request_with_params(
array(
'type' => 'post',
'subtype' => 'post,nav_menu_item',
)
),
$method
);
$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
}
@ -632,15 +744,21 @@ class WP_Test_REST_Search_Controller extends WP_Test_REST_Controller_Testcase {
/**
* Search through posts of an invalid post type.
*
*
* @dataProvider data_readable_http_methods
* @ticket 51458
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_items_search_term_subtype_invalid() {
public function test_get_items_search_term_subtype_invalid( $method ) {
$response = $this->do_request_with_params(
array(
'per_page' => 100,
'type' => 'term',
'subtype' => 'invalid',
)
),
$method
);
$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
@ -890,14 +1008,19 @@ class WP_Test_REST_Search_Controller extends WP_Test_REST_Controller_Testcase {
}
/**
* @dataProvider data_readable_http_methods
* @ticket 60771
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_sanitize_subtypes_validates_type() {
public function test_sanitize_subtypes_validates_type( $method ) {
$response = $this->do_request_with_params(
array(
'subtype' => 'page',
'type' => array( 'invalid' ),
)
),
$method
);
$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );

View File

@ -152,11 +152,38 @@ class WP_Test_REST_Sidebars_Controller extends WP_Test_REST_Controller_Testcase
}
/**
* @ticket 41683
* @ticket 56481
*/
public function test_get_items_no_permission() {
public function test_get_items_with_head_request_should_not_prepare_sidebar_data() {
wp_widgets_init();
$request = new WP_REST_Request( 'HEAD', '/wp/v2/sidebars' );
$hook_name = 'rest_prepare_sidebar';
$filter = new MockAction();
$callback = array( $filter, 'filter' );
add_filter( $hook_name, $callback );
$response = rest_get_server()->dispatch( $request );
remove_filter( $hook_name, $callback );
$this->assertNotWPError( $response );
$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->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' );
}
/**
* @dataProvider data_readable_http_methods
* @ticket 41683
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_items_no_permission( $method ) {
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'GET', '/wp/v2/sidebars' );
$request = new WP_REST_Request( $method, '/wp/v2/sidebars' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 401 );
}
@ -501,9 +528,68 @@ class WP_Test_REST_Sidebars_Controller extends WP_Test_REST_Controller_Testcase
}
/**
* @ticket 41683
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_item_no_permission() {
public function test_get_item_should_allow_adding_headers_via_filter( $method ) {
$hook_name = 'rest_prepare_sidebar';
$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' ) );
$this->setup_sidebar(
'sidebar-1',
array(
'name' => 'Test sidebar',
)
);
$request = new WP_REST_Request( $method, '/wp/v2/sidebars/sidebar-1' );
$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->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.' );
}
/**
* Data provider intended to provide HTTP method names for testing GET and HEAD requests.
*
* @return array
*/
public static function data_readable_http_methods() {
return array(
'GET request' => array( 'GET' ),
'HEAD request' => array( 'HEAD' ),
);
}
/**
* @dataProvider data_readable_http_methods
* @ticket 41683
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_item_no_permission( $method ) {
wp_set_current_user( 0 );
$this->setup_sidebar(
'sidebar-1',
@ -512,7 +598,7 @@ class WP_Test_REST_Sidebars_Controller extends WP_Test_REST_Controller_Testcase
)
);
$request = new WP_REST_Request( 'GET', '/wp/v2/sidebars/sidebar-1' );
$request = new WP_REST_Request( $method, '/wp/v2/sidebars/sidebar-1' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 401 );
}
@ -552,9 +638,13 @@ class WP_Test_REST_Sidebars_Controller extends WP_Test_REST_Controller_Testcase
}
/**
* @dataProvider data_readable_http_methods
* @ticket 41683
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_item_wrong_permission_author() {
public function test_get_item_wrong_permission_author( $method ) {
wp_set_current_user( self::$author_id );
$this->setup_sidebar(
'sidebar-1',
@ -563,7 +653,7 @@ class WP_Test_REST_Sidebars_Controller extends WP_Test_REST_Controller_Testcase
)
);
$request = new WP_REST_Request( 'GET', '/wp/v2/sidebars/sidebar-1' );
$request = new WP_REST_Request( $method, '/wp/v2/sidebars/sidebar-1' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 403 );
}

View File

@ -627,12 +627,18 @@ class WP_Test_REST_Tags_Controller extends WP_Test_REST_Controller_Testcase {
$this->assertErrorResponse( 'rest_no_route', $response, 404 );
}
public function test_get_terms_pagination_headers() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_terms_pagination_headers( $method ) {
$total_tags = self::$total_tags;
$total_pages = (int) ceil( $total_tags / 10 );
// Start of the index.
$request = new WP_REST_Request( 'GET', '/wp/v2/tags' );
$request = new WP_REST_Request( $method, '/wp/v2/tags' );
$response = rest_get_server()->dispatch( $request );
$headers = $response->get_headers();
$this->assertSame( $total_tags, $headers['X-WP-Total'] );
@ -1502,4 +1508,112 @@ class WP_Test_REST_Tags_Controller extends WP_Test_REST_Controller_Testcase {
$tag = get_term( $id, 'post_tag' );
$this->check_taxonomy_term( $tag, $data, $response->get_links() );
}
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_items_only_fetches_ids_for_head_requests( $method ) {
$is_head_request = 'HEAD' === $method;
$request = new WP_REST_Request( $method, '/wp/v2/tags' );
$filter = new MockAction();
add_filter( 'terms_pre_query', array( $filter, 'filter' ), 10, 2 );
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 200, $response->get_status() );
if ( $is_head_request ) {
$this->assertEmpty( $response->get_data() );
} else {
$this->assertNotEmpty( $response->get_data() );
}
$args = $filter->get_args();
$this->assertTrue( isset( $args[0][1] ), 'Query parameters were not captured.' );
$this->assertInstanceOf( WP_Term_Query::class, $args[0][1], 'Query parameters were not captured.' );
/** @var WP_Term_Query $query */
$query = $args[0][1];
if ( $is_head_request ) {
$this->assertArrayHasKey( 'fields', $query->query_vars, 'The fields parameter is not set in the query vars.' );
$this->assertSame( 'ids', $query->query_vars['fields'], 'The query must fetch only term IDs.' );
$this->assertArrayHasKey( 'update_term_meta_cache', $query->query_vars, 'The update_term_meta_cache key is missing in the query vars.' );
$this->assertFalse( $query->query_vars['update_term_meta_cache'], 'The update_term_meta_cache value should be false for HEAD requests.' );
} else {
$this->assertTrue(
! array_key_exists( 'fields', $query->query_vars ) || 'ids' !== $query->query_vars['fields'],
'The fields parameter should not be forced to "ids" for non-HEAD requests.'
);
$this->assertArrayHasKey( 'update_term_meta_cache', $query->query_vars, 'The update_term_meta_cache key is missing in the query vars.' );
$this->assertTrue( $query->query_vars['update_term_meta_cache'], 'The update_term_meta_cache value should be true for HEAD requests.' );
}
if ( ! $is_head_request ) {
return;
}
global $wpdb;
$terms_table = preg_quote( $wpdb->terms, '/' );
$pattern = '/SELECT\s+t\.term_id.+FROM\s+' . $terms_table . '\s+AS\s+t\s+INNER\s+JOIN/is';
// Assert that the SQL query only fetches the term_id column.
$this->assertMatchesRegularExpression( $pattern, $query->request, 'The SQL query does not match the expected string.' );
}
/**
* Data provider intended to provide HTTP method names for testing GET and HEAD requests.
*
* @return array
*/
public static function data_readable_http_methods() {
return array(
'GET request' => array( 'GET' ),
'HEAD request' => array( 'HEAD' ),
);
}
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_item_should_allow_adding_headers_via_filter( string $method ) {
$tag_id = self::factory()->tag->create();
$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( 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.' );
}
}

View File

@ -60,6 +60,22 @@ class WP_Test_REST_Taxonomies_Controller extends WP_Test_REST_Controller_Testcas
$this->assertSame( 'tags', $data['post_tag']['rest_base'] );
}
/**
* @ticket 56481
*/
public function test_get_items_with_head_request_should_not_prepare_taxonomy_data() {
$request = new WP_REST_Request( 'HEAD', '/wp/v2/taxonomies' );
$hook_name = 'rest_prepare_taxonomy';
$filter = new MockAction();
$callback = array( $filter, 'filter' );
add_filter( $hook_name, $callback );
$response = rest_get_server()->dispatch( $request );
remove_filter( $hook_name, $callback );
$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->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' );
}
public function test_get_items_context_edit() {
wp_set_current_user( self::$contributor_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies' );
@ -79,14 +95,33 @@ class WP_Test_REST_Taxonomies_Controller extends WP_Test_REST_Controller_Testcas
$this->assertSame( 'tags', $data['post_tag']['rest_base'] );
}
public function test_get_items_invalid_permission_for_context() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_items_invalid_permission_for_context( $method ) {
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies' );
$request = new WP_REST_Request( $method, '/wp/v2/taxonomies' );
$request->set_param( 'context', 'edit' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_view', $response, 401 );
}
/**
* Data provider intended to provide HTTP method names for testing GET and HEAD requests.
*
* @return array
*/
public static function data_readable_http_methods() {
return array(
'GET request' => array( 'GET' ),
'HEAD request' => array( 'HEAD' ),
);
}
public function test_get_taxonomies_for_type() {
$request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies' );
$request->set_param( 'type', 'post' );
@ -94,11 +129,20 @@ class WP_Test_REST_Taxonomies_Controller extends WP_Test_REST_Controller_Testcas
$this->check_taxonomies_for_type_response( 'post', $response );
}
public function test_get_taxonomies_for_invalid_type() {
$request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies' );
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_taxonomies_for_invalid_type( $method ) {
$request = new WP_REST_Request( $method, '/wp/v2/taxonomies' );
$request->set_param( 'type', 'wingding' );
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 200, $response->get_status() );
if ( 'HEAD' === $method ) {
return null;
}
$data = $response->get_data();
$this->assertSame( '{}', json_encode( $data ) );
}
@ -109,6 +153,41 @@ class WP_Test_REST_Taxonomies_Controller extends WP_Test_REST_Controller_Testcas
$this->check_taxonomy_object_response( 'view', $response );
}
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
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( 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.' );
}
public function test_get_item_edit_context() {
$editor_id = self::factory()->user->create( array( 'role' => 'editor' ) );
wp_set_current_user( $editor_id );
@ -118,33 +197,57 @@ class WP_Test_REST_Taxonomies_Controller extends WP_Test_REST_Controller_Testcas
$this->check_taxonomy_object_response( 'edit', $response );
}
public function test_get_item_invalid_permission_for_context() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_item_invalid_permission_for_context( $method ) {
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies/category' );
$request = new WP_REST_Request( $method, '/wp/v2/taxonomies/category' );
$request->set_param( 'context', 'edit' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_forbidden_context', $response, 401 );
}
public function test_get_invalid_taxonomy() {
$request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies/invalid' );
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_invalid_taxonomy( $method ) {
$request = new WP_REST_Request( $method, '/wp/v2/taxonomies/invalid' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_taxonomy_invalid', $response, 404 );
}
public function test_get_non_public_taxonomy_not_authenticated() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_non_public_taxonomy_not_authenticated( $method ) {
register_taxonomy( 'api-private', 'post', array( 'public' => false ) );
$request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies/api-private' );
$request = new WP_REST_Request( $method, '/wp/v2/taxonomies/api-private' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_forbidden', $response, 401 );
}
public function test_get_non_public_taxonomy_no_permission() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_non_public_taxonomy_no_permission( $method ) {
wp_set_current_user( self::$contributor_id );
register_taxonomy( 'api-private', 'post', array( 'public' => false ) );
$request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies/api-private' );
$request = new WP_REST_Request( $method, '/wp/v2/taxonomies/api-private' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_forbidden', $response, 403 );
}

View File

@ -234,14 +234,29 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase {
$this->check_user_data( $userdata, $data, 'view', $data['_links'] );
}
public function test_get_items_with_edit_context() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_items_with_edit_context( $method ) {
wp_set_current_user( self::$user );
$request = new WP_REST_Request( 'GET', '/wp/v2/users' );
$request = new WP_REST_Request( $method, '/wp/v2/users' );
$request->set_param( 'context', 'edit' );
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 200, $response->get_status() );
$this->assertSame(
200,
$response->get_status(),
sprintf( 'Expected HTTP status code 200 but got %s.', $response->get_status() )
);
if ( 'HEAD' === $method ) {
$this->assertNull( $response->get_data(), 'Expected null response data for HEAD request, but received non-null data.' );
return null;
}
$all_data = $response->get_data();
$data = $all_data[0];
@ -249,9 +264,27 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase {
$this->check_user_data( $userdata, $data, 'edit', $data['_links'] );
}
public function test_get_items_with_edit_context_without_permission() {
/**
* Data provider intended to provide HTTP method names for testing GET and HEAD requests.
*
* @return array
*/
public static function data_readable_http_methods() {
return array(
'GET request' => array( 'GET' ),
'HEAD request' => array( 'HEAD' ),
);
}
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_items_with_edit_context_without_permission( $method ) {
// Test with a user not logged in.
$request = new WP_REST_Request( 'GET', '/wp/v2/users' );
$request = new WP_REST_Request( $method, '/wp/v2/users' );
$request->set_param( 'context', 'edit' );
$response = rest_get_server()->dispatch( $request );
@ -261,7 +294,7 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase {
// capability in question: 'list_users'.
wp_set_current_user( self::$editor );
$request = new WP_REST_Request( 'GET', '/wp/v2/users' );
$request = new WP_REST_Request( $method, '/wp/v2/users' );
$request->set_param( 'context', 'edit' );
$response = rest_get_server()->dispatch( $request );
@ -319,14 +352,20 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase {
$this->assertNotContains( self::$user, $user_ids );
}
public function test_get_items_pagination_headers() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_items_pagination_headers( $method ) {
$total_users = self::$total_users;
$total_pages = (int) ceil( $total_users / 10 );
wp_set_current_user( self::$user );
// Start of the index.
$request = new WP_REST_Request( 'GET', '/wp/v2/users' );
$request = new WP_REST_Request( $method, '/wp/v2/users' );
$response = rest_get_server()->dispatch( $request );
$headers = $response->get_headers();
$this->assertSame( $total_users, $headers['X-WP-Total'] );
@ -344,7 +383,7 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase {
self::factory()->user->create();
++$total_users;
++$total_pages;
$request = new WP_REST_Request( 'GET', '/wp/v2/users' );
$request = new WP_REST_Request( $method, '/wp/v2/users' );
$request->set_param( 'page', 3 );
$response = rest_get_server()->dispatch( $request );
$headers = $response->get_headers();
@ -366,7 +405,7 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase {
$this->assertStringContainsString( '<' . $next_link . '>; rel="next"', $headers['Link'] );
// Last page.
$request = new WP_REST_Request( 'GET', '/wp/v2/users' );
$request = new WP_REST_Request( $method, '/wp/v2/users' );
$request->set_param( 'page', $total_pages );
$response = rest_get_server()->dispatch( $request );
$headers = $response->get_headers();
@ -382,7 +421,7 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase {
$this->assertStringNotContainsString( 'rel="next"', $headers['Link'] );
// Out of bounds.
$request = new WP_REST_Request( 'GET', '/wp/v2/users' );
$request = new WP_REST_Request( $method, '/wp/v2/users' );
$request->set_param( 'page', 100 );
$response = rest_get_server()->dispatch( $request );
$headers = $response->get_headers();
@ -411,14 +450,24 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase {
$this->assertCount( 5, $response->get_data() );
}
public function test_get_items_page() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_items_page( $method ) {
wp_set_current_user( self::$user );
$request = new WP_REST_Request( 'GET', '/wp/v2/users' );
$request = new WP_REST_Request( $method, '/wp/v2/users' );
$request->set_param( 'per_page', 5 );
$request->set_param( 'page', 2 );
$response = rest_get_server()->dispatch( $request );
$this->assertCount( 5, $response->get_data() );
if ( 'HEAD' !== $method ) {
$this->assertCount( 5, $response->get_data() );
}
$prev_link = add_query_arg(
array(
'per_page' => 5,
@ -1265,17 +1314,26 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase {
$this->assertErrorResponse( 'rest_user_cannot_view', $response, 401 );
}
public function test_get_current_user() {
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_current_user( $method ) {
wp_set_current_user( self::$user );
$request = new WP_REST_Request( 'GET', '/wp/v2/users/me' );
$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 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'] );
}
@ -3158,6 +3216,96 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase {
);
}
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_item_should_allow_adding_headers_via_filter( $method ) {
wp_set_current_user( 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( 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.' );
}
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_items_only_fetches_ids_for_head_requests( $method ) {
$is_head_request = 'HEAD' === $method;
$request = new WP_REST_Request( $method, '/wp/v2/users' );
$filter = new MockAction();
add_filter( 'pre_user_query', array( $filter, 'filter' ), 10, 2 );
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 200, $response->get_status() );
if ( $is_head_request ) {
$this->assertNull( $response->get_data() );
} else {
$this->assertNotEmpty( $response->get_data() );
}
$args = $filter->get_args();
$this->assertTrue( isset( $args[0][0] ), 'Query parameters were not captured.' );
$this->assertInstanceOf( WP_User_Query::class, $args[0][0], 'Query parameters were not captured.' );
/** @var WP_User $query */
$query = $args[0][0];
if ( $is_head_request ) {
$this->assertArrayHasKey( 'fields', $query->query_vars, 'The fields parameter is not set in the query vars.' );
$this->assertSame( 'id', $query->query_vars['fields'], 'The query must fetch only user IDs.' );
} else {
$this->assertTrue(
! array_key_exists( 'fields', $query->query_vars ) || 'id' !== $query->query_vars['fields'],
'The fields parameter should not be forced to "id" for non-HEAD requests.'
);
}
if ( ! $is_head_request ) {
return;
}
global $wpdb;
$users_table = preg_quote( $wpdb->users, '/' );
$pattern = '/SELECT SQL_CALC_FOUND_ROWS wptests_users.ID\n\s+FROM\s+' . $users_table . '/is';
// Assert that the SQL query only fetches the id column.
$this->assertMatchesRegularExpression( $pattern, $query->request, 'The SQL query does not match the expected string.' );
}
protected function check_user_data( $user, $data, $context, $links ) {
$this->assertSame( $user->ID, $data['id'] );
$this->assertSame( $user->display_name, $data['name'] );

View File

@ -121,6 +121,17 @@ class WP_Test_REST_Widget_Types_Controller extends WP_Test_REST_Controller_Testc
}
}
/**
* @ticket 56481
*/
public function test_get_items_with_head_request_should_not_prepare_widget_types_data() {
wp_set_current_user( self::$admin_id );
$request = new WP_REST_Request( 'HEAD', '/wp/v2/widget-types' );
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 200, $response->get_status(), 'The response status should be 200.' );
$this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' );
}
/**
* @ticket 53303
*/
@ -181,6 +192,56 @@ class WP_Test_REST_Widget_Types_Controller extends WP_Test_REST_Controller_Testc
$this->check_widget_type_object( $widget_type, $response->get_data(), $response->get_links() );
}
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_item_should_allow_adding_headers_via_filter( $method ) {
$widget_name = 'calendar';
wp_set_current_user( self::$admin_id );
$request = new WP_REST_Request( $method, '/wp/v2/widget-types/' . $widget_name );
$hook_name = 'rest_prepare_widget_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( 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->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.' );
}
/**
* Data provider intended to provide HTTP method names for testing GET and HEAD requests.
*
* @return array
*/
public static function data_readable_http_methods() {
return array(
'GET request' => array( 'GET' ),
'HEAD request' => array( 'HEAD' ),
);
}
/**
* @ticket 41683
*/
@ -200,12 +261,16 @@ class WP_Test_REST_Widget_Types_Controller extends WP_Test_REST_Controller_Testc
}
/**
* @dataProvider data_readable_http_methods
* @ticket 41683
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_widget_invalid_name() {
public function test_get_widget_invalid_name( $method ) {
$widget_type = 'fake';
wp_set_current_user( self::$admin_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/widget-types/' . $widget_type );
$request = new WP_REST_Request( $method, '/wp/v2/widget-types/' . $widget_type );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_widget_type_invalid', $response, 404 );
@ -251,41 +316,57 @@ class WP_Test_REST_Widget_Types_Controller extends WP_Test_REST_Controller_Testc
}
/**
* @dataProvider data_readable_http_methods
* @ticket 41683
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_items_wrong_permission() {
public function test_get_items_wrong_permission( $method ) {
wp_set_current_user( self::$subscriber_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/widget-types' );
$request = new WP_REST_Request( $method, '/wp/v2/widget-types' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 403 );
}
/**
* @dataProvider data_readable_http_methods
* @ticket 41683
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_item_wrong_permission() {
public function test_get_item_wrong_permission( $method ) {
wp_set_current_user( self::$subscriber_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/widget-types/calendar' );
$request = new WP_REST_Request( $method, '/wp/v2/widget-types/calendar' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 403 );
}
/**
* @dataProvider data_readable_http_methods
* @ticket 41683
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_items_no_permission() {
public function test_get_items_no_permission( $method ) {
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'GET', '/wp/v2/widget-types' );
$request = new WP_REST_Request( $method, '/wp/v2/widget-types' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 401 );
}
/**
* @dataProvider data_readable_http_methods
* @ticket 41683
* @ticket 56481
*
* @param string $method HTTP method to use.
*/
public function test_get_item_no_permission() {
public function test_get_item_no_permission( $method ) {
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'GET', '/wp/v2/widget-types/calendar' );
$request = new WP_REST_Request( $method, '/wp/v2/widget-types/calendar' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 401 );
}

View File

@ -228,15 +228,31 @@ class WP_Test_REST_Widgets_Controller extends WP_Test_REST_Controller_Testcase {
}
/**
* @dataProvider data_readable_http_methods
* @ticket 41683
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_no_permission() {
public function test_get_items_no_permission( $method ) {
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'GET', '/wp/v2/widgets' );
$request = new WP_REST_Request( $method, '/wp/v2/widgets' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 401 );
}
/**
* Data provider intended to provide HTTP method names for testing GET and HEAD requests.
*
* @return array
*/
public static function data_readable_http_methods() {
return array(
'GET request' => array( 'GET' ),
'HEAD request' => array( 'HEAD' ),
);
}
/**
* @ticket 53915
*/
@ -332,11 +348,15 @@ class WP_Test_REST_Widgets_Controller extends WP_Test_REST_Controller_Testcase {
}
/**
* @dataProvider data_readable_http_methods
* @ticket 41683
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_wrong_permission_author() {
public function test_get_items_wrong_permission_author( $method ) {
wp_set_current_user( self::$author_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/widgets' );
$request = new WP_REST_Request( $method, '/wp/v2/widgets' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 403 );
}
@ -377,8 +397,9 @@ class WP_Test_REST_Widgets_Controller extends WP_Test_REST_Controller_Testcase {
$request = new WP_REST_Request( 'GET', '/wp/v2/widgets' );
$response = rest_get_server()->dispatch( $request );
$data = $response->get_data();
$data = $this->remove_links( $data );
remove_filter( 'pre_http_request', array( $this, 'mocked_rss_response' ) );
$data = $response->get_data();
$data = $this->remove_links( $data );
$this->assertSameSets(
array(
array(
@ -406,6 +427,54 @@ class WP_Test_REST_Widgets_Controller extends WP_Test_REST_Controller_Testcase {
$wp_widget_factory->widgets['WP_Widget_RSS']->widget_options['show_instance_in_rest'] = true;
}
/**
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_items_with_head_request_should_not_prepare_widget_data( $method ) {
$block_content = '<!-- wp:paragraph --><p>Block test</p><!-- /wp:paragraph -->';
$this->setup_widget(
'rss',
1,
array(
'title' => 'RSS test',
'url' => 'https://wordpress.org/news/feed',
)
);
$this->setup_widget(
'block',
1,
array(
'content' => $block_content,
)
);
$this->setup_sidebar(
'sidebar-1',
array(
'name' => 'Test sidebar',
),
array( 'block-1', 'rss-1', 'testwidget' )
);
$request = new WP_REST_Request( 'HEAD', '/wp/v2/widgets' );
$hook_name = 'rest_prepare_post';
$filter = new MockAction();
$callback = array( $filter, 'filter' );
add_filter( $hook_name, $callback );
$response = rest_get_server()->dispatch( $request );
remove_filter( $hook_name, $callback );
$this->assertNotWPError( $response );
$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->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' );
}
public function mocked_rss_response() {
$single_value_headers = array(
'Content-Type' => 'application/rss+xml; charset=UTF-8',
@ -528,9 +597,64 @@ class WP_Test_REST_Widgets_Controller extends WP_Test_REST_Controller_Testcase {
}
/**
* @ticket 41683
* @dataProvider data_readable_http_methods
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_item_no_permission() {
public function test_get_item_should_allow_adding_headers_via_filter( $method ) {
$this->setup_widget(
'text',
1,
array(
'text' => 'Custom text test',
)
);
$this->setup_sidebar(
'sidebar-1',
array(
'name' => 'Test sidebar',
),
array( 'text-1' )
);
$request = new WP_REST_Request( $method, '/wp/v2/widgets/text-1' );
$hook_name = 'rest_prepare_widget';
$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->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.' );
}
/**
* @dataProvider data_readable_http_methods
* @ticket 41683
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_item_no_permission( $method ) {
wp_set_current_user( 0 );
$this->setup_widget(
@ -548,15 +672,19 @@ class WP_Test_REST_Widgets_Controller extends WP_Test_REST_Controller_Testcase {
array( 'text-1' )
);
$request = new WP_REST_Request( 'GET', '/wp/v2/widgets/text-1' );
$request = new WP_REST_Request( $method, '/wp/v2/widgets/text-1' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 401 );
}
/**
* @dataProvider data_readable_http_methods
* @ticket 41683
* @ticket 56481
*
* @param string $method The HTTP method to use.
*/
public function test_get_item_wrong_permission_author() {
public function test_get_item_wrong_permission_author( $method ) {
wp_set_current_user( self::$author_id );
$this->setup_widget(
'text',
@ -572,7 +700,7 @@ class WP_Test_REST_Widgets_Controller extends WP_Test_REST_Controller_Testcase {
)
);
$request = new WP_REST_Request( 'GET', '/wp/v2/widgets/text-1' );
$request = new WP_REST_Request( $method, '/wp/v2/widgets/text-1' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 403 );
}

View File

@ -123,6 +123,17 @@ class Tests_REST_WpRestBlockPatternCategoriesController extends WP_Test_REST_Con
}
}
/**
* @ticket 56481
*/
public function test_get_items_with_head_request_should_not_prepare_block_pattern_categories_data() {
wp_set_current_user( self::$admin_id );
$request = new WP_REST_Request( 'HEAD', static::REQUEST_ROUTE );
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 200, $response->get_status(), 'The response status should be 200.' );
$this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' );
}
/**
* Verify capability check for unauthorized request (not logged in).
*/

View File

@ -34,6 +34,11 @@ class Tests_REST_wpRestTemplateAutosavesController extends WP_Test_REST_Controll
*/
const TEMPLATE_PART_POST_TYPE = 'wp_template_part';
/**
* @var string
*/
const PARENT_POST_TYPE = 'wp_template';
/**
* Admin user ID.
*
@ -292,6 +297,28 @@ class Tests_REST_wpRestTemplateAutosavesController extends WP_Test_REST_Controll
);
}
/**
* @ticket 56481
*/
public function test_get_items_should_return_no_response_body_for_head_requests() {
wp_set_current_user( self::$admin_id );
$autosave_post_id = wp_create_post_autosave(
array(
'post_content' => 'Autosave content.',
'post_ID' => self::$template_post->ID,
'post_type' => self::PARENT_POST_TYPE,
)
);
$request = new WP_REST_Request(
'HEAD',
'/wp/v2/templates/' . self::TEST_THEME . '/' . self::TEMPLATE_NAME . '/autosaves'
);
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 200, $response->get_status(), 'Response status is 200.' );
$this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' );
}
/**
* Data provider for test_get_items_with_data_provider.
*
@ -426,6 +453,26 @@ class Tests_REST_wpRestTemplateAutosavesController extends WP_Test_REST_Controll
);
}
/**
* @ticket 56481
*/
public function test_get_item_should_return_no_response_body_for_head_requests() {
wp_set_current_user( self::$admin_id );
$autosave_post_id = wp_create_post_autosave(
array(
'post_content' => 'Autosave content.',
'post_ID' => self::$template_post->ID,
'post_type' => self::PARENT_POST_TYPE,
)
);
$request = new WP_REST_Request( 'HEAD', '/wp/v2/templates/' . self::TEST_THEME . '/' . self::TEMPLATE_NAME . '/autosaves/' . $autosave_post_id );
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 200, $response->get_status(), 'Response status is 200.' );
$this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' );
}
/**
* Data provider for test_get_item_with_data_provider.
*

View File

@ -426,18 +426,33 @@ class Tests_REST_wpRestTemplateRevisionsController extends WP_Test_REST_Controll
'template parts' => array( 'template_part_post', 'template-parts', self::TEST_THEME . '//' . self::TEMPLATE_PART_NAME ),
);
}
/**
* @ticket 56481
*/
public function test_get_items_should_return_no_response_body_for_head_requests() {
wp_set_current_user( self::$admin_id );
$request = new WP_REST_Request(
'HEAD',
'/wp/v2/templates/' . self::TEST_THEME . '/' . self::TEMPLATE_NAME . '/revisions'
);
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 200, $response->get_status(), 'Response status is 200.' );
$this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' );
}
/**
* @dataProvider data_get_items_endpoint_should_return_unauthorized_https_status_code_for_unauthorized_request
* @covers WP_REST_Template_Revisions_Controller::get_items_permissions_check
* @ticket 56922
* @ticket 56481
*
* @param string $rest_base Base part of the REST API endpoint to test.
* @param string $template_id Template ID to use in the test.
* @param string $method HTTP method to use.
*/
public function test_get_items_endpoint_should_return_unauthorized_https_status_code_for_unauthorized_request( $rest_base, $template_id ) {
public function test_get_items_endpoint_should_return_unauthorized_https_status_code_for_unauthorized_request( $rest_base, $template_id, $method ) {
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'GET', '/wp/v2/' . $rest_base . '/' . $template_id . '/revisions' );
$request = new WP_REST_Request( $method, '/wp/v2/' . $rest_base . '/' . $template_id . '/revisions' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_read', $response, WP_Http::UNAUTHORIZED );
}
@ -449,8 +464,10 @@ class Tests_REST_wpRestTemplateRevisionsController extends WP_Test_REST_Controll
*/
public function data_get_items_endpoint_should_return_unauthorized_https_status_code_for_unauthorized_request() {
return array(
'templates' => array( 'templates', self::TEST_THEME . '//' . self::TEMPLATE_NAME ),
'template parts' => array( 'template-parts', self::TEST_THEME . '//' . self::TEMPLATE_PART_NAME ),
'templates, GET request' => array( 'templates', self::TEST_THEME . '//' . self::TEMPLATE_NAME, 'GET' ),
'templates, HEAD request' => array( 'templates', self::TEST_THEME . '//' . self::TEMPLATE_NAME, 'HEAD' ),
'template parts, GET request' => array( 'template-parts', self::TEST_THEME . '//' . self::TEMPLATE_PART_NAME, 'GET' ),
'template parts, HEAD request' => array( 'template-parts', self::TEST_THEME . '//' . self::TEMPLATE_PART_NAME, 'HEAD' ),
);
}
@ -458,13 +475,15 @@ class Tests_REST_wpRestTemplateRevisionsController extends WP_Test_REST_Controll
* @dataProvider data_get_items_endpoint_should_return_forbidden_https_status_code_for_users_with_insufficient_permissions
* @covers WP_REST_Template_Revisions_Controller::get_items_permissions_check
* @ticket 56922
* @ticket 56481
*
* @param string $rest_base Base part of the REST API endpoint to test.
* @param string $template_id Template ID to use in the test.
* @param string $method HTTP method to use.
*/
public function test_get_items_endpoint_should_return_forbidden_https_status_code_for_users_with_insufficient_permissions( $rest_base, string $template_id ) {
public function test_get_items_endpoint_should_return_forbidden_https_status_code_for_users_with_insufficient_permissions( $rest_base, string $template_id, $method ) {
wp_set_current_user( self::$contributor_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/' . $rest_base . '/' . $template_id . '/revisions' );
$request = new WP_REST_Request( $method, '/wp/v2/' . $rest_base . '/' . $template_id . '/revisions' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_read', $response, WP_Http::FORBIDDEN );
}
@ -476,8 +495,10 @@ class Tests_REST_wpRestTemplateRevisionsController extends WP_Test_REST_Controll
*/
public function data_get_items_endpoint_should_return_forbidden_https_status_code_for_users_with_insufficient_permissions() {
return array(
'templates' => array( 'templates', self::TEST_THEME . '//' . self::TEMPLATE_NAME ),
'template parts' => array( 'template-parts', self::TEST_THEME . '//' . self::TEMPLATE_PART_NAME ),
'templates, GET request' => array( 'templates', self::TEST_THEME . '//' . self::TEMPLATE_NAME, 'GET' ),
'templates, HEAD request' => array( 'templates', self::TEST_THEME . '//' . self::TEMPLATE_NAME, 'HEAD' ),
'template parts, GET request' => array( 'template-parts', self::TEST_THEME . '//' . self::TEMPLATE_PART_NAME, 'GET' ),
'template parts, HEAD request' => array( 'template-parts', self::TEST_THEME . '//' . self::TEMPLATE_PART_NAME, 'HEAD' ),
);
}
@ -596,6 +617,19 @@ class Tests_REST_wpRestTemplateRevisionsController extends WP_Test_REST_Controll
);
}
/**
* @ticket 56481
*/
public function test_get_item_should_return_no_response_body_for_head_requests() {
wp_set_current_user( self::$admin_id );
$revisions = wp_get_post_revisions( self::$template_post, array( 'fields' => 'ids' ) );
$revision_id = array_shift( $revisions );
$request = new WP_REST_Request( 'HEAD', '/wp/v2/templates/' . self::TEST_THEME . '/' . self::TEMPLATE_NAME . '/revisions/' . $revision_id );
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 200, $response->get_status(), 'Response status is 200.' );
$this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' );
}
/**
* Data provider for test_get_item_with_data_provider.
*
@ -612,11 +646,13 @@ class Tests_REST_wpRestTemplateRevisionsController extends WP_Test_REST_Controll
* @dataProvider data_get_item_not_found
* @covers WP_REST_Template_Revisions_Controller::get_item
* @ticket 56922
* @ticket 56481
*
* @param string $parent_post_property_name A class property name that contains the parent post object.
* @param string $rest_base Base part of the REST API endpoint to test.
* @param string $method HTTP method to use.
*/
public function test_get_item_not_found( $parent_post_property_name, $rest_base ) {
public function test_get_item_not_found( $parent_post_property_name, $rest_base, $method ) {
wp_set_current_user( self::$admin_id );
$parent_post = self::$$parent_post_property_name;
@ -624,7 +660,7 @@ class Tests_REST_wpRestTemplateRevisionsController extends WP_Test_REST_Controll
$revisions = wp_get_post_revisions( $parent_post, array( 'fields' => 'ids' ) );
$revision_id = array_shift( $revisions );
$request = new WP_REST_Request( 'GET', '/wp/v2/' . $rest_base . '/invalid//parent/revisions/' . $revision_id );
$request = new WP_REST_Request( $method, '/wp/v2/' . $rest_base . '/invalid//parent/revisions/' . $revision_id );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_post_invalid_parent', $response, WP_Http::NOT_FOUND );
}
@ -636,8 +672,10 @@ class Tests_REST_wpRestTemplateRevisionsController extends WP_Test_REST_Controll
*/
public function data_get_item_not_found() {
return array(
'templates' => array( 'template_post', 'templates' ),
'template parts' => array( 'template_part_post', 'template-parts' ),
'templates, GET request' => array( 'template_post', 'templates', 'GET' ),
'templates, HEAD request' => array( 'template_post', 'templates', 'HEAD' ),
'template parts, GET request' => array( 'template_part_post', 'template-parts', 'GET' ),
'template parts, HEAD request' => array( 'template_part_post', 'template-parts', 'HEAD' ),
);
}
@ -645,13 +683,15 @@ class Tests_REST_wpRestTemplateRevisionsController extends WP_Test_REST_Controll
* @dataProvider data_get_item_invalid_parent_id
* @covers WP_REST_Template_Revisions_Controller::get_item
* @ticket 59875
* @ticket 56481
*
* @param string $parent_post_property_name A class property name that contains the parent post object.
* @param string $actual_parent_post_property_name A class property name that contains the parent post object.
* @param string $rest_base Base part of the REST API endpoint to test.
* @param string $template_id Template ID to use in the test.
* @param string $method HTTP method to use.
*/
public function test_get_item_invalid_parent_id( $parent_post_property_name, $actual_parent_post_property_name, $rest_base, $template_id ) {
public function test_get_item_invalid_parent_id( $parent_post_property_name, $actual_parent_post_property_name, $rest_base, $template_id, $method ) {
wp_set_current_user( self::$admin_id );
$parent_post = self::$$parent_post_property_name;
@ -659,7 +699,7 @@ class Tests_REST_wpRestTemplateRevisionsController extends WP_Test_REST_Controll
$revisions = wp_get_post_revisions( $parent_post, array( 'fields' => 'ids' ) );
$revision_id = array_shift( $revisions );
$request = new WP_REST_Request( 'GET', '/wp/v2/' . $rest_base . '/' . $template_id . '/revisions/' . $revision_id );
$request = new WP_REST_Request( $method, '/wp/v2/' . $rest_base . '/' . $template_id . '/revisions/' . $revision_id );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_revision_parent_id_mismatch', $response, 404 );
@ -675,17 +715,33 @@ class Tests_REST_wpRestTemplateRevisionsController extends WP_Test_REST_Controll
*/
public function data_get_item_invalid_parent_id() {
return array(
'templates' => array(
'templates, GET request' => array(
'template_post',
'template_post_2',
'templates',
self::TEST_THEME . '//' . self::TEMPLATE_NAME_2,
'GET',
),
'template parts' => array(
'templates, HEAD request' => array(
'template_post',
'template_post_2',
'templates',
self::TEST_THEME . '//' . self::TEMPLATE_NAME_2,
'HEAD',
),
'template parts, GET request' => array(
'template_part_post',
'template_part_post_2',
'template-parts',
self::TEST_THEME . '//' . self::TEMPLATE_PART_NAME_2,
'GET',
),
'template parts, HEAD request' => array(
'template_part_post',
'template_part_post_2',
'template-parts',
self::TEST_THEME . '//' . self::TEMPLATE_PART_NAME_2,
'HEAD',
),
);
}

View File

@ -175,6 +175,19 @@ class Tests_REST_WpRestTemplatesController extends WP_Test_REST_Controller_Testc
);
}
/**
* @ticket 56481
*
* @covers WP_REST_Templates_Controller::get_items
*/
public function test_get_items_should_return_no_response_body_for_head_requests() {
wp_set_current_user( self::$admin_id );
$request = new WP_REST_Request( 'HEAD', '/wp/v2/templates' );
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 200, $response->get_status(), 'Response status is 200.' );
$this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' );
}
/**
* @covers WP_REST_Templates_Controller::get_items
*/
@ -267,6 +280,20 @@ class Tests_REST_WpRestTemplatesController extends WP_Test_REST_Controller_Testc
);
}
/**
* @ticket 56481
*
* @covers WP_REST_Templates_Controller::get_item
* @covers WP_REST_Templates_Controller::prepare_item_for_response
*/
public function test_get_item_should_return_no_response_body_for_head_requests() {
wp_set_current_user( self::$admin_id );
$request = new WP_REST_Request( 'HEAD', '/wp/v2/templates/default//my_template' );
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 200, $response->get_status(), 'Response status is 200.' );
$this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' );
}
/**
* @covers WP_REST_Templates_Controller::get_item
*/
@ -311,7 +338,6 @@ class Tests_REST_WpRestTemplatesController extends WP_Test_REST_Controller_Testc
wp_set_current_user( self::$subscriber_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/templates/default//my_template' );
$response = rest_get_server()->dispatch( $request );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_manage_templates', $response, 403 );
}