From 5d667aed2fea4b401ec597acfcaeeb5b026e266c Mon Sep 17 00:00:00 2001 From: Chris Novak Date: Tue, 18 Jun 2019 22:38:33 -0700 Subject: [PATCH 1/8] Drush command to create Apigee Edge role Fixes #210 apigee-edge:create-edge-role Drush command will create role will correct permissions for Apigee Edge connection. --- apigee_edge.services.yml | 4 + drush.services.yml | 2 +- src/Commands/ApigeeEdgeCommands.php | 73 +++- src/KeyEntityFormEnhancer.php | 2 +- src/Util/EdgeConnectionUtilService.php | 279 +++++++++++++++ .../EdgeConnectionUtilServiceInterface.php | 49 +++ tests/src/Functional/ApiProductAccessTest.php | 8 +- tests/src/Functional/DeveloperTest.php | 1 - .../Unit/Commands/ApigeeEdgeCommandsTest.php | 162 +++++++++ .../Util/EdgeConnectionUtilServiceTest.php | 336 ++++++++++++++++++ 10 files changed, 908 insertions(+), 8 deletions(-) create mode 100644 src/Util/EdgeConnectionUtilService.php create mode 100644 src/Util/EdgeConnectionUtilServiceInterface.php create mode 100644 tests/src/Unit/Commands/ApigeeEdgeCommandsTest.php create mode 100644 tests/src/Unit/Util/EdgeConnectionUtilServiceTest.php diff --git a/apigee_edge.services.yml b/apigee_edge.services.yml index 0582af150..7ffcfd236 100644 --- a/apigee_edge.services.yml +++ b/apigee_edge.services.yml @@ -13,6 +13,10 @@ services: apigee_edge.cli: class: Drupal\apigee_edge\CliService + apigee_edge.edge_connection_util: + class: Drupal\apigee_edge\util\EdgeConnectionUtilService + arguments: ['@http_client'] + apigee_edge.sdk_connector: class: Drupal\apigee_edge\SDKConnector arguments: ['@http_client_factory', '@key.repository', '@entity_type.manager', '@config.factory', '@module_handler', '@info_parser'] diff --git a/drush.services.yml b/drush.services.yml index ecdecdb61..7d088cb35 100644 --- a/drush.services.yml +++ b/drush.services.yml @@ -1,6 +1,6 @@ services: apigee_edge.commands: class: \Drupal\apigee_edge\Commands\ApigeeEdgeCommands - arguments: ['@apigee_edge.cli'] + arguments: ['@apigee_edge.cli', '@apigee_edge.edge_connection_util'] tags: - { name: drush.command } diff --git a/src/Commands/ApigeeEdgeCommands.php b/src/Commands/ApigeeEdgeCommands.php index 1656e7be2..d91067f39 100644 --- a/src/Commands/ApigeeEdgeCommands.php +++ b/src/Commands/ApigeeEdgeCommands.php @@ -19,7 +19,9 @@ namespace Drupal\apigee_edge\Commands; +use Consolidation\AnnotatedCommand\CommandData; use Drupal\apigee_edge\CliServiceInterface; +use Drupal\apigee_edge\Util\EdgeConnectionUtilServiceInterface; use Drush\Commands\DrushCommands; /** @@ -27,6 +29,12 @@ */ class ApigeeEdgeCommands extends DrushCommands { + // Default base url. + const DEFAULT_BASE_URL = 'https://api.enterprise.apigee.com/v1'; + + // Default role name to create in Apigee Edge. + const DEFAULT_ROLE_NAME = 'drupalportal'; + /** * The interoperability cli service. * @@ -34,14 +42,20 @@ class ApigeeEdgeCommands extends DrushCommands { */ protected $cliService; + protected $edgeConnectionUtilService; + /** * ApigeeEdgeCommands constructor. * * @param \Drupal\apigee_edge\CliServiceInterface $cli_service * The CLI service which allows interoperability. + * @param \Drupal\apigee_edge\Util\EdgeConnectionUtilServiceInterface $edge_connection_util_service + * The Edge connection utilty service. */ - public function __construct(CliServiceInterface $cli_service = NULL) { + public function __construct(CliServiceInterface $cli_service = NULL, EdgeConnectionUtilServiceInterface $edge_connection_util_service = NULL) { + parent::__construct(); $this->cliService = $cli_service; + $this->edgeConnectionUtilService = $edge_connection_util_service; } /** @@ -57,4 +71,61 @@ public function sync() { $this->cliService->sync($this->io(), 'dt'); } + /** + * Create a role in an Apigee organization. + * + * Create role with proper permissions for connecting a Drupal + * Apigee Edge module to the Edge org. You must run this script + * as a user with the orgadmin role. + * + * @param string $org + * The Apigee Edge org to create the role in. + * @param string $email + * An Apigee user email address with orgadmin role for this org. + * @param array $options + * Drush options for the command. + * + * @option password + * Password for the Apigee orgadmin user. If not set, you will be prompted + * for the password. + * @option base-url + * Base URL to use, defaults to public cloud URL. + * @option role-name + * The role to create in the Apigee Edge org. + * @usage drush create-edge-role myorg me@example.com + * Create "drupalportal" role as orgadmin me@example.com for org myorg. + * @usage drush create-edge-role myorg me@example.com --base-url=https://api.edge.example.com + * Create role on private Apigee Edge server "api.edge.example.com". + * @usage drush create-edge-role myorg me@example.com --role-name=portal + * Create role named "portal". + * @command apigee-edge:create-edge-role + * @aliases create-edge-role + */ + public function createEdgeRole( + string $org, + string $email, + array $options = [ + 'password' => NULL, + 'base-url' => self::DEFAULT_BASE_URL, + 'role-name' => self::DEFAULT_ROLE_NAME, + ]) { + $this->edgeConnectionUtilService->createEdgeRoleForDrupal($this->io(), 'dt', $org, $email, $options['password'], $options['base-url'], $options['role-name']); + } + + /** + * Validate function for the createEdge method. + * + * @hook validate apigee-edge:create-edge-role + */ + public function validateCreateEdgeRole(CommandData $commandData) { + // If the user did not specify a password, then prompt for one. + $password = $commandData->input()->getOption('password'); + if (empty($password)) { + $password = $this->io()->askHidden("Enter a password:", function ($value) { + return $value; + }); + $commandData->input()->setOption('password', $password); + } + } + } diff --git a/src/KeyEntityFormEnhancer.php b/src/KeyEntityFormEnhancer.php index 63e2b8565..714728cee 100644 --- a/src/KeyEntityFormEnhancer.php +++ b/src/KeyEntityFormEnhancer.php @@ -263,7 +263,7 @@ public function validateForm(array &$form, FormStateInterface $form_state): void return; } - // If there is a form error already do not nothing. + // If there is a form error already do not continue. if (!empty($form_state->getErrors())) { return; } diff --git a/src/Util/EdgeConnectionUtilService.php b/src/Util/EdgeConnectionUtilService.php new file mode 100644 index 000000000..ebea1c9a1 --- /dev/null +++ b/src/Util/EdgeConnectionUtilService.php @@ -0,0 +1,279 @@ +httpClient = $http_client; + } + + /** + * {@inheritdoc} + */ + public function createEdgeRoleForDrupal(StyleInterface $io, callable $t, string $org, string $email, string $password, string $base_url, string $role_name) { + + if (!$this->isValidEdgeCredentials($io, $t, $org, $email, $password, $base_url)) { + return; + } + + $does_role_exist = $this->doesRoleExist($org, $email, $password, $base_url, $role_name); + + // If role does not exist, create it. + if (!$does_role_exist) { + $io->text($t('Role !role does not exist. Creating role.', ['!role' => $role_name])); + + $url = $base_url . '/o/' . $org . '/userroles'; + try { + $this->httpClient->post($url, [ + 'body' => json_encode([ + 'role' => [$role_name], + ]), + 'auth' => [$email, $password], + 'headers' => [ + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ], + ]); + } + catch (TransferException $exception) { + $this->handleHttpClientExceptions($exception, $io, $t, $url, $org, $email); + return; + } + } + else { + $io->text('Role ' . $role_name . ' already exists.'); + } + + $this->setDefaultPermissions($io, $t, $org, $email, $password, $base_url, $role_name); + + $io->success($t('Role !role is configured. Log into apigee.com to assign a user to this role.', ['!role' => $role_name])); + } + + /** + * Set default permissions for a role used for Drupal portal connections. + * + * @param \Symfony\Component\Console\Style\StyleInterface $io + * The IO interface of the CLI tool calling the method. + * @param callable $t + * The translation function akin to t(). + * @param string $org + * The Edge org to create the permissions in. + * @param string $email + * The email of an Edge user with org admin role to make Edge API calls. + * @param string $password + * The password of an Edge user email to make Edge API calls. + * @param string $base_url + * The base url of the Edge API. + * @param string $role_name + * The role name to add the permissions to. + */ + protected function setDefaultPermissions(StyleInterface $io, callable $t, string $org, string $email, string $password, string $base_url, string $role_name) { + $io->text('Setting permissions on role ' . $role_name . '.'); + + $permissions = [ + // GET access. + '/' => ['get'], + '/environments/' => ['get'], + '/environments/*/stats/*' => ['get'], + '/userroles' => ['get'], + // GET & PUT access. + '/companies' => ['get', 'put'], + '/companies/*/apps' => ['get', 'put'], + '/apiproducts' => ['get', 'put'], + // GET, PUT, & DELETE access. + '/developers' => ['get', 'put', 'delete'], + '/developers/*/apps' => ['get', 'put', 'delete'], + '/developers/*/apps/*' => ['get', 'put', 'delete'], + '/companies/*' => ['get', 'put', 'delete'], + '/companies/*/apps/*' => ['get', 'put', 'delete'], + ]; + + $url = $base_url . '/o/' . $org . '/userroles/' . $role_name . '/permissions'; + try { + foreach ($permissions as $path => $permission_verbs) { + $body = json_encode([ + 'path' => $path, + 'permissions' => $permission_verbs, + ]); + $io->text($path . ' -> ' . implode(',', $permission_verbs)); + $this->httpClient->post($url, [ + 'body' => $body, + 'auth' => [$email, $password], + 'headers' => [ + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ], + ]); + } + } + catch (TransferException $exception) { + $this->handleHttpClientExceptions($exception, $io, $t, $url, $org, $email); + return; + } + } + + /** + * Check to see if role exists. + * + * @param string $org + * The Edge org to create the permissions in. + * @param string $email + * The email of an Edge user with org admin role to make Edge API calls. + * @param string $password + * The password of an Edge user with org admin role to make Edge API calls. + * @param string $base_url + * The base url of the Edge API. + * @param string $role_name + * The role name to add the permissions to. + * + * @return bool + * Returns true if the role exists, or false if it doesn't. + * + * @throws \GuzzleHttp\Exception\TransferException + */ + public function doesRoleExist(string $org, string $email, string $password, string $base_url, string $role_name) { + $url = $base_url . '/o/' . $org . '/userroles/' . $role_name; + try { + $this->httpClient->get($url, [ + 'auth' => [$email, $password], + 'headers' => ['Accept' => 'application/json'], + ]); + } + catch (ClientException $exception) { + if ($exception->getCode() == 404) { + // Role does not exist. + return FALSE; + } + // Any other response was an exception. + throw $exception; + } + // If 200 response, role exists. + return TRUE; + } + + /** + * Validate the Apigee Edge org connection settings. + * + * @param \Symfony\Component\Console\Style\StyleInterface $io + * The IO interface of the CLI tool calling the method. + * @param callable $t + * The translation function akin to t(). + * @param string $org + * The Edge org to connect to. + * @param string $email + * The email of an Edge user with org admin role to make Edge API calls. + * @param string $password + * The password of an Edge user email to make Edge API calls. + * @param string $base_url + * The base url of the Edge API. + * + * @return bool + * Return true if the Edge API can be called, false if it cannot. + */ + public function isValidEdgeCredentials(StyleInterface $io, callable $t, string $org, string $email, string $password, string $base_url) { + $url = $base_url . '/o/' . $org; + try { + $response = $this->httpClient->get($url, [ + 'auth' => [$email, $password], + 'headers' => ['Accept' => 'application/json'], + ]); + } + catch (TransferException $exception) { + $this->handleHttpClientExceptions($exception, $io, $t, $url, $org, $email); + return FALSE; + } + + $body = json_decode($response->getBody()); + if (JSON_ERROR_NONE !== json_last_error()) { + $io->error($t('Unable to parse response from Apigee Edge into JSON. !error ', ['error' => json_last_error_msg()])); + $io->section($t('Server response from !url', ['!url' => $url])); + $io->text($response->getBody()); + return FALSE; + } + if (!isset($body->name)) { + $io->warning($t('The response from !url did not contain the org name.', ['!url' => $url])); + } + else { + $io->success($t('Connected to Edge org !org.', ['!org' => $body->name])); + } + return TRUE; + } + + /** + * Print out helpful information to user running command when error happens. + * + * @param \GuzzleHttp\Exception\TransferException $exception + * The exception thrown. + * @param \Symfony\Component\Console\Style\StyleInterface $io + * The IO interface of the CLI tool calling the method. + * @param callable $t + * The translation function akin to t(). + * @param string $url + * The url being connected to. + * @param string $org + * The organization to connect to. + * @param string $email + * The email of an Edge user with org admin role to make Edge API calls. + */ + public function handleHttpClientExceptions(TransferException $exception, StyleInterface $io, callable $t, string $url, string $org, string $email): void { + $io->error($t('Error connecting to Apigee Edge. !exception_message', ['!exception_message' => $exception->getMessage()])); + switch ($exception->getCode()) { + case 0: + $io->note($t('Your system may not be able to connect to !url.', [ + '!url' => $url, + ])); + return; + + case 403: + $io->note($t('User !email may not have the orgadmin role for Apigee Edge org !org.', [ + '!email' => $email, + '!org' => $org, + ])); + return; + + case 302: + $io->note($t('Edge endpoint gives a redirect response, is the url !url does not seem to be a valid Apigee Edge endpoint.', ['!url' => $url])); + return; + } + } + +} diff --git a/src/Util/EdgeConnectionUtilServiceInterface.php b/src/Util/EdgeConnectionUtilServiceInterface.php new file mode 100644 index 000000000..fd7f6b4e7 --- /dev/null +++ b/src/Util/EdgeConnectionUtilServiceInterface.php @@ -0,0 +1,49 @@ +> Bypass user. $this->drupalLogin($this->users[self::USER_WITH_BYPASS_PERM]); - // Even if a user has bypass permission it should see only those API - // Products on an other user's add/edit form that the other user has + // Even if a user has bypass permission they should see only those API + // Products on on an other user's add/edit form that the other user has // access. $this->drupalGet(Url::fromRoute('entity.developer_app.add_form_for_developer', [ 'user' => $this->users[AccountInterface::AUTHENTICATED_ROLE]->id(), @@ -319,7 +319,7 @@ protected function developerAppEditFormTest() { ])); $onlyPublicProductVisible(); - // But on the its own add/edit app forms it should see all API products. + // But on the its own add/edit app forms they should see all API products. $this->drupalGet(Url::fromRoute('entity.developer_app.add_form_for_developer', [ 'user' => $this->users[self::USER_WITH_BYPASS_PERM]->id(), ])); @@ -456,7 +456,7 @@ protected function messageIfUserShouldHaveAccessByRole(string $operation, UserIn } /** - * Error message, when a user should have access because it has bypass perm. + * Error message, when a user should have access because they have bypass perm. * * @param string $operation * Operation on API product. diff --git a/tests/src/Functional/DeveloperTest.php b/tests/src/Functional/DeveloperTest.php index 89a38a7ac..f49a7c395 100644 --- a/tests/src/Functional/DeveloperTest.php +++ b/tests/src/Functional/DeveloperTest.php @@ -23,7 +23,6 @@ use Apigee\Edge\Api\Management\Controller\CompanyMembersController; use Apigee\Edge\Api\Management\Entity\Company; use Apigee\Edge\Api\Management\Structure\CompanyMembership; -use Drupal\apigee_edge\Entity\Developer; use Drupal\apigee_edge\Entity\DeveloperInterface; use Drupal\Core\Url; diff --git a/tests/src/Unit/Commands/ApigeeEdgeCommandsTest.php b/tests/src/Unit/Commands/ApigeeEdgeCommandsTest.php new file mode 100644 index 000000000..274f35ad2 --- /dev/null +++ b/tests/src/Unit/Commands/ApigeeEdgeCommandsTest.php @@ -0,0 +1,162 @@ +edge_connection_util_service = $this->prophesize(EdgeConnectionUtilServiceInterface::class); + $this->cli_service = $this->prophesize(CliServiceInterface::class); + $this->apigee_edge_commands = new ApigeeEdgeCommands($this->cli_service->reveal(), $this->edge_connection_util_service->reveal()); + + // Set io in DrushCommands to a mock. + $apigee_edge_commands_reflection = new ReflectionClass($this->apigee_edge_commands); + $reflection_io_property = $apigee_edge_commands_reflection->getProperty('io'); + $reflection_io_property->setAccessible(TRUE); + $this->io = $this->prophesize(DrushStyle::class); + $reflection_io_property->setValue($this->apigee_edge_commands, $this->io->reveal()); + + $this->io->askHidden(Argument::type('string'), Argument::any()) + ->willReturn('I<3APIS!'); + } + + /** + * Calls to Drush command should pass through to CLI service. + */ + public function testCreateEdgeRole() { + + $drush_options = [ + 'password' => 'opensesame', + 'base-url' => ApigeeEdgeCommands::DEFAULT_BASE_URL, + 'role-name' => 'portalRole', + ]; + + $this->apigee_edge_commands->createEdgeRole('orgA', 'emailA', $drush_options); + + $this->edge_connection_util_service->createEdgeRoleForDrupal( + Argument::type(DrushStyle::class), + Argument::type('string'), + Argument::type('string'), + Argument::type('string'), + Argument::type('string'), + Argument::type('string'), + Argument::type('string') + ) + ->shouldHaveBeenCalledTimes(1); + + } + + /** + * Test validateCreateEdgeRole function does not prompt for password. + * + * When password option is set, do not prompt for password. + * + * @throws \ReflectionException + */ + public function testValidatePasswordParam() { + + $command_data_input = $this->prophesize(InputInterface::class); + $command_data_input->getOption('password')->willReturn('secret'); + $command_data = $this->prophesize(CommandData::class); + $command_data->input()->willReturn($command_data_input->reveal()); + + $this->apigee_edge_commands->validateCreateEdgeRole($command_data->reveal()); + + // Make sure password was not prompted to user. + $command_data_input->getOption('password')->shouldHaveBeenCalled(); + $this->io->askHidden(Argument::type('string'), Argument::any()) + ->shouldNotBeCalled(); + $command_data_input->setOption()->shouldNotHaveBeenCalled(); + } + + /** + * Test validateCreateEdgeRole prompts for password. + * + * When password option not set, password should be inputted by user. + * + * @throws \ReflectionException + */ + public function testValidatePasswordParamEmpty() { + + $command_data_input = $this->prophesize(InputInterface::class); + $command_data_input->getOption('password')->willReturn(NULL); + $command_data_input->setOption(Argument::type('string'), Argument::type('string'))->willReturn(); + $command_data = $this->prophesize(CommandData::class); + $command_data->input()->willReturn($command_data_input->reveal()); + + $this->apigee_edge_commands->validateCreateEdgeRole($command_data->reveal()); + + // Make sure password not requested. + $command_data_input->getOption('password')->shouldHaveBeenCalled(); + $this->io->askHidden(Argument::type('string'), Argument::any()) + ->shouldBeCalled(); + $command_data_input->setOption('password', 'I<3APIS!') + ->shouldHaveBeenCalled(); + + } + + } +} + +namespace { + + /** + * Mock out dt() so function exists for tests. + * + * @param string $message + * The string with placeholders to be interpolated. + * @param array $context + * An associative array of values to be inserted into the message. + * + * @return string + * The resulting string with all placeholders filled in. + */ + function dt(string $message, array $context = []): string { + return StringUtils::interpolate($message, $context); + } + +} diff --git a/tests/src/Unit/Util/EdgeConnectionUtilServiceTest.php b/tests/src/Unit/Util/EdgeConnectionUtilServiceTest.php new file mode 100644 index 000000000..f3dcd3a25 --- /dev/null +++ b/tests/src/Unit/Util/EdgeConnectionUtilServiceTest.php @@ -0,0 +1,336 @@ +http_client = $this->prophesize(Client::class); + } + + /** + * Should return true if creds are valid. + */ + public function testIsValidEdgeCredentialsNotValid() { + $body = "

not json

"; + + $response = $this->prophesize(Response::class); + $response->getBody() + ->shouldBeCalledTimes(2) + ->willReturn($body); + + $io = $this->prophesize(StyleInterface::class); + $io->error(Argument::containingString('Unable to parse response from Apigee Edge into JSON')) + ->shouldBeCalledTimes(1); + $io->section(Argument::type('string')) + ->shouldBeCalledTimes(1); + $io->text(Argument::type('string')) + ->shouldBeCalledTimes(1); + + $this->http_client + ->get(Argument::type('string'), Argument::type('array')) + ->willReturn($response->reveal()); + + $edge_connection_util_service = new EdgeConnectionUtilService($this->http_client->reveal()); + $is_valid_creds = $edge_connection_util_service->isValidEdgeCredentials($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->base_url); + $this->assertEquals(FALSE, $is_valid_creds, 'Credentials are not valid, should return false.'); + } + + /** + * Should return true if creds are valid. + */ + public function testIsValidEdgeCredentialsValid() { + $body = '{ "name": "' . $this->org . '" }'; + + $response = $this->prophesize(Response::class); + $response->getBody() + ->shouldBeCalledTimes(1) + ->willReturn($body); + + $this->http_client + ->get(Argument::type('string'), Argument::type('array')) + ->willReturn($response->reveal()); + + $io = $this->prophesize(StyleInterface::class); + $io->error(Argument::type('string')) + ->shouldNotBeCalled(); + $io->section(Argument::type('string')) + ->shouldNotBeCalled(); + $io->text(Argument::type('string')) + ->shouldNotBeCalled(); + $io->success(Argument::type('string')) + ->shouldBeCalled(); + + $edge_connection_util_service = new EdgeConnectionUtilService($this->http_client->reveal()); + $is_valid_creds = $edge_connection_util_service->isValidEdgeCredentials($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->base_url); + $this->assertEquals(TRUE, $is_valid_creds, 'Credentials are not valid, should return false.'); + } + + /** + * Should make a call to create role if does not exist. + */ + public function testCreateEdgeRoleWhenRoleDoesNotExistTest() { + + $io = $this->prophesize(StyleInterface::class); + $io->success(Argument::exact('Connected to Edge org ' . $this->org . '.'))->shouldBeCalledTimes(1); + $io->success(Argument::containingString('Role ' . $this->role_name . ' is configured.'))->shouldBeCalledTimes(1); + $io->text(Argument::containingString('Role ' . $this->role_name . ' already exists.'))->shouldBeCalledTimes(1); + $io->text(Argument::containingString('Setting permissions on role ' . $this->role_name . '.'))->shouldBeCalledTimes(1); + $io->text(Argument::containingString('/'))->shouldBeCalledTimes(12); + + $body = '{ "name": "' . $this->org . '" }'; + $response = $this->prophesize(Response::class); + $response->getBody() + ->shouldBeCalledTimes(1) + ->willReturn($body); + + $this->http_client + ->get(Argument::exact($this->base_url . '/o/' . $this->org), Argument::type('array')) + ->shouldBeCalledTimes(1) + ->willReturn($response->reveal()); + + $this->http_client + ->get(Argument::exact($this->base_url . '/o/' . $this->org . '/userroles/' . $this->role_name), Argument::type('array')) + ->shouldBeCalledTimes(1) + ->willReturn($response->reveal()); + + $this->http_client + ->post(Argument::type('string'), Argument::type('array')) + ->willReturn($response->reveal()); + + $edge_connection_util_service = new EdgeConnectionUtilService($this->http_client->reveal()); + $edge_connection_util_service->createEdgeRoleForDrupal($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->base_url, $this->role_name); + } + + /** + * Do not try to create role if it already exists. + */ + public function testCreateEdgeRoleForDrupalWhenRoleExistsTest() { + $io = $this->prophesize(StyleInterface::class); + $io->success(Argument::exact('Connected to Edge org ' . $this->org . '.'))->shouldBeCalledTimes(1); + $io->success(Argument::containingString('Role ' . $this->role_name . ' is configured.'))->shouldBeCalledTimes(1); + $io->text(Argument::containingString('Role ' . $this->role_name . ' already exists.'))->shouldBeCalledTimes(1); + $io->text(Argument::containingString('Setting permissions on role ' . $this->role_name . '.'))->shouldBeCalledTimes(1); + $io->text(Argument::containingString('/'))->shouldBeCalledTimes(12); + + $response_org = $this->prophesize(Response::class); + $response_org->getBody() + ->shouldBeCalledTimes(1) + ->willReturn('{ "name": "' . $this->org . '" }'); + + $this->http_client + ->get(Argument::exact($this->base_url . '/o/' . $this->org), Argument::type('array')) + ->shouldBeCalledTimes(1) + ->willReturn($response_org->reveal()); + + $response_user_role = $this->prophesize(Response::class); + + $this->http_client + ->get(Argument::exact($this->base_url . '/o/' . $this->org . '/userroles/' . $this->role_name), Argument::type('array')) + ->willReturn($response_user_role->reveal()); + + $this->http_client + ->post(Argument::exact($this->base_url . '/o/' . $this->org . '/userroles/' . $this->role_name . '/permissions'), Argument::type('array')) + ->shouldBeCalledTimes(12); + + $edge_connection_util_service = new EdgeConnectionUtilService($this->http_client->reveal()); + $edge_connection_util_service->createEdgeRoleForDrupal($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->base_url, $this->role_name); + } + + /** + * Validate doesRoleExist works when role does not exist. + */ + public function testDoesRoleExistTrue() { + + $this->http_client + ->get(Argument::cetera()) + ->shouldBeCalledTimes(1) + ->willReturn(NULL); + + $edge_connection_util_service = new EdgeConnectionUtilService($this->http_client->reveal()); + $does_role_exist = $edge_connection_util_service->doesRoleExist($this->org, $this->email, $this->password, $this->base_url, $this->role_name); + $this->assertEquals(TRUE, $does_role_exist, 'Method doesRoleExist() should return true when role exists.'); + } + + /** + * Validate doesRoleExist works when role exists. + */ + public function testDoesRoleExistNotTrue() { + + $request = $this->prophesize(RequestInterface::class); + $response = $this->prophesize(Response::class); + $response->getStatusCode()->willReturn(404); + + // Http client throws exception when role does not exist. + $exception = new ClientException('Role does not exist.', $request->reveal(), $response->reveal()); + + $this->http_client + ->get(Argument::type('string'), Argument::type('array')) + ->willThrow($exception); + + $edge_connection_util_service = new EdgeConnectionUtilService($this->http_client->reveal()); + $does_role_exist = $edge_connection_util_service->doesRoleExist($this->org, $this->email, $this->password, $this->base_url, $this->role_name); + + $this->assertEquals(FALSE, $does_role_exist, 'Method doesRoleExist() should return false when role exists.'); + } + + /** + * Validate when exception thrown function works correctly. + */ + public function testDoesRoleExistExceptionThrown() { + $request = $this->prophesize(RequestInterface::class); + $response = $this->prophesize(Response::class); + $response->getStatusCode()->willReturn(500); + + // Http client throws exception if network or server error happens. + $exception = new ServerException('Server error.', $request->reveal(), $response->reveal()); + $this->expectException(ServerException::class); + $this->http_client + ->get(Argument::type('string'), Argument::type('array')) + ->willThrow($exception); + + $edge_connection_util_service = new EdgeConnectionUtilService($this->http_client->reveal()); + $edge_connection_util_service->doesRoleExist($this->org, $this->email, $this->password, $this->base_url, $this->role_name); + } + + /** + * Make sure method outputs more info for error codes. + */ + public function testHandleHttpClientExceptions0Code() { + $exception = $this->prophesize(TransferException::class); + $io = $this->prophesize(StyleInterface::class); + $io->error(Argument::containingString('Error connecting to Apigee Edge'))->shouldBeCalledTimes(1); + $io->note(Argument::containingString('Your system may not be able to connect'))->shouldBeCalledTimes(1); + + $edge_connection_util_service = new EdgeConnectionUtilService($this->http_client->reveal()); + $edge_connection_util_service->handleHttpClientExceptions($exception->reveal(), $io->reveal(), [$this, 'mockDt'], 'http://api.apigee.com/test', $this->org, $this->email); + } + + /** + * Make sure method outputs more info for error codes. + */ + public function testHandleHttpClientExceptions403Code() { + $request = $this->prophesize(RequestInterface::class); + $response = $this->prophesize(Response::class); + $response->getStatusCode()->willReturn(403); + + $exception = new ClientException('Forbidden', $request->reveal(), $response->reveal()); + + $io = $this->prophesize(StyleInterface::class); + $io->error(Argument::containingString('Error connecting to Apigee Edge'))->shouldBeCalledTimes(1); + $io->note(Argument::containingString('User ' . $this->email . ' may not have the orgadmin role'))->shouldBeCalledTimes(1); + + $edge_connection_util_service = new EdgeConnectionUtilService($this->http_client->reveal()); + $edge_connection_util_service->handleHttpClientExceptions($exception, $io->reveal(), [$this, 'mockDt'], 'http://api.apigee.com/test', $this->org, $this->email); + } + + /** + * Make sure method outputs more info for error codes. + */ + public function testHandleHttpClientExceptions302Code() { + $request = $this->prophesize(RequestInterface::class); + $response = $this->prophesize(Response::class); + $response->getStatusCode()->willReturn(302); + + $exception = new ClientException('Forbidden', $request->reveal(), $response->reveal()); + + $io = $this->prophesize(StyleInterface::class); + $io->error(Argument::containingString('Error connecting to Apigee Edge'))->shouldBeCalledTimes(1); + $io->note(Argument::containingString('the url ' . $this->base_url . '/test' . ' does not seem to be a valid Apigee Edge endpoint.'))->shouldBeCalledTimes(1); + + $edge_connection_util_service = new EdgeConnectionUtilService($this->http_client->reveal()); + $edge_connection_util_service->handleHttpClientExceptions($exception, $io->reveal(), [$this, 'mockDt'], $this->base_url . '/test', $this->org, $this->email); + } + + /** + * Test setDefaultPermissions method. + * + * @throws \ReflectionException + */ + public function testSetDefaultPermissions() { + $edge_connection_util_service = new EdgeConnectionUtilService($this->http_client->reveal()); + $io = $this->prophesize(StyleInterface::class); + + $this->http_client->post(Argument::type('string'), Argument::type('array'))->shouldBeCalledTimes(12); + + // Make method under test not private. + $edge_connection_util_service_reflection = new ReflectionClass($edge_connection_util_service); + $method_set_default_permissions = $edge_connection_util_service_reflection->getMethod('setDefaultPermissions'); + $method_set_default_permissions->setAccessible(TRUE); + + $args = [ + $io->reveal(), + [$this, 'mockDt'], + $this->org, + $this->email, + $this->password, + $this->base_url, + $this->role_name, + ]; + $method_set_default_permissions->invokeArgs($edge_connection_util_service, $args); + } + + /** + * Mock translation method. + * + * @param string $message + * The message to return. + * @param array $context + * The context of vars to replace. + * + * @return string + * The message with context. + */ + public function mockDt(string $message, array $context): string { + // Do the same thing as Drush dt(). + return StringUtils::interpolate($message, $context); + } + +} From 9e0e91065101581ebe2fc5f00999f79ff39ef23c Mon Sep 17 00:00:00 2001 From: Chris Novak Date: Wed, 19 Jun 2019 16:41:59 -0700 Subject: [PATCH 2/8] Adding tests for Edge API interface --- .../Util/EdgeConnectionUtilServiceTest.php | 189 ++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 tests/src/Kernel/Util/EdgeConnectionUtilServiceTest.php diff --git a/tests/src/Kernel/Util/EdgeConnectionUtilServiceTest.php b/tests/src/Kernel/Util/EdgeConnectionUtilServiceTest.php new file mode 100644 index 000000000..7d66e2a12 --- /dev/null +++ b/tests/src/Kernel/Util/EdgeConnectionUtilServiceTest.php @@ -0,0 +1,189 @@ +markTestSkipped('Environment variable ' . $environment_var . ' is not set, cannot run tests. See CONTRIBUTING.md for more information.'); + } + } + + // Get environment variables for Edge connection. + $this->endpoint = getenv('APIGEE_EDGE_ENDPOINT'); + $this->organization = getenv('APIGEE_EDGE_ORGANIZATION'); + $this->orgadmin_email = getenv('APIGEE_EDGE_USERNAME'); + $this->orgadmin_password = getenv('APIGEE_EDGE_PASSWORD'); + + /** @var \GuzzleHttp\Client $client */ + $this->http_client = $this->container->get('http_client'); + } + + /** + * {@inheritdoc} + */ + protected function tearDown() { + $url = $this->endpoint . '/o/' . $this->organization . '/userroles/' . self::TEST_ROLE_NAME; + $response = $this->http_client->get($url, [ + 'http_errors' => FALSE, + 'auth' => [$this->orgadmin_email, $this->orgadmin_password], + 'headers' => [ + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ], + ]); + + if ($response->getStatusCode() == 200) { + $url = $this->endpoint . '/o/' . $this->organization . '/userroles/' . self::TEST_ROLE_NAME; + $this->http_client->delete($url, [ + 'auth' => [$this->orgadmin_email, $this->orgadmin_password], + 'headers' => [ + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ], + ]); + } + parent::tearDown(); + + } + + /** + * Fix for outbound HTTP requests fail with KernelTestBase. + * + * See comment #10: + * https://www.drupal.org/project/drupal/issues/2571475#comment-11938008 + */ + public function alter(ContainerBuilder $container) { + $container->removeDefinition('test.http_client.middleware'); + } + + /** + * Test actual call to Edge API that IsValidEdgeCredentials() uses. + */ + public function testIsValidEdgeCredentialsEdgeApi() { + $url = $this->endpoint . '/o/' . $this->organization; + $response = $this->http_client->get($url, [ + 'auth' => [$this->orgadmin_email, $this->orgadmin_password], + 'headers' => ['Accept' => 'application/json'], + ]); + + $body = json_decode($response->getBody()); + $this->assertTrue(isset($body->name), 'Edge org entity should contain "name" attribute.'); + $this->assertEquals($this->organization, $body->name, 'Edge org name attribute should match org being called in url.'); + } + + /** + * Test Edge API response/request for doesRoleExist() + */ + public function testDoesRoleExist() { + // Role should not exist. + $url = $this->endpoint . '/o/' . $this->organization . '/userroles/' . self::TEST_ROLE_NAME; + + $response = $this->http_client->get($url, [ + 'http_errors' => FALSE, + 'auth' => [$this->orgadmin_email, $this->orgadmin_password], + 'headers' => [ + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ], + ]); + $this->assertEquals('404', $response->getStatusCode(), 'Role that does not exist should return 404.'); + + } + + /** + * Test Edge API for creating role and setting permissions. + */ + public function testCreateEdgeRoleAndSetPermissions() { + + $url = $this->endpoint . '/o/' . $this->organization . '/userroles'; + $response = $this->http_client->post($url, [ + 'body' => json_encode([ + 'role' => [self::TEST_ROLE_NAME], + ]), + 'auth' => [$this->orgadmin_email, $this->orgadmin_password], + 'headers' => [ + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ], + ]); + $this->assertEquals('201', $response->getStatusCode(), 'Role should be created.'); + + // Add permissions to this role. + $url = $this->endpoint . '/o/' . $this->organization . '/userroles/' . self::TEST_ROLE_NAME . '/permissions'; + $body = json_encode([ + 'path' => '/developers', + 'permissions' => ['get', 'put', 'delete'], + ]); + $response = $this->http_client->post($url, [ + 'body' => $body, + 'auth' => [$this->orgadmin_email, $this->orgadmin_password], + 'headers' => [ + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ], + ]); + $this->assertEquals('201', $response->getStatusCode(), 'Permission on role should be created.'); + } + +} From 1a06c1c79121de84807a8b331e6ca60754fb0956 Mon Sep 17 00:00:00 2001 From: Chris Novak Date: Thu, 20 Jun 2019 10:17:21 -0700 Subject: [PATCH 3/8] Class properties should use camel case --- .../Util/EdgeConnectionUtilServiceTest.php | 36 +++---- .../Unit/Commands/ApigeeEdgeCommandsTest.php | 24 ++--- .../Util/EdgeConnectionUtilServiceTest.php | 98 +++++++++---------- 3 files changed, 79 insertions(+), 79 deletions(-) diff --git a/tests/src/Kernel/Util/EdgeConnectionUtilServiceTest.php b/tests/src/Kernel/Util/EdgeConnectionUtilServiceTest.php index 7d66e2a12..306dd965a 100644 --- a/tests/src/Kernel/Util/EdgeConnectionUtilServiceTest.php +++ b/tests/src/Kernel/Util/EdgeConnectionUtilServiceTest.php @@ -45,10 +45,10 @@ class EdgeConnectionUtilServiceTest extends KernelTestBase implements ServiceMod protected $endpoint; protected $organization; - protected $orgadmin_email; - protected $orgadmin_password; + protected $orgadminEmail; + protected $orgadminPassword; - protected $http_client; + protected $httpClient; /** * {@inheritdoc} @@ -72,11 +72,11 @@ protected function setUp() { // Get environment variables for Edge connection. $this->endpoint = getenv('APIGEE_EDGE_ENDPOINT'); $this->organization = getenv('APIGEE_EDGE_ORGANIZATION'); - $this->orgadmin_email = getenv('APIGEE_EDGE_USERNAME'); - $this->orgadmin_password = getenv('APIGEE_EDGE_PASSWORD'); + $this->orgadminEmail = getenv('APIGEE_EDGE_USERNAME'); + $this->orgadminPassword = getenv('APIGEE_EDGE_PASSWORD'); /** @var \GuzzleHttp\Client $client */ - $this->http_client = $this->container->get('http_client'); + $this->httpClient = $this->container->get('http_client'); } /** @@ -84,9 +84,9 @@ protected function setUp() { */ protected function tearDown() { $url = $this->endpoint . '/o/' . $this->organization . '/userroles/' . self::TEST_ROLE_NAME; - $response = $this->http_client->get($url, [ + $response = $this->httpClient->get($url, [ 'http_errors' => FALSE, - 'auth' => [$this->orgadmin_email, $this->orgadmin_password], + 'auth' => [$this->orgadminEmail, $this->orgadminPassword], 'headers' => [ 'Accept' => 'application/json', 'Content-Type' => 'application/json', @@ -95,8 +95,8 @@ protected function tearDown() { if ($response->getStatusCode() == 200) { $url = $this->endpoint . '/o/' . $this->organization . '/userroles/' . self::TEST_ROLE_NAME; - $this->http_client->delete($url, [ - 'auth' => [$this->orgadmin_email, $this->orgadmin_password], + $this->httpClient->delete($url, [ + 'auth' => [$this->orgadminEmail, $this->orgadminPassword], 'headers' => [ 'Accept' => 'application/json', 'Content-Type' => 'application/json', @@ -122,8 +122,8 @@ public function alter(ContainerBuilder $container) { */ public function testIsValidEdgeCredentialsEdgeApi() { $url = $this->endpoint . '/o/' . $this->organization; - $response = $this->http_client->get($url, [ - 'auth' => [$this->orgadmin_email, $this->orgadmin_password], + $response = $this->httpClient->get($url, [ + 'auth' => [$this->orgadminEmail, $this->orgadminPassword], 'headers' => ['Accept' => 'application/json'], ]); @@ -139,9 +139,9 @@ public function testDoesRoleExist() { // Role should not exist. $url = $this->endpoint . '/o/' . $this->organization . '/userroles/' . self::TEST_ROLE_NAME; - $response = $this->http_client->get($url, [ + $response = $this->httpClient->get($url, [ 'http_errors' => FALSE, - 'auth' => [$this->orgadmin_email, $this->orgadmin_password], + 'auth' => [$this->orgadminEmail, $this->orgadminPassword], 'headers' => [ 'Accept' => 'application/json', 'Content-Type' => 'application/json', @@ -157,11 +157,11 @@ public function testDoesRoleExist() { public function testCreateEdgeRoleAndSetPermissions() { $url = $this->endpoint . '/o/' . $this->organization . '/userroles'; - $response = $this->http_client->post($url, [ + $response = $this->httpClient->post($url, [ 'body' => json_encode([ 'role' => [self::TEST_ROLE_NAME], ]), - 'auth' => [$this->orgadmin_email, $this->orgadmin_password], + 'auth' => [$this->orgadminEmail, $this->orgadminPassword], 'headers' => [ 'Accept' => 'application/json', 'Content-Type' => 'application/json', @@ -175,9 +175,9 @@ public function testCreateEdgeRoleAndSetPermissions() { 'path' => '/developers', 'permissions' => ['get', 'put', 'delete'], ]); - $response = $this->http_client->post($url, [ + $response = $this->httpClient->post($url, [ 'body' => $body, - 'auth' => [$this->orgadmin_email, $this->orgadmin_password], + 'auth' => [$this->orgadminEmail, $this->orgadminPassword], 'headers' => [ 'Accept' => 'application/json', 'Content-Type' => 'application/json', diff --git a/tests/src/Unit/Commands/ApigeeEdgeCommandsTest.php b/tests/src/Unit/Commands/ApigeeEdgeCommandsTest.php index 274f35ad2..e9d5fc1bb 100644 --- a/tests/src/Unit/Commands/ApigeeEdgeCommandsTest.php +++ b/tests/src/Unit/Commands/ApigeeEdgeCommandsTest.php @@ -36,11 +36,11 @@ */ class ApigeeEdgeCommandsTest extends UnitTestCase { - protected $apigee_edge_commands; + protected $apigeeEdgeCommands; - protected $edge_connection_util_service; + protected $edgeConnectionUtilService; - protected $cli_service; + protected $cliService; protected $io; @@ -49,16 +49,16 @@ class ApigeeEdgeCommandsTest extends UnitTestCase { */ protected function setUp() { parent::setUp(); - $this->edge_connection_util_service = $this->prophesize(EdgeConnectionUtilServiceInterface::class); - $this->cli_service = $this->prophesize(CliServiceInterface::class); - $this->apigee_edge_commands = new ApigeeEdgeCommands($this->cli_service->reveal(), $this->edge_connection_util_service->reveal()); + $this->edgeConnectionUtilService = $this->prophesize(EdgeConnectionUtilServiceInterface::class); + $this->cliService = $this->prophesize(CliServiceInterface::class); + $this->apigeeEdgeCommands = new ApigeeEdgeCommands($this->cliService->reveal(), $this->edgeConnectionUtilService->reveal()); // Set io in DrushCommands to a mock. - $apigee_edge_commands_reflection = new ReflectionClass($this->apigee_edge_commands); + $apigee_edge_commands_reflection = new ReflectionClass($this->apigeeEdgeCommands); $reflection_io_property = $apigee_edge_commands_reflection->getProperty('io'); $reflection_io_property->setAccessible(TRUE); $this->io = $this->prophesize(DrushStyle::class); - $reflection_io_property->setValue($this->apigee_edge_commands, $this->io->reveal()); + $reflection_io_property->setValue($this->apigeeEdgeCommands, $this->io->reveal()); $this->io->askHidden(Argument::type('string'), Argument::any()) ->willReturn('I<3APIS!'); @@ -75,9 +75,9 @@ public function testCreateEdgeRole() { 'role-name' => 'portalRole', ]; - $this->apigee_edge_commands->createEdgeRole('orgA', 'emailA', $drush_options); + $this->apigeeEdgeCommands->createEdgeRole('orgA', 'emailA', $drush_options); - $this->edge_connection_util_service->createEdgeRoleForDrupal( + $this->edgeConnectionUtilService->createEdgeRoleForDrupal( Argument::type(DrushStyle::class), Argument::type('string'), Argument::type('string'), @@ -104,7 +104,7 @@ public function testValidatePasswordParam() { $command_data = $this->prophesize(CommandData::class); $command_data->input()->willReturn($command_data_input->reveal()); - $this->apigee_edge_commands->validateCreateEdgeRole($command_data->reveal()); + $this->apigeeEdgeCommands->validateCreateEdgeRole($command_data->reveal()); // Make sure password was not prompted to user. $command_data_input->getOption('password')->shouldHaveBeenCalled(); @@ -128,7 +128,7 @@ public function testValidatePasswordParamEmpty() { $command_data = $this->prophesize(CommandData::class); $command_data->input()->willReturn($command_data_input->reveal()); - $this->apigee_edge_commands->validateCreateEdgeRole($command_data->reveal()); + $this->apigeeEdgeCommands->validateCreateEdgeRole($command_data->reveal()); // Make sure password not requested. $command_data_input->getOption('password')->shouldHaveBeenCalled(); diff --git a/tests/src/Unit/Util/EdgeConnectionUtilServiceTest.php b/tests/src/Unit/Util/EdgeConnectionUtilServiceTest.php index f3dcd3a25..3bba09498 100644 --- a/tests/src/Unit/Util/EdgeConnectionUtilServiceTest.php +++ b/tests/src/Unit/Util/EdgeConnectionUtilServiceTest.php @@ -39,20 +39,20 @@ */ class EdgeConnectionUtilServiceTest extends UnitTestCase { - protected $base_url = 'http://api.apigee.com'; + protected $baseUrl = 'http://api.apigee.com'; protected $email = 'noreply@apigee.com'; protected $password = 'secret'; protected $org = 'org1'; - protected $role_name = 'drupal_connect_role'; + protected $roleName = 'drupal_connect_role'; - protected $http_client; + protected $httpClient; /** * {@inheritdoc} */ protected function setUp() { parent::setUp(); - $this->http_client = $this->prophesize(Client::class); + $this->httpClient = $this->prophesize(Client::class); } /** @@ -74,12 +74,12 @@ public function testIsValidEdgeCredentialsNotValid() { $io->text(Argument::type('string')) ->shouldBeCalledTimes(1); - $this->http_client + $this->httpClient ->get(Argument::type('string'), Argument::type('array')) ->willReturn($response->reveal()); - $edge_connection_util_service = new EdgeConnectionUtilService($this->http_client->reveal()); - $is_valid_creds = $edge_connection_util_service->isValidEdgeCredentials($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->base_url); + $edge_connection_util_service = new EdgeConnectionUtilService($this->httpClient->reveal()); + $is_valid_creds = $edge_connection_util_service->isValidEdgeCredentials($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->baseUrl); $this->assertEquals(FALSE, $is_valid_creds, 'Credentials are not valid, should return false.'); } @@ -94,7 +94,7 @@ public function testIsValidEdgeCredentialsValid() { ->shouldBeCalledTimes(1) ->willReturn($body); - $this->http_client + $this->httpClient ->get(Argument::type('string'), Argument::type('array')) ->willReturn($response->reveal()); @@ -108,8 +108,8 @@ public function testIsValidEdgeCredentialsValid() { $io->success(Argument::type('string')) ->shouldBeCalled(); - $edge_connection_util_service = new EdgeConnectionUtilService($this->http_client->reveal()); - $is_valid_creds = $edge_connection_util_service->isValidEdgeCredentials($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->base_url); + $edge_connection_util_service = new EdgeConnectionUtilService($this->httpClient->reveal()); + $is_valid_creds = $edge_connection_util_service->isValidEdgeCredentials($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->baseUrl); $this->assertEquals(TRUE, $is_valid_creds, 'Credentials are not valid, should return false.'); } @@ -120,9 +120,9 @@ public function testCreateEdgeRoleWhenRoleDoesNotExistTest() { $io = $this->prophesize(StyleInterface::class); $io->success(Argument::exact('Connected to Edge org ' . $this->org . '.'))->shouldBeCalledTimes(1); - $io->success(Argument::containingString('Role ' . $this->role_name . ' is configured.'))->shouldBeCalledTimes(1); - $io->text(Argument::containingString('Role ' . $this->role_name . ' already exists.'))->shouldBeCalledTimes(1); - $io->text(Argument::containingString('Setting permissions on role ' . $this->role_name . '.'))->shouldBeCalledTimes(1); + $io->success(Argument::containingString('Role ' . $this->roleName . ' is configured.'))->shouldBeCalledTimes(1); + $io->text(Argument::containingString('Role ' . $this->roleName . ' already exists.'))->shouldBeCalledTimes(1); + $io->text(Argument::containingString('Setting permissions on role ' . $this->roleName . '.'))->shouldBeCalledTimes(1); $io->text(Argument::containingString('/'))->shouldBeCalledTimes(12); $body = '{ "name": "' . $this->org . '" }'; @@ -131,22 +131,22 @@ public function testCreateEdgeRoleWhenRoleDoesNotExistTest() { ->shouldBeCalledTimes(1) ->willReturn($body); - $this->http_client - ->get(Argument::exact($this->base_url . '/o/' . $this->org), Argument::type('array')) + $this->httpClient + ->get(Argument::exact($this->baseUrl . '/o/' . $this->org), Argument::type('array')) ->shouldBeCalledTimes(1) ->willReturn($response->reveal()); - $this->http_client - ->get(Argument::exact($this->base_url . '/o/' . $this->org . '/userroles/' . $this->role_name), Argument::type('array')) + $this->httpClient + ->get(Argument::exact($this->baseUrl . '/o/' . $this->org . '/userroles/' . $this->roleName), Argument::type('array')) ->shouldBeCalledTimes(1) ->willReturn($response->reveal()); - $this->http_client + $this->httpClient ->post(Argument::type('string'), Argument::type('array')) ->willReturn($response->reveal()); - $edge_connection_util_service = new EdgeConnectionUtilService($this->http_client->reveal()); - $edge_connection_util_service->createEdgeRoleForDrupal($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->base_url, $this->role_name); + $edge_connection_util_service = new EdgeConnectionUtilService($this->httpClient->reveal()); + $edge_connection_util_service->createEdgeRoleForDrupal($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->baseUrl, $this->roleName); } /** @@ -155,9 +155,9 @@ public function testCreateEdgeRoleWhenRoleDoesNotExistTest() { public function testCreateEdgeRoleForDrupalWhenRoleExistsTest() { $io = $this->prophesize(StyleInterface::class); $io->success(Argument::exact('Connected to Edge org ' . $this->org . '.'))->shouldBeCalledTimes(1); - $io->success(Argument::containingString('Role ' . $this->role_name . ' is configured.'))->shouldBeCalledTimes(1); - $io->text(Argument::containingString('Role ' . $this->role_name . ' already exists.'))->shouldBeCalledTimes(1); - $io->text(Argument::containingString('Setting permissions on role ' . $this->role_name . '.'))->shouldBeCalledTimes(1); + $io->success(Argument::containingString('Role ' . $this->roleName . ' is configured.'))->shouldBeCalledTimes(1); + $io->text(Argument::containingString('Role ' . $this->roleName . ' already exists.'))->shouldBeCalledTimes(1); + $io->text(Argument::containingString('Setting permissions on role ' . $this->roleName . '.'))->shouldBeCalledTimes(1); $io->text(Argument::containingString('/'))->shouldBeCalledTimes(12); $response_org = $this->prophesize(Response::class); @@ -165,23 +165,23 @@ public function testCreateEdgeRoleForDrupalWhenRoleExistsTest() { ->shouldBeCalledTimes(1) ->willReturn('{ "name": "' . $this->org . '" }'); - $this->http_client - ->get(Argument::exact($this->base_url . '/o/' . $this->org), Argument::type('array')) + $this->httpClient + ->get(Argument::exact($this->baseUrl . '/o/' . $this->org), Argument::type('array')) ->shouldBeCalledTimes(1) ->willReturn($response_org->reveal()); $response_user_role = $this->prophesize(Response::class); - $this->http_client - ->get(Argument::exact($this->base_url . '/o/' . $this->org . '/userroles/' . $this->role_name), Argument::type('array')) + $this->httpClient + ->get(Argument::exact($this->baseUrl . '/o/' . $this->org . '/userroles/' . $this->roleName), Argument::type('array')) ->willReturn($response_user_role->reveal()); - $this->http_client - ->post(Argument::exact($this->base_url . '/o/' . $this->org . '/userroles/' . $this->role_name . '/permissions'), Argument::type('array')) + $this->httpClient + ->post(Argument::exact($this->baseUrl . '/o/' . $this->org . '/userroles/' . $this->roleName . '/permissions'), Argument::type('array')) ->shouldBeCalledTimes(12); - $edge_connection_util_service = new EdgeConnectionUtilService($this->http_client->reveal()); - $edge_connection_util_service->createEdgeRoleForDrupal($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->base_url, $this->role_name); + $edge_connection_util_service = new EdgeConnectionUtilService($this->httpClient->reveal()); + $edge_connection_util_service->createEdgeRoleForDrupal($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->baseUrl, $this->roleName); } /** @@ -189,13 +189,13 @@ public function testCreateEdgeRoleForDrupalWhenRoleExistsTest() { */ public function testDoesRoleExistTrue() { - $this->http_client + $this->httpClient ->get(Argument::cetera()) ->shouldBeCalledTimes(1) ->willReturn(NULL); - $edge_connection_util_service = new EdgeConnectionUtilService($this->http_client->reveal()); - $does_role_exist = $edge_connection_util_service->doesRoleExist($this->org, $this->email, $this->password, $this->base_url, $this->role_name); + $edge_connection_util_service = new EdgeConnectionUtilService($this->httpClient->reveal()); + $does_role_exist = $edge_connection_util_service->doesRoleExist($this->org, $this->email, $this->password, $this->baseUrl, $this->roleName); $this->assertEquals(TRUE, $does_role_exist, 'Method doesRoleExist() should return true when role exists.'); } @@ -211,12 +211,12 @@ public function testDoesRoleExistNotTrue() { // Http client throws exception when role does not exist. $exception = new ClientException('Role does not exist.', $request->reveal(), $response->reveal()); - $this->http_client + $this->httpClient ->get(Argument::type('string'), Argument::type('array')) ->willThrow($exception); - $edge_connection_util_service = new EdgeConnectionUtilService($this->http_client->reveal()); - $does_role_exist = $edge_connection_util_service->doesRoleExist($this->org, $this->email, $this->password, $this->base_url, $this->role_name); + $edge_connection_util_service = new EdgeConnectionUtilService($this->httpClient->reveal()); + $does_role_exist = $edge_connection_util_service->doesRoleExist($this->org, $this->email, $this->password, $this->baseUrl, $this->roleName); $this->assertEquals(FALSE, $does_role_exist, 'Method doesRoleExist() should return false when role exists.'); } @@ -232,12 +232,12 @@ public function testDoesRoleExistExceptionThrown() { // Http client throws exception if network or server error happens. $exception = new ServerException('Server error.', $request->reveal(), $response->reveal()); $this->expectException(ServerException::class); - $this->http_client + $this->httpClient ->get(Argument::type('string'), Argument::type('array')) ->willThrow($exception); - $edge_connection_util_service = new EdgeConnectionUtilService($this->http_client->reveal()); - $edge_connection_util_service->doesRoleExist($this->org, $this->email, $this->password, $this->base_url, $this->role_name); + $edge_connection_util_service = new EdgeConnectionUtilService($this->httpClient->reveal()); + $edge_connection_util_service->doesRoleExist($this->org, $this->email, $this->password, $this->baseUrl, $this->roleName); } /** @@ -249,7 +249,7 @@ public function testHandleHttpClientExceptions0Code() { $io->error(Argument::containingString('Error connecting to Apigee Edge'))->shouldBeCalledTimes(1); $io->note(Argument::containingString('Your system may not be able to connect'))->shouldBeCalledTimes(1); - $edge_connection_util_service = new EdgeConnectionUtilService($this->http_client->reveal()); + $edge_connection_util_service = new EdgeConnectionUtilService($this->httpClient->reveal()); $edge_connection_util_service->handleHttpClientExceptions($exception->reveal(), $io->reveal(), [$this, 'mockDt'], 'http://api.apigee.com/test', $this->org, $this->email); } @@ -267,7 +267,7 @@ public function testHandleHttpClientExceptions403Code() { $io->error(Argument::containingString('Error connecting to Apigee Edge'))->shouldBeCalledTimes(1); $io->note(Argument::containingString('User ' . $this->email . ' may not have the orgadmin role'))->shouldBeCalledTimes(1); - $edge_connection_util_service = new EdgeConnectionUtilService($this->http_client->reveal()); + $edge_connection_util_service = new EdgeConnectionUtilService($this->httpClient->reveal()); $edge_connection_util_service->handleHttpClientExceptions($exception, $io->reveal(), [$this, 'mockDt'], 'http://api.apigee.com/test', $this->org, $this->email); } @@ -283,10 +283,10 @@ public function testHandleHttpClientExceptions302Code() { $io = $this->prophesize(StyleInterface::class); $io->error(Argument::containingString('Error connecting to Apigee Edge'))->shouldBeCalledTimes(1); - $io->note(Argument::containingString('the url ' . $this->base_url . '/test' . ' does not seem to be a valid Apigee Edge endpoint.'))->shouldBeCalledTimes(1); + $io->note(Argument::containingString('the url ' . $this->baseUrl . '/test' . ' does not seem to be a valid Apigee Edge endpoint.'))->shouldBeCalledTimes(1); - $edge_connection_util_service = new EdgeConnectionUtilService($this->http_client->reveal()); - $edge_connection_util_service->handleHttpClientExceptions($exception, $io->reveal(), [$this, 'mockDt'], $this->base_url . '/test', $this->org, $this->email); + $edge_connection_util_service = new EdgeConnectionUtilService($this->httpClient->reveal()); + $edge_connection_util_service->handleHttpClientExceptions($exception, $io->reveal(), [$this, 'mockDt'], $this->baseUrl . '/test', $this->org, $this->email); } /** @@ -295,10 +295,10 @@ public function testHandleHttpClientExceptions302Code() { * @throws \ReflectionException */ public function testSetDefaultPermissions() { - $edge_connection_util_service = new EdgeConnectionUtilService($this->http_client->reveal()); + $edge_connection_util_service = new EdgeConnectionUtilService($this->httpClient->reveal()); $io = $this->prophesize(StyleInterface::class); - $this->http_client->post(Argument::type('string'), Argument::type('array'))->shouldBeCalledTimes(12); + $this->httpClient->post(Argument::type('string'), Argument::type('array'))->shouldBeCalledTimes(12); // Make method under test not private. $edge_connection_util_service_reflection = new ReflectionClass($edge_connection_util_service); @@ -311,8 +311,8 @@ public function testSetDefaultPermissions() { $this->org, $this->email, $this->password, - $this->base_url, - $this->role_name, + $this->baseUrl, + $this->roleName, ]; $method_set_default_permissions->invokeArgs($edge_connection_util_service, $args); } From a91116e0a29f8c31de27ecf05adde025ab9c690f Mon Sep 17 00:00:00 2001 From: Chris Novak Date: Mon, 30 Sep 2019 07:53:09 -0700 Subject: [PATCH 4/8] Code review changes * Renamed EdgeConnectionUtilService to ApigeeEdgeManagementCliService * Validating base_url * Added Drupal command --- apigee_edge.services.yml | 5 +- composer.json | 1 + console.services.yml | 5 + .../en/apigee_edge.role.create.yml | 12 ++ drush.services.yml | 2 +- src/CliService.php | 27 ++- src/CliServiceInterface.php | 20 ++ src/Command/CommandBase.php | 4 +- src/Command/CreateEdgeRoleCommand.php | 107 ++++++++++ .../Util/ApigeeEdgeManagementCliService.php} | 97 +++++++-- ...igeeEdgeManagementCliServiceInterface.php} | 16 +- src/Commands/ApigeeEdgeCommands.php | 35 ++- ...=> ApigeeEdgeManagementCliServiceTest.php} | 36 +++- .../Command/CreateEdgeRoleCommandTest.php | 199 ++++++++++++++++++ .../ApigeeEdgeManagementCliServiceTest.php} | 181 +++++++++++++--- .../Unit/Commands/ApigeeEdgeCommandsTest.php | 33 ++- 16 files changed, 679 insertions(+), 101 deletions(-) create mode 100644 console/translations/en/apigee_edge.role.create.yml create mode 100644 src/Command/CreateEdgeRoleCommand.php rename src/{Util/EdgeConnectionUtilService.php => Command/Util/ApigeeEdgeManagementCliService.php} (71%) rename src/{Util/EdgeConnectionUtilServiceInterface.php => Command/Util/ApigeeEdgeManagementCliServiceInterface.php} (77%) rename tests/src/Kernel/Util/{EdgeConnectionUtilServiceTest.php => ApigeeEdgeManagementCliServiceTest.php} (89%) create mode 100644 tests/src/Unit/Command/CreateEdgeRoleCommandTest.php rename tests/src/Unit/{Util/EdgeConnectionUtilServiceTest.php => Command/Util/ApigeeEdgeManagementCliServiceTest.php} (54%) diff --git a/apigee_edge.services.yml b/apigee_edge.services.yml index 7ffcfd236..765551c34 100644 --- a/apigee_edge.services.yml +++ b/apigee_edge.services.yml @@ -12,9 +12,10 @@ services: apigee_edge.cli: class: Drupal\apigee_edge\CliService + arguments: ['@apigee_edge.apigee_edge_mgmt_cli_service'] - apigee_edge.edge_connection_util: - class: Drupal\apigee_edge\util\EdgeConnectionUtilService + apigee_edge.apigee_edge_mgmt_cli_service: + class: Drupal\apigee_edge\Command\Util\ApigeeEdgeManagementCliService arguments: ['@http_client'] apigee_edge.sdk_connector: diff --git a/composer.json b/composer.json index e0e3ec254..db3797194 100644 --- a/composer.json +++ b/composer.json @@ -5,6 +5,7 @@ "description": "Apigee Edge for Drupal.", "require": { "php": ">=7.1", + "ext-json": "*", "apigee/apigee-client-php": "^2.0.1", "cweagans/composer-patches": "^1.6.5", "drupal/core": "~8.7.0", diff --git a/console.services.yml b/console.services.yml index d1f563876..85060d6df 100644 --- a/console.services.yml +++ b/console.services.yml @@ -4,3 +4,8 @@ services: arguments: ['@apigee_edge.cli', '@logger.log_message_parser', '@logger.factory'] tags: - { name: drupal.command } + apigee_edge.edge_role_create: + class: Drupal\apigee_edge\Command\CreateEdgeRoleCommand + arguments: ['@apigee_edge.cli', '@logger.log_message_parser', '@logger.factory'] + tags: + - { name: drupal.command } diff --git a/console/translations/en/apigee_edge.role.create.yml b/console/translations/en/apigee_edge.role.create.yml new file mode 100644 index 000000000..af94b71e6 --- /dev/null +++ b/console/translations/en/apigee_edge.role.create.yml @@ -0,0 +1,12 @@ +description: 'Create Apigee role for Drupal' +help: 'Create a custom Apigee role that limits permissions for Drupal connections to the Apigee API.' +arguments: + org: 'The Apigee Edge org to create the role in' + email: 'An Apigee user email address with orgadmin role for this org' +options: + password: 'Password for the Apigee orgadmin user (if not passed in you will be prompted)' + base-url: 'Base URL to use, defaults to public cloud URL https://api.enterprise.apigee.com/v1' + role-name: 'The role to create in the Apigee Edge org, defaults to "drupalportal"' +questions: + password: 'Enter password for Apigee orgadmin user' +messages: diff --git a/drush.services.yml b/drush.services.yml index 7d088cb35..1aa1a49f1 100644 --- a/drush.services.yml +++ b/drush.services.yml @@ -1,6 +1,6 @@ services: apigee_edge.commands: class: \Drupal\apigee_edge\Commands\ApigeeEdgeCommands - arguments: ['@apigee_edge.cli', '@apigee_edge.edge_connection_util'] + arguments: ['@apigee_edge.cli', '@apigee_edge.apigee_edge_mgmt_cli_service'] tags: - { name: drush.command } diff --git a/src/CliService.php b/src/CliService.php index f3f4bedda..adbef70cf 100644 --- a/src/CliService.php +++ b/src/CliService.php @@ -1,7 +1,7 @@ apigeeEdgeManagementCliService = $apigeeEdgeManagementCliService; + } + /** * {@inheritdoc} */ @@ -52,4 +70,11 @@ public function sync(StyleInterface $io, callable $t) { } } + /** + * {@inheritdoc} + */ + public function createEdgeRoleForDrupal(StyleInterface $io, callable $t, string $org, string $email, string $password, ?string $base_url, ?string $role_name) { + $this->apigeeEdgeManagementCliService->createEdgeRoleForDrupal($io, $t, $org, $email, $password, $base_url, $role_name); + } + } diff --git a/src/CliServiceInterface.php b/src/CliServiceInterface.php index 762f030cd..e6ba02898 100644 --- a/src/CliServiceInterface.php +++ b/src/CliServiceInterface.php @@ -36,4 +36,24 @@ interface CliServiceInterface { */ public function sync(StyleInterface $io, callable $t); + /** + * Create an Apigee role for Drupal use. + * + * @param \Symfony\Component\Console\Style\StyleInterface $io + * The IO interface of the CLI tool calling the method. + * @param callable $t + * The translation function akin to t(). + * @param string $org + * The organization to connect to. + * @param string $email + * The email of an Edge user with org admin role to make Edge API calls. + * @param string $password + * The password of an Edge user with org admin role to make Edge API calls. + * @param string|null $base_url + * The base url of the Edge API. + * @param string|null $role_name + * The role name to add the permissions to. + */ + public function createEdgeRoleForDrupal(StyleInterface $io, callable $t, string $org, string $email, string $password, ?string $base_url, ?string $role_name); + } diff --git a/src/Command/CommandBase.php b/src/Command/CommandBase.php index f3b153154..061df9d65 100644 --- a/src/Command/CommandBase.php +++ b/src/Command/CommandBase.php @@ -24,7 +24,7 @@ use Drupal\Console\Core\Style\DrupalStyle; use Drupal\Core\Logger\LoggerChannelFactoryInterface; use Drupal\Core\Logger\LogMessageParserInterface; -use Symfony\Component\Console\Command\Command; +use Drupal\Console\Core\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Logger\ConsoleLogger; use Symfony\Component\Console\Output\OutputInterface; @@ -115,7 +115,7 @@ protected function setupLogger(OutputInterface $output) { * @return \Symfony\Component\Console\Style\StyleInterface * The IO interface. */ - protected function getIo(): StyleInterface { + public function getIo(): StyleInterface { return $this->io; } diff --git a/src/Command/CreateEdgeRoleCommand.php b/src/Command/CreateEdgeRoleCommand.php new file mode 100644 index 000000000..fd94467bf --- /dev/null +++ b/src/Command/CreateEdgeRoleCommand.php @@ -0,0 +1,107 @@ +setName('apigee_edge:role:create') + ->setDescription($this->trans('commands.apigee_edge.role.create.description')) + ->setHelp('commands.apigee_edge.role.create.help') + ->addArgument( + 'org', + InputArgument::REQUIRED, + $this->trans('commands.apigee_edge.role.create.arguments.org') + ) + ->addArgument( + 'email', + InputArgument::REQUIRED, + $this->trans('commands.apigee_edge.role.create.arguments.email') + ) + ->addOption( + 'password', + 'p', + InputArgument::OPTIONAL, + $this->trans('commands.apigee_edge.role.create.options.password') + ) + ->addOption( + 'base-url', + 'b', + InputArgument::OPTIONAL, + $this->trans('commands.apigee_edge.role.create.options.base-url') + )->addOption( + 'role-name', + 'r', + InputArgument::OPTIONAL, + $this->trans('commands.apigee_edge.role.create.options.role-name') + );; + } + + /** + * {@inheritdoc} + */ + function interact(InputInterface $input, OutputInterface $output) { + $this->setupIo($input, $output); + $password = $input->getOption('password'); + if (!$password) { + $password = $this->getIo()->askHidden( + $this->trans('commands.apigee_edge.role.create.questions.password') + ); + $input->setOption('password', $password); + } + } + + /** + * {@inheritdoc} + */ + function execute(InputInterface $input, OutputInterface $output) { + $this->setupIo($input, $output); + $org = $input->getArgument('org'); + $email = $input->getArgument('email'); + $password = $input->getOption('password'); + $base_url = $input->getOption('base-url'); + $role_name = $input->getOption('role-name'); + + $this->cliService->createEdgeRoleForDrupal($this->getIo(), 't', $org, $email, $password, $base_url, $role_name); + } + +} diff --git a/src/Util/EdgeConnectionUtilService.php b/src/Command/Util/ApigeeEdgeManagementCliService.php similarity index 71% rename from src/Util/EdgeConnectionUtilService.php rename to src/Command/Util/ApigeeEdgeManagementCliService.php index ebea1c9a1..62e9b59c2 100644 --- a/src/Util/EdgeConnectionUtilService.php +++ b/src/Command/Util/ApigeeEdgeManagementCliService.php @@ -17,8 +17,9 @@ * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -namespace Drupal\apigee_edge\Util; +namespace Drupal\apigee_edge\Command\Util; +use Drupal\Component\Utility\UrlHelper; use GuzzleHttp\ClientInterface; use GuzzleHttp\Exception\ClientException; use GuzzleHttp\Exception\TransferException; @@ -27,7 +28,7 @@ /** * Defines an interface for Edge connection classes. */ -class EdgeConnectionUtilService implements EdgeConnectionUtilServiceInterface { +class ApigeeEdgeManagementCliService implements ApigeeEdgeManagementCliServiceInterface { /** * The HTTP client. @@ -37,7 +38,7 @@ class EdgeConnectionUtilService implements EdgeConnectionUtilServiceInterface { protected $httpClient; /** - * Constructs a EdgeRoleCreator object. + * Constructs a ApigeeEdgeManagementCliService object. * * @param \GuzzleHttp\ClientInterface $http_client * The HTTP client. @@ -47,9 +48,48 @@ public function __construct(ClientInterface $http_client) { } /** - * {@inheritdoc} + * Create role in Apigee Edge for Drupal to use for Edge connection. + * + * @param \Symfony\Component\Console\Style\StyleInterface $io + * The IO interface of the CLI tool calling the method. + * @param callable $t + * The translation function akin to t(). + * @param string $org + * The organization to connect to. + * @param string $email + * The email of an Edge user with org admin role to make Edge API calls. + * @param string $password + * The password of an Edge user with org admin role to make Edge API calls. + * @param null|string $base_url + * The base url of the Edge API. + * @param null|string $role_name + * The role name to add the permissions to. */ - public function createEdgeRoleForDrupal(StyleInterface $io, callable $t, string $org, string $email, string $password, string $base_url, string $role_name) { + public function createEdgeRoleForDrupal(StyleInterface $io, + callable $t, + string $org, + string $email, + string $password, + ?string $base_url, + ?string $role_name) { + + if ($base_url === NULL || $base_url === '') { + // Set default base URL if var is null or empty string. + $base_url = self::DEFAULT_BASE_URL; + } + else { + // Validate it is a valid URL. + if (!UrlHelper::isValid($base_url, TRUE)) { + $io->error($t('Base URL is not valid.')); + return; + } + + } + + if ($role_name === NULL || $role_name === '') { + // Set default if null or empty string. + $role_name = self::DEFAULT_ROLE_NAME; + } if (!$this->isValidEdgeCredentials($io, $t, $org, $email, $password, $base_url)) { return; @@ -59,9 +99,9 @@ public function createEdgeRoleForDrupal(StyleInterface $io, callable $t, string // If role does not exist, create it. if (!$does_role_exist) { - $io->text($t('Role !role does not exist. Creating role.', ['!role' => $role_name])); + $io->text($t('Role :role does not exist. Creating role.', [':role' => $role_name])); - $url = $base_url . '/o/' . $org . '/userroles'; + $url = "{$base_url}/o/{$org}/userroles"; try { $this->httpClient->post($url, [ 'body' => json_encode([ @@ -85,7 +125,7 @@ public function createEdgeRoleForDrupal(StyleInterface $io, callable $t, string $this->setDefaultPermissions($io, $t, $org, $email, $password, $base_url, $role_name); - $io->success($t('Role !role is configured. Log into apigee.com to assign a user to this role.', ['!role' => $role_name])); + $io->success($t('Role :role is configured. Log into apigee.com to assign a user to this role.', [':role' => $role_name])); } /** @@ -110,23 +150,29 @@ protected function setDefaultPermissions(StyleInterface $io, callable $t, string $io->text('Setting permissions on role ' . $role_name . '.'); $permissions = [ - // GET access. + // GET access by default for all resources. '/' => ['get'], + // Read only access to environments. '/environments/' => ['get'], '/environments/*/stats/*' => ['get'], + // We do not need to update/edit roles, just read them. '/userroles' => ['get'], - // GET & PUT access. - '/companies' => ['get', 'put'], - '/companies/*/apps' => ['get', 'put'], + // No need to create API products, only read and edit. '/apiproducts' => ['get', 'put'], - // GET, PUT, & DELETE access. + // Full CRUD for developers. '/developers' => ['get', 'put', 'delete'], + // Full CRUD for developer's apps. '/developers/*/apps' => ['get', 'put', 'delete'], '/developers/*/apps/*' => ['get', 'put', 'delete'], + // Full CRUD for companies. + '/companies' => ['get', 'put'], '/companies/*' => ['get', 'put', 'delete'], + // Full CRUD for company apps. + '/companies/*/apps' => ['get', 'put'], '/companies/*/apps/*' => ['get', 'put', 'delete'], ]; + // Resource URL for modifying permissions. $url = $base_url . '/o/' . $org . '/userroles/' . $role_name . '/permissions'; try { foreach ($permissions as $path => $permission_verbs) { @@ -225,15 +271,15 @@ public function isValidEdgeCredentials(StyleInterface $io, callable $t, string $ $body = json_decode($response->getBody()); if (JSON_ERROR_NONE !== json_last_error()) { $io->error($t('Unable to parse response from Apigee Edge into JSON. !error ', ['error' => json_last_error_msg()])); - $io->section($t('Server response from !url', ['!url' => $url])); + $io->section($t('Server response from :url', [':url' => $url])); $io->text($response->getBody()); return FALSE; } if (!isset($body->name)) { - $io->warning($t('The response from !url did not contain the org name.', ['!url' => $url])); + $io->warning($t('The response from :url did not contain the org name.', [':url' => $url])); } else { - $io->success($t('Connected to Edge org !org.', ['!org' => $body->name])); + $io->success($t('Connected to Edge org :org.', [':org' => $body->name])); } return TRUE; } @@ -255,24 +301,29 @@ public function isValidEdgeCredentials(StyleInterface $io, callable $t, string $ * The email of an Edge user with org admin role to make Edge API calls. */ public function handleHttpClientExceptions(TransferException $exception, StyleInterface $io, callable $t, string $url, string $org, string $email): void { - $io->error($t('Error connecting to Apigee Edge. !exception_message', ['!exception_message' => $exception->getMessage()])); + $io->error($t('Error connecting to Apigee Edge. :exception_message', [':exception_message' => $exception->getMessage()])); switch ($exception->getCode()) { case 0: - $io->note($t('Your system may not be able to connect to !url.', [ - '!url' => $url, + $io->note($t('Your system may not be able to connect to :url.', [ + ':url' => $url, ])); return; + case 401: + $io->note($t('Your username or password is invalid.')); + return; + case 403: - $io->note($t('User !email may not have the orgadmin role for Apigee Edge org !org.', [ - '!email' => $email, - '!org' => $org, + $io->note($t('User :email may not have the orgadmin role for Apigee Edge org :org.', [ + ':email' => $email, + ':org' => $org, ])); return; case 302: - $io->note($t('Edge endpoint gives a redirect response, is the url !url does not seem to be a valid Apigee Edge endpoint.', ['!url' => $url])); + $io->note($t('Edge endpoint gives a redirect response, is the url :url does not seem to be a valid Apigee Edge endpoint.', [':url' => $url])); return; + } } diff --git a/src/Util/EdgeConnectionUtilServiceInterface.php b/src/Command/Util/ApigeeEdgeManagementCliServiceInterface.php similarity index 77% rename from src/Util/EdgeConnectionUtilServiceInterface.php rename to src/Command/Util/ApigeeEdgeManagementCliServiceInterface.php index fd7f6b4e7..9f838a55f 100644 --- a/src/Util/EdgeConnectionUtilServiceInterface.php +++ b/src/Command/Util/ApigeeEdgeManagementCliServiceInterface.php @@ -17,14 +17,20 @@ * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -namespace Drupal\apigee_edge\Util; +namespace Drupal\apigee_edge\Command\Util; use Symfony\Component\Console\Style\StyleInterface; /** * Defines an interface for Edge connection classes. */ -interface EdgeConnectionUtilServiceInterface { +interface ApigeeEdgeManagementCliServiceInterface { + + // Default base url. + const DEFAULT_BASE_URL = 'https://api.enterprise.apigee.com/v1'; + + // Default role name to create in Apigee Edge. + const DEFAULT_ROLE_NAME = 'drupalportal'; /** * Create role in Apigee Edge for Drupal to use for Edge connection. @@ -39,11 +45,11 @@ interface EdgeConnectionUtilServiceInterface { * The email of an Edge user with org admin role to make Edge API calls. * @param string $password * The password of an Edge user with org admin role to make Edge API calls. - * @param string $base_url + * @param null|string $base_url * The base url of the Edge API. - * @param string $role_name + * @param null|string $role_name * The role name to add the permissions to. */ - public function createEdgeRoleForDrupal(StyleInterface $io, callable $t, string $org, string $email, string $password, string $base_url, string $role_name); + public function createEdgeRoleForDrupal(StyleInterface $io, callable $t, string $org, string $email, string $password, ?string $base_url, ?string $role_name); } diff --git a/src/Commands/ApigeeEdgeCommands.php b/src/Commands/ApigeeEdgeCommands.php index d91067f39..82eef676a 100644 --- a/src/Commands/ApigeeEdgeCommands.php +++ b/src/Commands/ApigeeEdgeCommands.php @@ -21,7 +21,6 @@ use Consolidation\AnnotatedCommand\CommandData; use Drupal\apigee_edge\CliServiceInterface; -use Drupal\apigee_edge\Util\EdgeConnectionUtilServiceInterface; use Drush\Commands\DrushCommands; /** @@ -29,12 +28,6 @@ */ class ApigeeEdgeCommands extends DrushCommands { - // Default base url. - const DEFAULT_BASE_URL = 'https://api.enterprise.apigee.com/v1'; - - // Default role name to create in Apigee Edge. - const DEFAULT_ROLE_NAME = 'drupalportal'; - /** * The interoperability cli service. * @@ -42,20 +35,15 @@ class ApigeeEdgeCommands extends DrushCommands { */ protected $cliService; - protected $edgeConnectionUtilService; - /** * ApigeeEdgeCommands constructor. * * @param \Drupal\apigee_edge\CliServiceInterface $cli_service * The CLI service which allows interoperability. - * @param \Drupal\apigee_edge\Util\EdgeConnectionUtilServiceInterface $edge_connection_util_service - * The Edge connection utilty service. */ - public function __construct(CliServiceInterface $cli_service = NULL, EdgeConnectionUtilServiceInterface $edge_connection_util_service = NULL) { + public function __construct(CliServiceInterface $cli_service = NULL) { parent::__construct(); $this->cliService = $cli_service; - $this->edgeConnectionUtilService = $edge_connection_util_service; } /** @@ -72,11 +60,10 @@ public function sync() { } /** - * Create a role in an Apigee organization. + * Create a custom role in an Apigee organization for Drupal usage. * - * Create role with proper permissions for connecting a Drupal - * Apigee Edge module to the Edge org. You must run this script - * as a user with the orgadmin role. + * Create a custom Apigee role that limits permissions for Drupal connections + * to the Apigee API. * * @param string $org * The Apigee Edge org to create the role in. @@ -89,9 +76,10 @@ public function sync() { * Password for the Apigee orgadmin user. If not set, you will be prompted * for the password. * @option base-url - * Base URL to use, defaults to public cloud URL. + * Base URL to use, defaults to public cloud URL: + * https://api.enterprise.apigee.com/v1. * @option role-name - * The role to create in the Apigee Edge org. + * The role to create in the Apigee Edge org, defaults to "drupalportal". * @usage drush create-edge-role myorg me@example.com * Create "drupalportal" role as orgadmin me@example.com for org myorg. * @usage drush create-edge-role myorg me@example.com --base-url=https://api.edge.example.com @@ -106,10 +94,10 @@ public function createEdgeRole( string $email, array $options = [ 'password' => NULL, - 'base-url' => self::DEFAULT_BASE_URL, - 'role-name' => self::DEFAULT_ROLE_NAME, + 'base-url' => NULL, + 'role-name' => NULL, ]) { - $this->edgeConnectionUtilService->createEdgeRoleForDrupal($this->io(), 'dt', $org, $email, $options['password'], $options['base-url'], $options['role-name']); + $this->cliService->createEdgeRoleForDrupal($this->io(), 'dt', $org, $email, $options['password'], $options['base-url'], $options['role-name']); } /** @@ -120,8 +108,9 @@ public function createEdgeRole( public function validateCreateEdgeRole(CommandData $commandData) { // If the user did not specify a password, then prompt for one. $password = $commandData->input()->getOption('password'); + $email = $commandData->input()->getArgument('email'); if (empty($password)) { - $password = $this->io()->askHidden("Enter a password:", function ($value) { + $password = $this->io()->askHidden(dt('Enter password for :email', [':email' => $email]), function ($value) { return $value; }); $commandData->input()->setOption('password', $password); diff --git a/tests/src/Kernel/Util/EdgeConnectionUtilServiceTest.php b/tests/src/Kernel/Util/ApigeeEdgeManagementCliServiceTest.php similarity index 89% rename from tests/src/Kernel/Util/EdgeConnectionUtilServiceTest.php rename to tests/src/Kernel/Util/ApigeeEdgeManagementCliServiceTest.php index 306dd965a..0dbfcf664 100644 --- a/tests/src/Kernel/Util/EdgeConnectionUtilServiceTest.php +++ b/tests/src/Kernel/Util/ApigeeEdgeManagementCliServiceTest.php @@ -24,17 +24,17 @@ use Drupal\KernelTests\KernelTestBase; /** - * EdgeConnectionUtilService Edge tests. + * ApigeeEdgeManagementCliService Edge tests. * - * Make sure Edge API works as expected for the EdgeConnectionUtilService. + * Make sure Edge API works as expected for the ApigeeEdgeManagementCliService. * * These tests validate Edge API request/responses needed for - * EdgeConnectionUtilService are valid. + * ApigeeEdgeManagementCliService are valid. * * @group apigee_edge * @group apigee_edge_kernel */ -class EdgeConnectionUtilServiceTest extends KernelTestBase implements ServiceModifierInterface { +class ApigeeEdgeManagementCliServiceTest extends KernelTestBase implements ServiceModifierInterface { protected const TEST_ROLE_NAME = 'temp_role'; @@ -43,11 +43,39 @@ class EdgeConnectionUtilServiceTest extends KernelTestBase implements ServiceMod */ protected static $modules = []; + /** + * Apigee API endpoint. + * + * @var array|false|string + */ protected $endpoint; + + /** + * Apigee Edge organization. + * + * @var array|false|string + */ protected $organization; + + /** + * Email of an account with the organization admin Apigee role. + * + * @var array|false|string + */ protected $orgadminEmail; + + /** + * The password of the orgadmin account. + * + * @var array|false|string + */ protected $orgadminPassword; + /** + * A GuzzleHttp\Client object. + * + * @var object|null + */ protected $httpClient; /** diff --git a/tests/src/Unit/Command/CreateEdgeRoleCommandTest.php b/tests/src/Unit/Command/CreateEdgeRoleCommandTest.php new file mode 100644 index 000000000..6d41631d7 --- /dev/null +++ b/tests/src/Unit/Command/CreateEdgeRoleCommandTest.php @@ -0,0 +1,199 @@ +cliService = $this->prophesize(CliServiceInterface::class); + $this->logMessageParser = $this->prophesize(LogMessageParserInterface::class); + $this->loggerChannelFactory = $this->prophesize(LoggerChannelFactoryInterface::class); + $this->createEdgeRoleCommand = new CreateEdgeRoleCommand($this->cliService->reveal(), + $this->logMessageParser->reveal(), $this->loggerChannelFactory->reveal()); + + $this->input = $this->prophesize(InputInterface::class); + $this->output = $this->prophesize(OutputInterface::class); + $this->io = $this->prophesize(DrupalStyle::class); + + $this->outputFormatter = $this->prophesize(OutputFormatterInterface::class)->reveal(); + $this->output->getFormatter()->willReturn($this->outputFormatter); + $this->output->getVerbosity()->willReturn(OutputInterface::VERBOSITY_DEBUG); + } + + /** + * Calls to Drush command should pass through to CLI service. + */ + public function testCreateEdgeRole() { + $this->input->getArgument(Argument::type('string'))->willReturn('XXX'); + $this->input->getOption(Argument::type('string'))->willReturn('XXX'); + + $this->createEdgeRoleCommand->execute($this->input->reveal(), $this->output->reveal()); + + $this->cliService->createEdgeRoleForDrupal( + Argument::type(DrupalStyle::class), + Argument::type('string'), + Argument::type('string'), + Argument::type('string'), + Argument::type('string'), + Argument::type('string'), + Argument::type('string') + ) + ->shouldHaveBeenCalledTimes(1); + } + + /** + * Test validateCreateEdgeRole function does not prompt for password. + * + * When password option is set, do not prompt for password. + */ + public function testInteractWithPasswordParam() { + + $this->input->getArgument(Argument::type('string'))->willReturn('XXX'); + $this->input->getOption('password')->willReturn('secret'); + $this->input->getOption(Argument::type('string'))->willReturn('XXX'); + $this->input->isInteractive()->willReturn(FALSE); + + $this->createEdgeRoleCommand->interact($this->input->reveal(), $this->output->reveal()); + + // Interact should not change password since it was passed in. + $this->input->getOption('password')->shouldHaveBeenCalled(); + $this->input->setOption('password')->shouldNotHaveBeenCalled(); + + } + + /** + * Test validateCreateEdgeRole prompts for password. + * + * When password option not set, password should be inputted by user. + */ + public function testInteractPasswordParamEmpty() { + + $this->input->getArgument(Argument::type('string'))->willReturn('XXX'); + $this->input->getOption('password')->willReturn(NULL); + $this->input->getOption(Argument::type('string'))->willReturn('XXX'); + $this->input->setOption(Argument::type('string'), NULL)->willReturn(NULL); + $this->input->isInteractive()->willReturn(FALSE); + + $this->createEdgeRoleCommand->interact($this->input->reveal(), $this->output->reveal()); + + // Interact should not change password since it was passed in. + $this->input->getOption('password')->shouldHaveBeenCalled(); + $this->input->setOption('password', NULL)->shouldHaveBeenCalled(); + + } + + } +} + +namespace { + + use Drush\Utils\StringUtils; + + /** + * Mock out t() so function exists for tests. + * + * @param string $message + * The string with placeholders to be interpolated. + * @param array $context + * An associative array of values to be inserted into the message. + * + * @return string + * The resulting string with all placeholders filled in. + */ + function t(string $message, array $context = []): string { + return StringUtils::interpolate($message, $context); + } + +} diff --git a/tests/src/Unit/Util/EdgeConnectionUtilServiceTest.php b/tests/src/Unit/Command/Util/ApigeeEdgeManagementCliServiceTest.php similarity index 54% rename from tests/src/Unit/Util/EdgeConnectionUtilServiceTest.php rename to tests/src/Unit/Command/Util/ApigeeEdgeManagementCliServiceTest.php index 3bba09498..5058380b1 100644 --- a/tests/src/Unit/Util/EdgeConnectionUtilServiceTest.php +++ b/tests/src/Unit/Command/Util/ApigeeEdgeManagementCliServiceTest.php @@ -19,7 +19,8 @@ namespace Drupal\Tests\apigee_edge\Unit; -use Drupal\apigee_edge\Util\EdgeConnectionUtilService; +use Drupal\apigee_edge\Command\Util\ApigeeEdgeManagementCliService; +use Drupal\apigee_edge\Command\Util\ApigeeEdgeManagementCliServiceInterface; use Drupal\Tests\UnitTestCase; use Drush\Utils\StringUtils; use GuzzleHttp\Client; @@ -33,18 +34,52 @@ use Symfony\Component\Console\Style\StyleInterface; /** - * Test EdgeConnectionUtilService. + * Test ApigeeEdgeManagementCliService. * * @group apigee_edge */ -class EdgeConnectionUtilServiceTest extends UnitTestCase { +class ApigeeEdgeManagementCliServiceTest extends UnitTestCase { + /** + * Test base url. + * + * @var string + */ protected $baseUrl = 'http://api.apigee.com'; + + /** + * Test email. + * + * @var string + */ protected $email = 'noreply@apigee.com'; + + /** + * Test password. + * + * @var string + */ protected $password = 'secret'; + + /** + * Test org. + * + * @var string + */ protected $org = 'org1'; + + /** + * Test role name. + * + * @var string + */ protected $roleName = 'drupal_connect_role'; + /** + * Mock HTTP Client. + * + * @var \Prophecy\Prophecy\ObjectProphecy + */ protected $httpClient; /** @@ -55,6 +90,76 @@ protected function setUp() { $this->httpClient = $this->prophesize(Client::class); } + /** + * Call createEdgeRoleForDrupal with null base URL to test default base URL. + */ + public function testCreateEdgeRoleForDrupalDefaultBaseUrl() { + $io = $this->prophesize(StyleInterface::class); + $io->success(Argument::exact('Connected to Edge org ' . $this->org . '.'))->shouldBeCalledTimes(1); + $io->success(Argument::containingString('Role ' . $this->roleName . ' is configured.'))->shouldBeCalledTimes(1); + $io->text(Argument::containingString('Role ' . $this->roleName . ' already exists.'))->shouldBeCalledTimes(1); + $io->text(Argument::containingString('Setting permissions on role ' . $this->roleName . '.'))->shouldBeCalledTimes(1); + $io->text(Argument::containingString('/'))->shouldBeCalledTimes(12); + + $response_org = $this->prophesize(Response::class); + $response_org->getBody() + ->shouldBeCalledTimes(1) + ->willReturn('{ "name": "' . $this->org . '" }'); + + $this->httpClient + ->get(Argument::exact(ApigeeEdgeManagementCliServiceInterface::DEFAULT_BASE_URL . '/o/' . $this->org), Argument::type('array')) + ->shouldBeCalledTimes(1) + ->willReturn($response_org->reveal()); + + $response_user_role = $this->prophesize(Response::class); + + $this->httpClient + ->get(Argument::exact(ApigeeEdgeManagementCliServiceInterface::DEFAULT_BASE_URL . '/o/' . $this->org . '/userroles/' . $this->roleName), Argument::type('array')) + ->willReturn($response_user_role->reveal()); + + $this->httpClient + ->post(Argument::exact(ApigeeEdgeManagementCliServiceInterface::DEFAULT_BASE_URL . '/o/' . $this->org . '/userroles/' . $this->roleName . '/permissions'), Argument::type('array')) + ->shouldBeCalledTimes(12); + + $apigee_edge_management_cli_service = new ApigeeEdgeManagementCliService($this->httpClient->reveal()); + $apigee_edge_management_cli_service->createEdgeRoleForDrupal($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, NULL, $this->roleName); + } + + /** + * Pass null role name to test using default role name. + */ + public function testCreateEdgeRoleForDrupalDefaultRoleName() { + $io = $this->prophesize(StyleInterface::class); + $io->success(Argument::exact('Connected to Edge org ' . $this->org . '.'))->shouldBeCalledTimes(1); + $io->success(Argument::containingString('Role ' . ApigeeEdgeManagementCliServiceInterface::DEFAULT_ROLE_NAME . ' is configured.'))->shouldBeCalledTimes(1); + $io->text(Argument::containingString('Role ' . ApigeeEdgeManagementCliServiceInterface::DEFAULT_ROLE_NAME . ' already exists.'))->shouldBeCalledTimes(1); + $io->text(Argument::containingString('Setting permissions on role ' . ApigeeEdgeManagementCliServiceInterface::DEFAULT_ROLE_NAME . '.'))->shouldBeCalledTimes(1); + $io->text(Argument::containingString('/'))->shouldBeCalledTimes(12); + + $response_org = $this->prophesize(Response::class); + $response_org->getBody() + ->shouldBeCalledTimes(1) + ->willReturn('{ "name": "' . $this->org . '" }'); + + $this->httpClient + ->get(Argument::exact($this->baseUrl . '/o/' . $this->org), Argument::type('array')) + ->shouldBeCalledTimes(1) + ->willReturn($response_org->reveal()); + + $response_user_role = $this->prophesize(Response::class); + + $this->httpClient + ->get(Argument::exact($this->baseUrl . '/o/' . $this->org . '/userroles/' . ApigeeEdgeManagementCliServiceInterface::DEFAULT_ROLE_NAME), Argument::type('array')) + ->willReturn($response_user_role->reveal()); + + $this->httpClient + ->post(Argument::exact($this->baseUrl . '/o/' . $this->org . '/userroles/' . ApigeeEdgeManagementCliServiceInterface::DEFAULT_ROLE_NAME . '/permissions'), Argument::type('array')) + ->shouldBeCalledTimes(12); + + $apigee_edge_management_cli_service = new ApigeeEdgeManagementCliService($this->httpClient->reveal()); + $apigee_edge_management_cli_service->createEdgeRoleForDrupal($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->baseUrl, NULL); + } + /** * Should return true if creds are valid. */ @@ -78,8 +183,8 @@ public function testIsValidEdgeCredentialsNotValid() { ->get(Argument::type('string'), Argument::type('array')) ->willReturn($response->reveal()); - $edge_connection_util_service = new EdgeConnectionUtilService($this->httpClient->reveal()); - $is_valid_creds = $edge_connection_util_service->isValidEdgeCredentials($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->baseUrl); + $apigee_edge_management_cli_service = new ApigeeEdgeManagementCliService($this->httpClient->reveal()); + $is_valid_creds = $apigee_edge_management_cli_service->isValidEdgeCredentials($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->baseUrl); $this->assertEquals(FALSE, $is_valid_creds, 'Credentials are not valid, should return false.'); } @@ -108,8 +213,8 @@ public function testIsValidEdgeCredentialsValid() { $io->success(Argument::type('string')) ->shouldBeCalled(); - $edge_connection_util_service = new EdgeConnectionUtilService($this->httpClient->reveal()); - $is_valid_creds = $edge_connection_util_service->isValidEdgeCredentials($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->baseUrl); + $apigee_edge_management_cli_service = new ApigeeEdgeManagementCliService($this->httpClient->reveal()); + $is_valid_creds = $apigee_edge_management_cli_service->isValidEdgeCredentials($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->baseUrl); $this->assertEquals(TRUE, $is_valid_creds, 'Credentials are not valid, should return false.'); } @@ -145,12 +250,12 @@ public function testCreateEdgeRoleWhenRoleDoesNotExistTest() { ->post(Argument::type('string'), Argument::type('array')) ->willReturn($response->reveal()); - $edge_connection_util_service = new EdgeConnectionUtilService($this->httpClient->reveal()); - $edge_connection_util_service->createEdgeRoleForDrupal($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->baseUrl, $this->roleName); + $apigee_edge_management_cli_service = new ApigeeEdgeManagementCliService($this->httpClient->reveal()); + $apigee_edge_management_cli_service->createEdgeRoleForDrupal($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->baseUrl, $this->roleName); } /** - * Do not try to create role if it already exists. + * Recreate role if it already exists. */ public function testCreateEdgeRoleForDrupalWhenRoleExistsTest() { $io = $this->prophesize(StyleInterface::class); @@ -180,8 +285,8 @@ public function testCreateEdgeRoleForDrupalWhenRoleExistsTest() { ->post(Argument::exact($this->baseUrl . '/o/' . $this->org . '/userroles/' . $this->roleName . '/permissions'), Argument::type('array')) ->shouldBeCalledTimes(12); - $edge_connection_util_service = new EdgeConnectionUtilService($this->httpClient->reveal()); - $edge_connection_util_service->createEdgeRoleForDrupal($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->baseUrl, $this->roleName); + $apigee_edge_management_cli_service = new ApigeeEdgeManagementCliService($this->httpClient->reveal()); + $apigee_edge_management_cli_service->createEdgeRoleForDrupal($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->baseUrl, $this->roleName); } /** @@ -194,8 +299,8 @@ public function testDoesRoleExistTrue() { ->shouldBeCalledTimes(1) ->willReturn(NULL); - $edge_connection_util_service = new EdgeConnectionUtilService($this->httpClient->reveal()); - $does_role_exist = $edge_connection_util_service->doesRoleExist($this->org, $this->email, $this->password, $this->baseUrl, $this->roleName); + $apigee_edge_management_cli_service = new ApigeeEdgeManagementCliService($this->httpClient->reveal()); + $does_role_exist = $apigee_edge_management_cli_service->doesRoleExist($this->org, $this->email, $this->password, $this->baseUrl, $this->roleName); $this->assertEquals(TRUE, $does_role_exist, 'Method doesRoleExist() should return true when role exists.'); } @@ -215,8 +320,8 @@ public function testDoesRoleExistNotTrue() { ->get(Argument::type('string'), Argument::type('array')) ->willThrow($exception); - $edge_connection_util_service = new EdgeConnectionUtilService($this->httpClient->reveal()); - $does_role_exist = $edge_connection_util_service->doesRoleExist($this->org, $this->email, $this->password, $this->baseUrl, $this->roleName); + $apigee_edge_management_cli_service = new ApigeeEdgeManagementCliService($this->httpClient->reveal()); + $does_role_exist = $apigee_edge_management_cli_service->doesRoleExist($this->org, $this->email, $this->password, $this->baseUrl, $this->roleName); $this->assertEquals(FALSE, $does_role_exist, 'Method doesRoleExist() should return false when role exists.'); } @@ -236,8 +341,8 @@ public function testDoesRoleExistExceptionThrown() { ->get(Argument::type('string'), Argument::type('array')) ->willThrow($exception); - $edge_connection_util_service = new EdgeConnectionUtilService($this->httpClient->reveal()); - $edge_connection_util_service->doesRoleExist($this->org, $this->email, $this->password, $this->baseUrl, $this->roleName); + $apigee_edge_management_cli_service = new ApigeeEdgeManagementCliService($this->httpClient->reveal()); + $apigee_edge_management_cli_service->doesRoleExist($this->org, $this->email, $this->password, $this->baseUrl, $this->roleName); } /** @@ -249,8 +354,26 @@ public function testHandleHttpClientExceptions0Code() { $io->error(Argument::containingString('Error connecting to Apigee Edge'))->shouldBeCalledTimes(1); $io->note(Argument::containingString('Your system may not be able to connect'))->shouldBeCalledTimes(1); - $edge_connection_util_service = new EdgeConnectionUtilService($this->httpClient->reveal()); - $edge_connection_util_service->handleHttpClientExceptions($exception->reveal(), $io->reveal(), [$this, 'mockDt'], 'http://api.apigee.com/test', $this->org, $this->email); + $apigee_edge_management_cli_service = new ApigeeEdgeManagementCliService($this->httpClient->reveal()); + $apigee_edge_management_cli_service->handleHttpClientExceptions($exception->reveal(), $io->reveal(), [$this, 'mockDt'], 'http://api.apigee.com/test', $this->org, $this->email); + } + + /** + * Make sure method outputs more info for error codes. + */ + public function testHandleHttpClientExceptions401Code() { + $request = $this->prophesize(RequestInterface::class); + $response = $this->prophesize(Response::class); + $response->getStatusCode()->willReturn(401); + + $exception = new ClientException('Forbidden', $request->reveal(), $response->reveal()); + + $io = $this->prophesize(StyleInterface::class); + $io->error(Argument::containingString('Error connecting to Apigee Edge'))->shouldBeCalledTimes(1); + $io->note(Argument::exact('Your username or password is invalid.'))->shouldBeCalledTimes(1); + + $apigee_edge_management_cli_service = new ApigeeEdgeManagementCliService($this->httpClient->reveal()); + $apigee_edge_management_cli_service->handleHttpClientExceptions($exception, $io->reveal(), [$this, 'mockDt'], 'http://api.apigee.com/test', $this->org, $this->email); } /** @@ -267,8 +390,8 @@ public function testHandleHttpClientExceptions403Code() { $io->error(Argument::containingString('Error connecting to Apigee Edge'))->shouldBeCalledTimes(1); $io->note(Argument::containingString('User ' . $this->email . ' may not have the orgadmin role'))->shouldBeCalledTimes(1); - $edge_connection_util_service = new EdgeConnectionUtilService($this->httpClient->reveal()); - $edge_connection_util_service->handleHttpClientExceptions($exception, $io->reveal(), [$this, 'mockDt'], 'http://api.apigee.com/test', $this->org, $this->email); + $apigee_edge_management_cli_service = new ApigeeEdgeManagementCliService($this->httpClient->reveal()); + $apigee_edge_management_cli_service->handleHttpClientExceptions($exception, $io->reveal(), [$this, 'mockDt'], 'http://api.apigee.com/test', $this->org, $this->email); } /** @@ -285,8 +408,8 @@ public function testHandleHttpClientExceptions302Code() { $io->error(Argument::containingString('Error connecting to Apigee Edge'))->shouldBeCalledTimes(1); $io->note(Argument::containingString('the url ' . $this->baseUrl . '/test' . ' does not seem to be a valid Apigee Edge endpoint.'))->shouldBeCalledTimes(1); - $edge_connection_util_service = new EdgeConnectionUtilService($this->httpClient->reveal()); - $edge_connection_util_service->handleHttpClientExceptions($exception, $io->reveal(), [$this, 'mockDt'], $this->baseUrl . '/test', $this->org, $this->email); + $apigee_edge_management_cli_service = new ApigeeEdgeManagementCliService($this->httpClient->reveal()); + $apigee_edge_management_cli_service->handleHttpClientExceptions($exception, $io->reveal(), [$this, 'mockDt'], $this->baseUrl . '/test', $this->org, $this->email); } /** @@ -295,14 +418,14 @@ public function testHandleHttpClientExceptions302Code() { * @throws \ReflectionException */ public function testSetDefaultPermissions() { - $edge_connection_util_service = new EdgeConnectionUtilService($this->httpClient->reveal()); + $apigee_edge_management_cli_service = new ApigeeEdgeManagementCliService($this->httpClient->reveal()); $io = $this->prophesize(StyleInterface::class); $this->httpClient->post(Argument::type('string'), Argument::type('array'))->shouldBeCalledTimes(12); // Make method under test not private. - $edge_connection_util_service_reflection = new ReflectionClass($edge_connection_util_service); - $method_set_default_permissions = $edge_connection_util_service_reflection->getMethod('setDefaultPermissions'); + $apigee_edge_management_cli_service_reflection = new ReflectionClass($apigee_edge_management_cli_service); + $method_set_default_permissions = $apigee_edge_management_cli_service_reflection->getMethod('setDefaultPermissions'); $method_set_default_permissions->setAccessible(TRUE); $args = [ @@ -314,7 +437,7 @@ public function testSetDefaultPermissions() { $this->baseUrl, $this->roleName, ]; - $method_set_default_permissions->invokeArgs($edge_connection_util_service, $args); + $method_set_default_permissions->invokeArgs($apigee_edge_management_cli_service, $args); } /** @@ -328,7 +451,7 @@ public function testSetDefaultPermissions() { * @return string * The message with context. */ - public function mockDt(string $message, array $context): string { + public function mockDt(string $message, array $context = []): string { // Do the same thing as Drush dt(). return StringUtils::interpolate($message, $context); } diff --git a/tests/src/Unit/Commands/ApigeeEdgeCommandsTest.php b/tests/src/Unit/Commands/ApigeeEdgeCommandsTest.php index e9d5fc1bb..1a21ba76c 100644 --- a/tests/src/Unit/Commands/ApigeeEdgeCommandsTest.php +++ b/tests/src/Unit/Commands/ApigeeEdgeCommandsTest.php @@ -22,7 +22,6 @@ use Consolidation\AnnotatedCommand\CommandData; use Drupal\apigee_edge\CliServiceInterface; use Drupal\apigee_edge\Commands\ApigeeEdgeCommands; - use Drupal\apigee_edge\Util\EdgeConnectionUtilServiceInterface; use Drupal\Tests\UnitTestCase; use Drush\Style\DrushStyle; use Prophecy\Argument; @@ -36,12 +35,25 @@ */ class ApigeeEdgeCommandsTest extends UnitTestCase { + /** + * The system under test. + * + * @var \Drupal\apigee_edge\Commands\ApigeeEdgeCommands + */ protected $apigeeEdgeCommands; - protected $edgeConnectionUtilService; - + /** + * The CLI Service mock. + * + * @var \Prophecy\Prophecy\ObjectProphecy + */ protected $cliService; + /** + * The DrushStyle mock. + * + * @var \Prophecy\Prophecy\ObjectProphecy + */ protected $io; /** @@ -49,9 +61,8 @@ class ApigeeEdgeCommandsTest extends UnitTestCase { */ protected function setUp() { parent::setUp(); - $this->edgeConnectionUtilService = $this->prophesize(EdgeConnectionUtilServiceInterface::class); $this->cliService = $this->prophesize(CliServiceInterface::class); - $this->apigeeEdgeCommands = new ApigeeEdgeCommands($this->cliService->reveal(), $this->edgeConnectionUtilService->reveal()); + $this->apigeeEdgeCommands = new ApigeeEdgeCommands($this->cliService->reveal()); // Set io in DrushCommands to a mock. $apigee_edge_commands_reflection = new ReflectionClass($this->apigeeEdgeCommands); @@ -71,13 +82,13 @@ public function testCreateEdgeRole() { $drush_options = [ 'password' => 'opensesame', - 'base-url' => ApigeeEdgeCommands::DEFAULT_BASE_URL, + 'base-url' => 'http://api.apigee.com/v1', 'role-name' => 'portalRole', ]; $this->apigeeEdgeCommands->createEdgeRole('orgA', 'emailA', $drush_options); - $this->edgeConnectionUtilService->createEdgeRoleForDrupal( + $this->cliService->createEdgeRoleForDrupal( Argument::type(DrushStyle::class), Argument::type('string'), Argument::type('string'), @@ -94,13 +105,12 @@ public function testCreateEdgeRole() { * Test validateCreateEdgeRole function does not prompt for password. * * When password option is set, do not prompt for password. - * - * @throws \ReflectionException */ public function testValidatePasswordParam() { $command_data_input = $this->prophesize(InputInterface::class); $command_data_input->getOption('password')->willReturn('secret'); + $command_data_input->getArgument('email')->willReturn('email.example.com'); $command_data = $this->prophesize(CommandData::class); $command_data->input()->willReturn($command_data_input->reveal()); @@ -117,14 +127,13 @@ public function testValidatePasswordParam() { * Test validateCreateEdgeRole prompts for password. * * When password option not set, password should be inputted by user. - * - * @throws \ReflectionException */ public function testValidatePasswordParamEmpty() { $command_data_input = $this->prophesize(InputInterface::class); $command_data_input->getOption('password')->willReturn(NULL); $command_data_input->setOption(Argument::type('string'), Argument::type('string'))->willReturn(); + $command_data_input->getArgument('email')->willReturn('email.example.com'); $command_data = $this->prophesize(CommandData::class); $command_data->input()->willReturn($command_data_input->reveal()); @@ -144,6 +153,8 @@ public function testValidatePasswordParamEmpty() { namespace { + use Drush\Utils\StringUtils; + /** * Mock out dt() so function exists for tests. * From 9a2e90a6cf0dd46491e25eae943fdd1ad385348e Mon Sep 17 00:00:00 2001 From: Chris Novak Date: Mon, 30 Sep 2019 08:23:42 -0700 Subject: [PATCH 5/8] Fix coding standards --- src/Command/CreateEdgeRoleCommand.php | 4 ++-- tests/src/Unit/Command/CreateEdgeRoleCommandTest.php | 2 ++ tests/src/Unit/Commands/ApigeeEdgeCommandsTest.php | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Command/CreateEdgeRoleCommand.php b/src/Command/CreateEdgeRoleCommand.php index fd94467bf..1592c42b6 100644 --- a/src/Command/CreateEdgeRoleCommand.php +++ b/src/Command/CreateEdgeRoleCommand.php @@ -79,7 +79,7 @@ protected function configure() { /** * {@inheritdoc} */ - function interact(InputInterface $input, OutputInterface $output) { + public function interact(InputInterface $input, OutputInterface $output) { $this->setupIo($input, $output); $password = $input->getOption('password'); if (!$password) { @@ -93,7 +93,7 @@ function interact(InputInterface $input, OutputInterface $output) { /** * {@inheritdoc} */ - function execute(InputInterface $input, OutputInterface $output) { + public function execute(InputInterface $input, OutputInterface $output) { $this->setupIo($input, $output); $org = $input->getArgument('org'); $email = $input->getArgument('email'); diff --git a/tests/src/Unit/Command/CreateEdgeRoleCommandTest.php b/tests/src/Unit/Command/CreateEdgeRoleCommandTest.php index 6d41631d7..2d62e5471 100644 --- a/tests/src/Unit/Command/CreateEdgeRoleCommandTest.php +++ b/tests/src/Unit/Command/CreateEdgeRoleCommandTest.php @@ -74,6 +74,7 @@ class CreateEdgeRoleCommandTest extends UnitTestCase { /** * The InputInterface mock. + * * @var \Prophecy\Prophecy\ObjectProphecy */ private $input; @@ -179,6 +180,7 @@ public function testInteractPasswordParamEmpty() { namespace { + // phpcs:disable PSR2.Namespaces.UseDeclaration.UseAfterNamespace use Drush\Utils\StringUtils; /** diff --git a/tests/src/Unit/Commands/ApigeeEdgeCommandsTest.php b/tests/src/Unit/Commands/ApigeeEdgeCommandsTest.php index 1a21ba76c..8101a976b 100644 --- a/tests/src/Unit/Commands/ApigeeEdgeCommandsTest.php +++ b/tests/src/Unit/Commands/ApigeeEdgeCommandsTest.php @@ -153,6 +153,7 @@ public function testValidatePasswordParamEmpty() { namespace { + // phpcs:disable PSR2.Namespaces.UseDeclaration.UseAfterNamespace use Drush\Utils\StringUtils; /** From a2b0ba42ffef8f2ff656c8c6aa61c655d9cd6bb2 Mon Sep 17 00:00:00 2001 From: Chris Novak Date: Wed, 9 Oct 2019 14:28:49 -0700 Subject: [PATCH 6/8] Code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added “force” parameter to force updates to Edge role if role already exists. If `-- force` is not given the command will error instead of making any changes. * Added more error checking, to make sure invalid endpoints throw a user friendly error. * Added drush and drupal console to travis build * Fixed code issues from PR review --- .travis/composer.json | 4 +- README.md | 1 + .../en/apigee_edge.role.create.yml | 1 + src/CliService.php | 22 +- src/CliServiceInterface.php | 13 +- src/Command/CreateEdgeRoleCommand.php | 29 +- .../Util/ApigeeEdgeManagementCliService.php | 88 +++--- ...pigeeEdgeManagementCliServiceInterface.php | 16 +- src/Commands/ApigeeEdgeCommands.php | 23 +- .../Command/CreateEdgeRoleCommandTest.php | 33 +- .../ApigeeEdgeManagementCliServiceTest.php | 297 +++++++++++------- .../Unit/Commands/ApigeeEdgeCommandsTest.php | 55 +++- 12 files changed, 394 insertions(+), 188 deletions(-) diff --git a/.travis/composer.json b/.travis/composer.json index f6d54e81a..4c5962f0c 100644 --- a/.travis/composer.json +++ b/.travis/composer.json @@ -8,7 +8,9 @@ "composer/installers": "^1.6", "drupal-composer/drupal-scaffold": "^2.5.4", "wikimedia/composer-merge-plugin": "dev-capture-input-options", - "zaporylie/composer-drupal-optimizations": "^1.0" + "zaporylie/composer-drupal-optimizations": "^1.0", + "drupal/console": "~1.0", + "drush/drush": "^9.7" }, "repositories": [ { diff --git a/README.md b/README.md index 1afbf2078..785b3ba72 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ considered to be in development. Experimental modules are included in the "Apige 2. Click **Extend** in the Drupal administration menu. 3. Select the **Apigee Edge** module. 4. Click **Install**. +5. Configure the [connection to your Apigee org](https://www.drupal.org/docs/8/modules/apigee-edge/configure-the-connection-to-apigee-edge) ## Notes diff --git a/console/translations/en/apigee_edge.role.create.yml b/console/translations/en/apigee_edge.role.create.yml index af94b71e6..82d000b95 100644 --- a/console/translations/en/apigee_edge.role.create.yml +++ b/console/translations/en/apigee_edge.role.create.yml @@ -7,6 +7,7 @@ options: password: 'Password for the Apigee orgadmin user (if not passed in you will be prompted)' base-url: 'Base URL to use, defaults to public cloud URL https://api.enterprise.apigee.com/v1' role-name: 'The role to create in the Apigee Edge org, defaults to "drupalportal"' + force: 'Force running of permissions on a role that already exists.' questions: password: 'Enter password for Apigee orgadmin user' messages: diff --git a/src/CliService.php b/src/CliService.php index adbef70cf..8f5f2ada4 100644 --- a/src/CliService.php +++ b/src/CliService.php @@ -73,8 +73,26 @@ public function sync(StyleInterface $io, callable $t) { /** * {@inheritdoc} */ - public function createEdgeRoleForDrupal(StyleInterface $io, callable $t, string $org, string $email, string $password, ?string $base_url, ?string $role_name) { - $this->apigeeEdgeManagementCliService->createEdgeRoleForDrupal($io, $t, $org, $email, $password, $base_url, $role_name); + public function createEdgeRoleForDrupal( + StyleInterface $io, + callable $t, + string $org, + string $email, + string $password, + ?string $base_url, + ?string $role_name, + ?bool $force + ) { + $this->apigeeEdgeManagementCliService->createEdgeRoleForDrupal( + $io, + $t, + $org, + $email, + $password, + $base_url, + $role_name, + $force + ); } } diff --git a/src/CliServiceInterface.php b/src/CliServiceInterface.php index e6ba02898..f5bee24a0 100644 --- a/src/CliServiceInterface.php +++ b/src/CliServiceInterface.php @@ -53,7 +53,18 @@ public function sync(StyleInterface $io, callable $t); * The base url of the Edge API. * @param string|null $role_name * The role name to add the permissions to. + * @param bool|null $force + * Force permissions to be set even if role exists. */ - public function createEdgeRoleForDrupal(StyleInterface $io, callable $t, string $org, string $email, string $password, ?string $base_url, ?string $role_name); + public function createEdgeRoleForDrupal( + StyleInterface $io, + callable $t, + string $org, + string $email, + string $password, + ?string $base_url, + ?string $role_name, + ?bool $force + ); } diff --git a/src/Command/CreateEdgeRoleCommand.php b/src/Command/CreateEdgeRoleCommand.php index 1592c42b6..8217682d3 100644 --- a/src/Command/CreateEdgeRoleCommand.php +++ b/src/Command/CreateEdgeRoleCommand.php @@ -21,6 +21,7 @@ use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; /** @@ -33,12 +34,6 @@ */ class CreateEdgeRoleCommand extends CommandBase { - // Default base url. - const DEFAULT_BASE_URL = 'https://api.enterprise.apigee.com/v1'; - - // Default role name to create in Apigee Edge. - const DEFAULT_ROLE_NAME = 'drupalportal'; - /** * {@inheritdoc} */ @@ -64,16 +59,23 @@ protected function configure() { $this->trans('commands.apigee_edge.role.create.options.password') ) ->addOption( - 'base-url', - 'b', - InputArgument::OPTIONAL, - $this->trans('commands.apigee_edge.role.create.options.base-url') - )->addOption( + 'base-url', + 'b', + InputArgument::OPTIONAL, + $this->trans('commands.apigee_edge.role.create.options.base-url') + ) + ->addOption( 'role-name', 'r', InputArgument::OPTIONAL, $this->trans('commands.apigee_edge.role.create.options.role-name') - );; + )->addOption( + 'force', + 'f', + InputOption::VALUE_NONE, + $this->trans('commands.apigee_edge.role.create.options.force') + ); + } /** @@ -100,8 +102,9 @@ public function execute(InputInterface $input, OutputInterface $output) { $password = $input->getOption('password'); $base_url = $input->getOption('base-url'); $role_name = $input->getOption('role-name'); + $force = $input->getOption('force'); - $this->cliService->createEdgeRoleForDrupal($this->getIo(), 't', $org, $email, $password, $base_url, $role_name); + $this->cliService->createEdgeRoleForDrupal($this->getIo(), 't', $org, $email, $password, $base_url, $role_name, $force); } } diff --git a/src/Command/Util/ApigeeEdgeManagementCliService.php b/src/Command/Util/ApigeeEdgeManagementCliService.php index 62e9b59c2..65d8361f5 100644 --- a/src/Command/Util/ApigeeEdgeManagementCliService.php +++ b/src/Command/Util/ApigeeEdgeManagementCliService.php @@ -21,6 +21,7 @@ use Drupal\Component\Utility\UrlHelper; use GuzzleHttp\ClientInterface; +use Apigee\Edge\ClientInterface as ApigeeClientInterface; use GuzzleHttp\Exception\ClientException; use GuzzleHttp\Exception\TransferException; use Symfony\Component\Console\Style\StyleInterface; @@ -48,22 +49,7 @@ public function __construct(ClientInterface $http_client) { } /** - * Create role in Apigee Edge for Drupal to use for Edge connection. - * - * @param \Symfony\Component\Console\Style\StyleInterface $io - * The IO interface of the CLI tool calling the method. - * @param callable $t - * The translation function akin to t(). - * @param string $org - * The organization to connect to. - * @param string $email - * The email of an Edge user with org admin role to make Edge API calls. - * @param string $password - * The password of an Edge user with org admin role to make Edge API calls. - * @param null|string $base_url - * The base url of the Edge API. - * @param null|string $role_name - * The role name to add the permissions to. + * {@inheritdoc} */ public function createEdgeRoleForDrupal(StyleInterface $io, callable $t, @@ -71,11 +57,12 @@ public function createEdgeRoleForDrupal(StyleInterface $io, string $email, string $password, ?string $base_url, - ?string $role_name) { + ?string $role_name, + bool $force) { - if ($base_url === NULL || $base_url === '') { - // Set default base URL if var is null or empty string. - $base_url = self::DEFAULT_BASE_URL; + // Set default base URL if var is null or empty string. + if (empty($base_url)) { + $base_url = ApigeeClientInterface::DEFAULT_ENDPOINT; } else { // Validate it is a valid URL. @@ -83,13 +70,10 @@ public function createEdgeRoleForDrupal(StyleInterface $io, $io->error($t('Base URL is not valid.')); return; } - } - if ($role_name === NULL || $role_name === '') { - // Set default if null or empty string. - $role_name = self::DEFAULT_ROLE_NAME; - } + // Set default if null or empty string. + $role_name = $role_name ?: self::DEFAULT_ROLE_NAME; if (!$this->isValidEdgeCredentials($io, $t, $org, $email, $password, $base_url)) { return; @@ -97,7 +81,14 @@ public function createEdgeRoleForDrupal(StyleInterface $io, $does_role_exist = $this->doesRoleExist($org, $email, $password, $base_url, $role_name); - // If role does not exist, create it. + // If role does not exist and force flag is not used, throw error. + if ($does_role_exist && !$force) { + $io->error('Role ' . $role_name . ' already exists.'); + $io->note('Run with --force option to set default permissions on this role.'); + return; + } + + // Create the role if it does not exist. if (!$does_role_exist) { $io->text($t('Role :role does not exist. Creating role.', [':role' => $role_name])); @@ -119,9 +110,6 @@ public function createEdgeRoleForDrupal(StyleInterface $io, return; } } - else { - $io->text('Role ' . $role_name . ' already exists.'); - } $this->setDefaultPermissions($io, $t, $org, $email, $password, $base_url, $role_name); @@ -152,7 +140,7 @@ protected function setDefaultPermissions(StyleInterface $io, callable $t, string $permissions = [ // GET access by default for all resources. '/' => ['get'], - // Read only access to environments. + // Read only access to environments for analytics. '/environments/' => ['get'], '/environments/*/stats/*' => ['get'], // We do not need to update/edit roles, just read them. @@ -219,7 +207,7 @@ protected function setDefaultPermissions(StyleInterface $io, callable $t, string public function doesRoleExist(string $org, string $email, string $password, string $base_url, string $role_name) { $url = $base_url . '/o/' . $org . '/userroles/' . $role_name; try { - $this->httpClient->get($url, [ + $response = $this->httpClient->get($url, [ 'auth' => [$email, $password], 'headers' => ['Accept' => 'application/json'], ]); @@ -232,8 +220,15 @@ public function doesRoleExist(string $org, string $email, string $password, stri // Any other response was an exception. throw $exception; } - // If 200 response, role exists. - return TRUE; + + // Make sure role exists. + $body = json_decode((string) $response->getBody()); + if (isset($body->name) && $body->name == $role_name) { + return TRUE; + } + else { + return FALSE; + } } /** @@ -268,15 +263,28 @@ public function isValidEdgeCredentials(StyleInterface $io, callable $t, string $ return FALSE; } - $body = json_decode($response->getBody()); + // Make sure a response is returned. + $raw_body = (string) $response->getBody(); + if (empty($raw_body)) { + $io->error($t('Response to :url returned empty. HTTP !response_code !response_reason', [ + ':url' => $url, + '!response_code' => $response->getStatusCode(), + '!response_reason' => json_last_error_msg(), + ])); + return FALSE; + } + $body = json_decode($raw_body); + if (JSON_ERROR_NONE !== json_last_error()) { - $io->error($t('Unable to parse response from Apigee Edge into JSON. !error ', ['error' => json_last_error_msg()])); - $io->section($t('Server response from :url', [':url' => $url])); - $io->text($response->getBody()); + $io->error($t('Unable to parse response from GET :url into JSON: !error ', [ + ':url' => $url, + '!error' => json_last_error_msg(), + ])); return FALSE; } if (!isset($body->name)) { - $io->warning($t('The response from :url did not contain the org name.', [':url' => $url])); + $io->error($t('The response from GET :url did not contain valid org data.', [':url' => $url])); + return FALSE; } else { $io->success($t('Connected to Edge org :org.', [':org' => $body->name])); @@ -301,7 +309,10 @@ public function isValidEdgeCredentials(StyleInterface $io, callable $t, string $ * The email of an Edge user with org admin role to make Edge API calls. */ public function handleHttpClientExceptions(TransferException $exception, StyleInterface $io, callable $t, string $url, string $org, string $email): void { + // Display error message. $io->error($t('Error connecting to Apigee Edge. :exception_message', [':exception_message' => $exception->getMessage()])); + + // Add a note to common situations on what could be wrong. switch ($exception->getCode()) { case 0: $io->note($t('Your system may not be able to connect to :url.', [ @@ -323,7 +334,6 @@ public function handleHttpClientExceptions(TransferException $exception, StyleIn case 302: $io->note($t('Edge endpoint gives a redirect response, is the url :url does not seem to be a valid Apigee Edge endpoint.', [':url' => $url])); return; - } } diff --git a/src/Command/Util/ApigeeEdgeManagementCliServiceInterface.php b/src/Command/Util/ApigeeEdgeManagementCliServiceInterface.php index 9f838a55f..4194b1b74 100644 --- a/src/Command/Util/ApigeeEdgeManagementCliServiceInterface.php +++ b/src/Command/Util/ApigeeEdgeManagementCliServiceInterface.php @@ -26,9 +26,6 @@ */ interface ApigeeEdgeManagementCliServiceInterface { - // Default base url. - const DEFAULT_BASE_URL = 'https://api.enterprise.apigee.com/v1'; - // Default role name to create in Apigee Edge. const DEFAULT_ROLE_NAME = 'drupalportal'; @@ -49,7 +46,18 @@ interface ApigeeEdgeManagementCliServiceInterface { * The base url of the Edge API. * @param null|string $role_name * The role name to add the permissions to. + * @param null|bool $force + * Force running of permissions on a role that already exists. */ - public function createEdgeRoleForDrupal(StyleInterface $io, callable $t, string $org, string $email, string $password, ?string $base_url, ?string $role_name); + public function createEdgeRoleForDrupal( + StyleInterface $io, + callable $t, + string $org, + string $email, + string $password, + ?string $base_url, + ?string $role_name, + bool $force + ); } diff --git a/src/Commands/ApigeeEdgeCommands.php b/src/Commands/ApigeeEdgeCommands.php index 82eef676a..658faf519 100644 --- a/src/Commands/ApigeeEdgeCommands.php +++ b/src/Commands/ApigeeEdgeCommands.php @@ -80,12 +80,17 @@ public function sync() { * https://api.enterprise.apigee.com/v1. * @option role-name * The role to create in the Apigee Edge org, defaults to "drupalportal". + * @option $force + * Force running of permissions on a role that already exists, defaults + * to throwing an error message if role exists. * @usage drush create-edge-role myorg me@example.com * Create "drupalportal" role as orgadmin me@example.com for org myorg. + * @usage drush create-edge-role myorg me@example.com --role-name=portal + * Create role named "portal" * @usage drush create-edge-role myorg me@example.com --base-url=https://api.edge.example.com * Create role on private Apigee Edge server "api.edge.example.com". - * @usage drush create-edge-role myorg me@example.com --role-name=portal - * Create role named "portal". + * @usage drush create-edge-role myorg me@example.com --force + * Update permissions on "drupalportal" role even if role already exists. * @command apigee-edge:create-edge-role * @aliases create-edge-role */ @@ -96,8 +101,20 @@ public function createEdgeRole( 'password' => NULL, 'base-url' => NULL, 'role-name' => NULL, + 'force' => FALSE, ]) { - $this->cliService->createEdgeRoleForDrupal($this->io(), 'dt', $org, $email, $options['password'], $options['base-url'], $options['role-name']); + + // Call the CLI Service. + $this->cliService->createEdgeRoleForDrupal( + $this->io(), + 'dt', + $org, + $email, + $options['password'], + $options['base-url'], + $options['role-name'], + $options['force'] + ); } /** diff --git a/tests/src/Unit/Command/CreateEdgeRoleCommandTest.php b/tests/src/Unit/Command/CreateEdgeRoleCommandTest.php index 2d62e5471..491fe1a67 100644 --- a/tests/src/Unit/Command/CreateEdgeRoleCommandTest.php +++ b/tests/src/Unit/Command/CreateEdgeRoleCommandTest.php @@ -129,9 +129,34 @@ public function testCreateEdgeRole() { Argument::type('string'), Argument::type('string'), Argument::type('string'), - Argument::type('string') - ) - ->shouldHaveBeenCalledTimes(1); + Argument::type('string'), + Argument::type('bool') + )->shouldHaveBeenCalledTimes(1); + } + + /** + * Calls to Drush command should pass through to CLI service. + */ + public function testCreateEdgeRoleForceParam() { + $this->input->getArgument(Argument::is('org'))->willReturn('myorg'); + $this->input->getArgument(Argument::is('email'))->willReturn('email@example.com'); + $this->input->getOption(Argument::is('password'))->willReturn('secret'); + $this->input->getOption(Argument::is('base-url'))->willReturn('http://base-url'); + $this->input->getOption(Argument::is('role-name'))->willReturn('custom_drupal_role'); + $this->input->getOption(Argument::is('force'))->willReturn('true'); + + $this->createEdgeRoleCommand->execute($this->input->reveal(), $this->output->reveal()); + + $this->cliService->createEdgeRoleForDrupal( + Argument::type(DrupalStyle::class), + Argument::type('string'), + Argument::type('string'), + Argument::type('string'), + Argument::type('string'), + Argument::type('string'), + Argument::type('string'), + Argument::type('bool') + )->shouldHaveBeenCalledTimes(1); } /** @@ -151,7 +176,6 @@ public function testInteractWithPasswordParam() { // Interact should not change password since it was passed in. $this->input->getOption('password')->shouldHaveBeenCalled(); $this->input->setOption('password')->shouldNotHaveBeenCalled(); - } /** @@ -172,7 +196,6 @@ public function testInteractPasswordParamEmpty() { // Interact should not change password since it was passed in. $this->input->getOption('password')->shouldHaveBeenCalled(); $this->input->setOption('password', NULL)->shouldHaveBeenCalled(); - } } diff --git a/tests/src/Unit/Command/Util/ApigeeEdgeManagementCliServiceTest.php b/tests/src/Unit/Command/Util/ApigeeEdgeManagementCliServiceTest.php index 5058380b1..e6caeaedd 100644 --- a/tests/src/Unit/Command/Util/ApigeeEdgeManagementCliServiceTest.php +++ b/tests/src/Unit/Command/Util/ApigeeEdgeManagementCliServiceTest.php @@ -19,6 +19,7 @@ namespace Drupal\Tests\apigee_edge\Unit; +use Apigee\Edge\ClientInterface as ApigeeClientInterface; use Drupal\apigee_edge\Command\Util\ApigeeEdgeManagementCliService; use Drupal\apigee_edge\Command\Util\ApigeeEdgeManagementCliServiceInterface; use Drupal\Tests\UnitTestCase; @@ -93,214 +94,266 @@ protected function setUp() { /** * Call createEdgeRoleForDrupal with null base URL to test default base URL. */ - public function testCreateEdgeRoleForDrupalDefaultBaseUrl() { + public function testCreateEdgeRoleForDrupalCustomRoleAndBaseUrl() { + // Output to user should show role created and permissions set. $io = $this->prophesize(StyleInterface::class); $io->success(Argument::exact('Connected to Edge org ' . $this->org . '.'))->shouldBeCalledTimes(1); $io->success(Argument::containingString('Role ' . $this->roleName . ' is configured.'))->shouldBeCalledTimes(1); - $io->text(Argument::containingString('Role ' . $this->roleName . ' already exists.'))->shouldBeCalledTimes(1); + $io->text(Argument::containingString('Role ' . $this->roleName . ' does not exist. Creating role.'))->shouldBeCalledTimes(1); $io->text(Argument::containingString('Setting permissions on role ' . $this->roleName . '.'))->shouldBeCalledTimes(1); $io->text(Argument::containingString('/'))->shouldBeCalledTimes(12); + // Org should exist. $response_org = $this->prophesize(Response::class); $response_org->getBody() ->shouldBeCalledTimes(1) ->willReturn('{ "name": "' . $this->org . '" }'); - $this->httpClient - ->get(Argument::exact(ApigeeEdgeManagementCliServiceInterface::DEFAULT_BASE_URL . '/o/' . $this->org), Argument::type('array')) + ->get(Argument::exact($this->baseUrl . '/o/' . $this->org), Argument::type('array')) ->shouldBeCalledTimes(1) ->willReturn($response_org->reveal()); - $response_user_role = $this->prophesize(Response::class); + // The role should not exist yet in system. + $request_role = $this->prophesize(RequestInterface::class); + $response_role = $this->prophesize(Response::class); + $response_role->getStatusCode()->willReturn(404); + $exception = new ClientException('Forbidden', $request_role->reveal(), $response_role->reveal()); + $this->httpClient + ->get(Argument::exact($this->baseUrl . '/o/' . $this->org . '/userroles/' . $this->roleName), Argument::type('array')) + ->willThrow($exception); + // The role should be created. $this->httpClient - ->get(Argument::exact(ApigeeEdgeManagementCliServiceInterface::DEFAULT_BASE_URL . '/o/' . $this->org . '/userroles/' . $this->roleName), Argument::type('array')) - ->willReturn($response_user_role->reveal()); + ->post(Argument::exact($this->baseUrl . '/o/' . $this->org . '/userroles'), Argument::type('array')) + ->shouldBeCalledTimes(1); + // The permissions should be set properly. $this->httpClient - ->post(Argument::exact(ApigeeEdgeManagementCliServiceInterface::DEFAULT_BASE_URL . '/o/' . $this->org . '/userroles/' . $this->roleName . '/permissions'), Argument::type('array')) + ->post(Argument::exact($this->baseUrl . '/o/' . $this->org . '/userroles/' . $this->roleName . '/permissions'), Argument::type('array')) ->shouldBeCalledTimes(12); $apigee_edge_management_cli_service = new ApigeeEdgeManagementCliService($this->httpClient->reveal()); - $apigee_edge_management_cli_service->createEdgeRoleForDrupal($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, NULL, $this->roleName); + $apigee_edge_management_cli_service->createEdgeRoleForDrupal($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->baseUrl, $this->roleName, FALSE); } /** * Pass null role name to test using default role name. */ - public function testCreateEdgeRoleForDrupalDefaultRoleName() { + public function testCreateEdgeRoleForDrupalDefaultRoleAndBaseUrl() { + // Output to user should show role created and permissions set. $io = $this->prophesize(StyleInterface::class); $io->success(Argument::exact('Connected to Edge org ' . $this->org . '.'))->shouldBeCalledTimes(1); $io->success(Argument::containingString('Role ' . ApigeeEdgeManagementCliServiceInterface::DEFAULT_ROLE_NAME . ' is configured.'))->shouldBeCalledTimes(1); - $io->text(Argument::containingString('Role ' . ApigeeEdgeManagementCliServiceInterface::DEFAULT_ROLE_NAME . ' already exists.'))->shouldBeCalledTimes(1); + $io->text(Argument::containingString('Role ' . ApigeeEdgeManagementCliServiceInterface::DEFAULT_ROLE_NAME . ' does not exist'))->shouldBeCalledTimes(1); $io->text(Argument::containingString('Setting permissions on role ' . ApigeeEdgeManagementCliServiceInterface::DEFAULT_ROLE_NAME . '.'))->shouldBeCalledTimes(1); $io->text(Argument::containingString('/'))->shouldBeCalledTimes(12); + // Org should exist. $response_org = $this->prophesize(Response::class); $response_org->getBody() ->shouldBeCalledTimes(1) ->willReturn('{ "name": "' . $this->org . '" }'); - $this->httpClient - ->get(Argument::exact($this->baseUrl . '/o/' . $this->org), Argument::type('array')) + ->get(Argument::exact(ApigeeClientInterface::DEFAULT_ENDPOINT . '/o/' . $this->org), Argument::type('array')) ->shouldBeCalledTimes(1) ->willReturn($response_org->reveal()); - $response_user_role = $this->prophesize(Response::class); + // The role should not exist yet in system. + $request_role = $this->prophesize(RequestInterface::class); + $response_role = $this->prophesize(Response::class); + $response_role->getStatusCode()->willReturn(404); + $exception = new ClientException('Forbidden', $request_role->reveal(), $response_role->reveal()); + $this->httpClient + ->get(Argument::exact(ApigeeClientInterface::DEFAULT_ENDPOINT . '/o/' . $this->org . '/userroles/' . ApigeeEdgeManagementCliServiceInterface::DEFAULT_ROLE_NAME), Argument::type('array')) + ->willThrow($exception); + // The role should be created. $this->httpClient - ->get(Argument::exact($this->baseUrl . '/o/' . $this->org . '/userroles/' . ApigeeEdgeManagementCliServiceInterface::DEFAULT_ROLE_NAME), Argument::type('array')) - ->willReturn($response_user_role->reveal()); + ->post(Argument::exact(ApigeeClientInterface::DEFAULT_ENDPOINT . '/o/' . $this->org . '/userroles'), Argument::type('array')) + ->shouldBeCalledTimes(1); + // The permissions should be set. $this->httpClient - ->post(Argument::exact($this->baseUrl . '/o/' . $this->org . '/userroles/' . ApigeeEdgeManagementCliServiceInterface::DEFAULT_ROLE_NAME . '/permissions'), Argument::type('array')) + ->post(Argument::exact(ApigeeClientInterface::DEFAULT_ENDPOINT . '/o/' . $this->org . '/userroles/' . ApigeeEdgeManagementCliServiceInterface::DEFAULT_ROLE_NAME . '/permissions'), Argument::type('array')) ->shouldBeCalledTimes(12); $apigee_edge_management_cli_service = new ApigeeEdgeManagementCliService($this->httpClient->reveal()); - $apigee_edge_management_cli_service->createEdgeRoleForDrupal($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->baseUrl, NULL); + $apigee_edge_management_cli_service->createEdgeRoleForDrupal($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, NULL, NULL, FALSE); } /** - * Should return true if creds are valid. + * Allow role to get modified w/force option. */ - public function testIsValidEdgeCredentialsNotValid() { - $body = "

not json

"; - - $response = $this->prophesize(Response::class); - $response->getBody() - ->shouldBeCalledTimes(2) - ->willReturn($body); - + public function testCreateEdgeRoleForDrupalWhenRoleExistsTestWithForceFlag() { + // Expected to output error if role does not exist. $io = $this->prophesize(StyleInterface::class); - $io->error(Argument::containingString('Unable to parse response from Apigee Edge into JSON')) - ->shouldBeCalledTimes(1); - $io->section(Argument::type('string')) - ->shouldBeCalledTimes(1); - $io->text(Argument::type('string')) - ->shouldBeCalledTimes(1); + $io->success(Argument::exact('Connected to Edge org ' . $this->org . '.'))->shouldBeCalledTimes(1); + $io->text(Argument::containingString('Setting permissions on role ' . $this->roleName . '.'))->shouldBeCalledTimes(1); + $io->text(Argument::containingString('/'))->shouldBeCalledTimes(12); + $io->success(Argument::containingString('Role ' . $this->roleName . ' is configured.'))->shouldBeCalledTimes(1); + // Return organization info. + $response_org = $this->prophesize(Response::class); + $response_org->getBody() + ->shouldBeCalledTimes(1) + ->willReturn('{ "name": "' . $this->org . '" }'); $this->httpClient - ->get(Argument::type('string'), Argument::type('array')) - ->willReturn($response->reveal()); - - $apigee_edge_management_cli_service = new ApigeeEdgeManagementCliService($this->httpClient->reveal()); - $is_valid_creds = $apigee_edge_management_cli_service->isValidEdgeCredentials($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->baseUrl); - $this->assertEquals(FALSE, $is_valid_creds, 'Credentials are not valid, should return false.'); - } - - /** - * Should return true if creds are valid. - */ - public function testIsValidEdgeCredentialsValid() { - $body = '{ "name": "' . $this->org . '" }'; - - $response = $this->prophesize(Response::class); - $response->getBody() + ->get(Argument::exact($this->baseUrl . '/o/' . $this->org), Argument::type('array')) ->shouldBeCalledTimes(1) - ->willReturn($body); + ->willReturn($response_org->reveal()); + // Return existing role. + $response_user_role = $this->prophesize(Response::class); + $response_user_role->getBody()->willReturn('{ "name": "' . $this->roleName . '" }'); $this->httpClient - ->get(Argument::type('string'), Argument::type('array')) - ->willReturn($response->reveal()); + ->get(Argument::exact($this->baseUrl . '/o/' . $this->org . '/userroles/' . $this->roleName), Argument::type('array')) + ->willReturn($response_user_role->reveal()); - $io = $this->prophesize(StyleInterface::class); - $io->error(Argument::type('string')) - ->shouldNotBeCalled(); - $io->section(Argument::type('string')) - ->shouldNotBeCalled(); - $io->text(Argument::type('string')) + // The role should NOT be created since is already exists. + $this->httpClient + ->post(Argument::exact($this->baseUrl . '/o/' . $this->org . '/userroles'), Argument::type('array')) ->shouldNotBeCalled(); - $io->success(Argument::type('string')) - ->shouldBeCalled(); + + // The permissions should be set. + $this->httpClient + ->post(Argument::exact($this->baseUrl . '/o/' . $this->org . '/userroles/' . $this->roleName . '/permissions'), Argument::type('array')) + ->shouldBeCalledTimes(12); $apigee_edge_management_cli_service = new ApigeeEdgeManagementCliService($this->httpClient->reveal()); - $is_valid_creds = $apigee_edge_management_cli_service->isValidEdgeCredentials($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->baseUrl); - $this->assertEquals(TRUE, $is_valid_creds, 'Credentials are not valid, should return false.'); + $apigee_edge_management_cli_service->createEdgeRoleForDrupal($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->baseUrl, $this->roleName, TRUE); } /** - * Should make a call to create role if does not exist. + * If force parameter is not passed in, do not mess with a role that exists. */ - public function testCreateEdgeRoleWhenRoleDoesNotExistTest() { - + public function testCreateEdgeRoleForDrupalWhenRoleExistsTestNoForceFlag() { + // Expected to output error if role does not exist. $io = $this->prophesize(StyleInterface::class); $io->success(Argument::exact('Connected to Edge org ' . $this->org . '.'))->shouldBeCalledTimes(1); - $io->success(Argument::containingString('Role ' . $this->roleName . ' is configured.'))->shouldBeCalledTimes(1); - $io->text(Argument::containingString('Role ' . $this->roleName . ' already exists.'))->shouldBeCalledTimes(1); - $io->text(Argument::containingString('Setting permissions on role ' . $this->roleName . '.'))->shouldBeCalledTimes(1); - $io->text(Argument::containingString('/'))->shouldBeCalledTimes(12); + $io->error(Argument::containingString('Role ' . $this->roleName . ' already exists.'))->shouldBeCalledTimes(1); + $io->note(Argument::containingString('Run with --force option'))->shouldBeCalled(); - $body = '{ "name": "' . $this->org . '" }'; - $response = $this->prophesize(Response::class); - $response->getBody() + // Return organization info. + $response_org = $this->prophesize(Response::class); + $response_org->getBody() ->shouldBeCalledTimes(1) - ->willReturn($body); - + ->willReturn('{ "name": "' . $this->org . '" }'); $this->httpClient ->get(Argument::exact($this->baseUrl . '/o/' . $this->org), Argument::type('array')) ->shouldBeCalledTimes(1) - ->willReturn($response->reveal()); + ->willReturn($response_org->reveal()); + // Return existing role. + $response_user_role = $this->prophesize(Response::class); + $response_user_role->getBody()->willReturn('{ "name": "' . $this->roleName . '" }'); $this->httpClient ->get(Argument::exact($this->baseUrl . '/o/' . $this->org . '/userroles/' . $this->roleName), Argument::type('array')) + ->willReturn($response_user_role->reveal()); + + $apigee_edge_management_cli_service = new ApigeeEdgeManagementCliService($this->httpClient->reveal()); + $apigee_edge_management_cli_service->createEdgeRoleForDrupal($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->baseUrl, $this->roleName, FALSE); + } + + /** + * Test isValidEdgeCredentials() bad endpoint response. + */ + public function testIsValidEdgeCredentialsBadEndpoint() { + // Mimic a invalid response for the call to get org details. + $body = "

not json

"; + $response = $this->prophesize(Response::class); + $response->getBody() ->shouldBeCalledTimes(1) - ->willReturn($response->reveal()); + ->willReturn($body); + // The user should see an error message. + $io = $this->prophesize(StyleInterface::class); + $io->error(Argument::containingString('Unable to parse response from GET')) + ->shouldBeCalledTimes(1); $this->httpClient - ->post(Argument::type('string'), Argument::type('array')) + ->get(Argument::type('string'), Argument::type('array')) ->willReturn($response->reveal()); $apigee_edge_management_cli_service = new ApigeeEdgeManagementCliService($this->httpClient->reveal()); - $apigee_edge_management_cli_service->createEdgeRoleForDrupal($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->baseUrl, $this->roleName); + $is_valid_creds = $apigee_edge_management_cli_service->isValidEdgeCredentials($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->baseUrl); + + // Assert return that creds are false. + $this->assertEquals(FALSE, $is_valid_creds, 'Credentials are not valid, should return false.'); } /** - * Recreate role if it already exists. + * Test isValidEdgeCredentials() unauthorized response. */ - public function testCreateEdgeRoleForDrupalWhenRoleExistsTest() { + public function testIsValidEdgeCredentialsUnauthorized() { + // Invalid password returns unauthorized 403. + $request_role = $this->prophesize(RequestInterface::class); + $response_role = $this->prophesize(Response::class); + $response_role->getStatusCode()->willReturn(403); + $exception = new ClientException('Unauthorized', $request_role->reveal(), $response_role->reveal()); + $this->httpClient + ->get(Argument::exact($this->baseUrl . '/o/' . $this->org), Argument::type('array')) + ->willThrow($exception) + ->shouldBeCalledTimes(1); + + // The user should see an error message. $io = $this->prophesize(StyleInterface::class); - $io->success(Argument::exact('Connected to Edge org ' . $this->org . '.'))->shouldBeCalledTimes(1); - $io->success(Argument::containingString('Role ' . $this->roleName . ' is configured.'))->shouldBeCalledTimes(1); - $io->text(Argument::containingString('Role ' . $this->roleName . ' already exists.'))->shouldBeCalledTimes(1); - $io->text(Argument::containingString('Setting permissions on role ' . $this->roleName . '.'))->shouldBeCalledTimes(1); - $io->text(Argument::containingString('/'))->shouldBeCalledTimes(12); + $io->error(Argument::containingString('Error connecting to Apigee Edge')) + ->shouldBeCalledTimes(1); + $io->note(Argument::containingString('may not have the orgadmin role for Apigee Edge org')) + ->shouldBeCalledTimes(1); + + $apigee_edge_management_cli_service = new ApigeeEdgeManagementCliService($this->httpClient->reveal()); + $is_valid_creds = $apigee_edge_management_cli_service->isValidEdgeCredentials($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->baseUrl); + $this->assertEquals(FALSE, $is_valid_creds, 'Credentials are not valid, should return false.'); + } + /** + * Should return true if creds are valid. + */ + public function testIsValidEdgeCredentialsValid() { + // Org should exist. $response_org = $this->prophesize(Response::class); $response_org->getBody() ->shouldBeCalledTimes(1) ->willReturn('{ "name": "' . $this->org . '" }'); - $this->httpClient ->get(Argument::exact($this->baseUrl . '/o/' . $this->org), Argument::type('array')) ->shouldBeCalledTimes(1) ->willReturn($response_org->reveal()); - $response_user_role = $this->prophesize(Response::class); - - $this->httpClient - ->get(Argument::exact($this->baseUrl . '/o/' . $this->org . '/userroles/' . $this->roleName), Argument::type('array')) - ->willReturn($response_user_role->reveal()); - - $this->httpClient - ->post(Argument::exact($this->baseUrl . '/o/' . $this->org . '/userroles/' . $this->roleName . '/permissions'), Argument::type('array')) - ->shouldBeCalledTimes(12); + // Errors should not be called. + $io = $this->prophesize(StyleInterface::class); + $io->error(Argument::type('string')) + ->shouldNotBeCalled(); + $io->section(Argument::type('string')) + ->shouldNotBeCalled(); + $io->text(Argument::type('string')) + ->shouldNotBeCalled(); + $io->success(Argument::type('string')) + ->shouldBeCalled(); $apigee_edge_management_cli_service = new ApigeeEdgeManagementCliService($this->httpClient->reveal()); - $apigee_edge_management_cli_service->createEdgeRoleForDrupal($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->baseUrl, $this->roleName); + $is_valid_creds = $apigee_edge_management_cli_service->isValidEdgeCredentials($io->reveal(), [$this, 'mockDt'], $this->org, $this->email, $this->password, $this->baseUrl); + + // Assertions. + $this->assertEquals(TRUE, $is_valid_creds, 'Credentials are not valid, should return false.'); } /** * Validate doesRoleExist works when role does not exist. */ public function testDoesRoleExistTrue() { - + // Return existing role. + $response_user_role = $this->prophesize(Response::class); + $response_user_role->getBody()->willReturn('{ "name": "' . $this->roleName . '" }'); $this->httpClient - ->get(Argument::cetera()) + ->get(Argument::exact($this->baseUrl . '/o/' . $this->org . '/userroles/' . $this->roleName), Argument::type('array')) ->shouldBeCalledTimes(1) - ->willReturn(NULL); + ->willReturn($response_user_role->reveal()); $apigee_edge_management_cli_service = new ApigeeEdgeManagementCliService($this->httpClient->reveal()); $does_role_exist = $apigee_edge_management_cli_service->doesRoleExist($this->org, $this->email, $this->password, $this->baseUrl, $this->roleName); + + // Assert returned true. $this->assertEquals(TRUE, $does_role_exist, 'Method doesRoleExist() should return true when role exists.'); } @@ -308,33 +361,30 @@ public function testDoesRoleExistTrue() { * Validate doesRoleExist works when role exists. */ public function testDoesRoleExistNotTrue() { - - $request = $this->prophesize(RequestInterface::class); - $response = $this->prophesize(Response::class); - $response->getStatusCode()->willReturn(404); - - // Http client throws exception when role does not exist. - $exception = new ClientException('Role does not exist.', $request->reveal(), $response->reveal()); - + // The role should not exist in system. + $request_role = $this->prophesize(RequestInterface::class); + $response_role = $this->prophesize(Response::class); + $response_role->getStatusCode()->willReturn(404); + $exception = new ClientException('Forbidden', $request_role->reveal(), $response_role->reveal()); $this->httpClient - ->get(Argument::type('string'), Argument::type('array')) + ->get(Argument::exact($this->baseUrl . '/o/' . $this->org . '/userroles/' . $this->roleName), Argument::type('array')) ->willThrow($exception); $apigee_edge_management_cli_service = new ApigeeEdgeManagementCliService($this->httpClient->reveal()); $does_role_exist = $apigee_edge_management_cli_service->doesRoleExist($this->org, $this->email, $this->password, $this->baseUrl, $this->roleName); + // Assert returns false. $this->assertEquals(FALSE, $does_role_exist, 'Method doesRoleExist() should return false when role exists.'); } /** * Validate when exception thrown function works correctly. */ - public function testDoesRoleExistExceptionThrown() { + public function testDoesRoleExistServerErrorThrown() { + // Http client throws exception if network or server error happens. $request = $this->prophesize(RequestInterface::class); $response = $this->prophesize(Response::class); $response->getStatusCode()->willReturn(500); - - // Http client throws exception if network or server error happens. $exception = new ServerException('Server error.', $request->reveal(), $response->reveal()); $this->expectException(ServerException::class); $this->httpClient @@ -349,11 +399,14 @@ public function testDoesRoleExistExceptionThrown() { * Make sure method outputs more info for error codes. */ public function testHandleHttpClientExceptions0Code() { - $exception = $this->prophesize(TransferException::class); + // Error message should output to user. $io = $this->prophesize(StyleInterface::class); $io->error(Argument::containingString('Error connecting to Apigee Edge'))->shouldBeCalledTimes(1); $io->note(Argument::containingString('Your system may not be able to connect'))->shouldBeCalledTimes(1); + // Create network error. + $exception = $this->prophesize(TransferException::class); + $apigee_edge_management_cli_service = new ApigeeEdgeManagementCliService($this->httpClient->reveal()); $apigee_edge_management_cli_service->handleHttpClientExceptions($exception->reveal(), $io->reveal(), [$this, 'mockDt'], 'http://api.apigee.com/test', $this->org, $this->email); } @@ -362,12 +415,13 @@ public function testHandleHttpClientExceptions0Code() { * Make sure method outputs more info for error codes. */ public function testHandleHttpClientExceptions401Code() { + // Server returns 401 unauthorized. $request = $this->prophesize(RequestInterface::class); $response = $this->prophesize(Response::class); $response->getStatusCode()->willReturn(401); + $exception = new ClientException('Unauthorized', $request->reveal(), $response->reveal()); - $exception = new ClientException('Forbidden', $request->reveal(), $response->reveal()); - + // Expect user friendly message displayed about error. $io = $this->prophesize(StyleInterface::class); $io->error(Argument::containingString('Error connecting to Apigee Edge'))->shouldBeCalledTimes(1); $io->note(Argument::exact('Your username or password is invalid.'))->shouldBeCalledTimes(1); @@ -380,12 +434,13 @@ public function testHandleHttpClientExceptions401Code() { * Make sure method outputs more info for error codes. */ public function testHandleHttpClientExceptions403Code() { + // Server returns 403 forbidden. $request = $this->prophesize(RequestInterface::class); $response = $this->prophesize(Response::class); $response->getStatusCode()->willReturn(403); - $exception = new ClientException('Forbidden', $request->reveal(), $response->reveal()); + // Expect error messages. $io = $this->prophesize(StyleInterface::class); $io->error(Argument::containingString('Error connecting to Apigee Edge'))->shouldBeCalledTimes(1); $io->note(Argument::containingString('User ' . $this->email . ' may not have the orgadmin role'))->shouldBeCalledTimes(1); @@ -398,12 +453,13 @@ public function testHandleHttpClientExceptions403Code() { * Make sure method outputs more info for error codes. */ public function testHandleHttpClientExceptions302Code() { + // Return a 302 redirection response, which Apigee API would not do. $request = $this->prophesize(RequestInterface::class); $response = $this->prophesize(Response::class); $response->getStatusCode()->willReturn(302); - $exception = new ClientException('Forbidden', $request->reveal(), $response->reveal()); + // User should see error message. $io = $this->prophesize(StyleInterface::class); $io->error(Argument::containingString('Error connecting to Apigee Edge'))->shouldBeCalledTimes(1); $io->note(Argument::containingString('the url ' . $this->baseUrl . '/test' . ' does not seem to be a valid Apigee Edge endpoint.'))->shouldBeCalledTimes(1); @@ -418,16 +474,17 @@ public function testHandleHttpClientExceptions302Code() { * @throws \ReflectionException */ public function testSetDefaultPermissions() { - $apigee_edge_management_cli_service = new ApigeeEdgeManagementCliService($this->httpClient->reveal()); - $io = $this->prophesize(StyleInterface::class); - + // The permissions POST call will be made 12 times. $this->httpClient->post(Argument::type('string'), Argument::type('array'))->shouldBeCalledTimes(12); // Make method under test not private. + $apigee_edge_management_cli_service = new ApigeeEdgeManagementCliService($this->httpClient->reveal()); $apigee_edge_management_cli_service_reflection = new ReflectionClass($apigee_edge_management_cli_service); $method_set_default_permissions = $apigee_edge_management_cli_service_reflection->getMethod('setDefaultPermissions'); $method_set_default_permissions->setAccessible(TRUE); + // Create input params. + $io = $this->prophesize(StyleInterface::class); $args = [ $io->reveal(), [$this, 'mockDt'], @@ -437,6 +494,8 @@ public function testSetDefaultPermissions() { $this->baseUrl, $this->roleName, ]; + + // Make call. $method_set_default_permissions->invokeArgs($apigee_edge_management_cli_service, $args); } diff --git a/tests/src/Unit/Commands/ApigeeEdgeCommandsTest.php b/tests/src/Unit/Commands/ApigeeEdgeCommandsTest.php index 8101a976b..a97470977 100644 --- a/tests/src/Unit/Commands/ApigeeEdgeCommandsTest.php +++ b/tests/src/Unit/Commands/ApigeeEdgeCommandsTest.php @@ -84,6 +84,7 @@ public function testCreateEdgeRole() { 'password' => 'opensesame', 'base-url' => 'http://api.apigee.com/v1', 'role-name' => 'portalRole', + 'force' => 'FALSE', ]; $this->apigeeEdgeCommands->createEdgeRole('orgA', 'emailA', $drush_options); @@ -95,7 +96,8 @@ public function testCreateEdgeRole() { Argument::type('string'), Argument::type('string'), Argument::type('string'), - Argument::type('string') + Argument::type('string'), + Argument::type('bool') ) ->shouldHaveBeenCalledTimes(1); @@ -145,7 +147,58 @@ public function testValidatePasswordParamEmpty() { ->shouldBeCalled(); $command_data_input->setOption('password', 'I<3APIS!') ->shouldHaveBeenCalled(); + } + + /** + * Test calling with force function when role already exists. + */ + public function testCreateEdgeEdgeRoleWithForceParam() { + $drush_options = [ + 'password' => 'opensesame', + 'base-url' => 'http://api.apigee.com/v1', + 'role-name' => 'portalRole', + 'force' => TRUE, + ]; + + $this->apigeeEdgeCommands->createEdgeRole('orgA', 'emailA', $drush_options); + + $this->cliService->createEdgeRoleForDrupal( + Argument::type(DrushStyle::class), + Argument::type('string'), + Argument::type('string'), + Argument::type('string'), + Argument::type('string'), + Argument::type('string'), + Argument::type('string'), + TRUE + ) + ->shouldHaveBeenCalledTimes(1); + } + /** + * Test calling when role exists but force flag not given, should error. + */ + public function testCreateEdgeEdgeRoleWithoutForceParam() { + $drush_options = [ + 'password' => 'opensesame', + 'base-url' => 'http://api.apigee.com/v1', + 'role-name' => 'portalRole', + 'force' => FALSE, + ]; + + $this->apigeeEdgeCommands->createEdgeRole('orgA', 'emailA', $drush_options); + + $this->cliService->createEdgeRoleForDrupal( + Argument::type(DrushStyle::class), + Argument::type('string'), + Argument::type('string'), + Argument::type('string'), + Argument::type('string'), + Argument::type('string'), + Argument::type('string'), + FALSE + ) + ->shouldHaveBeenCalledTimes(1); } } From 93497acefd79f1bc11d9921b027cc882e4b302d6 Mon Sep 17 00:00:00 2001 From: Chris Novak Date: Sun, 15 Dec 2019 13:33:22 -0800 Subject: [PATCH 7/8] Update tests/src/Functional/ApiProductAccessTest.php Co-Authored-By: Arlina Espinoza Rhoton --- tests/src/Functional/ApiProductAccessTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/Functional/ApiProductAccessTest.php b/tests/src/Functional/ApiProductAccessTest.php index fdff1d7a2..8f0f1d392 100644 --- a/tests/src/Functional/ApiProductAccessTest.php +++ b/tests/src/Functional/ApiProductAccessTest.php @@ -307,7 +307,7 @@ protected function developerAppEditFormTest() { // >> Bypass user. $this->drupalLogin($this->users[self::USER_WITH_BYPASS_PERM]); // Even if a user has bypass permission they should see only those API - // Products on on an other user's add/edit form that the other user has + // Products on another user's add/edit form that the other user has // access. $this->drupalGet(Url::fromRoute('entity.developer_app.add_form_for_developer', [ 'user' => $this->users[AccountInterface::AUTHENTICATED_ROLE]->id(), From d6e3c4d0ddf53207efb4fca8793e7104965f1d2f Mon Sep 17 00:00:00 2001 From: Chris Novak Date: Mon, 16 Dec 2019 07:50:48 -0800 Subject: [PATCH 8/8] Add more detail to help for force command --- console/translations/en/apigee_edge.role.create.yml | 4 ++-- src/Commands/ApigeeEdgeCommands.php | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/console/translations/en/apigee_edge.role.create.yml b/console/translations/en/apigee_edge.role.create.yml index 82d000b95..19db3c8a7 100644 --- a/console/translations/en/apigee_edge.role.create.yml +++ b/console/translations/en/apigee_edge.role.create.yml @@ -1,5 +1,5 @@ description: 'Create Apigee role for Drupal' -help: 'Create a custom Apigee role that limits permissions for Drupal connections to the Apigee API.' +help: 'Create a custom Apigee role that limits permissions for Drupal connections to the Apigee API. ' arguments: org: 'The Apigee Edge org to create the role in' email: 'An Apigee user email address with orgadmin role for this org' @@ -7,7 +7,7 @@ options: password: 'Password for the Apigee orgadmin user (if not passed in you will be prompted)' base-url: 'Base URL to use, defaults to public cloud URL https://api.enterprise.apigee.com/v1' role-name: 'The role to create in the Apigee Edge org, defaults to "drupalportal"' - force: 'Force running of permissions on a role that already exists.' + force: 'Force running of permissions on a role that already exists. Note that permissions are only added, any current permissions not not removed.' questions: password: 'Enter password for Apigee orgadmin user' messages: diff --git a/src/Commands/ApigeeEdgeCommands.php b/src/Commands/ApigeeEdgeCommands.php index 658faf519..fc49d167b 100644 --- a/src/Commands/ApigeeEdgeCommands.php +++ b/src/Commands/ApigeeEdgeCommands.php @@ -82,7 +82,8 @@ public function sync() { * The role to create in the Apigee Edge org, defaults to "drupalportal". * @option $force * Force running of permissions on a role that already exists, defaults - * to throwing an error message if role exists. + * to throwing an error message if role exists. Note that permissions are + * only added, any current permissions not not removed. * @usage drush create-edge-role myorg me@example.com * Create "drupalportal" role as orgadmin me@example.com for org myorg. * @usage drush create-edge-role myorg me@example.com --role-name=portal