From dc0cf6d8b47989b7c424d4e92942ea2f93a431de Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Mon, 16 Feb 2026 12:38:42 -0800 Subject: [PATCH] test(platform-service): add referrals route-level tests --- .../src/modules/referrals/routes.test.ts | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 services/platform-service/src/modules/referrals/routes.test.ts diff --git a/services/platform-service/src/modules/referrals/routes.test.ts b/services/platform-service/src/modules/referrals/routes.test.ts new file mode 100644 index 00000000..55ea607a --- /dev/null +++ b/services/platform-service/src/modules/referrals/routes.test.ts @@ -0,0 +1,191 @@ +/** + * Route-level tests for referrals module — Fastify inject. + */ + +import Fastify from 'fastify'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +const repoMock = { + listAll: vi.fn(), + countReferrals: vi.fn(), + getByReferrer: vi.fn(), + getByReferredEmail: vi.fn(), + create: vi.fn(), + update: vi.fn(), +}; + +const webhookMock = { + dispatchReferralStatusChanged: vi.fn(), +}; + +vi.mock('./repository.js', () => repoMock); +vi.mock('../../lib/webhooks.js', () => webhookMock); +vi.mock('../../lib/request-context.js', () => ({ + getRequestProductId: () => 'lysnrai', +})); + +const baseReferral = { + id: 'ref_1', + productId: 'lysnrai', + referrerId: 'user_1', + referrerEmail: 'referrer@example.com', + referredUserId: null, + referredEmail: 'friend@example.com', + status: 'pending', + referrerRewardTokens: 1000, + referredRewardTokens: 500, + referrerRewarded: false, + referredRewarded: false, + createdAt: '2026-02-16T00:00:00Z', + completedAt: null, +}; + +describe('referralRoutes', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('GET /referrals lists referrals', async () => { + repoMock.listAll.mockResolvedValue([baseReferral]); + + const { referralRoutes } = await import('./routes.js'); + const app = Fastify({ logger: false }); + await app.register(referralRoutes, { prefix: '/api' }); + + const res = await app.inject({ method: 'GET', url: '/api/referrals?limit=10&offset=0' }); + + expect(res.statusCode).toBe(200); + const data = JSON.parse(res.body); + expect(data.referrals).toHaveLength(1); + expect(data.count).toBe(1); + }); + + it('GET /referrals/stats returns counters', async () => { + repoMock.countReferrals.mockResolvedValue({ total: 10, completed: 4, rewarded: 2 }); + + const { referralRoutes } = await import('./routes.js'); + const app = Fastify({ logger: false }); + await app.register(referralRoutes, { prefix: '/api' }); + + const res = await app.inject({ method: 'GET', url: '/api/referrals/stats' }); + + expect(res.statusCode).toBe(200); + const data = JSON.parse(res.body); + expect(data.total).toBe(10); + }); + + it('GET /referrals/by-email/:email returns 404 when missing', async () => { + repoMock.getByReferredEmail.mockResolvedValue(null); + + const { referralRoutes } = await import('./routes.js'); + const app = Fastify({ logger: false }); + await app.register(referralRoutes, { prefix: '/api' }); + + const res = await app.inject({ method: 'GET', url: '/api/referrals/by-email/missing@example.com' }); + + expect(res.statusCode).toBe(404); + }); + + it('POST /referrals creates referral when email not existing', async () => { + repoMock.getByReferredEmail.mockResolvedValue(null); + repoMock.create.mockResolvedValue(baseReferral); + + const { referralRoutes } = await import('./routes.js'); + const app = Fastify({ logger: false }); + await app.register(referralRoutes, { prefix: '/api' }); + + const res = await app.inject({ + method: 'POST', + url: '/api/referrals', + payload: { + referrerId: 'user_1', + referrerEmail: 'referrer@example.com', + referredEmail: 'friend@example.com', + referrerRewardTokens: 1000, + referredRewardTokens: 500, + }, + }); + + expect(res.statusCode).toBe(201); + const data = JSON.parse(res.body); + expect(data.id).toBe('ref_1'); + }); + + it('POST /referrals returns 400 for duplicate referred email', async () => { + repoMock.getByReferredEmail.mockResolvedValue(baseReferral); + + const { referralRoutes } = await import('./routes.js'); + const app = Fastify({ logger: false }); + await app.register(referralRoutes, { prefix: '/api' }); + + const res = await app.inject({ + method: 'POST', + url: '/api/referrals', + payload: { + referrerId: 'user_1', + referrerEmail: 'referrer@example.com', + referredEmail: 'friend@example.com', + }, + }); + + expect(res.statusCode).toBe(400); + }); + + it('PUT /referrals/:id requires referrerId query param', async () => { + const { referralRoutes } = await import('./routes.js'); + const app = Fastify({ logger: false }); + await app.register(referralRoutes, { prefix: '/api' }); + + const res = await app.inject({ + method: 'PUT', + url: '/api/referrals/ref_1', + payload: { status: 'signed_up' }, + }); + + expect(res.statusCode).toBe(400); + }); + + it('PUT /referrals/:id updates referral and dispatches webhook', async () => { + repoMock.update.mockResolvedValue({ + ...baseReferral, + status: 'signed_up', + referredUserId: 'user_2', + completedAt: '2026-02-16T01:00:00Z', + }); + + const { referralRoutes } = await import('./routes.js'); + const app = Fastify({ logger: false }); + await app.register(referralRoutes, { prefix: '/api' }); + + const res = await app.inject({ + method: 'PUT', + url: '/api/referrals/ref_1?referrerId=user_1', + payload: { status: 'signed_up', referredUserId: 'user_2' }, + }); + + expect(res.statusCode).toBe(200); + const data = JSON.parse(res.body); + expect(data.status).toBe('signed_up'); + expect(webhookMock.dispatchReferralStatusChanged).toHaveBeenCalled(); + }); + + it('PUT /referrals/:id returns 404 when update returns null', async () => { + repoMock.update.mockResolvedValue(null); + + const { referralRoutes } = await import('./routes.js'); + const app = Fastify({ logger: false }); + await app.register(referralRoutes, { prefix: '/api' }); + + const res = await app.inject({ + method: 'PUT', + url: '/api/referrals/ref_1?referrerId=user_1', + payload: { status: 'signed_up' }, + }); + + expect(res.statusCode).toBe(404); + }); +});