profileShare

rasmusjy / voicetask

Read-only snapshot

No repository description.

main default branch 105 files Expires Sep 13, 2026, 9:06 AM
runtimeConfig.test.ts 4,860 bytes
1 import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
2 import { tmpdir } from 'node:os'
3 import path from 'node:path'
4 import { afterEach, beforeEach, describe, expect, it } from 'vitest'
5 import { applyConfig, configStatus, keyHint, mergeEnvFile } from './runtimeConfig'
6
7 describe('configStatus', () => {
8 it('reports demo mode when mocks are forced', () => {
9 const status = configStatus(true, { MOCK_PROVIDERS: '1' })
10 expect(status.mode).toBe('demo')
11 expect(status.demoLocked).toBe(true)
12 })
13
14 it('reports real mode only when both keys are present', () => {
15 expect(configStatus(false, { ANTHROPIC_API_KEY: 'sk-ant-1234abcd' }).mode).toBe('unconfigured')
16 expect(
17 configStatus(false, { ANTHROPIC_API_KEY: 'sk-ant-1234abcd', OPENAI_API_KEY: 'sk-1234wxyz' }).mode,
18 ).toBe('real')
19 })
20
21 it('treats .env.example placeholders as unset', () => {
22 const status = configStatus(false, {
23 ANTHROPIC_API_KEY: '@insert-anthropic-api-key@',
24 OPENAI_API_KEY: '@insert-openai-api-key@',
25 })
26 expect(status.mode).toBe('unconfigured')
27 expect(status.anthropicKeySet).toBe(false)
28 expect(status.openaiKeySet).toBe(false)
29 })
30
31 it('never exposes more than the last four characters of a key', () => {
32 const status = configStatus(false, { ANTHROPIC_API_KEY: 'sk-ant-secret-value-abcd' })
33 expect(status.anthropicKeyHint).toBe('...abcd')
34 expect(JSON.stringify(status)).not.toContain('secret')
35 expect(keyHint(null)).toBeNull()
36 })
37 })
38
39 describe('mergeEnvFile', () => {
40 it('replaces existing assignments and keeps comments and other keys', () => {
41 const merged = mergeEnvFile('# note\nMOCK_PROVIDERS=1\nPORT=4000\n', {
42 MOCK_PROVIDERS: '',
43 ANTHROPIC_API_KEY: 'sk-ant-new',
44 })
45 expect(merged).toBe('# note\nMOCK_PROVIDERS=\nPORT=4000\nANTHROPIC_API_KEY=sk-ant-new\n')
46 })
47
48 it('drops duplicate assignments of an updated key', () => {
49 const merged = mergeEnvFile('OPENAI_API_KEY=old-one\nPORT=3001\nOPENAI_API_KEY=old-two\n', {
50 OPENAI_API_KEY: 'sk-new',
51 })
52 expect(merged).toBe('OPENAI_API_KEY=sk-new\nPORT=3001\n')
53 })
54
55 it('writes into an empty file', () => {
56 expect(mergeEnvFile('', { MOCK_PROVIDERS: '1' })).toBe('MOCK_PROVIDERS=1\n')
57 })
58 })
59
60 describe('applyConfig', () => {
61 let dir: string
62 let envPath: string
63
64 beforeEach(async () => {
65 dir = await mkdtemp(path.join(tmpdir(), 'voicetask-env-'))
66 envPath = path.join(dir, '.env')
67 })
68
69 afterEach(async () => {
70 await rm(dir, { recursive: true, force: true })
71 })
72
73 it('stores keys and switches the running process to real providers', async () => {
74 const env: NodeJS.ProcessEnv = { MOCK_PROVIDERS: '1' }
75 const result = applyConfig(
76 { mode: 'real', anthropicKey: 'sk-ant-1234abcd', openaiKey: 'sk-1234wxyz' },
77 { demoLocked: false, envPath, env },
78 )
79
80 expect(result.restartRequired).toBe(false)
81 expect(result.status.mode).toBe('real')
82 expect(env.MOCK_PROVIDERS).toBeUndefined()
83 expect(env.ANTHROPIC_API_KEY).toBe('sk-ant-1234abcd')
84
85 const written = await readFile(envPath, 'utf8')
86 expect(written).toContain('ANTHROPIC_API_KEY=sk-ant-1234abcd')
87 expect(written).toContain('OPENAI_API_KEY=sk-1234wxyz')
88 expect(written).toContain('MOCK_PROVIDERS=')
89 })
90
91 it('keeps an existing key when only the other one is sent', async () => {
92 await writeFile(envPath, 'ANTHROPIC_API_KEY=sk-ant-old\n', 'utf8')
93 const env: NodeJS.ProcessEnv = { ANTHROPIC_API_KEY: 'sk-ant-old' }
94 applyConfig({ mode: 'real', openaiKey: 'sk-1234wxyz' }, { demoLocked: false, envPath, env })
95
96 const written = await readFile(envPath, 'utf8')
97 expect(written).toContain('ANTHROPIC_API_KEY=sk-ant-old')
98 expect(env.ANTHROPIC_API_KEY).toBe('sk-ant-old')
99 })
100
101 it('saves keys but stays in mocks when the server was started in demo mode', async () => {
102 const env: NodeJS.ProcessEnv = { MOCK_PROVIDERS: '1' }
103 const result = applyConfig(
104 { mode: 'real', anthropicKey: 'sk-ant-1234abcd', openaiKey: 'sk-1234wxyz' },
105 { demoLocked: true, envPath, env },
106 )
107
108 expect(result.restartRequired).toBe(true)
109 expect(result.status.mode).toBe('demo')
110 expect(env.ANTHROPIC_API_KEY).toBeUndefined()
111 expect(await readFile(envPath, 'utf8')).toContain('ANTHROPIC_API_KEY=sk-ant-1234abcd')
112 })
113
114 it('switches to demo mode without touching stored keys', async () => {
115 await writeFile(envPath, 'ANTHROPIC_API_KEY=sk-ant-old\nOPENAI_API_KEY=sk-old\n', 'utf8')
116 const env: NodeJS.ProcessEnv = { ANTHROPIC_API_KEY: 'sk-ant-old', OPENAI_API_KEY: 'sk-old' }
117 const result = applyConfig({ mode: 'demo' }, { demoLocked: false, envPath, env })
118
119 expect(result.status.mode).toBe('demo')
120 expect(env.MOCK_PROVIDERS).toBe('1')
121 expect(await readFile(envPath, 'utf8')).toContain('ANTHROPIC_API_KEY=sk-ant-old')
122 })
123 })
124