From 165524fb533b54d11c98696022ba3bd70e9800b5 Mon Sep 17 00:00:00 2001 From: Vishal Khode Date: Fri, 13 Mar 2026 15:17:47 +0530 Subject: [PATCH 1/6] CLI-1745: fix: resolve pointers in requestBody and property schemas for API commands. --- src/Command/Api/ApiBaseCommand.php | 2 +- src/Command/Api/ApiCommandHelper.php | 38 +++++++++++++++++-- .../src/Commands/Api/ApiCommandTest.php | 15 +++++++- 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/Command/Api/ApiBaseCommand.php b/src/Command/Api/ApiBaseCommand.php index 4d0cea76b..4a3eb4a67 100644 --- a/src/Command/Api/ApiBaseCommand.php +++ b/src/Command/Api/ApiBaseCommand.php @@ -245,7 +245,7 @@ private function castParamType(array $paramSpec, array|string|bool|int $value): if (in_array('integer', $types, true) && ctype_digit($value)) { return $this->doCastParamType('integer', $value); } - } elseif ($paramSpec['type'] === 'array') { + } elseif ($this->getParamType($paramSpec) === 'array') { if (is_array($value) && count($value) === 1) { return $this->castParamToArray($paramSpec, $value[0]); } diff --git a/src/Command/Api/ApiCommandHelper.php b/src/Command/Api/ApiCommandHelper.php index c8573e51d..3a6172b6d 100644 --- a/src/Command/Api/ApiCommandHelper.php +++ b/src/Command/Api/ApiCommandHelper.php @@ -86,6 +86,12 @@ private function addApiCommandParameters(array $schema, array $acquiaCloudSpec, // Parameters to be used in the request body. if (array_key_exists('requestBody', $schema)) { + // Resolve $ref in requestBody if present. + if (array_key_exists('$ref', $schema['requestBody'])) { + $parts = explode('/', $schema['requestBody']['$ref']); + $paramKey = end($parts); + $schema['requestBody'] = $acquiaCloudSpec['components']['requestBodies'][$paramKey]; + } [ $bodyInputDefinition, $requestBodyParamUsageSuffix, @@ -93,7 +99,7 @@ private function addApiCommandParameters(array $schema, array $acquiaCloudSpec, $requestBodySchema = $this->getRequestBodyFromParameterSchema($schema, $acquiaCloudSpec); /** @var \Symfony\Component\Console\Input\InputOption|InputArgument $parameterDefinition */ foreach ($bodyInputDefinition as $parameterDefinition) { - $parameterSpecification = $this->getPropertySpecFromRequestBodyParam($requestBodySchema, $parameterDefinition); + $parameterSpecification = $this->getPropertySpecFromRequestBodyParam($requestBodySchema, $parameterDefinition, $acquiaCloudSpec); $command->addPostParameter($parameterDefinition->getName(), $parameterSpecification); } $usage .= $requestBodyParamUsageSuffix; @@ -125,6 +131,12 @@ private function addApiCommandParametersForRequestBody(array $schema, array $acq $requestBodySchema['properties'] = []; } foreach ($requestBodySchema['properties'] as $propKey => $paramDefinition) { + // Resolve $ref inside individual property definitions. + if (array_key_exists('$ref', $paramDefinition)) { + $parts = explode('/', $paramDefinition['$ref']); + $paramKey = end($parts); + $paramDefinition = $this->getParameterSchemaFromSpec($paramKey, $acquiaCloudSpec); + } $isRequired = array_key_exists('required', $requestBodySchema) && in_array($propKey, $requestBodySchema['required'], true); $propKey = self::renameParameter($propKey); @@ -168,9 +180,17 @@ private function addApiCommandParametersForRequestBody(array $schema, array $acq private function addPostArgumentUsageToExample(mixed $requestBody, mixed $propKey, mixed $paramDefinition, string $type, string $usage, array $acquiaCloudSpec): string { $requestBodyContent = $this->getRequestBodyContent($requestBody, $acquiaCloudSpec); - + // Example may live directly on the content-type object (inline requestBody), + // or nested inside schema (e.g. $ref-resolved requestBodies). if (array_key_exists('example', $requestBodyContent)) { $example = $requestBodyContent['example']; + } elseif (array_key_exists('schema', $requestBodyContent) && array_key_exists('example', $requestBodyContent['schema'])) { + $example = $requestBodyContent['schema']['example']; + } else { + return $usage; + } + + if ($example) { $prefix = $type === 'argument' ? '' : "--$propKey="; if (array_key_exists($propKey, $example)) { if (!array_key_exists('type', $paramDefinition)) { @@ -482,10 +502,20 @@ private function getRequestBodyFromParameterSchema(array $schema, array $acquiaC return $requestBodySchema; } - private function getPropertySpecFromRequestBodyParam(array $requestBodySchema, mixed $parameterDefinition): mixed + private function getPropertySpecFromRequestBodyParam(array $requestBodySchema, mixed $parameterDefinition, array $acquiaCloudSpec = []): mixed { $name = self::restoreRenamedParameter($parameterDefinition->getName()); - return $requestBodySchema['properties'][$name] ?? null; + $spec = $requestBodySchema['properties'][$name] ?? []; + + // Resolve $ref in the property spec so downstream code (e.g. castParamType) always + // receives a fully resolved spec with a 'type' key rather than a bare $ref object. + if (array_key_exists('$ref', $spec)) { + $parts = explode('/', $spec['$ref']); + $paramKey = end($parts); + $spec = $this->getParameterSchemaFromSpec($paramKey, $acquiaCloudSpec); + } + + return $spec; } /** diff --git a/tests/phpunit/src/Commands/Api/ApiCommandTest.php b/tests/phpunit/src/Commands/Api/ApiCommandTest.php index bf09a80be..b5f566115 100644 --- a/tests/phpunit/src/Commands/Api/ApiCommandTest.php +++ b/tests/phpunit/src/Commands/Api/ApiCommandTest.php @@ -550,6 +550,11 @@ public static function providerTestApiCommandDefinitionRequestBody(): array 'post', '12-d314739e-296f-11e9-b210-d663bd873d93 --source="14-0c7e79ab-1c4a-424e-8446-76ae8be7e851"', ], + [ + 'api:private-networks:create', + 'post', + 'api:private-networks:create "123e4567-e89b-12d3-a456-426614174000" "us-east-1" "customer-private-network" --description="Private network for customer" --label="anyLabel" --isolation="{"dedicated_compute":false,"dedicated_network":false}"" --ingress="{"drupal_ssh":{"ingress_acls":["test-acls"]}}"" "{"cidr":"114.7.55.1\/16","private_egress_access":{"drupal":true},"vpns":[{"name":"vpn1","gateway_ip":"10.10.10.10","routes":["127.0.0.1\/32","127.0.0.2\/32"],"tunnel1":{"shared_key":"sharedKey1","internal_cidr":"192.1.1.0\/24","ike_versions":"1","startup_action":"start","dpd_timeout_action":"stop"},"tunnel2":{"shared_key":"sharedKey2","internal_cidr":"192.1.1.0\/14","ike_versions":"1","startup_action":"start","dpd_timeout_action":"stop"}}],"vpc_peers":[{"name":"vpcPeer1","aws_account":"123456789012","vpc_id":"vpc-1234567890abcdef0","vpc_cidr":"120.24.16.1\/24"}]}""', + ], ]; } @@ -563,7 +568,15 @@ public function testApiCommandDefinitionRequestBody(string $commandName, string { $this->command = $this->getApiCommandByName($commandName); $resource = self::getResourceFromSpec($this->command->getPath(), $method); - foreach ($resource['requestBody']['content']['application/hal+json']['example'] as $propKey => $value) { + if (array_key_exists('$ref', $resource['requestBody'])) { + $cloudApiSpec = self::getCloudApiSpec(); + $parts = explode('/', $resource['requestBody']['$ref']); + $paramKey = end($parts); + $resource['requestBody'] = $cloudApiSpec['components']['requestBodies'][$paramKey]; + } + $example = $resource['requestBody']['content']['application/hal+json']['example'] ?? $resource['requestBody']['content']['application/json']['schema']['example'] ?? null; + self::assertNotEmpty($example); + foreach ($example as $propKey => $value) { $this->assertTrue( $this->command->getDefinition() ->hasArgument($propKey) || $this->command->getDefinition() From 37e7835ad1c3cb2b43ed0cb2eda1706537116745 Mon Sep 17 00:00:00 2001 From: Vishal Khode Date: Fri, 13 Mar 2026 15:46:56 +0530 Subject: [PATCH 2/6] Removed the duplicate check code as suggested by co-pilot. --- src/Command/Api/ApiCommandHelper.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Command/Api/ApiCommandHelper.php b/src/Command/Api/ApiCommandHelper.php index 3a6172b6d..76fbe399a 100644 --- a/src/Command/Api/ApiCommandHelper.php +++ b/src/Command/Api/ApiCommandHelper.php @@ -86,12 +86,6 @@ private function addApiCommandParameters(array $schema, array $acquiaCloudSpec, // Parameters to be used in the request body. if (array_key_exists('requestBody', $schema)) { - // Resolve $ref in requestBody if present. - if (array_key_exists('$ref', $schema['requestBody'])) { - $parts = explode('/', $schema['requestBody']['$ref']); - $paramKey = end($parts); - $schema['requestBody'] = $acquiaCloudSpec['components']['requestBodies'][$paramKey]; - } [ $bodyInputDefinition, $requestBodyParamUsageSuffix, From b9f54e2cbec4b2dc5711cdd633e774a4bbe52cc4 Mon Sep 17 00:00:00 2001 From: Vishal Khode Date: Fri, 13 Mar 2026 17:55:08 +0530 Subject: [PATCH 3/6] Fixed the issue as suggested by co-pilot. --- src/Command/Api/ApiCommandHelper.php | 10 ++++++---- tests/phpunit/src/Commands/Api/ApiCommandTest.php | 6 +++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Command/Api/ApiCommandHelper.php b/src/Command/Api/ApiCommandHelper.php index 76fbe399a..e9b54ca91 100644 --- a/src/Command/Api/ApiCommandHelper.php +++ b/src/Command/Api/ApiCommandHelper.php @@ -192,21 +192,23 @@ private function addPostArgumentUsageToExample(mixed $requestBody, mixed $propKe } switch ($paramDefinition['type']) { case 'object': - $usage .= $prefix . '"' . json_encode($example[$propKey], JSON_THROW_ON_ERROR) . '"" '; + // Wrap JSON in single quotes so inner double quotes remain shell-safe. + $usage .= $prefix . "'" . json_encode($example[$propKey], JSON_THROW_ON_ERROR) . "' "; break; case 'array': $isMultidimensional = count($example[$propKey]) !== count($example[$propKey], COUNT_RECURSIVE); if (!$isMultidimensional) { foreach ($example[$propKey] as $value) { - $usage .= $prefix . "\"$value\" "; + $usage .= $prefix . "'$value' "; } } else { // @todo Pretty sure prevents the user from using the arguments. // Probably a bug. How can we allow users to specify a multidimensional array as an // argument? $value = json_encode($example[$propKey], JSON_THROW_ON_ERROR); - $usage .= $prefix . "\"$value\" "; + // Wrap JSON in single quotes so inner double quotes remain shell-safe. + $usage .= $prefix . "'$value' "; } break; @@ -218,7 +220,7 @@ private function addPostArgumentUsageToExample(mixed $requestBody, mixed $propKe } else { $value = $example[$propKey]; } - $usage .= $prefix . "\"$value\" "; + $usage .= $prefix . "'$value' "; break; } } diff --git a/tests/phpunit/src/Commands/Api/ApiCommandTest.php b/tests/phpunit/src/Commands/Api/ApiCommandTest.php index b5f566115..b7e7406ea 100644 --- a/tests/phpunit/src/Commands/Api/ApiCommandTest.php +++ b/tests/phpunit/src/Commands/Api/ApiCommandTest.php @@ -543,17 +543,17 @@ public static function providerTestApiCommandDefinitionRequestBody(): array [ 'api:accounts:ssh-key-create', 'post', - 'api:accounts:ssh-key-create "mykey" "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQChwPHzTTDKDpSbpa2+d22LcbQmsw92eLsUK3Fmei1fiGDkd34NsYCN8m7lsi3NbvdMS83CtPQPWiCveYPzFs1/hHc4PYj8opD2CNnr5iWVVbyaulCYHCgVv4aB/ojcexg8q483A4xJeF15TiCr/gu34rK6ucTvC/tn/rCwJBudczvEwt0klqYwv8Cl/ytaQboSuem5KgSjO3lMrb6CWtfSNhE43ZOw+UBFBqxIninN868vGMkIv9VY34Pwj54rPn/ItQd6Ef4B0KHHaGmzK0vfP+AK7FxNMoHnj3iYT33KZNqtDozdn5tYyH/bThPebEtgqUn+/w5l6wZIC/8zzvls/127ngHk+jNa0PlNyS2TxhPUK4NaPHIEnnrlp07JEYC4ImcBjaYCWAdcTcUkcJjwZQkN4bGmyO9cjICH98SdLD/HxqzTHeaYDbAX/Hu9HfaBb5dXLWsjw3Xc6hoVnUUZbMQyfgb0KgxDLh92eNGxJkpZiL0VDNOWCxDWsNpzwhLNkLqCvI6lyxiLaUzvJAk6dPaRhExmCbU1lDO2eR0FdSwC1TEhJOT9eDIK1r2hztZKs2oa5FNFfB/IFHVWasVFC9N2h/r/egB5zsRxC9MqBLRBq95NBxaRSFng6ML5WZSw41Qi4C/JWVm89rdj2WqScDHYyAdwyyppWU4T5c9Fmw== example@example.com"', + 'api:accounts:ssh-key-create \'mykey\' \'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQChwPHzTTDKDpSbpa2+d22LcbQmsw92eLsUK3Fmei1fiGDkd34NsYCN8m7lsi3NbvdMS83CtPQPWiCveYPzFs1/hHc4PYj8opD2CNnr5iWVVbyaulCYHCgVv4aB/ojcexg8q483A4xJeF15TiCr/gu34rK6ucTvC/tn/rCwJBudczvEwt0klqYwv8Cl/ytaQboSuem5KgSjO3lMrb6CWtfSNhE43ZOw+UBFBqxIninN868vGMkIv9VY34Pwj54rPn/ItQd6Ef4B0KHHaGmzK0vfP+AK7FxNMoHnj3iYT33KZNqtDozdn5tYyH/bThPebEtgqUn+/w5l6wZIC/8zzvls/127ngHk+jNa0PlNyS2TxhPUK4NaPHIEnnrlp07JEYC4ImcBjaYCWAdcTcUkcJjwZQkN4bGmyO9cjICH98SdLD/HxqzTHeaYDbAX/Hu9HfaBb5dXLWsjw3Xc6hoVnUUZbMQyfgb0KgxDLh92eNGxJkpZiL0VDNOWCxDWsNpzwhLNkLqCvI6lyxiLaUzvJAk6dPaRhExmCbU1lDO2eR0FdSwC1TEhJOT9eDIK1r2hztZKs2oa5FNFfB/IFHVWasVFC9N2h/r/egB5zsRxC9MqBLRBq95NBxaRSFng6ML5WZSw41Qi4C/JWVm89rdj2WqScDHYyAdwyyppWU4T5c9Fmw== example@example.com\'', ], [ 'api:environments:file-copy', 'post', - '12-d314739e-296f-11e9-b210-d663bd873d93 --source="14-0c7e79ab-1c4a-424e-8446-76ae8be7e851"', + '12-d314739e-296f-11e9-b210-d663bd873d93 --source=\'14-0c7e79ab-1c4a-424e-8446-76ae8be7e851\'', ], [ 'api:private-networks:create', 'post', - 'api:private-networks:create "123e4567-e89b-12d3-a456-426614174000" "us-east-1" "customer-private-network" --description="Private network for customer" --label="anyLabel" --isolation="{"dedicated_compute":false,"dedicated_network":false}"" --ingress="{"drupal_ssh":{"ingress_acls":["test-acls"]}}"" "{"cidr":"114.7.55.1\/16","private_egress_access":{"drupal":true},"vpns":[{"name":"vpn1","gateway_ip":"10.10.10.10","routes":["127.0.0.1\/32","127.0.0.2\/32"],"tunnel1":{"shared_key":"sharedKey1","internal_cidr":"192.1.1.0\/24","ike_versions":"1","startup_action":"start","dpd_timeout_action":"stop"},"tunnel2":{"shared_key":"sharedKey2","internal_cidr":"192.1.1.0\/14","ike_versions":"1","startup_action":"start","dpd_timeout_action":"stop"}}],"vpc_peers":[{"name":"vpcPeer1","aws_account":"123456789012","vpc_id":"vpc-1234567890abcdef0","vpc_cidr":"120.24.16.1\/24"}]}""', + 'api:private-networks:create \'123e4567-e89b-12d3-a456-426614174000\' \'us-east-1\' \'customer-private-network\' --description=\'Private network for customer\' --label=\'anyLabel\' --isolation=\'{"dedicated_compute":false,"dedicated_network":false}\' --ingress=\'{"drupal_ssh":{"ingress_acls":["test-acls"]}}\' \'{"cidr":"114.7.55.1\/16","private_egress_access":{"drupal":true},"vpns":[{"name":"vpn1","gateway_ip":"10.10.10.10","routes":["127.0.0.1\/32","127.0.0.2\/32"],"tunnel1":{"shared_key":"sharedKey1","internal_cidr":"192.1.1.0\/24","ike_versions":"1","startup_action":"start","dpd_timeout_action":"stop"},"tunnel2":{"shared_key":"sharedKey2","internal_cidr":"192.1.1.0\/14","ike_versions":"1","startup_action":"start","dpd_timeout_action":"stop"}}],"vpc_peers":[{"name":"vpcPeer1","aws_account":"123456789012","vpc_id":"vpc-1234567890abcdef0","vpc_cidr":"120.24.16.1\/24"}]}\'', ], ]; } From 02efa861d742fb05ace975fcc3a2d8377dcd003a Mon Sep 17 00:00:00 2001 From: Vishal Khode Date: Fri, 13 Mar 2026 18:22:32 +0530 Subject: [PATCH 4/6] Ignored failing mutation tests as it requires a seperate ticket to fix it. --- src/Command/Api/ApiCommandHelper.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Command/Api/ApiCommandHelper.php b/src/Command/Api/ApiCommandHelper.php index e9b54ca91..a541286dd 100644 --- a/src/Command/Api/ApiCommandHelper.php +++ b/src/Command/Api/ApiCommandHelper.php @@ -190,6 +190,14 @@ private function addPostArgumentUsageToExample(mixed $requestBody, mixed $propKe if (!array_key_exists('type', $paramDefinition)) { return $usage; } + /** + * @infection-ignore-all + * + * This is being ignored for now due to CLI-1745. We use single quotes in json_encode() + * to preserve inner double quotes, which causes mutation tests to fail. Fixing this + * could introduce regressions, so a dedicated ticket is needed to + * properly address and validate the changes without breaking existing functionality. + */ switch ($paramDefinition['type']) { case 'object': // Wrap JSON in single quotes so inner double quotes remain shell-safe. From 636d65acdac849b5e61aa4495a9e0826aa94c7aa Mon Sep 17 00:00:00 2001 From: Vishal Khode Date: Mon, 16 Mar 2026 12:52:12 +0530 Subject: [PATCH 5/6] Remove infection ignore to validate if CI is failing due to mutation tests. --- src/Command/Api/ApiCommandHelper.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Command/Api/ApiCommandHelper.php b/src/Command/Api/ApiCommandHelper.php index a541286dd..e9b54ca91 100644 --- a/src/Command/Api/ApiCommandHelper.php +++ b/src/Command/Api/ApiCommandHelper.php @@ -190,14 +190,6 @@ private function addPostArgumentUsageToExample(mixed $requestBody, mixed $propKe if (!array_key_exists('type', $paramDefinition)) { return $usage; } - /** - * @infection-ignore-all - * - * This is being ignored for now due to CLI-1745. We use single quotes in json_encode() - * to preserve inner double quotes, which causes mutation tests to fail. Fixing this - * could introduce regressions, so a dedicated ticket is needed to - * properly address and validate the changes without breaking existing functionality. - */ switch ($paramDefinition['type']) { case 'object': // Wrap JSON in single quotes so inner double quotes remain shell-safe. From 2e51bf39a10a151d72007edf9d97a94e49482801 Mon Sep 17 00:00:00 2001 From: Vishal Khode Date: Mon, 16 Mar 2026 19:11:01 +0530 Subject: [PATCH 6/6] Fixed minor issue and added more test coverage to fix mutation tests. --- src/Command/Api/ApiCommandHelper.php | 16 ++++--- .../src/Commands/Api/ApiCommandHelperTest.php | 37 ++++++++++++++++ .../src/Commands/Api/ApiCommandTest.php | 44 ++++++++++++++++--- 3 files changed, 85 insertions(+), 12 deletions(-) diff --git a/src/Command/Api/ApiCommandHelper.php b/src/Command/Api/ApiCommandHelper.php index e9b54ca91..aaa2236fd 100644 --- a/src/Command/Api/ApiCommandHelper.php +++ b/src/Command/Api/ApiCommandHelper.php @@ -147,7 +147,7 @@ private function addApiCommandParametersForRequestBody(array $schema, array $acq array_key_exists('type', $paramDefinition) && $paramDefinition['type'] === 'array' ? InputArgument::IS_ARRAY | InputArgument::REQUIRED : InputArgument::REQUIRED, $description ); - $usage = $this->addPostArgumentUsageToExample($schema['requestBody'], $propKey, $paramDefinition, 'argument', $usage, $acquiaCloudSpec); + $usage = $this->addPostArgumentUsageToExample($schema['requestBody'], $propKey, $paramDefinition, 'argument', $usage ? $usage . ' ' : '', $acquiaCloudSpec); } else { $inputDefinition[] = new InputOption( $propKey, @@ -155,7 +155,7 @@ private function addApiCommandParametersForRequestBody(array $schema, array $acq array_key_exists('type', $paramDefinition) && $paramDefinition['type'] === 'array' ? InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED : InputOption::VALUE_REQUIRED, array_key_exists('description', $paramDefinition) ? $paramDefinition['description'] : $propKey ); - $usage = $this->addPostArgumentUsageToExample($schema["requestBody"], $propKey, $paramDefinition, 'option', $usage, $acquiaCloudSpec); + $usage = $this->addPostArgumentUsageToExample($schema["requestBody"], $propKey, $paramDefinition, 'option', $usage ? $usage . ' ' : '', $acquiaCloudSpec); // @todo Add validator for $param['enum'] values? } } @@ -190,17 +190,18 @@ private function addPostArgumentUsageToExample(mixed $requestBody, mixed $propKe if (!array_key_exists('type', $paramDefinition)) { return $usage; } + $parts = []; switch ($paramDefinition['type']) { case 'object': // Wrap JSON in single quotes so inner double quotes remain shell-safe. - $usage .= $prefix . "'" . json_encode($example[$propKey], JSON_THROW_ON_ERROR) . "' "; + $parts[] = sprintf("%s'%s'", $prefix, json_encode($example[$propKey], JSON_THROW_ON_ERROR)); break; case 'array': $isMultidimensional = count($example[$propKey]) !== count($example[$propKey], COUNT_RECURSIVE); if (!$isMultidimensional) { foreach ($example[$propKey] as $value) { - $usage .= $prefix . "'$value' "; + $parts[] = sprintf("%s'%s'", $prefix, $value); } } else { // @todo Pretty sure prevents the user from using the arguments. @@ -208,7 +209,7 @@ private function addPostArgumentUsageToExample(mixed $requestBody, mixed $propKe // argument? $value = json_encode($example[$propKey], JSON_THROW_ON_ERROR); // Wrap JSON in single quotes so inner double quotes remain shell-safe. - $usage .= $prefix . "'$value' "; + $parts[] = sprintf("%s'%s'", $prefix, $value); } break; @@ -220,9 +221,12 @@ private function addPostArgumentUsageToExample(mixed $requestBody, mixed $propKe } else { $value = $example[$propKey]; } - $usage .= $prefix . "'$value' "; + $parts[] = sprintf("%s'%s'", $prefix, $value); break; } + if ($parts !== []) { + return $usage . implode(' ', $parts); + } } } return $usage; diff --git a/tests/phpunit/src/Commands/Api/ApiCommandHelperTest.php b/tests/phpunit/src/Commands/Api/ApiCommandHelperTest.php index 407827157..1dd469e48 100644 --- a/tests/phpunit/src/Commands/Api/ApiCommandHelperTest.php +++ b/tests/phpunit/src/Commands/Api/ApiCommandHelperTest.php @@ -109,4 +109,41 @@ public function testNamespaceWithAllHiddenCommandsDoesNotGetListCommand(): void $listCommands = $this->generateApiListCommands($apiCommands); $this->assertArrayNotHasKey('api:baz', $listCommands); } + + /** + * Calls private or protected method of ApiCommandHelper class via reflection. + * + * @throws \ReflectionException + */ + private function invokeApiCommandHelperMethod(string $methodName, array $args = []): mixed + { + $commandHelper = new ApiCommandHelper($this->logger); + $refClass = new ReflectionMethod($commandHelper::class, $methodName); + return $refClass->invokeArgs($commandHelper, $args); + } + + /** + * Test that addPostArgumentUsageToExample correctly formats a flat array with a single item. + */ + public function testAddPostArgumentUsageToExampleFlatArraySingleItem(): void + { + $result = $this->invokeApiCommandHelperMethod( + 'addPostArgumentUsageToExample', + [ + [ + 'content' => [ + 'application/json' => [ + 'example' => ['tags' => ['drupal']], + ], + ], + ], + 'tags', + ['type' => 'array'], + 'option', + '', + [], + ] + ); + $this->assertSame("--tags='drupal'", $result); + } } diff --git a/tests/phpunit/src/Commands/Api/ApiCommandTest.php b/tests/phpunit/src/Commands/Api/ApiCommandTest.php index b7e7406ea..d99be77a0 100644 --- a/tests/phpunit/src/Commands/Api/ApiCommandTest.php +++ b/tests/phpunit/src/Commands/Api/ApiCommandTest.php @@ -543,17 +543,49 @@ public static function providerTestApiCommandDefinitionRequestBody(): array [ 'api:accounts:ssh-key-create', 'post', - 'api:accounts:ssh-key-create \'mykey\' \'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQChwPHzTTDKDpSbpa2+d22LcbQmsw92eLsUK3Fmei1fiGDkd34NsYCN8m7lsi3NbvdMS83CtPQPWiCveYPzFs1/hHc4PYj8opD2CNnr5iWVVbyaulCYHCgVv4aB/ojcexg8q483A4xJeF15TiCr/gu34rK6ucTvC/tn/rCwJBudczvEwt0klqYwv8Cl/ytaQboSuem5KgSjO3lMrb6CWtfSNhE43ZOw+UBFBqxIninN868vGMkIv9VY34Pwj54rPn/ItQd6Ef4B0KHHaGmzK0vfP+AK7FxNMoHnj3iYT33KZNqtDozdn5tYyH/bThPebEtgqUn+/w5l6wZIC/8zzvls/127ngHk+jNa0PlNyS2TxhPUK4NaPHIEnnrlp07JEYC4ImcBjaYCWAdcTcUkcJjwZQkN4bGmyO9cjICH98SdLD/HxqzTHeaYDbAX/Hu9HfaBb5dXLWsjw3Xc6hoVnUUZbMQyfgb0KgxDLh92eNGxJkpZiL0VDNOWCxDWsNpzwhLNkLqCvI6lyxiLaUzvJAk6dPaRhExmCbU1lDO2eR0FdSwC1TEhJOT9eDIK1r2hztZKs2oa5FNFfB/IFHVWasVFC9N2h/r/egB5zsRxC9MqBLRBq95NBxaRSFng6ML5WZSw41Qi4C/JWVm89rdj2WqScDHYyAdwyyppWU4T5c9Fmw== example@example.com\'', + ['api:accounts:ssh-key-create \'mykey\' \'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQChwPHzTTDKDpSbpa2+d22LcbQmsw92eLsUK3Fmei1fiGDkd34NsYCN8m7lsi3NbvdMS83CtPQPWiCveYPzFs1/hHc4PYj8opD2CNnr5iWVVbyaulCYHCgVv4aB/ojcexg8q483A4xJeF15TiCr/gu34rK6ucTvC/tn/rCwJBudczvEwt0klqYwv8Cl/ytaQboSuem5KgSjO3lMrb6CWtfSNhE43ZOw+UBFBqxIninN868vGMkIv9VY34Pwj54rPn/ItQd6Ef4B0KHHaGmzK0vfP+AK7FxNMoHnj3iYT33KZNqtDozdn5tYyH/bThPebEtgqUn+/w5l6wZIC/8zzvls/127ngHk+jNa0PlNyS2TxhPUK4NaPHIEnnrlp07JEYC4ImcBjaYCWAdcTcUkcJjwZQkN4bGmyO9cjICH98SdLD/HxqzTHeaYDbAX/Hu9HfaBb5dXLWsjw3Xc6hoVnUUZbMQyfgb0KgxDLh92eNGxJkpZiL0VDNOWCxDWsNpzwhLNkLqCvI6lyxiLaUzvJAk6dPaRhExmCbU1lDO2eR0FdSwC1TEhJOT9eDIK1r2hztZKs2oa5FNFfB/IFHVWasVFC9N2h/r/egB5zsRxC9MqBLRBq95NBxaRSFng6ML5WZSw41Qi4C/JWVm89rdj2WqScDHYyAdwyyppWU4T5c9Fmw== example@example.com\''], ], [ 'api:environments:file-copy', 'post', - '12-d314739e-296f-11e9-b210-d663bd873d93 --source=\'14-0c7e79ab-1c4a-424e-8446-76ae8be7e851\'', + [ + 'api:environments:file-copy 12-d314739e-296f-11e9-b210-d663bd873d93 --source=\'14-0c7e79ab-1c4a-424e-8446-76ae8be7e851\'', + 'api:environments:file-copy myapp.dev --source=\'14-0c7e79ab-1c4a-424e-8446-76ae8be7e851\'', + ], ], [ 'api:private-networks:create', 'post', - 'api:private-networks:create \'123e4567-e89b-12d3-a456-426614174000\' \'us-east-1\' \'customer-private-network\' --description=\'Private network for customer\' --label=\'anyLabel\' --isolation=\'{"dedicated_compute":false,"dedicated_network":false}\' --ingress=\'{"drupal_ssh":{"ingress_acls":["test-acls"]}}\' \'{"cidr":"114.7.55.1\/16","private_egress_access":{"drupal":true},"vpns":[{"name":"vpn1","gateway_ip":"10.10.10.10","routes":["127.0.0.1\/32","127.0.0.2\/32"],"tunnel1":{"shared_key":"sharedKey1","internal_cidr":"192.1.1.0\/24","ike_versions":"1","startup_action":"start","dpd_timeout_action":"stop"},"tunnel2":{"shared_key":"sharedKey2","internal_cidr":"192.1.1.0\/14","ike_versions":"1","startup_action":"start","dpd_timeout_action":"stop"}}],"vpc_peers":[{"name":"vpcPeer1","aws_account":"123456789012","vpc_id":"vpc-1234567890abcdef0","vpc_cidr":"120.24.16.1\/24"}]}\'', + ['api:private-networks:create \'123e4567-e89b-12d3-a456-426614174000\' \'us-east-1\' \'customer-private-network\' --description=\'Private network for customer\' --label=\'anyLabel\' --isolation=\'{"dedicated_compute":false,"dedicated_network":false}\' --ingress=\'{"drupal_ssh":{"ingress_acls":["test-acls"]}}\' \'{"cidr":"114.7.55.1\/16","private_egress_access":{"drupal":true},"vpns":[{"name":"vpn1","gateway_ip":"10.10.10.10","routes":["127.0.0.1\/32","127.0.0.2\/32"],"tunnel1":{"shared_key":"sharedKey1","internal_cidr":"192.1.1.0\/24","ike_versions":"1","startup_action":"start","dpd_timeout_action":"stop"},"tunnel2":{"shared_key":"sharedKey2","internal_cidr":"192.1.1.0\/14","ike_versions":"1","startup_action":"start","dpd_timeout_action":"stop"}}],"vpc_peers":[{"name":"vpcPeer1","aws_account":"123456789012","vpc_id":"vpc-1234567890abcdef0","vpc_cidr":"120.24.16.1\/24"}]}\''], + ], + [ + 'api:private-networks:update-isolation', + 'put', + ['api:private-networks:update-isolation --dedicated_compute=\'1\' --dedicated_network=\'1\''], + ], + [ + 'api:environments:livedev-disable', + 'post', + [ + 'api:environments:livedev-disable 12-d314739e-296f-11e9-b210-d663bd873d93 --discard=\'1\'', + 'api:environments:livedev-disable myapp.dev --discard=\'1\'', + ], + ], + [ + 'api:environments:code-deploy', + 'post', + [ + 'api:environments:code-deploy 12-d314739e-296f-11e9-b210-d663bd873d93 \'14-0c7e79ab-1c4a-424e-8446-76ae8be7e851\' --message=\'Optional commit message\'', + 'api:environments:code-deploy myapp.dev \'14-0c7e79ab-1c4a-424e-8446-76ae8be7e851\' --message=\'Optional commit message\'', + ], + ], + [ + 'api:environments:code-switch', + 'post', + [ + 'api:environments:code-switch 12-d314739e-296f-11e9-b210-d663bd873d93 \'my-feature-branch\'', + 'api:environments:code-switch myapp.dev \'my-feature-branch\'', + ], ], ]; } @@ -564,7 +596,7 @@ public static function providerTestApiCommandDefinitionRequestBody(): array * @param $method * @param $usage */ - public function testApiCommandDefinitionRequestBody(string $commandName, string $method, string $usage): void + public function testApiCommandDefinitionRequestBody(string $commandName, string $method, array $usage): void { $this->command = $this->getApiCommandByName($commandName); $resource = self::getResourceFromSpec($this->command->getPath(), $method); @@ -574,7 +606,7 @@ public function testApiCommandDefinitionRequestBody(string $commandName, string $paramKey = end($parts); $resource['requestBody'] = $cloudApiSpec['components']['requestBodies'][$paramKey]; } - $example = $resource['requestBody']['content']['application/hal+json']['example'] ?? $resource['requestBody']['content']['application/json']['schema']['example'] ?? null; + $example = $resource['requestBody']['content']['application/hal+json']['example'] ?? $resource['requestBody']['content']['application/json']['schema']['example'] ?? $resource['requestBody']['content']['application/json']['example'] ?? null; self::assertNotEmpty($example); foreach ($example as $propKey => $value) { $this->assertTrue( @@ -584,7 +616,7 @@ public function testApiCommandDefinitionRequestBody(string $commandName, string "Command {$this->command->getName()} does not have expected argument or option $propKey" ); } - $this->assertStringContainsString($usage, $this->command->getUsages()[0]); + $this->assertSame($usage, $this->command->getUsages()); } public function testGetApplicationUuidFromBltYml(): void