mirror of
https://github.com/koel/koel
synced 2024-11-10 06:34:14 +00:00
feat(test): add tests for cloud storages
This commit is contained in:
parent
3bf620039f
commit
ff79332c6a
64 changed files with 379 additions and 83 deletions
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\License\LicenseServiceInterface;
|
||||
use App\Services\License\Contracts\LicenseServiceInterface;
|
||||
use Illuminate\Console\Command;
|
||||
use Throwable;
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\License\LicenseServiceInterface;
|
||||
use App\Services\License\Contracts\LicenseServiceInterface;
|
||||
use App\Values\LicenseStatus;
|
||||
use Illuminate\Console\Command;
|
||||
use Throwable;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\License\LicenseServiceInterface;
|
||||
use App\Services\License\Contracts\LicenseServiceInterface;
|
||||
use Illuminate\Console\Command;
|
||||
use Throwable;
|
||||
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Libraries\WatchRecord\InotifyWatchRecord;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use App\Repositories\UserRepository;
|
||||
use App\Services\MediaScanner;
|
||||
use App\Values\ScanConfiguration;
|
||||
use App\Values\ScanResult;
|
||||
use App\Values\WatchRecord\InotifyWatchRecord;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Str;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
namespace App\Console\Commands;
|
||||
|
||||
use App\Facades\License;
|
||||
use App\Services\SongStorage\DropboxStorage;
|
||||
use App\Services\SongStorages\DropboxStorage;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Console\Kernel as Artisan;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
|
25
app/Filesystems/DropboxFilesystem.php
Normal file
25
app/Filesystems/DropboxFilesystem.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace App\Filesystems;
|
||||
|
||||
use DateTimeInterface;
|
||||
use League\Flysystem\Filesystem;
|
||||
use Spatie\FlysystemDropbox\DropboxAdapter;
|
||||
|
||||
class DropboxFilesystem extends Filesystem
|
||||
{
|
||||
public function __construct(private DropboxAdapter $adapter)
|
||||
{
|
||||
parent::__construct($adapter, ['case_sensitive' => false]);
|
||||
}
|
||||
|
||||
public function temporaryUrl(string $path, ?DateTimeInterface $expiresAt = null, array $config = []): string
|
||||
{
|
||||
return $this->adapter->getUrl($path);
|
||||
}
|
||||
|
||||
public function getAdapter(): DropboxAdapter
|
||||
{
|
||||
return $this->adapter;
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@ namespace App\Http\Controllers\API;
|
|||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\API\ActivateLicenseRequest;
|
||||
use App\Models\License;
|
||||
use App\Services\License\LicenseServiceInterface;
|
||||
use App\Services\License\Contracts\LicenseServiceInterface;
|
||||
|
||||
class ActivateLicenseController extends Controller
|
||||
{
|
||||
|
|
|
@ -14,7 +14,7 @@ use App\Repositories\SongRepository;
|
|||
use App\Services\ApplicationInformationService;
|
||||
use App\Services\ITunesService;
|
||||
use App\Services\LastfmService;
|
||||
use App\Services\License\LicenseServiceInterface;
|
||||
use App\Services\License\Contracts\LicenseServiceInterface;
|
||||
use App\Services\QueueService;
|
||||
use App\Services\SpotifyService;
|
||||
use App\Services\YouTubeService;
|
||||
|
|
|
@ -6,7 +6,7 @@ use App\Exceptions\SongPathNotFoundException;
|
|||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\API\ObjectStorage\S3\PutSongRequest;
|
||||
use App\Http\Requests\API\ObjectStorage\S3\RemoveSongRequest;
|
||||
use App\Services\SongStorage\S3LambdaStorage;
|
||||
use App\Services\SongStorages\S3LambdaStorage;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ use App\Http\Resources\SongResource;
|
|||
use App\Models\User;
|
||||
use App\Repositories\AlbumRepository;
|
||||
use App\Repositories\SongRepository;
|
||||
use App\Services\SongStorage\SongStorage;
|
||||
use App\Services\SongStorages\SongStorage;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Models;
|
||||
|
||||
use App\Builders\AlbumBuilder;
|
||||
use App\Models\Concerns\SupportsDeleteWhereValueNotIn;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace App\Models;
|
|||
|
||||
use App\Builders\ArtistBuilder;
|
||||
use App\Facades\Util;
|
||||
use App\Models\Concerns\SupportsDeleteWhereValueNotIn;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
namespace App\Models\Concerns;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\DB;
|
|
@ -33,7 +33,7 @@ class Setting extends Model
|
|||
* @param array|string $key the key of the setting, or an associative array of settings,
|
||||
* in which case $value will be discarded
|
||||
*/
|
||||
public static function set(array|string $key, $value): void
|
||||
public static function set(array|string $key, $value = ''): void
|
||||
{
|
||||
if (is_array($key)) {
|
||||
foreach ($key as $k => $v) {
|
||||
|
@ -45,9 +45,4 @@ class Setting extends Model
|
|||
|
||||
self::query()->updateOrCreate(compact('key'), compact('value'));
|
||||
}
|
||||
|
||||
public static function unset(string $key): void
|
||||
{
|
||||
static::set($key, null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,11 +3,12 @@
|
|||
namespace App\Models;
|
||||
|
||||
use App\Builders\SongBuilder;
|
||||
use App\Models\Concerns\SupportsDeleteWhereValueNotIn;
|
||||
use App\Values\SongStorageMetadata\Contracts\SongStorageMetadata;
|
||||
use App\Values\SongStorageMetadata\DropboxMetadata;
|
||||
use App\Values\SongStorageMetadata\LegacyS3Metadata;
|
||||
use App\Values\SongStorageMetadata\LocalMetadata;
|
||||
use App\Values\SongStorageMetadata\S3CompatibleMetadata;
|
||||
use App\Values\SongStorageMetadata\SongStorageMetadata;
|
||||
use App\Values\SongStorageTypes;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
|
|
|
@ -4,10 +4,10 @@ namespace App\Providers;
|
|||
|
||||
use App\Services\ApiClients\ApiClient;
|
||||
use App\Services\ApiClients\LemonSqueezyApiClient;
|
||||
use App\Services\Contracts\MusicEncyclopedia;
|
||||
use App\Services\LastfmService;
|
||||
use App\Services\License\LicenseServiceInterface;
|
||||
use App\Services\License\Contracts\LicenseServiceInterface;
|
||||
use App\Services\LicenseService;
|
||||
use App\Services\MusicEncyclopedia;
|
||||
use App\Services\NullMusicEncyclopedia;
|
||||
use App\Services\SpotifyService;
|
||||
use Illuminate\Database\DatabaseManager;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Services\License\LicenseServiceInterface;
|
||||
use App\Services\License\Contracts\LicenseServiceInterface;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class LicenseServiceProvider extends ServiceProvider
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Services\SongStorage\DropboxStorage;
|
||||
use App\Services\SongStorage\LocalStorage;
|
||||
use App\Services\SongStorage\S3CompatibleStorage;
|
||||
use App\Services\SongStorage\SongStorage;
|
||||
use App\Services\SongStorages\DropboxStorage;
|
||||
use App\Services\SongStorages\LocalStorage;
|
||||
use App\Services\SongStorages\S3CompatibleStorage;
|
||||
use App\Services\SongStorages\SongStorage;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class SongStorageServiceProvider extends ServiceProvider
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Repositories;
|
||||
namespace App\Repositories\Contracts;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Repositories\Contracts\RepositoryInterface;
|
||||
use Illuminate\Contracts\Auth\Guard;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
namespace App\Services\Contracts;
|
||||
|
||||
use App\Models\Album;
|
||||
use App\Models\Artist;
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
namespace App\Services\Contracts;
|
||||
|
||||
use App\Models\Song;
|
||||
|
|
@ -4,9 +4,9 @@ namespace App\Services;
|
|||
|
||||
use App\Models\Song;
|
||||
use App\Models\SongZipArchive;
|
||||
use App\Services\SongStorage\CloudStorage;
|
||||
use App\Services\SongStorage\DropboxStorage;
|
||||
use App\Services\SongStorage\S3CompatibleStorage;
|
||||
use App\Services\SongStorages\CloudStorage;
|
||||
use App\Services\SongStorages\DropboxStorage;
|
||||
use App\Services\SongStorages\S3CompatibleStorage;
|
||||
use App\Values\SongStorageTypes;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
|
|
@ -99,6 +99,7 @@ class FileScanner
|
|||
$data['owner_id'] = $config->owner->id;
|
||||
}
|
||||
|
||||
// @todo Decouple song creation from scanning.
|
||||
$this->song = Song::query()->updateOrCreate(['path' => $this->filePath], $data); // @phpstan-ignore-line
|
||||
|
||||
return ScanResult::success($this->filePath);
|
||||
|
|
|
@ -7,6 +7,7 @@ use App\Models\Artist;
|
|||
use App\Models\Song;
|
||||
use App\Models\User;
|
||||
use App\Services\ApiClients\LastfmClient;
|
||||
use App\Services\Contracts\MusicEncyclopedia;
|
||||
use App\Values\AlbumInformation;
|
||||
use App\Values\ArtistInformation;
|
||||
use GuzzleHttp\Promise\Promise;
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace App\Services\License;
|
|||
|
||||
use App\Exceptions\MethodNotImplementedException;
|
||||
use App\Models\License;
|
||||
use App\Services\License\Contracts\LicenseServiceInterface;
|
||||
use App\Values\LicenseStatus;
|
||||
|
||||
class CommunityLicenseService implements LicenseServiceInterface
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\License;
|
||||
namespace App\Services\License\Contracts;
|
||||
|
||||
use App\Models\License;
|
||||
use App\Values\LicenseStatus;
|
|
@ -5,7 +5,7 @@ namespace App\Services;
|
|||
use App\Exceptions\FailedToActivateLicenseException;
|
||||
use App\Models\License;
|
||||
use App\Services\ApiClients\ApiClient;
|
||||
use App\Services\License\LicenseServiceInterface;
|
||||
use App\Services\License\Contracts\LicenseServiceInterface;
|
||||
use App\Values\LicenseInstance;
|
||||
use App\Values\LicenseMeta;
|
||||
use App\Values\LicenseStatus;
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace App\Services;
|
|||
|
||||
use App\Models\Album;
|
||||
use App\Models\Artist;
|
||||
use App\Services\Contracts\MusicEncyclopedia;
|
||||
use App\Values\AlbumInformation;
|
||||
use App\Values\ArtistInformation;
|
||||
use Illuminate\Cache\Repository as Cache;
|
||||
|
|
|
@ -4,13 +4,13 @@ namespace App\Services;
|
|||
|
||||
use App\Events\LibraryChanged;
|
||||
use App\Events\MediaScanCompleted;
|
||||
use App\Libraries\WatchRecord\WatchRecordInterface;
|
||||
use App\Models\Song;
|
||||
use App\Repositories\SettingRepository;
|
||||
use App\Repositories\SongRepository;
|
||||
use App\Values\ScanConfiguration;
|
||||
use App\Values\ScanResult;
|
||||
use App\Values\ScanResultCollection;
|
||||
use App\Values\WatchRecord\Contracts\WatchRecordInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use SplFileInfo;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace App\Services;
|
|||
|
||||
use App\Models\Album;
|
||||
use App\Models\Artist;
|
||||
use App\Services\Contracts\MusicEncyclopedia;
|
||||
use App\Values\AlbumInformation;
|
||||
use App\Values\ArtistInformation;
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ use App\Models\Album;
|
|||
use App\Models\Artist;
|
||||
use App\Models\Song;
|
||||
use App\Repositories\SongRepository;
|
||||
use App\Services\SongStorage\SongStorage;
|
||||
use App\Services\SongStorages\SongStorage;
|
||||
use App\Values\SongUpdateData;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\SongStorage;
|
||||
namespace App\Services\SongStorages;
|
||||
|
||||
use App\Exceptions\SongUploadFailedException;
|
||||
use App\Models\Song;
|
|
@ -1,7 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\SongStorage;
|
||||
namespace App\Services\SongStorages;
|
||||
|
||||
use App\Filesystems\DropboxFilesystem;
|
||||
use App\Models\Song;
|
||||
use App\Models\User;
|
||||
use App\Services\FileScanner;
|
||||
|
@ -11,22 +12,17 @@ use Illuminate\Support\Facades\Cache;
|
|||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use League\Flysystem\Filesystem;
|
||||
use Spatie\Dropbox\Client;
|
||||
use Spatie\FlysystemDropbox\DropboxAdapter;
|
||||
|
||||
final class DropboxStorage extends CloudStorage
|
||||
{
|
||||
public Filesystem $filesystem;
|
||||
private DropboxAdapter $adapter;
|
||||
|
||||
public function __construct(protected FileScanner $scanner, private array $config)
|
||||
{
|
||||
public function __construct(
|
||||
protected FileScanner $scanner,
|
||||
private DropboxFilesystem $filesystem,
|
||||
private array $config
|
||||
) {
|
||||
parent::__construct($scanner);
|
||||
|
||||
$client = new Client($this->maybeRefreshAccessToken());
|
||||
$this->adapter = new DropboxAdapter($client);
|
||||
$this->filesystem = new Filesystem($this->adapter, ['case_sensitive' => false]);
|
||||
$this->filesystem->getAdapter()->getClient()->setAccessToken($this->maybeRefreshAccessToken());
|
||||
}
|
||||
|
||||
public function storeUploadedFile(UploadedFile $file, User $uploader): Song
|
||||
|
@ -37,6 +33,7 @@ final class DropboxStorage extends CloudStorage
|
|||
$key = $this->generateStorageKey($file->getClientOriginalName(), $uploader);
|
||||
|
||||
$this->filesystem->write($key, File::get($result->path));
|
||||
|
||||
$song->update([
|
||||
'path' => "dropbox://$key",
|
||||
'storage' => SongStorageTypes::DROPBOX,
|
||||
|
@ -61,20 +58,20 @@ final class DropboxStorage extends CloudStorage
|
|||
->post('https://api.dropboxapi.com/oauth2/token', [
|
||||
'refresh_token' => $this->config['refresh_token'],
|
||||
'grant_type' => 'refresh_token',
|
||||
])->json();
|
||||
]);
|
||||
|
||||
Cache::put(
|
||||
'dropbox_access_token',
|
||||
$response['access_token'],
|
||||
now()->addSeconds($response['expires_in'] - 60) // 60 seconds buffer
|
||||
$response->json('access_token'),
|
||||
now()->addSeconds($response->json('expires_in') - 60) // 60 seconds buffer
|
||||
);
|
||||
|
||||
return $response['access_token'];
|
||||
return $response->json('access_token');
|
||||
}
|
||||
|
||||
public function getSongPresignedUrl(Song $song): string
|
||||
{
|
||||
return $this->adapter->getUrl($song->storage_metadata->getPath());
|
||||
return $this->filesystem->temporaryUrl($song->storage_metadata->getPath());
|
||||
}
|
||||
|
||||
public function supported(): bool
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\SongStorage;
|
||||
namespace App\Services\SongStorages;
|
||||
|
||||
use App\Exceptions\MediaPathNotSetException;
|
||||
use App\Exceptions\SongUploadFailedException;
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\SongStorage;
|
||||
namespace App\Services\SongStorages;
|
||||
|
||||
use App\Models\Song;
|
||||
use App\Models\User;
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\SongStorage;
|
||||
namespace App\Services\SongStorages;
|
||||
|
||||
use App\Events\LibraryChanged;
|
||||
use App\Exceptions\MethodNotImplementedException;
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\SongStorage;
|
||||
namespace App\Services\SongStorages;
|
||||
|
||||
use App\Exceptions\KoelPlusRequiredException;
|
||||
use App\Models\Song;
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Services\Streamers;
|
||||
|
||||
use App\Services\SongStorage\DropboxStorage;
|
||||
use App\Services\SongStorages\DropboxStorage;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Routing\Redirector;
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Services\Streamers;
|
||||
|
||||
use App\Services\SongStorage\S3CompatibleStorage;
|
||||
use App\Services\SongStorages\S3CompatibleStorage;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Routing\Redirector;
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Values;
|
||||
|
||||
use App\Values\Concerns\FormatsLastFmText;
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Values;
|
||||
|
||||
use App\Values\Concerns\FormatsLastFmText;
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
|
||||
final class ArtistInformation implements Arrayable
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Values;
|
||||
namespace App\Values\Concerns;
|
||||
|
||||
trait FormatsLastFmText
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Values\SongStorageMetadata;
|
||||
namespace App\Values\SongStorageMetadata\Contracts;
|
||||
|
||||
interface SongStorageMetadata
|
||||
{
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace App\Values\SongStorageMetadata;
|
||||
|
||||
use App\Values\SongStorageMetadata\Contracts\SongStorageMetadata;
|
||||
|
||||
class DropboxMetadata implements SongStorageMetadata
|
||||
{
|
||||
private function __construct(public string $path)
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace App\Values\SongStorageMetadata;
|
||||
|
||||
use App\Values\SongStorageMetadata\Contracts\SongStorageMetadata;
|
||||
|
||||
final class LocalMetadata implements SongStorageMetadata
|
||||
{
|
||||
private function __construct(public string $path)
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace App\Values\SongStorageMetadata;
|
||||
|
||||
use App\Values\SongStorageMetadata\Contracts\SongStorageMetadata;
|
||||
|
||||
class S3CompatibleMetadata implements SongStorageMetadata
|
||||
{
|
||||
private function __construct(public string $bucket, public string $key)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Libraries\WatchRecord;
|
||||
namespace App\Values\WatchRecord\Contracts;
|
||||
|
||||
interface WatchRecordInterface
|
||||
{
|
|
@ -1,6 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace App\Libraries\WatchRecord;
|
||||
namespace App\Values\WatchRecord;
|
||||
|
||||
use App\Values\WatchRecord\Contracts\WatchRecordInterface;
|
||||
|
||||
class InotifyWatchRecord extends WatchRecord implements WatchRecordInterface
|
||||
{
|
|
@ -1,6 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace App\Libraries\WatchRecord;
|
||||
namespace App\Values\WatchRecord;
|
||||
|
||||
use App\Values\WatchRecord\Contracts\WatchRecordInterface;
|
||||
|
||||
abstract class WatchRecord implements WatchRecordInterface
|
||||
{
|
|
@ -4,7 +4,7 @@ namespace Tests\Fakes;
|
|||
|
||||
use App\Exceptions\MethodNotImplementedException;
|
||||
use App\Models\License;
|
||||
use App\Services\License\LicenseServiceInterface;
|
||||
use App\Services\License\Contracts\LicenseServiceInterface;
|
||||
use App\Values\LicenseStatus;
|
||||
|
||||
class FakePlusLicenseService implements LicenseServiceInterface
|
||||
|
|
|
@ -27,7 +27,7 @@ class UploadTest extends TestCase
|
|||
|
||||
public function testUnauthorizedPost(): void
|
||||
{
|
||||
Setting::unset('media_path');
|
||||
Setting::set('media_path', '');
|
||||
|
||||
$this->postAs('/api/upload', ['file' => $this->file])->assertForbidden();
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ class UploadTest extends TestCase
|
|||
|
||||
public function testUploadFailsIfMediaPathIsNotSet(): void
|
||||
{
|
||||
Setting::unset('media_path');
|
||||
Setting::set('media_path', '');
|
||||
|
||||
$this->postAs('/api/upload', ['file' => $this->file], create_admin())->assertForbidden();
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Tests\Integration\Factories;
|
||||
|
||||
use App\Exceptions\KoelPlusRequiredException;
|
||||
use App\Models\Song;
|
||||
use App\Services\Streamers\PhpStreamer;
|
||||
use App\Services\Streamers\S3CompatibleStreamer;
|
||||
|
@ -10,6 +11,8 @@ use App\Services\Streamers\TranscodingStreamer;
|
|||
use App\Services\Streamers\XAccelRedirectStreamer;
|
||||
use App\Services\Streamers\XSendFileStreamer;
|
||||
use App\Services\TranscodingService;
|
||||
use App\Values\SongStorageTypes;
|
||||
use Exception;
|
||||
use Mockery;
|
||||
use phpmock\mockery\PHPMockery;
|
||||
use Tests\TestCase;
|
||||
|
@ -28,12 +31,41 @@ class StreamerFactoryTest extends TestCase
|
|||
PHPMockery::mock('App\Services\Streamers', 'file_exists')->andReturn(true);
|
||||
}
|
||||
|
||||
public function testCreateS3Streamer(): void
|
||||
public function testCreateStreamer(): void
|
||||
{
|
||||
/** @var Song $song */
|
||||
$song = Song::factory()->make(['path' => 's3://bucket/foo.mp3']);
|
||||
collect(SongStorageTypes::ALL_TYPES)
|
||||
->each(function (?string $type): void {
|
||||
switch ($type) {
|
||||
case SongStorageTypes::S3:
|
||||
self::expectException(KoelPlusRequiredException::class);
|
||||
$this->streamerFactory->createStreamer(
|
||||
Song::factory()->make(['path' => "s3://bucket/foo.mp3", 'storage' => $type])
|
||||
);
|
||||
break;
|
||||
|
||||
self::assertInstanceOf(S3CompatibleStreamer::class, $this->streamerFactory->createStreamer($song));
|
||||
case SongStorageTypes::S3_LAMBDA:
|
||||
self::assertInstanceOf(S3CompatibleStreamer::class, $this->streamerFactory->createStreamer(
|
||||
Song::factory()->make(['path' => "s3://bucket/foo.mp3", 'storage' => $type])
|
||||
));
|
||||
break;
|
||||
|
||||
case SongStorageTypes::DROPBOX:
|
||||
self::expectException(KoelPlusRequiredException::class);
|
||||
$this->streamerFactory->createStreamer(
|
||||
Song::factory()->make(['path' => "dropbox://foo.mp3", 'storage' => $type])
|
||||
);
|
||||
break;
|
||||
|
||||
case SongStorageTypes::LOCAL:
|
||||
self::assertInstanceOf(PhpStreamer::class, $this->streamerFactory->createStreamer(
|
||||
Song::factory()->make(['path' => test_path('songs/blank.mp3'), 'storage' => $type])
|
||||
));
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception("Unhandled storage type: $type");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function testCreateTranscodingStreamerIfSupported(): void
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Integration\KoelPlus\Services\SongStorages;
|
||||
|
||||
use App\Filesystems\DropboxFilesystem;
|
||||
use App\Models\Song;
|
||||
use App\Services\SongStorages\DropboxStorage;
|
||||
use Illuminate\Http\Client\Request;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Mockery;
|
||||
use Mockery\MockInterface;
|
||||
use Spatie\Dropbox\Client;
|
||||
use Spatie\FlysystemDropbox\DropboxAdapter;
|
||||
use Tests\PlusTestCase;
|
||||
|
||||
use function Tests\create_user;
|
||||
use function Tests\test_path;
|
||||
|
||||
class DropboxStorageTest extends PlusTestCase
|
||||
{
|
||||
private MockInterface|DropboxFilesystem $filesystem;
|
||||
private MockInterface|Client $client;
|
||||
private UploadedFile $file;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
config([
|
||||
'koel.storage_driver' => 'dropbox',
|
||||
'filesystems.disks.dropbox' => [
|
||||
'app_key' => 'dropbox-key',
|
||||
'app_secret' => 'dropbox-secret',
|
||||
'refresh_token' => 'coca-cola',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->client = $this->mock(Client::class);
|
||||
$this->filesystem = $this->mock(DropboxFilesystem::class);
|
||||
|
||||
$this->filesystem->allows('getAdapter')->andReturns(
|
||||
Mockery::mock(DropboxAdapter::class, ['getClient' => $this->client])
|
||||
);
|
||||
|
||||
self::mockRefreshAccessTokenCall();
|
||||
|
||||
$this->file = UploadedFile::fromFile(test_path('songs/full.mp3'), 'song.mp3'); //@phpstan-ignore-line
|
||||
}
|
||||
|
||||
public function testSupported(): void
|
||||
{
|
||||
$this->client->allows('setAccessToken');
|
||||
|
||||
self::assertTrue(app(DropboxStorage::class)->supported());
|
||||
}
|
||||
|
||||
public function testStoreUploadedFile(): void
|
||||
{
|
||||
$this->client->shouldReceive('setAccessToken')->with('free-bird')->once();
|
||||
|
||||
/** @var DropboxStorage $service */
|
||||
$service = app(DropboxStorage::class);
|
||||
|
||||
Http::assertSent(static function (Request $request) {
|
||||
return $request->hasHeader('Authorization', 'Basic ' . base64_encode('dropbox-key:dropbox-secret'))
|
||||
&& $request->isForm()
|
||||
&& $request['refresh_token'] === 'coca-cola'
|
||||
&& $request['grant_type'] === 'refresh_token';
|
||||
});
|
||||
|
||||
self::assertSame(0, Song::query()->where('storage', 'dropbox')->count());
|
||||
|
||||
$this->filesystem->shouldReceive('write')->once();
|
||||
$service->storeUploadedFile($this->file, create_user());
|
||||
|
||||
self::assertSame(1, Song::query()->where('storage', 'dropbox')->count());
|
||||
self::assertSame('free-bird', Cache::get('dropbox_access_token'));
|
||||
}
|
||||
|
||||
public function testAccessTokenCache(): void
|
||||
{
|
||||
Cache::put('dropbox_access_token', 'cached-token', now()->addHour());
|
||||
|
||||
$this->client->shouldReceive('setAccessToken')->with('cached-token')->once();
|
||||
app(DropboxStorage::class);
|
||||
|
||||
self::assertSame('cached-token', Cache::get('dropbox_access_token'));
|
||||
Http::assertNothingSent();
|
||||
}
|
||||
|
||||
public function testGetSongPresignedUrl(): void
|
||||
{
|
||||
$this->client->allows('setAccessToken');
|
||||
|
||||
/** @var Song $song */
|
||||
$song = Song::factory()->create(['path' => 'dropbox://song.mp3', 'storage' => 'dropbox']);
|
||||
|
||||
/** @var DropboxStorage $service */
|
||||
$service = app(DropboxStorage::class);
|
||||
|
||||
$this->filesystem->shouldReceive('temporaryUrl')
|
||||
->once()
|
||||
->with('song.mp3')
|
||||
->andReturn('https://dropbox.com/song.mp3?token=123');
|
||||
|
||||
self::assertSame('https://dropbox.com/song.mp3?token=123', $service->getSongPresignedUrl($song));
|
||||
}
|
||||
|
||||
private static function mockRefreshAccessTokenCall(): void
|
||||
{
|
||||
Http::preventStrayRequests();
|
||||
|
||||
Http::fake([
|
||||
'https://api.dropboxapi.com/oauth2/token' => Http::response([
|
||||
'access_token' => 'free-bird',
|
||||
'expires_in' => 3600,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Integration\KoelPlus\Services\FileStorage;
|
||||
namespace Tests\Integration\KoelPlus\Services\SongStorages;
|
||||
|
||||
use App\Models\Song;
|
||||
use App\Services\SongStorage\S3CompatibleStorage;
|
||||
use App\Services\SongStorages\S3CompatibleStorage;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Tests\PlusTestCase;
|
||||
|
@ -24,6 +24,11 @@ class S3CompatibleStorageTest extends PlusTestCase
|
|||
$this->file = UploadedFile::fromFile(test_path('songs/full.mp3'), 'song.mp3'); //@phpstan-ignore-line
|
||||
}
|
||||
|
||||
public function testSupported(): void
|
||||
{
|
||||
self::assertTrue($this->service->supported());
|
||||
}
|
||||
|
||||
public function testStoreUploadedFile(): void
|
||||
{
|
||||
self::assertEquals(0, Song::query()->where('storage', 's3')->count());
|
57
tests/Integration/KoelPlus/StreamerFactoryTest.php
Normal file
57
tests/Integration/KoelPlus/StreamerFactoryTest.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Integration\KoelPlus;
|
||||
|
||||
use App\Models\Song;
|
||||
use App\Services\Streamers\PhpStreamer;
|
||||
use App\Services\Streamers\S3CompatibleStreamer;
|
||||
use App\Services\Streamers\StreamerFactory;
|
||||
use App\Values\SongStorageTypes;
|
||||
use Exception;
|
||||
use phpmock\mockery\PHPMockery;
|
||||
use Tests\PlusTestCase;
|
||||
|
||||
use function Tests\test_path;
|
||||
|
||||
class StreamerFactoryTest extends PlusTestCase
|
||||
{
|
||||
private StreamerFactory $streamerFactory;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->streamerFactory = app(StreamerFactory::class);
|
||||
PHPMockery::mock('App\Services\Streamers', 'file_exists')->andReturn(true);
|
||||
}
|
||||
|
||||
public function testCreateStreamer(): void
|
||||
{
|
||||
collect(SongStorageTypes::ALL_TYPES)
|
||||
->each(function (?string $type): void {
|
||||
switch ($type) {
|
||||
case SongStorageTypes::S3:
|
||||
case SongStorageTypes::S3_LAMBDA:
|
||||
self::assertInstanceOf(S3CompatibleStreamer::class, $this->streamerFactory->createStreamer(
|
||||
Song::factory()->make(['path' => "s3://bucket/foo.mp3", 'storage' => $type])
|
||||
));
|
||||
break;
|
||||
|
||||
case SongStorageTypes::DROPBOX:
|
||||
$this->streamerFactory->createStreamer(
|
||||
Song::factory()->make(['path' => "dropbox://foo.mp3", 'storage' => $type])
|
||||
);
|
||||
break;
|
||||
|
||||
case SongStorageTypes::LOCAL:
|
||||
self::assertInstanceOf(PhpStreamer::class, $this->streamerFactory->createStreamer(
|
||||
Song::factory()->make(['path' => test_path('songs/blank.mp3'), 'storage' => $type])
|
||||
));
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception("Unhandled storage type: $type");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ use App\Listeners\DeleteNonExistingRecordsPostScan;
|
|||
use App\Models\Song;
|
||||
use App\Values\ScanResult;
|
||||
use App\Values\ScanResultCollection;
|
||||
use App\Values\SongStorageTypes;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Tests\TestCase;
|
||||
|
||||
|
@ -21,12 +22,16 @@ class DeleteNonExistingRecordsPostSyncTest extends TestCase
|
|||
$this->listener = app(DeleteNonExistingRecordsPostScan::class);
|
||||
}
|
||||
|
||||
public function testHandleDoesNotDeleteS3Entries(): void
|
||||
public function testHandleDoesNotDeleteCloudEntries(): void
|
||||
{
|
||||
$song = Song::factory()->create(['path' => 's3://do-not/delete-me.mp3']);
|
||||
$this->listener->handle(new MediaScanCompleted(ScanResultCollection::create()));
|
||||
collect(SongStorageTypes::ALL_TYPES)
|
||||
->filter(static fn ($type) => $type !== SongStorageTypes::LOCAL)
|
||||
->each(function ($type): void {
|
||||
$song = Song::factory()->create(['storage' => $type]);
|
||||
$this->listener->handle(new MediaScanCompleted(ScanResultCollection::create()));
|
||||
|
||||
self::assertModelExists($song);
|
||||
self::assertModelExists($song);
|
||||
});
|
||||
}
|
||||
|
||||
public function testHandle(): void
|
||||
|
|
|
@ -4,7 +4,6 @@ namespace Tests\Integration\Services;
|
|||
|
||||
use App\Events\LibraryChanged;
|
||||
use App\Events\MediaScanCompleted;
|
||||
use App\Libraries\WatchRecord\InotifyWatchRecord;
|
||||
use App\Models\Album;
|
||||
use App\Models\Artist;
|
||||
use App\Models\Setting;
|
||||
|
@ -12,6 +11,7 @@ use App\Models\Song;
|
|||
use App\Services\FileScanner;
|
||||
use App\Services\MediaScanner;
|
||||
use App\Values\ScanConfiguration;
|
||||
use App\Values\WatchRecord\InotifyWatchRecord;
|
||||
use getID3;
|
||||
use Illuminate\Support\Arr;
|
||||
use Mockery;
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Integration\Services\FileStorage;
|
||||
namespace Tests\Integration\Services\SongStorages;
|
||||
|
||||
use App\Exceptions\MediaPathNotSetException;
|
||||
use App\Exceptions\SongUploadFailedException;
|
||||
use App\Models\Setting;
|
||||
use App\Services\SongStorage\LocalStorage;
|
||||
use App\Services\SongStorages\LocalStorage;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Mockery;
|
||||
use Tests\TestCase;
|
||||
|
@ -26,7 +26,7 @@ class LocalStorageTest extends TestCase
|
|||
|
||||
public function testStoreUploadedFileWithMediaPathNotSet(): void
|
||||
{
|
||||
Setting::unset('media_path');
|
||||
Setting::set('media_path', '');
|
||||
|
||||
self::expectException(MediaPathNotSetException::class);
|
||||
$this->service->storeUploadedFile(Mockery::mock(UploadedFile::class), create_user());
|
|
@ -4,10 +4,10 @@ namespace Tests\Unit\Services;
|
|||
|
||||
use App\Models\Album;
|
||||
use App\Models\Artist;
|
||||
use App\Services\Contracts\MusicEncyclopedia;
|
||||
use App\Services\LastfmService;
|
||||
use App\Services\MediaInformationService;
|
||||
use App\Services\MediaMetadataService;
|
||||
use App\Services\MusicEncyclopedia;
|
||||
use App\Values\AlbumInformation;
|
||||
use App\Values\ArtistInformation;
|
||||
use Illuminate\Cache\Repository as Cache;
|
||||
|
|
16
tests/Unit/Services/SongStorages/DropboxStorageTest.php
Normal file
16
tests/Unit/Services/SongStorages/DropboxStorageTest.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\SongStorages;
|
||||
|
||||
use App\Exceptions\KoelPlusRequiredException;
|
||||
use App\Services\SongStorages\DropboxStorage;
|
||||
use Tests\TestCase;
|
||||
|
||||
class DropboxStorageTest extends TestCase
|
||||
{
|
||||
public function testSupported(): void
|
||||
{
|
||||
self::expectException(KoelPlusRequiredException::class);
|
||||
app(DropboxStorage::class);
|
||||
}
|
||||
}
|
16
tests/Unit/Services/SongStorages/S3CompatibleStorageTest.php
Normal file
16
tests/Unit/Services/SongStorages/S3CompatibleStorageTest.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\SongStorages;
|
||||
|
||||
use App\Exceptions\KoelPlusRequiredException;
|
||||
use App\Services\SongStorages\S3CompatibleStorage;
|
||||
use Tests\TestCase;
|
||||
|
||||
class S3CompatibleStorageTest extends TestCase
|
||||
{
|
||||
public function testSupported(): void
|
||||
{
|
||||
self::expectException(KoelPlusRequiredException::class);
|
||||
app(S3CompatibleStorage::class);
|
||||
}
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\SongStorage;
|
||||
namespace Tests\Unit\Services\SongStorages;
|
||||
|
||||
use App\Events\LibraryChanged;
|
||||
use App\Models\Song;
|
||||
use App\Repositories\SongRepository;
|
||||
use App\Repositories\UserRepository;
|
||||
use App\Services\MediaMetadataService;
|
||||
use App\Services\SongStorage\S3LambdaStorage;
|
||||
use App\Services\SongStorages\S3LambdaStorage;
|
||||
use Mockery;
|
||||
use Mockery\LegacyMockInterface;
|
||||
use Mockery\MockInterface;
|
||||
|
@ -127,4 +127,9 @@ class S3LambdaStorageTest extends TestCase
|
|||
|
||||
self::assertModelMissing($song);
|
||||
}
|
||||
|
||||
public function testSupported(): void
|
||||
{
|
||||
self::assertTrue($this->storage->supported());
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue