Skip to content

Commit fe28e2b

Browse files
committed
Facet integration tests
1 parent 9d1593e commit fe28e2b

File tree

8 files changed

+177
-27
lines changed

8 files changed

+177
-27
lines changed

src/Processors/FacetQueryProcessor.php

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
namespace Ensi\LaravelElasticQuerySpecification\Processors;
44

55
use Ensi\LaravelElasticQuery\Contracts\AggregationsBuilder;
6+
use Ensi\LaravelElasticQuery\Contracts\BoolQuery;
67
use Ensi\LaravelElasticQuerySpecification\Contracts\Constraint;
78
use Ensi\LaravelElasticQuerySpecification\Faceting\AllowedFacet;
8-
use Ensi\LaravelElasticQuerySpecification\Filtering\AllowedFilter;
99
use Ensi\LaravelElasticQuerySpecification\Specification\Specification;
1010
use Ensi\LaravelElasticQuerySpecification\Specification\Visitor;
11+
use Illuminate\Support\Collection;
1112

1213
class FacetQueryProcessor implements Visitor
1314
{
@@ -17,42 +18,55 @@ public function __construct(private AggregationsBuilder $builder, private string
1718

1819
public function visitRoot(Specification $specification): void
1920
{
20-
$facets = $specification->facets()->filter(fn (AllowedFacet $facet) => $facet->isActive());
21-
$current = $facets->first(fn (AllowedFacet $facet) => $facet->name() === $this->facetName);
21+
$facets = $specification->activeFacets();
22+
$constraints = $facets->flatMap(fn (AllowedFacet $facet) => $facet->filters());
2223

23-
$current?->disableFilters();
24+
if ($current = $this->findCurrentFacet($facets)) {
25+
$this->processFacet($current, $this->builder, $constraints);
26+
} else {
27+
$this->applyConstraints($this->builder, $constraints);
28+
}
29+
}
2430

25-
$facets->flatMap(fn (AllowedFacet $facet) => $facet->filters())
26-
->each(fn (AllowedFilter $filter) => $filter($this->builder));
31+
public function visitNested(string $field, Specification $specification): void
32+
{
33+
$facets = $specification->activeFacets();
34+
$constraints = $specification->constraints();
2735

28-
if ($current !== null) {
29-
$current->aggregate()($this->builder);
36+
if ($current = $this->findCurrentFacet($facets)) {
37+
$this->builder->nested(
38+
$field,
39+
fn (AggregationsBuilder $builder) => $this->processFacet($current, $builder, $constraints)
40+
);
41+
} else {
42+
$this->builder->whereHas(
43+
$field,
44+
fn (BoolQuery $query) => $this->applyConstraints($query, $constraints)
45+
);
3046
}
31-
32-
$current?->enableFilters();
3347
}
3448

35-
public function visitNested(string $field, Specification $specification): void
49+
public function done(): void
3650
{
37-
$current = $specification->facets()
38-
->filter(fn (AllowedFacet $facet) => $facet->isActive())
39-
->first(fn (AllowedFacet $facet) => $facet->name() === $this->facetName);
51+
}
4052

41-
$current?->disableFilters();
53+
private function findCurrentFacet(Collection $facets): ?AllowedFacet
54+
{
55+
return $facets->first(fn (AllowedFacet $facet) => $facet->name() === $this->facetName);
56+
}
4257

43-
$this->builder->nested($field, function (AggregationsBuilder $builder) use ($specification, $current) {
44-
$specification->constraints()
45-
->each(fn (Constraint $constraint) => $constraint($builder));
58+
private function processFacet(AllowedFacet $facet, AggregationsBuilder $builder, Collection $constraints): void
59+
{
60+
$facet->disableFilters();
4661

47-
if ($current !== null) {
48-
$current->aggregate()($builder);
49-
}
50-
});
62+
$this->applyConstraints($builder, $constraints);
5163

52-
$current?->enableFilters();
64+
$facet->aggregate()($builder);
65+
$facet->enableFilters();
5366
}
5467

55-
public function done(): void
68+
private function applyConstraints(BoolQuery $query, Collection $constraints): void
5669
{
70+
$constraints->each(fn (Constraint $constraint) => $constraint($query));
5771
}
5872
}

src/Specification/Specification.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,11 @@ public function hasActiveFacet(): bool
187187
{
188188
return null !== Arr::first($this->facets, fn (AllowedFacet $facet) => $facet->isActive());
189189
}
190+
191+
public function activeFacets(): Collection
192+
{
193+
return $this->facets()->filter(fn (AllowedFacet $facet) => $facet->isActive());
194+
}
190195
//endregion
191196

192197
private function addComponent(array &$target, string $name, mixed $component, string $type): void

tests/Data/fixtures/products.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"319": {
3535
"product_id": 319,
3636
"active": false,
37-
"name": "Leather gloes",
37+
"name": "Leather gloves",
3838
"code": "gloves",
3939
"rating": 0,
4040
"description": "L gloves descriptions",

tests/Integration/FacetingTest.php

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php
2+
3+
use Ensi\LaravelElasticQuerySpecification\Faceting\AllowedFacet;
4+
use Ensi\LaravelElasticQuerySpecification\Filtering\AllowedFilter;
5+
use Ensi\LaravelElasticQuerySpecification\Specification\CompositeSpecification;
6+
use Ensi\LaravelElasticQuerySpecification\Specification\Specification;
7+
8+
uses()->group('integration');
9+
10+
test('facets in root specification', function () {
11+
$spec = CompositeSpecification::new()
12+
->allowedFacets([
13+
AllowedFacet::minmax('rating'),
14+
AllowedFacet::terms('tags'),
15+
])
16+
->allowedFilters([
17+
AllowedFilter::exact('tags'),
18+
AllowedFilter::exact('rating'),
19+
]);
20+
21+
$request = [
22+
'facet' => ['tags', 'rating'],
23+
'filter' => ['tags' => 'water'],
24+
];
25+
26+
facetQuery($spec, $request)
27+
->assertBucketKeys('tags', ['water', 'drinks', 'clothes', 'gloves', 'video'])
28+
->assertMinMax('rating', 8, 10);
29+
});
30+
31+
test('facets in nested specification', function () {
32+
$spec = CompositeSpecification::new()
33+
->nested('offers', function (Specification $spec) {
34+
$spec
35+
->allowedFilters([
36+
AllowedFilter::exact('price'),
37+
AllowedFilter::exact('seller_id'),
38+
])
39+
->allowedFacets([
40+
AllowedFacet::minmax('price'),
41+
AllowedFacet::terms('seller_id'),
42+
]);
43+
});
44+
45+
$request = [
46+
'facet' => ['price', 'seller_id'],
47+
'filter' => ['seller_id' => 20],
48+
];
49+
50+
facetQuery($spec, $request)
51+
->assertBucketKeys('seller_id', [10, 15, 20, 90])
52+
->assertMinMax('price', 200, 28990);
53+
});
54+
55+
test('facets in different nested specification', function () {
56+
$spec = CompositeSpecification::new()
57+
->nested('offers', function (Specification $spec) {
58+
$spec->allowedFilters([AllowedFilter::exact('price')])
59+
->allowedFacets([AllowedFacet::minmax('price')]);
60+
})
61+
->nested('offers', function (Specification $spec) {
62+
$spec->allowedFilters([AllowedFilter::exact('seller_id')])
63+
->allowedFacets([AllowedFacet::terms('seller_id')]);
64+
});
65+
66+
$request = [
67+
'facet' => ['price', 'seller_id'],
68+
'filter' => ['seller_id' => 90],
69+
];
70+
71+
facetQuery($spec, $request)
72+
->assertBucketKeys('seller_id', [10, 15, 20, 90])
73+
->assertMinMax('price', 980, 41999);
74+
});
75+
76+
test('facets in root and nested specifications', function () {
77+
$spec = CompositeSpecification::new()
78+
->nested('offers', function (Specification $spec) {
79+
$spec->allowedFilters([AllowedFilter::exact('seller_id')])
80+
->allowedFacets([AllowedFacet::terms('seller_id')]);
81+
})
82+
->allowedFacets([AllowedFacet::terms('tags')])
83+
->allowedFilters([AllowedFilter::exact('tags')]);
84+
85+
$request = [
86+
'facet' => ['tags', 'seller_id'],
87+
'filter' => ['tags' => 'water', 'seller_id' => 20],
88+
];
89+
90+
facetQuery($spec, $request)
91+
->assertBucketKeys('tags', ['water', 'drinks', 'video'])
92+
->assertBucketKeys('seller_id', [10, 15, 20]);
93+
});

tests/Integration/TestAggregationResults.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
class TestAggregationResults
1616
{
17-
public function __construct(private Collection $results)
17+
public function __construct(protected Collection $results)
1818
{
1919
}
2020

@@ -55,4 +55,11 @@ public function assertMinMax(string $aggName, mixed $min, mixed $max): self
5555

5656
return $this;
5757
}
58+
59+
public function dump(): static
60+
{
61+
$this->results->dump();
62+
63+
return $this;
64+
}
5865
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace Ensi\LaravelElasticQuerySpecification\Tests\Integration;
4+
5+
use Ensi\LaravelElasticQuery\Aggregating\AggregationsQuery;
6+
use Ensi\LaravelElasticQuerySpecification\Contracts\QueryParameters;
7+
use Ensi\LaravelElasticQuerySpecification\FacetQueryBuilder;
8+
use Ensi\LaravelElasticQuerySpecification\Specification\CompositeSpecification;
9+
use Ensi\LaravelElasticQuerySpecification\Tests\Data\ProductsIndex;
10+
11+
class TestFacetResults extends TestAggregationResults
12+
{
13+
public static function make(
14+
CompositeSpecification $spec,
15+
QueryParameters $parameters,
16+
?AggregationsQuery $query = null
17+
): self {
18+
$builder = new FacetQueryBuilder($query ?? ProductsIndex::aggregate(), $spec, $parameters);
19+
$builder->validateResolved();
20+
21+
return new self($builder->get());
22+
}
23+
}

tests/Pest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Ensi\LaravelElasticQuerySpecification\CustomParameters;
66
use Ensi\LaravelElasticQuerySpecification\Specification\CompositeSpecification;
77
use Ensi\LaravelElasticQuerySpecification\Tests\Integration\TestAggregationResults;
8+
use Ensi\LaravelElasticQuerySpecification\Tests\Integration\TestFacetResults;
89
use Ensi\LaravelElasticQuerySpecification\Tests\Integration\TestSearchResults;
910
use Ensi\LaravelElasticQuerySpecification\Tests\TestCase;
1011
use Ensi\LaravelElasticQuerySpecification\Tests\UnitTestCase;
@@ -51,3 +52,10 @@ function aggQuery(CompositeSpecification $spec, array $parameters, ?Aggregations
5152

5253
return TestAggregationResults::make($spec, $queryParameters, $query);
5354
}
55+
56+
function facetQuery(CompositeSpecification $spec, array $parameters, ?AggregationsQuery $query = null): TestFacetResults
57+
{
58+
$queryParameters = new CustomParameters($parameters);
59+
60+
return TestFacetResults::make($spec, $queryParameters, $query);
61+
}

tests/Unit/Processors/FacetQueryProcessorTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
->where('bar', 50);
8181

8282
$query = Mockery::mock(AggregationsBuilder::class);
83-
$query->allows('nested')
83+
$query->allows('whereHas')
8484
->with('field', any())
8585
->andReturnUsing(function ($field, callable $callback) use ($query) {
8686
$callback($query);

0 commit comments

Comments
 (0)