Agent/utils/masking-mock-test.ts
2026-08-06 22:17:39 +08:00

272 lines
9.8 KiB
TypeScript

// @ts-nocheck
import {
replacePlaceholders,
parsePidMap,
parseMainPid,
parseCandidates,
} from '../reports/md-splitter.js';
import {
validateCandidateSelection,
formatValidationFailure,
} from './masking-validator.js';
const PASS = '\x1b[32m✓\x1b[0m';
const FAIL = '\x1b[31m✗\x1b[0m';
const isBrowser = typeof window !== 'undefined';
function ok(msg) { console.log(`${isBrowser ? '✓' : PASS} ${msg}`); }
function err(msg) { console.log(`${isBrowser ? '✗' : FAIL} ${msg}`); }
function header(s) { console.log(`\n=== ${s} ===`); }
let _pass = 0, _fail = 0;
function check(cond, msg) {
if (cond) { _pass++; ok(msg); }
else { _fail++; err(msg); }
}
function test_replacePlaceholders() {
header('test 1: replacePlaceholders');
const pidMap = {
'emp_test1': { name: '曹云飞', id: '00856' },
'emp_test2': { name: '张磊', id: '01126' },
};
let r = replacePlaceholders(
'| 姓名 | 工号 |\n|---|---|\n| [NAME:emp_test1] | [ID:emp_test1] |',
pidMap
);
check(r.text.includes('曹云飞'), '1.1 NAME 替换为真实姓名');
check(r.text.includes('00856'), '1.1 ID 替换为真实工号');
check(!r.text.includes('[NAME:'), '1.1 无 token 残留');
check(r.warnings.length === 0, '1.1 无 warnings');
r = replacePlaceholders(
'[NAME:emp_test1] vs [NAME:emp_test2]',
pidMap
);
check(r.text === '曹云飞 vs 张磊', `1.2 多 token 替换: "${r.text}"`);
r = replacePlaceholders('[NAME:emp_unknown]', pidMap);
check(r.text === '[NAME:emp_unknown]', '1.3 未匹配保留原文');
check(r.warnings.length === 1, `1.3 记 1 个 warning(实际 ${r.warnings.length})`);
check(r.warnings[0].pid === 'emp_unknown', '1.3 warning 含 pid');
r = replacePlaceholders('[NAME:emp_test1]', {});
check(r.text === '[NAME:emp_test1]', '1.4 空 pidMap 保留原文');
check(r.warnings.length === 1, '1.4 空 pidMap 记 warning');
r = replacePlaceholders('', pidMap);
check(r.text === '', '1.5 空 md 返回空');
check(r.warnings.length === 0, '1.5 空 md 无 warning');
r = replacePlaceholders('emp_test1', pidMap);
check(r.text === 'emp_test1', '1.6 默认不替换裸 emp_xxx');
r = replacePlaceholders('emp_test1', pidMap, { replaceBare: true });
check(r.text === '00856', `1.7 replaceBare 开启时替换裸 emp_xxx 为工号(实际: ${r.text})`);
const extPidMap = {
'emp_s1': { type: 'station', name: '三林东' },
'emp_c1': { type: 'carType', name: '11A01' },
'emp_t1': { type: 'trainNo', name: '1113' },
'emp_e1': { type: 'employee', name: '曹云飞', id: '00856' },
};
r = replacePlaceholders('[STATION:emp_s1] [CAR:emp_c1] [TRAIN:emp_t1] [NAME:emp_e1]', extPidMap);
check(r.text === '三林东 11A01 1113 曹云飞', `1.8 扩展 token 类型(实际: "${r.text}")`);
check(r.warnings.length === 0, '1.8 扩展 token 无 warning');
r = replacePlaceholders('[ID:emp_s1]', extPidMap);
check(r.text === '[ID:emp_s1]', '1.9 [ID:] 对车站无 id,保留原文');
check(r.warnings.length === 1, '1.9 [ID:] 车站记 warning');
r = replacePlaceholders('[ID:emp_e1]', extPidMap);
check(r.text === '00856', '1.9 [ID:] 员工返回工号');
}
function test_parseMeta() {
header('test 2: parsePidMap / parseMainPid / parseCandidates');
let meta = {
pidMap: { emp_test1: { name: '曹云飞', id: '00856' } },
mainPid: 'emp_test1',
candidates: null,
};
check(parsePidMap(meta).emp_test1.name === '曹云飞', '2.1 parsePidMap 单员工');
check(parseMainPid(meta) === 'emp_test1', '2.1 parseMainPid');
check(parseCandidates(meta) === null, '2.1 parseCandidates null');
meta = {
pidMap: {
emp_a: { name: '曹云飞', id: '00856' },
emp_b: { name: '张磊', id: '01126' },
},
mainPid: null,
candidates: ['emp_a', 'emp_b'],
};
check(Object.keys(parsePidMap(meta)).length === 2, '2.2 parsePidMap 多候选');
check(parseMainPid(meta) === null, '2.2 parseMainPid null');
check(parseCandidates(meta).length === 2, '2.2 parseCandidates 2 个');
check(Object.keys(parsePidMap(null)).length === 0, '2.3 空 meta pidMap');
check(parseMainPid(null) === null, '2.3 空 meta mainPid');
check(parseCandidates(null) === null, '2.3 空 meta candidates');
check(Object.keys(parsePidMap({})).length === 0, '2.4 空 object pidMap');
check(parseMainPid({}) === null, '2.4 空 object mainPid');
}
function test_validateCandidateSelection() {
header('test 3: validateCandidateSelection');
const cands = [{ employeeId: 'emp_a' }, { employeeId: 'emp_b' }];
check(validateCandidateSelection('emp_a', cands).valid, '3.1 emp_a 假名精确');
check(validateCandidateSelection('选 emp_b', cands).valid, '3.1 emp_b 含上下文');
check(validateCandidateSelection('emp_a', cands).employeeId === 'emp_a', '3.1 返回 employeeId');
check(validateCandidateSelection('1 号', cands).valid, '3.2 「1 号」');
check(validateCandidateSelection('选 1', cands).valid, '3.2 「选 1」');
check(validateCandidateSelection('第一个', cands).valid, '3.2 「第一个」');
check(validateCandidateSelection('第二号', cands).valid, '3.2 「第二号」');
check(validateCandidateSelection('1 号', cands).employeeId === 'emp_a', '3.2 序号→employeeId');
check(validateCandidateSelection('2 号', cands).employeeId === 'emp_b', '3.2 序号 2→emp_b');
let r = validateCandidateSelection('7 号', cands);
check(!r.valid, '3.3 7 号 越界失败');
check(r.reason === 'INDEX_OUT_OF_RANGE', '3.3 reason=INDEX_OUT_OF_RANGE');
check(r.index === 7 && r.total === 2, '3.3 含 index/total');
r = validateCandidateSelection('emp_xxx', cands);
check(!r.valid, '3.4 emp_xxx 不在候选');
check(r.reason === 'EMP_NOT_IN_CANDIDATES', '3.4 reason=EMP_NOT_IN_CANDIDATES');
r = validateCandidateSelection('曹云飞', cands);
check(!r.valid, '3.5 用真实姓名绕过 → 失败');
check(r.reason === 'UNRECOGNIZED_FORMAT', '3.5 reason=UNRECOGNIZED_FORMAT');
check(validateCandidateSelection('1 号', []).valid === false, '3.6 空 candidates');
check(validateCandidateSelection('', cands).valid === false, '3.7 空 AI 回复');
r = validateCandidateSelection('1 号', ['emp_x', 'emp_y']);
check(r.valid && r.employeeId === 'emp_x', '3.8 字符串数组候选');
}
function test_formatValidationFailure() {
header('test 4: formatValidationFailure');
let s = formatValidationFailure({ reason: 'INDEX_OUT_OF_RANGE', index: 7, total: 2 });
check(s.includes('7') && s.includes('2'), '4.1 INDEX_OUT_OF_RANGE 含 index/total');
check(s.includes('1-2'), '4.1 提示候选范围');
s = formatValidationFailure({ reason: 'EMP_NOT_IN_CANDIDATES', attempted: 'emp_xxx', validIds: ['emp_a', 'emp_b'] });
check(s.includes('emp_xxx'), '4.2 EMP_NOT_IN_CANDIDATES 含 attempted');
check(s.includes('emp_a'), '4.2 含候选列表');
s = formatValidationFailure({ reason: 'UNRECOGNIZED_FORMAT' });
check(s.includes('序号'), '4.3 UNRECOGNIZED_FORMAT 提示序号');
s = formatValidationFailure({ reason: 'NO_CANDIDATES' });
check(s.length > 0, '4.4 NO_CANDIDATES 有提示');
s = formatValidationFailure({ reason: 'NO_AI_RESPONSE' });
check(s.length > 0, '4.5 NO_AI_RESPONSE 有提示');
check(formatValidationFailure({ valid: true }) === '', '4.6 valid 返回空字符串');
s = formatValidationFailure({ reason: 'UNKNOWN_REASON' });
check(s.length > 0, '4.7 未知 reason 兜底');
}
function test_endToEndMock() {
header('test 5: 端到端 Mock(模拟多候选→选择→校验)');
const multiCandidateMeta = {
pidMap: {
emp_test1: { name: '曹云飞', id: '00856' },
emp_test2: { name: '张磊', id: '01126' },
},
mainPid: null,
candidates: ['emp_test1', 'emp_test2'],
};
const pidMap = parsePidMap(multiCandidateMeta);
const cands = parseCandidates(multiCandidateMeta);
check(cands && cands.length === 2, '5.1 解析多候选');
const aiResponse = '好的,为您查询 2 号员工';
const v = validateCandidateSelection(aiResponse, cands);
check(v.valid, '5.2 「2 号」校验通过');
check(v.employeeId === 'emp_test2', `5.3 提取 employeeId=${v.employeeId}`);
const personReportMeta = {
pidMap: { emp_test2: { name: '张磊', id: '01126' } },
mainPid: 'emp_test2',
candidates: null,
};
const personPidMap = parsePidMap(personReportMeta);
const mdWithToken = `# 个人报告\n\n| 姓名 | 工号 |\n|---|---|\n| [NAME:emp_test2] | [ID:emp_test2] |`;
const { text: uiText } = replacePlaceholders(mdWithToken, personPidMap);
check(uiText.includes('张磊') && uiText.includes('01126'), '5.4 UI 替换为明文');
check(!uiText.includes('[NAME:'), '5.5 UI 无 token 残留');
check(mdWithToken.includes('[NAME:emp_test2]'), '5.6 AI prompt 保留 token');
}
export function runAll() {
_pass = 0; _fail = 0;
console.log(' masking-mock-test v3.1 ');
try {
test_replacePlaceholders();
test_parseMeta();
test_validateCandidateSelection();
test_formatValidationFailure();
test_endToEndMock();
} catch (e) {
err(`异常: ${e.message}`);
console.error(e);
}
console.log(`\n ${_pass} pass, ${_fail} fail `);
return { pass: _pass, fail: _fail };
}
if (isBrowser) {
window.__maskingMockTest = { runAll };
console.log('masking-mock-test 已加载,运行 __maskingMockTest.runAll()');
}
if (!isBrowser && typeof require !== 'undefined' && require.main === module) {
runAll();
}