Add tests for DailyNotificationDigestCronJob and improve robustness of execution state handling

- Introduced unit tests for `DailyNotificationDigestCronJob` to ensure correct dispatching and state handling.
- Improved handling of invalid execution state by adding fallback to the previous day.
- Updated date formatting in the returned state to use `DateTimeInterface::ATOM` for consistency.
This commit is contained in:
2025-10-15 11:00:31 +02:00
parent 9c304ed6d3
commit 6a764353f9
2 changed files with 140 additions and 2 deletions

View File

@@ -53,11 +53,16 @@ readonly class DailyNotificationDigestCronjob implements CronJobInterface
public function run(array $lastExecutionData): ?array
{
$now = $this->clock->now();
if (isset($lastExecutionData['last_execution'])) {
$lastExecution = \DateTimeImmutable::createFromFormat(
\DateTimeImmutable::ATOM,
$lastExecutionData['last_execution']
);
if (false === $lastExecution) {
$lastExecution = $now->sub(new \DateInterval('P1D'));
}
} else {
$lastExecution = $now->sub(new \DateInterval('P1D'));
}
@@ -96,7 +101,7 @@ readonly class DailyNotificationDigestCronjob implements CronJobInterface
]);
return [
'last_execution' => $now->format('Y-m-d-H:i:s.u e'),
'last_execution' => $now->format(\DateTimeInterface::ATOM),
];
}
}

View File

@@ -12,16 +12,21 @@ declare(strict_types=1);
namespace Chill\MainBundle\Tests\Notification\Email;
use Chill\MainBundle\Notification\Email\DailyNotificationDigestCronjob;
use Chill\MainBundle\Notification\Email\NotificationEmailMessages\ScheduleDailyNotificationDigestMessage;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Result;
use Doctrine\DBAL\Statement;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Symfony\Component\Clock\ClockInterface;
use Symfony\Component\Clock\MockClock;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\MessageBusInterface;
/**
* @internal
*
* @coversNothing
* @covers \DailyNotificationDigestCronjob
*/
class DailyNotificationDigestCronJobTest extends TestCase
{
@@ -30,6 +35,7 @@ class DailyNotificationDigestCronJobTest extends TestCase
private MessageBusInterface $messageBus;
private LoggerInterface $logger;
private DailyNotificationDigestCronjob $cronjob;
private \DateTimeImmutable $firstNow;
protected function setUp(): void
{
@@ -38,6 +44,8 @@ class DailyNotificationDigestCronJobTest extends TestCase
$this->messageBus = $this->createMock(MessageBusInterface::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->firstNow = new \DateTimeImmutable('2024-01-02T07:15:00+00:00');
$this->cronjob = new DailyNotificationDigestCronjob(
$this->clock,
$this->connection,
@@ -78,4 +86,129 @@ class DailyNotificationDigestCronJobTest extends TestCase
'hour 23 - should not run' => [23, false],
];
}
public function testRunFirstExecutionReturnsStateAndDispatches(): array
{
// Use MockClock for deterministic time
$firstNow = $this->firstNow;
$clock = new MockClock($firstNow);
// Mock DBAL statement/result
$statement = $this->createMock(Statement::class);
$result = $this->createMock(Result::class);
$this->connection->method('prepare')->willReturn($statement);
$statement->method('bindValue')->willReturnSelf();
$statement->method('executeQuery')->willReturn($result);
$rows = [
['user_id' => 10],
['user_id' => 42],
];
$result->method('fetchAllAssociative')->willReturn($rows);
$dispatched = [];
$this->messageBus->method('dispatch')->willReturnCallback(function ($message) use (&$dispatched) {
$dispatched[] = $message;
return new Envelope($message);
});
$cron = new DailyNotificationDigestCronjob($clock, $this->connection, $this->messageBus, $this->logger);
$state = $cron->run([]);
// Assert dispatch count and message contents
self::assertCount(2, $dispatched);
$expectedLast = $firstNow->sub(new \DateInterval('P1D'));
foreach ($dispatched as $i => $msg) {
self::assertInstanceOf(ScheduleDailyNotificationDigestMessage::class, $msg);
self::assertTrue(in_array($msg->getUserId(), [10, 42], true));
self::assertEquals($firstNow, $msg->getCurrentDateTime(), 'compare the current date');
self::assertEquals($expectedLast, $msg->getLastExecutionDateTime(), 'compare the last execution date');
}
// Assert returned state
self::assertIsArray($state);
self::assertArrayHasKey('last_execution', $state);
self::assertSame($firstNow->format(\DateTimeInterface::ATOM), $state['last_execution']);
return $state;
}
/**
* @depends testRunFirstExecutionReturnsStateAndDispatches
*/
public function testRunSecondExecutionUsesPreviousState(array $previousState): void
{
$firstNow = $this->firstNow;
$secondNow = $firstNow->add(new \DateInterval('P1D'));
$clock = new MockClock($secondNow);
// Mock DBAL for a single user this time
$statement = $this->createMock(Statement::class);
$result = $this->createMock(Result::class);
$this->connection->method('prepare')->willReturn($statement);
$statement->method('bindValue')->willReturnSelf();
$statement->method('executeQuery')->willReturn($result);
$rows = [
['user_id' => 7],
];
$result->method('fetchAllAssociative')->willReturn($rows);
$captured = [];
$this->messageBus->method('dispatch')->willReturnCallback(function ($message) use (&$captured) {
$captured[] = $message;
return new Envelope($message);
});
$cron = new DailyNotificationDigestCronjob($clock, $this->connection, $this->messageBus, $this->logger);
$cron->run($previousState);
self::assertCount(1, $captured);
$msg = $captured[0];
self::assertInstanceOf(ScheduleDailyNotificationDigestMessage::class, $msg);
self::assertEquals(7, $msg->getUserId());
self::assertEquals($secondNow, $msg->getCurrentDateTime(), 'compare the current date');
self::assertEquals($firstNow, $msg->getLastExecutionDateTime(), 'compare the last execution date');
}
public function testRunWithInvalidExecutionState(): void
{
$firstNow = $this->firstNow;
$previousExpected = $firstNow->sub(new \DateInterval('P1D'));
$clock = new MockClock($firstNow);
// Mock DBAL for a single user this time
$statement = $this->createMock(Statement::class);
$result = $this->createMock(Result::class);
$this->connection->method('prepare')->willReturn($statement);
$statement->method('bindValue')->willReturnSelf();
$statement->method('executeQuery')->willReturn($result);
$rows = [
['user_id' => 7],
];
$result->method('fetchAllAssociative')->willReturn($rows);
$captured = [];
$this->messageBus->method('dispatch')->willReturnCallback(function ($message) use (&$captured) {
$captured[] = $message;
return new Envelope($message);
});
$cron = new DailyNotificationDigestCronjob($clock, $this->connection, $this->messageBus, $this->logger);
$cron->run(['last_execution' => 'invalid data']);
self::assertCount(1, $captured);
$msg = $captured[0];
self::assertInstanceOf(ScheduleDailyNotificationDigestMessage::class, $msg);
self::assertEquals(7, $msg->getUserId());
self::assertEquals($firstNow, $msg->getCurrentDateTime(), 'compare the current date');
self::assertEquals($previousExpected, $msg->getLastExecutionDateTime(), 'compare the last execution date');
}
}