272 lines
10 KiB
JavaScript
272 lines
10 KiB
JavaScript
/**
|
|
* PR-C Deprecation Warning System — Tests
|
|
*
|
|
* Run: node --test test/architecture/deprecation-warning.test.mjs
|
|
*/
|
|
|
|
import { describe, it, beforeEach } from "node:test";
|
|
import assert from "node:assert";
|
|
import { spawnSync } from "node:child_process";
|
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from "node:fs";
|
|
import { homedir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { warnOnce, resetWarnings, getWarningCount, warnMany, runDeprecationCheck } from "../../scripts/lib/deprecation-warning.mjs";
|
|
|
|
const CONFIG_PATH = join(homedir(), ".openclaw", "openclaw.json");
|
|
const TEST_DIR = join(homedir(), ".openclaw", "workspace", "test", "architecture");
|
|
const TMP_CONFIG = join(homedir(), ".openclaw", "openclaw-prc-test.json");
|
|
|
|
beforeEach(() => {
|
|
resetWarnings();
|
|
});
|
|
|
|
describe("PR-C: Deprecation Warning System", () => {
|
|
// ─── warnOnce utility tests ──────────────────────────────
|
|
describe("warnOnce utility", () => {
|
|
it("warn-01: same key only warns once", () => {
|
|
const r1 = warnOnce("test.key1", "first message");
|
|
const r2 = warnOnce("test.key1", "second message");
|
|
const r3 = warnOnce("test.key1", "third message");
|
|
|
|
assert.equal(r1, true, "first call must emit");
|
|
assert.equal(r2, false, "second call must be suppressed");
|
|
assert.equal(r3, false, "third call must be suppressed");
|
|
assert.equal(getWarningCount(), 1, "only 1 unique key warned");
|
|
});
|
|
|
|
it("warn-02: different keys both warn", () => {
|
|
const r1 = warnOnce("test.key1", "msg1");
|
|
const r2 = warnOnce("test.key2", "msg2");
|
|
|
|
assert.equal(r1, true);
|
|
assert.equal(r2, true);
|
|
assert.equal(getWarningCount(), 2);
|
|
});
|
|
|
|
it("warn-03: force option bypasses deduplication", () => {
|
|
warnOnce("test.force", "first");
|
|
const r = warnOnce("test.force", "second", { force: true });
|
|
|
|
assert.equal(r, true, "force must emit even if already warned");
|
|
assert.equal(getWarningCount(), 1, "key count unchanged (same key)");
|
|
});
|
|
|
|
it("warn-04: resetWarnings clears all state", () => {
|
|
warnOnce("test.a", "a");
|
|
warnOnce("test.b", "b");
|
|
assert.equal(getWarningCount(), 2);
|
|
|
|
resetWarnings();
|
|
assert.equal(getWarningCount(), 0);
|
|
|
|
// Can warn again after reset
|
|
const r = warnOnce("test.a", "a again");
|
|
assert.equal(r, true);
|
|
assert.equal(getWarningCount(), 1);
|
|
});
|
|
|
|
it("warn-05: warnMany deduplicates across batch", () => {
|
|
const emitted = warnMany([
|
|
{ key: "a", message: "msg a" },
|
|
{ key: "b", message: "msg b" },
|
|
{ key: "a", message: "msg a again" }, // duplicate
|
|
]);
|
|
|
|
assert.equal(emitted.length, 2, "only 2 unique warnings emitted");
|
|
assert.equal(getWarningCount(), 2);
|
|
});
|
|
|
|
it("warn-06: runDeprecationCheck catches errors", () => {
|
|
const result = runDeprecationCheck("test-check", () => {
|
|
return [{ key: "test.ok", message: "all good" }];
|
|
});
|
|
assert.equal(result.name, "test-check");
|
|
assert.equal(result.warnings.length, 1);
|
|
|
|
const errResult = runDeprecationCheck("bad-check", () => {
|
|
throw new Error("simulated failure");
|
|
});
|
|
assert.equal(errResult.warnings[0].key, "bad-check");
|
|
assert.ok(errResult.warnings[0].message.includes("simulated failure"));
|
|
});
|
|
});
|
|
|
|
// ─── Config detection tests ─────────────────────────────
|
|
describe("config detection", () => {
|
|
it("detect-01: real config loads without crashing", () => {
|
|
assert.ok(existsSync(CONFIG_PATH), "openclaw.json must exist");
|
|
const config = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
|
|
assert.ok(typeof config === "object");
|
|
assert.ok(config.gateway || config.agents, "config must have expected sections");
|
|
});
|
|
|
|
it("detect-02: active-memory blocking mode detected", () => {
|
|
// Read the actual config to check current state
|
|
const config = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
|
|
const activeCfg = config?.plugins?.entries?.["active-memory"];
|
|
|
|
if (activeCfg) {
|
|
const mode = activeCfg.config?.mode;
|
|
console.log(` [DETECT] active-memory mode: "${mode || "default"}"`);
|
|
|
|
if (mode === "blocking") {
|
|
const r = warnOnce("active-memory.blocking",
|
|
"active-memory blocking mode is deprecated. Set mode to 'precompute'.");
|
|
assert.equal(r, true);
|
|
}
|
|
}
|
|
assert.ok(true, "check completed without crash");
|
|
});
|
|
|
|
it("detect-03: dreaming REM phase detected", () => {
|
|
const config = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
|
|
const memoryCoreCfg = config?.plugins?.entries?.["memory-core"];
|
|
|
|
if (memoryCoreCfg?.config?.dreaming) {
|
|
const dreaming = memoryCoreCfg.config.dreaming;
|
|
const remEnabled = dreaming?.phases?.rem?.enabled;
|
|
console.log(` [DETECT] dreaming REM enabled: ${remEnabled ?? "not configured"}`);
|
|
|
|
if (remEnabled === true) {
|
|
warnOnce("dreaming.rem-phase",
|
|
"Dreaming REM phase is deprecated. Will be replaced by Collect/Promote.");
|
|
}
|
|
}
|
|
assert.ok(true, "check completed without crash");
|
|
});
|
|
|
|
it("detect-04: memory-wiki plugin reference detectable", () => {
|
|
const wikiPath = join(
|
|
"/opt/homebrew/lib/node_modules/openclaw",
|
|
"dist/extensions/memory-wiki/openclaw.plugin.json"
|
|
);
|
|
const exists = existsSync(wikiPath);
|
|
console.log(` [DETECT] memory-wiki plugin: ${exists ? "present" : "not found"}`);
|
|
|
|
if (exists) {
|
|
const plugin = JSON.parse(readFileSync(wikiPath, "utf8"));
|
|
assert.equal(plugin.id, "memory-wiki");
|
|
warnOnce("memory-wiki.plugin",
|
|
"memory-wiki plugin is legacy. Tools remain available during migration.");
|
|
}
|
|
assert.ok(true, "check completed without crash");
|
|
});
|
|
|
|
it("detect-05: QMD reference in config detectable", () => {
|
|
const config = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
|
|
const qmdRefs = [];
|
|
|
|
// Check memory search
|
|
if (config?.agents?.defaults?.memorySearch?.provider === "qmd") {
|
|
qmdRefs.push("memorySearch.provider=qmd");
|
|
}
|
|
if (config?.agents?.defaults?.memorySearch?.backend === "qmd") {
|
|
qmdRefs.push("memorySearch.backend=qmd");
|
|
}
|
|
|
|
// Check active-memory
|
|
const activeQmd = config?.plugins?.entries?.["active-memory"]?.config?.qmd;
|
|
if (activeQmd?.searchMode) {
|
|
qmdRefs.push("active-memory.qmd.searchMode");
|
|
}
|
|
|
|
console.log(` [DETECT] QMD references found: ${qmdRefs.length}`);
|
|
for (const ref of qmdRefs) {
|
|
console.log(` • ${ref}`);
|
|
warnOnce(`qmd.${ref}`, "QMD memory engine is legacy. Migrate to Builtin MemoryCore.");
|
|
}
|
|
|
|
assert.ok(true, "check completed without crash");
|
|
});
|
|
|
|
it("detect-06: Honcho/LanceDB plugins detectable", () => {
|
|
const extDir = "/opt/homebrew/lib/node_modules/openclaw/dist/extensions";
|
|
const legacyPlugins = ["memory-honcho", "memory-lancedb"];
|
|
let found = 0;
|
|
|
|
for (const name of legacyPlugins) {
|
|
// Check if directory or plugin reference exists
|
|
// These are NOT in the current install, which is expected (they're optional)
|
|
const config = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
|
|
const enabled = config?.plugins?.entries?.[name]?.enabled;
|
|
console.log(` [DETECT] ${name}: ${enabled === true ? "enabled" : "not enabled"}`);
|
|
|
|
if (enabled === true) {
|
|
warnOnce(`${name}.plugin`, `${name} memory plugin is legacy. Data readable during migration.`);
|
|
found++;
|
|
}
|
|
}
|
|
|
|
console.log(` [DETECT] legacy memory plugins enabled: ${found}`);
|
|
assert.ok(true, "check completed without crash");
|
|
});
|
|
|
|
it("detect-07: commitments auto-infer detectable", () => {
|
|
const config = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
|
|
const autoInfer = config?.commitments?.autoInfer;
|
|
console.log(` [DETECT] commitments auto-infer: ${autoInfer ?? "not configured"}`);
|
|
|
|
if (autoInfer === true) {
|
|
warnOnce("commitments.auto-infer",
|
|
"Commitments auto-infer is deprecated. Use explicit cron tasks.");
|
|
}
|
|
assert.ok(true, "check completed without crash");
|
|
});
|
|
});
|
|
|
|
// ─── Integration tests ──────────────────────────────────
|
|
describe("integration", () => {
|
|
it("integ-01: check-deprecations script runs without error", () => {
|
|
const scriptPath = join(import.meta.dirname, "../../scripts/check-deprecations.mjs");
|
|
const result = spawnSync("node", [scriptPath], {
|
|
encoding: "utf8",
|
|
timeout: 15000,
|
|
maxBuffer: 1024 * 1024,
|
|
});
|
|
|
|
console.log(" [INTEG] check-deprecations exit code:", result.status);
|
|
console.log(" [INTEG] check-deprecations stdout:",
|
|
result.stdout?.slice(0, 400) || "(empty)");
|
|
|
|
if (result.stderr) {
|
|
console.log(" [INTEG] stderr:", result.stderr.slice(0, 200));
|
|
}
|
|
|
|
// Script should never crash — always exit 0
|
|
assert.equal(result.status, 0,
|
|
"check-deprecations must exit 0 even with warnings");
|
|
});
|
|
|
|
it("integ-02: deprecation warnings do NOT cause process exit", () => {
|
|
// warnOnce writes to console.warn — it should never throw
|
|
assert.doesNotThrow(() => {
|
|
warnOnce("test.integration", "This is a test deprecation warning");
|
|
}, "warnOnce must never throw");
|
|
|
|
assert.equal(getWarningCount(), 1);
|
|
});
|
|
|
|
it("integ-03: all 8 deprecation categories checkable", () => {
|
|
const categories = [
|
|
"active-memory.blocking",
|
|
"dreaming.rem-phase",
|
|
"dreaming.dream-diary",
|
|
"qmd.reference",
|
|
"honcho.plugin",
|
|
"lancedb.plugin",
|
|
"memory-wiki.plugin",
|
|
"commitments.auto-infer",
|
|
];
|
|
|
|
// Each category should have a defined key pattern
|
|
for (const cat of categories) {
|
|
assert.ok(typeof cat === "string" && cat.length > 0,
|
|
`category ${cat} must be a non-empty string`);
|
|
}
|
|
|
|
console.log(` [INTEG] ${categories.length} deprecation categories defined`);
|
|
assert.equal(categories.length, 8, "must cover all 8 deprecation categories");
|
|
});
|
|
});
|
|
});
|