slotRepository = new InMemoryScheduleSlotRepository(); $this->exceptionRepository = new InMemoryScheduleExceptionRepository(); } #[Test] public function returnsEmptyWhenStudentHasNoClass(): void { $handler = $this->createHandler(classId: null); $result = $handler(new GetStudentScheduleQuery( studentId: self::STUDENT_ID, tenantId: self::TENANT_ID, date: '2026-03-02', )); self::assertSame([], $result); } #[Test] public function returnsScheduleForStudentClass(): void { $this->saveRecurringSlot(DayOfWeek::MONDAY, '08:00', '09:00'); $this->saveRecurringSlot(DayOfWeek::TUESDAY, '10:00', '11:00'); $handler = $this->createHandler(classId: self::CLASS_ID); $result = $handler(new GetStudentScheduleQuery( studentId: self::STUDENT_ID, tenantId: self::TENANT_ID, date: '2026-03-02', // Monday )); self::assertCount(2, $result); self::assertContainsOnlyInstancesOf(StudentScheduleSlotDto::class, $result); } #[Test] public function enrichesSlotsWithSubjectAndTeacherNames(): void { $this->saveRecurringSlot(DayOfWeek::MONDAY, '08:00', '09:00'); $handler = $this->createHandler( classId: self::CLASS_ID, subjectNames: [self::SUBJECT_ID => 'Mathématiques'], teacherNames: [self::TEACHER_ID => 'Jean Dupont'], ); $result = $handler(new GetStudentScheduleQuery( studentId: self::STUDENT_ID, tenantId: self::TENANT_ID, date: '2026-03-02', )); self::assertCount(1, $result); self::assertSame('Mathématiques', $result[0]->subjectName); self::assertSame('Jean Dupont', $result[0]->teacherName); } #[Test] public function computesMondayFromAnyDayOfWeek(): void { $this->saveRecurringSlot(DayOfWeek::MONDAY, '08:00', '09:00'); $handler = $this->createHandler(classId: self::CLASS_ID); // Wednesday of the same week → should still return Monday's slot $result = $handler(new GetStudentScheduleQuery( studentId: self::STUDENT_ID, tenantId: self::TENANT_ID, date: '2026-03-04', // Wednesday )); self::assertCount(1, $result); self::assertSame('2026-03-02', $result[0]->date); } #[Test] public function returnsCorrectDtoFields(): void { $this->saveRecurringSlot(DayOfWeek::MONDAY, '08:00', '09:00', 'Salle 101'); $handler = $this->createHandler( classId: self::CLASS_ID, subjectNames: [self::SUBJECT_ID => 'Français'], teacherNames: [self::TEACHER_ID => 'Marie Martin'], ); $result = $handler(new GetStudentScheduleQuery( studentId: self::STUDENT_ID, tenantId: self::TENANT_ID, date: '2026-03-02', )); self::assertCount(1, $result); $dto = $result[0]; self::assertSame('2026-03-02', $dto->date); self::assertSame(1, $dto->dayOfWeek); self::assertSame('08:00', $dto->startTime); self::assertSame('09:00', $dto->endTime); self::assertSame(self::SUBJECT_ID, $dto->subjectId); self::assertSame('Français', $dto->subjectName); self::assertSame(self::TEACHER_ID, $dto->teacherId); self::assertSame('Marie Martin', $dto->teacherName); self::assertSame('Salle 101', $dto->room); self::assertFalse($dto->isModified); self::assertNull($dto->exceptionId); } private function saveRecurringSlot( DayOfWeek $day, string $start, string $end, ?string $room = null, ): 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: $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 $subjectNames * @param array $teacherNames */ private function createHandler( ?string $classId = null, array $subjectNames = [], array $teacherNames = [], ): GetStudentScheduleHandler { $tenantId = TenantId::fromString(self::TENANT_ID); $studentClassReader = new class($classId) implements StudentClassReader { public function __construct(private ?string $classId) { } public function currentClassId(string $studentId, TenantId $tenantId): ?string { return $this->classId; } }; $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 subjectNames(string $tenantId, string ...$subjectIds): array { return $this->subjects; } public function teacherNames(string $tenantId, string ...$teacherIds): array { return $this->teachers; } }; return new GetStudentScheduleHandler( $studentClassReader, new ScheduleResolver($this->slotRepository, $this->exceptionRepository), $calendarProvider, $displayReader, ); } }