Lorsqu'un super-admin crée un établissement via l'interface, le système doit automatiquement créer la base tenant, exécuter les migrations, créer le premier utilisateur admin et envoyer l'invitation — le tout de manière asynchrone pour ne pas bloquer la réponse HTTP. Ce mécanisme rend chaque établissement opérationnel dès sa création sans intervention manuelle sur l'infrastructure.
262 lines
9.1 KiB
PHP
262 lines
9.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Unit\Scolarite\Application\Query\GetBlockedDates;
|
|
|
|
use App\Administration\Domain\Model\SchoolCalendar\CalendarEntry;
|
|
use App\Administration\Domain\Model\SchoolCalendar\CalendarEntryId;
|
|
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\Scolarite\Application\Port\HomeworkRulesChecker;
|
|
use App\Scolarite\Application\Port\HomeworkRulesCheckResult;
|
|
use App\Scolarite\Application\Query\GetBlockedDates\GetBlockedDatesHandler;
|
|
use App\Scolarite\Application\Query\GetBlockedDates\GetBlockedDatesQuery;
|
|
use App\Shared\Domain\Clock;
|
|
use App\Shared\Domain\Tenant\TenantId;
|
|
use DateTimeImmutable;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
final class GetBlockedDatesHandlerTest extends TestCase
|
|
{
|
|
private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440001';
|
|
private const string ACADEMIC_YEAR_ID = '550e8400-e29b-41d4-a716-446655440050';
|
|
|
|
private InMemorySchoolCalendarRepository $calendarRepository;
|
|
private GetBlockedDatesHandler $handler;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->calendarRepository = new InMemorySchoolCalendarRepository();
|
|
|
|
$rulesChecker = new class implements HomeworkRulesChecker {
|
|
public function verifier(
|
|
TenantId $tenantId,
|
|
DateTimeImmutable $dueDate,
|
|
DateTimeImmutable $creationDate,
|
|
): HomeworkRulesCheckResult {
|
|
return HomeworkRulesCheckResult::ok();
|
|
}
|
|
};
|
|
|
|
$clock = new class implements Clock {
|
|
public function now(): DateTimeImmutable
|
|
{
|
|
return new DateTimeImmutable('2026-03-01 10:00:00');
|
|
}
|
|
};
|
|
|
|
$this->handler = new GetBlockedDatesHandler(
|
|
$this->calendarRepository,
|
|
$rulesChecker,
|
|
$clock,
|
|
);
|
|
}
|
|
|
|
#[Test]
|
|
public function returnsWeekendsAsBlocked(): void
|
|
{
|
|
// 2026-03-02 est un lundi, 2026-03-08 est un dimanche
|
|
$result = ($this->handler)(new GetBlockedDatesQuery(
|
|
tenantId: self::TENANT_ID,
|
|
academicYearId: self::ACADEMIC_YEAR_ID,
|
|
startDate: '2026-03-02',
|
|
endDate: '2026-03-08',
|
|
));
|
|
|
|
$weekendDates = array_filter($result, static fn ($d) => $d->type === 'weekend');
|
|
|
|
self::assertCount(2, $weekendDates);
|
|
$dates = array_map(static fn ($d) => $d->date, array_values($weekendDates));
|
|
self::assertContains('2026-03-07', $dates);
|
|
self::assertContains('2026-03-08', $dates);
|
|
}
|
|
|
|
#[Test]
|
|
public function returnsHolidaysAsBlocked(): void
|
|
{
|
|
$calendar = $this->createCalendarWithHoliday(
|
|
new DateTimeImmutable('2026-03-04'),
|
|
'Jour de test',
|
|
);
|
|
$this->calendarRepository->save($calendar);
|
|
|
|
$result = ($this->handler)(new GetBlockedDatesQuery(
|
|
tenantId: self::TENANT_ID,
|
|
academicYearId: self::ACADEMIC_YEAR_ID,
|
|
startDate: '2026-03-02',
|
|
endDate: '2026-03-06',
|
|
));
|
|
|
|
$holidays = array_filter($result, static fn ($d) => $d->type === CalendarEntryType::HOLIDAY->value);
|
|
|
|
self::assertCount(1, $holidays);
|
|
$holiday = array_values($holidays)[0];
|
|
self::assertSame('2026-03-04', $holiday->date);
|
|
self::assertSame('Jour de test', $holiday->reason);
|
|
}
|
|
|
|
#[Test]
|
|
public function returnsEmptyForWeekWithNoBlockedDates(): void
|
|
{
|
|
// Lundi à vendredi sans calendrier configuré
|
|
$result = ($this->handler)(new GetBlockedDatesQuery(
|
|
tenantId: self::TENANT_ID,
|
|
academicYearId: self::ACADEMIC_YEAR_ID,
|
|
startDate: '2026-03-02',
|
|
endDate: '2026-03-06',
|
|
));
|
|
|
|
self::assertEmpty($result);
|
|
}
|
|
|
|
#[Test]
|
|
public function returnsVacationsAsBlocked(): void
|
|
{
|
|
$calendar = $this->createCalendarWithVacation(
|
|
new DateTimeImmutable('2026-03-02'),
|
|
new DateTimeImmutable('2026-03-06'),
|
|
'Vacances de printemps',
|
|
);
|
|
$this->calendarRepository->save($calendar);
|
|
|
|
$result = ($this->handler)(new GetBlockedDatesQuery(
|
|
tenantId: self::TENANT_ID,
|
|
academicYearId: self::ACADEMIC_YEAR_ID,
|
|
startDate: '2026-03-02',
|
|
endDate: '2026-03-06',
|
|
));
|
|
|
|
$vacations = array_filter($result, static fn ($d) => $d->type === CalendarEntryType::VACATION->value);
|
|
|
|
self::assertCount(5, $vacations);
|
|
}
|
|
|
|
#[Test]
|
|
public function returnsRuleHardBlockedDates(): void
|
|
{
|
|
$rulesChecker = new class implements HomeworkRulesChecker {
|
|
public function verifier(
|
|
TenantId $tenantId,
|
|
DateTimeImmutable $dueDate,
|
|
DateTimeImmutable $creationDate,
|
|
): HomeworkRulesCheckResult {
|
|
// Block Tuesday March 3
|
|
if ($dueDate->format('Y-m-d') === '2026-03-03') {
|
|
return new HomeworkRulesCheckResult(
|
|
warnings: [new \App\Scolarite\Application\Port\RuleWarning('minimum_delay', 'Délai minimum non respecté')],
|
|
bloquant: true,
|
|
);
|
|
}
|
|
|
|
return HomeworkRulesCheckResult::ok();
|
|
}
|
|
};
|
|
|
|
$clock = new class implements Clock {
|
|
public function now(): DateTimeImmutable
|
|
{
|
|
return new DateTimeImmutable('2026-03-01 10:00:00');
|
|
}
|
|
};
|
|
|
|
$handler = new GetBlockedDatesHandler($this->calendarRepository, $rulesChecker, $clock);
|
|
|
|
$result = ($handler)(new GetBlockedDatesQuery(
|
|
tenantId: self::TENANT_ID,
|
|
academicYearId: self::ACADEMIC_YEAR_ID,
|
|
startDate: '2026-03-02',
|
|
endDate: '2026-03-06',
|
|
));
|
|
|
|
$ruleBlocked = array_filter($result, static fn ($d) => $d->type === 'rule_hard');
|
|
self::assertCount(1, $ruleBlocked);
|
|
$blocked = array_values($ruleBlocked)[0];
|
|
self::assertSame('2026-03-03', $blocked->date);
|
|
self::assertSame('Délai minimum non respecté', $blocked->reason);
|
|
}
|
|
|
|
#[Test]
|
|
public function returnsRuleSoftWarningDates(): void
|
|
{
|
|
$rulesChecker = new class implements HomeworkRulesChecker {
|
|
public function verifier(
|
|
TenantId $tenantId,
|
|
DateTimeImmutable $dueDate,
|
|
DateTimeImmutable $creationDate,
|
|
): HomeworkRulesCheckResult {
|
|
if ($dueDate->format('Y-m-d') === '2026-03-04') {
|
|
return new HomeworkRulesCheckResult(
|
|
warnings: [new \App\Scolarite\Application\Port\RuleWarning('no_monday_after', 'Devoirs pour lundi déconseillés')],
|
|
bloquant: false,
|
|
);
|
|
}
|
|
|
|
return HomeworkRulesCheckResult::ok();
|
|
}
|
|
};
|
|
|
|
$clock = new class implements Clock {
|
|
public function now(): DateTimeImmutable
|
|
{
|
|
return new DateTimeImmutable('2026-03-01 10:00:00');
|
|
}
|
|
};
|
|
|
|
$handler = new GetBlockedDatesHandler($this->calendarRepository, $rulesChecker, $clock);
|
|
|
|
$result = ($handler)(new GetBlockedDatesQuery(
|
|
tenantId: self::TENANT_ID,
|
|
academicYearId: self::ACADEMIC_YEAR_ID,
|
|
startDate: '2026-03-02',
|
|
endDate: '2026-03-06',
|
|
));
|
|
|
|
$ruleSoft = array_filter($result, static fn ($d) => $d->type === 'rule_soft');
|
|
self::assertCount(1, $ruleSoft);
|
|
$soft = array_values($ruleSoft)[0];
|
|
self::assertSame('2026-03-04', $soft->date);
|
|
self::assertSame('rule_soft', $soft->type);
|
|
}
|
|
|
|
private function createCalendarWithHoliday(DateTimeImmutable $date, string $label): SchoolCalendar
|
|
{
|
|
$tenantId = TenantId::fromString(self::TENANT_ID);
|
|
$academicYearId = AcademicYearId::fromString(self::ACADEMIC_YEAR_ID);
|
|
|
|
$calendar = SchoolCalendar::initialiser($tenantId, $academicYearId);
|
|
$calendar->ajouterEntree(new CalendarEntry(
|
|
id: CalendarEntryId::generate(),
|
|
type: CalendarEntryType::HOLIDAY,
|
|
startDate: $date,
|
|
endDate: $date,
|
|
label: $label,
|
|
));
|
|
|
|
return $calendar;
|
|
}
|
|
|
|
private function createCalendarWithVacation(
|
|
DateTimeImmutable $startDate,
|
|
DateTimeImmutable $endDate,
|
|
string $label,
|
|
): SchoolCalendar {
|
|
$tenantId = TenantId::fromString(self::TENANT_ID);
|
|
$academicYearId = AcademicYearId::fromString(self::ACADEMIC_YEAR_ID);
|
|
|
|
$calendar = SchoolCalendar::initialiser($tenantId, $academicYearId);
|
|
$calendar->ajouterEntree(new CalendarEntry(
|
|
id: CalendarEntryId::generate(),
|
|
type: CalendarEntryType::VACATION,
|
|
startDate: $startDate,
|
|
endDate: $endDate,
|
|
label: $label,
|
|
));
|
|
|
|
return $calendar;
|
|
}
|
|
}
|