Files
michaelschiemer/tests/Feature/ImageApiControllerTest.php
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- 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.
2025-10-25 19:18:37 +02:00

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);
});