178 lines
5.8 KiB
PHP
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));
|
|
}
|
|
}
|