- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
266 lines
7.4 KiB
PHP
266 lines
7.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Application\Api\Images\ImageApiController;
|
|
use App\Domain\Media\Image;
|
|
use App\Domain\Media\ImageRepository;
|
|
use App\Framework\Core\ValueObjects\FileSize;
|
|
use App\Framework\Core\ValueObjects\Hash;
|
|
use App\Framework\Filesystem\ValueObjects\FilePath;
|
|
use App\Framework\Http\Exception\NotFound;
|
|
use App\Framework\Http\HttpRequest;
|
|
use App\Framework\Http\MimeType;
|
|
use App\Framework\Http\Responses\JsonResponse;
|
|
use App\Framework\Ulid\Ulid;
|
|
|
|
beforeEach(function () {
|
|
$this->imageRepository = Mockery::mock(ImageRepository::class);
|
|
$this->uploadSecurityService = Mockery::mock();
|
|
$this->imageProcessor = Mockery::mock();
|
|
$this->imageVariantRepository = Mockery::mock();
|
|
$this->ulidGenerator = Mockery::mock();
|
|
$this->pathProvider = Mockery::mock();
|
|
$this->clock = Mockery::mock();
|
|
|
|
$this->controller = new ImageApiController(
|
|
$this->imageRepository,
|
|
$this->uploadSecurityService,
|
|
$this->imageProcessor,
|
|
$this->imageVariantRepository,
|
|
$this->ulidGenerator,
|
|
$this->pathProvider,
|
|
$this->clock
|
|
);
|
|
|
|
// Test images
|
|
$this->testImages = [
|
|
new Image(
|
|
ulid: Ulid::fromString('01ABCDEFGHIJKLMNOPQRSTUVWX'),
|
|
filename: 'image1.jpg',
|
|
originalFilename: 'original1.jpg',
|
|
mimeType: MimeType::fromString('image/jpeg'),
|
|
fileSize: FileSize::fromBytes(1024),
|
|
width: 800,
|
|
height: 600,
|
|
hash: Hash::fromString('hash1'),
|
|
path: FilePath::create('/test/path'),
|
|
altText: 'Test image 1'
|
|
),
|
|
new Image(
|
|
ulid: Ulid::fromString('01BCDEFGHIJKLMNOPQRSTUVWY'),
|
|
filename: 'image2.png',
|
|
originalFilename: 'original2.png',
|
|
mimeType: MimeType::fromString('image/png'),
|
|
fileSize: FileSize::fromBytes(2048),
|
|
width: 400,
|
|
height: 300,
|
|
hash: Hash::fromString('hash2'),
|
|
path: FilePath::create('/test/path'),
|
|
altText: 'Test image 2'
|
|
),
|
|
];
|
|
});
|
|
|
|
afterEach(function () {
|
|
Mockery::close();
|
|
});
|
|
|
|
it('can get paginated list of images', function () {
|
|
// Arrange
|
|
$request = Mockery::mock(HttpRequest::class);
|
|
$request->queryParams = [
|
|
'limit' => '10',
|
|
'offset' => '0',
|
|
];
|
|
|
|
$this->imageRepository
|
|
->shouldReceive('findAll')
|
|
->with(10, 0, null)
|
|
->andReturn($this->testImages);
|
|
|
|
$this->imageRepository
|
|
->shouldReceive('count')
|
|
->with(null)
|
|
->andReturn(2);
|
|
|
|
// Act
|
|
$response = $this->controller->getImages($request);
|
|
|
|
// Assert
|
|
expect($response)->toBeInstanceOf(JsonResponse::class);
|
|
|
|
$data = $response->getData();
|
|
expect($data['images'])->toHaveCount(2);
|
|
expect($data['pagination']['total'])->toBe(2);
|
|
expect($data['pagination']['limit'])->toBe(10);
|
|
expect($data['pagination']['offset'])->toBe(0);
|
|
expect($data['pagination']['has_more'])->toBeFalse();
|
|
});
|
|
|
|
it('can search images with query parameter', function () {
|
|
// Arrange
|
|
$request = Mockery::mock(HttpRequest::class);
|
|
$request->queryParams = [
|
|
'limit' => '50',
|
|
'offset' => '0',
|
|
'search' => 'test',
|
|
];
|
|
|
|
$this->imageRepository
|
|
->shouldReceive('findAll')
|
|
->with(50, 0, 'test')
|
|
->andReturn([$this->testImages[0]]);
|
|
|
|
$this->imageRepository
|
|
->shouldReceive('count')
|
|
->with('test')
|
|
->andReturn(1);
|
|
|
|
// Act
|
|
$response = $this->controller->getImages($request);
|
|
|
|
// Assert
|
|
$data = $response->getData();
|
|
expect($data['images'])->toHaveCount(1);
|
|
expect($data['pagination']['total'])->toBe(1);
|
|
});
|
|
|
|
it('returns correct image URLs pointing to ShowImage controller', function () {
|
|
// Arrange
|
|
$request = Mockery::mock(HttpRequest::class);
|
|
$request->queryParams = [];
|
|
|
|
$this->imageRepository
|
|
->shouldReceive('findAll')
|
|
->andReturn([$this->testImages[0]]);
|
|
|
|
$this->imageRepository
|
|
->shouldReceive('count')
|
|
->andReturn(1);
|
|
|
|
// Act
|
|
$response = $this->controller->getImages($request);
|
|
|
|
// Assert
|
|
$data = $response->getData();
|
|
$image = $data['images'][0];
|
|
|
|
expect($image['url'])->toBe('/images/image1.jpg');
|
|
expect($image['thumbnail_url'])->toBe('/images/image1.jpg'); // Same for non-thumbnail
|
|
});
|
|
|
|
it('generates correct thumbnail URLs for original images', function () {
|
|
// Test with original filename
|
|
$originalImage = new Image(
|
|
ulid: Ulid::generate(),
|
|
filename: 'test_original.jpg',
|
|
originalFilename: 'test.jpg',
|
|
mimeType: MimeType::fromString('image/jpeg'),
|
|
fileSize: FileSize::fromBytes(1024),
|
|
width: 800,
|
|
height: 600,
|
|
hash: Hash::fromString('hash'),
|
|
path: FilePath::create('/test'),
|
|
altText: 'Test'
|
|
);
|
|
|
|
$request = Mockery::mock(HttpRequest::class);
|
|
$request->queryParams = [];
|
|
|
|
$this->imageRepository
|
|
->shouldReceive('findAll')
|
|
->andReturn([$originalImage]);
|
|
|
|
$this->imageRepository
|
|
->shouldReceive('count')
|
|
->andReturn(1);
|
|
|
|
$response = $this->controller->getImages($request);
|
|
$data = $response->getData();
|
|
$image = $data['images'][0];
|
|
|
|
expect($image['url'])->toBe('/images/test_original.jpg');
|
|
expect($image['thumbnail_url'])->toBe('/images/test_thumbnail.jpg');
|
|
});
|
|
|
|
it('can get single image by ULID', function () {
|
|
// Arrange
|
|
$ulid = '01ABCDEFGHIJKLMNOPQRSTUVWX';
|
|
$testImage = $this->testImages[0];
|
|
|
|
$this->imageRepository
|
|
->shouldReceive('findByUlid')
|
|
->with($ulid)
|
|
->andReturn($testImage);
|
|
|
|
// Act
|
|
$response = $this->controller->getImage($ulid);
|
|
|
|
// Assert
|
|
expect($response)->toBeInstanceOf(JsonResponse::class);
|
|
|
|
$data = $response->getData();
|
|
expect($data['ulid'])->toBe($ulid);
|
|
expect($data['filename'])->toBe('image1.jpg');
|
|
expect($data['url'])->toBe('/media/images/test/path/image1.jpg');
|
|
});
|
|
|
|
it('throws NotFound exception when image ULID not found', function () {
|
|
// Arrange
|
|
$ulid = 'NONEXISTENT_ULID_123456789';
|
|
|
|
$this->imageRepository
|
|
->shouldReceive('findByUlid')
|
|
->with($ulid)
|
|
->andReturn(null);
|
|
|
|
// Act & Assert
|
|
expect(fn () => $this->controller->getImage($ulid))
|
|
->toThrow(NotFound::class, "Image with ULID {$ulid} not found");
|
|
});
|
|
|
|
it('can search images with advanced parameters', function () {
|
|
// Arrange
|
|
$request = Mockery::mock(HttpRequest::class);
|
|
$request->queryParams = [
|
|
'q' => 'landscape',
|
|
'type' => 'jpeg',
|
|
'min_width' => '800',
|
|
'min_height' => '600',
|
|
];
|
|
|
|
$this->imageRepository
|
|
->shouldReceive('search')
|
|
->with('landscape', 'jpeg', 800, 600)
|
|
->andReturn([$this->testImages[0]]);
|
|
|
|
// Act
|
|
$response = $this->controller->searchImages($request);
|
|
|
|
// Assert
|
|
expect($response)->toBeInstanceOf(JsonResponse::class);
|
|
|
|
$data = $response->getData();
|
|
expect($data['results'])->toHaveCount(1);
|
|
expect($data['count'])->toBe(1);
|
|
});
|
|
|
|
it('handles empty search results', function () {
|
|
// Arrange
|
|
$request = Mockery::mock(HttpRequest::class);
|
|
$request->queryParams = ['q' => 'nonexistent'];
|
|
|
|
$this->imageRepository
|
|
->shouldReceive('search')
|
|
->andReturn([]);
|
|
|
|
// Act
|
|
$response = $this->controller->searchImages($request);
|
|
|
|
// Assert
|
|
$data = $response->getData();
|
|
expect($data['results'])->toBeEmpty();
|
|
expect($data['count'])->toBe(0);
|
|
});
|