Make backup wait for the server refresh and fail when it fails (closes #99)
check / check (push) Successful in 1m8s
check / check (push) Successful in 1m8s
lib.backup() refreshed through the background loop's refresh, which returns at once when one is already running and swallows a failure, so a backup could run on the previous file list, or on an empty cache, and exit 0. It now uses the refresh fresh() uses: it joins a running refresh or starts one, and rejects before touching any file when it fails. The CLI's error wrapper prints that as one line and exits 1. Model: opus-5-5
This commit was merged in pull request #122.
This commit is contained in:
@@ -625,6 +625,93 @@ describe("lib.backup", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// Every refresh fails, as with an expired session or no network.
|
||||
class FailingClient extends MockClient {
|
||||
override async collectionsSince(): Promise<CollectionsPage> {
|
||||
throw new Error("HTTP 401 from server");
|
||||
}
|
||||
}
|
||||
|
||||
// Holds its refresh open until `release()` is called, then reports a third
|
||||
// album, so a backup can be started while that refresh is still running.
|
||||
class HeldClient extends MockClient {
|
||||
release!: () => void;
|
||||
private held = new Promise<void>((resolve) => {
|
||||
this.release = resolve;
|
||||
});
|
||||
override async collectionsSince(): Promise<CollectionsPage> {
|
||||
await this.held;
|
||||
return {
|
||||
collections: [collection(3, "Later")],
|
||||
deleted: [],
|
||||
cursor: 2,
|
||||
};
|
||||
}
|
||||
override async filesSince(args: {
|
||||
collectionID: number;
|
||||
}): Promise<FilesPage> {
|
||||
if (args.collectionID !== 3) return super.filesSince(args);
|
||||
return { files: [file(300, 3, "late.jpg")], deleted: [], cursor: 2 };
|
||||
}
|
||||
}
|
||||
|
||||
// Fill the library cache on disk, so the next open starts its refresh in the
|
||||
// background instead of waiting for it.
|
||||
const fillCache = async (): Promise<void> => {
|
||||
const lib = await openLibrary(stubSource());
|
||||
await lib.close();
|
||||
};
|
||||
|
||||
describe("the refresh before a backup", () => {
|
||||
it("waits for a refresh already running and backs up what it found", async () => {
|
||||
await fillCache();
|
||||
const client = new HeldClient();
|
||||
const lib = await openLibrary(stubSource(), client);
|
||||
const outDir = join(root, "backup");
|
||||
|
||||
const backup = lib.backup({ downloadDirectory: outDir });
|
||||
client.release();
|
||||
const result = await backup;
|
||||
|
||||
expect(result.totalFiles).toBe(4);
|
||||
expect(existsSync(join(outDir, "originals", "300.jpg"))).toBe(true);
|
||||
await lib.close();
|
||||
});
|
||||
|
||||
it("fails before any download when the refresh fails, leaving failures.json as it was", async () => {
|
||||
await fillCache();
|
||||
const outDir = join(root, "backup");
|
||||
seedLedger(outDir, 100, "beach.jpg");
|
||||
const ledgerPath = join(outDir, "failures.json");
|
||||
const ledgerBefore = readFileSync(ledgerPath, "utf-8");
|
||||
const source = stubSource();
|
||||
const lib = await openLibrary(source, new FailingClient());
|
||||
|
||||
await expect(lib.backup({ downloadDirectory: outDir })).rejects.toThrow(
|
||||
"HTTP 401 from server",
|
||||
);
|
||||
|
||||
expect(source.originalCalls).toBe(0);
|
||||
expect(readFileSync(ledgerPath, "utf-8")).toBe(ledgerBefore);
|
||||
expect(existsSync(join(outDir, "originals"))).toBe(false);
|
||||
await lib.close();
|
||||
});
|
||||
|
||||
it("fails when the refresh fails on an empty cache, instead of backing up nothing", async () => {
|
||||
const source = stubSource();
|
||||
const lib = await openLibrary(source, new FailingClient());
|
||||
const outDir = join(root, "backup");
|
||||
|
||||
await expect(lib.backup({ downloadDirectory: outDir })).rejects.toThrow(
|
||||
"HTTP 401 from server",
|
||||
);
|
||||
|
||||
expect(source.originalCalls).toBe(0);
|
||||
expect(existsSync(outDir)).toBe(false);
|
||||
await lib.close();
|
||||
});
|
||||
});
|
||||
|
||||
// The album folders under collections/, driven through `runBackup` with a
|
||||
// stand-in library whose albums a test changes between runs.
|
||||
describe("backup album folders", () => {
|
||||
|
||||
Reference in New Issue
Block a user