presseportale/tests/Feature/Api/V1/PressReleaseImageApiTest.php
Kevin Adametz 92af015532 API: KI-Deklaration als v1-Pflichtfeld, Legacy-Default fuer /service (Entscheidung 10.07.)
Befund: v1 hatte keine KI-Deklarationsfelder — Submit lief seit der
KI-Kennzeichnungspflicht (09.07.) immer auf 422 und haette alle
Legacy-API-Kunden blockiert (Suite uebersah es: Factory deklariert).

- v1-Submit verlangt ai_text_declaration; der Call gilt dokumentiert
  als verbindliche Bestaetigung (Art. 50 KI-VO) und schliesst die
  Archiv-Kenntnisnahme (AGB par. 13) ein
- Bild-Upload verlangt ai_declaration (original/ai_generated/ai_edited)
- Legacy-/service-Aliasse defaulten auf none/original — das
  API-Kunden-Mailing (R6.4) weist auf die Erklaerungswirkung hin
- OpenAPI v1.yml + lang-Hinweis aktualisiert; Tests erweitert
  (Submit/Image/LegacyCompatibility)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 12:41:24 +00:00

150 lines
5.4 KiB
PHP

<?php
use App\Enums\PressReleaseStatus;
use App\Models\PressRelease;
use App\Models\PressReleaseImage;
use App\Models\User;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Laravel\Sanctum\Sanctum;
use Tests\TestCase;
test('api user can upload list and delete own press release images', function () {
/** @var TestCase $this */
Storage::fake('public');
$user = User::factory()->create();
$pressRelease = PressRelease::factory()->create([
'user_id' => $user->id,
'status' => PressReleaseStatus::Draft->value,
]);
Sanctum::actingAs($user, ['press-releases:read', 'press-release-images:write']);
$uploadResponse = $this->postJson("/api/v1/press-releases/{$pressRelease->id}/images", [
'image' => UploadedFile::fake()->image('press-release.jpg', 1200, 800)->size(5120),
'ai_declaration' => 'original',
'title' => 'Pressefoto',
'is_preview' => true,
]);
// Pressebilder werden auf das Hero-/Cover-Format normalisiert (harte
// Obergrenze 1280x580, siehe ImageService::PRESS_RELEASE_IMAGE_VARIANTS).
// Gespeichert werden daher die Cover-Maße, nicht die Originalgröße.
$uploadResponse
->assertCreated()
->assertJsonPath('data.title', 'Pressefoto')
->assertJsonPath('data.is_preview', true)
->assertJsonPath('data.width', 1280)
->assertJsonPath('data.height', 580)
->assertJsonStructure(['data' => ['variants', 'urls']]);
$image = PressReleaseImage::query()->firstOrFail();
expect(Storage::disk('public')->exists($image->path))->toBeTrue();
expect($image->variants)->toBeArray();
expect($image->variants)->toHaveKeys(['thumb', 'medium', 'large']);
foreach ($image->variants as $variantPath) {
expect(Storage::disk('public')->exists($variantPath))->toBeTrue();
}
$this->getJson("/api/v1/press-releases/{$pressRelease->id}/images")
->assertOk()
->assertJsonPath('data.0.id', $image->id)
->assertJsonPath('data.0.url', asset('storage/'.ltrim($image->path, '/')));
$this->getJson("/api/v1/press-releases/{$pressRelease->id}")
->assertOk()
->assertJsonPath('data.images.0.id', $image->id);
$variantPaths = $image->variants ?? [];
$this->deleteJson("/api/v1/press-release-images/{$image->id}")
->assertNoContent();
expect(Storage::disk('public')->exists($image->path))->toBeFalse();
foreach ($variantPaths as $variantPath) {
expect(Storage::disk('public')->exists($variantPath))->toBeFalse();
}
expect(PressReleaseImage::withTrashed()->find($image->id)?->trashed())->toBeTrue();
});
test('image upload is limited to eight megabytes', function () {
/** @var TestCase $this */
Storage::fake('public');
$user = User::factory()->create();
$pressRelease = PressRelease::factory()->create([
'user_id' => $user->id,
'status' => PressReleaseStatus::Draft->value,
]);
Sanctum::actingAs($user, ['press-release-images:write']);
$this->postJson("/api/v1/press-releases/{$pressRelease->id}/images", [
'image' => UploadedFile::fake()->image('too-large.jpg')->size(8193),
])->assertUnprocessable()
->assertInvalid(['image']);
});
test('api user cannot manage another users press release images', function () {
/** @var TestCase $this */
Storage::fake('public');
$owner = User::factory()->create();
$otherUser = User::factory()->create();
$pressRelease = PressRelease::factory()->create([
'user_id' => $owner->id,
'status' => PressReleaseStatus::Draft->value,
]);
Sanctum::actingAs($otherUser, ['press-releases:read', 'press-release-images:write']);
$this->postJson("/api/v1/press-releases/{$pressRelease->id}/images", [
'image' => UploadedFile::fake()->image('foreign.jpg'),
'ai_declaration' => 'original',
])->assertForbidden();
});
test('published press release images cannot be changed via api', function () {
/** @var TestCase $this */
Storage::fake('public');
$user = User::factory()->create();
$pressRelease = PressRelease::factory()->published()->create([
'user_id' => $user->id,
]);
Sanctum::actingAs($user, ['press-release-images:write']);
$this->postJson("/api/v1/press-releases/{$pressRelease->id}/images", [
'image' => UploadedFile::fake()->image('published.jpg'),
'ai_declaration' => 'original',
])->assertConflict()
->assertJsonPath('message', 'Only draft or rejected press releases may be edited.');
});
test('api image upload requires the per-image ai declaration', function () {
/** @var TestCase $this */
Storage::fake('public');
$user = User::factory()->create();
$pressRelease = PressRelease::factory()->create([
'user_id' => $user->id,
'status' => PressReleaseStatus::Draft->value,
]);
Sanctum::actingAs($user, ['press-release-images:write']);
$this->postJson("/api/v1/press-releases/{$pressRelease->id}/images", [
'image' => UploadedFile::fake()->image('undeklariert.jpg'),
])->assertUnprocessable()
->assertInvalid(['ai_declaration']);
$this->postJson("/api/v1/press-releases/{$pressRelease->id}/images", [
'image' => UploadedFile::fake()->image('ki-bild.jpg'),
'ai_declaration' => 'ai_generated',
])->assertCreated();
expect($pressRelease->images()->first()->ai_declaration?->value)->toBe('ai_generated');
});