| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138 |
- /**
- * 验证写 DMS 时**到底发了哪些字段**(不需要 token,也不碰真实 DMS)。
- *
- * 为什么要单独测这个:写入字段的规矩是**踩出来的**——
- * 1887 的模型把 summary/qpyszx/qyzt/rzqpyx 设为 must=true(不带就 214),
- * 但它们**只能传空值**(业务口径由业务方定,前端不猜);而 1889 不需要这些字段。
- * 多发/少发字段没有直观的行为反馈、极易被"顺手"改掉,所以直接把请求体抓下来断言。
- *
- * 做法:把 `globalThis.fetch` 打桩,捕获模块真实发出的 form body,
- * 解析出 `content` 的键集合,断言「不多不少」。
- *
- * 怎么跑(在项目根目录):
- * npx esbuild harness/tools/_entry-dms.ts --bundle --format=esm \
- * --outfile=harness/tools/_dms.mjs --alias:@=./src --define:import.meta.env='{}'
- * node harness/tools/verify-dms-payload-fields.mjs
- */
- globalThis.VITE_DMS_API = 'http://dms.invalid/dms'; // 只为让 base 非空;请求会被打桩拦住
- // ── 打桩 fetch:捕获请求,返回可控响应 ────────────────────────────────
- const captured = [];
- const json = (obj) => new Response(JSON.stringify(obj), { status: 200, headers: { 'Content-Type': 'application/json' } });
- globalThis.fetch = async (url, init = {}) => {
- const body = init.body ? String(init.body) : '';
- captured.push({ url: String(url), body });
- if (String(url).includes('/content/selectContentList')) {
- return json({ code: 202, message: '数据不存在', content: '数据不存在' }); // 空 → 走新增分支
- }
- if (String(url).includes('/content/addContent')) {
- return json({ code: 200, message: '成功', content: 'stub-uuid-0001' });
- }
- if (String(url).includes('/content/updateContent')) {
- return json({ code: 200, message: '成功', content: '执行成功' });
- }
- return json({ code: 200, message: '成功', content: '' });
- };
- import { readFileSync } from 'node:fs';
- const { upsertDmsSession, upsertDmsRecord } = await import('./_dms.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);
- }
- };
- /** 取出最近一次某端点的请求体里 content 的字段名(排序后) */
- const keysOfLast = (endpoint) => {
- const hit = [...captured].reverse().find((c) => c.url.includes(endpoint));
- if (!hit) return null;
- const params = new URLSearchParams(hit.body);
- const content = JSON.parse(params.get('content') || '{}');
- return Object.keys(content).sort();
- };
- const DISALLOWED = ['summary', 'qpyszx', 'qyzt', 'rzqpyx'];
- console.log('\n【1】会话新增:字段必须「不多不少」');
- await upsertDmsSession({ sessionId: 'sess-A', title: '会话标题甲', creditCode: '91310118TEST' });
- let keys = keysOfLast('/content/addContent');
- const EXPECT_SESSION_ADD = ['c_created_at', 'c_credit_code', 'c_session_id', 'c_source', 'c_title', 'c_updated_at', 'title',
- // 1887 的模型把这四个设为 must=true,四个全带才写得进去(少一个就 214);
- // 值故意留空 —— 运营口径由业务方定,前端不猜
- 'summary', 'qpyszx', 'qyzt', 'rzqpyx'].sort();
- check('字段集合与预期完全一致', JSON.stringify(keys) === JSON.stringify(EXPECT_SESSION_ADD), `实际 ${JSON.stringify(keys)}`);
- check('**带 title**(用户需求)', keys?.includes('title') === true);
- check('带上四个必填运营字段(值必须为空/默认,不是业务值)', ['summary','qpyszx','qyzt','rzqpyx'].every((k) => keys?.includes(k)), JSON.stringify(keys));
- check('这四个字段的值是空值/默认(不是猜的业务值)', (() => {
- const hit = [...captured].reverse().find((c) => c.url.includes('/content/addContent'));
- const c = JSON.parse(new URLSearchParams(hit.body).get('content'));
- return c.summary === '' && c.qpyszx === false && c.qyzt === false && c.rzqpyx === false;
- })(), '');
- console.log('\n【2】会话改名(更新):也要带 title');
- captured.length = 0;
- // 让这次走「更新」分支:先让 search 返回一行
- globalThis.fetch = async (url, init = {}) => {
- const body = init.body ? String(init.body) : '';
- captured.push({ url: String(url), body });
- if (String(url).includes('/content/selectContentList')) {
- return json({ code: 200, message: '成功', content: { data: [{ id: 'row-uuid', c_session_id: 'sess-A' }], count: 1 } });
- }
- return json({ code: 200, message: '成功', content: '执行成功' });
- };
- await upsertDmsSession({ sessionId: 'sess-A', title: '会话标题乙', creditCode: '91310118TEST' });
- keys = keysOfLast('/content/updateContent');
- check('更新走的是 updateContent', keys !== null);
- check('**更新也带 title**(原先不带,改名后 DMS 里标题不变)', keys?.includes('title') === true, JSON.stringify(keys));
- check('**更新不带这四个字段**(否则会把后端回填的值冲成空)', !DISALLOWED.some((k) => keys?.includes(k)), JSON.stringify(keys));
- console.log('\n【3】问答记录新增:title = 问题文本,且不多发字段');
- captured.length = 0;
- globalThis.fetch = async (url, init = {}) => {
- const body = init.body ? String(init.body) : '';
- captured.push({ url: String(url), body });
- if (String(url).includes('/content/selectContentList')) return json({ code: 202, message: '数据不存在', content: '数据不存在' });
- if (String(url).includes('/content/addContent')) return json({ code: 200, message: '成功', content: 'stub-uuid-0002' });
- return json({ code: 200, message: '成功', content: '' });
- };
- await upsertDmsRecord({ recordId: 'rec-A', sessionId: 'sess-A', creditCode: '91310118TEST', question: '这是问题文本' });
- keys = keysOfLast('/content/addContent');
- const EXPECT_RECORD_ADD = ['c_created_at', 'c_credit_code', 'c_question', 'c_record_id', 'c_session_id', 'c_source', 'title'].sort();
- check('字段集合与预期完全一致', JSON.stringify(keys) === JSON.stringify(EXPECT_RECORD_ADD), `实际 ${JSON.stringify(keys)}`);
- check('不含 summary 等多余字段', !DISALLOWED.some((k) => keys?.includes(k)), JSON.stringify(keys));
- const hit = [...captured].reverse().find((c) => c.url.includes('/content/addContent'));
- const content = JSON.parse(new URLSearchParams(hit.body).get('content'));
- check('**title 的值就是问题文本**', content.title === '这是问题文本', JSON.stringify(content.title));
- console.log('\n【4】会话写入(1887)必须保持停用 —— 源码级断言');
- {
- // 用户 2026-09-18 指示:只写问答记录(1889),会话行(1887)不写了。
- // 这条靠「调用点被注释掉」实现,极易在后续改动里被顺手恢复,所以在源码上钉死。
- const file = 'src/components/business-assistant/useBusinessAssistantChat.ts';
- const lines = readFileSync(file, 'utf8').split('\n');
- const live = lines
- .map((l, i) => ({ l, n: i + 1 }))
- .filter(({ l }) => !l.trim().startsWith('//') && /(upsertDmsSession|syncSessionTitleToServer)\s*\(/.test(l));
- check(
- '不再调用 upsertDmsSession / syncSessionTitleToServer(1887 已停用)',
- live.length === 0,
- live.map(({ l, n }) => `第${n}行: ${l.trim()}`).join(' | ')
- );
- const commented = lines.filter((l) => l.trim().startsWith('//') && /upsertDmsSession\s*\(/.test(l));
- check('停用的调用点以注释形式保留(符合项目「注释不删除」的约定)', commented.length >= 2, `找到 ${commented.length} 处`);
- }
- console.log(`\n===== 通过 ${pass} 项,失败 ${fail} 项 =====`);
- process.exit(fail ? 1 : 0);
|