seed(RolesAndPermissionsSeeder::class); }); /** * Default-Testhost ist „localhost" → der Controller löst auf Businessportal24 * auf. Daher die PM bewusst für dieses Portal veröffentlichen. */ function publishedRelease(): PressRelease { return PressRelease::factory() ->published() ->forPortal(Portal::Businessportal24) ->create(); } test('the legal request form renders with per-type hints and hidden honeypots', function () { /** @var TestCase $this */ $release = publishedRelease(); $this->get(route('legal-request.create', ['slug' => $release->slug, 'type' => 'report'])) ->assertOk() ->assertSee($release->title) // Hinweistexte je Typ sind serverseitig vorhanden (Umschaltung via Alpine); // finaler Wortlaut folgt dem Meldeverfahren v0.1 (R2.5, 10.07.2026). ->assertSee('Art. 17 DSGVO') ->assertSee('Persönlichkeitsrecht') ->assertSee('Eingangsbestätigung mit Vorgangsnummer') ->assertSee('kostenlos') // Querverweis auf die Meldeverfahren-Übersicht. ->assertSee(route('melden')) // Versteckte Honeypot-Felder im DOM. ->assertSee('name="website"', false) ->assertSee('name="homepage"', false); }); test('the triage offers the portal magic-link path', function () { /** @var TestCase $this */ $release = publishedRelease(); $this->get(route('legal-request.create', ['slug' => $release->slug, 'type' => 'dsgvo'])) ->assertOk() // Stufe 1: Anliegen-Karten inkl. Absender-Selbstservice. ->assertSee('Korrektur, Änderung oder Depublizierung') ->assertSee('Rechtswidrige Inhalte melden') // Passwortloser Anmeldelink ins Portal (PMs werden im Konto zusammengeführt). ->assertSee('Anmeldelink im Portal anfordern') ->assertSee(rtrim(config('domains.domain_portal_url', config('app.url')), '/').'/anmeldelink', false) // Fallback nur mit Identifizierungs-Hinweis. ->assertSee('persönliche Identifizierung erforderlich') // Vorauswahl aus dem Query-Parameter. ->assertSee("concern: 'dsgvo'", false); }); test('owner concerns preselect the self-service card', function () { /** @var TestCase $this */ $release = publishedRelease(); $this->get(route('legal-request.create', ['slug' => $release->slug, 'type' => 'depublication'])) ->assertOk() ->assertSee("concern: 'owner'", false); }); test('owner track types are rejected by the public store endpoint', function () { /** @var TestCase $this */ Mail::fake(); $release = publishedRelease(); $this->from(route('legal-request.create', ['slug' => $release->slug])) ->post(route('legal-request.store', ['slug' => $release->slug]), [ 'type' => 'correction', 'message' => 'Bitte den Titel dieser Mitteilung komplett austauschen.', ])->assertSessionHasErrors('type'); expect(LegalRequest::query()->count())->toBe(0); }); test('the report intake prefills the reported url from the query string', function () { /** @var TestCase $this */ $detailUrl = 'https://presseecho.test/de/press-release/beispiel-pm'; $this->get(route('melden.pm', ['edition' => 'de', 'reported_url' => $detailUrl])) ->assertOk() ->assertSee('value="'.$detailUrl.'"', false); }); test('the form 404s for a non-published release', function () { /** @var TestCase $this */ $draft = PressRelease::factory()->forPortal(Portal::Businessportal24)->create(); $this->get(route('legal-request.create', ['slug' => $draft->slug])) ->assertNotFound(); }); test('a dsgvo request is stored in the queue', function () { /** @var TestCase $this */ Mail::fake(); $release = publishedRelease(); $this->post(route('legal-request.store', ['slug' => $release->slug]), [ 'type' => LegalRequestType::Dsgvo->value, 'requester_email' => 'betroffen@example.test', 'message' => 'Bitte meine personenbezogenen Daten anonymisieren.', ])->assertRedirect(route('release.detail', ['slug' => $release->slug])) ->assertSessionHas('legal-status'); $request = LegalRequest::query()->firstOrFail(); expect($request->type)->toBe(LegalRequestType::Dsgvo); expect($request->status)->toBe(LegalRequestStatus::Open); expect($request->press_release_id)->toBe($release->id); expect($request->requester_email)->toBe('betroffen@example.test'); expect($request->portal)->toBe(Portal::Businessportal24->value); // T10: Vorgangsnummer im Hinweis + Eingangsbestätigung/Queue-Hinweis. expect(session('legal-status'))->toContain($request->reference()); Mail::assertQueued(LegalRequestReceived::class, fn ($mail) => $mail->hasTo('betroffen@example.test')); Mail::assertQueued(LegalRequestSubmitted::class); }); test('a personal rights request requires an email', function () { /** @var TestCase $this */ $release = publishedRelease(); $this->post(route('legal-request.store', ['slug' => $release->slug]), [ 'type' => LegalRequestType::PersonalRights->value, 'message' => 'Diese Meldung verletzt meine Persönlichkeitsrechte.', ])->assertSessionHasErrors('requester_email'); expect(LegalRequest::query()->count())->toBe(0); }); test('a report can be submitted without an email and feeds the same queue', function () { /** @var TestCase $this */ Mail::fake(); $release = publishedRelease(); $this->post(route('legal-request.store', ['slug' => $release->slug]), [ 'type' => LegalRequestType::Report->value, 'message' => 'Diese Pressemitteilung wirkt wie Spam.', ])->assertRedirect(route('release.detail', ['slug' => $release->slug])); $request = LegalRequest::query()->firstOrFail(); expect($request->type)->toBe(LegalRequestType::Report); expect($request->requester_email)->toBeNull(); // Ohne E-Mail keine Eingangsbestätigung — der interne Hinweis geht trotzdem raus. Mail::assertNotQueued(LegalRequestReceived::class); Mail::assertQueued(LegalRequestSubmitted::class); }); test('a filled honeypot field blocks bots without creating a request', function (string $field) { /** @var TestCase $this */ $release = publishedRelease(); // Soll: still und neutral abnicken — kein Validierungsfehler, der einem // Bot die Falle verrät, sondern der normale Erfolgs-Redirect. $this->post(route('legal-request.store', ['slug' => $release->slug]), [ 'type' => LegalRequestType::Report->value, 'message' => 'Spam spam spam spam.', $field => 'http://spam.example', ]) ->assertSessionHasNoErrors() ->assertRedirect(route('release.detail', ['slug' => $release->slug])); expect(LegalRequest::query()->count())->toBe(0); })->with(['website', 'homepage']); test('only one open request per release and type, other types still allowed', function () { /** @var TestCase $this */ $release = publishedRelease(); LegalRequest::factory() ->type(LegalRequestType::Dsgvo) ->create(['press_release_id' => $release->id, 'status' => LegalRequestStatus::Open]); // Zweite offene DSGVO-Anfrage → keine Dublette. $this->post(route('legal-request.store', ['slug' => $release->slug]), [ 'type' => LegalRequestType::Dsgvo->value, 'requester_email' => 'noch@example.test', 'message' => 'Nochmal dieselbe Anfrage bitte bearbeiten.', ]); expect(LegalRequest::query()->where('type', LegalRequestType::Dsgvo->value)->count())->toBe(1); // Anderer Typ bleibt möglich. $this->post(route('legal-request.store', ['slug' => $release->slug]), [ 'type' => LegalRequestType::Report->value, 'message' => 'Andere Art von Anliegen zu dieser PM.', ]); expect(LegalRequest::query()->where('type', LegalRequestType::Report->value)->count())->toBe(1); }); test('the admin queue lists open requests', function () { /** @var TestCase $this */ $admin = User::factory()->create(['is_active' => true]); $admin->assignRole('admin'); // Kurzer, fixer Titel: die Queue kürzt Titel via Str::limit(…, 60). $release = PressRelease::factory() ->published() ->forPortal(Portal::Businessportal24) ->create(['title' => 'Compliance-Test PM']); LegalRequest::factory() ->type(LegalRequestType::PersonalRights) ->create(['press_release_id' => $release->id, 'status' => LegalRequestStatus::Open]); Volt::actingAs($admin) ->test('admin.legal-requests.index') ->assertOk() ->assertSee('Compliance-Test PM') ->assertSee(LegalRequestType::PersonalRights->label()); }); test('an admin can resolve a request with a note', function () { /** @var TestCase $this */ $admin = User::factory()->create(['is_active' => true]); $admin->assignRole('admin'); $legalRequest = LegalRequest::factory() ->type(LegalRequestType::Dsgvo) ->create(['status' => LegalRequestStatus::Open]); Volt::actingAs($admin) ->test('admin.legal-requests.show', ['id' => $legalRequest->id]) ->set('adminNote', 'Daten anonymisiert.') ->call('resolve'); $legalRequest->refresh(); expect($legalRequest->status)->toBe(LegalRequestStatus::Resolved); expect($legalRequest->resolved_by_user_id)->toBe($admin->id); expect($legalRequest->resolved_at)->not->toBeNull(); expect($legalRequest->admin_note)->toBe('Daten anonymisiert.'); }); test('an admin can reject a request', function () { /** @var TestCase $this */ $admin = User::factory()->create(['is_active' => true]); $admin->assignRole('admin'); $legalRequest = LegalRequest::factory() ->type(LegalRequestType::Report) ->create(['status' => LegalRequestStatus::Open]); Volt::actingAs($admin) ->test('admin.legal-requests.show', ['id' => $legalRequest->id]) ->call('reject'); expect($legalRequest->fresh()->status)->toBe(LegalRequestStatus::Rejected); });