diff --git a/BundleProductDataExporter/Test/Integration/BundleProductTest.php b/BundleProductDataExporter/Test/Integration/BundleProductTest.php index 9acd66f6..27776946 100644 --- a/BundleProductDataExporter/Test/Integration/BundleProductTest.php +++ b/BundleProductDataExporter/Test/Integration/BundleProductTest.php @@ -17,6 +17,9 @@ */ class BundleProductTest extends AbstractProductTestHelper { + private const BUNDLE_SKU = 'bundle-product'; + private const DYNAMIC_BUNDLE_SKU = 'dynamic_bundle_product_with_special_price'; + /** * @var ArrayUtils */ @@ -47,7 +50,7 @@ protected function setUp() : void */ public function testBundleFixedProductOptions(array $bundleProductOptionsDataProvider) : void { - $extractedProduct = $this->getExtractedProduct('bundle-product', 'default'); + $extractedProduct = $this->getExtractedProduct(self::BUNDLE_SKU, 'default'); $this->assertNotEmpty($extractedProduct, 'Feed data must not be empty'); foreach ($bundleProductOptionsDataProvider as $key => $expectedData) { @@ -72,7 +75,7 @@ public function testBundleFixedProductOptions(array $bundleProductOptionsDataPro */ public function testBundleDynamicProductOptions(array $bundleProductOptionsDataProvider) : void { - $extractedProduct = $this->getExtractedProduct('dynamic_bundle_product_with_special_price', 'default'); + $extractedProduct = $this->getExtractedProduct(self::DYNAMIC_BUNDLE_SKU, 'default'); $this->assertNotEmpty($extractedProduct, 'Feed data must not be empty'); foreach ($bundleProductOptionsDataProvider as $key => $expectedData) { @@ -92,7 +95,7 @@ public function getBundleFixedProductOptionsDataProvider() : array 'bundleProduct' => [ 'item' => [ 'feedData' => [ - 'sku' => 'bundle-product', + 'sku' => self::BUNDLE_SKU, 'storeViewCode' => 'default', 'name' => 'Bundle Product', 'type' => 'bundle_fixed', @@ -134,7 +137,7 @@ public function getBundleDynamicProductOptionsDataProvider() : array 'bundleProduct' => [ 'item' => [ 'feedData' => [ - 'sku' => 'dynamic_bundle_product_with_special_price', + 'sku' => self::DYNAMIC_BUNDLE_SKU, 'storeViewCode' => 'default', 'name' => 'Bundle Product', 'type' => 'bundle', diff --git a/BundleProductDataExporter/Test/Integration/ExportBundleOptionWithParentTest.php b/BundleProductDataExporter/Test/Integration/ExportBundleOptionWithParentTest.php index aa15a38f..63fb1b24 100644 --- a/BundleProductDataExporter/Test/Integration/ExportBundleOptionWithParentTest.php +++ b/BundleProductDataExporter/Test/Integration/ExportBundleOptionWithParentTest.php @@ -7,18 +7,12 @@ namespace Magento\BundleProductDataExporter\Test\Integration; -use DateTime; -use DateTimeInterface; -use Magento\Catalog\Api\ProductRepositoryInterface; -use Magento\Framework\App\ResourceConnection; +use Magento\CatalogDataExporter\Test\Integration\AbstractProductTestHelper; use Magento\Framework\Exception\NoSuchEntityException; use Magento\DataExporter\Model\FeedInterface; use Magento\DataExporter\Model\FeedPool; -use Magento\Indexer\Model\Indexer; use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; -use RuntimeException; -use Throwable; use Zend_Db_Statement_Exception; /** @@ -29,45 +23,22 @@ * @SuppressWarnings(PHPMD.UnusedPrivateMethod) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class ExportBundleOptionWithParentTest extends TestCase +class ExportBundleOptionWithParentTest extends AbstractProductTestHelper { - private const PRODUCT_FEED_INDEXER = 'catalog_data_exporter_products'; - - /** - * @var Indexer - */ - private Indexer $indexer; - + private const SIMPLE_SKU = 'simple1'; /** * @var FeedInterface */ private FeedInterface $productsFeed; /** - * @var ProductRepositoryInterface + * @inheritDoc */ - private ProductRepositoryInterface $productRepository; - - /** - * @var ResourceConnection - */ - private ResourceConnection $resourceConnection; - - /** - * @param string|null $name - * @param array $data - * @param $dataName - */ - public function __construct( - ?string $name = null, - array $data = [], - $dataName = '' - ) { - parent::__construct($name, $data, $dataName); - $this->indexer = Bootstrap::getObjectManager()->create(Indexer::class); - $this->productRepository = Bootstrap::getObjectManager()->create(ProductRepositoryInterface::class); + protected function setUp() : void + { $this->productsFeed = Bootstrap::getObjectManager()->get(FeedPool::class)->getFeed('products'); - $this->resourceConnection = Bootstrap::getObjectManager()->create(ResourceConnection::class); + + parent::setUp(); } /** @@ -92,7 +63,7 @@ private function expectedBundleOptionsWithParentData(): array [ [ [ - 'sku' => 'simple1', + 'sku' => self::SIMPLE_SKU, 'type' => 'SIMPLE', 'parents' => [ 0 => ['sku' => 'bundle-product', 'productType' => 'bundle_fixed'], @@ -112,66 +83,18 @@ private function expectedBundleOptionsWithParentData(): array */ private function checkExpectedItemsAreExportedInFeed(array $expectedItems): void { - $ids = []; - foreach ($expectedItems as $expectedItem) { - $ids[] = $this->productRepository->get($expectedItem['sku'])->getId(); - } - $timestamp = new DateTime('Now - 1 second'); - $this->runIndexer($ids); - $actualProductsFeed = $this->productsFeed->getFeedSince($timestamp->format(DateTimeInterface::W3C)); + $extractedProduct = $this->getExtractedProduct(self::SIMPLE_SKU, 'default'); - self::assertNotEmpty($actualProductsFeed['feed'], 'Product Feed should not be empty'); + self::assertNotEmpty($extractedProduct, 'Product Feed should not be empty'); - foreach ($expectedItems as $index => $product) { - if (!isset($actualProductsFeed['feed'][$index])) { - self::fail("Cannot find product feed"); - } - - self::assertEquals( - $product['sku'], - $actualProductsFeed['feed'][$index]['sku'], - "Sku is not equal for index {$index}" - ); + foreach ($expectedItems as $product) { + self::assertEquals($product['sku'], $extractedProduct['sku']); self::assertEqualsCanonicalizing( $product['parents'], - $actualProductsFeed['feed'][$index]['parents'], - "Parents is not equal" + $extractedProduct['feedData']['parents'], + "Expected Parents are not equal to Actual" ); } } - - /** - * Run the indexer to extract product data - * @param $ids - * @return void - */ - private function runIndexer($ids): void - { - try { - $this->indexer->load(self::PRODUCT_FEED_INDEXER); - $this->indexer->reindexList($ids); - } catch (Throwable) { - throw new RuntimeException('Could not reindex product data'); - } - } - - /** - * @return void - */ - protected function tearDown(): void - { - parent::tearDown(); - $this->truncateIndexTable(); - } - - /** - * Truncates index table - */ - private function truncateIndexTable(): void - { - $connection = $this->resourceConnection->getConnection(); - $feedTable = $this->resourceConnection->getTableName('catalog_data_exporter_products'); - $connection->truncateTable($feedTable); - } } diff --git a/CatalogDataExporter/Test/Integration/AbstractProductTestHelper.php b/CatalogDataExporter/Test/Integration/AbstractProductTestHelper.php index 63c26dcb..16a79e17 100644 --- a/CatalogDataExporter/Test/Integration/AbstractProductTestHelper.php +++ b/CatalogDataExporter/Test/Integration/AbstractProductTestHelper.php @@ -14,6 +14,7 @@ use Magento\Catalog\Helper\Product as ProductHelper; use Magento\Catalog\Model\Product\Attribute\Source\Status; use Magento\Catalog\Model\Product\Visibility; +use Magento\Framework\Api\SearchCriteriaInterface; use Magento\Framework\App\ResourceConnection; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; @@ -126,18 +127,9 @@ protected function setUp(): void $this->taxClassSource = Bootstrap::getObjectManager()->create(TaxClassSource::class); $this->jsonSerializer = Bootstrap::getObjectManager()->create(Json::class); - } - /** - * Run the indexer to extract product data - * - * @param array $ids - * @return void - */ - protected function runIndexer(array $ids = []) : void - { $this->indexer->load(self::CATALOG_DATA_EXPORTER); - $this->indexer->reindexList($ids); + $this->reindexProductDataExporter(); } /** @@ -146,10 +138,10 @@ protected function runIndexer(array $ids = []) : void * @param string $sku * @param string $storeViewCode * @return array - * @throws \Zend_Db_Statement_Exception */ protected function getExtractedProduct(string $sku, string $storeViewCode) : array { + // Select data from exporter table $query = $this->connection->select() ->from(['ex' => $this->resource->getTableName(self::CATALOG_DATA_EXPORTER)]) ->where('ex.sku = ?', $sku) @@ -163,7 +155,37 @@ protected function getExtractedProduct(string $sku, string $storeViewCode) : arr $data[$row['sku']]['is_deleted'] = $row['is_deleted']; $data[$row['sku']]['feedData'] = $this->jsonSerializer->unserialize($row['feed_data']); } - return $data[$sku]; + + return !empty($data[$sku]) ? $data[$sku] : $data; + } + + /** + * Run partial exported indexer + * + * @param array $ids + * @return void + */ + protected function emulatePartialReindexBehavior(array $ids = []) : void + { + $this->indexer->reindexList($ids); + } + + /** + * Reindex all the product data exporter table for existing products + * + * @return void + */ + private function reindexProductDataExporter() : void + { + $searchCriteria = Bootstrap::getObjectManager()->create(SearchCriteriaInterface::class); + + $productIds = array_map(function ($product) { + return $product->getId(); + }, $this->productRepository->getList($searchCriteria)->getItems()); + + if (!empty($productIds)) { + $this->indexer->reindexList($productIds); + } } /** @@ -232,6 +254,8 @@ protected function validateCategoryData(ProductInterface $product, array $extrac */ protected function validateBaseProductData(ProductInterface $product, array $extract, string $storeViewCode) : void { + $this->assertNotEmpty($extract, "Exported Product Data is empty"); + $storeViewId = $this->storeRepositoryInterface->get($storeViewCode)->getCode(); $storeView = $this->storeManager->getStore($storeViewId); $websiteCode = $this->websiteRepositoryInterface->getById($storeView->getWebsiteId())->getCode(); @@ -498,4 +522,34 @@ protected function emulateCustomersBehaviorAfterDeleteAction(): void // \Magento\DataExporter\Model\Query\RemovedEntitiesByModifiedAtQuery::getQuery sleep(1); } + + /** + * @param string $sku + * @return int|null + * @throws NoSuchEntityException + */ + public function getProductId(string $sku): ?int + { + $product = $this->productRepository->get($sku); + return (int)$product->getId(); + } + + /** + * @return void + */ + protected function tearDown(): void + { + parent::tearDown(); + $this->truncateProductDataExporterIndexTable(); + } + + /** + * Truncates index table + */ + private function truncateProductDataExporterIndexTable(): void + { + $connection = $this->resource->getConnection(); + $feedTable = $this->resource->getTableName(self::CATALOG_DATA_EXPORTER); + $connection->truncateTable($feedTable); + } } diff --git a/CatalogDataExporter/Test/Integration/Category/AbstractCategoryTest.php b/CatalogDataExporter/Test/Integration/Category/AbstractCategoryTest.php index 0458b9e3..89072910 100644 --- a/CatalogDataExporter/Test/Integration/Category/AbstractCategoryTest.php +++ b/CatalogDataExporter/Test/Integration/Category/AbstractCategoryTest.php @@ -78,18 +78,9 @@ protected function setUp() : void $this->categoryRepository = Bootstrap::getObjectManager()->create(CategoryRepositoryInterface::class); $this->storeManager = Bootstrap::getObjectManager()->create(StoreManagerInterface::class); $this->categoryFeed = Bootstrap::getObjectManager()->get(FeedPool::class)->getFeed('categories'); - } - /** - * Run the indexer to extract categories data - * - * @param array $ids - * @return void - */ - protected function runIndexer(array $ids) : void - { $this->indexer->load(self::CATEGORY_FEED_INDEXER); - $this->indexer->reindexList($ids); + $this->reindexCategoryDataExporterTable(); } /** @@ -101,6 +92,8 @@ protected function runIndexer(array $ids) : void */ protected function assertBaseCategoryData(CategoryInterface $category, array $extract, StoreInterface $store) : void { + $this->assertNotEmpty($extract, "Exported Category Data is empty"); + $storeCode = $this->storeManager->getGroup($store->getStoreGroupId())->getCode(); $websiteCode = $this->storeManager->getWebsite($store->getWebsiteId())->getCode(); $this->assertEquals($store->getCode(), $extract['storeViewCode']); @@ -136,4 +129,44 @@ public function getCategoryById(int $categoryId, string $storeViewCode) : array } return []; } + + /** + * Run the category exporter indexer + * + * @param array $ids + * @return void + */ + protected function emulatePartialReindexBehavior(array $ids = []) : void + { + $this->indexer->reindexList($ids); + } + + /** + * Reindex the full category data exporter table + * + * @return void + */ + private function reindexCategoryDataExporterTable() : void + { + $this->indexer->reindexAll(); + } + + /** + * @return void + */ + protected function tearDown(): void + { + parent::tearDown(); + $this->truncateCategoryDataExporterIndexTable(); + } + + /** + * Truncates index table + */ + private function truncateCategoryDataExporterIndexTable(): void + { + $connection = $this->resource->getConnection(); + $feedTable = $this->resource->getTableName(self::CATEGORY_FEED_INDEXER); + $connection->truncateTable($feedTable); + } } diff --git a/CatalogDataExporter/Test/Integration/Category/CategoryRemovalTest.php b/CatalogDataExporter/Test/Integration/Category/CategoryRemovalTest.php index e95d09c3..e5a56444 100644 --- a/CatalogDataExporter/Test/Integration/Category/CategoryRemovalTest.php +++ b/CatalogDataExporter/Test/Integration/Category/CategoryRemovalTest.php @@ -28,11 +28,16 @@ class CategoryRemovalTest extends AbstractCategoryTest public function testCategoryRemoval() : void { $categoryId = 600; - $extractedCategory = $this->getCategoryById(600, 'default'); + + $extractedCategory = $this->getCategoryById($categoryId, 'default'); + $this->assertNotEmpty($extractedCategory, "Exported Category Data is empty"); $this->assertEquals(false, $extractedCategory['deleted']); + $this->deleteCategory($categoryId); - $extractedCategory = $this->getCategoryById(600, 'default'); - $this->assertTrue($extractedCategory['deleted']); + $this->emulatePartialReindexBehavior([$categoryId]); + + $extractedCategory = $this->getCategoryById($categoryId, 'default'); + $this->assertTrue($extractedCategory['deleted'], "Category is not set as deleted"); } /** diff --git a/CatalogDataExporter/Test/Integration/Category/CategoryTest.php b/CatalogDataExporter/Test/Integration/Category/CategoryTest.php index 58561eaa..9da5749c 100644 --- a/CatalogDataExporter/Test/Integration/Category/CategoryTest.php +++ b/CatalogDataExporter/Test/Integration/Category/CategoryTest.php @@ -32,19 +32,24 @@ public function testCategoriesInDifferentStoreViews() : void $storeCustomOne = $this->storeManager->getStore('custom_store_view_one'); $storeCustomTwo = $this->storeManager->getStore('custom_store_view_two'); + $categoryIdsInCustomStore = [400, 401, 402]; + $categoryIdsInDefaultStore = [500, 501, 502]; + try { - foreach ([400, 401, 402] as $categoryId) { + foreach ($categoryIdsInCustomStore as $categoryId) { foreach ([$storeCustomOne, $storeCustomTwo] as $store) { $this->storeManager->setCurrentStore($store); $category = $this->categoryRepository->get($categoryId, $store->getId()); + $this->emulatePartialReindexBehavior([$categoryId]); $extractedCategoryData = $this->getCategoryById($categoryId, $store->getCode()); $this->assertBaseCategoryData($category, $extractedCategoryData, $store); } } - foreach ([500, 501, 502] as $categoryId) { + foreach ($categoryIdsInDefaultStore as $categoryId) { $this->storeManager->setCurrentStore($storeDefault); $category = $this->categoryRepository->get($categoryId, $storeDefault->getId()); + $this->emulatePartialReindexBehavior([$categoryId]); $extractedCategoryData = $this->getCategoryById($categoryId, $storeDefault->getCode()); $this->assertBaseCategoryData($category, $extractedCategoryData, $storeDefault); } diff --git a/CatalogDataExporter/Test/Integration/GroupedProductsTest.php b/CatalogDataExporter/Test/Integration/GroupedProductsTest.php index b98a8e12..e971fdfc 100644 --- a/CatalogDataExporter/Test/Integration/GroupedProductsTest.php +++ b/CatalogDataExporter/Test/Integration/GroupedProductsTest.php @@ -17,12 +17,13 @@ */ class GroupedProductsTest extends AbstractProductTestHelper { + private const GROUPED_PRODUCT_SKU = 'grouped-product'; + /** * @var ArrayUtils */ private $arrayUtils; - /** * @inheritDoc */ @@ -48,7 +49,7 @@ protected function setUp() : void */ public function testGroupedProductOptions(array $groupedProductOptionsDataProvider) : void { - $extractedProduct = $this->getExtractedProduct('grouped-product', 'default'); + $extractedProduct = $this->getExtractedProduct(self::GROUPED_PRODUCT_SKU, 'default'); $this->assertNotEmpty($extractedProduct, 'Feed data must not be empty'); foreach ($groupedProductOptionsDataProvider as $key => $expectedData) { @@ -76,7 +77,7 @@ public function testGroupedProductOptionsInMultipleWebsites(array $groupedProduc $storeViews = ['fixture_second_store','fixture_third_store']; foreach ($storeViews as $store) { - $extractedProduct = $this->getExtractedProduct('grouped-product', $store); + $extractedProduct = $this->getExtractedProduct(self::GROUPED_PRODUCT_SKU, $store); $this->assertNotEmpty($extractedProduct, 'Feed data must not be empty'); // Assert values are equal for fixture_second_store @@ -99,7 +100,7 @@ public function getGroupedProductOptionsDataProvider() : array 'groupedProduct' => [ 'item' => [ 'feedData' => [ - 'sku' => 'grouped-product', + 'sku' => self::GROUPED_PRODUCT_SKU, 'storeViewCode' => 'default', 'name' => 'Grouped Product', 'type' => 'grouped', diff --git a/CatalogDataExporter/Test/Integration/ProductRemovalTest.php b/CatalogDataExporter/Test/Integration/ProductRemovalTest.php index eccea4d0..0e58a962 100644 --- a/CatalogDataExporter/Test/Integration/ProductRemovalTest.php +++ b/CatalogDataExporter/Test/Integration/ProductRemovalTest.php @@ -67,7 +67,7 @@ protected function deleteProduct(string $sku) : void if ($productId) { $this->productRepository->delete($product); $this->emulateCustomersBehaviorAfterDeleteAction(); - $this->runIndexer([$productId]); + $this->emulatePartialReindexBehavior([$productId]); } } catch (\Exception $e) { //Nothing to delete diff --git a/CatalogDataExporter/Test/Integration/ResubmitFailedFeedTest.php b/CatalogDataExporter/Test/Integration/ResubmitFailedFeedTest.php index bd950de2..32d6e678 100644 --- a/CatalogDataExporter/Test/Integration/ResubmitFailedFeedTest.php +++ b/CatalogDataExporter/Test/Integration/ResubmitFailedFeedTest.php @@ -23,16 +23,23 @@ /** * Test class to check that only feeds with "resyncable" statuses would be re-submitted */ -class ResubmitFailedFeedTest extends TestCase +class ResubmitFailedFeedTest extends AbstractProductTestHelper { private const EXPORT_SUCCESS_STATUS = 200; + /** + * @var FeedInterface + */ private FeedInterface $productFeed; + /** + * @var SubmitFeedInterface|ProductSubmitFeed|mixed + */ private SubmitFeedInterface $submitFeed; - private ProductRepositoryInterface $productRepository; - + /** + * @var ResourceConnection|mixed + */ private ResourceConnection $resourceConnection; public static function setUpBeforeClass(): void @@ -98,13 +105,15 @@ private function updateFeeds(array $expectedProducts): void { $queryData = []; foreach ($expectedProducts as $productData) { + $productId = $this->productRepository->get($productData['sku'])->getId(); $queryData[] = [ - 'id' => $this->productRepository->get($productData['sku'])->getId(), + 'id' => $productId, 'sku' => $productData['sku'], 'store_view_code' => $productData['store_view_code'], 'status' => $productData['status'] ]; } + $connection = $this->resourceConnection->getConnection(); $connection->insertOnDuplicate( $connection->getTableName($this->productFeed->getFeedMetadata()->getFeedTableName()), diff --git a/CatalogDataExporter/Test/Integration/SimpleProductsWebsiteUnassignTest.php b/CatalogDataExporter/Test/Integration/SimpleProductsWebsiteUnassignTest.php index 03e9260d..f359b475 100644 --- a/CatalogDataExporter/Test/Integration/SimpleProductsWebsiteUnassignTest.php +++ b/CatalogDataExporter/Test/Integration/SimpleProductsWebsiteUnassignTest.php @@ -33,7 +33,6 @@ protected function setUp(): void $this->objectManager = Bootstrap::getObjectManager(); $this->action = $this->objectManager->create(Action::class); parent::setUp(); - $this->emulateCustomersBehaviorAfterDeleteAction(); } /** @@ -58,15 +57,24 @@ public function testSimpleProductsOnSave(array $testData) : void $websiteRepository = $this->objectManager->get(WebsiteRepositoryInterface::class); foreach ($testData as $productData) { $product = $this->productRepository->get($productData['sku']); + $websiteIds = []; foreach ($productData['websites'] as $websiteCode) { $websiteIds[] = $websiteRepository->get($websiteCode)->getId(); } $product->setWebsiteIds($websiteIds); $this->productRepository->save($product); + + $this->emulateCustomersBehaviorAfterDeleteAction(); + $this->emulatePartialReindexBehavior([$product->getId()]); + foreach ($productData['expected_data'] as $storeViewCode => $isDeleted) { $extractedProduct = $this->getExtractedProduct($productData['sku'], $storeViewCode); - self::assertEquals($isDeleted, $extractedProduct['is_deleted']); + self::assertEquals( + $isDeleted, + $extractedProduct['is_deleted'], + "Product {$productData['sku']} is not deleted in store view {$storeViewCode}" + ); } } } @@ -103,10 +111,17 @@ public function testSimpleProductsOnBulkUpdate(array $skus, array $websitesToUna $this->action->updateWebsites($productIds, $websiteIds, 'remove'); + $this->emulateCustomersBehaviorAfterDeleteAction(); + $this->emulatePartialReindexBehavior($productIds); + foreach ($expectedData as $storeViewCode => $isDeleted) { foreach ($skus as $sku) { $extractedProduct = $this->getExtractedProduct($sku, $storeViewCode); - self::assertEquals($isDeleted, $extractedProduct['is_deleted']); + self::assertEquals( + $isDeleted, + $extractedProduct['is_deleted'], + "Product {$sku} is not deleted in store view {$storeViewCode}" + ); } } } diff --git a/CatalogInventoryDataExporter/Test/Integration/ProductBuyableTest.php b/CatalogInventoryDataExporter/Test/Integration/ProductBuyableTest.php index 9bfc6002..0134649c 100644 --- a/CatalogInventoryDataExporter/Test/Integration/ProductBuyableTest.php +++ b/CatalogInventoryDataExporter/Test/Integration/ProductBuyableTest.php @@ -7,11 +7,15 @@ namespace Magento\CatalogInventoryDataExporter\Test\Integration; +use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\Product\Attribute\Source\Status; use Magento\CatalogDataExporter\Test\Integration\AbstractProductTestHelper; use Magento\CatalogInventory\Model\Stock\Item; +use Magento\Framework\Exception\CouldNotSaveException; +use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Exception\StateException; use Magento\TestFramework\Helper\Bootstrap; /** @@ -26,8 +30,8 @@ class ProductBuyableTest extends AbstractProductTestHelper /** * Test constants */ - const SKU = 'simple7'; - const STORE_VIEW_CODE = 'default'; + private const SKU = 'simple7'; + private const STORE_VIEW_CODE = 'default'; /** * Validate buyable status of out of stock product @@ -40,7 +44,12 @@ class ProductBuyableTest extends AbstractProductTestHelper */ public function testOutOfStockProduct() : void { - $this->setIsInStock(false); + $product = $this->productRepository->get(self::SKU); + $productId = $product->getId(); + + $this->setIsInStock((int)$productId, false); + + $this->emulatePartialReindexBehavior([$productId]); $this->validateProductBuyable($this->getExtractedProduct(self::SKU, self::STORE_VIEW_CODE)); } @@ -54,7 +63,12 @@ public function testOutOfStockProduct() : void */ public function testDisabledProduct() : void { - $this->disableProduct(); + $product = $this->productRepository->get(self::SKU); + $productId = $product->getId(); + + $this->disableProduct($product); + + $this->emulatePartialReindexBehavior([$productId]); $this->validateDisabledProduct($this->getExtractedProduct(self::SKU, self::STORE_VIEW_CODE)); } @@ -68,24 +82,26 @@ public function testDisabledProduct() : void */ public function testEnabledProduct() : void { - $this->enableProduct(); - $this->setIsInStock(true); + $product = $this->productRepository->get(self::SKU); + $productId = $product->getId(); + + $this->enableProduct($product); + $this->setIsInStock((int)$productId, true); + + $this->emulatePartialReindexBehavior([$productId]); $this->validateEnabledProduct($this->getExtractedProduct(self::SKU, self::STORE_VIEW_CODE)); } /** * Set is in stock value of product * + * @param int $productId * @param bool $isInStock * @return void - * @throws NoSuchEntityException * @throws \Exception */ - protected function setIsInStock(bool $isInStock) : void + protected function setIsInStock(int $productId, bool $isInStock) : void { - $product = $this->productRepository->get(self::SKU); - $productId = $product->getId(); - /** @var \Magento\CatalogInventory\Model\Stock\Item $stockItem */ $stockItem = Bootstrap::getObjectManager()->create(Item::class); $stockItem->load($productId, 'product_id'); @@ -96,13 +112,16 @@ protected function setIsInStock(bool $isInStock) : void /** * Set product status to disabled * + * @param ProductInterface $product + * * @return void - * @throws NoSuchEntityException - * @throws \Exception + * + * @throws CouldNotSaveException + * @throws InputException + * @throws StateException */ - protected function disableProduct() : void + protected function disableProduct(ProductInterface $product) : void { - $product = $this->productRepository->get(self::SKU, true); $product->setStatus(Status::STATUS_DISABLED); $this->productRepository->save($product); } @@ -110,13 +129,16 @@ protected function disableProduct() : void /** * Set product status to enabled * + * @param ProductInterface $product + * * @return void - * @throws NoSuchEntityException - * @throws \Exception + * + * @throws CouldNotSaveException + * @throws InputException + * @throws StateException */ - protected function enableProduct() : void + protected function enableProduct(ProductInterface $product) : void { - $product = $this->productRepository->get(self::SKU, true); $product->setStatus(Status::STATUS_ENABLED); $this->productRepository->save($product); } diff --git a/CatalogInventoryDataExporter/Test/Integration/ProductInStockTest.php b/CatalogInventoryDataExporter/Test/Integration/ProductInStockTest.php index dad08358..bd2e516e 100644 --- a/CatalogInventoryDataExporter/Test/Integration/ProductInStockTest.php +++ b/CatalogInventoryDataExporter/Test/Integration/ProductInStockTest.php @@ -33,8 +33,11 @@ public function testProductInStock() : void $sku = 'simple5'; $storeViewCode = 'default'; - $this->changeInStockStatus($sku); + $productId = $this->getProductId($sku); + $this->changeInStockStatus($productId); + + $this->emulatePartialReindexBehavior([$productId]); $extractedProduct = $this->getExtractedProduct($sku, $storeViewCode); $this->validateProductInStock($extractedProduct); } @@ -42,16 +45,12 @@ public function testProductInStock() : void /** * Change inStock status of product * - * @param string $sku + * @param int $productId * @return void - * @throws NoSuchEntityException * @throws \Exception */ - protected function changeInStockStatus(string $sku) : void + protected function changeInStockStatus(int $productId) : void { - $product = $this->productRepository->get($sku); - $productId = $product->getId(); - /** @var \Magento\CatalogInventory\Model\Stock\Item $stockItem */ $stockItem = Bootstrap::getObjectManager()->create(Item::class); $stockItem->load($productId, 'product_id'); diff --git a/CatalogInventoryDataExporter/Test/Integration/ProductLowStockTest.php b/CatalogInventoryDataExporter/Test/Integration/ProductLowStockTest.php index 597e1c4a..533f18a4 100644 --- a/CatalogInventoryDataExporter/Test/Integration/ProductLowStockTest.php +++ b/CatalogInventoryDataExporter/Test/Integration/ProductLowStockTest.php @@ -33,8 +33,11 @@ public function testProductLowStock() : void $sku = 'simple6'; $storeViewCode = 'default'; - $this->changeLowStockStatus($sku); + $productId = $this->getProductId($sku); + $this->changeLowStockStatus($productId); + + $this->emulatePartialReindexBehavior([$productId]); $extractedProduct = $this->getExtractedProduct($sku, $storeViewCode); $this->validateProductLowStock($extractedProduct); } @@ -42,16 +45,12 @@ public function testProductLowStock() : void /** * Change lowStock status of product * - * @param string $sku + * @param int $productId * @return void - * @throws NoSuchEntityException * @throws \Exception */ - protected function changeLowStockStatus(string $sku) : void + protected function changeLowStockStatus(int $productId) : void { - $product = $this->productRepository->get($sku); - $productId = $product->getId(); - /** @var \Magento\CatalogInventory\Model\Stock\Item $stockItem */ $stockItem = Bootstrap::getObjectManager()->create(Item::class); $stockItem->load($productId, 'product_id'); diff --git a/CatalogUrlRewriteDataExporter/Test/Integration/ProductUrlsTest.php b/CatalogUrlRewriteDataExporter/Test/Integration/ProductUrlsTest.php index d576cb08..72757df6 100644 --- a/CatalogUrlRewriteDataExporter/Test/Integration/ProductUrlsTest.php +++ b/CatalogUrlRewriteDataExporter/Test/Integration/ProductUrlsTest.php @@ -96,8 +96,8 @@ public function testGetTechUrlIfUrlRewriteEmpty() : void $storeViewCode = 'default'; $UrlRewrite = Bootstrap::getObjectManager()->get(DbStorage::class); $UrlRewrite->deleteByData(['entity_id'=>10]); - $this->runIndexer([10]); $product = $this->productRepository->get($sku); + $this->emulatePartialReindexBehavior([$product->getId()]); $extractedProduct = $this->getExtractedProduct($sku, $storeViewCode); $this->assertEquals(strtok($product->getUrlInStore(), '?'), $extractedProduct['feedData']['url']); } diff --git a/ConfigurableProductDataExporter/Model/Provider/Product/Options.php b/ConfigurableProductDataExporter/Model/Provider/Product/Options.php index b1a7a53a..be0acd74 100755 --- a/ConfigurableProductDataExporter/Model/Provider/Product/Options.php +++ b/ConfigurableProductDataExporter/Model/Provider/Product/Options.php @@ -7,7 +7,6 @@ namespace Magento\ConfigurableProductDataExporter\Model\Provider\Product; -use Exception; use Magento\CatalogDataExporter\Model\Provider\Product\OptionProviderInterface; use Magento\ConfigurableProduct\Model\Product\Type\Configurable; use Magento\ConfigurableProductDataExporter\Model\Query\ProductOptionQuery; @@ -91,7 +90,7 @@ private function getTable(string $reference) } /** - * Returns possible attribute valies for a product + * Returns possible attribute values for a product * * @param int $entityId * @param int $attributeId @@ -225,6 +224,8 @@ private function getOptionKey($row): string /** * @inheritDoc + * + * @throws UnableRetrieveData */ public function get(array $values): array { @@ -246,29 +247,12 @@ public function get(array $values): array try { $options = []; $optionValuesData = $this->getOptionValuesData($queryArguments); - $select = $this->productOptionQuery->getQuery($queryArguments); - $cursor = $this->resourceConnection->getConnection()->query($select); - while ($row = $cursor->fetch()) { - if (!isset($temp[$row['productId'] . '-' . $row['attribute_id']])) { - $temp[$row['productId'] . '-' . $row['attribute_id']] = - $this->getPossibleAttributeValues($row['productId'], $row['attribute_id']); - } - $filter = $temp[$row['productId'] . '-' . $row['attribute_id']]; - - $key = $this->getOptionKey($row); - $options[$key] = $options[$key] ?? $this->formatOptionsRow($row); - - if (isset($optionValuesData[$row['attribute_id']][$row['storeViewCode']])) { - $options[$key]['optionsV2']['values'] = $this->getAssignedAttributeValues( - $optionValuesData[$row['attribute_id']][$row['storeViewCode']], - $filter - ); - } + $options = $this->getOptions($row, $temp, $options, $optionValuesData); } - } catch (Exception $exception) { + } catch (\Throwable $exception) { $this->logger->error($exception->getMessage(), ['exception' => $exception]); throw new UnableRetrieveData('Unable to retrieve configurable product options data'); } @@ -288,4 +272,46 @@ private function getAssignedAttributeValues(array $attributeValuesList, array $a return !empty($assignedAttributeValues) ? \array_values($assignedAttributeValues) : []; } + + /** + * Get Options + * + * @param mixed $row + * @param array $temp + * @param array $options + * @param array $optionValuesData + * @return array + */ + private function getOptions(mixed $row, array $temp, array $options, array $optionValuesData): array + { + try { + $key = $row['productId'] . '-' . $row['attribute_id']; + if (!isset($temp[$key])) { + $temp[$key] = $this->getPossibleAttributeValues($row['productId'], $row['attribute_id']); + } + $filter = $temp[$key]; + + $key = $this->getOptionKey($row); + $options[$key] = $options[$key] ?? $this->formatOptionsRow($row); + + if (isset($optionValuesData[$row['attribute_id']][$row['storeViewCode']])) { + $options[$key]['optionsV2']['values'] = $this->getAssignedAttributeValues( + $optionValuesData[$row['attribute_id']][$row['storeViewCode']], + $filter + ); + } + } catch (\Throwable $exception) { + $this->logger->error( + sprintf( + 'Unable to retrieve configurable product options data + (productId:%s, attributeId:%s, storeViewCode:%s)', + $row['productId'], + $row['attribute_id'], + $row['storeViewCode'] + ), + ['exception' => $exception] + ); + } + return $options; + } } diff --git a/ConfigurableProductDataExporter/Test/Integration/ConfigurableProductsTest.php b/ConfigurableProductDataExporter/Test/Integration/ConfigurableProductsTest.php index 71fddb56..d7020446 100755 --- a/ConfigurableProductDataExporter/Test/Integration/ConfigurableProductsTest.php +++ b/ConfigurableProductDataExporter/Test/Integration/ConfigurableProductsTest.php @@ -138,8 +138,6 @@ public function testConfigurableProductsWithOutOfStockChilds(array $outOfStockSk */ public function testParentProducts() : void { - $this->runIndexer([40, 50, 60, 70]); - $skus = ['simple_option_50', 'simple_option_60', 'simple_option_70']; $storeViewCodes = ['default', 'fixture_second_store']; @@ -168,8 +166,6 @@ public function testParentProducts() : void */ public function testParentProductsOnDifferentWebsites() : void { - $this->runIndexer([40, 50, 60, 70, 55, 59, 65]); - $skus = [ 'simple_option_50' => [ 'custom_store_view_one' => true, diff --git a/DataExporter/Plugin/ForceExporterIndexerModeOnSchedule.php b/DataExporter/Plugin/ForceExporterIndexerModeOnSchedule.php new file mode 100644 index 00000000..8fff9cf4 --- /dev/null +++ b/DataExporter/Plugin/ForceExporterIndexerModeOnSchedule.php @@ -0,0 +1,69 @@ +actionFactory = $actionFactory; + $this->logger = $logger; + } + + /** + * Intercept the setScheduled method to disable Update on Save for exporter indexers + * + * @param IndexerInterface $indexer + * @param callable $proceed + * @param bool $scheduled + * @return void + */ + public function aroundSetScheduled(IndexerInterface $indexer, callable $proceed, bool $scheduled) + { + if ($scheduled === true) { + return $proceed($scheduled); + } + + try { + $indexerAction = $this->actionFactory->create($indexer->getActionClass()); + + // Check if indexer is one of the DataExporter indexer + if ($indexerAction instanceof FeedIndexer) { + $this->logger->notice( + __("Update on Save (realtime) is not allowed for this indexer: %1", $indexer->getTitle()) + ); + return; + } + } catch (\Throwable $e) { + $this->logger->error( + 'Data Exporter exception has occurred: ' . $e->getMessage(), + ['exception' => $e] + ); + return $proceed($scheduled); + } + + return $proceed($scheduled); + } +} diff --git a/DataExporter/README.md b/DataExporter/README.md index 94c66620..c82f5bd1 100644 --- a/DataExporter/README.md +++ b/DataExporter/README.md @@ -1,3 +1,5 @@ +# Magento_DataExporter module + ## Release notes -*Magento_DataExporter* module \ No newline at end of file +*Magento_DataExporter* module diff --git a/DataExporter/Setup/Patch/Schema/SetExporterIndexerOnUpdateOnSchedule.php b/DataExporter/Setup/Patch/Schema/SetExporterIndexerOnUpdateOnSchedule.php new file mode 100644 index 00000000..e779f75d --- /dev/null +++ b/DataExporter/Setup/Patch/Schema/SetExporterIndexerOnUpdateOnSchedule.php @@ -0,0 +1,105 @@ +schemaSetup = $schemaSetup; + $this->logger = $logger; + $this->indexerCollection = $indexerCollection; + $this->actionFactory = $actionFactory; + } + + /** + * @inheritdoc + */ + public function apply() + { + $this->schemaSetup->startSetup(); + + $indexers = $this->indexerCollection->getItems(); + + foreach ($indexers as $indexer) { + try { + $indexerAction = $this->actionFactory->create($indexer->getActionClass()); + + if ($indexerAction instanceof \Magento\DataExporter\Model\Indexer\FeedIndexer) { + $this->logger->info( + sprintf("Setting mode Update On Schedule for indexer %s", $indexer->getTitle()) + ); + $indexer->setScheduled(true); + } + } catch (\Throwable $e) { + continue; + } + } + + $this->schemaSetup->endSetup(); + + return $this; + } + + /** + * @inheritDoc + */ + public static function getDependencies() + { + return []; + } + + /** + * @inheritDoc + */ + public function getAliases() + { + return []; + } +} diff --git a/DataExporter/Test/Integration/_files/overrides.xml b/DataExporter/Test/Integration/_files/overrides.xml index f03d3300..b75e509a 100644 --- a/DataExporter/Test/Integration/_files/overrides.xml +++ b/DataExporter/Test/Integration/_files/overrides.xml @@ -29,4 +29,10 @@ + + + + + + diff --git a/DataExporter/composer.json b/DataExporter/composer.json index b60b3b32..c525cb0b 100644 --- a/DataExporter/composer.json +++ b/DataExporter/composer.json @@ -18,6 +18,7 @@ "php": "~8.1.0||~8.2.0", "magento/framework": ">=103.0.4", "magento/module-analytics": ">=100.4.4", - "magento/module-query-xml": "self.version" + "magento/module-query-xml": "self.version", + "magento/module-indexer": ">=100.4.4" } } diff --git a/DataExporter/etc/di.xml b/DataExporter/etc/di.xml index ab99bc26..be3f164e 100644 --- a/DataExporter/etc/di.xml +++ b/DataExporter/etc/di.xml @@ -67,4 +67,10 @@ + + + + + diff --git a/InventoryDataExporter/README.md b/InventoryDataExporter/README.md index b6c07ca1..07487174 100644 --- a/InventoryDataExporter/README.md +++ b/InventoryDataExporter/README.md @@ -7,6 +7,6 @@ - `qtyForSale` calculated based on Reservations API - Stock is considered as infinite in the following cases: - Manage Stock disabled - - [Backorders](https://docs.magento.com/user-guide/catalog/inventory-backorders.html?itm_source=devdocs&itm_medium=quick_search&itm_campaign=federated_search&itm_term=backorer) enabled and Out-of-Stock threshold is set to 0. + - [Backorders](https://experienceleague.adobe.com/docs/commerce-admin/inventory/configuration/backorders.html) enabled and Out-of-Stock threshold is set to 0. - To calculate salable quantity Reservations API is used. - salable qty is calculated only for indexer in "on schedule" mode to prevent frequent reindexation during place order diff --git a/InventoryDataExporter/Test/Integration/AbstractInventoryTestHelper.php b/InventoryDataExporter/Test/Integration/AbstractInventoryTestHelper.php new file mode 100644 index 00000000..ee0e03ea --- /dev/null +++ b/InventoryDataExporter/Test/Integration/AbstractInventoryTestHelper.php @@ -0,0 +1,96 @@ +resource = Bootstrap::getObjectManager()->create(ResourceConnection::class); + $this->indexer = Bootstrap::getObjectManager()->create(Indexer::class); + + $this->indexer->load(self::STOCK_STATUS_FEED_INDEXER); + $this->reindexStockStatusDataExporterTable(); + } + + /** + * Reindex the full stock status data exporter table + * + * @return void + * @throws \Throwable + */ + private function reindexStockStatusDataExporterTable() : void + { + $this->indexer->reindexAll(); + } + + /** + * Run partial indexer + * + * @param array $ids + * @return void + */ + protected function emulatePartialReindexBehavior(array $ids = []) : void + { + $this->indexer->reindexList($ids); + } + + /** + * @return void + */ + protected function tearDown(): void + { + parent::tearDown(); + $this->truncateStockStatusDataExporterIndexTable(); + } + + /** + * Truncates index table + */ + private function truncateStockStatusDataExporterIndexTable(): void + { + $connection = $this->resource->getConnection(); + $feedTable = $this->resource->getTableName(self::STOCK_STATUS_FEED_INDEXER); + $connection->truncateTable($feedTable); + + $changeLogTable = $this->indexer->getView()->getChangelog()->getName(); + $connection->truncateTable($changeLogTable); + } +} diff --git a/InventoryDataExporter/Test/Integration/PartialReindexCheckTest.php b/InventoryDataExporter/Test/Integration/PartialReindexCheckTest.php index b45c2dd9..491fd3d9 100644 --- a/InventoryDataExporter/Test/Integration/PartialReindexCheckTest.php +++ b/InventoryDataExporter/Test/Integration/PartialReindexCheckTest.php @@ -10,6 +10,7 @@ use Magento\DataExporter\Model\FeedInterface; use Magento\DataExporter\Model\FeedPool; +use Magento\Indexer\Model\Indexer; use Magento\InventoryApi\Api\Data\SourceItemInterface; use Magento\InventoryApi\Api\Data\SourceItemInterfaceFactory; use Magento\InventoryApi\Api\SourceItemsSaveInterface; @@ -23,6 +24,8 @@ */ class PartialReindexCheckTest extends TestCase { + private const STOCK_STATUS_FEED_INDEXER = 'inventory_data_exporter_stock_status'; + /** * @var FeedInterface */ @@ -43,6 +46,11 @@ class PartialReindexCheckTest extends TestCase */ private $bulkSourceUnassign; + /** + * @var Indexer + */ + protected $indexer; + /** * @inheritDoc */ @@ -52,6 +60,7 @@ protected function setUp(): void $this->sourceItemsFactory = Bootstrap::getObjectManager()->get(SourceItemInterfaceFactory::class); $this->sourceItemsSave = Bootstrap::getObjectManager()->get(SourceItemsSaveInterface::class); $this->bulkSourceUnassign = Bootstrap::getObjectManager()->get(BulkSourceUnassignInterface::class); + $this->indexer = Bootstrap::getObjectManager()->create(Indexer::class); } /** @@ -59,20 +68,23 @@ protected function setUp(): void */ public function testSourceItemQtyUpdated() { + $sku = 'product_in_EU_stock_with_2_sources'; + $sourceItem = $this->sourceItemsFactory->create(['data' => [ SourceItemInterface::SOURCE_CODE => 'eu-2', - SourceItemInterface::SKU => 'product_in_EU_stock_with_2_sources', + SourceItemInterface::SKU => $sku, SourceItemInterface::QUANTITY => 2, SourceItemInterface::STATUS => SourceItemInterface::STATUS_IN_STOCK, ]]); $this->sourceItemsSave->execute([$sourceItem]); - $sku = 'product_in_EU_stock_with_2_sources'; + $this->runIndexer([$sku]); + $feedData = $this->getFeedData([$sku]); self::assertEquals( [ - 'sku' => 'product_in_EU_stock_with_2_sources', + 'sku' => $sku, 'stock_id' => 10, 'qty' => 7.5 // 5.5 (eu-1) + 2 (changed for eu-2) ], @@ -113,6 +125,8 @@ public function testSourceBulkUnassign() ['eu-1', 'default'] ); + $this->runIndexer($skus); + $feedData = $this->getFeedData($skus); $sku = 'product_with_default_stock_only'; @@ -193,4 +207,16 @@ private function getFeedData(array $skus): array } return $output; } + + /** + * Run the indexer to extract stock item data + * + * @param array $skus + * @return void + */ + private function runIndexer(array $skus = []) : void + { + $this->indexer->load(self::STOCK_STATUS_FEED_INDEXER); + $this->indexer->reindexList($skus); + } } diff --git a/InventoryDataExporter/Test/Integration/StockStatusScheduledReindexTest.php b/InventoryDataExporter/Test/Integration/StockStatusScheduledReindexTest.php index b932767a..d889f4fa 100644 --- a/InventoryDataExporter/Test/Integration/StockStatusScheduledReindexTest.php +++ b/InventoryDataExporter/Test/Integration/StockStatusScheduledReindexTest.php @@ -8,30 +8,17 @@ namespace Magento\InventoryDataExporter\Test\Integration; -use Magento\Framework\Indexer\IndexerInterface; -use Magento\Framework\Indexer\IndexerRegistry; use Magento\InventoryApi\Api\Data\SourceItemInterface; use Magento\InventoryApi\Api\Data\SourceItemInterfaceFactory; use Magento\InventoryApi\Api\SourceItemsSaveInterface; use Magento\TestFramework\Helper\Bootstrap; -use PHPUnit\Framework\TestCase; /** * @magentoDbIsolation disabled * @magentoAppIsolation enabled */ -class StockStatusScheduledReindexTest extends TestCase +class StockStatusScheduledReindexTest extends AbstractInventoryTestHelper { - /** - * feed indexer - */ - private const STOCK_STATUS_FEED_INDEXER = 'inventory_data_exporter_stock_status'; - - /** - * @var IndexerInterface - */ - private $indexer; - /** * @var SourceItemsSaveInterface */ @@ -47,20 +34,10 @@ class StockStatusScheduledReindexTest extends TestCase */ protected function setUp(): void { + parent::setUp(); + $this->sourceItemsFactory = Bootstrap::getObjectManager()->get(SourceItemInterfaceFactory::class); $this->sourceItemsSave = Bootstrap::getObjectManager()->get(SourceItemsSaveInterface::class); - $indexer = Bootstrap::getObjectManager()->create(IndexerRegistry::class); - $this->indexer = $indexer->get(self::STOCK_STATUS_FEED_INDEXER); - $this->indexer->setScheduled(true); - } - - protected function tearDown(): void - { - parent::tearDown(); - $changelog = $this->indexer->getView()->getChangelog(); - $currentVersion = $changelog->getVersion(); - $changelog->clear($currentVersion + 1); - $this->indexer->setScheduled(false); } /** @@ -74,8 +51,8 @@ public function testScheduledUpdate() $currentVersion = $this->indexer->getView()->getChangelog()->getVersion(); - // check no product added to changelog yet to prevent false-positive result - self::assertEmpty($this->indexer->getView()->getChangelog()->getList(0, $currentVersion + 1)); + // check the product is added to changelog due to reindex in the setUp + self::assertNotEmpty($this->indexer->getView()->getChangelog()->getList(0, $currentVersion)); $sourceItem = $this->sourceItemsFactory->create(['data' => [ SourceItemInterface::SOURCE_CODE => 'default', @@ -85,9 +62,9 @@ public function testScheduledUpdate() ]]); $this->sourceItemsSave->execute([$sourceItem]); - $currentVersion = $this->indexer->getView()->getChangelog()->getVersion(); + $newVersion = $this->indexer->getView()->getChangelog()->getVersion(); // verify SKU is present in changelog - self::assertEquals([$sku], $this->indexer->getView()->getChangelog()->getList(0, $currentVersion + 1)); + self::assertEquals([$sku], $this->indexer->getView()->getChangelog()->getList($currentVersion, $newVersion)); } } diff --git a/InventoryDataExporter/Test/Integration/UnassignProductFromStockTest.php b/InventoryDataExporter/Test/Integration/UnassignProductFromStockTest.php index a1e7fa6b..57b9a0f0 100644 --- a/InventoryDataExporter/Test/Integration/UnassignProductFromStockTest.php +++ b/InventoryDataExporter/Test/Integration/UnassignProductFromStockTest.php @@ -21,7 +21,7 @@ * @magentoDbIsolation disabled * @magentoAppIsolation enabled */ -class UnassignProductFromStockTest extends TestCase +class UnassignProductFromStockTest extends AbstractInventoryTestHelper { /** * @var FeedInterface @@ -43,6 +43,8 @@ class UnassignProductFromStockTest extends TestCase */ protected function setUp(): void { + parent::setUp(); + $this->stockStatusFeed = Bootstrap::getObjectManager()->get(FeedPool::class)->getFeed('inventoryStockStatus'); $this->sourceItemProcessor = Bootstrap::getObjectManager()->get(SourceItemsProcessorInterface::class); $this->bulkSourceUnassign = Bootstrap::getObjectManager()->get(BulkSourceUnassignInterface::class); @@ -62,6 +64,7 @@ public function testSourceItemStockUnassigned(string $sku, array $sourcesToLeave $sourceItems = $this->getSourcesData($sku, $sourcesToLeave); $this->sourceItemProcessor->execute($sku, $sourceItems); + $this->emulatePartialReindexBehavior([$sku]); $feedData = $this->getFeedData([$sku]); $this->verifyResults($feedData, $sku, $expectedData); @@ -84,6 +87,7 @@ public function testSourceItemsBulkUnassign(array $skus, array $sourcesToUnassig $sourcesToUnassign ); + $this->emulatePartialReindexBehavior($skus); $feedData = $this->getFeedData($skus); foreach ($skus as $sku) { @@ -108,6 +112,7 @@ private function getFeedData(array $skus): array } /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @return array[] */ public function stocksUnassignDataProvider(): array @@ -237,6 +242,7 @@ public function stocksUnassignDataProvider(): array } /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @return array[] */ public function stocksBulkUnassignDataProvider(): array diff --git a/ProductPriceDataExporter/Test/Integration/AbstractProductPriceTestHelper.php b/ProductPriceDataExporter/Test/Integration/AbstractProductPriceTestHelper.php index 6aa93d44..cc61768d 100644 --- a/ProductPriceDataExporter/Test/Integration/AbstractProductPriceTestHelper.php +++ b/ProductPriceDataExporter/Test/Integration/AbstractProductPriceTestHelper.php @@ -8,6 +8,7 @@ namespace Magento\ProductPriceDataExporter\Test\Integration; use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Api\SearchCriteriaInterface; use Magento\Framework\App\ResourceConnection; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\ObjectManagerInterface; @@ -55,6 +56,9 @@ protected function setUp(): void $this->productRepository = $this->objectManager->create(ProductRepositoryInterface::class); $this->indexer = $this->objectManager->create(Indexer::class); $this->resourceConnection = $this->objectManager->create(ResourceConnection::class); + + $this->indexer->load(self::PRODUCT_PRICE_FEED_INDEXER); + $this->reindexProductPriceDataExporter(); } /** @@ -105,17 +109,20 @@ protected function checkExpectedItemsAreExportedInFeed(array $expectedItems): vo } /** - * Run the indexer to extract product prices data - * @param $ids + * Reindex all the product price data exporter table for existing products + * * @return void */ - protected function runIndexer($ids): void + private function reindexProductPriceDataExporter() : void { - try { - $this->indexer->load(self::PRODUCT_PRICE_FEED_INDEXER); - $this->indexer->reindexList($ids); - } catch (\Throwable) { - throw new \RuntimeException('Could not reindex product prices data'); + $searchCriteria = Bootstrap::getObjectManager()->create(SearchCriteriaInterface::class); + + $productIds = array_map(function ($product) { + return $product->getId(); + }, $this->productRepository->getList($searchCriteria)->getItems()); + + if (!empty($productIds)) { + $this->indexer->reindexList($productIds); } } diff --git a/ProductPriceDataExporter/Test/Integration/ExportSingleProductPriceTest.php b/ProductPriceDataExporter/Test/Integration/ExportSingleProductPriceTest.php index 5e04aa85..91af1a25 100644 --- a/ProductPriceDataExporter/Test/Integration/ExportSingleProductPriceTest.php +++ b/ProductPriceDataExporter/Test/Integration/ExportSingleProductPriceTest.php @@ -25,6 +25,9 @@ */ class ExportSingleProductPriceTest extends AbstractProductPriceTestHelper { + /** + * @var CatalogRuleRepositoryInterface $catalogRuleRepository + */ private CatalogRuleRepositoryInterface $catalogRuleRepository; protected function setUp(): void @@ -82,11 +85,6 @@ public function testExportSimpleProductsWithDisabledCatalogPriceRulePrices(array */ public function testExportDownloadableProductsPrices(array $expectedDownloadableProductPricesDataProvider): void { - $affectedIds = []; - foreach ($expectedDownloadableProductPricesDataProvider as $expectedItem) { - $affectedIds[] = $this->productRepository->get($expectedItem['sku'])->getId(); - } - $this->runIndexer($affectedIds); $this->checkExpectedItemsAreExportedInFeed($expectedDownloadableProductPricesDataProvider); } diff --git a/ProductPriceDataExporter/Test/Integration/ExportSingleProductPriceUpdateOperationsTest.php b/ProductPriceDataExporter/Test/Integration/ExportSingleProductPriceUpdateOperationsTest.php index 010cfaf9..c2df2552 100644 --- a/ProductPriceDataExporter/Test/Integration/ExportSingleProductPriceUpdateOperationsTest.php +++ b/ProductPriceDataExporter/Test/Integration/ExportSingleProductPriceUpdateOperationsTest.php @@ -39,11 +39,6 @@ class ExportSingleProductPriceUpdateOperationsTest extends AbstractProductPriceT */ public function testUnassignProductFromWebsite(array $expectedSimpleProductPrices): void { - $affectedIds = []; - foreach ($expectedSimpleProductPrices as $expectedItem) { - $affectedIds[] = $this->productRepository->get($expectedItem['sku'])->getId(); - } - $this->runIndexer($affectedIds); $product = $this->productRepository->get('simple_product_with_tier_price'); $websiteRepository = $this->objectManager->get(WebsiteRepositoryInterface::class); $secondWebsiteId = $websiteRepository->get('test')->getId(); @@ -66,11 +61,6 @@ public function testUnassignProductFromWebsite(array $expectedSimpleProductPrice */ public function testDisableProductGlobally(array $expectedSimpleProductPrices): void { - $affectedIds = []; - foreach ($expectedSimpleProductPrices as $expectedItem) { - $affectedIds[] = $this->productRepository->get($expectedItem['sku'])->getId(); - } - $this->runIndexer($affectedIds); //Get product for edit in general scope (all websites) $product = $this->productRepository->get('simple_product_with_tier_price', true, 0); //Disable it on general level @@ -94,11 +84,6 @@ public function testDisableProductGlobally(array $expectedSimpleProductPrices): */ public function testEnableProductOnWebsite(array $expectedSimpleProductPrices): void { - $affectedIds = []; - foreach ($expectedSimpleProductPrices as $expectedItem) { - $affectedIds[] = $this->productRepository->get($expectedItem['sku'])->getId(); - } - $this->runIndexer($affectedIds); //Get product for edit in general scope (all websites) $product = $this->productRepository->get('simple_product_with_tier_price', true, 0); //Disable it on general level @@ -133,18 +118,12 @@ public function testEnableProductOnWebsite(array $expectedSimpleProductPrices): */ public function testReassignProductToWebsite(array $expectedSimpleProductPrices): void { - $affectedIds = []; - foreach ($expectedSimpleProductPrices as $expectedItem) { - $affectedIds[] = $this->productRepository->get($expectedItem['sku'])->getId(); - } - $this->runIndexer($affectedIds); $product = $this->productRepository->get('simple_product_with_tier_price'); $websiteRepository = $this->objectManager->get(WebsiteRepositoryInterface::class); $firstWebsiteId = $websiteRepository->get('base')->getId(); $secondWebsiteId = $websiteRepository->get('test')->getId(); $product->setWebsiteIds([$secondWebsiteId]); $this->productRepository->save($product); - $this->runIndexer($affectedIds); $product->setWebsiteIds([$firstWebsiteId, $secondWebsiteId]); $this->productRepository->save($product); diff --git a/ProductPriceDataExporter/etc/mview.xml b/ProductPriceDataExporter/etc/mview.xml index 9a3e3caf..c7258943 100644 --- a/ProductPriceDataExporter/etc/mview.xml +++ b/ProductPriceDataExporter/etc/mview.xml @@ -11,7 +11,6 @@
-
diff --git a/SalesOrdersDataExporter/Model/Provider/CreditMemoComment.php b/SalesOrdersDataExporter/Model/Provider/CreditMemoComment.php new file mode 100644 index 00000000..1d6b12bd --- /dev/null +++ b/SalesOrdersDataExporter/Model/Provider/CreditMemoComment.php @@ -0,0 +1,32 @@ + $creditMemo['entityId'], + 'creditMemoComments' => $creditMemo['comment'], + ]; + } + } + return $output; + } +} diff --git a/SalesOrdersDataExporter/Setup/Recurring.php b/SalesOrdersDataExporter/Setup/Recurring.php deleted file mode 100644 index 27dab00e..00000000 --- a/SalesOrdersDataExporter/Setup/Recurring.php +++ /dev/null @@ -1,83 +0,0 @@ -logger = $logger; - $this->indexerInterfaceFactory = $indexerInterfaceFactory; - } - - /** - * If orders indexer is found, will force mode to be On Schedule - * - * @param SchemaSetupInterface $setup - * @param ModuleContextInterface $context - * @return void - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function install(SchemaSetupInterface $setup, ModuleContextInterface $context): void - { - $setup->startSetup(); - - $ordersIndexer = $this->findOrdersIndexer(); - if ($ordersIndexer && !$ordersIndexer->isScheduled()) { - $ordersIndexer->setScheduled(true); - $this->logger->info( - 'Mode for indexer sales_order_data_exporter_v2 has been forced to \'Update by Schedule\'.' - ); - } - - $setup->endSetup(); - } - - /** - * Finds orders indexer in the set of available indexers - * - * @return IndexerInterface|null - */ - private function findOrdersIndexer(): ?IndexerInterface - { - try { - return $this->indexerInterfaceFactory->create()->load(self::ORDERS_INDEXER_NAME); - } catch (InvalidArgumentException) { - // ignored, if not found is expected to do nothing - return null; - } - } -} diff --git a/SalesOrdersDataExporter/Test/Integration/AbstractOrderFeedTest.php b/SalesOrdersDataExporter/Test/Integration/AbstractOrderFeedTest.php index acb7ce89..9317b73e 100644 --- a/SalesOrdersDataExporter/Test/Integration/AbstractOrderFeedTest.php +++ b/SalesOrdersDataExporter/Test/Integration/AbstractOrderFeedTest.php @@ -118,7 +118,7 @@ protected function runIndexer(array $ids) : void $this->indexer->load(self::ORDER_FEED_INDEXER); $this->indexer->reindexList($ids); } catch (Throwable $e) { - throw new RuntimeException('Could not reindex orders data'); + throw new RuntimeException('Could not reindex orders data', $e); } } } diff --git a/SalesOrdersDataExporter/Test/Integration/CreateOrderTest.php b/SalesOrdersDataExporter/Test/Integration/CreateOrderTest.php index eb51e7b9..7d781693 100644 --- a/SalesOrdersDataExporter/Test/Integration/CreateOrderTest.php +++ b/SalesOrdersDataExporter/Test/Integration/CreateOrderTest.php @@ -7,17 +7,19 @@ namespace Magento\SalesOrdersDataExporter\Test\Integration; +use Magento\DataExporter\Uuid\ResourceModel\UuidResource; +use Magento\Framework\Exception\InputException; +use Magento\Framework\Exception\NoSuchEntityException; use Magento\Sales\Api\Data\OrderInterface; use Magento\Sales\Api\Data\OrderInterfaceFactory; -use Magento\DataExporter\Uuid\ResourceModel\UuidResource; use Magento\Sales\Api\Data\OrderItemInterface; use Magento\Sales\Api\Data\ShipmentTrackInterface; use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\Creditmemo; use Magento\Sales\Model\Order\Invoice; +use Magento\Sales\Model\Order\Payment\Transaction\Repository as TransactionRepository; use Magento\Sales\Model\Order\Shipment; use Magento\TestFramework\Helper\Bootstrap; -use Magento\Sales\Model\Order\Payment\Transaction\Repository as TransactionRepository; /** * Test for orders data exporter functionality @@ -60,425 +62,208 @@ protected function setUp(): void } /** - * @param string $orderNumber - * @param string[] $dataToVerify - * - * @dataProvider orderWithTwoItemsDataProvider * @magentoDataFixture Magento/Sales/_files/customer_order_with_two_items.php * * @return void * @throws \Zend_Db_Statement_Exception - * @throws \Magento\Framework\Exception\InputException + * @throws InputException|NoSuchEntityException */ - public function testOrderWithTwoProductsInformation(string $orderNumber, array $dataToVerify): void + public function testOrderWithTwoProductsInformation(): void { /** @var OrderInterface $order */ - $order = $this->orderFactory->create()->loadByIncrementId($orderNumber); + $order = $this->orderFactory->create()->loadByIncrementId('100000555'); $orderId = $order->getEntityId(); $this->runIndexer([$orderId]); + $orderFeed = $this->getOrderFeedByIds([$orderId])[0]; - $expectedOrdersData = $this->getOrderDataToVerify($order, $dataToVerify); + $expectedOrderData = $this->getOrderDataToVerify($order); - $this->checkFields($expectedOrdersData, $orderFeed); + $this->checkFields($expectedOrderData, $orderFeed); } /** - * @param string $orderNumber - * @param string[] $dataToVerify - * - * @dataProvider orderFullWorkflowDataProvider - * @magentoDataFixture Magento_SalesOrdersDataExporter::Test/_files/order_full_work_flow.php + * @magentoDataFixture Magento/Sales/_files/customer_order_with_taxable_product.php * * @return void * @throws \Zend_Db_Statement_Exception */ - public function testOrderFullWorkflowInformation(string $orderNumber, array $dataToVerify): void + public function testOrderWithTaxableProductInformation(): void { /** @var OrderInterface $order */ - $order = $this->orderFactory->create()->loadByIncrementId($orderNumber); + $order = $this->orderFactory->create()->loadByIncrementId('test_order_with_taxable_product'); $orderId = $order->getEntityId(); $this->runIndexer([$orderId]); + $orderFeed = $this->getOrderFeedByIds([$orderId])[0]; - $expectedOrdersData = $this->getOrderDataToVerify($order, $dataToVerify); + $expectedOrderData = $this->getOrderDataToVerify($order); - $this->checkFields($expectedOrdersData, $orderFeed); + $this->checkFields($expectedOrderData, $orderFeed); } /** - * @param string $orderNumber - * @param string[] $dataToVerify - * - * @dataProvider orderWithTaxableProductDataProvider - * @magentoDataFixture Magento/Sales/_files/customer_order_with_taxable_product.php + * @magentoDataFixture Magento/Sales/_files/order_with_invoice_and_custom_status.php * * @return void * @throws \Zend_Db_Statement_Exception */ - public function testOrderWithTaxableProductInformation(string $orderNumber, array $dataToVerify): void + public function testOrderWithInvoiceAndCustomStatus(): void { /** @var OrderInterface $order */ - $order = $this->orderFactory->create()->loadByIncrementId($orderNumber); + $order = $this->orderFactory->create()->loadByIncrementId('100000001'); $orderId = $order->getEntityId(); $this->runIndexer([$orderId]); + $orderFeed = $this->getOrderFeedByIds([$orderId])[0]; - $expectedOrdersData = $this->getOrderDataToVerify($order, $dataToVerify); + $expectedOrderData = $this->getOrderDataToVerify($order); - $this->checkFields($expectedOrdersData, $orderFeed); + $this->checkFields($expectedOrderData, $orderFeed); } /** - * @param string $orderNumber - * @param string[] $dataToVerify - * - * @dataProvider orderWithInvoiceAndCustomStatusDataProvider - * @magentoDataFixture Magento/Sales/_files/order_with_invoice_and_custom_status.php + * @magentoDataFixture Magento_SalesOrdersDataExporter::Test/_files/order_full_work_flow.php * * @return void * @throws \Zend_Db_Statement_Exception */ - public function testOrderWithInvoiceAndCustomStatus(string $orderNumber, array $dataToVerify): void + public function testOrderFullWorkflowInformation(): void { /** @var OrderInterface $order */ - $order = $this->orderFactory->create()->loadByIncrementId($orderNumber); + $order = $this->orderFactory->create()->loadByIncrementId('100000001'); $orderId = $order->getEntityId(); $this->runIndexer([$orderId]); + $orderFeed = $this->getOrderFeedByIds([$orderId])[0]; - $expectedOrdersData = $this->getOrderDataToVerify($order, $dataToVerify); + $expectedOrderData = $this->getOrderDataToVerify($order); - $this->checkFields($expectedOrdersData, $orderFeed); + $this->checkFields($expectedOrderData, $orderFeed); } /** - * @param string $orderNumber - * @param string[] $dataToVerify - * - * @dataProvider orderWithCreditMemoDataProvider * @magentoDataFixture Magento_SalesOrdersDataExporter::Test/_files/order_with_invoice_shipment_creditmemo.php * * @return void * @throws \Zend_Db_Statement_Exception */ - public function testOrderWithCreditMemo(string $orderNumber, array $dataToVerify): void + public function testOrderWithCreditMemo(): void { /** @var OrderInterface $order */ - $order = $this->orderFactory->create()->loadByIncrementId($orderNumber); + $order = $this->orderFactory->create()->loadByIncrementId('100000111'); $orderId = $order->getEntityId(); $this->runIndexer([$orderId]); + $orderFeed = $this->getOrderFeedByIds([$orderId])[0]; - $expectedOrdersData = $this->getOrderDataToVerify($order, $dataToVerify); + $expectedOrderData = $this->getOrderDataToVerify($order); - $this->checkFields($expectedOrdersData, $orderFeed); + $this->checkFields($expectedOrderData, $orderFeed); } /** - * @param string $orderNumber - * @param string[] $dataToVerify - * - * @dataProvider orderWithConfigurableProductDataProvider * @magentoDataFixture Magento_SalesOrdersDataExporter::Test/_files/order_configurable_product.php * * @return void * @throws \Zend_Db_Statement_Exception */ - public function testOrderWithConfigurableProduct(string $orderNumber, array $dataToVerify): void + public function testOrderWithConfigurableProduct(): void { /** @var OrderInterface $order */ - $order = $this->orderFactory->create()->loadByIncrementId($orderNumber); + $order = $this->orderFactory->create()->loadByIncrementId('100000001'); $orderId = $order->getEntityId(); $this->runIndexer([$orderId]); + $orderFeed = $this->getOrderFeedByIds([$orderId])[0]; - $expectedOrdersData = $this->getOrderDataToVerify($order, $dataToVerify); + $expectedOrderData = $this->getOrderDataToVerify($order); - $this->checkFields($expectedOrdersData, $orderFeed); + $this->checkFields($expectedOrderData, $orderFeed); } /** - * @param string $orderNumber - * @param string[] $dataToVerify - * - * @dataProvider orderWithTransactionsDataProvider * @magentoDataFixture Magento_SalesOrdersDataExporter::Test/_files/transactions_detailed.php * * @return void * @throws \Zend_Db_Statement_Exception */ - public function testOrderWithTransactions(string $orderNumber, array $dataToVerify): void + public function testOrderWithTransactions(): void { /** @var OrderInterface $order */ - $order = $this->orderFactory->create()->loadByIncrementId($orderNumber); + $order = $this->orderFactory->create()->loadByIncrementId('100000001'); $orderId = $order->getEntityId(); $this->runIndexer([$orderId]); + $orderFeed = $this->getOrderFeedByIds([$orderId])[0]; - $expectedOrdersData = $this->getOrderDataToVerify($order, $dataToVerify); + $expectedOrderData = $this->getOrderDataToVerify($order); - $this->checkFields($expectedOrdersData, $orderFeed); + $this->checkFields($expectedOrderData, $orderFeed); } /** - * @param string $orderNumber - * @param string[] $dataToVerify - * - * @dataProvider orderWithAdditionalInformationDataProvider * @magentoDataFixture Magento_SalesOrdersDataExporter::Test/_files/order_with_additional_information.php * * @return void * @throws \Zend_Db_Statement_Exception */ - public function testOrderWithAdditionalData(string $orderNumber, array $dataToVerify): void + public function testOrderWithAdditionalData(): void { /** @var OrderInterface $order */ - $order = $this->orderFactory->create()->loadByIncrementId($orderNumber); + $order = $this->orderFactory->create()->loadByIncrementId('100000001'); $orderId = $order->getEntityId(); $this->runIndexer([$orderId]); - $orderFeed = $this->getOrderFeedByIds([$orderId])[0]; - $expectedOrdersData = $this->getOrderDataToVerify($order, $dataToVerify); - - $this->checkFields($expectedOrdersData, $orderFeed); - } - - /** - * @return array[] - */ - public function orderWithTwoItemsDataProvider(): array - { - return [ - [ - 'order_number' => '100000555', //customer_order_with_two_items - 'entities_to_verify' => [ - 'order_data', - 'items' - ] - ] - ]; - } - - /** - * @return array[] - */ - public function orderWithTaxableProductDataProvider(): array - { - return [ - [ - 'order_number' => 'test_order_with_taxable_product', //customer_order_with_taxable_product - 'entities_to_verify' => [ - 'order_data', - 'items' - ] - ] - ]; - } - /** - * @return array[] - */ - public function orderFullWorkflowDataProvider(): array - { - return [ - [ - 'order_number' => '100000001', //order_full_work_flow - 'entities_to_verify' => [ - 'order_data', - 'items', - 'shipments', - 'invoice' - ] - ] - ]; - } - - /** - * @return array[] - */ - public function orderWithInvoiceAndCustomStatusDataProvider(): array - { - return [ - [ - 'order_number' => '100000001', //order_with_invoice_and_custom_status - 'entities_to_verify' => [ - 'order_data', - 'items', - 'invoice' - ] - ], - ]; - } - - /** - * @return array[] - */ - public function orderWithCreditMemoDataProvider(): array - { - return [ - [ - 'order_number' => '100000111', //order_with_invoice_shipment_creditmemo - 'entities_to_verify' => [ - 'order_data', - 'items', - 'credit_memo' - ] - ], - ]; - } + $orderFeed = $this->getOrderFeedByIds([$orderId])[0]; + $expectedOrderData = $this->getOrderDataToVerify($order); - /** - * @return array[] - */ - public function orderWithConfigurableProductDataProvider(): array - { - return [ - [ - 'order_number' => '100000001', //order_configurable_product - 'entities_to_verify' => [ - 'order_data', - 'items' - ] - ], - ]; + $this->checkFields($expectedOrderData, $orderFeed); } /** - * @return array[] + * @param array $expectedData + * @param array $feedData */ - public function orderWithTransactionsDataProvider(): array + private function checkFields(array $expectedData, array $feedData): void { - return [ - [ - 'order_number' => '100000001', //transactions_detailed - 'entities_to_verify' => [ - 'order_data', - 'transactions' - ] - ], - ]; + foreach ($expectedData as $field => $expectedValue) { + if (is_array($expectedValue)) { + $this->assertArrayHasKey($field, $feedData, sprintf('Field %s is not set in feed', $field)); + $this->checkFields($expectedValue, $feedData[$field]); + } else { + $this->assertFieldEquals($field, $expectedValue, $feedData); + } + } } /** - * @return array[] + * @param mixed $fieldName + * @param mixed $expectedValue + * @param array $actualData */ - public function orderWithAdditionalInformationDataProvider(): array + private function assertFieldEquals(mixed $fieldName, mixed $expectedValue, array $actualData): void { - return [ - [ - 'order_number' => '100000001', //additional_information - 'entities_to_verify' => [ - 'order_data', - 'items', - 'additional_information' - ] - ], - ]; - } - - /** - * @param array $expectedOrderData - * @param array $feedData - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) - */ - private function checkFields(array $expectedOrderData, array $feedData): void - { - foreach ($expectedOrderData as $expectedField => $expectedData) { - if ($expectedField === 'items') { - $uncheckedIds = \array_flip(array_keys($expectedData)); - foreach ($feedData['items'] as $itemData) { - $itemIdField = isset($itemData['itemId']) ? 'itemId' : 'orderItemId'; - $expectedDataId = $itemData[$itemIdField]['id']; - if (isset($itemData[$itemIdField])) { - $this->checkFields($expectedData[$expectedDataId], $itemData); - unset($uncheckedIds[$expectedDataId]); - } - } - self::assertEmpty($uncheckedIds, "Some items are missed in feed"); - continue; - } - if ($expectedField === 'shipments') { - $uncheckedIds = \array_flip(array_keys($expectedData)); - foreach ($feedData['shipments'] as $itemData) { - $this->checkFields($expectedData[$itemData['shipmentId']['id']], $itemData); - unset($uncheckedIds[$itemData['shipmentId']['id']]); - } - self::assertEmpty($uncheckedIds, "Some shipment items are missed in feed"); - continue; - } - if ($expectedField === 'invoices') { - $uncheckedIds = \array_flip(array_keys($expectedData)); - foreach ($feedData['invoices'] as $itemData) { - $invoiceId = $itemData['entityId']; - $expectedInvoiceData = $expectedData[$invoiceId]; - $this->checkFields($expectedInvoiceData, $itemData); - unset($uncheckedIds[$invoiceId]); - } - self::assertEmpty($uncheckedIds, "Some invoice items are missed in feed"); - continue; - } - - if ($expectedField === 'creditMemos') { - $uncheckedIds = \array_flip(array_keys($expectedData)); - foreach ($feedData[$expectedField] as $itemData) { - $this->checkFields($expectedData[$itemData['creditMemoId']['id']], $itemData); - unset($uncheckedIds[$itemData['creditMemoId']['id']]); - } - self::assertEmpty($uncheckedIds, "Some credit memo items are missed in feed"); - continue; - } - - if ($expectedField === 'transactions') { - $uncheckedIds = \array_flip(array_keys($expectedData)); - foreach ($feedData[$expectedField] as $itemData) { - $this->checkFields($expectedData[$itemData['entityId']], $itemData); - unset($uncheckedIds[$itemData['entityId']]); - } - self::assertEmpty($uncheckedIds, "Some transaction items are missed in feed"); - continue; - } - - if (isset($feedData[$expectedField])) { - if (!\is_array($feedData[$expectedField])) { - self::assertEquals( - $expectedData, - $feedData[$expectedField], - sprintf( - "Expected data: %s doesn't equal to real field %s value: %s", - $expectedData, - $expectedField, - $feedData[$expectedField] - ) - ); - } else { - $this->checkFields($expectedData, $feedData[$expectedField]); - } - } + if (array_key_exists($fieldName, $actualData)) { + self::assertEquals( + $expectedValue, + $actualData[$fieldName], + "Expected data: $expectedValue doesn't equal to real field $fieldName value: {$actualData[$fieldName]}" + ); + } else { + self::assertNull( + $expectedValue, + "Field $fieldName is not set in feed but the expected value was $expectedValue" + ); } } /** * @param OrderInterface $order - * @param array $dataToVerify * @return array - * @throws \Magento\Framework\Exception\InputException + * @throws InputException */ - private function getOrderDataToVerify(OrderInterface $order, array $dataToVerify): array + private function getOrderDataToVerify(OrderInterface $order): array { - $expectedOrderData = []; - if (array_contains($dataToVerify, 'order_data')) { - $expectedOrderData = $this->getExpectedOrderData($order); - } - if (array_contains($dataToVerify, 'transactions')) { - $expectedOrderData['transactions'] = $this->getExpectedTransactionsData($order); - } - if (array_contains($dataToVerify, 'items')) { - $expectedOrderData['items'] = $this->getExpectedOrderItemsData($order); - } - if (array_contains($dataToVerify, 'invoice')) { - $expectedOrderData['invoices'] = $this->getExpectedInvoicesData($order); - } - if (array_contains($dataToVerify, 'credit_memo')) { - $expectedOrderData['creditMemos'] = $this->getExpectedCreditMemosData($order); - } - if (array_contains($dataToVerify, 'shipments')) { - $expectedOrderData['shipments'] = $this->getExpectedShipmentData($order); - } + $expectedOrderData = $this->getExpectedOrderData($order); + $expectedOrderData['transactions'] = $this->getExpectedTransactionsData($order); + $expectedOrderData['items'] = $this->getExpectedOrderItemsData($order); + $expectedOrderData['invoices'] = $this->getExpectedInvoicesData($order); + $expectedOrderData['creditMemos'] = $this->getExpectedCreditMemosData($order); + $expectedOrderData['shipments'] = $this->getExpectedShipmentData($order); return $expectedOrderData; } @@ -493,13 +278,13 @@ private function getOrderDataToVerify(OrderInterface $order, array $dataToVerify */ private function getOrderFeedByIds(array $ids, bool $excludeDeleted = false): array { - $output = []; - foreach ($this->ordersFeed->getFeedSince('1')['feed'] as $item) { - if ((!$excludeDeleted || !$item['deleted']) && \in_array($item['commerceOrderId'], $ids)) { - $output[] = $item; + $filteredFeed = array_filter( + $this->ordersFeed->getFeedSince('1')['feed'], + function ($item) use ($ids, $excludeDeleted) { + return (!$excludeDeleted || !$item['deleted']) && in_array($item['commerceOrderId'], $ids); } - } - return $output; + ); + return array_values($filteredFeed); } /** @@ -540,7 +325,7 @@ private function getExpectedOrderData(OrderInterface $order): array { $orderId = $order->getEntityId(); return [ - 'entityId' => $order->getEntityId(), + 'commerceOrderId' => $order->getEntityId(), 'commerceOrderNumber' => $order->getIncrementId(), 'orderId' => ['id' => $this->uuidResource->getAssignedIds([$orderId], 'order')[$orderId]], 'externalId' => ['id' => $orderId, 'salesChannel' => 'magento'], @@ -549,15 +334,23 @@ private function getExpectedOrderData(OrderInterface $order): array 'state' => $this->mapOrderState($order->getState()), 'status' => $order->getStatus(), 'totalInvoiced' => $order->getBaseTotalInvoiced(), + 'totalQtyOrdered' => $order->getTotalQtyOrdered(), + 'isVirtual' => $this->convertIntToBool($order->getIsVirtual()), + 'currency' => $order->getBaseCurrencyCode(), 'subtotal' => $order->getBaseSubtotal(), 'grandTotal' => $order->getBaseGrandTotal(), 'discountAmount' => $order->getBaseDiscountAmount(), - 'currency' => $order->getBaseCurrencyCode(), + 'amountCapturedOnline' => $order->getPayment()->getBaseAmountPaidOnline(), + 'amountRefundedOnline' => $order->getPayment()->getBaseAmountRefundedOnline(), + 'amountAuthorized' => $order->getPayment()->getBaseAmountAuthorized(), 'amountPaid' => $order->getPayment()->getBaseAmountPaid(), + 'amountRefunded' => $order->getPayment()->getBaseAmountRefunded(), + 'amountCanceled' => $order->getPayment()->getBaseAmountCanceled(), 'storeViewCode' => $order->getStore()->getCode(), 'websiteCode' => $order->getStore()->getWebsite()->getCode(), 'storeCode' => $order->getStore()->getWebsite()->getDefaultGroup()->getCode(), 'customerEmail' => $order->getCustomerEmail(), + 'customerNote' => $order->getCustomerNote(), 'additionalInformation' => $this->getExpectedOrderAdditionalInformationData($order), 'payment' => [ 'billingAddress' => [ @@ -570,8 +363,8 @@ private function getExpectedOrderData(OrderInterface $order): array 'country' => $order->getBillingAddress()->getCountryId(), 'firstname' => $order->getBillingAddress()->getFirstname() ], - 'paymentMethodName' => $order->getPayment()->getAdditionalInformation()['method_title'] ?? '', - 'paymentMethodCode' => $order->getPayment()->getMethod() ?? '', + 'paymentMethodName' => $order->getPayment()->getAdditionalInformation()['method_title'] ?? null, + 'paymentMethodCode' => $order->getPayment()->getMethod() ?? null, 'totalAmount' => $order->getBaseSubtotal(), 'taxAmount' => $order->getBaseTaxAmount(), 'currency' => $order->getOrderCurrencyCode() @@ -598,10 +391,10 @@ private function getExpectedOrderData(OrderInterface $order): array /** * @param OrderInterface $order - * @return array - * @throws \Magento\Framework\Exception\InputException + * @return array|null + * @throws InputException */ - private function getExpectedTransactionsData(OrderInterface $order): array + private function getExpectedTransactionsData(OrderInterface $order): ?array { $transactions = []; foreach (self::TRANSACTION_TYPES as $transactionType) { @@ -610,8 +403,8 @@ private function getExpectedTransactionsData(OrderInterface $order): array $order->getPayment()->getEntityId() ); if ($transaction) { - $transactions[$transaction->getId()] = [ - 'id' => $transaction->getId(), + $transactions[] = [ + 'entityId' => $transaction->getId(), 'txnId' => $transaction->getTxnId(), 'type' => $transaction->getTxnType(), 'createdAt' => $this->convertDate($transaction->getCreatedAt()) @@ -619,24 +412,24 @@ private function getExpectedTransactionsData(OrderInterface $order): array } } - return $transactions; + return empty($transactions) ? null : $transactions; } /** * @param OrderInterface $order - * @return array + * @return array|null */ - private function getExpectedOrderItemsData(OrderInterface $order): array + private function getExpectedOrderItemsData(OrderInterface $order): ?array { $items = []; foreach ($order->getItems() as $orderItem) { $itemId = $orderItem->getItemId(); $itemUuid = $this->uuidResource->getAssignedIds([$itemId], 'order_item')[$itemId]; - $items[$itemUuid] = [ + $items[] = [ 'itemId' => ['id' => $itemUuid], 'entityId' => $itemId, 'parentEntityId' => $orderItem->getParentItemId(), - 'isVirtual' => (bool)$orderItem->getIsVirtual(), + 'isVirtual' => $this->convertIntToBool($orderItem->getIsVirtual()), 'qtyInvoiced' => $orderItem->getQtyInvoiced(), 'qtyShipped' => $orderItem->getQtyShipped(), 'qtyBackordered' => $orderItem->getQtyBackordered(), @@ -646,7 +439,7 @@ private function getExpectedOrderItemsData(OrderInterface $order): array 'productType' => $orderItem->getProductType(), 'itemsShippedTogether' => $orderItem->getProductType() === 'configurable', 'sku' => $orderItem->getSku(), - 'productSku' => $orderItem->getProduct()->getSku(), + 'productSku' => $this->getExpectedOrderItemProductSku($orderItem), 'name' => $orderItem->getName(), 'qty' => $orderItem->getQtyOrdered(), 'unitPrice' => $orderItem->getBasePrice(), @@ -658,17 +451,31 @@ private function getExpectedOrderItemsData(OrderInterface $order): array 'additionalInformation' => $this->getExpectedItemAdditionalInformationData($orderItem) ]; } - return $items; + return empty($items) ? null : $items; + } + + /** + * Get the product SKU based on the product type. + * + * @param OrderItemInterface $orderItem + * @return string|null + */ + private function getExpectedOrderItemProductSku(OrderItemInterface $orderItem): ?string + { + if (in_array($orderItem->getProductType(), ['configurable', 'bundle'], true)) { + return $orderItem->getProduct()->getSku(); + } + + return $orderItem->getSku(); } /** * @param OrderInterface $order - * @return array + * @return array|null */ - private function getExpectedInvoicesData(OrderInterface $order): array + private function getExpectedInvoicesData(OrderInterface $order): ?array { $invoices = []; - /** @var Invoice $invoice */ foreach ($order->getInvoiceCollection() as $invoice) { $invoiceId = $invoice->getId(); @@ -677,28 +484,29 @@ private function getExpectedInvoicesData(OrderInterface $order): array foreach ($invoice->getItems() as $invoiceItem) { $orderItemId = $invoiceItem->getOrderItemId(); $itemUuid = $this->uuidResource->getAssignedIds([$orderItemId], 'order_item')[$orderItemId]; - $invoiceItems[$orderItemId] = [ + $invoiceItems[] = [ 'orderItemId' => ['id' => $itemUuid], 'qtyInvoiced' => $invoiceItem->getQty() ]; } - $invoices[$invoiceId] = [ + $invoices[] = [ 'entityId' => $invoiceId, - 'isUsedForRefund' => false, + 'isUsedForRefund' => $this->convertIntToBool($invoice->getIsUsedForRefund()), 'grandTotal' => $invoice->getBaseGrandTotal(), 'createdAt' => $this->convertDate($invoice->getCreatedAt()), 'commerceInvoiceNumber' => $invoice->getIncrementId(), 'invoiceItems' => $invoiceItems ]; } - return $invoices; + + return empty($invoices) ? null : $invoices; } /** * @param OrderInterface $order - * @return array + * @return array|null */ - private function getExpectedCreditMemosData(OrderInterface $order): array + private function getExpectedCreditMemosData(OrderInterface $order): ?array { $creditMemos = []; /** @var Creditmemo $creditMemo */ @@ -706,49 +514,63 @@ private function getExpectedCreditMemosData(OrderInterface $order): array $creditMemoItems = null; $creditMemoId = $creditMemo->getId(); $creditMemoUuid = $this->uuidResource->getAssignedIds([$creditMemoId], 'credit_memo')[$creditMemoId]; - $creditMemos[$creditMemoUuid] = [ + + foreach ($creditMemo->getItems() as $creditmemoItem) { + $creditMemoItemId = $creditmemoItem->getOrderItemId(); + $itemUuid = $this->uuidResource->getAssignedIds( + [$creditMemoItemId], + 'order_item' + )[$creditMemoItemId]; + $creditMemoItems[] = [ + 'orderItemId' => ['id' => $itemUuid], + 'qtyRefunded' => $creditmemoItem->getQty(), + 'basePrice' => $creditmemoItem->getBasePrice(), + 'baseRowTotal' => $creditmemoItem->getQty() * $creditmemoItem->getBasePrice() + - $creditmemoItem->getBaseDiscountAmount(), + //TODO: Need to be implemented + //'productTaxes' => '' + ]; + } + + $creditMemos[] = [ 'creditMemoId' => ['id' => $creditMemoUuid], 'entityId' => $creditMemoId, 'state' => $creditMemo->getState(), 'createdAt' => $this->convertDate($creditMemo->getCreatedAt()), 'shippingAmount' => $creditMemo->getBaseShippingAmount(), 'shippingTaxAmount' => $creditMemo->getBaseShippingTaxAmount(), - 'adjustment' => $creditMemo->getAdjustment(), + 'adjustment' => $creditMemo->getBaseAdjustment(), 'currency' => $creditMemo->getOrderCurrencyCode(), //TODO: Need to be implemented //'refundTaxes' => $creditMemo->getTaxAmount() 'subtotal' => $creditMemo->getBaseSubtotal(), 'productsTaxAmount' => $creditMemo->getBaseTaxAmount(), 'commerceCreditMemoNumber' => $creditMemo->getIncrementId(), - 'grandTotal' => $creditMemo->getBaseGrandTotal() + 'grandTotal' => $creditMemo->getBaseGrandTotal(), + 'refundItems' => $creditMemoItems, + 'creditMemoComments' => $this->extractComments($creditMemo->getComments()) ]; - foreach ($creditMemo->getItems() as $creditmemoItem) { - $creditMemoItemId = $creditmemoItem->getOrderItemId(); - $itemUuid = $this->uuidResource->getAssignedIds( - [$creditMemoItemId], - 'order_item' - )[$creditMemoItemId]; - $creditMemoItems[$itemUuid] = [ - 'orderItemId' => ['id' => $itemUuid], - 'qtyRefunded' => $creditmemoItem->getQty(), - 'basePrice' => $creditmemoItem->getBasePrice() . 1, - //TODO: Need to be implement - //'baseRowTotal' => $creditmemoItem->getBaseRowTotal(), - //TODO: Need to be implemented - //'productTaxes' => '' - ]; - } - $creditMemos[$creditMemoUuid]['refundItems'] = $creditMemoItems; } - return $creditMemos; + return empty($creditMemos) ? null : $creditMemos; + } + + private function extractComments(array $comments): ?array + { + $commentValues = array_map( + function ($comment) { + return $comment->getComment(); + }, + $comments + ); + return $commentValues ? array_values($commentValues) : null; } /** * @param OrderInterface $order - * @return array + * @return array|null */ - private function getExpectedShipmentData(OrderInterface $order): array + private function getExpectedShipmentData(OrderInterface $order): ?array { $shipments = []; @@ -759,35 +581,34 @@ private function getExpectedShipmentData(OrderInterface $order): array $shipmentId = $orderShipment->getId(); $shipmentUuid = $this->uuidResource->getAssignedIds([$shipmentId], 'order_shipment')[$shipmentId]; - $shipments[$shipmentUuid] = [ - 'shipmentId' => ['id' => $shipmentUuid], - 'createdAt' => $this->convertDate($orderShipment->getCreatedAt()), - 'updatedAt' => $this->convertDate($orderShipment->getUpdatedAt()), - 'commerceShipmentNumber' => $orderShipment->getIncrementId() - ]; + foreach ($orderShipment->getItems() as $shipmentItem) { - $shippingOrderItemId = $shipmentItem->getOrderItemId(); - $itemUuid = $this->uuidResource->getAssignedIds( - [$shippingOrderItemId], - 'order_item' - )[$shippingOrderItemId]; - $shipmentItems[$itemUuid] = [ + $itemId = $shipmentItem->getOrderItemId(); + $itemUuid = $this->uuidResource->getAssignedIds([$itemId], 'order_item')[$itemId]; + $shipmentItems[] = [ 'orderItemId' => ['id' => $itemUuid], 'qtyShipped' => $shipmentItem->getQty() ]; } + /** @var ShipmentTrackInterface $shipmentTrack */ foreach ($orderShipment->getTracks() as $shipmentTrack) { - $shipmentTrackItems[$shipmentTrack->getEntityId()] = [ + $shipmentTrackItems[] = [ 'qty' => $shipmentTrack->getQty() ]; } - $shipments[$shipmentUuid]['trackingInfo'] = $shipmentTrackItems; - $shipments[$shipmentUuid]['items'] = $shipmentItems; + $shipments[] = [ + 'shipmentId' => ['id' => $shipmentUuid], + 'createdAt' => $this->convertDate($orderShipment->getCreatedAt()), + 'updatedAt' => $this->convertDate($orderShipment->getUpdatedAt()), + 'commerceShipmentNumber' => $orderShipment->getIncrementId(), + 'trackingInfo' => $shipmentTrackItems, + 'items' => $shipmentItems + ]; } - return $shipments; + return empty($shipments) ? null : $shipments; } /** @@ -806,7 +627,7 @@ private function getExpectedItemAdditionalInformationData(OrderItemInterface $or } } - return $additionalInformation; + return empty($additionalInformation) ? null : $additionalInformation; } /** @@ -824,6 +645,11 @@ private function getExpectedOrderAdditionalInformationData(OrderInterface $order ]; } } - return $additionalInformation; + return empty($additionalInformation) ? null : $additionalInformation; + } + + private function convertIntToBool($value): ?bool + { + return $value !== null ? (bool) $value : null; } } diff --git a/SalesOrdersDataExporter/Test/_files/order_configurable_product.php b/SalesOrdersDataExporter/Test/_files/order_configurable_product.php index 1715d67f..3d857d3a 100644 --- a/SalesOrdersDataExporter/Test/_files/order_configurable_product.php +++ b/SalesOrdersDataExporter/Test/_files/order_configurable_product.php @@ -7,8 +7,6 @@ use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Sales\Model\Order; -use Magento\Sales\Model\Order\Address as OrderAddress; -use Magento\Sales\Model\Order\Item as OrderItem; use Magento\Sales\Model\Order\Payment; use Magento\Store\Model\StoreManagerInterface; use Magento\TestFramework\Workaround\Override\Fixture\Resolver; @@ -44,7 +42,7 @@ ); $qtyOrdered = 2; -$productOptions = array('color' => 'red', 'size' => 'medium'); +$productOptions = ['color' => 'red', 'size' => 'medium']; /** @var \Magento\Sales\Model\Order\Item $orderItem */ $orderConfigurableItem = $objectManager->create(\Magento\Sales\Model\Order\Item::class); $orderConfigurableItem->setProductId($configurableProduct->getId())->setQtyOrdered($qtyOrdered); diff --git a/SalesOrdersDataExporter/Test/_files/order_full_work_flow.php b/SalesOrdersDataExporter/Test/_files/order_full_work_flow.php index 9c14aea3..dddedbb6 100644 --- a/SalesOrdersDataExporter/Test/_files/order_full_work_flow.php +++ b/SalesOrdersDataExporter/Test/_files/order_full_work_flow.php @@ -13,37 +13,20 @@ /** @var \Magento\Sales\Model\Order $order */ $order = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Sales\Model\Order::class); $order->loadByIncrementId('100000001'); -$order->setData( - 'base_to_global_rate', - 2.1 -)->setData( - 'base_shipping_amount', - 20.1 -)->setData( - 'base_shipping_canceled', - 2.1 -)->setData( - 'base_shipping_invoiced', - 20.1 -)->setData( - 'base_shipping_refunded', - 3.1 -)->setData( - 'is_virtual', - 0 -)->setData( - 'shipping_method', - 'flatrate_flatrate' -)->setData( - 'shipping_description', - 'flatrate description' -)->setData( - 'shipping_amount', - 1.5 -)->setData( - 'shipping_tax_amount', - 0.5 -)->save(); +$order->setData('base_to_global_rate', 2.1) + ->setData('base_shipping_amount', 20.1) + ->setData('base_shipping_canceled', 2.1) + ->setData('base_shipping_invoiced', 20.1) + ->setData('base_shipping_refunded', 3.1) + ->setData('is_virtual', 0) + ->setData('shipping_method', 'flatrate_flatrate') + ->setData('shipping_description', 'flatrate description') + ->setData('shipping_amount', 1.5) + ->setBaseShippingTaxAmount(2) + ->setBaseTaxAmount(3) + ->setBaseDiscountAmount(5) + ->setBaseTotalInvoiced(30) + ->save(); $orderItems = $order->getItems(); /** @var \Magento\Sales\Api\Data\OrderItemInterface $orderItem */ @@ -53,6 +36,7 @@ $invoiceItem = $objectManager->create(\Magento\Sales\Api\Data\InvoiceItemCreationInterface::class); $invoiceItem->setOrderItemId($orderItem->getItemId()); $invoiceItem->setQty($orderItem->getQtyOrdered()); + /** @var \Magento\Sales\Api\InvoiceOrderInterface $invoiceOrder */ $invoiceOrder = $objectManager->create(\Magento\Sales\Api\InvoiceOrderInterface::class); $invoiceOrder->execute($order->getEntityId(), false, [$invoiceItem]); @@ -61,6 +45,7 @@ $shipmentItem = $objectManager->create(\Magento\Sales\Api\Data\ShipmentItemCreationInterface::class); $shipmentItem->setOrderItemId($orderItem->getItemId()); $shipmentItem->setQty($orderItem->getQtyOrdered()); + /** @var \Magento\Sales\Api\ShipOrderInterface $shipOrder */ $shipOrder = $objectManager->create(\Magento\Sales\Api\ShipOrderInterface::class); $shipOrder->execute($order->getEntityId(), [$shipmentItem]); diff --git a/SalesOrdersDataExporter/Test/_files/order_with_additional_information.php b/SalesOrdersDataExporter/Test/_files/order_with_additional_information.php index 841206b0..b436d8f7 100644 --- a/SalesOrdersDataExporter/Test/_files/order_with_additional_information.php +++ b/SalesOrdersDataExporter/Test/_files/order_with_additional_information.php @@ -10,7 +10,6 @@ use Magento\Sales\Model\Order\Address as OrderAddress; use Magento\Sales\Model\Order\Item as OrderItem; use Magento\Sales\Model\Order\Payment; -use Magento\Sales\Model\Order\Payment\Transaction; use Magento\Store\Model\StoreManagerInterface; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\Workaround\Override\Fixture\Resolver; diff --git a/SalesOrdersDataExporter/Test/_files/order_with_additional_information_rollback.php b/SalesOrdersDataExporter/Test/_files/order_with_additional_information_rollback.php index 512c6384..cd9a7a3a 100644 --- a/SalesOrdersDataExporter/Test/_files/order_with_additional_information_rollback.php +++ b/SalesOrdersDataExporter/Test/_files/order_with_additional_information_rollback.php @@ -7,4 +7,4 @@ use Magento\TestFramework\Workaround\Override\Fixture\Resolver; Resolver::getInstance()->requireDataFixture('Magento/Catalog/_files/product_simple.php'); -Resolver::getInstance()->requireDataFixture('Magento/Sales/_files/order_rollback.php'); \ No newline at end of file +Resolver::getInstance()->requireDataFixture('Magento/Sales/_files/order_rollback.php'); diff --git a/SalesOrdersDataExporter/Test/_files/order_with_invoice_shipment_creditmemo.php b/SalesOrdersDataExporter/Test/_files/order_with_invoice_shipment_creditmemo.php index 4d062dc4..a117d944 100644 --- a/SalesOrdersDataExporter/Test/_files/order_with_invoice_shipment_creditmemo.php +++ b/SalesOrdersDataExporter/Test/_files/order_with_invoice_shipment_creditmemo.php @@ -16,9 +16,9 @@ use Magento\Sales\Api\Data\OrderInterface; use Magento\Sales\Api\Data\OrderInterfaceFactory; use Magento\Sales\Api\Data\OrderItemInterface; +use Magento\Sales\Api\Data\OrderItemInterfaceFactory; use Magento\Sales\Api\Data\OrderPaymentInterfaceFactory; use Magento\Sales\Api\InvoiceManagementInterface; -use Magento\Sales\Api\Data\OrderItemInterfaceFactory; use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\Address; @@ -142,12 +142,15 @@ $orderData['items'][$item->getId()] = $item; } $order->setItems($orderData['items']); + $creditmemo = $creditmemoFactory->createByOrder($order, $orderData); $creditmemo->setBaseShippingAmount(3.14); $creditmemo->setBaseShippingTaxAmount(1.14); $creditmemo->setAdjustment(2.77); $creditmemo->setBaseTaxAmount(1.5); $creditmemo->setIncrementId($order->getIncrementId()); +$creditmemo->addComment("note"); +$creditmemo->addComment("note2"); $creditItem = $creditmemoItemFactory->create(); $creditItem->setCreditmemo($creditmemo) diff --git a/SalesOrdersDataExporter/Test/_files/transactions_detailed_rollback.php b/SalesOrdersDataExporter/Test/_files/transactions_detailed_rollback.php index 512c6384..cd9a7a3a 100644 --- a/SalesOrdersDataExporter/Test/_files/transactions_detailed_rollback.php +++ b/SalesOrdersDataExporter/Test/_files/transactions_detailed_rollback.php @@ -7,4 +7,4 @@ use Magento\TestFramework\Workaround\Override\Fixture\Resolver; Resolver::getInstance()->requireDataFixture('Magento/Catalog/_files/product_simple.php'); -Resolver::getInstance()->requireDataFixture('Magento/Sales/_files/order_rollback.php'); \ No newline at end of file +Resolver::getInstance()->requireDataFixture('Magento/Sales/_files/order_rollback.php'); diff --git a/SalesOrdersDataExporter/etc/et_schema.xml b/SalesOrdersDataExporter/etc/et_schema.xml index 994eb285..dd0bc3b8 100644 --- a/SalesOrdersDataExporter/etc/et_schema.xml +++ b/SalesOrdersDataExporter/etc/et_schema.xml @@ -214,7 +214,10 @@ provider="Magento\DataExporter\Model\Provider\DateConverter"> - + + + diff --git a/SalesOrdersDataExporter/etc/events.xml b/SalesOrdersDataExporter/etc/events.xml index 047ec890..a153d834 100644 --- a/SalesOrdersDataExporter/etc/events.xml +++ b/SalesOrdersDataExporter/etc/events.xml @@ -7,10 +7,10 @@ --> - + - + diff --git a/SalesOrdersDataExporter/etc/query.xml b/SalesOrdersDataExporter/etc/query.xml index 05153992..29d0f9d4 100644 --- a/SalesOrdersDataExporter/etc/query.xml +++ b/SalesOrdersDataExporter/etc/query.xml @@ -245,14 +245,14 @@ - + commerceOrderId - + entity_id diff --git a/dev/tests/integration/testsuite/Magento/CatalogRule/Model/Indexer/IndexerBuilderTest.php b/dev/tests/integration/testsuite/Magento/CatalogRule/Model/Indexer/IndexerBuilderTest.php new file mode 100644 index 00000000..85d1b69c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogRule/Model/Indexer/IndexerBuilderTest.php @@ -0,0 +1,243 @@ +indexerBuilder = Bootstrap::getObjectManager()->get( + \Magento\CatalogRule\Model\Indexer\IndexBuilder::class + ); + $this->resourceRule = Bootstrap::getObjectManager()->get(\Magento\CatalogRule\Model\ResourceModel\Rule::class); + $this->product = Bootstrap::getObjectManager()->get(\Magento\Catalog\Model\Product::class); + $this->storeManager = Bootstrap::getObjectManager()->get(StoreManagerInterface::class); + $this->productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class); + $this->connection = Bootstrap::getObjectManager()->get(ResourceConnection::class); + $this->indexProductProcessor = Bootstrap::getObjectManager()->get(Processor::class); + } + + protected function tearDown(): void + { + /** @var \Magento\Framework\Registry $registry */ + $registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\Framework\Registry::class); + + $registry->unregister('isSecureArea'); + $registry->register('isSecureArea', true); + + /** @var \Magento\Catalog\Model\ResourceModel\Product\Collection $productCollection */ + $productCollection = Bootstrap::getObjectManager()->get( + \Magento\Catalog\Model\ResourceModel\Product\Collection::class + ); + $productCollection->delete(); + + $registry->unregister('isSecureArea'); + $registry->register('isSecureArea', false); + + parent::tearDown(); + } + + /** + * @magentoDbIsolation disabled + * @magentoAppIsolation enabled + * @magentoDataFixture Magento/CatalogRule/_files/attribute.php + * @magentoDataFixture Magento/CatalogRule/_files/rule_by_attribute.php + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testReindexById() + { + $product = $this->product->loadByAttribute('sku', 'simple'); + $product->load($product->getId()); + $product->setData('test_attribute', 'test_attribute_value')->save(); + + $this->indexerBuilder->reindexById($product->getId()); + + $this->assertEquals(9.8, $this->resourceRule->getRulePrice(new \DateTime(), 1, 1, $product->getId())); + } + + /** + * @magentoDbIsolation disabled + * @magentoAppIsolation enabled + * @magentoDataFixture Magento/CatalogRule/_files/simple_product_with_catalog_rule_50_percent_off_tomorrow.php + * @magentoConfigFixture base_website general/locale/timezone Europe/Amsterdam + * @magentoConfigFixture general/locale/timezone America/Chicago + */ + public function testReindexByIdDifferentTimezones() + { + $productId = $this->productRepository->get('simple')->getId(); + $this->indexerBuilder->reindexById($productId); + + $mainWebsiteId = $this->storeManager->getWebsite('base')->getId(); + $secondWebsiteId = $this->storeManager->getWebsite('test')->getId(); + $rawTimestamp = (new \DateTime('+1 day'))->getTimestamp(); + $timestamp = $rawTimestamp - ($rawTimestamp % (60 * 60 * 24)); + $mainWebsiteActiveRules = + $this->resourceRule->getRulesFromProduct($timestamp, $mainWebsiteId, 1, $productId); + $secondWebsiteActiveRules = + $this->resourceRule->getRulesFromProduct($timestamp, $secondWebsiteId, 1, $productId); + + $this->assertCount(1, $mainWebsiteActiveRules); + // Avoid failure when staging is enabled as it removes catalog rule timestamp. + if ((int)$mainWebsiteActiveRules[0]['from_time'] !== 0) { + $this->assertCount(0, $secondWebsiteActiveRules); + } + } + + /** + * @magentoDbIsolation disabled + * @magentoAppIsolation enabled + * @magentoDataFixture Magento/CatalogRule/_files/attribute.php + * @magentoDataFixture Magento/CatalogRule/_files/rule_by_attribute.php + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testReindexByIds() + { + $this->prepareProducts(); + + $this->indexerBuilder->reindexByIds( + [ + $this->product->getId(), + $this->productSecond->getId(), + $this->productThird->getId(), + ] + ); + + $this->assertEquals(9.8, $this->resourceRule->getRulePrice(new \DateTime(), 1, 1, $this->product->getId())); + $this->assertEquals( + 9.8, + $this->resourceRule->getRulePrice(new \DateTime(), 1, 1, $this->productSecond->getId()) + ); + $this->assertFalse($this->resourceRule->getRulePrice(new \DateTime(), 1, 1, $this->productThird->getId())); + } + + /** + * @magentoDbIsolation disabled + * @magentoAppIsolation enabled + * @magentoDataFixtureBeforeTransaction Magento/CatalogRule/_files/attribute.php + * @magentoDataFixtureBeforeTransaction Magento/CatalogRule/_files/rule_by_attribute.php + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testReindexFull() + { + $this->prepareProducts(); + + $this->indexerBuilder->reindexFull(); + + $rulePrice = $this->resourceRule->getRulePrice(new \DateTime(), 1, 1, $this->product->getId()); + $this->assertEquals(9.8, $rulePrice); + $rulePrice = $this->resourceRule->getRulePrice(new \DateTime(), 1, 1, $this->productSecond->getId()); + $this->assertEquals(9.8, $rulePrice); + $this->assertFalse($this->resourceRule->getRulePrice(new \DateTime(), 1, 1, $this->productThird->getId())); + } + + /** + * Tests restoring triggers on `catalogrule_product_price` table after full reindexing in 'Update by schedule' mode. + * + * @magentoDbIsolation disabled + * @magentoAppIsolation enabled + */ + public function testRestoringTriggersAfterFullReindex() + { + $this->markTestSkipped('Skip this test as it fails with SaaS-Export extension MDEE-389'); + $tableName = $this->connection->getTableName('catalogrule_product_price'); + + $this->indexProductProcessor->getIndexer()->setScheduled(false); + $this->assertEquals(0, $this->getTriggersCount($tableName)); + + $this->indexProductProcessor->getIndexer()->setScheduled(true); + $this->assertGreaterThan(0, $this->getTriggersCount($tableName)); + + $this->indexerBuilder->reindexFull(); + $this->assertGreaterThan(0, $this->getTriggersCount($tableName)); + + $this->indexProductProcessor->getIndexer()->setScheduled(false); + $this->assertEquals(0, $this->getTriggersCount($tableName)); + } + + /** + * Returns triggers count. + * + * @param string $tableName + * @return int + * @throws \Zend_Db_Statement_Exception + */ + private function getTriggersCount(string $tableName): int + { + return count( + $this->connection->getConnection() + ->query('SHOW TRIGGERS LIKE \''. $tableName . '\'') + ->fetchAll() + ); + } + + protected function prepareProducts() + { + $product = $this->product->loadByAttribute('sku', 'simple'); + $product->load($product->getId()); + $this->product = $product; + + $this->product->setStoreId(0)->setData('test_attribute', 'test_attribute_value')->save(); + $this->productSecond = clone $this->product; + $this->productSecond->setId(null)->setUrlKey('product-second')->save(); + $this->productThird = clone $this->product; + $this->productThird->setId(null) + ->setUrlKey('product-third') + ->setData('test_attribute', 'NO_test_attribute_value') + ->save(); + } +}