Merge pull request #8769 from adobe-commerce-tier-4/L3-PR-2024-02-16

[Support Tier-4 vasyliev] 02-16-2024 Regular delivery of bugfixes and improvements
This commit is contained in:
Dmytro Horytskyi authored and GitHub committed 2024-03-19 15:33:34 -05:00
commit ba25af8a9e
31 files changed
+732 -561

No files matched your search

@@ -1,173 +0,0 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\Bundle\Model\Product;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Catalog\Model\Product\Attribute\Source\Status;
use Magento\Bundle\Model\ResourceModel\Selection as BundleSelection;
use Magento\Framework\ObjectManager\ResetAfterRequestInterface;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory as ProductCollectionFactory;
use Magento\Catalog\Model\Product;
use Magento\Catalog\Api\Data\ProductInterface;
/**
* Class to return ids of options and child products when all products in required option are disabled in bundle product
*/
class SelectionProductsDisabledRequired implements ResetAfterRequestInterface
{
/**
* @var BundleSelection
*/
private $bundleSelection;
/**
* @var StoreManagerInterface
*/
private $storeManager;
/**
* @var Status
*/
private $catalogProductStatus;
/**
* @var ProductCollectionFactory
*/
private $productCollectionFactory;
/**
* @var MetadataPool
*/
private $metadataPool;
/**
* @var string
*/
private $hasStockStatusFilter = 'has_stock_status_filter';
/**
* @var array
*/
private $productsDisabledRequired = [];
/**
* @param BundleSelection $bundleSelection
* @param StoreManagerInterface $storeManager
* @param Status $catalogProductStatus
* @param ProductCollectionFactory $productCollectionFactory
* @param MetadataPool $metadataPool
*/
public function __construct(
BundleSelection $bundleSelection,
StoreManagerInterface $storeManager,
Status $catalogProductStatus,
ProductCollectionFactory $productCollectionFactory,
MetadataPool $metadataPool
) {
$this->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 = [];
}
}
@@ -1,157 +0,0 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\Bundle\Model\ResourceModel\Indexer\Price;
use Magento\Bundle\Model\Product\SelectionProductsDisabledRequired;
use Magento\Catalog\Model\Product\Type;
use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\IndexTableStructure;
use Magento\Framework\App\ResourceConnection;
use Magento\Catalog\Model\Config;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\PriceModifierInterface;
use Magento\Bundle\Model\ResourceModel\Selection as BundleSelection;
use Magento\Framework\ObjectManager\ResetAfterRequestInterface;
/**
* Remove bundle product from price index when all products in required option are disabled
*/
class DisabledProductOptionPriceModifier implements PriceModifierInterface, ResetAfterRequestInterface
{
/**
* @var ResourceConnection
*/
private $resourceConnection;
/**
* @var SelectionProductsDisabledRequired
*/
private $selectionProductsDisabledRequired;
/**
* @var array
*/
private $websiteIdsOfProduct = [];
/**
* @var BundleSelection
*/
private BundleSelection $bundleSelection;
/**
* @var Config
*/
private Config $config;
/**
* @var MetadataPool
*/
private MetadataPool $metadataPool;
/**
* @param ResourceConnection $resourceConnection
* @param Config $config
* @param MetadataPool $metadataPool
* @param BundleSelection $bundleSelection
* @param SelectionProductsDisabledRequired $selectionProductsDisabledRequired
*/
public function __construct(
ResourceConnection $resourceConnection,
Config $config,
MetadataPool $metadataPool,
BundleSelection $bundleSelection,
SelectionProductsDisabledRequired $selectionProductsDisabledRequired
) {
$this->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 = [];
}
}
@@ -1,82 +0,0 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\Bundle\Plugin\Catalog\Helper;
use Magento\Catalog\Model\Product as ProductModel;
use Magento\Catalog\Model\Product\Type;
use Magento\Catalog\Helper\Product as Subject;
use Magento\Bundle\Model\Product\SelectionProductsDisabledRequired;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\CatalogInventory\Model\Configuration;
use Magento\Store\Model\ScopeInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;
/**
* Plugin to not show bundle product when all products in required option are disabled
*/
class Product
{
/**
* @var SelectionProductsDisabledRequired
*/
private $selectionProductsDisabledRequired;
/**
* @var ScopeConfigInterface
*/
private $scopeConfig;
/**
* @var ProductRepositoryInterface
*/
private $productRepository;
/**
* @param SelectionProductsDisabledRequired $selectionProductsDisabledRequired
* @param ScopeConfigInterface $scopeConfig
* @param ProductRepositoryInterface $productRepository
*/
public function __construct(
SelectionProductsDisabledRequired $selectionProductsDisabledRequired,
ScopeConfigInterface $scopeConfig,
ProductRepositoryInterface $productRepository
) {
$this->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;
}
}
-7
View File
@@ -277,13 +277,6 @@
</argument>
</arguments>
</type>
<type name="Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\BasePriceModifier">
<arguments>
<argument name="priceModifiers" xsi:type="array">
<item name="DisabledProductOptionPriceModifier" xsi:type="object">Magento\Bundle\Model\ResourceModel\Indexer\Price\DisabledProductOptionPriceModifier</item>
</argument>
</arguments>
</type>
<type name="Magento\CatalogInventory\Observer\SaveInventoryDataObserver">
<arguments>
<argument name="parentItemProcessorPool" xsi:type="array">
@@ -22,7 +22,4 @@
<type name="Magento\Catalog\ViewModel\Product\OptionsData">
<plugin name="add_bundle_options_data" type="Magento\Bundle\Plugin\Catalog\ViewModel\Product\AddBundleOptionsData" />
</type>
<type name="Magento\Catalog\Helper\Product">
<plugin name="dont_show_if_child_products_disabled" type="Magento\Bundle\Plugin\Catalog\Helper\Product"/>
</type>
</config>
@@ -1,70 +0,0 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\BundleGraphQl\Model\Resolver\Products\DataProvider\Product;
use Magento\Catalog\Model\ResourceModel\Product\Collection;
use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product\CollectionPostProcessorInterface;
use Magento\GraphQl\Model\Query\ContextInterface;
use Magento\Catalog\Model\Product;
use Magento\Bundle\Model\Product\SelectionProductsDisabledRequired;
/**
* Remove bundle product from collection when all products in required option are disabled
*/
class DisabledProductOptionPostProcessor implements CollectionPostProcessorInterface
{
/**
* @var SelectionProductsDisabledRequired
*/
private $selectionProductsDisabledRequired;
/**
* @param SelectionProductsDisabledRequired $selectionProductsDisabledRequired
*/
public function __construct(
SelectionProductsDisabledRequired $selectionProductsDisabledRequired
) {
$this->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;
}
}
@@ -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": "*",
@@ -23,11 +23,4 @@
</argument>
</arguments>
</type>
<type name="Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product\CompositeCollectionPostProcessor">
<arguments>
<argument name="collectionPostProcessors" xsi:type="array">
<item name="disabled_product_option" xsi:type="object">Magento\BundleGraphQl\Model\Resolver\Products\DataProvider\Product\DisabledProductOptionPostProcessor</item>
</argument>
</arguments>
</type>
</config>
@@ -1645,6 +1645,11 @@
<constraint xsi:type="primary" referenceId="PRIMARY">
<column name="id"/>
</constraint>
<index referenceId="CAT_PRD_IDX_PRICE_TMP_ENTT_ID_CSTR_GROUP_ID_WS_ID" indexType="btree">
<column name="entity_id"/>
<column name="customer_group_id"/>
<column name="website_id"/>
</index>
</table>
<table name="catalog_category_product_index_tmp" resource="default" engine="innodb"
comment="Catalog Category Product Indexer temporary table">
@@ -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
@@ -0,0 +1,69 @@
<?php
/************************************************************************
*
* Copyright 2023 Adobe
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe and its suppliers, if any. The intellectual
* and technical concepts contained herein are proprietary to Adobe
* and its suppliers and are protected by all applicable intellectual
* property laws, including trade secret and copyright laws.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe.
* ************************************************************************
*/
declare(strict_types=1);
namespace Magento\Cms\Test\Fixture;
use Magento\Cms\Api\BlockRepositoryInterface;
use Magento\Cms\Api\Data\BlockInterface;
use Magento\Framework\DataObject;
use Magento\TestFramework\Fixture\Api\ServiceFactory;
use Magento\TestFramework\Fixture\Data\ProcessorInterface;
use Magento\TestFramework\Fixture\RevertibleDataFixtureInterface;
class Block implements RevertibleDataFixtureInterface
{
private const DEFAULT_DATA = [
BlockInterface::IDENTIFIER => '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()]);
}
}
@@ -0,0 +1,57 @@
<?php
/************************************************************************
*
* Copyright 2023 Adobe
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe and its suppliers, if any. The intellectual
* and technical concepts contained herein are proprietary to Adobe
* and its suppliers and are protected by all applicable intellectual
* property laws, including trade secret and copyright laws.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe.
* ************************************************************************
*/
declare(strict_types=1);
namespace Magento\GraphQlCache\Model\Plugin\View;
use Magento\Framework\DataObject\IdentityInterface;
use Magento\Framework\View\LayoutInterface;
use Magento\GraphQlCache\Model\CacheableQuery;
class Layout
{
/**
* @param CacheableQuery $cacheableQuery
*/
public function __construct(
private readonly CacheableQuery $cacheableQuery
) {
}
/**
* Add block cache tags to cacheable query
*
* @param LayoutInterface $subject
* @param mixed $result
* @param mixed $name
* @param mixed $block
* @return mixed
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function afterSetBlock(
LayoutInterface $subject,
mixed $result,
mixed $name,
mixed $block
): mixed {
if ($block instanceof IdentityInterface) {
$this->cacheableQuery->addCacheTags($block->getIdentities());
}
return $result;
}
}
@@ -29,4 +29,7 @@
<type name="Magento\Integration\Api\UserTokenRevokerInterface">
<plugin name="set-guest-after-revoke" type="Magento\GraphQlCache\Model\Plugin\Auth\TokenRevoker"/>
</type>
<type name="Magento\Framework\View\LayoutInterface">
<plugin name="add_block_cache_tags_to_query_cache" type="Magento\GraphQlCache\Model\Plugin\View\Layout"/>
</type>
</config>
@@ -19,7 +19,8 @@ class PriceMapper implements MapperInterface
{
$templates[] = [
'price_mapping' => [
'match' => 'price_*',
"match_pattern" => "regex",
'match' => 'price_\\d+_\\d+',
'match_mapping_type' => 'string',
'mapping' => [
'type' => 'double',
@@ -147,7 +147,8 @@ class OpenSearchTest extends TestCase
'dynamic_templates' => [
[
'price_mapping' => [
'match' => 'price_*',
"match_pattern" => "regex",
'match' => 'price_\\d+_\\d+',
'match_mapping_type' => 'string',
'mapping' => [
'type' => 'double',
+26 -8
View File
@@ -12,6 +12,7 @@ use Magento\Directory\Model\AllowedCountries;
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 @@ class Quote extends AbstractExtensibleModel implements \Magento\Quote\Api\Data\C
* 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 @@ class Quote extends AbstractExtensibleModel implements \Magento\Quote\Api\Data\C
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 @@ class Quote extends AbstractExtensibleModel implements \Magento\Quote\Api\Data\C
// 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 @@ class Quote extends AbstractExtensibleModel implements \Magento\Quote\Api\Data\C
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 @@ class Quote extends AbstractExtensibleModel implements \Magento\Quote\Api\Data\C
* 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 @@ class Quote extends AbstractExtensibleModel implements \Magento\Quote\Api\Data\C
$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 @@ class Quote extends AbstractExtensibleModel implements \Magento\Quote\Api\Data\C
* @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(
@@ -30,6 +30,7 @@ use Magento\Framework\DataObject;
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 @@ class QuoteTest extends TestCase
}
/**
* @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 @@ class QuoteTest extends TestCase
->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
*/
@@ -94,16 +94,6 @@ abstract class Reorder extends Action\Action implements HttpPostActionInterface
// 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');
}
}
@@ -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 @@ class Reorder
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 @@ class Reorder
$this->orderInfoBuyRequestGetter = $orderInfoBuyRequestGetter;
$this->storeManager = $storeManager
?: ObjectManager::getInstance()->get(StoreManagerInterface::class);
$this->addToCartInvalidProduct = $addToCartInvalidProduct;
}
/**
@@ -276,6 +286,7 @@ class Reorder
$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()));
@@ -22,4 +22,9 @@
</argument>
</arguments>
</type>
<type name="Magento\Sales\Model\Reorder\Reorder">
<arguments>
<argument name="addToCartInvalidProduct" xsi:type="boolean">true</argument>
</arguments>
</type>
</config>
@@ -305,18 +305,19 @@ class Rule extends AbstractResource
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);
}
@@ -14,13 +14,15 @@ use Magento\Catalog\Api\Data\ProductInterface;
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 @@ QUERY;
'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 @@ QUERY;
$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 @@ QUERY;
$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 @@ QUERY;
}
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']);
}
#[
@@ -7,9 +7,19 @@ declare(strict_types=1);
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 @@ QUERY;
);
}
#[
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 <<<QUERY
{
categories(filters: {ids: {in: ["$categoryId"]}}) {
items{
description
}
}
}
QUERY;
}
/**
* Get Product query
*
@@ -0,0 +1,62 @@
<?php
/************************************************************************
*
* Copyright 2023 Adobe
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe and its suppliers, if any. The intellectual
* and technical concepts contained herein are proprietary to Adobe
* and its suppliers and are protected by all applicable intellectual
* property laws, including trade secret and copyright laws.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe.
* ***********************************************************************
*/
declare(strict_types=1);
namespace Magento\Catalog;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\DB\Adapter\AdapterInterface;
use Monolog\Test\TestCase;
class DbSchemaTest extends TestCase
{
/**
* @param string $tableName
* @param string $indexName
* @param array $columns
* @param string $indexType
* @return void
* @dataProvider indexDataProvider
*/
public function testIndex(
string $tableName,
string $indexName,
array $columns,
string $indexType = AdapterInterface::INDEX_TYPE_INDEX,
): void {
$connection = ObjectManager::getInstance()->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']
]
];
}
}
@@ -108,13 +108,7 @@ class ReorderTest extends AbstractController
$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'));
}
/**
@@ -117,7 +117,7 @@ class ReorderWithDifferentStoreTest extends AbstractController
$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());
}
}
@@ -72,8 +72,6 @@ class RuleTest extends \PHPUnit\Framework\TestCase
public function testGetActiveAttributes()
{
$rule = $this->fixtures->get('rule1');
$items = $this->resource->getActiveAttributes();
$this->assertEquals([], $items);
$rule->setIsActive(1);
$rule->save();
$items = $this->resource->getActiveAttributes();
@@ -0,0 +1,174 @@
<?php
/************************************************************************
*
* Copyright 2023 Adobe
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe and its suppliers, if any. The intellectual
* and technical concepts contained herein are proprietary to Adobe
* and its suppliers and are protected by all applicable intellectual
* property laws, including trade secret and copyright laws.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe.
* ************************************************************************
*/
declare(strict_types=1);
namespace Magento\Framework\Mview\Test\Unit\View;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\DB\Adapter\AdapterInterface;
use Magento\Framework\DB\Ddl\Table;
use Magento\Framework\DB\Query\Generator;
use Magento\Framework\DB\Select;
use Magento\Framework\Mview\View\ChangelogInterface;
use Magento\Framework\Mview\View\ChangelogBatchWalker;
use Magento\Framework\Mview\View\ChangelogBatchWalker\IdsContext;
use Magento\Framework\Mview\View\ChangelogBatchWalker\IdsSelectBuilderInterface;
use Magento\Framework\Mview\View\ChangelogBatchWalker\IdsTableBuilderInterface;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
/**
* Test Coverage for Changelog View.
*
* @see \Magento\Framework\Mview\View\Changelog
*/
class ChangelogBatchWalkerTest extends TestCase
{
/**
* @var ChangelogBatchWalker
*/
protected $model;
/**
* @var ResourceConnection|MockObject
*/
private $resourceConnection;
/**
* @var Generator|MockObject
*/
private $generator;
/**
* @var IdsTableBuilderInterface|MockObject
*/
private $idsTableBuilder;
/**
* @var IdsSelectBuilderInterface|MockObject
*/
private $idsSelectBuilder;
/**
* @var IdsContext|MockObject
*/
private $idsContext;
/**
* @var ChangelogInterface
*/
private $changeLog;
/**
* @var AdapterInterface|MockObject
*/
private $connection;
/**
* @var Table|MockObject
*/
private $table;
/**
* @var Select|MockObject
*/
private $select;
protected function setUp(): void
{
$this->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');
}
}
}
@@ -24,30 +24,34 @@ use Magento\Framework\Phrase;
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 @@ class ChangelogBatchWalker implements ChangelogBatchWalkerInterface
$idsTable = $this->idsTableBuilder->build($changelog);
try {
$connection->createTemporaryTable($idsTable);
$connection->createTable($idsTable);
$columns = $this->getIdsColumns($idsTable);
@@ -122,7 +126,7 @@ class ChangelogBatchWalker implements ChangelogBatchWalkerInterface
yield $ids;
}
} finally {
$connection->dropTemporaryTable($idsTable->getName());
$connection->dropTable($idsTable->getName());
}
}
@@ -50,7 +50,6 @@ class IdsTableBuilder implements IdsTableBuilderInterface
['unsigned' => true, 'nullable' => false],
'Entity ID'
);
$table->setOption('type', 'memory');
$table->addIndex(
self::INDEX_NAME_UNIQUE,
[
@@ -0,0 +1,87 @@
<?php
/************************************************************************
*
* Copyright 2023 Adobe
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe and its suppliers, if any. The intellectual
* and technical concepts contained herein are proprietary to Adobe
* and its suppliers and are protected by all applicable intellectual
* property laws, including trade secret and copyright laws.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe.
* ************************************************************************
*/
declare(strict_types=1);
namespace Magento\Framework\Test\Unit\Mview\View\ChangelogBatchWalker;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\DB\Adapter\AdapterInterface;
use Magento\Framework\DB\Ddl\Table;
use Magento\Framework\Mview\View\ChangelogBatchWalker\IdsTableBuilder;
use Magento\Framework\Mview\View\ChangelogInterface;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
class IdsTableBuilderTest extends TestCase
{
/**
* @var ResourceConnection|MockObject
*/
private $resourceConnection;
/**
* @var ChangelogInterface|MockObject
*/
private $changeLog;
/**
* @var AdapterInterface|MockObject
*/
private $connection;
/**
* @var Table|MockObject
*/
private $table;
/**
* @var IdsTableBuilder
*/
private $model;
protected function setUp(): void
{
$this->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);
}
}