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('

Ma réponse

', $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: '

Première version

')); $updated = $handler($this->createCommand(responseHtml: '

Version modifiée

')); self::assertSame('

Version modifiée

', $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: '

Tentative de modification

')); } #[Test] public function itSanitizesHtmlResponse(): void { $sanitizer = new class implements HtmlSanitizer { public function sanitize(string $html): string { return strip_tags($html, '

'); } }; $handler = $this->createHandler(htmlSanitizer: $sanitizer); $command = $this->createCommand(responseHtml: '

Texte

'); $submission = $handler($command); self::assertSame('

Texte

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 = '

Ma réponse

'): SaveDraftSubmissionCommand { return new SaveDraftSubmissionCommand( tenantId: self::TENANT_ID, homeworkId: (string) $this->homework->id, studentId: self::STUDENT_ID, responseHtml: $responseHtml, ); } }