From cc12618a958febbb30b2321a2784df1dc2d4f6fb Mon Sep 17 00:00:00 2001 From: Phan An Date: Tue, 9 Jan 2024 19:34:40 +0100 Subject: [PATCH] feat(plus): add song policy tests --- .../Commands/ActivateLicenseCommand.php | 4 +- .../Commands/CheckLicenseStatusCommand.php | 4 +- .../Commands/DeactivateLicenseCommand.php | 6 +- .../MethodNotImplementedException.php | 13 +++ app/Facades/License.php | 8 +- .../API/FetchInitialDataController.php | 4 +- .../API/MakeSongsPublicController.php | 3 +- app/Http/Controllers/API/SongController.php | 10 +- .../API/ChangeSongsVisibilityRequest.php | 7 ++ app/Providers/AppServiceProvider.php | 5 +- app/Providers/LicenseServiceProvider.php | 4 +- app/Services/CommunityLicenseService.php | 18 --- .../License/CommunityLicenseService.php | 35 ++++++ .../License/FakePlusLicenseService.php | 35 ++++++ app/Services/{ => License}/LicenseService.php | 6 +- .../License/LicenseServiceInterface.php | 19 +++ database/factories/InteractionFactory.php | 2 +- database/factories/SongFactory.php | 13 +++ routes/api.base.php | 7 +- tests/Feature/AlbumCoverTest.php | 1 + tests/Feature/AlbumInformationTest.php | 1 + tests/Feature/AlbumSongTest.php | 1 + tests/Feature/AlbumTest.php | 1 + tests/Feature/AlbumThumbnailTest.php | 1 + tests/Feature/ArtistAlbumTest.php | 1 + tests/Feature/ArtistImageTest.php | 1 + tests/Feature/ArtistInformationTest.php | 1 + tests/Feature/ArtistSongTest.php | 1 + tests/Feature/ArtistTest.php | 1 + tests/Feature/AuthTest.php | 1 + tests/Feature/DataTest.php | 2 + tests/Feature/DownloadTest.php | 42 ++++--- tests/Feature/ExcerptSearchTest.php | 1 + tests/Feature/FavoriteSongTest.php | 1 + tests/Feature/GenreTest.php | 1 + tests/Feature/InteractionTest.php | 1 + tests/Feature/KoelPlus/SongTest.php | 110 ++++++++++++++++++ tests/Feature/KoelPlus/SongVisibilityTest.php | 67 +++++++++++ tests/Feature/LastfmTest.php | 1 + tests/Feature/ObjectStorage/S3Test.php | 2 +- tests/Feature/OverviewTest.php | 1 + tests/Feature/PlayCountTest.php | 1 + tests/Feature/PlaylistFolderTest.php | 1 + tests/Feature/PlaylistSongTest.php | 1 + tests/Feature/PlaylistTest.php | 1 + tests/Feature/ProfileTest.php | 1 + tests/Feature/QueueTest.php | 1 + tests/Feature/RecentlyPlayedSongTest.php | 1 + tests/Feature/ScrobbleTest.php | 1 + tests/Feature/SettingTest.php | 7 +- tests/Feature/SongSearchTest.php | 1 + tests/Feature/SongTest.php | 1 + tests/Feature/SongVisibilityTest.php | 24 ++++ tests/Feature/UploadTest.php | 1 + tests/Feature/UserInvitationTest.php | 3 +- tests/Feature/UserTest.php | 1 + tests/Feature/YouTubeTest.php | 1 + .../Integration/Services/MediaScannerTest.php | 2 +- tests/TestCase.php | 22 +++- .../MakesHttpRequests.php} | 18 ++- tests/Traits/SandboxesTests.php | 23 ---- 61 files changed, 460 insertions(+), 95 deletions(-) create mode 100644 app/Exceptions/MethodNotImplementedException.php delete mode 100644 app/Services/CommunityLicenseService.php create mode 100644 app/Services/License/CommunityLicenseService.php create mode 100644 app/Services/License/FakePlusLicenseService.php rename app/Services/{ => License}/LicenseService.php (96%) create mode 100644 app/Services/License/LicenseServiceInterface.php create mode 100644 tests/Feature/KoelPlus/SongTest.php create mode 100644 tests/Feature/KoelPlus/SongVisibilityTest.php create mode 100644 tests/Feature/SongVisibilityTest.php rename tests/{Feature/TestCase.php => Traits/MakesHttpRequests.php} (76%) delete mode 100644 tests/Traits/SandboxesTests.php diff --git a/app/Console/Commands/ActivateLicenseCommand.php b/app/Console/Commands/ActivateLicenseCommand.php index cf81e70b..2d710dd0 100644 --- a/app/Console/Commands/ActivateLicenseCommand.php +++ b/app/Console/Commands/ActivateLicenseCommand.php @@ -2,7 +2,7 @@ namespace App\Console\Commands; -use App\Services\LicenseService; +use App\Services\License\LicenseServiceInterface; use Illuminate\Console\Command; use Throwable; @@ -11,7 +11,7 @@ class ActivateLicenseCommand extends Command protected $signature = 'koel:license:activate {key : The license key to activate.}'; protected $description = 'Activate a Koel Plus license'; - public function __construct(private LicenseService $licenseService) + public function __construct(private LicenseServiceInterface $licenseService) { parent::__construct(); } diff --git a/app/Console/Commands/CheckLicenseStatusCommand.php b/app/Console/Commands/CheckLicenseStatusCommand.php index ddcdb452..7d508c9a 100644 --- a/app/Console/Commands/CheckLicenseStatusCommand.php +++ b/app/Console/Commands/CheckLicenseStatusCommand.php @@ -2,7 +2,7 @@ namespace App\Console\Commands; -use App\Services\LicenseService; +use App\Services\License\LicenseServiceInterface; use App\Values\LicenseStatus; use Illuminate\Console\Command; use Throwable; @@ -12,7 +12,7 @@ class CheckLicenseStatusCommand extends Command protected $signature = 'koel:license:status'; protected $description = 'Check the current Koel Plus license status.'; - public function __construct(private LicenseService $licenseService) + public function __construct(private LicenseServiceInterface $licenseService) { parent::__construct(); } diff --git a/app/Console/Commands/DeactivateLicenseCommand.php b/app/Console/Commands/DeactivateLicenseCommand.php index 8118bc93..c0d64225 100644 --- a/app/Console/Commands/DeactivateLicenseCommand.php +++ b/app/Console/Commands/DeactivateLicenseCommand.php @@ -2,7 +2,7 @@ namespace App\Console\Commands; -use App\Services\LicenseService; +use App\Services\License\LicenseServiceInterface; use Illuminate\Console\Command; use Throwable; @@ -11,7 +11,7 @@ class DeactivateLicenseCommand extends Command protected $signature = 'koel:license:deactivate'; protected $description = 'Deactivate the currently active Koel Plus license'; - public function __construct(private LicenseService $plusService) + public function __construct(private LicenseServiceInterface $plusService) { parent::__construct(); } @@ -35,7 +35,7 @@ class DeactivateLicenseCommand extends Command $this->components->info('Deactivating your licenseā€¦'); try { - $this->plusService->deactivateLicense($status->license); + $this->plusService->deactivate($status->license); $this->components->info('Koel Plus has been deactivated. Plus features are now disabled.'); return self::SUCCESS; diff --git a/app/Exceptions/MethodNotImplementedException.php b/app/Exceptions/MethodNotImplementedException.php new file mode 100644 index 00000000..9f81bbab --- /dev/null +++ b/app/Exceptions/MethodNotImplementedException.php @@ -0,0 +1,13 @@ +getStatus(); diff --git a/app/Http/Controllers/API/MakeSongsPublicController.php b/app/Http/Controllers/API/MakeSongsPublicController.php index bab70efc..77077853 100644 --- a/app/Http/Controllers/API/MakeSongsPublicController.php +++ b/app/Http/Controllers/API/MakeSongsPublicController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\API; use App\Http\Controllers\Controller; use App\Http\Requests\API\ChangeSongsVisibilityRequest; +use App\Models\Song; use App\Models\User; use App\Repositories\SongRepository; use App\Services\SongService; @@ -18,7 +19,7 @@ class MakeSongsPublicController extends Controller SongService $songService, Authenticatable $user ) { - $songs = $repository->getMany(ids: $request->songs, scopedUser: $user); + $songs = Song::find($request->songs); $songs->each(fn ($song) => $this->authorize('own', $song)); $songService->makeSongsPublic($songs); diff --git a/app/Http/Controllers/API/SongController.php b/app/Http/Controllers/API/SongController.php index 88270bc8..0d694201 100644 --- a/app/Http/Controllers/API/SongController.php +++ b/app/Http/Controllers/API/SongController.php @@ -45,13 +45,15 @@ class SongController extends Controller public function show(Song $song) { + $this->authorize('access', $song); + return SongResource::make($this->songRepository->getOne($song->id)); } public function update(SongUpdateRequest $request) { - $this->songRepository->getMany(ids: $request->songs, scopedUser: $this->user) - ->each(fn (Song $song) => $this->authorize('edit', $song)); + // Don't use SongRepository::findMany() because it'd be already catered to the current user. + Song::find($request->songs)->each(fn (Song $song) => $this->authorize('edit', $song)); $updatedSongs = $this->songService->updateSongs($request->songs, SongUpdateData::fromRequest($request)); $albums = $this->albumRepository->getMany($updatedSongs->pluck('album_id')->toArray()); @@ -73,8 +75,8 @@ class SongController extends Controller public function destroy(DeleteSongsRequest $request) { - $this->songRepository->getMany(ids: $request->songs, scopedUser: $this->user) - ->each(fn (Song $song) => $this->authorize('delete', $song)); + // Don't use SongRepository::findMany() because it'd be already catered to the current user. + Song::find($request->songs)->each(fn (Song $song) => $this->authorize('delete', $song)); $this->songService->deleteSongs($request->songs); diff --git a/app/Http/Requests/API/ChangeSongsVisibilityRequest.php b/app/Http/Requests/API/ChangeSongsVisibilityRequest.php index 58c4ccdb..dfc67e07 100644 --- a/app/Http/Requests/API/ChangeSongsVisibilityRequest.php +++ b/app/Http/Requests/API/ChangeSongsVisibilityRequest.php @@ -2,6 +2,8 @@ namespace App\Http\Requests\API; +use App\Facades\License; + /** * @property-read array $songs */ @@ -14,4 +16,9 @@ class ChangeSongsVisibilityRequest extends Request 'songs' => 'required|exists:songs,id', ]; } + + public function authorize(): bool + { + return License::isPlus(); + } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 2f57c44d..ae36096a 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -5,7 +5,8 @@ namespace App\Providers; use App\Services\ApiClients\ApiClient; use App\Services\ApiClients\LemonSqueezyApiClient; use App\Services\LastfmService; -use App\Services\LicenseService; +use App\Services\License\LicenseService; +use App\Services\License\LicenseServiceInterface; use App\Services\MusicEncyclopedia; use App\Services\NullMusicEncyclopedia; use App\Services\SpotifyService; @@ -48,6 +49,8 @@ class AppServiceProvider extends ServiceProvider return $this->app->get(LastfmService::enabled() ? LastfmService::class : NullMusicEncyclopedia::class); }); + $this->app->bind(LicenseServiceInterface::class, LicenseService::class); + $this->app->when(LicenseService::class) ->needs(ApiClient::class) ->give(fn () => $this->app->get(LemonSqueezyApiClient::class)); diff --git a/app/Providers/LicenseServiceProvider.php b/app/Providers/LicenseServiceProvider.php index 190b9d82..ef4fb822 100644 --- a/app/Providers/LicenseServiceProvider.php +++ b/app/Providers/LicenseServiceProvider.php @@ -2,13 +2,13 @@ namespace App\Providers; -use App\Services\LicenseService; +use App\Services\License\LicenseServiceInterface; use Illuminate\Support\ServiceProvider; class LicenseServiceProvider extends ServiceProvider { public function register(): void { - app()->singleton('License', static fn (): LicenseService => app(LicenseService::class)); + app()->singleton('License', static fn (): LicenseServiceInterface => app(LicenseServiceInterface::class)); } } diff --git a/app/Services/CommunityLicenseService.php b/app/Services/CommunityLicenseService.php deleted file mode 100644 index 795a1b57..00000000 --- a/app/Services/CommunityLicenseService.php +++ /dev/null @@ -1,18 +0,0 @@ -client->post('licenses/deactivate', [ diff --git a/app/Services/License/LicenseServiceInterface.php b/app/Services/License/LicenseServiceInterface.php new file mode 100644 index 00000000..7707d1ff --- /dev/null +++ b/app/Services/License/LicenseServiceInterface.php @@ -0,0 +1,19 @@ + Song::factory(), 'user_id' => User::factory(), - 'liked' => $this->faker->boolean, + 'liked' => $this->faker->boolean(), 'play_count' => $this->faker->randomNumber(), ]; } diff --git a/database/factories/SongFactory.php b/database/factories/SongFactory.php index 7094e3d8..9e0562eb 100644 --- a/database/factories/SongFactory.php +++ b/database/factories/SongFactory.php @@ -4,6 +4,7 @@ namespace Database\Factories; use App\Models\Album; use App\Models\Song; +use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; class SongFactory extends Factory @@ -27,7 +28,19 @@ class SongFactory extends Factory 'path' => '/tmp/' . uniqid() . '.mp3', 'genre' => $this->faker->randomElement(['Rock', 'Pop', 'Jazz', 'Classical', 'Metal', 'Hip Hop', 'Rap']), 'year' => $this->faker->year(), + 'is_public' => $this->faker->boolean(), + 'owner_id' => User::factory(), 'mtime' => time(), ]; } + + public function public(): self + { + return $this->state(fn () => ['is_public' => true]); // @phpcs:ignore + } + + public function private(): self + { + return $this->state(fn () => ['is_public' => false]); // @phpcs:ignore + } } diff --git a/routes/api.base.php b/routes/api.base.php index 11338461..e8f05b5a 100644 --- a/routes/api.base.php +++ b/routes/api.base.php @@ -1,6 +1,5 @@ middleware('api')->group(static function (): void { Route::post('invitations', [UserInvitationController::class, 'invite']); Route::delete('invitations', [UserInvitationController::class, 'revoke']); - if (License::isPlus()) { - Route::put('songs/make-public', MakeSongsPublicController::class); - Route::put('songs/make-private', MakeSongsPrivateController::class); - } + Route::put('songs/make-public', MakeSongsPublicController::class); + Route::put('songs/make-private', MakeSongsPrivateController::class); }); // Object-storage (S3) routes diff --git a/tests/Feature/AlbumCoverTest.php b/tests/Feature/AlbumCoverTest.php index 66a86c41..0dbba439 100644 --- a/tests/Feature/AlbumCoverTest.php +++ b/tests/Feature/AlbumCoverTest.php @@ -8,6 +8,7 @@ use App\Models\User; use App\Services\MediaMetadataService; use Mockery; use Mockery\MockInterface; +use Tests\TestCase; class AlbumCoverTest extends TestCase { diff --git a/tests/Feature/AlbumInformationTest.php b/tests/Feature/AlbumInformationTest.php index f08bb729..1d644117 100644 --- a/tests/Feature/AlbumInformationTest.php +++ b/tests/Feature/AlbumInformationTest.php @@ -6,6 +6,7 @@ use App\Models\Album; use App\Services\MediaInformationService; use App\Values\AlbumInformation; use Mockery; +use Tests\TestCase; class AlbumInformationTest extends TestCase { diff --git a/tests/Feature/AlbumSongTest.php b/tests/Feature/AlbumSongTest.php index e9195082..3585cf17 100644 --- a/tests/Feature/AlbumSongTest.php +++ b/tests/Feature/AlbumSongTest.php @@ -4,6 +4,7 @@ namespace Tests\Feature; use App\Models\Album; use App\Models\Song; +use Tests\TestCase; class AlbumSongTest extends TestCase { diff --git a/tests/Feature/AlbumTest.php b/tests/Feature/AlbumTest.php index aa55b4a2..bf99f406 100644 --- a/tests/Feature/AlbumTest.php +++ b/tests/Feature/AlbumTest.php @@ -3,6 +3,7 @@ namespace Tests\Feature; use App\Models\Album; +use Tests\TestCase; class AlbumTest extends TestCase { diff --git a/tests/Feature/AlbumThumbnailTest.php b/tests/Feature/AlbumThumbnailTest.php index 06b7ea64..6e469402 100644 --- a/tests/Feature/AlbumThumbnailTest.php +++ b/tests/Feature/AlbumThumbnailTest.php @@ -6,6 +6,7 @@ use App\Models\Album; use App\Services\MediaMetadataService; use Mockery; use Mockery\MockInterface; +use Tests\TestCase; class AlbumThumbnailTest extends TestCase { diff --git a/tests/Feature/ArtistAlbumTest.php b/tests/Feature/ArtistAlbumTest.php index 65b2e5c3..e24675db 100644 --- a/tests/Feature/ArtistAlbumTest.php +++ b/tests/Feature/ArtistAlbumTest.php @@ -4,6 +4,7 @@ namespace Tests\Feature; use App\Models\Album; use App\Models\Artist; +use Tests\TestCase; class ArtistAlbumTest extends TestCase { diff --git a/tests/Feature/ArtistImageTest.php b/tests/Feature/ArtistImageTest.php index 71835ce7..70f0a569 100644 --- a/tests/Feature/ArtistImageTest.php +++ b/tests/Feature/ArtistImageTest.php @@ -8,6 +8,7 @@ use App\Models\User; use App\Services\MediaMetadataService; use Mockery; use Mockery\MockInterface; +use Tests\TestCase; class ArtistImageTest extends TestCase { diff --git a/tests/Feature/ArtistInformationTest.php b/tests/Feature/ArtistInformationTest.php index 61c94976..086396f4 100644 --- a/tests/Feature/ArtistInformationTest.php +++ b/tests/Feature/ArtistInformationTest.php @@ -6,6 +6,7 @@ use App\Models\Artist; use App\Services\MediaInformationService; use App\Values\ArtistInformation; use Mockery; +use Tests\TestCase; class ArtistInformationTest extends TestCase { diff --git a/tests/Feature/ArtistSongTest.php b/tests/Feature/ArtistSongTest.php index 5c2f0229..90d93cfd 100644 --- a/tests/Feature/ArtistSongTest.php +++ b/tests/Feature/ArtistSongTest.php @@ -4,6 +4,7 @@ namespace Tests\Feature; use App\Models\Artist; use App\Models\Song; +use Tests\TestCase; class ArtistSongTest extends TestCase { diff --git a/tests/Feature/ArtistTest.php b/tests/Feature/ArtistTest.php index a61de171..8426ea85 100644 --- a/tests/Feature/ArtistTest.php +++ b/tests/Feature/ArtistTest.php @@ -3,6 +3,7 @@ namespace Tests\Feature; use App\Models\Artist; +use Tests\TestCase; class ArtistTest extends TestCase { diff --git a/tests/Feature/AuthTest.php b/tests/Feature/AuthTest.php index a3e45f2c..ac1d59c2 100644 --- a/tests/Feature/AuthTest.php +++ b/tests/Feature/AuthTest.php @@ -4,6 +4,7 @@ namespace Tests\Feature; use App\Models\User; use Illuminate\Support\Facades\Hash; +use Tests\TestCase; class AuthTest extends TestCase { diff --git a/tests/Feature/DataTest.php b/tests/Feature/DataTest.php index 0c230037..b649a97d 100644 --- a/tests/Feature/DataTest.php +++ b/tests/Feature/DataTest.php @@ -2,6 +2,8 @@ namespace Tests\Feature; +use Tests\TestCase; + class DataTest extends TestCase { public function testIndex(): void diff --git a/tests/Feature/DownloadTest.php b/tests/Feature/DownloadTest.php index 542d3273..926940ec 100644 --- a/tests/Feature/DownloadTest.php +++ b/tests/Feature/DownloadTest.php @@ -4,14 +4,15 @@ namespace Tests\Feature; 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\Repositories\InteractionRepository; use App\Services\DownloadService; use Illuminate\Support\Collection; use Mockery; use Mockery\MockInterface; +use Tests\TestCase; class DownloadTest extends TestCase { @@ -90,14 +91,18 @@ class DownloadTest extends TestCase /** @var Album $album */ $album = Album::query()->first(); + $songs = Song::factory(3)->create(['album_id' => $album->id]); + /** @var User $user */ $user = User::factory()->create(); $this->downloadService ->shouldReceive('from') ->once() - ->with(Mockery::on(static function (Album $retrievedAlbum) use ($album): bool { - return $retrievedAlbum->id === $album->id; + ->with(Mockery::on(static function (Collection $retrievedSongs) use ($songs): bool { + self::assertEqualsCanonicalizing($retrievedSongs->pluck('id')->all(), $songs->pluck('id')->all()); + + return true; })) ->andReturn($this->mediaPath . '/blank.mp3'); @@ -110,14 +115,18 @@ class DownloadTest extends TestCase /** @var Artist $artist */ $artist = Artist::query()->first(); + $songs = Song::factory(3)->create(['artist_id' => $artist->id]); + /** @var User $user */ $user = User::factory()->create(); $this->downloadService ->shouldReceive('from') ->once() - ->with(Mockery::on(static function (Artist $retrievedArtist) use ($artist): bool { - return $retrievedArtist->id === $artist->id; + ->with(Mockery::on(static function (Collection $retrievedSongs) use ($songs): bool { + self::assertEqualsCanonicalizing($retrievedSongs->pluck('id')->all(), $songs->pluck('id')->all()); + + return true; })) ->andReturn($this->mediaPath . '/blank.mp3'); @@ -130,13 +139,19 @@ class DownloadTest extends TestCase /** @var User $user */ $user = User::factory()->create(); + $songs = Song::factory(3)->create(); + /** @var Playlist $playlist */ $playlist = Playlist::factory()->for($user)->create(); + $playlist->songs()->attach($songs); + $this->downloadService ->shouldReceive('from') - ->with(Mockery::on(static function (Playlist $retrievedPlaylist) use ($playlist): bool { - return $retrievedPlaylist->id === $playlist->id; + ->with(Mockery::on(static function (Collection $retrievedSongs) use ($songs): bool { + self::assertEqualsCanonicalizing($retrievedSongs->pluck('id')->all(), $songs->pluck('id')->all()); + + return true; })) ->once() ->andReturn($this->mediaPath . '/blank.mp3'); @@ -161,17 +176,16 @@ class DownloadTest extends TestCase { /** @var User $user */ $user = User::factory()->create(); - $favorites = Collection::make(); - self::mock(InteractionRepository::class) - ->shouldReceive('getUserFavorites') - ->once() - ->with(Mockery::on(static fn (User $retrievedUser) => $retrievedUser->is($user))) - ->andReturn($favorites); + $favorites = Interaction::factory(3)->for($user)->create(['liked' => true]); $this->downloadService ->shouldReceive('from') - ->with($favorites) + ->with(Mockery::on(static function (Collection $songs) use ($favorites): bool { + self::assertEqualsCanonicalizing($songs->pluck('id')->all(), $favorites->pluck('song_id')->all()); + + return true; + })) ->once() ->andReturn($this->mediaPath . '/blank.mp3'); diff --git a/tests/Feature/ExcerptSearchTest.php b/tests/Feature/ExcerptSearchTest.php index ef321eb5..8f16db3e 100644 --- a/tests/Feature/ExcerptSearchTest.php +++ b/tests/Feature/ExcerptSearchTest.php @@ -5,6 +5,7 @@ namespace Tests\Feature; use App\Models\Album; use App\Models\Artist; use App\Models\Song; +use Tests\TestCase; class ExcerptSearchTest extends TestCase { diff --git a/tests/Feature/FavoriteSongTest.php b/tests/Feature/FavoriteSongTest.php index 708ca9f1..e7e5e99f 100644 --- a/tests/Feature/FavoriteSongTest.php +++ b/tests/Feature/FavoriteSongTest.php @@ -4,6 +4,7 @@ namespace Tests\Feature; use App\Models\Interaction; use App\Models\User; +use Tests\TestCase; class FavoriteSongTest extends TestCase { diff --git a/tests/Feature/GenreTest.php b/tests/Feature/GenreTest.php index 5f6c43bb..c65a5dc1 100644 --- a/tests/Feature/GenreTest.php +++ b/tests/Feature/GenreTest.php @@ -4,6 +4,7 @@ namespace Tests\Feature; use App\Models\Song; use App\Values\Genre; +use Tests\TestCase; class GenreTest extends TestCase { diff --git a/tests/Feature/InteractionTest.php b/tests/Feature/InteractionTest.php index 3f519893..24b78531 100644 --- a/tests/Feature/InteractionTest.php +++ b/tests/Feature/InteractionTest.php @@ -7,6 +7,7 @@ use App\Events\SongsBatchLiked; use App\Models\Song; use App\Models\User; use Illuminate\Support\Collection; +use Tests\TestCase; class InteractionTest extends TestCase { diff --git a/tests/Feature/KoelPlus/SongTest.php b/tests/Feature/KoelPlus/SongTest.php new file mode 100644 index 00000000..d3f22e20 --- /dev/null +++ b/tests/Feature/KoelPlus/SongTest.php @@ -0,0 +1,110 @@ +create(); + + /** @var Song $publicSong */ + $publicSong = Song::factory()->public()->create(); + + // We can access public songs. + $this->getAs('api/songs/' . $publicSong->id, $user)->assertSuccessful(); + + /** @var Song $ownPrivateSong */ + $ownPrivateSong = Song::factory()->for($user, 'owner')->private()->create(); + + // We can access our own private songs. + $this->getAs('api/songs/' . $ownPrivateSong->id, $user)->assertSuccessful(); + + $externalPrivateSong = Song::factory()->private()->create(); + + // But we can't access private songs that are not ours. + $this->getAs('api/songs/' . $externalPrivateSong->id, $user)->assertForbidden(); + } + + public function testEditSongsPolicy(): void + { + /** @var User $currentUser */ + $currentUser = User::factory()->create(); + + /** @var User $anotherUser */ + $anotherUser = User::factory()->create(); + + /** @var Collection $externalSongs */ + $externalSongs = Song::factory(3)->for($anotherUser, 'owner')->private()->create(); + + // We can't edit songs that are not ours. + $this->putAs('api/songs', [ + 'songs' => $externalSongs->pluck('id')->toArray(), + 'data' => [ + 'title' => 'New Title', + ], + ], $currentUser)->assertForbidden(); + + // Even if some of the songs are owned by us, we still can't edit them. + $mixedSongs = $externalSongs->merge(Song::factory(2)->for($currentUser, 'owner')->create()); + + $this->putAs('api/songs', [ + 'songs' => $mixedSongs->pluck('id')->toArray(), + 'data' => [ + 'title' => 'New Title', + ], + ], $currentUser)->assertForbidden(); + + // But we can edit our own songs. + $ownSongs = Song::factory(3)->for($currentUser, 'owner')->create(); + + $this->putAs('api/songs', [ + 'songs' => $ownSongs->pluck('id')->toArray(), + 'data' => [ + 'title' => 'New Title', + ], + ], $currentUser)->assertSuccessful(); + } + + public function testDeleteSongsPolicy(): void + { + /** @var User $currentUser */ + $currentUser = User::factory()->create(); + + /** @var User $anotherUser */ + $anotherUser = User::factory()->create(); + + /** @var Collection $externalSongs */ + $externalSongs = Song::factory(3)->for($anotherUser, 'owner')->private()->create(); + + // We can't delete songs that are not ours. + $this->deleteAs('api/songs', ['songs' => $externalSongs->pluck('id')->toArray()], $currentUser) + ->assertForbidden(); + + // Even if some of the songs are owned by us, we still can't delete them. + $mixedSongs = $externalSongs->merge(Song::factory(2)->for($currentUser, 'owner')->create()); + + $this->deleteAs('api/songs', ['songs' => $mixedSongs->pluck('id')->toArray()], $currentUser) + ->assertForbidden(); + + // But we can delete our own songs. + $ownSongs = Song::factory(3)->for($currentUser, 'owner')->create(); + + $this->deleteAs('api/songs', ['songs' => $ownSongs->pluck('id')->toArray()], $currentUser) + ->assertSuccessful(); + } +} diff --git a/tests/Feature/KoelPlus/SongVisibilityTest.php b/tests/Feature/KoelPlus/SongVisibilityTest.php new file mode 100644 index 00000000..f3317515 --- /dev/null +++ b/tests/Feature/KoelPlus/SongVisibilityTest.php @@ -0,0 +1,67 @@ +create(); + + /** @var User $anotherUser */ + $anotherUser = User::factory()->create(); + + /** @var Collection $externalSongs */ + $externalSongs = Song::factory(3)->for($anotherUser, 'owner')->private()->create(); + + // We can't make public songs that are not ours. + $this->putAs('api/songs/make-public', ['songs' => $externalSongs->pluck('id')->toArray()], $currentUser) + ->assertForbidden(); + + // But we can our own songs. + $ownSongs = Song::factory(3)->for($currentUser, 'owner')->create(); + + $this->putAs('api/songs/make-public', ['songs' => $ownSongs->pluck('id')->toArray()], $currentUser) + ->assertSuccessful(); + + $ownSongs->each(static fn (Song $song) => self::assertTrue($song->refresh()->is_public)); + } + + public function testMakingSongPrivate(): void + { + /** @var User $currentUser */ + $currentUser = User::factory()->create(); + + /** @var User $anotherUser */ + $anotherUser = User::factory()->create(); + + /** @var Collection $externalSongs */ + $externalSongs = Song::factory(3)->for($anotherUser, 'owner')->public()->create(); + + // We can't make private songs that are not ours. + $this->putAs('api/songs/make-private', ['songs' => $externalSongs->pluck('id')->toArray()], $currentUser) + ->assertForbidden(); + + // But we can our own songs. + $ownSongs = Song::factory(3)->for($currentUser, 'owner')->create(); + + $this->putAs('api/songs/make-private', ['songs' => $ownSongs->pluck('id')->toArray()], $currentUser) + ->assertSuccessful(); + + $ownSongs->each(static fn (Song $song) => self::assertFalse($song->refresh()->is_public)); + } +} diff --git a/tests/Feature/LastfmTest.php b/tests/Feature/LastfmTest.php index b1ec7c54..69d66bf5 100644 --- a/tests/Feature/LastfmTest.php +++ b/tests/Feature/LastfmTest.php @@ -9,6 +9,7 @@ use Laravel\Sanctum\NewAccessToken; use Laravel\Sanctum\PersonalAccessToken; use Mockery; use Mockery\MockInterface; +use Tests\TestCase; class LastfmTest extends TestCase { diff --git a/tests/Feature/ObjectStorage/S3Test.php b/tests/Feature/ObjectStorage/S3Test.php index 9294a880..ba8a0e8a 100644 --- a/tests/Feature/ObjectStorage/S3Test.php +++ b/tests/Feature/ObjectStorage/S3Test.php @@ -5,7 +5,7 @@ namespace Tests\Feature\ObjectStorage; use App\Events\LibraryChanged; use App\Models\Song; use Illuminate\Foundation\Testing\WithoutMiddleware; -use Tests\Feature\TestCase; +use Tests\TestCase; class S3Test extends TestCase { diff --git a/tests/Feature/OverviewTest.php b/tests/Feature/OverviewTest.php index 6dbf5fb6..4997e659 100644 --- a/tests/Feature/OverviewTest.php +++ b/tests/Feature/OverviewTest.php @@ -4,6 +4,7 @@ namespace Tests\Feature; use App\Models\Interaction; use App\Models\User; +use Tests\TestCase; class OverviewTest extends TestCase { diff --git a/tests/Feature/PlayCountTest.php b/tests/Feature/PlayCountTest.php index b2c14076..e650617e 100644 --- a/tests/Feature/PlayCountTest.php +++ b/tests/Feature/PlayCountTest.php @@ -7,6 +7,7 @@ use App\Models\Interaction; use App\Models\Song; use App\Models\User; use Illuminate\Support\Facades\Event; +use Tests\TestCase; class PlayCountTest extends TestCase { diff --git a/tests/Feature/PlaylistFolderTest.php b/tests/Feature/PlaylistFolderTest.php index c074b7b6..7f0f7269 100644 --- a/tests/Feature/PlaylistFolderTest.php +++ b/tests/Feature/PlaylistFolderTest.php @@ -4,6 +4,7 @@ namespace Tests\Feature; use App\Models\PlaylistFolder; use App\Models\User; +use Tests\TestCase; class PlaylistFolderTest extends TestCase { diff --git a/tests/Feature/PlaylistSongTest.php b/tests/Feature/PlaylistSongTest.php index 32a198a3..5848d960 100644 --- a/tests/Feature/PlaylistSongTest.php +++ b/tests/Feature/PlaylistSongTest.php @@ -6,6 +6,7 @@ use App\Models\Playlist; use App\Models\Song; use App\Models\User; use Illuminate\Support\Collection; +use Tests\TestCase; class PlaylistSongTest extends TestCase { diff --git a/tests/Feature/PlaylistTest.php b/tests/Feature/PlaylistTest.php index 0af03963..2471e06a 100644 --- a/tests/Feature/PlaylistTest.php +++ b/tests/Feature/PlaylistTest.php @@ -7,6 +7,7 @@ use App\Models\Song; use App\Models\User; use App\Values\SmartPlaylistRule; use Illuminate\Support\Collection; +use Tests\TestCase; class PlaylistTest extends TestCase { diff --git a/tests/Feature/ProfileTest.php b/tests/Feature/ProfileTest.php index f2880e8e..6f83cc51 100644 --- a/tests/Feature/ProfileTest.php +++ b/tests/Feature/ProfileTest.php @@ -4,6 +4,7 @@ namespace Tests\Feature; use App\Models\User; use Illuminate\Support\Facades\Hash; +use Tests\TestCase; class ProfileTest extends TestCase { diff --git a/tests/Feature/QueueTest.php b/tests/Feature/QueueTest.php index 92d80f88..a66681b5 100644 --- a/tests/Feature/QueueTest.php +++ b/tests/Feature/QueueTest.php @@ -5,6 +5,7 @@ namespace Tests\Feature; use App\Models\QueueState; use App\Models\Song; use App\Models\User; +use Tests\TestCase; class QueueTest extends TestCase { diff --git a/tests/Feature/RecentlyPlayedSongTest.php b/tests/Feature/RecentlyPlayedSongTest.php index c567406e..9ed4f78e 100644 --- a/tests/Feature/RecentlyPlayedSongTest.php +++ b/tests/Feature/RecentlyPlayedSongTest.php @@ -4,6 +4,7 @@ namespace Tests\Feature; use App\Models\Interaction; use App\Models\User; +use Tests\TestCase; class RecentlyPlayedSongTest extends TestCase { diff --git a/tests/Feature/ScrobbleTest.php b/tests/Feature/ScrobbleTest.php index 2b9ec572..ee621c97 100644 --- a/tests/Feature/ScrobbleTest.php +++ b/tests/Feature/ScrobbleTest.php @@ -6,6 +6,7 @@ use App\Models\Song; use App\Models\User; use App\Services\LastfmService; use Mockery; +use Tests\TestCase; class ScrobbleTest extends TestCase { diff --git a/tests/Feature/SettingTest.php b/tests/Feature/SettingTest.php index fc073074..49b9fdb6 100644 --- a/tests/Feature/SettingTest.php +++ b/tests/Feature/SettingTest.php @@ -7,16 +7,17 @@ use App\Models\User; use App\Services\MediaScanner; use App\Values\ScanResultCollection; use Mockery\MockInterface; +use Tests\TestCase; class SettingTest extends TestCase { - private MediaScanner|MockInterface $mediaSyncService; + private MediaScanner|MockInterface $mediaScanner; public function setUp(): void { parent::setUp(); - $this->mediaSyncService = self::mock(MediaScanner::class); + $this->mediaScanner = self::mock(MediaScanner::class); } public function testSaveSettings(): void @@ -24,7 +25,7 @@ class SettingTest extends TestCase /** @var User $admin */ $admin = User::factory()->admin()->create(); - $this->mediaSyncService->shouldReceive('scan')->once() + $this->mediaScanner->shouldReceive('scan')->once() ->andReturn(ScanResultCollection::create()); $this->putAs('/api/settings', ['media_path' => __DIR__], $admin) diff --git a/tests/Feature/SongSearchTest.php b/tests/Feature/SongSearchTest.php index 88675005..9bedf76b 100644 --- a/tests/Feature/SongSearchTest.php +++ b/tests/Feature/SongSearchTest.php @@ -3,6 +3,7 @@ namespace Tests\Feature; use App\Models\Song; +use Tests\TestCase; class SongSearchTest extends TestCase { diff --git a/tests/Feature/SongTest.php b/tests/Feature/SongTest.php index 5d90f653..47a0f456 100644 --- a/tests/Feature/SongTest.php +++ b/tests/Feature/SongTest.php @@ -7,6 +7,7 @@ use App\Models\Artist; use App\Models\Song; use App\Models\User; use Illuminate\Support\Collection; +use Tests\TestCase; class SongTest extends TestCase { diff --git a/tests/Feature/SongVisibilityTest.php b/tests/Feature/SongVisibilityTest.php new file mode 100644 index 00000000..af6c7c11 --- /dev/null +++ b/tests/Feature/SongVisibilityTest.php @@ -0,0 +1,24 @@ +admin()->create(); + + Song::factory(3)->create(); + + $this->putAs('api/songs/make-public', ['songs' => Song::query()->pluck('id')->all()], $owner) + ->assertForbidden(); + + $this->putAs('api/songs/make-private', ['songs' => Song::query()->pluck('id')->all()], $owner) + ->assertForbidden(); + } +} diff --git a/tests/Feature/UploadTest.php b/tests/Feature/UploadTest.php index 42136c19..b28096de 100644 --- a/tests/Feature/UploadTest.php +++ b/tests/Feature/UploadTest.php @@ -10,6 +10,7 @@ use App\Models\User; use Illuminate\Http\Response; use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\Event; +use Tests\TestCase; class UploadTest extends TestCase { diff --git a/tests/Feature/UserInvitationTest.php b/tests/Feature/UserInvitationTest.php index 852e63e4..460a0970 100644 --- a/tests/Feature/UserInvitationTest.php +++ b/tests/Feature/UserInvitationTest.php @@ -4,8 +4,9 @@ namespace Tests\Feature; use App\Mail\UserInvite; use App\Models\User; +use Illuminate\Support\Facades\Mail; use Illuminate\Support\Str; -use Mail; +use Tests\TestCase; class UserInvitationTest extends TestCase { diff --git a/tests/Feature/UserTest.php b/tests/Feature/UserTest.php index de9c9548..ff3b7532 100644 --- a/tests/Feature/UserTest.php +++ b/tests/Feature/UserTest.php @@ -4,6 +4,7 @@ namespace Tests\Feature; use App\Models\User; use Illuminate\Support\Facades\Hash; +use Tests\TestCase; class UserTest extends TestCase { diff --git a/tests/Feature/YouTubeTest.php b/tests/Feature/YouTubeTest.php index 64118953..b53b49fd 100644 --- a/tests/Feature/YouTubeTest.php +++ b/tests/Feature/YouTubeTest.php @@ -6,6 +6,7 @@ use App\Models\Song; use App\Services\YouTubeService; use Mockery; use Mockery\MockInterface; +use Tests\TestCase; class YouTubeTest extends TestCase { diff --git a/tests/Integration/Services/MediaScannerTest.php b/tests/Integration/Services/MediaScannerTest.php index 61465dfb..0a6e10aa 100644 --- a/tests/Integration/Services/MediaScannerTest.php +++ b/tests/Integration/Services/MediaScannerTest.php @@ -16,7 +16,7 @@ use App\Values\ScanConfiguration; use getID3; use Illuminate\Support\Arr; use Mockery; -use Tests\Feature\TestCase; +use Tests\TestCase; class MediaScannerTest extends TestCase { diff --git a/tests/TestCase.php b/tests/TestCase.php index fa85eba4..fd6fe9ff 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -6,20 +6,21 @@ use App\Facades\License; use App\Models\Album; use App\Models\Artist; use App\Models\Song; -use App\Services\CommunityLicenseService; +use App\Services\License\CommunityLicenseService; use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts; use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\TestCase as BaseTestCase; +use Illuminate\Support\Facades\File; use ReflectionClass; use Tests\Traits\CreatesApplication; -use Tests\Traits\SandboxesTests; +use Tests\Traits\MakesHttpRequests; abstract class TestCase extends BaseTestCase { use ArraySubsetAsserts; use CreatesApplication; use DatabaseTransactions; - use SandboxesTests; + use MakesHttpRequests; public function setUp(): void { @@ -58,4 +59,19 @@ abstract class TestCase extends BaseTestCase return $property->getValue($object); } + + private static function createSandbox(): void + { + config(['koel.album_cover_dir' => 'sandbox/img/covers/']); + config(['koel.artist_image_dir' => 'sandbox/img/artists/']); + + File::ensureDirectoryExists(public_path(config('koel.album_cover_dir'))); + File::ensureDirectoryExists(public_path(config('koel.artist_image_dir'))); + File::ensureDirectoryExists(public_path('sandbox/media/')); + } + + private static function destroySandbox(): void + { + File::deleteDirectory(public_path('sandbox')); + } } diff --git a/tests/Feature/TestCase.php b/tests/Traits/MakesHttpRequests.php similarity index 76% rename from tests/Feature/TestCase.php rename to tests/Traits/MakesHttpRequests.php index f4694141..11666e9f 100644 --- a/tests/Feature/TestCase.php +++ b/tests/Traits/MakesHttpRequests.php @@ -1,20 +1,30 @@ create(); $this->withToken($user->createToken('koel')->plainTextToken); - return parent::json($method, $uri, $data, $headers); + return $this->json($method, $uri, $data, $headers); } protected function getAs(string $url, ?User $user = null): TestResponse diff --git a/tests/Traits/SandboxesTests.php b/tests/Traits/SandboxesTests.php deleted file mode 100644 index 82850625..00000000 --- a/tests/Traits/SandboxesTests.php +++ /dev/null @@ -1,23 +0,0 @@ - 'sandbox/img/covers/']); - config(['koel.artist_image_dir' => 'sandbox/img/artists/']); - - File::ensureDirectoryExists(public_path(config('koel.album_cover_dir'))); - File::ensureDirectoryExists(public_path(config('koel.artist_image_dir'))); - File::ensureDirectoryExists(public_path('sandbox/media/')); - } - - private static function destroySandbox(): void - { - File::deleteDirectory(public_path('sandbox')); - } -}