🎉 init: 小龙的工作空间
This commit is contained in:
@@ -0,0 +1,271 @@
|
||||
/**
|
||||
* 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");
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user