diff --git a/etc/di.xml b/etc/di.xml index 081b324fc..c07bfe127 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -49,24 +49,6 @@ - - - Magento\FunctionalTestingFramework\Page\Config\Data - - - - - Magento\FunctionalTestingFramework\Block\Config\Data - - - - - - Magento\FunctionalTestingFramework\Generate\GeneratePage - Magento\FunctionalTestingFramework\Generate\GenerateBlock - - - - + Magento\FunctionalTestingFramework\DataProfile\Config\Metadata @@ -198,7 +180,7 @@ Magento\FunctionalTestingFramework\Config\SchemaLocator\Metadata name - key + key key key @@ -222,11 +204,9 @@ name name - key - key name - name - mergeKey + persistedKey + mergeKey *Cest.xml Cest @@ -236,20 +216,14 @@ - mergeKey - mergeKey - mergeKey + mergeKey + mergeKey + mergeKey name name - key - key - name - key - key - name - key - key - name + persistedKey + persistedKey + persistedKey name diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Api/ApiExecutor.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Api/ApiExecutor.php deleted file mode 100644 index 2cad2bf16..000000000 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Api/ApiExecutor.php +++ /dev/null @@ -1,368 +0,0 @@ -operation = $operation; - $this->entityObject = $entityObject; - if ($dependentEntities != null) { - foreach ($dependentEntities as $entity) { - $this->dependentEntities[$entity->getName()] = $entity; - } - } - - $this->jsonDefinition = JsonDefinitionObjectHandler::getInstance()->getJsonDefinition( - $this->operation, - $this->entityObject->getType() - ); - } - - /** - * Executes an api request based on parameters given by constructor. - * - * @return string | null - */ - public function executeRequest() - { - $apiClientUrl = $this->jsonDefinition->getApiUrl(); - - $matchedParams = []; - preg_match_all("/[{](.+?)[}]/", $apiClientUrl, $matchedParams); - - if (!empty($matchedParams)) { - foreach ($matchedParams[0] as $paramKey => $paramValue) { - $param = $this->entityObject->getDataByName( - $matchedParams[1][$paramKey], - EntityDataObject::CEST_UNIQUE_VALUE - ); - $apiClientUrl = str_replace($paramValue, $param, $apiClientUrl); - } - } - - $authorization = $this->jsonDefinition->getAuth(); - $headers = $this->jsonDefinition->getHeaders(); - - if ($authorization) { - $headers[] = $this->getAuthorizationHeader($authorization); - } - - $jsonBody = $this->getEncodedJsonString(); - - $apiClientUtil = new ApiClientUtil( - $apiClientUrl, - $headers, - $this->jsonDefinition->getApiMethod(), - empty($jsonBody) ? null : $jsonBody - ); - - return $apiClientUtil->submit(); - } - - /** - * Returns the authorization token needed for some requests via REST call. - * - * @param string $authUrl - * @return string - */ - private function getAuthorizationHeader($authUrl) - { - $headers = ['Content-Type: application/json']; - $authCreds = [ - 'username' => getenv('MAGENTO_ADMIN_USERNAME'), - 'password' => getenv('MAGENTO_ADMIN_PASSWORD') - ]; - - $apiClientUtil = new ApiClientUtil($authUrl, $headers, 'POST', json_encode($authCreds)); - $token = $apiClientUtil->submit(); - $authHeader = 'Authorization: Bearer ' . str_replace('"', "", $token); - - return $authHeader; - } - - /** - * This function returns an array which is structurally equal to the json which is needed by the web api for - * entity creation. The function retrieves an array describing the json metadata and traverses any dependencies - * recursively forming an array which represents the json structure for the api of the desired type. - * - * @param EntityDataObject $entityObject - * @param array $jsonArrayMetadata - * @return array - * @throws \Exception - */ - private function convertJsonArray($entityObject, $jsonArrayMetadata) - { - $jsonArray = []; - self::incrementSequence($entityObject->getName()); - - foreach ($jsonArrayMetadata as $jsonElement) { - if ($jsonElement->getType() == JsonObjectExtractor::JSON_OBJECT_OBJ_NAME) { - $entityObj = $this->resolveJsonObjectAndEntityData($entityObject, $jsonElement->getValue()); - $jsonArray[$jsonElement->getValue()] = - $this->convertJsonArray($entityObj, $jsonElement->getNestedMetadata()); - continue; - } - - $jsonElementType = $jsonElement->getValue(); - - if (in_array($jsonElementType, ApiExecutor::PRIMITIVE_TYPES)) { - $elementData = $entityObject->getDataByName( - $jsonElement->getKey(), - EntityDataObject::CEST_UNIQUE_VALUE - ); - - // If data was defined at all, attempt to put it into JSON body - // If data was not defined, and element is required, throw exception - // If no data is defined, don't input defaults per primitive into JSON for the data - if ($elementData != null) { - if (array_key_exists($jsonElement->getKey(), $entityObject->getUniquenessData())) { - $uniqueData = $entityObject->getUniquenessDataByName($jsonElement->getKey()); - if ($uniqueData === 'suffix') { - $elementData .= (string)self::getSequence($entityObject->getName()); - } else { - $elementData = (string)self::getSequence($entityObject->getName()) . $elementData; - } - } - $jsonArray[$jsonElement->getKey()] = $this->castValue($jsonElementType, $elementData); - - } elseif ($jsonElement->getRequired()) { - throw new \Exception(sprintf( - ApiExecutor::EXCEPTION_REQUIRED_DATA, - $jsonElement->getType(), - $jsonElement->getKey(), - $this->entityObject->getName() - )); - } - } else { - $entityNamesOfType = $entityObject->getLinkedEntitiesOfType($jsonElementType); - - // If an element is required by metadata, but was not provided in the entity, throw an exception - if ($jsonElement->getRequired() && $entityNamesOfType == null) { - throw new \Exception(sprintf( - ApiExecutor::EXCEPTION_REQUIRED_DATA, - $jsonElement->getType(), - $jsonElement->getKey(), - $this->entityObject->getName() - )); - } - foreach ($entityNamesOfType as $entityName) { - $jsonDataSubArray = $this->resolveNonPrimitiveElement($entityName, $jsonElement); - - if ($jsonElement->getType() == 'array') { - $jsonArray[$jsonElement->getKey()][] = $jsonDataSubArray; - } else { - $jsonArray[$jsonElement->getKey()] = $jsonDataSubArray; - } - } - } - } - - return $jsonArray; - } - - /** - * This function does a comparison of the entity object being matched to the json element. If there is a mismatch in - * type we attempt to use a nested entity, if the entities are properly matched, we simply return the object. - * - * @param EntityDataObject $entityObject - * @param string $jsonElementValue - * @return EntityDataObject|null - */ - private function resolveJsonObjectAndEntityData($entityObject, $jsonElementValue) - { - if ($jsonElementValue != $entityObject->getType()) { - // if we have a mismatch attempt to retrieve linked data and return just the first linkage - $linkName = $entityObject->getLinkedEntitiesOfType($jsonElementValue)[0]; - return DataObjectHandler::getInstance()->getObject($linkName); - } - - return $entityObject; - } - - /** - * Resolves JsonObjects and pre-defined metadata (in other operation.xml file) referenced by the json metadata - * - * @param string $entityName - * @param JsonElement $jsonElement - * @return array - */ - private function resolveNonPrimitiveElement($entityName, $jsonElement) - { - $linkedEntityObj = $this->resolveLinkedEntityObject($entityName); - - // in array case - if (!empty($jsonElement->getNestedJsonElement($jsonElement->getValue())) - && $jsonElement->getType() == 'array' - ) { - $jsonSubArray = $this->convertJsonArray( - $linkedEntityObj, - [$jsonElement->getNestedJsonElement($jsonElement->getValue())] - ); - - return $jsonSubArray[$jsonElement->getValue()]; - } - - $jsonMetadata = JsonDefinitionObjectHandler::getInstance()->getJsonDefinition( - $this->operation, - $linkedEntityObj->getType() - )->getJsonMetadata(); - - return $this->convertJsonArray($linkedEntityObj, $jsonMetadata); - } - - /** - * Method to wrap entity resolution, checks locally defined dependent entities first - * - * @param string $entityName - * @return EntityDataObject - */ - private function resolveLinkedEntityObject($entityName) - { - // check our dependent entity list to see if we have this defined - if (array_key_exists($entityName, $this->dependentEntities)) { - return $this->dependentEntities[$entityName]; - } - - return DataObjectHandler::getInstance()->getObject($entityName); - } - - /** - * This function retrieves an array representative of json body for a request and returns it encoded as a string. - * - * @return string - */ - public function getEncodedJsonString() - { - $jsonMetadataArray = $this->convertJsonArray($this->entityObject, $this->jsonDefinition->getJsonMetadata()); - - return json_encode($jsonMetadataArray, JSON_PRETTY_PRINT); - } - - /** - * Increment an entity's sequence number by 1. - * - * @param string $entityName - * @return void - */ - private static function incrementSequence($entityName) - { - if (array_key_exists($entityName, self::$entitySequences)) { - self::$entitySequences[$entityName]++; - } else { - self::$entitySequences[$entityName] = 1; - } - } - - /** - * Get the current sequence number for an entity. - * - * @param string $entityName - * @return int - */ - private static function getSequence($entityName) - { - if (array_key_exists($entityName, self::$entitySequences)) { - return self::$entitySequences[$entityName]; - } - return 0; - } - - // @codingStandardsIgnoreStart - - /** - * This function takes a string value and its corresponding type and returns the string cast - * into its the type passed. - * - * @param string $type - * @param string $value - * @return mixed - */ - private function castValue($type, $value) - { - $newVal = $value; - - switch ($type) { - case 'string': - break; - case 'integer': - $newVal = (integer)$value; - break; - case 'boolean': - if (strtolower($newVal) === 'false') { - return false; - } - $newVal = (boolean)$value; - break; - case 'double': - $newVal = (double)$value; - break; - } - - return $newVal; - } - // @codingStandardsIgnoreEnd -} diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Api/EntityApiHandler.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Api/EntityApiHandler.php deleted file mode 100644 index 8116dbd87..000000000 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Api/EntityApiHandler.php +++ /dev/null @@ -1,107 +0,0 @@ -entityObject = clone $entityObject; - $this->dependentObjects = $dependentObjects; - } - - /** - * Function which executes a create request based on specific operation metadata - * @return void - */ - public function createEntity() - { - $apiExecutor = new ApiExecutor('create', $this->entityObject, $this->dependentObjects); - $result = $apiExecutor->executeRequest(); - - $this->createdObject = new EntityDataObject( - $this->entityObject->getName(), - $this->entityObject->getType(), - json_decode($result, true), - null, - null // No uniqueness data is needed to be further processed. - ); - } - - /** - * Function which executes a delete request based on specific operation metadata - * - * @return string | false - */ - public function deleteEntity() - { - $apiExecutor = new ApiExecutor('delete', $this->createdObject); - $result = $apiExecutor->executeRequest(); - - return $result; - } - - /** - * Returns the createdDataObject, instantiated when the entity is created via API. - * @return EntityDataObject - */ - public function getCreatedObject() - { - return $this->createdObject; - } - - /** - * Returns a specific data value based on the CreatedObject's definition. - * @param string $dataName - * @return string - */ - public function getCreatedDataByName($dataName) - { - $data = $this->createdObject->getDataByName($dataName, EntityDataObject::NO_UNIQUE_PROCESS); - if (empty($data)) { - $data = $this->entityObject->getDataByName($dataName, EntityDataObject::CEST_UNIQUE_VALUE); - } - return $data; - } - - // TODO add update function - /* public function updateEntity() - { - - }*/ -} diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/DataObjectHandler.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/DataObjectHandler.php index 633028538..f52659b34 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/DataObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/DataObjectHandler.php @@ -43,6 +43,12 @@ class DataObjectHandler implements ObjectHandlerInterface const ARRAY_ELEMENT_ITEM = 'item'; const ARRAY_ELEMENT_ITEM_VALUE = 'value'; + const VAR_VALUES = 'var'; + const VAR_KEY = 'key'; + const VAR_ENTITY = 'entityType'; + const VAR_FIELD = 'entityKey'; + const VAR_ENTITY_FIELD_SEPARATOR = '->'; + const REQUIRED_ENTITY = 'required-entity'; const REQUIRED_ENTITY_TYPE = 'type'; const REQUIRED_ENTITY_VALUE = 'value'; @@ -150,6 +156,7 @@ private function parseDataEntities() $dataValues = []; $linkedEntities = []; $arrayValues = []; + $vars = []; $uniquenessValues = []; if (array_key_exists(self::DATA_VALUES, $entity)) { @@ -186,12 +193,21 @@ private function parseDataEntities() } } + if (array_key_exists(self::VAR_VALUES, $entity)) { + foreach ($entity[self::VAR_VALUES] as $varElement) { + $varKey = $varElement[self::VAR_KEY]; + $varValue = $varElement[self::VAR_ENTITY] . self::VAR_ENTITY_FIELD_SEPARATOR . $varElement[self::VAR_FIELD]; + $vars[$varKey] = $varValue; + } + } + $entityDataObject = new EntityDataObject( $entityName, $entityType, $dataValues, $linkedEntities, - $uniquenessValues + $uniquenessValues, + $vars ); $this->data[$entityDataObject->getName()] = $entityDataObject; diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/JsonDefinitionObjectHandler.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/JsonDefinitionObjectHandler.php deleted file mode 100644 index 9243ff46e..000000000 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/JsonDefinitionObjectHandler.php +++ /dev/null @@ -1,216 +0,0 @@ -initJsonDefinitions(); - } - - return self::$JSON_DEFINITION_OBJECT_HANDLER; - } - - /** - * Returns a JsonDefinition object based on name - * - * @param string $jsonDefinitionName - * @return JsonDefinition - */ - public function getObject($jsonDefinitionName) - { - return $this->jsonDefinitions[$jsonDefinitionName]; - } - - /** - * Returns all Json Definition objects - * - * @return array - */ - public function getAllObjects() - { - return $this->jsonDefinitions; - } - - /** - * JsonDefintionArrayProcessor constructor. - */ - private function __construct() - { - $this->jsonDefExtractor = new JsonObjectExtractor(); - } - - /** - * This method takes an operation such as create and a data type such as 'customer' and returns the corresponding - * json definition defined in metadata.xml - * - * @param string $operation - * @param string $dataType - * @return JsonDefinition - */ - public function getJsonDefinition($operation, $dataType) - { - return $this->getObject($operation . $dataType); - } - - /** - * This method reads all jsonDefinitions from metadata xml into memory. - * @return void - */ - private function initJsonDefinitions() - { - $objectManager = ObjectManagerFactory::getObjectManager(); - $metadataParser = $objectManager->create(OperationMetadataParser::class); - foreach ($metadataParser->readOperationMetadata()[JsonDefinitionObjectHandler::ENTITY_OPERATION_ROOT_TAG] as - $jsonDefName => $jsonDefArray) { - $operation = $jsonDefArray[JsonDefinitionObjectHandler::ENTITY_OPERATION_TYPE]; - $dataType = $jsonDefArray[JsonDefinitionObjectHandler::ENTITY_OPERATION_DATA_TYPE]; - $url = $jsonDefArray[JsonDefinitionObjectHandler::ENTITY_OPERATION_URL] ?? null; - $method = $jsonDefArray[JsonDefinitionObjectHandler::ENTITY_OPERATION_METHOD] ?? null; - $auth = $jsonDefArray[JsonDefinitionObjectHandler::ENTITY_OPERATION_AUTH] ?? null; - $headers = []; - $params = []; - $jsonMetadata = []; - - if (array_key_exists(JsonDefinitionObjectHandler::ENTITY_OPERATION_HEADER, $jsonDefArray)) { - foreach ($jsonDefArray[JsonDefinitionObjectHandler::ENTITY_OPERATION_HEADER] as $headerEntry) { - $headers[] = $headerEntry[JsonDefinitionObjectHandler::ENTITY_OPERATION_HEADER_PARAM] . ': ' . - $headerEntry[JsonDefinitionObjectHandler::ENTITY_OPERATION_HEADER_VALUE]; - } - } - - if (array_key_exists(JsonDefinitionObjectHandler::ENTITY_OPERATION_URL_PARAM, $jsonDefArray)) { - foreach ($jsonDefArray[JsonDefinitionObjectHandler::ENTITY_OPERATION_URL_PARAM] as $paramEntry) { - $params[$paramEntry[JsonDefinitionObjectHandler::ENTITY_OPERATION_URL_PARAM_TYPE]] - [$paramEntry[JsonDefinitionObjectHandler::ENTITY_OPERATION_URL_PARAM_KEY]] = - $paramEntry[JsonDefinitionObjectHandler::ENTITY_OPERATION_URL_PARAM_VALUE]; - } - } - - // extract relevant jsonObjects as JsonElements - if (array_key_exists(JsonDefinitionObjectHandler::ENTITY_OPERATION_JSON_OBJECT, $jsonDefArray)) { - foreach ($jsonDefArray[JsonDefinitionObjectHandler::ENTITY_OPERATION_JSON_OBJECT] as $jsonObjectArray) { - $jsonMetadata[] = $this->jsonDefExtractor->extractJsonObject($jsonObjectArray); - } - } - - //handle loose entries - - if (array_key_exists(JsonDefinitionObjectHandler::ENTITY_OPERATION_ENTRY, $jsonDefArray)) { - foreach ($jsonDefArray[JsonDefinitionObjectHandler::ENTITY_OPERATION_ENTRY] as $jsonEntryType) { - $jsonMetadata[] = new JsonElement( - $jsonEntryType[JsonDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_KEY], - $jsonEntryType[JsonDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_VALUE], - JsonDefinitionObjectHandler::ENTITY_OPERATION_ENTRY, - $jsonEntryType[JsonDefinitionObjectHandler::ENTITY_OPERATION_REQUIRED] ?? null - ); - } - } - - if (array_key_exists(JsonDefinitionObjectHandler::ENTITY_OPERATION_ARRAY, $jsonDefArray)) { - foreach ($jsonDefArray[JsonDefinitionObjectHandler::ENTITY_OPERATION_ARRAY] as $jsonEntryType) { - $jsonSubMetadata = []; - $value = null; - $type = null; - - if (array_key_exists('jsonObject', $jsonEntryType)) { - $jsonNestedElement = $this->jsonDefExtractor->extractJsonObject( - $jsonEntryType['jsonObject'][0] - ); - $jsonSubMetadata[$jsonNestedElement->getKey()] = $jsonNestedElement; - $value = $jsonNestedElement->getValue(); - $type = $jsonNestedElement->getKey(); - } else { - $value = $jsonEntryType[JsonDefinitionObjectHandler::ENTITY_OPERATION_ARRAY_VALUE][0] - [JsonDefinitionObjectHandler::ENTITY_OPERATION_ARRAY_VALUE]; - $type = [JsonDefinitionObjectHandler::ENTITY_OPERATION_ARRAY_VALUE]; - } - - $jsonMetadata[] = new JsonElement( - $jsonEntryType[JsonDefinitionObjectHandler::ENTITY_OPERATION_ARRAY_KEY], - $value, - $type, - $jsonEntryType[JsonDefinitionObjectHandler::ENTITY_OPERATION_REQUIRED] ?? null, - $jsonSubMetadata - ); - } - } - - $this->jsonDefinitions[$operation . $dataType] = new JsonDefinition( - $jsonDefName, - $operation, - $dataType, - $method, - $url, - $auth, - $headers, - $params, - $jsonMetadata - ); - } - } -} diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/OperationDefinitionObjectHandler.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/OperationDefinitionObjectHandler.php new file mode 100644 index 000000000..9dda26324 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/OperationDefinitionObjectHandler.php @@ -0,0 +1,233 @@ +initDataDefinitions(); + } + + return self::$DATA_DEFINITION_OBJECT_HANDLER; + } + + /** + * Returns a OperationDefinitionObject object based on name + * + * @param string $operationDefinitionName + * @return OperationDefinitionObject + */ + public function getObject($operationDefinitionName) + { + return $this->operationDefinitions[$operationDefinitionName]; + } + + /** + * Returns all data Definition objects + * + * @return array + */ + public function getAllObjects() + { + return $this->operationDefinitions; + } + + /** + * DataDefintionArrayProcessor constructor. + */ + private function __construct() + { + $this->dataDefExtractor = new OperationElementExtractor(); + } + + /** + * This method takes an operation such as create and a data type such as 'customer' and returns the corresponding + * data definition defined in metadata.xml + * + * @param string $operation + * @param string $dataType + * @return OperationDefinitionObject + */ + public function getOperationDefinition($operation, $dataType) + { + return $this->getObject($operation . $dataType); + } + + /** + * This method reads all dataDefinitions from metadata xml into memory. + * @return void + */ + private function initDataDefinitions() + { + $objectManager = ObjectManagerFactory::getObjectManager(); + $metadataParser = $objectManager->create(OperationDefinitionParser::class); + foreach ($metadataParser->readOperationMetadata() + [OperationDefinitionObjectHandler::ENTITY_OPERATION_ROOT_TAG] as $dataDefName => $opDefArray) { + $operation = $opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_TYPE]; + $dataType = $opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_DATA_TYPE]; + $url = $opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_URL] ?? null; + $method = $opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_METHOD] ?? null; + $auth = $opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_AUTH] ?? null; + $successRegex = $opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_SUCCESS_REGEX] ?? null; + $returnRegex = $opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_RETURN_REGEX] ?? null; + $contentType = $opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_CONTENT_TYPE][0]['value'] + ?? null; + $headers = []; + $params = []; + $operationElements = []; + + if (array_key_exists(OperationDefinitionObjectHandler::ENTITY_OPERATION_HEADER, $opDefArray)) { + foreach ($opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_HEADER] as $headerEntry) { + if (isset($headerEntry[OperationDefinitionObjectHandler::ENTITY_OPERATION_HEADER_VALUE]) + && $headerEntry[OperationDefinitionObjectHandler::ENTITY_OPERATION_HEADER_VALUE] !== 'none') { + $headers[] = $headerEntry[OperationDefinitionObjectHandler::ENTITY_OPERATION_HEADER_PARAM] + . ': ' + . $headerEntry[OperationDefinitionObjectHandler::ENTITY_OPERATION_HEADER_VALUE]; + } + } + } + + if (array_key_exists(OperationDefinitionObjectHandler::ENTITY_OPERATION_URL_PARAM, $opDefArray)) { + foreach ($opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_URL_PARAM] as $paramEntry) { + $params[$paramEntry[OperationDefinitionObjectHandler::ENTITY_OPERATION_URL_PARAM_TYPE]] + [$paramEntry[OperationDefinitionObjectHandler::ENTITY_OPERATION_URL_PARAM_KEY]] = + $paramEntry[OperationDefinitionObjectHandler::ENTITY_OPERATION_URL_PARAM_VALUE]; + } + } + + // extract relevant OperationObjects as OperationElements + if (array_key_exists(OperationDefinitionObjectHandler::ENTITY_OPERATION_OBJECT, $opDefArray)) { + foreach ($opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_OBJECT] as $opElementArray) { + $operationElements[] = $this->dataDefExtractor->extractOperationElement($opElementArray); + } + } + + //handle loose operation fields + if (array_key_exists(OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY, $opDefArray)) { + foreach ($opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY] as $operationField) { + $operationElements[] = new OperationElement( + $operationField[OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_KEY], + $operationField[OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_VALUE], + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY, + $operationField[OperationDefinitionObjectHandler::ENTITY_OPERATION_REQUIRED] ?? null + ); + } + } + + // handle loose json arrays + if (array_key_exists(OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY, $opDefArray)) { + foreach ($opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY] as $operationField) { + $subOperationElements = []; + $value = null; + $type = null; + + if (array_key_exists( + OperationDefinitionObjectHandler::ENTITY_OPERATION_OBJECT, + $operationField + )) { + $nestedDataElement = $this->dataDefExtractor->extractOperationElement( + $operationField[OperationDefinitionObjectHandler::ENTITY_OPERATION_OBJECT][0] + ); + $subOperationElements[$nestedDataElement->getKey()] = $nestedDataElement; + $value = $nestedDataElement->getValue(); + $type = $nestedDataElement->getKey(); + } else { + $value = $operationField[OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY_VALUE][0] + [OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY_VALUE]; + $type = OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY; + } + + $operationElements[] = new OperationElement( + $operationField[OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY_KEY], + $value, + $type, + $operationField[OperationDefinitionObjectHandler::ENTITY_OPERATION_REQUIRED] ?? null, + $subOperationElements + ); + } + } + + $this->operationDefinitions[$operation . $dataType] = new OperationDefinitionObject( + $dataDefName, + $operation, + $dataType, + $method, + $url, + $auth, + $headers, + $params, + $operationElements, + $contentType, + $successRegex, + $returnRegex + ); + } + } +} diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/EntityDataObject.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/EntityDataObject.php index db602ad07..41730f3aa 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/EntityDataObject.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/EntityDataObject.php @@ -34,6 +34,13 @@ class EntityDataObject */ private $linkedEntities = []; + /** + * An array of variable mappings for static data + * + * @var array + */ + private $vars; + /** * An array of Data Name to Data Value * @@ -63,8 +70,9 @@ class EntityDataObject * @param array $data * @param array $linkedEntities * @param array $uniquenessData + * @param array $vars */ - public function __construct($entityName, $entityType, $data, $linkedEntities, $uniquenessData) + public function __construct($entityName, $entityType, $data, $linkedEntities, $uniquenessData, $vars = []) { $this->name = $entityName; $this->type = $entityType; @@ -73,6 +81,8 @@ public function __construct($entityName, $entityType, $data, $linkedEntities, $u if ($uniquenessData) { $this->uniquenessData = $uniquenessData; } + + $this->vars = $vars; } /** @@ -189,6 +199,22 @@ public function getDataByName($dataName, $uniDataFormat) return null; } + /** + * Function which returns a reference to another entity (e.g. a var with entity="category" field="id" returns as + * category->id) + * + * @param string $dataKey + * @return array|null + */ + public function getVarReference($dataKey) + { + if (array_key_exists($dataKey, $this->vars)) { + return $this->vars[$dataKey]; + } + + return null; + } + /** * This function takes an array of entityTypes indexed by name and a string that represents the type of interest. * The function returns an array of entityNames relevant to the specified type. diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/JsonDefinition.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/OperationDefinitionObject.php similarity index 54% rename from src/Magento/FunctionalTestingFramework/DataGenerator/Objects/JsonDefinition.php rename to src/Magento/FunctionalTestingFramework/DataGenerator/Objects/OperationDefinitionObject.php index f19a2e51e..8f9ac19a4 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/JsonDefinition.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/OperationDefinitionObject.php @@ -7,26 +7,28 @@ namespace Magento\FunctionalTestingFramework\DataGenerator\Objects; /** - * Class JsonDefinition + * Class OperationDefinitionObject */ -class JsonDefinition +class OperationDefinitionObject { + const HTTP_CONTENT_TYPE_HEADER = 'Content-Type'; + /** - * Json Definitions Name + * Data Definitions Name * * @var string */ private $name; /** - * Operation which the json defintion describes + * Operation which the data defintion describes * * @var string */ private $operation; /** - * Data type for which the json defintiion is used + * Data type for which the data defintiion is used * * @var string */ @@ -40,18 +42,18 @@ class JsonDefinition private $apiMethod; /** - * Base URL for the request + * Api request url. * * @var string */ - private $baseUrl; + private $apiUrl; /** * Resource specific URI for the request * * @var string */ - private $apiUrl; + private $apiUri; /** * Authorization path for retrieving a token @@ -60,6 +62,13 @@ class JsonDefinition */ private $auth; + /** + * Content type of body + * + * @var string + */ + private $contentType; + /** * Relevant headers for the request * @@ -75,48 +84,79 @@ class JsonDefinition private $params = []; /** - * The metadata describing the json fields and values themselves + * The metadata describing the data fields and values themselves * * @var array */ - private $jsonMetadata = []; + private $operationMetadata = []; + + /** + * Regex to check for request success. + * + * @var string + */ + private $successRegex; /** - * JsonDefinition constructor. + * Regex to grab return value from response. + * + * @var string + */ + private $returnRegex; + + /** + * OperationDefinitionObject constructor. * @param string $name * @param string $operation * @param string $dataType * @param string $apiMethod - * @param string $apiUrl + * @param string $apiUri * @param string $auth * @param array $headers * @param array $params - * @param array $jsonMetadata + * @param array $metaData + * @param string $contentType + * @param string $successRegex + * @param string $returnRegex */ public function __construct( $name, $operation, $dataType, $apiMethod, - $apiUrl, + $apiUri, $auth, $headers, $params, - $jsonMetadata + $metaData, + $contentType, + $successRegex = null, + $returnRegex = null ) { $this->name = $name; $this->operation = $operation; $this->dataType = $dataType; $this->apiMethod = $apiMethod; - $this->baseUrl = $apiUrl; + $this->apiUri = trim($apiUri, '/'); $this->auth = $auth; $this->headers = $headers; $this->params = $params; - $this->jsonMetadata = $jsonMetadata; + $this->operationMetadata = $metaData; + $this->successRegex = $successRegex; + $this->returnRegex = $returnRegex; + $this->apiUrl = null; + + if (!empty($contentType)) { + $this->contentType = $contentType; + } else { + $this->contentType = 'application/x-www-form-urlencoded'; + } + // add content type as a header + $this->headers[] = self::HTTP_CONTENT_TYPE_HEADER . ': ' . $this->contentType; } /** - * Getter for json data type + * Getter for data's data type * * @return string */ @@ -126,7 +166,7 @@ public function getDataType() } /** - * Getter for json operation + * Getter for data operation * * @return string */ @@ -146,20 +186,22 @@ public function getApiMethod() } /** - * Getter for api url + * Getter for api url for a store. * * @return string */ public function getApiUrl() { - $this->cleanApiUrl(); + if (!$this->apiUrl) { + $this->apiUrl = $this->apiUri; - if (array_key_exists('path', $this->params)) { - $this->addPathParam(); - } + if (array_key_exists('path', $this->params)) { + $this->addPathParam(); + } - if (array_key_exists('query', $this->params)) { - $this->addQueryParams(); + if (array_key_exists('query', $this->params)) { + $this->addQueryParams(); + } } return $this->apiUrl; @@ -186,27 +228,43 @@ public function getHeaders() } /** - * Getter for json metadata + * Getter for Content-type + * + * @return string + */ + public function getContentType() + { + return $this->contentType; + } + + /** + * Getter for data metadata * * @return array */ - public function getJsonMetadata() + public function getOperationMetadata() { - return $this->jsonMetadata; + return $this->operationMetadata; } /** - * Function to validate api format and add "/" char where necessary + * Getter for success regex. * - * @return void + * @return string */ - private function cleanApiUrl() + public function getSuccessRegex() { - if (substr($this->baseUrl, -1) == "/") { - $this->apiUrl = rtrim($this->baseUrl, "/"); - } else { - $this->apiUrl = $this->baseUrl; - } + return $this->successRegex; + } + + /** + * Getter for return regex. + * + * @return string + */ + public function getReturnRegex() + { + return $this->returnRegex; } /** @@ -222,21 +280,19 @@ private function addPathParam() } /** - * Function to append query params where necessary + * Function to append or add query parameters * * @return void */ - private function addQueryParams() + public function addQueryParams() { - foreach ($this->params['query'] as $paramName => $paramValue) { - if (!stringContains("?", $this->apiUrl)) { + if (strpos($this->apiUrl, '?') === false) { $this->apiUrl = $this->apiUrl . "?"; } else { $this->apiUrl = $this->apiUrl . "&"; } - - $this->apiUrl = $paramName . "=" . $paramValue; + $this->apiUrl = $this->apiUrl . $paramName . "=" . $paramValue; } } } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/JsonElement.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/OperationElement.php similarity index 76% rename from src/Magento/FunctionalTestingFramework/DataGenerator/Objects/JsonElement.php rename to src/Magento/FunctionalTestingFramework/DataGenerator/Objects/OperationElement.php index a2af57341..0580018fd 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/JsonElement.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/OperationElement.php @@ -6,51 +6,51 @@ namespace Magento\FunctionalTestingFramework\DataGenerator\Objects; -class JsonElement +class OperationElement { /** - * Json parameter name + * Data parameter name * * @var string */ private $key; /** - * Json parameter metadata value (e.g. string, bool) + * Data parameter metadata value (e.g. string, bool) * * @var string */ private $value; /** - * Json type such as array or entry + * Data type such as array or entry * * @var string */ private $type; /** - * Nested Json Objects defined within the same operation.xml file + * Nested data Objects defined within the same operation.xml file * * @var array */ private $nestedElements = []; /** - * Nested Metadata which must be included for a jsonElement of type jsonObject + * Nested Metadata which must be included for a dataElement of type dataObject * * @var array|null */ private $nestedMetadata = []; /** - * Json required attribute, used to determine if values need to be cast before insertion. + * Required attribute, used to determine if values need to be cast before insertion. * @var bool */ private $required; /** - * JsonElement constructor. + * OperationElement constructor. * @param string $key * @param string $value * @param string $type @@ -73,7 +73,7 @@ public function __construct($key, $value, $type, $required, $nestedElements = [] } /** - * Getter for json parameter name + * Getter for data parameter name * * @return string */ @@ -113,12 +113,12 @@ public function getRequired() } /** - * Returns the nested json element based on the type of entity passed + * Returns the nested data element based on the type of entity passed * * @param string $type * @return array */ - public function getNestedJsonElement($type) + public function getNestedOperationElement($type) { if (array_key_exists($type, $this->nestedElements)) { return $this->nestedElements[$type]; @@ -128,7 +128,7 @@ public function getNestedJsonElement($type) } /** - * Returns relevant nested json metadata for a json element which is a json object + * Returns relevant nested data metadata for a data element which is a data object * * @return array|null */ diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Parsers/DataProfileSchemaParser.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Parsers/DataProfileSchemaParser.php index c9ac3dda2..0a40afe09 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Parsers/DataProfileSchemaParser.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Parsers/DataProfileSchemaParser.php @@ -13,6 +13,13 @@ */ class DataProfileSchemaParser { + /** + * Data Profiles. + * + * @var DataInterface + */ + private $dataProfiles; + /** * DataProfileSchemaParser constructor. * @param DataInterface $dataProfiles diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Parsers/OperationMetadataParser.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Parsers/OperationDefinitionParser.php similarity index 80% rename from src/Magento/FunctionalTestingFramework/DataGenerator/Parsers/OperationMetadataParser.php rename to src/Magento/FunctionalTestingFramework/DataGenerator/Parsers/OperationDefinitionParser.php index b3c6cc514..e7a68435c 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Parsers/OperationMetadataParser.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Parsers/OperationDefinitionParser.php @@ -9,10 +9,17 @@ use Magento\FunctionalTestingFramework\Config\DataInterface; /** - * Class OperationMetadataParser + * Class OperationDefinitionParser */ -class OperationMetadataParser +class OperationDefinitionParser { + /** + * Meta Data. + * + * @var DataInterface + */ + private $metadata; + /** * MetadataParser constructor. * @param DataInterface $metadata diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/AbstractExecutor.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/AbstractExecutor.php new file mode 100644 index 000000000..6adf87892 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/AbstractExecutor.php @@ -0,0 +1,45 @@ +transport = new CurlTransport(); + $this->authorize(); + } + + /** + * Authorize customer on backend. + * + * @return void + * @throws TestFrameworkException + */ + public function authorize() + { + // Perform GET to backend url so form_key is set + $this->transport->write(self::$adminUrl, [], CurlInterface::GET); + $this->read(); + + // Authenticate admin user + $authUrl = self::$adminUrl . 'admin/auth/login/'; + $data = [ + 'login[username]' => getenv('MAGENTO_ADMIN_USERNAME'), + 'login[password]' => getenv('MAGENTO_ADMIN_PASSWORD'), + 'form_key' => $this->formKey, + ]; + $this->transport->write($authUrl, $data, CurlInterface::POST); + $response = $this->read(); + if (strpos($response, 'login-form')) { + throw new TestFrameworkException('Admin user authentication failed!'); + } + } + + /** + * Init Form Key from response. + * + * @return void + */ + private function setFormKey() + { + preg_match('!var FORM_KEY = \'(\w+)\';!', $this->response, $matches); + if (!empty($matches[1])) { + $this->formKey = $matches[1]; + } + } + + /** + * Send request to the remote server. + * + * @param string $url + * @param array $data + * @param string $method + * @param array $headers + * @return void + * @throws TestFrameworkException + */ + public function write($url, $data = [], $method = CurlInterface::POST, $headers = []) + { + $apiUrl = self::$adminUrl . $url; + if ($this->formKey) { + $data['form_key'] = $this->formKey; + } else { + throw new TestFrameworkException( + sprintf('Form key is absent! Url: "%s" Response: "%s"', $url, $this->response) + ); + } + + $this->transport->write($apiUrl, str_replace('null', '', http_build_query($data)), $method, $headers); + } + + /** + * Read response from server. + * + * @param string $successRegex + * @param string $returnRegex + * @return string|array + * @throws TestFrameworkException + */ + public function read($successRegex = null, $returnRegex = null) + { + $this->response = $this->transport->read(); + $this->setFormKey(); + + if (!empty($successRegex)) { + preg_match( + '/' . preg_quote($successRegex, '/') . '/', + $this->response, + $successMatches + ); + if (empty($successMatches)) { + throw new TestFrameworkException("Entity creation was not successful! Response: $this->response"); + } + } + + if (!empty($returnRegex)) { + preg_match( + '/' . preg_quote($returnRegex, '/') . '/', + $this->response, + $returnMatches + ); + if (!empty($returnMatches)) { + return $returnMatches; + } + } + return $this->response; + } + + /** + * Add additional option to cURL. + * + * @param int $option the CURLOPT_* constants + * @param int|string|bool|array $value + * @return void + */ + public function addOption($option, $value) + { + $this->transport->addOption($option, $value); + } + + /** + * Close the connection to the server. + * + * @return void + */ + public function close() + { + $this->transport->close(); + } +} diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/WebapiExecutor.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/WebapiExecutor.php new file mode 100644 index 000000000..652f620b4 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/WebapiExecutor.php @@ -0,0 +1,141 @@ +storeCode = $storeCode; + $this->transport = new CurlTransport(); + $this->authorize(); + } + + /** + * Returns the authorization token needed for some requests via REST call. + * + * @return void + */ + private function authorize() + { + $authUrl = parent::$baseUrl . 'rest/' . $this->storeCode . self::ADMIN_AUTH_URL; + $authCreds = [ + 'username' => getenv('MAGENTO_ADMIN_USERNAME'), + 'password' => getenv('MAGENTO_ADMIN_PASSWORD') + ]; + + $this->transport->write($authUrl, json_encode($authCreds), CurlInterface::POST, $this->headers); + $this->headers = array_merge( + ['Authorization: Bearer ' . str_replace('"', "", $this->read())], + $this->headers + ); + } + + /** + * Send request to the remote server. + * + * @param string $url + * @param array $data + * @param string $method + * @param array $headers + * @return void + */ + public function write($url, $data = [], $method = CurlInterface::POST, $headers = []) + { + $this->transport->write( + parent::$baseUrl . 'rest/' . $this->storeCode . '/' . trim($url, '/'), + json_encode($data, JSON_PRETTY_PRINT), + $method, + array_unique(array_merge($headers, $this->headers)) + ); + } + + /** + * Read response from server. + * + * @param string $successRegex + * @param string $returnRegex + * @return string + */ + public function read($successRegex = null, $returnRegex = null) + { + $this->response = $this->transport->read(); + return $this->response; + } + + /** + * Add additional option to cURL. + * + * @param int $option the CURLOPT_* constants + * @param int|string|bool|array $value + * @return void + */ + public function addOption($option, $value) + { + $this->transport->addOption($option, $value); + } + + /** + * Close the connection to the server. + * + * @return void + */ + public function close() + { + $this->transport->close(); + } +} diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/CurlHandler.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/CurlHandler.php new file mode 100644 index 000000000..e7d73676e --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/CurlHandler.php @@ -0,0 +1,196 @@ + CurlInterface::POST, + 'delete' => CurlInterface::DELETE, + 'update' => CurlInterface::PUT, + 'get' => CurlInterface::GET, + ]; + + /** + * ApiSubObject constructor. + * @param string $operation + * @param EntityDataObject $entityObject + * @param string $storeCode + */ + public function __construct($operation, $entityObject, $storeCode = 'default') + { + $this->operation = $operation; + $this->entityObject = $entityObject; + $this->storeCode = $storeCode; + + $this->operationDefinition = OperationDefinitionObjectHandler::getInstance()->getOperationDefinition( + $this->operation, + $this->entityObject->getType() + ); + $this->isJson = false; + } + + /** + * Executes an api request based on parameters given by constructor. + * + * @param array $dependentEntities + * @return array | null + * @throws TestFrameworkException + */ + public function executeRequest($dependentEntities) + { + $executor = null; + $successRegex = null; + $returnRegex = null; + + $apiUrl = $this->resolveUrlReference($this->operationDefinition->getApiUrl()); + $headers = $this->operationDefinition->getHeaders(); + $authorization = $this->operationDefinition->getAuth(); + $contentType = $this->operationDefinition->getContentType(); + $successRegex = $this->operationDefinition->getSuccessRegex(); + $returnRegex = $this->operationDefinition->getReturnRegex(); + + $operationDataResolver = new OperationDataArrayResolver($dependentEntities); + $this->requestData = $operationDataResolver->resolveOperationDataArray( + $this->entityObject, + $this->operationDefinition->getOperationMetadata(), + $this->operationDefinition->getOperation() + ); + + if (($contentType === 'application/json') && ($authorization === 'adminOauth')) { + $this->isJson = true; + $executor = new WebapiExecutor($this->storeCode); + } elseif ($authorization === 'adminFormKey') { + $executor = new AdminExecutor(); + //TODO: add frontend request executor + //} elseif ($authorization === 'customFromKey') { + } + + if (!$executor) { + throw new TestFrameworkException( + sprintf( + "Invalid content type and/or auth type. content type = %s, auth type = %s\n", + $contentType, + $authorization + ) + ); + } + + $executor->write( + $apiUrl, + $this->requestData, + self::$curlMethodMapping[$this->operation], + $headers + ); + + $response = $executor->read($successRegex, $returnRegex); + $executor->close(); + + return $response; + } + + /** + * Getter for request data in array. + * + * @return array + */ + public function getRequestDataArray() + { + return $this->requestData; + } + + /** + * If content type of a request is Json. + * + * @return bool + */ + public function isContentTypeJson() + { + return $this->isJson; + } + + /** + * Resolve rul reference from entity data. + * + * @param string $urlIn + * @return string + */ + private function resolveUrlReference($urlIn) + { + $urlOut = $urlIn; + $matchedParams = []; + preg_match_all("/[{](.+?)[}]/", $urlIn, $matchedParams); + + if (!empty($matchedParams)) { + foreach ($matchedParams[0] as $paramKey => $paramValue) { + $param = $this->entityObject->getDataByName( + $matchedParams[1][$paramKey], + EntityDataObject::CEST_UNIQUE_VALUE + ); + $urlOut = str_replace($paramValue, $param, $urlIn); + } + } + return $urlOut; + } +} diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/DataPersistenceHandler.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/DataPersistenceHandler.php new file mode 100644 index 000000000..7c80765b7 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/DataPersistenceHandler.php @@ -0,0 +1,173 @@ +entityObject = clone $entityObject; + $this->storeCode = 'default'; + + foreach ($dependentObjects as $dependentObject) { + $this->dependentObjects[] = $dependentObject->getCreatedObject(); + } + } + + /** + * Function which executes a create request based on specific operation metadata + * + * @param string $storeCode + * @return void + */ + public function createEntity($storeCode = null) + { + if (!empty($storeCode)) { + $this->storeCode = $storeCode; + } + $curlHandler = new CurlHandler('create', $this->entityObject, $this->storeCode); + $result = $curlHandler->executeRequest($this->dependentObjects); + $this->setCreatedEntity( + $result, + $curlHandler->getRequestDataArray(), + $curlHandler->isContentTypeJson() + ); + } + + /** + * Function which executes a delete request based on specific operation metadata + * + * @param string $storeCode + * @return void + */ + public function deleteEntity($storeCode = null) + { + if (!empty($storeCode)) { + $this->storeCode = $storeCode; + } + $curlHandler = new CurlHandler('delete', $this->createdObject, $this->storeCode); + $result = $curlHandler->executeRequest($this->dependentObjects); + } + + /** + * Returns the createdDataObject, instantiated when the entity is created via API. + * @return EntityDataObject + */ + public function getCreatedObject() + { + return $this->createdObject; + } + + /** + * Returns a specific data value based on the CreatedObject's definition. + * @param string $dataName + * @return string + */ + public function getCreatedDataByName($dataName) + { + return $this->createdObject->getDataByName($dataName, EntityDataObject::NO_UNIQUE_PROCESS); + } + + // TODO add update function + /* public function updateEntity() + { + + }*/ + + /** + * Save created entity. + * + * @param string|array $response + * @param array $requestDataArray + * @param bool $isJson + * @return void + */ + private function setCreatedEntity($response, $requestDataArray, $isJson) + { + if ($isJson) { + $persistedData = array_merge($requestDataArray, json_decode($response, true)); + } else { + $persistedData = array_merge( + $this->convertToFlatArray($requestDataArray), + ['return' => $response] + ); + } + + $this->createdObject = new EntityDataObject( + $this->entityObject->getName(), + $this->entityObject->getType(), + $persistedData, + null, + null + ); + } + + /** + * Convert an multi-dimensional array to flat array. + * + * @param array $arrayIn + * @param string $rootKey + * @return array + */ + private function convertToFlatArray($arrayIn, $rootKey = '') + { + $arrayOut = []; + foreach ($arrayIn as $key => $value) { + if (is_array($value)) { + if (!empty($rootKey)) { + $newRootKey = $rootKey . '[' . $key . ']'; + } else { + $newRootKey = $key; + } + $arrayOut = array_merge($arrayOut, $this->convertToFlatArray($value, $newRootKey)); + } elseif (!empty($rootKey)) { + $arrayOut[$rootKey . '[' . $key . ']'] = $value; + } else { + $arrayOut[$key] = $value; + } + } + return $arrayOut; + } +} diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/OperationDataArrayResolver.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/OperationDataArrayResolver.php new file mode 100644 index 000000000..49cb535a8 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/OperationDataArrayResolver.php @@ -0,0 +1,347 @@ +dependentEntities[$entity->getName()] = $entity; + } + } + } + + /** + * This function returns an array which is structurally equal to the data which is needed by the magento web api, + * magento backend / frontend requests for entity creation. The function retrieves an array describing the entity's + * operation metadata and traverses any dependencies recursively forming an array which represents the data + * structure for the request of the desired entity type. + * + * @param EntityDataObject $entityObject + * @param array $operationMetadata + * @param string $operation + * @return array + * @throws \Exception + */ + public function resolveOperationDataArray($entityObject, $operationMetadata, $operation) + { + $operationDataArray = []; + self::incrementSequence($entityObject->getName()); + + foreach ($operationMetadata as $operationElement) { + if ($operationElement->getType() == OperationElementExtractor::OPERATION_OBJECT_OBJ_NAME) { + $entityObj = $this->resolveOperationObjectAndEntityData($entityObject, $operationElement->getValue()); + $operationDataArray[$operationElement->getValue()] = + $this->resolveOperationDataArray($entityObj, $operationElement->getNestedMetadata(), $operation); + continue; + } + + $operationElementType = $operationElement->getValue(); + + if (in_array($operationElementType, self::PRIMITIVE_TYPES)) { + $elementData = $this->resolvePrimitiveReference( + $entityObject, + $operationElement->getKey(), + $operationElement->getType() + ); + + // If data was defined at all, attempt to put it into operation data array + // If data was not defined, and element is required, throw exception + // If no data is defined, don't input defaults per primitive into operation data array + if ($elementData != null) { + if (array_key_exists($operationElement->getKey(), $entityObject->getUniquenessData())) { + $uniqueData = $entityObject->getUniquenessDataByName($operationElement->getKey()); + if ($uniqueData === 'suffix') { + $elementData .= (string)self::getSequence($entityObject->getName()); + } else { + $elementData = (string)self::getSequence($entityObject->getName()) . $elementData; + } + } + $operationDataArray[$operationElement->getKey()] = $this->castValue( + $operationElementType, + $elementData + ); + + } elseif ($operationElement->getRequired()) { + throw new \Exception(sprintf( + self::EXCEPTION_REQUIRED_DATA, + $operationElement->getType(), + $operationElement->getKey(), + $entityObject->getName() + )); + } + } else { + $entityNamesOfType = $entityObject->getLinkedEntitiesOfType($operationElementType); + + // If an element is required by metadata, but was not provided in the entity, throw an exception + if ($operationElement->getRequired() && $entityNamesOfType == null) { + throw new \Exception(sprintf( + self::EXCEPTION_REQUIRED_DATA, + $operationElement->getType(), + $operationElement->getKey(), + $entityObject->getName() + )); + } + foreach ($entityNamesOfType as $entityName) { + $operationDataSubArray = $this->resolveNonPrimitiveElement( + $entityName, + $operationElement, + $operation + ); + + if ($operationElement->getType() == OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY) { + $operationDataArray[$operationElement->getKey()][] = $operationDataSubArray; + } else { + $operationDataArray[$operationElement->getKey()] = $operationDataSubArray; + } + } + } + } + + return $operationDataArray; + } + + /** + * Resolves a reference for a primitive piece of data, if the data cannot be found as a defined field, the method + * looks to see if any vars have been declared with the same operationKey and resolves based on defined dependent + * entities. + * + * @param EntityDataObject $entityObject + * @param string $operationKey + * @param string $operationElementType + * @return array|string + */ + private function resolvePrimitiveReference($entityObject, $operationKey, $operationElementType) + { + $elementData = $entityObject->getDataByName( + $operationKey, + EntityDataObject::CEST_UNIQUE_VALUE + ); + + if ($elementData == null && $entityObject->getVarReference($operationKey) != null) { + list($type, $field) = explode( + DataObjectHandler::VAR_ENTITY_FIELD_SEPARATOR, + $entityObject->getVarReference($operationKey) + ); + + if ($operationElementType == OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY) { + $elementDatas = []; + $entities = $this->getDependentEntitiesOfType($type); + foreach ($entities as $entity) { + $elementDatas[] = $entity->getDataByName($field, EntityDataObject::CEST_UNIQUE_VALUE); + } + + return $elementDatas; + + } + + $entity = $this->getDependentEntitiesOfType($type)[0]; + $elementData = $entity->getDataByName($field, EntityDataObject::CEST_UNIQUE_VALUE); + } + + return $elementData; + } + + /** + * Returns all dependent entities of the type passed in as an arg (the dependent entities are given at runtime, + * and are not statically defined). + * + * @param string $type + * @return array + */ + private function getDependentEntitiesOfType($type) + { + $entitiesOfType = []; + + foreach ($this->dependentEntities as $dependentEntity) { + if ($dependentEntity->getType() == $type) { + $entitiesOfType[] = $dependentEntity; + } + } + + return $entitiesOfType; + } + + /** + * This function does a comparison of the entity object being matched to the operation element. If there is a + * mismatch in type we attempt to use a nested entity, if the entities are properly matched, we simply return + * the object. + * + * @param EntityDataObject $entityObject + * @param string $operationElementValue + * @return EntityDataObject|null + */ + private function resolveOperationObjectAndEntityData($entityObject, $operationElementValue) + { + if ($operationElementValue != $entityObject->getType()) { + // if we have a mismatch attempt to retrieve linked data and return just the first linkage + $linkName = $entityObject->getLinkedEntitiesOfType($operationElementValue)[0]; + return DataObjectHandler::getInstance()->getObject($linkName); + } + + return $entityObject; + } + + /** + * Resolves DataObjects and pre-defined metadata (in other operation.xml file) referenced by the operation + * + * @param string $entityName + * @param OperationElement $operationElement + * @param string $operation + * @return array + */ + private function resolveNonPrimitiveElement($entityName, $operationElement, $operation) + { + $linkedEntityObj = $this->resolveLinkedEntityObject($entityName); + + // in array case + if (!empty($operationElement->getNestedOperationElement($operationElement->getValue())) + && $operationElement->getType() == OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY + ) { + $operationSubArray = $this->resolveOperationDataArray( + $linkedEntityObj, + [$operationElement->getNestedOperationElement($operationElement->getValue())], + $operation + ); + + return $operationSubArray[$operationElement->getValue()]; + } + + $operationMetadata = OperationDefinitionObjectHandler::getInstance()->getOperationDefinition( + $operation, + $linkedEntityObj->getType() + )->getOperationMetadata(); + + return $this->resolveOperationDataArray($linkedEntityObj, $operationMetadata, $operation); + } + + /** + * Method to wrap entity resolution, checks locally defined dependent entities first + * + * @param string $entityName + * @return EntityDataObject + */ + private function resolveLinkedEntityObject($entityName) + { + // check our dependent entity list to see if we have this defined + if (array_key_exists($entityName, $this->dependentEntities)) { + return $this->dependentEntities[$entityName]; + } + + return DataObjectHandler::getInstance()->getObject($entityName); + } + + /** + * Increment an entity's sequence number by 1. + * + * @param string $entityName + * @return void + */ + private static function incrementSequence($entityName) + { + if (array_key_exists($entityName, self::$entitySequences)) { + self::$entitySequences[$entityName]++; + } else { + self::$entitySequences[$entityName] = 1; + } + } + + /** + * Get the current sequence number for an entity. + * + * @param string $entityName + * @return int + */ + private static function getSequence($entityName) + { + if (array_key_exists($entityName, self::$entitySequences)) { + return self::$entitySequences[$entityName]; + } + return 0; + } + + // @codingStandardsIgnoreStart + /** + * This function takes a string value and its corresponding type and returns the string cast + * into its the type passed. + * + * @param string $type + * @param string $value + * @return mixed + */ + private function castValue($type, $value) + { + $newVal = $value; + + if (is_array($value)) { + $newVals = []; + foreach($value as $val) { + $newVals[] = $this->castValue($type, $val); + } + + return $newVals; + } + + switch ($type) { + case 'string': + break; + case 'integer': + $newVal = (integer)$value; + break; + case 'boolean': + if (strtolower($newVal) === 'false') { + return false; + } + $newVal = (boolean)$value; + break; + case 'double': + $newVal = (double)$value; + break; + } + + return $newVal; + } + // @codingStandardsIgnoreEnd +} diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Util/JsonObjectExtractor.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Util/JsonObjectExtractor.php deleted file mode 100644 index f5c80065a..000000000 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Util/JsonObjectExtractor.php +++ /dev/null @@ -1,132 +0,0 @@ -extractJsonEntries($jsonMetadata, $jsonObjectArray[JsonObjectExtractor::JSON_OBJECT_ENTRY]); - } - - // extract nested arrays - if (array_key_exists(JsonObjectExtractor::JSON_OBJECT_ARRAY, $jsonObjectArray)) { - $this->extractJsonArrays($jsonMetadata, $jsonObjectArray[JsonObjectExtractor::JSON_OBJECT_ARRAY]); - } - - // extract nested - if (array_key_exists(JsonObjectExtractor::JSON_OBJECT_OBJ_NAME, $jsonObjectArray)) { - foreach ($jsonObjectArray[JsonObjectExtractor::JSON_OBJECT_OBJ_NAME] as $jsonObject) { - $nestedJsonElement = $this->extractJsonObject($jsonObject); - $jsonMetadata[] = $nestedJsonElement; - } - } - - // a jsonObject specified in xml must contain corresponding metadata for the object - if (empty($jsonMetadata)) { - throw new \Exception("must specificy jsonObject metadata if declaration is used"); - } - - return new JsonElement( - $jsonDefKey, - $dataType, - JsonObjectExtractor::JSON_OBJECT_OBJ_NAME, - $jsonObjectArray[JsonDefinitionObjectHandler::ENTITY_OPERATION_REQUIRED] ?? null, - $nestedJsonElements, - $jsonMetadata - ); - } - - /** - * Creates and Adds relevant JsonElements from json entries defined within jsonObject array - * - * @param array &$jsonMetadata - * @param array $jsonEntryArray - * @return void - */ - private function extractJsonEntries(&$jsonMetadata, $jsonEntryArray) - { - foreach ($jsonEntryArray as $jsonEntryType) { - $jsonMetadata[] = new JsonElement( - $jsonEntryType[JsonDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_KEY], - $jsonEntryType[JsonDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_VALUE], - JsonDefinitionObjectHandler::ENTITY_OPERATION_ENTRY, - $jsonEntryType[JsonDefinitionObjectHandler::ENTITY_OPERATION_REQUIRED] ?? null - ); - } - } - - /** - * Creates and Adds relevant JsonElements from json arrays defined within jsonObject array - * - * @param array &$jsonArrayData - * @param array $jsonArrayArray - * @return void - */ - private function extractJsonArrays(&$jsonArrayData, $jsonArrayArray) - { - foreach ($jsonArrayArray as $jsonEntryType) { - $jsonElementValue = - $jsonEntryType[JsonDefinitionObjectHandler::ENTITY_OPERATION_ARRAY_VALUE][0] - [JsonObjectExtractor::JSON_OBJECT_ARRAY_VALUE] ?? null; - - $nestedJsonElements = []; - if (array_key_exists(JsonObjectExtractor::JSON_OBJECT_OBJ_NAME, $jsonEntryType)) { - //add the key to reference this object later - $jsonObjectKeyedArray = $jsonEntryType[JsonObjectExtractor::JSON_OBJECT_OBJ_NAME][0]; - $jsonObjectKeyedArray[JsonObjectExtractor::JSON_OBJECT_KEY] = - $jsonEntryType[JsonDefinitionObjectHandler::ENTITY_OPERATION_ARRAY_KEY]; - $jsonElement = $this->extractJsonObject($jsonObjectKeyedArray); - $jsonElementValue = $jsonElement->getValue(); - $nestedJsonElements[$jsonElement->getValue()] = $jsonElement; - } - $jsonArrayData[] = new JsonElement( - $jsonEntryType[JsonDefinitionObjectHandler::ENTITY_OPERATION_ARRAY_KEY], - $jsonElementValue, - JsonDefinitionObjectHandler::ENTITY_OPERATION_ARRAY, - $jsonEntryType[JsonDefinitionObjectHandler::ENTITY_OPERATION_REQUIRED] ?? null, - $nestedJsonElements - ); - } - } -} diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Util/OperationElementExtractor.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Util/OperationElementExtractor.php new file mode 100644 index 000000000..6f2e3e447 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Util/OperationElementExtractor.php @@ -0,0 +1,139 @@ +extractOperationField( + $operationElements, + $operationElementArray[OperationElementExtractor::OPERATION_OBJECT_ENTRY] + ); + } + + // extract nested arrays + if (array_key_exists(OperationElementExtractor::OPERATION_OBJECT_ARRAY, $operationElementArray)) { + $this->extractOperationArray( + $operationElements, + $operationElementArray[OperationElementExtractor::OPERATION_OBJECT_ARRAY] + ); + } + + // extract nested + if (array_key_exists(OperationElementExtractor::OPERATION_OBJECT_OBJ_NAME, $operationElementArray)) { + foreach ($operationElementArray[OperationElementExtractor::OPERATION_OBJECT_OBJ_NAME] as $operationObject) { + $nestedOperationElement = $this->extractOperationElement($operationObject); + $operationElements[] = $nestedOperationElement; + } + } + + // a dataObject specified in xml must contain corresponding metadata for the object + if (empty($operationElements)) { + throw new \Exception("must specify dataObject metadata if declaration is used"); + } + + return new OperationElement( + $operationDefKey, + $dataType, + OperationElementExtractor::OPERATION_OBJECT_OBJ_NAME, + $operationElementArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_REQUIRED] ?? null, + $nestedOperationElements, + $operationElements + ); + } + + /** + * Creates and Adds relevant DataElements from data entries defined within dataObject array + * + * @param array &$operationElements + * @param array $operationFieldArray + * @return void + */ + private function extractOperationField(&$operationElements, $operationFieldArray) + { + foreach ($operationFieldArray as $operationFieldType) { + $operationElements[] = new OperationElement( + $operationFieldType[OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_KEY], + $operationFieldType[OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_VALUE], + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY, + $operationFieldType[OperationDefinitionObjectHandler::ENTITY_OPERATION_REQUIRED] ?? null + ); + } + } + + /** + * Creates and Adds relevant DataElements from data arrays defined within dataObject array + * + * @param array &$operationArrayData + * @param array $operationArrayArray + * @return void + */ + private function extractOperationArray(&$operationArrayData, $operationArrayArray) + { + foreach ($operationArrayArray as $operationFieldType) { + $operationElementValue = + $operationFieldType[OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY_VALUE][0] + [OperationElementExtractor::OPERATION_OBJECT_ARRAY_VALUE] ?? null; + + $nestedOperationElements = []; + if (array_key_exists(OperationElementExtractor::OPERATION_OBJECT_OBJ_NAME, $operationFieldType)) { + //add the key to reference this object later + $operationObjectKeyedArray = $operationFieldType + [OperationElementExtractor::OPERATION_OBJECT_OBJ_NAME][0]; + $operationObjectKeyedArray[OperationElementExtractor::OPERATION_OBJECT_KEY] = + $operationFieldType[OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY_KEY]; + $operationElement = $this->extractOperationElement($operationObjectKeyedArray); + $operationElementValue = $operationElement->getValue(); + $nestedOperationElements[$operationElement->getValue()] = $operationElement; + } + $operationArrayData[] = new OperationElement( + $operationFieldType[OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY_KEY], + $operationElementValue, + OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY, + $operationFieldType[OperationDefinitionObjectHandler::ENTITY_OPERATION_REQUIRED] ?? null, + $nestedOperationElements + ); + } + } +} diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd b/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd index 2c4fcabc7..eadc0e396 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd @@ -6,28 +6,31 @@ - - + + + - + + + - + - + - + @@ -44,7 +47,7 @@ - + @@ -73,4 +76,11 @@ + + + + + + + \ No newline at end of file diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd b/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd index 24a7eed6b..c0494c911 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd @@ -30,6 +30,16 @@ + + + + + + + + + + diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/etc/sample.xml b/src/Magento/FunctionalTestingFramework/DataGenerator/etc/sample.xml deleted file mode 100644 index 722e5e6e7..000000000 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/etc/sample.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - simpleConfiguration - regressionConfiguration - - asdf - - - - AssertNumberOne - AssertNumberTwo - - - FirstNameData - - - AssertNumberOne - AssertNumberTwo - AssertNumberThree - - - \ No newline at end of file diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionGroupObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionGroupObject.php index e51117e36..a88bcae61 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionGroupObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionGroupObject.php @@ -54,9 +54,10 @@ public function __construct($name, $arguments, $actions) * Gets the ordered steps including merged waits * * @param array $arguments + * @param string $actionReferenceKey * @return array */ - public function getSteps($arguments) + public function getSteps($arguments, $actionReferenceKey) { $mergeUtil = new ActionMergeUtil(); $args = $this->arguments; @@ -65,7 +66,7 @@ public function getSteps($arguments) $args = array_merge($args, $arguments); } - return $mergeUtil->resolveActionSteps($this->getResolvedActionsWithArgs($args), true); + return $mergeUtil->resolveActionSteps($this->getResolvedActionsWithArgs($args, $actionReferenceKey), true); } /** @@ -73,17 +74,18 @@ public function getSteps($arguments) * action objects with proper argument.field references. * * @param array $arguments + * @param string $actionReferenceKey * @return array */ - private function getResolvedActionsWithArgs($arguments) + private function getResolvedActionsWithArgs($arguments, $actionReferenceKey) { $resolvedActions = []; $regexPattern = '/{{([\w]+)/'; foreach ($this->parsedActions as $action) { $varAttributes = array_intersect(self::VAR_ATTRIBUTES, array_keys($action->getCustomActionAttributes())); + $newActionAttributes = []; if (!empty($varAttributes)) { - $newActionAttributes = []; // 1 check to see if we have pertinent var foreach ($varAttributes as $varAttribute) { $attributeValue = $action->getCustomActionAttributes()[$varAttribute]; @@ -98,18 +100,14 @@ private function getResolvedActionsWithArgs($arguments) $matches ); } - - $resolvedActions[$action->getMergeKey()] = new ActionObject( - $action->getMergeKey(), - $action->getType(), - array_merge($action->getCustomActionAttributes(), $newActionAttributes), - $action->getLinkedAction(), - $action->getOrderOffset() - ); - } else { - // add action here if we do not see any userInput in this particular action - $resolvedActions[$action->getMergeKey()] = $action; } + $resolvedActions[$action->getMergeKey() . $actionReferenceKey] = new ActionObject( + $action->getMergeKey() . $actionReferenceKey, + $action->getType(), + array_merge($action->getCustomActionAttributes(), $newActionAttributes), + $action->getLinkedAction(), + $action->getOrderOffset() + ); } return $resolvedActions; diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php index 767012694..cc473075e 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php @@ -392,11 +392,12 @@ private function matchParameterReferences($reference, $parameters) ); } - //Attempt to Resolve {{data}} references to actual output. + //Attempt to Resolve {{data}} references to actual output. Trim parameter for whitespace before processing it. //If regex matched it means that it's either a 'StringLiteral' or $key.data$/$$key.data$$ reference. //Else assume it's a normal {{data.key}} reference and recurse through findAndReplace $resolvedParameters = []; foreach ($parameters as $parameter) { + $parameter = trim($parameter); preg_match_all("/[$'][\w.$]+[$']/", $parameter, $match); if (!empty($match[0])) { $resolvedParameters[] = ltrim(rtrim($parameter, "'"), "'"); diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/ActionMergeUtil.php b/src/Magento/FunctionalTestingFramework/Test/Util/ActionMergeUtil.php index 133ace223..ad38df07f 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/ActionMergeUtil.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/ActionMergeUtil.php @@ -77,7 +77,7 @@ private function resolveActionGroups($mergedSteps) $mergedStep->getCustomActionAttributes()[ActionObjectExtractor::ACTION_GROUP_REF] ); $args = $mergedStep->getCustomActionAttributes()[ActionObjectExtractor::ACTION_GROUP_ARGUMENTS] ?? null; - $actionsToMerge = $actionGroup->getSteps($args); + $actionsToMerge = $actionGroup->getSteps($args, $key); $newOrderedList = $newOrderedList + $actionsToMerge; } else { $newOrderedList[$key] = $mergedStep; diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd index 94d6d5a7a..edabbcbea 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd @@ -105,7 +105,6 @@ - @@ -375,11 +374,12 @@ + - + @@ -394,6 +394,7 @@ + @@ -616,38 +617,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Magento/FunctionalTestingFramework/Util/ApiClientUtil.php b/src/Magento/FunctionalTestingFramework/Util/ApiClientUtil.php deleted file mode 100644 index 0db7075b8..000000000 --- a/src/Magento/FunctionalTestingFramework/Util/ApiClientUtil.php +++ /dev/null @@ -1,117 +0,0 @@ -apiPath = $apiPath; - $this->headers = $headers; - $this->apiOperation = $apiOperation; - $this->jsonBody = $jsonBody; - - $this->curl = curl_init(); - } - - /** - * Submits the request based on object properties - * - * @param bool $verbose - * @return string|bool - * @throws \Exception - */ - public function submit($verbose = false) - { - $url = null; - - if ($this->jsonBody) { - curl_setopt($this->curl, CURLOPT_POSTFIELDS, $this->jsonBody); - } - - curl_setopt($this->curl, CURLOPT_VERBOSE, $verbose); - - if ((getenv('MAGENTO_RESTAPI_SERVER_HOST') !== false) - && (getenv('MAGENTO_RESTAPI_SERVER_HOST') !== '') ) { - $url = getenv('MAGENTO_RESTAPI_SERVER_HOST'); - } else { - $url = getenv('MAGENTO_BASE_URL'); - } - - if ((getenv('MAGENTO_RESTAPI_SERVER_PORT') !== false) - && (getenv('MAGENTO_RESTAPI_SERVER_PORT') !== '')) { - $url .= ':' . getenv('MAGENTO_RESTAPI_SERVER_PORT'); - } - - curl_setopt_array($this->curl, [ - CURLOPT_RETURNTRANSFER => 1, - CURLOPT_HTTPHEADER => $this->headers, - CURLOPT_CUSTOMREQUEST => $this->apiOperation, - CURLOPT_URL => $url . $this->apiPath - ]); - - $response = curl_exec($this->curl); - $http_code = curl_getinfo($this->curl, CURLINFO_HTTP_CODE); - - if ($response === false || !in_array($http_code, ApiClientUtil::SUCCESSFUL_HTTP_CODES)) { - throw new \Exception('API returned response code: ' . $http_code . ' Response:' . $response); - } - - curl_close($this->curl); - - return $response; - } -} diff --git a/src/Magento/FunctionalTestingFramework/Util/Protocol/CurlInterface.php b/src/Magento/FunctionalTestingFramework/Util/Protocol/CurlInterface.php new file mode 100644 index 000000000..792e0c6d4 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/Protocol/CurlInterface.php @@ -0,0 +1,57 @@ + CURLOPT_TIMEOUT, + 'maxredirects' => CURLOPT_MAXREDIRS, + 'proxy' => CURLOPT_PROXY, + 'ssl_cert' => CURLOPT_SSLCERT, + 'userpwd' => CURLOPT_USERPWD, + ]; + + /** + * Array of CURL options. + * + * @var array + */ + protected $options = []; + + /** + * A list of successful HTTP responses that will not trigger an exception. + * + * @var int[] SUCCESSFUL_HTTP_CODES + */ + const SUCCESSFUL_HTTP_CODES = [200, 201, 202, 203, 204, 205]; + + /** + * Apply current configuration array to curl resource. + * + * @return $this + */ + protected function applyConfig() + { + // apply additional options to cURL + foreach ($this->options as $option => $value) { + curl_setopt($this->getResource(), $option, $value); + } + + if (empty($this->config)) { + return $this; + } + foreach (array_keys($this->config) as $param) { + if (array_key_exists($param, $this->allowedParams)) { + curl_setopt($this->getResource(), $this->allowedParams[$param], $this->config[$param]); + } + } + return $this; + } + + /** + * Set array of additional cURL options. + * + * @param array $options + * @return $this + */ + public function setOptions(array $options = []) + { + $this->options = $options; + return $this; + } + + /** + * Add additional option to cURL. + * + * @param int $option + * @param int|string|bool|array $value + * @return $this + */ + public function addOption($option, $value) + { + $this->options[$option] = $value; + return $this; + } + + /** + * Set the configuration array for the adapter. + * + * @param array $config + * @return $this + */ + public function setConfig(array $config = []) + { + $this->config = $config; + return $this; + } + + /** + * Send request to the remote server. + * + * @param string $url + * @param array $body + * @param string $method + * @param array $headers + * @return void + * @throws TestFrameworkException + */ + public function write($url, $body = [], $method = CurlInterface::POST, $headers = []) + { + $this->applyConfig(); + $options = [ + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_COOKIEFILE => '', + CURLOPT_HTTPHEADER => $headers, + CURLOPT_SSL_VERIFYPEER => false, + ]; + switch ($method) { + case CurlInterface::POST: + $options[CURLOPT_POST] = true; + $options[CURLOPT_POSTFIELDS] = $body; + break; + case CurlInterface::PUT: + $options[CURLOPT_CUSTOMREQUEST] = self::PUT; + $options[CURLOPT_POSTFIELDS] = $body; + break; + case CurlInterface::DELETE: + $options[CURLOPT_CUSTOMREQUEST] = self::DELETE; + break; + case CurlInterface::GET: + $options[CURLOPT_HTTPGET] = true; + break; + default: + throw new TestFrameworkException("Undefined curl method: $method"); + } + + curl_setopt_array($this->getResource(), $options); + } + + /** + * Read response from server. + * + * @param string $successRegex + * @param string $returnRegex + * @return string + * @throws TestFrameworkException + */ + public function read($successRegex = null, $returnRegex = null) + { + $response = curl_exec($this->getResource()); + + if ($response === false) { + throw new TestFrameworkException(curl_error($this->getResource())); + } + $http_code = $this->getInfo(CURLINFO_HTTP_CODE); + if (!in_array($http_code, self::SUCCESSFUL_HTTP_CODES)) { + throw new TestFrameworkException('Error HTTP response code: ' . $http_code . ' Response:' . $response); + } + + return $response; + } + + /** + * Close the connection to the server. + * + * @return void + */ + public function close() + { + curl_close($this->getResource()); + $this->resource = null; + } + + /** + * Returns a cURL handle on success. + * + * @return resource + */ + protected function getResource() + { + if ($this->resource === null) { + $this->resource = curl_init(); + } + return $this->resource; + } + + /** + * Get last error number. + * + * @return int + */ + public function getErrno() + { + return curl_errno($this->getResource()); + } + + /** + * Get string with last error for the current session. + * + * @return string + */ + public function getError() + { + return curl_error($this->getResource()); + } + + /** + * Get information regarding a specific transfer. + * + * @param int $opt CURLINFO option + * @return string|array + */ + public function getInfo($opt = 0) + { + return curl_getinfo($this->getResource(), $opt); + } + + /** + * Provide curl_multi_* requests support. + * + * @param array $urls + * @param array $options + * @return array + */ + public function multiRequest(array $urls, array $options = []) + { + $handles = []; + $result = []; + + $multiHandle = curl_multi_init(); + + foreach ($urls as $key => $url) { + $handles[$key] = curl_init(); + curl_setopt($handles[$key], CURLOPT_URL, $url); + curl_setopt($handles[$key], CURLOPT_HEADER, 0); + curl_setopt($handles[$key], CURLOPT_RETURNTRANSFER, 1); + if (!empty($options)) { + curl_setopt_array($handles[$key], $options); + } + curl_multi_add_handle($multiHandle, $handles[$key]); + } + $process = null; + do { + curl_multi_exec($multiHandle, $process); + usleep(100); + } while ($process > 0); + + foreach ($handles as $key => $handle) { + $result[$key] = curl_multi_getcontent($handle); + curl_multi_remove_handle($multiHandle, $handle); + } + curl_multi_close($multiHandle); + return $result; + } + + /** + * Extract the response code from a response string. + * + * @param string $responseStr + * @return int + */ + public static function extractCode($responseStr) + { + preg_match("|^HTTP/[\d\.x]+ (\d+)|", $responseStr, $m); + + if (isset($m[1])) { + return (int)$m[1]; + } else { + return false; + } + } +} diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index 2798329bc..971688fcf 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -18,6 +18,8 @@ class TestGenerator { + const REQUIRED_ENTITY_REFERENCE = 'persistedKey'; + /** * Path to the export dir. * @@ -212,7 +214,7 @@ private function generateUseStatementsPhp() $useStatementsPhp = "use Magento\FunctionalTestingFramework\AcceptanceTester;\n"; $useStatementsPhp .= "use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler;\n"; - $useStatementsPhp .= "use Magento\FunctionalTestingFramework\DataGenerator\Api\EntityApiHandler;\n"; + $useStatementsPhp .= "use Magento\FunctionalTestingFramework\DataGenerator\Persist\DataPersistenceHandler;\n"; $useStatementsPhp .= "use Magento\FunctionalTestingFramework\DataGenerator\Objects\EntityDataObject;\n"; $allureStatements = [ @@ -395,7 +397,7 @@ private function generateStepsPhp($stepsObject, $stepsData, $hookObject = false) if (isset($customActionAttributes['selectorArray'])) { $selector = $customActionAttributes['selectorArray']; } elseif (isset($customActionAttributes['selector'])) { - $selector = $this->wrapWithDoubleQuotes($customActionAttributes['selector']); + $selector = $this->addUniquenessFunctionCall($customActionAttributes['selector']); } if (isset($customActionAttributes['selector1'])) { @@ -487,62 +489,54 @@ private function generateStepsPhp($stepsObject, $stepsData, $hookObject = false) foreach ($customActionAttributes as $customAttribute) { if (is_array($customAttribute) && $customAttribute['nodeName'] = 'required-entity') { if ($hookObject) { - $requiredEntities [] = "\$this->" . $customAttribute['name'] . "->getName() => " . - "\$this->" . $customAttribute['name'] . "->getType()"; - $requiredEntityObjects [] = '$this->' . $customAttribute['name']; + $requiredEntities [] = "\$this->" . $customAttribute[self::REQUIRED_ENTITY_REFERENCE] . + "->getName() => " . "\$this->" . $customAttribute[self::REQUIRED_ENTITY_REFERENCE] . + "->getType()"; + $requiredEntityObjects [] = '$this->' . $customAttribute + [self::REQUIRED_ENTITY_REFERENCE]; } else { - $requiredEntities [] = "\$" . $customAttribute['name'] . "->getName() => " - . "\$" . $customAttribute['name'] . "->getType()"; - $requiredEntityObjects [] = '$' . $customAttribute['name']; + $requiredEntities [] = "\$" . $customAttribute[self::REQUIRED_ENTITY_REFERENCE] + . "->getName() => " . "\$" . $customAttribute[self::REQUIRED_ENTITY_REFERENCE] . + "->getType()"; + $requiredEntityObjects [] = '$' . $customAttribute[self::REQUIRED_ENTITY_REFERENCE]; } } } - //If required-entities are defined, reassign dataObject to not overwrite the static definition. - //Also, EntityApiHandler needs to be defined with customData array. - if (!empty($requiredEntities)) { - $testSteps .= sprintf( - "\t\t$%s = new EntityDataObject($%s->getName(), $%s->getType(), $%s->getData() - , array_merge($%s->getLinkedEntities(), [%s]), $%s->getUniquenessData());\n", - $entity, - $entity, - $entity, - $entity, - $entity, - implode(", ", $requiredEntities), + + if ($hookObject) { + $createEntityFunctionCall = sprintf("\t\t\$this->%s->createEntity(", $key); + $dataPersistenceHandlerFunctionCall = sprintf( + "\t\t\$this->%s = new DataPersistenceHandler($%s", + $key, + $entity + ); + } else { + $createEntityFunctionCall = sprintf("\t\t\$%s->createEntity(", $key); + $dataPersistenceHandlerFunctionCall = sprintf( + "\t\t$%s = new DataPersistenceHandler($%s", + $key, $entity ); + } - if ($hookObject) { - $testSteps .= sprintf( - "\t\t\$this->%s = new EntityApiHandler($%s, [%s]);\n", - $key, - $entity, - implode(', ', $requiredEntityObjects) - ); - $testSteps .= sprintf("\t\t\$this->%s->createEntity();\n", $key); - } else { - $testSteps .= sprintf( - "\t\t$%s = new EntityApiHandler($%s, [%s]);\n", - $key, - $entity, - implode(', ', $requiredEntityObjects) - ); - $testSteps .= sprintf("\t\t$%s->createEntity();\n", $key); - } + if (isset($customActionAttributes['storeCode'])) { + $createEntityFunctionCall .= sprintf("\"%s\");\n", $customActionAttributes['storeCode']); } else { - if ($hookObject) { - $testSteps .= sprintf( - "\t\t\$this->%s = new EntityApiHandler($%s);\n", - $key, - $entity - ); - $testSteps .= sprintf("\t\t\$this->%s->createEntity();\n", $key); - } else { - $testSteps .= sprintf("\t\t$%s = new EntityApiHandler($%s);\n", $key, $entity); - $testSteps .= sprintf("\t\t$%s->createEntity();\n", $key); - } + $createEntityFunctionCall .= ");\n"; } + //If required-entities are defined, reassign dataObject to not overwrite the static definition. + //Also, DataPersistenceHandler needs to be defined with customData array. + if (!empty($requiredEntities)) { + $dataPersistenceHandlerFunctionCall .= sprintf( + ", [%s]);\n", + implode(', ', $requiredEntityObjects) + ); + } else { + $dataPersistenceHandlerFunctionCall .= ");\n"; + } + $testSteps .= $dataPersistenceHandlerFunctionCall; + $testSteps .= $createEntityFunctionCall; break; case "deleteData": $key = $customActionAttributes['createDataKey']; @@ -559,33 +553,6 @@ private function generateStepsPhp($stepsObject, $stepsData, $hookObject = false) $testSteps .= sprintf("\t\t$%s->deleteEntity();\n", $key); } break; - case "entity": - $entityData = '['; - foreach ($stepsData[$customActionAttributes['name']] as $dataKey => $dataValue) { - $variableReplace = $this->resolveTestVariable($dataValue, true); - $entityData .= sprintf("\"%s\" => \"%s\", ", $dataKey, $variableReplace); - } - $entityData .= ']'; - if ($hookObject) { - // no uniqueness attributes for data allowed within entity defined in cest. - $testSteps .= sprintf( - "\t\t\$this->%s = new EntityDataObject(\"%s\",\"%s\",%s,null,null);\n", - $customActionAttributes['name'], - $customActionAttributes['name'], - $customActionAttributes['type'], - $entityData - ); - } else { - // no uniqueness attributes for data allowed within entity defined in cest. - $testSteps .= sprintf( - "\t\t$%s = new EntityDataObject(\"%s\",\"%s\",%s,null,null);\n", - $customActionAttributes['name'], - $customActionAttributes['name'], - $customActionAttributes['type'], - $entityData - ); - } - break; case "dontSeeCurrentUrlEquals": case "dontSeeCurrentUrlMatches": case "seeInPopup": @@ -805,7 +772,7 @@ private function resolveTestVariable($inputString, $quoteBreak = false) $replaced = false; // Check for Cest-scope variables first, stricter regex match. - preg_match_all("/\\$\\$[\w.]+\\$\\$/", $outputString, $matches); + preg_match_all("/\\$\\$[\w.\[\]]+\\$\\$/", $outputString, $matches); foreach ($matches[0] as $match) { $replacement = null; $variable = $this->stripAndSplitReference($match, '$$'); @@ -824,7 +791,7 @@ private function resolveTestVariable($inputString, $quoteBreak = false) } // Check Test-scope variables - preg_match_all("/\\$[\w.]+\\$/", $outputString, $matches); + preg_match_all("/\\$[\w.\[\]]+\\$/", $outputString, $matches); foreach ($matches[0] as $match) { $replacement = null; $variable = $this->stripAndSplitReference($match, '$'); @@ -873,7 +840,7 @@ private function generateHooksPhp($hookObjects) foreach ($hookObject->getActions() as $step) { if ($step->getType() == "createData") { $hooks .= "\t/**\n"; - $hooks .= sprintf("\t * @var EntityApiHandler $%s;\n", $step->getMergeKey()); + $hooks .= sprintf("\t * @var DataPersistenceHandler $%s;\n", $step->getMergeKey()); $hooks .= "\t */\n"; $hooks .= sprintf("\tprotected $%s;\n\n", $step->getMergeKey()); $createData = true; @@ -1083,7 +1050,7 @@ private function wrapWithDoubleQuotes($input) } /** - * Strip beginning and ending quotes of input string. + * Strip beginning and ending double quotes of input string. * * @param string $input * @return string @@ -1093,10 +1060,10 @@ private function stripWrappedQuotes($input) if (empty($input)) { return ''; } - if (substr($input, 0, 1) === '"' || substr($input, 0, 1) === "'") { + if (substr($input, 0, 1) === '"') { $input = substr($input, 1); } - if (substr($input, -1, 1) === '"' || substr($input, -1, 1) === "'") { + if (substr($input, -1, 1) === '"') { $input = substr($input, 0, -1); } return $input;