dms-step0-verify.mjs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. /**
  2. * Step-0:切换前端到 DMS 之前的**小样本实测**。
  3. *
  4. * 为什么需要它:DMS 的 content 读写接口在文档里全是「📄 未实测」
  5. * (参数来自 SKILL.md/Apifox,OpenAPI 里是 @RequestBody 抓不到),
  6. * 而前端的 upsert 依赖三件没被证实的事:
  7. * ① addContent 是否要求 c_id(模型里 must=true)→ 若强校验,整个方案要改
  8. * ② addContent 返回体里有没有记录 uuid(有则省掉一次反查)
  9. * ③ timestamp 写入格式、states 默认值、长文本往返
  10. *
  11. * 怎么跑(在项目根目录;token 不落盘,从参数读):
  12. * node harness/tools/dms-step0-verify.mjs <DMS_TOKEN>
  13. *
  14. * 脚本会自行清理它写入的测试数据(session_id/record_id 以 step0_ 开头)。
  15. */
  16. const HOST = process.env.DMS_HOST || '121.43.55.7:10081';
  17. const BASE = `http://${HOST}/dms`;
  18. const TOKEN = process.argv[2] || process.env.DMS_TOKEN || '';
  19. const COL_SESSION = 1887;
  20. const MODEL_SESSION = 2034;
  21. const COL_RECORD = 1889;
  22. const MODEL_RECORD = 2038;
  23. const SUFFIX = Date.now().toString(36);
  24. const SESSION_ID = `step0_sess_${SUFFIX}`;
  25. const RECORD_ID = `step0_rec_${SUFFIX}`;
  26. const CREDIT = 'step0_credit_code';
  27. if (!TOKEN) {
  28. console.error('缺 token。用法:node harness/tools/dms-step0-verify.mjs <DMS_TOKEN>');
  29. process.exit(2);
  30. }
  31. const results = [];
  32. const note = (name, ok, detail) => {
  33. results.push({ name, ok, detail });
  34. console.log(` ${ok === null ? '? ' : ok ? 'ok ' : 'FAIL'} ${name}${detail ? ' → ' + detail : ''}`);
  35. };
  36. async function call(method, path, form) {
  37. const init = { method, headers: { token: TOKEN } };
  38. if (form) {
  39. init.headers['Content-Type'] = 'application/x-www-form-urlencoded';
  40. init.body = new URLSearchParams(form).toString();
  41. }
  42. const res = await fetch(`${BASE}${path}`, init);
  43. const text = await res.text();
  44. let json = null;
  45. try {
  46. json = JSON.parse(text);
  47. } catch {
  48. /* 非 JSON 原样返回 */
  49. }
  50. return { status: res.status, json, text };
  51. }
  52. const search = (columnId, field, value, extra = {}) =>
  53. call('POST', '/content/selectContentList', {
  54. columnId: String(columnId),
  55. page: '0',
  56. pageSize: '10',
  57. search: JSON.stringify([{ field, searchType: 1, content: { value } }]),
  58. ...extra,
  59. });
  60. const cleanup = async (columnId, field, value) => {
  61. const r = await search(columnId, field, value);
  62. const rows = r.json?.content?.data || [];
  63. for (const row of rows) {
  64. if (row?.id) await call('DELETE', `/content/delContentById?id=${encodeURIComponent(row.id)}`);
  65. }
  66. return rows.length;
  67. };
  68. const run = async () => {
  69. console.log(`\nDMS Step-0 实测 ${BASE} 标记=${SUFFIX}\n`);
  70. // ── 1. addContent:不传 c_id 会不会被拒(最高优先级)────────────────────
  71. console.log('【1】addContent(1887 会话)不传 c_id');
  72. const addSession = await call('POST', '/content/addContent', {
  73. columnId: String(COL_SESSION),
  74. modelId: String(MODEL_SESSION),
  75. content: JSON.stringify({
  76. c_credit_code: CREDIT,
  77. c_session_id: SESSION_ID,
  78. c_title: 'step0 会话标题',
  79. c_source: 'zhaoshang',
  80. }),
  81. });
  82. console.log(' 原始响应:', addSession.text.slice(0, 400));
  83. const addOk = addSession.json?.code === 200;
  84. note('addContent 不传 c_id 被接受', addOk, addOk ? '' : `code=${addSession.json?.code}`);
  85. const returnedUuid = addSession.json?.content?.id || addSession.json?.content?.uuid || null;
  86. note('addContent 响应回传记录 uuid', !!returnedUuid, returnedUuid ? String(returnedUuid) : '(未回传,需反查)');
  87. // ── 2. 反查:states 默认值 / c_id 是否自动生成 ──────────────────────────
  88. console.log('\n【2】反查(不带 states)');
  89. const found = await search(COL_SESSION, 'c_session_id', SESSION_ID);
  90. const rows = found.json?.content?.data || [];
  91. note('写后能查回(不传 states)', rows.length > 0, `命中 ${rows.length} 行, code=${found.json?.code}`);
  92. const row = rows[0] || {};
  93. console.log(' 行内容:', JSON.stringify(row).slice(0, 500));
  94. note('c_id 由 DMS 自动生成', row.c_id !== undefined && row.c_id !== null && row.c_id !== '', `c_id=${row.c_id}`);
  95. note('记录 uuid 存在行里(反查可取)', !!row.id, String(row.id || ''));
  96. const uuid = row.id || returnedUuid;
  97. // ── 3. timestamp 形态 ──────────────────────────────────────────────────
  98. console.log('\n【3】timestamp');
  99. note('c_created_at 未传时是否自动填充', row.c_created_at !== undefined && row.c_created_at !== null, `读到 ${JSON.stringify(row.c_created_at)}`);
  100. const tsFormats = ['2026-09-17 21:30:00', '2026-09-17T21:30:00+08:00'];
  101. for (const ts of tsFormats) {
  102. const r = await call('POST', '/content/updateContent', {
  103. columnId: String(COL_SESSION),
  104. modelId: String(MODEL_SESSION),
  105. content: JSON.stringify({ id: uuid, c_updated_at: ts }),
  106. });
  107. const back = await search(COL_SESSION, 'c_session_id', SESSION_ID);
  108. const got = back.json?.content?.data?.[0]?.c_updated_at;
  109. note(`写入 "${ts}"`, r.json?.code === 200 && got != null, `code=${r.json?.code}, 读回=${JSON.stringify(got)}`);
  110. }
  111. // ── 4. updateContent 改标题 ───────────────────────────────────────────
  112. console.log('\n【4】updateContent(带 id=uuid)');
  113. const upd = await call('POST', '/content/updateContent', {
  114. columnId: String(COL_SESSION),
  115. modelId: String(MODEL_SESSION),
  116. content: JSON.stringify({ id: uuid, c_title: 'step0 改后的标题' }),
  117. });
  118. const afterUpd = await search(COL_SESSION, 'c_session_id', SESSION_ID);
  119. const newTitle = afterUpd.json?.content?.data?.[0]?.c_title;
  120. note('改标题生效', upd.json?.code === 200 && newTitle === 'step0 改后的标题', `code=${upd.json?.code}, c_title=${JSON.stringify(newTitle)}`);
  121. // ── 5. 长文本 + 中文 + 协议标记往返 ────────────────────────────────────
  122. console.log('\n【5】1889 长文本往返(含协议标记)');
  123. const longAnswer = [
  124. '<!-- POLICY_TABLE {"data":[{"title":"测试政策"}]} -->',
  125. '<scope title="正在思考中...">\n</scope>',
  126. '第一段中文回答。'.repeat(120),
  127. '<question-cards>{"kind":"company_need"}</question-cards>',
  128. '结尾行',
  129. ].join('\n');
  130. const addRecord = await call('POST', '/content/addContent', {
  131. columnId: String(COL_RECORD),
  132. modelId: String(MODEL_RECORD),
  133. content: JSON.stringify({
  134. c_credit_code: CREDIT,
  135. c_session_id: SESSION_ID,
  136. c_record_id: RECORD_ID,
  137. c_question: 'step0 问题:高新技术企业奖励是多少?',
  138. c_answer: longAnswer,
  139. c_source: 'zhaoshang',
  140. }),
  141. });
  142. note('1889 addContent 成功', addRecord.json?.code === 200, `code=${addRecord.json?.code}`);
  143. const recRows = (await search(COL_RECORD, 'c_record_id', RECORD_ID)).json?.content?.data || [];
  144. const rec = recRows[0] || {};
  145. note('长文本逐字节一致', rec.c_answer === longAnswer, `原长 ${longAnswer.length} / 读回 ${String(rec.c_answer || '').length}`);
  146. note('中文问题往返一致', rec.c_question === 'step0 问题:高新技术企业奖励是多少?', JSON.stringify(rec.c_question));
  147. // ── 6. 反馈字段 update ────────────────────────────────────────────────
  148. console.log('\n【6】1889 反馈字段');
  149. const fb = await call('POST', '/content/updateContent', {
  150. columnId: String(COL_RECORD),
  151. modelId: String(MODEL_RECORD),
  152. content: JSON.stringify({
  153. id: rec.id,
  154. c_feedback_status: 2,
  155. c_feedback_option: '没有回答我的问题',
  156. c_feedback_remark: 'step0 备注',
  157. c_feedback_at: '2026-09-17 21:31:00',
  158. }),
  159. });
  160. const recAfter = (await search(COL_RECORD, 'c_record_id', RECORD_ID)).json?.content?.data?.[0] || {};
  161. note(
  162. '反馈字段写入生效',
  163. fb.json?.code === 200 && Number(recAfter.c_feedback_status) === 2 && recAfter.c_feedback_option === '没有回答我的问题',
  164. `code=${fb.json?.code}, status=${recAfter.c_feedback_status}, option=${JSON.stringify(recAfter.c_feedback_option)}, at=${JSON.stringify(recAfter.c_feedback_at)}`
  165. );
  166. // ── 7. 中文 search 精确匹配(访客 访客_xxx 依赖)──────────────────────
  167. console.log('\n【7】中文精确 search');
  168. const cn = await search(COL_SESSION, 'c_credit_code', CREDIT);
  169. note('中文/下划线 credit_code 精确命中', (cn.json?.content?.data || []).length > 0, `命中 ${(cn.json?.content?.data || []).length} 行`);
  170. // ── 8. pageSize 上限 ─────────────────────────────────────────────────
  171. console.log('\n【8】pageSize');
  172. const big = await call('POST', '/content/selectContentList', {
  173. columnId: String(COL_RECORD),
  174. page: '0',
  175. pageSize: '200',
  176. search: JSON.stringify([{ field: 'c_session_id', searchType: 1, content: { value: SESSION_ID } }]),
  177. });
  178. note('pageSize=200 被接受', big.json?.code === 200 || big.json?.code === 202, `code=${big.json?.code}, 返回 ${(big.json?.content?.data || []).length} 行`);
  179. // ── 9. delContentById 参数形态 ───────────────────────────────────────
  180. console.log('\n【9】delContentById 参数形态');
  181. const probe = await call('DELETE', `/content/delContentById?id=${encodeURIComponent(rec.id)}`);
  182. const stillThere = ((await search(COL_RECORD, 'c_record_id', RECORD_ID)).json?.content?.data || []).length;
  183. note('DELETE ?id=<uuid> 生效', stillThere === 0, `code=${probe.json?.code}, 剩余 ${stillThere} 行`);
  184. // ── 10. 清理 1887 测试行 ─────────────────────────────────────────────
  185. console.log('\n【10】清理');
  186. const removed = await cleanup(COL_SESSION, 'c_session_id', SESSION_ID);
  187. const left = await cleanup(COL_RECORD, 'c_record_id', RECORD_ID);
  188. note('测试数据已清理', true, `1887 删 ${removed} 行 / 1889 删 ${left} 行`);
  189. // ── 汇总 ─────────────────────────────────────────────────────────────
  190. const failed = results.filter((r) => r.ok === false);
  191. console.log(`\n===== 汇总:${results.length - failed.length}/${results.length} 通过 =====`);
  192. if (failed.length) {
  193. console.log('未通过项:');
  194. failed.forEach((f) => console.log(' - ' + f.name + (f.detail ? ' ' + f.detail : '')));
  195. }
  196. console.log('\n关键结论(回填到计划/记录里):');
  197. console.log(' addContent 是否需 c_id :', addOk ? '不需要(自动生成)' : '被拒 → 需 DMS 侧调整模型');
  198. console.log(' addContent 是否回 uuid :', returnedUuid ? '回传,可省一次反查' : '不回传,需 search 反查');
  199. console.log(' 时间戳 : 见【3】读回值');
  200. };
  201. run().catch((err) => {
  202. console.error('\n脚本异常:', err);
  203. process.exit(1);
  204. });