Skip to content

Commit cfb3509

Browse files
committed
Merge branch 'issue-17-custom-route-for-bulk-api' of https://github.com/magento/bulk-api-ce into issue-17-custom-route-for-bulk-api
2 parents 73cdea5 + 3a3839b commit cfb3509

File tree

5 files changed

+273
-6
lines changed

5 files changed

+273
-6
lines changed

app/code/Magento/Webapi/Model/Rest/Config.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class Config
2121
const HTTP_METHOD_DELETE = 'DELETE';
2222
const HTTP_METHOD_PUT = 'PUT';
2323
const HTTP_METHOD_POST = 'POST';
24+
const HTTP_METHOD_PATCH = 'PATCH';
2425
/**#@-*/
2526

2627
/**#@+

app/code/Magento/WebapiAsync/Model/ServiceConfig/Converter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class Converter implements \Magento\Framework\Config\ConverterInterface
2828
\Magento\Webapi\Model\Rest\Config::HTTP_METHOD_POST,
2929
\Magento\Webapi\Model\Rest\Config::HTTP_METHOD_PUT,
3030
\Magento\Webapi\Model\Rest\Config::HTTP_METHOD_DELETE,
31+
\Magento\Webapi\Model\Rest\Config::HTTP_METHOD_PATCH
3132
];
3233

3334
/**
@@ -172,7 +173,6 @@ private function isSynchronousInvocationOnlyTrue(\DOMElement $synchronousInvocat
172173
* Convert and merge "route" nodes, which represent route customizations
173174
* @param \DOMDocument $source
174175
* @return array
175-
*
176176
*/
177177
private function convertRouteCustomizations($source)
178178
{

app/code/Magento/WebapiAsync/Plugin/ControllerRest.php

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
use Magento\WebapiAsync\Model\ServiceConfig;
1212
use Magento\Webapi\Controller\PathProcessor;
13-
use Magento\Webapi\Controller\Rest;
1413
use Magento\Framework\App\RequestInterface;
1514
use Magento\WebapiAsync\Model\ServiceConfig\Converter;
1615

@@ -41,14 +40,13 @@ public function __construct(
4140
}
4241

4342
/**
44-
* Check is current rest api route path in route customization config.
45-
* If that replaces $request route path by related endpoint,
46-
* @param Rest $subject
43+
* Apply route customization.
44+
* @param \Magento\Webapi\Controller\Rest $subject
4745
* @param RequestInterface $request
4846
* @return array
4947
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
5048
*/
51-
public function beforeDispatch(Rest $subject, RequestInterface $request)
49+
public function beforeDispatch(\Magento\Webapi\Controller\Rest $subject, RequestInterface $request)
5250
{
5351
$routeCustomizations = $this->serviceConfig->getServices()[Converter::KEY_ROUTES] ?? [];
5452
if ($routeCustomizations) {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
<services xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
9+
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_WebapiAsync:etc/webapi_async.xsd">
10+
<route url="async/V1/products" method="POST" alias="asyncProducts" />
11+
</services>
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\WebapiAsync\Model;
10+
11+
use Magento\Catalog\Api\Data\ProductInterface;
12+
use Magento\Framework\Exception\NotFoundException;
13+
use Magento\TestFramework\MessageQueue\PreconditionFailedException;
14+
use Magento\TestFramework\MessageQueue\PublisherConsumerController;
15+
use Magento\TestFramework\MessageQueue\EnvironmentPreconditionException;
16+
use Magento\TestFramework\TestCase\WebapiAbstract;
17+
use Magento\TestFramework\Helper\Bootstrap;
18+
use Magento\Catalog\Model\ResourceModel\Product\Collection;
19+
use Magento\Framework\Phrase;
20+
use Magento\Framework\Registry;
21+
use Magento\Framework\Webapi\Exception;
22+
use Magento\Catalog\Api\ProductRepositoryInterface;
23+
24+
/**
25+
* Check async request for product creation service using custom route, scheduling bulk to rabbitmq
26+
* running consumers and check async.operation.add consumer
27+
* check if product was created by async requests
28+
*
29+
* @magentoAppIsolation enabled
30+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
31+
*/
32+
class AsyncScheduleCustomRouteTest extends WebapiAbstract
33+
{
34+
const ASYNC_RESOURCE_CUSTOM_PATH = '/asyncProducts';
35+
const ASYNC_CONSUMER_NAME = 'async.operations.all';
36+
37+
const KEY_TIER_PRICES = 'tier_prices';
38+
const KEY_SPECIAL_PRICE = 'special_price';
39+
const KEY_CATEGORY_LINKS = 'category_links';
40+
41+
const BULK_UUID_KEY = 'bulk_uuid';
42+
43+
protected $consumers = [
44+
self::ASYNC_CONSUMER_NAME,
45+
];
46+
47+
/**
48+
* @var string[]
49+
*/
50+
private $skus = [];
51+
52+
/**
53+
* @var PublisherConsumerController
54+
*/
55+
private $publisherConsumerController;
56+
57+
/**
58+
* @var ProductRepositoryInterface
59+
*/
60+
private $productRepository;
61+
62+
/**
63+
* @var \Magento\Framework\ObjectManagerInterface
64+
*/
65+
private $objectManager;
66+
67+
/**
68+
* @var Registry
69+
*/
70+
private $registry;
71+
72+
protected function setUp()
73+
{
74+
$this->objectManager = Bootstrap::getObjectManager();
75+
$this->logFilePath = TESTS_TEMP_DIR . "/MessageQueueTestLog.txt";
76+
$this->registry = $this->objectManager->get(Registry::class);
77+
78+
$params = array_merge_recursive(
79+
\Magento\TestFramework\Helper\Bootstrap::getInstance()->getAppInitParams(),
80+
['MAGE_DIRS' => ['cache' => ['path' => TESTS_TEMP_DIR . '/cache']]]
81+
);
82+
83+
/** @var PublisherConsumerController publisherConsumerController */
84+
$this->publisherConsumerController = $this->objectManager->create(PublisherConsumerController::class, [
85+
'consumers' => $this->consumers,
86+
'logFilePath' => $this->logFilePath,
87+
'appInitParams' => $params,
88+
]);
89+
$this->productRepository = $this->objectManager->create(ProductRepositoryInterface::class);
90+
91+
try {
92+
$this->publisherConsumerController->initialize();
93+
} catch (EnvironmentPreconditionException $e) {
94+
$this->markTestSkipped($e->getMessage());
95+
} catch (PreconditionFailedException $e) {
96+
$this->fail(
97+
$e->getMessage()
98+
);
99+
}
100+
101+
parent::setUp();
102+
}
103+
104+
/**
105+
* @dataProvider productCreationProvider
106+
*/
107+
public function testAsyncScheduleBulkByCustomRoute($product)
108+
{
109+
$this->_markTestAsRestOnly();
110+
$this->skus[] = $product['product'][ProductInterface::SKU];
111+
$this->clearProducts();
112+
113+
$response = $this->saveProductByCustomRoute($product);
114+
$this->assertArrayHasKey(self::BULK_UUID_KEY, $response);
115+
$this->assertNotNull($response[self::BULK_UUID_KEY]);
116+
117+
$this->assertCount(1, $response['request_items']);
118+
$this->assertEquals('accepted', $response['request_items'][0]['status']);
119+
$this->assertFalse($response['errors']);
120+
121+
//assert one products is created
122+
try {
123+
$this->publisherConsumerController->waitForAsynchronousResult(
124+
[$this, 'assertProductCreation'],
125+
[$product]
126+
);
127+
} catch (PreconditionFailedException $e) {
128+
$this->fail("Not all products were created");
129+
}
130+
}
131+
132+
public function tearDown()
133+
{
134+
$this->clearProducts();
135+
$this->publisherConsumerController->stopConsumers();
136+
parent::tearDown();
137+
}
138+
139+
private function clearProducts()
140+
{
141+
$size = $this->objectManager->create(Collection::class)
142+
->addAttributeToFilter('sku', ['in' => $this->skus])
143+
->load()
144+
->getSize();
145+
146+
if ($size == 0) {
147+
return;
148+
}
149+
150+
$this->registry->unregister('isSecureArea');
151+
$this->registry->register('isSecureArea', true);
152+
try {
153+
foreach ($this->skus as $sku) {
154+
$this->productRepository->deleteById($sku);
155+
}
156+
} catch (\Exception $e) {
157+
throw $e;
158+
//nothing to delete
159+
}
160+
$this->registry->unregister('isSecureArea');
161+
162+
$size = $this->objectManager->create(Collection::class)
163+
->addAttributeToFilter('sku', ['in' => $this->skus])
164+
->load()
165+
->getSize();
166+
167+
if ($size > 0) {
168+
throw new Exception(new Phrase("Collection size after clearing the products: %size", ['size' => $size]));
169+
}
170+
}
171+
172+
/**
173+
* @return array
174+
*/
175+
public function productCreationProvider()
176+
{
177+
$productBuilder = function ($data) {
178+
return array_replace_recursive(
179+
$this->getSimpleProductData(),
180+
$data
181+
);
182+
};
183+
184+
return [
185+
[
186+
[
187+
'product' =>
188+
$productBuilder([
189+
ProductInterface::TYPE_ID => 'simple',
190+
ProductInterface::SKU => 'psku-test-1',
191+
]),
192+
],
193+
],
194+
[
195+
[
196+
'product' => $productBuilder([
197+
ProductInterface::TYPE_ID => 'virtual',
198+
ProductInterface::SKU => 'psku-test-2',
199+
]),
200+
],
201+
],
202+
];
203+
}
204+
205+
/**
206+
* Get Simple Product Data
207+
*
208+
* @param array $productData
209+
* @return array
210+
*/
211+
private function getSimpleProductData($productData = [])
212+
{
213+
return [
214+
ProductInterface::SKU => isset($productData[ProductInterface::SKU])
215+
? $productData[ProductInterface::SKU] : uniqid('sku-', true),
216+
ProductInterface::NAME => isset($productData[ProductInterface::NAME])
217+
? $productData[ProductInterface::NAME] : uniqid('sku-', true),
218+
ProductInterface::VISIBILITY => 4,
219+
ProductInterface::TYPE_ID => 'simple',
220+
ProductInterface::PRICE => 3.62,
221+
ProductInterface::STATUS => 1,
222+
ProductInterface::TYPE_ID => 'simple',
223+
ProductInterface::ATTRIBUTE_SET_ID => 4,
224+
'custom_attributes' => [
225+
['attribute_code' => 'cost', 'value' => ''],
226+
['attribute_code' => 'description', 'value' => 'Description'],
227+
],
228+
];
229+
}
230+
231+
/**
232+
* @param $requestData
233+
* @param string|null $storeCode
234+
* @return mixed
235+
*/
236+
private function saveProductByCustomRoute($requestData, $storeCode = null)
237+
{
238+
$serviceInfo = [
239+
'rest' => [
240+
'resourcePath' => self::ASYNC_RESOURCE_CUSTOM_PATH,
241+
'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST,
242+
],
243+
];
244+
245+
return $this->_webApiCall($serviceInfo, $requestData, null, $storeCode);
246+
}
247+
248+
public function assertProductCreation()
249+
{
250+
$collection = $this->objectManager->create(Collection::class)
251+
->addAttributeToFilter('sku', ['in' => $this->skus])
252+
->load();
253+
$size = $collection->getSize();
254+
255+
return $size == count($this->skus);
256+
}
257+
}

0 commit comments

Comments
 (0)