Skip to content
This repository has been archived by the owner on May 13, 2021. It is now read-only.

Commit

Permalink
Merge #102
Browse files Browse the repository at this point in the history
102: Add feature tests r=curquiza a=mmachatschek

WIP

This adds feature tests to the repo and solves #90 and #81

~~TODOS:~~

- [x] ~~Add documentation how to run feature tests locally~~
- [x] Add more tests (based on https://github.com/meilisearch/meilisearch-symfony/)
- [x] Update the cleanup method and add a `SCOUT_PREFIX` config with a test key and only remove indexes with that prefix to avoid deleting documents from a local meilisearch instance


~~I added a matrix for the meilisearch containers to also test the library against older versions. If this should be removed, just tell me @shokme or @curquiza.~~

Co-authored-by: Markus Machatschek <mmachatschek@yahoo.com>
  • Loading branch information
bors[bot] and mmachatschek authored Mar 2, 2021
2 parents 5380ca1 + 42dc48d commit 8652b62
Show file tree
Hide file tree
Showing 10 changed files with 364 additions and 34 deletions.
8 changes: 6 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ jobs:
strategy:
matrix:
php-versions: ['7.2', '7.3', '7.4', '8.0']
env:
MEILISEARCH_KEY: masterKey
name: tests (php ${{ matrix.php-versions }})
runs-on: ubuntu-latest
steps:
Expand All @@ -25,7 +27,9 @@ jobs:
- name: Validate composer.json and composer.lock
run: composer validate
- name: Install dependencies
run: composer install --prefer-dist --no-progress --no-suggest
run: composer update --prefer-dist --no-progress --no-interaction
- name: MeiliSearch setup with Docker
run: docker run -d -p 7700:7700 getmeili/meilisearch:latest ./meilisearch --master-key=${{ env.MEILISEARCH_KEY }} --no-analytics=true
- name: Run tests
run: composer test

Expand All @@ -41,7 +45,7 @@ jobs:
- name: Validate composer.json and composer.lock
run: composer validate
- name: Install dependencies
run: composer install --prefer-dist --no-progress --no-suggest
run: composer update --prefer-dist --no-progress --no-interaction
- name: Run linter
env:
PHP_CS_FIXER_IGNORE_ENV: 1
Expand Down
13 changes: 11 additions & 2 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,22 @@
processIsolation="false"
stopOnFailure="false">
<testsuites>
<testsuite name="Test Suite">
<directory suffix=".php">./tests/</directory>
<testsuite name="Unit">
<directory suffix=".php">./tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory suffix=".php">./tests/Feature</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./src</directory>
</include>
</coverage>
<php>
<!--
<env name="MEILISEARCH_HOST" value="http://localhost:7700" />
<env name="MEILISEARCH_KEY" value="" />
-->
</php>
</phpunit>
2 changes: 1 addition & 1 deletion src/Engines/MeilisearchEngine.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ protected function performSearch(Builder $builder, array $searchParams = [])
protected function filters(Builder $builder)
{
return collect($builder->wheres)->map(function ($value, $key) {
return $key.'='.'"'.$value.'"';
return sprintf('%s="%s"', $key, $value);
})->values()->implode(' AND ');
}

Expand Down
42 changes: 42 additions & 0 deletions tests/Feature/FeatureTestCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace Meilisearch\Scout\Tests\Feature;

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use MeiliSearch\Client;
use MeiliSearch\Endpoints\Indexes;
use Meilisearch\Scout\Tests\TestCase;

abstract class FeatureTestCase extends TestCase
{
public function setUp(): void
{
parent::setUp();

Schema::create('searchable_models', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
$table->timestamps();
});

$this->cleanUp();
}

public function tearDown(): void
{
$this->cleanUp();

parent::tearDown();
}

protected function cleanUp(): void
{
collect(resolve(Client::class)->getAllIndexes())->each(function (Indexes $index) {
// Starts with prefix
if (substr($index->getUid(), 0, strlen($this->getPrefix())) === $this->getPrefix()) {
$index->delete();
}
});
}
}
50 changes: 50 additions & 0 deletions tests/Feature/MeilisearchConsoleCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace Meilisearch\Scout\Tests\Feature;

use MeiliSearch\Client;
use MeiliSearch\Exceptions\HTTPRequestException;

class MeilisearchConsoleCommandTest extends FeatureTestCase
{
/** @test */
public function nameArgumentIsRequired()
{
$this->expectExceptionMessage('Not enough arguments (missing: "name").');
$this->artisan('scout:index')
->execute();
}

/** @test */
public function indexCanBeCreatedAndDeleted()
{
$indexUid = $this->getPrefixedIndexUid('testindex');

$this->artisan('scout:index', [
'name' => $indexUid,
])
->expectsOutput('Index "'.$indexUid.'" created.')
->assertExitCode(0)
->run();

$indexResponse = resolve(Client::class)->index($indexUid)->fetchRawInfo();

$this->assertIsArray($indexResponse);
$this->assertSame($indexUid, $indexResponse['uid']);

$this->artisan('scout:index', [
'name' => $indexUid,
'--delete' => true,
])
->expectsOutput('Index "'.$indexUid.'" deleted.')
->assertExitCode(0)
->run();

try {
resolve(Client::class)->index($indexUid)->fetchRawInfo();
$this->fail('Exception should be thrown that index doesn\'t exist!');
} catch (HTTPRequestException $exception) {
$this->assertTrue(true);
}
}
}
156 changes: 156 additions & 0 deletions tests/Feature/MeilisearchTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<?php

namespace Meilisearch\Scout\Tests\Feature;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Testing\WithFaker;
use Laravel\Scout\EngineManager;
use MeiliSearch\Client;
use Meilisearch\Scout\Engines\MeilisearchEngine;
use Meilisearch\Scout\Tests\Fixtures\SearchableModel as BaseSearchableModel;

class MeilisearchTest extends FeatureTestCase
{
use WithFaker;

/** @test */
public function clientAndEngineCanBeResolved()
{
$this->assertInstanceOf(Client::class, resolve(Client::class));
$this->assertInstanceOf(EngineManager::class, resolve(EngineManager::class));
$this->assertInstanceOf(MeilisearchEngine::class, resolve(EngineManager::class)->engine('meilisearch'));
}

/** @test */
public function clientCanTalkToMeilisearch()
{
/** @var Client $engine */
$engine = resolve(Client::class);

$this->assertNull($engine->health());
$versionResponse = $engine->version();
$this->assertIsArray($versionResponse);
$this->assertArrayHasKey('commitSha', $versionResponse);
$this->assertArrayHasKey('buildDate', $versionResponse);
$this->assertArrayHasKey('pkgVersion', $versionResponse);
}

/** @test */
public function searchReturnsModels()
{
$model = $this->createSearchableModel('foo');
$this->createSearchableModel('bar');

$this->assertDatabaseCount('searchable_models', 2);

$searchResponse = $this->waitForPendingUpdates($model, function () {
return SearchableModel::search('bar')->raw();
});

$this->assertIsArray($searchResponse);
$this->assertArrayHasKey('hits', $searchResponse);
$this->assertArrayHasKey('query', $searchResponse);
$this->assertTrue(1 === count($searchResponse['hits']));
}

/** @test */
public function searchReturnsCorrectModelAfterUpdate()
{
$fooModel = $this->createSearchableModel('foo');
$this->createSearchableModel('bar');

$this->assertDatabaseCount('searchable_models', 2);

$searchResponse = $this->waitForPendingUpdates($fooModel, function () {
return SearchableModel::search('foo')->raw();
});

$this->assertIsArray($searchResponse);
$this->assertArrayHasKey('hits', $searchResponse);
$this->assertArrayHasKey('query', $searchResponse);
$this->assertTrue(1 === count($searchResponse['hits']));
$this->assertTrue('foo' === $searchResponse['hits'][0]['title']);

$fooModel->update(['title' => 'lorem']);

$searchResponse = $this->waitForPendingUpdates($fooModel, function () {
return SearchableModel::search('lorem')->raw();
});

$this->assertIsArray($searchResponse);
$this->assertArrayHasKey('hits', $searchResponse);
$this->assertArrayHasKey('query', $searchResponse);
$this->assertTrue(1 === count($searchResponse['hits']));
$this->assertTrue('lorem' === $searchResponse['hits'][0]['title']);
}

/** @test */
public function customSearchReturnsResults()
{
$models = $this->createMultipleSearchableModels(10);

$this->assertDatabaseCount('searchable_models', 10);

$searchResponse = $this->waitForPendingUpdates($models->first(), function () {
return SearchableModel::search('', function ($meilisearch, $query, $options) {
$options['limit'] = 2;

return $meilisearch->search($query, $options);
})->raw();
});

$this->assertIsArray($searchResponse);
$this->assertArrayHasKey('hits', $searchResponse);
$this->assertArrayHasKey('query', $searchResponse);
$this->assertTrue(2 === $searchResponse['limit']);
$this->assertTrue(2 === count($searchResponse['hits']));
}

/**
* Fixes race condition and waits some time for the indexation to complete.
*
* @param Model $model
* @param callable $callback
*
* @return mixed
*/
protected function waitForPendingUpdates($model, $callback)
{
$index = resolve(Client::class)->index($model->searchableAs());
$pendingUpdates = $index->getAllUpdateStatus();

foreach ($pendingUpdates as $pendingUpdate) {
if ('processed' !== $pendingUpdate['status']) {
$index->waitForPendingUpdate($pendingUpdate['updateId']);
}
}

return $callback();
}

protected function createMultipleSearchableModels(int $times = 1)
{
$models = collect();

for ($i = 1; $i <= $times; ++$i) {
$models->add($this->createSearchableModel());
}

return $models;
}

protected function createSearchableModel(?string $title = null)
{
return SearchableModel::create([
'title' => $title ?? $this->faker->sentence,
]);
}
}

class SearchableModel extends BaseSearchableModel
{
public function searchableAs()
{
return config('scout.prefix').$this->getTable();
}
}
4 changes: 3 additions & 1 deletion tests/Fixtures/SearchableModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@

namespace Meilisearch\Scout\Tests\Fixtures;

use Illuminate\Database\Eloquent\Concerns\HasTimestamps;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;

class SearchableModel extends Model
{
use Searchable;
use HasTimestamps;

/**
* The attributes that are mass assignable.
*/
protected $fillable = ['id'];
protected $fillable = ['id', 'title'];

public function searchableAs()
{
Expand Down
26 changes: 26 additions & 0 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,30 @@ protected function getPackageProviders($app)
MeilisearchServiceProvider::class,
];
}

protected function getEnvironmentSetUp($app)
{
if (env('DB_CONNECTION')) {
config()->set('database.default', env('DB_CONNECTION'));
} else {
config()->set('database.default', 'testing');
config()->set('database.connections.testing', [
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => '',
]);
}
config()->set('scout.driver', 'meilisearch');
config()->set('scout.prefix', $this->getPrefix());
}

protected function getPrefixedIndexUid(string $indexUid)
{
return sprintf('%s_%s', $this->getPrefix(), $indexUid);
}

protected function getPrefix()
{
return 'meilisearch-laravel-scout_testing';
}
}
Loading

0 comments on commit 8652b62

Please sign in to comment.