pp-planer/tests/Feature/FileConversionTest.php
Thorsten Bus 57d54ec06b feat: Wave 1 Foundation - Database, OAuth, Sync, Files, Layout, Email (T2-T7)
T2: Database Schema + All Migrations
- 10 migrations: users extension, services, songs, song_groups, song_slides,
  song_arrangements, song_arrangement_groups, service_songs, slides, cts_sync_log
- 9 Eloquent models with relationships and casts
- 9 factory classes for testing
- Tests: DatabaseSchemaTest (2 tests, 26 assertions) 

T3: ChurchTools OAuth Provider
- Custom Socialite provider for ChurchTools OAuth2
- AuthController with redirect/callback/logout
- Replaced Breeze login with OAuth-only (German UI)
- Removed all Breeze register/password-reset pages
- Tests: OAuthTest (9 tests, 54 assertions) 

T4: CTS API Service + Sync Command
- ChurchToolsService wrapping 5pm-HDH/churchtools-api
- SyncChurchToolsCommand (php artisan cts:sync)
- SyncController for refresh button
- CCLI-based song matching
- Tests: ChurchToolsSyncTest (2 tests) 

T5: File Conversion Service
- FileConversionService with letterbox/pillarbox to 1920×1080
- ConvertPowerPointJob (queued) with LibreOffice + spatie/pdf-to-image
- ZIP extraction and recursive processing
- Thumbnail generation (320×180)
- Tests: FileConversionTest (2 tests, 21 assertions) 

T6: Shared Vue Components
- AuthenticatedLayout with nav, user info, refresh button
- useAutoSave composable (500ms debounce)
- FlashMessage, ConfirmDialog, LoadingSpinner components
- HandleInertiaRequests middleware with shared props
- Tests: SharedPropsTest (7 tests) 

T7: Email Configuration
- MissingSongRequest mailable (German)
- Email template with song info and service link
- SONG_REQUEST_EMAIL config
- Tests: MissingSongMailTest (2 tests, 10 assertions) 

All tests passing: 30/30 (233 assertions)
All UI text in German with 'Du' form
Wave 1 complete: 7/7 tasks 
2026-03-01 19:39:26 +01:00

87 lines
3 KiB
PHP

<?php
use App\Services\FileConversionService;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
beforeEach(function () {
Storage::fake('public');
});
test('convert image creates 1920x1080 jpg with black bars and thumbnail', function () {
$service = app(FileConversionService::class);
$file = makePngUpload('landscape.png', 400, 300);
$result = $service->convertImage($file);
expect($result)->toHaveKeys(['filename', 'thumbnail']);
expect($result['filename'])->toStartWith('slides/')->toEndWith('.jpg');
expect($result['thumbnail'])->toStartWith('slides/thumbnails/')->toEndWith('.jpg');
expect(Storage::disk('public')->exists($result['filename']))->toBeTrue();
expect(Storage::disk('public')->exists($result['thumbnail']))->toBeTrue();
$mainPath = Storage::disk('public')->path($result['filename']);
[$width, $height] = getimagesize($mainPath);
expect($width)->toBe(1920);
expect($height)->toBe(1080);
$image = imagecreatefromjpeg($mainPath);
expect($image)->not->toBeFalse();
$corner = imagecolorsforindex($image, imagecolorat($image, 0, 0));
$center = imagecolorsforindex($image, imagecolorat($image, 960, 540));
expect($corner['red'] + $corner['green'] + $corner['blue'])->toBeLessThan(20);
expect($center['red'])->toBeGreaterThan(100);
$thumbPath = Storage::disk('public')->path($result['thumbnail']);
[$thumbWidth, $thumbHeight] = getimagesize($thumbPath);
expect($thumbWidth)->toBe(320);
expect($thumbHeight)->toBe(180);
});
test('portrait image gets pillarbox bars on left and right', function () {
$service = app(FileConversionService::class);
$file = makePngUpload('portrait.png', 1080, 1920);
$result = $service->convertImage($file);
$mainPath = Storage::disk('public')->path($result['filename']);
[$width, $height] = getimagesize($mainPath);
expect($width)->toBe(1920);
expect($height)->toBe(1080);
$image = imagecreatefromjpeg($mainPath);
expect($image)->not->toBeFalse();
$left = imagecolorsforindex($image, imagecolorat($image, 10, 540));
$right = imagecolorsforindex($image, imagecolorat($image, 1910, 540));
$center = imagecolorsforindex($image, imagecolorat($image, 960, 540));
expect($left['red'] + $left['green'] + $left['blue'])->toBeLessThan(20);
expect($right['red'] + $right['green'] + $right['blue'])->toBeLessThan(20);
expect($center['red'])->toBeGreaterThan(100);
});
function makePngUpload(string $name, int $width, int $height): UploadedFile
{
$path = tempnam(sys_get_temp_dir(), 'cts-img-');
if ($path === false) {
throw new RuntimeException('Temp-Datei konnte nicht erstellt werden.');
}
$image = imagecreatetruecolor($width, $height);
if ($image === false) {
throw new RuntimeException('Bild konnte nicht erstellt werden.');
}
$red = imagecolorallocate($image, 255, 0, 0);
imagefill($image, 0, 0, $red);
imagepng($image, $path);
return new UploadedFile($path, $name, 'image/png', null, true);
}