| 1 | import {mkdtemp, mkdir, readFile, rm, writeFile} from 'node:fs/promises'; |
| 2 | import os from 'node:os'; |
| 3 | import path from 'node:path'; |
| 4 | import {afterEach, describe, expect, it} from 'vitest'; |
| 5 | import {readAgentConfig, saveAgentConfig} from './config-store.mjs'; |
| 6 | |
| 7 | const roots = []; |
| 8 | |
| 9 | afterEach(async () => { |
| 10 | await Promise.all(roots.splice(0).map((root) => rm(root, {recursive: true, force: true}))); |
| 11 | }); |
| 12 | |
| 13 | async function fixture() { |
| 14 | const root = await mkdtemp(path.join(os.tmpdir(), 'vimax-config-')); |
| 15 | roots.push(root); |
| 16 | await mkdir(path.join(root, 'configs'), {recursive: true}); |
| 17 | await writeFile(path.join(root, 'configs', 'agent.local.yaml'), [ |
| 18 | 'llm:', |
| 19 | ' model_provider: openai', |
| 20 | ' model: existing-model', |
| 21 | ' base_url: https://example.test/v1', |
| 22 | ' api_key: secret-value', |
| 23 | '', |
| 24 | ].join('\n')); |
| 25 | return root; |
| 26 | } |
| 27 | |
| 28 | describe('agent config store', () => { |
| 29 | it('never returns stored API keys', async () => { |
| 30 | const root = await fixture(); |
| 31 | const config = await readAgentConfig(root); |
| 32 | expect(config.sections.llm).toMatchObject({model: 'existing-model', api_key: '', has_api_key: true}); |
| 33 | expect(JSON.stringify(config)).not.toContain('secret-value'); |
| 34 | }); |
| 35 | |
| 36 | it('keeps a stored key when a blank key is saved', async () => { |
| 37 | const root = await fixture(); |
| 38 | await saveAgentConfig(root, {sections: {llm: {model: 'new-model', api_key: ''}}}); |
| 39 | const saved = await readFile(path.join(root, 'configs', 'agent.local.yaml'), 'utf8'); |
| 40 | expect(saved).toContain('model: new-model'); |
| 41 | expect(saved).toContain('api_key: secret-value'); |
| 42 | }); |
| 43 | |
| 44 | it('rejects invalid base URLs', async () => { |
| 45 | const root = await fixture(); |
| 46 | await expect(saveAgentConfig(root, {sections: {llm: {base_url: 'file:///tmp/key'}}})).rejects.toThrow(/http/); |
| 47 | }); |
| 48 | }); |
| 49 |