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>
This commit is contained in:
parent
b624f80d7f
commit
e9bdff3ad3
36 changed files with 2631 additions and 30 deletions
77
tests/Feature/Images/ImageProvenanceInspectorTest.php
Normal file
77
tests/Feature/Images/ImageProvenanceInspectorTest.php
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
<?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 ');
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue