From 0e182e75c230f1b7a182fa45cfe257e8ae587144 Mon Sep 17 00:00:00 2001 From: George Kelly Date: Wed, 23 Mar 2022 14:16:50 +0000 Subject: [PATCH 1/4] Reworked sorting to be more sensible --- src/MapFilter.php | 10 +++ src/Sieve.php | 65 ++++++++----------- src/WrapsFilter.php | 2 + tests/MapFilterTest.php | 85 +++++++++++++++++++++++++ tests/Mocks/Card.php | 9 +++ tests/Mocks/Owner.php | 13 ++++ tests/Mocks/Pet.php | 13 ++++ tests/SieveTest.php | 138 +++++++++++++++++++++++++--------------- 8 files changed, 246 insertions(+), 89 deletions(-) create mode 100644 tests/MapFilterTest.php create mode 100644 tests/Mocks/Card.php create mode 100644 tests/Mocks/Owner.php create mode 100644 tests/Mocks/Pet.php diff --git a/src/MapFilter.php b/src/MapFilter.php index 37b2c5d..bdd6c58 100644 --- a/src/MapFilter.php +++ b/src/MapFilter.php @@ -18,6 +18,16 @@ public function wrap(ModifiesQueries $filter) $this->filter = $filter; } + public function getWrapped(): ModifiesQueries + { + return $this->filter; + } + + public function target() + { + return $this->column; + } + public function modifyQuery($query, SearchTerm $search) { if (strpos($this->column, '.') !== false) { diff --git a/src/Sieve.php b/src/Sieve.php index 181ded2..56bf33a 100644 --- a/src/Sieve.php +++ b/src/Sieve.php @@ -8,8 +8,6 @@ class Sieve { protected $filters = []; - protected $sortable = []; - protected $request; protected $defaultSort = null; @@ -19,15 +17,11 @@ public function __construct(Request $request) $this->request = $request; } - public function configure($callback, array $sortable = []) + public function configure($callback) { foreach ($callback(new FilterBuilder) as $prop => $filter) { $this->addFilter($prop, $filter); } - - foreach ($sortable as $sort) { - $this->addSort($sort); - } } public function addFilter($property, $filter) @@ -35,11 +29,6 @@ public function addFilter($property, $filter) $this->filters[] = compact('property', 'filter'); } - public function filters() - { - return new FilterBuilder; - } - public function getFilters() { return $this->filters; @@ -66,43 +55,45 @@ public function apply($queryBuilder) $search = new SearchTerm($property, $operator, $property, $term); $filter->modifyQuery($queryBuilder, $search); } - } - if ($sort = $this->getSort()) { - $queryBuilder->orderBy($sort['sortBy'], $sort['sortDirection']); - } + $column = $property; + while ($filter instanceof WrapsFilter) { + if ($filter instanceof MapFilter) { + $column = $filter->target(); + break; + } - return $this; - } + $filter = $filter->getWrapped(); + } - public function setDefaultSort($property = 'id', $direction = 'asc'): Sieve - { - $this->sortable[] = $property; - $this->defaultSort = $property . ':' . $direction; + if (strpos($column, ".") !== false) { + continue; + } + + if ($this->getSort() == "$property:desc") { + $queryBuilder->orderBy($column, "desc"); + } + + if ($this->getSort() == "$property:asc") { + $queryBuilder->orderBy($column, "asc"); + } + } return $this; } - public function addSort(string $sort) + public function getSort(): ?string { - $this->sortable[] = $sort; + return $this->request->get("sort") ?? $this->defaultSort; } - public function getSort(): ?array + public function setDefaultSort($property = 'id', $direction = 'asc'): Sieve { - $sort = $this->request->get("sort") ?? $this->defaultSort ?? ':'; - - list($sortBy, $sortDirection) = explode(':', $sort, 2); - - if ((in_array(strtolower($sortDirection), ['asc', 'desc'])) && (in_array($sortBy, $this->sortable))) { - return compact('sortBy', 'sortDirection'); - } + $this->sortable[] = $property; + $this->defaultSort = $property . ':' . $direction; - return null; + return $this; } - public function getSortable(): array - { - return $this->sortable; - } + } diff --git a/src/WrapsFilter.php b/src/WrapsFilter.php index cac1810..8b87067 100644 --- a/src/WrapsFilter.php +++ b/src/WrapsFilter.php @@ -5,4 +5,6 @@ interface WrapsFilter extends ModifiesQueries { public function wrap(ModifiesQueries $filter); + + public function getWrapped(): ModifiesQueries; } \ No newline at end of file diff --git a/tests/MapFilterTest.php b/tests/MapFilterTest.php new file mode 100644 index 0000000..0936bb8 --- /dev/null +++ b/tests/MapFilterTest.php @@ -0,0 +1,85 @@ +wrap(new StringFilter); + + $eloquentBuilder = Pet::query(); + $mapFilter->modifyQuery($eloquentBuilder, new SearchTerm( + 'owner_id', + 'eq', + 'owner_id', + 1 + )); + + + $builder = $eloquentBuilder->getQuery(); + $this->assertEquals( + "select * from `pets` where exists (select * from `owners` where `pets`.`owner_id` = `owners`.`id` and `id` = ?)", + $builder->toSql() + ); + } + + /** + * @test + */ + public function can_target_columns_in_the_same_table() + { + $mapFilter = new MapFilter('oid'); + $mapFilter->wrap(new StringFilter); + + $eloquentBuilder = Pet::query(); + $mapFilter->modifyQuery($eloquentBuilder, new SearchTerm( + 'owner_id', + 'eq', + 'owner_id', + 1 + )); + + + $builder = $eloquentBuilder->getQuery(); + $this->assertEquals( + "select * from `pets` where `oid` = ?", + $builder->toSql() + ); + } + + /** + * @test + */ + public function can_target_nested_relationships() + { + + $mapFilter = new MapFilter('owner.card.id'); + $mapFilter->wrap(new StringFilter); + + $eloquentBuilder = Pet::query(); + $mapFilter->modifyQuery($eloquentBuilder, new SearchTerm( + 'card_id', + 'eq', + 'card_id', + 1 + )); + + + $builder = $eloquentBuilder->getQuery(); + $this->assertEquals( + "select * from `pets` where exists (select * from `owners` where `pets`.`owner_id` = `owners`.`id` and exists (select * from `cards` where `owners`.`id` = `cards`.`owner_id` and `id` = ?))", + $builder->toSql() + ); + } +} diff --git a/tests/Mocks/Card.php b/tests/Mocks/Card.php new file mode 100644 index 0000000..2d6fa44 --- /dev/null +++ b/tests/Mocks/Card.php @@ -0,0 +1,9 @@ +hasMany(Card::class); + } +} diff --git a/tests/Mocks/Pet.php b/tests/Mocks/Pet.php new file mode 100644 index 0000000..7270bb7 --- /dev/null +++ b/tests/Mocks/Pet.php @@ -0,0 +1,13 @@ +belongsTo(Owner::class); + } +} diff --git a/tests/SieveTest.php b/tests/SieveTest.php index 71bc7e2..a7dfad1 100644 --- a/tests/SieveTest.php +++ b/tests/SieveTest.php @@ -4,6 +4,7 @@ use Illuminate\Database\Query\Builder; use Illuminate\Http\Request; +use Tests\Mocks\Pet; use UKFast\Sieve\Filters\StringFilter; use UKFast\Sieve\Sieve; @@ -20,41 +21,42 @@ public function filters_and_sorts() ]); $seive = new Sieve($request); - $seive->addFilter('name', new StringFilter); - $seive->addSort('name'); + $seive->configure(fn ($builder) => [ + 'name' => $builder->string(), + ]); /** @var Builder */ $builder = $this->app->make(Builder::class); + $builder->from('pets'); $seive->apply($builder); - $this->assertEquals(1, count($builder->wheres)); - - $where = $builder->wheres[0]; - - $this->assertEquals('In', $where['type']); - $this->assertEquals('name', $where['column']); - $this->assertEquals([ - 'Snoopy', - 'Hobbes', - ], $where['values']); - $this->assertEquals('and', $where['boolean']); + $this->assertEquals( + 'select * from "pets" where "name" in (?, ?) order by "name" desc', + $builder->toSql() + ); + } - $this->assertEquals(1, count($builder->orders)); + /** + * @test + */ + public function set_default_sort_filter() + { + $request = Request::create('/'); - $order = $builder->orders[0]; + $sieve = new Sieve($request); + $sieve->setDefaultSort('name', 'desc'); - $this->assertEquals('name', $order['column']); - $this->assertEquals('desc', $order['direction']); + $this->assertEquals($sieve->getSort(), 'name:desc'); } /** * @test */ - public function applies_sieve_filters_to_a_query_builder() + public function applies_sieve_sorts_to_a_query_builder_asc() { $request = Request::create('/', 'GET', [ - 'name:in' => 'Snoopy,Hobbes', + 'sort' => 'name:asc', ]); $seive = new Sieve($request); @@ -62,97 +64,129 @@ public function applies_sieve_filters_to_a_query_builder() /** @var Builder */ $builder = $this->app->make(Builder::class); + $builder->from('pets'); $seive->apply($builder); - $this->assertEquals(1, count($builder->wheres)); - - $where = $builder->wheres[0]; - - $this->assertEquals('In', $where['type']); - $this->assertEquals('name', $where['column']); - $this->assertEquals([ - 'Snoopy', - 'Hobbes', - ], $where['values']); - $this->assertEquals('and', $where['boolean']); + $this->assertEquals( + 'select * from "pets" order by "name" asc', + $builder->toSql() + ); } /** * @test */ - public function set_default_sort_filter() + public function ignores_undefined_sort() { - $request = Request::create('/'); + $request = Request::create('/', 'GET', [ + 'sort' => 'name:desc', + ]); - $sieve = new Sieve($request); - $sieve->setDefaultSort('name', 'desc'); + $seive = new Sieve($request); + + /** @var Builder */ + $builder = $this->app->make(Builder::class); - $this->assertEquals($sieve->getSort(), ['sortBy' => 'name', 'sortDirection' => 'desc']); + $seive->apply($builder); + + $this->assertEquals(null, $builder->orders); } /** * @test */ - public function applies_sieve_sorts_to_a_query_builder() + public function ignores_invalid_sort_direction() { $request = Request::create('/', 'GET', [ - 'sort' => 'name:desc', + 'sort' => 'name:foo', ]); $seive = new Sieve($request); - $seive->addSort('name'); + $seive->addFilter('name', new StringFilter); /** @var Builder */ $builder = $this->app->make(Builder::class); $seive->apply($builder); - $this->assertEquals(1, count($builder->orders)); + $this->assertEquals(null, $builder->orders); + } + + /** + * @test + */ + public function expands_no_eplicit_operator_to_eq() + { + $request = Request::create('/', 'GET', [ + 'name' => 'Snoopy', + ]); + + $seive = new Sieve($request); + $seive->configure(fn ($builder) => [ + 'name' => $builder->string(), + ]); + + /** @var Builder */ + $builder = $this->app->make(Builder::class); + $builder->from('pets'); - $order = $builder->orders[0]; + $seive->apply($builder); - $this->assertEquals('name', $order['column']); - $this->assertEquals('desc', $order['direction']); + $this->assertEquals( + 'select * from "pets" where "name" = ?', + $builder->toSql() + ); } /** * @test */ - public function ignores_undefined_sort() + public function sorts_can_be_remapped() { $request = Request::create('/', 'GET', [ - 'sort' => 'name:desc', + 'sort' => 'name:asc', ]); $seive = new Sieve($request); - $seive->addSort('id'); + $seive->configure(fn ($builder) => [ + 'name' => $builder->for('pname')->string(), + ]); /** @var Builder */ - $builder = $this->app->make(Builder::class); + $builder = Pet::query(); $seive->apply($builder); - $this->assertEquals(null, $builder->orders); + $this->assertEquals( + 'select * from `pets` order by `pname` asc', + $builder->toSql() + ); } /** + * Not figured out a good way to do this with eloquent yet * @test */ - public function ignores_invalid_sort_direction() + public function ignores_sorts_on_relationships() { $request = Request::create('/', 'GET', [ - 'sort' => 'name:foo', + 'sort' => 'owner_name:asc', ]); $seive = new Sieve($request); - $seive->addSort('name'); + $seive->configure(fn ($builder) => [ + 'owner_name' => $builder->for('owner.name')->string(), + ]); /** @var Builder */ - $builder = $this->app->make(Builder::class); + $builder = Pet::query(); $seive->apply($builder); - $this->assertEquals(null, $builder->orders); + $this->assertEquals( + 'select * from `pets`', + $builder->toSql() + ); } } From e8f5cd7b31fbdef683cc58fe4b9b42fe474f0bfc Mon Sep 17 00:00:00 2001 From: George Kelly Date: Wed, 23 Mar 2022 14:41:50 +0000 Subject: [PATCH 2/4] Added tests for filter builder --- tests/FilterBuilderTest.php | 100 +++++++++++++++++++++++++++++++++++ tests/Mocks/NoOpFilter.php | 18 +++++++ tests/Mocks/PenceWrapper.php | 37 +++++++++++++ 3 files changed, 155 insertions(+) create mode 100644 tests/FilterBuilderTest.php create mode 100644 tests/Mocks/NoOpFilter.php create mode 100644 tests/Mocks/PenceWrapper.php diff --git a/tests/FilterBuilderTest.php b/tests/FilterBuilderTest.php new file mode 100644 index 0000000..fcfd374 --- /dev/null +++ b/tests/FilterBuilderTest.php @@ -0,0 +1,100 @@ +assertInstanceOf(StringFilter::class, $builder->string()); + } + + /** + * @test + */ + public function can_build_enum() + { + $builder = new FilterBuilder; + + $this->assertInstanceOf(EnumFilter::class, $builder->enum(['a', 'b'])); + } + + /** + * @test + */ + public function can_build_numeric() + { + $builder = new FilterBuilder; + + $this->assertInstanceOf(NumericFilter::class, $builder->numeric()); + } + + /** + * @test + */ + public function can_build_date() + { + $builder = new FilterBuilder; + + $this->assertInstanceOf(DateFilter::class, $builder->date()); + } + + /** + * @test + */ + public function can_build_boolean() + { + $builder = new FilterBuilder; + + $this->assertInstanceOf(BooleanFilter::class, $builder->boolean()); + } + + + /** + * @test + */ + public function can_build_custom_filters() + { + $builder = new FilterBuilder; + + $this->assertInstanceOf(NoOpFilter::class, $builder->custom(new NoOpFilter)); + } + + /** + * @test + */ + public function can_wrap_filters() + { + $builder = new FilterBuilder; + $wrapped = $builder->wrap(new PenceWrapper)->string(); + + $this->assertInstanceOf(PenceWrapper::class, $wrapped); + } + + /** + * @test + */ + public function can_wrap_multiple_filters() + { + $builder = new FilterBuilder; + $wrapped = $builder->wrap(new PenceWrapper)->wrap(new MapFilter('target'))->string(); + + $this->assertInstanceOf(MapFilter::class, $wrapped); + } +} diff --git a/tests/Mocks/NoOpFilter.php b/tests/Mocks/NoOpFilter.php new file mode 100644 index 0000000..acbcdbf --- /dev/null +++ b/tests/Mocks/NoOpFilter.php @@ -0,0 +1,18 @@ +filter = $filter; + } + + public function getWrapped(): ModifiesQueries + { + return $this->filter; + } + + public function modifyQuery($query, SearchTerm $search) + { + $this->filter->modifyQuery($query, new SearchTerm( + $search->property(), + $search->operator(), + $search->column(), + $search->term() * 100 + )); + } + + public function operators() + { + return $this->filter->operators(); + } +} From dc6300fa97133d959876487235df3957506747be Mon Sep 17 00:00:00 2001 From: George Kelly Date: Wed, 23 Mar 2022 14:48:25 +0000 Subject: [PATCH 3/4] Added tests for service provider and search macro --- tests/Mocks/Pet.php | 11 ++++++++++- tests/SieveServiceProviderTest.php | 26 ++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 tests/SieveServiceProviderTest.php diff --git a/tests/Mocks/Pet.php b/tests/Mocks/Pet.php index 7270bb7..710d260 100644 --- a/tests/Mocks/Pet.php +++ b/tests/Mocks/Pet.php @@ -3,11 +3,20 @@ namespace Tests\Mocks; use Illuminate\Database\Eloquent\Model; +use UKFast\Sieve\Searchable; -class Pet extends Model +class Pet extends Model implements Searchable { public function owner() { return $this->belongsTo(Owner::class); } + + public function sieve($sieve) + { + return $sieve->configure(fn ($filter) => [ + 'id' => $filter->numeric(), + 'name' => $filter->string(), + ]); + } } diff --git a/tests/SieveServiceProviderTest.php b/tests/SieveServiceProviderTest.php new file mode 100644 index 0000000..6baa73f --- /dev/null +++ b/tests/SieveServiceProviderTest.php @@ -0,0 +1,26 @@ +search(); + $this->assertInstanceOf(Builder::class, $query); + } +} \ No newline at end of file From 1596a7b3ee7847edb59eab19d69a91403832b30d Mon Sep 17 00:00:00 2001 From: George Kelly Date: Wed, 23 Mar 2022 15:28:48 +0000 Subject: [PATCH 4/4] Added tests for filtesr --- src/Exceptions/InvalidSearchTermException.php | 2 + src/Filters/DateFilter.php | 2 +- src/Filters/EnumFilter.php | 33 ++---- tests/Filters/BooleanFilterTest.php | 65 +++++++++++ tests/Filters/DateFilterTest.php | 109 ++++++++++++++++++ tests/Filters/EnumFilterTest.php | 49 ++++++++ tests/Filters/NumericFilterTest.php | 75 ++++++++++++ tests/Filters/StringFilterTest.php | 13 +++ 8 files changed, 325 insertions(+), 23 deletions(-) create mode 100644 tests/Filters/BooleanFilterTest.php create mode 100644 tests/Filters/DateFilterTest.php create mode 100644 tests/Filters/EnumFilterTest.php create mode 100644 tests/Filters/NumericFilterTest.php diff --git a/src/Exceptions/InvalidSearchTermException.php b/src/Exceptions/InvalidSearchTermException.php index d6fad69..3601e1d 100644 --- a/src/Exceptions/InvalidSearchTermException.php +++ b/src/Exceptions/InvalidSearchTermException.php @@ -3,7 +3,9 @@ namespace UKFast\Sieve\Exceptions; use RuntimeException; +use UKFast\Sieve\SearchTerm; class InvalidSearchTermException extends RuntimeException { + public ?array $allowedValues = []; } diff --git a/src/Filters/DateFilter.php b/src/Filters/DateFilter.php index 7c695a5..15a9cbc 100644 --- a/src/Filters/DateFilter.php +++ b/src/Filters/DateFilter.php @@ -13,7 +13,7 @@ public function modifyQuery($query, SearchTerm $search) $query->where($search->column(), $search->term()); } if ($search->operator() == 'neq') { - $query->whereNot($search->column(), $search->term()); + $query->where($search->column(), '!=', $search->term()); } if ($search->operator() == 'in') { $query->whereIn($search->column(), explode(',', $search->term())); diff --git a/src/Filters/EnumFilter.php b/src/Filters/EnumFilter.php index 0f3c70d..573632e 100644 --- a/src/Filters/EnumFilter.php +++ b/src/Filters/EnumFilter.php @@ -17,32 +17,21 @@ public function __construct($allowedValues) public function modifyQuery($query, SearchTerm $search) { + $terms = [$search->term()]; if ($search->operator() == 'nin' || $search->operator() == 'in') { $terms = explode(",", $search->term()); - foreach ($terms as $term) { - if (!in_array($term, $this->allowedValues())) { - throw new InvalidSearchTermException( - "{$search->property()} must be one of " . implode(", ", $this->allowedValues) - ); - } - } - } - - if ($search->operator() == 'eq') { - $query->where($search->column(), $search->term()); - } - - if ($search->operator() == 'neq') { - $query->where($search->column(), '!=', $search->term()); } - - if ($search->operator() == 'in') { - $query->whereIn($search->column(), explode(',', $search->term())); - } - - if ($search->operator() == 'nin') { - $query->whereNotIn($search->column(), explode(',', $search->term())); + foreach ($terms as $term) { + if (!in_array($term, $this->allowedValues())) { + $exception = new InvalidSearchTermException( + "{$search->property()} must be one of " . implode(", ", $this->allowedValues) + ); + $exception->allowedValues = $this->allowedValues; + throw $exception; + } } + + (new StringFilter)->modifyQuery($query, $search); } public function operators() diff --git a/tests/Filters/BooleanFilterTest.php b/tests/Filters/BooleanFilterTest.php new file mode 100644 index 0000000..8a6a4a0 --- /dev/null +++ b/tests/Filters/BooleanFilterTest.php @@ -0,0 +1,65 @@ +getQuery(); + $filter->modifyQuery($builder, $this->searchTerm('eq', true)); + + $this->assertEquals( + "select * from `pets` where `is_active` = ?", + $builder->toSql() + ); + $this->assertEquals([1], $builder->getBindings()); + } + + /** + * @test + */ + public function can_filter_neq() + { + $filter = new BooleanFilter; + $builder = Pet::query()->getQuery(); + $filter->modifyQuery($builder, $this->searchTerm('neq', true)); + + $this->assertEquals( + "select * from `pets` where `is_active` != ?", + $builder->toSql() + ); + $this->assertEquals([1], $builder->getBindings()); + } + + /** + * @test + */ + public function can_override_true_and_false_val() + { + + $filter = new BooleanFilter('yes', 'no'); + $builder = Pet::query()->getQuery(); + $filter->modifyQuery($builder, $this->searchTerm('eq', true)); + + $this->assertEquals( + "select * from `pets` where `is_active` = ?", + $builder->toSql() + ); + $this->assertEquals(['yes'], $builder->getBindings()); + } + + private function searchTerm($operator, $term) + { + return new SearchTerm('is_active', $operator, 'is_active', $term); + } +} \ No newline at end of file diff --git a/tests/Filters/DateFilterTest.php b/tests/Filters/DateFilterTest.php new file mode 100644 index 0000000..7649c59 --- /dev/null +++ b/tests/Filters/DateFilterTest.php @@ -0,0 +1,109 @@ +getQuery(); + (new DateFilter)->modifyQuery($query, $this->searchTerm('eq', '2020-01-01T00:00:00+00:00')); + + $this->assertEquals( + "select * from `pets` where `created_at` = ?", + $query->toSql(), + ); + } + + /** + * @test + */ + public function can_filter_by_neq() + { + $query = Pet::query()->getQuery(); + (new DateFilter)->modifyQuery($query, $this->searchTerm('neq', '2020-01-01T00:00:00+00:00')); + + $this->assertEquals( + "select * from `pets` where `created_at` != ?", + $query->toSql(), + ); + } + + /** + * @test + */ + public function can_filter_by_in() + { + $query = Pet::query()->getQuery(); + $dates = implode(',', [ + '2020-01-01T00:00:00+00:00', + '2020-01-01T00:00:00+00:00', + '2020-01-01T00:00:00+00:00', + ]); + (new DateFilter)->modifyQuery($query, $this->searchTerm('in', $dates)); + + $this->assertEquals( + "select * from `pets` where `created_at` in (?, ?, ?)", + $query->toSql(), + ); + } + + /** + * @test + */ + public function can_filter_by_nin() + { + $query = Pet::query()->getQuery(); + $dates = implode(',', [ + '2020-01-01T00:00:00+00:00', + '2020-01-01T00:00:00+00:00', + '2020-01-01T00:00:00+00:00', + ]); + (new DateFilter)->modifyQuery($query, $this->searchTerm('nin', $dates)); + + $this->assertEquals( + "select * from `pets` where `created_at` not in (?, ?, ?)", + $query->toSql(), + ); + } + + /** + * @test + */ + public function can_filter_by_lt() + { + $query = Pet::query()->getQuery(); + (new DateFilter)->modifyQuery($query, $this->searchTerm('lt', '2020-01-01T00:00:00+00:00')); + + $this->assertEquals( + "select * from `pets` where `created_at` < ?", + $query->toSql(), + ); + } + + /** + * @test + */ + public function can_filter_by_gt() + { + $query = Pet::query()->getQuery(); + (new DateFilter)->modifyQuery($query, $this->searchTerm('gt', '2020-01-01T00:00:00+00:00')); + + $this->assertEquals( + "select * from `pets` where `created_at` > ?", + $query->toSql(), + ); + } + + private function searchTerm($operator, $term) + { + return new SearchTerm('created_at', $operator, 'created_at', $term); + } +} \ No newline at end of file diff --git a/tests/Filters/EnumFilterTest.php b/tests/Filters/EnumFilterTest.php new file mode 100644 index 0000000..76c4b8a --- /dev/null +++ b/tests/Filters/EnumFilterTest.php @@ -0,0 +1,49 @@ +app->make(Builder::class); + + $this->expectException(InvalidSearchTermException::class); + + try { + $filter->modifyQuery($query, $search); + } catch (InvalidSearchTermException $e) { + $this->assertEquals(['a', 'b', 'c'], $e->allowedValues); + throw $e; + } + } + + /** + * @test + */ + public function can_search_if_passed_a_valid_term() + { + $filter = new EnumFilter(['a', 'b', 'c']); + $search = new SearchTerm('letter', 'eq', 'letter', 'a'); + $query = $this->app->make(Builder::class); + $query->from("pets"); + + $filter->modifyQuery($query, $search); + + $this->assertEquals( + 'select * from "pets" where "letter" = ?', + $query->toSql(), + ); + } +} diff --git a/tests/Filters/NumericFilterTest.php b/tests/Filters/NumericFilterTest.php new file mode 100644 index 0000000..f9149a7 --- /dev/null +++ b/tests/Filters/NumericFilterTest.php @@ -0,0 +1,75 @@ +modifyQuery($builder, $search); + $where = $builder->wheres[0]; + + $this->assertEquals('age', $where['column']); + $this->assertEquals('=', $where['operator']); + $this->assertEquals(1, $where['value']); + } + + /** + * @test + */ + public function correctly_applies_neq_operator() + { + $search = new SearchTerm('age', 'neq', 'age', 1); + $builder = app(Builder::class); + + (new NumericFilter)->modifyQuery($builder, $search); + $where = $builder->wheres[0]; + + $this->assertEquals('age', $where['column']); + $this->assertEquals('!=', $where['operator']); + $this->assertEquals(1, $where['value']); + } + + /** + * @test + */ + public function correctly_applies_in_operator() + { + $search = new SearchTerm('age', 'in', 'age', '1,2'); + $builder = app(Builder::class); + + (new NumericFilter)->modifyQuery($builder, $search); + $where = $builder->wheres[0]; + + $this->assertEquals('age', $where['column']); + $this->assertEquals('In', $where['type']); + $this->assertEquals([1, 2], $where['values']); + } + + /** + * @test + */ + public function correctly_applies_nin_operator() + { + $search = new SearchTerm('age', 'nin', 'age', '1,2'); + $builder = app(Builder::class); + + (new NumericFilter)->modifyQuery($builder, $search); + $where = $builder->wheres[0]; + + $this->assertEquals('age', $where['column']); + $this->assertEquals('NotIn', $where['type']); + $this->assertEquals([1, 2], $where['values']); + } +} \ No newline at end of file diff --git a/tests/Filters/StringFilterTest.php b/tests/Filters/StringFilterTest.php index d4a17ff..23df630 100644 --- a/tests/Filters/StringFilterTest.php +++ b/tests/Filters/StringFilterTest.php @@ -72,4 +72,17 @@ public function correctly_applies_nin_operator() $this->assertEquals('NotIn', $where['type']); $this->assertEquals(['Bob', 'James'], $where['values']); } + + /** + * @test + */ + public function correctly_applies_lk_operator() + { + $search = new SearchTerm('name', 'lk', 'name', '*t\\\\est\\*'); + $builder = app(Builder::class); + + (new StringFilter)->modifyQuery($builder, $search); + $this->assertEquals('select * where "name" LIKE ?', $builder->toSql()); + $this->assertEquals(['%t\est*'], $builder->getBindings()); + } } \ No newline at end of file