createReplacement(); self::assertSame(ReplacementStatus::ACTIVE, $replacement->status); self::assertNull($replacement->endedAt); } #[Test] public function designerRecordsRemplacementDesigneEvent(): void { $replacement = $this->createReplacement(); $events = $replacement->pullDomainEvents(); self::assertCount(1, $events); self::assertInstanceOf(RemplacementDesigne::class, $events[0]); self::assertSame($replacement->id, $events[0]->replacementId); self::assertTrue($replacement->replacedTeacherId->equals($events[0]->replacedTeacherId)); self::assertTrue($replacement->replacementTeacherId->equals($events[0]->replacementTeacherId)); } #[Test] public function designerSetsAllProperties(): void { $tenantId = TenantId::fromString(self::TENANT_ID); $replacedTeacherId = UserId::fromString(self::REPLACED_TEACHER_ID); $replacementTeacherId = UserId::fromString(self::REPLACEMENT_TEACHER_ID); $startDate = new DateTimeImmutable('2026-03-01'); $endDate = new DateTimeImmutable('2026-03-31'); $classes = $this->createClasses(); $createdBy = UserId::fromString(self::CREATED_BY_ID); $now = new DateTimeImmutable('2026-02-15 10:00:00'); $replacement = TeacherReplacement::designer( tenantId: $tenantId, replacedTeacherId: $replacedTeacherId, replacementTeacherId: $replacementTeacherId, startDate: $startDate, endDate: $endDate, classes: $classes, reason: 'Congé maladie', createdBy: $createdBy, now: $now, ); self::assertTrue($replacement->tenantId->equals($tenantId)); self::assertTrue($replacement->replacedTeacherId->equals($replacedTeacherId)); self::assertTrue($replacement->replacementTeacherId->equals($replacementTeacherId)); self::assertEquals($startDate, $replacement->startDate); self::assertEquals($endDate, $replacement->endDate); self::assertCount(1, $replacement->classes); self::assertSame('Congé maladie', $replacement->reason); self::assertTrue($replacement->createdBy->equals($createdBy)); self::assertEquals($now, $replacement->createdAt); self::assertEquals($now, $replacement->updatedAt); self::assertNull($replacement->endedAt); } #[Test] public function designerThrowsWhenSameTeacher(): void { $this->expectException(RemplacementSameTeacherException::class); $sameTeacherId = UserId::fromString(self::REPLACED_TEACHER_ID); TeacherReplacement::designer( tenantId: TenantId::fromString(self::TENANT_ID), replacedTeacherId: $sameTeacherId, replacementTeacherId: $sameTeacherId, startDate: new DateTimeImmutable('2026-03-01'), endDate: new DateTimeImmutable('2026-03-31'), classes: $this->createClasses(), reason: null, createdBy: UserId::fromString(self::CREATED_BY_ID), now: new DateTimeImmutable('2026-02-15 10:00:00'), ); } #[Test] public function designerThrowsWhenEndDateBeforeStartDate(): void { $this->expectException(DatesRemplacementInvalidesException::class); TeacherReplacement::designer( tenantId: TenantId::fromString(self::TENANT_ID), replacedTeacherId: UserId::fromString(self::REPLACED_TEACHER_ID), replacementTeacherId: UserId::fromString(self::REPLACEMENT_TEACHER_ID), startDate: new DateTimeImmutable('2026-03-31'), endDate: new DateTimeImmutable('2026-03-01'), classes: $this->createClasses(), reason: null, createdBy: UserId::fromString(self::CREATED_BY_ID), now: new DateTimeImmutable('2026-02-15 10:00:00'), ); } #[Test] public function isActiveReturnsTrueWhenWithinDateRange(): void { $replacement = $this->createReplacement(); self::assertTrue($replacement->isActive(new DateTimeImmutable('2026-03-15'))); } #[Test] public function isActiveReturnsTrueOnStartDate(): void { $replacement = $this->createReplacement(); self::assertTrue($replacement->isActive(new DateTimeImmutable('2026-03-01'))); } #[Test] public function isActiveReturnsTrueOnEndDate(): void { $replacement = $this->createReplacement(); self::assertTrue($replacement->isActive(new DateTimeImmutable('2026-03-31'))); } #[Test] public function isActiveReturnsFalseBeforeStartDate(): void { $replacement = $this->createReplacement(); self::assertFalse($replacement->isActive(new DateTimeImmutable('2026-02-28'))); } #[Test] public function isActiveReturnsFalseAfterEndDate(): void { $replacement = $this->createReplacement(); self::assertFalse($replacement->isActive(new DateTimeImmutable('2026-04-01'))); } #[Test] public function isActiveReturnsFalseWhenEnded(): void { $replacement = $this->createReplacement(); $replacement->terminer(new DateTimeImmutable('2026-03-15')); self::assertFalse($replacement->isActive(new DateTimeImmutable('2026-03-15'))); } #[Test] public function isExpiredReturnsTrueAfterEndDate(): void { $replacement = $this->createReplacement(); self::assertTrue($replacement->isExpired(new DateTimeImmutable('2026-04-01'))); } #[Test] public function isExpiredReturnsFalseWhenStillActive(): void { $replacement = $this->createReplacement(); self::assertFalse($replacement->isExpired(new DateTimeImmutable('2026-03-15'))); } #[Test] public function isExpiredReturnsFalseWhenAlreadyEnded(): void { $replacement = $this->createReplacement(); $replacement->terminer(new DateTimeImmutable('2026-03-15')); self::assertFalse($replacement->isExpired(new DateTimeImmutable('2026-04-01'))); } #[Test] public function terminerChangesStatusAndRecordsEvent(): void { $replacement = $this->createReplacement(); $replacement->pullDomainEvents(); $at = new DateTimeImmutable('2026-03-15 14:00:00'); $replacement->terminer($at); self::assertSame(ReplacementStatus::ENDED, $replacement->status); self::assertEquals($at, $replacement->endedAt); self::assertEquals($at, $replacement->updatedAt); $events = $replacement->pullDomainEvents(); self::assertCount(1, $events); self::assertInstanceOf(RemplacementTermine::class, $events[0]); self::assertSame($replacement->id, $events[0]->replacementId); } #[Test] public function terminerThrowsWhenAlreadyEnded(): void { $replacement = $this->createReplacement(); $replacement->terminer(new DateTimeImmutable('2026-03-15')); $this->expectException(RemplacementDejaTermineException::class); $replacement->terminer(new DateTimeImmutable('2026-03-16')); } #[Test] public function couvreClasseMatiereReturnsTrueForMatchingPair(): void { $replacement = $this->createReplacement(); $pair = new ClassSubjectPair( ClassId::fromString(self::CLASS_ID), SubjectId::fromString(self::SUBJECT_ID), ); self::assertTrue($replacement->couvreClasseMatiere($pair)); } #[Test] public function couvreClasseMatiereReturnsFalseForNonMatchingPair(): void { $replacement = $this->createReplacement(); $pair = new ClassSubjectPair( ClassId::fromString('550e8400-e29b-41d4-a716-446655440099'), SubjectId::fromString(self::SUBJECT_ID), ); self::assertFalse($replacement->couvreClasseMatiere($pair)); } #[Test] public function reconstituteRestoresAllPropertiesWithoutEvents(): void { $id = TeacherReplacementId::generate(); $tenantId = TenantId::fromString(self::TENANT_ID); $replacedTeacherId = UserId::fromString(self::REPLACED_TEACHER_ID); $replacementTeacherId = UserId::fromString(self::REPLACEMENT_TEACHER_ID); $startDate = new DateTimeImmutable('2026-03-01'); $endDate = new DateTimeImmutable('2026-03-31'); $classes = $this->createClasses(); $createdBy = UserId::fromString(self::CREATED_BY_ID); $createdAt = new DateTimeImmutable('2026-02-15 10:00:00'); $endedAt = new DateTimeImmutable('2026-03-20 10:00:00'); $updatedAt = new DateTimeImmutable('2026-03-20 10:00:00'); $replacement = TeacherReplacement::reconstitute( id: $id, tenantId: $tenantId, replacedTeacherId: $replacedTeacherId, replacementTeacherId: $replacementTeacherId, startDate: $startDate, endDate: $endDate, status: ReplacementStatus::ENDED, classes: $classes, reason: 'Congé maladie', createdBy: $createdBy, createdAt: $createdAt, endedAt: $endedAt, updatedAt: $updatedAt, ); self::assertTrue($replacement->id->equals($id)); self::assertTrue($replacement->tenantId->equals($tenantId)); self::assertTrue($replacement->replacedTeacherId->equals($replacedTeacherId)); self::assertTrue($replacement->replacementTeacherId->equals($replacementTeacherId)); self::assertEquals($startDate, $replacement->startDate); self::assertEquals($endDate, $replacement->endDate); self::assertSame(ReplacementStatus::ENDED, $replacement->status); self::assertCount(1, $replacement->classes); self::assertSame('Congé maladie', $replacement->reason); self::assertTrue($replacement->createdBy->equals($createdBy)); self::assertEquals($createdAt, $replacement->createdAt); self::assertEquals($endedAt, $replacement->endedAt); self::assertEquals($updatedAt, $replacement->updatedAt); self::assertEmpty($replacement->pullDomainEvents()); } private function createReplacement(): TeacherReplacement { return TeacherReplacement::designer( tenantId: TenantId::fromString(self::TENANT_ID), replacedTeacherId: UserId::fromString(self::REPLACED_TEACHER_ID), replacementTeacherId: UserId::fromString(self::REPLACEMENT_TEACHER_ID), startDate: new DateTimeImmutable('2026-03-01'), endDate: new DateTimeImmutable('2026-03-31'), classes: $this->createClasses(), reason: null, createdBy: UserId::fromString(self::CREATED_BY_ID), now: new DateTimeImmutable('2026-02-15 10:00:00'), ); } /** @return array */ private function createClasses(): array { return [ new ClassSubjectPair( ClassId::fromString(self::CLASS_ID), SubjectId::fromString(self::SUBJECT_ID), ), ]; } }