406 lines
14 KiB
PHP
406 lines
14 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
namespace Uwil\Storage\Tests\Unit\Lib;
|
||
|
|
|
||
|
|
use Illuminate\Http\UploadedFile;
|
||
|
|
use Uwil\Storage\Config\StorageServiceConfig;
|
||
|
|
use Uwil\Storage\Contracts\FileSanitizer;
|
||
|
|
use Uwil\Storage\Contracts\StorageGateway;
|
||
|
|
use Uwil\Storage\Exceptions\UnexpectedStorageException;
|
||
|
|
use Uwil\Storage\Lib\StorageServiceImpl;
|
||
|
|
use Uwil\Storage\Lib\ValueObjects\DiskName;
|
||
|
|
use Uwil\Storage\Lib\ValueObjects\FileStream;
|
||
|
|
use Uwil\Storage\Lib\ValueObjects\SanitizedFile;
|
||
|
|
use Uwil\Storage\Lib\ValueObjects\StorageFilePath;
|
||
|
|
|
||
|
|
covers(StorageServiceImpl::class);
|
||
|
|
|
||
|
|
function createServiceConfig(): StorageServiceConfig
|
||
|
|
{
|
||
|
|
config()->set('filesystems.disks.backup', [
|
||
|
|
'driver' => 'local',
|
||
|
|
'root' => sys_get_temp_dir(),
|
||
|
|
]);
|
||
|
|
|
||
|
|
return new StorageServiceConfig(
|
||
|
|
defaultDisk: 'local',
|
||
|
|
eligibleDisks: ['local', 'backup'],
|
||
|
|
laravelExistingDisks: [
|
||
|
|
(object) ['disk' => 'local', 'driver' => 'local'],
|
||
|
|
(object) ['disk' => 'backup', 'driver' => 'local'],
|
||
|
|
],
|
||
|
|
allowedExtensions: ['pdf', 'txt', 'png']
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
describe('StorageServiceImpl file validation', function (): void {
|
||
|
|
it('delegates fileIsValid to FileSanitizer', function (): void {
|
||
|
|
// GIVEN
|
||
|
|
$config = createServiceConfig();
|
||
|
|
$sanitizer = \Mockery::mock(FileSanitizer::class);
|
||
|
|
$gateway = \Mockery::mock(StorageGateway::class);
|
||
|
|
$file = UploadedFile::fake()->create('dummy.txt');
|
||
|
|
|
||
|
|
$sanitizer->shouldReceive('fileIsValid')
|
||
|
|
->once()
|
||
|
|
->with($file)
|
||
|
|
->andReturn(true);
|
||
|
|
|
||
|
|
$service = new StorageServiceImpl($config, $sanitizer, $gateway);
|
||
|
|
|
||
|
|
// WHEN
|
||
|
|
$result = $service->fileIsValid($file);
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
expect($result)->toBeTrue();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('StorageServiceImpl file existence', function (): void {
|
||
|
|
it('checks file exists on default disk', function (): void {
|
||
|
|
// GIVEN
|
||
|
|
$config = createServiceConfig();
|
||
|
|
$sanitizer = \Mockery::mock(FileSanitizer::class);
|
||
|
|
$gateway = \Mockery::mock(StorageGateway::class);
|
||
|
|
$filePath = new StorageFilePath('documents/report.pdf');
|
||
|
|
|
||
|
|
$gateway->shouldReceive('fileExists')
|
||
|
|
->once()
|
||
|
|
->with(\Mockery::on(fn (DiskName $d) => $d->value === 'local'), $filePath)
|
||
|
|
->andReturn(true);
|
||
|
|
|
||
|
|
$service = new StorageServiceImpl($config, $sanitizer, $gateway);
|
||
|
|
|
||
|
|
// WHEN
|
||
|
|
$exists = $service->fileExists($filePath);
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
expect($exists)->toBeTrue();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('checks file exists on specified eligible disk', function (): void {
|
||
|
|
// GIVEN
|
||
|
|
$config = createServiceConfig();
|
||
|
|
$sanitizer = \Mockery::mock(FileSanitizer::class);
|
||
|
|
$gateway = \Mockery::mock(StorageGateway::class);
|
||
|
|
$disk = new DiskName('backup', 'local');
|
||
|
|
$filePath = new StorageFilePath('documents/report.pdf');
|
||
|
|
|
||
|
|
$gateway->shouldReceive('fileExists')
|
||
|
|
->once()
|
||
|
|
->with($disk, $filePath)
|
||
|
|
->andReturn(true);
|
||
|
|
|
||
|
|
$service = new StorageServiceImpl($config, $sanitizer, $gateway);
|
||
|
|
|
||
|
|
// WHEN
|
||
|
|
$exists = $service->fileExistsOnDisk($filePath, $disk);
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
expect($exists)->toBeTrue();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('throws exception if specified disk is not eligible during existence check', function (): void {
|
||
|
|
// GIVEN
|
||
|
|
$config = createServiceConfig();
|
||
|
|
$sanitizer = \Mockery::mock(FileSanitizer::class);
|
||
|
|
$gateway = \Mockery::mock(StorageGateway::class);
|
||
|
|
$disk = new DiskName('public', 'local');
|
||
|
|
$filePath = new StorageFilePath('documents/report.pdf');
|
||
|
|
$service = new StorageServiceImpl($config, $sanitizer, $gateway);
|
||
|
|
|
||
|
|
// WHEN
|
||
|
|
$action = fn () => $service->fileExistsOnDisk($filePath, $disk);
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
expect($action)->toThrow(
|
||
|
|
UnexpectedStorageException::class,
|
||
|
|
"Disk 'public' is not eligible for storage operations."
|
||
|
|
);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('StorageServiceImpl getFile', function (): void {
|
||
|
|
it('retrieves file stream from default disk', function (): void {
|
||
|
|
// GIVEN
|
||
|
|
$config = createServiceConfig();
|
||
|
|
$sanitizer = \Mockery::mock(FileSanitizer::class);
|
||
|
|
$gateway = \Mockery::mock(StorageGateway::class);
|
||
|
|
$filePath = new StorageFilePath('archive/data.txt');
|
||
|
|
$expectedStream = FileStream::fromString('file data');
|
||
|
|
|
||
|
|
$gateway->shouldReceive('getFile')
|
||
|
|
->once()
|
||
|
|
->with(\Mockery::on(fn (DiskName $d) => $d->value === 'local'), $filePath)
|
||
|
|
->andReturn($expectedStream);
|
||
|
|
|
||
|
|
$service = new StorageServiceImpl($config, $sanitizer, $gateway);
|
||
|
|
|
||
|
|
// WHEN
|
||
|
|
$result = $service->getFile($filePath);
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
expect($result)->toBe($expectedStream);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('StorageServiceImpl storing files', function (): void {
|
||
|
|
it('stores sanitized file onto default disk', function (): void {
|
||
|
|
// GIVEN
|
||
|
|
$config = createServiceConfig();
|
||
|
|
$sanitizer = \Mockery::mock(FileSanitizer::class);
|
||
|
|
$gateway = \Mockery::mock(StorageGateway::class);
|
||
|
|
$sanitizedFile = new SanitizedFile('doc.pdf', 'doc.pdf', FileStream::fromString('bytes'));
|
||
|
|
$expectedPath = new StorageFilePath('2026/11/03/aa/doc-12345678.pdf');
|
||
|
|
|
||
|
|
$gateway->shouldReceive('storeFile')
|
||
|
|
->once()
|
||
|
|
->with(\Mockery::on(fn (DiskName $d) => $d->value === 'local'), $sanitizedFile)
|
||
|
|
->andReturn($expectedPath);
|
||
|
|
|
||
|
|
$service = new StorageServiceImpl($config, $sanitizer, $gateway);
|
||
|
|
|
||
|
|
// WHEN
|
||
|
|
$path = $service->storeSanitized($sanitizedFile);
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
expect($path)->toBe($expectedPath);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('stores sanitized file onto specified eligible disk', function (): void {
|
||
|
|
// GIVEN
|
||
|
|
$config = createServiceConfig();
|
||
|
|
$sanitizer = \Mockery::mock(FileSanitizer::class);
|
||
|
|
$gateway = \Mockery::mock(StorageGateway::class);
|
||
|
|
$disk = new DiskName('backup', 'local');
|
||
|
|
$sanitizedFile = new SanitizedFile('doc.pdf', 'doc.pdf', FileStream::fromString('bytes'));
|
||
|
|
$expectedPath = new StorageFilePath('uuid.pdf');
|
||
|
|
|
||
|
|
$gateway->shouldReceive('storeFile')
|
||
|
|
->once()
|
||
|
|
->with($disk, $sanitizedFile)
|
||
|
|
->andReturn($expectedPath);
|
||
|
|
|
||
|
|
$service = new StorageServiceImpl($config, $sanitizer, $gateway);
|
||
|
|
|
||
|
|
// WHEN
|
||
|
|
$path = $service->storeSanitizedOntoDisk($sanitizedFile, $disk);
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
expect($path)->toBe($expectedPath);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('throws exception if storing sanitized file onto non-eligible disk', function (): void {
|
||
|
|
// GIVEN
|
||
|
|
$config = createServiceConfig();
|
||
|
|
$sanitizer = \Mockery::mock(FileSanitizer::class);
|
||
|
|
$gateway = \Mockery::mock(StorageGateway::class);
|
||
|
|
$disk = new DiskName('public', 'local');
|
||
|
|
$sanitizedFile = new SanitizedFile('doc.pdf', 'doc.pdf', FileStream::fromString('bytes'));
|
||
|
|
$service = new StorageServiceImpl($config, $sanitizer, $gateway);
|
||
|
|
|
||
|
|
// WHEN
|
||
|
|
$action = fn () => $service->storeSanitizedOntoDisk($sanitizedFile, $disk);
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
expect($action)->toThrow(UnexpectedStorageException::class, "Disk 'public' is not eligible for storage operations.");
|
||
|
|
});
|
||
|
|
|
||
|
|
it('stores uploaded file onto default disk', function (): void {
|
||
|
|
// GIVEN
|
||
|
|
$config = createServiceConfig();
|
||
|
|
$sanitizer = \Mockery::mock(FileSanitizer::class);
|
||
|
|
$gateway = \Mockery::mock(StorageGateway::class);
|
||
|
|
$uploadedFile = UploadedFile::fake()->create('file.pdf');
|
||
|
|
$sanitizedFile = new SanitizedFile('file.pdf', 'file.pdf', FileStream::fromString('pdf'));
|
||
|
|
$expectedPath = new StorageFilePath('stored/path.pdf');
|
||
|
|
|
||
|
|
$sanitizer->shouldReceive('sanitizeUploaded')
|
||
|
|
->once()
|
||
|
|
->with($uploadedFile)
|
||
|
|
->andReturn($sanitizedFile);
|
||
|
|
|
||
|
|
$gateway->shouldReceive('storeFile')
|
||
|
|
->once()
|
||
|
|
->with(\Mockery::on(fn (DiskName $d) => $d->value === 'local'), $sanitizedFile)
|
||
|
|
->andReturn($expectedPath);
|
||
|
|
|
||
|
|
$service = new StorageServiceImpl($config, $sanitizer, $gateway);
|
||
|
|
|
||
|
|
// WHEN
|
||
|
|
$path = $service->storeUploaded($uploadedFile);
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
expect($path)->toBe($expectedPath);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('stores uploaded file onto specified eligible disk', function (): void {
|
||
|
|
// GIVEN
|
||
|
|
$config = createServiceConfig();
|
||
|
|
$sanitizer = \Mockery::mock(FileSanitizer::class);
|
||
|
|
$gateway = \Mockery::mock(StorageGateway::class);
|
||
|
|
$disk = new DiskName('backup', 'local');
|
||
|
|
$uploadedFile = UploadedFile::fake()->create('file.pdf');
|
||
|
|
$sanitizedFile = new SanitizedFile('file.pdf', 'file.pdf', FileStream::fromString('pdf'));
|
||
|
|
$expectedPath = new StorageFilePath('uuid.pdf');
|
||
|
|
|
||
|
|
$sanitizer->shouldReceive('sanitizeUploaded')
|
||
|
|
->once()
|
||
|
|
->with($uploadedFile)
|
||
|
|
->andReturn($sanitizedFile);
|
||
|
|
|
||
|
|
$gateway->shouldReceive('storeFile')
|
||
|
|
->once()
|
||
|
|
->with($disk, $sanitizedFile)
|
||
|
|
->andReturn($expectedPath);
|
||
|
|
|
||
|
|
$service = new StorageServiceImpl($config, $sanitizer, $gateway);
|
||
|
|
|
||
|
|
// WHEN
|
||
|
|
$path = $service->storeUploadedOntoDisk($uploadedFile, $disk);
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
expect($path)->toBe($expectedPath);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('stores SplFileObject onto default disk', function (): void {
|
||
|
|
// GIVEN
|
||
|
|
$tempFile = tempnam(sys_get_temp_dir(), 'test_spl_svc_') . '.txt';
|
||
|
|
file_put_contents($tempFile, 'spl content');
|
||
|
|
|
||
|
|
$config = createServiceConfig();
|
||
|
|
$sanitizer = \Mockery::mock(FileSanitizer::class);
|
||
|
|
$gateway = \Mockery::mock(StorageGateway::class);
|
||
|
|
$splFile = new \SplFileObject($tempFile);
|
||
|
|
$sanitizedFile = new SanitizedFile('file.txt', 'file.txt', FileStream::fromString('txt'));
|
||
|
|
$expectedPath = new StorageFilePath('stored/spl.txt');
|
||
|
|
|
||
|
|
$sanitizer->shouldReceive('sanitizeFile')
|
||
|
|
->once()
|
||
|
|
->with($splFile)
|
||
|
|
->andReturn($sanitizedFile);
|
||
|
|
|
||
|
|
$gateway->shouldReceive('storeFile')
|
||
|
|
->once()
|
||
|
|
->with(\Mockery::on(fn (DiskName $d) => $d->value === 'local'), $sanitizedFile)
|
||
|
|
->andReturn($expectedPath);
|
||
|
|
|
||
|
|
$service = new StorageServiceImpl($config, $sanitizer, $gateway);
|
||
|
|
|
||
|
|
try {
|
||
|
|
// WHEN
|
||
|
|
$path = $service->storeFile($splFile);
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
expect($path)->toBe($expectedPath);
|
||
|
|
} finally {
|
||
|
|
unset($splFile);
|
||
|
|
if (file_exists($tempFile)) {
|
||
|
|
unlink($tempFile);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
it('stores SplFileObject onto specified eligible disk', function (): void {
|
||
|
|
// GIVEN
|
||
|
|
$tempFile = tempnam(sys_get_temp_dir(), 'test_spl_disk_') . '.txt';
|
||
|
|
file_put_contents($tempFile, 'spl disk content');
|
||
|
|
|
||
|
|
$config = createServiceConfig();
|
||
|
|
$sanitizer = \Mockery::mock(FileSanitizer::class);
|
||
|
|
$gateway = \Mockery::mock(StorageGateway::class);
|
||
|
|
$disk = new DiskName('backup', 'local');
|
||
|
|
$splFile = new \SplFileObject($tempFile);
|
||
|
|
$sanitizedFile = new SanitizedFile('file.txt', 'file.txt', FileStream::fromString('txt'));
|
||
|
|
$expectedPath = new StorageFilePath('s3/spl.txt');
|
||
|
|
|
||
|
|
$sanitizer->shouldReceive('sanitizeFile')
|
||
|
|
->once()
|
||
|
|
->with($splFile)
|
||
|
|
->andReturn($sanitizedFile);
|
||
|
|
|
||
|
|
$gateway->shouldReceive('storeFile')
|
||
|
|
->once()
|
||
|
|
->with($disk, $sanitizedFile)
|
||
|
|
->andReturn($expectedPath);
|
||
|
|
|
||
|
|
$service = new StorageServiceImpl($config, $sanitizer, $gateway);
|
||
|
|
|
||
|
|
try {
|
||
|
|
// WHEN
|
||
|
|
$path = $service->storeFileOntoDisk($splFile, $disk);
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
expect($path)->toBe($expectedPath);
|
||
|
|
} finally {
|
||
|
|
unset($splFile);
|
||
|
|
if (file_exists($tempFile)) {
|
||
|
|
unlink($tempFile);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('StorageServiceImpl file removal', function (): void {
|
||
|
|
it('removes file from default disk', function (): void {
|
||
|
|
// GIVEN
|
||
|
|
$config = createServiceConfig();
|
||
|
|
$sanitizer = \Mockery::mock(FileSanitizer::class);
|
||
|
|
$gateway = \Mockery::mock(StorageGateway::class);
|
||
|
|
$filePath = new StorageFilePath('to_delete.txt');
|
||
|
|
|
||
|
|
$gateway->shouldReceive('deleteFile')
|
||
|
|
->once()
|
||
|
|
->with(\Mockery::on(fn (DiskName $d) => $d->value === 'local'), $filePath)
|
||
|
|
->andReturn(true);
|
||
|
|
|
||
|
|
$service = new StorageServiceImpl($config, $sanitizer, $gateway);
|
||
|
|
|
||
|
|
// WHEN
|
||
|
|
$result = $service->remove($filePath);
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
expect($result)->toBeTrue();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('removes file from specified eligible disk', function (): void {
|
||
|
|
// GIVEN
|
||
|
|
$config = createServiceConfig();
|
||
|
|
$sanitizer = \Mockery::mock(FileSanitizer::class);
|
||
|
|
$gateway = \Mockery::mock(StorageGateway::class);
|
||
|
|
$disk = new DiskName('backup', 'local');
|
||
|
|
$filePath = new StorageFilePath('to_delete.txt');
|
||
|
|
|
||
|
|
$gateway->shouldReceive('deleteFile')
|
||
|
|
->once()
|
||
|
|
->with($disk, $filePath)
|
||
|
|
->andReturn(true);
|
||
|
|
|
||
|
|
$service = new StorageServiceImpl($config, $sanitizer, $gateway);
|
||
|
|
|
||
|
|
// WHEN
|
||
|
|
$result = $service->removeFromDisk($filePath, $disk);
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
expect($result)->toBeTrue();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('throws exception if specified disk is not eligible during removal', function (): void {
|
||
|
|
// GIVEN
|
||
|
|
$config = createServiceConfig();
|
||
|
|
$sanitizer = \Mockery::mock(FileSanitizer::class);
|
||
|
|
$gateway = \Mockery::mock(StorageGateway::class);
|
||
|
|
$disk = new DiskName('public', 'local');
|
||
|
|
$filePath = new StorageFilePath('to_delete.txt');
|
||
|
|
$service = new StorageServiceImpl($config, $sanitizer, $gateway);
|
||
|
|
|
||
|
|
// WHEN
|
||
|
|
$action = fn () => $service->removeFromDisk($filePath, $disk);
|
||
|
|
|
||
|
|
// THEN
|
||
|
|
expect($action)->toThrow(UnexpectedStorageException::class, "Disk 'public' is not eligible for storage operations.");
|
||
|
|
});
|
||
|
|
});
|