import { describe, it, expect, beforeAll, beforeEach, afterAll, vi } from 'vitest' // Keep external sockets / FCM no-op so buildPublic doesn't try to open them. vi.mock('../../src/plugins/websocket.js', () => ({ sendToUser: vi.fn(() => false), sendToSessionParticipant: vi.fn(() => false), registerWebSocketPlugin: vi.fn(async () => {}), registerWebSocketRoute: vi.fn(), isUserOnlineWs: vi.fn(() => false), getSessionConnections: vi.fn(() => ({})), })) vi.mock('../../src/services/notification.service.js', () => ({ sendPushNotification: vi.fn(async () => true), registerDeviceToken: vi.fn(async () => {}), })) const { buildPublic } = await import('../helpers/server.js') const { createCustomer } = await import('../helpers/fixtures.js') const { resetDbHard, db } = await import('../helpers/db.js') const { customerJwt, authHeader } = await import('../helpers/jwt.js') const { getCustomerById, markCustomerUspSeen, } = await import('../../src/services/customer.service.js') describe('Phase 4 — USP one-time gate', () => { let app beforeAll(async () => { app = await buildPublic() }) afterAll(async () => { await app.close() }) beforeEach(async () => { await resetDbHard() }) describe('migration default', () => { it('new customer row has usp_seen = false', async () => { const c = await createCustomer({ callName: 'New User' }) const row = await getCustomerById(c.id) expect(row).toBeTruthy() expect(row.usp_seen).toBe(false) }) }) describe('markCustomerUspSeen() service', () => { it('flips false → true and returns the updated row', async () => { const c = await createCustomer({ callName: 'Marker' }) const updated = await markCustomerUspSeen(c.id) expect(updated.usp_seen).toBe(true) const reread = await getCustomerById(c.id) expect(reread.usp_seen).toBe(true) }) it('is idempotent — second call still returns usp_seen=true, no error', async () => { const c = await createCustomer({ callName: 'Idem' }) await markCustomerUspSeen(c.id) const second = await markCustomerUspSeen(c.id) expect(second.usp_seen).toBe(true) }) }) describe('POST /api/client/auth/usp-seen', () => { it('returns 401 when no Authorization header is present', async () => { const res = await app.inject({ method: 'POST', url: '/api/client/auth/usp-seen', }) expect(res.statusCode).toBe(401) }) it('returns 200 + flips flag for an authed customer', async () => { const c = await createCustomer({ callName: 'Authed' }) const res = await app.inject({ method: 'POST', url: '/api/client/auth/usp-seen', headers: authHeader(customerJwt(c.id)), }) expect(res.statusCode).toBe(200) const body = res.json() expect(body.success).toBe(true) expect(body.data.id).toBe(c.id) expect(body.data.usp_seen).toBe(true) // DB persisted const reread = await getCustomerById(c.id) expect(reread.usp_seen).toBe(true) }) it('rejects a non-customer JWT (mitra) with 403', async () => { // Mint a JWT that says CUSTOMER but the route still asserts type — the // route reads user_type from the JWT claim, so use mitraJwt for negative. const { mitraJwt } = await import('../helpers/jwt.js') const fakeId = '00000000-0000-0000-0000-000000000001' const res = await app.inject({ method: 'POST', url: '/api/client/auth/usp-seen', headers: authHeader(mitraJwt(fakeId)), }) expect(res.statusCode).toBe(403) }) }) describe('GET /api/client/auth/me payload', () => { it('includes usp_seen in the response (false for fresh customer)', async () => { const c = await createCustomer({ callName: 'Reader' }) const res = await app.inject({ method: 'GET', url: '/api/client/auth/me', headers: authHeader(customerJwt(c.id)), }) expect(res.statusCode).toBe(200) const body = res.json() expect(body.data).toHaveProperty('usp_seen') expect(body.data.usp_seen).toBe(false) }) it('reflects usp_seen=true after the flag has been set', async () => { const c = await createCustomer({ callName: 'Reader2' }) await markCustomerUspSeen(c.id) const res = await app.inject({ method: 'GET', url: '/api/client/auth/me', headers: authHeader(customerJwt(c.id)), }) expect(res.statusCode).toBe(200) expect(res.json().data.usp_seen).toBe(true) }) }) })