Make WordPress Core

Changeset 65320


Ignore:
Timestamp:
11/06/2026 08:43:25 PM (less than one hour ago)
Author:
lancewillett
Message:

Tests: Run the cURL transport tests without live external HTTP.

Follow-up to [34639], [64733], [65084].

Developed in: ​https://github.com/WordPress/wordpress-develop/pull/15488

Props shailu25.
See #63914.

Location:
trunk/tests/phpunit/tests/http
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/tests/phpunit/tests/http/base.php

    r65084 r65320  
    1212 */
    1313abstract class WP_HTTP_UnitTestCase extends WP_UnitTestCase {
    14         // You can use your own version of data/WPHTTP-testcase-redirection-script.php here.
    15         public $redirection_script = 'http://api.wordpress.org/core/tests/1.0/redirection.php';
     14        /**
     15         * URL of the local redirection fixture (set in set_up() when available).
     16         *
     17         * Prefer the local PHP built-in server fixture. Tests that need redirection
     18         * coverage must call require_redirection_fixture() so missing fixtures skip
     19         * or fail instead of falling back to a live api.wordpress.org request.
     20         *
     21         * @var string
     22         */
     23        public $redirection_script = '';
    1624
    1725        /**
    … …  
    7179        private static $file_stream_fixture_fail_reason = null;
    7280
     81        /**
     82         * Captured HTTP request args from the most recent filtered request.
     83         *
     84         * @var array
     85         */
    7386        protected $http_request_args;
     87
     88        /**
     89         * Captured Requests options from the most recent intercepted request.
     90         *
     91         * @var array
     92         */
     93        protected $requests_options = array();
    7494
    7595        /**
    … …  
    297317        }
    298318
    299         public function filter_http_request_args( array $args ) {
    300                 $this->http_request_args = $args;
    301                 return $args;
     319        /**
     320         * Allows only this class's local HTTP fixture through the external-HTTP guard.
     321         *
     322         * Other tests continue to use the parent blocker for any non-mocked request.
     323         *
     324         * @param false|array|WP_Error $response A preemptive response, or false when none was given.
     325         * @param array                $args     Request arguments.
     326         * @param string               $url      The request URL.
     327         * @return false|array|WP_Error The preemptive response, or an error for a blocked request.
     328         */
     329        public function block_external_http_request( $response, array $args, string $url ) {
     330                if ( false === $response && $this->is_fixture_url( $url ) ) {
     331                        if ( ! has_action( 'requests-requests.before_redirect', array( $this, 'block_external_fixture_redirect' ) ) ) {
     332                                add_action( 'requests-requests.before_redirect', array( $this, 'block_external_fixture_redirect' ), 10, 5 );
     333                        }
     334
     335                        return $response;
     336                }
     337
     338                return parent::block_external_http_request( $response, $args, $url );
     339        }
     340
     341        /**
     342         * Whether a URL targets this class's local fixture server host and port.
     343         *
     344         * @param string $url Request URL.
     345         * @return bool True when the URL matches the fixture host and port.
     346         */
     347        protected function is_fixture_url( string $url ): bool {
     348                if ( ! self::$file_stream_fixture_url ) {
     349                        return false;
     350                }
     351
     352                $fixture_host = wp_parse_url( self::$file_stream_fixture_url, PHP_URL_HOST );
     353                $fixture_port = wp_parse_url( self::$file_stream_fixture_url, PHP_URL_PORT );
     354                $host         = wp_parse_url( $url, PHP_URL_HOST );
     355                $port         = wp_parse_url( $url, PHP_URL_PORT );
     356
     357                return is_string( $fixture_host )
     358                        && is_string( $host )
     359                        && strtolower( $fixture_host ) === strtolower( $host )
     360                        && (int) $fixture_port === (int) $port;
     361        }
     362
     363        /**
     364         * Blocks redirects from the local fixture to non-fixture URLs.
     365         *
     366         * Defensive only: the current redirection fixture never leaves this host/port,
     367         * so this callback is not exercised by existing tests.
     368         *
     369         * @throws WpOrg\Requests\Exception When the redirect target leaves the fixture server.
     370         *
     371         * @param string                  $location    Redirect location (passed by reference by Requests).
     372         * @param array                   $req_headers Request headers.
     373         * @param array|null              $req_data    Request data.
     374         * @param array                   $options     Requests options.
     375         * @param WpOrg\Requests\Response $response    Response that triggered the redirect.
     376         */
     377        public function block_external_fixture_redirect( &$location, &$req_headers, &$req_data, &$options, $response ) {
     378                if ( $this->is_fixture_url( $location ) ) {
     379                        return;
     380                }
     381
     382                $this->blocked_http_requests[] = $location;
     383
     384                throw new WpOrg\Requests\Exception(
     385                        'External HTTP redirects are blocked in tests that are not in the `external-http` group.',
     386                        'test_external_http_blocked'
     387                );
    302388        }
    303389
    … …  
    307393        public function test_redirect_on_301() {
    308394                // 5 : 5 & 301.
    309                 $res = $this->wp_remote_request( $this->redirection_script . '?code=301&rt=' . 5, array( 'redirection' => 5 ) );
     395                $res = $this->wp_remote_request( $this->get_redirection_script() . '?code=301&rt=' . 5, array( 'redirection' => 5 ) );
    310396
    311397                $this->assertNotWPError( $res );
    … …  
    318404        public function test_redirect_on_302() {
    319405                // 5 : 5 & 302.
    320                 $res = $this->wp_remote_request( $this->redirection_script . '?code=302&rt=' . 5, array( 'redirection' => 5 ) );
     406                $res = $this->wp_remote_request( $this->get_redirection_script() . '?code=302&rt=' . 5, array( 'redirection' => 5 ) );
    321407
    322408                $this->assertNotWPError( $res );
    … …  
    331417        public function test_redirect_on_301_no_redirect() {
    332418                // 5 > 0 & 301.
    333                 $res = $this->wp_remote_request( $this->redirection_script . '?code=301&rt=' . 5, array( 'redirection' => 0 ) );
     419                $res = $this->wp_remote_request( $this->get_redirection_script() . '?code=301&rt=' . 5, array( 'redirection' => 0 ) );
    334420
    335421                $this->assertNotWPError( $res );
    … …  
    344430        public function test_redirect_on_302_no_redirect() {
    345431                // 5 > 0 & 302.
    346                 $res = $this->wp_remote_request( $this->redirection_script . '?code=302&rt=' . 5, array( 'redirection' => 0 ) );
     432                $res = $this->wp_remote_request( $this->get_redirection_script() . '?code=302&rt=' . 5, array( 'redirection' => 0 ) );
    347433
    348434                $this->assertNotWPError( $res );
    … …  
    355441        public function test_redirections_equal() {
    356442                // 5 - 5.
    357                 $res = $this->wp_remote_request( $this->redirection_script . '?rt=' . 5, array( 'redirection' => 5 ) );
     443                $res = $this->wp_remote_request( $this->get_redirection_script() . '?rt=' . 5, array( 'redirection' => 5 ) );
    358444
    359445                $this->assertNotWPError( $res );
    … …  
    366452        public function test_no_head_redirections() {
    367453                // No redirections on HEAD request.
    368                 $res = $this->wp_remote_request( $this->redirection_script . '?code=302&rt=' . 1, array( 'method' => 'HEAD' ) );
     454                $res = $this->wp_remote_request( $this->get_redirection_script() . '?code=302&rt=' . 1, array( 'method' => 'HEAD' ) );
    369455
    370456                $this->assertNotWPError( $res );
    … …  
    380466                // Redirections on HEAD request when Requested.
    381467                $res = $this->wp_remote_request(
    382                         $this->redirection_script . '?rt=' . 5,
     468                        $this->get_redirection_script() . '?rt=' . 5,
    383469                        array(
    384470                                'redirection' => 5,
    … …  
    396482        public function test_redirections_greater() {
    397483                // 10 > 5.
    398                 $res = $this->wp_remote_request( $this->redirection_script . '?rt=' . 10, array( 'redirection' => 5 ) );
     484                $res = $this->wp_remote_request( $this->get_redirection_script() . '?rt=' . 10, array( 'redirection' => 5 ) );
    399485
    400486                $this->assertWPError( $res );
    … …  
    406492        public function test_redirections_greater_edgecase() {
    407493                // 6 > 5 (close edge case).
    408                 $res = $this->wp_remote_request( $this->redirection_script . '?rt=' . 6, array( 'redirection' => 5 ) );
     494                $res = $this->wp_remote_request( $this->get_redirection_script() . '?rt=' . 6, array( 'redirection' => 5 ) );
    409495
    410496                $this->assertWPError( $res );
    … …  
    416502        public function test_redirections_less_edgecase() {
    417503                // 4 < 5 (close edge case).
    418                 $res = $this->wp_remote_request( $this->redirection_script . '?rt=' . 4, array( 'redirection' => 5 ) );
     504                $res = $this->wp_remote_request( $this->get_redirection_script() . '?rt=' . 4, array( 'redirection' => 5 ) );
    419505
    420506                $this->assertNotWPError( $res );
    … …  
    428514        public function test_redirections_zero_redirections_specified() {
    429515                // 0 redirections asked for, should return the document?
    430                 $res = $this->wp_remote_request( $this->redirection_script . '?code=302&rt=' . 5, array( 'redirection' => 0 ) );
     516                $res = $this->wp_remote_request( $this->get_redirection_script() . '?code=302&rt=' . 5, array( 'redirection' => 0 ) );
    431517
    432518                $this->assertNotWPError( $res );
    … …  
    443529        public function test_location_header_on_201() {
    444530                // Prints PASS on initial load, FAIL if the client follows the specified redirection.
    445                 $res = $this->wp_remote_request( $this->redirection_script . '?201-location=true' );
     531                $res = $this->wp_remote_request( $this->get_redirection_script() . '?201-location=true' );
    446532
    447533                $this->assertNotWPError( $res );
    … …  
    458544         */
    459545        public function test_no_redirection_on_PUT() {
    460                 $url = $this->redirection_script . '?201-location=1';
     546                $url = $this->get_redirection_script() . '?201-location=1';
    461547
    462548                // Test 301 - POST to POST.
    … …  
    511597                        'test3' => '',
    512598                );
    513                 $res     = $this->wp_remote_request( $this->redirection_script . '?header-check', array( 'headers' => $headers ) );
     599                $res     = $this->wp_remote_request( $this->get_redirection_script() . '?header-check', array( 'headers' => $headers ) );
    514600
    515601                $this->assertNotWPError( $res );
    … …  
    547633
    548634                $this->fail( 'The local HTTP stream fixture server was not started.' );
     635        }
     636
     637        /**
     638         * Ensures the local redirection fixture server is available.
     639         *
     640         * Skips when a prerequisite is missing. Fails with a diagnostic when
     641         * fixture startup failed unexpectedly. Does not fall back to the live
     642         * api.wordpress.org redirection script.
     643         *
     644         * @ticket 63914
     645         */
     646        protected function require_redirection_fixture() {
     647                if ( ! empty( $this->redirection_script ) ) {
     648                        return;
     649                }
     650
     651                if ( self::$file_stream_fixture_skip_reason ) {
     652                        $this->markTestSkipped( self::$file_stream_fixture_skip_reason );
     653                }
     654
     655                if ( self::$file_stream_fixture_fail_reason ) {
     656                        $this->fail( self::$file_stream_fixture_fail_reason );
     657                }
     658
     659                $this->fail( 'The local HTTP redirection fixture server was not started.' );
     660        }
     661
     662        /**
     663         * Returns the local redirection fixture URL, requiring it first.
     664         *
     665         * @ticket 63914
     666         *
     667         * @return string Local redirection fixture URL.
     668         */
     669        protected function get_redirection_script() {
     670                $this->require_redirection_fixture();
     671                return $this->redirection_script;
    549672        }
    550673
    … …  
    647770         */
    648771        public function test_post_redirect_to_method_300( $response_code, $method ) {
    649                 $url = $this->redirection_script . '?post-redirect-to-method=1';
     772                $url = $this->get_redirection_script() . '?post-redirect-to-method=1';
    650773
    651774                $res = $this->wp_remote_post( add_query_arg( 'response_code', $response_code, $url ), array( 'timeout' => 30 ) );
    … …  
    696819         */
    697820        public function test_ip_url_with_host_header() {
    698                 $parts = wp_parse_url( $this->redirection_script );
     821                $parts = wp_parse_url( $this->get_redirection_script() );
    699822                $this->assertIsArray( $parts );
    700823                $this->assertNotEmpty( $parts['host'] );
    … …  
    742865
    743866        /**
    744          * Test HTTP requests where SSL verification is disabled but the CA bundle is still populated.
     867         * Test HTTP requests where SSL verification is disabled.
     868         *
     869         * Asserts that sslcertificates stays set when sslverify is false (#33978 /
     870         * r34639), and that WP_Http::request() maps that to Requests verify /
     871         * verifyname. Stops before connecting.
    745872         *
    746873         * @ticket 33978
     874         * @ticket 63914
    747875         *
    748876         * @covers ::wp_remote_head
    749877         */
    750878        public function test_https_url_without_ssl_verification() {
    751                 $url  = 'https://wordpress.org/';
    752                 $args = array(
    753                         'sslverify' => false,
    754                 );
    755 
     879                remove_filter( 'pre_http_request', array( $this, 'block_external_http_request' ), PHP_INT_MAX );
    756880                add_filter( 'http_request_args', array( $this, 'filter_http_request_args' ) );
    757 
    758                 $res = $this->wp_remote_head( $url, $args );
    759 
    760                 remove_filter( 'http_request_args', array( $this, 'filter_http_request_args' ) );
    761 
     881                add_action( 'requests-requests.before_request', array( $this, 'capture_requests_options' ), 10, 5 );
     882
     883                try {
     884                        // Call the global directly: the intentional WP_Error from the capture
     885                        // callback must not go through retry_on_error()'s 5s sleeps.
     886                        $res = wp_remote_head( 'https://wordpress.org/', array( 'sslverify' => false ) );
     887                } finally {
     888                        remove_filter( 'http_request_args', array( $this, 'filter_http_request_args' ) );
     889                        remove_action( 'requests-requests.before_request', array( $this, 'capture_requests_options' ), 10 );
     890                }
     891
     892                $this->assertWPError( $res );
    762893                $this->assertNotEmpty( $this->http_request_args['sslcertificates'] );
    763                 $this->assertNotWPError( $res );
    764         }
    765 
    766         /**
    767          * Test HTTP Cookie handling.
    768          *
    769          * Uses the local redirection fixture when available so cookie acceptance and
    770          * rejection checks do not depend on a live api.wordpress.org request.
    771          *
    772          * Parent-domain cookie acceptance is covered by
    773          * test_parent_domain_cookie_is_sent_on_redirect().
    774          *
    775          * @ticket 21182
    776          * @ticket 63914
    777          *
    778          * @covers ::wp_remote_get
    779          * @covers ::wp_remote_retrieve_body
    780          */
    781         public function test_cookie_handling() {
    782                 $url = $this->redirection_script . '?cookie-test=1';
    783 
    784                 $res = $this->wp_remote_get( $url );
    785 
    786                 $this->assertNotWPError( $res );
    787                 $this->assertSame( 'PASS', wp_remote_retrieve_body( $res ) );
    788         }
    789 
    790         /**
    791          * Verifies parent-domain cookies are accepted and sent on redirect.
    792          *
    793          * Complements test_cookie_handling(): the IP-based local fixture intentionally
    794          * omits parent-domain cookies, so this named-origin mock keeps Domain=.example.test
    795          * acceptance deterministic without a live network request.
    796          *
    797          * @ticket 21182
    798          * @ticket 63914
    799          *
    800          * @covers ::wp_remote_get
    801          * @covers WP_Http::normalize_cookies
    802          */
    803         public function test_parent_domain_cookie_is_sent_on_redirect() {
    804                 add_action( 'requests-requests.before_request', array( $this, 'inject_parent_domain_cookie_test_transport' ), 10, 5 );
    805 
    806                 try {
    807                         $res = $this->wp_remote_get( 'http://api.example.test/cookie-wildcard-test' );
    808                 } finally {
    809                         remove_action( 'requests-requests.before_request', array( $this, 'inject_parent_domain_cookie_test_transport' ), 10 );
    810                 }
    811 
    812                 $this->assertNotWPError( $res );
    813                 $this->assertSame( 'PASS', wp_remote_retrieve_body( $res ) );
    814         }
    815 
    816         /**
    817          * Injects the parent-domain cookie test transport into Requests options.
     894                $this->assertFalse( $this->requests_options['verify'] );
     895                $this->assertFalse( $this->requests_options['verifyname'] );
     896        }
     897
     898        /**
     899         * Stores HTTP request args for later assertion.
     900         *
     901         * @param array $args Request arguments.
     902         * @return array Unmodified request arguments.
     903         */
     904        public function filter_http_request_args( array $args ) {
     905                $this->http_request_args = $args;
     906                return $args;
     907        }
     908
     909        /**
     910         * Captures Requests options and stops before a network connection.
     911         *
     912         * @throws WpOrg\Requests\Exception Always, after capturing options.
    818913         *
    819914         * @param string       $url     Request URL.
    … …  
    823918         * @param array        $options Request options (passed by reference).
    824919         */
     920        public function capture_requests_options( $url, $headers, $data, $type, &$options ) {
     921                $this->requests_options = $options;
     922
     923                throw new WpOrg\Requests\Exception( 'Stopped after capturing the request options.', 'test_stopped' );
     924        }
     925
     926        /**
     927         * Test HTTP Cookie handling.
     928         *
     929         * Uses the local redirection fixture when available so cookie acceptance and
     930         * rejection checks do not depend on a live api.wordpress.org request.
     931         *
     932         * Parent-domain cookie acceptance is covered by
     933         * test_parent_domain_cookie_is_sent_on_redirect().
     934         *
     935         * @ticket 21182
     936         * @ticket 63914
     937         *
     938         * @covers ::wp_remote_get
     939         * @covers ::wp_remote_retrieve_body
     940         */
     941        public function test_cookie_handling() {
     942                $url = $this->get_redirection_script() . '?cookie-test=1';
     943
     944                $res = $this->wp_remote_get( $url );
     945
     946                $this->assertNotWPError( $res );
     947                $this->assertSame( 'PASS', wp_remote_retrieve_body( $res ) );
     948        }
     949
     950        /**
     951         * Verifies parent-domain cookies are accepted and sent on redirect.
     952         *
     953         * Complements test_cookie_handling(): the IP-based local fixture intentionally
     954         * omits parent-domain cookies, so this named-origin mock keeps Domain=.example.test
     955         * acceptance deterministic without a live network request.
     956         *
     957         * @ticket 21182
     958         * @ticket 63914
     959         *
     960         * @covers ::wp_remote_get
     961         * @covers WP_Http::normalize_cookies
     962         */
     963        public function test_parent_domain_cookie_is_sent_on_redirect() {
     964                // Allow the canned Requests transport to run outside external-http.
     965                remove_filter( 'pre_http_request', array( $this, 'block_external_http_request' ), PHP_INT_MAX );
     966                add_action( 'requests-requests.before_request', array( $this, 'inject_parent_domain_cookie_test_transport' ), 10, 5 );
     967
     968                try {
     969                        $res = $this->wp_remote_get( 'http://api.example.test/cookie-wildcard-test' );
     970                } finally {
     971                        remove_action( 'requests-requests.before_request', array( $this, 'inject_parent_domain_cookie_test_transport' ), 10 );
     972                }
     973
     974                $this->assertNotWPError( $res );
     975                $this->assertSame( 'PASS', wp_remote_retrieve_body( $res ) );
     976        }
     977
     978        /**
     979         * Injects the parent-domain cookie test transport into Requests options.
     980         *
     981         * @param string       $url     Request URL.
     982         * @param array        $headers Request headers.
     983         * @param string|array $data    Request data.
     984         * @param string       $type    HTTP method.
     985         * @param array        $options Request options (passed by reference).
     986         */
    825987        public function inject_parent_domain_cookie_test_transport( $url, $headers, $data, $type, &$options ) {
    826988                $options['transport'] = new WP_HTTP_Parent_Domain_Cookie_Test_Transport();
    … …  
    828990
    829991        /**
    830          * Test if HTTPS support works.
     992         * Test if HTTPS support works against a live endpoint.
     993         *
     994         * Remains in external-http: this is intentional live TLS smoke coverage for
     995         * the real transport. Parsing and arg-shape checks live elsewhere offline.
    831996         *
    832997         * @group ssl
     998         * @group external-http
    833999         * @ticket 25007
     1000         * @ticket 63914
    8341001         *
    8351002         * @covers ::wp_remote_get
    … …  
    8511018         */
    8521019        public function test_url_with_double_slashes_path() {
    853                 $url = $this->redirection_script . '?rt=' . 0;
     1020                $url = $this->get_redirection_script() . '?rt=' . 0;
    8541021
    8551022                $path = parse_url( $url, PHP_URL_PATH );
  • trunk/tests/phpunit/tests/http/curl.php

    r64265 r65320  
    55/**
    66 * @group http
    7  * @group external-http
     7 *
     8 * @ticket 63914
    89 */
    910class Tests_HTTP_curl extends WP_HTTP_UnitTestCase {
Note: See TracChangeset for help on using the changeset viewer.