CluPilotCloud/tests/Feature/ContactRequestTest.php

221 lines
8.5 KiB
PHP

<?php
use App\Mail\ContactRequestMail;
use App\Models\Customer;
use App\Models\InboundMail;
use Illuminate\Support\Facades\Mail;
/**
* Das Anfrageformular der öffentlichen Website.
*
* Es steht offen im Internet und verschickt eine Mail — die Grenzen sind hier
* das Wesentliche, nicht die Felder.
*/
beforeEach(function () {
Mail::fake();
});
it('zeigt das Formular', function () {
$this->get(route('contact'))
->assertOk()
->assertSee('Sprechen Sie uns an.')
->assertSee('Anfrage senden');
});
it('stellt beim Thema Enterprise die Frage, um die es geht', function () {
$this->get(route('contact', ['thema' => 'enterprise']))
->assertOk()
->assertSee('Eine eigene Maschine.')
// Nicht ein leeres Feld hinstellen: wer hier landet, kam über den Knopf
// unter den Paketen, und die zwei Zahlen entscheiden das Angebot.
->assertSee('Wie viel Speicher brauchen Sie', false);
});
it('fällt bei einem erfundenen Thema auf die allgemeine Fassung zurück', function () {
// ?thema= steht in der Adresse und ist damit von jedem frei wählbar.
$this->get(route('contact', ['thema' => '<script>']))
->assertOk()
->assertSee('Sprechen Sie uns an.');
});
it('schickt die Anfrage an das Haus und lässt die Antwort an den Fragenden gehen', function () {
$this->post(route('contact.send'), [
'company' => 'Kanzlei Muster',
'name' => 'Anna Muster',
'email' => 'anna@kanzlei-muster.at',
'phone' => '+43 1 2345678',
'message' => 'Wir haben 40 Mitarbeiter und brauchen etwa 400 GB.',
'topic' => 'enterprise',
])->assertRedirect(route('contact'));
Mail::assertQueued(ContactRequestMail::class, function (ContactRequestMail $mail) {
$envelope = $mail->envelope();
// Der Punkt der ganzen Mail: der Betreiber drückt „Antworten" und
// schreibt der Person, nicht sich selbst.
expect($envelope->replyTo[0]->address)->toBe('anna@kanzlei-muster.at');
// Gesendet wird AUS dem eigenen Haus, nicht aus der fremden Domain —
// sonst scheitert die Mail an SPF und DKIM und landet im Spam.
expect($envelope->from->address)->not->toBe('anna@kanzlei-muster.at');
// Betreff trägt Thema und Absender, damit die Postfachliste allein
// schon sagt, worum es geht.
expect($envelope->subject)->toBe('Anfrage: eigene Maschine — Kanzlei Muster');
return true;
});
});
it('nimmt den Namen in den Betreff, wenn keine Firma angegeben ist', function () {
$this->post(route('contact.send'), [
'name' => 'Bernd Beispiel',
'email' => 'bernd@example.at',
'message' => 'Ich hätte da eine Frage zu Ihren Paketen.',
]);
Mail::assertQueued(ContactRequestMail::class, fn (ContactRequestMail $mail) => $mail->envelope()->subject === 'Anfrage über die Website — Bernd Beispiel');
});
it('bestätigt sichtbar und zeigt das Formular danach nicht mehr', function () {
$this->post(route('contact.send'), [
'name' => 'Anna Muster',
'email' => 'anna@example.at',
'message' => 'Bitte melden Sie sich bei mir.',
]);
$this->followingRedirects()
->post(route('contact.send'), [
'name' => 'Anna Muster',
'email' => 'anna@example.at',
'message' => 'Bitte melden Sie sich bei mir.',
])
->assertOk()
->assertSee('Ihre Anfrage ist da.')
// Ein Formular, das nach dem Absenden weiter dasteht, lädt zum zweiten
// Absenden ein.
->assertDontSee('Anfrage senden');
});
it('verlangt Name, Adresse und Nachricht', function () {
$this->post(route('contact.send'), [])
->assertSessionHasErrors(['name', 'email', 'message']);
Mail::assertNothingQueued();
});
it('lässt eine Nachricht aus zwei Wörtern nicht durch', function () {
$this->post(route('contact.send'), [
'name' => 'Anna', 'email' => 'anna@example.at', 'message' => 'Hallo',
])->assertSessionHasErrors(['message']);
Mail::assertNothingQueued();
});
it('verwirft, was in den Honigtopf getappt ist', function () {
// Das Feld ist im Browser nicht zu sehen. Wer es ausfüllt, ist kein Mensch.
$this->post(route('contact.send'), [
'name' => 'Robot', 'email' => 'robot@example.com',
'message' => 'Kaufen Sie unsere Backlinks, sehr günstig.',
'website' => 'http://spam.example.com',
])->assertSessionHasErrors(['website']);
Mail::assertNothingQueued();
});
it('bremst den Versand, statt ihn offen stehen zu lassen', function () {
// Ein Formular, das ohne Bremse Mail verschickt, ist ein Versandweg für
// jeden, der es findet. Fünf in zehn Minuten, danach 429.
$payload = ['name' => 'Anna', 'email' => 'anna@example.at', 'message' => 'Eine ganz normale Anfrage.'];
for ($i = 0; $i < 5; $i++) {
$this->post(route('contact.send'), $payload)->assertRedirect();
}
$this->post(route('contact.send'), $payload)->assertStatus(429);
Mail::assertQueuedCount(5);
});
it('legt die Anfrage in den Posteingang der Konsole, nicht nur in eine Mail', function () {
$this->post(route('contact.send'), [
'company' => 'Kanzlei Muster',
'name' => 'Anna Muster',
'email' => 'anna@kanzlei-muster.at',
'phone' => '+43 1 2345678',
'message' => 'Wir haben 40 Mitarbeiter und brauchen etwa 400 GB.',
'topic' => 'enterprise',
]);
$eintrag = InboundMail::query()->latest('id')->first();
expect($eintrag)->not->toBeNull()
->and($eintrag->from_email)->toBe('anna@kanzlei-muster.at')
->and($eintrag->from_name)->toBe('Anna Muster · Kanzlei Muster')
->and($eintrag->subject)->toBe('Anfrage: eigene Maschine — Kanzlei Muster')
->and($eintrag->body)->toContain('40 Mitarbeiter')
// `inbound_mails` hat kein Feld für die Nummer. Sie im Formular zu
// verlieren hieße, den Fragenden nicht anrufen zu können.
->and($eintrag->body)->toContain('+43 1 2345678')
// Eine fremde Adresse bleibt ohne Zuordnung — und steht damit im
// Posteingang ganz oben, wo eine neue Anfrage hingehört.
->and($eintrag->customer_id)->toBeNull();
});
it('hängt die Anfrage an den Kunden, wenn die Adresse schon bekannt ist', function () {
$kunde = Customer::factory()->create(['email' => 'bestand@example.at']);
$this->post(route('contact.send'), [
'name' => 'Bestandskunde',
'email' => 'bestand@example.at',
'message' => 'Ich brauche mehr Speicher als mein Paket hergibt.',
]);
expect(InboundMail::query()->latest('id')->value('customer_id'))->toBe($kunde->id);
});
it('behält die Anfrage, auch wenn der Versand scheitert', function () {
// Der Fall, der das alles ausgelöst hat: support@ hatte kein eigenes
// SMTP-Konto, der Server wies den Absender ab, die Anfrage lag in
// `failed_jobs` — und der Besucher hatte „Ihre Anfrage ist da" gelesen.
Mail::shouldReceive('send')->once()->andThrow(new RuntimeException('553 Sender address rejected'));
// Der Versand fliegt dem Besucher um die Ohren, statt ihm eine Bestätigung
// für etwas zu zeigen, das nicht angekommen ist.
$this->post(route('contact.send'), [
'name' => 'Anna Muster',
'email' => 'anna@example.at',
'message' => 'Bitte melden Sie sich bei mir.',
])->assertStatus(500);
// Festgehalten, bevor überhaupt ein Versand versucht wurde.
expect(InboundMail::query()->where('from_email', 'anna@example.at')->exists())->toBeTrue();
});
it('gibt zwei Anfragen zwei verschiedene Kennungen', function () {
// message_id ist eindeutig. Eine zweite Anfrage darf nicht an der
// Datenbank scheitern, weil beide dieselbe synthetische Kennung tragen.
foreach (['eins@example.at', 'zwei@example.at'] as $adresse) {
$this->post(route('contact.send'), [
'name' => 'Anna', 'email' => $adresse, 'message' => 'Eine ganz normale Anfrage.',
])->assertRedirect();
}
expect(InboundMail::query()->count())->toBe(2);
});
it('trägt fremdes Markup nicht in die Mail, die unsere Adresse als Absender hat', function () {
$this->post(route('contact.send'), [
'name' => 'Anna Muster',
'email' => 'anna@example.at',
'message' => "Guten Tag\n<script>alert('x')</script>\nMit freundlichen Grüßen",
]);
Mail::assertQueued(ContactRequestMail::class, function (ContactRequestMail $mail) {
$html = $mail->render();
return ! str_contains($html, "<script>alert('x')</script>")
&& str_contains($html, '&lt;script&gt;');
});
});