77use DateTimeImmutable ;
88use Illuminate \Database \Eloquent \Collection as EloquentCollection ;
99use Laravel \Scout \Builder ;
10+ use Laravel \Scout \Jobs \RemoveFromSearch ;
11+ use Laravel \Scout \Tests \Unit \AlgoliaEngineTest ;
1012use Mockery as m ;
1113use MongoDB \BSON \Document ;
1214use MongoDB \BSON \Regex ;
2022use MongoDB \Model \BSONDocument ;
2123use PHPUnit \Framework \Attributes \DataProvider ;
2224
25+ use function serialize ;
26+ use function unserialize ;
27+
2328/** Unit tests that do not require an Atlas Search cluster */
2429class ScoutEngineTest extends TestCase
2530{
31+ private const EXPECTED_SEARCH_OPTIONS = ['allowDiskUse ' => true , 'typeMap ' => ['root ' => 'object ' , 'document ' => 'array ' , 'array ' => 'array ' ]];
32+
2633 /** @param callable(): Builder $builder */
2734 #[DataProvider('provideSearchPipelines ' )]
2835 public function testSearch (Closure $ builder , array $ expectedPipeline ): void
@@ -36,8 +43,7 @@ public function testSearch(Closure $builder, array $expectedPipeline): void
3643 $ cursor = m::mock (CursorInterface::class);
3744 $ cursor ->shouldReceive ('toArray ' )->once ()->with ()->andReturn ($ data );
3845
39- $ options = ['allowDiskUse ' => true , 'typeMap ' => ['root ' => 'object ' , 'document ' => 'array ' , 'array ' => 'array ' ]];
40- $ collection ->shouldReceive ('aggregate ' )->once ()->with ($ expectedPipeline , $ options )->andReturn ($ cursor );
46+ $ collection ->shouldReceive ('aggregate ' )->once ()->with ($ expectedPipeline , self ::EXPECTED_SEARCH_OPTIONS )->andReturn ($ cursor );
4147
4248 $ engine = new ScoutEngine ($ database , softDelete: false , prefix: '' );
4349 $ result = $ engine ->search ($ builder ());
@@ -157,6 +163,66 @@ function () {
157163
158164 public function testPaginate ()
159165 {
166+ $ perPage = 5 ;
167+ $ page = 3 ;
168+
169+ $ database = m::mock (Database::class);
170+ $ collection = m::mock (Collection::class);
171+ $ cursor = m::mock (CursorInterface::class);
172+ $ database ->shouldReceive ('selectCollection ' )
173+ ->with ('table_searchable ' )
174+ ->andReturn ($ collection );
175+ $ collection ->shouldReceive ('aggregate ' )
176+ ->once ()
177+ ->withArgs (function (...$ args ) {
178+ self ::assertSame ([
179+ [
180+ '$search ' => [
181+ 'index ' => 'scout ' ,
182+ 'text ' => [
183+ 'query ' => 'mustang ' ,
184+ 'path ' => [
185+ 'wildcard ' => '* ' ,
186+ ],
187+ 'fuzzy ' => [
188+ 'maxEdits ' => 2 ,
189+ ],
190+ ],
191+ 'count ' => [
192+ 'type ' => 'lowerBound ' ,
193+ ],
194+ 'sort ' => [
195+ 'name ' => -1 ,
196+ ],
197+ ],
198+ ],
199+ [
200+ '$addFields ' => [
201+ 'search_meta ' => '$$SEARCH_META ' ,
202+ ],
203+ ],
204+ [
205+ '$limit ' => 5 ,
206+ ],
207+ [
208+ '$skip ' => 10 ,
209+ ],
210+ ], $ args [0 ]);
211+
212+ $ this ->assertSame (self ::EXPECTED_SEARCH_OPTIONS , $ args [1 ]);
213+
214+ return true ;
215+ })
216+ ->andReturn ($ cursor );
217+ $ cursor ->shouldReceive ('toArray ' )
218+ ->once ()
219+ ->with ()
220+ ->andReturn ([['_id ' => 'key_1 ' ], ['_id ' => 'key_2 ' ]]);
221+
222+ $ engine = new ScoutEngine ($ database , softDelete: false , prefix: '' );
223+ $ builder = new Builder (new SearchableModel (), 'mustang ' );
224+ $ builder ->orderBy ('name ' , 'desc ' );
225+ $ engine ->paginate ($ builder , $ perPage , $ page );
160226 }
161227
162228 #[DataProvider('provideResultsForMapIds ' )]
@@ -254,18 +320,17 @@ public function testUpdateWithSoftDelete(): void
254320 [
255321 'updateOne ' => [
256322 ['_id ' => 'key_1 ' ],
257- ['$setOnInsert ' => ['_id ' => 'key_1 ' ], '$set ' => ['id ' => 1 , ' date ' => new UTCDateTime ( $ date ) ]],
323+ ['$setOnInsert ' => ['_id ' => 'key_1 ' ], '$set ' => ['id ' => 1 ]],
258324 ['upsert ' => true ],
259325 ],
260326 ],
261327 ]);
262328
329+ $ model = new SearchableModel (['id ' => 1 ]);
330+ $ model ->delete ();
331+
263332 $ engine = new ScoutEngine ($ database , softDelete: false , prefix: '' );
264- $ engine ->update (EloquentCollection::make ([
265- (new SearchableModel ([
266- 'id ' => 1 ,
267- ])),
268- ]));
333+ $ engine ->update (EloquentCollection::make ([$ model ]));
269334 }
270335
271336 public function testDelete (): void
@@ -286,6 +351,28 @@ public function testDelete(): void
286351 ]));
287352 }
288353
354+ /** @see AlgoliaEngineTest::test_delete_with_removeable_scout_collection_using_custom_search_key */
355+ public function testDeleteWithRemoveableScoutCollection (): void
356+ {
357+ $ job = new RemoveFromSearch (EloquentCollection::make ([
358+ new SearchableModel (['id ' => 5 , 'scout_key ' => 'key_5 ' ]),
359+ ]));
360+
361+ $ job = unserialize (serialize ($ job ));
362+
363+ $ database = m::mock (Database::class);
364+ $ collection = m::mock (Collection::class);
365+ $ database ->shouldReceive ('selectCollection ' )
366+ ->with ('table_indexable ' )
367+ ->andReturn ($ collection );
368+ $ collection ->shouldReceive ('deleteMany ' )
369+ ->once ()
370+ ->with (['_id ' => ['$in ' => ['key_5 ' ]]]);
371+
372+ $ engine = new ScoutEngine ($ database , softDelete: false , prefix: 'ignored_prefix_ ' );
373+ $ engine ->delete ($ job ->models );
374+ }
375+
289376 public function testDeleteAll (): void
290377 {
291378 $ collectionNames = ['scout-prefix-table1 ' , 'scout-prefix-table2 ' ];
0 commit comments