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