Skip to content

Commit 6dd119b

Browse files
authored
Merge branch 'main' into code-coverage
2 parents 50f20e6 + ca36e3b commit 6dd119b

File tree

6 files changed

+342
-19
lines changed

6 files changed

+342
-19
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ phpunit.xml
1212
phpcs.xml
1313
.phpcs.xml
1414
.phpunit.result.cache
15+
build/logs

features/http-mocking.feature

Lines changed: 120 additions & 0 deletions
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 |

features/steps.feature

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
Feature: Make sure "Given", "When", "Then" steps work as expected
2+
3+
Scenario: Variable names can only contain uppercase letters, digits and underscores and cannot begin with a digit.
4+
5+
When I run `echo value`
6+
And save STDOUT as {VARIABLE_NAME}
7+
And save STDOUT as {V}
8+
And save STDOUT as {_VARIABLE_NAME_STARTING_WITH_UNDERSCORE}
9+
And save STDOUT as {_}
10+
And save STDOUT as {VARIABLE_NAME_WITH_DIGIT_2}
11+
And save STDOUT as {V2}
12+
And save STDOUT as {_2}
13+
And save STDOUT as {2_VARIABLE_NAME_STARTING_WITH_DIGIT}
14+
And save STDOUT as {2}
15+
And save STDOUT as {VARIABLE_NAME_WITH_lowercase}
16+
And save STDOUT as {v}
17+
# Note this would give behat "undefined step" message as "save" step uses "\w+"
18+
#And save STDOUT as {VARIABLE_NAME_WITH_PERCENT_%}
19+
20+
When I run `echo {VARIABLE_NAME}`
21+
Then STDOUT should match /^value$/
22+
And STDOUT should be:
23+
"""
24+
value
25+
"""
26+
27+
When I run `echo {V}`
28+
Then STDOUT should match /^value$/
29+
30+
When I run `echo {_VARIABLE_NAME_STARTING_WITH_UNDERSCORE}`
31+
Then STDOUT should match /^value$/
32+
33+
When I run `echo {_}`
34+
Then STDOUT should match /^value$/
35+
36+
When I run `echo {VARIABLE_NAME_WITH_DIGIT_2}`
37+
Then STDOUT should match /^value$/
38+
39+
When I run `echo {V2}`
40+
Then STDOUT should match /^value$/
41+
42+
When I run `echo {_2}`
43+
Then STDOUT should match /^value$/
44+
45+
When I run `echo {2_VARIABLE_NAME_STARTING_WITH_DIGIT}`
46+
Then STDOUT should match /^\{2_VARIABLE_NAME_STARTING_WITH_DIGIT}$/
47+
And STDOUT should contain:
48+
"""
49+
{
50+
"""
51+
52+
When I run `echo {2}`
53+
Then STDOUT should match /^\{2}$/
54+
55+
When I run `echo {VARIABLE_NAME_WITH_lowercase}`
56+
Then STDOUT should match /^\{VARIABLE_NAME_WITH_lowercase}$/
57+
58+
When I run `echo {v}`
59+
Then STDOUT should match /^\{v}$/
60+
61+
Scenario: Special variables
62+
63+
When I run `echo {INVOKE_WP_CLI_WITH_PHP_ARGS-} cli info`
64+
And STDOUT should match /wp cli info/
65+
And STDERR should be empty
66+
67+
When I run `echo {WP_VERSION-latest}`
68+
Then STDOUT should match /\d\.\d/
69+
And STDERR should be empty

phpunit.xml.dist

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,10 @@
1212
<directory prefix="Test" suffix=".php">tests/tests</directory>
1313
</testsuite>
1414
</testsuites>
15+
16+
<coverage processUncoveredFiles="false">
17+
<include>
18+
<directory suffix=".php">src</directory>
19+
</include>
20+
</coverage>
1521
</phpunit>

src/Context/FeatureContext.php

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use RuntimeException;
1313
use WP_CLI\Process;
1414
use WP_CLI\Utils;
15+
use WP_CLI\WpOrgApi;
1516

1617
/**
1718
* Features context.
@@ -109,6 +110,8 @@ class FeatureContext implements SnippetAcceptingContext {
109110
private static $scenario_count = 0; // Scenario count, incremented on `@AfterScenario`.
110111
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.
111112

113+
private $mocked_requests = [];
114+
112115
/**
113116
* The current feature.
114117
*
@@ -758,25 +761,24 @@ private function replace_wp_versions( $str ) {
758761
if ( null === $wp_versions ) {
759762
$wp_versions = [];
760763

761-
$response = Utils\http_request( 'GET', 'https://api.wordpress.org/core/version-check/1.7/', null, [], [ 'timeout' => 30 ] );
762-
if ( 200 === $response->status_code ) {
763-
$body = json_decode( $response->body );
764-
if ( is_object( $body ) && isset( $body->offers ) && is_array( $body->offers ) ) {
765-
// Latest version alias.
766-
$wp_versions['{WP_VERSION-latest}'] = count( $body->offers ) ? $body->offers[0]->version : '';
767-
foreach ( $body->offers as $offer ) {
768-
$sub_ver = preg_replace( '/(^[0-9]+\.[0-9]+)\.[0-9]+$/', '$1', $offer->version );
769-
$sub_ver_key = "{WP_VERSION-{$sub_ver}-latest}";
770-
771-
$main_ver = preg_replace( '/(^[0-9]+)\.[0-9]+$/', '$1', $sub_ver );
772-
$main_ver_key = "{WP_VERSION-{$main_ver}-latest}";
773-
774-
if ( ! isset( $wp_versions[ $main_ver_key ] ) ) {
775-
$wp_versions[ $main_ver_key ] = $offer->version;
776-
}
777-
if ( ! isset( $wp_versions[ $sub_ver_key ] ) ) {
778-
$wp_versions[ $sub_ver_key ] = $offer->version;
779-
}
764+
$wp_org_api = new WpOrgApi();
765+
$result = $wp_org_api->get_core_version_check();
766+
767+
if ( is_array( $result ) && ! empty( $result['offers'] ) ) {
768+
// Latest version alias.
769+
$wp_versions['{WP_VERSION-latest}'] = count( $result['offers'] ) ? $result['offers'][0]['version'] : '';
770+
foreach ( $result['offers'] as $offer ) {
771+
$sub_ver = preg_replace( '/(^[0-9]+\.[0-9]+)\.[0-9]+$/', '$1', $offer['version'] );
772+
$sub_ver_key = "{WP_VERSION-{$sub_ver}-latest}";
773+
774+
$main_ver = preg_replace( '/(^[0-9]+)\.[0-9]+$/', '$1', $sub_ver );
775+
$main_ver_key = "{WP_VERSION-{$main_ver}-latest}";
776+
777+
if ( ! isset( $wp_versions[ $main_ver_key ] ) ) {
778+
$wp_versions[ $main_ver_key ] = $offer['version'];
779+
}
780+
if ( ! isset( $wp_versions[ $sub_ver_key ] ) ) {
781+
$wp_versions[ $sub_ver_key ] = $offer['version'];
780782
}
781783
}
782784
}

src/Context/GivenStepDefinitions.php

Lines changed: 125 additions & 0 deletions
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)