Roadmap 6.17. Veröffentlichungs- und Ablehnungsmails zu API-Meldungen gehen an Pressekontakt oder Firmen-E-Mail — Empfänger ohne (bekanntes) Konto. Die Mail sagte „in Ihrem Konto … mit Passwort oder Magic-Link“, gelesen wurde „geht nicht“. Jetzt ein Kasten mit der Adresse und dem Knopf „Anmeldelink anfordern“: signierter Link (30 Tage, Adresse verschlüsselt) auf /zugang, ein Klick schickt den Anmeldelink, die Anmeldung landet auf der Meldung. Ablehnung zu API-Meldungen: nur ansehen, neu eingereicht wird auf dem ursprünglichen Weg. Kunden mit Konto: Knopf „Im pressekonto öffnen“. Stufen 2 und 3 des Trichters messen die neue Seite mit. Changelog: Verbessert Public: Wer eine Veröffentlichungs- oder Ablehnungsmail bekommt, erhält mit einem Klick einen Anmeldelink an genau diese Adresse – ohne Konto und ohne Passwort. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
157 lines
6.8 KiB
PHP
157 lines
6.8 KiB
PHP
<?php
|
|
|
|
use App\Enums\ConversionEventType;
|
|
use App\Enums\ConversionSource;
|
|
use App\Enums\Portal;
|
|
use App\Mail\MagicLoginLink;
|
|
use App\Mail\PressReleasePublished;
|
|
use App\Mail\PressReleaseRejected;
|
|
use App\Models\Company;
|
|
use App\Models\ConversionEvent;
|
|
use App\Models\MagicLink;
|
|
use App\Models\PressRelease;
|
|
use App\Models\User;
|
|
use App\Services\Auth\MailAccessLink;
|
|
use Database\Seeders\RolesAndPermissionsSeeder;
|
|
use Illuminate\Support\Facades\Mail;
|
|
use Livewire\Livewire;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* Anmeldelink an die Adresse einer Kundenmail (Roadmap 6.17): Empfänger von
|
|
* API-Meldungen haben aus ihrer Sicht kein Konto. Ein Klick in der Mail
|
|
* schickt den Anmeldelink an genau ihre Adresse, die Anmeldung landet auf
|
|
* der Meldung.
|
|
*/
|
|
beforeEach(function (): void {
|
|
/** @var TestCase $this */
|
|
$this->seed(RolesAndPermissionsSeeder::class);
|
|
Mail::fake();
|
|
});
|
|
|
|
function apiRelease(array $attributes = []): PressRelease
|
|
{
|
|
$company = Company::factory()->businessportal24()->create([
|
|
'name' => 'Firma GmbH', 'slug' => 'firma-gmbh', 'email' => 'presse@firma.de', 'is_active' => true,
|
|
]);
|
|
|
|
return PressRelease::factory()->published()->forPortal(Portal::Businessportal24)->create(array_merge([
|
|
'company_id' => $company->id, 'language' => 'de', 'title' => 'Firma GmbH eröffnet Standort',
|
|
], $attributes));
|
|
}
|
|
|
|
/**
|
|
* @return array{t: string, src: string}
|
|
*/
|
|
function accessQuery(string $url): array
|
|
{
|
|
parse_str((string) parse_url($url, PHP_URL_QUERY), $query);
|
|
|
|
return ['t' => (string) $query['t'], 'src' => (string) $query['src']];
|
|
}
|
|
|
|
// ------------------------------------------------------------------ Mails
|
|
|
|
test('the publication mail offers a sign-in link to the address it went to when the recipient has no account', function () {
|
|
/** @var TestCase $this */
|
|
$release = apiRelease();
|
|
$mail = new PressReleasePublished($release->user, $release, 'Frau Muster', 'presse@firma.de', true);
|
|
$html = $mail->render();
|
|
|
|
expect($mail->accessEmail)->toBe('presse@firma.de')
|
|
->and($mail->accessUrl)->toContain('/zugang?')
|
|
->and($mail->accessUrl)->not->toContain('presse@firma.de')
|
|
->and($mail->accessUrl)->not->toContain('presse%40firma.de')
|
|
->and($html)->toContain('Etwas ändern oder die Aufrufe sehen?')
|
|
->and($html)->toContain('weder ein Konto noch ein Passwort')
|
|
->and($html)->toContain('presse@firma.de')
|
|
->and($html)->toContain('den Newsroom von Firma GmbH pflegen')
|
|
->and($html)->toContain('Anmeldelink anfordern')
|
|
->and($html)->not->toContain('Im pressekonto öffnen')
|
|
->and($html)->not->toContain('mit Ihrem Passwort oder per Magic-Link');
|
|
});
|
|
|
|
test('the rejection mail lets recipients without an account view the release, resubmission stays with the original channel', function () {
|
|
/** @var TestCase $this */
|
|
$release = apiRelease(['status' => 'rejected']);
|
|
$html = (new PressReleaseRejected($release->user, $release, 'Kein Anlass erkennbar.', 'Frau Muster', 'presse@firma.de'))->render();
|
|
|
|
expect($html)->toContain('ohne Konto und ohne Passwort ansehen')
|
|
->and($html)->toContain('auf demselben Weg ein wie diese Meldung')
|
|
->and($html)->toContain('Anmeldelink anfordern')
|
|
->and($html)->not->toContain('Pressemitteilung bearbeiten');
|
|
|
|
// Wer selbst eingereicht hat, überarbeitet wie bisher im Konto.
|
|
$own = (new PressReleaseRejected($release->user, $release, 'Kein Anlass erkennbar.'))->render();
|
|
expect($own)->toContain('Pressemitteilung bearbeiten')
|
|
->and($own)->not->toContain('Anmeldelink anfordern');
|
|
});
|
|
|
|
// --------------------------------------------------------- Zugangsseite
|
|
|
|
test('the access page only opens with a valid signature and shows the address masked', function () {
|
|
/** @var TestCase $this */
|
|
$release = apiRelease();
|
|
$url = app(MailAccessLink::class)->url('presse@firma.de', $release, ConversionSource::MailPublish);
|
|
|
|
$this->get($url)
|
|
->assertOk()
|
|
->assertSeeText('p***@firma.de')
|
|
->assertSeeText('Firma GmbH eröffnet Standort')
|
|
->assertDontSeeText('presse@firma.de');
|
|
|
|
$this->get(str_replace('src=mail_publish', 'src=mail_reject', $url))->assertForbidden();
|
|
|
|
$opened = ConversionEvent::query()->where('type', ConversionEventType::ManagePageOpened->value)->sole();
|
|
expect($opened->source)->toBe(ConversionSource::MailPublish)
|
|
->and($opened->press_release_id)->toBe($release->id);
|
|
});
|
|
|
|
test('one click sends the sign-in link to that address and the login lands on the release', function () {
|
|
/** @var TestCase $this */
|
|
$release = apiRelease();
|
|
$url = app(MailAccessLink::class)->url('presse@firma.de', $release, ConversionSource::MailPublish);
|
|
|
|
Livewire::withQueryParams(accessQuery($url))
|
|
->test('auth.mail-access')
|
|
->call('requestLink')
|
|
->assertSet('sent', true)
|
|
->assertSee('p***@firma.de');
|
|
|
|
$user = User::query()->where('email', 'presse@firma.de')->firstOrFail();
|
|
Mail::assertQueued(MagicLoginLink::class, fn (MagicLoginLink $mail): bool => $mail->user->is($user));
|
|
|
|
$link = MagicLink::query()->where('user_id', $user->id)->latest('id')->firstOrFail();
|
|
expect($link->source)->toBe(ConversionSource::MailPublish->value)
|
|
->and($link->payload)->toBe(['company_id' => $release->company_id, 'press_release_id' => $release->id])
|
|
->and(ConversionEvent::query()->where('type', ConversionEventType::AccessRequested->value)->sole()->company_id)->toBe($release->company_id);
|
|
|
|
$plain = str_replace('/magic-login/', '', parse_url(Mail::queued(MagicLoginLink::class)->first()->loginUrl, PHP_URL_PATH));
|
|
$this->get('https://pressekonto.test/magic-login/'.ltrim($plain, '/'))
|
|
->assertRedirect(route('me.press-releases.show', ['id' => $release->id], absolute: false));
|
|
});
|
|
|
|
test('requests per address are limited', function () {
|
|
/** @var TestCase $this */
|
|
$release = apiRelease();
|
|
$query = accessQuery(app(MailAccessLink::class)->url('presse@firma.de', $release, ConversionSource::MailReject));
|
|
|
|
foreach (range(1, 3) as $i) {
|
|
Livewire::withQueryParams($query)->test('auth.mail-access')->call('requestLink')->assertHasNoErrors();
|
|
}
|
|
|
|
Livewire::withQueryParams($query)->test('auth.mail-access')->call('requestLink')->assertHasErrors('link');
|
|
Mail::assertQueuedCount(3);
|
|
});
|
|
|
|
test('a signed-in recipient goes straight to the release', function () {
|
|
/** @var TestCase $this */
|
|
$release = apiRelease();
|
|
$user = User::factory()->create(['email' => 'presse@firma.de', 'is_active' => true]);
|
|
$user->assignRole('customer');
|
|
$user->companies()->attach($release->company_id, ['role' => 'responsible']);
|
|
|
|
$this->actingAs($user)
|
|
->get(app(MailAccessLink::class)->url('presse@firma.de', $release, ConversionSource::MailPublish))
|
|
->assertRedirect(route('me.press-releases.show', ['id' => $release->id]));
|
|
});
|