| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221 |
- /**
- * 打**真实 DMS**验证企业信息同步的落库行为(栏目 1888 企业基础信息 / 1886 企业荣誉信息)。
- *
- * 为什么注入假分类响应:分类接口 `POST /api/company/classify` 目前对**任何带内容的
- * 请求体都返回 422**(2026-09-18 实测,连文档里的合成示例也被拒,属后端问题),
- * 所以这一段用 `applyCompanyClassification(分类结果)` 直接验证「拿到结果之后」的落库链路。
- *
- * 验证什么:
- * ① 首写:1888 出现一行且六个标签字段 = 对应角度的 JSON 数组;1886 按标签各一行
- * ② 重跑同样的数据:**零新增**(幂等,不产生重复行)
- * ③ 改标签:1888 仍只有一行、字段已更新;1886 集合按增删收敛(旧行被销毁查不到)
- * ④ 资质荣誉 = 空集:删光「我方」行,**迁移数据行(c_source 非 company_info_sync)不动**
- * ⑤ 清理:按测试信用代码删掉本次产生的全部行,复查残留 0
- *
- * 怎么跑(先按 _entry-company-classify.ts 头部注释重新打包):
- * 1) npm run dev # 代理负责注入 DMS token
- * 2) node harness/tools/verify-company-classify-dms.mjs https://localhost:8083/dms-api
- *
- * ⚠️ 脚本**不持有 token**(和浏览器一样只打代理地址);测试数据用可识别的信用代码前缀,
- * 结束时自行清理。
- */
- process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
- const base = process.argv[2] || 'https://localhost:8083/dms-api';
- globalThis.VITE_DMS_API = base;
- const {
- applyCompanyClassification,
- HONOR_SYNC_SOURCE,
- HONOR_TAG_FIELD,
- ANGLE_TO_FIELD,
- DMS_COLUMN_ENTERPRISE,
- DMS_MODEL_ENTERPRISE,
- DMS_COLUMN_HONOR,
- DMS_MODEL_HONOR,
- addDmsContent,
- deleteDmsContent,
- searchDmsContents,
- } = await import('./_company-classify.mjs');
- let pass = 0;
- let fail = 0;
- const check = (name, ok, extra = '') => {
- if (ok) {
- pass++;
- console.log(` ok ${name}`);
- } else {
- fail++;
- console.log(` FAIL ${name}${extra === '' ? '' : ` → ${JSON.stringify(extra)}`}`);
- }
- };
- const TEST_CREDIT_CODE = `TEST_CC_${Date.now()}`;
- const TEST_NAME = '分类同步测试公司';
- const findEnterprise = async () => {
- const rows = await searchDmsContents(DMS_COLUMN_ENTERPRISE, {
- search: [{ field: 'c_credit_code', searchType: 1, content: { value: TEST_CREDIT_CODE } }],
- page: 0,
- pageSize: 10,
- });
- return rows;
- };
- const findHonors = async () => {
- const rows = await searchDmsContents(DMS_COLUMN_HONOR, {
- search: [{ field: 'c_credit_code', searchType: 1, content: { value: TEST_CREDIT_CODE } }],
- page: 0,
- pageSize: 200,
- });
- return rows;
- };
- const classifyWith = (categories, extra = {}) => ({
- company_name: TEST_NAME,
- credit_code: TEST_CREDIT_CODE,
- legal_representative: '测试法人',
- categories,
- status: 'completed',
- errors: {},
- ...extra,
- });
- console.log(`测试信用代码:${TEST_CREDIT_CODE}\n`);
- try {
- /* ---------------------------------------------------------------- *
- * ① 首写
- * ---------------------------------------------------------------- */
- console.log('【1】首写:1888 一行 + 1886 按标签各一行');
- const first = classifyWith({
- 基本信息: ['小微企业'],
- 资质荣誉: ['高新技术企业', '专精特新'],
- 产业信息: ['第一产业', '现代农业'],
- 经营活动: ['对外投资'],
- 行业信息: ['涉农(广义)'],
- 许可认证: ['食品生产许可'],
- });
- await applyCompanyClassification(first);
- const rows1 = await findEnterprise();
- check('1888 恰好一行', rows1.length === 1, rows1.length);
- const row1 = rows1[0] || {};
- check('身份字段正确', row1.c_name === TEST_NAME && row1.c_credit_code === TEST_CREDIT_CODE && row1.c_oper_name === '测试法人', {
- c_name: row1.c_name,
- c_credit_code: row1.c_credit_code,
- c_oper_name: row1.c_oper_name,
- });
- check('基本信息标签', row1[ANGLE_TO_FIELD['基本信息']] === '["小微企业"]', row1[ANGLE_TO_FIELD['基本信息']]);
- check('产业信息标签', row1[ANGLE_TO_FIELD['产业信息']] === '["第一产业","现代农业"]', row1[ANGLE_TO_FIELD['产业信息']]);
- check('许可认证标签', row1[ANGLE_TO_FIELD['许可认证']] === '["食品生产许可"]', row1[ANGLE_TO_FIELD['许可认证']]);
- // ⚠️ c_tag_honor 在 DMS 里的 frontType 是 content(富文本),其余是 text/varchar ——
- // 富文本字段有可能被清洗,所以单独断言它逐字往返
- check(
- '资质荣誉标签(富文本字段)逐字往返',
- row1[ANGLE_TO_FIELD['资质荣誉']] === '["高新技术企业","专精特新"]',
- row1[ANGLE_TO_FIELD['资质荣誉']]
- );
- const honors1 = await findHonors();
- const owned1 = honors1.filter((r) => r.c_source === HONOR_SYNC_SOURCE);
- const tags1 = owned1.map((r) => r[HONOR_TAG_FIELD]).sort();
- check('1886 我方行数 = 资质荣誉标签数', owned1.length === 2, owned1.length);
- check('1886 标签内容正确', JSON.stringify(tags1) === JSON.stringify(['专精特新', '高新技术企业']), tags1);
- check('1886 行带企业与归属', owned1.every((r) => r.c_credit_code === TEST_CREDIT_CODE && r.c_name === TEST_NAME));
- /* ---------------------------------------------------------------- *
- * ② 幂等:同数据重跑
- * ---------------------------------------------------------------- */
- console.log('\n【2】同数据重跑 → 零新增');
- await applyCompanyClassification(first);
- const rows2 = await findEnterprise();
- const honors2 = await findHonors();
- check('1888 仍是一行(没有重复)', rows2.length === 1, rows2.length);
- check('1888 记录 uuid 未变(走的是更新/零写入)', rows2[0]?.id === row1.id, { before: row1.id, after: rows2[0]?.id });
- check('1886 仍是我方 2 行', honors2.filter((r) => r.c_source === HONOR_SYNC_SOURCE).length === 2, honors2.length);
- /* ---------------------------------------------------------------- *
- * ③ 改标签
- * ---------------------------------------------------------------- */
- console.log('\n【3】改标签 → 1888 更新、1886 增删收敛');
- const second = classifyWith({
- 基本信息: ['小微企业', '专精特新'],
- 资质荣誉: ['高新技术企业', '科技小巨人'], // 去掉「专精特新」,新增「科技小巨人」
- 产业信息: ['第一产业', '现代农业'],
- 经营活动: ['对外投资'],
- 行业信息: ['涉农(广义)'],
- 许可认证: ['食品生产许可'],
- });
- await applyCompanyClassification(second);
- const rows3 = await findEnterprise();
- check('1888 仍是一行', rows3.length === 1, rows3.length);
- check('1888 基本信息已更新', rows3[0]?.[ANGLE_TO_FIELD['基本信息']] === '["小微企业","专精特新"]', rows3[0]?.[ANGLE_TO_FIELD['基本信息']]);
- const honors3 = await findHonors();
- const tags3 = honors3.filter((r) => r.c_source === HONOR_SYNC_SOURCE).map((r) => r[HONOR_TAG_FIELD]).sort();
- check('1886 收敛到新集合(2 行)', tags3.length === 2, tags3);
- check('被移除的标签已不在', !tags3.includes('专精特新'), tags3);
- check('新增的标签已在', tags3.includes('科技小巨人'), tags3);
- /* ---------------------------------------------------------------- *
- * ④ 空集 + 迁移行保护
- * ---------------------------------------------------------------- */
- console.log('\n【4】资质荣誉空集 → 删光我方行;迁移数据行不动');
- const foreignId = await addDmsContent(DMS_COLUMN_HONOR, DMS_MODEL_HONOR, {
- c_id: 0,
- c_credit_code: TEST_CREDIT_CODE,
- c_name: TEST_NAME,
- c_source: 'migrate_test', // 模拟迁移数据:不是我们写的
- [HONOR_TAG_FIELD]: '迁移来的荣誉',
- c_created_at: '2026-09-18 12:00:00',
- });
- check('预置了 1 条迁移行(用于验证不被误删)', !!foreignId, foreignId);
- await applyCompanyClassification(
- classifyWith({
- 基本信息: ['小微企业', '专精特新'],
- 资质荣誉: [], // completed + 空数组 = 确实没有荣誉
- 产业信息: ['第一产业', '现代农业'],
- 经营活动: ['对外投资'],
- 行业信息: ['涉农(广义)'],
- 许可认证: ['食品生产许可'],
- })
- );
- const honors4 = await findHonors();
- check('我方荣誉行已删光', honors4.filter((r) => r.c_source === HONOR_SYNC_SOURCE).length === 0, honors4.length);
- check('迁移行仍在(未被误删)', honors4.some((r) => r.id === foreignId && r[HONOR_TAG_FIELD] === '迁移来的荣誉'), honors4.map((r) => r.c_source));
- /* ---------------------------------------------------------------- *
- * ⑤ 缺失信用代码 → 跳过
- * ---------------------------------------------------------------- */
- console.log('\n【5】分类结果缺信用代码 → 整条跳过(不写任何行)');
- const noCredit = { company_name: '无信用代码的公司', credit_code: null, categories: { 基本信息: ['x'] }, status: 'completed' };
- await applyCompanyClassification(noCredit);
- check('1888 行数未变(仍是 1,且没有新建无名行)', (await findEnterprise()).length === 1);
- } finally {
- /* ---------------------------------------------------------------- *
- * 清理
- * ---------------------------------------------------------------- */
- console.log('\n【清理】');
- const cleanup = async (columnId, label) => {
- const rows = columnId === DMS_COLUMN_ENTERPRISE ? await findEnterprise() : await findHonors();
- for (const row of rows) {
- if (typeof row.id === 'string') await deleteDmsContent(columnId, row.id);
- }
- console.log(` ${label} 清理 ${rows.length} 行`);
- };
- await cleanup(DMS_COLUMN_ENTERPRISE, '1888');
- await cleanup(DMS_COLUMN_HONOR, '1886');
- const leftE = await findEnterprise();
- const leftH = await findHonors();
- check('残留 0 行(1888)', leftE.length === 0, leftE.length);
- check('残留 0 行(1886)', leftH.length === 0, leftH.length);
- }
- console.log(`\n===== 通过 ${pass} 项,失败 ${fail} 项 =====`);
- process.exit(fail ? 1 : 0);
|