audit follow-up: burn down lint hotspots and dedupe channel gating flows
This commit is contained in:
@@ -16,6 +16,45 @@ import type { GatewayRequest, GatewayResponse, GatewayError, GatewayEvent, Outbo
|
||||
import { ComponentRegistry } from '../../intents/index.js';
|
||||
import { RoutingPolicy } from '../../routing/index.js';
|
||||
|
||||
function asSessionHandlerSessionManager(value: unknown): Parameters<typeof createSessionHandlers>[0]['sessionManager'] {
|
||||
return value as Parameters<typeof createSessionHandlers>[0]['sessionManager'];
|
||||
}
|
||||
|
||||
function asToolRegistry(value: unknown): Parameters<typeof createToolHandlers>[0]['toolRegistry'] {
|
||||
return value as Parameters<typeof createToolHandlers>[0]['toolRegistry'];
|
||||
}
|
||||
|
||||
function asToolExecutor(value: unknown): Parameters<typeof createToolHandlers>[0]['toolExecutor'] {
|
||||
return value as Parameters<typeof createToolHandlers>[0]['toolExecutor'];
|
||||
}
|
||||
|
||||
function asSessionBridge(value: unknown): Parameters<typeof createAgentHandlers>[0]['sessionBridge'] {
|
||||
return value as Parameters<typeof createAgentHandlers>[0]['sessionBridge'];
|
||||
}
|
||||
|
||||
function asHistorySessionManager(value: unknown): Parameters<typeof createHistoryHandlers>[0]['sessionManager'] {
|
||||
return value as Parameters<typeof createHistoryHandlers>[0]['sessionManager'];
|
||||
}
|
||||
|
||||
function asConfigValue(value: unknown): Parameters<typeof createConfigHandlers>[0]['config'] {
|
||||
return value as Parameters<typeof createConfigHandlers>[0]['config'];
|
||||
}
|
||||
|
||||
function asRedactInput(value: unknown): Parameters<typeof redactConfig>[0] {
|
||||
return value as Parameters<typeof redactConfig>[0];
|
||||
}
|
||||
|
||||
function getPath(value: unknown, ...path: string[]): unknown {
|
||||
let current: unknown = value;
|
||||
for (const key of path) {
|
||||
if (!current || typeof current !== 'object') {
|
||||
return undefined;
|
||||
}
|
||||
current = (current as Record<string, unknown>)[key];
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
describe('system handlers', () => {
|
||||
const deps = {
|
||||
startTime: Date.now() - 60_000,
|
||||
@@ -45,21 +84,21 @@ describe('system handlers', () => {
|
||||
const req: GatewayRequest = { id: 2, method: 'system.services' };
|
||||
const result = await handlers['system.services'](req) as GatewayResponse;
|
||||
expect(result.id).toBe(2);
|
||||
expect((result.result as any).services).toEqual([]);
|
||||
expect(getPath(result.result, 'services')).toEqual([]);
|
||||
});
|
||||
|
||||
it('system.services returns services from getServices callback', async () => {
|
||||
const handlers = createSystemHandlers({
|
||||
...deps,
|
||||
getServices: () => ([
|
||||
getServices: () => [
|
||||
{ name: 'telegram', type: 'channel', status: 'connected', description: 'Telegram bot' },
|
||||
{ name: 'cron', type: 'automation', status: 'configured', description: 'Cron scheduler', itemCount: 2 },
|
||||
] as any),
|
||||
],
|
||||
});
|
||||
|
||||
const req: GatewayRequest = { id: 3, method: 'system.services' };
|
||||
const result = await handlers['system.services'](req) as GatewayResponse;
|
||||
expect((result.result as any).services).toEqual([
|
||||
expect(getPath(result.result, 'services')).toEqual([
|
||||
{ name: 'telegram', type: 'channel', status: 'connected', description: 'Telegram bot' },
|
||||
{ name: 'cron', type: 'automation', status: 'configured', description: 'Cron scheduler', itemCount: 2 },
|
||||
]);
|
||||
@@ -69,8 +108,8 @@ describe('system handlers', () => {
|
||||
const req: GatewayRequest = { id: 4, method: 'system.presence' };
|
||||
const result = await handlers['system.presence'](req) as GatewayResponse;
|
||||
expect(result.id).toBe(4);
|
||||
expect((result.result as any).presence).toEqual([]);
|
||||
expect((result.result as any).summary).toEqual({ total: 0, online: 0, offline: 0 });
|
||||
expect(getPath(result.result, 'presence')).toEqual([]);
|
||||
expect(getPath(result.result, 'summary')).toEqual({ total: 0, online: 0, offline: 0 });
|
||||
});
|
||||
|
||||
it('system.presence returns filtered presence entries', async () => {
|
||||
@@ -111,9 +150,10 @@ describe('system handlers', () => {
|
||||
params: { channel: 'telegram', status: 'online', limit: 10 },
|
||||
};
|
||||
const result = await handlers['system.presence'](req) as GatewayResponse;
|
||||
expect((result.result as any).presence).toHaveLength(1);
|
||||
expect((result.result as any).presence[0].channel).toBe('telegram');
|
||||
expect((result.result as any).summary).toEqual({ total: 1, online: 1, offline: 0 });
|
||||
const presence = getPath(result.result, 'presence') as Array<{ channel: string }>;
|
||||
expect(presence).toHaveLength(1);
|
||||
expect(presence[0]?.channel).toBe('telegram');
|
||||
expect(getPath(result.result, 'summary')).toEqual({ total: 1, online: 1, offline: 0 });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -197,7 +237,7 @@ describe('session handlers', () => {
|
||||
};
|
||||
|
||||
const handlers = createSessionHandlers({
|
||||
sessionManager: mockSessionManager as any,
|
||||
sessionManager: asSessionHandlerSessionManager(mockSessionManager),
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -274,8 +314,8 @@ describe('tool handlers', () => {
|
||||
};
|
||||
|
||||
const handlers = createToolHandlers({
|
||||
toolRegistry: mockRegistry as any,
|
||||
toolExecutor: mockExecutor as any,
|
||||
toolRegistry: asToolRegistry(mockRegistry),
|
||||
toolExecutor: asToolExecutor(mockExecutor),
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -334,7 +374,7 @@ describe('agent handlers', () => {
|
||||
const laneQueue = new LaneQueue();
|
||||
|
||||
const handlers = createAgentHandlers({
|
||||
sessionBridge: mockBridge as any,
|
||||
sessionBridge: asSessionBridge(mockBridge),
|
||||
laneQueue,
|
||||
});
|
||||
|
||||
@@ -357,7 +397,7 @@ describe('agent handlers', () => {
|
||||
expect(sent).toHaveLength(1);
|
||||
const doneEvent = sent[0] as GatewayEvent;
|
||||
expect(doneEvent.event).toBe('done');
|
||||
expect((doneEvent.data as any).content).toBe('response text');
|
||||
expect(getPath(doneEvent.data, 'content')).toBe('response text');
|
||||
});
|
||||
|
||||
it('agent.send passes attachments to agent.process', async () => {
|
||||
@@ -472,7 +512,7 @@ describe('agent handlers', () => {
|
||||
|
||||
const errorEvent = sent[0] as GatewayEvent;
|
||||
expect(errorEvent.event).toBe('error');
|
||||
expect((errorEvent.data as any).message).toBe('model failed');
|
||||
expect(getPath(errorEvent.data, 'message')).toBe('model failed');
|
||||
});
|
||||
|
||||
it('agent.send sets and cleans up tool use callback', async () => {
|
||||
@@ -501,8 +541,8 @@ describe('agent handlers', () => {
|
||||
const req: GatewayRequest = { id: 7, method: 'agent.cancel', params: { connectionId: 'conn-1' } };
|
||||
const result = await handlers['agent.cancel'](req) as GatewayResponse;
|
||||
|
||||
expect((result.result as any).cancelled).toBe(true);
|
||||
expect((result.result as any).message).toContain('Cancellation requested');
|
||||
expect(getPath(result.result, 'cancelled')).toBe(true);
|
||||
expect(getPath(result.result, 'message')).toContain('Cancellation requested');
|
||||
expect(mockBridge.cancel).toHaveBeenCalledWith('conn-1');
|
||||
});
|
||||
|
||||
@@ -511,8 +551,8 @@ describe('agent handlers', () => {
|
||||
const req: GatewayRequest = { id: 8, method: 'agent.cancel', params: { connectionId: 'conn-1' } };
|
||||
const result = await handlers['agent.cancel'](req) as GatewayResponse;
|
||||
|
||||
expect((result.result as any).cancelled).toBe(false);
|
||||
expect((result.result as any).message).toContain('No active operation');
|
||||
expect(getPath(result.result, 'cancelled')).toBe(false);
|
||||
expect(getPath(result.result, 'message')).toContain('No active operation');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -607,11 +647,12 @@ describe('routing handlers', () => {
|
||||
|
||||
describe('history handlers', () => {
|
||||
it('history.search returns ranked results', async () => {
|
||||
const historySessionManager = asHistorySessionManager({
|
||||
searchHistory: () => [{ sessionId: 'ws:test', messageId: 1, role: 'user', content: 'deploy', score: 0.9, createdAt: 123 }],
|
||||
reindexHistory: () => 0,
|
||||
});
|
||||
const handlers = createHistoryHandlers({
|
||||
sessionManager: {
|
||||
searchHistory: () => [{ sessionId: 'ws:test', messageId: 1, role: 'user', content: 'deploy', score: 0.9, createdAt: 123 }],
|
||||
reindexHistory: () => 0,
|
||||
} as any,
|
||||
sessionManager: historySessionManager,
|
||||
});
|
||||
|
||||
const req: GatewayRequest = { id: 13, method: 'history.search', params: { query: 'deploy' } };
|
||||
@@ -621,11 +662,12 @@ describe('history handlers', () => {
|
||||
});
|
||||
|
||||
it('history.reindex returns count', async () => {
|
||||
const historySessionManager = asHistorySessionManager({
|
||||
searchHistory: () => [],
|
||||
reindexHistory: () => 42,
|
||||
});
|
||||
const handlers = createHistoryHandlers({
|
||||
sessionManager: {
|
||||
searchHistory: () => [],
|
||||
reindexHistory: () => 42,
|
||||
} as any,
|
||||
sessionManager: historySessionManager,
|
||||
});
|
||||
|
||||
const req: GatewayRequest = { id: 14, method: 'history.reindex' };
|
||||
@@ -650,7 +692,7 @@ describe('system.restart handler', () => {
|
||||
const result = await handlers['system.restart'](req) as GatewayResponse;
|
||||
|
||||
expect(result.id).toBe(1);
|
||||
expect((result.result as any).restarting).toBe(true);
|
||||
expect(getPath(result.result, 'restarting')).toBe(true);
|
||||
|
||||
// Restart is called asynchronously via queueMicrotask
|
||||
await new Promise<void>((resolve) => queueMicrotask(resolve));
|
||||
@@ -691,21 +733,20 @@ describe('config handlers', () => {
|
||||
|
||||
it('config.get returns redacted config', async () => {
|
||||
const config = makeConfig();
|
||||
const handlers = createConfigHandlers({ config: config as any });
|
||||
const handlers = createConfigHandlers({ config: asConfigValue(config) });
|
||||
const req: GatewayRequest = { id: 1, method: 'config.get' };
|
||||
const result = await handlers['config.get'](req) as GatewayResponse;
|
||||
|
||||
const r = result.result as Record<string, any>;
|
||||
expect(r.telegram.bot_token).toBe('***');
|
||||
expect(r.models.default.api_key).toBe('***');
|
||||
expect(getPath(result.result, 'telegram', 'bot_token')).toBe('***');
|
||||
expect(getPath(result.result, 'models', 'default', 'api_key')).toBe('***');
|
||||
// Non-secret values are preserved
|
||||
expect(r.server.port).toBe(18800);
|
||||
expect(r.hooks.confirm).toEqual(['shell.exec']);
|
||||
expect(getPath(result.result, 'server', 'port')).toBe(18800);
|
||||
expect(getPath(result.result, 'hooks', 'confirm')).toEqual(['shell.exec']);
|
||||
});
|
||||
|
||||
it('config.patch applies valid patches', async () => {
|
||||
const config = makeConfig();
|
||||
const handlers = createConfigHandlers({ config: config as any });
|
||||
const handlers = createConfigHandlers({ config: asConfigValue(config) });
|
||||
const req: GatewayRequest = {
|
||||
id: 2,
|
||||
method: 'config.patch',
|
||||
@@ -729,7 +770,7 @@ describe('config handlers', () => {
|
||||
|
||||
it('config.patch rejects unknown keys', async () => {
|
||||
const config = makeConfig();
|
||||
const handlers = createConfigHandlers({ config: config as any });
|
||||
const handlers = createConfigHandlers({ config: asConfigValue(config) });
|
||||
const req: GatewayRequest = {
|
||||
id: 3,
|
||||
method: 'config.patch',
|
||||
@@ -750,7 +791,7 @@ describe('config handlers', () => {
|
||||
|
||||
it('config.patch rejects invalid value types', async () => {
|
||||
const config = makeConfig();
|
||||
const handlers = createConfigHandlers({ config: config as any });
|
||||
const handlers = createConfigHandlers({ config: asConfigValue(config) });
|
||||
const req: GatewayRequest = {
|
||||
id: 4,
|
||||
method: 'config.patch',
|
||||
@@ -771,7 +812,10 @@ describe('config handlers', () => {
|
||||
it('config.patch persists changes when persistence callback is provided', async () => {
|
||||
const config = makeConfig();
|
||||
const persist = vi.fn();
|
||||
const handlers = createConfigHandlers({ config: config as any, persistConfig: persist as any });
|
||||
const handlers = createConfigHandlers({
|
||||
config: asConfigValue(config),
|
||||
persistConfig: persist as () => Promise<void>,
|
||||
});
|
||||
const req: GatewayRequest = {
|
||||
id: 6,
|
||||
method: 'config.patch',
|
||||
@@ -791,7 +835,10 @@ describe('config handlers', () => {
|
||||
const config = makeConfig();
|
||||
const before = [...config.hooks.confirm];
|
||||
const persist = vi.fn().mockRejectedValue(new Error('disk full'));
|
||||
const handlers = createConfigHandlers({ config: config as any, persistConfig: persist as any });
|
||||
const handlers = createConfigHandlers({
|
||||
config: asConfigValue(config),
|
||||
persistConfig: persist as () => Promise<void>,
|
||||
});
|
||||
const req: GatewayRequest = {
|
||||
id: 7,
|
||||
method: 'config.patch',
|
||||
@@ -809,7 +856,7 @@ describe('config handlers', () => {
|
||||
|
||||
it('config.patch requires patches object', async () => {
|
||||
const config = makeConfig();
|
||||
const handlers = createConfigHandlers({ config: config as any });
|
||||
const handlers = createConfigHandlers({ config: asConfigValue(config) });
|
||||
const req: GatewayRequest = { id: 5, method: 'config.patch', params: {} };
|
||||
const result = await handlers['config.patch'](req) as GatewayError;
|
||||
|
||||
@@ -874,135 +921,122 @@ describe('redactConfig – comprehensive credential redaction', () => {
|
||||
}
|
||||
|
||||
it('redacts telegram.bot_token', () => {
|
||||
const result = redactConfig(makeFullConfig() as any);
|
||||
expect((result.telegram as any).bot_token).toBe('***');
|
||||
const result = redactConfig(asRedactInput(makeFullConfig()));
|
||||
expect(getPath(result, 'telegram', 'bot_token')).toBe('***');
|
||||
});
|
||||
|
||||
it('redacts discord.bot_token', () => {
|
||||
const result = redactConfig(makeFullConfig() as any);
|
||||
expect((result.discord as any).bot_token).toBe('***');
|
||||
const result = redactConfig(asRedactInput(makeFullConfig()));
|
||||
expect(getPath(result, 'discord', 'bot_token')).toBe('***');
|
||||
});
|
||||
|
||||
it('redacts slack.bot_token, app_token, and signing_secret', () => {
|
||||
const result = redactConfig(makeFullConfig() as any);
|
||||
const slack = result.slack as any;
|
||||
expect(slack.bot_token).toBe('***');
|
||||
expect(slack.app_token).toBe('***');
|
||||
expect(slack.signing_secret).toBe('***');
|
||||
const result = redactConfig(asRedactInput(makeFullConfig()));
|
||||
expect(getPath(result, 'slack', 'bot_token')).toBe('***');
|
||||
expect(getPath(result, 'slack', 'app_token')).toBe('***');
|
||||
expect(getPath(result, 'slack', 'signing_secret')).toBe('***');
|
||||
});
|
||||
|
||||
it('redacts matrix.access_token', () => {
|
||||
const result = redactConfig(makeFullConfig() as any);
|
||||
expect((result.matrix as any).access_token).toBe('***');
|
||||
const result = redactConfig(asRedactInput(makeFullConfig()));
|
||||
expect(getPath(result, 'matrix', 'access_token')).toBe('***');
|
||||
});
|
||||
|
||||
it('redacts server.token', () => {
|
||||
const result = redactConfig(makeFullConfig() as any);
|
||||
expect((result.server as any).token).toBe('***');
|
||||
const result = redactConfig(asRedactInput(makeFullConfig()));
|
||||
expect(getPath(result, 'server', 'token')).toBe('***');
|
||||
});
|
||||
|
||||
it('redacts model api_key and auth_token for all tiers', () => {
|
||||
const result = redactConfig(makeFullConfig() as any);
|
||||
const models = result.models as any;
|
||||
|
||||
expect(models.default.api_key).toBe('***');
|
||||
expect(models.default.auth_token).toBe('***');
|
||||
expect(models.fast.api_key).toBe('***');
|
||||
expect(models.complex.auth_token).toBe('***');
|
||||
const result = redactConfig(asRedactInput(makeFullConfig()));
|
||||
expect(getPath(result, 'models', 'default', 'api_key')).toBe('***');
|
||||
expect(getPath(result, 'models', 'default', 'auth_token')).toBe('***');
|
||||
expect(getPath(result, 'models', 'fast', 'api_key')).toBe('***');
|
||||
expect(getPath(result, 'models', 'complex', 'auth_token')).toBe('***');
|
||||
// local has no keys — should remain unchanged
|
||||
expect(models.local.api_key).toBeUndefined();
|
||||
expect(getPath(result, 'models', 'local', 'api_key')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('redacts model fallback api_key and auth_token', () => {
|
||||
const result = redactConfig(makeFullConfig() as any);
|
||||
const models = result.models as any;
|
||||
|
||||
expect(models.default.fallback.api_key).toBe('***');
|
||||
expect(models.default.fallback.auth_token).toBe('***');
|
||||
expect(models.fast.fallback.api_key).toBe('***');
|
||||
const result = redactConfig(asRedactInput(makeFullConfig()));
|
||||
expect(getPath(result, 'models', 'default', 'fallback', 'api_key')).toBe('***');
|
||||
expect(getPath(result, 'models', 'default', 'fallback', 'auth_token')).toBe('***');
|
||||
expect(getPath(result, 'models', 'fast', 'fallback', 'api_key')).toBe('***');
|
||||
});
|
||||
|
||||
it('redacts local_providers api_key, auth_token, and their fallbacks', () => {
|
||||
const result = redactConfig(makeFullConfig() as any);
|
||||
const ollama = (result.models as any).local_providers.ollama;
|
||||
|
||||
expect(ollama.api_key).toBe('***');
|
||||
expect(ollama.auth_token).toBe('***');
|
||||
expect(ollama.fallback.api_key).toBe('***');
|
||||
const result = redactConfig(asRedactInput(makeFullConfig()));
|
||||
expect(getPath(result, 'models', 'local_providers', 'ollama', 'api_key')).toBe('***');
|
||||
expect(getPath(result, 'models', 'local_providers', 'ollama', 'auth_token')).toBe('***');
|
||||
expect(getPath(result, 'models', 'local_providers', 'ollama', 'fallback', 'api_key')).toBe('***');
|
||||
});
|
||||
|
||||
it('redacts web_search.api_key', () => {
|
||||
const result = redactConfig(makeFullConfig() as any);
|
||||
expect((result.web_search as any).api_key).toBe('***');
|
||||
const result = redactConfig(asRedactInput(makeFullConfig()));
|
||||
expect(getPath(result, 'web_search', 'api_key')).toBe('***');
|
||||
});
|
||||
|
||||
it('redacts audio.transcription_api_key', () => {
|
||||
const result = redactConfig(makeFullConfig() as any);
|
||||
expect((result.audio as any).transcription_api_key).toBe('***');
|
||||
const result = redactConfig(asRedactInput(makeFullConfig()));
|
||||
expect(getPath(result, 'audio', 'transcription_api_key')).toBe('***');
|
||||
});
|
||||
|
||||
it('redacts memory.embedding.api_key', () => {
|
||||
const result = redactConfig(makeFullConfig() as any);
|
||||
expect((result.memory as any).embedding.api_key).toBe('***');
|
||||
const result = redactConfig(asRedactInput(makeFullConfig()));
|
||||
expect(getPath(result, 'memory', 'embedding', 'api_key')).toBe('***');
|
||||
});
|
||||
|
||||
it('redacts automation webhook secrets', () => {
|
||||
const result = redactConfig(makeFullConfig() as any);
|
||||
const webhooks = (result.automation as any).webhooks;
|
||||
|
||||
expect(webhooks[0].secret).toBe('***');
|
||||
expect(webhooks[1].secret).toBe('***');
|
||||
const result = redactConfig(asRedactInput(makeFullConfig()));
|
||||
expect(getPath(result, 'automation', 'webhooks', '0', 'secret')).toBe('***');
|
||||
expect(getPath(result, 'automation', 'webhooks', '1', 'secret')).toBe('***');
|
||||
// Webhook without a secret should remain unaffected
|
||||
expect(webhooks[2].secret).toBeUndefined();
|
||||
expect(getPath(result, 'automation', 'webhooks', '2', 'secret')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('redacts automation gmail credentials_file and token_file', () => {
|
||||
const result = redactConfig(makeFullConfig() as any);
|
||||
const gmail = (result.automation as any).gmail;
|
||||
|
||||
expect(gmail.credentials_file).toBe('***');
|
||||
expect(gmail.token_file).toBe('***');
|
||||
const result = redactConfig(asRedactInput(makeFullConfig()));
|
||||
expect(getPath(result, 'automation', 'gmail', 'credentials_file')).toBe('***');
|
||||
expect(getPath(result, 'automation', 'gmail', 'token_file')).toBe('***');
|
||||
});
|
||||
|
||||
it('redacts all MCP server env vars', () => {
|
||||
const result = redactConfig(makeFullConfig() as any);
|
||||
const servers = (result.mcp as any).servers;
|
||||
|
||||
expect(servers[0].env.API_KEY).toBe('***');
|
||||
expect(servers[0].env.DATABASE_URL).toBe('***');
|
||||
const result = redactConfig(asRedactInput(makeFullConfig()));
|
||||
expect(getPath(result, 'mcp', 'servers', '0', 'env', 'API_KEY')).toBe('***');
|
||||
expect(getPath(result, 'mcp', 'servers', '0', 'env', 'DATABASE_URL')).toBe('***');
|
||||
// Server without env should be unaffected
|
||||
expect(servers[1].env).toBeUndefined();
|
||||
expect(getPath(result, 'mcp', 'servers', '1', 'env')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('preserves non-secret fields', () => {
|
||||
const result = redactConfig(makeFullConfig() as any);
|
||||
const result = redactConfig(asRedactInput(makeFullConfig()));
|
||||
|
||||
// telegram
|
||||
expect((result.telegram as any).allowed_chat_ids).toEqual([1]);
|
||||
expect((result.telegram as any).require_mention).toBe(true);
|
||||
expect(getPath(result, 'telegram', 'allowed_chat_ids')).toEqual([1]);
|
||||
expect(getPath(result, 'telegram', 'require_mention')).toBe(true);
|
||||
// discord
|
||||
expect((result.discord as any).allowed_guild_ids).toEqual(['g1']);
|
||||
expect(getPath(result, 'discord', 'allowed_guild_ids')).toEqual(['g1']);
|
||||
// slack
|
||||
expect((result.slack as any).allowed_channel_ids).toEqual([]);
|
||||
expect(getPath(result, 'slack', 'allowed_channel_ids')).toEqual([]);
|
||||
// server
|
||||
expect((result.server as any).port).toBe(18800);
|
||||
expect((result.server as any).tailscale).toBeDefined();
|
||||
expect(getPath(result, 'server', 'port')).toBe(18800);
|
||||
expect(getPath(result, 'server', 'tailscale')).toBeDefined();
|
||||
// models
|
||||
expect((result.models as any).default.provider).toBe('anthropic');
|
||||
expect((result.models as any).default.model).toBe('claude');
|
||||
expect((result.models as any).fallback_chain).toEqual(['anthropic']);
|
||||
expect(getPath(result, 'models', 'default', 'provider')).toBe('anthropic');
|
||||
expect(getPath(result, 'models', 'default', 'model')).toBe('claude');
|
||||
expect(getPath(result, 'models', 'fallback_chain')).toEqual(['anthropic']);
|
||||
// web_search
|
||||
expect((result.web_search as any).provider).toBe('brave');
|
||||
expect((result.web_search as any).max_results).toBe(5);
|
||||
expect(getPath(result, 'web_search', 'provider')).toBe('brave');
|
||||
expect(getPath(result, 'web_search', 'max_results')).toBe(5);
|
||||
// audio
|
||||
expect((result.audio as any).transcription_model).toBe('whisper-1');
|
||||
expect(getPath(result, 'audio', 'transcription_model')).toBe('whisper-1');
|
||||
// memory
|
||||
expect((result.memory as any).embedding.model).toBe('text-embedding-3-small');
|
||||
expect(getPath(result, 'memory', 'embedding', 'model')).toBe('text-embedding-3-small');
|
||||
// hooks
|
||||
expect((result.hooks as any).confirm).toEqual(['shell.exec']);
|
||||
expect(getPath(result, 'hooks', 'confirm')).toEqual(['shell.exec']);
|
||||
// mcp
|
||||
expect((result.mcp as any).servers[0].name).toBe('my-server');
|
||||
expect((result.mcp as any).servers[0].command).toBe('node');
|
||||
expect(getPath(result, 'mcp', 'servers', '0', 'name')).toBe('my-server');
|
||||
expect(getPath(result, 'mcp', 'servers', '0', 'command')).toBe('node');
|
||||
});
|
||||
|
||||
it('handles missing optional sections gracefully', () => {
|
||||
@@ -1013,8 +1047,8 @@ describe('redactConfig – comprehensive credential redaction', () => {
|
||||
hooks: { confirm: [], log: [], silent: [] },
|
||||
};
|
||||
// Should not throw even when discord, slack, automation, mcp, etc. are absent
|
||||
const result = redactConfig(minimal as any);
|
||||
expect((result.telegram as any).bot_token).toBe('***');
|
||||
const result = redactConfig(asRedactInput(minimal));
|
||||
expect(getPath(result, 'telegram', 'bot_token')).toBe('***');
|
||||
expect(result.discord).toBeUndefined();
|
||||
expect(result.slack).toBeUndefined();
|
||||
expect(result.automation).toBeUndefined();
|
||||
@@ -1022,7 +1056,7 @@ describe('redactConfig – comprehensive credential redaction', () => {
|
||||
|
||||
it('does not mutate the original config object', () => {
|
||||
const config = makeFullConfig();
|
||||
redactConfig(config as any);
|
||||
redactConfig(asRedactInput(config));
|
||||
// Original secrets should still be intact
|
||||
expect(config.telegram.bot_token).toBe('tg-secret');
|
||||
expect(config.models.default.api_key).toBe('sk-def');
|
||||
|
||||
Reference in New Issue
Block a user