Sti3bas / laravel-scout-array-driver

Array driver for Laravel Scout
MIT License
89 stars 8 forks source link

Call to undefined method ArrayStore::search() #14

Closed seivad closed 1 year ago

seivad commented 1 year ago

Hey mate,

Based on the Laravel Scout example, I am using Meilisearch and using a callback function to narrow results down within the controller. For testing I am trying to use your package but it is complaining about the ->search() function you need to return with based on the docs.

Doc example:

Order::search(
    'Star Trek',
    function (SearchIndex $algolia, string $query, array $options) {
        $options['body']['query']['bool']['filter']['geo_distance'] = [
            'distance' => '1000km',
            'location' => ['lat' => 36, 'lon' => 111],
        ];

        return $algolia->search($query, $options);
    }
)->get();

and my terminal error Error: Call to undefined method Sti3bas\ScoutArray\ArrayStore::search() in...

I can see you've implemented the search() method which calls performSearch() but so I'm not sure why it can't find that function during the test. Any ideas would be great so I can test these controllers that utilise Scout.

Sti3bas commented 1 year ago

@seivad can you please provide the full stack trace?

Maybe you could even provide a minimal reproducible repo?

cbaconnier commented 1 year ago

First of all, thanks for the library! Implementing laravel-scout in a existing project would have been a pain, but luckily with your library it seems we don't even have to update our tests :heart:

Here a reproducible example:

https://github.com/cbaconnier/laravel-scout-array-bug/commit/cdb3620abe1696497cc6cbdb702d64f2dffa4970

git clone git@github.com:cbaconnier/laravel-scout-array-bug.git
cd laravel-scout-array-bug
composer install
cp .env.example .env
php artisan key:generate
php artisan test

From my analysis I see few issues

First, there is this index string passed in parameters. It contains users_index and it's passed in our callback function. But neither Algolia or Meilisearch have it. https://github.com/Sti3bas/laravel-scout-array-driver/blob/6e103a237b064110279226889d048d596786bdeb/src/Engines/ArrayEngine.php#L112-L122

Algolia https://github.com/laravel/scout/blob/0ec3c900bd26f0ab69105bf5b72beef3852d78a8/src/Engines/AlgoliaEngine.php#L139-L152

Meilisearch https://github.com/laravel/scout/blob/0ec3c900bd26f0ab69105bf5b72beef3852d78a8/src/Engines/MeilisearchEngine.php#L142-L159

Then, when we use the callback, the first argument will be the underlying engine ( Algolia\AlgoliaSearch\SearchIndex or Meilisearch\Endpoints\Indexes ) They probably wont work the same and testing will probably be painful.

I'm not sure this callback is used elsewhere in this library. But if it was only attended to use with this search, my suggestion would be to remove the callback call. By removing the callback, we can still use the typing instead of having Indexes|ArrayEngine (or use phpdoc) and we wont need to add a new search method in the store and yada yada...

Also, as a more extended feature, it could be interesting to store the callback and test it;

Search::assertSearchedWithCallback(function(Indexes $meiliSearch, string $query, array $options){
    return true;
}); // 
Stacktrace ``` TypeError: Illuminate\Routing\RouteFileRegistrar::{closure}(): Argument #3 ($options) must be of type array, string given in /tmp/laravel-scout-array-bug/routes/web.php:21 Stack trace: #0 [internal function]: Illuminate\Routing\RouteFileRegistrar->{closure}() #1 /tmp/laravel-scout-array-bug/vendor/sti3bas/laravel-scout-array-driver/src/Engines/ArrayEngine.php(115): call_user_func() #2 /tmp/laravel-scout-array-bug/vendor/sti3bas/laravel-scout-array-driver/src/Engines/ArrayEngine.php(82): Sti3bas\ScoutArray\Engines\ArrayEngine->performSearch() #3 /tmp/laravel-scout-array-bug/vendor/laravel/scout/src/Engines/Engine.php(136): Sti3bas\ScoutArray\Engines\ArrayEngine->search() #4 /tmp/laravel-scout-array-bug/vendor/laravel/scout/src/Builder.php(294): Laravel\Scout\Engines\Engine->get() #5 /tmp/laravel-scout-array-bug/routes/web.php(27): Laravel\Scout\Builder->get() #6 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Routing/CallableDispatcher.php(40): Illuminate\Routing\RouteFileRegistrar->{closure}() #7 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Routing/Route.php(237): Illuminate\Routing\CallableDispatcher->dispatch() #8 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Routing/Route.php(208): Illuminate\Routing\Route->runCallable() #9 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Routing/Router.php(799): Illuminate\Routing\Route->run() #10 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(141): Illuminate\Routing\Router->Illuminate\Routing\{closure}() #11 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(50): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}() #12 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\Routing\Middleware\SubstituteBindings->handle() #13 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php(78): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}() #14 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\Foundation\Http\Middleware\VerifyCsrfToken->handle() #15 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php(49): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}() #16 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\View\Middleware\ShareErrorsFromSession->handle() #17 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(121): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}() #18 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(64): Illuminate\Session\Middleware\StartSession->handleStatefulRequest() #19 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\Session\Middleware\StartSession->handle() #20 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php(37): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}() #21 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse->handle() #22 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php(67): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}() #23 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\Cookie\Middleware\EncryptCookies->handle() #24 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(116): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}() #25 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Routing/Router.php(798): Illuminate\Pipeline\Pipeline->then() #26 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Routing/Router.php(777): Illuminate\Routing\Router->runRouteWithinStack() #27 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Routing/Router.php(741): Illuminate\Routing\Router->runRoute() #28 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Routing/Router.php(730): Illuminate\Routing\Router->dispatchToRoute() #29 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\Routing\Router->dispatch() #30 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(141): Illuminate\Foundation\Http\Kernel->Illuminate\Foundation\Http\{closure}() #31 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}() #32 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php(31): Illuminate\Foundation\Http\Middleware\TransformsRequest->handle() #33 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull->handle() #34 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}() #35 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(40): Illuminate\Foundation\Http\Middleware\TransformsRequest->handle() #36 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\Foundation\Http\Middleware\TrimStrings->handle() #37 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php(27): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}() #38 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\Foundation\Http\Middleware\ValidatePostSize->handle() #39 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(86): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}() #40 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance->handle() #41 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(49): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}() #42 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\Http\Middleware\HandleCors->handle() #43 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Http/Middleware/TrustProxies.php(39): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}() #44 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\Http\Middleware\TrustProxies->handle() #45 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(116): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}() #46 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\Pipeline\Pipeline->then() #47 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\Foundation\Http\Kernel->sendRequestThroughRouter() #48 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php(563): Illuminate\Foundation\Http\Kernel->handle() #49 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php(324): Illuminate\Foundation\Testing\TestCase->call() #50 /tmp/laravel-scout-array-bug/tests/Feature/ScoutSearchTest.php(37): Illuminate\Foundation\Testing\TestCase->get() #51 /tmp/laravel-scout-array-bug/vendor/phpunit/phpunit/src/Framework/TestCase.php(1065): Tests\Feature\ScoutSearchTest->test_it_returns_john_doe_when_using_callback() #52 /tmp/laravel-scout-array-bug/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php(174): PHPUnit\Framework\TestCase->runTest() #53 /tmp/laravel-scout-array-bug/vendor/phpunit/phpunit/src/Framework/TestCase.php(632): Illuminate\Foundation\Testing\TestCase->runTest() #54 /tmp/laravel-scout-array-bug/vendor/phpunit/phpunit/src/Framework/TestRunner.php(101): PHPUnit\Framework\TestCase->runBare() #55 /tmp/laravel-scout-array-bug/vendor/phpunit/phpunit/src/Framework/TestCase.php(468): PHPUnit\Framework\TestRunner->run() #56 /tmp/laravel-scout-array-bug/vendor/phpunit/phpunit/src/Framework/TestSuite.php(340): PHPUnit\Framework\TestCase->run() #57 /tmp/laravel-scout-array-bug/vendor/phpunit/phpunit/src/Framework/TestSuite.php(340): PHPUnit\Framework\TestSuite->run() #58 /tmp/laravel-scout-array-bug/vendor/phpunit/phpunit/src/Framework/TestSuite.php(340): PHPUnit\Framework\TestSuite->run() #59 /tmp/laravel-scout-array-bug/vendor/phpunit/phpunit/src/TextUI/TestRunner.php(63): PHPUnit\Framework\TestSuite->run() #60 /tmp/laravel-scout-array-bug/vendor/phpunit/phpunit/src/TextUI/Application.php(168): PHPUnit\TextUI\TestRunner->run() #61 /tmp/laravel-scout-array-bug/vendor/phpunit/phpunit/phpunit(99): PHPUnit\TextUI\Application->run() #62 {main} ```
Sti3bas commented 1 year ago

Hey @cbaconnier,

Thanks for putting an effort into debugging this issue and providing a detailed description. Really appreciated!

It seems that I overlooked or missed the coverage of the search functionality, most likely because I've used the JS version of Algolia's search client in my project while I was creating this library.

I would like to fix this issue by releasing a new version which would also provide an additional features like faking the search results and adding new search assertions.

The API would be something like this:

Search::fakeSearch(User::class, 'john', [
    User::factory()->create(['name' => 'John Doe']),
    User::factory()->create(['name' => 'John Cook']),
]);

$response = $this->get('/search/john');

$response->assertStatus(200);

$response->assertSee('John Doe');
$response->assertSee('John Cook');

Search::assertSearchedFor(User::class, 'john', function(MockInterface $meilisearch) {
   // an optional closure to make sure that the correct methods were called and correct options were passed to underlying engine using Mockery spies

    $meilisearch->shouldHaveReceived('search')->with('john', [
        'perPage' => null,
        'sort' => ['name:asc'],
        'filter' => 'email IS NOT NULL'
    ]);
});

I have an MVP ready, just need to cleanup/polish the code, but would like to hear your thoughts on this first!

cbaconnier commented 1 year ago

Hey @Sti3bas,

That's by far a better idea that I was thinking of. I will close my PR.

Thank you very much for the time you have spent on this, I'm looking forward to update my tests with your solution 😄

Sti3bas commented 1 year ago

@seivad @cbaconnier a temporary fix was just released in v3.5