import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import request from 'supertest'; import { setupApp, resetDb, teardown } from './setup.js'; import { makePayload } from './fixtures.js'; import { getPrisma } from '../src/lib/prisma.js'; let app: any; beforeAll(async () => { ({ app } = await setupApp()); }, 90000); afterAll(async () => { await teardown(); }); async function login() { const r = await request(app) .post('/api/auth/login') .send({ username: 'admin', password: 'admin-password-1234' }); return r.body.token; } async function pushInquiry(payload: any) { await request(app) .post('/api/ingest') .set('Authorization', 'Bearer dev-ingest-token-1234') .send(payload); } describe('inquiries list', () => { it('lists filtered by country', async () => { await resetDb(); await pushInquiry(makePayload({ customer: { country: 'PE' } })); await pushInquiry( makePayload({ inquiry: { inquiryId: 'X2' }, customer: { customerId: 'c2', country: 'US' } }), ); const token = await login(); const res = await request(app) .get('/api/inquiries?country=PE') .set('Authorization', 'Bearer ' + token); expect(res.status).toBe(200); expect(res.body.items.length).toBe(1); expect(res.body.items[0].customer.country).toBe('PE'); expect(res.body).toHaveProperty('total'); expect(res.body).toHaveProperty('page'); expect(res.body).toHaveProperty('size'); }); it('401 without jwt', async () => { const res = await request(app).get('/api/inquiries'); expect(res.status).toBe(401); }); }); describe('inquiries detail', () => { it('gets inquiry detail with customer + message count', async () => { await resetDb(); await pushInquiry(makePayload()); // inquiryId='13776617920', 2 messages const token = await login(); const res = await request(app) .get('/api/inquiries/13776617920') .set('Authorization', 'Bearer ' + token); expect(res.status).toBe(200); expect(res.body.inquiryId).toBe('13776617920'); expect(res.body.customer).toBeTruthy(); expect(res.body._count.messages).toBe(2); }); it('404 for unknown inquiry', async () => { const token = await login(); const res = await request(app) .get('/api/inquiries/NOPE') .set('Authorization', 'Bearer ' + token); expect(res.status).toBe(404); }); }); describe('inquiry messages pagination', () => { // 造 50 条消息(sendTime 各不同),用于测默认从最新 + before 向上翻页 async function seed50() { await resetDb(); const prisma = getPrisma(); await pushInquiry(makePayload()); // 建 inquiry + 2 条消息 const base = Date.parse('2026-07-13T02:12:00Z'); await prisma.chatMessage.createMany({ data: Array.from({ length: 48 }, (_, i) => ({ messageId: 'p' + i, inquiryId: '13776617920', sender: 'x', direction: 'incoming' as const, sendTime: new Date(base + (i + 1) * 1000), content: 'c' + i, translation: '', })), }); } it('默认无 cursor 返回最新一页(asc 旧→新)+ prevCursor 指向更旧', async () => { await seed50(); const token = await login(); const r1 = await request(app) .get('/api/inquiries/13776617920/messages?size=20') .set('Authorization', 'Bearer ' + token); expect(r1.status).toBe(200); expect(r1.body.items.length).toBe(20); expect(r1.body.prevCursor).toBeTruthy(); // 默认从最新:返回的 20 条应是全局最新的 20 条,且按 sendTime 升序(顶旧底新) const ts = r1.body.items.map((m: any) => new Date(m.sendTime).getTime()); for (let i = 1; i < ts.length; i++) expect(ts[i]).toBeGreaterThanOrEqual(ts[i - 1]); // 最新一条(全局最大 sendTime)应在 items 末尾 const prisma = getPrisma(); const maxRow = await prisma.chatMessage.findFirst({ where: { inquiryId: '13776617920' }, orderBy: { sendTime: 'desc' }, }); expect(ts[ts.length - 1]).toBe(maxRow!.sendTime.getTime()); }); it('before cursor 向上加载更旧,与已加载不重叠', async () => { await seed50(); const token = await login(); const r1 = await request(app) .get('/api/inquiries/13776617920/messages?size=20') .set('Authorization', 'Bearer ' + token); const c = r1.body.prevCursor; expect(c).toBeTruthy(); const r2 = await request(app) .get( `/api/inquiries/13776617920/messages?size=20&beforeSendTime=${encodeURIComponent(c.beforeSendTime)}&beforeMessageId=${c.beforeMessageId}`, ) .set('Authorization', 'Bearer ' + token); expect(r2.status).toBe(200); expect(r2.body.items.length).toBe(20); // 不重叠 const ids1 = new Set(r1.body.items.map((m: any) => m.messageId)); expect(r2.body.items.every((m: any) => !ids1.has(m.messageId))).toBe(true); // r2 全部比 r1 第一条更旧 const r1First = new Date(r1.body.items[0].sendTime).getTime(); expect(r2.body.items.every((m: any) => new Date(m.sendTime).getTime() <= r1First)).toBe(true); }); it('400 when only one cursor field provided (after or before)', async () => { const token = await login(); const r1 = await request(app) .get('/api/inquiries/13776617920/messages?afterSendTime=2026-07-13T02:12:00Z') .set('Authorization', 'Bearer ' + token); expect(r1.status).toBe(400); const r2 = await request(app) .get('/api/inquiries/13776617920/messages?beforeSendTime=2026-07-13T02:12:00Z') .set('Authorization', 'Bearer ' + token); expect(r2.status).toBe(400); }); });