From 780796202bc6ae0cdd4851d0863d8389a84a6f57 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Fri, 6 Sep 2024 13:08:09 -0400 Subject: [PATCH 01/40] MAGE-1023 Fix landing page typing --- Block/Algolia.php | 2 +- Helper/LandingPageHelper.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Block/Algolia.php b/Block/Algolia.php index 2ee70c0ee..518008904 100755 --- a/Block/Algolia.php +++ b/Block/Algolia.php @@ -215,7 +215,7 @@ protected function getAddToCartUrl($additional = []): string return $this->_urlBuilder->getUrl('checkout/cart/add', $routeParams); } - protected function getCurrentLandingPage(): LandingPage|null|false + protected function getCurrentLandingPage(): \Algolia\AlgoliaSearch\Model\LandingPage|null|false { $landingPageId = $this->getRequest()->getParam('landing_page_id'); if (!$landingPageId) { diff --git a/Helper/LandingPageHelper.php b/Helper/LandingPageHelper.php index e26810c37..954298e85 100644 --- a/Helper/LandingPageHelper.php +++ b/Helper/LandingPageHelper.php @@ -59,7 +59,7 @@ public function __construct( parent::__construct($context); } - public function getLandingPage($pageId) + public function getLandingPage($pageId): LandingPage|null|false { if ($pageId !== null && $pageId !== $this->landingPage->getId()) { $this->landingPage->setStoreId($this->storeManager->getStore()->getId()); From 86afd11b3c2e07b8a3735ded78d6b61745df5656 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Mon, 9 Sep 2024 14:09:48 -0400 Subject: [PATCH 02/40] MAGE-1023 Use alias to clearly distinguish type --- Block/Algolia.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Block/Algolia.php b/Block/Algolia.php index 518008904..528626603 100755 --- a/Block/Algolia.php +++ b/Block/Algolia.php @@ -10,6 +10,7 @@ use Algolia\AlgoliaSearch\Helper\Entity\ProductHelper; use Algolia\AlgoliaSearch\Helper\Entity\SuggestionHelper; use Algolia\AlgoliaSearch\Helper\LandingPageHelper; +use Algolia\AlgoliaSearch\Model\LandingPage as LandingPageModel; use Algolia\AlgoliaSearch\Registry\CurrentCategory; use Algolia\AlgoliaSearch\Registry\CurrentProduct; use Algolia\AlgoliaSearch\Service\Product\SortingTransformer; @@ -215,7 +216,7 @@ protected function getAddToCartUrl($additional = []): string return $this->_urlBuilder->getUrl('checkout/cart/add', $routeParams); } - protected function getCurrentLandingPage(): \Algolia\AlgoliaSearch\Model\LandingPage|null|false + protected function getCurrentLandingPage(): LandingPageModel|null|false { $landingPageId = $this->getRequest()->getParam('landing_page_id'); if (!$landingPageId) { From 84757bf12482d20e74fde6dc243bb00c0cd29320 Mon Sep 17 00:00:00 2001 From: Damien Couchez Date: Thu, 12 Sep 2024 15:30:50 +0200 Subject: [PATCH 03/40] Fixing get category collection when two root categories start with the same number (#1582) (#1613) Co-authored-by: Igor Figueiredo <63747236+igorfigueiredogen@users.noreply.github.com> --- Helper/Entity/CategoryHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Helper/Entity/CategoryHelper.php b/Helper/Entity/CategoryHelper.php index e0b88854e..fc4b6463a 100755 --- a/Helper/Entity/CategoryHelper.php +++ b/Helper/Entity/CategoryHelper.php @@ -125,7 +125,7 @@ public function getCategoryCollectionQuery($storeId, $categoryIds = null) { /** @var \Magento\Store\Model\Store $store */ $store = $this->storeManager->getStore($storeId); - $storeRootCategoryPath = sprintf('%d/%d', $this->getRootCategoryId(), $store->getRootCategoryId()); + $storeRootCategoryPath = sprintf('%d/%d/', $this->getRootCategoryId(), $store->getRootCategoryId()); $unserializedCategorysAttrs = $this->getAdditionalAttributes($storeId); $additionalAttr = array_column($unserializedCategorysAttrs, 'attribute'); From e581624edbe14344249ef0db4e7302f1a2c62d69 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Tue, 24 Sep 2024 15:55:45 -0400 Subject: [PATCH 04/40] MAGE-1066 Fix unexpected null type --- Helper/ConfigHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Helper/ConfigHelper.php b/Helper/ConfigHelper.php index f13192d98..f1523bb0d 100755 --- a/Helper/ConfigHelper.php +++ b/Helper/ConfigHelper.php @@ -1242,7 +1242,7 @@ public function getSearchOnlyAPIKey($storeId = null) */ public function getIndexPrefix(int $storeId = null): string { - return $this->configInterface->getValue(self::INDEX_PREFIX, ScopeInterface::SCOPE_STORE, $storeId); + return (string) $this->configInterface->getValue(self::INDEX_PREFIX, ScopeInterface::SCOPE_STORE, $storeId); } /** From 7391d9ab2ddeedd32798a606bb0ab3d026cda2a9 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Tue, 24 Sep 2024 15:56:23 -0400 Subject: [PATCH 05/40] MAGE-1066 Add unit test --- Test/Unit/ConfigHelperTest.php | 82 ++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 Test/Unit/ConfigHelperTest.php diff --git a/Test/Unit/ConfigHelperTest.php b/Test/Unit/ConfigHelperTest.php new file mode 100644 index 000000000..24bd0522f --- /dev/null +++ b/Test/Unit/ConfigHelperTest.php @@ -0,0 +1,82 @@ +configInterface = $this->createMock(ScopeConfigInterface::class); + $this->configWriter = $this->createMock(WriterInterface::class); + $this->storeManager = $this->createMock(StoreManagerInterface::class); + $this->currency = $this->createMock(Currency::class); + $this->dirCurrency = $this->createMock(DirCurrency::class); + $this->directoryList = $this->createMock(DirectoryList::class); + $this->moduleResource = $this->createMock(ResourceInterface::class); + $this->productMetadata = $this->createMock(ProductMetadataInterface::class); + $this->eventManager = $this->createMock(ManagerInterface::class); + $this->serializer = $this->createMock(SerializerInterface::class); + $this->groupCollection = $this->createMock(GroupCollection::class); + $this->groupExcludedWebsiteRepository = $this->createMock(GroupExcludedWebsiteRepositoryInterface::class); + $this->cookieHelper = $this->createMock(CookieHelper::class); + + $this->configHelper = new ConfigHelper( + $this->configInterface, + $this->configWriter, + $this->storeManager, + $this->currency, + $this->dirCurrency, + $this->directoryList, + $this->moduleResource, + $this->productMetadata, + $this->eventManager, + $this->serializer, + $this->groupCollection, + $this->groupExcludedWebsiteRepository, + $this->cookieHelper + ); + } + + public function testGetIndexPrefix() + { + $testPrefix = 'foo_bar_'; + $this->configInterface->method('getValue')->willReturn($testPrefix); + $this->assertEquals($testPrefix, $this->configHelper->getIndexPrefix()); + } + + public function testGetIndexPrefixWhenNull() { + $this->configInterface->method('getValue')->willReturn(null); + $this->assertEquals('', $this->configHelper->getIndexPrefix()); + } +} From b8b98c98ba5a323cb7276f8e9181d6c0451bfa17 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Tue, 24 Sep 2024 16:38:26 -0400 Subject: [PATCH 06/40] MAGE-1066 Handle serialize failure to ensure proper data type is returned --- Helper/ConfigHelper.php | 2 +- Test/Unit/ConfigHelperTest.php | 31 +++++++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/Helper/ConfigHelper.php b/Helper/ConfigHelper.php index f1523bb0d..ae87512a0 100755 --- a/Helper/ConfigHelper.php +++ b/Helper/ConfigHelper.php @@ -446,7 +446,7 @@ public function getAutocompleteSections($storeId = null) } protected function serialize(array $value): string { - return $this->serializer->serialize($value); + return $this->serializer->serialize($value) ?: ''; } /** diff --git a/Test/Unit/ConfigHelperTest.php b/Test/Unit/ConfigHelperTest.php index 24bd0522f..0b980d118 100644 --- a/Test/Unit/ConfigHelperTest.php +++ b/Test/Unit/ConfigHelperTest.php @@ -18,9 +18,18 @@ use Magento\Store\Model\StoreManagerInterface; use PHPUnit\Framework\TestCase; +class ConfigHelperTestable extends ConfigHelper +{ + /** expose protected methods for unit testing */ + public function serialize(array $value): string + { + return parent::serialize($value); + } +} + class ConfigHelperTest extends TestCase { - protected ConfigHelper $configHelper; + protected ConfigHelperTestable $configHelper; protected ScopeConfigInterface $configInterface; protected WriterInterface $configWriter; protected StoreManagerInterface $storeManager; @@ -51,7 +60,7 @@ public function setUp(): void $this->groupExcludedWebsiteRepository = $this->createMock(GroupExcludedWebsiteRepositoryInterface::class); $this->cookieHelper = $this->createMock(CookieHelper::class); - $this->configHelper = new ConfigHelper( + $this->configHelper = new ConfigHelperTestable( $this->configInterface, $this->configWriter, $this->storeManager, @@ -79,4 +88,22 @@ public function testGetIndexPrefixWhenNull() { $this->configInterface->method('getValue')->willReturn(null); $this->assertEquals('', $this->configHelper->getIndexPrefix()); } + + public function testSerializerReturnsString() { + $this->serializer->method('serialize')->willReturn('{"foo":"bar"}'); + $array = [ + 'foo' => 'bar' + ]; + $result = $this->configHelper->serialize($array); + $this->assertEquals('{"foo":"bar"}', $result); + } + + public function testSerializerFailure() { + $this->serializer->method('serialize')->willReturn(false); + $array = [ + 'foo' => 'bar' + ]; + $result = $this->configHelper->serialize($array); + $this->assertEquals('', $result); + } } From 9ef48b8732a62521fdeabf90a875fb1375f6a4e2 Mon Sep 17 00:00:00 2001 From: Damien Couchez Date: Wed, 25 Sep 2024 15:55:30 +0200 Subject: [PATCH 07/40] MAGE-1055: Fix current integration tests (#1620) * MAGE-1055: change values alignment and config paths * MAGE-1055: fix flakyness * MAGE-1055: add more waiting in saveConfigurationToAlgolia --- Model/IndicesConfigurator.php | 8 ++ Test/Integration/AssertValues/Magento23.php | 15 --- .../{Magento24.php => Magento246.php} | 2 +- Test/Integration/AssertValues/Magento247.php | 15 +++ Test/Integration/ConfigTest.php | 8 +- Test/Integration/ProductsIndexingTest.php | 96 +++++++++---------- Test/Integration/QueueTest.php | 2 +- Test/Integration/TestCase.php | 12 +-- 8 files changed, 82 insertions(+), 76 deletions(-) delete mode 100644 Test/Integration/AssertValues/Magento23.php rename Test/Integration/AssertValues/{Magento24.php => Magento246.php} (95%) create mode 100644 Test/Integration/AssertValues/Magento247.php diff --git a/Model/IndicesConfigurator.php b/Model/IndicesConfigurator.php index 8d30aeb90..8179adfca 100644 --- a/Model/IndicesConfigurator.php +++ b/Model/IndicesConfigurator.php @@ -104,9 +104,12 @@ public function saveConfigurationToAlgolia(int $storeId, bool $useTmpIndex = fal } $this->setCategoriesSettings($storeId); + $this->algoliaHelper->waitLastTask(); + /* heck if we want to index CMS pages */ if ($this->configHelper->isPagesIndexEnabled($storeId)) { $this->setPagesSettings($storeId); + $this->algoliaHelper->waitLastTask(); } else { $this->logger->log('CMS Page Indexing is not enabled for the store.'); } @@ -114,14 +117,19 @@ public function saveConfigurationToAlgolia(int $storeId, bool $useTmpIndex = fal //Check if we want to index Query Suggestions if ($this->configHelper->isQuerySuggestionsIndexEnabled($storeId)) { $this->setQuerySuggestionsSettings($storeId); + $this->algoliaHelper->waitLastTask(); } else { $this->logger->log('Query Suggestions Indexing is not enabled for the store.'); } $this->setAdditionalSectionsSettings($storeId); + $this->algoliaHelper->waitLastTask(); + $this->setProductsSettings($storeId, $useTmpIndex); + $this->algoliaHelper->waitLastTask(); $this->setExtraSettings($storeId, $useTmpIndex); + $this->algoliaHelper->waitLastTask(); } /** diff --git a/Test/Integration/AssertValues/Magento23.php b/Test/Integration/AssertValues/Magento23.php deleted file mode 100644 index 1f262575f..000000000 --- a/Test/Integration/AssertValues/Magento23.php +++ /dev/null @@ -1,15 +0,0 @@ -setConfig('algoliasearch_instant/instant/facets', $this->getSerializer()->serialize($facets)); + $this->setConfig('algoliasearch_instant/instant_facets/facets', $this->getSerializer()->serialize($facets)); // Set don't replace category pages with Algolia - categories attribute shouldn't be included in facets $this->setConfig('algoliasearch_instant/instant/replace_categories', '0'); @@ -179,7 +179,7 @@ private function replicaCreationTest($withCustomerGroups = false) ]; $this->setConfig('algoliasearch_instant/instant/is_instant_enabled', '1'); // Needed to set replicas to Algolia - $this->setConfig('algoliasearch_instant/instant/sorts', $this->getSerializer()->serialize($sortingIndicesData)); + $this->setConfig('algoliasearch_instant/instant_sorts/sorts', $this->getSerializer()->serialize($sortingIndicesData)); $this->setConfig('algoliasearch_advanced/advanced/customer_groups_enable', $enableCustomGroups); $sortingIndicesWithRankingWhichShouldBeCreated = [ @@ -221,13 +221,11 @@ public function testExtraSettings() $indexName = $this->indexPrefix . 'default_' . $section; $this->algoliaHelper->setSettings($indexName, ['exactOnSingleWordQuery' => 'attribute']); + $this->algoliaHelper->waitLastTask(); } - $this->algoliaHelper->waitLastTask(); - foreach ($sections as $section) { $indexName = $this->indexPrefix . 'default_' . $section; - $currentSettings = $this->algoliaHelper->getSettings($indexName); $this->assertArrayHasKey('exactOnSingleWordQuery', $currentSettings); diff --git a/Test/Integration/ProductsIndexingTest.php b/Test/Integration/ProductsIndexingTest.php index 64c315403..18147ae85 100644 --- a/Test/Integration/ProductsIndexingTest.php +++ b/Test/Integration/ProductsIndexingTest.php @@ -33,54 +33,6 @@ public function testIncludingOutOfStock() $this->processTest($indexer, 'products', $this->assertValues->productsOutOfStockCount); } - public function testDefaultIndexableAttributes() - { - $empty = $this->getSerializer()->serialize([]); - - $this->setConfig('algoliasearch_products/products/product_additional_attributes', $empty); - $this->setConfig('algoliasearch_instant/instant/facets', $empty); - $this->setConfig('algoliasearch_instant/instant/sorts', $empty); - $this->setConfig('algoliasearch_products/products/custom_ranking_product_attributes', $empty); - - /** @var Product $indexer */ - $indexer = $this->getObjectManager()->create(Product::class); - $indexer->executeRow($this->getValidTestProduct()); - - $this->algoliaHelper->waitLastTask(); - - $results = $this->algoliaHelper->getObjects($this->indexPrefix . 'default_products', [$this->getValidTestProduct()]); - $hit = reset($results['results']); - - $defaultAttributes = [ - 'objectID', - 'name', - 'url', - 'visibility_search', - 'visibility_catalog', - 'categories', - 'categories_without_path', - 'thumbnail_url', - 'image_url', - 'in_stock', - 'price', - 'type_id', - 'algoliaLastUpdateAtCET', - 'categoryIds', - ]; - - if (!$hit) { - $this->markTestIncomplete('Hit was not returned correctly from Algolia. No Hit to run assetions on.'); - } - - foreach ($defaultAttributes as $key => $attribute) { - $this->assertArrayHasKey($attribute, $hit, 'Products attribute "' . $attribute . '" should be indexed but it is not"'); - unset($hit[$attribute]); - } - - $extraAttributes = implode(', ', array_keys($hit)); - $this->assertEmpty($hit, 'Extra products attributes (' . $extraAttributes . ') are indexed and should not be.'); - } - public function testNoSpecialPrice() { /** @var Product $indexer */ @@ -139,6 +91,54 @@ public function deprecatedTestSpecialPrice() $this->assertEquals($to, $algoliaProduct['price']['USD']['special_to_date']); } + public function testDefaultIndexableAttributes() + { + $empty = $this->getSerializer()->serialize([]); + + $this->setConfig('algoliasearch_products/products/product_additional_attributes', $empty); + $this->setConfig('algoliasearch_instant/instant_facets/facets', $empty); + $this->setConfig('algoliasearch_instant/instant_sorts/sorts', $empty); + $this->setConfig('algoliasearch_products/products/custom_ranking_product_attributes', $empty); + + /** @var Product $indexer */ + $indexer = $this->getObjectManager()->create(Product::class); + $indexer->executeRow($this->getValidTestProduct()); + + $this->algoliaHelper->waitLastTask(); + + $results = $this->algoliaHelper->getObjects($this->indexPrefix . 'default_products', [$this->getValidTestProduct()]); + $hit = reset($results['results']); + + $defaultAttributes = [ + 'objectID', + 'name', + 'url', + 'visibility_search', + 'visibility_catalog', + 'categories', + 'categories_without_path', + 'thumbnail_url', + 'image_url', + 'in_stock', + 'price', + 'type_id', + 'algoliaLastUpdateAtCET', + 'categoryIds', + ]; + + if (!$hit) { + $this->markTestIncomplete('Hit was not returned correctly from Algolia. No Hit to run assetions on.'); + } + + foreach ($defaultAttributes as $key => $attribute) { + $this->assertArrayHasKey($attribute, $hit, 'Products attribute "' . $attribute . '" should be indexed but it is not"'); + unset($hit[$attribute]); + } + + $extraAttributes = implode(', ', array_keys($hit)); + $this->assertEmpty($hit, 'Extra products attributes (' . $extraAttributes . ') are indexed and should not be.'); + } + private function setOneProductOutOfStock() { /** @var StockRegistry $stockRegistry */ diff --git a/Test/Integration/QueueTest.php b/Test/Integration/QueueTest.php index 2d38fff21..640aae3e0 100644 --- a/Test/Integration/QueueTest.php +++ b/Test/Integration/QueueTest.php @@ -145,7 +145,7 @@ public function testSettings() $this->resetConfigs([ 'algoliasearch_queue/queue/number_of_job_to_run', 'algoliasearch_advanced/queue/number_of_element_by_page', - 'algoliasearch_instant/instant/facets', + 'algoliasearch_instant/instant_facets/facets', 'algoliasearch_products/products/product_additional_attributes', ]); diff --git a/Test/Integration/TestCase.php b/Test/Integration/TestCase.php index f13eb4636..3fb927224 100644 --- a/Test/Integration/TestCase.php +++ b/Test/Integration/TestCase.php @@ -6,8 +6,8 @@ use Algolia\AlgoliaSearch\Helper\AlgoliaHelper; use Algolia\AlgoliaSearch\Helper\ConfigHelper; use Algolia\AlgoliaSearch\Setup\Patch\Schema\ConfigPatch; -use Algolia\AlgoliaSearch\Test\Integration\AssertValues\Magento23; -use Algolia\AlgoliaSearch\Test\Integration\AssertValues\Magento24; +use Algolia\AlgoliaSearch\Test\Integration\AssertValues\Magento246; +use Algolia\AlgoliaSearch\Test\Integration\AssertValues\Magento247; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\ProductMetadataInterface; use Magento\Store\Model\ScopeInterface; @@ -33,7 +33,7 @@ abstract class TestCase extends \TC /** @var ConfigHelper */ protected $configHelper; - /** @var Magento23|Magento24 */ + /** @var Magento246|Magento247 */ protected $assertValues; public function setUp(): void @@ -97,10 +97,10 @@ private function bootstrap() return; } - if (version_compare($this->getMagentoVersion(), '2.4.0', '<')) { - $this->assertValues = new Magento23(); + if (version_compare($this->getMagentoVersion(), '2.4.7', '<')) { + $this->assertValues = new Magento246(); } else { - $this->assertValues = new Magento24(); + $this->assertValues = new Magento247(); } $this->configHelper = $this->getObjectManager()->create(ConfigHelper::class); From 531e86768f93bae9087e57ef4477e2e55d2c2316 Mon Sep 17 00:00:00 2001 From: Damien Couchez Date: Fri, 27 Sep 2024 10:19:33 +0200 Subject: [PATCH 08/40] MAGE-1060: POC for multi-store indexing testing (#1622) * MAGE-1060: POC for multi-store bootstrap * MAGE-1056: adding a asbtract class for multiple stores scenario + starting of the category class * MAGE-1056: cleaning --- Test/Integration/MultiStoreCategoriesTest.php | 34 ++++++++ Test/Integration/MultiStoreConfigTest.php | 37 ++++++++ Test/Integration/MultiStoreTestCase.php | 84 +++++++++++++++++++ Test/Integration/TestCase.php | 9 +- 4 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 Test/Integration/MultiStoreCategoriesTest.php create mode 100644 Test/Integration/MultiStoreConfigTest.php create mode 100644 Test/Integration/MultiStoreTestCase.php diff --git a/Test/Integration/MultiStoreCategoriesTest.php b/Test/Integration/MultiStoreCategoriesTest.php new file mode 100644 index 000000000..48a63a282 --- /dev/null +++ b/Test/Integration/MultiStoreCategoriesTest.php @@ -0,0 +1,34 @@ +categoriesIndexer = $this->getObjectManager()->create(Category::class); + + $this->categoriesIndexer->executeFull(); + $this->algoliaHelper->waitLastTask(); + } + + public function testMultiStoreCategoryIndices() + { + foreach ($this->storeManager->getStores() as $store) { + $this->assertNbOfRecordsPerStore($store->getCode(), 'categories', $this->assertValues->expectedCategory); + } + } +} diff --git a/Test/Integration/MultiStoreConfigTest.php b/Test/Integration/MultiStoreConfigTest.php new file mode 100644 index 000000000..2b8424fb0 --- /dev/null +++ b/Test/Integration/MultiStoreConfigTest.php @@ -0,0 +1,37 @@ +storeManager->getWebsites(); + $stores = $this->storeManager->getStores(); + + // Check that stores and websites are properly created + $this->assertEquals(count($websites), 2); + $this->assertEquals(count($stores), 3); + + foreach ($stores as $store) { + $this->setupStore($store); + } + + $indicesCreatedByTest = 0; + $indices = $this->algoliaHelper->listIndexes(); + + foreach ($indices['items'] as $index) { + $name = $index['name']; + + if (mb_strpos($name, $this->indexPrefix) === 0) { + $indicesCreatedByTest++; + } + } + + // Check that the configuration created the appropriate number of indices (4 per store => 3*4=12) + $this->assertEquals($indicesCreatedByTest, 12); + } +} diff --git a/Test/Integration/MultiStoreTestCase.php b/Test/Integration/MultiStoreTestCase.php new file mode 100644 index 000000000..0d6969404 --- /dev/null +++ b/Test/Integration/MultiStoreTestCase.php @@ -0,0 +1,84 @@ +storeManager = $this->getObjectManager()->create(StoreManager::class); + + /** @var IndicesConfigurator $indicesConfigurator */ + $this->indicesConfigurator = $this->getObjectManager()->create(IndicesConfigurator::class); + + parent::setUp(); + + foreach ($this->storeManager->getStores() as $store) { + $this->setupStore($store); + } + } + + /** + * @param string $storeCode + * @param string $entity + * @param int $expectedNumber + * + * @return void + * @throws AlgoliaException + */ + protected function assertNbOfRecordsPerStore(string $storeCode, string $entity, int $expectedNumber): void + { + $resultsDefault = $this->algoliaHelper->query($this->indexPrefix . $storeCode . '_' . $entity, '', []); + + $this->assertEquals($expectedNumber, $resultsDefault['results'][0]['nbHits']); + } + + /** + * @param StoreInterface $store + * + * @return void + * @throws AlgoliaException + * @throws LocalizedException + * @throws NoSuchEntityException + */ + protected function setupStore(StoreInterface $store): void + { + $this->setConfig( + 'algoliasearch_credentials/credentials/application_id', + getenv('ALGOLIA_APPLICATION_ID'), + $store->getCode() + ); + $this->setConfig( + 'algoliasearch_credentials/credentials/search_only_api_key', + getenv('ALGOLIA_SEARCH_KEY_1') ?: getenv('ALGOLIA_SEARCH_API_KEY'), + $store->getCode() + ); + $this->setConfig( + 'algoliasearch_credentials/credentials/api_key', + getenv('ALGOLIA_API_KEY'), + $store->getCode() + ); + $this->setConfig( + 'algoliasearch_credentials/credentials/index_prefix', + $this->indexPrefix, + $store->getCode() + ); + + $this->indicesConfigurator->saveConfigurationToAlgolia($store->getId()); + } + +} diff --git a/Test/Integration/TestCase.php b/Test/Integration/TestCase.php index 3fb927224..f4f2d6a68 100644 --- a/Test/Integration/TestCase.php +++ b/Test/Integration/TestCase.php @@ -58,13 +58,16 @@ protected function resetConfigs($configs = []) } } - protected function setConfig($path, $value) - { + protected function setConfig( + $path, + $value, + $scopeCode = 'default' + ) { $this->getObjectManager()->get(\Magento\Framework\App\Config\MutableScopeConfigInterface::class)->setValue( $path, $value, ScopeInterface::SCOPE_STORE, - 'default' + $scopeCode ); } From 6564ef3bd5d49fb1ec321e0a4eed6ef4fee1ffd5 Mon Sep 17 00:00:00 2001 From: Damien Couchez Date: Wed, 2 Oct 2024 10:50:56 +0200 Subject: [PATCH 09/40] MAGE-1056: Add MultiStore category tests (#1623) * MAGE-1056: add category tests * MAGE-1056: improved updateCategory() * MAGE-1056: improved assertAlgoliaRecordValues() * MAGE-1056: check category disabled * MAGE-1056: add category config tests * MAGE-1056: reorganize the folder structure * MAGE-1056: update products tests * MAGE-1056: fix products tests * MAGE-1056: update tearDown --- Test/Integration/AssertValues/Magento247.php | 2 +- .../{ => Category}/CategoriesIndexingTest.php | 3 +- .../Category/MultiStoreCategoriesTest.php | 171 ++++++++++++++++++ Test/Integration/{ => Config}/ConfigTest.php | 3 +- .../Config/MultiStoreConfigTest.php | 89 +++++++++ Test/Integration/MultiStoreCategoriesTest.php | 34 ---- Test/Integration/MultiStoreConfigTest.php | 37 ---- Test/Integration/MultiStoreTestCase.php | 33 +++- .../{ => Page}/PagesIndexingTest.php | 3 +- .../{ => Product}/ProductsIndexingTest.php | 141 ++++++++------- Test/Integration/{ => Queue}/QueueTest.php | 3 +- Test/Integration/{ => Search}/SearchTest.php | 3 +- Test/Integration/TestCase.php | 11 +- 13 files changed, 391 insertions(+), 142 deletions(-) rename Test/Integration/{ => Category}/CategoriesIndexingTest.php (93%) create mode 100644 Test/Integration/Category/MultiStoreCategoriesTest.php rename Test/Integration/{ => Config}/ConfigTest.php (98%) create mode 100644 Test/Integration/Config/MultiStoreConfigTest.php delete mode 100644 Test/Integration/MultiStoreCategoriesTest.php delete mode 100644 Test/Integration/MultiStoreConfigTest.php rename Test/Integration/{ => Page}/PagesIndexingTest.php (97%) rename Test/Integration/{ => Product}/ProductsIndexingTest.php (79%) rename Test/Integration/{ => Queue}/QueueTest.php (99%) rename Test/Integration/{ => Search}/SearchTest.php (84%) diff --git a/Test/Integration/AssertValues/Magento247.php b/Test/Integration/AssertValues/Magento247.php index cc3619ef6..d6f2877af 100644 --- a/Test/Integration/AssertValues/Magento247.php +++ b/Test/Integration/AssertValues/Magento247.php @@ -4,7 +4,7 @@ class Magento247 { - public $productsOnStockCount = 182; + public $productsOnStockCount = 180; public $productsOutOfStockCount = 183; public $lastJobDataSize = 13; public $expectedCategory = 17; diff --git a/Test/Integration/CategoriesIndexingTest.php b/Test/Integration/Category/CategoriesIndexingTest.php similarity index 93% rename from Test/Integration/CategoriesIndexingTest.php rename to Test/Integration/Category/CategoriesIndexingTest.php index 6d5d03c56..c577a9fed 100644 --- a/Test/Integration/CategoriesIndexingTest.php +++ b/Test/Integration/Category/CategoriesIndexingTest.php @@ -1,8 +1,9 @@ categoriesIndexer = $this->objectManager->get(Category::class); + $this->categoryRepository = $this->objectManager->get(CategoryRepositoryInterface::class); + $this->categoryCollectionFactory = $this->objectManager->get(CollectionFactory::class); + + + $this->categoriesIndexer->executeFull(); + $this->algoliaHelper->waitLastTask(); + } + + /** + * @throws CouldNotSaveException + * @throws ExceededRetriesException + * @throws AlgoliaException + * @throws NoSuchEntityException + */ + public function testMultiStoreCategoryIndices() + { + // Check that every store has the right number of categories + foreach ($this->storeManager->getStores() as $store) { + $this->assertNbOfRecordsPerStore($store->getCode(), 'categories', $this->assertValues->expectedCategory); + } + + $defaultStore = $this->storeRepository->get('default'); + $fixtureSecondStore = $this->storeRepository->get('fixture_second_store'); + + $bagsCategory = $this->loadCategory(self::BAGS_CATEGORY_ID, $defaultStore->getId()); + + $this->assertEquals(self::BAGS_CATEGORY_NAME, $bagsCategory->getName()); + + // Change a category name at store level + $bagsCategoryAlt = $this->updateCategory( + self::BAGS_CATEGORY_ID, + $fixtureSecondStore->getId(), + ['name' => self::BAGS_CATEGORY_NAME_ALT] + ); + + $this->assertEquals(self::BAGS_CATEGORY_NAME, $bagsCategory->getName()); + $this->assertEquals(self::BAGS_CATEGORY_NAME_ALT, $bagsCategoryAlt->getName()); + + $this->categoriesIndexer->execute([self::BAGS_CATEGORY_ID]); + $this->algoliaHelper->waitLastTask(); + + $this->assertAlgoliaRecordValues( + $this->indexPrefix . 'default_categories', + (string) self::BAGS_CATEGORY_ID, + ['name' => self::BAGS_CATEGORY_NAME] + ); + + $this->assertAlgoliaRecordValues( + $this->indexPrefix . 'fixture_second_store_categories', + (string) self::BAGS_CATEGORY_ID, + ['name' => self::BAGS_CATEGORY_NAME_ALT] + ); + + // Disable this category at store level + $bagsCategoryAlt = $this->updateCategory( + self::BAGS_CATEGORY_ID, + $fixtureSecondStore->getId(), + ['is_active' => 0] + ); + + $this->categoriesIndexer->execute([self::BAGS_CATEGORY_ID]); + $this->algoliaHelper->waitLastTask(); + + $this->assertNbOfRecordsPerStore( + $defaultStore->getCode(), + 'categories', + $this->assertValues->expectedCategory + ); + + $this->assertNbOfRecordsPerStore( + $fixtureSecondStore->getCode(), + 'categories', + $this->assertValues->expectedCategory - 1 + ); + } + + /** + * Loads category by name. + * + * @param int $categoryId + * @param int $storeId + * + * @return CategoryInterface + * @throws NoSuchEntityException + */ + private function loadCategory(int $categoryId, int $storeId): CategoryInterface + { + return $this->categoryRepository->get($categoryId, $storeId); + } + + /** + * @param int $categoryId + * @param int $storeId + * @param array $values + * + * @return CategoryInterface + * @throws CouldNotSaveException + * @throws NoSuchEntityException + * + * @see Magento\Catalog\Block\Product\ListProduct\SortingTest + */ + private function updateCategory(int $categoryId, int $storeId, array $values): CategoryInterface + { + $oldStoreId = $this->storeManager->getStore()->getId(); + $this->storeManager->setCurrentStore($storeId); + $category = $this->loadCategory($categoryId, $storeId); + foreach ($values as $attribute => $value) { + $category->setData($attribute, $value); + } + $categoryAlt = $this->categoryRepository->save($category); + $this->storeManager->setCurrentStore($oldStoreId); + + return $categoryAlt; + } + + public function tearDown(): void + { + $defaultStore = $this->storeRepository->get('default'); + + // Restore category name in case DB is not cleaned up + $this->updateCategory( + self::BAGS_CATEGORY_ID, + $defaultStore->getId(), + [ + 'name' => self::BAGS_CATEGORY_NAME, + 'is_active' => 1 + ] + ); + + parent::tearDown(); + } +} diff --git a/Test/Integration/ConfigTest.php b/Test/Integration/Config/ConfigTest.php similarity index 98% rename from Test/Integration/ConfigTest.php rename to Test/Integration/Config/ConfigTest.php index 5bc539e46..0fa769f42 100644 --- a/Test/Integration/ConfigTest.php +++ b/Test/Integration/Config/ConfigTest.php @@ -1,9 +1,10 @@ storeManager->getWebsites(); + $stores = $this->storeManager->getStores(); + + // Check that stores and websites are properly created + $this->assertEquals(count($websites), 2); + $this->assertEquals(count($stores), 3); + + foreach ($stores as $store) { + $this->setupStore($store); + } + + $indicesCreatedByTest = 0; + $indices = $this->algoliaHelper->listIndexes(); + + foreach ($indices['items'] as $index) { + $name = $index['name']; + + if (mb_strpos($name, $this->indexPrefix) === 0) { + $indicesCreatedByTest++; + } + } + + // Check that the configuration created the appropriate number of indices (4 per store => 3*4=12) + $this->assertEquals($indicesCreatedByTest, 12); + + + $defaultStore = $this->storeRepository->get('default'); + $fixtureSecondStore = $this->storeRepository->get('fixture_second_store'); + + // Change category configuration at store level (attributes and ranking) + $attributesFromConfig = $this->configHelper->getCategoryAdditionalAttributes($defaultStore->getId()); + $attributesFromConfigAlt = $attributesFromConfig; + $attributesFromConfigAlt[] = [ + "attribute" => self::ADDITIONAL_ATTRIBUTE, + "searchable" => "1", + "order" => "unordered", + "retrievable" => "1", + ]; + + $this->setConfig( + ConfigHelper::CATEGORY_ATTRIBUTES, + json_encode($attributesFromConfigAlt), + $fixtureSecondStore->getCode()) + ; + + $rankingsFromConfig = $this->configHelper->getCategoryCustomRanking($defaultStore->getId()); + $rankingsFromConfigAlt = $rankingsFromConfig; + $rankingsFromConfigAlt[] = [ + "attribute" => self::ADDITIONAL_ATTRIBUTE, + "order" => "desc", + ]; + + $this->setConfig( + ConfigHelper::CATEGORY_CUSTOM_RANKING, + json_encode($rankingsFromConfigAlt), + $fixtureSecondStore->getCode()) + ; + + $this->indicesConfigurator->saveConfigurationToAlgolia($fixtureSecondStore->getId()); + + $defaultIndexSettings = $this->algoliaHelper->getSettings($this->indexPrefix . 'default_categories'); + $fixtureIndexSettings = $this->algoliaHelper->getSettings($this->indexPrefix . 'fixture_second_store_categories'); + + $attributeFromConfig = 'unordered(' . self::ADDITIONAL_ATTRIBUTE . ')'; + $this->assertNotContains($attributeFromConfig, $defaultIndexSettings['searchableAttributes']); + $this->assertContains($attributeFromConfig, $fixtureIndexSettings['searchableAttributes']); + + $rankingFromConfig = 'desc(' . self::ADDITIONAL_ATTRIBUTE . ')'; + $this->assertNotContains($rankingFromConfig, $defaultIndexSettings['customRanking']); + $this->assertContains($rankingFromConfig, $fixtureIndexSettings['customRanking']); + } +} diff --git a/Test/Integration/MultiStoreCategoriesTest.php b/Test/Integration/MultiStoreCategoriesTest.php deleted file mode 100644 index 48a63a282..000000000 --- a/Test/Integration/MultiStoreCategoriesTest.php +++ /dev/null @@ -1,34 +0,0 @@ -categoriesIndexer = $this->getObjectManager()->create(Category::class); - - $this->categoriesIndexer->executeFull(); - $this->algoliaHelper->waitLastTask(); - } - - public function testMultiStoreCategoryIndices() - { - foreach ($this->storeManager->getStores() as $store) { - $this->assertNbOfRecordsPerStore($store->getCode(), 'categories', $this->assertValues->expectedCategory); - } - } -} diff --git a/Test/Integration/MultiStoreConfigTest.php b/Test/Integration/MultiStoreConfigTest.php deleted file mode 100644 index 2b8424fb0..000000000 --- a/Test/Integration/MultiStoreConfigTest.php +++ /dev/null @@ -1,37 +0,0 @@ -storeManager->getWebsites(); - $stores = $this->storeManager->getStores(); - - // Check that stores and websites are properly created - $this->assertEquals(count($websites), 2); - $this->assertEquals(count($stores), 3); - - foreach ($stores as $store) { - $this->setupStore($store); - } - - $indicesCreatedByTest = 0; - $indices = $this->algoliaHelper->listIndexes(); - - foreach ($indices['items'] as $index) { - $name = $index['name']; - - if (mb_strpos($name, $this->indexPrefix) === 0) { - $indicesCreatedByTest++; - } - } - - // Check that the configuration created the appropriate number of indices (4 per store => 3*4=12) - $this->assertEquals($indicesCreatedByTest, 12); - } -} diff --git a/Test/Integration/MultiStoreTestCase.php b/Test/Integration/MultiStoreTestCase.php index 0d6969404..f5458b646 100644 --- a/Test/Integration/MultiStoreTestCase.php +++ b/Test/Integration/MultiStoreTestCase.php @@ -7,6 +7,7 @@ use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Api\StoreRepositoryInterface; use Magento\Store\Model\StoreManager; abstract class MultiStoreTestCase extends IndexingTestCase @@ -14,18 +15,24 @@ abstract class MultiStoreTestCase extends IndexingTestCase /** @var StoreManager */ protected $storeManager; + /** @var StoreRepositoryInterface */ + protected $storeRepository; + /** @var IndicesConfigurator */ protected $indicesConfigurator; public function setUp():void { + parent::setUp(); + /** @var StoreManager $storeManager */ - $this->storeManager = $this->getObjectManager()->create(StoreManager::class); + $this->storeManager = $this->objectManager->get(StoreManager::class); /** @var IndicesConfigurator $indicesConfigurator */ - $this->indicesConfigurator = $this->getObjectManager()->create(IndicesConfigurator::class); + $this->indicesConfigurator = $this->objectManager->get(IndicesConfigurator::class); - parent::setUp(); + /** @var StoreRepositoryInterface $storeRepository */ + $this->storeRepository = $this->objectManager->get(StoreRepositoryInterface::class); foreach ($this->storeManager->getStores() as $store) { $this->setupStore($store); @@ -47,6 +54,26 @@ protected function assertNbOfRecordsPerStore(string $storeCode, string $entity, $this->assertEquals($expectedNumber, $resultsDefault['results'][0]['nbHits']); } + /** + * @param string $indexName + * @param string $recordId + * @param array $expectedValues + * + * @return void + * @throws AlgoliaException + */ + public function assertAlgoliaRecordValues( + string $indexName, + string $recordId, + array $expectedValues + ) : void { + $res = $this->algoliaHelper->getObjects($indexName, [$recordId]); + $record = reset($res['results']); + foreach ($expectedValues as $attribute => $expectedValue) { + $this->assertEquals($expectedValue, $record[$attribute]); + } + } + /** * @param StoreInterface $store * diff --git a/Test/Integration/PagesIndexingTest.php b/Test/Integration/Page/PagesIndexingTest.php similarity index 97% rename from Test/Integration/PagesIndexingTest.php rename to Test/Integration/Page/PagesIndexingTest.php index 85b366503..97b1351e9 100644 --- a/Test/Integration/PagesIndexingTest.php +++ b/Test/Integration/Page/PagesIndexingTest.php @@ -1,9 +1,10 @@ productsIndexer = $this->objectManager->get(Product::class); + $this->stockRegistry = $this->objectManager->get(StockRegistry::class); + $this->indexerRegistry = $this->objectManager->get(IndexerRegistry::class); + + $priceIndexer = $this->indexerRegistry->get('catalog_product_price'); + $priceIndexer->reindexAll(); + } + public function testOnlyOnStockProducts() { + $this->assertEquals(true, true); + $this->setConfig('cataloginventory/options/show_out_of_stock', 0); $this->setOneProductOutOfStock(); - /** @var Product $indexer */ - $indexer = $this->getObjectManager()->create(Product::class); - - $this->processTest($indexer, 'products', $this->assertValues->productsOnStockCount); + $this->processTest($this->productsIndexer, 'products', $this->assertValues->productsOnStockCount); } public function testIncludingOutOfStock() @@ -27,17 +53,60 @@ public function testIncludingOutOfStock() $this->setOneProductOutOfStock(); + $this->processTest($this->productsIndexer, 'products', $this->assertValues->productsOutOfStockCount); + } + + public function testDefaultIndexableAttributes() + { + $empty = $this->getSerializer()->serialize([]); + + $this->setConfig('algoliasearch_products/products/product_additional_attributes', $empty); + $this->setConfig('algoliasearch_instant/instant_facets/facets', $empty); + $this->setConfig('algoliasearch_instant/instant_sorts/sorts', $empty); + $this->setConfig('algoliasearch_products/products/custom_ranking_product_attributes', $empty); + /** @var Product $indexer */ $indexer = $this->getObjectManager()->create(Product::class); + $indexer->executeRow($this->getValidTestProduct()); + + $this->algoliaHelper->waitLastTask(); + + $results = $this->algoliaHelper->getObjects($this->indexPrefix . 'default_products', [$this->getValidTestProduct()]); + $hit = reset($results['results']); + + $defaultAttributes = [ + 'objectID', + 'name', + 'url', + 'visibility_search', + 'visibility_catalog', + 'categories', + 'categories_without_path', + 'thumbnail_url', + 'image_url', + 'in_stock', + 'price', + 'type_id', + 'algoliaLastUpdateAtCET', + 'categoryIds', + ]; + + if (!$hit) { + $this->markTestIncomplete('Hit was not returned correctly from Algolia. No Hit to run assetions on.'); + } + + foreach ($defaultAttributes as $key => $attribute) { + $this->assertArrayHasKey($attribute, $hit, 'Products attribute "' . $attribute . '" should be indexed but it is not"'); + unset($hit[$attribute]); + } - $this->processTest($indexer, 'products', $this->assertValues->productsOutOfStockCount); + $extraAttributes = implode(', ', array_keys($hit)); + $this->assertEmpty($hit, 'Extra products attributes (' . $extraAttributes . ') are indexed and should not be.'); } public function testNoSpecialPrice() { - /** @var Product $indexer */ - $indexer = $this->getObjectManager()->create(Product::class); - $indexer->execute([9]); + $this->productsIndexer->execute([9]); $this->algoliaHelper->waitLastTask(); @@ -91,61 +160,11 @@ public function deprecatedTestSpecialPrice() $this->assertEquals($to, $algoliaProduct['price']['USD']['special_to_date']); } - public function testDefaultIndexableAttributes() - { - $empty = $this->getSerializer()->serialize([]); - - $this->setConfig('algoliasearch_products/products/product_additional_attributes', $empty); - $this->setConfig('algoliasearch_instant/instant_facets/facets', $empty); - $this->setConfig('algoliasearch_instant/instant_sorts/sorts', $empty); - $this->setConfig('algoliasearch_products/products/custom_ranking_product_attributes', $empty); - - /** @var Product $indexer */ - $indexer = $this->getObjectManager()->create(Product::class); - $indexer->executeRow($this->getValidTestProduct()); - - $this->algoliaHelper->waitLastTask(); - - $results = $this->algoliaHelper->getObjects($this->indexPrefix . 'default_products', [$this->getValidTestProduct()]); - $hit = reset($results['results']); - - $defaultAttributes = [ - 'objectID', - 'name', - 'url', - 'visibility_search', - 'visibility_catalog', - 'categories', - 'categories_without_path', - 'thumbnail_url', - 'image_url', - 'in_stock', - 'price', - 'type_id', - 'algoliaLastUpdateAtCET', - 'categoryIds', - ]; - - if (!$hit) { - $this->markTestIncomplete('Hit was not returned correctly from Algolia. No Hit to run assetions on.'); - } - - foreach ($defaultAttributes as $key => $attribute) { - $this->assertArrayHasKey($attribute, $hit, 'Products attribute "' . $attribute . '" should be indexed but it is not"'); - unset($hit[$attribute]); - } - - $extraAttributes = implode(', ', array_keys($hit)); - $this->assertEmpty($hit, 'Extra products attributes (' . $extraAttributes . ') are indexed and should not be.'); - } - private function setOneProductOutOfStock() { - /** @var StockRegistry $stockRegistry */ - $stockRegistry = $this->getObjectManager()->create(\Magento\CatalogInventory\Model\StockRegistry::class); - $stockItem = $stockRegistry->getStockItemBySku('24-MB01'); + $stockItem = $this->stockRegistry->getStockItemBySku('24-MB01'); $stockItem->setIsInStock(false); - $stockRegistry->updateStockItemBySku('24-MB01', $stockItem); + $this->stockRegistry->updateStockItemBySku('24-MB01', $stockItem); } private function getValidTestProduct() diff --git a/Test/Integration/QueueTest.php b/Test/Integration/Queue/QueueTest.php similarity index 99% rename from Test/Integration/QueueTest.php rename to Test/Integration/Queue/QueueTest.php index 640aae3e0..0cb0d1ce6 100644 --- a/Test/Integration/QueueTest.php +++ b/Test/Integration/Queue/QueueTest.php @@ -1,6 +1,6 @@ clearIndices(); + $this->algoliaHelper->waitLastTask(); + $this->clearIndices(); // Remaining replicas } protected function resetConfigs($configs = []) @@ -100,6 +107,8 @@ private function bootstrap() return; } + $this->objectManager = Bootstrap::getObjectManager(); + if (version_compare($this->getMagentoVersion(), '2.4.7', '<')) { $this->assertValues = new Magento246(); } else { From 6483c8dcbabb374f151a974a105d45bd01fce9e0 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Wed, 2 Oct 2024 18:11:23 -0400 Subject: [PATCH 10/40] MAGE-1044 Add basic replica config test --- .../Product/ReplicaIndexingTest.php | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 Test/Integration/Product/ReplicaIndexingTest.php diff --git a/Test/Integration/Product/ReplicaIndexingTest.php b/Test/Integration/Product/ReplicaIndexingTest.php new file mode 100644 index 000000000..73a3cbcd8 --- /dev/null +++ b/Test/Integration/Product/ReplicaIndexingTest.php @@ -0,0 +1,79 @@ +productIndexer = $this->objectManager->get(ProductIndexer::class); + $this->replicaManager = $this->objectManager->get(ReplicaManagerInterface::class); + $this->indicesConfigurator = $this->getObjectManager()->get(IndicesConfigurator::class); + $this->indexSuffix = 'products'; + + } + + protected function getIndexName(string $storeIndexPart): string + { + return $this->indexPrefix . $storeIndexPart . $this->indexSuffix; + } + + public function processFullReindexProducts(): void + { + $this->processFullReindex($this->productIndexer, $this->indexSuffix); + } + + public function testReplicaIndex(): void + { + $sorting = $this->configHelper->getSorting(); + $sortAttr = 'created_at'; + + // Has created_at sort + $this->assertTrue( + (bool) + array_filter( + $sorting, + function($sort) use ($sortAttr) { + return $sort['attribute'] == $sortAttr; + } + ) + ); + + // Expected replica max + $this->assertEquals($this->replicaManager->getMaxVirtualReplicasPerIndex(), 20); + + // Replicas will not get created if InstantSearch is not used + $this->setConfig('algoliasearch_instant/instant/is_instant_enabled', 1); + + $this->indicesConfigurator->saveConfigurationToAlgolia(1); + $this->algoliaHelper->waitLastTask(); + + // Assert replica config created + $indexName = $this->getIndexName('default_'); + $currentSettings = $this->algoliaHelper->getSettings($indexName); + $this->assertArrayHasKey('replicas', $currentSettings); + + $this->assertTrue( + (bool) + array_filter( + $currentSettings['replicas'], + function($replicaIndex) use ($indexName, $sortAttr) { + return str_contains($replicaIndex, $indexName . '_' . $sortAttr); + } + ) + ); + } +} From 673a138a15fec263a3c9f4a8db4da480d4649b9e Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Wed, 2 Oct 2024 18:41:49 -0400 Subject: [PATCH 11/40] MAGE-1044 Include sort direction in test --- .../Product/ReplicaIndexingTest.php | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/Test/Integration/Product/ReplicaIndexingTest.php b/Test/Integration/Product/ReplicaIndexingTest.php index 73a3cbcd8..811f0024f 100644 --- a/Test/Integration/Product/ReplicaIndexingTest.php +++ b/Test/Integration/Product/ReplicaIndexingTest.php @@ -24,6 +24,8 @@ public function setUp(): void $this->indicesConfigurator = $this->getObjectManager()->get(IndicesConfigurator::class); $this->indexSuffix = 'products'; + // Replicas will not get created if InstantSearch is not used + $this->setConfig('algoliasearch_instant/instant/is_instant_enabled', 1); } protected function getIndexName(string $storeIndexPart): string @@ -36,18 +38,20 @@ public function processFullReindexProducts(): void $this->processFullReindex($this->productIndexer, $this->indexSuffix); } - public function testReplicaIndex(): void + public function testReplicaConfig(): void { $sorting = $this->configHelper->getSorting(); $sortAttr = 'created_at'; + $sortDir = 'desc'; // Has created_at sort $this->assertTrue( (bool) array_filter( $sorting, - function($sort) use ($sortAttr) { - return $sort['attribute'] == $sortAttr; + function($sort) use ($sortAttr, $sortDir) { + return $sort['attribute'] == $sortAttr + && $sort['sort'] == $sortDir; } ) ); @@ -55,9 +59,6 @@ function($sort) use ($sortAttr) { // Expected replica max $this->assertEquals($this->replicaManager->getMaxVirtualReplicasPerIndex(), 20); - // Replicas will not get created if InstantSearch is not used - $this->setConfig('algoliasearch_instant/instant/is_instant_enabled', 1); - $this->indicesConfigurator->saveConfigurationToAlgolia(1); $this->algoliaHelper->waitLastTask(); @@ -70,10 +71,16 @@ function($sort) use ($sortAttr) { (bool) array_filter( $currentSettings['replicas'], - function($replicaIndex) use ($indexName, $sortAttr) { - return str_contains($replicaIndex, $indexName . '_' . $sortAttr); + function($replicaIndex) use ($indexName, $sortAttr, $sortDir) { + return str_contains($replicaIndex, $indexName . '_' . $sortAttr . '_' . $sortDir); } ) ); + + } + + public function tearDown(): void + { + $this->setConfig('algoliasearch_instant/instant/is_instant_enabled', 0); } } From 6ea433b7fb2d03f4f3962db2951ec69e3a046518 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Wed, 2 Oct 2024 19:10:53 -0400 Subject: [PATCH 12/40] MAGE-1044 Assert replica index ranking config --- .../Product/ReplicaIndexingTest.php | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/Test/Integration/Product/ReplicaIndexingTest.php b/Test/Integration/Product/ReplicaIndexingTest.php index 811f0024f..ee6e08b14 100644 --- a/Test/Integration/Product/ReplicaIndexingTest.php +++ b/Test/Integration/Product/ReplicaIndexingTest.php @@ -46,8 +46,7 @@ public function testReplicaConfig(): void // Has created_at sort $this->assertTrue( - (bool) - array_filter( + (bool) array_filter( $sorting, function($sort) use ($sortAttr, $sortDir) { return $sort['attribute'] == $sortAttr @@ -57,7 +56,7 @@ function($sort) use ($sortAttr, $sortDir) { ); // Expected replica max - $this->assertEquals($this->replicaManager->getMaxVirtualReplicasPerIndex(), 20); + $this->assertEquals(20, $this->replicaManager->getMaxVirtualReplicasPerIndex()); $this->indicesConfigurator->saveConfigurationToAlgolia(1); $this->algoliaHelper->waitLastTask(); @@ -67,16 +66,26 @@ function($sort) use ($sortAttr, $sortDir) { $currentSettings = $this->algoliaHelper->getSettings($indexName); $this->assertArrayHasKey('replicas', $currentSettings); + $sortIndexName = $indexName . '_' . $sortAttr . '_' . $sortDir; + $this->assertTrue( - (bool) - array_filter( + (bool) array_filter( $currentSettings['replicas'], - function($replicaIndex) use ($indexName, $sortAttr, $sortDir) { - return str_contains($replicaIndex, $indexName . '_' . $sortAttr . '_' . $sortDir); + function($replica) use ($sortIndexName) { + return str_contains($replica, $sortIndexName); } ) ); + // Assert replica index created + $replicaSettings = $this->algoliaHelper->getSettings($sortIndexName); + $this->assertArrayHasKey('primary', $replicaSettings); + $this->assertEquals($indexName, $replicaSettings['primary']); + + // Assert standard replica ranking config + $this->assertArrayHasKey('ranking', $replicaSettings); + $this->assertContains("$sortDir($sortAttr)", $replicaSettings['ranking']); + } public function tearDown(): void From f5fb91b0be5afeb1dc1222736876bd1a67fbe029 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Sat, 5 Oct 2024 18:41:56 -0400 Subject: [PATCH 13/40] MAGE-1081 Pull fresh settings data if Algolia state changes over life of singleton --- Service/Product/ReplicaManager.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Service/Product/ReplicaManager.php b/Service/Product/ReplicaManager.php index 0efa73433..9191383b0 100644 --- a/Service/Product/ReplicaManager.php +++ b/Service/Product/ReplicaManager.php @@ -133,12 +133,14 @@ protected function clearAlgoliaReplicaSettingCache($primaryIndexName = null): vo * relevant to the Magento integration * * @param string $primaryIndexName + * @param bool $refreshCache * @return string[] Array of replica index names * @throws LocalizedException + * @throws NoSuchEntityException */ - protected function getMagentoReplicaConfigurationFromAlgolia(string $primaryIndexName): array + protected function getMagentoReplicaConfigurationFromAlgolia(string $primaryIndexName, bool $refreshCache = false): array { - $algoliaReplicas = $this->getReplicaConfigurationFromAlgolia($primaryIndexName); + $algoliaReplicas = $this->getReplicaConfigurationFromAlgolia($primaryIndexName, $refreshCache); $magentoReplicas = $this->getMagentoReplicaSettings($primaryIndexName, $algoliaReplicas); return array_values(array_intersect($magentoReplicas, $algoliaReplicas)); } @@ -241,7 +243,7 @@ protected function setReplicasOnPrimaryIndex(int $storeId): array $indexName = $this->indexNameFetcher->getProductIndexName($storeId); $sortingIndices = $this->sortingTransformer->getSortingIndices($storeId); $newMagentoReplicasSetting = $this->sortingTransformer->transformSortingIndicesToReplicaSetting($sortingIndices); - $oldMagentoReplicasSetting = $this->getMagentoReplicaConfigurationFromAlgolia($indexName); + $oldMagentoReplicasSetting = $this->getMagentoReplicaConfigurationFromAlgolia($indexName, true); $nonMagentoReplicasSetting = $this->getNonMagentoReplicaConfigurationFromAlgolia($indexName); $oldMagentoReplicaIndices = $this->getBareIndexNamesFromReplicaSetting($oldMagentoReplicasSetting); $newMagentoReplicaIndices = $this->getBareIndexNamesFromReplicaSetting($newMagentoReplicasSetting); From f675d31a29b35664275a9a97cd190e4ad6b1265a Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Sun, 6 Oct 2024 18:17:29 -0400 Subject: [PATCH 14/40] MAGE-1044 Test sorting attribute update --- Helper/ConfigHelper.php | 2 +- .../Product/ReplicaIndexingTest.php | 102 ++++++++++++++---- 2 files changed, 85 insertions(+), 19 deletions(-) diff --git a/Helper/ConfigHelper.php b/Helper/ConfigHelper.php index ae87512a0..80ea53baf 100755 --- a/Helper/ConfigHelper.php +++ b/Helper/ConfigHelper.php @@ -1157,7 +1157,7 @@ public function getRawSortingValue(?int $storeId = null): string * @param int|null $scopeId * @return void */ - public function setSorting(array $sorting, ?string $scope = null, ?int $scopeId = null): void + public function setSorting(array $sorting, string $scope = Magento\Framework\App\Config\ScopeConfigInterface::SCOPE_TYPE_DEFAULT, ?int $scopeId = null): void { $this->configWriter->save( self::SORTING_INDICES, diff --git a/Test/Integration/Product/ReplicaIndexingTest.php b/Test/Integration/Product/ReplicaIndexingTest.php index ee6e08b14..a35ae8247 100644 --- a/Test/Integration/Product/ReplicaIndexingTest.php +++ b/Test/Integration/Product/ReplicaIndexingTest.php @@ -3,6 +3,7 @@ namespace Algolia\AlgoliaSearch\Test\Integration\Product; use Algolia\AlgoliaSearch\Api\Product\ReplicaManagerInterface; +use Algolia\AlgoliaSearch\Helper\Entity\ProductHelper; use Algolia\AlgoliaSearch\Model\Indexer\Product as ProductIndexer; use Algolia\AlgoliaSearch\Model\IndicesConfigurator; use Algolia\AlgoliaSearch\Test\Integration\IndexingTestCase; @@ -16,16 +17,17 @@ class ReplicaIndexingTest extends IndexingTestCase protected ?string $indexSuffix; + protected ?array $ogSortingState; + public function setUp(): void { parent::setUp(); $this->productIndexer = $this->objectManager->get(ProductIndexer::class); $this->replicaManager = $this->objectManager->get(ReplicaManagerInterface::class); - $this->indicesConfigurator = $this->getObjectManager()->get(IndicesConfigurator::class); + $this->indicesConfigurator = $this->objectManager->get(IndicesConfigurator::class); $this->indexSuffix = 'products'; - // Replicas will not get created if InstantSearch is not used - $this->setConfig('algoliasearch_instant/instant/is_instant_enabled', 1); + $this->ogSortingState = $this->configHelper->getSorting(); } protected function getIndexName(string $storeIndexPart): string @@ -38,25 +40,40 @@ public function processFullReindexProducts(): void $this->processFullReindex($this->productIndexer, $this->indexSuffix); } - public function testReplicaConfig(): void + protected function hasSortingAttribute($sortAttr, $sortDir): bool { $sorting = $this->configHelper->getSorting(); - $sortAttr = 'created_at'; - $sortDir = 'desc'; - - // Has created_at sort - $this->assertTrue( - (bool) array_filter( - $sorting, - function($sort) use ($sortAttr, $sortDir) { - return $sort['attribute'] == $sortAttr - && $sort['sort'] == $sortDir; - } - ) + return (bool) array_filter( + $sorting, + function($sort) use ($sortAttr, $sortDir) { + return $sort['attribute'] == $sortAttr + && $sort['sort'] == $sortDir; + } ); + } - // Expected replica max + protected function assertSortingAttribute($sortAttr, $sortDir): void + { + $this->assertTrue($this->hasSortingAttribute($sortAttr, $sortDir)); + } + + protected function assertNoSortingAttribute($sortAttr, $sortDir): void + { + $this->assertTrue(!$this->hasSortingAttribute($sortAttr, $sortDir)); + } + + public function testReplicaLimits() { $this->assertEquals(20, $this->replicaManager->getMaxVirtualReplicasPerIndex()); + } + + /** + * @magentoConfigFixture current_store algoliasearch_instant/instant/is_instant_enabled 1 + */ + public function testStandardReplicaConfig(): void + { + $sortAttr = 'created_at'; + $sortDir = 'desc'; + $this->assertSortingAttribute($sortAttr, $sortDir); $this->indicesConfigurator->saveConfigurationToAlgolia(1); $this->algoliaHelper->waitLastTask(); @@ -88,8 +105,57 @@ function($replica) use ($sortIndexName) { } + /** + * @magentoDbIsolation disabled + */ + public function testVirtualReplicaConfig(): void + { + $productHelper = $this->objectManager->get(ProductHelper::class); + $sortAttr = 'color'; + $sortDir = 'asc'; + $attributes = $productHelper->getAllAttributes(); + $this->assertArrayHasKey($sortAttr, $attributes); + + $this->assertNoSortingAttribute($sortAttr, $sortDir); + + $sorting = $this->configHelper->getSorting(); + $sorting[] = [ + 'attribute' => $sortAttr, + 'sort' => $sortDir, + 'sortLabel' => $sortAttr + ]; + $encoded = json_encode($sorting); +// $this->setConfig('algoliasearch_instant/instant_sorts/sorts', $encoded); + $this->configHelper->setSorting($sorting); + + $connection = $this->objectManager->create(\Magento\Framework\App\ResourceConnection::class) + ->getConnection(); +// $connection->beginTransaction(); +// $this->objectManager->get(\Magento\Framework\App\Config\Storage\WriterInterface::class)->save( +// \Algolia\AlgoliaSearch\Helper\ConfigHelper::SORTING_INDICES, +// $encoded, +// 'default' +// ); +// $connection->commit(); + + + $select = $connection->select() + ->from('core_config_data', 'value') + ->where('path = ?', 'algoliasearch_instant/instant_sorts/sorts') + ->where('scope = ?', 'default') + ->where('scope_id = ?', 0); + + $configValue = $connection->fetchOne($select); + + // Assert that the correct value was written to the database + $this->assertEquals($encoded, $configValue); + +// $this->assertSortingAttribute($sortAttr, $sortDir); + + } + public function tearDown(): void { - $this->setConfig('algoliasearch_instant/instant/is_instant_enabled', 0); + $this->configHelper->setSorting($this->ogSortingState); } } From 93e969881f7a6147db70b0134238d3eb3388374b Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Sun, 6 Oct 2024 19:05:07 -0400 Subject: [PATCH 15/40] MAGE-1044 Test sorting attribute via ConfigHelper --- .../Product/ReplicaIndexingTest.php | 37 +++++-------------- Test/Integration/TestCase.php | 29 ++++++++++++++- 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/Test/Integration/Product/ReplicaIndexingTest.php b/Test/Integration/Product/ReplicaIndexingTest.php index a35ae8247..84272f630 100644 --- a/Test/Integration/Product/ReplicaIndexingTest.php +++ b/Test/Integration/Product/ReplicaIndexingTest.php @@ -10,14 +10,14 @@ class ReplicaIndexingTest extends IndexingTestCase { - protected ?ReplicaManagerInterface $replicaManager; - protected ?ProductIndexer $productIndexer; + protected ?ReplicaManagerInterface $replicaManager = null; + protected ?ProductIndexer $productIndexer = null; - protected ?IndicesConfigurator $indicesConfigurator; + protected ?IndicesConfigurator $indicesConfigurator = null; - protected ?string $indexSuffix; + protected ?string $indexSuffix = null; - protected ?array $ogSortingState; + protected ?array $ogSortingState = null; public function setUp(): void { @@ -107,6 +107,7 @@ function($replica) use ($sortIndexName) { /** * @magentoDbIsolation disabled + * @group virtual */ public function testVirtualReplicaConfig(): void { @@ -124,33 +125,13 @@ public function testVirtualReplicaConfig(): void 'sort' => $sortDir, 'sortLabel' => $sortAttr ]; - $encoded = json_encode($sorting); -// $this->setConfig('algoliasearch_instant/instant_sorts/sorts', $encoded); $this->configHelper->setSorting($sorting); - $connection = $this->objectManager->create(\Magento\Framework\App\ResourceConnection::class) - ->getConnection(); -// $connection->beginTransaction(); -// $this->objectManager->get(\Magento\Framework\App\Config\Storage\WriterInterface::class)->save( -// \Algolia\AlgoliaSearch\Helper\ConfigHelper::SORTING_INDICES, -// $encoded, -// 'default' -// ); -// $connection->commit(); + $this->assertConfigInDb('algoliasearch_instant/instant_sorts/sorts', json_encode($sorting)); + $this->refreshConfigFromDb(); - $select = $connection->select() - ->from('core_config_data', 'value') - ->where('path = ?', 'algoliasearch_instant/instant_sorts/sorts') - ->where('scope = ?', 'default') - ->where('scope_id = ?', 0); - - $configValue = $connection->fetchOne($select); - - // Assert that the correct value was written to the database - $this->assertEquals($encoded, $configValue); - -// $this->assertSortingAttribute($sortAttr, $sortDir); + $this->assertSortingAttribute($sortAttr, $sortDir); } diff --git a/Test/Integration/TestCase.php b/Test/Integration/TestCase.php index 8f2b12d00..1af7aab20 100644 --- a/Test/Integration/TestCase.php +++ b/Test/Integration/TestCase.php @@ -8,6 +8,7 @@ use Algolia\AlgoliaSearch\Setup\Patch\Schema\ConfigPatch; use Algolia\AlgoliaSearch\Test\Integration\AssertValues\Magento246; use Algolia\AlgoliaSearch\Test\Integration\AssertValues\Magento247; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\ProductMetadataInterface; use Magento\Framework\ObjectManagerInterface; use Magento\Store\Model\ScopeInterface; @@ -78,6 +79,32 @@ protected function setConfig( ); } + protected function assertConfigInDb( + string $path, + mixed $value, + string $scope = ScopeConfigInterface::SCOPE_TYPE_DEFAULT, + int $scopeId = 0 + ): void + { + $connection = $this->objectManager->create(\Magento\Framework\App\ResourceConnection::class) + ->getConnection(); + + $select = $connection->select() + ->from('core_config_data', 'value') + ->where('path = ?', $path) + ->where('scope = ?', $scope) + ->where('scope_id = ?', $scopeId); + + $configValue = $connection->fetchOne($select); + + $this->assertEquals($value, $configValue); + } + + protected function refreshConfigFromDb(): void + { + $this->objectManager->get(\Magento\Framework\App\Config\ReinitableConfigInterface::class)->reinit(); + } + protected function clearIndices() { $indices = $this->algoliaHelper->listIndexes(); @@ -107,7 +134,7 @@ private function bootstrap() return; } - $this->objectManager = Bootstrap::getObjectManager(); + $this->objectManager = $this->getObjectManager(); if (version_compare($this->getMagentoVersion(), '2.4.7', '<')) { $this->assertValues = new Magento246(); From 265f8ec96749b13c6bed133fb5a09e0b68bf9796 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Sun, 6 Oct 2024 20:52:00 -0400 Subject: [PATCH 16/40] MAGE-1044 Preserve test bootstrap on config reinit --- Test/Integration/TestCase.php | 43 +++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/Test/Integration/TestCase.php b/Test/Integration/TestCase.php index 1af7aab20..f61aeff66 100644 --- a/Test/Integration/TestCase.php +++ b/Test/Integration/TestCase.php @@ -100,9 +100,52 @@ protected function assertConfigInDb( $this->assertEquals($value, $configValue); } + /** + * If testing classes that use WriterInterface under the hood to update the database + * then you need a way to refresh the in-memory cache + * This function achieves that while preserving the original bootstrap config + */ protected function refreshConfigFromDb(): void { + $bootstrap = $this->getBootstrapConfig(); $this->objectManager->get(\Magento\Framework\App\Config\ReinitableConfigInterface::class)->reinit(); + $this->setConfigFromArray($bootstrap); + } + + /** + * @return array + */ + protected function getBootstrapConfig(): array + { + $config = $this->objectManager->get(ScopeConfigInterface::class); + + $bootstrap = [ + ConfigHelper::APPLICATION_ID, + ConfigHelper::SEARCH_ONLY_API_KEY, + ConfigHelper::API_KEY, + ConfigHelper::INDEX_PREFIX + ]; + + return array_combine( + $bootstrap, + array_map( + function($setting) use ($config) { + return $config->getValue($setting, ScopeInterface::SCOPE_STORE); + }, + $bootstrap + ) + ); + } + + /** + * @param array $settings + * @return void + */ + protected function setConfigFromArray(array $settings): void + { + foreach ($settings as $key => $value) { + $this->setConfig($key, $value); + } } protected function clearIndices() From ee9dc43abb507f64c631055d97010c2087859366 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Sun, 6 Oct 2024 21:25:10 -0400 Subject: [PATCH 17/40] MAGE-1044 Add virtual replica test --- Service/Product/ReplicaManager.php | 8 +- .../Product/ReplicaIndexingTest.php | 77 +++++++++++++++---- 2 files changed, 69 insertions(+), 16 deletions(-) diff --git a/Service/Product/ReplicaManager.php b/Service/Product/ReplicaManager.php index 0efa73433..9191383b0 100644 --- a/Service/Product/ReplicaManager.php +++ b/Service/Product/ReplicaManager.php @@ -133,12 +133,14 @@ protected function clearAlgoliaReplicaSettingCache($primaryIndexName = null): vo * relevant to the Magento integration * * @param string $primaryIndexName + * @param bool $refreshCache * @return string[] Array of replica index names * @throws LocalizedException + * @throws NoSuchEntityException */ - protected function getMagentoReplicaConfigurationFromAlgolia(string $primaryIndexName): array + protected function getMagentoReplicaConfigurationFromAlgolia(string $primaryIndexName, bool $refreshCache = false): array { - $algoliaReplicas = $this->getReplicaConfigurationFromAlgolia($primaryIndexName); + $algoliaReplicas = $this->getReplicaConfigurationFromAlgolia($primaryIndexName, $refreshCache); $magentoReplicas = $this->getMagentoReplicaSettings($primaryIndexName, $algoliaReplicas); return array_values(array_intersect($magentoReplicas, $algoliaReplicas)); } @@ -241,7 +243,7 @@ protected function setReplicasOnPrimaryIndex(int $storeId): array $indexName = $this->indexNameFetcher->getProductIndexName($storeId); $sortingIndices = $this->sortingTransformer->getSortingIndices($storeId); $newMagentoReplicasSetting = $this->sortingTransformer->transformSortingIndicesToReplicaSetting($sortingIndices); - $oldMagentoReplicasSetting = $this->getMagentoReplicaConfigurationFromAlgolia($indexName); + $oldMagentoReplicasSetting = $this->getMagentoReplicaConfigurationFromAlgolia($indexName, true); $nonMagentoReplicasSetting = $this->getNonMagentoReplicaConfigurationFromAlgolia($indexName); $oldMagentoReplicaIndices = $this->getBareIndexNamesFromReplicaSetting($oldMagentoReplicasSetting); $newMagentoReplicaIndices = $this->getBareIndexNamesFromReplicaSetting($newMagentoReplicasSetting); diff --git a/Test/Integration/Product/ReplicaIndexingTest.php b/Test/Integration/Product/ReplicaIndexingTest.php index 84272f630..c97e2c426 100644 --- a/Test/Integration/Product/ReplicaIndexingTest.php +++ b/Test/Integration/Product/ReplicaIndexingTest.php @@ -59,10 +59,11 @@ protected function assertSortingAttribute($sortAttr, $sortDir): void protected function assertNoSortingAttribute($sortAttr, $sortDir): void { - $this->assertTrue(!$this->hasSortingAttribute($sortAttr, $sortDir)); + $this->assertFalse($this->hasSortingAttribute($sortAttr, $sortDir)); } - public function testReplicaLimits() { + public function testReplicaLimits() + { $this->assertEquals(20, $this->replicaManager->getMaxVirtualReplicasPerIndex()); } @@ -85,14 +86,8 @@ public function testStandardReplicaConfig(): void $sortIndexName = $indexName . '_' . $sortAttr . '_' . $sortDir; - $this->assertTrue( - (bool) array_filter( - $currentSettings['replicas'], - function($replica) use ($sortIndexName) { - return str_contains($replica, $sortIndexName); - } - ) - ); + $this->assertTrue($this->isStandardReplica($currentSettings['replicas'], $sortIndexName)); + $this->assertFalse($this->isVirtualReplica($currentSettings['replicas'], $sortIndexName)); // Assert replica index created $replicaSettings = $this->algoliaHelper->getSettings($sortIndexName); @@ -111,6 +106,11 @@ function($replica) use ($sortIndexName) { */ public function testVirtualReplicaConfig(): void { + $indexName = $this->getIndexName('default_'); + $ogAlgoliaSettings = $this->algoliaHelper->getSettings($indexName); + + $this->assertFalse(array_key_exists('replicas', $ogAlgoliaSettings)); + $productHelper = $this->objectManager->get(ProductHelper::class); $sortAttr = 'color'; $sortDir = 'asc'; @@ -121,9 +121,10 @@ public function testVirtualReplicaConfig(): void $sorting = $this->configHelper->getSorting(); $sorting[] = [ - 'attribute' => $sortAttr, - 'sort' => $sortDir, - 'sortLabel' => $sortAttr + 'attribute' => $sortAttr, + 'sort' => $sortDir, + 'sortLabel' => $sortAttr, + 'virtualReplica' => 1 ]; $this->configHelper->setSorting($sorting); @@ -133,6 +134,56 @@ public function testVirtualReplicaConfig(): void $this->assertSortingAttribute($sortAttr, $sortDir); + // Cannot use config fixture because we have disabled db isolation + $this->setConfig('algoliasearch_instant/instant/is_instant_enabled', 1); + + $this->indicesConfigurator->saveConfigurationToAlgolia(1); + $this->algoliaHelper->waitLastTask(); + + // Assert replica config created + $currentSettings = $this->algoliaHelper->getSettings($indexName); + $this->assertArrayHasKey('replicas', $currentSettings); + + $sortIndexName = $indexName . '_' . $sortAttr . '_' . $sortDir; + + $this->assertTrue($this->isVirtualReplica($currentSettings['replicas'], $sortIndexName)); + $this->assertFalse($this->isStandardReplica($currentSettings['replicas'], $sortIndexName)); + + // Assert replica index created + $replicaSettings = $this->algoliaHelper->getSettings($sortIndexName); + $this->assertArrayHasKey('primary', $replicaSettings); + $this->assertEquals($indexName, $replicaSettings['primary']); + + // Restore prior state (for this test only) + $this->algoliaHelper->setSettings($indexName, $ogAlgoliaSettings); + $this->setConfig('algoliasearch_instant/instant/is_instant_enabled', 0); + + } + + /** + * @param string[] $replicaSetting + * @param string $replicaIndexName + * @return bool + */ + protected function isVirtualReplica(array $replicaSetting, string $replicaIndexName): bool + { + return (bool) array_filter( + $replicaSetting, + function ($replica) use ($replicaIndexName) { + return str_contains($replica, "virtual($replicaIndexName)"); + } + ); + } + + protected function isStandardReplica(array $replicaSetting, string $replicaIndexName): bool + { + return (bool) array_filter( + $replicaSetting, + function ($replica) use ($replicaIndexName) { + $regex = '/^' . preg_quote($replicaIndexName) . '$/'; + return preg_match($regex, $replica); + } + ); } public function tearDown(): void From c53e32bade916b6499a9e840bb57c9d8a34a4c0e Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Sun, 6 Oct 2024 21:38:59 -0400 Subject: [PATCH 18/40] MAGE-1044 Assert replica ranking by sort attribute --- Test/Integration/Product/ReplicaIndexingTest.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Test/Integration/Product/ReplicaIndexingTest.php b/Test/Integration/Product/ReplicaIndexingTest.php index c97e2c426..7bc182ca4 100644 --- a/Test/Integration/Product/ReplicaIndexingTest.php +++ b/Test/Integration/Product/ReplicaIndexingTest.php @@ -96,7 +96,7 @@ public function testStandardReplicaConfig(): void // Assert standard replica ranking config $this->assertArrayHasKey('ranking', $replicaSettings); - $this->assertContains("$sortDir($sortAttr)", $replicaSettings['ranking']); + $this->assertEquals("$sortDir($sortAttr)", array_shift($replicaSettings['ranking'])); } @@ -154,6 +154,10 @@ public function testVirtualReplicaConfig(): void $this->assertArrayHasKey('primary', $replicaSettings); $this->assertEquals($indexName, $replicaSettings['primary']); + // Assert virtual replica ranking config + $this->assertArrayHasKey('customRanking', $replicaSettings); + $this->assertEquals("$sortDir($sortAttr)", array_shift($replicaSettings['customRanking'])); + // Restore prior state (for this test only) $this->algoliaHelper->setSettings($indexName, $ogAlgoliaSettings); $this->setConfig('algoliasearch_instant/instant/is_instant_enabled', 0); From 49ea18a7434d3fa6cb417268b895b4575921d808 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Mon, 7 Oct 2024 09:59:48 -0400 Subject: [PATCH 19/40] MAGE-1044 Localize setup/teardown for virtual replica test case --- .../Product/ReplicaIndexingTest.php | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/Test/Integration/Product/ReplicaIndexingTest.php b/Test/Integration/Product/ReplicaIndexingTest.php index 7bc182ca4..1c67df4e7 100644 --- a/Test/Integration/Product/ReplicaIndexingTest.php +++ b/Test/Integration/Product/ReplicaIndexingTest.php @@ -17,8 +17,6 @@ class ReplicaIndexingTest extends IndexingTestCase protected ?string $indexSuffix = null; - protected ?array $ogSortingState = null; - public function setUp(): void { parent::setUp(); @@ -26,8 +24,6 @@ public function setUp(): void $this->replicaManager = $this->objectManager->get(ReplicaManagerInterface::class); $this->indicesConfigurator = $this->objectManager->get(IndicesConfigurator::class); $this->indexSuffix = 'products'; - - $this->ogSortingState = $this->configHelper->getSorting(); } protected function getIndexName(string $storeIndexPart): string @@ -40,28 +36,6 @@ public function processFullReindexProducts(): void $this->processFullReindex($this->productIndexer, $this->indexSuffix); } - protected function hasSortingAttribute($sortAttr, $sortDir): bool - { - $sorting = $this->configHelper->getSorting(); - return (bool) array_filter( - $sorting, - function($sort) use ($sortAttr, $sortDir) { - return $sort['attribute'] == $sortAttr - && $sort['sort'] == $sortDir; - } - ); - } - - protected function assertSortingAttribute($sortAttr, $sortDir): void - { - $this->assertTrue($this->hasSortingAttribute($sortAttr, $sortDir)); - } - - protected function assertNoSortingAttribute($sortAttr, $sortDir): void - { - $this->assertFalse($this->hasSortingAttribute($sortAttr, $sortDir)); - } - public function testReplicaLimits() { $this->assertEquals(20, $this->replicaManager->getMaxVirtualReplicasPerIndex()); @@ -101,6 +75,8 @@ public function testStandardReplicaConfig(): void } /** + * This test involves verifying modifications in the database + * so it must be responsible for its own set up and tear down * @magentoDbIsolation disabled * @group virtual */ @@ -108,6 +84,7 @@ public function testVirtualReplicaConfig(): void { $indexName = $this->getIndexName('default_'); $ogAlgoliaSettings = $this->algoliaHelper->getSettings($indexName); + $ogSortingState = $this->configHelper->getSorting(); $this->assertFalse(array_key_exists('replicas', $ogAlgoliaSettings)); @@ -160,8 +137,8 @@ public function testVirtualReplicaConfig(): void // Restore prior state (for this test only) $this->algoliaHelper->setSettings($indexName, $ogAlgoliaSettings); + $this->configHelper->setSorting($ogSortingState); $this->setConfig('algoliasearch_instant/instant/is_instant_enabled', 0); - } /** @@ -190,8 +167,31 @@ function ($replica) use ($replicaIndexName) { ); } + protected function hasSortingAttribute($sortAttr, $sortDir): bool + { + $sorting = $this->configHelper->getSorting(); + return (bool) array_filter( + $sorting, + function($sort) use ($sortAttr, $sortDir) { + return $sort['attribute'] == $sortAttr + && $sort['sort'] == $sortDir; + } + ); + } + + protected function assertSortingAttribute($sortAttr, $sortDir): void + { + $this->assertTrue($this->hasSortingAttribute($sortAttr, $sortDir)); + } + + protected function assertNoSortingAttribute($sortAttr, $sortDir): void + { + $this->assertFalse($this->hasSortingAttribute($sortAttr, $sortDir)); + } + public function tearDown(): void { - $this->configHelper->setSorting($this->ogSortingState); + parent::tearDown(); } + } From 8a1a68fc0dd7c17a0ab0f4a4a6828c01788c05e6 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Mon, 7 Oct 2024 15:33:49 -0400 Subject: [PATCH 20/40] MAGE-1044 Test replica sync command --- Console/Command/ReplicaSyncCommand.php | 6 +-- .../Product/ReplicaIndexingTest.php | 42 +++++++++++++++++++ Test/Integration/TestCase.php | 12 ++++++ 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/Console/Command/ReplicaSyncCommand.php b/Console/Command/ReplicaSyncCommand.php index edff1333a..dfc5b8ab3 100644 --- a/Console/Command/ReplicaSyncCommand.php +++ b/Console/Command/ReplicaSyncCommand.php @@ -11,7 +11,7 @@ use Algolia\AlgoliaSearch\Exceptions\ExceededRetriesException; use Algolia\AlgoliaSearch\Helper\Entity\ProductHelper; use Algolia\AlgoliaSearch\Service\StoreNameFetcher; -use Magento\Framework\App\State; +use Magento\Framework\App\State as AppState; use Magento\Framework\Console\Cli; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; @@ -27,12 +27,12 @@ public function __construct( protected ReplicaManagerInterface $replicaManager, protected ProductHelper $productHelper, protected StoreManagerInterface $storeManager, - State $state, + AppState $appState, StoreNameFetcher $storeNameFetcher, ?string $name = null ) { - parent::__construct($state, $storeNameFetcher, $name); + parent::__construct($appState, $storeNameFetcher, $name); } protected function getReplicaCommandName(): string diff --git a/Test/Integration/Product/ReplicaIndexingTest.php b/Test/Integration/Product/ReplicaIndexingTest.php index 1c67df4e7..0b72e8c1c 100644 --- a/Test/Integration/Product/ReplicaIndexingTest.php +++ b/Test/Integration/Product/ReplicaIndexingTest.php @@ -141,6 +141,48 @@ public function testVirtualReplicaConfig(): void $this->setConfig('algoliasearch_instant/instant/is_instant_enabled', 0); } + /** + * @depends testReplicaSync + * @magentoConfigFixture current_store algoliasearch_instant/instant/is_instant_enabled 1 + */ + public function testReplicaRebuild(): void + { + $indexName = $this->getIndexName('default_'); + $ogAlgoliaSettings = $this->algoliaHelper->getSettings($indexName); + + $cmd = $this->objectManager->get(\Algolia\AlgoliaSearch\Console\Command\ReplicaRebuildCommand::class); + $this->assertTrue(true); + } + + /** + * @magentoConfigFixture current_store algoliasearch_instant/instant/is_instant_enabled 1 + */ + public function testReplicaSync(): void + { + $indexName = $this->getIndexName('default_'); + $ogAlgoliaSettings = $this->algoliaHelper->getSettings($indexName); + $this->assertFalse(array_key_exists('replicas', $ogAlgoliaSettings)); + $sorting = $this->configHelper->getSorting(); + + $cmd = $this->objectManager->create(\Algolia\AlgoliaSearch\Console\Command\ReplicaSyncCommand::class); + + $this->mockProperty($cmd, 'output', \Symfony\Component\Console\Output\OutputInterface::class); + + $cmd->syncReplicas(); + +// $this->indicesConfigurator->saveConfigurationToAlgolia(1); +// $this->algoliaHelper->waitLastTask(); + + $currentSettings = $this->algoliaHelper->getSettings($indexName); + $this->assertArrayHasKey('replicas', $currentSettings); + + $this->assertTrue(count($currentSettings['replicas']) >= count($sorting)); + + // reset + $this->algoliaHelper->setSettings($indexName, $ogAlgoliaSettings); + } + + /** * @param string[] $replicaSetting * @param string $replicaIndexName diff --git a/Test/Integration/TestCase.php b/Test/Integration/TestCase.php index f61aeff66..ebad5a721 100644 --- a/Test/Integration/TestCase.php +++ b/Test/Integration/TestCase.php @@ -148,6 +148,18 @@ protected function setConfigFromArray(array $settings): void } } + /** + * @throws \ReflectionException + */ + protected function mockProperty($object, $propertyName, $propertyClass): void + { + $mock = $this->createMock($propertyClass); + $reflection = new \ReflectionClass($object); + $property = $reflection->getProperty($propertyName); + $property->setAccessible(true); + $property->setValue($object, $mock); + } + protected function clearIndices() { $indices = $this->algoliaHelper->listIndexes(); From d69759c9e07249de6e71b7673138e272f4725839 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Mon, 7 Oct 2024 17:35:58 -0400 Subject: [PATCH 21/40] MAGE-1044 Add mock sort update for in-memory config tests --- .../Product/ReplicaIndexingTest.php | 57 +++++++++++++------ 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/Test/Integration/Product/ReplicaIndexingTest.php b/Test/Integration/Product/ReplicaIndexingTest.php index 0b72e8c1c..c7043d828 100644 --- a/Test/Integration/Product/ReplicaIndexingTest.php +++ b/Test/Integration/Product/ReplicaIndexingTest.php @@ -3,6 +3,7 @@ namespace Algolia\AlgoliaSearch\Test\Integration\Product; use Algolia\AlgoliaSearch\Api\Product\ReplicaManagerInterface; +use Algolia\AlgoliaSearch\Helper\ConfigHelper; use Algolia\AlgoliaSearch\Helper\Entity\ProductHelper; use Algolia\AlgoliaSearch\Model\Indexer\Product as ProductIndexer; use Algolia\AlgoliaSearch\Model\IndicesConfigurator; @@ -83,11 +84,8 @@ public function testStandardReplicaConfig(): void public function testVirtualReplicaConfig(): void { $indexName = $this->getIndexName('default_'); - $ogAlgoliaSettings = $this->algoliaHelper->getSettings($indexName); $ogSortingState = $this->configHelper->getSorting(); - $this->assertFalse(array_key_exists('replicas', $ogAlgoliaSettings)); - $productHelper = $this->objectManager->get(ProductHelper::class); $sortAttr = 'color'; $sortDir = 'asc'; @@ -105,14 +103,14 @@ public function testVirtualReplicaConfig(): void ]; $this->configHelper->setSorting($sorting); - $this->assertConfigInDb('algoliasearch_instant/instant_sorts/sorts', json_encode($sorting)); + $this->assertConfigInDb(ConfigHelper::SORTING_INDICES, json_encode($sorting)); $this->refreshConfigFromDb(); $this->assertSortingAttribute($sortAttr, $sortDir); // Cannot use config fixture because we have disabled db isolation - $this->setConfig('algoliasearch_instant/instant/is_instant_enabled', 1); + $this->setConfig(ConfigHelper::IS_INSTANT_ENABLED, 1); $this->indicesConfigurator->saveConfigurationToAlgolia(1); $this->algoliaHelper->waitLastTask(); @@ -136,9 +134,37 @@ public function testVirtualReplicaConfig(): void $this->assertEquals("$sortDir($sortAttr)", array_shift($replicaSettings['customRanking'])); // Restore prior state (for this test only) - $this->algoliaHelper->setSettings($indexName, $ogAlgoliaSettings); $this->configHelper->setSorting($ogSortingState); - $this->setConfig('algoliasearch_instant/instant/is_instant_enabled', 0); + $this->setConfig(ConfigHelper::IS_INSTANT_ENABLED, 0); + } + + /** + * ConfigHelper::setSorting used WriterInterface which does not update unless DB isolation is disabled + * This provides a workaround to test using MutableScopeConfigInterface with DB isolation enabled + */ + protected function mockSortUpdate(string $sortAttr, string $sortDir, array $attr): void + { + $sorting = $this->configHelper->getSorting(); + $existing = array_filter($sorting, function ($item) use ($sortAttr, $sortDir) { + return $item['attribute'] === $sortAttr && $item['sort'] === $sortDir; + }); + + + if ($existing) { + $idx = array_key_first($existing); + $sorting[$idx] = array_merge($existing[$idx], $attr); + } + else { + $sorting[] = array_merge( + [ + 'attribute' => $sortAttr, + 'sort' => $sortDir, + 'sortLabel' => $sortAttr + ], + $attr + ); + } + $this->setConfig(ConfigHelper::SORTING_INDICES, json_encode($sorting)); } /** @@ -148,7 +174,6 @@ public function testVirtualReplicaConfig(): void public function testReplicaRebuild(): void { $indexName = $this->getIndexName('default_'); - $ogAlgoliaSettings = $this->algoliaHelper->getSettings($indexName); $cmd = $this->objectManager->get(\Algolia\AlgoliaSearch\Console\Command\ReplicaRebuildCommand::class); $this->assertTrue(true); @@ -156,13 +181,15 @@ public function testReplicaRebuild(): void /** * @magentoConfigFixture current_store algoliasearch_instant/instant/is_instant_enabled 1 + * @throws \ReflectionException */ public function testReplicaSync(): void { $indexName = $this->getIndexName('default_'); - $ogAlgoliaSettings = $this->algoliaHelper->getSettings($indexName); - $this->assertFalse(array_key_exists('replicas', $ogAlgoliaSettings)); - $sorting = $this->configHelper->getSorting(); + + $this->mockSortUpdate('created_at', 'desc', ['virtualReplica' => 1]); + + $sorting = $this->objectManager->get(\Algolia\AlgoliaSearch\Service\Product\SortingTransformer::class)->getSortingIndices(1, null, null, true); $cmd = $this->objectManager->create(\Algolia\AlgoliaSearch\Console\Command\ReplicaSyncCommand::class); @@ -170,16 +197,12 @@ public function testReplicaSync(): void $cmd->syncReplicas(); -// $this->indicesConfigurator->saveConfigurationToAlgolia(1); -// $this->algoliaHelper->waitLastTask(); - $currentSettings = $this->algoliaHelper->getSettings($indexName); $this->assertArrayHasKey('replicas', $currentSettings); + $replicas = $currentSettings['replicas']; - $this->assertTrue(count($currentSettings['replicas']) >= count($sorting)); + $this->assertTrue(count($replicas) >= count($sorting)); - // reset - $this->algoliaHelper->setSettings($indexName, $ogAlgoliaSettings); } From 34b41bbb56e9fb4099bad53ddcba43b4649cab35 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Mon, 7 Oct 2024 20:29:11 -0400 Subject: [PATCH 22/40] MAGE-1044 Verify replica index to sort attribute parity for sync command --- Test/Integration/Product/ReplicaIndexingTest.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Test/Integration/Product/ReplicaIndexingTest.php b/Test/Integration/Product/ReplicaIndexingTest.php index c7043d828..d63e431fc 100644 --- a/Test/Integration/Product/ReplicaIndexingTest.php +++ b/Test/Integration/Product/ReplicaIndexingTest.php @@ -139,7 +139,7 @@ public function testVirtualReplicaConfig(): void } /** - * ConfigHelper::setSorting used WriterInterface which does not update unless DB isolation is disabled + * ConfigHelper::setSorting uses WriterInterface which does not update unless DB isolation is disabled * This provides a workaround to test using MutableScopeConfigInterface with DB isolation enabled */ protected function mockSortUpdate(string $sortAttr, string $sortDir, array $attr): void @@ -201,7 +201,15 @@ public function testReplicaSync(): void $this->assertArrayHasKey('replicas', $currentSettings); $replicas = $currentSettings['replicas']; - $this->assertTrue(count($replicas) >= count($sorting)); + $this->assertEquals(count($sorting), count($replicas)); + + foreach ($sorting as $sortAttr) { + $replicaIndexName = $sortAttr['name']; + $needle = array_key_exists('virtualReplica', $sortAttr) && $sortAttr['virtualReplica'] + ? "virtual($replicaIndexName)" + : $replicaIndexName; + $this->assertContains($needle, $replicas); + } } From e420ed368de96df64747d86e59182a567e577d7d Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Mon, 7 Oct 2024 20:53:05 -0400 Subject: [PATCH 23/40] MAGE-1044 Refactor common assertions --- .../Product/ReplicaIndexingTest.php | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/Test/Integration/Product/ReplicaIndexingTest.php b/Test/Integration/Product/ReplicaIndexingTest.php index d63e431fc..5686fb903 100644 --- a/Test/Integration/Product/ReplicaIndexingTest.php +++ b/Test/Integration/Product/ReplicaIndexingTest.php @@ -64,15 +64,8 @@ public function testStandardReplicaConfig(): void $this->assertTrue($this->isStandardReplica($currentSettings['replicas'], $sortIndexName)); $this->assertFalse($this->isVirtualReplica($currentSettings['replicas'], $sortIndexName)); - // Assert replica index created - $replicaSettings = $this->algoliaHelper->getSettings($sortIndexName); - $this->assertArrayHasKey('primary', $replicaSettings); - $this->assertEquals($indexName, $replicaSettings['primary']); - - // Assert standard replica ranking config - $this->assertArrayHasKey('ranking', $replicaSettings); - $this->assertEquals("$sortDir($sortAttr)", array_shift($replicaSettings['ranking'])); - + $replicaSettings = $this->assertReplicaIndexExists($indexName, $sortIndexName); + $this->assertStandardReplicaRanking($replicaSettings, $sortAttr, $sortDir); } /** @@ -125,13 +118,8 @@ public function testVirtualReplicaConfig(): void $this->assertFalse($this->isStandardReplica($currentSettings['replicas'], $sortIndexName)); // Assert replica index created - $replicaSettings = $this->algoliaHelper->getSettings($sortIndexName); - $this->assertArrayHasKey('primary', $replicaSettings); - $this->assertEquals($indexName, $replicaSettings['primary']); - - // Assert virtual replica ranking config - $this->assertArrayHasKey('customRanking', $replicaSettings); - $this->assertEquals("$sortDir($sortAttr)", array_shift($replicaSettings['customRanking'])); + $replicaSettings = $this->assertReplicaIndexExists($indexName, $sortIndexName); + $this->assertVirtualReplicaRanking($replicaSettings, $sortAttr, $sortDir); // Restore prior state (for this test only) $this->configHelper->setSorting($ogSortingState); @@ -202,7 +190,14 @@ public function testReplicaSync(): void $replicas = $currentSettings['replicas']; $this->assertEquals(count($sorting), count($replicas)); + $this->assertSortToReplicaConfigParity($sorting, $replicas); + + // TODO: Test ranking + + } + protected function assertSortToReplicaConfigParity(array $sorting, array $replicas): void + { foreach ($sorting as $sortAttr) { $replicaIndexName = $sortAttr['name']; $needle = array_key_exists('virtualReplica', $sortAttr) && $sortAttr['virtualReplica'] @@ -210,9 +205,27 @@ public function testReplicaSync(): void : $replicaIndexName; $this->assertContains($needle, $replicas); } + } + protected function assertReplicaIndexExists(string $primaryIndexName, string $replicaIndexName): array + { + $replicaSettings = $this->algoliaHelper->getSettings($replicaIndexName); + $this->assertArrayHasKey('primary', $replicaSettings); + $this->assertEquals($primaryIndexName, $replicaSettings['primary']); + return $replicaSettings; } + protected function assertStandardReplicaRanking(array $replicaSettings, string $sortAttr, string $sortDir): void + { + $this->assertArrayHasKey('ranking', $replicaSettings); + $this->assertEquals("$sortDir($sortAttr)", array_shift($replicaSettings['ranking'])); + } + + protected function assertVirtualReplicaRanking(array $replicaSettings, string $sortAttr, string $sortDir): void + { + $this->assertArrayHasKey('customRanking', $replicaSettings); + $this->assertEquals("$sortDir($sortAttr)", array_shift($replicaSettings['customRanking'])); + } /** * @param string[] $replicaSetting From d3d00d2972b53ad5b0ae9c87cb8b6e495bcc564d Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Mon, 7 Oct 2024 21:59:50 -0400 Subject: [PATCH 24/40] MAGE-1044 Test ranking on replica sync command --- .../Product/ReplicaIndexingTest.php | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/Test/Integration/Product/ReplicaIndexingTest.php b/Test/Integration/Product/ReplicaIndexingTest.php index 5686fb903..4eef673ca 100644 --- a/Test/Integration/Product/ReplicaIndexingTest.php +++ b/Test/Integration/Product/ReplicaIndexingTest.php @@ -65,7 +65,7 @@ public function testStandardReplicaConfig(): void $this->assertFalse($this->isVirtualReplica($currentSettings['replicas'], $sortIndexName)); $replicaSettings = $this->assertReplicaIndexExists($indexName, $sortIndexName); - $this->assertStandardReplicaRanking($replicaSettings, $sortAttr, $sortDir); + $this->assertStandardReplicaRanking($replicaSettings, "$sortDir($sortAttr)"); } /** @@ -119,7 +119,7 @@ public function testVirtualReplicaConfig(): void // Assert replica index created $replicaSettings = $this->assertReplicaIndexExists($indexName, $sortIndexName); - $this->assertVirtualReplicaRanking($replicaSettings, $sortAttr, $sortDir); + $this->assertVirtualReplicaRanking($replicaSettings, "$sortDir($sortAttr)"); // Restore prior state (for this test only) $this->configHelper->setSorting($ogSortingState); @@ -184,26 +184,33 @@ public function testReplicaSync(): void $this->mockProperty($cmd, 'output', \Symfony\Component\Console\Output\OutputInterface::class); $cmd->syncReplicas(); + $this->algoliaHelper->waitLastTask(); $currentSettings = $this->algoliaHelper->getSettings($indexName); $this->assertArrayHasKey('replicas', $currentSettings); $replicas = $currentSettings['replicas']; $this->assertEquals(count($sorting), count($replicas)); - $this->assertSortToReplicaConfigParity($sorting, $replicas); - - // TODO: Test ranking - + $this->assertSortToReplicaConfigParity($indexName, $sorting, $replicas); } - protected function assertSortToReplicaConfigParity(array $sorting, array $replicas): void + protected function assertSortToReplicaConfigParity(string $primaryIndexName, array $sorting, array $replicas): void { foreach ($sorting as $sortAttr) { $replicaIndexName = $sortAttr['name']; - $needle = array_key_exists('virtualReplica', $sortAttr) && $sortAttr['virtualReplica'] + $isVirtual = array_key_exists('virtualReplica', $sortAttr) && $sortAttr['virtualReplica']; + $needle = $isVirtual ? "virtual($replicaIndexName)" : $replicaIndexName; $this->assertContains($needle, $replicas); + + $replicaSettings = $this->assertReplicaIndexExists($primaryIndexName, $replicaIndexName); + $sort = reset($sortAttr['ranking']); + if ($isVirtual) { + $this->assertVirtualReplicaRanking($replicaSettings, $sort); + } else { + $this->assertStandardReplicaRanking($replicaSettings, $sort); + } } } @@ -215,13 +222,28 @@ protected function assertReplicaIndexExists(string $primaryIndexName, string $re return $replicaSettings; } - protected function assertStandardReplicaRanking(array $replicaSettings, string $sortAttr, string $sortDir): void + protected function assertReplicaRanking(array $replicaSettings, string $rankingKey, string $sort) { + $this->assertArrayHasKey($rankingKey, $replicaSettings); + $this->assertEquals($sort, reset($replicaSettings[$rankingKey])); + } + + protected function assertStandardReplicaRanking(array $replicaSettings, string $sort): void + { + $this->assertReplicaRanking($replicaSettings, 'ranking', $sort); + } + + protected function assertVirtualReplicaRanking(array $replicaSettings, string $sort): void + { + $this->assertReplicaRanking($replicaSettings, 'customRanking', $sort); + } + + protected function assertStandardReplicaRankingOld(array $replicaSettings, string $sortAttr, string $sortDir): void { $this->assertArrayHasKey('ranking', $replicaSettings); $this->assertEquals("$sortDir($sortAttr)", array_shift($replicaSettings['ranking'])); } - protected function assertVirtualReplicaRanking(array $replicaSettings, string $sortAttr, string $sortDir): void + protected function assertVirtualReplicaRankingOld(array $replicaSettings, string $sortAttr, string $sortDir): void { $this->assertArrayHasKey('customRanking', $replicaSettings); $this->assertEquals("$sortDir($sortAttr)", array_shift($replicaSettings['customRanking'])); From 6b08374f89c9cf4f1cad8733bfeee9a3186a6c81 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Mon, 7 Oct 2024 22:43:15 -0400 Subject: [PATCH 25/40] MAGE-1044 Test replica rebuild command --- .../Product/ReplicaIndexingTest.php | 34 +++++++++++++++++-- Test/Integration/TestCase.php | 12 +++++-- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/Test/Integration/Product/ReplicaIndexingTest.php b/Test/Integration/Product/ReplicaIndexingTest.php index 4eef673ca..d20a0ba87 100644 --- a/Test/Integration/Product/ReplicaIndexingTest.php +++ b/Test/Integration/Product/ReplicaIndexingTest.php @@ -3,6 +3,8 @@ namespace Algolia\AlgoliaSearch\Test\Integration\Product; use Algolia\AlgoliaSearch\Api\Product\ReplicaManagerInterface; +use Algolia\AlgoliaSearch\Exceptions\AlgoliaException; +use Algolia\AlgoliaSearch\Exceptions\ExceededRetriesException; use Algolia\AlgoliaSearch\Helper\ConfigHelper; use Algolia\AlgoliaSearch\Helper\Entity\ProductHelper; use Algolia\AlgoliaSearch\Model\Indexer\Product as ProductIndexer; @@ -158,17 +160,43 @@ protected function mockSortUpdate(string $sortAttr, string $sortDir, array $attr /** * @depends testReplicaSync * @magentoConfigFixture current_store algoliasearch_instant/instant/is_instant_enabled 1 + * @throws AlgoliaException + * @throws ExceededRetriesException + * @throws \ReflectionException */ public function testReplicaRebuild(): void { $indexName = $this->getIndexName('default_'); - $cmd = $this->objectManager->get(\Algolia\AlgoliaSearch\Console\Command\ReplicaRebuildCommand::class); - $this->assertTrue(true); + $this->mockSortUpdate('price', 'desc', ['virtualReplica' => 1]); + $sorting = $this->objectManager->get(\Algolia\AlgoliaSearch\Service\Product\SortingTransformer::class)->getSortingIndices(1, null, null, true); + + $syncCmd = $this->objectManager->get(\Algolia\AlgoliaSearch\Console\Command\ReplicaSyncCommand::class); + $this->mockProperty($syncCmd, 'output', \Symfony\Component\Console\Output\OutputInterface::class); + $syncCmd->syncReplicas(); + $this->algoliaHelper->waitLastTask(); + + $rebuildCmd = $this->objectManager->get(\Algolia\AlgoliaSearch\Console\Command\ReplicaRebuildCommand::class); + $this->callReflectedMethod( + $rebuildCmd, + 'execute', + $this->createMock(\Symfony\Component\Console\Input\InputInterface::class), + $this->createMock(\Symfony\Component\Console\Output\OutputInterface::class) + ); + $this->algoliaHelper->waitLastTask(); + + $currentSettings = $this->algoliaHelper->getSettings($indexName); + $this->assertArrayHasKey('replicas', $currentSettings); + $replicas = $currentSettings['replicas']; + + $this->assertEquals(count($sorting), count($replicas)); + $this->assertSortToReplicaConfigParity($indexName, $sorting, $replicas); } /** * @magentoConfigFixture current_store algoliasearch_instant/instant/is_instant_enabled 1 + * @throws AlgoliaException + * @throws ExceededRetriesException * @throws \ReflectionException */ public function testReplicaSync(): void @@ -179,7 +207,7 @@ public function testReplicaSync(): void $sorting = $this->objectManager->get(\Algolia\AlgoliaSearch\Service\Product\SortingTransformer::class)->getSortingIndices(1, null, null, true); - $cmd = $this->objectManager->create(\Algolia\AlgoliaSearch\Console\Command\ReplicaSyncCommand::class); + $cmd = $this->objectManager->get(\Algolia\AlgoliaSearch\Console\Command\ReplicaSyncCommand::class); $this->mockProperty($cmd, 'output', \Symfony\Component\Console\Output\OutputInterface::class); diff --git a/Test/Integration/TestCase.php b/Test/Integration/TestCase.php index ebad5a721..5f28cde7d 100644 --- a/Test/Integration/TestCase.php +++ b/Test/Integration/TestCase.php @@ -151,15 +151,23 @@ protected function setConfigFromArray(array $settings): void /** * @throws \ReflectionException */ - protected function mockProperty($object, $propertyName, $propertyClass): void + protected function mockProperty(object $object, string $propertyName, string $propertyClass): void { $mock = $this->createMock($propertyClass); $reflection = new \ReflectionClass($object); $property = $reflection->getProperty($propertyName); - $property->setAccessible(true); $property->setValue($object, $mock); } + /** + * @throws \ReflectionException + */ + protected function callReflectedMethod(object $object, string $method, mixed ...$args): void + { + $reflection = new \ReflectionClass($object); + $reflection->getMethod($method)->invoke($object, ...$args); + } + protected function clearIndices() { $indices = $this->algoliaHelper->listIndexes(); From 48be4f8387a62226aa58c1186be2fa455f65f8bb Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Tue, 8 Oct 2024 11:54:47 -0400 Subject: [PATCH 26/40] MAGE-1044 Remove placeholder tearDown --- Test/Integration/Product/ReplicaIndexingTest.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Test/Integration/Product/ReplicaIndexingTest.php b/Test/Integration/Product/ReplicaIndexingTest.php index d20a0ba87..0a175f062 100644 --- a/Test/Integration/Product/ReplicaIndexingTest.php +++ b/Test/Integration/Product/ReplicaIndexingTest.php @@ -325,9 +325,4 @@ protected function assertNoSortingAttribute($sortAttr, $sortDir): void $this->assertFalse($this->hasSortingAttribute($sortAttr, $sortDir)); } - public function tearDown(): void - { - parent::tearDown(); - } - } From 3faa84486bc446befbcc029d45592e048aaee58c Mon Sep 17 00:00:00 2001 From: Damien Couchez Date: Thu, 10 Oct 2024 16:32:33 +0200 Subject: [PATCH 27/40] MAGE-1057: Fix/add product tests (#1626) * MAGE-1057: fix specialPrice test * MAGE-1057: add new fixtures * MAGE-1057: add first product tests for multistore scenarios * MAGE-1057: add Rules test * MAGE-1057: fix flakiness when all tests are run at once * MAGE-1057: changes after review * MAGE-1057: handle EE and CE expected values * MAGE-1057: merge fix * MAGE-1057: fix rule creation flakiness * MAGE-1057: make sure to use AlgoliaHelper when managing Rules to be sure to use waitForLastTask() efficiently * MAGE-1057: remove unused client instanciation --- Helper/AlgoliaHelper.php | 29 +++ Helper/Entity/ProductHelper.php | 21 +- Helper/Logger.php | 2 +- .../Integration/AssertValues/Magento246CE.php | 8 + .../Integration/AssertValues/Magento246EE.php | 8 + .../Integration/AssertValues/Magento247CE.php | 8 + .../Integration/AssertValues/Magento247EE.php | 8 + .../{Magento246.php => Magento24CE.php} | 3 +- .../{Magento247.php => Magento24EE.php} | 3 +- .../Config/MultiStoreConfigTest.php | 54 +++-- Test/Integration/IndexingTestCase.php | 21 ++ Test/Integration/MultiStoreTestCase.php | 30 +-- .../Product/MultiStoreProductsTest.php | 204 ++++++++++++++++++ .../Product/ProductsIndexingTest.php | 125 ++++++----- Test/Integration/TestCase.php | 34 ++- ...d_website_with_two_stores_and_products.php | 93 ++++++++ ..._with_two_stores_and_products_rollback.php | 24 +++ 17 files changed, 565 insertions(+), 110 deletions(-) create mode 100644 Test/Integration/AssertValues/Magento246CE.php create mode 100644 Test/Integration/AssertValues/Magento246EE.php create mode 100644 Test/Integration/AssertValues/Magento247CE.php create mode 100644 Test/Integration/AssertValues/Magento247EE.php rename Test/Integration/AssertValues/{Magento246.php => Magento24CE.php} (83%) rename Test/Integration/AssertValues/{Magento247.php => Magento24EE.php} (83%) create mode 100644 Test/Integration/Product/MultiStoreProductsTest.php create mode 100644 Test/Integration/_files/second_website_with_two_stores_and_products.php create mode 100644 Test/Integration/_files/second_website_with_two_stores_and_products_rollback.php diff --git a/Helper/AlgoliaHelper.php b/Helper/AlgoliaHelper.php index 593bb96ff..41181560f 100755 --- a/Helper/AlgoliaHelper.php +++ b/Helper/AlgoliaHelper.php @@ -6,6 +6,7 @@ use Algolia\AlgoliaSearch\Configuration\SearchConfig; use Algolia\AlgoliaSearch\Exceptions\AlgoliaException; use Algolia\AlgoliaSearch\Exceptions\ExceededRetriesException; +use Algolia\AlgoliaSearch\Model\Search\SearchRulesResponse; use Algolia\AlgoliaSearch\Response\AbstractResponse; use Algolia\AlgoliaSearch\Response\BatchIndexingResponse; use Algolia\AlgoliaSearch\Response\MultiResponse; @@ -453,6 +454,19 @@ public function saveRule(array $rule, string $indexName, bool $forwardToReplicas self::setLastOperationInfo($indexName, $res); } + /** + * @param string $indexName + * @param array $rules + * @param bool $forwardToReplicas + * @return void + */ + public function saveRules(string $indexName, array $rules, bool $forwardToReplicas = false): void + { + $res = $this->client->saveRules($indexName, $rules, $forwardToReplicas); + + self::setLastOperationInfo($indexName, $res); + } + /** * @param string $indexName @@ -521,6 +535,21 @@ public function copyQueryRules(string $fromIndexName, string $toIndexName): void self::setLastOperationInfo($fromIndexName, $response); } + /** + * @param string $indexName + * @param array|null $searchRulesParams + * + * @return SearchRulesResponse|mixed[] + * + * @throws AlgoliaException + */ + public function searchRules(string $indexName, array$searchRulesParams = null) + { + $this->checkClient(__FUNCTION__); + + return $this->client->searchRules($indexName, $searchRulesParams); + } + /** * @param $methodName * @return void diff --git a/Helper/Entity/ProductHelper.php b/Helper/Entity/ProductHelper.php index 48bed478a..250d3cb63 100755 --- a/Helper/Entity/ProductHelper.php +++ b/Helper/Entity/ProductHelper.php @@ -359,9 +359,12 @@ public function setSettings(string $indexName, string $indexNameTmp, int $storeI $this->logger->log('Pushing the same settings to TMP index as well'); } - $this->setFacetsQueryRules($indexName); + $this->setFacetsQueryRules($indexName, $storeId); + $this->algoliaHelper->waitLastTask(); + if ($saveToTmpIndicesToo) { - $this->setFacetsQueryRules($indexNameTmp); + $this->setFacetsQueryRules($indexNameTmp, $storeId); + $this->algoliaHelper->waitLastTask(); } $this->replicaManager->syncReplicasToAlgolia($storeId, $indexSettings); @@ -1204,17 +1207,16 @@ protected function getAttributesForFaceting($storeId) /** * @param $indexName + * @param $storeId * @return void * @throws AlgoliaException */ - protected function setFacetsQueryRules($indexName) + protected function setFacetsQueryRules($indexName, $storeId = null) { - $client = $this->algoliaHelper->getClient(); - $this->clearFacetsQueryRules($indexName); $rules = []; - $facets = $this->configHelper->getFacets(); + $facets = $this->configHelper->getFacets($storeId); foreach ($facets as $facet) { if (!array_key_exists('create_rule', $facet) || $facet['create_rule'] !== '1') { continue; @@ -1245,7 +1247,7 @@ protected function setFacetsQueryRules($indexName) if ($rules) { $this->logger->log('Setting facets query rules to "' . $indexName . '" index: ' . json_encode($rules)); - $client->saveRules($indexName, $rules, true); + $this->algoliaHelper->saveRules($indexName, $rules, true); } } @@ -1260,8 +1262,7 @@ protected function clearFacetsQueryRules($indexName): void $hitsPerPage = 100; $page = 0; do { - $client = $this->algoliaHelper->getClient(); - $fetchedQueryRules = $client->searchRules($indexName, [ + $fetchedQueryRules = $this->algoliaHelper->searchRules($indexName, [ 'context' => 'magento_filters', 'page' => $page, 'hitsPerPage' => $hitsPerPage, @@ -1273,7 +1274,7 @@ protected function clearFacetsQueryRules($indexName): void } foreach ($fetchedQueryRules['hits'] as $hit) { - $client->deleteRule($indexName, $hit[AlgoliaHelper::ALGOLIA_API_OBJECT_ID], true); + $this->algoliaHelper->deleteRule($indexName, $hit[AlgoliaHelper::ALGOLIA_API_OBJECT_ID], true); } $page++; diff --git a/Helper/Logger.php b/Helper/Logger.php index a50b9a739..e639f8cc2 100755 --- a/Helper/Logger.php +++ b/Helper/Logger.php @@ -35,7 +35,7 @@ public function isEnable() public function getStoreName($storeId) { - if ($storeId === null) { + if ($storeId === null || !isset($this->stores[$storeId])) { return 'undefined store'; } diff --git a/Test/Integration/AssertValues/Magento246CE.php b/Test/Integration/AssertValues/Magento246CE.php new file mode 100644 index 000000000..62be64d29 --- /dev/null +++ b/Test/Integration/AssertValues/Magento246CE.php @@ -0,0 +1,8 @@ +storeManager->getStores(); // Check that stores and websites are properly created - $this->assertEquals(count($websites), 2); - $this->assertEquals(count($stores), 3); + $this->assertEquals(2, count($websites)); + $this->assertEquals(3, count($stores)); foreach ($stores as $store) { - $this->setupStore($store); + $this->setupStore($store, true); } $indicesCreatedByTest = 0; @@ -37,9 +37,8 @@ public function testMultiStoreIndicesCreation() } } - // Check that the configuration created the appropriate number of indices (4 per store => 3*4=12) - $this->assertEquals($indicesCreatedByTest, 12); - + // Check that the configuration created the appropriate number of indices (7 (4 mains + 3 replicas per store => 3*7=21) + $this->assertEquals(21, $indicesCreatedByTest); $defaultStore = $this->storeRepository->get('default'); $fixtureSecondStore = $this->storeRepository->get('fixture_second_store'); @@ -73,17 +72,48 @@ public function testMultiStoreIndicesCreation() $fixtureSecondStore->getCode()) ; + // Query rules check (activate one QR on the fixture store) + $facetsFromConfig = $this->configHelper->getFacets($defaultStore->getId()); + $facetsFromConfigAlt = $facetsFromConfig; + foreach ($facetsFromConfigAlt as $key => $facet) { + if ($facet['attribute'] === "color") { + $facetsFromConfigAlt[$key]['create_rule'] = "1"; + break; + } + } + + $this->setConfig( + ConfigHelper::FACETS, + json_encode($facetsFromConfigAlt), + $fixtureSecondStore->getCode() + ); + $this->indicesConfigurator->saveConfigurationToAlgolia($fixtureSecondStore->getId()); + $this->algoliaHelper->waitLastTask(); - $defaultIndexSettings = $this->algoliaHelper->getSettings($this->indexPrefix . 'default_categories'); - $fixtureIndexSettings = $this->algoliaHelper->getSettings($this->indexPrefix . 'fixture_second_store_categories'); + $defaultCategoryIndexSettings = $this->algoliaHelper->getSettings($this->indexPrefix . 'default_categories'); + $fixtureCategoryIndexSettings = $this->algoliaHelper->getSettings($this->indexPrefix . 'fixture_second_store_categories'); $attributeFromConfig = 'unordered(' . self::ADDITIONAL_ATTRIBUTE . ')'; - $this->assertNotContains($attributeFromConfig, $defaultIndexSettings['searchableAttributes']); - $this->assertContains($attributeFromConfig, $fixtureIndexSettings['searchableAttributes']); + $this->assertNotContains($attributeFromConfig, $defaultCategoryIndexSettings['searchableAttributes']); + $this->assertContains($attributeFromConfig, $fixtureCategoryIndexSettings['searchableAttributes']); $rankingFromConfig = 'desc(' . self::ADDITIONAL_ATTRIBUTE . ')'; - $this->assertNotContains($rankingFromConfig, $defaultIndexSettings['customRanking']); - $this->assertContains($rankingFromConfig, $fixtureIndexSettings['customRanking']); + $this->assertNotContains($rankingFromConfig, $defaultCategoryIndexSettings['customRanking']); + $this->assertContains($rankingFromConfig, $fixtureCategoryIndexSettings['customRanking']); + + $defaultProductIndexRules = $this->algoliaHelper->searchRules($this->indexPrefix . 'default_products'); + $fixtureProductIndexRules = $this->algoliaHelper->searchRules($this->indexPrefix . 'fixture_second_store_products'); + + // Check that the Rule has only been created for the fixture store + $this->assertEquals(0, $defaultProductIndexRules['nbHits']); + $this->assertEquals(1, $fixtureProductIndexRules['nbHits']); + } + + public function tearDown(): void + { + parent::tearDown(); + + $this->setConfig(ConfigHelper::IS_INSTANT_ENABLED, 0); } } diff --git a/Test/Integration/IndexingTestCase.php b/Test/Integration/IndexingTestCase.php index 69db4f641..f5432c689 100644 --- a/Test/Integration/IndexingTestCase.php +++ b/Test/Integration/IndexingTestCase.php @@ -2,6 +2,7 @@ namespace Algolia\AlgoliaSearch\Test\Integration; +use Algolia\AlgoliaSearch\Exceptions\AlgoliaException; use Magento\Framework\Indexer\ActionInterface; abstract class IndexingTestCase extends TestCase @@ -25,4 +26,24 @@ protected function processTest(ActionInterface $indexer, $indexSuffix, $expected $this->assertEquals($expectedNbHits, $resultsDefault['results'][0]['nbHits']); } + + /** + * @param string $indexName + * @param string $recordId + * @param array $expectedValues + * + * @return void + * @throws AlgoliaException + */ + public function assertAlgoliaRecordValues( + string $indexName, + string $recordId, + array $expectedValues + ) : void { + $res = $this->algoliaHelper->getObjects($indexName, [$recordId]); + $record = reset($res['results']); + foreach ($expectedValues as $attribute => $expectedValue) { + $this->assertEquals($expectedValue, $record[$attribute]); + } + } } diff --git a/Test/Integration/MultiStoreTestCase.php b/Test/Integration/MultiStoreTestCase.php index f5458b646..3119a6261 100644 --- a/Test/Integration/MultiStoreTestCase.php +++ b/Test/Integration/MultiStoreTestCase.php @@ -3,6 +3,7 @@ namespace Algolia\AlgoliaSearch\Test\Integration; use Algolia\AlgoliaSearch\Exceptions\AlgoliaException; +use Algolia\AlgoliaSearch\Helper\ConfigHelper; use Algolia\AlgoliaSearch\Model\IndicesConfigurator; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; @@ -54,35 +55,16 @@ protected function assertNbOfRecordsPerStore(string $storeCode, string $entity, $this->assertEquals($expectedNumber, $resultsDefault['results'][0]['nbHits']); } - /** - * @param string $indexName - * @param string $recordId - * @param array $expectedValues - * - * @return void - * @throws AlgoliaException - */ - public function assertAlgoliaRecordValues( - string $indexName, - string $recordId, - array $expectedValues - ) : void { - $res = $this->algoliaHelper->getObjects($indexName, [$recordId]); - $record = reset($res['results']); - foreach ($expectedValues as $attribute => $expectedValue) { - $this->assertEquals($expectedValue, $record[$attribute]); - } - } - /** * @param StoreInterface $store + * @param bool $enableInstantSearch * * @return void * @throws AlgoliaException * @throws LocalizedException * @throws NoSuchEntityException */ - protected function setupStore(StoreInterface $store): void + protected function setupStore(StoreInterface $store, bool $enableInstantSearch = false): void { $this->setConfig( 'algoliasearch_credentials/credentials/application_id', @@ -105,7 +87,11 @@ protected function setupStore(StoreInterface $store): void $store->getCode() ); + if ($enableInstantSearch) { + $this->setConfig(ConfigHelper::IS_INSTANT_ENABLED, 1, $store->getCode()); + } + $this->indicesConfigurator->saveConfigurationToAlgolia($store->getId()); + $this->algoliaHelper->waitLastTask(); } - } diff --git a/Test/Integration/Product/MultiStoreProductsTest.php b/Test/Integration/Product/MultiStoreProductsTest.php new file mode 100644 index 000000000..97162ae5b --- /dev/null +++ b/Test/Integration/Product/MultiStoreProductsTest.php @@ -0,0 +1,204 @@ +productsIndexer = $this->objectManager->get(Product::class); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->productCollectionFactory = $this->objectManager->get(CollectionFactory::class); + $this->websiteRepository = $this->objectManager->get(WebsiteRepositoryInterface::class); + + $this->indexerRegistry = $this->objectManager->get(IndexerRegistry::class); + $this->productPriceIndexer = $this->indexerRegistry->get('catalog_product_price'); + $this->productPriceIndexer->reindexAll(); + + $this->productsIndexer->executeFull(); + $this->algoliaHelper->waitLastTask(); + } + + public function testMultiStoreProductIndices() + { + // Check that every store has the right number of products + foreach ($this->storeManager->getStores() as $store) { + $this->assertNbOfRecordsPerStore( + $store->getCode(), + 'products', + $store->getCode() === 'default' ? + $this->assertValues->productsCountWithoutGiftcards : + count(self::SKUS) + ); + } + + $defaultStore = $this->storeRepository->get('default'); + $fixtureSecondStore = $this->storeRepository->get('fixture_second_store'); + $fixtureThirdStore = $this->storeRepository->get('fixture_third_store'); + + try { + $voyageYogaBag = $this->loadProduct(self::VOYAGE_YOGA_BAG_ID, $defaultStore->getId()); + } catch (\Exception $e) { + $this->markTestIncomplete('Product could not be found.'); + } + + $this->assertEquals(self::VOYAGE_YOGA_BAG_NAME, $voyageYogaBag->getName()); + + // Change a product name at store level + $voyageYogaBagAlt = $this->updateProduct( + self::VOYAGE_YOGA_BAG_ID, + $fixtureSecondStore->getId(), + ['name' => self::VOYAGE_YOGA_BAG_NAME_ALT] + ); + + $this->assertEquals(self::VOYAGE_YOGA_BAG_NAME, $voyageYogaBag->getName()); + $this->assertEquals(self::VOYAGE_YOGA_BAG_NAME_ALT, $voyageYogaBagAlt->getName()); + + $this->productsIndexer->execute([self::VOYAGE_YOGA_BAG_ID]); + $this->algoliaHelper->waitLastTask(); + + $this->assertAlgoliaRecordValues( + $this->indexPrefix . 'default_products', + (string) self::VOYAGE_YOGA_BAG_ID, + ['name' => self::VOYAGE_YOGA_BAG_NAME] + ); + + $this->assertAlgoliaRecordValues( + $this->indexPrefix . 'fixture_second_store_products', + (string) self::VOYAGE_YOGA_BAG_ID, + ['name' => self::VOYAGE_YOGA_BAG_NAME_ALT] + ); + + // Unassign product from a single website (removed from test website (second and third store)) + $baseWebsite = $this->websiteRepository->get('base'); + + $voyageYogaBag = $this->loadProduct(self::VOYAGE_YOGA_BAG_ID); + + $voyageYogaBag->setWebsiteIds([$baseWebsite->getId()]); + $this->productRepository->save($voyageYogaBag); + $this->productPriceIndexer->reindexRow(self::VOYAGE_YOGA_BAG_ID); + + $this->productsIndexer->execute([self::VOYAGE_YOGA_BAG_ID]); + $this->algoliaHelper->waitLastTask(); + + // default store should have the same number of products + $this->assertNbOfRecordsPerStore( + $defaultStore->getCode(), + 'products', + $this->assertValues->productsCountWithoutGiftcards + ); + + // Stores from test website must have one less product + $this->assertNbOfRecordsPerStore( + $fixtureThirdStore->getCode(), + 'products', + count(self::SKUS) - 1 + ); + + $this->assertNbOfRecordsPerStore( + $fixtureSecondStore->getCode(), + 'products', + count(self::SKUS) - 1 + ); + } + + /** + * Loads product by id. + * + * @param int $productId + * @param int|null $storeId + * + * @return ProductInterface + * @throws NoSuchEntityException + */ + private function loadProduct(int $productId, int $storeId = null): ProductInterface + { + return $this->productRepository->getById($productId, true, $storeId); + } + + /** + * @param int $productId + * @param int $storeId + * @param array $values + * + * @return ProductInterface + * @throws CouldNotSaveException + * @throws NoSuchEntityException + * + */ + private function updateProduct(int $productId, int $storeId, array $values): ProductInterface + { + $oldStoreId = $this->storeManager->getStore()->getId(); + $this->storeManager->setCurrentStore($storeId); + $product = $this->loadProduct($productId, $storeId); + foreach ($values as $attribute => $value) { + $product->setData($attribute, $value); + } + $productAlt = $this->productRepository->save($product); + $this->storeManager->setCurrentStore($oldStoreId); + + return $productAlt; + } + + public function tearDown(): void + { + $defaultStore = $this->storeRepository->get('default'); + + // Restore product name in case DB is not cleaned up + $this->updateProduct( + self::VOYAGE_YOGA_BAG_ID, + $defaultStore->getId(), + [ + 'name' => self::VOYAGE_YOGA_BAG_NAME, + ] + ); + + parent::tearDown(); + } +} diff --git a/Test/Integration/Product/ProductsIndexingTest.php b/Test/Integration/Product/ProductsIndexingTest.php index c703b408c..e5d5411a8 100644 --- a/Test/Integration/Product/ProductsIndexingTest.php +++ b/Test/Integration/Product/ProductsIndexingTest.php @@ -2,8 +2,11 @@ namespace Algolia\AlgoliaSearch\Test\Integration\Product; -use Algolia\AlgoliaSearch\Model\Indexer\Product; +use Algolia\AlgoliaSearch\Helper\ConfigHelper; +use Algolia\AlgoliaSearch\Model\Indexer\Product as ProductIndexer; use Algolia\AlgoliaSearch\Test\Integration\IndexingTestCase; +use Magento\Catalog\Api\Data\SpecialPriceInterfaceFactory; +use Magento\Catalog\Model\Product; use Magento\CatalogInventory\Model\StockRegistry; use Magento\Framework\Indexer\IndexerRegistry; @@ -13,7 +16,7 @@ */ class ProductsIndexingTest extends IndexingTestCase { - /** @var Product */ + /** @var ProductIndexer */ protected $productsIndexer; /** @var StockRegistry */ @@ -22,36 +25,40 @@ class ProductsIndexingTest extends IndexingTestCase /*** @var IndexerRegistry */ protected $indexerRegistry; + protected $productPriceIndexer; + protected $testProductId; + const SPECIAL_PRICE_TEST_PRODUCT_ID = 9; + + const OUT_OF_STOCK_PRODUCT_SKU = '24-MB01'; + public function setUp():void { parent::setUp(); - $this->productsIndexer = $this->objectManager->get(Product::class); + $this->productsIndexer = $this->objectManager->get(ProductIndexer::class); $this->stockRegistry = $this->objectManager->get(StockRegistry::class); $this->indexerRegistry = $this->objectManager->get(IndexerRegistry::class); - $priceIndexer = $this->indexerRegistry->get('catalog_product_price'); - $priceIndexer->reindexAll(); + $this->productPriceIndexer = $this->indexerRegistry->get('catalog_product_price'); + $this->productPriceIndexer->reindexAll(); } public function testOnlyOnStockProducts() { - $this->assertEquals(true, true); + $this->setConfig(ConfigHelper::SHOW_OUT_OF_STOCK, 0); - $this->setConfig('cataloginventory/options/show_out_of_stock', 0); - - $this->setOneProductOutOfStock(); + $this->updateStockItem(self::OUT_OF_STOCK_PRODUCT_SKU, false); $this->processTest($this->productsIndexer, 'products', $this->assertValues->productsOnStockCount); } public function testIncludingOutOfStock() { - $this->setConfig('cataloginventory/options/show_out_of_stock', 1); + $this->setConfig(ConfigHelper::SHOW_OUT_OF_STOCK, 1); - $this->setOneProductOutOfStock(); + $this->updateStockItem(self::OUT_OF_STOCK_PRODUCT_SKU, false); $this->processTest($this->productsIndexer, 'products', $this->assertValues->productsOutOfStockCount); } @@ -60,15 +67,12 @@ public function testDefaultIndexableAttributes() { $empty = $this->getSerializer()->serialize([]); - $this->setConfig('algoliasearch_products/products/product_additional_attributes', $empty); - $this->setConfig('algoliasearch_instant/instant_facets/facets', $empty); - $this->setConfig('algoliasearch_instant/instant_sorts/sorts', $empty); - $this->setConfig('algoliasearch_products/products/custom_ranking_product_attributes', $empty); - - /** @var Product $indexer */ - $indexer = $this->getObjectManager()->create(Product::class); - $indexer->executeRow($this->getValidTestProduct()); + $this->setConfig(ConfigHelper::PRODUCT_ATTRIBUTES, $empty); + $this->setConfig(ConfigHelper::FACETS, $empty); + $this->setConfig(ConfigHelper::SORTING_INDICES, $empty); + $this->setConfig(ConfigHelper::PRODUCT_CUSTOM_RANKING, $empty); + $this->productsIndexer->executeRow($this->getValidTestProduct()); $this->algoliaHelper->waitLastTask(); $results = $this->algoliaHelper->getObjects($this->indexPrefix . 'default_products', [$this->getValidTestProduct()]); @@ -104,13 +108,16 @@ public function testDefaultIndexableAttributes() $this->assertEmpty($hit, 'Extra products attributes (' . $extraAttributes . ') are indexed and should not be.'); } - public function testNoSpecialPrice() + public function testSpecialPrice() { - $this->productsIndexer->execute([9]); - + $this->productsIndexer->execute([self::SPECIAL_PRICE_TEST_PRODUCT_ID]); $this->algoliaHelper->waitLastTask(); - $res = $this->algoliaHelper->getObjects($this->indexPrefix . 'default_products', ['9']); + $res = $this->algoliaHelper->getObjects( + $this->indexPrefix . + 'default_products', + [(string) self::SPECIAL_PRICE_TEST_PRODUCT_ID] + ); $algoliaProduct = reset($res['results']); if (!$algoliaProduct || !array_key_exists('price', $algoliaProduct)) { @@ -120,61 +127,71 @@ public function testNoSpecialPrice() $this->assertEquals(32, $algoliaProduct['price']['USD']['default']); $this->assertEquals('', $algoliaProduct['price']['USD']['special_from_date']); $this->assertEquals('', $algoliaProduct['price']['USD']['special_to_date']); - } - public function deprecatedTestSpecialPrice() - { - /** @var \Magento\Framework\App\ProductMetadataInterface $productMetadata */ - $productMetadata = $this->getObjectManager()->create(\Magento\Framework\App\ProductMetadataInterface::class); - $version = $productMetadata->getVersion(); - if (version_compare($version, '2.1', '<') === true) { - $this->markTestSkipped(); - } + $specialPrice = 29; + $fromDatetime = new \DateTime(); + $toDatetime = new \DateTime(); + $priceFrom = $fromDatetime->modify('-2 day')->format('Y-m-d H:i:s'); + $priceTo = $toDatetime->modify('+2 day')->format('Y-m-d H:i:s'); - /** @var \Magento\Catalog\Model\Product $product */ - $product = $this->getObjectManager()->create(\Magento\Catalog\Model\Product::class); - $product->load(9); + $product = $this->objectManager->create(\Magento\Catalog\Model\Product::class); + $product->load(self::SPECIAL_PRICE_TEST_PRODUCT_ID); - $specialPrice = 29; - $from = 1452556800; - $to = 1699920000; $product->setCustomAttributes([ 'special_price' => $specialPrice, - 'special_from_date' => date('m/d/Y', $from), - 'special_to_date' => date('m/d/Y', $to), + 'special_from_date' => date($priceFrom), + 'special_to_date' => date($priceTo), ]); - $product->save(); - /** @var Product $indexer */ - $indexer = $this->getObjectManager()->create(Product::class); - $indexer->execute([9]); - + $this->productsIndexer->execute([self::SPECIAL_PRICE_TEST_PRODUCT_ID]); $this->algoliaHelper->waitLastTask(); - $res = $this->algoliaHelper->getObjects($this->indexPrefix . 'default_products', ['9']); + $res = $this->algoliaHelper->getObjects( + $this->indexPrefix . + 'default_products', + [(string) self::SPECIAL_PRICE_TEST_PRODUCT_ID] + ); $algoliaProduct = reset($res['results']); $this->assertEquals($specialPrice, $algoliaProduct['price']['USD']['default']); - $this->assertEquals($from, $algoliaProduct['price']['USD']['special_from_date']); - $this->assertEquals($to, $algoliaProduct['price']['USD']['special_to_date']); + $this->assertEquals("$32.00", $algoliaProduct['price']['USD']['default_original_formated']); } - private function setOneProductOutOfStock() + private function updateStockItem($sku, $isInStock) { - $stockItem = $this->stockRegistry->getStockItemBySku('24-MB01'); - $stockItem->setIsInStock(false); - $this->stockRegistry->updateStockItemBySku('24-MB01', $stockItem); + $stockItem = $this->stockRegistry->getStockItemBySku($sku); + $stockItem->setIsInStock($isInStock); + $this->stockRegistry->updateStockItemBySku($sku, $stockItem); } private function getValidTestProduct() { if (!$this->testProductId) { - /** @var \Magento\Catalog\Model\Product $product */ - $product = $this->getObjectManager()->get(\Magento\Catalog\Model\Product::class); + /** @var Product $product */ + $product = $this->getObjectManager()->get(Product::class); $this->testProductId = $product->getIdBySku('MSH09'); } return $this->testProductId; } + + public function tearDown(): void + { + /** @var Product $product */ + $product = $this->objectManager->create(\Magento\Catalog\Model\Product::class); + $product->load(self::SPECIAL_PRICE_TEST_PRODUCT_ID); + + $product->setCustomAttributes([ + 'special_price' => null, + 'special_from_date' => null, + 'special_to_date' => null, + ]); + $product->getResource()->saveAttribute($product, 'special_price'); + $product->save(); + + $this->updateStockItem(self::OUT_OF_STOCK_PRODUCT_SKU, true); + + parent::tearDown(); + } } diff --git a/Test/Integration/TestCase.php b/Test/Integration/TestCase.php index 5f28cde7d..cca981563 100644 --- a/Test/Integration/TestCase.php +++ b/Test/Integration/TestCase.php @@ -6,8 +6,10 @@ use Algolia\AlgoliaSearch\Helper\AlgoliaHelper; use Algolia\AlgoliaSearch\Helper\ConfigHelper; use Algolia\AlgoliaSearch\Setup\Patch\Schema\ConfigPatch; -use Algolia\AlgoliaSearch\Test\Integration\AssertValues\Magento246; -use Algolia\AlgoliaSearch\Test\Integration\AssertValues\Magento247; +use Algolia\AlgoliaSearch\Test\Integration\AssertValues\Magento246CE; +use Algolia\AlgoliaSearch\Test\Integration\AssertValues\Magento246EE; +use Algolia\AlgoliaSearch\Test\Integration\AssertValues\Magento247CE; +use Algolia\AlgoliaSearch\Test\Integration\AssertValues\Magento247EE; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\ProductMetadataInterface; use Magento\Framework\ObjectManagerInterface; @@ -39,9 +41,12 @@ abstract class TestCase extends \TC /** @var ConfigHelper */ protected $configHelper; - /** @var Magento246|Magento247 */ + /** @var Magento246CE|Magento246EE|Magento247CE|Magento247EE */ protected $assertValues; + /** @var ProductMetadataInterface */ + protected $productMetadata; + public function setUp(): void { $this->bootstrap(); @@ -197,12 +202,21 @@ private function bootstrap() return; } - $this->objectManager = $this->getObjectManager(); + $this->objectManager = Bootstrap::getObjectManager(); + $this->productMetadata = $this->objectManager->get(ProductMetadataInterface::class); if (version_compare($this->getMagentoVersion(), '2.4.7', '<')) { - $this->assertValues = new Magento246(); + if ($this->getMagentEdition() === 'Community') { + $this->assertValues = new Magento246CE(); + } else { + $this->assertValues = new Magento246EE(); + } } else { - $this->assertValues = new Magento247(); + if ($this->getMagentEdition() === 'Community') { + $this->assertValues = new Magento247CE(); + } else { + $this->assertValues = new Magento247EE(); + } } $this->configHelper = $this->getObjectManager()->create(ConfigHelper::class); @@ -240,10 +254,12 @@ protected function invokeMethod(&$object, $methodName, array $parameters = []) private function getMagentoVersion() { - /** @var ProductMetadataInterface $productMetadata */ - $productMetadata = $this->getObjectManager()->get(ProductMetadataInterface::class); + return $this->productMetadata->getVersion(); + } - return $productMetadata->getVersion(); + private function getMagentEdition() + { + return $this->productMetadata->getEdition(); } protected function getSerializer() diff --git a/Test/Integration/_files/second_website_with_two_stores_and_products.php b/Test/Integration/_files/second_website_with_two_stores_and_products.php new file mode 100644 index 000000000..f07321e61 --- /dev/null +++ b/Test/Integration/_files/second_website_with_two_stores_and_products.php @@ -0,0 +1,93 @@ +create(\Magento\Store\Model\Website::class); +/** @var $website \Magento\Store\Model\Website */ +if (!$website->load('test', 'code')->getId()) { + $website->setData(['code' => 'test', 'name' => 'Test Website', 'default_group_id' => '1', 'is_default' => '0']); + $website->save(); +} +$websiteId = $website->getId(); +$store = Bootstrap::getObjectManager()->create(\Magento\Store\Model\Store::class); +if (!$store->load('fixture_second_store', 'code')->getId()) { + $groupId = Bootstrap::getObjectManager()->get( + StoreManagerInterface::class + )->getWebsite()->getDefaultGroupId(); + $store->setCode( + 'fixture_second_store' + )->setWebsiteId( + $websiteId + )->setGroupId( + $groupId + )->setName( + 'Fixture Second Store' + )->setSortOrder( + 10 + )->setIsActive( + 1 + ); + $store->save(); +} + +$store = Bootstrap::getObjectManager()->create(\Magento\Store\Model\Store::class); +if (!$store->load('fixture_third_store', 'code')->getId()) { + $groupId = Bootstrap::getObjectManager()->get( + StoreManagerInterface::class + )->getWebsite()->getDefaultGroupId(); + $store->setCode( + 'fixture_third_store' + )->setWebsiteId( + $websiteId + )->setGroupId( + $groupId + )->setName( + 'Fixture Third Store' + )->setSortOrder( + 11 + )->setIsActive( + 1 + ); + $store->save(); +} + +$objectManager = Bootstrap::getObjectManager(); +$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class); +$websites = $websiteRepository->getList(); +$websiteIds = []; +foreach ($websites as $website) { + $websiteIds[] = $website->getId(); +} + +$configManager = $objectManager->get(\Magento\Framework\App\Config\MutableScopeConfigInterface::class); + +$configManager->setValue('algoliasearch_credentials/credentials/application_id', getenv('ALGOLIA_APPLICATION_ID')); +$configManager->setValue('algoliasearch_credentials/credentials/search_only_api_key', getenv('ALGOLIA_SEARCH_API_KEY')); +$configManager->setValue('algoliasearch_credentials/credentials/api_key', getenv('ALGOLIA_API_KEY')); +$configManager->setValue('algoliasearch_credentials/credentials/index_prefix', 'TEMP'); +// Temporarily disable indexing during product assignment to stores +$configManager->setValue('algoliasearch_credentials/credentials/enable_backend', 0); + +$productSkus = MultiStoreProductsTest::SKUS; +$productRepository = Bootstrap::getObjectManager() + ->get(ProductRepositoryInterface::class); + +foreach ($productSkus as $sku) { + $product = $productRepository->get($sku); + $product->setWebsiteIds($websiteIds); + $productRepository->save($product); +} + +$configManager->setValue('algoliasearch_credentials/credentials/enable_backend', 1); + +/* Refresh CatalogSearch index */ +/** @var IndexerRegistry $indexerRegistry */ +$indexerRegistry = Bootstrap::getObjectManager() + ->create(IndexerRegistry::class); +$indexerRegistry->get(Fulltext::INDEXER_ID)->reindexAll(); diff --git a/Test/Integration/_files/second_website_with_two_stores_and_products_rollback.php b/Test/Integration/_files/second_website_with_two_stores_and_products_rollback.php new file mode 100644 index 000000000..af719e217 --- /dev/null +++ b/Test/Integration/_files/second_website_with_two_stores_and_products_rollback.php @@ -0,0 +1,24 @@ +get(\Magento\Framework\Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); +$website = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Store\Model\Website::class); +/** @var $website \Magento\Store\Model\Website */ +$websiteId = $website->load('test', 'code')->getId(); +if ($websiteId) { + $website->delete(); +} +$store = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Store\Model\Store::class); +if ($store->load('fixture_second_store', 'code')->getId()) { + $store->delete(); +} + +$store = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Store\Model\Store::class); +if ($store->load('fixture_third_store', 'code')->getId()) { + $store->delete(); +} +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); From e6b8d0c329c747654386aeec1938736bcf12181d Mon Sep 17 00:00:00 2001 From: Damien Couchez Date: Mon, 14 Oct 2024 15:04:42 +0200 Subject: [PATCH 28/40] MAGE-1058: add multistores Pages tests (#1629) --- Helper/Entity/PageHelper.php | 6 +- Test/Integration/Page/MultiStorePagesTest.php | 148 ++++++++++++++++++ 2 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 Test/Integration/Page/MultiStorePagesTest.php diff --git a/Helper/Entity/PageHelper.php b/Helper/Entity/PageHelper.php index a0a642bba..e70f11cdf 100755 --- a/Helper/Entity/PageHelper.php +++ b/Helper/Entity/PageHelper.php @@ -59,7 +59,7 @@ public function getPages($storeId, array $pageIds = null) $magentoPages->addFieldToFilter('page_id', ['in' => $pageIds]); } - $excludedPages = $this->getExcludedPageIds(); + $excludedPages = $this->getExcludedPageIds($storeId); if (count($excludedPages)) { $magentoPages->addFieldToFilter('identifier', ['nin' => $excludedPages]); } @@ -116,9 +116,9 @@ public function getPages($storeId, array $pageIds = null) return $pages; } - public function getExcludedPageIds() + public function getExcludedPageIds($storeId = null) { - $excludedPages = array_values($this->configHelper->getExcludedPages()); + $excludedPages = array_values($this->configHelper->getExcludedPages($storeId)); foreach ($excludedPages as &$excludedPage) { $excludedPage = $excludedPage['attribute']; } diff --git a/Test/Integration/Page/MultiStorePagesTest.php b/Test/Integration/Page/MultiStorePagesTest.php new file mode 100644 index 000000000..84cb1c5a6 --- /dev/null +++ b/Test/Integration/Page/MultiStorePagesTest.php @@ -0,0 +1,148 @@ +pagesIndexer = $this->objectManager->get(Page::class); + $this->pageRepository = $this->objectManager->get(PageRepositoryInterface::class); + $this->pageCollectionFactory = $this->objectManager->get(CollectionFactory::class); + + $this->pagesIndexer->executeFull(); + $this->algoliaHelper->waitLastTask(); + } + + /*** + * @magentoDbIsolation disabled + * + * @throws ExceededRetriesException + * @throws AlgoliaException + */ + public function testMultiStorePageIndices() + { + // Check that every store has the right number of pages + foreach ($this->storeManager->getStores() as $store) { + $this->assertNbOfRecordsPerStore( + $store->getCode(), + 'pages', + $store->getCode() === 'fixture_second_store' ? // we excluded 2 pages on setupStore() + $this->assertValues->expectedExcludePages : + $this->assertValues->expectedPages + ); + } + + $defaultStore = $this->storeRepository->get('default'); + $fixtureSecondStore = $this->storeRepository->get('fixture_second_store'); + + try { + $aboutUsPage = $this->loadPage(self::ABOUT_US_PAGE_ID); + } catch (\Exception $e) { + $this->markTestIncomplete('Page could not be found.'); + } + + // Setting the page only for default store + $aboutUsPage->setStores([$defaultStore->getId()]); + $this->pageRepository->save($aboutUsPage); + + $this->pagesIndexer->execute([self::ABOUT_US_PAGE_ID]); + $this->algoliaHelper->waitLastTask(); + + $this->assertNbOfRecordsPerStore( + $defaultStore->getCode(), + 'pages', + $this->assertValues->expectedPages + ); + + $this->assertNbOfRecordsPerStore( + $fixtureSecondStore->getCode(), + 'pages', + $this->assertValues->expectedExcludePages - 1 + ); + } + + /** + * Loads page by id. + * + * @param int $pageId + * + * @return PageInterface + * @throws LocalizedException + */ + private function loadPage(int $pageId): PageInterface + { + return $this->pageRepository->getById($pageId); + } + + protected function resetPage(PageInterface $page): void + { + $page->setStores([0]); + $this->pageRepository->save($page); + } + + /** + * @param StoreInterface $store + * @param bool $enableInstantSearch + * + * @return void + * @throws AlgoliaException + * @throws LocalizedException + * @throws NoSuchEntityException + */ + protected function setupStore(StoreInterface $store, bool $enableInstantSearch = false): void + { + // Exclude 2 pages on second store + $excludedPages = $store->getCode() === 'fixture_second_store' ? + [['attribute' => 'no-route'], ['attribute' => 'home']]: + []; + + $this->setConfig( + ConfigHelper::EXCLUDED_PAGES, + $this->getSerializer()->serialize($excludedPages), + $store->getCode() + ); + + parent::setupStore($store, $enableInstantSearch); + } + + public function tearDown(): void + { + // Restore page in case DB is not cleaned up + $aboutUsPage = $this->loadPage(self::ABOUT_US_PAGE_ID); + $this->resetPage($aboutUsPage); + + parent::tearDown(); + } +} From 6781b57d83382f8fe77daf6b222d266f8717f184 Mon Sep 17 00:00:00 2001 From: Mohammad Rahman Date: Wed, 9 Oct 2024 19:49:41 -0400 Subject: [PATCH 29/40] MAGE-1050: category search test included. --- Test/Integration/Search/SearchTest.php | 30 ++++++++++++++++++++------ 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/Test/Integration/Search/SearchTest.php b/Test/Integration/Search/SearchTest.php index 0212830dc..70a17dd06 100644 --- a/Test/Integration/Search/SearchTest.php +++ b/Test/Integration/Search/SearchTest.php @@ -8,18 +8,34 @@ class SearchTest extends TestCase { - public function testSearch() + /** @var Product */ + protected $productIndexer; + + /** @var Data */ + protected $helper; + + public function setUp(): void { - /** @var Product $indexer */ - $indexer = $this->getObjectManager()->create(Product::class); - $indexer->executeFull(); + parent::setUp(); + $this->productIndexer = $this->objectManager->get(Product::class); + $this->helper = $this->getObjectManager()->create(Data::class); + + $this->productIndexer->executeFull(); $this->algoliaHelper->waitLastTask(); + } - /** @var Data $helper */ - $helper = $this->getObjectManager()->create(Data::class); - list($results, $totalHits, $facetsFromAlgolia) = $helper->getSearchResult('', 1); + public function testSearch() + { + list($results, $totalHits, $facetsFromAlgolia) = $this->helper->getSearchResult('', 1); + $this->assertNotEmpty($results); + } + public function testCategorySearch() + { + list($results, $totalHits, $facetsFromAlgolia) = $this->helper->getSearchResult('', 1, [ + 'facetFilters' => ['categoryIds:' . $this->assertValues->expectedCategory] + ]); $this->assertNotEmpty($results); } } From 1bf61517349d49267d418ec0fdc95f7385f93269 Mon Sep 17 00:00:00 2001 From: Mohammad Rahman Date: Fri, 11 Oct 2024 12:47:27 -0400 Subject: [PATCH 30/40] MAGE-1050: code updated as per code review suggestions. --- Test/Integration/Search/SearchTest.php | 62 ++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/Test/Integration/Search/SearchTest.php b/Test/Integration/Search/SearchTest.php index 70a17dd06..803222144 100644 --- a/Test/Integration/Search/SearchTest.php +++ b/Test/Integration/Search/SearchTest.php @@ -5,6 +5,7 @@ use Algolia\AlgoliaSearch\Helper\Data; use Algolia\AlgoliaSearch\Model\Indexer\Product; use Algolia\AlgoliaSearch\Test\Integration\TestCase; +use Magento\Framework\Exception\NoSuchEntityException; class SearchTest extends TestCase { @@ -27,15 +28,68 @@ public function setUp(): void public function testSearch() { - list($results, $totalHits, $facetsFromAlgolia) = $this->helper->getSearchResult('', 1); - $this->assertNotEmpty($results); + $query = 'bag'; + $results = $this->search($query); + $result = $this->getFirstResult($results); + // Search returns result + $this->assertNotEmpty($result, "Query didn't bring result"); + + $product = $this->objectManager->create(\Magento\Catalog\Model\Product::class); + $product->load($result['entity_id']); + // Result exists in DB + $this->assertNotEmpty($product->getName(), "Query result item couldn't find in the DB"); + // Query word exists title + $this->assertStringContainsString($query, strtolower($product->getName()), "Query word doesn't exist in product name"); + } + + public function testSearchBySku() + { + $sku = "24-MB01"; + $results = $this->search($sku); + $result = $this->getFirstResult($results); + // Search by SKU returns result + $this->assertNotEmpty($result, "SKU search didn't bring result"); + + $product = $this->objectManager->create(\Magento\Catalog\Model\Product::class); + $product->load($result['entity_id']); + // Result exists in DB + $this->assertNotEmpty($product->getSku(), "SKU search result item couldn't find in the DB"); + // Query word exists title + $this->assertEquals($sku, $product->getSku(), "Query SKU doesn't match with product SKU"); } public function testCategorySearch() { - list($results, $totalHits, $facetsFromAlgolia) = $this->helper->getSearchResult('', 1, [ + // Get products by categoryId + $results = $this->search('', 1, [ 'facetFilters' => ['categoryIds:' . $this->assertValues->expectedCategory] ]); - $this->assertNotEmpty($results); + $result = $this->getFirstResult($results); + // Category filter returns result + $this->assertNotEmpty($result, "Category filter didn't return result"); + } + + /** + * @param array $results + * @return array + */ + protected function getFirstResult(array $results): array + { + list($results, $totalHits, $facetsFromAlgolia) = $results; + return array_shift($results); + } + + /** + * @param string $query + * @param int $storeId + * @return array + */ + protected function search(string $query = '', int $storeId = 1, array $params = []): array + { + try { + return $this->helper->getSearchResult($query, $storeId, $params); + } catch (NoSuchEntityException $e) { + return []; + } } } From bc7a29ad75c8e71cedac2d8d06c9b7ced24a753b Mon Sep 17 00:00:00 2001 From: Mohammad Rahman Date: Mon, 14 Oct 2024 09:43:22 -0400 Subject: [PATCH 31/40] MAGE-1050: code updated as per code review suggestions. --- Test/Integration/Search/SearchTest.php | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Test/Integration/Search/SearchTest.php b/Test/Integration/Search/SearchTest.php index 803222144..5f6c5e1aa 100644 --- a/Test/Integration/Search/SearchTest.php +++ b/Test/Integration/Search/SearchTest.php @@ -61,12 +61,21 @@ public function testSearchBySku() public function testCategorySearch() { // Get products by categoryId - $results = $this->search('', 1, [ + list($results, $totalHits, $facetsFromAlgolia) = $this->search('', 1, [ 'facetFilters' => ['categoryIds:' . $this->assertValues->expectedCategory] ]); - $result = $this->getFirstResult($results); // Category filter returns result - $this->assertNotEmpty($result, "Category filter didn't return result"); + $this->assertNotEmpty($results, "Category filter didn't return result"); + + $collection = $this->objectManager->create(\Magento\Catalog\Model\ResourceModel\Product\Collection::class); + $collection + ->addAttributeToSelect('*') + ->addAttributeToFilter('status',\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->addAttributeToFilter('visibility', \Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->addCategoriesFilter(["in" => $this->assertValues->expectedCategory]) + ->setStore(1); + // Products in category count matches + $this->assertEquals(count($results), $collection->count(), "Indexed number of products in a category doesn't match with DB"); } /** @@ -82,6 +91,7 @@ protected function getFirstResult(array $results): array /** * @param string $query * @param int $storeId + * @param array $params * @return array */ protected function search(string $query = '', int $storeId = 1, array $params = []): array From a24b02f08d84e3367a2145c3aec28a76f074310d Mon Sep 17 00:00:00 2001 From: Mohammad Rahman Date: Mon, 14 Oct 2024 14:39:20 -0400 Subject: [PATCH 32/40] MAGE-1050:code updated as per code review suggestions --- Test/Integration/Search/SearchTest.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Test/Integration/Search/SearchTest.php b/Test/Integration/Search/SearchTest.php index 5f6c5e1aa..51dc41b8c 100644 --- a/Test/Integration/Search/SearchTest.php +++ b/Test/Integration/Search/SearchTest.php @@ -9,6 +9,8 @@ class SearchTest extends TestCase { + const BAGS_CATEGORY_ID = 4; + /** @var Product */ protected $productIndexer; @@ -62,7 +64,7 @@ public function testCategorySearch() { // Get products by categoryId list($results, $totalHits, $facetsFromAlgolia) = $this->search('', 1, [ - 'facetFilters' => ['categoryIds:' . $this->assertValues->expectedCategory] + 'facetFilters' => ['categoryIds:' . self::BAGS_CATEGORY_ID] ]); // Category filter returns result $this->assertNotEmpty($results, "Category filter didn't return result"); @@ -72,7 +74,7 @@ public function testCategorySearch() ->addAttributeToSelect('*') ->addAttributeToFilter('status',\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->addAttributeToFilter('visibility', \Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) - ->addCategoriesFilter(["in" => $this->assertValues->expectedCategory]) + ->addCategoriesFilter(["in" => self::BAGS_CATEGORY_ID]) ->setStore(1); // Products in category count matches $this->assertEquals(count($results), $collection->count(), "Indexed number of products in a category doesn't match with DB"); From 6ccee7678048f910daec95a3bdaab291a2d65bc1 Mon Sep 17 00:00:00 2001 From: Damien Couchez Date: Tue, 15 Oct 2024 10:13:58 +0200 Subject: [PATCH 33/40] Update Test/Integration/Search/SearchTest.php switch to $this->objectManager --- Test/Integration/Search/SearchTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Test/Integration/Search/SearchTest.php b/Test/Integration/Search/SearchTest.php index 51dc41b8c..575c5dbd8 100644 --- a/Test/Integration/Search/SearchTest.php +++ b/Test/Integration/Search/SearchTest.php @@ -22,7 +22,7 @@ public function setUp(): void parent::setUp(); $this->productIndexer = $this->objectManager->get(Product::class); - $this->helper = $this->getObjectManager()->create(Data::class); + $this->helper = $this->objectManager->create(Data::class); $this->productIndexer->executeFull(); $this->algoliaHelper->waitLastTask(); From be5dac2c93e7dab17f313ccd85e8c99e23bde574 Mon Sep 17 00:00:00 2001 From: Damien Couchez Date: Wed, 16 Oct 2024 10:31:30 +0200 Subject: [PATCH 34/40] MAGE-1054: Fix queue tests (#1630) * MAGE-1054: refactor queue tests * MAGE-1054: re-add the skipped tests * MAGE-1054: fix tests --- Helper/AlgoliaHelper.php | 2 +- Setup/Patch/Schema/ConfigPatch.php | 4 +- Test/Integration/Queue/QueueTest.php | 146 ++++++++++++--------------- 3 files changed, 65 insertions(+), 87 deletions(-) diff --git a/Helper/AlgoliaHelper.php b/Helper/AlgoliaHelper.php index 41181560f..c8662e180 100755 --- a/Helper/AlgoliaHelper.php +++ b/Helper/AlgoliaHelper.php @@ -300,7 +300,7 @@ public function moveIndex(string $fromIndexName, string $toIndexName): void 'destination' => $toIndexName ] ); - self::setLastOperationInfo($fromIndexName, $response); + self::setLastOperationInfo($toIndexName, $response); } /** diff --git a/Setup/Patch/Schema/ConfigPatch.php b/Setup/Patch/Schema/ConfigPatch.php index a28b9681d..6450af8f7 100644 --- a/Setup/Patch/Schema/ConfigPatch.php +++ b/Setup/Patch/Schema/ConfigPatch.php @@ -112,7 +112,7 @@ class ConfigPatch implements SchemaPatchInterface ], ], - 'algoliasearch_instant/instant/facets' => [ + 'algoliasearch_instant/instant_facets/facets' => [ [ 'attribute' => 'price', 'type' => 'slider', @@ -135,7 +135,7 @@ class ConfigPatch implements SchemaPatchInterface 'create_rule' => '2', ], ], - 'algoliasearch_instant/instant/sorts' => [ + 'algoliasearch_instant/instant_sorts/sorts' => [ [ 'attribute' => 'price', 'sort' => 'asc', diff --git a/Test/Integration/Queue/QueueTest.php b/Test/Integration/Queue/QueueTest.php index 0cb0d1ce6..c389830b6 100644 --- a/Test/Integration/Queue/QueueTest.php +++ b/Test/Integration/Queue/QueueTest.php @@ -2,6 +2,7 @@ namespace Algolia\AlgoliaSearch\Test\Integration\Queue; +use Algolia\AlgoliaSearch\Helper\ConfigHelper; use Algolia\AlgoliaSearch\Model\Indexer\Product; use Algolia\AlgoliaSearch\Model\Indexer\QueueRunner; use Algolia\AlgoliaSearch\Model\IndicesConfigurator; @@ -12,6 +13,10 @@ use Magento\Framework\App\ResourceConnection; use Magento\Framework\DB\Adapter\AdapterInterface; +/** + * @magentoDbIsolation disabled + * @magentoAppIsolation enabled + */ class QueueTest extends TestCase { private const INCOMPLETE_REASON = "Must revisit transaction handling across connections."; @@ -29,27 +34,27 @@ public function setUp(): void { parent::setUp(); - $this->jobsCollectionFactory = $this->getObjectManager()->create(JobsCollectionFactory::class); + $this->jobsCollectionFactory = $this->objectManager->create(JobsCollectionFactory::class); /** @var ResourceConnection $resource */ - $resource = $this->getObjectManager()->create(ResourceConnection::class); + $resource = $this->objectManager->get(ResourceConnection::class); $this->connection = $resource->getConnection(); - $this->queue = $this->getObjectManager()->create(Queue::class); + $this->queue = $this->objectManager->get(Queue::class); } public function testFill() { $this->resetConfigs([ - 'algoliasearch_queue/queue/number_of_job_to_run', - 'algoliasearch_advanced/queue/number_of_element_by_page', + ConfigHelper::NUMBER_OF_JOB_TO_RUN, + ConfigHelper::NUMBER_OF_ELEMENT_BY_PAGE, ]); - $this->setConfig('algoliasearch_queue/queue/active', '1'); + $this->setConfig(ConfigHelper::IS_ACTIVE, '1'); $this->connection->query('TRUNCATE TABLE algoliasearch_queue'); /** @var Product $indexer */ - $indexer = $this->getObjectManager()->create(Product::class); + $indexer = $this->objectManager->get(Product::class); $indexer->executeFull(); $rows = $this->connection->query('SELECT * FROM algoliasearch_queue')->fetchAll(); @@ -80,18 +85,17 @@ public function testFill() } } - /** - * @depends testFill - * @magentoDbIsolation disabled - */ public function testExecute() { - $this->markTestIncomplete(self::INCOMPLETE_REASON); + $this->setConfig(ConfigHelper::IS_ACTIVE, '1'); + $this->connection->query('TRUNCATE TABLE algoliasearch_queue'); - $this->setConfig('algoliasearch_queue/queue/active', '1'); + /** @var Product $indexer */ + $indexer = $this->objectManager->get(Product::class); + $indexer->executeFull(); /** @var Queue $queue */ - $queue = $this->getObjectManager()->create(Queue::class); + $queue = $this->objectManager->get(Queue::class); // Run the first two jobs - saveSettings, batch $queue->runCron(2, true); @@ -109,7 +113,7 @@ public function testExecute() $this->assertTrue($existsDefaultTmpIndex, 'Default products production index does not exists and it should'); - // Run the second two jobs - batch, move + // Run the last move - move $queue->runCron(2, true); $this->algoliaHelper->waitLastTask(); @@ -132,31 +136,26 @@ public function testExecute() $this->assertTrue($existsDefaultProdIndex, 'Default product production index does not exists and it should'); /** TODO: There are mystery items being added to queue from unknown save process on product_id=1 */ - /* $rows = $this->connection->query('SELECT * FROM algoliasearch_queue')->fetchAll(); - $this->assertEquals(0, count($rows)); */ + $rows = $this->connection->query('SELECT * FROM algoliasearch_queue')->fetchAll(); + $this->assertEquals(0, count($rows)); } - /** - * @magentoDbIsolation disabled - */ public function testSettings() { - $this->markTestIncomplete(self::INCOMPLETE_REASON); - $this->resetConfigs([ - 'algoliasearch_queue/queue/number_of_job_to_run', - 'algoliasearch_advanced/queue/number_of_element_by_page', - 'algoliasearch_instant/instant_facets/facets', - 'algoliasearch_products/products/product_additional_attributes', + ConfigHelper::NUMBER_OF_JOB_TO_RUN, + ConfigHelper::NUMBER_OF_ELEMENT_BY_PAGE, + ConfigHelper::FACETS, + ConfigHelper::PRODUCT_ATTRIBUTES ]); - $this->setConfig('algoliasearch_queue/queue/active', '1'); + $this->setConfig(ConfigHelper::IS_ACTIVE, '1'); $this->connection->query('DELETE FROM algoliasearch_queue'); // Reindex products multiple times /** @var Product $indexer */ - $indexer = $this->getObjectManager()->create(Product::class); + $indexer = $this->objectManager->get(Product::class); $indexer->executeFull(); $indexer->executeFull(); $indexer->executeFull(); @@ -166,7 +165,7 @@ public function testSettings() // Process the whole queue /** @var QueueRunner $queueRunner */ - $queueRunner = $this->getObjectManager()->create(QueueRunner::class); + $queueRunner = $this->objectManager->get(QueueRunner::class); $queueRunner->executeFull(); $queueRunner->executeFull(); $queueRunner->executeFull(); @@ -181,19 +180,16 @@ public function testSettings() $this->assertFalse(empty($settings['searchableAttributes']), 'SearchableAttributes should be set, but they are not.'); } - /** - * @magentoDbIsolation disabled - */ public function testMergeSettings() { - $this->setConfig('algoliasearch_queue/queue/active', '1'); - $this->setConfig('algoliasearch_queue/queue/number_of_job_to_run', 1); - $this->setConfig('algoliasearch_advanced/queue/number_of_element_by_page', 300); + $this->setConfig(ConfigHelper::IS_ACTIVE, '1'); + $this->setConfig(ConfigHelper::NUMBER_OF_JOB_TO_RUN, 1); + $this->setConfig(ConfigHelper::NUMBER_OF_ELEMENT_BY_PAGE, 300); $this->connection->query('DELETE FROM algoliasearch_queue'); /** @var Product $productIndexer */ - $productIndexer = $this->getObjectManager()->create(Product::class); + $productIndexer = $this->objectManager->get(Product::class); $productIndexer->executeFull(); $rows = $this->connection->query('SELECT * FROM algoliasearch_queue')->fetchAll(); @@ -208,7 +204,7 @@ public function testMergeSettings() $this->assertEquals(['sku'], $settings['disableTypoToleranceOnAttributes']); /** @var QueueRunner $queueRunner */ - $queueRunner = $this->getObjectManager()->create(QueueRunner::class); + $queueRunner = $this->objectManager->get(QueueRunner::class); $queueRunner->executeFull(); $this->algoliaHelper->waitLastTask(); @@ -223,9 +219,6 @@ public function testMergeSettings() $this->assertEquals(['sku'], $settings['disableTypoToleranceOnAttributes']); } - /** - * @magentoDbIsolation disabled - */ public function testMerging() { $this->connection->query('DELETE FROM algoliasearch_queue'); @@ -434,9 +427,6 @@ public function testMerging() $this->assertEquals($expectedProductJob, $productJob->toArray()); } - /** - * @magentoDbIsolation disabled - */ public function testMergingWithStaticMethods() { $this->connection->query('TRUNCATE TABLE algoliasearch_queue'); @@ -587,13 +577,8 @@ public function testMergingWithStaticMethods() $this->assertEquals('rebuildStoreProductIndex', $jobs[11]->getMethod()); } - /** - * @magentoDbIsolation disabled - */ public function testGetJobs() { - $this->markTestIncomplete(self::INCOMPLETE_REASON); - $this->connection->query('TRUNCATE TABLE algoliasearch_queue'); $data = [ @@ -740,18 +725,12 @@ public function testGetJobs() $expectedFirstJob = [ 'job_id' => '1', - 'created' => '2017-09-01 12:00:00', - 'pid' => null, + 'pid' => $pid, 'class' => \Algolia\AlgoliaSearch\Helper\Data::class, 'method' => 'rebuildStoreCategoryIndex', 'data' => '{"store_id":"1","category_ids":["9","22"]}', - 'max_retries' => '3', - 'retries' => '0', - 'error_log' => '', - 'data_size' => 3, 'merged_ids' => ['1', '7'], 'store_id' => '1', - 'is_full_reindex' => 0, 'decoded_data' => [ 'store_id' => '1', 'category_ids' => [ @@ -760,24 +739,16 @@ public function testGetJobs() 2 => '40', ], ], - 'locked_at' => null, - 'debug' => null, ]; $expectedLastJob = [ 'job_id' => '6', - 'created' => '2017-09-01 12:00:00', - 'pid' => null, + 'pid' => $pid, 'class' => \Algolia\AlgoliaSearch\Helper\Data::class, 'method' => 'rebuildStoreProductIndex', 'data' => '{"store_id":"3","product_ids":["448"]}', - 'max_retries' => '3', - 'retries' => '0', - 'error_log' => '', - 'data_size' => 2, 'merged_ids' => ['6', '12'], 'store_id' => '3', - 'is_full_reindex' => 0, 'decoded_data' => [ 'store_id' => '3', 'product_ids' => [ @@ -785,18 +756,31 @@ public function testGetJobs() 1 => '405', ], ], - 'locked_at' => null, - 'debug' => null, ]; /** @var Job $firstJob */ $firstJob = reset($jobs); + $firstJob = $firstJob->toArray(); /** @var Job $lastJob */ $lastJob = end($jobs); + $lastJob = $lastJob->toArray(); + + $valuesToCheck = [ + 'job_id', + 'method', + 'class', + 'store_id', + 'pid', + 'data', + 'merged_ids', + 'decoded_data', + ]; - $this->assertEquals($expectedFirstJob, $firstJob->toArray()); - $this->assertEquals($expectedLastJob, $lastJob->toArray()); + foreach ($valuesToCheck as $valueToCheck) { + $this->assertEquals($expectedFirstJob[$valueToCheck], $firstJob[$valueToCheck]); + $this->assertEquals($expectedLastJob[$valueToCheck], $lastJob[$valueToCheck]); + } $dbJobs = $this->connection->query('SELECT * FROM algoliasearch_queue')->fetchAll(); @@ -807,14 +791,11 @@ public function testGetJobs() } } - /** - * @magentoDbIsolation disabled - */ public function testHugeJob() { // Default value - maxBatchSize = 1000 - $this->setConfig('algoliasearch_queue/queue/number_of_job_to_run', 10); - $this->setConfig('algoliasearch_advanced/queue/number_of_element_by_page', 100); + $this->setConfig(ConfigHelper::NUMBER_OF_JOB_TO_RUN, 10); + $this->setConfig(ConfigHelper::NUMBER_OF_ELEMENT_BY_PAGE, 100); $productIds = range(1, 5000); $jsonProductIds = json_encode($productIds); @@ -851,8 +832,8 @@ public function testHugeJob() public function testMaxSingleJobSize() { // Default value - maxBatchSize = 1000 - $this->setConfig('algoliasearch_queue/queue/number_of_job_to_run', 10); - $this->setConfig('algoliasearch_advanced/queue/number_of_element_by_page', 100); + $this->setConfig(ConfigHelper::NUMBER_OF_JOB_TO_RUN, 10); + $this->setConfig(ConfigHelper::NUMBER_OF_ELEMENT_BY_PAGE, 100); $productIds = range(1, 99); $jsonProductIds = json_encode($productIds); @@ -889,25 +870,22 @@ public function testMaxSingleJobSize() $this->assertEquals($pid, $lastJob['pid']); } - /** - * @magentoDbIsolation disabled - */ public function testMaxSingleJobsSizeOnProductReindex() { $this->resetConfigs([ - 'algoliasearch_queue/queue/number_of_job_to_run', - 'algoliasearch_advanced/queue/number_of_element_by_page', + ConfigHelper::NUMBER_OF_JOB_TO_RUN, + ConfigHelper::NUMBER_OF_ELEMENT_BY_PAGE, ]); - $this->setConfig('algoliasearch_queue/queue/active', '1'); + $this->setConfig(ConfigHelper::IS_ACTIVE, '1'); - $this->setConfig('algoliasearch_queue/queue/number_of_job_to_run', 10); - $this->setConfig('algoliasearch_advanced/queue/number_of_element_by_page', 100); + $this->setConfig(ConfigHelper::NUMBER_OF_JOB_TO_RUN, 10); + $this->setConfig(ConfigHelper::NUMBER_OF_ELEMENT_BY_PAGE, 100); $this->connection->query('TRUNCATE TABLE algoliasearch_queue'); /** @var Product $indexer */ - $indexer = $this->getObjectManager()->create(Product::class); + $indexer = $this->objectManager->get(Product::class); $indexer->execute(range(1, 512)); $dbJobs = $this->connection->query('SELECT * FROM algoliasearch_queue')->fetchAll(); From 63b79ef9a37c7c2a5f35a08ee2ccb3141b7b4c1b Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Wed, 16 Oct 2024 06:52:01 -0400 Subject: [PATCH 35/40] Feat/mage 1051 pricing testing (#1631) * MAGE-1044 remove unused dependency * MAGE-1051 Update method visibility for setUp/tearDown * MAGE-1051 Lift index suffix logic to super class * MAGE-1051 Add regular price indexing test * MAGE-1051 Add data provider for product data check * MAGE-1051 Test catalog price rule * MAGE-1051 Clean up code post merge * MAGE-1051 Group special price test with PricingTest and refactor shared method * MAGE-1051 Refactor shared indexers --- .../Category/MultiStoreCategoriesTest.php | 4 +- .../Config/MultiStoreConfigTest.php | 2 +- Test/Integration/IndexingTestCase.php | 2 +- Test/Integration/MultiStoreTestCase.php | 2 +- .../Product/MultiStoreProductsTest.php | 4 +- Test/Integration/Product/PricingTest.php | 209 ++++++++++++++++++ .../Product/ProductsIndexingTest.php | 112 ++-------- .../Product/ProductsIndexingTestCase.php | 39 ++++ .../Product/ReplicaIndexingTest.php | 64 +++--- Test/Integration/Queue/QueueTest.php | 2 +- Test/Integration/TestCase.php | 57 ++--- Test/Unit/ConfigHelperTest.php | 2 +- 12 files changed, 328 insertions(+), 171 deletions(-) create mode 100644 Test/Integration/Product/PricingTest.php create mode 100644 Test/Integration/Product/ProductsIndexingTestCase.php diff --git a/Test/Integration/Category/MultiStoreCategoriesTest.php b/Test/Integration/Category/MultiStoreCategoriesTest.php index f84484fe8..11af9176d 100644 --- a/Test/Integration/Category/MultiStoreCategoriesTest.php +++ b/Test/Integration/Category/MultiStoreCategoriesTest.php @@ -32,7 +32,7 @@ class MultiStoreCategoriesTest extends MultiStoreTestCase const BAGS_CATEGORY_NAME = "Bags"; const BAGS_CATEGORY_NAME_ALT = "Bags Alt"; - public function setUp():void + protected function setUp():void { parent::setUp(); @@ -152,7 +152,7 @@ private function updateCategory(int $categoryId, int $storeId, array $values): C return $categoryAlt; } - public function tearDown(): void + protected function tearDown(): void { $defaultStore = $this->storeRepository->get('default'); diff --git a/Test/Integration/Config/MultiStoreConfigTest.php b/Test/Integration/Config/MultiStoreConfigTest.php index d28fb0c52..76e99147c 100644 --- a/Test/Integration/Config/MultiStoreConfigTest.php +++ b/Test/Integration/Config/MultiStoreConfigTest.php @@ -110,7 +110,7 @@ public function testMultiStoreIndicesCreation() $this->assertEquals(1, $fixtureProductIndexRules['nbHits']); } - public function tearDown(): void + protected function tearDown(): void { parent::tearDown(); diff --git a/Test/Integration/IndexingTestCase.php b/Test/Integration/IndexingTestCase.php index f5432c689..d26e9a1c8 100644 --- a/Test/Integration/IndexingTestCase.php +++ b/Test/Integration/IndexingTestCase.php @@ -7,7 +7,7 @@ abstract class IndexingTestCase extends TestCase { - public function setUp(): void + protected function setUp(): void { parent::setUp(); diff --git a/Test/Integration/MultiStoreTestCase.php b/Test/Integration/MultiStoreTestCase.php index 3119a6261..25ea0f71b 100644 --- a/Test/Integration/MultiStoreTestCase.php +++ b/Test/Integration/MultiStoreTestCase.php @@ -22,7 +22,7 @@ abstract class MultiStoreTestCase extends IndexingTestCase /** @var IndicesConfigurator */ protected $indicesConfigurator; - public function setUp():void + protected function setUp(): void { parent::setUp(); diff --git a/Test/Integration/Product/MultiStoreProductsTest.php b/Test/Integration/Product/MultiStoreProductsTest.php index 97162ae5b..207ce43b3 100644 --- a/Test/Integration/Product/MultiStoreProductsTest.php +++ b/Test/Integration/Product/MultiStoreProductsTest.php @@ -48,7 +48,7 @@ class MultiStoreProductsTest extends MultiStoreTestCase '24-WB01' ]; - public function setUp():void + protected function setUp():void { parent::setUp(); @@ -186,7 +186,7 @@ private function updateProduct(int $productId, int $storeId, array $values): Pro return $productAlt; } - public function tearDown(): void + protected function tearDown(): void { $defaultStore = $this->storeRepository->get('default'); diff --git a/Test/Integration/Product/PricingTest.php b/Test/Integration/Product/PricingTest.php new file mode 100644 index 000000000..2b0142aeb --- /dev/null +++ b/Test/Integration/Product/PricingTest.php @@ -0,0 +1,209 @@ + + */ + protected const ASSERT_PRODUCT_PRICES = [ + self::PRODUCT_ID_SIMPLE_STANDARD_PRICE => 34, + self::PRODUCT_ID_CONFIGURABLE_STANDARD_PRICE => 52, + self::PRODUCT_ID_CONFIGURABLE_CATALOG_PRICE_RULE => 39.2 + ]; + + protected ?string $indexName = null; + + protected function setUp(): void + { + parent::setUp(); + + $this->indexSuffix = 'products'; + $this->indexName = $this->getIndexName('default'); + } + + /** + * @param int|int[] $productIds + * @return void + * @throws NoSuchEntityException + * @throws AlgoliaException + * @throws ExceededRetriesException + */ + protected function indexProducts(int|array $productIds): void + { + if (!is_array($productIds)) { + $productIds = [$productIds]; + } + $this->productIndexer->execute($productIds); + $this->algoliaHelper->waitLastTask(); + } + + protected function getAlgoliaObjectById(int $productId): ?array + { + $res = $this->algoliaHelper->getObjects( + $this->indexName, + [(string) $productId] + ); + return reset($res['results']); + } + + protected function assertAlgoliaPrice(int $productId): void + { + $algoliaProduct = $this->getAlgoliaObjectById($productId); + $this->assertNotNull($algoliaProduct, "Algolia product index was not successful."); + $this->assertEquals(self::ASSERT_PRODUCT_PRICES[$productId], $algoliaProduct['price']['USD']['default']); + } + + /** + * @depends testMagentoProductData + * @throws AlgoliaException + * @throws ExceededRetriesException + * @throws NoSuchEntityException + */ + public function testRegularPriceSimple(): void + { + $productId = self::PRODUCT_ID_SIMPLE_STANDARD_PRICE; + $this->indexProducts($productId); + $this->assertAlgoliaPrice($productId); + } + + /** + * @depends testMagentoProductData + * @throws AlgoliaException + * @throws ExceededRetriesException + * @throws NoSuchEntityException + */ + public function testRegularPriceConfigurable(): void + { + $productId = self::PRODUCT_ID_CONFIGURABLE_STANDARD_PRICE; + $this->indexProducts($productId); + $this->assertAlgoliaPrice($productId); + } + + /** + * @depends testMagentoProductData + * @throws AlgoliaException + * @throws ExceededRetriesException + * @throws NoSuchEntityException + */ + public function testCatalogPriceRule(): void + { + $productId = self::PRODUCT_ID_CONFIGURABLE_CATALOG_PRICE_RULE; + $this->indexProducts($productId); + $this->assertAlgoliaPrice($productId); + } + + /** + * @dataProvider productProvider + */ + public function testMagentoProductData(int $productId, float $expectedPrice): void + { + /** + * @var Product $product + */ + $product = $this->objectManager->get('Magento\Catalog\Model\ProductRepository')->getById($productId); + $this->assertTrue($product->isInStock(), "Product is not in stock"); + $this->assertTrue($product->getIsSalable(), "Product is not salable"); + $actualPrice = $product->getFinalPrice(); + $this->assertEquals($actualPrice, $expectedPrice, "Product price does not match expectation"); + } + + public static function productProvider(): array + { + return array_map( + function ($key, $value) { + return [$key, $value]; + }, + array_keys(self::ASSERT_PRODUCT_PRICES), + self::ASSERT_PRODUCT_PRICES + ); + } + + public function testSpecialPrice(): void + { + $this->productIndexer->execute([self::SPECIAL_PRICE_TEST_PRODUCT_ID]); + $this->algoliaHelper->waitLastTask(); + + $res = $this->algoliaHelper->getObjects( + $this->indexPrefix . + 'default_products', + [(string) self::SPECIAL_PRICE_TEST_PRODUCT_ID] + ); + $algoliaProduct = reset($res['results']); + + if (!$algoliaProduct || !array_key_exists('price', $algoliaProduct)) { + $this->markTestIncomplete('Hit was not returned correctly from Algolia. No Hit to run assetions.'); + } + + $this->assertEquals(32, $algoliaProduct['price']['USD']['default']); + $this->assertEquals('', $algoliaProduct['price']['USD']['special_from_date']); + $this->assertEquals('', $algoliaProduct['price']['USD']['special_to_date']); + + $specialPrice = 29; + $fromDatetime = new \DateTime(); + $toDatetime = new \DateTime(); + $priceFrom = $fromDatetime->modify('-2 day')->format('Y-m-d H:i:s'); + $priceTo = $toDatetime->modify('+2 day')->format('Y-m-d H:i:s'); + + $product = $this->objectManager->create(Product::class); + $product->load(self::SPECIAL_PRICE_TEST_PRODUCT_ID); + + $product->setCustomAttributes([ + 'special_price' => $specialPrice, + 'special_from_date' => date($priceFrom), + 'special_to_date' => date($priceTo), + ]); + $product->save(); + + $this->productIndexer->execute([self::SPECIAL_PRICE_TEST_PRODUCT_ID]); + $this->algoliaHelper->waitLastTask(); + + $res = $this->algoliaHelper->getObjects( + $this->indexPrefix . + 'default_products', + [(string) self::SPECIAL_PRICE_TEST_PRODUCT_ID] + ); + $algoliaProduct = reset($res['results']); + + $this->assertEquals($specialPrice, $algoliaProduct['price']['USD']['default']); + $this->assertEquals("$32.00", $algoliaProduct['price']['USD']['default_original_formated']); + } + + protected function tearDown(): void + { + /** @var Product $product */ + $product = $this->objectManager->create(Product::class); + $product->load(self::SPECIAL_PRICE_TEST_PRODUCT_ID); + + $product->setCustomAttributes([ + 'special_price' => null, + 'special_from_date' => null, + 'special_to_date' => null, + ]); + $product->getResource()->saveAttribute($product, 'special_price'); + $product->save(); + + parent::tearDown(); + } + +} diff --git a/Test/Integration/Product/ProductsIndexingTest.php b/Test/Integration/Product/ProductsIndexingTest.php index e5d5411a8..1b42779ea 100644 --- a/Test/Integration/Product/ProductsIndexingTest.php +++ b/Test/Integration/Product/ProductsIndexingTest.php @@ -2,25 +2,19 @@ namespace Algolia\AlgoliaSearch\Test\Integration\Product; +use Algolia\AlgoliaSearch\Exceptions\AlgoliaException; +use Algolia\AlgoliaSearch\Exceptions\ExceededRetriesException; use Algolia\AlgoliaSearch\Helper\ConfigHelper; -use Algolia\AlgoliaSearch\Model\Indexer\Product as ProductIndexer; -use Algolia\AlgoliaSearch\Test\Integration\IndexingTestCase; -use Magento\Catalog\Api\Data\SpecialPriceInterfaceFactory; use Magento\Catalog\Model\Product; -use Magento\CatalogInventory\Model\StockRegistry; +use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Indexer\IndexerRegistry; /** * @magentoDbIsolation disabled * @magentoAppIsolation enabled */ -class ProductsIndexingTest extends IndexingTestCase +class ProductsIndexingTest extends ProductsIndexingTestCase { - /** @var ProductIndexer */ - protected $productsIndexer; - - /** @var StockRegistry */ - protected $stockRegistry; /*** @var IndexerRegistry */ protected $indexerRegistry; @@ -29,29 +23,15 @@ class ProductsIndexingTest extends IndexingTestCase protected $testProductId; - const SPECIAL_PRICE_TEST_PRODUCT_ID = 9; - const OUT_OF_STOCK_PRODUCT_SKU = '24-MB01'; - public function setUp():void - { - parent::setUp(); - - $this->productsIndexer = $this->objectManager->get(ProductIndexer::class); - $this->stockRegistry = $this->objectManager->get(StockRegistry::class); - $this->indexerRegistry = $this->objectManager->get(IndexerRegistry::class); - - $this->productPriceIndexer = $this->indexerRegistry->get('catalog_product_price'); - $this->productPriceIndexer->reindexAll(); - } - public function testOnlyOnStockProducts() { $this->setConfig(ConfigHelper::SHOW_OUT_OF_STOCK, 0); $this->updateStockItem(self::OUT_OF_STOCK_PRODUCT_SKU, false); - $this->processTest($this->productsIndexer, 'products', $this->assertValues->productsOnStockCount); + $this->processTest($this->productIndexer, 'products', $this->assertValues->productsOnStockCount); } public function testIncludingOutOfStock() @@ -60,7 +40,7 @@ public function testIncludingOutOfStock() $this->updateStockItem(self::OUT_OF_STOCK_PRODUCT_SKU, false); - $this->processTest($this->productsIndexer, 'products', $this->assertValues->productsOutOfStockCount); + $this->processTest($this->productIndexer, 'products', $this->assertValues->productsOutOfStockCount); } public function testDefaultIndexableAttributes() @@ -72,7 +52,7 @@ public function testDefaultIndexableAttributes() $this->setConfig(ConfigHelper::SORTING_INDICES, $empty); $this->setConfig(ConfigHelper::PRODUCT_CUSTOM_RANKING, $empty); - $this->productsIndexer->executeRow($this->getValidTestProduct()); + $this->productIndexer->executeRow($this->getValidTestProduct()); $this->algoliaHelper->waitLastTask(); $results = $this->algoliaHelper->getObjects($this->indexPrefix . 'default_products', [$this->getValidTestProduct()]); @@ -108,63 +88,6 @@ public function testDefaultIndexableAttributes() $this->assertEmpty($hit, 'Extra products attributes (' . $extraAttributes . ') are indexed and should not be.'); } - public function testSpecialPrice() - { - $this->productsIndexer->execute([self::SPECIAL_PRICE_TEST_PRODUCT_ID]); - $this->algoliaHelper->waitLastTask(); - - $res = $this->algoliaHelper->getObjects( - $this->indexPrefix . - 'default_products', - [(string) self::SPECIAL_PRICE_TEST_PRODUCT_ID] - ); - $algoliaProduct = reset($res['results']); - - if (!$algoliaProduct || !array_key_exists('price', $algoliaProduct)) { - $this->markTestIncomplete('Hit was not returned correctly from Algolia. No Hit to run assetions.'); - } - - $this->assertEquals(32, $algoliaProduct['price']['USD']['default']); - $this->assertEquals('', $algoliaProduct['price']['USD']['special_from_date']); - $this->assertEquals('', $algoliaProduct['price']['USD']['special_to_date']); - - $specialPrice = 29; - $fromDatetime = new \DateTime(); - $toDatetime = new \DateTime(); - $priceFrom = $fromDatetime->modify('-2 day')->format('Y-m-d H:i:s'); - $priceTo = $toDatetime->modify('+2 day')->format('Y-m-d H:i:s'); - - $product = $this->objectManager->create(\Magento\Catalog\Model\Product::class); - $product->load(self::SPECIAL_PRICE_TEST_PRODUCT_ID); - - $product->setCustomAttributes([ - 'special_price' => $specialPrice, - 'special_from_date' => date($priceFrom), - 'special_to_date' => date($priceTo), - ]); - $product->save(); - - $this->productsIndexer->execute([self::SPECIAL_PRICE_TEST_PRODUCT_ID]); - $this->algoliaHelper->waitLastTask(); - - $res = $this->algoliaHelper->getObjects( - $this->indexPrefix . - 'default_products', - [(string) self::SPECIAL_PRICE_TEST_PRODUCT_ID] - ); - $algoliaProduct = reset($res['results']); - - $this->assertEquals($specialPrice, $algoliaProduct['price']['USD']['default']); - $this->assertEquals("$32.00", $algoliaProduct['price']['USD']['default_original_formated']); - } - - private function updateStockItem($sku, $isInStock) - { - $stockItem = $this->stockRegistry->getStockItemBySku($sku); - $stockItem->setIsInStock($isInStock); - $this->stockRegistry->updateStockItemBySku($sku, $stockItem); - } - private function getValidTestProduct() { if (!$this->testProductId) { @@ -176,22 +99,15 @@ private function getValidTestProduct() return $this->testProductId; } - public function tearDown(): void + /** + * @throws NoSuchEntityException + * @throws ExceededRetriesException + * @throws AlgoliaException + */ + protected function tearDown(): void { - /** @var Product $product */ - $product = $this->objectManager->create(\Magento\Catalog\Model\Product::class); - $product->load(self::SPECIAL_PRICE_TEST_PRODUCT_ID); - - $product->setCustomAttributes([ - 'special_price' => null, - 'special_from_date' => null, - 'special_to_date' => null, - ]); - $product->getResource()->saveAttribute($product, 'special_price'); - $product->save(); + parent::tearDown(); $this->updateStockItem(self::OUT_OF_STOCK_PRODUCT_SKU, true); - - parent::tearDown(); } } diff --git a/Test/Integration/Product/ProductsIndexingTestCase.php b/Test/Integration/Product/ProductsIndexingTestCase.php new file mode 100644 index 000000000..f712a3b0f --- /dev/null +++ b/Test/Integration/Product/ProductsIndexingTestCase.php @@ -0,0 +1,39 @@ +productIndexer = $this->objectManager->get(ProductIndexer::class); + $this->stockRegistry = $this->objectManager->get(StockRegistry::class); + + $this->objectManager + ->get(IndexerRegistry::class) + ->get('catalog_product_price') + ->reindexAll(); + } + + /** + * @throws NoSuchEntityException + */ + protected function updateStockItem(string $sku, bool $isInStock): void + { + $stockItem = $this->stockRegistry->getStockItemBySku($sku); + $stockItem->setIsInStock($isInStock); + $this->stockRegistry->updateStockItemBySku($sku, $stockItem); + } +} diff --git a/Test/Integration/Product/ReplicaIndexingTest.php b/Test/Integration/Product/ReplicaIndexingTest.php index 0a175f062..c0e27cc53 100644 --- a/Test/Integration/Product/ReplicaIndexingTest.php +++ b/Test/Integration/Product/ReplicaIndexingTest.php @@ -9,34 +9,25 @@ use Algolia\AlgoliaSearch\Helper\Entity\ProductHelper; use Algolia\AlgoliaSearch\Model\Indexer\Product as ProductIndexer; use Algolia\AlgoliaSearch\Model\IndicesConfigurator; -use Algolia\AlgoliaSearch\Test\Integration\IndexingTestCase; +use Algolia\AlgoliaSearch\Test\Integration\TestCase; -class ReplicaIndexingTest extends IndexingTestCase +class ReplicaIndexingTest extends TestCase { protected ?ReplicaManagerInterface $replicaManager = null; protected ?ProductIndexer $productIndexer = null; protected ?IndicesConfigurator $indicesConfigurator = null; - protected ?string $indexSuffix = null; + protected ?string $indexName = null; - public function setUp(): void + protected function setUp(): void { parent::setUp(); $this->productIndexer = $this->objectManager->get(ProductIndexer::class); $this->replicaManager = $this->objectManager->get(ReplicaManagerInterface::class); $this->indicesConfigurator = $this->objectManager->get(IndicesConfigurator::class); $this->indexSuffix = 'products'; - } - - protected function getIndexName(string $storeIndexPart): string - { - return $this->indexPrefix . $storeIndexPart . $this->indexSuffix; - } - - public function processFullReindexProducts(): void - { - $this->processFullReindex($this->productIndexer, $this->indexSuffix); + $this->indexName = $this->getIndexName('default'); } public function testReplicaLimits() @@ -57,16 +48,16 @@ public function testStandardReplicaConfig(): void $this->algoliaHelper->waitLastTask(); // Assert replica config created - $indexName = $this->getIndexName('default_'); - $currentSettings = $this->algoliaHelper->getSettings($indexName); + $primaryIndexName = $this->indexName; + $currentSettings = $this->algoliaHelper->getSettings($primaryIndexName); $this->assertArrayHasKey('replicas', $currentSettings); - $sortIndexName = $indexName . '_' . $sortAttr . '_' . $sortDir; + $replicaIndexName = $primaryIndexName . '_' . $sortAttr . '_' . $sortDir; - $this->assertTrue($this->isStandardReplica($currentSettings['replicas'], $sortIndexName)); - $this->assertFalse($this->isVirtualReplica($currentSettings['replicas'], $sortIndexName)); + $this->assertTrue($this->isStandardReplica($currentSettings['replicas'], $replicaIndexName)); + $this->assertFalse($this->isVirtualReplica($currentSettings['replicas'], $replicaIndexName)); - $replicaSettings = $this->assertReplicaIndexExists($indexName, $sortIndexName); + $replicaSettings = $this->assertReplicaIndexExists($primaryIndexName, $replicaIndexName); $this->assertStandardReplicaRanking($replicaSettings, "$sortDir($sortAttr)"); } @@ -78,7 +69,7 @@ public function testStandardReplicaConfig(): void */ public function testVirtualReplicaConfig(): void { - $indexName = $this->getIndexName('default_'); + $primaryIndexName = $this->getIndexName('default'); $ogSortingState = $this->configHelper->getSorting(); $productHelper = $this->objectManager->get(ProductHelper::class); @@ -111,16 +102,16 @@ public function testVirtualReplicaConfig(): void $this->algoliaHelper->waitLastTask(); // Assert replica config created - $currentSettings = $this->algoliaHelper->getSettings($indexName); + $currentSettings = $this->algoliaHelper->getSettings($primaryIndexName); $this->assertArrayHasKey('replicas', $currentSettings); - $sortIndexName = $indexName . '_' . $sortAttr . '_' . $sortDir; + $replicaIndexName = $primaryIndexName . '_' . $sortAttr . '_' . $sortDir; - $this->assertTrue($this->isVirtualReplica($currentSettings['replicas'], $sortIndexName)); - $this->assertFalse($this->isStandardReplica($currentSettings['replicas'], $sortIndexName)); + $this->assertTrue($this->isVirtualReplica($currentSettings['replicas'], $replicaIndexName)); + $this->assertFalse($this->isStandardReplica($currentSettings['replicas'], $replicaIndexName)); // Assert replica index created - $replicaSettings = $this->assertReplicaIndexExists($indexName, $sortIndexName); + $replicaSettings = $this->assertReplicaIndexExists($primaryIndexName, $replicaIndexName); $this->assertVirtualReplicaRanking($replicaSettings, "$sortDir($sortAttr)"); // Restore prior state (for this test only) @@ -166,7 +157,7 @@ protected function mockSortUpdate(string $sortAttr, string $sortDir, array $attr */ public function testReplicaRebuild(): void { - $indexName = $this->getIndexName('default_'); + $primaryIndexName = $this->getIndexName('default'); $this->mockSortUpdate('price', 'desc', ['virtualReplica' => 1]); $sorting = $this->objectManager->get(\Algolia\AlgoliaSearch\Service\Product\SortingTransformer::class)->getSortingIndices(1, null, null, true); @@ -177,20 +168,22 @@ public function testReplicaRebuild(): void $this->algoliaHelper->waitLastTask(); $rebuildCmd = $this->objectManager->get(\Algolia\AlgoliaSearch\Console\Command\ReplicaRebuildCommand::class); - $this->callReflectedMethod( + $this->invokeMethod( $rebuildCmd, 'execute', - $this->createMock(\Symfony\Component\Console\Input\InputInterface::class), - $this->createMock(\Symfony\Component\Console\Output\OutputInterface::class) + [ + $this->createMock(\Symfony\Component\Console\Input\InputInterface::class), + $this->createMock(\Symfony\Component\Console\Output\OutputInterface::class) + ] ); $this->algoliaHelper->waitLastTask(); - $currentSettings = $this->algoliaHelper->getSettings($indexName); + $currentSettings = $this->algoliaHelper->getSettings($primaryIndexName); $this->assertArrayHasKey('replicas', $currentSettings); $replicas = $currentSettings['replicas']; $this->assertEquals(count($sorting), count($replicas)); - $this->assertSortToReplicaConfigParity($indexName, $sorting, $replicas); + $this->assertSortToReplicaConfigParity($primaryIndexName, $sorting, $replicas); } /** @@ -201,8 +194,7 @@ public function testReplicaRebuild(): void */ public function testReplicaSync(): void { - $indexName = $this->getIndexName('default_'); - + $primaryIndexName = $this->getIndexName('default'); $this->mockSortUpdate('created_at', 'desc', ['virtualReplica' => 1]); $sorting = $this->objectManager->get(\Algolia\AlgoliaSearch\Service\Product\SortingTransformer::class)->getSortingIndices(1, null, null, true); @@ -214,12 +206,12 @@ public function testReplicaSync(): void $cmd->syncReplicas(); $this->algoliaHelper->waitLastTask(); - $currentSettings = $this->algoliaHelper->getSettings($indexName); + $currentSettings = $this->algoliaHelper->getSettings($primaryIndexName); $this->assertArrayHasKey('replicas', $currentSettings); $replicas = $currentSettings['replicas']; $this->assertEquals(count($sorting), count($replicas)); - $this->assertSortToReplicaConfigParity($indexName, $sorting, $replicas); + $this->assertSortToReplicaConfigParity($primaryIndexName, $sorting, $replicas); } protected function assertSortToReplicaConfigParity(string $primaryIndexName, array $sorting, array $replicas): void diff --git a/Test/Integration/Queue/QueueTest.php b/Test/Integration/Queue/QueueTest.php index c389830b6..6466815ca 100644 --- a/Test/Integration/Queue/QueueTest.php +++ b/Test/Integration/Queue/QueueTest.php @@ -30,7 +30,7 @@ class QueueTest extends TestCase /** @var Queue */ private $queue; - public function setUp(): void + protected function setUp(): void { parent::setUp(); diff --git a/Test/Integration/TestCase.php b/Test/Integration/TestCase.php index cca981563..35d179603 100644 --- a/Test/Integration/TestCase.php +++ b/Test/Integration/TestCase.php @@ -3,6 +3,7 @@ namespace Algolia\AlgoliaSearch\Test\Integration; use Algolia\AlgoliaSearch\Exceptions\AlgoliaException; +use Algolia\AlgoliaSearch\Exceptions\ExceededRetriesException; use Algolia\AlgoliaSearch\Helper\AlgoliaHelper; use Algolia\AlgoliaSearch\Helper\ConfigHelper; use Algolia\AlgoliaSearch\Setup\Patch\Schema\ConfigPatch; @@ -47,18 +48,29 @@ abstract class TestCase extends \TC /** @var ProductMetadataInterface */ protected $productMetadata; - public function setUp(): void + protected ?string $indexSuffix = null; + + protected function setUp(): void { $this->bootstrap(); } - public function tearDown(): void + /** + * @throws ExceededRetriesException + * @throws AlgoliaException + */ + protected function tearDown(): void { $this->clearIndices(); $this->algoliaHelper->waitLastTask(); $this->clearIndices(); // Remaining replicas } + protected function getIndexName(string $storeIndexPart): string + { + return $this->indexPrefix . $storeIndexPart . ($this->indexSuffix ? '_' . $this->indexSuffix : ''); + } + protected function resetConfigs($configs = []) { /** @var ConfigPatch $installClass */ @@ -153,26 +165,6 @@ protected function setConfigFromArray(array $settings): void } } - /** - * @throws \ReflectionException - */ - protected function mockProperty(object $object, string $propertyName, string $propertyClass): void - { - $mock = $this->createMock($propertyClass); - $reflection = new \ReflectionClass($object); - $property = $reflection->getProperty($propertyName); - $property->setValue($object, $mock); - } - - /** - * @throws \ReflectionException - */ - protected function callReflectedMethod(object $object, string $method, mixed ...$args): void - { - $reflection = new \ReflectionClass($object); - $reflection->getMethod($method)->invoke($object, ...$args); - } - protected function clearIndices() { $indices = $this->algoliaHelper->listIndexes(); @@ -202,7 +194,7 @@ private function bootstrap() return; } - $this->objectManager = Bootstrap::getObjectManager(); + $this->objectManager = $this->getObjectManager(); $this->productMetadata = $this->objectManager->get(ProductMetadataInterface::class); if (version_compare($this->getMagentoVersion(), '2.4.7', '<')) { @@ -232,6 +224,18 @@ private function bootstrap() $this->boostrapped = true; } + + /** + * @throws \ReflectionException + */ + protected function mockProperty(object $object, string $propertyName, string $propertyClass): void + { + $mock = $this->createMock($propertyClass); + $reflection = new \ReflectionClass($object); + $property = $reflection->getProperty($propertyName); + $property->setValue($object, $mock); + } + /** * Call protected/private method of a class. * @@ -243,13 +247,10 @@ private function bootstrap() * * @return mixed method return */ - protected function invokeMethod(&$object, $methodName, array $parameters = []) + protected function invokeMethod(object $object, string $methodName, array $parameters = []) { $reflection = new \ReflectionClass(get_class($object)); - $method = $reflection->getMethod($methodName); - $method->setAccessible(true); - - return $method->invokeArgs($object, $parameters); + $reflection->getMethod($methodName)->invokeArgs($object, $parameters); } private function getMagentoVersion() diff --git a/Test/Unit/ConfigHelperTest.php b/Test/Unit/ConfigHelperTest.php index 0b980d118..9ff2849eb 100644 --- a/Test/Unit/ConfigHelperTest.php +++ b/Test/Unit/ConfigHelperTest.php @@ -44,7 +44,7 @@ class ConfigHelperTest extends TestCase protected GroupExcludedWebsiteRepositoryInterface $groupExcludedWebsiteRepository; protected CookieHelper $cookieHelper; - public function setUp(): void + protected function setUp(): void { $this->configInterface = $this->createMock(ScopeConfigInterface::class); $this->configWriter = $this->createMock(WriterInterface::class); From 43d86debff118a619c8b571360a4efdfcce1c6b7 Mon Sep 17 00:00:00 2001 From: Damien Couchez Date: Wed, 16 Oct 2024 13:50:06 +0200 Subject: [PATCH 36/40] MAGE-1051: add missing return on invokeMethod --- Test/Integration/TestCase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Test/Integration/TestCase.php b/Test/Integration/TestCase.php index 35d179603..2c3f39ec0 100644 --- a/Test/Integration/TestCase.php +++ b/Test/Integration/TestCase.php @@ -250,7 +250,7 @@ protected function mockProperty(object $object, string $propertyName, string $pr protected function invokeMethod(object $object, string $methodName, array $parameters = []) { $reflection = new \ReflectionClass(get_class($object)); - $reflection->getMethod($methodName)->invokeArgs($object, $parameters); + return $reflection->getMethod($methodName)->invokeArgs($object, $parameters); } private function getMagentoVersion() From ac34f9b015b540e1cdc1bb9882fd9d0492c8a1e2 Mon Sep 17 00:00:00 2001 From: Damien Couchez Date: Thu, 24 Oct 2024 14:10:59 +0200 Subject: [PATCH 37/40] MAGE-1093: removed mode during setSettings if index has NeuralSearch enabled (#1633) --- Helper/AlgoliaHelper.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Helper/AlgoliaHelper.php b/Helper/AlgoliaHelper.php index c8662e180..52fb59fa3 100755 --- a/Helper/AlgoliaHelper.php +++ b/Helper/AlgoliaHelper.php @@ -361,6 +361,10 @@ public function mergeSettings($indexName, $settings, $mergeSettingsFrom = '') $removes = ['slaves', 'replicas', 'decompoundedAttributes']; + if (isset($onlineSettings['mode']) && $onlineSettings['mode'] == 'neuralSearch') { + $removes[] = 'mode'; + } + if (isset($settings['attributesToIndex'])) { $settings['searchableAttributes'] = $settings['attributesToIndex']; unset($settings['attributesToIndex']); From ec4c5d921f164276d6971b42507292b1f594ce1d Mon Sep 17 00:00:00 2001 From: Mohammad Rahman <167926401+mrahman3177@users.noreply.github.com> Date: Wed, 30 Oct 2024 06:22:50 -0400 Subject: [PATCH 38/40] MAGE-992: php client version updated (#1632) * MAGE-992: php client version updated * MAGE-992: code updated as per code review suggestions --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 531855ccd..cc61a0d29 100755 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ "require": { "php": "~8.1|~8.2|~8.3", "magento/framework": "~103.0", - "algolia/algoliasearch-client-php": "^4.0@beta", + "algolia/algoliasearch-client-php": "^4.0", "guzzlehttp/guzzle": "^6.3.3|^7.3.0", "ext-json": "*", "ext-PDO": "*", From 4738dc2cba7c02701f71d4fd8247a01ac6ce3e00 Mon Sep 17 00:00:00 2001 From: Damien Couchez Date: Wed, 30 Oct 2024 16:34:39 +0100 Subject: [PATCH 39/40] MAGE-1078: separate classes to prevent phpcs to report an error (#1637) --- Test/Unit/ConfigHelperTest.php | 10 ---------- Test/Unit/ConfigHelperTestable.php | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 10 deletions(-) create mode 100644 Test/Unit/ConfigHelperTestable.php diff --git a/Test/Unit/ConfigHelperTest.php b/Test/Unit/ConfigHelperTest.php index 9ff2849eb..c3da8c03d 100644 --- a/Test/Unit/ConfigHelperTest.php +++ b/Test/Unit/ConfigHelperTest.php @@ -2,7 +2,6 @@ namespace Algolia\AlgoliaSearch\Test\Unit; -use Algolia\AlgoliaSearch\Helper\ConfigHelper; use Magento\Cookie\Helper\Cookie as CookieHelper; use Magento\Customer\Api\GroupExcludedWebsiteRepositoryInterface; use Magento\Customer\Model\ResourceModel\Group\Collection as GroupCollection; @@ -18,15 +17,6 @@ use Magento\Store\Model\StoreManagerInterface; use PHPUnit\Framework\TestCase; -class ConfigHelperTestable extends ConfigHelper -{ - /** expose protected methods for unit testing */ - public function serialize(array $value): string - { - return parent::serialize($value); - } -} - class ConfigHelperTest extends TestCase { protected ConfigHelperTestable $configHelper; diff --git a/Test/Unit/ConfigHelperTestable.php b/Test/Unit/ConfigHelperTestable.php new file mode 100644 index 000000000..4b483658e --- /dev/null +++ b/Test/Unit/ConfigHelperTestable.php @@ -0,0 +1,14 @@ + Date: Thu, 31 Oct 2024 10:28:49 +0100 Subject: [PATCH 40/40] MAGE-1078: update extension for 3.14.3 release (#1638) --- CHANGELOG.md | 14 ++++++++++++++ README.md | 2 +- composer.json | 2 +- etc/module.xml | 2 +- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57cb4edfc..79b24ce5f 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # CHANGE LOG +## 3.14.3 + +### Updates +- Updated PHP client version in the composer file +- Tests: added new integration scenarios including multi-stores ones. + +### Bug Fixes +- Fixed landing page typing error +- Improved query method for alternate root categories - Thank you @igorfigueiredogen +- Fixed an error where a missing prefix can throw errors with strong types added to ConfigHelper +- Fixed an error where stale cache data was preventing ranking applied on replica +- Fixed reindexing issue with NeuralSearch +- Tests : Fixed current integration tests + ## 3.14.2 ### Updates diff --git a/README.md b/README.md index 141da2392..ea899c708 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Algolia Search & Discovery extension for Magento 2 ================================================== -![Latest version](https://img.shields.io/badge/latest-3.14.2-green) +![Latest version](https://img.shields.io/badge/latest-3.14.3-green) ![Magento 2](https://img.shields.io/badge/Magento-2.4.x-orange) ![PHP](https://img.shields.io/badge/PHP-8.1%2C8.2%2C8.3-blue) diff --git a/composer.json b/composer.json index cc61a0d29..d646e8b3d 100755 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "Algolia Search & Discovery extension for Magento 2", "type": "magento2-module", "license": ["MIT"], - "version": "3.14.2", + "version": "3.14.3", "require": { "php": "~8.1|~8.2|~8.3", "magento/framework": "~103.0", diff --git a/etc/module.xml b/etc/module.xml index 9ae6e168d..d02f109b0 100755 --- a/etc/module.xml +++ b/etc/module.xml @@ -1,6 +1,6 @@ - +