/** * 验证「把附件地址拼进 question 文本」这条链路(方案②)。 * * 背景(2026-09-18):新接口 /api/chat 只收 {thread_id, question},多字段 422, * 旧协议的 transmission.files 发不出去;改为把 OSS 地址拼进 question。 * * 不变量: * ① 没有附件时**必须原样返回**问题(不能凭空多个换行 —— 那会改变所有普通提问) * ② 有附件时:原文在前、每条 URL 各占一行、**不加**任何自定义措辞 * ③ 空/无效 URL 被过滤(上传失败的文件不该混进问题里) * * 怎么跑(在项目根目录): * npx esbuild harness/tools/_entry-attachments.ts --bundle --format=esm \ * --outfile=harness/tools/_attachments.mjs * node harness/tools/verify-attachment-question.mjs */ import { buildQuestionWithAttachments } from './_attachments.mjs'; let pass = 0; let fail = 0; const check = (name, cond, extra = '') => { if (cond) { pass++; console.log(' ok ' + name); } else { fail++; console.log(' FAIL ' + name + ' ' + extra); } }; const Q = '帮我看看这份材料符合条件吗'; const U1 = 'https://bucket.oss-cn-shanghai.aliyuncs.com/upload/a.png'; const U2 = 'https://bucket.oss-cn-shanghai.aliyuncs.com/upload/b.pdf'; console.log('【1】没有附件时:必须原样返回'); check('undefined → 原文', buildQuestionWithAttachments(Q, undefined) === Q); check('null → 原文', buildQuestionWithAttachments(Q, null) === Q); check('空数组 → 原文', buildQuestionWithAttachments(Q, []) === Q); check('全是空串 → 原文(不要多个换行)', buildQuestionWithAttachments(Q, ['', ' ', null]) === Q, JSON.stringify(buildQuestionWithAttachments(Q, ['', ' ', null]))); console.log('\n【2】有附件时:原文在前、URL 各一行'); check('单个附件', buildQuestionWithAttachments(Q, [U1]) === `${Q}\n${U1}`, JSON.stringify(buildQuestionWithAttachments(Q, [U1]))); check('多个附件按顺序', buildQuestionWithAttachments(Q, [U1, U2]) === `${Q}\n${U1}\n${U2}`); check('**原文完整保留在前面**', buildQuestionWithAttachments(Q, [U1]).startsWith(Q + '\n')); check('**不添加自定义措辞**(只有原文与 URL)', !/附件|文件|attachment/i.test(buildQuestionWithAttachments(Q, [U1]).slice(Q.length))); console.log('\n【3】脏数据过滤'); check('混入空值只保留有效 URL', buildQuestionWithAttachments(Q, [U1, '', null, U2]) === `${Q}\n${U1}\n${U2}`); check('URL 前后空格被去掉', buildQuestionWithAttachments(Q, [` ${U1} `]) === `${Q}\n${U1}`); console.log(`\n===== 通过 ${pass} 项,失败 ${fail} 项 =====`); process.exit(fail ? 1 : 0);