const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const test = require('node:test'); const FeedbackPages = require('../public/js/feedback-pages.js'); const feedbackId = '2062179707935264769'; function read(relativePath) { return fs.readFileSync(path.resolve(__dirname, '..', relativePath), 'utf8'); } function feedbackFixture(overrides) { return Object.assign({ feedbackId, appUserId: '2062179707935264701', appUserNickName: '叶子', appUserPhone: '19100000000', feedbackType: 'bug', feedbackContent: '页面按钮无响应', contactInfo: '19181970173', handleStatus: '2', handleResult: '已经修复', handlerId: '2062179707935264702', handleTime: '2026-07-29 13:00:00', status: '0', remark: '感谢反馈' }, overrides); } test('反馈请求只构造 AppFeedbackBody 并裁剪文本', () => { assert.deepEqual(FeedbackPages.buildFeedbackBody({ feedbackType: ' bug ', feedbackContent: ' 页面按钮无响应 ', contactInfo: ' 19181970173 ', feedbackTitle: 'must-drop', appUserId: 'must-drop', handlerId: 'must-drop' }), { feedbackType: 'bug', feedbackContent: '页面按钮无响应', contactInfo: '19181970173' }); assert.deepEqual(FeedbackPages.buildFeedbackBody({ feedbackType: '', feedbackContent: ' 建议增加导出 ', contactInfo: ' ' }), { feedbackContent: '建议增加导出' }); }); test('反馈内容必填且类型只允许后端确认的四种值', () => { assert.equal(FeedbackPages.validateFeedbackBody({ feedbackContent: '建议增加导出' }), ''); assert.equal(FeedbackPages.validateFeedbackBody({ feedbackType: 'advice', feedbackContent: '建议增加导出' }), ''); assert.match(FeedbackPages.validateFeedbackBody({ feedbackContent: '' }), /反馈内容/); assert.match(FeedbackPages.validateFeedbackBody({ feedbackType: 'suggestion', feedbackContent: '建议增加导出' }), /反馈类型/); }); test('FeedbackVo 只保留用户可见字段并保持长 ID 字符串', () => { assert.deepEqual(FeedbackPages.normalizeFeedback(feedbackFixture()), { feedbackId, feedbackType: 'bug', feedbackContent: '页面按钮无响应', contactInfo: '19181970173', handleStatus: '2', handleResult: '已经修复', handleTime: '2026-07-29 13:00:00', status: '0', remark: '感谢反馈' }); assert.equal(FeedbackPages.normalizeFeedback(feedbackFixture({ feedbackId: Number.MAX_SAFE_INTEGER + 1 })), null); assert.equal(FeedbackPages.normalizeFeedback(feedbackFixture({ feedbackContent: '' })), null); assert.equal(FeedbackPages.normalizeFeedback(feedbackFixture({ handleStatus: '9' })), null); assert.equal(FeedbackPages.normalizeFeedback(feedbackFixture({ status: '9' })), null); }); test('反馈数组包含非法记录时整批拒绝', () => { assert.equal(FeedbackPages.normalizeFeedbackList([feedbackFixture()]).length, 1); assert.deepEqual(FeedbackPages.normalizeFeedbackList([ feedbackFixture(), feedbackFixture({ feedbackId: '' }) ]), []); assert.deepEqual(FeedbackPages.normalizeFeedbackList({ rows: [feedbackFixture()] }), []); }); test('详情只精确匹配 URL 中的反馈 ID 且不存在时不回退', () => { const secondId = '2062179707935264770'; const list = [feedbackFixture(), feedbackFixture({ feedbackId: secondId, feedbackContent: '第二条' })]; assert.equal(FeedbackPages.getFeedbackId('?feedbackId=' + secondId), secondId); assert.equal(FeedbackPages.getFeedbackId('?feedbackId=unsafe'), ''); assert.equal(FeedbackPages.findFeedbackById(list, secondId).feedbackContent, '第二条'); assert.equal(FeedbackPages.findFeedbackById(list, '2062179707935264999'), null); assert.equal( FeedbackPages.buildFeedbackDetailUrl(secondId), 'ticket-detail.html?feedbackId=2062179707935264770' ); }); test('反馈列表和详情转义文本且不暴露内部账号信息', () => { const item = feedbackFixture({ feedbackContent: '', handleResult: '', appUserId: 'secret-user', handlerId: 'secret-handler', appUserPhone: 'secret-account-phone' }); const listHtml = FeedbackPages.renderFeedbackList([item], { detailPage: 'ticket-detail.html' }); const detailHtml = FeedbackPages.renderFeedbackDetail(item); assert.match(listHtml, /<script>/); assert.match(detailHtml, /<img src=x>/); assert.doesNotMatch(listHtml + detailHtml, / { ['profile-feedback.html', 'submit-ticket.html'].forEach((page) => { const source = read(page); assert.doesNotMatch(source, /data-feature-status="pending"|pending-pages\.js/); assert.match(source, /data-feedback-form/); assert.match(source, /name="feedbackType"/); assert.match(source, /name="feedbackContent"/); assert.match(source, /name="contactInfo"/); assert.match(source, /public\/js\/feedback-pages\.js/); assert.doesNotMatch(source, /name="(?:feedbackTitle|feedbackId|appUserId|handlerId)"/); }); }); test('我的工单和详情页读取同一反馈集合且不制造处理动作', () => { const listPage = read('my-tickets.html'); const detailPage = read('ticket-detail.html'); [listPage, detailPage].forEach((source) => { assert.doesNotMatch(source, /data-feature-status="pending"|pending-pages\.js/); assert.match(source, /public\/js\/feedback-pages\.js/); }); assert.match(listPage, /data-feedback-list/); assert.match(listPage, /data-feedback-refresh/); assert.match(detailPage, /data-feedback-detail/); assert.doesNotMatch(detailPage, /回复|追问|删除|关闭工单|name="feedbackId"/); }); test('反馈运行时提交后重读同一 ID,详情不存在时不回退', () => { const source = read('public/js/feedback-pages.js'); assert.match(source, /await api\.submitFeedback\(/); assert.match(source, /await api\.myFeedback\(\)/); assert.match(source, /提交后无法读取同一反馈记录/); assert.match(source, /反馈记录不存在或无权查看/); });