Skip to content

Commit eb27ec2

Browse files
committed
feat(config): fix previously documented env var formula not working
asdf
1 parent 8abd3ed commit eb27ec2

File tree

2 files changed

+890
-80
lines changed

2 files changed

+890
-80
lines changed
Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
import { vi, describe, it, expect, beforeEach, afterEach } from "vitest";
2+
import fs from "fs";
3+
import ini from "ini";
4+
5+
// Mock dependencies
6+
vi.mock("fs");
7+
vi.mock("./data_dir.js", () => ({
8+
default: {
9+
CONFIG_INI_PATH: "/test/config.ini"
10+
}
11+
}));
12+
vi.mock("./resource_dir.js", () => ({
13+
default: {
14+
RESOURCE_DIR: "/test/resources"
15+
}
16+
}));
17+
18+
describe("Config Service", () => {
19+
let originalEnv: NodeJS.ProcessEnv;
20+
21+
beforeEach(() => {
22+
// Save original environment
23+
originalEnv = { ...process.env };
24+
25+
// Clear all TRILIUM env vars
26+
Object.keys(process.env).forEach(key => {
27+
if (key.startsWith("TRILIUM_")) {
28+
delete process.env[key];
29+
}
30+
});
31+
32+
// Mock fs to return empty config
33+
vi.mocked(fs.existsSync).mockReturnValue(true);
34+
vi.mocked(fs.readFileSync).mockImplementation((path) => {
35+
if (String(path).includes("config-sample.ini")) {
36+
return "" as any; // Return string for INI parsing
37+
}
38+
// Return empty INI config as string
39+
return `
40+
[General]
41+
[Network]
42+
[Session]
43+
[Sync]
44+
[MultiFactorAuthentication]
45+
[Logging]
46+
` as any;
47+
});
48+
49+
// Clear module cache to reload config with new env vars
50+
vi.resetModules();
51+
});
52+
53+
afterEach(() => {
54+
// Restore original environment
55+
process.env = originalEnv;
56+
vi.clearAllMocks();
57+
});
58+
59+
describe("Environment Variable Naming", () => {
60+
it("should use standard environment variables following TRILIUM_[SECTION]_[KEY] pattern", async () => {
61+
// Set standard env vars
62+
process.env.TRILIUM_GENERAL_INSTANCENAME = "test-instance";
63+
process.env.TRILIUM_NETWORK_CORSALLOWORIGIN = "https://example.com";
64+
process.env.TRILIUM_SYNC_SYNCSERVERHOST = "sync.example.com";
65+
process.env.TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHBASEURL = "https://auth.example.com";
66+
process.env.TRILIUM_LOGGING_RETENTIONDAYS = "30";
67+
68+
const { default: config } = await import("./config.js");
69+
70+
expect(config.General.instanceName).toBe("test-instance");
71+
expect(config.Network.corsAllowOrigin).toBe("https://example.com");
72+
expect(config.Sync.syncServerHost).toBe("sync.example.com");
73+
expect(config.MultiFactorAuthentication.oauthBaseUrl).toBe("https://auth.example.com");
74+
expect(config.Logging.retentionDays).toBe(30);
75+
});
76+
77+
it("should maintain backward compatibility with alias environment variables", async () => {
78+
// Set alias/legacy env vars
79+
process.env.TRILIUM_NETWORK_CORS_ALLOW_ORIGIN = "https://legacy.com";
80+
process.env.TRILIUM_SYNC_SERVER_HOST = "legacy-sync.com";
81+
process.env.TRILIUM_OAUTH_BASE_URL = "https://legacy-auth.com";
82+
process.env.TRILIUM_LOGGING_RETENTION_DAYS = "60";
83+
84+
const { default: config } = await import("./config.js");
85+
86+
expect(config.Network.corsAllowOrigin).toBe("https://legacy.com");
87+
expect(config.Sync.syncServerHost).toBe("legacy-sync.com");
88+
expect(config.MultiFactorAuthentication.oauthBaseUrl).toBe("https://legacy-auth.com");
89+
expect(config.Logging.retentionDays).toBe(60);
90+
});
91+
92+
it("should prioritize standard env vars over aliases when both are set", async () => {
93+
// Set both standard and alias env vars - standard should win
94+
process.env.TRILIUM_NETWORK_CORSALLOWORIGIN = "standard-cors.com";
95+
process.env.TRILIUM_NETWORK_CORS_ALLOW_ORIGIN = "alias-cors.com";
96+
97+
process.env.TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHBASEURL = "standard-auth.com";
98+
process.env.TRILIUM_OAUTH_BASE_URL = "alias-auth.com";
99+
100+
const { default: config } = await import("./config.js");
101+
102+
expect(config.Network.corsAllowOrigin).toBe("standard-cors.com");
103+
expect(config.MultiFactorAuthentication.oauthBaseUrl).toBe("standard-auth.com");
104+
});
105+
106+
it("should handle all CORS environment variables correctly", async () => {
107+
// Test with standard naming
108+
process.env.TRILIUM_NETWORK_CORSALLOWORIGIN = "*";
109+
process.env.TRILIUM_NETWORK_CORSALLOWMETHODS = "GET,POST,PUT";
110+
process.env.TRILIUM_NETWORK_CORSALLOWHEADERS = "Content-Type,Authorization";
111+
112+
let { default: config } = await import("./config.js");
113+
114+
expect(config.Network.corsAllowOrigin).toBe("*");
115+
expect(config.Network.corsAllowMethods).toBe("GET,POST,PUT");
116+
expect(config.Network.corsAllowHeaders).toBe("Content-Type,Authorization");
117+
118+
// Clear and test with alias naming
119+
delete process.env.TRILIUM_NETWORK_CORSALLOWORIGIN;
120+
delete process.env.TRILIUM_NETWORK_CORSALLOWMETHODS;
121+
delete process.env.TRILIUM_NETWORK_CORSALLOWHEADERS;
122+
123+
process.env.TRILIUM_NETWORK_CORS_ALLOW_ORIGIN = "https://app.com";
124+
process.env.TRILIUM_NETWORK_CORS_ALLOW_METHODS = "GET,POST";
125+
process.env.TRILIUM_NETWORK_CORS_ALLOW_HEADERS = "X-Custom-Header";
126+
127+
vi.resetModules();
128+
config = (await import("./config.js")).default;
129+
130+
expect(config.Network.corsAllowOrigin).toBe("https://app.com");
131+
expect(config.Network.corsAllowMethods).toBe("GET,POST");
132+
expect(config.Network.corsAllowHeaders).toBe("X-Custom-Header");
133+
});
134+
135+
it("should handle all OAuth/MFA environment variables correctly", async () => {
136+
// Test with standard naming
137+
process.env.TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHBASEURL = "https://oauth.standard.com";
138+
process.env.TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHCLIENTID = "standard-client-id";
139+
process.env.TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHCLIENTSECRET = "standard-secret";
140+
process.env.TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERBASEURL = "https://issuer.standard.com";
141+
process.env.TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERNAME = "Standard Auth";
142+
process.env.TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERICON = "standard-icon.png";
143+
144+
let { default: config } = await import("./config.js");
145+
146+
expect(config.MultiFactorAuthentication.oauthBaseUrl).toBe("https://oauth.standard.com");
147+
expect(config.MultiFactorAuthentication.oauthClientId).toBe("standard-client-id");
148+
expect(config.MultiFactorAuthentication.oauthClientSecret).toBe("standard-secret");
149+
expect(config.MultiFactorAuthentication.oauthIssuerBaseUrl).toBe("https://issuer.standard.com");
150+
expect(config.MultiFactorAuthentication.oauthIssuerName).toBe("Standard Auth");
151+
expect(config.MultiFactorAuthentication.oauthIssuerIcon).toBe("standard-icon.png");
152+
153+
// Clear and test with alias naming
154+
Object.keys(process.env).forEach(key => {
155+
if (key.startsWith("TRILIUM_MULTIFACTORAUTHENTICATION_")) {
156+
delete process.env[key];
157+
}
158+
});
159+
160+
process.env.TRILIUM_OAUTH_BASE_URL = "https://oauth.alias.com";
161+
process.env.TRILIUM_OAUTH_CLIENT_ID = "alias-client-id";
162+
process.env.TRILIUM_OAUTH_CLIENT_SECRET = "alias-secret";
163+
process.env.TRILIUM_OAUTH_ISSUER_BASE_URL = "https://issuer.alias.com";
164+
process.env.TRILIUM_OAUTH_ISSUER_NAME = "Alias Auth";
165+
process.env.TRILIUM_OAUTH_ISSUER_ICON = "alias-icon.png";
166+
167+
vi.resetModules();
168+
config = (await import("./config.js")).default;
169+
170+
expect(config.MultiFactorAuthentication.oauthBaseUrl).toBe("https://oauth.alias.com");
171+
expect(config.MultiFactorAuthentication.oauthClientId).toBe("alias-client-id");
172+
expect(config.MultiFactorAuthentication.oauthClientSecret).toBe("alias-secret");
173+
expect(config.MultiFactorAuthentication.oauthIssuerBaseUrl).toBe("https://issuer.alias.com");
174+
expect(config.MultiFactorAuthentication.oauthIssuerName).toBe("Alias Auth");
175+
expect(config.MultiFactorAuthentication.oauthIssuerIcon).toBe("alias-icon.png");
176+
});
177+
178+
it("should handle all Sync environment variables correctly", async () => {
179+
// Test with standard naming
180+
process.env.TRILIUM_SYNC_SYNCSERVERHOST = "sync-standard.com";
181+
process.env.TRILIUM_SYNC_SYNCSERVERTIMEOUT = "60000";
182+
process.env.TRILIUM_SYNC_SYNCPROXY = "proxy-standard.com";
183+
184+
let { default: config } = await import("./config.js");
185+
186+
expect(config.Sync.syncServerHost).toBe("sync-standard.com");
187+
expect(config.Sync.syncServerTimeout).toBe("60000");
188+
expect(config.Sync.syncProxy).toBe("proxy-standard.com");
189+
190+
// Clear and test with alias naming
191+
delete process.env.TRILIUM_SYNC_SYNCSERVERHOST;
192+
delete process.env.TRILIUM_SYNC_SYNCSERVERTIMEOUT;
193+
delete process.env.TRILIUM_SYNC_SYNCPROXY;
194+
195+
process.env.TRILIUM_SYNC_SERVER_HOST = "sync-alias.com";
196+
process.env.TRILIUM_SYNC_SERVER_TIMEOUT = "30000";
197+
process.env.TRILIUM_SYNC_SERVER_PROXY = "proxy-alias.com";
198+
199+
vi.resetModules();
200+
config = (await import("./config.js")).default;
201+
202+
expect(config.Sync.syncServerHost).toBe("sync-alias.com");
203+
expect(config.Sync.syncServerTimeout).toBe("30000");
204+
expect(config.Sync.syncProxy).toBe("proxy-alias.com");
205+
});
206+
});
207+
208+
describe("INI Config Integration", () => {
209+
it("should fall back to INI config when no env vars are set", async () => {
210+
// Mock INI config with values
211+
vi.mocked(fs.readFileSync).mockImplementation((path) => {
212+
if (String(path).includes("config-sample.ini")) {
213+
return "" as any;
214+
}
215+
return `
216+
[General]
217+
instanceName=ini-instance
218+
219+
[Network]
220+
corsAllowOrigin=https://ini-cors.com
221+
port=9000
222+
223+
[Sync]
224+
syncServerHost=ini-sync.com
225+
226+
[MultiFactorAuthentication]
227+
oauthBaseUrl=https://ini-oauth.com
228+
229+
[Logging]
230+
retentionDays=45
231+
` as any;
232+
});
233+
234+
const { default: config } = await import("./config.js");
235+
236+
expect(config.General.instanceName).toBe("ini-instance");
237+
expect(config.Network.corsAllowOrigin).toBe("https://ini-cors.com");
238+
expect(config.Network.port).toBe("9000");
239+
expect(config.Sync.syncServerHost).toBe("ini-sync.com");
240+
expect(config.MultiFactorAuthentication.oauthBaseUrl).toBe("https://ini-oauth.com");
241+
expect(config.Logging.retentionDays).toBe(45);
242+
});
243+
244+
it("should prioritize env vars over INI config", async () => {
245+
// Mock INI config with values
246+
vi.mocked(fs.readFileSync).mockImplementation((path) => {
247+
if (String(path).includes("config-sample.ini")) {
248+
return "" as any;
249+
}
250+
return `
251+
[General]
252+
instanceName=ini-instance
253+
254+
[Network]
255+
corsAllowOrigin=https://ini-cors.com
256+
` as any;
257+
});
258+
259+
// Set env vars that should override INI
260+
process.env.TRILIUM_GENERAL_INSTANCENAME = "env-instance";
261+
process.env.TRILIUM_NETWORK_CORS_ALLOW_ORIGIN = "https://env-cors.com"; // Using alias
262+
263+
const { default: config } = await import("./config.js");
264+
265+
expect(config.General.instanceName).toBe("env-instance");
266+
expect(config.Network.corsAllowOrigin).toBe("https://env-cors.com");
267+
});
268+
});
269+
270+
describe("Type Transformations", () => {
271+
it("should correctly transform boolean values", async () => {
272+
process.env.TRILIUM_GENERAL_NOAUTHENTICATION = "true";
273+
process.env.TRILIUM_GENERAL_NOBACKUP = "1";
274+
process.env.TRILIUM_GENERAL_READONLY = "false";
275+
process.env.TRILIUM_NETWORK_HTTPS = "0";
276+
277+
const { default: config } = await import("./config.js");
278+
279+
expect(config.General.noAuthentication).toBe(true);
280+
expect(config.General.noBackup).toBe(true);
281+
expect(config.General.readOnly).toBe(false);
282+
expect(config.Network.https).toBe(false);
283+
});
284+
285+
it("should correctly transform integer values", async () => {
286+
process.env.TRILIUM_SESSION_COOKIEMAXAGE = "3600";
287+
process.env.TRILIUM_LOGGING_RETENTIONDAYS = "7";
288+
289+
const { default: config } = await import("./config.js");
290+
291+
expect(config.Session.cookieMaxAge).toBe(3600);
292+
expect(config.Logging.retentionDays).toBe(7);
293+
});
294+
295+
it("should use default values for invalid integers", async () => {
296+
process.env.TRILIUM_SESSION_COOKIEMAXAGE = "invalid";
297+
process.env.TRILIUM_LOGGING_RETENTION_DAYS = "not-a-number"; // Using alias
298+
299+
const { default: config } = await import("./config.js");
300+
301+
expect(config.Session.cookieMaxAge).toBe(21 * 24 * 60 * 60); // Default
302+
expect(config.Logging.retentionDays).toBe(90); // Default
303+
});
304+
});
305+
306+
describe("Default Values", () => {
307+
it("should use correct default values when no config is provided", async () => {
308+
const { default: config } = await import("./config.js");
309+
310+
// General defaults
311+
expect(config.General.instanceName).toBe("");
312+
expect(config.General.noAuthentication).toBe(false);
313+
expect(config.General.noBackup).toBe(false);
314+
expect(config.General.noDesktopIcon).toBe(false);
315+
expect(config.General.readOnly).toBe(false);
316+
317+
// Network defaults
318+
expect(config.Network.host).toBe("0.0.0.0");
319+
expect(config.Network.port).toBe("3000");
320+
expect(config.Network.https).toBe(false);
321+
expect(config.Network.certPath).toBe("");
322+
expect(config.Network.keyPath).toBe("");
323+
expect(config.Network.trustedReverseProxy).toBe(false);
324+
expect(config.Network.corsAllowOrigin).toBe("");
325+
expect(config.Network.corsAllowMethods).toBe("");
326+
expect(config.Network.corsAllowHeaders).toBe("");
327+
328+
// Session defaults
329+
expect(config.Session.cookieMaxAge).toBe(21 * 24 * 60 * 60);
330+
331+
// Sync defaults
332+
expect(config.Sync.syncServerHost).toBe("");
333+
expect(config.Sync.syncServerTimeout).toBe("120000");
334+
expect(config.Sync.syncProxy).toBe("");
335+
336+
// OAuth defaults
337+
expect(config.MultiFactorAuthentication.oauthBaseUrl).toBe("");
338+
expect(config.MultiFactorAuthentication.oauthClientId).toBe("");
339+
expect(config.MultiFactorAuthentication.oauthClientSecret).toBe("");
340+
expect(config.MultiFactorAuthentication.oauthIssuerBaseUrl).toBe("https://accounts.google.com");
341+
expect(config.MultiFactorAuthentication.oauthIssuerName).toBe("Google");
342+
expect(config.MultiFactorAuthentication.oauthIssuerIcon).toBe("");
343+
344+
// Logging defaults
345+
expect(config.Logging.retentionDays).toBe(90);
346+
});
347+
});
348+
});

0 commit comments

Comments
 (0)