presseportale/tests/Feature/Images/OpenGraphAndTeaserCreditTest.php

168 lines
8.3 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
use App\Enums\Portal;
use App\Models\Category;
use App\Models\Company;
use App\Models\PressRelease;
use App\Models\PressReleaseImage;
use App\Models\User;
use App\Services\Image\ImageService;
use App\Services\PressRelease\Images\ImageCorrectionService;
use Database\Seeders\RolesAndPermissionsSeeder;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;
/**
* Bildkennzeichnung Block H: OG-Variante mit eingebranntem Nachweis,
* Neuaufbau nach Korrektur, Fokuspunkt/Letterbox, Nachweis in Teasern.
*/
beforeEach(function (): void {
/** @var TestCase $this */
$this->seed(RolesAndPermissionsSeeder::class);
Storage::fake('local');
Storage::fake('public');
});
function ogPixel(string $path, int $x, int $y): array
{
$img = imagecreatefromjpeg(Storage::disk('local')->path($path));
$rgb = imagecolorat($img, $x, $y);
imagedestroy($img);
return [($rgb >> 16) & 0xFF, ($rgb >> 8) & 0xFF, $rgb & 0xFF];
}
test('the og variant carries a darker caption bar at the bottom while the rest stays untouched', function () {
$upload = UploadedFile::fake()->image('hell.jpg', 2400, 1260)->size(200);
// Ein helles Bild, damit der Balken messbar ist.
$white = imagecreatetruecolor(2400, 1260);
imagefilledrectangle($white, 0, 0, 2400, 1260, imagecolorallocate($white, 250, 250, 250));
imagejpeg($white, $upload->getRealPath(), 95);
imagedestroy($white);
$stored = (new ImageService)->storePressReleaseImage($upload, 7, 'local', ogCaption: 'Foto: Anna Beispiel · KI-generiert');
expect($stored['variants'])->toHaveKey('og');
[$w, $h] = getimagesize(Storage::disk('local')->path($stored['variants']['og']));
expect($w)->toBe(1200)->and($h)->toBe(630);
$top = ogPixel($stored['variants']['og'], 600, 40);
$bar = ogPixel($stored['variants']['og'], 1100, 615);
expect($top[0])->toBeGreaterThan(235)
->and($bar[0])->toBeLessThan(200);
// Cover-Variante ohne Balken.
$coverBottom = ogPixel($stored['variants']['cover'], 1200, 570);
expect($coverBottom[0])->toBeGreaterThan(235);
});
test('the focus point moves the crop and no_crop keeps the whole image', function () {
$upload = UploadedFile::fake()->image('split.jpg', 2000, 1000)->size(200);
// Linke Hälfte rot, rechte Hälfte blau.
$img = imagecreatetruecolor(2000, 1000);
imagefilledrectangle($img, 0, 0, 999, 1000, imagecolorallocate($img, 220, 30, 30));
imagefilledrectangle($img, 1000, 0, 2000, 1000, imagecolorallocate($img, 30, 30, 220));
imagejpeg($img, $upload->getRealPath(), 95);
imagedestroy($img);
$service = new ImageService;
// thumb 320×240 aus 2000×1000: Ausschnitt 1333 px breit. Fokus links →
// Ausschnitt 0–1333 (rot bis Pixel 240 der Miniatur), Fokus rechts →
// 667–2000 (blau ab Pixel 80).
$left = $service->storePressReleaseImage($upload, 1, 'local', focus: ['x' => 0, 'y' => 50]);
$leftPixel = ogPixel($left['variants']['thumb'], 100, 120);
expect($leftPixel[0])->toBeGreaterThan($leftPixel[2]);
$upload2 = UploadedFile::fake()->image('split2.jpg', 2000, 1000)->size(200);
copy($upload->getRealPath(), $upload2->getRealPath());
$right = $service->storePressReleaseImage($upload2, 2, 'local', focus: ['x' => 100, 'y' => 50]);
$rightPixel = ogPixel($right['variants']['thumb'], 100, 120);
expect($rightPixel[2])->toBeGreaterThan($rightPixel[0]);
$upload3 = UploadedFile::fake()->image('split3.jpg', 2000, 1000)->size(200);
copy($upload->getRealPath(), $upload3->getRealPath());
$boxed = $service->storePressReleaseImage($upload3, 3, 'local', noCrop: true);
[$w, $h] = getimagesize(Storage::disk('local')->path($boxed['variants']['cover']));
// 2000×1000 im 1280×580-Rahmen: Bild 1160×580, Ränder links/rechts je 60 px.
$edge = ogPixel($boxed['variants']['cover'], 10, 290);
expect($w)->toBe(1280)->and($h)->toBe(580)
->and($edge[0])->toBeGreaterThan(230)->and(abs($edge[0] - $edge[2]))->toBeLessThan(15);
});
test('a credit correction regenerates the og variant and the detail page uses it', function () {
/** @var TestCase $this */
$category = Category::factory()->create(['portal' => Portal::Presseecho]);
$category->translations()->create(['locale' => 'de', 'name' => 'Energie', 'slug' => 'energie-og']);
$company = Company::factory()->presseecho()->create(['name' => 'Beispiel AG', 'is_active' => true]);
$pr = PressRelease::factory()->published()->forPortal(Portal::Presseecho)->for($category)->for($company)->create([
'slug' => 'og-test', 'published_ever_at' => now()->subDay(),
]);
$upload = UploadedFile::fake()->image('foto.jpg', 1600, 900)->size(100);
$stored = (new ImageService)->storePressReleaseImage($upload, $pr->id, 'local', ogCaption: 'Foto: Alt');
$image = PressReleaseImage::factory()->for($pr)->rulesV2()->create([
'disk' => 'local', 'path' => $stored['path'], 'variants' => $stored['variants'], 'copyright' => 'Alt', 'published_ever_at' => now()->subDay(),
]);
$before = md5((string) Storage::disk('local')->get($stored['variants']['og']));
$admin = User::factory()->create(['is_active' => true]);
$admin->assignRole('admin');
app(ImageCorrectionService::class)->correctCredit($image, $admin, 'Neuer sehr langer Nachweis der gekürzt werden muss weil er nicht in den Balken passt Anna Beispiel Musterfirma GmbH', 'Einwand');
$after = md5((string) Storage::disk('local')->get($image->fresh()->variants['og']));
expect($after)->not->toBe($before);
$this->get('https://presseecho.test/de/press-release/og-test')
->assertSuccessful()
->assertSee('<meta property="og:image" content="'.route('media.press-release-images.show', ['image' => $image->id, 'variant' => 'og']).'">', false);
});
test('teasers carry the credit in alt and title and the hero shows a caption', function () {
/** @var TestCase $this */
$category = Category::factory()->create(['portal' => Portal::Presseecho]);
$category->translations()->create(['locale' => 'de', 'name' => 'Energie', 'slug' => 'energie-teaser']);
$company = Company::factory()->presseecho()->create(['name' => 'Beispiel AG', 'is_active' => true]);
$pr = PressRelease::factory()->published()->forPortal(Portal::Presseecho)->for($category)->for($company)->create([
'title' => 'Teaser-Meldung', 'published_at' => now()->subHour(), 'published_ever_at' => now()->subHour(),
]);
PressReleaseImage::factory()->for($pr)->rulesV2()->create(['copyright' => 'Anna Beispiel', 'ai_declaration' => 'ai_generated']);
expect($pr->fresh()->load('images', 'company')->cardImageCredit())->toBe('Foto: Anna Beispiel · KI-generiert');
$this->get('https://presseecho.test/de/category/energie-teaser')
->assertSuccessful()
->assertSee('alt="Teaser-Meldung · Foto: Anna Beispiel · KI-generiert"', false)
->assertSee('data-image-credit-teaser', false);
});
test('the regenerate command builds missing og variants for the existing stock with the credit line', function () {
/** @var TestCase $this */
$company = Company::factory()->presseecho()->create(['name' => 'Bestand GmbH', 'is_active' => true]);
$pr = PressRelease::factory()->published()->for($company)->create(['published_ever_at' => now()->subDay()]);
$upload = UploadedFile::fake()->image('alt.jpg', 1600, 900)->size(100);
$stored = (new ImageService)->storePressReleaseImage($upload, $pr->id, 'local');
$variants = $stored['variants'];
Storage::disk('local')->delete($variants['og']);
unset($variants['og']);
$image = PressReleaseImage::factory()->for($pr)->legacy()->create(['disk' => 'local', 'path' => $stored['path'], 'variants' => $variants]);
$this->artisan('press-release-images:regenerate-variants', ['--only' => 'og', '--published' => true])
->expectsOutputToContain('Trockenlauf: OG-Varianten für 1 von 1 Bildern.')
->assertSuccessful();
expect($image->fresh()->variants)->not->toHaveKey('og');
$this->artisan('press-release-images:regenerate-variants', ['--only' => 'og', '--published' => true, '--execute' => true])
->expectsOutputToContain('OG-Varianten erzeugt: 1')
->assertSuccessful();
$og = $image->fresh()->variants['og'] ?? null;
expect($og)->not->toBeNull()
->and(Storage::disk('local')->exists($og))->toBeTrue()
->and(getimagesize(Storage::disk('local')->path($og))[0])->toBe(1200);
});