mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 07:53:09 +00:00
3b58694eb9
app-settings.registry.ts lists every app_settings key the server reads by name with its shape, default, environment override and flags. The admin form's keys, its masked and encrypted handling and the public config's reads derive from it, isOidcConfigured() replaces the three issuer/client-id checks in AuthService, and OidcService resolves its values through the same rule while still requiring the client secret. Every literal getValue() read goes through readAppSetting()/resolveAppSetting(), and ESLint refuses a new one.
116 lines
5.3 KiB
TypeScript
116 lines
5.3 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
import {
|
|
ADMIN_FORM_SETTING_KEYS,
|
|
APP_SETTINGS,
|
|
ENCRYPTED_APP_SETTING_KEYS,
|
|
MANAGED_LOCKED_APP_SETTING_KEYS,
|
|
MASKED_ADMIN_SETTING_KEYS,
|
|
PUBLIC_CONFIG_SETTING_KEYS,
|
|
isOidcConfigured,
|
|
isOidcConfiguredIn,
|
|
readAppSetting,
|
|
resolveAppSetting,
|
|
type AppSettingDef,
|
|
} from '../../../src/nest/common/app-settings.registry';
|
|
import { MANAGED_LOCKED_SETTING_KEYS } from '../../../src/nest/common/managed';
|
|
|
|
/** A stand-in for AppSettingsRepository.getValue over a fixed set of rows. */
|
|
function repoOf(rows: Record<string, string>) {
|
|
const getValue = vi.fn(async (key: string) => (key in rows ? rows[key] : null));
|
|
return { getValue };
|
|
}
|
|
|
|
afterEach(() => vi.unstubAllEnvs());
|
|
|
|
describe('the derived key lists', () => {
|
|
it('APPSET-001: the admin form answers the keys it always has, in the same order', () => {
|
|
expect([...ADMIN_FORM_SETTING_KEYS]).toEqual([
|
|
'allow_registration', 'allowed_file_types', 'require_mfa',
|
|
'smtp_host', 'smtp_port', 'smtp_user', 'smtp_pass', 'smtp_from', 'smtp_skip_tls_verify',
|
|
'notification_channels', 'admin_webhook_url', 'admin_ntfy_server', 'admin_ntfy_topic', 'admin_ntfy_token',
|
|
'notify_trip_reminder',
|
|
'password_login', 'password_registration', 'oidc_login', 'oidc_registration',
|
|
'passkey_login', 'webauthn_rp_id', 'webauthn_origins',
|
|
'places_provider',
|
|
]);
|
|
});
|
|
|
|
it('APPSET-002: the masked and the encrypted keys', () => {
|
|
expect([...MASKED_ADMIN_SETTING_KEYS].sort()).toEqual(['admin_ntfy_token', 'admin_webhook_url', 'smtp_pass']);
|
|
expect([...ENCRYPTED_APP_SETTING_KEYS].sort()).toEqual(['admin_ntfy_token', 'admin_webhook_url', 'oidc_client_secret', 'smtp_pass']);
|
|
expect((APP_SETTINGS.smtp_pass as AppSettingDef).encrypted).toBe('always');
|
|
expect((APP_SETTINGS.admin_webhook_url as AppSettingDef).encrypted).toBe('if-plain');
|
|
});
|
|
|
|
it('APPSET-003: the public config reads the keys it always read', () => {
|
|
expect([...PUBLIC_CONFIG_SETTING_KEYS].sort()).toEqual(
|
|
[
|
|
'places_provider', 'oidc_display_name', 'oidc_issuer', 'oidc_client_id', 'require_mfa',
|
|
'notification_channel', 'notify_trip_reminder', 'smtp_host', 'notification_channels',
|
|
'places_photos_enabled', 'places_autocomplete_enabled', 'places_details_enabled',
|
|
'places_enrich_enabled', 'place_shadow_enabled',
|
|
].sort(),
|
|
);
|
|
});
|
|
|
|
it('APPSET-004: a registered key is managed-locked exactly when the managed list names it', () => {
|
|
const locked = new Set<string>(MANAGED_LOCKED_SETTING_KEYS);
|
|
const registered = Object.keys(APP_SETTINGS);
|
|
expect([...MANAGED_LOCKED_APP_SETTING_KEYS].sort()).toEqual(registered.filter((k) => locked.has(k)).sort());
|
|
});
|
|
|
|
it('APPSET-005: every documented default is a value its schema accepts', () => {
|
|
for (const [key, def] of Object.entries(APP_SETTINGS) as Array<[string, AppSettingDef]>) {
|
|
if (def.default !== null) expect(def.schema.safeParse(def.default).success, key).toBe(true);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('reading', () => {
|
|
it('APPSET-010: readAppSetting answers what getValue answers', async () => {
|
|
const repo = repoOf({ require_mfa: 'true' });
|
|
expect(await readAppSetting(repo, 'require_mfa')).toBe('true');
|
|
expect(await readAppSetting(repo, 'smtp_host')).toBeNull();
|
|
expect(repo.getValue).toHaveBeenCalledWith('smtp_host');
|
|
});
|
|
|
|
it('APPSET-011: resolveAppSetting takes a non-empty environment value over the stored one', async () => {
|
|
const repo = repoOf({ smtp_host: 'stored.example' });
|
|
vi.stubEnv('SMTP_HOST', 'env.example');
|
|
expect(await resolveAppSetting(repo, 'smtp_host')).toBe('env.example');
|
|
vi.stubEnv('SMTP_HOST', '');
|
|
expect(await resolveAppSetting(repo, 'smtp_host')).toBe('stored.example');
|
|
// A key without an environment variable reads the row.
|
|
expect(await resolveAppSetting(repoOf({ require_mfa: 'true' }), 'require_mfa')).toBe('true');
|
|
});
|
|
});
|
|
|
|
describe('isOidcConfigured', () => {
|
|
it('APPSET-020: wants an issuer and a client id, from either side, and nothing else', async () => {
|
|
vi.stubEnv('OIDC_ISSUER', '');
|
|
vi.stubEnv('OIDC_CLIENT_ID', '');
|
|
expect(await isOidcConfigured(repoOf({}))).toBe(false);
|
|
expect(await isOidcConfigured(repoOf({ oidc_issuer: 'https://idp' }))).toBe(false);
|
|
expect(await isOidcConfigured(repoOf({ oidc_issuer: 'https://idp', oidc_client_id: 'trek' }))).toBe(true);
|
|
expect(await isOidcConfigured(repoOf({ oidc_issuer: '', oidc_client_id: 'trek' }))).toBe(false);
|
|
vi.stubEnv('OIDC_ISSUER', 'https://idp');
|
|
expect(await isOidcConfigured(repoOf({ oidc_client_id: 'trek' }))).toBe(true);
|
|
});
|
|
|
|
it('APPSET-021: does not read the client id when there is no issuer', async () => {
|
|
vi.stubEnv('OIDC_ISSUER', '');
|
|
const repo = repoOf({});
|
|
await isOidcConfigured(repo);
|
|
expect(repo.getValue).not.toHaveBeenCalledWith('oidc_client_id');
|
|
});
|
|
|
|
it('APPSET-022: the batch form answers the same over values already read', () => {
|
|
vi.stubEnv('OIDC_ISSUER', '');
|
|
vi.stubEnv('OIDC_CLIENT_ID', '');
|
|
expect(isOidcConfiguredIn(new Map())).toBe(false);
|
|
expect(isOidcConfiguredIn(new Map([['oidc_issuer', 'https://idp'], ['oidc_client_id', 'trek']]))).toBe(true);
|
|
vi.stubEnv('OIDC_CLIENT_ID', 'trek');
|
|
expect(isOidcConfiguredIn(new Map([['oidc_issuer', 'https://idp']]))).toBe(true);
|
|
});
|
|
});
|