Files
quak/test/cli/run.test.ts
T
sneak 6967117cee
check / check (push) Successful in 2m19s
An expired session exits 3 with one line saying to run quak login (closes #164)
A 401 from the server reaches run in src/cli-run.ts as the ApiError the
refresh threw, so run recognises it there, prints one line saying to run
"quak login" and exits 3. A missing or corrupt session file keeps its message
and also exits 3. quak backup meets the 401 on its first refresh, before it
touches any file. Every other error still exits 1.

Judgement call: quak logout is unchanged; it handles its own errors and
deletes the session file whatever the server answers.
Judgement call: quak backup still prints its two progress lines before the
error line.

Model: opus-5-5
2026-10-06 04:31:45 +00:00

82 lines
2.7 KiB
TypeScript

/**
* Tests for `run` in `src/cli-run.ts`, which every CLI command goes through.
*/
import { PassThrough } from "node:stream";
import { describe, it, expect } from "vitest";
import { ApiError } from "../../src/api/client.js";
import { run } from "../../src/cli-run.js";
// A stream whose written text is kept in `text`; writes finish at once, so
// nothing is left waiting to drain.
const collector = (): { stream: PassThrough; text: () => string } => {
const stream = new PassThrough();
const chunks: string[] = [];
stream.on("data", (chunk: Buffer) => chunks.push(chunk.toString()));
return { stream, text: () => chunks.join("") };
};
const runToExit = async (
command: Promise<number>,
): Promise<{ code: number; stdout: string; stderr: string }> => {
const stdout = collector();
const stderr = collector();
const code = await new Promise<number>((resolve) => {
void run(command, stdout.stream, stderr.stream, resolve);
});
return { code, stdout: stdout.text(), stderr: stderr.text() };
};
describe("run", () => {
it("exits with the code the command returns", async () => {
const result = await runToExit(Promise.resolve(3));
expect(result).toEqual({ code: 3, stdout: "", stderr: "" });
});
it("prints a thrown error as one line without a stack trace and exits 1", async () => {
const failing = async (): Promise<number> => {
throw new Error(
"ENOTDIR: not a directory, mkdir '/dev/null/x/originals'",
);
};
const result = await runToExit(failing());
expect(result.code).toBe(1);
expect(result.stdout).toBe("");
expect(result.stderr).toBe(
"quak: ENOTDIR: not a directory, mkdir '/dev/null/x/originals'\n",
);
});
it("prints a thrown value that is not an Error", async () => {
const result = await runToExit(Promise.reject("offline"));
expect(result).toEqual({
code: 1,
stdout: "",
stderr: "quak: offline\n",
});
});
it("on a 401 from the server says to run quak login, on one line, and exits 3", async () => {
const result = await runToExit(
Promise.reject(new ApiError("unauthorized", 401)),
);
expect(result).toEqual({
code: 3,
stdout: "",
stderr: `quak: the saved session is no longer valid; run "quak login"\n`,
});
});
it("prints another HTTP error as it is and exits 1", async () => {
const result = await runToExit(
Promise.reject(new ApiError("forbidden", 403)),
);
expect(result).toEqual({
code: 1,
stdout: "",
stderr: "quak: forbidden\n",
});
});
});