diff --git a/.env.example b/.env.example new file mode 100644 index 0000000000000000000000000000000000000000..901dac01533b86258d7ccd6495b87e096624b5bb --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +IQ_ES_HOSTS=127.0.0.1:9200 +IQ_ES_USER= +IQ_ES_PASSWORD= +IQ_ES_PRODUCT_SEARCH_INDEX= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4418083dcce47bc337ffbb4ee03cc3e25bf4a205..5249d08131dba73ed78c1342e0116d20fe4795a4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .DS_Store /composer.lock /vendor/ -/.idea/ \ No newline at end of file +/.idea/ +.env +.phpunit.result.cache \ No newline at end of file diff --git a/composer.json b/composer.json index e95c6a64f86618f32635703877928178d241d8f1..236261288cd93366c882e917a708eebfab3e03be 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.4", "ramsey/collection": "^1.2", - "iqdev/search-dc": "1.1.*", + "iqdev/search-dc": "1.2.*", "elasticsearch/elasticsearch": "^8.5", "vlucas/phpdotenv": "^5.4.1" }, @@ -27,12 +27,24 @@ "IQDEV\\ElasticSearch\\": "src/ElasticSearch/" } }, + "autoload-dev": { + "psr-4": { + "IQDEV\\ElasticSearchTests\\": "tests/" + } + }, "repositories": [ { "type": "vcs", "url": "ssh://git@gitlab.iqdev.digital:8422/piligrimov/search-dc.git" } ], + "require-dev": { + "phpunit/phpunit": "^9.5", + "symfony/var-dumper": "^5.4" + }, + "scripts": { + "tests": "php ./vendor/bin/phpunit --testdox --verbose" + }, "config": { "allow-plugins": { "php-http/discovery": true diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000000000000000000000000000000000000..4f8a4c9919faf0701ec67c64b254544d91b93fb2 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,21 @@ + + + + + + tests + + + + + + src/ElasticSearch/ + + + \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..9d32b3831d23fcd9fb6fe0e4532c2c52dac98f00 --- /dev/null +++ b/readme.md @@ -0,0 +1,24 @@ +# Проведение тестов +Для работы корректности фильтрации и индексации данных в эластике необходимо подключение к elasticsearch + +## Индексация elasticsearch +Эластик поднимается и настраивается отдельно. + +Настроить подключение к эластику в переменных окружения .env + +```dotenv +IQ_ES_HOSTS=http://127.0.0.1:9200 +IQ_ES_USER=elastic +IQ_ES_PASSWORD=passsword +IQ_ES_PRODUCT_SEARCH_INDEX=product-test +``` + +Для наполнения эластика данными выполнить команду: + +`php tests/CLI/DefaultSeed.php` + +## Запуск тестов + +Для проведения тестов можно использовать команду + +`php composer tests` \ No newline at end of file diff --git a/src/ElasticSearch/Converter/CriteriaToEsRequest.php b/src/ElasticSearch/Converter/CriteriaToEsRequest.php index b5b50c6bda91dced8755b13de2db8e5ecc5c3dad..0150585d3a4716c04af9e0e5628b4376d778f8a3 100644 --- a/src/ElasticSearch/Converter/CriteriaToEsRequest.php +++ b/src/ElasticSearch/Converter/CriteriaToEsRequest.php @@ -2,6 +2,8 @@ namespace IQDEV\ElasticSearch\Converter; +use IQDEV\ElasticSearch\Filter\PostFilterCollection; +use IQDEV\ElasticSearch\Filter\QueryFilterCollection; use IQDEV\ElasticSearch\Order\OrderAscType; use IQDEV\ElasticSearch\Order\OrderDescType; use IQDEV\ElasticSearch\Order\OrderField; @@ -23,7 +25,10 @@ use IQDEV\Search\Document\Property\PropertyType; use IQDEV\Search\FIlter\Filter; use IQDEV\Search\Filter\FilterCollection; use IQDEV\Search\Filter\FilterGroupCollection; +use IQDEV\Search\Filter\FilterKeyword; +use IQDEV\Search\Filter\FilterNumber; use IQDEV\Search\Filter\FilterOperator; +use IQDEV\Search\Filter\FilterType; use IQDEV\Search\Filter\LogicOperator; use IQDEV\Search\Order\Order; use IQDEV\Search\Order\OrderAscType as SOrderAscType; @@ -126,34 +131,59 @@ final class CriteriaToEsRequest } } - $keywordFilter = $this->getKeywordFilter($criteria); - if (false === $keywordFilter->isEmpty()) { + [$queryFilters, $postFilters] = $this->groupFilters($criteria->filters()); + + $keywordQueryFilter = $this->getKeywordFilter($queryFilters); + $keywordPostFilter = $this->getKeywordFilter($postFilters); + if (false === $keywordQueryFilter->isEmpty() || false === $keywordPostFilter->isEmpty()) { $keywordNestedFilter = new Nested(); - $keywordNestedFilter->setPath('search_data') - ->setQuery($keywordFilter); + $keywordNestedFilter->setPath('search_data'); + + if (false === $keywordQueryFilter->isEmpty()) { + $keywordNestedFilterQuery = clone $keywordNestedFilter; + $keywordNestedFilterQuery->setQuery($keywordQueryFilter); + $request->getQuery()->filter($keywordNestedFilterQuery); + } - $request->getPostFilter()->filter($keywordNestedFilter); + if (false === $keywordPostFilter->isEmpty()) { + $keywordNestedFilterPost = clone $keywordNestedFilter; + $keywordNestedFilterPost->setQuery($keywordPostFilter); + $request->getPostFilter()->filter($keywordNestedFilterPost); + } } - $numberFilter = $this->getNumberFilter($criteria); - if (false === $numberFilter->isEmpty()) { + $numberQueryFilter = $this->getNumberFilter($queryFilters); + $numberPostFilter = $this->getNumberFilter($postFilters); + if (false === $numberQueryFilter->isEmpty() || false === $numberPostFilter->isEmpty()) { $numberNestedFilter = new Nested(); - $numberNestedFilter->setPath('search_data') - ->setQuery($numberFilter); + $numberNestedFilter->setPath('search_data'); - $request->getPostFilter()->filter($numberNestedFilter); + if (false === $numberQueryFilter->isEmpty()) { + $numberNestedFilterQuery = clone $numberNestedFilter; + $numberNestedFilterQuery->setQuery($numberQueryFilter); + $request->getQuery()->filter($numberNestedFilterQuery); + } + + if (false === $numberPostFilter->isEmpty()) { + $numberNestedFilterPost = clone $numberNestedFilter; + $numberNestedFilterPost->setQuery($numberPostFilter); + $request->getPostFilter()->filter($numberNestedFilterPost); + } } return $request; } - private function getNumberFilter(Criteria $criteria, array $excludeFilter = []): Query + private function getNumberFilter(FilterCollection $filterCollection, array $excludeFilter = []): Query { $numberFilter = new Query(); + if ($filterCollection->isEmpty()) { + return $numberFilter; + } $ranges = []; - foreach ($criteria->filters() as $filterGroup) { + foreach ($filterCollection as $filterGroup) { /** @var FilterGroupCollection $filterGroup */ if ($filterGroup->isEmpty()) { continue; @@ -201,10 +231,15 @@ final class CriteriaToEsRequest return $numberFilter; } - private function getKeywordFilter(Criteria $criteria, array $excludeFilter = []): Query + private function getKeywordFilter(FilterCollection $filterCollection, array $excludeFilter = []): Query { $keywordFilter = new Query(); - foreach ($criteria->filters() as $filterGroup) { + + if ($filterCollection->isEmpty()) { + return $keywordFilter; + } + + foreach ($filterCollection as $filterGroup) { /** @var FilterGroupCollection $filterGroup */ if ($filterGroup->isEmpty()) { continue; @@ -274,107 +309,70 @@ final class CriteriaToEsRequest ) ); + [$queryFilters, $postFilters] = $this->groupFilters($criteria->filters()); + $getKey = static fn(string $type, string $name): string => sprintf('%s_facet_%s', $type, $name); - foreach ($criteria->filters() as $filterGroup) { + foreach ($postFilters as $filterGroup) { /** @var FilterGroupCollection $filterGroup */ foreach ($filterGroup as $filter) { /** @var Filter $filter */ $field = $filter->field()->value(); - if (in_array( - $filter->operator()->value(), - [ - FilterOperator::LT, - FilterOperator::LTE, - FilterOperator::GT, - FilterOperator::GTE, - ], - true - ) - ) { - $aggsFiltered = new Aggs($getKey('number', $field)); + if ($filter->value() instanceof FilterNumber) { + continue; + } + + if (in_array($filter->operator()->value(), [], true)) { + continue; + } + + if ('search' === $field) { + continue; + } + + if ('category_id' === $field) { + continue; + } + + if ($filter->value() instanceof FilterKeyword) { + $aggsFiltered = new Aggs($getKey('keyword', $field)); $aggsFiltered->addAggs( - AggsFacetStats::create( + AggsFacetTerms::create( 'agg_special', - 'number_facet' + 'keyword_facet' ) ); + $queryKeywordFiltered = new Query(); - $queryNumberFiltered = new Query(); - $keywordFilter = $this->getKeywordFilter($criteria); - $numberFilter = $this->getNumberFilter($criteria, [$field]); - + $keywordFilter = $this->getKeywordFilter($postFilters, [$field]); + $numberFilter = $this->getNumberFilter($postFilters); if (false === $keywordFilter->isEmpty()) { $nestedFilterKeyword = new Nested(); $nestedFilterKeyword->setPath('search_data') ->setQuery($keywordFilter); - $queryNumberFiltered->filter($nestedFilterKeyword); + $queryKeywordFiltered->filter($nestedFilterKeyword); } if (false === $numberFilter->isEmpty()) { $nestedFilterNumber = new Nested(); $nestedFilterNumber->setPath('search_data') ->setQuery($numberFilter); - $queryNumberFiltered->filter($nestedFilterNumber); + $queryKeywordFiltered->filter($nestedFilterNumber); } - if ($queryNumberFiltered->isEmpty() === false) { - $aggsFiltered->setQuery($queryNumberFiltered); + if ($queryKeywordFiltered->isEmpty() === false) { + $aggsFiltered->setQuery($queryKeywordFiltered); } else { $aggsFiltered->setNested((new Nested())->setPath('search_data')); } $request->getAggs()->add($aggsFiltered); - continue; } - - if (in_array($filter->operator()->value(), [], true)) { - continue; - } - - if ('search' === $field) { - continue; - } - - if ('category_id' === $field) { - continue; - } - - $aggsFiltered = new Aggs($getKey('keyword', $field)); - $aggsFiltered->addAggs( - AggsFacetTerms::create( - 'agg_special', - 'keyword_facet' - ) - ); - $queryKeywordFiltered = new Query(); - $keywordFilter = $this->getKeywordFilter($criteria, [$field]); - $numberFilter = $this->getNumberFilter($criteria); - if (false === $keywordFilter->isEmpty()) { - $nestedFilterKeyword = new Nested(); - $nestedFilterKeyword->setPath('search_data') - ->setQuery($keywordFilter); - $queryKeywordFiltered->filter($nestedFilterKeyword); - } - - if (false === $numberFilter->isEmpty()) { - $nestedFilterNumber = new Nested(); - $nestedFilterNumber->setPath('search_data') - ->setQuery($numberFilter); - $queryKeywordFiltered->filter($nestedFilterNumber); - } - - if ($queryKeywordFiltered->isEmpty() === false) { - $aggsFiltered->setQuery($queryKeywordFiltered); - } else { - $aggsFiltered->setNested((new Nested())->setPath('search_data')); - } - - $request->getAggs()->add($aggsFiltered); } + } - $keywordFilter = $this->getKeywordFilter($criteria); - $numberFilter = $this->getNumberFilter($criteria); + $keywordFilter = $this->getKeywordFilter($postFilters); + $numberFilter = $this->getNumberFilter($postFilters); $aggsKeywordFiltered = new Aggs('keyword_facet_filtered'); $aggsKeywordFiltered->addAggs( @@ -427,8 +425,34 @@ final class CriteriaToEsRequest $request->getAggs() ->add($aggsKeywordFiltered) ->add($aggsNumberFiltered); - } return $request; } + + /** + * @param FilterCollection $filters + * @return FilterCollection[] + */ + private function groupFilters(FilterCollection $filters): array + { + $queryFilters = new QueryFilterCollection(); + $postFilters = new PostFilterCollection(); + foreach ($filters as $filterGroup) { + /** @var FilterGroupCollection $filterGroup */ + if ($filterGroup->isEmpty()) { + continue; + } + + switch ($filterGroup->getFilterType()->value()) { + case FilterType::QUERY: + $queryFilters->add($filterGroup); + break; + case FilterType::POST: + $postFilters->add($filterGroup); + break; + } + } + + return [$queryFilters, $postFilters]; + } } diff --git a/src/ElasticSearch/Converter/EsResponseToResult.php b/src/ElasticSearch/Converter/EsResponseToResult.php index 2fc3db6767d86ab94966a0cad56a2aea5cbaac56..6d734f109b386324d3a4484d11e2e77ca397e38e 100644 --- a/src/ElasticSearch/Converter/EsResponseToResult.php +++ b/src/ElasticSearch/Converter/EsResponseToResult.php @@ -35,122 +35,120 @@ final class EsResponseToResult } if (isset($data['aggregations']['keyword_facet']['agg_keyword_facet_code']['buckets'])) { - $buckets = $data['aggregations']['keyword_facet']['agg_keyword_facet_code']['buckets']; - $bucketsFiltered = []; - if (isset($data['aggregations']['keyword_facet_filtered']['all_keyword_facet_filtered']['agg_keyword_facet_code']['buckets'])) { - foreach ($data['aggregations']['keyword_facet_filtered']['all_keyword_facet_filtered']['agg_keyword_facet_code']['buckets'] as $bucket) { - $bucketsFiltered[$bucket['key']] = []; - foreach ($bucket['agg_keyword_facet_value']['buckets'] as $values) { - $bucketsFiltered[$bucket['key']][$values['key']] = $values; - } - } - } + $this->parseKeywordFacet($data, $catalogSearchResult); + } - foreach ($buckets as $bucket) { - $code = $bucket['key']; - if (isset($data['aggregations']["keyword_facet_$code"]['agg_special']['agg_keyword_facet_code']['buckets'])) { - $bucketsFiltered[$code] = []; - foreach ($data['aggregations']["keyword_facet_$code"]['agg_special']['agg_keyword_facet_code']['buckets'] as $filteredBucket) { - if ($filteredBucket['key'] === $code) { - foreach ($filteredBucket['agg_keyword_facet_value']['buckets'] as $values) { - $bucketsFiltered[$code][$values['key']] = $values; - } - } - } - } - } - $bucketsFiltered = array_filter($bucketsFiltered); + if (isset($data['aggregations']['number_facet']['agg_number_facet_code']['buckets'])) { + $this->parseNumberFacet($data, $catalogSearchResult); + } - foreach ($buckets as $bucket) { - $code = $bucket['key']; - $valueBucket = $bucket['agg_keyword_facet_value']['buckets']; + return $catalogSearchResult; + } - $facet = new Facet(new FacetListType(), $code); + private function productFromArray(array $data): Product + { + if (!isset($data['data']['id'])) { + throw new \RuntimeException('id is not set'); + } + $id = $data['data']['id']; - foreach ($valueBucket as $value) { - $count = 0; + $title = $data['data']['title'] ?? ''; + $info = $data; - if (isset($bucketsFiltered[$code][$value['key']])) { - $count = $bucketsFiltered[$code][$value['key']]['doc_count']; - } + return new Product($id, $title, $info); + } - $facet->products->add( - FacetItemList::create( - $value['key'], - $count, - isset($bucketsFiltered[$code][$value['key']]) - ) - ); + private function parseKeywordFacet(array $data, Result $catalogSearchResult) + { + $buckets = $data['aggregations']['keyword_facet']['agg_keyword_facet_code']['buckets']; + $bucketsFiltered = []; + if (isset($data['aggregations']['keyword_facet_filtered']['all_keyword_facet_filtered']['agg_keyword_facet_code']['buckets'])) { + foreach ($data['aggregations']['keyword_facet_filtered']['all_keyword_facet_filtered']['agg_keyword_facet_code']['buckets'] as $bucket) { + $bucketsFiltered[$bucket['key']] = []; + foreach ($bucket['agg_keyword_facet_value']['buckets'] as $values) { + $bucketsFiltered[$bucket['key']][$values['key']] = $values; } - - $catalogSearchResult->getFacets()->add($facet); } } - if (isset($data['aggregations']['number_facet']['agg_number_facet_code']['buckets'])) { - $buckets = $data['aggregations']['number_facet']['agg_number_facet_code']['buckets']; - $bucketsFiltered = []; - - if (isset($data['aggregations']['number_facet_filtered']['all_number_facet_filtered']['agg_number_facet_code']['buckets'])) { - foreach ($data['aggregations']['number_facet_filtered']['all_number_facet_filtered']['agg_number_facet_code']['buckets'] as $bucket) { - $bucketsFiltered[$bucket['key']] = $bucket['agg_number_facet_value']; - } - } - - foreach ($buckets as $bucket) { - $code = $bucket['key']; - if (isset($data['aggregations']["number_facet_$code"]['agg_special']['agg_number_facet_code']['buckets'])) { - $bucketsFiltered[$code] = []; - foreach ($data['aggregations']["number_facet_$code"]['agg_special']['agg_number_facet_code']['buckets'] as $filteredBucked) { - if ($filteredBucked['key'] === $code) { - $bucketsFiltered[$code] = $filteredBucked['agg_number_facet_value']; + foreach ($buckets as $bucket) { + $code = $bucket['key']; + if (isset($data['aggregations']["keyword_facet_$code"]['agg_special']['agg_keyword_facet_code']['buckets'])) { + $bucketsFiltered[$code] = []; + foreach ($data['aggregations']["keyword_facet_$code"]['agg_special']['agg_keyword_facet_code']['buckets'] as $filteredBucket) { + if ($filteredBucket['key'] === $code) { + foreach ($filteredBucket['agg_keyword_facet_value']['buckets'] as $values) { + $bucketsFiltered[$code][$values['key']] = $values; } } } } - $bucketsFiltered = array_filter($bucketsFiltered); - - foreach ($buckets as $bucket) { - $code = $bucket['key']; - $workBucket = $bucket['agg_number_facet_value']; - $selectedBuket = $bucketsFiltered[$code] ?? null; - - $facet = new Facet(new FacetRangeType(), $code); - $facetItem = FacetItemRange::create( - FacetItemRangeDTO::create( - $workBucket['min'], - $workBucket['max'], - $workBucket['avg'], - $workBucket['sum'] - ), - isset($selectedBuket) ? FacetItemRangeDTO::create( - $selectedBuket['min'], - $selectedBuket['max'], - $selectedBuket['avg'], - $selectedBuket['sum'] - ) : FacetItemRangeDTO::create(), - $selectedBuket['count'] ?? $workBucket['count'], - isset($selectedBuket) - ); - $facet->products->add($facetItem); + } + $bucketsFiltered = array_filter($bucketsFiltered); + + + foreach ($buckets as $bucket) { + $code = $bucket['key']; + $valueBucket = $bucket['agg_keyword_facet_value']['buckets']; - $catalogSearchResult->getFacets()->add($facet); + $facet = new Facet(new FacetListType(), $code); + + foreach ($valueBucket as $value) { + $count = 0; + + if (isset($bucketsFiltered[$code][$value['key']])) { + $count = $bucketsFiltered[$code][$value['key']]['doc_count']; + } + + $facet->products->add( + FacetItemList::create( + $value['key'], + $count, + isset($bucketsFiltered[$code][$value['key']]) + ) + ); } - } - return $catalogSearchResult; + $catalogSearchResult->getFacets()->add($facet); + } } - private function productFromArray(array $data): Product + private function parseNumberFacet(array $data, Result $catalogSearchResult) { - if (!isset($data['data']['id'])) { - throw new \RuntimeException('id is not set'); - } - $id = $data['data']['id']; + $buckets = $data['aggregations']['number_facet']['agg_number_facet_code']['buckets']; + $bucketsFiltered = []; - $title = $data['data']['title'] ?? ''; - $info = $data; + if (isset($data['aggregations']['number_facet_filtered']['all_number_facet_filtered']['agg_number_facet_code']['buckets'])) { + foreach ($data['aggregations']['number_facet_filtered']['all_number_facet_filtered']['agg_number_facet_code']['buckets'] as $bucket) { + $bucketsFiltered[$bucket['key']] = $bucket['agg_number_facet_value']; + } + } - return new Product($id, $title, $info); + foreach ($buckets as $bucket) { + $code = $bucket['key']; + $workBucket = $bucket['agg_number_facet_value']; + $selectedBuket = !empty($bucketsFiltered[$code]) ? $bucketsFiltered[$code] : null; + + $facet = new Facet(new FacetRangeType(), $code); + $facetItem = FacetItemRange::create( + FacetItemRangeDTO::create( + $workBucket['min'], + $workBucket['max'], + $workBucket['avg'], + $workBucket['sum'] + ), + $selectedBuket !== null ? FacetItemRangeDTO::create( + $selectedBuket['min'], + $selectedBuket['max'], + $selectedBuket['avg'], + $selectedBuket['sum'] + ) : FacetItemRangeDTO::create(), + $selectedBuket ? $selectedBuket['count'] : 0, + $selectedBuket !== null + ); + $facet->products->add($facetItem); + + $catalogSearchResult->getFacets()->add($facet); + } } } diff --git a/src/ElasticSearch/Filter/PostFilterCollection.php b/src/ElasticSearch/Filter/PostFilterCollection.php new file mode 100644 index 0000000000000000000000000000000000000000..315d43aff5c2d3a81f784822cbec161dabc527d8 --- /dev/null +++ b/src/ElasticSearch/Filter/PostFilterCollection.php @@ -0,0 +1,9 @@ + &$value) { if (is_array($value)) { $value = call_user_func([__CLASS__, __FUNCTION__], $value, $callback); if (!empty($value)) { $value = self::array_filter_recursive($value, $callback); + } else { + unset($array[$key]); } } } diff --git a/src/ElasticSearch/Indexer/IndexRunner.php b/src/ElasticSearch/Indexer/IndexRunner.php index 8ac968bd3dd07b3afb90fb6af3244f34967d4955..f2e4e737edb26827b3195d2cf85308888c85493f 100644 --- a/src/ElasticSearch/Indexer/IndexRunner.php +++ b/src/ElasticSearch/Indexer/IndexRunner.php @@ -74,11 +74,15 @@ final class IndexRunner continue; } - if (!($index instanceof AddIndex) && !($index instanceof UpdateIndex)) { + if ($index instanceof AddIndex) { + $this->esClient->index($index->es()); continue; } - $this->esClient->index($index->es()); + if ($index instanceof UpdateIndex) { + $this->esClient->update($index->es()); + continue; + } } } } diff --git a/src/ElasticSearch/Search/BoolQuery/Query.php b/src/ElasticSearch/Search/BoolQuery/Query.php index 2e35ab00ea61a3a8f61bd1d53b459208c71eb571..e027710fd38f4e1db58e547336ef24e91ed6d15f 100644 --- a/src/ElasticSearch/Search/BoolQuery/Query.php +++ b/src/ElasticSearch/Search/BoolQuery/Query.php @@ -11,6 +11,7 @@ final class Query implements Esable protected BoolQueryCollection $filter; protected BoolQueryCollection $should; protected BoolQueryCollection $mustNot; + protected BoolQueryCollection $match; protected string $type; diff --git a/tests/AbstractTestCase.php b/tests/AbstractTestCase.php new file mode 100644 index 0000000000000000000000000000000000000000..dd3fe12b1214088c807a208bf4477e2d5f8f68fc --- /dev/null +++ b/tests/AbstractTestCase.php @@ -0,0 +1,28 @@ + $value) { + $this->assertArrayHasKey($key, $actualKeys, $message); + if (isset($actualKeys[$key])) { + $this->assertEquals($actualKeys[$key], $value, $key); + } + } + } +} \ No newline at end of file diff --git a/tests/CLI/DefaultSeed.php b/tests/CLI/DefaultSeed.php new file mode 100644 index 0000000000000000000000000000000000000000..7902cd872bc540ab538b954da3edadcd2729b2f0 --- /dev/null +++ b/tests/CLI/DefaultSeed.php @@ -0,0 +1,6 @@ +start(); \ No newline at end of file diff --git a/tests/Config/ChangingStateConfiguration.php b/tests/Config/ChangingStateConfiguration.php new file mode 100644 index 0000000000000000000000000000000000000000..b1883814bc25037057aa8917f1c72d200f375bad --- /dev/null +++ b/tests/Config/ChangingStateConfiguration.php @@ -0,0 +1,13 @@ +filters()->add( + (new FilterGroupCollection([ + new Filter( + new Field('category_id'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('t-short') + ) + ])) + ->setFilterType(FilterType::query()) + ); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + $expected = [ + 'hits' => [ + 'h1', + 'h2', + 'h3' + ], + 'facets' => [ + 0 => [ + 'code' => 'brand', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'nike', + 'count' => 2, + 'active' => true + ], + 1 => [ + 'label' => null, + 'value' => 'rebook', + 'count' => 1, + 'active' => true + ] + ], + 'range' => [] + ] + ], + 1 => [ + 'code' => 'color', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'red', + 'count' => 1, + 'active' => true + ], + 1 => [ + 'label' => null, + 'value' => 'white', + 'count' => 2, + 'active' => true + ] + ], + 'range' => [] + ] + ], + 2 => [ + 'code' => 'size', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'xl', + 'count' => 2, + 'active' => true + ], + 1 => [ + 'label' => null, + 'value' => 'xxl', + 'count' => 1, + 'active' => true + ] + ], + 'range' => [] + ] + ], + 3 => [ + 'code' => 'price', + 'label' => null, + 'type' => 'range', + 'items' => [ + 'list' => [], + 'range' => [ + 0 => [ + 'label' => null, + 'count' => 3, + 'active' => true, + 'fullRange' => [ + 'min' => 104.0, + 'max' => 106.0 + ], + 'activeRange' => [ + 'min' => 104.0, + 'max' => 106.0 + ] + ] + ] + ] + ] + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatDataWFacets($result)); + } + + public function testEmptyKeywordFilter() + { + $criteria = new Criteria(); + + $criteria->filters()->add( + (new FilterGroupCollection([ + new Filter( + new Field('category_id'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('t-short') + ) + ])) + ->setFilterType(FilterType::query()) + ); + + $criteria->filters()->add(new FilterGroupCollection([ + new Filter( + new Field('color'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('white') + ) + ])); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + $expected = [ + 'hits' => [ + 'h2', + 'h3' + ], + 'facets' => [ + 0 => [ + 'code' => 'brand', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'nike', + 'count' => 1, + 'active' => true + ], + 1 => [ + 'label' => null, + 'value' => 'rebook', + 'count' => 1, + 'active' => true + ] + ], + 'range' => [] + ] + ], + 1 => [ + 'code' => 'color', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'red', + 'count' => 1, + 'active' => true + ], + 1 => [ + 'label' => null, + 'value' => 'white', + 'count' => 2, + 'active' => true + ] + ], + 'range' => [] + ] + ], + 2 => [ + 'code' => 'size', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'xl', + 'count' => 1, + 'active' => true + ], + 1 => [ + 'label' => null, + 'value' => 'xxl', + 'count' => 1, + 'active' => true + ] + ], + 'range' => [] + ] + ], + 3 => [ + 'code' => 'price', + 'label' => null, + 'type' => 'range', + 'items' => [ + 'list' => [], + 'range' => [ + 0 => [ + 'label' => null, + 'count' => 2, + 'active' => true, + 'fullRange' => [ + 'min' => 104.0, + 'max' => 106.0 + ], + 'activeRange' => [ + 'min' => 105.0, + 'max' => 106.0 + ] + ] + ] + ] + ] + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatDataWFacets($result)); + } + + public function testRangeFilter() + { + $criteria = new Criteria(); + + $criteria->filters()->add( + (new FilterGroupCollection([ + new Filter( + new Field('category_id'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('t-short') + ) + ])) + ->setFilterType(FilterType::query()) + ); + + $criteria->filters()->add(new FilterGroupCollection([ + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::LTE), + new FilterNumber(104.50) + ) + ])); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + $expected = [ + 'hits' => [ + 'h1', + ], + 'facets' => [ + 0 => [ + 'code' => 'brand', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'nike', + 'count' => 1, + 'active' => true + ], + 1 => [ + 'label' => null, + 'value' => 'rebook', + 'count' => 0, + 'active' => false + ] + ], + 'range' => [] + ] + ], + 1 => [ + 'code' => 'color', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'red', + 'count' => 1, + 'active' => true + ], + 1 => [ + 'label' => null, + 'value' => 'white', + 'count' => 0, + 'active' => false + ] + ], + 'range' => [] + ] + ], + 2 => [ + 'code' => 'size', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'xl', + 'count' => 1, + 'active' => true + ], + 1 => [ + 'label' => null, + 'value' => 'xxl', + 'count' => 0, + 'active' => false + ] + ], + 'range' => [] + ] + ], + 3 => [ + 'code' => 'price', + 'label' => null, + 'type' => 'range', + 'items' => [ + 'list' => [], + 'range' => [ + 0 => [ + 'label' => null, + 'count' => 1, + 'active' => true, + 'fullRange' => [ + 'min' => 104.0, + 'max' => 106.0 + ], + 'activeRange' => [ + 'min' => 104.0, + 'max' => 104.0 + ] + ] + ] + ] + ] + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatDataWFacets($result)); + } + + public function testCombineFilter() + { + $criteria = new Criteria(); + + $criteria->filters()->add( + (new FilterGroupCollection([ + new Filter( + new Field('category_id'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('t-short') + ) + ])) + ->setFilterType(FilterType::query()) + ); + + $criteria->filters()->add(new FilterGroupCollection([ + new Filter( + new Field('color'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('black') + ) + ])); + + $criteria->filters()->add(new FilterGroupCollection([ + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::LTE), + new FilterNumber(104) + ) + ])); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [], + 'facets' => [ + 0 => [ + 'code' => 'brand', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'nike', + 'count' => 0, + 'active' => false + ], + 1 => [ + 'label' => null, + 'value' => 'rebook', + 'count' => 0, + 'active' => false + ] + ], + 'range' => [] + ] + ], + 1 => [ + 'code' => 'color', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'red', + 'count' => 1, + 'active' => true + ], + 1 => [ + 'label' => null, + 'value' => 'white', + 'count' => 0, + 'active' => false + ] + ], + 'range' => [] + ] + ], + 2 => [ + 'code' => 'size', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'xl', + 'count' => 0, + 'active' => false + ], + 1 => [ + 'label' => null, + 'value' => 'xxl', + 'count' => 0, + 'active' => false + ] + ], + 'range' => [] + ] + ], + 3 => [ + 'code' => 'price', + 'label' => null, + 'type' => 'range', + 'items' => [ + 'list' => [], + 'range' => [ + 0 => [ + 'label' => null, + 'count' => 0, + 'active' => false, + 'fullRange' => [ + 'min' => 104.0, + 'max' => 106.0 + ], + 'activeRange' => [ + 'min' => null, + 'max' => null + ] + ] + ] + ] + ] + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatDataWFacets($result)); + } + + public function testCombineFilterTwo() + { + $criteria = new Criteria(); + + $criteria->filters()->add( + (new FilterGroupCollection([ + new Filter( + new Field('category_id'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('t-short') + ) + ])) + ->setFilterType(FilterType::query()) + ); + + $criteria->filters()->add(new FilterGroupCollection([ + new Filter( + new Field('color'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('red') + ) + ])); + + $criteria->filters()->add(new FilterGroupCollection([ + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::LTE), + new FilterNumber(105) + ) + ])); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [ + 'h1' + ], + 'facets' => [ + 0 => [ + 'code' => 'brand', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'nike', + 'count' => 1, + 'active' => true + ], + 1 => [ + 'label' => null, + 'value' => 'rebook', + 'count' => 0, + 'active' => false + ] + ], + 'range' => [] + ] + ], + 1 => [ + 'code' => 'color', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'red', + 'count' => 1, + 'active' => true + ], + 1 => [ + 'label' => null, + 'value' => 'white', + 'count' => 1, + 'active' => true + ] + ], + 'range' => [] + ] + ], + 2 => [ + 'code' => 'size', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'xl', + 'count' => 1, + 'active' => true + ], + 1 => [ + 'label' => null, + 'value' => 'xxl', + 'count' => 0, + 'active' => false + ] + ], + 'range' => [] + ] + ], + 3 => [ + 'code' => 'price', + 'label' => null, + 'type' => 'range', + 'items' => [ + 'list' => [], + 'range' => [ + 0 => [ + 'label' => null, + 'count' => 1, + 'active' => true, + 'fullRange' => [ + 'min' => 104.0, + 'max' => 106.0 + ], + 'activeRange' => [ + 'min' => 104.0, + 'max' => 104.0 + ] + ] + ] + ] + ] + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatDataWFacets($result)); + } + + public function testKeywordFilter() + { + $criteria = new Criteria(); + + $criteria->filters()->add( + (new FilterGroupCollection([ + new Filter( + new Field('category_id'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('t-short') + ) + ])) + ->setFilterType(FilterType::query()) + ); + + $criteria->filters()->add(new FilterGroupCollection([ + new Filter( + new Field('color'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('white') + ) + ])); + + $criteria->filters()->add(new FilterGroupCollection([ + new Filter( + new Field('brand'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('nike') + ) + ])); + + $criteria->filters()->add(new FilterGroupCollection([ + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::GT), + new FilterNumber(100) + ) + ])); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [ + 'h2' + ], + 'facets' => [ + 0 => [ + 'code' => 'brand', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'nike', + 'count' => 1, + 'active' => true + ], + 1 => [ + 'label' => null, + 'value' => 'rebook', + 'count' => 1, + 'active' => true + ] + ], + 'range' => [] + ] + ], + 1 => [ + 'code' => 'color', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'red', + 'count' => 1, + 'active' => true + ], + 1 => [ + 'label' => null, + 'value' => 'white', + 'count' => 1, + 'active' => true + ] + ], + 'range' => [] + ] + ], + 2 => [ + 'code' => 'size', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'xl', + 'count' => 0, + 'active' => false + ], + 1 => [ + 'label' => null, + 'value' => 'xxl', + 'count' => 1, + 'active' => true + ] + ], + 'range' => [] + ] + ], + 3 => [ + 'code' => 'price', + 'label' => null, + 'type' => 'range', + 'items' => [ + 'list' => [], + 'range' => [ + 0 => [ + 'label' => null, + 'count' => 1, + 'active' => true, + 'fullRange' => [ + 'min' => 104.0, + 'max' => 106.0 + ], + 'activeRange' => [ + 'min' => 105.0, + 'max' => 105.0 + ] + ] + ] + ] + ] + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatDataWFacets($result)); + } + + public function testKeywordFilterTwo() + { + $criteria = new Criteria(); + + $criteria->filters()->add( + (new FilterGroupCollection([ + new Filter( + new Field('category_id'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('t-short') + ) + ])) + ->setFilterType(FilterType::query()) + ); + + $criteria->filters()->add(new FilterGroupCollection([ + new Filter( + new Field('color'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('white') + ) + ])); + + $criteria->filters()->add( + (new FilterGroupCollection([ + new Filter( + new Field('brand'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('nike') + ), + new Filter( + new Field('brand'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('reebok') + ) + ])) + ->setLogicalType(LogicOperator::or()) + ); + + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [ + 'h2' + ], + 'facets' => [ + 0 => [ + 'code' => 'brand', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'nike', + 'count' => 1, + 'active' => true + ], + 1 => [ + 'label' => null, + 'value' => 'rebook', + 'count' => 1, + 'active' => true + ] + ], + 'range' => [] + ] + ], + 1 => [ + 'code' => 'color', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'red', + 'count' => 1, + 'active' => true + ], + 1 => [ + 'label' => null, + 'value' => 'white', + 'count' => 1, + 'active' => true + ] + ], + 'range' => [] + ] + ], + 2 => [ + 'code' => 'size', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'xl', + 'count' => 0, + 'active' => false + ], + 1 => [ + 'label' => null, + 'value' => 'xxl', + 'count' => 1, + 'active' => true + ] + ], + 'range' => [] + ] + ], + 3 => [ + 'code' => 'price', + 'label' => null, + 'type' => 'range', + 'items' => [ + 'list' => [], + 'range' => [ + 0 => [ + 'label' => null, + 'count' => 1, + 'active' => true, + 'fullRange' => [ + 'min' => 104.0, + 'max' => 106.0 + ], + 'activeRange' => [ + 'min' => 105.0, + 'max' => 105.0 + ] + ] + ] + ] + ] + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatDataWFacets($result)); + } +} \ No newline at end of file diff --git a/tests/FIlter/CommonRangeKeywordsTest.php b/tests/FIlter/CommonRangeKeywordsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..2ced2c2e97123dcfb1d7b5ce105c4ea468947b22 --- /dev/null +++ b/tests/FIlter/CommonRangeKeywordsTest.php @@ -0,0 +1,143 @@ +filters()->add(new FilterGroupCollection([ + new Filter( + new Field('color'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('red') + ) + ])); + + $criteria->filters()->add(new FilterGroupCollection([ + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::GT), + new FilterNumber(102) + ) + ])); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [ + 'h1' + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatData($result)); + } + + /** + * Поиск элементов по фильтру свойства и цене + * + * @return void + */ + public function testExistByFilterAndMaxPrice(): void + { + $criteria = new Criteria(); + + $criteria->filters()->add(new FilterGroupCollection([ + new Filter( + new Field('color'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('red') + ) + ])); + + $criteria->filters()->add(new FilterGroupCollection([ + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::LT), + new FilterNumber(102) + ) + ])); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [ + 's2' + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatData($result)); + } + + /** + * Поиск элементов по фильтру свойства и цене + * + * @return void + */ + public function testExistByFilterAndRangePrice(): void + { + $criteria = new Criteria(); + + $criteria->filters()->add(new FilterGroupCollection([ + new Filter( + new Field('color'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('red') + ) + ])); + + $criteria->filters()->add(new FilterGroupCollection([ + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::GTE), + new FilterNumber(101) + ), + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::LTE), + new FilterNumber(104) + ), + ])); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + $expected = [ + 'hits' => [ + 'h1', + 's2' + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatData($result)); + } +} \ No newline at end of file diff --git a/tests/FIlter/IndexesTest.php b/tests/FIlter/IndexesTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b5aaa98c677a743793d517f07862a9b4c305cf09 --- /dev/null +++ b/tests/FIlter/IndexesTest.php @@ -0,0 +1,83 @@ + 'test', + 'name' => 'Test товар', + 'category' => 'indexes', + 'properties' => [ + 'prop1' => 'value1', + 'prop2' => 'value2', + 'prop3' => 'value3', + ] + ]; + + private IndexRunner $indexRunner; + private Client $esClient; + private Configuration $configuration; + + public function __construct(?string $name = null, array $data = [], $dataName = '') + { + parent::__construct($name, $data, $dataName); + + $this->configuration = new ChangingStateConfiguration(); + $this->esClient = ClientFactory::create(); + + $this->indexRunner = new IndexRunner( + $this->esClient, + $this->configuration, + new TestLogger() + ); + } + + public function testUpdate() + { + $indexProvider = new TestIndexProvider($this->configuration, [$this->product]); + $this->indexRunner->run($indexProvider); + + $updateData = [ + 'id' => $this->product['id'], + 'category' => $this->product['category'], + 'type' => 'update', + 'name' => 'Обновленный элемент' + ]; + $indexProvider = new TestIndexProvider($this->configuration, [$updateData]); + $this->indexRunner->run($indexProvider); + + $response = $this->esClient->search([ + 'index' => $this->configuration->getIndexName(), + 'body' => [ + 'query' => [ + 'match' => [ + '_id' => $this->product['id'] + ], + ] + ] + ]); + $esResponseToResult = new EsResponseToResult(); + $result = $esResponseToResult->fromResponse($response); + + unset($updateData['type']); + $expected = [ + 'products' => [ + array_merge($this->product, $updateData) + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatDataProducts($result)); + } +} \ No newline at end of file diff --git a/tests/FIlter/KeywordsTest.php b/tests/FIlter/KeywordsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..51acdf6439db9efcb44b2185821597160eeade5f --- /dev/null +++ b/tests/FIlter/KeywordsTest.php @@ -0,0 +1,242 @@ +filters()->add(new FilterGroupCollection([ + new Filter( + new Field('color'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('') + ) + ])); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [] + ]; + + $this->assertEquals($expected, FormatData::formatData($result)); + } + + /** + * Поиск элементов по фильтру свойства + * + * @return void + */ + public function testExistByFilter(): void + { + $criteria = new Criteria(); + + $criteria->filters()->add(new FilterGroupCollection([ + new Filter( + new Field('color'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('red') + ) + ])); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [ + 's2', + 'h1' + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatData($result)); + } + + /** + * Поиск элементов по фильтру свойства и категории + * + * @return void + */ + public function testExistByFilterAndCategory(): void + { + $criteria = new Criteria(); + + $criteria->filters()->add( + (new FilterGroupCollection([ + new Filter( + new Field('category_id'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('shoes') + ) + ])) + ->setFilterType(FilterType::query()) + ); + + $criteria->filters()->add(new FilterGroupCollection([ + new Filter( + new Field('color'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('red') + ) + ])); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [ + 's2', + ] + ]; + + $this->assertEquals($expected, FormatData::formatData($result)); + } + + /** + * Поиск элементов по нескольким свойствам + * + * @return void + */ + public function testExistByMultipleFilter(): void + { + $criteria = new Criteria(); + + $criteria->filters()->add(new FilterGroupCollection([ + new Filter( + new Field('color'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('red') + ), + new Filter( + new Field('size'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('xxl') + ) + ])); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [] + ]; + + $this->assertEquals($expected, FormatData::formatData($result)); + } + + /** + * Поиск элементов по нескольким свойствам + * + * @return void + */ + public function testExistByMultipleFilter2(): void + { + $criteria = new Criteria(); + + $criteria->filters()->add(new FilterGroupCollection([ + new Filter( + new Field('color'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('red') + ), + new Filter( + new Field('size'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('xl') + ) + ])); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [ + 'h1' + ] + ]; + + $this->assertEquals($expected, FormatData::formatData($result)); + } + + /** + * Поиск элементов по нескольким свойствам и категории + * + * @return void + */ + public function testExistByMultipleFilterAndCategory(): void + { + $criteria = new Criteria(); + + $criteria->filters()->add( + (new FilterGroupCollection([ + new Filter( + new Field('category_id'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('prices') + ) + ])) + ->setFilterType(FilterType::query()) + ); + + $criteria->filters()->add(new FilterGroupCollection([ + new Filter( + new Field('color'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('red') + ), + new Filter( + new Field('size'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('xl') + ) + ])); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [] + ]; + + $this->assertEquals($expected, FormatData::formatData($result)); + } +} \ No newline at end of file diff --git a/tests/FIlter/QueryAndPostFilterTest.php b/tests/FIlter/QueryAndPostFilterTest.php new file mode 100644 index 0000000000000000000000000000000000000000..dcf6fffb2faa9e66555dc7e7dd411625da909532 --- /dev/null +++ b/tests/FIlter/QueryAndPostFilterTest.php @@ -0,0 +1,1696 @@ +setFilterType(FilterType::query()); + $criteria->filters()->add($filterCollectionBrand); + + + $q = new SearchQuery($criteria); + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + $expected = [ + 'hits' => [], + 'facets' => [] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatDataWFacets($result)); + } + + /** + * query по сущ бренду + * @return void + */ + public function testQueryExistingBrand() + { + $criteria = new Criteria(); + + $filterCollectionBrand = new FilterGroupCollection([ + new Filter( + new Field('brand'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('adidas') + ) + ]); + $filterCollectionBrand->setFilterType(FilterType::query()); + $criteria->filters()->add($filterCollectionBrand); + + + $q = new SearchQuery($criteria); + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + $expected = [ + 'hits' => [ + 's1', + 's2', + ], + 'facets' => [ + 0 => [ + 'code' => 'brand', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'adidas', + 'count' => 2, + 'active' => true, + ] + ], + 'range' => [], + ] + ], + 1 => [ + 'code' => 'color', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'green', + 'count' => 1, + 'active' => true, + ], + 1 => [ + 'label' => null, + 'value' => 'red', + 'count' => 1, + 'active' => true, + ] + ], + 'range' => [], + ] + ], + 2 => [ + 'code' => 'size', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => '46', + 'count' => 1, + 'active' => true, + ], + 1 => [ + 'label' => null, + 'value' => '47', + 'count' => 1, + 'active' => true, + ] + ], + 'range' => [], + ] + ], + 3 => [ + 'code' => 'price', + 'label' => null, + 'type' => 'range', + 'items' => [ + 'list' => [], + 'range' => [ + 0 => [ + 'label' => null, + 'count' => 2, + 'active' => true, + 'fullRange' => [ + 'min' => 100.0, + 'max' => 101.0, + ], + 'activeRange' => [ + 'min' => 100.0, + 'max' => 101.0, + ] + ] + ] + ] + ] + ], + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatDataWFacets($result)); + } + + /** + * query по сущ бренду и post_filter по бренду + * @return void + */ + public function testQueryExistingBrandAndPostFilterExistingBrand() + { + $criteria = new Criteria(); + + $filterCollectionBrand = new FilterGroupCollection([ + new Filter( + new Field('brand'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('adidas') + ), + new Filter( + new Field('brand'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('nike') + ), + ]); + $filterCollectionBrand + ->setLogicalType(LogicOperator::or()) + ->setFilterType(FilterType::query()); + $criteria->filters()->add($filterCollectionBrand); + + $filterCollectionBrand = new FilterGroupCollection([ + new Filter( + new Field('brand'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('nike') + ), + ]); + $criteria->filters()->add($filterCollectionBrand); + + + $q = new SearchQuery($criteria); + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + $expected = [ + 'hits' => [ + 's4', + 'h1', + 'h2', + ], + 'facets' => [ + 0 => [ + 'code' => 'brand', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'adidas', + 'count' => 2, + 'active' => true, + ], + 1 => [ + 'label' => null, + 'value' => 'nike', + 'count' => 3, + 'active' => true, + ] + ], + 'range' => [], + ] + ], + 1 => [ + 'code' => 'color', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'green', + 'count' => 1, + 'active' => true, + ], + 1 => [ + 'label' => null, + 'value' => 'red', + 'count' => 1, + 'active' => true, + ], + 2 => [ + 'label' => null, + 'value' => 'white', + 'count' => 1, + 'active' => true, + ] + ], + 'range' => [], + ] + ], + 2 => [ + 'code' => 'size', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => '43', + 'count' => 1, + 'active' => true, + ], + 1 => [ + 'label' => null, + 'value' => '46', + 'count' => 0, + 'active' => false, + ], + 2 => [ + 'label' => null, + 'value' => '47', + 'count' => 0, + 'active' => false, + ], + 3 => [ + 'label' => null, + 'value' => 'xl', + 'count' => 1, + 'active' => true, + ], + 4 => [ + 'label' => null, + 'value' => 'xxl', + 'count' => 1, + 'active' => true, + ] + ], + 'range' => [], + ], + ], + 3 => [ + 'code' => 'price', + 'label' => null, + 'type' => 'range', + 'items' => [ + 'list' => [], + 'range' => [ + 0 => [ + 'label' => null, + 'count' => 3, + 'active' => true, + 'fullRange' => [ + 'min' => 100.0, + 'max' => 105.0, + ], + 'activeRange' => [ + 'min' => 103.0, + 'max' => 105.0, + ] + ] + ] + ] + ] + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatDataWFacets($result)); + } + + /** + * query по сущ бренду и post_filter по цвету + * @return void + */ + public function testQueryExistingBrandAndPostFilterExistingColor() + { + $criteria = new Criteria(); + + $filterCollectionBrand = new FilterGroupCollection([ + new Filter( + new Field('brand'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('nike') + ), + ]); + $filterCollectionBrand + ->setLogicalType(LogicOperator::or()) + ->setFilterType(FilterType::query()); + $criteria->filters()->add($filterCollectionBrand); + + $filterCollectionColor = new FilterGroupCollection([ + new Filter( + new Field('color'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('white') + ), + ]); + $criteria->filters()->add($filterCollectionColor); + + + $q = new SearchQuery($criteria); + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + $expected = [ + 'hits' => [ + 'h2', + ], + 'facets' => [ + 0 => [ + 'code' => 'brand', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'nike', + 'count' => 1, + 'active' => true, + ] + ], + 'range' => [], + ] + ], + 1 => [ + 'code' => 'color', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'green', + 'count' => 1, + 'active' => true, + ], + 1 => [ + 'label' => null, + 'value' => 'red', + 'count' => 1, + 'active' => true, + ], + 2 => [ + 'label' => null, + 'value' => 'white', + 'count' => 1, + 'active' => true, + ] + ], + 'range' => [], + ] + ], + 2 => [ + 'code' => 'size', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => '43', + 'count' => 0, + 'active' => false, + ], + 1 => [ + 'label' => null, + 'value' => 'xl', + 'count' => 0, + 'active' => false, + ], + 2 => [ + 'label' => null, + 'value' => 'xxl', + 'count' => 1, + 'active' => true, + ] + ], + 'range' => [], + ] + ], + 3 => [ + 'code' => 'price', + 'label' => null, + 'type' => 'range', + 'items' => [ + 'list' => [], + 'range' => [ + 0 => [ + 'label' => null, + 'count' => 1, + 'active' => true, + 'fullRange' => [ + 'min' => 103.0, + 'max' => 105.0, + ], + 'activeRange' => [ + 'min' => 105.0, + 'max' => 105.0, + ] + ] + ] + ] + ] + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatDataWFacets($result)); + } + + /** + * query по сущ бренду и цвету и post_filter по бренду + * @return void + */ + public function testQueryExistingBrandAndColorAndPostFilterExistingColor() + { + $criteria = new Criteria(); + + $filterCollectionBrand = new FilterGroupCollection([ + new Filter( + new Field('brand'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('nike') + ), + new Filter( + new Field('brand'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('rebook') + ), + ]); + $filterCollectionBrand + ->setLogicalType(LogicOperator::or()) + ->setFilterType(FilterType::query()); + $criteria->filters()->add($filterCollectionBrand); + + $filterCollectionColor = new FilterGroupCollection([ + new Filter( + new Field('color'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('white') + ), + ]); + $filterCollectionColor->setFilterType(FilterType::query()); + $criteria->filters()->add($filterCollectionColor); + + $filterCollectionBrand = new FilterGroupCollection([ + new Filter( + new Field('brand'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('nike') + ), + ]); + $criteria->filters()->add($filterCollectionBrand); + + + $q = new SearchQuery($criteria); + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + $expected = [ + 'hits' => [ + 'h2', + ], + 'facets' => [ + 0 => [ + 'code' => 'brand', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'nike', + 'count' => 1, + 'active' => true, + ], + 1 => [ + 'label' => null, + 'value' => 'rebook', + 'count' => 2, + 'active' => true, + ] + ], + 'range' => [] + ] + ], + 1 => [ + 'code' => 'color', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'white', + 'count' => 1, + 'active' => true, + ] + ], + 'range' => [] + ] + ], + 2 => [ + 'code' => 'size', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'xl', + 'count' => 0, + 'active' => false, + ], + 1 => [ + 'label' => null, + 'value' => 'xxl', + 'count' => 1, + 'active' => true, + ] + ], + 'range' => [] + ] + ], + 3 => [ + 'code' => 'price', + 'label' => null, + 'type' => 'range', + 'items' => [ + 'list' => [], + 'range' => [ + 0 => [ + 'label' => null, + 'count' => 1, + 'active' => true, + 'fullRange' => [ + 'min' => 105.0, + 'max' => 107.0, + ], + 'activeRange' => [ + 'min' => 105.0, + 'max' => 105.0, + ] + ] + ] + ] + ] + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatDataWFacets($result)); + } + + /** + * query по сущ бренду и post_filter по бренду и цвету + * @return void + */ + public function testQueryExistingBrandAndPostFilterExistingBrandAndColor() + { + $criteria = new Criteria(); + + $filterCollectionBrand = new FilterGroupCollection([ + new Filter( + new Field('brand'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('nike') + ), + new Filter( + new Field('brand'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('rebook') + ), + ]); + $filterCollectionBrand + ->setLogicalType(LogicOperator::or()) + ->setFilterType(FilterType::query()); + $criteria->filters()->add($filterCollectionBrand); + + + $filterCollectionBrand = new FilterGroupCollection([ + new Filter( + new Field('brand'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('nike') + ), + ]); + $criteria->filters()->add($filterCollectionBrand); + + $filterCollectionColor = new FilterGroupCollection([ + new Filter( + new Field('color'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('red') + ), + ]); + $criteria->filters()->add($filterCollectionColor); + + + $q = new SearchQuery($criteria); + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + $expected = [ + 'hits' => [ + 'h1', + ], + 'facets' => [ + 0 => [ + 'code' => 'brand', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'nike', + 'count' => 1, + 'active' => true, + ], + 1 => [ + 'label' => null, + 'value' => 'rebook', + 'count' => 0, + 'active' => false, + ] + ], + 'range' => [] + ] + ], + 1 => [ + 'code' => 'color', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'blue', + 'count' => 0, + 'active' => false, + ], + 1 => [ + 'label' => null, + 'value' => 'green', + 'count' => 1, + 'active' => true, + ], + 2 => [ + 'label' => null, + 'value' => 'red', + 'count' => 1, + 'active' => true, + ], + 3 => [ + 'label' => null, + 'value' => 'white', + 'count' => 1, + 'active' => true, + ] + ], + 'range' => [] + ] + ], + 2 => [ + 'code' => 'size', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => '43', + 'count' => 0, + 'active' => false, + ], + 1 => [ + 'label' => null, + 'value' => '47', + 'count' => 0, + 'active' => false, + ], + 2 => [ + 'label' => null, + 'value' => 'xl', + 'count' => 1, + 'active' => true, + ], + 3 => [ + 'label' => null, + 'value' => 'xxl', + 'count' => 0, + 'active' => false, + ] + ], + 'range' => [] + ] + ], + 3 => [ + 'code' => 'price', + 'label' => null, + 'type' => 'range', + 'items' => [ + 'list' => [], + 'range' => [ + 0 => [ + 'label' => null, + 'count' => 1, + 'active' => true, + 'fullRange' => [ + 'min' => 102.0, + 'max' => 107.0, + ], + 'activeRange' => [ + 'min' => 104.0, + 'max' => 104.0, + ] + ] + ] + ] + ] + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatDataWFacets($result)); + } + + /** + * query по не сущ цене + * @return void + */ + public function testQueryNonExistentPrice() + { + $criteria = new Criteria(); + + $filterCollectionPrice = new FilterGroupCollection([ + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::GTE), + new FilterNumber(108) + ) + ]); + $filterCollectionPrice->setFilterType(FilterType::query()); + $criteria->filters()->add($filterCollectionPrice); + + + $q = new SearchQuery($criteria); + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + $expected = [ + 'hits' => [], + 'facets' => [], + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatDataWFacets($result)); + } + + /** + * query по сущ цене + * @return void + */ + public function testQueryExistingPrice() + { + $criteria = new Criteria(); + + $filterCollectionPrice = new FilterGroupCollection([ + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::GTE), + new FilterNumber(105) + ) + ]); + $filterCollectionPrice->setFilterType(FilterType::query()); + $criteria->filters()->add($filterCollectionPrice); + + + $q = new SearchQuery($criteria); + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + $expected = [ + 'hits' => [ + 'h2', + 'h3', + 'p1', + ], + 'facets' => [ + 0 => [ + 'code' => 'brand', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'nike', + 'count' => 1, + 'active' => true, + ], + 1 => [ + 'label' => null, + 'value' => 'rebook', + 'count' => 2, + 'active' => true, + ] + ], + 'range' => [], + ] + ], + 1 => [ + 'code' => 'color', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'white', + 'count' => 3, + 'active' => true, + ] + ], + 'range' => [] + ] + ], + 2 => [ + 'code' => 'size', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'xl', + 'count' => 2, + 'active' => true, + ], + 1 => [ + 'label' => null, + 'value' => 'xxl', + 'count' => 1, + 'active' => true, + ] + ], + 'range' => [] + ] + ], + 3 => [ + 'code' => 'price', + 'label' => null, + 'type' => 'range', + 'items' => [ + 'list' => [], + 'range' => [ + 0 => [ + 'label' => null, + 'count' => 3, + 'active' => true, + 'fullRange' => [ + 'min' => 105.0, + 'max' => 107.0, + ], + 'activeRange' => [ + 'min' => 105.0, + 'max' => 107.0, + ] + ] + ] + ] + ] + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatDataWFacets($result)); + } + + /** + * query по сущ цене и бренду + * @return void + */ + public function testQueryExistingPriceAndBrand() + { + $criteria = new Criteria(); + + $filterCollection = new FilterGroupCollection([ + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::GTE), + new FilterNumber(104) + ), + new Filter( + new Field('brand'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('nike') + ), + ]); + $filterCollection->setFilterType(FilterType::query()); + $criteria->filters()->add($filterCollection); + + $q = new SearchQuery($criteria); + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + $expected = [ + 'hits' => [ + 'h1', + 'h2', + ], + 'facets' => [ + 0 => [ + 'code' => 'brand', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'nike', + 'count' => 2, + 'active' => true, + ] + ], + 'range' => [] + ] + ], + 1 => [ + 'code' => 'color', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'red', + 'count' => 1, + 'active' => true, + ], + 1 => [ + 'label' => null, + 'value' => 'white', + 'count' => 1, + 'active' => true, + ] + ], + 'range' => [] + ] + ], + 2 => [ + 'code' => 'size', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'xl', + 'count' => 1, + 'active' => true, + ], + 1 => [ + 'label' => null, + 'value' => 'xxl', + 'count' => 1, + 'active' => true, + ] + ], + 'range' => [] + ] + ], + 3 => [ + 'code' => 'price', + 'label' => null, + 'type' => 'range', + 'items' => [ + 'list' => [], + 'range' => [ + 0 => [ + 'label' => null, + 'count' => 2, + 'active' => true, + 'fullRange' => [ + 'min' => 104.0, + 'max' => 105.0, + ], + 'activeRange' => [ + 'min' => 104.0, + 'max' => 105.0, + ] + ] + ] + ] + ] + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatDataWFacets($result)); + } + + /** + * query по сущ цене и бренду и post_filter по цвету + * @return void + */ + public function testQueryExistingPriceAndBrandAndPostFilterColor() + { + $criteria = new Criteria(); + + $filterCollection = new FilterGroupCollection([ + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::GTE), + new FilterNumber(104) + ), + new Filter( + new Field('brand'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('nike') + ), + ]); + $filterCollection->setFilterType(FilterType::query()); + $criteria->filters()->add($filterCollection); + + $filterCollectionColor = new FilterGroupCollection([ + new Filter( + new Field('color'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('red') + ), + ]); + $criteria->filters()->add($filterCollectionColor); + + $q = new SearchQuery($criteria); + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + $expected = [ + 'hits' => [ + 'h1', + ], + 'facets' => [ + 0 => [ + 'code' => 'brand', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'nike', + 'count' => 1, + 'active' => true, + ] + ], + 'range' => [] + ] + ], + 1 => [ + 'code' => 'color', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'red', + 'count' => 1, + 'active' => true, + ], + 1 => [ + 'label' => null, + 'value' => 'white', + 'count' => 1, + 'active' => true, + ] + ], + 'range' => [] + ] + ], + 2 => [ + 'code' => 'size', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'xl', + 'count' => 1, + 'active' => true, + ], + 1 => [ + 'label' => null, + 'value' => 'xxl', + 'count' => 0, + 'active' => false, + ] + ], + 'range' => [] + ] + ], + 3 => [ + 'code' => 'price', + 'label' => null, + 'type' => 'range', + 'items' => [ + 'list' => [], + 'range' => [ + 0 => [ + 'label' => null, + 'count' => 1, + 'active' => true, + 'fullRange' => [ + 'min' => 104.0, + 'max' => 105.0, + ], + 'activeRange' => [ + 'min' => 104.0, + 'max' => 104.0, + ] + ] + ] + ] + ] + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatDataWFacets($result)); + } + + /** + * query по сущ цене и бренду и post_filter по цвету и бренду + * @return void + */ + public function testQueryExistingPriceAndBrandAndPostFilterColorAndBrand() + { + $criteria = new Criteria(); + + $filterCollectionPrice = new FilterGroupCollection([ + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::LTE), + new FilterNumber(104) + ), + ]); + $filterCollectionPrice->setFilterType(FilterType::query()); + $criteria->filters()->add($filterCollectionPrice); + + $filterCollectionBrand = new FilterGroupCollection([ + new Filter( + new Field('brand'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('nike') + ), + new Filter( + new Field('brand'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('adidas') + ), + ]); + $filterCollectionBrand + ->setLogicalType(LogicOperator::or()) + ->setFilterType(FilterType::query()); + $criteria->filters()->add($filterCollectionBrand); + + + $filterCollectionColor = new FilterGroupCollection([ + new Filter( + new Field('color'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('green') + ), + ]); + $criteria->filters()->add($filterCollectionColor); + + $filterCollectionBrand = new FilterGroupCollection([ + new Filter( + new Field('brand'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('nike') + ), + ]); + $criteria->filters()->add($filterCollectionBrand); + + + $q = new SearchQuery($criteria); + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + $expected = [ + 'hits' => [ + 's4', + ], + 'facets' => [ + 0 => [ + 'code' => 'brand', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'adidas', + 'count' => 1, + 'active' => true, + ], + 1 => [ + 'label' => null, + 'value' => 'nike', + 'count' => 1, + 'active' => true, + ] + ], + 'range' => [] + ] + ], + 1 => [ + 'code' => 'color', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'green', + 'count' => 1, + 'active' => true, + ], + 1 => [ + 'label' => null, + 'value' => 'red', + 'count' => 1, + 'active' => true, + ] + ], + 'range' => [] + ] + ], + 2 => [ + 'code' => 'size', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => '43', + 'count' => 1, + 'active' => true, + ], + 1 => [ + 'label' => null, + 'value' => '46', + 'count' => 0, + 'active' => false, + ], + 2 => [ + 'label' => null, + 'value' => '47', + 'count' => 0, + 'active' => false, + ], + 3 => [ + 'label' => null, + 'value' => 'xl', + 'count' => 0, + 'active' => false, + ] + ], + 'range' => [] + ] + ], + 3 => [ + 'code' => 'price', + 'label' => null, + 'type' => 'range', + 'items' => [ + 'list' => [], + 'range' => [ + 0 => [ + 'label' => null, + 'count' => 1, + 'active' => true, + 'fullRange' => [ + 'min' => 100.0, + 'max' => 104.0, + ], + 'activeRange' => [ + 'min' => 103.0, + 'max' => 103.0, + ] + ] + ] + ] + ] + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatDataWFacets($result)); + } + + /** + * query по сущ цене и бренду и post_filter по цене + * @return void + */ + public function testQueryExistingPriceAndBrandAndPostFilterPrice() + { + $criteria = new Criteria(); + + $filterCollectionPrice = new FilterGroupCollection([ + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::LTE), + new FilterNumber(104) + ), + ]); + $filterCollectionPrice->setFilterType(FilterType::query()); + $criteria->filters()->add($filterCollectionPrice); + + $filterCollectionBrand = new FilterGroupCollection([ + new Filter( + new Field('brand'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('nike') + ), + new Filter( + new Field('brand'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('adidas') + ), + ]); + $filterCollectionBrand + ->setLogicalType(LogicOperator::or()) + ->setFilterType(FilterType::query()); + $criteria->filters()->add($filterCollectionBrand); + + + $filterCollectionPrice = new FilterGroupCollection([ + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::GTE), + new FilterNumber(102) + ), + ]); + $criteria->filters()->add($filterCollectionPrice); + + + $q = new SearchQuery($criteria); + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + $expected = [ + 'hits' => [ + 's4', + 'h1', + ], + 'facets' => [ + 0 => [ + 'code' => 'brand', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'adidas', + 'count' => 0, + 'active' => false, + ], + 1 => [ + 'label' => null, + 'value' => 'nike', + 'count' => 2, + 'active' => true, + ] + ], + 'range' => [] + ] + ], + 1 => [ + 'code' => 'color', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'green', + 'count' => 1, + 'active' => true, + ], + 1 => [ + 'label' => null, + 'value' => 'red', + 'count' => 1, + 'active' => true, + ] + ], + 'range' => [] + ] + ], + 2 => [ + 'code' => 'size', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => '43', + 'count' => 1, + 'active' => true, + ], + 1 => [ + 'label' => null, + 'value' => '46', + 'count' => 0, + 'active' => false, + ], + 2 => [ + 'label' => null, + 'value' => '47', + 'count' => 0, + 'active' => false, + ], + 3 => [ + 'label' => null, + 'value' => 'xl', + 'count' => 1, + 'active' => true, + ] + ], + 'range' => [], + ] + ], + 3 => [ + 'code' => 'price', + 'label' => null, + 'type' => 'range', + 'items' => [ + 'list' => [], + 'range' => [ + 0 => [ + 'label' => null, + 'count' => 2, + 'active' => true, + 'fullRange' => [ + 'min' => 100.0, + 'max' => 104.0, + ], + 'activeRange' => [ + 'min' => 103.0, + 'max' => 104.0, + ] + ] + ] + ] + ] + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatDataWFacets($result)); + } + + /** + * query по сущ цене и бренду и post_filter по цвету и цене + * @return void + */ + public function testQueryExistingPriceAndBrandAndPostFilterPriceAndColor() + { + $criteria = new Criteria(); + + $filterCollectionPrice = new FilterGroupCollection([ + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::LTE), + new FilterNumber(104) + ), + ]); + $filterCollectionPrice->setFilterType(FilterType::query()); + $criteria->filters()->add($filterCollectionPrice); + + $filterCollectionBrand = new FilterGroupCollection([ + new Filter( + new Field('brand'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('nike') + ), + new Filter( + new Field('brand'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('adidas') + ), + ]); + $filterCollectionBrand + ->setLogicalType(LogicOperator::or()) + ->setFilterType(FilterType::query()); + $criteria->filters()->add($filterCollectionBrand); + + + $filterCollectionPrice = new FilterGroupCollection([ + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::GTE), + new FilterNumber(101) + ), + ]); + $criteria->filters()->add($filterCollectionPrice); + + $filterCollectionColor = new FilterGroupCollection([ + new Filter( + new Field('color'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('green') + ), + ]); + $criteria->filters()->add($filterCollectionColor); + + + $q = new SearchQuery($criteria); + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + $expected = [ + 'hits' => [ + 's4', + ], + 'facets' => [ + 0 => [ + 'code' => 'brand', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'adidas', + 'count' => 0, + 'active' => false, + ], + 1 => [ + 'label' => null, + 'value' => 'nike', + 'count' => 1, + 'active' => true, + ] + ], + 'range' => [] + ] + ], + 1 => [ + 'code' => 'color', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => 'green', + 'count' => 1, + 'active' => true, + ], + 1 => [ + 'label' => null, + 'value' => 'red', + 'count' => 2, + 'active' => true, + ] + ], + 'range' => [] + ] + ], + 2 => [ + 'code' => 'size', + 'label' => null, + 'type' => 'list', + 'items' => [ + 'list' => [ + 0 => [ + 'label' => null, + 'value' => '43', + 'count' => 1, + 'active' => true, + ], + 1 => [ + 'label' => null, + 'value' => '46', + 'count' => 0, + 'active' => false, + ], + 2 => [ + 'label' => null, + 'value' => '47', + 'count' => 0, + 'active' => false, + ], + 3 => [ + 'label' => null, + 'value' => 'xl', + 'count' => 0, + 'active' => false, + ] + ], + 'range' => [] + ] + ], + 3 => [ + 'code' => 'price', + 'label' => null, + 'type' => 'range', + 'items' => [ + 'list' => [], + 'range' => [ + 0 => [ + 'label' => null, + 'count' => 1, + 'active' => true, + 'fullRange' => [ + 'min' => 100.0, + 'max' => 104.0, + ], + 'activeRange' => [ + 'min' => 103.0, + 'max' => 103.0, + ], + ] + ] + ] + ] + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatDataWFacets($result)); + } +} diff --git a/tests/FIlter/QueryTest.php b/tests/FIlter/QueryTest.php new file mode 100644 index 0000000000000000000000000000000000000000..df79ee2efa0e7442b0a0b33283db5e83f8d4a8da --- /dev/null +++ b/tests/FIlter/QueryTest.php @@ -0,0 +1,490 @@ + [ + 'key' => 'category_id', + 'value' => 'shoes', + ], + 'brand' => [ + 'key' => 'brand', + 'value' => 'nike', + ], + 'price' => [ + 'key' => 'price', + 'max' => 100.0, + 'min' => 10.0, + ] + ]; + + $criteria = new Criteria(); + + + $filterCollectionCategory = new FilterGroupCollection([ + new Filter( + new Field($filter['category']['key']), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword($filter['category']['value']) + ) + ]); + $filterCollectionCategory->setFilterType(FilterType::query()); + $criteria->filters()->add($filterCollectionCategory); + + $filterCollectionBrand = new FilterGroupCollection([ + new Filter( + new Field($filter['brand']['key']), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword($filter['brand']['value']) + ) + ]); + + $filterCollectionPrice = new FilterGroupCollection([ + new Filter( + new Field($filter['price']['key']), + new FilterOperator(FilterOperator::LT), + new FilterNumber($filter['price']['min']) + ), + new Filter( + new Field($filter['price']['key']), + new FilterOperator(FilterOperator::GT), + new FilterNumber($filter['price']['max']) + ), + ]); + + // Формирование фильтра для post + $criteriaPost = clone $criteria; + $criteriaPost->filters()->add(clone $filterCollectionPrice); + $criteriaPost->filters()->add(clone $filterCollectionBrand); + + + // Формирование фильтра для query + $criteriaQuery = clone $criteria; + + $filterTypeQuery = FilterType::query(); + $filterCollectionPrice->setFilterType($filterTypeQuery); + $filterCollectionBrand->setFilterType($filterTypeQuery); + $criteriaQuery->filters()->add(clone $filterCollectionPrice); + $criteriaQuery->filters()->add(clone $filterCollectionBrand); + + + // Получение классов с данными для запроса в es + $criteriaToEsRequest = new CriteriaToEsRequest(); + $requestPost = $criteriaToEsRequest->fromCriteria($criteriaPost); + $requestQuery = $criteriaToEsRequest->fromCriteria($criteriaQuery); + + + $expectedFilter = [ + [ + "nested" => [ + "path" => "search_data", + "query" => [ + "bool" => [ + "filter" => [ + [ + "nested" => [ + "path" => "search_data.keyword_facet", + "query" => [ + "bool" => [ + "filter" => [ + [ + "term" => [ + "search_data.keyword_facet.facet_code" => $filter['brand']['key'] + ] + ], + [ + "term" => [ + "search_data.keyword_facet.facet_value" => $filter['brand']['value'] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ], + [ + "nested" => [ + "path" => "search_data", + "query" => [ + "bool" => [ + "filter" => [ + [ + "nested" => [ + "path" => "search_data.number_facet", + "query" => [ + "bool" => [ + "filter" => [ + [ + "term" => [ + "search_data.number_facet.facet_code" => $filter['price']['key'] + ] + ], + [ + "range" => [ + "search_data.number_facet.facet_value" => [ + "lt" => $filter['price']['min'], + "gt" => $filter['price']['max'], + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ]; + $expected = [ + "query" => [ + "bool" => [ + "must" => [ + [ + "term" => [ + "category_id" => $filter['category']['value'] + ] + ], + ] + ] + ], + ]; + + + $this->assertArray( + array_merge($expected, [ + "query" => [ + "bool" => [ + "filter" => $expectedFilter, + ] + ] + ]), + $requestQuery->es(), + 'query response' + ); + + + $this->assertArray( + array_merge($expected, [ + "post_filter" => [ + "bool" => [ + "filter" => $expectedFilter, + ] + ] + ]), + $requestPost->es(), + 'post response' + ); + } + + + public function testAddingASingleKeyFilterToPostAndQuery() + { + $filter = [ + 'price' => [ + 'key' => 'price', + 'max' => 100.0, + 'min' => 10.0, + 'lower' => 0.0, + ] + ]; + + $criteria = new Criteria(); + + $filterCollectionPrice = new FilterGroupCollection([ + new Filter( + new Field($filter['price']['key']), + new FilterOperator(FilterOperator::LT), + new FilterNumber($filter['price']['min']) + ), + new Filter( + new Field($filter['price']['key']), + new FilterOperator(FilterOperator::GT), + new FilterNumber($filter['price']['max']) + ), + ]); + $criteria->filters()->add($filterCollectionPrice); + + + $filterCollectionQueryPrice = new FilterGroupCollection([ + new Filter( + new Field($filter['price']['key']), + new FilterOperator(FilterOperator::LT), + new FilterNumber($filter['price']['lower']) + ), + ]); + $filterCollectionQueryPrice->setFilterType(FilterType::query()); + $criteria->filters()->add($filterCollectionQueryPrice); + + + $criteriaToEsRequest = new CriteriaToEsRequest(); + $request = $criteriaToEsRequest->fromCriteria($criteria); + + + $expected = [ + "post_filter" => [ + "bool" => [ + "filter" => [ + 0 => [ + "nested" => [ + "path" => "search_data", + "query" => [ + "bool" => [ + "filter" => [ + 0 => [ + "nested" => [ + "path" => "search_data.number_facet", + "query" => [ + "bool" => [ + "filter" => [ + [ + "term" => [ + "search_data.number_facet.facet_code" => "price" + ] + ], + [ + "range" => [ + "search_data.number_facet.facet_value" => [ + "lt" => $filter['price']['min'], + "gt" => $filter['price']['max'], + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ], + "query" => [ + "bool" => [ + "filter" => [ + 0 => [ + "nested" => [ + "path" => "search_data", + "query" => [ + "bool" => [ + "filter" => [ + 0 => [ + "nested" => [ + "path" => "search_data.number_facet", + "query" => [ + "bool" => [ + "filter" => [ + 0 => [ + "term" => [ + "search_data.number_facet.facet_code" => "price" + ] + ], + 1 => [ + "range" => [ + "search_data.number_facet.facet_value" => [ + "lt" => $filter['price']['lower'] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ]; + + + $this->assertArray($expected, $request->es()); + } + + public function testGlobalFilterPrice() + { + $filter = [ + 'price' => [ + 'key' => 'price', + 'min' => 105, + 'lower' => 103, + ] + ]; + + $criteria = new Criteria(); + + $filterCollectionPrice = new FilterGroupCollection([ + new Filter( + new Field($filter['price']['key']), + new FilterOperator(FilterOperator::GTE), + new FilterNumber($filter['price']['min']) + ) + ]); + $criteria->filters()->add($filterCollectionPrice); + + + $filterCollectionQueryPrice = new FilterGroupCollection([ + new Filter( + new Field($filter['price']['key']), + new FilterOperator(FilterOperator::GTE), + new FilterNumber($filter['price']['lower']) + ), + ]); + $filterCollectionQueryPrice->setFilterType(FilterType::query()); + $criteria->filters()->add($filterCollectionQueryPrice); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + $expected = [ + 'hits' => [ + 'h2', + 'h3', + 'p1', + ], + "facets" => [ + 0 => [ + "code" => "brand", + "label" => null, + "type" => "list", + "items" => [ + "list" => [ + 0 => [ + "label" => null, + "value" => "nike", + "count" => 1, + "active" => true, + ], + 1 => [ + "label" => null, + "value" => "rebook", + "count" => 2, + "active" => true, + ] + ], + "range" => [], + ], + ], + 1 => [ + "code" => "color", + "label" => null, + "type" => "list", + "items" => [ + "list" => [ + 0 => [ + "label" => null, + "value" => "green", + "count" => 0, + "active" => false, + ], + 1 => [ + "label" => null, + "value" => "red", + "count" => 0, + "active" => false, + ], + 2 => [ + "label" => null, + "value" => "white", + "count" => 3, + "active" => true, + ], + ], + "range" => [], + ], + ], + 2 => [ + "code" => "size", + "label" => null, + "type" => "list", + "items" => [ + "list" => [ + 0 => [ + "label" => null, + "count" => 0, + "value" => "43", + "active" => false, + ], + 1 => [ + "label" => null, + "value" => "xl", + "count" => 2, + "active" => true, + ], + 2 => [ + "label" => null, + "value" => "xxl", + "count" => 1, + "active" => true, + ], + ], + "range" => [], + ], + ], + 3 => [ + "code" => "price", + "label" => null, + "type" => "range", + "items" => [ + "list" => [], + "range" => [ + 0 => [ + "label" => null, + "count" => 3, + "active" => true, + "fullRange" => [ + "min" => 103.0, + "max" => 107.0, + ], + "activeRange" => [ + "min" => 105.0, + "max" => 107.0, + ] + ] + ] + ] + ] + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatDataWFacets($result)); + } +} \ No newline at end of file diff --git a/tests/FIlter/RangeTest.php b/tests/FIlter/RangeTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b9f6726439c3ef77184c23ac0867f166a2d76055 --- /dev/null +++ b/tests/FIlter/RangeTest.php @@ -0,0 +1,372 @@ +filters()->add(new FilterGroupCollection([ + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::GTE), + new FilterNumber(103) + ), + ])); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [ + 's4', + 'h1', + 'h2', + 'h3', + 'p1' + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatData($result)); + } + + /** + * Поиск элементов по мин цене + * + * @return void + */ + public function testExistByMinPrice2(): void + { + $criteria = new Criteria(); + + $criteria->filters()->add(new FilterGroupCollection([ + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::GT), + new FilterNumber(103.01) + ), + ])); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [ + 'h1', + 'h2', + 'h3', + 'p1' + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatData($result)); + } + + /** + * Поиск элементов по мин цене + * + * @return void + */ + public function testExistByMinPrice3(): void + { + $criteria = new Criteria(); + + $criteria->filters()->add(new FilterGroupCollection([ + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::GT), + new FilterNumber(102.99) + ), + ])); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [ + 's4', + 'h1', + 'h2', + 'h3', + 'p1' + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatData($result)); + } + + /** + * Поиск элементов по макс цене + * + * @return void + */ + public function testExistByMaxPrice(): void + { + $criteria = new Criteria(); + + $criteria->filters()->add(new FilterGroupCollection([ + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::LTE), + new FilterNumber(102) + ), + ])); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [ + 's1', + 's2', + 's3', + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatData($result)); + } + + /** + * Поиск элементов по макс цене + * + * @return void + */ + public function testExistByMaxPrice2(): void + { + $criteria = new Criteria(); + + $criteria->filters()->add(new FilterGroupCollection([ + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::LT), + new FilterNumber(102.99) + ), + ])); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [ + 's1', + 's2', + 's3', + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatData($result)); + } + + /** + * Поиск элементов по макс цене + * + * @return void + */ + public function testExistByMaxPrice3(): void + { + $criteria = new Criteria(); + + $criteria->filters()->add(new FilterGroupCollection([ + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::LT), + new FilterNumber(101.99) + ), + ])); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [ + 's1', + 's2', + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatData($result)); + } + + /** + * Поиск элементов по диапазону цен + * + * @return void + */ + public function testExistByRangePrice(): void + { + $criteria = new Criteria(); + + $criteria->filters()->add(new FilterGroupCollection([ + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::GTE), + new FilterNumber(101) + ), + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::LTE), + new FilterNumber(102) + ), + ])); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [ + 's2', + 's3', + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatData($result)); + } + + /** + * Поиск элементов по диапазону цен + * + * @return void + */ + public function testExistByRangePrice2(): void + { + $criteria = new Criteria(); + + $criteria->filters()->add(new FilterGroupCollection([ + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::GTE), + new FilterNumber(101.01) + ), + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::LTE), + new FilterNumber(102) + ), + ])); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [ + 's3', + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatData($result)); + } + + /** + * Поиск элементов по диапазону цен + * + * @return void + */ + public function testExistByRangePrice3(): void + { + $criteria = new Criteria(); + + $criteria->filters()->add(new FilterGroupCollection([ + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::GTE), + new FilterNumber(101) + ), + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::LTE), + new FilterNumber(101.99) + ), + ])); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [ + 's2', + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatData($result)); + } + + /** + * Поиск элементов по диапазону цен + * + * @return void + */ + public function testExistByRangePrice4(): void + { + $criteria = new Criteria(); + + $criteria->filters()->add(new FilterGroupCollection([ + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::GTE), + new FilterNumber(101.99) + ), + new Filter( + new Field('price'), + new FilterOperator(FilterOperator::LTE), + new FilterNumber(101.99) + ), + ])); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [] + ]; + + $this->assertEquals($expected, FormatData::formatData($result)); + } +} \ No newline at end of file diff --git a/tests/FIlter/SearchItemsTest.php b/tests/FIlter/SearchItemsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..7f53c9173351c4b1dcb10f0e57e5e8cd038f7ba4 --- /dev/null +++ b/tests/FIlter/SearchItemsTest.php @@ -0,0 +1,226 @@ +filters()->add( + (new FilterGroupCollection([ + new Filter( + new Field('category_id'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('prices') + ) + ])) + ->setFilterType(FilterType::query()) + ); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [ + 'p1' + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatData($result)); + } + + /** + * Поиск всех элементов раздела + * + * @return void + */ + public function testExistByEmptyCategory(): void + { + $criteria = new Criteria(); + + $criteria->filters()->add( + (new FilterGroupCollection([ + new Filter( + new Field('category_id'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('') + ) + ])) + ->setFilterType(FilterType::query()) + ); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [] + ]; + + $this->assertEquals($expected, FormatData::formatData($result)); + } + + /** + * Поиск всех элементов + * + * @return void + */ + public function testExistAllItems(): void + { + $criteria = new Criteria(); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [ + 's1', + 's2', + 's3', + 's4', + 'h1', + 'h2', + 'h3', + 'p1' + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatData($result)); + } + + /** + * Поиск существующего элемента по части строки поиска + * + * @return void + */ + public function testExistItemsByPart(): void + { + $criteria = new Criteria(); + + $criteria->filters()->add( + (new FilterGroupCollection([ + new Filter( + new Field('search'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('Nike') + ) + ])) + ->setFilterType(FilterType::query()) + ); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [ + 's4', + 'h1', + 'h2', + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatData($result)); + } + + /** + * Поиск существующего элемента по полному названию + * + * @return void + */ + public function testExistItemsByName(): void + { + $criteria = new Criteria(); + + $criteria->filters()->add( + (new FilterGroupCollection([ + new Filter( + new Field('search'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('Nike Dri-FIT Strike') + ) + ])) + ->setFilterType(FilterType::query()) + ); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [ + 'h1', + 'h2', + 's4' + ] + ]; + + $this->assertEquals($expected, FormatData::formatData($result)); + } + + /** + * Поиск существующего элемента по полному названию + * + * @return void + */ + public function testExistItemsByCyrillicName(): void + { + $criteria = new Criteria(); + + $criteria->filters()->add( + (new FilterGroupCollection([ + new Filter( + new Field('search'), + new FilterOperator(FilterOperator::EQ), + new FilterKeyword('Товар с ценой') + ) + ])) + ->setFilterType(FilterType::query()) + ); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [ + 'p1', + ] + ]; + + $this->assertEqualsCanonicalizing($expected, FormatData::formatData($result)); + } +} \ No newline at end of file diff --git a/tests/FIlter/SortTest.php b/tests/FIlter/SortTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a7a28d9f43ebe856774824af911efa08b5f758b3 --- /dev/null +++ b/tests/FIlter/SortTest.php @@ -0,0 +1,305 @@ +sorting()->add(new Order( + new AttrType('category_id'), + new OrderAscType(), + )); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [ + 'p1', + 's1', + 's2', + 's3', + 's4', + 'h1', + 'h2', + 'h3', + ] + ]; + + $this->assertEquals($expected, FormatData::formatData($result)); + } + + /** + * Обратная сортировка элементов по категории + * + * @return void + */ + public function testSortByCategoryReverse(): void + { + $criteria = new Criteria(); + + $criteria->sorting()->add(new Order( + new AttrType('category_id'), + new OrderDescType(), + )); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [ + 'h1', + 'h2', + 'h3', + 's1', + 's2', + 's3', + 's4', + 'p1', + ] + ]; + + $this->assertEquals($expected, FormatData::formatData($result)); + } + + /** + * Сортировка элементов по свойству + * + * @return void + */ + public function testSortByKeyword(): void + { + $criteria = new Criteria(); + + $criteria->sorting()->add(new Order( + new PropertyType('color', PropertyType::TYPE_KEYWORD), + new OrderAscType(), + )); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [ + 's3', + 's1', + 's4', + 's2', + 'h1', + 'h2', + 'h3', + 'p1' + ] + ]; + + $this->assertEquals($expected, FormatData::formatData($result)); + } + + /** + * Сортировка элементов по свойству + * + * @return void + */ + public function testSortByKeywordReverse(): void + { + $criteria = new Criteria(); + + $criteria->sorting()->add(new Order( + new PropertyType('color', PropertyType::TYPE_KEYWORD), + new OrderDescType(), + )); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [ + 'h2', + 'h3', + 'p1', + 's2', + 'h1', + 's1', + 's4', + 's3', + ] + ]; + + $this->assertEquals($expected, FormatData::formatData($result)); + } + + /** + * Сортировка элементов по свойству + * + * @return void + */ + public function testSortByNumber(): void + { + $criteria = new Criteria(); + + $criteria->sorting()->add(new Order( + new PropertyType('price', PropertyType::TYPE_NUMBER), + new OrderAscType(), + )); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [ + 's1', + 's2', + 's3', + 's4', + 'h1', + 'h2', + 'h3', + 'p1' + ] + ]; + + $this->assertEquals($expected, FormatData::formatData($result)); + } + + /** + * Сортировка элементов по свойству + * + * @return void + */ + public function testSortByNumberReverse(): void + { + $criteria = new Criteria(); + + $criteria->sorting()->add(new Order( + new PropertyType('price', PropertyType::TYPE_NUMBER), + new OrderDescType(), + )); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [ + 'p1', + 'h3', + 'h2', + 'h1', + 's4', + 's3', + 's2', + 's1', + ] + ]; + + $this->assertEquals($expected, FormatData::formatData($result)); + } + + /** + * Сортировка элементов по свойству + * + * @return void + */ + public function testSortByCombined(): void + { + $criteria = new Criteria(); + + $criteria->sorting()->add(new Order( + new PropertyType('size', PropertyType::TYPE_KEYWORD), + new OrderAscType(), + )); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [ + 's4', + 's1', + 's2', + 's3', + 'h1', + 'h3', + 'p1', + 'h2', + ] + ]; + + $this->assertEquals($expected, FormatData::formatData($result)); + } + + /** + * Сортировка элементов по свойству + * + * @return void + */ + public function testSortByCombinedReverse(): void + { + $criteria = new Criteria(); + + $criteria->sorting()->add(new Order( + new PropertyType('size', PropertyType::TYPE_KEYWORD), + new OrderDescType(), + )); + + $q = new SearchQuery($criteria); + + $handler = SearchClient::getInstance(); + $result = $handler->handle($q)->result; + + + $expected = [ + 'hits' => [ + 'h2', + 'h1', + 'h3', + 'p1', + 's2', + 's3', + 's1', + 's4', + ] + ]; + + $this->assertEquals($expected, FormatData::formatData($result)); + } +} \ No newline at end of file diff --git a/tests/Factory/ClientFactory.php b/tests/Factory/ClientFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..30f2480f40a7fb967e3e1d59274bbfcf01de2f7a --- /dev/null +++ b/tests/Factory/ClientFactory.php @@ -0,0 +1,25 @@ +setHosts(explode(',', $_ENV['IQ_ES_HOSTS'] ?: 'http://localhost:9200')) + ->setBasicAuthentication($_ENV['IQ_ES_USER'], $_ENV['IQ_ES_PASSWORD']) + ->build(); + + return self::$instance; + } +} \ No newline at end of file diff --git a/tests/Helpers/Arr.php b/tests/Helpers/Arr.php new file mode 100644 index 0000000000000000000000000000000000000000..54f6cc8ec967943789b9de0b4a34e3aa36a26c84 --- /dev/null +++ b/tests/Helpers/Arr.php @@ -0,0 +1,27 @@ + $value) { + if (is_array($value) && ! empty($value)) { + $results = array_merge($results, static::dot($value, $prepend.$key.'.')); + } else { + $results[$prepend.$key] = $value; + } + } + + return $results; + } +} \ No newline at end of file diff --git a/tests/Helpers/FormatData.php b/tests/Helpers/FormatData.php new file mode 100644 index 0000000000000000000000000000000000000000..f042c9a1dddcb40e5642e45c755a2a227f0d9c8f --- /dev/null +++ b/tests/Helpers/FormatData.php @@ -0,0 +1,110 @@ +getProducts(); + $aResult = ['hits' => []]; + foreach ($oProductCollection as $oProduct) { + /** @var Product $oProduct */ + $aResult['hits'][] = $oProduct->id; + } + return $aResult; + } + + public static function formatDataWFacets(Result $result): array + { + $aResult = ['facets' => []]; + $aResult['hits'] = static::formatData($result)['hits']; + foreach ($result->getFacets() as $facet) { + /** @var Facet $facet */ + $dataFacet = [ + 'code' => $facet->getCode(), + 'label' => null, // $facet->getLabel(), + 'type' => $facet->getType() instanceof FacetRangeType ? 'range' : ($facet->getType() instanceof FacetListType ? 'list' : null), + 'items' => [ + 'list' => [], + 'range' => [] + ], + ]; + + $items = $facet->products->sort('getValue'); + foreach ($items as $item) { + if ($item instanceof FacetItemList) { + /** @var FacetItemList $item */ + $dataFacet['items']['list'][] = [ + 'label' => $item->getLabel(), + 'value' => $item->getValue(), + 'count' => $item->getCount(), + 'active' => $item->isActive(), + ]; + } + if ($item instanceof FacetItemRange) { + /** @var FacetItemRange $item */ + + $aData = [ + 'label' => $item->getLabel(), + 'count' => $item->getCount(), + 'active' => $item->isActive(), + ]; + + $aData['fullRange'] = $item->getFullRange(); + $aData['activeRange'] = $item->getSelectedRange(); + + if ($result->getTotal() > 0 && empty(array_filter($aData['activeRange']))) { + $aData['activeRange'] = $aData['fullRange']; + } + + $dataFacet['items']['range'][] = $aData; + } + } + + $aResult['facets'][] = $dataFacet; + } + return $aResult; + } + + public static function formatDataProducts(Result $result): array + { + $products = []; + /** @var Product $product */ + foreach ($result->getProducts() as $product) { + $data = [ + 'id' => $product->id, + 'category' => $product->info['category_id'] + ]; + + if ($product->title) { + $data['name'] = $product->title; + } + + if (isset($product->info['search_data'])) { + $props = $product->info['search_data']; + if (!empty($props['keyword_facet'])) { + foreach ($props['keyword_facet'] as $prop) { + $data['properties'][$prop['facet_code']] = $prop['facet_value']; + } + } + if (!empty($props['number_facet'])) { + foreach ($props['number_facet'] as $prop) { + $data['properties'][$prop['facet_code']] = $prop['facet_value']; + } + } + } + + $products[] = $data; + } + return ['products' => $products]; + } +} \ No newline at end of file diff --git a/tests/Helpers/TestIndexProvider.php b/tests/Helpers/TestIndexProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..89761f057372389acdb74884a098905dd209bdd4 --- /dev/null +++ b/tests/Helpers/TestIndexProvider.php @@ -0,0 +1,116 @@ +configuration = $configuration; + $this->products = $products; + } + + /** + * @inheritDoc + */ + public function get(): \Generator + { + foreach ($this->products as $product) { + $document = new ProductDocument(new FacetCategory($product['category'])); + //todo по-хорошему нужны базовые классы, которые будут описывать свойства + // и формировать структуру для последующей обработки + + $data = [ + 'id' => $product['id'], + ]; + + if (isset($product['name'])) { + $document->setSearchContent($product['name']); + $data['title'] = $product['name']; + } + + $document->setAdditionData($data); + + if (isset($product['properties'])) { + foreach ($product['properties'] as $key => $prop) { + if ($key === 'price') { + $document->getNumberFacets()->add(new FacetNumber($key, $prop)); + } else { + $document->getKeywordFacets()->add(new FacetKeyword($key, $prop)); + } + } + } + + $product['type'] = $product['type'] ?? null; + switch ($product['type']) { + case 'update': + $document->skipEmpty(true); + $index = new UpdateIndex( + $this->configuration->getIndexName(), + $document, + $product['id'] + ); + break; + case 'delete': + $index = new DeleteIndex( + $this->configuration->getIndexName(), + $product['id'] + ); + break; + default: + $index = new AddIndex( + $this->configuration->getIndexName(), + $document, + $product['id'] + ); + break; + } + + yield $index; + } + } + + /** + * @inheritDoc + */ + public function setBatchSize(int $size): void + { + } + + /** + * @inheritDoc + */ + public function getBatchSize(): ?int + { + return null; + } + + /** + * @inheritDoc + */ + public function setLimit(int $limit): void + { + } + + /** + * @inheritDoc + */ + public function getLimit(): ?int + { + return null; + } +} \ No newline at end of file diff --git a/tests/Seed/DefaultSeed.php b/tests/Seed/DefaultSeed.php new file mode 100644 index 0000000000000000000000000000000000000000..8ab1eeacdd7cfaee6f7e7e1b346d28ee7645f836 --- /dev/null +++ b/tests/Seed/DefaultSeed.php @@ -0,0 +1,84 @@ +configuration = new BaseConfiguration(); + + $this->indexRunner = new IndexRunner( + ClientFactory::create(), + $this->configuration, + new TestLogger() + ); + } + + public function start() + { + $provider = new TestIndexProvider($this->configuration, [ + [ + 'id' => 's1', + 'name' => 'Кроссовки NMD_R1 Boba Fett Spectoo', + 'category' => 'shoes', + 'properties' => ['brand' => 'adidas', 'color' => 'green', 'size' => 46,'price' => 100] + ], + [ + 'id' => 's2', + 'name' => 'КРОССОВКИ ULTRABOOST 5.0 DNA', + 'category' => 'shoes', + 'properties' => ['brand' => 'adidas', 'color' => 'red', 'size' => 47,'price' => 101] + ], + [ + 'id' => 's3', + 'name' => 'Кроссовки Reebok Royal Techque', + 'category' => 'shoes', + 'properties' => ['brand' => 'rebook', 'color' => 'blue', 'size' => 47,'price' => 102] + ], + [ + 'id' => 's4', + 'name' => 'Nike Air Zoom Pegasus 39', + 'category' => 'shoes', + 'properties' => ['brand' => 'nike', 'color' => 'green', 'size' => 43,'price' => 103] + ], + [ + 'id' => 'h1', + 'name' => 'Nike Dri-FIT Strike', + 'category' => 't-short', + 'properties' => ['brand' => 'nike', 'color' => 'red', 'size' => 'xl','price' => 104] + ], + [ + 'id' => 'h2', + 'name' => 'Nike Dri-FIT Rise 365', + 'category' => 't-short', + 'properties' => ['brand' => 'nike', 'color' => 'white', 'size' => 'xxl','price' => 105] + ], + [ + 'id' => 'h3', + 'name' => 'Компрессионная Футболка ACTIVCHILL Graphic Move', + 'category' => 't-short', + 'properties' => ['brand' => 'rebook', 'color' => 'white', 'size' => 'xl','price' => 106] + ], + [ + 'id' => 'p1', + 'name' => 'Товар с ценой', + 'category' => 'prices', + 'properties' => ['brand' => 'rebook', 'color' => 'white', 'size' => 'xl','price' => 107] + ], + ]); + + $this->indexRunner->run($provider); + } +} \ No newline at end of file diff --git a/tests/Service/SearchClient.php b/tests/Service/SearchClient.php new file mode 100644 index 0000000000000000000000000000000000000000..bb3d57a2ee96f38a0cf018f972aa015ca586408f --- /dev/null +++ b/tests/Service/SearchClient.php @@ -0,0 +1,29 @@ +load();