diff --git a/app/code/Magento/Bundle/Model/Product/SelectionProductsDisabledRequired.php b/app/code/Magento/Bundle/Model/Product/SelectionProductsDisabledRequired.php deleted file mode 100644 index 424330a1671e..000000000000 --- a/app/code/Magento/Bundle/Model/Product/SelectionProductsDisabledRequired.php +++ /dev/null @@ -1,173 +0,0 @@ -bundleSelection = $bundleSelection; - $this->storeManager = $storeManager; - $this->catalogProductStatus = $catalogProductStatus; - $this->productCollectionFactory = $productCollectionFactory; - $this->metadataPool = $metadataPool; - } - - /** - * Return ids of options and child products when all products in required option are disabled in bundle product - * - * @param int $bundleId - * @param int|null $websiteId - * @return array - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - public function getChildProductIds(int $bundleId, ?int $websiteId = null): array - { - if (!$websiteId) { - $websiteId = (int)$this->storeManager->getStore()->getWebsiteId(); - } - $cacheKey = $this->getCacheKey($bundleId, $websiteId); - if (isset($this->productsDisabledRequired[$cacheKey])) { - return $this->productsDisabledRequired[$cacheKey]; - } - $selectionProductIds = $this->bundleSelection->getChildrenIds($bundleId); - /** for cases when no required products found */ - if (count($selectionProductIds) === 1 && isset($selectionProductIds[0])) { - $this->productsDisabledRequired[$cacheKey] = []; - return $this->productsDisabledRequired[$cacheKey]; - } - $products = $this->getProducts($selectionProductIds, $websiteId); - if (!$products) { - $this->productsDisabledRequired[$cacheKey] = []; - return $this->productsDisabledRequired[$cacheKey]; - } - foreach ($selectionProductIds as $optionId => $optionProductIds) { - foreach ($optionProductIds as $productId) { - if (isset($products[$productId])) { - /** @var Product $product */ - $product = $products[$productId]; - if (in_array($product->getStatus(), $this->catalogProductStatus->getVisibleStatusIds())) { - unset($selectionProductIds[$optionId]); - } - } - } - } - $this->productsDisabledRequired[$cacheKey] = $selectionProductIds; - return $this->productsDisabledRequired[$cacheKey]; - } - - /** - * Get products objects - * - * @param array $selectionProductIds - * @param int $websiteId - * @return ProductInterface[] - */ - private function getProducts(array $selectionProductIds, int $websiteId): array - { - $productIds = []; - $defaultStore = $this->storeManager->getWebsite($websiteId)->getDefaultStore(); - $defaultStoreId = $defaultStore ? $defaultStore->getId() : null; - foreach ($selectionProductIds as $optionProductIds) { - $productIds[] = $optionProductIds; - } - $productIds = array_merge([], ...$productIds); - $productCollection = $this->productCollectionFactory->create(); - $productCollection->joinAttribute( - ProductInterface::STATUS, - Product::ENTITY . '/' . ProductInterface::STATUS, - $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(), - null, - 'inner', - $defaultStoreId - ); - $productCollection->addIdFilter($productIds); - $productCollection->addStoreFilter($defaultStoreId); - $productCollection->setFlag($this->hasStockStatusFilter, true); - return $productCollection->getItems(); - } - - /** - * Get cache key - * - * @param int $bundleId - * @param int $websiteId - * @return string - */ - private function getCacheKey(int $bundleId, int $websiteId): string - { - return $bundleId . '-' . $websiteId; - } - - /** - * @inheritDoc - */ - public function _resetState(): void - { - $this->productsDisabledRequired = []; - } -} diff --git a/app/code/Magento/Bundle/Model/ResourceModel/Indexer/Price/DisabledProductOptionPriceModifier.php b/app/code/Magento/Bundle/Model/ResourceModel/Indexer/Price/DisabledProductOptionPriceModifier.php deleted file mode 100644 index 1730c3b62d3b..000000000000 --- a/app/code/Magento/Bundle/Model/ResourceModel/Indexer/Price/DisabledProductOptionPriceModifier.php +++ /dev/null @@ -1,157 +0,0 @@ -resourceConnection = $resourceConnection; - $this->config = $config; - $this->metadataPool = $metadataPool; - $this->bundleSelection = $bundleSelection; - $this->selectionProductsDisabledRequired = $selectionProductsDisabledRequired; - } - - /** - * Remove bundle product from price index when all products in required option are disabled - * - * @param IndexTableStructure $priceTable - * @param array $entityIds - * @return void - * @throws \Magento\Framework\Exception\LocalizedException - */ - public function modifyPrice(IndexTableStructure $priceTable, array $entityIds = []) : void - { - foreach ($this->getBundleIds($entityIds) as $entityId) { - $entityId = (int) $entityId; - foreach ($this->getWebsiteIdsOfProduct($entityId) as $websiteId) { - $websiteId = (int) $websiteId; - $productIdsDisabledRequired = $this->selectionProductsDisabledRequired - ->getChildProductIds($entityId, $websiteId); - if ($productIdsDisabledRequired) { - $connection = $this->resourceConnection->getConnection('indexer'); - $select = $connection->select(); - $select->from(['price_index' => $priceTable->getTableName()], []); - $priceEntityField = $priceTable->getEntityField(); - $select->where('price_index.website_id = ?', $websiteId); - $select->where("price_index.{$priceEntityField} = ?", $entityId); - $query = $select->deleteFromSelect('price_index'); - $connection->query($query); - } - } - } - } - - /** - * Get all website ids of product - * - * @param int $entityId - * @return array - */ - private function getWebsiteIdsOfProduct(int $entityId): array - { - if (isset($this->websiteIdsOfProduct[$entityId])) { - return $this->websiteIdsOfProduct[$entityId]; - } - $connection = $this->resourceConnection->getConnection('indexer'); - $select = $connection->select(); - $select->from( - ['product_in_websites' => $this->resourceConnection->getTableName('catalog_product_website')], - ['website_id'] - )->where('product_in_websites.product_id = ?', $entityId); - $this->websiteIdsOfProduct[$entityId] = $connection->fetchCol($select); - - return $this->websiteIdsOfProduct[$entityId]; - } - - /** - * Get Bundle Ids - * - * @param array $entityIds - * @return \Traversable - */ - private function getBundleIds(array $entityIds): \Traversable - { - $connection = $this->resourceConnection->getConnection('indexer'); - $select = $connection->select(); - $select->from( - ['cpe' => $this->resourceConnection->getTableName('catalog_product_entity')], - ['entity_id'] - )->where('cpe.entity_id in ( ? )', !empty($entityIds) ? $entityIds : [0], \Zend_Db::INT_TYPE) - ->where('type_id = ?', Type::TYPE_BUNDLE); - - $statement = $select->query(); - while ($id = $statement->fetchColumn()) { - yield $id; - } - } - - /** - * @inheritDoc - */ - public function _resetState(): void - { - $this->websiteIdsOfProduct = []; - } -} diff --git a/app/code/Magento/Bundle/Plugin/Catalog/Helper/Product.php b/app/code/Magento/Bundle/Plugin/Catalog/Helper/Product.php deleted file mode 100644 index 0b090b2cbad7..000000000000 --- a/app/code/Magento/Bundle/Plugin/Catalog/Helper/Product.php +++ /dev/null @@ -1,82 +0,0 @@ -selectionProductsDisabledRequired = $selectionProductsDisabledRequired; - $this->scopeConfig = $scopeConfig; - $this->productRepository = $productRepository; - } - - /** - * Do not show bundle product when all products in required option are disabled - * - * @param Subject $subject - * @param bool $result - * @param ProductModel|int $product - * @return bool - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function afterCanShow(Subject $subject, $result, $product) - { - if (is_int($product)) { - $product = $this->productRepository->getById($product); - } - $productId = (int)$product->getEntityId(); - if ($result == false || $product->getTypeId() !== Type::TYPE_BUNDLE) { - return $result; - } - $isShowOutOfStock = $this->scopeConfig->getValue( - Configuration::XML_PATH_SHOW_OUT_OF_STOCK, - ScopeInterface::SCOPE_STORE - ); - if ($isShowOutOfStock) { - return $result; - } - $productIdsDisabledRequired = $this->selectionProductsDisabledRequired->getChildProductIds($productId); - return $productIdsDisabledRequired ? false : $result; - } -} diff --git a/app/code/Magento/Bundle/etc/di.xml b/app/code/Magento/Bundle/etc/di.xml index 7601224056be..3ddefc1a0559 100644 --- a/app/code/Magento/Bundle/etc/di.xml +++ b/app/code/Magento/Bundle/etc/di.xml @@ -277,13 +277,6 @@ - - - - Magento\Bundle\Model\ResourceModel\Indexer\Price\DisabledProductOptionPriceModifier - - - diff --git a/app/code/Magento/Bundle/etc/frontend/di.xml b/app/code/Magento/Bundle/etc/frontend/di.xml index 411cf91cbc8b..54f5ff0a1f48 100644 --- a/app/code/Magento/Bundle/etc/frontend/di.xml +++ b/app/code/Magento/Bundle/etc/frontend/di.xml @@ -22,7 +22,4 @@ - - - diff --git a/app/code/Magento/BundleGraphQl/Model/Resolver/Products/DataProvider/Product/DisabledProductOptionPostProcessor.php b/app/code/Magento/BundleGraphQl/Model/Resolver/Products/DataProvider/Product/DisabledProductOptionPostProcessor.php deleted file mode 100644 index 8887fa14fd8c..000000000000 --- a/app/code/Magento/BundleGraphQl/Model/Resolver/Products/DataProvider/Product/DisabledProductOptionPostProcessor.php +++ /dev/null @@ -1,70 +0,0 @@ -selectionProductsDisabledRequired = $selectionProductsDisabledRequired; - } - - /** - * Remove bundle product from collection when all products in required option are disabled - * - * @param Collection $collection - * @param array $attributeNames - * @param ContextInterface|null $context - * @return Collection - * @throws \Magento\Framework\Exception\LocalizedException - * @throws \Magento\Framework\Exception\NoSuchEntityException - */ - public function process( - Collection $collection, - array $attributeNames, - ?ContextInterface $context = null - ): Collection { - if (!$collection->isLoaded()) { - $collection->load(); - } - /** @var Product $product */ - foreach ($collection as $key => $product) { - if ($product->getTypeId() !== Product\Type::TYPE_BUNDLE || $context === null) { - continue; - } - $productId = (int)$product->getEntityId(); - $websiteId = (int)$context->getExtensionAttributes()->getStore()->getWebsiteId(); - $productIdsDisabledRequired = $this->selectionProductsDisabledRequired->getChildProductIds( - $productId, - $websiteId - ); - if ($productIdsDisabledRequired) { - $collection->removeItemByKey($key); - } - } - return $collection; - } -} diff --git a/app/code/Magento/BundleGraphQl/composer.json b/app/code/Magento/BundleGraphQl/composer.json index e8cc6723f1f8..2f7605cc1098 100644 --- a/app/code/Magento/BundleGraphQl/composer.json +++ b/app/code/Magento/BundleGraphQl/composer.json @@ -6,7 +6,6 @@ "php": "~8.1.0||~8.2.0||~8.3.0", "magento/module-catalog": "*", "magento/module-bundle": "*", - "magento/module-graph-ql": "*", "magento/module-catalog-graph-ql": "*", "magento/module-quote": "*", "magento/module-quote-graph-ql": "*", diff --git a/app/code/Magento/BundleGraphQl/etc/di.xml b/app/code/Magento/BundleGraphQl/etc/di.xml index 879359839a64..15acad7c6bf0 100644 --- a/app/code/Magento/BundleGraphQl/etc/di.xml +++ b/app/code/Magento/BundleGraphQl/etc/di.xml @@ -23,11 +23,4 @@ - - - - Magento\BundleGraphQl\Model\Resolver\Products\DataProvider\Product\DisabledProductOptionPostProcessor - - - diff --git a/app/code/Magento/Catalog/etc/db_schema.xml b/app/code/Magento/Catalog/etc/db_schema.xml index 36decbcff089..b564ffa3e652 100644 --- a/app/code/Magento/Catalog/etc/db_schema.xml +++ b/app/code/Magento/Catalog/etc/db_schema.xml @@ -1645,6 +1645,11 @@ + + + + + diff --git a/app/code/Magento/Catalog/etc/db_schema_whitelist.json b/app/code/Magento/Catalog/etc/db_schema_whitelist.json index fd332606bb22..4b6a247f5ff4 100644 --- a/app/code/Magento/Catalog/etc/db_schema_whitelist.json +++ b/app/code/Magento/Catalog/etc/db_schema_whitelist.json @@ -992,7 +992,8 @@ "index": { "CATALOG_PRODUCT_INDEX_PRICE_TMP_CUSTOMER_GROUP_ID": true, "CATALOG_PRODUCT_INDEX_PRICE_TMP_WEBSITE_ID": true, - "CATALOG_PRODUCT_INDEX_PRICE_TMP_MIN_PRICE": true + "CATALOG_PRODUCT_INDEX_PRICE_TMP_MIN_PRICE": true, + "CAT_PRD_IDX_PRICE_TMP_ENTT_ID_CSTR_GROUP_ID_WS_ID": true }, "constraint": { "PRIMARY": true diff --git a/app/code/Magento/Cms/Test/Fixture/Block.php b/app/code/Magento/Cms/Test/Fixture/Block.php new file mode 100644 index 000000000000..2a3c4079504e --- /dev/null +++ b/app/code/Magento/Cms/Test/Fixture/Block.php @@ -0,0 +1,69 @@ + 'block%uniqid%', + BlockInterface::TITLE => 'Block%uniqid%', + BlockInterface::CONTENT => 'BlockContent%uniqid%', + BlockInterface::CREATION_TIME => null, + BlockInterface::UPDATE_TIME => null, + 'active' => true + ]; + + /** + * @param ProcessorInterface $dataProcessor + * @param ServiceFactory $serviceFactory + */ + public function __construct( + private readonly ProcessorInterface $dataProcessor, + private readonly ServiceFactory $serviceFactory + ) { + } + + /** + * {@inheritdoc} + * @param array $data Parameters. Same format as Block::DEFAULT_DATA. + */ + public function apply(array $data = []): ?DataObject + { + $data = $this->dataProcessor->process($this, array_merge(self::DEFAULT_DATA, $data)); + $service = $this->serviceFactory->create(BlockRepositoryInterface::class, 'save'); + + return $service->execute(['block' => $data]); + } + + /** + * @inheritdoc + */ + public function revert(DataObject $data): void + { + $service = $this->serviceFactory->create(BlockRepositoryInterface::class, 'deleteById'); + $service->execute(['blockId' => $data->getId()]); + } +} diff --git a/app/code/Magento/GraphQlCache/Model/Plugin/View/Layout.php b/app/code/Magento/GraphQlCache/Model/Plugin/View/Layout.php new file mode 100644 index 000000000000..c2668a5d4d40 --- /dev/null +++ b/app/code/Magento/GraphQlCache/Model/Plugin/View/Layout.php @@ -0,0 +1,57 @@ +cacheableQuery->addCacheTags($block->getIdentities()); + } + return $result; + } +} diff --git a/app/code/Magento/GraphQlCache/etc/graphql/di.xml b/app/code/Magento/GraphQlCache/etc/graphql/di.xml index 1a85f02b5be9..f1a63cfb7be7 100644 --- a/app/code/Magento/GraphQlCache/etc/graphql/di.xml +++ b/app/code/Magento/GraphQlCache/etc/graphql/di.xml @@ -29,4 +29,7 @@ + + + diff --git a/app/code/Magento/OpenSearch/Model/Adapter/DynamicTemplates/PriceMapper.php b/app/code/Magento/OpenSearch/Model/Adapter/DynamicTemplates/PriceMapper.php index 03ced99cc632..545d8b95c006 100644 --- a/app/code/Magento/OpenSearch/Model/Adapter/DynamicTemplates/PriceMapper.php +++ b/app/code/Magento/OpenSearch/Model/Adapter/DynamicTemplates/PriceMapper.php @@ -19,7 +19,8 @@ public function processTemplates(array $templates): array { $templates[] = [ 'price_mapping' => [ - 'match' => 'price_*', + "match_pattern" => "regex", + 'match' => 'price_\\d+_\\d+', 'match_mapping_type' => 'string', 'mapping' => [ 'type' => 'double', diff --git a/app/code/Magento/OpenSearch/Test/Unit/Model/OpenSearchTest.php b/app/code/Magento/OpenSearch/Test/Unit/Model/OpenSearchTest.php index 5fa55dcab9ca..551b4ee98275 100644 --- a/app/code/Magento/OpenSearch/Test/Unit/Model/OpenSearchTest.php +++ b/app/code/Magento/OpenSearch/Test/Unit/Model/OpenSearchTest.php @@ -147,7 +147,8 @@ public function testAddFieldsMapping() 'dynamic_templates' => [ [ 'price_mapping' => [ - 'match' => 'price_*', + "match_pattern" => "regex", + 'match' => 'price_\\d+_\\d+', 'match_mapping_type' => 'string', 'mapping' => [ 'type' => 'double', diff --git a/app/code/Magento/Quote/Model/Quote.php b/app/code/Magento/Quote/Model/Quote.php index 7270734e3ff4..380d2d7ca1c6 100644 --- a/app/code/Magento/Quote/Model/Quote.php +++ b/app/code/Magento/Quote/Model/Quote.php @@ -12,6 +12,7 @@ use Magento\Framework\Api\AttributeValueFactory; use Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface; use Magento\Framework\App\ObjectManager; +use Magento\Framework\DataObject; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Model\AbstractExtensibleModel; use Magento\Quote\Api\Data\PaymentInterface; @@ -1619,7 +1620,7 @@ public function addItem(\Magento\Quote\Model\Quote\Item $item) * Add product. Returns error message if product type instance can't prepare product. * * @param mixed $product - * @param null|float|\Magento\Framework\DataObject $request + * @param null|float|DataObject $request * @param null|string $processMode * @return \Magento\Quote\Model\Quote\Item|string * @throws \Magento\Framework\Exception\LocalizedException @@ -1637,11 +1638,12 @@ public function addProduct( if (is_numeric($request)) { $request = $this->objectFactory->create(['qty' => $request]); } - if (!$request instanceof \Magento\Framework\DataObject) { + if (!$request instanceof DataObject) { throw new \Magento\Framework\Exception\LocalizedException( __('We found an invalid request for adding product to quote.') ); } + $invalidProductAddFlag = $this->checkForInvalidProductAdd($request); if (!$product->isSalable()) { throw new \Magento\Framework\Exception\LocalizedException( @@ -1699,7 +1701,9 @@ public function addProduct( // collect errors instead of throwing first one if ($item->getHasError()) { - $this->deleteItem($item); + if (!$invalidProductAddFlag) { + $this->deleteItem($item); + } foreach ($item->getMessage(false) as $message) { if (!in_array($message, $errors)) { // filter duplicate messages @@ -1717,6 +1721,20 @@ public function addProduct( return $parentItem; } + /** + * Checks if invalid products should be added to quote + * + * @param DataObject $request + * @return bool + */ + private function checkForInvalidProductAdd(DataObject $request): bool + { + $forceAdd = $request->getAddToCartInvalidProduct(); + $request->unsetData('add_to_cart_invalid_product'); + + return (bool) $forceAdd; + } + /** * Adding catalog product object data to quote * @@ -1772,8 +1790,8 @@ protected function _addCatalogProduct(\Magento\Catalog\Model\Product $product, $ * For more options see \Magento\Catalog\Helper\Product->addParamsToBuyRequest() * * @param int $itemId - * @param \Magento\Framework\DataObject $buyRequest - * @param null|array|\Magento\Framework\DataObject $params + * @param DataObject $buyRequest + * @param null|array|DataObject $params * @return \Magento\Quote\Model\Quote\Item * @throws \Magento\Framework\Exception\LocalizedException * @@ -1795,9 +1813,9 @@ public function updateItem($itemId, $buyRequest, $params = null) $product = clone $this->productRepository->getById($productId, false, $this->getStore()->getId()); if (!$params) { - $params = new \Magento\Framework\DataObject(); + $params = new DataObject(); } elseif (is_array($params)) { - $params = new \Magento\Framework\DataObject($params); + $params = new DataObject($params); } $params->setCurrentConfig($item->getBuyRequest()); $buyRequest = $this->_catalogProduct->addParamsToBuyRequest($buyRequest, $params); @@ -2146,7 +2164,7 @@ protected function _clearErrorInfo() * @param string|null $origin Usually a name of module, that embeds error * @param int|null $code Error code, unique for origin, that sets it * @param string|null $message Error message - * @param \Magento\Framework\DataObject|null $additionalData Any additional data, that caller would like to store + * @param DataObject|null $additionalData Any additional data, that caller would like to store * @return $this */ public function addErrorInfo( diff --git a/app/code/Magento/Quote/Test/Unit/Model/QuoteTest.php b/app/code/Magento/Quote/Test/Unit/Model/QuoteTest.php index b7571da30b9b..4698ab56dfbf 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/QuoteTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/QuoteTest.php @@ -30,6 +30,7 @@ use Magento\Framework\DataObject\Copy; use Magento\Framework\DataObject\Factory; use Magento\Framework\Event\Manager; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Model\Context; use Magento\Framework\Phrase; @@ -1072,17 +1073,28 @@ public function testAddProductItemPreparation(): void } /** + * @param $request + * @param $hasError * @return void + * @throws LocalizedException + * @dataProvider dataProviderForTestAddProductItem */ - public function testAddProductItemNew(): void + public function testAddProductItemNew($request, $hasError): void { - $itemMock = $this->createMock(Item::class); + $itemMock = $this->getMockBuilder(Item::class) + ->disableOriginalConstructor() + ->addMethods(['getHasError']) + ->onlyMethods(['representProduct', 'setProduct', 'setOptions', 'setQuote', 'getProduct']) + ->getMock(); + $itemMock->expects($this->once())->method('getHasError')->willReturn($hasError); + $product = $this->createMock(Product::class); + $itemMock->expects($this->any())->method('getProduct')->willReturn($product); $expectedResult = $itemMock; $requestMock = $this->createMock( DataObject::class ); - $this->objectFactoryMock->expects($this->once()) + $this->objectFactoryMock->expects($this->any()) ->method('create') ->with(['qty' => 1]) ->willReturn($requestMock); @@ -1145,10 +1157,29 @@ public function testAddProductItemNew(): void ->method('getTypeInstance') ->willReturn($typeInstanceMock); - $result = $this->quote->addProduct($this->productMock, null); + $result = $this->quote->addProduct($this->productMock, $request); $this->assertEquals($expectedResult, $result); } + /** + * @return array[] + */ + public function dataProviderForTestAddProductItem(): array + { + return [ + 'not_invalid_product_add' => [null, false], + 'invalid_product_add' => [ + new DataObject( + [ + 'add_to_cart_invalid_product' => true, + 'qty' => 1 + ] + ), + true + ] + ]; + } + /** * @return void */ diff --git a/app/code/Magento/Sales/Controller/AbstractController/Reorder.php b/app/code/Magento/Sales/Controller/AbstractController/Reorder.php index 5eb485e26219..e4dc6b00204d 100644 --- a/app/code/Magento/Sales/Controller/AbstractController/Reorder.php +++ b/app/code/Magento/Sales/Controller/AbstractController/Reorder.php @@ -94,16 +94,6 @@ public function execute() // to session for guest customer, as it does \Magento\Checkout\Model\Cart::save which is deprecated. $this->checkoutSession->setQuoteId($reorderOutput->getCart()->getId()); - $errors = $reorderOutput->getErrors(); - if (!empty($errors)) { - $useNotice = $this->_objectManager->get(\Magento\Checkout\Model\Session::class)->getUseNotice(true); - foreach ($errors as $error) { - $useNotice - ? $this->messageManager->addNoticeMessage($error->getMessage()) - : $this->messageManager->addErrorMessage($error->getMessage()); - } - } - return $resultRedirect->setPath('checkout/cart'); } } diff --git a/app/code/Magento/Sales/Model/Reorder/Reorder.php b/app/code/Magento/Sales/Model/Reorder/Reorder.php index 9c89b73e8d25..27c2e280848a 100644 --- a/app/code/Magento/Sales/Model/Reorder/Reorder.php +++ b/app/code/Magento/Sales/Model/Reorder/Reorder.php @@ -111,6 +111,11 @@ class Reorder */ private $storeManager; + /** + * @var bool + */ + private bool $addToCartInvalidProduct; + /** * @param OrderFactory $orderFactory * @param CustomerCartResolver $customerCartProvider @@ -121,6 +126,9 @@ class Reorder * @param ProductCollectionFactory $productCollectionFactory * @param OrderInfoBuyRequestGetter $orderInfoBuyRequestGetter * @param StoreManagerInterface|null $storeManager + * @param bool $addToCartInvalidProduct + * + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( OrderFactory $orderFactory, @@ -131,7 +139,8 @@ public function __construct( LoggerInterface $logger, ProductCollectionFactory $productCollectionFactory, OrderInfoBuyRequestGetter $orderInfoBuyRequestGetter, - ?StoreManagerInterface $storeManager = null + ?StoreManagerInterface $storeManager = null, + bool $addToCartInvalidProduct = false ) { $this->orderFactory = $orderFactory; $this->cartRepository = $cartRepository; @@ -143,6 +152,7 @@ public function __construct( $this->orderInfoBuyRequestGetter = $orderInfoBuyRequestGetter; $this->storeManager = $storeManager ?: ObjectManager::getInstance()->get(StoreManagerInterface::class); + $this->addToCartInvalidProduct = $addToCartInvalidProduct; } /** @@ -276,6 +286,7 @@ private function addItemToCart(OrderItemInterface $orderItem, Quote $cart, Produ $addProductResult = null; try { + $infoBuyRequest->setAddToCartInvalidProduct($this->addToCartInvalidProduct); $addProductResult = $cart->addProduct($product, $infoBuyRequest); } catch (\Magento\Framework\Exception\LocalizedException $e) { $this->addError($this->getCartItemErrorMessage($orderItem, $product, $e->getMessage())); diff --git a/app/code/Magento/Sales/etc/frontend/di.xml b/app/code/Magento/Sales/etc/frontend/di.xml index b2d4a7d78a6e..695cf037d776 100644 --- a/app/code/Magento/Sales/etc/frontend/di.xml +++ b/app/code/Magento/Sales/etc/frontend/di.xml @@ -22,4 +22,9 @@ + + + true + + diff --git a/app/code/Magento/SalesRule/Model/ResourceModel/Rule.php b/app/code/Magento/SalesRule/Model/ResourceModel/Rule.php index 125e2194b45e..4ed17d7ef815 100644 --- a/app/code/Magento/SalesRule/Model/ResourceModel/Rule.php +++ b/app/code/Magento/SalesRule/Model/ResourceModel/Rule.php @@ -305,18 +305,19 @@ public function getStoreLabel($ruleId, $storeId) public function getActiveAttributes() { $connection = $this->getConnection(); + $subSelect = $connection->select(); + $subSelect->reset(); + $subSelect->from($this->getTable('salesrule_product_attribute')) + ->group('attribute_id'); $select = $connection->select()->from( - ['a' => $this->getTable('salesrule_product_attribute')], - new \Zend_Db_Expr('DISTINCT ea.attribute_code') + ['a' => $subSelect], + new \Zend_Db_Expr('ea.attribute_code') )->joinInner( ['ea' => $this->getTable('eav_attribute')], 'ea.attribute_id = a.attribute_id', [] - )->joinInner( - ['sr' => $this->getTable('salesrule')], - 'a.' . $this->getLinkField() . ' = sr.' . $this->getLinkField() . ' AND sr.is_active = 1', - [] ); + return $connection->fetchAll($select); } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Bundle/BundleProductViewTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Bundle/BundleProductViewTest.php index cff69caa05a6..bec3402fe370 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Bundle/BundleProductViewTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Bundle/BundleProductViewTest.php @@ -14,13 +14,15 @@ use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Test\Fixture\Product as ProductFixture; use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Store\Test\Fixture\Group as GroupFixture; +use Magento\Store\Test\Fixture\Store as StoreFixture; +use Magento\Store\Test\Fixture\Website as WebsiteFixture; use Magento\TestFramework\Fixture\DataFixture; +use Magento\TestFramework\Fixture\DbIsolation; use Magento\TestFramework\ObjectManager; use Magento\TestFramework\TestCase\GraphQlAbstract; use Magento\Store\Model\StoreManagerInterface; use Magento\Catalog\Model\Product\Attribute\Source\Status as ProductStatus; -use Magento\TestFramework\Fixture\DataFixtureStorage; -use Magento\TestFramework\Fixture\DataFixtureStorageManager; /** * Test querying Bundle products @@ -278,7 +280,7 @@ private function assertBundleProductOptions($product, $actualResponse) 'quantity' => (int)$bundleProductLink->getQty(), 'position' => $bundleProductLink->getPosition(), 'is_default' => (bool)$bundleProductLink->getIsDefault(), - 'price_type' => self::KEY_PRICE_TYPE_FIXED, + 'price_type' => self::KEY_PRICE_TYPE_FIXED, 'can_change_quantity' => $bundleProductLink->getCanChangeQuantity() ] ); @@ -443,9 +445,20 @@ public function testNonExistentFieldQtyExceptionOnBundleProduct() $this->graphQlQuery($query); } - /** - * @magentoApiDataFixture Magento/Bundle/_files/product_1.php - */ + #[ + DbIsolation(false), + DataFixture(WebsiteFixture::class, as: 'website2'), + DataFixture(GroupFixture::class, ['website_id' => '$website2.id$'], 'group2'), + DataFixture(StoreFixture::class, ['store_group_id' => '$group2.id$', 'code' => 'store2'], 'store2'), + DataFixture(ProductFixture::class, ['sku' => 'p1', 'website_ids' => [1, '$website2.id$']], 'p1'), + DataFixture(ProductFixture::class, ['sku' => 'p2', 'website_ids' => [1, '$website2.id$']], 'p2'), + DataFixture(BundleOptionFixture::class, ['product_links' => ['$p1$']], 'opt1'), + DataFixture(BundleOptionFixture::class, ['product_links' => ['$p2$']], 'opt2'), + DataFixture( + BundleProductFixture::class, + ['sku' => 'bundle-product', '_options' => ['$opt1$', '$opt2$'], 'website_ids' => [1, '$website2.id$']] + ), + ] public function testBundleProductWithDisabledProductOption() { /** @var StoreManagerInterface $storeManager */ @@ -453,7 +466,7 @@ public function testBundleProductWithDisabledProductOption() $storeIdDefault = $storeManager->getDefaultStoreView()->getId(); /** @var ProductRepositoryInterface $productRepository */ $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); - $simpleProduct = $productRepository->get('simple', false, $storeIdDefault, true); + $simpleProduct = $productRepository->get('p1', true, $storeIdDefault, true); $simpleProduct->setStatus(ProductStatus::STATUS_DISABLED); $simpleProduct->setStoreIds([$storeIdDefault]); $productRepository->save($simpleProduct); @@ -508,8 +521,11 @@ public function testBundleProductWithDisabledProductOption() } QUERY; - $response = $this->graphQlQuery($query); + $response = $this->graphQlQuery($query, [], '', ['Store' => 'default']); $this->assertEmpty($response['products']['items']); + $response = $this->graphQlQuery($query, [], '', ['Store' => 'store2']); + $this->assertNotEmpty($response['products']['items']); + $this->assertEquals($productSku, $response['products']['items'][0]['sku']); } #[ diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/PageCache/CacheTagTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/PageCache/CacheTagTest.php index 93e6caebe2ba..46fa6de59467 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/PageCache/CacheTagTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/PageCache/CacheTagTest.php @@ -7,9 +7,19 @@ namespace Magento\GraphQl\PageCache; +use Magento\Catalog\Api\CategoryRepositoryInterface; use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Model\Product; +use Magento\Catalog\Test\Fixture\Category as CategoryFixture; +use Magento\Cms\Model\BlockRepository; +use Magento\Cms\Test\Fixture\Block as BlockFixture; use Magento\GraphQlCache\Model\CacheId\CacheIdCalculator; +use Magento\PageCache\Model\Config; +use Magento\Store\Model\Store; +use Magento\TestFramework\Fixture\Config as ConfigFixture; +use Magento\TestFramework\Fixture\DataFixture; +use Magento\TestFramework\Fixture\DataFixtureStorageManager; +use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\ObjectManager; /** @@ -133,6 +143,140 @@ public function testCacheInvalidationForCategoriesWithProduct() ); } + #[ + ConfigFixture(Config::XML_PAGECACHE_TYPE, Config::VARNISH), + DataFixture(BlockFixture::class, ['content' => 'Original Block content'], as: 'block'), + DataFixture(CategoryFixture::class, as: 'category1'), + DataFixture(CategoryFixture::class, ['description' => 'Original Category description'], as: 'category2'), + ] + public function testCacheInvalidationForCategoriesWithWidget(): void + { + $fixtures = DataFixtureStorageManager::getStorage(); + $block = $fixtures->get('block'); + $category1 = $fixtures->get('category1'); + $category2 = $fixtures->get('category2'); + $queryForCategory1 = $this->getCategoriesQuery((int) $category1->getId()); + $queryForCategory2 = $this->getCategoriesQuery((int) $category2->getId()); + + $this->updateCategoryDescription((int) $category1->getId(), $this->getBlockWidget((int) $block->getId())); + + $responseCacheIdForCategory1 = $this->getQueryResponseCacheKey($queryForCategory1); + // Verify we get MISS for category1 query in the first request + $responseMissForCategory1 = $this->assertCacheMissAndReturnResponse( + $queryForCategory1, + [CacheIdCalculator::CACHE_ID_HEADER => $responseCacheIdForCategory1] + ); + + // Verify we get HIT for category 1 query in the second request + $responseHitForCategory1 = $this->assertCacheHitAndReturnResponse( + $queryForCategory1, + [CacheIdCalculator::CACHE_ID_HEADER => $responseCacheIdForCategory1] + ); + + $this->assertEquals($responseMissForCategory1['body'], $responseHitForCategory1['body']); + + // Verify category1 description contains block content + $this->assertCategoryDescription('Original Block content', $responseHitForCategory1); + + $responseCacheIdForCategory2 = $this->getQueryResponseCacheKey($queryForCategory2); + // Verify we get MISS for category2 query in the first request + $responseMissForCategory2 = $this->assertCacheMissAndReturnResponse( + $queryForCategory2, + [CacheIdCalculator::CACHE_ID_HEADER => $responseCacheIdForCategory2] + ); + + // Verify we get HIT for category 2 query in the second request + $responseHitForCategory2 = $this->assertCacheHitAndReturnResponse( + $queryForCategory2, + [CacheIdCalculator::CACHE_ID_HEADER => $responseCacheIdForCategory2] + ); + + $this->assertEquals($responseMissForCategory2['body'], $responseHitForCategory2['body']); + + // Verify category2 description is the same as created + $this->assertCategoryDescription('Original Category description', $responseHitForCategory2); + + // Update block content + $newBlockContent = 'New block content!!!'; + $this->updateBlockContent((int) $block->getId(), $newBlockContent); + + // Verify we get MISS for category1 query after block is updated + $this->assertCacheMissAndReturnResponse( + $queryForCategory1, + [CacheIdCalculator::CACHE_ID_HEADER => $responseCacheIdForCategory1] + ); + + // Verify we get HIT for category1 query in the second request after block is updated + $responseHitForCategory1 = $this->assertCacheHitAndReturnResponse( + $queryForCategory1, + [CacheIdCalculator::CACHE_ID_HEADER => $responseCacheIdForCategory1] + ); + + // Verify we get HIT for category2 query after block is updated + $responseHitForCategory2 = $this->assertCacheHitAndReturnResponse( + $queryForCategory2, + [CacheIdCalculator::CACHE_ID_HEADER => $responseCacheIdForCategory2] + ); + + // Verify the updated block data is returned in category1 query response + $this->assertCategoryDescription($newBlockContent, $responseHitForCategory1); + + // Verify category2 description is the same as created + $this->assertCategoryDescription('Original Category description', $responseHitForCategory2); + } + + private function assertCategoryDescription(string $expected, array $response): void + { + $responseBody = $response['body']; + $this->assertIsArray($responseBody); + $this->assertArrayNotHasKey('errors', $responseBody); + $this->assertStringContainsString($expected, $responseBody['categories']['items'][0]['description']); + } + + private function getQueryResponseCacheKey(string $query): string + { + $response = $this->graphQlQueryWithResponseHeaders($query); + $this->assertArrayHasKey(CacheIdCalculator::CACHE_ID_HEADER, $response['headers']); + return $response['headers'][CacheIdCalculator::CACHE_ID_HEADER]; + } + + private function updateBlockContent(int $id, string $content): void + { + $blockRepository = Bootstrap::getObjectManager()->get(BlockRepository::class); + $block = $blockRepository->getById($id); + $block->setContent($content); + $blockRepository->save($block); + } + + private function updateCategoryDescription(int $id, string $description): void + { + $categoryRepository = Bootstrap::getObjectManager()->get(CategoryRepositoryInterface::class); + $category = $categoryRepository->get($id, Store::DEFAULT_STORE_ID); + $category->setCustomAttribute('description', $description); + $categoryRepository->save($category); + } + + private function getBlockWidget(int $blockId): string + { + return "{{widget type=\"Magento\\Cms\\Block\\Widget\\Block\" " . + "template=\"widget/static_block/default.phtml\" " . + "block_id=\"$blockId\" " . + "type_name=\"CMS Static Block\"}}"; + } + + private function getCategoriesQuery(int $categoryId): string + { + return <<get(ResourceConnection::class)->getConnection(); + $indexes = $connection->getIndexList($tableName); + $this->assertArrayHasKey($indexName, $indexes); + $this->assertSame($columns, $indexes[$indexName]['COLUMNS_LIST']); + $this->assertSame($indexType, $indexes[$indexName]['INDEX_TYPE']); + } + + /** + * @return array[] + */ + public function indexDataProvider(): array + { + return [ + [ + 'catalog_product_index_price_tmp', + 'CAT_PRD_IDX_PRICE_TMP_ENTT_ID_CSTR_GROUP_ID_WS_ID', + ['entity_id', 'customer_group_id', 'website_id'] + ] + ]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Order/ReorderTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Order/ReorderTest.php index 6a508e9a95ee..6e2015c5a241 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Controller/Order/ReorderTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Controller/Order/ReorderTest.php @@ -108,13 +108,7 @@ public function testReorderProductLowQty(): void $order = $this->orderFactory->create()->loadByIncrementId('55555555'); $this->customerSession->setCustomerId($order->getCustomerId()); $this->dispatchReorderRequest((int)$order->getId()); - $origMessage = (string)__('The requested qty is not available'); - $message = $this->escaper->escapeHtml( - __('Could not add the product with SKU "%1" to the shopping cart: %2', 'simple-1', $origMessage) - ); - $constraint = $this->logicalOr($this->containsEqual($origMessage), $this->containsEqual($message)); - $this->assertThat($this->getMessages(MessageInterface::TYPE_ERROR), $constraint); - $this->quote = $this->checkoutSession->getQuote(); + $this->assertRedirect($this->stringContains('checkout/cart')); } /** diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Order/ReorderWithDifferentStoreTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Order/ReorderWithDifferentStoreTest.php index 33d86189aa4f..0e9f60719df9 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Controller/Order/ReorderWithDifferentStoreTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Controller/Order/ReorderWithDifferentStoreTest.php @@ -117,7 +117,7 @@ public function testReorderWithDifferentStoreAndGlobalCustomerAccount(): void $this->dispatch('sales/order/reorder/'); $this->assertRedirect($this->stringContains('checkout/cart')); $this->quote = $this->checkoutSession->getQuote(); - $quoteItemsCollection = $this->quote->getAllItems(); - $this->assertCount(0, $quoteItemsCollection); + + $this->assertCount(1, $this->quote->getErrors()); } } diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Model/ResourceModel/RuleTest.php b/dev/tests/integration/testsuite/Magento/SalesRule/Model/ResourceModel/RuleTest.php index 1299c5fca09f..f2ec3bbb5449 100644 --- a/dev/tests/integration/testsuite/Magento/SalesRule/Model/ResourceModel/RuleTest.php +++ b/dev/tests/integration/testsuite/Magento/SalesRule/Model/ResourceModel/RuleTest.php @@ -72,8 +72,6 @@ public function testAfterSave() public function testGetActiveAttributes() { $rule = $this->fixtures->get('rule1'); - $items = $this->resource->getActiveAttributes(); - $this->assertEquals([], $items); $rule->setIsActive(1); $rule->save(); $items = $this->resource->getActiveAttributes(); diff --git a/lib/internal/Magento/Framework/Mview/Test/Unit/View/ChangelogBatchWalkerTest.php b/lib/internal/Magento/Framework/Mview/Test/Unit/View/ChangelogBatchWalkerTest.php new file mode 100644 index 000000000000..afbe2cf33e05 --- /dev/null +++ b/lib/internal/Magento/Framework/Mview/Test/Unit/View/ChangelogBatchWalkerTest.php @@ -0,0 +1,174 @@ +resourceConnection = $this->getMockBuilder(ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + $this->generator = $this->getMockBuilder(Generator::class) + ->disableOriginalConstructor() + ->getMock(); + $this->idsTableBuilder = $this->getMockBuilder(IdsTableBuilderInterface::class) + ->getMockForAbstractClass(); + $this->idsSelectBuilder = $this->getMockBuilder(IdsSelectBuilderInterface::class) + ->getMockForAbstractClass(); + $this->idsContext = $this->getMockBuilder(IdsContext::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->idsContext->expects($this->any()) + ->method('getSelectBuilder') + ->willReturn($this->idsSelectBuilder); + $this->idsContext->expects($this->any()) + ->method('getTableBuilder') + ->willReturn($this->idsTableBuilder); + + $this->changeLog = $this->getMockBuilder(ChangelogInterface::class) + ->getMockForAbstractClass(); + $this->connection = $this->getMockBuilder(AdapterInterface::class) + ->getMockForAbstractClass(); + $this->table = $this->getMockBuilder(Table::class) + ->disableOriginalConstructor() + ->getMock(); + $this->resourceConnection->expects($this->any()) + ->method('getConnection') + ->willReturn($this->connection); + + $this->select = $this->getMockBuilder(Select::class) + ->disableOriginalConstructor() + ->getMock(); + $this->select->expects($this->any()) + ->method('from') + ->willReturnSelf(); + $this->select->expects($this->any()) + ->method('where') + ->willReturnSelf(); + $this->select->expects($this->any()) + ->method('distinct') + ->willReturnSelf(); + $this->connection->expects($this->any()) + ->method('select') + ->willReturn($this->select); + + $this->model = new ChangelogBatchWalker( + $this->resourceConnection, + $this->generator, + $this->idsContext + ); + } + + public function testNoTemporaryTablesUsed() + { + $this->connection->expects($this->once()) + ->method('isTableExists') + ->willReturn(true); + $this->table->expects($this->any()) + ->method('getColumns') + ->willReturn([]); + $this->idsTableBuilder->expects($this->any()) + ->method('build') + ->willReturn($this->table); + $this->idsSelectBuilder->expects($this->any()) + ->method('build') + ->willReturn($this->select); + $this->generator->expects($this->any()) + ->method('generate') + ->willReturn([]); + + foreach ($this->model->walk($this->changeLog, 1, 2, 1) as $iteration) { + $this->assertEmpty($iteration); + $this->connection->expects($this->once()) + ->method('createTable'); + $this->connection->expects($this->never()) + ->method('createTemporaryTableTable'); + } + } +} diff --git a/lib/internal/Magento/Framework/Mview/View/ChangelogBatchWalker.php b/lib/internal/Magento/Framework/Mview/View/ChangelogBatchWalker.php index 61956b0cd3ae..474c687a1381 100644 --- a/lib/internal/Magento/Framework/Mview/View/ChangelogBatchWalker.php +++ b/lib/internal/Magento/Framework/Mview/View/ChangelogBatchWalker.php @@ -24,30 +24,34 @@ class ChangelogBatchWalker implements ChangelogBatchWalkerInterface { /** - * @var \Magento\Framework\App\ResourceConnection + * @var ResourceConnection */ private ResourceConnection $resourceConnection; + /** - * @var \Magento\Framework\DB\Query\Generator + * @var Generator */ private Generator $generator; + /** - * @var \Magento\Framework\Mview\View\ChangelogBatchWalker\IdsTableBuilderInterface + * @var IdsTableBuilderInterface */ private IdsTableBuilderInterface $idsTableBuilder; + /** - * @var \Magento\Framework\Mview\View\ChangelogBatchWalker\IdsSelectBuilderInterface + * @var IdsSelectBuilderInterface */ private IdsSelectBuilderInterface $idsSelectBuilder; + /** - * @var \Magento\Framework\Mview\View\ChangelogBatchWalker\IdsFetcherInterface + * @var IdsFetcherInterface */ private IdsFetcherInterface $idsFetcher; /** * @param ResourceConnection $resourceConnection - * @param \Magento\Framework\DB\Query\Generator $generator - * @param \Magento\Framework\Mview\View\ChangelogBatchWalker\IdsContext $idsContext + * @param Generator $generator + * @param IdsContext $idsContext */ public function __construct( ResourceConnection $resourceConnection, @@ -80,7 +84,7 @@ public function walk( $idsTable = $this->idsTableBuilder->build($changelog); try { - $connection->createTemporaryTable($idsTable); + $connection->createTable($idsTable); $columns = $this->getIdsColumns($idsTable); @@ -122,7 +126,7 @@ public function walk( yield $ids; } } finally { - $connection->dropTemporaryTable($idsTable->getName()); + $connection->dropTable($idsTable->getName()); } } diff --git a/lib/internal/Magento/Framework/Mview/View/ChangelogBatchWalker/IdsTableBuilder.php b/lib/internal/Magento/Framework/Mview/View/ChangelogBatchWalker/IdsTableBuilder.php index 69b1527fe661..fe0753144dca 100644 --- a/lib/internal/Magento/Framework/Mview/View/ChangelogBatchWalker/IdsTableBuilder.php +++ b/lib/internal/Magento/Framework/Mview/View/ChangelogBatchWalker/IdsTableBuilder.php @@ -50,7 +50,6 @@ public function build(ChangelogInterface $changelog): Table ['unsigned' => true, 'nullable' => false], 'Entity ID' ); - $table->setOption('type', 'memory'); $table->addIndex( self::INDEX_NAME_UNIQUE, [ diff --git a/lib/internal/Magento/Framework/Test/Unit/Mview/View/ChangelogBatchWalker/IdsTableBuilderTest.php b/lib/internal/Magento/Framework/Test/Unit/Mview/View/ChangelogBatchWalker/IdsTableBuilderTest.php new file mode 100644 index 000000000000..a46cab3dc2cc --- /dev/null +++ b/lib/internal/Magento/Framework/Test/Unit/Mview/View/ChangelogBatchWalker/IdsTableBuilderTest.php @@ -0,0 +1,87 @@ +changeLog = $this->getMockBuilder(ChangelogInterface::class) + ->getMockForAbstractClass(); + $this->connection = $this->getMockBuilder(AdapterInterface::class) + ->getMockForAbstractClass(); + $this->table = $this->getMockBuilder(Table::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->resourceConnection = $this->getMockBuilder(ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + $this->resourceConnection->expects($this->any()) + ->method('getConnection') + ->willReturn($this->connection); + $this->connection->expects($this->any()) + ->method('newTable') + ->willReturn($this->table); + + $this->model = new IdsTableBuilder($this->resourceConnection); + } + + public function testBuildDoNotCreateMemoryTable() : void + { + $this->table->expects($this->never()) + ->method('setOption') + ->with('type', 'memory'); + + $this->model->build($this->changeLog); + } +}