Skip to content
Merged
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
13 changes: 13 additions & 0 deletions features/behat-steps.feature
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,19 @@ Feature: Test that WP-CLI Behat steps work as expected
"""
Then the wp-cli.yml file should exist
And the mock-requests.php file should exist
And the mock-requests-data.php file should exist

When I run `php -l mock-requests.php`
Then STDOUT should contain:
"""
No syntax errors detected
"""

When I run `php -l mock-requests-data.php`
Then STDOUT should contain:
"""
No syntax errors detected
"""

@require-wp
Scenario: Test background process launch
Expand Down
15 changes: 15 additions & 0 deletions phpcs.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,19 @@
<rule ref="WordPress.WP.GlobalVariablesOverride">
<exclude-pattern>*/generate-coverage\.php$</exclude-pattern>
</rule>

<!-- These are stand-alone files that are copied into the test run directory and
executed by the child WP-CLI process, not loaded as part of this package.
They declare a class once per supported Requests major version, and their
method signatures are dictated by the transport interface they implement,
so unused parameters cannot be dropped. -->
<rule ref="Generic.Files.OneObjectStructurePerFile">
<exclude-pattern>*/src/Context/templates/*</exclude-pattern>
</rule>
<rule ref="Generic.Classes.DuplicateClassName">
<exclude-pattern>*/src/Context/templates/*</exclude-pattern>
</rule>
<rule ref="Generic.CodeAnalysis.UnusedFunctionParameter">
<exclude-pattern>*/src/Context/templates/*</exclude-pattern>
</rule>
</ruleset>
3 changes: 3 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ parameters:
- tests
excludePaths:
- tests/data
# Templates are copied into the test run directory and executed by the
# child WP-CLI process, where Requests v1 classes may still exist.
- src/Context/templates
scanDirectories:
- vendor/wp-cli/wp-cli
- vendor/phpunit/php-code-coverage
Expand Down
143 changes: 5 additions & 138 deletions src/Context/GivenStepDefinitions.php
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ public function given_a_request_to_a_url_respond_with_file( $url_or_pattern, PyS

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

if ( ! file_exists( $dir ) ) {
Expand All @@ -209,146 +210,12 @@ public function given_a_request_to_a_url_respond_with_file( $url_or_pattern, PyS

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

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

$mock_file_contents = <<<FILE
<?php
/**
* HTTP request mocking supporting both Requests v1 and v2.
*/

trait WP_CLI_Tests_Mock_Requests_Trait {
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 );
}
if ( ! empty( \$options['filename'] ) ) {
\$body = '';
\$body_pos = strpos( \$response, "\\r\\n\\r\\n" );
if ( false !== \$body_pos ) {
\$body = substr( \$response, \$body_pos + 4 );
}
file_put_contents( \$options['filename'], \$body );
}
return \$response;
}
}

if ( class_exists( '\WpOrg\Requests\Transport\Curl' ) ) {
return ( new \WpOrg\Requests\Transport\Curl() )->request( \$url, \$headers, \$data, \$options );
}

return ( new \Requests_Transport_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;
}
}

if ( interface_exists( '\WpOrg\Requests\Transport' ) ) {
class WP_CLI_Tests_Mock_Requests_Transport implements \WpOrg\Requests\Transport {
use WP_CLI_Tests_Mock_Requests_Trait;
}
} else {
class WP_CLI_Tests_Mock_Requests_Transport implements \Requests_Transport {
use WP_CLI_Tests_Mock_Requests_Trait;
}
}

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 );
}

if ( class_exists( '\WpOrg\Requests\Requests' ) ) {
WpOrg\Requests\Requests::parse_multiple(
\$response,
array(
'url' => \$url,
'headers' => array(),
'data' => array(),
'options' => array_merge(
WpOrg\Requests\Requests::OPTION_DEFAULTS,
array(
'hooks' => new WpOrg\Requests\Hooks(),
)
),
)
);
} else {
\Requests::parse_multiple(
\$response,
array(
'url' => \$url,
'headers' => array(),
'data' => array(),
'options' => array(
'blocking' => true,
'filename' => false,
'follow_redirects' => true,
'redirected' => 0,
'redirects' => 10,
'hooks' => new Requests_Hooks(),
),
)
);
}

if ( ! empty( \$parsed_args['filename'] ) ) {
file_put_contents( \$parsed_args['filename'], \$response->body );
}

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
$data_file,
'<?php' . PHP_EOL . 'return ' . var_export( $this->mocked_requests, true /* return */ ) . ';' . PHP_EOL
);

copy( __DIR__ . '/templates/mock-requests.php', $mock_file );
}

/**
Expand Down
135 changes: 135 additions & 0 deletions src/Context/templates/mock-requests.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<?php
/**
* HTTP request mocking supporting both Requests v1 and v2.
*
* This file is copied verbatim into the test run directory by
* `WP_CLI\Tests\Context\GivenStepDefinitions::given_a_request_to_a_url_respond_with_file()`.
* The mocked responses themselves are generated next to it as `mock-requests-data.php`.
*/

trait WP_CLI_Tests_Mock_Requests_Trait {
public function request( $url, $headers = array(), $data = array(), $options = array() ) {
$mocked_requests = require __DIR__ . '/mock-requests-data.php';

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 );
}
if ( ! empty( $options['filename'] ) ) {
$body = '';
$body_pos = strpos( $response, "\r\n\r\n" );
if ( false !== $body_pos ) {
$body = substr( $response, $body_pos + 4 );
}
file_put_contents( $options['filename'], $body );
}
return $response;
}
}

if ( class_exists( '\WpOrg\Requests\Transport\Curl' ) ) {
return ( new \WpOrg\Requests\Transport\Curl() )->request( $url, $headers, $data, $options );
}

return ( new \Requests_Transport_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;
}
}

if ( interface_exists( '\WpOrg\Requests\Transport' ) ) {
class WP_CLI_Tests_Mock_Requests_Transport implements \WpOrg\Requests\Transport {
use WP_CLI_Tests_Mock_Requests_Trait;
}
} else {
class WP_CLI_Tests_Mock_Requests_Transport implements \Requests_Transport {
use WP_CLI_Tests_Mock_Requests_Trait;
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

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 = require __DIR__ . '/mock-requests-data.php';

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 );
}

if ( class_exists( '\WpOrg\Requests\Requests' ) ) {
WpOrg\Requests\Requests::parse_multiple(
$response,
array(
'url' => $url,
'headers' => array(),
'data' => array(),
'options' => array_merge(
WpOrg\Requests\Requests::OPTION_DEFAULTS,
array(
'hooks' => new WpOrg\Requests\Hooks(),
)
),
)
);
} else {
\Requests::parse_multiple(
$response,
array(
'url' => $url,
'headers' => array(),
'data' => array(),
'options' => array(
'blocking' => true,
'filename' => false,
'follow_redirects' => true,
'redirected' => 0,
'redirects' => 10,
'hooks' => new Requests_Hooks(),
),
)
);
}

if ( ! empty( $parsed_args['filename'] ) ) {
file_put_contents( $parsed_args['filename'], $response->body );
}

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
);
Loading