Skip to content

Commit 892c5d7

Browse files
authored
Merge pull request #235 from wp-cli/try/210-http-mocking
Add http request mocking support
2 parents 2db8868 + a3f9c16 commit 892c5d7

File tree

3 files changed

+247
-0
lines changed

3 files changed

+247
-0
lines changed

features/http-mocking.feature

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
Feature: HTTP request mocking
2+
3+
Scenario: Mock HTTP request in WP-CLI
4+
Given that HTTP requests to https://api.github.com/repos/wp-cli/wp-cli/releases?per_page=100 will respond with:
5+
"""
6+
HTTP/1.1 200
7+
Content-Type: application/json
8+
9+
[
10+
{
11+
"url": "https://api.github.com/repos/wp-cli/wp-cli/releases/169243978",
12+
"assets_url": "https://api.github.com/repos/wp-cli/wp-cli/releases/169243978/assets",
13+
"upload_url": "https://uploads.github.com/repos/wp-cli/wp-cli/releases/169243978/assets{?name,label}",
14+
"html_url": "https://github.com/wp-cli/wp-cli/releases/tag/v999.9.9",
15+
"id": 169243978,
16+
"author": {
17+
"login": "schlessera",
18+
"id": 83631,
19+
"node_id": "MDQ6VXNlcjgzNjMx",
20+
"avatar_url": "https://avatars.githubusercontent.com/u/83631?v=4",
21+
"gravatar_id": "",
22+
"url": "https://api.github.com/users/schlessera",
23+
"html_url": "https://github.com/schlessera",
24+
"followers_url": "https://api.github.com/users/schlessera/followers",
25+
"following_url": "https://api.github.com/users/schlessera/following{/other_user}",
26+
"gists_url": "https://api.github.com/users/schlessera/gists{/gist_id}",
27+
"starred_url": "https://api.github.com/users/schlessera/starred{/owner}{/repo}",
28+
"subscriptions_url": "https://api.github.com/users/schlessera/subscriptions",
29+
"organizations_url": "https://api.github.com/users/schlessera/orgs",
30+
"repos_url": "https://api.github.com/users/schlessera/repos",
31+
"events_url": "https://api.github.com/users/schlessera/events{/privacy}",
32+
"received_events_url": "https://api.github.com/users/schlessera/received_events",
33+
"type": "User",
34+
"user_view_type": "public",
35+
"site_admin": false
36+
},
37+
"node_id": "RE_kwDOACQFs84KFnVK",
38+
"tag_name": "v999.9.9",
39+
"target_commitish": "main",
40+
"name": "Version 999.9.9",
41+
"draft": false,
42+
"prerelease": false,
43+
"created_at": "2024-08-08T03:04:55Z",
44+
"published_at": "2024-08-08T03:51:13Z",
45+
"assets": [
46+
{
47+
"url": "https://api.github.com/repos/wp-cli/wp-cli/releases/assets/184590231",
48+
"id": 184590231,
49+
"node_id": "RA_kwDOACQFs84LAJ-X",
50+
"name": "wp-cli-999.9.9.phar",
51+
"label": null,
52+
"content_type": "application/octet-stream",
53+
"state": "uploaded",
54+
"size": 7048108,
55+
"download_count": 722639,
56+
"created_at": "2024-08-08T03:51:05Z",
57+
"updated_at": "2024-08-08T03:51:08Z",
58+
"browser_download_url": "https://github.com/wp-cli/wp-cli/releases/download/v999.9.9/wp-cli-999.9.9.phar"
59+
}
60+
],
61+
"tarball_url": "https://api.github.com/repos/wp-cli/wp-cli/tarball/v999.9.9",
62+
"zipball_url": "https://api.github.com/repos/wp-cli/wp-cli/zipball/v999.9.9",
63+
"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)]",
64+
"reactions": {
65+
"url": "https://api.github.com/repos/wp-cli/wp-cli/releases/169243978/reactions",
66+
"total_count": 9,
67+
"+1": 4,
68+
"-1": 0,
69+
"laugh": 0,
70+
"hooray": 1,
71+
"confused": 0,
72+
"heart": 0,
73+
"rocket": 4,
74+
"eyes": 0
75+
}
76+
}
77+
]
78+
"""
79+
80+
When I run `wp cli check-update`
81+
Then STDOUT should be a table containing rows:
82+
| version | update_type | package_url |
83+
| 999.9.9 | major | https://github.com/wp-cli/wp-cli/releases/download/v999.9.9/wp-cli-999.9.9.phar |
84+
85+
Scenario: Mock HTTP request in WordPress
86+
Given a WP install
87+
And that HTTP requests to https://api.wordpress.org/core/version-check/1.7/ will respond with:
88+
"""
89+
HTTP/1.1 200
90+
Content-Type: application/json
91+
92+
{
93+
"offers": [
94+
{
95+
"response": "latest",
96+
"download": "https:\/\/downloads.wordpress.org\/release\/wordpress-999.9.9.zip",
97+
"locale": "en_US",
98+
"packages": {
99+
"full": "https:\/\/downloads.wordpress.org\/release\/wordpress-999.9.9.zip",
100+
"no_content": "https:\/\/downloads.wordpress.org\/release\/wordpress-999.9.9-no-content.zip",
101+
"new_bundled": "https:\/\/downloads.wordpress.org\/release\/wordpress-999.9.9-new-bundled.zip",
102+
"partial": false,
103+
"rollback": false
104+
},
105+
"current": "999.9.9",
106+
"version": "999.9.9",
107+
"php_version": "7.2.24",
108+
"mysql_version": "5.5.5",
109+
"new_bundled": "6.7",
110+
"partial_version": false
111+
}
112+
],
113+
"translations": []
114+
}
115+
"""
116+
117+
When I run `wp core check-update`
118+
Then STDOUT should be a table containing rows:
119+
| version | update_type | package_url |
120+
| 999.9.9 | major | https://downloads.wordpress.org/release/wordpress-999.9.9.zip |

src/Context/FeatureContext.php

+2
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ class FeatureContext implements SnippetAcceptingContext {
107107
private static $scenario_count = 0; // Scenario count, incremented on `@AfterScenario`.
108108
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.
109109

110+
private $mocked_requests = [];
111+
110112
/**
111113
* Get the path to the Composer vendor folder.
112114
*

src/Context/GivenStepDefinitions.php

+125
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,131 @@ public function given_string_replaced_with_string_in_a_specific_file( $search, $
8181
file_put_contents( $full_path, $contents );
8282
}
8383

84+
/**
85+
* @Given /^that HTTP requests to (.*?) will respond with:$/
86+
*/
87+
public function given_a_request_to_a_url_respond_with_file( $url_or_pattern, PyStringNode $content ) {
88+
if ( ! isset( $this->variables['RUN_DIR'] ) ) {
89+
$this->create_run_dir();
90+
}
91+
92+
$config_file = $this->variables['RUN_DIR'] . '/wp-cli.yml';
93+
$mock_file = $this->variables['RUN_DIR'] . '/mock-requests.php';
94+
$dir = dirname( $config_file );
95+
96+
if ( ! file_exists( $dir ) ) {
97+
mkdir( $dir, 0777, true /*recursive*/ );
98+
}
99+
100+
$config_file_contents = <<<FILE
101+
require:
102+
- mock-requests.php
103+
FILE;
104+
105+
file_put_contents(
106+
$config_file,
107+
$config_file_contents
108+
);
109+
110+
$this->mocked_requests[ $url_or_pattern ] = (string) $content;
111+
112+
$mocked_requests = var_export( $this->mocked_requests, true /* return */ );
113+
114+
$mock_file_contents = <<<FILE
115+
<?php
116+
use WpOrg\Requests\Hooks;
117+
use WpOrg\Requests\Transport;
118+
use WpOrg\Requests\Transport\Curl;
119+
use WpOrg\Requests\Requests;
120+
121+
class WP_CLI_Tests_Mock_Requests_Transport implements Transport {
122+
public function request( \$url, \$headers = array(), \$data = array(), \$options = array() ) {
123+
\$mocked_requests = $mocked_requests;
124+
125+
foreach ( \$mocked_requests as \$pattern => \$response ) {
126+
\$pattern = '/' . preg_quote( \$pattern, '/' ) . '/';
127+
if ( 1 === preg_match( \$pattern, \$url ) ) {
128+
\$pos = strpos( \$response, "\\n\\n");
129+
if ( false !== \$pos ) {
130+
\$response = substr( \$response, 0, \$pos ) . "\\r\\n\\r\\n" . substr( \$response, \$pos + 2 );
131+
}
132+
return \$response;
133+
}
134+
}
135+
136+
return (new Curl())->request( \$url, \$headers, \$data, \$options );
137+
}
138+
139+
public function request_multiple( \$requests, \$options ) {
140+
throw new Exception( 'Method not implemented: ' . __METHOD__ );
141+
}
142+
143+
public static function test( \$capabilities = array() ) {
144+
return true;
145+
}
146+
}
147+
148+
WP_CLI::add_hook(
149+
'http_request_options',
150+
static function( \$options ) {
151+
\$options['transport'] = new WP_CLI_Tests_Mock_Requests_Transport();
152+
return \$options;
153+
}
154+
);
155+
156+
WP_CLI::add_wp_hook(
157+
'pre_http_request',
158+
static function( \$pre, \$parsed_args, \$url ) {
159+
\$mocked_requests = $mocked_requests;
160+
161+
foreach ( \$mocked_requests as \$pattern => \$response ) {
162+
\$pattern = '/' . preg_quote( \$pattern, '/' ) . '/';
163+
if ( 1 === preg_match( \$pattern, \$url ) ) {
164+
\$pos = strpos( \$response, "\n\n");
165+
if ( false !== \$pos ) {
166+
\$response = substr( \$response, 0, \$pos ) . "\r\n\r\n" . substr( \$response, \$pos + 2 );
167+
}
168+
Requests::parse_multiple(
169+
\$response,
170+
array(
171+
'url' => \$url,
172+
'headers' => array(),
173+
'data' => array(),
174+
'options' => array_merge(
175+
Requests::OPTION_DEFAULTS,
176+
array(
177+
'hooks' => new Hooks(),
178+
)
179+
),
180+
)
181+
);
182+
183+
return array(
184+
'headers' => \$response->headers->getAll(),
185+
'body' => \$response->body,
186+
'response' => array(
187+
'code' => \$response->status_code,
188+
'message' => get_status_header_desc( \$response->status_code ),
189+
),
190+
'cookies' => array(),
191+
'filename' => '',
192+
);
193+
}
194+
}
195+
196+
return \$pre;
197+
},
198+
10,
199+
3
200+
);
201+
FILE;
202+
203+
file_put_contents(
204+
$mock_file,
205+
$mock_file_contents
206+
);
207+
}
208+
84209
/**
85210
* @Given WP files
86211
*/

0 commit comments

Comments
 (0)