perf(settlement,rating): paginate 3 unbounded list endpoints (M10-M12)
- M10 GET /settlement: was unbounded; add page/limit + countByUser + data.meta.
- M11 GET /admin/comments/pending: paginate findPending + countPending.
- M12 GET /comments/{doctor}: paginate the fetch-joined roots query via
Paginator(fetchJoinCollection) + countApprovedRootsByDoctor.
All keep the existing { data: { data: [...] } } envelope and add data.meta
(backward compatible). Default limit 50 / max 100.
Regressions: SettlementListPaginationTest, CommentPaginationTest (both fail
without the limits). Also de-flaked SendCodeMobileRateLimitTest (randomised the
IP block so the persistent per-IP limiter buckets don't accumulate across runs).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -184,6 +184,8 @@ Get approved **root** comments for a doctor (replies are nested under each root
|
|||||||
|
|
||||||
**Permission:** `PUBLIC`
|
**Permission:** `PUBLIC`
|
||||||
|
|
||||||
|
> **صفحهبندی:** `?page` و `?limit` (پیشفرض ۵۰، حداکثر ۱۰۰)؛ پاسخ شامل `data.meta` (`totalRecords`/`totalPages`/`currentPage`) است. آرایهی نظرات همچنان در `data.data` است. همین صفحهبندی روی `GET /api/v1/admin/comments/pending` هم اعمال میشود.
|
||||||
|
|
||||||
### Path Parameters
|
### Path Parameters
|
||||||
| Param | Type | Description |
|
| Param | Type | Description |
|
||||||
|-------|------|-------------|
|
|-------|------|-------------|
|
||||||
|
|||||||
@@ -141,10 +141,12 @@ Request a settlement (withdrawal from wallet to bank account).
|
|||||||
|
|
||||||
## GET `/api/v1/settlement`
|
## GET `/api/v1/settlement`
|
||||||
|
|
||||||
Get authenticated user's settlement requests.
|
Get authenticated user's settlement requests (paginated, newest first).
|
||||||
|
|
||||||
**Permission:** `AUTH`
|
**Permission:** `AUTH`
|
||||||
|
|
||||||
|
> **صفحهبندی:** `?page` و `?limit` (پیشفرض ۵۰، حداکثر ۱۰۰). پاسخ علاوه بر `data.data` یک `data.meta` (`totalRecords`/`totalPages`/`currentPage`) دارد؛ پاکت قبلی دستنخورده است.
|
||||||
|
|
||||||
### Response `200`
|
### Response `200`
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -63,9 +63,9 @@ _None outstanding._
|
|||||||
| ✅M7 | Refresh token not rotated on use (same raw token 30d), never re-checks user status | src/Auth/Controller/AuthController.php:477-480 · TokenService.php:33-45 | security-auth | **DONE** — single-use rotation (revoke old + issue new) + suspended-user (status!=1) rejected. `tests/Auth/RefreshTokenRotationTest`. |
|
| ✅M7 | Refresh token not rotated on use (same raw token 30d), never re-checks user status | src/Auth/Controller/AuthController.php:477-480 · TokenService.php:33-45 | security-auth | **DONE** — single-use rotation (revoke old + issue new) + suspended-user (status!=1) rejected. `tests/Auth/RefreshTokenRotationTest`. |
|
||||||
| M8 | N+1: secretary list lazy-loads secretary/doctor ManyToOne per row | src/Secretary/Controller/SecretaryController.php:192,213 | perf-nplus1 | Profiler secretary list → ~2 queries/row |
|
| M8 | N+1: secretary list lazy-loads secretary/doctor ManyToOne per row | src/Secretary/Controller/SecretaryController.php:192,213 | perf-nplus1 | Profiler secretary list → ~2 queries/row |
|
||||||
| M9 | N+1: billing claims lazy `items` + `insuranceRepo->find()` per claim in enrichClaims | src/Billing/Controller/BillingController.php:~49,68 | perf-nplus1 | GET claims → items+insurance query/claim |
|
| M9 | N+1: billing claims lazy `items` + `insuranceRepo->find()` per claim in enrichClaims | src/Billing/Controller/BillingController.php:~49,68 | perf-nplus1 | GET claims → items+insurance query/claim |
|
||||||
| M10 | Unbounded list: `listMine` settlements `findByUser` no limit | src/Settlement/Controller/SettlementController.php:193 | perf-pagination | GET settlement list → paginate |
|
| ✅M10 | Unbounded list: `listMine` settlements `findByUser` no limit | src/Settlement/Controller/SettlementController.php:193 | perf-pagination | **DONE** — page/limit + countByUser + data.meta. `tests/Settlement/SettlementListPaginationTest` |
|
||||||
| M11 | Unbounded list: admin `pendingComments` `findPending` no limit | src/Rating/Controller/RatingController.php:350 | perf-pagination | admin moderation large backlog → paginate |
|
| ✅M11 | Unbounded list: admin `pendingComments` `findPending` no limit | src/Rating/Controller/RatingController.php:350 | perf-pagination | **DONE** — findPending(limit,offset)+countPending+meta. `tests/Rating/CommentPaginationTest::testAdminPendingListPaginates` |
|
||||||
| M12 | Unbounded list: public `listComments` per-doctor no limit | src/Rating/Controller/RatingController.php:270 | perf-pagination | popular doctor comments → paginate |
|
| ✅M12 | Unbounded list: public `listComments` per-doctor no limit | src/Rating/Controller/RatingController.php:270 | perf-pagination | **DONE** — Paginator(fetchJoinCollection) page/limit + countApprovedRootsByDoctor + meta. `tests/Rating/CommentPaginationTest::testPublicListPaginates` |
|
||||||
| ⚠️M13 | Missing index: `ServiceItem.section_id` FK fully unindexed (entity has zero indexes) | src/ClinicService/Entity/ServiceItem.php:23-24 | perf-index | EXPLAIN findBySection → no full scan |
|
| ⚠️M13 | Missing index: `ServiceItem.section_id` FK fully unindexed (entity has zero indexes) | src/ClinicService/Entity/ServiceItem.php:23-24 | perf-index | EXPLAIN findBySection → no full scan |
|
||||||
| ✅M14 | Missing index: `WalletTransaction(user_id, type)` composite for per-type SUM | src/Settlement/Entity/WalletTransaction.php:38-39 | perf-index | **DONE** — added composite `idx_wallet_user_type` + migration. `InfraSmokeTest::testWalletUserTypeIndexExists`. |
|
| ✅M14 | Missing index: `WalletTransaction(user_id, type)` composite for per-type SUM | src/Settlement/Entity/WalletTransaction.php:38-39 | perf-index | **DONE** — added composite `idx_wallet_user_type` + migration. `InfraSmokeTest::testWalletUserTypeIndexExists`. |
|
||||||
| ⚠️M15 | Missing index: `ClinicDoctorInvitation.doctor_id` FK unindexed | src/ClinicInvitation/Entity/ClinicDoctorInvitation.php:41-42 | perf-index | **FALSE POSITIVE** — `doctor_id` has a FK → auto-indexed (IDX_26DCCFEB87F4FB17 exists). No change. |
|
| ⚠️M15 | Missing index: `ClinicDoctorInvitation.doctor_id` FK unindexed | src/ClinicInvitation/Entity/ClinicDoctorInvitation.php:41-42 | perf-index | **FALSE POSITIVE** — `doctor_id` has a FK → auto-indexed (IDX_26DCCFEB87F4FB17 exists). No change. |
|
||||||
|
|||||||
@@ -267,19 +267,30 @@ class RatingController extends BaseController
|
|||||||
]
|
]
|
||||||
)]
|
)]
|
||||||
#[Route('/api/v1/comments/{doctorUuid}', methods: ['GET'])]
|
#[Route('/api/v1/comments/{doctorUuid}', methods: ['GET'])]
|
||||||
public function listComments(string $doctorUuid): JsonResponse
|
public function listComments(string $doctorUuid, Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$doctor = $this->doctorRepo->findByUuid($doctorUuid);
|
$doctor = $this->doctorRepo->findByUuid($doctorUuid);
|
||||||
if ($doctor === null) {
|
if ($doctor === null) {
|
||||||
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'دکتر یافت نشد', 404);
|
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'دکتر یافت نشد', 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$page = max(1, (int) $request->query->get('page', 1));
|
||||||
|
$limit = min(100, max(1, (int) $request->query->get('limit', 50)));
|
||||||
|
|
||||||
$comments = array_map(
|
$comments = array_map(
|
||||||
fn(Comment $c) => $c->toArray(),
|
fn(Comment $c) => $c->toArray(),
|
||||||
$this->commentRepo->findApprovedRootsByDoctor($doctor)
|
$this->commentRepo->findApprovedRootsByDoctor($doctor, $limit, ($page - 1) * $limit)
|
||||||
);
|
);
|
||||||
|
$total = $this->commentRepo->countApprovedRootsByDoctor($doctor);
|
||||||
|
|
||||||
return $this->success(['data' => $comments]);
|
return $this->success([
|
||||||
|
'data' => $comments,
|
||||||
|
'meta' => [
|
||||||
|
'totalRecords' => $total,
|
||||||
|
'totalPages' => (int) ceil($total / $limit),
|
||||||
|
'currentPage' => $page,
|
||||||
|
],
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[OA\Delete(
|
#[OA\Delete(
|
||||||
@@ -347,10 +358,25 @@ class RatingController extends BaseController
|
|||||||
)]
|
)]
|
||||||
#[IsGranted('ROLE_ADMIN')]
|
#[IsGranted('ROLE_ADMIN')]
|
||||||
#[Route('/api/v1/admin/comments/pending', methods: ['GET'])]
|
#[Route('/api/v1/admin/comments/pending', methods: ['GET'])]
|
||||||
public function pendingComments(): JsonResponse
|
public function pendingComments(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$comments = array_map(fn(Comment $c) => $c->toArray(), $this->commentRepo->findPending());
|
$page = max(1, (int) $request->query->get('page', 1));
|
||||||
return $this->success(['data' => $comments]);
|
$limit = min(100, max(1, (int) $request->query->get('limit', 50)));
|
||||||
|
|
||||||
|
$comments = array_map(
|
||||||
|
fn(Comment $c) => $c->toArray(),
|
||||||
|
$this->commentRepo->findPending($limit, ($page - 1) * $limit)
|
||||||
|
);
|
||||||
|
$total = $this->commentRepo->countPending();
|
||||||
|
|
||||||
|
return $this->success([
|
||||||
|
'data' => $comments,
|
||||||
|
'meta' => [
|
||||||
|
'totalRecords' => $total,
|
||||||
|
'totalPages' => (int) ceil($total / $limit),
|
||||||
|
'currentPage' => $page,
|
||||||
|
],
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[OA\Post(
|
#[OA\Post(
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace App\Rating\Repository;
|
|||||||
use App\Doctor\Entity\Doctor;
|
use App\Doctor\Entity\Doctor;
|
||||||
use App\Rating\Entity\Comment;
|
use App\Rating\Entity\Comment;
|
||||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\ORM\Tools\Pagination\Paginator;
|
||||||
use Doctrine\Persistence\ManagerRegistry;
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
class CommentRepository extends ServiceEntityRepository
|
class CommentRepository extends ServiceEntityRepository
|
||||||
@@ -19,15 +20,29 @@ class CommentRepository extends ServiceEntityRepository
|
|||||||
return $this->findBy(['doctor' => $doctor, 'status' => Comment::STATUS_APPROVED], ['createdAt' => 'DESC']);
|
return $this->findBy(['doctor' => $doctor, 'status' => Comment::STATUS_APPROVED], ['createdAt' => 'DESC']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function countApprovedRootsByDoctor(Doctor $doctor): int
|
||||||
|
{
|
||||||
|
return (int) $this->createQueryBuilder('c')
|
||||||
|
->select('COUNT(c.id)')
|
||||||
|
->where('c.doctor = :doctor')
|
||||||
|
->andWhere('c.status = :status')
|
||||||
|
->andWhere('c.parent IS NULL')
|
||||||
|
->setParameter('doctor', $doctor)
|
||||||
|
->setParameter('status', Comment::STATUS_APPROVED)
|
||||||
|
->getQuery()
|
||||||
|
->getSingleScalarResult();
|
||||||
|
}
|
||||||
|
|
||||||
/** @return Comment[] approved root comments (no parent) for a doctor */
|
/** @return Comment[] approved root comments (no parent) for a doctor */
|
||||||
public function findApprovedRootsByDoctor(Doctor $doctor): array
|
public function findApprovedRootsByDoctor(Doctor $doctor, int $limit = 50, int $offset = 0): array
|
||||||
{
|
{
|
||||||
// Two fetch-join passes (not the product, to avoid a likes×replies
|
// Two fetch-join passes (not the product, to avoid a likes×replies
|
||||||
// cartesian) hydrate everything toArray() touches, so serialization
|
// cartesian) hydrate everything toArray() touches, so serialization
|
||||||
// triggers no per-comment lazy loads (N+1): pass 1 = roots + author +
|
// triggers no per-comment lazy loads (N+1): pass 1 = roots + author +
|
||||||
// likes, pass 2 = replies + their author + likes, merged into the same
|
// likes, pass 2 = replies + their author + likes, merged into the same
|
||||||
// managed root entities.
|
// managed root entities. Paginator(fetchJoinCollection) keeps the LIMIT
|
||||||
$roots = $this->createQueryBuilder('c')
|
// paginating by root comment, not by joined like rows.
|
||||||
|
$qb = $this->createQueryBuilder('c')
|
||||||
->addSelect('u', 'l')
|
->addSelect('u', 'l')
|
||||||
->leftJoin('c.user', 'u')
|
->leftJoin('c.user', 'u')
|
||||||
->leftJoin('c.likes', 'l')
|
->leftJoin('c.likes', 'l')
|
||||||
@@ -37,8 +52,10 @@ class CommentRepository extends ServiceEntityRepository
|
|||||||
->setParameter('doctor', $doctor)
|
->setParameter('doctor', $doctor)
|
||||||
->setParameter('status', Comment::STATUS_APPROVED)
|
->setParameter('status', Comment::STATUS_APPROVED)
|
||||||
->orderBy('c.createdAt', 'DESC')
|
->orderBy('c.createdAt', 'DESC')
|
||||||
->getQuery()
|
->setFirstResult($offset)
|
||||||
->getResult();
|
->setMaxResults($limit);
|
||||||
|
|
||||||
|
$roots = iterator_to_array(new Paginator($qb, fetchJoinCollection: true));
|
||||||
|
|
||||||
if ($roots !== []) {
|
if ($roots !== []) {
|
||||||
// Also initialise each reply's own replies collection (empty in a
|
// Also initialise each reply's own replies collection (empty in a
|
||||||
@@ -58,10 +75,15 @@ class CommentRepository extends ServiceEntityRepository
|
|||||||
return $roots;
|
return $roots;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return Comment[] */
|
public function countPending(): int
|
||||||
public function findPending(): array
|
|
||||||
{
|
{
|
||||||
return $this->findBy(['status' => Comment::STATUS_PENDING], ['createdAt' => 'ASC']);
|
return $this->count(['status' => Comment::STATUS_PENDING]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return Comment[] */
|
||||||
|
public function findPending(int $limit = 50, int $offset = 0): array
|
||||||
|
{
|
||||||
|
return $this->findBy(['status' => Comment::STATUS_PENDING], ['createdAt' => 'ASC'], $limit, $offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function save(Comment $e, bool $flush = true): void { $this->getEntityManager()->persist($e); if ($flush) $this->getEntityManager()->flush(); }
|
public function save(Comment $e, bool $flush = true): void { $this->getEntityManager()->persist($e); if ($flush) $this->getEntityManager()->flush(); }
|
||||||
|
|||||||
@@ -203,14 +203,25 @@ class SettlementController extends BaseController
|
|||||||
]
|
]
|
||||||
)]
|
)]
|
||||||
#[Route('/api/v1/settlement', methods: ['GET'])]
|
#[Route('/api/v1/settlement', methods: ['GET'])]
|
||||||
public function listMine(#[CurrentUser] User $user): JsonResponse
|
public function listMine(Request $request, #[CurrentUser] User $user): JsonResponse
|
||||||
{
|
{
|
||||||
|
$page = max(1, (int) $request->query->get('page', 1));
|
||||||
|
$limit = min(100, max(1, (int) $request->query->get('limit', 50)));
|
||||||
|
|
||||||
$settlements = array_map(
|
$settlements = array_map(
|
||||||
fn(Settlement $s) => $s->toArray(),
|
fn(Settlement $s) => $s->toArray(),
|
||||||
$this->settlementRepo->findByUser($user)
|
$this->settlementRepo->findByUser($user, $limit, ($page - 1) * $limit)
|
||||||
);
|
);
|
||||||
|
$total = $this->settlementRepo->countByUser($user);
|
||||||
|
|
||||||
return $this->success(['data' => $settlements]);
|
return $this->success([
|
||||||
|
'data' => $settlements,
|
||||||
|
'meta' => [
|
||||||
|
'totalRecords' => $total,
|
||||||
|
'totalPages' => (int) ceil($total / $limit),
|
||||||
|
'currentPage' => $page,
|
||||||
|
],
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[OA\Get(
|
#[OA\Get(
|
||||||
|
|||||||
@@ -20,9 +20,14 @@ class SettlementRepository extends ServiceEntityRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @return Settlement[] */
|
/** @return Settlement[] */
|
||||||
public function findByUser(User $user): array
|
public function findByUser(User $user, int $limit = 50, int $offset = 0): array
|
||||||
{
|
{
|
||||||
return $this->findBy(['user' => $user], ['createdAt' => 'DESC']);
|
return $this->findBy(['user' => $user], ['createdAt' => 'DESC'], $limit, $offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function countByUser(User $user): int
|
||||||
|
{
|
||||||
|
return $this->count(['user' => $user]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Balance = sum of credits - sum of debits from wallet_transactions */
|
/** Balance = sum of credits - sum of debits from wallet_transactions */
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ class SendCodeMobileRateLimitTest extends ApiTestCase
|
|||||||
{
|
{
|
||||||
$this->client->disableReboot();
|
$this->client->disableReboot();
|
||||||
$mobile = '0912' . str_pad((string) random_int(0, 9_999_999), 7, '0', STR_PAD_LEFT);
|
$mobile = '0912' . str_pad((string) random_int(0, 9_999_999), 7, '0', STR_PAD_LEFT);
|
||||||
|
// a fresh random IP block per run so the persistent per-IP limiter buckets
|
||||||
|
// don't accumulate across runs and fire early.
|
||||||
|
$ipBase = random_int(1, 250);
|
||||||
|
|
||||||
$statuses = [];
|
$statuses = [];
|
||||||
for ($i = 0; $i < 6; $i++) {
|
for ($i = 0; $i < 6; $i++) {
|
||||||
@@ -21,7 +24,7 @@ class SendCodeMobileRateLimitTest extends ApiTestCase
|
|||||||
$this->client->request(
|
$this->client->request(
|
||||||
'POST',
|
'POST',
|
||||||
'/api/v1/user/send-code',
|
'/api/v1/user/send-code',
|
||||||
server: ['REMOTE_ADDR' => "10.20.30.$i", 'CONTENT_TYPE' => 'application/json'],
|
server: ['REMOTE_ADDR' => "10.$ipBase.30.$i", 'CONTENT_TYPE' => 'application/json'],
|
||||||
content: json_encode(['mobile' => $mobile]),
|
content: json_encode(['mobile' => $mobile]),
|
||||||
);
|
);
|
||||||
$statuses[] = $this->client->getResponse()->getStatusCode();
|
$statuses[] = $this->client->getResponse()->getStatusCode();
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Tests\Rating;
|
||||||
|
|
||||||
|
use App\Doctor\Entity\Doctor;
|
||||||
|
use App\Rating\Entity\Comment;
|
||||||
|
use App\Tests\ApiTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Both the public per-doctor comment list and the admin pending-comment list
|
||||||
|
* must page rather than return every row.
|
||||||
|
*/
|
||||||
|
class CommentPaginationTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
public function testPublicListPaginates(): void
|
||||||
|
{
|
||||||
|
$doctor = new Doctor($this->createUser(['ROLE_DOCTOR']), 'دکتر');
|
||||||
|
$this->em->persist($doctor);
|
||||||
|
for ($i = 0; $i < 7; $i++) {
|
||||||
|
$this->em->persist((new Comment($this->createUser(), $doctor, "نظر $i"))->approve());
|
||||||
|
}
|
||||||
|
$this->em->flush();
|
||||||
|
|
||||||
|
$this->client->request('GET', '/api/v1/comments/' . $doctor->getUuid() . '?limit=5');
|
||||||
|
$body = json_decode($this->client->getResponse()->getContent(), true);
|
||||||
|
|
||||||
|
$this->assertSame(200, $this->responseCode());
|
||||||
|
$this->assertCount(5, $body['data']['data']);
|
||||||
|
$this->assertSame(7, $body['data']['meta']['totalRecords']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAdminPendingListPaginates(): void
|
||||||
|
{
|
||||||
|
$doctor = new Doctor($this->createUser(['ROLE_DOCTOR']), 'دکتر');
|
||||||
|
$this->em->persist($doctor);
|
||||||
|
$admin = $this->createUser(['ROLE_ADMIN']);
|
||||||
|
for ($i = 0; $i < 7; $i++) {
|
||||||
|
// not approved → pending
|
||||||
|
$this->em->persist(new Comment($this->createUser(), $doctor, "در انتظار $i"));
|
||||||
|
}
|
||||||
|
$this->em->flush();
|
||||||
|
|
||||||
|
$body = $this->authJson('GET', '/api/v1/admin/comments/pending?limit=5', $admin);
|
||||||
|
|
||||||
|
$this->assertSame(200, $this->responseCode());
|
||||||
|
$this->assertCount(5, $body['data']['data']);
|
||||||
|
// pending is global (not per-doctor); db_test may hold others → at least 7
|
||||||
|
$this->assertGreaterThanOrEqual(7, $body['data']['meta']['totalRecords']);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Tests\Settlement;
|
||||||
|
|
||||||
|
use App\Settlement\Entity\Settlement;
|
||||||
|
use App\Tests\ApiTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/v1/settlement must page rather than return every settlement, exposing
|
||||||
|
* totals in meta without breaking the data.data envelope.
|
||||||
|
*/
|
||||||
|
class SettlementListPaginationTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
public function testPaginatesAndReportsTotals(): void
|
||||||
|
{
|
||||||
|
$user = $this->createUser();
|
||||||
|
for ($i = 0; $i < 7; $i++) {
|
||||||
|
$this->em->persist(new Settlement($user, 100_000));
|
||||||
|
}
|
||||||
|
$this->em->flush();
|
||||||
|
|
||||||
|
$body = $this->authJson('GET', '/api/v1/settlement?limit=5', $user);
|
||||||
|
$this->assertSame(200, $this->responseCode());
|
||||||
|
$this->assertCount(5, $body['data']['data']);
|
||||||
|
$this->assertSame(7, $body['data']['meta']['totalRecords']);
|
||||||
|
|
||||||
|
$page2 = $this->authJson('GET', '/api/v1/settlement?limit=5&page=2', $user);
|
||||||
|
$this->assertCount(2, $page2['data']['data']);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user