em->persist($province); $city = new City($name, $province); $this->em->persist($city); return $city; } private function makePost(string $title, ?City $city): Blog { $blog = new Blog($this->createUser(['ROLE_ADMIN']), $title, 'متن آزمایشی مقاله برای تست'); $blog->setStatus(Blog::STATUS_PUBLISHED)->setCity($city); $this->em->persist($blog); return $blog; } /** @return array published list keyed by title */ private function listBy(string $query = ''): array { $this->client->request('GET', '/api/v1/blogs?limit=50' . $query); $this->assertSame(200, $this->responseCode()); $payload = json_decode($this->client->getResponse()->getContent(), true); $byTitle = []; foreach ($payload['data'] as $row) { $byTitle[$row['title']] = $row; } return $byTitle; } public function testCityScopedListReturnsCityPostsPlusNationwide(): void { $yasuj = $this->makeCity('یاسوج'); $tabriz = $this->makeCity('تبریز'); $tag = bin2hex(random_bytes(4)); $this->makePost("یاسوجی-$tag", $yasuj); $this->makePost("تبریزی-$tag", $tabriz); $this->makePost("سراسری-$tag", null); $this->em->flush(); $scoped = $this->listBy('&city_id=' . $yasuj->getId()); $this->assertArrayHasKey("یاسوجی-$tag", $scoped, 'city post missing'); $this->assertArrayHasKey("سراسری-$tag", $scoped, 'nationwide post must appear on a city domain'); $this->assertArrayNotHasKey("تبریزی-$tag", $scoped, 'another city\'s post leaked into the list'); } public function testUnscopedListReturnsEveryPublishedPost(): void { $yasuj = $this->makeCity('یاسوج'); $tag = bin2hex(random_bytes(4)); $this->makePost("یاسوجی-$tag", $yasuj); $this->makePost("سراسری-$tag", null); $this->em->flush(); $all = $this->listBy(); $this->assertArrayHasKey("یاسوجی-$tag", $all); $this->assertArrayHasKey("سراسری-$tag", $all); } public function testCityAppearsInListAndDetailPayload(): void { $yasuj = $this->makeCity('یاسوج'); $tag = bin2hex(random_bytes(4)); $cityPost = $this->makePost("یاسوجی-$tag", $yasuj); $natPost = $this->makePost("سراسری-$tag", null); $this->em->flush(); $list = $this->listBy(); $this->assertSame( ['id' => (string) $yasuj->getId(), 'name' => 'یاسوج'], $list["یاسوجی-$tag"]['city'] ); $this->assertNull($list["سراسری-$tag"]['city'], 'nationwide post must report city: null'); $this->client->request('GET', '/api/v1/blog/' . $cityPost->getSlug()); $this->assertSame(200, $this->responseCode()); $detail = json_decode($this->client->getResponse()->getContent(), true)['data']['data']; $this->assertSame('یاسوج', $detail['city']['name']); $this->client->request('GET', '/api/v1/blog/' . $natPost->getSlug()); $this->assertSame(200, $this->responseCode()); $natDetail = json_decode($this->client->getResponse()->getContent(), true)['data']['data']; $this->assertNull($natDetail['city']); } public function testAdminCanCreatePostWithAndWithoutCity(): void { $admin = $this->createUser(['ROLE_ADMIN']); $yasuj = $this->makeCity('یاسوج'); $this->em->flush(); $withCity = $this->authJson('POST', '/api/v1/blog', $admin, [ 'title' => 'مقاله شهری ' . bin2hex(random_bytes(3)), 'body' => 'متن آزمایشی مقاله برای تست', 'city_id' => $yasuj->getId(), ]); $this->assertSame(201, $this->responseCode()); $this->assertSame('یاسوج', $withCity['data']['data']['city']['name']); $nationwide = $this->authJson('POST', '/api/v1/blog', $admin, [ 'title' => 'مقاله سراسری ' . bin2hex(random_bytes(3)), 'body' => 'متن آزمایشی مقاله برای تست', ]); $this->assertSame(201, $this->responseCode()); $this->assertNull($nationwide['data']['data']['city'], 'omitting city_id must mean nationwide'); } public function testAdminCanMovePostBetweenCityAndNationwide(): void { $admin = $this->createUser(['ROLE_ADMIN']); $yasuj = $this->makeCity('یاسوج'); $post = $this->makePost('مقاله ' . bin2hex(random_bytes(3)), null); $this->em->flush(); $assigned = $this->authJson('PATCH', '/api/v1/blog/' . $post->getUuid(), $admin, [ 'city_id' => $yasuj->getId(), ]); $this->assertSame(200, $this->responseCode()); $this->assertSame('یاسوج', $assigned['data']['data']['city']['name']); $cleared = $this->authJson('PATCH', '/api/v1/blog/' . $post->getUuid(), $admin, [ 'city_id' => null, ]); $this->assertSame(200, $this->responseCode()); $this->assertNull($cleared['data']['data']['city'], 'city_id: null must make the post nationwide again'); } public function testPatchWithoutCityIdLeavesCityUntouched(): void { $admin = $this->createUser(['ROLE_ADMIN']); $yasuj = $this->makeCity('یاسوج'); $post = $this->makePost('مقاله ' . bin2hex(random_bytes(3)), $yasuj); $this->em->flush(); $updated = $this->authJson('PATCH', '/api/v1/blog/' . $post->getUuid(), $admin, [ 'summary' => 'خلاصه جدید', ]); $this->assertSame(200, $this->responseCode()); $this->assertSame('یاسوج', $updated['data']['data']['city']['name'], 'PATCH must not silently clear the city'); } public function testUnknownCityIdIsRejected(): void { $admin = $this->createUser(['ROLE_ADMIN']); $this->em->flush(); $this->authJson('POST', '/api/v1/blog', $admin, [ 'title' => 'مقاله ' . bin2hex(random_bytes(3)), 'body' => 'متن آزمایشی مقاله برای تست', 'city_id' => 999999, ]); $this->assertSame(422, $this->responseCode()); } }