Skip to content

Commit 561fa41

Browse files
authored
MQE-259 (#5)
* MQE-228:Resolve bootstrap.php in Acceptance Test Framework - delete bootstrap.php file, relocated to tests project - update to include class def for TestGenerator.php * MQE-19: Tests and Framework packaging. * MQE-259 Schema Changes, DI Changes, ApiExecutor/EntityAPIHandler Changes. * MQE-259 * MQE-259 Test/Hook CustomData definition and methods complete. * MQE-259 Finished CreatedData required entity changes. Adjusted Entity. * MQE-259 Made getData exposed to be able to clone entitydataobjects, bugfixing for scoping. * MQE-259 In-test variable replacement. * MQE-259 Static Test Fixes Fixes to make the codesniffer tests be happy. * MQE-259 More Codesniffer fixes. * MQE-259 Test Fixes Still more test fixes to have the build be happy. * MQE-259 CR Fixes. Minor fix to TestGenerator as well, introduced during merge of static fixes. * MQE-259 CodeSniffer fix. * MQE-259 CR Use Statements appended regardless of data existense. * MQE-259 Static Fix * MQE 259 CR changes * MQE-259 More CR Changes * MQE-259 EntityAPI Static Test * Merge Static Test Fixes.
1 parent a23a169 commit 561fa41

File tree

9 files changed

+361
-61
lines changed

9 files changed

+361
-61
lines changed

etc/di.xml

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,10 @@
221221
<argument name="idAttributes" xsi:type="array">
222222
<item name="/config/cest" xsi:type="string">name</item>
223223
<item name="/config/cest/test" xsi:type="string">name</item>
224-
<item name="/config/cest/test/(acceptPopup|amOnPage|amOnUrl|appendField|assertArraySubset|attachFile|cancelPopup|checkOption|click|clickWithLeftButton|clickWithRightButton|closeTab|createData|deleteData|dontSee|dontSeeCheckboxIsChecked|dontSeeCookie|dontSeeCurrentUrlEquals|dontSeeCurrentUrlMatches|dontSeeElement|dontSeeElementInDOM|dontSeeInCurrentUrl|dontSeeInField|dontSeeInPageSource|dontSeeInSource|dontSeeInTitle|dontSeeLink|dontSeeOptionIsSelected|doubleClick|dragAndDrop|executeJS|fillField|formatMoney|grabAttributeFrom|grabCookie|grabFromCurrentUrl|grabMultiple|grabPageSource|grabTextFrom|grabValueFrom|loadSessionSnapshot|loginAsAdmin|makeScreenshot|maximizeWindow|moveBack|moveForward|moveMouseOver|openNewTab|pauseExecution|performOn|pressKey|reloadPage|resetCookie|resizeWindow|scrollTo|searchAndMultiSelectOption|see|seeCheckboxIsChecked|seeCookie|seeCurrentUrlEquals|seeCurrentUrlMatches|seeElement|seeElementInDOM|seeInCurrentUrl|seeInField|seeInFormFields|seeInPageSource|seeInPopup|seeInSource|seeInTitle|seeLink|seeNumberOfElements|seeOptionIsSelected|selectOption|setCookie|switchToIFrame|switchToNextTab|switchToPreviousTab|switchToWindow|typeInPopup|uncheckOption|unselectOption|wait|waitForAjaxLoad|waitForElement|waitForElementChange|waitForElementNotVisible|waitForElementVisible|waitForJS|waitForPageLoad|waitForText)" xsi:type="string">mergeKey</item>
224+
<item name="/config/cest/test/entity/data" xsi:type="string">key</item>
225+
<item name="/config/cest/test/entity/required-entity" xsi:type="string">key</item>
226+
<item name="/config/cest/test/createData/required-entity" xsi:type="string">name</item>
227+
<item name="/config/cest/test/(acceptPopup|amOnPage|amOnUrl|appendField|assertArraySubset|attachFile|cancelPopup|checkOption|click|clickWithLeftButton|clickWithRightButton|closeTab|createData|deleteData|dontSee|dontSeeCheckboxIsChecked|dontSeeCookie|dontSeeCurrentUrlEquals|dontSeeCurrentUrlMatches|dontSeeElement|dontSeeElementInDOM|dontSeeInCurrentUrl|dontSeeInField|dontSeeInPageSource|dontSeeInSource|dontSeeInTitle|dontSeeLink|dontSeeOptionIsSelected|doubleClick|dragAndDrop|entity|executeJS|fillField|formatMoney|grabAttributeFrom|grabCookie|grabFromCurrentUrl|grabMultiple|grabPageSource|grabTextFrom|grabValueFrom|loadSessionSnapshot|loginAsAdmin|makeScreenshot|maximizeWindow|moveBack|moveForward|moveMouseOver|openNewTab|pauseExecution|performOn|pressKey|reloadPage|resetCookie|resizeWindow|scrollTo|searchAndMultiSelectOption|see|seeCheckboxIsChecked|seeCookie|seeCurrentUrlEquals|seeCurrentUrlMatches|seeElement|seeElementInDOM|seeInCurrentUrl|seeInField|seeInFormFields|seeInPageSource|seeInPopup|seeInSource|seeInTitle|seeLink|seeNumberOfElements|seeOptionIsSelected|selectOption|setCookie|switchToIFrame|switchToNextTab|switchToPreviousTab|switchToWindow|typeInPopup|uncheckOption|unselectOption|wait|waitForAjaxLoad|waitForElement|waitForElementChange|waitForElementNotVisible|waitForElementVisible|waitForJS|waitForPageLoad|waitForText)" xsi:type="string">mergeKey</item>
225228
</argument>
226229
<argument name="fileName" xsi:type="string">*Cest.xml</argument>
227230
<argument name="defaultScope" xsi:type="string">Cest</argument>
@@ -231,9 +234,20 @@
231234
<virtualType name="Magento\AcceptanceTestFramework\Test\Config\Dom\ArrayNodeConfig" type="Magento\AcceptanceTestFramework\Config\Dom\ArrayNodeConfig">
232235
<arguments>
233236
<argument name="assocArrayAttributes" xsi:type="array">
234-
<item name="/config/cest/test/(acceptPopup|amOnPage|amOnUrl|appendField|assertArraySubset|attachFile|cancelPopup|checkOption|click|clickWithLeftButton|clickWithRightButton|closeTab|createData|deleteData|dontSee|dontSeeCheckboxIsChecked|dontSeeCookie|dontSeeCurrentUrlEquals|dontSeeCurrentUrlMatches|dontSeeElement|dontSeeElementInDOM|dontSeeInCurrentUrl|dontSeeInField|dontSeeInPageSource|dontSeeInSource|dontSeeInTitle|dontSeeLink|dontSeeOptionIsSelected|doubleClick|dragAndDrop|executeJS|fillField|formatMoney|grabAttributeFrom|grabCookie|grabFromCurrentUrl|grabMultiple|grabPageSource|grabTextFrom|grabValueFrom|loadSessionSnapshot|loginAsAdmin|makeScreenshot|maximizeWindow|moveBack|moveForward|moveMouseOver|openNewTab|pauseExecution|performOn|pressKey|reloadPage|resetCookie|resizeWindow|scrollTo|searchAndMultiSelectOption|see|seeCheckboxIsChecked|seeCookie|seeCurrentUrlEquals|seeCurrentUrlMatches|seeElement|seeElementInDOM|seeInCurrentUrl|seeInField|seeInFormFields|seeInPageSource|seeInPopup|seeInSource|seeInTitle|seeLink|seeNumberOfElements|seeOptionIsSelected|selectOption|setCookie|switchToIFrame|switchToNextTab|switchToPreviousTab|switchToWindow|typeInPopup|uncheckOption|unselectOption|wait|waitForAjaxLoad|waitForElement|waitForElementChange|waitForElementNotVisible|waitForElementVisible|waitForJS|waitForPageLoad|waitForText)" xsi:type="string">mergeKey</item>
237+
<item name="/config/cest/test/(acceptPopup|amOnPage|amOnUrl|appendField|assertArraySubset|attachFile|cancelPopup|checkOption|click|clickWithLeftButton|clickWithRightButton|closeTab|createData|deleteData|dontSee|dontSeeCheckboxIsChecked|dontSeeCookie|dontSeeCurrentUrlEquals|dontSeeCurrentUrlMatches|dontSeeElement|dontSeeElementInDOM|dontSeeInCurrentUrl|dontSeeInField|dontSeeInPageSource|dontSeeInSource|dontSeeInTitle|dontSeeLink|dontSeeOptionIsSelected|doubleClick|dragAndDrop|entity|executeJS|fillField|formatMoney|grabAttributeFrom|grabCookie|grabFromCurrentUrl|grabMultiple|grabPageSource|grabTextFrom|grabValueFrom|loadSessionSnapshot|loginAsAdmin|makeScreenshot|maximizeWindow|moveBack|moveForward|moveMouseOver|openNewTab|pauseExecution|performOn|pressKey|reloadPage|resetCookie|resizeWindow|scrollTo|searchAndMultiSelectOption|see|seeCheckboxIsChecked|seeCookie|seeCurrentUrlEquals|seeCurrentUrlMatches|seeElement|seeElementInDOM|seeInCurrentUrl|seeInField|seeInFormFields|seeInPageSource|seeInPopup|seeInSource|seeInTitle|seeLink|seeNumberOfElements|seeOptionIsSelected|selectOption|setCookie|switchToIFrame|switchToNextTab|switchToPreviousTab|switchToWindow|typeInPopup|uncheckOption|unselectOption|wait|waitForAjaxLoad|waitForElement|waitForElementChange|waitForElementNotVisible|waitForElementVisible|waitForJS|waitForPageLoad|waitForText)" xsi:type="string">mergeKey</item>
238+
<item name="/config/cest/before/(acceptPopup|amOnPage|amOnUrl|appendField|assertArraySubset|attachFile|cancelPopup|checkOption|click|clickWithLeftButton|clickWithRightButton|closeTab|createData|deleteData|dontSee|dontSeeCheckboxIsChecked|dontSeeCookie|dontSeeCurrentUrlEquals|dontSeeCurrentUrlMatches|dontSeeElement|dontSeeElementInDOM|dontSeeInCurrentUrl|dontSeeInField|dontSeeInPageSource|dontSeeInSource|dontSeeInTitle|dontSeeLink|dontSeeOptionIsSelected|doubleClick|dragAndDrop|entity|executeJS|fillField|formatMoney|grabAttributeFrom|grabCookie|grabFromCurrentUrl|grabMultiple|grabPageSource|grabTextFrom|grabValueFrom|loadSessionSnapshot|loginAsAdmin|makeScreenshot|maximizeWindow|moveBack|moveForward|moveMouseOver|openNewTab|pauseExecution|performOn|pressKey|reloadPage|resetCookie|resizeWindow|scrollTo|searchAndMultiSelectOption|see|seeCheckboxIsChecked|seeCookie|seeCurrentUrlEquals|seeCurrentUrlMatches|seeElement|seeElementInDOM|seeInCurrentUrl|seeInField|seeInFormFields|seeInPageSource|seeInPopup|seeInSource|seeInTitle|seeLink|seeNumberOfElements|seeOptionIsSelected|selectOption|setCookie|switchToIFrame|switchToNextTab|switchToPreviousTab|switchToWindow|typeInPopup|uncheckOption|unselectOption|wait|waitForAjaxLoad|waitForElement|waitForElementChange|waitForElementNotVisible|waitForElementVisible|waitForJS|waitForPageLoad|waitForText)" xsi:type="string">mergeKey</item>
239+
<item name="/config/cest/after/(acceptPopup|amOnPage|amOnUrl|appendField|assertArraySubset|attachFile|cancelPopup|checkOption|click|clickWithLeftButton|clickWithRightButton|closeTab|createData|deleteData|dontSee|dontSeeCheckboxIsChecked|dontSeeCookie|dontSeeCurrentUrlEquals|dontSeeCurrentUrlMatches|dontSeeElement|dontSeeElementInDOM|dontSeeInCurrentUrl|dontSeeInField|dontSeeInPageSource|dontSeeInSource|dontSeeInTitle|dontSeeLink|dontSeeOptionIsSelected|doubleClick|dragAndDrop|entity|executeJS|fillField|formatMoney|grabAttributeFrom|grabCookie|grabFromCurrentUrl|grabMultiple|grabPageSource|grabTextFrom|grabValueFrom|loadSessionSnapshot|loginAsAdmin|makeScreenshot|maximizeWindow|moveBack|moveForward|moveMouseOver|openNewTab|pauseExecution|performOn|pressKey|reloadPage|resetCookie|resizeWindow|scrollTo|searchAndMultiSelectOption|see|seeCheckboxIsChecked|seeCookie|seeCurrentUrlEquals|seeCurrentUrlMatches|seeElement|seeElementInDOM|seeInCurrentUrl|seeInField|seeInFormFields|seeInPageSource|seeInPopup|seeInSource|seeInTitle|seeLink|seeNumberOfElements|seeOptionIsSelected|selectOption|setCookie|switchToIFrame|switchToNextTab|switchToPreviousTab|switchToWindow|typeInPopup|uncheckOption|unselectOption|wait|waitForAjaxLoad|waitForElement|waitForElementChange|waitForElementNotVisible|waitForElementVisible|waitForJS|waitForPageLoad|waitForText)" xsi:type="string">mergeKey</item>
235240
<item name="/config/cest/test" xsi:type="string">name</item>
236241
<item name="/config/cest" xsi:type="string">name</item>
242+
<item name="/config/cest/test/entity/data" xsi:type="string">key</item>
243+
<item name="/config/cest/test/entity/required-entity" xsi:type="string">key</item>
244+
<item name="/config/cest/test/createData/required-entity" xsi:type="string">name</item>
245+
<item name="/config/cest/before/entity/data" xsi:type="string">key</item>
246+
<item name="/config/cest/before/entity/required-entity" xsi:type="string">key</item>
247+
<item name="/config/cest/before/createData/required-entity" xsi:type="string">name</item>
248+
<item name="/config/cest/after/entity/data" xsi:type="string">key</item>
249+
<item name="/config/cest/after/entity/required-entity" xsi:type="string">key</item>
250+
<item name="/config/cest/after/createData/required-entity" xsi:type="string">name</item>
237251
</argument>
238252
<argument name="numericArrays" xsi:type="array">
239253
<item name="/config/cest/annotations/env" xsi:type="string">/config/cest/annotations/env</item>

src/Magento/AcceptanceTestFramework/DataGenerator/Api/ApiExecutor.php

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,30 @@ class ApiExecutor
3232
*/
3333
private $jsonDefinition;
3434

35+
/**
36+
* The array of dependentEntities this class can be given. When finding linked entities, APIExecutor
37+
* uses this repository before looking for static data.
38+
* @var null
39+
*/
40+
private $dependentEntities;
41+
3542
const PRIMITIVE_TYPES = ['string', 'boolean', 'integer', 'double', 'array'];
3643

3744
/**
3845
* ApiSubObject constructor.
3946
* @param string $operation
4047
* @param EntityDataObject $entityObject
48+
* @param array $dependentEntities
4149
*/
42-
public function __construct($operation, $entityObject)
50+
public function __construct($operation, $entityObject, $dependentEntities = null)
4351
{
4452
$this->operation = $operation;
4553
$this->entityObject = $entityObject;
54+
if ($dependentEntities != null) {
55+
foreach ($dependentEntities as $entity) {
56+
$this->dependentEntities[$entity->getName()] = $entity;
57+
}
58+
}
4659

4760
$this->jsonDefinition = JsonDefinitionObjectHandler::getInstance()->getJsonDefinition(
4861
$this->operation,
@@ -140,7 +153,14 @@ private function getJsonDataArray($entityObject, $jsonDefMetadata = null)
140153
$entityNamesOfType = $entityObject->getLinkedEntitiesOfType($jsonElementType);
141154

142155
foreach ($entityNamesOfType as $entityName) {
143-
$linkedEntityObj = DataObjectHandler::getInstance()->getObject($entityName);
156+
// If this entity's name exists in the dependentEntities (Test-defined data), use that.
157+
// Else go to the DataManager and try and get the entity from the overall repository of data.
158+
if (array_key_exists($entityName, $this->dependentEntities)) {
159+
$linkedEntityObj = $this->dependentEntities[$entityName];
160+
} else {
161+
$linkedEntityObj = DataObjectHandler::getInstance()->getObject($entityName);
162+
}
163+
144164
$jsonDataSubArray = self::getJsonDataArray($linkedEntityObj);
145165

146166
if ($jsonElement->getType() == 'array') {

src/Magento/AcceptanceTestFramework/DataGenerator/Api/EntityApiHandler.php

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@
66

77
namespace Magento\AcceptanceTestFramework\DataGenerator\Api;
88

9+
use Codeception\Test\Cest;
10+
use Magento\AcceptanceTestFramework\Config\Data;
11+
use Magento\AcceptanceTestFramework\DataGenerator\Handlers\DataObjectHandler;
912
use Magento\AcceptanceTestFramework\DataGenerator\Objects\EntityDataObject;
13+
use Magento\AcceptanceTestFramework\Test\Handlers\CestObjectHandler;
14+
use Magento\AcceptanceTestFramework\Test\Managers\CestArrayProcessor;
15+
use Magento\AcceptanceTestFramework\Util\TestGenerator;
1016

1117
class EntityApiHandler
1218
{
@@ -24,23 +30,30 @@ class EntityApiHandler
2430
*/
2531
private $createdObject;
2632

33+
/**
34+
* Array of dependent entities, handed to ApiExecutor when entity is created.
35+
* @var array|null
36+
*/
37+
private $dependentObjects = [];
38+
2739
/**
2840
* ApiPersistenceHandler constructor.
2941
* @param EntityDataObject $entityObject
42+
* @param array $dependentObjects
3043
*/
31-
public function __construct($entityObject)
44+
public function __construct($entityObject, $dependentObjects = null)
3245
{
33-
$this->entityObject = $entityObject;
46+
$this->entityObject = clone $entityObject;
47+
$this->dependentObjects = $dependentObjects;
3448
}
3549

3650
/**
3751
* Function which executes a create request based on specific operation metadata
38-
*
39-
* @return string | false
52+
* @return void
4053
*/
4154
public function createEntity()
4255
{
43-
$apiExecutor = new ApiExecutor('create', $this->entityObject);
56+
$apiExecutor = new ApiExecutor('create', $this->entityObject, $this->dependentObjects);
4457
$result = $apiExecutor->executeRequest();
4558

4659
$this->createdObject = new EntityDataObject(
@@ -49,8 +62,6 @@ public function createEntity()
4962
json_decode($result, true),
5063
null
5164
);
52-
53-
return $result;
5465
}
5566

5667
/**
@@ -66,6 +77,25 @@ public function deleteEntity()
6677
return $result;
6778
}
6879

80+
/**
81+
* Returns the createdDataObject, instantiated when the entity is created via API.
82+
* @return EntityDataObject
83+
*/
84+
public function getCreatedObject()
85+
{
86+
return $this->createdObject;
87+
}
88+
89+
/**
90+
* Returns a specific data value based on the CreatedObject's definition.
91+
* @param string $dataName
92+
* @return string
93+
*/
94+
public function getCreatedDataByName($dataName)
95+
{
96+
return $this->createdObject->getDataByName($dataName);
97+
}
98+
6999
// TODO add update function
70100
/* public function updateEntity()
71101
{

src/Magento/AcceptanceTestFramework/DataGenerator/Objects/EntityDataObject.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,15 @@ public function getType()
8484
return $this->type;
8585
}
8686

87+
/**
88+
* Getter for Entity's data.
89+
* @return array
90+
*/
91+
public function getData()
92+
{
93+
return $this->data;
94+
}
95+
8796
/**
8897
* This function retrieves data from an entity defined in xml.
8998
*

src/Magento/AcceptanceTestFramework/Test/Handlers/CestObjectHandler.php

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ class CestObjectHandler implements ObjectHandlerInterface
3232
const NODE_NAME = 'nodeName';
3333
const NAME = 'name';
3434
const ANNOTATION_VALUE = 'value';
35+
const TEST_STEP_ENTITY_CREATION = 'entity';
36+
const TEST_ENTITY_CREATION_KEY = 'key';
37+
const TEST_ENTITY_CREATION_VALUE = 'value';
38+
const TEST_STEP_DATA_CREATION = 'createData';
3539

3640
/**
3741
* Cest Object Handler
@@ -139,6 +143,7 @@ private function initCestData($parsedArray)
139143
$tests = $this->stripDescriptorTags($tests, CestObjectHandler::CEST_ANNOTATIONS);
140144
}
141145

146+
//add customData before here.
142147
$this->cests[$cestName] = new CestObject(
143148
$cestName,
144149
$annotations,
@@ -165,7 +170,8 @@ private function extractHook($hookType, $cestHook)
165170

166171
$hook = new CestHookObject(
167172
$hookType,
168-
$this->extractTestActions($hookActions)
173+
$this->extractTestActions($hookActions),
174+
$this->extractTestEntities($hookActions)
169175
);
170176

171177
return $hook;
@@ -228,7 +234,8 @@ private function extractTestData($cestTestData)
228234
$testObjects[] = new TestObject(
229235
$testName,
230236
$this->extractTestActions($testActions),
231-
$testAnnotations
237+
$testAnnotations,
238+
$this->extractTestEntities($testActions)
232239
);
233240
}
234241

@@ -238,6 +245,8 @@ private function extractTestData($cestTestData)
238245
/**
239246
* This method takes an array of test actions read in from a CestHook or Test. The actions are stripped of
240247
* irrelevant tags and returned as an array of ActionObjects.
248+
* This method also strips data-declaration from test-action attributes and passes in the appropriate customData
249+
* array to the CestObject.
241250
*
242251
* @param array $testActions
243252
* @return array
@@ -248,6 +257,14 @@ private function extractTestActions($testActions)
248257
$actions = [];
249258

250259
foreach ($testActions as $actionName => $actionData) {
260+
$mergeKey = $actionData[CestObjectHandler::TEST_STEP_MERGE_KEY];
261+
if ($actionData[CestObjectHandler::NODE_NAME] === CestObjectHandler::TEST_STEP_ENTITY_CREATION) {
262+
foreach ($actionData as $key => $attribute) {
263+
if (is_array($attribute)) {
264+
unset($actionData[$key]);
265+
}
266+
}
267+
}
251268
$mergeKey = $actionData[CestObjectHandler::TEST_STEP_MERGE_KEY];
252269
$actionAttributes = $this->stripDescriptorTags(
253270
$actionData,
@@ -258,7 +275,8 @@ private function extractTestActions($testActions)
258275
$order = null;
259276

260277
if (array_key_exists(CestObjectHandler::TEST_ACTION_BEFORE, $actionData)
261-
and array_key_exists(CestObjectHandler::TEST_ACTION_AFTER, $actionData)) {
278+
and array_key_exists(CestObjectHandler::TEST_ACTION_AFTER, $actionData)
279+
) {
262280
throw new XmlException(sprintf(self::BEFORE_AFTER_ERROR_MSG, $actionName));
263281
}
264282

@@ -286,6 +304,32 @@ private function extractTestActions($testActions)
286304
return $actions;
287305
}
288306

307+
/**
308+
* Extracts custom entity or data definitions from test actions.
309+
* Returns array of entity data objects indexed by mergeKey, and an array of key-value pairs.
310+
* @param array $testActions
311+
* @return array $entityData
312+
*/
313+
private function extractTestEntities($testActions)
314+
{
315+
$testEntities = [];
316+
317+
foreach ($testActions as $actionName => $actionData) {
318+
$entityData = [];
319+
if ($actionData[CestObjectHandler::NODE_NAME] === CestObjectHandler::TEST_STEP_ENTITY_CREATION) {
320+
foreach ($actionData as $key => $attribute) {
321+
if (is_array($attribute)) {
322+
$entityData[$attribute[CestObjectHandler::TEST_ENTITY_CREATION_KEY]]
323+
= $attribute[CestObjectHandler::TEST_ENTITY_CREATION_VALUE];
324+
unset($actionData[$key]);
325+
}
326+
}
327+
$testEntities[$actionData[CestObjectHandler::NAME]] = $entityData;
328+
}
329+
}
330+
331+
return $testEntities;
332+
}
289333
// @codingStandardsIgnoreStart
290334
/**
291335
* This method takes an array of data and an array representing irrelevant tags. The method strips

0 commit comments

Comments
 (0)