presseportale/tests/Feature/Images/ImageProvenanceInspectorTest.php
Kevin Adametz e9bdff3ad3 images: Datenmodell, Textarchiv, Herkunftsprüfung und gemeinsamer Uploader für Formular v2 (Bildkennzeichnung Block A)
- press_release_images: Kundenangabe/Portalkennzeichnung getrennt, Herkunftsbefund, Beweisoriginal, Snapshot-Pfade, published_ever_at, rules_version, Fokus/Letterbox; Bestand unangetastet
- legal_texts (insert-only) mit img-confirm-v1 (alter Wortlaut), v2 (AGB 0.6), v3 (AGB 0.7, noch ohne Datum); image-rights:backfill-confirmations
- image_rights_confirmations polymorph, insert-only, IP als versionierter HMAC; Löschung nur für nie veröffentlichte Uploads
- ImageProvenanceInspector (Imagick/exif): IPTC, XMP, EXIF; algorithmicallyEnhanced nur Vermerk; unlesbare Datei = error
- PressReleaseImageUploader: Original vor Derivaten, Hash-Bindung, Titelbild-Regel unter Sperre, Dateien bei Fehlern zurückgerollt; Admin-Upload ohne Bestätigung
- ImageService: kein Rückfall auf die Quelldatei, og-Variante, Fokuspunkt und no_crop

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 09:22:05 +00:00

77 lines
3.7 KiB
PHP

<?php
use App\Enums\ImageProvenanceStatus;
use App\Services\Image\ImageProvenanceInspector;
/**
* Bildkennzeichnung A.5: echte Fixture-Dateien (tests/Fixtures/images,
* erzeugt mit generate-fixtures.php), keine Fake-Bilder.
*/
function provenanceFixture(string $name): string
{
return base_path('tests/Fixtures/images/'.$name);
}
test('a camera jpeg with classic iptc yields an unsigned credit suggestion and no ai suggestion', function () {
$result = (new ImageProvenanceInspector)->inspect(provenanceFixture('camera-iptc.jpg'));
expect($result->status)->toBe(ImageProvenanceStatus::UnsignedMetadata)
->and($result->creditSuggestion())->toBe('Musterfirma GmbH / Anna Beispiel')
->and($result->derived['credit_suggestion_source'])->toBe('credit')
->and($result->suggestedAiDeclaration())->toBeNull()
->and($result->enforcedAiDeclaration())->toBeNull()
->and($result->iptc['byline'])->toBe('Anna Beispiel')
->and($result->iptc['copyright_notice'])->toBe('© 2026 Musterfirma GmbH')
->and($result->fileSha256)->toBe(hash_file('sha256', provenanceFixture('camera-iptc.jpg')));
});
test('xmp trainedAlgorithmicMedia as full iptc uri suggests ai_generated but never locks in phase one', function () {
$result = (new ImageProvenanceInspector)->inspect(provenanceFixture('xmp-ai-generated.jpg'));
expect($result->status)->toBe(ImageProvenanceStatus::UnsignedMetadata)
->and($result->iptc['digital_source_type'])->toBe('trainedAlgorithmicMedia')
->and($result->suggestedAiDeclaration()?->value)->toBe('ai_generated')
->and($result->enforcedAiDeclaration())->toBeNull()
->and($result->creditSuggestion())->toBe('KI-Bild / Musterfirma');
});
test('algorithmicallyEnhanced only sets the enhancement hint and takes the creator list as credit', function () {
$result = (new ImageProvenanceInspector)->inspect(provenanceFixture('xmp-enhanced.jpg'));
expect($result->suggestedAiDeclaration())->toBeNull()
->and($result->derived['enhancement_hint'])->toBeTrue()
->and($result->creditSuggestion())->toBe('Max Mustermann')
->and($result->derived['credit_suggestion_source'])->toBe('creator');
});
test('a png without metadata is absent, not an error', function () {
$result = (new ImageProvenanceInspector)->inspect(provenanceFixture('plain.png'));
expect($result->status)->toBe(ImageProvenanceStatus::Absent)
->and($result->creditSuggestion())->toBeNull()
->and($result->error)->toBeNull();
});
test('webp xmp digitalCapture suggests original and falls back to the copyright notice for the credit', function () {
$result = (new ImageProvenanceInspector)->inspect(provenanceFixture('webp-capture.webp'));
expect($result->status)->toBe(ImageProvenanceStatus::UnsignedMetadata)
->and($result->suggestedAiDeclaration()?->value)->toBe('original')
->and($result->creditSuggestion())->toBe('© Beispiel AG')
->and($result->derived['credit_suggestion_source'])->toBe('copyright_notice');
});
test('an unreadable file reports error instead of pretending there is no metadata', function () {
$result = (new ImageProvenanceInspector)->inspect(provenanceFixture('broken.jpg'));
expect($result->status)->toBe(ImageProvenanceStatus::Error)
->and($result->error)->not->toBeNull()
->and($result->toArray()['error']['class'])->toBe('ImagickException');
});
test('the result serialises with status, tool and duration for the image row', function () {
$array = (new ImageProvenanceInspector)->inspect(provenanceFixture('camera-iptc.jpg'))->toArray();
expect($array)->toHaveKeys(['status', 'file_sha256', 'tool', 'duration_ms', 'iptc', 'exif', 'derived'])
->and($array['tool'])->toStartWith('imagick ');
});