dsh-im-ops/test/context-enhancement-settings.test.mjs

521 lines
27 KiB
JavaScript

import assert from 'node:assert/strict';
import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import test from 'node:test';
import {
BotWorkspaceStore,
createWorkspaceAwareController,
observeBotWorkspaceRemovals,
} from '../src/channels/shared/bot-workspace-store.mjs';
import {
DEFAULT_CONTEXT_ENHANCEMENT_CONFIG,
captureContextEnhancement,
enhanceContextContent,
} from '../src/channels/shared/context-enhancement.mjs';
import { TokenBotController } from '../src/channels/shared/token-bot-controller.mjs';
import {
TokenBotConfigStore,
deriveTokenBotIdentity,
} from '../src/channels/shared/token-config-store.mjs';
import { createWecomRpcHandler, WECOM_ENDPOINTS } from '../plugin-src/host/channels/wecom/rpc.mjs';
import { createWeixinRpcHandler, WEIXIN_ENDPOINTS } from '../plugin-src/host/channels/weixin/rpc.mjs';
import { createFeishuRpcHandler, FEISHU_ENDPOINTS } from '../plugin-src/host/channels/feishu/rpc.mjs';
import { createDingtalkRpcHandler, DINGTALK_ENDPOINTS } from '../plugin-src/host/channels/dingtalk/rpc.mjs';
import { createQqRpcHandler, QQ_ENDPOINTS } from '../plugin-src/host/channels/qq/rpc.mjs';
import { createSlackRpcHandler, SLACK_ENDPOINTS } from '../plugin-src/host/channels/slack/rpc.mjs';
import { createTelegramRpcHandler, TELEGRAM_ENDPOINTS } from '../plugin-src/host/channels/telegram/rpc.mjs';
import { createDiscordRpcHandler, DISCORD_ENDPOINTS } from '../plugin-src/host/channels/discord/rpc.mjs';
import { createWhatsappRpcHandler, WHATSAPP_ENDPOINTS } from '../plugin-src/host/channels/whatsapp/rpc.mjs';
import {
SET_CONTEXT_ENHANCEMENT_ENDPOINT,
validContextEnhancementPayload,
} from '../plugin-src/host/channels/shared/context-enhancement-rpc.mjs';
const CHANNELS = [
['wecom', createWecomRpcHandler, WECOM_ENDPOINTS],
['weixin', createWeixinRpcHandler, WEIXIN_ENDPOINTS],
['feishu', createFeishuRpcHandler, FEISHU_ENDPOINTS],
['dingtalk', createDingtalkRpcHandler, DINGTALK_ENDPOINTS],
['qq', createQqRpcHandler, QQ_ENDPOINTS],
['slack', createSlackRpcHandler, SLACK_ENDPOINTS],
['telegram', createTelegramRpcHandler, TELEGRAM_ENDPOINTS],
['discord', createDiscordRpcHandler, DISCORD_ENDPOINTS],
['whatsapp', createWhatsappRpcHandler, WHATSAPP_ENDPOINTS],
];
const enabled = (overrides = {}) => ({
groupEnabled: true, directEnabled: true, fields: ['channel', 'botId'], guidance: 'saved guidance',
...overrides,
});
async function fixture(t) {
const directory = await mkdtemp(join(tmpdir(), 'dsh-im-context-settings-'));
t.after(() => rm(directory, { recursive: true, force: true }));
const path = join(directory, 'workspaces.json');
const store = await new BotWorkspaceStore(path, { defaultWorkspace: directory }).load();
return { directory, path, store };
}
function deferred() {
let resolve;
const promise = new Promise((done) => { resolve = done; });
return { promise, resolve };
}
function fakeController(botIds) {
const unavailable = () => { throw new Error('unrelated lifecycle action must not run'); };
return {
status: () => ({ bots: botIds.map((botId) => ({ botId, configured: true, connected: false })) }),
startProvisioning: unavailable,
registrationStatus: unavailable,
submitVerification: unavailable,
cancelProvisioning: unavailable,
bindCredentials: unavailable,
reconnectBot: unavailable,
deleteBot: unavailable,
startRegistration: unavailable,
cancelRegistration: unavailable,
disconnect: unavailable,
setAccessPolicy: unavailable,
approveSender: unavailable,
revokeSender: unavailable,
};
}
function awareController(store, core, extra = {}) {
return createWorkspaceAwareController(core, {
workspaces: store,
stateFor: () => { throw new Error('saving enhancement must not load or clear session state'); },
...extra,
});
}
test('old workspace files remain untouched and default enhancement off', async (t) => {
const { directory, path } = await fixture(t);
const original = JSON.stringify({ version: 1, workspaces: { old_bot: directory }, agentPresets: { old_bot: 'preset-one' } });
await writeFile(path, original);
const store = await new BotWorkspaceStore(path).load();
assert.equal(store.contextEnhancementFor('old_bot'), DEFAULT_CONTEXT_ENHANCEMENT_CONFIG);
assert.equal(store.contextEnhancementFor('unknown_bot'), DEFAULT_CONTEXT_ENHANCEMENT_CONFIG);
assert.equal(store.workspaceFor('old_bot'), directory);
assert.equal(store.agentPresetFor('old_bot'), 'preset-one');
assert.equal(store.decorateStatus({ bots: [{ botId: 'old_bot' }] }).bots[0].contextEnhancement, DEFAULT_CONTEXT_ENHANCEMENT_CONFIG);
assert.equal(await readFile(path, 'utf8'), original, 'reading/decorating old config must not migrate/write it');
});
test('complete per-bot settings persist all switch combinations and explicit empty fields/text', async (t) => {
const { path, directory, store } = await fixture(t);
await store.ensure('bot_one', { defaultAgentPreset: 'preset-one' });
await store.ensure('bot_two');
const generation = store.generationFor('bot_one');
const incarnation = store.incarnationFor('bot_one');
for (const groupEnabled of [false, true]) {
for (const directEnabled of [false, true]) {
const selected = { groupEnabled, directEnabled, fields: [], guidance: '' };
const saved = await store.setContextEnhancement('bot_one', selected);
assert.deepEqual(saved, selected);
assert.equal(store.contextEnhancementFor('bot_one'), saved);
assert.equal(store.contextEnhancementFor('bot_two'), DEFAULT_CONTEXT_ENHANCEMENT_CONFIG);
const reloaded = await new BotWorkspaceStore(path).load();
assert.deepEqual(reloaded.contextEnhancementFor('bot_one'), selected);
}
}
assert.equal(store.workspaceFor('bot_one'), directory);
assert.equal(store.agentPresetFor('bot_one'), 'preset-one');
assert.equal(store.generationFor('bot_one'), generation);
assert.equal(store.incarnationFor('bot_one'), incarnation);
const document = JSON.parse(await readFile(path, 'utf8'));
assert.deepEqual(document.contextEnhancement.bot_one, enabled({ fields: [], guidance: '' }));
assert.equal(Object.hasOwn(document.contextEnhancement, 'bot_two'), false);
const alternate = join(directory, 'alternate');
await mkdir(alternate);
const before = store.contextEnhancementFor('bot_one');
await store.setWorkspace('bot_one', alternate);
await store.setAgentPreset('bot_one', 'preset-two');
assert.equal(store.contextEnhancementFor('bot_one'), before);
});
test('malformed enhancement entries fail off without poisoning valid workspace or preset data', async (t) => {
const { directory, path } = await fixture(t);
const malformed = [null, [], true, 'bad', {}, enabled({ fields: ['secret'] }), enabled({ directEnabled: 'yes' })];
for (const invalid of malformed) {
const original = {
version: 1,
workspaces: { broken_bot: directory, healthy_bot: directory },
agentPresets: { broken_bot: 'preset-one' },
contextEnhancement: { broken_bot: invalid, healthy_bot: enabled(), '../bad-id': enabled() },
};
await writeFile(path, JSON.stringify(original));
const store = await new BotWorkspaceStore(path).load();
assert.equal(store.contextEnhancementFor('broken_bot'), DEFAULT_CONTEXT_ENHANCEMENT_CONFIG);
assert.deepEqual(store.contextEnhancementFor('healthy_bot'), enabled());
assert.equal(store.workspaceFor('broken_bot'), directory);
assert.equal(store.agentPresetFor('broken_bot'), 'preset-one');
}
for (const contextEnhancement of [null, [], 'broken', true, 1]) {
await writeFile(path, JSON.stringify({ version: 1, workspaces: { bot_one: directory }, contextEnhancement }));
const store = await new BotWorkspaceStore(path).load();
assert.equal(store.contextEnhancementFor('bot_one'), DEFAULT_CONTEXT_ENHANCEMENT_CONFIG);
}
await writeFile(path, JSON.stringify({ version: 1, workspaces: { bot_one: 'relative' }, contextEnhancement: null }));
await assert.rejects(new BotWorkspaceStore(path).load(), /workspace config is invalid/);
});
test('pending and failed writes never publish an uncommitted context snapshot', async (t) => {
const { path, store } = await fixture(t);
await store.ensure('bot_one');
const first = await store.setContextEnhancement('bot_one', enabled());
const provider = { botId: 'bot_one', getSettings: () => store.contextEnhancementFor('bot_one') };
const queuedMessage = captureContextEnhancement(provider, 'group');
const diskBefore = await readFile(path, 'utf8');
await mkdir(`${path}.tmp`);
const save = store.setContextEnhancement('bot_one', enabled({ fields: [], guidance: '' }));
await Promise.resolve();
assert.equal(store.contextEnhancementFor('bot_one'), first, 'the persist await must not expose partial settings');
await assert.rejects(save);
assert.equal(store.contextEnhancementFor('bot_one'), first);
assert.equal(await readFile(path, 'utf8'), diskBefore);
await rm(`${path}.tmp`, { recursive: true });
await store.setContextEnhancement('bot_one', enabled({ groupEnabled: false, directEnabled: false, fields: [], guidance: '' }));
assert.equal(captureContextEnhancement(provider, 'group'), null);
const text = enhanceContextContent('queued text', queuedMessage, () => ({ channel: 'qq' }));
assert.match(text, /saved guidance/);
assert.match(text, /"botId":"bot_one"/);
assert.equal(Object.isFrozen(first), true);
assert.equal(Object.isFrozen(first.fields), true);
});
test('invalid saves reject atomically and deleted or rebound bot incarnations cannot receive stale updates', async (t) => {
const { path, store } = await fixture(t);
await store.ensure('bot_one');
const initial = await store.setContextEnhancement('bot_one', enabled());
const diskBefore = await readFile(path, 'utf8');
await assert.rejects(store.setContextEnhancement('bot_one', enabled({ fields: ['token'] })), { code: 'context-enhancement-invalid' });
assert.equal(store.contextEnhancementFor('bot_one'), initial);
assert.equal(await readFile(path, 'utf8'), diskBefore);
const oldIncarnation = store.incarnationFor('bot_one');
await store.remove('bot_one');
await store.ensure('bot_one');
await assert.rejects(store.setContextEnhancement('bot_one', enabled(), { incarnation: oldIncarnation }), { code: 'workspace-bot-not-found' });
await assert.rejects(store.setContextEnhancement('bot_one', enabled(), { incarnation: null }), { code: 'workspace-bot-not-found' });
await assert.rejects(store.setContextEnhancement('missing_bot', enabled()), { code: 'workspace-bot-not-found' });
assert.equal(store.contextEnhancementFor('bot_one'), DEFAULT_CONTEXT_ENHANCEMENT_CONFIG);
});
test('context update captures incarnation before waiting on controller status', async (t) => {
const { store } = await fixture(t);
await store.ensure('bot_one');
const statusStarted = deferred();
const statusResult = deferred();
const controller = awareController(store, {
async status() { statusStarted.resolve(); return statusResult.promise; },
});
const pending = controller.updateContextEnhancement('bot_one', enabled());
await statusStarted.promise;
await store.remove('bot_one');
await store.ensure('bot_one');
statusResult.resolve({ bots: [{ botId: 'bot_one' }] });
await assert.rejects(pending, { code: 'workspace-bot-not-found' });
assert.equal(store.contextEnhancementFor('bot_one'), DEFAULT_CONTEXT_ENHANCEMENT_CONFIG);
});
test('removal abort preserves settings, committed deletion removes them, and reconciliation cleans orphans', async (t) => {
const { directory, path, store } = await fixture(t);
await store.ensure('bot_one');
await store.ensure('bot_two');
const saved = await store.setContextEnhancement('bot_one', enabled({ fields: [], guidance: '' }));
const removal = await store.beginRemoval('bot_one');
await store.abortRemoval(removal);
assert.equal(store.contextEnhancementFor('bot_one'), saved);
const observed = observeBotWorkspaceRemovals({ async remove(botId) { return { botId }; } }, { workspaces: store });
await observed.remove('bot_one');
assert.equal(store.contextEnhancementFor('bot_one'), DEFAULT_CONTEXT_ENHANCEMENT_CONFIG);
const remaining = JSON.parse(await readFile(path, 'utf8'));
assert.equal(remaining.contextEnhancement, undefined);
assert.deepEqual(remaining.workspaces, { bot_two: directory });
await writeFile(path, JSON.stringify({
version: 1, workspaces: { bot_two: directory }, contextEnhancement: { orphan_bot: enabled() },
}));
const reloaded = await new BotWorkspaceStore(path).load();
await reloaded.reconcile(['bot_two']);
assert.equal(JSON.parse(await readFile(path, 'utf8')).contextEnhancement, undefined);
await reloaded.remove('bot_two');
await assert.rejects(readFile(path), { code: 'ENOENT' });
});
test('failed cleanup retires context settings and startup reconciliation heals stale disk state', async (t) => {
const { path, store } = await fixture(t);
await store.ensure('bot_one');
await store.ensure('bot_two');
await store.setContextEnhancement('bot_one', enabled());
await mkdir(`${path}.tmp`);
await assert.rejects(store.remove('bot_one'));
assert.equal(store.has('bot_one'), false);
assert.equal(store.contextEnhancementFor('bot_one'), DEFAULT_CONTEXT_ENHANCEMENT_CONFIG);
await rm(`${path}.tmp`, { recursive: true });
const reloaded = await new BotWorkspaceStore(path).load();
assert.equal(reloaded.contextEnhancementFor('bot_one').groupEnabled, true);
await reloaded.reconcile(['bot_two']);
assert.equal(reloaded.contextEnhancementFor('bot_one'), DEFAULT_CONTEXT_ENHANCEMENT_CONFIG);
assert.equal(JSON.parse(await readFile(path, 'utf8')).contextEnhancement, undefined);
});
for (const recovery of ['retry', 'restart']) {
test(`failed same-id rebind rolls back credentials and cannot revive old context after ${recovery}`, async (t) => {
const { directory, path, store } = await fixture(t);
const configPath = join(directory, 'bots.json');
const identityOptions = { channel: 'test', botPrefix: 'bot', tokenRefPrefix: 'BOT_TOKEN' };
const configStore = await new TokenBotConfigStore(configPath, identityOptions).load();
const credentials = new Map();
const runtimeStarts = [];
const controllers = [];
const createController = (workspaces, configs) => {
const core = new TokenBotController({
descriptor: { key: 'test', label: 'Test', connectionLabel: 'connection' },
configStore: observeBotWorkspaceRemovals(configs, { workspaces }),
credentials: {
async resolve(ref) { return credentials.has(ref) ? { value: credentials.get(ref) } : undefined; },
async set(ref, value) { credentials.set(ref, value); },
async unset(ref) { credentials.delete(ref); },
},
async inspectToken(token) { return { platformId: token.split(':')[0], name: 'Test bot' }; },
deriveIdentity: (id) => deriveTokenBotIdentity(id, identityOptions),
maskPlatformId: (id) => id,
async createRuntime({ botId }) {
runtimeStarts.push(botId);
await workspaces.ensure(botId);
return {
status: { ready: true, connectionState: 'connected', harnessReachable: true },
async start() {},
async stop() {},
};
},
logger: { warn() {} },
});
const controller = createWorkspaceAwareController(core, {
workspaces,
stateFor: async () => ({ async clearSessions() {} }),
});
controllers.push(controller);
return controller;
};
t.after(async () => { await Promise.all(controllers.map((controller) => controller.close())); });
let controller = createController(store, configStore);
await controller.bindCredentials({ token: 'one:original' });
await controller.bindCredentials({ token: 'two:original' });
const first = deriveTokenBotIdentity('one', identityOptions);
const second = deriveTokenBotIdentity('two', identityOptions);
const oldWorkspace = join(directory, 'old-workspace');
await mkdir(oldWorkspace);
await store.setWorkspace(first.botId, oldWorkspace);
await store.setAgentPreset(first.botId, 'old-preset');
await store.setContextEnhancement(first.botId, enabled());
const otherSettings = await store.setContextEnhancement(second.botId, enabled({ guidance: 'other bot' }));
const originalDocument = await readFile(path, 'utf8');
const otherGeneration = store.generationFor(second.botId);
// Only metadata persistence fails; the independent bot config remains writable.
await mkdir(`${path}.tmp`);
await controller.deleteBot(first.botId);
assert.equal(configStore.get(first.botId), null);
assert.equal(store.contextEnhancementFor(first.botId), DEFAULT_CONTEXT_ENHANCEMENT_CONFIG);
assert.equal(await readFile(path, 'utf8'), originalDocument);
const previousStarts = runtimeStarts.length;
await assert.rejects(controller.bindCredentials({ token: 'one:rebound' }));
assert.equal(configStore.get(first.botId), null, 'failed cleanup must precede the new config commit');
assert.equal(credentials.has(first.tokenRef), false, 'the existing config-save catch rolls back the new credential');
assert.equal(runtimeStarts.length, previousStarts, 'a blocked rebind must not attempt runtime startup');
assert.equal(await readFile(path, 'utf8'), originalDocument);
assert.equal(store.contextEnhancementFor(second.botId), otherSettings);
assert.equal(store.generationFor(second.botId), otherGeneration);
const reloadedConfigs = await new TokenBotConfigStore(configPath, identityOptions).load();
assert.equal(reloadedConfigs.get(first.botId), null, 'restart has no active config for the deleted id');
await rm(`${path}.tmp`, { recursive: true });
let currentStore = store;
if (recovery === 'restart') {
await controller.close();
currentStore = await new BotWorkspaceStore(path, { defaultWorkspace: directory }).load();
await currentStore.reconcile(reloadedConfigs.list().map((bot) => bot.botId));
assert.equal(currentStore.contextEnhancementFor(first.botId), DEFAULT_CONTEXT_ENHANCEMENT_CONFIG);
controller = createController(currentStore, reloadedConfigs);
}
const rebound = await controller.bindCredentials({ token: 'one:rebound' });
assert.equal(rebound.bots.find((bot) => bot.botId === first.botId).connected, true);
assert.equal(currentStore.workspaceFor(first.botId), directory);
assert.equal(currentStore.agentPresetFor(first.botId), null);
assert.equal(currentStore.contextEnhancementFor(first.botId), DEFAULT_CONTEXT_ENHANCEMENT_CONFIG);
assert.equal(credentials.get(first.tokenRef), 'one:rebound');
const reloaded = await new BotWorkspaceStore(path, { defaultWorkspace: directory }).load();
assert.equal(reloaded.workspaceFor(first.botId), directory);
assert.equal(reloaded.agentPresetFor(first.botId), null);
assert.equal(reloaded.contextEnhancementFor(first.botId), DEFAULT_CONTEXT_ENHANCEMENT_CONFIG);
assert.deepEqual(reloaded.contextEnhancementFor(second.botId), otherSettings);
});
}
for (const idKey of ['botId', 'id']) {
test(`${idKey} config-save observation retries only dirty IDs and preserves normal save timing/results`, async (t) => {
const { path, store } = await fixture(t);
await store.ensure('bot_one');
await store.ensure('bot_two');
await store.setContextEnhancement('bot_one', enabled());
const saveMethod = idKey === 'id' ? 'saveBot' : 'save';
const method = idKey === 'id' ? 'removeBot' : 'remove';
const result = Promise.resolve('saved');
const calls = [];
const target = {
[saveMethod](config) { assert.equal(this, target); calls.push(config); return result; },
async [method](botId) { return { [idKey]: botId }; },
};
const observed = observeBotWorkspaceRemovals(target, {
workspaces: store,
method,
botIdFromRemoved: (removed) => removed[idKey],
...(idKey === 'id' ? { saveMethod, botIdFromSave: (config) => config.id } : {}),
});
assert.equal(observed[saveMethod]({ [idKey]: 'bot_two' }), result);
assert.equal(calls.length, 1, 'normal save starts synchronously, without a new queue/await');
await mkdir(`${path}.tmp`);
await observed[method]('bot_one');
await assert.rejects(observed[saveMethod]({ [idKey]: 'bot_one' }));
assert.equal(calls.length, 1, 'failed metadata cleanup prevents config save');
assert.equal(observed[saveMethod]({ [idKey]: 'bot_two' }), result);
assert.equal(calls.length, 2, 'another bot is not blocked by this retired id');
await rm(`${path}.tmp`, { recursive: true });
assert.equal(await observed[saveMethod]({ [idKey]: 'bot_one' }), 'saved');
assert.equal(calls.length, 3);
const document = JSON.parse(await readFile(path, 'utf8'));
assert.equal(Object.hasOwn(document.workspaces, 'bot_one'), false);
assert.equal(Object.hasOwn(document.workspaces, 'bot_two'), true);
assert.equal(document.contextEnhancement, undefined);
});
}
test('context save performs no session/lifecycle work and no fallible status/catalog I/O after commit', async (t) => {
const { store } = await fixture(t);
await store.ensure('bot_one');
let statusReads = 0;
let catalogReads = 0;
const controller = awareController(store, {
status() {
statusReads += 1;
assert.equal(statusReads, 1, 'must not make a second status call after persistence');
return { bots: [{ botId: 'bot_one' }] };
},
}, {
agentPresetCatalog() {
catalogReads += 1;
assert.equal(store.contextEnhancementFor('bot_one'), DEFAULT_CONTEXT_ENHANCEMENT_CONFIG);
return { items: [{ id: 'preset-one', label: 'One' }] };
},
});
const result = await controller.updateContextEnhancement('bot_one', enabled());
assert.deepEqual(result.bots[0].contextEnhancement, enabled());
assert.equal(result.agentPresetCatalog.items[0].id, 'preset-one');
assert.equal(catalogReads, 1);
const brokenCatalog = awareController(store, fakeController(['bot_one']), {
agentPresetCatalog() { throw new Error('catalog unavailable'); },
});
await assert.rejects(brokenCatalog.updateContextEnhancement('bot_one', enabled({ fields: [] })), /catalog unavailable/);
assert.deepEqual(store.contextEnhancementFor('bot_one'), enabled());
});
test('shared context RPC payload validator rejects unknown keys and incomplete settings', () => {
assert.equal(validContextEnhancementPayload({ botId: 'bot_one', config: enabled() }), true);
for (const payload of [
null, [], {}, { botId: 'bot_one' }, { botId: '../bot_one', config: enabled() },
{ botId: 'bot_one', config: enabled(), token: 'no' },
{ botId: 'bot_one', config: { groupEnabled: true } },
{ botId: 'bot_one', config: enabled({ fields: ['password'] }) },
]) assert.equal(validContextEnhancementPayload(payload), false);
});
for (const [channel, createHandler, endpoints] of CHANNELS.filter(([name]) => (
!['slack', 'telegram', 'discord'].includes(name)
))) {
test(`${channel} QR/status projection failure happens before a context save commits`, async (t) => {
const { path, store } = await fixture(t);
const botId = `${channel}_projection`;
await store.ensure(botId);
const previous = store.contextEnhancementFor(botId);
const diskBefore = await readFile(path, 'utf8');
const core = fakeController([botId]);
core.status = () => ({
bots: [{ botId, configured: true }],
provisioning: {
verificationUrl: 'https://example.test/qr', qrValue: 'whatsapp-qr',
},
registration: {
state: 'qr_ready', attempt: 1, qrCodeUrl: 'https://accounts.feishu.cn/fixture',
},
});
let projections = 0;
const handler = createHandler(awareController(store, core), {
async encodeQr() {
projections += 1;
assert.equal(store.contextEnhancementFor(botId), previous);
throw new Error('QR rendering unavailable');
},
});
const result = await handler(endpoints.setContextEnhancement, { botId, config: enabled() });
assert.equal(result.ok, false);
assert.equal(projections, 1);
assert.equal(store.contextEnhancementFor(botId), previous);
assert.equal(await readFile(path, 'utf8'), diskBefore);
});
}
for (const [channel, createHandler, endpoints] of CHANNELS) {
test(`${channel} context RPC returns its complete status, preserves empty settings and rejects invalid saves`, async (t) => {
const { path, store } = await fixture(t);
const botId = `${channel}_one`;
const otherBotId = `${channel}_two`;
await store.ensure(botId);
await store.ensure(otherBotId);
const controller = awareController(store, fakeController([botId, otherBotId]));
const handler = createHandler(controller);
assert.equal(endpoints.setContextEnhancement, SET_CONTEXT_ENHANCEMENT_ENDPOINT);
const before = await handler(endpoints.status, {});
assert.equal(before.ok, true);
assert.deepEqual(before.value.bots[0].contextEnhancement, DEFAULT_CONTEXT_ENHANCEMENT_CONFIG);
const selected = enabled({ fields: ['botId', 'channel', 'botId'], guidance: ' \n ' });
const saved = await handler(endpoints.setContextEnhancement, { botId, config: selected });
assert.equal(saved.ok, true);
assert.equal(saved.value.bots.length, 2);
assert.deepEqual(saved.value.bots[0].contextEnhancement, enabled({ fields: ['channel', 'botId'], guidance: '' }));
assert.deepEqual(saved.value.bots[1].contextEnhancement, DEFAULT_CONTEXT_ENHANCEMENT_CONFIG);
const original = store.contextEnhancementFor(botId);
const diskBefore = await readFile(path, 'utf8');
for (const config of [enabled({ groupEnabled: 'yes' }), enabled({ fields: ['secret'] }), enabled({ guidance: 'x'.repeat(8001) })]) {
const failed = await handler(endpoints.setContextEnhancement, { botId, config });
assert.equal(failed.ok, false);
assert.equal(failed.error.code, 'bad-request');
assert.equal(store.contextEnhancementFor(botId), original);
}
assert.equal(await readFile(path, 'utf8'), diskBefore);
const missing = await handler(endpoints.setContextEnhancement, { botId: 'missing_bot', config: enabled() });
assert.equal(missing.ok, false);
assert.equal(missing.error.code, 'workspace-bot-not-found');
const cancelled = await handler(endpoints.setContextEnhancement, { botId, config: enabled() }, AbortSignal.abort());
assert.equal(cancelled.ok, false);
assert.equal(cancelled.error.code, 'cancelled');
assert.equal(store.contextEnhancementFor(botId), original);
await mkdir(`${path}.tmp`);
const failedWrite = await handler(endpoints.setContextEnhancement, { botId, config: enabled({ fields: [] }) });
assert.equal(failedWrite.ok, false);
assert.equal(store.contextEnhancementFor(botId), original);
assert.equal(await readFile(path, 'utf8'), diskBefore);
await rm(`${path}.tmp`, { recursive: true });
const cleared = await handler(endpoints.setContextEnhancement, { botId, config: enabled({ fields: [], guidance: '' }) });
assert.equal(cleared.ok, true);
assert.deepEqual(cleared.value.bots[0].contextEnhancement.fields, []);
assert.equal(cleared.value.bots[0].contextEnhancement.guidance, '');
});
}