Test: the exit code when an album is the only record left out
check / check (push) Successful in 3m11s
check / check (push) Successful in 3m11s
A backup whose only record quak cannot decrypt is an album exits 1 with nothing in failures.json, and 0 once the album decrypts or is deleted. The test of a file in two albums now also checks that the album where its copy does not decrypt neither links the original nor lists the file. Model: opus-5-5
This commit is contained in:
@@ -23,7 +23,13 @@
|
|||||||
* key replaced by bytes no key opens, or its metadata text that is not JSON.
|
* key replaced by bytes no key opens, or its metadata text that is not JSON.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
import {
|
||||||
|
existsSync,
|
||||||
|
mkdtempSync,
|
||||||
|
readdirSync,
|
||||||
|
readFileSync,
|
||||||
|
rmSync,
|
||||||
|
} from "node:fs";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
import sodium from "libsodium-wrappers-sumo";
|
import sodium from "libsodium-wrappers-sumo";
|
||||||
@@ -253,6 +259,18 @@ const brokenAccount = (): FakeEnte => {
|
|||||||
return ente;
|
return ente;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Album 1, "Trip", holds file 10, and album 2, "Family", holds file 20. Both
|
||||||
|
// files decrypt, but album 2's own key does not, so the album is the only
|
||||||
|
// record left out. All of it as of time 1.
|
||||||
|
const brokenAlbumAccount = (): FakeEnte => {
|
||||||
|
const ente = new FakeEnte();
|
||||||
|
ente.putAlbum(1, "Trip", 1);
|
||||||
|
ente.putFile(1, 10, 1);
|
||||||
|
ente.putAlbum(2, "Family", 1, true);
|
||||||
|
ente.putFile(2, 20, 1);
|
||||||
|
return ente;
|
||||||
|
};
|
||||||
|
|
||||||
// Collects everything written to it.
|
// Collects everything written to it.
|
||||||
class Output {
|
class Output {
|
||||||
text = "";
|
text = "";
|
||||||
@@ -321,6 +339,12 @@ const failures = (): Record<string, { error: string; attempts: number }> => {
|
|||||||
const stored = (id: number): boolean =>
|
const stored = (id: number): boolean =>
|
||||||
existsSync(join(backupDir(), DAY, `2026-03-01.${id}.jpg`));
|
existsSync(join(backupDir(), DAY, `2026-03-01.${id}.jpg`));
|
||||||
|
|
||||||
|
// The IDs of the files that album `name`'s JSON lists.
|
||||||
|
const albumFiles = (name: string): number[] =>
|
||||||
|
JSON.parse(
|
||||||
|
readFileSync(join(backupDir(), "collections", `${name}.json`), "utf-8"),
|
||||||
|
).files.map((f: { id: number }) => f.id);
|
||||||
|
|
||||||
describe("quak backup with records it cannot decrypt", () => {
|
describe("quak backup with records it cannot decrypt", () => {
|
||||||
it("backs up every other file, records each file it cannot decrypt in failures.json, names each such album, and exits 1", async () => {
|
it("backs up every other file, records each file it cannot decrypt in failures.json, names each such album, and exits 1", async () => {
|
||||||
expect(await backup(brokenAccount())).toBe(1);
|
expect(await backup(brokenAccount())).toBe(1);
|
||||||
@@ -398,6 +422,29 @@ describe("quak backup with records it cannot decrypt", () => {
|
|||||||
expect(stderr.text).not.toContain("FAILED");
|
expect(stderr.text).not.toContain("FAILED");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("exits 1 when the only record it cannot decrypt is an album, and 0 once the album decrypts", async () => {
|
||||||
|
const ente = brokenAlbumAccount();
|
||||||
|
|
||||||
|
// No file is in `failures.json`: the album alone keeps the exit code
|
||||||
|
// at 1.
|
||||||
|
expect(await backup(ente)).toBe(1);
|
||||||
|
expect(failures()).toEqual({});
|
||||||
|
|
||||||
|
ente.putAlbum(2, "Family", 2);
|
||||||
|
expect(await backup(ente)).toBe(0);
|
||||||
|
expect(stored(20)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("exits 1 when the only record it cannot decrypt is an album, and 0 once the album is deleted", async () => {
|
||||||
|
const ente = brokenAlbumAccount();
|
||||||
|
|
||||||
|
expect(await backup(ente)).toBe(1);
|
||||||
|
expect(failures()).toEqual({});
|
||||||
|
|
||||||
|
ente.deleteAlbum(2, 2);
|
||||||
|
expect(await backup(ente)).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
it("backs up a file that does not decrypt in one album but does in another, and does not report it", async () => {
|
it("backs up a file that does not decrypt in one album but does in another, and does not report it", async () => {
|
||||||
// File 30 is in albums 1 and 3, each holding its key sealed under the
|
// File 30 is in albums 1 and 3, each holding its key sealed under the
|
||||||
// album's own key. In album 1 that is corrupt; in album 3 it is not,
|
// album's own key. In album 1 that is corrupt; in album 3 it is not,
|
||||||
@@ -412,6 +459,13 @@ describe("quak backup with records it cannot decrypt", () => {
|
|||||||
|
|
||||||
expect(stored(30)).toBe(true);
|
expect(stored(30)).toBe(true);
|
||||||
expect(failures()).toEqual({});
|
expect(failures()).toEqual({});
|
||||||
|
// Album 1 has no copy of file 30 that decrypts, so it neither links
|
||||||
|
// the original nor lists the file; album 3 lists it.
|
||||||
|
expect(readdirSync(join(backupDir(), "collections", "Trip"))).toEqual(
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
expect(albumFiles("Trip")).toEqual([]);
|
||||||
|
expect(albumFiles("Favorites")).toEqual([30]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("backs up that file in a run limited to the album where it does not decrypt", async () => {
|
it("backs up that file in a run limited to the album where it does not decrypt", async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user