import assert from 'node:assert/strict'; import test from 'node:test'; import { createFeedbackService } from './user-feedback.mjs'; function createMockPool() { const inserts = []; return { inserts, async query(sql, params) { if (/INSERT INTO h5_user_feedback/.test(sql)) { inserts.push(params); return [{ affectedRows: 1 }]; } throw new Error(`Unexpected query: ${sql}`); }, }; } test('submit feedback stores normalized payload', async () => { const pool = createMockPool(); const service = createFeedbackService(pool); const tinyPng = Buffer.from( 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==', 'base64', ); const result = await service.submit('user-1', { type: 'bug', title: '页面无法保存', description: '点击保存后没有反应', contact: 'test@example.com', images: [ { filename: 'screen.png', mimeType: 'image/png', dataBase64: tinyPng.toString('base64'), }, ], context: { pagePath: '/space/page/abc', userAgent: 'Mozilla/5.0', }, }); assert.ok(result.id); assert.equal(result.type, 'bug'); assert.equal(result.status, 'pending'); assert.equal(pool.inserts.length, 1); assert.equal(pool.inserts[0][1], 'user-1'); assert.equal(pool.inserts[0][2], 'bug'); }); test('submit feedback rejects invalid type', async () => { const service = createFeedbackService(createMockPool()); await assert.rejects( () => service.submit('user-1', { type: 'invalid', title: 'x', description: 'y', }), /反馈类型/, ); }); test('submit feedback rejects empty description', async () => { const service = createFeedbackService(createMockPool()); await assert.rejects( () => service.submit('user-1', { type: 'feature', title: '想要导出 PDF', description: ' ', }), /详细描述/, ); }); test('listAll returns paginated board items without contact or images', async () => { let queryCount = 0; const pool = { async query(sql, params) { if (/SELECT COUNT\(\*\)/.test(sql)) { return [[{ total: 23 }]]; } if (/FROM h5_user_feedback f/.test(sql) && /LIMIT \? OFFSET \?/.test(sql)) { queryCount += 1; assert.equal(params[0], 10); assert.equal(params[1], 10); return [ [ { id: 'fb-1', user_id: 'user-2', type: 'bug', title: '按钮无响应', description: '点击保存没有反应', status: 'pending', images_json: JSON.stringify([{ mimeType: 'image/png', dataBase64: 'abc' }]), context_json: JSON.stringify({ pagePath: '/space' }), created_at: 1000, updated_at: 1000, submitter_display_name: 'Alice', }, ], ]; } throw new Error(`Unexpected query: ${sql}`); }, }; const service = createFeedbackService(pool); const result = await service.listAll({ page: 2, limit: 10 }); assert.equal(queryCount, 1); assert.equal(result.page, 2); assert.equal(result.pageSize, 10); assert.equal(result.total, 23); assert.equal(result.totalPages, 3); assert.equal(result.items.length, 1); assert.equal(result.items[0].submitterDisplayName, 'Alice'); assert.equal(result.items[0].imageCount, 1); assert.equal(result.items[0].images, undefined); assert.equal(result.items[0].contact, undefined); }); test('getById hides contact from non-owner', async () => { const pool = { async query(sql, params) { if (/WHERE f.id = \?/.test(sql)) { assert.equal(params[0], 'fb-1'); return [ [ { id: 'fb-1', user_id: 'user-2', type: 'bug', title: '按钮无响应', description: '详细内容', contact: 'secret@example.com', status: 'pending', images_json: '[]', context_json: '{}', created_at: 1000, updated_at: 1000, submitter_display_name: 'Alice', }, ], ]; } throw new Error(`Unexpected query: ${sql}`); }, }; const service = createFeedbackService(pool); const item = await service.getById('fb-1', 'viewer-1'); assert.equal(item.title, '按钮无响应'); assert.equal(item.contact, undefined); const ownItem = await service.getById('fb-1', 'user-2'); assert.equal(ownItem.contact, 'secret@example.com'); });