/** * PR-A Baseline Test 08: Config Loading * Verifies: config loads correctly, invalid config is rejected * Freezes: config loading contract */ import { describe, it } from "node:test"; import assert from "node:assert"; import { execSync } from "node:child_process"; import { existsSync, readFileSync, writeFileSync, unlinkSync } from "node:fs"; import { homedir } from "node:os"; import { join } from "node:path"; const CONFIG_PATH = join(homedir(), ".openclaw", "openclaw.json"); const TEMP_INVALID_CONFIG = join(homedir(), ".openclaw", "openclaw-invalid-test.json"); describe("test-08: Config Loading Baseline", () => { it("08.01 - config file exists and is valid JSON", () => { assert.ok(existsSync(CONFIG_PATH), "openclaw.json must exist"); const content = readFileSync(CONFIG_PATH, "utf8"); let parsed; try { parsed = JSON.parse(content); } catch (e) { assert.fail(`config must be valid JSON: ${e.message}`); } console.log("[BASELINE] config top-level keys:", Object.keys(parsed).join(", ")); assert.ok(typeof parsed === "object", "config must be object"); }); it("08.02 - config sections recorded", () => { const config = JSON.parse(readFileSync(CONFIG_PATH, "utf8")); const sections = { gateway: !!config.gateway, agents: !!config.agents, models: !!config.models, plugins: !!config.plugins, channels: !!config.channels, tools: !!config.tools, diagnostics: !!config.diagnostics, }; console.log("[BASELINE] config sections present:", JSON.stringify(sections)); assert.ok(sections.gateway, "gateway section must exist"); assert.ok(sections.models, "models section should exist"); }); it("08.03 - config validate CLI works", () => { const out = execSync("openclaw config get gateway 2>&1", { timeout: 10000, encoding: "utf8", }).trim(); console.log("[BASELINE] config get gateway:", out.substring(0, 200)); // Should not error assert.ok(!out.toLowerCase().includes("error") || out.includes("Error"), "config get must return data or clear error"); }); it("08.04 - invalid JSON config is rejected", () => { // Create a temporary invalid config for testing rejection behavior const invalidContent = "{ this is not valid json {{{"; try { // Don't actually create it on the real config path // Instead use openclaw config validate with a test file writeFileSync(TEMP_INVALID_CONFIG, invalidContent, "utf8"); // Test that openclaw rejects it try { execSync(`OPENCLAW_CONFIG_PATH="${TEMP_INVALID_CONFIG}" openclaw config get gateway 2>&1`, { timeout: 10000, encoding: "utf8", }); } catch (e) { console.log("[BASELINE] invalid config rejection:", e.stderr?.substring(0, 200) || e.message?.substring(0, 200)); // Error on invalid config is expected behavior assert.ok(e.stderr || e.message, "invalid config must produce error"); } } finally { try { unlinkSync(TEMP_INVALID_CONFIG); } catch {} } }); it("08.05 - config with unknown keys loads without crash", () => { const config = JSON.parse(readFileSync(CONFIG_PATH, "utf8")); // Check if unknown keys cause silent failure // In practice, extra keys should be silently ignored or warned console.log("[BASELINE] config key count:", Object.keys(config).length); assert.ok(Object.keys(config).length > 0, "config must have sections"); }); it("08.06 - config dot-path get works", () => { const paths = [ "gateway.bind", "gateway.auth.mode", "gateway.port", ]; for (const p of paths) { try { const out = execSync(`openclaw config get "${p}" 2>&1`, { timeout: 10000, encoding: "utf8", }).trim(); console.log(`[BASELINE] config get ${p}:`, out.substring(0, 100)); } catch (e) { console.log(`[BASELINE] config get ${p}: not found or error`); } } assert.ok(true, "baseline frozen: dot-path get"); }); });