ldc-file-storage-service/tests/Unit/Config/StorageServiceConfigTest.php
Jérémy Munsch 8dc7434133
feat(StorageServiceConfig): add prefix handling and validation
- Introduced prefix property in StorageServiceConfig.
- Implemented prefix validation and trimming logic.
- Updated path generators to utilize the configured prefix.
- Added tests for prefix validation and retrieval.
2026-11-04 08:11:57 +01:00

417 lines
14 KiB
PHP

<?php
declare(strict_types=1);
namespace Uwil\Storage\Tests\Unit\Config;
use Uwil\Storage\Config\StorageServiceConfig;
use Uwil\Storage\Exceptions\UnexpectedStorageException;
use Uwil\Storage\Lib\ValueObjects\DiskName;
covers(StorageServiceConfig::class);
describe('StorageServiceConfig instantiation', function (): void {
it('can instantiate with valid configuration', function (): void {
// GIVEN
$defaultDisk = 'local';
$eligibleDisks = ['local'];
$laravelExistingDisks = [(object) ['disk' => 'local', 'driver' => 'local']];
$allowedExtensions = ['jpg', 'png', 'pdf'];
// WHEN
$config = new StorageServiceConfig(
defaultDisk: $defaultDisk,
eligibleDisks: $eligibleDisks,
laravelExistingDisks: $laravelExistingDisks,
allowedExtensions: $allowedExtensions
);
// THEN
expect($config)->toBeInstanceOf(StorageServiceConfig::class)
->and($config->getDefaultDisk())->toBeInstanceOf(DiskName::class)
->and($config->getDefaultDisk()->value)->toBe('local')
->and($config->getEligibleDisks())->toHaveCount(1)
->and($config->getEligibleDisks()[0]->value)->toBe('local')
->and($config->getAllowedExtensions())->toBe(['jpg', 'png', 'pdf']);
});
});
describe('StorageServiceConfig disk validation', function (): void {
it('throws exception if default disk does not exist', function (): void {
// GIVEN
$defaultDisk = 'non_existent_disk';
$eligibleDisks = ['local'];
$laravelExistingDisks = [(object) ['disk' => 'local', 'driver' => 'local']];
$allowedExtensions = ['pdf'];
// WHEN
$action = fn () => new StorageServiceConfig(
defaultDisk: $defaultDisk,
eligibleDisks: $eligibleDisks,
laravelExistingDisks: $laravelExistingDisks,
allowedExtensions: $allowedExtensions
);
// THEN
expect($action)->toThrow(
UnexpectedStorageException::class,
'Default disk does not exist'
);
});
it('throws exception if an eligible disk does not exist', function (): void {
// GIVEN
$defaultDisk = 'local';
$eligibleDisks = ['local', 'unknown_disk'];
$laravelExistingDisks = [(object) ['disk' => 'local', 'driver' => 'local']];
$allowedExtensions = ['pdf'];
// WHEN
$action = fn () => new StorageServiceConfig(
defaultDisk: $defaultDisk,
eligibleDisks: $eligibleDisks,
laravelExistingDisks: $laravelExistingDisks,
allowedExtensions: $allowedExtensions
);
// THEN
expect($action)->toThrow(
UnexpectedStorageException::class,
'Eligible disk unknown_disk does not exist'
);
});
it('throws exception if a Laravel existing disk does not exist', function (): void {
// GIVEN
$defaultDisk = 'local';
$eligibleDisks = ['local'];
$laravelExistingDisks = [
(object) ['disk' => 'local', 'driver' => 'local'],
(object) ['disk' => 'phantom_disk', 'driver' => 'local'],
];
$allowedExtensions = ['pdf'];
// WHEN
$action = fn () => new StorageServiceConfig(
defaultDisk: $defaultDisk,
eligibleDisks: $eligibleDisks,
laravelExistingDisks: $laravelExistingDisks,
allowedExtensions: $allowedExtensions
);
// THEN
expect($action)->toThrow(
UnexpectedStorageException::class,
'Laravel existing disk phantom_disk does not exist'
);
});
it('throws exception if laravelExistingDisks is empty', function (): void {
// GIVEN
$defaultDisk = 'local';
$eligibleDisks = ['local'];
$laravelExistingDisks = [];
$allowedExtensions = ['pdf'];
// WHEN
$action = fn () => new StorageServiceConfig(
defaultDisk: $defaultDisk,
eligibleDisks: $eligibleDisks,
laravelExistingDisks: $laravelExistingDisks,
allowedExtensions: $allowedExtensions
);
// THEN
expect($action)->toThrow(
UnexpectedStorageException::class,
'Laravel existing disks cannot be empty'
);
});
it('throws exception if eligibleDisks is empty', function (): void {
// GIVEN
$defaultDisk = 'local';
$eligibleDisks = [];
$laravelExistingDisks = [(object) ['disk' => 'local', 'driver' => 'local']];
$allowedExtensions = ['pdf'];
// WHEN
$action = fn () => new StorageServiceConfig(
defaultDisk: $defaultDisk,
eligibleDisks: $eligibleDisks,
laravelExistingDisks: $laravelExistingDisks,
allowedExtensions: $allowedExtensions
);
// THEN
expect($action)->toThrow(
UnexpectedStorageException::class,
'Eligible disks cannot be empty'
);
});
it('throws exception if no eligible disks exist in Laravel existing disks', function (): void {
// GIVEN
$defaultDisk = 'local';
$eligibleDisks = ['public'];
$laravelExistingDisks = [(object) ['disk' => 'local', 'driver' => 'local']];
$allowedExtensions = ['pdf'];
// WHEN
$action = fn () => new StorageServiceConfig(
defaultDisk: $defaultDisk,
eligibleDisks: $eligibleDisks,
laravelExistingDisks: $laravelExistingDisks,
allowedExtensions: $allowedExtensions
);
// THEN
expect($action)->toThrow(
UnexpectedStorageException::class,
'No eligible disks exist in Laravel existing disks'
);
});
it('throws exception if default disk does not exist in Laravel existing disks', function (): void {
// GIVEN
$defaultDisk = 'local';
$eligibleDisks = ['public'];
$laravelExistingDisks = [(object) ['disk' => 'public', 'driver' => 'local']];
$allowedExtensions = ['pdf'];
// WHEN
$action = fn () => new StorageServiceConfig(
defaultDisk: $defaultDisk,
eligibleDisks: $eligibleDisks,
laravelExistingDisks: $laravelExistingDisks,
allowedExtensions: $allowedExtensions
);
// THEN
expect($action)->toThrow(
UnexpectedStorageException::class,
'Default disk does not exist in Laravel existing disks'
);
});
});
describe('StorageServiceConfig allowed extensions validation', function (): void {
it('throws exception if allowedExtensions is empty', function (): void {
// GIVEN
$defaultDisk = 'local';
$eligibleDisks = ['local'];
$laravelExistingDisks = [(object) ['disk' => 'local', 'driver' => 'local']];
$allowedExtensions = [];
// WHEN
$action = fn () => new StorageServiceConfig(
defaultDisk: $defaultDisk,
eligibleDisks: $eligibleDisks,
laravelExistingDisks: $laravelExistingDisks,
allowedExtensions: $allowedExtensions
);
// THEN
expect($action)->toThrow(
UnexpectedStorageException::class,
'Allowed extensions cannot be empty'
);
});
it('throws exception if an allowed extension is empty or invalid', function (string $invalidExtension): void {
// GIVEN
$defaultDisk = 'local';
$eligibleDisks = ['local'];
$laravelExistingDisks = [(object) ['disk' => 'local', 'driver' => 'local']];
$allowedExtensions = ['jpg', $invalidExtension];
// WHEN
$action = fn () => new StorageServiceConfig(
defaultDisk: $defaultDisk,
eligibleDisks: $eligibleDisks,
laravelExistingDisks: $laravelExistingDisks,
allowedExtensions: $allowedExtensions
);
// THEN
expect($action)->toThrow(
UnexpectedStorageException::class,
"Allowed extension {$invalidExtension} is not valid"
);
})->with([
'empty string' => '',
'whitespace' => ' ',
]);
it('throws exception if an allowed extension is forbidden (svg or xml)', function (string $forbidden): void {
// GIVEN
$defaultDisk = 'local';
$eligibleDisks = ['local'];
$laravelExistingDisks = [(object) ['disk' => 'local', 'driver' => 'local']];
$allowedExtensions = ['jpg', $forbidden];
// WHEN
$action = fn () => new StorageServiceConfig(
defaultDisk: $defaultDisk,
eligibleDisks: $eligibleDisks,
laravelExistingDisks: $laravelExistingDisks,
allowedExtensions: $allowedExtensions
);
// THEN
expect($action)->toThrow(
UnexpectedStorageException::class,
"Extension '{$forbidden}' is forbidden: XML/SVG sanitization is currently not supported."
);
})->with([
'svg' => 'svg',
'svg with .' => '.svg',
'SVG upper' => 'SVG',
'xml' => 'xml',
'xml with .' => '.xml',
'XML upper' => 'XML',
]);
});
describe('StorageServiceConfig isDiskEligible', function (): void {
it('returns true when checking eligible disk with string or DiskName', function (): void {
// GIVEN
$config = new StorageServiceConfig(
defaultDisk: 'local',
eligibleDisks: ['local'],
laravelExistingDisks: [(object) ['disk' => 'local', 'driver' => 'local']],
allowedExtensions: ['pdf']
);
$diskNameVo = new DiskName('local', 'local');
// WHEN
$eligibleFromString = $config->isDiskEligible('local');
$eligibleFromVo = $config->isDiskEligible($diskNameVo);
// THEN
expect($eligibleFromString)->toBeTrue()
->and($eligibleFromVo)->toBeTrue();
});
it('returns false when checking non-eligible disk', function (): void {
// GIVEN
$config = new StorageServiceConfig(
defaultDisk: 'local',
eligibleDisks: ['local'],
laravelExistingDisks: [(object) ['disk' => 'local', 'driver' => 'local']],
allowedExtensions: ['pdf']
);
// WHEN
$isEligible = $config->isDiskEligible('public');
// THEN
expect($isEligible)->toBeFalse();
});
});
describe('StorageServiceConfig isExtensionAllowed', function (): void {
it('returns true for allowed extensions regardless of case or leading dot', function (string $extension): void {
// GIVEN
$config = new StorageServiceConfig(
defaultDisk: 'local',
eligibleDisks: ['local'],
laravelExistingDisks: [(object) ['disk' => 'local', 'driver' => 'local']],
allowedExtensions: ['jpg', 'png', 'pdf']
);
// WHEN
$isAllowed = $config->isExtensionAllowed($extension);
// THEN
expect($isAllowed)->toBeTrue();
})->with([
'exact' => 'jpg',
'with dot' => '.jpg',
'uppercase' => 'PNG',
'upper dot' => '.PDF',
]);
it('returns false for non-allowed extensions', function (string $extension): void {
// GIVEN
$config = new StorageServiceConfig(
defaultDisk: 'local',
eligibleDisks: ['local'],
laravelExistingDisks: [(object) ['disk' => 'local', 'driver' => 'local']],
allowedExtensions: ['jpg', 'png', 'pdf']
);
// WHEN
$isAllowed = $config->isExtensionAllowed($extension);
// THEN
expect($isAllowed)->toBeFalse();
})->with([
'exe' => 'exe',
'zip' => 'zip',
'svg' => 'svg',
'xml' => 'xml',
'.svg' => '.svg',
]);
});
describe('StorageServiceConfig prefix validation and retrieval', function (): void {
it('returns null when prefix is omitted or null', function (?string $prefix): void {
// GIVEN
$config = new StorageServiceConfig(
defaultDisk: 'local',
eligibleDisks: ['local'],
laravelExistingDisks: [(object) ['disk' => 'local', 'driver' => 'local']],
allowedExtensions: ['pdf'],
prefix: $prefix
);
// THEN
expect($config->getPrefix())->toBeNull();
})->with([
'null' => null,
'empty' => '',
'spaces' => ' ',
'slashes' => '///',
]);
it('trims leading and trailing slashes and spaces from prefix', function (string $input, string $expected): void {
// GIVEN
$config = new StorageServiceConfig(
defaultDisk: 'local',
eligibleDisks: ['local'],
laravelExistingDisks: [(object) ['disk' => 'local', 'driver' => 'local']],
allowedExtensions: ['pdf'],
prefix: $input
);
// THEN
expect($config->getPrefix())->toBe($expected);
})->with([
'simple' => ['media', 'media'],
'leading slash' => ['/media', 'media'],
'trailing slash' => ['media/', 'media'],
'both slashes' => ['/media/', 'media'],
'nested' => ['/uploads/images/', 'uploads/images'],
'with spaces' => [' media ', 'media'],
]);
it('throws exception if prefix contains invalid sequences', function (string $invalidPrefix): void {
// GIVEN
$action = fn () => new StorageServiceConfig(
defaultDisk: 'local',
eligibleDisks: ['local'],
laravelExistingDisks: [(object) ['disk' => 'local', 'driver' => 'local']],
allowedExtensions: ['pdf'],
prefix: $invalidPrefix
);
// THEN
expect($action)->toThrow(UnexpectedStorageException::class);
})->with([
'dot dot' => '../media',
'nested dot dot' => 'media/../sub',
'backslash' => 'media\\sub',
'double slash' => 'media//sub',
]);
});