openclaw / src /logging /config.test.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
76289e7 verified
Raw History Blame Contribute Delete
7.13 kB
// Logging config tests cover config file loading and defaults.
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import { withEnv } from "../test-utils/env.js";
import { readLoggingConfig } from "./config.js";
import { applyLoggingConfig, resetLogger } from "./logger.js";
const originalArgv = process.argv;
let tempDirs: string[] = [];
function writeConfig(source: string): string {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-logging-config-"));
tempDirs.push(dir);
const configPath = path.join(dir, "openclaw.json");
fs.writeFileSync(configPath, source);
return configPath;
}
describe("readLoggingConfig", () => {
afterEach(() => {
resetLogger();
process.argv = originalArgv;
for (const dir of tempDirs) {
fs.rmSync(dir, { force: true, recursive: true });
}
tempDirs = [];
});
it("returns the applied runtime snapshot without bootstrap filesystem work", () => {
const existsSync = vi.spyOn(fs, "existsSync");
applyLoggingConfig({ level: "debug", consoleStyle: "json" });
expect(readLoggingConfig()).toEqual({ level: "debug", consoleStyle: "json" });
expect(existsSync).not.toHaveBeenCalled();
});
it("reads logging style without a mutating config load for config schema", () => {
process.argv = ["node", "openclaw", "config", "schema"];
const configPath = writeConfig(`{ logging: { consoleStyle: "json" } }`);
withEnv({ OPENCLAW_CONFIG_PATH: configPath }, () => {
expect(readLoggingConfig()).toStrictEqual({ consoleStyle: "json" });
});
});
it("reads logging config directly from the active config path", () => {
const configPath = writeConfig(`{
logging: {
level: "debug",
file: "/tmp/openclaw-custom.log",
maxFileBytes: 1234,
},
}`);
withEnv({ OPENCLAW_CONFIG_PATH: configPath }, () => {
expect(readLoggingConfig()).toStrictEqual({
level: "debug",
file: "/tmp/openclaw-custom.log",
maxFileBytes: 1234,
});
});
});
it("supports JSON5 comments and trailing commas", () => {
const configPath = writeConfig(`{
// users commonly keep comments in openclaw.json
logging: {
consoleLevel: "warn",
},
}`);
withEnv({ OPENCLAW_CONFIG_PATH: configPath }, () => {
expect(readLoggingConfig()).toStrictEqual({
consoleLevel: "warn",
});
});
});
it("resolves nested includes and environment substitutions for logging style", () => {
const configPath = writeConfig(`{ logging: { $include: "./logging.json5" } }`);
fs.writeFileSync(
path.join(path.dirname(configPath), "logging.json5"),
`{ consoleStyle: "\${OPENCLAW_TEST_CONSOLE_STYLE}", file: "\${MISSING_LOG_FILE}" }`,
);
withEnv(
{
OPENCLAW_CONFIG_PATH: configPath,
OPENCLAW_TEST_CONSOLE_STYLE: "json",
MISSING_LOG_FILE: undefined,
},
() => {
expect(readLoggingConfig()).toStrictEqual({ consoleStyle: "json" });
},
);
});
it("preserves direct logging style when unrelated config resolution fails", () => {
const configPath = writeConfig(`{
logging: { consoleStyle: "json", file: "\${MISSING_LOG_FILE}" },
plugins: { $include: "./missing-plugins.json5" },
models: { providers: { demo: { apiKey: "\${MISSING_DEMO_KEY}" } } },
}`);
withEnv(
{
OPENCLAW_CONFIG_PATH: configPath,
MISSING_DEMO_KEY: undefined,
MISSING_LOG_FILE: undefined,
},
() => {
expect(readLoggingConfig()).toStrictEqual({ consoleStyle: "json" });
},
);
});
it("resolves root-included logging when an unrelated sibling include fails", () => {
const configPath = writeConfig(`{
$include: "./base.json5",
plugins: { $include: "./missing-plugins.json5" },
}`);
fs.writeFileSync(
path.join(path.dirname(configPath), "base.json5"),
`{ logging: { consoleStyle: "json", file: "\${MISSING_LOG_FILE}" } }`,
);
withEnv(
{
OPENCLAW_CONFIG_PATH: configPath,
MISSING_LOG_FILE: undefined,
},
() => {
expect(readLoggingConfig()).toStrictEqual({ consoleStyle: "json" });
},
);
});
it("does not cache a partial style while environment-backed fields are unresolved", () => {
const configPath = writeConfig(`{
logging: {
consoleStyle: "json",
file: "\${OPENCLAW_TEST_LOG_FILE}",
level: "debug",
},
}`);
withEnv(
{
OPENCLAW_CONFIG_PATH: configPath,
OPENCLAW_TEST_LOG_FILE: undefined,
},
() => {
expect(readLoggingConfig()).toStrictEqual({ consoleStyle: "json" });
},
);
withEnv(
{
OPENCLAW_CONFIG_PATH: configPath,
OPENCLAW_TEST_LOG_FILE: "/tmp/openclaw-env-backed.log",
},
() => {
expect(readLoggingConfig()).toStrictEqual({
consoleStyle: "json",
file: "/tmp/openclaw-env-backed.log",
level: "debug",
});
},
);
});
it("preserves custom redaction patterns while another logging field is unresolved", () => {
const configPath = writeConfig(`{
logging: {
consoleStyle: "json",
redactPatterns: ["/custom-only-secret/g"],
file: "\${MISSING_LOG_FILE}",
},
}`);
withEnv(
{
OPENCLAW_CONFIG_PATH: configPath,
MISSING_LOG_FILE: undefined,
},
() => {
expect(readLoggingConfig()).toStrictEqual({
consoleStyle: "json",
redactPatterns: ["/custom-only-secret/g"],
});
},
);
});
it("returns undefined for missing or malformed config files", () => {
withEnv(
{ OPENCLAW_CONFIG_PATH: path.join(os.tmpdir(), "openclaw-missing-config.json") },
() => {
expect(readLoggingConfig()).toBeUndefined();
},
);
const configPath = writeConfig(`{ logging: `);
withEnv({ OPENCLAW_CONFIG_PATH: configPath }, () => {
expect(readLoggingConfig()).toBeUndefined();
});
});
it("caches a missing config until the path selector changes", () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-logging-config-missing-"));
tempDirs.push(dir);
const firstPath = path.join(dir, "missing-first.json");
const secondPath = path.join(dir, "missing-second.json");
const existsSync = vi.spyOn(fs, "existsSync");
try {
withEnv({ OPENCLAW_CONFIG_PATH: firstPath }, () => {
expect(readLoggingConfig()).toBeUndefined();
expect(readLoggingConfig()).toBeUndefined();
});
withEnv({ OPENCLAW_CONFIG_PATH: secondPath }, () => {
expect(readLoggingConfig()).toBeUndefined();
});
const checksFor = (configPath: string) =>
existsSync.mock.calls.filter(([candidate]) => candidate === configPath).length;
expect(checksFor(firstPath)).toBe(1);
expect(checksFor(secondPath)).toBe(1);
} finally {
existsSync.mockRestore();
}
});
});