8ccf2db05c
Memind CI / Test, build, and release guards (push) Failing after 3m18s
Map Page Data failure codes to Chinese remediation hints, trigger one-shot goosed repair for remediable cases, and recover poisoned thinking sessions. Route local DeepSeek through the no-think proxy via host.docker.internal so tool rounds no longer hit reasoning_content 400; document gate and case-study scenarios for event registration repair. Co-authored-by: Cursor <cursoragent@cursor.com>
1122 lines
37 KiB
JavaScript
1122 lines
37 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import {
|
|
createLlmProviderService,
|
|
CUSTOM_PROVIDER_ID,
|
|
MEMIND_DEEPSEEK_NO_THINK_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-v4-pro',
|
|
});
|
|
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('deleteKey treats a missing TKMind custom provider as already deleted', async () => {
|
|
const row = {
|
|
id: 'key-relay',
|
|
provider_id: CUSTOM_PROVIDER_ID,
|
|
provider_kind: 'custom',
|
|
goosed_provider_id: 'custom_relay_buyer_ollama',
|
|
name: 'Relay Buyer Ollama',
|
|
status: 'active',
|
|
is_selected: 0,
|
|
};
|
|
let deleted = false;
|
|
const pool = {
|
|
async query(sql, params = []) {
|
|
if (sql.includes('SELECT * FROM h5_llm_provider_keys WHERE id = ?')) {
|
|
return [[params[0] === row.id ? row : null].filter(Boolean)];
|
|
}
|
|
if (sql.includes('DELETE FROM h5_llm_provider_keys WHERE id = ?')) {
|
|
deleted = params[0] === row.id;
|
|
return [[]];
|
|
}
|
|
throw new Error(`Unexpected SQL: ${sql}`);
|
|
},
|
|
};
|
|
const mockFetch = async (_url, init = {}) => {
|
|
assert.equal(init.method, 'DELETE');
|
|
return {
|
|
ok: false,
|
|
status: 500,
|
|
text: async () => JSON.stringify({
|
|
message: 'Provider not found: custom_relay_buyer_ollama',
|
|
}),
|
|
};
|
|
};
|
|
const service = createLlmProviderService(pool, {
|
|
apiTarget: 'https://127.0.0.1:18006',
|
|
apiSecret: 'secret',
|
|
encryptionKey: 'unit-test-secret',
|
|
apiFetchImpl: mockFetch,
|
|
});
|
|
|
|
const result = await service.deleteKey(row.id);
|
|
|
|
assert.equal(result.ok, true);
|
|
assert.equal(deleted, true);
|
|
});
|
|
|
|
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-v4-pro',
|
|
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('DeepSeek catalog uses the currently supported V4 model names', () => {
|
|
const deepseek = LLM_PROVIDER_CATALOG.find((item) => item.id === 'custom_deepseek');
|
|
assert.ok(deepseek);
|
|
assert.equal(deepseek.defaultModel, 'deepseek-v4-pro');
|
|
assert.deepEqual(deepseek.models, ['deepseek-v4-pro', 'deepseek-v4-flash']);
|
|
});
|
|
|
|
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-v4-pro',
|
|
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-v4-pro');
|
|
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-v4-pro');
|
|
});
|
|
|
|
test('createChatCompletion uses selected OpenAI-compatible provider', async () => {
|
|
const encrypted = encryptSecret('sk-direct-test', 'unit-test-secret');
|
|
const row = {
|
|
id: 'key-direct',
|
|
provider_id: CUSTOM_PROVIDER_ID,
|
|
provider_kind: 'custom',
|
|
api_url: 'http://127.0.0.1:19999/v1',
|
|
base_path: null,
|
|
models_json: JSON.stringify(['direct-model']),
|
|
goosed_provider_id: null,
|
|
engine: 'openai',
|
|
relay_provider: 'deepseek',
|
|
name: 'Direct Provider',
|
|
api_key_ciphertext: encrypted.ciphertext,
|
|
api_key_iv: encrypted.iv,
|
|
api_key_tag: encrypted.tag,
|
|
default_model: 'direct-model',
|
|
status: 'active',
|
|
is_selected: 1,
|
|
created_at: 1,
|
|
updated_at: 1,
|
|
};
|
|
const pool = {
|
|
async query(sql, params = []) {
|
|
if (sql.includes('WHERE is_selected = 1')) return [[row]];
|
|
throw new Error(`Unexpected SQL: ${sql} ${JSON.stringify(params)}`);
|
|
},
|
|
};
|
|
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: 'direct reply' } }],
|
|
usage: { prompt_tokens: 11, completion_tokens: 7 },
|
|
}),
|
|
};
|
|
};
|
|
const service = createLlmProviderService(pool, {
|
|
encryptionKey: 'unit-test-secret',
|
|
apiFetchImpl: mockFetch,
|
|
});
|
|
|
|
const result = await service.createChatCompletion({
|
|
messages: [{ role: 'user', content: 'hi' }],
|
|
});
|
|
|
|
assert.equal(result.ok, true);
|
|
assert.equal(result.reply, 'direct reply');
|
|
assert.equal(result.model, 'direct-model');
|
|
assert.deepEqual(result.usage, {
|
|
inputTokens: 11,
|
|
outputTokens: 7,
|
|
raw: { prompt_tokens: 11, completion_tokens: 7 },
|
|
});
|
|
assert.equal(calls[0].url, 'http://127.0.0.1:19999/v1/chat/completions');
|
|
assert.equal(calls[0].init.headers.Authorization, 'Bearer sk-direct-test');
|
|
assert.equal(calls[0].body.provider, 'deepseek');
|
|
assert.equal(calls[0].body.stream, false);
|
|
});
|
|
|
|
test('createChatCompletion can use an explicit provider key for router calls', async () => {
|
|
const encryptedSelected = encryptSecret('sk-selected-test', 'unit-test-secret');
|
|
const encryptedRouter = encryptSecret('sk-router-test', 'unit-test-secret');
|
|
const selectedRow = {
|
|
id: 'key-selected',
|
|
provider_id: CUSTOM_PROVIDER_ID,
|
|
provider_kind: 'custom',
|
|
api_url: 'http://127.0.0.1:19998/v1',
|
|
base_path: null,
|
|
models_json: JSON.stringify(['selected-model']),
|
|
goosed_provider_id: null,
|
|
engine: 'openai',
|
|
relay_provider: null,
|
|
name: 'Selected Provider',
|
|
api_key_ciphertext: encryptedSelected.ciphertext,
|
|
api_key_iv: encryptedSelected.iv,
|
|
api_key_tag: encryptedSelected.tag,
|
|
default_model: 'selected-model',
|
|
status: 'active',
|
|
is_selected: 1,
|
|
created_at: 1,
|
|
updated_at: 1,
|
|
};
|
|
const routerRow = {
|
|
...selectedRow,
|
|
id: 'key-router',
|
|
api_url: 'http://127.0.0.1:19997/v1',
|
|
models_json: JSON.stringify(['router-model']),
|
|
name: 'Router Provider',
|
|
api_key_ciphertext: encryptedRouter.ciphertext,
|
|
api_key_iv: encryptedRouter.iv,
|
|
api_key_tag: encryptedRouter.tag,
|
|
default_model: 'router-model',
|
|
is_selected: 0,
|
|
};
|
|
const rows = [selectedRow, routerRow];
|
|
const pool = {
|
|
async query(sql, params = []) {
|
|
if (sql.includes('WHERE id = ?')) {
|
|
return [[rows.find((row) => row.id === params[0])].filter(Boolean)];
|
|
}
|
|
if (sql.includes('WHERE is_selected = 1')) return [[selectedRow]];
|
|
throw new Error(`Unexpected SQL: ${sql} ${JSON.stringify(params)}`);
|
|
},
|
|
};
|
|
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: 'router reply' } }],
|
|
}),
|
|
};
|
|
};
|
|
const service = createLlmProviderService(pool, {
|
|
encryptionKey: 'unit-test-secret',
|
|
apiFetchImpl: mockFetch,
|
|
});
|
|
|
|
const result = await service.createChatCompletion({
|
|
providerKeyId: 'key-router',
|
|
model: 'router-model',
|
|
messages: [{ role: 'user', content: 'route this' }],
|
|
});
|
|
|
|
assert.equal(result.ok, true);
|
|
assert.equal(result.providerKeyId, 'key-router');
|
|
assert.equal(result.model, 'router-model');
|
|
assert.equal(result.reply, 'router reply');
|
|
assert.equal(calls[0].url, 'http://127.0.0.1:19997/v1/chat/completions');
|
|
assert.equal(calls[0].init.headers.Authorization, 'Bearer sk-router-test');
|
|
});
|
|
|
|
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-v4-pro',
|
|
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-v4-flash',
|
|
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-v4-flash');
|
|
assert.equal(runtime.env.AIDER_MODEL, 'deepseek/deepseek-v4-flash');
|
|
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-v4-flash',
|
|
env: { LLM_MODEL: 'deepseek-v4-flash', 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-v4-flash',
|
|
env: { LLM_MODEL: 'deepseek-v4-flash', 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-v4-pro',
|
|
env: {
|
|
AIDER_MODEL: 'deepseek/deepseek-v4-pro',
|
|
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-v4-pro']);
|
|
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-v4-flash',
|
|
env: { LLM_MODEL: 'deepseek-v4-flash', 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-v4-pro',
|
|
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-v4-pro',
|
|
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-v4-pro',
|
|
apiKey: 'sk-test',
|
|
},
|
|
mockFetch,
|
|
);
|
|
assert.deepEqual(
|
|
writes.map((item) => item.key),
|
|
['DEEPSEEK_API_KEY', 'GOOSE_PROVIDER', 'GOOSE_MODEL', 'TKMIND_PROVIDER', 'TKMIND_MODEL'],
|
|
);
|
|
});
|
|
|
|
test('syncProfileToGoosed routes local DeepSeek through the no-thinking proxy', async () => {
|
|
const previousProfile = process.env.MEMIND_RUNTIME_PROFILE;
|
|
const previousDisableThinking = process.env.MEMIND_DEEPSEEK_DISABLE_THINKING;
|
|
const previousProxyBase = process.env.MEMIND_DEEPSEEK_NO_THINK_BASE_URL;
|
|
const previousGateway = process.env.MEMIND_GOOSED_HOST_GATEWAY;
|
|
process.env.MEMIND_RUNTIME_PROFILE = 'local';
|
|
delete process.env.MEMIND_DEEPSEEK_DISABLE_THINKING;
|
|
delete process.env.MEMIND_DEEPSEEK_NO_THINK_BASE_URL;
|
|
process.env.MEMIND_GOOSED_HOST_GATEWAY = 'host.lima.internal';
|
|
|
|
try {
|
|
const calls = [];
|
|
const mockFetch = async (url, init = {}) => {
|
|
const href = String(url);
|
|
const body = init.body ? JSON.parse(init.body) : null;
|
|
calls.push({ href, method: init.method ?? 'GET', body });
|
|
if (
|
|
init.method === 'PUT'
|
|
&& href.includes(`/config/custom-providers/${MEMIND_DEEPSEEK_NO_THINK_PROVIDER_ID}`)
|
|
) {
|
|
return {
|
|
ok: false,
|
|
status: 500,
|
|
text: async () => JSON.stringify({
|
|
message: `Provider not found: ${MEMIND_DEEPSEEK_NO_THINK_PROVIDER_ID}`,
|
|
}),
|
|
};
|
|
}
|
|
if (init.method === 'POST' && href.endsWith('/config/custom-providers')) {
|
|
return {
|
|
ok: true,
|
|
json: async () => ({ provider_name: MEMIND_DEEPSEEK_NO_THINK_PROVIDER_ID }),
|
|
text: async () => '',
|
|
};
|
|
}
|
|
return { ok: true, status: 200, text: async () => '' };
|
|
};
|
|
|
|
const providerId = await syncProfileToGoosed(
|
|
'https://127.0.0.1:18006',
|
|
'secret',
|
|
{
|
|
providerKind: 'builtin',
|
|
providerId: 'custom_deepseek',
|
|
defaultModel: 'deepseek-v4-pro',
|
|
apiKey: 'sk-test',
|
|
},
|
|
mockFetch,
|
|
);
|
|
|
|
assert.equal(providerId, MEMIND_DEEPSEEK_NO_THINK_PROVIDER_ID);
|
|
const createCall = calls.find(
|
|
(call) => call.method === 'POST' && call.href.endsWith('/config/custom-providers'),
|
|
);
|
|
assert.equal(createCall?.body?.api_url, 'http://host.lima.internal:18036/v1');
|
|
assert.equal(createCall?.body?.preserves_thinking, false);
|
|
const providerWrite = calls.find(
|
|
(call) => call.href.includes('/config/upsert') && call.body?.key === 'GOOSE_PROVIDER',
|
|
);
|
|
assert.equal(providerWrite?.body?.value, MEMIND_DEEPSEEK_NO_THINK_PROVIDER_ID);
|
|
} finally {
|
|
if (previousProfile == null) delete process.env.MEMIND_RUNTIME_PROFILE;
|
|
else process.env.MEMIND_RUNTIME_PROFILE = previousProfile;
|
|
if (previousDisableThinking == null) delete process.env.MEMIND_DEEPSEEK_DISABLE_THINKING;
|
|
else process.env.MEMIND_DEEPSEEK_DISABLE_THINKING = previousDisableThinking;
|
|
if (previousProxyBase == null) delete process.env.MEMIND_DEEPSEEK_NO_THINK_BASE_URL;
|
|
else process.env.MEMIND_DEEPSEEK_NO_THINK_BASE_URL = previousProxyBase;
|
|
if (previousGateway == null) delete process.env.MEMIND_GOOSED_HOST_GATEWAY;
|
|
else process.env.MEMIND_GOOSED_HOST_GATEWAY = previousGateway;
|
|
}
|
|
});
|
|
|
|
test('syncProfileToGoosed routes local Moonshot profiles through schema compat proxy', async () => {
|
|
const previousProfile = process.env.MEMIND_RUNTIME_PROFILE;
|
|
const previousCompat = process.env.MEMIND_MOONSHOT_TOOL_SCHEMA_COMPAT;
|
|
const previousBase = process.env.MEMIND_MOONSHOT_COMPAT_BASE_URL;
|
|
const previousGateway = process.env.MEMIND_GOOSED_HOST_GATEWAY;
|
|
process.env.MEMIND_RUNTIME_PROFILE = 'split-service';
|
|
delete process.env.MEMIND_MOONSHOT_TOOL_SCHEMA_COMPAT;
|
|
delete process.env.MEMIND_MOONSHOT_COMPAT_BASE_URL;
|
|
process.env.MEMIND_GOOSED_HOST_GATEWAY = 'host.lima.internal';
|
|
|
|
try {
|
|
const calls = [];
|
|
const mockFetch = async (url, init = {}) => {
|
|
const href = String(url);
|
|
const body = init.body ? JSON.parse(init.body) : null;
|
|
calls.push({ href, method: init.method ?? 'GET', body });
|
|
if (
|
|
init.method === 'PUT'
|
|
&& href.includes('/config/custom-providers/custom_kimi')
|
|
) {
|
|
return { ok: true, status: 200, text: async () => '' };
|
|
}
|
|
return { ok: true, status: 200, text: async () => '' };
|
|
};
|
|
|
|
const providerId = await syncProfileToGoosed(
|
|
'https://127.0.0.1:18006',
|
|
'secret',
|
|
{
|
|
providerKind: 'custom',
|
|
providerId: 'custom_kimi',
|
|
goosedProviderId: 'custom_kimi',
|
|
name: 'kimi',
|
|
apiUrl: 'https://api.moonshot.cn/v1',
|
|
apiKey: 'sk-test',
|
|
models: ['kimi-k2.6', 'kimi-k3'],
|
|
defaultModel: 'kimi-k2.6',
|
|
engine: 'openai',
|
|
},
|
|
mockFetch,
|
|
);
|
|
|
|
assert.equal(providerId, 'custom_kimi');
|
|
const updateCall = calls.find(
|
|
(call) => call.method === 'PUT'
|
|
&& call.href.includes('/config/custom-providers/custom_kimi'),
|
|
);
|
|
assert.equal(
|
|
updateCall?.body?.api_url,
|
|
'http://host.lima.internal:18036/moonshot/v1',
|
|
);
|
|
assert.equal(updateCall?.body?.preserves_thinking, false);
|
|
} finally {
|
|
if (previousProfile == null) delete process.env.MEMIND_RUNTIME_PROFILE;
|
|
else process.env.MEMIND_RUNTIME_PROFILE = previousProfile;
|
|
if (previousCompat == null) delete process.env.MEMIND_MOONSHOT_TOOL_SCHEMA_COMPAT;
|
|
else process.env.MEMIND_MOONSHOT_TOOL_SCHEMA_COMPAT = previousCompat;
|
|
if (previousBase == null) delete process.env.MEMIND_MOONSHOT_COMPAT_BASE_URL;
|
|
else process.env.MEMIND_MOONSHOT_COMPAT_BASE_URL = previousBase;
|
|
if (previousGateway == null) delete process.env.MEMIND_GOOSED_HOST_GATEWAY;
|
|
else process.env.MEMIND_GOOSED_HOST_GATEWAY = previousGateway;
|
|
}
|
|
});
|
|
|
|
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-v4-pro']),
|
|
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-v4-pro',
|
|
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);
|
|
});
|