createUser(['ROLE_DOCTOR']); $doctor = new Doctor($owner, 'دکتر خاموش'); $this->em->persist($doctor); $date = date('Y-m-d', strtotime('tomorrow')); $dayKey = (string)(((int) date('w', strtotime($date)) + 1) % 7); $schedule = $this->newWeeklySchedule($doctor, [ $dayKey => ['sessions' => [[ 'active' => true, 'start_time' => '10:00', 'end_time' => '12:00', 'duration_per_patient' => 30, 'location_id' => 1, ]]], ]); $schedule->setMeta(['online_booking_enabled' => false]); $this->em->persist($schedule); $this->em->flush(); return [$doctor, $date]; } public function testPublicSeesNoSlotsWhenBookingDisabled(): void { [$doctor, $date] = $this->makeDoctorWithBookingDisabled(); $calc = static::getContainer()->get(SlotCalculatorService::class); $this->assertSame([], $calc->getAllSlotsWithAvailability($doctor, $date)); $this->assertSame([], $calc->getAvailableSlots($doctor, $date)); $this->assertFalse($calc->hasAnyAvailability($doctor, $date)); $this->assertSame(SlotCalculatorService::EMPTY_OUTSIDE_WINDOW, $calc->explainEmptyDay($doctor, $date)); } public function testManagementSeesSlotsWhenBookingDisabled(): void { [$doctor, $date] = $this->makeDoctorWithBookingDisabled(); $calc = static::getContainer()->get(SlotCalculatorService::class); $sessions = $calc->getAllSlotsWithAvailability($doctor, $date, null, true); $this->assertNotSame([], $sessions); $this->assertNotSame([], $calc->getAvailableSlots($doctor, $date, null, true)); $this->assertTrue($calc->hasAnyAvailability($doctor, $date, null, true)); $this->assertNull($calc->explainEmptyDay($doctor, $date, null, true)); // location هم باید تعیین شود تا نوبتِ دستی بدون آدرس ثبت نشود. $firstStart = (int) $sessions[0]['slots'][0]['start']; $this->assertSame(1, $calc->resolveSlotLocationId($doctor, $firstStart, null, true)); $this->assertNull($calc->resolveSlotLocationId($doctor, $firstStart, null, false)); } public function testPastDateRejectedEvenForManagement(): void { [$doctor] = $this->makeDoctorWithBookingDisabled(); $calc = static::getContainer()->get(SlotCalculatorService::class); $yesterday = date('Y-m-d', strtotime('yesterday')); $this->assertSame([], $calc->getAllSlotsWithAvailability($doctor, $yesterday, null, true)); } private function slotsUri(Doctor $doctor, string $date, bool $management): string { return sprintf( '/api/v1/appointment-slots?doctor_uuid=%s&date=%s%s', $doctor->getUuid(), $date, $management ? '&management=1' : '', ); } public function testEndpointAnonymousGetsNoSlots(): void { [$doctor, $date] = $this->makeDoctorWithBookingDisabled(); $this->client->request('GET', $this->slotsUri($doctor, $date, false)); $body = json_decode($this->client->getResponse()->getContent(), true); self::assertSame(200, $this->responseCode()); self::assertSame([], $body['data']['sessions']); self::assertSame(SlotCalculatorService::EMPTY_OUTSIDE_WINDOW, $body['data']['empty_reason']); } public function testEndpointOwnerDoctorWithManagementGetsSlots(): void { [$doctor, $date] = $this->makeDoctorWithBookingDisabled(); $body = $this->authJson('GET', $this->slotsUri($doctor, $date, true), $doctor->getUser()); self::assertSame(200, $this->responseCode()); self::assertNotSame([], $body['data']['sessions']); } public function testEndpointManagementFlagIgnoredForUnauthorizedUser(): void { [$doctor, $date] = $this->makeDoctorWithBookingDisabled(); $stranger = $this->createUser(['ROLE_USER']); // management=1 بدون دسترسی نباید توگل را دور بزند (fail-safe عمومی). $body = $this->authJson('GET', $this->slotsUri($doctor, $date, true), $stranger); self::assertSame(200, $this->responseCode()); self::assertSame([], $body['data']['sessions']); } /** * سناریوی واقعیِ گزارش‌شده: پزشک عضو کلینیک، نوبت‌دهی آنلاینِ کلینیک خاموش، * مالک کلینیک از پنل «نوبت‌ها» با management=1 باید اسلات‌ها را ببیند. */ public function testClinicOwnerSeesClinicSlotsWhenBookingDisabled(): void { $doctorOwner = $this->createUser(['ROLE_DOCTOR']); $doctor = new Doctor($doctorOwner, 'دکتر احمدی'); $this->em->persist($doctor); $clinicOwner = $this->createUser(['ROLE_USER', 'ROLE_CLINIC']); $clinic = new Clinic($clinicOwner); $clinic->setName('کلینیک تخصصی نمونه'); $clinic->getDoctors()->add($doctor); $this->em->persist($clinic); $date = date('Y-m-d', strtotime('tomorrow')); $dayKey = (string)(((int) date('w', strtotime($date)) + 1) % 7); // برنامهٔ نوبت‌دهی در محیط کلینیک با نوبت‌دهی آنلاینِ خاموش. $schedule = $this->newWeeklySchedule($doctor, [ $dayKey => ['sessions' => [[ 'active' => true, 'start_time' => '10:00', 'end_time' => '12:00', 'duration_per_patient' => 30, 'location_id' => 1, ]]], ], $clinic); $schedule->setMeta(['online_booking_enabled' => false]); $this->em->persist($schedule); $this->em->flush(); $uri = sprintf( '/api/v1/appointment-slots?doctor_uuid=%s&date=%s&clinic_uuid=%s&management=1', $doctor->getUuid(), $date, $clinic->getUuid(), ); // مالک کلینیک → اسلات می‌بیند. $body = $this->authJson('GET', $uri, $clinicOwner); self::assertSame(200, $this->responseCode()); self::assertNotSame([], $body['data']['sessions'], 'مالک کلینیک باید اسلات‌های محیط کلینیک را ببیند'); // بدون احراز → عمومی، خالی. $this->client->request('GET', $uri); $anon = json_decode($this->client->getResponse()->getContent(), true); self::assertSame([], $anon['data']['sessions']); } }