






















@@ -8,6 +8,7 @@ import * as backupShared from "./backup-shared.js";
88import {
99buildBackupArchiveRoot,
1010encodeAbsolutePathForBackupArchive,
11+type BackupAsset,
1112resolveBackupPlanFromPaths,
1213resolveBackupPlanFromDisk,
1314} from "./backup-shared.js";
@@ -21,6 +22,25 @@ import {
21222223const { backupCreateCommand } = await import("./backup.js");
232425+type CapturedBackupManifest = {
26+schemaVersion: 1;
27+createdAt: string;
28+archiveRoot: string;
29+platform: NodeJS.Platform;
30+options: {
31+includeWorkspace: boolean;
32+onlyConfig: boolean;
33+};
34+paths: {
35+stateDir: string;
36+configPath: string;
37+oauthDir: string;
38+workspaceDirs: string[];
39+};
40+assets: Array<Pick<BackupAsset, "kind" | "sourcePath" | "archivePath">>;
41+skipped: Array<{ kind: string; sourcePath: string; reason: string; coveredBy?: string }>;
42+};
43+2444describe("backup commands", () => {
2545let tempHome: TempHomeEnv;
2646@@ -89,10 +109,19 @@ describe("backup commands", () => {
89109) {
90110expect(plan.included).toHaveLength(1);
91111const [included] = plan.included;
92-expect(included).toMatchObject({ kind: "state" });
93-expect(plan.skipped).toEqual(
94-expect.arrayContaining([expect.objectContaining({ kind: "workspace", reason: "covered" })]),
95-);
112+if (!included) {
113+throw new Error("Expected state asset to be included");
114+}
115+expect(included.kind).toBe("state");
116+expect(plan.skipped).toHaveLength(1);
117+const [skipped] = plan.skipped;
118+if (!skipped) {
119+throw new Error("Expected covered workspace skip entry");
120+}
121+expect(skipped.kind).toBe("workspace");
122+expect(skipped.reason).toBe("covered");
123+expect(skipped.coveredBy).toBe(included.displayPath);
124+expect(path.relative(included.sourcePath, skipped.sourcePath).startsWith("..")).toBe(false);
96125}
9712698127function expectOnlyAssetKind(assets: Array<{ kind: string }>, kind: string) {
@@ -159,9 +188,7 @@ describe("backup commands", () => {
159188const externalWorkspace = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-workspace-"));
160189const configPath = path.join(tempHome.home, "custom-config.json");
161190const backupDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-backups-"));
162-let capturedManifest: {
163-assets: Array<{ kind: string; archivePath: string }>;
164-} | null = null;
191+let capturedManifest: CapturedBackupManifest | null = null;
165192let capturedEntryPaths: string[] = [];
166193let capturedOnWriteEntry: ((entry: { path: string }) => void) | null = null;
167194try {
@@ -200,9 +227,9 @@ describe("backup commands", () => {
200227options: { file: string; onWriteEntry?: (entry: { path: string }) => void },
201228entryPaths: string[],
202229) => {
203-capturedManifest = JSON.parse(await fs.readFile(entryPaths[0], "utf8")) as {
204-assets: Array<{ kind: string; archivePath: string }>;
205-};
230+capturedManifest = JSON.parse(
231+await fs.readFile(entryPaths[0], "utf8"),
232+) as CapturedBackupManifest;
206233capturedEntryPaths = entryPaths;
207234capturedOnWriteEntry = options.onWriteEntry ?? null;
208235await fs.writeFile(options.file, "archive-bytes", "utf8");
@@ -217,22 +244,36 @@ describe("backup commands", () => {
217244expect(result.archivePath).toBe(
218245path.join(backupDir, `${buildBackupArchiveRoot(nowMs)}.tar.gz`),
219246);
220-expect(capturedManifest).toEqual(expect.objectContaining({ assets: expect.any(Array) }));
221247expect(typeof capturedOnWriteEntry).toBe("function");
222248if (capturedManifest === null || capturedOnWriteEntry === null) {
223249throw new Error("Expected backup manifest and archive entry callback");
224250}
225-const manifest = capturedManifest as unknown as {
226-assets: Array<{ kind: string; archivePath: string }>;
227-};
251+const manifest = capturedManifest as CapturedBackupManifest;
228252const onWriteEntry = capturedOnWriteEntry as unknown as (entry: { path: string }) => void;
253+expect(manifest.schemaVersion).toBe(1);
254+expect(manifest.createdAt).toBe(result.createdAt);
255+expect(manifest.archiveRoot).toBe(result.archiveRoot);
256+expect(manifest.platform).toBe(process.platform);
257+expect(manifest.options).toEqual({ includeWorkspace: true, onlyConfig: false });
258+expect(manifest.paths).toEqual({
259+ stateDir,
260+ configPath,
261+oauthDir: path.join(stateDir, "credentials"),
262+workspaceDirs: [externalWorkspace],
263+});
229264expect(manifest.assets).toEqual(
230-expect.arrayContaining([
231-expect.objectContaining({ kind: "state" }),
232-expect.objectContaining({ kind: "config" }),
233-expect.objectContaining({ kind: "workspace" }),
234-]),
265+result.assets.map((asset) => ({
266+kind: asset.kind,
267+sourcePath: asset.sourcePath,
268+archivePath: asset.archivePath,
269+})),
235270);
271+expect(manifest.assets.map((asset) => asset.kind).toSorted()).toEqual([
272+"config",
273+"state",
274+"workspace",
275+]);
276+expect(manifest.skipped).toEqual([]);
236277237278const stateAsset = result.assets.find((asset) => asset.kind === "state");
238279const workspaceAsset = result.assets.find((asset) => asset.kind === "workspace");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。