1
0
mirror of https://github.com/flarum/core.git synced 2025-08-04 23:47:32 +02:00

feat: revamp search (#3893)

* refactor: move gambits to frontend (#3885)
* refactor: move gambits to frontend
* test: GambitManager
* refactor: merge filterer and searcher concepts (#3892)
* chore: drop remaining backend regex gambits
* refactor: merge filterer & searcher concept
* refactor: adapt extenders
* refactor: no longer need to push gambits to `q`
* refactor: filters to gambits
* refactor: drop shred `Query` namespace
* chore: cleanup
* chore: leftover gambit references on the backend (#3894)
* chore: leftover gambit references on the backend
* chore: namespace
* feat: search driver backend extension API (#3902)
* feat: first iteration of search drivers
* feat: indexer API & tweaks
* feat: changes after POC driver
* fix: properly fire custom observables
* chore: remove debugging code
* fix: phpstan
* fix: custom eloquent events
* chore: drop POC usage
* test: indexer extender API
* fix: extension searcher fails without filters
* fix: phpstan
* fix: frontend created gambit
* feat: advanced page and localized driver settings (#3905)
* feat: allow getting total search results and replacing filters (#3906)
* feat: allow accessing total search results
* feat: allow replacing filters
* chore: phpstan
This commit is contained in:
Sami Mazouz
2023-11-11 19:43:09 +01:00
committed by GitHub
parent 9e04b010d8
commit 4b126d9f4c
161 changed files with 2734 additions and 2197 deletions

View File

@@ -30,9 +30,9 @@ class AbstractSerializeControllerTest extends TestCase
$this->request('GET', '/api/dummy-serialize')
);
$json = json_decode((string) $response->getBody(), true);
$json = json_decode($contents = (string) $response->getBody(), true);
$this->assertEquals(500, $response->getStatusCode());
$this->assertEquals(500, $response->getStatusCode(), $contents);
$this->assertStringStartsWith('InvalidArgumentException: Serializer required for controller: '.DummySerializeController::class, $json['errors'][0]['detail']);
}
}

View File

@@ -115,7 +115,7 @@ class DeleteTest extends TestCase
$response = $this->send($request);
$this->assertEquals(403, $response->getStatusCode());
$this->assertEquals(403, $response->getStatusCode(), $response->getBody()->getContents());
}
/**

View File

@@ -62,11 +62,11 @@ class ListTest extends TestCase
$request = $this->request('GET', '/api/access-tokens', compact('authenticatedAs'))
);
$data = Arr::get(json_decode($response->getBody()->getContents(), true), 'data');
$data = Arr::get(json_decode($contents = $response->getBody()->getContents(), true), 'data');
$testsTokenId = AccessToken::findValid($request->getAttribute('tests_token'))->id;
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals(200, $response->getStatusCode(), $contents);
$this->assertEqualsCanonicalizing(array_merge($canViewIds, [$testsTokenId]), Arr::pluck($data, 'id'));
}
@@ -112,14 +112,14 @@ class ListTest extends TestCase
])
);
$data = Arr::get(json_decode($response->getBody()->getContents(), true), 'data');
$data = Arr::get(json_decode($contents = $response->getBody()->getContents(), true), 'data');
$testsTokenId = AccessToken::findValid($request->getAttribute('tests_token'))->id;
if ($authenticatedAs === $userId) {
$canViewIds[] = $testsTokenId;
}
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals(200, $response->getStatusCode(), $contents);
$this->assertEqualsCanonicalizing($canViewIds, Arr::pluck($data, 'id'));
}

View File

@@ -265,200 +265,4 @@ class ListTest extends TestCase
// Order-independent comparison
$this->assertEqualsCanonicalizing(['1', '2'], Arr::pluck($data, 'id'), 'IDs do not match');
}
/**
* @test
*/
public function author_gambit_works()
{
$response = $this->send(
$this->request('GET', '/api/discussions')
->withQueryParams([
'filter' => ['q' => 'author:normal'],
'include' => 'mostRelevantPost',
])
);
$data = json_decode($response->getBody()->getContents(), true)['data'];
// Order-independent comparison
$this->assertEqualsCanonicalizing(['2', '3'], Arr::pluck($data, 'id'), 'IDs do not match');
}
/**
* @test
*/
public function author_gambit_works_negated()
{
$response = $this->send(
$this->request('GET', '/api/discussions')
->withQueryParams([
'filter' => ['q' => '-author:normal'],
'include' => 'mostRelevantPost',
])
);
$data = json_decode($response->getBody()->getContents(), true)['data'];
// Order-independent comparison
$this->assertEquals(['1'], Arr::pluck($data, 'id'), 'IDs do not match');
}
/**
* @test
*/
public function created_gambit_works_with_date()
{
$response = $this->send(
$this->request('GET', '/api/discussions')
->withQueryParams([
'filter' => ['q' => 'created:1995-05-21'],
'include' => 'mostRelevantPost',
])
);
$data = json_decode($response->getBody()->getContents(), true)['data'];
// Order-independent comparison
$this->assertEquals(['3'], Arr::pluck($data, 'id'), 'IDs do not match');
}
/**
* @test
*/
public function created_gambit_works_negated_with_date()
{
$response = $this->send(
$this->request('GET', '/api/discussions')
->withQueryParams([
'filter' => ['q' => '-created:1995-05-21'],
'include' => 'mostRelevantPost',
])
);
$data = json_decode($response->getBody()->getContents(), true)['data'];
// Order-independent comparison
$this->assertEqualsCanonicalizing(['1', '2'], Arr::pluck($data, 'id'), 'IDs do not match');
}
/**
* @test
*/
public function created_gambit_works_with_range()
{
$response = $this->send(
$this->request('GET', '/api/discussions')
->withQueryParams([
'filter' => ['q' => 'created:1980-05-21..2000-05-21'],
'include' => 'mostRelevantPost',
])
);
$data = json_decode($response->getBody()->getContents(), true)['data'];
// Order-independent comparison
$this->assertEqualsCanonicalizing(['2', '3'], Arr::pluck($data, 'id'), 'IDs do not match');
}
/**
* @test
*/
public function created_gambit_works_negated_with_range()
{
$response = $this->send(
$this->request('GET', '/api/discussions')
->withQueryParams([
'filter' => ['q' => '-created:1980-05-21..2000-05-21'],
'include' => 'mostRelevantPost',
])
);
$data = json_decode($response->getBody()->getContents(), true)['data'];
// Order-independent comparison
$this->assertEquals(['1'], Arr::pluck($data, 'id'), 'IDs do not match');
}
/**
* @test
*/
public function hidden_gambit_works()
{
$response = $this->send(
$this->request('GET', '/api/discussions', ['authenticatedAs' => 1])
->withQueryParams([
'filter' => ['q' => 'is:hidden'],
'include' => 'mostRelevantPost',
])
);
$data = json_decode($response->getBody()->getContents(), true)['data'];
// Order-independent comparison
$this->assertEquals(['4'], Arr::pluck($data, 'id'), 'IDs do not match');
}
/**
* @test
*/
public function hidden_gambit_works_negated()
{
$response = $this->send(
$this->request('GET', '/api/discussions', ['authenticatedAs' => 1])
->withQueryParams([
'filter' => ['q' => '-is:hidden'],
'include' => 'mostRelevantPost',
])
);
$data = json_decode($response->getBody()->getContents(), true)['data'];
// Order-independent comparison
$this->assertEqualsCanonicalizing(['1', '2', '3'], Arr::pluck($data, 'id'), 'IDs do not match');
}
/**
* @test
*/
public function unread_gambit_works()
{
$this->app();
$this->read();
$response = $this->send(
$this->request('GET', '/api/discussions', ['authenticatedAs' => 2])
->withQueryParams([
'filter' => ['q' => 'is:unread'],
'include' => 'mostRelevantPost',
])
);
$data = json_decode($response->getBody()->getContents(), true)['data'];
// Order-independent comparison
$this->assertEquals(['3'], Arr::pluck($data, 'id'), 'IDs do not match');
}
/**
* @test
*/
public function unread_gambit_works_when_negated()
{
$this->app();
$this->read();
$response = $this->send(
$this->request('GET', '/api/discussions', ['authenticatedAs' => 2])
->withQueryParams([
'filter' => ['q' => '-is:unread'],
'include' => 'mostRelevantPost',
])
);
$data = json_decode($response->getBody()->getContents(), true)['data'];
// Order-independent comparison
$this->assertEqualsCanonicalizing(['1', '2'], Arr::pluck($data, 'id'), 'IDs do not match');
}
}

View File

@@ -14,7 +14,7 @@ use Flarum\Testing\integration\RetrievesAuthorizedUsers;
use Flarum\Testing\integration\TestCase;
use Illuminate\Support\Arr;
class ListTests extends TestCase
class ListTest extends TestCase
{
use RetrievesAuthorizedUsers;

View File

@@ -236,7 +236,7 @@ class GroupSearchTest extends TestCase
return $this->send(
$this->request('GET', '/api/users', $auth)
->withQueryParams(['filter' => ['q' => 'group:'.implode(',', $group)]])
->withQueryParams(['filter' => ['group' => implode(',', $group)]])
);
}

View File

@@ -223,94 +223,4 @@ class ListTest extends TestCase
$data = json_decode($response->getBody()->getContents(), true)['data'];
$this->assertEquals(['1', '2'], Arr::pluck($data, 'id'));
}
/**
* @test
*/
public function group_gambit_works()
{
$response = $this->send(
$this->request('GET', '/api/users', [
'authenticatedAs' => 1,
])->withQueryParams([
'filter' => ['q' => 'group:1'],
])
);
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getBody()->getContents(), true)['data'];
$this->assertEquals(['1'], Arr::pluck($data, 'id'));
}
/**
* @test
*/
public function group_gambit_works_negated()
{
$response = $this->send(
$this->request('GET', '/api/users', [
'authenticatedAs' => 1,
])->withQueryParams([
'filter' => ['q' => '-group:1'],
])
);
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getBody()->getContents(), true)['data'];
$this->assertEquals(['2'], Arr::pluck($data, 'id'));
}
/**
* @test
*/
public function email_gambit_works()
{
$response = $this->send(
$this->request('GET', '/api/users', [
'authenticatedAs' => 1,
])->withQueryParams([
'filter' => ['q' => 'email:admin@machine.local'],
])
);
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getBody()->getContents(), true)['data'];
$this->assertEquals(['1'], Arr::pluck($data, 'id'));
}
/**
* @test
*/
public function email_gambit_works_negated()
{
$response = $this->send(
$this->request('GET', '/api/users', [
'authenticatedAs' => 1,
])->withQueryParams([
'filter' => ['q' => '-email:admin@machine.local'],
])
);
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getBody()->getContents(), true)['data'];
$this->assertEquals(['2'], Arr::pluck($data, 'id'));
}
/**
* @test
*/
public function email_gambit_only_works_for_admin()
{
$response = $this->send(
$this->request('GET', '/api/users', [
'authenticatedAs' => 2,
])->withQueryParams([
'filter' => ['q' => 'email:admin@machine.local'],
])
);
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getBody()->getContents(), true)['data'];
$this->assertEquals([], Arr::pluck($data, 'id'));
}
}

View File

@@ -1,131 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Tests\integration\extenders;
use Carbon\Carbon;
use Flarum\Discussion\Filter\DiscussionFilterer;
use Flarum\Extend;
use Flarum\Filter\FilterInterface;
use Flarum\Filter\FilterState;
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
use Flarum\Testing\integration\TestCase;
class FilterTest extends TestCase
{
use RetrievesAuthorizedUsers;
public function prepDb()
{
$this->prepareDatabase([
'discussions' => [
['id' => 1, 'title' => 'DISCUSSION 1', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'first_post_id' => 1, 'comment_count' => 1],
['id' => 2, 'title' => 'DISCUSSION 2', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'first_post_id' => 2, 'comment_count' => 1],
],
'posts' => [
['id' => 1, 'discussion_id' => 1, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'type' => 'comment', 'content' => '<t><p>foo bar</p></t>'],
['id' => 2, 'discussion_id' => 2, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'type' => 'comment', 'content' => '<t><p>foo bar not the same</p></t>'],
],
'users' => [
$this->normalUser(),
],
]);
}
public function filterDiscussions($filters, $limit = null)
{
$response = $this->send(
$this->request('GET', '/api/discussions', [
'authenticatedAs' => 1,
])->withQueryParams([
'filter' => $filters,
'include' => 'mostRelevantPost',
])
);
return json_decode($response->getBody()->getContents(), true)['data'];
}
/**
* @test
*/
public function works_as_expected_with_no_modifications()
{
$this->prepDb();
$searchForAll = json_encode($this->filterDiscussions([], 5));
$this->assertStringContainsString('DISCUSSION 1', $searchForAll);
$this->assertStringContainsString('DISCUSSION 2', $searchForAll);
}
/**
* @test
*/
public function custom_filter_has_effect_if_added()
{
$this->extend((new Extend\Filter(DiscussionFilterer::class))->addFilter(NoResultFilter::class));
$this->prepDb();
$withResultSearch = json_encode($this->filterDiscussions(['noResult' => 0], 5));
$this->assertStringContainsString('DISCUSSION 1', $withResultSearch);
$this->assertStringContainsString('DISCUSSION 2', $withResultSearch);
$this->assertEquals([], $this->filterDiscussions(['noResult' => 1], 5));
}
/**
* @test
*/
public function filter_mutator_has_effect_if_added()
{
$this->extend((new Extend\Filter(DiscussionFilterer::class))->addFilterMutator(function ($filterState, $criteria) {
$filterState->getQuery()->whereRaw('1=0');
}));
$this->prepDb();
$this->assertEquals([], $this->filterDiscussions([], 5));
}
/**
* @test
*/
public function filter_mutator_has_effect_if_added_with_invokable_class()
{
$this->extend((new Extend\Filter(DiscussionFilterer::class))->addFilterMutator(CustomFilterMutator::class));
$this->prepDb();
$this->assertEquals([], $this->filterDiscussions([], 5));
}
}
class NoResultFilter implements FilterInterface
{
public function getFilterKey(): string
{
return 'noResult';
}
public function filter(FilterState $filterState, string|array $filterValue, bool $negate): void
{
if ($filterValue) {
$filterState->getQuery()
->whereRaw('0=1');
}
}
}
class CustomFilterMutator
{
public function __invoke($filterState, $criteria)
{
$filterState->getQuery()->whereRaw('1=0');
}
}

View File

@@ -10,21 +10,22 @@
namespace Flarum\Tests\integration\extenders;
use Carbon\Carbon;
use Flarum\Discussion\Discussion;
use Flarum\Discussion\Search\DiscussionSearcher;
use Flarum\Discussion\Search\Filter\UnreadFilter;
use Flarum\Extend;
use Flarum\Group\Group;
use Flarum\Query\QueryCriteria;
use Flarum\Search\AbstractRegexGambit;
use Flarum\Search\AbstractSearcher;
use Flarum\Search\GambitInterface;
use Flarum\Search\AbstractFulltextFilter;
use Flarum\Search\Database\DatabaseSearchDriver;
use Flarum\Search\Database\DatabaseSearchState;
use Flarum\Search\Filter\FilterInterface;
use Flarum\Search\SearchCriteria;
use Flarum\Search\SearchManager;
use Flarum\Search\SearchState;
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
use Flarum\Testing\integration\TestCase;
use Flarum\User\User;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Database\Eloquent\Builder;
class SimpleFlarumSearchTest extends TestCase
class SearchDriverTest extends TestCase
{
use RetrievesAuthorizedUsers;
@@ -62,15 +63,19 @@ class SimpleFlarumSearchTest extends TestCase
$this->database()->table('posts')->whereIn('id', [1, 2])->delete();
}
public function searchDiscussions($query, $limit = null)
public function searchDiscussions($query, $limit = null, array $filters = [])
{
$this->app();
$actor = User::find(1);
$criteria = new QueryCriteria($actor, ['q' => $query]);
$filters['q'] = $query;
return $this->app()->getContainer()->make(DiscussionSearcher::class)->search($criteria, $limit)->getResults();
return $this->app()
->getContainer()
->make(SearchManager::class)
->query(Discussion::class, new SearchCriteria($actor, $filters, $limit))
->getResults();
}
/**
@@ -94,7 +99,10 @@ class SimpleFlarumSearchTest extends TestCase
*/
public function custom_full_text_gambit_has_effect_if_added()
{
$this->extend((new Extend\SimpleFlarumSearch(DiscussionSearcher::class))->setFullTextGambit(NoResultFullTextGambit::class));
$this->extend(
(new Extend\SearchDriver(DatabaseSearchDriver::class))
->setFulltext(DiscussionSearcher::class, NoResultFullTextFilter::class)
);
$this->assertEquals('[]', json_encode($this->searchDiscussions('in text', 5)));
}
@@ -102,16 +110,36 @@ class SimpleFlarumSearchTest extends TestCase
/**
* @test
*/
public function custom_filter_gambit_has_effect_if_added()
public function custom_filter_has_effect_if_added()
{
$this->extend((new Extend\SimpleFlarumSearch(DiscussionSearcher::class))->addGambit(NoResultFilterGambit::class));
$this->extend(
(new Extend\SearchDriver(DatabaseSearchDriver::class))
->addFilter(DiscussionSearcher::class, NoResultFilter::class)
);
$this->prepDb();
$withResultSearch = json_encode($this->searchDiscussions('noResult:0', 5));
$withResultSearch = json_encode($this->searchDiscussions('', 5, ['noResult' => '0']));
$this->assertStringContainsString('DISCUSSION 1', $withResultSearch);
$this->assertStringContainsString('DISCUSSION 2', $withResultSearch);
$this->assertEquals('[]', json_encode($this->searchDiscussions('noResult:1', 5)));
$this->assertEquals('[]', json_encode($this->searchDiscussions('', 5, ['noResult' => '1'])));
}
/**
* @test
*/
public function existing_filter_can_be_replaced()
{
$this->extend(
(new Extend\SearchDriver(DatabaseSearchDriver::class))
->replaceFilter(DiscussionSearcher::class, UnreadFilter::class, NoResultFilter::class)
);
$this->prepDb();
$this->assertNotContains(UnreadFilter::class, $this->app()->getContainer()->make('flarum.search.filters')[DiscussionSearcher::class]);
$this->assertContains(NoResultFilter::class, $this->app()->getContainer()->make('flarum.search.filters')[DiscussionSearcher::class]);
$this->assertEquals('[]', json_encode($this->searchDiscussions('', 5, ['noResult' => '1'])));
}
/**
@@ -119,9 +147,12 @@ class SimpleFlarumSearchTest extends TestCase
*/
public function search_mutator_has_effect_if_added()
{
$this->extend((new Extend\SimpleFlarumSearch(DiscussionSearcher::class))->addSearchMutator(function ($search, $criteria) {
$search->getquery()->whereRaw('1=0');
}));
$this->extend(
(new Extend\SearchDriver(DatabaseSearchDriver::class))
->addMutator(DiscussionSearcher::class, function (DatabaseSearchState $search) {
$search->getQuery()->whereRaw('1=0');
})
);
$this->prepDb();
@@ -133,68 +164,41 @@ class SimpleFlarumSearchTest extends TestCase
*/
public function search_mutator_has_effect_if_added_with_invokable_class()
{
$this->extend((new Extend\SimpleFlarumSearch(DiscussionSearcher::class))->addSearchMutator(CustomSearchMutator::class));
$this->extend(
(new Extend\SearchDriver(DatabaseSearchDriver::class))
->addMutator(DiscussionSearcher::class, CustomSearchMutator::class)
);
$this->prepDb();
$this->assertEquals('[]', json_encode($this->searchDiscussions('in text', 5)));
}
}
/**
* @test
*/
public function cant_resolve_custom_searcher_without_fulltext_gambit()
class NoResultFullTextFilter extends AbstractFulltextFilter
{
public function search(SearchState $state, string $value): void
{
$this->expectException(BindingResolutionException::class);
$this->app()->getContainer()->make(CustomSearcher::class);
}
/**
* @test
*/
public function can_resolve_custom_searcher_with_fulltext_gambit()
{
$this->extend(
(new Extend\SimpleFlarumSearch(CustomSearcher::class))->setFullTextGambit(CustomFullTextGambit::class)
);
$anExceptionWasThrown = false;
try {
$this->app()->getContainer()->make(CustomSearcher::class);
} catch (BindingResolutionException) {
$anExceptionWasThrown = true;
}
$this->assertFalse($anExceptionWasThrown);
$state->getQuery()->whereRaw('0=1');
}
}
class NoResultFullTextGambit implements GambitInterface
/**
* @implements FilterInterface<DatabaseSearchState>
*/
class NoResultFilter implements FilterInterface
{
public function apply(SearchState $search, string $bit): bool
public function getFilterKey(): string
{
$search->getQuery()
->whereRaw('0=1');
return true;
}
}
class NoResultFilterGambit extends AbstractRegexGambit
{
public function getGambitPattern(): string
{
return 'noResult:(.+)';
return 'noResult';
}
public function conditions(SearchState $search, array $matches, bool $negate): void
public function filter(SearchState $state, array|string $value, bool $negate): void
{
$noResults = trim($matches[1], ' ');
$noResults = trim($value, ' ');
if ($noResults == '1') {
$search->getQuery()
->whereRaw('0=1');
$state->getQuery()->whereRaw('0=1');
}
}
}
@@ -206,20 +210,3 @@ class CustomSearchMutator
$search->getQuery()->whereRaw('1=0');
}
}
class CustomSearcher extends AbstractSearcher
{
// This isn't actually used, we just need it to implement the abstract method.
protected function getQuery(User $actor): Builder
{
return Group::query();
}
}
class CustomFullTextGambit implements GambitInterface
{
public function apply(SearchState $search, string $bit): bool
{
return true;
}
}

View File

@@ -0,0 +1,212 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Tests\integration\extenders;
use Carbon\Carbon;
use Flarum\Discussion\Discussion;
use Flarum\Extend;
use Flarum\Post\CommentPost;
use Flarum\Search\IndexerInterface;
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
use Flarum\Testing\integration\TestCase;
use PHPUnit\Framework\Assert;
class SearchIndexTest extends TestCase
{
use RetrievesAuthorizedUsers;
protected function setUp(): void
{
parent::setUp();
$this->prepareDatabase([
'discussions' => [
['id' => 1, 'title' => 'DISCUSSION 1', 'created_at' => Carbon::now()->subDays(1)->toDateTimeString(), 'hidden_at' => null, 'comment_count' => 1, 'user_id' => 1, 'first_post_id' => 1],
['id' => 2, 'title' => 'DISCUSSION 2', 'created_at' => Carbon::now()->subDays(2)->toDateTimeString(), 'hidden_at' => Carbon::now(), 'comment_count' => 1, 'user_id' => 1],
],
'posts' => [
['id' => 1, 'number' => 1, 'discussion_id' => 1, 'created_at' => Carbon::now(), 'user_id' => 1, 'type' => 'comment', 'content' => '<r><p>content</p></r>', 'hidden_at' => null],
['id' => 2, 'number' => 2, 'discussion_id' => 1, 'created_at' => Carbon::now(), 'user_id' => 1, 'type' => 'comment', 'content' => '<r><p>content</p></r>', 'hidden_at' => Carbon::now()],
],
]);
}
public static function modelProvider(): array
{
return [
['discussions', Discussion::class, 'title'],
['posts', CommentPost::class, 'content'],
];
}
/** @dataProvider modelProvider */
public function test_indexer_triggered_on_create(string $type, string $modelClass, string $attribute)
{
$this->extend(
(new Extend\SearchIndex())
->indexer($modelClass, TestIndexer::class)
);
// Create discussion
$response = $this->send(
$this->request('POST', "/api/$type", [
'authenticatedAs' => 1,
'json' => [
'data' => [
'attributes' => [
$attribute => 'test',
],
'relationships' => ($type === 'posts' ? [
'discussion' => [
'data' => [
'type' => 'discussions',
'id' => 1,
],
],
] : null),
]
],
]),
);
Assert::assertEquals('save', TestIndexer::$triggered, $response->getBody()->getContents());
}
/** @dataProvider modelProvider */
public function test_indexer_triggered_on_save(string $type, string $modelClass, string $attribute)
{
$this->extend(
(new Extend\SearchIndex())
->indexer($modelClass, TestIndexer::class)
);
// Rename discussion
$response = $this->send(
$this->request('PATCH', "/api/$type/1", [
'authenticatedAs' => 1,
'json' => [
'data' => [
'attributes' => [
$attribute => 'changed'
]
]
],
]),
);
Assert::assertEquals('save', TestIndexer::$triggered, $response->getBody()->getContents());
}
/** @dataProvider modelProvider */
public function test_indexer_triggered_on_delete(string $type, string $modelClass, string $attribute)
{
$this->extend(
(new Extend\SearchIndex())
->indexer($modelClass, TestIndexer::class)
);
// Delete discussion
$response = $this->send(
$this->request('DELETE', "/api/$type/1", [
'authenticatedAs' => 1,
'json' => [],
]),
);
Assert::assertEquals('delete', TestIndexer::$triggered, $response->getBody()->getContents());
}
/** @dataProvider modelProvider */
public function test_indexer_triggered_on_hide(string $type, string $modelClass, string $attribute)
{
$this->extend(
(new Extend\SearchIndex())
->indexer($modelClass, TestIndexer::class)
);
// Hide discussion
$response = $this->send(
$this->request('PATCH', "/api/$type/1", [
'authenticatedAs' => 1,
'json' => [
'data' => [
'attributes' => [
'isHidden' => true
]
]
],
]),
);
Assert::assertEquals('delete', TestIndexer::$triggered, $response->getBody()->getContents());
}
/** @dataProvider modelProvider */
public function test_indexer_triggered_on_restore(string $type, string $modelClass, string $attribute)
{
$this->extend(
(new Extend\SearchIndex())
->indexer($modelClass, TestIndexer::class)
);
// Restore discussion
$response = $this->send(
$this->request('PATCH', "/api/$type/2", [
'authenticatedAs' => 1,
'json' => [
'data' => [
'attributes' => [
'isHidden' => false
]
]
],
]),
);
Assert::assertEquals('save', TestIndexer::$triggered, $response->getBody()->getContents());
}
protected function tearDown(): void
{
TestIndexer::$triggered = null;
parent::tearDown();
}
}
class TestIndexer implements IndexerInterface
{
public static ?string $triggered = null;
public static function index(): string
{
return 'test';
}
public function save(array $models): void
{
self::$triggered = 'save';
}
public function delete(array $models): void
{
self::$triggered = 'delete';
}
public function build(): void
{
//
}
public function flush(): void
{
//
}
}