Files
myep-team/tests/Service/Cron/UploadReminderServiceTest.php
T

178 lines
5.8 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Tests\Service\Cron;
use App\Config\EmailTextKey;
use App\Email\Mailer;
use App\Entity\Application;
use App\Entity\Assignment;
use App\Entity\Destination;
use App\Entity\Disposition;
use App\Entity\Embeddable\Communication;
use App\Entity\Teamer;
use App\Repository\DispositionRepository;
use App\Service\Cron\UploadReminderService;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\NullLogger;
/**
* Pins the reminder rules themselves: which day each reminder targets, and which wording it uses.
*
* The queries behind them are covered in DispositionRepositoryTest; what is asserted here is the
* arithmetic the service hands over, because a reminder that asks for the wrong day is just as
* silent as the broken queries these replaced.
*/
class UploadReminderServiceTest extends TestCase
{
private const CONTRACT_DEADLINE_DAYS = 5;
private const INVOICE_DEADLINE_DAYS = 14;
private DispositionRepository&MockObject $repository;
private Mailer&MockObject $mailer;
private UploadReminderService $service;
protected function setUp(): void
{
$this->repository = $this->createMock(DispositionRepository::class);
$this->mailer = $this->createMock(Mailer::class);
$this->service = new UploadReminderService(
$this->repository,
$this->mailer,
new NullLogger(),
self::CONTRACT_DEADLINE_DAYS,
self::INVOICE_DEADLINE_DAYS,
);
}
public function testTheContractReminderAsksForTheConfiguredPeriod(): void
{
$this->repository
->expects($this->once())
->method('findDispositionsForContractReminder')
->with(self::CONTRACT_DEADLINE_DAYS)
->willReturn([])
;
$this->assertSame(
'No due contract upload reminders to be sent to teamers',
$this->service->sendContractUploadReminders()
);
}
public function testTheContractReminderMailsEachTeamerOnce(): void
{
$this->repository
->method('findDispositionsForContractReminder')
->willReturn([$this->createDisposition(), $this->createDisposition()])
;
$this->mailer
->expects($this->exactly(2))
->method('createAndSendText')
->with(EmailTextKey::REMINDER_CONTRACT_UPLOAD, $this->anything(), $this->anything())
;
$this->assertSame(
'Sent 2 due contract upload reminders to teamers',
$this->service->sendContractUploadReminders()
);
}
/**
* The first reminder goes out the day after the assignment ended, the second on the last day
* of the upload period. Both are anchored on the end date, so each targets exactly one day and
* the cron can run twice without mailing anyone twice.
*/
public function testTheTwoInvoiceRemindersTargetTheDayAfterTheEndAndTheDeadline(): void
{
$today = new \DateTimeImmutable('today');
$requested = [];
$this->repository
->expects($this->exactly(2))
->method('findDispositionsForInvoiceReminder')
->willReturnCallback(function (\DateTimeImmutable $endDate) use (&$requested): array {
$requested[] = $endDate->format('Y-m-d');
return [];
})
;
$this->service->sendInvoiceUploadReminders();
$this->assertSame([
$today->modify('-1 day')->format('Y-m-d'),
$today->modify('-'.self::INVOICE_DEADLINE_DAYS.' days')->format('Y-m-d'),
], $requested);
}
/**
* The second mail must not repeat "within the next 14 days" - by then the period is over.
*/
public function testTheSecondInvoiceReminderUsesTheFinalWording(): void
{
$this->repository
->method('findDispositionsForInvoiceReminder')
->willReturnOnConsecutiveCalls([$this->createDisposition()], [$this->createDisposition()])
;
$keys = [];
$this->mailer
->method('createAndSendText')
->willReturnCallback(function (EmailTextKey $key) use (&$keys): void {
$keys[] = $key;
})
;
$message = $this->service->sendInvoiceUploadReminders();
$this->assertSame([
EmailTextKey::REMINDER_INVOICE_UPLOAD,
EmailTextKey::REMINDER_INVOICE_UPLOAD_FINAL,
], $keys);
$this->assertSame('Sent 2 due invoice upload reminders to teamers', $message);
}
public function testTheInvoiceReminderPassesTheDeadlineToTheWording(): void
{
$this->repository
->method('findDispositionsForInvoiceReminder')
->willReturnOnConsecutiveCalls([$this->createDisposition()], [])
;
$this->mailer
->expects($this->once())
->method('createAndSendText')
->with(
EmailTextKey::REMINDER_INVOICE_UPLOAD,
$this->callback(fn (array $placeholders): bool => self::INVOICE_DEADLINE_DAYS === $placeholders['invoiceUploadDeadlineDays']
&& 'Skireise' === substr((string) $placeholders['destination'], -8)),
['to' => '[email protected]'],
)
;
$this->service->sendInvoiceUploadReminders();
}
private function createDisposition(): Disposition
{
$destination = (new Destination())
->setProduct('Skireise')
->setDateFrom(new \DateTimeImmutable('2026-03-01'))
->setDateTo(new \DateTimeImmutable('2026-03-14'))
;
$assignment = (new Assignment())->setDestination($destination);
$teamer = (new Teamer())->setCommunication(
(new Communication())->setEmail('[email protected]')
);
return new Disposition(new Application($assignment, $teamer));
}
}