feat(test): add SmartPlaylistService tests (#1616)

This commit is contained in:
Phan An 2022-12-05 22:49:46 +01:00 committed by GitHub
parent dcc0e82a0b
commit 0b486e699b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 542 additions and 263 deletions

View file

@ -22,7 +22,7 @@ const models: SmartPlaylistModel[] = [
}, {
name: 'interactions.play_count',
type: 'number',
label: 'Plays'
label: 'Play Count'
}, {
name: 'interactions.last_played_at',
type: 'date',

View file

@ -2,9 +2,549 @@
namespace Tests\Integration\Services;
use App\Models\Album;
use App\Models\Artist;
use App\Models\Interaction;
use App\Models\Playlist;
use App\Models\Song;
use App\Models\User;
use App\Services\SmartPlaylistService;
use Illuminate\Support\Collection;
use Tests\TestCase;
class SmartPlaylistServiceTest extends TestCase
{
// @todo write tests
private SmartPlaylistService $service;
public function setUp(): void
{
parent::setUp();
$this->service = app(SmartPlaylistService::class);
}
public function testTitleIs(): void
{
$matches = Song::factory()->count(3)->create(['title' => 'Foo Something']);
Song::factory()->count(3)->create(['title' => 'Bar Something']);
$this->assertMatchesAgainstRules($matches, [
[
'id' => 1,
'rules' => [
[
'id' => 1,
'model' => 'title',
'operator' => 'is',
'value' => ['Foo Something'],
],
],
],
]);
}
public function testTitleIsNot(): void
{
$matches = Song::factory()->count(3)->create(['title' => 'Foo Something']);
Song::factory()->count(3)->create(['title' => 'Bar Something']);
$this->assertMatchesAgainstRules($matches, [
[
'id' => 1,
'rules' => [
[
'id' => 1,
'model' => 'title',
'operator' => 'isNot',
'value' => ['Bar Something'],
],
],
],
]);
}
public function testTitleContains(): void
{
$matches = Song::factory()->count(3)->create(['title' => 'Foo Something']);
Song::factory()->count(3)->create(['title' => 'Foo Nothing']);
$this->assertMatchesAgainstRules($matches, [
[
'id' => 1,
'rules' => [
[
'id' => 1,
'model' => 'title',
'operator' => 'contains',
'value' => ['Some'],
],
],
],
]);
}
public function testTitleDoesNotContain(): void
{
$matches = Song::factory()->count(3)->create(['title' => 'Foo Something']);
Song::factory()->count(3)->create(['title' => 'Foo Nothing']);
$this->assertMatchesAgainstRules($matches, [
[
'id' => 1,
'rules' => [
[
'id' => 1,
'model' => 'title',
'operator' => 'notContain',
'value' => ['Nothing'],
],
],
],
]);
}
public function testTitleBeginsWith(): void
{
$matches = Song::factory()->count(3)->create(['title' => 'Foo Something']);
Song::factory()->count(3)->create(['title' => 'Bar Something']);
$this->assertMatchesAgainstRules($matches, [
[
'id' => 1,
'rules' => [
[
'id' => 1,
'model' => 'title',
'operator' => 'beginsWith',
'value' => ['Foo'],
],
],
],
]);
}
public function testTitleEndsWith(): void
{
$matches = Song::factory()->count(3)->create(['title' => 'Foo Something']);
Song::factory()->count(3)->create(['title' => 'Foo Nothing']);
$this->assertMatchesAgainstRules($matches, [
[
'id' => 1,
'rules' => [
[
'id' => 1,
'model' => 'title',
'operator' => 'endsWith',
'value' => ['Something'],
],
],
],
]);
}
public function testAlbumIs(): void
{
$albums = Album::factory()->count(2)->create(['name' => 'Foo Album']);
$matches = Song::factory()->count(3)->for($albums[0])->create()
->merge(Song::factory()->count(2)->for($albums[1])->create());
Song::factory()->count(3)->create();
$this->assertMatchesAgainstRules($matches, [
[
'id' => 1,
'rules' => [
[
'id' => 1,
'model' => 'album.name',
'operator' => 'is',
'value' => ['Foo Album'],
],
],
],
]);
}
public function testArtistIs(): void
{
$matches = Song::factory()
->count(3)
->for(Artist::factory()->create(['name' => 'Foo Artist']))
->create();
Song::factory()->count(3)->create();
$this->assertMatchesAgainstRules($matches, [
[
'id' => 1,
'rules' => [
[
'id' => 1,
'model' => 'artist.name',
'operator' => 'is',
'value' => ['Foo Artist'],
],
],
],
]);
}
public function testGenreIsOrContains(): void
{
$matches = Song::factory()->count(3)->create(['genre' => 'Foo Genre'])
->merge(Song::factory()->count(2)->create(['genre' => 'Bar Genre']));
Song::factory()->count(3)->create(['genre' => 'Baz Genre']);
$this->assertMatchesAgainstRules($matches, [
[
'id' => 1,
'rules' => [
[
'id' => 1,
'model' => 'genre',
'operator' => 'is',
'value' => ['Foo Genre'],
],
],
],
[
'id' => 2,
'rules' => [
[
'id' => 1,
'model' => 'genre',
'operator' => 'contains',
'value' => ['Bar'],
],
],
],
]);
}
public function testYearIsGreaterThan(): void
{
$matches = Song::factory()->count(3)->create(['year' => 2030])
->merge(Song::factory()->count(2)->create(['year' => 2022]));
Song::factory()->count(3)->create(['year' => 2020]);
$this->assertMatchesAgainstRules($matches, [
[
'id' => 1,
'rules' => [
[
'id' => 1,
'model' => 'year',
'operator' => 'isGreaterThan',
'value' => [2021],
],
],
],
]);
}
public function testYearIsLessThan(): void
{
$matches = Song::factory()->count(3)->create(['year' => 1980])
->merge(Song::factory()->count(2)->create(['year' => 1978]));
Song::factory()->count(3)->create(['year' => 1991]);
$this->assertMatchesAgainstRules($matches, [
[
'id' => 1,
'rules' => [
[
'id' => 1,
'model' => 'year',
'operator' => 'isLessThan',
'value' => [1981],
],
],
],
]);
}
public function testYearIsBetween(): void
{
$matches = Song::factory()->count(3)->create(['year' => 1980])
->merge(Song::factory()->count(2)->create(['year' => 1978]));
Song::factory()->count(3)->create(['year' => 1991]);
$this->assertMatchesAgainstRules($matches, [
[
'id' => 1,
'rules' => [
[
'id' => 1,
'model' => 'year',
'operator' => 'isBetween',
'value' => [1970, 1985],
],
],
],
]);
}
public function testPlayCountIsGreaterThan(): void
{
/** @var User $user */
$user = User::factory()->create();
$matches = Song::factory()->count(2)->create();
$notMatch = Song::factory()->create();
Interaction::factory()
->for($matches[0])
->for($user)
->create(['play_count' => 1000]);
Interaction::factory()
->for($matches[1])
->for($user)
->create(['play_count' => 2000]);
Interaction::factory()
->for($user)
->for($notMatch)
->create(['play_count' => 500]);
$this->assertMatchesAgainstRules($matches, [
[
'id' => 1,
'rules' => [
[
'id' => 1,
'model' => 'interactions.play_count',
'operator' => 'isGreaterThan',
'value' => [999],
],
],
],
], $user);
}
public function testLastPlayedAtIsInLast(): void
{
/** @var User $user */
$user = User::factory()->create();
$matches = Song::factory()->count(2)->create();
$notMatch = Song::factory()->create();
Interaction::factory()
->for($matches[0])
->for($user)
->create(['last_played_at' => now()->subDays(2)]);
Interaction::factory()
->for($matches[1])
->for($user)
->create(['last_played_at' => now()->subDay()]);
Interaction::factory()
->for($user)
->for($notMatch)
->count(2)
->create(['last_played_at' => now()->subDays(4)]);
$this->assertMatchesAgainstRules($matches, [
[
'id' => 1,
'rules' => [
[
'id' => 1,
'model' => 'interactions.last_played_at',
'operator' => 'inLast',
'value' => [3],
],
],
],
], $user);
}
public function testLastPlayedNotInLast(): void
{
/** @var User $user */
$user = User::factory()->create();
$matches = Song::factory()->count(2)->create();
$notMatch = Song::factory()->create();
Interaction::factory()
->for($matches[0])
->for($user)
->create(['last_played_at' => now()->subDays(4)]);
Interaction::factory()
->for($matches[1])
->for($user)
->create(['last_played_at' => now()->subDays(3)]);
Interaction::factory()
->for($user)
->for($notMatch)
->count(2)
->create(['last_played_at' => now()->subDays(2)]);
$this->assertMatchesAgainstRules($matches, [
[
'id' => 1,
'rules' => [
[
'id' => 1,
'model' => 'interactions.last_played_at',
'operator' => 'notInLast',
'value' => [2],
],
],
],
], $user);
}
public function testLastPlayedIs(): void
{
/** @var User $user */
$user = User::factory()->create();
$matches = Song::factory()->count(2)->create();
$notMatch = Song::factory()->create();
Interaction::factory()
->for($matches[0])
->for($user)
->create(['last_played_at' => now()]);
Interaction::factory()
->for($matches[1])
->for($user)
->create(['last_played_at' => now()]);
Interaction::factory()
->for($user)
->for($notMatch)
->count(2)
->create(['last_played_at' => now()->subDays(4)]);
$this->assertMatchesAgainstRules($matches, [
[
'id' => 1,
'rules' => [
[
'id' => 1,
'model' => 'interactions.last_played_at',
'operator' => 'is',
'value' => [now()->format('Y-m-d')],
],
],
],
], $user);
}
public function testLengthIsGreaterThan(): void
{
$matches = Song::factory()->count(3)->create(['length' => 300])
->merge(Song::factory()->count(2)->create(['length' => 200]));
Song::factory()->count(3)->create(['length' => 100]);
$this->assertMatchesAgainstRules($matches, [
[
'id' => 1,
'rules' => [
[
'id' => 1,
'model' => 'length',
'operator' => 'isGreaterThan',
'value' => [199],
],
],
],
]);
}
public function testLengthIsInBetween(): void
{
$matches = Song::factory()->count(3)->create(['length' => 300])
->merge(Song::factory()->count(2)->create(['length' => 200]));
Song::factory()->count(3)->create(['length' => 100]);
$this->assertMatchesAgainstRules($matches, [
[
'id' => 1,
'rules' => [
[
'id' => 1,
'model' => 'length',
'operator' => 'isBetween',
'value' => [199, 301],
],
],
],
]);
}
public function testDateAddedInLast(): void
{
$matches = Song::factory()->count(3)->create(['created_at' => now()->subDays(2)])
->merge(Song::factory()->count(2)->create(['created_at' => now()->subDay()]))
->merge(Song::factory()->count(1)->create(['created_at' => today()]));
Song::factory()->count(3)->create(['created_at' => now()->subDays(4)]);
$this->assertMatchesAgainstRules($matches, [
[
'id' => 1,
'rules' => [
[
'id' => 1,
'model' => 'created_at',
'operator' => 'inLast',
'value' => [3],
],
],
],
]);
}
public function testDateAddedNotInLast(): void
{
$matches = Song::factory()->count(3)->create(['created_at' => now()->subDays(4)])
->merge(Song::factory()->count(2)->create(['created_at' => now()->subDays(5)]))
->merge(Song::factory()->count(1)->create(['created_at' => now()->subDays(6)]));
Song::factory()->count(3)->create(['created_at' => now()->subDays(2)]);
$this->assertMatchesAgainstRules($matches, [
[
'id' => 1,
'rules' => [
[
'id' => 1,
'model' => 'created_at',
'operator' => 'notInLast',
'value' => [3],
],
],
],
]);
}
private function assertMatchesAgainstRules(Collection $matches, array $rules, ?User $playlistOwner = null): void
{
/** @var Playlist $playlist */
$playlist = Playlist::factory()
->for($playlistOwner ?? User::factory()->create())
->create(['rules' => $rules]);
self::assertEqualsCanonicalizing(
$matches->pluck('id')->all(),
$this->service->getSongs($playlist)->pluck('id')->all()
);
}
}

View file

@ -1,44 +0,0 @@
[
{
"id": 1541356285385,
"rules": [
{
"id": 1541356313181,
"model": "title",
"operator": "is",
"value": [
"Foo"
]
},
{
"id": 1542573964152,
"model": "album.name",
"operator": "isNot",
"value": [
"Bar"
]
}
]
},
{
"id": 1542573971423,
"rules": [
{
"id": 1542573972485,
"model": "title",
"operator": "is",
"value": [
"Baz"
]
},
{
"id": 1542573978609,
"model": "created_at",
"operator": "isGreaterThan",
"value": [
"2021.10.01"
]
}
]
}
]

View file

@ -1,15 +0,0 @@
[
{
"id": 1541356285385,
"rules": [
{
"id": 1541356313181,
"model": "title",
"operator": "beginsWith",
"value": [
"Foo"
]
}
]
}
]

View file

@ -1,15 +0,0 @@
[
{
"id": 1541356285385,
"rules": [
{
"id": 1541356313181,
"model": "title",
"operator": "contains",
"value": [
"Foo"
]
}
]
}
]

View file

@ -1,15 +0,0 @@
[
{
"id": 1541356285385,
"rules": [
{
"id": 1541356313181,
"model": "title",
"operator": "notContain",
"value": [
"Foo"
]
}
]
}
]

View file

@ -1,15 +0,0 @@
[
{
"id": 1541356285385,
"rules": [
{
"id": 1541356313181,
"model": "title",
"operator": "endsWith",
"value": [
"Foo"
]
}
]
}
]

View file

@ -1,15 +0,0 @@
[
{
"id": 1541356285385,
"rules": [
{
"id": 1541356313181,
"model": "created_at",
"operator": "inLast",
"value": [
"7"
]
}
]
}
]

View file

@ -1,23 +0,0 @@
[
{
"id": 1541356285385,
"rules": [
{
"id": 1541356313181,
"model": "title",
"operator": "is",
"value": [
"Foo"
]
},
{
"id": 1542572962736,
"model": "artist.name",
"operator": "isNot",
"value": [
"Bar"
]
}
]
}
]

View file

@ -1,28 +0,0 @@
[
{
"id": 1541356285385,
"rules": [
{
"id": 1541356313181,
"model": "title",
"operator": "is",
"value": [
"Foo"
]
}
]
},
{
"id": 1542573097361,
"rules": [
{
"id": 1542573098295,
"model": "artist.name",
"operator": "is",
"value": [
"Bar"
]
}
]
}
]

View file

@ -1,15 +0,0 @@
[
{
"id": 1541356285385,
"rules": [
{
"id": 1541356313181,
"model": "title",
"operator": "is",
"value": [
"Foo"
]
}
]
}
]

View file

@ -1,16 +0,0 @@
[
{
"id": 1541356285385,
"rules": [
{
"id": 1541356313181,
"model": "created_at",
"operator": "isBetween",
"value": [
"2021.10.01",
"2021.11.01"
]
}
]
}
]

View file

@ -1,15 +0,0 @@
[
{
"id": 1541356285385,
"rules": [
{
"id": 1541356313181,
"model": "length",
"operator": "isLessThan",
"value": [
"300"
]
}
]
}
]

View file

@ -1,15 +0,0 @@
[
{
"id": 1541356285385,
"rules": [
{
"id": 1541356313181,
"model": "title",
"operator": "isNot",
"value": [
"Foo"
]
}
]
}
]

View file

@ -1,15 +0,0 @@
[
{
"id": 1541356285385,
"rules": [
{
"id": 1541356313181,
"model": "created_at",
"operator": "notInLast",
"value": [
"7"
]
}
]
}
]

View file

@ -1,15 +0,0 @@
[
{
"id": 1541356285385,
"rules": [
{
"id": 1541356313181,
"model": "interactions.play_count",
"operator": "is",
"value": [
"42"
]
}
]
}
]