Files
Classeo/backend/tests/Unit/Administration/Domain/Model/Subject/SubjectTest.php
Mathias STRASSER f19d0ae3ef feat: Gestion des périodes scolaires
L'administration d'un établissement nécessite de découper l'année
scolaire en trimestres ou semestres avant de pouvoir saisir les notes
et générer les bulletins.

Ce module permet de configurer les périodes par année scolaire
(current/previous/next résolus en UUID v5 déterministes), de modifier
les dates individuelles avec validation anti-chevauchement, et de
consulter la période en cours avec le décompte des jours restants.

Les dates par défaut de février s'adaptent aux années bissextiles.
Le repository utilise UPSERT transactionnel pour garantir l'intégrité
lors du changement de mode (trimestres ↔ semestres). Les domain events
de Subject sont étendus pour couvrir toutes les mutations (code,
couleur, description) en plus du renommage.
2026-02-06 14:27:55 +01:00

332 lines
12 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Tests\Unit\Administration\Domain\Model\Subject;
use App\Administration\Domain\Event\MatiereCreee;
use App\Administration\Domain\Event\MatiereModifiee;
use App\Administration\Domain\Event\MatiereSupprimee;
use App\Administration\Domain\Model\SchoolClass\SchoolId;
use App\Administration\Domain\Model\Subject\Subject;
use App\Administration\Domain\Model\Subject\SubjectCode;
use App\Administration\Domain\Model\Subject\SubjectColor;
use App\Administration\Domain\Model\Subject\SubjectId;
use App\Administration\Domain\Model\Subject\SubjectName;
use App\Administration\Domain\Model\Subject\SubjectStatus;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
final class SubjectTest extends TestCase
{
private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440001';
private const string SCHOOL_ID = '550e8400-e29b-41d4-a716-446655440002';
#[Test]
public function creerCreatesSubjectWithActiveStatus(): void
{
$subject = $this->createSubject();
self::assertSame(SubjectStatus::ACTIVE, $subject->status);
self::assertTrue($subject->estActive());
self::assertTrue($subject->peutEtreUtilisee());
}
#[Test]
public function creerRecordsMatiereCreeeEvent(): void
{
$subject = $this->createSubject();
$events = $subject->pullDomainEvents();
self::assertCount(1, $events);
self::assertInstanceOf(MatiereCreee::class, $events[0]);
self::assertSame($subject->id, $events[0]->subjectId);
self::assertSame($subject->tenantId, $events[0]->tenantId);
self::assertSame($subject->name, $events[0]->name);
self::assertSame($subject->code, $events[0]->code);
}
#[Test]
public function creerSetsAllProperties(): void
{
$tenantId = TenantId::fromString(self::TENANT_ID);
$schoolId = SchoolId::fromString(self::SCHOOL_ID);
$name = new SubjectName('Mathématiques');
$code = new SubjectCode('MATH');
$color = new SubjectColor('#3B82F6');
$createdAt = new DateTimeImmutable('2026-01-15 10:00:00');
$subject = Subject::creer(
tenantId: $tenantId,
schoolId: $schoolId,
name: $name,
code: $code,
color: $color,
createdAt: $createdAt,
);
self::assertTrue($subject->tenantId->equals($tenantId));
self::assertTrue($subject->schoolId->equals($schoolId));
self::assertTrue($subject->name->equals($name));
self::assertTrue($subject->code->equals($code));
self::assertNotNull($subject->color);
self::assertTrue($subject->color->equals($color));
self::assertEquals($createdAt, $subject->createdAt);
self::assertEquals($createdAt, $subject->updatedAt);
self::assertNull($subject->deletedAt);
self::assertNull($subject->description);
}
#[Test]
public function creerWithNullColor(): void
{
$subject = Subject::creer(
tenantId: TenantId::fromString(self::TENANT_ID),
schoolId: SchoolId::fromString(self::SCHOOL_ID),
name: new SubjectName('Arts plastiques'),
code: new SubjectCode('ART'),
color: null,
createdAt: new DateTimeImmutable(),
);
self::assertNull($subject->color);
}
#[Test]
public function renommerChangesNameAndRecordsEvent(): void
{
$subject = $this->createSubject();
$subject->pullDomainEvents();
$ancienNom = $subject->name;
$nouveauNom = new SubjectName('Mathématiques avancées');
$at = new DateTimeImmutable('2026-02-01 10:00:00');
$subject->renommer($nouveauNom, $at);
self::assertTrue($subject->name->equals($nouveauNom));
self::assertEquals($at, $subject->updatedAt);
$events = $subject->pullDomainEvents();
self::assertCount(1, $events);
self::assertInstanceOf(MatiereModifiee::class, $events[0]);
self::assertSame('nom', $events[0]->champ);
self::assertSame((string) $ancienNom, $events[0]->ancienneValeur);
self::assertSame((string) $nouveauNom, $events[0]->nouvelleValeur);
}
#[Test]
public function renommerWithSameNameDoesNothing(): void
{
$subject = $this->createSubject();
$subject->pullDomainEvents();
$originalUpdatedAt = $subject->updatedAt;
$subject->renommer(new SubjectName('Mathématiques'), new DateTimeImmutable('2026-02-01 10:00:00'));
self::assertEquals($originalUpdatedAt, $subject->updatedAt);
self::assertEmpty($subject->pullDomainEvents());
}
#[Test]
public function changerCodeUpdatesCodeAndRecordsEvent(): void
{
$subject = $this->createSubject();
$subject->pullDomainEvents();
$at = new DateTimeImmutable('2026-02-01 10:00:00');
$nouveauCode = new SubjectCode('MATHS');
$subject->changerCode($nouveauCode, $at);
self::assertTrue($subject->code->equals($nouveauCode));
self::assertEquals($at, $subject->updatedAt);
$events = $subject->pullDomainEvents();
self::assertCount(1, $events);
self::assertInstanceOf(MatiereModifiee::class, $events[0]);
self::assertSame('code', $events[0]->champ);
self::assertSame('MATH', $events[0]->ancienneValeur);
self::assertSame('MATHS', $events[0]->nouvelleValeur);
}
#[Test]
public function changerCodeWithSameCodeDoesNothing(): void
{
$subject = $this->createSubject();
$subject->pullDomainEvents();
$originalUpdatedAt = $subject->updatedAt;
$subject->changerCode(new SubjectCode('MATH'), new DateTimeImmutable('2026-02-01 10:00:00'));
self::assertEquals($originalUpdatedAt, $subject->updatedAt);
self::assertEmpty($subject->pullDomainEvents());
}
#[Test]
public function changerCouleurUpdatesColorAndRecordsEvent(): void
{
$subject = $this->createSubject();
$subject->pullDomainEvents();
$at = new DateTimeImmutable('2026-02-01 10:00:00');
$nouvelleCouleur = new SubjectColor('#EF4444');
$subject->changerCouleur($nouvelleCouleur, $at);
self::assertNotNull($subject->color);
self::assertTrue($subject->color->equals($nouvelleCouleur));
self::assertEquals($at, $subject->updatedAt);
$events = $subject->pullDomainEvents();
self::assertCount(1, $events);
self::assertInstanceOf(MatiereModifiee::class, $events[0]);
self::assertSame('couleur', $events[0]->champ);
self::assertSame('#3B82F6', $events[0]->ancienneValeur);
self::assertSame('#EF4444', $events[0]->nouvelleValeur);
}
#[Test]
public function changerCouleurToNullRemovesColorAndRecordsEvent(): void
{
$subject = $this->createSubject();
$subject->pullDomainEvents();
$at = new DateTimeImmutable('2026-02-01 10:00:00');
$subject->changerCouleur(null, $at);
self::assertNull($subject->color);
self::assertEquals($at, $subject->updatedAt);
$events = $subject->pullDomainEvents();
self::assertCount(1, $events);
self::assertInstanceOf(MatiereModifiee::class, $events[0]);
self::assertSame('couleur', $events[0]->champ);
self::assertSame('#3B82F6', $events[0]->ancienneValeur);
self::assertNull($events[0]->nouvelleValeur);
}
#[Test]
public function changerCouleurWithSameColorDoesNothing(): void
{
$subject = $this->createSubject();
$subject->pullDomainEvents();
$originalUpdatedAt = $subject->updatedAt;
$subject->changerCouleur(new SubjectColor('#3B82F6'), new DateTimeImmutable('2026-02-01 10:00:00'));
self::assertEquals($originalUpdatedAt, $subject->updatedAt);
self::assertEmpty($subject->pullDomainEvents());
}
#[Test]
public function decrireUpdatesDescriptionAndRecordsEvent(): void
{
$subject = $this->createSubject();
$subject->pullDomainEvents();
$at = new DateTimeImmutable('2026-02-01 10:00:00');
$subject->decrire('Cours de mathématiques généralistes', $at);
self::assertSame('Cours de mathématiques généralistes', $subject->description);
self::assertEquals($at, $subject->updatedAt);
$events = $subject->pullDomainEvents();
self::assertCount(1, $events);
self::assertInstanceOf(MatiereModifiee::class, $events[0]);
self::assertSame('description', $events[0]->champ);
self::assertNull($events[0]->ancienneValeur);
self::assertSame('Cours de mathématiques généralistes', $events[0]->nouvelleValeur);
}
#[Test]
public function archiverChangesStatusAndRecordsEvent(): void
{
$subject = $this->createSubject();
$subject->pullDomainEvents();
$at = new DateTimeImmutable('2026-02-01 10:00:00');
$subject->archiver($at);
self::assertSame(SubjectStatus::ARCHIVED, $subject->status);
self::assertFalse($subject->estActive());
self::assertFalse($subject->peutEtreUtilisee());
self::assertEquals($at, $subject->deletedAt);
self::assertEquals($at, $subject->updatedAt);
$events = $subject->pullDomainEvents();
self::assertCount(1, $events);
self::assertInstanceOf(MatiereSupprimee::class, $events[0]);
}
#[Test]
public function archiverAlreadyArchivedSubjectDoesNothing(): void
{
$subject = $this->createSubject();
$subject->archiver(new DateTimeImmutable('2026-02-01 10:00:00'));
$subject->pullDomainEvents();
$originalDeletedAt = $subject->deletedAt;
$subject->archiver(new DateTimeImmutable('2026-02-02 10:00:00'));
self::assertEquals($originalDeletedAt, $subject->deletedAt);
self::assertEmpty($subject->pullDomainEvents());
}
#[Test]
public function reconstituteRestoresAllProperties(): void
{
$id = SubjectId::generate();
$tenantId = TenantId::fromString(self::TENANT_ID);
$schoolId = SchoolId::fromString(self::SCHOOL_ID);
$name = new SubjectName('Mathématiques');
$code = new SubjectCode('MATH');
$color = new SubjectColor('#3B82F6');
$status = SubjectStatus::ARCHIVED;
$description = 'Test description';
$createdAt = new DateTimeImmutable('2026-01-15 10:00:00');
$updatedAt = new DateTimeImmutable('2026-02-01 10:00:00');
$deletedAt = new DateTimeImmutable('2026-02-01 10:00:00');
$subject = Subject::reconstitute(
id: $id,
tenantId: $tenantId,
schoolId: $schoolId,
name: $name,
code: $code,
color: $color,
status: $status,
description: $description,
createdAt: $createdAt,
updatedAt: $updatedAt,
deletedAt: $deletedAt,
);
self::assertTrue($subject->id->equals($id));
self::assertTrue($subject->tenantId->equals($tenantId));
self::assertTrue($subject->schoolId->equals($schoolId));
self::assertTrue($subject->name->equals($name));
self::assertTrue($subject->code->equals($code));
self::assertNotNull($subject->color);
self::assertTrue($subject->color->equals($color));
self::assertSame($status, $subject->status);
self::assertSame($description, $subject->description);
self::assertEquals($createdAt, $subject->createdAt);
self::assertEquals($updatedAt, $subject->updatedAt);
self::assertEquals($deletedAt, $subject->deletedAt);
self::assertEmpty($subject->pullDomainEvents());
}
private function createSubject(): Subject
{
return Subject::creer(
tenantId: TenantId::fromString(self::TENANT_ID),
schoolId: SchoolId::fromString(self::SCHOOL_ID),
name: new SubjectName('Mathématiques'),
code: new SubjectCode('MATH'),
color: new SubjectColor('#3B82F6'),
createdAt: new DateTimeImmutable('2026-01-15 10:00:00'),
);
}
}