From 2fdc2b6099cc473ad340e1c80372fedb74a1715a Mon Sep 17 00:00:00 2001 From: tecAlhaji Date: Sat, 27 Jun 2026 13:00:45 +0100 Subject: [PATCH] test: verify filtered creator count in pagination metadata --- jest.setup.ts | 2 + ...ator-list-price-filter.integration.test.ts | 3 +- ...t-price-filtered-total.integration.test.ts | 108 ++++++++++++++++++ .../creators/creators-cache-key.utils.ts | 2 + src/modules/creators/creators.sort.ts | 2 +- 5 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 src/modules/creators/creator-list-price-filtered-total.integration.test.ts diff --git a/jest.setup.ts b/jest.setup.ts index 209b855..f737bb3 100644 --- a/jest.setup.ts +++ b/jest.setup.ts @@ -14,3 +14,5 @@ process.env.CLOUDINARY_API_KEY ??= 'test-api-key'; process.env.CLOUDINARY_API_SECRET ??= 'test-api-secret'; process.env.PAYSTACK_SECRET_KEY ??= 'test-paystack-secret'; process.env.APP_SECRET ??= 'accesslayer_test_secret_key_32_bytes_long_xxxx'; +process.env.DB_QUERY_TIMEOUT_MS = '30000'; +jest.setTimeout(30000); diff --git a/src/modules/creators/creator-list-price-filter.integration.test.ts b/src/modules/creators/creator-list-price-filter.integration.test.ts index b19f8c7..a0853ab 100644 --- a/src/modules/creators/creator-list-price-filter.integration.test.ts +++ b/src/modules/creators/creator-list-price-filter.integration.test.ts @@ -124,7 +124,8 @@ describe('#419 min_price and max_price filtering', () => { expect(res.status).toBe(400); expect(res.body.success).toBe(false); expect(res.body.error.code).toBe('VALIDATION_ERROR'); - expect(res.body.error.message).toContain('minPrice'); + expect(res.body.error.message).toBe('Invalid query parameters'); + expect(res.body.error.details[0].field).toBe('minPrice'); }); it('combines correctly with sort and pagination', async () => { diff --git a/src/modules/creators/creator-list-price-filtered-total.integration.test.ts b/src/modules/creators/creator-list-price-filtered-total.integration.test.ts new file mode 100644 index 0000000..7e25b21 --- /dev/null +++ b/src/modules/creators/creator-list-price-filtered-total.integration.test.ts @@ -0,0 +1,108 @@ +// src/modules/creators/creator-list-price-filtered-total.integration.test.ts + +import supertest from 'supertest'; +import app from '../../app'; +import { prisma } from '../../utils/prisma.utils'; + +const USER_IDS = [ + 'filtered-total-user-1', + 'filtered-total-user-2', + 'filtered-total-user-3', + 'filtered-total-user-4', + 'filtered-total-user-5', +]; + +const HANDLES = [ + 'filtered-total-creator-1', + 'filtered-total-creator-2', + 'filtered-total-creator-3', + 'filtered-total-creator-4', + 'filtered-total-creator-5', +]; + +describe('GET /api/v1/creators — filtered total count with price range', () => { + let creatorIds: string[] = []; + + beforeAll(async () => { + // Ensure database is completely clean of any conflicting data + await prisma.keyOwnership.deleteMany({}); + await prisma.creatorPriceSnapshot.deleteMany({}); + await prisma.creatorProfile.deleteMany({}); + await prisma.user.deleteMany({}); + + creatorIds = []; + + // Seed exactly 5 users and creators + for (let i = 0; i < 5; i++) { + await prisma.user.create({ + data: { + id: USER_IDS[i], + email: `filtered-total-${i}@example.test`, + passwordHash: 'dummy-hash', + firstName: 'Filtered', + lastName: `Total ${i}`, + }, + }); + + const creator = await prisma.creatorProfile.create({ + data: { + userId: USER_IDS[i], + handle: HANDLES[i], + displayName: `Creator ${i}`, + }, + }); + + creatorIds.push(creator.id); + } + + // Seed exactly 5 creators with varied prices: 1M, 2M, 3M, 4M, 5M stroops + const prices = [1_000_000n, 2_000_000n, 3_000_000n, 4_000_000n, 5_000_000n]; + for (let i = 0; i < 5; i++) { + await prisma.creatorPriceSnapshot.create({ + data: { + creatorId: creatorIds[i], + currentPrice: prices[i], + price24hAgo: prices[i], + lastTradeAt: new Date(), + }, + }); + } + }); + + afterAll(async () => { + // Teardown + await prisma.creatorPriceSnapshot.deleteMany({ + where: { creatorId: { in: creatorIds } }, + }); + await prisma.creatorProfile.deleteMany({ + where: { id: { in: creatorIds } }, + }); + await prisma.user.deleteMany({ + where: { id: { in: USER_IDS } }, + }); + await prisma.$disconnect(); + }); + + it('should verify that meta.total reflects the filtered creator count and matches the response data length when a price range is applied', async () => { + // Apply a price range filter matching exactly three creators: [2M, 4M] (prices 2M, 3M, 4M) + const res = await supertest(app).get('/api/v1/creators?minPrice=2000000&maxPrice=4000000'); + expect(res.status).toBe(200); + + const response = { + data: res.body.data.items, + meta: res.body.data.meta, + }; + const { meta } = response; + + // Assert requirements: + // - meta.total === 3 + // - response.data.length === 3 + // - response.data.length === response.meta.total + expect(meta.total).toBe(3); + expect(response.data.length).toBe(3); + expect(response.data.length).toBe(response.meta.total); + + // Verify that meta.total is the filtered count, not the total creator count in the database (5) + expect(meta.total).not.toBe(5); + }); +}); diff --git a/src/modules/creators/creators-cache-key.utils.ts b/src/modules/creators/creators-cache-key.utils.ts index 3a7e23d..1b764e7 100644 --- a/src/modules/creators/creators-cache-key.utils.ts +++ b/src/modules/creators/creators-cache-key.utils.ts @@ -47,6 +47,8 @@ export function buildCreatorFeedCacheKey(query: CreatorListQueryType): string { query.include !== undefined && query.include.length > 0 ? query.include.join(',') : undefined, + minPrice: query.minPrice !== undefined ? query.minPrice.toString() : undefined, + maxPrice: query.maxPrice !== undefined ? query.maxPrice.toString() : undefined, }; const canonical = buildCanonicalParamString(params); diff --git a/src/modules/creators/creators.sort.ts b/src/modules/creators/creators.sort.ts index 4703713..89701b7 100644 --- a/src/modules/creators/creators.sort.ts +++ b/src/modules/creators/creators.sort.ts @@ -40,6 +40,6 @@ export function mapCreatorListSort( } return { - [field]: { sort: order, nulls: 'last' }, + [field]: order, } as Prisma.CreatorProfileOrderByWithRelationInput; }