/** * QiWei Express 接口测试(supertest,不启动真实端口) * * 运行:cd backend && pnpm test */ import { describe, it, before, after, beforeEach } from 'node:test'; import assert from 'node:assert'; import fs from 'node:fs'; import path from 'node:path'; import os from 'node:os'; import { fileURLToPath } from 'node:url'; import request from 'supertest'; import { createQiweiApp } from '../src/apps/qiwei/app.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const fixturePath = path.join(__dirname, 'fixtures', 'qiwei_callbacks_sample.json'); const samplePayload = JSON.parse(fs.readFileSync(fixturePath, 'utf-8')) as Record; function tempStorePath(): string { return path.join(os.tmpdir(), `qiwei-api-test-${process.pid}-${Date.now()}.json`); } describe('QiWei API /api/qiwei', () => { let storeFile: string; const app = createQiweiApp(); before(() => { storeFile = tempStorePath(); process.env.QIWEI_ROOM_DOC_STORE = storeFile; }); after(() => { if (fs.existsSync(storeFile)) { fs.unlinkSync(storeFile); } delete process.env.QIWEI_ROOM_DOC_STORE; delete process.env.QIWEI_GUID; delete process.env.QIWEI_TOKEN; }); beforeEach(() => { if (fs.existsSync(storeFile)) { fs.unlinkSync(storeFile); } }); it('POST /webhook — 解析 docid 并写入台账', async () => { const res = await request(app) .post('/api/qiwei/webhook') .send(samplePayload) .expect(200); assert.strictEqual(res.body.ok, true); assert.ok(Array.isArray(res.body.docRefs)); assert.ok(res.body.docRefs.length >= 2); const docids = res.body.docRefs.map((r: { docid: string | null }) => r.docid); assert.ok(docids.includes('DOC_TEST_abc123xyz')); assert.ok(docids.includes('DOC_OTHER_room2')); assert.ok(Array.isArray(res.body.saved)); assert.strictEqual(res.body.saved.length, 2); }); it('GET /room-docs — 查询群↔文档台账', async () => { await request(app).post('/api/qiwei/webhook').send(samplePayload).expect(200); const res = await request(app).get('/api/qiwei/room-docs').expect(200); assert.strictEqual(res.body.ok, true); assert.ok(res.body.items.length >= 2); const roomIds = res.body.items.map((i: { roomId: string | number }) => String(i.roomId)); assert.ok(roomIds.includes('10791082136095292')); assert.ok(roomIds.includes('99999999999999')); }); it('GET /room-docs/:roomId — 单个群文档', async () => { await request(app).post('/api/qiwei/webhook').send(samplePayload).expect(200); const res = await request(app) .get('/api/qiwei/room-docs/10791082136095292') .expect(200); assert.strictEqual(res.body.item.docid, 'DOC_TEST_abc123xyz'); assert.ok(res.body.item.linkUrl.includes('doc.weixin.qq.com')); }); it('GET /room-docs/:roomId — 未登记返回 404', async () => { await request(app).get('/api/qiwei/room-docs/00000000000000').expect(404); }); it('POST /screen — 无法解析 docid 时命中异常模板', async () => { const payload = { data: [ { cmd: 15000, msgType: 13, fromRoomId: 111, msgData: { linkUrl: 'https://doc.weixin.qq.com/some/page-without-docid', title: '无 docid 的链接', }, }, ], }; const res = await request(app).post('/api/qiwei/screen').send(payload).expect(200); assert.strictEqual(res.body.ok, true); const codes = res.body.exceptions.map((e: { code: string }) => e.code); assert.ok(codes.includes('DOCID_PARSE_FAILED')); }); it('POST /sync — 缺少 guid 返回 400', async () => { delete process.env.QIWEI_GUID; const res = await request(app).post('/api/qiwei/sync').send({}).expect(400); assert.strictEqual(res.body.ok, false); assert.ok(String(res.body.error).includes('guid')); }); it('POST /sync — 无 QIWEI_TOKEN 返回 500', async () => { delete process.env.QIWEI_TOKEN; process.env.QIWEI_GUID = 'test-guid'; const res = await request(app) .post('/api/qiwei/sync') .send({ guid: 'test-guid', msgSeq: 0 }) .expect(500); assert.strictEqual(res.body.ok, false); }); });