*/ private function idsOf(array $body): array { return array_map(static fn(array $r) => (int) $r['id'], $body['data']); } private function createdId(array $body): int { return (int) $body['data']['data']['id']; } // ── specialties ─────────────────────────────────────────────────────────── public function testSpecialtySearchByParentNameReturnsChildren(): void { $admin = $this->createUser(['ROLE_ADMIN']); $marker = 'پوستآزمون' . uniqid(); $parentId = $this->createdId( $this->authJson('POST', '/api/v1/admin/specialty', $admin, ['name' => $marker]) ); $childId = $this->createdId( $this->authJson('POST', '/api/v1/admin/specialty', $admin, ['name' => 'لیزر درمانی ' . uniqid(), 'parent_id' => $parentId]) ); $unrelatedId = $this->createdId( $this->authJson('POST', '/api/v1/admin/specialty', $admin, ['name' => 'قلب' . uniqid()]) ); $body = $this->authJson('GET', '/api/v1/admin/specialties?limit=100&search=' . urlencode($marker), $admin); self::assertSame(200, $this->responseCode()); $ids = $this->idsOf($body); self::assertContains($childId, $ids, 'فرزند باید با نام والد پیدا شود'); self::assertContains($parentId, $ids, 'خودِ والد هم باید بماند'); self::assertNotContains($unrelatedId, $ids); } public function testSpecialtyRowCarriesParentName(): void { $admin = $this->createUser(['ROLE_ADMIN']); $marker = 'والدآزمون' . uniqid(); $parentId = $this->createdId( $this->authJson('POST', '/api/v1/admin/specialty', $admin, ['name' => $marker]) ); $this->authJson('POST', '/api/v1/admin/specialty', $admin, ['name' => 'زیرشاخه ' . uniqid(), 'parent_id' => $parentId]); $body = $this->authJson('GET', '/api/v1/admin/specialties?limit=100&search=' . urlencode($marker), $admin); $child = null; foreach ($body['data'] as $row) { if ((int) ($row['parent_id'] ?? 0) === $parentId) $child = $row; } self::assertNotNull($child); self::assertSame($marker, $child['parent_name']); } public function testSpecialtySearchBySlugStillMatches(): void { $admin = $this->createUser(['ROLE_ADMIN']); $slug = 'slug-test-' . uniqid(); $id = $this->createdId( $this->authJson('POST', '/api/v1/admin/specialty', $admin, ['name' => 'نام دلخواه ' . uniqid(), 'slug' => $slug]) ); $body = $this->authJson('GET', '/api/v1/admin/specialties?limit=100&search=' . urlencode($slug), $admin); self::assertContains($id, $this->idsOf($body)); } // ── doctor services ─────────────────────────────────────────────────────── public function testDoctorServiceSearchBySpecialtyName(): void { $admin = $this->createUser(['ROLE_ADMIN']); $marker = 'تخصصآزمون' . uniqid(); $specialtyId = $this->createdId( $this->authJson('POST', '/api/v1/admin/specialty', $admin, ['name' => $marker]) ); $serviceId = $this->createdId( $this->authJson('POST', '/api/v1/admin/doctor-service', $admin, ['name' => 'بوتاکس ' . uniqid(), 'specialty_id' => $specialtyId]) ); $unrelatedId = $this->createdId( $this->authJson('POST', '/api/v1/admin/doctor-service', $admin, ['name' => 'اکو' . uniqid()]) ); $body = $this->authJson('GET', '/api/v1/admin/doctor-services?limit=100&search=' . urlencode($marker), $admin); self::assertSame(200, $this->responseCode()); $ids = $this->idsOf($body); self::assertContains($serviceId, $ids); self::assertNotContains($unrelatedId, $ids); $row = null; foreach ($body['data'] as $r) { if ((int) $r['id'] === $serviceId) $row = $r; } self::assertSame($marker, $row['specialty_name']); } public function testDoctorServiceWithoutSpecialtyIsStillListed(): void { $admin = $this->createUser(['ROLE_ADMIN']); $marker = 'خدمتتنها' . uniqid(); $id = $this->createdId( $this->authJson('POST', '/api/v1/admin/doctor-service', $admin, ['name' => $marker]) ); $body = $this->authJson('GET', '/api/v1/admin/doctor-services?limit=100&search=' . urlencode($marker), $admin); $ids = $this->idsOf($body); self::assertContains($id, $ids, 'LEFT JOIN نباید ردیفِ بدون تخصص را حذف کند'); self::assertSame(1, $body['meta']['totalRecords']); } // ── cities ──────────────────────────────────────────────────────────────── public function testCitySearchByProvinceName(): void { $admin = $this->createUser(['ROLE_ADMIN']); $marker = 'استانآزمون' . uniqid(); $provinceId = $this->createdId( $this->authJson('POST', '/api/v1/admin/province', $admin, ['name' => $marker]) ); $cityId = $this->createdId( $this->authJson('POST', '/api/v1/admin/city', $admin, ['name' => 'شهر یک ' . uniqid(), 'province_id' => $provinceId]) ); $unrelatedId = $this->createdId( $this->authJson('POST', '/api/v1/admin/city', $admin, ['name' => 'شهر دو' . uniqid()]) ); $body = $this->authJson('GET', '/api/v1/admin/cities?limit=100&search=' . urlencode($marker), $admin); self::assertSame(200, $this->responseCode()); $ids = $this->idsOf($body); self::assertContains($cityId, $ids); self::assertNotContains($unrelatedId, $ids); } public function testCitySearchByDomain(): void { $admin = $this->createUser(['ROLE_ADMIN']); $domain = 'search-test-' . uniqid() . '.example'; $cityId = $this->createdId( $this->authJson('POST', '/api/v1/admin/city', $admin, ['name' => 'شهر دامنه‌دار ' . uniqid(), 'domain' => $domain]) ); $body = $this->authJson('GET', '/api/v1/admin/cities?limit=100&search=' . urlencode($domain), $admin); self::assertContains($cityId, $this->idsOf($body)); } // ── boundaries ──────────────────────────────────────────────────────────── public function testUnknownTermReturnsEmptyList(): void { $admin = $this->createUser(['ROLE_ADMIN']); $body = $this->authJson('GET', '/api/v1/admin/specialties?search=' . urlencode('چنینچیزینیست' . uniqid()), $admin); self::assertSame(200, $this->responseCode()); self::assertSame([], $body['data']); self::assertSame(0, $body['meta']['totalRecords']); } public function testNonAdminForbidden(): void { $user = $this->createUser(['ROLE_USER']); $this->authJson('GET', '/api/v1/admin/specialties?search=x', $user); self::assertSame(403, $this->responseCode()); } }