Files
memind/llm-providers.test.mjs
T
2026-07-02 09:42:08 +08:00

796 lines
25 KiB
JavaScript

import assert from 'node:assert/strict';
import test from 'node:test';
import {
createLlmProviderService,
CUSTOM_PROVIDER_ID,
buildExecutorLaunchPlan,
decryptSecret,
encryptSecret,
LLM_PROVIDER_CATALOG,
LOCAL_LLM_FALLBACK,
maskApiKey,
normalizeApiUrl,
resolveApiBaseUrl,
parseModelList,
resolveChatCompletionsUrl,
testLocalLlmConnection,
testRelayConnection,
syncProfileToGoosed,
} from './llm-providers.mjs';
test('encryptSecret round-trips with derived key', () => {
const encrypted = encryptSecret('sk-test-key-12345678', 'unit-test-secret');
const plain = decryptSecret(encrypted, 'unit-test-secret');
assert.equal(plain, 'sk-test-key-12345678');
});
test('parseModelList accepts comma and newline separated values', () => {
assert.deepEqual(parseModelList('qwen2.5:3b, llama3.2:1b\nmistral'), [
'qwen2.5:3b',
'llama3.2:1b',
'mistral',
]);
});
test('normalizeApiUrl adds http scheme', () => {
assert.equal(
normalizeApiUrl('127.0.0.1:18300/relay/buyer/v1/chat/completions'),
'http://127.0.0.1:18300/relay/buyer/v1/chat/completions',
);
});
test('maskApiKey hides middle segment', () => {
const masked = maskApiKey('sk-1234567890abcdef');
assert.ok(masked.startsWith('sk-1'));
assert.ok(masked.endsWith('cdef'));
assert.ok(masked.includes('*'));
});
test('createKey selects first profile and syncs to goosed', async () => {
const rows = [];
let selectedId = null;
const syncCalls = [];
const pool = {
async query(sql, params = []) {
if (sql.includes('COUNT(*) AS total FROM h5_llm_provider_keys')) {
return [[{ total: rows.length }]];
}
if (sql.includes('UPDATE h5_llm_provider_keys SET is_selected = 0')) {
for (const row of rows) row.is_selected = 0;
return [[]];
}
if (sql.includes('INSERT INTO h5_llm_provider_keys')) {
const encrypted = {
ciphertext: params[9],
iv: params[10],
tag: params[11],
};
const row = {
id: params[0],
provider_id: params[1],
provider_kind: params[2],
api_url: params[3],
base_path: params[4],
models_json: params[5],
goosed_provider_id: null,
engine: params[6],
relay_provider: params[7],
name: params[8],
api_key_ciphertext: encrypted.ciphertext,
api_key_iv: encrypted.iv,
api_key_tag: encrypted.tag,
default_model: params[12],
status: 'active',
is_selected: params[13],
created_at: params[14],
updated_at: params[15],
};
rows.push(row);
if (row.is_selected) selectedId = row.id;
return [[]];
}
if (sql.includes('SELECT * FROM h5_llm_provider_keys WHERE id = ?')) {
return [[rows.find((row) => row.id === params[0]) ?? null].filter(Boolean)];
}
if (sql.includes('ORDER BY is_selected DESC')) {
return [rows];
}
if (sql.includes('SELECT id FROM h5_llm_provider_keys WHERE name = ?')) {
return [[rows.find((row) => row.name === params[0])].filter(Boolean)];
}
if (sql.includes('SET goosed_provider_id = ?')) {
const target = rows.find((row) => row.id === params[3]);
if (target) {
target.goosed_provider_id = params[0];
target.provider_id = params[1];
}
return [[]];
}
throw new Error(`Unexpected SQL: ${sql}`);
},
};
const mockFetch = async (url, init) => {
syncCalls.push({ url: String(url), body: init?.body ? JSON.parse(init.body) : null });
if (String(url).includes('/config/custom-providers') && init?.method === 'POST') {
return { ok: true, json: async () => ({ provider_name: 'relay_ollama' }) };
}
return { ok: true, text: async () => '' };
};
const service = createLlmProviderService(pool, {
apiTarget: 'https://127.0.0.1:18006',
apiSecret: 'secret',
encryptionKey: 'unit-test-secret',
apiFetchImpl: mockFetch,
});
const result = await service.createKey({
providerId: 'custom_deepseek',
name: 'DeepSeek 主账号',
apiKey: 'sk-deepseek-test',
defaultModel: 'deepseek-chat',
});
assert.equal(result.ok, true);
assert.equal(result.key.isSelected, true);
assert.equal(rows.length, 1);
assert.equal(selectedId, rows[0].id);
assert.equal(syncCalls.filter((item) => item.body?.key === 'DEEPSEEK_API_KEY').length, 1);
});
test('create custom relay profile registers goosed custom provider', async () => {
const rows = [];
const mockFetch = async (url, init) => {
if (String(url).includes('/config/custom-providers') && init?.method === 'POST') {
const body = JSON.parse(init.body);
assert.equal(body.api_url, 'http://127.0.0.1:18300/relay/buyer/v1/chat/completions');
assert.deepEqual(body.models, ['qwen2.5:3b', 'llama3.2:1b']);
assert.equal(body.headers['X-Provider'], 'ollama');
return { ok: true, json: async () => ({ provider_name: 'relay_buyer' }) };
}
return { ok: true, text: async () => '' };
};
const pool = {
async query(sql, params = []) {
if (sql.includes('COUNT(*) AS total FROM h5_llm_provider_keys')) return [[{ total: 0 }]];
if (sql.includes('UPDATE h5_llm_provider_keys SET is_selected = 0')) return [[]];
if (sql.includes('INSERT INTO h5_llm_provider_keys')) {
rows.push({
id: params[0],
provider_id: params[1],
provider_kind: params[2],
api_url: params[3],
base_path: params[4],
models_json: params[5],
goosed_provider_id: null,
engine: params[6],
relay_provider: params[7],
name: params[8],
api_key_ciphertext: params[9],
api_key_iv: params[10],
api_key_tag: params[11],
default_model: params[12],
status: 'active',
is_selected: params[13],
created_at: params[14],
updated_at: params[15],
});
return [[]];
}
if (sql.includes('SELECT * FROM h5_llm_provider_keys WHERE id = ?')) {
return [[rows.find((row) => row.id === params[0]) ?? null].filter(Boolean)];
}
if (sql.includes('SELECT id FROM h5_llm_provider_keys WHERE name = ?')) return [[]];
if (sql.includes('SET goosed_provider_id = ?')) {
rows[0].goosed_provider_id = params[0];
rows[0].provider_id = params[1];
return [[]];
}
throw new Error(`Unexpected SQL: ${sql}`);
},
};
const service = createLlmProviderService(pool, {
apiTarget: 'https://127.0.0.1:18006',
apiSecret: 'secret',
encryptionKey: 'unit-test-secret',
apiFetchImpl: mockFetch,
});
const result = await service.createKey({
providerId: CUSTOM_PROVIDER_ID,
name: 'Relay Ollama',
apiKey: 'UqyHPKSSEZq0-oPnl8sru-7hZcJ2anPUL1yAVk866Vo',
apiUrl: 'http://127.0.0.1:18300/relay/buyer/v1/chat/completions',
models: ['qwen2.5:3b', 'llama3.2:1b'],
defaultModel: 'qwen2.5:3b',
relayProvider: 'ollama',
});
assert.equal(result.ok, true);
assert.equal(result.key.providerKind, 'custom');
assert.equal(rows[0].provider_kind, 'custom');
});
test('selectKey rejects disabled profile', async () => {
const row = {
id: 'key-1',
provider_id: 'custom_deepseek',
provider_kind: 'builtin',
name: 'disabled',
api_key_ciphertext: encryptSecret('sk-x', 'unit-test-secret').ciphertext,
api_key_iv: encryptSecret('sk-x', 'unit-test-secret').iv,
api_key_tag: encryptSecret('sk-x', 'unit-test-secret').tag,
default_model: 'deepseek-chat',
status: 'disabled',
is_selected: 0,
created_at: 1,
updated_at: 1,
};
const pool = {
async query(sql, params = []) {
if (sql.includes('SELECT * FROM h5_llm_provider_keys WHERE id = ?')) {
return [[row]];
}
throw new Error(`Unexpected SQL: ${sql}`);
},
};
const service = createLlmProviderService(pool, {
apiTarget: 'https://127.0.0.1:18006',
apiSecret: 'secret',
encryptionKey: 'unit-test-secret',
});
const result = await service.selectKey('key-1');
assert.equal(result.ok, false);
assert.match(result.message, /禁用/);
});
test('catalog includes custom provider template', () => {
const custom = LLM_PROVIDER_CATALOG.find((item) => item.id === CUSTOM_PROVIDER_ID);
assert.ok(custom);
assert.equal(custom.kind, 'custom');
});
test('listExecutorBindings returns all executor placeholders', async () => {
const pool = {
async query(sql) {
if (sql.includes('ORDER BY is_selected DESC')) return [[]];
if (sql.includes('FROM h5_llm_executor_bindings')) return [[]];
throw new Error(`Unexpected SQL: ${sql}`);
},
};
const service = createLlmProviderService(pool, {
apiTarget: 'https://127.0.0.1:18006',
apiSecret: 'secret',
encryptionKey: 'unit-test-secret',
});
const bindings = await service.listExecutorBindings();
assert.deepEqual(
bindings.map((binding) => binding.executor),
['goose', 'aider', 'openhands'],
);
assert.equal(bindings[0].enabled, false);
});
test('resolveChatCompletionsUrl keeps full completions path', () => {
assert.equal(
resolveChatCompletionsUrl('http://127.0.0.1:18300/relay/buyer/v1/chat/completions'),
'http://127.0.0.1:18300/relay/buyer/v1/chat/completions',
);
});
test('resolveApiBaseUrl strips chat completions suffix', () => {
assert.equal(
resolveApiBaseUrl('http://127.0.0.1:18300/relay/buyer/v1/chat/completions'),
'http://127.0.0.1:18300/relay/buyer/v1',
);
});
test('testKey checks builtin DeepSeek through OpenAI compatible endpoint', async () => {
const encrypted = encryptSecret('sk-deepseek-test', 'unit-test-secret');
const row = {
id: 'key-deepseek',
provider_id: 'custom_deepseek',
provider_kind: 'builtin',
name: 'DeepSeek',
api_key_ciphertext: encrypted.ciphertext,
api_key_iv: encrypted.iv,
api_key_tag: encrypted.tag,
default_model: 'deepseek-chat',
status: 'active',
is_selected: 1,
created_at: 1,
updated_at: 1,
};
const pool = {
async query(sql, params = []) {
if (sql.includes('SELECT * FROM h5_llm_provider_keys WHERE id = ?')) {
assert.equal(params[0], 'key-deepseek');
return [[row]];
}
throw new Error(`Unexpected SQL: ${sql}`);
},
};
const calls = [];
const mockFetch = async (url, init) => {
calls.push({ url: String(url), init, body: JSON.parse(init.body) });
return {
ok: true,
text: async () => JSON.stringify({ choices: [{ message: { content: 'ok' } }] }),
};
};
const service = createLlmProviderService(pool, {
apiTarget: 'https://127.0.0.1:18006',
apiSecret: 'secret',
encryptionKey: 'unit-test-secret',
apiFetchImpl: mockFetch,
});
const result = await service.testKey('key-deepseek', 'deepseek-chat');
assert.equal(result.ok, true);
assert.equal(calls[0].url, 'https://api.deepseek.com/v1/chat/completions');
assert.equal(calls[0].init.headers.Authorization, 'Bearer sk-deepseek-test');
assert.equal(calls[0].body.model, 'deepseek-chat');
});
test('getExecutorRuntimeConfig resolves aider env from binding', async () => {
const encrypted = encryptSecret('sk-deepseek-test', 'unit-test-secret');
const keyRow = {
id: 'key-deepseek',
provider_id: 'custom_deepseek',
provider_kind: 'builtin',
name: 'DeepSeek 主账号',
api_key_ciphertext: encrypted.ciphertext,
api_key_iv: encrypted.iv,
api_key_tag: encrypted.tag,
default_model: 'deepseek-chat',
status: 'active',
is_selected: 1,
created_at: 1,
updated_at: 1,
};
const bindingRow = {
id: 'binding-aider',
executor: 'aider',
purpose: 'default',
provider_key_id: 'key-deepseek',
model: 'deepseek-reasoner',
enabled: 1,
created_at: 1,
updated_at: 1,
};
const pool = {
async query(sql, params = []) {
if (sql.includes('SELECT * FROM h5_llm_executor_bindings')) return [[bindingRow]];
if (sql.includes('SELECT * FROM h5_llm_provider_keys WHERE id = ?')) {
assert.equal(params[0], 'key-deepseek');
return [[keyRow]];
}
throw new Error(`Unexpected SQL: ${sql}`);
},
};
const service = createLlmProviderService(pool, {
apiTarget: 'https://127.0.0.1:18006',
apiSecret: 'secret',
encryptionKey: 'unit-test-secret',
});
const runtime = await service.getExecutorRuntimeConfig('aider');
assert.equal(runtime.ok, true);
assert.equal(runtime.model, 'deepseek-reasoner');
assert.equal(runtime.env.AIDER_MODEL, 'deepseek/deepseek-reasoner');
assert.equal(runtime.env.DEEPSEEK_API_KEY, '[hidden]');
});
test('buildExecutorLaunchPlan creates openhands headless command', () => {
const plan = buildExecutorLaunchPlan({
ok: true,
executor: 'openhands',
executorLabel: 'OpenHands',
purpose: 'default',
providerName: 'DeepSeek 主账号',
model: 'deepseek-reasoner',
env: { LLM_MODEL: 'deepseek-reasoner', LLM_API_KEY: '[hidden]' },
}, {
mode: 'headless',
cwd: '/repo/memind',
instruction: 'add search',
});
assert.equal(plan.ok, true);
assert.equal(plan.command, 'openhands');
assert.deepEqual(plan.args.slice(0, 3), ['--headless', '--json', '--override-with-envs']);
assert.equal(plan.cwd, '/repo/memind');
assert.ok(plan.args.includes('--task'));
});
test('buildExecutorLaunchPlan creates openhands serve command', () => {
const plan = buildExecutorLaunchPlan({
ok: true,
executor: 'openhands',
executorLabel: 'OpenHands',
purpose: 'default',
providerName: 'DeepSeek 主账号',
model: 'deepseek-reasoner',
env: { LLM_MODEL: 'deepseek-reasoner', LLM_API_KEY: '[hidden]' },
}, {
mode: 'serve',
cwd: '/repo/memind',
});
assert.equal(plan.ok, true);
assert.equal(plan.command, 'openhands');
assert.deepEqual(plan.args, ['serve', '--mount-cwd']);
});
test('buildExecutorLaunchPlan adds aider one-shot message when provided', () => {
const plan = buildExecutorLaunchPlan({
ok: true,
executor: 'aider',
executorLabel: 'Aider',
purpose: 'default',
providerName: 'DeepSeek 主账号',
model: 'deepseek-chat',
env: {
AIDER_MODEL: 'deepseek/deepseek-chat',
OPENAI_API_KEY: '[hidden]',
TKMIND_EXECUTOR_PROVIDER: 'custom_deepseek',
},
}, {
cwd: '/repo/memind',
instruction: 'fix login button',
});
assert.equal(plan.ok, true);
assert.equal(plan.command, 'aider');
assert.deepEqual(plan.args.slice(0, 2), ['--model', 'deepseek/deepseek-chat']);
assert.ok(plan.args.includes('--message'));
assert.ok(plan.args.includes('fix login button'));
});
test('buildExecutorLaunchPlan rejects openhands headless without instruction', () => {
const plan = buildExecutorLaunchPlan({
ok: true,
executor: 'openhands',
executorLabel: 'OpenHands',
purpose: 'default',
providerName: 'DeepSeek 主账号',
model: 'deepseek-reasoner',
env: { LLM_MODEL: 'deepseek-reasoner', LLM_API_KEY: '[hidden]' },
}, {
mode: 'headless',
cwd: '/repo/memind',
});
assert.equal(plan.ok, false);
assert.match(plan.message ?? '', /instruction/);
});
test('launchExecutor rejects goose launch path', async () => {
const pool = {
async query() {
throw new Error('should not query for goose launch');
},
};
const service = createLlmProviderService(pool, {
apiTarget: 'https://127.0.0.1:18006',
apiSecret: 'secret',
encryptionKey: 'unit-test-secret',
});
const result = await service.launchExecutor('goose');
assert.equal(result.ok, false);
assert.match(result.message ?? '', /Goose/);
});
test('launchExecutor rejects openhands headless without instruction', async () => {
const pool = {
async query() {
throw new Error('should not query for validation failure');
},
};
const service = createLlmProviderService(pool, {
apiTarget: 'https://127.0.0.1:18006',
apiSecret: 'secret',
encryptionKey: 'unit-test-secret',
});
const result = await service.launchExecutor('openhands', { mode: 'headless' });
assert.equal(result.ok, false);
assert.match(result.message ?? '', /instruction/);
});
test('launchExecutor returns friendly error when command is missing', async () => {
const encryptedKey = encryptSecret('sk-x', 'unit-test-secret');
const keyRow = {
id: 'key-1',
provider_id: 'custom_deepseek',
provider_kind: 'builtin',
name: 'DeepSeek',
api_key_ciphertext: encryptedKey.ciphertext,
api_key_iv: encryptedKey.iv,
api_key_tag: encryptedKey.tag,
default_model: 'deepseek-chat',
status: 'active',
is_selected: 1,
created_at: 1,
updated_at: 1,
};
const bindingRow = {
id: 'binding-1',
executor: 'aider',
purpose: 'default',
provider_key_id: 'key-1',
model: 'deepseek-chat',
enabled: 1,
created_at: 1,
updated_at: 1,
};
const pool = {
async query(sql) {
if (sql.includes('SELECT * FROM h5_llm_executor_bindings WHERE executor = ? AND purpose = ? LIMIT 1')) {
return [[bindingRow]];
}
if (sql.includes('SELECT * FROM h5_llm_executor_bindings')) return [[]];
if (sql.includes('SELECT * FROM h5_llm_provider_keys WHERE id = ?')) {
return [[keyRow]];
}
throw new Error(`Unexpected SQL: ${sql}`);
},
};
const service = createLlmProviderService(pool, {
apiTarget: 'https://127.0.0.1:18006',
apiSecret: 'secret',
encryptionKey: 'unit-test-secret',
});
const oldAiderBin = process.env.AIDER_BIN;
process.env.AIDER_BIN = '/definitely/missing/aider';
try {
const result = await service.launchExecutor('aider', { instruction: 'hello' });
assert.equal(result.ok, false);
assert.match(result.message ?? '', /PATH|未安装/);
} finally {
if (oldAiderBin == null) delete process.env.AIDER_BIN;
else process.env.AIDER_BIN = oldAiderBin;
}
});
test('testRelayConnection surfaces 401 as token error', async () => {
const mockFetch = async () => ({
ok: false,
status: 401,
text: async () => '{}',
});
const result = await testRelayConnection(
{
apiUrl: 'http://127.0.0.1:18300/relay/buyer/v1/chat/completions',
apiKey: 'bad-token',
model: 'qwen2.5:3b',
relayProvider: 'ollama',
},
mockFetch,
);
assert.equal(result.ok, false);
assert.match(result.message ?? '', /401/);
assert.match(result.message ?? '', /Token/);
});
test('testRelayConnection parses OpenAI-style response', async () => {
const mockFetch = async () => ({
ok: true,
text: async () =>
JSON.stringify({
choices: [{ message: { content: 'Hi there' } }],
}),
});
const result = await testRelayConnection(
{
apiUrl: 'http://127.0.0.1:18300/relay/buyer/v1/chat/completions',
apiKey: 'token',
model: 'qwen2.5:3b',
relayProvider: 'ollama',
},
mockFetch,
);
assert.equal(result.ok, true);
assert.equal(result.reply, 'Hi there');
});
test('syncProfileToGoosed writes provider, model and secret keys for builtin', async () => {
const writes = [];
const mockFetch = async (_url, init) => {
writes.push(JSON.parse(init.body));
return { ok: true, text: async () => '' };
};
await syncProfileToGoosed(
'https://127.0.0.1:18006',
'secret',
{
providerKind: 'builtin',
providerId: 'custom_deepseek',
defaultModel: 'deepseek-chat',
apiKey: 'sk-test',
},
mockFetch,
);
assert.deepEqual(
writes.map((item) => item.key),
['DEEPSEEK_API_KEY', 'GOOSE_PROVIDER', 'GOOSE_MODEL', 'TKMIND_PROVIDER', 'TKMIND_MODEL'],
);
});
test('syncSelectedToGoosed writes selected provider to every configured target', async () => {
const encrypted = encryptSecret('sk-test', 'unit-test-secret');
const row = {
id: 'key-deepseek',
provider_id: 'custom_deepseek',
provider_kind: 'builtin',
name: 'DeepSeek',
api_url: null,
base_path: null,
models_json: JSON.stringify(['deepseek-chat']),
goosed_provider_id: null,
engine: 'openai',
relay_provider: null,
api_key_ciphertext: encrypted.ciphertext,
api_key_iv: encrypted.iv,
api_key_tag: encrypted.tag,
default_model: 'deepseek-chat',
status: 'active',
is_selected: 1,
created_at: 1,
updated_at: 1,
};
const pool = {
async query(sql) {
if (sql.includes('SELECT * FROM h5_llm_executor_bindings')) return [[]];
if (sql.includes('is_selected = 1')) return [[row]];
throw new Error(`Unexpected SQL: ${sql}`);
},
};
const calls = [];
const mockFetch = async (url, init) => {
calls.push({ url: String(url), body: JSON.parse(init.body) });
if (String(url).includes('/chat/completions')) {
return {
ok: true,
json: async () => ({ choices: [{ message: { content: 'ok' } }] }),
text: async () => JSON.stringify({ choices: [{ message: { content: 'ok' } }] }),
};
}
return { ok: true, text: async () => '' };
};
const service = createLlmProviderService(pool, {
apiTarget: 'https://127.0.0.1:18006',
apiTargets: ['https://127.0.0.1:18006', 'https://127.0.0.1:18007'],
apiSecret: 'secret',
encryptionKey: 'unit-test-secret',
apiFetchImpl: mockFetch,
});
const result = await service.syncSelectedToGoosed();
assert.equal(result.ok, true);
assert.deepEqual(result.targets, ['https://127.0.0.1:18006', 'https://127.0.0.1:18007']);
assert.deepEqual(
calls
.filter((call) => call.url.includes('/config/upsert') && call.body.key === 'GOOSE_PROVIDER')
.map((call) => new URL(call.url).port),
['18006', '18007'],
);
});
test('syncProfileToGoosed tolerates missing /config/upsert endpoint', async () => {
const mockFetch = async (_url, init) => {
if (String(_url).includes('/config/custom-providers')) {
return {
ok: true,
json: async () => ({ provider_name: 'custom_local_ollama_7b' }),
};
}
if (String(_url).includes('/config/upsert')) {
return { ok: false, status: 404, text: async () => '' };
}
return { ok: true, text: async () => '' };
};
const providerId = await syncProfileToGoosed(
'https://127.0.0.1:18006',
'secret',
{
providerKind: 'custom',
providerId: CUSTOM_PROVIDER_ID,
name: 'Local Ollama 7B',
apiUrl: LOCAL_LLM_FALLBACK.apiUrl,
apiKey: LOCAL_LLM_FALLBACK.apiKey,
models: [LOCAL_LLM_FALLBACK.model],
defaultModel: LOCAL_LLM_FALLBACK.model,
engine: 'openai',
},
mockFetch,
);
assert.equal(providerId, 'custom_local_ollama_7b');
});
test('LOCAL_LLM_FALLBACK defaults to local ollama 7b', () => {
assert.equal(LOCAL_LLM_FALLBACK.model, 'qwen2.5:3b');
assert.match(LOCAL_LLM_FALLBACK.apiUrl, /11434/);
assert.equal(LOCAL_LLM_FALLBACK.enabled, true);
});
test('syncSelectedToGoosed does not auto-fallback to local when selected relay is down', async () => {
const encrypted = encryptSecret('token', 'unit-test-secret');
const row = {
id: 'key-relay',
provider_id: '__custom__',
provider_kind: 'custom',
name: 'Relay Buyer Ollama',
api_url: 'http://127.0.0.1:18300/relay/buyer/v1/chat/completions',
base_path: null,
models_json: JSON.stringify(['qwen2.5:3b']),
goosed_provider_id: null,
engine: 'openai',
relay_provider: 'ollama',
api_key_ciphertext: encrypted.ciphertext,
api_key_iv: encrypted.iv,
api_key_tag: encrypted.tag,
default_model: 'qwen2.5:3b',
status: 'active',
is_selected: 1,
created_at: 1,
updated_at: 1,
};
const pool = {
async query(sql) {
if (sql.includes('SELECT * FROM h5_llm_executor_bindings')) return [[]];
if (sql.includes('is_selected = 1')) return [[row]];
throw new Error(`Unexpected SQL: ${sql}`);
},
};
const mockFetch = async () => {
throw Object.assign(new Error('connect ECONNREFUSED'), { code: 'ECONNREFUSED' });
};
const service = createLlmProviderService(pool, {
apiTarget: 'https://127.0.0.1:18006',
apiSecret: 'secret',
encryptionKey: 'unit-test-secret',
apiFetchImpl: mockFetch,
});
const result = await service.syncSelectedToGoosed();
assert.equal(result.ok, false);
assert.equal(result.synced, false);
assert.ok(result.message);
});
test('ensureBootstrapRelay keeps existing selected provider', async () => {
const rows = [
{
id: 'key-deepseek',
provider_id: 'custom_deepseek',
provider_kind: 'builtin',
name: 'DeepSeek',
status: 'active',
is_selected: 1,
updated_at: 1,
},
{
id: 'key-relay',
provider_id: 'custom_relay_buyer_ollama_27',
provider_kind: 'custom',
name: 'Relay Buyer Ollama',
status: 'active',
is_selected: 0,
updated_at: 1,
},
];
const pool = {
async query(sql, params = []) {
if (sql.includes('SELECT id FROM h5_llm_provider_keys WHERE name = ?')) {
return [[rows.find((row) => row.name === params[0])].filter(Boolean)];
}
if (sql.includes('UPDATE h5_llm_provider_keys SET is_selected')) {
throw new Error('bootstrap relay should not change the selected provider');
}
throw new Error(`Unexpected SQL: ${sql}`);
},
};
const service = createLlmProviderService(pool, {
apiTarget: 'https://127.0.0.1:18006',
apiSecret: 'secret',
encryptionKey: 'unit-test-secret',
});
const result = await service.ensureBootstrapRelay();
assert.equal(result.ok, true);
assert.equal(result.created, false);
assert.equal(rows.find((row) => row.id === 'key-deepseek').is_selected, 1);
assert.equal(rows.find((row) => row.id === 'key-relay').is_selected, 0);
});