diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/UpdateCartItems.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/UpdateCartItems.php index ed1cb7a9dab50..ddd713f8008fd 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/UpdateCartItems.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/UpdateCartItems.php @@ -116,17 +116,17 @@ private function processCartItems(Quote $cart, array $items): void if (!isset($item['quantity'])) { throw new GraphQlInputException(__('Required parameter "quantity" for "cart_items" is missing.')); } - $qty = (float)$item['quantity']; + $quantity = (float)$item['quantity']; - if ($qty <= 0.0) { + if ($quantity <= 0.0) { $this->cartItemRepository->deleteById((int)$cart->getId(), $itemId); } else { $customizableOptions = $item['customizable_options'] ?? null; if ($customizableOptions === null) { // Update only item's qty - $this->updateItemQty($itemId, $cart, $qty); + $this->updateItemQty($itemId, $cart, $quantity); } else { // Update customizable options (and QTY if changed) - $this->updateCartItem->execute($cart, $itemId, $qty, $customizableOptions); + $this->updateCartItem->execute($cart, $itemId, $quantity, $customizableOptions); $this->quoteRepository->save($cart); } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/EditQuoteItemCustomOptionsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/EditQuoteItemCustomOptionsTest.php new file mode 100644 index 0000000000000..09495f92648be --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/EditQuoteItemCustomOptionsTest.php @@ -0,0 +1,275 @@ +getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + $this->productCustomOptionsRepository = $objectManager->get(ProductCustomOptionRepositoryInterface::class); + $this->quoteFactory = $objectManager->get(QuoteFactory::class); + $this->quoteResource = $objectManager->get(QuoteResource::class); + $this->productRepository = $objectManager->get(ProductRepositoryInterface::class); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/set_custom_options_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product_with_options.php + */ + public function testChangeQuoteItemCustomOptions() + { + $sku = 'simple_product'; + $quoteItemId = $this->getQuoteItemIdBySku($sku); + $customOptionsValues = $this->getCustomOptionsValuesForQuery($sku); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $customizableOptionsQuery = preg_replace('/"([^"]+)"\s*:\s*/', '$1:', json_encode($customOptionsValues)); + + $query = $this->getQuery($maskedQuoteId, $quoteItemId, $customizableOptionsQuery); + $response = $this->graphQlMutation($query); + $itemOptionsResponse = $response['updateCartItems']['cart']['items'][0]['customizable_options']; + self::assertCount(2, $itemOptionsResponse); + self::assertEquals('test', $itemOptionsResponse[0]['values'][0]['value']); + self::assertEquals('test', $itemOptionsResponse[1]['values'][0]['value']); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/set_custom_options_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product_with_options.php + */ + public function testOptionsSetPersistsOnQtyChange() + { + $sku = 'simple_product'; + $newQuantity = 2; + $quoteItemId = $this->getQuoteItemIdBySku($sku); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = <<graphQlMutation($query); + $cartItemResponse = $response['updateCartItems']['cart']['items'][0]; + + self::assertEquals($newQuantity, $cartItemResponse['quantity']); + self::assertCount(2, $cartItemResponse['customizable_options']); + self::assertEquals('initial value', $cartItemResponse['customizable_options'][0]['values'][0]['value']); + self::assertEquals('initial value', $cartItemResponse['customizable_options'][1]['values'][0]['value']); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/set_custom_options_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product_with_options.php + */ + public function testOptionsSetChangedOnChangeOneOption() + { + $sku = 'simple_product'; + $quoteItemId = $this->getQuoteItemIdBySku($sku); + + /* Get only the first option */ + $customOptionsValues = array_slice($this->getCustomOptionsValuesForQuery($sku), 0, 1); + + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $customizableOptionsQuery = preg_replace('/"([^"]+)"\s*:\s*/', '$1:', json_encode($customOptionsValues)); + $query = $this->getQuery($maskedQuoteId, $quoteItemId, $customizableOptionsQuery); + + $response = $this->graphQlMutation($query); + $itemOptionsResponse = $response['updateCartItems']['cart']['items'][0]['customizable_options']; + self::assertCount(1, $itemOptionsResponse); + self::assertEquals('test', $itemOptionsResponse[0]['values'][0]['value']); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/set_custom_options_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product_with_options.php + * @group recent + */ + public function testOptionSetPersistsOnExtraOptionWithIncorrectId() + { + $sku = 'simple_product'; + $quoteItemId = $this->getQuoteItemIdBySku($sku); + $customOptionsValues = $this->getCustomOptionsValuesForQuery($sku); + + /* Add nonexistent option to the query */ + $customOptionsValues[] = ['id' => -10, 'value' => 'value']; + + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $customizableOptionsQuery = preg_replace('/"([^"]+)"\s*:\s*/', '$1:', json_encode($customOptionsValues)); + $query = $this->getQuery($maskedQuoteId, $quoteItemId, $customizableOptionsQuery); + + $response = $this->graphQlMutation($query); + $itemOptionsResponse = $response['updateCartItems']['cart']['items'][0]['customizable_options']; + self::assertCount(2, $itemOptionsResponse); + } + + /** + * Returns GraphQl query for updating items in shopping cart + * + * @param string $maskedQuoteId + * @param int $quoteItemId + * @param $customizableOptionsQuery + * @return string + */ + private function getQuery(string $maskedQuoteId, int $quoteItemId, $customizableOptionsQuery): string + { + return <<quoteFactory->create(); + $product = $this->productRepository->get($sku); + $this->quoteResource->load($quote, 'test_quote', 'reserved_order_id'); + /** @var Item $quoteItem */ + $quoteItem = $quote->getItemByProduct($product); + + return (int)$quoteItem->getId(); + } + + + /** + * Generate an array with test values for customizable options + * based on the option type + * + * @param string $sku + * @return array + */ + private function getCustomOptionsValuesForQuery(string $sku): array + { + $customOptions = $this->productCustomOptionsRepository->getList($sku); + $customOptionsValues = []; + + foreach ($customOptions as $customOption) { + $optionType = $customOption->getType(); + if ($optionType == 'field' || $optionType == 'area') { + $customOptionsValues[] = [ + 'id' => (int) $customOption->getOptionId(), + 'value' => 'test' + ]; + } elseif ($optionType == 'drop_down') { + $optionSelectValues = $customOption->getValues(); + $customOptionsValues[] = [ + 'id' => (int) $customOption->getOptionId(), + 'value' => reset($optionSelectValues)->getOptionTypeId() + ]; + } + } + + return $customOptionsValues; + } +} diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Catalog/_files/set_custom_options_simple_product.php b/dev/tests/integration/testsuite/Magento/GraphQl/Catalog/_files/set_custom_options_simple_product.php new file mode 100644 index 0000000000000..618ed0e25602f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Catalog/_files/set_custom_options_simple_product.php @@ -0,0 +1,54 @@ + 'test_option_code_1', + 'type' => 'field', + 'is_require' => false, + 'sort_order' => 1, + 'price' => -10.0, + 'price_type' => 'fixed', + 'sku' => 'sku1', + 'max_characters' => 100, + ], + [ + 'title' => 'area option', + 'type' => 'area', + 'is_require' => false, + 'sort_order' => 2, + 'price' => 20.0, + 'price_type' => 'percent', + 'sku' => 'sku2', + 'max_characters' => 100 + ] +]; + +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +$product = $productRepository->get('simple_product'); +/** @var ProductCustomOptionInterfaceFactory $customOptionFactory */ +$customOptionFactory = $objectManager->get(ProductCustomOptionInterfaceFactory::class); + +foreach ($optionsSet as $option) { + /** @var ProductCustomOptionInterface $customOption */ + $customOption = $customOptionFactory->create(['data' => $option]); + $customOption->setProductSku($product->getSku()); + + $productCustomOptions[] = $customOption; +} + +$product->setOptions($productCustomOptions); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/add_simple_product_with_options.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/add_simple_product_with_options.php new file mode 100644 index 0000000000000..68e2261ab02b0 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/add_simple_product_with_options.php @@ -0,0 +1,51 @@ +get(ProductRepositoryInterface::class); +/** @var QuoteFactory $quoteFactory */ +$quoteFactory = Bootstrap::getObjectManager()->get(QuoteFactory::class); +/** @var QuoteResource $quoteResource */ +$quoteResource = Bootstrap::getObjectManager()->get(QuoteResource::class); +/** @var CartRepositoryInterface $cartRepository */ +$cartRepository = Bootstrap::getObjectManager()->get(CartRepositoryInterface::class); +/** @var OptionFactory $productOptionFactory */ +$productOptionFactory = Bootstrap::getObjectManager()->get(OptionFactory::class); +/** @var DataObjectFactory $dataObjectFactory */ +$dataObjectFactory = Bootstrap::getObjectManager()->get(DataObjectFactory::class); + +/** @var ProductOption $productOption */ +$productOption = $productOptionFactory->create(); +$product = $productRepository->get('simple_product'); +$productOptions = $productOption->getProductOptionCollection($product); +$cartItemCustomOptions = []; + +/** @var ProductOption $productOption */ +foreach ($productOptions as $productOption) { + $cartItemCustomOptions[$productOption->getId()] = 'initial value'; +} + +$request = $dataObjectFactory->create([ + 'data' => [ + 'qty' => 1.0, + 'options' => $cartItemCustomOptions, + ], +]); + +$quote = $quoteFactory->create(); +$quoteResource->load($quote, 'test_quote', 'reserved_order_id'); +$quote->addProduct($product, $request); +$cartRepository->save($quote);