diff --git a/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml
index 5034c72b9ee3f..6e7e4a7a16573 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml
@@ -107,4 +107,35 @@
CustomAttributeDynamicPrice
CustomAttributePriceViewRange
+
+ Test 123 Dynamic
+ test-dynamic-bundle-product
+ bundle
+ 4
+ 4
+ 1
+ test-dynamic-bundle-product
+ EavStockItem
+ CustomDynamicProductDescription
+ CustomDynamicProductShortDescription
+ CustomAttributeDynamicPrice
+ CustomAttributePriceViewRange
+
+
+ Test 123 Fixed
+ test-fixed-bundle-product
+ bundle
+ 4
+ 10
+ 4
+ 1
+ api-fixed-bundle-product
+ EavStockItem
+ CustomFixedProductDescription
+ CustomFixedProductShortDescription
+ CustomAttributePriceView
+ CustomAttributeFixPrice
+ CustomAttributeFixWeight
+ CustomAttributeFixSku
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminShouldBeAbleToMassUpdateAttributesForBundleProductsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminShouldBeAbleToMassUpdateAttributesForBundleProductsTest.xml
new file mode 100644
index 0000000000000..fe55fda4d0e05
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminShouldBeAbleToMassUpdateAttributesForBundleProductsTest.xml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCustomerSearchBundleProductsByKeywordsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCustomerSearchBundleProductsByKeywordsTest.xml
new file mode 100644
index 0000000000000..d27cd0df88239
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCustomerSearchBundleProductsByKeywordsTest.xml
@@ -0,0 +1,137 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml
index 6c448d0cbbd3b..0fad3e14f6c86 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml
@@ -557,4 +557,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeMassUpdateActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeMassUpdateActionGroup.xml
new file mode 100644
index 0000000000000..57b180ada1536
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeMassUpdateActionGroup.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml
index 389c41abf0bd1..1684bd0c8a2c3 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml
@@ -35,4 +35,20 @@
news_from_date
2018-05-17 00:00:00
+
+ description
+ Dynamicscription testing 321
+
+
+ short_description
+ Short dynamictest 555
+
+
+ description
+ Fixedscription testing 321
+
+
+ short_description
+ Short Fixedtest 555
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMassUpdateData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMassUpdateData.xml
new file mode 100644
index 0000000000000..99908f1c9df5f
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMassUpdateData.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+ New Bundle Product Name
+ This is the description
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminUpdateAttributesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminUpdateAttributesSection.xml
index 53af1d5bd6eb1..69d7297628d56 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminUpdateAttributesSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminUpdateAttributesSection.xml
@@ -36,6 +36,7 @@
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml
index 067d76821d687..34e86566d73ba 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml
@@ -64,6 +64,13 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/DeleteCustomStoreActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/DeleteCustomStoreActionGroup.xml
index 8a1d830661aad..70fc08528f85e 100644
--- a/app/code/Magento/Store/Test/Mftf/ActionGroup/DeleteCustomStoreActionGroup.xml
+++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/DeleteCustomStoreActionGroup.xml
@@ -22,6 +22,8 @@
+
+
@@ -52,4 +54,4 @@
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontAddMultipleStoreProductsToWishlistTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontAddMultipleStoreProductsToWishlistTest.xml
index ede63322235f2..e8b645990390e 100644
--- a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontAddMultipleStoreProductsToWishlistTest.xml
+++ b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontAddMultipleStoreProductsToWishlistTest.xml
@@ -10,16 +10,14 @@
-
+
-
-
-
-
+
+
@@ -36,6 +34,7 @@
+
@@ -43,8 +42,16 @@
-
+
+
+
+
+
+
+
+
+
@@ -52,7 +59,9 @@
+
+
@@ -65,36 +74,37 @@
+
+
+
-
-
-
-
-
+
+
+
-
+
-
-
+
+
-
+
-
-
+
+
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderCancelTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderCancelTest.php
index 62a44f851e405..bb8e246b573eb 100644
--- a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderCancelTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderCancelTest.php
@@ -6,21 +6,51 @@
namespace Magento\Sales\Service\V1;
+use Magento\Framework\ObjectManagerInterface;
+use Magento\Sales\Model\Order;
+use Magento\TestFramework\Helper\Bootstrap;
use Magento\TestFramework\TestCase\WebapiAbstract;
+/**
+ * Canceling of the order
+ */
class OrderCancelTest extends WebapiAbstract
{
const SERVICE_VERSION = 'V1';
-
const SERVICE_NAME = 'salesOrderManagementV1';
/**
- * @magentoApiDataFixture Magento/Sales/_files/order.php
+ * @var ObjectManagerInterface
*/
- public function testOrderCancel()
+ private $objectManager;
+
+ /**
+ * @inheritdoc
+ */
+ protected function setUp()
+ {
+ $this->objectManager = Bootstrap::getObjectManager();
+ }
+
+ /**
+ * Gets order by increment ID.
+ *
+ * @param string $incrementId
+ * @return Order
+ */
+ private function getOrder(string $incrementId): Order
+ {
+ return $this->objectManager->create(Order::class)->loadByIncrementId($incrementId);
+ }
+
+ /**
+ * Send API request for canceling the order
+ *
+ * @param Order $order
+ * @return array|bool|float|int|string
+ */
+ private function sendCancelRequest(Order $order)
{
- $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
- $order = $objectManager->get(\Magento\Sales\Model\Order::class)->loadByIncrementId('100000001');
$serviceInfo = [
'rest' => [
'resourcePath' => '/V1/orders/' . $order->getId() . '/cancel',
@@ -33,7 +63,36 @@ public function testOrderCancel()
],
];
$requestData = ['id' => $order->getId()];
- $result = $this->_webApiCall($serviceInfo, $requestData);
+ return $this->_webApiCall($serviceInfo, $requestData);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Sales/_files/order.php
+ */
+ public function testOrderCancel()
+ {
+ $order = $this->getOrder('100000001');
+ $result = $this->sendCancelRequest($order);
$this->assertTrue($result);
}
+
+ /**
+ * @magentoApiDataFixture Magento/Sales/_files/order_state_hold.php
+ */
+ public function testOrderWithStateHoldedShouldNotBeCanceled()
+ {
+ $order = $this->getOrder('100000001');
+ $result = $this->sendCancelRequest($order);
+ $this->assertFalse($result);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Sales/_files/order_with_shipping_and_invoice.php
+ */
+ public function testOrderWithStateCompleteShouldNotBeCanceled()
+ {
+ $order = $this->getOrder('100000001');
+ $result = $this->sendCancelRequest($order);
+ $this->assertFalse($result);
+ }
}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipOrderTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipOrderTest.php
index e2a156cc6fbfc..049f49c93c7cd 100644
--- a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipOrderTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipOrderTest.php
@@ -5,6 +5,12 @@
*/
namespace Magento\Sales\Service\V1;
+use Magento\Catalog\Api\ProductRepositoryInterface;
+use Magento\Framework\ObjectManagerInterface;
+use Magento\Sales\Api\Data\OrderItemInterface;
+use Magento\Sales\Api\ShipmentRepositoryInterface;
+use Magento\Sales\Model\Order;
+
/**
* API test for creation of Shipment for certain Order.
*/
@@ -14,22 +20,28 @@ class ShipOrderTest extends \Magento\TestFramework\TestCase\WebapiAbstract
const SERVICE_VERSION = 'V1';
/**
- * @var \Magento\Framework\ObjectManagerInterface
+ * @var ObjectManagerInterface
*/
private $objectManager;
/**
- * @var \Magento\Sales\Api\ShipmentRepositoryInterface
+ * @var ShipmentRepositoryInterface
*/
private $shipmentRepository;
+ /**
+ * @var ProductRepositoryInterface
+ */
+ private $productRepository;
+
+ /**
+ * @inheritdoc
+ */
protected function setUp()
{
$this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
-
- $this->shipmentRepository = $this->objectManager->get(
- \Magento\Sales\Api\ShipmentRepositoryInterface::class
- );
+ $this->shipmentRepository = $this->objectManager->get(ShipmentRepositoryInterface::class);
+ $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class);
}
/**
@@ -40,9 +52,8 @@ public function testConfigurableShipOrder()
$this->markTestIncomplete('https://github.com/magento-engcom/msi/issues/1335');
$productsQuantity = 1;
- /** @var \Magento\Sales\Model\Order $existingOrder */
- $existingOrder = $this->objectManager->create(\Magento\Sales\Model\Order::class)
- ->loadByIncrementId('100000001');
+ /** @var Order $existingOrder */
+ $existingOrder = $this->getOrder('100000001');
$requestData = [
'orderId' => $existingOrder->getId(),
@@ -83,9 +94,8 @@ public function testConfigurableShipOrder()
*/
public function testShipOrder()
{
- /** @var \Magento\Sales\Model\Order $existingOrder */
- $existingOrder = $this->objectManager->create(\Magento\Sales\Model\Order::class)
- ->loadByIncrementId('100000001');
+ /** @var Order $existingOrder */
+ $existingOrder = $this->getOrder('100000001');
$requestData = [
'orderId' => $existingOrder->getId(),
@@ -103,7 +113,7 @@ public function testShipOrder()
]
];
- /** @var \Magento\Sales\Api\Data\OrderItemInterface $item */
+ /** @var OrderItemInterface $item */
foreach ($existingOrder->getAllItems() as $item) {
$requestData['items'][] = [
'order_item_id' => $item->getItemId(),
@@ -121,9 +131,8 @@ public function testShipOrder()
$this->fail('Failed asserting that Shipment was created');
}
- /** @var \Magento\Sales\Model\Order $updatedOrder */
- $updatedOrder = $this->objectManager->create(\Magento\Sales\Model\Order::class)
- ->loadByIncrementId('100000001');
+ /** @var Order $updatedOrder */
+ $updatedOrder = $this->getOrder('100000001');
$this->assertNotEquals(
$existingOrder->getStatus(),
@@ -144,9 +153,8 @@ public function testShipOrderWithoutTrackingNumberReturnsError()
{
$this->_markTestAsRestOnly('SOAP requires an tracking number to be provided so this case is not possible.');
- /** @var \Magento\Sales\Model\Order $existingOrder */
- $existingOrder = $this->objectManager->create(\Magento\Sales\Model\Order::class)
- ->loadByIncrementId('100000001');
+ /** @var Order $existingOrder */
+ $existingOrder = $this->getOrder('100000001');
$requestData = [
'orderId' => $existingOrder->getId(),
@@ -170,9 +178,7 @@ public function testShipOrderWithoutTrackingNumberReturnsError()
*/
public function testPartialShipOrderWithBundleShippedSeparately()
{
- /** @var \Magento\Sales\Model\Order $existingOrder */
- $existingOrder = $this->objectManager->create(\Magento\Sales\Model\Order::class)
- ->loadByIncrementId('100000001');
+ $existingOrder = $this->getOrder('100000001');
$requestData = [
'orderId' => $existingOrder->getId(),
@@ -213,9 +219,8 @@ public function testPartialShipOrderWithBundleShippedSeparately()
$this->assertEquals(1, $shipment->getTotalQty());
- /** @var \Magento\Sales\Model\Order $existingOrder */
- $existingOrder = $this->objectManager->create(\Magento\Sales\Model\Order::class)
- ->loadByIncrementId('100000001');
+ /** @var Order $existingOrder */
+ $existingOrder = $this->getOrder('100000001');
foreach ($existingOrder->getAllItems() as $item) {
if ($item->getItemId() == $shippedItemId) {
@@ -227,10 +232,73 @@ public function testPartialShipOrderWithBundleShippedSeparately()
}
/**
- * @param \Magento\Sales\Model\Order $order
+ * @magentoApiDataFixture Magento/Bundle/_files/order_with_2_bundles_shipping_separately.php
+ */
+ public function testPartialShipOrderWithTwoBundleShippedSeparatelyContainsSameSimple()
+ {
+ $order = $this->getOrder('order_bundle_separately_shipped');
+
+ $requestData = [
+ 'orderId' => $order->getId(),
+ 'items' => [],
+ 'comment' => [
+ 'comment' => 'Test Comment',
+ 'is_visible_on_front' => 1,
+ ],
+ 'tracks' => [],
+ ];
+
+ $shippedItemId = null;
+ $parentItemId = null;
+ foreach ($order->getAllItems() as $item) {
+ if ($item->getSku() === 'simple1') {
+ $requestData['items'][] = [
+ 'order_item_id' => $item->getItemId(),
+ 'qty' => $item->getQtyOrdered(),
+ ];
+ $shippedItemId = $item->getItemId();
+ $parentItemId = $item->getParentItemId();
+ break;
+ }
+ }
+
+ $shipmentId = $this->_webApiCall($this->getServiceInfo($order), $requestData);
+ $this->assertNotEmpty($shipmentId);
+
+ try {
+ $shipment = $this->shipmentRepository->get($shipmentId);
+ } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
+ $this->fail('Failed asserting that Shipment was created');
+ }
+
+ $this->assertEquals(1, $shipment->getTotalQty());
+
+ $order = $this->getOrder('order_bundle_separately_shipped');
+
+ foreach ($order->getAllItems() as $item) {
+ if (in_array($item->getItemId(), [$shippedItemId, $parentItemId])) {
+ $this->assertEquals(1, $item->getQtyShipped());
+ continue;
+ }
+ $this->assertEquals(0, $item->getQtyShipped());
+ }
+
+ try {
+ $this->_webApiCall($this->getServiceInfo($order), $requestData);
+ $this->fail('Expected exception was not raised');
+ } catch (\Exception $exception) {
+ $this->assertExceptionMessage(
+ $exception,
+ 'Shipment Document Validation Error(s): You can\'t create a shipment without products.'
+ );
+ }
+ }
+
+ /**
+ * @param Order $order
* @return array
*/
- private function getServiceInfo(\Magento\Sales\Model\Order $order)
+ private function getServiceInfo(Order $order): array
{
$serviceInfo = [
'rest' => [
@@ -243,6 +311,41 @@ private function getServiceInfo(\Magento\Sales\Model\Order $order)
'operation' => self::SERVICE_READ_NAME . 'execute',
],
];
+
return $serviceInfo;
}
+
+ /**
+ * Returns order by increment id.
+ *
+ * @param string $incrementId
+ * @return Order
+ */
+ private function getOrder(string $incrementId): Order
+ {
+ return $this->objectManager->create(Order::class)->loadByIncrementId($incrementId);
+ }
+
+ /**
+ * Assert correct exception message.
+ *
+ * @param \Exception $exception
+ * @param string $expectedMessage
+ * @return void
+ */
+ private function assertExceptionMessage(\Exception $exception, string $expectedMessage): void
+ {
+ $actualMessage = '';
+ switch (TESTS_WEB_API_ADAPTER) {
+ case self::ADAPTER_SOAP:
+ $actualMessage = trim(preg_replace('/\s+/', ' ', $exception->getMessage()));
+ break;
+ case self::ADAPTER_REST:
+ $error = $this->processRestExceptionResult($exception);
+ $actualMessage = $error['message'];
+ break;
+ }
+
+ $this->assertEquals($expectedMessage, $actualMessage);
+ }
}
diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/order_with_2_bundles_shipping_separately.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/order_with_2_bundles_shipping_separately.php
new file mode 100644
index 0000000000000..11d99ef59f911
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/order_with_2_bundles_shipping_separately.php
@@ -0,0 +1,97 @@
+create(Address::class, ['data' => $addressData]);
+$billingAddress->setAddressType('billing');
+
+$shippingAddress = clone $billingAddress;
+$shippingAddress->setId(null)
+ ->setAddressType('shipping')
+ ->setShippingMethod('flatrate_flatrate');
+
+/** @var Payment $payment */
+$payment = $objectManager->create(Payment::class);
+$payment->setMethod('checkmo');
+
+/** @var ProductRepositoryInterface $productRepository */
+$productRepository = $objectManager->create(ProductRepositoryInterface::class);
+
+$bundleProduct = $productRepository->get('bundle-product-separate-shipping-1');
+$bundleProduct2 = $productRepository->get('bundle-product-separate-shipping-2');
+$selectionProducts = [
+ $bundleProduct->getId() => [10, 12],
+ $bundleProduct2->getId() => [11, 13],
+];
+
+/** @var Cart $cart */
+$cart = $objectManager->create(Cart::class);
+
+foreach ([$bundleProduct, $bundleProduct2] as $product) {
+
+ /** @var BundleProductType $typeInstance */
+ $typeInstance = $product->getTypeInstance();
+ $typeInstance->setStoreFilter($product->getStoreId(), $product);
+ $optionCollection = $typeInstance->getOptionsCollection($product);
+
+ $bundleOptions = [];
+ $bundleOptionsQty = [];
+ $optionsData = [];
+
+ /** @var Option $option */
+ foreach ($optionCollection as $option) {
+ $selectionsCollection = $typeInstance->getSelectionsCollection([$option->getId()], $product);
+ $selectionIds = $selectionProducts[$product->getId()];
+ $selectionsCollection->addIdFilter($selectionIds);
+
+ foreach ($selectionIds as $productId) {
+ $selection = $selectionsCollection->getItemByColumnValue('product_id', $productId);
+ if ($selection !== null) {
+ $bundleOptions[$option->getId()] = $selection->getSelectionId();
+ $optionsData[$option->getId()] = $selection->getProductId();
+ $bundleOptionsQty[$option->getId()] = 1;
+ }
+ }
+ }
+
+ $requestInfo = [
+ 'product' => $product->getId(),
+ 'bundle_option' => $bundleOptions,
+ 'bundle_option_qty' => $bundleOptionsQty,
+ 'qty' => 1,
+ ];
+
+ $cart->addProduct($product, $requestInfo);
+}
+
+$cart->getQuote()
+ ->setReservedOrderId('order_bundle_separately_shipped')
+ ->setBillingAddress($billingAddress)
+ ->setShippingAddress($shippingAddress)
+ ->setCheckoutMethod(CartManagementInterface::METHOD_GUEST)
+ ->setPayment($payment);
+$cart->save();
+
+/** @var QuoteManagement $quoteManager */
+$quoteManager = $objectManager->get(QuoteManagement::class);
+$orderId = $quoteManager->placeOrder($cart->getQuote()->getId());
+
+$objectManager->removeSharedInstance(Session::class);
diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/order_with_2_bundles_shipping_separately_rollback.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/order_with_2_bundles_shipping_separately_rollback.php
new file mode 100644
index 0000000000000..96e3adb65a7d6
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/order_with_2_bundles_shipping_separately_rollback.php
@@ -0,0 +1,8 @@
+create(ProductRepositoryInterface::class);
+
+/** @var ProductFactory $productFactory */
+$productFactory = $objectManager->create(ProductFactory::class);
+
+/** @var OptionInterfaceFactory $bundleOptionFactory */
+$bundleOptionFactory = $objectManager->create(OptionInterfaceFactory::class);
+
+/** @var LinkInterfaceFactory $bundleLinkFactory */
+$bundleLinkFactory = $objectManager->create(LinkInterfaceFactory::class);
+
+$bundleProduct = $productFactory->create();
+$attributeSetId = $bundleProduct->getDefaultAttributeSetId();
+$bundleProduct->setTypeId(BundleProductType::TYPE_BUNDLE)
+ ->setAttributeSetId($attributeSetId)
+ ->setWebsiteIds([1])
+ ->setName('Bundle Product With Separate Items Shipping')
+ ->setSku('bundle-product-separate-shipping-1')
+ ->setVisibility(Visibility::VISIBILITY_BOTH)
+ ->setStatus(Status::STATUS_ENABLED)
+ ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1])
+ ->setPriceView(1)
+ ->setPriceType(BundlePrice::PRICE_TYPE_FIXED)
+ ->setPrice(10.0)
+ ->setShipmentType(1)
+ ->setBundleOptionsData(
+ [
+ [
+ 'title' => 'Option 1',
+ 'default_title' => 'Option 1',
+ 'type' => 'radio',
+ 'required' => 1,
+ 'delete' => '',
+ ],
+ [
+ 'title' => 'Option 2',
+ 'default_title' => 'Option 2',
+ 'type' => 'radio',
+ 'required' => 1,
+ 'delete' => '',
+ ],
+ ]
+ )->setBundleSelectionsData(
+ [
+ [
+ ['product_id' => 10, 'selection_qty' => 1, 'selection_can_change_qty' => 1, 'delete' => ''],
+ ['product_id' => 11, 'selection_qty' => 1, 'selection_can_change_qty' => 1, 'delete' => ''],
+ ],
+ [
+ ['product_id' => 12, 'selection_qty' => 1, 'selection_can_change_qty' => 1, 'delete' => ''],
+ ['product_id' => 13, 'selection_qty' => 1, 'selection_can_change_qty' => 1, 'delete' => ''],
+ ],
+ ]
+ );
+
+$bundleProduct2 = $productFactory->create(['data' => $bundleProduct->getData()]);
+$bundleProduct2
+ ->setName('Bundle Product With Separate Items Shipping Two')
+ ->setSku('bundle-product-separate-shipping-2');
+
+foreach ([$bundleProduct, $bundleProduct2] as $product) {
+ if ($product->getBundleOptionsData()) {
+ $options = [];
+ foreach ($product->getBundleOptionsData() as $key => $optionData) {
+ if (!(bool)$optionData['delete']) {
+ $option = $bundleOptionFactory->create(['data' => $optionData]);
+ $option->setSku($product->getSku());
+ $option->setOptionId(null);
+
+ $links = [];
+ $bundleLinks = $product->getBundleSelectionsData();
+ if (!empty($bundleLinks[$key])) {
+ foreach ($bundleLinks[$key] as $linkData) {
+ if (!(bool)$linkData['delete']) {
+ $link = $bundleLinkFactory->create(['data' => $linkData]);
+ $linkProduct = $productRepository->getById($linkData['product_id']);
+ $link->setSku($linkProduct->getSku());
+ $link->setQty($linkData['selection_qty']);
+ $links[] = $link;
+ }
+ }
+ $option->setProductLinks($links);
+ $options[] = $option;
+ }
+ }
+ }
+ $extension = $product->getExtensionAttributes();
+ $extension->setBundleProductOptions($options);
+ $product->setExtensionAttributes($extension);
+ }
+
+ $productRepository->save($product, true);
+}
diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/two_bundle_products_with_separate_shipping_rollback.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/two_bundle_products_with_separate_shipping_rollback.php
new file mode 100644
index 0000000000000..c2c4d992d763e
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/two_bundle_products_with_separate_shipping_rollback.php
@@ -0,0 +1,33 @@
+get(Registry::class);
+
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', true);
+
+/** @var ProductRepositoryInterface $productRepository */
+$productRepository = $objectManager->get(ProductRepositoryInterface::class);
+
+try {
+ $productRepository->deleteById('bundle-product-separate-shipping-1');
+ $productRepository->deleteById('bundle-product-separate-shipping-2');
+} catch (NoSuchEntityException $exception) {
+ // When DbIsolation is used products can be already removed by rollback main transaction
+}
+
+$registry->register('isSecureArea', false);
+
+require __DIR__ . '/multiple_products_rollback.php';
diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_state_hold.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_state_hold.php
new file mode 100644
index 0000000000000..5578a55b57e8b
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_state_hold.php
@@ -0,0 +1,15 @@
+setState(Order::STATE_HOLDED);
+$order->setStatus(Order::STATE_HOLDED);
+$order->save();