mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 11:17:40 +00:00
refactor(test): extract shared doctor migration test setup
This commit is contained in:
@@ -4,64 +4,54 @@ import { describe, expect, it, vi } from "vitest";
|
|||||||
import { withTempHome } from "../../test/helpers/temp-home.js";
|
import { withTempHome } from "../../test/helpers/temp-home.js";
|
||||||
import { loadAndMaybeMigrateDoctorConfig } from "./doctor-config-flow.js";
|
import { loadAndMaybeMigrateDoctorConfig } from "./doctor-config-flow.js";
|
||||||
|
|
||||||
|
async function runDoctorConfigWithInput(params: {
|
||||||
|
config: Record<string, unknown>;
|
||||||
|
repair?: boolean;
|
||||||
|
}) {
|
||||||
|
return withTempHome(async (home) => {
|
||||||
|
const configDir = path.join(home, ".openclaw");
|
||||||
|
await fs.mkdir(configDir, { recursive: true });
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(configDir, "openclaw.json"),
|
||||||
|
JSON.stringify(params.config, null, 2),
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
return loadAndMaybeMigrateDoctorConfig({
|
||||||
|
options: { nonInteractive: true, repair: params.repair },
|
||||||
|
confirm: async () => false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
describe("doctor config flow", () => {
|
describe("doctor config flow", () => {
|
||||||
it("preserves invalid config for doctor repairs", async () => {
|
it("preserves invalid config for doctor repairs", async () => {
|
||||||
await withTempHome(async (home) => {
|
const result = await runDoctorConfigWithInput({
|
||||||
const configDir = path.join(home, ".openclaw");
|
config: {
|
||||||
await fs.mkdir(configDir, { recursive: true });
|
gateway: { auth: { mode: "token", token: 123 } },
|
||||||
await fs.writeFile(
|
agents: { list: [{ id: "pi" }] },
|
||||||
path.join(configDir, "openclaw.json"),
|
},
|
||||||
JSON.stringify(
|
});
|
||||||
{
|
|
||||||
gateway: { auth: { mode: "token", token: 123 } },
|
|
||||||
agents: { list: [{ id: "pi" }] },
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
),
|
|
||||||
"utf-8",
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = await loadAndMaybeMigrateDoctorConfig({
|
expect((result.cfg as Record<string, unknown>).gateway).toEqual({
|
||||||
options: { nonInteractive: true },
|
auth: { mode: "token", token: 123 },
|
||||||
confirm: async () => false,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect((result.cfg as Record<string, unknown>).gateway).toEqual({
|
|
||||||
auth: { mode: "token", token: 123 },
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("drops unknown keys on repair", async () => {
|
it("drops unknown keys on repair", async () => {
|
||||||
await withTempHome(async (home) => {
|
const result = await runDoctorConfigWithInput({
|
||||||
const configDir = path.join(home, ".openclaw");
|
repair: true,
|
||||||
await fs.mkdir(configDir, { recursive: true });
|
config: {
|
||||||
await fs.writeFile(
|
bridge: { bind: "auto" },
|
||||||
path.join(configDir, "openclaw.json"),
|
gateway: { auth: { mode: "token", token: "ok", extra: true } },
|
||||||
JSON.stringify(
|
agents: { list: [{ id: "pi" }] },
|
||||||
{
|
},
|
||||||
bridge: { bind: "auto" },
|
});
|
||||||
gateway: { auth: { mode: "token", token: "ok", extra: true } },
|
|
||||||
agents: { list: [{ id: "pi" }] },
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
),
|
|
||||||
"utf-8",
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = await loadAndMaybeMigrateDoctorConfig({
|
const cfg = result.cfg as Record<string, unknown>;
|
||||||
options: { nonInteractive: true, repair: true },
|
expect(cfg.bridge).toBeUndefined();
|
||||||
confirm: async () => false,
|
expect((cfg.gateway as Record<string, unknown>)?.auth).toEqual({
|
||||||
});
|
mode: "token",
|
||||||
|
token: "ok",
|
||||||
const cfg = result.cfg as Record<string, unknown>;
|
|
||||||
expect(cfg.bridge).toBeUndefined();
|
|
||||||
expect((cfg.gateway as Record<string, unknown>)?.auth).toEqual({
|
|
||||||
mode: "token",
|
|
||||||
token: "ok",
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -86,60 +76,46 @@ describe("doctor config flow", () => {
|
|||||||
});
|
});
|
||||||
vi.stubGlobal("fetch", fetchSpy);
|
vi.stubGlobal("fetch", fetchSpy);
|
||||||
try {
|
try {
|
||||||
await withTempHome(async (home) => {
|
const result = await runDoctorConfigWithInput({
|
||||||
const configDir = path.join(home, ".openclaw");
|
repair: true,
|
||||||
await fs.mkdir(configDir, { recursive: true });
|
config: {
|
||||||
await fs.writeFile(
|
|
||||||
path.join(configDir, "openclaw.json"),
|
|
||||||
JSON.stringify(
|
|
||||||
{
|
|
||||||
channels: {
|
|
||||||
telegram: {
|
|
||||||
botToken: "123:abc",
|
|
||||||
allowFrom: ["@testuser"],
|
|
||||||
groupAllowFrom: ["groupUser"],
|
|
||||||
groups: {
|
|
||||||
"-100123": {
|
|
||||||
allowFrom: ["tg:@topicUser"],
|
|
||||||
topics: { "99": { allowFrom: ["@accountUser"] } },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
accounts: {
|
|
||||||
alerts: { botToken: "456:def", allowFrom: ["@accountUser"] },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
),
|
|
||||||
"utf-8",
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = await loadAndMaybeMigrateDoctorConfig({
|
|
||||||
options: { nonInteractive: true, repair: true },
|
|
||||||
confirm: async () => false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const cfg = result.cfg as unknown as {
|
|
||||||
channels: {
|
channels: {
|
||||||
telegram: {
|
telegram: {
|
||||||
allowFrom: string[];
|
botToken: "123:abc",
|
||||||
groupAllowFrom: string[];
|
allowFrom: ["@testuser"],
|
||||||
groups: Record<
|
groupAllowFrom: ["groupUser"],
|
||||||
string,
|
groups: {
|
||||||
{ allowFrom: string[]; topics: Record<string, { allowFrom: string[] }> }
|
"-100123": {
|
||||||
>;
|
allowFrom: ["tg:@topicUser"],
|
||||||
accounts: Record<string, { allowFrom: string[] }>;
|
topics: { "99": { allowFrom: ["@accountUser"] } },
|
||||||
};
|
},
|
||||||
|
},
|
||||||
|
accounts: {
|
||||||
|
alerts: { botToken: "456:def", allowFrom: ["@accountUser"] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const cfg = result.cfg as unknown as {
|
||||||
|
channels: {
|
||||||
|
telegram: {
|
||||||
|
allowFrom: string[];
|
||||||
|
groupAllowFrom: string[];
|
||||||
|
groups: Record<
|
||||||
|
string,
|
||||||
|
{ allowFrom: string[]; topics: Record<string, { allowFrom: string[] }> }
|
||||||
|
>;
|
||||||
|
accounts: Record<string, { allowFrom: string[] }>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
expect(cfg.channels.telegram.allowFrom).toEqual(["111"]);
|
};
|
||||||
expect(cfg.channels.telegram.groupAllowFrom).toEqual(["222"]);
|
expect(cfg.channels.telegram.allowFrom).toEqual(["111"]);
|
||||||
expect(cfg.channels.telegram.groups["-100123"].allowFrom).toEqual(["333"]);
|
expect(cfg.channels.telegram.groupAllowFrom).toEqual(["222"]);
|
||||||
expect(cfg.channels.telegram.groups["-100123"].topics["99"].allowFrom).toEqual(["444"]);
|
expect(cfg.channels.telegram.groups["-100123"].allowFrom).toEqual(["333"]);
|
||||||
expect(cfg.channels.telegram.accounts.alerts.allowFrom).toEqual(["444"]);
|
expect(cfg.channels.telegram.groups["-100123"].topics["99"].allowFrom).toEqual(["444"]);
|
||||||
});
|
expect(cfg.channels.telegram.accounts.alerts.allowFrom).toEqual(["444"]);
|
||||||
} finally {
|
} finally {
|
||||||
vi.unstubAllGlobals();
|
vi.unstubAllGlobals();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,15 @@ describe("normalizeLegacyConfigValues", () => {
|
|||||||
fs.writeFileSync(path.join(dir, "creds.json"), JSON.stringify({ me: {} }));
|
fs.writeFileSync(path.join(dir, "creds.json"), JSON.stringify({ me: {} }));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const expectNoWhatsAppConfigForLegacyAuth = (setup?: () => void) => {
|
||||||
|
setup?.();
|
||||||
|
const res = normalizeLegacyConfigValues({
|
||||||
|
messages: { ackReaction: "👀", ackReactionScope: "group-mentions" },
|
||||||
|
});
|
||||||
|
expect(res.config.channels?.whatsapp).toBeUndefined();
|
||||||
|
expect(res.changes).toEqual([]);
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
previousOauthDir = process.env.OPENCLAW_OAUTH_DIR;
|
previousOauthDir = process.env.OPENCLAW_OAUTH_DIR;
|
||||||
tempOauthDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-oauth-"));
|
tempOauthDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-oauth-"));
|
||||||
@@ -57,39 +66,24 @@ describe("normalizeLegacyConfigValues", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("does not add whatsapp config when only auth exists (issue #900)", () => {
|
it("does not add whatsapp config when only auth exists (issue #900)", () => {
|
||||||
const credsDir = path.join(tempOauthDir ?? "", "whatsapp", "default");
|
expectNoWhatsAppConfigForLegacyAuth(() => {
|
||||||
writeCreds(credsDir);
|
const credsDir = path.join(tempOauthDir ?? "", "whatsapp", "default");
|
||||||
|
writeCreds(credsDir);
|
||||||
const res = normalizeLegacyConfigValues({
|
|
||||||
messages: { ackReaction: "👀", ackReactionScope: "group-mentions" },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(res.config.channels?.whatsapp).toBeUndefined();
|
|
||||||
expect(res.changes).toEqual([]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not add whatsapp config when only legacy auth exists (issue #900)", () => {
|
it("does not add whatsapp config when only legacy auth exists (issue #900)", () => {
|
||||||
const credsPath = path.join(tempOauthDir ?? "", "creds.json");
|
expectNoWhatsAppConfigForLegacyAuth(() => {
|
||||||
fs.writeFileSync(credsPath, JSON.stringify({ me: {} }));
|
const credsPath = path.join(tempOauthDir ?? "", "creds.json");
|
||||||
|
fs.writeFileSync(credsPath, JSON.stringify({ me: {} }));
|
||||||
const res = normalizeLegacyConfigValues({
|
|
||||||
messages: { ackReaction: "👀", ackReactionScope: "group-mentions" },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(res.config.channels?.whatsapp).toBeUndefined();
|
|
||||||
expect(res.changes).toEqual([]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not add whatsapp config when only non-default auth exists (issue #900)", () => {
|
it("does not add whatsapp config when only non-default auth exists (issue #900)", () => {
|
||||||
const credsDir = path.join(tempOauthDir ?? "", "whatsapp", "work");
|
expectNoWhatsAppConfigForLegacyAuth(() => {
|
||||||
writeCreds(credsDir);
|
const credsDir = path.join(tempOauthDir ?? "", "whatsapp", "work");
|
||||||
|
writeCreds(credsDir);
|
||||||
const res = normalizeLegacyConfigValues({
|
|
||||||
messages: { ackReaction: "👀", ackReactionScope: "group-mentions" },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(res.config.channels?.whatsapp).toBeUndefined();
|
|
||||||
expect(res.changes).toEqual([]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("copies legacy ack reaction when authDir override exists", () => {
|
it("copies legacy ack reaction when authDir override exists", () => {
|
||||||
|
|||||||
@@ -68,6 +68,38 @@ async function runAndReadSessionsStore(params: {
|
|||||||
return readSessionsStore(params.targetDir);
|
return readSessionsStore(params.targetDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type StateDirMigrationResult = Awaited<ReturnType<typeof autoMigrateLegacyStateDir>>;
|
||||||
|
|
||||||
|
const DIR_LINK_TYPE = process.platform === "win32" ? "junction" : "dir";
|
||||||
|
|
||||||
|
function getStateDirMigrationPaths(root: string) {
|
||||||
|
return {
|
||||||
|
targetDir: path.join(root, ".openclaw"),
|
||||||
|
legacyDir: path.join(root, ".clawdbot"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureLegacyAndTargetStateDirs(root: string) {
|
||||||
|
const paths = getStateDirMigrationPaths(root);
|
||||||
|
fs.mkdirSync(paths.targetDir, { recursive: true });
|
||||||
|
fs.mkdirSync(paths.legacyDir, { recursive: true });
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runStateDirMigration(root: string, env = {} as NodeJS.ProcessEnv) {
|
||||||
|
return autoMigrateLegacyStateDir({
|
||||||
|
env,
|
||||||
|
homedir: () => root,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectTargetAlreadyExistsWarning(result: StateDirMigrationResult, targetDir: string) {
|
||||||
|
expect(result.migrated).toBe(false);
|
||||||
|
expect(result.warnings).toEqual([
|
||||||
|
`State dir migration skipped: target already exists (${targetDir}). Remove or merge manually.`,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
describe("doctor legacy state migrations", () => {
|
describe("doctor legacy state migrations", () => {
|
||||||
it("migrates legacy sessions into agents/<id>/sessions", async () => {
|
it("migrates legacy sessions into agents/<id>/sessions", async () => {
|
||||||
const root = await makeTempRoot();
|
const root = await makeTempRoot();
|
||||||
@@ -383,10 +415,7 @@ describe("doctor legacy state migrations", () => {
|
|||||||
|
|
||||||
it("does nothing when no legacy state dir exists", async () => {
|
it("does nothing when no legacy state dir exists", async () => {
|
||||||
const root = await makeTempRoot();
|
const root = await makeTempRoot();
|
||||||
const result = await autoMigrateLegacyStateDir({
|
const result = await runStateDirMigration(root);
|
||||||
env: {} as NodeJS.ProcessEnv,
|
|
||||||
homedir: () => root,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result.migrated).toBe(false);
|
expect(result.migrated).toBe(false);
|
||||||
expect(result.skipped).toBe(false);
|
expect(result.skipped).toBe(false);
|
||||||
@@ -395,13 +424,12 @@ describe("doctor legacy state migrations", () => {
|
|||||||
|
|
||||||
it("skips state dir migration when env override is set", async () => {
|
it("skips state dir migration when env override is set", async () => {
|
||||||
const root = await makeTempRoot();
|
const root = await makeTempRoot();
|
||||||
const legacyDir = path.join(root, ".openclaw");
|
const { legacyDir } = getStateDirMigrationPaths(root);
|
||||||
fs.mkdirSync(legacyDir, { recursive: true });
|
fs.mkdirSync(legacyDir, { recursive: true });
|
||||||
|
|
||||||
const result = await autoMigrateLegacyStateDir({
|
const result = await runStateDirMigration(root, {
|
||||||
env: { OPENCLAW_STATE_DIR: "/custom/state" } as NodeJS.ProcessEnv,
|
OPENCLAW_STATE_DIR: "/custom/state",
|
||||||
homedir: () => root,
|
} as NodeJS.ProcessEnv);
|
||||||
});
|
|
||||||
|
|
||||||
expect(result.skipped).toBe(true);
|
expect(result.skipped).toBe(true);
|
||||||
expect(result.migrated).toBe(false);
|
expect(result.migrated).toBe(false);
|
||||||
@@ -409,20 +437,18 @@ describe("doctor legacy state migrations", () => {
|
|||||||
|
|
||||||
it("does not warn when legacy state dir is an already-migrated symlink mirror", async () => {
|
it("does not warn when legacy state dir is an already-migrated symlink mirror", async () => {
|
||||||
const root = await makeTempRoot();
|
const root = await makeTempRoot();
|
||||||
const targetDir = path.join(root, ".openclaw");
|
const { targetDir, legacyDir } = ensureLegacyAndTargetStateDirs(root);
|
||||||
const legacyDir = path.join(root, ".clawdbot");
|
|
||||||
fs.mkdirSync(path.join(targetDir, "sessions"), { recursive: true });
|
fs.mkdirSync(path.join(targetDir, "sessions"), { recursive: true });
|
||||||
fs.mkdirSync(path.join(targetDir, "agent"), { recursive: true });
|
fs.mkdirSync(path.join(targetDir, "agent"), { recursive: true });
|
||||||
fs.mkdirSync(legacyDir, { recursive: true });
|
|
||||||
|
|
||||||
const dirLinkType = process.platform === "win32" ? "junction" : "dir";
|
fs.symlinkSync(
|
||||||
fs.symlinkSync(path.join(targetDir, "sessions"), path.join(legacyDir, "sessions"), dirLinkType);
|
path.join(targetDir, "sessions"),
|
||||||
fs.symlinkSync(path.join(targetDir, "agent"), path.join(legacyDir, "agent"), dirLinkType);
|
path.join(legacyDir, "sessions"),
|
||||||
|
DIR_LINK_TYPE,
|
||||||
|
);
|
||||||
|
fs.symlinkSync(path.join(targetDir, "agent"), path.join(legacyDir, "agent"), DIR_LINK_TYPE);
|
||||||
|
|
||||||
const result = await autoMigrateLegacyStateDir({
|
const result = await runStateDirMigration(root);
|
||||||
env: {} as NodeJS.ProcessEnv,
|
|
||||||
homedir: () => root,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result.migrated).toBe(false);
|
expect(result.migrated).toBe(false);
|
||||||
expect(result.warnings).toEqual([]);
|
expect(result.warnings).toEqual([]);
|
||||||
@@ -430,60 +456,34 @@ describe("doctor legacy state migrations", () => {
|
|||||||
|
|
||||||
it("warns when legacy state dir is empty and target already exists", async () => {
|
it("warns when legacy state dir is empty and target already exists", async () => {
|
||||||
const root = await makeTempRoot();
|
const root = await makeTempRoot();
|
||||||
const targetDir = path.join(root, ".openclaw");
|
const { targetDir } = ensureLegacyAndTargetStateDirs(root);
|
||||||
const legacyDir = path.join(root, ".clawdbot");
|
|
||||||
fs.mkdirSync(targetDir, { recursive: true });
|
|
||||||
fs.mkdirSync(legacyDir, { recursive: true });
|
|
||||||
|
|
||||||
const result = await autoMigrateLegacyStateDir({
|
const result = await runStateDirMigration(root);
|
||||||
env: {} as NodeJS.ProcessEnv,
|
expectTargetAlreadyExistsWarning(result, targetDir);
|
||||||
homedir: () => root,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result.migrated).toBe(false);
|
|
||||||
expect(result.warnings).toEqual([
|
|
||||||
`State dir migration skipped: target already exists (${targetDir}). Remove or merge manually.`,
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("warns when legacy state dir contains non-symlink entries and target already exists", async () => {
|
it("warns when legacy state dir contains non-symlink entries and target already exists", async () => {
|
||||||
const root = await makeTempRoot();
|
const root = await makeTempRoot();
|
||||||
const targetDir = path.join(root, ".openclaw");
|
const { targetDir, legacyDir } = ensureLegacyAndTargetStateDirs(root);
|
||||||
const legacyDir = path.join(root, ".clawdbot");
|
|
||||||
fs.mkdirSync(targetDir, { recursive: true });
|
|
||||||
fs.mkdirSync(legacyDir, { recursive: true });
|
|
||||||
fs.writeFileSync(path.join(legacyDir, "sessions.json"), "{}", "utf-8");
|
fs.writeFileSync(path.join(legacyDir, "sessions.json"), "{}", "utf-8");
|
||||||
|
|
||||||
const result = await autoMigrateLegacyStateDir({
|
const result = await runStateDirMigration(root);
|
||||||
env: {} as NodeJS.ProcessEnv,
|
expectTargetAlreadyExistsWarning(result, targetDir);
|
||||||
homedir: () => root,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result.migrated).toBe(false);
|
|
||||||
expect(result.warnings).toEqual([
|
|
||||||
`State dir migration skipped: target already exists (${targetDir}). Remove or merge manually.`,
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not warn when legacy state dir contains nested symlink mirrors", async () => {
|
it("does not warn when legacy state dir contains nested symlink mirrors", async () => {
|
||||||
const root = await makeTempRoot();
|
const root = await makeTempRoot();
|
||||||
const targetDir = path.join(root, ".openclaw");
|
const { targetDir, legacyDir } = ensureLegacyAndTargetStateDirs(root);
|
||||||
const legacyDir = path.join(root, ".clawdbot");
|
|
||||||
fs.mkdirSync(path.join(targetDir, "agents", "main"), { recursive: true });
|
fs.mkdirSync(path.join(targetDir, "agents", "main"), { recursive: true });
|
||||||
fs.mkdirSync(legacyDir, { recursive: true });
|
|
||||||
fs.mkdirSync(path.join(legacyDir, "agents"), { recursive: true });
|
fs.mkdirSync(path.join(legacyDir, "agents"), { recursive: true });
|
||||||
|
|
||||||
const dirLinkType = process.platform === "win32" ? "junction" : "dir";
|
|
||||||
fs.symlinkSync(
|
fs.symlinkSync(
|
||||||
path.join(targetDir, "agents", "main"),
|
path.join(targetDir, "agents", "main"),
|
||||||
path.join(legacyDir, "agents", "main"),
|
path.join(legacyDir, "agents", "main"),
|
||||||
dirLinkType,
|
DIR_LINK_TYPE,
|
||||||
);
|
);
|
||||||
|
|
||||||
const result = await autoMigrateLegacyStateDir({
|
const result = await runStateDirMigration(root);
|
||||||
env: {} as NodeJS.ProcessEnv,
|
|
||||||
homedir: () => root,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result.migrated).toBe(false);
|
expect(result.migrated).toBe(false);
|
||||||
expect(result.warnings).toEqual([]);
|
expect(result.warnings).toEqual([]);
|
||||||
@@ -491,72 +491,41 @@ describe("doctor legacy state migrations", () => {
|
|||||||
|
|
||||||
it("warns when legacy state dir symlink points outside the target tree", async () => {
|
it("warns when legacy state dir symlink points outside the target tree", async () => {
|
||||||
const root = await makeTempRoot();
|
const root = await makeTempRoot();
|
||||||
const targetDir = path.join(root, ".openclaw");
|
const { targetDir, legacyDir } = ensureLegacyAndTargetStateDirs(root);
|
||||||
const legacyDir = path.join(root, ".clawdbot");
|
|
||||||
const outsideDir = path.join(root, ".outside-state");
|
const outsideDir = path.join(root, ".outside-state");
|
||||||
fs.mkdirSync(path.join(targetDir, "sessions"), { recursive: true });
|
fs.mkdirSync(path.join(targetDir, "sessions"), { recursive: true });
|
||||||
fs.mkdirSync(legacyDir, { recursive: true });
|
|
||||||
fs.mkdirSync(outsideDir, { recursive: true });
|
fs.mkdirSync(outsideDir, { recursive: true });
|
||||||
|
|
||||||
const dirLinkType = process.platform === "win32" ? "junction" : "dir";
|
fs.symlinkSync(path.join(outsideDir), path.join(legacyDir, "sessions"), DIR_LINK_TYPE);
|
||||||
fs.symlinkSync(path.join(outsideDir), path.join(legacyDir, "sessions"), dirLinkType);
|
|
||||||
|
|
||||||
const result = await autoMigrateLegacyStateDir({
|
const result = await runStateDirMigration(root);
|
||||||
env: {} as NodeJS.ProcessEnv,
|
expectTargetAlreadyExistsWarning(result, targetDir);
|
||||||
homedir: () => root,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result.migrated).toBe(false);
|
|
||||||
expect(result.warnings).toEqual([
|
|
||||||
`State dir migration skipped: target already exists (${targetDir}). Remove or merge manually.`,
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("warns when legacy state dir contains a broken symlink target", async () => {
|
it("warns when legacy state dir contains a broken symlink target", async () => {
|
||||||
const root = await makeTempRoot();
|
const root = await makeTempRoot();
|
||||||
const targetDir = path.join(root, ".openclaw");
|
const { targetDir, legacyDir } = ensureLegacyAndTargetStateDirs(root);
|
||||||
const legacyDir = path.join(root, ".clawdbot");
|
|
||||||
fs.mkdirSync(path.join(targetDir, "sessions"), { recursive: true });
|
fs.mkdirSync(path.join(targetDir, "sessions"), { recursive: true });
|
||||||
fs.mkdirSync(legacyDir, { recursive: true });
|
|
||||||
|
|
||||||
const dirLinkType = process.platform === "win32" ? "junction" : "dir";
|
|
||||||
const targetSessionDir = path.join(targetDir, "sessions");
|
const targetSessionDir = path.join(targetDir, "sessions");
|
||||||
fs.symlinkSync(targetSessionDir, path.join(legacyDir, "sessions"), dirLinkType);
|
fs.symlinkSync(targetSessionDir, path.join(legacyDir, "sessions"), DIR_LINK_TYPE);
|
||||||
fs.rmSync(targetSessionDir, { recursive: true, force: true });
|
fs.rmSync(targetSessionDir, { recursive: true, force: true });
|
||||||
|
|
||||||
const result = await autoMigrateLegacyStateDir({
|
const result = await runStateDirMigration(root);
|
||||||
env: {} as NodeJS.ProcessEnv,
|
expectTargetAlreadyExistsWarning(result, targetDir);
|
||||||
homedir: () => root,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result.migrated).toBe(false);
|
|
||||||
expect(result.warnings).toEqual([
|
|
||||||
`State dir migration skipped: target already exists (${targetDir}). Remove or merge manually.`,
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("warns when legacy symlink escapes target tree through second-hop symlink", async () => {
|
it("warns when legacy symlink escapes target tree through second-hop symlink", async () => {
|
||||||
const root = await makeTempRoot();
|
const root = await makeTempRoot();
|
||||||
const targetDir = path.join(root, ".openclaw");
|
const { targetDir, legacyDir } = ensureLegacyAndTargetStateDirs(root);
|
||||||
const legacyDir = path.join(root, ".clawdbot");
|
|
||||||
const outsideDir = path.join(root, ".outside-state");
|
const outsideDir = path.join(root, ".outside-state");
|
||||||
fs.mkdirSync(targetDir, { recursive: true });
|
|
||||||
fs.mkdirSync(legacyDir, { recursive: true });
|
|
||||||
fs.mkdirSync(outsideDir, { recursive: true });
|
fs.mkdirSync(outsideDir, { recursive: true });
|
||||||
|
|
||||||
const dirLinkType = process.platform === "win32" ? "junction" : "dir";
|
|
||||||
const targetHop = path.join(targetDir, "hop");
|
const targetHop = path.join(targetDir, "hop");
|
||||||
fs.symlinkSync(outsideDir, targetHop, dirLinkType);
|
fs.symlinkSync(outsideDir, targetHop, DIR_LINK_TYPE);
|
||||||
fs.symlinkSync(targetHop, path.join(legacyDir, "sessions"), dirLinkType);
|
fs.symlinkSync(targetHop, path.join(legacyDir, "sessions"), DIR_LINK_TYPE);
|
||||||
|
|
||||||
const result = await autoMigrateLegacyStateDir({
|
const result = await runStateDirMigration(root);
|
||||||
env: {} as NodeJS.ProcessEnv,
|
expectTargetAlreadyExistsWarning(result, targetDir);
|
||||||
homedir: () => root,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result.migrated).toBe(false);
|
|
||||||
expect(result.warnings).toEqual([
|
|
||||||
`State dir migration skipped: target already exists (${targetDir}). Remove or merge manually.`,
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user