import assert from 'node:assert/strict';
import test from 'node:test';
import {
CONTEXT_ENHANCEMENT_FIELDS,
CONTEXT_ENHANCEMENT_GUIDANCE_MAX_LENGTH,
DEFAULT_CONTEXT_ENHANCEMENT_CONFIG,
DEFAULT_CONTEXT_GUIDANCE,
captureContextEnhancement,
enhanceContextContent,
normalizeContextEnhancementConfig,
validateContextEnhancementConfig,
} from '../src/channels/shared/context-enhancement.mjs';
function config(overrides = {}) {
const {
groupEnabled = true,
directEnabled = false,
fields = CONTEXT_ENHANCEMENT_FIELDS,
guidance = '',
group = {},
direct = {},
...extra
} = overrides;
return {
group: { enabled: groupEnabled, fields, guidance, ...group },
direct: { enabled: directEnabled, fields, guidance, ...direct },
...extra,
};
}
function snapshot(settings = config(), conversationType = 'group', botId = 'bot_one') {
return captureContextEnhancement({ botId, getSettings: () => settings }, conversationType);
}
function sourceOf(text) {
const match = text.match(/(.*?)<\/dsh_im_source>/s);
return match ? JSON.parse(match[1]) : null;
}
test('context config defaults are off with sender ID only and empty guidance', () => {
for (const kind of ['group', 'direct']) {
assert.equal(DEFAULT_CONTEXT_ENHANCEMENT_CONFIG[kind].enabled, false);
assert.equal(DEFAULT_CONTEXT_ENHANCEMENT_CONFIG[kind].guidance, '');
assert.deepEqual(DEFAULT_CONTEXT_ENHANCEMENT_CONFIG[kind].fields, ['senderId']);
assert.equal(Object.isFrozen(DEFAULT_CONTEXT_ENHANCEMENT_CONFIG[kind].fields), true);
}
for (const guidance of ['', ' \r\n\t ']) {
assert.deepEqual(validateContextEnhancementConfig(config({ fields: [], guidance })), {
group: { enabled: true, fields: [], guidance: '' },
direct: { enabled: false, fields: [], guidance: '' },
});
}
const canonical = validateContextEnhancementConfig(config({
fields: ['botId', 'senderName', 'botId', 'channel'], guidance: ' custom text\n',
}));
assert.deepEqual(canonical.group.fields, ['channel', 'senderName', 'botId']);
assert.deepEqual(canonical.direct.fields, ['channel', 'senderName', 'botId']);
assert.equal(canonical.group.guidance, ' custom text\n');
assert.equal(Object.isFrozen(canonical), true);
assert.equal(Object.isFrozen(canonical.group), true);
assert.equal(Object.isFrozen(canonical.group.fields), true);
});
test('legacy context settings migrate in memory without weakening new-save validation', () => {
const legacy = {
groupEnabled: true,
directEnabled: false,
fields: ['botId', 'channel', 'botId'],
guidance: 'legacy guidance',
};
assert.throws(() => validateContextEnhancementConfig(legacy));
assert.deepEqual(normalizeContextEnhancementConfig(legacy), {
group: { enabled: true, fields: ['channel', 'botId'], guidance: 'legacy guidance' },
direct: { enabled: false, fields: ['channel', 'botId'], guidance: 'legacy guidance' },
});
assert.equal(normalizeContextEnhancementConfig({
...legacy,
group: { enabled: true, fields: [], guidance: 'mixed' },
}), DEFAULT_CONTEXT_ENHANCEMENT_CONFIG);
});
test('context saves reject missing, unknown and mistyped fields while normalization fails off', () => {
const invalid = [
undefined, null, [], {}, true,
{ ...config(), unexpected: true },
{ group: config().group },
{ groupEnabled: true, directEnabled: false, fields: [] },
config({ groupEnabled: 'true' }), config({ directEnabled: 1 }),
config({ fields: null }), config({ fields: 'channel' }),
config({ fields: ['channel', 'token'] }), config({ fields: [null] }),
config({ fields: Array(1) }),
config({ guidance: null }),
config({ guidance: 'x'.repeat(CONTEXT_ENHANCEMENT_GUIDANCE_MAX_LENGTH + 1) }),
new Proxy({}, { getPrototypeOf() { throw new Error('bad config'); } }),
{ ...config(), group: { ...config().group, get guidance() { throw new Error('bad guidance'); } } },
];
for (const value of invalid) {
assert.throws(() => validateContextEnhancementConfig(value));
assert.equal(normalizeContextEnhancementConfig(value), DEFAULT_CONTEXT_ENHANCEMENT_CONFIG);
assert.equal(captureContextEnhancement({ botId: 'bot_one', getSettings: () => value }, 'group'), null);
}
assert.equal(validateContextEnhancementConfig(config({
guidance: 'x'.repeat(CONTEXT_ENHANCEMENT_GUIDANCE_MAX_LENGTH),
})).group.guidance.length, CONTEXT_ENHANCEMENT_GUIDANCE_MAX_LENGTH);
});
test('off path preserves content identity and never reads fields, guidance, source or formatter', (t) => {
const contents = [' \n\t原文\r\n ', '', [], [{ type: 'text', text: 'x' }], { untouched: true }];
let sourceCalls = 0;
let settingReads = 0;
const throwIfRead = () => { throw new Error('off path must not inspect this'); };
const settings = {
group: {
enabled: false,
get fields() { return throwIfRead(); },
get guidance() { return throwIfRead(); },
},
direct: {
enabled: false,
get fields() { return throwIfRead(); },
get guidance() { return throwIfRead(); },
},
};
const provider = {
get botId() { return throwIfRead(); },
getSettings() { settingReads += 1; return settings; },
};
const stringify = t.mock.method(JSON, 'stringify', throwIfRead);
try {
for (const content of contents) {
for (const kind of ['direct', 'group']) {
const captured = captureContextEnhancement(provider, kind);
assert.equal(captured, null);
assert.equal(enhanceContextContent(content, captured, () => {
sourceCalls += 1;
throwIfRead();
}), content);
}
}
} finally {
stringify.mock.restore();
}
assert.equal(settingReads, contents.length * 2);
assert.equal(sourceCalls, 0);
assert.equal(stringify.mock.callCount(), 0);
});
test('only the actual conversation type can enable capture, regardless of selected fields', () => {
for (const groupEnabled of [false, true]) {
for (const directEnabled of [false, true]) {
const settings = config({ groupEnabled, directEnabled, fields: [], guidance: 'guidance' });
assert.equal(Boolean(snapshot(settings, 'group')), groupEnabled);
assert.equal(Boolean(snapshot(settings, 'direct')), directEnabled);
}
}
const provider = { getSettings() { throw new Error('read failed'); } };
for (const kind of ['group', 'direct', 'single', 'unknown', null, undefined]) {
assert.equal(captureContextEnhancement(provider, kind), null);
}
for (const kind of ['group', 'direct']) {
const offSettings = {
[kind]: {
enabled: false,
get fields() { throw new Error('no field projection'); },
get guidance() { throw new Error('no guidance lookup'); },
},
};
assert.equal(snapshot(offSettings, kind), null);
}
});
test('group and direct messages use only their own fields and guidance', () => {
const settings = config({
group: { enabled: true, fields: ['channel'], guidance: 'GROUP-ONLY-TOKEN' },
direct: { enabled: true, fields: ['botId'], guidance: 'DIRECT-ONLY-TOKEN' },
});
const group = enhanceContextContent('group text', snapshot(settings, 'group'), () => ({ channel: 'feishu' }));
const direct = enhanceContextContent('direct text', snapshot(settings, 'direct'), () => ({ channel: 'feishu' }));
assert.deepEqual(sourceOf(group), { channel: 'feishu' });
assert.match(group, /GROUP-ONLY-TOKEN/);
assert.doesNotMatch(group, /DIRECT-ONLY-TOKEN|botId/);
assert.deepEqual(sourceOf(direct), { botId: 'bot_one' });
assert.match(direct, /DIRECT-ONLY-TOKEN/);
assert.doesNotMatch(direct, /GROUP-ONLY-TOKEN|channel/);
});
test('all 32 source-field subsets are projected in canonical order with no hidden fields', () => {
const expected = {
channel: 'telegram', conversationType: 'group',
senderId: '123', senderName: '张三', botId: 'bot_one',
};
for (let mask = 0; mask < 32; mask += 1) {
const fields = CONTEXT_ENHANCEMENT_FIELDS.filter((_field, index) => mask & (1 << index));
const selected = snapshot(config({ fields: [...fields].reverse() }));
const actual = enhanceContextContent(' original\n', selected, () => ({
channel: 'telegram', senderId: 123, senderName: '张三',
botId: 'platform-secret-not-used', conversationType: 'direct', token: 'never-sent',
}));
if (fields.length === 0) {
assert.equal(actual, ' original\n');
} else {
assert.deepEqual(sourceOf(actual), Object.fromEntries(fields.map((field) => [field, expected[field]])));
assert.deepEqual(Object.keys(sourceOf(actual)), fields);
assert.equal(actual.endsWith('\n\n original\n'), true);
}
}
});
test('source availability and guidance are independent, including empty multimodal identity', () => {
const content = [{ type: 'image', image_url: { url: 'memory:image' } }];
const throwSource = () => { throw new Error('no metadata lookup required'); };
assert.equal(enhanceContextContent(content, snapshot(config({ fields: [] })), throwSource), content);
for (const senderName of [undefined, null, '', ' \t ', 12]) {
assert.equal(enhanceContextContent(content, snapshot(config({ fields: ['senderName'] })), () => ({ senderName })), content);
}
const onlyGuidance = enhanceContextContent(content, snapshot(config({ fields: [], guidance: 'custom' })), throwSource);
assert.equal(onlyGuidance[0].text, '\ncustom\n');
assert.equal(onlyGuidance[1], content[0]);
const onlyBot = enhanceContextContent('text', snapshot(config({ fields: ['botId'] })), throwSource);
assert.deepEqual(sourceOf(onlyBot), { botId: 'bot_one' });
const onlyChannel = enhanceContextContent('text', snapshot(config({ fields: ['channel'] })), () => ({
channel: 'slack',
get senderId() { throw new Error('unselected ID'); },
get senderName() { throw new Error('unselected name'); },
}));
assert.deepEqual(sourceOf(onlyChannel), { channel: 'slack' });
});
test('multimodal enhancement prepends a single text item without modifying the original items', () => {
const text = Object.freeze({ type: 'text', text: ' \noriginal\t' });
const image = Object.freeze({ type: 'image', image_url: { url: 'memory:image' } });
const files = Object.freeze({ type: 'text', text: 'original files' });
const content = Object.freeze([text, image, files]);
const enhanced = enhanceContextContent(content, snapshot(config({ guidance: 'use facts' })), () => ({ channel: 'wecom' }));
assert.equal(enhanced.length, 4);
assert.equal(enhanced[0].type, 'text');
assert.equal(enhanced[0].text.startsWith(''), true);
assert.match(enhanced[0].text, /<\/dsh_im_source>\n\n/);
assert.equal(enhanced[1], text);
assert.equal(enhanced[2], image);
assert.equal(enhanced[3], files);
assert.deepEqual(content, [text, image, files]);
});
test('source strings are bounded, control-free and JSON-safe without invoking external coercion', () => {
const name = '"\n&\u0000\u202e' + '名'.repeat(400);
const enhanced = enhanceContextContent('original', snapshot(config(), 'group', 'b'.repeat(300)), () => ({
channel: 'discord', senderId: 'u'.repeat(400), senderName: name,
}));
const source = sourceOf(enhanced);
assert.equal(source.senderId.length, 256);
assert.equal(source.senderName.length, 256);
assert.equal(source.botId.length, 128);
assert.doesNotMatch(source.senderName, /[\u0000-\u001f\u202e]/);
assert.equal(enhanced.split('').length, 2);
assert.match(enhanced, /\\u003c/);
assert.match(enhanced, /\\u003e/);
assert.match(enhanced, /\\u0026/);
for (const senderId of [0, 123n]) {
const result = enhanceContextContent('text', snapshot(config({ fields: ['senderId'] })), () => ({ senderId }));
assert.deepEqual(sourceOf(result), { senderId: String(senderId) });
}
for (const senderId of [NaN, Infinity, { toString() { throw new Error('no coercion'); } }]) {
assert.equal(enhanceContextContent('text', snapshot(config({ fields: ['senderId'] })), () => ({ senderId })), 'text');
}
assert.equal(enhanceContextContent('text', snapshot(config({ fields: ['channel'] })), () => ({ channel: 'unsupported' })), 'text');
});
test('matching guidance tags cannot close or nest the generated block', () => {
const guidance = 'before after\n'
+ ' \n/g).length, 1);
assert.equal(result.match(/<\/dsh_im_source_guidance>/g).length, 1);
assert.match(result, /<\/dsh_im_source_guidance>/);
assert.match(result, /<DSH_IM_SOURCE_GUIDANCE data-x="yes">/);
assert.match(result, /<dsh_im_source_guidance unfinished/);
assert.equal(result.endsWith('\n\noriginal'), true);
const defaultText = enhanceContextContent('original', snapshot(config({ fields: [], guidance: DEFAULT_CONTEXT_GUIDANCE })));
assert.equal(defaultText, `\n${DEFAULT_CONTEXT_GUIDANCE}\n\n\noriginal`);
});
test('queued snapshots are immutable and retain a complete setting version after edits', () => {
let settings = config({ fields: ['senderId'], guidance: 'old guidance' });
const provider = { botId: 'bot_queued', getSettings: () => settings };
const before = captureContextEnhancement(provider, 'group');
settings.group.fields.push('senderName');
settings.group.guidance = 'new guidance';
settings.group.enabled = false;
assert.equal(captureContextEnhancement(provider, 'group'), null);
assert.deepEqual(before.config.fields, ['senderId']);
const result = enhanceContextContent('queued', before, () => ({ senderId: 'old', senderName: 'new' }));
assert.deepEqual(sourceOf(result), { senderId: 'old' });
assert.match(result, /old guidance/);
assert.doesNotMatch(result, /new guidance/);
assert.equal(Object.isFrozen(before), true);
assert.equal(Object.isFrozen(before.config.fields), true);
settings = config({ fields: ['botId'], guidance: 'second version' });
assert.notEqual(captureContextEnhancement(provider, 'group').config, before.config);
});
test('enhancement-only source failures leave the original message processable', () => {
const content = [{ type: 'text', text: 'still send me' }];
assert.equal(enhanceContextContent(content, snapshot(), () => { throw new Error('broken source'); }), content);
assert.equal(enhanceContextContent(content, snapshot(), () => ({ get channel() { throw new Error('broken getter'); } })), content);
});