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,173 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Unit\Administration\Infrastructure\Persistence\InMemory;
|
||||
|
||||
use App\Administration\Domain\Exception\ClasseNotFoundException;
|
||||
use App\Administration\Domain\Model\SchoolClass\AcademicYearId;
|
||||
use App\Administration\Domain\Model\SchoolClass\ClassId;
|
||||
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\Tenant\TenantId;
|
||||
use DateTimeImmutable;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class InMemoryClassRepositoryTest 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 $repository;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->repository = new InMemoryClassRepository();
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function saveAndGet(): void
|
||||
{
|
||||
$class = $this->createClass('6ème A');
|
||||
|
||||
$this->repository->save($class);
|
||||
|
||||
$retrieved = $this->repository->get($class->id);
|
||||
self::assertTrue($class->id->equals($retrieved->id));
|
||||
self::assertTrue($class->name->equals($retrieved->name));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function getThrowsExceptionForUnknownId(): void
|
||||
{
|
||||
$this->expectException(ClasseNotFoundException::class);
|
||||
|
||||
$this->repository->get(ClassId::generate());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function findByIdReturnsNullForUnknownId(): void
|
||||
{
|
||||
$result = $this->repository->findById(ClassId::generate());
|
||||
|
||||
self::assertNull($result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function findByName(): void
|
||||
{
|
||||
$class = $this->createClass('6ème A');
|
||||
$this->repository->save($class);
|
||||
|
||||
$found = $this->repository->findByName(
|
||||
new ClassName('6ème A'),
|
||||
TenantId::fromString(self::TENANT_ID),
|
||||
AcademicYearId::fromString(self::ACADEMIC_YEAR_ID),
|
||||
);
|
||||
|
||||
self::assertNotNull($found);
|
||||
self::assertTrue($class->id->equals($found->id));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function findByNameReturnsNullForUnknownName(): void
|
||||
{
|
||||
$class = $this->createClass('6ème A');
|
||||
$this->repository->save($class);
|
||||
|
||||
$found = $this->repository->findByName(
|
||||
new ClassName('6ème B'),
|
||||
TenantId::fromString(self::TENANT_ID),
|
||||
AcademicYearId::fromString(self::ACADEMIC_YEAR_ID),
|
||||
);
|
||||
|
||||
self::assertNull($found);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function findByNameIsCaseInsensitive(): void
|
||||
{
|
||||
$class = $this->createClass('6ème A');
|
||||
$this->repository->save($class);
|
||||
|
||||
$found = $this->repository->findByName(
|
||||
new ClassName('6ÈME A'),
|
||||
TenantId::fromString(self::TENANT_ID),
|
||||
AcademicYearId::fromString(self::ACADEMIC_YEAR_ID),
|
||||
);
|
||||
|
||||
self::assertNotNull($found);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function findActiveByTenantAndYear(): void
|
||||
{
|
||||
$class1 = $this->createClass('6ème A');
|
||||
$class2 = $this->createClass('6ème B');
|
||||
$class3 = $this->createClass('6ème C');
|
||||
$class3->archiver(new DateTimeImmutable());
|
||||
|
||||
$this->repository->save($class1);
|
||||
$this->repository->save($class2);
|
||||
$this->repository->save($class3);
|
||||
|
||||
$activeClasses = $this->repository->findActiveByTenantAndYear(
|
||||
TenantId::fromString(self::TENANT_ID),
|
||||
AcademicYearId::fromString(self::ACADEMIC_YEAR_ID),
|
||||
);
|
||||
|
||||
self::assertCount(2, $activeClasses);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function findActiveByTenantAndYearReturnsEmptyArrayForDifferentTenant(): void
|
||||
{
|
||||
$class = $this->createClass('6ème A');
|
||||
$this->repository->save($class);
|
||||
|
||||
$activeClasses = $this->repository->findActiveByTenantAndYear(
|
||||
TenantId::fromString('550e8400-e29b-41d4-a716-446655440099'),
|
||||
AcademicYearId::fromString(self::ACADEMIC_YEAR_ID),
|
||||
);
|
||||
|
||||
self::assertEmpty($activeClasses);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function delete(): void
|
||||
{
|
||||
$class = $this->createClass('6ème A');
|
||||
$this->repository->save($class);
|
||||
|
||||
$this->repository->delete($class->id);
|
||||
|
||||
self::assertNull($this->repository->findById($class->id));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function deleteNonExistentClassDoesNotThrow(): void
|
||||
{
|
||||
// Should not throw
|
||||
$this->repository->delete(ClassId::generate());
|
||||
|
||||
$this->expectNotToPerformAssertions();
|
||||
}
|
||||
|
||||
private function createClass(string $name): SchoolClass
|
||||
{
|
||||
return SchoolClass::creer(
|
||||
tenantId: TenantId::fromString(self::TENANT_ID),
|
||||
schoolId: SchoolId::fromString(self::SCHOOL_ID),
|
||||
academicYearId: AcademicYearId::fromString(self::ACADEMIC_YEAR_ID),
|
||||
name: new ClassName($name),
|
||||
level: SchoolLevel::SIXIEME,
|
||||
capacity: 30,
|
||||
createdAt: new DateTimeImmutable('2026-01-15 10:00:00'),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user