129 lines
4.6 KiB
JavaScript
129 lines
4.6 KiB
JavaScript
const test = require("node:test");
|
|
const assert = require("node:assert/strict");
|
|
const { ConfigError, loadConfig, redactedConfig } = require("../src/config");
|
|
|
|
function validEnvironment(overrides = {}) {
|
|
return {
|
|
NODE_ENV: "production",
|
|
HOST: "127.0.0.1",
|
|
PORT: "7532",
|
|
DATABASE_PATH: "/data/db/announcements.sqlite",
|
|
APP_TIMEZONE: "America/Chicago",
|
|
S3_ENDPOINT: "http://seaweedfs:8333",
|
|
S3_REGION: "us-east-1",
|
|
S3_BUCKET: "announcements",
|
|
S3_ACCESS_KEY_ID: "test-access-key",
|
|
S3_SECRET_ACCESS_KEY: "test-secret-key",
|
|
S3_FORCE_PATH_STYLE: "true",
|
|
MQTT_URL: "mqtt://broker:1883",
|
|
MQTT_USERNAME: "mqtt-user",
|
|
MQTT_PASSWORD: "mqtt-password",
|
|
HA_WEBHOOK_URL: "http://home-assistant:8123/api/webhook/example",
|
|
HA_WEBHOOK_SECRET: "webhook-secret",
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
test("loadConfig validates and normalizes the supported environment", () => {
|
|
const config = loadConfig(validEnvironment());
|
|
|
|
assert.equal(config.environment, "production");
|
|
assert.equal(config.port, 7532);
|
|
assert.equal(config.timeZone, "America/Chicago");
|
|
assert.equal(config.storage.endpoint, "http://seaweedfs:8333");
|
|
assert.equal(config.storage.forcePathStyle, true);
|
|
assert.equal(config.storage.healthProbeIntervalMs, 30_000);
|
|
assert.equal(config.storage.connectionTimeoutMs, 2_000);
|
|
assert.equal(config.storage.requestTimeoutMs, 5_000);
|
|
assert.equal(config.storage.maxAttempts, 2);
|
|
assert.equal(config.processing.tempDirectory, "/tmp/announcements-uploads");
|
|
assert.equal(config.processing.maxFileBytes, 25 * 1024 * 1024);
|
|
assert.equal(config.processing.maxFiles, 20);
|
|
assert.equal(config.processing.maxInputPixels, 40_000_000);
|
|
});
|
|
|
|
test("production requires private S3 credentials", () => {
|
|
assert.throws(
|
|
() => loadConfig(validEnvironment({ S3_ACCESS_KEY_ID: "" })),
|
|
(error) =>
|
|
error instanceof ConfigError && error.field === "S3_ACCESS_KEY_ID",
|
|
);
|
|
|
|
const development = loadConfig(
|
|
validEnvironment({
|
|
NODE_ENV: "development",
|
|
S3_ACCESS_KEY_ID: "",
|
|
S3_SECRET_ACCESS_KEY: "",
|
|
}),
|
|
);
|
|
assert.equal(development.storage.accessKeyId, null);
|
|
assert.equal(development.storage.secretAccessKey, null);
|
|
});
|
|
|
|
test("loadConfig rejects invalid typed values", () => {
|
|
assert.throws(
|
|
() => loadConfig(validEnvironment({ PORT: "7532.5" })),
|
|
/PORT must be an integer/,
|
|
);
|
|
assert.throws(
|
|
() => loadConfig(validEnvironment({ APP_TIMEZONE: "Church/Local" })),
|
|
/valid IANA time zone/,
|
|
);
|
|
assert.throws(
|
|
() => loadConfig(validEnvironment({ S3_FORCE_PATH_STYLE: "sometimes" })),
|
|
/must be a boolean/,
|
|
);
|
|
assert.throws(
|
|
() => loadConfig(validEnvironment({ S3_HEALTH_PROBE_INTERVAL_MS: "999" })),
|
|
/S3_HEALTH_PROBE_INTERVAL_MS must be at least 1000/,
|
|
);
|
|
assert.throws(
|
|
() => loadConfig(validEnvironment({ S3_REQUEST_TIMEOUT_MS: "999" })),
|
|
/S3_REQUEST_TIMEOUT_MS must be at least 1000/,
|
|
);
|
|
assert.throws(
|
|
() => loadConfig(validEnvironment({ S3_CONNECT_TIMEOUT_MS: "249" })),
|
|
/S3_CONNECT_TIMEOUT_MS must be at least 250/,
|
|
);
|
|
assert.throws(
|
|
() => loadConfig(validEnvironment({ S3_MAX_ATTEMPTS: "6" })),
|
|
/S3_MAX_ATTEMPTS must be at most 5/,
|
|
);
|
|
assert.throws(
|
|
() => loadConfig(validEnvironment({ DATABASE_PATH: "relative.sqlite" })),
|
|
/must be an absolute path/,
|
|
);
|
|
assert.throws(
|
|
() => loadConfig(validEnvironment({ UPLOAD_TEMP_DIRECTORY: "relative" })),
|
|
/UPLOAD_TEMP_DIRECTORY must be an absolute path/,
|
|
);
|
|
assert.throws(
|
|
() => loadConfig(validEnvironment({ UPLOAD_MAX_FILE_BYTES: "0" })),
|
|
/UPLOAD_MAX_FILE_BYTES must be at least 1/,
|
|
);
|
|
assert.throws(
|
|
() => loadConfig(validEnvironment({ UPLOAD_MAX_FILES: "101" })),
|
|
/UPLOAD_MAX_FILES must be at most 100/,
|
|
);
|
|
assert.throws(
|
|
() => loadConfig(validEnvironment({ IMAGE_MAX_INPUT_PIXELS: "no-limit" })),
|
|
/IMAGE_MAX_INPUT_PIXELS must be an integer/,
|
|
);
|
|
});
|
|
|
|
test("redactedConfig never returns credentials, passwords, paths, or webhook URLs", () => {
|
|
const summary = redactedConfig(loadConfig(validEnvironment()));
|
|
const serialized = JSON.stringify(summary);
|
|
|
|
assert.equal(summary.storage.configured, true);
|
|
assert.equal(summary.mqtt.configured, true);
|
|
assert.equal(summary.notifications.configured, true);
|
|
assert.doesNotMatch(serialized, /test-access-key/);
|
|
assert.doesNotMatch(serialized, /test-secret-key/);
|
|
assert.doesNotMatch(serialized, /mqtt-password/);
|
|
assert.doesNotMatch(serialized, /webhook-secret/);
|
|
assert.doesNotMatch(serialized, /announcements\.sqlite/);
|
|
assert.doesNotMatch(serialized, /seaweedfs/);
|
|
assert.doesNotMatch(serialized, /home-assistant/);
|
|
});
|