- Move 12 markdown files from root to docs/ subdirectories - Organize documentation by category: • docs/troubleshooting/ (1 file) - Technical troubleshooting guides • docs/deployment/ (4 files) - Deployment and security documentation • docs/guides/ (3 files) - Feature-specific guides • docs/planning/ (4 files) - Planning and improvement proposals Root directory cleanup: - Reduced from 16 to 4 markdown files in root - Only essential project files remain: • CLAUDE.md (AI instructions) • README.md (Main project readme) • CLEANUP_PLAN.md (Current cleanup plan) • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements) This improves: ✅ Documentation discoverability ✅ Logical organization by purpose ✅ Clean root directory ✅ Better maintainability
196 lines
5.8 KiB
PHP
196 lines
5.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Application\Media\ShowImage;
|
|
use App\Domain\Media\Image;
|
|
use App\Domain\Media\ImageRepository;
|
|
use App\Domain\Media\ImageVariantRepository;
|
|
use App\Framework\Core\PathProvider;
|
|
use App\Framework\Http\HttpRequest;
|
|
use App\Framework\Router\Result\FileResult;
|
|
use App\Framework\Exception\FrameworkException;
|
|
use App\Framework\Filesystem\FilePath;
|
|
use App\Framework\Http\MimeType;
|
|
use App\Framework\Core\ValueObjects\FileSize;
|
|
use App\Framework\Core\ValueObjects\Hash;
|
|
use App\Framework\Ulid\Ulid;
|
|
|
|
beforeEach(function () {
|
|
// Create test directory and file
|
|
$this->testDir = '/tmp/test_show_image';
|
|
if (!is_dir($this->testDir)) {
|
|
mkdir($this->testDir, 0755, true);
|
|
}
|
|
|
|
$this->testImagePath = $this->testDir . '/test-show.jpg';
|
|
file_put_contents($this->testImagePath, 'fake-jpeg-content');
|
|
|
|
// Mock repositories
|
|
$this->imageRepository = Mockery::mock(ImageRepository::class);
|
|
$this->imageVariantRepository = Mockery::mock(ImageVariantRepository::class);
|
|
$this->pathProvider = Mockery::mock(PathProvider::class);
|
|
|
|
// Create controller
|
|
$this->controller = new ShowImage(
|
|
$this->pathProvider,
|
|
$this->imageRepository,
|
|
$this->imageVariantRepository
|
|
);
|
|
|
|
// Test image entity
|
|
$this->testImage = new Image(
|
|
ulid: Ulid::generate(),
|
|
filename: 'test-show.jpg',
|
|
originalFilename: 'original.jpg',
|
|
mimeType: MimeType::fromString('image/jpeg'),
|
|
fileSize: FileSize::fromBytes(strlen('fake-jpeg-content')),
|
|
width: 300,
|
|
height: 400,
|
|
hash: Hash::fromString('test-hash'),
|
|
path: FilePath::create($this->testDir),
|
|
altText: 'Test show image'
|
|
);
|
|
});
|
|
|
|
afterEach(function () {
|
|
if (file_exists($this->testImagePath)) {
|
|
unlink($this->testImagePath);
|
|
}
|
|
if (is_dir($this->testDir)) {
|
|
rmdir($this->testDir);
|
|
}
|
|
Mockery::close();
|
|
});
|
|
|
|
it('can serve existing image file', function () {
|
|
// Arrange
|
|
$filename = 'test-show.jpg';
|
|
$request = Mockery::mock(HttpRequest::class);
|
|
|
|
$this->imageRepository
|
|
->shouldReceive('findByFilename')
|
|
->with($filename)
|
|
->andReturn($this->testImage);
|
|
|
|
// Act
|
|
$result = $this->controller->__invoke($filename, $request);
|
|
|
|
// Assert
|
|
expect($result)->toBeInstanceOf(FileResult::class);
|
|
});
|
|
|
|
it('falls back to image variant repository when image not found', function () {
|
|
// Arrange
|
|
$filename = 'variant-image.jpg';
|
|
$request = Mockery::mock(HttpRequest::class);
|
|
|
|
$this->imageRepository
|
|
->shouldReceive('findByFilename')
|
|
->with($filename)
|
|
->andReturn(null);
|
|
|
|
$this->imageVariantRepository
|
|
->shouldReceive('findByFilename')
|
|
->with($filename)
|
|
->andReturn($this->testImage);
|
|
|
|
// Act
|
|
$result = $this->controller->__invoke($filename, $request);
|
|
|
|
// Assert
|
|
expect($result)->toBeInstanceOf(FileResult::class);
|
|
});
|
|
|
|
it('throws exception when image not found in either repository', function () {
|
|
// Arrange
|
|
$filename = 'nonexistent.jpg';
|
|
$request = Mockery::mock(HttpRequest::class);
|
|
|
|
$this->imageRepository
|
|
->shouldReceive('findByFilename')
|
|
->with($filename)
|
|
->andReturn(null);
|
|
|
|
$this->imageVariantRepository
|
|
->shouldReceive('findByFilename')
|
|
->with($filename)
|
|
->andReturn(null);
|
|
|
|
// Act & Assert
|
|
expect(fn() => $this->controller->__invoke($filename, $request))
|
|
->toThrow(FrameworkException::class, 'Image not found: nonexistent.jpg');
|
|
});
|
|
|
|
it('throws exception when image file does not exist on filesystem', function () {
|
|
// Arrange
|
|
$filename = 'missing-file.jpg';
|
|
$request = Mockery::mock(HttpRequest::class);
|
|
|
|
$missingFileImage = new Image(
|
|
ulid: Ulid::generate(),
|
|
filename: 'missing-file.jpg',
|
|
originalFilename: 'original.jpg',
|
|
mimeType: MimeType::fromString('image/jpeg'),
|
|
fileSize: FileSize::fromBytes(100),
|
|
width: 300,
|
|
height: 400,
|
|
hash: Hash::fromString('test-hash'),
|
|
path: FilePath::create('/nonexistent/path'),
|
|
altText: 'Missing file'
|
|
);
|
|
|
|
$this->imageRepository
|
|
->shouldReceive('findByFilename')
|
|
->with($filename)
|
|
->andReturn($missingFileImage);
|
|
|
|
// Act & Assert
|
|
expect(fn() => $this->controller->__invoke($filename, $request))
|
|
->toThrow(FrameworkException::class, 'Image file not found on filesystem');
|
|
});
|
|
|
|
it('determines correct MIME type based on file extension', function () {
|
|
// Test JPEG
|
|
$jpegFilename = 'test.jpg';
|
|
$jpegImage = $this->testImage->withFilename($jpegFilename);
|
|
$request = Mockery::mock(HttpRequest::class);
|
|
|
|
$this->imageRepository
|
|
->shouldReceive('findByFilename')
|
|
->with($jpegFilename)
|
|
->andReturn($jpegImage);
|
|
|
|
$result = $this->controller->__invoke($jpegFilename, $request);
|
|
expect($result)->toBeInstanceOf(FileResult::class);
|
|
});
|
|
|
|
it('handles different image file extensions', function () {
|
|
$extensions = [
|
|
'test.png' => 'image/png',
|
|
'test.gif' => 'image/gif',
|
|
'test.webp' => 'image/webp',
|
|
'test.avif' => 'image/avif',
|
|
'test.unknown' => 'image/jpeg' // fallback
|
|
];
|
|
|
|
foreach ($extensions as $filename => $expectedMime) {
|
|
// Create test file for each extension
|
|
$testPath = $this->testDir . '/' . $filename;
|
|
file_put_contents($testPath, 'fake-content');
|
|
|
|
$testImage = $this->testImage->withFilename($filename);
|
|
$request = Mockery::mock(HttpRequest::class);
|
|
|
|
$this->imageRepository
|
|
->shouldReceive('findByFilename')
|
|
->with($filename)
|
|
->andReturn($testImage);
|
|
|
|
$result = $this->controller->__invoke($filename, $request);
|
|
expect($result)->toBeInstanceOf(FileResult::class);
|
|
|
|
// Clean up
|
|
unlink($testPath);
|
|
}
|
|
}); |