From ff85b7af76df96d72b1439440ecb4b635db9a8bb Mon Sep 17 00:00:00 2001 From: Mike Russell <3056352+MichaelJ2324@users.noreply.github.com> Date: Tue, 11 Feb 2025 23:53:54 -0500 Subject: [PATCH 1/2] CXAPPS-269 - Add in Integrate API's and Upsert to SugarBean --- composer.json | 2 +- examples/IntegrateApi.php | 59 ++++ src/Client/SugarApi.php | 8 + .../AbstractSugarBeanCollectionEndpoint.php | 15 +- .../Abstracts/AbstractSugarBeanEndpoint.php | 130 ++++---- src/Endpoint/Integrate.php | 192 ++++++++++++ src/Endpoint/MLPackage.php | 13 +- src/Endpoint/Me.php | 4 +- src/Endpoint/ModuleLoader.php | 3 + .../Provider/SugarEndpointProvider.php | 6 + src/Endpoint/Traits/IntegrateSyncKeyTrait.php | 49 +++ src/Endpoint/Traits/ModuleAwareTrait.php | 36 ++- ...bstractSugarBeanCollectionEndpointTest.php | 37 ++- .../AbstractSugarBeanEndpointTest.php | 120 ++++---- tests/Endpoint/IntegrateTest.php | 280 ++++++++++++++++++ tests/Endpoint/MeTest.php | 18 +- tests/Endpoint/MetadataTest.php | 4 +- tests/Endpoint/ModuleLoaderTest.php | 9 +- 18 files changed, 814 insertions(+), 171 deletions(-) create mode 100644 examples/IntegrateApi.php create mode 100644 src/Endpoint/Integrate.php create mode 100644 src/Endpoint/Traits/IntegrateSyncKeyTrait.php create mode 100644 tests/Endpoint/IntegrateTest.php diff --git a/composer.json b/composer.json index 2a20061..644fd37 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ ], "require": { "php": ">=8.0", - "michaelj2324/php-rest-client": ">=3.0.2" + "michaelj2324/php-rest-client": "3.x-dev" }, "require-dev": { "phpunit/phpunit": "9.*", diff --git a/examples/IntegrateApi.php b/examples/IntegrateApi.php new file mode 100644 index 0000000..c1f40f3 --- /dev/null +++ b/examples/IntegrateApi.php @@ -0,0 +1,59 @@ +getAuth()->setLogger($logger); +try { + if ($SugarAPI->isAuthenticated()) { + echo "Logged In: " . json_encode($SugarAPI->getAuth()->getToken(), JSON_PRETTY_PRINT) . "\n"; + + $account = $SugarAPI->module('Accounts'); + $account->set([ + 'name' => 'Upsert Test Account', + 'sync_key' => 'foobar', + ]); + $account->upsert(); + echo "Record: " . json_encode($account->toArray(), JSON_PRETTY_PRINT) . "\n"; + + $contact = $SugarAPI->module('Contacts'); + $contact->set([ + 'first_name' => 'Upsert', + 'last_name' => 'Test Contact', + ]); + //Create a contact + $contact->save(); + echo "Created Contact: " . $contact->getId() . "\n"; + + $integrate = $SugarAPI->integrate(); + $integrate->fromBean($contact); + $integrate->setSyncKey('uniqueSyncKey'); + if ($integrate->getResponse()->getStatusCode() == 200) { + echo "Contact Sync Key set: " . $contact->getSyncKey() . "\n"; + } + + $samecontact = $SugarAPI->integrate('Contacts'); + $samecontact->getBySyncKey('uniqueSyncKey'); + echo "Retrieved Contact by Sync Key: " . $samecontact->getId() . "\n"; + + $integrate->deleteBySyncKey(); + echo "Deleted Contact: " . json_encode($account->toArray(), JSON_PRETTY_PRINT) . "\n"; + } else { + echo "Could not login."; + print_r($logger->records); + $oauthEndpoint = $SugarAPI->getAuth()->getActionEndpoint('authenticate'); + $response = $oauthEndpoint->getResponse(); + if ($response) { + $statusCode = $oauthEndpoint->getResponse()->getStatusCode(); + echo "[$statusCode] - " . $oauthEndpoint->getResponse()->getBody()->getContents(); + } + } +} catch (Exception $ex) { + echo "Exception Occurred: " . $ex->getMessage(); + echo $ex->getTraceAsString(); +} diff --git a/src/Client/SugarApi.php b/src/Client/SugarApi.php index c00f718..27e6f5c 100644 --- a/src/Client/SugarApi.php +++ b/src/Client/SugarApi.php @@ -6,9 +6,11 @@ namespace Sugarcrm\REST\Client; +use GuzzleHttp\Client; use MRussell\REST\Auth\AuthControllerInterface; use Psr\Http\Message\MessageInterface; use Sugarcrm\REST\Endpoint\Generic; +use Sugarcrm\REST\Endpoint\Integrate; use Sugarcrm\REST\Endpoint\MLPackage; use Sugarcrm\REST\Endpoint\ModuleLoader; use Sugarcrm\REST\Endpoint\Ping; @@ -45,6 +47,7 @@ * @method Note note(string $id = null) - * @method ModuleLoader moduleLoader() - * @method MLPackage mlp(string $id = null) + * @method Integrate integrate(string $module = '', string $id = '') */ class SugarApi extends AbstractClient implements PlatformAwareInterface { @@ -108,6 +111,11 @@ public function __construct($server = '', array $credentials = []) } } + protected function initHttpClient(): void + { + $this->httpClient = new Client(['handler' => $this->getHandlerStack(),'verify' => false]); + } + /** * Setup the default Auth Controller and EndpointProvider */ diff --git a/src/Endpoint/Abstracts/AbstractSugarBeanCollectionEndpoint.php b/src/Endpoint/Abstracts/AbstractSugarBeanCollectionEndpoint.php index 54902b7..4fdf5b9 100644 --- a/src/Endpoint/Abstracts/AbstractSugarBeanCollectionEndpoint.php +++ b/src/Endpoint/Abstracts/AbstractSugarBeanCollectionEndpoint.php @@ -38,16 +38,17 @@ abstract class AbstractSugarBeanCollectionEndpoint extends AbstractSugarCollecti protected string $_modelInterface = SugarBean::class; - public function getCollectionResponseProp(): string + public function setUrlArgs(array $args): static { - $prop = parent::getCollectionResponseProp(); - return empty($prop) ? self::SUGAR_COLLECTION_RESP_PROP : $prop; + parent::setUrlArgs($args); + $this->syncModuleAndUrlArgs(); + return $this; } - public function setUrlArgs(array $args): static + public function getCollectionResponseProp(): string { - $args = $this->configureModuleUrlArg($args); - return parent::setUrlArgs($args); + $prop = parent::getCollectionResponseProp(); + return empty($prop) ? self::SUGAR_COLLECTION_RESP_PROP : $prop; } /** @@ -102,7 +103,7 @@ protected function configurePayload(): mixed */ protected function configureURL(array $urlArgs): string { - $urlArgs['module'] = $this->getModule(); + $urlArgs = $this->addModuleToUrlArgs($urlArgs); return parent::configureURL($urlArgs); } diff --git a/src/Endpoint/Abstracts/AbstractSugarBeanEndpoint.php b/src/Endpoint/Abstracts/AbstractSugarBeanEndpoint.php index 2b77532..3507d71 100644 --- a/src/Endpoint/Abstracts/AbstractSugarBeanEndpoint.php +++ b/src/Endpoint/Abstracts/AbstractSugarBeanEndpoint.php @@ -19,9 +19,11 @@ use MRussell\REST\Traits\PsrLoggerTrait; use Sugarcrm\REST\Endpoint\Data\FilterData; use Sugarcrm\REST\Endpoint\AuditLog; +use Sugarcrm\REST\Endpoint\Integrate; use Sugarcrm\REST\Endpoint\SugarEndpointInterface; use Sugarcrm\REST\Endpoint\Traits\CompileRequestTrait; use Sugarcrm\REST\Endpoint\Traits\FieldsDataTrait; +use Sugarcrm\REST\Endpoint\Traits\IntegrateSyncKeyTrait; use Sugarcrm\REST\Endpoint\Traits\ModuleAwareTrait; /** @@ -39,14 +41,18 @@ * @method $this audit() * @method $this file() * @method $this duplicateCheck() + * @method $this upsert() */ abstract class AbstractSugarBeanEndpoint extends ModelEndpoint implements SugarEndpointInterface { - use CompileRequestTrait; + use CompileRequestTrait { + compileRequest as private doCompileRequest; + } use PsrLoggerTrait; use ModuleAwareTrait; use FieldsDataTrait; use FileUploadsTrait; + use IntegrateSyncKeyTrait; public const MODEL_ACTION_VAR = 'action'; @@ -80,11 +86,13 @@ abstract class AbstractSugarBeanEndpoint extends ModelEndpoint implements SugarE public const BEAN_ACTION_DUPLICATE_CHECK = 'duplicateCheck'; - public const BEAN_ACTION_ARG1_VAR = 'actionArg1'; + public const BEAN_ACTION_UPSERT = 'upsert'; + + public const BEAN_ACTION_ARG1_VAR = 'actArg1'; - public const BEAN_ACTION_ARG2_VAR = 'actionArg2'; + public const BEAN_ACTION_ARG2_VAR = 'actArg2'; - public const BEAN_ACTION_ARG3_VAR = 'actionArg3'; + public const BEAN_ACTION_ARG3_VAR = 'actArg3'; public const BEAN_MODULE_URL_ARG = 'module'; @@ -92,7 +100,7 @@ abstract class AbstractSugarBeanEndpoint extends ModelEndpoint implements SugarE * @inheritdoc */ protected static array $_DEFAULT_PROPERTIES = [ - self::PROPERTY_URL => '$module/$id/$:action/$:actionArg1/$:actionArg2/$:actionArg3', + self::PROPERTY_URL => '$module/$:id/$:action/$:actArg1/$:actArg2/$:actArg3', self::PROPERTY_HTTP_METHOD => 'GET', self::PROPERTY_AUTH => true, ]; @@ -116,6 +124,7 @@ abstract class AbstractSugarBeanEndpoint extends ModelEndpoint implements SugarE self::BEAN_ACTION_ATTACH_FILE => "POST", self::BEAN_ACTION_TEMP_FILE_UPLOAD => "POST", self::BEAN_ACTION_DUPLICATE_CHECK => "POST", + self::BEAN_ACTION_UPSERT => "PATCH", ]; /** @@ -139,21 +148,16 @@ public function __construct(array $urlArgs = [], array $properties = []) } /** - * Passed in options get changed such that 1st Option (key 0) becomes Module - * 2nd Option (Key 1) becomes ID * @inheritdoc */ public function setUrlArgs(array $args): static { - $args = $this->configureModuleUrlArg($args); - if (isset($args[1])) { - $this->set($this->getKeyProperty(), $args[1]); - unset($args[1]); - } - - return parent::setUrlArgs($args); + parent::setUrlArgs($args); + $this->syncModuleAndUrlArgs(); + return $this; } + /** * Configure Uploads on Request * @inheritdoc @@ -179,6 +183,16 @@ protected function configurePayload(): mixed { $data = $this->getData(); switch ($this->getCurrentAction()) { + case self::BEAN_ACTION_UPSERT: + $data->reset(); + $data->set($this->toArray()); + $syncKeyField = $this->getSyncKeyField(); + if (!empty($syncKeyField)) { + $data[Integrate::DATA_SYNC_KEY_FIELD] = $syncKeyField; + } + + $data[Integrate::DATA_SYNC_KEY_VALUE] = $this->getSyncKey(); + return $data; case self::MODEL_ACTION_CREATE: case self::MODEL_ACTION_UPDATE: $data->reset(); @@ -220,6 +234,10 @@ protected function parseResponse(Response $response): void $this->clear(); $this->syncFromApi($this->parseResponseBodyToArray($body, $this->getModelResponseProp())); return; + case self::BEAN_ACTION_UPSERT: + $body = $this->getResponseContent($response); + $this->syncFromApi($this->parseResponseBodyToArray($body, Integrate::INTEGRATE_RESPONSE_PROP)); + return; } } @@ -266,13 +284,21 @@ protected function resetUploads(): void protected function configureURL(array $urlArgs): string { $action = null; - $urlArgs = $this->configureModuleUrlArg($urlArgs); + $urlArgs = $this->addModuleToUrlArgs($urlArgs); switch ($this->getCurrentAction()) { case self::BEAN_ACTION_CREATE_RELATED: case self::BEAN_ACTION_MASS_RELATE: case self::BEAN_ACTION_UNLINK: case self::BEAN_ACTION_FILTER_RELATED: $action = self::BEAN_ACTION_RELATE; + break; + case self::BEAN_ACTION_UPSERT: + $urlArgs[self::MODEL_ID_VAR] = Integrate::SYNC_KEY; + $syncKey = $this->getSyncKey(); + if (!empty($syncKey)) { + $action = $syncKey; + } + break; case self::BEAN_ACTION_TEMP_FILE_UPLOAD: $urlArgs[self::MODEL_ID_VAR] = 'temp'; @@ -310,39 +336,43 @@ protected function configureURL(array $urlArgs): string protected function configureAction(string $action, array $arguments = []): void { $urlArgs = $this->getUrlArgs(); - if (isset($urlArgs[self::BEAN_ACTION_ARG1_VAR])) { - unset($urlArgs[self::BEAN_ACTION_ARG1_VAR]); - } - - if (isset($urlArgs[self::BEAN_ACTION_ARG2_VAR])) { - unset($urlArgs[self::BEAN_ACTION_ARG2_VAR]); - } - - if (isset($urlArgs[self::BEAN_ACTION_ARG3_VAR])) { - unset($urlArgs[self::BEAN_ACTION_ARG3_VAR]); + if (isset($urlArgs[self::MODEL_ACTION_VAR]) && $urlArgs[self::MODEL_ACTION_VAR] != $action) { + unset($urlArgs[self::MODEL_ACTION_VAR]); } - if (!empty($arguments)) { - switch ($action) { - case self::BEAN_ACTION_TEMP_FILE_UPLOAD: - case self::BEAN_ACTION_ATTACH_FILE: - $this->_upload = true; - // no break - case self::BEAN_ACTION_RELATE: - case self::BEAN_ACTION_DOWNLOAD_FILE: - case self::BEAN_ACTION_UNLINK: - case self::BEAN_ACTION_CREATE_RELATED: - case self::BEAN_ACTION_FILTER_RELATED: - if (isset($arguments[0])) { - $urlArgs[self::BEAN_ACTION_ARG1_VAR] = $arguments[0]; - if (isset($arguments[1])) { - $urlArgs[self::BEAN_ACTION_ARG2_VAR] = $arguments[1]; - if (isset($arguments[2])) { - $urlArgs[self::BEAN_ACTION_ARG3_VAR] = $arguments[2]; - } + switch ($action) { + case self::BEAN_ACTION_TEMP_FILE_UPLOAD: + case self::BEAN_ACTION_ATTACH_FILE: + $this->_upload = true; + // no break + case self::BEAN_ACTION_RELATE: + case self::BEAN_ACTION_DOWNLOAD_FILE: + case self::BEAN_ACTION_UNLINK: + case self::BEAN_ACTION_CREATE_RELATED: + case self::BEAN_ACTION_FILTER_RELATED: + if (isset($arguments[0])) { + $urlArgs[self::BEAN_ACTION_ARG1_VAR] = $arguments[0]; + if (isset($arguments[1])) { + $urlArgs[self::BEAN_ACTION_ARG2_VAR] = $arguments[1]; + if (isset($arguments[2])) { + $urlArgs[self::BEAN_ACTION_ARG3_VAR] = $arguments[2]; } } - } + } + + break; + default: + //If action is not defined above, remove action args + $actionArgs = [ + self::BEAN_ACTION_ARG1_VAR, + self::BEAN_ACTION_ARG2_VAR, + self::BEAN_ACTION_ARG3_VAR, + ]; + foreach ($actionArgs as $actionArg) { + if (isset($urlArgs[$actionArg])) { + unset($urlArgs[$actionArg]); + } + } } $this->setUrlArgs($urlArgs); @@ -542,13 +572,11 @@ protected function configureFileUploadQueryParams(): array 'delete_if_fails' => $this->_deleteFileOnFail, ]; - if ($this->_deleteFileOnFail) { - if (!empty($this->_client)) { - $data['platform'] = $this->getClient()->getPlatform(); - $token = $this->getClient()->getAuth()->getTokenProp('access_token'); - if ($token) { - $data['oauth_token'] = $this->getClient()->getAuth()->getTokenProp('access_token'); - } + if ($this->_deleteFileOnFail && !empty($this->_client)) { + $data['platform'] = $this->getClient()->getPlatform(); + $token = $this->getClient()->getAuth()->getTokenProp('access_token'); + if ($token) { + $data['oauth_token'] = $this->getClient()->getAuth()->getTokenProp('access_token'); } } diff --git a/src/Endpoint/Integrate.php b/src/Endpoint/Integrate.php new file mode 100644 index 0000000..83cff9d --- /dev/null +++ b/src/Endpoint/Integrate.php @@ -0,0 +1,192 @@ + 'integrate/$module/$:recordId/$:syncKeyField/$:syncKey', + self::PROPERTY_RESPONSE_PROP => self::INTEGRATE_RESPONSE_PROP, + ]; + + /** + * All the extra actions that can be done on a Sugar Bean + */ + protected static array $_DEFAULT_SUGAR_BEAN_ACTIONS = [ + self::INTEGRATE_ACTION_RETRIEVE => 'GET', + self::INTEGRATE_ACTION_DELETE => 'DELETE', + self::INTEGRATE_ACTION_SET_SK => 'PUT', + self::BEAN_ACTION_UPSERT => 'PATCH', + ]; + + protected AbstractSugarBeanEndpoint $_sugarBean; + + public function fromBean(AbstractSugarBeanEndpoint $sugarBean): static + { + $this->clear(); + $this->setModule($sugarBean->getModule()); + $this->set($sugarBean->toArray()); + $this->_sugarBean = $sugarBean; + return $this; + } + + protected function configureSyncKey(string $syncKey = null, string $syncKeyField = null, bool $clearOnChange = false): void + { + if ($syncKeyField) { + $this->setSyncKeyField($syncKeyField); + } + + $syncKeyField = $this->getSyncKeyField(); + if ($syncKey) { + $currentKey = $this->getSyncKey(); + if ($currentKey !== $syncKey && $clearOnChange) { + $this->clear(); + } + + $this->set($syncKeyField ?: self::SYNC_KEY, $syncKey); + } + } + + public function getBySyncKey(string $syncKey = null, string $syncKeyField = null): static + { + $this->configureSyncKey($syncKey, $syncKeyField, true); + $this->setCurrentAction(self::INTEGRATE_ACTION_RETRIEVE); + return $this->execute(); + } + + public function setSyncKey(string $syncKey = null, string $syncKeyField = null): static + { + $this->configureSyncKey($syncKey, $syncKeyField); + $this->setCurrentAction(self::INTEGRATE_ACTION_SET_SK); + return $this->execute(); + } + + public function deleteBySyncKey(string $syncKey = null, string $syncKeyField = null): static + { + $this->configureSyncKey($syncKey, $syncKeyField); + $this->setCurrentAction(self::INTEGRATE_ACTION_DELETE); + return $this->execute(); + } + + protected function configureAction(string $action, array $arguments = []): void + { + switch ($action) { + case self::MODEL_ACTION_UPDATE: + case self::MODEL_ACTION_CREATE: + $action = self::BEAN_ACTION_UPSERT; + break; + case self::MODEL_ACTION_DELETE: + $action = self::INTEGRATE_ACTION_DELETE; + break; + case self::MODEL_ACTION_RETRIEVE: + $action = self::INTEGRATE_ACTION_RETRIEVE; + break; + } + if ($this->_action !== $action) { + $this->_action = $action; + } + + parent::configureAction($action, $arguments); + } + + protected function configureURL(array $urlArgs): string + { + $syncKeyField = $this->getSyncKeyField(); + $syncKey = $this->getSyncKey(); + switch ($this->getCurrentAction()) { + case self::MODEL_ACTION_UPDATE: + case self::MODEL_ACTION_CREATE: + case self::BEAN_ACTION_UPSERT: + case self::MODEL_ACTION_DELETE: + case self::INTEGRATE_ACTION_DELETE: + case self::MODEL_ACTION_RETRIEVE: + case self::INTEGRATE_ACTION_RETRIEVE: + unset($urlArgs[self::MODEL_ID_VAR]); + if (!empty($syncKeyField)) { + $urlArgs[self::URL_VAR_RECORD_ID] = $syncKeyField; + $urlArgs[self::PROPERTY_SYNC_KEY_FIELD] = $syncKey; + unset($urlArgs[self::URL_VAR_SYNC_KEY_VALUE]); + } + + break; + case self::INTEGRATE_ACTION_SET_SK: + $urlArgs[self::URL_VAR_RECORD_ID] = $this->getId(); + if (!empty($syncKeyField)) { + $urlArgs[self::PROPERTY_SYNC_KEY_FIELD] = $syncKeyField; + $urlArgs[self::URL_VAR_SYNC_KEY_VALUE] = $syncKey; + } + + break; + } + + return parent::configureURL($urlArgs); + } + + protected function configurePayload(): mixed + { + $data = $this->getData(); + $field = $this->getSyncKeyField(); + if (!empty($field)) { + $data[self::DATA_SYNC_KEY_FIELD] = $field; + } + + $data[self::DATA_SYNC_KEY_VALUE] = $this->getSyncKey(); + return parent::configurePayload(); + } + + protected function parseResponse(Response $response): void + { + if ($response->getStatusCode() == 200) { + switch ($this->getCurrentAction()) { + case self::INTEGRATE_ACTION_DELETE: + case self::MODEL_ACTION_DELETE: + $this->clear(); + if (isset($this->_sugarBean)) { + $this->_sugarBean->clear(); + } + + break; + default: + if (isset($this->_sugarBean)) { + $syncKeyField = $this->getSyncKeyField(); + $syncKey = $this->getSyncKey(); + $this->_sugarBean->set($syncKeyField ?: self::SYNC_KEY, $syncKey); + if ($syncKeyField !== '' && $syncKeyField !== '0') { + $this->_sugarBean->setSyncKeyField($syncKeyField); + } + } + + if ($this->getCurrentAction() !== self::INTEGRATE_ACTION_SET_SK) { + $body = $this->getResponseContent($response); + $this->syncFromApi($this->parseResponseBodyToArray($body, $this->getModelResponseProp())); + if (isset($this->_sugarBean)) { + $this->_sugarBean->set($this->toArray()); + } + } + } + } + } +} diff --git a/src/Endpoint/MLPackage.php b/src/Endpoint/MLPackage.php index f384004..cf4a678 100644 --- a/src/Endpoint/MLPackage.php +++ b/src/Endpoint/MLPackage.php @@ -3,7 +3,6 @@ namespace Sugarcrm\REST\Endpoint; use GuzzleHttp\Psr7\Response; -use MRussell\REST\Endpoint\ModelEndpoint; class MLPackage extends SugarBean { @@ -37,15 +36,6 @@ class MLPackage extends SugarBean protected array $_installOutput = []; - public function setUrlArgs(array $args): static - { - if (isset($args[0])) { - $this->set($this->getKeyProperty(), $args[0]); - unset($args[0]); - } - return ModelEndpoint::setUrlArgs($args); - } - public function install(array $options = [], bool $async = false): static { $this->_installing = true; @@ -85,6 +75,7 @@ protected function configurePayload(): mixed $this->setFile(self::MLP_FIELD_PROP, $data[self::MLP_FIELD_PROP]); $this->_upload = true; } + return parent::configurePayload(); } @@ -102,8 +93,10 @@ protected function parseResponse(Response $response): void if (!empty($data['message'])) { $this->_installOutput = $data['message'] ?? []; } + break; } + if (($data['status'] ?? "") == 'installed') { $this->_installing = false; } diff --git a/src/Endpoint/Me.php b/src/Endpoint/Me.php index 682a9f4..b9d37b3 100644 --- a/src/Endpoint/Me.php +++ b/src/Endpoint/Me.php @@ -44,7 +44,7 @@ class Me extends ModelEndpoint implements SugarEndpointInterface public const USER_ACTION_FOLLOWING = 'following'; protected static array $_DEFAULT_PROPERTIES = [ - self::PROPERTY_URL => 'me/$:action/$:actionArg1', + self::PROPERTY_URL => 'me/$:action/$:actArg1', self::PROPERTY_AUTH => true, self::PROPERTY_HTTP_METHOD => "GET", ]; @@ -115,7 +115,7 @@ protected function configureAction(string $action, array $arguments = []): void case self::USER_ACTION_DELETE_PREFERENCE: case self::USER_ACTION_CREATE_PREFERENCE: if (isset($arguments[0])) { - $this->_urlArgs['actionArg1'] = $arguments[0]; + $this->_urlArgs['actArg1'] = $arguments[0]; } } } diff --git a/src/Endpoint/ModuleLoader.php b/src/Endpoint/ModuleLoader.php index 98d34f6..c1d0045 100644 --- a/src/Endpoint/ModuleLoader.php +++ b/src/Endpoint/ModuleLoader.php @@ -29,10 +29,12 @@ public function setUrlArgs(array $args): static $this->_filter = $args[0]; unset($args[0]); } + if (!empty($args[self::URL_ARG_FILTER])) { $this->_filter = $args[self::URL_ARG_FILTER]; unset($args[self::URL_ARG_FILTER]); } + return parent::setUrlArgs($args); } @@ -41,6 +43,7 @@ protected function configureURL(array $urlArgs): string if (!empty($this->_filter)) { $urlArgs[self::URL_ARG_FILTER] = $this->_filter; } + return parent::configureURL($urlArgs); } diff --git a/src/Endpoint/Provider/SugarEndpointProvider.php b/src/Endpoint/Provider/SugarEndpointProvider.php index 97caa08..ddee4be 100644 --- a/src/Endpoint/Provider/SugarEndpointProvider.php +++ b/src/Endpoint/Provider/SugarEndpointProvider.php @@ -11,6 +11,7 @@ use Sugarcrm\REST\Auth\SugarOAuthController; use Sugarcrm\REST\Endpoint\AuditLog; use Sugarcrm\REST\Endpoint\Email; +use Sugarcrm\REST\Endpoint\Integrate; use Sugarcrm\REST\Endpoint\MLPackage; use Sugarcrm\REST\Endpoint\ModuleLoader; use Sugarcrm\REST\Endpoint\SugarBean; @@ -155,5 +156,10 @@ class SugarEndpointProvider extends VersionedEndpointProvider self::ENDPOINT_CLASS => MLPackage::class, self::ENDPOINT_PROPERTIES => [], ], + [ + self::ENDPOINT_NAME => 'integrate', + self::ENDPOINT_CLASS => Integrate::class, + self::ENDPOINT_PROPERTIES => [], + ], ]; } diff --git a/src/Endpoint/Traits/IntegrateSyncKeyTrait.php b/src/Endpoint/Traits/IntegrateSyncKeyTrait.php new file mode 100644 index 0000000..c8645dc --- /dev/null +++ b/src/Endpoint/Traits/IntegrateSyncKeyTrait.php @@ -0,0 +1,49 @@ +_syncKeyField = $field; + if (method_exists($this, 'setProperty')) { + $this->setProperty(Integrate::PROPERTY_SYNC_KEY_FIELD, $field); + } + + return $this; + } + + public function getSyncKeyField(): string + { + $field = $this->_syncKeyField ?? ""; + if (method_exists($this, 'getProperty')) { + $field = $this->getProperty(Integrate::PROPERTY_SYNC_KEY_FIELD) ?? ""; + } + + return $this->_syncKeyField ?? $field; + } + + public function getSyncKey(): string|int|null + { + $key = null; + $field = $this->getSyncKeyField(); + if (empty($field)) { + $field = Integrate::SYNC_KEY; + } + + if (method_exists($this, 'get')) { + $key = $this->get($field); + } elseif (property_exists($this, '_attributes')) { + $key = $this->_attributes[$field]; + } + + return $key; + } + + +} diff --git a/src/Endpoint/Traits/ModuleAwareTrait.php b/src/Endpoint/Traits/ModuleAwareTrait.php index 3718da1..423b42b 100644 --- a/src/Endpoint/Traits/ModuleAwareTrait.php +++ b/src/Endpoint/Traits/ModuleAwareTrait.php @@ -14,7 +14,12 @@ trait ModuleAwareTrait public function getModule(): string { - return $this->_beanName; + $module = $this->_beanName; + if (method_exists($this, 'getProperty')) { + $module = $this->getProperty(AbstractSugarBeanEndpoint::BEAN_MODULE_URL_ARG); + } + + return empty($this->_beanName) ? ($module ?? "") : $this->_beanName; } /** @@ -24,27 +29,36 @@ public function getModule(): string public function setModule(string $module): static { $this->_beanName = $module; + if (method_exists($this, 'setProperty')) { + $this->setProperty(AbstractSugarBeanEndpoint::BEAN_MODULE_URL_ARG, $module); + } + return $this; } - /** - * Alter the URL Args array to set the Module Var - */ - protected function configureModuleUrlArg(array $urlArgs): array - { - if (isset($urlArgs[0])) { - $urlArgs[AbstractSugarBeanEndpoint::BEAN_MODULE_URL_ARG] = $urlArgs[0]; - unset($urlArgs[0]); - } + protected function setModuleFromUrlArgs(array $urlArgs): void + { if (isset($urlArgs[AbstractSugarBeanEndpoint::BEAN_MODULE_URL_ARG]) && $this->getModule() != $urlArgs[AbstractSugarBeanEndpoint::BEAN_MODULE_URL_ARG]) { $this->setModule($urlArgs[AbstractSugarBeanEndpoint::BEAN_MODULE_URL_ARG]); } + } - if (!isset($urlArgs[AbstractSugarBeanEndpoint::BEAN_MODULE_URL_ARG]) && !empty($this->getModule())) { + protected function addModuleToUrlArgs(array $urlArgs): array + { + $module = $this->getModule(); + if (!isset($urlArgs[AbstractSugarBeanEndpoint::BEAN_MODULE_URL_ARG]) && !empty($module)) { $urlArgs[AbstractSugarBeanEndpoint::BEAN_MODULE_URL_ARG] = $this->getModule(); } return $urlArgs; } + + protected function syncModuleAndUrlArgs(): void + { + if (property_exists($this, '_urlArgs') && !empty($this->_urlArgs[AbstractSugarBeanEndpoint::BEAN_MODULE_URL_ARG])) { + $this->setModuleFromUrlArgs($this->_urlArgs); + unset($this->_urlArgs[AbstractSugarBeanEndpoint::BEAN_MODULE_URL_ARG]); + } + } } diff --git a/tests/Endpoint/AbstractSugarBeanCollectionEndpointTest.php b/tests/Endpoint/AbstractSugarBeanCollectionEndpointTest.php index 6e3b131..9185ca9 100644 --- a/tests/Endpoint/AbstractSugarBeanCollectionEndpointTest.php +++ b/tests/Endpoint/AbstractSugarBeanCollectionEndpointTest.php @@ -38,37 +38,35 @@ protected function tearDown(): void parent::tearDown(); } + /** + * @covers ::getModule + * @covers ::setModule + */ + public function testSetModule(): void + { + $Endpoint = new SugarBeanCollectionEndpoint(); + $this->assertEquals($Endpoint, $Endpoint->setModule('Accounts')); + $this->assertEquals('Accounts', $Endpoint->getModule()); + } + /** * @covers ::setUrlArgs */ public function testSetUrlArgs(): void { $Endpoint = new SugarBeanCollectionEndpoint(); - $this->assertEquals($Endpoint, $Endpoint->setUrlArgs([ - 'Accounts', - ])); - $this->assertEquals([ - 'module' => 'Accounts', - ], $Endpoint->getUrlArgs()); - $this->assertEquals($Endpoint, $Endpoint->setUrlArgs([ - 'Accounts', - 'foo', - ])); - $this->assertEquals([ - 'module' => 'Accounts', - 1 => 'foo', - ], $Endpoint->getUrlArgs()); + $this->assertEquals($Endpoint, $Endpoint->setUrlArgs(['Accounts'])); + $this->assertEmpty($Endpoint->getUrlArgs()); + $this->assertEquals('Accounts', $Endpoint->getModule()); } /** - * @covers ::getModule - * @covers ::setModule + * @covers ::getCollectionResponseProp */ - public function testSetModule(): void + public function testGetCollectionResponseProp(): void { $Endpoint = new SugarBeanCollectionEndpoint(); - $this->assertEquals($Endpoint, $Endpoint->setModule('Accounts')); - $this->assertEquals('Accounts', $Endpoint->getModule()); + $this->assertEquals('records', $Endpoint->getCollectionResponseProp()); } /** @@ -137,6 +135,7 @@ public function testConfigurePayload(): void /** * @covers ::configureURL + * @covers ::addModuleToUrlArgs */ public function testConfigureURL(): void { diff --git a/tests/Endpoint/AbstractSugarBeanEndpointTest.php b/tests/Endpoint/AbstractSugarBeanEndpointTest.php index 093beb2..5edc572 100644 --- a/tests/Endpoint/AbstractSugarBeanEndpointTest.php +++ b/tests/Endpoint/AbstractSugarBeanEndpointTest.php @@ -53,10 +53,27 @@ public function testCompileRequest(): void $this->assertEmpty($Request->getBody()->getContents()); } + /** + * @covers ::execute + * @covers ::setDefaultAction + */ + public function testDefaultAction(): void + { + $Bean = new SugarBean(); + $Bean->setBaseUrl('http://localhost/rest/v11/'); + $Bean->setUrlArgs(['Foo', 'bar']); + + $Request = $Bean->compileRequest(); + $this->assertEquals("GET", $Request->getMethod()); + $this->assertEquals('http://localhost/rest/v11/Foo/bar', $Request->getUri()->__toString()); + $this->assertEmpty($Request->getBody()->getContents()); + } + /** * @covers ::setUrlArgs * @covers ::getModule - * @covers ::configureModuleUrlArg + * @covers ::syncModuleAndUrlArgs + * @covers ::setModuleFromUrlArgs */ public function testSetUrlArgs(): void { @@ -65,35 +82,30 @@ public function testSetUrlArgs(): void $this->assertEquals($Bean, $Bean->setUrlArgs([ 'Test', ])); - $this->assertEquals([ - 'module' => 'Test', - ], $Bean->getUrlArgs()); + $this->assertEquals([], $Bean->getUrlArgs()); $this->assertEquals('Test', $Bean->getModule()); $this->assertEquals($Bean, $Bean->setUrlArgs([ 'Test', '123-abc', ])); - $this->assertEquals([ - 'module' => 'Test', - ], $Bean->getUrlArgs()); + $this->assertEquals([], $Bean->getUrlArgs()); $this->assertEquals('Test', $Bean->getModule()); - $this->assertEquals('123-abc', $Bean->get('id')); + $this->assertEquals('123-abc', $Bean->getId()); $this->assertEquals($Bean, $Bean->setUrlArgs([ 'Test', '123-abc', 'foo', ])); $this->assertEquals([ - 2 => 'foo', - 'module' => 'Test', + 'action' => 'foo', ], $Bean->getUrlArgs()); $this->assertEquals('Test', $Bean->getModule()); - $this->assertEquals('123-abc', $Bean->get('id')); + $this->assertEquals('123-abc', $Bean->getId()); } /** * @covers ::setModule - * @covers ::configureModuleUrlArg + * @covers ::getModule */ public function testSetModule(): void { @@ -101,21 +113,10 @@ public function testSetModule(): void $this->assertEquals($Bean, $Bean->setModule('Test')); $this->assertEquals('Test', $Bean->getModule()); - $Reflection = new \ReflectionClass($Bean); - $configureModuleArg = $Reflection->getMethod('configureModuleUrlArg'); - $configureModuleArg->setAccessible(true); - - $args = [ - 0 => 'foobar', - ]; - $args = $configureModuleArg->invoke($Bean, $args); - $this->assertFalse(isset($args[0])); - $this->assertEquals('foobar', $Bean->getModule()); - $this->assertEquals('foobar', $args['module']); - $args = $configureModuleArg->invoke($Bean, []); - $this->assertFalse(isset($args[0])); - $this->assertEquals('foobar', $Bean->getModule()); - $this->assertEquals('foobar', $args['module']); + $Bean->setModule(''); + $this->assertEquals('', $Bean->getModule()); + $Bean->setProperty(SugarBean::BEAN_MODULE_URL_ARG, 'Test'); + $this->assertEquals('Test', $Bean->getModule()); } /** @@ -323,6 +324,8 @@ public function testFilterRelated(): void /** * @covers ::configureURL + * @covers ::configureAction + * @covers ::addModuleToUrlArgs */ public function testConfigureURL(): void { @@ -356,30 +359,44 @@ public function testConfigureURL(): void $Bean->setCurrentAction(SugarBean::BEAN_ACTION_AUDIT); $this->assertEquals('Foo/bar/audit', $configureUrl->invoke($Bean, $Bean->getUrlArgs())); - //More Options Needed - $options[] = 'baz'; + //Verify that action is unset for CRUD operations, and not in URL + $options['action'] = 'test'; + $Bean->setUrlArgs($options); + $urlArgs = $Bean->getUrlArgs(); + $Bean->setCurrentAction(SugarBean::MODEL_ACTION_RETRIEVE); + $this->assertEquals('Foo/bar', $configureUrl->invoke($Bean, $urlArgs)); + $Bean->setCurrentAction(SugarBean::MODEL_ACTION_UPDATE); + $this->assertEquals('Foo/bar', $configureUrl->invoke($Bean, $urlArgs)); + $Bean->setCurrentAction(SugarBean::MODEL_ACTION_DELETE); + $this->assertEquals('Foo/bar', $configureUrl->invoke($Bean, $urlArgs)); + $Bean->setCurrentAction(SugarBean::MODEL_ACTION_CREATE); + $this->assertEquals('Foo', $configureUrl->invoke($Bean, $urlArgs)); + unset($options['action']); + + //Actions with arguments + $options['actArg1'] = 'baz'; $Bean->setUrlArgs($options); $Bean->setCurrentAction(SugarBean::BEAN_ACTION_CREATE_RELATED); $this->assertEquals('Foo/bar/link/baz', $configureUrl->invoke($Bean, $Bean->getUrlArgs())); - $options[] = 'foz'; + $options['actArg2'] = 'foz'; $Bean->setUrlArgs($options); $Bean->setCurrentAction(SugarBean::BEAN_ACTION_UNLINK); $this->assertEquals('Foo/bar/link/baz/foz', $configureUrl->invoke($Bean, $Bean->getUrlArgs())); $Bean->setCurrentAction(SugarBean::BEAN_ACTION_RELATE); $this->assertEquals('Foo/bar/link/baz/foz', $configureUrl->invoke($Bean, $Bean->getUrlArgs())); - $options = ['Foo', 'bar', 'uploadFile']; + $options = ['Foo', 'bar', 'actArg1' => 'uploadFile']; $Bean->setUrlArgs($options); $Bean->setCurrentAction(SugarBean::BEAN_ACTION_ATTACH_FILE); $this->assertEquals('Foo/bar/file/uploadFile', $configureUrl->invoke($Bean, $Bean->getUrlArgs())); $Bean->setCurrentAction(SugarBean::BEAN_ACTION_DOWNLOAD_FILE); $this->assertEquals('Foo/bar/file/uploadFile', $configureUrl->invoke($Bean, $Bean->getUrlArgs())); - $options = [ - 'Foo', 'bar', - 'action' => 'test', - ]; - $Bean->setCurrentAction(SugarBean::MODEL_ACTION_RETRIEVE); - unset($Bean[$Bean->getKeyProperty()]); - $this->assertEquals('Foo/bar', $configureUrl->invoke($Bean, $options)); + + //Integrate API + $Bean->setCurrentAction(SugarBean::BEAN_ACTION_UPSERT); + $this->assertEquals('Foo/sync_key', $configureUrl->invoke($Bean, $Bean->getUrlArgs())); + $Bean->setSyncKeyField('sync_key'); + $Bean['sync_key'] = 'foo'; + $this->assertEquals('Foo/sync_key/foo', $configureUrl->invoke($Bean, $Bean->getUrlArgs())); } /** @@ -398,52 +415,45 @@ public function testConfigureAction(): void $configureAction->invoke($Bean, SugarBean::BEAN_ACTION_RELATE, ['foo', 'bar']); $this->assertEquals('Test', $Bean->getModule()); $this->assertEquals([ - 'module' => 'Test', - 'actionArg1' => 'foo', - 'actionArg2' => 'bar', + 'actArg1' => 'foo', + 'actArg2' => 'bar', ], $Bean->getUrlArgs()); $Bean->setUrlArgs(['Test', '1234']); $configureAction->invoke($Bean, SugarBean::BEAN_ACTION_ATTACH_FILE, ['fileField']); $this->assertEquals('Test', $Bean->getModule()); $this->assertEquals([ - 'module' => 'Test', - 'actionArg1' => 'fileField', + 'actArg1' => 'fileField', ], $Bean->getUrlArgs()); $Bean->setUrlArgs(['Test', '1234']); $configureAction->invoke($Bean, SugarBean::BEAN_ACTION_DOWNLOAD_FILE, ['fileField']); $this->assertEquals('Test', $Bean->getModule()); $this->assertEquals([ - 'module' => 'Test', - 'actionArg1' => 'fileField', + 'actArg1' => 'fileField', ], $Bean->getUrlArgs()); $Bean->setUrlArgs(['Test', '1234']); $configureAction->invoke($Bean, SugarBean::BEAN_ACTION_UNLINK, ['foo', 'bar']); $this->assertEquals('Test', $Bean->getModule()); $this->assertEquals([ - 'module' => 'Test', - 'actionArg1' => 'foo', - 'actionArg2' => 'bar', + 'actArg1' => 'foo', + 'actArg2' => 'bar', ], $Bean->getUrlArgs()); $Bean->setUrlArgs(['Test', '1234']); $configureAction->invoke($Bean, SugarBean::BEAN_ACTION_CREATE_RELATED, ['foo', 'bar', 'baz']); $this->assertEquals('Test', $Bean->getModule()); $this->assertEquals([ - 'module' => 'Test', - 'actionArg1' => 'foo', - 'actionArg2' => 'bar', - 'actionArg3' => 'baz', + 'actArg1' => 'foo', + 'actArg2' => 'bar', + 'actArg3' => 'baz', ], $Bean->getUrlArgs()); $Bean->setUrlArgs(['Test', '1234']); $configureAction->invoke($Bean, SugarBean::MODEL_ACTION_CREATE, ['foo', 'bar', 'baz']); $this->assertEquals('Test', $Bean->getModule()); - $this->assertEquals([ - 'module' => 'Test', - ], $Bean->getUrlArgs()); + $this->assertEquals([], $Bean->getUrlArgs()); } /** diff --git a/tests/Endpoint/IntegrateTest.php b/tests/Endpoint/IntegrateTest.php new file mode 100644 index 0000000..5ad22df --- /dev/null +++ b/tests/Endpoint/IntegrateTest.php @@ -0,0 +1,280 @@ + ['id' => '12345', 'name' => 'Test Account']]; + + protected function setUp(): void + { + $this->client = new Client(); + parent::setUp(); + } + + protected function tearDown(): void + { + parent::tearDown(); + } + + public function testActions(): void + { + $endpoint = new Integrate(); + $reflection = new \ReflectionClass($endpoint::class); + $actions = $reflection->getProperty('_actions'); + $actions->setAccessible(true); + $actions = $actions->getValue($endpoint); + $this->assertEquals([ + Integrate::MODEL_ACTION_RETRIEVE => 'GET', + Integrate::MODEL_ACTION_CREATE => 'POST', + Integrate::MODEL_ACTION_UPDATE => 'PUT', + Integrate::MODEL_ACTION_DELETE => 'DELETE', + Integrate::BEAN_ACTION_UPSERT => 'PATCH', + Integrate::INTEGRATE_ACTION_RETRIEVE => 'GET', + Integrate::INTEGRATE_ACTION_DELETE => 'DELETE', + Integrate::INTEGRATE_ACTION_SET_SK => 'PUT', + ], $actions); + } + + /** + * @covers ::configureAction + */ + public function testConfigureAction(): void + { + $endpoint = new Integrate(); + $endpoint->setCurrentAction(Integrate::MODEL_ACTION_RETRIEVE); + $this->assertEquals(Integrate::INTEGRATE_ACTION_RETRIEVE, $endpoint->getCurrentAction()); + $endpoint->setCurrentAction(Integrate::MODEL_ACTION_CREATE); + $this->assertEquals(Integrate::BEAN_ACTION_UPSERT, $endpoint->getCurrentAction()); + $endpoint->setCurrentAction(Integrate::MODEL_ACTION_UPDATE); + $this->assertEquals(Integrate::BEAN_ACTION_UPSERT, $endpoint->getCurrentAction()); + $endpoint->setCurrentAction(Integrate::MODEL_ACTION_DELETE); + $this->assertEquals(Integrate::INTEGRATE_ACTION_DELETE, $endpoint->getCurrentAction()); + } + + /** + * @covers ::getSyncKey + * @covers ::getSyncKeyField + * @covers ::configureSyncKey + * @covers ::setSyncKeyField + * @covers ::setSyncKey + * @covers ::configureURL + * @covers ::configurePayload + * @covers ::parseResponse + */ + public function testSetSyncKey(): void + { + $this->client->mockResponses->append(new Response('200')); + $endpoint = new Integrate(); + $endpoint->setClient($this->client); + $endpoint->setModule('Accounts'); + $endpoint['id'] = '12345'; + $endpoint->setSyncKey('test'); + $request = $this->client->mockResponses->getLastRequest(); + $this->assertEquals('PUT', $request->getMethod()); + $this->assertEquals('/rest/v11/integrate/Accounts/12345', $request->getUri()->getPath()); + $body = json_decode($request->getBody()->getContents(), true); + $this->assertNotEmpty($body); + $this->assertEquals('test', $body[Integrate::DATA_SYNC_KEY_VALUE]); + $this->assertEquals('test', $endpoint->getSyncKey()); + + $this->client->mockResponses->append(new Response('200')); + $endpoint->setSyncKeyField('sync_key'); + $endpoint->setSyncKey('test2'); + + $request = $this->client->mockResponses->getLastRequest(); + $this->assertEquals('PUT', $request->getMethod()); + $this->assertEquals('/rest/v11/integrate/Accounts/12345/sync_key/test2', $request->getUri()->getPath()); + $body = json_decode($request->getBody()->getContents(), true); + $this->assertNotEmpty($body); + $this->assertEquals('test2', $body[Integrate::DATA_SYNC_KEY_VALUE]); + $this->assertEquals('test2', $endpoint->getSyncKey()); + + $this->client->mockResponses->append(new Response('200')); + $endpoint->setSyncKey('test3', 'foobar'); + $request = $this->client->mockResponses->getLastRequest(); + $this->assertEquals('PUT', $request->getMethod()); + $this->assertEquals('/rest/v11/integrate/Accounts/12345/foobar/test3', $request->getUri()->getPath()); + $body = json_decode($request->getBody()->getContents(), true); + $this->assertNotEmpty($body); + $this->assertEquals('test3', $body[Integrate::DATA_SYNC_KEY_VALUE]); + $this->assertEquals('test3', $endpoint->getSyncKey()); + $this->assertEquals('foobar', $endpoint->getSyncKeyField()); + + $this->client->mockResponses->append(new Response('200')); + $account = $this->client->module('Accounts', $endpoint->getId()); + $endpoint->fromBean($account); + $endpoint->setSyncKey('test4'); + + $request = $this->client->mockResponses->getLastRequest(); + $this->assertEquals('PUT', $request->getMethod()); + $this->assertEquals('/rest/v11/integrate/Accounts/12345/foobar/test4', $request->getUri()->getPath()); + $body = json_decode($request->getBody()->getContents(), true); + $this->assertNotEmpty($body); + $this->assertEquals('test4', $body[Integrate::DATA_SYNC_KEY_VALUE]); + $this->assertEquals('test4', $account['foobar']); + } + + /** + * @covers ::getSyncKey + * @covers ::getSyncKeyField + * @covers ::getBySyncKey + * @covers ::configureSyncKey + * @covers ::configureURL + * @covers ::configureAction + * @covers ::fromBean + * @covers ::parseResponse + */ + public function testGetBySyncKey(): void + { + $this->client->mockResponses->append(new Response('200', [], json_encode($this->responsePayload))); + $endpoint = new Integrate(); + $endpoint->setClient($this->client); + $endpoint->setModule('Accounts'); + $endpoint->getBySyncKey('test'); + + $request = $this->client->mockResponses->getLastRequest(); + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals('/rest/v11/integrate/Accounts', $request->getUri()->getPath()); + $body = []; + parse_str($request->getUri()->getQuery(), $body); + $this->assertNotEmpty($body); + $this->assertEquals('test', $body[Integrate::DATA_SYNC_KEY_VALUE]); + $this->assertEquals('test', $endpoint->getSyncKey()); + $this->assertEquals('', $endpoint->getSyncKeyField()); + $this->assertEquals('test', $endpoint['sync_key']); + $this->assertEquals('12345', $endpoint->getId()); + $this->assertEquals('Test Account', $endpoint->get('name')); + + $this->client->mockResponses->append(new Response('200', [], json_encode($this->responsePayload))); + $endpoint->setCurrentAction(Integrate::MODEL_ACTION_RETRIEVE); + $endpoint->execute(); + $request = $this->client->mockResponses->getLastRequest(); + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals('/rest/v11/integrate/Accounts', $request->getUri()->getPath()); + $body = []; + parse_str($request->getUri()->getQuery(), $body); + $this->assertNotEmpty($body); + $this->assertEquals('test', $body[Integrate::DATA_SYNC_KEY_VALUE]); + + $endpoint['test'] = 'foobar'; + $this->client->mockResponses->append(new Response('200', [], json_encode($this->responsePayload))); + $endpoint->setSyncKeyField('sync_key'); + $endpoint->getBySyncKey('test2'); + + $request = $this->client->mockResponses->getLastRequest(); + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals('/rest/v11/integrate/Accounts/sync_key/test2', $request->getUri()->getPath()); + $body = []; + parse_str($request->getUri()->getQuery(), $body); + $this->assertNotEmpty($body); + $this->assertEquals('test2', $body[Integrate::DATA_SYNC_KEY_VALUE]); + $this->assertEquals('test2', $endpoint->getSyncKey()); + $this->assertEquals('sync_key', $endpoint->getSyncKeyField()); + $this->assertEquals('12345', $endpoint->getId()); + $this->assertEquals('Test Account', $endpoint->get('name')); + //Verifying that clear was called, when getBySyncKey was called with new sync key value + $this->assertEmpty($endpoint->get('test')); + + $this->client->mockResponses->append(new Response('200', [], json_encode($this->responsePayload))); + $endpoint->setSyncKeyField('unique_key'); + $account = $this->client->module('Accounts', $endpoint->getId()); + $endpoint->fromBean($account); + $endpoint->getBySyncKey('test3'); + + $request = $this->client->mockResponses->getLastRequest(); + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals('/rest/v11/integrate/Accounts/unique_key/test3', $request->getUri()->getPath()); + $body = []; + parse_str($request->getUri()->getQuery(), $body); + $this->assertNotEmpty($body); + $this->assertEquals('test3', $body[Integrate::DATA_SYNC_KEY_VALUE]); + $this->assertEquals('test3', $endpoint->getSyncKey()); + $this->assertEquals('unique_key', $endpoint->getSyncKeyField()); + $this->assertEquals('12345', $endpoint->getId()); + $this->assertEquals('Test Account', $endpoint->get('name')); + $this->assertEquals('12345', $account->getId()); + $this->assertEquals('Test Account', $account->get('name')); + $this->assertEquals('test3', $account->get('unique_key')); + } + + /** + * @covers ::getSyncKey + * @covers ::getSyncKeyField + * @covers ::deleteBySyncKey + * @covers ::configureSyncKey + * @covers ::configureURL + * @covers ::configureAction + * @covers ::fromBean + * @covers ::parseResponse + */ + public function testDeleteBySyncKey(): void + { + $this->client->mockResponses->append(new Response('200')); + $endpoint = new Integrate(); + $endpoint->setClient($this->client); + $endpoint->setModule('Accounts'); + $endpoint->deleteBySyncKey('test'); + + $request = $this->client->mockResponses->getLastRequest(); + $this->assertEquals('DELETE', $request->getMethod()); + $this->assertEquals('/rest/v11/integrate/Accounts', $request->getUri()->getPath()); + $body = json_decode($request->getBody()->getContents(), true); + $this->assertNotEmpty($body); + $this->assertEquals('test', $body[Integrate::DATA_SYNC_KEY_VALUE]); + $this->assertEmpty($endpoint->toArray()); + + $this->client->mockResponses->append(new Response('200')); + $endpoint->setSyncKeyField('sync_key'); + $endpoint->deleteBySyncKey('test2'); + + $request = $this->client->mockResponses->getLastRequest(); + $this->assertEquals('DELETE', $request->getMethod()); + $this->assertEquals('/rest/v11/integrate/Accounts/sync_key/test2', $request->getUri()->getPath()); + $body = json_decode($request->getBody()->getContents(), true); + $this->assertNotEmpty($body); + $this->assertEquals('sync_key', $body[Integrate::DATA_SYNC_KEY_FIELD]); + $this->assertEquals('test2', $body[Integrate::DATA_SYNC_KEY_VALUE]); + $this->assertEmpty($endpoint->toArray()); + + $this->client->mockResponses->append(new Response('200')); + $endpoint['sync_key'] = 'test3'; + $endpoint->delete(); + $request = $this->client->mockResponses->getLastRequest(); + $this->assertEquals('DELETE', $request->getMethod()); + $this->assertEquals('/rest/v11/integrate/Accounts/sync_key/test3', $request->getUri()->getPath()); + $body = json_decode($request->getBody()->getContents(), true); + $this->assertNotEmpty($body); + $this->assertEquals('sync_key', $body[Integrate::DATA_SYNC_KEY_FIELD]); + $this->assertEquals('test3', $body[Integrate::DATA_SYNC_KEY_VALUE]); + $this->assertEmpty($endpoint->toArray()); + + $this->client->mockResponses->append(new Response('200')); + $endpoint->setSyncKeyField('unique_key'); + $account = $this->client->module('Accounts', '12345'); + $endpoint->fromBean($account); + $endpoint->deleteBySyncKey('test4'); + + $request = $this->client->mockResponses->getLastRequest(); + $this->assertEquals('DELETE', $request->getMethod()); + $this->assertEquals('/rest/v11/integrate/Accounts/unique_key/test4', $request->getUri()->getPath()); + $body = json_decode($request->getBody()->getContents(), true); + $this->assertNotEmpty($body); + $this->assertEquals('unique_key', $body[Integrate::DATA_SYNC_KEY_FIELD]); + $this->assertEquals('test4', $body[Integrate::DATA_SYNC_KEY_VALUE]); + $this->assertEmpty($endpoint->toArray()); + $this->assertEmpty($account->toArray()); + } +} diff --git a/tests/Endpoint/MeTest.php b/tests/Endpoint/MeTest.php index f367929..04630a1 100644 --- a/tests/Endpoint/MeTest.php +++ b/tests/Endpoint/MeTest.php @@ -67,7 +67,7 @@ public function testConfigureUrl(): void $action->setValue($Me, $Me::USER_ACTION_SAVE_PREFERENCES); $this->assertEquals('me/preferences', $configureUrl->invoke($Me, [])); $action->setValue($Me, $Me::USER_ACTION_CREATE_PREFERENCE); - $this->assertEquals('me/preference/pref1', $configureUrl->invoke($Me, ['actionArg1' => 'pref1'])); + $this->assertEquals('me/preference/pref1', $configureUrl->invoke($Me, ['actArg1' => 'pref1'])); $action->setValue($Me, $Me::MODEL_ACTION_DELETE); $this->assertEquals('me', $configureUrl->invoke($Me, ['action' => 'preference'])); } @@ -96,31 +96,31 @@ public function testConfigureAction(): void $options = $Me->getUrlArgs(); $this->assertEquals("POST", $properties['httpMethod']); - $this->assertArrayHasKey('actionArg1', $options); - $this->assertEquals('foo', $options['actionArg1']); + $this->assertArrayHasKey('actArg1', $options); + $this->assertEquals('foo', $options['actArg1']); $configureAction->invoke($Me, $Me::USER_ACTION_GET_PREFERENCE, ['foo']); $properties = $Me->getProperties(); $options = $Me->getUrlArgs(); $this->assertEquals("GET", $properties['httpMethod']); - $this->assertArrayHasKey('actionArg1', $options); - $this->assertEquals('foo', $options['actionArg1']); + $this->assertArrayHasKey('actArg1', $options); + $this->assertEquals('foo', $options['actArg1']); $configureAction->invoke($Me, $Me::USER_ACTION_UPDATE_PREFERENCE, ['foo']); $properties = $Me->getProperties(); $options = $Me->getUrlArgs(); $this->assertEquals("PUT", $properties['httpMethod']); - $this->assertArrayHasKey('actionArg1', $options); - $this->assertEquals('foo', $options['actionArg1']); + $this->assertArrayHasKey('actArg1', $options); + $this->assertEquals('foo', $options['actArg1']); $configureAction->invoke($Me, $Me::USER_ACTION_DELETE_PREFERENCE, ['foo']); $properties = $Me->getProperties(); $options = $Me->getUrlArgs(); $this->assertEquals("DELETE", $properties['httpMethod']); - $this->assertArrayHasKey('actionArg1', $options); - $this->assertEquals('foo', $options['actionArg1']); + $this->assertArrayHasKey('actArg1', $options); + $this->assertEquals('foo', $options['actArg1']); } } diff --git a/tests/Endpoint/MetadataTest.php b/tests/Endpoint/MetadataTest.php index a68535a..d9fdb0b 100644 --- a/tests/Endpoint/MetadataTest.php +++ b/tests/Endpoint/MetadataTest.php @@ -44,12 +44,12 @@ public function testGetMetadataTypes(): void // $Metadata->setAuth(new SugarOAuthController()); $Metadata->setBaseUrl('http://localhost/rest/v11'); $Metadata->getHash(); - $this->assertEquals([$Metadata::METADATA_TYPE_HASH], $Metadata->getUrlArgs()); + $this->assertEquals(['type' => $Metadata::METADATA_TYPE_HASH], $Metadata->getUrlArgs()); $this->assertEquals('http://localhost/rest/v11/metadata/_hash', $this->client->mockResponses->getLastRequest()->getUri()->__toString()); $this->client->mockResponses->append(new Response(200)); $Metadata->getPublic(); - $this->assertEquals([$Metadata::METADATA_TYPE_PUBLIC], $Metadata->getUrlArgs()); + $this->assertEquals(['type' => $Metadata::METADATA_TYPE_PUBLIC], $Metadata->getUrlArgs()); $this->assertEquals('http://localhost/rest/v11/metadata/public', $this->client->mockResponses->getLastRequest()->getUri()->__toString()); } } diff --git a/tests/Endpoint/ModuleLoaderTest.php b/tests/Endpoint/ModuleLoaderTest.php index a5e7cd3..d434b07 100644 --- a/tests/Endpoint/ModuleLoaderTest.php +++ b/tests/Endpoint/ModuleLoaderTest.php @@ -29,10 +29,11 @@ protected function tearDown(): void /** * @covers ::setUrlArgs */ - public function testSetUrlArgs() + public function testSetUrlArgs(): void { $Packages = new ModuleLoader(); $Packages->setUrlArgs(['foo']); + $ReflectionClass = new \ReflectionClass($Packages); $filter = $ReflectionClass->getProperty('_filter'); $filter->setAccessible(true); @@ -51,7 +52,7 @@ public function testSetUrlArgs() * @covers ::configureURL * @covers ::execute */ - public function testStaged() + public function testStaged(): void { $this->client->mockResponses->append(new Response(200, [], json_encode([ 'packages' => [ @@ -88,7 +89,7 @@ public function testStaged() * @covers ::configureURL * @covers ::execute */ - public function testInstalled() + public function testInstalled(): void { $this->client->mockResponses->append(new Response(200, [], json_encode([ 'packages' => [ @@ -115,7 +116,7 @@ public function testInstalled() /** * @covers ::newPackage */ - public function testNewPackage() + public function testNewPackage(): void { $packages = new ModuleLoader(); $mlp = $packages->newPackage(); From d6005f2b0c84d6eaf119edb56cee8cd8fff828e8 Mon Sep 17 00:00:00 2001 From: Mike Russell <3056352+MichaelJ2324@users.noreply.github.com> Date: Wed, 12 Feb 2025 10:20:46 -0500 Subject: [PATCH 2/2] Dump to v3.0.5 of Rest Client Framework for automatic URL Arg handling --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 644fd37..7a794e0 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ ], "require": { "php": ">=8.0", - "michaelj2324/php-rest-client": "3.x-dev" + "michaelj2324/php-rest-client": ">=3.0.5" }, "require-dev": { "phpunit/phpunit": "9.*",