Les événements métier (emails d'invitation, reset password, activation) bloquaient la réponse API en étant traités de manière synchrone. Ce commit route ces événements vers un transport AMQP asynchrone avec un worker dédié, garantissant des réponses API rapides et une gestion robuste des échecs. Le retry utilise une stratégie Fibonacci (1s, 1s, 2s, 3s, 5s, 8s, 13s) qui offre un bon compromis entre réactivité et protection des services externes. Les messages qui épuisent leurs tentatives arrivent dans une dead-letter queue Doctrine avec alerte email à l'admin. La commande console CreateTestActivationTokenCommand détecte désormais les comptes déjà actifs et génère un token de réinitialisation de mot de passe au lieu d'un token d'activation, évitant une erreur bloquante lors de la ré-invitation par un admin.
132 lines
4.7 KiB
PHP
132 lines
4.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Unit\Shared\Infrastructure\Console;
|
|
|
|
use App\Shared\Infrastructure\Console\ReviewFailedMessagesCommand;
|
|
use Doctrine\DBAL\Connection;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use PHPUnit\Framework\MockObject\MockObject;
|
|
use PHPUnit\Framework\TestCase;
|
|
use Symfony\Component\Console\Command\Command;
|
|
use Symfony\Component\Console\Tester\CommandTester;
|
|
|
|
final class ReviewFailedMessagesCommandTest extends TestCase
|
|
{
|
|
private Connection&MockObject $connection;
|
|
private CommandTester $commandTester;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->connection = $this->createMock(Connection::class);
|
|
$command = new ReviewFailedMessagesCommand($this->connection);
|
|
$this->commandTester = new CommandTester($command);
|
|
}
|
|
|
|
#[Test]
|
|
public function listShowsSuccessWhenNoFailedMessages(): void
|
|
{
|
|
$this->connection->method('fetchAllAssociative')->willReturn([]);
|
|
|
|
$this->commandTester->execute([]);
|
|
|
|
self::assertSame(Command::SUCCESS, $this->commandTester->getStatusCode());
|
|
self::assertStringContainsString('Aucun message en echec', $this->commandTester->getDisplay());
|
|
}
|
|
|
|
#[Test]
|
|
public function listDisplaysMessagesTable(): void
|
|
{
|
|
$this->connection->method('fetchAllAssociative')->willReturn([
|
|
['id' => 1, 'headers' => '{"type":"App\\\\Test\\\\FooEvent"}', 'created_at' => '2026-02-08 10:00:00'],
|
|
['id' => 2, 'headers' => '{"type":"App\\\\Test\\\\BarEvent"}', 'created_at' => '2026-02-08 11:00:00'],
|
|
]);
|
|
|
|
$this->commandTester->execute([]);
|
|
|
|
$output = $this->commandTester->getDisplay();
|
|
self::assertSame(Command::SUCCESS, $this->commandTester->getStatusCode());
|
|
self::assertStringContainsString('2 message(s) en echec', $output);
|
|
self::assertStringContainsString('FooEvent', $output);
|
|
self::assertStringContainsString('messenger:failed:retry', $output);
|
|
}
|
|
|
|
#[Test]
|
|
public function listRejectsInvalidLimit(): void
|
|
{
|
|
$this->commandTester->execute(['--limit' => '0']);
|
|
|
|
self::assertSame(Command::FAILURE, $this->commandTester->getStatusCode());
|
|
self::assertStringContainsString('entier positif', $this->commandTester->getDisplay());
|
|
}
|
|
|
|
#[Test]
|
|
public function listRejectsNegativeLimit(): void
|
|
{
|
|
$this->commandTester->execute(['--limit' => '-5']);
|
|
|
|
self::assertSame(Command::FAILURE, $this->commandTester->getStatusCode());
|
|
}
|
|
|
|
#[Test]
|
|
public function showRequiresIdOption(): void
|
|
{
|
|
$this->commandTester->execute(['action' => 'show']);
|
|
|
|
self::assertSame(Command::FAILURE, $this->commandTester->getStatusCode());
|
|
self::assertStringContainsString('--id est requise', $this->commandTester->getDisplay());
|
|
}
|
|
|
|
#[Test]
|
|
public function showRejectsNonNumericId(): void
|
|
{
|
|
$this->commandTester->execute(['action' => 'show', '--id' => 'abc']);
|
|
|
|
self::assertSame(Command::FAILURE, $this->commandTester->getStatusCode());
|
|
self::assertStringContainsString('entier positif', $this->commandTester->getDisplay());
|
|
}
|
|
|
|
#[Test]
|
|
public function showReturnsFailureWhenMessageNotFound(): void
|
|
{
|
|
$this->connection->method('fetchAssociative')->willReturn(false);
|
|
|
|
$this->commandTester->execute(['action' => 'show', '--id' => '999']);
|
|
|
|
self::assertSame(Command::FAILURE, $this->commandTester->getStatusCode());
|
|
self::assertStringContainsString('introuvable', $this->commandTester->getDisplay());
|
|
}
|
|
|
|
#[Test]
|
|
public function showDisplaysMessageDetails(): void
|
|
{
|
|
$this->connection->method('fetchAssociative')->willReturn([
|
|
'id' => 42,
|
|
'headers' => '{"type":"App\\\\Test\\\\FooEvent"}',
|
|
'body' => '{}',
|
|
'queue_name' => 'failed',
|
|
'created_at' => '2026-02-08 10:00:00',
|
|
'delivered_at' => null,
|
|
'available_at' => '2026-02-08 10:00:00',
|
|
]);
|
|
|
|
$this->commandTester->execute(['action' => 'show', '--id' => '42']);
|
|
|
|
$output = $this->commandTester->getDisplay();
|
|
self::assertSame(Command::SUCCESS, $this->commandTester->getStatusCode());
|
|
self::assertStringContainsString('Message #42', $output);
|
|
self::assertStringContainsString('FooEvent', $output);
|
|
self::assertStringContainsString('messenger:failed:retry 42', $output);
|
|
}
|
|
|
|
#[Test]
|
|
public function invalidActionReturnsFailure(): void
|
|
{
|
|
$this->commandTester->execute(['action' => 'explode']);
|
|
|
|
self::assertSame(Command::FAILURE, $this->commandTester->getStatusCode());
|
|
self::assertStringContainsString('Action inconnue', $this->commandTester->getDisplay());
|
|
}
|
|
}
|