diff --git a/.gitattributes b/.gitattributes index 0acb7092c..f37a3882a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,8 +1,5 @@ .gitattributes export-ignore .gitignore export-ignore .github export-ignore -dev export-ignore tests export-ignore -codecov.yml export-ignore -ruleset.xml export-ignore -.travis.yml export-ignore +/*.dist export-ignore diff --git a/README.md b/README.md index 6a6c01760..19f92ec0d 100644 --- a/README.md +++ b/README.md @@ -73,21 +73,13 @@ be found for Mac or Windows. 3. Run tests. ```sh - > composer test + > vendor/bin/phpunit ``` -4. Updating dependencies after changing `composer.json`: +4. Run static analysis tools ```sh - > composer update - ` - ``` - -5. Formatting source: - - ```sh - > composer cs-lint - > composer cs-fix + > phpstan -c phpstan.neon.dist ``` ## License diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index 972f1e6bd..000000000 --- a/codecov.yml +++ /dev/null @@ -1,3 +0,0 @@ -ignore: - - "metadata" - - "src/Testing" diff --git a/composer.json b/composer.json index 9d46994c8..48cdbfc22 100644 --- a/composer.json +++ b/composer.json @@ -34,17 +34,9 @@ }, "autoload-dev": { "psr-4": { - "Google\\ApiCore\\Dev\\": "dev/src", "Google\\ApiCore\\Tests\\": "tests", - "GPBMetadata\\Google\\": "metadata/Google", "Google\\Showcase\\": "tests/Conformance/src", "GPBMetadata\\Google\\Showcase\\": "tests/Conformance/metadata" } - }, - "scripts": { - "regenerate-test-protos": "dev/sh/regenerate-test-protos.sh", - "test": "./vendor/bin/phpunit", - "cs-lint": "vendor/bin/php-tools cs-fixer googleapis/gax-php --ref $(git rev-parse --abbrev-ref HEAD) --flags \"\\-n --dry-run\"", - "cs-fix": "vendor/bin/php-tools cs-fixer googleapis/gax-php --ref $(git rev-parse --abbrev-ref HEAD) --flags \"\\-n\"" } } diff --git a/dev/sh/build-protobuf-docs.sh b/dev/sh/build-protobuf-docs.sh deleted file mode 100644 index 1e0213024..000000000 --- a/dev/sh/build-protobuf-docs.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/bash - -# Script to build docs for PHP Protobuf. To be run locally and submitted -# manually when an update is desired. -# The Protobuf team does not maintain the reference documentation for PHP at -# this time and rely on the Core Client Libraries team to do so. -# Docs are deployed to developers.google.com, but soon will be deployed to -# protobuf.dev. -# @see https://developers.google.com/protocol-buffers/docs/reference/overview - -# This script expects to be invoked from the gax-php root. It requires a git tag -# of a valid protobuf-php releas eas the first argument. -# @see https://github.com/protocolbuffers/protobuf-php/tags - -# Once ran, copy the "api-docs" directory to the protocolbuffers.github.io repo: -# https://github.com/protocolbuffers/protocolbuffers.github.io/tree/main/content/reference/php/api-docs - -set -ev - -if [ "$#" -ne 1 ]; then - echo "Usage: $0 TAG" - exit 1 -fi -GIT_TAG_NAME=$1 -ROOT_DIR=$(pwd) -DOC_OUTPUT_DIR=${ROOT_DIR}/api-docs -DOCTUM_EXECUTABLE=${ROOT_DIR}/doctum.phar - -function downloadDoctum() { - # Download the latest (5.1.x) release - # You can fetch the latest (5.1.x) version code here: - # https://doctum.long-term.support/releases/5.1/VERSION - rm -f "${DOCTUM_EXECUTABLE}" - rm -f "${DOCTUM_EXECUTABLE}.sha256" - curl -# https://doctum.long-term.support/releases/5.5/doctum.phar -o "${DOCTUM_EXECUTABLE}" - curl -# https://doctum.long-term.support/releases/5.5/doctum.phar.sha256 -o "${DOCTUM_EXECUTABLE}.sha256" - sha256sum --strict --check "${DOCTUM_EXECUTABLE}.sha256" - rm -f "${DOCTUM_EXECUTABLE}.sha256" -} - -function buildDocs() { - DOCTUM_CONFIG=${ROOT_DIR}/dev/src/Docs/doctum-protobuf-config.php - PROTOBUF_DOCS_VERSION=${GIT_TAG_NAME} php ${DOCTUM_EXECUTABLE} update ${DOCTUM_CONFIG} -v -} -# ensure we have the correct version of protobuf -composer update google/protobuf:$GIT_TAG_NAME -# download doctum.phar -downloadDoctum -# build the docs -buildDocs ${GIT_TAG_NAME} - -# Construct the base index file to redirect to the Protobuf namespace -UPDATED_INDEX_FILE=$(cat << EndOfMessage - -EndOfMessage -) -echo ${UPDATED_INDEX_FILE} > ${DOC_OUTPUT_DIR}/index.html diff --git a/dev/sh/regenerate-test-protos.sh b/dev/sh/regenerate-test-protos.sh deleted file mode 100755 index b22338f88..000000000 --- a/dev/sh/regenerate-test-protos.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -# Run this script whenever changes are made to mocks.proto to regenerate the -# PHP protobuf message classes. -# -# This script expected to be invoked from the gax-php root using: -# $ composer regenerate-test-protos - -echo ${pwd} -cd src -protoc --php_out . ./Testing/mocks.proto -cp -r ./GPBMetadata/* ../metadata/ -cp -r ./Google/ApiCore/* ./ -rm -r ./GPBMetadata -rm -r ./Google diff --git a/dev/sh/test-composer-conflict.sh b/dev/sh/test-composer-conflict.sh deleted file mode 100755 index b4f5434f4..000000000 --- a/dev/sh/test-composer-conflict.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -# Try to run `composer install`, with the expectation that it will FAIL! -# This is to test that the 'conflict' clause of the composer.json file -# is correctly blocking installation when an incompatible protobuf -# extension is present. - -if composer install ; then - echo "Expected 'composer install' to fail, but it succeeded!" - exit 1 -else - echo "'composer install' failed, as expected" -fi - diff --git a/dev/src/Docs/doctum-protobuf-config.php b/dev/src/Docs/doctum-protobuf-config.php deleted file mode 100644 index e0efa81bd..000000000 --- a/dev/src/Docs/doctum-protobuf-config.php +++ /dev/null @@ -1,59 +0,0 @@ -= 7.1 to build docs, found version ' . phpversion()); -} - -$version = getenv('PROTOBUF_DOCS_VERSION'); - -$gaxRootDir = realpath(__DIR__ . '/../../../'); -$protobufRootDir = realpath($gaxRootDir . '/vendor/google/protobuf'); -$iterator = Finder::create() - ->files() - ->name('*.php') - ->exclude('GPBMetadata') - ->in("$protobufRootDir/src") -; - -return new Doctum($iterator, [ - 'title' => "Google Protobuf - $version", - 'version' => $version, - 'build_dir' => "$gaxRootDir/api-docs", - 'cache_dir' => "$gaxRootDir/cache/%version%", - 'remote_repository' => new GitHubRemoteRepository('protocolbuffers/protobuf-php', $protobufRootDir), - 'default_opened_level' => 1, -]); diff --git a/metadata/ApiCore/Testing/Mocks.php b/metadata/ApiCore/Testing/Mocks.php index 5dcb11f0c..924dae47f 100644 Binary files a/metadata/ApiCore/Testing/Mocks.php and b/metadata/ApiCore/Testing/Mocks.php differ diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 825f7f706..4a40d8366 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,8 +1,5 @@ - - - src/ApiCore diff --git a/src/ClientOptionsTrait.php b/src/ClientOptionsTrait.php index 5def36e9e..f5fbafdd4 100644 --- a/src/ClientOptionsTrait.php +++ b/src/ClientOptionsTrait.php @@ -152,8 +152,7 @@ private function buildClientOptions(array|ClientOptions $options) $options['logger'] = ApplicationDefaultCredentials::getDefaultLogger(); } - if ( - $options['logger'] !== null + if ($options['logger'] !== null && $options['logger'] !== false && !$options['logger'] instanceof LoggerInterface ) { diff --git a/src/OperationResponse.php b/src/OperationResponse.php index 8e58c810e..9dfeb0a73 100644 --- a/src/OperationResponse.php +++ b/src/OperationResponse.php @@ -101,7 +101,7 @@ class OperationResponse * OperationResponse constructor. * * @param string $operationName - * @param object $operationsClient + * @param object|null $operationsClient * @param array $options { * Optional. Options for configuring the operation response object. * diff --git a/src/ResourceTemplate/AbsoluteResourceTemplate.php b/src/ResourceTemplate/AbsoluteResourceTemplate.php index e6cb49231..7f2425f2e 100644 --- a/src/ResourceTemplate/AbsoluteResourceTemplate.php +++ b/src/ResourceTemplate/AbsoluteResourceTemplate.php @@ -54,7 +54,7 @@ class AbsoluteResourceTemplate implements ResourceTemplateInterface { private RelativeResourceTemplate $resourceTemplate; - /** @var string|bool */ + /** @var string */ private $verb; /** diff --git a/src/ResourceTemplate/Segment.php b/src/ResourceTemplate/Segment.php index fb9dc6d43..cee08f015 100644 --- a/src/ResourceTemplate/Segment.php +++ b/src/ResourceTemplate/Segment.php @@ -51,8 +51,8 @@ class Segment private ?string $value; private ?string $key; private ?RelativeResourceTemplate $template; - private ?string $stringRepr; - private ?string $separator; + private string $stringRepr; + private string $separator; /** * Segment constructor. diff --git a/src/Serializer.php b/src/Serializer.php index 1af6764dd..b9e1aa1de 100644 --- a/src/Serializer.php +++ b/src/Serializer.php @@ -37,6 +37,17 @@ use Google\Protobuf\DescriptorPool; use Google\Protobuf\FieldDescriptor; use Google\Protobuf\Internal\Message; +use Google\Rpc\BadRequest; +use Google\Rpc\DebugInfo; +use Google\Rpc\ErrorInfo; +use Google\Rpc\Help; +use Google\Rpc\LocalizedMessage; +use Google\Rpc\PreconditionFailure; +use Google\Rpc\QuotaFailure; +use Google\Rpc\RequestInfo; +use Google\Rpc\ResourceInfo; +use Google\Rpc\RetryInfo; +use Google\Rpc\Status; use RuntimeException; /** @@ -180,7 +191,7 @@ public static function decodeMetadata(array $metadata, ?array &$errors = null) $result = []; // If metadata contains a "status" bin, use that instead if (isset($metadata['grpc-status-details-bin'])) { - $status = new \Google\Rpc\Status(); + $status = new Status(); $status->mergeFromString($metadata['grpc-status-details-bin'][0]); foreach ($status->getDetails() as $any) { if (isset(KnownTypes::TYPE_URLS[$any->getTypeUrl()])) { @@ -212,7 +223,18 @@ public static function decodeMetadata(array $metadata, ?array &$errors = null) if (self::hasBinaryHeaderSuffix($key)) { if (isset(KnownTypes::BIN_TYPES[$key])) { $class = KnownTypes::BIN_TYPES[$key]; - /** @var Message $message */ + /** + * @var BadRequest + * | DebugInfo + * | ErrorInfo + * | Help + * | LocalizedMessage + * | PreconditionFailure + * | QuotaFailure + * | RequestInfo + * | ResourceInfo + * | RetryInfo $message + */ $message = new $class(); try { $message->mergeFromString($value); @@ -470,12 +492,10 @@ private function checkFieldRepeated(FieldDescriptor $field): bool if (method_exists($field, 'isRepeated')) { return $field->isRepeated(); } - if (method_exists($field, 'getLabel')) { return $field->getLabel() === GPBLabel::REPEATED; } - - throw new \Exception('No field repeated method avaialble'); + throw new \LogicException('unable to check field repeated'); } /** diff --git a/src/Testing/MockRequest.php b/src/Testing/MockRequest.php index f7a23a30e..be0099103 100644 --- a/src/Testing/MockRequest.php +++ b/src/Testing/MockRequest.php @@ -82,5 +82,4 @@ public function setPageSize($var) return $this; } - } diff --git a/src/Testing/MockRequestBody.php b/src/Testing/MockRequestBody.php index 7d877d809..6039f8569 100644 --- a/src/Testing/MockRequestBody.php +++ b/src/Testing/MockRequestBody.php @@ -643,5 +643,4 @@ public function getOneofField() { return $this->whichOneof('oneof_field'); } - } diff --git a/src/Testing/MockResponse.php b/src/Testing/MockResponse.php index 6d93ad96c..c9135ce4e 100644 --- a/src/Testing/MockResponse.php +++ b/src/Testing/MockResponse.php @@ -162,5 +162,4 @@ public function setResourcesMap($var) return $this; } - } diff --git a/src/Transport/Rest/JsonStreamDecoder.php b/src/Transport/Rest/JsonStreamDecoder.php index c01f21135..90aba0680 100644 --- a/src/Transport/Rest/JsonStreamDecoder.php +++ b/src/Transport/Rest/JsonStreamDecoder.php @@ -92,7 +92,7 @@ public function __construct(StreamInterface $stream, string $decodeType, array $ public function decode() { try { - foreach ($this->_decode() as $response) { + foreach ($this->doDecode() as $response) { yield $response; } } catch (RuntimeException $re) { @@ -112,7 +112,7 @@ public function decode() /** * @return \Generator */ - private function _decode() + private function doDecode() { $decodeType = $this->decodeType; $str = false; diff --git a/src/Transport/Rest/RestServerStreamingCall.php b/src/Transport/Rest/RestServerStreamingCall.php index 35d796199..80d5e3e3c 100644 --- a/src/Transport/Rest/RestServerStreamingCall.php +++ b/src/Transport/Rest/RestServerStreamingCall.php @@ -55,7 +55,7 @@ class RestServerStreamingCall implements ServerStreamingCallInterface private array $decoderOptions; private RequestInterface $originalRequest; - private ?JsonStreamDecoder $decoder; + private JsonStreamDecoder $decoder; private string $decodeType; private ?ResponseInterface $response; private stdClass $status; @@ -181,7 +181,7 @@ public function getPeer() */ public function cancel() { - if (!is_null($this->decoder)) { + if (isset($this->decoder)) { $this->decoder->close(); } } diff --git a/src/Transport/RestTransport.php b/src/Transport/RestTransport.php index 296937fa4..aab687182 100644 --- a/src/Transport/RestTransport.php +++ b/src/Transport/RestTransport.php @@ -200,7 +200,7 @@ public function startServerStreamingCall(Call $call, array $options) $request = $this->requestBuilder->build( $call->getMethod(), $call->getMessage() - // Exclude headers here because they will be added in _serverStreamRequest(). + // Exclude headers here because they will be added in doServerStreamRequest(). ); $decoderOptions = []; @@ -209,7 +209,7 @@ public function startServerStreamingCall(Call $call, array $options) } return new ServerStream( - $this->_serverStreamRequest( + $this->doServerStreamRequest( $this->httpHandler, $request, $headers, @@ -233,7 +233,7 @@ public function startServerStreamingCall(Call $call, array $options) * * @return RestServerStreamingCall */ - private function _serverStreamRequest( + private function doServerStreamRequest( $httpHandler, $request, $headers, diff --git a/tests/Unit/AgentHeaderTest.php b/tests/Unit/AgentHeaderTest.php index 27e7399ef..69dc6c74b 100644 --- a/tests/Unit/AgentHeaderTest.php +++ b/tests/Unit/AgentHeaderTest.php @@ -113,9 +113,9 @@ public function testWithNullVersionInput() public function testGetGapicVersionWithVersionFile() { - require_once __DIR__ . '/testdata/src/GapicClientStub.php'; + require_once __DIR__ . '/testdata/mocks/src/GapicClientStub.php'; $expectedVersion = '1.2.3-dev'; - $actualVersion = AgentHeader::readGapicVersionFromFile(\GapicClientStub::class); + $actualVersion = AgentHeader::readGapicVersionFromFile(GapicClientStub::class); $this->assertEquals($expectedVersion, $actualVersion); } diff --git a/tests/Unit/ApiExceptionTest.php b/tests/Unit/ApiExceptionTest.php index 645023769..b4ce68be5 100644 --- a/tests/Unit/ApiExceptionTest.php +++ b/tests/Unit/ApiExceptionTest.php @@ -98,7 +98,7 @@ public function testWithMetadataWithoutErrorInfo($metadata, $metadataArray, $uns $this->assertSame(Code::OK, $apiException->getCode()); $this->assertSame($expectedMessageWithoutErrorDetails, $apiException->getMessage()); $this->assertSame($metadata, $apiException->getMetadata()); - $this->assertCount($unserializedErrorsCount ,$apiException->getErrorDetails()); + $this->assertCount($unserializedErrorsCount, $apiException->getErrorDetails()); } /** @@ -123,7 +123,7 @@ public function testCreateFromApiResponse($metadata, $metadataArray, $unserializ $this->assertSame(Code::OK, $apiException->getCode()); $this->assertSame($expectedMessage, $apiException->getMessage()); $this->assertSame($metadata, $apiException->getMetadata()); - $this->assertCount($unserializedErrorsCount ,$apiException->getErrorDetails()); + $this->assertCount($unserializedErrorsCount, $apiException->getErrorDetails()); } public function getMetadata() @@ -242,7 +242,7 @@ public function testWithMetadataWithErrorInfo($metadata, $metadataArray) 'code' => Code::OK, 'status' => 'OK', 'details' => $metadataArray, - ], + ], JSON_PRETTY_PRINT ); diff --git a/tests/Unit/ArrayTraitTest.php b/tests/Unit/ArrayTraitTest.php index fe31e8fca..c11da2613 100644 --- a/tests/Unit/ArrayTraitTest.php +++ b/tests/Unit/ArrayTraitTest.php @@ -42,7 +42,16 @@ class ArrayTraitTest extends TestCase public function setUp(): void { - $this->implementation = new ArrayTraitStub(); + $this->implementation = new class() { + use ArrayTrait { + arrayFilterRemoveNull as public; + isAssoc as public; + pluck as public; + pluckArray as public; + subsetArray as public; + arrayMergeRecursive as public; + } + }; } public function testPluck() @@ -182,15 +191,3 @@ public function testArrayMergeRecursive() $this->assertEquals($expected, $res); } } - -class ArrayTraitStub -{ - use ArrayTrait { - arrayFilterRemoveNull as public; - isAssoc as public; - pluck as public; - pluckArray as public; - subsetArray as public; - arrayMergeRecursive as public; - } -} diff --git a/tests/Unit/BidiStreamTest.php b/tests/Unit/BidiStreamTest.php index 58089b05b..d9453e578 100644 --- a/tests/Unit/BidiStreamTest.php +++ b/tests/Unit/BidiStreamTest.php @@ -373,6 +373,7 @@ public function testReadCallsLogger() $stream->writeAll($requests); - foreach ($stream->closeWriteAndReadAll() as $_) {} + foreach ($stream->closeWriteAndReadAll() as $_) { + } } } diff --git a/tests/Unit/ClientOptionsTraitTest.php b/tests/Unit/ClientOptionsTraitTest.php index 5dbea3a87..a5c9d964a 100644 --- a/tests/Unit/ClientOptionsTraitTest.php +++ b/tests/Unit/ClientOptionsTraitTest.php @@ -52,34 +52,84 @@ class ClientOptionsTraitTest extends TestCase use ProphecyTrait; use TestTrait; + private $clientStub; + private $universeDomainClientStub; + + public function setUp(): void + { + $this->clientStub = new class() { + use ClientOptionsTrait { + buildClientOptions as public; + createCredentialsWrapper as public; + determineMtlsEndpoint as public; + getGapicVersion as public; + shouldUseMtlsEndpoint as public; + } + + private const SERVICE_NAME = 'TEST_SERVICE_NAME'; + + public function set($name, $val, $static = false) + { + if (!property_exists($this, $name)) { + throw new \InvalidArgumentException("Property not found: $name"); + } + if ($static) { + $this::$$name = $val; + } else { + $this->$name = $val; + } + } + + public static function getClientDefaults() + { + return [ + 'apiEndpoint' => 'test.address.com:443', + 'gcpApiConfigPath' => __DIR__ . '/testdata/resources/test_service_grpc_config.json', + ]; + } + }; + + $this->universeDomainClientStub = new class() { + use ClientOptionsTrait { + buildClientOptions as public; + } + + private const SERVICE_ADDRESS_TEMPLATE = 'stub.UNIVERSE_DOMAIN'; + + public static function getClientDefaults() + { + return [ + 'apiEndpoint' => 'test.address.com:443', + ]; + } + }; + } + public function tearDown(): void { // Reset the static gapicVersion field between tests - $client = new StubClientOptionsClient(); - $client->set('gapicVersionFromFile', null, true); + $this->clientStub->set('gapicVersionFromFile', null, true); } public function testGetGapicVersionWithVersionFile() { - require_once __DIR__ . '/testdata/src/GapicClientStub.php'; + require_once __DIR__ . '/testdata/mocks/src/GapicClientStub.php'; $version = '1.2.3-dev'; - $client = new \GapicClientStub(); + $client = new GapicClientStub(); $this->assertEquals($version, $client::getGapicVersion([])); } public function testGetGapicVersionWithNoAvailableVersion() { - $client = new StubClientOptionsClient(); - $this->assertSame('', $client::getGapicVersion([])); + $this->assertSame('', $this->clientStub::getGapicVersion([])); } public function testGetGapicVersionWithLibVersion() { $version = '1.2.3-dev'; - $client = new StubClientOptionsClient(); - $client->set('gapicVersionFromFile', $version, true); + $this->clientStub->set('gapicVersionFromFile', $version, true); $options = ['libVersion' => $version]; - $this->assertEquals($version, $client::getGapicVersion( + $this->assertEquals($version, $this->clientStub::getGapicVersion( $options )); } @@ -89,8 +139,7 @@ public function testGetGapicVersionWithLibVersion() */ public function testCreateCredentialsWrapper($auth, $authConfig, $expectedCredentialsWrapper) { - $client = new StubClientOptionsClient(); - $actualCredentialsWrapper = $client->createCredentialsWrapper( + $actualCredentialsWrapper = $this->clientStub->createCredentialsWrapper( $auth, $authConfig, GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN @@ -101,12 +150,13 @@ public function testCreateCredentialsWrapper($auth, $authConfig, $expectedCreden public function createCredentialsWrapperData() { - $keyFilePath = __DIR__ . '/testdata/json-key-file.json'; + $keyFilePath = __DIR__ . '/testdata/creds/json-key-file.json'; $keyFile = json_decode(file_get_contents($keyFilePath), true); + $fetcher = $this->prophesize(FetchAuthTokenInterface::class)->reveal(); $credentialsWrapper = new CredentialsWrapper($fetcher); + return [ - [null, [], CredentialsWrapper::build()], [$keyFilePath, [], CredentialsWrapper::build(['keyFile' => $keyFile])], [$keyFile, [], CredentialsWrapper::build(['keyFile' => $keyFile])], [$fetcher, [], new CredentialsWrapper($fetcher)], @@ -114,16 +164,34 @@ public function createCredentialsWrapperData() ]; } + /** + * @runInSeparateProcess + */ + public function testCreateCredentialsWrapperFromEnv() + { + $keyFilePath = __DIR__ . '/testdata/creds/json-key-file.json'; + putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $keyFilePath); + + $expectedCredentialsWrapper = CredentialsWrapper::build(); + $actualCredentialsWrapper = $this->clientStub->createCredentialsWrapper( + null, + [], + GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN + ); + + $this->assertEquals($expectedCredentialsWrapper, $actualCredentialsWrapper); + } + + /** * @dataProvider createCredentialsWrapperValidationExceptionData */ public function testCreateCredentialsWrapperValidationException($auth, $authConfig) { - $client = new StubClientOptionsClient(); $this->expectException(ValidationException::class); - $client->createCredentialsWrapper( + $this->clientStub->createCredentialsWrapper( $auth, $authConfig, '' @@ -143,11 +211,10 @@ public function createCredentialsWrapperValidationExceptionData() */ public function testCreateCredentialsWrapperInvalidArgumentException($auth, $authConfig) { - $client = new StubClientOptionsClient(); $this->expectException(InvalidArgumentException::class); - $client->createCredentialsWrapper( + $this->clientStub->createCredentialsWrapper( $auth, $authConfig, '' @@ -169,8 +236,7 @@ public function testBuildClientOptions($options, $expectedUpdatedOptions) if (!extension_loaded('sysvshm')) { $this->markTestSkipped('The sysvshm extension must be installed to execute this test.'); } - $client = new StubClientOptionsClient(); - $updatedOptions = $client->buildClientOptions($options); + $updatedOptions = $this->clientStub->buildClientOptions($options); $this->assertEquals($expectedUpdatedOptions, $updatedOptions); } @@ -178,13 +244,13 @@ public function buildClientOptionsProvider() { $apiConfig = new ApiConfig(); $apiConfig->mergeFromJsonString( - file_get_contents(__DIR__ . '/testdata/test_service_grpc_config.json') + file_get_contents(__DIR__ . '/testdata/resources/test_service_grpc_config.json') ); $grpcGcpConfig = new Config('test.address.com:443', $apiConfig); $defaultOptions = [ 'apiEndpoint' => 'test.address.com:443', - 'gcpApiConfigPath' => __DIR__ . '/testdata/test_service_grpc_config.json', + 'gcpApiConfigPath' => __DIR__ . '/testdata/resources/test_service_grpc_config.json', 'disableRetries' => false, 'transport' => null, 'transportConfig' => [ @@ -253,8 +319,22 @@ public function testBuildClientOptionsRestOnly($options, $expectedUpdatedOptions if (!extension_loaded('sysvshm')) { $this->markTestSkipped('The sysvshm extension must be installed to execute this test.'); } - $client = new RestOnlyClient(); - $updatedOptions = $client->buildClientOptions($options); + $restOnlyClient = new class() { + use ClientOptionsTrait { + buildClientOptions as public; + } + + private static function supportedTransports() + { + return ['rest', 'fake-transport']; + } + + private static function defaultTransport() + { + return 'rest'; + } + }; + $updatedOptions = $restOnlyClient->buildClientOptions($options); $this->assertEquals($expectedUpdatedOptions, $updatedOptions); } @@ -314,24 +394,44 @@ public function buildClientOptionsProviderRestOnly() public function testDefaultScopes() { - $client = new DefaultScopeAndAudienceClientOptionsClient(); + $defaultScopeClient = new class() { + use ClientOptionsTrait { + buildClientOptions as public; + } + + const SERVICE_ADDRESS = 'service-address'; + + public static $serviceScopes = [ + 'default-scope-1', + 'default-scope-2', + ]; + + public static function getClientDefaults() + { + return [ + 'credentialsConfig' => [ + 'defaultScopes' => self::$serviceScopes, + ], + ]; + } + }; // verify scopes are not set by default - $defaultOptions = $client->buildClientOptions([]); + $defaultOptions = $defaultScopeClient->buildClientOptions([]); $this->assertArrayNotHasKey('scopes', $defaultOptions['credentialsConfig']); // verify scopes are set when a custom api endpoint is used - $defaultOptions = $client->buildClientOptions([ + $defaultOptions = $defaultScopeClient->buildClientOptions([ 'apiEndpoint' => 'www.someotherendpoint.com', ]); $this->assertArrayHasKey('scopes', $defaultOptions['credentialsConfig']); $this->assertEquals( - $client::$serviceScopes, + $defaultScopeClient::$serviceScopes, $defaultOptions['credentialsConfig']['scopes'] ); // verify user-defined scopes override default scopes - $defaultOptions = $client->buildClientOptions([ + $defaultOptions = $defaultScopeClient->buildClientOptions([ 'credentialsConfig' => ['scopes' => ['user-scope-1']], 'apiEndpoint' => 'www.someotherendpoint.com', ]); @@ -342,8 +442,8 @@ public function testDefaultScopes() ); // verify empty default scopes has no effect - $client::$serviceScopes = null; - $defaultOptions = $client->buildClientOptions([ + $defaultScopeClient::$serviceScopes = null; + $defaultOptions = $defaultScopeClient->buildClientOptions([ 'apiEndpoint' => 'www.someotherendpoint.com', ]); $this->assertArrayNotHasKey('scopes', $defaultOptions['credentialsConfig']); @@ -352,11 +452,9 @@ public function testDefaultScopes() /** @dataProvider provideDetermineMtlsEndpoint */ public function testDetermineMtlsEndpoint($apiEndpoint, $expected) { - $client = new StubClientOptionsClient(); - $this->assertEquals( $expected, - $client::determineMtlsEndpoint($apiEndpoint) + $this->clientStub::determineMtlsEndpoint($apiEndpoint) ); } @@ -382,12 +480,11 @@ public function provideDetermineMtlsEndpoint() */ public function testShouldUseMtlsEndpoint($envVarValue, $options, $expected) { - $client = new StubClientOptionsClient(); putenv('GOOGLE_API_USE_MTLS_ENDPOINT=' . $envVarValue); $this->assertEquals( $expected, - $client->shouldUseMtlsEndpoint($options) + $this->clientStub->shouldUseMtlsEndpoint($options) ); } @@ -414,8 +511,7 @@ public function testMtlsClientOptions($envVars, $options, $expected) putenv($envVar); } - $client = new StubClientOptionsClient(); - $options = $client->buildClientOptions($options); + $options = $this->clientStub->buildClientOptions($options); // Only check the keys we care about $options = array_intersect_key( @@ -430,6 +526,8 @@ public function provideMtlsClientOptions() { $defaultEndpoint = 'test.address.com:443'; $mtlsEndpoint = 'test.mtls.address.com:443'; + $homeDir = PHP_OS_FAMILY === 'Windows' ? 'APPDATA' : 'HOME'; + return [ [ [], @@ -460,7 +558,7 @@ public function provideMtlsClientOptions() ], [ [ - 'HOME=' . __DIR__ . '/testdata/nonexistant', + $homeDir . '=' . __DIR__ . '/testdata/nonexistant', 'GOOGLE_API_USE_MTLS_ENDPOINT', // no env var CredentialsLoader::MTLS_CERT_ENV_VAR . '=true', ], @@ -475,12 +573,12 @@ public function provideMtlsClientOptions() */ public function testMtlsClientOptionWithDefaultClientCertSource() { - putenv('HOME=' . __DIR__ . '/testdata/mtls'); + $homeDir = PHP_OS_FAMILY === 'Windows' ? 'APPDATA' : 'HOME'; + putenv($homeDir . '=' . __DIR__ . '/testdata/creds/mtls'); putenv('GOOGLE_API_USE_MTLS_ENDPOINT=auto'); putenv(CredentialsLoader::MTLS_CERT_ENV_VAR . '=true'); - $client = new StubClientOptionsClient(); - $options = $client->buildClientOptions([]); + $options = $this->clientStub->buildClientOptions([]); $this->assertSame('test.mtls.address.com:443', $options['apiEndpoint']); $this->assertTrue(is_callable($options['clientCertSource'])); @@ -496,8 +594,7 @@ public function testServiceAddressTemplate(array $options, string $expectedEndpo if ($envVar) { putenv($envVar); } - $client = new UniverseDomainStubClientOptionsClient(); - $updatedOptions = $client->buildClientOptions($options); + $updatedOptions = $this->universeDomainClientStub->buildClientOptions($options); $this->assertEquals($expectedEndpoint, $updatedOptions['apiEndpoint']); } @@ -544,27 +641,26 @@ public function testMtlsWithUniverseDomainThrowsException() $this->expectException(ValidationException::class); $this->expectExceptionMessage('mTLS is not supported outside the "googleapis.com" universe'); - $client = new UniverseDomainStubClientOptionsClient(); - $client->buildClientOptions([ + $this->universeDomainClientStub->buildClientOptions([ 'universeDomain' => 'foo.com', - 'clientCertSource' => function () { $this->fail('this should not be called');}, + 'clientCertSource' => function () { + $this->fail('this should not be called'); + }, ]); } public function testBuildClientOptionsTwice() { - $client = new StubClientOptionsClient(); - $options = $client->buildClientOptions([]); - $options2 = $client->buildClientOptions($options); + $options = $this->clientStub->buildClientOptions([]); + $options2 = $this->clientStub->buildClientOptions($options); $this->assertEquals($options, $options2); } public function testBuildClientOptionsWithClientOptions() { - $client = new StubClientOptionsClient(); $clientOptions = new ClientOptions([]); $clientOptions->setApiEndpoint('TestEndpoint.com'); - $builtOptions = $client->buildClientOptions($clientOptions); + $builtOptions = $this->clientStub->buildClientOptions($clientOptions); $this->assertEquals($clientOptions['apiEndpoint'], $builtOptions['apiEndpoint']); } @@ -576,11 +672,10 @@ public function testLoggerIsNullWhenFalseIsPassed() { putenv('GOOGLE_SDK_PHP_LOGGING=true'); - $client = new StubClientOptionsClient(); $optionsArray = [ 'logger' => false, ]; - $options = $client->buildClientOptions($optionsArray); + $options = $this->clientStub->buildClientOptions($optionsArray); $this->assertFalse($options['transportConfig']['rest']['logger']); $this->assertFalse($options['transportConfig']['grpc']['logger']); @@ -594,9 +689,8 @@ public function testLoggerIsNotNullIfFlagIsEmptyAndEnvVarSet() { putenv('GOOGLE_SDK_PHP_LOGGING=true'); - $client = new StubClientOptionsClient(); $optionsArray = []; - $options = $client->buildClientOptions($optionsArray); + $options = $this->clientStub->buildClientOptions($optionsArray); $this->assertInstanceOf(StdOutLogger::class, $options['transportConfig']['rest']['logger']); $this->assertInstanceOf(StdOutLogger::class, $options['transportConfig']['grpc']['logger']); @@ -609,8 +703,7 @@ public function testLogConfiguration() { putenv('GOOGLE_SDK_PHP_LOGGING=true'); - $client = new StubClientOptionsClient(); - $options = $client->buildClientOptions([ + $options = $this->clientStub->buildClientOptions([ 'apiEndpoint' => 'test' ]); $parsedOutput = json_decode($this->getActualOutputForAssertion(), true); @@ -623,9 +716,8 @@ public function testLogConfiguration() public function testLoggerIsNullIfFlagIsEmptyAndNoEnvVar() { - $client = new StubClientOptionsClient(); $optionsArray = []; - $options = $client->buildClientOptions($optionsArray); + $options = $this->clientStub->buildClientOptions($optionsArray); $this->assertNull($options['transportConfig']['rest']['logger']); $this->assertNull($options['transportConfig']['grpc']['logger']); @@ -633,12 +725,11 @@ public function testLoggerIsNullIfFlagIsEmptyAndNoEnvVar() public function testLoggerIsSetWhenALoggerIsPassed() { - $client = new StubClientOptionsClient(); $logger = new StdOutLogger(); $optionsArray = [ 'logger' => $logger ]; - $options = $client->buildClientOptions($optionsArray); + $options = $this->clientStub->buildClientOptions($optionsArray); $this->assertEquals($options['transportConfig']['rest']['logger'], $logger); $this->assertEquals($options['transportConfig']['grpc']['logger'], $logger); @@ -651,99 +742,9 @@ public function testExceptionIsRaisedIfOptionsIsInvalid() 'The "logger" option in the options array should be PSR-3 LoggerInterface compatible' ); - $client = new StubClientOptionsClient(); $optionsArray = [ 'logger' => 'nonValidOption' ]; - $client->buildClientOptions($optionsArray); - } -} - -class StubClientOptionsClient -{ - use ClientOptionsTrait { - buildClientOptions as public; - createCredentialsWrapper as public; - determineMtlsEndpoint as public; - getGapicVersion as public; - shouldUseMtlsEndpoint as public; - } - - private const SERVICE_NAME = 'TEST_SERVICE_NAME'; - - public function set($name, $val, $static = false) - { - if (!property_exists($this, $name)) { - throw new \InvalidArgumentException("Property not found: $name"); - } - if ($static) { - $this::$$name = $val; - } else { - $this->$name = $val; - } - } - - public static function getClientDefaults() - { - return [ - 'apiEndpoint' => 'test.address.com:443', - 'gcpApiConfigPath' => __DIR__ . '/testdata/test_service_grpc_config.json', - ]; - } -} - -class RestOnlyClient -{ - use ClientOptionsTrait { - buildClientOptions as public; - } - - private static function supportedTransports() - { - return ['rest', 'fake-transport']; - } - - private static function defaultTransport() - { - return 'rest'; - } -} - -class DefaultScopeAndAudienceClientOptionsClient -{ - use ClientOptionsTrait { - buildClientOptions as public; - } - - const SERVICE_ADDRESS = 'service-address'; - - public static $serviceScopes = [ - 'default-scope-1', - 'default-scope-2', - ]; - - public static function getClientDefaults() - { - return [ - 'credentialsConfig' => [ - 'defaultScopes' => self::$serviceScopes, - ], - ]; - } -} - -class UniverseDomainStubClientOptionsClient -{ - use ClientOptionsTrait { - buildClientOptions as public; - } - - private const SERVICE_ADDRESS_TEMPLATE = 'stub.UNIVERSE_DOMAIN'; - - public static function getClientDefaults() - { - return [ - 'apiEndpoint' => 'test.address.com:443', - ]; + $this->clientStub->buildClientOptions($optionsArray); } } diff --git a/tests/Unit/CredentialsWrapperTest.php b/tests/Unit/CredentialsWrapperTest.php index 9c4da6b1f..d7d5a1c09 100644 --- a/tests/Unit/CredentialsWrapperTest.php +++ b/tests/Unit/CredentialsWrapperTest.php @@ -61,7 +61,7 @@ class CredentialsWrapperTest extends TestCase public function testBuildWithoutExplicitKeyFile($args, $expectedCredentialsWrapper) { $appDefaultCreds = getenv('GOOGLE_APPLICATION_CREDENTIALS'); - $this->setEnv('GOOGLE_APPLICATION_CREDENTIALS', __DIR__ . '/testdata/json-key-file.json'); + $this->setEnv('GOOGLE_APPLICATION_CREDENTIALS', __DIR__ . '/testdata/creds/json-key-file.json'); $actualCredentialsWrapper = CredentialsWrapper::build($args); $this->assertEquals($expectedCredentialsWrapper, $actualCredentialsWrapper); @@ -81,7 +81,7 @@ public function testBuildWithKeyFile($args, $expectedCredentialsWrapper) public function buildDataWithoutExplicitKeyFile() { $appDefaultCreds = getenv('GOOGLE_APPLICATION_CREDENTIALS'); - $this->setEnv('GOOGLE_APPLICATION_CREDENTIALS', __DIR__ . '/testdata/json-key-file.json'); + $this->setEnv('GOOGLE_APPLICATION_CREDENTIALS', __DIR__ . '/testdata/creds/json-key-file.json'); $scopes = ['myscope']; $authHttpHandler = HttpHandlerFactory::build(); $asyncAuthHttpHandler = function ($request, $options) use ($authHttpHandler) { @@ -95,31 +95,55 @@ public function buildDataWithoutExplicitKeyFile() $testData = [ [ [], - new CredentialsWrapper(ApplicationDefaultCredentials::getCredentials(null, $authHttpHandler, null, $defaultAuthCache)), + new CredentialsWrapper(ApplicationDefaultCredentials::getCredentials( + httpHandler: $authHttpHandler, + cache: $defaultAuthCache + )), ], [ ['scopes' => $scopes], - new CredentialsWrapper(ApplicationDefaultCredentials::getCredentials($scopes, $authHttpHandler, null, $defaultAuthCache)), + new CredentialsWrapper(ApplicationDefaultCredentials::getCredentials( + scope: $scopes, + httpHandler: $authHttpHandler, + cache: $defaultAuthCache + )), ], [ ['scopes' => $scopes, 'authHttpHandler' => $asyncAuthHttpHandler], - new CredentialsWrapper(ApplicationDefaultCredentials::getCredentials($scopes, $asyncAuthHttpHandler, null, $defaultAuthCache), $asyncAuthHttpHandler), + new CredentialsWrapper(ApplicationDefaultCredentials::getCredentials( + $scopes, + $asyncAuthHttpHandler, + cache: $defaultAuthCache + ), $asyncAuthHttpHandler), ], [ ['enableCaching' => false], - new CredentialsWrapper(ApplicationDefaultCredentials::getCredentials(null, $authHttpHandler, null, null)), + new CredentialsWrapper(ApplicationDefaultCredentials::getCredentials( + httpHandler: $authHttpHandler + )), ], [ ['authCacheOptions' => $authCacheOptions], - new CredentialsWrapper(ApplicationDefaultCredentials::getCredentials(null, $authHttpHandler, $authCacheOptions, $defaultAuthCache)), + new CredentialsWrapper(ApplicationDefaultCredentials::getCredentials( + httpHandler: $authHttpHandler, + cacheConfig: $authCacheOptions, + cache: $defaultAuthCache + )), ], [ ['authCache' => $authCache], - new CredentialsWrapper(ApplicationDefaultCredentials::getCredentials(null, $authHttpHandler, null, $authCache)), + new CredentialsWrapper(ApplicationDefaultCredentials::getCredentials( + httpHandler: $authHttpHandler, + cache: $authCache + )), ], [ ['quotaProject' => $quotaProject], - new CredentialsWrapper(ApplicationDefaultCredentials::getCredentials(null, $authHttpHandler, null, $defaultAuthCache, $quotaProject)), + new CredentialsWrapper(ApplicationDefaultCredentials::getCredentials( + httpHandler: $authHttpHandler, + cache: $defaultAuthCache, + quotaProject: $quotaProject + )), ], ]; @@ -130,11 +154,12 @@ public function buildDataWithoutExplicitKeyFile() public function buildDataWithKeyFile() { - $keyFilePath = __DIR__ . '/testdata/json-key-file.json'; + $keyFilePath = __DIR__ . '/testdata/creds/json-key-file.json'; $keyFile = json_decode(file_get_contents($keyFilePath), true); $scopes = ['myscope']; - $authHttpHandler = function () {}; + $authHttpHandler = function () { + }; $defaultAuthCache = new MemoryCacheItemPool(); $authCache = new SysVCacheItemPool(); $authCacheOptions = ['lifetime' => 600]; @@ -193,8 +218,11 @@ private function makeExpectedKeyFileCreds($keyFile, $scopes, $cache, $cacheConfi /** * @dataProvider provideCheckUniverseDomainFails */ - public function testCheckUniverseDomainFails(?string $universeDomain, ?string $credentialsUniverse, ?string $message = null) - { + public function testCheckUniverseDomainFails( + ?string $universeDomain, + ?string $credentialsUniverse, + ?string $message = null + ) { $this->expectException(ValidationException::class); $this->expectExceptionMessage($message ?: sprintf( 'The configured universe domain (%s) does not match the credential universe domain (%s)', @@ -259,7 +287,7 @@ public function provideCheckUniverseDomainFails() ['googleapis.com', ''], ['', 'googleapis.com', 'The universe domain cannot be empty'], [null, 'foo.com'], // null in CredentialsWrapper will default to "googleapis.com" - ['foo.com', null], // Credentials not implementing GetUniverseDomainInterface will default to "googleapis.com" + ['foo.com', null], // Credentials not implementing GetUniverseDomainInterface will default to googleapis.com ]; } @@ -318,9 +346,11 @@ public function testCheckUniverseDomainOnGceCredentialsDoesNotCheck() /** * @dataProvider getBearerStringData + * @runInSeparateProcess */ - public function testGetBearerString($fetcher, $expectedBearerString) + public function testGetBearerString(string $fetcherFunc, $expectedBearerString) { + $fetcher = $this->$fetcherFunc(); $credentialsWrapper = new CredentialsWrapper($fetcher); $bearerString = $credentialsWrapper->getBearerString(); $this->assertSame($expectedBearerString, $bearerString); @@ -328,61 +358,24 @@ public function testGetBearerString($fetcher, $expectedBearerString) public function getBearerStringData() { - $expiredFetcher = $this->prophesize(FetchAuthTokenInterface::class); - $expiredFetcher->getLastReceivedToken() - ->willReturn([ - 'access_token' => 123, - 'expires_at' => time() - 1 - ]); - $expiredFetcher->fetchAuthToken(Argument::any()) - ->willReturn([ - 'access_token' => 456, - 'expires_at' => time() + 1000 - ]); - $eagerExpiredFetcher = $this->prophesize(FetchAuthTokenInterface::class); - $eagerExpiredFetcher->getLastReceivedToken() - ->willReturn([ - 'access_token' => 123, - 'expires_at' => time() + 1 - ]); - $eagerExpiredFetcher->fetchAuthToken(Argument::any()) - ->willReturn([ - 'access_token' => 456, - 'expires_at' => time() + 10 // within 10 second eager threshold - ]); - $unexpiredFetcher = $this->prophesize(FetchAuthTokenInterface::class); - $unexpiredFetcher->getLastReceivedToken() - ->willReturn([ - 'access_token' => 123, - 'expires_at' => time() + 100, - ]); - $insecureFetcher = $this->prophesize(FetchAuthTokenInterface::class); - $insecureFetcher->getLastReceivedToken()->willReturn(null); - $insecureFetcher->fetchAuthToken(Argument::any()) - ->willReturn([ - 'access_token' => '', - ]); - $nullFetcher = $this->prophesize(FetchAuthTokenInterface::class); - $nullFetcher->getLastReceivedToken()->willReturn(null); - $nullFetcher->fetchAuthToken(Argument::any()) - ->willReturn([ - 'access_token' => null, - ]); return [ - [$expiredFetcher->reveal(), 'Bearer 456'], - [$eagerExpiredFetcher->reveal(), 'Bearer 456'], - [$unexpiredFetcher->reveal(), 'Bearer 123'], - [$insecureFetcher->reveal(), ''], - [$nullFetcher->reveal(), ''] + ['getExpiredFetcher', 'Bearer 456'], + ['getEagerExpiredFetcher', 'Bearer 456'], + ['getUnexpiredFetcher', 'Bearer 123'], + ['getInsecureFetcher', ''], + ['getNullFetcher', ''], ]; } /** * @dataProvider getAuthorizationHeaderCallbackData + * @runInSeparateProcess */ - public function testGetAuthorizationHeaderCallback($fetcher, $expectedCallbackResponse) + public function testGetAuthorizationHeaderCallback(string $fetcherFunc, $expectedCallbackResponse) { - $httpHandler = function () {}; + $fetcher = $this->$fetcherFunc(); + $httpHandler = function () { + }; $credentialsWrapper = new CredentialsWrapper($fetcher, $httpHandler); $callback = $credentialsWrapper->getAuthorizationHeaderCallback('audience'); $actualResponse = $callback(); @@ -391,16 +384,34 @@ public function testGetAuthorizationHeaderCallback($fetcher, $expectedCallbackRe public function getAuthorizationHeaderCallbackData() { - $expiredFetcher = $this->prophesize(); - $expiredFetcher->willImplement(FetchAuthTokenInterface::class); - $expiredFetcher->willImplement(UpdateMetadataInterface::class); + return [ + ['getExpiredFetcher', ['authorization' => ['Bearer 456']]], + ['getExpiredInvalidFetcher', []], + ['getUnexpiredFetcher', ['authorization' => ['Bearer 123']]], + ['getInsecureFetcher', []], + ['getNullFetcher', []], + ['getCustomFetcher', ['authorization' => ['Bearer 123']]], + ]; + } + + private function getExpiredFetcher() + { + $expiredFetcher = $this->prophesize(FetchAuthTokenInterface::class); $expiredFetcher->getLastReceivedToken() ->willReturn([ 'access_token' => 123, 'expires_at' => time() - 1 ]); - $expiredFetcher->updateMetadata(Argument::any(), 'audience', Argument::type('callable')) - ->willReturn(['authorization' => ['Bearer 456']]); + $expiredFetcher->fetchAuthToken(Argument::any()) + ->willReturn([ + 'access_token' => 456, + 'expires_at' => time() + 1000 + ]); + return $expiredFetcher->reveal(); + } + + private function getExpiredInvalidFetcher() + { $expiredInvalidFetcher = $this->prophesize(FetchAuthTokenInterface::class); $expiredInvalidFetcher->getLastReceivedToken() ->willReturn([ @@ -409,44 +420,70 @@ public function getAuthorizationHeaderCallbackData() ]); $expiredInvalidFetcher->fetchAuthToken(Argument::any()) ->willReturn(['not-a' => 'valid-token']); - $unexpiredFetcher = $this->prophesize(); - $unexpiredFetcher->willImplement(FetchAuthTokenInterface::class); + return $expiredInvalidFetcher->reveal(); + } + + private function getEagerExpiredFetcher() + { + $eagerExpiredFetcher = $this->prophesize(FetchAuthTokenInterface::class); + $eagerExpiredFetcher->getLastReceivedToken() + ->willReturn([ + 'access_token' => 123, + 'expires_at' => time() + 1 + ]); + $eagerExpiredFetcher->fetchAuthToken(Argument::any()) + ->willReturn([ + 'access_token' => 456, + 'expires_at' => time() + 10 // within 10 second eager threshold + ]); + return $eagerExpiredFetcher->reveal(); + } + + private function getUnexpiredFetcher() + { + $unexpiredFetcher = $this->prophesize(FetchAuthTokenInterface::class); $unexpiredFetcher->getLastReceivedToken() ->willReturn([ 'access_token' => 123, 'expires_at' => time() + 100, ]); + $unexpiredFetcher->fetchAuthToken(Argument::any()) + ->shouldNotBeCalled(); + return $unexpiredFetcher->reveal(); + } + private function getInsecureFetcher() + { $insecureFetcher = $this->prophesize(FetchAuthTokenInterface::class); $insecureFetcher->getLastReceivedToken()->willReturn(null); $insecureFetcher->fetchAuthToken(Argument::any()) ->willReturn([ 'access_token' => '', ]); + return $insecureFetcher->reveal(); + } + + private function getNullFetcher() + { $nullFetcher = $this->prophesize(FetchAuthTokenInterface::class); $nullFetcher->getLastReceivedToken()->willReturn(null); $nullFetcher->fetchAuthToken(Argument::any()) ->willReturn([ 'access_token' => null, ]); + return $nullFetcher->reveal(); + } - $customFetcher = $this->prophesize(); - $customFetcher->willImplement(FetchAuthTokenInterface::class); + private function getCustomFetcher() + { + $customFetcher = $this->prophesize(FetchAuthTokenInterface::class); $customFetcher->getLastReceivedToken()->willReturn(null); $customFetcher->fetchAuthToken(Argument::any()) ->willReturn([ 'access_token' => 123, 'expires_at' => time() + 100, ]); - - return [ - [$expiredFetcher->reveal(), ['authorization' => ['Bearer 456']]], - [$expiredInvalidFetcher->reveal(), []], - [$unexpiredFetcher->reveal(), ['authorization' => ['Bearer 123']]], - [$insecureFetcher->reveal(), []], - [$nullFetcher->reveal(), []], - [$customFetcher->reveal(), ['authorization' => ['Bearer 123']]], - ]; + return $customFetcher->reveal(); } /** @@ -572,7 +609,7 @@ public function testGetProjectIdWithFetchAuthTokenCache() public function testSerializeCredentialsWrapper() { $credentialsWrapper = CredentialsWrapper::build([ - 'keyFile' => __DIR__ . '/testdata/json-key-file.json', + 'keyFile' => __DIR__ . '/testdata/creds/json-key-file.json', ]); $serialized = serialize($credentialsWrapper); $this->assertIsString($serialized); diff --git a/tests/Unit/FixedSizeCollectionTest.php b/tests/Unit/FixedSizeCollectionTest.php index dade34d7b..00771d074 100644 --- a/tests/Unit/FixedSizeCollectionTest.php +++ b/tests/Unit/FixedSizeCollectionTest.php @@ -194,7 +194,9 @@ public function testInvalidPageCount() ->willReturn(2); $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('collectionSize must be greater than or equal to the number of elements in initialPage'); + $this->expectExceptionMessage( + 'collectionSize must be greater than or equal to the number of elements in initialPage' + ); new FixedSizeCollection($page->reveal(), $collectionSize); } diff --git a/tests/Unit/GapicClientTraitTest.php b/tests/Unit/GapicClientTraitTest.php index 8f702c57c..ae62e7539 100644 --- a/tests/Unit/GapicClientTraitTest.php +++ b/tests/Unit/GapicClientTraitTest.php @@ -68,11 +68,18 @@ class GapicClientTraitTest extends TestCase use ProphecyTrait; use TestTrait; + public function setUp(): void + { + $keyFilePath = __DIR__ . '/testdata/creds/json-key-file.json'; + putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $keyFilePath); + } + public function tearDown(): void { // Reset the static gapicVersion field between tests $client = new StubGapicClient(); $client->set('gapicVersionFromFile', null, true); + putenv('GOOGLE_APPLICATION_CREDENTIALS='); } public function testHeadersOverwriteBehavior() @@ -108,7 +115,7 @@ public function testHeadersOverwriteBehavior() ]; $transport = $this->prophesize(TransportInterface::class); $credentialsWrapper = CredentialsWrapper::build([ - 'keyFile' => __DIR__ . '/testdata/json-key-file.json' + 'keyFile' => __DIR__ . '/testdata/creds/json-key-file.json' ]); $transport->startUnaryCall( Argument::type(Call::class), @@ -170,7 +177,7 @@ public function testVersionedHeadersOverwriteBehavior() ]; $transport = $this->prophesize(TransportInterface::class); $credentialsWrapper = CredentialsWrapper::build([ - 'keyFile' => __DIR__ . '/testdata/json-key-file.json' + 'keyFile' => __DIR__ . '/testdata/creds/json-key-file.json' ]); $transport->startUnaryCall( Argument::type(Call::class), @@ -668,7 +675,7 @@ public function createTransportData() : 'rest'; $transportConfig = [ 'rest' => [ - 'restClientConfigPath' => __DIR__ . '/testdata/test_service_rest_client_config.php', + 'restClientConfigPath' => __DIR__ . '/testdata/resources/test_service_rest_client_config.php', ], ]; return [ @@ -700,7 +707,7 @@ public function createTransportDataInvalid() $apiEndpoint = 'address:443'; $transportConfig = [ 'rest' => [ - 'restConfigPath' => __DIR__ . '/testdata/test_service_rest_client_config.php', + 'restConfigPath' => __DIR__ . '/testdata/resources/test_service_rest_client_config.php', ], ]; return [ @@ -944,7 +951,7 @@ private function buildClientToTestModifyCallMethods($clientClass = null) ]; $transport = $this->prophesize(TransportInterface::class); $credentialsWrapper = CredentialsWrapper::build([ - 'keyFile' => __DIR__ . '/testdata/json-key-file.json' + 'keyFile' => __DIR__ . '/testdata/creds/json-key-file.json' ]); $clientClass = $clientClass ?: StubGapicClientExtension::class; $client = new $clientClass(); @@ -977,7 +984,7 @@ public function testModifyUnaryCallFromStartCall() ], 'headers' => AgentHeader::buildAgentHeader([]), 'credentialsWrapper' => CredentialsWrapper::build([ - 'keyFile' => __DIR__ . '/testdata/json-key-file.json' + 'keyFile' => __DIR__ . '/testdata/creds/json-key-file.json' ]) ] ) @@ -1003,7 +1010,7 @@ public function testModifyUnaryCallFromOperationsCall() ], 'headers' => AgentHeader::buildAgentHeader([]), 'credentialsWrapper' => CredentialsWrapper::build([ - 'keyFile' => __DIR__ . '/testdata/json-key-file.json' + 'keyFile' => __DIR__ . '/testdata/creds/json-key-file.json' ]), 'metadataReturnType' => 'metadataType' ] @@ -1031,7 +1038,7 @@ public function testModifyUnaryCallFromGetPagedListResponse() ], 'headers' => AgentHeader::buildAgentHeader([]), 'credentialsWrapper' => CredentialsWrapper::build([ - 'keyFile' => __DIR__ . '/testdata/json-key-file.json' + 'keyFile' => __DIR__ . '/testdata/creds/json-key-file.json' ]) ] ) @@ -1059,7 +1066,7 @@ public function testModifyStreamingCallFromStartCall($callArgs, $expectedMethod, ], 'headers' => AgentHeader::buildAgentHeader([]), 'credentialsWrapper' => CredentialsWrapper::build([ - 'keyFile' => __DIR__ . '/testdata/json-key-file.json' + 'keyFile' => __DIR__ . '/testdata/creds/json-key-file.json' ]) ] ) @@ -1376,7 +1383,7 @@ public function __invoke(Call $call, array $options) ], 'headers' => AgentHeader::buildAgentHeader([]), 'credentialsWrapper' => CredentialsWrapper::build([ - 'keyFile' => __DIR__ . '/testdata/json-key-file.json' + 'keyFile' => __DIR__ . '/testdata/creds/json-key-file.json' ]) ] ) @@ -1446,7 +1453,7 @@ public function __invoke(Call $call, array $options) ], 'headers' => AgentHeader::buildAgentHeader([]), 'credentialsWrapper' => CredentialsWrapper::build([ - 'keyFile' => __DIR__ . '/testdata/json-key-file.json' + 'keyFile' => __DIR__ . '/testdata/creds/json-key-file.json' ]) ] ) @@ -1463,7 +1470,7 @@ public function __invoke(Call $call, array $options) $this->assertEquals(['middleware1', 'middleware2'], $callOrder); } - public function testPrependMiddlewareOrder() + public function testPrependMiddlewareOrder() { list($client, $transport) = $this->buildClientToTestModifyCallMethods(); @@ -1486,42 +1493,42 @@ public function __invoke(Call $call, array $options) } }; }; - $middleware2 = function (callable $handler) use (&$callOrder) { - return new class($handler, $callOrder) implements MiddlewareInterface { - private $handler; - private array $callOrder; - public function __construct( - callable $handler, - array &$callOrder - ) { - $this->handler = $handler; - $this->callOrder = &$callOrder; - } - public function __invoke(Call $call, array $options) - { - $this->callOrder[] = 'middleware2'; - return ($this->handler)($call, $options); - } + $middleware2 = function (callable $handler) use (&$callOrder) { + return new class($handler, $callOrder) implements MiddlewareInterface { + private $handler; + private array $callOrder; + public function __construct( + callable $handler, + array &$callOrder + ) { + $this->handler = $handler; + $this->callOrder = &$callOrder; + } + public function __invoke(Call $call, array $options) + { + $this->callOrder[] = 'middleware2'; + return ($this->handler)($call, $options); + } + }; }; - }; - $client->prependMiddleware($middleware1); - $client->prependMiddleware($middleware2); + $client->prependMiddleware($middleware1); + $client->prependMiddleware($middleware2); - $transport->startUnaryCall( - Argument::type(Call::class), - [ + $transport->startUnaryCall( + Argument::type(Call::class), + [ 'transportOptions' => [ - 'custom' => ['addModifyUnaryCallableOption' => true] + 'custom' => ['addModifyUnaryCallableOption' => true] ], 'headers' => AgentHeader::buildAgentHeader([]), 'credentialsWrapper' => CredentialsWrapper::build([ - 'keyFile' => __DIR__ . '/testdata/json-key-file.json' + 'keyFile' => __DIR__ . '/testdata/creds/json-key-file.json' ]) - ] - ) - ->shouldBeCalledOnce() - ->willReturn(new FulfilledPromise(new Operation())); + ] + ) + ->shouldBeCalledOnce() + ->willReturn(new FulfilledPromise(new Operation())); $client->startCall( 'simpleMethod', @@ -1563,7 +1570,7 @@ public function testCallOptionsForV2Surface() [ 'headers' => AgentHeader::buildAgentHeader([]) + ['Foo' => 'Bar'], 'credentialsWrapper' => CredentialsWrapper::build([ - 'keyFile' => __DIR__ . '/testdata/json-key-file.json' + 'keyFile' => __DIR__ . '/testdata/creds/json-key-file.json' ]), 'timeoutMillis' => null, // adds null timeoutMillis, 'transportOptions' => [], @@ -1594,7 +1601,7 @@ public function testInvalidCallOptionsTypeForV1SurfaceDoesNotThrowException() 'transportOptions' => ['custom' => ['addModifyUnaryCallableOption' => true]], 'headers' => AgentHeader::buildAgentHeader([]), 'credentialsWrapper' => CredentialsWrapper::build([ - 'keyFile' => __DIR__ . '/testdata/json-key-file.json' + 'keyFile' => __DIR__ . '/testdata/creds/json-key-file.json' ]), 'timeoutMillis' => 'blue', // invalid type, this is ignored ] @@ -1696,13 +1703,12 @@ public function testKeyFileIsIgnoredWhenApiKeyOptionIsSupplied() $client = new GapicV2SurfaceClient([ 'apiKey' => 'abc-123', 'credentialsConfig' => [ - 'keyFile' => __DIR__ . '/testdata/json-key-file.json', + 'keyFile' => __DIR__ . '/testdata/creds/json-key-file.json', ], ]); $prop = new \ReflectionProperty($client, 'credentialsWrapper'); $this->assertInstanceOf(ApiKeyHeaderCredentials::class, $prop->getValue($client)); - } public function testApiKeyOptionAndQuotaProject() @@ -1802,15 +1808,15 @@ public static function getClientDefaults() return [ 'apiEndpoint' => 'test.address.com:443', 'serviceName' => 'test.interface.v1.api', - 'clientConfig' => __DIR__ . '/testdata/test_service_client_config.json', - 'descriptorsConfigPath' => __DIR__ . '/testdata/test_service_descriptor_config.php', + 'clientConfig' => __DIR__ . '/testdata/resources/test_service_client_config.json', + 'descriptorsConfigPath' => __DIR__ . '/testdata/resources/test_service_descriptor_config.php', 'disableRetries' => false, 'auth' => null, 'authConfig' => null, 'transport' => null, 'transportConfig' => [ 'rest' => [ - 'restClientConfigPath' => __DIR__ . '/testdata/test_service_rest_client_config.php', + 'restClientConfigPath' => __DIR__ . '/testdata/resources/test_service_rest_client_config.php', ] ], ]; diff --git a/tests/Unit/InsecureCredentialsWrapperTest.php b/tests/Unit/InsecureCredentialsWrapperTest.php index ce0af535c..556ed69cd 100644 --- a/tests/Unit/InsecureCredentialsWrapperTest.php +++ b/tests/Unit/InsecureCredentialsWrapperTest.php @@ -80,5 +80,4 @@ public function testInsecureCredentialsWrapperWithGrpcTransport() $this->assertInstanceOf(Promise::class, $response); } - } diff --git a/tests/Unit/InsecureRequestBuilderTest.php b/tests/Unit/InsecureRequestBuilderTest.php index e3cd70885..084ad3f70 100644 --- a/tests/Unit/InsecureRequestBuilderTest.php +++ b/tests/Unit/InsecureRequestBuilderTest.php @@ -15,6 +15,8 @@ * limitations under the License. */ +namespace Google\ApiCore\Tests\Unit; + use Google\ApiCore\InsecureRequestBuilder; use Google\ApiCore\Testing\MockRequestBody; use PHPUnit\Framework\TestCase; @@ -32,7 +34,7 @@ public function setUp(): void { $this->builder = new InsecureRequestBuilder( 'www.example.com', - __DIR__ . '/testdata/test_service_rest_client_config.php' + __DIR__ . '/testdata/resources/test_service_rest_client_config.php' ); } diff --git a/tests/Unit/Middleware/RetryMiddlewareTest.php b/tests/Unit/Middleware/RetryMiddlewareTest.php index 256f91fa9..f9e07853e 100644 --- a/tests/Unit/Middleware/RetryMiddlewareTest.php +++ b/tests/Unit/Middleware/RetryMiddlewareTest.php @@ -399,7 +399,7 @@ public function testRetriesAreIncludedInTheOptionsArray() ]); $callCount = 0; - $handler = function(Call $call, $options) use (&$callCount, &$reportedRetries) { + $handler = function (Call $call, $options) use (&$callCount, &$reportedRetries) { $promise = new Promise(function () use (&$callCount, &$reportedRetries, $options, &$promise) { if ($callCount === 0) { ++$callCount; @@ -526,4 +526,4 @@ public function testUnlimitedMaxRetries() $middleware($call->reveal(), [])->wait(); } -} \ No newline at end of file +} diff --git a/tests/Unit/OperationResponseTest.php b/tests/Unit/OperationResponseTest.php index a4697579a..43ab160a4 100644 --- a/tests/Unit/OperationResponseTest.php +++ b/tests/Unit/OperationResponseTest.php @@ -31,6 +31,13 @@ */ namespace Google\ApiCore\Tests\Unit; +use Google\CustomOperation\Client\NewSurfaceCustomOperationClient; +use Google\CustomOperation\CancelOperationRequest as CustomCancelOperationRequest; +use Google\CustomOperation\CustomOperation; +use Google\CustomOperation\CustomOperationClient; +use Google\CustomOperation\CustomOperationWithErrorAnnotations; +use Google\CustomOperation\DeleteOperationRequest as CustomDeleteOperationRequest; +use Google\CustomOperation\GetOperationRequest as CustomGetOperationRequest; use Google\ApiCore\LongRunning\OperationsClient; use Google\ApiCore\OperationResponse; use Google\LongRunning\CancelOperationRequest; @@ -50,6 +57,16 @@ class OperationResponseTest extends TestCase use ProphecyTrait; use TestTrait; + public static function setUpBeforeClass(): void + { + self::autoloadTestdata('mocks', 'Google'); + } + + public static function tearDownAfterClass(): void + { + putenv('GOOGLE_APPLICATION_CREDENTIALS='); + } + /** * @dataProvider provideOperationsClients */ @@ -64,6 +81,9 @@ public function testBasic($opClient) public function provideOperationsClients() { + $keyFilePath = __DIR__ . '/testdata/creds/json-key-file.json'; + putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $keyFilePath); + return [ [$this->createOperationsClient()], [$this->prophesize(LROOperationsClient::class)->reveal()], @@ -261,13 +281,10 @@ public function testCustomOperation() public function testNewSurfaceCustomOperation() { - // This mock requires a specific namespace, so it must be defined in a separate file - require_once __DIR__ . '/testdata/src/CustomOperationClient.php'; - $phpunit = $this; $operationName = 'test-123'; - $operationClient = $this->prophesize(Client\NewSurfaceCustomOperationClient::class); - $operationClient->getNewSurfaceOperation(Argument::type(Client\GetOperationRequest::class)) + $operationClient = $this->prophesize(NewSurfaceCustomOperationClient::class); + $operationClient->getNewSurfaceOperation(Argument::type(CustomGetOperationRequest::class)) ->shouldBeCalledOnce() ->will(function ($args) use ($phpunit) { list($request) = $args; @@ -275,15 +292,17 @@ public function testNewSurfaceCustomOperation() $phpunit->assertEquals('arg2', $request->arg2); $phpunit->assertEquals('arg3', $request->arg3); return new class { - public function getDone() { + public function getDone() + { return true; } - public function getError() { + public function getError() + { return false; } }; }); - $operationClient->cancelNewSurfaceOperation(Argument::type(Client\CancelOperationRequest::class)) + $operationClient->cancelNewSurfaceOperation(Argument::type(CustomCancelOperationRequest::class)) ->shouldBeCalledOnce() ->will(function ($args) use ($phpunit) { list($request) = $args; @@ -292,7 +311,7 @@ public function getError() { $phpunit->assertEquals('arg3', $request->arg3); return true; }); - $operationClient->deleteNewSurfaceOperation(Argument::type(Client\DeleteOperationRequest::class)) + $operationClient->deleteNewSurfaceOperation(Argument::type(CustomDeleteOperationRequest::class)) ->shouldBeCalledOnce() ->will(function ($args) use ($phpunit) { list($request) = $args; @@ -309,9 +328,9 @@ public function getError() { 'setArgumentTwo' => 'arg2', 'setArgumentThree' => 'arg3' ], - 'getOperationRequest' => Client\GetOperationRequest::class, - 'cancelOperationRequest' => Client\CancelOperationRequest::class, - 'deleteOperationRequest' => Client\DeleteOperationRequest::class, + 'getOperationRequest' => CustomGetOperationRequest::class, + 'cancelOperationRequest' => CustomCancelOperationRequest::class, + 'deleteOperationRequest' => CustomDeleteOperationRequest::class, ]; $operationResponse = new OperationResponse($operationName, $operationClient->reveal(), $options); @@ -335,10 +354,7 @@ public function testRequestClassWithoutBuildThrowsException() $this->expectException(LogicException::class); $this->expectExceptionMessage('Request class must support the static build method'); - // This mock requires a specific namespace, so it must be defined in a separate file - require_once __DIR__ . '/testdata/src/CustomOperationClient.php'; - - $operationClient = $this->prophesize(Client\NewSurfaceCustomOperationClient::class); + $operationClient = $this->prophesize(NewSurfaceCustomOperationClient::class); $options = [ 'getOperationRequest' => \stdClass::class, // a class that does not have a "build" method. ]; @@ -510,7 +526,31 @@ public function testDeleteWithLROOperationsClient() private function createOperationResponse($options, $reloadCount) { $opName = 'operations/opname'; - return new FakeOperationResponse($opName, $this->createOperationClient($reloadCount), $options); + return new class($opName, $this->createOperationClient($reloadCount), $options) extends OperationResponse { + private $currentTime = 0; + private $sleeps; + + public function getSleeps() + { + return $this->sleeps; + } + + public function sleepMillis(int $millis) + { + $this->currentTime += $millis; + $this->sleeps[] = $millis; + } + + public function setTimes($times) + { + $this->times = $times; + } + + public function getCurrentTimeMillis() + { + return $this->currentTime; + } + }; } private function createOperationClient($reloadCount) @@ -530,50 +570,3 @@ private function createOperationClient($reloadCount) return $opClient->reveal(); } } - -class FakeOperationResponse extends OperationResponse -{ - private $currentTime = 0; - private $sleeps; - - public function getSleeps() - { - return $this->sleeps; - } - - public function sleepMillis(int $millis) - { - $this->currentTime += $millis; - $this->sleeps[] = $millis; - } - - public function setTimes($times) - { - $this->times = $times; - } - - public function getCurrentTimeMillis() - { - return $this->currentTime; - } -} - -interface CustomOperationClient -{ - public function getMyOperationPlease($name, $requiredArg1, $requiredArg2); - public function cancelMyOperationPlease($name, $requiredArg1, $requiredArg2); - public function deleteMyOperationPlease($name, $requiredArg1, $requiredArg2); -} - -interface CustomOperation -{ - public function isThisOperationDoneOrWhat(); - public function getError(); -} - -interface CustomOperationWithErrorAnnotations -{ - public function isThisOperationDoneOrWhat(); - public function getTheErrorCode(); - public function getTheErrorMessage(); -} diff --git a/tests/Unit/Options/OptionsTraitTest.php b/tests/Unit/Options/OptionsTraitTest.php index ae784a7a3..1e24517ba 100644 --- a/tests/Unit/Options/OptionsTraitTest.php +++ b/tests/Unit/Options/OptionsTraitTest.php @@ -46,7 +46,7 @@ class OptionsTraitTest extends TestCase public function testDefaultOptionsExistAndUnrecognizedOptionsAreIgnored() { - $options = new OptionsTraitStub([ + $options = $this->newStub([ 'option3' => 'foo', 'option4' => 'bar', ]); @@ -60,13 +60,9 @@ public function testDefaultOptionsExistAndUnrecognizedOptionsAreIgnored() public function testInvalidTypesThrowException() { $this->expectException(TypeError::class); - $this->expectExceptionMessage( - PHP_MAJOR_VERSION < 8 - ? 'Google\ApiCore\Tests\Unit\Options\OptionsTraitStub::$option2 must be int or null, string used' - : 'Cannot assign string to property Google\ApiCore\Tests\Unit\Options\OptionsTraitStub::$option2 of type ?int' - ); + $this->expectExceptionMessage('Cannot assign string to property ArrayAccess@anonymous::$option2 of type ?int'); - $options = new OptionsTraitStub([ + $this->newStub([ 'option1' => 123, // this is okay because it is cast to a string 'option2' => 'bar', // this will throw an exception ]); @@ -74,13 +70,13 @@ public function testInvalidTypesThrowException() public function testArrayGet() { - $options = new OptionsTraitStub(['option1' => 'abc']); + $options = $this->newStub(['option1' => 'abc']); $this->assertEquals('abc', $options['option1']); } public function testArrayIsset() { - $options = new OptionsTraitStub(['option1' => 'abc']); + $options = $this->newStub(['option1' => 'abc']); $this->assertTrue(isset($options['option1'])); $this->assertFalse(isset($options['option2'])); // valid option $this->assertFalse(isset($options['option3'])); // invalid option @@ -89,14 +85,14 @@ public function testArrayIsset() public function testArraySetThrowsException() { $this->expectException(BadMethodCallException::class); - $options = new OptionsTraitStub([]); + $options = $this->newStub([]); $options['option1'] = 'abc'; } public function testArrayUnsetThrowsException() { $this->expectException(BadMethodCallException::class); - $options = new OptionsTraitStub([]); + $options = $this->newStub([]); unset($options['option1']); } @@ -104,36 +100,38 @@ public function testInvalidFilePathThrowsException() { $this->expectException(ValidationException::class); $this->expectExceptionMessage('Could not find specified file: does/not/exist.php'); - new OptionsTraitStub(['file' => 'does/not/exist.php']); + $this->newStub(['file' => 'does/not/exist.php']); } public function testValidateFileExists() { - $options = new OptionsTraitStub(['option1' => 'foo', 'file' => __FILE__]); + $options = $this->newStub(['option1' => 'foo', 'file' => __FILE__]); $this->assertEquals(__FILE__, $options['file']); } -} - -class OptionsTraitStub implements ArrayAccess -{ - use OptionsTrait; - - private ?string $option1; - private ?int $option2; - private ?string $file; - - public function __construct(array $options) - { - $this->option1 = $options['option1'] ?? null; - $this->option2 = $options['option2'] ?? null; - $this->setFile($options['file'] ?? null); - } - private function setFile(?string $file) + private function newStub(array $options) { - if (!is_null($file)) { - self::validateFileExists($file); - } - $this->file = $file; + return new class($options) implements ArrayAccess { + use OptionsTrait; + + private ?string $option1; + private ?int $option2; + private ?string $file; + + public function __construct(array $options) + { + $this->option1 = $options['option1'] ?? null; + $this->option2 = $options['option2'] ?? null; + $this->setFile($options['file'] ?? null); + } + + private function setFile(?string $file) + { + if (!is_null($file)) { + self::validateFileExists($file); + } + $this->file = $file; + } + }; } } diff --git a/tests/Unit/ProtobufBandaidTest.php b/tests/Unit/ProtobufBandaidTest.php index 7b8f955e6..866623570 100644 --- a/tests/Unit/ProtobufBandaidTest.php +++ b/tests/Unit/ProtobufBandaidTest.php @@ -21,6 +21,8 @@ class ProtobufBandaidTest extends GeneratedTest { + use TestTrait; + /** * @dataProvider protobufMessageProvider */ @@ -31,6 +33,9 @@ public function testCompare($expected, $actual) public function protobufMessageProvider() { + $this->autoloadTestdata('generated'); + $this->autoloadTestdata('generated/metadata', 'GPBMetadata\\' . __NAMESPACE__); + $msg1 = new MyMessage(); $msg2 = new Mymessage(); return [ diff --git a/tests/Unit/RequestBuilderTest.php b/tests/Unit/RequestBuilderTest.php index 42b7ac241..a587be4f8 100644 --- a/tests/Unit/RequestBuilderTest.php +++ b/tests/Unit/RequestBuilderTest.php @@ -46,11 +46,11 @@ public function setUp(): void { $this->builder = new RequestBuilder( 'www.example.com', - __DIR__ . '/testdata/test_service_rest_client_config.php' + __DIR__ . '/testdata/resources/test_service_rest_client_config.php' ); $this->numericEnumsBuilder = new RequestBuilder( 'www.example.com', - __DIR__ . '/testdata/test_numeric_enums_rest_client_config.php' + __DIR__ . '/testdata/resources/test_numeric_enums_rest_client_config.php' ); } @@ -447,7 +447,10 @@ public function testMethodWithOneOfInQueryString() public function testMethodWithNumericEnumsQueryParam() { - $request = $this->numericEnumsBuilder->build(self::SERVICE_NAME . '/MethodWithNumericEnumsQueryParam', new MockRequestBody()); + $request = $this->numericEnumsBuilder->build( + self::SERVICE_NAME . '/MethodWithNumericEnumsQueryParam', + new MockRequestBody() + ); $query = Query::parse($request->getUri()->getQuery()); $this->assertEquals('json;enum-encoding=int', $query['$alt']); @@ -459,7 +462,9 @@ public function testThrowsExceptionWithNonMatchingFormat() $message->setName('invalid/name/format'); $this->expectException(ValidationException::class); - $this->expectExceptionMessage('Could not map bindings for test.interface.v1.api/MethodWithAdditionalBindings to any Uri template.'); + $this->expectExceptionMessage( + 'Could not map bindings for test.interface.v1.api/MethodWithAdditionalBindings to any Uri template.' + ); $this->builder->build(self::SERVICE_NAME . '/MethodWithAdditionalBindings', $message); } @@ -469,7 +474,9 @@ public function testThrowsExceptionWithNonExistantMethod() $message = new MockRequestBody(); $this->expectException(ValidationException::class); - $this->expectExceptionMessage('Failed to build request, as the provided path (myResource/doesntExist) was not found in the configuration.'); + $this->expectExceptionMessage( + 'Failed to build request, as the provided path (myResource/doesntExist) was not found in the configuration.' + ); $this->builder->build('myResource/doesntExist', $message); } diff --git a/tests/Unit/ResourceHelperTraitTest.php b/tests/Unit/ResourceHelperTraitTest.php index ce2bd1981..612356167 100644 --- a/tests/Unit/ResourceHelperTraitTest.php +++ b/tests/Unit/ResourceHelperTraitTest.php @@ -38,29 +38,61 @@ class ResourceHelperTraitTest extends TestCase { + private $stub; + + public function setUp(): void + { + $this->stub = new class() { + use ResourceHelperTrait; + + const CONFIG_PATH = __DIR__ . '/testdata/resources/test_service_descriptor_config.php'; + const SERVICE_NAME = 'test.interface.v1.api'; + + private static function getClientDefaults() + { + return ['descriptorsConfigPath' => self::CONFIG_PATH]; + } + + public static function parseName($formattedName, $template = null) + { + return self::parseFormattedName($formattedName, $template); + } + + public static function testRegisterPathTemplates() + { + self::registerPathTemplates(); + return self::$templateMap; + } + + public static function testGetPathTemplate($key) + { + return self::getPathTemplate($key); + } + }; + } public function testRegisterPathTemplates() { - $got = ResourceHelperTraitStub::testRegisterPathTemplates(); + $got = $this->stub::testRegisterPathTemplates(); $this->assertEquals(count($got), 4); $this->assertTrue($got['project'] instanceof PathTemplate); } public function testGetPathTemplate() { - $got = ResourceHelperTraitStub::testGetPathTemplate('project'); + $got = $this->stub::testGetPathTemplate('project'); $this->assertNotNull($got); $this->assertTrue($got instanceof PathTemplate); } public function testGetPathTemplateNull() { - $got = ResourceHelperTraitStub::testGetPathTemplate('does_not_exist'); + $got = $this->stub::testGetPathTemplate('does_not_exist'); $this->assertNull($got); } public function testParseName() { - $got = ResourceHelperTraitStub::parseName('projects/abc123', 'project'); + $got = $this->stub::parseName('projects/abc123', 'project'); $this->assertEquals(count($got), 1); $this->assertEquals($got['project'], 'abc123'); } @@ -70,7 +102,7 @@ public function testParseNameInvalidTemplate() $this->expectException(ValidationException::class); $this->expectExceptionMessage('Template name does_not_exist does not exist'); - ResourceHelperTraitStub::parseName('projects/abc123', 'does_not_exist'); + $this->stub::parseName('projects/abc123', 'does_not_exist'); } public function testParseNameNoMatchingPattern() @@ -78,36 +110,6 @@ public function testParseNameNoMatchingPattern() $this->expectException(ValidationException::class); $this->expectExceptionMessage('Input did not match any known format. Input: no/matching/pattern'); - ResourceHelperTraitStub::parseName('no/matching/pattern'); - } - -} - -class ResourceHelperTraitStub -{ - use ResourceHelperTrait; - - const CONFIG_PATH = __DIR__ . '/testdata/test_service_descriptor_config.php'; - const SERVICE_NAME = 'test.interface.v1.api'; - - private static function getClientDefaults() - { - return ['descriptorsConfigPath' => self::CONFIG_PATH]; - } - - public static function parseName($formattedName, $template = null) - { - return self::parseFormattedName($formattedName, $template); - } - - public static function testRegisterPathTemplates() - { - self::registerPathTemplates(); - return self::$templateMap; - } - - public static function testGetPathTemplate($key) - { - return self::getPathTemplate($key); + $this->stub::parseName('no/matching/pattern'); } } diff --git a/tests/Unit/ResourceTemplate/ParserTest.php b/tests/Unit/ResourceTemplate/ParserTest.php index 6452af987..e3a479fef 100644 --- a/tests/Unit/ResourceTemplate/ParserTest.php +++ b/tests/Unit/ResourceTemplate/ParserTest.php @@ -75,16 +75,16 @@ public function testParseBasicSegments() $this->testParseSegments( 'foo/bar/baz', [self::literalSegment('foo'), - self::literalSegment('bar'), - self::literalSegment('baz')] + self::literalSegment('bar'), + self::literalSegment('baz')] ); $this->testParseSegments( 'foos/{foo}/bars/{bar}', [self::literalSegment('foos'), - self::variableSegment('foo', new RelativeResourceTemplate('*')), - self::literalSegment('bars'), - self::variableSegment('bar', new RelativeResourceTemplate('*'))] + self::variableSegment('foo', new RelativeResourceTemplate('*')), + self::literalSegment('bars'), + self::variableSegment('bar', new RelativeResourceTemplate('*'))] ); } @@ -93,29 +93,29 @@ public function testParseBasicNonSlashSeparators() $this->testParseSegments( 'foos/{foo}_{oof}', [self::literalSegment('foos'), - self::variableSegment('foo', new RelativeResourceTemplate('*'), '_'), - self::variableSegment('oof', new RelativeResourceTemplate('*'))] + self::variableSegment('foo', new RelativeResourceTemplate('*'), '_'), + self::variableSegment('oof', new RelativeResourceTemplate('*'))] ); $this->testParseSegments( 'foos/{foo}-{oof}', [self::literalSegment('foos'), - self::variableSegment('foo', new RelativeResourceTemplate('*'), '-'), - self::variableSegment('oof', new RelativeResourceTemplate('*'))] + self::variableSegment('foo', new RelativeResourceTemplate('*'), '-'), + self::variableSegment('oof', new RelativeResourceTemplate('*'))] ); $this->testParseSegments( 'foos/{foo}~{oof}', [self::literalSegment('foos'), - self::variableSegment('foo', new RelativeResourceTemplate('*'), '~'), - self::variableSegment('oof', new RelativeResourceTemplate('*'))] + self::variableSegment('foo', new RelativeResourceTemplate('*'), '~'), + self::variableSegment('oof', new RelativeResourceTemplate('*'))] ); $this->testParseSegments( 'foos/{foo}.{oof}', [self::literalSegment('foos'), - self::variableSegment('foo', new RelativeResourceTemplate('*'), '.'), - self::variableSegment('oof', new RelativeResourceTemplate('*'))] + self::variableSegment('foo', new RelativeResourceTemplate('*'), '.'), + self::variableSegment('oof', new RelativeResourceTemplate('*'))] ); } @@ -124,29 +124,29 @@ public function testParseMultipleNonSlashSeparators() $this->testParseSegments( 'foos/{foo}_{oof}-{bar}.{baz}~{car}', [self::literalSegment('foos'), - self::variableSegment('foo', new RelativeResourceTemplate('*'), '_'), - self::variableSegment('oof', new RelativeResourceTemplate('*'), '-'), - self::variableSegment('bar', new RelativeResourceTemplate('*'), '.'), - self::variableSegment('baz', new RelativeResourceTemplate('*'), '~'), - self::variableSegment('car', new RelativeResourceTemplate('*'))] + self::variableSegment('foo', new RelativeResourceTemplate('*'), '_'), + self::variableSegment('oof', new RelativeResourceTemplate('*'), '-'), + self::variableSegment('bar', new RelativeResourceTemplate('*'), '.'), + self::variableSegment('baz', new RelativeResourceTemplate('*'), '~'), + self::variableSegment('car', new RelativeResourceTemplate('*'))] ); $this->testParseSegments( 'foos/{foo}.{oof}_{bar}.{car}', [self::literalSegment('foos'), - self::variableSegment('foo', new RelativeResourceTemplate('*'), '.'), - self::variableSegment('oof', new RelativeResourceTemplate('*'), '_'), - self::variableSegment('bar', new RelativeResourceTemplate('*'), '.'), - self::variableSegment('car', new RelativeResourceTemplate('*'))] + self::variableSegment('foo', new RelativeResourceTemplate('*'), '.'), + self::variableSegment('oof', new RelativeResourceTemplate('*'), '_'), + self::variableSegment('bar', new RelativeResourceTemplate('*'), '.'), + self::variableSegment('car', new RelativeResourceTemplate('*'))] ); $this->testParseSegments( 'foos/{foo}-{oof}.{bar}~{car}', [self::literalSegment('foos'), - self::variableSegment('foo', new RelativeResourceTemplate('*'), '-'), - self::variableSegment('oof', new RelativeResourceTemplate('*'), '.'), - self::variableSegment('bar', new RelativeResourceTemplate('*'), '~'), - self::variableSegment('car', new RelativeResourceTemplate('*'))] + self::variableSegment('foo', new RelativeResourceTemplate('*'), '-'), + self::variableSegment('oof', new RelativeResourceTemplate('*'), '.'), + self::variableSegment('bar', new RelativeResourceTemplate('*'), '~'), + self::variableSegment('car', new RelativeResourceTemplate('*'))] ); } @@ -155,26 +155,26 @@ public function testParseNonSlashSeparatorsWithParents() $this->testParseSegments( 'foos/{foo}_{oof}-{bar}.{baz}~{car}/projects/{project}/locations/{state}~{city}.{cell}', [self::literalSegment('foos'), - self::variableSegment('foo', new RelativeResourceTemplate('*'), '_'), - self::variableSegment('oof', new RelativeResourceTemplate('*'), '-'), - self::variableSegment('bar', new RelativeResourceTemplate('*'), '.'), - self::variableSegment('baz', new RelativeResourceTemplate('*'), '~'), - self::variableSegment('car', new RelativeResourceTemplate('*')), - self::literalSegment('projects'), - self::variableSegment('project', new RelativeResourceTemplate('*')), - self::literalSegment('locations'), - self::variableSegment('state', new RelativeResourceTemplate('*'), '~'), - self::variableSegment('city', new RelativeResourceTemplate('*'), '.'), - self::variableSegment('cell', new RelativeResourceTemplate('*'))] + self::variableSegment('foo', new RelativeResourceTemplate('*'), '_'), + self::variableSegment('oof', new RelativeResourceTemplate('*'), '-'), + self::variableSegment('bar', new RelativeResourceTemplate('*'), '.'), + self::variableSegment('baz', new RelativeResourceTemplate('*'), '~'), + self::variableSegment('car', new RelativeResourceTemplate('*')), + self::literalSegment('projects'), + self::variableSegment('project', new RelativeResourceTemplate('*')), + self::literalSegment('locations'), + self::variableSegment('state', new RelativeResourceTemplate('*'), '~'), + self::variableSegment('city', new RelativeResourceTemplate('*'), '.'), + self::variableSegment('cell', new RelativeResourceTemplate('*'))] ); $this->testParseSegments( 'customers/{customer_id}/userLocationViews/{country_criterion_id}~{is_targeting_location}', [self::literalSegment('customers'), - self::variableSegment('customer_id', new RelativeResourceTemplate('*')), - self::literalSegment('userLocationViews'), - self::variableSegment('country_criterion_id', new RelativeResourceTemplate('*'), '~'), - self::variableSegment('is_targeting_location', new RelativeResourceTemplate('*'))] + self::variableSegment('customer_id', new RelativeResourceTemplate('*')), + self::literalSegment('userLocationViews'), + self::variableSegment('country_criterion_id', new RelativeResourceTemplate('*'), '~'), + self::variableSegment('is_targeting_location', new RelativeResourceTemplate('*'))] ); } diff --git a/tests/Unit/ResourceTemplate/RelativeResourceTemplateTest.php b/tests/Unit/ResourceTemplate/RelativeResourceTemplateTest.php index 31d48d663..d29142eb8 100644 --- a/tests/Unit/ResourceTemplate/RelativeResourceTemplateTest.php +++ b/tests/Unit/ResourceTemplate/RelativeResourceTemplateTest.php @@ -205,19 +205,29 @@ public function matchData() ['$0' => '{}!@#$%^&*()+=[]\|`~-_'], ], [ - 'foos/{foo}_{oof}', - 'foos/imafoo_thisisanoof', - ['foo' => 'imafoo', 'oof' => 'thisisanoof'], - ], - [ - 'foos/{foo}.{oof}_{bar}.{car}', - 'foos/food.doof_mars.porsche', - ['foo' => 'food', 'oof' => 'doof', 'bar' => 'mars', 'car' => 'porsche'], - ], - [ - 'foos/{foo}_{oof}-{bar}.{baz}~{car}/projects/{project}/locations/{state}~{city}.{cell}', - 'foos/food_doof-mars.bazz~porsche/projects/someProject/locations/wa~sea.fre3', - ['foo' => 'food', 'oof' => 'doof', 'bar' => 'mars', 'baz' => 'bazz', 'car' => 'porsche', 'project' => 'someProject', 'state' => 'wa', 'city' => 'sea', 'cell' => 'fre3'], + 'foos/{foo}_{oof}', + 'foos/imafoo_thisisanoof', + ['foo' => 'imafoo', 'oof' => 'thisisanoof'], + ], + [ + 'foos/{foo}.{oof}_{bar}.{car}', + 'foos/food.doof_mars.porsche', + ['foo' => 'food', 'oof' => 'doof', 'bar' => 'mars', 'car' => 'porsche'], + ], + [ + 'foos/{foo}_{oof}-{bar}.{baz}~{car}/projects/{project}/locations/{state}~{city}.{cell}', + 'foos/food_doof-mars.bazz~porsche/projects/someProject/locations/wa~sea.fre3', + [ + 'foo' => 'food', + 'oof' => 'doof', + 'bar' => 'mars', + 'baz' => 'bazz', + 'car' => 'porsche', + 'project' => 'someProject', + 'state' => 'wa', + 'city' => 'sea', + 'cell' => 'fre3' + ], ], ]; } @@ -325,8 +335,8 @@ public function invalidRenderData() [ 'buckets/{hello=*}', ['hello' => ''], // Invalid binding - "Error rendering 'buckets/{hello=*}': expected binding 'hello' to match segment '{hello=*}', instead got ''\n" . - "Provided bindings: Array\n" . + "Error rendering 'buckets/{hello=*}': expected binding 'hello' to match segment '{hello=*}', instead " . + "got ''\nProvided bindings: Array\n" . "(\n" . " [hello] => \n" . ")\n", @@ -334,8 +344,8 @@ public function invalidRenderData() [ 'buckets/{hello=*}', ['hello' => null], // Invalid binding - "Error rendering 'buckets/{hello=*}': expected binding 'hello' to match segment '{hello=*}', instead got null\n" . - "Provided bindings: Array\n" . + "Error rendering 'buckets/{hello=*}': expected binding 'hello' to match segment '{hello=*}', instead " . + "got null\nProvided bindings: Array\n" . "(\n" . " [hello] => \n" . ")\n", @@ -343,8 +353,8 @@ public function invalidRenderData() [ 'buckets/*/objects/**', ['$0' => 'foo', '$1' => ''], // Invalid binding - "Error rendering 'buckets/*/objects/**': expected binding '$1' to match segment '**', instead got ''\n" . - "Provided bindings: Array\n" . + "Error rendering 'buckets/*/objects/**': expected binding '$1' to match segment '**', instead got " . + "''\nProvided bindings: Array\n" . "(\n" . " [$0] => foo\n" . " [$1] => \n" . @@ -353,8 +363,8 @@ public function invalidRenderData() [ 'buckets/*/objects/**', ['$0' => 'foo', '$1' => null], // Invalid binding - "Error rendering 'buckets/*/objects/**': expected binding '$1' to match segment '**', instead got null\n" . - "Provided bindings: Array\n" . + "Error rendering 'buckets/*/objects/**': expected binding '$1' to match segment '**', instead got " . + "null\nProvided bindings: Array\n" . "(\n" . " [$0] => foo\n" . " [$1] => \n" . diff --git a/tests/Unit/RetrySettingsTest.php b/tests/Unit/RetrySettingsTest.php index 986f7c542..4d44ca373 100644 --- a/tests/Unit/RetrySettingsTest.php +++ b/tests/Unit/RetrySettingsTest.php @@ -41,13 +41,13 @@ class RetrySettingsTest extends TestCase private static function buildInputConfig() { - $contents = file_get_contents(__DIR__ . '/testdata/test_service_client_config.json'); + $contents = file_get_contents(__DIR__ . '/testdata/resources/test_service_client_config.json'); return json_decode($contents, true); } private static function buildInvalidInputConfig() { - $contents = file_get_contents(__DIR__ . '/testdata/test_service_invalid_client_config.json'); + $contents = file_get_contents(__DIR__ . '/testdata/resources/test_service_invalid_client_config.json'); return json_decode($contents, true); } @@ -274,10 +274,14 @@ public function retrySettingsProvider() [ // Test with a custom retry function [ - 'retryFunction' => function ($ex, $options) {return true;} + 'retryFunction' => function ($ex, $options) { + return true; + } ] + $defaultSettings, [ - 'retryFunction' => function ($ex, $options) {return true;} + 'retryFunction' => function ($ex, $options) { + return true; + } ] + $defaultExpectedValues ], [ @@ -369,10 +373,14 @@ public function withRetrySettingsProvider() // Test with a custom retry function $defaultSettings, [ - 'retryFunction' => function ($ex, $options) {return true;} + 'retryFunction' => function ($ex, $options) { + return true; + } ], [ - 'retryFunction' => function ($ex, $options) {return true;} + 'retryFunction' => function ($ex, $options) { + return true; + } ] + $defaultExpectedValues ], [ diff --git a/tests/Unit/ServerStreamTest.php b/tests/Unit/ServerStreamTest.php index 30b90baea..a792563d1 100644 --- a/tests/Unit/ServerStreamTest.php +++ b/tests/Unit/ServerStreamTest.php @@ -230,6 +230,7 @@ public function testReadCallsLogger() $stream = new ServerStream($call, logger: $logger->reveal()); // Loop to read the responses - foreach ($stream->readAll() as $response) {} + foreach ($stream->readAll() as $response) { + } } } diff --git a/tests/Unit/TestTrait.php b/tests/Unit/TestTrait.php index 34ab0c10b..82132a51b 100644 --- a/tests/Unit/TestTrait.php +++ b/tests/Unit/TestTrait.php @@ -36,6 +36,7 @@ use Google\ApiCore\Testing\MockResponse; use Google\Protobuf\Any; use Google\Rpc\Status; +use PDO; trait TestTrait { @@ -126,10 +127,19 @@ public static function requiresGrpcExtension() } } - public static function requiresPhp7() + private static function autoloadTestdata(string $dir, string $namespace = __NAMESPACE__) { - if (version_compare(phpversion(), '7.0', '<')) { - self::markTestSkipped('This test requires PHP 7.0 or above.'); + // This is required for tests to pass on Windows with PHP 8.1 + // @TODO remove this once we drop PHP 8.1 support + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' && PHP_VERSION_ID < 80200) { + self::markTestSkipped('Skip on Windows + PHP 8.1 to avoid gRPC shutdown crash'); } + + // add mocks to autoloader + $loader = file_exists(__DIR__ . '/../../../vendor/autoload.php') + ? require __DIR__ . '/../../../vendor/autoload.php' + : require __DIR__ . '/../../vendor/autoload.php'; + + $loader->addPsr4($namespace . '\\', __DIR__ . '/testdata/' . $dir); } } diff --git a/tests/Unit/Transport/GrpcFallbackTransportTest.php b/tests/Unit/Transport/GrpcFallbackTransportTest.php index 96a2000fa..205f26ab5 100644 --- a/tests/Unit/Transport/GrpcFallbackTransportTest.php +++ b/tests/Unit/Transport/GrpcFallbackTransportTest.php @@ -91,7 +91,7 @@ public function testStartUnaryCall($apiEndpoint, $requestMessage) ->setName('hello') ->setNumber(15); - $httpHandler = function (RequestInterface $request, array $options = []) use ($expectedResponse, $expectedRequest) { + $httpHandler = function (RequestInterface $request) use ($expectedResponse, $expectedRequest) { $this->assertEquals($expectedRequest, $request); return Create::promiseFor( diff --git a/tests/Unit/Transport/GrpcTransportTest.php b/tests/Unit/Transport/GrpcTransportTest.php index 53ef2a1d3..6eafcb667 100644 --- a/tests/Unit/Transport/GrpcTransportTest.php +++ b/tests/Unit/Transport/GrpcTransportTest.php @@ -50,6 +50,7 @@ use Grpc\CallInvoker; use Grpc\ChannelCredentials; use Grpc\ClientStreamingCall; +use Grpc\Interceptor; use Grpc\ServerStreamingCall; use Grpc\UnaryCall; use GuzzleHttp\Promise\Promise; @@ -443,7 +444,7 @@ public function testAudienceOption() public function testClientCertSourceOptionValid() { $mockClientCertSource = function () { - return 'MOCK_CERT_SOURCE'; + return ['MOCK_KEY', 'MOCK_CERT']; }; $transport = GrpcTransport::build( 'address.com:123', @@ -455,8 +456,6 @@ public function testClientCertSourceOptionValid() public function testClientCertSourceOptionInvalid() { - self::requiresPhp7(); - $mockClientCertSource = 'foo'; $this->expectException(TypeError::class); @@ -544,6 +543,48 @@ public function buildInvalidData() */ public function testExperimentalInterceptors($callType, $interceptor) { + $mockCallInvoker = new class($this->buildMockCallForInterceptor($callType)) { + private $called = false; + private $mockCall; + + public function __construct($mockCall) + { + $this->mockCall = $mockCall; + } + + public function createChannelFactory($hostname, $opts) + { + // no-op + } + + public function UnaryCall($channel, $method, $deserialize, $options) + { + $this->called = true; + return $this->mockCall; + } + + public function ServerStreamingCall($channel, $method, $deserialize, $options) + { + $this->called = true; + return $this->mockCall; + } + + public function ClientStreamingCall($channel, $method, $deserialize, $options) + { + // no-op + } + + public function BidiStreamingCall($channel, $method, $deserialize, $options) + { + // no-op + } + + public function wasCalled() + { + return $this->called; + } + }; + $transport = new GrpcTransport( 'example.com', [ @@ -553,8 +594,6 @@ public function testExperimentalInterceptors($callType, $interceptor) [$interceptor] ); - $mockCallInvoker = new MockCallInvoker($this->buildMockCallForInterceptor($callType)); - $r = new \ReflectionProperty(BaseStub::class, 'call_invoker'); $r->setValue( $transport, @@ -577,18 +616,28 @@ public function testExperimentalInterceptors($callType, $interceptor) public function interceptorDataProvider() { + $this->autoloadTestdata('mocks', __NAMESPACE__); + + $deprecatedInterceptors = (new \ReflectionClass(Interceptor::class)) + ->getMethod('interceptUnaryUnary') + ->getParameters()[3] + ->getName() === 'metadata'; + + $interceptor = $deprecatedInterceptors ? new DeprecatedTestInterceptor(): new TestInterceptor(); + $unaryInterceptor = $deprecatedInterceptors ? new DeprecatedTestUnaryInterceptor(): new TestUnaryInterceptor(); + return [ [ UnaryCall::class, - new TestUnaryInterceptor() + $unaryInterceptor ], [ UnaryCall::class, - new TestInterceptor() + $interceptor ], [ ServerStreamingCall::class, - new TestInterceptor() + $interceptor ] ]; } @@ -616,46 +665,3 @@ private function buildMockCallForInterceptor($callType) return $mockCall->reveal(); } } - -class MockCallInvoker implements CallInvoker -{ - private $called = false; - private $mockCall; - - public function __construct($mockCall) - { - $this->mockCall = $mockCall; - } - - public function createChannelFactory($hostname, $opts) - { - // no-op - } - - public function UnaryCall($channel, $method, $deserialize, $options) - { - $this->called = true; - return $this->mockCall; - } - - public function ServerStreamingCall($channel, $method, $deserialize, $options) - { - $this->called = true; - return $this->mockCall; - } - - public function ClientStreamingCall($channel, $method, $deserialize, $options) - { - // no-op - } - - public function BidiStreamingCall($channel, $method, $deserialize, $options) - { - // no-op - } - - public function wasCalled() - { - return $this->called; - } -} diff --git a/tests/Unit/Transport/Rest/JsonStreamDecoderTest.php b/tests/Unit/Transport/Rest/JsonStreamDecoderTest.php index 4b6e25b19..04a761940 100644 --- a/tests/Unit/Transport/Rest/JsonStreamDecoderTest.php +++ b/tests/Unit/Transport/Rest/JsonStreamDecoderTest.php @@ -115,7 +115,9 @@ public function buildResponseStreams() ]), ]; - $stream = function ($data) {return $this->messagesToStream($data);}; + $stream = function ($data) { + return $this->messagesToStream($data); + }; return [ [$operations, Operation::class, $stream($operations), /*readChunkSizeBytes*/ 10], [$operations, Operation::class, $stream($operations), /*readChunkSizeBytes*/ 1024], @@ -178,7 +180,6 @@ public function testJsonStreamDecoderBadClose($payload) try { // Just iterating the stream will throw the exception foreach ($decoder->decode() as $op) { - } } finally { $stream->close(); diff --git a/tests/Unit/Transport/RestTransportTest.php b/tests/Unit/Transport/RestTransportTest.php index 2a659473e..b4daa40c6 100644 --- a/tests/Unit/Transport/RestTransportTest.php +++ b/tests/Unit/Transport/RestTransportTest.php @@ -428,7 +428,7 @@ public function buildDataRest() { $uri = 'address.com'; $apiEndpoint = "$uri:443"; - $restConfigPath = __DIR__ . '/../testdata/test_service_rest_client_config.php'; + $restConfigPath = __DIR__ . '/../testdata/resources/test_service_rest_client_config.php'; $requestBuilder = new RequestBuilder($apiEndpoint, $restConfigPath); $httpHandler = [HttpHandlerFactory::build(), 'async']; return [ @@ -454,7 +454,7 @@ public function testClientCertSourceOptionValid() }; $transport = RestTransport::build( 'address.com:123', - __DIR__ . '/../testdata/test_service_rest_client_config.php', + __DIR__ . '/../testdata/resources/test_service_rest_client_config.php', ['clientCertSource' => $mockClientCertSource] ); @@ -467,8 +467,6 @@ public function testClientCertSourceOptionValid() public function testClientCertSourceOptionInvalid() { - self::requiresPhp7(); - $mockClientCertSource = 'foo'; $this->expectException(TypeError::class); @@ -476,7 +474,7 @@ public function testClientCertSourceOptionInvalid() RestTransport::build( 'address.com:123', - __DIR__ . '/../testdata/test_service_rest_client_config.php', + __DIR__ . '/../testdata/resources/test_service_rest_client_config.php', ['clientCertSource' => $mockClientCertSource] ); } @@ -493,7 +491,7 @@ public function testBuildInvalid($apiEndpoint, $restConfigPath, $args) public function buildInvalidData() { - $restConfigPath = __DIR__ . '/../testdata/test_service_rest_client_config.php'; + $restConfigPath = __DIR__ . '/../testdata/resources/test_service_rest_client_config.php'; return [ [ 'addresswithtoo:many:segments', diff --git a/tests/Unit/ValidationTraitTest.php b/tests/Unit/ValidationTraitTest.php index 310d57757..869316a3d 100644 --- a/tests/Unit/ValidationTraitTest.php +++ b/tests/Unit/ValidationTraitTest.php @@ -27,7 +27,9 @@ class ValidationTraitTest extends TestCase public function setUp(): void { - $this->stub = new ValidationTraitStub(); + $this->stub = new class () { + use ValidationTrait; + }; } public function testValidateMissingRequiredKey() @@ -79,8 +81,3 @@ public function testValidateValidArrayWithNotNull() $this->assertEquals($input, $arr); } } - -class ValidationTraitStub -{ - use ValidationTrait; -} diff --git a/tests/Unit/testdata/json-key-file.json b/tests/Unit/testdata/creds/json-key-file.json similarity index 100% rename from tests/Unit/testdata/json-key-file.json rename to tests/Unit/testdata/creds/json-key-file.json diff --git a/tests/Unit/testdata/mtls/.secureConnect/context_aware_metadata.json b/tests/Unit/testdata/creds/mtls/.secureConnect/context_aware_metadata.json similarity index 100% rename from tests/Unit/testdata/mtls/.secureConnect/context_aware_metadata.json rename to tests/Unit/testdata/creds/mtls/.secureConnect/context_aware_metadata.json diff --git a/tests/Unit/MyMessage.php b/tests/Unit/testdata/generated/MyMessage.php similarity index 100% rename from tests/Unit/MyMessage.php rename to tests/Unit/testdata/generated/MyMessage.php diff --git a/metadata/Google/ApiCore/Tests/Unit/Example.php b/tests/Unit/testdata/generated/metadata/Example.php similarity index 100% rename from metadata/Google/ApiCore/Tests/Unit/Example.php rename to tests/Unit/testdata/generated/metadata/Example.php diff --git a/tests/Unit/testdata/mocks/CustomOperation/CancelOperationRequest.php b/tests/Unit/testdata/mocks/CustomOperation/CancelOperationRequest.php new file mode 100644 index 000000000..f0b168fae --- /dev/null +++ b/tests/Unit/testdata/mocks/CustomOperation/CancelOperationRequest.php @@ -0,0 +1,20 @@ +name = $name; + $request->arg2 = $arg2; + $request->arg3 = $arg3; + + return $request; + } +} diff --git a/tests/Unit/testdata/mocks/CustomOperation/Client/NewSurfaceCustomOperationClient.php b/tests/Unit/testdata/mocks/CustomOperation/Client/NewSurfaceCustomOperationClient.php new file mode 100644 index 000000000..8128fc840 --- /dev/null +++ b/tests/Unit/testdata/mocks/CustomOperation/Client/NewSurfaceCustomOperationClient.php @@ -0,0 +1,23 @@ +name = $name; + $request->arg2 = $arg2; + $request->arg3 = $arg3; + + return $request; + } +} diff --git a/tests/Unit/testdata/mocks/CustomOperation/GetOperationRequest.php b/tests/Unit/testdata/mocks/CustomOperation/GetOperationRequest.php new file mode 100644 index 000000000..7e2783ccb --- /dev/null +++ b/tests/Unit/testdata/mocks/CustomOperation/GetOperationRequest.php @@ -0,0 +1,20 @@ +name = $name; + $request->arg2 = $arg2; + $request->arg3 = $arg3; + + return $request; + } +} diff --git a/tests/bootstrap_deprecated_grpc_interceptors.php b/tests/Unit/testdata/mocks/DeprecatedTestInterceptor.php similarity index 56% rename from tests/bootstrap_deprecated_grpc_interceptors.php rename to tests/Unit/testdata/mocks/DeprecatedTestInterceptor.php index 3d7b2eec7..cc625ae44 100644 --- a/tests/bootstrap_deprecated_grpc_interceptors.php +++ b/tests/Unit/testdata/mocks/DeprecatedTestInterceptor.php @@ -2,25 +2,9 @@ namespace Google\ApiCore\Tests\Unit\Transport; -use Google\ApiCore\Transport\Grpc\UnaryInterceptorInterface; use Grpc\Interceptor; -class TestUnaryInterceptor implements UnaryInterceptorInterface -{ - public function interceptUnaryUnary( - $method, - $argument, - $deserialize, - array $metadata, - array $options, - callable $continuation - ) { - $options['test-interceptor-insert'] = 'inserted-value'; - return $continuation($method, $argument, $deserialize, $metadata, $options); - } -} - -class TestInterceptor extends Interceptor +class DeprecatedTestInterceptor extends Interceptor { public function interceptUnaryUnary( $method, @@ -28,7 +12,7 @@ public function interceptUnaryUnary( $deserialize, array $metadata = [], array $options = [], - $continuation + $continuation = null ) { $options['test-interceptor-insert'] = 'inserted-value'; return $continuation($method, $argument, $deserialize, $metadata, $options); @@ -40,7 +24,7 @@ public function interceptUnaryStream( $deserialize, array $metadata = [], array $options = [], - $continuation + $continuation = null ) { $options['test-interceptor-insert'] = 'inserted-value'; return $continuation($method, $argument, $deserialize, $metadata, $options); diff --git a/tests/Unit/testdata/mocks/DeprecatedTestUnaryInterceptor.php b/tests/Unit/testdata/mocks/DeprecatedTestUnaryInterceptor.php new file mode 100644 index 000000000..0f89fc2a0 --- /dev/null +++ b/tests/Unit/testdata/mocks/DeprecatedTestUnaryInterceptor.php @@ -0,0 +1,20 @@ +name = $name; - $request->arg2 = $arg2; - $request->arg3 = $arg3; - - return $request; - } -} - -class GetOperationRequest extends BaseOperationRequest -{ -} - -class CancelOperationRequest extends BaseOperationRequest -{ -} - -class DeleteOperationRequest extends BaseOperationRequest -{ -} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 56c6f4c44..b1110074e 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -35,19 +35,6 @@ use Google\ApiCore\Testing\MessageAwareArrayComparator; use Google\ApiCore\Testing\ProtobufGPBEmptyComparator; use Google\ApiCore\Testing\ProtobufMessageComparator; -use Grpc\Interceptor; - -// This is a long line, but better not set some temporary variables that get picked up by something later -if ( - (new \ReflectionClass(Interceptor::class)) - ->getMethod('interceptUnaryUnary') - ->getParameters()[3] - ->getName() === 'metadata' -) { - require_once __DIR__ . '/bootstrap_deprecated_grpc_interceptors.php'; -} else { - require_once __DIR__ . '/bootstrap_grpc_interceptors.php'; -} date_default_timezone_set('UTC'); ini_set('error_reporting', E_ALL);