Skip to content

Commit 97bd6b8

Browse files
committed
Merge remote-tracking branch 'origin/sprint-develop' into MQE-326-multi-stores
# Conflicts: # src/Magento/FunctionalTestingFramework/DataGenerator/Api/ApiExecutor.php # src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/JsonDefinitionObjectHandler.php # src/Magento/FunctionalTestingFramework/DataGenerator/Objects/DataElement.php # src/Magento/FunctionalTestingFramework/DataGenerator/Util/JsonObjectExtractor.php # src/Magento/FunctionalTestingFramework/Util/TestGenerator.php
2 parents 423534c + 36bc43c commit 97bd6b8

File tree

18 files changed

+1044
-73
lines changed

18 files changed

+1044
-73
lines changed
Lines changed: 368 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,368 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
namespace Magento\FunctionalTestingFramework\DataGenerator\Api;
8+
9+
use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler;
10+
use Magento\FunctionalTestingFramework\DataGenerator\Handlers\JsonDefinitionObjectHandler;
11+
use Magento\FunctionalTestingFramework\DataGenerator\Objects\EntityDataObject;
12+
use Magento\FunctionalTestingFramework\DataGenerator\Objects\JsonDefinition;
13+
use Magento\FunctionalTestingFramework\DataGenerator\Objects\JsonElement;
14+
use Magento\FunctionalTestingFramework\DataGenerator\Util\JsonObjectExtractor;
15+
use Magento\FunctionalTestingFramework\Util\ApiClientUtil;
16+
use Magento\Setup\Exception;
17+
18+
/**
19+
* Class ApiExecutor
20+
*/
21+
class ApiExecutor
22+
{
23+
const PRIMITIVE_TYPES = ['string', 'boolean', 'integer', 'double', 'array'];
24+
const EXCEPTION_REQUIRED_DATA = "%s of key \" %s\" in \"%s\" is required by metadata, but was not provided.";
25+
26+
/**
27+
* Describes the operation for the executor ('create','update','delete')
28+
*
29+
* @var string
30+
*/
31+
private $operation;
32+
33+
/**
34+
* The entity object data being created, updated, or deleted.
35+
*
36+
* @var EntityDataObject $entityObject
37+
*/
38+
private $entityObject;
39+
40+
/**
41+
* The json definitions used to map the operation.
42+
*
43+
* @var JsonDefinition $jsonDefinition
44+
*/
45+
private $jsonDefinition;
46+
47+
/**
48+
* The array of dependentEntities this class can be given. When finding linked entities, APIExecutor
49+
* uses this repository before looking for static data.
50+
*
51+
* @var array
52+
*/
53+
private $dependentEntities = [];
54+
55+
/**
56+
* The array of entity name and number of objects being created,
57+
* we don't need to track objects in update and delete operations.
58+
*
59+
* @var array
60+
*/
61+
private static $entitySequences = [];
62+
63+
/**
64+
* ApiSubObject constructor.
65+
* @param string $operation
66+
* @param EntityDataObject $entityObject
67+
* @param array $dependentEntities
68+
*/
69+
public function __construct($operation, $entityObject, $dependentEntities = null)
70+
{
71+
$this->operation = $operation;
72+
$this->entityObject = $entityObject;
73+
if ($dependentEntities != null) {
74+
foreach ($dependentEntities as $entity) {
75+
$this->dependentEntities[$entity->getName()] = $entity;
76+
}
77+
}
78+
79+
$this->jsonDefinition = JsonDefinitionObjectHandler::getInstance()->getJsonDefinition(
80+
$this->operation,
81+
$this->entityObject->getType()
82+
);
83+
}
84+
85+
/**
86+
* Executes an api request based on parameters given by constructor.
87+
*
88+
* @return string | null
89+
*/
90+
public function executeRequest()
91+
{
92+
$apiClientUrl = $this->jsonDefinition->getApiUrl();
93+
94+
$matchedParams = [];
95+
preg_match_all("/[{](.+?)[}]/", $apiClientUrl, $matchedParams);
96+
97+
if (!empty($matchedParams)) {
98+
foreach ($matchedParams[0] as $paramKey => $paramValue) {
99+
$param = $this->entityObject->getDataByName(
100+
$matchedParams[1][$paramKey],
101+
EntityDataObject::CEST_UNIQUE_VALUE
102+
);
103+
$apiClientUrl = str_replace($paramValue, $param, $apiClientUrl);
104+
}
105+
}
106+
107+
$authorization = $this->jsonDefinition->getAuth();
108+
$headers = $this->jsonDefinition->getHeaders();
109+
110+
if ($authorization) {
111+
$headers[] = $this->getAuthorizationHeader($authorization);
112+
}
113+
114+
$jsonBody = $this->getEncodedJsonString();
115+
116+
$apiClientUtil = new ApiClientUtil(
117+
$apiClientUrl,
118+
$headers,
119+
$this->jsonDefinition->getApiMethod(),
120+
empty($jsonBody) ? null : $jsonBody
121+
);
122+
123+
return $apiClientUtil->submit();
124+
}
125+
126+
/**
127+
* Returns the authorization token needed for some requests via REST call.
128+
*
129+
* @param string $authUrl
130+
* @return string
131+
*/
132+
private function getAuthorizationHeader($authUrl)
133+
{
134+
$headers = ['Content-Type: application/json'];
135+
$authCreds = [
136+
'username' => getenv('MAGENTO_ADMIN_USERNAME'),
137+
'password' => getenv('MAGENTO_ADMIN_PASSWORD')
138+
];
139+
140+
$apiClientUtil = new ApiClientUtil($authUrl, $headers, 'POST', json_encode($authCreds));
141+
$token = $apiClientUtil->submit();
142+
$authHeader = 'Authorization: Bearer ' . str_replace('"', "", $token);
143+
144+
return $authHeader;
145+
}
146+
147+
/**
148+
* This function returns an array which is structurally equal to the json which is needed by the web api for
149+
* entity creation. The function retrieves an array describing the json metadata and traverses any dependencies
150+
* recursively forming an array which represents the json structure for the api of the desired type.
151+
*
152+
* @param EntityDataObject $entityObject
153+
* @param array $jsonArrayMetadata
154+
* @return array
155+
* @throws \Exception
156+
*/
157+
private function convertJsonArray($entityObject, $jsonArrayMetadata)
158+
{
159+
$jsonArray = [];
160+
self::incrementSequence($entityObject->getName());
161+
162+
foreach ($jsonArrayMetadata as $jsonElement) {
163+
if ($jsonElement->getType() == JsonObjectExtractor::JSON_OBJECT_OBJ_NAME) {
164+
$entityObj = $this->resolveJsonObjectAndEntityData($entityObject, $jsonElement->getValue());
165+
$jsonArray[$jsonElement->getValue()] =
166+
$this->convertJsonArray($entityObj, $jsonElement->getNestedMetadata());
167+
continue;
168+
}
169+
170+
$jsonElementType = $jsonElement->getValue();
171+
172+
if (in_array($jsonElementType, ApiExecutor::PRIMITIVE_TYPES)) {
173+
$elementData = $entityObject->getDataByName(
174+
$jsonElement->getKey(),
175+
EntityDataObject::CEST_UNIQUE_VALUE
176+
);
177+
178+
// If data was defined at all, attempt to put it into JSON body
179+
// If data was not defined, and element is required, throw exception
180+
// If no data is defined, don't input defaults per primitive into JSON for the data
181+
if ($elementData != null) {
182+
if (array_key_exists($jsonElement->getKey(), $entityObject->getUniquenessData())) {
183+
$uniqueData = $entityObject->getUniquenessDataByName($jsonElement->getKey());
184+
if ($uniqueData === 'suffix') {
185+
$elementData .= (string)self::getSequence($entityObject->getName());
186+
} else {
187+
$elementData = (string)self::getSequence($entityObject->getName()) . $elementData;
188+
}
189+
}
190+
$jsonArray[$jsonElement->getKey()] = $this->castValue($jsonElementType, $elementData);
191+
192+
} elseif ($jsonElement->getRequired()) {
193+
throw new \Exception(sprintf(
194+
ApiExecutor::EXCEPTION_REQUIRED_DATA,
195+
$jsonElement->getType(),
196+
$jsonElement->getKey(),
197+
$this->entityObject->getName()
198+
));
199+
}
200+
} else {
201+
$entityNamesOfType = $entityObject->getLinkedEntitiesOfType($jsonElementType);
202+
203+
// If an element is required by metadata, but was not provided in the entity, throw an exception
204+
if ($jsonElement->getRequired() && $entityNamesOfType == null) {
205+
throw new \Exception(sprintf(
206+
ApiExecutor::EXCEPTION_REQUIRED_DATA,
207+
$jsonElement->getType(),
208+
$jsonElement->getKey(),
209+
$this->entityObject->getName()
210+
));
211+
}
212+
foreach ($entityNamesOfType as $entityName) {
213+
$jsonDataSubArray = $this->resolveNonPrimitiveElement($entityName, $jsonElement);
214+
215+
if ($jsonElement->getType() == 'array') {
216+
$jsonArray[$jsonElement->getKey()][] = $jsonDataSubArray;
217+
} else {
218+
$jsonArray[$jsonElement->getKey()] = $jsonDataSubArray;
219+
}
220+
}
221+
}
222+
}
223+
224+
return $jsonArray;
225+
}
226+
227+
/**
228+
* This function does a comparison of the entity object being matched to the json element. If there is a mismatch in
229+
* type we attempt to use a nested entity, if the entities are properly matched, we simply return the object.
230+
*
231+
* @param EntityDataObject $entityObject
232+
* @param string $jsonElementValue
233+
* @return EntityDataObject|null
234+
*/
235+
private function resolveJsonObjectAndEntityData($entityObject, $jsonElementValue)
236+
{
237+
if ($jsonElementValue != $entityObject->getType()) {
238+
// if we have a mismatch attempt to retrieve linked data and return just the first linkage
239+
$linkName = $entityObject->getLinkedEntitiesOfType($jsonElementValue)[0];
240+
return DataObjectHandler::getInstance()->getObject($linkName);
241+
}
242+
243+
return $entityObject;
244+
}
245+
246+
/**
247+
* Resolves JsonObjects and pre-defined metadata (in other operation.xml file) referenced by the json metadata
248+
*
249+
* @param string $entityName
250+
* @param JsonElement $jsonElement
251+
* @return array
252+
*/
253+
private function resolveNonPrimitiveElement($entityName, $jsonElement)
254+
{
255+
$linkedEntityObj = $this->resolveLinkedEntityObject($entityName);
256+
257+
// in array case
258+
if (!empty($jsonElement->getNestedJsonElement($jsonElement->getValue()))
259+
&& $jsonElement->getType() == 'array'
260+
) {
261+
$jsonSubArray = $this->convertJsonArray(
262+
$linkedEntityObj,
263+
[$jsonElement->getNestedJsonElement($jsonElement->getValue())]
264+
);
265+
266+
return $jsonSubArray[$jsonElement->getValue()];
267+
}
268+
269+
$jsonMetadata = JsonDefinitionObjectHandler::getInstance()->getJsonDefinition(
270+
$this->operation,
271+
$linkedEntityObj->getType()
272+
)->getJsonMetadata();
273+
274+
return $this->convertJsonArray($linkedEntityObj, $jsonMetadata);
275+
}
276+
277+
/**
278+
* Method to wrap entity resolution, checks locally defined dependent entities first
279+
*
280+
* @param string $entityName
281+
* @return EntityDataObject
282+
*/
283+
private function resolveLinkedEntityObject($entityName)
284+
{
285+
// check our dependent entity list to see if we have this defined
286+
if (array_key_exists($entityName, $this->dependentEntities)) {
287+
return $this->dependentEntities[$entityName];
288+
}
289+
290+
return DataObjectHandler::getInstance()->getObject($entityName);
291+
}
292+
293+
/**
294+
* This function retrieves an array representative of json body for a request and returns it encoded as a string.
295+
*
296+
* @return string
297+
*/
298+
public function getEncodedJsonString()
299+
{
300+
$jsonMetadataArray = $this->convertJsonArray($this->entityObject, $this->jsonDefinition->getJsonMetadata());
301+
302+
return json_encode($jsonMetadataArray, JSON_PRETTY_PRINT);
303+
}
304+
305+
/**
306+
* Increment an entity's sequence number by 1.
307+
*
308+
* @param string $entityName
309+
* @return void
310+
*/
311+
private static function incrementSequence($entityName)
312+
{
313+
if (array_key_exists($entityName, self::$entitySequences)) {
314+
self::$entitySequences[$entityName]++;
315+
} else {
316+
self::$entitySequences[$entityName] = 1;
317+
}
318+
}
319+
320+
/**
321+
* Get the current sequence number for an entity.
322+
*
323+
* @param string $entityName
324+
* @return int
325+
*/
326+
private static function getSequence($entityName)
327+
{
328+
if (array_key_exists($entityName, self::$entitySequences)) {
329+
return self::$entitySequences[$entityName];
330+
}
331+
return 0;
332+
}
333+
334+
// @codingStandardsIgnoreStart
335+
336+
/**
337+
* This function takes a string value and its corresponding type and returns the string cast
338+
* into its the type passed.
339+
*
340+
* @param string $type
341+
* @param string $value
342+
* @return mixed
343+
*/
344+
private function castValue($type, $value)
345+
{
346+
$newVal = $value;
347+
348+
switch ($type) {
349+
case 'string':
350+
break;
351+
case 'integer':
352+
$newVal = (integer)$value;
353+
break;
354+
case 'boolean':
355+
if (strtolower($newVal) === 'false') {
356+
return false;
357+
}
358+
$newVal = (boolean)$value;
359+
break;
360+
case 'double':
361+
$newVal = (double)$value;
362+
break;
363+
}
364+
365+
return $newVal;
366+
}
367+
// @codingStandardsIgnoreEnd
368+
}

0 commit comments

Comments
 (0)