taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 26acdb9af3ac22fc575841ea97719e410a16e419
parent 7c65529fc9956f9f483c0cf8df6d322b73bf0ef3
Author: Florian Dold <dold@taler.net>
Date:   Tue, 21 Jul 2026 19:15:34 +0200

util: test that config directories are loaded in sorted order

Diffstat:
Mpackages/taler-util/src/talerconfig.test.ts | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 84 insertions(+), 0 deletions(-)

diff --git a/packages/taler-util/src/talerconfig.test.ts b/packages/taler-util/src/talerconfig.test.ts @@ -19,6 +19,9 @@ */ import { test } from "node:test"; import assert from "node:assert"; +import nodejs_fs from "node:fs"; +import nodejs_os from "node:os"; +import nodejs_path from "node:path"; import { pathsub, Configuration } from "./talerconfig.js"; test("pathsub", (t) => { @@ -191,3 +194,84 @@ test("% introduces a comment, as in the C implementation", (t) => { "[EXCHANGE]\nBASE_URL = https://real/\n\n", ); }); + +/** + * Load a directory of config files in an order the filesystem chose, by + * handing the loader the directory listing reversed. Later files override + * earlier ones, so an unsorted listing makes the effective configuration + * depend on the filesystem. + */ +function withReversedDirListing<T>(f: () => T): T { + const original = nodejs_fs.readdirSync; + (nodejs_fs as any).readdirSync = (...args: any[]) => + (original as any)(...args) + .slice() + .reverse(); + try { + return f(); + } finally { + (nodejs_fs as any).readdirSync = original; + } +} + +function makeConfigDir(entries: Array<[string, string]>): string { + const dir = nodejs_fs.mkdtempSync( + nodejs_path.join(nodejs_os.tmpdir(), "talerconfig-test-"), + ); + nodejs_fs.mkdirSync(nodejs_path.join(dir, "conf.d")); + for (const [name, value] of entries) { + nodejs_fs.writeFileSync( + nodejs_path.join(dir, "conf.d", name), + `[test]\nVALUE = ${value}\n`, + ); + } + return dir; +} + +test("@inline-matching@ loads glob results in sorted order", (t) => { + // configuration.c qsorts the glob results, since later files override + // earlier ones; without that the effective configuration for a conf.d + // directory with overlapping keys differs from machine to machine. + const dir = makeConfigDir([ + ["a.conf", "first"], + ["b.conf", "second"], + ["c.conf", "last"], + ]); + nodejs_fs.writeFileSync( + nodejs_path.join(dir, "main.conf"), + "@inline-matching@ conf.d/*.conf\n", + ); + const config = withReversedDirListing(() => + Configuration.load(nodejs_path.join(dir, "main.conf")), + ); + assert.strictEqual(config.getString("test", "value").required(), "last"); +}); + +test("the default config directory is loaded in sorted order", (t) => { + const dir = makeConfigDir([ + ["a.conf", "first"], + ["b.conf", "second"], + ["c.conf", "last"], + ]); + // The main config file must not set the key itself, so that the value can + // only come from the defaults directory. + nodejs_fs.writeFileSync( + nodejs_path.join(dir, "main.conf"), + "[unrelated]\nX = y\n", + ); + const varname = "TALER_EXCHANGE_BASE_CONFIG"; + const saved = process.env[varname]; + process.env[varname] = nodejs_path.join(dir, "conf.d"); + try { + const config = withReversedDirListing(() => + Configuration.load(nodejs_path.join(dir, "main.conf")), + ); + assert.strictEqual(config.getString("test", "value").required(), "last"); + } finally { + if (saved === undefined) { + delete process.env[varname]; + } else { + process.env[varname] = saved; + } + } +});