ldc-file-storage-service/tests/Unit/Lib/StorageServiceImplTest.php

406 lines
14 KiB
PHP
Raw Normal View History

<?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.");
});
});