Les établissements ont besoin de définir leur référentiel de matières pour pouvoir ensuite les associer aux enseignants et aux classes. Cette fonctionnalité permet aux administrateurs de créer, modifier et archiver les matières avec leurs propriétés (nom, code court, couleur). L'architecture suit le pattern DDD avec des Value Objects utilisant les property hooks PHP 8.5 pour garantir l'immutabilité et la validation. L'isolation multi-tenant est assurée par vérification dans les handlers.
208 lines
6.9 KiB
PHP
208 lines
6.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Unit\Administration\Application\Command\CreateSubject;
|
|
|
|
use App\Administration\Application\Command\CreateSubject\CreateSubjectCommand;
|
|
use App\Administration\Application\Command\CreateSubject\CreateSubjectHandler;
|
|
use App\Administration\Domain\Exception\SubjectDejaExistanteException;
|
|
use App\Administration\Domain\Model\Subject\SubjectId;
|
|
use App\Administration\Domain\Model\Subject\SubjectStatus;
|
|
use App\Administration\Infrastructure\Persistence\InMemory\InMemorySubjectRepository;
|
|
use App\Shared\Domain\Clock;
|
|
use DateTimeImmutable;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
final class CreateSubjectHandlerTest extends TestCase
|
|
{
|
|
private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440001';
|
|
private const string SCHOOL_ID = '550e8400-e29b-41d4-a716-446655440002';
|
|
|
|
private InMemorySubjectRepository $subjectRepository;
|
|
private Clock $clock;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->subjectRepository = new InMemorySubjectRepository();
|
|
$this->clock = new class implements Clock {
|
|
public function now(): DateTimeImmutable
|
|
{
|
|
return new DateTimeImmutable('2026-01-31 10:00:00');
|
|
}
|
|
};
|
|
}
|
|
|
|
#[Test]
|
|
public function itCreatesSubjectSuccessfully(): void
|
|
{
|
|
$handler = new CreateSubjectHandler($this->subjectRepository, $this->clock);
|
|
$command = new CreateSubjectCommand(
|
|
tenantId: self::TENANT_ID,
|
|
schoolId: self::SCHOOL_ID,
|
|
name: 'Mathématiques',
|
|
code: 'MATH',
|
|
color: '#3B82F6',
|
|
);
|
|
|
|
$subject = $handler($command);
|
|
|
|
self::assertNotEmpty((string) $subject->id);
|
|
self::assertSame('Mathématiques', (string) $subject->name);
|
|
self::assertSame('MATH', (string) $subject->code);
|
|
self::assertNotNull($subject->color);
|
|
self::assertSame('#3B82F6', (string) $subject->color);
|
|
self::assertSame(SubjectStatus::ACTIVE, $subject->status);
|
|
}
|
|
|
|
#[Test]
|
|
public function itCreatesSubjectWithNullColor(): void
|
|
{
|
|
$handler = new CreateSubjectHandler($this->subjectRepository, $this->clock);
|
|
$command = new CreateSubjectCommand(
|
|
tenantId: self::TENANT_ID,
|
|
schoolId: self::SCHOOL_ID,
|
|
name: 'Arts plastiques',
|
|
code: 'ART',
|
|
color: null,
|
|
);
|
|
|
|
$subject = $handler($command);
|
|
|
|
self::assertNotEmpty((string) $subject->id);
|
|
self::assertSame('Arts plastiques', (string) $subject->name);
|
|
self::assertSame('ART', (string) $subject->code);
|
|
self::assertNull($subject->color);
|
|
}
|
|
|
|
#[Test]
|
|
public function itPersistsSubjectInRepository(): void
|
|
{
|
|
$handler = new CreateSubjectHandler($this->subjectRepository, $this->clock);
|
|
$command = new CreateSubjectCommand(
|
|
tenantId: self::TENANT_ID,
|
|
schoolId: self::SCHOOL_ID,
|
|
name: 'Mathématiques',
|
|
code: 'MATH',
|
|
color: '#3B82F6',
|
|
);
|
|
|
|
$createdSubject = $handler($command);
|
|
|
|
$subject = $this->subjectRepository->get(
|
|
SubjectId::fromString((string) $createdSubject->id),
|
|
);
|
|
|
|
self::assertSame('Mathématiques', (string) $subject->name);
|
|
self::assertSame('MATH', (string) $subject->code);
|
|
self::assertSame(SubjectStatus::ACTIVE, $subject->status);
|
|
}
|
|
|
|
#[Test]
|
|
public function itThrowsExceptionWhenSubjectCodeAlreadyExists(): void
|
|
{
|
|
$handler = new CreateSubjectHandler($this->subjectRepository, $this->clock);
|
|
$command = new CreateSubjectCommand(
|
|
tenantId: self::TENANT_ID,
|
|
schoolId: self::SCHOOL_ID,
|
|
name: 'Mathématiques',
|
|
code: 'MATH',
|
|
color: '#3B82F6',
|
|
);
|
|
|
|
// First creation should succeed
|
|
$handler($command);
|
|
|
|
// Second creation with same code should throw
|
|
$this->expectException(SubjectDejaExistanteException::class);
|
|
$this->expectExceptionMessage('Une matière avec le code "MATH" existe déjà dans cet établissement.');
|
|
|
|
$commandDuplicate = new CreateSubjectCommand(
|
|
tenantId: self::TENANT_ID,
|
|
schoolId: self::SCHOOL_ID,
|
|
name: 'Maths avancées', // Different name, same code
|
|
code: 'MATH',
|
|
color: '#EF4444',
|
|
);
|
|
$handler($commandDuplicate);
|
|
}
|
|
|
|
#[Test]
|
|
public function itAllowsSameCodeInDifferentTenant(): void
|
|
{
|
|
$handler = new CreateSubjectHandler($this->subjectRepository, $this->clock);
|
|
|
|
// Create in tenant 1
|
|
$command1 = new CreateSubjectCommand(
|
|
tenantId: self::TENANT_ID,
|
|
schoolId: self::SCHOOL_ID,
|
|
name: 'Mathématiques',
|
|
code: 'MATH',
|
|
color: '#3B82F6',
|
|
);
|
|
$subject1 = $handler($command1);
|
|
|
|
// Create same code in tenant 2 should succeed
|
|
$command2 = new CreateSubjectCommand(
|
|
tenantId: '550e8400-e29b-41d4-a716-446655440099',
|
|
schoolId: self::SCHOOL_ID,
|
|
name: 'Mathématiques',
|
|
code: 'MATH',
|
|
color: '#3B82F6',
|
|
);
|
|
$subject2 = $handler($command2);
|
|
|
|
self::assertFalse($subject1->id->equals($subject2->id));
|
|
self::assertSame('MATH', (string) $subject1->code);
|
|
self::assertSame('MATH', (string) $subject2->code);
|
|
}
|
|
|
|
#[Test]
|
|
public function itAllowsSameCodeInDifferentSchool(): void
|
|
{
|
|
$handler = new CreateSubjectHandler($this->subjectRepository, $this->clock);
|
|
|
|
// Create in school 1
|
|
$command1 = new CreateSubjectCommand(
|
|
tenantId: self::TENANT_ID,
|
|
schoolId: self::SCHOOL_ID,
|
|
name: 'Mathématiques',
|
|
code: 'MATH',
|
|
color: '#3B82F6',
|
|
);
|
|
$subject1 = $handler($command1);
|
|
|
|
// Create same code in school 2 should succeed
|
|
$command2 = new CreateSubjectCommand(
|
|
tenantId: self::TENANT_ID,
|
|
schoolId: '550e8400-e29b-41d4-a716-446655440099',
|
|
name: 'Mathématiques',
|
|
code: 'MATH',
|
|
color: '#3B82F6',
|
|
);
|
|
$subject2 = $handler($command2);
|
|
|
|
self::assertFalse($subject1->id->equals($subject2->id));
|
|
self::assertSame('MATH', (string) $subject1->code);
|
|
self::assertSame('MATH', (string) $subject2->code);
|
|
}
|
|
|
|
#[Test]
|
|
public function itNormalizesCodeToUppercase(): void
|
|
{
|
|
$handler = new CreateSubjectHandler($this->subjectRepository, $this->clock);
|
|
$command = new CreateSubjectCommand(
|
|
tenantId: self::TENANT_ID,
|
|
schoolId: self::SCHOOL_ID,
|
|
name: 'Mathématiques',
|
|
code: 'math',
|
|
color: '#3B82F6',
|
|
);
|
|
|
|
$subject = $handler($command);
|
|
|
|
self::assertSame('MATH', (string) $subject->code);
|
|
}
|
|
}
|