release 3.0.0
This commit is contained in:
commit
516350ecf6
12 changed files with 408 additions and 21 deletions
14
CHANGELOG.md
14
CHANGELOG.md
|
|
@ -6,12 +6,26 @@ All notable changes to this project will be documented in this file.
|
|||
- This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
|
||||
- Commits respect [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) & use [Gitmoji](https://gitmoji.dev/).
|
||||
|
||||
## [1.0.3] - 2026-11-04
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- *(StorageServiceConfig)* Support scoped driver with boot-time underlying driver resolution
|
||||
|
||||
### 🎨 Styling
|
||||
|
||||
- *(tests)* Fix spacing in docblock for allowedExtensions
|
||||
|
||||
## [1.0.2] - 2026-11-04
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- *(StorageServiceConfig)* Add prefix handling and validation
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- *(cliff.toml)* Add spacing before release headings in changelog
|
||||
|
||||
## [1.0.1] - 2026-11-04
|
||||
|
||||
### 🚀 Features
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"name": "uwil/storage-laravel",
|
||||
"description": "Handles file storage",
|
||||
"license": "MIT",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"readme": "https://lab.s-web.fr/ldc-web/projets/storage-laravel/-/blob/main/README.md",
|
||||
"support": {
|
||||
"email": "digital@ldc.fr",
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ final class StorageServiceConfig
|
|||
|
||||
private ?string $prefix = null;
|
||||
|
||||
/** @var array<string, string> Laravel disk name => physical driver (scoped disks resolved to their parent) */
|
||||
private array $realDrivers = [];
|
||||
|
||||
/**
|
||||
* @param string[] $eligibleDisks
|
||||
* @param object{'disk':string,'driver':string}[] $laravelExistingDisks
|
||||
|
|
@ -140,6 +143,15 @@ final class StorageServiceConfig
|
|||
$disk->disk,
|
||||
$disk->driver
|
||||
), $intersectWithDrivers);
|
||||
|
||||
$this->resolveRealDrivers();
|
||||
}
|
||||
|
||||
private function resolveRealDrivers(): void
|
||||
{
|
||||
foreach ([$this->default, ...$this->eligibles] as $disk) {
|
||||
$this->realDrivers[$disk->value] = Utils::resolveDriver($disk->value, $disk->driver);
|
||||
}
|
||||
}
|
||||
|
||||
public function getPrefix(): ?string
|
||||
|
|
@ -191,4 +203,15 @@ final class StorageServiceConfig
|
|||
{
|
||||
return $this->allowedExtensions;
|
||||
}
|
||||
|
||||
public function getRealDriver(DiskName $disk): string
|
||||
{
|
||||
return $this->realDrivers[$disk->value]
|
||||
?? throw new UnexpectedStorageException("Disk {$disk->value} is neither the default nor an eligible disk");
|
||||
}
|
||||
|
||||
public function isS3Driver(DiskName $disk): bool
|
||||
{
|
||||
return $this->getRealDriver($disk) === 's3';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ namespace Uwil\Storage\Lib;
|
|||
use Illuminate\Contracts\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Uwil\Storage\Config\StorageServiceConfig;
|
||||
use Uwil\Storage\Contracts\PathGenerator;
|
||||
use Uwil\Storage\Contracts\StorageGateway;
|
||||
use Uwil\Storage\Exceptions\StorageException;
|
||||
|
|
@ -21,6 +22,7 @@ use Uwil\Storage\Lib\ValueObjects\StorageFilePath;
|
|||
readonly class StorageGatewayImpl implements StorageGateway
|
||||
{
|
||||
public function __construct(
|
||||
private StorageServiceConfig $config,
|
||||
private LoggerInterface $logger,
|
||||
private DateHashPathGenerator $datePathGenerator,
|
||||
private UuidPathGenerator $uuidPathGenerator
|
||||
|
|
@ -123,12 +125,12 @@ readonly class StorageGatewayImpl implements StorageGateway
|
|||
|
||||
private function pathGeneratorFor(DiskName $disk): PathGenerator
|
||||
{
|
||||
return match ($disk->driver) {
|
||||
'local' => $this->datePathGenerator,
|
||||
'ftp' => $this->datePathGenerator,
|
||||
'sftp' => $this->datePathGenerator,
|
||||
's3' => $this->uuidPathGenerator,
|
||||
default => throw new UnexpectedStorageException("Unsupported disk driver: {$disk->driver} on disk {$disk->value}"),
|
||||
$realDriver = $this->config->getRealDriver($disk);
|
||||
|
||||
return match ($realDriver) {
|
||||
'local', 'ftp', 'sftp' => $this->datePathGenerator,
|
||||
's3' => $this->uuidPathGenerator,
|
||||
default => throw new UnexpectedStorageException("Unsupported disk driver: {$realDriver} on disk {$disk->value}"),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,10 @@ declare(strict_types=1);
|
|||
|
||||
namespace Uwil\Storage\Lib;
|
||||
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Uwil\Storage\Config\StorageServiceConfig;
|
||||
use Uwil\Storage\Exceptions\UnexpectedStorageException;
|
||||
|
||||
class Utils
|
||||
{
|
||||
|
|
@ -14,6 +16,45 @@ class Utils
|
|||
// Abstract class.
|
||||
}
|
||||
|
||||
public static function resolveDriver(string $disk, ?string $driver = null): string
|
||||
{
|
||||
$currentDisk = $disk;
|
||||
|
||||
if ($driver === null) {
|
||||
/** @var mixed $raw */
|
||||
$raw = Config::get("filesystems.disks.{$currentDisk}.driver");
|
||||
$driver = is_string($raw) ? $raw : '';
|
||||
}
|
||||
|
||||
$visited = [$currentDisk];
|
||||
|
||||
while ($driver === 'scoped') {
|
||||
/** @var mixed $parentDisk */
|
||||
$parentDisk = Config::get("filesystems.disks.{$currentDisk}.disk");
|
||||
|
||||
if (! is_string($parentDisk) || $parentDisk === '') {
|
||||
throw new UnexpectedStorageException("Scoped disk '{$currentDisk}' is missing a valid parent 'disk' configuration.");
|
||||
}
|
||||
|
||||
if (in_array($parentDisk, $visited, true)) {
|
||||
throw new UnexpectedStorageException("Circular reference detected in scoped disk '{$currentDisk}'.");
|
||||
}
|
||||
|
||||
/** @var mixed $parentDriver */
|
||||
$parentDriver = Config::get("filesystems.disks.{$parentDisk}.driver");
|
||||
|
||||
if (! is_string($parentDriver) || $parentDriver === '') {
|
||||
throw new UnexpectedStorageException("Parent disk '{$parentDisk}' of scoped disk '{$currentDisk}' has no valid driver configured.");
|
||||
}
|
||||
|
||||
$visited[] = $parentDisk;
|
||||
$driver = $parentDriver;
|
||||
$currentDisk = $parentDisk;
|
||||
}
|
||||
|
||||
return $driver;
|
||||
}
|
||||
|
||||
public static function diskExists(string | \UnitEnum $disk): bool
|
||||
{
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use Uwil\Storage\Lib\Utils;
|
|||
|
||||
final readonly class DiskName implements \Stringable
|
||||
{
|
||||
public const array ALLOWED_DRIVERS = ['local', 'ftp', 'sftp', 's3'];
|
||||
public const array ALLOWED_DRIVERS = ['local', 'ftp', 'sftp', 's3', 'scoped'];
|
||||
|
||||
public string $value;
|
||||
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ class StorageServiceProvider extends ServiceProvider
|
|||
$this->app->bind(
|
||||
StorageGateway::class,
|
||||
fn (Application $app) => new StorageGatewayImpl(
|
||||
$app->make(StorageServiceConfig::class),
|
||||
$app->make(LoggerInterface::class),
|
||||
$app->make(DateHashPathGenerator::class),
|
||||
$app->make(UuidPathGenerator::class),
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Uwil\Storage\Tests\Unit\Config;
|
||||
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Uwil\Storage\Config\StorageServiceConfig;
|
||||
use Uwil\Storage\Exceptions\UnexpectedStorageException;
|
||||
use Uwil\Storage\Lib\ValueObjects\DiskName;
|
||||
|
|
@ -415,3 +416,97 @@ describe('StorageServiceConfig prefix validation and retrieval', function (): vo
|
|||
'double slash' => 'media//sub',
|
||||
]);
|
||||
});
|
||||
|
||||
describe('StorageServiceConfig real driver resolution', function (): void {
|
||||
it('resolves direct drivers of default and eligible disks', function (): void {
|
||||
// GIVEN
|
||||
Storage::fake('s3');
|
||||
|
||||
// WHEN
|
||||
$config = new StorageServiceConfig(
|
||||
defaultDisk: 'local',
|
||||
eligibleDisks: ['local', 's3'],
|
||||
laravelExistingDisks: [
|
||||
(object) ['disk' => 'local', 'driver' => 'local'],
|
||||
(object) ['disk' => 's3', 'driver' => 's3'],
|
||||
],
|
||||
allowedExtensions: ['pdf']
|
||||
);
|
||||
$localDisk = new DiskName('local', 'local');
|
||||
$s3Disk = new DiskName('s3', 's3');
|
||||
|
||||
// THEN
|
||||
expect($config->getRealDriver($localDisk))->toBe('local')
|
||||
->and($config->isS3Driver($localDisk))->toBeFalse()
|
||||
->and($config->getRealDriver($s3Disk))->toBe('s3')
|
||||
->and($config->isS3Driver($s3Disk))->toBeTrue();
|
||||
});
|
||||
|
||||
it('resolves the underlying driver of a scoped disk at boot', function (): void {
|
||||
// GIVEN
|
||||
config()->set('filesystems.disks.s3_parent', ['driver' => 's3']);
|
||||
config()->set('filesystems.disks.media', [
|
||||
'driver' => 'scoped',
|
||||
'disk' => 's3_parent',
|
||||
'prefix' => 'media',
|
||||
]);
|
||||
Storage::fake('media');
|
||||
|
||||
// WHEN
|
||||
$config = new StorageServiceConfig(
|
||||
defaultDisk: 'media',
|
||||
eligibleDisks: ['media'],
|
||||
laravelExistingDisks: [(object) ['disk' => 'media', 'driver' => 'scoped']],
|
||||
allowedExtensions: ['pdf']
|
||||
);
|
||||
$media = new DiskName('media', 'scoped');
|
||||
|
||||
// THEN
|
||||
expect($config->getRealDriver($media))->toBe('s3')
|
||||
->and($config->isS3Driver($media))->toBeTrue();
|
||||
});
|
||||
|
||||
it('fails at boot when a scoped disk is misconfigured', function (): void {
|
||||
// GIVEN
|
||||
config()->set('filesystems.disks.broken', ['driver' => 'scoped']);
|
||||
Storage::fake('broken');
|
||||
|
||||
// WHEN
|
||||
$action = fn () => new StorageServiceConfig(
|
||||
defaultDisk: 'local',
|
||||
eligibleDisks: ['local', 'broken'],
|
||||
laravelExistingDisks: [
|
||||
(object) ['disk' => 'local', 'driver' => 'local'],
|
||||
(object) ['disk' => 'broken', 'driver' => 'scoped'],
|
||||
],
|
||||
allowedExtensions: ['pdf']
|
||||
);
|
||||
|
||||
// THEN
|
||||
expect($action)->toThrow(
|
||||
UnexpectedStorageException::class,
|
||||
"Scoped disk 'broken' is missing a valid parent 'disk' configuration."
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when asking the driver of a disk that is neither default nor eligible', function (): void {
|
||||
// GIVEN
|
||||
$config = new StorageServiceConfig(
|
||||
defaultDisk: 'local',
|
||||
eligibleDisks: ['local'],
|
||||
laravelExistingDisks: [(object) ['disk' => 'local', 'driver' => 'local']],
|
||||
allowedExtensions: ['pdf']
|
||||
);
|
||||
Storage::fake('other');
|
||||
$other = new DiskName('other', 'local');
|
||||
|
||||
// WHEN
|
||||
$action = fn () => $config->getRealDriver($other);
|
||||
|
||||
// THEN
|
||||
expect($action)->toThrow(
|
||||
UnexpectedStorageException::class,
|
||||
'Disk other is neither the default nor an eligible disk'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ use Uwil\Storage\Lib\ValueObjects\SanitizedFile;
|
|||
covers(FileSanitizerImpl::class);
|
||||
|
||||
/**
|
||||
* @param array<string> $allowedExtensions
|
||||
* @param array<string> $allowedExtensions
|
||||
*/
|
||||
function createConfig(array $allowedExtensions = ['jpg', 'png', 'pdf', 'txt']): StorageServiceConfig
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,8 +4,11 @@ declare(strict_types=1);
|
|||
|
||||
namespace Uwil\Storage\Tests\Unit\Lib;
|
||||
|
||||
use Illuminate\Contracts\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Uwil\Storage\Config\StorageServiceConfig;
|
||||
use Uwil\Storage\Exceptions\UnexpectedStorageException;
|
||||
use Uwil\Storage\Lib\PathGenerators\DateHashPathGenerator;
|
||||
use Uwil\Storage\Lib\PathGenerators\UuidPathGenerator;
|
||||
|
|
@ -14,14 +17,36 @@ use Uwil\Storage\Lib\ValueObjects\DiskName;
|
|||
use Uwil\Storage\Lib\ValueObjects\FileStream;
|
||||
use Uwil\Storage\Lib\ValueObjects\SanitizedFile;
|
||||
use Uwil\Storage\Lib\ValueObjects\StorageFilePath;
|
||||
use Illuminate\Contracts\Filesystem\Filesystem;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
covers(StorageGatewayImpl::class);
|
||||
|
||||
function createGateway(): StorageGatewayImpl
|
||||
/**
|
||||
* @param array<string, string> $extraDisks eligible disks to declare in addition to `local` (name => driver)
|
||||
*/
|
||||
function createGatewayConfig(array $extraDisks = []): StorageServiceConfig
|
||||
{
|
||||
$existing = [(object) ['disk' => 'local', 'driver' => 'local']];
|
||||
|
||||
foreach ($extraDisks as $name => $driver) {
|
||||
Storage::fake($name);
|
||||
$existing[] = (object) ['disk' => $name, 'driver' => $driver];
|
||||
}
|
||||
|
||||
return new StorageServiceConfig(
|
||||
defaultDisk: 'local',
|
||||
eligibleDisks: ['local', ...array_keys($extraDisks)],
|
||||
laravelExistingDisks: $existing,
|
||||
allowedExtensions: ['txt']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $extraDisks
|
||||
*/
|
||||
function createGateway(array $extraDisks = []): StorageGatewayImpl
|
||||
{
|
||||
return new StorageGatewayImpl(
|
||||
config: createGatewayConfig($extraDisks),
|
||||
logger: new NullLogger(),
|
||||
datePathGenerator: new DateHashPathGenerator(),
|
||||
uuidPathGenerator: new UuidPathGenerator()
|
||||
|
|
@ -52,7 +77,7 @@ describe('StorageGatewayImpl storeFile', function (): void {
|
|||
it('stores a file onto an s3 disk using UuidPathGenerator', function (): void {
|
||||
// GIVEN
|
||||
Storage::fake('s3');
|
||||
$gateway = createGateway();
|
||||
$gateway = createGateway(['s3' => 's3']);
|
||||
$disk = new DiskName('s3', 's3');
|
||||
$sanitizedFile = new SanitizedFile(
|
||||
originalName: 'picture.png',
|
||||
|
|
@ -74,9 +99,9 @@ describe('StorageGatewayImpl storeFile', function (): void {
|
|||
$mockStorage = \Mockery::mock(Filesystem::class);
|
||||
$mockStorage->shouldReceive('put')->andReturn(false);
|
||||
|
||||
$gateway = createGateway();
|
||||
Storage::shouldReceive('disk')->with('local')->andReturn($mockStorage);
|
||||
|
||||
$gateway = createGateway();
|
||||
$disk = new DiskName('local', 'local');
|
||||
$sanitizedFile = new SanitizedFile(
|
||||
originalName: 'test.txt',
|
||||
|
|
@ -96,9 +121,9 @@ describe('StorageGatewayImpl storeFile', function (): void {
|
|||
$mockStorage = \Mockery::mock(Filesystem::class);
|
||||
$mockStorage->shouldReceive('put')->andThrow(new \RuntimeException('Network drop'));
|
||||
|
||||
$gateway = createGateway();
|
||||
Storage::shouldReceive('disk')->with('local')->andReturn($mockStorage);
|
||||
|
||||
$gateway = createGateway();
|
||||
$disk = new DiskName('local', 'local');
|
||||
$sanitizedFile = new SanitizedFile(
|
||||
originalName: 'test.txt',
|
||||
|
|
@ -119,9 +144,9 @@ describe('StorageGatewayImpl storeFile', function (): void {
|
|||
$mockStorage = \Mockery::mock(Filesystem::class);
|
||||
$mockStorage->shouldReceive('put')->andThrow($directException);
|
||||
|
||||
$gateway = createGateway();
|
||||
Storage::shouldReceive('disk')->with('local')->andReturn($mockStorage);
|
||||
|
||||
$gateway = createGateway();
|
||||
$disk = new DiskName('local', 'local');
|
||||
$sanitizedFile = new SanitizedFile(
|
||||
originalName: 'test.txt',
|
||||
|
|
@ -143,9 +168,9 @@ describe('StorageGatewayImpl storeFile', function (): void {
|
|||
config()->set("filesystems.disks.{$driver}", ['driver' => $driver]);
|
||||
$mockStorage = \Mockery::mock(Filesystem::class);
|
||||
$mockStorage->shouldReceive('put')->andReturn(true);
|
||||
$gateway = createGateway([$driver => $driver]);
|
||||
Storage::shouldReceive('disk')->with($driver)->andReturn($mockStorage);
|
||||
|
||||
$gateway = createGateway();
|
||||
$disk = new DiskName($driver, $driver);
|
||||
$sanitizedFile = new SanitizedFile(
|
||||
originalName: 'test.txt',
|
||||
|
|
@ -163,6 +188,83 @@ describe('StorageGatewayImpl storeFile', function (): void {
|
|||
'ftp driver' => 'ftp',
|
||||
'sftp driver' => 'sftp',
|
||||
]);
|
||||
|
||||
it('stores a file onto a scoped disk resolving underlying s3 driver', function (): void {
|
||||
// GIVEN
|
||||
config()->set('filesystems.disks.s3', ['driver' => 's3']);
|
||||
config()->set('filesystems.disks.scoped_s3', [
|
||||
'driver' => 'scoped',
|
||||
'disk' => 's3',
|
||||
'prefix' => 'media',
|
||||
]);
|
||||
$mockStorage = \Mockery::mock(Filesystem::class);
|
||||
$mockStorage->shouldReceive('put')->andReturn(true);
|
||||
$gateway = createGateway(['scoped_s3' => 'scoped']);
|
||||
Storage::shouldReceive('disk')->with('scoped_s3')->andReturn($mockStorage);
|
||||
|
||||
$disk = new DiskName('scoped_s3', 'scoped');
|
||||
$sanitizedFile = new SanitizedFile(
|
||||
originalName: 'photo.jpg',
|
||||
sanitizedName: 'photo.jpg',
|
||||
contents: FileStream::fromString('image bytes')
|
||||
);
|
||||
|
||||
// WHEN
|
||||
$path = $gateway->storeFile($disk, $sanitizedFile);
|
||||
|
||||
// THEN
|
||||
expect($path)->toBeInstanceOf(StorageFilePath::class)
|
||||
->and($path->value)->toMatch('#^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.jpg$#');
|
||||
});
|
||||
|
||||
it('stores a file onto a scoped disk resolving underlying local driver', function (): void {
|
||||
// GIVEN
|
||||
config()->set('filesystems.disks.local_parent', ['driver' => 'local']);
|
||||
config()->set('filesystems.disks.scoped_local', [
|
||||
'driver' => 'scoped',
|
||||
'disk' => 'local_parent',
|
||||
'prefix' => 'uploads',
|
||||
]);
|
||||
$mockStorage = \Mockery::mock(Filesystem::class);
|
||||
$mockStorage->shouldReceive('put')->andReturn(true);
|
||||
$gateway = createGateway(['scoped_local' => 'scoped']);
|
||||
Storage::shouldReceive('disk')->with('scoped_local')->andReturn($mockStorage);
|
||||
|
||||
$disk = new DiskName('scoped_local', 'scoped');
|
||||
$sanitizedFile = new SanitizedFile(
|
||||
originalName: 'doc.pdf',
|
||||
sanitizedName: 'doc.pdf',
|
||||
contents: FileStream::fromString('pdf data')
|
||||
);
|
||||
|
||||
// WHEN
|
||||
$path = $gateway->storeFile($disk, $sanitizedFile);
|
||||
|
||||
// THEN
|
||||
expect($path)->toBeInstanceOf(StorageFilePath::class)
|
||||
->and($path->value)->toStartWith(gmdate('Y') . '/');
|
||||
});
|
||||
|
||||
it('rejects a disk that is neither default nor eligible', function (): void {
|
||||
// GIVEN
|
||||
$gateway = createGateway();
|
||||
Storage::fake('unknown');
|
||||
$disk = new DiskName('unknown', 'local');
|
||||
$sanitizedFile = new SanitizedFile(
|
||||
originalName: 'doc.pdf',
|
||||
sanitizedName: 'doc.pdf',
|
||||
contents: FileStream::fromString('data')
|
||||
);
|
||||
|
||||
// WHEN
|
||||
$action = fn () => $gateway->storeFile($disk, $sanitizedFile);
|
||||
|
||||
// THEN
|
||||
expect($action)->toThrow(
|
||||
UnexpectedStorageException::class,
|
||||
'Disk unknown is neither the default nor an eligible disk'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('StorageGatewayImpl getFile', function (): void {
|
||||
|
|
@ -320,6 +422,7 @@ describe('StorageGatewayImpl logging', function (): void {
|
|||
$logger->shouldReceive('info')->with(\Mockery::pattern('/^File deletion result for disk:.*: success$/'))->once();
|
||||
|
||||
$gateway = new StorageGatewayImpl(
|
||||
config: createGatewayConfig(),
|
||||
logger: $logger,
|
||||
datePathGenerator: new DateHashPathGenerator(),
|
||||
uuidPathGenerator: new UuidPathGenerator()
|
||||
|
|
@ -346,6 +449,7 @@ describe('StorageGatewayImpl logging', function (): void {
|
|||
$logger->shouldReceive('debug')->with('File not found on disk: local, path: not_found.txt')->once();
|
||||
|
||||
$gateway = new StorageGatewayImpl(
|
||||
config: createGatewayConfig(),
|
||||
logger: $logger,
|
||||
datePathGenerator: new DateHashPathGenerator(),
|
||||
uuidPathGenerator: new UuidPathGenerator()
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||
namespace Uwil\Storage\Tests\Unit\Lib;
|
||||
|
||||
use Uwil\Storage\Config\StorageServiceConfig;
|
||||
use Uwil\Storage\Exceptions\UnexpectedStorageException;
|
||||
use Uwil\Storage\Lib\Utils;
|
||||
|
||||
covers(Utils::class);
|
||||
|
|
@ -99,3 +100,108 @@ describe('Utils isExtensionAllowed', function (): void {
|
|||
expect($fallback)->toBeTrue();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Utils resolveDriver', function (): void {
|
||||
it('returns direct driver for non-scoped disk', function (): void {
|
||||
// GIVEN
|
||||
config()->set('filesystems.disks.my_s3', ['driver' => 's3']);
|
||||
|
||||
// WHEN
|
||||
$driver = Utils::resolveDriver('my_s3');
|
||||
|
||||
// THEN
|
||||
expect($driver)->toBe('s3');
|
||||
});
|
||||
|
||||
it('resolves parent driver for scoped disk from string', function (): void {
|
||||
// GIVEN
|
||||
config()->set('filesystems.disks.parent_local', ['driver' => 'local']);
|
||||
config()->set('filesystems.disks.scoped_disk', [
|
||||
'driver' => 'scoped',
|
||||
'disk' => 'parent_local',
|
||||
]);
|
||||
|
||||
// WHEN
|
||||
$driver = Utils::resolveDriver('scoped_disk');
|
||||
|
||||
// THEN
|
||||
expect($driver)->toBe('local');
|
||||
});
|
||||
|
||||
it('resolves parent driver for scoped disk when driver is given explicitly', function (): void {
|
||||
// GIVEN
|
||||
config()->set('filesystems.disks.parent_s3', ['driver' => 's3']);
|
||||
config()->set('filesystems.disks.scoped_s3', [
|
||||
'driver' => 'scoped',
|
||||
'disk' => 'parent_s3',
|
||||
]);
|
||||
|
||||
// WHEN
|
||||
$driver = Utils::resolveDriver('scoped_s3', 'scoped');
|
||||
|
||||
// THEN
|
||||
expect($driver)->toBe('s3');
|
||||
});
|
||||
|
||||
it('returns explicit non-scoped driver without reading config', function (): void {
|
||||
// WHEN
|
||||
$driver = Utils::resolveDriver('not_configured', 'sftp');
|
||||
|
||||
// THEN
|
||||
expect($driver)->toBe('sftp');
|
||||
});
|
||||
|
||||
it('throws exception when parent disk has no driver configured', function (): void {
|
||||
// GIVEN
|
||||
config()->set('filesystems.disks.scoped_orphan', [
|
||||
'driver' => 'scoped',
|
||||
'disk' => 'missing_parent',
|
||||
]);
|
||||
|
||||
// WHEN
|
||||
$action = fn () => Utils::resolveDriver('scoped_orphan');
|
||||
|
||||
// THEN
|
||||
expect($action)->toThrow(
|
||||
UnexpectedStorageException::class,
|
||||
"Parent disk 'missing_parent' of scoped disk 'scoped_orphan' has no valid driver configured."
|
||||
);
|
||||
});
|
||||
|
||||
it('throws exception when scoped disk has missing parent disk', function (): void {
|
||||
// GIVEN
|
||||
config()->set('filesystems.disks.scoped_invalid', [
|
||||
'driver' => 'scoped',
|
||||
]);
|
||||
|
||||
// WHEN
|
||||
$action = fn () => Utils::resolveDriver('scoped_invalid');
|
||||
|
||||
// THEN
|
||||
expect($action)->toThrow(
|
||||
UnexpectedStorageException::class,
|
||||
"Scoped disk 'scoped_invalid' is missing a valid parent 'disk' configuration."
|
||||
);
|
||||
});
|
||||
|
||||
it('throws exception when scoped disk has circular reference', function (): void {
|
||||
// GIVEN
|
||||
config()->set('filesystems.disks.scoped_x', [
|
||||
'driver' => 'scoped',
|
||||
'disk' => 'scoped_y',
|
||||
]);
|
||||
config()->set('filesystems.disks.scoped_y', [
|
||||
'driver' => 'scoped',
|
||||
'disk' => 'scoped_x',
|
||||
]);
|
||||
|
||||
// WHEN
|
||||
$action = fn () => Utils::resolveDriver('scoped_x');
|
||||
|
||||
// THEN
|
||||
expect($action)->toThrow(
|
||||
UnexpectedStorageException::class,
|
||||
"Circular reference detected in scoped disk 'scoped_y'."
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -22,10 +22,11 @@ describe('DiskName instantiation', function (): void {
|
|||
->and($vo->value)->toBe('local')
|
||||
->and($vo->driver)->toBe($driver);
|
||||
})->with([
|
||||
'local driver' => 'local',
|
||||
'ftp driver' => 'ftp',
|
||||
'sftp driver' => 'sftp',
|
||||
's3 driver' => 's3',
|
||||
'local driver' => 'local',
|
||||
'ftp driver' => 'ftp',
|
||||
'sftp driver' => 'sftp',
|
||||
's3 driver' => 's3',
|
||||
'scoped driver' => 'scoped',
|
||||
]);
|
||||
|
||||
it('can instantiate with a BackedEnum value', function (): void {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue