createUser(['ROLE_DOCTOR', 'ROLE_CLINIC']); $doctor = new Doctor($user, 'دکتر دو محیطه'); $this->em->persist($doctor); $clinic = new Clinic($user); $clinic->setName('کلینیک همان شخص'); $this->em->persist($clinic); $this->em->flush(); return [$user, $doctor, $clinic]; } private function switchTo(User $user, string $type, string $uuid): void { // هر درخواستِ API ممکن است EntityManager را پاک کند، پس کاربر دوباره از // همین EM گرفته می‌شود تا UserActiveContext به نمونهٔ جداشده وصل نشود. $managed = $this->em->find(User::class, $user->getId()); $existing = $this->em->getRepository(UserActiveContext::class)->findOneBy(['user' => $managed]); if ($existing !== null) { $this->em->remove($existing); $this->em->flush(); } $this->em->persist(new UserActiveContext($managed, $uuid, $type)); $this->em->flush(); } private function createBankAccount(User $user, string $bankName): array { $res = $this->authJson('POST', '/api/v1/my/payment-methods/bank-accounts', $user, [ 'bank_name' => $bankName, 'account_number' => (string) random_int(1_000_000, 9_999_999), ]); self::assertSame(201, $this->responseCode()); return $res['data']; } private function listBankAccounts(User $user): array { $res = $this->authJson('GET', '/api/v1/my/payment-methods/bank-accounts', $user); self::assertSame(200, $this->responseCode()); return $res['data']; } /** ✅ همان شخص، دو محیط، دو دستهٔ جدا از کارت‌ها. */ public function testTheSamePersonSeesDifferentCardsInEachEnvironment(): void { [$user, $doctor, $clinic] = $this->multiEnvironmentUser(); $this->switchTo($user, EntityContext::TYPE_DOCTOR, $doctor->getUuid()); $personal = $this->createBankAccount($user, 'کارت مطب'); $this->switchTo($user, EntityContext::TYPE_CLINIC, $clinic->getUuid()); $clinical = $this->createBankAccount($user, 'کارت کلینیک'); self::assertSame(['کارت کلینیک'], array_column($this->listBankAccounts($user), 'bank_name')); $this->switchTo($user, EntityContext::TYPE_DOCTOR, $doctor->getUuid()); self::assertSame(['کارت مطب'], array_column($this->listBankAccounts($user), 'bank_name')); self::assertNotSame($personal['uuid'], $clinical['uuid']); } /** ❌ کارتِ محیط دیگر حتی برای همان شخص قابل ویرایش نیست. */ public function testACardOfTheOtherEnvironmentCannotBeEditedEvenByItsOwner(): void { [$user, $doctor, $clinic] = $this->multiEnvironmentUser(); $this->switchTo($user, EntityContext::TYPE_DOCTOR, $doctor->getUuid()); $personal = $this->createBankAccount($user, 'کارت مطب'); $this->switchTo($user, EntityContext::TYPE_CLINIC, $clinic->getUuid()); $this->authJson('PATCH', '/api/v1/my/payment-methods/bank-accounts/' . $personal['uuid'] . '/status', $user); self::assertSame(404, $this->responseCode()); } /** * ⚠️ مرزی: کارتِ بی‌محیط (بازماندهٔ پیش از فاز ۶) در فهرست می‌آید با نشانهٔ * `entity_type: null`، ولی تا وقتی محیطش تعیین نشده قابل ویرایش نیست. */ public function testAnUnassignedCardIsListedButNotEditable(): void { [$user, $doctor] = $this->multiEnvironmentUser(); $this->switchTo($user, EntityContext::TYPE_DOCTOR, $doctor->getUuid()); $orphan = new BankAccount($user, 'کارت بی‌محیط', '555000', '', ''); $this->em->persist($orphan); $this->em->flush(); $listed = $this->listBankAccounts($user); self::assertSame(['کارت بی‌محیط'], array_column($listed, 'bank_name')); self::assertNull($listed[0]['entity_type'], 'باید با نشانهٔ «محیط تعیین‌نشده» بیاید'); $this->authJson('PATCH', '/api/v1/my/payment-methods/bank-accounts/' . $orphan->getUuid() . '/status', $user); self::assertSame(404, $this->responseCode(), 'تا تعیین محیط، ویرایش‌پذیر نیست'); } /** ✅ انتساب، کارتِ بی‌محیط را به محیط فعال می‌چسباند و ویرایش‌پذیرش می‌کند. */ public function testAssigningAnEnvironmentMakesTheCardUsable(): void { [$user, $doctor] = $this->multiEnvironmentUser(); $this->switchTo($user, EntityContext::TYPE_DOCTOR, $doctor->getUuid()); $orphan = new BankAccount($user, 'کارت بی‌محیط', '555000', '', ''); $this->em->persist($orphan); $this->em->flush(); $uuid = $orphan->getUuid(); $res = $this->authJson('PATCH', "/api/v1/my/payment-methods/bank-accounts/$uuid/environment", $user); self::assertSame(200, $this->responseCode()); self::assertSame('doctor', $res['data']['entity_type']); $this->authJson('PATCH', "/api/v1/my/payment-methods/bank-accounts/$uuid/status", $user); self::assertSame(200, $this->responseCode(), 'بعد از انتساب باید ویرایش‌پذیر باشد'); } /** ❌ انتساب دوباره روی کارتی که محیط دارد، بی‌اثر است — نه ربودن کارت محیط دیگر. */ public function testAssigningAnAlreadyAssignedCardIsRejected(): void { [$user, $doctor, $clinic] = $this->multiEnvironmentUser(); $this->switchTo($user, EntityContext::TYPE_DOCTOR, $doctor->getUuid()); $personal = $this->createBankAccount($user, 'کارت مطب'); $this->switchTo($user, EntityContext::TYPE_CLINIC, $clinic->getUuid()); $this->authJson( 'PATCH', '/api/v1/my/payment-methods/bank-accounts/' . $personal['uuid'] . '/environment', $user, ); self::assertSame(404, $this->responseCode()); } /** ❌ کارتِ بی‌محیطِ شخص دیگر با انتساب هم به دست نمی‌آید. */ public function testAnotherPersonsUnassignedCardCannotBeClaimed(): void { [$user, $doctor] = $this->multiEnvironmentUser(); $this->switchTo($user, EntityContext::TYPE_DOCTOR, $doctor->getUuid()); [$stranger] = $this->multiEnvironmentUser(); $orphan = new Pos($stranger, 'کارتخوان بیگانه', '900900'); $this->em->persist($orphan); $this->em->flush(); $this->authJson('PATCH', '/api/v1/my/payment-methods/pos/' . $orphan->getUuid() . '/environment', $user); self::assertSame(404, $this->responseCode()); } /** شکل ردیفِ بی‌محیط باید دقیقاً همان شکل ردیفِ محیط‌دار باشد، وگرنه پنل می‌شکند. */ public function testUnassignedRowsHaveTheSameShapeAsAssignedOnes(): void { [$user, $doctor] = $this->multiEnvironmentUser(); $this->switchTo($user, EntityContext::TYPE_DOCTOR, $doctor->getUuid()); $assigned = $this->createBankAccount($user, 'کارت محیط‌دار'); $orphan = new BankAccount($user, 'کارت بی‌محیط', '555000', '', ''); $this->em->persist($orphan); $this->em->flush(); $rows = $this->listBankAccounts($user); self::assertCount(2, $rows); $keys = array_map(static fn (array $row) => array_keys($row), $rows); self::assertSame($keys[0], $keys[1], 'کلیدهای ردیف بی‌محیط با ردیف محیط‌دار یکی نیست'); self::assertSame(array_keys($assigned), $keys[0]); } }