import assert from 'node:assert/strict'; import test from 'node:test'; import { createLlmProviderService, CUSTOM_PROVIDER_ID, decryptSecret, encryptSecret, LLM_PROVIDER_CATALOG, LOCAL_LLM_FALLBACK, maskApiKey, normalizeApiUrl, 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('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('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('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('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); });