Skip to content

[Messenger] Add command completion for failed messages #43663

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
->notPath('Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Custom/_name_entry_label.html.php')
// explicit trigger_error tests
->notPath('Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php')
// stop removing spaces on the end of the line in strings
->notPath('Symfony/Component/Messenger/Tests/Command/FailedMessagesShowCommandTest.php')
)
->setCacheFile('.php-cs-fixer.cache')
;
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
namespace Symfony\Component\Messenger\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Helper\Dumper;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Style\SymfonyStyle;
Expand All @@ -23,6 +25,7 @@
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp;
use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp;
use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface;
use Symfony\Component\Messenger\Transport\Receiver\MessageCountAwareInterface;
use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface;
use Symfony\Component\VarDumper\Caster\Caster;
Expand All @@ -39,6 +42,8 @@
*/
abstract class AbstractFailedMessagesCommand extends Command
{
protected const DEFAULT_TRANSPORT_OPTION = 'choose';

protected $failureTransports;

private $globalFailureReceiverName;
Expand Down Expand Up @@ -263,4 +268,31 @@ protected function interactiveChooseFailureTransport(SymfonyStyle $io)

return $io->askQuestion($question);
}

public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestOptionValuesFor('transport')) {
$suggestions->suggestValues(array_keys($this->failureTransports->getProvidedServices()));

return;
}

if ($input->mustSuggestArgumentValuesFor('id')) {
$transport = $input->getOption('transport');
$transport = self::DEFAULT_TRANSPORT_OPTION === $transport ? $this->getGlobalFailureReceiverName() : $transport;
$receiver = $this->getReceiver($transport);

if (!$receiver instanceof ListableReceiverInterface) {
return;
}

$ids = [];
foreach ($receiver->all(50) as $envelope) {
$ids[] = $this->getMessageId($envelope);
}
$suggestions->suggestValues($ids);

return;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ protected function configure(): void
->setDefinition([
new InputArgument('id', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'Specific message id(s) to remove'),
new InputOption('force', null, InputOption::VALUE_NONE, 'Force the operation without confirmation'),
new InputOption('transport', null, InputOption::VALUE_OPTIONAL, 'Use a specific failure transport'),
new InputOption('transport', null, InputOption::VALUE_OPTIONAL, 'Use a specific failure transport', self::DEFAULT_TRANSPORT_OPTION),
new InputOption('show-messages', null, InputOption::VALUE_NONE, 'Display messages before removing it (if multiple ids are given)'),
])
->setDescription(self::$defaultDescription)
Expand All @@ -61,7 +61,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
$io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output);

$failureTransportName = $input->getOption('transport');
if (null === $failureTransportName) {
if (self::DEFAULT_TRANSPORT_OPTION === $failureTransportName) {
$failureTransportName = $this->getGlobalFailureReceiverName();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@
*/
class FailedMessagesRetryCommand extends AbstractFailedMessagesCommand
{
private const DEFAULT_TRANSPORT_OPTION = 'choose';

protected static $defaultName = 'messenger:failed:retry';
protected static $defaultDescription = 'Retry one or more messages from the failure transport';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@
*/
class FailedMessagesShowCommand extends AbstractFailedMessagesCommand
{
private const DEFAULT_TRANSPORT_OPTION = 'choose';

protected static $defaultName = 'messenger:failed:show';
protected static $defaultDescription = 'Show one or more messages from the failure transport';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@
namespace Symfony\Component\Messenger\Tests\Command;

use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Tester\CommandCompletionTester;
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\Messenger\Command\FailedMessagesRemoveCommand;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\InvalidArgumentException;
use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp;
use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface;

class FailedMessagesRemoveCommandTest extends TestCase
Expand Down Expand Up @@ -268,4 +270,78 @@ public function testRemoveMultipleMessagesAndDisplayMessagesWithServiceLocator()
$this->assertStringContainsString('Message with id 20 removed.', $tester->getDisplay());
$this->assertStringContainsString('Message with id 30 removed.', $tester->getDisplay());
}

public function testCompletingTransport()
{
$globalFailureReceiverName = 'failure_receiver';

$receiver = $this->createMock(ListableReceiverInterface::class);

$serviceLocator = $this->createMock(ServiceLocator::class);
$serviceLocator->expects($this->once())->method('getProvidedServices')->willReturn([
'global_receiver' => $receiver,
$globalFailureReceiverName => $receiver,
]);

$command = new FailedMessagesRemoveCommand(
$globalFailureReceiverName,
$serviceLocator
);
$tester = new CommandCompletionTester($command);

$suggestions = $tester->complete(['--transport']);
$this->assertSame(['global_receiver', 'failure_receiver'], $suggestions);
}

public function testCompleteId()
{
$globalFailureReceiverName = 'failure_receiver';

$receiver = $this->createMock(ListableReceiverInterface::class);
$receiver->expects($this->once())->method('all')->with(50)->willReturn([
Envelope::wrap(new \stdClass(), [new TransportMessageIdStamp('2ab50dfa1fbf')]),
Envelope::wrap(new \stdClass(), [new TransportMessageIdStamp('78c2da843723')]),
]);

$serviceLocator = $this->createMock(ServiceLocator::class);
$serviceLocator->expects($this->once())->method('has')->with($globalFailureReceiverName)->willReturn(true);
$serviceLocator->expects($this->any())->method('get')->with($globalFailureReceiverName)->willReturn($receiver);

$command = new FailedMessagesRemoveCommand(
$globalFailureReceiverName,
$serviceLocator
);
$tester = new CommandCompletionTester($command);

$suggestions = $tester->complete(['']);

$this->assertSame(['2ab50dfa1fbf', '78c2da843723'], $suggestions);
}

public function testCompleteIdWithSpecifiedTransport()
{
$globalFailureReceiverName = 'failure_receiver';
$anotherFailureReceiverName = 'another_receiver';

$receiver = $this->createMock(ListableReceiverInterface::class);
$receiver->expects($this->once())->method('all')->with(50)->willReturn([
Envelope::wrap(new \stdClass(), [new TransportMessageIdStamp('2ab50dfa1fbf')]),
Envelope::wrap(new \stdClass(), [new TransportMessageIdStamp('78c2da843723')]),
]);

$serviceLocator = $this->createMock(ServiceLocator::class);
$serviceLocator->expects($this->once())->method('has')->with($anotherFailureReceiverName)->willReturn(true);
$serviceLocator->expects($this->any())->method('get')->with($anotherFailureReceiverName)->willReturn($receiver);

$command = new FailedMessagesRemoveCommand(
$globalFailureReceiverName,
$serviceLocator
);

$tester = new CommandCompletionTester($command);

$suggestions = $tester->complete(['--transport', $anotherFailureReceiverName, ' ']);

$this->assertSame(['2ab50dfa1fbf', '78c2da843723'], $suggestions);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@
namespace Symfony\Component\Messenger\Tests\Command;

use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Tester\CommandCompletionTester;
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Messenger\Command\FailedMessagesRetryCommand;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp;
use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface;

class FailedMessagesRetryCommandTest extends TestCase
Expand Down Expand Up @@ -144,4 +146,83 @@ public function testBasicRunWithServiceLocatorWithSpecificFailureTransport()

$this->assertStringContainsString('[OK]', $tester->getDisplay());
}

public function testCompletingTransport()
{
$globalFailureReceiverName = 'failure_receiver';

$receiver = $this->createMock(ListableReceiverInterface::class);

$serviceLocator = $this->createMock(ServiceLocator::class);
$serviceLocator->expects($this->once())->method('getProvidedServices')->willReturn([
'global_receiver' => $receiver,
$globalFailureReceiverName => $receiver,
]);

$command = new FailedMessagesRetryCommand(
$globalFailureReceiverName,
$serviceLocator,
$this->createMock(MessageBusInterface::class),
new EventDispatcher()
);
$tester = new CommandCompletionTester($command);

$suggestions = $tester->complete(['--transport']);
$this->assertSame(['global_receiver', 'failure_receiver'], $suggestions);
}

public function testCompleteId()
{
$globalFailureReceiverName = 'failure_receiver';

$receiver = $this->createMock(ListableReceiverInterface::class);
$receiver->expects($this->once())->method('all')->with(50)->willReturn([
Envelope::wrap(new \stdClass(), [new TransportMessageIdStamp('2ab50dfa1fbf')]),
Envelope::wrap(new \stdClass(), [new TransportMessageIdStamp('78c2da843723')]),
]);

$serviceLocator = $this->createMock(ServiceLocator::class);
$serviceLocator->expects($this->once())->method('has')->with($globalFailureReceiverName)->willReturn(true);
$serviceLocator->expects($this->any())->method('get')->with($globalFailureReceiverName)->willReturn($receiver);

$command = new FailedMessagesRetryCommand(
$globalFailureReceiverName,
$serviceLocator,
$this->createMock(MessageBusInterface::class),
new EventDispatcher()
);
$tester = new CommandCompletionTester($command);

$suggestions = $tester->complete(['']);

$this->assertSame(['2ab50dfa1fbf', '78c2da843723'], $suggestions);
}

public function testCompleteIdWithSpecifiedTransport()
{
$globalFailureReceiverName = 'failure_receiver';
$anotherFailureReceiverName = 'another_receiver';

$receiver = $this->createMock(ListableReceiverInterface::class);
$receiver->expects($this->once())->method('all')->with(50)->willReturn([
Envelope::wrap(new \stdClass(), [new TransportMessageIdStamp('2ab50dfa1fbf')]),
Envelope::wrap(new \stdClass(), [new TransportMessageIdStamp('78c2da843723')]),
]);

$serviceLocator = $this->createMock(ServiceLocator::class);
$serviceLocator->expects($this->once())->method('has')->with($anotherFailureReceiverName)->willReturn(true);
$serviceLocator->expects($this->any())->method('get')->with($anotherFailureReceiverName)->willReturn($receiver);

$command = new FailedMessagesRetryCommand(
$globalFailureReceiverName,
$serviceLocator,
$this->createMock(MessageBusInterface::class),
new EventDispatcher()
);
$tester = new CommandCompletionTester($command);

$suggestions = $tester->complete(['--transport', $anotherFailureReceiverName, ' ']);

$this->assertSame(['2ab50dfa1fbf', '78c2da843723'], $suggestions);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Tester\CommandCompletionTester;
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\Messenger\Command\FailedMessagesShowCommand;
Expand Down Expand Up @@ -607,4 +608,78 @@ public function testListMessagesWithServiceLocatorFromSpecificTransport()
$redeliveryStamp->getRedeliveredAt()->format('Y-m-d H:i:s')),
$tester->getDisplay(true));
}


public function testCompletingTransport()
{
$globalFailureReceiverName = 'failure_receiver';

$receiver = $this->createMock(ListableReceiverInterface::class);

$serviceLocator = $this->createMock(ServiceLocator::class);
$serviceLocator->expects($this->once())->method('getProvidedServices')->willReturn([
'global_receiver' => $receiver,
$globalFailureReceiverName => $receiver,
]);

$command = new FailedMessagesShowCommand(
$globalFailureReceiverName,
$serviceLocator
);
$tester = new CommandCompletionTester($command);

$suggestions = $tester->complete(['--transport']);
$this->assertSame(['global_receiver', 'failure_receiver'], $suggestions);
}

public function testCompleteId()
{
$globalFailureReceiverName = 'failure_receiver';

$receiver = $this->createMock(ListableReceiverInterface::class);
$receiver->expects($this->once())->method('all')->with(50)->willReturn([
Envelope::wrap(new \stdClass(), [new TransportMessageIdStamp('2ab50dfa1fbf')]),
Envelope::wrap(new \stdClass(), [new TransportMessageIdStamp('78c2da843723')]),
]);

$serviceLocator = $this->createMock(ServiceLocator::class);
$serviceLocator->expects($this->once())->method('has')->with($globalFailureReceiverName)->willReturn(true);
$serviceLocator->expects($this->any())->method('get')->with($globalFailureReceiverName)->willReturn($receiver);

$command = new FailedMessagesShowCommand(
$globalFailureReceiverName,
$serviceLocator
);
$tester = new CommandCompletionTester($command);

$suggestions = $tester->complete(['']);

$this->assertSame(['2ab50dfa1fbf', '78c2da843723'], $suggestions);
}

public function testCompleteIdWithSpecifiedTransport()
{
$globalFailureReceiverName = 'failure_receiver';
$anotherFailureReceiverName = 'another_receiver';

$receiver = $this->createMock(ListableReceiverInterface::class);
$receiver->expects($this->once())->method('all')->with(50)->willReturn([
Envelope::wrap(new \stdClass(), [new TransportMessageIdStamp('2ab50dfa1fbf')]),
Envelope::wrap(new \stdClass(), [new TransportMessageIdStamp('78c2da843723')]),
]);

$serviceLocator = $this->createMock(ServiceLocator::class);
$serviceLocator->expects($this->once())->method('has')->with($anotherFailureReceiverName)->willReturn(true);
$serviceLocator->expects($this->any())->method('get')->with($anotherFailureReceiverName)->willReturn($receiver);

$command = new FailedMessagesShowCommand(
$globalFailureReceiverName,
$serviceLocator
);
$tester = new CommandCompletionTester($command);

$suggestions = $tester->complete(['--transport', $anotherFailureReceiverName, ' ']);

$this->assertSame(['2ab50dfa1fbf', '78c2da843723'], $suggestions);
}
}