wordpress-develop/tests/phpunit/tests/rest-api/rest-request.php
Timothy Jacobs 5149a7efd5 REST API: Fix warning when using set_param() on a JSON request with no body.
In [47559] the `WP_REST_Request::set_param()` method was adjusted to try and overwrite an existing parameter definition before forcing the value in the first parameter slot. If `set_param()` was called on a request with an `application/json` content type and an empty body, a PHP warning would be issued. This was due to the JSON parameter type not being set to an array when the body is empty.

This commit avoids the warning by adding an `is_array()` check before calling `array_key_exists`. Ideally, `WP_REST_Reuest::parse_json_params()` would set the JSON parameter type to an empty array in this case, but that is too large of a change at this point in the cycle.

Props manooweb.
Fixes #50786.


git-svn-id: https://develop.svn.wordpress.org/trunk@48642 602fd350-edb4-49c9-b593-d223f7449a82
2020-07-27 18:44:14 +00:00

783 lines
22 KiB
PHP

<?php
/**
* Unit tests covering WP_REST_Request functionality.
*
* @package WordPress
* @subpackage REST API
*/
/**
* @group restapi
*/
class Tests_REST_Request extends WP_UnitTestCase {
public $request;
public function setUp() {
parent::setUp();
$this->request = new WP_REST_Request();
}
public function test_header() {
$value = 'application/x-wp-example';
$this->request->set_header( 'Content-Type', $value );
$this->assertEquals( $value, $this->request->get_header( 'Content-Type' ) );
}
public function test_header_missing() {
$this->assertNull( $this->request->get_header( 'missing' ) );
$this->assertNull( $this->request->get_header_as_array( 'missing' ) );
}
public function test_remove_header() {
$this->request->add_header( 'Test-Header', 'value' );
$this->assertEquals( 'value', $this->request->get_header( 'Test-Header' ) );
$this->request->remove_header( 'Test-Header' );
$this->assertNull( $this->request->get_header( 'Test-Header' ) );
}
public function test_header_multiple() {
$value1 = 'application/x-wp-example-1';
$value2 = 'application/x-wp-example-2';
$this->request->add_header( 'Accept', $value1 );
$this->request->add_header( 'Accept', $value2 );
$this->assertEquals( $value1 . ',' . $value2, $this->request->get_header( 'Accept' ) );
$this->assertEquals( array( $value1, $value2 ), $this->request->get_header_as_array( 'Accept' ) );
}
public static function header_provider() {
return array(
array( 'Test', 'test' ),
array( 'TEST', 'test' ),
array( 'Test-Header', 'test_header' ),
array( 'test-header', 'test_header' ),
array( 'Test_Header', 'test_header' ),
array( 'test_header', 'test_header' ),
);
}
/**
* @dataProvider header_provider
* @param string $original Original header key.
* @param string $expected Expected canonicalized version.
*/
public function test_header_canonicalization( $original, $expected ) {
$this->assertEquals( $expected, $this->request->canonicalize_header_name( $original ) );
}
public static function content_type_provider() {
return array(
// Check basic parsing.
array( 'application/x-wp-example', 'application/x-wp-example', 'application', 'x-wp-example', '' ),
array( 'application/x-wp-example; charset=utf-8', 'application/x-wp-example', 'application', 'x-wp-example', 'charset=utf-8' ),
// Check case insensitivity.
array( 'APPLICATION/x-WP-Example', 'application/x-wp-example', 'application', 'x-wp-example', '' ),
);
}
/**
* @dataProvider content_type_provider
*
* @param string $header Header value.
* @param string $value Full type value.
* @param string $type Main type (application, text, etc).
* @param string $subtype Subtype (json, etc).
* @param string $parameters Parameters (charset=utf-8, etc).
*/
public function test_content_type_parsing( $header, $value, $type, $subtype, $parameters ) {
// Check we start with nothing.
$this->assertEmpty( $this->request->get_content_type() );
$this->request->set_header( 'Content-Type', $header );
$parsed = $this->request->get_content_type();
$this->assertEquals( $value, $parsed['value'] );
$this->assertEquals( $type, $parsed['type'] );
$this->assertEquals( $subtype, $parsed['subtype'] );
$this->assertEquals( $parameters, $parsed['parameters'] );
}
protected function request_with_parameters() {
$this->request->set_url_params(
array(
'source' => 'url',
'has_url_params' => true,
)
);
$this->request->set_query_params(
array(
'source' => 'query',
'has_query_params' => true,
)
);
$this->request->set_body_params(
array(
'source' => 'body',
'has_body_params' => true,
)
);
$json_data = wp_json_encode(
array(
'source' => 'json',
'has_json_params' => true,
)
);
$this->request->set_body( $json_data );
$this->request->set_default_params(
array(
'source' => 'defaults',
'has_default_params' => true,
)
);
}
public function test_parameter_order() {
$this->request_with_parameters();
$this->request->set_method( 'GET' );
// Check that query takes precedence.
$this->assertEquals( 'query', $this->request->get_param( 'source' ) );
// Check that the correct arguments are parsed (and that falling through
// the stack works).
$this->assertTrue( $this->request->get_param( 'has_url_params' ) );
$this->assertTrue( $this->request->get_param( 'has_query_params' ) );
$this->assertTrue( $this->request->get_param( 'has_default_params' ) );
// POST and JSON parameters shouldn't be parsed.
$this->assertEmpty( $this->request->get_param( 'has_body_params' ) );
$this->assertEmpty( $this->request->get_param( 'has_json_params' ) );
}
public function test_parameter_order_post() {
$this->request_with_parameters();
$this->request->set_method( 'POST' );
$this->request->set_header( 'Content-Type', 'application/x-www-form-urlencoded' );
$this->request->set_attributes( array( 'accept_json' => true ) );
// Check that POST takes precedence.
$this->assertEquals( 'body', $this->request->get_param( 'source' ) );
// Check that the correct arguments are parsed (and that falling through
// the stack works).
$this->assertTrue( $this->request->get_param( 'has_url_params' ) );
$this->assertTrue( $this->request->get_param( 'has_query_params' ) );
$this->assertTrue( $this->request->get_param( 'has_body_params' ) );
$this->assertTrue( $this->request->get_param( 'has_default_params' ) );
// JSON shouldn't be parsed.
$this->assertEmpty( $this->request->get_param( 'has_json_params' ) );
}
public function test_parameter_order_json() {
$this->request_with_parameters();
$this->request->set_method( 'POST' );
$this->request->set_header( 'Content-Type', 'application/json' );
$this->request->set_attributes( array( 'accept_json' => true ) );
// Check that JSON takes precedence.
$this->assertEquals( 'json', $this->request->get_param( 'source' ) );
// Check that the correct arguments are parsed (and that falling through
// the stack works).
$this->assertTrue( $this->request->get_param( 'has_url_params' ) );
$this->assertTrue( $this->request->get_param( 'has_query_params' ) );
$this->assertTrue( $this->request->get_param( 'has_body_params' ) );
$this->assertTrue( $this->request->get_param( 'has_json_params' ) );
$this->assertTrue( $this->request->get_param( 'has_default_params' ) );
}
public function test_parameter_order_json_invalid() {
$this->request_with_parameters();
$this->request->set_method( 'POST' );
$this->request->set_header( 'Content-Type', 'application/json' );
$this->request->set_attributes( array( 'accept_json' => true ) );
// Use invalid JSON data.
$this->request->set_body( '{ this is not json }' );
// Check that JSON is ignored.
$this->assertEquals( 'body', $this->request->get_param( 'source' ) );
// Check that the correct arguments are parsed (and that falling through
// the stack works).
$this->assertTrue( $this->request->get_param( 'has_url_params' ) );
$this->assertTrue( $this->request->get_param( 'has_query_params' ) );
$this->assertTrue( $this->request->get_param( 'has_body_params' ) );
$this->assertTrue( $this->request->get_param( 'has_default_params' ) );
// JSON should be ignored.
$this->assertEmpty( $this->request->get_param( 'has_json_params' ) );
}
public function non_post_http_methods_with_request_body_provider() {
return array(
array( 'PUT' ),
array( 'PATCH' ),
array( 'DELETE' ),
);
}
/**
* Tests that methods supporting request bodies have access to the
* request's body. For POST this is straightforward via `$_POST`; for
* other methods `WP_REST_Request` needs to parse the body for us.
*
* @dataProvider non_post_http_methods_with_request_body_provider
*/
public function test_non_post_body_parameters( $request_method ) {
$data = array(
'foo' => 'bar',
'alot' => array(
'of' => 'parameters',
),
'list' => array(
'of',
'cool',
'stuff',
),
);
$this->request->set_method( $request_method );
$this->request->set_body_params( array() );
$this->request->set_body( http_build_query( $data ) );
foreach ( $data as $key => $expected_value ) {
$this->assertEquals( $expected_value, $this->request->get_param( $key ) );
}
}
public function test_parameters_for_json_put() {
$data = array(
'foo' => 'bar',
'alot' => array(
'of' => 'parameters',
),
'list' => array(
'of',
'cool',
'stuff',
),
);
$this->request->set_method( 'PUT' );
$this->request->add_header( 'content-type', 'application/json' );
$this->request->set_body( wp_json_encode( $data ) );
foreach ( $data as $key => $expected_value ) {
$this->assertEquals( $expected_value, $this->request->get_param( $key ) );
}
}
public function test_parameters_for_json_post() {
$data = array(
'foo' => 'bar',
'alot' => array(
'of' => 'parameters',
),
'list' => array(
'of',
'cool',
'stuff',
),
);
$this->request->set_method( 'POST' );
$this->request->add_header( 'content-type', 'application/json' );
$this->request->set_body( wp_json_encode( $data ) );
foreach ( $data as $key => $expected_value ) {
$this->assertEquals( $expected_value, $this->request->get_param( $key ) );
}
}
public function test_parameter_merging() {
$this->request_with_parameters();
$this->request->set_method( 'POST' );
$expected = array(
'source' => 'body',
'has_url_params' => true,
'has_query_params' => true,
'has_body_params' => true,
'has_default_params' => true,
);
$this->assertEquals( $expected, $this->request->get_params() );
}
public function test_parameter_merging_with_numeric_keys() {
$this->request->set_query_params(
array(
'1' => 'hello',
'2' => 'goodbye',
)
);
$expected = array(
'1' => 'hello',
'2' => 'goodbye',
);
$this->assertEquals( $expected, $this->request->get_params() );
}
public function test_sanitize_params() {
$this->request->set_url_params(
array(
'someinteger' => '123',
'somestring' => 'hello',
)
);
$this->request->set_attributes(
array(
'args' => array(
'someinteger' => array(
'sanitize_callback' => 'absint',
),
'somestring' => array(
'sanitize_callback' => 'absint',
),
),
)
);
$this->request->sanitize_params();
$this->assertEquals( 123, $this->request->get_param( 'someinteger' ) );
$this->assertEquals( 0, $this->request->get_param( 'somestring' ) );
}
public function test_sanitize_params_error() {
$this->request->set_url_params(
array(
'successparam' => '123',
'failparam' => '123',
)
);
$this->request->set_attributes(
array(
'args' => array(
'successparam' => array(
'sanitize_callback' => 'absint',
),
'failparam' => array(
'sanitize_callback' => array( $this, '_return_wp_error_on_validate_callback' ),
),
),
)
);
$valid = $this->request->sanitize_params();
$this->assertWPError( $valid );
$this->assertEquals( 'rest_invalid_param', $valid->get_error_code() );
}
public function test_sanitize_params_with_null_callback() {
$this->request->set_url_params(
array(
'some_email' => '',
)
);
$this->request->set_attributes(
array(
'args' => array(
'some_email' => array(
'type' => 'string',
'format' => 'email',
'sanitize_callback' => null,
),
),
)
);
$this->assertTrue( $this->request->sanitize_params() );
}
public function test_sanitize_params_with_false_callback() {
$this->request->set_url_params(
array(
'some_uri' => 1.23422,
)
);
$this->request->set_attributes(
array(
'args' => array(
'some_uri' => array(
'type' => 'string',
'format' => 'uri',
'sanitize_callback' => false,
),
),
)
);
$this->assertTrue( $this->request->sanitize_params() );
}
public function test_has_valid_params_required_flag() {
$this->request->set_attributes(
array(
'args' => array(
'someinteger' => array(
'required' => true,
),
),
)
);
$valid = $this->request->has_valid_params();
$this->assertWPError( $valid );
$this->assertEquals( 'rest_missing_callback_param', $valid->get_error_code() );
}
public function test_has_valid_params_required_flag_multiple() {
$this->request->set_attributes(
array(
'args' => array(
'someinteger' => array(
'required' => true,
),
'someotherinteger' => array(
'required' => true,
),
),
)
);
$valid = $this->request->has_valid_params();
$this->assertWPError( $valid );
$this->assertEquals( 'rest_missing_callback_param', $valid->get_error_code() );
$data = $valid->get_error_data( 'rest_missing_callback_param' );
$this->assertTrue( in_array( 'someinteger', $data['params'], true ) );
$this->assertTrue( in_array( 'someotherinteger', $data['params'], true ) );
}
public function test_has_valid_params_validate_callback() {
$this->request->set_url_params(
array(
'someinteger' => '123',
)
);
$this->request->set_attributes(
array(
'args' => array(
'someinteger' => array(
'validate_callback' => '__return_false',
),
),
)
);
$valid = $this->request->has_valid_params();
$this->assertWPError( $valid );
$this->assertEquals( 'rest_invalid_param', $valid->get_error_code() );
}
public function test_has_valid_params_json_error() {
$this->request->set_header( 'Content-Type', 'application/json' );
$this->request->set_body( '{"invalid": JSON}' );
$valid = $this->request->has_valid_params();
$this->assertWPError( $valid );
$this->assertEquals( 'rest_invalid_json', $valid->get_error_code() );
$data = $valid->get_error_data();
$this->assertEquals( JSON_ERROR_SYNTAX, $data['json_error_code'] );
}
public function test_has_valid_params_empty_json_no_error() {
$this->request->set_header( 'Content-Type', 'application/json' );
$this->request->set_body( '' );
$valid = $this->request->has_valid_params();
$this->assertNotWPError( $valid );
}
public function test_has_multiple_invalid_params_validate_callback() {
$this->request->set_url_params(
array(
'someinteger' => '123',
'someotherinteger' => '123',
)
);
$this->request->set_attributes(
array(
'args' => array(
'someinteger' => array(
'validate_callback' => '__return_false',
),
'someotherinteger' => array(
'validate_callback' => '__return_false',
),
),
)
);
$valid = $this->request->has_valid_params();
$this->assertWPError( $valid );
$this->assertEquals( 'rest_invalid_param', $valid->get_error_code() );
$data = $valid->get_error_data( 'rest_invalid_param' );
$this->assertArrayHasKey( 'someinteger', $data['params'] );
$this->assertArrayHasKey( 'someotherinteger', $data['params'] );
}
public function test_invalid_params_error_response_format() {
$this->request->set_url_params(
array(
'someinteger' => '123',
'someotherparams' => '123',
)
);
$this->request->set_attributes(
array(
'args' => array(
'someinteger' => array(
'validate_callback' => '__return_false',
),
'someotherparams' => array(
'validate_callback' => array( $this, '_return_wp_error_on_validate_callback' ),
),
),
)
);
$valid = $this->request->has_valid_params();
$this->assertWPError( $valid );
$error_data = $valid->get_error_data();
$this->assertEquals( array( 'someinteger', 'someotherparams' ), array_keys( $error_data['params'] ) );
$this->assertEquals( 'This is not valid!', $error_data['params']['someotherparams'] );
}
public function _return_wp_error_on_validate_callback() {
return new WP_Error( 'some-error', 'This is not valid!' );
}
public function data_from_url() {
return array(
array(
'permalink_structure' => '/%post_name%/',
'original_url' => 'http://' . WP_TESTS_DOMAIN . '/wp-json/wp/v2/posts/1?foo=bar',
),
array(
'permalink_structure' => '',
'original_url' => 'http://' . WP_TESTS_DOMAIN . '/index.php?rest_route=%2Fwp%2Fv2%2Fposts%2F1&foo=bar',
),
);
}
/**
* @dataProvider data_from_url
*/
public function test_from_url( $permalink_structure, $original_url ) {
update_option( 'permalink_structure', $permalink_structure );
$url = add_query_arg( 'foo', 'bar', rest_url( '/wp/v2/posts/1' ) );
$this->assertEquals( $original_url, $url );
$request = WP_REST_Request::from_url( $url );
$this->assertInstanceOf( 'WP_REST_Request', $request );
$this->assertEquals( '/wp/v2/posts/1', $request->get_route() );
$this->assertEqualSets(
array(
'foo' => 'bar',
),
$request->get_query_params()
);
}
/**
* @dataProvider data_from_url
*/
public function test_from_url_invalid( $permalink_structure ) {
update_option( 'permalink_structure', $permalink_structure );
$using_site = site_url( '/wp/v2/posts/1' );
$request = WP_REST_Request::from_url( $using_site );
$this->assertFalse( $request );
$using_home = home_url( '/wp/v2/posts/1' );
$request = WP_REST_Request::from_url( $using_home );
$this->assertFalse( $request );
}
public function test_set_param() {
$request = new WP_REST_Request();
$request->set_param( 'param', 'value' );
$this->assertEquals( 'value', $request->get_param( 'param' ) );
}
public function test_set_param_follows_parameter_order() {
$request = new WP_REST_Request();
$request->add_header( 'content-type', 'application/json' );
$request->set_method( 'POST' );
$request->set_body(
wp_json_encode(
array(
'param' => 'value',
)
)
);
$this->assertEquals( 'value', $request->get_param( 'param' ) );
$this->assertEquals(
array( 'param' => 'value' ),
$request->get_json_params()
);
$request->set_param( 'param', 'new_value' );
$this->assertEquals( 'new_value', $request->get_param( 'param' ) );
$this->assertEquals(
array( 'param' => 'new_value' ),
$request->get_json_params()
);
}
/**
* @ticket 40838
*/
public function test_set_param_updates_param_in_json_and_query() {
$request = new WP_REST_Request();
$request->add_header( 'content-type', 'application/json' );
$request->set_method( 'POST' );
$request->set_body(
wp_json_encode(
array(
'param' => 'value_body',
)
)
);
$request->set_query_params(
array(
'param' => 'value_query',
)
);
$request->set_param( 'param', 'new_value' );
$this->assertEquals( 'new_value', $request->get_param( 'param' ) );
$this->assertEquals( array(), $request->get_body_params() );
$this->assertEquals( array( 'param' => 'new_value' ), $request->get_json_params() );
$this->assertEquals( array( 'param' => 'new_value' ), $request->get_query_params() );
}
/**
* @ticket 40838
*/
public function test_set_param_updates_param_if_already_exists_in_query() {
$request = new WP_REST_Request();
$request->add_header( 'content-type', 'application/json' );
$request->set_method( 'POST' );
$request->set_body(
wp_json_encode(
array(
'param_body' => 'value_body',
)
)
);
$original_defaults = array(
'param_query' => 'default_query_value',
'param_body' => 'default_body_value',
);
$request->set_default_params( $original_defaults );
$request->set_query_params(
array(
'param_query' => 'value_query',
)
);
$request->set_param( 'param_query', 'new_value' );
$this->assertEquals( 'new_value', $request->get_param( 'param_query' ) );
$this->assertEquals( array(), $request->get_body_params() );
$this->assertEquals( array( 'param_body' => 'value_body' ), $request->get_json_params() );
$this->assertEquals( array( 'param_query' => 'new_value' ), $request->get_query_params() );
// Verify the default wasn't overwritten.
$this->assertEquals( $original_defaults, $request->get_default_params() );
}
/**
* @ticket 40838
*/
public function test_set_param_to_null_updates_param_in_json_and_query() {
$request = new WP_REST_Request();
$request->add_header( 'content-type', 'application/json' );
$request->set_method( 'POST' );
$request->set_body(
wp_json_encode(
array(
'param' => 'value_body',
)
)
);
$request->set_query_params(
array(
'param' => 'value_query',
)
);
$request->set_param( 'param', null );
$this->assertEquals( null, $request->get_param( 'param' ) );
$this->assertEquals( array(), $request->get_body_params() );
$this->assertEquals( array( 'param' => null ), $request->get_json_params() );
$this->assertEquals( array( 'param' => null ), $request->get_query_params() );
}
/**
* @ticket 40838
*/
public function test_set_param_from_null_updates_param_in_json_and_query_with_null() {
$request = new WP_REST_Request();
$request->add_header( 'content-type', 'application/json' );
$request->set_method( 'POST' );
$request->set_body(
wp_json_encode(
array(
'param' => null,
)
)
);
$request->set_query_params(
array(
'param' => null,
)
);
$request->set_param( 'param', 'new_value' );
$this->assertEquals( 'new_value', $request->get_param( 'param' ) );
$this->assertEquals( array(), $request->get_body_params() );
$this->assertEquals( array( 'param' => 'new_value' ), $request->get_json_params() );
$this->assertEquals( array( 'param' => 'new_value' ), $request->get_query_params() );
}
/**
* @ticket 50786
*/
public function test_set_param_with_invalid_json() {
$request = new WP_REST_Request();
$request->add_header( 'content-type', 'application/json' );
$request->set_method( 'POST' );
$request->set_body( '' );
$request->set_param( 'param', 'value' );
$this->assertTrue( $request->has_param( 'param' ) );
$this->assertEquals( 'value', $request->get_param( 'param' ) );
}
}