…])` می‌دهد و * BaseController خودش یک لایهٔ `data` دیگر می‌گذارد. */ private function createdAddress(array $body): array { return $body['data']['data']; } private function createdAddressId(array $body): int { return (int) $this->createdAddress($body)['id']; } private function createdAddressUuid(array $body): string { return $this->createdAddress($body)['uuid']; } private function newRepresentative(): User { $user = $this->createUser(['ROLE_USER', 'ROLE_REPRESENTATION']); $this->em->persist(new Representation($user, 'نمایندهٔ ' . uniqid())); $this->em->flush(); return $user; } private function doctorCreatedBy(User $repUser): string { $body = $this->authJson('POST', '/api/v1/representation/doctor', $repUser, [ 'mobile' => '09' . str_pad((string) random_int(0, 999_999_999), 9, '0', STR_PAD_LEFT), 'name' => 'دکتر آدرس ' . uniqid(), ]); self::assertSame(201, $this->responseCode()); return $body['data']['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']; } // ── آدرس پزشک ───────────────────────────────────────────────────────────── public function testRepresentativeCreatesUpdatesAndDeletesADoctorAddress(): void { $repUser = $this->newRepresentative(); $uuid = $this->doctorCreatedBy($repUser); $created = $this->authJson('POST', '/api/v1/clinic-pro/doctor-address', $repUser, [ 'doctor_uuid' => $uuid, 'name' => 'مطب مرکزی', 'address' => 'یزد، خیابان آزمون', 'telephone' => '03512222222', ]); self::assertSame(201, $this->responseCode()); $addressId = $this->createdAddressId($created); $this->authJson('PATCH', '/api/v1/clinic-pro/doctor-address/' . $addressId, $repUser, [ 'address' => 'یزد، خیابان تازه', ]); self::assertSame(200, $this->responseCode()); $this->em->clear(); self::assertSame( 'یزد، خیابان تازه', $this->em->getRepository(DoctorAddress::class)->find($addressId)->getAddress(), ); $this->authJson('DELETE', '/api/v1/clinic-pro/doctor-address/' . $addressId, $repUser); self::assertSame(200, $this->responseCode()); $this->em->clear(); self::assertNull($this->em->getRepository(DoctorAddress::class)->find($addressId)); } public function testAnotherRepresentativeCannotTouchTheAddress(): void { $owner = $this->newRepresentative(); $stranger = $this->newRepresentative(); $uuid = $this->doctorCreatedBy($owner); $created = $this->authJson('POST', '/api/v1/clinic-pro/doctor-address', $owner, [ 'doctor_uuid' => $uuid, 'address' => 'اصلی', ]); $addressId = $this->createdAddressId($created); $this->authJson('POST', '/api/v1/clinic-pro/doctor-address', $stranger, [ 'doctor_uuid' => $uuid, 'address' => 'نفوذی', ]); self::assertSame(403, $this->responseCode()); $this->authJson('PATCH', '/api/v1/clinic-pro/doctor-address/' . $addressId, $stranger, ['address' => 'نفوذی']); self::assertSame(403, $this->responseCode()); $this->authJson('DELETE', '/api/v1/clinic-pro/doctor-address/' . $addressId, $stranger); self::assertSame(403, $this->responseCode()); $this->em->clear(); self::assertSame( 'اصلی', $this->em->getRepository(DoctorAddress::class)->find($addressId)->getAddress(), ); } public function testDoctorUuidWinsOverTheSenderOwnProfile(): void { // نماینده‌ای که خودش پزشک هم هست: آدرس باید روی پزشکِ زیرمجموعه بنشیند، // نه روی پروفایل خودش. $repUser = $this->newRepresentative(); $selfDoc = new Doctor($repUser, 'پزشکِ خودِ نماینده'); $this->em->persist($selfDoc); $this->em->flush(); $selfDocId = $selfDoc->getId(); $targetUuid = $this->doctorCreatedBy($repUser); $created = $this->authJson('POST', '/api/v1/clinic-pro/doctor-address', $repUser, [ 'doctor_uuid' => $targetUuid, 'address' => 'باید روی پزشک زیرمجموعه بنشیند', ]); self::assertSame(201, $this->responseCode()); $this->em->clear(); $address = $this->em->getRepository(DoctorAddress::class)->find($this->createdAddressId($created)); $target = $this->em->getRepository(Doctor::class)->findOneBy(['uuid' => $targetUuid]); self::assertSame($target->getId(), $address->getDoctor()->getId()); self::assertNotSame($selfDocId, $address->getDoctor()->getId()); } public function testRepresentativeWithoutDoctorUuidGetsAValidationError(): void { $repUser = $this->newRepresentative(); $body = $this->authJson('POST', '/api/v1/clinic-pro/doctor-address', $repUser, ['address' => 'بی‌هدف']); self::assertSame(422, $this->responseCode()); self::assertSame('doctor_uuid', $body['errors'][0]['field']); } public function testPlainUserStillGetsForbidden(): void { $plain = $this->createUser(['ROLE_USER']); $this->authJson('POST', '/api/v1/clinic-pro/doctor-address', $plain, ['address' => 'x']); self::assertSame(403, $this->responseCode()); } public function testDoctorCanStillCreateTheirOwnAddressWithoutUuid(): void { $doctorUser = $this->createUser(['ROLE_USER', 'ROLE_DOCTOR']); $doctor = new Doctor($doctorUser, 'پزشک خودگردان'); $this->em->persist($doctor); $this->em->flush(); $doctorId = $doctor->getId(); $created = $this->authJson('POST', '/api/v1/clinic-pro/doctor-address', $doctorUser, ['address' => 'مطب خودم']); self::assertSame(201, $this->responseCode()); $this->em->clear(); $address = $this->em->getRepository(DoctorAddress::class)->find($this->createdAddressId($created)); self::assertSame($doctorId, $address->getDoctor()->getId()); } public function testClinicAddressIsStillUnreachableThroughTheDoctorAddressRoute(): void { $repUser = $this->newRepresentative(); $clinicUuid = $this->clinicCreatedBy($repUser); $clinic = $this->em->getRepository(Clinic::class)->findOneBy(['uuid' => $clinicUuid]); $address = DoctorAddress::forClinic($clinic->getId()); $address->setAddress('آدرس کلینیک'); $this->em->persist($address); $this->em->flush(); $this->authJson('PATCH', '/api/v1/clinic-pro/doctor-address/' . $address->getId(), $repUser, ['address' => 'x']); self::assertSame(403, $this->responseCode()); } // ── آدرس کلینیک ─────────────────────────────────────────────────────────── public function testRepresentativeCreatesUpdatesAndDeletesAClinicAddress(): void { $repUser = $this->newRepresentative(); $uuid = $this->clinicCreatedBy($repUser); $created = $this->authJson('POST', '/api/v1/clinic/' . $uuid . '/address', $repUser, [ 'address' => 'یزد، بلوار آزمون', 'telephone' => '03513333333', ]); self::assertSame(201, $this->responseCode()); $addressUuid = $this->createdAddressUuid($created); $this->authJson('PATCH', '/api/v1/clinic/' . $uuid . '/address/' . $addressUuid, $repUser, [ 'address' => 'یزد، بلوار تازه', ]); self::assertSame(200, $this->responseCode()); $this->authJson('DELETE', '/api/v1/clinic/' . $uuid . '/address/' . $addressUuid, $repUser); self::assertSame(200, $this->responseCode()); } public function testAnotherRepresentativeCannotTouchTheClinicAddress(): void { $owner = $this->newRepresentative(); $stranger = $this->newRepresentative(); $uuid = $this->clinicCreatedBy($owner); $created = $this->authJson('POST', '/api/v1/clinic/' . $uuid . '/address', $owner, ['address' => 'اصلی']); $addressUuid = $this->createdAddressUuid($created); $this->authJson('PATCH', '/api/v1/clinic/' . $uuid . '/address/' . $addressUuid, $stranger, ['address' => 'نفوذی']); self::assertSame(403, $this->responseCode()); $this->authJson('DELETE', '/api/v1/clinic/' . $uuid . '/address/' . $addressUuid, $stranger); self::assertSame(403, $this->responseCode()); } public function testClinicOwnerIsUnaffected(): void { $ownerUser = $this->createUser(['ROLE_USER', 'ROLE_CLINIC']); $clinic = new Clinic($ownerUser); $clinic->setName('کلینیک خودگردان'); $this->em->persist($clinic); $this->em->flush(); $this->authJson('POST', '/api/v1/clinic/' . $clinic->getUuid() . '/address', $ownerUser, ['address' => 'مال خودم']); self::assertSame(201, $this->responseCode()); } }