/** * v0.8.2 P1-5: 敏感值脱敏单源工具测试 * * 锁定契约: * - 键名归一化匹配(api_key / authKey / Authorization 等形态均命中) * - 长值保留后 4 位、短值完全掩码 * - 嵌套对象/数组递归;非敏感字段原样保留 * - 循环引用与深度上限防护(防御恶意构造的工具参数) * - 原对象不被修改 */ import { describe, it, expect } from 'vitest'; import { deepMaskSensitive, maskSensitiveValue } from '../mask'; describe('maskSensitiveValue', () => { it('长值保留后 4 位', () => { expect(maskSensitiveValue('sk-abcdefgh12345678')).toBe('***5678'); }); it('短值(≤4)完全掩码', () => { expect(maskSensitiveValue('abc')).toBe('***'); expect(maskSensitiveValue('abcd')).toBe('***'); }); }); describe('deepMaskSensitive', () => { it('顶层与嵌套的敏感键均被掩码', () => { const input = { url: 'https://api.example.com', headers: { Authorization: 'Bearer sk-abcdefgh12345678', 'content-type': 'application/json', }, api_key: 'sk-abcdefgh12345678', nested: { authToken: 'token-1234567890' }, }; const out = deepMaskSensitive(input) as typeof input; expect(out.url).toBe('https://api.example.com'); expect(out['api_key']).toBe('***5678'); expect((out.headers as Record).Authorization).toBe('***5678'); expect((out.headers as Record)['content-type']).toBe('application/json'); expect((out.nested as { authToken: string }).authToken).toBe('***7890'); }); it('数组内对象同样脱敏', () => { const out = deepMaskSensitive([{ secret: 'supersecret-value-42' }, { ok: 1 }]) as Array< Record >; expect(out[0].secret).toBe('***e-42'); expect(out[1].ok).toBe(1); }); it('原对象不被修改', () => { const input = { api_key: 'sk-abcdefgh12345678' }; deepMaskSensitive(input); expect(input.api_key).toBe('sk-abcdefgh12345678'); }); it('循环引用与深度上限不抛错', () => { const a: Record = { name: 'a' }; a.self = a; const out = deepMaskSensitive(a) as Record; expect(out.name).toBe('a'); expect(out.self).toBe('[circular]'); const deep: Record = { v: 0 }; let cur = deep; for (let i = 0; i < 20; i++) { cur.next = { v: i + 1 }; cur = cur.next as Record; } const deepOut = deepMaskSensitive(deep) as Record; expect(deepOut.v).toBe(0); expect(JSON.stringify(deepOut)).toContain('depth-limit'); }); });