'holínky', 'to' => 'nike'], ['from' => 'wpjshop', 'to' => 'kupshop'], ['from' => 'schody', 'to' => 'žebříky'], ]; public function setUp(): void { parent::setUp(); $this->fulltextElastic = $this->autowire(FulltextElasticForTest::class); } public function testUpdateProduct() { // Indexing $this->fulltextElastic->setDataOnly(true); $this->fulltextElastic->updateProductFulltext([1, 2]); $data = $this->fulltextElastic->getData(); $this->assertArraySubset($this->data->getTable('products')->getRow(0), $data); // Index settings $indexSettings = $this->fulltextElastic->returnIndexSettings(FulltextElastic::INDEX_PRODUCTS); $this->assertEquals($this->data->getTable('products')->getRow(1)[0], $indexSettings); // Searching $this->fulltextElastic->searchProducts('testik neco', 10, 5, 'price'); $data = $this->fulltextElastic->getData(); $this->assertEquals($this->data->getTable('products')->getRow(2)[0], $data); } public function testUpdateProductPartial() { $this->fulltextElastic->setDataOnly(true); $this->fulltextElastic->partialProductsUpdate([ 1 => ['title', 'ean', 'discount', ElasticUpdateGroup::Sections], 2 => [ElasticUpdateGroup::Price, ElasticUpdateGroup::Parameters], ]); $data = $this->fulltextElastic->getData(); $this->assertArrayHasKeys(['title', 'ean', 'sections', 'parent_sections', 'price'], $data[1]['doc']); $this->assertArrayNotHasKey('parameters', $data[1]['doc']); $this->assertArrayNotHasKey('variations', $data[1]['doc']); $this->assertArrayNotHasKey('code', $data[1]['doc']); $this->assertArrayHasKeys(['parameters', 'price'], $data[3]['doc']); $this->assertArrayNotHasKey('ean', $data[3]['doc']); $this->assertArrayNotHasKey('sections', $data[3]['doc']); } public function testUpdateSection() { // Indexing $this->fulltextElastic->setDataOnly(true); $this->fulltextElastic->updateSection([1, 2]); $data = $this->fulltextElastic->getData(); $this->assertArraySubset($this->data->getTable('section')->getRow(0), $data); // Index settings $indexSettings = $this->fulltextElastic->returnIndexSettings(FulltextElastic::INDEX_SECTIONS); $this->assertEquals($this->data->getTable('section')->getRow(1)[0], $indexSettings); // Searching $config = [FulltextElastic::INDEX_SECTIONS => ['count' => 10, 'offset' => 5]]; $this->fulltextElastic->search('testik neco', $config, [FulltextElastic::INDEX_SECTIONS]); $data = $this->fulltextElastic->getData(); $this->assertEquals($this->data->getTable('section')->getRow(2)[0], $data[1]); } public function testUpdateProducer() { // Indexing $this->fulltextElastic->setDataOnly(true); $this->fulltextElastic->updateProducer(10); $data = $this->fulltextElastic->getData(); $this->assertArraySubset($this->data->getTable('producer')->getRow(0), $data); // Index settings $indexSettings = $this->fulltextElastic->returnIndexSettings(FulltextElastic::INDEX_PRODUCERS); $this->assertEquals($this->data->getTable('producer')->getRow(1)[0], $indexSettings); // Searching $config = [FulltextElastic::INDEX_PRODUCERS => ['count' => 10, 'offset' => 5]]; $this->fulltextElastic->search('testik neco', $config, [FulltextElastic::INDEX_PRODUCERS]); $data = $this->fulltextElastic->getData(); $this->assertEquals($this->data->getTable('producer')->getRow(2)[0], $data[1]); } public function testUpdateArticle() { $this->fulltextElastic->setDataOnly(true); $this->fulltextElastic->updateArticle(6); $data = $this->fulltextElastic->getData(); $this->assertArraySubset($this->data->getTable('article')->getRow(0), $data); // Index settings $indexSettings = $this->fulltextElastic->returnIndexSettings(FulltextElastic::INDEX_ARTICLES); $this->assertEquals($this->data->getTable('article')->getRow(1)[0], $indexSettings); // Searching $config = [FulltextElastic::INDEX_ARTICLES => ['count' => 10, 'offset' => 5]]; $this->fulltextElastic->search('testik neco', $config, [FulltextElastic::INDEX_ARTICLES]); $data = $this->fulltextElastic->getData(); $this->assertEquals($this->data->getTable('article')->getRow(2)[0], $data[1]); } public function testUpdateProducerEmpty() { $this->fulltextElastic->setDataOnly(true); $dbcfg = \Settings::getDefault(); $dbcfg->cat_show_empty = 'N'; $result = $this->fulltextElastic->updateProducer(10); $this->assertTrue($result); $data = $this->fulltextElastic->getData(); $this->assertEquals($this->data->getTable('producer')->getRow(0), $data); sqlQuery('UPDATE `products` SET `producer` = NULL WHERE `producer` = 10'); $result = $this->fulltextElastic->updateProducer(10); $this->assertFalse($result); $data = $this->fulltextElastic->getData(); $this->assertEmpty($data); } public function testUpdateSectionEmpty() { $this->fulltextElastic->setDataOnly(true); $dbcfg = \Settings::getDefault(); $dbcfg->cat_show_empty = 'N'; $this->fulltextElastic->updateSection(1); $data = $this->fulltextElastic->getData(); $this->assertEmpty($data); $this->fulltextElastic->updateSection([1, 2]); $data = $this->fulltextElastic->getData(); $this->assertArraySubset($this->data->getTable('section2')->getRow(0), $data); } public function testSearch(): void { $this->fulltextElastic->updateIndex(); $this->fulltextElastic->commit(); $config = []; foreach (FulltextElastic::INDEX_TYPES as $type) { $config[$type] = [ 'count' => 5, ]; } $result = $this->fulltextElastic->search('nike', $config); $this->assertEquals(FulltextElastic::INDEX_TYPES, array_keys($result), 'Assert that result has all search types'); $this->assertCount(1, $result[FulltextElastic::INDEX_PRODUCTS]); $this->assertCount(1, $result[FulltextElastic::INDEX_SECTIONS]); $this->assertCount(1, $result[FulltextElastic::INDEX_PRODUCERS]); $this->assertCount(0, $result[FulltextElastic::INDEX_ARTICLES]); // Product id=10 has very long product code $prodRes = $this->fulltextElastic->searchProducts('RD102velmidlouhykodproduktuprotest', 5, 0); $this->assertArrayHasKey('10', $prodRes); $this->assertCount(1, $prodRes); $prodRes = $this->fulltextElastic->searchProducts('RD102', 5, 0); $this->assertArrayHasKey('10', $prodRes); $this->assertCount(1, $prodRes); $prodRes = $this->fulltextElastic->searchProducts('RD102velmidlouhykodproduktuprotest vrtačka', 5, 0); $this->assertArrayHasKey('10', $prodRes); $this->assertCount(1, $prodRes); // Search in "Columbia Lay D Down" without spaces $prodRes = $this->fulltextElastic->searchProducts('ydd', 5, 0); $this->assertArrayHasKey('4', $prodRes); $this->assertCount(1, $prodRes); } public function getDataSet() { $this->data = $this->getJsonDataSetFromFile(); return parent::getDataSet(); } public function testSynonymsProducts() { $this->fulltextElastic->updateIndex(FulltextElastic::INDEX_PRODUCTS); $this->fulltextElastic->updateSynonyms($this->testSynonyms); $this->fulltextElastic->commit(); $testTerm = $this->testSynonyms[0]['from']; $realTerm = $this->testSynonyms[0]['to']; $prodRes = $this->fulltextElastic->searchProducts($testTerm, 5, 0); $prodRes = array_keys($prodRes); $this->assertNotEmpty($prodRes); $sqlProdRes = sqlQueryBuilder() ->select('*') ->from('products', 'p') ->where("p.title LIKE '%{$realTerm}%'") ->execute() ->fetchAllAssociative(); $this->assertEquals((int) $prodRes[0], (int) $sqlProdRes[0]['id']); } public function testSynonymsProducers() { $this->fulltextElastic->updateIndex(FulltextElastic::INDEX_PRODUCERS); $this->fulltextElastic->updateSynonyms($this->testSynonyms); $this->fulltextElastic->commit(); $testTerm = $this->testSynonyms[0]['from']; $realTerm = $this->testSynonyms[0]['to']; $config = [FulltextElastic::INDEX_PRODUCERS => ['count' => 5, 'offset' => 0]]; $res = $this->fulltextElastic->search($testTerm, $config, [FulltextElastic::INDEX_PRODUCERS]); $this->assertNotEmpty($res[FulltextElastic::INDEX_PRODUCERS]); $sqlRes = sqlQueryBuilder() ->select('*') ->from('producers', 'p') ->where("p.name LIKE '%{$realTerm}%'") ->execute() ->fetchAllAssociative(); $this->assertEquals((int) $res[FulltextElastic::INDEX_PRODUCERS][0]['id'], (int) $sqlRes[0]['id']); } public function testSynonymsArticles() { $this->fulltextElastic->updateIndex(FulltextElastic::INDEX_ARTICLES); $this->fulltextElastic->updateSynonyms($this->testSynonyms); $this->fulltextElastic->commit(); $testTerm = $this->testSynonyms[1]['from']; $realTerm = $this->testSynonyms[1]['to']; $config = [FulltextElastic::INDEX_ARTICLES => ['count' => 5, 'offset' => 0]]; $res = $this->fulltextElastic->search($testTerm, $config, [FulltextElastic::INDEX_ARTICLES]); $this->assertNotEmpty($res[FulltextElastic::INDEX_ARTICLES]); $sqlRes = sqlQueryBuilder() ->select('*') ->from('articles', 'a') ->where("a.title LIKE '%{$realTerm}%'") ->execute() ->fetchAllAssociative(); $this->assertEquals((int) $res[FulltextElastic::INDEX_ARTICLES][0]['id'], (int) $sqlRes[0]['id']); } public function testSynonymsSections() { $this->fulltextElastic->updateIndex(FulltextElastic::INDEX_SECTIONS); $this->fulltextElastic->updateSynonyms($this->testSynonyms); $this->fulltextElastic->commit(); $testTerm = $this->testSynonyms[2]['from']; $realTerm = $this->testSynonyms[2]['to']; $config = [FulltextElastic::INDEX_SECTIONS => ['count' => 5]]; $res = $this->fulltextElastic->search($testTerm, $config, [FulltextElastic::INDEX_SECTIONS])[FulltextElastic::INDEX_SECTIONS]; $this->assertNotEmpty($res); $sqlRes = sqlQueryBuilder() ->select('*') ->from('sections', 's') ->where("s.name LIKE '%{$realTerm}%'") ->execute() ->fetchAllAssociative(); $this->assertEquals((int) $res[0]['id'], (int) $sqlRes[0]['id']); } /** * Product#8 -> title LIKE '%mobilní%' * Product#11 -> long_descr LIKE '%mobilní%'. * * title has higher priority -> Product#8 has to be higher up */ public function testSearchOrderSimple() { $this->fulltextElastic->updateIndex(FulltextElastic::INDEX_PRODUCTS); $this->fulltextElastic->commit(); $config = [ FulltextElastic::INDEX_PRODUCTS => [ 'count' => 5, ], ]; $res = $this->fulltextElastic->search('mobilní', $config, [FulltextElastic::INDEX_PRODUCTS]); $products = $res[FulltextElastic::INDEX_PRODUCTS]; $this->assertGreaterThanOrEqual(2, count($products)); // Product search result format: // product_id => search position $this->assertTrue($products[8] < $products[11]); } public function testSearchOrder() { $this->fulltextElastic->updateIndex(FulltextElastic::INDEX_PRODUCTS); $this->fulltextElastic->commit(); $config = [ FulltextElastic::INDEX_PRODUCTS => [ 'count' => 5, ], ]; $res = $this->fulltextElastic->search('vrtačka', $config, [FulltextElastic::INDEX_PRODUCTS]); $products = array_keys($res[FulltextElastic::INDEX_PRODUCTS]); $this->assertTrue(array_search(10, $products) < array_search(8, $products), 'Product#10 has to be higher up then Product#8'); $this->assertTrue(array_search(9, $products) < array_search(8, $products), 'Product#9 has to be higher up then Product#8'); $this->assertTrue(array_search(9, $products) < array_search(10, $products), 'Product#9 has to be higher up then Product#10'); } public function testThesaurus() { $this->fulltextElastic->updateSynonyms([]); $this->fulltextElastic->updateIndex(FulltextElastic::INDEX_PRODUCTS); $this->fulltextElastic->commit(); $config = [ FulltextElastic::INDEX_PRODUCTS => [ 'count' => 5, ], ]; $actualInput = 'vrtačkou'; $expectedTitleContains = 'vrtačka'; $res = $this->fulltextElastic->search($actualInput, $config, [FulltextElastic::INDEX_PRODUCTS]); $products = array_keys($res[FulltextElastic::INDEX_PRODUCTS]); $this->assertNotEmpty($products); $sqlRes = sqlQueryBuilder()->select('*') ->from('products', 'p') ->where("p.id = {$products[0]}") ->execute()->fetchAllAssociative(); $this->assertNotEmpty($sqlRes); $this->assertStringContainsString($expectedTitleContains, $sqlRes[0]['title']); } public function testRepairCurrentAsIndexNotAsAlias() { $sUrl = $this->fulltextElastic->getServerUrl(); $index = $this->fulltextElastic->getIndex(FulltextElastic::INDEX_PRODUCTS); // Delete "current" alias and index $this->fulltextElastic->curlInitSession("{$sUrl}/_aliases", 'POST', json_encode([ 'actions' => [ [ 'remove' => [ 'index' => '*', 'alias' => $index, ], ], ], ])); $this->fulltextElastic->curlInitSession("{$sUrl}/{$index}", 'DELETE', ''); // Create index using incorrect name - current in index name try { $this->fulltextElastic->updateProduct(1); } catch (FulltextException) { } $indexSettings = $this->fulltextElastic->curlInitSession("{$sUrl}/{$index}", 'GET', ''); // Autocreate is disabled, check index does not exists $this->assertEquals(key($indexSettings), 'error'); // Create index manually $alias = $this->fulltextElastic->getIndex(FulltextElastic::INDEX_PRODUCTS, false); $this->fulltextElastic->curlInitSession("{$sUrl}/{$alias}", 'PUT', ''); // Run reindex - should fix alias $this->fulltextElastic->updateIndex(FulltextElastic::INDEX_PRODUCTS); $this->fulltextElastic->commit(); // Check alias if fixed $indexSettings = $this->fulltextElastic->curlInitSession("{$sUrl}/{$index}", 'GET', ''); $this->assertTrue(strpos(key($indexSettings), 'current') === false); } protected function assertArrayHasKeys(array $keys, $data) { foreach ($keys as $key) { $this->assertArrayHasKey($key, $data); } } } class FulltextElasticForTest extends FulltextElastic { private $data; protected bool $dataOnly = false; public function __construct() { $languageContext = ServiceContainer::getService(LanguageContext::class); $sentryLogger = ServiceContainer::getService(SentryLogger::class); parent::__construct($languageContext, $sentryLogger); $this->setArticleList( ServiceContainer::getService(ArticleList::class) ); } public function executeCurl($category, $param, $customRequest, $urlEnd) { $this->data = json_decode($param, true); if ($this->dataOnly) { return []; } return parent::executeCurl($category, $param, $customRequest, $urlEnd); } public function bulkUpdate($values, $category, $type) { $finalParam = []; switch ($type) { case 'put': foreach ($values as $line) { $finalParam[] = [ 'index' => [ '_index' => $this->settings['index'], '_type' => $category, '_id' => $line['id'], ], ]; $finalParam[] = $line; } break; case 'update': foreach ($values as $line) { $finalParam[] = [ 'update' => [ '_id' => $line['id'], '_type' => $category, '_index' => $this->settings['index'], ], ]; $finalParam[] = [ 'doc' => [ 'weight' => (floatval($line['delivery']) / 10) * (30 + floatval($line['sold']) / 30), 'sold' => intval($line['sold']), 'delivery' => intval($line['delivery']), ], ]; } break; case 'update_partial': foreach ($values as $line) { $finalParam[] = [ 'update' => [ '_id' => $line['id'], '_type' => '_doc', '_index' => $this->settings['index'], ], ]; $finalParam[] = [ 'doc' => $line, 'upsert' => new \stdClass(), ]; } break; } $this->data = $finalParam; if ($this->dataOnly) { return []; } return parent::bulkUpdate($values, $category, $type); } public function getData() { $data = $this->data; $this->data = []; return $data; } public function commit() { $sUrl = $this->getServerUrl(); $this->curlInitSession("{$sUrl}/_refresh", 'POST', ''); } public function setDataOnly(bool $dataOnly): FulltextElasticForTest { $this->dataOnly = $dataOnly; return $this; } public function multiSearch(array $queries) { $this->data = array_map(function ($x) {return json_decode($x, true); }, $queries); if ($this->dataOnly) { return []; } return parent::multiSearch($queries); } }