feat(tenant): implement tenant filter scope to manage cross-tenant data visibility
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Payment;
|
||||
|
||||
use App\Appointment\Entity\Appointment;
|
||||
use App\Auth\Entity\User;
|
||||
use App\Auth\Entity\UserActiveContext;
|
||||
use App\Doctor\Entity\Doctor;
|
||||
use App\Payment\Entity\Payment;
|
||||
use App\Shared\Context\EntityContext;
|
||||
use App\Tests\ApiTestCase;
|
||||
|
||||
/**
|
||||
* پرداختکننده باید پرداخت خودش را ببیند، حتی وقتی خودش صاحب یک محیط دیگر است.
|
||||
*
|
||||
* پرداختِ بیمار به محیطِ پزشکِ مقصد تعلق دارد. `TenantFilterSubscriber` برای کاربری
|
||||
* که محیط حلشده دارد فیلتر را روشن میکند، و چون `Payment` محیطدار است، پزشکی که
|
||||
* از مطب دیگری نوبت گرفته بود صفحهٔ نتیجهٔ پرداخت خودش را ۴۰۴ میگرفت.
|
||||
*/
|
||||
class PayerSeesOwnPaymentTest extends ApiTestCase
|
||||
{
|
||||
/**
|
||||
* پزشکی که محیط کاریِ فعالش هم ذخیره شده است.
|
||||
*
|
||||
* بدون این ردیف، `EntityContextResolver` محیط را «انتخابشده» نمیداند و
|
||||
* `TenantFilterSubscriber` فیلتر را روشن نمیکند — یعنی تست باگ را نمیبیند،
|
||||
* درحالیکه کاربر واقعی که وارد پنل شده آن را میگیرد.
|
||||
*/
|
||||
private function makeDoctor(string $name): Doctor
|
||||
{
|
||||
$user = $this->createUser(['ROLE_USER', 'ROLE_DOCTOR']);
|
||||
$doctor = new Doctor($user, $name);
|
||||
$doctor->setMobileNumber($user->getMobileNumber());
|
||||
$this->em->persist($doctor);
|
||||
$this->em->flush();
|
||||
|
||||
$this->em->persist(new UserActiveContext($user, $doctor->getUuid(), EntityContext::TYPE_DOCTOR));
|
||||
$this->em->flush();
|
||||
|
||||
return $doctor;
|
||||
}
|
||||
|
||||
/** پرداختی که «پزشکِ پرداختکننده» برای نوبتِ «پزشکِ مقصد» انجام داده است. */
|
||||
private function makeCrossTenantPayment(Doctor $target, User $payer): Payment
|
||||
{
|
||||
$start = strtotime('+20 days') + random_int(0, 500_000) * 7;
|
||||
$appointment = $this->newAppointment($target, $payer, $start, $start + 900);
|
||||
$appointment->setVisitPriceRials(1_500_000);
|
||||
$this->em->persist($appointment);
|
||||
$this->em->flush();
|
||||
|
||||
$payment = new Payment($payer, 1_500_000, 'mellat', Payment::TYPE_APPOINTMENT, '');
|
||||
$payment->setAppointment($appointment);
|
||||
$payment->assignTenant(EntityContext::forBooking($target, null));
|
||||
$this->em->persist($payment);
|
||||
$this->em->flush();
|
||||
|
||||
return $payment;
|
||||
}
|
||||
|
||||
public function testDoctorSeesThePaymentHeMadeAtAnotherPractice(): void
|
||||
{
|
||||
$target = $this->makeDoctor('پزشک مقصد');
|
||||
$payer = $this->makeDoctor('پزشک پرداختکننده');
|
||||
|
||||
$payment = $this->makeCrossTenantPayment($target, $payer->getUser());
|
||||
|
||||
$body = $this->authJson('GET', '/api/v1/payment/' . $payment->getUuid(), $payer->getUser());
|
||||
|
||||
self::assertSame(200, $this->responseCode());
|
||||
self::assertSame($payment->getUuid(), $body['data']['uuid']);
|
||||
}
|
||||
|
||||
public function testTheSamePaymentAppearsInHisPaymentsList(): void
|
||||
{
|
||||
$target = $this->makeDoctor('پزشک مقصد');
|
||||
$payer = $this->makeDoctor('پزشک پرداختکننده');
|
||||
|
||||
$payment = $this->makeCrossTenantPayment($target, $payer->getUser());
|
||||
|
||||
$body = $this->authJson('GET', '/api/v1/my/payments?limit=100', $payer->getUser());
|
||||
|
||||
self::assertSame(200, $this->responseCode());
|
||||
self::assertContains(
|
||||
$payment->getUuid(),
|
||||
array_column($body['data'], 'uuid'),
|
||||
'پرداختِ خودِ کاربر باید در فهرست پرداختهایش باشد',
|
||||
);
|
||||
}
|
||||
|
||||
/** بیمار ساده — بدون محیط — از قبل هم کار میکرد و نباید بشکند. */
|
||||
public function testPlainPatientStillSeesHisPayment(): void
|
||||
{
|
||||
$target = $this->makeDoctor('پزشک مقصد');
|
||||
$patient = $this->createUser();
|
||||
|
||||
$payment = $this->makeCrossTenantPayment($target, $patient);
|
||||
|
||||
$body = $this->authJson('GET', '/api/v1/payment/' . $payment->getUuid(), $patient);
|
||||
|
||||
self::assertSame(200, $this->responseCode());
|
||||
self::assertSame($payment->getUuid(), $body['data']['uuid']);
|
||||
}
|
||||
|
||||
/** دورزدنِ فیلتر نباید به معنی بازشدن پرداختِ دیگران باشد. */
|
||||
public function testAnotherUserIsStillForbidden(): void
|
||||
{
|
||||
$target = $this->makeDoctor('پزشک مقصد');
|
||||
$payer = $this->createUser();
|
||||
$stranger = $this->createUser();
|
||||
|
||||
$payment = $this->makeCrossTenantPayment($target, $payer);
|
||||
|
||||
$this->authJson('GET', '/api/v1/payment/' . $payment->getUuid(), $stranger);
|
||||
|
||||
self::assertSame(403, $this->responseCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* همان الگو روی نوبت: نوبتی که کاربر بهعنوان بیمار گرفته، در محیطِ پزشکِ مقصد
|
||||
* ثبت میشود و برای کاربرِ صاحبِ محیطِ دیگر نامرئی میشد.
|
||||
*/
|
||||
public function testDoctorSeesTheAppointmentHeBookedAtAnotherPractice(): void
|
||||
{
|
||||
$target = $this->makeDoctor('پزشک مقصد');
|
||||
$payer = $this->makeDoctor('پزشک بیمار');
|
||||
|
||||
$payment = $this->makeCrossTenantPayment($target, $payer->getUser());
|
||||
|
||||
$body = $this->authJson('GET', '/api/v1/appointments/user', $payer->getUser());
|
||||
|
||||
self::assertSame(200, $this->responseCode());
|
||||
self::assertContains(
|
||||
$payment->getAppointment()->getUuid(),
|
||||
array_column($body['data']['data'] ?? $body['data'], 'uuid'),
|
||||
'نوبتِ خودِ کاربر باید در فهرست نوبتهایش باشد',
|
||||
);
|
||||
}
|
||||
|
||||
public function testUnknownPaymentIsStillNotFound(): void
|
||||
{
|
||||
$user = $this->createUser();
|
||||
|
||||
$this->authJson('GET', '/api/v1/payment/00000000-0000-4000-8000-000000000000', $user);
|
||||
|
||||
self::assertSame(404, $this->responseCode());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user