mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-15 20:10:42 +00:00
Merge branch 'main' into ui/dashboard-v2.1.3
This commit is contained in:
201
src/infra/state-migrations.test.ts
Normal file
201
src/infra/state-migrations.test.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { resolveChannelAllowFromPath } from "../pairing/pairing-store.js";
|
||||
import { createTrackedTempDirs } from "../test-utils/tracked-temp-dirs.js";
|
||||
import { detectLegacyStateMigrations, runLegacyStateMigrations } from "./state-migrations.js";
|
||||
|
||||
const tempDirs = createTrackedTempDirs();
|
||||
const createTempDir = () => tempDirs.make("openclaw-state-migrations-test-");
|
||||
|
||||
function createConfig(): OpenClawConfig {
|
||||
return {
|
||||
agents: {
|
||||
list: [{ id: "worker-1", default: true }],
|
||||
},
|
||||
session: {
|
||||
mainKey: "desk",
|
||||
},
|
||||
channels: {
|
||||
telegram: {
|
||||
accounts: {
|
||||
beta: {},
|
||||
alpha: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
}
|
||||
|
||||
function createEnv(stateDir: string): NodeJS.ProcessEnv {
|
||||
return {
|
||||
...process.env,
|
||||
OPENCLAW_STATE_DIR: stateDir,
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
await tempDirs.cleanup();
|
||||
});
|
||||
|
||||
describe("state migrations", () => {
|
||||
it("detects legacy sessions, agent files, whatsapp auth, and telegram allowFrom copies", async () => {
|
||||
const root = await createTempDir();
|
||||
const stateDir = path.join(root, ".openclaw");
|
||||
const env = createEnv(stateDir);
|
||||
const cfg = createConfig();
|
||||
|
||||
await fs.mkdir(path.join(stateDir, "sessions"), { recursive: true });
|
||||
await fs.mkdir(path.join(stateDir, "agents", "worker-1", "sessions"), { recursive: true });
|
||||
await fs.mkdir(path.join(stateDir, "agent"), { recursive: true });
|
||||
await fs.mkdir(path.join(stateDir, "credentials"), { recursive: true });
|
||||
|
||||
await fs.writeFile(
|
||||
path.join(stateDir, "sessions", "sessions.json"),
|
||||
`${JSON.stringify({ legacyDirect: { sessionId: "legacy-direct", updatedAt: 10 } }, null, 2)}\n`,
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(path.join(stateDir, "sessions", "trace.jsonl"), "{}\n", "utf8");
|
||||
await fs.writeFile(
|
||||
path.join(stateDir, "agents", "worker-1", "sessions", "sessions.json"),
|
||||
`${JSON.stringify({ "group:123@g.us": { sessionId: "group-session", updatedAt: 5 } }, null, 2)}\n`,
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(path.join(stateDir, "agent", "settings.json"), '{"ok":true}\n', "utf8");
|
||||
await fs.writeFile(path.join(stateDir, "credentials", "creds.json"), '{"auth":true}\n', "utf8");
|
||||
await fs.writeFile(
|
||||
path.join(stateDir, "credentials", "oauth.json"),
|
||||
'{"oauth":true}\n',
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(resolveChannelAllowFromPath("telegram", env), '["123","456"]\n', "utf8");
|
||||
|
||||
const detected = await detectLegacyStateMigrations({
|
||||
cfg,
|
||||
env,
|
||||
homedir: () => root,
|
||||
});
|
||||
|
||||
expect(detected.targetAgentId).toBe("worker-1");
|
||||
expect(detected.targetMainKey).toBe("desk");
|
||||
expect(detected.sessions.hasLegacy).toBe(true);
|
||||
expect(detected.sessions.legacyKeys).toEqual(["group:123@g.us"]);
|
||||
expect(detected.agentDir.hasLegacy).toBe(true);
|
||||
expect(detected.whatsappAuth.hasLegacy).toBe(true);
|
||||
expect(detected.pairingAllowFrom.hasLegacyTelegram).toBe(true);
|
||||
expect(detected.pairingAllowFrom.copyPlans.map((plan) => plan.targetPath)).toEqual([
|
||||
resolveChannelAllowFromPath("telegram", env, "alpha"),
|
||||
resolveChannelAllowFromPath("telegram", env, "beta"),
|
||||
]);
|
||||
expect(detected.preview).toEqual([
|
||||
`- Sessions: ${path.join(stateDir, "sessions")} → ${path.join(stateDir, "agents", "worker-1", "sessions")}`,
|
||||
`- Sessions: canonicalize legacy keys in ${path.join(stateDir, "agents", "worker-1", "sessions", "sessions.json")}`,
|
||||
`- Agent dir: ${path.join(stateDir, "agent")} → ${path.join(stateDir, "agents", "worker-1", "agent")}`,
|
||||
`- WhatsApp auth: ${path.join(stateDir, "credentials")} → ${path.join(stateDir, "credentials", "whatsapp", "default")} (keep oauth.json)`,
|
||||
`- Telegram pairing allowFrom: ${resolveChannelAllowFromPath("telegram", env)} → ${resolveChannelAllowFromPath("telegram", env, "alpha")}`,
|
||||
`- Telegram pairing allowFrom: ${resolveChannelAllowFromPath("telegram", env)} → ${resolveChannelAllowFromPath("telegram", env, "beta")}`,
|
||||
]);
|
||||
});
|
||||
|
||||
it("runs legacy state migrations and canonicalizes the merged session store", async () => {
|
||||
const root = await createTempDir();
|
||||
const stateDir = path.join(root, ".openclaw");
|
||||
const env = createEnv(stateDir);
|
||||
const cfg = createConfig();
|
||||
|
||||
await fs.mkdir(path.join(stateDir, "sessions"), { recursive: true });
|
||||
await fs.mkdir(path.join(stateDir, "agents", "worker-1", "sessions"), { recursive: true });
|
||||
await fs.mkdir(path.join(stateDir, "agent"), { recursive: true });
|
||||
await fs.mkdir(path.join(stateDir, "credentials"), { recursive: true });
|
||||
|
||||
await fs.writeFile(
|
||||
path.join(stateDir, "sessions", "sessions.json"),
|
||||
`${JSON.stringify({ legacyDirect: { sessionId: "legacy-direct", updatedAt: 10 } }, null, 2)}\n`,
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(path.join(stateDir, "sessions", "trace.jsonl"), "{}\n", "utf8");
|
||||
await fs.writeFile(
|
||||
path.join(stateDir, "agents", "worker-1", "sessions", "sessions.json"),
|
||||
`${JSON.stringify({ "group:123@g.us": { sessionId: "group-session", updatedAt: 5 } }, null, 2)}\n`,
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(path.join(stateDir, "agent", "settings.json"), '{"ok":true}\n', "utf8");
|
||||
await fs.writeFile(path.join(stateDir, "credentials", "creds.json"), '{"auth":true}\n', "utf8");
|
||||
await fs.writeFile(
|
||||
path.join(stateDir, "credentials", "pre-key-1.json"),
|
||||
'{"preKey":true}\n',
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(
|
||||
path.join(stateDir, "credentials", "oauth.json"),
|
||||
'{"oauth":true}\n',
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(resolveChannelAllowFromPath("telegram", env), '["123","456"]\n', "utf8");
|
||||
|
||||
const detected = await detectLegacyStateMigrations({
|
||||
cfg,
|
||||
env,
|
||||
homedir: () => root,
|
||||
});
|
||||
const result = await runLegacyStateMigrations({
|
||||
detected,
|
||||
now: () => 1234,
|
||||
});
|
||||
|
||||
expect(result.warnings).toEqual([]);
|
||||
expect(result.changes).toEqual([
|
||||
`Migrated latest direct-chat session → agent:worker-1:desk`,
|
||||
`Merged sessions store → ${path.join(stateDir, "agents", "worker-1", "sessions", "sessions.json")}`,
|
||||
"Canonicalized 1 legacy session key(s)",
|
||||
"Moved trace.jsonl → agents/worker-1/sessions",
|
||||
"Moved agent file settings.json → agents/worker-1/agent",
|
||||
"Moved WhatsApp auth creds.json → whatsapp/default",
|
||||
"Moved WhatsApp auth pre-key-1.json → whatsapp/default",
|
||||
`Copied Telegram pairing allowFrom → ${resolveChannelAllowFromPath("telegram", env, "alpha")}`,
|
||||
`Copied Telegram pairing allowFrom → ${resolveChannelAllowFromPath("telegram", env, "beta")}`,
|
||||
]);
|
||||
|
||||
const mergedStore = JSON.parse(
|
||||
await fs.readFile(
|
||||
path.join(stateDir, "agents", "worker-1", "sessions", "sessions.json"),
|
||||
"utf8",
|
||||
),
|
||||
) as Record<string, { sessionId: string }>;
|
||||
expect(mergedStore["agent:worker-1:desk"]?.sessionId).toBe("legacy-direct");
|
||||
expect(mergedStore["agent:worker-1:whatsapp:group:123@g.us"]?.sessionId).toBe("group-session");
|
||||
|
||||
await expect(
|
||||
fs.readFile(path.join(stateDir, "agents", "worker-1", "sessions", "trace.jsonl"), "utf8"),
|
||||
).resolves.toBe("{}\n");
|
||||
await expect(fs.stat(path.join(stateDir, "sessions", "sessions.json"))).rejects.toMatchObject({
|
||||
code: "ENOENT",
|
||||
});
|
||||
await expect(fs.stat(path.join(stateDir, "sessions", "trace.jsonl"))).rejects.toMatchObject({
|
||||
code: "ENOENT",
|
||||
});
|
||||
|
||||
await expect(
|
||||
fs.readFile(path.join(stateDir, "agents", "worker-1", "agent", "settings.json"), "utf8"),
|
||||
).resolves.toContain('"ok":true');
|
||||
await expect(
|
||||
fs.readFile(path.join(stateDir, "credentials", "whatsapp", "default", "creds.json"), "utf8"),
|
||||
).resolves.toContain('"auth":true');
|
||||
await expect(
|
||||
fs.readFile(
|
||||
path.join(stateDir, "credentials", "whatsapp", "default", "pre-key-1.json"),
|
||||
"utf8",
|
||||
),
|
||||
).resolves.toContain('"preKey":true');
|
||||
await expect(
|
||||
fs.readFile(path.join(stateDir, "credentials", "oauth.json"), "utf8"),
|
||||
).resolves.toContain('"oauth":true');
|
||||
await expect(
|
||||
fs.readFile(resolveChannelAllowFromPath("telegram", env, "alpha"), "utf8"),
|
||||
).resolves.toBe('["123","456"]\n');
|
||||
await expect(
|
||||
fs.readFile(resolveChannelAllowFromPath("telegram", env, "beta"), "utf8"),
|
||||
).resolves.toBe('["123","456"]\n');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user