feat(lastfm): batch like/unlike are now asynchronous

This commit is contained in:
Phan An 2021-06-04 17:19:33 +02:00
parent 6c24b529cc
commit ef1add3877
No known key found for this signature in database
GPG key ID: A81E4477F0BB6FDC
28 changed files with 358 additions and 359 deletions

View file

@ -0,0 +1,21 @@
<?php
namespace App\Events;
use App\Models\User;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Collection;
class SongsBatchLiked extends Event
{
use SerializesModels;
public $songs;
public $user;
public function __construct(Collection $songs, User $user)
{
$this->songs = $songs;
$this->user = $user;
}
}

View file

@ -0,0 +1,21 @@
<?php
namespace App\Events;
use App\Models\User;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Collection;
class SongsBatchUnliked
{
use SerializesModels;
public $songs;
public $user;
public function __construct(Collection $songs, User $user)
{
$this->songs = $songs;
$this->user = $user;
}
}

View file

@ -26,7 +26,7 @@ class ScrobbleController extends Controller
public function store(ScrobbleStoreRequest $request, Song $song)
{
if (!$song->artist->is_unknown && $this->currentUser->connectedToLastfm()) {
ScrobbleJob::dispatch($this->currentUser, $song, (int) $request->timestamp);
ScrobbleJob::dispatch($this->currentUser, $song, $request->timestamp);
}
return response()->json(null, Response::HTTP_NO_CONTENT);

View file

@ -1,20 +0,0 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
abstract class Job
{
/*
|--------------------------------------------------------------------------
| Queueable Jobs
|--------------------------------------------------------------------------
|
| This job base class provides a central location to place any logic that
| is shared across all of your jobs. The trait included with the class
| provides access to the "onQueue" and "delay" queue helper methods.
|
*/
use Queueable;
}

View file

@ -1,39 +0,0 @@
<?php
namespace App\Jobs;
use App\Models\Interaction;
use App\Models\User;
use App\Services\LastfmService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class LoveTrackOnLastfmJob implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
use SerializesModels;
private $user;
private $interaction;
public function __construct(User $user, Interaction $interaction)
{
$this->user = $user;
$this->interaction = $interaction;
}
public function handle(LastfmService $lastfmService): void
{
$lastfmService->toggleLoveTrack(
$this->interaction->song->title,
$this->interaction->song->artist->name,
$this->user->lastfm_session_key,
$this->interaction->liked
);
}
}

View file

@ -1,41 +0,0 @@
<?php
namespace App\Jobs;
use App\Models\Album;
use App\Models\Song;
use App\Models\User;
use App\Services\LastfmService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class UpdateLastfmNowPlayingJob implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
use SerializesModels;
private $user;
private $song;
public function __construct(User $user, Song $song)
{
$this->user = $user;
$this->song = $song;
}
public function handle(LastfmService $lastfmService): void
{
$lastfmService->updateNowPlaying(
$this->song->artist->name,
$this->song->title,
$this->song->album->name === Album::UNKNOWN_NAME ? '' : $this->song->album->name,
$this->song->length,
$this->user->lastfm_session_key
);
}
}

View file

View file

@ -0,0 +1,29 @@
<?php
namespace App\Listeners;
use App\Events\SongsBatchLiked;
use App\Models\Song;
use App\Services\LastfmService;
use App\Values\LastfmLoveTrackParameters;
use Illuminate\Contracts\Queue\ShouldQueue;
class LoveMultipleTracksOnLastfm implements ShouldQueue
{
private $lastfm;
public function __construct(LastfmService $lastfm)
{
$this->lastfm = $lastfm;
}
public function handle(SongsBatchLiked $event): void
{
$this->lastfm->batchToggleLoveTracks(
$event->songs->map(static function (Song $song): LastfmLoveTrackParameters {
return LastfmLoveTrackParameters::make($song->title, $song->artist->name);
}),
$event->user
);
}
}

View file

@ -3,10 +3,11 @@
namespace App\Listeners;
use App\Events\SongLikeToggled;
use App\Jobs\LoveTrackOnLastfmJob;
use App\Services\LastfmService;
use App\Values\LastfmLoveTrackParameters;
use Illuminate\Contracts\Queue\ShouldQueue;
class LoveTrackOnLastfm
class LoveTrackOnLastfm implements ShouldQueue
{
private $lastfm;
@ -25,6 +26,13 @@ class LoveTrackOnLastfm
return;
}
LoveTrackOnLastfmJob::dispatch($event->user, $event->interaction);
$this->lastfm->toggleLoveTrack(
LastfmLoveTrackParameters::make(
$event->interaction->song->title,
$event->interaction->song->artist->name,
),
$event->user->lastfm_session_key,
$event->interaction->liked
);
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace App\Listeners;
use App\Events\SongsBatchUnliked;
use App\Services\LastfmService;
use Illuminate\Contracts\Queue\ShouldQueue;
class UnloveMultipleTracksOnLastfm implements ShouldQueue
{
private $lastfm;
public function __construct(LastfmService $lastfm)
{
$this->lastfm = $lastfm;
}
public function handle(SongsBatchUnliked $event): void
{
$this->lastfm->batchToggleLoveTracks($event->songs, $event->user, false);
}
}

View file

@ -3,10 +3,10 @@
namespace App\Listeners;
use App\Events\SongStartedPlaying;
use App\Jobs\UpdateLastfmNowPlayingJob;
use App\Services\LastfmService;
use Illuminate\Contracts\Queue\ShouldQueue;
class UpdateLastfmNowPlaying
class UpdateLastfmNowPlaying implements ShouldQueue
{
private $lastfm;
@ -21,6 +21,12 @@ class UpdateLastfmNowPlaying
return;
}
UpdateLastfmNowPlayingJob::dispatch($event->user, $event->song);
$this->lastfm->updateNowPlaying(
$event->song->artist->name,
$event->song->title,
$event->song->album->is_unknown ? '' : $event->song->album->name,
$event->song->length,
$event->user->lastfm_session_key
);
}
}

View file

@ -15,9 +15,10 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
* @property User $user
* @property int $id
*
* @method self firstOrCreate(array $where, array $params = [])
* @method static self firstOrCreate(array $where, array $params = [])
* @method static self find(int $id)
* @method static Builder whereSongIdAndUserId(string $songId, string $userId)
* @method static Builder whereIn(...$params)
*/
class Interaction extends Model
{

View file

@ -36,7 +36,7 @@ use Laravel\Scout\Searchable;
* @method static self first()
* @method static EloquentCollection orderBy(...$args)
* @method static int count()
* @method static self|null find($id)
* @method static self|Collection|null find($id)
* @method static Builder take(int $count)
*/
class Song extends Model

View file

@ -7,12 +7,16 @@ use App\Events\ArtistInformationFetched;
use App\Events\LibraryChanged;
use App\Events\MediaCacheObsolete;
use App\Events\SongLikeToggled;
use App\Events\SongsBatchLiked;
use App\Events\SongsBatchUnliked;
use App\Events\SongStartedPlaying;
use App\Listeners\ClearMediaCache;
use App\Listeners\DownloadAlbumCover;
use App\Listeners\DownloadArtistImage;
use App\Listeners\LoveMultipleTracksOnLastfm;
use App\Listeners\LoveTrackOnLastfm;
use App\Listeners\TidyLibrary;
use App\Listeners\UnloveMultipleTracksOnLastfm;
use App\Listeners\UpdateLastfmNowPlaying;
use App\Models\Album;
use App\Models\Song;
@ -32,6 +36,14 @@ class EventServiceProvider extends ServiceProvider
LoveTrackOnLastfm::class,
],
SongsBatchLiked::class => [
LoveMultipleTracksOnLastfm::class,
],
SongsBatchUnliked::class => [
UnloveMultipleTracksOnLastfm::class,
],
SongStartedPlaying::class => [
UpdateLastfmNowPlaying::class,
],

View file

@ -4,21 +4,45 @@ namespace App\Services;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Promise\Promise;
use Illuminate\Contracts\Cache\Repository as Cache;
use Illuminate\Log\Logger;
use Illuminate\Support\Str;
use InvalidArgumentException;
use SimpleXMLElement;
use Webmozart\Assert\Assert;
/**
* @method object get($uri, ...$args)
* @method object post($uri, ...$data)
* @method object put($uri, ...$data)
* @method object patch($uri, ...$data)
* @method object head($uri, ...$data)
* @method object delete($uri)
* @method object get(string $uri, array $data = [], bool $appendKey = true)
* @method object post($uri, array $data = [], bool $appendKey = true)
* @method object put($uri, array $data = [], bool $appendKey = true)
* @method object patch($uri, array $data = [], bool $appendKey = true)
* @method object head($uri, array $data = [], bool $appendKey = true)
* @method object delete($uri, array $data = [], bool $appendKey = true)
* @method Promise getAsync(string $uri, array $data = [], bool $appendKey = true)
* @method Promise postAsync($uri, array $data = [], bool $appendKey = true)
* @method Promise putAsync($uri, array $data = [], bool $appendKey = true)
* @method Promise patchAsync($uri, array $data = [], bool $appendKey = true)
* @method Promise headAsync($uri, array $data = [], bool $appendKey = true)
* @method Promise deleteAsync($uri, array $data = [], bool $appendKey = true)
*/
abstract class AbstractApiClient
{
private const MAGIC_METHODS = [
'get',
'post',
'put',
'patch',
'head',
'delete',
'getAsync',
'postAsync',
'putAsync',
'patchAsync',
'headAsync',
'deleteAsync',
];
protected $responseFormat = 'json';
protected $client;
protected $cache;
@ -50,7 +74,7 @@ abstract class AbstractApiClient
* an "API signature" of the request. Appending an API key will break the request.
* @param array<mixed> $params An array of parameters
*
* @return mixed|SimpleXMLElement|null
* @return mixed|SimpleXMLElement|void
*/
public function request(string $method, string $uri, bool $appendKey = true, array $params = [])
{
@ -73,6 +97,11 @@ abstract class AbstractApiClient
}
}
public function requestAsync(string $method, string $uri, bool $appendKey = true, array $params = []): Promise
{
return $this->getClient()->$method($this->buildUrl($uri, $appendKey), ['form_params' => $params]);
}
/**
* Make an HTTP call to the external resource.
*
@ -81,19 +110,25 @@ abstract class AbstractApiClient
*
* @throws InvalidArgumentException
*
* @return mixed|SimpleXMLElement|null
* @return mixed|SimpleXMLElement|void
*/
public function __call(string $method, array $args)
{
Assert::inArray($method, self::MAGIC_METHODS);
if (count($args) < 1) {
throw new InvalidArgumentException('Magic request methods require a URI and optional options array');
}
$uri = $args[0];
$opts = $args[1] ?? [];
$params = $args[1] ?? [];
$appendKey = $args[2] ?? true;
return $this->request($method, $uri, $appendKey, $opts);
if (Str::endsWith($method, 'Async')) {
return $this->requestAsync($method, $uri, $appendKey, $params);
} else {
return $this->request($method, $uri, $appendKey, $params);
}
}
/**
@ -113,9 +148,9 @@ abstract class AbstractApiClient
if ($appendKey) {
if (parse_url($uri, PHP_URL_QUERY)) {
$uri .= "&{$this->keyParam}=" . $this->getKey();
$uri .= "&$this->keyParam=" . $this->getKey();
} else {
$uri .= "?{$this->keyParam}=" . $this->getKey();
$uri .= "?$this->keyParam=" . $this->getKey();
}
}

View file

@ -3,18 +3,15 @@
namespace App\Services;
use App\Events\SongLikeToggled;
use App\Events\SongsBatchLiked;
use App\Events\SongsBatchUnliked;
use App\Models\Interaction;
use App\Models\Song;
use App\Models\User;
use Illuminate\Support\Collection;
class InteractionService
{
private $interaction;
public function __construct(Interaction $interaction)
{
$this->interaction = $interaction;
}
/**
* Increase the number of times a song is played by a user.
*
@ -22,7 +19,7 @@ class InteractionService
*/
public function increasePlayCount(string $songId, User $user): Interaction
{
return tap($this->interaction->firstOrCreate([
return tap(Interaction::firstOrCreate([
'song_id' => $songId,
'user_id' => $user->id,
]), static function (Interaction $interaction): void {
@ -42,7 +39,7 @@ class InteractionService
*/
public function toggleLike(string $songId, User $user): Interaction
{
return tap($this->interaction->firstOrCreate([
return tap(Interaction::firstOrCreate([
'song_id' => $songId,
'user_id' => $user->id,
]), static function (Interaction $interaction): void {
@ -60,10 +57,10 @@ class InteractionService
*
* @return array<Interaction> the array of Interaction objects
*/
public function batchLike(array $songIds, User $user): array
public function batchLike(array $songIds, User $user): Collection
{
return collect($songIds)->map(function ($songId) use ($user): Interaction {
return tap($this->interaction->firstOrCreate([
$interactions = collect($songIds)->map(static function ($songId) use ($user): Interaction {
return tap(Interaction::firstOrCreate([
'song_id' => $songId,
'user_id' => $user->id,
]), static function (Interaction $interaction): void {
@ -73,10 +70,14 @@ class InteractionService
$interaction->liked = true;
$interaction->save();
event(new SongLikeToggled($interaction));
});
})->all();
});
event(new SongsBatchLiked($interactions->map(static function (Interaction $interaction): Song {
return $interaction->song;
}), $user));
return $interactions;
}
/**
@ -86,17 +87,10 @@ class InteractionService
*/
public function batchUnlike(array $songIds, User $user): void
{
$this->interaction
->whereIn('song_id', $songIds)
Interaction::whereIn('song_id', $songIds)
->where('user_id', $user->id)
->get()
->each(
static function (Interaction $interaction): void {
$interaction->liked = false;
$interaction->save();
->update(['liked' => false]);
event(new SongLikeToggled($interaction));
}
);
event(new SongsBatchUnliked(Song::find($songIds), $user));
}
}

View file

@ -2,12 +2,16 @@
namespace App\Services;
use App\Values\LastfmLoveTrackParameters;
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\Utils;
use Illuminate\Support\Collection;
use Throwable;
class LastfmService extends AbstractApiClient implements ApiConsumerInterface
{
/**
* Override the key param, since, again, Lastfm wants to be different.
* Override the key param, since, again, Last.fm wants to be different.
*/
protected $keyParam = 'api_key';
@ -27,11 +31,7 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
return $this->getKey() && $this->getSecret();
}
/**
* Get information about an artist.
*
* @return array<mixed>|null
*/
/** @return array<mixed>|null */
public function getArtistInformation(string $name): ?array
{
if (!$this->enabled()) {
@ -76,11 +76,7 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
];
}
/**
* Get information about an album.
*
* @return array<mixed>|null
*/
/** @return array<mixed>|null */
public function getAlbumInformation(string $albumName, string $artistName): ?array
{
if (!$this->enabled()) {
@ -159,25 +155,25 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
}
}
/**
* Scrobble a song.
*
* @param string $artist The artist name
* @param string $track The track name
* @param string|int $timestamp The UNIX timestamp
* @param string $album The album name
* @param string $sk The session key
*/
public function scrobble(string $artist, string $track, $timestamp, string $album, string $sk): void
{
$params = compact('artist', 'track', 'timestamp', 'sk');
public function scrobble(
string $artistName,
string $trackName,
$timestamp,
string $albumName,
string $sessionKey
): void {
$params = [
'artist' => $artistName,
'track' => $trackName,
'timestamp' => $timestamp,
'sk' => $sessionKey,
'method' => 'track.scrobble',
];
if ($album) {
$params['album'] = $album;
if ($albumName) {
$params['album'] = $albumName;
}
$params['method'] = 'track.scrobble';
try {
$this->post('/', $this->buildAuthCallParams($params), false);
} catch (Throwable $e) {
@ -185,42 +181,63 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
}
}
/**
* Love or unlove a track on Last.fm.
*
* @param string $track The track name
* @param string $artist The artist's name
* @param string $sk The session key
* @param bool $love Whether to love or unlove. Such cheesy terms... urrgggh
*/
public function toggleLoveTrack(string $track, string $artist, string $sk, ?bool $love = true): void
public function toggleLoveTrack(LastfmLoveTrackParameters $params, string $sessionKey, bool $love = true): void
{
$params = compact('track', 'artist', 'sk');
$params['method'] = $love ? 'track.love' : 'track.unlove';
try {
$this->post('/', $this->buildAuthCallParams($params), false);
$this->post('/', $this->buildAuthCallParams([
'track' => $params->getTrackName(),
'artist' => $params->getArtistName(),
'sk' => $sessionKey,
'method' => $love ? 'track.love' : 'track.unlove',
]), false);
} catch (Throwable $e) {
$this->logger->error($e);
}
}
/**
* @param Collection|array<LastfmLoveTrackParameters> $parameterCollection
*/
public function batchToggleLoveTracks(Collection $parameterCollection, string $sessionKey, bool $love = true): void
{
$promises = $parameterCollection->map(
function (LastfmLoveTrackParameters $params) use ($sessionKey, $love): Promise {
return $this->postAsync('/', $this->buildAuthCallParams([
'track' => $params->getTrackName(),
'artist' => $params->getArtistName(),
'sk' => $sessionKey,
'method' => $love ? 'track.love' : 'track.unlove',
]), false);
}
);
try {
Utils::unwrap($promises);
} catch (Throwable $e) {
$this->logger->error($e);
}
}
/**
* Update a track's "now playing" on Last.fm.
*
* @param string $artist Name of the artist
* @param string $track Name of the track
* @param string $album Name of the album
* @param int|float $duration Duration of the track, in seconds
* @param string $sk The session key
*/
public function updateNowPlaying(string $artist, string $track, string $album, $duration, string $sk): void
{
$params = compact('artist', 'track', 'duration', 'sk');
$params['method'] = 'track.updateNowPlaying';
public function updateNowPlaying(
string $artistName,
string $trackName,
string $albumName,
$duration,
string $sessionKey
): void {
$params = [
'artist' => $artistName,
'track' => $trackName,
'duration' => $duration,
'sk' => $sessionKey,
'method' => 'track.updateNowPlaying',
];
if ($album) {
$params['album'] = $album;
if ($albumName) {
$params['album'] = $albumName;
}
try {

View file

@ -0,0 +1,30 @@
<?php
namespace App\Values;
class LastfmLoveTrackParameters
{
private $trackName;
private $artistName;
private function __construct(string $trackName, string $artistName)
{
$this->trackName = $trackName;
$this->artistName = $artistName;
}
public static function make(string $trackName, string $artistName): self
{
return new static($trackName, $artistName);
}
public function getTrackName(): string
{
return $this->trackName;
}
public function getArtistName(): string
{
return $this->artistName;
}
}

View file

@ -29,7 +29,8 @@
"lstrojny/functional-php": "^1.14",
"teamtnt/laravel-scout-tntsearch-driver": "^11.1",
"algolia/algoliasearch-client-php": "^2.7",
"laravel/ui": "^3.2"
"laravel/ui": "^3.2",
"webmozart/assert": "^1.10"
},
"require-dev": {
"facade/ignition": "^2.5",

2
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "5f54c3d8584004c6454de204fd4c4509",
"content-hash": "058ece2df4d81093f9268a416cb1d0ef",
"packages": [
{
"name": "algolia/algoliasearch-client-php",

View file

@ -5,6 +5,7 @@ namespace Database\Factories;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
class UserFactory extends Factory
{
@ -18,8 +19,10 @@ class UserFactory extends Factory
'email' => $this->faker->email,
'password' => Hash::make('secret'),
'is_admin' => false,
'preferences' => [],
'remember_token' => str_random(10),
'preferences' => [
'lastfm_session_key' => Str::random(),
],
'remember_token' => Str::random(10),
];
}

View file

@ -3,8 +3,10 @@
namespace Tests\Feature;
use App\Events\SongLikeToggled;
use App\Events\SongsBatchLiked;
use App\Models\Song;
use App\Models\User;
use Illuminate\Support\Collection;
class InteractionTest extends TestCase
{
@ -66,10 +68,12 @@ class InteractionTest extends TestCase
public function testToggleBatch(): void
{
$this->expectsEvents(SongLikeToggled::class);
$this->expectsEvents(SongsBatchLiked::class);
/** @var User $user */
$user = User::factory()->create();
/** @var Collection|array<Song> $songs */
$songs = Song::orderBy('id')->take(2)->get();
$songIds = array_pluck($songs->toArray(), 'id');

View file

@ -0,0 +1,39 @@
<?php
namespace Tests\Integration\Listeners;
use App\Events\SongLikeToggled;
use App\Listeners\LoveTrackOnLastfm;
use App\Models\Interaction;
use App\Models\User;
use App\Services\LastfmService;
use App\Values\LastfmLoveTrackParameters;
use Mockery;
use Tests\Feature\TestCase;
class LoveTrackOnLastFmTest extends TestCase
{
public function testHandle(): void
{
/** @var User $user */
$user = User::factory()->create(['preferences' => ['lastfm_session_key' => 'bar']]);
/** @var Interaction $interaction */
$interaction = Interaction::factory()->create();
$lastfm = Mockery::mock(LastfmService::class, ['enabled' => true]);
$lastfm->shouldReceive('toggleLoveTrack')
->with(
Mockery::on(static function (LastfmLoveTrackParameters $params) use ($interaction): bool {
self::assertSame($interaction->song->title, $params->getTrackName());
self::assertSame($interaction->song->artist->name, $params->getArtistName());
return true;
}),
'bar',
$interaction->liked
);
(new LoveTrackOnLastfm($lastfm))->handle(new SongLikeToggled($interaction, $user));
}
}

View file

@ -1,51 +0,0 @@
<?php
namespace Tests\Integration\Listeners;
use App\Events\SongLikeToggled;
use App\Jobs\LoveTrackOnLastfmJob;
use App\Listeners\LoveTrackOnLastfm;
use App\Models\Interaction;
use App\Models\Song;
use App\Models\User;
use App\Services\LastfmService;
use Exception;
use Illuminate\Support\Facades\Queue;
use Mockery;
use Mockery\MockInterface;
use Tests\Feature\TestCase;
class LoveTrackOnLastfmTest extends TestCase
{
/**
* @throws Exception
*/
public function testHandle(): void
{
static::createSampleMediaSet();
$user = User::factory()->create(['preferences' => ['lastfm_session_key' => 'bar']]);
$interaction = Interaction::create([
'user_id' => $user->id,
'song_id' => Song::first()->id,
]);
$queue = Queue::fake();
/** @var LastfmService|MockInterface $lastfm */
$lastfm = Mockery::mock(LastfmService::class, ['enabled' => true]);
(new LoveTrackOnLastfm($lastfm))->handle(new SongLikeToggled($interaction, $user));
$queue->assertPushed(
LoveTrackOnLastfmJob::class,
static function (LoveTrackOnLastfmJob $job) use ($interaction, $user): bool {
static::assertSame($interaction, static::getNonPublicProperty($job, 'interaction'));
static::assertSame($user, static::getNonPublicProperty($job, 'user'));
return true;
}
);
}
}

View file

@ -3,40 +3,27 @@
namespace Tests\Integration\Listeners;
use App\Events\SongStartedPlaying;
use App\Jobs\UpdateLastfmNowPlayingJob;
use App\Listeners\UpdateLastfmNowPlaying;
use App\Models\Song;
use App\Models\User;
use App\Services\LastfmService;
use Illuminate\Support\Facades\Queue;
use Mockery;
use Mockery\MockInterface;
use Tests\Feature\TestCase;
class UpdateLastfmNowPlayingTest extends TestCase
{
public function testUpdateNowPlayingStatus(): void
{
static::createSampleMediaSet();
/** @var User $user */
$user = User::factory()->create();
$user = User::factory()->create(['preferences' => ['lastfm_session_key' => 'bar']]);
$song = Song::first();
/** @var Song $song */
$song = Song::factory()->create();
$queue = Queue::fake();
/** @var LastfmService|MockInterface $lastfm */
$lastfm = Mockery::mock(LastfmService::class, ['enabled' => true]);
$lastfm->shouldReceive('updateNowPlaying')
->with($song->artist->name, $song->title, $song->album->name, $song->length, $user->lastfm_session_key);
(new UpdateLastfmNowPlaying($lastfm))->handle(new SongStartedPlaying($song, $user));
$queue->assertPushed(
UpdateLastfmNowPlayingJob::class,
static function (UpdateLastfmNowPlayingJob $job) use ($user, $song): bool {
self::assertSame($user, static::getNonPublicProperty($job, 'user'));
self::assertSame($song, static::getNonPublicProperty($job, 'song'));
return true;
}
);
}
}

View file

@ -3,6 +3,8 @@
namespace Tests\Integration\Services;
use App\Events\SongLikeToggled;
use App\Events\SongsBatchLiked;
use App\Events\SongsBatchUnliked;
use App\Models\Interaction;
use App\Models\Song;
use App\Models\User;
@ -18,7 +20,7 @@ class InteractionServiceTest extends TestCase
{
parent::setUp();
$this->interactionService = new InteractionService(new Interaction());
$this->interactionService = new InteractionService();
}
public function testIncreasePlayCount(): void
@ -47,7 +49,7 @@ class InteractionServiceTest extends TestCase
public function testLikeMultipleSongs(): void
{
$this->expectsEvents(SongLikeToggled::class);
$this->expectsEvents(SongsBatchLiked::class);
/** @var Collection $songs */
$songs = Song::factory(2)->create();
@ -64,8 +66,9 @@ class InteractionServiceTest extends TestCase
public function testUnlikeMultipleSongs(): void
{
$this->expectsEvents(SongLikeToggled::class);
$this->expectsEvents(SongsBatchUnliked::class);
/** @var User $user */
$user = User::factory()->create();
/** @var Collection $interactions */

View file

@ -1,41 +0,0 @@
<?php
namespace Tests\Unit\Jobs;
use App\Jobs\LoveTrackOnLastfmJob;
use App\Models\Interaction;
use App\Models\User;
use App\Services\LastfmService;
use Mockery;
use Tests\TestCase;
class LoveTrackOnLastfmJobTest extends TestCase
{
private $job;
private $user;
private $interaction;
public function setUp(): void
{
parent::setUp();
$this->user = User::factory()->create(['preferences' => ['lastfm_session_key' => 'foo']]);
$this->interaction = Interaction::factory()->make();
$this->job = new LoveTrackOnLastfmJob($this->user, $this->interaction);
}
public function testHandle(): void
{
$lastFm = Mockery::mock(LastfmService::class);
$lastFm->shouldReceive('toggleLoveTrack')
->once()
->with(
$this->interaction->song->title,
$this->interaction->song->artist->name,
'foo',
$this->interaction->liked
);
$this->job->handle($lastFm);
}
}

View file

@ -1,42 +0,0 @@
<?php
namespace Tests\Unit\Jobs;
use App\Jobs\UpdateLastfmNowPlayingJob;
use App\Models\Song;
use App\Models\User;
use App\Services\LastfmService;
use Mockery;
use Tests\TestCase;
class UpdateLastfmNowPlayingJobTest extends TestCase
{
private $job;
private $user;
private $song;
public function setUp(): void
{
parent::setUp();
$this->user = User::factory()->create(['preferences' => ['lastfm_session_key' => 'foo']]);
$this->song = Song::factory()->make();
$this->job = new UpdateLastfmNowPlayingJob($this->user, $this->song);
}
public function testHandle(): void
{
$lastFm = Mockery::mock(LastfmService::class);
$lastFm->shouldReceive('updateNowPlaying')
->once()
->with(
$this->song->artist->name,
$this->song->title,
$this->song->album->name,
$this->song->length,
'foo'
);
$this->job->handle($lastFm);
}
}