mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 20:05:46 -06:00
ff83e3efe8
Doctor migrations and auth-profile writes open per-agent and shared state databases under the fixture's temporary directory. Clearing the plugin state store or the runtime auth snapshots does not release those handles, so Windows fails the directory removal with EBUSY while Linux unlinks the open files and stays green. Close the cached databases before each removal, matching the ordering the zalouser and zalo fixtures already use.
169 lines
5.2 KiB
TypeScript
169 lines
5.2 KiB
TypeScript
// Active Memory tests cover doctor contract api plugin behavior.
|
|
import fs from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { expectDefined } from "@openclaw/normalization-core";
|
|
import {
|
|
createPluginStateKeyedStoreForTests,
|
|
resetPluginStateStoreForTests,
|
|
} from "openclaw/plugin-sdk/plugin-state-test-runtime";
|
|
import type {
|
|
OpenKeyedStoreOptions,
|
|
PluginDoctorStateMigrationContext,
|
|
} from "openclaw/plugin-sdk/runtime-doctor-migrations";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import {
|
|
legacyConfigRules,
|
|
normalizeCompatibilityConfig,
|
|
stateMigrations,
|
|
} from "./doctor-contract-api.js";
|
|
|
|
it("removes the retired QMD override while preserving Active Memory siblings", () => {
|
|
expect(legacyConfigRules).toEqual([
|
|
expect.objectContaining({
|
|
path: ["plugins", "entries", "active-memory", "config", "qmd"],
|
|
message: expect.stringContaining("doctor --fix"),
|
|
}),
|
|
]);
|
|
const cfg = {
|
|
plugins: {
|
|
entries: {
|
|
"active-memory": {
|
|
config: { enabled: true, qmd: { searchMode: "search" } },
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = normalizeCompatibilityConfig({ cfg });
|
|
|
|
expect(result.config).toHaveProperty("plugins.entries.active-memory.config.enabled", true);
|
|
expect(result.config).not.toHaveProperty("plugins.entries.active-memory.config.qmd");
|
|
expect(result.changes).toEqual(["Removed retired Active Memory QMD search-mode configuration."]);
|
|
});
|
|
|
|
function createDoctorContext(env: NodeJS.ProcessEnv): PluginDoctorStateMigrationContext {
|
|
return {
|
|
openPluginStateKeyedStore<T>(options: OpenKeyedStoreOptions) {
|
|
return createPluginStateKeyedStoreForTests<T>("active-memory", {
|
|
...options,
|
|
env: options.env ?? env,
|
|
});
|
|
},
|
|
};
|
|
}
|
|
|
|
describe("active-memory doctor state migration", () => {
|
|
let stateDir = "";
|
|
let env: NodeJS.ProcessEnv;
|
|
|
|
beforeEach(async () => {
|
|
resetPluginStateStoreForTests();
|
|
stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-active-memory-doctor-"));
|
|
env = { ...process.env, OPENCLAW_STATE_DIR: stateDir };
|
|
});
|
|
|
|
afterEach(async () => {
|
|
vi.useRealTimers();
|
|
resetPluginStateStoreForTests();
|
|
await fs.rm(stateDir, { recursive: true, force: true });
|
|
});
|
|
|
|
it("imports legacy session opt-outs into plugin state", async () => {
|
|
const sourcePath = path.join(stateDir, "plugins", "active-memory", "session-toggles.json");
|
|
await fs.mkdir(path.dirname(sourcePath), { recursive: true });
|
|
await fs.writeFile(
|
|
sourcePath,
|
|
JSON.stringify({
|
|
sessions: {
|
|
"telegram:dm:123": { disabled: true, updatedAt: 1700 },
|
|
"telegram:dm:456": { disabled: false, updatedAt: 1701 },
|
|
},
|
|
}),
|
|
);
|
|
|
|
const migration = expectDefined(stateMigrations[0], "active-memory state migration");
|
|
await expect(
|
|
migration.detectLegacyState({
|
|
config: {},
|
|
env,
|
|
stateDir,
|
|
oauthDir: path.join(stateDir, "oauth"),
|
|
context: createDoctorContext(env),
|
|
}),
|
|
).resolves.toMatchObject({
|
|
preview: [expect.stringContaining("1 entry")],
|
|
});
|
|
|
|
const result = await migration.migrateLegacyState({
|
|
config: {},
|
|
env,
|
|
stateDir,
|
|
oauthDir: path.join(stateDir, "oauth"),
|
|
context: createDoctorContext(env),
|
|
});
|
|
|
|
expect(result.warnings).toEqual([]);
|
|
expect(result.changes).toEqual([
|
|
expect.stringContaining("Migrated 1 Active Memory session toggle entry"),
|
|
expect.stringContaining("Archived Active Memory session toggles legacy source"),
|
|
]);
|
|
await expect(fs.access(sourcePath)).rejects.toThrow();
|
|
await expect(fs.access(`${sourcePath}.migrated`)).resolves.toBeUndefined();
|
|
|
|
const entries = await createDoctorContext(env)
|
|
.openPluginStateKeyedStore({
|
|
namespace: "session-toggles",
|
|
maxEntries: 10_000,
|
|
})
|
|
.entries();
|
|
expect(entries).toMatchObject([
|
|
{
|
|
key: expect.any(String),
|
|
value: {
|
|
sessionKey: "telegram:dm:123",
|
|
disabled: true,
|
|
updatedAt: 1700,
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("normalizes malformed legacy updatedAt values before importing toggles", async () => {
|
|
vi.useFakeTimers();
|
|
vi.setSystemTime(new Date("2026-07-10T00:00:00.000Z"));
|
|
const sourcePath = path.join(stateDir, "plugins", "active-memory", "session-toggles.json");
|
|
await fs.mkdir(path.dirname(sourcePath), { recursive: true });
|
|
await fs.writeFile(
|
|
sourcePath,
|
|
'{"sessions":{"telegram:dm:bad":{"disabled":true,"updatedAt":1e999}}}',
|
|
);
|
|
|
|
const migration = expectDefined(stateMigrations[0], "active-memory state migration");
|
|
const result = await migration.migrateLegacyState({
|
|
config: {},
|
|
env,
|
|
stateDir,
|
|
oauthDir: path.join(stateDir, "oauth"),
|
|
context: createDoctorContext(env),
|
|
});
|
|
|
|
expect(result.warnings).toEqual([]);
|
|
const entries = await createDoctorContext(env)
|
|
.openPluginStateKeyedStore({
|
|
namespace: "session-toggles",
|
|
maxEntries: 10_000,
|
|
})
|
|
.entries();
|
|
expect(entries).toMatchObject([
|
|
{
|
|
value: {
|
|
sessionKey: "telegram:dm:bad",
|
|
disabled: true,
|
|
updatedAt: Date.parse("2026-07-10T00:00:00.000Z"),
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
});
|