mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-12 17:35:58 +00:00
test: extract backup path plan coverage
This commit is contained in:
@@ -83,39 +83,26 @@ export function buildBackupArchivePath(archiveRoot: string, sourcePath: string):
|
||||
return path.posix.join(archiveRoot, "payload", encodeAbsolutePathForBackupArchive(sourcePath));
|
||||
}
|
||||
|
||||
function compareCandidates(left: BackupAssetCandidate, right: BackupAssetCandidate): number {
|
||||
const depthDelta = left.canonicalPath.length - right.canonicalPath.length;
|
||||
if (depthDelta !== 0) {
|
||||
return depthDelta;
|
||||
}
|
||||
const priorityDelta = backupAssetPriority(left.kind) - backupAssetPriority(right.kind);
|
||||
if (priorityDelta !== 0) {
|
||||
return priorityDelta;
|
||||
}
|
||||
return left.canonicalPath.localeCompare(right.canonicalPath);
|
||||
}
|
||||
|
||||
async function canonicalizeExistingPath(targetPath: string): Promise<string> {
|
||||
try {
|
||||
return await fs.realpath(targetPath);
|
||||
} catch {
|
||||
return path.resolve(targetPath);
|
||||
}
|
||||
}
|
||||
|
||||
export async function resolveBackupPlanFromDisk(
|
||||
params: {
|
||||
includeWorkspace?: boolean;
|
||||
onlyConfig?: boolean;
|
||||
nowMs?: number;
|
||||
} = {},
|
||||
): Promise<BackupPlan> {
|
||||
export async function resolveBackupPlanFromPaths(params: {
|
||||
stateDir: string;
|
||||
configPath: string;
|
||||
oauthDir: string;
|
||||
workspaceDirs?: string[];
|
||||
includeWorkspace?: boolean;
|
||||
onlyConfig?: boolean;
|
||||
configInsideState?: boolean;
|
||||
oauthInsideState?: boolean;
|
||||
nowMs?: number;
|
||||
}): Promise<BackupPlan> {
|
||||
const includeWorkspace = params.includeWorkspace ?? true;
|
||||
const onlyConfig = params.onlyConfig ?? false;
|
||||
const stateDir = resolveStateDir();
|
||||
const configPath = resolveConfigPath();
|
||||
const oauthDir = resolveOAuthDir();
|
||||
const stateDir = params.stateDir;
|
||||
const configPath = params.configPath;
|
||||
const oauthDir = params.oauthDir;
|
||||
const archiveRoot = buildBackupArchiveRoot(params.nowMs);
|
||||
const workspaceDirs = includeWorkspace ? (params.workspaceDirs ?? []) : [];
|
||||
const configInsideState = params.configInsideState ?? false;
|
||||
const oauthInsideState = params.oauthInsideState ?? false;
|
||||
|
||||
if (onlyConfig) {
|
||||
const resolvedConfigPath = path.resolve(configPath);
|
||||
@@ -155,34 +142,18 @@ export async function resolveBackupPlanFromDisk(
|
||||
};
|
||||
}
|
||||
|
||||
const configSnapshot = await readConfigFileSnapshot();
|
||||
if (includeWorkspace && configSnapshot.exists && !configSnapshot.valid) {
|
||||
throw new Error(
|
||||
`Config invalid at ${shortenHomePath(configSnapshot.path)}. OpenClaw cannot reliably discover custom workspaces for backup. Fix the config or rerun with --no-include-workspace for a partial backup.`,
|
||||
);
|
||||
}
|
||||
const cleanupPlan = buildCleanupPlan({
|
||||
cfg: configSnapshot.config,
|
||||
stateDir,
|
||||
configPath,
|
||||
oauthDir,
|
||||
});
|
||||
const workspaceDirs = includeWorkspace ? cleanupPlan.workspaceDirs : [];
|
||||
|
||||
const rawCandidates: Array<Pick<BackupAssetCandidate, "kind" | "sourcePath">> = [
|
||||
{ kind: "state", sourcePath: path.resolve(stateDir) },
|
||||
...(cleanupPlan.configInsideState
|
||||
...(configInsideState
|
||||
? []
|
||||
: [{ kind: "config" as const, sourcePath: path.resolve(configPath) }]),
|
||||
...(cleanupPlan.oauthInsideState
|
||||
...(oauthInsideState
|
||||
? []
|
||||
: [{ kind: "credentials" as const, sourcePath: path.resolve(oauthDir) }]),
|
||||
...(includeWorkspace
|
||||
? workspaceDirs.map((workspaceDir) => ({
|
||||
kind: "workspace" as const,
|
||||
sourcePath: path.resolve(workspaceDir),
|
||||
}))
|
||||
: []),
|
||||
...workspaceDirs.map((workspaceDir) => ({
|
||||
kind: "workspace" as const,
|
||||
sourcePath: path.resolve(workspaceDir),
|
||||
})),
|
||||
];
|
||||
|
||||
const candidates: BackupAssetCandidate[] = await Promise.all(
|
||||
@@ -252,3 +223,61 @@ export async function resolveBackupPlanFromDisk(
|
||||
skipped,
|
||||
};
|
||||
}
|
||||
|
||||
function compareCandidates(left: BackupAssetCandidate, right: BackupAssetCandidate): number {
|
||||
const depthDelta = left.canonicalPath.length - right.canonicalPath.length;
|
||||
if (depthDelta !== 0) {
|
||||
return depthDelta;
|
||||
}
|
||||
const priorityDelta = backupAssetPriority(left.kind) - backupAssetPriority(right.kind);
|
||||
if (priorityDelta !== 0) {
|
||||
return priorityDelta;
|
||||
}
|
||||
return left.canonicalPath.localeCompare(right.canonicalPath);
|
||||
}
|
||||
|
||||
async function canonicalizeExistingPath(targetPath: string): Promise<string> {
|
||||
try {
|
||||
return await fs.realpath(targetPath);
|
||||
} catch {
|
||||
return path.resolve(targetPath);
|
||||
}
|
||||
}
|
||||
|
||||
export async function resolveBackupPlanFromDisk(
|
||||
params: {
|
||||
includeWorkspace?: boolean;
|
||||
onlyConfig?: boolean;
|
||||
nowMs?: number;
|
||||
} = {},
|
||||
): Promise<BackupPlan> {
|
||||
const includeWorkspace = params.includeWorkspace ?? true;
|
||||
const onlyConfig = params.onlyConfig ?? false;
|
||||
const stateDir = resolveStateDir();
|
||||
const configPath = resolveConfigPath();
|
||||
const oauthDir = resolveOAuthDir();
|
||||
|
||||
const configSnapshot = await readConfigFileSnapshot();
|
||||
if (includeWorkspace && configSnapshot.exists && !configSnapshot.valid) {
|
||||
throw new Error(
|
||||
`Config invalid at ${shortenHomePath(configSnapshot.path)}. OpenClaw cannot reliably discover custom workspaces for backup. Fix the config or rerun with --no-include-workspace for a partial backup.`,
|
||||
);
|
||||
}
|
||||
const cleanupPlan = buildCleanupPlan({
|
||||
cfg: configSnapshot.config,
|
||||
stateDir,
|
||||
configPath,
|
||||
oauthDir,
|
||||
});
|
||||
return await resolveBackupPlanFromPaths({
|
||||
stateDir,
|
||||
configPath,
|
||||
oauthDir,
|
||||
workspaceDirs: includeWorkspace ? cleanupPlan.workspaceDirs : [],
|
||||
includeWorkspace,
|
||||
onlyConfig,
|
||||
configInsideState: cleanupPlan.configInsideState,
|
||||
oauthInsideState: cleanupPlan.oauthInsideState,
|
||||
nowMs: params.nowMs,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { createTempHomeEnv, type TempHomeEnv } from "../test-utils/temp-home.js"
|
||||
import {
|
||||
buildBackupArchiveRoot,
|
||||
encodeAbsolutePathForBackupArchive,
|
||||
resolveBackupPlanFromPaths,
|
||||
resolveBackupPlanFromDisk,
|
||||
} from "./backup-shared.js";
|
||||
import { backupCreateCommand } from "./backup.js";
|
||||
@@ -88,13 +89,25 @@ describe("backup commands", () => {
|
||||
|
||||
it("collapses default config, credentials, and workspace into the state backup root", async () => {
|
||||
const stateDir = path.join(tempHome.home, ".openclaw");
|
||||
await fs.writeFile(path.join(stateDir, "openclaw.json"), JSON.stringify({}), "utf8");
|
||||
await fs.mkdir(path.join(stateDir, "credentials"), { recursive: true });
|
||||
await fs.writeFile(path.join(stateDir, "credentials", "oauth.json"), "{}", "utf8");
|
||||
await fs.mkdir(path.join(stateDir, "workspace"), { recursive: true });
|
||||
await fs.writeFile(path.join(stateDir, "workspace", "SOUL.md"), "# soul\n", "utf8");
|
||||
const configPath = path.join(stateDir, "openclaw.json");
|
||||
const oauthDir = path.join(stateDir, "credentials");
|
||||
const workspaceDir = path.join(stateDir, "workspace");
|
||||
await fs.writeFile(configPath, JSON.stringify({}), "utf8");
|
||||
await fs.mkdir(oauthDir, { recursive: true });
|
||||
await fs.writeFile(path.join(oauthDir, "oauth.json"), "{}", "utf8");
|
||||
await fs.mkdir(workspaceDir, { recursive: true });
|
||||
await fs.writeFile(path.join(workspaceDir, "SOUL.md"), "# soul\n", "utf8");
|
||||
|
||||
const plan = await resolveBackupPlanFromDisk({ includeWorkspace: true, nowMs: 123 });
|
||||
const plan = await resolveBackupPlanFromPaths({
|
||||
stateDir,
|
||||
configPath,
|
||||
oauthDir,
|
||||
workspaceDirs: [workspaceDir],
|
||||
includeWorkspace: true,
|
||||
configInsideState: true,
|
||||
oauthInsideState: true,
|
||||
nowMs: 123,
|
||||
});
|
||||
expectWorkspaceCoveredByState(plan);
|
||||
});
|
||||
|
||||
@@ -111,19 +124,16 @@ describe("backup commands", () => {
|
||||
await fs.mkdir(workspaceDir, { recursive: true });
|
||||
await fs.writeFile(path.join(workspaceDir, "SOUL.md"), "# soul\n", "utf8");
|
||||
await fs.symlink(workspaceDir, workspaceLink);
|
||||
await fs.writeFile(
|
||||
path.join(stateDir, "openclaw.json"),
|
||||
JSON.stringify({
|
||||
agents: {
|
||||
defaults: {
|
||||
workspace: workspaceLink,
|
||||
},
|
||||
},
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
const plan = await resolveBackupPlanFromDisk({ includeWorkspace: true, nowMs: 123 });
|
||||
const plan = await resolveBackupPlanFromPaths({
|
||||
stateDir,
|
||||
configPath: path.join(stateDir, "openclaw.json"),
|
||||
oauthDir: path.join(stateDir, "credentials"),
|
||||
workspaceDirs: [workspaceLink],
|
||||
includeWorkspace: true,
|
||||
configInsideState: true,
|
||||
oauthInsideState: true,
|
||||
nowMs: 123,
|
||||
});
|
||||
expectWorkspaceCoveredByState(plan);
|
||||
} finally {
|
||||
await fs.rm(symlinkDir, { recursive: true, force: true });
|
||||
|
||||
Reference in New Issue
Block a user