Phase 8 (Rest) + Umbauten vom 10./11.06.: - Ein Titelbild pro PM (Cover 1280x580), SVG-Platzhalter-Set + Picker, PressReleaseCoverImage-Resolver - Lizenz-/Rechteformular nach "Lizenztyp Bildupload" (7 Lizenztypen, Personen-/Sachrechte-Status, bedingte Pflichtfelder, Risikohinweise) - Veroeffentlichungs-Box vereinfacht (Embargo aus der Form-UI entfernt), geplante Termine in Europe/Berlin (Speicherung UTC, DISPLAY_TIMEZONE) - Quota-Stub (users.press_release_quota) + monatlicher Reset-Command - Einreichungs-Modal einheitlich in Show/Create/Edit; Ghost-Buttons auf filled; PM-Editor-Layout responsive entkoppelt (.pr-editor-layout) KI-Pruef-Pipeline (Phasen 1-5 des Entwicklungsplans): - API-Haertung: status nicht mehr per API setzbar, eigene Submit-Route durch denselben Funnel (Blacklist, Quota, Status-Log) - Klassifikation Rot/Gelb/Gruen asynchron (Queue classification, OpenAI-Treiber + deterministischer Fallback), ki_audits-Audit-Log - Routing: Rot -> rejected + Mail, Gelb -> Review-Queue, Gruen -> Auto-Publish; Scheduler publiziert nur gruene faellige PMs - Content-Score 0-100 -> Stufe (Standard/Geprueft/Hochwertig) inkl. Editor-Panel und Badges; Re-Klassifikation/-Score bei Aenderung - Admin: KI-Badge + Filter, On-Demand-Pruefung mit Anbieter-Override Suite: 442 passed, 4 skipped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
122 lines
4.4 KiB
PHP
122 lines
4.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),
|
|
'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'),
|
|
])->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'),
|
|
])->assertConflict()
|
|
->assertJsonPath('message', 'Only draft or rejected press releases may be edited.');
|
|
});
|