All checks were successful
CI / secret-scan (pull_request) Successful in 15s
CI / php-lint (pull_request) Successful in 28s
CI / static-tests (pull_request) Successful in 1m6s
CI / js-tests (pull_request) Successful in 40s
CI / secret-scan (push) Successful in 14s
CI / php-lint (push) Successful in 33s
CI / static-tests (push) Successful in 1m11s
CI / js-tests (push) Successful in 38s
Client SMTP maison (zero lib, contrainte from-scratch) : ESMTP + STARTTLS + AUTH LOGIN, conduit par SmtpClient contre un SmtpTransport injectable (seam de test). SmtpMailer assemble un message text/plain UTF-8 (dot-stuffing, en-tetes RFC2047) et implemente l'interface Mailer existante. PasswordResetController choisit SmtpMailer si SMTP_HOST+USER+PASSWORD presents, sinon garde LogMailer (dev sans infra mail inchange). STARTTLS exige avant AUTH (pas d'auth en clair). Garde anti-injection CRLF sur les adresses (SmtpClient) + filter_var du destinataire (SmtpMailer). readReply borne (anti-boucle sur reponse malformee). Secrets uniquement en .env (hote) : placeholders dans .env.example / .env.prod.example, rien de versionne. Revue compliance : verdict block initial (injection CRLF + readReply non borne), 2 must_fix corriges + tests de regression. 8 tests SMTP, 429 total, PHPStan L6.
68 lines
2.6 KiB
PHP
68 lines
2.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Unit\Auth;
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
use App\Auth\SmtpClient;
|
|
use App\Auth\SmtpMailer;
|
|
use App\Tests\Support\FakeSmtpTransport;
|
|
|
|
final class SmtpMailerTest extends TestCase
|
|
{
|
|
/** @return list<string> sequence nominale de reponses serveur */
|
|
private function happyReplies(): array
|
|
{
|
|
return [
|
|
"220 ready\r\n", "250 ok\r\n", "220 go\r\n", "250 ok\r\n",
|
|
"334 u\r\n", "334 p\r\n", "235 ok\r\n", "250 ok\r\n", "250 ok\r\n",
|
|
"354 data\r\n", "250 queued\r\n", "221 bye\r\n",
|
|
];
|
|
}
|
|
|
|
private function mailer(FakeSmtpTransport $t): SmtpMailer
|
|
{
|
|
return new SmtpMailer(
|
|
new SmtpClient($t),
|
|
'smtp-relay.brevo.com',
|
|
587,
|
|
'login@smtp-brevo.com',
|
|
'secret',
|
|
'noreply@a3n.fr',
|
|
'Wakdo',
|
|
);
|
|
}
|
|
|
|
public function testBuildsAndSendsResetMessage(): void
|
|
{
|
|
$t = new FakeSmtpTransport($this->happyReplies());
|
|
$this->mailer($t)->sendPasswordReset('client@example.fr', 'https://corentin-wakdo-admin.stark.a3n.fr/reset_password?token=abc');
|
|
|
|
$sent = $t->written();
|
|
self::assertStringContainsString('From: Wakdo <noreply@a3n.fr>', $sent);
|
|
self::assertStringContainsString('To: <client@example.fr>', $sent);
|
|
self::assertStringContainsString('Subject: Reinitialisation de votre mot de passe Wakdo', $sent);
|
|
self::assertStringContainsString('Content-Type: text/plain; charset=UTF-8', $sent);
|
|
self::assertStringContainsString('https://corentin-wakdo-admin.stark.a3n.fr/reset_password?token=abc', $sent);
|
|
// L'enveloppe SMTP doit porter l'expediteur et le destinataire reels.
|
|
self::assertStringContainsString('MAIL FROM:<noreply@a3n.fr>', $sent);
|
|
self::assertStringContainsString('RCPT TO:<client@example.fr>', $sent);
|
|
}
|
|
|
|
public function testRejectsInvalidRecipient(): void
|
|
{
|
|
$t = new FakeSmtpTransport($this->happyReplies());
|
|
$this->expectException(\RuntimeException::class);
|
|
$this->mailer($t)->sendPasswordReset("victim@x.fr\r\nBcc: evil@x.com", 'https://x/reset?token=t');
|
|
}
|
|
|
|
public function testHeaderAndBodySeparatedByBlankLine(): void
|
|
{
|
|
$t = new FakeSmtpTransport($this->happyReplies());
|
|
$this->mailer($t)->sendPasswordReset('c@e.fr', 'https://x/reset?token=t');
|
|
|
|
// En-tetes et corps separes par une ligne vide (CRLF CRLF).
|
|
self::assertStringContainsString("Content-Transfer-Encoding: 8bit\r\n\r\nBonjour,", $t->written());
|
|
}
|
|
}
|