createSlot(); self::assertTrue($slot->tenantId->equals(TenantId::fromString(self::TENANT_ID))); self::assertTrue($slot->classId->equals(ClassId::fromString(self::CLASS_ID))); self::assertTrue($slot->subjectId->equals(SubjectId::fromString(self::SUBJECT_ID))); self::assertTrue($slot->teacherId->equals(UserId::fromString(self::TEACHER_ID))); self::assertSame(DayOfWeek::MONDAY, $slot->dayOfWeek); self::assertSame('08:00', $slot->timeSlot->startTime); self::assertSame('09:00', $slot->timeSlot->endTime); self::assertNull($slot->room); self::assertTrue($slot->isRecurring); } #[Test] public function creerRecordsCoursCreeEvent(): void { $slot = $this->createSlot(); $events = $slot->pullDomainEvents(); self::assertCount(1, $events); self::assertInstanceOf(CoursCree::class, $events[0]); self::assertTrue($slot->id->equals($events[0]->slotId)); } #[Test] public function creerWithRoomSetsRoom(): void { $slot = $this->createSlot(room: 'Salle 101'); self::assertSame('Salle 101', $slot->room); } #[Test] public function modifierUpdatesProperties(): void { $slot = $this->createSlot(); $slot->pullDomainEvents(); $newSubjectId = SubjectId::fromString('550e8400-e29b-41d4-a716-446655440031'); $newTeacherId = UserId::fromString('550e8400-e29b-41d4-a716-446655440011'); $newTimeSlot = new TimeSlot('10:00', '11:00'); $now = new DateTimeImmutable('2026-03-02 15:00:00'); $newClassId = ClassId::fromString('550e8400-e29b-41d4-a716-446655440021'); $slot->modifier( classId: $newClassId, subjectId: $newSubjectId, teacherId: $newTeacherId, dayOfWeek: DayOfWeek::TUESDAY, timeSlot: $newTimeSlot, room: 'Salle 202', at: $now, ); self::assertTrue($slot->classId->equals($newClassId)); self::assertTrue($slot->subjectId->equals($newSubjectId)); self::assertTrue($slot->teacherId->equals($newTeacherId)); self::assertSame(DayOfWeek::TUESDAY, $slot->dayOfWeek); self::assertSame('10:00', $slot->timeSlot->startTime); self::assertSame('11:00', $slot->timeSlot->endTime); self::assertSame('Salle 202', $slot->room); self::assertEquals($now, $slot->updatedAt); } #[Test] public function modifierRecordsCoursModifieEvent(): void { $slot = $this->createSlot(); $slot->pullDomainEvents(); $now = new DateTimeImmutable('2026-03-02 15:00:00'); $slot->modifier( classId: $slot->classId, subjectId: $slot->subjectId, teacherId: $slot->teacherId, dayOfWeek: DayOfWeek::TUESDAY, timeSlot: new TimeSlot('10:00', '11:00'), room: null, at: $now, ); $events = $slot->pullDomainEvents(); self::assertCount(1, $events); self::assertInstanceOf(CoursModifie::class, $events[0]); self::assertTrue($slot->id->equals($events[0]->slotId)); } #[Test] public function supprimerRecordsCoursSupprime(): void { $slot = $this->createSlot(); $slot->pullDomainEvents(); $now = new DateTimeImmutable('2026-03-02 15:00:00'); $slot->supprimer($now); $events = $slot->pullDomainEvents(); self::assertCount(1, $events); self::assertInstanceOf(CoursSupprime::class, $events[0]); self::assertTrue($slot->id->equals($events[0]->slotId)); } #[Test] public function reconstituteRestoresAllPropertiesWithoutEvents(): void { $id = ScheduleSlotId::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); $timeSlot = new TimeSlot('14:00', '15:30'); $createdAt = new DateTimeImmutable('2026-03-01 10:00:00'); $updatedAt = new DateTimeImmutable('2026-03-02 14:00:00'); $slot = ScheduleSlot::reconstitute( id: $id, tenantId: $tenantId, classId: $classId, subjectId: $subjectId, teacherId: $teacherId, dayOfWeek: DayOfWeek::FRIDAY, timeSlot: $timeSlot, room: 'Salle 305', isRecurring: false, createdAt: $createdAt, updatedAt: $updatedAt, ); self::assertTrue($slot->id->equals($id)); self::assertTrue($slot->tenantId->equals($tenantId)); self::assertTrue($slot->classId->equals($classId)); self::assertTrue($slot->subjectId->equals($subjectId)); self::assertTrue($slot->teacherId->equals($teacherId)); self::assertSame(DayOfWeek::FRIDAY, $slot->dayOfWeek); self::assertSame('14:00', $slot->timeSlot->startTime); self::assertSame('15:30', $slot->timeSlot->endTime); self::assertSame('Salle 305', $slot->room); self::assertFalse($slot->isRecurring); self::assertEquals($createdAt, $slot->createdAt); self::assertEquals($updatedAt, $slot->updatedAt); self::assertEmpty($slot->pullDomainEvents()); } #[Test] public function creerWithRecurrenceBoundsSetsDates(): void { $recurrenceStart = new DateTimeImmutable('2026-09-01'); $recurrenceEnd = new DateTimeImmutable('2027-07-04'); $slot = ScheduleSlot::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), dayOfWeek: DayOfWeek::MONDAY, timeSlot: new TimeSlot('08:00', '09:00'), room: null, isRecurring: true, now: new DateTimeImmutable('2026-09-01 10:00:00'), recurrenceStart: $recurrenceStart, recurrenceEnd: $recurrenceEnd, ); self::assertEquals($recurrenceStart, $slot->recurrenceStart); self::assertEquals($recurrenceEnd, $slot->recurrenceEnd); } #[Test] public function creerWithoutRecurrenceBoundsDefaultsToNull(): void { $slot = $this->createSlot(); self::assertNull($slot->recurrenceStart); self::assertNull($slot->recurrenceEnd); } #[Test] public function isActiveOnDateReturnsTrueForMatchingDayWithinBounds(): void { $slot = $this->createRecurringSlot( dayOfWeek: DayOfWeek::MONDAY, recurrenceStart: new DateTimeImmutable('2026-09-01'), recurrenceEnd: new DateTimeImmutable('2027-07-04'), ); // 2026-09-07 is a Monday within bounds self::assertTrue($slot->isActiveOnDate(new DateTimeImmutable('2026-09-07'))); } #[Test] public function isActiveOnDateReturnsFalseForWrongDayOfWeek(): void { $slot = $this->createRecurringSlot( dayOfWeek: DayOfWeek::MONDAY, recurrenceStart: new DateTimeImmutable('2026-09-01'), recurrenceEnd: new DateTimeImmutable('2027-07-04'), ); // 2026-09-08 is a Tuesday self::assertFalse($slot->isActiveOnDate(new DateTimeImmutable('2026-09-08'))); } #[Test] public function isActiveOnDateReturnsFalseBeforeRecurrenceStart(): void { $slot = $this->createRecurringSlot( dayOfWeek: DayOfWeek::MONDAY, recurrenceStart: new DateTimeImmutable('2026-09-01'), recurrenceEnd: new DateTimeImmutable('2027-07-04'), ); // 2026-08-25 is a Monday but before start self::assertFalse($slot->isActiveOnDate(new DateTimeImmutable('2026-08-25'))); } #[Test] public function isActiveOnDateReturnsFalseAfterRecurrenceEnd(): void { $slot = $this->createRecurringSlot( dayOfWeek: DayOfWeek::MONDAY, recurrenceStart: new DateTimeImmutable('2026-09-01'), recurrenceEnd: new DateTimeImmutable('2027-07-04'), ); // 2027-07-07 is a Monday but after end self::assertFalse($slot->isActiveOnDate(new DateTimeImmutable('2027-07-07'))); } #[Test] public function isActiveOnDateReturnsTrueWithNoBounds(): void { $slot = $this->createRecurringSlot( dayOfWeek: DayOfWeek::MONDAY, recurrenceStart: null, recurrenceEnd: null, ); // Any Monday should work self::assertTrue($slot->isActiveOnDate(new DateTimeImmutable('2026-09-07'))); } #[Test] public function isActiveOnDateReturnsFalseForNonRecurringSlot(): void { $slot = ScheduleSlot::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), dayOfWeek: DayOfWeek::MONDAY, timeSlot: new TimeSlot('08:00', '09:00'), room: null, isRecurring: false, now: new DateTimeImmutable('2026-09-01 10:00:00'), ); self::assertFalse($slot->isActiveOnDate(new DateTimeImmutable('2026-09-07'))); } #[Test] public function isActiveOnDateIncludesBoundaryDates(): void { $slot = $this->createRecurringSlot( dayOfWeek: DayOfWeek::MONDAY, recurrenceStart: new DateTimeImmutable('2026-09-07'), recurrenceEnd: new DateTimeImmutable('2026-09-07'), ); // Exact start = exact end date self::assertTrue($slot->isActiveOnDate(new DateTimeImmutable('2026-09-07'))); } #[Test] public function reconstituteRestoresRecurrenceBounds(): void { $recurrenceStart = new DateTimeImmutable('2026-09-01'); $recurrenceEnd = new DateTimeImmutable('2027-07-04'); $slot = ScheduleSlot::reconstitute( id: ScheduleSlotId::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), dayOfWeek: DayOfWeek::FRIDAY, timeSlot: new TimeSlot('14:00', '15:30'), room: null, isRecurring: true, createdAt: new DateTimeImmutable('2026-03-01 10:00:00'), updatedAt: new DateTimeImmutable('2026-03-02 14:00:00'), recurrenceStart: $recurrenceStart, recurrenceEnd: $recurrenceEnd, ); self::assertEquals($recurrenceStart, $slot->recurrenceStart); self::assertEquals($recurrenceEnd, $slot->recurrenceEnd); } private function createSlot(?string $room = null): ScheduleSlot { return ScheduleSlot::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), dayOfWeek: DayOfWeek::MONDAY, timeSlot: new TimeSlot('08:00', '09:00'), room: $room, isRecurring: true, now: new DateTimeImmutable('2026-03-01 10:00:00'), ); } private function createRecurringSlot( DayOfWeek $dayOfWeek, ?DateTimeImmutable $recurrenceStart, ?DateTimeImmutable $recurrenceEnd, ): ScheduleSlot { return ScheduleSlot::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), dayOfWeek: $dayOfWeek, timeSlot: new TimeSlot('08:00', '09:00'), room: null, isRecurring: true, now: new DateTimeImmutable('2026-09-01 10:00:00'), recurrenceStart: $recurrenceStart, recurrenceEnd: $recurrenceEnd, ); } }