Verification pass found three tests only guarded correctness, not the fix's behavior: - H7: add repository white-box test asserting likes/replies come back as initialised PersistentCollections (lazy without the fetch-join). - H8: add a query-count test (constant vs coverage-row count) — without the batch fetch the count grows ~1 per row. - H5: add an end-to-end test hitting DELETE /api/v1/doctor and asserting the insurance config is purged (the service unit test didn't cover the wiring). All three now fail when their fix is reverted. Suite: 39 tests / 92 assertions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
91 lines
3.7 KiB
PHP
91 lines
3.7 KiB
PHP
<?php
|
|
|
|
namespace App\Tests\Rating;
|
|
|
|
use App\Doctor\Entity\Doctor;
|
|
use App\Rating\Entity\Comment;
|
|
use App\Rating\Entity\Like;
|
|
use App\Tests\ApiTestCase;
|
|
use Doctrine\ORM\PersistentCollection;
|
|
|
|
/**
|
|
* The public comment list fetch-joins likes/replies/authors (two passes) to
|
|
* avoid lazy loading per comment (N+1). This guards the refactor's correctness:
|
|
* the serialized tree — like counts, approved replies, author names — must be
|
|
* unchanged.
|
|
*/
|
|
class CommentListNPlusOneTest extends ApiTestCase
|
|
{
|
|
public function testTreeIsSerializedCorrectly(): void
|
|
{
|
|
$doctor = new Doctor($this->createUser(['ROLE_DOCTOR']), 'دکتر تست');
|
|
$this->em->persist($doctor);
|
|
|
|
$root = (new Comment($this->createUser(), $doctor, 'نظر ریشه'))->approve();
|
|
$this->em->persist($root);
|
|
// two likes + one dislike → like_count 2, dislike_count 1
|
|
$this->em->persist(new Like($this->createUser(), $root, 1));
|
|
$this->em->persist(new Like($this->createUser(), $root, 1));
|
|
$this->em->persist(new Like($this->createUser(), $root, -1));
|
|
|
|
$reply = (new Comment($this->createUser(), $doctor, 'پاسخ تأییدشده', $root))->approve();
|
|
$this->em->persist($reply);
|
|
// a pending reply must NOT appear
|
|
$this->em->persist(new Comment($this->createUser(), $doctor, 'پاسخ در انتظار', $root));
|
|
$this->em->flush();
|
|
|
|
$this->em->clear();
|
|
|
|
$body = $this->jsonGet('/api/v1/comments/' . $doctor->getUuid());
|
|
|
|
$roots = $body['data']['data'];
|
|
$this->assertCount(1, $roots);
|
|
|
|
$r = $roots[0];
|
|
$this->assertSame('نظر ریشه', $r['comment']);
|
|
$this->assertSame(2, $r['like_status']['like_count']);
|
|
$this->assertSame(1, $r['like_status']['dislike_count']);
|
|
$this->assertCount(1, $r['replies']); // only the approved reply
|
|
$this->assertSame('پاسخ تأییدشده', $r['replies'][0]['comment']);
|
|
}
|
|
|
|
private function jsonGet(string $uri): array
|
|
{
|
|
$this->client->request('GET', $uri);
|
|
|
|
return json_decode($this->client->getResponse()->getContent(), true) ?? [];
|
|
}
|
|
|
|
/**
|
|
* True N+1 regression: after the repository loads the roots, their likes and
|
|
* replies collections must already be initialised (fetch-joined). Without the
|
|
* fetch-join they are lazy and would each trigger a query during serialization.
|
|
*/
|
|
public function testRepositoryFetchJoinsCollections(): void
|
|
{
|
|
$doctor = new Doctor($this->createUser(['ROLE_DOCTOR']), 'دکتر تست');
|
|
$this->em->persist($doctor);
|
|
$root = (new Comment($this->createUser(), $doctor, 'نظر'))->approve();
|
|
$this->em->persist($root);
|
|
$this->em->persist(new Like($this->createUser(), $root, 1));
|
|
$this->em->persist((new Comment($this->createUser(), $doctor, 'پاسخ', $root))->approve());
|
|
$this->em->flush();
|
|
$doctorId = $doctor->getId();
|
|
|
|
// fresh load — collections must come back initialised, not lazy proxies
|
|
$this->em->clear();
|
|
$doctor = $this->em->getRepository(Doctor::class)->find($doctorId);
|
|
$roots = $this->em->getRepository(Comment::class)->findApprovedRootsByDoctor($doctor);
|
|
|
|
$this->assertNotEmpty($roots);
|
|
foreach ($roots as $r) {
|
|
$likes = $r->getLikes();
|
|
$replies = $r->getReplies();
|
|
$this->assertInstanceOf(PersistentCollection::class, $likes);
|
|
$this->assertInstanceOf(PersistentCollection::class, $replies);
|
|
$this->assertTrue($likes->isInitialized(), 'likes collection was not fetch-joined');
|
|
$this->assertTrue($replies->isInitialized(), 'replies collection was not fetch-joined');
|
|
}
|
|
}
|
|
}
|