wp-cli-tests
wp-cli-tests copied to clipboard
Mock HTTP requests
Feature Request
- [x] Yes, I reviewed the contribution guidelines.
Describe your use case and the problem you are facing
When writing my own CLI commands, they sometimes perform HTTP requests. E.g. I'm currently working with a software licence server, I want a CLI command to activate the licence, but I don't want to use a real licence server which would decrement the available licence activations and return different responses when the test is re-run.
Maybe WP CLI already has this utility, but I didn't see it.
Describe the solution you'd like
Filter pre_http_request by creating an mu-plugin for the test case which loads a PHP file containing the mocked response.
Add this to GivenStepDefinitions.php
/**
* @Given /^a request to (.*?) responds? with (.*)$/
*/
public function given_a_request_to_a_url_respond_with_file( $url_substring_pattern, $remote_request_response_file ) {
$project_dir = realpath( self::get_vendor_dir() . '/../' );
switch ( true ) {
case is_file( $remote_request_response_file ):
$response_file = realpath( $remote_request_response_file );
break;
case is_file( ltrim( $remote_request_response_file, './' ) ):
$response_file = realpath( ltrim( $remote_request_response_file, './' ) );
break;
case is_file( $project_dir . '/' . $remote_request_response_file ):
$response_file = $project_dir . '/' . $remote_request_response_file;
break;
default:
WP_CLI::error( "File not found: {$remote_request_response_file}" );
}
if ( substr( $url_substring_pattern, 0, 1 ) !== substr( $url_substring_pattern, -1 ) ) {
$url_substring_pattern = '/' . preg_quote( $url_substring_pattern, '/' ) . '/';
}
$mu_plugin_name = basename( $remote_request_response_file );
$mu_php = <<<MU_PHP
<?php
/**
* Plugin Name: $mu_plugin_name
* Description: Mock a response for a remote request matching `$url_substring_pattern`.
*/
/**
* Filter the HTTP request to return a mock response.
*
* @hooked pre_http_request
* @see \WP_Http::request()
*
* @param false|array \$pre
* @param array \$parsed_args
* @param string \$url The request URL.
*
* @return false|array{headers:array,body:string,response:array{code:int|false,message:bool|string},cookies:array,http_response:null|array}
*/
add_filter( 'pre_http_request', function( \$pre, \$parsed_args, \$url ) {
if ( 1 !== preg_match( '$url_substring_pattern', \$url ) ) {
return \$pre;
}
return include '$response_file';
}, 10, 3 );
MU_PHP;
file_put_contents(
$this->variables['RUN_DIR'] . '/wp-content/mu-plugins/' . $mu_plugin_name,
$mu_php
);
}
And a feature would look like:
Scenario: licence set with activate flag
Given a WP install
Given a plugin located at ./test-plugin
Given a request to wp-json/slswc/v1/activate? responds with tests/_data/features/activate-success.php
When I run `wp test-plugin licence set-key abcdefghijklmnopqrstuvwxyzn --activate`
Then STDERR should be empty
And STDOUT should contain:
"""
Success: active
"""
This could be generalised to almost every filter, so maybe I missed it because I was looking for a http mock.
I need to write tests on that switch statement.
I like the idea of being able to easily mock HTTP requests in Behat tests. I think I recently needed something similar as well. Is there any use case in WP-CLI itself where we could use this?
I think there are undoubtedly places it could be used in WP-CLI itself but it hasn't been required thanks to predictable behaviour – e.g. wordpress.org is probably not returning HTTP 500s for wp core update and we can install a known old plugin version before testing wp plugin update.
In context of wp scaffold package, we have already decided we want to provide testing tools for third party plugins, and the question becomes: will this function be widely used enough to warrant its inclusion and supporting it?
Obviously, I think it's a valuable function and should be included. And I guess I am using this issue as a litmus test for other PRs that I'll know not to open if the policy is to only add functions that are directly used by WP-CLI itself, e.g. the Given a plugin located at ./test-plugin above.
More broadly, providing more tools for testing plugins is only going to improve the WordPress ecosystem. Alternatively, I could publish a Composer package with this (and more functions) but the lack of visibility would be to the detriment of everyone who would potentially use this. I searched GitHub for plugins with Behat WP-CLI tests and found very few, I think there is some low hanging fruit like this that can help improve that.
I wouldn't call it a policy or requirement to only add things that we need ourselves, but if we have more use cases that helps shaping and testing such a new feature.
For example, there might be a need to easily disable all network requests to mimic a server being offline, or a way to simply respond with a specific HTTP status for some requests or all requests. Such scenarios could be helpful for both plugins and WP-CLI.
I have another use case! 💡
I was just looking at https://github.com/wp-cli/wp-cli/issues/6004 and realized we don't have any test coverage for wp cli update or at least wp cli check-updates. A common code path there is to fetch https://api.github.com/repos/wp-cli/wp-cli/releases?per_page=100 and look for the latest major/minor/patch releases.
If there were a way to mock that HTTP request, that would already be very helpful. To do that, we'd to be able to alter the options \WP_CLI\Utils\http_request() passes to the Requests library.
One solution I could think of right now is to add a new WP-CLI hook inside \WP_CLI\Utils\http_request() to filter the $options passed to the Requests library, for example to change the Transport instance used by Requests or use Requests' own hooks system if needed.
Let's say the hook name is http_request_options, then in a test we could do something like:
use WpOrg\Requests\Transport;
class Mock_Requests_Transport implements Transport {
// ...
}
WP_CLI::add_hook(
'http_request_options',
static function( $options ) {
// Modify transport here.
return $options;
}
}
Some examples of mock transports:
https://github.com/WordPress/Requests/blob/4b8a349f2c33efce3e1e22647c8634f29c5b90fe/tests/Fixtures/TransportMock.php https://github.com/wp-cli/wp-cli/blob/0187f2b4ce7e83f31bf4beecd5ad4f11ddf8b57b/tests/mock-requests-transport.php
Of course it would be nice to abstract all of this behind a Given a request to ... step.
Ideally we could support this both for requests made both through WP-CLI internals and through WordPress.
Maybe that step could somehow make both work seamlessly?
I think I have cracked it. At least https://github.com/wp-cli/wp-cli-tests/pull/235 seems to solve this, PTAL :)