mirror of
https://github.com/koel/koel
synced 2024-11-10 06:34:14 +00:00
Big revamp for lastfm and youtube services
This commit is contained in:
parent
d4d2b0aff3
commit
67357316bc
24 changed files with 298 additions and 321 deletions
|
@ -3,10 +3,21 @@
|
|||
namespace App\Http\Controllers\API\MediaInformation;
|
||||
|
||||
use App\Models\Song;
|
||||
use App\Services\MediaInformationService;
|
||||
use App\Services\YouTubeService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class SongController extends Controller
|
||||
{
|
||||
private $youTubeService;
|
||||
|
||||
public function __construct(MediaInformationService $mediaInformationService, YouTubeService $youTubeService)
|
||||
{
|
||||
parent::__construct($mediaInformationService);
|
||||
|
||||
$this->youTubeService = $youTubeService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get extra information about a song.
|
||||
*
|
||||
|
@ -20,7 +31,7 @@ class SongController extends Controller
|
|||
'lyrics' => $song->lyrics,
|
||||
'album_info' => $this->mediaInformationService->getAlbumInformation($song->album),
|
||||
'artist_info' => $this->mediaInformationService->getArtistInformation($song->artist),
|
||||
'youtube' => $song->getRelatedYouTubeVideos(),
|
||||
'youtube' => $this->youTubeService->searchVideosRelatedToSong($song),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,18 @@ namespace App\Http\Controllers\API;
|
|||
|
||||
use App\Http\Requests\API\YouTubeSearchRequest;
|
||||
use App\Models\Song;
|
||||
use App\Services\YouTubeService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class YouTubeController extends Controller
|
||||
{
|
||||
private $youTubeService;
|
||||
|
||||
public function __construct(YouTubeService $youTubeService)
|
||||
{
|
||||
$this->youTubeService = $youTubeService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for YouTube videos related to a song (using its title and artist name).
|
||||
*
|
||||
|
@ -18,6 +26,6 @@ class YouTubeController extends Controller
|
|||
*/
|
||||
public function searchVideosRelatedToSong(YouTubeSearchRequest $request, Song $song)
|
||||
{
|
||||
return response()->json($song->getRelatedYouTubeVideos($request->pageToken));
|
||||
return response()->json($this->youTubeService->searchVideosRelatedToSong($song, $request->pageToken));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -266,6 +266,7 @@ class Song extends Model
|
|||
*/
|
||||
public static function getFavorites(User $user, $toArray = false)
|
||||
{
|
||||
/** @var Collection $songs */
|
||||
$songs = Interaction::whereUserIdAndLike($user->id, true)
|
||||
->with('song')
|
||||
->get()
|
||||
|
@ -302,18 +303,6 @@ class Song extends Model
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the YouTube videos related to this song.
|
||||
*
|
||||
* @param string $youTubePageToken The YouTube page token, for pagination purpose.
|
||||
*
|
||||
* @return false|object
|
||||
*/
|
||||
public function getRelatedYouTubeVideos($youTubePageToken = '')
|
||||
{
|
||||
return YouTube::searchVideosRelatedToSong($this, $youTubePageToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sometimes the tags extracted from getID3 are HTML entity encoded.
|
||||
* This makes sure they are always sane.
|
||||
|
|
|
@ -25,7 +25,7 @@ class LastfmServiceProvider extends ServiceProvider
|
|||
public function register()
|
||||
{
|
||||
app()->singleton('Lastfm', function () {
|
||||
return new LastfmService();
|
||||
return app()->make(LastfmService::class);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Services\YouTube;
|
||||
use App\Services\YouTubeService;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class YouTubeServiceProvider extends ServiceProvider
|
||||
|
@ -25,7 +25,7 @@ class YouTubeServiceProvider extends ServiceProvider
|
|||
public function register()
|
||||
{
|
||||
app()->singleton('YouTube', function () {
|
||||
return new YouTube();
|
||||
return app()->make(YouTubeService::class);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,26 +7,17 @@ use GuzzleHttp\Exception\ClientException;
|
|||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class RESTfulService.
|
||||
*
|
||||
* @method object get($uri)
|
||||
* @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)
|
||||
*/
|
||||
class RESTfulService
|
||||
abstract class ApiClient
|
||||
{
|
||||
protected $responseFormat = 'json';
|
||||
|
||||
/**
|
||||
* The API endpoint.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $endpoint;
|
||||
|
||||
/**
|
||||
* The GuzzleHttp client to talk to the API.
|
||||
*
|
||||
|
@ -34,13 +25,6 @@ class RESTfulService
|
|||
*/
|
||||
protected $client;
|
||||
|
||||
/**
|
||||
* The API key.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $key;
|
||||
|
||||
/**
|
||||
* The query parameter name for the key.
|
||||
* For example, Last.fm use api_key, like this:
|
||||
|
@ -50,19 +34,9 @@ class RESTfulService
|
|||
*/
|
||||
protected $keyParam = 'key';
|
||||
|
||||
/**
|
||||
* The API secret.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $secret;
|
||||
|
||||
public function __construct($key, $secret, $endpoint, Client $client)
|
||||
public function __construct(Client $client)
|
||||
{
|
||||
$this->setKey($key);
|
||||
$this->setSecret($secret);
|
||||
$this->setEndpoint($endpoint);
|
||||
$this->setClient($client);
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -80,7 +54,7 @@ class RESTfulService
|
|||
public function request($verb, $uri, $appendKey = true, array $params = [])
|
||||
{
|
||||
try {
|
||||
$body = (string) $this->getClient()
|
||||
$body = (string)$this->getClient()
|
||||
->$verb($this->buildUrl($uri, $appendKey), ['form_params' => $params])
|
||||
->getBody();
|
||||
|
||||
|
@ -104,7 +78,7 @@ class RESTfulService
|
|||
* @param string $method The HTTP method
|
||||
* @param array $args An array of parameters
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws InvalidArgumentException
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
|
@ -136,14 +110,14 @@ class RESTfulService
|
|||
$uri = "/$uri";
|
||||
}
|
||||
|
||||
$uri = $this->endpoint.$uri;
|
||||
$uri = $this->getEndpoint() . $uri;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,59 +132,9 @@ class RESTfulService
|
|||
return $this->client;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Client $client
|
||||
*/
|
||||
public function setClient($client)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
abstract public function getKey();
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
abstract public function getSecret();
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
*/
|
||||
public function setKey($key)
|
||||
{
|
||||
$this->key = $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSecret()
|
||||
{
|
||||
return $this->secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $secret
|
||||
*/
|
||||
public function setSecret($secret)
|
||||
{
|
||||
$this->secret = $secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getEndpoint()
|
||||
{
|
||||
return $this->endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $endpoint
|
||||
*/
|
||||
public function setEndpoint($endpoint)
|
||||
{
|
||||
$this->endpoint = $endpoint;
|
||||
}
|
||||
abstract public function getEndpoint();
|
||||
}
|
16
app/Services/ApiConsumerInterface.php
Normal file
16
app/Services/ApiConsumerInterface.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
|
||||
interface ApiConsumerInterface
|
||||
{
|
||||
/** @return string */
|
||||
public function getEndpoint();
|
||||
|
||||
/** @return string */
|
||||
public function getKey();
|
||||
|
||||
/** @return string|null */
|
||||
public function getSecret();
|
||||
}
|
|
@ -3,11 +3,9 @@
|
|||
namespace App\Services;
|
||||
|
||||
use Exception;
|
||||
use GuzzleHttp\Client;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Log;
|
||||
|
||||
class LastfmService extends RESTfulService
|
||||
class LastfmService extends ApiClient implements ApiConsumerInterface
|
||||
{
|
||||
/**
|
||||
* Specify the response format, since Last.fm only returns XML.
|
||||
|
@ -23,23 +21,6 @@ class LastfmService extends RESTfulService
|
|||
*/
|
||||
protected $keyParam = 'api_key';
|
||||
|
||||
/**
|
||||
* Construct an instance of Lastfm service.
|
||||
*
|
||||
* @param string $key Last.fm API key.
|
||||
* @param string $secret Last.fm API shared secret.
|
||||
* @param Client $client The Guzzle HTTP client.
|
||||
*/
|
||||
public function __construct($key = null, $secret = null, Client $client = null)
|
||||
{
|
||||
parent::__construct(
|
||||
$key ?: config('koel.lastfm.key'),
|
||||
$secret ?: config('koel.lastfm.secret'),
|
||||
'https://ws.audioscrobbler.com/2.0',
|
||||
$client ?: new Client()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if our application is using Last.fm.
|
||||
*
|
||||
|
@ -47,7 +28,7 @@ class LastfmService extends RESTfulService
|
|||
*/
|
||||
public function used()
|
||||
{
|
||||
return config('koel.lastfm.key') && config('koel.lastfm.secret');
|
||||
return $this->getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -67,7 +48,7 @@ class LastfmService extends RESTfulService
|
|||
*
|
||||
* @return array|false
|
||||
*/
|
||||
public function getArtistInfo($name)
|
||||
public function getArtistInformation($name)
|
||||
{
|
||||
if (!$this->enabled()) {
|
||||
return false;
|
||||
|
@ -92,7 +73,7 @@ class LastfmService extends RESTfulService
|
|||
return false;
|
||||
}
|
||||
|
||||
return $this->buildArtistInfo($artist);
|
||||
return $this->buildArtistInformation($artist);
|
||||
} catch (Exception $e) {
|
||||
Log::error($e);
|
||||
|
||||
|
@ -107,7 +88,7 @@ class LastfmService extends RESTfulService
|
|||
*
|
||||
* @return array
|
||||
*/
|
||||
private function buildArtistInfo(array $lastfmArtist)
|
||||
private function buildArtistInformation(array $lastfmArtist)
|
||||
{
|
||||
return [
|
||||
'url' => array_get($lastfmArtist, 'url'),
|
||||
|
@ -127,7 +108,7 @@ class LastfmService extends RESTfulService
|
|||
*
|
||||
* @return array|false
|
||||
*/
|
||||
public function getAlbumInfo($name, $artistName)
|
||||
public function getAlbumInformation($name, $artistName)
|
||||
{
|
||||
if (!$this->enabled()) {
|
||||
return false;
|
||||
|
@ -153,7 +134,7 @@ class LastfmService extends RESTfulService
|
|||
return false;
|
||||
}
|
||||
|
||||
return $this->buildAlbumInfo($album);
|
||||
return $this->buildAlbumInformation($album);
|
||||
} catch (Exception $e) {
|
||||
Log::error($e);
|
||||
|
||||
|
@ -168,7 +149,7 @@ class LastfmService extends RESTfulService
|
|||
*
|
||||
* @return array
|
||||
*/
|
||||
private function buildAlbumInfo(array $lastfmAlbum)
|
||||
private function buildAlbumInformation(array $lastfmAlbum)
|
||||
{
|
||||
return [
|
||||
'url' => array_get($lastfmAlbum, 'url'),
|
||||
|
@ -349,4 +330,19 @@ class LastfmService extends RESTfulService
|
|||
|
||||
return trim(str_replace('Read more on Last.fm', '', nl2br(strip_tags(html_entity_decode($str)))));
|
||||
}
|
||||
|
||||
public function getKey()
|
||||
{
|
||||
return config('koel.lastfm.key');
|
||||
}
|
||||
|
||||
public function getEndpoint()
|
||||
{
|
||||
return config('koel.lastfm.endpoint');
|
||||
}
|
||||
|
||||
public function getSecret()
|
||||
{
|
||||
return config('koel.lastfm.secret');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,12 +29,15 @@ class MediaInformationService
|
|||
return false;
|
||||
}
|
||||
|
||||
$info = $this->lastfmService->getAlbumInfo($album->name, $album->artist->name);
|
||||
event(new AlbumInformationFetched($album, $info));
|
||||
$info = $this->lastfmService->getAlbumInformation($album->name, $album->artist->name);
|
||||
|
||||
// The album may have been updated.
|
||||
$album->refresh();
|
||||
$info['cover'] = $album->cover;
|
||||
if ($info) {
|
||||
event(new AlbumInformationFetched($album, $info));
|
||||
|
||||
// The album may have been updated.
|
||||
$album->refresh();
|
||||
$info['cover'] = $album->cover;
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
@ -52,12 +55,15 @@ class MediaInformationService
|
|||
return false;
|
||||
}
|
||||
|
||||
$info = $this->lastfmService->getArtistInfo($artist->name);
|
||||
event(new ArtistInformationFetched($artist, $info));
|
||||
$info = $this->lastfmService->getArtistInformation($artist->name);
|
||||
|
||||
// The artist may have been updated.
|
||||
$artist->refresh();
|
||||
$info['image'] = $artist->image;
|
||||
if ($info) {
|
||||
event(new ArtistInformationFetched($artist, $info));
|
||||
|
||||
// The artist may have been updated.
|
||||
$artist->refresh();
|
||||
$info['image'] = $artist->image;
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
|
|
@ -4,26 +4,9 @@ namespace App\Services;
|
|||
|
||||
use App\Models\Song;
|
||||
use Cache;
|
||||
use GuzzleHttp\Client;
|
||||
|
||||
class YouTube extends RESTfulService
|
||||
class YouTubeService extends ApiClient implements ApiConsumerInterface
|
||||
{
|
||||
/**
|
||||
* Construct an instance of YouTube service.
|
||||
*
|
||||
* @param string $key The YouTube API key
|
||||
* @param Client|null $client The Guzzle HTTP client
|
||||
*/
|
||||
public function __construct($key = null, Client $client = null)
|
||||
{
|
||||
parent::__construct(
|
||||
$key ?: config('koel.youtube.key'),
|
||||
null,
|
||||
'https://www.googleapis.com/youtube/v3',
|
||||
$client ?: new Client()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if our application is using YouTube.
|
||||
*
|
||||
|
@ -31,7 +14,7 @@ class YouTube extends RESTfulService
|
|||
*/
|
||||
public function enabled()
|
||||
{
|
||||
return (bool) config('koel.youtube.key');
|
||||
return (bool) $this->getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -79,4 +62,22 @@ class YouTube extends RESTfulService
|
|||
return $this->get($uri);
|
||||
});
|
||||
}
|
||||
|
||||
/** @return string */
|
||||
public function getEndpoint()
|
||||
{
|
||||
return config('koel.youtube.endpoint');
|
||||
}
|
||||
|
||||
/** @return string */
|
||||
public function getKey()
|
||||
{
|
||||
return config('koel.youtube.key');
|
||||
}
|
||||
|
||||
/** @return string|null */
|
||||
public function getSecret()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -15,7 +15,9 @@
|
|||
"predis/predis": "~1.0",
|
||||
"doctrine/dbal": "^2.5",
|
||||
"jackiedo/dotenv-editor": "^1.0",
|
||||
"ext-exif": "*"
|
||||
"ext-exif": "*",
|
||||
"ext-json": "*",
|
||||
"ext-SimpleXML": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"fzaninotto/faker": "~1.4",
|
||||
|
|
|
@ -58,6 +58,7 @@ return [
|
|||
|
||||
'youtube' => [
|
||||
'key' => env('YOUTUBE_API_KEY'),
|
||||
'endpoint' => 'https://www.googleapis.com/youtube/v3',
|
||||
],
|
||||
|
||||
/*
|
||||
|
@ -72,6 +73,7 @@ return [
|
|||
'lastfm' => [
|
||||
'key' => env('LASTFM_API_KEY'),
|
||||
'secret' => env('LASTFM_API_SECRET'),
|
||||
'endpoint' => 'https://ws.audioscrobbler.com/2.0',
|
||||
],
|
||||
|
||||
/*
|
||||
|
|
|
@ -12,37 +12,31 @@ use App\Models\Interaction;
|
|||
use App\Models\Song;
|
||||
use App\Models\User;
|
||||
use App\Services\LastfmService;
|
||||
use Exception;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use Illuminate\Contracts\Auth\Guard;
|
||||
use Illuminate\Foundation\Testing\WithoutMiddleware;
|
||||
use Illuminate\Routing\Redirector;
|
||||
use Mockery as m;
|
||||
use Mockery\MockInterface;
|
||||
use Tymon\JWTAuth\JWTAuth;
|
||||
|
||||
class LastfmTest extends TestCase
|
||||
{
|
||||
use WithoutMiddleware;
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
m::close();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function testGetSessionKey()
|
||||
{
|
||||
/** @var Client $client */
|
||||
$client = m::mock(Client::class, [
|
||||
'get' => new Response(200, [], file_get_contents(__DIR__.'../../blobs/lastfm/session-key.xml')),
|
||||
]);
|
||||
|
||||
$api = new LastfmService(null, null, $client);
|
||||
|
||||
$this->assertEquals('foo', $api->getSessionKey('bar'));
|
||||
$this->assertEquals('foo', (new LastfmService($client))->getSessionKey('bar'));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function session_key_can_be_set()
|
||||
public function testSetSessionKey()
|
||||
{
|
||||
$user = factory(User::class)->create();
|
||||
$this->postAsUser('api/lastfm/session-key', ['key' => 'foo'], $user);
|
||||
|
@ -53,31 +47,30 @@ class LastfmTest extends TestCase
|
|||
/** @test */
|
||||
public function user_can_connect_to_lastfm()
|
||||
{
|
||||
/** @var Redirector|m\MockInterface $redirector */
|
||||
/** @var Redirector|MockInterface $redirector */
|
||||
$redirector = m::mock(Redirector::class);
|
||||
$redirector->shouldReceive('to')->once();
|
||||
|
||||
/** @var Guard|m\MockInterface $guard */
|
||||
/** @var Guard|MockInterface $guard */
|
||||
$guard = m::mock(Guard::class, ['user' => factory(User::class)->create()]);
|
||||
$auth = m::mock(JWTAuth::class, [
|
||||
'parseToken' => '',
|
||||
'getToken' => '',
|
||||
]);
|
||||
|
||||
(new LastfmController($guard))->connect($redirector, new LastfmService(), $auth);
|
||||
(new LastfmController($guard))->connect($redirector, app(LastfmService::class), $auth);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function lastfm_session_key_can_be_retrieved_and_stored()
|
||||
public function testRetrieveAndStoreSessionKey()
|
||||
{
|
||||
/** @var LastfmCallbackRequest|m\MockInterface $request */
|
||||
/** @var LastfmCallbackRequest $request */
|
||||
$request = m::mock(LastfmCallbackRequest::class);
|
||||
$request->token = 'foo';
|
||||
/** @var LastfmService|m\MockInterface $lastfm */
|
||||
/** @var LastfmService $lastfm */
|
||||
$lastfm = m::mock(LastfmService::class, ['getSessionKey' => 'bar']);
|
||||
|
||||
$user = factory(User::class)->create();
|
||||
/** @var Guard|m\MockInterface $guard */
|
||||
/** @var Guard $guard */
|
||||
$guard = m::mock(Guard::class, ['user' => $user]);
|
||||
|
||||
(new LastfmController($guard))->callback($request, $lastfm);
|
||||
|
@ -85,8 +78,7 @@ class LastfmTest extends TestCase
|
|||
$this->assertEquals('bar', $user->lastfm_session_key);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function user_can_disconnect_from_lastfm()
|
||||
public function testDisconnectUser()
|
||||
{
|
||||
$user = factory(User::class)->create(['preferences' => ['lastfm_session_key' => 'bar']]);
|
||||
$this->deleteAsUser('api/lastfm/disconnect', [], $user);
|
||||
|
@ -94,8 +86,10 @@ class LastfmTest extends TestCase
|
|||
$this->assertNull($user->lastfm_session_key);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function user_can_love_a_track_on_lastfm()
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testLoveTrack()
|
||||
{
|
||||
$this->withoutEvents();
|
||||
$this->createSampleMediaSet();
|
||||
|
@ -107,16 +101,19 @@ class LastfmTest extends TestCase
|
|||
'song_id' => Song::first()->id,
|
||||
]);
|
||||
|
||||
/** @var LastfmService|m\MockInterface $lastfm */
|
||||
/** @var LastfmService|MockInterface $lastfm */
|
||||
$lastfm = m::mock(LastfmService::class, ['enabled' => true]);
|
||||
$lastfm->shouldReceive('toggleLoveTrack')
|
||||
->once()
|
||||
->with($interaction->song->title, $interaction->song->album->artist->name, 'bar', false);
|
||||
|
||||
(new LoveTrackOnLastfm($lastfm))->handle(new SongLikeToggled($interaction, $user));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function user_now_playing_status_can_be_updated_to_lastfm()
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testUpdateNowPlayingStatus()
|
||||
{
|
||||
$this->withoutEvents();
|
||||
$this->createSampleMediaSet();
|
||||
|
@ -124,9 +121,10 @@ class LastfmTest extends TestCase
|
|||
$user = factory(User::class)->create(['preferences' => ['lastfm_session_key' => 'bar']]);
|
||||
$song = Song::first();
|
||||
|
||||
/** @var LastfmService|m\MockInterface $lastfm */
|
||||
/** @var LastfmService|MockInterface $lastfm */
|
||||
$lastfm = m::mock(LastfmService::class, ['enabled' => true]);
|
||||
$lastfm->shouldReceive('updateNowPlaying')
|
||||
->once()
|
||||
->with($song->album->artist->name, $song->title, $song->album->name, $song->length, 'bar');
|
||||
|
||||
(new UpdateLastfmNowPlaying($lastfm))->handle(new SongStartedPlaying($song, $user));
|
||||
|
|
|
@ -10,6 +10,7 @@ use Exception;
|
|||
use JWTAuth;
|
||||
use Laravel\BrowserKitTesting\DatabaseTransactions;
|
||||
use Laravel\BrowserKitTesting\TestCase as BaseTestCase;
|
||||
use Mockery;
|
||||
use Tests\CreatesApplication;
|
||||
use Tests\Traits\InteractsWithIoc;
|
||||
|
||||
|
@ -89,4 +90,10 @@ abstract class TestCase extends BaseTestCase
|
|||
'Authorization' => 'Bearer '.JWTAuth::fromUser($user),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
Mockery::close();
|
||||
parent::tearDown();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,21 +3,37 @@
|
|||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Song;
|
||||
use App\Services\YouTubeService;
|
||||
use Exception;
|
||||
use Illuminate\Foundation\Testing\WithoutMiddleware;
|
||||
use Mockery\MockInterface;
|
||||
use YouTube;
|
||||
|
||||
class YouTubeTest extends TestCase
|
||||
{
|
||||
use WithoutMiddleware;
|
||||
|
||||
/** @test */
|
||||
public function youtube_videos_related_to_a_song_can_be_searched()
|
||||
/** @var YouTubeService|MockInterface */
|
||||
private $youTubeService;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->youTubeService = $this->mockIocDependency(YouTubeService::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testSearchYouTubeVideos()
|
||||
{
|
||||
$this->createSampleMediaSet();
|
||||
$song = Song::first();
|
||||
|
||||
// We test on the facade here
|
||||
YouTube::shouldReceive('searchVideosRelatedToSong')->once();
|
||||
$this->youTubeService
|
||||
->shouldReceive('searchVideosRelatedToSong')
|
||||
->once();
|
||||
|
||||
$this->getAsUser("/api/youtube/search/song/{$song->id}");
|
||||
}
|
||||
|
|
|
@ -86,25 +86,6 @@ class SongTest extends TestCase
|
|||
// Then the song shouldn't be scrobbled
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_gets_related_youtube_videos()
|
||||
{
|
||||
// Given there's a song
|
||||
/** @var Song $song */
|
||||
$song = factory(Song::class)->create();
|
||||
|
||||
// When I get is related YouTube videos
|
||||
YouTube::shouldReceive('searchVideosRelatedToSong')
|
||||
->once()
|
||||
->with($song, 'foo')
|
||||
->andReturn(['bar' => 'baz']);
|
||||
|
||||
$videos = $song->getRelatedYouTubeVideos('foo');
|
||||
|
||||
// Then I see the related YouTube videos returned
|
||||
$this->assertEquals(['bar' => 'baz'], $videos);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_can_be_retrieved_using_its_path()
|
||||
{
|
||||
|
|
|
@ -5,39 +5,32 @@ namespace Tests\Integration\Services;
|
|||
use App\Models\Album;
|
||||
use App\Models\Artist;
|
||||
use App\Services\LastfmService;
|
||||
use Exception;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use Mockery as m;
|
||||
use Tests\TestCase;
|
||||
|
||||
class LastfmTest extends TestCase
|
||||
class LastfmServiceTest extends TestCase
|
||||
{
|
||||
protected function tearDown()
|
||||
{
|
||||
m::close();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
public function it_returns_artist_info_if_artist_is_found_on_lastfm()
|
||||
public function testGetArtistInformation()
|
||||
{
|
||||
// Given an artist that exists on Last.fm
|
||||
/** @var Artist $artist */
|
||||
$artist = factory(Artist::class)->create(['name' => 'foo']);
|
||||
$artist = factory(Artist::class)->make(['name' => 'foo']);
|
||||
|
||||
// When I request the service for the artist's info
|
||||
/** @var Client $client */
|
||||
$client = m::mock(Client::class, [
|
||||
'get' => new Response(200, [], file_get_contents(__DIR__.'../../../blobs/lastfm/artist.xml')),
|
||||
]);
|
||||
|
||||
$api = new LastfmService(null, null, $client);
|
||||
$info = $api->getArtistInfo($artist->name);
|
||||
$api = new LastfmService($client);
|
||||
$info = $api->getArtistInformation($artist->name
|
||||
|
||||
);
|
||||
|
||||
// Then I see the info when the request is the successful
|
||||
$this->assertEquals([
|
||||
'url' => 'http://www.last.fm/music/Kamelot',
|
||||
'image' => 'http://foo.bar/extralarge.jpg',
|
||||
|
@ -51,46 +44,40 @@ class LastfmTest extends TestCase
|
|||
$this->assertNotNull(cache('0aff3bc1259154f0e9db860026cda7a6'));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_returns_false_if_artist_info_is_not_found_on_lastfm()
|
||||
public function testGetArtistInformationForNonExistentArtist()
|
||||
{
|
||||
// Given an artist that doesn't exist on Last.fm
|
||||
/** @var Artist $artist */
|
||||
$artist = factory(Artist::class)->create();
|
||||
$artist = factory(Artist::class)->make();
|
||||
|
||||
// When I request the service for the artist info
|
||||
/** @var Client $client */
|
||||
$client = m::mock(Client::class, [
|
||||
'get' => new Response(400, [], file_get_contents(__DIR__.'../../../blobs/lastfm/artist-notfound.xml')),
|
||||
]);
|
||||
|
||||
$api = new LastfmService(null, null, $client);
|
||||
$result = $api->getArtistInfo($artist->name);
|
||||
$api = new LastfmService($client);
|
||||
$result = $api->getArtistInformation($artist->name);
|
||||
|
||||
// Then I receive boolean false
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
public function it_returns_album_info_if_album_is_found_on_lastfm()
|
||||
public function testGetAlbumInformation()
|
||||
{
|
||||
// Given an album that exists on Last.fm
|
||||
/** @var Album $album */
|
||||
$album = factory(Album::class)->create([
|
||||
'artist_id' => factory(Artist::class)->create(['name' => 'bar'])->id,
|
||||
'name' => 'foo',
|
||||
]);
|
||||
|
||||
// When I request the service for the album's info
|
||||
/** @var Client $client */
|
||||
$client = m::mock(Client::class, [
|
||||
'get' => new Response(200, [], file_get_contents(__DIR__.'../../../blobs/lastfm/album.xml')),
|
||||
]);
|
||||
|
||||
$api = new LastfmService(null, null, $client);
|
||||
$info = $api->getAlbumInfo($album->name, $album->artist->name);
|
||||
$api = new LastfmService($client);
|
||||
$info = $api->getAlbumInformation($album->name, $album->artist->name);
|
||||
|
||||
// Then I get the album's info
|
||||
$this->assertEquals([
|
||||
|
@ -114,25 +101,22 @@ class LastfmTest extends TestCase
|
|||
],
|
||||
], $info);
|
||||
|
||||
// And the response is cached
|
||||
$this->assertNotNull(cache('fca889d13b3222589d7d020669cc5a38'));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_returns_false_if_album_info_is_not_found_on_lastfm()
|
||||
public function testGetAlbumInformationForNonExistentAlbum()
|
||||
{
|
||||
// Given there's an album which doesn't exist on Last.fm
|
||||
/** @var Album $album */
|
||||
$album = factory(Album::class)->create();
|
||||
|
||||
// When I request the service for the album's info
|
||||
/** @var Client $client */
|
||||
$client = m::mock(Client::class, [
|
||||
'get' => new Response(400, [], file_get_contents(__DIR__.'../../../blobs/lastfm/album-notfound.xml')),
|
||||
]);
|
||||
|
||||
$api = new LastfmService(null, null, $client);
|
||||
$result = $api->getAlbumInfo($album->name, $album->artist->name);
|
||||
$api = new LastfmService($client);
|
||||
$result = $api->getAlbumInformation($album->name, $album->artist->name);
|
||||
|
||||
// Then I receive a boolean false
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
}
|
|
@ -44,7 +44,7 @@ class MediaInformationServiceTest extends TestCase
|
|||
$album = factory(Album::class)->create();
|
||||
|
||||
$this->lastFmService
|
||||
->shouldReceive('getAlbumInfo')
|
||||
->shouldReceive('getAlbumInformation')
|
||||
->once()
|
||||
->with($album->name, $album->artist->name)
|
||||
->andReturn(['foo' => 'bar']);
|
||||
|
@ -68,7 +68,7 @@ class MediaInformationServiceTest extends TestCase
|
|||
$artist = factory(Artist::class)->create();
|
||||
|
||||
$this->lastFmService
|
||||
->shouldReceive('getArtistInfo')
|
||||
->shouldReceive('getArtistInformation')
|
||||
->once()
|
||||
->with($artist->name)
|
||||
->andReturn(['foo' => 'bar']);
|
||||
|
|
|
@ -2,13 +2,14 @@
|
|||
|
||||
namespace Tests\Integration\Services;
|
||||
|
||||
use App\Services\YouTube;
|
||||
use App\Services\YouTubeService;
|
||||
use Exception;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use Mockery as m;
|
||||
use Tests\TestCase;
|
||||
|
||||
class YouTubeTest extends TestCase
|
||||
class YouTubeServiceTest extends TestCase
|
||||
{
|
||||
protected function tearDown()
|
||||
{
|
||||
|
@ -17,24 +18,21 @@ class YouTubeTest extends TestCase
|
|||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
public function videos_can_be_searched_from_youtube()
|
||||
public function testSearch()
|
||||
{
|
||||
$this->withoutEvents();
|
||||
|
||||
/** @var Client $client */
|
||||
$client = m::mock(Client::class, [
|
||||
'get' => new Response(200, [], file_get_contents(__DIR__.'../../../blobs/youtube/search.json')),
|
||||
]);
|
||||
|
||||
$api = new YouTube(null, $client);
|
||||
$api = new YouTubeService($client);
|
||||
$response = $api->search('Lorem Ipsum');
|
||||
|
||||
$this->assertEquals('Slipknot - Snuff [OFFICIAL VIDEO]', $response->items[0]->snippet->title);
|
||||
|
||||
// Is it cached?
|
||||
$this->assertNotNull(cache('1492972ec5c8e6b3a9323ba719655ddb'));
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ namespace Tests;
|
|||
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
|
||||
use Mockery;
|
||||
use Tests\Traits\InteractsWithIoc;
|
||||
|
||||
abstract class TestCase extends BaseTestCase
|
||||
|
@ -15,4 +16,10 @@ abstract class TestCase extends BaseTestCase
|
|||
parent::setUp();
|
||||
$this->prepareForTests();
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
Mockery::close();
|
||||
parent::tearDown();
|
||||
}
|
||||
}
|
||||
|
|
54
tests/Unit/Services/ApiClientTest.php
Normal file
54
tests/Unit/Services/ApiClientTest.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Services;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use Illuminate\Foundation\Testing\WithoutMiddleware;
|
||||
use Mockery as m;
|
||||
use Tests\TestCase;
|
||||
use Tests\Unit\Stubs\ConcreteApiClient;
|
||||
|
||||
class ApiClientTest extends TestCase
|
||||
{
|
||||
use WithoutMiddleware;
|
||||
|
||||
public function testBuildUri()
|
||||
{
|
||||
/** @var Client $client */
|
||||
$client = m::mock(Client::class);
|
||||
$api = new ConcreteApiClient($client);
|
||||
|
||||
$this->assertEquals('http://foo.com/get/param?key=bar', $api->buildUrl('get/param'));
|
||||
$this->assertEquals('http://foo.com/get/param?baz=moo&key=bar', $api->buildUrl('/get/param?baz=moo'));
|
||||
$this->assertEquals('http://baz.com/?key=bar', $api->buildUrl('http://baz.com/'));
|
||||
}
|
||||
|
||||
public function provideRequestData()
|
||||
{
|
||||
return [
|
||||
['get', '{"foo":"bar"}'],
|
||||
['post', '{"foo":"bar"}'],
|
||||
['put', '{"foo":"bar"}'],
|
||||
['delete', '{"foo":"bar"}'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideRequestData
|
||||
*
|
||||
* @param $method
|
||||
* @param $responseBody
|
||||
*/
|
||||
public function testRequest($method, $responseBody)
|
||||
{
|
||||
/** @var Client $client */
|
||||
$client = m::mock(Client::class, [
|
||||
$method => new Response(200, [], $responseBody),
|
||||
]);
|
||||
|
||||
$api = new ConcreteApiClient($client);
|
||||
|
||||
$this->assertSame((array) json_decode($responseBody), (array) $api->$method('/'));
|
||||
}
|
||||
}
|
|
@ -3,23 +3,28 @@
|
|||
namespace Tests\Unit\Services;
|
||||
|
||||
use App\Services\LastfmService;
|
||||
use Mockery;
|
||||
use Mockery\MockInterface;
|
||||
use Tests\TestCase;
|
||||
|
||||
class LastfmTest extends TestCase
|
||||
class LastfmServiceTest extends TestCase
|
||||
{
|
||||
/** @test */
|
||||
public function it_builds_lastfm_compatible_api_parameters()
|
||||
public function testBuildAuthCallParams()
|
||||
{
|
||||
// Given there are raw parameters
|
||||
$api = new LastfmService('key', 'secret');
|
||||
/** @var LastfmService|MockInterface $lastfm */
|
||||
$lastfm = Mockery::mock(LastfmService::class)->makePartial();
|
||||
$lastfm->shouldReceive('getKey')->andReturn('key');
|
||||
$lastfm->shouldReceive('getSecret')->andReturn('secret');
|
||||
|
||||
$params = [
|
||||
'qux' => '安',
|
||||
'bar' => 'baz',
|
||||
];
|
||||
|
||||
// When I build Last.fm-compatible API parameters using the raw parameters
|
||||
$builtParams = $api->buildAuthCallParams($params);
|
||||
$builtParamsAsString = $api->buildAuthCallParams($params, true);
|
||||
$builtParams = $lastfm->buildAuthCallParams($params);
|
||||
$builtParamsAsString = $lastfm->buildAuthCallParams($params, true);
|
||||
|
||||
// Then I receive the Last.fm-compatible API parameters
|
||||
$this->assertEquals([
|
|
@ -1,51 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Services;
|
||||
|
||||
use App\Services\RESTfulService;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use Illuminate\Foundation\Testing\WithoutMiddleware;
|
||||
use Mockery as m;
|
||||
use Tests\TestCase;
|
||||
|
||||
class RESTfulAPIServiceTest extends TestCase
|
||||
{
|
||||
use WithoutMiddleware;
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
m::close();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function a_uri_can_be_constructed()
|
||||
{
|
||||
/** @var Client $client */
|
||||
$client = m::mock(Client::class);
|
||||
$api = new RESTfulService('bar', null, 'http://foo.com', $client);
|
||||
$this->assertEquals('http://foo.com/get/param?key=bar', $api->buildUrl('get/param'));
|
||||
$this->assertEquals('http://foo.com/get/param?baz=moo&key=bar', $api->buildUrl('/get/param?baz=moo'));
|
||||
$this->assertEquals('http://baz.com/?key=bar', $api->buildUrl('http://baz.com/'));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function a_request_can_be_made()
|
||||
{
|
||||
/** @var Client $client */
|
||||
$client = m::mock(Client::class, [
|
||||
'get' => new Response(200, [], '{"foo":"bar"}'),
|
||||
'post' => new Response(200, [], '{"foo":"bar"}'),
|
||||
'delete' => new Response(200, [], '{"foo":"bar"}'),
|
||||
'put' => new Response(200, [], '{"foo":"bar"}'),
|
||||
]);
|
||||
|
||||
$api = new RESTfulService('foo', null, 'http://foo.com', $client);
|
||||
|
||||
$this->assertObjectHasAttribute('foo', $api->get('/'));
|
||||
$this->assertObjectHasAttribute('foo', $api->post('/'));
|
||||
$this->assertObjectHasAttribute('foo', $api->put('/'));
|
||||
$this->assertObjectHasAttribute('foo', $api->delete('/'));
|
||||
}
|
||||
}
|
23
tests/Unit/Stubs/ConcreteApiClient.php
Normal file
23
tests/Unit/Stubs/ConcreteApiClient.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Stubs;
|
||||
|
||||
use App\Services\ApiClient;
|
||||
|
||||
class ConcreteApiClient extends ApiClient
|
||||
{
|
||||
public function getKey()
|
||||
{
|
||||
return 'bar';
|
||||
}
|
||||
|
||||
public function getSecret()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getEndpoint()
|
||||
{
|
||||
return 'http://foo.com';
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue