refactor: avoid leadking database keys (#1874)

This commit is contained in:
Phan An 2024-11-09 15:56:48 +01:00 committed by GitHub
parent bcfbf31b35
commit aa7ddd9d94
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
76 changed files with 286 additions and 293 deletions

View file

@ -34,7 +34,7 @@ class ChangePasswordCommand extends Command
return self::FAILURE;
}
$this->comment("Changing the user's password (ID: $user->id, email: $user->email)");
$this->comment("Changing the user's password (ID: {$user->id}, email: $user->email)");
$user->password = $this->hash->make($this->askForPassword());
$user->save();

View file

@ -173,7 +173,7 @@ class ScanCommand extends Command
exit(self::INVALID);
});
$this->components->info("Setting owner to $user->name (ID $user->id).");
$this->components->info("Setting owner to $user->name (ID {$user->id}).");
return $user;
}
@ -181,7 +181,7 @@ class ScanCommand extends Command
$user = $this->userRepository->getDefaultAdminUser();
$this->components->warn(
"No song owner specified. Setting the first admin ($user->name, ID $user->id) as owner."
"No song owner specified. Setting the first admin ($user->name, ID {$user->id}) as owner."
);
return $user;

View file

@ -10,6 +10,6 @@ final class UserAlreadySubscribedToPodcast extends Exception
{
public static function make(User $user, Podcast $podcast): self
{
return new self("User $user->id has already subscribed to podcast $podcast->id");
return new self("User {$user->id} has already subscribed to podcast {$podcast->id}");
}
}

View file

@ -2,6 +2,7 @@
namespace App\Http\Controllers\API;
use App\Facades\License;
use App\Http\Controllers\Controller;
use App\Http\Requests\API\ChangeSongsVisibilityRequest;
use App\Models\Song;
@ -14,6 +15,8 @@ class PrivatizeSongsController extends Controller
/** @param User $user */
public function __invoke(ChangeSongsVisibilityRequest $request, SongService $songService, Authenticatable $user)
{
License::requirePlus();
$songs = Song::query()->findMany($request->songs);
$songs->each(fn ($song) => $this->authorize('own', $song));

View file

@ -2,6 +2,7 @@
namespace App\Http\Controllers\API;
use App\Facades\License;
use App\Http\Controllers\Controller;
use App\Http\Requests\API\ChangeSongsVisibilityRequest;
use App\Models\Song;
@ -14,6 +15,8 @@ class PublicizeSongsController extends Controller
/** @param User $user */
public function __invoke(ChangeSongsVisibilityRequest $request, SongService $songService, Authenticatable $user)
{
License::requirePlus();
$songs = Song::query()->findMany($request->songs);
$songs->each(fn ($song) => $this->authorize('own', $song));

View file

@ -15,7 +15,7 @@ class UploadAlbumCoverController extends Controller
$this->authorize('update', $album);
$metadataService->writeAlbumCover($album, $request->getFileContent());
Cache::delete("album.info.$album->id");
Cache::delete("album.info.{$album->id}");
return response()->json(['cover_url' => $album->cover]);
}

View file

@ -15,7 +15,7 @@ class UploadArtistImageController extends Controller
$this->authorize('update', $artist);
$metadataService->writeArtistImage($artist, $request->getFileContent());
Cache::delete("artist.info.$artist->id");
Cache::delete("artist.info.{$artist->id}");
return response()->json(['image_url' => $artist->image]);
}

View file

@ -18,7 +18,7 @@ class ProfileUpdateRequest extends Request
{
return [
'name' => 'required',
'email' => 'required|email|unique:users,email,' . auth()->user()->id,
'email' => 'required|email|unique:users,email,' . auth()->user()->getAuthIdentifier(),
'current_password' => 'sometimes|required_with:new_password',
'new_password' => ['sometimes', Password::defaults()],
];

View file

@ -19,7 +19,6 @@ use Laravel\Scout\Searchable;
* @property string $cover The album cover's URL
* @property string|null $cover_path The absolute path to the cover file
* @property bool $has_cover If the album has a non-default cover image
* @property int $id
* @property string $name Name of the album
* @property Artist $artist The album's artist
* @property int $artist_id

View file

@ -8,6 +8,7 @@ use App\Models\Song as Playable;
use App\Values\SmartPlaylistRuleGroupCollection;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
@ -24,16 +25,16 @@ use Laravel\Scout\Searchable;
* @property bool $is_smart
* @property int $user_id
* @property User $user
* @property Collection<array-key, Playable> $playables
* @property ?SmartPlaylistRuleGroupCollection $rule_groups
* @property ?SmartPlaylistRuleGroupCollection $rules
* @property Carbon $created_at
* @property EloquentCollection<array-key, Playable> $playables
* @property EloquentCollection<array-key, User> $collaborators
* @property bool $own_songs_only
* @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-key, PlaylistFolder> $folders
* @property-read EloquentCollection<array-key, PlaylistFolder> $folders
* @property-read bool $is_collaborative
*/
class Playlist extends Model
{
@ -107,7 +108,7 @@ class Playlist extends Model
public function ownedBy(User $user): bool
{
return $this->user_id === $user->id;
return $this->user->is($user);
}
public function inFolder(PlaylistFolder $folder): bool

View file

@ -11,7 +11,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
/**
* @property string $id
* @property string $name
* @property User $user
* @property Collection<array-key, Playlist> $playlists
@ -38,6 +37,6 @@ class PlaylistFolder extends Model
public function ownedBy(User $user): bool
{
return $this->user_id === $user->id;
return $this->user->is($user);
}
}

View file

@ -162,6 +162,7 @@ class Song extends Model
public function ownedBy(User $user): bool
{
// Do not use $song->owner->is($user) here, as it may trigger an extra query.
return $this->owner_id === $user->id;
}

View file

@ -92,7 +92,7 @@ class User extends Authenticatable
public function subscribedToPodcast(Podcast $podcast): bool
{
return $this->podcasts()->where('podcast_id', $podcast->id)->exists();
return $this->podcasts()->whereKey($podcast)->exists();
}
public function subscribeToPodcast(Podcast $podcast): void

View file

@ -10,6 +10,7 @@ class SongPolicy
{
public function own(User $user, Song $song): bool
{
// Do not use $song->owner->is($user) here, as it may trigger an extra query.
return $song->owner_id === $user->id;
}

View file

@ -14,7 +14,7 @@ class MacroProvider extends ServiceProvider
{
public function boot(): void
{
Collection::macro('orderByArray', function (array $orderBy, string $key = 'id'): Collection {
Collection::macro('orderByArray', function (array $orderBy, string $key = 'id') {
/** @var Collection $this */
return $this->sortBy(static fn ($item) => array_search($item->$key, $orderBy, true))->values();
});

View file

@ -8,8 +8,8 @@ use App\Models\Album;
use App\Models\Artist;
use App\Models\User;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Support\Collection;
/**
* @extends Repository<Album>

View file

@ -9,7 +9,6 @@ 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;
/** @extends Repository<Artist> */
class ArtistRepository extends Repository
@ -44,7 +43,7 @@ class ArtistRepository extends Repository
}
/** @return Collection|array<array-key, Artist> */
public function getMany(array $ids, bool $preserveOrder = false, ?User $user = null): Collection|BaseCollection
public function getMany(array $ids, bool $preserveOrder = false, ?User $user = null): Collection
{
$artists = Artist::query()
->isStandard()

View file

@ -2,9 +2,8 @@
namespace App\Repositories\Contracts;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
/** @template T of Model */
interface RepositoryInterface
@ -25,7 +24,7 @@ interface RepositoryInterface
public function getMany(array $ids, bool $preserveOrder = false): Collection;
/** @return Collection<int, T> */
public function getAll(): EloquentCollection;
public function getAll(): Collection;
/** @return T|null */
public function findFirstWhere(...$params): ?Model;

View file

@ -4,7 +4,7 @@ namespace App\Repositories;
use App\Models\Podcast;
use App\Models\User;
use Illuminate\Support\Collection;
use Illuminate\Database\Eloquent\Collection;
/** @extends Repository<Podcast> */
class PodcastRepository extends Repository

View file

@ -4,9 +4,8 @@ namespace App\Repositories;
use App\Repositories\Contracts\RepositoryInterface;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
/**
* @template T of Model
@ -68,9 +67,9 @@ abstract class Repository implements RepositoryInterface
}
/** @inheritDoc */ // @phpcs:ignore
public function getAll(): EloquentCollection
public function getAll(): Collection
{
return $this->modelClass::all();
return $this->modelClass::all(); // @phpstan-ignore-line
}
/** @inheritDoc */

View file

@ -13,7 +13,7 @@ use App\Models\Song;
use App\Models\User;
use App\Values\Genre;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Support\Collection;
use Illuminate\Database\Eloquent\Collection;
/** @extends Repository<Song> */
class SongRepository extends Repository
@ -78,7 +78,10 @@ class SongRepository extends Repository
return Song::query(type: PlayableType::SONG, user: $scopedUser)
->accessible()
->withMeta()
->when($ownSongsOnly, static fn (SongBuilder $query) => $query->where('songs.owner_id', $scopedUser->id))
->when(
$ownSongsOnly,
static fn (SongBuilder $query) => $query->where('songs.owner_id', $scopedUser->id)
)
->sort($sortColumns, $sortDirection)
->simplePaginate($perPage);
}
@ -129,7 +132,7 @@ class SongRepository extends Repository
return Song::query(user: $scopedUser ?? $this->auth->user())
->accessible()
->withMeta()
->where('album_id', $album->id)
->whereBelongsTo($album)
->orderBy('songs.disc')
->orderBy('songs.track')
->orderBy('songs.title')
@ -195,7 +198,7 @@ class SongRepository extends Repository
->whereIn('songs.id', $ids)
->get();
return $preserveOrder ? $songs->orderByArray($ids) : $songs;
return $preserveOrder ? $songs->orderByArray($ids) : $songs; // @phpstan-ignore-line
}
/**

View file

@ -22,7 +22,7 @@ final class AllPlaylistsAreAccessibleBy implements ValidationRule
$accessiblePlaylists = $accessiblePlaylists->merge($this->user->collaboratedPlaylists);
}
if (array_diff(Arr::wrap($value), $accessiblePlaylists->pluck('id')->toArray())) {
if (array_diff(Arr::wrap($value), $accessiblePlaylists->modelKeys())) {
$fail(
License::isPlus()
? 'Not all playlists are accessible by the user'

View file

@ -9,7 +9,7 @@ use App\Services\SongStorages\DropboxStorage;
use App\Services\SongStorages\S3CompatibleStorage;
use App\Services\SongStorages\SftpStorage;
use App\Values\Podcast\EpisodePlayable;
use Illuminate\Support\Collection;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\File;
class DownloadService
@ -17,7 +17,7 @@ class DownloadService
public function getDownloadablePath(Collection $songs): ?string
{
if ($songs->count() === 1) {
return $this->getLocalPath($songs->first());
return $this->getLocalPath($songs->first()); // @phpstan-ignore-line
}
return (new SongZipArchive())

View file

@ -8,7 +8,7 @@ use App\Events\SongLikeToggled;
use App\Models\Interaction;
use App\Models\Song as Playable;
use App\Models\User;
use Illuminate\Support\Collection;
use Illuminate\Database\Eloquent\Collection;
class InteractionService
{
@ -57,14 +57,20 @@ class InteractionService
public function likeMany(Collection $playables, User $user): Collection
{
$interactions = $playables->map(static function (Playable $playable) use ($user): Interaction {
return tap(Interaction::query()->firstOrCreate([
'song_id' => $playable->id,
'user_id' => $user->id,
]), static function (Interaction $interaction): void {
$interaction->play_count ??= 0;
$interaction->liked = true;
$interaction->save();
});
$interaction = Interaction::query()->whereBelongsTo($playable)->whereBelongsTo($user)->first();
if ($interaction) {
$interaction->update(['liked' => true]);
} else {
$interaction = Interaction::query()->create([
'song_id' => $playable->id,
'user_id' => $user->id,
'play_count' => 0,
'liked' => true,
]);
}
return $interaction;
});
event(new MultipleSongsLiked($playables, $user));
@ -80,8 +86,8 @@ class InteractionService
public function unlikeMany(Collection $playables, User $user): void
{
Interaction::query()
->whereIn('song_id', $playables->pluck('id')->all())
->where('user_id', $user->id)
->whereBelongsTo($playables)
->whereBelongsTo($user)
->update(['liked' => false]);
event(new MultipleSongsUnliked($playables, $user));

View file

@ -50,7 +50,7 @@ class LastfmService implements MusicEncyclopedia
return rescue_if(static::enabled(), function () use ($artist): ?ArtistInformation {
return Cache::remember(
"lastfm.artist.$artist->id",
"lastfm.artist.{$artist->id}",
now()->addWeek(),
fn () => $this->connector->send(new GetArtistInfoRequest($artist))->dto()
);
@ -65,7 +65,7 @@ class LastfmService implements MusicEncyclopedia
return rescue_if(static::enabled(), function () use ($album): ?AlbumInformation {
return Cache::remember(
"lastfm.album.$album->id",
"lastfm.album.{$album->id}",
now()->addWeek(),
fn () => $this->connector->send(new GetAlbumInfoRequest($album))->dto()
);

View file

@ -23,16 +23,20 @@ class MediaInformationService
return null;
}
return Cache::remember("album.info.$album->id", now()->addWeek(), function () use ($album): AlbumInformation {
$info = $this->encyclopedia->getAlbumInformation($album) ?: AlbumInformation::make();
return Cache::remember(
"album.info.{$album->id}",
now()->addWeek(),
function () use ($album): AlbumInformation {
$info = $this->encyclopedia->getAlbumInformation($album) ?: AlbumInformation::make();
rescue_unless($album->has_cover, function () use ($info, $album): void {
$this->mediaMetadataService->tryDownloadAlbumCover($album);
$info->cover = $album->cover;
});
rescue_unless($album->has_cover, function () use ($info, $album): void {
$this->mediaMetadataService->tryDownloadAlbumCover($album);
$info->cover = $album->cover;
});
return $info;
});
return $info;
}
);
}
public function getArtistInformation(Artist $artist): ?ArtistInformation
@ -42,7 +46,7 @@ class MediaInformationService
}
return Cache::remember(
"artist.info.$artist->id",
"artist.info.{$artist->id}",
now()->addWeek(),
function () use ($artist): ArtistInformation {
$info = $this->encyclopedia->getArtistInformation($artist) ?: ArtistInformation::make();

View file

@ -11,6 +11,7 @@ use App\Models\Song as Playable;
use App\Models\User;
use App\Repositories\SongRepository;
use App\Values\SmartPlaylistRuleGroupCollection;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use InvalidArgumentException;
@ -85,12 +86,12 @@ class PlaylistService
return $playlist;
}
/** @return Collection<array-key, Playable> */
/** @return EloquentCollection<array-key, Playable> */
public function addPlayablesToPlaylist(
Playlist $playlist,
Collection|Playable|array $playables,
User $user
): Collection {
): EloquentCollection {
return DB::transaction(function () use ($playlist, $playables, $user) {
$playables = Collection::wrap($playables);

View file

@ -16,11 +16,12 @@ class QueueService
public function getQueueState(User $user): QueueStateDTO
{
$state = QueueState::query()->where('user_id', $user->id)->firstOrCreate([
'user_id' => $user->id,
], [
'song_ids' => [],
]);
$state = QueueState::query()
->whereBelongsTo($user)
->firstOrCreate(
['user_id' => $user->id],
['song_ids' => []],
);
$currentSong = $state->current_song_id ? $this->songRepository->findOne($state->current_song_id, $user) : null;
@ -33,11 +34,7 @@ class QueueService
public function updateQueueState(User $user, array $songIds): void
{
QueueState::query()->updateOrCreate([
'user_id' => $user->id,
], [
'song_ids' => $songIds,
]);
QueueState::query()->updateOrCreate(['user_id' => $user->id], ['song_ids' => $songIds]);
}
public function updatePlaybackStatus(User $user, Song $song, int $position): void

View file

@ -52,7 +52,7 @@ class SearchService
{
try {
return $repository->getMany(
ids: $repository->modelClass::search($keywords)->get()->take($count)->pluck('id')->all(), // @phpstan-ignore-line
ids: $repository->modelClass::search($keywords)->get()->take($count)->modelKeys(), // @phpstan-ignore-line
preserveOrder: true,
);
} catch (Throwable $e) {

View file

@ -12,7 +12,7 @@ use App\Models\User;
use App\Values\SmartPlaylistRule as Rule;
use App\Values\SmartPlaylistRuleGroup as RuleGroup;
use App\Values\SmartPlaylistSqlElements as SqlElements;
use Illuminate\Support\Collection;
use Illuminate\Database\Eloquent\Collection;
class SmartPlaylistService
{

View file

@ -9,6 +9,7 @@ use App\Models\Song;
use App\Repositories\SongRepository;
use App\Services\SongStorages\SongStorage;
use App\Values\SongUpdateData;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
@ -97,34 +98,31 @@ class SongService
return $this->songRepository->getOne($song->id);
}
public function markSongsAsPublic(Collection $songs): void
public function markSongsAsPublic(EloquentCollection $songs): void
{
Song::query()->whereIn('id', $songs->pluck('id'))->update(['is_public' => true]);
$songs->toQuery()->update(['is_public' => true]);
}
/** @return array<string> IDs of songs that are marked as private */
public function markSongsAsPrivate(Collection $songs): array
public function markSongsAsPrivate(EloquentCollection $songs): array
{
if (License::isPlus()) {
// Songs that are in collaborative playlists can't be marked as private.
/**
* @var Collection<array-key, Song> $collaborativeSongs
*/
$collaborativeSongs = Song::query()
->whereIn('songs.id', $songs->pluck('id'))
->join('playlist_song', 'songs.id', '=', 'playlist_song.song_id')
->join('playlist_collaborators', 'playlist_song.playlist_id', '=', 'playlist_collaborators.playlist_id')
->select('songs.id')
->distinct()
->pluck('songs.id')
->all();
License::requirePlus();
$applicableSongIds = $songs->whereNotIn('id', $collaborativeSongs)->pluck('id')->all();
} else {
$applicableSongIds = $songs->pluck('id')->all();
}
// Songs that are in collaborative playlists can't be marked as private.
/**
* @var Collection<array-key, Song> $collaborativeSongs
*/
$collaborativeSongs = $songs->toQuery()
->join('playlist_song', 'songs.id', '=', 'playlist_song.song_id')
->join('playlist_collaborators', 'playlist_song.playlist_id', '=', 'playlist_collaborators.playlist_id')
->select('songs.id')
->distinct()
->pluck('songs.id')
->all();
Song::query()->whereIn('id', $applicableSongIds)->update(['is_public' => false]);
$applicableSongIds = $songs->whereNotIn('id', $collaborativeSongs)->modelKeys();
Song::query()->whereKey($applicableSongIds)->update(['is_public' => false]);
return $applicableSongIds;
}

View file

@ -24,9 +24,9 @@ final class LicenseInstance implements Arrayable, Jsonable
public static function fromJsonObject(object $json): self
{
return new self(
id: $json->id,
name: $json->name,
createdAt: Carbon::parse($json->created_at),
id: object_get($json, 'id'),
name: object_get($json, 'name'),
createdAt: Carbon::parse(object_get($json, 'created_at')),
);
}

View file

@ -28,7 +28,7 @@ final class EpisodePlayable implements Arrayable, Jsonable
public static function getForEpisode(Episode $episode): ?self
{
/** @var self|null $cached */
$cached = Cache::get("episode-playable.$episode->id");
$cached = Cache::get("episode-playable.{$episode->id}");
return $cached?->valid() ? $cached : self::createForEpisode($episode);
}
@ -44,7 +44,7 @@ final class EpisodePlayable implements Arrayable, Jsonable
}
$playable = new self($file, md5_file($file));
Cache::forever("episode-playable.$episode->id", $playable);
Cache::forever("episode-playable.{$episode->id}", $playable);
return $playable;
}

View file

@ -13,7 +13,7 @@ class QueueStateFactory extends Factory
{
return [
'user_id' => User::factory(),
'song_ids' => Song::factory()->count(3)->create()->pluck('id')->toArray(),
'song_ids' => Song::factory()->count(3)->create()->modelKeys(),
'current_song_id' => null,
'playback_position' => 0,
];

View file

@ -26,10 +26,12 @@ parameters:
# Laravel factories allow declaration of dynamic methods as "states"
- '#Call to an undefined method Illuminate\\Database\\Eloquent\\Factories\\Factory::#'
- '#expects App\\Models\\User\|null, Illuminate\\Database\\Eloquent\\Collection\|Illuminate\\Database\\Eloquent\\Model given#'
- '#expects App\\Models\\[a-zA-Z]*, Illuminate\\Database\\Eloquent\\Model\|null given#'
- '#Method App\\Models\\.*::query\(\) should return App\\Builders\\.*Builder but returns Illuminate\\Database\\Eloquent\\Builder<Illuminate\\Database\\Eloquent\\Model>#'
- '#Parameter \#1 \$callback of method Illuminate\\Support\\Collection<int,Illuminate\\Database\\Eloquent\\Model>::each\(\) expects callable\(Illuminate\\Database\\Eloquent\\Model, int\)#'
- '#Access to an undefined property Illuminate\\Database\\Eloquent\\Model::#'
- '#Unknown parameter \$(type|user) in call to static method App\\Models\\Song::query\(\)#'
- "#Called 'modelKeys' on Laravel collection, but could have been retrieved as a query#"
excludePaths:

View file

@ -33,7 +33,7 @@ class AlbumCoverTest extends TestCase
->once()
->with(Mockery::on(static fn (Album $target) => $target->is($album)), 'data:image/jpeg;base64,Rm9v');
$this->putAs("api/album/$album->id/cover", ['cover' => 'data:image/jpeg;base64,Rm9v'], create_admin())
$this->putAs("api/album/{$album->id}/cover", ['cover' => 'data:image/jpeg;base64,Rm9v'], create_admin())
->assertOk();
}
@ -44,7 +44,7 @@ class AlbumCoverTest extends TestCase
$this->mediaMetadataService->shouldNotReceive('writeAlbumCover');
$this->putAs('api/album/' . $album->id . '/cover', ['cover' => 'data:image/jpeg;base64,Rm9v'], create_user())
$this->putAs("api/album/{$album->id}/cover", ['cover' => 'data:image/jpeg;base64,Rm9v'], create_user())
->assertForbidden();
}
}

View file

@ -43,7 +43,7 @@ class AlbumInformationTest extends TestCase
]
));
$this->getAs('api/albums/' . $album->id . '/information')
$this->getAs("api/albums/{$album->id}/information")
->assertJsonStructure(AlbumInformation::JSON_STRUCTURE);
}

View file

@ -14,10 +14,9 @@ class AlbumSongTest extends TestCase
public function index(): void
{
$album = Album::factory()->create();
Song::factory(5)->for($album)->create();
$this->getAs('api/albums/' . $album->id . '/songs')
$this->getAs("api/albums/{$album->id}/songs")
->assertJsonStructure(['*' => SongResource::JSON_STRUCTURE]);
}
}

View file

@ -14,10 +14,9 @@ class ArtistAlbumTest extends TestCase
public function index(): void
{
$artist = Artist::factory()->create();
Album::factory(5)->for($artist)->create();
$this->getAs('api/artists/' . $artist->id . '/albums')
$this->getAs("api/artists/{$artist->id}/albums")
->assertJsonStructure(['*' => AlbumResource::JSON_STRUCTURE]);
}
}

View file

@ -32,18 +32,18 @@ class ArtistImageTest extends TestCase
->once()
->with(Mockery::on(static fn (Artist $target) => $target->is($artist)), 'data:image/jpeg;base64,Rm9v');
$this->putAs("api/artist/$artist->id/image", ['image' => 'data:image/jpeg;base64,Rm9v'], create_admin())
$this->putAs("api/artist/{$artist->id}/image", ['image' => 'data:image/jpeg;base64,Rm9v'], create_admin())
->assertOk();
}
#[Test]
public function updateNotAllowedForNormalUsers(): void
{
Artist::factory()->create(['id' => 9999]);
$artist = Artist::factory()->create();
$this->mediaMetadataService->shouldNotReceive('writeArtistImage');
$this->putAs('api/artist/9999/image', ['image' => 'data:image/jpeg;base64,Rm9v'])
$this->putAs("api/artist/{$artist->id}/image", ['image' => 'data:image/jpeg;base64,Rm9v'])
->assertForbidden();
}
}

View file

@ -31,7 +31,7 @@ class ArtistInformationTest extends TestCase
],
));
$this->getAs('api/artists/' . $artist->id . '/information')
$this->getAs("api/artists/{$artist->id}/information")
->assertJsonStructure(ArtistInformation::JSON_STRUCTURE);
}

View file

@ -17,7 +17,7 @@ class ArtistSongTest extends TestCase
Song::factory(5)->for($artist)->create();
$this->getAs('api/artists/' . $artist->id . '/songs')
$this->getAs("api/artists/{$artist->id}/songs")
->assertJsonStructure(['*' => SongResource::JSON_STRUCTURE]);
}
}

View file

@ -8,7 +8,7 @@ use App\Models\Interaction;
use App\Models\Playlist;
use App\Models\Song;
use App\Services\DownloadService;
use Illuminate\Support\Collection;
use Illuminate\Database\Eloquent\Collection;
use Mockery;
use Mockery\MockInterface;
use PHPUnit\Framework\Attributes\Test;
@ -47,7 +47,7 @@ class DownloadTest extends TestCase
->shouldReceive('getDownloadablePath')
->once()
->with(Mockery::on(static function (Collection $retrievedSongs) use ($song) {
return $retrievedSongs->count() === 1 && $retrievedSongs->first()->id === $song->id;
return $retrievedSongs->count() === 1 && $retrievedSongs->first()->is($song);
}))
->andReturn(test_path('songs/blank.mp3'));
@ -65,7 +65,7 @@ class DownloadTest extends TestCase
->shouldReceive('getDownloadablePath')
->once()
->with(Mockery::on(static function (Collection $retrievedSongs) use ($songs): bool {
self::assertEqualsCanonicalizing($retrievedSongs->pluck('id')->all(), $songs->pluck('id')->all());
self::assertEqualsCanonicalizing($retrievedSongs->modelKeys(), $songs->modelKeys());
return true;
}))
@ -89,7 +89,7 @@ class DownloadTest extends TestCase
->shouldReceive('getDownloadablePath')
->once()
->with(Mockery::on(static function (Collection $retrievedSongs) use ($songs): bool {
self::assertEqualsCanonicalizing($retrievedSongs->pluck('id')->all(), $songs->pluck('id')->all());
self::assertEqualsCanonicalizing($retrievedSongs->modelKeys(), $songs->modelKeys());
return true;
}))
@ -110,7 +110,7 @@ class DownloadTest extends TestCase
->shouldReceive('getDownloadablePath')
->once()
->with(Mockery::on(static function (Collection $retrievedSongs) use ($songs): bool {
self::assertEqualsCanonicalizing($retrievedSongs->pluck('id')->all(), $songs->pluck('id')->all());
self::assertEqualsCanonicalizing($retrievedSongs->modelKeys(), $songs->modelKeys());
return true;
}))
@ -133,7 +133,7 @@ class DownloadTest extends TestCase
$this->downloadService
->shouldReceive('getDownloadablePath')
->with(Mockery::on(static function (Collection $retrievedSongs) use ($songs): bool {
self::assertEqualsCanonicalizing($retrievedSongs->pluck('id')->all(), $songs->pluck('id')->all());
self::assertEqualsCanonicalizing($retrievedSongs->modelKeys(), $songs->modelKeys());
return true;
}))
@ -162,7 +162,7 @@ class DownloadTest extends TestCase
$this->downloadService
->shouldReceive('getDownloadablePath')
->with(Mockery::on(static function (Collection $songs) use ($favorites): bool {
self::assertEqualsCanonicalizing($songs->pluck('id')->all(), $favorites->pluck('song_id')->all());
self::assertEqualsCanonicalizing($songs->modelKeys(), $favorites->pluck('song_id')->all());
return true;
}))

View file

@ -76,7 +76,7 @@ class InteractionTest extends TestCase
$user = create_user();
$songs = Song::factory(2)->create();
$songIds = $songs->pluck('id')->all();
$songIds = $songs->modelKeys();
$this->postAs('api/interaction/batch/like', ['songs' => $songIds], $user);

View file

@ -38,7 +38,7 @@ class AlbumCoverTest extends PlusTestCase
->once()
->with(Mockery::on(static fn (Album $target) => $target->is($album)), 'data:image/jpeg;base64,Rm9v');
$this->putAs("api/albums/$album->id/cover", ['cover' => 'data:image/jpeg;base64,Rm9v'], $user)
$this->putAs("api/albums/{$album->id}/cover", ['cover' => 'data:image/jpeg;base64,Rm9v'], $user)
->assertOk();
}
@ -56,7 +56,7 @@ class AlbumCoverTest extends PlusTestCase
->shouldReceive('writeAlbumCover')
->never();
$this->putAs("api/albums/$album->id/cover", ['cover' => 'data:image/jpeg;base64,Rm9v'], $user)
$this->putAs("api/albums/{$album->id}/cover", ['cover' => 'data:image/jpeg;base64,Rm9v'], $user)
->assertForbidden();
}
@ -74,7 +74,7 @@ class AlbumCoverTest extends PlusTestCase
->once()
->with(Mockery::on(static fn (Album $target) => $target->is($album)), 'data:image/jpeg;base64,Rm9v');
$this->putAs("api/albums/$album->id/cover", ['cover' => 'data:image/jpeg;base64,Rm9v'], create_admin())
$this->putAs("api/albums/{$album->id}/cover", ['cover' => 'data:image/jpeg;base64,Rm9v'], create_admin())
->assertOk();
}
}

View file

@ -38,7 +38,7 @@ class ArtistImageTest extends PlusTestCase
->once()
->with(Mockery::on(static fn (Artist $target) => $target->is($artist)), 'data:image/jpeg;base64,Rm9v');
$this->putAs("api/artists/$artist->id/image", ['image' => 'data:image/jpeg;base64,Rm9v'], $user)
$this->putAs("api/artists/{$artist->id}/image", ['image' => 'data:image/jpeg;base64,Rm9v'], $user)
->assertOk();
}
@ -56,7 +56,7 @@ class ArtistImageTest extends PlusTestCase
->shouldReceive('writeArtistImage')
->never();
$this->putAs("api/artists/$artist->id/image", ['image' => 'data:image/jpeg;base64,Rm9v'], $user)
$this->putAs("api/artists/{$artist->id}/image", ['image' => 'data:image/jpeg;base64,Rm9v'], $user)
->assertForbidden();
}
@ -74,7 +74,11 @@ class ArtistImageTest extends PlusTestCase
->once()
->with(Mockery::on(static fn (Artist $target) => $target->is($artist)), 'data:image/jpeg;base64,Rm9v');
$this->putAs("api/artists/$artist->id/image", ['image' => 'data:image/jpeg;base64,Rm9v'], create_admin())
$this->putAs(
"api/artists/{$artist->id}/image",
['image' => 'data:image/jpeg;base64,Rm9v'],
create_admin()
)
->assertOk();
}
}

View file

@ -21,7 +21,7 @@ class DownloadTest extends PlusTestCase
// Can't download a private song that doesn't belong to the user
/** @var Song $externalPrivateSong */
$externalPrivateSong = Song::factory()->private()->create();
$this->get("download/songs?songs[]=$externalPrivateSong->id&api_token=" . $apiToken)
$this->get("download/songs?songs[]={$externalPrivateSong->id}&api_token=" . $apiToken)
->assertForbidden();
// Can download a public song that doesn't belong to the user
@ -33,7 +33,7 @@ class DownloadTest extends PlusTestCase
->once()
->andReturn(test_path('songs/blank.mp3'));
$this->get("download/songs?songs[]=$externalPublicSong->id&api_token=" . $apiToken)
$this->get("download/songs?songs[]={$externalPublicSong->id}&api_token=" . $apiToken)
->assertOk();
// Can download a private song that belongs to the user
@ -42,7 +42,7 @@ class DownloadTest extends PlusTestCase
$downloadService->shouldReceive('getDownloadablePath')
->once()
->andReturn(test_path('songs/blank.mp3'));
$this->get("download/songs?songs[]=$ownSong->id&api_token=" . $apiToken)
$this->get("download/songs?songs[]={$ownSong->id}&api_token=" . $apiToken)
->assertOk();
}
}

View file

@ -6,7 +6,6 @@ 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 PHPUnit\Framework\Attributes\Test;
use Tests\PlusTestCase;
@ -23,7 +22,6 @@ class InteractionTest extends PlusTestCase
$owner = create_user();
// Can't increase play count of a private song that doesn't belong to the user
/** @var Song $externalPrivateSong */
$externalPrivateSong = Song::factory()->private()->create();
$this->postAs('api/interaction/play', ['song' => $externalPrivateSong->id], $owner)
->assertForbidden();
@ -75,26 +73,23 @@ class InteractionTest extends PlusTestCase
$owner = create_user();
// Can't batch like private songs that don't belong to the user
/** @var Collection $externalPrivateSongs */
$externalPrivateSongs = Song::factory()->count(3)->private()->create();
$this->postAs('api/interaction/batch/like', ['songs' => $externalPrivateSongs->pluck('id')->all()], $owner)
$this->postAs('api/interaction/batch/like', ['songs' => $externalPrivateSongs->modelKeys()], $owner)
->assertForbidden();
// Can batch like public songs that don't belong to the user
/** @var Collection $externalPublicSongs */
$externalPublicSongs = Song::factory()->count(3)->public()->create();
$this->postAs('api/interaction/batch/like', ['songs' => $externalPublicSongs->pluck('id')->all()], $owner)
$this->postAs('api/interaction/batch/like', ['songs' => $externalPublicSongs->modelKeys()], $owner)
->assertSuccessful();
// Can batch like private songs that belong to the user
/** @var Collection $ownPrivateSongs */
$ownPrivateSongs = Song::factory()->count(3)->private()->for($owner, 'owner')->create();
$this->postAs('api/interaction/batch/like', ['songs' => $ownPrivateSongs->pluck('id')->all()], $owner)
$this->postAs('api/interaction/batch/like', ['songs' => $ownPrivateSongs->modelKeys()], $owner)
->assertSuccessful();
// Can't batch like a mix of inaccessible and accessible songs
$mixedSongs = $externalPrivateSongs->merge($externalPublicSongs);
$this->postAs('api/interaction/batch/like', ['songs' => $mixedSongs->pluck('id')->all()], $owner)
$this->postAs('api/interaction/batch/like', ['songs' => $mixedSongs->modelKeys()], $owner)
->assertForbidden();
}
@ -106,26 +101,23 @@ class InteractionTest extends PlusTestCase
$owner = create_user();
// Can't batch unlike private songs that don't belong to the user
/** @var Collection $externalPrivateSongs */
$externalPrivateSongs = Song::factory()->count(3)->private()->create();
$this->postAs('api/interaction/batch/unlike', ['songs' => $externalPrivateSongs->pluck('id')->all()], $owner)
$this->postAs('api/interaction/batch/unlike', ['songs' => $externalPrivateSongs->modelKeys()], $owner)
->assertForbidden();
// Can batch unlike public songs that don't belong to the user
/** @var Collection $externalPublicSongs */
$externalPublicSongs = Song::factory()->count(3)->public()->create();
$this->postAs('api/interaction/batch/unlike', ['songs' => $externalPublicSongs->pluck('id')->all()], $owner)
$this->postAs('api/interaction/batch/unlike', ['songs' => $externalPublicSongs->modelKeys()], $owner)
->assertSuccessful();
// Can batch unlike private songs that belong to the user
/** @var Collection $ownPrivateSongs */
$ownPrivateSongs = Song::factory()->count(3)->private()->for($owner, 'owner')->create();
$this->postAs('api/interaction/batch/unlike', ['songs' => $ownPrivateSongs->pluck('id')->all()], $owner)
$this->postAs('api/interaction/batch/unlike', ['songs' => $ownPrivateSongs->modelKeys()], $owner)
->assertSuccessful();
// Can't batch unlike a mix of inaccessible and accessible songs
$mixedSongs = $externalPrivateSongs->merge($externalPublicSongs);
$this->postAs('api/interaction/batch/unlike', ['songs' => $mixedSongs->pluck('id')->all()], $owner)
$this->postAs('api/interaction/batch/unlike', ['songs' => $mixedSongs->modelKeys()], $owner)
->assertForbidden();
}
}

View file

@ -19,7 +19,7 @@ class PlaylistCollaborationTest extends PlusTestCase
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create();
$this->postAs("api/playlists/$playlist->id/collaborators/invite", [], $playlist->user)
$this->postAs("api/playlists/{$playlist->id}/collaborators/invite", [], $playlist->user)
->assertJsonStructure(PlaylistCollaborationTokenResource::JSON_STRUCTURE);
}

View file

@ -18,7 +18,11 @@ class PlaylistCoverTest extends PlusTestCase
$collaborator = create_user();
$playlist->addCollaborator($collaborator);
$this->putAs("api/playlists/$playlist->id/cover", ['cover' => 'data:image/jpeg;base64,Rm9v'], $collaborator)
$this->putAs(
"api/playlists/{$playlist->id}/cover",
['cover' => 'data:image/jpeg;base64,Rm9v'],
$collaborator
)
->assertForbidden();
}
@ -30,7 +34,7 @@ class PlaylistCoverTest extends PlusTestCase
$collaborator = create_user();
$playlist->addCollaborator($collaborator);
$this->deleteAs("api/playlists/$playlist->id/cover", [], $collaborator)
$this->deleteAs("api/playlists/{$playlist->id}/cover", [], $collaborator)
->assertForbidden();
}
}

View file

@ -22,7 +22,7 @@ class PlaylistFolderTest extends PlusTestCase
/** @var PlaylistFolder $ownerFolder */
$ownerFolder = PlaylistFolder::factory()->for($playlist->user)->create();
$ownerFolder->playlists()->attach($playlist->id);
$ownerFolder->playlists()->attach($playlist);
self::assertTrue($playlist->refresh()->getFolder($playlist->user)?->is($ownerFolder));
/** @var PlaylistFolder $collaboratorFolder */
@ -30,7 +30,7 @@ class PlaylistFolderTest extends PlusTestCase
self::assertNull($playlist->getFolder($collaborator));
$this->postAs(
"api/playlist-folders/$collaboratorFolder->id/playlists",
"api/playlist-folders/{$collaboratorFolder->id}/playlists",
['playlists' => [$playlist->id]],
$collaborator
)
@ -54,17 +54,17 @@ class PlaylistFolderTest extends PlusTestCase
/** @var PlaylistFolder $ownerFolder */
$ownerFolder = PlaylistFolder::factory()->for($playlist->user)->create();
$ownerFolder->playlists()->attach($playlist->id);
$ownerFolder->playlists()->attach($playlist);
self::assertTrue($playlist->refresh()->getFolder($playlist->user)?->is($ownerFolder));
/** @var PlaylistFolder $collaboratorFolder */
$collaboratorFolder = PlaylistFolder::factory()->for($collaborator)->create();
$collaboratorFolder->playlists()->attach($playlist->id);
$collaboratorFolder->playlists()->attach($playlist);
self::assertTrue($playlist->refresh()->getFolder($collaborator)?->is($collaboratorFolder));
$this->deleteAs(
"api/playlist-folders/$collaboratorFolder->id/playlists",
"api/playlist-folders/{$collaboratorFolder->id}/playlists",
['playlists' => [$playlist->id]],
$collaborator
)

View file

@ -22,7 +22,7 @@ class PlaylistSongTest extends PlusTestCase
$collaborator = create_user();
$playlist->addCollaborator($collaborator);
$this->getAs("api/playlists/$playlist->id/songs", $collaborator)
$this->getAs("api/playlists/{$playlist->id}/songs", $collaborator)
->assertSuccessful()
->assertJsonStructure(['*' => CollaborativeSongResource::JSON_STRUCTURE])
->assertJsonCount(3);
@ -42,7 +42,7 @@ class PlaylistSongTest extends PlusTestCase
$collaborator = create_user();
$playlist->addCollaborator($collaborator);
$this->getAs("api/playlists/$playlist->id/songs", $collaborator)
$this->getAs("api/playlists/{$playlist->id}/songs", $collaborator)
->assertSuccessful()
->assertJsonStructure(['*' => CollaborativeSongResource::JSON_STRUCTURE])
->assertJsonCount(3)
@ -58,7 +58,7 @@ class PlaylistSongTest extends PlusTestCase
$playlist->addCollaborator($collaborator);
$songs = Song::factory()->for($collaborator, 'owner')->count(3)->create();
$this->postAs("api/playlists/$playlist->id/songs", ['songs' => $songs->pluck('id')->all()], $collaborator)
$this->postAs("api/playlists/{$playlist->id}/songs", ['songs' => $songs->modelKeys()], $collaborator)
->assertSuccessful();
$playlist->refresh();
@ -75,7 +75,7 @@ class PlaylistSongTest extends PlusTestCase
$songs = Song::factory()->for($collaborator, 'owner')->count(3)->create();
$playlist->addPlayables($songs);
$this->deleteAs("api/playlists/$playlist->id/songs", ['songs' => $songs->pluck('id')->all()], $collaborator)
$this->deleteAs("api/playlists/{$playlist->id}/songs", ['songs' => $songs->modelKeys()], $collaborator)
->assertSuccessful();
$playlist->refresh();

View file

@ -52,7 +52,7 @@ class PlaylistTest extends PlusTestCase
/** @var Playlist $playlist */
$playlist = Playlist::factory()->smart()->create();
$this->putAs("api/playlists/$playlist->id", [
$this->putAs("api/playlists/{$playlist->id}", [
'name' => 'Foo',
'own_songs_only' => true,
'rules' => $playlist->rules->toArray(),
@ -72,7 +72,7 @@ class PlaylistTest extends PlusTestCase
$collaborator = create_user();
$playlist->addCollaborator($collaborator);
$this->putAs("api/playlists/$playlist->id", ['name' => 'Nope'], $collaborator)
$this->putAs("api/playlists/{$playlist->id}", ['name' => 'Nope'], $collaborator)
->assertForbidden();
}
}

View file

@ -29,7 +29,7 @@ class SongPlayTest extends PlusTestCase
->shouldReceive('stream')
->once();
$this->get("play/$song->id?t=$token->audioToken")
$this->get("play/{$song->id}?t=$token->audioToken")
->assertOk();
}
@ -48,7 +48,7 @@ class SongPlayTest extends PlusTestCase
->shouldReceive('stream')
->once();
$this->get("play/$song->id?t=$token->audioToken")
$this->get("play/{$song->id}?t=$token->audioToken")
->assertOk();
}
@ -63,7 +63,7 @@ class SongPlayTest extends PlusTestCase
/** @var CompositeToken $token */
$token = app(TokenManager::class)->createCompositeToken(create_user());
$this->get("play/$song->id?t=$token->audioToken")
$this->get("play/{$song->id}?t=$token->audioToken")
->assertForbidden();
}
}

View file

@ -3,7 +3,6 @@
namespace Tests\Feature\KoelPlus;
use App\Models\Song;
use Illuminate\Support\Collection;
use PHPUnit\Framework\Attributes\Test;
use Tests\PlusTestCase;
@ -18,7 +17,6 @@ class SongTest extends PlusTestCase
Song::factory(2)->public()->create();
/** @var Collection<array-key, Song> $ownSongs */
$ownSongs = Song::factory(3)->for($user, 'owner')->create();
$this->getAs('api/songs?own_songs_only=true', $user)
@ -55,19 +53,19 @@ class SongTest extends PlusTestCase
$publicSong = Song::factory()->public()->create();
// We can access public songs.
$this->getAs("api/songs/$publicSong->id", $user)->assertSuccessful();
$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();
$this->getAs("api/songs/{$ownPrivateSong->id}", $user)->assertSuccessful();
/** @var Song $externalUnownedSong */
$externalUnownedSong = Song::factory()->private()->create();
// But we can't access private songs that are not ours.
$this->getAs("api/songs/$externalUnownedSong->id", $user)->assertForbidden();
$this->getAs("api/songs/{$externalUnownedSong->id}", $user)->assertForbidden();
}
#[Test]
@ -76,12 +74,11 @@ class SongTest extends PlusTestCase
$currentUser = create_user();
$anotherUser = create_user();
/** @var Collection<Song> $externalUnownedSongs */
$externalUnownedSongs = Song::factory(3)->for($anotherUser, 'owner')->private()->create();
// We can't edit songs that are not ours.
$this->putAs('api/songs', [
'songs' => $externalUnownedSongs->pluck('id')->toArray(),
'songs' => $externalUnownedSongs->modelKeys(),
'data' => [
'title' => 'New Title',
],
@ -91,7 +88,7 @@ class SongTest extends PlusTestCase
$mixedSongs = $externalUnownedSongs->merge(Song::factory(2)->for($currentUser, 'owner')->create());
$this->putAs('api/songs', [
'songs' => $mixedSongs->pluck('id')->toArray(),
'songs' => $mixedSongs->modelKeys(),
'data' => [
'title' => 'New Title',
],
@ -101,7 +98,7 @@ class SongTest extends PlusTestCase
$ownSongs = Song::factory(3)->for($currentUser, 'owner')->create();
$this->putAs('api/songs', [
'songs' => $ownSongs->pluck('id')->toArray(),
'songs' => $ownSongs->modelKeys(),
'data' => [
'title' => 'New Title',
],
@ -114,35 +111,33 @@ class SongTest extends PlusTestCase
$currentUser = create_user();
$anotherUser = create_user();
/** @var Collection<Song> $externalUnownedSongs */
$externalUnownedSongs = Song::factory(3)->for($anotherUser, 'owner')->private()->create();
// We can't delete songs that are not ours.
$this->deleteAs('api/songs', ['songs' => $externalUnownedSongs->pluck('id')->toArray()], $currentUser)
$this->deleteAs('api/songs', ['songs' => $externalUnownedSongs->modelKeys()], $currentUser)
->assertForbidden();
// Even if some of the songs are owned by us, we still can't delete them.
$mixedSongs = $externalUnownedSongs->merge(Song::factory(2)->for($currentUser, 'owner')->create());
$this->deleteAs('api/songs', ['songs' => $mixedSongs->pluck('id')->toArray()], $currentUser)
$this->deleteAs('api/songs', ['songs' => $mixedSongs->modelKeys()], $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)
$this->deleteAs('api/songs', ['songs' => $ownSongs->modelKeys()], $currentUser)
->assertSuccessful();
}
#[Test]
public function publicizeSongs(): void
public function markSongsAsPublic(): void
{
$user = create_user();
/** @var Song $songs */
$songs = Song::factory(3)->for($user, 'owner')->private()->create();
$this->putAs('api/songs/publicize', ['songs' => $songs->pluck('id')->toArray()], $user)
$this->putAs('api/songs/publicize', ['songs' => $songs->modelKeys()], $user)
->assertSuccessful();
$songs->each(static function (Song $song): void {
@ -152,14 +147,13 @@ class SongTest extends PlusTestCase
}
#[Test]
public function privatizeSongs(): void
public function markSongsAsPrivate(): void
{
$user = create_user();
/** @var Song $songs */
$songs = Song::factory(3)->for($user, 'owner')->public()->create();
$this->putAs('api/songs/privatize', ['songs' => $songs->pluck('id')->toArray()], $user)
$this->putAs('api/songs/privatize', ['songs' => $songs->modelKeys()], $user)
->assertSuccessful();
$songs->each(static function (Song $song): void {
@ -173,12 +167,12 @@ class SongTest extends PlusTestCase
{
$songs = Song::factory(3)->public()->create();
$this->putAs('api/songs/privatize', ['songs' => $songs->pluck('id')->toArray()])
$this->putAs('api/songs/privatize', ['songs' => $songs->modelKeys()])
->assertForbidden();
$otherSongs = Song::factory(3)->private()->create();
$this->putAs('api/songs/publicize', ['songs' => $otherSongs->pluck('id')->toArray()])
$this->putAs('api/songs/publicize', ['songs' => $otherSongs->modelKeys()])
->assertForbidden();
}
}

View file

@ -3,7 +3,6 @@
namespace Tests\Feature\KoelPlus;
use App\Models\Song;
use Illuminate\Support\Collection;
use PHPUnit\Framework\Attributes\Test;
use Tests\PlusTestCase;
@ -17,17 +16,16 @@ class SongVisibilityTest extends PlusTestCase
$currentUser = create_user();
$anotherUser = create_user();
/** @var Collection<Song> $externalSongs */
$externalSongs = Song::factory(3)->for($anotherUser, 'owner')->private()->create();
// We can't make public songs that are not ours.
$this->putAs('api/songs/publicize', ['songs' => $externalSongs->pluck('id')->toArray()], $currentUser)
$this->putAs('api/songs/publicize', ['songs' => $externalSongs->modelKeys()], $currentUser)
->assertForbidden();
// But we can our own songs.
$ownSongs = Song::factory(3)->for($currentUser, 'owner')->create();
$this->putAs('api/songs/publicize', ['songs' => $ownSongs->pluck('id')->toArray()], $currentUser)
$this->putAs('api/songs/publicize', ['songs' => $ownSongs->modelKeys()], $currentUser)
->assertSuccessful();
$ownSongs->each(static fn (Song $song) => self::assertTrue($song->refresh()->is_public));
@ -39,17 +37,16 @@ class SongVisibilityTest extends PlusTestCase
$currentUser = create_user();
$anotherUser = create_user();
/** @var Collection<Song> $externalSongs */
$externalSongs = Song::factory(3)->for($anotherUser, 'owner')->public()->create();
// We can't Mark as Private songs that are not ours.
$this->putAs('api/songs/privatize', ['songs' => $externalSongs->pluck('id')->toArray()], $currentUser)
$this->putAs('api/songs/privatize', ['songs' => $externalSongs->modelKeys()], $currentUser)
->assertForbidden();
// But we can our own songs.
$ownSongs = Song::factory(3)->for($currentUser, 'owner')->create();
$this->putAs('api/songs/privatize', ['songs' => $ownSongs->pluck('id')->toArray()], $currentUser)
$this->putAs('api/songs/privatize', ['songs' => $ownSongs->modelKeys()], $currentUser)
->assertSuccessful();
$ownSongs->each(static fn (Song $song) => self::assertFalse($song->refresh()->is_public));

View file

@ -22,7 +22,7 @@ class PlayCountTest extends TestCase
'play_count' => 10,
]);
$this->postAs('/api/interaction/play', ['song' => $interaction->song->id], $interaction->user)
$this->postAs('/api/interaction/play', ['song' => $interaction->song_id], $interaction->user)
->assertJsonStructure([
'type',
'id',
@ -53,8 +53,8 @@ class PlayCountTest extends TestCase
]);
$interaction = Interaction::query()
->where('song_id', $song->id)
->where('user_id', $user->id)
->whereBelongsTo($song)
->whereBelongsTo($user)
->first();
self::assertSame(1, $interaction->play_count);

View file

@ -19,7 +19,7 @@ class PlaylistCoverTest extends TestCase
self::assertNull($playlist->cover);
$this->putAs(
"api/playlists/$playlist->id/cover",
"api/playlists/{$playlist->id}/cover",
['cover' => read_as_data_url(test_path('blobs/cover.png'))],
$playlist->user
)
@ -33,7 +33,11 @@ class PlaylistCoverTest extends TestCase
{
$playlist = Playlist::factory()->create();
$this->putAs("api/playlists/$playlist->id/cover", ['cover' => 'data:image/jpeg;base64,Rm9v'], create_user())
$this->putAs(
"api/playlists/{$playlist->id}/cover",
['cover' => 'data:image/jpeg;base64,Rm9v'],
create_user()
)
->assertForbidden();
}
@ -42,7 +46,7 @@ class PlaylistCoverTest extends TestCase
{
$playlist = Playlist::factory()->create(['cover' => 'cover.jpg']);
$this->deleteAs("api/playlists/$playlist->id/cover", [], $playlist->user)
$this->deleteAs("api/playlists/{$playlist->id}/cover", [], $playlist->user)
->assertNoContent();
self::assertNull($playlist->refresh()->cover);
@ -53,7 +57,7 @@ class PlaylistCoverTest extends TestCase
{
$playlist = Playlist::factory()->create(['cover' => 'cover.jpg']);
$this->deleteAs("api/playlists/$playlist->id/cover", [], create_user())
$this->deleteAs("api/playlists/{$playlist->id}/cover", [], create_user())
->assertForbidden();
self::assertSame('cover.jpg', $playlist->refresh()->getRawOriginal('cover'));

View file

@ -39,7 +39,7 @@ class PlaylistFolderTest extends TestCase
{
$folder = PlaylistFolder::factory()->create(['name' => 'Metal']);
$this->patchAs('api/playlist-folders/' . $folder->id, ['name' => 'Classical'], $folder->user)
$this->patchAs("api/playlist-folders/{$folder->id}", ['name' => 'Classical'], $folder->user)
->assertJsonStructure(PlaylistFolderResource::JSON_STRUCTURE);
self::assertSame('Classical', $folder->fresh()->name);
@ -50,7 +50,7 @@ class PlaylistFolderTest extends TestCase
{
$folder = PlaylistFolder::factory()->create(['name' => 'Metal']);
$this->patchAs('api/playlist-folders/' . $folder->id, ['name' => 'Classical'])
$this->patchAs("api/playlist-folders/{$folder->id}", ['name' => 'Classical'])
->assertForbidden();
self::assertSame('Metal', $folder->fresh()->name);
@ -61,7 +61,7 @@ class PlaylistFolderTest extends TestCase
{
$folder = PlaylistFolder::factory()->create();
$this->deleteAs('api/playlist-folders/' . $folder->id, ['name' => 'Classical'], $folder->user)
$this->deleteAs("api/playlist-folders/{$folder->id}", ['name' => 'Classical'], $folder->user)
->assertNoContent();
self::assertModelMissing($folder);
@ -73,7 +73,7 @@ class PlaylistFolderTest extends TestCase
/** @var PlaylistFolder $folder */
$folder = PlaylistFolder::factory()->create();
$this->deleteAs('api/playlist-folders/' . $folder->id, ['name' => 'Classical'])
$this->deleteAs("api/playlist-folders/{$folder->id}", ['name' => 'Classical'])
->assertForbidden();
self::assertModelExists($folder);
@ -89,7 +89,11 @@ class PlaylistFolderTest extends TestCase
$playlist = Playlist::factory()->for($folder->user)->create();
self::assertNull($playlist->getFolderId($folder->user));
$this->postAs("api/playlist-folders/$folder->id/playlists", ['playlists' => [$playlist->id]], $folder->user)
$this->postAs(
"api/playlist-folders/{$folder->id}/playlists",
['playlists' => [$playlist->id]],
$folder->user
)
->assertSuccessful();
self::assertTrue($playlist->fresh()->getFolder($folder->user)->is($folder));
@ -105,7 +109,7 @@ class PlaylistFolderTest extends TestCase
$playlist = Playlist::factory()->for($folder->user)->create();
self::assertNull($playlist->getFolderId($folder->user));
$this->postAs("api/playlist-folders/$folder->id/playlists", ['playlists' => [$playlist->id]])
$this->postAs("api/playlist-folders/{$folder->id}/playlists", ['playlists' => [$playlist->id]])
->assertUnprocessable();
self::assertNull($playlist->fresh()->getFolder($folder->user));
@ -120,10 +124,14 @@ class PlaylistFolderTest extends TestCase
/** @var Playlist $playlist */
$playlist = Playlist::factory()->for($folder->user)->create();
$folder->playlists()->attach($playlist->id);
$folder->playlists()->attach($playlist);
self::assertTrue($playlist->refresh()->getFolder($folder->user)->is($folder));
$this->deleteAs("api/playlist-folders/$folder->id/playlists", ['playlists' => [$playlist->id]], $folder->user)
$this->deleteAs(
"api/playlist-folders/{$folder->id}/playlists",
['playlists' => [$playlist->id]],
$folder->user
)
->assertSuccessful();
self::assertNull($playlist->fresh()->getFolder($folder->user));
@ -138,10 +146,10 @@ class PlaylistFolderTest extends TestCase
/** @var Playlist $playlist */
$playlist = Playlist::factory()->for($folder->user)->create();
$folder->playlists()->attach($playlist->id);
$folder->playlists()->attach($playlist);
self::assertTrue($playlist->refresh()->getFolder($folder->user)->is($folder));
$this->deleteAs("api/playlist-folders/$folder->id/playlists", ['playlists' => [$playlist->id]])
$this->deleteAs("api/playlist-folders/{$folder->id}/playlists", ['playlists' => [$playlist->id]])
->assertUnprocessable();
self::assertTrue($playlist->refresh()->getFolder($folder->user)->is($folder));

View file

@ -5,7 +5,6 @@ namespace Tests\Feature;
use App\Http\Resources\SongResource;
use App\Models\Playlist;
use App\Models\Song;
use Illuminate\Support\Collection;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
@ -20,7 +19,7 @@ class PlaylistSongTest extends TestCase
$playlist = Playlist::factory()->create();
$playlist->addPlayables(Song::factory(5)->create());
$this->getAs("api/playlists/$playlist->id/songs", $playlist->user)
$this->getAs("api/playlists/{$playlist->id}/songs", $playlist->user)
->assertJsonStructure(['*' => SongResource::JSON_STRUCTURE]);
}
@ -46,7 +45,7 @@ class PlaylistSongTest extends TestCase
],
]);
$this->getAs("api/playlists/$playlist->id/songs", $playlist->user)
$this->getAs("api/playlists/{$playlist->id}/songs", $playlist->user)
->assertJsonStructure(['*' => SongResource::JSON_STRUCTURE]);
}
@ -57,7 +56,7 @@ class PlaylistSongTest extends TestCase
$playlist = Playlist::factory()->for(create_user())->create();
$playlist->addPlayables(Song::factory(5)->create());
$this->getAs("api/playlists/$playlist->id/songs")
$this->getAs("api/playlists/{$playlist->id}/songs")
->assertForbidden();
}
@ -67,13 +66,12 @@ class PlaylistSongTest extends TestCase
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create();
/** @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)
$this->postAs("api/playlists/{$playlist->id}/songs", ['songs' => $songs->modelKeys()], $playlist->user)
->assertSuccessful();
self::assertEqualsCanonicalizing($songs->pluck('id')->all(), $playlist->playables->pluck('id')->all());
self::assertEqualsCanonicalizing($songs->modelKeys(), $playlist->playables->modelKeys());
}
#[Test]
@ -84,7 +82,6 @@ class PlaylistSongTest extends TestCase
$toRemainSongs = Song::factory(5)->create();
/** @var Collection<array-key, Song> $toBeRemovedSongs */
$toBeRemovedSongs = Song::factory(2)->create();
$playlist->addPlayables($toRemainSongs->merge($toBeRemovedSongs));
@ -92,15 +89,15 @@ class PlaylistSongTest extends TestCase
self::assertCount(7, $playlist->playables);
$this->deleteAs(
"api/playlists/$playlist->id/songs",
['songs' => $toBeRemovedSongs->pluck('id')->all()],
"api/playlists/{$playlist->id}/songs",
['songs' => $toBeRemovedSongs->modelKeys()],
$playlist->user
)
->assertNoContent();
$playlist->refresh();
self::assertEqualsCanonicalizing($toRemainSongs->pluck('id')->all(), $playlist->playables->pluck('id')->all());
self::assertEqualsCanonicalizing($toRemainSongs->modelKeys(), $playlist->playables->modelKeys());
}
#[Test]
@ -112,10 +109,10 @@ class PlaylistSongTest extends TestCase
/** @var Song $song */
$song = Song::factory()->create();
$this->postAs("api/playlists/$playlist->id/songs", ['songs' => [$song->id]])
$this->postAs("api/playlists/{$playlist->id}/songs", ['songs' => [$song->id]])
->assertForbidden();
$this->deleteAs("api/playlists/$playlist->id/songs", ['songs' => [$song->id]])
$this->deleteAs("api/playlists/{$playlist->id}/songs", ['songs' => [$song->id]])
->assertForbidden();
}
@ -139,12 +136,12 @@ class PlaylistSongTest extends TestCase
],
]);
$songs = Song::factory(2)->create()->pluck('id')->all();
$songs = Song::factory(2)->create()->modelKeys();
$this->postAs("api/playlists/$playlist->id/songs", ['songs' => $songs], $playlist->user)
$this->postAs("api/playlists/{$playlist->id}/songs", ['songs' => $songs], $playlist->user)
->assertForbidden();
$this->deleteAs("api/playlists/$playlist->id/songs", ['songs' => $songs], $playlist->user)
$this->deleteAs("api/playlists/{$playlist->id}/songs", ['songs' => $songs], $playlist->user)
->assertForbidden();
}
}

View file

@ -6,7 +6,6 @@ use App\Http\Resources\PlaylistResource;
use App\Models\Playlist;
use App\Models\Song;
use App\Values\SmartPlaylistRule;
use Illuminate\Support\Collection;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
@ -30,23 +29,21 @@ class PlaylistTest extends TestCase
{
$user = create_user();
/** @var array<Song>|Collection $songs */
$songs = Song::factory(4)->create();
$this->postAs('api/playlists', [
'name' => 'Foo Bar',
'songs' => $songs->pluck('id')->all(),
'songs' => $songs->modelKeys(),
'rules' => [],
], $user)
->assertJsonStructure(PlaylistResource::JSON_STRUCTURE);
/** @var Playlist $playlist */
$playlist = Playlist::query()->latest()->first();
self::assertSame('Foo Bar', $playlist->name);
self::assertTrue($playlist->ownedBy($user));
self::assertNull($playlist->getFolder());
self::assertEqualsCanonicalizing($songs->pluck('id')->all(), $playlist->playables->pluck('id')->all());
self::assertEqualsCanonicalizing($songs->modelKeys(), $playlist->playables->modelKeys());
}
#[Test]
@ -70,7 +67,6 @@ class PlaylistTest extends TestCase
],
], $user)->assertJsonStructure(PlaylistResource::JSON_STRUCTURE);
/** @var Playlist $playlist */
$playlist = Playlist::query()->latest()->first();
self::assertSame('Smart Foo Bar', $playlist->name);
@ -98,7 +94,7 @@ class PlaylistTest extends TestCase
],
],
],
'songs' => Song::factory(3)->create()->pluck('id')->all(),
'songs' => Song::factory(3)->create()->modelKeys(),
])->assertUnprocessable();
}
@ -116,10 +112,9 @@ class PlaylistTest extends TestCase
#[Test]
public function updatePlaylistName(): void
{
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create(['name' => 'Foo']);
$this->putAs("api/playlists/$playlist->id", ['name' => 'Bar'], $playlist->user)
$this->putAs("api/playlists/{$playlist->id}", ['name' => 'Bar'], $playlist->user)
->assertJsonStructure(PlaylistResource::JSON_STRUCTURE);
self::assertSame('Bar', $playlist->refresh()->name);
@ -128,20 +123,18 @@ class PlaylistTest extends TestCase
#[Test]
public function nonOwnerCannotUpdatePlaylist(): void
{
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create(['name' => 'Foo']);
$this->putAs("api/playlists/$playlist->id", ['name' => 'Qux'])->assertForbidden();
$this->putAs("api/playlists/{$playlist->id}", ['name' => 'Qux'])->assertForbidden();
self::assertSame('Foo', $playlist->refresh()->name);
}
#[Test]
public function deletePlaylist(): void
{
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create();
$this->deleteAs("api/playlists/$playlist->id", [], $playlist->user);
$this->deleteAs("api/playlists/{$playlist->id}", [], $playlist->user);
self::assertModelMissing($playlist);
}
@ -149,10 +142,9 @@ class PlaylistTest extends TestCase
#[Test]
public function nonOwnerCannotDeletePlaylist(): void
{
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create();
$this->deleteAs("api/playlists/$playlist->id")->assertForbidden();
$this->deleteAs("api/playlists/{$playlist->id}")->assertForbidden();
self::assertModelExists($playlist);
}

View file

@ -45,13 +45,13 @@ class QueueTest extends TestCase
self::assertDatabaseMissing(QueueState::class, ['user_id' => $user->id]);
$songIds = Song::factory(3)->create()->pluck('id')->toArray();
$songIds = Song::factory(3)->create()->modelKeys();
$this->putAs('api/queue/state', ['songs' => $songIds], $user)
->assertNoContent();
/** @var QueueState $queue */
$queue = QueueState::query()->where('user_id', $user->id)->firstOrFail();
$queue = QueueState::query()->whereBelongsTo($user)->firstOrFail();
self::assertEqualsCanonicalizing($songIds, $queue->song_ids);
}

View file

@ -30,7 +30,7 @@ class ScrobbleTest extends TestCase
)
->once();
$this->postAs("/api/songs/$song->id/scrobble", ['timestamp' => 100], $user)
$this->postAs("/api/songs/{$song->id}/scrobble", ['timestamp' => 100], $user)
->assertNoContent();
}
}

View file

@ -33,7 +33,7 @@ class SongPlayTest extends TestCase
->shouldReceive('stream')
->once();
$this->get("play/$song->id?t=$token->audioToken")
$this->get("play/{$song->id}?t=$token->audioToken")
->assertOk();
}
@ -58,7 +58,7 @@ class SongPlayTest extends TestCase
->shouldReceive('stream')
->once();
$this->get("play/$song->id?t=$token->audioToken")
$this->get("play/{$song->id}?t=$token->audioToken")
->assertOk();
config(['koel.streaming.transcode_flac' => false]);
@ -79,7 +79,7 @@ class SongPlayTest extends TestCase
->shouldReceive('stream')
->once();
$this->get("play/$song->id/1?t=$token->audioToken")
$this->get("play/{$song->id}/1?t=$token->audioToken")
->assertOk();
}
}

View file

@ -35,10 +35,9 @@ class SongTest extends TestCase
#[Test]
public function destroy(): void
{
/** @var Collection<array-key, Song> $songs */
$songs = Song::factory(3)->create();
$this->deleteAs('api/songs', ['songs' => $songs->pluck('id')->all()], create_admin())
$this->deleteAs('api/songs', ['songs' => $songs->modelKeys()], create_admin())
->assertNoContent();
$songs->each(fn (Song $song) => $this->assertModelMissing($song));
@ -47,10 +46,9 @@ class SongTest extends TestCase
#[Test]
public function unauthorizedDelete(): void
{
/** @var Collection<array-key, Song> $songs */
$songs = Song::factory(3)->create();
$this->deleteAs('api/songs', ['songs' => $songs->pluck('id')->all()])
$this->deleteAs('api/songs', ['songs' => $songs->modelKeys()])
->assertForbidden();
$songs->each(fn (Song $song) => $this->assertModelExists($song));
@ -122,7 +120,7 @@ class SongTest extends TestCase
#[Test]
public function multipleUpdateNoCompilation(): void
{
$songIds = Song::factory(3)->create()->pluck('id')->all();
$songIds = Song::factory(3)->create()->modelKeys();
$this->putAs('/api/songs', [
'songs' => $songIds,
@ -159,9 +157,8 @@ class SongTest extends TestCase
#[Test]
public function multipleUpdateCreatingNewAlbumsAndArtists(): void
{
/** @var Collection<array-key, Song> $originalSongs */
$originalSongs = Song::factory(3)->create();
$originalSongIds = $originalSongs->pluck('id')->all();
$originalSongIds = $originalSongs->modelKeys();
$originalAlbumNames = $originalSongs->pluck('album.name')->all();
$originalAlbumIds = $originalSongs->pluck('album_id')->all();
@ -177,7 +174,6 @@ class SongTest extends TestCase
], create_admin())
->assertOk();
/** @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
@ -263,10 +259,7 @@ class SongTest extends TestCase
{
Song::factory(5)->create();
self::assertNotSame(0, Song::query()->count());
$ids = Song::query()->select('id')->get()->pluck('id')->all();
Song::deleteByChunk($ids, 1);
Song::deleteByChunk(Song::query()->get()->modelKeys(), 1);
self::assertSame(0, Song::query()->count());
}

View file

@ -16,10 +16,10 @@ class SongVisibilityTest extends TestCase
$owner = create_admin();
Song::factory(3)->create();
$this->putAs('api/songs/publicize', ['songs' => Song::query()->pluck('id')->all()], $owner)
$this->putAs('api/songs/publicize', ['songs' => Song::query()->get()->modelKeys()], $owner)
->assertForbidden();
$this->putAs('api/songs/privatize', ['songs' => Song::query()->pluck('id')->all()], $owner)
$this->putAs('api/songs/privatize', ['songs' => Song::query()->get()->modelKeys()], $owner)
->assertForbidden();
}
}

View file

@ -51,7 +51,7 @@ class UserTest extends TestCase
$admin = create_admin();
$user = create_admin(['password' => 'secret']);
$this->putAs("api/user/$user->id", [
$this->putAs("api/user/{$user->id}", [
'name' => 'Foo',
'email' => 'bar@baz.com',
'password' => 'new-secret',
@ -72,7 +72,7 @@ class UserTest extends TestCase
{
$user = create_user();
$this->deleteAs("api/user/$user->id", [], create_admin());
$this->deleteAs("api/user/{$user->id}", [], create_admin());
self::assertModelMissing($user);
}
@ -81,7 +81,7 @@ class UserTest extends TestCase
{
$admin = create_admin();
$this->deleteAs("api/user/$admin->id", [], $admin)->assertForbidden();
$this->deleteAs("api/user/{$admin->id}", [], $admin)->assertForbidden();
self::assertModelExists($admin);
}
}

View file

@ -52,8 +52,8 @@ class SmartPlaylistServiceTest extends PlusTestCase
]);
self::assertEqualsCanonicalizing(
$matches->pluck('id')->all(),
$this->service->getSongs($playlist, $owner)->pluck('id')->all()
$matches->modelKeys(),
$this->service->getSongs($playlist, $owner)->modelKeys()
);
}
}

View file

@ -66,8 +66,8 @@ class InteractionServiceTest extends TestCase
$songs->each(static function (Song $song) use ($user): void {
/** @var Interaction $interaction */
$interaction = Interaction::query()
->where('song_id', $song->id)
->where('user_id', $user->id)
->whereBelongsTo($song)
->whereBelongsTo($user)
->first();
self::assertTrue($interaction->liked);
@ -82,10 +82,9 @@ class InteractionServiceTest extends TestCase
Event::fake(MultipleSongsUnliked::class);
$user = create_user();
/** @var Collection $interactions */
$interactions = Interaction::factory(3)->for($user)->create(['liked' => true]);
$this->interactionService->unlikeMany($interactions->map(static fn (Interaction $i) => $i->song), $user);
$this->interactionService->unlikeMany($interactions->map(static fn (Interaction $i) => $i->song), $user); // @phpstan-ignore-line
$interactions->each(static function (Interaction $interaction): void {
self::assertFalse($interaction->refresh()->liked);

View file

@ -8,7 +8,6 @@ use App\Models\Podcast;
use App\Models\Song;
use App\Services\PlaylistService;
use App\Values\SmartPlaylistRuleGroupCollection;
use Illuminate\Support\Collection;
use InvalidArgumentException as BaseInvalidArgumentException;
use PHPUnit\Framework\Attributes\Test;
use Tests\PlusTestCase;
@ -43,17 +42,15 @@ class PlaylistServiceTest extends TestCase
#[Test]
public function createPlaylistWithSongs(): void
{
/** @var Collection<array-key, Song> $songs */
$songs = Song::factory(3)->create();
$user = create_user();
$playlist = $this->service->createPlaylist('foo', $user, null, $songs->pluck('id')->all());
$playlist = $this->service->createPlaylist('foo', $user, null, $songs->modelKeys());
self::assertSame('foo', $playlist->name);
self::assertTrue($user->is($playlist->user));
self::assertFalse($playlist->is_smart);
self::assertEqualsCanonicalizing($playlist->playables->pluck('id')->all(), $songs->pluck('id')->all());
self::assertEqualsCanonicalizing($playlist->playables->modelKeys(), $songs->modelKeys());
}
#[Test]
@ -213,7 +210,7 @@ class PlaylistServiceTest extends TestCase
self::assertCount(2, $addedSongs);
self::assertCount(5, $playlist->playables);
self::assertEqualsCanonicalizing($addedSongs->pluck('id')->all(), $songs->pluck('id')->all());
self::assertEqualsCanonicalizing($addedSongs->modelKeys(), $songs->modelKeys());
$songs->each(static fn (Song $song) => self::assertTrue($playlist->playables->contains($song)));
}
@ -235,7 +232,7 @@ class PlaylistServiceTest extends TestCase
self::assertCount(2, $addedEpisodes);
self::assertCount(5, $playlist->playables);
self::assertEqualsCanonicalizing($addedEpisodes->pluck('id')->all(), $episodes->pluck('id')->all());
self::assertEqualsCanonicalizing($addedEpisodes->modelKeys(), $episodes->modelKeys());
}
#[Test]
@ -252,7 +249,7 @@ class PlaylistServiceTest extends TestCase
self::assertCount(4, $addedEpisodes);
self::assertCount(7, $playlist->playables);
self::assertEqualsCanonicalizing($addedEpisodes->pluck('id')->all(), $playables->pluck('id')->all());
self::assertEqualsCanonicalizing($addedEpisodes->modelKeys(), $playables->modelKeys());
}
#[Test]
@ -292,23 +289,22 @@ class PlaylistServiceTest extends TestCase
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create();
/** @var Collection<array-key, Song> $songs */
$songs = Song::factory(4)->create();
$ids = $songs->pluck('id')->all();
$ids = $songs->modelKeys();
$playlist->addPlayables($songs);
$this->service->movePlayablesInPlaylist($playlist, [$ids[2], $ids[3]], $ids[0], 'after');
self::assertSame([$ids[0], $ids[2], $ids[3], $ids[1]], $playlist->refresh()->playables->pluck('id')->all());
self::assertSame([$ids[0], $ids[2], $ids[3], $ids[1]], $playlist->refresh()->playables->modelKeys());
$this->service->movePlayablesInPlaylist($playlist, [$ids[0]], $ids[3], 'before');
self::assertSame([$ids[2], $ids[0], $ids[3], $ids[1]], $playlist->refresh()->playables->pluck('id')->all());
self::assertSame([$ids[2], $ids[0], $ids[3], $ids[1]], $playlist->refresh()->playables->modelKeys());
// move to the first position
$this->service->movePlayablesInPlaylist($playlist, [$ids[0], $ids[1]], $ids[2], 'before');
self::assertSame([$ids[0], $ids[1], $ids[2], $ids[3]], $playlist->refresh()->playables->pluck('id')->all());
self::assertSame([$ids[0], $ids[1], $ids[2], $ids[3]], $playlist->refresh()->playables->modelKeys());
// move to the last position
$this->service->movePlayablesInPlaylist($playlist, [$ids[0], $ids[1]], $ids[3], 'after');
self::assertSame([$ids[2], $ids[3], $ids[0], $ids[1]], $playlist->refresh()->playables->pluck('id')->all());
self::assertSame([$ids[2], $ids[3], $ids[0], $ids[1]], $playlist->refresh()->playables->modelKeys());
}
}

View file

@ -49,11 +49,11 @@ class QueueServiceTest extends TestCase
'user_id' => $user->id,
]);
$songIds = Song::factory()->count(3)->create()->pluck('id')->toArray();
$songIds = Song::factory()->count(3)->create()->modelKeys();
$this->service->updateQueueState($user, $songIds);
/** @var QueueState $queueState */
$queueState = QueueState::query()->where('user_id', $user->id)->firstOrFail();
$queueState = QueueState::query()->whereBelongsTo($user)->firstOrFail();
self::assertEqualsCanonicalizing($songIds, $queueState->song_ids);
self::assertNull($queueState->current_song_id);
self::assertSame(0, $queueState->playback_position);
@ -65,7 +65,7 @@ class QueueServiceTest extends TestCase
/** @var QueueState $state */
$state = QueueState::factory()->create();
$songIds = Song::factory()->count(3)->create()->pluck('id')->toArray();
$songIds = Song::factory()->count(3)->create()->modelKeys();
$this->service->updateQueueState($state->user, $songIds);
$state->refresh();

View file

@ -9,7 +9,7 @@ use App\Models\Playlist;
use App\Models\Song;
use App\Models\User;
use App\Services\SmartPlaylistService;
use Illuminate\Support\Collection;
use Illuminate\Database\Eloquent\Collection;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
@ -564,8 +564,8 @@ class SmartPlaylistServiceTest extends TestCase
$playlist = Playlist::factory()->for($owner ?? create_admin())->create(['rules' => $rules]);
self::assertEqualsCanonicalizing(
$matches->pluck('id')->all(),
$this->service->getSongs($playlist)->pluck('id')->all()
$matches->modelKeys(),
$this->service->getSongs($playlist)->modelKeys()
);
}
}

View file

@ -28,7 +28,7 @@ class EpisodePlayableTest extends TestCase
Http::assertSentCount(1);
self::assertSame('acbd18db4cc2f85cedef654fccc4a4d8', $playable->checksum);
self::assertTrue(Cache::has("episode-playable.$episode->id"));
self::assertTrue(Cache::has("episode-playable.{$episode->id}"));
$retrieved = EpisodePlayable::getForEpisode($episode);

View file

@ -26,7 +26,7 @@ class AlbumTest extends TestCase
$artist = Artist::factory()->create();
$name = 'Foo';
self::assertNull(Album::query()->where('artist_id', $artist->id)->where('name', $name)->first());
self::assertNull(Album::query()->whereBelongsTo($artist)->where('name', $name)->first());
$album = Album::getOrCreate($artist, $name);
self::assertSame('Foo', $album->name);

View file

@ -49,7 +49,7 @@ class MediaInformationServiceTest extends TestCase
->shouldNotReceive('tryDownloadAlbumCover');
self::assertSame($info, $this->mediaInformationService->getAlbumInformation($album));
self::assertNotNull(cache()->get('album.info.' . $album->id));
self::assertNotNull(cache()->get("album.info.{$album->id}"));
}
#[Test]
@ -93,7 +93,7 @@ class MediaInformationServiceTest extends TestCase
->shouldNotReceive('tryDownloadArtistImage');
self::assertSame($info, $this->mediaInformationService->getArtistInformation($artist));
self::assertNotNull(cache()->get('artist.info.' . $artist->id));
self::assertNotNull(cache()->get("artist.info.{$artist->id}"));
}
#[Test]

View file

@ -5,7 +5,7 @@ namespace Tests\Unit\Services;
use App\Models\Playlist;
use App\Models\PlaylistFolder;
use App\Services\PlaylistFolderService;
use Illuminate\Support\Collection;
use Illuminate\Database\Eloquent\Collection;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
@ -57,7 +57,7 @@ class PlaylistFolderServiceTest extends TestCase
/** @var PlaylistFolder $folder */
$folder = PlaylistFolder::factory()->for($user)->create();
$this->service->addPlaylistsToFolder($folder, $playlists->pluck('id')->all());
$this->service->addPlaylistsToFolder($folder, $playlists->modelKeys());
self::assertCount(3, $folder->playlists);
}
@ -70,9 +70,9 @@ class PlaylistFolderServiceTest extends TestCase
/** @var Collection<array-key, Playlist> $playlists */
$playlists = Playlist::factory()->count(3)->create();
$folder->playlists()->attach($playlists->pluck('id')->all());
$folder->playlists()->attach($playlists);
$this->service->movePlaylistsToRootLevel($folder, $playlists->pluck('id')->all());
$this->service->movePlaylistsToRootLevel($folder, $playlists->modelKeys());
self::assertCount(0, $folder->playlists);