feat: Gestion des classes scolaires
Permet aux administrateurs de créer, modifier et supprimer des classes pour organiser les élèves par niveau. L'archivage soft-delete préserve l'historique tout en masquant les classes obsolètes. Inclut la validation des noms (2-50 caractères), les niveaux scolaires du CP à la Terminale, et les contrôles d'accès par rôle.
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Unit\Administration\Application\Command\UpdateClass;
|
||||
|
||||
use App\Administration\Application\Command\UpdateClass\UpdateClassCommand;
|
||||
use App\Administration\Application\Command\UpdateClass\UpdateClassHandler;
|
||||
use App\Administration\Domain\Exception\ClasseDejaExistanteException;
|
||||
use App\Administration\Domain\Model\SchoolClass\AcademicYearId;
|
||||
use App\Administration\Domain\Model\SchoolClass\ClassName;
|
||||
use App\Administration\Domain\Model\SchoolClass\SchoolClass;
|
||||
use App\Administration\Domain\Model\SchoolClass\SchoolId;
|
||||
use App\Administration\Domain\Model\SchoolClass\SchoolLevel;
|
||||
use App\Administration\Infrastructure\Persistence\InMemory\InMemoryClassRepository;
|
||||
use App\Shared\Domain\Clock;
|
||||
use App\Shared\Domain\Tenant\TenantId;
|
||||
use DateTimeImmutable;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class UpdateClassHandlerTest extends TestCase
|
||||
{
|
||||
private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440001';
|
||||
private const string SCHOOL_ID = '550e8400-e29b-41d4-a716-446655440002';
|
||||
private const string ACADEMIC_YEAR_ID = '550e8400-e29b-41d4-a716-446655440003';
|
||||
|
||||
private InMemoryClassRepository $classRepository;
|
||||
private Clock $clock;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->classRepository = new InMemoryClassRepository();
|
||||
$this->clock = new class implements Clock {
|
||||
public function now(): DateTimeImmutable
|
||||
{
|
||||
return new DateTimeImmutable('2026-02-01 10:00:00');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itUpdatesClassName(): void
|
||||
{
|
||||
$class = $this->createAndSaveClass();
|
||||
$handler = new UpdateClassHandler($this->classRepository, $this->clock);
|
||||
|
||||
$command = new UpdateClassCommand(
|
||||
classId: (string) $class->id,
|
||||
name: '6ème B',
|
||||
);
|
||||
|
||||
$handler($command);
|
||||
|
||||
$updatedClass = $this->classRepository->get($class->id);
|
||||
self::assertSame('6ème B', (string) $updatedClass->name);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itUpdatesClassLevel(): void
|
||||
{
|
||||
$class = $this->createAndSaveClass();
|
||||
$handler = new UpdateClassHandler($this->classRepository, $this->clock);
|
||||
|
||||
$command = new UpdateClassCommand(
|
||||
classId: (string) $class->id,
|
||||
level: SchoolLevel::CINQUIEME->value,
|
||||
);
|
||||
|
||||
$handler($command);
|
||||
|
||||
$updatedClass = $this->classRepository->get($class->id);
|
||||
self::assertSame(SchoolLevel::CINQUIEME, $updatedClass->level);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itClearsClassLevel(): void
|
||||
{
|
||||
$class = $this->createAndSaveClass();
|
||||
$handler = new UpdateClassHandler($this->classRepository, $this->clock);
|
||||
|
||||
$command = new UpdateClassCommand(
|
||||
classId: (string) $class->id,
|
||||
clearLevel: true,
|
||||
);
|
||||
|
||||
$handler($command);
|
||||
|
||||
$updatedClass = $this->classRepository->get($class->id);
|
||||
self::assertNull($updatedClass->level);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itUpdatesClassCapacity(): void
|
||||
{
|
||||
$class = $this->createAndSaveClass();
|
||||
$handler = new UpdateClassHandler($this->classRepository, $this->clock);
|
||||
|
||||
$command = new UpdateClassCommand(
|
||||
classId: (string) $class->id,
|
||||
capacity: 35,
|
||||
);
|
||||
|
||||
$handler($command);
|
||||
|
||||
$updatedClass = $this->classRepository->get($class->id);
|
||||
self::assertSame(35, $updatedClass->capacity);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itUpdatesClassDescription(): void
|
||||
{
|
||||
$class = $this->createAndSaveClass();
|
||||
$handler = new UpdateClassHandler($this->classRepository, $this->clock);
|
||||
|
||||
$command = new UpdateClassCommand(
|
||||
classId: (string) $class->id,
|
||||
description: 'Classe option musique',
|
||||
);
|
||||
|
||||
$handler($command);
|
||||
|
||||
$updatedClass = $this->classRepository->get($class->id);
|
||||
self::assertSame('Classe option musique', $updatedClass->description);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itUpdatesMultipleFields(): void
|
||||
{
|
||||
$class = $this->createAndSaveClass();
|
||||
$handler = new UpdateClassHandler($this->classRepository, $this->clock);
|
||||
|
||||
$command = new UpdateClassCommand(
|
||||
classId: (string) $class->id,
|
||||
name: '5ème C',
|
||||
level: SchoolLevel::CINQUIEME->value,
|
||||
capacity: 28,
|
||||
description: 'Section européenne',
|
||||
);
|
||||
|
||||
$handler($command);
|
||||
|
||||
$updatedClass = $this->classRepository->get($class->id);
|
||||
self::assertSame('5ème C', (string) $updatedClass->name);
|
||||
self::assertSame(SchoolLevel::CINQUIEME, $updatedClass->level);
|
||||
self::assertSame(28, $updatedClass->capacity);
|
||||
self::assertSame('Section européenne', $updatedClass->description);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itThrowsExceptionWhenRenamingToExistingName(): void
|
||||
{
|
||||
// Create first class
|
||||
$class1 = $this->createAndSaveClass();
|
||||
|
||||
// Create second class with different name
|
||||
$class2 = SchoolClass::creer(
|
||||
tenantId: TenantId::fromString(self::TENANT_ID),
|
||||
schoolId: SchoolId::fromString(self::SCHOOL_ID),
|
||||
academicYearId: AcademicYearId::fromString(self::ACADEMIC_YEAR_ID),
|
||||
name: new ClassName('6ème B'),
|
||||
level: SchoolLevel::SIXIEME,
|
||||
capacity: 30,
|
||||
createdAt: new DateTimeImmutable('2026-01-15 10:00:00'),
|
||||
);
|
||||
$this->classRepository->save($class2);
|
||||
|
||||
$handler = new UpdateClassHandler($this->classRepository, $this->clock);
|
||||
|
||||
// Try to rename class2 to class1's name
|
||||
$command = new UpdateClassCommand(
|
||||
classId: (string) $class2->id,
|
||||
name: '6ème A',
|
||||
);
|
||||
|
||||
$this->expectException(ClasseDejaExistanteException::class);
|
||||
$this->expectExceptionMessage('Une classe avec le nom "6ème A" existe déjà pour cette année scolaire.');
|
||||
|
||||
$handler($command);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itAllowsRenamingToSameName(): void
|
||||
{
|
||||
$class = $this->createAndSaveClass();
|
||||
$handler = new UpdateClassHandler($this->classRepository, $this->clock);
|
||||
|
||||
// Renaming to the same name should work
|
||||
$command = new UpdateClassCommand(
|
||||
classId: (string) $class->id,
|
||||
name: '6ème A',
|
||||
);
|
||||
|
||||
$handler($command);
|
||||
|
||||
$updatedClass = $this->classRepository->get($class->id);
|
||||
self::assertSame('6ème A', (string) $updatedClass->name);
|
||||
}
|
||||
|
||||
private function createAndSaveClass(): SchoolClass
|
||||
{
|
||||
$class = SchoolClass::creer(
|
||||
tenantId: TenantId::fromString(self::TENANT_ID),
|
||||
schoolId: SchoolId::fromString(self::SCHOOL_ID),
|
||||
academicYearId: AcademicYearId::fromString(self::ACADEMIC_YEAR_ID),
|
||||
name: new ClassName('6ème A'),
|
||||
level: SchoolLevel::SIXIEME,
|
||||
capacity: 30,
|
||||
createdAt: new DateTimeImmutable('2026-01-15 10:00:00'),
|
||||
);
|
||||
|
||||
$this->classRepository->save($class);
|
||||
|
||||
return $class;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user