From a40898066faa56dd7edfdc1bcd7647aed00b4c48 Mon Sep 17 00:00:00 2001 From: Geoff Taylor Date: Mon, 20 May 2024 13:56:37 -0400 Subject: [PATCH] fix: WPGraphQL E2E module concurrentRawRequests fix --- src/Codeception/Module/WPGraphQL.php | 144 +++++++++++------- .../functional/WPGraphQLModuleCest.php | 2 +- 2 files changed, 94 insertions(+), 52 deletions(-) diff --git a/src/Codeception/Module/WPGraphQL.php b/src/Codeception/Module/WPGraphQL.php index e4fc50e..74f5545 100644 --- a/src/Codeception/Module/WPGraphQL.php +++ b/src/Codeception/Module/WPGraphQL.php @@ -5,6 +5,10 @@ use Codeception\Exception\ModuleException; use Codeception\Module; use Codeception\TestInterface; +use GuzzleHttp\Psr7; +use GuzzleHttp\Client; +use GuzzleHttp\Cookie\CookieJar; +use Tests\WPGraphQL\Logger\CodeceptLogger as Signal; /** * WPGraphQL module for Codeception @@ -28,6 +32,8 @@ class WPGraphQL extends Module { /** @var \Tests\WPGraphQL\Logger\CodeceptLogger */ private $logger = null; + protected static $cookieJar = null; + private function getHeaders() { $headers = [ 'Content-Type' => 'application/json' ]; $auth_header = $this->config['auth_header']; @@ -50,20 +56,22 @@ public function _before( TestInterface $test ) { if ( empty( $endpoint ) ) { throw new ModuleException( $this, 'Invalid endpoint.' ); } - $this->client = new \GuzzleHttp\Client( + $this->client = new Client( [ 'base_uri' => $endpoint, 'timeout' => 300, ] ); - $this->logger = new \Tests\WPGraphQL\Logger\CodeceptLogger(); + $this->logger = new Signal(); + self::$cookieJar = new CookieJar; } /** * Parses the request options and return a formatted request options * * @param array $selected_options The request options to parse. - * @return void + * + * @return array */ protected function parseRequestOptions( array $selected_options ) { $default_options = [ 'headers' => $this->getHeaders() ]; @@ -73,7 +81,7 @@ protected function parseRequestOptions( array $selected_options ) { $request_options = $default_options; foreach( $selected_options as $key => $value ) { - if ( in_array( $key, [ 'headers', 'suppress_mod_token' ] ) ) { + if ( in_array( $key, [ 'headers', 'suppress_mod_token', 'use_cookies' ] ) ) { continue; } @@ -85,6 +93,10 @@ protected function parseRequestOptions( array $selected_options ) { && isset( $request_options['headers']['Authorization'] ) ) { unset( $request_options['headers']['Authorization'] ); } + + if ( isset( $selected_options['use_cookies'] ) && true === $selected_options['use_cookies'] ) { + $request_options['cookies'] = self::$cookieJar; + } if ( ! empty( $selected_options['headers'] ) && is_array( $selected_options['headers'] ) ) { $request_options['headers'] = array_merge( $request_options['headers'], $selected_options['headers'] ); @@ -101,7 +113,7 @@ protected function parseRequestOptions( array $selected_options ) { * * @throws \Codeception\Exception\ModuleException Invalid endpoint | Invalid query. * - * @return array + * @return \Psr\Http\Message\ResponseInterface */ public function getRawRequest( string $query, array $selected_options = [] ) { $endpoint = $this->config['endpoint']; @@ -124,8 +136,7 @@ public function getRawRequest( string $query, array $selected_options = [] ) { throw new ModuleException( $this, 'Invalid response.' ); } - $this->logger->logData( $response->getHeaders() ); - $this->logger->logData( (string) $response->getBody() ); + $this->logger->logData( Psr7\Message::toString( $response ) ); return $response; } @@ -158,13 +169,13 @@ public function getRequest( string $query, array $selected_options = [] ) { /** * Sends a POST request to the GraphQL endpoint and return a response * - * @param string $query The GraphQL query to send. - * @param array $variables The variables to send with the query. - * @param string|null $selected_options Selected options to control various aspects of a request. + * @param string $query The GraphQL query to send. + * @param ?array $variables The variables to send with the query. + * @param ?array $selected_options Selected options to control various aspects of a request. * * @throws \Codeception\Exception\ModuleException Invalid endpoint. * - * @return array + * @return \Psr\Http\Message\ResponseInterface */ public function postRawRequest( $query, $variables = [], $selected_options = [] ) { $endpoint = $this->config['endpoint']; @@ -198,8 +209,7 @@ public function postRawRequest( $query, $variables = [], $selected_options = [] throw new ModuleException( $this, 'Invalid response.' ); } - $this->logger->logData( $response->getHeaders() ); - $this->logger->logData( (string) $response->getBody() ); + $this->logger->logData( Psr7\Message::toString( $response ) ); return $response; } @@ -207,9 +217,9 @@ public function postRawRequest( $query, $variables = [], $selected_options = [] /** * Sends POST request to the GraphQL endpoint and returns the query results * - * @param string $query The GraphQL query to send. - * @param array $variables The variables to send with the query. - * @param string|null $selected_options Selected options to control various aspects of a request. + * @param string $query The GraphQL query to send. + * @param ?array $variables The variables to send with the query. + * @param ?array $selected_options Selected options to control various aspects of a request. * * @throws \Codeception\Exception\ModuleException Invalid endpoint. * @@ -233,12 +243,12 @@ public function postRequest( $query, $variables = [], $selected_options = [] ) { /** * Sends a batch query request to the GraphQL endpoint and return a response * - * @param object{'query': string, 'variables': array} $operations An array of operations to send. - * @param array $selected_options Selected options to control various aspects of a request. + * @param object{query: string, variables: ?array}[] $operations An array of operations to send. + * @param array $selected_options Selected options to control various aspects of a request. * * @throws \Codeception\Exception\ModuleException Invalid endpoint. * - * @return array + * @return \Psr\Http\Message\ResponseInterface */ public function batchRawRequest( $operations, $selected_options = [] ) { $endpoint = $this->config['endpoint']; @@ -266,8 +276,7 @@ public function batchRawRequest( $operations, $selected_options = [] ) { throw new ModuleException( $this, 'Invalid response.' ); } - $this->logger->logData( $response->getHeaders() ); - $this->logger->logData( (string) $response->getBody() ); + $this->logger->logData( Psr7\Message::toString( $response ) ); return $response; } @@ -275,8 +284,8 @@ public function batchRawRequest( $operations, $selected_options = [] ) { /** * Sends a batch query request to the GraphQL endpoint and returns the query results * - * @param object{'query': string, 'variables': array} $operations An array of operations to send. - * @param array $selected_options Selected options to control various aspects of a request. + * @param object{query: string, variables: ?array}[] $operations An array of operations to send. + * @param array $selected_options Selected options to control various aspects of a request. * * @throws \Codeception\Exception\ModuleException Invalid endpoint. * @@ -300,13 +309,13 @@ public function batchRequest( $operations, $selected_options = [] ) { /** * Sends a concurrent requests to the GraphQL endpoint and returns a response * - * @param {'query': string, 'variables': array} $operations An array of operations to send. - * @param array $selected_options Selected options to control various aspects of a request. - * @param int $stagger The time in milliseconds to stagger requests. + * @param {query: string, variables: ?array}[] $operations An array of operations to send. + * @param array $selected_options Selected options to control various aspects of a request. + * @param int $stagger The time in milliseconds to stagger requests. * * @throws \Codeception\Exception\ModuleException Invalid endpoint. * - * @return array + * @return \Psr\Http\Message\ResponseInterface[] */ public function concurrentRawRequests( $operations, $selected_options = [], $stagger = 800 ) { $endpoint = $this->config['endpoint']; @@ -324,30 +333,66 @@ public function concurrentRawRequests( $operations, $selected_options = [], $sta $this->logger->logData( "With request options: \n" . json_encode( $request_options, JSON_PRETTY_PRINT ) ."\n" ); - $promises = []; - foreach ( $operations as $index => $operation ) { - $body = json_encode( $operation ); - $delay = $stagger * ($index + 1); - $connected = false; - $progress = function ( $downloadTotal, $downloadedBytes, $uploadTotal, $uploadedBytes ) use ( $index, &$connected ) { - if ( $uploadTotal === $uploadedBytes && 0 === $downloadTotal && ! $connected ) { - $this->logger->logData( - "Session mutation request $index connected @ " - . date( 'Y-m-d H:i:s', time() ) + $client = $this->client; + $logger = $this->logger; + + $iterator = static function ( $ops, $st, $ro ) use ( $client, $logger ) { + $ops_started = []; + foreach ( $ops as $index => $op ) { + yield static function () use ( $st, $index, $ro, $op, $client, $logger, $ops_started ) { + $body = json_encode( $op ); + $delay = $st * $index; + return $client->postAsync( + '', + array_merge( + $ro, + [ + 'body' => $body, + 'delay' => $delay, + 'progress' => static function ( $downloadTotal, $downloadedBytes, $uploadTotal, $uploadedBytes ) use ( $index, &$ops_started, $logger ) { + // Log the connection time + if ( + $uploadTotal === $uploadedBytes + && 0 === $downloadTotal + && ! isset( $ops_started[ $index ] ) ) { + $logger->logData( + "Session mutation request $index connected @ " + . date_format( date_create('now'), 'Y-m-d H:i:s:v' ) + ); + $ops_started[ $index ] = true; + } + } + ] + ) ); - $connected = true; - } - }; - $promises[] = $this->client->postAsync( - '', - array_merge( $request_options, compact( 'body', 'progress', 'delay' ) ), - ); - } + }; + } + }; + + $ops_completed = []; + $pool = new \GuzzleHttp\Pool( + $client, + $iterator( $operations, $stagger, $request_options ), + [ + 'concurrency' => count( $operations ), + 'fulfilled' => static function ( $response, $index ) use ( $logger, &$ops_completed ) { + $logger->logData( + "Finished session mutation request $index @ " + . date_format( date_create('now'), 'Y-m-d H:i:s:v' ) . "\n" + ); + $logger->logData( Psr7\Message::toString( $response ) ); + $ops_completed[ $index ] = $response; + }, + ] + ); + + $promise = $pool->promise(); - $responses = \GuzzleHttp\Promise\Utils::unwrap( $promises ); - \GuzzleHttp\Promise\Utils::settle( $promises )->wait(); + $promise->wait(); - return $responses; + ksort( $ops_completed ); + + return $ops_completed; } /** @@ -377,9 +422,6 @@ public function concurrentRequests( $operations, $selected_options = [], $stagge throw new ModuleException( $this, 'Empty response.' ); } - $this->logger->logData( $response->getHeaders() ); - $this->logger->logData( (string) $response->getBody() ); - $queryResults[] = json_decode( $response->getBody(), true ); } diff --git a/tests/codeception/functional/WPGraphQLModuleCest.php b/tests/codeception/functional/WPGraphQLModuleCest.php index 2f2d183..931ad93 100644 --- a/tests/codeception/functional/WPGraphQLModuleCest.php +++ b/tests/codeception/functional/WPGraphQLModuleCest.php @@ -319,7 +319,7 @@ public function testRequestOptions( FunctionalTester $I ) { $I->assertQueryError( $response ); - $response = $I->postRequest( $query, $variables ); + $response = $I->postRequest( $query, $variables, [ 'use_cookies' => true ] ); $I->assertQuerySuccessful( $response, [ $I->expectField( 'createPost.post.id', Signal::NOT_NULL ) ] );