fix(cs): broken static analysis

This commit is contained in:
Phan An 2022-07-27 17:32:36 +02:00
parent e4ca67bc69
commit a3c1f7aec4
No known key found for this signature in database
GPG key ID: A81E4477F0BB6FDC
39 changed files with 186 additions and 217 deletions

View file

@ -3,7 +3,8 @@ on:
push:
branches:
- master
- next
# @fixme Tmp.disable until ready
# - next
pull_request:
workflow_dispatch:
jobs:

View file

@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
php-version: [ 7.4, 8.0, 8.1 ]
php-version: [ 8.0, 8.1 ]
fail-fast: false
steps:
- uses: actions/checkout@v1

View file

@ -6,7 +6,6 @@ use App\Console\Commands\Traits\AskForPassword;
use App\Exceptions\InstallationFailedException;
use App\Models\Setting;
use App\Models\User;
use App\Repositories\SettingRepository;
use App\Services\MediaCacheService;
use Illuminate\Console\Command;
use Illuminate\Contracts\Console\Kernel as Artisan;
@ -27,30 +26,16 @@ class InitCommand extends Command
protected $signature = 'koel:init {--no-assets}';
protected $description = 'Install or upgrade Koel';
private MediaCacheService $mediaCacheService;
private Artisan $artisan;
private DotenvEditor $dotenvEditor;
private Hash $hash;
private DB $db;
private SettingRepository $settingRepository;
private bool $adminSeeded = false;
public function __construct(
MediaCacheService $mediaCacheService,
SettingRepository $settingRepository,
Artisan $artisan,
Hash $hash,
DotenvEditor $dotenvEditor,
DB $db
private MediaCacheService $mediaCacheService,
private Artisan $artisan,
private Hash $hash,
private DotenvEditor $dotenvEditor,
private DB $db
) {
parent::__construct();
$this->mediaCacheService = $mediaCacheService;
$this->artisan = $artisan;
$this->dotenvEditor = $dotenvEditor;
$this->hash = $hash;
$this->db = $db;
$this->settingRepository = $settingRepository;
}
public function handle(): void

View file

@ -7,17 +7,13 @@ use App\Http\Resources\ArtistResource;
use App\Models\Artist;
use App\Models\User;
use App\Repositories\ArtistRepository;
use App\Services\MediaInformationService;
use Illuminate\Contracts\Auth\Authenticatable;
class ArtistController extends Controller
{
/** @param User $user */
public function __construct(
private ArtistRepository $artistRepository,
private MediaInformationService $informationService,
private ?Authenticatable $user
) {
public function __construct(private ArtistRepository $artistRepository, private ?Authenticatable $user)
{
}
public function index()

View file

@ -5,7 +5,7 @@ namespace App\Http\Controllers\V6\Requests;
use App\Http\Requests\API\Request;
/**
* @property-read array<string> songs
* @property-read array<string> $songs
*/
class AddSongsToPlaylistRequest extends Request
{

View file

@ -5,7 +5,7 @@ namespace App\Http\Controllers\V6\Requests;
use App\Http\Requests\API\Request;
/**
* @property-read array<string> songs
* @property-read array<string> $songs
*/
class RemoveSongsFromPlaylistRequest extends Request
{

View file

@ -31,6 +31,9 @@ use Laravel\Scout\Searchable;
* Notice that this doesn't guarantee the thumbnail exists.
* @property string|null $thumbnail The public URL to the album's thumbnail
* @property Carbon $created_at
* @property float|string $length Total length of the album in seconds (dynamically calculated)
* @property int|string $play_count Total number of times the album's songs have been played (dynamically calculated)
* @property int|string $song_count Total number of songs on the album (dynamically calculated)
*
* @method static self firstOrCreate(array $where, array $params = [])
* @method static self|null find(int $id)

View file

@ -3,6 +3,7 @@
namespace App\Models;
use App\Facades\Util;
use Carbon\Carbon;
use Illuminate\Contracts\Database\Query\Builder as BuilderContract;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
@ -24,6 +25,11 @@ use Laravel\Scout\Searchable;
* @property Collection $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)
* @property string|int $play_count Total number of times the artist has been played (dynamically calculated)
* @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
*
* @method static self find(int $id)
* @method static self firstOrCreate(array $where, array $params = [])

View file

@ -54,7 +54,6 @@ class Playlist extends Model
return Attribute::get(fn (): bool => $this->rule_groups->isNotEmpty());
}
/** @return Collection|array<array-key, SmartPlaylistRuleGroup> */
protected function ruleGroups(): Attribute
{
// aliasing the attribute to avoid confusion

View file

@ -2,6 +2,7 @@
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@ -27,6 +28,9 @@ use Laravel\Scout\Searchable;
* @property int $artist_id
* @property int $mtime
* @property int $contributing_artist_id
* @property ?bool $liked Whether the song is liked by the current user (dynamically calculated)
* @property ?int $play_count The number of times the song has been played by the current user (dynamically calculated)
* @property Carbon $created_at
*
* @method static self updateOrCreate(array $where, array $params)
* @method static Builder select(string $string)

View file

@ -6,17 +6,12 @@ use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
/**
* @property array<string>|null $s3_params
* @property array<string>|null $s3_params The bucket and key name of an S3 object.
*
* @method static Builder hostedOnS3()
*/
trait SupportsS3
{
/**
* Get the bucket and key name of an S3 object.
*
* @return array<string>|null
*/
protected function s3Params(): Attribute
{
return Attribute::get(function (): ?array {

View file

@ -16,7 +16,7 @@ use Laravel\Sanctum\HasApiTokens;
* @property UserPreferences $preferences
* @property int $id
* @property bool $is_admin
* @property string $lastfm_session_key
* @property ?string $lastfm_session_key
* @property string $name
* @property string $email
* @property string $password
@ -25,6 +25,7 @@ use Laravel\Sanctum\HasApiTokens;
* @method static self create(array $params)
* @method static int count()
* @method static Builder where(...$params)
* @method static self|null firstWhere(...$params)
*/
class User extends Authenticatable
{
@ -60,8 +61,6 @@ class User extends Authenticatable
/**
* Get the user's Last.fm session key.
*
* @return string|null The key if found, or null if user isn't connected to Last.fm
*/
protected function lastfmSessionKey(): Attribute
{

View file

@ -14,11 +14,6 @@ use Illuminate\Validation\Rules\Password;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
Playlist::class => PlaylistPolicy::class,
User::class => UserPolicy::class,

View file

@ -3,8 +3,8 @@
namespace App\Repositories;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Throwable;
abstract class AbstractRepository implements RepositoryInterface

View file

@ -5,7 +5,7 @@ namespace App\Repositories;
use App\Models\Album;
use App\Models\User;
use App\Repositories\Traits\Searchable;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Collection;
class AlbumRepository extends AbstractRepository
{

View file

@ -2,8 +2,8 @@
namespace App\Repositories;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
interface RepositoryInterface
{

View file

@ -11,7 +11,7 @@ use App\Repositories\Traits\Searchable;
use App\Services\Helper;
use Illuminate\Contracts\Database\Query\Builder;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Collection;
use Webmozart\Assert\Assert;
class SongRepository extends AbstractRepository

View file

@ -157,7 +157,7 @@ class FileSynchronizer
$cover = $matches ? $matches[0] : null;
return $cover && static::isImage($cover) ? $cover : null;
return $cover && self::isImage($cover) ? $cover : null;
});
}

View file

@ -55,6 +55,6 @@ class SearchService
return $this->songRepository
->search($keywords)
->get()
->map(static fn (Song $song): string => $song->id);
->map(static fn (Song $song): string => $song->id); // @phpstan-ignore-line
}
}

View file

@ -10,7 +10,7 @@ use SpotifyWebAPI\SpotifyWebAPI;
use Throwable;
/**
* @method array search(string $keywords, string|array $type, string|object $options = [])
* @method array search(string $keywords, string|array $type, array|object $options = [])
*/
class SpotifyClient
{

View file

@ -20,18 +20,12 @@ class TokenManager
public function deleteTokenByPlainTextToken(string $plainTextToken): void
{
$token = PersonalAccessToken::findToken($plainTextToken);
if ($token) {
$token->delete();
}
PersonalAccessToken::findToken($plainTextToken)?->delete();
}
public function getUserFromPlainTextToken(string $plainTextToken): ?User
{
$token = PersonalAccessToken::findToken($plainTextToken);
return $token ? $token->tokenable : null;
return PersonalAccessToken::findToken($plainTextToken)?->tokenable;
}
public function refreshToken(User $user): NewAccessToken

View file

@ -18,7 +18,7 @@ final class SongScanInformation implements Arrayable
public ?int $track,
public ?int $disc,
public ?string $lyrics,
public ?int $length,
public ?float $length,
public ?array $cover,
public ?string $path,
public ?int $mTime,

View file

@ -22,6 +22,8 @@ parameters:
- '#Call to an undefined method Illuminate\\Filesystem\\FilesystemAdapter::getAdapter\(\)#'
- '#Call to an undefined method Mockery\\ExpectationInterface|Mockery\\HigherOrderMessage::with\(\)#'
- '#Call to an undefined method Laravel\\Scout\\Builder::with\(\)#'
- '#Call to an undefined method Illuminate\\Contracts\\Database\\Query\\Builder::isStandard\(\)#'
- '#Call to an undefined method Illuminate\\Contracts\\Database\\Query\\Builder::withMeta\(\)#'
- '#should return App\\Models\\.*(\|null)? but returns Illuminate\\Database\\Eloquent\\Model(\|null)?#'
# Laravel factories allow declaration of dynamic methods as "states"
- '#Call to an undefined method Illuminate\\Database\\Eloquent\\Factories\\Factory::#'

View file

@ -7,10 +7,11 @@ use App\Models\Album;
use App\Models\User;
use App\Services\MediaMetadataService;
use Mockery;
use Mockery\MockInterface;
class AlbumCoverTest extends TestCase
{
private $mediaMetadataService;
private MediaMetadataService|MockInterface $mediaMetadataService;
public function setUp(): void
{
@ -23,21 +24,19 @@ class AlbumCoverTest extends TestCase
{
$this->expectsEvents(LibraryChanged::class);
/** @var User $user */
$user = User::factory()->admin()->create();
/** @var Album $album */
$album = Album::factory()->create(['id' => 9999]);
$this->mediaMetadataService
->shouldReceive('writeAlbumCover')
->once()
->with(Mockery::on(static function (Album $album): bool {
return $album->id === 9999;
}), 'Foo', 'jpeg');
->with(Mockery::on(static fn (Album $album) => $album->id === 9999), 'Foo', 'jpeg');
$response = $this->putAs('api/album/' . $album->id . '/cover', [
'cover' => 'data:image/jpeg;base64,Rm9v',
], User::factory()->admin()->create());
$response->assertStatus(200);
$this->putAs('api/album/' . $album->id . '/cover', ['cover' => 'data:image/jpeg;base64,Rm9v'], $user)
->assertOk();
}
public function testUpdateNotAllowedForNormalUsers(): void
@ -49,9 +48,10 @@ class AlbumCoverTest extends TestCase
->shouldReceive('writeAlbumCover')
->never();
$this->putAs('api/album/' . $album->id . '/cover', [
'cover' => 'data:image/jpeg;base64,Rm9v',
], User::factory()->create())
->assertStatus(403);
/** @var User $user */
$user = User::factory()->create();
$this->putAs('api/album/' . $album->id . '/cover', ['cover' => 'data:image/jpeg;base64,Rm9v'], $user)
->assertForbidden();
}
}

View file

@ -11,8 +11,7 @@ use Mockery\MockInterface;
class ArtistImageTest extends TestCase
{
/** @var MockInterface|MediaMetadataService */
private $mediaMetadataService;
private MediaMetadataService|MockInterface $mediaMetadataService;
public function setUp(): void
{
@ -24,19 +23,19 @@ class ArtistImageTest extends TestCase
public function testUpdate(): void
{
$this->expectsEvents(LibraryChanged::class);
/** @var User $admin */
$admin = User::factory()->admin()->create();
Artist::factory()->create(['id' => 9999]);
$this->mediaMetadataService
->shouldReceive('writeArtistImage')
->once()
->with(Mockery::on(static function (Artist $artist): bool {
return $artist->id === 9999;
}), 'Foo', 'jpeg');
->with(Mockery::on(static fn (Artist $artist) => $artist->id === 9999), 'Foo', 'jpeg');
$this->putAs('api/artist/9999/image', [
'image' => 'data:image/jpeg;base64,Rm9v',
], User::factory()->admin()->create())
->assertStatus(200);
$this->putAs('api/artist/9999/image', ['image' => 'data:image/jpeg;base64,Rm9v'], $admin)
->assertOk();
}
public function testUpdateNotAllowedForNormalUsers(): void
@ -47,9 +46,7 @@ class ArtistImageTest extends TestCase
->shouldReceive('writeArtistImage')
->never();
$this->putAs('api/artist/9999/image', [
'image' => 'data:image/jpeg;base64,Rm9v',
], User::factory()->create())
->assertStatus(403);
$this->putAs('api/artist/9999/image', ['image' => 'data:image/jpeg;base64,Rm9v'])
->assertForbidden();
}
}

View file

@ -9,7 +9,6 @@ use App\Models\Song;
use App\Models\User;
use App\Repositories\InteractionRepository;
use App\Services\DownloadService;
use Illuminate\Http\Response;
use Illuminate\Support\Collection;
use Mockery;
use Mockery\MockInterface;
@ -152,7 +151,7 @@ class DownloadTest extends TestCase
$user = User::factory()->create();
$this->get("download/playlist/{$playlist->id}?api_token=" . $user->createToken('Koel')->plainTextToken)
->assertStatus(Response::HTTP_FORBIDDEN);
->assertForbidden();
}
public function testDownloadFavorites(): void

View file

@ -32,7 +32,7 @@ class LastfmTest extends TestCase
/** @var User $user */
$user = User::factory()->create();
$this->postAs('api/lastfm/session-key', ['key' => 'foo'], $user)
->assertStatus(204);
->assertNoContent();
self::assertEquals('foo', $user->refresh()->lastfm_session_key);
}

View file

@ -100,27 +100,20 @@ class PlaylistTest extends TestCase
public function testCreatingPlaylistWithNonExistentSongsFails(): void
{
$response = $this->postAs('api/playlist', [
$this->postAs('api/playlist', [
'name' => 'Foo Bar',
'rules' => [],
'songs' => ['foo'],
]);
$response->assertUnprocessable();
])
->assertUnprocessable();
}
public function testUpdatePlaylistName(): void
{
/** @var User $user */
$user = User::factory()->create();
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create([
'user_id' => $user->id,
'name' => 'Foo',
]);
$playlist = Playlist::factory()->create(['name' => 'Foo']);
$this->putAs("api/playlist/$playlist->id", ['name' => 'Bar'], $user);
$this->putAs("api/playlist/$playlist->id", ['name' => 'Bar'], $playlist->user);
self::assertSame('Bar', $playlist->refresh()->name);
}
@ -128,26 +121,19 @@ class PlaylistTest extends TestCase
public function testNonOwnerCannotUpdatePlaylist(): void
{
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create([
'name' => 'Foo',
]);
$playlist = Playlist::factory()->create(['name' => 'Foo']);
$response = $this->putAs("api/playlist/$playlist->id", ['name' => 'Qux']);
$response->assertStatus(403);
$this->putAs("api/playlist/$playlist->id", ['name' => 'Qux'])->assertForbidden();
}
public function testDeletePlaylist(): void
{
/** @var User $user */
$user = User::factory()->create();
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create([
'user_id' => $user->id,
]);
$playlist = Playlist::factory()->create();
$this->deleteAs("api/playlist/$playlist->id", [], $user);
self::assertDatabaseMissing('playlists', ['id' => $playlist->id]);
$this->deleteAs("api/playlist/$playlist->id", [], $playlist->user);
self::assertModelMissing($playlist);
}
public function testNonOwnerCannotDeletePlaylist(): void
@ -155,7 +141,8 @@ class PlaylistTest extends TestCase
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create();
$this->deleteAs("api/playlist/$playlist->id")
->assertStatus(403);
$this->deleteAs("api/playlist/$playlist->id")->assertForbidden();
self::assertModelExists($playlist);
}
}

View file

@ -3,56 +3,56 @@
namespace Tests\Feature;
use App\Models\User;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Hash;
class ProfileTest extends TestCase
{
private User $user;
public function setUp(): void
{
parent::setUp();
$this->user = User::factory()->create(['password' => Hash::make('secret')]);
}
public function testUpdateProfileRequiresCurrentPassword(): void
{
$this->putAs('api/me', [
'name' => 'Foo',
'email' => 'bar@baz.com',
], $this->user)
->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY);
])
->assertUnprocessable();
}
public function testUpdateProfileWithoutNewPassword(): void
{
/** @var User $user */
$user = User::factory()->create(['password' => Hash::make('secret')]);
$this->putAs('api/me', [
'name' => 'Foo',
'email' => 'bar@baz.com',
'current_password' => 'secret',
], $this->user);
], $user);
$this->user->refresh();
$user->refresh();
self::assertSame('Foo', $this->user->name);
self::assertSame('bar@baz.com', $this->user->email);
self::assertTrue(Hash::check('secret', $this->user->password));
self::assertSame('Foo', $user->name);
self::assertSame('bar@baz.com', $user->email);
self::assertTrue(Hash::check('secret', $user->password));
}
public function testUpdateProfileWithNewPassword(): void
{
$this->putAs('api/me', [
/** @var User $user */
$user = User::factory()->create(['password' => Hash::make('secret')]);
$token = $this->putAs('api/me', [
'name' => 'Foo',
'email' => 'bar@baz.com',
'new_password' => 'new-secret',
'current_password' => 'secret',
], $this->user)
->assertHeader('Authorization', $this->user->refresh()->api_token);
], $user)
->headers
->get('Authorization');
self::assertSame('Foo', $this->user->name);
self::assertSame('bar@baz.com', $this->user->email);
self::assertTrue(Hash::check('new-secret', $this->user->password));
$user->refresh();
self::assertNotNull($token);
self::assertSame('Foo', $user->name);
self::assertSame('bar@baz.com', $user->email);
self::assertTrue(Hash::check('new-secret', $user->password));
}
}

View file

@ -5,12 +5,11 @@ namespace Tests\Feature;
use App\Models\Setting;
use App\Models\User;
use App\Services\MediaSyncService;
use Mockery\LegacyMockInterface;
use Mockery\MockInterface;
class SettingTest extends TestCase
{
private MediaSyncService|MockInterface|LegacyMockInterface $mediaSyncService;
private MediaSyncService|MockInterface $mediaSyncService;
public function setUp(): void
{
@ -21,9 +20,12 @@ class SettingTest extends TestCase
public function testSaveSettings(): void
{
/** @var User $admin */
$admin = User::factory()->admin()->create();
$this->mediaSyncService->shouldReceive('sync')->once();
$this->putAs('/api/settings', ['media_path' => __DIR__], User::factory()->admin()->create())
$this->putAs('/api/settings', ['media_path' => __DIR__], $admin)
->assertSuccessful();
self::assertEquals(__DIR__, Setting::get('media_path'));
@ -31,7 +33,7 @@ class SettingTest extends TestCase
public function testNonAdminCannotSaveSettings(): void
{
$this->putAs('/api/settings', ['media_path' => __DIR__], User::factory()->create())
$this->putAs('/api/settings', ['media_path' => __DIR__])
->assertForbidden();
}
}

View file

@ -19,6 +19,8 @@ class SongTest extends TestCase
public function testSingleUpdateAllInfoNoCompilation(): void
{
/** @var User $user */
$user = User::factory()->admin()->create();
$song = Song::first();
$this->putAs('/api/songs', [
@ -31,8 +33,8 @@ class SongTest extends TestCase
'track' => 1,
'disc' => 2,
],
], User::factory()->admin()->create())
->assertStatus(200);
], $user)
->assertOk();
/** @var Artist $artist */
$artist = Artist::where('name', 'John Cena')->first();
@ -53,6 +55,8 @@ class SongTest extends TestCase
public function testSingleUpdateSomeInfoNoCompilation(): void
{
/** @var User $user */
$user = User::factory()->admin()->create();
$song = Song::first();
$originalArtistId = $song->artist->id;
@ -65,8 +69,8 @@ class SongTest extends TestCase
'lyrics' => 'Lorem ipsum dolor sic amet.',
'track' => 1,
],
], User::factory()->admin()->create())
->assertStatus(200);
], $user)
->assertOk();
// We don't expect the song's artist to change
self::assertEquals($originalArtistId, Song::find($song->id)->artist->id);
@ -77,6 +81,8 @@ class SongTest extends TestCase
public function testMultipleUpdateNoCompilation(): void
{
/** @var User $user */
$user = User::factory()->admin()->create();
$songIds = Song::latest()->take(3)->pluck('id')->toArray();
$this->putAs('/api/songs', [
@ -88,8 +94,8 @@ class SongTest extends TestCase
'lyrics' => 'bar',
'track' => 9999,
],
], User::factory()->admin()->create())
->assertStatus(200);
], $user)
->assertOk();
$songs = Song::whereIn('id', $songIds)->get();
@ -105,6 +111,9 @@ class SongTest extends TestCase
public function testMultipleUpdateCreatingNewAlbumsAndArtists(): void
{
/** @var User $user */
$user = User::factory()->admin()->create();
/** @var array<array-key, Song>|Collection $originalSongs */
$originalSongs = Song::latest()->take(3)->get();
$songIds = $originalSongs->pluck('id')->toArray();
@ -118,8 +127,8 @@ class SongTest extends TestCase
'lyrics' => 'Lorem ipsum dolor sic amet.',
'track' => 1,
],
], User::factory()->admin()->create())
->assertStatus(200);
], $user)
->assertOk();
/** @var array<Song>|Collection $songs */
$songs = Song::latest()->take(3)->get();
@ -141,6 +150,8 @@ class SongTest extends TestCase
public function testSingleUpdateAllInfoWithCompilation(): void
{
/** @var User $user */
$user = User::factory()->admin()->create();
$song = Song::first();
$this->putAs('/api/songs', [
@ -154,8 +165,8 @@ class SongTest extends TestCase
'track' => 1,
'disc' => 2,
],
], User::factory()->admin()->create())
->assertStatus(200);
], $user)
->assertOk();
/** @var Album $album */
$album = Album::where('name', 'One by One')->first();
@ -182,7 +193,9 @@ class SongTest extends TestCase
{
self::assertNotEquals(0, Song::count());
$ids = Song::select('id')->get()->pluck('id')->all();
Song::deleteByChunk($ids, 'id', 1);
self::assertEquals(0, Song::count());
}
}

View file

@ -9,12 +9,11 @@ use App\Models\Song;
use App\Models\User;
use App\Services\UploadService;
use Illuminate\Http\UploadedFile;
use Mockery\LegacyMockInterface;
use Mockery\MockInterface;
class UploadTest extends TestCase
{
private UploadService|MockInterface|LegacyMockInterface $uploadService;
private UploadService|MockInterface $uploadService;
public function setUp(): void
{
@ -32,7 +31,7 @@ class UploadTest extends TestCase
->shouldReceive('handleUploadedFile')
->never();
$this->postAs('/api/upload', ['file' => $file], User::factory()->create())->assertStatus(403);
$this->postAs('/api/upload', ['file' => $file])->assertForbidden();
}
/** @return array<mixed> */
@ -49,32 +48,35 @@ class UploadTest extends TestCase
{
$file = UploadedFile::fake()->create('foo.mp3', 2048);
/** @var User $admin */
$admin = User::factory()->admin()->create();
$this->uploadService
->shouldReceive('handleUploadedFile')
->once()
->with($file)
->andThrow($exceptionClass);
$this->postAs('/api/upload', ['file' => $file], User::factory()->admin()->create())
->assertStatus($statusCode);
$this->postAs('/api/upload', ['file' => $file], $admin)->assertStatus($statusCode);
}
public function testPost(): void
{
Setting::set('media_path', '/media/koel');
$file = UploadedFile::fake()->create('foo.mp3', 2048);
/** @var Song $song */
$song = Song::factory()->create();
/** @var User $admin */
$admin = User::factory()->admin()->create();
$this->uploadService
->shouldReceive('handleUploadedFile')
->once()
->with($file)
->andReturn($song);
$this->postAs('/api/upload', ['file' => $file], User::factory()->admin()->create())
->assertJsonStructure([
'song',
'album',
]);
$this->postAs('/api/upload', ['file' => $file], $admin)->assertJsonStructure(['song', 'album']);
}
}

View file

@ -7,11 +7,6 @@ use Illuminate\Support\Facades\Hash;
class UserTest extends TestCase
{
public function setUp(): void
{
parent::setUp();
}
public function testNonAdminCannotCreateUser(): void
{
$this->postAs('api/user', [
@ -24,15 +19,17 @@ class UserTest extends TestCase
public function testAdminCreatesUser(): void
{
/** @var User $admin */
$admin = User::factory()->admin()->create();
$this->postAs('api/user', [
'name' => 'Foo',
'email' => 'bar@baz.com',
'password' => 'secret',
'is_admin' => true,
], User::factory()->admin()->create())
], $admin)
->assertSuccessful();
/** @var User $user */
$user = User::firstWhere('email', 'bar@baz.com');
self::assertTrue(Hash::check('secret', $user->password));
@ -43,6 +40,9 @@ class UserTest extends TestCase
public function testAdminUpdatesUser(): void
{
/** @var User $admin */
$admin = User::factory()->admin()->create();
/** @var User $user */
$user = User::factory()->admin()->create(['password' => 'secret']);
@ -51,7 +51,8 @@ class UserTest extends TestCase
'email' => 'bar@baz.com',
'password' => 'new-secret',
'is_admin' => false,
], User::factory()->admin()->create());
], $admin)
->assertSuccessful();
$user->refresh();
@ -65,10 +66,12 @@ class UserTest extends TestCase
{
/** @var User $user */
$user = User::factory()->create();
/** @var User $admin */
$admin = User::factory()->admin()->create();
$this->deleteAs("api/user/$user->id", [], $admin);
self::assertDatabaseMissing('users', ['id' => $user->id]);
self::assertModelMissing($user);
}
public function testSeppukuNotAllowed(): void
@ -77,9 +80,7 @@ class UserTest extends TestCase
$admin = User::factory()->admin()->create();
// A user can't delete himself
$this->deleteAs("api/user/$admin->id", [], $admin)
->assertStatus(403);
self::assertDatabaseHas('users', ['id' => $admin->id]);
$this->deleteAs("api/user/$admin->id", [], $admin)->assertForbidden();
self::assertModelExists($admin);
}
}

View file

@ -5,7 +5,7 @@ namespace Tests\Feature\V6;
use App\Models\Playlist;
use App\Models\Song;
use App\Models\User;
use Illuminate\Http\Response;
use Illuminate\Support\Collection;
class PlaylistSongTest extends TestCase
{
@ -53,7 +53,7 @@ class PlaylistSongTest extends TestCase
$playlist->songs()->attach(Song::factory(5)->create());
$this->getAs('api/playlists/' . $playlist->id . '/songs')
->assertStatus(Response::HTTP_FORBIDDEN);
->assertForbidden();
}
public function testAddSongsToPlaylist(): void
@ -61,6 +61,7 @@ class PlaylistSongTest extends TestCase
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create();
/** @var Collection|array<array-key, Song> $songs */
$songs = Song::factory(2)->create();
$this->postAs('api/playlists/' . $playlist->id . '/songs', [
@ -77,7 +78,10 @@ class PlaylistSongTest extends TestCase
$playlist = Playlist::factory()->create();
$toRemainSongs = Song::factory(5)->create();
/** @var Collection|array<array-key, Song> $toBeRemovedSongs */
$toBeRemovedSongs = Song::factory(2)->create();
$playlist->songs()->attach($toRemainSongs->merge($toBeRemovedSongs));
self::assertCount(7, $playlist->songs);
@ -103,10 +107,10 @@ class PlaylistSongTest extends TestCase
$song = Song::factory()->create();
$this->postAs('api/playlists/' . $playlist->id . '/songs', ['songs' => [$song->id]])
->assertStatus(Response::HTTP_FORBIDDEN);
->assertForbidden();
$this->deleteAs('api/playlists/' . $playlist->id . '/songs', ['songs' => [$song->id]])
->assertStatus(Response::HTTP_FORBIDDEN);
->assertForbidden();
}
public function testSmartPlaylistContentCannotBeModified(): void
@ -128,12 +132,14 @@ class PlaylistSongTest extends TestCase
],
]);
$songs = Song::factory(2)->create()->map(static fn (Song $song) => $song->id)->all();
/** @var Collection|array<array-key, Song> $songs */
$songs = Song::factory(2)->create();
$songIds = $songs->map(static fn (Song $song) => $song->id)->all();
$this->postAs('api/playlists/' . $playlist->id . '/songs', ['songs' => $songs], $playlist->user)
->assertStatus(Response::HTTP_FORBIDDEN);
$this->postAs('api/playlists/' . $playlist->id . '/songs', ['songs' => $songIds], $playlist->user)
->assertForbidden();
$this->deleteAs('api/playlists/' . $playlist->id . '/songs', ['songs' => $songs], $playlist->user)
->assertStatus(Response::HTTP_FORBIDDEN);
$this->deleteAs('api/playlists/' . $playlist->id . '/songs', ['songs' => $songIds], $playlist->user)
->assertForbidden();
}
}

View file

@ -43,7 +43,7 @@ class FileSynchronizerTest extends TestCase
];
self::assertArraySubset($expectedData, $info->toArray());
self::assertEqualsWithDelta(10, $info->length, 0.001);
self::assertEqualsWithDelta(10, $info->length, 0.1);
}
/** @test */

View file

@ -2,19 +2,9 @@
namespace Tests\Integration\Services;
use App\Services\SmartPlaylistService;
use Carbon\Carbon;
use Tests\TestCase;
class SmartPlaylistServiceTest extends TestCase
{
private SmartPlaylistService $service;
public function setUp(): void
{
parent::setUp();
$this->service = app(SmartPlaylistService::class);
Carbon::setTestNow(new Carbon('2018-07-15'));
}
// @todo write tests
}

View file

@ -4,7 +4,6 @@ namespace Tests\Unit\Services;
use App\Models\Song;
use App\Repositories\SongRepository;
use App\Services\Helper;
use App\Services\MediaMetadataService;
use App\Services\S3Service;
use Aws\CommandInterface;
@ -12,15 +11,14 @@ use Aws\S3\S3ClientInterface;
use GuzzleHttp\Psr7\Request;
use Illuminate\Cache\Repository as Cache;
use Mockery;
use Mockery\LegacyMockInterface;
use Mockery\MockInterface;
use Tests\TestCase;
class S3ServiceTest extends TestCase
{
private $s3Client;
private $cache;
private $metadataService;
private $songRepository;
private $helper;
private LegacyMockInterface|MockInterface|S3ClientInterface $s3Client;
private LegacyMockInterface|Cache|MockInterface $cache;
private S3Service $s3Service;
public function setUp(): void
@ -29,17 +27,11 @@ class S3ServiceTest extends TestCase
$this->s3Client = Mockery::mock(S3ClientInterface::class);
$this->cache = Mockery::mock(Cache::class);
$this->metadataService = Mockery::mock(MediaMetadataService::class);
$this->songRepository = Mockery::mock(SongRepository::class);
$this->helper = Mockery::mock(Helper::class);
$this->s3Service = new S3Service(
$this->s3Client,
$this->cache,
$this->metadataService,
$this->songRepository,
$this->helper
);
$metadataService = Mockery::mock(MediaMetadataService::class);
$songRepository = Mockery::mock(SongRepository::class);
$this->s3Service = new S3Service($this->s3Client, $this->cache, $metadataService, $songRepository);
}
public function testGetSongPublicUrl(): void
@ -48,6 +40,7 @@ class S3ServiceTest extends TestCase
$song = Song::factory()->create(['path' => 's3://foo/bar']);
$cmd = Mockery::mock(CommandInterface::class);
$this->s3Client->shouldReceive('getCommand')
->with('GetObject', [
'Bucket' => 'foo',

View file

@ -14,7 +14,7 @@ use Tests\TestCase;
class SpotifyServiceTest extends TestCase
{
private SpotifyService $service;
private SpotifyClient|LegacyMockInterface|MockInterface $client;
private SpotifyClient|MockInterface|LegacyMockInterface $client;
public function setUp(): void
{
@ -48,7 +48,7 @@ class SpotifyServiceTest extends TestCase
$this->client->shouldNotReceive('search');
self::assertNull($this->service->tryGetArtistImage(Artist::factory()->create()));
self::assertNull($this->service->tryGetArtistImage(Mockery::mock(Artist::class)));
}
public function testTryGetAlbumImage(): void
@ -73,7 +73,7 @@ class SpotifyServiceTest extends TestCase
$this->client->shouldNotReceive('search');
self::assertNull($this->service->tryGetAlbumCover(Album::factory()->create()));
self::assertNull($this->service->tryGetAlbumCover(Mockery::mock(Album::class)));
}
/** @return array<mixed> */