- Added PermissionGateTrait to manage access control for AppointmentPlanController and BillingController. - Introduced denyUnlessGrantedForPlanning method in AppointmentPlanController to handle specific permission checks for planning appointments. - Updated existing methods in both controllers to utilize the new permission checks. - Refactored ResourcePermissionTrait to use PermissionGateTrait for cleaner permission management. - Added tests to ensure proper permission enforcement across different scenarios, including cross-tenant access restrictions for staff.
88 lines
3.9 KiB
PHP
88 lines
3.9 KiB
PHP
<?php
|
|
|
|
namespace App\Tests\Secretary;
|
|
|
|
use App\Appointment\Entity\Appointment;
|
|
use App\Auth\Entity\UserActiveContext;
|
|
use App\Clinic\Entity\Clinic;
|
|
use App\Doctor\Entity\Doctor;
|
|
use App\Secretary\Entity\DoctorSecretary;
|
|
use App\Tests\ApiTestCase;
|
|
|
|
/**
|
|
* A clinic-owned secretary must only see the appointments of the doctors they
|
|
* are actually assigned to — not every doctor in the clinic.
|
|
*/
|
|
class SecretaryAppointmentScopeTest extends ApiTestCase
|
|
{
|
|
public function testSecretarySeesOnlyAssignedDoctorsAppointments(): void
|
|
{
|
|
$owner = $this->createUser(['ROLE_CLINIC']);
|
|
$clinic = new Clinic($owner);
|
|
$this->em->persist($clinic);
|
|
|
|
$doctorA = new Doctor($this->createUser(['ROLE_DOCTOR']), 'دکتر A');
|
|
$doctorB = new Doctor($this->createUser(['ROLE_DOCTOR']), 'دکتر B');
|
|
$this->em->persist($doctorA);
|
|
$this->em->persist($doctorB);
|
|
$clinic->getDoctors()->add($doctorA);
|
|
$clinic->getDoctors()->add($doctorB);
|
|
|
|
// منشی فقط به دکتر A تخصیص داده شده
|
|
$secretaryUser = $this->createUser(['ROLE_SECRETARY']);
|
|
$rel = new DoctorSecretary($doctorA, $secretaryUser, $clinic);
|
|
$this->em->persist($rel);
|
|
|
|
// scope فعالِ منشی = این کلینیک
|
|
$this->em->persist(new UserActiveContext($secretaryUser, $clinic->getUuid(), 'clinic'));
|
|
|
|
// یک نوبت برای هر پزشک
|
|
$patient = $this->createUser(['ROLE_USER']);
|
|
$start = time() + 3600;
|
|
// نوبتها در همین کلینیک ثبت میشوند، نه در مطب شخصی — وگرنه محیطشان با
|
|
// محیط فعالِ منشی یکی نیست و اصلاً نباید در این لیست بیایند.
|
|
$this->em->persist($this->newAppointment($doctorA, $patient, $start, $start + 900, $clinic));
|
|
$this->em->persist($this->newAppointment($doctorB, $patient, $start + 1800, $start + 2700, $clinic));
|
|
$this->em->flush();
|
|
|
|
$body = $this->authJson('GET', '/api/v1/my/appointments', $secretaryUser);
|
|
|
|
$this->assertSame(200, $this->responseCode());
|
|
// فقط نوبت دکتر A دیده میشود، نه دکتر B
|
|
$this->assertSame(1, $body['meta']['totalRecords']);
|
|
}
|
|
|
|
/**
|
|
* منشیِ بدون رابطهٔ فعال دیگر لیست خالی نمیگیرد، ۴۰۳ میگیرد.
|
|
*
|
|
* تا ۲۰۲۶-۰۸-۰۷ این مسیر گِیت مجوز نداشت و پاسخ خالی از فیلترِ خودِ کوئری
|
|
* میآمد. با گِیتِ `appointments.view` (یافتهٔ ۸ آدیت)، `SecretaryAccessChecker`
|
|
* برای منشیِ بیرابطه `can()=false` میدهد — همان رفتار fail-closed مستندِ
|
|
* `SecretaryPermissionChecker`. «خالیِ خاموش» با «اجازه نداری» یکی نیست.
|
|
*/
|
|
public function testSecretaryWithNoAssignmentIsDenied(): void
|
|
{
|
|
$owner = $this->createUser(['ROLE_CLINIC']);
|
|
$clinic = new Clinic($owner);
|
|
$this->em->persist($clinic);
|
|
|
|
$doctor = new Doctor($this->createUser(['ROLE_DOCTOR']), 'دکتر تنها');
|
|
$this->em->persist($doctor);
|
|
$clinic->getDoctors()->add($doctor);
|
|
|
|
// منشی context کلینیک دارد ولی رابطهی فعال ندارد
|
|
$secretaryUser = $this->createUser(['ROLE_SECRETARY']);
|
|
$this->em->persist(new UserActiveContext($secretaryUser, $clinic->getUuid(), 'clinic'));
|
|
|
|
$patient = $this->createUser(['ROLE_USER']);
|
|
$start = time() + 3600;
|
|
$this->em->persist($this->newAppointment($doctor, $patient, $start, $start + 900));
|
|
$this->em->flush();
|
|
|
|
$body = $this->authJson('GET', '/api/v1/my/appointments', $secretaryUser);
|
|
|
|
$this->assertSame(403, $this->responseCode());
|
|
$this->assertSame('ERR_FORBIDDEN_001', $body['errors'][0]['code']);
|
|
}
|
|
}
|