From 0afff0f74e88e5097a2be33e6004b0367ecc7b6d Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Wed, 29 May 2024 19:21:20 -0700 Subject: [PATCH] feat: new surface custom LROs (#712) --- composer.json | 1 + src/CodeGenerator.php | 7 +- src/Generation/GapicClientV2Generator.php | 3 +- src/Generation/ResourcesGenerator.php | 11 +- src/Generation/ServiceDetails.php | 15 +- .../src/V1/Gapic/AddressesGapicClient.php | 3 + .../resources/addresses_descriptor_config.php | 6 + .../out/src/Gapic/CustomLroGapicClient.php | 3 + .../custom_lro_descriptor_config.php | 3 + .../CustomLroNew/custom_lro_new.proto | 106 +++++++ .../CustomLroNew/custom_lro_new.yaml | 28 ++ .../GetOperationRequest.build.txt | 19 ++ .../samples/CustomLroClient/create_foo.php | 80 +++++ .../CustomLroOperationsClient/cancel.php | 64 ++++ .../CustomLroOperationsClient/delete.php | 64 ++++ .../samples/CustomLroOperationsClient/get.php | 77 +++++ .../out/src/Client/CustomLroClient.php | 266 ++++++++++++++++ .../src/Client/CustomLroOperationsClient.php | 240 ++++++++++++++ .../CustomLroNew/out/src/gapic_metadata.json | 47 +++ .../resources/custom_lro_client_config.json | 31 ++ .../custom_lro_descriptor_config.php | 50 +++ .../custom_lro_operations_client_config.json | 41 +++ ...ustom_lro_operations_descriptor_config.php | 40 +++ ...stom_lro_operations_rest_client_config.php | 46 +++ .../custom_lro_rest_client_config.php | 53 ++++ .../tests/Unit/Client/CustomLroClientTest.php | 251 +++++++++++++++ .../Client/CustomLroOperationsClientTest.php | 298 ++++++++++++++++++ tests/Unit/ProtoTests/GoldenUpdateMain.php | 9 +- tests/Unit/autoload.php | 14 +- 29 files changed, 1864 insertions(+), 12 deletions(-) create mode 100644 tests/Unit/ProtoTests/CustomLroNew/custom_lro_new.proto create mode 100644 tests/Unit/ProtoTests/CustomLroNew/custom_lro_new.yaml create mode 100644 tests/Unit/ProtoTests/CustomLroNew/out/fragments/Testing/CustomLroNew/GetOperationRequest.build.txt create mode 100644 tests/Unit/ProtoTests/CustomLroNew/out/samples/CustomLroClient/create_foo.php create mode 100644 tests/Unit/ProtoTests/CustomLroNew/out/samples/CustomLroOperationsClient/cancel.php create mode 100644 tests/Unit/ProtoTests/CustomLroNew/out/samples/CustomLroOperationsClient/delete.php create mode 100644 tests/Unit/ProtoTests/CustomLroNew/out/samples/CustomLroOperationsClient/get.php create mode 100644 tests/Unit/ProtoTests/CustomLroNew/out/src/Client/CustomLroClient.php create mode 100644 tests/Unit/ProtoTests/CustomLroNew/out/src/Client/CustomLroOperationsClient.php create mode 100644 tests/Unit/ProtoTests/CustomLroNew/out/src/gapic_metadata.json create mode 100644 tests/Unit/ProtoTests/CustomLroNew/out/src/resources/custom_lro_client_config.json create mode 100644 tests/Unit/ProtoTests/CustomLroNew/out/src/resources/custom_lro_descriptor_config.php create mode 100644 tests/Unit/ProtoTests/CustomLroNew/out/src/resources/custom_lro_operations_client_config.json create mode 100644 tests/Unit/ProtoTests/CustomLroNew/out/src/resources/custom_lro_operations_descriptor_config.php create mode 100644 tests/Unit/ProtoTests/CustomLroNew/out/src/resources/custom_lro_operations_rest_client_config.php create mode 100644 tests/Unit/ProtoTests/CustomLroNew/out/src/resources/custom_lro_rest_client_config.php create mode 100644 tests/Unit/ProtoTests/CustomLroNew/out/tests/Unit/Client/CustomLroClientTest.php create mode 100644 tests/Unit/ProtoTests/CustomLroNew/out/tests/Unit/Client/CustomLroOperationsClientTest.php diff --git a/composer.json b/composer.json index ad33f94b8..b352e35f5 100644 --- a/composer.json +++ b/composer.json @@ -23,6 +23,7 @@ "Testing\\BasicPaginated\\": "tests/Unit/ProtoTests/BasicPaginated/out/src", "Testing\\BasicServerStreaming\\": "tests/Unit/ProtoTests/BasicServerStreaming/out/src", "Testing\\CustomLro\\": "tests/Unit/ProtoTests/CustomLro/out/src", + "Testing\\CustomLroNew\\": "tests/Unit/ProtoTests/CustomLroNew/out/src", "Testing\\Deprecated\\": "tests/Unit/ProtoTests/DeprecatedService/out/src", "Testing\\DisableSnippets\\": "tests/Unit/ProtoTests/DisableSnippets/out/src", "Testing\\GrpcServiceConfig\\": "tests/Unit/ProtoTests/GrpcServiceConfig/out/src", diff --git a/src/CodeGenerator.php b/src/CodeGenerator.php index f313bf028..6a105e598 100644 --- a/src/CodeGenerator.php +++ b/src/CodeGenerator.php @@ -267,7 +267,8 @@ public static function generate( $result[] = $file; } - if ($transportType === Transport::REST) { + // GAPIC enums are only needed for legacy DIREGAPICs + if ($transportType === Transport::REST && $migrationMode !== MigrationMode::NEW_SURFACE_ONLY) { foreach (static::generateEnumConstants( $byPackage, $catalog, @@ -404,14 +405,14 @@ private static function generateServices( $code = ResourcesGenerator::generateDescriptorConfig($service, $gapicYamlConfig); $code = Formatter::format($code); yield ["src/{$version}resources/{$service->descriptorConfigFilename}", $code]; - + // Resource: rest_client_config.php if ($service->transportType !== Transport::GRPC) { $code = ResourcesGenerator::generateRestConfig($service, $serviceYamlConfig, $numericEnums); $code = Formatter::format($code); yield ["src/{$version}resources/{$service->restConfigFilename}", $code]; } - + // Resource: client_config.json $json = ResourcesGenerator::generateClientConfig($service, $gapicYamlConfig, $grpcServiceConfig); yield ["src/{$version}resources/{$service->clientConfigFilename}", $json]; diff --git a/src/Generation/GapicClientV2Generator.php b/src/Generation/GapicClientV2Generator.php index 718405d6f..d69a71dd7 100644 --- a/src/Generation/GapicClientV2Generator.php +++ b/src/Generation/GapicClientV2Generator.php @@ -505,7 +505,8 @@ private function getClientDefaults(): PhpClassMember ]); } - if ($this->serviceDetails->hasCustomOp) { + if ($this->serviceDetails->hasCustomOp && $this->serviceDetails->migrationMode !== MigrationMode::NEW_SURFACE_ONLY) { + // This is only needed for legacy custom operations clients. $clientDefaultValues['operationsClientClass'] = AST::access( $this->ctx->type($this->serviceDetails->customOperationServiceClientType), AST::CLS diff --git a/src/Generation/ResourcesGenerator.php b/src/Generation/ResourcesGenerator.php index 8286cde28..bd11e1b43 100644 --- a/src/Generation/ResourcesGenerator.php +++ b/src/Generation/ResourcesGenerator.php @@ -203,13 +203,20 @@ public static function customOperationDescriptor(ServiceDetails $serviceDetails, ->map(fn ($x) => $x->getter->getName())->toArray() ), 'getOperationMethod' => $method->operationPollingMethod->methodName, - 'cancelOperationMethod' => $serviceDetails->hasCustomOpCancel ? 'cancel': AST::NULL, - 'deleteOperationMethod' => $serviceDetails->hasCustomOpDelete ? 'delete': AST::NULL, + 'cancelOperationMethod' => $serviceDetails->customOpCancel ? 'cancel': AST::NULL, + 'deleteOperationMethod' => $serviceDetails->customOpDelete ? 'delete': AST::NULL, 'operationErrorCodeMethod' => $errorCode->getter->getName(), 'operationErrorMessageMethod' => $errorMessage->getter->getName(), 'operationNameMethod' => $name->getter->getName(), 'operationStatusMethod' => $status->getter->getName(), 'operationStatusDoneValue' => $doneValue, + 'getOperationRequest' => $method->operationPollingMethod->requestType->getFullname(), + 'cancelOperationRequest' => $serviceDetails->customOpCancel + ? $serviceDetails->customOpCancel->requestType->getFullname() + : AST::NULL, + 'deleteOperationRequest' => $serviceDetails->customOpDelete + ? $serviceDetails->customOpDelete->requestType->getFullname() + : AST::NULL, ]); } diff --git a/src/Generation/ServiceDetails.php b/src/Generation/ServiceDetails.php index 23d26c684..6e5b3184a 100644 --- a/src/Generation/ServiceDetails.php +++ b/src/Generation/ServiceDetails.php @@ -121,9 +121,9 @@ class ServiceDetails /** @var Type *Readonly* The Type of the service's operations_service client. */ public Type $customOperationServiceClientType; - public bool $hasCustomOpDelete; + public ?MethodDetails $customOpDelete; - public bool $hasCustomOpCancel; + public ?MethodDetails $customOpCancel; /** @var bool *Readonly* Whether this service makes use of resources. */ public bool $hasResources; @@ -209,9 +209,16 @@ public function __construct( // but for simplicity we will assume they are all the same and use the first. $this->customOperationService = $customOperations[0]->operationService; + // Find if there is a "Delete" or "Cancel" method on the operation service. + // If so, assume these are the "delete" and "cancel" methods respectively. + $operationCancel = Vector::new($this->customOperationService->getMethod()) + ->filter(fn ($x) => $x->getName() === 'Cancel'); + $operationDelete = Vector::new($this->customOperationService->getMethod()) + ->filter(fn ($x) => $x->getName() === 'Delete'); + // Determine if the operation service implements the Cancel and/or the Delete RPCs. - $this->hasCustomOpCancel = Vector::new($this->customOperationService->getMethod())->any(fn ($x) => $x->getName() === 'Cancel'); - $this->hasCustomOpDelete = Vector::new($this->customOperationService->getMethod())->any(fn ($x) => $x->getName() === 'Delete'); + $this->customOpCancel = $operationCancel->count() !== 0 ? MethodDetails::create($this, $operationCancel->firstOrNull()) : null; + $this->customOpDelete = $operationDelete->count() !== 0 ? MethodDetails::create($this, $operationDelete->firstOrNull()) : null; // Assuming the custom operations service client is in the same namespace as the client to generate. $cname = $this->customOperationService->getName() . 'Client'; diff --git a/tests/Integration/goldens/compute_small/src/V1/Gapic/AddressesGapicClient.php b/tests/Integration/goldens/compute_small/src/V1/Gapic/AddressesGapicClient.php index 09e9d94d9..9bf1dd263 100644 --- a/tests/Integration/goldens/compute_small/src/V1/Gapic/AddressesGapicClient.php +++ b/tests/Integration/goldens/compute_small/src/V1/Gapic/AddressesGapicClient.php @@ -167,6 +167,9 @@ private function getDefaultOperationDescriptor() 'operationNameMethod' => 'getName', 'operationStatusMethod' => 'getStatus', 'operationStatusDoneValue' => \Google\Cloud\Compute\V1\Operation\Status::DONE, + 'getOperationRequest' => '\Google\Cloud\Compute\V1\GetRegionOperationRequest', + 'cancelOperationRequest' => null, + 'deleteOperationRequest' => null, ]; } diff --git a/tests/Integration/goldens/compute_small/src/V1/resources/addresses_descriptor_config.php b/tests/Integration/goldens/compute_small/src/V1/resources/addresses_descriptor_config.php index ebd410d83..9758ff43b 100644 --- a/tests/Integration/goldens/compute_small/src/V1/resources/addresses_descriptor_config.php +++ b/tests/Integration/goldens/compute_small/src/V1/resources/addresses_descriptor_config.php @@ -37,6 +37,9 @@ 'operationNameMethod' => 'getName', 'operationStatusMethod' => 'getStatus', 'operationStatusDoneValue' => \Google\Cloud\Compute\V1\Operation\Status::DONE, + 'getOperationRequest' => '\Google\Cloud\Compute\V1\GetRegionOperationRequest', + 'cancelOperationRequest' => null, + 'deleteOperationRequest' => null, ], ], 'Insert' => [ @@ -53,6 +56,9 @@ 'operationNameMethod' => 'getName', 'operationStatusMethod' => 'getStatus', 'operationStatusDoneValue' => \Google\Cloud\Compute\V1\Operation\Status::DONE, + 'getOperationRequest' => '\Google\Cloud\Compute\V1\GetRegionOperationRequest', + 'cancelOperationRequest' => null, + 'deleteOperationRequest' => null, ], ], 'AggregatedList' => [ diff --git a/tests/Unit/ProtoTests/CustomLro/out/src/Gapic/CustomLroGapicClient.php b/tests/Unit/ProtoTests/CustomLro/out/src/Gapic/CustomLroGapicClient.php index 34b9403b1..a36d4dbfa 100644 --- a/tests/Unit/ProtoTests/CustomLro/out/src/Gapic/CustomLroGapicClient.php +++ b/tests/Unit/ProtoTests/CustomLro/out/src/Gapic/CustomLroGapicClient.php @@ -163,6 +163,9 @@ private function getDefaultOperationDescriptor() 'operationNameMethod' => 'getName', 'operationStatusMethod' => 'getStatus', 'operationStatusDoneValue' => \Testing\CustomLro\CustomOperationResponse\Status::DONE, + 'getOperationRequest' => '\Testing\CustomLro\GetOperationRequest', + 'cancelOperationRequest' => '\Testing\CustomLro\CancelOperationRequest', + 'deleteOperationRequest' => '\Testing\CustomLro\DeleteOperationRequest', ]; } diff --git a/tests/Unit/ProtoTests/CustomLro/out/src/resources/custom_lro_descriptor_config.php b/tests/Unit/ProtoTests/CustomLro/out/src/resources/custom_lro_descriptor_config.php index 3f5b3b9c5..31bb1d0ee 100644 --- a/tests/Unit/ProtoTests/CustomLro/out/src/resources/custom_lro_descriptor_config.php +++ b/tests/Unit/ProtoTests/CustomLro/out/src/resources/custom_lro_descriptor_config.php @@ -38,6 +38,9 @@ 'operationNameMethod' => 'getName', 'operationStatusMethod' => 'getStatus', 'operationStatusDoneValue' => \Testing\CustomLro\CustomOperationResponse\Status::DONE, + 'getOperationRequest' => '\Testing\CustomLro\GetOperationRequest', + 'cancelOperationRequest' => '\Testing\CustomLro\CancelOperationRequest', + 'deleteOperationRequest' => '\Testing\CustomLro\DeleteOperationRequest', ], ], ], diff --git a/tests/Unit/ProtoTests/CustomLroNew/custom_lro_new.proto b/tests/Unit/ProtoTests/CustomLroNew/custom_lro_new.proto new file mode 100644 index 000000000..cbf2aad78 --- /dev/null +++ b/tests/Unit/ProtoTests/CustomLroNew/custom_lro_new.proto @@ -0,0 +1,106 @@ +syntax = "proto3"; + +import "google/api/annotations.proto"; +import "google/api/client.proto"; +import "google/api/field_behavior.proto"; +import "google/cloud/extended_operations.proto"; +import "google/protobuf/empty.proto"; + +package testing.customlronew; + +option php_namespace = "Testing\\CustomLroNew"; + +service CustomLro { + option (google.api.default_host) = "customlro.example.com"; + option (google.api.oauth_scopes) = "scope1,scope2"; + rpc CreateFoo(CreateFooRequest) returns (CustomOperationResponse) { + option (google.api.http) = { + post: "/foo" + body: "*" + }; + option (google.cloud.operation_service) = "CustomLroOperations"; + } +} + +service CustomLroOperations { + option (google.api.default_host) = "customlro.example.com"; + option (google.api.oauth_scopes) = "scope1,scope2"; + rpc Get(GetOperationRequest) returns (CustomOperationResponse) { + option (google.api.http) = { + get: "/operation" + }; + option (google.cloud.operation_polling_method) = true; + option (google.api.method_signature) = 'operation,project,region,foo'; + } + + rpc Cancel(CancelOperationRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + patch: "/operation" + }; + } + + rpc Delete(DeleteOperationRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + delete: "/operation" + }; + } +} + +message CustomOperationResponse { + optional string name = 1 [(google.cloud.operation_field) = NAME]; + + optional string http_error_message = 2 [(google.cloud.operation_field) = ERROR_MESSAGE]; + + optional int32 http_error_status_code = 3 [(google.cloud.operation_field) = ERROR_CODE]; + + enum Status { + // A value indicating that the enum field is not set. + UNDEFINED_STATUS = 0; + + DONE = 1; + + PENDING = 2; + + RUNNING = 3; + } + + optional Status status = 4 [(google.cloud.operation_field) = STATUS]; +} + +message CreateFooRequest { + string foo = 1 [(google.cloud.operation_request_field) = "foo"]; + + string project = 2 [ + (google.cloud.operation_request_field) = "project", + (google.api.field_behavior) = REQUIRED]; + + string region = 3 [ + (google.cloud.operation_request_field) = "region", + (google.api.field_behavior) = REQUIRED]; +} + +message GetOperationRequest { + // Name of the Operations resource to return. + string operation = 1 [ + (google.cloud.operation_response_field) = "name", + (google.api.field_behavior) = REQUIRED]; + + // Project ID for this request. + string project = 2 [(google.api.field_behavior) = REQUIRED]; + + // Name of the region for this request. + string region = 3 [(google.api.field_behavior) = REQUIRED]; + + // The foo from the initial request. + string foo = 4 [(google.api.field_behavior) = REQUIRED]; +} + +message CancelOperationRequest { + // Name of th Operations resource to cancel. + string operation = 1 [(google.api.field_behavior) = REQUIRED]; +} + +message DeleteOperationRequest { + // Name of th Operations resource to delete. + string operation = 1 [(google.api.field_behavior) = REQUIRED]; +} diff --git a/tests/Unit/ProtoTests/CustomLroNew/custom_lro_new.yaml b/tests/Unit/ProtoTests/CustomLroNew/custom_lro_new.yaml new file mode 100644 index 000000000..f1a0d3e01 --- /dev/null +++ b/tests/Unit/ProtoTests/CustomLroNew/custom_lro_new.yaml @@ -0,0 +1,28 @@ +type: google.api.Service +config_version: 1 +name: customlro.example.com +title: Custom LRO Example API + +# Included protobuf APIs +apis: +- name: testing.customlro.CustomLro +- name: testing.customlro.CustomLroOperations + +# Documentation section +documentation: + summary: + A simple Custom LRO Example API. + +# Auth section +authentication: + rules: + - selector: '*' + oauth: + canonical_scopes: scope1, + scope2 + +# Backend section +backend: + rules: + - selector: testing.customlro.CustomLro.CreateFoo + deadline: 1.0 diff --git a/tests/Unit/ProtoTests/CustomLroNew/out/fragments/Testing/CustomLroNew/GetOperationRequest.build.txt b/tests/Unit/ProtoTests/CustomLroNew/out/fragments/Testing/CustomLroNew/GetOperationRequest.build.txt new file mode 100644 index 000000000..d6abd968d --- /dev/null +++ b/tests/Unit/ProtoTests/CustomLroNew/out/fragments/Testing/CustomLroNew/GetOperationRequest.build.txt @@ -0,0 +1,19 @@ + + /** + * @param string $operation Name of the Operations resource to return. + * @param string $project Project ID for this request. + * @param string $region Name of the region for this request. + * @param string $foo The foo from the initial request. + * + * @return \Testing\CustomLroNew\GetOperationRequest + * + * @experimental + */ + public static function build(string $operation, string $project, string $region, string $foo): self + { + return (new self()) + ->setOperation($operation) + ->setProject($project) + ->setRegion($region) + ->setFoo($foo); + } \ No newline at end of file diff --git a/tests/Unit/ProtoTests/CustomLroNew/out/samples/CustomLroClient/create_foo.php b/tests/Unit/ProtoTests/CustomLroNew/out/samples/CustomLroClient/create_foo.php new file mode 100644 index 000000000..505044160 --- /dev/null +++ b/tests/Unit/ProtoTests/CustomLroNew/out/samples/CustomLroClient/create_foo.php @@ -0,0 +1,80 @@ +setProject($project) + ->setRegion($region); + + // Call the API and handle any network failures. + try { + /** @var OperationResponse $response */ + $response = $customLroClient->createFoo($request); + $response->pollUntilComplete(); + + if ($response->operationSucceeded()) { + printf('Operation completed successfully.' . PHP_EOL); + } else { + /** @var Status $error */ + $error = $response->getError(); + printf('Operation failed with error data: %s' . PHP_EOL, $error->serializeToJsonString()); + } + } catch (ApiException $ex) { + printf('Call failed with message: %s' . PHP_EOL, $ex->getMessage()); + } +} + +/** + * Helper to execute the sample. + * + * This sample has been automatically generated and should be regarded as a code + * template only. It will require modifications to work: + * - It may require correct/in-range values for request initialization. + * - It may require specifying regional endpoints when creating the service client, + * please see the apiEndpoint client configuration option for more details. + */ +function callSample(): void +{ + $project = '[PROJECT]'; + $region = '[REGION]'; + + create_foo_sample($project, $region); +} +// [END customlro_generated_CustomLro_CreateFoo_sync] diff --git a/tests/Unit/ProtoTests/CustomLroNew/out/samples/CustomLroOperationsClient/cancel.php b/tests/Unit/ProtoTests/CustomLroNew/out/samples/CustomLroOperationsClient/cancel.php new file mode 100644 index 000000000..7255ac2da --- /dev/null +++ b/tests/Unit/ProtoTests/CustomLroNew/out/samples/CustomLroOperationsClient/cancel.php @@ -0,0 +1,64 @@ +setOperation($operation); + + // Call the API and handle any network failures. + try { + $customLroOperationsClient->cancel($request); + printf('Call completed successfully.' . PHP_EOL); + } catch (ApiException $ex) { + printf('Call failed with message: %s' . PHP_EOL, $ex->getMessage()); + } +} + +/** + * Helper to execute the sample. + * + * This sample has been automatically generated and should be regarded as a code + * template only. It will require modifications to work: + * - It may require correct/in-range values for request initialization. + * - It may require specifying regional endpoints when creating the service client, + * please see the apiEndpoint client configuration option for more details. + */ +function callSample(): void +{ + $operation = '[OPERATION]'; + + cancel_sample($operation); +} +// [END customlro_generated_CustomLroOperations_Cancel_sync] diff --git a/tests/Unit/ProtoTests/CustomLroNew/out/samples/CustomLroOperationsClient/delete.php b/tests/Unit/ProtoTests/CustomLroNew/out/samples/CustomLroOperationsClient/delete.php new file mode 100644 index 000000000..c69af7806 --- /dev/null +++ b/tests/Unit/ProtoTests/CustomLroNew/out/samples/CustomLroOperationsClient/delete.php @@ -0,0 +1,64 @@ +setOperation($operation); + + // Call the API and handle any network failures. + try { + $customLroOperationsClient->delete($request); + printf('Call completed successfully.' . PHP_EOL); + } catch (ApiException $ex) { + printf('Call failed with message: %s' . PHP_EOL, $ex->getMessage()); + } +} + +/** + * Helper to execute the sample. + * + * This sample has been automatically generated and should be regarded as a code + * template only. It will require modifications to work: + * - It may require correct/in-range values for request initialization. + * - It may require specifying regional endpoints when creating the service client, + * please see the apiEndpoint client configuration option for more details. + */ +function callSample(): void +{ + $operation = '[OPERATION]'; + + delete_sample($operation); +} +// [END customlro_generated_CustomLroOperations_Delete_sync] diff --git a/tests/Unit/ProtoTests/CustomLroNew/out/samples/CustomLroOperationsClient/get.php b/tests/Unit/ProtoTests/CustomLroNew/out/samples/CustomLroOperationsClient/get.php new file mode 100644 index 000000000..7ecd752ea --- /dev/null +++ b/tests/Unit/ProtoTests/CustomLroNew/out/samples/CustomLroOperationsClient/get.php @@ -0,0 +1,77 @@ +setOperation($operation) + ->setProject($project) + ->setRegion($region) + ->setFoo($foo); + + // Call the API and handle any network failures. + try { + /** @var CustomOperationResponse $response */ + $response = $customLroOperationsClient->get($request); + printf('Response data: %s' . PHP_EOL, $response->serializeToJsonString()); + } catch (ApiException $ex) { + printf('Call failed with message: %s' . PHP_EOL, $ex->getMessage()); + } +} + +/** + * Helper to execute the sample. + * + * This sample has been automatically generated and should be regarded as a code + * template only. It will require modifications to work: + * - It may require correct/in-range values for request initialization. + * - It may require specifying regional endpoints when creating the service client, + * please see the apiEndpoint client configuration option for more details. + */ +function callSample(): void +{ + $operation = '[OPERATION]'; + $project = '[PROJECT]'; + $region = '[REGION]'; + $foo = '[FOO]'; + + get_sample($operation, $project, $region, $foo); +} +// [END customlro_generated_CustomLroOperations_Get_sync] diff --git a/tests/Unit/ProtoTests/CustomLroNew/out/src/Client/CustomLroClient.php b/tests/Unit/ProtoTests/CustomLroNew/out/src/Client/CustomLroClient.php new file mode 100644 index 000000000..cb6893af5 --- /dev/null +++ b/tests/Unit/ProtoTests/CustomLroNew/out/src/Client/CustomLroClient.php @@ -0,0 +1,266 @@ + self::SERVICE_NAME, + 'apiEndpoint' => self::SERVICE_ADDRESS . ':' . self::DEFAULT_SERVICE_PORT, + 'clientConfig' => __DIR__ . '/../resources/custom_lro_client_config.json', + 'descriptorsConfigPath' => __DIR__ . '/../resources/custom_lro_descriptor_config.php', + 'credentialsConfig' => [ + 'defaultScopes' => self::$serviceScopes, + 'useJwtAccessWithScope' => false, + ], + 'transportConfig' => [ + 'rest' => [ + 'restClientConfigPath' => __DIR__ . '/../resources/custom_lro_rest_client_config.php', + ], + ], + ]; + } + + /** Implements GapicClientTrait::defaultTransport. */ + private static function defaultTransport() + { + return 'rest'; + } + + /** Implements ClientOptionsTrait::supportedTransports. */ + private static function supportedTransports() + { + return [ + 'rest', + ]; + } + + /** + * Return an CustomLroOperationsClient object with the same endpoint as $this. + * + * @return CustomLroOperationsClient + */ + public function getOperationsClient() + { + return $this->operationsClient; + } + + /** Return the default longrunning operation descriptor config. */ + private function getDefaultOperationDescriptor() + { + return [ + 'additionalArgumentMethods' => [ + 'getProject', + 'getRegion', + 'getFoo', + ], + 'getOperationMethod' => 'get', + 'cancelOperationMethod' => 'cancel', + 'deleteOperationMethod' => 'delete', + 'operationErrorCodeMethod' => 'getHttpErrorStatusCode', + 'operationErrorMessageMethod' => 'getHttpErrorMessage', + 'operationNameMethod' => 'getName', + 'operationStatusMethod' => 'getStatus', + 'operationStatusDoneValue' => \Testing\CustomLroNew\CustomOperationResponse\Status::DONE, + 'getOperationRequest' => '\Testing\CustomLroNew\GetOperationRequest', + 'cancelOperationRequest' => '\Testing\CustomLroNew\CancelOperationRequest', + 'deleteOperationRequest' => '\Testing\CustomLroNew\DeleteOperationRequest', + ]; + } + + /** + * Resume an existing long running operation that was previously started by a long + * running API method. If $methodName is not provided, or does not match a long + * running API method, then the operation can still be resumed, but the + * OperationResponse object will not deserialize the final response. + * + * @param string $operationName The name of the long running operation + * @param string $methodName The name of the method used to start the operation + * + * @return OperationResponse + */ + public function resumeOperation($operationName, $methodName = null) + { + $options = isset($this->descriptors[$methodName]['longRunning']) ? $this->descriptors[$methodName]['longRunning'] : $this->getDefaultOperationDescriptor(); + $operation = new OperationResponse($operationName, $this->getOperationsClient(), $options); + $operation->reload(); + return $operation; + } + + /** + * Create the default operation client for the service. + * + * @param array $options ClientOptions for the client. + * + * @return CustomLroOperationsClient + */ + private function createOperationsClient(array $options) + { + // Unset client-specific configuration options + unset($options['serviceName'], $options['clientConfig'], $options['descriptorsConfigPath']); + + if (isset($options['operationsClient'])) { + return $options['operationsClient']; + } + + return new CustomLroOperationsClient($options); + } + + /** + * Constructor. + * + * @param array $options { + * Optional. Options for configuring the service API wrapper. + * + * @type string $apiEndpoint + * The address of the API remote host. May optionally include the port, formatted + * as ":". Default 'customlro.example.com:443'. + * @type string|array|FetchAuthTokenInterface|CredentialsWrapper $credentials + * The credentials to be used by the client to authorize API calls. This option + * accepts either a path to a credentials file, or a decoded credentials file as a + * PHP array. + * *Advanced usage*: In addition, this option can also accept a pre-constructed + * {@see \Google\Auth\FetchAuthTokenInterface} object or + * {@see \Google\ApiCore\CredentialsWrapper} object. Note that when one of these + * objects are provided, any settings in $credentialsConfig will be ignored. + * @type array $credentialsConfig + * Options used to configure credentials, including auth token caching, for the + * client. For a full list of supporting configuration options, see + * {@see \Google\ApiCore\CredentialsWrapper::build()} . + * @type bool $disableRetries + * Determines whether or not retries defined by the client configuration should be + * disabled. Defaults to `false`. + * @type string|array $clientConfig + * Client method configuration, including retry settings. This option can be either + * a path to a JSON file, or a PHP array containing the decoded JSON data. By + * default this settings points to the default client config file, which is + * provided in the resources folder. + * @type string|TransportInterface $transport + * The transport used for executing network requests. At the moment, supports only + * `rest`. *Advanced usage*: Additionally, it is possible to pass in an already + * instantiated {@see \Google\ApiCore\Transport\TransportInterface} object. Note + * that when this object is provided, any settings in $transportConfig, and any + * $apiEndpoint setting, will be ignored. + * @type array $transportConfig + * Configuration options that will be used to construct the transport. Options for + * each supported transport type should be passed in a key for that transport. For + * example: + * $transportConfig = [ + * 'rest' => [...], + * ]; + * See the {@see \Google\ApiCore\Transport\RestTransport::build()} method for the + * supported options. + * @type callable $clientCertSource + * A callable which returns the client cert as a string. This can be used to + * provide a certificate and private key to the transport layer for mTLS. + * } + * + * @throws ValidationException + */ + public function __construct(array $options = []) + { + $clientOptions = $this->buildClientOptions($options); + $this->setClientOptions($clientOptions); + $this->operationsClient = $this->createOperationsClient($clientOptions); + } + + /** Handles execution of the async variants for each documented method. */ + public function __call($method, $args) + { + if (substr($method, -5) !== 'Async') { + trigger_error('Call to undefined method ' . __CLASS__ . "::$method()", E_USER_ERROR); + } + + array_unshift($args, substr($method, 0, -5)); + return call_user_func_array([$this, 'startAsyncCall'], $args); + } + + /** + * The async variant is {@see CustomLroClient::createFooAsync()} . + * + * @example samples/CustomLroClient/create_foo.php + * + * @param CreateFooRequest $request A request to house fields associated with the call. + * @param array $callOptions { + * Optional. + * + * @type RetrySettings|array $retrySettings + * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an + * associative array of retry settings parameters. See the documentation on + * {@see RetrySettings} for example usage. + * } + * + * @return OperationResponse + * + * @throws ApiException Thrown if the API call fails. + */ + public function createFoo(CreateFooRequest $request, array $callOptions = []): OperationResponse + { + return $this->startApiCall('CreateFoo', $request, $callOptions)->wait(); + } +} diff --git a/tests/Unit/ProtoTests/CustomLroNew/out/src/Client/CustomLroOperationsClient.php b/tests/Unit/ProtoTests/CustomLroNew/out/src/Client/CustomLroOperationsClient.php new file mode 100644 index 000000000..e08c343d8 --- /dev/null +++ b/tests/Unit/ProtoTests/CustomLroNew/out/src/Client/CustomLroOperationsClient.php @@ -0,0 +1,240 @@ + self::SERVICE_NAME, + 'apiEndpoint' => self::SERVICE_ADDRESS . ':' . self::DEFAULT_SERVICE_PORT, + 'clientConfig' => __DIR__ . '/../resources/custom_lro_operations_client_config.json', + 'descriptorsConfigPath' => __DIR__ . '/../resources/custom_lro_operations_descriptor_config.php', + 'credentialsConfig' => [ + 'defaultScopes' => self::$serviceScopes, + 'useJwtAccessWithScope' => false, + ], + 'transportConfig' => [ + 'rest' => [ + 'restClientConfigPath' => __DIR__ . '/../resources/custom_lro_operations_rest_client_config.php', + ], + ], + ]; + } + + /** Implements GapicClientTrait::defaultTransport. */ + private static function defaultTransport() + { + return 'rest'; + } + + /** Implements ClientOptionsTrait::supportedTransports. */ + private static function supportedTransports() + { + return [ + 'rest', + ]; + } + + /** + * Constructor. + * + * @param array $options { + * Optional. Options for configuring the service API wrapper. + * + * @type string $apiEndpoint + * The address of the API remote host. May optionally include the port, formatted + * as ":". Default 'customlro.example.com:443'. + * @type string|array|FetchAuthTokenInterface|CredentialsWrapper $credentials + * The credentials to be used by the client to authorize API calls. This option + * accepts either a path to a credentials file, or a decoded credentials file as a + * PHP array. + * *Advanced usage*: In addition, this option can also accept a pre-constructed + * {@see \Google\Auth\FetchAuthTokenInterface} object or + * {@see \Google\ApiCore\CredentialsWrapper} object. Note that when one of these + * objects are provided, any settings in $credentialsConfig will be ignored. + * @type array $credentialsConfig + * Options used to configure credentials, including auth token caching, for the + * client. For a full list of supporting configuration options, see + * {@see \Google\ApiCore\CredentialsWrapper::build()} . + * @type bool $disableRetries + * Determines whether or not retries defined by the client configuration should be + * disabled. Defaults to `false`. + * @type string|array $clientConfig + * Client method configuration, including retry settings. This option can be either + * a path to a JSON file, or a PHP array containing the decoded JSON data. By + * default this settings points to the default client config file, which is + * provided in the resources folder. + * @type string|TransportInterface $transport + * The transport used for executing network requests. At the moment, supports only + * `rest`. *Advanced usage*: Additionally, it is possible to pass in an already + * instantiated {@see \Google\ApiCore\Transport\TransportInterface} object. Note + * that when this object is provided, any settings in $transportConfig, and any + * $apiEndpoint setting, will be ignored. + * @type array $transportConfig + * Configuration options that will be used to construct the transport. Options for + * each supported transport type should be passed in a key for that transport. For + * example: + * $transportConfig = [ + * 'rest' => [...], + * ]; + * See the {@see \Google\ApiCore\Transport\RestTransport::build()} method for the + * supported options. + * @type callable $clientCertSource + * A callable which returns the client cert as a string. This can be used to + * provide a certificate and private key to the transport layer for mTLS. + * } + * + * @throws ValidationException + */ + public function __construct(array $options = []) + { + $clientOptions = $this->buildClientOptions($options); + $this->setClientOptions($clientOptions); + } + + /** Handles execution of the async variants for each documented method. */ + public function __call($method, $args) + { + if (substr($method, -5) !== 'Async') { + trigger_error('Call to undefined method ' . __CLASS__ . "::$method()", E_USER_ERROR); + } + + array_unshift($args, substr($method, 0, -5)); + return call_user_func_array([$this, 'startAsyncCall'], $args); + } + + /** + * The async variant is {@see CustomLroOperationsClient::cancelAsync()} . + * + * @example samples/CustomLroOperationsClient/cancel.php + * + * @param CancelOperationRequest $request A request to house fields associated with the call. + * @param array $callOptions { + * Optional. + * + * @type RetrySettings|array $retrySettings + * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an + * associative array of retry settings parameters. See the documentation on + * {@see RetrySettings} for example usage. + * } + * + * @throws ApiException Thrown if the API call fails. + */ + public function cancel(CancelOperationRequest $request, array $callOptions = []): void + { + $this->startApiCall('Cancel', $request, $callOptions)->wait(); + } + + /** + * The async variant is {@see CustomLroOperationsClient::deleteAsync()} . + * + * @example samples/CustomLroOperationsClient/delete.php + * + * @param DeleteOperationRequest $request A request to house fields associated with the call. + * @param array $callOptions { + * Optional. + * + * @type RetrySettings|array $retrySettings + * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an + * associative array of retry settings parameters. See the documentation on + * {@see RetrySettings} for example usage. + * } + * + * @throws ApiException Thrown if the API call fails. + */ + public function delete(DeleteOperationRequest $request, array $callOptions = []): void + { + $this->startApiCall('Delete', $request, $callOptions)->wait(); + } + + /** + * The async variant is {@see CustomLroOperationsClient::getAsync()} . + * + * @example samples/CustomLroOperationsClient/get.php + * + * @param GetOperationRequest $request A request to house fields associated with the call. + * @param array $callOptions { + * Optional. + * + * @type RetrySettings|array $retrySettings + * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an + * associative array of retry settings parameters. See the documentation on + * {@see RetrySettings} for example usage. + * } + * + * @return CustomOperationResponse + * + * @throws ApiException Thrown if the API call fails. + */ + public function get(GetOperationRequest $request, array $callOptions = []): CustomOperationResponse + { + return $this->startApiCall('Get', $request, $callOptions)->wait(); + } +} diff --git a/tests/Unit/ProtoTests/CustomLroNew/out/src/gapic_metadata.json b/tests/Unit/ProtoTests/CustomLroNew/out/src/gapic_metadata.json new file mode 100644 index 000000000..5793a2802 --- /dev/null +++ b/tests/Unit/ProtoTests/CustomLroNew/out/src/gapic_metadata.json @@ -0,0 +1,47 @@ +{ + "schema": "1.0", + "comment": "This file maps proto services\/RPCs to the corresponding library clients\/methods", + "language": "php", + "protoPackage": "testing.customlronew", + "libraryPackage": "Testing\\CustomLroNew", + "services": { + "CustomLro": { + "clients": { + "grpc": { + "libraryClient": "CustomLroGapicClient", + "rpcs": { + "CreateFoo": { + "methods": [ + "createFoo" + ] + } + } + } + } + }, + "CustomLroOperations": { + "clients": { + "grpc": { + "libraryClient": "CustomLroOperationsGapicClient", + "rpcs": { + "Cancel": { + "methods": [ + "cancel" + ] + }, + "Delete": { + "methods": [ + "delete" + ] + }, + "Get": { + "methods": [ + "get" + ] + } + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/Unit/ProtoTests/CustomLroNew/out/src/resources/custom_lro_client_config.json b/tests/Unit/ProtoTests/CustomLroNew/out/src/resources/custom_lro_client_config.json new file mode 100644 index 000000000..696e82868 --- /dev/null +++ b/tests/Unit/ProtoTests/CustomLroNew/out/src/resources/custom_lro_client_config.json @@ -0,0 +1,31 @@ +{ + "interfaces": { + "testing.customlronew.CustomLro": { + "retry_codes": { + "idempotent": [ + "DEADLINE_EXCEEDED", + "UNAVAILABLE" + ], + "non_idempotent": [] + }, + "retry_params": { + "default": { + "initial_retry_delay_millis": 100, + "retry_delay_multiplier": 1.3, + "max_retry_delay_millis": 60000, + "initial_rpc_timeout_millis": 20000, + "rpc_timeout_multiplier": 1.0, + "max_rpc_timeout_millis": 20000, + "total_timeout_millis": 600000 + } + }, + "methods": { + "CreateFoo": { + "timeout_millis": 60000, + "retry_codes_name": "non_idempotent", + "retry_params_name": "default" + } + } + } + } +} diff --git a/tests/Unit/ProtoTests/CustomLroNew/out/src/resources/custom_lro_descriptor_config.php b/tests/Unit/ProtoTests/CustomLroNew/out/src/resources/custom_lro_descriptor_config.php new file mode 100644 index 000000000..125c46504 --- /dev/null +++ b/tests/Unit/ProtoTests/CustomLroNew/out/src/resources/custom_lro_descriptor_config.php @@ -0,0 +1,50 @@ + [ + 'testing.customlronew.CustomLro' => [ + 'CreateFoo' => [ + 'longRunning' => [ + 'additionalArgumentMethods' => [ + 'getProject', + 'getRegion', + 'getFoo', + ], + 'getOperationMethod' => 'get', + 'cancelOperationMethod' => 'cancel', + 'deleteOperationMethod' => 'delete', + 'operationErrorCodeMethod' => 'getHttpErrorStatusCode', + 'operationErrorMessageMethod' => 'getHttpErrorMessage', + 'operationNameMethod' => 'getName', + 'operationStatusMethod' => 'getStatus', + 'operationStatusDoneValue' => \Testing\CustomLroNew\CustomOperationResponse\Status::DONE, + 'getOperationRequest' => '\Testing\CustomLroNew\GetOperationRequest', + 'cancelOperationRequest' => '\Testing\CustomLroNew\CancelOperationRequest', + 'deleteOperationRequest' => '\Testing\CustomLroNew\DeleteOperationRequest', + ], + 'responseType' => 'Testing\CustomLroNew\CustomOperationResponse', + 'callType' => \Google\ApiCore\Call::LONGRUNNING_CALL, + ], + ], + ], +]; diff --git a/tests/Unit/ProtoTests/CustomLroNew/out/src/resources/custom_lro_operations_client_config.json b/tests/Unit/ProtoTests/CustomLroNew/out/src/resources/custom_lro_operations_client_config.json new file mode 100644 index 000000000..c55929f54 --- /dev/null +++ b/tests/Unit/ProtoTests/CustomLroNew/out/src/resources/custom_lro_operations_client_config.json @@ -0,0 +1,41 @@ +{ + "interfaces": { + "testing.customlronew.CustomLroOperations": { + "retry_codes": { + "idempotent": [ + "DEADLINE_EXCEEDED", + "UNAVAILABLE" + ], + "non_idempotent": [] + }, + "retry_params": { + "default": { + "initial_retry_delay_millis": 100, + "retry_delay_multiplier": 1.3, + "max_retry_delay_millis": 60000, + "initial_rpc_timeout_millis": 20000, + "rpc_timeout_multiplier": 1.0, + "max_rpc_timeout_millis": 20000, + "total_timeout_millis": 600000 + } + }, + "methods": { + "Cancel": { + "timeout_millis": 60000, + "retry_codes_name": "non_idempotent", + "retry_params_name": "default" + }, + "Delete": { + "timeout_millis": 60000, + "retry_codes_name": "non_idempotent", + "retry_params_name": "default" + }, + "Get": { + "timeout_millis": 60000, + "retry_codes_name": "idempotent", + "retry_params_name": "default" + } + } + } + } +} diff --git a/tests/Unit/ProtoTests/CustomLroNew/out/src/resources/custom_lro_operations_descriptor_config.php b/tests/Unit/ProtoTests/CustomLroNew/out/src/resources/custom_lro_operations_descriptor_config.php new file mode 100644 index 000000000..41d5c5fc0 --- /dev/null +++ b/tests/Unit/ProtoTests/CustomLroNew/out/src/resources/custom_lro_operations_descriptor_config.php @@ -0,0 +1,40 @@ + [ + 'testing.customlronew.CustomLroOperations' => [ + 'Cancel' => [ + 'callType' => \Google\ApiCore\Call::UNARY_CALL, + 'responseType' => 'Google\Protobuf\GPBEmpty', + ], + 'Delete' => [ + 'callType' => \Google\ApiCore\Call::UNARY_CALL, + 'responseType' => 'Google\Protobuf\GPBEmpty', + ], + 'Get' => [ + 'callType' => \Google\ApiCore\Call::UNARY_CALL, + 'responseType' => 'Testing\CustomLroNew\CustomOperationResponse', + ], + ], + ], +]; diff --git a/tests/Unit/ProtoTests/CustomLroNew/out/src/resources/custom_lro_operations_rest_client_config.php b/tests/Unit/ProtoTests/CustomLroNew/out/src/resources/custom_lro_operations_rest_client_config.php new file mode 100644 index 000000000..acd9fad85 --- /dev/null +++ b/tests/Unit/ProtoTests/CustomLroNew/out/src/resources/custom_lro_operations_rest_client_config.php @@ -0,0 +1,46 @@ + [ + 'testing.customlronew.CustomLroOperations' => [ + 'Cancel' => [ + 'method' => 'patch', + 'uriTemplate' => '/operation', + ], + 'Delete' => [ + 'method' => 'delete', + 'uriTemplate' => '/operation', + ], + 'Get' => [ + 'method' => 'get', + 'uriTemplate' => '/operation', + 'queryParams' => [ + 'project', + 'region', + 'foo', + ], + ], + ], + ], + 'numericEnums' => true, +]; diff --git a/tests/Unit/ProtoTests/CustomLroNew/out/src/resources/custom_lro_rest_client_config.php b/tests/Unit/ProtoTests/CustomLroNew/out/src/resources/custom_lro_rest_client_config.php new file mode 100644 index 000000000..793a7b0f1 --- /dev/null +++ b/tests/Unit/ProtoTests/CustomLroNew/out/src/resources/custom_lro_rest_client_config.php @@ -0,0 +1,53 @@ + [ + 'testing.customlronew.CustomLro' => [ + 'CreateFoo' => [ + 'method' => 'post', + 'uriTemplate' => '/foo', + 'body' => '*', + ], + ], + 'testing.customlronew.CustomLroOperations' => [ + 'Cancel' => [ + 'method' => 'patch', + 'uriTemplate' => '/operation', + ], + 'Delete' => [ + 'method' => 'delete', + 'uriTemplate' => '/operation', + ], + 'Get' => [ + 'method' => 'get', + 'uriTemplate' => '/operation', + 'queryParams' => [ + 'project', + 'region', + 'foo', + ], + ], + ], + ], + 'numericEnums' => true, +]; diff --git a/tests/Unit/ProtoTests/CustomLroNew/out/tests/Unit/Client/CustomLroClientTest.php b/tests/Unit/ProtoTests/CustomLroNew/out/tests/Unit/Client/CustomLroClientTest.php new file mode 100644 index 000000000..c683b4b87 --- /dev/null +++ b/tests/Unit/ProtoTests/CustomLroNew/out/tests/Unit/Client/CustomLroClientTest.php @@ -0,0 +1,251 @@ +getMockBuilder(CredentialsWrapper::class)->disableOriginalConstructor()->getMock(); + } + + /** @return CustomLroClient */ + private function createClient(array $options = []) + { + $options += [ + 'credentials' => $this->createCredentials(), + ]; + return new CustomLroClient($options); + } + + /** @test */ + public function createFooTest() + { + $operationsTransport = $this->createTransport(); + $operationsClient = new CustomLroOperationsClient([ + 'apiEndpoint' => '', + 'transport' => $operationsTransport, + 'credentials' => $this->createCredentials(), + ]); + $transport = $this->createTransport(); + $gapicClient = $this->createClient([ + 'transport' => $transport, + 'operationsClient' => $operationsClient, + ]); + $this->assertTrue($transport->isExhausted()); + $this->assertTrue($operationsTransport->isExhausted()); + // Mock response + $incompleteOperation = new CustomOperationResponse(); + $incompleteOperation->setName('customOperations/createFooTest'); + $incompleteOperation->setStatus(Status::RUNNING); + $transport->addResponse($incompleteOperation); + $completeOperation = new CustomOperationResponse(); + $completeOperation->setName('customOperations/createFooTest'); + $completeOperation->setStatus(Status::DONE); + $operationsTransport->addResponse($completeOperation); + // Mock request + $project = 'project-309310695'; + $region = 'region-934795532'; + $request = (new CreateFooRequest()) + ->setProject($project) + ->setRegion($region); + $response = $gapicClient->createFoo($request); + $this->assertFalse($response->isDone()); + $apiRequests = $transport->popReceivedCalls(); + $this->assertSame(1, count($apiRequests)); + $operationsRequestsEmpty = $operationsTransport->popReceivedCalls(); + $this->assertSame(0, count($operationsRequestsEmpty)); + $actualApiFuncCall = $apiRequests[0]->getFuncCall(); + $actualApiRequestObject = $apiRequests[0]->getRequestObject(); + $this->assertSame('/testing.customlronew.CustomLro/CreateFoo', $actualApiFuncCall); + $actualValue = $actualApiRequestObject->getProject(); + $this->assertProtobufEquals($project, $actualValue); + $actualValue = $actualApiRequestObject->getRegion(); + $this->assertProtobufEquals($region, $actualValue); + $expectedOperationsRequestObject = new \Testing\CustomLroNew\GetOperationRequest(); + $expectedOperationsRequestObject->setOperation($completeOperation->getName()); + $expectedOperationsRequestObject->setProject($project); + $expectedOperationsRequestObject->setRegion($region); + $expectedOperationsRequestObject->setFoo($actualApiRequestObject->getFoo()); + $response->pollUntilComplete([ + 'initialPollDelayMillis' => 1, + ]); + $this->assertTrue($response->isDone()); + $apiRequestsEmpty = $transport->popReceivedCalls(); + $this->assertSame(0, count($apiRequestsEmpty)); + $operationsRequests = $operationsTransport->popReceivedCalls(); + $this->assertSame(1, count($operationsRequests)); + $actualOperationsFuncCall = $operationsRequests[0]->getFuncCall(); + $actualOperationsRequestObject = $operationsRequests[0]->getRequestObject(); + $this->assertSame('/testing.customlronew.CustomLroOperations/Get', $actualOperationsFuncCall); + $this->assertEquals($expectedOperationsRequestObject, $actualOperationsRequestObject); + $this->assertTrue($transport->isExhausted()); + $this->assertTrue($operationsTransport->isExhausted()); + } + + /** @test */ + public function createFooExceptionTest() + { + $operationsTransport = $this->createTransport(); + $operationsClient = new CustomLroOperationsClient([ + 'apiEndpoint' => '', + 'transport' => $operationsTransport, + 'credentials' => $this->createCredentials(), + ]); + $transport = $this->createTransport(); + $gapicClient = $this->createClient([ + 'transport' => $transport, + 'operationsClient' => $operationsClient, + ]); + $this->assertTrue($transport->isExhausted()); + $this->assertTrue($operationsTransport->isExhausted()); + // Mock response + $incompleteOperation = new CustomOperationResponse(); + $incompleteOperation->setName('customOperations/createFooExceptionTest'); + $incompleteOperation->setStatus(Status::RUNNING); + $transport->addResponse($incompleteOperation); + $status = new stdClass(); + $status->code = Code::DATA_LOSS; + $status->details = 'internal error'; + $expectedExceptionMessage = json_encode([ + 'message' => 'internal error', + 'code' => Code::DATA_LOSS, + 'status' => 'DATA_LOSS', + 'details' => [], + ], JSON_PRETTY_PRINT); + $operationsTransport->addResponse(null, $status); + // Mock request + $project = 'project-309310695'; + $region = 'region-934795532'; + $request = (new CreateFooRequest()) + ->setProject($project) + ->setRegion($region); + $response = $gapicClient->createFoo($request); + $this->assertFalse($response->isDone()); + $this->assertNull($response->getResult()); + try { + $response->pollUntilComplete([ + 'initialPollDelayMillis' => 1, + ]); + // If the pollUntilComplete() method call did not throw, fail the test + $this->fail('Expected an ApiException, but no exception was thrown.'); + } catch (ApiException $ex) { + $this->assertEquals($status->code, $ex->getCode()); + $this->assertEquals($expectedExceptionMessage, $ex->getMessage()); + } + // Call popReceivedCalls to ensure the stubs are exhausted + $transport->popReceivedCalls(); + $operationsTransport->popReceivedCalls(); + $this->assertTrue($transport->isExhausted()); + $this->assertTrue($operationsTransport->isExhausted()); + } + + /** @test */ + public function createFooAsyncTest() + { + $operationsTransport = $this->createTransport(); + $operationsClient = new CustomLroOperationsClient([ + 'apiEndpoint' => '', + 'transport' => $operationsTransport, + 'credentials' => $this->createCredentials(), + ]); + $transport = $this->createTransport(); + $gapicClient = $this->createClient([ + 'transport' => $transport, + 'operationsClient' => $operationsClient, + ]); + $this->assertTrue($transport->isExhausted()); + $this->assertTrue($operationsTransport->isExhausted()); + // Mock response + $incompleteOperation = new CustomOperationResponse(); + $incompleteOperation->setName('customOperations/createFooAsyncTest'); + $incompleteOperation->setStatus(Status::RUNNING); + $transport->addResponse($incompleteOperation); + $completeOperation = new CustomOperationResponse(); + $completeOperation->setName('customOperations/createFooAsyncTest'); + $completeOperation->setStatus(Status::DONE); + $operationsTransport->addResponse($completeOperation); + // Mock request + $project = 'project-309310695'; + $region = 'region-934795532'; + $request = (new CreateFooRequest()) + ->setProject($project) + ->setRegion($region); + $response = $gapicClient->createFoo($request); + $this->assertFalse($response->isDone()); + $apiRequests = $transport->popReceivedCalls(); + $this->assertSame(1, count($apiRequests)); + $operationsRequestsEmpty = $operationsTransport->popReceivedCalls(); + $this->assertSame(0, count($operationsRequestsEmpty)); + $actualApiFuncCall = $apiRequests[0]->getFuncCall(); + $actualApiRequestObject = $apiRequests[0]->getRequestObject(); + $this->assertSame('/testing.customlronew.CustomLro/CreateFoo', $actualApiFuncCall); + $actualValue = $actualApiRequestObject->getProject(); + $this->assertProtobufEquals($project, $actualValue); + $actualValue = $actualApiRequestObject->getRegion(); + $this->assertProtobufEquals($region, $actualValue); + $expectedOperationsRequestObject = new \Testing\CustomLroNew\GetOperationRequest(); + $expectedOperationsRequestObject->setOperation($completeOperation->getName()); + $expectedOperationsRequestObject->setProject($project); + $expectedOperationsRequestObject->setRegion($region); + $expectedOperationsRequestObject->setFoo($actualApiRequestObject->getFoo()); + $response->pollUntilComplete([ + 'initialPollDelayMillis' => 1, + ]); + $this->assertTrue($response->isDone()); + $apiRequestsEmpty = $transport->popReceivedCalls(); + $this->assertSame(0, count($apiRequestsEmpty)); + $operationsRequests = $operationsTransport->popReceivedCalls(); + $this->assertSame(1, count($operationsRequests)); + $actualOperationsFuncCall = $operationsRequests[0]->getFuncCall(); + $actualOperationsRequestObject = $operationsRequests[0]->getRequestObject(); + $this->assertSame('/testing.customlronew.CustomLroOperations/Get', $actualOperationsFuncCall); + $this->assertEquals($expectedOperationsRequestObject, $actualOperationsRequestObject); + $this->assertTrue($transport->isExhausted()); + $this->assertTrue($operationsTransport->isExhausted()); + } +} diff --git a/tests/Unit/ProtoTests/CustomLroNew/out/tests/Unit/Client/CustomLroOperationsClientTest.php b/tests/Unit/ProtoTests/CustomLroNew/out/tests/Unit/Client/CustomLroOperationsClientTest.php new file mode 100644 index 000000000..054881dc7 --- /dev/null +++ b/tests/Unit/ProtoTests/CustomLroNew/out/tests/Unit/Client/CustomLroOperationsClientTest.php @@ -0,0 +1,298 @@ +getMockBuilder(CredentialsWrapper::class)->disableOriginalConstructor()->getMock(); + } + + /** @return CustomLroOperationsClient */ + private function createClient(array $options = []) + { + $options += [ + 'credentials' => $this->createCredentials(), + ]; + return new CustomLroOperationsClient($options); + } + + /** @test */ + public function cancelTest() + { + $transport = $this->createTransport(); + $gapicClient = $this->createClient([ + 'transport' => $transport, + ]); + $this->assertTrue($transport->isExhausted()); + // Mock response + $expectedResponse = new GPBEmpty(); + $transport->addResponse($expectedResponse); + // Mock request + $operation = 'operation1662702951'; + $request = (new CancelOperationRequest()) + ->setOperation($operation); + $gapicClient->cancel($request); + $actualRequests = $transport->popReceivedCalls(); + $this->assertSame(1, count($actualRequests)); + $actualFuncCall = $actualRequests[0]->getFuncCall(); + $actualRequestObject = $actualRequests[0]->getRequestObject(); + $this->assertSame('/testing.customlronew.CustomLroOperations/Cancel', $actualFuncCall); + $actualValue = $actualRequestObject->getOperation(); + $this->assertProtobufEquals($operation, $actualValue); + $this->assertTrue($transport->isExhausted()); + } + + /** @test */ + public function cancelExceptionTest() + { + $transport = $this->createTransport(); + $gapicClient = $this->createClient([ + 'transport' => $transport, + ]); + $this->assertTrue($transport->isExhausted()); + $status = new stdClass(); + $status->code = Code::DATA_LOSS; + $status->details = 'internal error'; + $expectedExceptionMessage = json_encode([ + 'message' => 'internal error', + 'code' => Code::DATA_LOSS, + 'status' => 'DATA_LOSS', + 'details' => [], + ], JSON_PRETTY_PRINT); + $transport->addResponse(null, $status); + // Mock request + $operation = 'operation1662702951'; + $request = (new CancelOperationRequest()) + ->setOperation($operation); + try { + $gapicClient->cancel($request); + // If the $gapicClient method call did not throw, fail the test + $this->fail('Expected an ApiException, but no exception was thrown.'); + } catch (ApiException $ex) { + $this->assertEquals($status->code, $ex->getCode()); + $this->assertEquals($expectedExceptionMessage, $ex->getMessage()); + } + // Call popReceivedCalls to ensure the stub is exhausted + $transport->popReceivedCalls(); + $this->assertTrue($transport->isExhausted()); + } + + /** @test */ + public function deleteTest() + { + $transport = $this->createTransport(); + $gapicClient = $this->createClient([ + 'transport' => $transport, + ]); + $this->assertTrue($transport->isExhausted()); + // Mock response + $expectedResponse = new GPBEmpty(); + $transport->addResponse($expectedResponse); + // Mock request + $operation = 'operation1662702951'; + $request = (new DeleteOperationRequest()) + ->setOperation($operation); + $gapicClient->delete($request); + $actualRequests = $transport->popReceivedCalls(); + $this->assertSame(1, count($actualRequests)); + $actualFuncCall = $actualRequests[0]->getFuncCall(); + $actualRequestObject = $actualRequests[0]->getRequestObject(); + $this->assertSame('/testing.customlronew.CustomLroOperations/Delete', $actualFuncCall); + $actualValue = $actualRequestObject->getOperation(); + $this->assertProtobufEquals($operation, $actualValue); + $this->assertTrue($transport->isExhausted()); + } + + /** @test */ + public function deleteExceptionTest() + { + $transport = $this->createTransport(); + $gapicClient = $this->createClient([ + 'transport' => $transport, + ]); + $this->assertTrue($transport->isExhausted()); + $status = new stdClass(); + $status->code = Code::DATA_LOSS; + $status->details = 'internal error'; + $expectedExceptionMessage = json_encode([ + 'message' => 'internal error', + 'code' => Code::DATA_LOSS, + 'status' => 'DATA_LOSS', + 'details' => [], + ], JSON_PRETTY_PRINT); + $transport->addResponse(null, $status); + // Mock request + $operation = 'operation1662702951'; + $request = (new DeleteOperationRequest()) + ->setOperation($operation); + try { + $gapicClient->delete($request); + // If the $gapicClient method call did not throw, fail the test + $this->fail('Expected an ApiException, but no exception was thrown.'); + } catch (ApiException $ex) { + $this->assertEquals($status->code, $ex->getCode()); + $this->assertEquals($expectedExceptionMessage, $ex->getMessage()); + } + // Call popReceivedCalls to ensure the stub is exhausted + $transport->popReceivedCalls(); + $this->assertTrue($transport->isExhausted()); + } + + /** @test */ + public function getTest() + { + $transport = $this->createTransport(); + $gapicClient = $this->createClient([ + 'transport' => $transport, + ]); + $this->assertTrue($transport->isExhausted()); + // Mock response + $name = 'name3373707'; + $httpErrorMessage = 'httpErrorMessage1276263769'; + $httpErrorStatusCode = 1386087020; + $expectedResponse = new CustomOperationResponse(); + $expectedResponse->setName($name); + $expectedResponse->setHttpErrorMessage($httpErrorMessage); + $expectedResponse->setHttpErrorStatusCode($httpErrorStatusCode); + $transport->addResponse($expectedResponse); + // Mock request + $operation = 'operation1662702951'; + $project = 'project-309310695'; + $region = 'region-934795532'; + $foo = 'foo101574'; + $request = (new \Testing\CustomLroNew\GetOperationRequest()) + ->setOperation($operation) + ->setProject($project) + ->setRegion($region) + ->setFoo($foo); + $response = $gapicClient->get($request); + $this->assertEquals($expectedResponse, $response); + $actualRequests = $transport->popReceivedCalls(); + $this->assertSame(1, count($actualRequests)); + $actualFuncCall = $actualRequests[0]->getFuncCall(); + $actualRequestObject = $actualRequests[0]->getRequestObject(); + $this->assertSame('/testing.customlronew.CustomLroOperations/Get', $actualFuncCall); + $actualValue = $actualRequestObject->getOperation(); + $this->assertProtobufEquals($operation, $actualValue); + $actualValue = $actualRequestObject->getProject(); + $this->assertProtobufEquals($project, $actualValue); + $actualValue = $actualRequestObject->getRegion(); + $this->assertProtobufEquals($region, $actualValue); + $actualValue = $actualRequestObject->getFoo(); + $this->assertProtobufEquals($foo, $actualValue); + $this->assertTrue($transport->isExhausted()); + } + + /** @test */ + public function getExceptionTest() + { + $transport = $this->createTransport(); + $gapicClient = $this->createClient([ + 'transport' => $transport, + ]); + $this->assertTrue($transport->isExhausted()); + $status = new stdClass(); + $status->code = Code::DATA_LOSS; + $status->details = 'internal error'; + $expectedExceptionMessage = json_encode([ + 'message' => 'internal error', + 'code' => Code::DATA_LOSS, + 'status' => 'DATA_LOSS', + 'details' => [], + ], JSON_PRETTY_PRINT); + $transport->addResponse(null, $status); + // Mock request + $operation = 'operation1662702951'; + $project = 'project-309310695'; + $region = 'region-934795532'; + $foo = 'foo101574'; + $request = (new \Testing\CustomLroNew\GetOperationRequest()) + ->setOperation($operation) + ->setProject($project) + ->setRegion($region) + ->setFoo($foo); + try { + $gapicClient->get($request); + // If the $gapicClient method call did not throw, fail the test + $this->fail('Expected an ApiException, but no exception was thrown.'); + } catch (ApiException $ex) { + $this->assertEquals($status->code, $ex->getCode()); + $this->assertEquals($expectedExceptionMessage, $ex->getMessage()); + } + // Call popReceivedCalls to ensure the stub is exhausted + $transport->popReceivedCalls(); + $this->assertTrue($transport->isExhausted()); + } + + /** @test */ + public function cancelAsyncTest() + { + $transport = $this->createTransport(); + $gapicClient = $this->createClient([ + 'transport' => $transport, + ]); + $this->assertTrue($transport->isExhausted()); + // Mock response + $expectedResponse = new GPBEmpty(); + $transport->addResponse($expectedResponse); + // Mock request + $operation = 'operation1662702951'; + $request = (new CancelOperationRequest()) + ->setOperation($operation); + $gapicClient->cancelAsync($request)->wait(); + $actualRequests = $transport->popReceivedCalls(); + $this->assertSame(1, count($actualRequests)); + $actualFuncCall = $actualRequests[0]->getFuncCall(); + $actualRequestObject = $actualRequests[0]->getRequestObject(); + $this->assertSame('/testing.customlronew.CustomLroOperations/Cancel', $actualFuncCall); + $actualValue = $actualRequestObject->getOperation(); + $this->assertProtobufEquals($operation, $actualValue); + $this->assertTrue($transport->isExhausted()); + } +} diff --git a/tests/Unit/ProtoTests/GoldenUpdateMain.php b/tests/Unit/ProtoTests/GoldenUpdateMain.php index 518d1fa9e..e9a8d501c 100644 --- a/tests/Unit/ProtoTests/GoldenUpdateMain.php +++ b/tests/Unit/ProtoTests/GoldenUpdateMain.php @@ -104,7 +104,14 @@ class GoldenUpdateMain 'protoPath' => 'BasicGrpcOnly/basic-grpc-only.proto', 'migrationMode' => MigrationMode::NEW_SURFACE_ONLY, 'transport' => 'grpc' - ] + ], + 18 => [ + 'name' => 'CustomLro (new surface only)', + 'protoPath' => 'CustomLroNew/custom_lro_new.proto', + 'package' => 'testing.customlronew', + 'transport' => 'rest', + 'migrationMode' => MigrationMode::NEW_SURFACE_ONLY + ], ]; public static function updateAll() diff --git a/tests/Unit/autoload.php b/tests/Unit/autoload.php index aff694fbe..94a3a56ac 100644 --- a/tests/Unit/autoload.php +++ b/tests/Unit/autoload.php @@ -78,7 +78,19 @@ function protosOnDemandLoader($class) if (substr($class, 0, 8) === 'Testing\\' && (strpos($class, 'Request') !== false || strpos($class, 'Response') !== false)) { // Create an alias to `FakeMessage` for any non-existant class that looks like it's a proto message class. - class_alias(FakeMessage::class, $class); + $classAlias = FakeMessage::class; + + $parts = explode('\\', $class); + $fragment = sprintf('%s/ProtoTests/%s/out/fragments/%s.build.txt', __DIR__, $parts[1], implode('/', $parts)); + + // if a "build" fragment exists, create an anonymous class with the build method + if (file_exists($fragment)) { + $classAlias = get_class(eval(sprintf( + 'return new class() extends FakeMessage { %s };', + file_get_contents($fragment) + ))); + } + class_alias($classAlias, $class); } }