Les administrateurs d'établissement avaient besoin de gérer le calendrier scolaire (FR80) pour que l'EDT et les devoirs respectent automatiquement les jours non travaillés. Sans cette configuration centralisée, chaque module devait gérer indépendamment les contraintes de dates. Le calendrier s'appuie sur l'API data.education.gouv.fr pour importer les vacances officielles par zone (A/B/C) et calcule les 11 jours fériés français (dont les fêtes mobiles liées à Pâques). Les enseignants sont notifiés par email lors de l'ajout d'une journée pédagogique. Un query IsSchoolDay et une validation des dates d'échéance de devoirs permettent aux autres modules de s'intégrer sans couplage direct.
168 lines
5.4 KiB
PHP
168 lines
5.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Unit\Administration\Application\Command\AddPedagogicalDay;
|
|
|
|
use App\Administration\Application\Command\AddPedagogicalDay\AddPedagogicalDayCommand;
|
|
use App\Administration\Application\Command\AddPedagogicalDay\AddPedagogicalDayHandler;
|
|
use App\Administration\Domain\Event\JourneePedagogiqueAjoutee;
|
|
use App\Administration\Domain\Model\SchoolCalendar\CalendarEntryType;
|
|
use App\Administration\Domain\Model\SchoolCalendar\SchoolCalendar;
|
|
use App\Administration\Domain\Model\SchoolClass\AcademicYearId;
|
|
use App\Administration\Infrastructure\Persistence\InMemory\InMemorySchoolCalendarRepository;
|
|
use App\Shared\Domain\Clock;
|
|
use App\Shared\Domain\Tenant\TenantId;
|
|
use DateTimeImmutable;
|
|
use InvalidArgumentException;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
final class AddPedagogicalDayHandlerTest extends TestCase
|
|
{
|
|
private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440001';
|
|
private const string ACADEMIC_YEAR_ID = '550e8400-e29b-41d4-a716-446655440010';
|
|
|
|
private InMemorySchoolCalendarRepository $repository;
|
|
private AddPedagogicalDayHandler $handler;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->repository = new InMemorySchoolCalendarRepository();
|
|
$clock = new class implements Clock {
|
|
public function now(): DateTimeImmutable
|
|
{
|
|
return new DateTimeImmutable('2026-02-17 10:00:00');
|
|
}
|
|
};
|
|
|
|
$this->handler = new AddPedagogicalDayHandler(
|
|
calendarRepository: $this->repository,
|
|
clock: $clock,
|
|
);
|
|
}
|
|
|
|
#[Test]
|
|
public function itAddsPedagogicalDayToExistingCalendar(): void
|
|
{
|
|
$this->seedCalendar();
|
|
|
|
$command = new AddPedagogicalDayCommand(
|
|
tenantId: self::TENANT_ID,
|
|
academicYearId: self::ACADEMIC_YEAR_ID,
|
|
date: '2025-03-14',
|
|
label: 'Formation enseignants',
|
|
);
|
|
|
|
$calendar = ($this->handler)($command);
|
|
|
|
$entries = $calendar->entries();
|
|
$pedagogicalDays = array_filter(
|
|
$entries,
|
|
static fn ($e) => $e->type === CalendarEntryType::PEDAGOGICAL_DAY,
|
|
);
|
|
|
|
self::assertCount(1, $pedagogicalDays);
|
|
$day = array_values($pedagogicalDays)[0];
|
|
self::assertSame('Formation enseignants', $day->label);
|
|
self::assertSame('2025-03-14', $day->startDate->format('Y-m-d'));
|
|
}
|
|
|
|
#[Test]
|
|
public function itCreatesNewCalendarIfNoneExists(): void
|
|
{
|
|
$command = new AddPedagogicalDayCommand(
|
|
tenantId: self::TENANT_ID,
|
|
academicYearId: self::ACADEMIC_YEAR_ID,
|
|
date: '2025-03-14',
|
|
label: 'Formation enseignants',
|
|
);
|
|
|
|
$calendar = ($this->handler)($command);
|
|
|
|
self::assertCount(1, $calendar->entries());
|
|
}
|
|
|
|
#[Test]
|
|
public function itRecordsJourneePedagogiqueAjouteeEvent(): void
|
|
{
|
|
$command = new AddPedagogicalDayCommand(
|
|
tenantId: self::TENANT_ID,
|
|
academicYearId: self::ACADEMIC_YEAR_ID,
|
|
date: '2025-03-14',
|
|
label: 'Formation enseignants',
|
|
);
|
|
|
|
$calendar = ($this->handler)($command);
|
|
|
|
$events = $calendar->pullDomainEvents();
|
|
self::assertCount(1, $events);
|
|
self::assertInstanceOf(JourneePedagogiqueAjoutee::class, $events[0]);
|
|
self::assertSame('Formation enseignants', $events[0]->label);
|
|
}
|
|
|
|
#[Test]
|
|
public function itSavesCalendarWithPedagogicalDay(): void
|
|
{
|
|
$command = new AddPedagogicalDayCommand(
|
|
tenantId: self::TENANT_ID,
|
|
academicYearId: self::ACADEMIC_YEAR_ID,
|
|
date: '2025-03-14',
|
|
label: 'Formation',
|
|
description: 'Journée de formation continue',
|
|
);
|
|
|
|
($this->handler)($command);
|
|
|
|
$saved = $this->repository->getByTenantAndYear(
|
|
TenantId::fromString(self::TENANT_ID),
|
|
AcademicYearId::fromString(self::ACADEMIC_YEAR_ID),
|
|
);
|
|
|
|
self::assertCount(1, $saved->entries());
|
|
self::assertSame('Journée de formation continue', $saved->entries()[0]->description);
|
|
}
|
|
|
|
#[Test]
|
|
public function itRejectsMalformedDate(): void
|
|
{
|
|
$command = new AddPedagogicalDayCommand(
|
|
tenantId: self::TENANT_ID,
|
|
academicYearId: self::ACADEMIC_YEAR_ID,
|
|
date: 'not-a-date',
|
|
label: 'Formation',
|
|
);
|
|
|
|
$this->expectException(InvalidArgumentException::class);
|
|
$this->expectExceptionMessage('La date doit être au format YYYY-MM-DD.');
|
|
|
|
($this->handler)($command);
|
|
}
|
|
|
|
#[Test]
|
|
public function itRejectsImpossibleCalendarDate(): void
|
|
{
|
|
$command = new AddPedagogicalDayCommand(
|
|
tenantId: self::TENANT_ID,
|
|
academicYearId: self::ACADEMIC_YEAR_ID,
|
|
date: '2025-06-31',
|
|
label: 'Formation',
|
|
);
|
|
|
|
$this->expectException(InvalidArgumentException::class);
|
|
$this->expectExceptionMessage('La date n\'existe pas dans le calendrier.');
|
|
|
|
($this->handler)($command);
|
|
}
|
|
|
|
private function seedCalendar(): void
|
|
{
|
|
$calendar = SchoolCalendar::initialiser(
|
|
tenantId: TenantId::fromString(self::TENANT_ID),
|
|
academicYearId: AcademicYearId::fromString(self::ACADEMIC_YEAR_ID),
|
|
);
|
|
|
|
$this->repository->save($calendar);
|
|
}
|
|
}
|