feat: Permettre aux enseignants de créer et gérer les devoirs
Les enseignants avaient besoin d'un outil pour créer des devoirs assignés à leurs classes, avec filtrage automatique par matière selon la classe sélectionnée. Le système valide que la date d'échéance tombe un jour ouvrable (lundi-vendredi) et empêche les dates dans le passé. Le domaine modélise le devoir comme un agrégat avec pièces jointes, statut brouillon/publié, et événements métier (création, modification, suppression). Les handlers de notification écoutent ces événements pour les futurs envois aux parents et élèves.
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Unit\Scolarite\Domain\Model\Homework;
|
||||
|
||||
use App\Scolarite\Domain\Exception\PieceJointeInvalideException;
|
||||
use App\Scolarite\Domain\Model\Homework\HomeworkAttachment;
|
||||
use App\Scolarite\Domain\Model\Homework\HomeworkAttachmentId;
|
||||
use DateTimeImmutable;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class HomeworkAttachmentTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function createsValidAttachment(): void
|
||||
{
|
||||
$id = HomeworkAttachmentId::generate();
|
||||
$uploadedAt = new DateTimeImmutable('2026-03-12 10:00:00');
|
||||
|
||||
$attachment = new HomeworkAttachment(
|
||||
id: $id,
|
||||
filename: 'exercices.pdf',
|
||||
filePath: 'homework/abc123/exercices.pdf',
|
||||
fileSize: 500_000,
|
||||
mimeType: 'application/pdf',
|
||||
uploadedAt: $uploadedAt,
|
||||
);
|
||||
|
||||
self::assertTrue($attachment->id->equals($id));
|
||||
self::assertSame('exercices.pdf', $attachment->filename);
|
||||
self::assertSame('homework/abc123/exercices.pdf', $attachment->filePath);
|
||||
self::assertSame(500_000, $attachment->fileSize);
|
||||
self::assertSame('application/pdf', $attachment->mimeType);
|
||||
self::assertEquals($uploadedAt, $attachment->uploadedAt);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function acceptsJpegMimeType(): void
|
||||
{
|
||||
$attachment = $this->createAttachmentWithMimeType('image/jpeg');
|
||||
|
||||
self::assertSame('image/jpeg', $attachment->mimeType);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function acceptsPngMimeType(): void
|
||||
{
|
||||
$attachment = $this->createAttachmentWithMimeType('image/png');
|
||||
|
||||
self::assertSame('image/png', $attachment->mimeType);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function rejectsInvalidMimeType(): void
|
||||
{
|
||||
$this->expectException(PieceJointeInvalideException::class);
|
||||
$this->expectExceptionMessageMatches('/text\/plain/');
|
||||
|
||||
$this->createAttachmentWithMimeType('text/plain');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function rejectsFileTooLarge(): void
|
||||
{
|
||||
$this->expectException(PieceJointeInvalideException::class);
|
||||
$this->expectExceptionMessageMatches('/taille maximum/');
|
||||
|
||||
new HomeworkAttachment(
|
||||
id: HomeworkAttachmentId::generate(),
|
||||
filename: 'big.pdf',
|
||||
filePath: 'homework/abc/big.pdf',
|
||||
fileSize: 11 * 1024 * 1024, // 11 Mo
|
||||
mimeType: 'application/pdf',
|
||||
uploadedAt: new DateTimeImmutable(),
|
||||
);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function acceptsExactMaxSize(): void
|
||||
{
|
||||
$attachment = new HomeworkAttachment(
|
||||
id: HomeworkAttachmentId::generate(),
|
||||
filename: 'max.pdf',
|
||||
filePath: 'homework/abc/max.pdf',
|
||||
fileSize: 10 * 1024 * 1024, // Exactement 10 Mo
|
||||
mimeType: 'application/pdf',
|
||||
uploadedAt: new DateTimeImmutable(),
|
||||
);
|
||||
|
||||
self::assertSame(10 * 1024 * 1024, $attachment->fileSize);
|
||||
}
|
||||
|
||||
private function createAttachmentWithMimeType(string $mimeType): HomeworkAttachment
|
||||
{
|
||||
return new HomeworkAttachment(
|
||||
id: HomeworkAttachmentId::generate(),
|
||||
filename: 'test.file',
|
||||
filePath: 'homework/abc/test.file',
|
||||
fileSize: 1000,
|
||||
mimeType: $mimeType,
|
||||
uploadedAt: new DateTimeImmutable(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Unit\Scolarite\Domain\Model\Homework;
|
||||
|
||||
use App\Administration\Domain\Model\SchoolClass\ClassId;
|
||||
use App\Administration\Domain\Model\Subject\SubjectId;
|
||||
use App\Administration\Domain\Model\User\UserId;
|
||||
use App\Scolarite\Domain\Event\DevoirCree;
|
||||
use App\Scolarite\Domain\Event\DevoirModifie;
|
||||
use App\Scolarite\Domain\Event\DevoirSupprime;
|
||||
use App\Scolarite\Domain\Exception\DevoirDejaSupprimeException;
|
||||
use App\Scolarite\Domain\Model\Homework\Homework;
|
||||
use App\Scolarite\Domain\Model\Homework\HomeworkId;
|
||||
use App\Scolarite\Domain\Model\Homework\HomeworkStatus;
|
||||
use App\Shared\Domain\Tenant\TenantId;
|
||||
use DateTimeImmutable;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class HomeworkTest extends TestCase
|
||||
{
|
||||
private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440001';
|
||||
private const string CLASS_ID = '550e8400-e29b-41d4-a716-446655440020';
|
||||
private const string SUBJECT_ID = '550e8400-e29b-41d4-a716-446655440030';
|
||||
private const string TEACHER_ID = '550e8400-e29b-41d4-a716-446655440010';
|
||||
|
||||
#[Test]
|
||||
public function creerCreatesPublishedHomework(): void
|
||||
{
|
||||
$homework = $this->createHomework();
|
||||
|
||||
self::assertSame(HomeworkStatus::PUBLISHED, $homework->status);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function creerRecordsDevoirCreeEvent(): void
|
||||
{
|
||||
$homework = $this->createHomework();
|
||||
|
||||
$events = $homework->pullDomainEvents();
|
||||
|
||||
self::assertCount(1, $events);
|
||||
self::assertInstanceOf(DevoirCree::class, $events[0]);
|
||||
self::assertSame($homework->id, $events[0]->homeworkId);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function creerSetsAllProperties(): void
|
||||
{
|
||||
$tenantId = TenantId::fromString(self::TENANT_ID);
|
||||
$classId = ClassId::fromString(self::CLASS_ID);
|
||||
$subjectId = SubjectId::fromString(self::SUBJECT_ID);
|
||||
$teacherId = UserId::fromString(self::TEACHER_ID);
|
||||
$dueDate = new DateTimeImmutable('2026-04-15');
|
||||
$now = new DateTimeImmutable('2026-03-12 10:00:00');
|
||||
|
||||
$homework = Homework::creer(
|
||||
tenantId: $tenantId,
|
||||
classId: $classId,
|
||||
subjectId: $subjectId,
|
||||
teacherId: $teacherId,
|
||||
title: 'Exercices chapitre 5',
|
||||
description: 'Faire les exercices 1 à 10',
|
||||
dueDate: $dueDate,
|
||||
now: $now,
|
||||
);
|
||||
|
||||
self::assertTrue($homework->tenantId->equals($tenantId));
|
||||
self::assertTrue($homework->classId->equals($classId));
|
||||
self::assertTrue($homework->subjectId->equals($subjectId));
|
||||
self::assertTrue($homework->teacherId->equals($teacherId));
|
||||
self::assertSame('Exercices chapitre 5', $homework->title);
|
||||
self::assertSame('Faire les exercices 1 à 10', $homework->description);
|
||||
self::assertEquals($dueDate, $homework->dueDate);
|
||||
self::assertSame(HomeworkStatus::PUBLISHED, $homework->status);
|
||||
self::assertEquals($now, $homework->createdAt);
|
||||
self::assertEquals($now, $homework->updatedAt);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function creerAllowsNullDescription(): void
|
||||
{
|
||||
$homework = Homework::creer(
|
||||
tenantId: TenantId::fromString(self::TENANT_ID),
|
||||
classId: ClassId::fromString(self::CLASS_ID),
|
||||
subjectId: SubjectId::fromString(self::SUBJECT_ID),
|
||||
teacherId: UserId::fromString(self::TEACHER_ID),
|
||||
title: 'Devoir sans description',
|
||||
description: null,
|
||||
dueDate: new DateTimeImmutable('2026-04-15'),
|
||||
now: new DateTimeImmutable('2026-03-12 10:00:00'),
|
||||
);
|
||||
|
||||
self::assertNull($homework->description);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function modifierUpdatesFieldsAndRecordsEvent(): void
|
||||
{
|
||||
$homework = $this->createHomework();
|
||||
$homework->pullDomainEvents();
|
||||
$modifiedAt = new DateTimeImmutable('2026-03-13 14:00:00');
|
||||
$newDueDate = new DateTimeImmutable('2026-04-20');
|
||||
|
||||
$homework->modifier(
|
||||
title: 'Titre modifié',
|
||||
description: 'Nouvelle description',
|
||||
dueDate: $newDueDate,
|
||||
now: $modifiedAt,
|
||||
);
|
||||
|
||||
self::assertSame('Titre modifié', $homework->title);
|
||||
self::assertSame('Nouvelle description', $homework->description);
|
||||
self::assertEquals($newDueDate, $homework->dueDate);
|
||||
self::assertEquals($modifiedAt, $homework->updatedAt);
|
||||
|
||||
$events = $homework->pullDomainEvents();
|
||||
self::assertCount(1, $events);
|
||||
self::assertInstanceOf(DevoirModifie::class, $events[0]);
|
||||
self::assertSame($homework->id, $events[0]->homeworkId);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function modifierThrowsWhenDeleted(): void
|
||||
{
|
||||
$homework = $this->createHomework();
|
||||
$homework->supprimer(new DateTimeImmutable('2026-03-13'));
|
||||
|
||||
$this->expectException(DevoirDejaSupprimeException::class);
|
||||
|
||||
$homework->modifier(
|
||||
title: 'Titre',
|
||||
description: null,
|
||||
dueDate: new DateTimeImmutable('2026-04-20'),
|
||||
now: new DateTimeImmutable('2026-03-14'),
|
||||
);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function supprimerChangesStatusAndRecordsEvent(): void
|
||||
{
|
||||
$homework = $this->createHomework();
|
||||
$homework->pullDomainEvents();
|
||||
$deletedAt = new DateTimeImmutable('2026-03-14 08:00:00');
|
||||
|
||||
$homework->supprimer($deletedAt);
|
||||
|
||||
self::assertSame(HomeworkStatus::DELETED, $homework->status);
|
||||
self::assertEquals($deletedAt, $homework->updatedAt);
|
||||
|
||||
$events = $homework->pullDomainEvents();
|
||||
self::assertCount(1, $events);
|
||||
self::assertInstanceOf(DevoirSupprime::class, $events[0]);
|
||||
self::assertSame($homework->id, $events[0]->homeworkId);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function supprimerThrowsWhenAlreadyDeleted(): void
|
||||
{
|
||||
$homework = $this->createHomework();
|
||||
$homework->supprimer(new DateTimeImmutable('2026-03-14'));
|
||||
|
||||
$this->expectException(DevoirDejaSupprimeException::class);
|
||||
|
||||
$homework->supprimer(new DateTimeImmutable('2026-03-15'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function reconstituteRestoresAllPropertiesWithoutEvents(): void
|
||||
{
|
||||
$id = HomeworkId::generate();
|
||||
$tenantId = TenantId::fromString(self::TENANT_ID);
|
||||
$classId = ClassId::fromString(self::CLASS_ID);
|
||||
$subjectId = SubjectId::fromString(self::SUBJECT_ID);
|
||||
$teacherId = UserId::fromString(self::TEACHER_ID);
|
||||
$dueDate = new DateTimeImmutable('2026-04-15');
|
||||
$createdAt = new DateTimeImmutable('2026-03-12 10:00:00');
|
||||
$updatedAt = new DateTimeImmutable('2026-03-13 14:00:00');
|
||||
|
||||
$homework = Homework::reconstitute(
|
||||
id: $id,
|
||||
tenantId: $tenantId,
|
||||
classId: $classId,
|
||||
subjectId: $subjectId,
|
||||
teacherId: $teacherId,
|
||||
title: 'Exercices chapitre 5',
|
||||
description: 'Faire les exercices',
|
||||
dueDate: $dueDate,
|
||||
status: HomeworkStatus::PUBLISHED,
|
||||
createdAt: $createdAt,
|
||||
updatedAt: $updatedAt,
|
||||
);
|
||||
|
||||
self::assertTrue($homework->id->equals($id));
|
||||
self::assertTrue($homework->tenantId->equals($tenantId));
|
||||
self::assertTrue($homework->classId->equals($classId));
|
||||
self::assertTrue($homework->subjectId->equals($subjectId));
|
||||
self::assertTrue($homework->teacherId->equals($teacherId));
|
||||
self::assertSame('Exercices chapitre 5', $homework->title);
|
||||
self::assertSame('Faire les exercices', $homework->description);
|
||||
self::assertEquals($dueDate, $homework->dueDate);
|
||||
self::assertSame(HomeworkStatus::PUBLISHED, $homework->status);
|
||||
self::assertEquals($createdAt, $homework->createdAt);
|
||||
self::assertEquals($updatedAt, $homework->updatedAt);
|
||||
self::assertEmpty($homework->pullDomainEvents());
|
||||
}
|
||||
|
||||
private function createHomework(): Homework
|
||||
{
|
||||
return Homework::creer(
|
||||
tenantId: TenantId::fromString(self::TENANT_ID),
|
||||
classId: ClassId::fromString(self::CLASS_ID),
|
||||
subjectId: SubjectId::fromString(self::SUBJECT_ID),
|
||||
teacherId: UserId::fromString(self::TEACHER_ID),
|
||||
title: 'Exercices chapitre 5',
|
||||
description: 'Faire les exercices 1 à 10',
|
||||
dueDate: new DateTimeImmutable('2026-04-15'),
|
||||
now: new DateTimeImmutable('2026-03-12 10:00:00'),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user