Files
quak/test/cli/bin.test.ts
T
sneak d3965373ab
check / check (push) Successful in 9m58s
Tests pass under heavy host load (closes #233)
vitest runs at most four workers instead of one per core; minWorkers is
set to 1 because vitest 2 rejects a maxWorkers below its default
minWorkers. test/cli/bin.test.ts compiles the sources bin/quak.ts imports
and starts libsodium once, before its tests run. The fake accounts in
test/ente-server.ts and test/thumbnails/thumbnails.test.ts use
libsodium's lowest Argon2id cost, since each login runs the KDF again.

Model: opus-5-5
2026-11-10 20:33:29 +00:00

108 lines
3.8 KiB
TypeScript

/**
* Tests for the retry options `bin/quak.ts` loads the saved session with
* (`quak backup` gets the unattended ones, every other command the default),
* each with the callbacks that log its requests and retries, and for
* `quak files` taking its collection ID as an optional argument.
*
* Each test runs `bin/quak.ts`, as the smoke test does, with its session loader
* replaced by one that records the options it is given and reports no session,
* so the command stops before it makes any request.
*
* The first load of the sources `bin/quak.ts` imports compiles them, and the
* first `init()` starts libsodium. On a loaded host that takes longer than a
* test's timeout, so both happen here, before the tests run. Each test's
* `vi.resetModules()` then only runs the compiled sources again.
*/
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterAll, afterEach, describe, expect, it, vi } from "vitest";
import type { ApiClientOptions } from "../../src/api/client.js";
import "../../src/cli-commands.js";
import { init } from "../../src/crypto/index.js";
import "../../src/index.js";
import { UNATTENDED_RETRY_OPTIONS } from "../../src/retry.js";
await init();
const loaded = vi.hoisted(() => [] as (ApiClientOptions | undefined)[]);
vi.mock("../../src/cli-session.js", () => ({
loadSession: (_path: string, apiOptions?: ApiClientOptions) => {
loaded.push(apiOptions);
return null;
},
}));
const argv = process.argv;
const dir = mkdtempSync(join(tmpdir(), "quak-bin-test-"));
// Run `quak <args>` to completion and return what it wrote on stderr, which is
// kept off the terminal. The exit is swallowed.
const quak = async (...args: string[]): Promise<string> => {
let written = "";
vi.spyOn(process.stderr, "write").mockImplementation((text) => {
written += String(text);
return true;
});
vi.spyOn(process, "exit").mockImplementation(() => undefined as never);
process.argv = ["node", "quak", ...args];
vi.resetModules();
await import("../../bin/quak.js");
return written;
};
afterEach(() => {
process.argv = argv;
loaded.length = 0;
vi.restoreAllMocks();
});
afterAll(() => {
rmSync(dir, { recursive: true, force: true });
});
describe("bin/quak.ts session loading", () => {
it("loads the session for backup with the unattended retry options", async () => {
await quak("backup", dir);
// `vi.resetModules()` gave `bin/quak.ts` its own copy of
// `src/retry.ts`, whose `sleep` is a different function, so the
// options are compared by their numbers.
const { attempts, baseDelayMs, maxDelayMs } = UNATTENDED_RETRY_OPTIONS;
expect(loaded).toEqual([
{
retry: expect.objectContaining({
attempts,
baseDelayMs,
maxDelayMs,
onRetry: expect.any(Function),
}),
onRequest: expect.any(Function),
},
]);
});
it("loads the session for another command with the default retry options", async () => {
await quak("collections");
expect(loaded).toEqual([
{
retry: { onRetry: expect.any(Function) },
onRequest: expect.any(Function),
},
]);
});
});
// The command line takes `files` with or without a collection ID, and the
// command runs as far as its session check either way (issue #211).
describe("bin/quak.ts files", () => {
it("runs without a collection ID", async () => {
expect(await quak("files")).toMatch(/^Not logged in/);
});
it("runs with a collection ID", async () => {
expect(await quak("files", "7")).toMatch(/^Not logged in/);
});
});