From d551d1e3f70f8ae09c4de6958125f22b855d5569 Mon Sep 17 00:00:00 2001 From: Eric Bohanon Date: Wed, 25 Oct 2017 11:14:11 -0500 Subject: [PATCH 1/3] MAGETWO-81033: Create GraphQL endpoint for fetching single product - Adds functional tests, fixtures, and client - Adds support for simple and configurable product --- .../Magento/GraphQl/Controller/GraphQl.php | 77 +++ .../Model/Resolver/MediaGalleryEntries.php | 77 +++ .../GraphQl/Model/Resolver/Product.php | 149 +++++ .../Magento/GraphQl/Model/SchemaGenerator.php | 301 ++++++++++ .../Model/SchemaGeneratorInterface.php | 20 + app/code/Magento/GraphQl/README.md | 5 + app/code/Magento/GraphQl/composer.json | 25 + app/code/Magento/GraphQl/etc/di.xml | 18 + app/code/Magento/GraphQl/etc/graphql/di.xml | 11 + app/code/Magento/GraphQl/etc/module.xml | 14 + app/code/Magento/GraphQl/registration.php | 9 + composer.json | 4 +- composer.lock | 479 ++++++++------- .../TestFramework/Helper/JsonSerializer.php | 77 +++ .../TestFramework/TestCase/GraphQl/Client.php | 95 +++ .../TestCase/GraphQlAbstract.php | 49 ++ .../TestCase/HttpClient/CurlClient.php | 172 ++++++ .../CurlClientWithCookies.php} | 34 +- .../TestCase/Webapi/Adapter/Rest.php | 21 +- .../Webapi/Adapter/Rest/CurlClient.php | 310 ---------- .../Webapi/Adapter/Rest/RestClient.php | 133 +++++ .../TestFramework/TestCase/WebapiAbstract.php | 9 +- .../Catalog/GraphQl/ProductViewTest.php | 565 ++++++++++++++++++ .../GraphQl/ConfigurableProductViewTest.php | 255 ++++++++ .../Framework/Stdlib/CookieManagerTest.php | 8 +- .../Magento/Webapi/DeserializationTest.php | 6 +- .../Webapi/Routing/CoreRoutingTest.php | 6 +- .../Magento/Catalog/_files/product_simple.php | 2 +- .../_files/product_simple_with_all_fields.php | 38 ++ ...roduct_simple_with_all_fields_rollback.php | 8 + .../product_simple_with_full_option_set.php | 215 +++++++ ...t_simple_with_full_option_set_rollback.php | 8 + ...duct_simple_with_media_gallery_entries.php | 72 +++ ...le_with_media_gallery_entries_rollback.php | 7 + .../_files/magento_image.jpg | Bin 0 -> 13353 bytes ..._configurable_with_category_and_weight.php | 213 +++++++ ...able_with_category_and_weight_rollback.php | 7 + ..._configurable_with_media_gallery_video.php | 72 +++ ...able_with_media_gallery_video_rollback.php | 7 + .../Framework/Webapi/GraphQl/Request.php | 110 ++++ .../Framework/Webapi/GraphQl/Response.php | 86 +++ 41 files changed, 3221 insertions(+), 553 deletions(-) create mode 100644 app/code/Magento/GraphQl/Controller/GraphQl.php create mode 100644 app/code/Magento/GraphQl/Model/Resolver/MediaGalleryEntries.php create mode 100644 app/code/Magento/GraphQl/Model/Resolver/Product.php create mode 100644 app/code/Magento/GraphQl/Model/SchemaGenerator.php create mode 100644 app/code/Magento/GraphQl/Model/SchemaGeneratorInterface.php create mode 100644 app/code/Magento/GraphQl/README.md create mode 100644 app/code/Magento/GraphQl/composer.json create mode 100644 app/code/Magento/GraphQl/etc/di.xml create mode 100644 app/code/Magento/GraphQl/etc/graphql/di.xml create mode 100644 app/code/Magento/GraphQl/etc/module.xml create mode 100644 app/code/Magento/GraphQl/registration.php create mode 100644 dev/tests/api-functional/framework/Magento/TestFramework/Helper/JsonSerializer.php create mode 100644 dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php create mode 100644 dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQlAbstract.php create mode 100644 dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClient.php rename dev/tests/api-functional/framework/Magento/TestFramework/TestCase/{Webapi/Curl.php => HttpClient/CurlClientWithCookies.php} (73%) delete mode 100644 dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/CurlClient.php create mode 100644 dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/RestClient.php create mode 100644 dev/tests/api-functional/testsuite/Magento/Catalog/GraphQl/ProductViewTest.php create mode 100644 dev/tests/api-functional/testsuite/Magento/ConfigurableProduct/GraphQl/ConfigurableProductViewTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_all_fields.php create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_all_fields_rollback.php create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_full_option_set.php create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_full_option_set_rollback.php create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_media_gallery_entries.php create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_media_gallery_entries_rollback.php create mode 100644 dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/magento_image.jpg create mode 100644 dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_category_and_weight.php create mode 100644 dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_category_and_weight_rollback.php create mode 100644 dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_media_gallery_video.php create mode 100644 dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_media_gallery_video_rollback.php create mode 100644 lib/internal/Magento/Framework/Webapi/GraphQl/Request.php create mode 100644 lib/internal/Magento/Framework/Webapi/GraphQl/Response.php diff --git a/app/code/Magento/GraphQl/Controller/GraphQl.php b/app/code/Magento/GraphQl/Controller/GraphQl.php new file mode 100644 index 000000000000..ada7b5eaa60d --- /dev/null +++ b/app/code/Magento/GraphQl/Controller/GraphQl.php @@ -0,0 +1,77 @@ +response = $response; + $this->schemaGenerator = $schemaGenerator; + } + + /** + * Handle GraphQL request + * + * @param RequestInterface $request + * @return ResponseInterface + */ + public function dispatch(RequestInterface $request) + { + try { + if ($request->getHeader('Content-Type') + && strpos($request->getHeader('Content-Type'), 'application/json') !== false + ) { + $raw = file_get_contents('php://input') ?: ''; + $data = json_decode($raw, true); + } else { + $data = $_REQUEST; + } + $schema = $this->schemaGenerator->generate(); + $result = \GraphQL\GraphQL::execute( + $schema, + isset($data['query']) ? $data['query'] : '', + null, + null, + isset($data['variables']) ? $data['variables'] : [] + ); + } catch (\Exception $error) { + $result['extensions']['exception'] = FormattedError::createFromException($error); + $this->response->setStatusCode(500); + } + $this->response->setBody(json_encode($result))->setHeader('Content-Type', 'application/json'); + return $this->response; + } +} diff --git a/app/code/Magento/GraphQl/Model/Resolver/MediaGalleryEntries.php b/app/code/Magento/GraphQl/Model/Resolver/MediaGalleryEntries.php new file mode 100644 index 000000000000..98a71388f2b9 --- /dev/null +++ b/app/code/Magento/GraphQl/Model/Resolver/MediaGalleryEntries.php @@ -0,0 +1,77 @@ +mediaGalleryManagement = $mediaGalleryManagement; + $this->serviceOutputProcessor = $serviceOutputProcessor; + } + + /** + * Get media gallery entries for the specified product. + * + * @param string $sku + * @return array|null + */ + public function getMediaGalleryEntries(string $sku) + { + try { + $mediaGalleryObjectArray = $this->mediaGalleryManagement->getList($sku); + } catch (NoSuchEntityException $e) { + // No error should be thrown, null result should be returned + return null; + } + + $mediaGalleryList = $this->serviceOutputProcessor->process( + $mediaGalleryObjectArray, + ProductAttributeMediaGalleryManagementInterface::class, + 'getList' + ); + + foreach ($mediaGalleryList as $key => $mediaGallery) { + if (isset($mediaGallery[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]) + && isset($mediaGallery[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]['video_content'])) { + $mediaGallery = array_merge( + $mediaGallery, + $mediaGallery[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY] + ); + $mediaGalleryList[$key] = $mediaGallery; + } + } + + return $mediaGalleryList; + } +} diff --git a/app/code/Magento/GraphQl/Model/Resolver/Product.php b/app/code/Magento/GraphQl/Model/Resolver/Product.php new file mode 100644 index 000000000000..86f0464ac72b --- /dev/null +++ b/app/code/Magento/GraphQl/Model/Resolver/Product.php @@ -0,0 +1,149 @@ +productRepository = $productRepository; + $this->serviceOutputProcessor = $serviceOutputProcessor; + $this->mediaGalleryResolver = $mediaGalleryResolver; + } + + /** + * Resolves product by Sku + * + * @param string $sku + * @return array|null + */ + public function getProduct(string $sku) + { + try { + $productObject = $this->productRepository->get($sku); + } catch (NoSuchEntityException $e) { + // No error should be thrown, null result should be returned + return null; + } + return $this->processProduct($productObject); + } + + /** + * Resolves product by Id + * + * @param int $productId + * @return array|null + */ + public function getProductById(int $productId) + { + try { + $productObject = $this->productRepository->getById($productId); + } catch (NoSuchEntityException $e) { + // No error should be thrown, null result should be returned + return null; + } + return $this->processProduct($productObject); + } + + /** + * Retrieve single product data in array format + * + * @param \Magento\Catalog\Api\Data\ProductInterface $productObject + * @return array|null + */ + private function processProduct(\Magento\Catalog\Api\Data\ProductInterface $productObject) + { + $product = $this->serviceOutputProcessor->process( + $productObject, + ProductRepositoryInterface::class, + 'get' + ); + $product = array_merge($product, $product['extension_attributes']); + if (isset($product['custom_attributes'])) { + $customAttributes = []; + foreach ($product['custom_attributes'] as $attribute) { + $isArray = false; + if (is_array($attribute['value'])) { + $isArray = true; + foreach ($attribute['value'] as $attributeValue) { + if (is_array($attributeValue)) { + $customAttributes[$attribute['attribute_code']] = json_encode($attribute['value']); + continue; + } + $customAttributes[$attribute['attribute_code']] = implode(',', $attribute['value']); + continue; + } + } + if ($isArray) { + continue; + } + $customAttributes[$attribute['attribute_code']] = $attribute['value']; + } + } + $product = array_merge($product, $customAttributes); + $product = array_merge($product, $product['product_links']); + $product['media_gallery_entries'] = $this + ->mediaGalleryResolver->getMediaGalleryEntries($productObject->getSku()); + + if (isset($product['configurable_product_links'])) { + $product['configurable_product_links'] = $this + ->resolveConfigurableProductLinks($product['configurable_product_links']); + } + + return $product; + } + + /** + * Resolve links for configurable product into simple products + * + * @param int[] + * @return array + */ + private function resolveConfigurableProductLinks($configurableProductLinks) + { + if (empty($configurableProductLinks)) { + return []; + } + $result = []; + foreach ($configurableProductLinks as $key => $id) { + $result[$key] = $this->getProductById($id); + } + return $result; + } +} diff --git a/app/code/Magento/GraphQl/Model/SchemaGenerator.php b/app/code/Magento/GraphQl/Model/SchemaGenerator.php new file mode 100644 index 000000000000..e2085dd4056b --- /dev/null +++ b/app/code/Magento/GraphQl/Model/SchemaGenerator.php @@ -0,0 +1,301 @@ +productResolver = $productResolver; + } + + /** + * @inheritdoc + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + */ + public function generate() + { + $typeConfigDecorator = function ($typeConfig, $typeDefinitionNode) { + $name = $typeConfig['name']; + if ($name == 'Query') { + $typeConfig['resolveField'] = function ($value, $args, $context, ResolveInfo $info) { + if (empty($args['sku']) || !is_string($args['sku'])) { + throw new LocalizedException(__('SKU is a required argument in a string format')); + } + + $productData = $this->productResolver->getProduct($args['sku']); + switch ($info->fieldName) { + case 'product': + return $productData; + default: + return null; + } + }; + } + + if ($name === 'AbstractProduct') { + $typeConfig['resolveType'] = function ($objectValue, $context, $info) { + switch ($objectValue['type_id']) { + case 'configurable': + return 'ConfigurableProduct'; + case 'simple': + return 'SimpleProduct'; + default: + return null; + } + }; + } + return $typeConfig; + }; + + return BuildSchema::build($this->readSchema(), $typeConfigDecorator); + } + + /** + * Gets type data from string + * + * @param string $type + * @return array + * @throws \Exception + */ + public function getTypeData($type) + { + /** @var \Magento\Framework\Reflection\TypeProcessor $typeProcessor */ + $typeProcessor = \Magento\Framework\App\ObjectManager::getInstance()->get( + \Magento\Framework\Reflection\TypeProcessor::class + ); + $typesData = $typeProcessor->getTypeData($type); + + $result = []; + if (isset($typesData['parameters'])) { + foreach ($typesData['parameters'] as $attributeCode => $parameter) { + $snakeAttributeCode = \Magento\Framework\Api\SimpleDataObjectConverter::camelCaseToSnakeCase( + $attributeCode + ); + + if ($snakeAttributeCode == 'custom_attributes') { + continue; + } + + if ($typeProcessor->isTypeAny($parameter['type'])) { + throw new \Exception("Mixed type detected"); + } elseif ($typeProcessor->isArrayType($parameter['type'])) { + $arrayItemType = $typeProcessor->getArrayItemType($parameter['type']); + if ($typeProcessor->isTypeSimple($arrayItemType)) { + $result[$snakeAttributeCode][] = $arrayItemType; + } else { + $result[$snakeAttributeCode][] = $this->getTypeData($arrayItemType); + } + } elseif ($typeProcessor->isTypeSimple($parameter['type'])) { + $result[$snakeAttributeCode] = $parameter['type']; + } else { + if ($snakeAttributeCode == 'extension_attributes') { + $extensionAttributes = $this->getTypeData($parameter['type']); + $result = array_merge($result, $extensionAttributes); + } else { + $result[$snakeAttributeCode][] = $this->getTypeData($parameter['type']); + } + } + } + } + return $result; + } + + public function generateType($typeName, $data) + { + if (!in_array($typeName, $this->generatedTypes)) { + $this->generatedTypes[] = $typeName; + } else { + return; + } + + $typeFields = $this->convertFieldsToGraphQlSchemaFormat($typeName, $data); + if ($typeName == 'AbstractProduct') { + echo << $type) { + if (!$skipField) { + $productFields .= "\n {$field}: "; + } + + if (is_array($type)) { + $isAssociativeArray = $this->isAssociativeArray($type); + if ($isAssociativeArray) { + $parentField = ucfirst($this->underscoreToCamelCase($parentField)); + if ($field === 'content' || $field === 'video_content') { + $parentField = ucfirst($this->underscoreToCamelCase($field)); + $this->generateType($typeName . $parentField, $type); + $productFields .= $typeName . $parentField; + } else { + $this->generateType($typeName . $parentField, $type); + $productFields .= $typeName . $parentField; + } + } else { + $convertedField = $this->convertFieldsToGraphQlSchemaFormat( + $typeName, + $type, + !$isAssociativeArray, + $field + ); + $productFields .= "[{$convertedField}]"; + } + } else { + $type = ucfirst($type); + $productFields .= "{$type}"; + } + } + return $productFields; + } + + /** + * Reads graphQL schema + * + * @return string + */ + private function readSchema() + { + /** @var \Magento\Webapi\Model\ServiceMetadata $serviceMetadata */ + $serviceMetadata = \Magento\Framework\App\ObjectManager::getInstance()->get( + \Magento\Webapi\Model\ServiceMetadata::class + ); + $serviceMetadata->getServicesConfig(); + /** @var \Magento\Eav\Api\AttributeManagementInterface $management */ + $management = \Magento\Framework\App\ObjectManager::getInstance()->get( + \Magento\Eav\Api\AttributeManagementInterface::class + ); + $result = []; + $attributes = $management->getAttributes('catalog_product', 4); + foreach ($attributes as $attribute) { + $result[$attribute->getAttributeCode()] = 'string'; + } + + $staticAttributes = $this->getTypeData('CatalogDataProductInterface'); + $result = array_merge($result, $staticAttributes); + + unset($result['stock_item']); + unset($result['bundle_product_options']); + unset($result['downloadable_product_links']); + unset($result['downloadable_product_samples']); + unset($result['configurable_product_links']); + unset($result['quantity_and_stock_status']); + unset($result['sku_type']); + + $videoContent = $result['media_gallery_entries'][0]['video_content'][0]; + $content = $result['media_gallery_entries'][0]['content'][0]; + $result['media_gallery_entries'][0]['video_content'] = $videoContent; + $result['media_gallery_entries'][0]['content'] = $content; + + ob_start(); + + echo <<generateType('ConfigurableProductOptions', $result['configurable_product_options'][0]); + unset($result['configurable_product_options']); + $this->generateType('AbstractProduct', $result); + + $schema = ob_get_contents(); + ob_end_clean(); + return $schema; + } +} diff --git a/app/code/Magento/GraphQl/Model/SchemaGeneratorInterface.php b/app/code/Magento/GraphQl/Model/SchemaGeneratorInterface.php new file mode 100644 index 000000000000..d6a682239063 --- /dev/null +++ b/app/code/Magento/GraphQl/Model/SchemaGeneratorInterface.php @@ -0,0 +1,20 @@ + + + + + + + + graphql + + + + + diff --git a/app/code/Magento/GraphQl/etc/graphql/di.xml b/app/code/Magento/GraphQl/etc/graphql/di.xml new file mode 100644 index 000000000000..1defa1b73edb --- /dev/null +++ b/app/code/Magento/GraphQl/etc/graphql/di.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/app/code/Magento/GraphQl/etc/module.xml b/app/code/Magento/GraphQl/etc/module.xml new file mode 100644 index 000000000000..fc3376bd0125 --- /dev/null +++ b/app/code/Magento/GraphQl/etc/module.xml @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/app/code/Magento/GraphQl/registration.php b/app/code/Magento/GraphQl/registration.php new file mode 100644 index 000000000000..123ab6ee0db8 --- /dev/null +++ b/app/code/Magento/GraphQl/registration.php @@ -0,0 +1,9 @@ +=5.5,<8.0-DEV" + }, + "require-dev": { + "phpunit/phpunit": "^4.8", + "psr/http-message": "^1.0" + }, + "suggest": { + "psr/http-message": "To use standard GraphQL server", + "react/promise": "To leverage async resolving on React PHP platform" + }, + "type": "library", + "autoload": { + "files": [ + "src/deprecated.php" + ], + "psr-4": { + "GraphQL\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD" + ], + "description": "A PHP port of GraphQL reference implementation", + "homepage": "https://github.com/webonyx/graphql-php", + "keywords": [ + "api", + "graphql" + ], + "time": "2017-10-13 17:45:55" }, { "name": "zendframework/zend-captcha", @@ -2019,7 +2066,7 @@ "captcha", "zf2" ], - "time": "2017-02-23T08:09:44+00:00" + "time": "2017-02-23 08:09:44" }, { "name": "zendframework/zend-code", @@ -2072,7 +2119,7 @@ "code", "zf2" ], - "time": "2016-10-24T13:23:32+00:00" + "time": "2016-10-24 13:23:32" }, { "name": "zendframework/zend-config", @@ -2128,7 +2175,7 @@ "config", "zf2" ], - "time": "2016-02-04T23:01:10+00:00" + "time": "2016-02-04 23:01:10" }, { "name": "zendframework/zend-console", @@ -2180,7 +2227,7 @@ "console", "zf2" ], - "time": "2016-02-09T17:15:12+00:00" + "time": "2016-02-09 17:15:12" }, { "name": "zendframework/zend-crypt", @@ -2230,7 +2277,7 @@ "crypt", "zf2" ], - "time": "2016-02-03T23:46:30+00:00" + "time": "2016-02-03 23:46:30" }, { "name": "zendframework/zend-db", @@ -2287,7 +2334,7 @@ "db", "zf2" ], - "time": "2016-08-09T19:28:55+00:00" + "time": "2016-08-09 19:28:55" }, { "name": "zendframework/zend-di", @@ -2334,7 +2381,7 @@ "di", "zf2" ], - "time": "2016-04-25T20:58:11+00:00" + "time": "2016-04-25 20:58:11" }, { "name": "zendframework/zend-escaper", @@ -2378,7 +2425,7 @@ "escaper", "zf2" ], - "time": "2016-06-30T19:48:38+00:00" + "time": "2016-06-30 19:48:38" }, { "name": "zendframework/zend-eventmanager", @@ -2425,7 +2472,7 @@ "eventmanager", "zf2" ], - "time": "2016-02-18T20:49:05+00:00" + "time": "2016-02-18 20:49:05" }, { "name": "zendframework/zend-filter", @@ -2485,7 +2532,7 @@ "filter", "zf2" ], - "time": "2017-05-17T20:56:17+00:00" + "time": "2017-05-17 20:56:17" }, { "name": "zendframework/zend-form", @@ -2562,39 +2609,39 @@ "form", "zf2" ], - "time": "2017-05-18T14:59:53+00:00" + "time": "2017-05-18 14:59:53" }, { "name": "zendframework/zend-http", - "version": "2.6.0", + "version": "2.7.0", "source": { "type": "git", "url": "https://github.com/zendframework/zend-http.git", - "reference": "09f4d279f46d86be63171ff62ee0f79eca878678" + "reference": "78aa510c0ea64bfb2aa234f50c4f232c9531acfa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-http/zipball/09f4d279f46d86be63171ff62ee0f79eca878678", - "reference": "09f4d279f46d86be63171ff62ee0f79eca878678", + "url": "https://api.github.com/repos/zendframework/zend-http/zipball/78aa510c0ea64bfb2aa234f50c4f232c9531acfa", + "reference": "78aa510c0ea64bfb2aa234f50c4f232c9531acfa", "shasum": "" }, "require": { - "php": "^5.5 || ^7.0", - "zendframework/zend-loader": "^2.5", - "zendframework/zend-stdlib": "^2.5 || ^3.0", - "zendframework/zend-uri": "^2.5", - "zendframework/zend-validator": "^2.5" + "php": "^5.6 || ^7.0", + "zendframework/zend-loader": "^2.5.1", + "zendframework/zend-stdlib": "^3.1 || ^2.7.7", + "zendframework/zend-uri": "^2.5.2", + "zendframework/zend-validator": "^2.10.1" }, "require-dev": { - "phpunit/phpunit": "^4.0", + "phpunit/phpunit": "^6.4.1 || ^5.7.15", "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.5" + "zendframework/zend-config": "^3.1 || ^2.6" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.6-dev", - "dev-develop": "2.7-dev" + "dev-master": "2.7-dev", + "dev-develop": "2.8-dev" } }, "autoload": { @@ -2609,10 +2656,13 @@ "description": "provides an easy interface for performing Hyper-Text Transfer Protocol (HTTP) requests", "homepage": "https://github.com/zendframework/zend-http", "keywords": [ + "ZendFramework", "http", - "zf2" + "http client", + "zend", + "zf" ], - "time": "2017-01-31T14:41:02+00:00" + "time": "2017-10-13 12:06:24" }, { "name": "zendframework/zend-hydrator", @@ -2670,7 +2720,7 @@ "hydrator", "zf2" ], - "time": "2016-02-18T22:38:26+00:00" + "time": "2016-02-18 22:38:26" }, { "name": "zendframework/zend-i18n", @@ -2737,7 +2787,7 @@ "i18n", "zf2" ], - "time": "2017-05-17T17:00:12+00:00" + "time": "2017-05-17 17:00:12" }, { "name": "zendframework/zend-inputfilter", @@ -2792,7 +2842,7 @@ "inputfilter", "zf2" ], - "time": "2017-05-18T14:20:56+00:00" + "time": "2017-05-18 14:20:56" }, { "name": "zendframework/zend-json", @@ -2847,7 +2897,7 @@ "json", "zf2" ], - "time": "2016-02-04T21:20:26+00:00" + "time": "2016-02-04 21:20:26" }, { "name": "zendframework/zend-loader", @@ -2891,7 +2941,7 @@ "loader", "zf2" ], - "time": "2015-06-03T14:05:47+00:00" + "time": "2015-06-03 14:05:47" }, { "name": "zendframework/zend-log", @@ -2962,7 +3012,7 @@ "logging", "zf2" ], - "time": "2017-05-17T16:03:26+00:00" + "time": "2017-05-17 16:03:26" }, { "name": "zendframework/zend-mail", @@ -3024,7 +3074,7 @@ "mail", "zf2" ], - "time": "2017-06-08T20:03:58+00:00" + "time": "2017-06-08 20:03:58" }, { "name": "zendframework/zend-math", @@ -3074,7 +3124,7 @@ "math", "zf2" ], - "time": "2016-04-07T16:29:53+00:00" + "time": "2016-04-07 16:29:53" }, { "name": "zendframework/zend-mime", @@ -3123,7 +3173,7 @@ "mime", "zf2" ], - "time": "2017-01-16T16:43:38+00:00" + "time": "2017-01-16 16:43:38" }, { "name": "zendframework/zend-modulemanager", @@ -3181,7 +3231,7 @@ "modulemanager", "zf2" ], - "time": "2017-07-11T19:39:57+00:00" + "time": "2017-07-11 19:39:57" }, { "name": "zendframework/zend-mvc", @@ -3268,7 +3318,7 @@ "mvc", "zf2" ], - "time": "2016-02-23T15:24:59+00:00" + "time": "2016-02-23 15:24:59" }, { "name": "zendframework/zend-serializer", @@ -3325,7 +3375,7 @@ "serializer", "zf2" ], - "time": "2016-06-21T17:01:55+00:00" + "time": "2016-06-21 17:01:55" }, { "name": "zendframework/zend-server", @@ -3371,7 +3421,7 @@ "server", "zf2" ], - "time": "2016-06-20T22:27:55+00:00" + "time": "2016-06-20 22:27:55" }, { "name": "zendframework/zend-servicemanager", @@ -3423,7 +3473,7 @@ "servicemanager", "zf2" ], - "time": "2016-12-19T19:14:29+00:00" + "time": "2016-12-19 19:14:29" }, { "name": "zendframework/zend-session", @@ -3489,7 +3539,7 @@ "session", "zf2" ], - "time": "2017-06-19T21:31:39+00:00" + "time": "2017-06-19 21:31:39" }, { "name": "zendframework/zend-soap", @@ -3541,7 +3591,7 @@ "soap", "zf2" ], - "time": "2016-04-21T16:06:27+00:00" + "time": "2016-04-21 16:06:27" }, { "name": "zendframework/zend-stdlib", @@ -3600,7 +3650,7 @@ "stdlib", "zf2" ], - "time": "2016-04-12T21:17:31+00:00" + "time": "2016-04-12 21:17:31" }, { "name": "zendframework/zend-text", @@ -3647,7 +3697,7 @@ "text", "zf2" ], - "time": "2016-02-08T19:03:52+00:00" + "time": "2016-02-08 19:03:52" }, { "name": "zendframework/zend-uri", @@ -3694,7 +3744,7 @@ "uri", "zf2" ], - "time": "2016-02-17T22:38:51+00:00" + "time": "2016-02-17 22:38:51" }, { "name": "zendframework/zend-validator", @@ -3765,7 +3815,7 @@ "validator", "zf2" ], - "time": "2017-08-22T14:19:23+00:00" + "time": "2017-08-22 14:19:23" }, { "name": "zendframework/zend-view", @@ -3852,7 +3902,7 @@ "view", "zf2" ], - "time": "2017-03-21T15:05:56+00:00" + "time": "2017-03-21 15:05:56" } ], "packages-dev": [ @@ -3908,7 +3958,7 @@ "constructor", "instantiate" ], - "time": "2015-06-14T21:17:01+00:00" + "time": "2015-06-14 21:17:01" }, { "name": "friendsofphp/php-cs-fixer", @@ -3978,7 +4028,7 @@ } ], "description": "A tool to automatically fix PHP code style", - "time": "2017-03-31T12:59:38+00:00" + "time": "2017-03-31 12:59:38" }, { "name": "ircmaxell/password-compat", @@ -4020,7 +4070,7 @@ "hashing", "password" ], - "time": "2014-11-20T16:49:30+00:00" + "time": "2014-11-20 16:49:30" }, { "name": "lusitanian/oauth", @@ -4087,41 +4137,44 @@ "oauth", "security" ], - "time": "2016-07-12T22:15:40+00:00" + "time": "2016-07-12 22:15:40" }, { "name": "myclabs/deep-copy", - "version": "1.6.1", + "version": "1.7.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102" + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8e6e04167378abf1ddb4d3522d8755c5fd90d102", - "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", "shasum": "" }, "require": { - "php": ">=5.4.0" + "php": "^5.6 || ^7.0" }, "require-dev": { - "doctrine/collections": "1.*", - "phpunit/phpunit": "~4.1" + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^4.1" }, "type": "library", "autoload": { "psr-4": { "DeepCopy\\": "src/DeepCopy/" - } + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "description": "Create deep copies (clones) of your objects", - "homepage": "https://github.com/myclabs/DeepCopy", "keywords": [ "clone", "copy", @@ -4129,7 +4182,7 @@ "object", "object graph" ], - "time": "2017-04-12T18:52:22+00:00" + "time": "2017-10-19 19:58:43" }, { "name": "pdepend/pdepend", @@ -4169,7 +4222,7 @@ "BSD-3-Clause" ], "description": "Official version of pdepend to be handled with Composer", - "time": "2017-01-19T14:23:36+00:00" + "time": "2017-01-19 14:23:36" }, { "name": "phar-io/manifest", @@ -4224,7 +4277,7 @@ } ], "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "time": "2017-03-05T18:14:27+00:00" + "time": "2017-03-05 18:14:27" }, { "name": "phar-io/version", @@ -4271,7 +4324,7 @@ } ], "description": "Library for handling version information and constraints", - "time": "2017-03-05T17:38:23+00:00" + "time": "2017-03-05 17:38:23" }, { "name": "phpdocumentor/reflection-common", @@ -4325,7 +4378,7 @@ "reflection", "static analysis" ], - "time": "2017-09-11T18:02:19+00:00" + "time": "2017-09-11 18:02:19" }, { "name": "phpdocumentor/reflection-docblock", @@ -4370,7 +4423,7 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2017-08-30T18:51:59+00:00" + "time": "2017-08-30 18:51:59" }, { "name": "phpdocumentor/type-resolver", @@ -4417,7 +4470,7 @@ "email": "me@mikevanriel.com" } ], - "time": "2017-07-14T14:27:02+00:00" + "time": "2017-07-14 14:27:02" }, { "name": "phpmd/phpmd", @@ -4483,7 +4536,7 @@ "phpmd", "pmd" ], - "time": "2017-01-20T14:41:10+00:00" + "time": "2017-01-20 14:41:10" }, { "name": "phpspec/prophecy", @@ -4546,7 +4599,7 @@ "spy", "stub" ], - "time": "2017-09-04T11:05:03+00:00" + "time": "2017-09-04 11:05:03" }, { "name": "phpunit/php-code-coverage", @@ -4610,7 +4663,7 @@ "testing", "xunit" ], - "time": "2017-08-03T12:40:43+00:00" + "time": "2017-08-03 12:40:43" }, { "name": "phpunit/php-file-iterator", @@ -4657,7 +4710,7 @@ "filesystem", "iterator" ], - "time": "2016-10-03T07:40:28+00:00" + "time": "2016-10-03 07:40:28" }, { "name": "phpunit/php-text-template", @@ -4698,7 +4751,7 @@ "keywords": [ "template" ], - "time": "2015-06-21T13:50:34+00:00" + "time": "2015-06-21 13:50:34" }, { "name": "phpunit/php-timer", @@ -4747,7 +4800,7 @@ "keywords": [ "timer" ], - "time": "2017-02-26T11:10:40+00:00" + "time": "2017-02-26 11:10:40" }, { "name": "phpunit/php-token-stream", @@ -4796,7 +4849,7 @@ "keywords": [ "tokenizer" ], - "time": "2017-08-20T05:47:52+00:00" + "time": "2017-08-20 05:47:52" }, { "name": "phpunit/phpunit", @@ -4880,7 +4933,7 @@ "testing", "xunit" ], - "time": "2017-08-03T13:59:28+00:00" + "time": "2017-08-03 13:59:28" }, { "name": "phpunit/phpunit-mock-objects", @@ -4939,7 +4992,7 @@ "mock", "xunit" ], - "time": "2017-08-03T14:08:16+00:00" + "time": "2017-08-03 14:08:16" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -4984,7 +5037,7 @@ ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2017-03-04T06:30:41+00:00" + "time": "2017-03-04 06:30:41" }, { "name": "sebastian/comparator", @@ -5048,7 +5101,7 @@ "compare", "equality" ], - "time": "2017-03-03T06:26:08+00:00" + "time": "2017-03-03 06:26:08" }, { "name": "sebastian/diff", @@ -5100,7 +5153,7 @@ "keywords": [ "diff" ], - "time": "2017-05-22T07:24:03+00:00" + "time": "2017-05-22 07:24:03" }, { "name": "sebastian/environment", @@ -5150,7 +5203,7 @@ "environment", "hhvm" ], - "time": "2017-07-01T08:51:00+00:00" + "time": "2017-07-01 08:51:00" }, { "name": "sebastian/exporter", @@ -5217,7 +5270,7 @@ "export", "exporter" ], - "time": "2017-04-03T13:19:02+00:00" + "time": "2017-04-03 13:19:02" }, { "name": "sebastian/finder-facade", @@ -5256,7 +5309,7 @@ ], "description": "FinderFacade is a convenience wrapper for Symfony's Finder component.", "homepage": "https://github.com/sebastianbergmann/finder-facade", - "time": "2016-02-17T07:02:23+00:00" + "time": "2016-02-17 07:02:23" }, { "name": "sebastian/global-state", @@ -5307,7 +5360,7 @@ "keywords": [ "global state" ], - "time": "2017-04-27T15:39:26+00:00" + "time": "2017-04-27 15:39:26" }, { "name": "sebastian/object-enumerator", @@ -5354,7 +5407,7 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2017-08-03T12:35:26+00:00" + "time": "2017-08-03 12:35:26" }, { "name": "sebastian/object-reflector", @@ -5399,7 +5452,7 @@ ], "description": "Allows reflection of object attributes, including inherited and non-public ones", "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "time": "2017-03-29T09:07:27+00:00" + "time": "2017-03-29 09:07:27" }, { "name": "sebastian/phpcpd", @@ -5450,7 +5503,7 @@ ], "description": "Copy/Paste Detector (CPD) for PHP code.", "homepage": "https://github.com/sebastianbergmann/phpcpd", - "time": "2016-04-17T19:32:49+00:00" + "time": "2016-04-17 19:32:49" }, { "name": "sebastian/recursion-context", @@ -5503,7 +5556,7 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2017-03-03T06:23:57+00:00" + "time": "2017-03-03 06:23:57" }, { "name": "sebastian/resource-operations", @@ -5545,7 +5598,7 @@ ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2015-07-28T20:34:47+00:00" + "time": "2015-07-28 20:34:47" }, { "name": "sebastian/version", @@ -5588,7 +5641,7 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", - "time": "2016-10-03T07:35:21+00:00" + "time": "2016-10-03 07:35:21" }, { "name": "squizlabs/php_codesniffer", @@ -5639,20 +5692,20 @@ "phpcs", "standards" ], - "time": "2017-06-14T01:23:49+00:00" + "time": "2017-06-14 01:23:49" }, { "name": "symfony/config", - "version": "v3.3.9", + "version": "v3.3.10", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "f9f19a39ee178f61bb2190f51ff7c517c2159315" + "reference": "4ab62407bff9cd97c410a7feaef04c375aaa5cfd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/f9f19a39ee178f61bb2190f51ff7c517c2159315", - "reference": "f9f19a39ee178f61bb2190f51ff7c517c2159315", + "url": "https://api.github.com/repos/symfony/config/zipball/4ab62407bff9cd97c410a7feaef04c375aaa5cfd", + "reference": "4ab62407bff9cd97c410a7feaef04c375aaa5cfd", "shasum": "" }, "require": { @@ -5701,20 +5754,20 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2017-09-04T16:28:07+00:00" + "time": "2017-10-04 18:56:58" }, { "name": "symfony/dependency-injection", - "version": "v3.3.9", + "version": "v3.3.10", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "e593f06dd90a81c7b70ac1c49862a061b0ec06d2" + "reference": "8ebad929aee3ca185b05f55d9cc5521670821ad1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/e593f06dd90a81c7b70ac1c49862a061b0ec06d2", - "reference": "e593f06dd90a81c7b70ac1c49862a061b0ec06d2", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/8ebad929aee3ca185b05f55d9cc5521670821ad1", + "reference": "8ebad929aee3ca185b05f55d9cc5521670821ad1", "shasum": "" }, "require": { @@ -5771,20 +5824,20 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2017-09-05T20:39:38+00:00" + "time": "2017-10-04 17:15:30" }, { "name": "symfony/polyfill-php54", - "version": "v1.5.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php54.git", - "reference": "b7763422a5334c914ef0298ed21b253d25913a6e" + "reference": "d7810a14b2c6c1aff415e1bb755f611b3d5327bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php54/zipball/b7763422a5334c914ef0298ed21b253d25913a6e", - "reference": "b7763422a5334c914ef0298ed21b253d25913a6e", + "url": "https://api.github.com/repos/symfony/polyfill-php54/zipball/d7810a14b2c6c1aff415e1bb755f611b3d5327bc", + "reference": "d7810a14b2c6c1aff415e1bb755f611b3d5327bc", "shasum": "" }, "require": { @@ -5793,7 +5846,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.5-dev" + "dev-master": "1.6-dev" } }, "autoload": { @@ -5829,20 +5882,20 @@ "portable", "shim" ], - "time": "2017-06-14T15:44:48+00:00" + "time": "2017-10-11 12:05:26" }, { "name": "symfony/polyfill-php55", - "version": "v1.5.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php55.git", - "reference": "29b1381d66f16e0581aab0b9f678ccf073288f68" + "reference": "b64e7f0c37ecf144ecc16668936eef94e628fbfd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php55/zipball/29b1381d66f16e0581aab0b9f678ccf073288f68", - "reference": "29b1381d66f16e0581aab0b9f678ccf073288f68", + "url": "https://api.github.com/repos/symfony/polyfill-php55/zipball/b64e7f0c37ecf144ecc16668936eef94e628fbfd", + "reference": "b64e7f0c37ecf144ecc16668936eef94e628fbfd", "shasum": "" }, "require": { @@ -5852,7 +5905,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.5-dev" + "dev-master": "1.6-dev" } }, "autoload": { @@ -5885,20 +5938,20 @@ "portable", "shim" ], - "time": "2017-06-14T15:44:48+00:00" + "time": "2017-10-11 12:05:26" }, { "name": "symfony/polyfill-php70", - "version": "v1.5.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "b6482e68974486984f59449ecea1fbbb22ff840f" + "reference": "0442b9c0596610bd24ae7b5f0a6cdbbc16d9fcff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/b6482e68974486984f59449ecea1fbbb22ff840f", - "reference": "b6482e68974486984f59449ecea1fbbb22ff840f", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/0442b9c0596610bd24ae7b5f0a6cdbbc16d9fcff", + "reference": "0442b9c0596610bd24ae7b5f0a6cdbbc16d9fcff", "shasum": "" }, "require": { @@ -5908,7 +5961,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.5-dev" + "dev-master": "1.6-dev" } }, "autoload": { @@ -5944,20 +5997,20 @@ "portable", "shim" ], - "time": "2017-06-14T15:44:48+00:00" + "time": "2017-10-11 12:05:26" }, { "name": "symfony/polyfill-php72", - "version": "v1.5.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "8abc9097f5001d310f0edba727469c988acc6ea7" + "reference": "6de4f4884b97abbbed9f0a84a95ff2ff77254254" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/8abc9097f5001d310f0edba727469c988acc6ea7", - "reference": "8abc9097f5001d310f0edba727469c988acc6ea7", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/6de4f4884b97abbbed9f0a84a95ff2ff77254254", + "reference": "6de4f4884b97abbbed9f0a84a95ff2ff77254254", "shasum": "" }, "require": { @@ -5966,7 +6019,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.5-dev" + "dev-master": "1.6-dev" } }, "autoload": { @@ -5999,20 +6052,20 @@ "portable", "shim" ], - "time": "2017-07-11T13:25:55+00:00" + "time": "2017-10-11 12:05:26" }, { "name": "symfony/polyfill-xml", - "version": "v1.5.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-xml.git", - "reference": "7d536462e554da7b05600a926303bf9b99153275" + "reference": "d7bcb5c3bb1832c532379df50825c08f43a64134" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-xml/zipball/7d536462e554da7b05600a926303bf9b99153275", - "reference": "7d536462e554da7b05600a926303bf9b99153275", + "url": "https://api.github.com/repos/symfony/polyfill-xml/zipball/d7bcb5c3bb1832c532379df50825c08f43a64134", + "reference": "d7bcb5c3bb1832c532379df50825c08f43a64134", "shasum": "" }, "require": { @@ -6022,7 +6075,7 @@ "type": "metapackage", "extra": { "branch-alias": { - "dev-master": "1.5-dev" + "dev-master": "1.6-dev" } }, "notification-url": "https://packagist.org/downloads/", @@ -6047,20 +6100,20 @@ "portable", "shim" ], - "time": "2017-06-14T15:44:48+00:00" + "time": "2017-10-11 12:05:26" }, { "name": "symfony/stopwatch", - "version": "v3.3.9", + "version": "v3.3.10", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "9a5610a8d6a50985a7be485c0ba745c22607beeb" + "reference": "170edf8b3247d7b6779eb6fa7428f342702ca184" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/9a5610a8d6a50985a7be485c0ba745c22607beeb", - "reference": "9a5610a8d6a50985a7be485c0ba745c22607beeb", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/170edf8b3247d7b6779eb6fa7428f342702ca184", + "reference": "170edf8b3247d7b6779eb6fa7428f342702ca184", "shasum": "" }, "require": { @@ -6096,7 +6149,7 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2017-07-29T21:54:42+00:00" + "time": "2017-10-02 06:42:24" }, { "name": "theseer/fdomdocument", @@ -6136,7 +6189,7 @@ ], "description": "The classes contained within this repository extend the standard DOM to use exceptions at all occasions of errors instead of PHP warnings or notices. They also add various custom methods and shortcuts for convenience and to simplify the usage of DOM.", "homepage": "https://github.com/theseer/fDOMDocument", - "time": "2017-06-30T11:53:12+00:00" + "time": "2017-06-30 11:53:12" }, { "name": "theseer/tokenizer", @@ -6176,7 +6229,7 @@ } ], "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "time": "2017-04-07T12:08:54+00:00" + "time": "2017-04-07 12:08:54" }, { "name": "webmozart/assert", @@ -6226,7 +6279,7 @@ "check", "validate" ], - "time": "2016-11-23T20:04:58+00:00" + "time": "2016-11-23 20:04:58" } ], "aliases": [], diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/Helper/JsonSerializer.php b/dev/tests/api-functional/framework/Magento/TestFramework/Helper/JsonSerializer.php new file mode 100644 index 000000000000..6a8ae3bc72ff --- /dev/null +++ b/dev/tests/api-functional/framework/Magento/TestFramework/Helper/JsonSerializer.php @@ -0,0 +1,77 @@ + 'Maximum depth exceeded', + JSON_ERROR_STATE_MISMATCH => 'State mismatch', + JSON_ERROR_CTRL_CHAR => 'Unexpected control character found', + JSON_ERROR_SYNTAX => 'Syntax error, invalid JSON', + ]; + + /** + * Encode a string as a JSON object with error checking + * + * @param mixed $data + * @return string + * @throws \Exception + */ + public function jsonEncode($data) + { + $ret = json_encode($data); + $this->checkJsonError($data); + + // return the json String + return $ret; + } + + /** + * Decode a JSON string with error checking + * + * @param string $data + * @param bool $asArray + * @throws \Exception + * @return mixed + */ + public function jsonDecode($data, $asArray = true) + { + $ret = json_decode($data, $asArray); + $this->checkJsonError($data); + + // return the array + return $ret; + } + + /** + * Checks for JSON error in the latest encoding / decoding and throws an exception in case of error + * + * @throws \Exception + */ + private function checkJsonError() + { + $jsonError = json_last_error(); + if ($jsonError !== JSON_ERROR_NONE) { + // find appropriate error message + $message = 'Unknown JSON Error'; + if (isset($this->jsonErrorMessages[$jsonError])) { + $message = $this->jsonErrorMessages[$jsonError]; + } + + throw new \Exception( + 'JSON Encoding / Decoding error: ' . $message . var_export(func_get_arg(0), true), + $jsonError + ); + } + } +} diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php new file mode 100644 index 000000000000..7cea3c2f6ee3 --- /dev/null +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php @@ -0,0 +1,95 @@ +curlClient = $curlClient ?: $objectManager->get(CurlClient::class); + $this->json = $json ?: $objectManager->get(JsonSerializer::class); + } + + /** + * Perform HTTP POST request for query + * + * @param string $query + * @param array $variables + * @param string $operationName + * @param array $headers + * @return array|string|int|float|bool + * @throws \Exception + */ + public function postQuery(string $query, array $variables = [], string $operationName = '', array $headers = []) + { + $url = $this->getEndpointUrl(); + $headers = array_merge($headers, ['Accept: application/json', 'Content-Type: application/json']); + $requestArray = [ + 'query' => $query, + 'variables' => empty($variables) ? $variables : null, + 'operationName' => empty($operationName) ? $operationName : null + ]; + $postData = $this->json->jsonEncode($requestArray); + + $responseBody = $this->curlClient->post($url, $postData, $headers); + $responseBodyArray = $this->json->jsonDecode($responseBody); + + if (isset($responseBodyArray['errors'])) { + $errorMessage = ''; + if (is_array($responseBodyArray['errors'])) { + foreach ($responseBodyArray['errors'] as $error) { + if (isset($error['message'])) { + $errorMessage .= $error['message'] . PHP_EOL; + } + } + throw new \Exception('GraphQL response contains errors: ' . $errorMessage); + } + throw new \Exception('GraphQL responded with an unknown error: ' . $responseBody); + } elseif (!isset($responseBodyArray['data'])) { + throw new \Exception('Unknown GraphQL response body: ' . $responseBody); + } + + return $responseBodyArray['data']; + } + + /** + * @return string resource URL + * @throws \Exception + */ + public function getEndpointUrl() + { + return rtrim(TESTS_BASE_URL, '/') . '/graphql'; + } +} diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQlAbstract.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQlAbstract.php new file mode 100644 index 000000000000..c76c22c4cca0 --- /dev/null +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQlAbstract.php @@ -0,0 +1,49 @@ +getGraphQlClient()->postQuery($query, $variables, $operationName); + } + + /** + * Get GraphQL adapter (create if requested one does not exist). + * + * @return \Magento\TestFramework\TestCase\GraphQl\Client + */ + private function getGraphQlClient() + { + if ($this->graphQlClient === null) { + return Bootstrap::getObjectManager()->get(\Magento\TestFramework\TestCase\GraphQl\Client::class); + } else { + $this->graphQlClient; + } + } +} diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClient.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClient.php new file mode 100644 index 000000000000..787f207ef33e --- /dev/null +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClient.php @@ -0,0 +1,172 @@ +invokeApi($url, $curlOpts, $headers); + return $resp["body"]; + } + + /** + * Perform HTTP DELETE request + * + * @param string $url + * @param array $headers + * @return string + */ + public function delete($url, $headers = []) + { + $curlOpts = []; + $curlOpts[CURLOPT_CUSTOMREQUEST] = \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_DELETE; + + $resp = $this->invokeApi($url, $curlOpts, $headers); + return $resp["body"]; + } + + /** + * Perform HTTP POST request + * + * @param string $url + * @param array|string $data + * @param array $headers + * @return string + */ + public function post($url, $data, $headers = []) + { + $curlOpts = []; + $curlOpts[CURLOPT_CUSTOMREQUEST] = \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST; + $headers[] = 'Content-Length: ' . strlen($data); + $curlOpts[CURLOPT_POSTFIELDS] = $data; + + $resp = $this->invokeApi($url, $curlOpts, $headers); + return $resp["body"]; + } + + /** + * Perform HTTP PUT request + * + * @param string $url + * @param array|string $data + * @param array $headers + * @return string + */ + public function put($url, $data, $headers = []) + { + $curlOpts = []; + $curlOpts[CURLOPT_CUSTOMREQUEST] = \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_PUT; + $headers[] = 'Content-Length: ' . strlen($data); + $curlOpts[CURLOPT_POSTFIELDS] = $data; + + $resp = $this->invokeApi($url, $curlOpts, $headers); + return $resp["body"]; + } + + /** + * Makes the REST api call using passed $curl object + * + * @param string $url + * @param array $additionalCurlOpts cURL Options + * @param array $headers + * @return array + * @throws \Exception + */ + public function invokeApi($url, $additionalCurlOpts, $headers = []) + { + // initialize cURL + $curl = curl_init($url); + if ($curl === false) { + throw new \Exception("Error Initializing cURL for baseUrl: " . $url); + } + + // get cURL options + $curlOpts = $this->getCurlOptions($additionalCurlOpts, $headers); + + // add CURL opts + foreach ($curlOpts as $opt => $val) { + curl_setopt($curl, $opt, $val); + } + + $response = curl_exec($curl); + if ($response === false) { + throw new \Exception(curl_error($curl)); + } + + $resp = []; + $headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE); + $resp["header"] = substr($response, 0, $headerSize); + $resp["body"] = substr($response, $headerSize); + + $resp["meta"] = curl_getinfo($curl); + if ($resp["meta"] === false) { + throw new \Exception(curl_error($curl)); + } + + curl_close($curl); + + $meta = $resp["meta"]; + if ($meta && $meta['http_code'] >= 400) { + throw new \Exception($resp["body"], $meta['http_code']); + } + + return $resp; + } + + /** + * Constructs and returns a curl options array + * + * @param array $customCurlOpts Additional / overridden cURL options + * @param array $headers + * @return array + */ + private function getCurlOptions($customCurlOpts = [], $headers = []) + { + // default curl options + $curlOpts = [ + CURLOPT_RETURNTRANSFER => true, // return result instead of echoing + CURLOPT_SSL_VERIFYPEER => false, // stop cURL from verifying the peer's certificate + CURLOPT_FOLLOWLOCATION => false, // follow redirects, Location: headers + CURLOPT_MAXREDIRS => 10, // but don't redirect more than 10 times + CURLOPT_HTTPHEADER => [], + CURLOPT_HEADER => 1, + ]; + + // merge headers + $headers = array_merge($curlOpts[CURLOPT_HTTPHEADER], $headers); + if (TESTS_XDEBUG_ENABLED) { + $headers[] = 'Cookie: XDEBUG_SESSION=' . TESTS_XDEBUG_SESSION; + } + $curlOpts[CURLOPT_HTTPHEADER] = $headers; + + // merge custom Curl Options & return + foreach ($customCurlOpts as $opt => $val) { + $curlOpts[$opt] = $val; + } + + return $curlOpts; + } +} diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Curl.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClientWithCookies.php similarity index 73% rename from dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Curl.php rename to dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClientWithCookies.php index 1af16a9e8ca9..ecd748d89bb2 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Curl.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClientWithCookies.php @@ -4,17 +4,39 @@ * See COPYING.txt for license details. */ -namespace Magento\TestFramework\TestCase\Webapi; +namespace Magento\TestFramework\TestCase\HttpClient; + +use Magento\TestFramework\Helper\JsonSerializer; +use Magento\TestFramework\Helper\Bootstrap; /** - * A Curl client that can be called independently, outside of REST controller. - * - * Used by CookieManager tests. + * A Curl client that can be called independently, outside of any Web API controller used by CookieManager tests. */ -class Curl extends Adapter\Rest\CurlClient +class CurlClientWithCookies { const COOKIE_HEADER = 'Set-Cookie: '; + /** @var CurlClient */ + protected $curlClient; + + /** @var JsonSerializer */ + protected $jsonSerializer; + + /** + * CurlClient constructor. + * + * @param CurlClient $curlClient + * @param \Magento\TestFramework\Helper\JsonSerializer $jsonSerializer + */ + public function __construct( + CurlClient $curlClient, + \Magento\TestFramework\Helper\JsonSerializer $jsonSerializer + ) { + $objectManager = Bootstrap::getObjectManager(); + $this->curlClient = $curlClient ? : $objectManager->get(CurlClient::class); + $this->jsonSerializer = $jsonSerializer ? : $objectManager->get(JsonSerializer::class); + } + /** * @param string $resourcePath Resource URL like /V1/Resource1/123 * @return string resource URL @@ -43,7 +65,7 @@ public function get($resourcePath, $data = [], $headers = []) $curlOpts = []; $curlOpts[CURLOPT_CUSTOMREQUEST] = \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_GET; $curlOpts[CURLOPT_SSLVERSION] = 3; - $response = $this->_invokeApi($url, $curlOpts, $headers); + $response = $this->curlClient->invokeApi($url, $curlOpts, $headers); $response['cookies'] = $this->cookieParse($response['header']); return $response; } diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest.php index d3938f72cd0a..49a1fa9de943 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest.php @@ -1,7 +1,5 @@ _config = $objectManager->get(\Magento\Webapi\Model\Config::class); - $this->curlClient = $objectManager->get(\Magento\TestFramework\TestCase\Webapi\Adapter\Rest\CurlClient::class); + $this->restClient = $objectManager->get(\Magento\TestFramework\TestCase\Webapi\Adapter\Rest\RestClient::class); $this->documentationGenerator = $objectManager->get( \Magento\TestFramework\TestCase\Webapi\Adapter\Rest\DocumentationGenerator::class ); @@ -82,7 +83,7 @@ public function call($serviceInfo, $arguments = [], $storeCode = null, $integrat $authHeader = $oAuthClient->buildBearerTokenAuthorizationHeader($restServiceInfo['token']); } else { $authHeader = $oAuthClient->buildOauthAuthorizationHeader( - $this->curlClient->constructResourceUrl($resourcePath), + $this->restClient->constructResourceUrl($resourcePath), $accessCredentials['key'], $accessCredentials['secret'], ($httpMethod == 'PUT' || $httpMethod == 'POST') && $urlFormEncoded ? $arguments : [], @@ -92,16 +93,16 @@ public function call($serviceInfo, $arguments = [], $storeCode = null, $integrat $authHeader = array_merge($authHeader, ['Accept: application/json', 'Content-Type: application/json']); switch ($httpMethod) { case Request::HTTP_METHOD_GET: - $response = $this->curlClient->get($resourcePath, [], $authHeader); + $response = $this->restClient->get($resourcePath, [], $authHeader); break; case Request::HTTP_METHOD_POST: - $response = $this->curlClient->post($resourcePath, $arguments, $authHeader); + $response = $this->restClient->post($resourcePath, $arguments, $authHeader); break; case Request::HTTP_METHOD_PUT: - $response = $this->curlClient->put($resourcePath, $arguments, $authHeader); + $response = $this->restClient->put($resourcePath, $arguments, $authHeader); break; case Request::HTTP_METHOD_DELETE: - $response = $this->curlClient->delete($resourcePath, $authHeader); + $response = $this->restClient->delete($resourcePath, $authHeader); break; default: throw new \LogicException("HTTP method '{$httpMethod}' is not supported."); diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/CurlClient.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/CurlClient.php deleted file mode 100644 index 29198624e931..000000000000 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/CurlClient.php +++ /dev/null @@ -1,310 +0,0 @@ - 'Maximum depth exceeded', - JSON_ERROR_STATE_MISMATCH => 'State mismatch', - JSON_ERROR_CTRL_CHAR => 'Unexpected control character found', - JSON_ERROR_SYNTAX => 'Syntax error, invalid JSON', - ]; - - /** - * Perform HTTP GET request - * - * @param string $resourcePath Resource URL like /V1/Resource1/123 - * @param array $data - * @param array $headers - * @return mixed - */ - public function get($resourcePath, $data = [], $headers = []) - { - $url = $this->constructResourceUrl($resourcePath); - if (!empty($data)) { - $url .= '?' . http_build_query($data); - } - - $curlOpts = []; - $curlOpts[CURLOPT_CUSTOMREQUEST] = \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_GET; - $resp = $this->_invokeApi($url, $curlOpts, $headers); - $respArray = $this->_jsonDecode($resp["body"]); - return $respArray; - } - - /** - * Perform HTTP POST request - * - * @param string $resourcePath Resource URL like /V1/Resource1/123 - * @param array $data - * @param array $headers - * @return mixed - */ - public function post($resourcePath, $data, $headers = []) - { - return $this->_postOrPut($resourcePath, $data, false, $headers); - } - - /** - * Perform HTTP PUT request - * - * @param string $resourcePath Resource URL like /V1/Resource1/123 - * @param array $data - * @param array $headers - * @return mixed - */ - public function put($resourcePath, $data, $headers = []) - { - return $this->_postOrPut($resourcePath, $data, true, $headers); - } - - /** - * Perform HTTP DELETE request - * - * @param string $resourcePath Resource URL like /V1/Resource1/123 - * @param array $headers - * @return mixed - */ - public function delete($resourcePath, $headers = []) - { - $url = $this->constructResourceUrl($resourcePath); - - $curlOpts = []; - $curlOpts[CURLOPT_CUSTOMREQUEST] = \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_DELETE; - - $resp = $this->_invokeApi($url, $curlOpts, $headers); - $respArray = $this->_jsonDecode($resp["body"]); - - return $respArray; - } - - /** - * Perform HTTP POST or PUT request - * - * @param string $resourcePath Resource URL like /V1/Resource1/123 - * @param array $data - * @param boolean $put Set true to post data as HTTP PUT operation (update). If this value is set to false, - * HTTP POST (create) will be used - * @param array $headers - * @return mixed - */ - protected function _postOrPut($resourcePath, $data, $put = false, $headers = []) - { - $url = $this->constructResourceUrl($resourcePath); - - if (in_array("Content-Type: application/json", $headers)) { - // json encode data - if ($data != self::EMPTY_REQUEST_BODY) { - $data = $this->_jsonEncode($data); - } else { - $data = ''; - } - } - - $curlOpts = []; - if ($put) { - $curlOpts[CURLOPT_CUSTOMREQUEST] = \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_PUT; - } else { - $curlOpts[CURLOPT_CUSTOMREQUEST] = \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST; - } - $headers[] = 'Content-Length: ' . strlen($data); - $curlOpts[CURLOPT_POSTFIELDS] = $data; - - $this->responseArray = $this->_invokeApi($url, $curlOpts, $headers); - $respBodyArray = $this->_jsonDecode($this->responseArray["body"]); - - return $respBodyArray; - } - - /** - * Set Rest base path if available - * - * @param string $restBasePath - * - * @return void - */ - public function setRestBasePath($restBasePath) - { - $this->restBasePath = $restBasePath; - } - - /** - * Get current response array - * - * @return array - */ - public function getCurrentResponseArray() - { - return $this->responseArray; - } - - /** - * @param string $resourcePath Resource URL like /V1/Resource1/123 - * @return string resource URL - * @throws \Exception - */ - public function constructResourceUrl($resourcePath) - { - return rtrim(TESTS_BASE_URL, '/') . $this->restBasePath . ltrim($resourcePath, '/'); - } - - /** - * Makes the REST api call using passed $curl object - * - * @param string $url - * @param array $additionalCurlOpts cURL Options - * @param array $headers - * @return array - * @throws \Exception - */ - protected function _invokeApi($url, $additionalCurlOpts, $headers = []) - { - // initialize cURL - $curl = curl_init($url); - if ($curl === false) { - throw new \Exception("Error Initializing cURL for baseUrl: " . $url); - } - - // get cURL options - $curlOpts = $this->_getCurlOptions($additionalCurlOpts, $headers); - - // add CURL opts - foreach ($curlOpts as $opt => $val) { - curl_setopt($curl, $opt, $val); - } - - $response = curl_exec($curl); - if ($response === false) { - throw new \Exception(curl_error($curl)); - } - - $resp = []; - $headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE); - $resp["header"] = substr($response, 0, $headerSize); - $resp["body"] = substr($response, $headerSize); - - $resp["meta"] = curl_getinfo($curl); - if ($resp["meta"] === false) { - throw new \Exception(curl_error($curl)); - } - - curl_close($curl); - - $meta = $resp["meta"]; - if ($meta && $meta['http_code'] >= 400) { - throw new \Exception($resp["body"], $meta['http_code']); - } - - return $resp; - } - - /** - * Constructs and returns a curl options array - * - * @param array $customCurlOpts Additional / overridden cURL options - * @param array $headers - * @return array - */ - protected function _getCurlOptions($customCurlOpts = [], $headers = []) - { - // default curl options - $curlOpts = [ - CURLOPT_RETURNTRANSFER => true, // return result instead of echoing - CURLOPT_SSL_VERIFYPEER => false, // stop cURL from verifying the peer's certificate - CURLOPT_FOLLOWLOCATION => false, // follow redirects, Location: headers - CURLOPT_MAXREDIRS => 10, // but don't redirect more than 10 times - CURLOPT_HTTPHEADER => [], - CURLOPT_HEADER => 1, - ]; - - // merge headers - $headers = array_merge($curlOpts[CURLOPT_HTTPHEADER], $headers); - if (TESTS_XDEBUG_ENABLED) { - $headers[] = 'Cookie: XDEBUG_SESSION=' . TESTS_XDEBUG_SESSION; - } - $curlOpts[CURLOPT_HTTPHEADER] = $headers; - - // merge custom Curl Options & return - foreach ($customCurlOpts as $opt => $val) { - $curlOpts[$opt] = $val; - } - - return $curlOpts; - } - - /** - * JSON encode with error checking - * - * @param mixed $data - * @return string - * @throws \Exception - */ - protected function _jsonEncode($data) - { - $ret = json_encode($data); - $this->_checkJsonError($data); - - // return the json String - return $ret; - } - - /** - * Decode a JSON string with error checking - * - * @param string $data - * @param bool $asArray - * @throws \Exception - * @return mixed - */ - protected function _jsonDecode($data, $asArray = true) - { - $ret = json_decode($data, $asArray); - $this->_checkJsonError($data); - - // return the array - return $ret; - } - - /** - * Checks for JSON error in the latest encoding / decoding and throws an exception in case of error - * - * @throws \Exception - */ - protected function _checkJsonError() - { - $jsonError = json_last_error(); - if ($jsonError !== JSON_ERROR_NONE) { - // find appropriate error message - $message = 'Unknown JSON Error'; - if (isset($this->_jsonErrorMessages[$jsonError])) { - $message = $this->_jsonErrorMessages[$jsonError]; - } - - throw new \Exception( - 'JSON Encoding / Decoding error: ' . $message . var_export(func_get_arg(0), true), - $jsonError - ); - } - } -} diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/RestClient.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/RestClient.php new file mode 100644 index 000000000000..18f9e2e09bbe --- /dev/null +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/RestClient.php @@ -0,0 +1,133 @@ +curlClient = $curlClient ? : $objectManager->get(CurlClient::class); + $this->jsonSerializer = $jsonSerializer ? : $objectManager->get(JsonSerializer::class); + } + + /** + * Perform HTTP GET request + * + * @param string $resourcePath Resource URL like /V1/Resource1/123 + * @param array $data + * @param array $headers + * @return mixed + */ + public function get($resourcePath, $data = [], $headers = []) + { + $url = $this->constructResourceUrl($resourcePath); + if (!empty($data)) { + $url .= '?' . http_build_query($data); + } + + $responseBody = $this->curlClient->get($url, $data, $headers); + return $this->jsonSerializer->jsonDecode($responseBody); + } + + /** + * Perform HTTP POST request + * + * @param string $resourcePath Resource URL like /V1/Resource1/123 + * @param array $data + * @param array $headers + * @return mixed + */ + public function post($resourcePath, $data, $headers = []) + { + $url = $this->constructResourceUrl($resourcePath); + if (in_array("Content-Type: application/json", $headers)) { + // json encode data + if ($data != self::EMPTY_REQUEST_BODY) { + $data = $this->jsonSerializer->jsonEncode($data); + } else { + $data = ''; + } + } + $responseBody = $this->curlClient->post($url, $data, $headers); + return $this->jsonSerializer->jsonDecode($responseBody); + } + + /** + * Perform HTTP PUT request + * + * @param string $resourcePath Resource URL like /V1/Resource1/123 + * @param array $data + * @param array $headers + * @return mixed + */ + public function put($resourcePath, $data, $headers = []) + { + $url = $this->constructResourceUrl($resourcePath); + if (in_array("Content-Type: application/json", $headers)) { + // json encode data + if ($data != self::EMPTY_REQUEST_BODY) { + $data = $this->jsonSerializer->jsonEncode($data); + } else { + $data = ''; + } + } + $responseBody = $this->curlClient->put($url, $data, $headers); + return $this->jsonSerializer->jsonDecode($responseBody); + } + + /** + * Perform HTTP DELETE request + * + * @param string $resourcePath Resource URL like /V1/Resource1/123 + * @param array $headers + * @return mixed + */ + public function delete($resourcePath, $headers = []) + { + $url = $this->constructResourceUrl($resourcePath); + $responseBody = $this->curlClient->delete($url, $headers); + return $this->jsonSerializer->jsonDecode($responseBody); + } + + /** + * @param string $resourcePath Resource URL like /V1/Resource1/123 + * @return string resource URL + * @throws \Exception + */ + public function constructResourceUrl($resourcePath) + { + return rtrim(TESTS_BASE_URL, '/') . $this->restBasePath . ltrim($resourcePath, '/'); + } +} diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php index d09b6095af00..9ad051b686d4 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php @@ -1,7 +1,5 @@ _webApiAdapters[$webApiAdapterCode] = new $this->_webApiAdaptersMap[$webApiAdapterCode](); + $this->_webApiAdapters[$webApiAdapterCode] = Bootstrap::getObjectManager()->get( + $this->_webApiAdaptersMap[$webApiAdapterCode] + ); } return $this->_webApiAdapters[$webApiAdapterCode]; } diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/GraphQl/ProductViewTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/GraphQl/ProductViewTest.php new file mode 100644 index 000000000000..3e488c379cc3 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/GraphQl/ProductViewTest.php @@ -0,0 +1,565 @@ +graphQlQuery($query); + + /** + * @var ProductRepositoryInterface $productRepository + */ + + $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); + $product = $productRepository->get($prductSku, false, null, true); + $this->assertArrayHasKey('product', $response); + $this->assertBaseFields($product, $response['product']); + $this->assertEavAttributes($product, $response['product']); + $this->assertCategoryIds($product, $response['product']); + $this->assertOptions($product, $response['product']); + $this->assertTierPrices($product, $response['product']); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/product_simple_with_media_gallery_entries.php + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testQueryMediaGalleryEntryFieldsSimpleProduct() + { + + $prductSku = 'simple'; + + $query = <<graphQlQuery($query); + + /** + * @var ProductRepositoryInterface $productRepository + */ + $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); + $product = $productRepository->get($prductSku, false, null, true); + $this->assertMediaGalleryEntries($product, $response['product']); + } + + /** + * @param ProductInterface $product + * @param array $actualResponse + */ + private function assertMediaGalleryEntries($product, $actualResponse) + { + $mediaGalleryEntries = $product->getMediaGalleryEntries(); + $this->assertCount(1, $mediaGalleryEntries, "Precondition failed, incorrect number of media gallery entries."); + $this->assertTrue( + is_array([$actualResponse['media_gallery_entries']]), + "Media galleries field must be of an array type." + ); + $this->assertCount(1, $actualResponse['media_gallery_entries'], "There must be 1 record in media gallery."); + $mediaGalleryEntry = $mediaGalleryEntries[0]; + $this->assertResponseFields( + $actualResponse['media_gallery_entries'][0], + [ + 'disabled' => (bool)$mediaGalleryEntry->isDisabled(), + 'file' => $mediaGalleryEntry->getFile(), + 'id' => $mediaGalleryEntry->getId(), + 'label' => $mediaGalleryEntry->getLabel(), + 'media_type' => $mediaGalleryEntry->getMediaType(), + 'position' => $mediaGalleryEntry->getPosition(), + ] + ); + $videoContent = $mediaGalleryEntry->getExtensionAttributes()->getVideoContent(); + $this->assertResponseFields( + $actualResponse['media_gallery_entries'][0]['video_content'], + [ + 'media_type' => $videoContent->getMediaType(), + 'video_description' => $videoContent->getVideoDescription(), + 'video_metadata' => $videoContent->getVideoMetadata(), + 'video_provider' => $videoContent->getVideoProvider(), + 'video_title' => $videoContent->getVideoTitle(), + 'video_url' => $videoContent->getVideoUrl(), + ] + ); + } + + /** + * @param ProductInterface $product + * @param array $actualResponse + */ + private function assertCategoryIds($product, $actualResponse) + { + $categoryIdsAttribute = $product->getCustomAttribute('category_ids'); + $this->assertNotEmpty($categoryIdsAttribute, "Precondition failed: 'category_ids' must not be empty"); + $categoryIdsAttributeValue = $categoryIdsAttribute ? $categoryIdsAttribute->getValue() : []; + $expectedValue = implode(',', $categoryIdsAttributeValue); + $this->assertEquals($expectedValue, $actualResponse['category_ids']); + } + + /** + * @param ProductInterface $product + * @param $actualResponse + */ + private function assertTierPrices($product, $actualResponse) + { + $tierPrices = $product->getTierPrices(); + $this->assertNotEmpty($actualResponse['tier_prices'], "Precondition failed: 'tier_prices' must not be empty"); + foreach ($actualResponse['tier_prices'] as $tierPriceIndex => $tierPriceArray) { + foreach ($tierPriceArray as $key => $value) { + /** + * @var \Magento\Catalog\Model\Product\TierPrice $tierPrice + */ + $tierPrice = $tierPrices[$tierPriceIndex]; + $this->assertEquals($value, $tierPrice->getData($key)); + } + } + } + + /** + * @param ProductInterface $product + * @param $actualResponse + */ + private function assertOptions($product, $actualResponse) + { + $productOptions = $product->getOptions(); + $this->assertNotEmpty($actualResponse['options'], "Precondition failed: 'options' must not be empty"); + foreach ($actualResponse['options'] as $optionsIndex => $optionsArray) { + /** @var \Magento\Catalog\Model\Product\Option $option */ + $option = $productOptions[$optionsIndex]; + $assertionMap = [ + ['response_field' => 'product_sku', 'expected_value' => $option->getProductSku()], + ['response_field' => 'sort_order', 'expected_value' => $option->getSortOrder()], + ['response_field' => 'title', 'expected_value' => $option->getTitle()], + ['response_field' => 'type', 'expected_value' => $option->getType()], + ['response_field' => 'option_id', 'expected_value' => $option->getOptionId()], + ['response_field' => 'is_require', 'expected_value' => $option->getIsRequire()], + ['response_field' => 'sort_order', 'expected_value' => $option->getSortOrder()] + ]; + + if (!empty($option->getValues())) { + $value = current($optionsArray['values']); + /** @var \Magento\Catalog\Model\Product\Option\Value $productValue */ + $productValue = current($option->getValues()); + $assertionMapValues = [ + ['response_field' => 'title', 'expected_value' => $productValue->getTitle()], + ['response_field' => 'sort_order', 'expected_value' => $productValue->getSortOrder()], + ['response_field' => 'price', 'expected_value' => $productValue->getPrice()], + ['response_field' => 'price_type', 'expected_value' => $productValue->getPriceType()], + ['response_field' => 'sku', 'expected_value' => $productValue->getSku()], + ['response_field' => 'option_type_id', 'expected_value' => $productValue->getOptionTypeId()] + ]; + $this->assertResponseFields($value, $assertionMapValues); + } else { + if ($option->getType() === 'file') { + $assertionMap = array_merge( + $assertionMap, + [ + ['response_field' => 'file_extension', 'expected_value' => $option->getFileExtension()], + ['response_field' => 'image_size_x', 'expected_value' => $option->getImageSizeX()], + ['response_field' => 'image_size_y', 'expected_value' => $option->getImageSizeY()] + ] + ); + } elseif ($option->getType() === 'area') { + $assertionMap = array_merge( + $assertionMap, + [ + ['response_field' => 'max_characters', 'expected_value' => $option->getMaxCharacters()], + ] + ); + } + + $assertionMap = array_merge( + $assertionMap, + [ + ['response_field' => 'price', 'expected_value' => $option->getPrice()], + ['response_field' => 'price_type', 'expected_value' => $option->getPriceType()], + ['response_field' => 'sku', 'expected_value' => $option->getSku()] + ] + ); + } + $this->assertResponseFields($optionsArray, $assertionMap); + } + } + + /** + * @param ProductInterface $product + * @param array $actualResponse + */ + private function assertBaseFields($product, $actualResponse) + { + /** + * ['product_object_field_name', 'expected_value'] + */ + $assertionMap = [ + ['response_field' => 'attribute_set_id', 'expected_value' => $product->getAttributeSetId()], + ['response_field' => 'created_at', 'expected_value' => $product->getCreatedAt()], + ['response_field' => 'id', 'expected_value' => $product->getId()], + ['response_field' => 'name', 'expected_value' => $product->getName()], + ['response_field' => 'price', 'expected_value' => $product->getPrice()], + ['response_field' => 'sku', 'expected_value' => $product->getSku()], + ['response_field' => 'status', 'expected_value' => $product->getStatus()], + ['response_field' => 'type_id', 'expected_value' => $product->getTypeId()], + ['response_field' => 'updated_at', 'expected_value' => $product->getUpdatedAt()], + ['response_field' => 'visibility', 'expected_value' => $product->getVisibility()], + ['response_field' => 'weight', 'expected_value' => $product->getWeight()], + ]; + + $this->assertResponseFields($actualResponse, $assertionMap); + } + + /** + * @param ProductInterface $product + * @param array $actualResponse + */ + private function assertEavAttributes($product, $actualResponse) + { + $eavAttributes = [ + 'url_key', + 'description', + 'has_options', + 'meta_description', + 'meta_keyword', + 'meta_title', + 'short_description', + 'tax_class_id', + 'country_of_manufacture', + 'msrp', + 'gift_message_available', + 'has_options', + 'minimal_price', + 'msrp_display_actual_price_type', + 'news_from_date', + 'old_id', + 'options_container', + 'required_options', + 'special_price', + 'special_from_date', + 'special_to_date', + ]; + $assertionMap = []; + foreach ($eavAttributes as $attributeCode) { + $expectedAttribute = $product->getCustomAttribute($attributeCode); + $assertionMap[] = [ + 'response_field' => $attributeCode, + 'expected_value' => $expectedAttribute ? $expectedAttribute->getValue() : null + ]; + } + + $this->assertResponseFields($actualResponse, $assertionMap); + } + + /** + * @param array $actualResponse + * @param array $assertionMap ['response_field_name' => 'response_field_value', ...] + * OR [['response_field' => $field, 'expected_value' => $value], ...] + */ + private function assertResponseFields($actualResponse, $assertionMap) + { + foreach ($assertionMap as $key => $assertionData) { + $expectedValue = isset($assertionData['expected_value']) + ? $assertionData['expected_value'] + : $assertionData; + $responseField = isset($assertionData['response_field']) ? $assertionData['response_field'] : $key; + $this->assertNotNull( + $expectedValue, + "Value of '{$responseField}' field must not be NULL" + ); + $this->assertEquals( + $expectedValue, + $actualResponse[$responseField], + "Value of '{$responseField}' field in response does not match expected value: " + . var_export($expectedValue, true) + ); + } + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/ConfigurableProduct/GraphQl/ConfigurableProductViewTest.php b/dev/tests/api-functional/testsuite/Magento/ConfigurableProduct/GraphQl/ConfigurableProductViewTest.php new file mode 100644 index 000000000000..fc6993545339 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/ConfigurableProduct/GraphQl/ConfigurableProductViewTest.php @@ -0,0 +1,255 @@ +graphQlQuery($query); + + /** + * @var ProductRepositoryInterface $productRepository + */ + $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); + $product = $productRepository->get($productSku, false, null, true); + + $this->assertArrayHasKey('product', $response); + $this->assertBaseFields($product, $response['product']); + $this->assertConfigurableProductLinks($response['product']); + } + + /** + * @param ProductInterface $product + * @param array $actualResponse + */ + private function assertBaseFields($product, $actualResponse) + { + /** + * ['product_object_field_name', 'expected_value'] + */ + $assertionMap = [ + ['response_field' => 'attribute_set_id', 'expected_value' => $product->getAttributeSetId()], + ['response_field' => 'created_at', 'expected_value' => $product->getCreatedAt()], + ['response_field' => 'id', 'expected_value' => $product->getId()], + ['response_field' => 'name', 'expected_value' => $product->getName()], + ['response_field' => 'sku', 'expected_value' => $product->getSku()], + ['response_field' => 'status', 'expected_value' => $product->getStatus()], + ['response_field' => 'type_id', 'expected_value' => $product->getTypeId()], + ['response_field' => 'updated_at', 'expected_value' => $product->getUpdatedAt()], + ['response_field' => 'visibility', 'expected_value' => $product->getVisibility()], + ['response_field' => 'weight', 'expected_value' => $product->getWeight()], + ]; + + $this->assertResponseFields($actualResponse, $assertionMap); + } + + /** + * Asserts various fields for child products for a configurable products + * + * @param $actualResponse + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + private function assertConfigurableProductLinks($actualResponse) + { + $this->assertNotEmpty( + $actualResponse['configurable_product_links'], + "Precondition failed: 'configurable_product_links' must not be empty" + ); + foreach ($actualResponse[ + 'configurable_product_links' + ] as $configurableProductLinkIndex => $configurableProductLinkArray) { + $this->assertNotEmpty($configurableProductLinkArray); + $this->assertTrue( + isset($configurableProductLinkArray['id']), + 'configurable_product_links elements don\'t contain id key' + ); + $indexValue = $configurableProductLinkArray['id']; + unset($configurableProductLinkArray['id']); + $this->assertTrue( + isset($configurableProductLinkArray['category_ids']), + 'configurable_product_links doesn\'t contain category_ids key' + ); + $this->assertTrue( + isset($configurableProductLinkArray['category_links']), + 'configurable_product_links doesn\'t contain category_links key' + ); + $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); + /** @var \Magento\Catalog\Model\Product $childProduct */ + $childProduct = $productRepository->getById($indexValue); + + /** @var \Magento\Catalog\Api\Data\ProductLinkInterface[] */ + $links = $childProduct->getExtensionAttributes()->getCategoryLinks(); + $this->assertCount(1, $links, "Precondition failed, incorrect number of category_links."); + $position =$links[0]->getPosition(); + $categoryId = $links[0]->getCategoryId(); + + $actualValue + = $actualResponse['configurable_product_links'][$configurableProductLinkIndex]['category_links'][0]; + $this->assertEquals($actualValue, ['position' => $position, 'category_id' =>$categoryId]); + unset($configurableProductLinkArray['category_links']); + + $categoryIdsAttribute = $childProduct->getCustomAttribute('category_ids'); + $this->assertNotEmpty($categoryIdsAttribute, "Precondition failed: 'category_ids' must not be empty"); + $categoryIdsAttributeValue = $categoryIdsAttribute ? $categoryIdsAttribute->getValue() : []; + $expectedValue = implode(',', $categoryIdsAttributeValue); + $this->assertEquals($expectedValue, $actualResponse['category_ids']); + unset($configurableProductLinkArray['category_ids']); + + $mediaGalleryEntries = $childProduct->getMediaGalleryEntries(); + $this->assertCount( + 1, + $mediaGalleryEntries, + "Precondition failed since there are incorrect number of media gallery entries" + ); + $this->assertTrue( + is_array( + $actualResponse['configurable_product_links'] + [$configurableProductLinkIndex] + ['media_gallery_entries'] + ) + ); + $this->assertCount( + 1, + $actualResponse['configurable_product_links'][$configurableProductLinkIndex]['media_gallery_entries'], + "there must be 1 record in the media gallery" + ); + $mediaGalleryEntry = $mediaGalleryEntries[0]; + $this->assertResponseFields( + $actualResponse['configurable_product_links'] + [$configurableProductLinkIndex] + ['media_gallery_entries'][0], + [ + 'disabled' => (bool)$mediaGalleryEntry->isDisabled(), + 'file' => $mediaGalleryEntry->getFile(), + 'id' => $mediaGalleryEntry->getId(), + 'label' => $mediaGalleryEntry->getLabel(), + 'media_type' => $mediaGalleryEntry->getMediaType(), + 'position' => $mediaGalleryEntry->getPosition() + ] + ); + $videoContent = $mediaGalleryEntry->getExtensionAttributes()->getVideoContent(); + $this->assertResponseFields( + $actualResponse['configurable_product_links'] + [$configurableProductLinkIndex] + ['media_gallery_entries'] + [0] + ['video_content'], + [ + 'media_type' =>$videoContent->getMediaType(), + 'video_description' => $videoContent->getVideoDescription(), + 'video_metadata' =>$videoContent->getVideoMetadata(), + 'video_provider' => $videoContent->getVideoProvider(), + 'video_title' => $videoContent->getVideoTitle(), + 'video_url' => $videoContent->getVideoUrl() + ] + ); + unset($configurableProductLinkArray['media_gallery_entries']); + + foreach ($configurableProductLinkArray as $key => $value) { + $this->assertEquals($value, $childProduct->getData($key)); + } + } + } + + /** + * @param array $actualResponse + * @param array $assertionMap ['response_field_name' => 'response_field_value', ...] + * OR [['response_field' => $field, 'expected_value' => $value], ...] + */ + private function assertResponseFields(array $actualResponse, array $assertionMap) + { + foreach ($assertionMap as $key => $assertionData) { + $expectedValue = isset($assertionData['expected_value']) + ? $assertionData['expected_value'] + : $assertionData; + $responseField = isset($assertionData['response_field']) ? $assertionData['response_field'] : $key; + $this->assertNotNull( + $expectedValue, + "Value of '{$responseField}' field must not be NULL" + ); + $this->assertEquals( + $expectedValue, + $actualResponse[$responseField], + "Value of '{$responseField}' field in response does not match expected value: " + . var_export($expectedValue, true) + ); + } + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/Framework/Stdlib/CookieManagerTest.php b/dev/tests/api-functional/testsuite/Magento/Framework/Stdlib/CookieManagerTest.php index 36efc5994449..724a43d8dcba 100644 --- a/dev/tests/api-functional/testsuite/Magento/Framework/Stdlib/CookieManagerTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Framework/Stdlib/CookieManagerTest.php @@ -7,7 +7,7 @@ */ use Magento\TestFramework\Helper\Bootstrap; -use Magento\TestFramework\TestCase\Webapi\Curl; +use Magento\TestFramework\TestCase\HttpClient\CurlClientWithCookies; /** * End to end test of the Cookie Manager, using curl. @@ -18,14 +18,16 @@ class CookieManagerTest extends \Magento\TestFramework\TestCase\WebapiAbstract { private $cookieTesterUrl = 'testmoduleone/CookieTester'; - /** @var Curl */ + /** @var CurlClientWithCookies */ protected $curlClient; public function setUp() { $objectManager = Bootstrap::getObjectManager(); $this->config = $objectManager->get(\Magento\Webapi\Model\Config::class); - $this->curlClient = $objectManager->get(\Magento\TestFramework\TestCase\Webapi\Curl::class); + $this->curlClient = $objectManager->get( + \Magento\TestFramework\TestCase\HttpClient\CurlClientWithCookies::class + ); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/Webapi/DeserializationTest.php b/dev/tests/api-functional/testsuite/Magento/Webapi/DeserializationTest.php index 36ec5added45..98ba33f940f2 100644 --- a/dev/tests/api-functional/testsuite/Magento/Webapi/DeserializationTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Webapi/DeserializationTest.php @@ -6,7 +6,7 @@ namespace Magento\Webapi; -use Magento\TestFramework\TestCase\Webapi\Adapter\Rest\CurlClient; +use Magento\TestFramework\TestCase\Webapi\Adapter\Rest\RestClient; class DeserializationTest extends \Magento\TestFramework\TestCase\WebapiAbstract { @@ -40,7 +40,7 @@ public function testPostRequestWithEmptyBody() ]; $expectedMessage = '{"message":"%fieldName is a required field.","parameters":{"fieldName":"item"}}'; try { - $this->_webApiCall($serviceInfo, CurlClient::EMPTY_REQUEST_BODY); + $this->_webApiCall($serviceInfo, RestClient::EMPTY_REQUEST_BODY); } catch (\Exception $e) { $this->assertEquals(\Magento\Framework\Webapi\Exception::HTTP_BAD_REQUEST, $e->getCode()); $this->assertContains( @@ -66,7 +66,7 @@ public function testPutRequestWithEmptyBody() ]; $expectedMessage = '{"message":"%fieldName is a required field.","parameters":{"fieldName":"entityItem"}}'; try { - $this->_webApiCall($serviceInfo, CurlClient::EMPTY_REQUEST_BODY); + $this->_webApiCall($serviceInfo, RestClient::EMPTY_REQUEST_BODY); } catch (\Exception $e) { $this->assertEquals(\Magento\Framework\Webapi\Exception::HTTP_BAD_REQUEST, $e->getCode()); $this->assertContains( diff --git a/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/CoreRoutingTest.php b/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/CoreRoutingTest.php index 89533a0a6247..c4e052e11310 100644 --- a/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/CoreRoutingTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/CoreRoutingTest.php @@ -10,7 +10,7 @@ namespace Magento\Webapi\Routing; use Magento\TestFramework\Helper\Bootstrap; -use Magento\TestFramework\TestCase\Webapi\Adapter\Rest\CurlClient; +use Magento\TestFramework\TestCase\Webapi\Adapter\Rest\RestClient; class CoreRoutingTest extends \Magento\Webapi\Routing\BaseService { @@ -80,9 +80,9 @@ public function testExceptionSoapInternalError() public function testRestNoAcceptHeader() { $this->_markTestAsRestOnly(); - /** @var $curlClient CurlClient */ + /** @var $curlClient RestClient */ $curlClient = Bootstrap::getObjectManager()->get( - \Magento\TestFramework\TestCase\Webapi\Adapter\Rest\CurlClient::class + \Magento\TestFramework\TestCase\Webapi\Adapter\Rest\RestClient::class ); $response = $curlClient->get('/V1/testmodule1/resource1/1', [], ['Accept:']); $this->assertEquals('testProduct1', $response['name'], "Empty Accept header failed to return response."); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple.php index b3e5e96c18ca..1c8a4e64cdfd 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple.php @@ -182,7 +182,7 @@ $product->setOptions($options); -/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepositoryFactory */ +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ $productRepository = $objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); $productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_all_fields.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_all_fields.php new file mode 100644 index 000000000000..c1db1b80c690 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_all_fields.php @@ -0,0 +1,38 @@ +create(\Magento\Catalog\Api\ProductRepositoryInterface::class); + +$product = $productRepository->get('simple', true); +$eavAttributeValues = [ + 'category_ids' => [2], + 'cost' => 123.234, + 'country_of_manufacture' => 'US', + 'msrp' => 10.48, + 'gift_message_available' => 0, + 'minimal_price' => 450, + 'msrp_display_actual_price_type' => 0, + 'news_from_date' => '2017-08-10', + 'news_to_date' => '2017-08-11', + 'old_id' => 35235, + 'options_container' => 'Options Container', + 'required_options' => 1, + 'special_price' => 343.82, + 'special_from_date' => '2017-01-02', + 'special_to_date' => '2017-01-03' +]; + +foreach ($eavAttributeValues as $attributeCode => $attributeValue) { + $product->setCustomAttribute($attributeCode, $attributeValue); +} +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_all_fields_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_all_fields_rollback.php new file mode 100644 index 000000000000..19074da5f8ba --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_all_fields_rollback.php @@ -0,0 +1,8 @@ +reinitialize(); + +/** @var \Magento\TestFramework\ObjectManager $objectManager */ +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +/** @var \Magento\Catalog\Api\CategoryLinkManagementInterface $categoryLinkManagement */ +$categoryLinkManagement = $objectManager->get(\Magento\Catalog\Api\CategoryLinkManagementInterface::class); + +$tierPrices = []; +/** @var \Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory $tierPriceFactory */ +$tierPriceFactory = $objectManager->get(\Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory::class); +/** @var $tpExtensionAttributes */ +$tpExtensionAttributesFactory = $objectManager->get(ProductTierPriceExtensionFactory::class); + +$adminWebsite = $objectManager->get(\Magento\Store\Api\WebsiteRepositoryInterface::class)->get('admin'); +$tierPriceExtensionAttributes1 = $tpExtensionAttributesFactory->create() + ->setWebsiteId($adminWebsite->getId()); + +$tierPrices[] = $tierPriceFactory->create( + [ + 'data' => [ + 'customer_group_id' => \Magento\Customer\Model\Group::CUST_GROUP_ALL, + 'qty' => 2, + 'value' => 8 + ] + ] +)->setExtensionAttributes($tierPriceExtensionAttributes1); + +$tierPrices[] = $tierPriceFactory->create( + [ + 'data' => [ + 'customer_group_id' => \Magento\Customer\Model\Group::CUST_GROUP_ALL, + 'qty' => 5, + 'value' => 5 + ] + ] +)->setExtensionAttributes($tierPriceExtensionAttributes1); + +$tierPrices[] = $tierPriceFactory->create( + [ + 'data' => [ + 'customer_group_id' => \Magento\Customer\Model\Group::NOT_LOGGED_IN_ID, + 'qty' => 3, + 'value' => 5 + ] + ] +)->setExtensionAttributes($tierPriceExtensionAttributes1); + +$tierPriceExtensionAttributes2 = $tpExtensionAttributesFactory->create() + ->setWebsiteId($adminWebsite->getId()) + ->setPercentageValue(50); + +$tierPrices[] = $tierPriceFactory->create( + [ + 'data' => [ + 'customer_group_id' => \Magento\Customer\Model\Group::NOT_LOGGED_IN_ID, + 'qty' => 10 + ] + ] +)->setExtensionAttributes($tierPriceExtensionAttributes2); + +/** @var $product \Magento\Catalog\Model\Product */ +$product = $objectManager->create(\Magento\Catalog\Model\Product::class); +$product->isObjectNew(true); +$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) + ->setId(1) + ->setAttributeSetId(4) + ->setWebsiteIds([1]) + ->setName('Simple Product') + ->setSku('simple') + ->setPrice(10) + ->setWeight(1) + ->setShortDescription("Short description") + ->setTaxClassId(0) + ->setTierPrices($tierPrices) + ->setDescription('Description with html tag') + ->setMetaTitle('meta title') + ->setMetaKeyword('meta keyword') + ->setMetaDescription('meta description') + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->setStockData( + [ + 'use_config_manage_stock' => 1, + 'qty' => 100, + 'is_qty_decimal' => 0, + 'is_in_stock' => 1, + ] + )->setCanSaveCustomOptions(true) + ->setHasOptions(true); + +$oldOptions = [ + [ + 'previous_group' => 'text', + 'title' => 'Test Field', + 'type' => 'field', + 'is_require' => 1, + 'sort_order' => 0, + 'price' => 1, + 'price_type' => 'fixed', + 'sku' => '1-text', + 'max_characters' => 100, + ], + [ + 'previous_group' => 'date', + 'title' => 'Test Date and Time', + 'type' => 'date_time', + 'is_require' => 1, + 'sort_order' => 0, + 'price' => 2, + 'price_type' => 'fixed', + 'sku' => '2-date', + ], + [ + 'previous_group' => 'select', + 'title' => 'Test Select', + 'type' => 'drop_down', + 'is_require' => 1, + 'sort_order' => 0, + 'values' => [ + [ + 'option_type_id' => null, + 'title' => 'Option 1', + 'price' => 3, + 'price_type' => 'fixed', + 'sku' => '3-1-select', + ], + [ + 'option_type_id' => null, + 'title' => 'Option 2', + 'price' => 3, + 'price_type' => 'fixed', + 'sku' => '3-2-select', + ], + ] + ], + [ + 'previous_group' => 'select', + 'title' => 'Test Radio', + 'type' => 'radio', + 'is_require' => 1, + 'sort_order' => 0, + 'values' => [ + [ + 'option_type_id' => null, + 'title' => 'Option 1', + 'price' => 3, + 'price_type' => 'fixed', + 'sku' => '4-1-radio', + ], + [ + 'option_type_id' => null, + 'title' => 'Option 2', + 'price' => 3, + 'price_type' => 'fixed', + 'sku' => '4-2-radio', + ], + ] + ], + [ + 'previous_group' => 'select', + 'title' => 'File option', + 'type' => 'file', + 'sort_order' => 3, + 'is_require' => 1, + 'price' => 30, + 'price_type' => 'percent', + 'sku' => 'sku3', + 'file_extension' => 'jpg, png, gif', + 'image_size_x' => 10, + 'image_size_y' => 20, + ], + [ + 'title' => 'area option', + 'type' => 'area', + 'sort_order' => 2, + 'is_require' => 1, + 'price' => 3.95, + 'price_type' => 'percent', + 'sku' => 'sku2', + 'max_characters' => 20, + ] +]; + +$options = []; + +/** @var \Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory $customOptionFactory */ +$customOptionFactory = $objectManager->create(\Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory::class); + +foreach ($oldOptions as $option) { + /** @var \Magento\Catalog\Api\Data\ProductCustomOptionInterface $option */ + $option = $customOptionFactory->create(['data' => $option]); + $option->setProductSku($product->getSku()); + + $options[] = $option; +} + +$product->setOptions($options); + +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); +$productRepository->save($product); + +$categoryLinkManagement->assignProductToCategories( + $product->getSku(), + [2] +); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_full_option_set_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_full_option_set_rollback.php new file mode 100644 index 000000000000..19074da5f8ba --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_full_option_set_rollback.php @@ -0,0 +1,8 @@ +get( + \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterfaceFactory::class +); + +/** + * @var \Magento\Framework\Api\Data\ImageContentInterfaceFactory $imageContentFactory + */ +$imageContentFactory = $objectManager->get(\Magento\Framework\Api\Data\ImageContentInterfaceFactory::class); +$imageContent = $imageContentFactory->create(); +$testImagePath = __DIR__ .'/magento_image.jpg'; +$imageContent->setBase64EncodedData(base64_encode(file_get_contents($testImagePath))); +$imageContent->setType("image/jpeg"); +$imageContent->setName("1.jpg"); + +$video = $mediaGalleryEntryFactory->create(); +$video->setDisabled(false); +//$video->setFile('1.png'); +$video->setFile('1.jpg'); +$video->setLabel('Video Label'); +$video->setMediaType('external-video'); +$video->setPosition(2); +$video->setContent($imageContent); + +/** + * @var \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryExtensionFactory $mediaGalleryEntryExtensionFactory + */ +$mediaGalleryEntryExtensionFactory = $objectManager->get( + \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryExtensionFactory::class +); +$mediaGalleryEntryExtension = $mediaGalleryEntryExtensionFactory->create(); + +/** + * @var \Magento\Framework\Api\Data\VideoContentInterfaceFactory $videoContentFactory + */ +$videoContentFactory = $objectManager->get( + \Magento\Framework\Api\Data\VideoContentInterfaceFactory::class +); +$videoContent = $videoContentFactory->create(); +$videoContent->setMediaType('external-video'); +$videoContent->setVideoDescription('Video description'); +$videoContent->setVideoProvider('youtube'); +$videoContent->setVideoMetadata('Video Metadata'); +$videoContent->setVideoTitle('Video title'); +$videoContent->setVideoUrl('http://www.youtube.com/v/tH_2PFNmWoga'); + +$mediaGalleryEntryExtension->setVideoContent($videoContent); +$video->setExtensionAttributes($mediaGalleryEntryExtension); + +/** + * @var \Magento\Catalog\Api\ProductAttributeMediaGalleryManagementInterface $mediaGalleryManagement + */ +$mediaGalleryManagement = $objectManager->get( + \Magento\Catalog\Api\ProductAttributeMediaGalleryManagementInterface::class +); +$mediaGalleryManagement->create('simple', $video); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_media_gallery_entries_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_media_gallery_entries_rollback.php new file mode 100644 index 000000000000..0de2cddd7aa3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_media_gallery_entries_rollback.php @@ -0,0 +1,7 @@ +-S*MMLE4D_#j{DVP&fq_9lfP;a7LqJ0O z6-a1Es1JdJhK7NKg@u8J`$stM;D)yR2E{M*&ii+5PA;E~Lc+0KirirL{|%2PlhWJ}Q>Jc>_Z}PC+ois}3Kd zfI=tiSK0`FT93T08aDy|sWt$>PmDBNdal9qW-X#95D#kY>VLa046l{$0odJz9^g|K zqAg`drr|?=8%$#eUshC|dG>LhcdaHU--x>STB}^WxxnMkr!4+@F^Ya+3@J(dj z4-m$>@11E2ZN8l5<#Y83Fm4NIXEr*mo<)BcEt~*yJLO+BUiFp-q@$aR4<-j0u6C<* zz576sOD_DFur*!wLDuZj=5jI~fYMEW+kW2^l**a2TzYH;vc^h|HHTZCwd^WK1P*|| z1LLKgZLEUhG=us{F0GR~BnS;Iag;}c{JNn%0LTp7Vr*0{i_TI>_2_SjBR?>~of)`P za7CS(D))fTiRNnOZhnf+?N?{S$4n4A{`}03M1h-+J~-bDz=}P8l|$wv$3xusS z{CSgWf5q+Ea-cH;V9IGP+Iqc|dZwnLvaSVRov%(EwhRu9{UaHC05+SI+++4&`rGT@ zS>ej|j}PhW7C1X_jSRr2VgU-X;OpX{A}_oRE8oWss-wzi0IUL2 zf!>fVbgsFm+-YhuB?Q1=D}o({<40cxNYW?G)T(*_C=FS|kfrR3I!%&8Zqx`XuZOXE z3f&$sZ6J2X=(j|nQ%rbIv?z(5re+u5j z!moL$F`zn!8^*tbyy13j69A2x_tI4Eu=?yy(U+d<4LuKgyCPIhfY*5pP};ILe&-x| ze(~fOP0QUN%*5F<{#5S6jxh{8zXP%t)^a?2zH6gR$_*%>R7G8E45Qz1obLhPc~A1n zZld-WF5~mfAB4Ea2kA@_k(sIUfHc zbpk*i@S2U*`srV6y1P=X-WBT?JlsU~t|kWndqZG{=9&|W#Wa`RzBsLq`v744`gReQ zVf9;8>-;;-+K2yzOl^pBxxd)k@-(ymus)ZdbQFcUpy&l9X2xZY5F8z2 z0>H`^Ti6*oj;g2DoJ?=ikNa(auM%g<{MU1C`57<4;pIq!3BNuEs;}}PfYWYgUO#mk zw4nHfF^#&B!V?pFIYjgafJd|!(ll08KB2yiBMmxqGjHnc91fhcLY84^4f$Z9P%_yg z_j?V0Y0cT?9}fU4+9zO9^>|HP!@Ng+Tvk=snm>K{6?aGY(E?!rP0wrjW!%2a(c$&L z(z-))*5mD8V0SO0zSX9vL5CTc9Fe0;MpdJ$i> zY*Y)mN_}>+je?qcQ~<2XB!_2B-}G097tB{B+UV!?q!gFAs^syQTL$2qijSXh={egz z2sv)nI#Zl(`L-(6ajx|ymKVT_p}amg{%o)PVwKBt-*vR+kFP#6;T6S4KST&tXR6WB z3OaH24#-=*WMt!*dwAZ4hFZ_JV^-q;pL-hN%a3_z)m=8v{V7|*AiF!N% zuEF_JSq80d-)@fd@09Z@y5*}x;>NwC$r(p_Ku??eEimcajr{!7UpJ4fDNaV(VB!RS zFR%mv5|@#qI2p@Vm>BGB=)=F>_G56%7K@2{0DfQRwy?QVnkng9^SgryFMawCZ+zwU zWcqvYkDB=)K_h4N2MtV_*4|8O`B>vO6aWBfKGQJfE(0m*^@A308S>})|1W<4gb&`a zf82)z1qA_tfPexA`*`>xkoX%a08r3K$V7-Js0@r~!~%kbFz6)AZ0w2{giJ!L1`hw8 zt3lv^cVMGo3&U=WWmra<)I#cVch`4L4@t6ZMxGzy>GX4bo6U+sMDbwu&Myii%6t9L zjAoDJ!ye!V1f>gB(^J0Ca^9{HVy<5H^6%7NW&2&A8~7&5ugYxlq`IVoyb!ppb3P2d zPAM?c&p{%uJ~*{pj%z0CYG2Wo*;6bPBj#%HPzf(njewy)umIEIY=9kU@ShQ05! zlX6&IUnV=yq=4G=V>T#!kdzb?YQTa?v#hgqTAxLx#zrs7tVEjcFzmd*IL?vH{ITY6 zBXw2r{YwM4a7rh){a8`9665rh3_C~@k-(-Mr?l1U}{!G7Krn?kIGB>Z6 zO1idLl^J&E0Zp81_-~wEJ8aRjTxTbjpS3BHg`BNne&k!LL~h^{QDPTZF-FU!hb@7( zsEj)z61u0pb%W`NiWtHre{0D+99m~4b6YPf!3GPGB|;R_TNkvmfZeAx;^k;zE3nu! zLZ$7D-NvenY(xQ>fU}e z@h`}R-9cV4gPkrQazu)fr*QpSsjShAZTEu?y}Vv#8@rRun@B}+lYKjd%~Nn}f1*S^ zk7NUXYCLB-t-8y)i&RtT`;c2!KSPOhWN@r}g}pCdHo*ULsfiR77~{yuc0vxv#!eg$ zT1p@)aWPhP@7BL|yN`zC5W!*(cdGTA`S}ZUQQZVKtI>LlW`T<0JK%Iod_Y3D9gyc; zA9jBcIpMmaAXy&seW)_1|H_3yVRjuhnFZ6DNx^Y+}2bx6N8+mq(%oBRb=%QtTft>Nqx&6f+|f6 z^>A|GoIc1;ag7{2buC&9ajT0@ntqVDKOo#Fx~Z;Pk*eG!rVgv_L^3u+OA+=0UI!Joci*Vi>W){WE0 z^yc-bGk?hw^t4Yt{za_q_yRruVi9Fq;sxdt4pENaABRmy5HJ^_$>BwrhX}a^26@zX zAP|3|@U6YZXF~(*PxjJX?q3zYu*a@3)_{WpPI7`BGS(yNdP0HTo=r2Mq=j4}MPV

l7*;hK;W_qz!Jh4s!iBfC* z<&&-((-g+hwQi_$&!+a5&;kDKP_M&Tkas}tV*pf9Ivw?JQE!M{3~_fdI^4=adubj& zr3rs6bwix+HLq3PM@+5cQ;f=1Y&W#CDCiwk*)=uZ{V5k#Ejzl5eWxvk2v%+D3pl{{MhpVS109uJ>*B73R_&HNRH3ve0Yp-T@9xJa^Lm+vw~1vPj1_R{R>|iMlkc#X){{g~EdhYvI6H$~Z}C&c2(Q z6}d1Z=U3D>r^TbT;3xO^GLHnl+oF4DC$9ryjSy$8;{9ZQd6XydXaBb2JgJ zv5s0eRj@JIDJf@yC9;KE#xRzb*hVzc7IS_VU+2ud19uA>o3tcyXiI`Vib$j`)aJf#FCo`YM%ky|W&k+K%t5 zK238{z+-mY^W@O$;Xpj}ro)yF^sm%*f=;k|o9_vaMeJ343iA7$rCI5$vJ78CUHp+I zEpSHnhga~!Gg%{QaANIPcwB9>w%Da|4@yr5``F$XPw~HGe|9AGT9yw(a(qQL%@}fx z_yK3ZM}^23RZmWI7C&c{rte*`IIC}Q`(1^SWD{)#>LcJVK^SNDlYGTsxSR}dGIJU8 z^xAtgQxM0%?{QYG-m9R7=;L_F&l+V}`5ucFEl`T#5=H*$npJlEUEhm zOGcJ|2L*z!zN(X1*~@;E&hse8eyejhiq7@ffMRP2f?jaK`$Vu35@FgoonU^ZRm%2A zA%tB+&A-SAra3gR_1oc1eQE>8X+K|c7deBtg_)8pl0R-O9%~~T@*+8Wmi(WNg#LFR zRp;BUpd>iRU6o-%Oxk|`FH8hQR&^fstAyU}BR;JtCUDdQ``kTt{Cgdcy9W4ultg4BsPRTr&5Hr_Nie|&`XIHo-FtIEaC3C$R z5K-Y&Eo&MO(~YzTg>(*g)4=-lnrK6ix39p7Dp5rEfV{IV^40m=@tP9G;Rfor$8Lfd zf&kpoL`XAX2^oRB-I0LKt=M=vlt-54%ds#FqJMELI-%D+eI-NAWj1&YrBi&MF@oUu z8ZNGFCi4Z<2pbHatW}J3)izRM0*t2b$cS%JE4pIVx9Ym>ZV-)zWy>Vn3Rm;f!4b!; zheSpfH`kcyuf|zot2?xau-y?Z--_M=L*MC_mI4BOg#p5DnIMxd&lpt^i=fnT(?Nle zx$l7BMC7#YZQyjAh6Mc_O3-USiv<1D5cbr-l9l4wFrA3fzWfDIV36v8zCxbl&?9$a zxxrLx{kn;2Qo<_*PAhy}BUv32@`u~)7a|e!XcagGQIM6gaUz)DTtM5^BqaDLkeimN zTPiI=KA6`kl9bt(FwLH})#jGEr`BxCC#>e$w$*;q&liqaWm&YfwdsV+RGKN|6fLH852 z!?dP(hJMRkl7Tc%P~W-D9g9v|wK(R=wj#pQQ>#asGXlLDyx*W+gb(s~2kboyJjGI^ zP3>-=SI3kRh19<65`qOIA%|*wL3Gs!V}1}n9u&K}S|!Zu*{#i;WfC;3omwMXmtWb( zNKSk;Yh3$=qBx#v7sS$G)P|FT{SKHWG`bslw`Iq9--T&gnXLa*{VZ+z&C2c~t+g3+Q!#J|3`)HIcTNG%|B+KWL4dGm{;LM3N_2i8xeE@$+2a}Kb}fLl@i8ywmG zbzO$r#OJ7+q5Nw+m`XsiW|888d6Ms8u&MJvlF1FXhLb2=eB`Z?3dq~~53Pr1@^Oy1 zn8}}n#Ua>|q|WiGs^E#9a5y|#pirQBf*mfmv=1Q9<*Qf9;q)_e0_`-;o#|I*JQ|7e zouxR;O7ny+c49-0xX|McJ?+w$Zbj0K-F*{8Q>U%(Kzk@vdZ8$-5R21bUl0u-K1lyO z!K@NfxBDE9LPB%}PS4gyTgEb>ph>+?su8dVg8DiSICs676G8&#$HSPq2%mFg`i+kd zH~ReWdlbeWW7YWArEK7zXf#`G4NL^l2_Ivu+F4eWK8b5W(PjYb1fo@)%i7HcTo#k- zpL`dPKBY8IV(8tp?^XyTO=en}ZPFq{Y=5RXL#${+&3 z0+w>d6SLN1c3CjYc}E#(XJmw}B~(hs_LF9)_KO#n-QX;UyCxz!kJ1Re2e0%%%uty? zrzIfWLj`@4PRH7j++p8LHOU@s&mrIEFspK+Jwv(@y0VueD{m-QJqqt$^S$9tM^?U1#m;kBK(v@*X&RxV; z^+-{0ze(}#Po5-!FRYC09P3&S55I3D2Gk{b3ayB7)hv20z=pm&RY9LyVhWxn;cYZ3 z$jH8Qt0Mz9!v#!S+2a+>;P}S60uA$x(0B;M^ewUAW*i2IS3jt6X;p-Mb5n*jQ61of zeT^f{80VEHV{mKRuGGYUKu!V~iM2=3f`#28$D#5Ph(osLeDkk{F)^9nT~y1kb0g3V zVGnC^_gmL?n7v0Jc4nqEPj&FdRpOESlC;~Ny1eL8;>0*vRTeMs&%Dx*YKRKplH;=< zMA_;3a^3+oRLyc-vFS^}R`Infi3Zo1k`pLrpPhAw{j`!0X+e4-7Ed(Zj~E@VU5l$A zwJ@b6ooqP&ccmXuP2N@BDQu;f(0*ZE{AcU$toS#Gg&1wH0wfi|Yjs$amWVz>EeFOq z#H8O{usJnT!8?-|guER#_xNV@Y1@CR6^2jcQ`6Ia$#4|%rZ}O3Le^A+Er!fA<01J@ z2*aIn?(0<=a#Wt(M4~73GQxwZlLPj71KToU_7}3dvY>IDJm`|$!GN~y*f&W{-bph_ zIyb?+?3e2w47uTcHtc6O-UNT9kOFE4Bhgeo0|^|?@2q6 z&ap|mi(MzK42LY6Rkw;QyF*W5@<5&?Js#8TDn@(4M8sut;c0@0m55(tkc90p@rqDi zeu4&9bQk=^Is>MQ0>tsLMasYhDz!5k_hZf%hi&|Ng&*TRNA<`^Ci*KE0|ALfAgMwe zAK}XmMxtjrb?o_LQV-nCWi-=qC0oT;xYB0FFSK=|!Q?V9JoDBw$tS9X4J{V^juFPO zB1j^1v!;}*6lUx65qxm9ILo(&of7+^zv6{z(OoDaYsPWKK7E-68X03{erPT|_>D8$ zxRkW3G+MQus|YMXx!;qZ2qTpSiCqlNVUegA%+@o9kiH@c9Qo=h=_5!l5MM9UQlu`Q zshkRbI+!l)0RRqb3>?_4^zc8mgLn7hE5UyT0XCv=g7hGwvrW&PRkgdp+B{2~6-f~W zrv%YVPK>17^O|4V%P{o6S%!{lpGa@*?dtnLTg6SZeMGW1o{p(r3l|p1uJ1dQ=;5`I zJ#D|L%kR^bQJR*f6Smt3g^I0U=`5Q@goGD+OS=((`i@slNDwsRSIQ(2Y?fdT1{K-2 zEBNc`RVYVsl*b1&t=HnNSzIf?KO0NW32RHvR*l`OLX*lCP7V$+mMl>uJ5DP0vjK-L8R_WxWGGT55*&Pv3z)K2%Dl&=cad?cvbVvvn~hg~!G(N14n%pnlc_ zia$3_e+D|g{Q8&~SDp|y?)@ryLU8Uq1V zJ8q{2%iX?cIJYYnQl;-?U(T((`ph%P6y)g(l{=aP_gaEo^a~ zR0d+Da_WKgU%Pxck_|!RvL1%~m%Tb4`X9 z$lH=3{n5JXJ+{T>J5R6uS)or`W4V1hm@CX4R>;EeWQ90!(8Oio^UwQpEB@797-^pC zMaj6bgi`s^rF#xaV+NbUcZJb7wkb#3U{3HAOdKpP~@pT&1eXY7NuQmlosF$h!jLuH&tnAY<}wQPj2m_Lt}np0L=| z&WVc-jHGxG43m;f3Db!Ljv~8n5){$)`{?B|&gI641zs7oz_Q{rv-a zC)TTN=5~K+TJaA$f(MH$;zy+G>RNca1{sU;(h{O2X_^q2M;w)E#s+|NWLa9^Cl*Wg ztg)z!zKjkFo@k#w_@yDpOf)M0u>K?|@yMGO_!tv6Da3u%4kfOJG5uRKm1&&NJKT&- zl2?1f3Fo2Pm7i|2n6R!yv(-1~zq-gxyB!8&#E zRovi!|wZ!F}-g=1?8Sr=y$j$dc(cF>$83ekK}8 z#Z`+TGNPo!n5Ph7^<&tH^zxJ2@$^gKB7gW#^uD+Aj-Oo`uXAFHK$&rn;V%3j_fXas zPNa%XtcqyA=?)L0^k{eeK2Wk?5#>N$QcdWktYYeVG%Dd}B5PxXhpSw&%Hz~3DzIq` z-wa3dU|W0#Vw5-%Q=3x7LM^iFOk4bk1L1E^M<^%`bQU=ahO(w9wDL=ECkTAm=yOY!>0@K5Eo zZJDdBT9!JG^@%nd@O^vj0#h=T&9VwmBbn%J9L+wtxN?snisuaK)nGqp)*XbjuLU*(2t#w<4Cko}K`KGF?A zq>udxP;giXSSSGWKid@mI0O>1BP1db@khqtRQr>O#qjGj3JEC#vw%`=-3%cat58DT zj!~k4L+`(KZ9zc(CK(f@Iz&cF4HTT-kY7CyiYPlsjm#Ox2VP@_uCDIt&!zI_%F63C zTSO^>=TOjcEBgJ7Jr_1!_wpGMjkF4?d2v1`IKI5*2_>d|lPyZoo2k&_LGad^jhBiM z5Tyx`6tzel^&`n|vF;YVVPx1lqj40K;+onqJ;K(|f&sTi65Y{tCAR*vmww0}Zrk4y zUl1?2$h(_dY`Q<7OZG~VGs&AEp>)R_c^AgMgU!GB4)o0Da;nGs?5s%D5vhH87{gbho^4NAj;DM5v%!a5eH+8m!t=^$HMQE1J82Y2 zi*5ZBH6*!RAR52-H+05VQpQUB_;msN~)KV${J1K~oikw@cH0abtD}2@al;OLy zsJ0uyTO&qVP%=*x!8P1K!wt?XPR5GSp2+zQ#1G)_1!#{8yyeS34`N%w7mbPv;o8#3 zroi^|idVM1<~YeEf5$TX%}Z0!7=I>spbv(EtcncORfeL5kj{&01cc%_7KZ$Y7pq0J zLm?9&H&F2-%I~ncd=^4a0hFwvmSmOS8H=fwiTxa(layj4pTy!G(8p;@l&FKHQcO9) zC1*yJtg%X;V%wYgvvQ=;F)E&(p;HrVv)WV}ZdabnOxC=qhTUTL&*dcJGjq_2l|mEt znn`76%A~AMC@f~_S9&q1#Z(RulZCK@nUPWCrE&X`%u13AwEf9LYJ?tDLoIgmE%)?a#o``=0ieW>i6*S% zG^R*4sb2jVg$YLKw0xxc_jkPb`&wHy9W=alzw1!eUaYX9RcpdgKmBpVI~q6FwtFos zUR(Ml-A#_dVqBYr0vm?$vhVUK4)d{-+Qe$2QnCUX;tzb>h7oQnXr#WA?0UkI)S3W% zEv;O`>rkSUtXAyi6^5vzj@(?#82|pLOxlY$dOeLk%i3<+YN6E=?U0o}dP|cOC9eEi4g&pe|xf{lp`FB7@?vLD~)#0;td=z~@ zRF_dl;T!NS4*r}FVG*CMBq~lB5dtC4BsOV|jS4}%!;e5J@;aJKjRh{#dq05b3t!oP z9l4u4{i`pT&v{%c67#pScHiQh)cvD-v{qUv~>^ru#qX>xHP5RqeIhrY&!IEZF*ltF153hAX^+S1k%x2`# z%K1%>0gP#@yGfdeq(k*K4@|`|NTwL1ph$94_5nv7L#FTy)x8s>;tnk1S=qpWbEL z23fUwUvST0y77^Jh3N4mM7C5>RuxR{dcdiqiJpZ~7I^j68a$r6G)y+@blIot^ygn6xDbj~ zw4zA!njc+^Og!#%I6#flP$47D3{^6QV$iar&oeylNz#x!!N1Ub_U3h^6_AoIo`g*+ zHI{$bLeXI6CjG4<+MtCsa>x>kS4%h+ll$X`m~wG9>Q;@js}7u}GRQH4*eB|_J#T!p zvawO#{FA_Yj2~@AVW)7H6epO)8QNdB<0VU+f(`c1&qpJbO<50=>ONnXw^2spCdZ={ zXz34D@~&|_yhH#eA?$VJVHtWS+Xk$OJkg$%KUG$I?2UW-7jLTxWIQ=;8Ta!d(IlDp zw{t*|HIHjKQ-tqap`LasiR-kXEJ61ix^n)}BLG zg)LzO0C?dov;B+=*)(JXB*#c$s2qb7_MGqM+>MX^NJIksOhRetu^Z$&Ab4$hmY3HqUyce$2Gy zPABJ8#lE}FHIyg6E8n1SQK8jDx{-Bgeez>IH&Slzd~V^PX-#P!IsRiz`qOLdVAu2O zyFNB@WGs2a)pp`EA|%OA} zw%kJ|1W&X&JQYb85SD)en`mgD4^&?t7#(FiFu|(y5adj&X zCgW${$$Mer&e^lUn!ez~(Nl?=S?qn|+x6yqxce=DI{G?_jd`+CMaym2l%$>_SV|&! zzBEe9?RxYR?V0Vt_rU%Ux}LVV@C__ytIQ>4jZ-wumhHNnx@sPezqr8=PPXY146OcG zdx=`jt%_M}{m+6uc602s(QKVr0q(djxDCI{B^SbgUvF{>B2da`w4SC%~PBLrf6pzGlX)R7O3`Yr7>4zY;;M`Hc zm$1Va@xbijmF6@ZO>~=z_%aCfGd$jJ+}zgi<9+svi2K|;$=g}-7wDB8bFg`nUcX4K zw(7IAQrqLos&QF~xVU`}2{N_QEpYgvm}%_ANK7!YhGux$mW{n(<7#ya46%D~c( z%3Sc)Jo}VS)L?u5Og9q^oqU!VC?}!e;xV{xq_o6;8>Ib&b)w@-dVxPvo~EB&Td3_d zZt}(VXKYJ)eU3Hilknpk?^XdTGra7ctRo{wYUY%uX6aL3bsa6vU^BAPRFrsyFG;&O zhnu^yf9V`}Qp6p++>SWo*%G?#u4BQ>6z1ku2k%?Uul|Z#eS6*%a|*NCg@w-(#=>92 z%#8E|k80Heb2@p0w$S_YWmc-qqMbi;y1ZvqHRk|f$zqAFtuG^vr3XkY)B3{BGQa{`=HJt zDQ)VJ3Z^fM=CoK)y9R%QvjHhOkxmp`bt zztMF!-OQpK4oO~OcP{ulBpGQ*qX(5`i#0d>z_qS?3e#_v5uOg(s}413v)}GeY{w?q z({F{sF4^%Etu}_pPMhSs_*=XmPAXboS{{|;@FQKG$*D&{C5 zRCyaA#xVmUf=|Xj;CZd-W3i`)Fb`KepmLC&UfMl`r%uu(^eJf*v0IFScp+J8M4_iY z5cM0r1zuPGfg^;vZ#=L6t&XOXr64RvdSd7sX49XVzxbSVS5n5F`$oE~Ha^w?;T_?( z+OJTP8_Cbq`T>F>JaOoiL&UelYx)bm?`^r;|8uNPe&>mi8QnL4hZ74-WmTp zj1R5rL1Fl1wB59Ooa+1qfZJx%qg#O7b!}-Pe?6VJ74u2 zJ4vMdHV;Ma*){oy2}@062V4^3IivXy;b)PT+9YsbQ@nK<8|RNR=Rt3dD9cKeo4~h zrxU%JhA`N&Rna9g8byE`uGc8K0?pItOp3b-&=v z1NU)rfe{bChX0P1tc!)H6E)hlVt@SscI8rUL#+7@fUzjhTxP#;y7t)@TEa?}aP2HS zLAiXue~`l@{zTTXxP!3WW&I;I+LloZT&|cC!=uXqClSa`DFw*I)5uol@xx z%+09R9P|9n+e)`wDOOnx41pxU(kb*jLN6YC+Qjx!snbRmK$6XUIbvQd8Msk+?f{fkh zXzS{QLBv2V%RlCi;OY0d>m}#&m36$Um*}`m`|>Ei^C)Um>#{Lkzs94xN5IYUl~>on zoUuY{Y%<&9LaTicc018;wD(R&a#@_iZC;|s7<9%_A);{(g@AhQf-22Avw{Y_aMMf0 zCy$5i&9J}`!J^NWc&vsX>v}3+tHSmAVk-}^s8B;1UY(U|YDkCLR;A}>gN_Wl6+9I5 zcV|A+UKTWbe~Z-bjvwbH6R0@W<|jB}wDx$8gDZL|bj?wZS738oZ(_j=W?&t9G?!4E zW$Pm+v$b~3rXANI!7d~fB}-TsscaI0TK>>Fj72!8lIPoSNkC$e7O3GV2$Zs5kIQLu zWxNAp8=tgNos+HP*EBL5lT@&U+4AtQ%04E@i-J&5l*l|I+oc#aZ&v@Axc8O+1L#ov A%K!iX literal 0 HcmV?d00001 diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_category_and_weight.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_category_and_weight.php new file mode 100644 index 000000000000..00c2b6cf8701 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_category_and_weight.php @@ -0,0 +1,213 @@ +reinitialize(); + +require __DIR__ . '/configurable_attribute.php'; + +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = Bootstrap::getObjectManager() + ->create(ProductRepositoryInterface::class); + +/** @var $installer CategorySetup */ +$installer = Bootstrap::getObjectManager()->create(CategorySetup::class); + +/* Create simple products per each option value*/ +/** @var AttributeOptionInterface[] $options */ +$options = $attribute->getOptions(); + +$attributeValues = []; +$attributeSetId = $installer->getAttributeSetId('catalog_product', 'Default'); +$associatedProductIds = []; +$productIds = [10, 20]; +array_shift($options); //remove the first option which is empty + +foreach ($options as $option) { + /** @var $product Product */ + $product = Bootstrap::getObjectManager()->create(Product::class); + $productId = array_shift($productIds); + $product->setTypeId(Type::TYPE_SIMPLE) + ->setId($productId) + ->setAttributeSetId($attributeSetId) + ->setWebsiteIds([1]) + ->setName('Configurable Option' . $option->getLabel()) + ->setSku('simple_' . $productId) + ->setPrice($productId) + ->setTestConfigurable($option->getValue()) + ->setVisibility(Visibility::VISIBILITY_NOT_VISIBLE) + ->setStatus(Status::STATUS_ENABLED) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]); + $eavAttributeValues = [ + 'category_ids' => [2] + ]; + foreach ($eavAttributeValues as $eavCategoryAttributeCode => $eavCategoryAtttributeValues) { + $product->setCustomAttribute($eavCategoryAttributeCode, $eavCategoryAtttributeValues); + } + + $product = $productRepository->save($product); + + /** + * @var \Magento\TestFramework\ObjectManager $objectManager + */ + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + + /** + * @var \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterfaceFactory $mediaGalleryEntryFactory + */ + + $mediaGalleryEntryFactory = $objectManager->get( + \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterfaceFactory::class + ); + + /** + * @var \Magento\Framework\Api\Data\ImageContentInterfaceFactory $imageContentFactory + */ + $imageContentFactory = $objectManager->get(\Magento\Framework\Api\Data\ImageContentInterfaceFactory::class); + $imageContent = $imageContentFactory->create(); + $testImagePath = __DIR__ .'/magento_image.jpg'; + $imageContent->setBase64EncodedData(base64_encode(file_get_contents($testImagePath))); + $imageContent->setType("image/jpeg"); + $imageContent->setName("1.jpg"); + + $video = $mediaGalleryEntryFactory->create(); + $video->setDisabled(false); + $video->setFile('1.jpg'); + $video->setLabel('Video Label'); + $video->setMediaType('external-video'); + $video->setPosition(2); + $video->setContent($imageContent); + + /** + * @var \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryExtensionFactory $mediaGalleryEntryExtensionFactory + */ + $mediaGalleryEntryExtensionFactory = $objectManager->get( + \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryExtensionFactory::class + ); + $mediaGalleryEntryExtension = $mediaGalleryEntryExtensionFactory->create(); + + /** + * @var \Magento\Framework\Api\Data\VideoContentInterfaceFactory $videoContentFactory + */ + $videoContentFactory = $objectManager->get( + \Magento\Framework\Api\Data\VideoContentInterfaceFactory::class + ); + $videoContent = $videoContentFactory->create(); + $videoContent->setMediaType('external-video'); + $videoContent->setVideoDescription('Video description'); + $videoContent->setVideoProvider('youtube'); + $videoContent->setVideoMetadata('Video Metadata'); + $videoContent->setVideoTitle('Video title'); + $videoContent->setVideoUrl('http://www.youtube.com/v/tH_2PFNmWoga'); + + $mediaGalleryEntryExtension->setVideoContent($videoContent); + $video->setExtensionAttributes($mediaGalleryEntryExtension); + + /** + * @var \Magento\Catalog\Api\ProductAttributeMediaGalleryManagementInterface $mediaGalleryManagement + */ + $mediaGalleryManagement = $objectManager->get( + \Magento\Catalog\Api\ProductAttributeMediaGalleryManagementInterface::class + ); + $mediaGalleryManagement->create('simple_' . $productId, $video); + + /** @var \Magento\CatalogInventory\Model\Stock\Item $stockItem */ + $stockItem = Bootstrap::getObjectManager()->create(\Magento\CatalogInventory\Model\Stock\Item::class); + $stockItem->load($productId, 'product_id'); + + if (!$stockItem->getProductId()) { + $stockItem->setProductId($productId); + } + $stockItem->setUseConfigManageStock(1); + $stockItem->setQty(1000); + $stockItem->setIsQtyDecimal(0); + $stockItem->setIsInStock(1); + $stockItem->save(); + + $attributeValues[] = [ + 'label' => 'test', + 'attribute_id' => $attribute->getId(), + 'value_index' => $option->getValue(), + ]; + $associatedProductIds[] = $product->getId(); +} + +/** @var $product Product */ +$product = Bootstrap::getObjectManager()->create(Product::class); + +/** @var Factory $optionsFactory */ +$optionsFactory = Bootstrap::getObjectManager()->create(Factory::class); + +$configurableAttributesData = [ + [ + 'attribute_id' => $attribute->getId(), + 'code' => $attribute->getAttributeCode(), + 'label' => $attribute->getStoreLabel(), + 'position' => '0', + 'values' => $attributeValues, + ], +]; + +$configurableOptions = $optionsFactory->create($configurableAttributesData); + +$extensionConfigurableAttributes = $product->getExtensionAttributes(); +$extensionConfigurableAttributes->setConfigurableProductOptions($configurableOptions); +$extensionConfigurableAttributes->setConfigurableProductLinks($associatedProductIds); + +$product->setExtensionAttributes($extensionConfigurableAttributes); + +// Remove any previously created product with the same id. +/** @var \Magento\Framework\Registry $registry */ +$registry = Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); +try { + $productToDelete = $productRepository->getById(1); + $productRepository->delete($productToDelete); + + /** @var \Magento\Quote\Model\ResourceModel\Quote\Item $itemResource */ + $itemResource = Bootstrap::getObjectManager()->get(\Magento\Quote\Model\ResourceModel\Quote\Item::class); + $itemResource->getConnection()->delete( + $itemResource->getMainTable(), + 'product_id = ' . $productToDelete->getId() + ); +} catch (\Exception $e) { + // Nothing to remove +} +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); + +$product->setTypeId(Configurable::TYPE_CODE) + ->setId(1) + ->setAttributeSetId($attributeSetId) + ->setWebsiteIds([1]) + ->setName('Configurable Product') + ->setSku('configurable') + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setWeight(1) + ->setStatus(Status::STATUS_ENABLED) + ->setStockData(['use_config_manage_stock' => 1, 'is_in_stock' => 1]); + +$productRepository->save($product); + +/** @var \Magento\Catalog\Api\CategoryLinkManagementInterface $categoryLinkManagement */ +$categoryLinkManagement = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Catalog\Api\CategoryLinkManagementInterface::class); + +$categoryLinkManagement->assignProductToCategories( + $product->getSku(), + [2] +); diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_category_and_weight_rollback.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_category_and_weight_rollback.php new file mode 100644 index 000000000000..7460c3a215b1 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_category_and_weight_rollback.php @@ -0,0 +1,7 @@ +get( + \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterfaceFactory::class +); + +/** + * @var \Magento\Framework\Api\Data\ImageContentInterfaceFactory $imageContentFactory + */ +$imageContentFactory = $objectManager->get(\Magento\Framework\Api\Data\ImageContentInterfaceFactory::class); +$imageContent = $imageContentFactory->create(); +$testImagePath = __DIR__ .'/magento_image.jpg'; +$imageContent->setBase64EncodedData(base64_encode(file_get_contents($testImagePath))); +$imageContent->setType("image/jpeg"); +$imageContent->setName("1.jpg"); + +$video = $mediaGalleryEntryFactory->create(); +$video->setDisabled(false); +//$video->setFile('1.png'); +$video->setFile('1.jpg'); +$video->setLabel('Video Label'); +$video->setMediaType('external-video'); +$video->setPosition(2); +$video->setContent($imageContent); + +/** + * @var \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryExtensionFactory $mediaGalleryEntryExtensionFactory + */ +$mediaGalleryEntryExtensionFactory = $objectManager->get( + \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryExtensionFactory::class +); +$mediaGalleryEntryExtension = $mediaGalleryEntryExtensionFactory->create(); + +/** + * @var \Magento\Framework\Api\Data\VideoContentInterfaceFactory $videoContentFactory + */ +$videoContentFactory = $objectManager->get( + \Magento\Framework\Api\Data\VideoContentInterfaceFactory::class +); +$videoContent = $videoContentFactory->create(); +$videoContent->setMediaType('external-video'); +$videoContent->setVideoDescription('Video description'); +$videoContent->setVideoProvider('youtube'); +$videoContent->setVideoMetadata('Video Metadata'); +$videoContent->setVideoTitle('Video title'); +$videoContent->setVideoUrl('http://www.youtube.com/v/tH_2PFNmWoga'); + +$mediaGalleryEntryExtension->setVideoContent($videoContent); +$video->setExtensionAttributes($mediaGalleryEntryExtension); + +/** + * @var \Magento\Catalog\Api\ProductAttributeMediaGalleryManagementInterface $mediaGalleryManagement + */ +$mediaGalleryManagement = $objectManager->get( + \Magento\Catalog\Api\ProductAttributeMediaGalleryManagementInterface::class +); +$mediaGalleryManagement->create('configurable', $video); diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_media_gallery_video_rollback.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_media_gallery_video_rollback.php new file mode 100644 index 000000000000..7460c3a215b1 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_media_gallery_video_rollback.php @@ -0,0 +1,7 @@ +deserializerFactory = $deserializerFactory; + } + + /** + * Fetch data from HTTP Request body. + * + * @return array + */ + public function getBodyParams() + { + if (null == $this->_bodyParams) { + $this->_bodyParams = []; + //avoid JSON decoding with empty string + if ($this->getContent()) { + $this->_bodyParams = (array)$this->getDeserializer()->deserialize((string)$this->getContent()); + } + } + return $this->_bodyParams; + } + + /** + * Get Content-Type of request. + * + * @return string + * @throws \Magento\Framework\Exception\InputException + */ + public function getContentType() + { + $headerValue = $this->getHeader('Content-Type'); + + if (!$headerValue) { + throw new \Magento\Framework\Exception\InputException(new Phrase('Content-Type header is empty.')); + } + if (!preg_match('~^([a-z\d/\-+.]+)(?:; *charset=(.+))?$~Ui', $headerValue, $matches)) { + throw new \Magento\Framework\Exception\InputException(new Phrase('Content-Type header is invalid.')); + } + // request encoding check if it is specified in header + if (isset($matches[2]) && self::REQUEST_CHARSET != strtolower($matches[2])) { + throw new \Magento\Framework\Exception\InputException(new Phrase('UTF-8 is the only supported charset.')); + } + + return $matches[1]; + } + + /** + * Get request deserializer. + * + * @return \Magento\Framework\Webapi\Rest\Request\DeserializerInterface + */ + private function getDeserializer() + { + if (null === $this->_deserializer) { + $this->deserializer = $this->deserializerFactory->get($this->getContentType()); + } + return $this->deserializer; + } +} diff --git a/lib/internal/Magento/Framework/Webapi/GraphQl/Response.php b/lib/internal/Magento/Framework/Webapi/GraphQl/Response.php new file mode 100644 index 000000000000..2dbcb9f00463 --- /dev/null +++ b/lib/internal/Magento/Framework/Webapi/GraphQl/Response.php @@ -0,0 +1,86 @@ +renderer = $rendererFactory->get(); + $this->errorProcessor = $errorProcessor; + $this->appState = $appState; + } + + /** + * Send response to the client, render exceptions if they are present. + * + * @return void + */ + public function sendResponse() + { + try { + parent::sendResponse(); + } catch (\Exception $e) { + if ($e instanceof \Magento\Framework\Webapi\Exception) { + // If the server does not support all MIME types accepted by the client it SHOULD send 406. + $httpCode = $e->getHttpCode() == + \Magento\Framework\Webapi\Exception::HTTP_NOT_ACCEPTABLE ? + \Magento\Framework\Webapi\Exception::HTTP_NOT_ACCEPTABLE : + \Magento\Framework\Webapi\Exception::HTTP_INTERNAL_ERROR; + } else { + $httpCode = \Magento\Framework\Webapi\Exception::HTTP_INTERNAL_ERROR; + } + + /** If error was encountered during "error rendering" process then use error renderer. */ + $this->errorProcessor->renderException($e, $httpCode); + } + } + + /** + * Register an exception with the response + * + * @param \Exception $e + * @return $this + */ + public function setException($e) + { + $this->exceptions[] = $e; + return $this; + } +} From 2d15b5e9f852971695f409ce657cc4e9278d76aa Mon Sep 17 00:00:00 2001 From: Eric Bohanon Date: Wed, 25 Oct 2017 11:33:39 -0500 Subject: [PATCH 2/3] MAGETWO-81033: Remove unnecessary classes --- .../Framework/Webapi/GraphQl/Request.php | 110 ------------------ .../Framework/Webapi/GraphQl/Response.php | 86 -------------- 2 files changed, 196 deletions(-) delete mode 100644 lib/internal/Magento/Framework/Webapi/GraphQl/Request.php delete mode 100644 lib/internal/Magento/Framework/Webapi/GraphQl/Response.php diff --git a/lib/internal/Magento/Framework/Webapi/GraphQl/Request.php b/lib/internal/Magento/Framework/Webapi/GraphQl/Request.php deleted file mode 100644 index cdca30fcdb7e..000000000000 --- a/lib/internal/Magento/Framework/Webapi/GraphQl/Request.php +++ /dev/null @@ -1,110 +0,0 @@ -deserializerFactory = $deserializerFactory; - } - - /** - * Fetch data from HTTP Request body. - * - * @return array - */ - public function getBodyParams() - { - if (null == $this->_bodyParams) { - $this->_bodyParams = []; - //avoid JSON decoding with empty string - if ($this->getContent()) { - $this->_bodyParams = (array)$this->getDeserializer()->deserialize((string)$this->getContent()); - } - } - return $this->_bodyParams; - } - - /** - * Get Content-Type of request. - * - * @return string - * @throws \Magento\Framework\Exception\InputException - */ - public function getContentType() - { - $headerValue = $this->getHeader('Content-Type'); - - if (!$headerValue) { - throw new \Magento\Framework\Exception\InputException(new Phrase('Content-Type header is empty.')); - } - if (!preg_match('~^([a-z\d/\-+.]+)(?:; *charset=(.+))?$~Ui', $headerValue, $matches)) { - throw new \Magento\Framework\Exception\InputException(new Phrase('Content-Type header is invalid.')); - } - // request encoding check if it is specified in header - if (isset($matches[2]) && self::REQUEST_CHARSET != strtolower($matches[2])) { - throw new \Magento\Framework\Exception\InputException(new Phrase('UTF-8 is the only supported charset.')); - } - - return $matches[1]; - } - - /** - * Get request deserializer. - * - * @return \Magento\Framework\Webapi\Rest\Request\DeserializerInterface - */ - private function getDeserializer() - { - if (null === $this->_deserializer) { - $this->deserializer = $this->deserializerFactory->get($this->getContentType()); - } - return $this->deserializer; - } -} diff --git a/lib/internal/Magento/Framework/Webapi/GraphQl/Response.php b/lib/internal/Magento/Framework/Webapi/GraphQl/Response.php deleted file mode 100644 index 2dbcb9f00463..000000000000 --- a/lib/internal/Magento/Framework/Webapi/GraphQl/Response.php +++ /dev/null @@ -1,86 +0,0 @@ -renderer = $rendererFactory->get(); - $this->errorProcessor = $errorProcessor; - $this->appState = $appState; - } - - /** - * Send response to the client, render exceptions if they are present. - * - * @return void - */ - public function sendResponse() - { - try { - parent::sendResponse(); - } catch (\Exception $e) { - if ($e instanceof \Magento\Framework\Webapi\Exception) { - // If the server does not support all MIME types accepted by the client it SHOULD send 406. - $httpCode = $e->getHttpCode() == - \Magento\Framework\Webapi\Exception::HTTP_NOT_ACCEPTABLE ? - \Magento\Framework\Webapi\Exception::HTTP_NOT_ACCEPTABLE : - \Magento\Framework\Webapi\Exception::HTTP_INTERNAL_ERROR; - } else { - $httpCode = \Magento\Framework\Webapi\Exception::HTTP_INTERNAL_ERROR; - } - - /** If error was encountered during "error rendering" process then use error renderer. */ - $this->errorProcessor->renderException($e, $httpCode); - } - } - - /** - * Register an exception with the response - * - * @param \Exception $e - * @return $this - */ - public function setException($e) - { - $this->exceptions[] = $e; - return $this; - } -} From f24a929cb9b1f9d572e55854fe261a93907ae27d Mon Sep 17 00:00:00 2001 From: Eric Bohanon Date: Wed, 25 Oct 2017 11:41:59 -0500 Subject: [PATCH 3/3] MAGETWO-81033: Create GraphQL single product fetch endpoint - Address static failure --- .../_files/product_configurable_with_category_and_weight.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_category_and_weight.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_category_and_weight.php index 00c2b6cf8701..519841ddb61e 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_category_and_weight.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_category_and_weight.php @@ -14,6 +14,7 @@ use Magento\ConfigurableProduct\Model\Product\Type\Configurable; use Magento\Eav\Api\Data\AttributeOptionInterface; use Magento\TestFramework\Helper\Bootstrap; +use Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryExtensionFactory; \Magento\TestFramework\Helper\Bootstrap::getInstance()->reinitialize(); @@ -92,7 +93,7 @@ $video->setContent($imageContent); /** - * @var \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryExtensionFactory $mediaGalleryEntryExtensionFactory + * @var ProductAttributeMediaGalleryEntryExtensionFactory $mediaGalleryEntryExtensionFactory */ $mediaGalleryEntryExtensionFactory = $objectManager->get( \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryExtensionFactory::class