test(backup): align archive stream fixtures

Adapt the stream-shaped test harness required by the v6.11 backup backport.

(cherry picked from commit 13c1d3c408353d226d2968ff2a0a8108252976e1)
This commit is contained in:
Dallin Romney
2026-07-14 13:16:30 -07:00
parent a72609753b
commit ec376862e5
3 changed files with 49 additions and 36 deletions
+4 -7
View File
@@ -6,6 +6,7 @@ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi }
import { createTempHomeEnv, type TempHomeEnv } from "../test-utils/temp-home.js";
import {
backupVerifyCommandMock,
createMockTarStream,
createBackupTestRuntime,
mockStateOnlyBackupPlan,
resetBackupTempHome,
@@ -70,7 +71,7 @@ describe("backupCreateCommand atomic archive write", () => {
archivePrefix: "openclaw-backup-failure-",
});
try {
tarCreateMock.mockRejectedValueOnce(new Error("disk full"));
tarCreateMock.mockReturnValueOnce(createMockTarStream({ error: new Error("disk full") }));
await expect(
backupCreateCommand(runtime, {
@@ -93,9 +94,7 @@ describe("backupCreateCommand atomic archive write", () => {
const realLink = fs.link.bind(fs);
const linkSpy = vi.spyOn(fs, "link");
try {
tarCreateMock.mockImplementationOnce(async ({ file }: { file: string }) => {
await fs.writeFile(file, "archive-bytes", "utf8");
});
tarCreateMock.mockReturnValueOnce(createMockTarStream());
linkSpy.mockImplementationOnce(async (existingPath, newPath) => {
await fs.writeFile(newPath, "concurrent-archive", "utf8");
return await realLink(existingPath, newPath);
@@ -120,9 +119,7 @@ describe("backupCreateCommand atomic archive write", () => {
});
const linkSpy = vi.spyOn(fs, "link");
try {
tarCreateMock.mockImplementationOnce(async ({ file }: { file: string }) => {
await fs.writeFile(file, "archive-bytes", "utf8");
});
tarCreateMock.mockReturnValueOnce(createMockTarStream());
linkSpy.mockRejectedValueOnce(
Object.assign(new Error("hard links not supported"), { code: "EOPNOTSUPP" }),
);
+19
View File
@@ -1,6 +1,7 @@
// Backup test support provides temp config/state fixtures and mocked backup runtime helpers.
import fs from "node:fs/promises";
import path from "node:path";
import { Readable } from "node:stream";
import { vi } from "vitest";
import type { RuntimeEnv } from "../runtime.js";
import { deleteTestEnvValue } from "../test-utils/env.js";
@@ -14,6 +15,24 @@ const backupTestMocks = vi.hoisted(() => ({
export const { backupVerifyCommandMock, tarCreateMock } = backupTestMocks;
export function createMockTarStream(
params: {
beforeRead?: () => Promise<void> | void;
contents?: string;
error?: Error;
} = {},
): Readable {
return Readable.from(
(async function* () {
await params.beforeRead?.();
if (params.error) {
throw params.error;
}
yield params.contents ?? "archive-bytes";
})(),
);
}
vi.mock("tar", () => ({
c: backupTestMocks.tarCreateMock,
}));
+26 -29
View File
@@ -17,6 +17,7 @@ import {
} from "./backup-shared.js";
import {
backupVerifyCommandMock,
createMockTarStream,
createBackupTestRuntime,
mockStateOnlyBackupPlan,
resetBackupTempHome,
@@ -78,9 +79,7 @@ describe("backup commands", () => {
beforeEach(async () => {
await resetBackupTempHome(tempHome);
tarCreateMock.mockReset();
tarCreateMock.mockImplementation(async ({ file }: { file: string }) => {
await fs.writeFile(file, "archive-bytes", "utf8");
});
tarCreateMock.mockImplementation(() => createMockTarStream());
backupVerifyCommandMock.mockReset();
backupVerifyCommandMock.mockResolvedValue({
ok: true,
@@ -268,17 +267,16 @@ describe("backup commands", () => {
}),
);
tarCreateMock.mockImplementationOnce(
async (
options: { file: string; onWriteEntry?: (entry: { path: string }) => void },
entryPaths: string[],
) => {
capturedManifest = JSON.parse(
await fs.readFile(entryPaths[0], "utf8"),
) as CapturedBackupManifest;
capturedEntryPaths = entryPaths;
capturedOnWriteEntry = options.onWriteEntry ?? null;
await fs.writeFile(options.file, "archive-bytes", "utf8");
},
(options: { onWriteEntry?: (entry: { path: string }) => void }, entryPaths: string[]) =>
createMockTarStream({
beforeRead: async () => {
capturedManifest = JSON.parse(
await fs.readFile(entryPaths[0], "utf8"),
) as CapturedBackupManifest;
capturedEntryPaths = entryPaths;
capturedOnWriteEntry = options.onWriteEntry ?? null;
},
}),
);
const result = await backupCreateCommand(runtime, {
output: backupDir,
@@ -367,21 +365,20 @@ describe("backup commands", () => {
const runtime = createBackupTestRuntime();
await mockStateOnlyBackupPlan(stateDir);
tarCreateMock.mockImplementationOnce(
async (
options: { file: string; filter?: (entryPath: string) => boolean },
entryPaths: string[],
) => {
const manifestPath = entryPaths[0];
const stateRoot = entryPaths[1];
if (!manifestPath || !stateRoot) {
throw new Error("backup test expected manifest and state entries");
}
expect(options.filter?.(manifestPath)).toBe(true);
expect(
options.filter?.(path.join(stateRoot, "agents", "main", "sessions", "s.jsonl")),
).toBe(false);
await fs.writeFile(options.file, "archive-bytes", "utf8");
},
(options: { filter?: (entryPath: string) => boolean }, entryPaths: string[]) =>
createMockTarStream({
beforeRead: () => {
const manifestPath = entryPaths[0];
const stateRoot = entryPaths[1];
if (!manifestPath || !stateRoot) {
throw new Error("backup test expected manifest and state entries");
}
expect(options.filter?.(manifestPath)).toBe(true);
expect(
options.filter?.(path.join(stateRoot, "agents", "main", "sessions", "s.jsonl")),
).toBe(false);
},
}),
);
const result = await backupCreateCommand(runtime, {