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.
193 lines
6.5 KiB
PHP
193 lines
6.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Unit\Administration\Application\Command\CreateClass;
|
|
|
|
use App\Administration\Application\Command\CreateClass\CreateClassCommand;
|
|
use App\Administration\Application\Command\CreateClass\CreateClassHandler;
|
|
use App\Administration\Domain\Exception\ClasseDejaExistanteException;
|
|
use App\Administration\Domain\Model\SchoolClass\ClassId;
|
|
use App\Administration\Domain\Model\SchoolClass\ClassStatus;
|
|
use App\Administration\Domain\Model\SchoolClass\SchoolLevel;
|
|
use App\Administration\Infrastructure\Persistence\InMemory\InMemoryClassRepository;
|
|
use App\Shared\Domain\Clock;
|
|
use DateTimeImmutable;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
final class CreateClassHandlerTest 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-01-31 10:00:00');
|
|
}
|
|
};
|
|
}
|
|
|
|
#[Test]
|
|
public function itCreatesClassSuccessfully(): void
|
|
{
|
|
$handler = new CreateClassHandler($this->classRepository, $this->clock);
|
|
$command = new CreateClassCommand(
|
|
tenantId: self::TENANT_ID,
|
|
schoolId: self::SCHOOL_ID,
|
|
academicYearId: self::ACADEMIC_YEAR_ID,
|
|
name: '6ème A',
|
|
level: SchoolLevel::SIXIEME->value,
|
|
capacity: 30,
|
|
);
|
|
|
|
$class = $handler($command);
|
|
|
|
self::assertNotEmpty((string) $class->id);
|
|
self::assertSame('6ème A', (string) $class->name);
|
|
self::assertSame(SchoolLevel::SIXIEME, $class->level);
|
|
self::assertSame(30, $class->capacity);
|
|
}
|
|
|
|
#[Test]
|
|
public function itCreatesClassWithNullLevelAndCapacity(): void
|
|
{
|
|
$handler = new CreateClassHandler($this->classRepository, $this->clock);
|
|
$command = new CreateClassCommand(
|
|
tenantId: self::TENANT_ID,
|
|
schoolId: self::SCHOOL_ID,
|
|
academicYearId: self::ACADEMIC_YEAR_ID,
|
|
name: 'Classe spéciale',
|
|
level: null,
|
|
capacity: null,
|
|
);
|
|
|
|
$class = $handler($command);
|
|
|
|
self::assertNotEmpty((string) $class->id);
|
|
self::assertSame('Classe spéciale', (string) $class->name);
|
|
self::assertNull($class->level);
|
|
self::assertNull($class->capacity);
|
|
}
|
|
|
|
#[Test]
|
|
public function itPersistsClassInRepository(): void
|
|
{
|
|
$handler = new CreateClassHandler($this->classRepository, $this->clock);
|
|
$command = new CreateClassCommand(
|
|
tenantId: self::TENANT_ID,
|
|
schoolId: self::SCHOOL_ID,
|
|
academicYearId: self::ACADEMIC_YEAR_ID,
|
|
name: '6ème A',
|
|
level: SchoolLevel::SIXIEME->value,
|
|
capacity: 30,
|
|
);
|
|
|
|
$createdClass = $handler($command);
|
|
|
|
$class = $this->classRepository->get(
|
|
ClassId::fromString((string) $createdClass->id),
|
|
);
|
|
|
|
self::assertSame('6ème A', (string) $class->name);
|
|
self::assertSame(ClassStatus::ACTIVE, $class->status);
|
|
self::assertSame(SchoolLevel::SIXIEME, $class->level);
|
|
self::assertSame(30, $class->capacity);
|
|
}
|
|
|
|
#[Test]
|
|
public function itThrowsExceptionWhenClassNameAlreadyExists(): void
|
|
{
|
|
$handler = new CreateClassHandler($this->classRepository, $this->clock);
|
|
$command = new CreateClassCommand(
|
|
tenantId: self::TENANT_ID,
|
|
schoolId: self::SCHOOL_ID,
|
|
academicYearId: self::ACADEMIC_YEAR_ID,
|
|
name: '6ème A',
|
|
level: SchoolLevel::SIXIEME->value,
|
|
capacity: 30,
|
|
);
|
|
|
|
// First creation should succeed
|
|
$handler($command);
|
|
|
|
// Second creation with same name should throw
|
|
$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 itAllowsSameNameInDifferentTenant(): void
|
|
{
|
|
$handler = new CreateClassHandler($this->classRepository, $this->clock);
|
|
|
|
// Create in tenant 1
|
|
$command1 = new CreateClassCommand(
|
|
tenantId: self::TENANT_ID,
|
|
schoolId: self::SCHOOL_ID,
|
|
academicYearId: self::ACADEMIC_YEAR_ID,
|
|
name: '6ème A',
|
|
level: SchoolLevel::SIXIEME->value,
|
|
capacity: 30,
|
|
);
|
|
$class1 = $handler($command1);
|
|
|
|
// Create same name in tenant 2 should succeed
|
|
$command2 = new CreateClassCommand(
|
|
tenantId: '550e8400-e29b-41d4-a716-446655440099',
|
|
schoolId: self::SCHOOL_ID,
|
|
academicYearId: self::ACADEMIC_YEAR_ID,
|
|
name: '6ème A',
|
|
level: SchoolLevel::SIXIEME->value,
|
|
capacity: 30,
|
|
);
|
|
$class2 = $handler($command2);
|
|
|
|
self::assertFalse($class1->id->equals($class2->id));
|
|
self::assertSame('6ème A', (string) $class1->name);
|
|
self::assertSame('6ème A', (string) $class2->name);
|
|
}
|
|
|
|
#[Test]
|
|
public function itAllowsSameNameInDifferentAcademicYear(): void
|
|
{
|
|
$handler = new CreateClassHandler($this->classRepository, $this->clock);
|
|
|
|
// Create in year 1
|
|
$command1 = new CreateClassCommand(
|
|
tenantId: self::TENANT_ID,
|
|
schoolId: self::SCHOOL_ID,
|
|
academicYearId: self::ACADEMIC_YEAR_ID,
|
|
name: '6ème A',
|
|
level: SchoolLevel::SIXIEME->value,
|
|
capacity: 30,
|
|
);
|
|
$class1 = $handler($command1);
|
|
|
|
// Create same name in year 2 should succeed
|
|
$command2 = new CreateClassCommand(
|
|
tenantId: self::TENANT_ID,
|
|
schoolId: self::SCHOOL_ID,
|
|
academicYearId: '550e8400-e29b-41d4-a716-446655440099',
|
|
name: '6ème A',
|
|
level: SchoolLevel::SIXIEME->value,
|
|
capacity: 30,
|
|
);
|
|
$class2 = $handler($command2);
|
|
|
|
self::assertFalse($class1->id->equals($class2->id));
|
|
self::assertSame('6ème A', (string) $class1->name);
|
|
self::assertSame('6ème A', (string) $class2->name);
|
|
}
|
|
}
|