Skip to content

Commit 99e3d4a

Browse files
committed
test(commands): share atomic backup setup
1 parent b78713a commit 99e3d4a

1 file changed

Lines changed: 44 additions & 69 deletions

File tree

src/commands/backup.atomic.test.ts

Lines changed: 44 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -45,33 +45,48 @@ describe("backupCreateCommand atomic archive write", () => {
4545
await tempHome.restore();
4646
});
4747

48-
it("does not leave a partial final archive behind when tar creation fails", async () => {
48+
async function prepareAtomicBackupScenario(params: {
49+
archivePrefix: string;
50+
outputName?: string;
51+
}) {
4952
const stateDir = path.join(tempHome.home, ".openclaw");
50-
const archiveDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-backup-failure-"));
51-
try {
52-
await fs.writeFile(path.join(stateDir, "openclaw.json"), JSON.stringify({}), "utf8");
53-
await fs.writeFile(path.join(stateDir, "state.txt"), "state\n", "utf8");
53+
const archiveDir = await fs.mkdtemp(path.join(os.tmpdir(), params.archivePrefix));
54+
await fs.writeFile(path.join(stateDir, "openclaw.json"), JSON.stringify({}), "utf8");
55+
await fs.writeFile(path.join(stateDir, "state.txt"), "state\n", "utf8");
56+
57+
const runtime = {
58+
log: vi.fn(),
59+
error: vi.fn(),
60+
exit: vi.fn(),
61+
};
62+
const outputPath = path.join(archiveDir, params.outputName ?? "backup.tar.gz");
63+
64+
vi.spyOn(backupShared, "resolveBackupPlanFromDisk").mockResolvedValue(
65+
await resolveBackupPlanFromPaths({
66+
stateDir,
67+
configPath: path.join(stateDir, "openclaw.json"),
68+
oauthDir: path.join(stateDir, "credentials"),
69+
includeWorkspace: false,
70+
configInsideState: true,
71+
oauthInsideState: true,
72+
nowMs: 123,
73+
}),
74+
);
75+
76+
return {
77+
archiveDir,
78+
outputPath,
79+
runtime,
80+
};
81+
}
5482

83+
it("does not leave a partial final archive behind when tar creation fails", async () => {
84+
const { archiveDir, outputPath, runtime } = await prepareAtomicBackupScenario({
85+
archivePrefix: "openclaw-backup-failure-",
86+
});
87+
try {
5588
tarCreateMock.mockRejectedValueOnce(new Error("disk full"));
5689

57-
const runtime = {
58-
log: vi.fn(),
59-
error: vi.fn(),
60-
exit: vi.fn(),
61-
};
62-
const outputPath = path.join(archiveDir, "backup.tar.gz");
63-
vi.spyOn(backupShared, "resolveBackupPlanFromDisk").mockResolvedValue(
64-
await resolveBackupPlanFromPaths({
65-
stateDir,
66-
configPath: path.join(stateDir, "openclaw.json"),
67-
oauthDir: path.join(stateDir, "credentials"),
68-
includeWorkspace: false,
69-
configInsideState: true,
70-
oauthInsideState: true,
71-
nowMs: 123,
72-
}),
73-
);
74-
7590
await expect(
7691
backupCreateCommand(runtime, {
7792
output: outputPath,
@@ -87,14 +102,12 @@ describe("backupCreateCommand atomic archive write", () => {
87102
});
88103

89104
it("does not overwrite an archive created after readiness checks complete", async () => {
90-
const stateDir = path.join(tempHome.home, ".openclaw");
91-
const archiveDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-backup-race-"));
105+
const { archiveDir, outputPath, runtime } = await prepareAtomicBackupScenario({
106+
archivePrefix: "openclaw-backup-race-",
107+
});
92108
const realLink = fs.link.bind(fs);
93109
const linkSpy = vi.spyOn(fs, "link");
94110
try {
95-
await fs.writeFile(path.join(stateDir, "openclaw.json"), JSON.stringify({}), "utf8");
96-
await fs.writeFile(path.join(stateDir, "state.txt"), "state\n", "utf8");
97-
98111
tarCreateMock.mockImplementationOnce(async ({ file }: { file: string }) => {
99112
await fs.writeFile(file, "archive-bytes", "utf8");
100113
});
@@ -103,24 +116,6 @@ describe("backupCreateCommand atomic archive write", () => {
103116
return await realLink(existingPath, newPath);
104117
});
105118

106-
const runtime = {
107-
log: vi.fn(),
108-
error: vi.fn(),
109-
exit: vi.fn(),
110-
};
111-
const outputPath = path.join(archiveDir, "backup.tar.gz");
112-
vi.spyOn(backupShared, "resolveBackupPlanFromDisk").mockResolvedValue(
113-
await resolveBackupPlanFromPaths({
114-
stateDir,
115-
configPath: path.join(stateDir, "openclaw.json"),
116-
oauthDir: path.join(stateDir, "credentials"),
117-
includeWorkspace: false,
118-
configInsideState: true,
119-
oauthInsideState: true,
120-
nowMs: 123,
121-
}),
122-
);
123-
124119
await expect(
125120
backupCreateCommand(runtime, {
126121
output: outputPath,
@@ -135,38 +130,18 @@ describe("backupCreateCommand atomic archive write", () => {
135130
});
136131

137132
it("falls back to exclusive copy when hard-link publication is unsupported", async () => {
138-
const stateDir = path.join(tempHome.home, ".openclaw");
139-
const archiveDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-backup-copy-fallback-"));
133+
const { archiveDir, outputPath, runtime } = await prepareAtomicBackupScenario({
134+
archivePrefix: "openclaw-backup-copy-fallback-",
135+
});
140136
const linkSpy = vi.spyOn(fs, "link");
141137
try {
142-
await fs.writeFile(path.join(stateDir, "openclaw.json"), JSON.stringify({}), "utf8");
143-
await fs.writeFile(path.join(stateDir, "state.txt"), "state\n", "utf8");
144-
145138
tarCreateMock.mockImplementationOnce(async ({ file }: { file: string }) => {
146139
await fs.writeFile(file, "archive-bytes", "utf8");
147140
});
148141
linkSpy.mockRejectedValueOnce(
149142
Object.assign(new Error("hard links not supported"), { code: "EOPNOTSUPP" }),
150143
);
151144

152-
const runtime = {
153-
log: vi.fn(),
154-
error: vi.fn(),
155-
exit: vi.fn(),
156-
};
157-
const outputPath = path.join(archiveDir, "backup.tar.gz");
158-
vi.spyOn(backupShared, "resolveBackupPlanFromDisk").mockResolvedValue(
159-
await resolveBackupPlanFromPaths({
160-
stateDir,
161-
configPath: path.join(stateDir, "openclaw.json"),
162-
oauthDir: path.join(stateDir, "credentials"),
163-
includeWorkspace: false,
164-
configInsideState: true,
165-
oauthInsideState: true,
166-
nowMs: 123,
167-
}),
168-
);
169-
170145
const result = await backupCreateCommand(runtime, {
171146
output: outputPath,
172147
});

0 commit comments

Comments
 (0)