slotRepository = new InMemoryScheduleSlotRepository(); $this->exceptionRepository = new InMemoryScheduleExceptionRepository(); } #[Test] public function returnsEmptyWhenParentHasNoChildren(): void { $handler = $this->createHandler(children: []); $result = $handler(new GetChildrenScheduleQuery( parentId: self::PARENT_ID, tenantId: self::TENANT_ID, date: '2026-03-02', )); self::assertSame([], $result); } #[Test] public function returnsScheduleForSingleChild(): void { $this->saveRecurringSlot(self::CLASS1_ID, DayOfWeek::MONDAY, '08:00', '09:00'); $handler = $this->createHandler( children: [ ['studentId' => self::CHILD1_ID, 'firstName' => 'Alice', 'lastName' => 'Dupont'], ], classMapping: [self::CHILD1_ID => self::CLASS1_ID], ); $result = $handler(new GetChildrenScheduleQuery( parentId: self::PARENT_ID, tenantId: self::TENANT_ID, date: '2026-03-02', // Monday )); self::assertCount(1, $result); self::assertInstanceOf(ChildScheduleDto::class, $result[0]); self::assertSame(self::CHILD1_ID, $result[0]->childId); self::assertSame('Alice', $result[0]->firstName); self::assertSame('Dupont', $result[0]->lastName); self::assertCount(1, $result[0]->slots); } #[Test] public function returnsScheduleForMultipleChildren(): void { $this->saveRecurringSlot(self::CLASS1_ID, DayOfWeek::MONDAY, '08:00', '09:00'); $this->saveRecurringSlot(self::CLASS2_ID, DayOfWeek::MONDAY, '10:00', '11:00'); $this->saveRecurringSlot(self::CLASS2_ID, DayOfWeek::TUESDAY, '14:00', '15:00'); $handler = $this->createHandler( children: [ ['studentId' => self::CHILD1_ID, 'firstName' => 'Alice', 'lastName' => 'Dupont'], ['studentId' => self::CHILD2_ID, 'firstName' => 'Bob', 'lastName' => 'Dupont'], ], classMapping: [ self::CHILD1_ID => self::CLASS1_ID, self::CHILD2_ID => self::CLASS2_ID, ], ); $result = $handler(new GetChildrenScheduleQuery( parentId: self::PARENT_ID, tenantId: self::TENANT_ID, date: '2026-03-02', )); self::assertCount(2, $result); self::assertSame(self::CHILD1_ID, $result[0]->childId); self::assertCount(1, $result[0]->slots); self::assertSame(self::CHILD2_ID, $result[1]->childId); self::assertCount(2, $result[1]->slots); } #[Test] public function returnsEmptySlotsWhenChildHasNoClass(): void { $handler = $this->createHandler( children: [ ['studentId' => self::CHILD1_ID, 'firstName' => 'Alice', 'lastName' => 'Dupont'], ], classMapping: [], ); $result = $handler(new GetChildrenScheduleQuery( parentId: self::PARENT_ID, tenantId: self::TENANT_ID, date: '2026-03-02', )); self::assertCount(1, $result); self::assertSame(self::CHILD1_ID, $result[0]->childId); self::assertSame([], $result[0]->slots); } #[Test] public function enrichesSlotsWithSubjectAndTeacherNames(): void { $this->saveRecurringSlot(self::CLASS1_ID, DayOfWeek::MONDAY, '08:00', '09:00'); $handler = $this->createHandler( children: [ ['studentId' => self::CHILD1_ID, 'firstName' => 'Alice', 'lastName' => 'Dupont'], ], classMapping: [self::CHILD1_ID => self::CLASS1_ID], subjectNames: [self::SUBJECT_ID => 'Mathématiques'], teacherNames: [self::TEACHER_ID => 'Jean Martin'], ); $result = $handler(new GetChildrenScheduleQuery( parentId: self::PARENT_ID, tenantId: self::TENANT_ID, date: '2026-03-02', )); self::assertSame('Mathématiques', $result[0]->slots[0]->subjectName); self::assertSame('Jean Martin', $result[0]->slots[0]->teacherName); } #[Test] public function computesMondayFromAnyDayOfWeek(): void { $this->saveRecurringSlot(self::CLASS1_ID, DayOfWeek::MONDAY, '08:00', '09:00'); $handler = $this->createHandler( children: [ ['studentId' => self::CHILD1_ID, 'firstName' => 'Alice', 'lastName' => 'Dupont'], ], classMapping: [self::CHILD1_ID => self::CLASS1_ID], ); $result = $handler(new GetChildrenScheduleQuery( parentId: self::PARENT_ID, tenantId: self::TENANT_ID, date: '2026-03-04', // Wednesday )); self::assertCount(1, $result); self::assertSame('2026-03-02', $result[0]->slots[0]->date); } private function saveRecurringSlot( string $classId, DayOfWeek $day, string $start, string $end, ?string $room = null, ): void { $slot = ScheduleSlot::creer( tenantId: TenantId::fromString(self::TENANT_ID), classId: ClassId::fromString($classId), subjectId: SubjectId::fromString(self::SUBJECT_ID), teacherId: UserId::fromString(self::TEACHER_ID), dayOfWeek: $day, timeSlot: new TimeSlot($start, $end), room: $room, isRecurring: true, now: new DateTimeImmutable('2026-01-01'), recurrenceStart: new DateTimeImmutable('2026-01-01'), ); $this->slotRepository->save($slot); } /** * @param array $children * @param array $classMapping studentId => classId * @param array $subjectNames * @param array $teacherNames */ private function createHandler( array $children = [], array $classMapping = [], array $subjectNames = [], array $teacherNames = [], ): GetChildrenScheduleHandler { $tenantId = TenantId::fromString(self::TENANT_ID); $parentChildrenReader = new class($children) implements ParentChildrenReader { /** @param array $children */ public function __construct(private array $children) { } public function childrenOf(string $guardianId, TenantId $tenantId): array { return $this->children; } }; $studentClassReader = new class($classMapping) implements StudentClassReader { /** @param array $mapping */ public function __construct(private array $mapping) { } public function currentClassId(string $studentId, TenantId $tenantId): ?string { return $this->mapping[$studentId] ?? null; } }; $calendarProvider = new class($tenantId) implements CurrentCalendarProvider { public function __construct(private TenantId $tenantId) { } public function forCurrentYear(TenantId $tenantId): SchoolCalendar { return SchoolCalendar::initialiser($this->tenantId, AcademicYearId::generate()); } }; $displayReader = new class($subjectNames, $teacherNames) implements ScheduleDisplayReader { /** @param array $subjects @param array $teachers */ public function __construct( private array $subjects, private array $teachers, ) { } public function subjectDisplay(string $tenantId, string ...$subjectIds): array { $display = []; foreach ($this->subjects as $id => $name) { $display[$id] = ['name' => $name, 'color' => null]; } return $display; } public function teacherNames(string $tenantId, string ...$teacherIds): array { return $this->teachers; } }; return new GetChildrenScheduleHandler( $parentChildrenReader, $studentClassReader, new ScheduleResolver($this->slotRepository, $this->exceptionRepository), $calendarProvider, $displayReader, ); } }