check / check (push) Waiting to run
Every line quak prints on stderr now goes through one log, in src/cli-log.ts, at one of six levels. The default, info, prints what quak printed before. -v adds each retry and the start and end of each refresh and ML data fetch; -vv adds each request with its result. -q prints only fatal lines, and is refused with -v; both are program options. Every non-zero exit logs one fatal line, so a run that exits 1 because files failed says how many and where to look. Every line reporting a failure is at error. --json changes stdout only, so backup and --json runs log their progress on stderr. withRetry and ApiClient report retries and requests through new callbacks. Model: opus-5-5
98 lines
3.4 KiB
TypeScript
98 lines
3.4 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.
|
|
*/
|
|
|
|
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 { UNATTENDED_RETRY_OPTIONS } from "../../src/retry.js";
|
|
|
|
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/);
|
|
});
|
|
});
|