/** * 测试用假实现(§3 可选导出)。 * * 让调用方在**无网络**下单测全部判定路径(含降级、合并、block 阻断)——验收 8。 * `createFakeRules` 给一份可注入的规则数据;`createFakeGuardPort` 给一个脚本化的 * LLM 护栏 Port。词表本身由调用方在测试里给(seed),本文件不内置任何真实词表 * (包内 grep 不到硬编码词表——验收 7)。 */ /** * 造一份可注入的规则。所有内容来自 `seed`,无内置词表。 * * @param {object} [seed] * @param {string[]} [seed.words] 敏感词表(测试自定义) * @param {number} [seed.lengthMin] * @param {number} [seed.lengthMax] * @param {import('./rules-check.js').FormatRule[]} [seed.formatRules] * @param {Record} [seed.policies] 二期 policy 覆盖(一期默认不报错) * @returns {import('./rules-check.js').Rules} */ export function createFakeRules(seed = {}) { /** @type {import('./rules-check.js').Rules} */ const rules = { words: Array.isArray(seed.words) ? [...seed.words] : [], formatRules: Array.isArray(seed.formatRules) ? [...seed.formatRules] : [], } if (typeof seed.lengthMin === 'number') rules.lengthMin = seed.lengthMin if (typeof seed.lengthMax === 'number') rules.lengthMax = seed.lengthMax if (seed.policies && typeof seed.policies === 'object') rules.policies = seed.policies return rules } /** * 造一个脚本化的 LLM 护栏 Port,覆盖 unsafe / safe / 抛错 / 动态响应路径。 * * @param {object} [scripted] * @param {boolean} [scripted.unsafe] 固定返回的 unsafe 标志 * @param {string[]} [scripted.categories] 固定返回的基础安全类目 * @param {string[]} [scripted.reasons] 固定返回的原因 * @param {Error} [scripted.throws] 若提供,则每次 classify 直接抛它 * @param {(args:{text:string,context?:any,region?:string}) => * ({categories?:string[],unsafe?:boolean,reasons?:string[]}|undefined)} [scripted.respond] * 动态响应钩子,优先于固定值;返回 undefined 视为 safe 空结果 * @returns {{ classify:(args:any) => Promise, calls: any[] }} */ export function createFakeGuardPort(scripted = {}) { /** @type {any[]} */ const calls = [] return { calls, async classify(args) { calls.push(args) if (scripted.throws) throw scripted.throws if (typeof scripted.respond === 'function') { const r = scripted.respond(args) return { categories: (r && r.categories) || [], unsafe: Boolean(r && r.unsafe), reasons: (r && r.reasons) || [], } } return { categories: Array.isArray(scripted.categories) ? scripted.categories : [], unsafe: Boolean(scripted.unsafe), reasons: Array.isArray(scripted.reasons) ? scripted.reasons : [], } }, } }