diff --git a/app/Models/Album.php b/app/Models/Album.php index f912992c..556e08ad 100644 --- a/app/Models/Album.php +++ b/app/Models/Album.php @@ -7,6 +7,8 @@ use App\Traits\SupportsDeleteWhereIDsNotIn; use Exception; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasMany; use Log; /** @@ -33,16 +35,31 @@ class Album extends Model protected $casts = ['artist_id' => 'integer']; protected $appends = ['is_compilation']; + /** + * An album belongs to an artist. + * + * @return BelongsTo + */ public function artist() { return $this->belongsTo(Artist::class); } + /** + * An album can contain many songs. + * + * @return HasMany + */ public function songs() { return $this->hasMany(Song::class); } + /** + * Indicate if the album is unknown. + * + * @return bool + */ public function getIsUnknownAttribute() { return $this->id === self::UNKNOWN_ID; @@ -166,11 +183,23 @@ class Album extends Model return app()->publicPath().'/public/img/covers/'.uniqid('', true).".$extension"; } + /** + * Set the album cover. + * + * @param string $value + */ public function setCoverAttribute($value) { $this->attributes['cover'] = $value ?: self::UNKNOWN_COVER; } + /** + * Get the album cover. + * + * @param string $value + * + * @return string + */ public function getCoverAttribute($value) { return app()->staticUrl('public/img/covers/'.($value ?: self::UNKNOWN_COVER)); diff --git a/app/Models/Artist.php b/app/Models/Artist.php index 75bbca42..5c17b086 100644 --- a/app/Models/Artist.php +++ b/app/Models/Artist.php @@ -6,7 +6,10 @@ use App\Facades\Lastfm; use App\Facades\Util; use App\Traits\SupportsDeleteWhereIDsNotIn; use Exception; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\HasManyThrough; use Log; /** @@ -15,7 +18,7 @@ use Log; * @property string image * @property bool is_unknown * @property bool is_various - * @property \Illuminate\Database\Eloquent\Collection songs + * @property Collection songs */ class Artist extends Model { @@ -30,21 +33,42 @@ class Artist extends Model protected $hidden = ['created_at', 'updated_at']; + /** + * An artist can have many albums. + * + * @return HasMany + */ public function albums() { return $this->hasMany(Album::class); } + /** + * An artist can have many songs. + * Unless he is Rick Astley. + * + * @return HasManyThrough + */ public function songs() { return $this->hasManyThrough(Song::class, Album::class); } + /** + * Indicate if the artist is unknown. + * + * @return bool + */ public function getIsUnknownAttribute() { return $this->id === self::UNKNOWN_ID; } + /** + * Indicate if the artist is the special "Various Artists" + * + * @return bool + */ public function getIsVariousAttribute() { return $this->id === self::VARIOUS_ID; diff --git a/app/Models/Interaction.php b/app/Models/Interaction.php index 671c8ada..83a7f48e 100644 --- a/app/Models/Interaction.php +++ b/app/Models/Interaction.php @@ -5,6 +5,7 @@ namespace App\Models; use App\Events\SongLikeToggled; use App\Traits\CanFilterByUser; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; /** * @property bool liked @@ -26,11 +27,21 @@ class Interaction extends Model protected $hidden = ['id', 'user_id', 'created_at', 'updated_at']; + /** + * An interaction belongs to a user. + * + * @return BelongsTo + */ public function user() { return $this->belongsTo(User::class); } + /** + * An interaction is associated with a song. + * + * @return BelongsTo + */ public function song() { return $this->belongsTo(Song::class); diff --git a/app/Models/Playlist.php b/app/Models/Playlist.php index 31e170c4..13409f1e 100644 --- a/app/Models/Playlist.php +++ b/app/Models/Playlist.php @@ -5,6 +5,8 @@ namespace App\Models; use App\Traits\CanFilterByUser; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; /** * @property int user_id @@ -22,11 +24,21 @@ class Playlist extends Model 'user_id' => 'int', ]; + /** + * A playlist can have many songs. + * + * @return BelongsToMany + */ public function songs() { return $this->belongsToMany(Song::class); } + /** + * A playlist belongs to a user. + * + * @return BelongsTo + */ public function user() { return $this->belongsTo(User::class); diff --git a/tests/Feature/DownloadTest.php b/tests/Feature/DownloadTest.php index bc98afe9..df734ee0 100644 --- a/tests/Feature/DownloadTest.php +++ b/tests/Feature/DownloadTest.php @@ -17,7 +17,8 @@ class DownloadTest extends TestCase $this->createSampleMediaSet(); } - public function testOneSong() + /** @test */ + public function a_single_song_can_be_downloaded() { $song = Song::first(); Download::shouldReceive('from') @@ -28,7 +29,8 @@ class DownloadTest extends TestCase ->seeStatusCode(200); } - public function testMultipleSongs() + /** @test */ + public function multiple_songs_can_be_downloaded() { $songs = Song::take(2)->get(); Download::shouldReceive('from') @@ -39,7 +41,8 @@ class DownloadTest extends TestCase ->seeStatusCode(200); } - public function testAlbum() + /** @test */ + public function a_whole_album_can_be_downloaded() { $album = Album::first(); @@ -51,7 +54,8 @@ class DownloadTest extends TestCase ->seeStatusCode(200); } - public function testArtist() + /** @test */ + public function a_whole_artists_biography_can_be_downloaded() { $artist = Artist::first(); @@ -63,7 +67,8 @@ class DownloadTest extends TestCase ->seeStatusCode(200); } - public function testPlaylist() + /** @test */ + public function a_whole_playlist_can_be_downloaded() { $user = factory(User::class)->create(); @@ -82,7 +87,8 @@ class DownloadTest extends TestCase ->seeStatusCode(200); } - public function testFavorites() + /** @test */ + public function all_favorite_songs_can_be_downloaded() { Download::shouldReceive('from') ->once() diff --git a/tests/Feature/InteractionTest.php b/tests/Feature/InteractionTest.php index ea3d2e20..056b7678 100644 --- a/tests/Feature/InteractionTest.php +++ b/tests/Feature/InteractionTest.php @@ -17,7 +17,8 @@ class InteractionTest extends TestCase $this->createSampleMediaSet(); } - public function testPlayCountRegister() + /** @test */ + public function play_count_is_increased() { $this->withoutEvents(); $user = factory(User::class)->create(); @@ -41,7 +42,8 @@ class InteractionTest extends TestCase ]); } - public function testLikeRegister() + /** @test */ + public function user_can_like_and_unlike_a_song() { $this->expectsEvents(SongLikeToggled::class); @@ -66,7 +68,8 @@ class InteractionTest extends TestCase ]); } - public function testBatchLikeAndUnlike() + /** @test */ + public function user_can_like_and_unlike_songs_in_batch() { $this->expectsEvents(SongLikeToggled::class); diff --git a/tests/Feature/LastfmTest.php b/tests/Feature/LastfmTest.php index 75d2af26..7d4f7501 100644 --- a/tests/Feature/LastfmTest.php +++ b/tests/Feature/LastfmTest.php @@ -24,6 +24,12 @@ class LastfmTest extends TestCase { use WithoutMiddleware; + protected function tearDown() + { + m::close(); + parent::tearDown(); + } + public function testGetSessionKey() { $client = m::mock(Client::class, [ @@ -35,7 +41,8 @@ class LastfmTest extends TestCase $this->assertEquals('foo', $api->getSessionKey('bar')); } - public function testSetSessionKey() + /** @test */ + public function session_key_can_be_set() { $user = factory(User::class)->create(); $this->postAsUser('api/lastfm/session-key', ['key' => 'foo'], $user); @@ -43,7 +50,8 @@ class LastfmTest extends TestCase $this->assertEquals('foo', $user->lastfm_session_key); } - public function testControllerConnect() + /** @test */ + public function user_can_connect_to_lastfm() { $redirector = m::mock(Redirector::class); $redirector->shouldReceive('to')->once(); @@ -57,7 +65,8 @@ class LastfmTest extends TestCase (new LastfmController($guard))->connect($redirector, new Lastfm(), $auth); } - public function testControllerCallback() + /** @test */ + public function lastfm_session_key_can_be_retrieved_and_stored() { $request = m::mock(Request::class); $request->token = 'foo'; @@ -71,7 +80,8 @@ class LastfmTest extends TestCase $this->assertEquals('bar', $user->lastfm_session_key); } - public function testControllerDisconnect() + /** @test */ + public function user_can_disconnect_from_lastfm() { $user = factory(User::class)->create(['preferences' => ['lastfm_session_key' => 'bar']]); $this->deleteAsUser('api/lastfm/disconnect', [], $user); @@ -79,7 +89,8 @@ class LastfmTest extends TestCase $this->assertNull($user->lastfm_session_key); } - public function testLoveTrack() + /** @test */ + public function user_can_love_a_track_on_lastfm() { $this->withoutEvents(); $this->createSampleMediaSet(); @@ -98,7 +109,8 @@ class LastfmTest extends TestCase (new LoveTrackOnLastfm($lastfm))->handle(new SongLikeToggled($interaction, $user)); } - public function testUpdateNowPlaying() + /** @test */ + public function user_now_playing_status_can_be_updated_to_lastfm() { $this->withoutEvents(); $this->createSampleMediaSet(); diff --git a/tests/Feature/MediaTest.php b/tests/Feature/MediaTest.php index ccac8858..c89d68de 100644 --- a/tests/Feature/MediaTest.php +++ b/tests/Feature/MediaTest.php @@ -16,7 +16,14 @@ class MediaTest extends TestCase { use WithoutMiddleware; - public function testSync() + protected function tearDown() + { + m::close(); + parent::tearDown(); + } + + /** @test */ + public function songs_can_be_synced() { $this->expectsEvents(LibraryChanged::class); @@ -78,7 +85,8 @@ class MediaTest extends TestCase $this->assertEquals($currentCover, Album::find($album->id)->cover); } - public function testForceSync() + /** @test */ + public function songs_can_be_force_synced() { $this->expectsEvents(LibraryChanged::class); @@ -112,7 +120,8 @@ class MediaTest extends TestCase $this->assertEquals($originalLyrics, $song->lyrics); } - public function testSyncSelectiveTags() + /** @test */ + public function songs_can_be_synced_with_selectively_tags() { $this->expectsEvents(LibraryChanged::class); @@ -137,7 +146,8 @@ class MediaTest extends TestCase $this->assertEquals('Booom Wroooom', $song->lyrics); } - public function testAlwaysSyncAllTagsIfFileIsNew() + /** @test */ + public function all_tags_are_catered_for_if_syncing_new_file() { $media = new Media(); $media->sync($this->mediaPath); @@ -155,7 +165,8 @@ class MediaTest extends TestCase $this->assertEquals($song, $addedSong); } - public function testWatchSingleFileAdded() + /** @test */ + public function added_song_is_synced_when_watching() { $this->expectsEvents(LibraryChanged::class); @@ -166,7 +177,8 @@ class MediaTest extends TestCase $this->seeInDatabase('songs', ['path' => $path]); } - public function testWatchSingleFileDeleted() + /** @test */ + public function deleted_song_is_synced_when_watching() { $this->expectsEvents(LibraryChanged::class); @@ -178,7 +190,8 @@ class MediaTest extends TestCase $this->notSeeInDatabase('songs', ['id' => $song->id]); } - public function testWatchDirectoryDeleted() + /** @test */ + public function deleted_directory_is_synced_when_watching() { $this->expectsEvents(LibraryChanged::class); @@ -192,7 +205,8 @@ class MediaTest extends TestCase $this->notSeeInDatabase('songs', ['path' => $this->mediaPath.'/subdir/back-in-black.mp3']); } - public function testHtmlEntitiesInTags() + /** @test */ + public function html_entities_in_tags_are_recognized_and_saved_properly() { $getID3 = m::mock(getID3::class, [ 'analyze' => [ @@ -215,7 +229,8 @@ class MediaTest extends TestCase $this->assertEquals('水谷広実', $info['title']); } - public function testDotDirectories() + /** @test */ + public function hidden_files_can_optionally_be_ignored_when_syncing() { config(['koel.ignore_dot_files' => false]); $media = new Media(); diff --git a/tests/Feature/ObjectStorage/S3Test.php b/tests/Feature/ObjectStorage/S3Test.php index df8ff356..b9a2549e 100644 --- a/tests/Feature/ObjectStorage/S3Test.php +++ b/tests/Feature/ObjectStorage/S3Test.php @@ -16,7 +16,8 @@ class S3Test extends TestCase $this->disableMiddlewareForAllTests(); } - public function testPut() + /** @test */ + public function a_song_can_be_added() { $this->post('api/os/s3/song', [ 'bucket' => 'koel', @@ -32,7 +33,8 @@ class S3Test extends TestCase ])->seeInDatabase('songs', ['path' => 's3://koel/sample.mp3']); } - public function testRemove() + /** @test */ + public function a_song_can_be_removed() { $this->expectsEvents(LibraryChanged::class); $this->post('api/os/s3/song', [ diff --git a/tests/Feature/PlaylistTest.php b/tests/Feature/PlaylistTest.php index d75c4c2b..bf3b25c6 100644 --- a/tests/Feature/PlaylistTest.php +++ b/tests/Feature/PlaylistTest.php @@ -14,7 +14,8 @@ class PlaylistTest extends TestCase $this->createSampleMediaSet(); } - public function testCreatePlaylist() + /** @test */ + public function user_can_create_a_playlist() { $user = factory(User::class)->create(); @@ -47,7 +48,8 @@ class PlaylistTest extends TestCase ]); } - public function testUpdatePlaylistName() + /** @test */ + public function user_can_update_a_playlists_name() { $user = factory(User::class)->create(); @@ -67,7 +69,8 @@ class PlaylistTest extends TestCase ->seeStatusCode(403); } - public function testSyncPlaylist() + /** @test */ + public function playlists_can_be_synced() { $user = factory(User::class)->create(); @@ -103,7 +106,8 @@ class PlaylistTest extends TestCase ]); } - public function testDeletePlaylist() + /** @test */ + public function user_can_delete_a_playlist() { $user = factory(User::class)->create(); diff --git a/tests/Feature/ProfileTest.php b/tests/Feature/ProfileTest.php index 0af1d831..3a28a316 100644 --- a/tests/Feature/ProfileTest.php +++ b/tests/Feature/ProfileTest.php @@ -9,7 +9,8 @@ class ProfileTest extends TestCase { use WithoutMiddleware; - public function testUpdate() + /** @test */ + public function user_can_update_his_profile() { $user = factory(User::class)->create(); $this->putAsUser('api/me', ['name' => 'Foo', 'email' => 'bar@baz.com'], $user); diff --git a/tests/Feature/ScrobbleTest.php b/tests/Feature/ScrobbleTest.php index 6eb9033b..2d686e4d 100644 --- a/tests/Feature/ScrobbleTest.php +++ b/tests/Feature/ScrobbleTest.php @@ -11,7 +11,14 @@ class ScrobbleTest extends TestCase { use WithoutMiddleware; - public function testScrobble() + protected function tearDown() + { + m::close(); + parent::tearDown(); + } + + /** @test */ + public function a_song_can_be_scrobbed_via_lastfm() { $this->withoutEvents(); $this->createSampleMediaSet(); diff --git a/tests/Feature/SettingTest.php b/tests/Feature/SettingTest.php index b027f42a..93f5ff1e 100644 --- a/tests/Feature/SettingTest.php +++ b/tests/Feature/SettingTest.php @@ -11,42 +11,8 @@ class SettingTest extends TestCase { use WithoutMiddleware; - public function testSetSingleKeyValue() - { - Setting::set('foo', 'bar'); - - $this->seeInDatabase('settings', ['key' => 'foo', 'value' => 's:3:"bar";']); - } - - public function testSetMultipleKeyValue() - { - Setting::set([ - 'foo' => 'bar', - 'baz' => 'qux', - ]); - - $this->seeInDatabase('settings', ['key' => 'foo', 'value' => 's:3:"bar";']); - $this->seeInDatabase('settings', ['key' => 'baz', 'value' => 's:3:"qux";']); - } - - public function testExistingShouldBeUpdated() - { - Setting::set('foo', 'bar'); - Setting::set('foo', 'baz'); - - $this->assertEquals('baz', Setting::get('foo')); - } - - public function testGet() - { - Setting::set('foo', 'bar'); - Setting::set('bar', ['baz' => 'qux']); - - $this->assertEquals('bar', Setting::get('foo')); - $this->assertEquals(['baz' => 'qux'], Setting::get('bar')); - } - - public function testApplicationSetting() + /** @test */ + public function application_setting_is_saved_properly() { Media::shouldReceive('sync')->once(); diff --git a/tests/Feature/UserTest.php b/tests/Feature/UserTest.php index 6c344bb4..5d2997d0 100644 --- a/tests/Feature/UserTest.php +++ b/tests/Feature/UserTest.php @@ -6,7 +6,8 @@ use App\Models\User; class UserTest extends TestCase { - public function testCreateUser() + /** @test */ + public function admin_can_create_a_user() { // Non-admins can't do shit $this->postAsUser('api/user', [ @@ -26,7 +27,8 @@ class UserTest extends TestCase $this->seeInDatabase('users', ['name' => 'Foo']); } - public function testUpdateUser() + /** @test */ + public function admin_can_update_a_user() { $user = factory(User::class)->create(); @@ -39,7 +41,8 @@ class UserTest extends TestCase $this->seeInDatabase('users', ['name' => 'Foo', 'email' => 'bar@baz.com']); } - public function testDeleteUser() + /** @test */ + public function admin_can_delete_a_user() { $user = factory(User::class)->create(); $admin = factory(User::class, 'admin')->create(); @@ -53,7 +56,8 @@ class UserTest extends TestCase ->seeInDatabase('users', ['id' => $admin->id]); } - public function testUserPreferences() + /** @test */ + public function user_can_update_their_preferences() { $user = factory(User::class)->create(); $this->assertNull($user->getPreference('foo')); @@ -64,15 +68,4 @@ class UserTest extends TestCase $user->deletePreference('foo'); $this->assertNull($user->getPreference('foo')); } - - public function testHidingUserPreferences() - { - $user = factory(User::class)->create([ - 'preferences' => [ - 'lastfm_session_key' => '123456', - ], - ]); - - $this->assertEquals('hidden', $user->preferences['lastfm_session_key']); - } } diff --git a/tests/Feature/YouTubeTest.php b/tests/Feature/YouTubeTest.php index 0f29a501..5d8c692a 100644 --- a/tests/Feature/YouTubeTest.php +++ b/tests/Feature/YouTubeTest.php @@ -3,41 +3,21 @@ namespace Tests\Feature; use App\Models\Song; -use App\Services\YouTube; -use GuzzleHttp\Client; -use GuzzleHttp\Psr7\Response; use Illuminate\Foundation\Testing\WithoutMiddleware; -use Mockery as m; -use YouTube as YouTubeFacade; +use YouTube; class YouTubeTest extends TestCase { use WithoutMiddleware; - public function testSearch() - { - $this->withoutEvents(); - - $client = m::mock(Client::class, [ - 'get' => new Response(200, [], file_get_contents(__DIR__.'../../blobs/youtube/search.json')), - ]); - - $api = new YouTube(null, $client); - $response = $api->search('Lorem Ipsum'); - - $this->assertEquals('Slipknot - Snuff [OFFICIAL VIDEO]', $response->items[0]->snippet->title); - - // Is it cached? - $this->assertNotNull(cache('1492972ec5c8e6b3a9323ba719655ddb')); - } - - public function testSearchVideosRelatedToSong() + /** @test */ + public function youtube_videos_related_to_a_song_can_be_searched() { $this->createSampleMediaSet(); $song = Song::first(); // We test on the facade here - YouTubeFacade::shouldReceive('searchVideosRelatedToSong')->once(); + YouTube::shouldReceive('searchVideosRelatedToSong')->once(); $this->getAsUser("/api/youtube/search/song/{$song->id}"); } diff --git a/tests/Integration/YouTubeTest.php b/tests/Integration/YouTubeTest.php new file mode 100644 index 00000000..03762cfc --- /dev/null +++ b/tests/Integration/YouTubeTest.php @@ -0,0 +1,36 @@ +withoutEvents(); + + $client = m::mock(Client::class, [ + 'get' => new Response(200, [], file_get_contents(__DIR__.'../../blobs/youtube/search.json')), + ]); + + $api = new YouTube(null, $client); + $response = $api->search('Lorem Ipsum'); + + $this->assertEquals('Slipknot - Snuff [OFFICIAL VIDEO]', $response->items[0]->snippet->title); + + // Is it cached? + $this->assertNotNull(cache('1492972ec5c8e6b3a9323ba719655ddb')); + } +} diff --git a/tests/Feature/RESTfulAPIServiceTest.php b/tests/Unit/RESTfulAPIServiceTest.php similarity index 76% rename from tests/Feature/RESTfulAPIServiceTest.php rename to tests/Unit/RESTfulAPIServiceTest.php index 27195336..fe60d85d 100644 --- a/tests/Feature/RESTfulAPIServiceTest.php +++ b/tests/Unit/RESTfulAPIServiceTest.php @@ -1,27 +1,39 @@ 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 testRequest() + /** @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"}'), diff --git a/tests/Unit/SettingTest.php b/tests/Unit/SettingTest.php index 01893a33..cb981f42 100644 --- a/tests/Unit/SettingTest.php +++ b/tests/Unit/SettingTest.php @@ -52,6 +52,15 @@ class SettingTest extends TestCase ]); } + /** @test */ + public function existing_settings_should_be_updated() + { + Setting::set('foo', 'bar'); + Setting::set('foo', 'baz'); + + $this->assertEquals('baz', Setting::get('foo')); + } + /** @test */ public function it_gets_the_setting_value_in_an_unserialized_format() {