Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,154 @@
import { EventBus } from "@suaveplan/events";
import { describe, expect, it } from "@suaveplan/testing/runner";
import { createConfig } from "../src/core/config-builder/config-builder.js";
import { ConfigError } from "../src/error-codes/error-codes.js";
import { cliLayer } from "../src/layers/cli-layer/cli-layer.js";
import { defaultsLayer } from "../src/layers/defaults-layer/defaults-layer.js";
import { envLayer } from "../src/layers/env-layer/env-layer.js";
import { fileLayer } from "../src/layers/file-layer/file-layer.js";
import { objectLayer } from "../src/layers/object-layer/object-layer.js";
describe("createConfig integration", () => {
it("full 5-layer pipeline produces correct merged config", {
timeout: 5000,
}, () => {
const store = createConfig({
layers: [
defaultsLayer({
server: { port: 3000, host: "0.0.0.0" },
db: { pool: 5, timeout: 30_000, password: "defaultpw" },
log: { level: "info", pretty: true },
}),
fileLayer({
content: {
server: { port: 3100 },
db: { pool: 10 },
},
}),
objectLayer({
server: { port: 3200 },
}),
envLayer({
mapping: { PORT: "server.port", DB_POOL: "db.pool" },
source: { PORT: "3300", DB_POOL: "15" },
}),
cliLayer({
args: ["--port=3400", "--debug"],
mapping: { port: "server.port", debug: "log.debug" },
}),
],
sensitive: ["db.password"],
});
// CLI (priority 40) wins for port
expect(store.get("server.port")).toBe(3400);
// ENV (priority 30) wins for db.pool over file/object/defaults
expect(store.get("db.pool")).toBe(15);
// CLI added debug flag
expect(store.get("log.debug")).toBe(true);
// Defaults survive for host
expect(store.get("server.host")).toBe("0.0.0.0");
// File layer contributed timeout (no higher layer overrode it)
expect(store.get("db.timeout")).toBe(30_000);
// sensitive masking
const masked = store.toMasked() as Record<
string,
Record<string, unknown>
>;
expect(masked.db.password).toBe("****");
expect(store.get("db.password")).toBe("defaultpw");
});
it("has() returns true/false correctly", { timeout: 5000 }, () => {
const store = createConfig({
layers: [defaultsLayer({ port: 3000 })],
});
expect(store.has("port")).toBe(true);
expect(store.has("missing")).toBe(false);
});
it("toJSON returns full frozen config", { timeout: 5000 }, () => {
const store = createConfig({
layers: [defaultsLayer({ port: 3000 })],
});
const json = store.toJSON();
expect(json).toEqual({ port: 3000 });
expect(() => {
(json as Record<string, unknown>).port = 9999;
}).toThrow();
});
it("reload with validation throws when validator rejects", {
timeout: 30000,
}, async () => {
let pass = true;
const dynamic = {
name: "dynamic",
priority: 0,
resolve: () => ({ port: pass ? 3000 : "bad" }),
};
const store = createConfig({
layers: [dynamic],
validate: (d) => {
if (typeof (d as Record<string, unknown>).port !== "number") {
throw new Error("port must be number");
}
return d;
},
});
pass = false;
await expect(store.reload()).rejects.toBeInstanceOf(ConfigError);
});
it("watch-mode: fileLayer with EventBus triggers reload", {
timeout: 30000,
}, async () => {
type BusMap = { "config:file:changed": Record<string, never> };
const bus = new EventBus<BusMap>();
let fileContent = { port: 3000 };
const fLayer = fileLayer({
get content() {
return fileContent;
},
watchBus: bus,
});
const store = createConfig({
layers: [fLayer],
});
expect(store.get("port")).toBe(3000);
// Simulate file change
fileContent = { port: 4000 };
await bus.emit("config:file:changed", {});
// Give the async reload a tick to complete
await new Promise((resolve) => setTimeout(resolve, 50));
expect(store.get("port")).toBe(4000);
});
it("environment override applies on reload", {
timeout: 30000,
}, async () => {
let counter = 0;
const store = createConfig({
layers: [
{
name: "dynamic",
priority: 0,
resolve: () => ({ count: ++counter, debug: false }),
},
],
environments: { test: { debug: true } },
environment: "test",
});
expect(store.get("debug")).toBe(true);
await store.reload();
expect(store.get("debug")).toBe(true);
});
});