fake.js 2.87 KB
/**
 * 测试用假实现(§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<string, object>} [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<any>, 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 : [],
      }
    },
  }
}