Files
Classeo/backend/tests/Unit/Scolarite/Application/Command/SaveDraftSubmission/SaveDraftSubmissionHandlerTest.php
Mathias STRASSER df25a8cbb0
Some checks failed
CI / Backend Tests (push) Has been cancelled
CI / Frontend Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Naming Conventions (push) Has been cancelled
CI / Build Check (push) Has been cancelled
feat: Permettre à l'élève de rendre un devoir avec réponse texte et pièces jointes
L'élève peut désormais répondre à un devoir via un éditeur WYSIWYG,
joindre des fichiers (PDF, JPEG, PNG, DOCX), sauvegarder un brouillon
et soumettre définitivement son rendu. Le système détecte automatiquement
les soumissions en retard par rapport à la date d'échéance.

Côté enseignant, une page dédiée affiche la liste complète des élèves
avec leur statut (soumis, en retard, brouillon, non rendu), le détail
de chaque rendu avec ses pièces jointes téléchargeables, et les
statistiques de rendus par classe.
2026-03-25 19:38:47 +01:00

221 lines
7.6 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Tests\Unit\Scolarite\Application\Command\SaveDraftSubmission;
use App\Administration\Domain\Model\SchoolClass\ClassId;
use App\Administration\Domain\Model\User\UserId;
use App\Scolarite\Application\Command\SaveDraftSubmission\SaveDraftSubmissionCommand;
use App\Scolarite\Application\Command\SaveDraftSubmission\SaveDraftSubmissionHandler;
use App\Scolarite\Application\Port\HtmlSanitizer;
use App\Scolarite\Application\Port\StudentClassReader;
use App\Scolarite\Domain\Exception\EleveNonAffecteAuDevoirException;
use App\Scolarite\Domain\Exception\RenduDejaSoumisException;
use App\Scolarite\Domain\Model\Homework\Homework;
use App\Scolarite\Domain\Model\Homework\HomeworkId;
use App\Scolarite\Domain\Model\HomeworkSubmission\SubmissionStatus;
use App\Scolarite\Infrastructure\Persistence\InMemory\InMemoryHomeworkRepository;
use App\Scolarite\Infrastructure\Persistence\InMemory\InMemoryHomeworkSubmissionRepository;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
final class SaveDraftSubmissionHandlerTest 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';
private const string STUDENT_ID = '550e8400-e29b-41d4-a716-446655440060';
private InMemoryHomeworkRepository $homeworkRepository;
private InMemoryHomeworkSubmissionRepository $submissionRepository;
private Clock $clock;
private Homework $homework;
protected function setUp(): void
{
$this->homeworkRepository = new InMemoryHomeworkRepository();
$this->submissionRepository = new InMemoryHomeworkSubmissionRepository();
$this->clock = new class implements Clock {
public function now(): DateTimeImmutable
{
return new DateTimeImmutable('2026-03-24 10:00:00');
}
};
$this->homework = Homework::creer(
tenantId: TenantId::fromString(self::TENANT_ID),
classId: ClassId::fromString(self::CLASS_ID),
subjectId: \App\Administration\Domain\Model\Subject\SubjectId::fromString(self::SUBJECT_ID),
teacherId: UserId::fromString(self::TEACHER_ID),
title: 'Exercices chapitre 5',
description: null,
dueDate: new DateTimeImmutable('2026-04-15'),
now: new DateTimeImmutable('2026-03-12 10:00:00'),
);
$this->homeworkRepository->save($this->homework);
}
#[Test]
public function itCreatesNewDraftSubmission(): void
{
$handler = $this->createHandler();
$command = $this->createCommand();
$submission = $handler($command);
self::assertSame(SubmissionStatus::DRAFT, $submission->status);
self::assertSame('<p>Ma réponse</p>', $submission->responseHtml);
self::assertNull($submission->submittedAt);
}
#[Test]
public function itPersistsDraftInRepository(): void
{
$handler = $this->createHandler();
$command = $this->createCommand();
$created = $handler($command);
$found = $this->submissionRepository->findByHomeworkAndStudent(
HomeworkId::fromString((string) $this->homework->id),
UserId::fromString(self::STUDENT_ID),
TenantId::fromString(self::TENANT_ID),
);
self::assertNotNull($found);
self::assertTrue($found->id->equals($created->id));
}
#[Test]
public function itUpdatesExistingDraft(): void
{
$handler = $this->createHandler();
$handler($this->createCommand(responseHtml: '<p>Première version</p>'));
$updated = $handler($this->createCommand(responseHtml: '<p>Version modifiée</p>'));
self::assertSame('<p>Version modifiée</p>', $updated->responseHtml);
self::assertSame(SubmissionStatus::DRAFT, $updated->status);
}
#[Test]
public function itThrowsWhenStudentNotInClass(): void
{
$handler = $this->createHandler(studentClassId: null);
$this->expectException(EleveNonAffecteAuDevoirException::class);
$handler($this->createCommand());
}
#[Test]
public function itThrowsWhenStudentInDifferentClass(): void
{
$handler = $this->createHandler(studentClassId: '550e8400-e29b-41d4-a716-446655440099');
$this->expectException(EleveNonAffecteAuDevoirException::class);
$handler($this->createCommand());
}
#[Test]
public function itThrowsWhenUpdatingSubmittedSubmission(): void
{
$handler = $this->createHandler();
$handler($this->createCommand());
// Soumettre le rendu
$submission = $this->submissionRepository->findByHomeworkAndStudent(
HomeworkId::fromString((string) $this->homework->id),
UserId::fromString(self::STUDENT_ID),
TenantId::fromString(self::TENANT_ID),
);
self::assertNotNull($submission);
$submission->soumettre(
dueDate: new DateTimeImmutable('2026-04-15'),
now: new DateTimeImmutable('2026-03-24 11:00:00'),
);
$this->submissionRepository->save($submission);
$this->expectException(RenduDejaSoumisException::class);
$handler($this->createCommand(responseHtml: '<p>Tentative de modification</p>'));
}
#[Test]
public function itSanitizesHtmlResponse(): void
{
$sanitizer = new class implements HtmlSanitizer {
public function sanitize(string $html): string
{
return strip_tags($html, '<p><strong><em>');
}
};
$handler = $this->createHandler(htmlSanitizer: $sanitizer);
$command = $this->createCommand(responseHtml: '<p>Texte</p><script>alert("xss")</script>');
$submission = $handler($command);
self::assertSame('<p>Texte</p>alert("xss")', $submission->responseHtml);
}
#[Test]
public function itAllowsNullResponse(): void
{
$handler = $this->createHandler();
$command = $this->createCommand(responseHtml: null);
$submission = $handler($command);
self::assertNull($submission->responseHtml);
}
private function createHandler(
?string $studentClassId = self::CLASS_ID,
?HtmlSanitizer $htmlSanitizer = null,
): SaveDraftSubmissionHandler {
$studentClassReader = new class($studentClassId) implements StudentClassReader {
public function __construct(private readonly ?string $classId)
{
}
public function currentClassId(string $studentId, TenantId $tenantId): ?string
{
return $this->classId;
}
};
$sanitizer = $htmlSanitizer ?? new class implements HtmlSanitizer {
public function sanitize(string $html): string
{
return $html;
}
};
return new SaveDraftSubmissionHandler(
$this->homeworkRepository,
$this->submissionRepository,
$studentClassReader,
$sanitizer,
$this->clock,
);
}
private function createCommand(?string $responseHtml = '<p>Ma réponse</p>'): SaveDraftSubmissionCommand
{
return new SaveDraftSubmissionCommand(
tenantId: self::TENANT_ID,
homeworkId: (string) $this->homework->id,
studentId: self::STUDENT_ID,
responseHtml: $responseHtml,
);
}
}