SuperSeller3000/tests/Unit/Application/Storage/LocalStorageManagerTest.php
Simon Kuehn 6bf001b0c0 feat: implement Plan 2 — Article Management API
- 6 new domain repository interfaces (StoragePath, ArticlePhoto, AttributeValue, ChannelField, ArticleTypePlatformConfig, AttributeMapping)
- 6 Doctrine repository implementations
- StorageManager with multi-path quota-aware file storage (LocalStorageManager)
- Application services: ArticleTypeService, ArticleService, ArticleValidator, PhotoService, PlatformService, MappingService
- REST controllers: ArticleType, Article, Photo, Platform, Mapping (all under /api prefix)
- inventory_number sequence migration
- 22 unit tests passing, PHPStan level 9 clean, CS Fixer clean
- Fixed phpdoc_to_comment CS Fixer rule (disabled) to preserve @var type annotations
- Fixed PHPStan: User::getUserIdentifier non-empty-string, AIPipelineJob nullable missingFields

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 05:19:20 +00:00

88 lines
3.1 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Tests\Unit\Application\Storage;
use App\Domain\Storage\Repository\StoragePathRepositoryInterface;
use App\Domain\Storage\StoragePath;
use App\Infrastructure\Storage\LocalStorageManager;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
final class LocalStorageManagerTest extends TestCase
{
private StoragePathRepositoryInterface&MockObject $repo;
private LocalStorageManager $manager;
private string $tmpFile;
protected function setUp(): void
{
$this->repo = $this->createMock(StoragePathRepositoryInterface::class);
$this->manager = new LocalStorageManager($this->repo);
$this->tmpFile = sys_get_temp_dir().'/test-upload-'.uniqid().'.jpg';
file_put_contents($this->tmpFile, str_repeat('x', 100));
}
protected function tearDown(): void
{
if (file_exists($this->tmpFile)) {
unlink($this->tmpFile);
}
}
public function test_store_picks_active_path_with_quota(): void
{
$path = new StoragePath('Main', sys_get_temp_dir().'/storage-test-'.uniqid(), 1_000_000, 10);
mkdir($path->getBasePath(), recursive: true);
$this->repo->expects($this->once())
->method('findActiveSortedByPriority')
->willReturn([$path]);
$stored = $this->manager->store($this->tmpFile, 'photo.jpg');
$this->assertSame($path->getId()->toRfc4122(), $stored->storagePath->getId()->toRfc4122());
$this->assertStringEndsWith('.jpg', $stored->filename);
$this->assertFileExists($path->getBasePath().'/'.$stored->filename);
// cleanup
unlink($path->getBasePath().'/'.$stored->filename);
rmdir($path->getBasePath());
}
public function test_throws_when_no_active_path(): void
{
$this->repo->method('findActiveSortedByPriority')->willReturn([]);
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('No active storage path');
$this->manager->store($this->tmpFile, 'photo.jpg');
}
public function test_skips_full_path_and_uses_next(): void
{
$fullPath = new StoragePath('Full', sys_get_temp_dir().'/full-'.uniqid(), 50, 20);
$okPath = new StoragePath('OK', sys_get_temp_dir().'/ok-'.uniqid(), 1_000_000, 10);
mkdir($fullPath->getBasePath(), recursive: true);
mkdir($okPath->getBasePath(), recursive: true);
// fullPath quota (50 bytes) is less than file size (100 bytes) — skip it
$this->repo->method('findActiveSortedByPriority')->willReturn([$fullPath, $okPath]);
$stored = $this->manager->store($this->tmpFile, 'photo.jpg');
$this->assertSame($okPath->getId()->toRfc4122(), $stored->storagePath->getId()->toRfc4122());
unlink($okPath->getBasePath().'/'.$stored->filename);
rmdir($fullPath->getBasePath());
rmdir($okPath->getBasePath());
}
public function test_get_full_path(): void
{
$path = new StoragePath('Main', '/srv/storage', 1_000_000, 10);
$this->assertSame('/srv/storage/photo.jpg', $this->manager->getFullPath($path, 'photo.jpg'));
}
}