config.test.mjs
236 lines 9.7 KB
Raw
1 /**
2 * Config load tests: file + env, missing vault_path, vault path validation, hub_setup merge.
3 */
4 import { describe, it, before, after } from 'node:test';
5 import assert from 'node:assert';
6 import fs from 'fs';
7 import os from 'os';
8 import path from 'path';
9 import { fileURLToPath } from 'url';
10 import { loadConfig } from '../lib/config.mjs';
11
12 const __dirname = path.dirname(fileURLToPath(import.meta.url));
13 const fixturesDir = path.join(__dirname, 'fixtures');
14 const dataDir = path.join(fixturesDir, 'data');
15 const hubSetupPath = path.join(dataDir, 'hub_setup.yaml');
16
17 describe('loadConfig', () => {
18 const envBackup = { ...process.env };
19
20 after(() => {
21 process.env.KNOWTATION_VAULT_PATH = envBackup.KNOWTATION_VAULT_PATH;
22 process.env.KNOWTATION_DATA_DIR = envBackup.KNOWTATION_DATA_DIR;
23 process.env.KNOWTATION_VECTOR_STORE = envBackup.KNOWTATION_VECTOR_STORE;
24 process.env.OLLAMA_URL = envBackup.OLLAMA_URL;
25 delete process.env.KNOWTATION_VAULT_PATH;
26 delete process.env.KNOWTATION_DATA_DIR;
27 delete process.env.KNOWTATION_VECTOR_STORE;
28 delete process.env.OLLAMA_URL;
29 delete process.env.MUSE_URL;
30 if (envBackup.KNOWTATION_VAULT_PATH !== undefined) process.env.KNOWTATION_VAULT_PATH = envBackup.KNOWTATION_VAULT_PATH;
31 if (envBackup.KNOWTATION_DATA_DIR !== undefined) process.env.KNOWTATION_DATA_DIR = envBackup.KNOWTATION_DATA_DIR;
32 if (envBackup.KNOWTATION_VECTOR_STORE !== undefined) process.env.KNOWTATION_VECTOR_STORE = envBackup.KNOWTATION_VECTOR_STORE;
33 if (envBackup.OLLAMA_URL !== undefined) process.env.OLLAMA_URL = envBackup.OLLAMA_URL;
34 if (envBackup.MUSE_URL !== undefined) process.env.MUSE_URL = envBackup.MUSE_URL;
35 });
36
37 it('loads from fixture config when cwd is fixtures', () => {
38 const prevVault = process.env.KNOWTATION_VAULT_PATH;
39 const prevData = process.env.KNOWTATION_DATA_DIR;
40 delete process.env.KNOWTATION_VAULT_PATH;
41 delete process.env.KNOWTATION_DATA_DIR;
42 try {
43 const config = loadConfig(fixturesDir);
44 assert.strictEqual(typeof config.vault_path, 'string');
45 assert(config.vault_path.endsWith('vault-fs') || config.vault_path.includes('vault-fs'));
46 assert.strictEqual(config.data_dir, path.resolve(fixturesDir, 'data'));
47 assert(Array.isArray(config.ignore));
48 assert(config.ignore.includes('templates'));
49 assert(config.ignore.includes('meta'));
50 } finally {
51 if (prevVault !== undefined) process.env.KNOWTATION_VAULT_PATH = prevVault;
52 else delete process.env.KNOWTATION_VAULT_PATH;
53 if (prevData !== undefined) process.env.KNOWTATION_DATA_DIR = prevData;
54 else delete process.env.KNOWTATION_DATA_DIR;
55 }
56 });
57
58 it('throws when vault_path is missing (no file, no env)', () => {
59 const emptyDir = path.join(__dirname, 'fixtures', 'config');
60 const prev = process.env.KNOWTATION_VAULT_PATH;
61 delete process.env.KNOWTATION_VAULT_PATH;
62 try {
63 assert.throws(
64 () => loadConfig(emptyDir),
65 /vault_path is required/
66 );
67 } finally {
68 if (prev !== undefined) process.env.KNOWTATION_VAULT_PATH = prev;
69 }
70 });
71
72 it('respects KNOWTATION_VAULT_PATH env override', () => {
73 const vaultAbs = path.join(fixturesDir, 'vault-fs');
74 process.env.KNOWTATION_VAULT_PATH = vaultAbs;
75 try {
76 const config = loadConfig(fixturesDir);
77 assert.strictEqual(config.vault_path, vaultAbs);
78 } finally {
79 delete process.env.KNOWTATION_VAULT_PATH;
80 }
81 });
82
83 it('respects KNOWTATION_VECTOR_STORE env override', () => {
84 process.env.KNOWTATION_VAULT_PATH = path.join(fixturesDir, 'vault-fs');
85 process.env.KNOWTATION_VECTOR_STORE = 'sqlite-vec';
86 try {
87 const config = loadConfig(fixturesDir);
88 assert.strictEqual(config.vector_store, 'sqlite-vec');
89 } finally {
90 delete process.env.KNOWTATION_VAULT_PATH;
91 delete process.env.KNOWTATION_VECTOR_STORE;
92 }
93 });
94
95 it('sets embedding.ollama_url from OLLAMA_URL env (overrides file)', () => {
96 const prevOllama = process.env.OLLAMA_URL;
97 process.env.KNOWTATION_VAULT_PATH = path.join(fixturesDir, 'vault-fs');
98 process.env.OLLAMA_URL = 'http://ollama.example:11434';
99 try {
100 const config = loadConfig(fixturesDir);
101 assert.strictEqual(config.embedding.ollama_url, 'http://ollama.example:11434');
102 } finally {
103 delete process.env.KNOWTATION_VAULT_PATH;
104 if (prevOllama !== undefined) process.env.OLLAMA_URL = prevOllama;
105 else delete process.env.OLLAMA_URL;
106 }
107 });
108
109 it('sets embedding.provider and default model from EMBEDDING_PROVIDER env', () => {
110 const prevProv = process.env.EMBEDDING_PROVIDER;
111 const prevModel = process.env.EMBEDDING_MODEL;
112 process.env.KNOWTATION_VAULT_PATH = path.join(fixturesDir, 'vault-fs');
113 process.env.EMBEDDING_PROVIDER = 'voyage';
114 delete process.env.EMBEDDING_MODEL;
115 try {
116 const config = loadConfig(fixturesDir);
117 assert.strictEqual(config.embedding.provider, 'voyage');
118 assert.strictEqual(config.embedding.model, 'voyage-4-lite');
119 } finally {
120 delete process.env.KNOWTATION_VAULT_PATH;
121 if (prevProv !== undefined) process.env.EMBEDDING_PROVIDER = prevProv;
122 else delete process.env.EMBEDDING_PROVIDER;
123 if (prevModel !== undefined) process.env.EMBEDDING_MODEL = prevModel;
124 else delete process.env.EMBEDDING_MODEL;
125 }
126 });
127
128 it('EMBEDDING_MODEL env overrides default for voyage provider', () => {
129 const prevProv = process.env.EMBEDDING_PROVIDER;
130 const prevModel = process.env.EMBEDDING_MODEL;
131 process.env.KNOWTATION_VAULT_PATH = path.join(fixturesDir, 'vault-fs');
132 process.env.EMBEDDING_PROVIDER = 'voyage';
133 process.env.EMBEDDING_MODEL = 'voyage-3-lite';
134 try {
135 const config = loadConfig(fixturesDir);
136 assert.strictEqual(config.embedding.provider, 'voyage');
137 assert.strictEqual(config.embedding.model, 'voyage-3-lite');
138 } finally {
139 delete process.env.KNOWTATION_VAULT_PATH;
140 if (prevProv !== undefined) process.env.EMBEDDING_PROVIDER = prevProv;
141 else delete process.env.EMBEDDING_PROVIDER;
142 if (prevModel !== undefined) process.env.EMBEDDING_MODEL = prevModel;
143 else delete process.env.EMBEDDING_MODEL;
144 }
145 });
146
147 it('merges hub_setup.yaml (vault.git) over config when present', () => {
148 fs.mkdirSync(dataDir, { recursive: true });
149 fs.writeFileSync(
150 hubSetupPath,
151 'vault:\n git:\n enabled: true\n remote: https://github.com/test/repo.git\n',
152 'utf8'
153 );
154 try {
155 const config = loadConfig(fixturesDir);
156 assert.strictEqual(config.vault_git?.enabled, true);
157 assert.strictEqual(config.vault_git?.remote, 'https://github.com/test/repo.git');
158 } finally {
159 try { fs.unlinkSync(hubSetupPath); } catch (_) {}
160 try { fs.rmdirSync(dataDir); } catch (_) {}
161 }
162 });
163
164 it('does not apply hub_setup vault_path when KNOWTATION_VAULT_PATH is set', () => {
165 fs.mkdirSync(dataDir, { recursive: true });
166 const vaultAbs = path.join(fixturesDir, 'vault-fs');
167 process.env.KNOWTATION_VAULT_PATH = vaultAbs;
168 fs.writeFileSync(
169 hubSetupPath,
170 'vault_path: markdown-import\nvault:\n git:\n enabled: false\n',
171 'utf8'
172 );
173 try {
174 const config = loadConfig(fixturesDir);
175 assert.strictEqual(config.vault_path, vaultAbs);
176 } finally {
177 delete process.env.KNOWTATION_VAULT_PATH;
178 try { fs.unlinkSync(hubSetupPath); } catch (_) {}
179 try { fs.rmdirSync(dataDir); } catch (_) {}
180 }
181 });
182
183 it('merges muse.url from config/local.yaml when MUSE_URL env is unset', () => {
184 const prevVault = process.env.KNOWTATION_VAULT_PATH;
185 const prevMuse = process.env.MUSE_URL;
186 const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'kt-muse-yaml-'));
187 const cfgDir = path.join(tmp, 'config');
188 fs.mkdirSync(cfgDir, { recursive: true });
189 const vaultAbs = path.join(fixturesDir, 'vault-fs');
190 const dataAbs = path.join(fixturesDir, 'data');
191 fs.writeFileSync(
192 path.join(cfgDir, 'local.yaml'),
193 `vault_path: ${JSON.stringify(vaultAbs)}\ndata_dir: ${JSON.stringify(dataAbs)}\nmuse:\n url: https://muse.from-yaml.example/\n`,
194 'utf8',
195 );
196 delete process.env.MUSE_URL;
197 delete process.env.KNOWTATION_VAULT_PATH;
198 try {
199 const config = loadConfig(tmp);
200 assert.strictEqual(config.muse.url, 'https://muse.from-yaml.example');
201 } finally {
202 if (prevVault !== undefined) process.env.KNOWTATION_VAULT_PATH = prevVault;
203 else delete process.env.KNOWTATION_VAULT_PATH;
204 if (prevMuse !== undefined) process.env.MUSE_URL = prevMuse;
205 else delete process.env.MUSE_URL;
206 fs.rmSync(tmp, { recursive: true, force: true });
207 }
208 });
209
210 it('MUSE_URL env overrides muse.url in YAML', () => {
211 const prevVault = process.env.KNOWTATION_VAULT_PATH;
212 const prevMuse = process.env.MUSE_URL;
213 const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'kt-muse-env-'));
214 const cfgDir = path.join(tmp, 'config');
215 fs.mkdirSync(cfgDir, { recursive: true });
216 const vaultAbs = path.join(fixturesDir, 'vault-fs');
217 const dataAbs = path.join(fixturesDir, 'data');
218 fs.writeFileSync(
219 path.join(cfgDir, 'local.yaml'),
220 `vault_path: ${JSON.stringify(vaultAbs)}\ndata_dir: ${JSON.stringify(dataAbs)}\nmuse:\n url: https://muse.from-yaml.example/\n`,
221 'utf8',
222 );
223 process.env.MUSE_URL = 'https://muse.from-env.example/';
224 delete process.env.KNOWTATION_VAULT_PATH;
225 try {
226 const config = loadConfig(tmp);
227 assert.strictEqual(config.muse.url, 'https://muse.from-env.example');
228 } finally {
229 if (prevVault !== undefined) process.env.KNOWTATION_VAULT_PATH = prevVault;
230 else delete process.env.KNOWTATION_VAULT_PATH;
231 if (prevMuse !== undefined) process.env.MUSE_URL = prevMuse;
232 else delete process.env.MUSE_URL;
233 fs.rmSync(tmp, { recursive: true, force: true });
234 }
235 });
236 });
File History 1 commit