From 74b4e08621d00abd6cf00702f0caa2284c42f391 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 12 Nov 2024 09:38:26 +0800 Subject: [PATCH 01/32] [10.x] Supports PHP 8.4 Signed-off-by: Mior Muhammad Zaki --- .github/workflows/static-analysis.yml | 2 +- .github/workflows/tests.yml | 23 +++++++++++++---------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 3581927c..cb2e5865 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -25,7 +25,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.2 + php-version: 8.3 tools: composer:v2 coverage: none diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b9922876..eb904412 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,17 +16,20 @@ jobs: strategy: fail-fast: true matrix: - php: ['8.0', 8.1, 8.2, 8.3] - laravel: [9, 10, 11] - exclude: - - php: '8.0' - laravel: 10 - - php: '8.0' + php: [8.1, 8.2, 8.3] + laravel: [10, 11] + include: + - php: 8.4 laravel: 11 - - php: '8.1' - laravel: 11 - - php: 8.3 + - php: 8.2 + laravel: 9 + - php: 8.1 laravel: 9 + - php: '8.0' + laravel: 9 + exclude: + - php: 8.1 + laravel: 11 name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} @@ -65,7 +68,7 @@ jobs: strategy: fail-fast: true matrix: - php: ['8.0', 8.1, 8.2, 8.3] + php: ['8.0', 8.1, 8.2, 8.3, 8.4] name: PHP ${{ matrix.php }} using Meilisearch From 3415d998d3977e44a81900eddd2399f11c5eab82 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 12 Nov 2024 10:29:24 +0800 Subject: [PATCH 02/32] wip Signed-off-by: Mior Muhammad Zaki --- tests/Feature/SearchableTest.php | 44 ++++++++++++++++-------------- tests/Unit/SearchableScopeTest.php | 11 ++++---- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/tests/Feature/SearchableTest.php b/tests/Feature/SearchableTest.php index 638c93a7..b33edd7e 100644 --- a/tests/Feature/SearchableTest.php +++ b/tests/Feature/SearchableTest.php @@ -19,9 +19,9 @@ class SearchableTest extends TestCase public function test_searchable_using_update_is_called_on_collection() { $collection = m::mock(); - $collection->shouldReceive('isEmpty')->andReturn(false); - $collection->shouldReceive('first->makeSearchableUsing')->with($collection)->andReturn($collection); - $collection->shouldReceive('first->searchableUsing->update')->with($collection); + $collection->shouldReceive('isEmpty')->once()->andReturn(false); + $collection->shouldReceive('first->makeSearchableUsing')->with($collection)->once()->andReturn($collection); + $collection->shouldReceive('first->searchableUsing->update')->with($collection)->once(); $model = new SearchableModel(); $model->queueMakeSearchable($collection); @@ -45,7 +45,7 @@ public function test_overridden_make_searchable_is_dispatched() Scout::makeSearchableUsing(OverriddenMakeSearchable::class); $collection = m::mock(); - $collection->shouldReceive('isEmpty')->andReturn(false); + $collection->shouldReceive('isEmpty')->once()->andReturn(false); $collection->shouldReceive('first->syncWithSearchUsingQueue'); $collection->shouldReceive('first->syncWithSearchUsing'); @@ -58,7 +58,7 @@ public function test_overridden_make_searchable_is_dispatched() public function test_searchable_using_delete_is_called_on_collection() { $collection = m::mock(); - $collection->shouldReceive('isEmpty')->andReturn(false); + $collection->shouldReceive('isEmpty')->once()->andReturn(false); $collection->shouldReceive('first->searchableUsing->delete')->with($collection); $model = new SearchableModel; @@ -68,7 +68,7 @@ public function test_searchable_using_delete_is_called_on_collection() public function test_searchable_using_delete_is_not_called_on_empty_collection() { $collection = m::mock(); - $collection->shouldReceive('isEmpty')->andReturn(true); + $collection->shouldReceive('isEmpty')->once()->andReturn(true); $collection->shouldNotReceive('first->searchableUsing->delete'); $model = new SearchableModel; @@ -83,7 +83,7 @@ public function test_overridden_remove_from_search_is_dispatched() Scout::removeFromSearchUsing(OverriddenRemoveFromSearch::class); $collection = m::mock(); - $collection->shouldReceive('isEmpty')->andReturn(false); + $collection->shouldReceive('isEmpty')->once()->andReturn(false); $collection->shouldReceive('first->syncWithSearchUsingQueue'); $collection->shouldReceive('first->syncWithSearchUsing'); @@ -105,11 +105,11 @@ public function test_was_searchable_on_model_without_soft_deletes() public function test_it_queries_searchable_models_by_their_ids_with_integer_key_type() { $model = M::mock(SearchableModel::class)->makePartial(); - $model->shouldReceive('newQuery')->andReturnSelf(); - $model->shouldReceive('getScoutKeyType')->andReturn('int'); - $model->shouldReceive('getScoutKeyName')->andReturn('id'); - $model->shouldReceive('qualifyColumn')->with('id')->andReturn('qualified_id'); - $model->shouldReceive('whereIntegerInRaw')->with('qualified_id', [1, 2, 3])->andReturnSelf(); + $model->shouldReceive('newQuery')->once()->andReturnSelf(); + $model->shouldReceive('getScoutKeyType')->once()->andReturn('int'); + $model->shouldReceive('getScoutKeyName')->once()->andReturn('id'); + $model->shouldReceive('qualifyColumn')->with('id')->once()->andReturn('qualified_id'); + $model->shouldReceive('whereIntegerInRaw')->with('qualified_id', [1, 2, 3])->once()->andReturnSelf(); $scoutBuilder = M::mock(\Laravel\Scout\Builder::class); $scoutBuilder->queryCallback = null; @@ -120,11 +120,11 @@ public function test_it_queries_searchable_models_by_their_ids_with_integer_key_ public function test_it_queries_searchable_models_by_their_ids_with_string_key_type() { $model = M::mock(SearchableModel::class)->makePartial(); - $model->shouldReceive('newQuery')->andReturnSelf(); - $model->shouldReceive('getScoutKeyType')->andReturn('string'); - $model->shouldReceive('getScoutKeyName')->andReturn('id'); - $model->shouldReceive('qualifyColumn')->with('id')->andReturn('qualified_id'); - $model->shouldReceive('whereIn')->with('qualified_id', [1, 2, 3])->andReturnSelf(); + $model->shouldReceive('newQuery')->once()->andReturnSelf(); + $model->shouldReceive('getScoutKeyType')->once()->andReturn('string'); + $model->shouldReceive('getScoutKeyName')->once()->andReturn('id'); + $model->shouldReceive('qualifyColumn')->with('id')->once()->andReturn('qualified_id'); + $model->shouldReceive('whereIn')->with('qualified_id', [1, 2, 3])->once()->andReturnSelf(); $scoutBuilder = M::mock(\Laravel\Scout\Builder::class); $scoutBuilder->queryCallback = null; @@ -169,10 +169,11 @@ class ModelStubForMakeAllSearchable extends SearchableModel { public function newQuery() { - $mock = m::mock(Builder::class); + $mock = m::spy(Builder::class); $mock->shouldReceive('when') ->with(true, m::type('Closure')) + ->once() ->andReturnUsing(function ($condition, $callback) use ($mock) { $callback($mock); @@ -181,8 +182,11 @@ public function newQuery() $mock->shouldReceive('orderBy') ->with('model_stub_for_make_all_searchables.id') - ->andReturnSelf() - ->shouldReceive('searchable'); + ->once() + ->andReturnSelf(); + + $mock->shouldReceive('searchable') + ->once(); $mock->shouldReceive('when')->andReturnSelf(); diff --git a/tests/Unit/SearchableScopeTest.php b/tests/Unit/SearchableScopeTest.php index 0202b738..42eabb7c 100644 --- a/tests/Unit/SearchableScopeTest.php +++ b/tests/Unit/SearchableScopeTest.php @@ -16,32 +16,33 @@ protected function tearDown(): void public function test_chunks_by_id() { - $builder = m::mock(Builder::class); + $builder = m::spy(Builder::class); + $builder->shouldReceive('macro')->with('searchable', m::on(function ($callback) use ($builder) { $model = m::mock(Model::class); $model->shouldReceive('getScoutKeyName')->once()->andReturn('id'); - $builder->shouldReceive('chunkById')->with(500, m::type(\Closure::class), 'users.id', 'id'); + $builder->shouldReceive('chunkById')->with(500, m::type(\Closure::class), 'users.id', 'id')->once(); $builder->shouldReceive('getModel')->once()->andReturn($model); $builder->shouldReceive('qualifyColumn')->once()->andReturn('users.id'); $callback($builder, 500); return true; - })); + }))->once(); $builder->shouldReceive('macro')->with('unsearchable', m::on(function ($callback) use ($builder) { $model = m::mock(Model::class); $model->shouldReceive('getScoutKeyName')->once()->andReturn('id'); - $builder->shouldReceive('chunkById')->with(500, m::type(\Closure::class), 'users.id', 'id'); + $builder->shouldReceive('chunkById')->with(500, m::type(\Closure::class), 'users.id', 'id')->once(); $builder->shouldReceive('getModel')->once()->andReturn($model); $builder->shouldReceive('qualifyColumn')->once()->andReturn('users.id'); $callback($builder, 500); return true; - })); + }))->once(); (new SearchableScope())->extend($builder); } From f6125886500b95f7b4abb4b1bc58ab2dfef5b600 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 12 Nov 2024 11:18:36 +0800 Subject: [PATCH 03/32] wip Signed-off-by: Mior Muhammad Zaki --- composer.json | 4 +- testbench.yaml | 4 +- .../Jobs/RemovableScoutCollectionTest.php | 28 ++++++ tests/Feature/Jobs/RemoveFromSearchTest.php | 69 ++++++++++++++ tests/Unit/RemoveFromSearchTest.php | 89 ------------------- workbench/app/Models/Chirp.php | 34 +++++++ workbench/app/Models/User.php | 42 +++++++++ .../Providers/WorkbenchServiceProvider.php | 36 ++++++++ workbench/database/factories/ChirpFactory.php | 33 +++++++ workbench/database/factories/UserFactory.php | 54 +++++++++++ .../2024_11_12_030345_create_chirps_table.php | 29 ++++++ 11 files changed, 331 insertions(+), 91 deletions(-) create mode 100644 tests/Feature/Jobs/RemovableScoutCollectionTest.php create mode 100644 tests/Feature/Jobs/RemoveFromSearchTest.php delete mode 100644 tests/Unit/RemoveFromSearchTest.php create mode 100644 workbench/app/Models/Chirp.php create mode 100644 workbench/app/Models/User.php create mode 100644 workbench/app/Providers/WorkbenchServiceProvider.php create mode 100644 workbench/database/factories/ChirpFactory.php create mode 100644 workbench/database/factories/UserFactory.php create mode 100644 workbench/database/migrations/2024_11_12_030345_create_chirps_table.php diff --git a/composer.json b/composer.json index bcc2cec0..5f5feb53 100644 --- a/composer.json +++ b/composer.json @@ -44,7 +44,9 @@ }, "autoload-dev": { "psr-4": { - "Laravel\\Scout\\Tests\\": "tests/" + "Laravel\\Scout\\Tests\\": "tests/", + "Workbench\\App\\": "workbench/app/", + "Workbench\\Database\\Factories\\": "workbench/database/factories/" } }, "extra": { diff --git a/testbench.yaml b/testbench.yaml index 5511d482..39aa6ffe 100644 --- a/testbench.yaml +++ b/testbench.yaml @@ -1,7 +1,9 @@ providers: + - Workbench\App\Providers\WorkbenchServiceProvider - Laravel\Scout\ScoutServiceProvider -migrations: true +migrations: + - workbench/database/migrations workbench: install: true diff --git a/tests/Feature/Jobs/RemovableScoutCollectionTest.php b/tests/Feature/Jobs/RemovableScoutCollectionTest.php new file mode 100644 index 00000000..a5752504 --- /dev/null +++ b/tests/Feature/Jobs/RemovableScoutCollectionTest.php @@ -0,0 +1,28 @@ +make(['scout_id' => '1234']), + ChirpFactory::new()->make(['scout_id' => '2345']), + UserFactory::new()->make(['id' => 3456]), + UserFactory::new()->make(['id' => 7891]), + ]); + + $this->assertEquals([ + '1234', + '2345', + 3456, + 7891, + ], $collection->getQueueableIds()); + } +} diff --git a/tests/Feature/Jobs/RemoveFromSearchTest.php b/tests/Feature/Jobs/RemoveFromSearchTest.php new file mode 100644 index 00000000..4e656b30 --- /dev/null +++ b/tests/Feature/Jobs/RemoveFromSearchTest.php @@ -0,0 +1,69 @@ +create(); + + $job = new RemoveFromSearch($models = RemoveableScoutCollection::make([$model])); + + $this->app->make('scout.spied')->shouldReceive('delete')->with(m::type(RemoveableScoutCollection::class))->once(); + + $job->handle(); + } + + public function test_models_are_deserialized_without_the_database() + { + $model = UserFactory::new()->create(['id' => 1234]); + + $job = new RemoveFromSearch($models = RemoveableScoutCollection::make([$model])); + + $job = unserialize(serialize($job)); + + $this->assertInstanceOf(RemoveableScoutCollection::class, $job->models); + $this->assertCount(1, $job->models); + $this->assertInstanceOf(User::class, $job->models->first()); + $this->assertSame(1234, $job->models->first()->getScoutKey()); + } + + public function test_models_are_deserialized_without_the_database_using_custom_scout_key() + { + $model = ChirpFactory::new()->create(['scout_id' => $uuid = Str::uuid()]); + + $job = new RemoveFromSearch($models = RemoveableScoutCollection::make([$model])); + + $job = unserialize(serialize($job)); + + $this->assertInstanceOf(RemoveableScoutCollection::class, $job->models); + $this->assertCount(1, $job->models); + $this->assertInstanceOf(Chirp::class, $job->models->first()); + $this->assertEquals($uuid, $job->models->first()->getScoutKey()); + $this->assertEquals('scout_id', $job->models->first()->getScoutKeyName()); + } +} diff --git a/tests/Unit/RemoveFromSearchTest.php b/tests/Unit/RemoveFromSearchTest.php deleted file mode 100644 index 4a5132ec..00000000 --- a/tests/Unit/RemoveFromSearchTest.php +++ /dev/null @@ -1,89 +0,0 @@ -with('scout.after_commit', m::any())->andReturn(false); - Config::shouldReceive('get')->with('scout.soft_delete', m::any())->andReturn(false); - } - - protected function tearDown(): void - { - m::close(); - } - - public function test_handle_passes_the_collection_to_engine() - { - $job = new RemoveFromSearch(Collection::make([ - $model = m::mock(), - ])); - - $model->shouldReceive('searchableUsing->delete')->with( - m::on(function ($collection) use ($model) { - return $collection instanceof RemoveableScoutCollection && $collection->first() === $model; - }) - ); - - $job->handle(); - } - - public function test_models_are_deserialized_without_the_database() - { - $job = new RemoveFromSearch(Collection::make([ - $model = new SearchableModel(['id' => 1234]), - ])); - - $job = unserialize(serialize($job)); - - $this->assertInstanceOf(Collection::class, $job->models); - $this->assertCount(1, $job->models); - $this->assertInstanceOf(SearchableModel::class, $job->models->first()); - $this->assertTrue($model->is($job->models->first())); - $this->assertEquals(1234, $job->models->first()->getScoutKey()); - } - - public function test_models_are_deserialized_without_the_database_using_custom_scout_key() - { - $job = new RemoveFromSearch(Collection::make([ - $model = new SearchableModelWithCustomKey(['other_id' => 1234]), - ])); - - $job = unserialize(serialize($job)); - - $this->assertInstanceOf(Collection::class, $job->models); - $this->assertCount(1, $job->models); - $this->assertInstanceOf(SearchableModelWithCustomKey::class, $job->models->first()); - $this->assertTrue($model->is($job->models->first())); - $this->assertEquals(1234, $job->models->first()->getScoutKey()); - $this->assertEquals('other_id', $job->models->first()->getScoutKeyName()); - } - - public function test_removeable_scout_collection_returns_scout_keys() - { - $collection = RemoveableScoutCollection::make([ - new SearchableModelWithCustomKey(['other_id' => 1234]), - new SearchableModelWithCustomKey(['other_id' => 2345]), - new SearchableModel(['id' => 3456]), - new SearchableModel(['id' => 7891]), - ]); - - $this->assertEquals([ - 1234, - 2345, - 3456, - 7891, - ], $collection->getQueueableIds()); - } -} diff --git a/workbench/app/Models/Chirp.php b/workbench/app/Models/Chirp.php new file mode 100644 index 00000000..205684c0 --- /dev/null +++ b/workbench/app/Models/Chirp.php @@ -0,0 +1,34 @@ +getScoutKeyName()]; + } + + /** {@inheritDoc} */ + public function getScoutKey() + { + return $this->scout_id; + } + + /** {@inheritDoc} */ + public function getScoutKeyName() + { + return 'scout_id'; + } +} diff --git a/workbench/app/Models/User.php b/workbench/app/Models/User.php new file mode 100644 index 00000000..40fbf4a8 --- /dev/null +++ b/workbench/app/Models/User.php @@ -0,0 +1,42 @@ + + */ + protected $fillable = [ + 'name', + 'email', + 'password', + ]; + + /** + * The attributes that should be hidden for serialization. + * + * @var array + */ + protected $hidden = [ + 'password', + 'remember_token', + ]; + + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + 'email_verified_at' => 'datetime', + 'password' => 'hashed', + ]; +} diff --git a/workbench/app/Providers/WorkbenchServiceProvider.php b/workbench/app/Providers/WorkbenchServiceProvider.php new file mode 100644 index 00000000..6f17bd3d --- /dev/null +++ b/workbench/app/Providers/WorkbenchServiceProvider.php @@ -0,0 +1,36 @@ +app->singleton('scout.spied', function () { + return m::spy(NullEngine::class); + }); + + $this->callAfterResolving(EngineManager::class, function ($engine) { + $engine->extend('testing', function ($app) { + return $app->make('scout.spied'); + }); + }); + } + + /** + * Bootstrap services. + */ + public function boot(): void + { + // + } +} diff --git a/workbench/database/factories/ChirpFactory.php b/workbench/database/factories/ChirpFactory.php new file mode 100644 index 00000000..052dd572 --- /dev/null +++ b/workbench/database/factories/ChirpFactory.php @@ -0,0 +1,33 @@ + + */ +class ChirpFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = Chirp::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'content' => fake()->realText(), + ]; + } +} diff --git a/workbench/database/factories/UserFactory.php b/workbench/database/factories/UserFactory.php new file mode 100644 index 00000000..dfcab01b --- /dev/null +++ b/workbench/database/factories/UserFactory.php @@ -0,0 +1,54 @@ + + */ +class UserFactory extends Factory +{ + /** + * The current password being used by the factory. + */ + protected static ?string $password; + + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = User::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'name' => fake()->name(), + 'email' => fake()->unique()->safeEmail(), + 'email_verified_at' => now(), + 'password' => static::$password ??= Hash::make('password'), + 'remember_token' => Str::random(10), + ]; + } + + /** + * Indicate that the model's email address should be unverified. + */ + public function unverified(): static + { + return $this->state(fn (array $attributes) => [ + 'email_verified_at' => null, + ]); + } +} diff --git a/workbench/database/migrations/2024_11_12_030345_create_chirps_table.php b/workbench/database/migrations/2024_11_12_030345_create_chirps_table.php new file mode 100644 index 00000000..ffc5d102 --- /dev/null +++ b/workbench/database/migrations/2024_11_12_030345_create_chirps_table.php @@ -0,0 +1,29 @@ +id(); + $table->uuid('scout_id'); + $table->text('content'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('chirps'); + } +}; From a0a149fc1c54f02f493d8f9fcbc1d3952e6ca039 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Tue, 12 Nov 2024 03:18:58 +0000 Subject: [PATCH 04/32] Apply fixes from StyleCI --- tests/Feature/Jobs/RemoveFromSearchTest.php | 3 +-- workbench/app/Providers/WorkbenchServiceProvider.php | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/Feature/Jobs/RemoveFromSearchTest.php b/tests/Feature/Jobs/RemoveFromSearchTest.php index 4e656b30..c43a0f13 100644 --- a/tests/Feature/Jobs/RemoveFromSearchTest.php +++ b/tests/Feature/Jobs/RemoveFromSearchTest.php @@ -4,10 +4,9 @@ //use Illuminate\Database\Eloquent\Collection; use Illuminate\Foundation\Testing\RefreshDatabase; -use Illuminate\Support\Collection; use Illuminate\Support\Str; -use Laravel\Scout\Jobs\RemoveFromSearch; use Laravel\Scout\Jobs\RemoveableScoutCollection; +use Laravel\Scout\Jobs\RemoveFromSearch; use Mockery as m; use Orchestra\Testbench\Attributes\WithConfig; use Orchestra\Testbench\Attributes\WithMigration; diff --git a/workbench/app/Providers/WorkbenchServiceProvider.php b/workbench/app/Providers/WorkbenchServiceProvider.php index 6f17bd3d..7fcbd77a 100644 --- a/workbench/app/Providers/WorkbenchServiceProvider.php +++ b/workbench/app/Providers/WorkbenchServiceProvider.php @@ -4,7 +4,6 @@ use Illuminate\Support\ServiceProvider; use Laravel\Scout\EngineManager; -use Laravel\Scout\Engines\Engine; use Laravel\Scout\Engines\NullEngine; use Mockery as m; From e0bb35a131873a2a332e14d5a0f55b919cec97ec Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 12 Nov 2024 11:45:55 +0800 Subject: [PATCH 05/32] wip Signed-off-by: Mior Muhammad Zaki --- tests/Feature/BuilderTest.php | 79 +++--------------- tests/Feature/CollectionEngineTest.php | 81 +++++++++++-------- tests/Feature/ModelObserverTest.php | 10 +++ ...hableUserModelWithCustomSearchableData.php | 20 ----- .../Unit/ModelObserverWithSoftDeletesTest.php | 2 +- workbench/app/Models/User.php | 21 +++++ 6 files changed, 90 insertions(+), 123 deletions(-) create mode 100644 tests/Feature/ModelObserverTest.php delete mode 100644 tests/Fixtures/SearchableUserModelWithCustomSearchableData.php diff --git a/tests/Feature/BuilderTest.php b/tests/Feature/BuilderTest.php index 7d11dab4..84324222 100644 --- a/tests/Feature/BuilderTest.php +++ b/tests/Feature/BuilderTest.php @@ -3,26 +3,23 @@ namespace Laravel\Scout\Tests\Feature; use Illuminate\Database\Eloquent\Factories\Sequence; -use Illuminate\Foundation\Auth\User; use Illuminate\Foundation\Testing\LazilyRefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; -use Laravel\Scout\EngineManager; -use Laravel\Scout\Engines\MeilisearchEngine; -use Laravel\Scout\Tests\Fixtures\SearchableUserModel; use Mockery as m; -use Orchestra\Testbench\Concerns\WithLaravelMigrations; +use Orchestra\Testbench\Attributes\WithConfig; +use Orchestra\Testbench\Attributes\WithMigration; use Orchestra\Testbench\Concerns\WithWorkbench; use Orchestra\Testbench\Factories\UserFactory; use Orchestra\Testbench\TestCase; +use Workbench\App\Models\User as SearchableUser; +#[WithConfig('scout.driver', 'database')] +#[WithMigration] class BuilderTest extends TestCase { - use LazilyRefreshDatabase, WithFaker, WithLaravelMigrations, WithWorkbench; - - protected function defineEnvironment($app) - { - $app->make('config')->set('scout.driver', 'fake'); - } + use LazilyRefreshDatabase; + use WithFaker; + use WithWorkbench; protected function afterRefreshingDatabase() { @@ -37,9 +34,7 @@ protected function afterRefreshingDatabase() public function test_it_can_paginate_without_custom_query_callback() { - $this->prepareScoutSearchMockUsing('Laravel'); - - $paginator = SearchableUserModel::search('Laravel')->paginate(); + $paginator = SearchableUser::search('Laravel')->paginate(); $this->assertSame(50, $paginator->total()); $this->assertSame(4, $paginator->lastPage()); @@ -48,9 +43,7 @@ public function test_it_can_paginate_without_custom_query_callback() public function test_it_can_paginate_with_custom_query_callback() { - $this->prepareScoutSearchMockUsing('Laravel'); - - $paginator = SearchableUserModel::search('Laravel')->query(function ($builder) { + $paginator = SearchableUser::search('Laravel')->query(function ($builder) { return $builder->where('id', '<', 11); })->paginate(); @@ -61,60 +54,10 @@ public function test_it_can_paginate_with_custom_query_callback() public function test_it_can_paginate_raw_without_custom_query_callback() { - $this->prepareScoutSearchMockUsing('Laravel'); - - $paginator = SearchableUserModel::search('Laravel')->paginateRaw(); + $paginator = SearchableUser::search('Laravel')->paginateRaw(); $this->assertSame(50, $paginator->total()); $this->assertSame(4, $paginator->lastPage()); $this->assertSame(15, $paginator->perPage()); } - - public function test_it_can_paginate_raw_with_custom_query_callback() - { - $this->prepareScoutSearchMockUsing('Laravel'); - - $paginator = SearchableUserModel::search('Laravel')->query(function ($builder) { - return $builder->where('id', '<', 11); - })->paginateRaw(); - - $this->assertSame(10, $paginator->total()); - $this->assertSame(1, $paginator->lastPage()); - $this->assertSame(15, $paginator->perPage()); - } - - protected function prepareScoutSearchMockUsing($searchQuery) - { - $engine = m::mock('Meilisearch\Client'); - $indexes = m::mock('Meilisearch\Endpoints\Indexes'); - - $manager = $this->app->make(EngineManager::class); - $manager->extend('fake', function () use ($engine) { - return new MeilisearchEngine($engine); - }); - - $query = User::where('name', 'like', $searchQuery.'%'); - - $hitsPerPage = 15; - $page = 1; - $totalPages = intval($query->count() / $hitsPerPage); - - $engine->shouldReceive('index')->with('users')->andReturn($indexes); - $indexes->shouldReceive('rawSearch') - ->with($searchQuery, ['hitsPerPage' => $hitsPerPage, 'page' => $page]) - ->andReturn([ - 'query' => $searchQuery, - 'hits' => $query->get()->transform(function ($result) { - return [ - 'id' => $result->getKey(), - 'name' => $result->name, - ]; - })->toArray(), - 'hitsPerPage' => $hitsPerPage, - 'page' => $page, - 'totalHits' => $query->count(), - 'totalPages' => $totalPages > 0 ? $totalPages : 0, - 'processingTimeMs' => 1, - ]); - } } diff --git a/tests/Feature/CollectionEngineTest.php b/tests/Feature/CollectionEngineTest.php index 2f07a5c9..16925c35 100644 --- a/tests/Feature/CollectionEngineTest.php +++ b/tests/Feature/CollectionEngineTest.php @@ -5,22 +5,19 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Foundation\Testing\LazilyRefreshDatabase; use Laravel\Scout\Tests\Fixtures\SearchableModelWithUnloadedValue; -use Laravel\Scout\Tests\Fixtures\SearchableUserModel; -use Laravel\Scout\Tests\Fixtures\SearchableUserModelWithCustomCreatedAt; -use Laravel\Scout\Tests\Fixtures\SearchableUserModelWithCustomSearchableData; -use Orchestra\Testbench\Concerns\WithLaravelMigrations; +use Orchestra\Testbench\Attributes\WithConfig; +use Orchestra\Testbench\Attributes\WithMigration; use Orchestra\Testbench\Concerns\WithWorkbench; use Orchestra\Testbench\Factories\UserFactory; use Orchestra\Testbench\TestCase; +use Workbench\App\Models\User as SearchableUser; +#[WithConfig('scout.driver', 'collection')] +#[WithMigration] class CollectionEngineTest extends TestCase { - use LazilyRefreshDatabase, WithLaravelMigrations, WithWorkbench; - - protected function defineEnvironment($app) - { - $app->make('config')->set('scout.driver', 'collection'); - } + use LazilyRefreshDatabase; + use WithWorkbench; protected function afterRefreshingDatabase() { @@ -39,113 +36,113 @@ protected function afterRefreshingDatabase() public function test_it_can_retrieve_results_with_empty_search() { - $models = SearchableUserModel::search()->get(); + $models = SearchableUser::search()->get(); $this->assertCount(2, $models); } public function test_it_can_retrieve_results() { - $models = SearchableUserModel::search('Taylor')->where('email', 'taylor@laravel.com')->get(); + $models = SearchableUser::search('Taylor')->where('email', 'taylor@laravel.com')->get(); $this->assertCount(1, $models); $this->assertEquals(1, $models[0]->id); - $models = SearchableUserModel::search('Taylor')->query(function ($query) { + $models = SearchableUser::search('Taylor')->query(function ($query) { $query->where('email', 'like', 'taylor@laravel.com'); })->get(); $this->assertCount(1, $models); $this->assertEquals(1, $models[0]->id); - $models = SearchableUserModel::search('Abigail')->where('email', 'abigail@laravel.com')->get(); + $models = SearchableUser::search('Abigail')->where('email', 'abigail@laravel.com')->get(); $this->assertCount(1, $models); $this->assertEquals(2, $models[0]->id); - $models = SearchableUserModel::search('Taylor')->where('email', 'abigail@laravel.com')->get(); + $models = SearchableUser::search('Taylor')->where('email', 'abigail@laravel.com')->get(); $this->assertCount(0, $models); - $models = SearchableUserModel::search('Taylor')->where('email', 'taylor@laravel.com')->get(); + $models = SearchableUser::search('Taylor')->where('email', 'taylor@laravel.com')->get(); $this->assertCount(1, $models); - $models = SearchableUserModel::search('otwell')->get(); + $models = SearchableUser::search('otwell')->get(); $this->assertCount(2, $models); - $models = SearchableUserModel::search('laravel')->get(); + $models = SearchableUser::search('laravel')->get(); $this->assertCount(2, $models); - $models = SearchableUserModel::search('foo')->get(); + $models = SearchableUser::search('foo')->get(); $this->assertCount(0, $models); - $models = SearchableUserModel::search('Abigail')->where('email', 'taylor@laravel.com')->get(); + $models = SearchableUser::search('Abigail')->where('email', 'taylor@laravel.com')->get(); $this->assertCount(0, $models); } public function test_it_can_retrieve_results_matching_to_custom_searchable_data() { - $models = SearchableUserModelWithCustomSearchableData::search('rolyaT')->get(); + $models = SearchableUserWithCustomSearchableData::search('rolyaT')->get(); $this->assertCount(1, $models); } public function test_it_can_paginate_results() { - $models = SearchableUserModel::search('Taylor')->where('email', 'taylor@laravel.com')->paginate(); + $models = SearchableUser::search('Taylor')->where('email', 'taylor@laravel.com')->paginate(); $this->assertCount(1, $models); - $models = SearchableUserModel::search('Taylor')->where('email', 'abigail@laravel.com')->paginate(); + $models = SearchableUser::search('Taylor')->where('email', 'abigail@laravel.com')->paginate(); $this->assertCount(0, $models); - $models = SearchableUserModel::search('Taylor')->where('email', 'taylor@laravel.com')->paginate(); + $models = SearchableUser::search('Taylor')->where('email', 'taylor@laravel.com')->paginate(); $this->assertCount(1, $models); - $models = SearchableUserModel::search('laravel')->paginate(); + $models = SearchableUser::search('laravel')->paginate(); $this->assertCount(2, $models); $dummyQuery = function ($query) { $query->where('name', '!=', 'Dummy'); }; - $models = SearchableUserModel::search('laravel')->query($dummyQuery)->orderBy('name')->paginate(1, 'page', 1); + $models = SearchableUser::search('laravel')->query($dummyQuery)->orderBy('name')->paginate(1, 'page', 1); $this->assertCount(1, $models); $this->assertEquals('Abigail Otwell', $models[0]->name); - $models = SearchableUserModel::search('laravel')->query($dummyQuery)->orderBy('name')->paginate(1, 'page', 2); + $models = SearchableUser::search('laravel')->query($dummyQuery)->orderBy('name')->paginate(1, 'page', 2); $this->assertCount(1, $models); $this->assertEquals('Taylor Otwell', $models[0]->name); } public function test_limit_is_applied() { - $models = SearchableUserModel::search('laravel')->get(); + $models = SearchableUser::search('laravel')->get(); $this->assertCount(2, $models); - $models = SearchableUserModel::search('laravel')->take(1)->get(); + $models = SearchableUser::search('laravel')->take(1)->get(); $this->assertCount(1, $models); } public function test_it_can_order_results() { - $models = SearchableUserModel::search('laravel')->orderBy('name', 'asc')->paginate(1, 'page', 1); + $models = SearchableUser::search('laravel')->orderBy('name', 'asc')->paginate(1, 'page', 1); $this->assertCount(1, $models); $this->assertEquals('Abigail Otwell', $models[0]->name); - $models = SearchableUserModel::search('laravel')->orderBy('name', 'desc')->paginate(1, 'page', 1); + $models = SearchableUser::search('laravel')->orderBy('name', 'desc')->paginate(1, 'page', 1); $this->assertCount(1, $models); $this->assertEquals('Taylor Otwell', $models[0]->name); } public function test_it_can_order_by_latest_and_oldest() { - $models = SearchableUserModel::search('laravel')->latest()->paginate(1, 'page', 1); + $models = SearchableUser::search('laravel')->latest()->paginate(1, 'page', 1); $this->assertCount(1, $models); $this->assertEquals('Abigail Otwell', $models[0]->name); - $models = SearchableUserModel::search('laravel')->oldest()->paginate(1, 'page', 1); + $models = SearchableUser::search('laravel')->oldest()->paginate(1, 'page', 1); $this->assertCount(1, $models); $this->assertEquals('Taylor Otwell', $models[0]->name); } public function test_it_can_order_by_custom_model_created_at_timestamp() { - $query = SearchableUserModelWithCustomCreatedAt::search()->latest(); + $query = SearchableUserWithCustomCreatedAt::search()->latest(); $this->assertCount(1, $query->orders); $this->assertEquals('created', $query->orders[0]['column']); @@ -160,3 +157,19 @@ public function test_it_calls_make_searchable_using_before_searching() $this->assertCount(2, $models); } } + +class SearchableUserWithCustomCreatedAt extends SearchableUser +{ + public const CREATED_AT = 'created'; +} + +class SearchableUserWithCustomSearchableData extends SearchableUser +{ + /** {@inheritDoc} */ + public function toSearchableArray(): array + { + return [ + 'reversed_name' => strrev($this->name), + ]; + } +} diff --git a/tests/Feature/ModelObserverTest.php b/tests/Feature/ModelObserverTest.php new file mode 100644 index 00000000..48d662bf --- /dev/null +++ b/tests/Feature/ModelObserverTest.php @@ -0,0 +1,10 @@ + strrev($this->name), - ]; - } -} diff --git a/tests/Unit/ModelObserverWithSoftDeletesTest.php b/tests/Unit/ModelObserverWithSoftDeletesTest.php index 1a124f8f..21adc126 100644 --- a/tests/Unit/ModelObserverWithSoftDeletesTest.php +++ b/tests/Unit/ModelObserverWithSoftDeletesTest.php @@ -53,7 +53,7 @@ public function test_restored_handler_makes_model_searchable() $observer = new ModelObserver; $model = m::mock(SearchableModelWithSoftDeletes::class); $model->shouldReceive('searchShouldUpdate')->never(); - $model->shouldReceive('shouldBeSearchable')->andReturn(true); + $model->shouldReceive('shouldBeSearchable')->once()->andReturn(true); $model->shouldReceive('searchable')->once(); $model->shouldReceive('unsearchable')->never(); $observer->restored($model); diff --git a/workbench/app/Models/User.php b/workbench/app/Models/User.php index 40fbf4a8..06e7479e 100644 --- a/workbench/app/Models/User.php +++ b/workbench/app/Models/User.php @@ -9,6 +9,13 @@ class User extends Authenticatable { use Searchable; + /** + * The table associated with the model. + * + * @var string|null + */ + protected $table = 'users'; + /** * The attributes that are mass assignable. * @@ -39,4 +46,18 @@ class User extends Authenticatable 'email_verified_at' => 'datetime', 'password' => 'hashed', ]; + + /** + * Get the indexable data array for the model. + * + * @return array + */ + public function toSearchableArray() + { + return [ + 'id' => $this->id, + 'name' => $this->name, + 'email' => $this->email, + ]; + } } From e79ea24a7300cc855b67dbc198bba1a260c00b12 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 12 Nov 2024 11:48:06 +0800 Subject: [PATCH 06/32] wip Signed-off-by: Mior Muhammad Zaki --- tests/Feature/CollectionEngineTest.php | 23 +++++++++++++-- .../SearchableModelWithUnloadedValue.php | 28 ------------------- 2 files changed, 21 insertions(+), 30 deletions(-) delete mode 100644 tests/Fixtures/SearchableModelWithUnloadedValue.php diff --git a/tests/Feature/CollectionEngineTest.php b/tests/Feature/CollectionEngineTest.php index 16925c35..eee9e203 100644 --- a/tests/Feature/CollectionEngineTest.php +++ b/tests/Feature/CollectionEngineTest.php @@ -2,9 +2,9 @@ namespace Laravel\Scout\Tests\Feature; +use Illuminate\Support\Collection; use Illuminate\Database\Eloquent\Model; use Illuminate\Foundation\Testing\LazilyRefreshDatabase; -use Laravel\Scout\Tests\Fixtures\SearchableModelWithUnloadedValue; use Orchestra\Testbench\Attributes\WithConfig; use Orchestra\Testbench\Attributes\WithMigration; use Orchestra\Testbench\Concerns\WithWorkbench; @@ -152,7 +152,7 @@ public function test_it_calls_make_searchable_using_before_searching() { Model::preventAccessingMissingAttributes(true); - $models = SearchableModelWithUnloadedValue::search('loaded')->get(); + $models = SearchableUserWithUnloadedValue::search('loaded')->get(); $this->assertCount(2, $models); } @@ -173,3 +173,22 @@ public function toSearchableArray(): array ]; } } + +class SearchableUserWithUnloadedValue extends SearchableUser +{ + /** {@inheritDoc} */ + public function toSearchableArray() + { + return [ + 'value' => $this->unloadedValue, + ]; + } + + /** {@inheritDoc} */ + public function makeSearchableUsing(Collection $models) + { + return $models->each( + fn ($model) => $model->unloadedValue = 'loaded', + ); + } +} diff --git a/tests/Fixtures/SearchableModelWithUnloadedValue.php b/tests/Fixtures/SearchableModelWithUnloadedValue.php deleted file mode 100644 index 1526adfe..00000000 --- a/tests/Fixtures/SearchableModelWithUnloadedValue.php +++ /dev/null @@ -1,28 +0,0 @@ - $this->unloadedValue, - ]; - } - - public function makeSearchableUsing(Collection $models) - { - return $models->each( - fn ($model) => $model->unloadedValue = 'loaded', - ); - } -} From 1604df0dfc0bfcac47d12ca6be782669f5215c8a Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Tue, 12 Nov 2024 03:49:09 +0000 Subject: [PATCH 07/32] Apply fixes from StyleCI --- tests/Feature/BuilderTest.php | 1 - tests/Feature/CollectionEngineTest.php | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/Feature/BuilderTest.php b/tests/Feature/BuilderTest.php index 84324222..100a2dc1 100644 --- a/tests/Feature/BuilderTest.php +++ b/tests/Feature/BuilderTest.php @@ -5,7 +5,6 @@ use Illuminate\Database\Eloquent\Factories\Sequence; use Illuminate\Foundation\Testing\LazilyRefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; -use Mockery as m; use Orchestra\Testbench\Attributes\WithConfig; use Orchestra\Testbench\Attributes\WithMigration; use Orchestra\Testbench\Concerns\WithWorkbench; diff --git a/tests/Feature/CollectionEngineTest.php b/tests/Feature/CollectionEngineTest.php index eee9e203..c11dabd6 100644 --- a/tests/Feature/CollectionEngineTest.php +++ b/tests/Feature/CollectionEngineTest.php @@ -2,9 +2,9 @@ namespace Laravel\Scout\Tests\Feature; -use Illuminate\Support\Collection; use Illuminate\Database\Eloquent\Model; use Illuminate\Foundation\Testing\LazilyRefreshDatabase; +use Illuminate\Support\Collection; use Orchestra\Testbench\Attributes\WithConfig; use Orchestra\Testbench\Attributes\WithMigration; use Orchestra\Testbench\Concerns\WithWorkbench; From 29d37e7c4491d28e190f54cc1f1ccc4929ca1865 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 12 Nov 2024 12:34:55 +0800 Subject: [PATCH 08/32] wip Signed-off-by: Mior Muhammad Zaki --- tests/Feature/Engines/Algolia3EngineTest.php | 176 +++++++++++++++++++ tests/Unit/Algolia3EngineTest.php | 143 --------------- 2 files changed, 176 insertions(+), 143 deletions(-) create mode 100644 tests/Feature/Engines/Algolia3EngineTest.php diff --git a/tests/Feature/Engines/Algolia3EngineTest.php b/tests/Feature/Engines/Algolia3EngineTest.php new file mode 100644 index 00000000..d5bfde05 --- /dev/null +++ b/tests/Feature/Engines/Algolia3EngineTest.php @@ -0,0 +1,176 @@ +client = m::spy(SearchClient::class); + + $manager->extend('algolia', fn () => new Algolia3Engine($this->client)); + }); + + $this->beforeApplicationDestroyed(function () { + unset($this->client); + }); + } + + public function test_update_adds_objects_to_index() + { + $model = UserFactory::new()->createQuietly(); + + $engine = $this->app->make(EngineManager::class)->engine('algolia'); + + $this->client->shouldReceive('initIndex')->with('users')->once()->andReturn($index = m::mock(stdClass::class)); + $index->shouldReceive('saveObjects')->with([[ + 'id' => $model->getKey(), + 'name' => $model->name, + 'email' => $model->email, + 'objectID' => $model->getScoutKey(), + ]])->once(); + + $engine->update(Collection::make([$model])); + } + + public function test_delete_removes_objects_to_index() + { + $model = UserFactory::new()->createQuietly(); + + $engine = $this->app->make(EngineManager::class)->engine('algolia'); + + $this->client->shouldReceive('initIndex')->with('users')->once()->andReturn($index = m::mock(stdClass::class)); + $index->shouldReceive('deleteObjects')->with([1])->once(); + + $engine->delete(Collection::make([$model])); + } + + public function test_delete_removes_objects_to_index_with_a_custom_search_key() + { + $model = ChirpFactory::new()->createQuietly([ + 'scout_id' => 'my-algolia-key.5', + ]); + + $engine = $this->app->make(EngineManager::class)->engine('algolia'); + + $this->client->shouldReceive('initIndex')->with('chirps')->once()->andReturn($index = m::mock(Indexes::class)); + $index->shouldReceive('deleteObjects')->once()->with(['my-algolia-key.5']); + + $engine->delete(Collection::make([$model])); + } + + public function test_delete_with_removeable_scout_collection_using_custom_search_key() + { + $model = ChirpFactory::new()->createQuietly([ + 'scout_id' => 'my-algolia-key.5', + ]); + + $engine = $this->app->make(EngineManager::class)->engine('algolia'); + + $job = new RemoveFromSearch(RemoveableScoutCollection::make([$model])); + + $job = unserialize(serialize($job)); + + $this->client->shouldReceive('initIndex')->with('chirps')->once()->andReturn($index = m::mock(stdClass::class)); + $index->shouldReceive('deleteObjects')->once()->with(['my-algolia-key.5']); + + $job->handle(); + } + + public function test_search_sends_correct_parameters_to_algolia() + { + UserFactory::new()->createQuietly(['name' => 'zonda']); + + $engine = $this->app->make(EngineManager::class)->engine('algolia'); + + $this->client->shouldReceive('initIndex')->with('users')->once()->andReturn($index = m::mock(stdClass::class)); + $index->shouldReceive('search')->with('zonda', [ + 'numericFilters' => ['foo=1'], + ])->once(); + + $builder = new Builder(new User, 'zonda'); + $builder->where('foo', 1); + + $engine->search($builder); + } + + public function test_search_sends_correct_parameters_to_algolia_for_where_in_search() + { + UserFactory::new()->createQuietly(['name' => 'zonda']); + + $engine = $this->app->make(EngineManager::class)->engine('algolia'); + + $this->client->shouldReceive('initIndex')->with('users')->once()->andReturn($index = m::mock(stdClass::class)); + $index->shouldReceive('search')->with('zonda', [ + 'numericFilters' => ['foo=1', ['bar=1', 'bar=2']], + ]); + + $builder = new Builder(new User, 'zonda'); + $builder->where('foo', 1)->whereIn('bar', [1, 2]); + + $engine->search($builder); + } + + public function test_search_sends_correct_parameters_to_algolia_for_empty_where_in_search() + { + UserFactory::new()->createQuietly(['name' => 'zonda']); + + $engine = $this->app->make(EngineManager::class)->engine('algolia'); + + $this->client->shouldReceive('initIndex')->with('users')->once()->andReturn($index = m::mock(stdClass::class)); + $index->shouldReceive('search')->with('zonda', [ + 'numericFilters' => ['foo=1', '0=1'], + ]); + + $builder = new Builder(new User, 'zonda'); + $builder->where('foo', 1)->whereIn('bar', []); + $engine->search($builder); + } + + public function test_map_correctly_maps_results_to_models() + { + $model = UserFactory::new()->createQuietly(['name' => 'zonda']); + + $engine = $this->app->make(EngineManager::class)->engine('algolia'); + + $builder = m::mock(Builder::class); + + $results = $engine->map($builder, [ + 'nbHits' => 1, + 'hits' => [ + ['objectID' => 1, 'id' => 1, '_rankingInfo' => ['nbTypos' => 0]], + ], + ], $model); + + $this->assertCount(1, $results); + $this->assertEquals(['_rankingInfo' => ['nbTypos' => 0]], $results->first()->scoutMetaData()); + Assert::assertArraySubset(['id' => 1, 'name' => 'zonda'], $results->first()->toArray()); + } +} diff --git a/tests/Unit/Algolia3EngineTest.php b/tests/Unit/Algolia3EngineTest.php index 2b486bed..0fdb5043 100644 --- a/tests/Unit/Algolia3EngineTest.php +++ b/tests/Unit/Algolia3EngineTest.php @@ -32,149 +32,6 @@ protected function tearDown(): void m::close(); } - public function test_update_adds_objects_to_index() - { - $client = m::mock(SearchClient::class); - $client->shouldReceive('initIndex')->with('table')->andReturn($index = m::mock(stdClass::class)); - $index->shouldReceive('saveObjects')->with([[ - 'id' => 1, - 'objectID' => 1, - ]]); - - $engine = new Algolia3Engine($client); - $engine->update(Collection::make([new SearchableModel])); - } - - public function test_delete_removes_objects_to_index() - { - $client = m::mock(SearchClient::class); - $client->shouldReceive('initIndex')->with('table')->andReturn($index = m::mock(stdClass::class)); - $index->shouldReceive('deleteObjects')->with([1]); - - $engine = new Algolia3Engine($client); - $engine->delete(Collection::make([new SearchableModel(['id' => 1])])); - } - - public function test_delete_removes_objects_to_index_with_a_custom_search_key() - { - $client = m::mock(SearchClient::class); - $client->shouldReceive('initIndex')->with('table')->andReturn($index = m::mock(Indexes::class)); - $index->shouldReceive('deleteObjects')->once()->with(['my-algolia-key.5']); - - $engine = new Algolia3Engine($client); - $engine->delete(Collection::make([new Algolia3CustomKeySearchableModel(['id' => 5])])); - } - - public function test_delete_with_removeable_scout_collection_using_custom_search_key() - { - $job = new RemoveFromSearch(Collection::make([ - new Algolia3CustomKeySearchableModel(['id' => 5]), - ])); - - $job = unserialize(serialize($job)); - - $client = m::mock(SearchClient::class); - $client->shouldReceive('initIndex')->with('table')->andReturn($index = m::mock(stdClass::class)); - $index->shouldReceive('deleteObjects')->once()->with(['my-algolia-key.5']); - - $engine = new Algolia3Engine($client); - $engine->delete($job->models); - } - - public function test_remove_from_search_job_uses_custom_search_key() - { - $job = new RemoveFromSearch(Collection::make([ - new Algolia3CustomKeySearchableModel(['id' => 5]), - ])); - - $job = unserialize(serialize($job)); - - Container::getInstance()->bind(EngineManager::class, function () { - $engine = m::mock(Algolia3Engine::class); - - $engine->shouldReceive('delete')->once()->with(m::on(function ($collection) { - $keyName = ($model = $collection->first())->getScoutKeyName(); - - return $model->getAttributes()[$keyName] === 'my-algolia-key.5'; - })); - - $manager = m::mock(EngineManager::class); - - $manager->shouldReceive('engine')->andReturn($engine); - - return $manager; - }); - - $job->handle(); - } - - public function test_search_sends_correct_parameters_to_algolia() - { - $client = m::mock(SearchClient::class); - $client->shouldReceive('initIndex')->with('table')->andReturn($index = m::mock(stdClass::class)); - $index->shouldReceive('search')->with('zonda', [ - 'numericFilters' => ['foo=1'], - ]); - - $engine = new Algolia3Engine($client); - $builder = new Builder(new SearchableModel, 'zonda'); - $builder->where('foo', 1); - $engine->search($builder); - } - - public function test_search_sends_correct_parameters_to_algolia_for_where_in_search() - { - $client = m::mock(SearchClient::class); - $client->shouldReceive('initIndex')->with('table')->andReturn($index = m::mock(stdClass::class)); - $index->shouldReceive('search')->with('zonda', [ - 'numericFilters' => ['foo=1', ['bar=1', 'bar=2']], - ]); - - $engine = new Algolia3Engine($client); - $builder = new Builder(new SearchableModel, 'zonda'); - $builder->where('foo', 1)->whereIn('bar', [1, 2]); - $engine->search($builder); - } - - public function test_search_sends_correct_parameters_to_algolia_for_empty_where_in_search() - { - $client = m::mock(SearchClient::class); - $client->shouldReceive('initIndex')->with('table')->andReturn($index = m::mock(stdClass::class)); - $index->shouldReceive('search')->with('zonda', [ - 'numericFilters' => ['foo=1', '0=1'], - ]); - - $engine = new Algolia3Engine($client); - $builder = new Builder(new SearchableModel, 'zonda'); - $builder->where('foo', 1)->whereIn('bar', []); - $engine->search($builder); - } - - public function test_map_correctly_maps_results_to_models() - { - $client = m::mock(SearchClient::class); - $engine = new Algolia3Engine($client); - - $model = m::mock(stdClass::class); - - $model->shouldReceive('getScoutModelsByIds')->andReturn($models = Collection::make([ - new SearchableModel(['id' => 1, 'name' => 'test']), - ])); - - $builder = m::mock(Builder::class); - - $results = $engine->map($builder, [ - 'nbHits' => 1, - 'hits' => [ - ['objectID' => 1, 'id' => 1, '_rankingInfo' => ['nbTypos' => 0]], - ], - ], $model); - - $this->assertCount(1, $results); - $this->assertEquals(['id' => 1, 'name' => 'test'], $results->first()->toArray()); - $this->assertEquals(['_rankingInfo' => ['nbTypos' => 0]], $results->first()->scoutMetaData()); - } - public function test_map_method_respects_order() { $client = m::mock(SearchClient::class); From dcc115d8ca1180a02603d97b1d0af06e5a1bc2fb Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Tue, 12 Nov 2024 04:35:24 +0000 Subject: [PATCH 09/32] Apply fixes from StyleCI --- tests/Feature/Engines/Algolia3EngineTest.php | 2 +- tests/Unit/Algolia3EngineTest.php | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/Feature/Engines/Algolia3EngineTest.php b/tests/Feature/Engines/Algolia3EngineTest.php index d5bfde05..da1d78cc 100644 --- a/tests/Feature/Engines/Algolia3EngineTest.php +++ b/tests/Feature/Engines/Algolia3EngineTest.php @@ -9,8 +9,8 @@ use Laravel\Scout\Builder; use Laravel\Scout\EngineManager; use Laravel\Scout\Engines\Algolia3Engine; -use Laravel\Scout\Jobs\RemoveFromSearch; use Laravel\Scout\Jobs\RemoveableScoutCollection; +use Laravel\Scout\Jobs\RemoveFromSearch; use Mockery as m; use Orchestra\Testbench\Attributes\WithMigration; use Orchestra\Testbench\Concerns\WithWorkbench; diff --git a/tests/Unit/Algolia3EngineTest.php b/tests/Unit/Algolia3EngineTest.php index 0fdb5043..e25085a1 100644 --- a/tests/Unit/Algolia3EngineTest.php +++ b/tests/Unit/Algolia3EngineTest.php @@ -8,9 +8,7 @@ use Illuminate\Support\Facades\Config; use Illuminate\Support\LazyCollection; use Laravel\Scout\Builder; -use Laravel\Scout\EngineManager; use Laravel\Scout\Engines\Algolia3Engine; -use Laravel\Scout\Jobs\RemoveFromSearch; use Laravel\Scout\Tests\Fixtures\EmptySearchableModel; use Laravel\Scout\Tests\Fixtures\SearchableModel; use Laravel\Scout\Tests\Fixtures\SoftDeletedEmptySearchableModel; From d88923531d04cc95f1ed660102246de29e8300d6 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 12 Nov 2024 13:15:47 +0800 Subject: [PATCH 10/32] wip Signed-off-by: Mior Muhammad Zaki --- tests/Feature/BuilderTest.php | 8 +- tests/Feature/CollectionEngineTest.php | 2 +- tests/Feature/Engines/Algolia3EngineTest.php | 102 ++++++- tests/Feature/Engines/Algolia4EngineTest.php | 246 ++++++++++++++++ .../Jobs/RemovableScoutCollectionTest.php | 6 +- tests/Feature/Jobs/RemoveFromSearchTest.php | 10 +- tests/Feature/SearchableTest.php | 14 +- ...SearchableModelWithSensitiveAttributes.php | 2 - .../SearchableModelWithSoftDeletes.php | 2 +- .../Integration/MeilisearchSearchableTest.php | 2 +- tests/Integration/SearchableTests.php | 3 +- tests/Unit/Algolia3EngineTest.php | 137 +-------- tests/Unit/Algolia4EngineTest.php | 278 +----------------- tests/Unit/MeilisearchEngineTest.php | 40 +-- tests/Unit/SearchableScopeTest.php | 2 +- workbench/app/Models/Chirp.php | 7 + workbench/app/Models/SearchableUser.php | 20 ++ workbench/app/Models/User.php | 17 -- .../factories/SearchableUserFactory.php | 15 + 19 files changed, 423 insertions(+), 490 deletions(-) create mode 100644 tests/Feature/Engines/Algolia4EngineTest.php create mode 100644 workbench/app/Models/SearchableUser.php create mode 100644 workbench/database/factories/SearchableUserFactory.php diff --git a/tests/Feature/BuilderTest.php b/tests/Feature/BuilderTest.php index 100a2dc1..66d390e5 100644 --- a/tests/Feature/BuilderTest.php +++ b/tests/Feature/BuilderTest.php @@ -8,9 +8,9 @@ use Orchestra\Testbench\Attributes\WithConfig; use Orchestra\Testbench\Attributes\WithMigration; use Orchestra\Testbench\Concerns\WithWorkbench; -use Orchestra\Testbench\Factories\UserFactory; use Orchestra\Testbench\TestCase; -use Workbench\App\Models\User as SearchableUser; +use Workbench\App\Models\SearchableUser; +use Workbench\Database\Factories\SearchableUserFactory; #[WithConfig('scout.driver', 'database')] #[WithMigration] @@ -24,11 +24,11 @@ protected function afterRefreshingDatabase() { $this->setUpFaker(); - UserFactory::new()->count(50)->state(new Sequence(function () { + SearchableUserFactory::new()->count(50)->state(new Sequence(function () { return ['name' => 'Laravel '.$this->faker()->name()]; }))->create(); - UserFactory::new()->times(50)->create(); + SearchableUserFactory::new()->times(50)->create(); } public function test_it_can_paginate_without_custom_query_callback() diff --git a/tests/Feature/CollectionEngineTest.php b/tests/Feature/CollectionEngineTest.php index c11dabd6..f8570a4a 100644 --- a/tests/Feature/CollectionEngineTest.php +++ b/tests/Feature/CollectionEngineTest.php @@ -10,7 +10,7 @@ use Orchestra\Testbench\Concerns\WithWorkbench; use Orchestra\Testbench\Factories\UserFactory; use Orchestra\Testbench\TestCase; -use Workbench\App\Models\User as SearchableUser; +use Workbench\App\Models\SearchableUser; #[WithConfig('scout.driver', 'collection')] #[WithMigration] diff --git a/tests/Feature/Engines/Algolia3EngineTest.php b/tests/Feature/Engines/Algolia3EngineTest.php index d5bfde05..019dd19d 100644 --- a/tests/Feature/Engines/Algolia3EngineTest.php +++ b/tests/Feature/Engines/Algolia3EngineTest.php @@ -9,15 +9,17 @@ use Laravel\Scout\Builder; use Laravel\Scout\EngineManager; use Laravel\Scout\Engines\Algolia3Engine; -use Laravel\Scout\Jobs\RemoveFromSearch; use Laravel\Scout\Jobs\RemoveableScoutCollection; +use Laravel\Scout\Jobs\RemoveFromSearch; use Mockery as m; +use Orchestra\Testbench\Attributes\WithConfig; use Orchestra\Testbench\Attributes\WithMigration; use Orchestra\Testbench\Concerns\WithWorkbench; use Orchestra\Testbench\TestCase; -use Workbench\App\Models\User; +use Workbench\App\Models\Chirp; +use Workbench\App\Models\SearchableUser; use Workbench\Database\Factories\ChirpFactory; -use Workbench\Database\Factories\UserFactory; +use Workbench\Database\Factories\SearchableUserFactory; use function Orchestra\Testbench\after_resolving; @@ -35,7 +37,7 @@ protected function defineEnvironment($app) after_resolving($app, EngineManager::class, function ($manager) { $this->client = m::spy(SearchClient::class); - $manager->extend('algolia', fn () => new Algolia3Engine($this->client)); + $manager->extend('algolia', fn () => new Algolia3Engine($this->client, config('scout.soft_delete'))); }); $this->beforeApplicationDestroyed(function () { @@ -45,7 +47,7 @@ protected function defineEnvironment($app) public function test_update_adds_objects_to_index() { - $model = UserFactory::new()->createQuietly(); + $model = SearchableUserFactory::new()->createQuietly(); $engine = $this->app->make(EngineManager::class)->engine('algolia'); @@ -62,7 +64,7 @@ public function test_update_adds_objects_to_index() public function test_delete_removes_objects_to_index() { - $model = UserFactory::new()->createQuietly(); + $model = SearchableUserFactory::new()->createQuietly(); $engine = $this->app->make(EngineManager::class)->engine('algolia'); @@ -106,7 +108,7 @@ public function test_delete_with_removeable_scout_collection_using_custom_search public function test_search_sends_correct_parameters_to_algolia() { - UserFactory::new()->createQuietly(['name' => 'zonda']); + SearchableUserFactory::new()->createQuietly(['name' => 'zonda']); $engine = $this->app->make(EngineManager::class)->engine('algolia'); @@ -115,7 +117,7 @@ public function test_search_sends_correct_parameters_to_algolia() 'numericFilters' => ['foo=1'], ])->once(); - $builder = new Builder(new User, 'zonda'); + $builder = new Builder(new SearchableUser, 'zonda'); $builder->where('foo', 1); $engine->search($builder); @@ -123,7 +125,7 @@ public function test_search_sends_correct_parameters_to_algolia() public function test_search_sends_correct_parameters_to_algolia_for_where_in_search() { - UserFactory::new()->createQuietly(['name' => 'zonda']); + SearchableUserFactory::new()->createQuietly(['name' => 'zonda']); $engine = $this->app->make(EngineManager::class)->engine('algolia'); @@ -132,7 +134,7 @@ public function test_search_sends_correct_parameters_to_algolia_for_where_in_sea 'numericFilters' => ['foo=1', ['bar=1', 'bar=2']], ]); - $builder = new Builder(new User, 'zonda'); + $builder = new Builder(new SearchableUser, 'zonda'); $builder->where('foo', 1)->whereIn('bar', [1, 2]); $engine->search($builder); @@ -140,7 +142,7 @@ public function test_search_sends_correct_parameters_to_algolia_for_where_in_sea public function test_search_sends_correct_parameters_to_algolia_for_empty_where_in_search() { - UserFactory::new()->createQuietly(['name' => 'zonda']); + SearchableUserFactory::new()->createQuietly(['name' => 'zonda']); $engine = $this->app->make(EngineManager::class)->engine('algolia'); @@ -149,14 +151,14 @@ public function test_search_sends_correct_parameters_to_algolia_for_empty_where_ 'numericFilters' => ['foo=1', '0=1'], ]); - $builder = new Builder(new User, 'zonda'); + $builder = new Builder(new SearchableUser, 'zonda'); $builder->where('foo', 1)->whereIn('bar', []); $engine->search($builder); } public function test_map_correctly_maps_results_to_models() { - $model = UserFactory::new()->createQuietly(['name' => 'zonda']); + $model = SearchableUserFactory::new()->createQuietly(['name' => 'zonda']); $engine = $this->app->make(EngineManager::class)->engine('algolia'); @@ -173,4 +175,78 @@ public function test_map_correctly_maps_results_to_models() $this->assertEquals(['_rankingInfo' => ['nbTypos' => 0]], $results->first()->scoutMetaData()); Assert::assertArraySubset(['id' => 1, 'name' => 'zonda'], $results->first()->toArray()); } + + public function test_a_model_is_indexed_with_a_custom_algolia_key() + { + $model = ChirpFactory::new()->createQuietly([ + 'scout_id' => 'my-algolia-key.1', + ]); + + $engine = $this->app->make(EngineManager::class)->engine('algolia'); + + $this->client->shouldReceive('initIndex')->with('chirps')->once()->andReturn($index = m::mock(stdClass::class)); + $index->shouldReceive('saveObjects')->with([[ + 'content' => $model->content, + 'objectID' => 'my-algolia-key.1', + ]])->once(); + + $engine->update(Collection::make([$model])); + } + + public function test_a_model_is_removed_with_a_custom_algolia_key() + { + $model = ChirpFactory::new()->createQuietly([ + 'scout_id' => 'my-algolia-key.1', + ]); + + $engine = $this->app->make(EngineManager::class)->engine('algolia'); + + $this->client->shouldReceive('initIndex')->with('chirps')->once()->andReturn($index = m::mock(stdClass::class)); + $index->shouldReceive('deleteObjects')->with(['my-algolia-key.1']); + + $engine->delete(Collection::make([$model])); + } + + public function test_flush_a_model_with_a_custom_algolia_key() + { + $model = ChirpFactory::new()->createQuietly([ + 'scout_id' => 'my-algolia-key.1', + ]); + + $engine = $this->app->make(EngineManager::class)->engine('algolia'); + + $this->client->shouldReceive('initIndex')->with('chirps')->once()->andReturn($index = m::mock(stdClass::class)); + $index->shouldReceive('clearObjects'); + + $engine->flush(new Chirp); + } + + public function test_update_empty_searchable_array_does_not_add_objects_to_index() + { + $_ENV['searchable.user'] = []; + + $engine = $this->app->make(EngineManager::class)->engine('algolia'); + + $this->client->shouldReceive('initIndex')->with('users')->once()->andReturn($index = m::mock(stdClass::class)); + $index->shouldNotReceive('saveObjects'); + + $engine->update(Collection::make([new SearchableUser])); + + unset($_ENV['searchable.user']); + } + + #[WithConfig('scout.soft_delete', true)] + public function test_update_empty_searchable_array_from_soft_deleted_model_does_not_add_objects_to_index() + { + $_ENV['searchable.chirp'] = []; + + $engine = $this->app->make(EngineManager::class)->engine('algolia'); + + $this->client->shouldReceive('initIndex')->with('chirps')->once()->andReturn($index = m::mock(stdClass::class)); + $index->shouldNotReceive('saveObjects'); + + $engine->update(Collection::make([new Chirp])); + + unset($_ENV['searchable.chirp']); + } } diff --git a/tests/Feature/Engines/Algolia4EngineTest.php b/tests/Feature/Engines/Algolia4EngineTest.php new file mode 100644 index 00000000..df2fc7e6 --- /dev/null +++ b/tests/Feature/Engines/Algolia4EngineTest.php @@ -0,0 +1,246 @@ +client = m::spy(SearchClient::class); + + $manager->extend('algolia', fn () => new Algolia4Engine($this->client, config('scout.soft_delete'))); + }); + + $this->beforeApplicationDestroyed(function () { + unset($this->client); + }); + } + + public function test_update_adds_objects_to_index() + { + $model = SearchableUserFactory::new()->createQuietly(); + + $engine = $this->app->make(EngineManager::class)->engine('algolia'); + + $this->client->shouldReceive('saveObjects')->with('users', [[ + 'id' => $model->getKey(), + 'name' => $model->name, + 'email' => $model->email, + 'objectID' => $model->getScoutKey(), + ]])->once(); + + $engine->update(Collection::make([$model])); + } + + public function test_delete_removes_objects_to_index() + { + $model = SearchableUserFactory::new()->createQuietly(); + + $engine = $this->app->make(EngineManager::class)->engine('algolia'); + + $this->client->shouldReceive('deleteObjects')->with('users', [1])->once(); + + $engine->delete(Collection::make([$model])); + } + + public function test_delete_removes_objects_to_index_with_a_custom_search_key() + { + $model = ChirpFactory::new()->createQuietly([ + 'scout_id' => 'my-algolia-key.5', + ]); + + $engine = $this->app->make(EngineManager::class)->engine('algolia'); + + $this->client->shouldReceive('deleteObjects')->once()->with('chirps', ['my-algolia-key.5']); + + $engine->delete(Collection::make([$model])); + } + + public function test_delete_with_removeable_scout_collection_using_custom_search_key() + { + $model = ChirpFactory::new()->createQuietly([ + 'scout_id' => 'my-algolia-key.5', + ]); + + $engine = $this->app->make(EngineManager::class)->engine('algolia'); + + $job = new RemoveFromSearch(RemoveableScoutCollection::make([$model])); + + $job = unserialize(serialize($job)); + + $this->client->shouldReceive('deleteObjects')->once()->with('chirps', ['my-algolia-key.5']); + + $job->handle(); + } + + public function test_search_sends_correct_parameters_to_algolia() + { + SearchableUserFactory::new()->createQuietly(['name' => 'zonda']); + + $engine = $this->app->make(EngineManager::class)->engine('algolia'); + + $this->client->shouldReceive('searchSingleIndex')->with( + 'users', + ['query' => 'zonda'], + ['numericFilters' => ['foo=1']] + )->once(); + + $builder = new Builder(new SearchableUser, 'zonda'); + $builder->where('foo', 1); + + $engine->search($builder); + } + + public function test_search_sends_correct_parameters_to_algolia_for_where_in_search() + { + SearchableUserFactory::new()->createQuietly(['name' => 'zonda']); + + $engine = $this->app->make(EngineManager::class)->engine('algolia'); + + $this->client->shouldReceive('searchSingleIndex')->with( + 'users', + ['query' => 'zonda'], + ['numericFilters' => ['foo=1', ['bar=1', 'bar=2']]], + )->once(); + + $builder = new Builder(new SearchableUser, 'zonda'); + $builder->where('foo', 1)->whereIn('bar', [1, 2]); + + $engine->search($builder); + } + + public function test_search_sends_correct_parameters_to_algolia_for_empty_where_in_search() + { + SearchableUserFactory::new()->createQuietly(['name' => 'zonda']); + + $engine = $this->app->make(EngineManager::class)->engine('algolia'); + + $this->client->shouldReceive('searchSingleIndex')->with( + 'users', + ['query' => 'zonda'], + ['numericFilters' => ['foo=1', '0=1']] + )->once(); + + $builder = new Builder(new SearchableUser, 'zonda'); + $builder->where('foo', 1)->whereIn('bar', []); + $engine->search($builder); + } + + public function test_map_correctly_maps_results_to_models() + { + $model = SearchableUserFactory::new()->createQuietly(['name' => 'zonda']); + + $engine = $this->app->make(EngineManager::class)->engine('algolia'); + + $builder = m::mock(Builder::class); + + $results = $engine->map($builder, [ + 'nbHits' => 1, + 'hits' => [ + ['objectID' => 1, 'id' => 1, '_rankingInfo' => ['nbTypos' => 0]], + ], + ], $model); + + $this->assertCount(1, $results); + $this->assertEquals(['_rankingInfo' => ['nbTypos' => 0]], $results->first()->scoutMetaData()); + Assert::assertArraySubset(['id' => 1, 'name' => 'zonda'], $results->first()->toArray()); + } + + public function test_a_model_is_indexed_with_a_custom_algolia_key() + { + $model = ChirpFactory::new()->createQuietly([ + 'scout_id' => 'my-algolia-key.1', + ]); + + $engine = $this->app->make(EngineManager::class)->engine('algolia'); + + $this->client->shouldReceive('saveObjects')->with('chirps', [[ + 'content' => $model->content, + 'objectID' => 'my-algolia-key.1', + ]])->once(); + + $engine->update(Collection::make([$model])); + } + + public function test_a_model_is_removed_with_a_custom_algolia_key() + { + $model = ChirpFactory::new()->createQuietly([ + 'scout_id' => 'my-algolia-key.1', + ]); + + $engine = $this->app->make(EngineManager::class)->engine('algolia'); + + $this->client->shouldReceive('deleteObjects')->with('chirps', ['my-algolia-key.1'])->once(); + + $engine->delete(Collection::make([$model])); + } + + public function test_flush_a_model_with_a_custom_algolia_key() + { + $model = ChirpFactory::new()->createQuietly([ + 'scout_id' => 'my-algolia-key.1', + ]); + + $engine = $this->app->make(EngineManager::class)->engine('algolia'); + + $this->client->shouldReceive('clearObjects')->with('chirps')->once(); + + $engine->flush(new Chirp); + } + + public function test_update_empty_searchable_array_does_not_add_objects_to_index() + { + $_ENV['searchable.user'] = []; + + $engine = $this->app->make(EngineManager::class)->engine('algolia'); + + $this->client->shouldNotReceive('saveObjects')->with('users'); + + $engine->update(Collection::make([new SearchableUser])); + + unset($_ENV['searchable.user']); + } + + #[WithConfig('scout.soft_delete', true)] + public function test_update_empty_searchable_array_from_soft_deleted_model_does_not_add_objects_to_index() + { + $_ENV['searchable.chirp'] = []; + + $engine = $this->app->make(EngineManager::class)->engine('algolia'); + + $this->client->shouldNotReceive('saveObjects')->with('chirps'); + + $engine->update(Collection::make([new Chirp])); + + unset($_ENV['searchable.chirp']); + } +} diff --git a/tests/Feature/Jobs/RemovableScoutCollectionTest.php b/tests/Feature/Jobs/RemovableScoutCollectionTest.php index a5752504..52821034 100644 --- a/tests/Feature/Jobs/RemovableScoutCollectionTest.php +++ b/tests/Feature/Jobs/RemovableScoutCollectionTest.php @@ -5,7 +5,7 @@ use Laravel\Scout\Jobs\RemoveableScoutCollection; use Orchestra\Testbench\TestCase; use Workbench\Database\Factories\ChirpFactory; -use Workbench\Database\Factories\UserFactory; +use Workbench\Database\Factories\SearchableUserFactory; class RemovableScoutCollectionTest extends TestCase { @@ -14,8 +14,8 @@ public function test_removeable_scout_collection_returns_scout_keys() $collection = RemoveableScoutCollection::make([ ChirpFactory::new()->make(['scout_id' => '1234']), ChirpFactory::new()->make(['scout_id' => '2345']), - UserFactory::new()->make(['id' => 3456]), - UserFactory::new()->make(['id' => 7891]), + SearchableUserFactory::new()->make(['id' => 3456]), + SearchableUserFactory::new()->make(['id' => 7891]), ]); $this->assertEquals([ diff --git a/tests/Feature/Jobs/RemoveFromSearchTest.php b/tests/Feature/Jobs/RemoveFromSearchTest.php index c43a0f13..ab4b15b6 100644 --- a/tests/Feature/Jobs/RemoveFromSearchTest.php +++ b/tests/Feature/Jobs/RemoveFromSearchTest.php @@ -13,9 +13,9 @@ use Orchestra\Testbench\Concerns\WithWorkbench; use Orchestra\Testbench\TestCase; use Workbench\App\Models\Chirp; -use Workbench\App\Models\User; +use Workbench\App\Models\SearchableUser; use Workbench\Database\Factories\ChirpFactory; -use Workbench\Database\Factories\UserFactory; +use Workbench\Database\Factories\SearchableUserFactory; #[WithConfig('scout.driver', 'testing')] #[WithConfig('scout.after_commit', false)] @@ -28,7 +28,7 @@ class RemoveFromSearchTest extends TestCase public function test_handle_passes_the_collection_to_engine() { - $model = UserFactory::new()->create(); + $model = SearchableUserFactory::new()->create(); $job = new RemoveFromSearch($models = RemoveableScoutCollection::make([$model])); @@ -39,7 +39,7 @@ public function test_handle_passes_the_collection_to_engine() public function test_models_are_deserialized_without_the_database() { - $model = UserFactory::new()->create(['id' => 1234]); + $model = SearchableUserFactory::new()->create(['id' => 1234]); $job = new RemoveFromSearch($models = RemoveableScoutCollection::make([$model])); @@ -47,7 +47,7 @@ public function test_models_are_deserialized_without_the_database() $this->assertInstanceOf(RemoveableScoutCollection::class, $job->models); $this->assertCount(1, $job->models); - $this->assertInstanceOf(User::class, $job->models->first()); + $this->assertInstanceOf(SearchableUser::class, $job->models->first()); $this->assertSame(1234, $job->models->first()->getScoutKey()); } diff --git a/tests/Feature/SearchableTest.php b/tests/Feature/SearchableTest.php index b33edd7e..908f283b 100644 --- a/tests/Feature/SearchableTest.php +++ b/tests/Feature/SearchableTest.php @@ -23,7 +23,7 @@ public function test_searchable_using_update_is_called_on_collection() $collection->shouldReceive('first->makeSearchableUsing')->with($collection)->once()->andReturn($collection); $collection->shouldReceive('first->searchableUsing->update')->with($collection)->once(); - $model = new SearchableModel(); + $model = new SearchableModel; $model->queueMakeSearchable($collection); } @@ -172,13 +172,13 @@ public function newQuery() $mock = m::spy(Builder::class); $mock->shouldReceive('when') - ->with(true, m::type('Closure')) - ->once() - ->andReturnUsing(function ($condition, $callback) use ($mock) { - $callback($mock); + ->with(true, m::type('Closure')) + ->once() + ->andReturnUsing(function ($condition, $callback) use ($mock) { + $callback($mock); - return $mock; - }); + return $mock; + }); $mock->shouldReceive('orderBy') ->with('model_stub_for_make_all_searchables.id') diff --git a/tests/Fixtures/SearchableModelWithSensitiveAttributes.php b/tests/Fixtures/SearchableModelWithSensitiveAttributes.php index 6601b185..87443499 100644 --- a/tests/Fixtures/SearchableModelWithSensitiveAttributes.php +++ b/tests/Fixtures/SearchableModelWithSensitiveAttributes.php @@ -21,8 +21,6 @@ class SearchableModelWithSensitiveAttributes extends Model /** * When updating a model, this method determines if we * should perform a search engine update or not. - * - * @return bool */ public function searchIndexShouldBeUpdated(): bool { diff --git a/tests/Fixtures/SearchableModelWithSoftDeletes.php b/tests/Fixtures/SearchableModelWithSoftDeletes.php index ed4a4260..a82234cf 100644 --- a/tests/Fixtures/SearchableModelWithSoftDeletes.php +++ b/tests/Fixtures/SearchableModelWithSoftDeletes.php @@ -8,8 +8,8 @@ class SearchableModelWithSoftDeletes extends Model { - use SoftDeletes; use Searchable; + use SoftDeletes; /** * The attributes that are mass assignable. diff --git a/tests/Integration/MeilisearchSearchableTest.php b/tests/Integration/MeilisearchSearchableTest.php index 0881fac1..8fdc4441 100644 --- a/tests/Integration/MeilisearchSearchableTest.php +++ b/tests/Integration/MeilisearchSearchableTest.php @@ -163,7 +163,7 @@ public function test_uses_different_indexes() $index->shouldReceive('rawSearch')->once()->andReturn([]); $engine = new MeilisearchEngine($client); - $builder = new Builder(new VersionableModel(), ''); + $builder = new Builder(new VersionableModel, ''); $engine->search($builder); } diff --git a/tests/Integration/SearchableTests.php b/tests/Integration/SearchableTests.php index ef25c85f..3bf9e366 100644 --- a/tests/Integration/SearchableTests.php +++ b/tests/Integration/SearchableTests.php @@ -107,8 +107,7 @@ protected function itCanUsePaginatedSearchWithQueryCallback() protected function itCanUsePaginatedSearchWithEmptyQueryCallback() { - $queryCallback = function ($query) { - }; + $queryCallback = function ($query) {}; return User::search('*')->query($queryCallback)->paginate(); } diff --git a/tests/Unit/Algolia3EngineTest.php b/tests/Unit/Algolia3EngineTest.php index 0fdb5043..2d87bc6f 100644 --- a/tests/Unit/Algolia3EngineTest.php +++ b/tests/Unit/Algolia3EngineTest.php @@ -3,90 +3,16 @@ namespace Laravel\Scout\Tests\Unit; use Algolia\AlgoliaSearch\SearchClient; -use Illuminate\Container\Container; -use Illuminate\Database\Eloquent\Collection; -use Illuminate\Support\Facades\Config; use Illuminate\Support\LazyCollection; use Laravel\Scout\Builder; -use Laravel\Scout\EngineManager; use Laravel\Scout\Engines\Algolia3Engine; -use Laravel\Scout\Jobs\RemoveFromSearch; -use Laravel\Scout\Tests\Fixtures\EmptySearchableModel; use Laravel\Scout\Tests\Fixtures\SearchableModel; -use Laravel\Scout\Tests\Fixtures\SoftDeletedEmptySearchableModel; use Mockery as m; -use PHPUnit\Framework\TestCase; +use Orchestra\Testbench\TestCase; use stdClass; class Algolia3EngineTest extends TestCase { - protected function setUp(): void - { - Config::shouldReceive('get')->with('scout.after_commit', m::any())->andReturn(false); - Config::shouldReceive('get')->with('scout.soft_delete', m::any())->andReturn(false); - } - - protected function tearDown(): void - { - Container::getInstance()->flush(); - m::close(); - } - - public function test_map_method_respects_order() - { - $client = m::mock(SearchClient::class); - $engine = new Algolia3Engine($client); - - $model = m::mock(stdClass::class); - $model->shouldReceive('getScoutModelsByIds')->andReturn($models = Collection::make([ - new SearchableModel(['id' => 1]), - new SearchableModel(['id' => 2]), - new SearchableModel(['id' => 3]), - new SearchableModel(['id' => 4]), - ])); - - $builder = m::mock(Builder::class); - - $results = $engine->map($builder, ['nbHits' => 4, 'hits' => [ - ['objectID' => 1, 'id' => 1], - ['objectID' => 2, 'id' => 2], - ['objectID' => 4, 'id' => 4], - ['objectID' => 3, 'id' => 3], - ]], $model); - - $this->assertCount(4, $results); - - // It's important we assert with array keys to ensure - // they have been reset after sorting. - $this->assertEquals([ - 0 => ['id' => 1], - 1 => ['id' => 2], - 2 => ['id' => 4], - 3 => ['id' => 3], - ], $results->toArray()); - } - - public function test_lazy_map_correctly_maps_results_to_models() - { - $client = m::mock(SearchClient::class); - $engine = new Algolia3Engine($client); - - $model = m::mock(stdClass::class); - $model->shouldReceive('queryScoutModelsByIds->cursor')->andReturn($models = LazyCollection::make([ - new SearchableModel(['id' => 1, 'name' => 'test']), - ])); - - $builder = m::mock(Builder::class); - - $results = $engine->lazyMap($builder, ['nbHits' => 1, 'hits' => [ - ['objectID' => 1, 'id' => 1, '_rankingInfo' => ['nbTypos' => 0]], - ]], $model); - - $this->assertCount(1, $results); - $this->assertEquals(['id' => 1, 'name' => 'test'], $results->first()->toArray()); - $this->assertEquals(['_rankingInfo' => ['nbTypos' => 0]], $results->first()->scoutMetaData()); - } - public function test_lazy_map_method_respects_order() { $client = m::mock(SearchClient::class); @@ -120,65 +46,4 @@ public function test_lazy_map_method_respects_order() 3 => ['id' => 3], ], $results->toArray()); } - - public function test_a_model_is_indexed_with_a_custom_algolia_key() - { - $client = m::mock(SearchClient::class); - $client->shouldReceive('initIndex')->with('table')->andReturn($index = m::mock(stdClass::class)); - $index->shouldReceive('saveObjects')->with([[ - 'id' => 1, - 'objectID' => 'my-algolia-key.1', - ]]); - - $engine = new Algolia3Engine($client); - $engine->update(Collection::make([new Algolia3CustomKeySearchableModel])); - } - - public function test_a_model_is_removed_with_a_custom_algolia_key() - { - $client = m::mock(SearchClient::class); - $client->shouldReceive('initIndex')->with('table')->andReturn($index = m::mock(stdClass::class)); - $index->shouldReceive('deleteObjects')->with(['my-algolia-key.1']); - - $engine = new Algolia3Engine($client); - $engine->delete(Collection::make([new Algolia3CustomKeySearchableModel(['id' => 1])])); - } - - public function test_flush_a_model_with_a_custom_algolia_key() - { - $client = m::mock(SearchClient::class); - $client->shouldReceive('initIndex')->with('table')->andReturn($index = m::mock(stdClass::class)); - $index->shouldReceive('clearObjects'); - - $engine = new Algolia3Engine($client); - $engine->flush(new Algolia3CustomKeySearchableModel); - } - - public function test_update_empty_searchable_array_does_not_add_objects_to_index() - { - $client = m::mock(SearchClient::class); - $client->shouldReceive('initIndex')->with('table')->andReturn($index = m::mock(stdClass::class)); - $index->shouldNotReceive('saveObjects'); - - $engine = new Algolia3Engine($client); - $engine->update(Collection::make([new EmptySearchableModel])); - } - - public function test_update_empty_searchable_array_from_soft_deleted_model_does_not_add_objects_to_index() - { - $client = m::mock(SearchClient::class); - $client->shouldReceive('initIndex')->with('table')->andReturn($index = m::mock('StdClass')); - $index->shouldNotReceive('saveObjects'); - - $engine = new Algolia3Engine($client, true); - $engine->update(Collection::make([new SoftDeletedEmptySearchableModel])); - } -} - -class Algolia3CustomKeySearchableModel extends SearchableModel -{ - public function getScoutKey() - { - return 'my-algolia-key.'.$this->getKey(); - } } diff --git a/tests/Unit/Algolia4EngineTest.php b/tests/Unit/Algolia4EngineTest.php index 49da1075..fef3ae5b 100644 --- a/tests/Unit/Algolia4EngineTest.php +++ b/tests/Unit/Algolia4EngineTest.php @@ -3,178 +3,16 @@ namespace Laravel\Scout\Tests\Unit; use Algolia\AlgoliaSearch\Api\SearchClient; -use Illuminate\Container\Container; use Illuminate\Database\Eloquent\Collection; -use Illuminate\Support\Facades\Config; -use Illuminate\Support\LazyCollection; use Laravel\Scout\Builder; -use Laravel\Scout\EngineManager; use Laravel\Scout\Engines\Algolia4Engine; -use Laravel\Scout\Jobs\RemoveFromSearch; -use Laravel\Scout\Tests\Fixtures\EmptySearchableModel; use Laravel\Scout\Tests\Fixtures\SearchableModel; -use Laravel\Scout\Tests\Fixtures\SoftDeletedEmptySearchableModel; use Mockery as m; -use PHPUnit\Framework\TestCase; +use Orchestra\Testbench\TestCase; use stdClass; class Algolia4EngineTest extends TestCase { - protected function setUp(): void - { - Config::shouldReceive('get')->with('scout.after_commit', m::any())->andReturn(false); - Config::shouldReceive('get')->with('scout.soft_delete', m::any())->andReturn(false); - } - - protected function tearDown(): void - { - Container::getInstance()->flush(); - m::close(); - } - - public function test_update_adds_objects_to_index() - { - $client = m::mock(SearchClient::class); - $client->shouldReceive('initIndex')->with('table')->andReturn($index = m::mock(stdClass::class)); - $index->shouldReceive('saveObjects')->with([[ - 'id' => 1, - 'objectID' => 1, - ]]); - - $engine = new Algolia4Engine($client); - $engine->update(Collection::make([new SearchableModel])); - } - - public function test_delete_removes_objects_to_index() - { - $client = m::mock(SearchClient::class); - $client->shouldReceive('deleteObjects')->with('table', [1]); - - $engine = new Algolia4Engine($client); - $engine->delete(Collection::make([new SearchableModel(['id' => 1])])); - } - - public function test_delete_removes_objects_to_index_with_a_custom_search_key() - { - $client = m::mock(SearchClient::class); - $client->shouldReceive('deleteObjects')->once()->with('table', ['my-algolia-key.5']); - - $engine = new Algolia4Engine($client); - $engine->delete(Collection::make([new Algolia4CustomKeySearchableModel(['id' => 5])])); - } - - public function test_delete_with_removeable_scout_collection_using_custom_search_key() - { - $job = new RemoveFromSearch(Collection::make([ - new Algolia4CustomKeySearchableModel(['id' => 5]), - ])); - - $job = unserialize(serialize($job)); - - $client = m::mock(SearchClient::class); - $client->shouldReceive('deleteObjects')->once()->with('table', ['my-algolia-key.5']); - - $engine = new Algolia4Engine($client); - $engine->delete($job->models); - } - - public function test_remove_from_search_job_uses_custom_search_key() - { - $job = new RemoveFromSearch(Collection::make([ - new Algolia4CustomKeySearchableModel(['id' => 5]), - ])); - - $job = unserialize(serialize($job)); - - Container::getInstance()->bind(EngineManager::class, function () { - $engine = m::mock(Algolia4Engine::class); - - $engine->shouldReceive('delete')->once()->with(m::on(function ($collection) { - $keyName = ($model = $collection->first())->getScoutKeyName(); - - return $model->getAttributes()[$keyName] === 'my-algolia-key.5'; - })); - - $manager = m::mock(EngineManager::class); - - $manager->shouldReceive('engine')->andReturn($engine); - - return $manager; - }); - - $job->handle(); - } - - public function test_search_sends_correct_parameters_to_algolia() - { - $client = m::mock(SearchClient::class); - $client->shouldReceive('searchSingleIndex')->with( - 'table', - ['query' => 'zonda'], - ['numericFilters' => ['foo=1']] - ); - - $engine = new Algolia4Engine($client); - $builder = new Builder(new SearchableModel, 'zonda'); - $builder->where('foo', 1); - $engine->search($builder); - } - - public function test_search_sends_correct_parameters_to_algolia_for_where_in_search() - { - $client = m::mock(SearchClient::class); - $client->shouldReceive('searchSingleIndex')->with( - 'table', - ['query' => 'zonda'], - ['numericFilters' => ['foo=1', ['bar=1', 'bar=2']]] - ); - - $engine = new Algolia4Engine($client); - $builder = new Builder(new SearchableModel, 'zonda'); - $builder->where('foo', 1)->whereIn('bar', [1, 2]); - $engine->search($builder); - } - - public function test_search_sends_correct_parameters_to_algolia_for_empty_where_in_search() - { - $client = m::mock(SearchClient::class); - $client->shouldReceive('searchSingleIndex')->with( - 'table', - ['query' => 'zonda'], - ['numericFilters' => ['foo=1', '0=1']] - ); - - $engine = new Algolia4Engine($client); - $builder = new Builder(new SearchableModel, 'zonda'); - $builder->where('foo', 1)->whereIn('bar', []); - $engine->search($builder); - } - - public function test_map_correctly_maps_results_to_models() - { - $client = m::mock(SearchClient::class); - $engine = new Algolia4Engine($client); - - $model = m::mock(stdClass::class); - - $model->shouldReceive('getScoutModelsByIds')->andReturn($models = Collection::make([ - new SearchableModel(['id' => 1, 'name' => 'test']), - ])); - - $builder = m::mock(Builder::class); - - $results = $engine->map($builder, [ - 'nbHits' => 1, - 'hits' => [ - ['objectID' => 1, 'id' => 1, '_rankingInfo' => ['nbTypos' => 0]], - ], - ], $model); - - $this->assertCount(1, $results); - $this->assertEquals(['id' => 1, 'name' => 'test'], $results->first()->toArray()); - $this->assertEquals(['_rankingInfo' => ['nbTypos' => 0]], $results->first()->scoutMetaData()); - } - public function test_map_method_respects_order() { $client = m::mock(SearchClient::class); @@ -208,118 +46,4 @@ public function test_map_method_respects_order() 3 => ['id' => 3], ], $results->toArray()); } - - public function test_lazy_map_correctly_maps_results_to_models() - { - $client = m::mock(SearchClient::class); - $engine = new Algolia4Engine($client); - - $model = m::mock(stdClass::class); - $model->shouldReceive('queryScoutModelsByIds->cursor')->andReturn($models = LazyCollection::make([ - new SearchableModel(['id' => 1, 'name' => 'test']), - ])); - - $builder = m::mock(Builder::class); - - $results = $engine->lazyMap($builder, ['nbHits' => 1, 'hits' => [ - ['objectID' => 1, 'id' => 1, '_rankingInfo' => ['nbTypos' => 0]], - ]], $model); - - $this->assertCount(1, $results); - $this->assertEquals(['id' => 1, 'name' => 'test'], $results->first()->toArray()); - $this->assertEquals(['_rankingInfo' => ['nbTypos' => 0]], $results->first()->scoutMetaData()); - } - - public function test_lazy_map_method_respects_order() - { - $client = m::mock(SearchClient::class); - $engine = new Algolia4Engine($client); - - $model = m::mock(stdClass::class); - $model->shouldReceive('queryScoutModelsByIds->cursor')->andReturn($models = LazyCollection::make([ - new SearchableModel(['id' => 1]), - new SearchableModel(['id' => 2]), - new SearchableModel(['id' => 3]), - new SearchableModel(['id' => 4]), - ])); - - $builder = m::mock(Builder::class); - - $results = $engine->lazyMap($builder, ['nbHits' => 4, 'hits' => [ - ['objectID' => 1, 'id' => 1], - ['objectID' => 2, 'id' => 2], - ['objectID' => 4, 'id' => 4], - ['objectID' => 3, 'id' => 3], - ]], $model); - - $this->assertCount(4, $results); - - // It's important we assert with array keys to ensure - // they have been reset after sorting. - $this->assertEquals([ - 0 => ['id' => 1], - 1 => ['id' => 2], - 2 => ['id' => 4], - 3 => ['id' => 3], - ], $results->toArray()); - } - - public function test_a_model_is_indexed_with_a_custom_algolia_key() - { - $client = m::mock(SearchClient::class); - $client->shouldReceive('initIndex')->with('table')->andReturn($index = m::mock(stdClass::class)); - $index->shouldReceive('saveObjects')->with([[ - 'id' => 1, - 'objectID' => 'my-algolia-key.1', - ]]); - - $engine = new Algolia4Engine($client); - $engine->update(Collection::make([new Algolia4CustomKeySearchableModel])); - } - - public function test_a_model_is_removed_with_a_custom_algolia_key() - { - $client = m::mock(SearchClient::class); - $client->shouldReceive('deleteObjects')->with('table', ['my-algolia-key.1']); - - $engine = new Algolia4Engine($client); - $engine->delete(Collection::make([new Algolia4CustomKeySearchableModel(['id' => 1])])); - } - - public function test_flush_a_model_with_a_custom_algolia_key() - { - $client = m::mock(SearchClient::class); - $client->shouldReceive('clearObjects')->with('table'); - - $engine = new Algolia4Engine($client); - $engine->flush(new Algolia4CustomKeySearchableModel); - } - - public function test_update_empty_searchable_array_does_not_add_objects_to_index() - { - $client = m::mock(SearchClient::class); - $client->shouldReceive('initIndex')->with('table')->andReturn($index = m::mock(stdClass::class)); - $index->shouldNotReceive('saveObjects'); - - $engine = new Algolia4Engine($client); - $engine->update(Collection::make([new EmptySearchableModel])); - } - - public function test_update_empty_searchable_array_from_soft_deleted_model_does_not_add_objects_to_index() - { - $client = m::mock(SearchClient::class); - $client->shouldReceive('initIndex')->with('table')->andReturn($index = m::mock('StdClass')); - $index->shouldNotReceive('saveObjects'); - - $engine = new Algolia4Engine($client, true); - $engine->update(Collection::make([new SoftDeletedEmptySearchableModel])); - } -} - -class Algolia4CustomKeySearchableModel extends SearchableModel -{ - public function getScoutKey() - { - return 'my-algolia-key.'.$this->getKey(); - } } diff --git a/tests/Unit/MeilisearchEngineTest.php b/tests/Unit/MeilisearchEngineTest.php index ff57c7f9..faebbcf8 100644 --- a/tests/Unit/MeilisearchEngineTest.php +++ b/tests/Unit/MeilisearchEngineTest.php @@ -47,7 +47,7 @@ public function test_update_adds_objects_to_index() ]); $engine = new MeilisearchEngine($client); - $engine->update(Collection::make([new SearchableModel()])); + $engine->update(Collection::make([new SearchableModel])); } public function test_delete_removes_objects_to_index() @@ -122,7 +122,7 @@ public function test_search_sends_correct_parameters_to_meilisearch() ]); $engine = new MeilisearchEngine($client); - $builder = new Builder(new SearchableModel(), 'mustang', function ($meilisearch, $query, $options) { + $builder = new Builder(new SearchableModel, 'mustang', function ($meilisearch, $query, $options) { $options['filter'] = 'foo=1 AND bar=2'; return $meilisearch->search($query, $options); @@ -140,7 +140,7 @@ public function test_search_includes_at_least_scoutKeyName_in_attributesToRetrie ]); $engine = new MeilisearchEngine($client); - $builder = new Builder(new SearchableModel(), 'mustang', function ($meilisearch, $query, $options) { + $builder = new Builder(new SearchableModel, 'mustang', function ($meilisearch, $query, $options) { $options['filter'] = 'foo=1 AND bar=2'; return $meilisearch->search($query, $options); @@ -152,7 +152,7 @@ public function test_search_includes_at_least_scoutKeyName_in_attributesToRetrie public function test_submitting_a_callable_search_with_search_method_returns_array() { $builder = new Builder( - new SearchableModel(), + new SearchableModel, $query = 'mustang', $callable = function ($meilisearch, $query, $options) { $options['filter'] = 'foo=1'; @@ -181,7 +181,7 @@ public function test_submitting_a_callable_search_with_search_method_returns_arr public function test_submitting_a_callable_search_with_raw_search_method_works() { $builder = new Builder( - new SearchableModel(), + new SearchableModel, $query = 'mustang', $callable = function ($meilisearch, $query, $options) { $options['filter'] = 'foo=1'; @@ -418,7 +418,7 @@ public function test_flush_a_model_with_a_custom_meilisearch_key() $index->shouldReceive('deleteAllDocuments'); $engine = new MeilisearchEngine($client); - $engine->flush(new MeilisearchCustomKeySearchableModel()); + $engine->flush(new MeilisearchCustomKeySearchableModel); } public function test_update_empty_searchable_array_does_not_add_documents_to_index() @@ -428,7 +428,7 @@ public function test_update_empty_searchable_array_does_not_add_documents_to_ind $index->shouldNotReceive('addDocuments'); $engine = new MeilisearchEngine($client); - $engine->update(Collection::make([new EmptySearchableModel()])); + $engine->update(Collection::make([new EmptySearchableModel])); } public function test_pagination_correct_parameters() @@ -445,7 +445,7 @@ public function test_pagination_correct_parameters() ]); $engine = new MeilisearchEngine($client); - $builder = new Builder(new SearchableModel(), 'mustang', function ($meilisearch, $query, $options) { + $builder = new Builder(new SearchableModel, 'mustang', function ($meilisearch, $query, $options) { $options['filter'] = 'foo=1'; return $meilisearch->search($query, $options); @@ -468,7 +468,7 @@ public function test_pagination_sorted_parameter() ]); $engine = new MeilisearchEngine($client); - $builder = new Builder(new SearchableModel(), 'mustang', function ($meilisearch, $query, $options) { + $builder = new Builder(new SearchableModel, 'mustang', function ($meilisearch, $query, $options) { $options['filter'] = 'foo=1'; return $meilisearch->search($query, $options); @@ -485,7 +485,7 @@ public function test_update_empty_searchable_array_from_soft_deleted_model_does_ $index->shouldNotReceive('addDocuments'); $engine = new MeilisearchEngine($client, true); - $engine->update(Collection::make([new SoftDeletedEmptySearchableModel()])); + $engine->update(Collection::make([new SoftDeletedEmptySearchableModel])); } public function test_engine_forwards_calls_to_meilisearch_client() @@ -501,7 +501,7 @@ public function test_updating_empty_eloquent_collection_does_nothing() { $client = m::mock(Client::class); $engine = new MeilisearchEngine($client); - $engine->update(new Collection()); + $engine->update(new Collection); $this->assertTrue(true); } @@ -512,13 +512,13 @@ public function test_performing_search_without_callback_works() $index->shouldReceive('rawSearch')->once()->andReturn([]); $engine = new MeilisearchEngine($client); - $builder = new Builder(new SearchableModel(), ''); + $builder = new Builder(new SearchableModel, ''); $engine->search($builder); } public function test_where_conditions_are_applied() { - $builder = new Builder(new SearchableModel(), ''); + $builder = new Builder(new SearchableModel, ''); $builder->where('foo', 'bar'); $builder->where('key', 'value'); $client = m::mock(Client::class); @@ -534,7 +534,7 @@ public function test_where_conditions_are_applied() public function test_where_in_conditions_are_applied() { - $builder = new Builder(new SearchableModel(), ''); + $builder = new Builder(new SearchableModel, ''); $builder->where('foo', 'bar'); $builder->where('bar', 'baz'); $builder->whereIn('qux', [1, 2]); @@ -552,7 +552,7 @@ public function test_where_in_conditions_are_applied() public function test_where_not_in_conditions_are_applied() { - $builder = new Builder(new SearchableModel(), ''); + $builder = new Builder(new SearchableModel, ''); $builder->where('foo', 'bar'); $builder->where('bar', 'baz'); $builder->whereIn('qux', [1, 2]); @@ -571,7 +571,7 @@ public function test_where_not_in_conditions_are_applied() public function test_where_in_conditions_are_applied_without_other_conditions() { - $builder = new Builder(new SearchableModel(), ''); + $builder = new Builder(new SearchableModel, ''); $builder->whereIn('qux', [1, 2]); $builder->whereIn('quux', [1, 2]); $client = m::mock(Client::class); @@ -587,7 +587,7 @@ public function test_where_in_conditions_are_applied_without_other_conditions() public function test_where_not_in_conditions_are_applied_without_other_conditions() { - $builder = new Builder(new SearchableModel(), ''); + $builder = new Builder(new SearchableModel, ''); $builder->whereIn('qux', [1, 2]); $builder->whereIn('quux', [1, 2]); $builder->whereNotIn('eaea', [3]); @@ -604,7 +604,7 @@ public function test_where_not_in_conditions_are_applied_without_other_condition public function test_empty_where_in_conditions_are_applied_correctly() { - $builder = new Builder(new SearchableModel(), ''); + $builder = new Builder(new SearchableModel, ''); $builder->where('foo', 'bar'); $builder->where('bar', 'baz'); $builder->whereIn('qux', []); @@ -621,9 +621,9 @@ public function test_empty_where_in_conditions_are_applied_correctly() public function test_engine_returns_hits_entry_from_search_response() { - $this->assertTrue(3 === (new MeilisearchEngine(m::mock(Client::class)))->getTotalCount([ + $this->assertTrue((new MeilisearchEngine(m::mock(Client::class)))->getTotalCount([ 'totalHits' => 3, - ])); + ]) === 3); } public function test_delete_all_indexes_works_with_pagination() diff --git a/tests/Unit/SearchableScopeTest.php b/tests/Unit/SearchableScopeTest.php index 42eabb7c..5373414f 100644 --- a/tests/Unit/SearchableScopeTest.php +++ b/tests/Unit/SearchableScopeTest.php @@ -44,6 +44,6 @@ public function test_chunks_by_id() return true; }))->once(); - (new SearchableScope())->extend($builder); + (new SearchableScope)->extend($builder); } } diff --git a/workbench/app/Models/Chirp.php b/workbench/app/Models/Chirp.php index 205684c0..c9df9556 100644 --- a/workbench/app/Models/Chirp.php +++ b/workbench/app/Models/Chirp.php @@ -31,4 +31,11 @@ public function getScoutKeyName() { return 'scout_id'; } + + public function toSearchableArray() + { + return $_ENV['searchable.chirp'] ?? [ + 'content' => $this->content, + ]; + } } diff --git a/workbench/app/Models/SearchableUser.php b/workbench/app/Models/SearchableUser.php new file mode 100644 index 00000000..6e27c363 --- /dev/null +++ b/workbench/app/Models/SearchableUser.php @@ -0,0 +1,20 @@ + $this->id, + 'name' => $this->name, + 'email' => $this->email, + ]; + } +} diff --git a/workbench/app/Models/User.php b/workbench/app/Models/User.php index 06e7479e..ec189c0b 100644 --- a/workbench/app/Models/User.php +++ b/workbench/app/Models/User.php @@ -3,12 +3,9 @@ namespace Workbench\App\Models; use Illuminate\Foundation\Auth\User as Authenticatable; -use Laravel\Scout\Searchable; class User extends Authenticatable { - use Searchable; - /** * The table associated with the model. * @@ -46,18 +43,4 @@ class User extends Authenticatable 'email_verified_at' => 'datetime', 'password' => 'hashed', ]; - - /** - * Get the indexable data array for the model. - * - * @return array - */ - public function toSearchableArray() - { - return [ - 'id' => $this->id, - 'name' => $this->name, - 'email' => $this->email, - ]; - } } diff --git a/workbench/database/factories/SearchableUserFactory.php b/workbench/database/factories/SearchableUserFactory.php new file mode 100644 index 00000000..4adccadb --- /dev/null +++ b/workbench/database/factories/SearchableUserFactory.php @@ -0,0 +1,15 @@ + + */ + protected $model = SearchableUser::class; +} From b7e2629cb0c24a5bc859e728bafae979389d44aa Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 12 Nov 2024 13:26:30 +0800 Subject: [PATCH 11/32] wip Signed-off-by: Mior Muhammad Zaki --- .../Jobs/RemovableScoutCollectionTest.php | 10 +++++++++ tests/Unit/Algolia3EngineTest.php | 21 ++++++++++++++++++- tests/Unit/Algolia4EngineTest.php | 16 ++++++++++++++ tests/Unit/RemoveableScoutCollectionTest.php | 10 --------- 4 files changed, 46 insertions(+), 11 deletions(-) diff --git a/tests/Feature/Jobs/RemovableScoutCollectionTest.php b/tests/Feature/Jobs/RemovableScoutCollectionTest.php index 52821034..2dae2b2c 100644 --- a/tests/Feature/Jobs/RemovableScoutCollectionTest.php +++ b/tests/Feature/Jobs/RemovableScoutCollectionTest.php @@ -9,6 +9,16 @@ class RemovableScoutCollectionTest extends TestCase { + public function test_get_queuable_ids() + { + $collection = RemoveableScoutCollection::make([ + SearchableUserFactory::new()->make(['id' => 1]), + SearchableUserFactory::new()->make(['id' => 2]), + ]); + + $this->assertEquals([1, 2], $collection->getQueueableIds()); + } + public function test_removeable_scout_collection_returns_scout_keys() { $collection = RemoveableScoutCollection::make([ diff --git a/tests/Unit/Algolia3EngineTest.php b/tests/Unit/Algolia3EngineTest.php index 2d87bc6f..d956c3b0 100644 --- a/tests/Unit/Algolia3EngineTest.php +++ b/tests/Unit/Algolia3EngineTest.php @@ -3,16 +3,35 @@ namespace Laravel\Scout\Tests\Unit; use Algolia\AlgoliaSearch\SearchClient; +use Illuminate\Container\Container; +use Illuminate\Database\Eloquent\Collection; +use Illuminate\Support\Facades\Config; use Illuminate\Support\LazyCollection; use Laravel\Scout\Builder; +use Laravel\Scout\EngineManager; use Laravel\Scout\Engines\Algolia3Engine; +use Laravel\Scout\Jobs\RemoveFromSearch; +use Laravel\Scout\Tests\Fixtures\EmptySearchableModel; use Laravel\Scout\Tests\Fixtures\SearchableModel; +use Laravel\Scout\Tests\Fixtures\SoftDeletedEmptySearchableModel; use Mockery as m; -use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\TestCase; use stdClass; class Algolia3EngineTest extends TestCase { + protected function setUp(): void + { + Config::shouldReceive('get')->with('scout.after_commit', m::any())->andReturn(false); + Config::shouldReceive('get')->with('scout.soft_delete', m::any())->andReturn(false); + } + + protected function tearDown(): void + { + Container::getInstance()->flush(); + m::close(); + } + public function test_lazy_map_method_respects_order() { $client = m::mock(SearchClient::class); diff --git a/tests/Unit/Algolia4EngineTest.php b/tests/Unit/Algolia4EngineTest.php index fef3ae5b..56800c8b 100644 --- a/tests/Unit/Algolia4EngineTest.php +++ b/tests/Unit/Algolia4EngineTest.php @@ -3,7 +3,9 @@ namespace Laravel\Scout\Tests\Unit; use Algolia\AlgoliaSearch\Api\SearchClient; +use Illuminate\Container\Container; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Support\Facades\Config; use Laravel\Scout\Builder; use Laravel\Scout\Engines\Algolia4Engine; use Laravel\Scout\Tests\Fixtures\SearchableModel; @@ -13,6 +15,20 @@ class Algolia4EngineTest extends TestCase { + + protected function setUp(): void + { + Config::shouldReceive('get')->with('scout.after_commit', m::any())->andReturn(false); + Config::shouldReceive('get')->with('scout.soft_delete', m::any())->andReturn(false); + } + + protected function tearDown(): void + { + Container::getInstance()->flush(); + + m::close(); + } + public function test_map_method_respects_order() { $client = m::mock(SearchClient::class); diff --git a/tests/Unit/RemoveableScoutCollectionTest.php b/tests/Unit/RemoveableScoutCollectionTest.php index 1acef29c..e54928d0 100644 --- a/tests/Unit/RemoveableScoutCollectionTest.php +++ b/tests/Unit/RemoveableScoutCollectionTest.php @@ -16,16 +16,6 @@ protected function setUp(): void Config::shouldReceive('get')->with('scout.soft_delete', m::any())->andReturn(false); } - public function test_get_queuable_ids() - { - $collection = RemoveableScoutCollection::make([ - new SearchableModel(['id' => 1]), - new SearchableModel(['id' => 2]), - ]); - - $this->assertEquals([1, 2], $collection->getQueueableIds()); - } - public function test_get_queuable_ids_resolves_custom_scout_keys() { $collection = RemoveableScoutCollection::make([ From aa7cc8dee3181cea4c3b3288950a0b4b3a1e80a7 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Tue, 12 Nov 2024 05:26:57 +0000 Subject: [PATCH 12/32] Apply fixes from StyleCI --- tests/Integration/SearchableTests.php | 3 ++- tests/Unit/Algolia3EngineTest.php | 3 --- tests/Unit/Algolia4EngineTest.php | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/Integration/SearchableTests.php b/tests/Integration/SearchableTests.php index 3bf9e366..ef25c85f 100644 --- a/tests/Integration/SearchableTests.php +++ b/tests/Integration/SearchableTests.php @@ -107,7 +107,8 @@ protected function itCanUsePaginatedSearchWithQueryCallback() protected function itCanUsePaginatedSearchWithEmptyQueryCallback() { - $queryCallback = function ($query) {}; + $queryCallback = function ($query) { + }; return User::search('*')->query($queryCallback)->paginate(); } diff --git a/tests/Unit/Algolia3EngineTest.php b/tests/Unit/Algolia3EngineTest.php index ce0d78af..df64646b 100644 --- a/tests/Unit/Algolia3EngineTest.php +++ b/tests/Unit/Algolia3EngineTest.php @@ -4,14 +4,11 @@ use Algolia\AlgoliaSearch\SearchClient; use Illuminate\Container\Container; -use Illuminate\Database\Eloquent\Collection; use Illuminate\Support\Facades\Config; use Illuminate\Support\LazyCollection; use Laravel\Scout\Builder; use Laravel\Scout\Engines\Algolia3Engine; -use Laravel\Scout\Tests\Fixtures\EmptySearchableModel; use Laravel\Scout\Tests\Fixtures\SearchableModel; -use Laravel\Scout\Tests\Fixtures\SoftDeletedEmptySearchableModel; use Mockery as m; use PHPUnit\Framework\TestCase; use stdClass; diff --git a/tests/Unit/Algolia4EngineTest.php b/tests/Unit/Algolia4EngineTest.php index 56800c8b..eec9bbe3 100644 --- a/tests/Unit/Algolia4EngineTest.php +++ b/tests/Unit/Algolia4EngineTest.php @@ -15,7 +15,6 @@ class Algolia4EngineTest extends TestCase { - protected function setUp(): void { Config::shouldReceive('get')->with('scout.after_commit', m::any())->andReturn(false); From a877eb2337f09e3152dd77bba9ca47d614c642e9 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 12 Nov 2024 13:49:05 +0800 Subject: [PATCH 13/32] wip Signed-off-by: Mior Muhammad Zaki --- tests/Feature/Jobs/MakeSearchableTest.php | 33 ++++++++++++++ .../Jobs/RemovableScoutCollectionTest.php | 17 ++++++++ tests/Feature/Jobs/RemoveFromSearchTest.php | 5 +-- tests/Unit/Algolia3EngineTest.php | 34 +++++++++++++++ tests/Unit/Algolia4EngineTest.php | 34 +++++++++++++++ tests/Unit/MakeSearchableTest.php | 2 +- tests/Unit/RemoveableScoutCollectionTest.php | 43 ------------------- 7 files changed, 121 insertions(+), 47 deletions(-) create mode 100644 tests/Feature/Jobs/MakeSearchableTest.php delete mode 100644 tests/Unit/RemoveableScoutCollectionTest.php diff --git a/tests/Feature/Jobs/MakeSearchableTest.php b/tests/Feature/Jobs/MakeSearchableTest.php new file mode 100644 index 00000000..958e49a2 --- /dev/null +++ b/tests/Feature/Jobs/MakeSearchableTest.php @@ -0,0 +1,33 @@ +create(); + + $job = new MakeSearchable($collection = Collection::make([$model])); + + $this->app->make('scout.spied')->shouldReceive('update')->with($collection)->once(); + + $job->handle(); + } +} diff --git a/tests/Feature/Jobs/RemovableScoutCollectionTest.php b/tests/Feature/Jobs/RemovableScoutCollectionTest.php index 2dae2b2c..e03098c9 100644 --- a/tests/Feature/Jobs/RemovableScoutCollectionTest.php +++ b/tests/Feature/Jobs/RemovableScoutCollectionTest.php @@ -19,6 +19,23 @@ public function test_get_queuable_ids() $this->assertEquals([1, 2], $collection->getQueueableIds()); } + public function test_get_queuable_ids_resolves_custom_scout_keys() + { + $collection = RemoveableScoutCollection::make([ + ChirpFactory::new()->make(['scout_id' => 'custom-key.1']), + ChirpFactory::new()->make(['scout_id' => 'custom-key.2']), + ChirpFactory::new()->make(['scout_id' => 'custom-key.3']), + ChirpFactory::new()->make(['scout_id' => 'custom-key.4']), + ]); + + $this->assertEquals([ + 'custom-key.1', + 'custom-key.2', + 'custom-key.3', + 'custom-key.4', + ], $collection->getQueueableIds()); + } + public function test_removeable_scout_collection_returns_scout_keys() { $collection = RemoveableScoutCollection::make([ diff --git a/tests/Feature/Jobs/RemoveFromSearchTest.php b/tests/Feature/Jobs/RemoveFromSearchTest.php index ab4b15b6..fbf1dafa 100644 --- a/tests/Feature/Jobs/RemoveFromSearchTest.php +++ b/tests/Feature/Jobs/RemoveFromSearchTest.php @@ -2,8 +2,7 @@ namespace Laravel\Scout\Tests\Feature\Jobs; -//use Illuminate\Database\Eloquent\Collection; -use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Foundation\Testing\LazilyRefreshDatabase; use Illuminate\Support\Str; use Laravel\Scout\Jobs\RemoveableScoutCollection; use Laravel\Scout\Jobs\RemoveFromSearch; @@ -23,7 +22,7 @@ #[WithMigration] class RemoveFromSearchTest extends TestCase { - use RefreshDatabase; + use LazilyRefreshDatabase; use WithWorkbench; public function test_handle_passes_the_collection_to_engine() diff --git a/tests/Unit/Algolia3EngineTest.php b/tests/Unit/Algolia3EngineTest.php index ce0d78af..cddeb4af 100644 --- a/tests/Unit/Algolia3EngineTest.php +++ b/tests/Unit/Algolia3EngineTest.php @@ -63,4 +63,38 @@ public function test_lazy_map_method_respects_order() 3 => ['id' => 3], ], $results->toArray()); } + + public function test_map_method_respects_order() + { + $client = m::mock(SearchClient::class); + $engine = new Algolia3Engine($client); + + $model = m::mock(stdClass::class); + $model->shouldReceive('getScoutModelsByIds')->andReturn($models = Collection::make([ + new SearchableModel(['id' => 1]), + new SearchableModel(['id' => 2]), + new SearchableModel(['id' => 3]), + new SearchableModel(['id' => 4]), + ])); + + $builder = m::mock(Builder::class); + + $results = $engine->map($builder, ['nbHits' => 4, 'hits' => [ + ['objectID' => 1, 'id' => 1], + ['objectID' => 2, 'id' => 2], + ['objectID' => 4, 'id' => 4], + ['objectID' => 3, 'id' => 3], + ]], $model); + + $this->assertCount(4, $results); + + // It's important we assert with array keys to ensure + // they have been reset after sorting. + $this->assertEquals([ + 0 => ['id' => 1], + 1 => ['id' => 2], + 2 => ['id' => 4], + 3 => ['id' => 3], + ], $results->toArray()); + } } diff --git a/tests/Unit/Algolia4EngineTest.php b/tests/Unit/Algolia4EngineTest.php index 56800c8b..c2c9dfa6 100644 --- a/tests/Unit/Algolia4EngineTest.php +++ b/tests/Unit/Algolia4EngineTest.php @@ -6,6 +6,7 @@ use Illuminate\Container\Container; use Illuminate\Database\Eloquent\Collection; use Illuminate\Support\Facades\Config; +use Illuminate\Support\LazyCollection; use Laravel\Scout\Builder; use Laravel\Scout\Engines\Algolia4Engine; use Laravel\Scout\Tests\Fixtures\SearchableModel; @@ -28,6 +29,39 @@ protected function tearDown(): void m::close(); } + public function test_lazy_map_method_respects_order() + { + $client = m::mock(SearchClient::class); + $engine = new Algolia4Engine($client); + + $model = m::mock(stdClass::class); + $model->shouldReceive('queryScoutModelsByIds->cursor')->andReturn($models = LazyCollection::make([ + new SearchableModel(['id' => 1]), + new SearchableModel(['id' => 2]), + new SearchableModel(['id' => 3]), + new SearchableModel(['id' => 4]), + ])); + + $builder = m::mock(Builder::class); + + $results = $engine->lazyMap($builder, ['nbHits' => 4, 'hits' => [ + ['objectID' => 1, 'id' => 1], + ['objectID' => 2, 'id' => 2], + ['objectID' => 4, 'id' => 4], + ['objectID' => 3, 'id' => 3], + ]], $model); + + $this->assertCount(4, $results); + + // It's important we assert with array keys to ensure + // they have been reset after sorting. + $this->assertEquals([ + 0 => ['id' => 1], + 1 => ['id' => 2], + 2 => ['id' => 4], + 3 => ['id' => 3], + ], $results->toArray()); + } public function test_map_method_respects_order() { diff --git a/tests/Unit/MakeSearchableTest.php b/tests/Unit/MakeSearchableTest.php index 5d537e00..e3b8c3a6 100644 --- a/tests/Unit/MakeSearchableTest.php +++ b/tests/Unit/MakeSearchableTest.php @@ -21,7 +21,7 @@ public function test_handle_passes_the_collection_to_engine() $model = m::mock(new SearchableModel)->makePartial(), ])); - $model->shouldReceive('searchableUsing->update')->with($collection); + $model->shouldReceive('searchableUsing->update')->with($collection)->once(); $job->handle(); } diff --git a/tests/Unit/RemoveableScoutCollectionTest.php b/tests/Unit/RemoveableScoutCollectionTest.php deleted file mode 100644 index e54928d0..00000000 --- a/tests/Unit/RemoveableScoutCollectionTest.php +++ /dev/null @@ -1,43 +0,0 @@ -with('scout.after_commit', m::any())->andReturn(false); - Config::shouldReceive('get')->with('scout.soft_delete', m::any())->andReturn(false); - } - - public function test_get_queuable_ids_resolves_custom_scout_keys() - { - $collection = RemoveableScoutCollection::make([ - new SearchCustomKeySearchableModel(['id' => 1]), - new SearchCustomKeySearchableModel(['id' => 2]), - new SearchCustomKeySearchableModel(['id' => 3]), - new SearchCustomKeySearchableModel(['id' => 4]), - ]); - - $this->assertEquals([ - 'custom-key.1', - 'custom-key.2', - 'custom-key.3', - 'custom-key.4', - ], $collection->getQueueableIds()); - } -} - -class SearchCustomKeySearchableModel extends SearchableModel -{ - public function getScoutKey() - { - return 'custom-key.'.$this->getKey(); - } -} From 3e66edb81acf71f244d263b0bf6cb87c601a91f1 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 12 Nov 2024 13:49:33 +0800 Subject: [PATCH 14/32] wip Signed-off-by: Mior Muhammad Zaki --- tests/Integration/SearchableTests.php | 3 +-- tests/Integration/TestCase.php | 2 -- tests/Unit/Algolia4EngineTest.php | 1 + 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/Integration/SearchableTests.php b/tests/Integration/SearchableTests.php index ef25c85f..3bf9e366 100644 --- a/tests/Integration/SearchableTests.php +++ b/tests/Integration/SearchableTests.php @@ -107,8 +107,7 @@ protected function itCanUsePaginatedSearchWithQueryCallback() protected function itCanUsePaginatedSearchWithEmptyQueryCallback() { - $queryCallback = function ($query) { - }; + $queryCallback = function ($query) {}; return User::search('*')->query($queryCallback)->paginate(); } diff --git a/tests/Integration/TestCase.php b/tests/Integration/TestCase.php index 73f6be09..5722e37f 100644 --- a/tests/Integration/TestCase.php +++ b/tests/Integration/TestCase.php @@ -24,8 +24,6 @@ protected function importScoutIndexFrom($model = null) /** * Clean up the testing environment before the next test case. - * - * @return void */ public static function tearDownAfterClass(): void { diff --git a/tests/Unit/Algolia4EngineTest.php b/tests/Unit/Algolia4EngineTest.php index 200c3c0a..965878db 100644 --- a/tests/Unit/Algolia4EngineTest.php +++ b/tests/Unit/Algolia4EngineTest.php @@ -28,6 +28,7 @@ protected function tearDown(): void m::close(); } + public function test_lazy_map_method_respects_order() { $client = m::mock(SearchClient::class); From 0995862b215327f8ab1534bdc2336bd9a45f4e91 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Tue, 12 Nov 2024 05:49:44 +0000 Subject: [PATCH 15/32] Apply fixes from StyleCI --- tests/Integration/SearchableTests.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Integration/SearchableTests.php b/tests/Integration/SearchableTests.php index 3bf9e366..ef25c85f 100644 --- a/tests/Integration/SearchableTests.php +++ b/tests/Integration/SearchableTests.php @@ -107,7 +107,8 @@ protected function itCanUsePaginatedSearchWithQueryCallback() protected function itCanUsePaginatedSearchWithEmptyQueryCallback() { - $queryCallback = function ($query) {}; + $queryCallback = function ($query) { + }; return User::search('*')->query($queryCallback)->paginate(); } From 72fce0051815907374b0fa3f7607550d5ba166ee Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 12 Nov 2024 14:36:28 +0800 Subject: [PATCH 16/32] wip Signed-off-by: Mior Muhammad Zaki --- tests/Feature/Engines/Algolia3EngineTest.php | 80 +++-- tests/Feature/Engines/Algolia4EngineTest.php | 62 ++-- tests/Feature/Engines/MeilisearchTest.php | 302 ++++++++++++++++++ tests/Unit/Algolia3EngineTest.php | 1 + tests/Unit/MakeSearchableTest.php | 28 -- tests/Unit/MeilisearchEngineTest.php | 274 +--------------- .../Unit/ModelObserverWithSoftDeletesTest.php | 2 +- workbench/app/Models/Chirp.php | 2 + .../2024_11_12_030345_create_chirps_table.php | 1 + 9 files changed, 374 insertions(+), 378 deletions(-) create mode 100644 tests/Feature/Engines/MeilisearchTest.php delete mode 100644 tests/Unit/MakeSearchableTest.php diff --git a/tests/Feature/Engines/Algolia3EngineTest.php b/tests/Feature/Engines/Algolia3EngineTest.php index 019dd19d..fe1ae945 100644 --- a/tests/Feature/Engines/Algolia3EngineTest.php +++ b/tests/Feature/Engines/Algolia3EngineTest.php @@ -23,7 +23,7 @@ use function Orchestra\Testbench\after_resolving; -#[WithConfig('scout.driver', 'algolia')] +#[WithConfig('scout.driver', 'algolia3-testing')] #[WithMigration] class Algolia3EngineTest extends TestCase { @@ -37,7 +37,7 @@ protected function defineEnvironment($app) after_resolving($app, EngineManager::class, function ($manager) { $this->client = m::spy(SearchClient::class); - $manager->extend('algolia', fn () => new Algolia3Engine($this->client, config('scout.soft_delete'))); + $manager->extend('algolia3-testing', fn () => new Algolia3Engine($this->client, config('scout.soft_delete'))); }); $this->beforeApplicationDestroyed(function () { @@ -49,15 +49,15 @@ public function test_update_adds_objects_to_index() { $model = SearchableUserFactory::new()->createQuietly(); - $engine = $this->app->make(EngineManager::class)->engine('algolia'); + $engine = $this->app->make(EngineManager::class)->engine(); - $this->client->shouldReceive('initIndex')->with('users')->once()->andReturn($index = m::mock(stdClass::class)); - $index->shouldReceive('saveObjects')->with([[ + $this->client->shouldReceive('initIndex')->once()->with('users')->andReturn($index = m::mock(stdClass::class)); + $index->shouldReceive('saveObjects')->once()->with([[ 'id' => $model->getKey(), 'name' => $model->name, 'email' => $model->email, 'objectID' => $model->getScoutKey(), - ]])->once(); + ]]); $engine->update(Collection::make([$model])); } @@ -66,10 +66,10 @@ public function test_delete_removes_objects_to_index() { $model = SearchableUserFactory::new()->createQuietly(); - $engine = $this->app->make(EngineManager::class)->engine('algolia'); + $engine = $this->app->make(EngineManager::class)->engine(); - $this->client->shouldReceive('initIndex')->with('users')->once()->andReturn($index = m::mock(stdClass::class)); - $index->shouldReceive('deleteObjects')->with([1])->once(); + $this->client->shouldReceive('initIndex')->once()->with('users')->andReturn($index = m::mock(stdClass::class)); + $index->shouldReceive('deleteObjects')->once()->with([1]); $engine->delete(Collection::make([$model])); } @@ -80,9 +80,9 @@ public function test_delete_removes_objects_to_index_with_a_custom_search_key() 'scout_id' => 'my-algolia-key.5', ]); - $engine = $this->app->make(EngineManager::class)->engine('algolia'); + $engine = $this->app->make(EngineManager::class)->engine(); - $this->client->shouldReceive('initIndex')->with('chirps')->once()->andReturn($index = m::mock(Indexes::class)); + $this->client->shouldReceive('initIndex')->once()->with('chirps')->andReturn($index = m::mock(Indexes::class)); $index->shouldReceive('deleteObjects')->once()->with(['my-algolia-key.5']); $engine->delete(Collection::make([$model])); @@ -94,13 +94,13 @@ public function test_delete_with_removeable_scout_collection_using_custom_search 'scout_id' => 'my-algolia-key.5', ]); - $engine = $this->app->make(EngineManager::class)->engine('algolia'); + $engine = $this->app->make(EngineManager::class)->engine(); $job = new RemoveFromSearch(RemoveableScoutCollection::make([$model])); $job = unserialize(serialize($job)); - $this->client->shouldReceive('initIndex')->with('chirps')->once()->andReturn($index = m::mock(stdClass::class)); + $this->client->shouldReceive('initIndex')->once()->with('chirps')->andReturn($index = m::mock(stdClass::class)); $index->shouldReceive('deleteObjects')->once()->with(['my-algolia-key.5']); $job->handle(); @@ -108,12 +108,10 @@ public function test_delete_with_removeable_scout_collection_using_custom_search public function test_search_sends_correct_parameters_to_algolia() { - SearchableUserFactory::new()->createQuietly(['name' => 'zonda']); + $engine = $this->app->make(EngineManager::class)->engine(); - $engine = $this->app->make(EngineManager::class)->engine('algolia'); - - $this->client->shouldReceive('initIndex')->with('users')->once()->andReturn($index = m::mock(stdClass::class)); - $index->shouldReceive('search')->with('zonda', [ + $this->client->shouldReceive('initIndex')->once()->with('users')->andReturn($index = m::mock(stdClass::class)); + $index->shouldReceive('search')->once()->with('zonda', [ 'numericFilters' => ['foo=1'], ])->once(); @@ -125,12 +123,10 @@ public function test_search_sends_correct_parameters_to_algolia() public function test_search_sends_correct_parameters_to_algolia_for_where_in_search() { - SearchableUserFactory::new()->createQuietly(['name' => 'zonda']); - - $engine = $this->app->make(EngineManager::class)->engine('algolia'); + $engine = $this->app->make(EngineManager::class)->engine(); - $this->client->shouldReceive('initIndex')->with('users')->once()->andReturn($index = m::mock(stdClass::class)); - $index->shouldReceive('search')->with('zonda', [ + $this->client->shouldReceive('initIndex')->once()->with('users')->andReturn($index = m::mock(stdClass::class)); + $index->shouldReceive('search')->once()->with('zonda', [ 'numericFilters' => ['foo=1', ['bar=1', 'bar=2']], ]); @@ -142,12 +138,10 @@ public function test_search_sends_correct_parameters_to_algolia_for_where_in_sea public function test_search_sends_correct_parameters_to_algolia_for_empty_where_in_search() { - SearchableUserFactory::new()->createQuietly(['name' => 'zonda']); - - $engine = $this->app->make(EngineManager::class)->engine('algolia'); + $engine = $this->app->make(EngineManager::class)->engine(); - $this->client->shouldReceive('initIndex')->with('users')->once()->andReturn($index = m::mock(stdClass::class)); - $index->shouldReceive('search')->with('zonda', [ + $this->client->shouldReceive('initIndex')->once()->with('users')->andReturn($index = m::mock(stdClass::class)); + $index->shouldReceive('search')->once()->with('zonda', [ 'numericFilters' => ['foo=1', '0=1'], ]); @@ -160,7 +154,7 @@ public function test_map_correctly_maps_results_to_models() { $model = SearchableUserFactory::new()->createQuietly(['name' => 'zonda']); - $engine = $this->app->make(EngineManager::class)->engine('algolia'); + $engine = $this->app->make(EngineManager::class)->engine(); $builder = m::mock(Builder::class); @@ -182,13 +176,13 @@ public function test_a_model_is_indexed_with_a_custom_algolia_key() 'scout_id' => 'my-algolia-key.1', ]); - $engine = $this->app->make(EngineManager::class)->engine('algolia'); + $engine = $this->app->make(EngineManager::class)->engine(); - $this->client->shouldReceive('initIndex')->with('chirps')->once()->andReturn($index = m::mock(stdClass::class)); - $index->shouldReceive('saveObjects')->with([[ + $this->client->shouldReceive('initIndex')->once()->with('chirps')->andReturn($index = m::mock(stdClass::class)); + $index->shouldReceive('saveObjects')->once()->with([[ 'content' => $model->content, 'objectID' => 'my-algolia-key.1', - ]])->once(); + ]]); $engine->update(Collection::make([$model])); } @@ -199,10 +193,10 @@ public function test_a_model_is_removed_with_a_custom_algolia_key() 'scout_id' => 'my-algolia-key.1', ]); - $engine = $this->app->make(EngineManager::class)->engine('algolia'); + $engine = $this->app->make(EngineManager::class)->engine(); - $this->client->shouldReceive('initIndex')->with('chirps')->once()->andReturn($index = m::mock(stdClass::class)); - $index->shouldReceive('deleteObjects')->with(['my-algolia-key.1']); + $this->client->shouldReceive('initIndex')->once()->with('chirps')->andReturn($index = m::mock(stdClass::class)); + $index->shouldReceive('deleteObjects')->once()->with(['my-algolia-key.1']); $engine->delete(Collection::make([$model])); } @@ -213,10 +207,10 @@ public function test_flush_a_model_with_a_custom_algolia_key() 'scout_id' => 'my-algolia-key.1', ]); - $engine = $this->app->make(EngineManager::class)->engine('algolia'); + $engine = $this->app->make(EngineManager::class)->engine(); - $this->client->shouldReceive('initIndex')->with('chirps')->once()->andReturn($index = m::mock(stdClass::class)); - $index->shouldReceive('clearObjects'); + $this->client->shouldReceive('initIndex')->once()->with('chirps')->andReturn($index = m::mock(stdClass::class)); + $index->shouldReceive('clearObjects')->once(); $engine->flush(new Chirp); } @@ -225,9 +219,9 @@ public function test_update_empty_searchable_array_does_not_add_objects_to_index { $_ENV['searchable.user'] = []; - $engine = $this->app->make(EngineManager::class)->engine('algolia'); + $engine = $this->app->make(EngineManager::class)->engine(); - $this->client->shouldReceive('initIndex')->with('users')->once()->andReturn($index = m::mock(stdClass::class)); + $this->client->shouldReceive('initIndex')->once()->with('users')->andReturn($index = m::mock(stdClass::class)); $index->shouldNotReceive('saveObjects'); $engine->update(Collection::make([new SearchableUser])); @@ -240,9 +234,9 @@ public function test_update_empty_searchable_array_from_soft_deleted_model_does_ { $_ENV['searchable.chirp'] = []; - $engine = $this->app->make(EngineManager::class)->engine('algolia'); + $engine = $this->app->make(EngineManager::class)->engine(); - $this->client->shouldReceive('initIndex')->with('chirps')->once()->andReturn($index = m::mock(stdClass::class)); + $this->client->shouldReceive('initIndex')->once()->with('chirps')->andReturn($index = m::mock(stdClass::class)); $index->shouldNotReceive('saveObjects'); $engine->update(Collection::make([new Chirp])); diff --git a/tests/Feature/Engines/Algolia4EngineTest.php b/tests/Feature/Engines/Algolia4EngineTest.php index df2fc7e6..3c02de3c 100644 --- a/tests/Feature/Engines/Algolia4EngineTest.php +++ b/tests/Feature/Engines/Algolia4EngineTest.php @@ -23,7 +23,7 @@ use function Orchestra\Testbench\after_resolving; -#[WithConfig('scout.driver', 'algolia')] +#[WithConfig('scout.driver', 'algolia4-testing')] #[WithMigration] class Algolia4EngineTest extends TestCase { @@ -37,7 +37,7 @@ protected function defineEnvironment($app) after_resolving($app, EngineManager::class, function ($manager) { $this->client = m::spy(SearchClient::class); - $manager->extend('algolia', fn () => new Algolia4Engine($this->client, config('scout.soft_delete'))); + $manager->extend('algolia4-testing', fn () => new Algolia4Engine($this->client, config('scout.soft_delete'))); }); $this->beforeApplicationDestroyed(function () { @@ -49,14 +49,14 @@ public function test_update_adds_objects_to_index() { $model = SearchableUserFactory::new()->createQuietly(); - $engine = $this->app->make(EngineManager::class)->engine('algolia'); + $engine = $this->app->make(EngineManager::class)->engine(); - $this->client->shouldReceive('saveObjects')->with('users', [[ + $this->client->shouldReceive('saveObjects')->once()->with('users', [[ 'id' => $model->getKey(), 'name' => $model->name, 'email' => $model->email, 'objectID' => $model->getScoutKey(), - ]])->once(); + ]]); $engine->update(Collection::make([$model])); } @@ -65,9 +65,9 @@ public function test_delete_removes_objects_to_index() { $model = SearchableUserFactory::new()->createQuietly(); - $engine = $this->app->make(EngineManager::class)->engine('algolia'); + $engine = $this->app->make(EngineManager::class)->engine(); - $this->client->shouldReceive('deleteObjects')->with('users', [1])->once(); + $this->client->shouldReceive('deleteObjects')->once()->with('users', [1]); $engine->delete(Collection::make([$model])); } @@ -78,7 +78,7 @@ public function test_delete_removes_objects_to_index_with_a_custom_search_key() 'scout_id' => 'my-algolia-key.5', ]); - $engine = $this->app->make(EngineManager::class)->engine('algolia'); + $engine = $this->app->make(EngineManager::class)->engine(); $this->client->shouldReceive('deleteObjects')->once()->with('chirps', ['my-algolia-key.5']); @@ -91,7 +91,7 @@ public function test_delete_with_removeable_scout_collection_using_custom_search 'scout_id' => 'my-algolia-key.5', ]); - $engine = $this->app->make(EngineManager::class)->engine('algolia'); + $engine = $this->app->make(EngineManager::class)->engine(); $job = new RemoveFromSearch(RemoveableScoutCollection::make([$model])); @@ -104,15 +104,13 @@ public function test_delete_with_removeable_scout_collection_using_custom_search public function test_search_sends_correct_parameters_to_algolia() { - SearchableUserFactory::new()->createQuietly(['name' => 'zonda']); + $engine = $this->app->make(EngineManager::class)->engine(); - $engine = $this->app->make(EngineManager::class)->engine('algolia'); - - $this->client->shouldReceive('searchSingleIndex')->with( + $this->client->shouldReceive('searchSingleIndex')->once()->with( 'users', ['query' => 'zonda'], ['numericFilters' => ['foo=1']] - )->once(); + ); $builder = new Builder(new SearchableUser, 'zonda'); $builder->where('foo', 1); @@ -122,15 +120,13 @@ public function test_search_sends_correct_parameters_to_algolia() public function test_search_sends_correct_parameters_to_algolia_for_where_in_search() { - SearchableUserFactory::new()->createQuietly(['name' => 'zonda']); - - $engine = $this->app->make(EngineManager::class)->engine('algolia'); + $engine = $this->app->make(EngineManager::class)->engine(); - $this->client->shouldReceive('searchSingleIndex')->with( + $this->client->shouldReceive('searchSingleIndex')->once()->with( 'users', ['query' => 'zonda'], ['numericFilters' => ['foo=1', ['bar=1', 'bar=2']]], - )->once(); + ); $builder = new Builder(new SearchableUser, 'zonda'); $builder->where('foo', 1)->whereIn('bar', [1, 2]); @@ -140,15 +136,13 @@ public function test_search_sends_correct_parameters_to_algolia_for_where_in_sea public function test_search_sends_correct_parameters_to_algolia_for_empty_where_in_search() { - SearchableUserFactory::new()->createQuietly(['name' => 'zonda']); - - $engine = $this->app->make(EngineManager::class)->engine('algolia'); + $engine = $this->app->make(EngineManager::class)->engine(); - $this->client->shouldReceive('searchSingleIndex')->with( + $this->client->shouldReceive('searchSingleIndex')->once()->with( 'users', ['query' => 'zonda'], ['numericFilters' => ['foo=1', '0=1']] - )->once(); + ); $builder = new Builder(new SearchableUser, 'zonda'); $builder->where('foo', 1)->whereIn('bar', []); @@ -159,7 +153,7 @@ public function test_map_correctly_maps_results_to_models() { $model = SearchableUserFactory::new()->createQuietly(['name' => 'zonda']); - $engine = $this->app->make(EngineManager::class)->engine('algolia'); + $engine = $this->app->make(EngineManager::class)->engine(); $builder = m::mock(Builder::class); @@ -181,12 +175,12 @@ public function test_a_model_is_indexed_with_a_custom_algolia_key() 'scout_id' => 'my-algolia-key.1', ]); - $engine = $this->app->make(EngineManager::class)->engine('algolia'); + $engine = $this->app->make(EngineManager::class)->engine(); - $this->client->shouldReceive('saveObjects')->with('chirps', [[ + $this->client->shouldReceive('saveObjects')->once()->with('chirps', [[ 'content' => $model->content, 'objectID' => 'my-algolia-key.1', - ]])->once(); + ]]); $engine->update(Collection::make([$model])); } @@ -197,9 +191,9 @@ public function test_a_model_is_removed_with_a_custom_algolia_key() 'scout_id' => 'my-algolia-key.1', ]); - $engine = $this->app->make(EngineManager::class)->engine('algolia'); + $engine = $this->app->make(EngineManager::class)->engine(); - $this->client->shouldReceive('deleteObjects')->with('chirps', ['my-algolia-key.1'])->once(); + $this->client->shouldReceive('deleteObjects')->once()->with('chirps', ['my-algolia-key.1']); $engine->delete(Collection::make([$model])); } @@ -210,9 +204,9 @@ public function test_flush_a_model_with_a_custom_algolia_key() 'scout_id' => 'my-algolia-key.1', ]); - $engine = $this->app->make(EngineManager::class)->engine('algolia'); + $engine = $this->app->make(EngineManager::class)->engine(); - $this->client->shouldReceive('clearObjects')->with('chirps')->once(); + $this->client->shouldReceive('clearObjects')->once()->with('chirps'); $engine->flush(new Chirp); } @@ -221,7 +215,7 @@ public function test_update_empty_searchable_array_does_not_add_objects_to_index { $_ENV['searchable.user'] = []; - $engine = $this->app->make(EngineManager::class)->engine('algolia'); + $engine = $this->app->make(EngineManager::class)->engine(); $this->client->shouldNotReceive('saveObjects')->with('users'); @@ -235,7 +229,7 @@ public function test_update_empty_searchable_array_from_soft_deleted_model_does_ { $_ENV['searchable.chirp'] = []; - $engine = $this->app->make(EngineManager::class)->engine('algolia'); + $engine = $this->app->make(EngineManager::class)->engine(); $this->client->shouldNotReceive('saveObjects')->with('chirps'); diff --git a/tests/Feature/Engines/MeilisearchTest.php b/tests/Feature/Engines/MeilisearchTest.php new file mode 100644 index 00000000..b82443ae --- /dev/null +++ b/tests/Feature/Engines/MeilisearchTest.php @@ -0,0 +1,302 @@ +client = m::spy(SearchClient::class); + + $manager->extend('meilisearch-testing', fn () => new MeilisearchEngine($this->client, config('scout.soft_delete'))); + }); + + $this->beforeApplicationDestroyed(function () { + unset($this->client); + }); + } + + public function test_update_adds_objects_to_index() + { + $model = SearchableUserFactory::new()->createQuietly(); + + $engine = $this->app->make(EngineManager::class)->engine(); + + $this->client->shouldReceive('index')->once()->with('users')->andReturn($index = m::mock(Indexes::class)); + $index->shouldReceive('addDocuments')->once()->with( + [$model->toSearchableArray()], 'id' + ); + + $engine->update(Collection::make([$model])); + } + + public function test_delete_removes_objects_to_index() + { + $model = SearchableUserFactory::new()->createQuietly(); + + $engine = $this->app->make(EngineManager::class)->engine(); + + $this->client->shouldReceive('index')->once()->with('users')->andReturn($index = m::mock(Indexes::class)); + $index->shouldReceive('deleteDocuments')->once()->with([1]); + + $engine->delete(Collection::make([$model])); + } + + public function test_delete_removes_objects_to_index_with_a_custom_search_key() + { + $model = ChirpFactory::new()->createQuietly(['scout_id' => 'my-meilisearch-key.5']); + + $engine = $this->app->make(EngineManager::class)->engine(); + + $this->client->shouldReceive('index')->once()->with('chirps')->andReturn($index = m::mock(Indexes::class)); + $index->shouldReceive('deleteDocuments')->once()->with(['my-meilisearch-key.5']); + + $engine->delete(Collection::make([$model])); + } + + public function test_delete_with_removeable_scout_collection_using_custom_search_key() + { + $model = ChirpFactory::new()->createQuietly(['scout_id' => 'my-meilisearch-key.5']); + + $job = new RemoveFromSearch(RemoveableScoutCollection::make([$model])); + + $engine = $this->app->make(EngineManager::class)->engine(); + + $job = unserialize(serialize($job)); + + $this->client->shouldReceive('index')->once()->with('chirps')->andReturn($index = m::mock(Indexes::class)); + $index->shouldReceive('deleteDocuments')->once()->with(['my-meilisearch-key.5']); + + $job->handle(); + } + + public function test_search_sends_correct_parameters_to_meilisearch() + { + $engine = $this->app->make(EngineManager::class)->engine(); + + $this->client->shouldReceive('index')->once()->with('users')->andReturn($index = m::mock(Indexes::class)); + $index->shouldReceive('search')->once()->with('mustang', [ + 'filter' => 'foo=1 AND bar=2', + ]); + + $builder = new Builder(new SearchableUser, 'mustang', function ($meilisearch, $query, $options) { + $options['filter'] = 'foo=1 AND bar=2'; + + return $meilisearch->search($query, $options); + }); + + $engine->search($builder); + } + + public function test_search_includes_at_least_scoutKeyName_in_attributesToRetrieve_on_builder_options() + { + $engine = $this->app->make(EngineManager::class)->engine(); + + $this->client->shouldReceive('index')->once()->with('users')->andReturn($index = m::mock(Indexes::class)); + $index->shouldReceive('search')->once()->with('mustang', [ + 'filter' => 'foo=1 AND bar=2', + 'attributesToRetrieve' => ['id', 'foo'], + ]); + + $builder = new Builder(new SearchableUser, 'mustang', function ($meilisearch, $query, $options) { + $options['filter'] = 'foo=1 AND bar=2'; + + return $meilisearch->search($query, $options); + }); + $builder->options = ['attributesToRetrieve' => ['foo']]; + + $engine->search($builder); + } + + public function test_submitting_a_callable_search_with_search_method_returns_array() + { + $engine = $this->app->make(EngineManager::class)->engine(); + + $builder = new Builder( + new SearchableUser, + $query = 'mustang', + $callable = function ($meilisearch, $query, $options) { + $options['filter'] = 'foo=1'; + + return $meilisearch->search($query, $options); + } + ); + + $this->client->shouldReceive('index')->once()->with('users')->andReturn($index = m::mock(Indexes::class)); + $index->shouldReceive('search')->once()->with($query, ['filter' => 'foo=1'])->andReturn(new SearchResult($expectedResult = [ + 'hits' => [], + 'page' => 1, + 'hitsPerPage' => $builder->limit, + 'totalPages' => 1, + 'totalHits' => 0, + 'processingTimeMs' => 1, + 'query' => 'mustang', + ])); + + $result = $engine->search($builder); + + $this->assertSame($expectedResult, $result); + } + + public function test_submitting_a_callable_search_with_raw_search_method_works() + { + $engine = $this->app->make(EngineManager::class)->engine(); + + $builder = new Builder( + new SearchableUser, + $query = 'mustang', + $callable = function ($meilisearch, $query, $options) { + $options['filter'] = 'foo=1'; + + return $meilisearch->rawSearch($query, $options); + } + ); + + $this->client->shouldReceive('index')->once()->with('users')->andReturn($index = m::mock(Indexes::class)); + $index->shouldReceive('rawSearch')->once()->with($query, ['filter' => 'foo=1'])->andReturn($expectedResult = [ + 'hits' => [], + 'page' => 1, + 'hitsPerPage' => $builder->limit, + 'totalPages' => 1, + 'totalHits' => 0, + 'processingTimeMs' => 1, + 'query' => $query, + ]); + + $result = $engine->search($builder); + + $this->assertSame($expectedResult, $result); + } + + public function test_where_in_conditions_are_applied() + { + $engine = $this->app->make(EngineManager::class)->engine(); + + $builder = new Builder(new SearchableUser, ''); + $builder->where('foo', 'bar'); + $builder->where('bar', 'baz'); + $builder->whereIn('qux', [1, 2]); + $builder->whereIn('quux', [1, 2]); + + $this->client->shouldReceive('index')->once()->with('users')->andReturn($index = m::mock(Indexes::class)); + $index->shouldReceive('rawSearch')->once()->with($builder->query, array_filter([ + 'filter' => 'foo="bar" AND bar="baz" AND qux IN [1, 2] AND quux IN [1, 2]', + 'hitsPerPage' => $builder->limit, + ]))->andReturn([]); + + $engine->search($builder); + } + + public function test_where_not_in_conditions_are_applied() + { + $engine = $this->app->make(EngineManager::class)->engine(); + + $builder = new Builder(new SearchableUser, ''); + $builder->where('foo', 'bar'); + $builder->where('bar', 'baz'); + $builder->whereIn('qux', [1, 2]); + $builder->whereIn('quux', [1, 2]); + $builder->whereNotIn('eaea', [3]); + + $this->client->shouldReceive('index')->once()->with('users')->andReturn($index = m::mock(Indexes::class)); + $index->shouldReceive('rawSearch')->once()->with($builder->query, array_filter([ + 'filter' => 'foo="bar" AND bar="baz" AND qux IN [1, 2] AND quux IN [1, 2] AND eaea NOT IN [3]', + 'hitsPerPage' => $builder->limit, + ]))->andReturn([]); + + $engine->search($builder); + } + + public function test_where_in_conditions_are_applied_without_other_conditions() + { + $engine = $this->app->make(EngineManager::class)->engine(); + + $builder = new Builder(new SearchableUser, ''); + $builder->whereIn('qux', [1, 2]); + $builder->whereIn('quux', [1, 2]); + + $this->client->shouldReceive('index')->once()->with('users')->andReturn($index = m::mock(Indexes::class)); + $index->shouldReceive('rawSearch')->once()->with($builder->query, array_filter([ + 'filter' => 'qux IN [1, 2] AND quux IN [1, 2]', + 'hitsPerPage' => $builder->limit, + ]))->andReturn([]); + + $engine->search($builder); + } + + public function test_where_not_in_conditions_are_applied_without_other_conditions() + { + $engine = $this->app->make(EngineManager::class)->engine(); + + $builder = new Builder(new SearchableUser, ''); + $builder->whereIn('qux', [1, 2]); + $builder->whereIn('quux', [1, 2]); + $builder->whereNotIn('eaea', [3]); + + $this->client->shouldReceive('index')->once()->with('users')->andReturn($index = m::mock(Indexes::class)); + $index->shouldReceive('rawSearch')->once()->with($builder->query, array_filter([ + 'filter' => 'qux IN [1, 2] AND quux IN [1, 2] AND eaea NOT IN [3]', + 'hitsPerPage' => $builder->limit, + ]))->andReturn([]); + + $engine->search($builder); + } + + public function test_empty_where_in_conditions_are_applied_correctly() + { + $engine = $this->app->make(EngineManager::class)->engine(); + + $builder = new Builder(new SearchableUser, ''); + $builder->where('foo', 'bar'); + $builder->where('bar', 'baz'); + $builder->whereIn('qux', []); + + $this->client->shouldReceive('index')->once()->with('users')->andReturn($index = m::mock(Indexes::class)); + $index->shouldReceive('rawSearch')->once()->with($builder->query, array_filter([ + 'filter' => 'foo="bar" AND bar="baz" AND qux IN []', + 'hitsPerPage' => $builder->limit, + ]))->andReturn([]); + + $engine->search($builder); + } + + public function test_delete_all_indexes_works_with_pagination() + { + $engine = $this->app->make(EngineManager::class)->engine(); + + $this->client->shouldReceive('getIndexes')->andReturn($indexesResults = m::mock(IndexesResults::class)); + + $indexesResults->shouldReceive('getResults')->once(); + + $engine->deleteAllIndexes(); + } +} diff --git a/tests/Unit/Algolia3EngineTest.php b/tests/Unit/Algolia3EngineTest.php index f95ef3ee..337b92c7 100644 --- a/tests/Unit/Algolia3EngineTest.php +++ b/tests/Unit/Algolia3EngineTest.php @@ -4,6 +4,7 @@ use Algolia\AlgoliaSearch\SearchClient; use Illuminate\Container\Container; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\Config; use Illuminate\Support\LazyCollection; use Laravel\Scout\Builder; diff --git a/tests/Unit/MakeSearchableTest.php b/tests/Unit/MakeSearchableTest.php deleted file mode 100644 index e3b8c3a6..00000000 --- a/tests/Unit/MakeSearchableTest.php +++ /dev/null @@ -1,28 +0,0 @@ -makePartial(), - ])); - - $model->shouldReceive('searchableUsing->update')->with($collection)->once(); - - $job->handle(); - } -} diff --git a/tests/Unit/MeilisearchEngineTest.php b/tests/Unit/MeilisearchEngineTest.php index faebbcf8..1ba6eadf 100644 --- a/tests/Unit/MeilisearchEngineTest.php +++ b/tests/Unit/MeilisearchEngineTest.php @@ -35,178 +35,6 @@ protected function tearDown(): void m::close(); } - public function test_update_adds_objects_to_index() - { - $client = m::mock(Client::class); - $client->shouldReceive('index')->with('table')->andReturn($index = m::mock(Indexes::class)); - $index->shouldReceive('addDocuments')->with([ - [ - 'id' => 1, - ], - 'id', - ]); - - $engine = new MeilisearchEngine($client); - $engine->update(Collection::make([new SearchableModel])); - } - - public function test_delete_removes_objects_to_index() - { - $client = m::mock(Client::class); - $client->shouldReceive('index')->with('table')->andReturn($index = m::mock(Indexes::class)); - $index->shouldReceive('deleteDocuments')->with([1]); - - $engine = new MeilisearchEngine($client); - $engine->delete(Collection::make([new SearchableModel(['id' => 1])])); - } - - public function test_delete_removes_objects_to_index_with_a_custom_search_key() - { - $client = m::mock(Client::class); - $client->shouldReceive('index')->with('table')->andReturn($index = m::mock(Indexes::class)); - $index->shouldReceive('deleteDocuments')->once()->with(['my-meilisearch-key.5']); - - $engine = new MeilisearchEngine($client); - $engine->delete(Collection::make([new MeilisearchCustomKeySearchableModel(['id' => 5])])); - } - - public function test_delete_with_removeable_scout_collection_using_custom_search_key() - { - $job = new RemoveFromSearch(Collection::make([ - new MeilisearchCustomKeySearchableModel(['id' => 5]), - ])); - - $job = unserialize(serialize($job)); - - $client = m::mock(Client::class); - $client->shouldReceive('index')->with('table')->andReturn($index = m::mock(Indexes::class)); - $index->shouldReceive('deleteDocuments')->once()->with(['my-meilisearch-key.5']); - - $engine = new MeilisearchEngine($client); - $engine->delete($job->models); - } - - public function test_remove_from_search_job_uses_custom_search_key() - { - $job = new RemoveFromSearch(Collection::make([ - new MeilisearchCustomKeySearchableModel(['id' => 5]), - ])); - - $job = unserialize(serialize($job)); - - Container::getInstance()->bind(EngineManager::class, function () { - $engine = m::mock(MeilisearchEngine::class); - - $engine->shouldReceive('delete')->once()->with(m::on(function ($collection) { - $keyName = ($model = $collection->first())->getScoutKeyName(); - - return $model->getAttributes()[$keyName] === 'my-meilisearch-key.5'; - })); - - $manager = m::mock(EngineManager::class); - - $manager->shouldReceive('engine')->andReturn($engine); - - return $manager; - }); - - $job->handle(); - } - - public function test_search_sends_correct_parameters_to_meilisearch() - { - $client = m::mock(Client::class); - $client->shouldReceive('index')->with('table')->andReturn($index = m::mock(Indexes::class)); - $index->shouldReceive('search')->with('mustang', [ - 'filter' => 'foo=1 AND bar=2', - ]); - - $engine = new MeilisearchEngine($client); - $builder = new Builder(new SearchableModel, 'mustang', function ($meilisearch, $query, $options) { - $options['filter'] = 'foo=1 AND bar=2'; - - return $meilisearch->search($query, $options); - }); - $engine->search($builder); - } - - public function test_search_includes_at_least_scoutKeyName_in_attributesToRetrieve_on_builder_options() - { - $client = m::mock(Client::class); - $client->shouldReceive('index')->with('table')->andReturn($index = m::mock(Indexes::class)); - $index->shouldReceive('search')->with('mustang', [ - 'filter' => 'foo=1 AND bar=2', - 'attributesToRetrieve' => ['id', 'foo'], - ]); - - $engine = new MeilisearchEngine($client); - $builder = new Builder(new SearchableModel, 'mustang', function ($meilisearch, $query, $options) { - $options['filter'] = 'foo=1 AND bar=2'; - - return $meilisearch->search($query, $options); - }); - $builder->options = ['attributesToRetrieve' => ['foo']]; - $engine->search($builder); - } - - public function test_submitting_a_callable_search_with_search_method_returns_array() - { - $builder = new Builder( - new SearchableModel, - $query = 'mustang', - $callable = function ($meilisearch, $query, $options) { - $options['filter'] = 'foo=1'; - - return $meilisearch->search($query, $options); - } - ); - $client = m::mock(Client::class); - $client->shouldReceive('index')->with('table')->andReturn($index = m::mock(Indexes::class)); - $index->shouldReceive('search')->with($query, ['filter' => 'foo=1'])->andReturn(new SearchResult($expectedResult = [ - 'hits' => [], - 'page' => 1, - 'hitsPerPage' => $builder->limit, - 'totalPages' => 1, - 'totalHits' => 0, - 'processingTimeMs' => 1, - 'query' => 'mustang', - ])); - - $engine = new MeilisearchEngine($client); - $result = $engine->search($builder); - - $this->assertSame($expectedResult, $result); - } - - public function test_submitting_a_callable_search_with_raw_search_method_works() - { - $builder = new Builder( - new SearchableModel, - $query = 'mustang', - $callable = function ($meilisearch, $query, $options) { - $options['filter'] = 'foo=1'; - - return $meilisearch->rawSearch($query, $options); - } - ); - $client = m::mock(Client::class); - $client->shouldReceive('index')->with('table')->andReturn($index = m::mock(Indexes::class)); - $index->shouldReceive('rawSearch')->with($query, ['filter' => 'foo=1'])->andReturn($expectedResult = [ - 'hits' => [], - 'page' => 1, - 'hitsPerPage' => $builder->limit, - 'totalPages' => 1, - 'totalHits' => 0, - 'processingTimeMs' => 1, - 'query' => $query, - ]); - - $engine = new MeilisearchEngine($client); - $result = $engine->search($builder); - - $this->assertSame($expectedResult, $result); - } - public function test_map_ids_returns_empty_collection_if_no_hits() { $client = m::mock(Client::class); @@ -261,10 +89,10 @@ public function test_returns_primary_keys_when_custom_array_order_present() $builder = m::mock(Builder::class); $model = m::mock(stdClass::class); - $model->shouldReceive(['getScoutKeyName' => 'custom_key']); + $model->shouldReceive(['getScoutKeyName' => 'custom_key'])->once(); $builder->model = $model; - $engine->shouldReceive('keys')->passthru(); + $engine->shouldReceive('keys')->once()->passthru(); $engine ->shouldReceive('search') @@ -532,110 +360,12 @@ public function test_where_conditions_are_applied() $engine->search($builder); } - public function test_where_in_conditions_are_applied() - { - $builder = new Builder(new SearchableModel, ''); - $builder->where('foo', 'bar'); - $builder->where('bar', 'baz'); - $builder->whereIn('qux', [1, 2]); - $builder->whereIn('quux', [1, 2]); - $client = m::mock(Client::class); - $client->shouldReceive('index')->once()->andReturn($index = m::mock(Indexes::class)); - $index->shouldReceive('rawSearch')->once()->with($builder->query, array_filter([ - 'filter' => 'foo="bar" AND bar="baz" AND qux IN [1, 2] AND quux IN [1, 2]', - 'hitsPerPage' => $builder->limit, - ]))->andReturn([]); - - $engine = new MeilisearchEngine($client); - $engine->search($builder); - } - - public function test_where_not_in_conditions_are_applied() - { - $builder = new Builder(new SearchableModel, ''); - $builder->where('foo', 'bar'); - $builder->where('bar', 'baz'); - $builder->whereIn('qux', [1, 2]); - $builder->whereIn('quux', [1, 2]); - $builder->whereNotIn('eaea', [3]); - $client = m::mock(Client::class); - $client->shouldReceive('index')->once()->andReturn($index = m::mock(Indexes::class)); - $index->shouldReceive('rawSearch')->once()->with($builder->query, array_filter([ - 'filter' => 'foo="bar" AND bar="baz" AND qux IN [1, 2] AND quux IN [1, 2] AND eaea NOT IN [3]', - 'hitsPerPage' => $builder->limit, - ]))->andReturn([]); - - $engine = new MeilisearchEngine($client); - $engine->search($builder); - } - - public function test_where_in_conditions_are_applied_without_other_conditions() - { - $builder = new Builder(new SearchableModel, ''); - $builder->whereIn('qux', [1, 2]); - $builder->whereIn('quux', [1, 2]); - $client = m::mock(Client::class); - $client->shouldReceive('index')->once()->andReturn($index = m::mock(Indexes::class)); - $index->shouldReceive('rawSearch')->once()->with($builder->query, array_filter([ - 'filter' => 'qux IN [1, 2] AND quux IN [1, 2]', - 'hitsPerPage' => $builder->limit, - ]))->andReturn([]); - - $engine = new MeilisearchEngine($client); - $engine->search($builder); - } - - public function test_where_not_in_conditions_are_applied_without_other_conditions() - { - $builder = new Builder(new SearchableModel, ''); - $builder->whereIn('qux', [1, 2]); - $builder->whereIn('quux', [1, 2]); - $builder->whereNotIn('eaea', [3]); - $client = m::mock(Client::class); - $client->shouldReceive('index')->once()->andReturn($index = m::mock(Indexes::class)); - $index->shouldReceive('rawSearch')->once()->with($builder->query, array_filter([ - 'filter' => 'qux IN [1, 2] AND quux IN [1, 2] AND eaea NOT IN [3]', - 'hitsPerPage' => $builder->limit, - ]))->andReturn([]); - - $engine = new MeilisearchEngine($client); - $engine->search($builder); - } - - public function test_empty_where_in_conditions_are_applied_correctly() - { - $builder = new Builder(new SearchableModel, ''); - $builder->where('foo', 'bar'); - $builder->where('bar', 'baz'); - $builder->whereIn('qux', []); - $client = m::mock(Client::class); - $client->shouldReceive('index')->once()->andReturn($index = m::mock(Indexes::class)); - $index->shouldReceive('rawSearch')->once()->with($builder->query, array_filter([ - 'filter' => 'foo="bar" AND bar="baz" AND qux IN []', - 'hitsPerPage' => $builder->limit, - ]))->andReturn([]); - - $engine = new MeilisearchEngine($client); - $engine->search($builder); - } - public function test_engine_returns_hits_entry_from_search_response() { $this->assertTrue((new MeilisearchEngine(m::mock(Client::class)))->getTotalCount([ 'totalHits' => 3, ]) === 3); } - - public function test_delete_all_indexes_works_with_pagination() - { - $client = m::mock(Client::class); - $client->shouldReceive('getIndexes')->andReturn($indexesResults = m::mock(IndexesResults::class)); - - $indexesResults->shouldReceive('getResults')->once(); - - $engine = new MeilisearchEngine($client); - $engine->deleteAllIndexes(); - } } class MeilisearchCustomKeySearchableModel extends SearchableModel diff --git a/tests/Unit/ModelObserverWithSoftDeletesTest.php b/tests/Unit/ModelObserverWithSoftDeletesTest.php index 21adc126..0ae79ac2 100644 --- a/tests/Unit/ModelObserverWithSoftDeletesTest.php +++ b/tests/Unit/ModelObserverWithSoftDeletesTest.php @@ -28,7 +28,7 @@ public function test_deleted_handler_makes_model_unsearchable_when_it_should_not $observer = new ModelObserver; $model = m::mock(SearchableModelWithSoftDeletes::class); $model->shouldReceive('searchShouldUpdate')->never(); // The saved event is forced - $model->shouldReceive('shouldBeSearchable')->andReturn(false); // Should not be searchable + $model->shouldReceive('shouldBeSearchable')->once()->andReturn(false); // Should not be searchable $model->shouldReceive('wasSearchableBeforeDelete')->andReturn(true); $model->shouldReceive('wasSearchableBeforeUpdate')->andReturn(true); $model->shouldReceive('searchable')->never(); diff --git a/workbench/app/Models/Chirp.php b/workbench/app/Models/Chirp.php index c9df9556..294c10dd 100644 --- a/workbench/app/Models/Chirp.php +++ b/workbench/app/Models/Chirp.php @@ -4,12 +4,14 @@ use Illuminate\Database\Eloquent\Concerns\HasUuids; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\SoftDeletes; use Laravel\Scout\Searchable; class Chirp extends Model { use HasUuids; use Searchable; + use SoftDeletes; /** {@inheritDoc} */ protected $fillable = ['scout_id']; diff --git a/workbench/database/migrations/2024_11_12_030345_create_chirps_table.php b/workbench/database/migrations/2024_11_12_030345_create_chirps_table.php index ffc5d102..73a7fb61 100644 --- a/workbench/database/migrations/2024_11_12_030345_create_chirps_table.php +++ b/workbench/database/migrations/2024_11_12_030345_create_chirps_table.php @@ -16,6 +16,7 @@ public function up(): void $table->uuid('scout_id'); $table->text('content'); $table->timestamps(); + $table->softDeletes(); }); } From f0814a1f117ef0dc9c60240434fb2298679d8b7e Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 12 Nov 2024 14:53:25 +0800 Subject: [PATCH 17/32] wip Signed-off-by: Mior Muhammad Zaki --- ...archTest.php => MeilisearchEngineTest.php} | 137 +++++++++++++++++- tests/Unit/MeilisearchEngineTest.php | 123 +--------------- 2 files changed, 138 insertions(+), 122 deletions(-) rename tests/Feature/Engines/{MeilisearchTest.php => MeilisearchEngineTest.php} (70%) diff --git a/tests/Feature/Engines/MeilisearchTest.php b/tests/Feature/Engines/MeilisearchEngineTest.php similarity index 70% rename from tests/Feature/Engines/MeilisearchTest.php rename to tests/Feature/Engines/MeilisearchEngineTest.php index b82443ae..1fb8336f 100644 --- a/tests/Feature/Engines/MeilisearchTest.php +++ b/tests/Feature/Engines/MeilisearchEngineTest.php @@ -7,8 +7,8 @@ use Laravel\Scout\Builder; use Laravel\Scout\EngineManager; use Laravel\Scout\Engines\MeilisearchEngine; -use Laravel\Scout\Jobs\RemoveFromSearch; use Laravel\Scout\Jobs\RemoveableScoutCollection; +use Laravel\Scout\Jobs\RemoveFromSearch; use Meilisearch\Client as SearchClient; use Meilisearch\Contracts\IndexesResults; use Meilisearch\Endpoints\Indexes; @@ -18,14 +18,16 @@ use Orchestra\Testbench\Attributes\WithMigration; use Orchestra\Testbench\Concerns\WithWorkbench; use Orchestra\Testbench\TestCase; +use Workbench\App\Models\Chirp; use Workbench\App\Models\SearchableUser; use Workbench\Database\Factories\ChirpFactory; use Workbench\Database\Factories\SearchableUserFactory; + use function Orchestra\Testbench\after_resolving; #[WithConfig('scout.driver', 'meilisearch-testing')] #[WithMigration] -class MeilisearchTest extends TestCase +class MeilisearchEngineTest extends TestCase { use LazilyRefreshDatabase; use WithWorkbench; @@ -216,6 +218,137 @@ public function test_where_in_conditions_are_applied() $engine->search($builder); } + public function test_a_model_is_indexed_with_a_custom_meilisearch_key() + { + $model = ChirpFactory::new()->createQuietly(['scout_id' => 'my-meilisearch-key.5']); + + $engine = $this->app->make(EngineManager::class)->engine(); + + $this->client->shouldReceive('index')->once()->with('chirps')->andReturn($index = m::mock(Indexes::class)); + $index->shouldReceive('addDocuments')->once()->with([[ + 'scout_id' => 'my-meilisearch-key.5', + 'content' => $model->content, + ]], 'scout_id'); + + $engine->update(Collection::make([$model])); + } + + public function test_flush_a_model_with_a_custom_meilisearch_key() + { + $engine = $this->app->make(EngineManager::class)->engine(); + + $this->client->shouldReceive('index')->once()->with('chirps')->andReturn($index = m::mock(Indexes::class)); + $index->shouldReceive('deleteAllDocuments'); + + $engine->flush(new Chirp); + } + + public function test_update_empty_searchable_array_does_not_add_documents_to_index() + { + $_ENV['searchable.user'] = []; + + $engine = $this->app->make(EngineManager::class)->engine(); + + $this->client->shouldReceive('index')->once()->with('users')->andReturn($index = m::mock(Indexes::class)); + $index->shouldNotReceive('addDocuments'); + + $engine->update(Collection::make([new SearchableUser])); + + unset($_ENV['searchable.user']); + } + + public function test_pagination_correct_parameters() + { + $engine = $this->app->make(EngineManager::class)->engine(); + + $perPage = 5; + $page = 2; + + $this->client->shouldReceive('index')->once()->with('users')->andReturn($index = m::mock(Indexes::class)); + $index->shouldReceive('search')->once()->with('mustang', [ + 'filter' => 'foo=1', + 'hitsPerPage' => $perPage, + 'page' => $page, + ]); + + $builder = new Builder(new SearchableUser, 'mustang', function ($meilisearch, $query, $options) { + $options['filter'] = 'foo=1'; + + return $meilisearch->search($query, $options); + }); + + $engine->paginate($builder, $perPage, $page); + } + + public function test_pagination_sorted_parameter() + { + $engine = $this->app->make(EngineManager::class)->engine(); + + $perPage = 5; + $page = 2; + + $this->client->shouldReceive('index')->once()->with('users')->andReturn($index = m::mock(Indexes::class)); + $index->shouldReceive('search')->once()->with('mustang', [ + 'filter' => 'foo=1', + 'hitsPerPage' => $perPage, + 'page' => $page, + 'sort' => ['name:asc'], + ]); + + $builder = new Builder(new SearchableUser, 'mustang', function ($meilisearch, $query, $options) { + $options['filter'] = 'foo=1'; + + return $meilisearch->search($query, $options); + }); + $builder->orderBy('name', 'asc'); + + $engine->paginate($builder, $perPage, $page); + } + + #[WithConfig('scout.soft_delete', true)] + public function test_update_empty_searchable_array_from_soft_deleted_model_does_not_add_documents_to_index() + { + $_ENV['searchable.chirp'] = []; + + $engine = $this->app->make(EngineManager::class)->engine(); + + $this->client->shouldReceive('index')->once()->with('chirps')->andReturn($index = m::mock(Indexes::class)); + $index->shouldNotReceive('addDocuments'); + + $engine->update(Collection::make([new Chirp])); + + unset($_ENV['searchable.chirp']); + } + + public function test_performing_search_without_callback_works() + { + $engine = $this->app->make(EngineManager::class)->engine(); + + $builder = new Builder(new SearchableUser, ''); + + $this->client->shouldReceive('index')->once()->with('users')->andReturn($index = m::mock(Indexes::class)); + $index->shouldReceive('rawSearch')->once()->andReturn([]); + + $engine->search($builder); + } + + public function test_where_conditions_are_applied() + { + $engine = $this->app->make(EngineManager::class)->engine(); + + $builder = new Builder(new SearchableUser, ''); + $builder->where('foo', 'bar'); + $builder->where('key', 'value'); + + $this->client->shouldReceive('index')->once()->with('users')->andReturn($index = m::mock(Indexes::class)); + $index->shouldReceive('rawSearch')->once()->with($builder->query, array_filter([ + 'filter' => 'foo="bar" AND key="value"', + 'hitsPerPage' => $builder->limit, + ]))->andReturn([]); + + $engine->search($builder); + } + public function test_where_not_in_conditions_are_applied() { $engine = $this->app->make(EngineManager::class)->engine(); diff --git a/tests/Unit/MeilisearchEngineTest.php b/tests/Unit/MeilisearchEngineTest.php index 1ba6eadf..17ab8520 100644 --- a/tests/Unit/MeilisearchEngineTest.php +++ b/tests/Unit/MeilisearchEngineTest.php @@ -85,7 +85,7 @@ public function test_map_ids_returns_correct_values_of_primary_key() public function test_returns_primary_keys_when_custom_array_order_present() { - $engine = m::mock(MeilisearchEngine::class); + $engine = m::spy(MeilisearchEngine::class); $builder = m::mock(Builder::class); $model = m::mock(stdClass::class); @@ -226,103 +226,13 @@ public function test_lazy_map_method_respects_order() ], $results->toArray()); } - public function test_a_model_is_indexed_with_a_custom_meilisearch_key() - { - $client = m::mock(Client::class); - $client->shouldReceive('index')->with('table')->andReturn($index = m::mock(Indexes::class)); - $index->shouldReceive('addDocuments')->once()->with([[ - 'meilisearch-key' => 'my-meilisearch-key.5', - 'id' => 5, - ]], 'meilisearch-key'); - - $engine = new MeilisearchEngine($client); - $engine->update(Collection::make([new MeilisearchCustomKeySearchableModel(['id' => 5])])); - } - - public function test_flush_a_model_with_a_custom_meilisearch_key() - { - $client = m::mock(Client::class); - $client->shouldReceive('index')->with('table')->andReturn($index = m::mock(Indexes::class)); - $index->shouldReceive('deleteAllDocuments'); - - $engine = new MeilisearchEngine($client); - $engine->flush(new MeilisearchCustomKeySearchableModel); - } - - public function test_update_empty_searchable_array_does_not_add_documents_to_index() - { - $client = m::mock(Client::class); - $client->shouldReceive('index')->with('table')->andReturn($index = m::mock(Indexes::class)); - $index->shouldNotReceive('addDocuments'); - - $engine = new MeilisearchEngine($client); - $engine->update(Collection::make([new EmptySearchableModel])); - } - - public function test_pagination_correct_parameters() - { - $perPage = 5; - $page = 2; - - $client = m::mock(Client::class); - $client->shouldReceive('index')->with('table')->andReturn($index = m::mock(Indexes::class)); - $index->shouldReceive('search')->with('mustang', [ - 'filter' => 'foo=1', - 'hitsPerPage' => $perPage, - 'page' => $page, - ]); - - $engine = new MeilisearchEngine($client); - $builder = new Builder(new SearchableModel, 'mustang', function ($meilisearch, $query, $options) { - $options['filter'] = 'foo=1'; - - return $meilisearch->search($query, $options); - }); - $engine->paginate($builder, $perPage, $page); - } - - public function test_pagination_sorted_parameter() - { - $perPage = 5; - $page = 2; - - $client = m::mock(Client::class); - $client->shouldReceive('index')->with('table')->andReturn($index = m::mock(Indexes::class)); - $index->shouldReceive('search')->with('mustang', [ - 'filter' => 'foo=1', - 'hitsPerPage' => $perPage, - 'page' => $page, - 'sort' => ['name:asc'], - ]); - - $engine = new MeilisearchEngine($client); - $builder = new Builder(new SearchableModel, 'mustang', function ($meilisearch, $query, $options) { - $options['filter'] = 'foo=1'; - - return $meilisearch->search($query, $options); - }); - $builder->orderBy('name', 'asc'); - $engine->paginate($builder, $perPage, $page); - } - - public function test_update_empty_searchable_array_from_soft_deleted_model_does_not_add_documents_to_index() - { - $client = m::mock(Client::class); - $client->shouldReceive('index')->with('table')->andReturn(m::mock(Indexes::class)); - $client->shouldReceive('index')->with('table')->andReturn($index = m::mock(Indexes::class)); - $index->shouldNotReceive('addDocuments'); - - $engine = new MeilisearchEngine($client, true); - $engine->update(Collection::make([new SoftDeletedEmptySearchableModel])); - } - public function test_engine_forwards_calls_to_meilisearch_client() { $client = m::mock(Client::class); - $client->shouldReceive('testMethodOnClient')->once(); + $client->shouldReceive('testMethodOnClient')->once()->andReturn('meilisearch'); $engine = new MeilisearchEngine($client); - $engine->testMethodOnClient(); + $this->assertSame('meilisearch', $engine->testMethodOnClient()); } public function test_updating_empty_eloquent_collection_does_nothing() @@ -333,33 +243,6 @@ public function test_updating_empty_eloquent_collection_does_nothing() $this->assertTrue(true); } - public function test_performing_search_without_callback_works() - { - $client = m::mock(Client::class); - $client->shouldReceive('index')->once()->andReturn($index = m::mock(Indexes::class)); - $index->shouldReceive('rawSearch')->once()->andReturn([]); - - $engine = new MeilisearchEngine($client); - $builder = new Builder(new SearchableModel, ''); - $engine->search($builder); - } - - public function test_where_conditions_are_applied() - { - $builder = new Builder(new SearchableModel, ''); - $builder->where('foo', 'bar'); - $builder->where('key', 'value'); - $client = m::mock(Client::class); - $client->shouldReceive('index')->once()->andReturn($index = m::mock(Indexes::class)); - $index->shouldReceive('rawSearch')->once()->with($builder->query, array_filter([ - 'filter' => 'foo="bar" AND key="value"', - 'hitsPerPage' => $builder->limit, - ]))->andReturn([]); - - $engine = new MeilisearchEngine($client); - $engine->search($builder); - } - public function test_engine_returns_hits_entry_from_search_response() { $this->assertTrue((new MeilisearchEngine(m::mock(Client::class)))->getTotalCount([ From 9eda2aa0a0a6f78d1decccecd74b38c9e8e969d5 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Tue, 12 Nov 2024 06:53:55 +0000 Subject: [PATCH 18/32] Apply fixes from StyleCI --- tests/Unit/MeilisearchEngineTest.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/Unit/MeilisearchEngineTest.php b/tests/Unit/MeilisearchEngineTest.php index 17ab8520..3bd1e7b2 100644 --- a/tests/Unit/MeilisearchEngineTest.php +++ b/tests/Unit/MeilisearchEngineTest.php @@ -7,16 +7,9 @@ use Illuminate\Support\Facades\Config; use Illuminate\Support\LazyCollection; use Laravel\Scout\Builder; -use Laravel\Scout\EngineManager; use Laravel\Scout\Engines\MeilisearchEngine; -use Laravel\Scout\Jobs\RemoveFromSearch; -use Laravel\Scout\Tests\Fixtures\EmptySearchableModel; use Laravel\Scout\Tests\Fixtures\SearchableModel; -use Laravel\Scout\Tests\Fixtures\SoftDeletedEmptySearchableModel; use Meilisearch\Client; -use Meilisearch\Contracts\IndexesResults; -use Meilisearch\Endpoints\Indexes; -use Meilisearch\Search\SearchResult; use Mockery as m; use PHPUnit\Framework\TestCase; use stdClass; From d17351b8b1221d5f953752f9242260d8e2c29233 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 12 Nov 2024 14:58:23 +0800 Subject: [PATCH 19/32] wip Signed-off-by: Mior Muhammad Zaki --- tests/Integration/SearchableTests.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Integration/SearchableTests.php b/tests/Integration/SearchableTests.php index ef25c85f..7821f928 100644 --- a/tests/Integration/SearchableTests.php +++ b/tests/Integration/SearchableTests.php @@ -108,6 +108,7 @@ protected function itCanUsePaginatedSearchWithQueryCallback() protected function itCanUsePaginatedSearchWithEmptyQueryCallback() { $queryCallback = function ($query) { + // }; return User::search('*')->query($queryCallback)->paginate(); From 9e0c8bfb4f50308fe2e28d9b890ecc9b93f72299 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 12 Nov 2024 15:30:51 +0800 Subject: [PATCH 20/32] wip Signed-off-by: Mior Muhammad Zaki --- .../ModelObserverWithSoftDeletesTest.php | 55 ++++++++++++ .../Unit/ModelObserverWithSoftDeletesTest.php | 83 ------------------- 2 files changed, 55 insertions(+), 83 deletions(-) create mode 100644 tests/Feature/ModelObserverWithSoftDeletesTest.php delete mode 100644 tests/Unit/ModelObserverWithSoftDeletesTest.php diff --git a/tests/Feature/ModelObserverWithSoftDeletesTest.php b/tests/Feature/ModelObserverWithSoftDeletesTest.php new file mode 100644 index 00000000..5dd26dd0 --- /dev/null +++ b/tests/Feature/ModelObserverWithSoftDeletesTest.php @@ -0,0 +1,55 @@ +createQuietly(); + + tap($this->app->make('scout.spied'), function ($scout) { + $scout->shouldReceive('delete')->once(); + }); + + $model->forceDelete(); + } + + public function test_deleted_handler_makes_model_searchable_when_it_should_be_searchable() + { + $model = ChirpFactory::new()->createQuietly(); + + tap($this->app->make('scout.spied'), function ($scout) { + $scout->shouldReceive('update')->once(); + }); + + $model->delete(); + } + + public function test_restored_handler_makes_model_searchable() + { + $model = ChirpFactory::new()->createQuietly([ + 'deleted_at' => now(), + ]); + + tap($this->app->make('scout.spied'), function ($scout) { + $scout->shouldReceive('update')->twice(); + }); + + $model->restore(); + } +} diff --git a/tests/Unit/ModelObserverWithSoftDeletesTest.php b/tests/Unit/ModelObserverWithSoftDeletesTest.php deleted file mode 100644 index 0ae79ac2..00000000 --- a/tests/Unit/ModelObserverWithSoftDeletesTest.php +++ /dev/null @@ -1,83 +0,0 @@ -with('scout.after_commit', m::any())->andReturn(false); - Config::shouldReceive('get')->with('scout.soft_delete', m::any())->andReturn(true); - } - - protected function tearDown(): void - { - m::close(); - } - - public function test_deleted_handler_makes_model_unsearchable_when_it_should_not_be_searchable() - { - $observer = new ModelObserver; - $model = m::mock(SearchableModelWithSoftDeletes::class); - $model->shouldReceive('searchShouldUpdate')->never(); // The saved event is forced - $model->shouldReceive('shouldBeSearchable')->once()->andReturn(false); // Should not be searchable - $model->shouldReceive('wasSearchableBeforeDelete')->andReturn(true); - $model->shouldReceive('wasSearchableBeforeUpdate')->andReturn(true); - $model->shouldReceive('searchable')->never(); - $model->shouldReceive('unsearchable')->once(); - $observer->deleted($model); - } - - public function test_deleted_handler_makes_model_searchable_when_it_should_be_searchable() - { - $observer = new ModelObserver; - $model = m::mock(SearchableModelWithSoftDeletes::class); - $model->shouldReceive('searchShouldUpdate')->never(); // The saved event is forced - $model->shouldReceive('shouldBeSearchable')->andReturn(true); // Should be searchable - $model->shouldReceive('wasSearchableBeforeDelete')->andReturn(true); - $model->shouldReceive('searchable')->once(); - $model->shouldReceive('unsearchable')->never(); - $observer->deleted($model); - } - - public function test_restored_handler_makes_model_searchable() - { - $observer = new ModelObserver; - $model = m::mock(SearchableModelWithSoftDeletes::class); - $model->shouldReceive('searchShouldUpdate')->never(); - $model->shouldReceive('shouldBeSearchable')->once()->andReturn(true); - $model->shouldReceive('searchable')->once(); - $model->shouldReceive('unsearchable')->never(); - $observer->restored($model); - } - - public function test_unsearchable_should_be_called_when_deleting() - { - $model = m::mock( - new SearchableModelWithSensitiveAttributes([ - 'first_name' => 'taylor', - 'last_name' => 'Otwell', - 'remember_token' => 123, - 'password' => 'secret', - ]) - )->makePartial(); - - // Let's pretend it's in sync with the database. - $model->syncOriginal(); - - // Assertions - $model->shouldReceive('searchable')->once(); - $model->shouldReceive('unsearchable')->never(); - - $observer = new ModelObserver; - $observer->deleted($model); - } -} From 0a77b2a703323110e5cd5a338a312a443b67f326 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 12 Nov 2024 15:59:35 +0800 Subject: [PATCH 21/32] wip Signed-off-by: Mior Muhammad Zaki --- tests/Feature/ModelObserverTest.php | 57 ++++++++++++++++++++++++- tests/Unit/ModelObserverTest.php | 18 -------- workbench/app/Models/SearchableUser.php | 6 +++ 3 files changed, 62 insertions(+), 19 deletions(-) diff --git a/tests/Feature/ModelObserverTest.php b/tests/Feature/ModelObserverTest.php index 48d662bf..2e33ee8c 100644 --- a/tests/Feature/ModelObserverTest.php +++ b/tests/Feature/ModelObserverTest.php @@ -2,9 +2,64 @@ namespace Laravel\Scout\Tests\Feature; +use Illuminate\Foundation\Testing\LazilyRefreshDatabase; +use Illuminate\Support\Facades\Bus; +use Laravel\Scout\Jobs\MakeSearchable; +use Laravel\Scout\ModelObserver; +use Orchestra\Testbench\Attributes\WithConfig; +use Orchestra\Testbench\Attributes\WithMigration; +use Orchestra\Testbench\Concerns\WithWorkbench; use Orchestra\Testbench\TestCase; +use Workbench\Database\Factories\SearchableUserFactory; +#[WithConfig('scout.driver', 'testing')] +#[WithConfig('scout.after_commit', false)] +#[WithConfig('scout.soft_delete', false)] +#[WithMigration] class ModelObserverTest extends TestCase { - // + use LazilyRefreshDatabase; + use WithWorkbench; + + public function test_saved_handler_makes_model_searchable() + { + $model = SearchableUserFactory::new()->createQuietly(['name' => 'Laravel']); + + tap($this->app->make('scout.spied'), function ($scout) { + $scout->shouldReceive('update')->once(); + }); + + $model->name = 'Laravel Scout'; + $model->save(); + } + + public function test_saved_handler_doesnt_make_model_searchable_when_search_shouldnt_update() + { + $_ENV['search-index.user'] = false; + + $model = SearchableUserFactory::new()->createQuietly(['name' => 'Laravel']); + + tap($this->app->make('scout.spied'), function ($scout) { + $scout->shouldNotReceive('update'); + }); + + $model->save(); + + unset($_ENV['search-index.user']); + } + + public function test_saved_handler_doesnt_make_model_searchable_when_disabled() + { + $model = SearchableUserFactory::new()->createQuietly(['name' => 'Laravel']); + + ModelObserver::disableSyncingFor($model::class); + + tap($this->app->make('scout.spied'), function ($scout) { + $scout->shouldNotReceive('update'); + }); + + $model->save(); + + ModelObserver::enableSyncingFor($model::class); + } } diff --git a/tests/Unit/ModelObserverTest.php b/tests/Unit/ModelObserverTest.php index ce92db4a..afc88a0f 100644 --- a/tests/Unit/ModelObserverTest.php +++ b/tests/Unit/ModelObserverTest.php @@ -24,25 +24,7 @@ protected function tearDown(): void m::close(); } - public function test_saved_handler_makes_model_searchable() - { - $observer = new ModelObserver; - $model = m::mock(); - $model->shouldReceive('searchIndexShouldBeUpdated')->andReturn(true); - $model->shouldReceive('shouldBeSearchable')->andReturn(true); - $model->shouldReceive('searchable')->once(); - $observer->saved($model); - } - public function test_saved_handler_doesnt_make_model_searchable_when_search_shouldnt_update() - { - $observer = new ModelObserver; - $model = m::mock(); - $model->shouldReceive('searchIndexShouldBeUpdated')->andReturn(false); - $model->shouldReceive('shouldBeSearchable')->andReturn(true); - $model->shouldReceive('searchable')->never(); - $observer->saved($model); - } public function test_saved_handler_doesnt_make_model_searchable_when_disabled() { diff --git a/workbench/app/Models/SearchableUser.php b/workbench/app/Models/SearchableUser.php index 6e27c363..d9bc26cb 100644 --- a/workbench/app/Models/SearchableUser.php +++ b/workbench/app/Models/SearchableUser.php @@ -17,4 +17,10 @@ public function toSearchableArray() 'email' => $this->email, ]; } + + /** {@inheritDoc} */ + public function searchIndexShouldBeUpdated() + { + return $_ENV['search-index.user'] ?? true; + } } From 396b398aca756d5021457dc139a563c0bbadf5d3 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Tue, 12 Nov 2024 07:59:45 +0000 Subject: [PATCH 22/32] Apply fixes from StyleCI --- tests/Feature/ModelObserverTest.php | 2 -- tests/Unit/ModelObserverTest.php | 2 -- 2 files changed, 4 deletions(-) diff --git a/tests/Feature/ModelObserverTest.php b/tests/Feature/ModelObserverTest.php index 2e33ee8c..2f5f41b1 100644 --- a/tests/Feature/ModelObserverTest.php +++ b/tests/Feature/ModelObserverTest.php @@ -3,8 +3,6 @@ namespace Laravel\Scout\Tests\Feature; use Illuminate\Foundation\Testing\LazilyRefreshDatabase; -use Illuminate\Support\Facades\Bus; -use Laravel\Scout\Jobs\MakeSearchable; use Laravel\Scout\ModelObserver; use Orchestra\Testbench\Attributes\WithConfig; use Orchestra\Testbench\Attributes\WithMigration; diff --git a/tests/Unit/ModelObserverTest.php b/tests/Unit/ModelObserverTest.php index afc88a0f..7823a769 100644 --- a/tests/Unit/ModelObserverTest.php +++ b/tests/Unit/ModelObserverTest.php @@ -24,8 +24,6 @@ protected function tearDown(): void m::close(); } - - public function test_saved_handler_doesnt_make_model_searchable_when_disabled() { $observer = new ModelObserver; From 6abff7fea2361f090ebc63893ee855c2c66af7c5 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 12 Nov 2024 16:34:58 +0800 Subject: [PATCH 23/32] wip Signed-off-by: Mior Muhammad Zaki --- tests/Feature/ModelObserverTest.php | 86 +++++++++++++++++++++++-- tests/Unit/ModelObserverTest.php | 64 ------------------ workbench/app/Models/Chirp.php | 2 +- workbench/app/Models/SearchableUser.php | 23 ++++++- 4 files changed, 104 insertions(+), 71 deletions(-) diff --git a/tests/Feature/ModelObserverTest.php b/tests/Feature/ModelObserverTest.php index 2e33ee8c..041df0aa 100644 --- a/tests/Feature/ModelObserverTest.php +++ b/tests/Feature/ModelObserverTest.php @@ -10,6 +10,8 @@ use Orchestra\Testbench\Attributes\WithMigration; use Orchestra\Testbench\Concerns\WithWorkbench; use Orchestra\Testbench\TestCase; +use Workbench\App\Models\SearchableUser; +use Workbench\Database\Factories\ChirpFactory; use Workbench\Database\Factories\SearchableUserFactory; #[WithConfig('scout.driver', 'testing')] @@ -35,7 +37,7 @@ public function test_saved_handler_makes_model_searchable() public function test_saved_handler_doesnt_make_model_searchable_when_search_shouldnt_update() { - $_ENV['search-index.user'] = false; + $_ENV['user.searchIndexShouldBeUpdated'] = false; $model = SearchableUserFactory::new()->createQuietly(['name' => 'Laravel']); @@ -43,23 +45,99 @@ public function test_saved_handler_doesnt_make_model_searchable_when_search_shou $scout->shouldNotReceive('update'); }); + $model->name = 'Laravel Scout'; $model->save(); - unset($_ENV['search-index.user']); + unset($_ENV['user.searchIndexShouldBeUpdated']); } public function test_saved_handler_doesnt_make_model_searchable_when_disabled() { $model = SearchableUserFactory::new()->createQuietly(['name' => 'Laravel']); - ModelObserver::disableSyncingFor($model::class); + ModelObserver::disableSyncingFor(SearchableUser::class); tap($this->app->make('scout.spied'), function ($scout) { $scout->shouldNotReceive('update'); }); + $model->name = 'Laravel Scout'; $model->save(); - ModelObserver::enableSyncingFor($model::class); + ModelObserver::enableSyncingFor(SearchableUser::class); + } + + public function test_saved_handler_makes_model_unsearchable_when_disabled_per_model_rule() + { + $_ENV['user.shouldBeSearchable'] = false; + + $model = SearchableUserFactory::new()->createQuietly(['name' => 'Laravel']); + + tap($this->app->make('scout.spied'), function ($scout) { + $scout->shouldNotReceive('update'); + }); + + $model->name = 'Laravel Scout'; + $model->save(); + + unset($_ENV['user.shouldBeSearchable']); + } + + public function saved_handler_doesnt_make_model_unsearchable_when_disabled_per_model_rule_and_already_unsearchable() + { + $_ENV['user.wasSearchableBeforeUpdate'] = false; + $_ENV['user.shouldBeSearchable'] = false; + + $model = SearchableUserFactory::new()->createQuietly(['name' => 'Laravel']); + + tap($this->app->make('scout.spied'), function ($scout) { + $scout->shouldNotReceive('update'); + }); + + $model->name = 'Laravel Scout'; + $model->save(); + + unset($_ENV['user.shouldBeSearchable'], $_ENV['user.wasSearchableBeforeUpdate']); + } + + public function test_deleted_handler_doesnt_make_model_unsearchable_when_already_unsearchable() + { + $_ENV['user.wasSearchableBeforeDelete'] = false; + + $model = SearchableUserFactory::new()->createQuietly(); + + tap($this->app->make('scout.spied'), function ($scout) { + $scout->shouldNotReceive('delete'); + }); + + $model->delete(); + + unset($_ENV['user.wasSearchableBeforeDelete']); + } + + public function test_deleted_handler_makes_model_unsearchable() + { + $_ENV['user.wasSearchableBeforeDelete'] = true; + + $model = SearchableUserFactory::new()->createQuietly(); + + tap($this->app->make('scout.spied'), function ($scout) { + $scout->shouldReceive('delete')->once(); + }); + + $model->forceDelete(); + + unset($_ENV['user.wasSearchableBeforeDelete']); + } + + public function test_deleted_handler_on_soft_delete_model_makes_model_unsearchable() + { + $model = ChirpFactory::new()->createQuietly(); + + tap($this->app->make('scout.spied'), function ($scout) { + $scout->shouldReceive('delete')->once(); + }); + + $model->delete(); } } diff --git a/tests/Unit/ModelObserverTest.php b/tests/Unit/ModelObserverTest.php index afc88a0f..dc74ae76 100644 --- a/tests/Unit/ModelObserverTest.php +++ b/tests/Unit/ModelObserverTest.php @@ -24,70 +24,6 @@ protected function tearDown(): void m::close(); } - - - public function test_saved_handler_doesnt_make_model_searchable_when_disabled() - { - $observer = new ModelObserver; - $model = m::mock(); - $observer->disableSyncingFor(get_class($model)); - $model->shouldReceive('searchable')->never(); - $observer->saved($model); - $observer->enableSyncingFor(get_class($model)); - } - - public function test_saved_handler_makes_model_unsearchable_when_disabled_per_model_rule() - { - $observer = new ModelObserver; - $model = m::mock(); - $model->shouldReceive('searchIndexShouldBeUpdated')->andReturn(true); - $model->shouldReceive('shouldBeSearchable')->andReturn(false); - $model->shouldReceive('wasSearchableBeforeUpdate')->andReturn(true); - $model->shouldReceive('searchable')->never(); - $model->shouldReceive('unsearchable')->once(); - $observer->saved($model); - } - - public function test_saved_handler_doesnt_make_model_unsearchable_when_disabled_per_model_rule_and_already_unsearchable() - { - $observer = new ModelObserver; - $model = m::mock(Model::class); - $model->shouldReceive('searchIndexShouldBeUpdated')->andReturn(true); - $model->shouldReceive('shouldBeSearchable')->andReturn(false); - $model->shouldReceive('wasSearchableBeforeUpdate')->andReturn(false); - $model->shouldReceive('searchable')->never(); - $model->shouldReceive('unsearchable')->never(); - $observer->saved($model); - } - - public function test_deleted_handler_doesnt_make_model_unsearchable_when_already_unsearchable() - { - $observer = new ModelObserver; - $model = m::mock(); - $model->shouldReceive('wasSearchableBeforeDelete')->andReturn(false); - $model->shouldReceive('unsearchable')->never(); - $observer->deleted($model); - } - - public function test_deleted_handler_makes_model_unsearchable() - { - $observer = new ModelObserver; - $model = m::mock(); - $model->shouldReceive('wasSearchableBeforeDelete')->andReturn(true); - $model->shouldReceive('unsearchable')->once(); - $observer->deleted($model); - } - - public function test_deleted_handler_on_soft_delete_model_makes_model_unsearchable() - { - $observer = new ModelObserver; - $model = m::mock(SearchableModelWithSoftDeletes::class); - $model->shouldReceive('wasSearchableBeforeDelete')->andReturn(true); - $model->shouldReceive('searchable')->never(); - $model->shouldReceive('unsearchable')->once(); - $observer->deleted($model); - } - public function test_update_on_sensitive_attributes_triggers_search() { $model = m::mock( diff --git a/workbench/app/Models/Chirp.php b/workbench/app/Models/Chirp.php index 294c10dd..abb6d11a 100644 --- a/workbench/app/Models/Chirp.php +++ b/workbench/app/Models/Chirp.php @@ -36,7 +36,7 @@ public function getScoutKeyName() public function toSearchableArray() { - return $_ENV['searchable.chirp'] ?? [ + return $_ENV['chirp.toSearchableArray'] ?? [ 'content' => $this->content, ]; } diff --git a/workbench/app/Models/SearchableUser.php b/workbench/app/Models/SearchableUser.php index d9bc26cb..a1826fe7 100644 --- a/workbench/app/Models/SearchableUser.php +++ b/workbench/app/Models/SearchableUser.php @@ -11,16 +11,35 @@ class SearchableUser extends User /** {@inheritDoc} */ public function toSearchableArray() { - return $_ENV['searchable.user'] ?? [ + return $_ENV['user.toSearchableArray'] ?? [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, ]; } + /** {@inheritDoc} */ + public function wasSearchableBeforeUpdate() + { + return $_ENV['user.wasSearchableBeforeUpdate'] ?? true; + } + + /** {@inheritDoc} */ + public function wasSearchableBeforeDelete() + { + return $_ENV['user.wasSearchableBeforeDelete'] ?? true; + } + + /** {@inheritDoc} */ + public function shouldBeSearchable() + { + return $_ENV['user.shouldBeSearchable'] ?? true; + } + + /** {@inheritDoc} */ public function searchIndexShouldBeUpdated() { - return $_ENV['search-index.user'] ?? true; + return $_ENV['user.searchIndexShouldBeUpdated'] ?? true; } } From 3afee1a72dc4e9010bcd118b45c8adfd1d06a67f Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 12 Nov 2024 16:38:43 +0800 Subject: [PATCH 24/32] wip Signed-off-by: Mior Muhammad Zaki --- tests/Feature/DatabaseEngineTest.php | 72 +++++++++---------- tests/Feature/Engines/Algolia3EngineTest.php | 8 +-- .../Feature/Engines/MeilisearchEngineTest.php | 8 +-- tests/Feature/ModelObserverTest.php | 2 - tests/Unit/ModelObserverTest.php | 2 - 5 files changed, 44 insertions(+), 48 deletions(-) diff --git a/tests/Feature/DatabaseEngineTest.php b/tests/Feature/DatabaseEngineTest.php index e3f6df12..848b6485 100644 --- a/tests/Feature/DatabaseEngineTest.php +++ b/tests/Feature/DatabaseEngineTest.php @@ -4,11 +4,11 @@ use Illuminate\Foundation\Testing\LazilyRefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; -use Laravel\Scout\Tests\Fixtures\SearchableUserDatabaseModel; use Orchestra\Testbench\Concerns\WithLaravelMigrations; use Orchestra\Testbench\Concerns\WithWorkbench; -use Orchestra\Testbench\Factories\UserFactory; use Orchestra\Testbench\TestCase; +use Workbench\App\Models\SearchableUser; +use Workbench\Database\Factories\SearchableUserFactory; class DatabaseEngineTest extends TestCase { @@ -21,12 +21,12 @@ protected function defineEnvironment($app) protected function afterRefreshingDatabase() { - UserFactory::new()->create([ + SearchableUserFactory::new()->create([ 'name' => 'Taylor Otwell', 'email' => 'taylor@laravel.com', ]); - UserFactory::new()->create([ + SearchableUserFactory::new()->create([ 'name' => 'Abigail Otwell', 'email' => 'abigail@laravel.com', ]); @@ -34,115 +34,115 @@ protected function afterRefreshingDatabase() public function test_it_can_retrieve_results_with_empty_search() { - $models = SearchableUserDatabaseModel::search()->get(); + $models = SearchableUser::search()->get(); $this->assertCount(2, $models); } public function test_it_does_not_add_search_where_clauses_with_empty_search() { - SearchableUserDatabaseModel::search('')->query(function ($builder) { + SearchableUser::search('')->query(function ($builder) { $this->assertSame('select * from "users"', $builder->toSql()); })->get(); } public function test_it_adds_search_where_clauses_with_non_empty_search() { - SearchableUserDatabaseModel::search('Taylor')->query(function ($builder) { + SearchableUser::search('Taylor')->query(function ($builder) { $this->assertSame('select * from "users" where ("users"."id" like ? or "users"."name" like ? or "users"."email" like ?)', $builder->toSql()); })->get(); } public function test_it_can_retrieve_results() { - $models = SearchableUserDatabaseModel::search('Taylor')->where('email', 'taylor@laravel.com')->get(); + $models = SearchableUser::search('Taylor')->where('email', 'taylor@laravel.com')->get(); $this->assertCount(1, $models); $this->assertEquals(1, $models[0]->id); - $models = SearchableUserDatabaseModel::search('Taylor')->query(function ($query) { + $models = SearchableUser::search('Taylor')->query(function ($query) { $query->where('email', 'like', 'taylor@laravel.com'); })->get(); $this->assertCount(1, $models); $this->assertEquals(1, $models[0]->id); - $models = SearchableUserDatabaseModel::search('Abigail')->where('email', 'abigail@laravel.com')->get(); + $models = SearchableUser::search('Abigail')->where('email', 'abigail@laravel.com')->get(); $this->assertCount(1, $models); $this->assertEquals(2, $models[0]->id); - $models = SearchableUserDatabaseModel::search('Taylor')->where('email', 'abigail@laravel.com')->get(); + $models = SearchableUser::search('Taylor')->where('email', 'abigail@laravel.com')->get(); $this->assertCount(0, $models); - $models = SearchableUserDatabaseModel::search('Taylor')->where('email', 'taylor@laravel.com')->get(); + $models = SearchableUser::search('Taylor')->where('email', 'taylor@laravel.com')->get(); $this->assertCount(1, $models); - $models = SearchableUserDatabaseModel::search('otwell')->get(); + $models = SearchableUser::search('otwell')->get(); $this->assertCount(2, $models); - $models = SearchableUserDatabaseModel::search('laravel')->get(); + $models = SearchableUser::search('laravel')->get(); $this->assertCount(2, $models); - $models = SearchableUserDatabaseModel::search('foo')->get(); + $models = SearchableUser::search('foo')->get(); $this->assertCount(0, $models); - $models = SearchableUserDatabaseModel::search('Abigail')->where('email', 'taylor@laravel.com')->get(); + $models = SearchableUser::search('Abigail')->where('email', 'taylor@laravel.com')->get(); $this->assertCount(0, $models); } public function test_it_can_paginate_results() { - $models = SearchableUserDatabaseModel::search('Taylor')->where('email', 'taylor@laravel.com')->paginate(); + $models = SearchableUser::search('Taylor')->where('email', 'taylor@laravel.com')->paginate(); $this->assertCount(1, $models); - $models = SearchableUserDatabaseModel::search('Taylor')->where('email', 'abigail@laravel.com')->paginate(); + $models = SearchableUser::search('Taylor')->where('email', 'abigail@laravel.com')->paginate(); $this->assertCount(0, $models); - $models = SearchableUserDatabaseModel::search('Taylor')->where('email', 'taylor@laravel.com')->paginate(); + $models = SearchableUser::search('Taylor')->where('email', 'taylor@laravel.com')->paginate(); $this->assertCount(1, $models); - $models = SearchableUserDatabaseModel::search('laravel')->paginate(); + $models = SearchableUser::search('laravel')->paginate(); $this->assertCount(2, $models); } public function test_it_can_paginate_using_a_custom_page_name() { - $models = SearchableUserDatabaseModel::search('Taylor')->where('email', 'taylor@laravel.com')->paginate(); + $models = SearchableUser::search('Taylor')->where('email', 'taylor@laravel.com')->paginate(); $this->assertStringContainsString('page=1', $models->url(1)); - $models = SearchableUserDatabaseModel::search('Taylor')->where('email', 'taylor@laravel.com')->paginate(pageName: 'foo'); + $models = SearchableUser::search('Taylor')->where('email', 'taylor@laravel.com')->paginate(pageName: 'foo'); $this->assertStringContainsString('foo=1', $models->url(1)); - $models = SearchableUserDatabaseModel::search('Taylor')->where('email', 'taylor@laravel.com')->paginate(pageName: 'bar'); + $models = SearchableUser::search('Taylor')->where('email', 'taylor@laravel.com')->paginate(pageName: 'bar'); $this->assertStringContainsString('bar=1', $models->url(1)); } public function test_it_can_simple_paginate_using_a_custom_page_name() { - $models = SearchableUserDatabaseModel::search('Taylor')->where('email', 'taylor@laravel.com')->simplePaginate(); + $models = SearchableUser::search('Taylor')->where('email', 'taylor@laravel.com')->simplePaginate(); $this->assertStringContainsString('page=1', $models->url(1)); - $models = SearchableUserDatabaseModel::search('Taylor')->where('email', 'taylor@laravel.com')->simplePaginate(pageName: 'foo'); + $models = SearchableUser::search('Taylor')->where('email', 'taylor@laravel.com')->simplePaginate(pageName: 'foo'); $this->assertStringContainsString('foo=1', $models->url(1)); - $models = SearchableUserDatabaseModel::search('Taylor')->where('email', 'taylor@laravel.com')->simplePaginate(pageName: 'bar'); + $models = SearchableUser::search('Taylor')->where('email', 'taylor@laravel.com')->simplePaginate(pageName: 'bar'); $this->assertStringContainsString('bar=1', $models->url(1)); } public function test_limit_is_applied() { - $models = SearchableUserDatabaseModel::search('laravel')->get(); + $models = SearchableUser::search('laravel')->get(); $this->assertCount(2, $models); - $models = SearchableUserDatabaseModel::search('laravel')->take(1)->get(); + $models = SearchableUser::search('laravel')->take(1)->get(); $this->assertCount(1, $models); } public function test_tap_is_applied() { - $models = SearchableUserDatabaseModel::search('laravel')->get(); + $models = SearchableUser::search('laravel')->get(); $this->assertCount(2, $models); - $models = SearchableUserDatabaseModel::search('laravel')->tap(function ($query) { + $models = SearchableUser::search('laravel')->tap(function ($query) { return $query->take(1); })->get(); $this->assertCount(1, $models); @@ -150,27 +150,27 @@ public function test_tap_is_applied() public function test_it_can_order_results() { - $models = SearchableUserDatabaseModel::search('laravel')->orderBy('name', 'asc')->take(1)->get(); + $models = SearchableUser::search('laravel')->orderBy('name', 'asc')->take(1)->get(); $this->assertCount(1, $models); $this->assertEquals('Abigail Otwell', $models[0]->name); - $modelsPaginate = SearchableUserDatabaseModel::search('laravel')->orderBy('name', 'asc')->paginate(1, 'page', 1); + $modelsPaginate = SearchableUser::search('laravel')->orderBy('name', 'asc')->paginate(1, 'page', 1); $this->assertCount(1, $modelsPaginate); $this->assertEquals('Abigail Otwell', $modelsPaginate[0]->name); - $modelsSimplePaginate = SearchableUserDatabaseModel::search('laravel')->orderBy('name', 'asc')->simplePaginate(1, 'page', 1); + $modelsSimplePaginate = SearchableUser::search('laravel')->orderBy('name', 'asc')->simplePaginate(1, 'page', 1); $this->assertCount(1, $modelsPaginate); $this->assertEquals('Abigail Otwell', $modelsSimplePaginate[0]->name); - $models = SearchableUserDatabaseModel::search('laravel')->orderBy('name', 'desc')->take(1)->get(); + $models = SearchableUser::search('laravel')->orderBy('name', 'desc')->take(1)->get(); $this->assertCount(1, $models); $this->assertEquals('Taylor Otwell', $models[0]->name); - $modelsPaginate = SearchableUserDatabaseModel::search('laravel')->orderBy('name', 'desc')->paginate(1, 'page', 1); + $modelsPaginate = SearchableUser::search('laravel')->orderBy('name', 'desc')->paginate(1, 'page', 1); $this->assertCount(1, $modelsPaginate); $this->assertEquals('Taylor Otwell', $modelsPaginate[0]->name); - $modelsSimplePaginate = SearchableUserDatabaseModel::search('laravel')->orderBy('name', 'desc')->simplePaginate(1, 'page', 1); + $modelsSimplePaginate = SearchableUser::search('laravel')->orderBy('name', 'desc')->simplePaginate(1, 'page', 1); $this->assertCount(1, $modelsSimplePaginate); $this->assertEquals('Taylor Otwell', $modelsSimplePaginate[0]->name); } diff --git a/tests/Feature/Engines/Algolia3EngineTest.php b/tests/Feature/Engines/Algolia3EngineTest.php index fe1ae945..cd46d65e 100644 --- a/tests/Feature/Engines/Algolia3EngineTest.php +++ b/tests/Feature/Engines/Algolia3EngineTest.php @@ -217,7 +217,7 @@ public function test_flush_a_model_with_a_custom_algolia_key() public function test_update_empty_searchable_array_does_not_add_objects_to_index() { - $_ENV['searchable.user'] = []; + $_ENV['user.toSearchableArray'] = []; $engine = $this->app->make(EngineManager::class)->engine(); @@ -226,13 +226,13 @@ public function test_update_empty_searchable_array_does_not_add_objects_to_index $engine->update(Collection::make([new SearchableUser])); - unset($_ENV['searchable.user']); + unset($_ENV['user.toSearchableArray']); } #[WithConfig('scout.soft_delete', true)] public function test_update_empty_searchable_array_from_soft_deleted_model_does_not_add_objects_to_index() { - $_ENV['searchable.chirp'] = []; + $_ENV['chirp.toSearchableArray'] = []; $engine = $this->app->make(EngineManager::class)->engine(); @@ -241,6 +241,6 @@ public function test_update_empty_searchable_array_from_soft_deleted_model_does_ $engine->update(Collection::make([new Chirp])); - unset($_ENV['searchable.chirp']); + unset($_ENV['chirp.toSearchableArray']); } } diff --git a/tests/Feature/Engines/MeilisearchEngineTest.php b/tests/Feature/Engines/MeilisearchEngineTest.php index 1fb8336f..8c8053bf 100644 --- a/tests/Feature/Engines/MeilisearchEngineTest.php +++ b/tests/Feature/Engines/MeilisearchEngineTest.php @@ -245,7 +245,7 @@ public function test_flush_a_model_with_a_custom_meilisearch_key() public function test_update_empty_searchable_array_does_not_add_documents_to_index() { - $_ENV['searchable.user'] = []; + $_ENV['user.toSearchableArray'] = []; $engine = $this->app->make(EngineManager::class)->engine(); @@ -254,7 +254,7 @@ public function test_update_empty_searchable_array_does_not_add_documents_to_ind $engine->update(Collection::make([new SearchableUser])); - unset($_ENV['searchable.user']); + unset($_ENV['user.toSearchableArray']); } public function test_pagination_correct_parameters() @@ -308,7 +308,7 @@ public function test_pagination_sorted_parameter() #[WithConfig('scout.soft_delete', true)] public function test_update_empty_searchable_array_from_soft_deleted_model_does_not_add_documents_to_index() { - $_ENV['searchable.chirp'] = []; + $_ENV['chirp.toSearchableArray'] = []; $engine = $this->app->make(EngineManager::class)->engine(); @@ -317,7 +317,7 @@ public function test_update_empty_searchable_array_from_soft_deleted_model_does_ $engine->update(Collection::make([new Chirp])); - unset($_ENV['searchable.chirp']); + unset($_ENV['chirp.toSearchableArray']); } public function test_performing_search_without_callback_works() diff --git a/tests/Feature/ModelObserverTest.php b/tests/Feature/ModelObserverTest.php index 041df0aa..41611d27 100644 --- a/tests/Feature/ModelObserverTest.php +++ b/tests/Feature/ModelObserverTest.php @@ -3,8 +3,6 @@ namespace Laravel\Scout\Tests\Feature; use Illuminate\Foundation\Testing\LazilyRefreshDatabase; -use Illuminate\Support\Facades\Bus; -use Laravel\Scout\Jobs\MakeSearchable; use Laravel\Scout\ModelObserver; use Orchestra\Testbench\Attributes\WithConfig; use Orchestra\Testbench\Attributes\WithMigration; diff --git a/tests/Unit/ModelObserverTest.php b/tests/Unit/ModelObserverTest.php index dc74ae76..c008bb70 100644 --- a/tests/Unit/ModelObserverTest.php +++ b/tests/Unit/ModelObserverTest.php @@ -2,11 +2,9 @@ namespace Laravel\Scout\Tests\Unit; -use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Config; use Laravel\Scout\ModelObserver; use Laravel\Scout\Tests\Fixtures\SearchableModelWithSensitiveAttributes; -use Laravel\Scout\Tests\Fixtures\SearchableModelWithSoftDeletes; use Mockery as m; use PHPUnit\Framework\TestCase; From e1fc15d5d4eb15ac4de415f3911cfca33ede7ada Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Tue, 12 Nov 2024 08:39:24 +0000 Subject: [PATCH 25/32] Apply fixes from StyleCI --- workbench/app/Models/SearchableUser.php | 1 - 1 file changed, 1 deletion(-) diff --git a/workbench/app/Models/SearchableUser.php b/workbench/app/Models/SearchableUser.php index a1826fe7..94bc08fc 100644 --- a/workbench/app/Models/SearchableUser.php +++ b/workbench/app/Models/SearchableUser.php @@ -36,7 +36,6 @@ public function shouldBeSearchable() return $_ENV['user.shouldBeSearchable'] ?? true; } - /** {@inheritDoc} */ public function searchIndexShouldBeUpdated() { From d1a27765dde46cfb3cc7fab6d789e2631ba1a7f2 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 12 Nov 2024 16:51:29 +0800 Subject: [PATCH 26/32] wip Signed-off-by: Mior Muhammad Zaki --- .../Fixtures/SearchableUserDatabaseModel.php | 27 ----------------- tests/Fixtures/SearchableUserModel.php | 13 --------- ...SearchableUserModelWithCustomCreatedAt.php | 15 ---------- .../SoftDeletedEmptySearchableModel.php | 21 -------------- tests/Fixtures/User.php | 24 --------------- tests/Integration/AlgoliaSearchableTest.php | 4 +-- .../Integration/MeilisearchSearchableTest.php | 4 +-- tests/Integration/SearchableTests.php | 29 ++++++++++++------- tests/Integration/TypesenseSearchableTest.php | 4 +-- workbench/app/Models/Chirp.php | 6 +++- workbench/app/Models/SearchableUser.php | 6 +++- 11 files changed, 34 insertions(+), 119 deletions(-) delete mode 100644 tests/Fixtures/SearchableUserDatabaseModel.php delete mode 100644 tests/Fixtures/SearchableUserModel.php delete mode 100644 tests/Fixtures/SearchableUserModelWithCustomCreatedAt.php delete mode 100644 tests/Fixtures/SoftDeletedEmptySearchableModel.php delete mode 100644 tests/Fixtures/User.php diff --git a/tests/Fixtures/SearchableUserDatabaseModel.php b/tests/Fixtures/SearchableUserDatabaseModel.php deleted file mode 100644 index 5be01b02..00000000 --- a/tests/Fixtures/SearchableUserDatabaseModel.php +++ /dev/null @@ -1,27 +0,0 @@ - $this->id, - 'name' => $this->name, - 'email' => $this->email, - ]; - } -} diff --git a/tests/Fixtures/SearchableUserModel.php b/tests/Fixtures/SearchableUserModel.php deleted file mode 100644 index 49eef0f0..00000000 --- a/tests/Fixtures/SearchableUserModel.php +++ /dev/null @@ -1,13 +0,0 @@ - 1]; - } -} diff --git a/tests/Fixtures/User.php b/tests/Fixtures/User.php deleted file mode 100644 index 908961f9..00000000 --- a/tests/Fixtures/User.php +++ /dev/null @@ -1,24 +0,0 @@ - - */ - public function toSearchableArray(): array - { - return [ - 'id' => (int) $this->id, - 'name' => $this->name, - ]; - } -} diff --git a/tests/Integration/AlgoliaSearchableTest.php b/tests/Integration/AlgoliaSearchableTest.php index 76ce55d0..2fb66973 100644 --- a/tests/Integration/AlgoliaSearchableTest.php +++ b/tests/Integration/AlgoliaSearchableTest.php @@ -2,8 +2,8 @@ namespace Laravel\Scout\Tests\Integration; -use Laravel\Scout\Tests\Fixtures\User; use Orchestra\Testbench\Attributes\RequiresEnv; +use Workbench\App\Models\SearchableUser; /** * @group algolia @@ -42,7 +42,7 @@ protected function defineDatabaseMigrations() */ protected function afterRefreshingDatabase() { - $this->importScoutIndexFrom(User::class); + $this->importScoutIndexFrom(SearchableUser::class); } public function test_it_can_use_basic_search() diff --git a/tests/Integration/MeilisearchSearchableTest.php b/tests/Integration/MeilisearchSearchableTest.php index 8fdc4441..e4caf7a8 100644 --- a/tests/Integration/MeilisearchSearchableTest.php +++ b/tests/Integration/MeilisearchSearchableTest.php @@ -5,12 +5,12 @@ use Illuminate\Database\Eloquent\Collection; use Laravel\Scout\Builder; use Laravel\Scout\Engines\MeilisearchEngine; -use Laravel\Scout\Tests\Fixtures\User; use Laravel\Scout\Tests\Fixtures\VersionableModel; use Meilisearch\Client; use Meilisearch\Endpoints\Indexes; use Mockery as m; use Orchestra\Testbench\Attributes\RequiresEnv; +use Workbench\App\Models\SearchableUser; /** * @group meilisearch @@ -48,7 +48,7 @@ protected function defineScoutDatabaseMigrations() { $this->baseDefineScoutDatabaseMigrations(); - $this->importScoutIndexFrom(User::class); + $this->importScoutIndexFrom(SearchableUser::class); } public function test_it_can_use_basic_search() diff --git a/tests/Integration/SearchableTests.php b/tests/Integration/SearchableTests.php index 7821f928..7efccd30 100644 --- a/tests/Integration/SearchableTests.php +++ b/tests/Integration/SearchableTests.php @@ -4,8 +4,8 @@ use Illuminate\Database\Eloquent\Factories\Sequence; use Illuminate\Support\LazyCollection; -use Laravel\Scout\Tests\Fixtures\User; -use Orchestra\Testbench\Factories\UserFactory; +use Workbench\App\Models\SearchableUser; +use Workbench\Database\Factories\UserFactory; trait SearchableTests { @@ -17,6 +17,13 @@ trait SearchableTests */ protected function defineScoutEnvironment($app) { + $_ENV['user.toSearchableArray'] = function ($model) { + return [ + 'id' => (int) $model->id, + 'name' => $model->name, + ]; + }; + $app['config']->set('scout.driver', static::scoutDriver()); } @@ -63,24 +70,24 @@ protected function defineScoutDatabaseMigrations(): void protected function itCanUseBasicSearch() { - return User::search('lar')->take(10)->get(); + return SearchableUser::search('lar')->take(10)->get(); } protected function itCanUseBasicSearchWithQueryCallback() { - return User::search('lar')->take(10)->query(function ($query) { + return SearchableUser::search('lar')->take(10)->query(function ($query) { return $query->whereNotNull('email_verified_at'); })->get(); } protected function itCanUseBasicSearchToFetchKeys() { - return User::search('lar')->take(10)->keys(); + return SearchableUser::search('lar')->take(10)->keys(); } protected function itCanUseBasicSearchWithQueryCallbackToFetchKeys() { - return User::search('lar')->take(10)->query(function ($query) { + return SearchableUser::search('lar')->take(10)->query(function ($query) { return $query->whereNotNull('email_verified_at'); })->keys(); } @@ -88,8 +95,8 @@ protected function itCanUseBasicSearchWithQueryCallbackToFetchKeys() protected function itCanUsePaginatedSearch() { return [ - User::search('lar')->take(10)->paginate(5, 'page', 1), - User::search('lar')->take(10)->paginate(5, 'page', 2), + SearchableUser::search('lar')->take(10)->paginate(5, 'page', 1), + SearchableUser::search('lar')->take(10)->paginate(5, 'page', 2), ]; } @@ -100,8 +107,8 @@ protected function itCanUsePaginatedSearchWithQueryCallback() }; return [ - User::search('lar')->take(10)->query($queryCallback)->paginate(5, 'page', 1), - User::search('lar')->take(10)->query($queryCallback)->paginate(5, 'page', 2), + SearchableUser::search('lar')->take(10)->query($queryCallback)->paginate(5, 'page', 1), + SearchableUser::search('lar')->take(10)->query($queryCallback)->paginate(5, 'page', 2), ]; } @@ -111,6 +118,6 @@ protected function itCanUsePaginatedSearchWithEmptyQueryCallback() // }; - return User::search('*')->query($queryCallback)->paginate(); + return SearchableUser::search('*')->query($queryCallback)->paginate(); } } diff --git a/tests/Integration/TypesenseSearchableTest.php b/tests/Integration/TypesenseSearchableTest.php index 237bd468..1d8cf557 100644 --- a/tests/Integration/TypesenseSearchableTest.php +++ b/tests/Integration/TypesenseSearchableTest.php @@ -2,8 +2,8 @@ namespace Laravel\Scout\Tests\Integration; -use Laravel\Scout\Tests\Fixtures\User; use Orchestra\Testbench\Attributes\RequiresEnv; +use Workbench\App\Models\SearchableUser; /** * @group typesense @@ -42,7 +42,7 @@ protected function defineDatabaseMigrations() */ protected function afterRefreshingDatabase() { - $this->importScoutIndexFrom(User::class); + $this->importScoutIndexFrom(SearchableUser::class); } public function test_it_can_use_basic_search() diff --git a/workbench/app/Models/Chirp.php b/workbench/app/Models/Chirp.php index abb6d11a..bc9bb00d 100644 --- a/workbench/app/Models/Chirp.php +++ b/workbench/app/Models/Chirp.php @@ -36,7 +36,11 @@ public function getScoutKeyName() public function toSearchableArray() { - return $_ENV['chirp.toSearchableArray'] ?? [ + if (isset($_ENV['chirp.toSearchableArray'])) { + return value($_ENV['chirp.toSearchableArray'], $this); + } + + return [ 'content' => $this->content, ]; } diff --git a/workbench/app/Models/SearchableUser.php b/workbench/app/Models/SearchableUser.php index a1826fe7..9492bada 100644 --- a/workbench/app/Models/SearchableUser.php +++ b/workbench/app/Models/SearchableUser.php @@ -11,7 +11,11 @@ class SearchableUser extends User /** {@inheritDoc} */ public function toSearchableArray() { - return $_ENV['user.toSearchableArray'] ?? [ + if (isset($_ENV['user.toSearchableArray'])) { + return value($_ENV['user.toSearchableArray'], $this); + } + + return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, From 568a8954fd51042dc97ddf8273e15e8e8d09b40a Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 12 Nov 2024 16:58:11 +0800 Subject: [PATCH 27/32] wip Signed-off-by: Mior Muhammad Zaki --- tests/Fixtures/EmptySearchableModel.php | 11 -------- .../Fixtures/SearchableModelWithCustomKey.php | 28 ------------------- 2 files changed, 39 deletions(-) delete mode 100644 tests/Fixtures/EmptySearchableModel.php delete mode 100644 tests/Fixtures/SearchableModelWithCustomKey.php diff --git a/tests/Fixtures/EmptySearchableModel.php b/tests/Fixtures/EmptySearchableModel.php deleted file mode 100644 index 6ab93d29..00000000 --- a/tests/Fixtures/EmptySearchableModel.php +++ /dev/null @@ -1,11 +0,0 @@ -other_id; - } - - public function getScoutKeyName() - { - return 'other_id'; - } -} From 942d235db051fd9cd523dc7db9a154ad27f0c1e0 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 12 Nov 2024 17:00:35 +0800 Subject: [PATCH 28/32] wip Signed-off-by: Mior Muhammad Zaki --- workbench/app/Models/User.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workbench/app/Models/User.php b/workbench/app/Models/User.php index ec189c0b..dc6027ef 100644 --- a/workbench/app/Models/User.php +++ b/workbench/app/Models/User.php @@ -41,6 +41,6 @@ class User extends Authenticatable */ protected $casts = [ 'email_verified_at' => 'datetime', - 'password' => 'hashed', + // 'password' => 'hashed', ]; } From 4c049f16a0f53f1bbfa3b41ce8159def648ac613 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 12 Nov 2024 17:07:47 +0800 Subject: [PATCH 29/32] wip Signed-off-by: Mior Muhammad Zaki --- workbench/database/factories/ChirpFactory.php | 1 + 1 file changed, 1 insertion(+) diff --git a/workbench/database/factories/ChirpFactory.php b/workbench/database/factories/ChirpFactory.php index 052dd572..6e54359d 100644 --- a/workbench/database/factories/ChirpFactory.php +++ b/workbench/database/factories/ChirpFactory.php @@ -27,6 +27,7 @@ class ChirpFactory extends Factory public function definition(): array { return [ + 'scout_id' => fake()->uuid(), 'content' => fake()->realText(), ]; } From 3dba26b28799140f9804e9b60a5520ec238ce625 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 12 Nov 2024 17:17:50 +0800 Subject: [PATCH 30/32] wip Signed-off-by: Mior Muhammad Zaki --- tests/Feature/ModelObserverTest.php | 84 +++++++++++++++++++++ tests/Unit/ModelObserverTest.php | 98 ------------------------- workbench/app/Models/SearchableUser.php | 24 +++++- 3 files changed, 104 insertions(+), 102 deletions(-) delete mode 100644 tests/Unit/ModelObserverTest.php diff --git a/tests/Feature/ModelObserverTest.php b/tests/Feature/ModelObserverTest.php index 41611d27..818d0443 100644 --- a/tests/Feature/ModelObserverTest.php +++ b/tests/Feature/ModelObserverTest.php @@ -138,4 +138,88 @@ public function test_deleted_handler_on_soft_delete_model_makes_model_unsearchab $model->delete(); } + + public function test_update_on_sensitive_attributes_triggers_search() + { + $_ENV['user.searchIndexShouldBeUpdated'] = function ($model) { + $sensitiveAttributeKeys = ['name', 'email']; + + return collect($model->getDirty())->keys() + ->intersect($sensitiveAttributeKeys) + ->isNotEmpty(); + }; + + $model = SearchableUserFactory::new()->createQuietly([ + 'name' => 'taylor Otwell', + 'remember_token' => 123, + 'password' => 'secret', + ]); + + $model->password = 'extremelySecurePassword'; + $model->name = 'Taylor'; + + tap($this->app->make('scout.spied'), function ($scout) { + $scout->shouldReceive('update')->once(); + }); + + $model->save(); + + unset($_ENV['user.searchIndexShouldBeUpdated']); + } + + public function test_update_on_non_sensitive_attributes_doesnt_trigger_search() + { + $_ENV['user.searchIndexShouldBeUpdated'] = function ($model) { + $sensitiveAttributeKeys = ['name', 'email']; + + return collect($model->getDirty())->keys() + ->intersect($sensitiveAttributeKeys) + ->isNotEmpty(); + }; + + $model = SearchableUserFactory::new()->createQuietly([ + 'name' => 'taylor Otwell', + 'remember_token' => 123, + 'password' => 'secret', + ]); + + $model->password = 'extremelySecurePassword'; + $model->remember_token = 456; + + tap($this->app->make('scout.spied'), function ($scout) { + $scout->shouldNotReceive('update'); + $scout->shouldNotReceive('delete'); + }); + + $model->save(); + + unset($_ENV['user.searchIndexShouldBeUpdated']); + } + + public function test_unsearchable_should_be_called_when_deleting() + { + + $_ENV['user.searchIndexShouldBeUpdated'] = function ($model) { + $sensitiveAttributeKeys = ['name', 'email']; + + return collect($model->getDirty())->keys() + ->intersect($sensitiveAttributeKeys) + ->isNotEmpty(); + }; + + $model = SearchableUserFactory::new()->createQuietly([ + 'name' => 'taylor Otwell', + 'remember_token' => 123, + 'password' => 'secret', + ]); + + tap($this->app->make('scout.spied'), function ($scout) { + $scout->shouldNotReceive('update'); + $scout->shouldReceive('delete')->once(); + }); + + $model->delete(); + + unset($_ENV['user.searchIndexShouldBeUpdated']); + } } diff --git a/tests/Unit/ModelObserverTest.php b/tests/Unit/ModelObserverTest.php deleted file mode 100644 index c008bb70..00000000 --- a/tests/Unit/ModelObserverTest.php +++ /dev/null @@ -1,98 +0,0 @@ -with('scout.after_commit', m::any())->andReturn(false); - Config::shouldReceive('get')->with('scout.soft_delete', m::any())->andReturn(false); - } - - protected function tearDown(): void - { - m::close(); - } - - public function test_update_on_sensitive_attributes_triggers_search() - { - $model = m::mock( - new SearchableModelWithSensitiveAttributes([ - 'first_name' => 'taylor', - 'last_name' => 'Otwell', - 'remember_token' => 123, - 'password' => 'secret', - ]) - )->makePartial(); - - // Let's pretend it's in sync with the database. - $model->syncOriginal(); - - // Update - $model->password = 'extremelySecurePassword'; - $model->first_name = 'Taylor'; - - // Assertions - $model->shouldReceive('searchable')->once(); - $model->shouldReceive('unsearchable')->never(); - - $observer = new ModelObserver; - $observer->saved($model); - } - - public function test_update_on_non_sensitive_attributes_doesnt_trigger_search() - { - $model = m::mock( - new SearchableModelWithSensitiveAttributes([ - 'first_name' => 'taylor', - 'last_name' => 'Otwell', - 'remember_token' => 123, - 'password' => 'secret', - ]) - )->makePartial(); - - // Let's pretend it's in sync with the database. - $model->syncOriginal(); - - // Update - $model->password = 'extremelySecurePassword'; - $model->remember_token = 456; - - // Assertions - $model->shouldReceive('searchable')->never(); - $model->shouldReceive('unsearchable')->never(); - - $observer = new ModelObserver; - $observer->saved($model); - } - - public function test_unsearchable_should_be_called_when_deleting() - { - $model = m::mock( - new SearchableModelWithSensitiveAttributes([ - 'first_name' => 'taylor', - 'last_name' => 'Otwell', - 'remember_token' => 123, - 'password' => 'secret', - ]) - )->makePartial(); - - // Let's pretend it's in sync with the database. - $model->syncOriginal(); - - // Assertions - $model->shouldReceive('searchable')->never(); - $model->shouldReceive('unsearchable')->once(); - - $observer = new ModelObserver; - $observer->deleted($model); - } -} diff --git a/workbench/app/Models/SearchableUser.php b/workbench/app/Models/SearchableUser.php index ef36c308..a56899f8 100644 --- a/workbench/app/Models/SearchableUser.php +++ b/workbench/app/Models/SearchableUser.php @@ -25,24 +25,40 @@ public function toSearchableArray() /** {@inheritDoc} */ public function wasSearchableBeforeUpdate() { - return $_ENV['user.wasSearchableBeforeUpdate'] ?? true; + if (isset($_ENV['user.wasSearchableBeforeUpdate'])) { + return value($_ENV['user.wasSearchableBeforeUpdate'], $this); + } + + return true; } /** {@inheritDoc} */ public function wasSearchableBeforeDelete() { - return $_ENV['user.wasSearchableBeforeDelete'] ?? true; + if (isset($_ENV['user.wasSearchableBeforeDelete'])) { + return value($_ENV['user.wasSearchableBeforeDelete'], $this); + } + + return true; } /** {@inheritDoc} */ public function shouldBeSearchable() { - return $_ENV['user.shouldBeSearchable'] ?? true; + if (isset($_ENV['user.shouldBeSearchable'])) { + return value($_ENV['user.shouldBeSearchable'], $this); + } + + return true; } /** {@inheritDoc} */ public function searchIndexShouldBeUpdated() { - return $_ENV['user.searchIndexShouldBeUpdated'] ?? true; + if (isset($_ENV['user.searchIndexShouldBeUpdated'])) { + return value($_ENV['user.searchIndexShouldBeUpdated'], $this); + } + + return true; } } From b36a6494191202c76e3b87c056cc1024838dfb4b Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Tue, 12 Nov 2024 09:18:03 +0000 Subject: [PATCH 31/32] Apply fixes from StyleCI --- tests/Feature/ModelObserverTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Feature/ModelObserverTest.php b/tests/Feature/ModelObserverTest.php index 818d0443..313f5a69 100644 --- a/tests/Feature/ModelObserverTest.php +++ b/tests/Feature/ModelObserverTest.php @@ -198,7 +198,6 @@ public function test_update_on_non_sensitive_attributes_doesnt_trigger_search() public function test_unsearchable_should_be_called_when_deleting() { - $_ENV['user.searchIndexShouldBeUpdated'] = function ($model) { $sensitiveAttributeKeys = ['name', 'email']; From 68151b4e3075ed4ec3abf86122177fa3ec7c8eb0 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 12 Nov 2024 17:18:56 +0800 Subject: [PATCH 32/32] wip Signed-off-by: Mior Muhammad Zaki --- ...SearchableModelWithSensitiveAttributes.php | 33 ------------------- 1 file changed, 33 deletions(-) delete mode 100644 tests/Fixtures/SearchableModelWithSensitiveAttributes.php diff --git a/tests/Fixtures/SearchableModelWithSensitiveAttributes.php b/tests/Fixtures/SearchableModelWithSensitiveAttributes.php deleted file mode 100644 index 87443499..00000000 --- a/tests/Fixtures/SearchableModelWithSensitiveAttributes.php +++ /dev/null @@ -1,33 +0,0 @@ -getDirty())->keys() - ->intersect($sensitiveAttributeKeys) - ->isNotEmpty(); - } -}