- Publish/Reject/Archive und changeStatus erzwingen konsistent press-releases:publish; changeStatus schloss die Lücke, über die das Status-Modal die Prüfung umging (Fix#1/H2). - publish()/changeStatusFromAdmin sperren PM- und User-Zeile transaktional; QuotaExceededException wird in Admin-Publish und im Auto-Publish-Job abgefangen (Warnung statt 500 / bleibt in Review, Fix#3/M2). - Slug-Erzeugung nur noch über HasUniqueSlug (generateUniqueSlug + saveWithUniqueSlugRetry), redundanter Controller-Helper entfernt (M3). - API-/Legacy-Text wird vor der Persistenz sanitisiert (M6); Scheduler/Legacy-Tracking ignorieren soft-gelöschte PMs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
111 lines
3.9 KiB
PHP
111 lines
3.9 KiB
PHP
<?php
|
||
|
||
use App\Models\Category;
|
||
use App\Models\Company;
|
||
use App\Models\PressRelease;
|
||
use App\Models\User;
|
||
use Laravel\Sanctum\Sanctum;
|
||
use Tests\TestCase;
|
||
|
||
test('api user can create list update and delete own press releases', function () {
|
||
/** @var TestCase $this */
|
||
$user = User::factory()->create();
|
||
$company = Company::factory()->presseecho()->create();
|
||
$category = Category::factory()->withTranslations()->create();
|
||
$user->companies()->attach($company->id, ['role' => 'owner']);
|
||
|
||
Sanctum::actingAs($user, ['press-releases:read', 'press-releases:write']);
|
||
|
||
$createResponse = $this->postJson('/api/v1/press-releases', [
|
||
'company_id' => $company->id,
|
||
'category_id' => $category->id,
|
||
'language' => 'de',
|
||
'title' => 'Neue API Pressemitteilung',
|
||
'text' => 'Inhalt der Pressemitteilung',
|
||
'status' => 'draft',
|
||
]);
|
||
|
||
$createResponse
|
||
->assertCreated()
|
||
->assertJsonPath('data.title', 'Neue API Pressemitteilung')
|
||
->assertJsonPath('data.portal', 'presseecho');
|
||
|
||
$pressReleaseId = PressRelease::withoutGlobalScopes()
|
||
->where('title', 'Neue API Pressemitteilung')
|
||
->value('id');
|
||
|
||
$this->getJson('/api/v1/press-releases')
|
||
->assertOk()
|
||
->assertJsonPath('data.0.id', $pressReleaseId);
|
||
|
||
$this->patchJson("/api/v1/press-releases/{$pressReleaseId}", [
|
||
'title' => 'Aktualisierte API Pressemitteilung',
|
||
])
|
||
->assertOk()
|
||
->assertJsonPath('data.title', 'Aktualisierte API Pressemitteilung');
|
||
|
||
$this->deleteJson("/api/v1/press-releases/{$pressReleaseId}")
|
||
->assertNoContent();
|
||
|
||
expect(PressRelease::withoutGlobalScopes()->withTrashed()->find($pressReleaseId)?->trashed())->toBeTrue();
|
||
});
|
||
|
||
test('api submitted html text is sanitized before persistence', function () {
|
||
/**
|
||
* Regression zu Befund M6 (Audit 14.07.2026): Über die API eingereichtes
|
||
* HTML muss – wie im Web-Formular – vor der Speicherung durch den Sanitizer.
|
||
*/
|
||
/** @var TestCase $this */
|
||
$user = User::factory()->create();
|
||
$company = Company::factory()->presseecho()->create();
|
||
$category = Category::factory()->withTranslations()->create();
|
||
$user->companies()->attach($company->id, ['role' => 'owner']);
|
||
|
||
Sanctum::actingAs($user, ['press-releases:read', 'press-releases:write']);
|
||
|
||
$this->postJson('/api/v1/press-releases', [
|
||
'company_id' => $company->id,
|
||
'category_id' => $category->id,
|
||
'language' => 'de',
|
||
'title' => 'XSS Versuch',
|
||
'text' => '<p>Sauber</p><script>alert(document.cookie)</script><a href="javascript:alert(1)">x</a>',
|
||
'status' => 'draft',
|
||
])->assertCreated();
|
||
|
||
$stored = PressRelease::withoutGlobalScopes()->where('title', 'XSS Versuch')->firstOrFail();
|
||
|
||
expect($stored->text)
|
||
->not->toContain('<script')
|
||
->not->toContain('javascript:')
|
||
->toContain('<p>Sauber</p>');
|
||
});
|
||
|
||
test('api write ability is required to create press releases', function () {
|
||
/** @var TestCase $this */
|
||
$user = User::factory()->create();
|
||
$company = Company::factory()->presseecho()->create();
|
||
$category = Category::factory()->withTranslations()->create();
|
||
$user->companies()->attach($company->id, ['role' => 'owner']);
|
||
|
||
Sanctum::actingAs($user, ['press-releases:read']);
|
||
|
||
$this->postJson('/api/v1/press-releases', [
|
||
'company_id' => $company->id,
|
||
'category_id' => $category->id,
|
||
'language' => 'de',
|
||
'title' => 'Nicht erlaubt',
|
||
'text' => 'Inhalt',
|
||
])->assertForbidden();
|
||
});
|
||
|
||
test('api user cannot access another users press release', function () {
|
||
/** @var TestCase $this */
|
||
$owner = User::factory()->create();
|
||
$otherUser = User::factory()->create();
|
||
$pressRelease = PressRelease::factory()->create(['user_id' => $owner->id]);
|
||
|
||
Sanctum::actingAs($otherUser, ['press-releases:read']);
|
||
|
||
$this->getJson("/api/v1/press-releases/{$pressRelease->id}")
|
||
->assertForbidden();
|
||
});
|