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:
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;
+ }
+ }
+});