Skip to content

Commit

Permalink
Merge pull request #36 from wp-graphql/fix/concurrent-requests-fix
Browse files Browse the repository at this point in the history
fix: WPGraphQL E2E module concurrentRawRequests fix
  • Loading branch information
kidunot89 committed May 20, 2024
2 parents 796f2fa + a408980 commit 3dea505
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 52 deletions.
144 changes: 93 additions & 51 deletions src/Codeception/Module/WPGraphQL.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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'];
Expand All @@ -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() ];
Expand All @@ -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;
}

Expand All @@ -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'] );
Expand All @@ -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'];
Expand All @@ -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;
}
Expand Down Expand Up @@ -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'];
Expand Down Expand Up @@ -198,18 +209,17 @@ 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;
}

/**
* 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.
*
Expand All @@ -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'];
Expand Down Expand Up @@ -266,17 +276,16 @@ 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;
}

/**
* 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.
*
Expand All @@ -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'];
Expand All @@ -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;
}

/**
Expand Down Expand Up @@ -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 );
}

Expand Down
2 changes: 1 addition & 1 deletion tests/codeception/functional/WPGraphQLModuleCest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) ] );

Expand Down

0 comments on commit 3dea505

Please sign in to comment.