subjectRepository = new InMemorySubjectRepository(); $this->clock = new class implements Clock { public function now(): DateTimeImmutable { return new DateTimeImmutable('2026-02-01 10:00:00'); } }; } #[Test] public function itUpdatesSubjectName(): void { $subject = $this->createAndSaveSubject(); $handler = new UpdateSubjectHandler($this->subjectRepository, $this->clock); $command = new UpdateSubjectCommand( subjectId: (string) $subject->id, tenantId: self::TENANT_ID, schoolId: self::SCHOOL_ID, name: 'Mathématiques avancées', ); $updatedSubject = $handler($command); self::assertSame('Mathématiques avancées', (string) $updatedSubject->name); } #[Test] public function itUpdatesSubjectCode(): void { $subject = $this->createAndSaveSubject(); $handler = new UpdateSubjectHandler($this->subjectRepository, $this->clock); $command = new UpdateSubjectCommand( subjectId: (string) $subject->id, tenantId: self::TENANT_ID, schoolId: self::SCHOOL_ID, code: 'MATHS', ); $updatedSubject = $handler($command); self::assertSame('MATHS', (string) $updatedSubject->code); } #[Test] public function itUpdatesSubjectColor(): void { $subject = $this->createAndSaveSubject(); $handler = new UpdateSubjectHandler($this->subjectRepository, $this->clock); $command = new UpdateSubjectCommand( subjectId: (string) $subject->id, tenantId: self::TENANT_ID, schoolId: self::SCHOOL_ID, color: '#EF4444', ); $updatedSubject = $handler($command); self::assertNotNull($updatedSubject->color); self::assertSame('#EF4444', (string) $updatedSubject->color); } #[Test] public function itClearsSubjectColor(): void { $subject = $this->createAndSaveSubject(); $handler = new UpdateSubjectHandler($this->subjectRepository, $this->clock); $command = new UpdateSubjectCommand( subjectId: (string) $subject->id, tenantId: self::TENANT_ID, schoolId: self::SCHOOL_ID, clearColor: true, ); $updatedSubject = $handler($command); self::assertNull($updatedSubject->color); } #[Test] public function itUpdatesSubjectDescription(): void { $subject = $this->createAndSaveSubject(); $handler = new UpdateSubjectHandler($this->subjectRepository, $this->clock); $command = new UpdateSubjectCommand( subjectId: (string) $subject->id, tenantId: self::TENANT_ID, schoolId: self::SCHOOL_ID, description: 'Cours de mathématiques pour tous les niveaux', ); $updatedSubject = $handler($command); self::assertSame('Cours de mathématiques pour tous les niveaux', $updatedSubject->description); } #[Test] public function itClearsSubjectDescription(): void { $subject = $this->createAndSaveSubject(); // First add a description $subject->decrire('Une description', new DateTimeImmutable()); $this->subjectRepository->save($subject); $handler = new UpdateSubjectHandler($this->subjectRepository, $this->clock); $command = new UpdateSubjectCommand( subjectId: (string) $subject->id, tenantId: self::TENANT_ID, schoolId: self::SCHOOL_ID, clearDescription: true, ); $updatedSubject = $handler($command); self::assertNull($updatedSubject->description); } #[Test] public function itThrowsExceptionWhenChangingToExistingCode(): void { // Create first subject with code MATH $subject1 = $this->createAndSaveSubject(); // Create second subject with code FR $subject2 = Subject::creer( tenantId: TenantId::fromString(self::TENANT_ID), schoolId: SchoolId::fromString(self::SCHOOL_ID), name: new SubjectName('Français'), code: new SubjectCode('FR'), color: new SubjectColor('#EF4444'), createdAt: new DateTimeImmutable('2026-01-15 10:00:00'), ); $this->subjectRepository->save($subject2); $handler = new UpdateSubjectHandler($this->subjectRepository, $this->clock); // Try to change subject2's code to MATH (which already exists) $this->expectException(SubjectDejaExistanteException::class); $command = new UpdateSubjectCommand( subjectId: (string) $subject2->id, tenantId: self::TENANT_ID, schoolId: self::SCHOOL_ID, code: 'MATH', ); $handler($command); } #[Test] public function itAllowsKeepingSameCode(): void { $subject = $this->createAndSaveSubject(); $handler = new UpdateSubjectHandler($this->subjectRepository, $this->clock); // Update name but keep same code $command = new UpdateSubjectCommand( subjectId: (string) $subject->id, tenantId: self::TENANT_ID, schoolId: self::SCHOOL_ID, name: 'Maths avancées', code: 'MATH', // Same code ); $updatedSubject = $handler($command); self::assertSame('Maths avancées', (string) $updatedSubject->name); self::assertSame('MATH', (string) $updatedSubject->code); } #[Test] public function itUpdatesMultipleFieldsAtOnce(): void { $subject = $this->createAndSaveSubject(); $handler = new UpdateSubjectHandler($this->subjectRepository, $this->clock); $command = new UpdateSubjectCommand( subjectId: (string) $subject->id, tenantId: self::TENANT_ID, schoolId: self::SCHOOL_ID, name: 'Maths avancées', color: '#10B981', description: 'Niveau supérieur', ); $updatedSubject = $handler($command); self::assertSame('Maths avancées', (string) $updatedSubject->name); self::assertNotNull($updatedSubject->color); self::assertSame('#10B981', (string) $updatedSubject->color); self::assertSame('Niveau supérieur', $updatedSubject->description); } #[Test] public function itThrowsExceptionWhenSubjectBelongsToDifferentTenant(): void { $subject = $this->createAndSaveSubject(); $handler = new UpdateSubjectHandler($this->subjectRepository, $this->clock); $this->expectException(SubjectNotFoundException::class); // Try to update with a different tenant ID (tenant isolation violation) $command = new UpdateSubjectCommand( subjectId: (string) $subject->id, tenantId: self::OTHER_TENANT_ID, schoolId: self::SCHOOL_ID, name: 'Tentative de modification', ); $handler($command); } private function createAndSaveSubject(): Subject { $subject = Subject::creer( tenantId: TenantId::fromString(self::TENANT_ID), schoolId: SchoolId::fromString(self::SCHOOL_ID), name: new SubjectName('Mathématiques'), code: new SubjectCode('MATH'), color: new SubjectColor('#3B82F6'), createdAt: new DateTimeImmutable('2026-01-15 10:00:00'), ); $this->subjectRepository->save($subject); return $subject; } }