Skip to content

Commit dc21e43

Browse files
authored
Merge pull request #1627 from algolia/feature/MAGE-1044-replica-testing
Feature/mage 1044 replica testing
2 parents 56bd4dc + 48be4f8 commit dc21e43

File tree

4 files changed

+423
-5
lines changed

4 files changed

+423
-5
lines changed

Console/Command/ReplicaSyncCommand.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
use Algolia\AlgoliaSearch\Exceptions\ExceededRetriesException;
1212
use Algolia\AlgoliaSearch\Helper\Entity\ProductHelper;
1313
use Algolia\AlgoliaSearch\Service\StoreNameFetcher;
14-
use Magento\Framework\App\State;
14+
use Magento\Framework\App\State as AppState;
1515
use Magento\Framework\Console\Cli;
1616
use Magento\Framework\Exception\LocalizedException;
1717
use Magento\Framework\Exception\NoSuchEntityException;
@@ -27,12 +27,12 @@ public function __construct(
2727
protected ReplicaManagerInterface $replicaManager,
2828
protected ProductHelper $productHelper,
2929
protected StoreManagerInterface $storeManager,
30-
State $state,
30+
AppState $appState,
3131
StoreNameFetcher $storeNameFetcher,
3232
?string $name = null
3333
)
3434
{
35-
parent::__construct($state, $storeNameFetcher, $name);
35+
parent::__construct($appState, $storeNameFetcher, $name);
3636
}
3737

3838
protected function getReplicaCommandName(): string

Helper/ConfigHelper.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1157,7 +1157,7 @@ public function getRawSortingValue(?int $storeId = null): string
11571157
* @param int|null $scopeId
11581158
* @return void
11591159
*/
1160-
public function setSorting(array $sorting, ?string $scope = null, ?int $scopeId = null): void
1160+
public function setSorting(array $sorting, string $scope = Magento\Framework\App\Config\ScopeConfigInterface::SCOPE_TYPE_DEFAULT, ?int $scopeId = null): void
11611161
{
11621162
$this->configWriter->save(
11631163
self::SORTING_INDICES,
Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
<?php
2+
3+
namespace Algolia\AlgoliaSearch\Test\Integration\Product;
4+
5+
use Algolia\AlgoliaSearch\Api\Product\ReplicaManagerInterface;
6+
use Algolia\AlgoliaSearch\Exceptions\AlgoliaException;
7+
use Algolia\AlgoliaSearch\Exceptions\ExceededRetriesException;
8+
use Algolia\AlgoliaSearch\Helper\ConfigHelper;
9+
use Algolia\AlgoliaSearch\Helper\Entity\ProductHelper;
10+
use Algolia\AlgoliaSearch\Model\Indexer\Product as ProductIndexer;
11+
use Algolia\AlgoliaSearch\Model\IndicesConfigurator;
12+
use Algolia\AlgoliaSearch\Test\Integration\IndexingTestCase;
13+
14+
class ReplicaIndexingTest extends IndexingTestCase
15+
{
16+
protected ?ReplicaManagerInterface $replicaManager = null;
17+
protected ?ProductIndexer $productIndexer = null;
18+
19+
protected ?IndicesConfigurator $indicesConfigurator = null;
20+
21+
protected ?string $indexSuffix = null;
22+
23+
public function setUp(): void
24+
{
25+
parent::setUp();
26+
$this->productIndexer = $this->objectManager->get(ProductIndexer::class);
27+
$this->replicaManager = $this->objectManager->get(ReplicaManagerInterface::class);
28+
$this->indicesConfigurator = $this->objectManager->get(IndicesConfigurator::class);
29+
$this->indexSuffix = 'products';
30+
}
31+
32+
protected function getIndexName(string $storeIndexPart): string
33+
{
34+
return $this->indexPrefix . $storeIndexPart . $this->indexSuffix;
35+
}
36+
37+
public function processFullReindexProducts(): void
38+
{
39+
$this->processFullReindex($this->productIndexer, $this->indexSuffix);
40+
}
41+
42+
public function testReplicaLimits()
43+
{
44+
$this->assertEquals(20, $this->replicaManager->getMaxVirtualReplicasPerIndex());
45+
}
46+
47+
/**
48+
* @magentoConfigFixture current_store algoliasearch_instant/instant/is_instant_enabled 1
49+
*/
50+
public function testStandardReplicaConfig(): void
51+
{
52+
$sortAttr = 'created_at';
53+
$sortDir = 'desc';
54+
$this->assertSortingAttribute($sortAttr, $sortDir);
55+
56+
$this->indicesConfigurator->saveConfigurationToAlgolia(1);
57+
$this->algoliaHelper->waitLastTask();
58+
59+
// Assert replica config created
60+
$indexName = $this->getIndexName('default_');
61+
$currentSettings = $this->algoliaHelper->getSettings($indexName);
62+
$this->assertArrayHasKey('replicas', $currentSettings);
63+
64+
$sortIndexName = $indexName . '_' . $sortAttr . '_' . $sortDir;
65+
66+
$this->assertTrue($this->isStandardReplica($currentSettings['replicas'], $sortIndexName));
67+
$this->assertFalse($this->isVirtualReplica($currentSettings['replicas'], $sortIndexName));
68+
69+
$replicaSettings = $this->assertReplicaIndexExists($indexName, $sortIndexName);
70+
$this->assertStandardReplicaRanking($replicaSettings, "$sortDir($sortAttr)");
71+
}
72+
73+
/**
74+
* This test involves verifying modifications in the database
75+
* so it must be responsible for its own set up and tear down
76+
* @magentoDbIsolation disabled
77+
* @group virtual
78+
*/
79+
public function testVirtualReplicaConfig(): void
80+
{
81+
$indexName = $this->getIndexName('default_');
82+
$ogSortingState = $this->configHelper->getSorting();
83+
84+
$productHelper = $this->objectManager->get(ProductHelper::class);
85+
$sortAttr = 'color';
86+
$sortDir = 'asc';
87+
$attributes = $productHelper->getAllAttributes();
88+
$this->assertArrayHasKey($sortAttr, $attributes);
89+
90+
$this->assertNoSortingAttribute($sortAttr, $sortDir);
91+
92+
$sorting = $this->configHelper->getSorting();
93+
$sorting[] = [
94+
'attribute' => $sortAttr,
95+
'sort' => $sortDir,
96+
'sortLabel' => $sortAttr,
97+
'virtualReplica' => 1
98+
];
99+
$this->configHelper->setSorting($sorting);
100+
101+
$this->assertConfigInDb(ConfigHelper::SORTING_INDICES, json_encode($sorting));
102+
103+
$this->refreshConfigFromDb();
104+
105+
$this->assertSortingAttribute($sortAttr, $sortDir);
106+
107+
// Cannot use config fixture because we have disabled db isolation
108+
$this->setConfig(ConfigHelper::IS_INSTANT_ENABLED, 1);
109+
110+
$this->indicesConfigurator->saveConfigurationToAlgolia(1);
111+
$this->algoliaHelper->waitLastTask();
112+
113+
// Assert replica config created
114+
$currentSettings = $this->algoliaHelper->getSettings($indexName);
115+
$this->assertArrayHasKey('replicas', $currentSettings);
116+
117+
$sortIndexName = $indexName . '_' . $sortAttr . '_' . $sortDir;
118+
119+
$this->assertTrue($this->isVirtualReplica($currentSettings['replicas'], $sortIndexName));
120+
$this->assertFalse($this->isStandardReplica($currentSettings['replicas'], $sortIndexName));
121+
122+
// Assert replica index created
123+
$replicaSettings = $this->assertReplicaIndexExists($indexName, $sortIndexName);
124+
$this->assertVirtualReplicaRanking($replicaSettings, "$sortDir($sortAttr)");
125+
126+
// Restore prior state (for this test only)
127+
$this->configHelper->setSorting($ogSortingState);
128+
$this->setConfig(ConfigHelper::IS_INSTANT_ENABLED, 0);
129+
}
130+
131+
/**
132+
* ConfigHelper::setSorting uses WriterInterface which does not update unless DB isolation is disabled
133+
* This provides a workaround to test using MutableScopeConfigInterface with DB isolation enabled
134+
*/
135+
protected function mockSortUpdate(string $sortAttr, string $sortDir, array $attr): void
136+
{
137+
$sorting = $this->configHelper->getSorting();
138+
$existing = array_filter($sorting, function ($item) use ($sortAttr, $sortDir) {
139+
return $item['attribute'] === $sortAttr && $item['sort'] === $sortDir;
140+
});
141+
142+
143+
if ($existing) {
144+
$idx = array_key_first($existing);
145+
$sorting[$idx] = array_merge($existing[$idx], $attr);
146+
}
147+
else {
148+
$sorting[] = array_merge(
149+
[
150+
'attribute' => $sortAttr,
151+
'sort' => $sortDir,
152+
'sortLabel' => $sortAttr
153+
],
154+
$attr
155+
);
156+
}
157+
$this->setConfig(ConfigHelper::SORTING_INDICES, json_encode($sorting));
158+
}
159+
160+
/**
161+
* @depends testReplicaSync
162+
* @magentoConfigFixture current_store algoliasearch_instant/instant/is_instant_enabled 1
163+
* @throws AlgoliaException
164+
* @throws ExceededRetriesException
165+
* @throws \ReflectionException
166+
*/
167+
public function testReplicaRebuild(): void
168+
{
169+
$indexName = $this->getIndexName('default_');
170+
171+
$this->mockSortUpdate('price', 'desc', ['virtualReplica' => 1]);
172+
$sorting = $this->objectManager->get(\Algolia\AlgoliaSearch\Service\Product\SortingTransformer::class)->getSortingIndices(1, null, null, true);
173+
174+
$syncCmd = $this->objectManager->get(\Algolia\AlgoliaSearch\Console\Command\ReplicaSyncCommand::class);
175+
$this->mockProperty($syncCmd, 'output', \Symfony\Component\Console\Output\OutputInterface::class);
176+
$syncCmd->syncReplicas();
177+
$this->algoliaHelper->waitLastTask();
178+
179+
$rebuildCmd = $this->objectManager->get(\Algolia\AlgoliaSearch\Console\Command\ReplicaRebuildCommand::class);
180+
$this->callReflectedMethod(
181+
$rebuildCmd,
182+
'execute',
183+
$this->createMock(\Symfony\Component\Console\Input\InputInterface::class),
184+
$this->createMock(\Symfony\Component\Console\Output\OutputInterface::class)
185+
);
186+
$this->algoliaHelper->waitLastTask();
187+
188+
$currentSettings = $this->algoliaHelper->getSettings($indexName);
189+
$this->assertArrayHasKey('replicas', $currentSettings);
190+
$replicas = $currentSettings['replicas'];
191+
192+
$this->assertEquals(count($sorting), count($replicas));
193+
$this->assertSortToReplicaConfigParity($indexName, $sorting, $replicas);
194+
}
195+
196+
/**
197+
* @magentoConfigFixture current_store algoliasearch_instant/instant/is_instant_enabled 1
198+
* @throws AlgoliaException
199+
* @throws ExceededRetriesException
200+
* @throws \ReflectionException
201+
*/
202+
public function testReplicaSync(): void
203+
{
204+
$indexName = $this->getIndexName('default_');
205+
206+
$this->mockSortUpdate('created_at', 'desc', ['virtualReplica' => 1]);
207+
208+
$sorting = $this->objectManager->get(\Algolia\AlgoliaSearch\Service\Product\SortingTransformer::class)->getSortingIndices(1, null, null, true);
209+
210+
$cmd = $this->objectManager->get(\Algolia\AlgoliaSearch\Console\Command\ReplicaSyncCommand::class);
211+
212+
$this->mockProperty($cmd, 'output', \Symfony\Component\Console\Output\OutputInterface::class);
213+
214+
$cmd->syncReplicas();
215+
$this->algoliaHelper->waitLastTask();
216+
217+
$currentSettings = $this->algoliaHelper->getSettings($indexName);
218+
$this->assertArrayHasKey('replicas', $currentSettings);
219+
$replicas = $currentSettings['replicas'];
220+
221+
$this->assertEquals(count($sorting), count($replicas));
222+
$this->assertSortToReplicaConfigParity($indexName, $sorting, $replicas);
223+
}
224+
225+
protected function assertSortToReplicaConfigParity(string $primaryIndexName, array $sorting, array $replicas): void
226+
{
227+
foreach ($sorting as $sortAttr) {
228+
$replicaIndexName = $sortAttr['name'];
229+
$isVirtual = array_key_exists('virtualReplica', $sortAttr) && $sortAttr['virtualReplica'];
230+
$needle = $isVirtual
231+
? "virtual($replicaIndexName)"
232+
: $replicaIndexName;
233+
$this->assertContains($needle, $replicas);
234+
235+
$replicaSettings = $this->assertReplicaIndexExists($primaryIndexName, $replicaIndexName);
236+
$sort = reset($sortAttr['ranking']);
237+
if ($isVirtual) {
238+
$this->assertVirtualReplicaRanking($replicaSettings, $sort);
239+
} else {
240+
$this->assertStandardReplicaRanking($replicaSettings, $sort);
241+
}
242+
}
243+
}
244+
245+
protected function assertReplicaIndexExists(string $primaryIndexName, string $replicaIndexName): array
246+
{
247+
$replicaSettings = $this->algoliaHelper->getSettings($replicaIndexName);
248+
$this->assertArrayHasKey('primary', $replicaSettings);
249+
$this->assertEquals($primaryIndexName, $replicaSettings['primary']);
250+
return $replicaSettings;
251+
}
252+
253+
protected function assertReplicaRanking(array $replicaSettings, string $rankingKey, string $sort) {
254+
$this->assertArrayHasKey($rankingKey, $replicaSettings);
255+
$this->assertEquals($sort, reset($replicaSettings[$rankingKey]));
256+
}
257+
258+
protected function assertStandardReplicaRanking(array $replicaSettings, string $sort): void
259+
{
260+
$this->assertReplicaRanking($replicaSettings, 'ranking', $sort);
261+
}
262+
263+
protected function assertVirtualReplicaRanking(array $replicaSettings, string $sort): void
264+
{
265+
$this->assertReplicaRanking($replicaSettings, 'customRanking', $sort);
266+
}
267+
268+
protected function assertStandardReplicaRankingOld(array $replicaSettings, string $sortAttr, string $sortDir): void
269+
{
270+
$this->assertArrayHasKey('ranking', $replicaSettings);
271+
$this->assertEquals("$sortDir($sortAttr)", array_shift($replicaSettings['ranking']));
272+
}
273+
274+
protected function assertVirtualReplicaRankingOld(array $replicaSettings, string $sortAttr, string $sortDir): void
275+
{
276+
$this->assertArrayHasKey('customRanking', $replicaSettings);
277+
$this->assertEquals("$sortDir($sortAttr)", array_shift($replicaSettings['customRanking']));
278+
}
279+
280+
/**
281+
* @param string[] $replicaSetting
282+
* @param string $replicaIndexName
283+
* @return bool
284+
*/
285+
protected function isVirtualReplica(array $replicaSetting, string $replicaIndexName): bool
286+
{
287+
return (bool) array_filter(
288+
$replicaSetting,
289+
function ($replica) use ($replicaIndexName) {
290+
return str_contains($replica, "virtual($replicaIndexName)");
291+
}
292+
);
293+
}
294+
295+
protected function isStandardReplica(array $replicaSetting, string $replicaIndexName): bool
296+
{
297+
return (bool) array_filter(
298+
$replicaSetting,
299+
function ($replica) use ($replicaIndexName) {
300+
$regex = '/^' . preg_quote($replicaIndexName) . '$/';
301+
return preg_match($regex, $replica);
302+
}
303+
);
304+
}
305+
306+
protected function hasSortingAttribute($sortAttr, $sortDir): bool
307+
{
308+
$sorting = $this->configHelper->getSorting();
309+
return (bool) array_filter(
310+
$sorting,
311+
function($sort) use ($sortAttr, $sortDir) {
312+
return $sort['attribute'] == $sortAttr
313+
&& $sort['sort'] == $sortDir;
314+
}
315+
);
316+
}
317+
318+
protected function assertSortingAttribute($sortAttr, $sortDir): void
319+
{
320+
$this->assertTrue($this->hasSortingAttribute($sortAttr, $sortDir));
321+
}
322+
323+
protected function assertNoSortingAttribute($sortAttr, $sortDir): void
324+
{
325+
$this->assertFalse($this->hasSortingAttribute($sortAttr, $sortDir));
326+
}
327+
328+
}

0 commit comments

Comments
 (0)