repository = new InMemoryScheduleSlotRepository(); $this->detector = new ScheduleConflictDetector($this->repository); } #[Test] public function detectsClassConflict(): void { $tenantId = TenantId::fromString(self::TENANT_ID); $existingSlot = $this->createSlot( dayOfWeek: DayOfWeek::MONDAY, startTime: '08:00', endTime: '09:00', ); $this->repository->save($existingSlot); // Même classe, même créneau, enseignant différent $newSlot = $this->createSlot( teacherId: '550e8400-e29b-41d4-a716-446655440011', dayOfWeek: DayOfWeek::MONDAY, startTime: '08:30', endTime: '09:30', ); $conflicts = $this->detector->detectConflicts($newSlot, $tenantId); self::assertNotEmpty($conflicts); $types = array_map(static fn ($c) => $c->type, $conflicts); self::assertContains('class', $types); } #[Test] public function noClassConflictWhenDifferentClass(): void { $tenantId = TenantId::fromString(self::TENANT_ID); $existingSlot = $this->createSlot( dayOfWeek: DayOfWeek::MONDAY, startTime: '08:00', endTime: '09:00', ); $this->repository->save($existingSlot); $newSlot = $this->createSlot( teacherId: '550e8400-e29b-41d4-a716-446655440011', classId: '550e8400-e29b-41d4-a716-446655440021', dayOfWeek: DayOfWeek::MONDAY, startTime: '08:00', endTime: '09:00', ); $conflicts = $this->detector->detectConflicts($newSlot, $tenantId); self::assertEmpty($conflicts); } #[Test] public function detectsTeacherConflict(): void { $tenantId = TenantId::fromString(self::TENANT_ID); $existingSlot = $this->createSlot( teacherId: self::TEACHER_ID, dayOfWeek: DayOfWeek::MONDAY, startTime: '08:00', endTime: '09:00', ); $this->repository->save($existingSlot); $newSlot = $this->createSlot( teacherId: self::TEACHER_ID, classId: '550e8400-e29b-41d4-a716-446655440021', dayOfWeek: DayOfWeek::MONDAY, startTime: '08:30', endTime: '09:30', ); $conflicts = $this->detector->detectConflicts($newSlot, $tenantId); self::assertNotEmpty($conflicts); self::assertSame('teacher', $conflicts[0]->type); } #[Test] public function noConflictWhenDifferentDay(): void { $tenantId = TenantId::fromString(self::TENANT_ID); $existingSlot = $this->createSlot( teacherId: self::TEACHER_ID, dayOfWeek: DayOfWeek::MONDAY, startTime: '08:00', endTime: '09:00', ); $this->repository->save($existingSlot); $newSlot = $this->createSlot( teacherId: self::TEACHER_ID, classId: '550e8400-e29b-41d4-a716-446655440021', dayOfWeek: DayOfWeek::TUESDAY, startTime: '08:00', endTime: '09:00', ); $conflicts = $this->detector->detectConflicts($newSlot, $tenantId); self::assertEmpty($conflicts); } #[Test] public function noConflictWhenAdjacentTimes(): void { $tenantId = TenantId::fromString(self::TENANT_ID); $existingSlot = $this->createSlot( teacherId: self::TEACHER_ID, dayOfWeek: DayOfWeek::MONDAY, startTime: '08:00', endTime: '09:00', ); $this->repository->save($existingSlot); $newSlot = $this->createSlot( teacherId: self::TEACHER_ID, classId: '550e8400-e29b-41d4-a716-446655440021', dayOfWeek: DayOfWeek::MONDAY, startTime: '09:00', endTime: '10:00', ); $conflicts = $this->detector->detectConflicts($newSlot, $tenantId); self::assertEmpty($conflicts); } #[Test] public function detectsRoomConflict(): void { $tenantId = TenantId::fromString(self::TENANT_ID); $existingSlot = $this->createSlot( teacherId: self::TEACHER_ID, dayOfWeek: DayOfWeek::MONDAY, startTime: '08:00', endTime: '09:00', room: 'Salle 101', ); $this->repository->save($existingSlot); $newSlot = $this->createSlot( teacherId: '550e8400-e29b-41d4-a716-446655440011', classId: '550e8400-e29b-41d4-a716-446655440021', dayOfWeek: DayOfWeek::MONDAY, startTime: '08:30', endTime: '09:30', room: 'Salle 101', ); $conflicts = $this->detector->detectConflicts($newSlot, $tenantId); self::assertNotEmpty($conflicts); self::assertSame('room', $conflicts[0]->type); } #[Test] public function noRoomConflictWhenNoRoom(): void { $tenantId = TenantId::fromString(self::TENANT_ID); $existingSlot = $this->createSlot( teacherId: self::TEACHER_ID, dayOfWeek: DayOfWeek::MONDAY, startTime: '08:00', endTime: '09:00', ); $this->repository->save($existingSlot); $newSlot = $this->createSlot( teacherId: '550e8400-e29b-41d4-a716-446655440011', classId: '550e8400-e29b-41d4-a716-446655440021', dayOfWeek: DayOfWeek::MONDAY, startTime: '08:30', endTime: '09:30', ); $conflicts = $this->detector->detectConflicts($newSlot, $tenantId); self::assertEmpty($conflicts); } #[Test] public function excludesCurrentSlotWhenUpdating(): void { $tenantId = TenantId::fromString(self::TENANT_ID); $existingSlot = $this->createSlot( teacherId: self::TEACHER_ID, dayOfWeek: DayOfWeek::MONDAY, startTime: '08:00', endTime: '09:00', ); $this->repository->save($existingSlot); $conflicts = $this->detector->detectConflicts($existingSlot, $tenantId, $existingSlot->id); self::assertEmpty($conflicts); } #[Test] public function detectsBothTeacherAndRoomConflicts(): void { $tenantId = TenantId::fromString(self::TENANT_ID); $existingSlot = $this->createSlot( teacherId: self::TEACHER_ID, dayOfWeek: DayOfWeek::MONDAY, startTime: '08:00', endTime: '09:00', room: 'Salle 101', ); $this->repository->save($existingSlot); $newSlot = $this->createSlot( teacherId: self::TEACHER_ID, classId: '550e8400-e29b-41d4-a716-446655440021', dayOfWeek: DayOfWeek::MONDAY, startTime: '08:30', endTime: '09:30', room: 'Salle 101', ); $conflicts = $this->detector->detectConflicts($newSlot, $tenantId); $types = array_map(static fn ($c) => $c->type, $conflicts); self::assertContains('teacher', $types); self::assertContains('room', $types); } private function createSlot( string $teacherId = self::TEACHER_ID, string $classId = self::CLASS_ID, DayOfWeek $dayOfWeek = DayOfWeek::MONDAY, string $startTime = '08:00', string $endTime = '09:00', ?string $room = null, ): ScheduleSlot { return ScheduleSlot::creer( tenantId: TenantId::fromString(self::TENANT_ID), classId: ClassId::fromString($classId), subjectId: SubjectId::fromString(self::SUBJECT_ID), teacherId: UserId::fromString($teacherId), dayOfWeek: $dayOfWeek, timeSlot: new TimeSlot($startTime, $endTime), room: $room, isRecurring: true, now: new DateTimeImmutable('2026-03-01 10:00:00'), ); } }