Refresh before backup-metadata and the thumbnail helpers answer (closes #100)
check / check (push) Successful in 1m0s
check / check (push) Successful in 1m0s
backup-metadata, helper list-missing-thumbnails and helper fix-missing-thumbnails now await lib.fresh() before reading the library, as collections, files, get and get-thumb already do. A file added since the cache was written is included, and a failed refresh is thrown, so the CLI prints one line and exits 1 instead of answering from a stale or empty cache. The README lists them among the commands that refresh first. Model: opus-5-5
This commit was merged in pull request #123.
This commit is contained in:
@@ -33,7 +33,9 @@ import {
|
||||
getCommand,
|
||||
getThumbCommand,
|
||||
backupCommand,
|
||||
backupMetadataCommand,
|
||||
listMissingThumbnailsCommand,
|
||||
fixMissingThumbnailsCommand,
|
||||
} from "../../src/cli-commands.js";
|
||||
import { run } from "../../src/cli-run.js";
|
||||
import { loadSession } from "../../src/cli-session.js";
|
||||
@@ -84,8 +86,26 @@ const FILES: Record<number, EnteFile[]> = {
|
||||
|
||||
// An original is 7 bytes and a thumbnail 3. `failID` makes that file's
|
||||
// original fail; `emptyThumbID` makes the server report that file's
|
||||
// thumbnail as empty.
|
||||
const fakeClient = (opts: { failID?: number; emptyThumbID?: number } = {}) => {
|
||||
// thumbnail as empty. `withNewFile` adds new.jpg (102) to Vacation, advancing
|
||||
// the collection's updationTime as the server does, and `refreshError` makes
|
||||
// listing collections fail with that message.
|
||||
const fakeClient = (
|
||||
opts: {
|
||||
failID?: number;
|
||||
emptyThumbID?: number;
|
||||
withNewFile?: boolean;
|
||||
refreshError?: string;
|
||||
} = {},
|
||||
) => {
|
||||
const collections = opts.withNewFile
|
||||
? [{ ...COLLECTIONS[0], updationTime: 2 }, COLLECTIONS[1]]
|
||||
: COLLECTIONS;
|
||||
const files = opts.withNewFile
|
||||
? {
|
||||
...FILES,
|
||||
1: [...FILES[1], { ...file(102, 1, "new.jpg"), updationTime: 2 }],
|
||||
}
|
||||
: FILES;
|
||||
const source: ContentSource = {
|
||||
original: async ({ file: f, destination }) => {
|
||||
if (f.id === opts.failID) throw new Error("HTTP 500 from server");
|
||||
@@ -99,18 +119,19 @@ const fakeClient = (opts: { failID?: number; emptyThumbID?: number } = {}) => {
|
||||
};
|
||||
const fake = {
|
||||
whoami: () => ({ email: "cli@example.com", userID: USER_ID }),
|
||||
collectionsSince: async () => ({
|
||||
collections: COLLECTIONS,
|
||||
deleted: [],
|
||||
cursor: 1,
|
||||
}),
|
||||
collectionsSince: async () => {
|
||||
if (opts.refreshError) throw new Error(opts.refreshError);
|
||||
return { collections, deleted: [], cursor: 1 };
|
||||
},
|
||||
filesSince: async (args: { collectionID: number }) => ({
|
||||
files: FILES[args.collectionID] ?? [],
|
||||
files: files[args.collectionID] ?? [],
|
||||
deleted: [],
|
||||
cursor: 1,
|
||||
}),
|
||||
contentSource: () => source,
|
||||
getApiClient: () => ({
|
||||
// The ML data request of `backup-metadata`: no file has any.
|
||||
postJSON: async () => ({ data: [] }),
|
||||
getThumbnailStream: async (fileID: number) =>
|
||||
new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
@@ -492,3 +513,66 @@ describe("helper list-missing-thumbnails", () => {
|
||||
expect(stderr.text).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
// Each test first runs `collections` so the cache holds the account as it was,
|
||||
// then changes the server under it.
|
||||
describe("backup-metadata and the thumbnail helpers refresh first", () => {
|
||||
beforeEach(async () => {
|
||||
expect(await collectionsCommand(context(), {})).toBe(0);
|
||||
stdout.text = "";
|
||||
stderr.text = "";
|
||||
});
|
||||
|
||||
it("backup-metadata writes a file added since the cache was written", async () => {
|
||||
const ctx = context(fakeClient({ withNewFile: true }));
|
||||
const dir = join(root, "dump");
|
||||
expect(await backupMetadataCommand(ctx, dir, {})).toBe(0);
|
||||
expect(
|
||||
existsSync(join(dir, "collections", "1-Vacation", "102.json")),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("list-missing-thumbnails checks a file added since the cache was written", async () => {
|
||||
const ctx = context(
|
||||
fakeClient({ withNewFile: true, emptyThumbID: 102 }),
|
||||
);
|
||||
expect(await listMissingThumbnailsCommand(ctx, {})).toBe(0);
|
||||
expect(stdout.text).toBe(
|
||||
"102\tnew.jpg\tVacation\tempty thumbnail (0 bytes)\n",
|
||||
);
|
||||
});
|
||||
|
||||
it("fix-missing-thumbnails finds a file added since the cache was written", async () => {
|
||||
const ctx = context(fakeClient({ withNewFile: true }));
|
||||
expect(
|
||||
await fixMissingThumbnailsCommand(ctx, {
|
||||
file: ["102"],
|
||||
json: true,
|
||||
}),
|
||||
).toBe(0);
|
||||
// Found, then skipped because the server records no thumbnail size
|
||||
// for it; a file missing from the cache would fail as not found.
|
||||
expect(JSON.parse(stdout.text)).toMatchObject([
|
||||
{ fileID: 102, title: "new.jpg", status: "skipped" },
|
||||
]);
|
||||
});
|
||||
|
||||
// `run` in `cli-run.ts` prints a thrown error as one line and exits 1.
|
||||
it("all three throw when the refresh fails", async () => {
|
||||
const ctx = context(
|
||||
fakeClient({ refreshError: "HTTP 503 from server" }),
|
||||
);
|
||||
const dir = join(root, "dump");
|
||||
await expect(backupMetadataCommand(ctx, dir, {})).rejects.toThrow(
|
||||
"HTTP 503 from server",
|
||||
);
|
||||
expect(existsSync(dir)).toBe(false);
|
||||
await expect(listMissingThumbnailsCommand(ctx, {})).rejects.toThrow(
|
||||
"HTTP 503 from server",
|
||||
);
|
||||
await expect(
|
||||
fixMissingThumbnailsCommand(ctx, { file: ["100"] }),
|
||||
).rejects.toThrow("HTTP 503 from server");
|
||||
expect(stdout.text).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user