mirror of
https://github.com/koel/koel
synced 2024-11-24 05:03:05 +00:00
Fix basic errors detected by PHPStan
This commit is contained in:
parent
c4beca787b
commit
d88dd79f15
45 changed files with 505 additions and 1403 deletions
|
@ -59,7 +59,7 @@ class InitCommand extends Command
|
|||
|
||||
$this->comment(PHP_EOL.'🎆 Success! Koel can now be run from localhost with `php artisan serve`.');
|
||||
|
||||
if ($this->settingRepository->getMediaPath()) {
|
||||
if (Setting::get('media_path')) {
|
||||
$this->comment('You can also scan for media with `php artisan koel:sync`.');
|
||||
}
|
||||
|
||||
|
@ -150,7 +150,7 @@ class InitCommand extends Command
|
|||
|
||||
private function maybeSetMediaPath(): void
|
||||
{
|
||||
if ($this->settingRepository->getMediaPath()) {
|
||||
if (Setting::get('media_path')) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -128,7 +128,7 @@ class SyncMediaCommand extends Command
|
|||
|
||||
private function ensureMediaPath(): void
|
||||
{
|
||||
if ($this->settingRepository->getMediaPath()) {
|
||||
if (Setting::get('media_path')) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,9 @@ use Illuminate\Auth\Access\AuthorizationException;
|
|||
use Illuminate\Auth\AuthenticationException;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Illuminate\Foundation\Validation\ValidationException;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
|
@ -30,9 +32,9 @@ class Handler extends ExceptionHandler
|
|||
*
|
||||
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
|
||||
*
|
||||
* @param \Exception $e
|
||||
* @throws Exception
|
||||
*/
|
||||
public function report(Exception $e)
|
||||
public function report(Exception $e): void
|
||||
{
|
||||
parent::report($e);
|
||||
}
|
||||
|
@ -40,12 +42,9 @@ class Handler extends ExceptionHandler
|
|||
/**
|
||||
* Render an exception into an HTTP response.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Exception $e
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
* @param Request $request
|
||||
*/
|
||||
public function render($request, Exception $e)
|
||||
public function render($request, Exception $e): Response
|
||||
{
|
||||
if ($e instanceof ModelNotFoundException) {
|
||||
$e = new NotFoundHttpException($e->getMessage(), $e);
|
||||
|
@ -57,12 +56,9 @@ class Handler extends ExceptionHandler
|
|||
/**
|
||||
* Convert an authentication exception into an unauthenticated response.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Illuminate\Auth\AuthenticationException $exception
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
* @param Request $request
|
||||
*/
|
||||
protected function unauthenticated($request, AuthenticationException $exception)
|
||||
protected function unauthenticated($request, AuthenticationException $exception): Response
|
||||
{
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json(['error' => 'Unauthenticated.'], 401);
|
||||
|
|
|
@ -5,11 +5,20 @@ namespace App\Http\Controllers\API;
|
|||
use App\Http\Requests\API\UserLoginRequest;
|
||||
use Exception;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use JWTAuth;
|
||||
use Log;
|
||||
use Illuminate\Log\Logger;
|
||||
use Tymon\JWTAuth\JWTAuth;
|
||||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
private $auth;
|
||||
private $logger;
|
||||
|
||||
public function __construct(JWTAuth $auth, Logger $logger)
|
||||
{
|
||||
$this->auth = $auth;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a user in.
|
||||
*
|
||||
|
@ -17,7 +26,7 @@ class AuthController extends Controller
|
|||
*/
|
||||
public function login(UserLoginRequest $request)
|
||||
{
|
||||
$token = JWTAuth::attempt($request->only('email', 'password'));
|
||||
$token = $this->auth->attempt($request->only('email', 'password'));
|
||||
abort_unless($token, 401, 'Invalid credentials');
|
||||
|
||||
return response()->json(compact('token'));
|
||||
|
@ -30,11 +39,11 @@ class AuthController extends Controller
|
|||
*/
|
||||
public function logout()
|
||||
{
|
||||
if ($token = JWTAuth::getToken()) {
|
||||
if ($token = $this->auth->getToken()) {
|
||||
try {
|
||||
JWTAuth::invalidate($token);
|
||||
$this->auth->invalidate($token);
|
||||
} catch (Exception $e) {
|
||||
Log::error($e);
|
||||
$this->logger->error($e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,10 +4,17 @@ namespace App\Observers;
|
|||
|
||||
use App\Models\Album;
|
||||
use Exception;
|
||||
use Log;
|
||||
use Illuminate\Log\Logger;
|
||||
|
||||
class AlbumObserver
|
||||
{
|
||||
private $logger;
|
||||
|
||||
public function __construct(Logger $logger)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
public function deleted(Album $album): void
|
||||
{
|
||||
if (!$album->has_cover) {
|
||||
|
@ -17,7 +24,7 @@ class AlbumObserver
|
|||
try {
|
||||
unlink($album->cover_path);
|
||||
} catch (Exception $e) {
|
||||
Log::error($e);
|
||||
$this->logger->error($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,40 +3,36 @@
|
|||
namespace App\Providers;
|
||||
|
||||
use Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider;
|
||||
use DB;
|
||||
use Illuminate\Database\DatabaseManager;
|
||||
use Illuminate\Database\Schema\Builder;
|
||||
use Illuminate\Database\SQLiteConnection;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Validation\Factory as Validator;
|
||||
use Laravel\Tinker\TinkerServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
public function boot(Builder $schema, DatabaseManager $db, Validator $validator): void
|
||||
{
|
||||
// Fix utf8mb4-related error starting from Laravel 5.4
|
||||
Schema::defaultStringLength(191);
|
||||
$schema->defaultStringLength(191);
|
||||
|
||||
// Enable on delete cascade for sqlite connections
|
||||
if (DB::connection() instanceof SQLiteConnection) {
|
||||
DB::statement(DB::raw('PRAGMA foreign_keys = ON'));
|
||||
if ($db->connection() instanceof SQLiteConnection) {
|
||||
$db->statement($db->raw('PRAGMA foreign_keys = ON'));
|
||||
}
|
||||
|
||||
// Add some custom validation rules
|
||||
Validator::extend('path.valid', function ($attribute, $value, $parameters, $validator) {
|
||||
$validator->extend('path.valid', static function ($attribute, $value): bool {
|
||||
return is_dir($value) && is_readable($value);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace App\Providers;
|
||||
|
||||
use AWS;
|
||||
use Aws\AwsClientInterface;
|
||||
use Aws\S3\S3ClientInterface;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
@ -18,7 +17,7 @@ class ObjectStorageServiceProvider extends ServiceProvider
|
|||
return null;
|
||||
}
|
||||
|
||||
return AWS::createClient('s3');
|
||||
return app('aws')->createClient('s3');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,9 @@ namespace App\Services;
|
|||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\ClientException;
|
||||
use Illuminate\Log\Logger;
|
||||
use Illuminate\Contracts\Cache\Repository as Cache;
|
||||
use InvalidArgumentException;
|
||||
use Log;
|
||||
use SimpleXMLElement;
|
||||
|
||||
/**
|
||||
|
@ -19,11 +20,9 @@ use SimpleXMLElement;
|
|||
abstract class ApiClient
|
||||
{
|
||||
protected $responseFormat = 'json';
|
||||
|
||||
/**
|
||||
* The GuzzleHttp client to talk to the API.
|
||||
*/
|
||||
protected $client;
|
||||
protected $cache;
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* The query parameter name for the key.
|
||||
|
@ -34,9 +33,11 @@ abstract class ApiClient
|
|||
*/
|
||||
protected $keyParam = 'key';
|
||||
|
||||
public function __construct(Client $client)
|
||||
public function __construct(Client $client, Cache $cache, Logger $logger)
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->cache = $cache;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -68,9 +69,9 @@ abstract class ApiClient
|
|||
|
||||
return $body;
|
||||
} catch (ClientException $e) {
|
||||
Log::error($e);
|
||||
$this->logger->error($e);
|
||||
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,8 +5,6 @@ namespace App\Services;
|
|||
interface ApiConsumerInterface
|
||||
{
|
||||
public function getEndpoint(): ?string;
|
||||
|
||||
public function getKey(): ?string;
|
||||
|
||||
public function getSecret(): ?string;
|
||||
}
|
||||
|
|
|
@ -3,9 +3,6 @@
|
|||
namespace App\Services;
|
||||
|
||||
use Exception;
|
||||
use GuzzleHttp\Client;
|
||||
use Illuminate\Contracts\Cache\Repository as Cache;
|
||||
use Log;
|
||||
|
||||
class LastfmService extends ApiClient implements ApiConsumerInterface
|
||||
{
|
||||
|
@ -22,16 +19,6 @@ class LastfmService extends ApiClient implements ApiConsumerInterface
|
|||
* @var string
|
||||
*/
|
||||
protected $keyParam = 'api_key';
|
||||
/**
|
||||
* @var Cache
|
||||
*/
|
||||
private $cache;
|
||||
|
||||
public function __construct(Client $client, Cache $cache)
|
||||
{
|
||||
parent::__construct($client);
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if our application is using Last.fm.
|
||||
|
@ -82,7 +69,7 @@ class LastfmService extends ApiClient implements ApiConsumerInterface
|
|||
return $this->buildArtistInformation($artist);
|
||||
});
|
||||
} catch (Exception $e) {
|
||||
Log::error($e);
|
||||
$this->logger->error($e);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -141,7 +128,7 @@ class LastfmService extends ApiClient implements ApiConsumerInterface
|
|||
return $this->buildAlbumInformation($album);
|
||||
});
|
||||
} catch (Exception $e) {
|
||||
Log::error($e);
|
||||
$this->logger->error($e);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -190,7 +177,7 @@ class LastfmService extends ApiClient implements ApiConsumerInterface
|
|||
try {
|
||||
return (string) $this->get("/?$query", [], false)->session->key;
|
||||
} catch (Exception $e) {
|
||||
Log::error($e);
|
||||
$this->logger->error($e);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -218,7 +205,7 @@ class LastfmService extends ApiClient implements ApiConsumerInterface
|
|||
try {
|
||||
$this->post('/', $this->buildAuthCallParams($params), false);
|
||||
} catch (Exception $e) {
|
||||
Log::error($e);
|
||||
$this->logger->error($e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -238,7 +225,7 @@ class LastfmService extends ApiClient implements ApiConsumerInterface
|
|||
try {
|
||||
$this->post('/', $this->buildAuthCallParams($params), false);
|
||||
} catch (Exception $e) {
|
||||
Log::error($e);
|
||||
$this->logger->error($e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -263,7 +250,7 @@ class LastfmService extends ApiClient implements ApiConsumerInterface
|
|||
try {
|
||||
$this->post('/', $this->buildAuthCallParams($params), false);
|
||||
} catch (Exception $e) {
|
||||
Log::error($e);
|
||||
$this->logger->error($e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,10 +5,17 @@ namespace App\Services;
|
|||
use App\Models\Album;
|
||||
use App\Models\Artist;
|
||||
use Exception;
|
||||
use Log;
|
||||
use Illuminate\Log\Logger;
|
||||
|
||||
class MediaMetadataService
|
||||
{
|
||||
private $logger;
|
||||
|
||||
public function __construct(Logger $logger)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a copy of the album cover.
|
||||
*/
|
||||
|
@ -47,7 +54,7 @@ class MediaMetadataService
|
|||
|
||||
$album->update(['cover' => basename($destination)]);
|
||||
} catch (Exception $e) {
|
||||
Log::error($e);
|
||||
$this->logger->error($e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,7 +85,7 @@ class MediaMetadataService
|
|||
|
||||
$artist->update(['image' => basename($destination)]);
|
||||
} catch (Exception $e) {
|
||||
Log::error($e);
|
||||
$this->logger->error($e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,13 +7,14 @@ use App\Events\LibraryChanged;
|
|||
use App\Libraries\WatchRecord\WatchRecordInterface;
|
||||
use App\Models\Album;
|
||||
use App\Models\Artist;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Song;
|
||||
use App\Repositories\AlbumRepository;
|
||||
use App\Repositories\ArtistRepository;
|
||||
use App\Repositories\SettingRepository;
|
||||
use App\Repositories\SongRepository;
|
||||
use Exception;
|
||||
use Log;
|
||||
use Illuminate\Log\Logger;
|
||||
use SplFileInfo;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
||||
|
@ -46,6 +47,7 @@ class MediaSyncService
|
|||
private $artistRepository;
|
||||
private $albumRepository;
|
||||
private $settingRepository;
|
||||
private $logger;
|
||||
|
||||
public function __construct(
|
||||
MediaMetadataService $mediaMetadataService,
|
||||
|
@ -55,7 +57,8 @@ class MediaSyncService
|
|||
SettingRepository $settingRepository,
|
||||
HelperService $helperService,
|
||||
FileSynchronizer $fileSynchronizer,
|
||||
Finder $finder
|
||||
Finder $finder,
|
||||
Logger $logger
|
||||
) {
|
||||
$this->mediaMetadataService = $mediaMetadataService;
|
||||
$this->songRepository = $songRepository;
|
||||
|
@ -65,6 +68,7 @@ class MediaSyncService
|
|||
$this->artistRepository = $artistRepository;
|
||||
$this->albumRepository = $albumRepository;
|
||||
$this->settingRepository = $settingRepository;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -100,7 +104,7 @@ class MediaSyncService
|
|||
'unmodified' => [],
|
||||
];
|
||||
|
||||
$songPaths = $this->gatherFiles($mediaPath ?: $this->settingRepository->getMediaPath());
|
||||
$songPaths = $this->gatherFiles($mediaPath ?: Setting::get('media_path'));
|
||||
$syncCommand && $syncCommand->createProgressBar(count($songPaths));
|
||||
|
||||
foreach ($songPaths as $path) {
|
||||
|
@ -162,7 +166,7 @@ class MediaSyncService
|
|||
*/
|
||||
public function syncByWatchRecord(WatchRecordInterface $record): void
|
||||
{
|
||||
Log::info("New watch record received: '$record'");
|
||||
$this->logger->info("New watch record received: '$record'");
|
||||
$record->isFile() ? $this->syncFileRecord($record) : $this->syncDirectoryRecord($record);
|
||||
}
|
||||
|
||||
|
@ -174,7 +178,7 @@ class MediaSyncService
|
|||
private function syncFileRecord(WatchRecordInterface $record): void
|
||||
{
|
||||
$path = $record->getPath();
|
||||
Log::info("'$path' is a file.");
|
||||
$this->logger->info("'$path' is a file.");
|
||||
|
||||
// If the file has been deleted...
|
||||
if ($record->isDeleted()) {
|
||||
|
@ -192,7 +196,7 @@ class MediaSyncService
|
|||
private function syncDirectoryRecord(WatchRecordInterface $record): void
|
||||
{
|
||||
$path = $record->getPath();
|
||||
Log::info("'$path' is a directory.");
|
||||
$this->logger->info("'$path' is a directory.");
|
||||
|
||||
if ($record->isDeleted()) {
|
||||
$this->handleDeletedDirectoryRecord($path);
|
||||
|
@ -253,11 +257,11 @@ class MediaSyncService
|
|||
{
|
||||
if ($song = $this->songRepository->getOneByPath($path)) {
|
||||
$song->delete();
|
||||
Log::info("$path deleted.");
|
||||
$this->logger->info("$path deleted.");
|
||||
|
||||
event(new LibraryChanged());
|
||||
} else {
|
||||
Log::info("$path doesn't exist in our database--skipping.");
|
||||
$this->logger->info("$path doesn't exist in our database--skipping.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -266,9 +270,9 @@ class MediaSyncService
|
|||
$result = $this->fileSynchronizer->setFile($path)->sync($this->tags);
|
||||
|
||||
if ($result === FileSynchronizer::SYNC_RESULT_SUCCESS) {
|
||||
Log::info("Synchronized $path");
|
||||
$this->logger->info("Synchronized $path");
|
||||
} else {
|
||||
Log::info("Failed to synchronized $path. Maybe an invalid file?");
|
||||
$this->logger->info("Failed to synchronized $path. Maybe an invalid file?");
|
||||
}
|
||||
|
||||
event(new LibraryChanged());
|
||||
|
@ -277,11 +281,11 @@ class MediaSyncService
|
|||
private function handleDeletedDirectoryRecord(string $path): void
|
||||
{
|
||||
if ($count = Song::inDirectory($path)->delete()) {
|
||||
Log::info("Deleted $count song(s) under $path");
|
||||
$this->logger->info("Deleted $count song(s) under $path");
|
||||
|
||||
event(new LibraryChanged());
|
||||
} else {
|
||||
Log::info("$path is empty--no action needed.");
|
||||
$this->logger->info("$path is empty--no action needed.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -291,7 +295,7 @@ class MediaSyncService
|
|||
$this->fileSynchronizer->setFile($file)->sync($this->tags);
|
||||
}
|
||||
|
||||
Log::info("Synced all song(s) under $path");
|
||||
$this->logger->info("Synced all song(s) under $path");
|
||||
|
||||
event(new LibraryChanged());
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
namespace App\Services;
|
||||
|
||||
use App\Models\Song;
|
||||
use Cache;
|
||||
|
||||
class YouTubeService extends ApiClient implements ApiConsumerInterface
|
||||
{
|
||||
|
@ -44,7 +43,7 @@ class YouTubeService extends ApiClient implements ApiConsumerInterface
|
|||
public function search(string $q, string $pageToken = '', int $perPage = 10)
|
||||
{
|
||||
if (!$this->enabled()) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
$uri = sprintf('search?part=snippet&type=video&maxResults=%s&pageToken=%s&q=%s',
|
||||
|
@ -53,7 +52,7 @@ class YouTubeService extends ApiClient implements ApiConsumerInterface
|
|||
urlencode($q)
|
||||
);
|
||||
|
||||
return Cache::remember(md5("youtube_$uri"), 60 * 24 * 7, function () use ($uri) {
|
||||
return $this->cache->remember(md5("youtube_$uri"), 60 * 24 * 7, function () use ($uri) {
|
||||
return $this->get($uri);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -3,20 +3,9 @@
|
|||
namespace App\Services;
|
||||
|
||||
use Exception;
|
||||
use GuzzleHttp\Client;
|
||||
use Illuminate\Contracts\Cache\Repository as Cache;
|
||||
use Log;
|
||||
|
||||
class iTunesService extends ApiClient implements ApiConsumerInterface
|
||||
{
|
||||
private $cache;
|
||||
|
||||
public function __construct(Client $client, Cache $cache)
|
||||
{
|
||||
parent::__construct($client);
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether to use iTunes services.
|
||||
*/
|
||||
|
@ -62,7 +51,7 @@ class iTunesService extends ApiClient implements ApiConsumerInterface
|
|||
}
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
Log::error($e);
|
||||
$this->logger->error($e);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
namespace App\Traits;
|
||||
|
||||
use DB;
|
||||
use Exception;
|
||||
use Illuminate\Database\DatabaseManager;
|
||||
|
||||
/**
|
||||
* With reference to GitHub issue #463.
|
||||
|
@ -56,16 +56,18 @@ trait SupportsDeleteWhereIDsNotIn
|
|||
*/
|
||||
public static function deleteByChunk(array $ids, string $key = 'id', int $chunkSize = 65535): void
|
||||
{
|
||||
DB::beginTransaction();
|
||||
/** @var DatabaseManager $db */
|
||||
$db = app(DatabaseManager::class);
|
||||
$db->beginTransaction();
|
||||
|
||||
try {
|
||||
foreach (array_chunk($ids, $chunkSize) as $chunk) {
|
||||
static::whereIn($key, $chunk)->delete();
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
$db->commit();
|
||||
} catch (Exception $e) {
|
||||
DB::rollBack();
|
||||
$db->rollBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,8 @@
|
|||
"laravel/tinker": "^1.0",
|
||||
"laravel/browser-kit-testing": "^2.0",
|
||||
"mikey179/vfsStream": "^1.6",
|
||||
"phpstan/phpstan": "^0.10.3"
|
||||
"phpstan/phpstan": "^0.10.3",
|
||||
"php-mock/php-mock-mockery": "^1.3"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
|
@ -48,8 +49,7 @@
|
|||
},
|
||||
"autoload-dev": {
|
||||
"classmap": [
|
||||
"tests/TestCase.php",
|
||||
"tests/e2e"
|
||||
"tests/TestCase.php"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
|
|
170
composer.lock
generated
170
composer.lock
generated
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "85f650d77535301c67a4dc4a0fa378ee",
|
||||
"content-hash": "f40663bd1d80d0151691dbcb57b9fbb8",
|
||||
"packages": [
|
||||
{
|
||||
"name": "asm89/stack-cors",
|
||||
|
@ -4857,6 +4857,174 @@
|
|||
"description": "Library for handling version information and constraints",
|
||||
"time": "2018-07-08T19:19:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "php-mock/php-mock",
|
||||
"version": "2.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-mock/php-mock.git",
|
||||
"reference": "22d297231118e6fd5b9db087fbe1ef866c2b95d2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-mock/php-mock/zipball/22d297231118e6fd5b9db087fbe1ef866c2b95d2",
|
||||
"reference": "22d297231118e6fd5b9db087fbe1ef866c2b95d2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.6",
|
||||
"phpunit/php-text-template": "^1"
|
||||
},
|
||||
"replace": {
|
||||
"malkusch/php-mock": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^5.7"
|
||||
},
|
||||
"suggest": {
|
||||
"php-mock/php-mock-phpunit": "Allows integration into PHPUnit testcase with the trait PHPMock."
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"phpmock\\": [
|
||||
"classes/",
|
||||
"tests/"
|
||||
]
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"WTFPL"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Markus Malkusch",
|
||||
"email": "markus@malkusch.de",
|
||||
"homepage": "http://markus.malkusch.de",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "PHP-Mock can mock built-in PHP functions (e.g. time()). PHP-Mock relies on PHP's namespace fallback policy. No further extension is needed.",
|
||||
"homepage": "https://github.com/php-mock/php-mock",
|
||||
"keywords": [
|
||||
"BDD",
|
||||
"TDD",
|
||||
"function",
|
||||
"mock",
|
||||
"stub",
|
||||
"test",
|
||||
"test double"
|
||||
],
|
||||
"time": "2017-02-17T20:52:52+00:00"
|
||||
},
|
||||
{
|
||||
"name": "php-mock/php-mock-integration",
|
||||
"version": "2.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-mock/php-mock-integration.git",
|
||||
"reference": "5a0d7d7755f823bc2a230cfa45058b40f9013bc4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-mock/php-mock-integration/zipball/5a0d7d7755f823bc2a230cfa45058b40f9013bc4",
|
||||
"reference": "5a0d7d7755f823bc2a230cfa45058b40f9013bc4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.6",
|
||||
"php-mock/php-mock": "^2",
|
||||
"phpunit/php-text-template": "^1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4|^5"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"phpmock\\integration\\": "classes/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"WTFPL"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Markus Malkusch",
|
||||
"email": "markus@malkusch.de",
|
||||
"homepage": "http://markus.malkusch.de",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "Integration package for PHP-Mock",
|
||||
"homepage": "https://github.com/php-mock/php-mock-integration",
|
||||
"keywords": [
|
||||
"BDD",
|
||||
"TDD",
|
||||
"function",
|
||||
"mock",
|
||||
"stub",
|
||||
"test",
|
||||
"test double"
|
||||
],
|
||||
"time": "2017-02-17T21:31:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "php-mock/php-mock-mockery",
|
||||
"version": "1.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-mock/php-mock-mockery.git",
|
||||
"reference": "d6d3df9d9232f1623f1ca3cfdaacd53415593825"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-mock/php-mock-mockery/zipball/d6d3df9d9232f1623f1ca3cfdaacd53415593825",
|
||||
"reference": "d6d3df9d9232f1623f1ca3cfdaacd53415593825",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"mockery/mockery": "^1",
|
||||
"php": ">=5.6",
|
||||
"php-mock/php-mock-integration": "^2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4|^5"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"phpmock\\mockery\\": "classes/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"WTFPL"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Markus Malkusch",
|
||||
"email": "markus@malkusch.de",
|
||||
"homepage": "http://markus.malkusch.de",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "Mock built-in PHP functions (e.g. time()) with Mockery. This package relies on PHP's namespace fallback policy. No further extension is needed.",
|
||||
"homepage": "https://github.com/php-mock/php-mock-mockery",
|
||||
"keywords": [
|
||||
"BDD",
|
||||
"TDD",
|
||||
"function",
|
||||
"mock",
|
||||
"mockery",
|
||||
"stub",
|
||||
"test",
|
||||
"test double"
|
||||
],
|
||||
"time": "2018-03-27T07:00:25+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpdocumentor/reflection-common",
|
||||
"version": "1.0.1",
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
namespace Tests;
|
||||
|
||||
use App\Models\User;
|
||||
use Artisan;
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
|
||||
use Illuminate\Contracts\Console\Kernel as Artisan;
|
||||
use Illuminate\Foundation\Application;
|
||||
|
||||
trait CreatesApplication
|
||||
|
@ -12,6 +12,9 @@ trait CreatesApplication
|
|||
protected $coverPath;
|
||||
protected $mediaPath = __DIR__.'/songs';
|
||||
|
||||
/** @var Artisan */
|
||||
private $artisan;
|
||||
|
||||
/**
|
||||
* The base URL to use while testing the application.
|
||||
*
|
||||
|
@ -28,7 +31,8 @@ trait CreatesApplication
|
|||
{
|
||||
$app = require __DIR__.'/../bootstrap/app.php';
|
||||
|
||||
$app->make(Kernel::class)->bootstrap();
|
||||
$this->artisan = $app->make(Artisan::class);
|
||||
$this->artisan->bootstrap();
|
||||
|
||||
$this->coverPath = $app->basePath().'/public/img/covers';
|
||||
|
||||
|
@ -37,10 +41,10 @@ trait CreatesApplication
|
|||
|
||||
private function prepareForTests()
|
||||
{
|
||||
Artisan::call('migrate');
|
||||
$this->artisan->call('migrate');
|
||||
|
||||
if (!User::all()->count()) {
|
||||
Artisan::call('db:seed');
|
||||
$this->artisan->call('db:seed');
|
||||
}
|
||||
|
||||
if (!file_exists($this->coverPath)) {
|
||||
|
|
|
@ -7,22 +7,24 @@ use App\Services\LastfmService;
|
|||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use Illuminate\Contracts\Cache\Repository as Cache;
|
||||
use Illuminate\Log\Logger;
|
||||
use Mockery;
|
||||
use Tymon\JWTAuth\JWTAuth;
|
||||
|
||||
class LastfmTest extends TestCase
|
||||
{
|
||||
public function testGetSessionKey()
|
||||
public function testGetSessionKey(): void
|
||||
{
|
||||
/** @var Client $client */
|
||||
$client = Mockery::mock(Client::class, [
|
||||
'get' => new Response(200, [], file_get_contents(__DIR__.'../../blobs/lastfm/session-key.xml')),
|
||||
]);
|
||||
|
||||
self::assertEquals('foo', (new LastfmService($client, app(Cache::class)))->getSessionKey('bar'));
|
||||
$service = new LastfmService($client, app(Cache::class), app(Logger::class));
|
||||
self::assertEquals('foo', $service->getSessionKey('bar'));
|
||||
}
|
||||
|
||||
public function testSetSessionKey()
|
||||
public function testSetSessionKey(): void
|
||||
{
|
||||
$user = factory(User::class)->create();
|
||||
$this->postAsUser('api/lastfm/session-key', ['key' => 'foo'], $user)
|
||||
|
@ -32,7 +34,7 @@ class LastfmTest extends TestCase
|
|||
self::assertEquals('foo', $user->lastfm_session_key);
|
||||
}
|
||||
|
||||
public function testConnectToLastfm()
|
||||
public function testConnectToLastfm(): void
|
||||
{
|
||||
$this->mockIocDependency(JWTAuth::class, [
|
||||
'parseToken' => null,
|
||||
|
@ -43,7 +45,7 @@ class LastfmTest extends TestCase
|
|||
->assertRedirectedTo('https://www.last.fm/api/auth/?api_key=foo&cb=http%3A%2F%2Flocalhost%2Fapi%2Flastfm%2Fcallback%3Fjwt-token%3Dfoo');
|
||||
}
|
||||
|
||||
public function testRetrieveAndStoreSessionKey()
|
||||
public function testRetrieveAndStoreSessionKey(): void
|
||||
{
|
||||
$lastfm = $this->mockIocDependency(LastfmService::class);
|
||||
$lastfm->shouldReceive('getSessionKey')
|
||||
|
@ -59,7 +61,7 @@ class LastfmTest extends TestCase
|
|||
$this->assertEquals('bar', $user->lastfm_session_key);
|
||||
}
|
||||
|
||||
public function testDisconnectUser()
|
||||
public function testDisconnectUser(): void
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = factory(User::class)->create(['preferences' => ['lastfm_session_key' => 'bar']]);
|
||||
|
|
|
@ -8,19 +8,25 @@ use App\Models\Song;
|
|||
use App\Models\User;
|
||||
use Exception;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use JWTAuth;
|
||||
use Laravel\BrowserKitTesting\TestCase as BaseTestCase;
|
||||
use Mockery;
|
||||
use Tests\CreatesApplication;
|
||||
use Tests\Traits\InteractsWithIoc;
|
||||
use Tymon\JWTAuth\JWTAuth;
|
||||
|
||||
abstract class TestCase extends BaseTestCase
|
||||
{
|
||||
use CreatesApplication, DatabaseTransactions, InteractsWithIoc;
|
||||
|
||||
/** @var JWTAuth */
|
||||
private $auth;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->auth = app(JWTAuth::class);
|
||||
|
||||
$this->prepareForTests();
|
||||
}
|
||||
|
||||
|
@ -29,7 +35,7 @@ abstract class TestCase extends BaseTestCase
|
|||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function createSampleMediaSet()
|
||||
protected function createSampleMediaSet(): void
|
||||
{
|
||||
$artist = factory(Artist::class)->create();
|
||||
|
||||
|
@ -47,50 +53,39 @@ abstract class TestCase extends BaseTestCase
|
|||
}
|
||||
}
|
||||
|
||||
protected function getAsUser($url, $user = null)
|
||||
protected function getAsUser($url, $user = null): self
|
||||
{
|
||||
if (!$user) {
|
||||
$user = factory(User::class)->create();
|
||||
}
|
||||
|
||||
return $this->get($url, [
|
||||
'Authorization' => 'Bearer '.JWTAuth::fromUser($user),
|
||||
'Authorization' => 'Bearer ' . $this->generateJwtToken($user),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function deleteAsUser($url, $data = [], $user = null)
|
||||
protected function deleteAsUser($url, $data = [], $user = null): self
|
||||
{
|
||||
if (!$user) {
|
||||
$user = factory(User::class)->create();
|
||||
}
|
||||
|
||||
return $this->delete($url, $data, [
|
||||
'Authorization' => 'Bearer '.JWTAuth::fromUser($user),
|
||||
'Authorization' => 'Bearer ' . $this->generateJwtToken($user),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function postAsUser($url, $data, $user = null)
|
||||
protected function postAsUser($url, $data, $user = null): self
|
||||
{
|
||||
if (!$user) {
|
||||
$user = factory(User::class)->create();
|
||||
}
|
||||
|
||||
return $this->post($url, $data, [
|
||||
'Authorization' => 'Bearer '.JWTAuth::fromUser($user),
|
||||
'Authorization' => 'Bearer ' . $this->generateJwtToken($user),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function putAsUser($url, $data, $user = null)
|
||||
protected function putAsUser($url, $data, $user = null): self
|
||||
{
|
||||
if (!$user) {
|
||||
$user = factory(User::class)->create();
|
||||
}
|
||||
|
||||
return $this->put($url, $data, [
|
||||
'Authorization' => 'Bearer '.JWTAuth::fromUser($user),
|
||||
'Authorization' => 'Bearer ' . $this->generateJwtToken($user),
|
||||
]);
|
||||
}
|
||||
|
||||
private function generateJwtToken(?User $user): string
|
||||
{
|
||||
return $this->auth->fromUser($user ?: factory(User::class)->create());
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
if ($container = Mockery::getContainer()) {
|
||||
|
|
|
@ -1,96 +1,91 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\Streamers {
|
||||
function file_exists()
|
||||
namespace Tests\Integration\Factories;
|
||||
|
||||
use App\Factories\StreamerFactory;
|
||||
use App\Models\Song;
|
||||
use App\Services\Streamers\PHPStreamer;
|
||||
use App\Services\Streamers\S3Streamer;
|
||||
use App\Services\Streamers\TranscodingStreamer;
|
||||
use App\Services\Streamers\XAccelRedirectStreamer;
|
||||
use App\Services\Streamers\XSendFileStreamer;
|
||||
use App\Services\TranscodingService;
|
||||
use Tests\TestCase;
|
||||
use phpmock\mockery\PHPMockery;
|
||||
|
||||
class StreamerFactoryTest extends TestCase
|
||||
{
|
||||
/** @var StreamerFactory */
|
||||
private $streamerFactory;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
namespace Tests\Integration\Factories {
|
||||
use App\Factories\StreamerFactory;
|
||||
use App\Models\Song;
|
||||
use App\Services\Streamers\PHPStreamer;
|
||||
use App\Services\Streamers\S3Streamer;
|
||||
use App\Services\Streamers\TranscodingStreamer;
|
||||
use App\Services\Streamers\XAccelRedirectStreamer;
|
||||
use App\Services\Streamers\XSendFileStreamer;
|
||||
use App\Services\TranscodingService;
|
||||
use Tests\TestCase;
|
||||
|
||||
class StreamerFactoryTest extends TestCase
|
||||
{
|
||||
/** @var StreamerFactory */
|
||||
private $streamerFactory;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
$this->streamerFactory = app(StreamerFactory::class);
|
||||
}
|
||||
|
||||
public function testCreateS3Streamer()
|
||||
{
|
||||
/** @var Song $song */
|
||||
$song = factory(Song::class)->make(['path' => 's3://bucket/foo.mp3']);
|
||||
|
||||
self::assertInstanceOf(S3Streamer::class, $this->streamerFactory->createStreamer($song));
|
||||
}
|
||||
|
||||
public function testCreateTranscodingStreamerIfSupported()
|
||||
{
|
||||
$this->mockIocDependency(TranscodingService::class, [
|
||||
'songShouldBeTranscoded' => true,
|
||||
]);
|
||||
|
||||
/** @var StreamerFactory $streamerFactory */
|
||||
$streamerFactory = app(StreamerFactory::class);
|
||||
/** @var Song $song */
|
||||
$song = factory(Song::class)->make();
|
||||
self::assertInstanceOf(TranscodingStreamer::class, $streamerFactory->createStreamer($song, null));
|
||||
}
|
||||
|
||||
public function testCreateTranscodingStreamerIfForced()
|
||||
{
|
||||
$this->mockIocDependency(TranscodingService::class, [
|
||||
'songShouldBeTranscoded' => false,
|
||||
]);
|
||||
|
||||
/** @var StreamerFactory $streamerFactory */
|
||||
$streamerFactory = app(StreamerFactory::class);
|
||||
|
||||
$song = factory(Song::class)->make();
|
||||
self::assertInstanceOf(TranscodingStreamer::class, $streamerFactory->createStreamer($song, true));
|
||||
}
|
||||
|
||||
public function provideStreamingConfigData()
|
||||
{
|
||||
return [
|
||||
[null, PHPStreamer::class],
|
||||
['x-sendfile', XSendFileStreamer::class],
|
||||
['x-accel-redirect', XAccelRedirectStreamer::class],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideStreamingConfigData
|
||||
*
|
||||
* @param string|null $config
|
||||
* @param string $expectedClass
|
||||
*/
|
||||
public function testCreatePHPStreamer($config, $expectedClass)
|
||||
{
|
||||
$this->mockIocDependency(TranscodingService::class, [
|
||||
'songShouldBeTranscoded' => false,
|
||||
]);
|
||||
|
||||
config(['koel.streaming.method' => $config]);
|
||||
|
||||
/** @var StreamerFactory $streamerFactory */
|
||||
$streamerFactory = app(StreamerFactory::class);
|
||||
|
||||
$song = factory(Song::class)->make();
|
||||
self::assertInstanceOf($expectedClass, $streamerFactory->createStreamer($song));
|
||||
}
|
||||
parent::setUp();
|
||||
$this->streamerFactory = app(StreamerFactory::class);
|
||||
PHPMockery::mock('App\Services\Streamers', 'file_exists')->andReturn(true);
|
||||
}
|
||||
|
||||
public function testCreateS3Streamer(): void
|
||||
{
|
||||
/** @var Song $song */
|
||||
$song = factory(Song::class)->make(['path' => 's3://bucket/foo.mp3']);
|
||||
|
||||
self::assertInstanceOf(S3Streamer::class, $this->streamerFactory->createStreamer($song));
|
||||
}
|
||||
|
||||
public function testCreateTranscodingStreamerIfSupported(): void
|
||||
{
|
||||
$this->mockIocDependency(TranscodingService::class, [
|
||||
'songShouldBeTranscoded' => true,
|
||||
]);
|
||||
|
||||
/** @var StreamerFactory $streamerFactory */
|
||||
$streamerFactory = app(StreamerFactory::class);
|
||||
/** @var Song $song */
|
||||
$song = factory(Song::class)->make();
|
||||
self::assertInstanceOf(TranscodingStreamer::class, $streamerFactory->createStreamer($song, null));
|
||||
}
|
||||
|
||||
public function testCreateTranscodingStreamerIfForced(): void
|
||||
{
|
||||
$this->mockIocDependency(TranscodingService::class, [
|
||||
'songShouldBeTranscoded' => false,
|
||||
]);
|
||||
|
||||
/** @var StreamerFactory $streamerFactory */
|
||||
$streamerFactory = app(StreamerFactory::class);
|
||||
|
||||
$song = factory(Song::class)->make();
|
||||
self::assertInstanceOf(TranscodingStreamer::class, $streamerFactory->createStreamer($song, true));
|
||||
}
|
||||
|
||||
public function provideStreamingConfigData(): array
|
||||
{
|
||||
return [
|
||||
[null, PHPStreamer::class],
|
||||
['x-sendfile', XSendFileStreamer::class],
|
||||
['x-accel-redirect', XAccelRedirectStreamer::class],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideStreamingConfigData
|
||||
*
|
||||
* @param string|null $config
|
||||
* @param string $expectedClass
|
||||
*/
|
||||
public function testCreatePHPStreamer($config, $expectedClass): void
|
||||
{
|
||||
$this->mockIocDependency(TranscodingService::class, [
|
||||
'songShouldBeTranscoded' => false,
|
||||
]);
|
||||
|
||||
config(['koel.streaming.method' => $config]);
|
||||
|
||||
/** @var StreamerFactory $streamerFactory */
|
||||
$streamerFactory = app(StreamerFactory::class);
|
||||
|
||||
$song = factory(Song::class)->make();
|
||||
self::assertInstanceOf($expectedClass, $streamerFactory->createStreamer($song));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,48 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace App\Listeners {
|
||||
if (!function_exists(__NAMESPACE__.'/init_get')) {
|
||||
function ini_get($key)
|
||||
{
|
||||
if ($key === 'allow_url_fopen') {
|
||||
return true;
|
||||
}
|
||||
namespace Tests\Integration\Listeners;
|
||||
|
||||
return \ini_get($key);
|
||||
}
|
||||
}
|
||||
}
|
||||
use App\Events\AlbumInformationFetched;
|
||||
use App\Models\Album;
|
||||
use App\Services\MediaMetadataService;
|
||||
use Mockery\MockInterface;
|
||||
use phpmock\mockery\PHPMockery;
|
||||
use Tests\TestCase;
|
||||
|
||||
namespace Tests\Integration\Listeners {
|
||||
use App\Events\AlbumInformationFetched;
|
||||
use App\Models\Album;
|
||||
use App\Services\MediaMetadataService;
|
||||
use Mockery\MockInterface;
|
||||
use Tests\TestCase;
|
||||
class DownloadAlbumCoverTest extends TestCase
|
||||
{
|
||||
/** @var MediaMetadataService|MockInterface */
|
||||
private $mediaMetaDataService;
|
||||
|
||||
class DownloadAlbumCoverTest extends TestCase
|
||||
public function setUp()
|
||||
{
|
||||
/** @var MediaMetadataService|MockInterface */
|
||||
private $mediaMetaDataService;
|
||||
parent::setUp();
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
$this->mediaMetaDataService = $this->mockIocDependency(MediaMetadataService::class);
|
||||
PHPMockery::mock('App\Listeners', 'ini_get')->andReturn(true);
|
||||
}
|
||||
|
||||
$this->mediaMetaDataService = $this->mockIocDependency(MediaMetadataService::class);
|
||||
}
|
||||
public function testHandle()
|
||||
{
|
||||
$album = factory(Album::class)->make(['cover' => null]);
|
||||
$event = new AlbumInformationFetched($album, ['image' => 'https://foo.bar/baz.jpg']);
|
||||
|
||||
public function testHandle()
|
||||
{
|
||||
$album = factory(Album::class)->make(['cover' => null]);
|
||||
$event = new AlbumInformationFetched($album, ['image' => 'https://foo.bar/baz.jpg']);
|
||||
$this->mediaMetaDataService
|
||||
->shouldReceive('downloadAlbumCover')
|
||||
->once()
|
||||
->with($album, 'https://foo.bar/baz.jpg');
|
||||
|
||||
$this->mediaMetaDataService
|
||||
->shouldReceive('downloadAlbumCover')
|
||||
->once()
|
||||
->with($album, 'https://foo.bar/baz.jpg');
|
||||
|
||||
event($event);
|
||||
}
|
||||
event($event);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,48 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace App\Listeners {
|
||||
if (function_exists(__NAMESPACE__.'/init_get')) {
|
||||
function ini_get($key)
|
||||
{
|
||||
if ($key === 'allow_url_fopen') {
|
||||
return true;
|
||||
}
|
||||
namespace Tests\Integration\Listeners;
|
||||
|
||||
return \ini_get($key);
|
||||
}
|
||||
}
|
||||
}
|
||||
use App\Events\ArtistInformationFetched;
|
||||
use App\Models\Artist;
|
||||
use App\Services\MediaMetadataService;
|
||||
use Mockery\MockInterface;
|
||||
use phpmock\mockery\PHPMockery;
|
||||
use Tests\TestCase;
|
||||
|
||||
namespace Tests\Integration\Listeners {
|
||||
use App\Events\ArtistInformationFetched;
|
||||
use App\Models\Artist;
|
||||
use App\Services\MediaMetadataService;
|
||||
use Mockery\MockInterface;
|
||||
use Tests\TestCase;
|
||||
class DownloadArtistImageTest extends TestCase
|
||||
{
|
||||
/** @var MediaMetadataService|MockInterface */
|
||||
private $mediaMetaDataService;
|
||||
|
||||
class DownloadArtistImageTest extends TestCase
|
||||
public function setUp()
|
||||
{
|
||||
/** @var MediaMetadataService|MockInterface */
|
||||
private $mediaMetaDataService;
|
||||
parent::setUp();
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
$this->mediaMetaDataService = $this->mockIocDependency(MediaMetadataService::class);
|
||||
PHPMockery::mock('App\Listeners', 'ini_get')->andReturn(true);
|
||||
}
|
||||
|
||||
$this->mediaMetaDataService = $this->mockIocDependency(MediaMetadataService::class);
|
||||
}
|
||||
public function testHandle()
|
||||
{
|
||||
$artist = factory(Artist::class)->make(['image' => null]);
|
||||
$event = new ArtistInformationFetched($artist, ['image' => 'https://foo.bar/baz.jpg']);
|
||||
|
||||
public function testHandle()
|
||||
{
|
||||
$artist = factory(Artist::class)->make(['image' => null]);
|
||||
$event = new ArtistInformationFetched($artist, ['image' => 'https://foo.bar/baz.jpg']);
|
||||
$this->mediaMetaDataService
|
||||
->shouldReceive('downloadArtistImage')
|
||||
->once()
|
||||
->with($artist, 'https://foo.bar/baz.jpg');
|
||||
|
||||
$this->mediaMetaDataService
|
||||
->shouldReceive('downloadArtistImage')
|
||||
->once()
|
||||
->with($artist, 'https://foo.bar/baz.jpg');
|
||||
|
||||
event($event);
|
||||
}
|
||||
event($event);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,20 +9,16 @@ use Exception;
|
|||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use Illuminate\Contracts\Cache\Repository as Cache;
|
||||
use Illuminate\Log\Logger;
|
||||
use Mockery;
|
||||
use Tests\TestCase;
|
||||
|
||||
class LastfmServiceTest extends TestCase
|
||||
{
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testGetArtistInformation()
|
||||
public function testGetArtistInformation(): void
|
||||
{
|
||||
/** @var Artist $artist */
|
||||
$artist = factory(Artist::class)->make(['name' => 'foo']);
|
||||
|
@ -32,7 +28,7 @@ class LastfmServiceTest extends TestCase
|
|||
'get' => new Response(200, [], file_get_contents(__DIR__.'../../../blobs/lastfm/artist.xml')),
|
||||
]);
|
||||
|
||||
$api = new LastfmService($client, app(Cache::class));
|
||||
$api = new LastfmService($client, app(Cache::class), app(Logger::class));
|
||||
$info = $api->getArtistInformation($artist->name);
|
||||
|
||||
$this->assertEquals([
|
||||
|
@ -44,11 +40,10 @@ class LastfmServiceTest extends TestCase
|
|||
],
|
||||
], $info);
|
||||
|
||||
// And the response XML is cached as well
|
||||
$this->assertNotNull(cache('0aff3bc1259154f0e9db860026cda7a6'));
|
||||
self::assertNotNull(cache('0aff3bc1259154f0e9db860026cda7a6'));
|
||||
}
|
||||
|
||||
public function testGetArtistInformationForNonExistentArtist()
|
||||
public function testGetArtistInformationForNonExistentArtist(): void
|
||||
{
|
||||
/** @var Artist $artist */
|
||||
$artist = factory(Artist::class)->make();
|
||||
|
@ -58,15 +53,15 @@ class LastfmServiceTest extends TestCase
|
|||
'get' => new Response(400, [], file_get_contents(__DIR__.'../../../blobs/lastfm/artist-notfound.xml')),
|
||||
]);
|
||||
|
||||
$api = new LastfmService($client, app(Cache::class));
|
||||
$api = new LastfmService($client, app(Cache::class), app(Logger::class));
|
||||
|
||||
$this->assertNull($api->getArtistInformation($artist->name));
|
||||
self::assertNull($api->getArtistInformation($artist->name));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testGetAlbumInformation()
|
||||
public function testGetAlbumInformation(): void
|
||||
{
|
||||
/** @var Album $album */
|
||||
$album = factory(Album::class)->create([
|
||||
|
@ -79,7 +74,7 @@ class LastfmServiceTest extends TestCase
|
|||
'get' => new Response(200, [], file_get_contents(__DIR__.'../../../blobs/lastfm/album.xml')),
|
||||
]);
|
||||
|
||||
$api = new LastfmService($client, app(Cache::class));
|
||||
$api = new LastfmService($client, app(Cache::class), app(Logger::class));
|
||||
$info = $api->getAlbumInformation($album->name, $album->artist->name);
|
||||
|
||||
// Then I get the album's info
|
||||
|
@ -104,10 +99,10 @@ class LastfmServiceTest extends TestCase
|
|||
],
|
||||
], $info);
|
||||
|
||||
$this->assertNotNull(cache('fca889d13b3222589d7d020669cc5a38'));
|
||||
self::assertNotNull(cache('fca889d13b3222589d7d020669cc5a38'));
|
||||
}
|
||||
|
||||
public function testGetAlbumInformationForNonExistentAlbum()
|
||||
public function testGetAlbumInformationForNonExistentAlbum(): void
|
||||
{
|
||||
/** @var Album $album */
|
||||
$album = factory(Album::class)->create();
|
||||
|
@ -117,8 +112,8 @@ class LastfmServiceTest extends TestCase
|
|||
'get' => new Response(400, [], file_get_contents(__DIR__.'../../../blobs/lastfm/album-notfound.xml')),
|
||||
]);
|
||||
|
||||
$api = new LastfmService($client, app(Cache::class));
|
||||
$api = new LastfmService($client, app(Cache::class), app(Logger::class));
|
||||
|
||||
$this->assertNull($api->getAlbumInformation($album->name, $album->artist->name));
|
||||
self::assertNull($api->getAlbumInformation($album->name, $album->artist->name));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace Tests\Integration\Services;
|
|||
use App\Models\Album;
|
||||
use App\Models\Artist;
|
||||
use App\Services\MediaMetadataService;
|
||||
use Illuminate\Log\Logger;
|
||||
use org\bovigo\vfs\vfsStream;
|
||||
use Tests\TestCase;
|
||||
|
||||
|
@ -16,11 +17,10 @@ class MediaMetadataServiceTest extends TestCase
|
|||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->mediaMetadataService = new MediaMetadataService();
|
||||
$this->mediaMetadataService = new MediaMetadataService(app(Logger::class));
|
||||
}
|
||||
|
||||
public function testCopyAlbumCover()
|
||||
public function testCopyAlbumCover(): void
|
||||
{
|
||||
/** @var Album $album */
|
||||
$album = factory(Album::class)->create();
|
||||
|
@ -34,7 +34,7 @@ class MediaMetadataServiceTest extends TestCase
|
|||
$this->assertEquals('http://localhost/public/img/covers/bar.jpg', Album::find($album->id)->cover);
|
||||
}
|
||||
|
||||
public function testWriteAlbumCover()
|
||||
public function testWriteAlbumCover(): void
|
||||
{
|
||||
/** @var Album $album */
|
||||
$album = factory(Album::class)->create();
|
||||
|
@ -48,7 +48,7 @@ class MediaMetadataServiceTest extends TestCase
|
|||
$this->assertEquals('http://localhost/public/img/covers/foo.jpg', Album::find($album->id)->cover);
|
||||
}
|
||||
|
||||
public function testWriteArtistImage()
|
||||
public function testWriteArtistImage(): void
|
||||
{
|
||||
/** @var Artist $artist */
|
||||
$artist = factory(Artist::class)->create();
|
||||
|
|
|
@ -6,30 +6,26 @@ use App\Services\YouTubeService;
|
|||
use Exception;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use Mockery as m;
|
||||
use Illuminate\Contracts\Cache\Repository;
|
||||
use Illuminate\Log\Logger;
|
||||
use Mockery;
|
||||
use Tests\TestCase;
|
||||
|
||||
class YouTubeServiceTest extends TestCase
|
||||
{
|
||||
protected function tearDown()
|
||||
{
|
||||
m::close();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testSearch()
|
||||
public function testSearch(): void
|
||||
{
|
||||
$this->withoutEvents();
|
||||
|
||||
/** @var Client $client */
|
||||
$client = m::mock(Client::class, [
|
||||
$client = Mockery::mock(Client::class, [
|
||||
'get' => new Response(200, [], file_get_contents(__DIR__.'../../../blobs/youtube/search.json')),
|
||||
]);
|
||||
|
||||
$api = new YouTubeService($client);
|
||||
$api = new YouTubeService($client, app(Repository::class), app(Logger::class));
|
||||
$response = $api->search('Lorem Ipsum');
|
||||
|
||||
$this->assertEquals('Slipknot - Snuff [OFFICIAL VIDEO]', $response->items[0]->snippet->title);
|
||||
|
|
|
@ -7,8 +7,8 @@ use Exception;
|
|||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use Illuminate\Contracts\Cache\Repository as Cache;
|
||||
use Illuminate\Log\Logger;
|
||||
use Mockery;
|
||||
use Mockery\MockInterface;
|
||||
use Tests\TestCase;
|
||||
|
||||
class iTunesServiceTest extends TestCase
|
||||
|
@ -16,7 +16,7 @@ class iTunesServiceTest extends TestCase
|
|||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testGetTrackUrl()
|
||||
public function testGetTrackUrl(): void
|
||||
{
|
||||
$term = 'Foo Bar';
|
||||
|
||||
|
@ -25,10 +25,10 @@ class iTunesServiceTest extends TestCase
|
|||
'get' => new Response(200, [], file_get_contents(__DIR__.'../../../blobs/itunes/track.json')),
|
||||
]);
|
||||
|
||||
/** @var Cache|MockInterface $cache */
|
||||
$cache = app(Cache::class);
|
||||
$logger = app(Logger::class);
|
||||
|
||||
$url = (new iTunesService($client, $cache))->getTrackUrl($term);
|
||||
$url = (new iTunesService($client, $cache, $logger))->getTrackUrl($term);
|
||||
|
||||
self::assertEquals(
|
||||
'https://itunes.apple.com/us/album/i-remember-you/id265611220?i=265611396&uo=4&at=1000lsGu',
|
||||
|
|
|
@ -4,8 +4,10 @@ namespace Tests\Unit\Services;
|
|||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use Illuminate\Contracts\Cache\Repository as Cache;
|
||||
use Illuminate\Foundation\Testing\WithoutMiddleware;
|
||||
use Mockery as m;
|
||||
use Illuminate\Log\Logger;
|
||||
use Mockery;
|
||||
use Tests\TestCase;
|
||||
use Tests\Unit\Stubs\ConcreteApiClient;
|
||||
|
||||
|
@ -13,15 +15,36 @@ class ApiClientTest extends TestCase
|
|||
{
|
||||
use WithoutMiddleware;
|
||||
|
||||
public function testBuildUri()
|
||||
{
|
||||
/** @var Client $client */
|
||||
$client = m::mock(Client::class);
|
||||
$api = new ConcreteApiClient($client);
|
||||
/** @var Cache */
|
||||
private $cache;
|
||||
|
||||
$this->assertEquals('http://foo.com/get/param?key=bar', $api->buildUrl('get/param'));
|
||||
$this->assertEquals('http://foo.com/get/param?baz=moo&key=bar', $api->buildUrl('/get/param?baz=moo'));
|
||||
$this->assertEquals('http://baz.com/?key=bar', $api->buildUrl('http://baz.com/'));
|
||||
/** @var Client */
|
||||
private $client;
|
||||
|
||||
/** @var Logger */
|
||||
private $logger;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
/**
|
||||
* @var Client client
|
||||
* @var Cache cache
|
||||
* @var Logger logger
|
||||
*/
|
||||
$this->client = Mockery::mock(Client::class);
|
||||
$this->cache = Mockery::mock(Cache::class);
|
||||
$this->logger = Mockery::mock(Logger::class);
|
||||
}
|
||||
|
||||
public function testBuildUri(): void
|
||||
{
|
||||
$api = new ConcreteApiClient($this->client, $this->cache, $this->logger);
|
||||
|
||||
self::assertEquals('http://foo.com/get/param?key=bar', $api->buildUrl('get/param'));
|
||||
self::assertEquals('http://foo.com/get/param?baz=moo&key=bar', $api->buildUrl('/get/param?baz=moo'));
|
||||
self::assertEquals('http://baz.com/?key=bar', $api->buildUrl('http://baz.com/'));
|
||||
}
|
||||
|
||||
public function provideRequestData()
|
||||
|
@ -43,12 +66,12 @@ class ApiClientTest extends TestCase
|
|||
public function testRequest($method, $responseBody)
|
||||
{
|
||||
/** @var Client $client */
|
||||
$client = m::mock(Client::class, [
|
||||
$client = Mockery::mock(Client::class, [
|
||||
$method => new Response(200, [], $responseBody),
|
||||
]);
|
||||
|
||||
$api = new ConcreteApiClient($client);
|
||||
$api = new ConcreteApiClient($client, $this->cache, $this->logger);
|
||||
|
||||
$this->assertSame((array) json_decode($responseBody), (array) $api->$method('/'));
|
||||
self::assertSame((array) json_decode($responseBody), (array) $api->$method('/'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace E2E;
|
||||
|
||||
use Facebook\WebDriver\WebDriverBy;
|
||||
|
||||
class AlbumsScreenTest extends TestCase
|
||||
{
|
||||
public function testAlbumsScreen()
|
||||
{
|
||||
$this->loginAndGoTo('albums');
|
||||
|
||||
static::assertNotEmpty($this->els('#albumsWrapper .albums article.item'));
|
||||
$firstAlbum = $this->el('#albumsWrapper .albums article.item:nth-child(1)');
|
||||
static::assertNotEmpty($firstAlbum->findElement(WebDriverBy::cssSelector('.info .name'))->getText());
|
||||
static::assertNotEmpty($firstAlbum->findElement(WebDriverBy::cssSelector('.info .artist'))->getText());
|
||||
static::assertContains('10 songs', $firstAlbum->findElement(WebDriverBy::cssSelector('.meta'))->getText());
|
||||
|
||||
// test the view modes
|
||||
$this->click('#albumsWrapper > h1.heading > span.view-modes > a.list');
|
||||
static::assertCount(1, $this->els('#albumsWrapper > div.albums.as-list'));
|
||||
$this->click('#albumsWrapper > h1.heading > span.view-modes > a.thumbnails');
|
||||
static::assertCount(1, $this->els('#albumsWrapper > div.albums.as-thumbnails'));
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace E2E;
|
||||
|
||||
abstract class AllSongsScreenTest extends TestCase
|
||||
{
|
||||
// All we need to test should have been cover in SongListTest class.
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace E2E;
|
||||
|
||||
use Facebook\WebDriver\WebDriverBy;
|
||||
|
||||
class ArtistsScreenTest extends TestCase
|
||||
{
|
||||
public function testArtistsScreen()
|
||||
{
|
||||
$this->loginAndGoTo('artists');
|
||||
|
||||
static::assertNotEmpty($this->els('#artistsWrapper .artists article.item'));
|
||||
$firstArtist = $this->el('#artistsWrapper .artists article.item:nth-child(1)');
|
||||
static::assertNotEmpty($firstArtist->findElement(WebDriverBy::cssSelector('.info .name'))->getText());
|
||||
static::assertContains('5 albums • 50 songs', $firstArtist->findElement(WebDriverBy::cssSelector('.meta'))->getText());
|
||||
|
||||
// test the view modes
|
||||
$this->click('#artistsWrapper > h1.heading > span.view-modes > a.list');
|
||||
static::assertCount(1, $this->els('#artistsWrapper > div.artists.as-list'));
|
||||
$this->click('#artistsWrapper > h1.heading > span.view-modes > a.thumbnails');
|
||||
static::assertCount(1, $this->els('#artistsWrapper > div.artists.as-thumbnails'));
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace E2E;
|
||||
|
||||
class DefaultsTest extends TestCase
|
||||
{
|
||||
public function testDefaults()
|
||||
{
|
||||
static::assertContains('Koel', $this->driver->getTitle());
|
||||
|
||||
$formSelector = '#app > div.login-wrapper > form';
|
||||
|
||||
// Our login form should be there.
|
||||
static::assertCount(1, $this->els($formSelector));
|
||||
|
||||
// We submit rubbish and expect an error class on the form.
|
||||
$this->login('foo@bar.com', 'ThisIsWongOnSoManyLevels')
|
||||
->see("$formSelector.error");
|
||||
|
||||
// Now we submit good stuff and make sure we're in.
|
||||
$this->login()
|
||||
->seeText('Koel Admin', '#userBadge > a.view-profile.control > span');
|
||||
|
||||
// Default URL must be Home
|
||||
static::assertEquals($this->url.'/#!/home', $this->driver->getCurrentURL());
|
||||
|
||||
// While we're at this, test logging out as well.
|
||||
$this->click('#userBadge > a.logout');
|
||||
$this->see($formSelector);
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace E2E;
|
||||
|
||||
abstract class FavoritesScreenTest extends TestCase
|
||||
{
|
||||
// "Favorites" functionality has been covered in SongListTest
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace E2E;
|
||||
|
||||
use Facebook\WebDriver\Remote\RemoteWebElement;
|
||||
|
||||
class HomeScreenTest extends TestCase
|
||||
{
|
||||
public function testHomeScreen()
|
||||
{
|
||||
$this->loginAndGoTo('home');
|
||||
|
||||
// We must see some greetings
|
||||
static::assertTrue($this->el('#homeWrapper > h1')->isDisplayed());
|
||||
|
||||
// 6 recently added albums
|
||||
static::assertCount(6, $this->els('#homeWrapper section.recently-added article'));
|
||||
|
||||
// 10 recently added songs
|
||||
static::assertCount(10, $this->els('#homeWrapper .recently-added-song-list .song-item-home'));
|
||||
|
||||
// Shuffle must work for latest albums
|
||||
$this->click('#homeWrapper section.recently-added article:nth-child(1) a.shuffle-album');
|
||||
static::assertCount(10, $this->els('#queueWrapper .song-list-wrap tr.song-item'));
|
||||
|
||||
$this->goto('home');
|
||||
|
||||
// Simulate a "double click to play" action
|
||||
/** @var $clickedSong RemoteWebElement */
|
||||
$clickedSong = $this->el('#homeWrapper section.recently-added > div > div:nth-child(2) li:nth-child(1) .details');
|
||||
$this->doubleClick($clickedSong);
|
||||
// The song must appear at the top of "Recently played" section
|
||||
/** @var $mostRecentSong RemoteWebElement */
|
||||
$mostRecentSong = $this->el('#homeWrapper .recently-added-song-list .song-item-home:nth-child(1) .details');
|
||||
static::assertEquals($mostRecentSong->getText(), $clickedSong->getText());
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace E2E;
|
||||
|
||||
/**
|
||||
* Class PlaybackControlsTest.
|
||||
*
|
||||
* Tests the playback controls (the footer buttons).
|
||||
*/
|
||||
class PlaybackControlsTest extends TestCase
|
||||
{
|
||||
public function testPlaybackControls()
|
||||
{
|
||||
$this->loginAndWait();
|
||||
|
||||
// Show and hide the extra panel
|
||||
$this->click('#mainFooter .control.info');
|
||||
$this->notSee('#extra');
|
||||
$this->click('#mainFooter .control.info');
|
||||
$this->see('#extra');
|
||||
|
||||
// Show and hide the Presets
|
||||
$this->click('#mainFooter .control.equalizer');
|
||||
$this->see('#equalizer');
|
||||
// clicking anywhere else should close the equalizer
|
||||
$this->click('#extra');
|
||||
$this->notSee('#equalizer');
|
||||
|
||||
// Circle around the repeat state
|
||||
$this->click('#mainFooter .control.repeat');
|
||||
$this->see('#mainFooter .control.repeat.REPEAT_ALL');
|
||||
$this->click('#mainFooter .control.repeat');
|
||||
$this->see('#mainFooter .control.repeat.REPEAT_ONE');
|
||||
$this->click('#mainFooter .control.repeat');
|
||||
$this->see('#mainFooter .control.repeat.NO_REPEAT');
|
||||
|
||||
// Mute/unmute
|
||||
$currentValue = $this->el('#volumeRange')->getAttribute('value');
|
||||
$this->click('#volume .fa-volume-up');
|
||||
$this->see('#mainFooter .fa-volume-off');
|
||||
static::assertEquals(0, $this->el('#volumeRange')->getAttribute('value'));
|
||||
$this->click('#volume .fa-volume-off');
|
||||
$this->see('#mainFooter .fa-volume-up');
|
||||
static::assertEquals($currentValue, $this->el('#volumeRange')->getAttribute('value'));
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace E2E;
|
||||
|
||||
class PlaylistScreenTest extends TestCase
|
||||
{
|
||||
use SongListActions;
|
||||
|
||||
public function testPlaylistScreen()
|
||||
{
|
||||
$this->loginAndGoTo('songs')
|
||||
->selectRange()
|
||||
->createPlaylist('Bar')
|
||||
->seeText('Bar', '#playlists > ul');
|
||||
|
||||
$this->click('#sidebar .playlist:nth-last-child(1)');
|
||||
$this->see('#playlistWrapper');
|
||||
|
||||
$this->click('#playlistWrapper .btn-delete-playlist');
|
||||
// expect a confirmation
|
||||
$this->see('.alertify');
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace E2E;
|
||||
|
||||
use Facebook\WebDriver\WebDriverExpectedCondition;
|
||||
use Facebook\WebDriver\WebDriverKeys;
|
||||
|
||||
class ProfileScreenTest extends TestCase
|
||||
{
|
||||
public function testProfileScreen()
|
||||
{
|
||||
$this->loginAndWait()
|
||||
->click('a.view-profile');
|
||||
$this->see('#profileWrapper')
|
||||
// Now we change some user profile details
|
||||
->typeIn('#profileWrapper input[name="name"]', 'Mr Bar')
|
||||
->typeIn('#profileWrapper input[name="email"]', 'bar@koel.net')
|
||||
->enter()
|
||||
->see('.alertify-logs')
|
||||
// Dismiss the alert first
|
||||
->press(WebDriverKeys::ESCAPE)
|
||||
->notSee('.alertify-logs');
|
||||
|
||||
$avatar = $this->el('a.view-profile img');
|
||||
// Expect the Gravatar to be updated
|
||||
static::assertEquals('https://www.gravatar.com/avatar/36df72b4484fed183fad058f30b55d21?s=256', $avatar->getAttribute('src'));
|
||||
|
||||
// Check "Confirm Closing" and validate its functionality
|
||||
$this->click('#profileWrapper input[name="confirmClosing"]');
|
||||
$this->refresh()
|
||||
->waitUntil(WebDriverExpectedCondition::alertIsPresent());
|
||||
$this->driver->switchTo()->alert()->dismiss();
|
||||
|
||||
// Reverse all changes for other tests to not be affected
|
||||
$this->typeIn('#profileWrapper input[name="name"]', 'Koel Admin')
|
||||
->typeIn('#profileWrapper input[name="email"]', 'koel@example.com')
|
||||
->enter()
|
||||
->see('.alertify-logs')
|
||||
->press(WebDriverKeys::ESCAPE)
|
||||
->notSee('.alertify-logs')
|
||||
->click('#profileWrapper input[name="confirmClosing"]');
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace E2E;
|
||||
|
||||
class QueueScreenTest extends TestCase
|
||||
{
|
||||
public function test()
|
||||
{
|
||||
$this->loginAndGoTo('queue');
|
||||
static::assertContains('Current Queue', $this->el('#queueWrapper > h1 > span')->getText());
|
||||
|
||||
// As the queue is currently empty, the "Shuffling all song" link should be there
|
||||
$this->click('#queueWrapper a.start');
|
||||
$this->see('#queueWrapper .song-item');
|
||||
|
||||
// Clear the queue
|
||||
$this->click('#queueWrapper .buttons button.btn-clear-queue');
|
||||
static::assertEmpty($this->els('#queueWrapper tr.song-item'));
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace E2E;
|
||||
|
||||
class SideBarTest extends TestCase
|
||||
{
|
||||
public function testSideBar()
|
||||
{
|
||||
$this->loginAndWait();
|
||||
|
||||
// All basic navigation
|
||||
foreach (['home', 'queue', 'songs', 'albums', 'artists', 'youtube', 'settings', 'users'] as $screen) {
|
||||
$this->goto($screen)
|
||||
->waitUntil(function () use ($screen) {
|
||||
return $this->driver->getCurrentURL() === $this->url.'/#!/'.$screen;
|
||||
});
|
||||
}
|
||||
|
||||
// Add a playlist
|
||||
$this->click('#playlists > h1 > i.create');
|
||||
$this->see('#playlists > form.create')
|
||||
->typeIn('#playlists > form > input[type="text"]', 'Bar')
|
||||
->enter()
|
||||
->waitUntil(function () {
|
||||
$list = $this->els('#playlists .playlist');
|
||||
|
||||
return end($list)->getText() === 'Bar';
|
||||
});
|
||||
|
||||
// Double click to edit/rename a playlist
|
||||
$this->doubleClick('#playlists .playlist:nth-child(2)')
|
||||
->see('#playlists .playlist:nth-child(2) input[type="text"]')
|
||||
->typeIn('#playlists .playlist:nth-child(2) input[type="text"]', 'Qux')
|
||||
->enter()
|
||||
->seeText('Qux', '#playlists .playlist:nth-child(2)');
|
||||
|
||||
// Edit with an empty name shouldn't do anything.
|
||||
$this->doubleClick('#playlists .playlist:nth-child(2)');
|
||||
$this->click('#playlists .playlist:nth-child(2) input[type="text"]')->clear();
|
||||
$this->enter()->seeText('Qux', '#playlists .playlist:nth-child(2)');
|
||||
}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace E2E;
|
||||
|
||||
use Facebook\WebDriver\Interactions\WebDriverActions;
|
||||
use Facebook\WebDriver\WebDriverKeys;
|
||||
|
||||
trait SongListActions
|
||||
{
|
||||
use WebDriverShortcuts;
|
||||
|
||||
public function selectAllSongs()
|
||||
{
|
||||
$this->click($this->wrapperId); // make sure focus is there before executing shortcut keys
|
||||
(new WebDriverActions($this->driver))
|
||||
->keyDown(null, WebDriverKeys::COMMAND)
|
||||
->keyDown(null, 'A')
|
||||
->keyUp(null, 'A')
|
||||
->keyUp(null, WebDriverKeys::COMMAND)
|
||||
->perform();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function selectSong($i = 1)
|
||||
{
|
||||
return $this->click("{$this->wrapperId} tr.song-item:nth-child($i)");
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a range of songs.
|
||||
*
|
||||
* @param int $from
|
||||
* @param int $to
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function selectRange($from = 1, $to = 5)
|
||||
{
|
||||
$this->click("{$this->wrapperId} tr.song-item:nth-child($from)");
|
||||
|
||||
(new WebDriverActions($this->driver))
|
||||
->keyDown(null, WebDriverKeys::SHIFT)
|
||||
->click($this->el("{$this->wrapperId} tr.song-item:nth-child($to)"))
|
||||
->keyUp(null, WebDriverKeys::SHIFT)
|
||||
->perform();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deselect (Ctrl/Cmd+click) songs.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function cmdSelectSongs()
|
||||
{
|
||||
$actions = (new WebDriverActions($this->driver))->keyDown(null, WebDriverKeys::COMMAND);
|
||||
foreach (func_get_args() as $i) {
|
||||
$actions->click($this->el("{$this->wrapperId} tr.song-item:nth-child($i)"));
|
||||
}
|
||||
$actions->keyUp(null, WebDriverKeys::COMMAND)->perform();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function rightClickOnSong($i = 1)
|
||||
{
|
||||
$this->rightClick("{$this->wrapperId} tr.song-item:nth-child($i)");
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function createPlaylist($playlistName)
|
||||
{
|
||||
// Try adding a song into a new playlist
|
||||
$this->click("{$this->wrapperId} .buttons button.btn-add-to");
|
||||
$this->typeIn("{$this->wrapperId} .buttons input[type='text']", $playlistName)
|
||||
->enter();
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -1,159 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace E2E;
|
||||
|
||||
use Facebook\WebDriver\WebDriverBy;
|
||||
use Facebook\WebDriver\WebDriverElement;
|
||||
use Facebook\WebDriver\WebDriverKeys;
|
||||
|
||||
class SongListTest extends TestCase
|
||||
{
|
||||
use SongListActions;
|
||||
|
||||
public function testSelection()
|
||||
{
|
||||
$this->loginAndWait()->repopulateList();
|
||||
|
||||
// Single song selection
|
||||
static::assertContains('selected', $this->selectSong()->getAttribute('class'));
|
||||
|
||||
// Shift+Click
|
||||
$this->selectRange();
|
||||
// should have 5 selected rows
|
||||
static::assertCount(5, $this->els('#queueWrapper tr.song-item.selected'));
|
||||
|
||||
// Cmd+Click
|
||||
$this->cmdSelectSongs(2, 3);
|
||||
// should have only 3 selected rows remaining
|
||||
static::assertCount(3, $this->els('#queueWrapper tr.song-item.selected'));
|
||||
// 2nd and 3rd rows must not be selected
|
||||
static::assertNotContains(
|
||||
'selected',
|
||||
$this->el('#queueWrapper tr.song-item:nth-child(2)')->getAttribute('class')
|
||||
);
|
||||
static::assertNotContains(
|
||||
'selected',
|
||||
$this->el('#queueWrapper tr.song-item:nth-child(3)')->getAttribute('class')
|
||||
);
|
||||
|
||||
// Delete key should remove selected songs
|
||||
$this->press(WebDriverKeys::DELETE)->waitUntil(function () {
|
||||
return count($this->els('#queueWrapper tr.song-item.selected')) === 0
|
||||
&& count($this->els('#queueWrapper tr.song-item')) === 7;
|
||||
});
|
||||
|
||||
// Ctrl+A/Cmd+A should select all songs
|
||||
$this->selectAllSongs();
|
||||
static::assertCount(7, $this->els('#queueWrapper tr.song-item.selected'));
|
||||
}
|
||||
|
||||
public function testActionButtons()
|
||||
{
|
||||
$this->loginAndWait()
|
||||
->repopulateList()
|
||||
// Since no songs are selected, the "Shuffle All" button must be shown
|
||||
->see('#queueWrapper button.btn-shuffle-all')
|
||||
// Now we selected all songs for the "Shuffle Selected" button to be shown
|
||||
->selectAllSongs()
|
||||
->see('#queueWrapper button.btn-shuffle-selected');
|
||||
|
||||
// Add to favorites
|
||||
$this->selectSong();
|
||||
$this->click('#queueWrapper .buttons button.btn-add-to');
|
||||
$this->click('#queueWrapper .buttons .add-to li.favorites');
|
||||
$this->goto('favorites');
|
||||
static::assertCount(1, $this->els('#favoritesWrapper tr.song-item'));
|
||||
|
||||
$this->goto('queue')
|
||||
->selectSong();
|
||||
// Try adding a song into a new playlist
|
||||
$this->createPlaylist('Foo')
|
||||
->seeText('Foo', '#playlists > ul');
|
||||
}
|
||||
|
||||
public function testSorting()
|
||||
{
|
||||
$this->loginAndWait()->repopulateList();
|
||||
|
||||
// Confirm that we can't sort in Queue screen
|
||||
/** @var WebDriverElement $th */
|
||||
foreach ($this->els('#queueWrapper div.song-list-wrap th') as $th) {
|
||||
if (!$th->isDisplayed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($th->findElements(WebDriverBy::tagName('i')) as $sortDirectionIcon) {
|
||||
static::assertFalse($sortDirectionIcon->isDisplayed());
|
||||
}
|
||||
}
|
||||
|
||||
// Now go to All Songs screen and sort there
|
||||
$this->goto('songs')
|
||||
->click('#songsWrapper div.song-list-wrap th:nth-child(2)');
|
||||
$last = null;
|
||||
$results = [];
|
||||
/** @var WebDriverElement $td */
|
||||
foreach ($this->els('#songsWrapper div.song-list-wrap td.title') as $td) {
|
||||
$current = $td->getText();
|
||||
$results[] = $last === null ? true : $current <= $last;
|
||||
$last = $current;
|
||||
}
|
||||
static::assertNotContains(false, $results);
|
||||
|
||||
// Second click will reverse the sort
|
||||
$this->click('#songsWrapper div.song-list-wrap th:nth-child(2)');
|
||||
$last = null;
|
||||
$results = [];
|
||||
/** @var WebDriverElement $td */
|
||||
foreach ($this->els('#songsWrapper div.song-list-wrap td.title') as $td) {
|
||||
$current = $td->getText();
|
||||
$results[] = $last === null ? true : $current >= $last;
|
||||
$last = $current;
|
||||
}
|
||||
static::assertNotContains(false, $results);
|
||||
}
|
||||
|
||||
public function testContextMenu()
|
||||
{
|
||||
$this->loginAndGoTo('songs')
|
||||
->rightClickOnSong()
|
||||
->see('#songsWrapper .song-menu');
|
||||
|
||||
// 7 sub menu items
|
||||
static::assertCount(7, $this->els('#songsWrapper .song-menu > li'));
|
||||
|
||||
// Clicking the "Go to Album" menu item
|
||||
$this->click('#songsWrapper .song-menu > li:nth-child(2)');
|
||||
$this->see('#albumWrapper');
|
||||
|
||||
// Clicking the "Go to Artist" menu item
|
||||
$this->back()
|
||||
->rightClickOnSong()
|
||||
->click('#songsWrapper .song-menu > li:nth-child(3)');
|
||||
$this->see('#artistWrapper');
|
||||
|
||||
// Clicking "Edit"
|
||||
$this->back()
|
||||
->rightClickOnSong()
|
||||
->click('#songsWrapper .song-menu > li:nth-child(5)');
|
||||
$this->see('#editSongsOverlay form');
|
||||
|
||||
// Updating song
|
||||
$this->typeIn('#editSongsOverlay form input[name="title"]', 'Foo')
|
||||
->typeIn('#editSongsOverlay form input[name="track"]', 99)
|
||||
->enter()
|
||||
->notSee('#editSongsOverlay form');
|
||||
static::assertEquals('99', $this->el('#songsWrapper tr.song-item:nth-child(1) .track-number')->getText());
|
||||
static::assertEquals('Foo', $this->el('#songsWrapper tr.song-item:nth-child(1) .title')->getText());
|
||||
}
|
||||
|
||||
private function repopulateList()
|
||||
{
|
||||
// Go back to Albums and queue an album of 10 songs
|
||||
$this->goto('albums');
|
||||
$this->click('#albumsWrapper > div > article:nth-child(1) .meta a.shuffle-album');
|
||||
$this->goto('queue');
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -1,173 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace E2E;
|
||||
|
||||
use App\Application;
|
||||
use Facebook\WebDriver\Remote\DesiredCapabilities;
|
||||
use Facebook\WebDriver\Remote\RemoteWebDriver;
|
||||
use Facebook\WebDriver\WebDriverDimension;
|
||||
use Facebook\WebDriver\WebDriverPoint;
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
|
||||
abstract class TestCase extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
use WebDriverShortcuts;
|
||||
|
||||
/**
|
||||
* @var Application
|
||||
*/
|
||||
protected $app;
|
||||
|
||||
/**
|
||||
* ID of the current screen wrapper (with leading #).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $wrapperId;
|
||||
|
||||
/**
|
||||
* The default Koel URL for E2E (server by `php artisan serve --port=8081`).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $url = 'http://localhost:8081';
|
||||
protected $coverPath;
|
||||
|
||||
/**
|
||||
* TestCase constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->createApp();
|
||||
$this->resetData();
|
||||
$this->driver = RemoteWebDriver::create('http://localhost:4444/wd/hub', DesiredCapabilities::chrome());
|
||||
$this->driver->manage()->window()->setSize(new WebDriverDimension(1440, 900));
|
||||
$this->driver->manage()->window()->setPosition(new WebDriverPoint(0, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Application
|
||||
*/
|
||||
protected function createApp()
|
||||
{
|
||||
$this->app = require __DIR__.'/../../bootstrap/app.php';
|
||||
$this->app->make(Kernel::class)->bootstrap();
|
||||
|
||||
return $this->app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the test data for E2E tests.
|
||||
*/
|
||||
protected function resetData()
|
||||
{
|
||||
// Make sure we have a fresh database.
|
||||
@unlink(__DIR__.'/../../database/e2e.sqlite');
|
||||
touch(__DIR__.'/../../database/e2e.sqlite');
|
||||
|
||||
Artisan::call('migrate');
|
||||
Artisan::call('db:seed');
|
||||
Artisan::call('db:seed', ['--class' => 'E2EDataSeeder']);
|
||||
|
||||
if (!file_exists($this->coverPath)) {
|
||||
@mkdir($this->coverPath, 0777, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log into Koel.
|
||||
*
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function login($username = 'koel@example.com', $password = 'SoSecureK0el')
|
||||
{
|
||||
$this->typeIn("#app > div.login-wrapper > form > [type='email']", $username);
|
||||
$this->typeIn("#app > div.login-wrapper > form > [type='password']", $password);
|
||||
$this->enter();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log in and wait for the app to finish loading.
|
||||
*
|
||||
* @throws \Exception
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function loginAndWait()
|
||||
{
|
||||
$this->login()
|
||||
->seeText('Koel Admin', '#userBadge > a.view-profile');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to a specific screen.
|
||||
*
|
||||
* @param $screen
|
||||
*
|
||||
* @throws \Exception
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function goto($screen)
|
||||
{
|
||||
$this->wrapperId = "#{$screen}Wrapper";
|
||||
|
||||
if ($screen === 'favorites') {
|
||||
$this->click('#sidebar .favorites a');
|
||||
} else {
|
||||
$this->click("#sidebar a.$screen");
|
||||
}
|
||||
|
||||
$this->see($this->wrapperId);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log in and go to a specific screen.
|
||||
*
|
||||
* @param $screen string
|
||||
*
|
||||
* @throws \Exception
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function loginAndGoTo($screen)
|
||||
{
|
||||
return $this->loginAndWait()->goto($screen);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the user to press ENTER key before continuing.
|
||||
*/
|
||||
protected function waitForUserInput()
|
||||
{
|
||||
if (trim(fgets(fopen('php://stdin', 'rb'))) !== chr(13)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected function focusIntoApp()
|
||||
{
|
||||
$this->click('#app');
|
||||
}
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->driver->get($this->url);
|
||||
}
|
||||
|
||||
public function tearDown()
|
||||
{
|
||||
$this->driver->quit();
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace E2E;
|
||||
|
||||
use Facebook\WebDriver\Interactions\Internal\WebDriverMouseMoveAction;
|
||||
|
||||
class UsersScreenTest extends TestCase
|
||||
{
|
||||
public function testUsersScreen()
|
||||
{
|
||||
$this->loginAndGoTo('users')->see('.user-item.me');
|
||||
// Hover to the first user item
|
||||
(new WebDriverMouseMoveAction($this->driver->getMouse(), $this->el('#usersWrapper .user-item.me')))
|
||||
->perform();
|
||||
// and validate that the button reads "Update Profile" instead of "Edit"
|
||||
static::assertContains('Update Profile',
|
||||
$this->el('#usersWrapper .user-item.me .btn-edit')->getText());
|
||||
// Also, clicking it should show the "Profile & Preferences" panel
|
||||
$this->click('#usersWrapper .user-item.me .btn-edit');
|
||||
$this->see('#profileWrapper')
|
||||
->back()
|
||||
// Add new user
|
||||
->click('#usersWrapper .btn-add');
|
||||
$this->see('form.user-add')
|
||||
->typeIn('form.user-add input[name="name"]', 'Foo')
|
||||
->typeIn('form.user-add input[name="email"]', 'foo@koel.net')
|
||||
->typeIn('form.user-add input[name="password"]', 'SecureMuch')
|
||||
->enter()
|
||||
->seeText('foo@koel.net', '#usersWrapper');
|
||||
|
||||
// Hover the next user item (not me)
|
||||
(new WebDriverMouseMoveAction($this->driver->getMouse(), $this->el('#usersWrapper .user-item:not(.me)')))
|
||||
->perform();
|
||||
static::assertContains('Edit', $this->el('#usersWrapper .user-item:not(.me) .btn-edit')->getText());
|
||||
// Edit user
|
||||
$this->click('#usersWrapper .user-item:not(.me) .btn-edit');
|
||||
$this->see('form.user-edit')
|
||||
->typeIn('form.user-edit input[name="email"]', 'bar@koel.net')
|
||||
->enter()
|
||||
->seeText('bar@koel.net', '#usersWrapper');
|
||||
}
|
||||
}
|
|
@ -1,257 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace E2E;
|
||||
|
||||
use Facebook\WebDriver\Interactions\Internal\WebDriverDoubleClickAction;
|
||||
use Facebook\WebDriver\Remote\RemoteWebDriver;
|
||||
use Facebook\WebDriver\WebDriverBy;
|
||||
use Facebook\WebDriver\WebDriverElement;
|
||||
use Facebook\WebDriver\WebDriverExpectedCondition;
|
||||
use Facebook\WebDriver\WebDriverKeys;
|
||||
|
||||
trait WebDriverShortcuts
|
||||
{
|
||||
/**
|
||||
* @var RemoteWebDriver
|
||||
*/
|
||||
protected $driver;
|
||||
|
||||
/**
|
||||
* @param $selector WebDriverElement|string
|
||||
*
|
||||
* @return WebDriverElement
|
||||
*/
|
||||
protected function el($selector)
|
||||
{
|
||||
if (is_string($selector)) {
|
||||
return $this->driver->findElement(WebDriverBy::cssSelector($selector));
|
||||
}
|
||||
|
||||
return $selector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of elements by a selector.
|
||||
*
|
||||
* @param $selector
|
||||
*
|
||||
* @return \Facebook\WebDriver\Remote\RemoteWebElement[]
|
||||
*/
|
||||
protected function els($selector)
|
||||
{
|
||||
return $this->driver->findElements(WebDriverBy::cssSelector($selector));
|
||||
}
|
||||
|
||||
/**
|
||||
* Type a string.
|
||||
*
|
||||
* @param $string
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function type($string)
|
||||
{
|
||||
$this->driver->getKeyboard()->sendKeys($string);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type into an element.
|
||||
*
|
||||
* @param $element
|
||||
* @param $string
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function typeIn($element, $string)
|
||||
{
|
||||
$this->click($element)->clear();
|
||||
|
||||
return $this->type($string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Press a key.
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function press($key = WebDriverKeys::ENTER)
|
||||
{
|
||||
$this->driver->getKeyboard()->pressKey($key);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Press enter.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function enter()
|
||||
{
|
||||
return $this->press();
|
||||
}
|
||||
|
||||
/**
|
||||
* Click an element.
|
||||
*
|
||||
* @param $element
|
||||
*
|
||||
* @return WebDriverElement
|
||||
*/
|
||||
protected function click($element)
|
||||
{
|
||||
return $this->el($element)->click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Right-click an element.
|
||||
*
|
||||
* @param $element
|
||||
*
|
||||
* @return \Facebook\WebDriver\Remote\RemoteMouse
|
||||
*/
|
||||
protected function rightClick($element)
|
||||
{
|
||||
return $this->driver->getMouse()->contextClick($this->el($element)->getCoordinates());
|
||||
}
|
||||
|
||||
/**
|
||||
* Double-click and element.
|
||||
*
|
||||
* @param $element
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function doubleClick($element)
|
||||
{
|
||||
(new WebDriverDoubleClickAction($this->driver->getMouse(), $this->el($element)))->perform();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleep (implicit wait) for some seconds.
|
||||
*
|
||||
* @param $seconds
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function sleep($seconds)
|
||||
{
|
||||
$this->driver->manage()->timeouts()->implicitlyWait($seconds);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until a condition is met.
|
||||
*
|
||||
* @param $func (closure|WebDriverExpectedCondition)
|
||||
* @param int $timeout
|
||||
*
|
||||
* @throws \Exception
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function waitUntil($func, $timeout = 10)
|
||||
{
|
||||
$this->driver->wait($timeout)->until($func);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait and validate an element to be visible.
|
||||
*
|
||||
* @param $selector
|
||||
*
|
||||
* @throws \Exception
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function see($selector)
|
||||
{
|
||||
$this->waitUntil(WebDriverExpectedCondition::visibilityOfElementLocated(
|
||||
WebDriverBy::cssSelector($selector)
|
||||
));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait and validate an element to be invisible.
|
||||
*
|
||||
* @param $selector string The element's CSS selector.
|
||||
*
|
||||
* @throws \Exception
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function notSee($selector)
|
||||
{
|
||||
$this->waitUntil(WebDriverExpectedCondition::invisibilityOfElementLocated(
|
||||
WebDriverBy::cssSelector($selector)
|
||||
));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait and validate a text to be visible in an element.
|
||||
*
|
||||
* @param $text
|
||||
* @param $selector string The element's CSS selector.
|
||||
*
|
||||
* @throws \Exception
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function seeText($text, $selector)
|
||||
{
|
||||
$this->waitUntil(WebDriverExpectedCondition::textToBePresentInElement(
|
||||
WebDriverBy::cssSelector($selector), $text
|
||||
));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate back.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function back()
|
||||
{
|
||||
$this->driver->navigate()->back();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate forward.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function forward()
|
||||
{
|
||||
$this->driver->navigate()->forward();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the page.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function refresh()
|
||||
{
|
||||
$this->driver->navigate()->refresh();
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace E2E;
|
||||
|
||||
/**
|
||||
* Class ZSettingsScreenTest
|
||||
* The name is an ugly trick to force this test to run last, due to it changing the whole suite's
|
||||
* data, causing other tests to fail otherwise.
|
||||
*/
|
||||
class ZSettingsScreenTest extends TestCase
|
||||
{
|
||||
public function testSettingsScreen()
|
||||
{
|
||||
$this->loginAndGoTo('settings')
|
||||
->typeIn('#inputSettingsPath', dirname(__DIR__.'/../songs'))
|
||||
->enter()
|
||||
// Wait for the page to reload
|
||||
->waitUntil(function () {
|
||||
return $this->driver->executeScript('return document.readyState') === 'complete';
|
||||
})
|
||||
// And for the loading screen to disappear
|
||||
->notSee('#overlay')
|
||||
->goto('albums')
|
||||
// and make sure the scanning is good.
|
||||
->seeText('Koel Testing Vol', '#albumsWrapper');
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue