feat(build): uprade to Laravel 10 (and PHP 8.1)

This commit is contained in:
Phan An 2024-04-18 13:27:07 +02:00
parent 902c439fed
commit 3b7d47cb25
48 changed files with 1912 additions and 1717 deletions

View file

@ -10,12 +10,12 @@ use Illuminate\Database\Query\JoinClause;
class AlbumBuilder extends Builder
{
public function isStandard(): static
public function isStandard(): self
{
return $this->whereNot('albums.id', Album::UNKNOWN_ID);
}
public function accessibleBy(User $user): static
public function accessibleBy(User $user): self
{
if (License::isCommunity()) {
// With the Community license, all albums are accessible by all users.

View file

@ -10,12 +10,12 @@ use Illuminate\Database\Query\JoinClause;
class ArtistBuilder extends Builder
{
public function isStandard(): static
public function isStandard(): self
{
return $this->whereNotIn('artists.id', [Artist::UNKNOWN_ID, Artist::VARIOUS_ID]);
}
public function accessibleBy(User $user): static
public function accessibleBy(User $user): self
{
if (License::isCommunity()) {
// With the Community license, all artists are accessible by all users.

View file

@ -32,7 +32,7 @@ class SongBuilder extends Builder
'albums.name',
];
public function inDirectory(string $path): static
public function inDirectory(string $path): self
{
// Make sure the path ends with a directory separator.
$path = rtrim(trim($path), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
@ -40,7 +40,7 @@ class SongBuilder extends Builder
return $this->where('path', 'LIKE', "$path%");
}
public function withMetaFor(User $user, bool $requiresInteractions = false): static
public function withMetaFor(User $user, bool $requiresInteractions = false): self
{
$joinClosure = static function (JoinClause $join) use ($user): void {
$join->on('interactions.song_id', '=', 'songs.id')->where('interactions.user_id', $user->id);
@ -65,7 +65,7 @@ class SongBuilder extends Builder
);
}
public function accessibleBy(User $user, bool $withTableName = true): static
public function accessibleBy(User $user, bool $withTableName = true): self
{
if (License::isCommunity()) {
// In the Community Edition, all songs are accessible by all users.
@ -78,7 +78,7 @@ class SongBuilder extends Builder
});
}
public function sort(string $column, string $direction): static
public function sort(string $column, string $direction): self
{
$column = self::normalizeSortColumn($column);
@ -105,7 +105,7 @@ class SongBuilder extends Builder
: $column;
}
public function storedOnCloud(): static
public function storedOnCloud(): self
{
return $this->whereNotNull('storage')
->where('storage', '!=', '');

View file

@ -14,7 +14,7 @@ class AuthController extends Controller
{
use ThrottlesLogins;
public function __construct(private AuthenticationService $auth)
public function __construct(private readonly AuthenticationService $auth)
{
}
@ -33,7 +33,7 @@ class AuthController extends Controller
}
}
public function logout(Request $request)
public function logout(Request $request): Response
{
attempt(fn () => $this->auth->logoutViaBearerToken($request->bearerToken()));

View file

@ -9,7 +9,7 @@ use Illuminate\Contracts\Auth\Authenticatable;
class GetOneTimeTokenController extends Controller
{
/** @var User $user */
/** @param User $user */
public function __invoke(AuthenticationService $auth, Authenticatable $user)
{
return response()->json(['token' => $auth->generateOneTimeToken($user)]);

View file

@ -18,7 +18,7 @@ class LikeMultipleSongsController extends Controller
InteractionService $interactionService,
Authenticatable $user
) {
/** @var Collection|array<array-key, Song> $songs */
/** @var Collection<array-key, Song> $songs */
$songs = Song::query()->findMany($request->songs);
$songs->each(fn (Song $song) => $this->authorize('access', $song));

View file

@ -23,7 +23,7 @@ use Laravel\Scout\Searchable;
* @property string $name Name of the album
* @property Artist $artist The album's artist
* @property int $artist_id
* @property Collection $songs
* @property Collection<array-key, Song> $songs
* @property bool $is_unknown If the album is the Unknown Album
* @property string|null $thumbnail_name The file name of the album's thumbnail
* @property string|null $thumbnail_path The full path to the thumbnail.

View file

@ -21,7 +21,7 @@ use Laravel\Scout\Searchable;
* @property string|null $image Public URL to the artist's image
* @property bool $is_unknown If the artist is Unknown Artist
* @property bool $is_various If the artist is Various Artist
* @property Collection $songs
* @property Collection<array-key, Song> $songs
* @property bool $has_image If the artist has a (non-default) image
* @property string|null $image_path Absolute path to the artist's image
* @property float|string $length Total length of the artist's songs in seconds (dynamically calculated)
@ -29,7 +29,7 @@ use Laravel\Scout\Searchable;
* @property string|int $song_count Total number of songs by the artist (dynamically calculated)
* @property string|int $album_count Total number of albums by the artist (dynamically calculated)
* @property Carbon $created_at
* @property Collection|array<array-key, Album> $albums
* @property Collection<array-key, Album> $albums
*/
class Artist extends Model
{

View file

@ -24,17 +24,17 @@ use LogicException;
* @property bool $is_smart
* @property int $user_id
* @property User $user
* @property Collection|array<array-key, Song> $songs
* @property Collection<array-key, Song> $songs
* @property array<string> $song_ids
* @property ?SmartPlaylistRuleGroupCollection $rule_groups
* @property ?SmartPlaylistRuleGroupCollection $rules
* @property Carbon $created_at
* @property bool $own_songs_only
* @property Collection|array<array-key, User> $collaborators
* @property Collection<array-key, User> $collaborators
* @property-read bool $is_collaborative
* @property-read ?string $cover The playlist cover's URL
* @property-read ?string $cover_path
* @property-read Collection|array<array-key, PlaylistFolder> $folders
* @property-read Collection<array-key, PlaylistFolder> $folders
*/
class Playlist extends Model
{
@ -180,7 +180,7 @@ class Playlist extends Model
}
/**
* @param Collection|array<array-key, Song>|Song|array<string> $songs
* @param Collection<array-key, Song>|Song|array<string> $songs
*/
public function removeSongs(Collection|Song|array $songs): void
{

View file

@ -14,7 +14,7 @@ use Illuminate\Support\Str;
* @property string $id
* @property string $name
* @property User $user
* @property Collection|array<array-key, Playlist> $playlists
* @property Collection<array-key, Playlist> $playlists
* @property int $user_id
* @property Carbon $created_at
*/

View file

@ -28,15 +28,15 @@ use Laravel\Sanctum\PersonalAccessToken;
* @property string $password
* @property-read bool $has_custom_avatar
* @property-read string $avatar
* @property Collection|array<array-key, Playlist> $playlists
* @property Collection|array<array-key, PlaylistFolder> $playlist_folders
* @property Collection<array-key, Playlist> $playlists
* @property Collection<array-key, PlaylistFolder> $playlist_folders
* @property PersonalAccessToken $currentAccessToken
* @property ?Carbon $invitation_accepted_at
* @property ?User $invitedBy
* @property ?string $invitation_token
* @property ?Carbon $invited_at
* @property-read bool $is_prospect
* @property Collection|array<array-key, Playlist> $collaboratedPlaylists
* @property Collection<array-key, Playlist> $collaboratedPlaylists
* @property ?string $sso_provider
* @property ?string $sso_id
* @property bool $is_sso

View file

@ -13,7 +13,6 @@ use Illuminate\Database\Schema\Builder;
use Illuminate\Database\SQLiteConnection;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Factory as Validator;
use SpotifyWebAPI\Session as SpotifySession;
class AppServiceProvider extends ServiceProvider
@ -21,14 +20,14 @@ class AppServiceProvider extends ServiceProvider
/**
* Bootstrap any application services.
*/
public function boot(Builder $schema, DatabaseManager $db, Validator $validator): void
public function boot(Builder $schema, DatabaseManager $db): void
{
// Fix utf8mb4-related error starting from Laravel 5.4
$schema->defaultStringLength(191);
// Enable on delete cascade for sqlite connections
if ($db->connection() instanceof SQLiteConnection) {
$db->statement($db->raw('PRAGMA foreign_keys = ON'));
$db->statement($db->raw('PRAGMA foreign_keys = ON')->getValue($db->getQueryGrammar()));
}
// disable wrapping JSON resource in a `data` key

View file

@ -9,6 +9,7 @@ use App\Models\User;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Support\Collection as BaseCollection;
class ArtistRepository extends Repository
{
@ -43,7 +44,7 @@ class ArtistRepository extends Repository
}
/** @return Collection|array<array-key, Artist> */
public function getMany(array $ids, bool $inThatOrder = false, ?User $user = null): Collection
public function getMany(array $ids, bool $inThatOrder = false, ?User $user = null): Collection | BaseCollection
{
$artists = Artist::query()
->isStandard()

View file

@ -11,9 +11,9 @@ interface RepositoryInterface
public function findOne($id): ?Model;
/** @return Collection|array<Model> */
/** @return Collection<Model> */
public function getMany(array $ids, bool $inThatOrder = false): Collection;
/** @return Collection|array<Model> */
/** @return Collection<Model> */
public function getAll(): Collection;
}

View file

@ -19,10 +19,10 @@ class GenreRepository
->groupBy('genre')
->orderBy('genre')
->get()
->transform(static fn (object $record): Genre => Genre::make(
name: $record->genre ?: Genre::NO_GENRE,
songCount: $record->song_count,
length: $record->length
->transform(static fn (object $record): Genre => Genre::make( // @phpstan-ignore-line
name: $record->genre ?: Genre::NO_GENRE, // @phpstan-ignore-line
songCount: $record->song_count, // @phpstan-ignore-line
length: $record->length // @phpstan-ignore-line
));
}

View file

@ -9,7 +9,7 @@ use Illuminate\Support\Collection;
class PlaylistRepository extends Repository
{
/** @return array<array-key, Playlist>|Collection<Playlist> */
/** @return Collection<array-key, Playlist> */
public function getAllAccessibleByUser(User $user): Collection
{
$ownPlaylists = Playlist::query()

View file

@ -38,7 +38,7 @@ abstract class Repository implements RepositoryInterface
return $this->model->find($id);
}
/** @return Collection|array<array-key, Model> */
/** @return Collection<array-key, Model> */
public function getMany(array $ids, bool $inThatOrder = false): Collection
{
$models = $this->model::query()->find($ids);
@ -46,7 +46,7 @@ abstract class Repository implements RepositoryInterface
return $inThatOrder ? $models->orderByArray($ids) : $models;
}
/** @return Collection|array<array-key, Model> */
/** @return Collection<array-key, Model> */
public function getAll(): Collection
{
return $this->model->all();

View file

@ -50,9 +50,9 @@ class InteractionService
/**
* Like several songs at once as a user.
*
* @param array<array-key, Song>|Collection $songs
* @param Collection<array-key, Song> $songs
*
* @return array<Interaction>|Collection The array of Interaction objects
* @return Collection<array-key, Interaction> The array of Interaction objects
*/
public function likeMany(Collection $songs, User $user): Collection
{

View file

@ -83,7 +83,7 @@ class LastfmService implements MusicEncyclopedia
}
/**
* @param Collection|array<array-key, Song> $songs
* @param Collection<array-key, Song> $songs
*/
public function batchToggleLoveTracks(Collection $songs, User $user, bool $love): void
{

View file

@ -4,7 +4,6 @@ namespace App\Services;
use App\Models\Album;
use App\Models\Artist;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\DB;
class LibraryManager
@ -12,10 +11,7 @@ class LibraryManager
/**
* Delete albums and artists that have no songs.
*
* @return array{
* albums: Collection<array-key, Album>,
* artists: Collection<array-key, Artist>,
* }
* @return array<mixed>
*/
public function prune(bool $dryRun = false): array
{

View file

@ -49,7 +49,7 @@ class PlaylistCollaborationService
return $collaborationToken->playlist;
}
/** @return Collection|array<array-key, PlaylistCollaborator> */
/** @return Collection<array-key, PlaylistCollaborator> */
public function getCollaborators(Playlist $playlist): Collection
{
return $playlist->collaborators->unless(

View file

@ -86,7 +86,7 @@ class PlaylistService
return $playlist;
}
/** @return Collection<Song>|array<array-key, Song> */
/** @return Collection<array-key, Song> */
public function addSongsToPlaylist(Playlist $playlist, Collection|Song|array $songs, User $user): Collection
{
return DB::transaction(function () use ($playlist, $songs, $user) {

View file

@ -24,7 +24,7 @@ class SongService
) {
}
/** @return Collection|array<array-key, Song> */
/** @return Collection<array-key, Song> */
public function updateSongs(array $ids, SongUpdateData $data): Collection
{
if (count($ids) === 1) {
@ -96,7 +96,7 @@ class SongService
{
if (License::isPlus()) {
/**
* @var Collection|array<array-key, Song> $collaborativeSongs
* @var Collection<array-key, Song> $collaborativeSongs
* Songs that are in collaborative playlists and can't be marked as private as a result
*/
$collaborativeSongs = Song::query()
@ -128,7 +128,7 @@ class SongService
DB::transaction(function () use ($ids): void {
$shouldBackUp = config('koel.backup_on_delete');
/** @var Collection|array<array-key, Song> $songs */
/** @var Collection<array-key, Song> $songs */
$songs = Song::query()->findMany($ids);
Song::destroy($ids);

View file

@ -17,8 +17,8 @@ final class DropboxStorage extends CloudStorage
{
public function __construct(
protected FileScanner $scanner,
private DropboxFilesystem $filesystem,
private array $config
private readonly DropboxFilesystem $filesystem,
private readonly array $config
) {
parent::__construct($scanner);
@ -78,7 +78,7 @@ final class DropboxStorage extends CloudStorage
return $this->filesystem->temporaryUrl($song->storage_metadata->getPath());
}
protected function supported(): bool
public function supported(): bool
{
return SongStorageTypes::supported(SongStorageTypes::DROPBOX);
}

View file

@ -67,7 +67,7 @@ class S3CompatibleStorage extends CloudStorage
Storage::disk('s3')->delete('test.txt');
}
protected function supported(): bool
public function supported(): bool
{
return SongStorageTypes::supported(SongStorageTypes::S3);
}

View file

@ -87,7 +87,7 @@ final class S3LambdaStorage extends S3CompatibleStorage
$song->delete();
}
protected function supported(): bool
public function supported(): bool
{
return SongStorageTypes::supported(SongStorageTypes::S3_LAMBDA);
}

View file

@ -18,7 +18,7 @@ class UserInvitationService
{
}
/** @return Collection|array<array-key, User> */
/** @return Collection<array-key, User> */
public function invite(array $emails, bool $isAdmin, User $invitor): Collection
{
return DB::transaction(function () use ($emails, $isAdmin, $invitor) {

View file

@ -11,25 +11,25 @@ final class ScanResultCollection extends Collection
return new self();
}
/** @return Collection|array<array-key, ScanResult> */
/** @return Collection<array-key, ScanResult> */
public function valid(): Collection
{
return $this->filter(static fn (ScanResult $result): bool => $result->isValid());
}
/** @return Collection|array<array-key, ScanResult> */
/** @return Collection<array-key, ScanResult> */
public function success(): Collection
{
return $this->filter(static fn (ScanResult $result): bool => $result->isSuccess());
}
/** @return Collection|array<array-key, ScanResult> */
/** @return Collection<array-key, ScanResult> */
public function skipped(): Collection
{
return $this->filter(static fn (ScanResult $result): bool => $result->isSkipped());
}
/** @return Collection|array<array-key, ScanResult> */
/** @return Collection<array-key, ScanResult> */
public function error(): Collection
{
return $this->filter(static fn (ScanResult $result): bool => $result->isError());

View file

@ -1,5 +1,5 @@
{
"name": "phanan/koel",
"name": "koel/koel",
"description": "Personal audio streaming service that works.",
"keywords": [
"audio",
@ -9,29 +9,28 @@
"license": "MIT",
"type": "project",
"require": {
"php": ">=8.0",
"laravel/framework": "^9.0",
"php": ">=8.1",
"laravel/framework": "^10.0",
"james-heinrich/getid3": "^1.9",
"guzzlehttp/guzzle": "^7.0.1",
"pusher/pusher-php-server": "^4.0",
"pusher/pusher-php-server": "^7.0",
"predis/predis": "~1.0",
"jackiedo/dotenv-editor": "^2.0",
"jackiedo/dotenv-editor": "^2.1",
"ext-exif": "*",
"ext-gd": "*",
"ext-fileinfo": "*",
"ext-json": "*",
"ext-SimpleXML": "*",
"daverandom/resume": "^0.0.3",
"laravel/helpers": "^1.0",
"laravel/helpers": "^1.7",
"intervention/image": "^2.5",
"doctrine/dbal": "^3.0",
"lstrojny/functional-php": "^1.14",
"teamtnt/laravel-scout-tntsearch-driver": "^11.1",
"teamtnt/laravel-scout-tntsearch-driver": "^14.0",
"algolia/algoliasearch-client-php": "^3.3",
"laravel/ui": "^3.2",
"webmozart/assert": "^1.10",
"laravel/sanctum": "^2.15",
"laravel/scout": "^9.4",
"laravel/sanctum": "^3.2",
"laravel/scout": "^10.0",
"nunomaduro/collision": "^6.2",
"jwilsson/spotify-web-api-php": "^5.2",
"meilisearch/meilisearch-php": "^0.24.0",
@ -40,7 +39,8 @@
"spatie/flysystem-dropbox": "^3.0",
"saloonphp/saloon": "^3.8",
"saloonphp/laravel-plugin": "^3.0",
"laravel/socialite": "^5.12"
"laravel/socialite": "^5.12",
"laravel/ui": "^4.5"
},
"require-dev": {
"mockery/mockery": "~1.0",
@ -49,8 +49,8 @@
"dms/phpunit-arraysubset-asserts": "^0.2.1",
"fakerphp/faker": "^1.13",
"slevomat/coding-standard": "^7.0",
"nunomaduro/larastan": "^2.1",
"laravel/tinker": "^2.7"
"laravel/tinker": "^2.9",
"larastan/larastan": "^2.9"
},
"suggest": {
"ext-zip": "Allow downloading multiple songs as Zip archives"

3356
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,7 @@ class CopyArtistToContributingArtist extends Migration
{
public function up(): void
{
/** @var Collection|array<array-key, Song> $songs */
/** @var Collection<array-key, Song> $songs */
$songs = Song::with('album', 'album.artist')->get();
$songs->each(static function (Song $song): void {

View file

@ -0,0 +1,15 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('personal_access_tokens', static function (Blueprint $table): void {
$table->timestamp('expires_at')->nullable();
});
}
};

View file

@ -1,5 +1,5 @@
includes:
- ./vendor/nunomaduro/larastan/extension.neon
- ./vendor/larastan/larastan/extension.neon
parameters:
paths:

View file

@ -34,8 +34,8 @@
</a>
</li>
</ul>
<ul v-else class="genres">
<li v-for="i in 20" :key="i">
<ul v-else class="text-center">
<li v-for="i in 20" :key="i" class="inline-block">
<GenreItemSkeleton />
</li>
</ul>

View file

@ -3,10 +3,12 @@
namespace Tests\Feature;
use App\Events\MultipleSongsLiked;
use App\Events\PlaybackStarted;
use App\Events\SongLikeToggled;
use App\Models\Interaction;
use App\Models\Song;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Event;
use Tests\TestCase;
use function Tests\create_user;
@ -15,7 +17,7 @@ class InteractionTest extends TestCase
{
public function testIncreasePlayCount(): void
{
$this->withoutEvents();
Event::fake(PlaybackStarted::class);
$user = create_user();
@ -41,7 +43,7 @@ class InteractionTest extends TestCase
public function testToggleLike(): void
{
$this->expectsEvents(SongLikeToggled::class);
Event::fake(SongLikeToggled::class);
$user = create_user();
@ -63,15 +65,17 @@ class InteractionTest extends TestCase
'song_id' => $song->id,
'liked' => 0,
]);
Event::assertDispatched(SongLikeToggled::class);
}
public function testToggleLikeBatch(): void
{
$this->expectsEvents(MultipleSongsLiked::class);
Event::fake(MultipleSongsLiked::class);
$user = create_user();
/** @var Collection|array<Song> $songs */
/** @var Collection<Song> $songs */
$songs = Song::factory(2)->create();
$songIds = $songs->pluck('id')->all();
@ -94,5 +98,7 @@ class InteractionTest extends TestCase
'liked' => 0,
]);
}
Event::assertDispatched(MultipleSongsLiked::class);
}
}

View file

@ -2,8 +2,12 @@
namespace Tests\Feature\KoelPlus;
use App\Events\MultipleSongsLiked;
use App\Events\MultipleSongsUnliked;
use App\Events\SongLikeToggled;
use App\Models\Song;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Event;
use Tests\PlusTestCase;
use function Tests\create_user;
@ -12,7 +16,7 @@ class InteractionTest extends PlusTestCase
{
public function testPolicyForRegisterPlay(): void
{
$this->withoutEvents();
Event::fake(SongLikeToggled::class);
$owner = create_user();
@ -37,7 +41,7 @@ class InteractionTest extends PlusTestCase
public function testPolicyForToggleLike(): void
{
$this->withoutEvents();
Event::fake(SongLikeToggled::class);
$owner = create_user();
@ -62,7 +66,7 @@ class InteractionTest extends PlusTestCase
public function testPolicyForBatchLike(): void
{
$this->withoutEvents();
Event::fake(MultipleSongsLiked::class);
$owner = create_user();
@ -92,7 +96,7 @@ class InteractionTest extends PlusTestCase
public function testPolicyForBatchUnlike(): void
{
$this->withoutEvents();
Event::fake(MultipleSongsUnliked::class);
$owner = create_user();

View file

@ -16,7 +16,7 @@ class SongTest extends PlusTestCase
Song::factory(2)->public()->create();
/** @var Collection<Song>|array<array-key, Song> $ownSongs */
/** @var Collection<array-key, Song> $ownSongs */
$ownSongs = Song::factory(3)->for($user, 'owner')->create();
$this->getAs('api/songs?own_songs_only=true', $user)

View file

@ -62,7 +62,7 @@ class PlaylistSongTest extends TestCase
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create();
/** @var Collection|array<array-key, Song> $songs */
/** @var Collection<array-key, Song> $songs */
$songs = Song::factory(2)->create();
$this->postAs("api/playlists/$playlist->id/songs", ['songs' => $songs->pluck('id')->all()], $playlist->user)
@ -78,7 +78,7 @@ class PlaylistSongTest extends TestCase
$toRemainSongs = Song::factory(5)->create();
/** @var Collection|array<array-key, Song> $toBeRemovedSongs */
/** @var Collection<array-key, Song> $toBeRemovedSongs */
$toBeRemovedSongs = Song::factory(2)->create();
$playlist->addSongs($toRemainSongs->merge($toBeRemovedSongs));

View file

@ -14,8 +14,6 @@ class ScrobbleTest extends TestCase
{
public function testLastfmScrobble(): void
{
$this->withoutEvents();
$user = create_user();
/** @var Song $song */

View file

@ -31,7 +31,7 @@ class SongTest extends TestCase
public function testDelete(): void
{
/** @var Collection|array<array-key, Song> $songs */
/** @var Collection<array-key, Song> $songs */
$songs = Song::factory(3)->create();
$this->deleteAs('api/songs', ['songs' => $songs->pluck('id')->all()], create_admin())
@ -42,7 +42,7 @@ class SongTest extends TestCase
public function testUnauthorizedDelete(): void
{
/** @var Collection|array<array-key, Song> $songs */
/** @var Collection<array-key, Song> $songs */
$songs = Song::factory(3)->create();
$this->deleteAs('api/songs', ['songs' => $songs->pluck('id')->all()])
@ -128,7 +128,7 @@ class SongTest extends TestCase
], create_admin())
->assertOk();
/** @var Collection|array<array-key, Song> $songs */
/** @var Collection<array-key, Song> $songs */
$songs = Song::query()->whereIn('id', $songIds)->get();
// All of these songs must now belong to a new album and artist set
@ -150,7 +150,7 @@ class SongTest extends TestCase
public function testMultipleUpdateCreatingNewAlbumsAndArtists(): void
{
/** @var array<array-key, Song>|Collection $originalSongs */
/** @var Collection<array-key, Song> $originalSongs */
$originalSongs = Song::factory(3)->create();
$originalSongIds = $originalSongs->pluck('id')->all();
$originalAlbumNames = $originalSongs->pluck('album.name')->all();
@ -168,7 +168,7 @@ class SongTest extends TestCase
], create_admin())
->assertOk();
/** @var array<array-key, Song>|Collection $songs */
/** @var Collection<array-key, Song> $songs */
$songs = Song::query()->whereIn('id', $originalSongIds)->get()->orderByArray($originalSongIds);
// Even though the album name doesn't change, a new artist should have been created

View file

@ -10,6 +10,7 @@ use App\Exceptions\PlaylistCollaborationTokenExpiredException;
use App\Models\Playlist;
use App\Models\PlaylistCollaborationToken;
use App\Services\PlaylistCollaborationService;
use Illuminate\Support\Facades\Event;
use Tests\PlusTestCase;
use function Tests\create_user;
@ -49,7 +50,7 @@ class PlaylistCollaborationServiceTest extends PlusTestCase
public function testAcceptUsingToken(): void
{
$this->expectsEvents(NewPlaylistCollaboratorJoined::class);
Event::fake(NewPlaylistCollaboratorJoined::class);
/** @var PlaylistCollaborationToken $token */
$token = PlaylistCollaborationToken::factory()->create();
@ -59,12 +60,13 @@ class PlaylistCollaborationServiceTest extends PlusTestCase
$this->service->acceptUsingToken($token->token, $user);
self::assertTrue($token->refresh()->playlist->collaborators->contains($user));
Event::assertDispatched(NewPlaylistCollaboratorJoined::class);
}
public function testFailsToAcceptExpiredToken(): void
{
$this->expectException(PlaylistCollaborationTokenExpiredException::class);
$this->doesntExpectEvents(NewPlaylistCollaboratorJoined::class);
Event::fake(NewPlaylistCollaboratorJoined::class);
/** @var PlaylistCollaborationToken $token */
$token = PlaylistCollaborationToken::factory()->create();
@ -75,6 +77,7 @@ class PlaylistCollaborationServiceTest extends PlusTestCase
$this->service->acceptUsingToken($token->token, $user);
self::assertFalse($token->refresh()->playlist->collaborators->contains($user));
Event::assertNotDispatched(NewPlaylistCollaboratorJoined::class);
}
public function testGetCollaborators(): void

View file

@ -36,7 +36,7 @@ class DeleteNonExistingRecordsPostSyncTest extends TestCase
public function testHandle(): void
{
/** @var Collection|array<Song> $songs */
/** @var Collection|array<array-key, Song> $songs */
$songs = Song::factory(4)->create();
self::assertCount(4, Song::all());

View file

@ -9,6 +9,7 @@ use App\Models\Interaction;
use App\Models\Song;
use App\Services\InteractionService;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Event;
use Tests\TestCase;
use function Tests\create_user;
@ -36,7 +37,7 @@ class InteractionServiceTest extends TestCase
public function testToggleLike(): void
{
$this->expectsEvents(SongLikeToggled::class);
Event::fake(SongLikeToggled::class);
/** @var Interaction $interaction */
$interaction = Interaction::factory()->create();
@ -45,11 +46,12 @@ class InteractionServiceTest extends TestCase
$this->interactionService->toggleLike($interaction->song, $interaction->user);
self::assertNotSame($currentLiked, $interaction->refresh()->liked);
Event::assertDispatched(SongLikeToggled::class);
}
public function testLikeMultipleSongs(): void
{
$this->expectsEvents(MultipleSongsLiked::class);
Event::fake(MultipleSongsLiked::class);
/** @var Collection $songs */
$songs = Song::factory(2)->create();
@ -66,11 +68,13 @@ class InteractionServiceTest extends TestCase
self::assertTrue($interaction->liked);
});
Event::assertDispatched(MultipleSongsLiked::class);
}
public function testUnlikeMultipleSongs(): void
{
$this->expectsEvents(MultipleSongsUnliked::class);
Event::fake(MultipleSongsUnliked::class);
$user = create_user();
/** @var Collection $interactions */
@ -81,5 +85,7 @@ class InteractionServiceTest extends TestCase
$interactions->each(static function (Interaction $interaction): void {
self::assertFalse($interaction->refresh()->liked);
});
Event::assertDispatched(MultipleSongsUnliked::class);
}
}

View file

@ -13,6 +13,7 @@ use App\Values\ScanConfiguration;
use App\Values\WatchRecord\InotifyWatchRecord;
use getID3;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Event;
use Mockery;
use Tests\TestCase;
@ -37,11 +38,13 @@ class MediaScannerTest extends TestCase
public function testScan(): void
{
$this->expectsEvents(MediaScanCompleted::class);
Event::fake(MediaScanCompleted::class);
$owner = create_admin();
$this->scanner->scan(ScanConfiguration::make(owner: $owner));
Event::assertDispatched(MediaScanCompleted::class);
// Standard mp3 files under root path should be recognized
self::assertDatabaseHas(Song::class, [
'path' => $this->path('/full.mp3'),
@ -92,8 +95,6 @@ class MediaScannerTest extends TestCase
public function testModifiedFileIsRescanned(): void
{
$this->expectsEvents(MediaScanCompleted::class);
$config = ScanConfiguration::make(owner: create_admin());
$this->scanner->scan($config);
@ -108,7 +109,7 @@ class MediaScannerTest extends TestCase
public function testRescanWithoutForceDoesNotResetData(): void
{
$this->expectsEvents(MediaScanCompleted::class);
Event::fake(MediaScanCompleted::class);
$config = ScanConfiguration::make(owner: create_admin());
@ -131,7 +132,7 @@ class MediaScannerTest extends TestCase
public function testForceScanResetsData(): void
{
$this->expectsEvents(MediaScanCompleted::class);
Event::fake(MediaScanCompleted::class);
$owner = create_admin();
$this->scanner->scan(ScanConfiguration::make(owner: $owner));
@ -156,7 +157,7 @@ class MediaScannerTest extends TestCase
public function testScanWithIgnoredTags(): void
{
$this->expectsEvents(MediaScanCompleted::class);
Event::fake(MediaScanCompleted::class);
$owner = create_admin();
$this->scanner->scan(ScanConfiguration::make(owner: $owner));
@ -179,7 +180,7 @@ class MediaScannerTest extends TestCase
public function testScanAllTagsForNewFilesRegardlessOfIgnoredOption(): void
{
$this->expectsEvents(MediaScanCompleted::class);
Event::fake(MediaScanCompleted::class);
$owner = create_admin();
$this->scanner->scan(ScanConfiguration::make(owner: $owner));
@ -229,7 +230,7 @@ class MediaScannerTest extends TestCase
public function testScanDeletedDirectoryViaWatch(): void
{
$this->expectsEvents(MediaScanCompleted::class);
Event::fake(MediaScanCompleted::class);
$config = ScanConfiguration::make(owner: create_admin());

View file

@ -39,7 +39,7 @@ class PlaylistServiceTest extends TestCase
public function testCreatePlaylistWithSongs(): void
{
/** @var array<array-key, Song>|Collection $songs */
/** @var Collection<array-key, Song> $songs */
$songs = Song::factory(3)->create();
$user = create_user();
@ -239,7 +239,7 @@ class PlaylistServiceTest extends TestCase
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create();
/** @var Collection|array<array-key, Song> $songs */
/** @var Collection<array-key, Song> $songs */
$songs = Song::factory(4)->create();
$ids = $songs->pluck('id')->all();
$playlist->addSongs($songs);

View file

@ -47,7 +47,7 @@ class PlaylistFolderServiceTest extends TestCase
{
$user = create_user();
/** @var Collection|array<array-key, Playlist> $playlists */
/** @var Collection<array-key, Playlist> $playlists */
$playlists = Playlist::factory()->for($user)->count(3)->create();
/** @var PlaylistFolder $folder */
@ -63,7 +63,7 @@ class PlaylistFolderServiceTest extends TestCase
/** @var PlaylistFolder $folder */
$folder = PlaylistFolder::factory()->create();
/** @var Collection|array<array-key, Playlist> $playlists */
/** @var Collection<array-key, Playlist> $playlists */
$playlists = Playlist::factory()->count(3)->create();
$folder->playlists()->attach($playlists->pluck('id')->all());

View file

@ -3,6 +3,7 @@
namespace Tests\Unit\Services\SongStorages;
use App\Exceptions\KoelPlusRequiredException;
use App\Models\Song;
use App\Services\SongStorages\DropboxStorage;
use Tests\TestCase;
@ -11,6 +12,12 @@ class DropboxStorageTest extends TestCase
public function testSupported(): void
{
$this->expectException(KoelPlusRequiredException::class);
app(DropboxStorage::class);
/** @var Song $song */
$song = Song::factory()->create();
/** @var DropboxStorage $service */
$service = app(DropboxStorage::class);
$service->getSongPresignedUrl($song);
}
}

View file

@ -3,6 +3,7 @@
namespace Tests\Unit\Services\SongStorages;
use App\Exceptions\KoelPlusRequiredException;
use App\Models\Song;
use App\Services\SongStorages\S3CompatibleStorage;
use Tests\TestCase;
@ -11,6 +12,12 @@ class S3CompatibleStorageTest extends TestCase
public function testSupported(): void
{
$this->expectException(KoelPlusRequiredException::class);
app(S3CompatibleStorage::class);
/** @var Song $song */
$song = Song::factory()->create();
/** @var S3CompatibleStorage $service */
$service = app(S3CompatibleStorage::class);
$service->getSongPresignedUrl($song);
}
}