Add failure handling for export generation errors

Introduce `OnExportGenerationFails` subscriber to handle export generation failures. It logs errors, updates the export status to failure, and records generation errors. Added tests to validate the new functionality.
This commit is contained in:
Julien Fastré 2025-04-05 00:07:08 +02:00
parent e48bec490c
commit d1d6a00ebf
Signed by: julienfastre
GPG Key ID: BDE2190974723FCB
3 changed files with 138 additions and 0 deletions

View File

@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Export\Messenger;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\MainBundle\Entity\ExportGeneration;
use Chill\MainBundle\Repository\ExportGenerationRepository;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
class OnExportGenerationFails implements EventSubscriberInterface
{
private const LOG_PREFIX = '[export_generation failed] ';
public function __construct(
private LoggerInterface $logger,
private ExportGenerationRepository $repository,
private EntityManagerInterface $entityManager,
) {}
public static function getSubscribedEvents()
{
return [
WorkerMessageFailedEvent::class => 'onMessageFailed',
];
}
public function onMessageFailed(WorkerMessageFailedEvent $event): void
{
if ($event->willRetry()) {
return;
}
$message = $event->getEnvelope()->getMessage();
if (!$message instanceof ExportRequestGenerationMessage) {
return;
}
if (null === $exportGeneration = $this->repository->find($message->id)) {
throw new \UnexpectedValueException('ExportRequestGenerationMessage not found');
}
$this->logger->error(self::LOG_PREFIX.'ExportRequestGenerationMessage failed to execute generation', [
'exportId' => $message->id,
'userId' => $message->userId,
'alias' => $exportGeneration->getExportAlias(),
'throwable_message' => $event->getThrowable()->getMessage(),
'throwable_trace' => $event->getThrowable()->getTraceAsString(),
'throwable' => get_class($event->getThrowable()),
'full_generation_duration_failure' => microtime(true) - $exportGeneration->getCreatedAt()->getTimestamp(),
]);
$this->markObjectAsFailed($event, $exportGeneration);
$this->entityManager->flush();
}
private function markObjectAsFailed(WorkerMessageFailedEvent $event, ExportGeneration $exportGeneration): void
{
$exportGeneration->getStoredObject()->addGenerationErrors($event->getThrowable()->getMessage());
$exportGeneration->getStoredObject()->setStatus(StoredObject::STATUS_FAILURE);
}
}

View File

@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Tests\Export\Messenger;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\MainBundle\Entity\ExportGeneration;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Export\Exception\ExportGenerationException;
use Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessage;
use Chill\MainBundle\Export\Messenger\OnExportGenerationFails;
use Chill\MainBundle\Repository\ExportGenerationRepository;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use Psr\Log\NullLogger;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
/**
* @internal
*
* @coversNothing
*/
class OnExportGenerationFailsTest extends TestCase
{
use ProphecyTrait;
public function testOnExportGenerationFails(): void
{
$exportGeneration = new ExportGeneration('dummy');
$exportGeneration->setCreatedAt(new \DateTimeImmutable('10 seconds ago'));
$repository = $this->prophesize(ExportGenerationRepository::class);
$repository->find($exportGeneration->getId())->willReturn($exportGeneration);
$entityManager = $this->prophesize(EntityManagerInterface::class);
$entityManager->flush()->shouldBeCalled();
$user = $this->prophesize(User::class);
$user->getId()->willReturn(1);
$subscriber = new OnExportGenerationFails(new NullLogger(), $repository->reveal(), $entityManager->reveal());
$subscriber->onMessageFailed(new WorkerMessageFailedEvent(
new Envelope(new ExportRequestGenerationMessage($exportGeneration, $user->reveal())),
'dummyReceiver',
new ExportGenerationException('dummy_exception'),
));
self::assertEquals(StoredObject::STATUS_FAILURE, $exportGeneration->getStoredObject()->getStatus());
self::assertStringContainsString('dummy_exception', $exportGeneration->getStoredObject()->getGenerationErrors());
}
}

View File

@ -8,6 +8,8 @@ services:
Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessageHandler: ~
Chill\MainBundle\Export\Messenger\OnExportGenerationFails: ~
Chill\MainBundle\Export\ExportFormHelper: ~
Chill\MainBundle\Export\ExportGenerator: ~