|
|
@@ -65,38 +65,65 @@ const keysOfLast = (endpoint) => {
|
|
|
const DISALLOWED = ['summary', 'qpyszx', 'qyzt', 'rzqpyx'];
|
|
|
|
|
|
console.log('\n【1】会话新增:字段必须「不多不少」');
|
|
|
-await upsertDmsSession({ sessionId: 'sess-A', title: '会话标题甲', creditCode: '91310118TEST' });
|
|
|
+await upsertDmsSession({ sessionId: 'sess-A', sessionTitle: '会话标题甲', 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',
|
|
|
+const EXPECT_SESSION_ADD = [
|
|
|
+ 'c_created_at', 'c_credit_code', 'c_session_id', 'c_source', 'c_updated_at',
|
|
|
+ // 访客标识(2026-09-22 起进 c_ip)与登录标记
|
|
|
+ 'c_ip', 'c_is_login',
|
|
|
+ // DMS 的系统字段:只在创建那一刻带,不带会落成字符串 "null"
|
|
|
+ 'title',
|
|
|
// 1887 的模型把这四个设为 must=true,四个全带才写得进去(少一个就 214);
|
|
|
// 值故意留空 —— 运营口径由业务方定,前端不猜
|
|
|
- 'summary', 'qpyszx', 'qyzt', 'rzqpyx'].sort();
|
|
|
+ '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');
|
|
|
+check('**带系统 title**(不带会落成字符串 "null")', keys?.includes('title') === true);
|
|
|
+check('**不带 c_title**(用户 2026-09-22:标题由运营侧生成)', keys?.includes('c_title') !== true, JSON.stringify(keys));
|
|
|
+
|
|
|
+console.log('\n【2】会话更新:不碰标题、不带运营字段、已有值不重写');
|
|
|
captured.length = 0;
|
|
|
-// 让这次走「更新」分支:先让 search 返回一行
|
|
|
+// 让这次走「更新」分支:search 返回一行,且 c_ip / c_is_login 都已有值
|
|
|
+const existingRow = { id: 'row-uuid', c_session_id: 'sess-A', c_ip: '访客_既有值', c_is_login: '1', c_ent_name: '甲测试公司' };
|
|
|
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: { data: [existingRow], count: 1 } });
|
|
|
}
|
|
|
return json({ code: 200, message: '成功', content: '执行成功' });
|
|
|
};
|
|
|
-await upsertDmsSession({ sessionId: 'sess-A', title: '会话标题乙', creditCode: '91310118TEST' });
|
|
|
+await upsertDmsSession({ sessionId: 'sess-A', sessionTitle: '会话标题乙', creditCode: '91310118TEST' });
|
|
|
keys = keysOfLast('/content/updateContent');
|
|
|
check('更新走的是 updateContent', keys !== null);
|
|
|
-check('**更新也带 title**(原先不带,改名后 DMS 里标题不变)', keys?.includes('title') === true, JSON.stringify(keys));
|
|
|
+check('**更新不带 title / c_title**(前端不碰标题)', !keys?.includes('title') && !keys?.includes('c_title'), JSON.stringify(keys));
|
|
|
check('**更新不带这四个字段**(否则会把后端回填的值冲成空)', !DISALLOWED.some((k) => keys?.includes(k)), JSON.stringify(keys));
|
|
|
+check('**c_ip 已有值 → 不重写**(可能是后端回填的真实 IP)', !keys?.includes('c_ip'), JSON.stringify(keys));
|
|
|
+check('**c_is_login 已有值 → 不重写**', !keys?.includes('c_is_login'), JSON.stringify(keys));
|
|
|
+
|
|
|
+console.log('\n【2b】值不同就更新 / 值相同不写');
|
|
|
+{
|
|
|
+ await upsertDmsSession({
|
|
|
+ sessionId: 'sess-A',
|
|
|
+ sessionTitle: '会话标题乙',
|
|
|
+ creditCode: '91310118TEST',
|
|
|
+ companyInfo: { profile: { data: { Name: '乙测试公司', CreditCode: '91310118CHAT' } } },
|
|
|
+ });
|
|
|
+ keys = keysOfLast('/content/updateContent');
|
|
|
+ check('**值不同 → 企业名称写进 patch**', keys?.includes('c_ent_name'), JSON.stringify(keys));
|
|
|
+ check('**值不同 → 真实信用代码写进 patch**', keys?.includes('c_credit_code'), JSON.stringify(keys));
|
|
|
|
|
|
+ existingRow.c_ent_name = '乙测试公司';
|
|
|
+ existingRow.c_credit_code = '91310118CHAT';
|
|
|
+ await upsertDmsSession({
|
|
|
+ sessionId: 'sess-A',
|
|
|
+ sessionTitle: '会话标题乙',
|
|
|
+ companyInfo: { profile: { data: { Name: '乙测试公司', CreditCode: '91310118CHAT' } } },
|
|
|
+ });
|
|
|
+ keys = keysOfLast('/content/updateContent');
|
|
|
+ check('**值相同 → 不写 c_ent_name**(不产生无谓写入)', !keys?.includes('c_ent_name'), JSON.stringify(keys));
|
|
|
+ check('**值相同 → 不写 c_credit_code**', !keys?.includes('c_credit_code'), JSON.stringify(keys));
|
|
|
+}
|
|
|
console.log('\n【3】问答记录新增:title = 问题文本,且不多发字段');
|
|
|
captured.length = 0;
|
|
|
globalThis.fetch = async (url, init = {}) => {
|
|
|
@@ -116,23 +143,29 @@ const hit = [...captured].reverse().find((c) => c.url.includes('/content/addCont
|
|
|
const content = JSON.parse(new URLSearchParams(hit.body).get('content'));
|
|
|
check('**title 的值就是问题文本**', content.title === '这是问题文本', JSON.stringify(content.title));
|
|
|
|
|
|
-console.log('\n【4】会话写入(1887)必须保持停用 —— 源码级断言');
|
|
|
+console.log('\n【4】会话写入(1887)只在回答完成处发生一次 —— 源码级断言');
|
|
|
{
|
|
|
- // 用户 2026-09-18 指示:只写问答记录(1889),会话行(1887)不写了。
|
|
|
- // 这条靠「调用点被注释掉」实现,极易在后续改动里被顺手恢复,所以在源码上钉死。
|
|
|
+ // 2026-09-22:1887 改为写企业字段,触发点**只有一处** —— 回答完成的 totalResponse
|
|
|
+ // (那时才知道本轮有没有 company_info)。
|
|
|
+ // 曾经在 sendMessage / submitQuestionAnswers 里也各写一次,那会让同一次会话写两遍,
|
|
|
+ // 极易在后续改动里被顺手恢复,所以在源码上钉死。
|
|
|
const file = 'src/components/business-assistant/useBusinessAssistantChat.ts';
|
|
|
- const lines = readFileSync(file, 'utf8').split('\n');
|
|
|
+ const src = readFileSync(file, 'utf8');
|
|
|
+ const lines = src.split('\n');
|
|
|
const live = lines
|
|
|
.map((l, i) => ({ l, n: i + 1 }))
|
|
|
- .filter(({ l }) => !l.trim().startsWith('//') && /(upsertDmsSession|syncSessionTitleToServer)\s*\(/.test(l));
|
|
|
+ .filter(({ l }) => !l.trim().startsWith('//') && /upsertDmsSession\s*\(/.test(l));
|
|
|
+ check('活跃调用点只有 1 处', live.length === 1, live.map(({ l, n }) => `第${n}行: ${l.trim()}`).join(' | '));
|
|
|
+
|
|
|
+ // 它必须在 totalResponse 监听器之后、下一个 addEventListener 之前
|
|
|
+ const at = live.length ? live[0].n - 1 : -1;
|
|
|
+ const totalResponseAt = lines.findIndex((l) => l.includes("addEventListener('totalResponse'"));
|
|
|
+ const nextListenerAt = lines.findIndex((l, i) => i > totalResponseAt && l.includes("addEventListener('"));
|
|
|
check(
|
|
|
- '不再调用 upsertDmsSession / syncSessionTitleToServer(1887 已停用)',
|
|
|
- live.length === 0,
|
|
|
- live.map(({ l, n }) => `第${n}行: ${l.trim()}`).join(' | ')
|
|
|
+ '那一处在 totalResponse 监听器内(回答完成时才写)',
|
|
|
+ at > totalResponseAt && (nextListenerAt === -1 || at < nextListenerAt),
|
|
|
+ `调用@第${at + 1}行 / totalResponse@第${totalResponseAt + 1}行 / 下一个监听器@第${nextListenerAt + 1}行`
|
|
|
);
|
|
|
- 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);
|