refactor progress

This commit is contained in:
Hampus Kraft
2026-02-17 12:22:36 +00:00
parent cb31608523
commit d5abd1a7e4
8257 changed files with 1190207 additions and 761040 deletions

View File

@@ -0,0 +1,236 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {mkdtempSync, rmSync, writeFileSync} from 'node:fs';
import {tmpdir} from 'node:os';
import path from 'node:path';
import {getConfig, loadConfig, resetConfig} from '@fluxer/config/src/ConfigLoader';
import {type ConfigObject, deepMerge} from '@fluxer/config/src/config_loader/ConfigObjectMerge';
import {afterEach, beforeEach, describe, expect, test, vi} from 'vitest';
function createTempConfig(config: Record<string, unknown>): string {
const dir = mkdtempSync(path.join(tmpdir(), 'fluxer-config-test-'));
const configPath = path.join(dir, 'config.json');
writeFileSync(configPath, JSON.stringify(config));
return configPath;
}
function makeMinimalConfig(overrides: Record<string, unknown> = {}): Record<string, unknown> {
const base: ConfigObject = {
env: 'test',
domain: {
base_domain: 'localhost',
public_port: 8080,
},
database: {
backend: 'sqlite',
sqlite_path: ':memory:',
},
s3: {
access_key_id: 'test-key',
secret_access_key: 'test-secret',
endpoint: 'http://localhost:9000',
},
services: {
server: {port: 8772, host: '0.0.0.0'},
media_proxy: {secret_key: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'},
admin: {
secret_key_base: 'abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789',
oauth_client_secret: 'fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210',
},
app_proxy: {port: 8773, sentry_report_host: 'sentry.io', sentry_dsn: 'https://test@sentry.io/1'},
marketing: {
enabled: false,
port: 8774,
host: '0.0.0.0',
secret_key_base: 'marketing0123456789abcdef0123456789abcdef0123456789abcdef01234567',
},
gateway: {
port: 8771,
api_host: 'http://localhost:8772/api',
admin_reload_secret: 'deadbeef0123456789abcdef0123456789abcdef0123456789abcdef01234567',
media_proxy_endpoint: 'http://localhost:8772/media',
},
},
gateway: {
rpc_secret: 'rpc-test-secret',
},
auth: {
sudo_mode_secret: 'sudo-test-secret',
connection_initiation_secret: 'connection-initiation-test-secret',
vapid: {
public_key: 'test-vapid-public-key',
private_key: 'test-vapid-private-key',
},
},
integrations: {
search: {
url: 'http://127.0.0.1:7700',
api_key: 'test-search-key',
},
},
};
return deepMerge(base, overrides as ConfigObject);
}
describe('ConfigLoader', () => {
let tempPaths: Array<string> = [];
beforeEach(() => {
resetConfig();
});
afterEach(() => {
resetConfig();
for (const p of tempPaths) {
try {
rmSync(path.dirname(p), {recursive: true, force: true});
} catch {}
}
tempPaths = [];
});
test('loadConfig loads and caches config', async () => {
const configPath = createTempConfig(makeMinimalConfig());
tempPaths.push(configPath);
const config = await loadConfig([configPath]);
expect(config.env).toBe('test');
expect(config.domain.base_domain).toBe('localhost');
});
test('getConfig throws when config is not loaded', () => {
expect(() => getConfig()).toThrow('Config not loaded');
});
test('resetConfig clears the cache', async () => {
const configPath = createTempConfig(makeMinimalConfig());
tempPaths.push(configPath);
await loadConfig([configPath]);
expect(() => getConfig()).not.toThrow();
resetConfig();
expect(() => getConfig()).toThrow('Config not loaded');
});
test('throws when no config file is found', async () => {
await expect(loadConfig(['/nonexistent/path.json'])).rejects.toThrow('No config file found');
});
test('throws when config paths array is empty', async () => {
await expect(loadConfig([])).rejects.toThrow('FLUXER_CONFIG must be set');
});
test('derives endpoints from domain config', async () => {
const configPath = createTempConfig(makeMinimalConfig());
tempPaths.push(configPath);
const config = await loadConfig([configPath]);
expect(config.endpoints.api).toContain('localhost');
expect(config.endpoints.api).toContain('/api');
expect(config.endpoints.gateway).toContain('ws');
});
test('endpoint_overrides take precedence over derived endpoints', async () => {
const configPath = createTempConfig(
makeMinimalConfig({
endpoint_overrides: {
api: 'https://custom-api.example.com',
api_client: 'https://custom-api-client.example.com',
gateway: 'wss://custom-gw.example.com',
},
}),
);
tempPaths.push(configPath);
const config = await loadConfig([configPath]);
expect(config.endpoints.api).toBe('https://custom-api.example.com');
expect(config.endpoints.api_client).toBe('https://custom-api-client.example.com');
expect(config.endpoints.gateway).toBe('wss://custom-gw.example.com');
expect(config.endpoints.app).toContain('localhost');
});
test('allows unknown properties (but warns)', async () => {
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
const configPath = createTempConfig(
makeMinimalConfig({
extra_root: true,
auth: {
sudo_mode_secret: 'sudo-test-secret',
connection_initiation_secret: 'connection-initiation-test-secret',
vapid: {
public_key: 'test-vapid-public-key',
private_key: 'test-vapid-private-key',
},
extra_auth: 'oops',
},
}),
);
tempPaths.push(configPath);
await expect(loadConfig([configPath])).resolves.toBeDefined();
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('"extra_root"'));
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('"extra_auth"'));
warnSpy.mockRestore();
});
test('allows voice enabled without global credentials when default_region is omitted', async () => {
const configPath = createTempConfig(
makeMinimalConfig({
integrations: {
voice: {
enabled: true,
},
},
}),
);
tempPaths.push(configPath);
const config = await loadConfig([configPath]);
expect(config.integrations.voice.enabled).toBe(true);
expect(config.integrations.voice.api_key).toBeUndefined();
expect(config.integrations.voice.api_secret).toBeUndefined();
});
test('requires voice credentials when default_region bootstrap is configured', async () => {
const configPath = createTempConfig(
makeMinimalConfig({
integrations: {
voice: {
enabled: true,
default_region: {
id: 'default',
name: 'Default',
emoji: ':earth_africa:',
latitude: 0,
longitude: 0,
},
},
},
}),
);
tempPaths.push(configPath);
await expect(loadConfig([configPath])).rejects.toThrow('api_key');
await expect(loadConfig([configPath])).rejects.toThrow('api_secret');
});
});

View File

@@ -0,0 +1,82 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {deepMerge, isPlainObject} from '@fluxer/config/src/config_loader/ConfigObjectMerge';
import {describe, expect, test} from 'vitest';
describe('isPlainObject', () => {
test('returns true for plain objects', () => {
expect(isPlainObject({})).toBe(true);
expect(isPlainObject({a: 1})).toBe(true);
});
test('returns false for arrays', () => {
expect(isPlainObject([])).toBe(false);
expect(isPlainObject([1, 2])).toBe(false);
});
test('returns false for null and primitives', () => {
expect(isPlainObject(null)).toBe(false);
expect(isPlainObject(undefined)).toBe(false);
expect(isPlainObject(42)).toBe(false);
expect(isPlainObject('string')).toBe(false);
expect(isPlainObject(true)).toBe(false);
});
});
describe('deepMerge', () => {
test('merges flat objects', () => {
const result = deepMerge({a: 1, b: 2}, {b: 3, c: 4});
expect(result).toEqual({a: 1, b: 3, c: 4});
});
test('merges nested objects recursively', () => {
const target = {database: {host: 'localhost', port: 5432}};
const source = {database: {port: 3306, name: 'test'}};
const result = deepMerge(target, source);
expect(result).toEqual({database: {host: 'localhost', port: 3306, name: 'test'}});
});
test('replaces arrays instead of merging them', () => {
const target = {tags: ['a', 'b']};
const source = {tags: ['c']};
const result = deepMerge(target, source);
expect(result).toEqual({tags: ['c']});
});
test('source overrides target for non-object values', () => {
const target = {a: 'old', b: {nested: true}};
const source = {a: 'new', b: 'replaced'};
const result = deepMerge(target, source);
expect(result).toEqual({a: 'new', b: 'replaced'});
});
test('does not mutate the target', () => {
const target = {a: 1, nested: {b: 2}};
const source = {a: 99, nested: {c: 3}};
deepMerge(target, source);
expect(target).toEqual({a: 1, nested: {b: 2}});
});
test('handles empty objects', () => {
expect(deepMerge({}, {a: 1})).toEqual({a: 1});
expect(deepMerge({a: 1}, {})).toEqual({a: 1});
expect(deepMerge({}, {})).toEqual({});
});
});

View File

@@ -0,0 +1,388 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {
buildUrl,
type DomainConfig,
deriveDomain,
deriveEndpointsFromDomain,
} from '@fluxer/config/src/EndpointDerivation';
import {describe, expect, test} from 'vitest';
describe('buildUrl', () => {
test('omits standard HTTP port (80)', () => {
expect(buildUrl('http', 'example.com', 80, '/path')).toBe('http://example.com/path');
});
test('omits standard HTTPS port (443)', () => {
expect(buildUrl('https', 'example.com', 443, '/path')).toBe('https://example.com/path');
});
test('omits standard WebSocket port (80)', () => {
expect(buildUrl('ws', 'example.com', 80, '/gateway')).toBe('ws://example.com/gateway');
});
test('omits standard secure WebSocket port (443)', () => {
expect(buildUrl('wss', 'example.com', 443, '/gateway')).toBe('wss://example.com/gateway');
});
test('includes non-standard port', () => {
expect(buildUrl('http', 'localhost', 8088, '/api')).toBe('http://localhost:8088/api');
});
test('includes non-standard HTTPS port', () => {
expect(buildUrl('https', 'example.com', 8443, '/api')).toBe('https://example.com:8443/api');
});
test('handles missing port', () => {
expect(buildUrl('https', 'example.com', undefined, '/api')).toBe('https://example.com/api');
});
test('handles missing path', () => {
expect(buildUrl('https', 'example.com', 443)).toBe('https://example.com');
});
test('handles empty path', () => {
expect(buildUrl('https', 'example.com', 443, '')).toBe('https://example.com');
});
test('handles root path', () => {
expect(buildUrl('https', 'example.com', 443, '/')).toBe('https://example.com/');
});
});
describe('deriveDomain', () => {
const baseConfig: DomainConfig = {
base_domain: 'fluxer.dev',
public_scheme: 'https',
internal_scheme: 'http',
};
test('uses base domain for api endpoint', () => {
expect(deriveDomain('api', baseConfig)).toBe('fluxer.dev');
});
test('uses base domain for app endpoint', () => {
expect(deriveDomain('app', baseConfig)).toBe('fluxer.dev');
});
test('uses base domain for gateway endpoint', () => {
expect(deriveDomain('gateway', baseConfig)).toBe('fluxer.dev');
});
test('uses base domain for media endpoint', () => {
expect(deriveDomain('media', baseConfig)).toBe('fluxer.dev');
});
test('uses custom static CDN domain when specified', () => {
const config = {...baseConfig, static_cdn_domain: 'cdn.fluxer.dev'};
expect(deriveDomain('static_cdn', config)).toBe('cdn.fluxer.dev');
});
test('uses base domain for static CDN when custom domain not specified', () => {
expect(deriveDomain('static_cdn', baseConfig)).toBe('fluxerstatic.com');
});
test('uses custom invite domain when specified', () => {
const config = {...baseConfig, invite_domain: 'fluxer.gg'};
expect(deriveDomain('invite', config)).toBe('fluxer.gg');
});
test('uses base domain for invite when custom domain not specified', () => {
expect(deriveDomain('invite', baseConfig)).toBe('fluxer.dev');
});
test('uses custom gift domain when specified', () => {
const config = {...baseConfig, gift_domain: 'fluxer.gift'};
expect(deriveDomain('gift', config)).toBe('fluxer.gift');
});
test('uses base domain for gift when custom domain not specified', () => {
expect(deriveDomain('gift', baseConfig)).toBe('fluxer.dev');
});
});
describe('deriveEndpointsFromDomain', () => {
describe('development environment (localhost)', () => {
const devConfig: DomainConfig = {
base_domain: 'localhost',
public_scheme: 'http',
internal_scheme: 'http',
public_port: 8088,
internal_port: 8088,
};
const endpoints = deriveEndpointsFromDomain(devConfig);
test('derives api endpoint with port', () => {
expect(endpoints.api).toBe('http://localhost:8088/api');
});
test('derives api client endpoint with port', () => {
expect(endpoints.api_client).toBe('http://localhost:8088/api');
});
test('derives app endpoint with port', () => {
expect(endpoints.app).toBe('http://localhost:8088');
});
test('derives gateway endpoint with ws scheme', () => {
expect(endpoints.gateway).toBe('ws://localhost:8088/gateway');
});
test('derives media endpoint with port', () => {
expect(endpoints.media).toBe('http://localhost:8088/media');
});
test('derives static CDN endpoint via CDN host', () => {
expect(endpoints.static_cdn).toBe('https://fluxerstatic.com');
});
test('derives admin endpoint with port', () => {
expect(endpoints.admin).toBe('http://localhost:8088/admin');
});
test('derives marketing endpoint with port', () => {
expect(endpoints.marketing).toBe('http://localhost:8088/marketing');
});
test('derives invite endpoint with port', () => {
expect(endpoints.invite).toBe('http://localhost:8088/invite');
});
test('derives gift endpoint with port', () => {
expect(endpoints.gift).toBe('http://localhost:8088/gift');
});
});
describe('production environment (standard HTTPS port)', () => {
const prodConfig: DomainConfig = {
base_domain: 'fluxer.app',
public_scheme: 'https',
internal_scheme: 'http',
public_port: 443,
internal_port: 8080,
};
const endpoints = deriveEndpointsFromDomain(prodConfig);
test('derives api endpoint without port', () => {
expect(endpoints.api).toBe('https://fluxer.app/api');
});
test('derives api client endpoint without port', () => {
expect(endpoints.api_client).toBe('https://fluxer.app/api');
});
test('derives app endpoint without port', () => {
expect(endpoints.app).toBe('https://fluxer.app');
});
test('derives gateway endpoint with wss scheme without port', () => {
expect(endpoints.gateway).toBe('wss://fluxer.app/gateway');
});
test('derives media endpoint without port', () => {
expect(endpoints.media).toBe('https://fluxer.app/media');
});
test('derives static CDN endpoint without port', () => {
expect(endpoints.static_cdn).toBe('https://fluxerstatic.com');
});
test('derives admin endpoint without port', () => {
expect(endpoints.admin).toBe('https://fluxer.app/admin');
});
test('derives marketing endpoint without port', () => {
expect(endpoints.marketing).toBe('https://fluxer.app/marketing');
});
test('derives invite endpoint without port', () => {
expect(endpoints.invite).toBe('https://fluxer.app/invite');
});
test('derives gift endpoint without port', () => {
expect(endpoints.gift).toBe('https://fluxer.app/gift');
});
});
describe('staging environment (custom port)', () => {
const stagingConfig: DomainConfig = {
base_domain: 'staging.fluxer.dev',
public_scheme: 'https',
internal_scheme: 'http',
public_port: 8443,
internal_port: 8080,
};
const endpoints = deriveEndpointsFromDomain(stagingConfig);
test('derives api endpoint with custom port', () => {
expect(endpoints.api).toBe('https://staging.fluxer.dev:8443/api');
});
test('derives api client endpoint with custom port', () => {
expect(endpoints.api_client).toBe('https://staging.fluxer.dev:8443/api');
});
test('derives app endpoint with custom port', () => {
expect(endpoints.app).toBe('https://staging.fluxer.dev:8443');
});
test('derives gateway endpoint with wss and custom port', () => {
expect(endpoints.gateway).toBe('wss://staging.fluxer.dev:8443/gateway');
});
});
describe('custom CDN domain', () => {
const staticCdnConfig: DomainConfig = {
base_domain: 'fluxer.app',
public_scheme: 'https',
internal_scheme: 'http',
public_port: 443,
static_cdn_domain: 'cdn.fluxer.app',
};
const endpoints = deriveEndpointsFromDomain(staticCdnConfig);
test('uses custom CDN domain', () => {
expect(endpoints.static_cdn).toBe('https://cdn.fluxer.app');
});
test('other endpoints use base domain', () => {
expect(endpoints.api).toBe('https://fluxer.app/api');
expect(endpoints.app).toBe('https://fluxer.app');
});
});
describe('custom invite and gift domains', () => {
const customConfig: DomainConfig = {
base_domain: 'fluxer.app',
public_scheme: 'https',
internal_scheme: 'http',
public_port: 443,
invite_domain: 'fluxer.gg',
gift_domain: 'fluxer.gift',
};
const endpoints = deriveEndpointsFromDomain(customConfig);
test('uses custom invite domain', () => {
expect(endpoints.invite).toBe('https://fluxer.gg/invite');
});
test('uses custom gift domain', () => {
expect(endpoints.gift).toBe('https://fluxer.gift/gift');
});
test('other endpoints use base domain', () => {
expect(endpoints.api).toBe('https://fluxer.app/api');
expect(endpoints.app).toBe('https://fluxer.app');
});
});
describe('WebSocket scheme derivation', () => {
test('derives ws from http', () => {
const config: DomainConfig = {
base_domain: 'localhost',
public_scheme: 'http',
internal_scheme: 'http',
public_port: 8088,
};
const endpoints = deriveEndpointsFromDomain(config);
expect(endpoints.gateway).toBe('ws://localhost:8088/gateway');
});
test('derives wss from https', () => {
const config: DomainConfig = {
base_domain: 'fluxer.app',
public_scheme: 'https',
internal_scheme: 'http',
public_port: 443,
};
const endpoints = deriveEndpointsFromDomain(config);
expect(endpoints.gateway).toBe('wss://fluxer.app/gateway');
});
});
describe('canary environment', () => {
const canaryConfig: DomainConfig = {
base_domain: 'canary.fluxer.app',
public_scheme: 'https',
internal_scheme: 'http',
public_port: 443,
static_cdn_domain: 'cdn-canary.fluxer.app',
};
const endpoints = deriveEndpointsFromDomain(canaryConfig);
test('derives api endpoint for canary', () => {
expect(endpoints.api).toBe('https://canary.fluxer.app/api');
});
test('derives app endpoint for canary', () => {
expect(endpoints.app).toBe('https://canary.fluxer.app');
});
test('derives gateway endpoint for canary', () => {
expect(endpoints.gateway).toBe('wss://canary.fluxer.app/gateway');
});
test('uses custom CDN domain for canary', () => {
expect(endpoints.static_cdn).toBe('https://cdn-canary.fluxer.app');
});
});
describe('edge cases', () => {
test('handles standard HTTP port (80)', () => {
const config: DomainConfig = {
base_domain: 'example.com',
public_scheme: 'http',
internal_scheme: 'http',
public_port: 80,
};
const endpoints = deriveEndpointsFromDomain(config);
expect(endpoints.api).toBe('http://example.com/api');
expect(endpoints.gateway).toBe('ws://example.com/gateway');
});
test('handles ports when undefined', () => {
const config: DomainConfig = {
base_domain: 'example.com',
public_scheme: 'https',
internal_scheme: 'http',
};
const endpoints = deriveEndpointsFromDomain(config);
expect(endpoints.api).toBe('https://example.com/api');
expect(endpoints.app).toBe('https://example.com');
});
test('handles IPv4 addresses', () => {
const config: DomainConfig = {
base_domain: '127.0.0.1',
public_scheme: 'http',
internal_scheme: 'http',
public_port: 8088,
};
const endpoints = deriveEndpointsFromDomain(config);
expect(endpoints.api).toBe('http://127.0.0.1:8088/api');
});
});
});

View File

@@ -0,0 +1,154 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {buildEnvOverrides, parseEnvValue, setNestedValue} from '@fluxer/config/src/config_loader/EnvironmentOverrides';
import {describe, expect, test} from 'vitest';
describe('parseEnvValue', () => {
test('parses boolean true', () => {
expect(parseEnvValue('true')).toBe(true);
expect(parseEnvValue(' true ')).toBe(true);
});
test('parses boolean false', () => {
expect(parseEnvValue('false')).toBe(false);
expect(parseEnvValue(' false ')).toBe(false);
});
test('parses integers', () => {
expect(parseEnvValue('42')).toBe(42);
expect(parseEnvValue('-7')).toBe(-7);
expect(parseEnvValue('0')).toBe(0);
});
test('parses floats', () => {
expect(parseEnvValue('3.14')).toBe(3.14);
expect(parseEnvValue('-0.5')).toBe(-0.5);
});
test('parses JSON objects', () => {
expect(parseEnvValue('{"key": "value"}')).toEqual({key: 'value'});
});
test('parses JSON arrays', () => {
expect(parseEnvValue('[1, 2, 3]')).toEqual([1, 2, 3]);
});
test('returns raw string for invalid JSON-like values', () => {
expect(parseEnvValue('{not json}')).toBe('{not json}');
});
test('returns raw string for plain strings', () => {
expect(parseEnvValue('hello')).toBe('hello');
expect(parseEnvValue('localhost')).toBe('localhost');
});
});
describe('setNestedValue', () => {
test('sets a top-level key', () => {
const target: Record<string, unknown> = {};
setNestedValue(target, ['port'], 8080);
expect(target).toEqual({port: 8080});
});
test('sets a nested key', () => {
const target: Record<string, unknown> = {};
setNestedValue(target, ['database', 'host'], 'localhost');
expect(target).toEqual({database: {host: 'localhost'}});
});
test('sets a deeply nested key', () => {
const target: Record<string, unknown> = {};
setNestedValue(target, ['a', 'b', 'c'], 'deep');
expect(target).toEqual({a: {b: {c: 'deep'}}});
});
test('does nothing for empty keys', () => {
const target: Record<string, unknown> = {existing: true};
setNestedValue(target, [], 'value');
expect(target).toEqual({existing: true});
});
test('overwrites non-object intermediate values', () => {
const target: Record<string, unknown> = {a: 'string'};
setNestedValue(target, ['a', 'b'], 'nested');
expect(target).toEqual({a: {b: 'nested'}});
});
});
describe('buildEnvOverrides', () => {
test('extracts env vars with the given prefix', () => {
const env = {
FLUXER_CONFIG__ENV: 'production',
FLUXER_CONFIG__DATABASE__HOST: 'db.example.com',
UNRELATED_VAR: 'ignored',
} as NodeJS.ProcessEnv;
const result = buildEnvOverrides(env, 'FLUXER_CONFIG__');
expect(result).toEqual({
env: 'production',
database: {host: 'db.example.com'},
});
});
test('lowercases key segments', () => {
const env = {
FLUXER_CONFIG__DATABASE__PORT: '5432',
} as NodeJS.ProcessEnv;
const result = buildEnvOverrides(env, 'FLUXER_CONFIG__');
expect(result).toEqual({database: {port: 5432}});
});
test('skips keys that are exactly the prefix with no remainder', () => {
const env = {
FLUXER_CONFIG__: 'ignored',
} as NodeJS.ProcessEnv;
const result = buildEnvOverrides(env, 'FLUXER_CONFIG__');
expect(result).toEqual({});
});
test('skips undefined values', () => {
const env = {
FLUXER_CONFIG__MISSING: undefined,
} as NodeJS.ProcessEnv;
const result = buildEnvOverrides(env, 'FLUXER_CONFIG__');
expect(result).toEqual({});
});
test('supports custom prefix', () => {
const env = {
MYAPP__PORT: '3000',
} as NodeJS.ProcessEnv;
const result = buildEnvOverrides(env, 'MYAPP__');
expect(result).toEqual({port: 3000});
});
test('returns empty object when no matching vars exist', () => {
const env = {
UNRELATED: 'value',
} as NodeJS.ProcessEnv;
const result = buildEnvOverrides(env, 'FLUXER_CONFIG__');
expect(result).toEqual({});
});
});

View File

@@ -0,0 +1,102 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import type {MasterConfig} from '@fluxer/config/src/MasterZodSchema.generated';
import {
extractBaseServiceConfig,
extractBuildInfoConfig,
extractKVClientConfig,
extractRateLimit,
} from '@fluxer/config/src/ServiceConfigSlices';
import {describe, expect, test} from 'vitest';
function createMasterStub(overrides: Partial<MasterConfig> = {}): MasterConfig {
return {
env: 'development',
telemetry: {enabled: false, otlp_endpoint: 'http://localhost:4318', api_key: '', trace_sampling_ratio: 1},
sentry: {enabled: false, dsn: ''},
internal: {
kv: 'redis://127.0.0.1:6379/0',
media_proxy: 'http://localhost:8088/media',
},
services: {} as MasterConfig['services'],
...overrides,
} as MasterConfig;
}
describe('extractBaseServiceConfig', () => {
test('returns env, telemetry, and sentry from master config', () => {
const master = createMasterStub({env: 'production'});
const result = extractBaseServiceConfig(master);
expect(result).toEqual({
env: 'production',
telemetry: master.telemetry,
sentry: master.sentry,
});
});
});
describe('extractKVClientConfig', () => {
test('returns kvUrl', () => {
const master = createMasterStub();
const result = extractKVClientConfig(master);
expect(result).toEqual({
kvUrl: 'redis://127.0.0.1:6379/0',
});
});
test('throws when internal is missing', () => {
const master = createMasterStub();
(master as Record<string, unknown>).internal = undefined;
expect(() => extractKVClientConfig(master)).toThrow('internal configuration is required');
});
});
describe('extractBuildInfoConfig', () => {
test('returns releaseChannel and buildTimestamp', () => {
const result = extractBuildInfoConfig();
expect(result).toHaveProperty('releaseChannel');
expect(result).toHaveProperty('buildTimestamp');
expect(typeof result.releaseChannel).toBe('string');
expect(typeof result.buildTimestamp).toBe('string');
});
});
describe('extractRateLimit', () => {
test('returns undefined for null input', () => {
expect(extractRateLimit(null)).toBeUndefined();
});
test('returns undefined for undefined input', () => {
expect(extractRateLimit(undefined)).toBeUndefined();
});
test('returns undefined when limit is missing', () => {
expect(extractRateLimit({window_ms: 60000})).toBeUndefined();
});
test('returns undefined when window_ms is missing', () => {
expect(extractRateLimit({limit: 100})).toBeUndefined();
});
test('returns normalised object for valid input', () => {
const result = extractRateLimit({limit: 100, window_ms: 60000});
expect(result).toEqual({limit: 100, windowMs: 60000});
});
});