createUser(['ROLE_USER', 'ROLE_REPRESENTATION']); $this->em->persist(new Representation($user, 'نمایندهٔ ' . uniqid())); $this->em->flush(); return $user; } /** کلینیکی که همان نماینده ثبتش کرده؛ uuid برمی‌گردد. */ private function clinicCreatedBy(User $repUser): string { $body = $this->authJson('POST', '/api/v1/representation/clinic', $repUser, [ 'owner_mobile' => '09' . str_pad((string) random_int(0, 999_999_999), 9, '0', STR_PAD_LEFT), 'name' => 'کلینیک آزمون ' . uniqid(), ]); self::assertSame(200, $this->responseCode(), 'ساخت کلینیک توسط نماینده باید ۲۰۰ بدهد'); return $body['data']['uuid']; } private function editLogCount(): int { return (int) static::getContainer()->get(Connection::class) ->fetchOne("SELECT COUNT(*) FROM app_log WHERE channel = 'representation_edit'"); } private function reloadClinic(string $uuid): Clinic { $this->em->clear(); return $this->em->getRepository(Clinic::class)->findOneBy(['uuid' => $uuid]); } // ── مسیر موفق ───────────────────────────────────────────────────────────── public function testOwningRepresentativeCanEditLogoAndDescription(): void { $repUser = $this->newRepresentative(); $uuid = $this->clinicCreatedBy($repUser); $before = $this->editLogCount(); $body = $this->authJson('PATCH', '/api/v1/clinic/' . $uuid, $repUser, [ 'clinic_logo' => 'https://example.test/logo.png', 'info' => 'معرفی تازهٔ کلینیک', 'telephone' => '03511111111', ]); self::assertSame(200, $this->responseCode()); self::assertTrue($body['success']); $clinic = $this->reloadClinic($uuid); self::assertSame('https://example.test/logo.png', $clinic->getClinicLogo()); self::assertSame('معرفی تازهٔ کلینیک', $clinic->getInfo()); self::assertSame($before + 1, $this->editLogCount()); } public function testTheWholePayloadTheEditFormSendsIsAccepted(): void { // فرم ویرایش کلینیک همیشه specialties و doctor_services و insurance را // می‌فرستد؛ اگر بیرون از whitelist باشند هر ذخیره‌ای ۴۰۳ می‌شود. $repUser = $this->newRepresentative(); $uuid = $this->clinicCreatedBy($repUser); $this->authJson('PATCH', '/api/v1/clinic/' . $uuid, $repUser, [ 'name' => 'کلینیک با نام تازه', 'telephone' => '03514444444', 'info' => 'توضیحات', '24_7' => true, 'specialties' => [], 'insurance' => [], 'doctor_services' => [], 'social_media' => [ 'instagram' => 'https://instagram.com/test', 'telegram' => null, 'aparat' => null, 'youtube' => null, 'linkedin' => null, ], ]); self::assertSame(200, $this->responseCode()); self::assertSame('کلینیک با نام تازه', $this->reloadClinic($uuid)->getName()); } public function testSecretaryPreCheckDoesNotBlockARepresentative(): void { // denyUnlessGranted پیش از واکشی رکورد اجرا می‌شود؛ این تست تثبیت می‌کند که // نقشِ غیرمنشی از آن رد می‌شود و ۴۰۳ زودهنگام نمی‌گیرد. $repUser = $this->newRepresentative(); $uuid = $this->clinicCreatedBy($repUser); $this->authJson('PATCH', '/api/v1/clinic/' . $uuid, $repUser, ['info' => 'x']); self::assertSame(200, $this->responseCode()); } // ── مسیر خطا ────────────────────────────────────────────────────────────── public function testAnotherRepresentativeIsForbidden(): void { $owner = $this->newRepresentative(); $stranger = $this->newRepresentative(); $uuid = $this->clinicCreatedBy($owner); $body = $this->authJson('PATCH', '/api/v1/clinic/' . $uuid, $stranger, ['info' => 'نباید ذخیره شود']); self::assertSame(403, $this->responseCode()); self::assertSame('ERR_AUTH_006', $body['errors'][0]['code']); self::assertNotSame('نباید ذخیره شود', $this->reloadClinic($uuid)->getInfo()); } public function testChangingClinicMembershipIsForbiddenForRepresentative(): void { $repUser = $this->newRepresentative(); $uuid = $this->clinicCreatedBy($repUser); $body = $this->authJson('PATCH', '/api/v1/clinic/' . $uuid, $repUser, [ 'info' => 'این هم نباید ذخیره شود', 'doctors' => [1], ]); self::assertSame(403, $this->responseCode()); self::assertSame('doctors', $body['errors'][0]['field']); self::assertNotSame('این هم نباید ذخیره شود', $this->reloadClinic($uuid)->getInfo()); } // ── مرزی ────────────────────────────────────────────────────────────────── public function testClinicWithoutARepresentationIsNotEditableByAnyRepresentative(): void { $repUser = $this->newRepresentative(); $ownerUser = $this->createUser(['ROLE_USER', 'ROLE_CLINIC']); $orphan = new Clinic($ownerUser); $orphan->setName('کلینیک بی‌نماینده'); $this->em->persist($orphan); $this->em->flush(); $this->authJson('PATCH', '/api/v1/clinic/' . $orphan->getUuid(), $repUser, ['info' => 'x']); self::assertSame(403, $this->responseCode()); } public function testClinicOwnerIsUnaffectedByTheWhitelist(): void { $ownerUser = $this->createUser(['ROLE_USER', 'ROLE_CLINIC']); $clinic = new Clinic($ownerUser); $clinic->setName('کلینیک خودگردان'); $this->em->persist($clinic); $this->em->flush(); $uuid = $clinic->getUuid(); $before = $this->editLogCount(); // `doctors` برای نماینده ممنوع است اما برای مالک نه؛ آرایهٔ خالی یعنی «پاک کن». $this->authJson('PATCH', '/api/v1/clinic/' . $uuid, $ownerUser, [ 'doctors' => [], 'info' => 'مالک آزادانه ویرایش می‌کند', ]); self::assertSame(200, $this->responseCode()); self::assertSame('مالک آزادانه ویرایش می‌کند', $this->reloadClinic($uuid)->getInfo()); self::assertSame($before, $this->editLogCount(), 'ویرایش مالک نباید لاگ نماینده بسازد'); } public function testAdminIsUnaffectedByTheWhitelist(): void { $repUser = $this->newRepresentative(); $uuid = $this->clinicCreatedBy($repUser); $admin = $this->createUser(['ROLE_ADMIN']); $before = $this->editLogCount(); $this->authJson('PATCH', '/api/v1/clinic/' . $uuid, $admin, ['doctors' => [], 'info' => 'ادمین']); self::assertSame(200, $this->responseCode()); self::assertSame('ادمین', $this->reloadClinic($uuid)->getInfo()); self::assertSame($before, $this->editLogCount(), 'ویرایش ادمین نباید لاگ نماینده بسازد'); } }