check / check (push) Successful in 3m8s
A 401 from the server that ends a command reaches run in src/cli-run.ts as an ApiError; run prints one line saying to run "quak login" and exits 3. quak backup meets it on its first refresh, before it touches any file. For the commands that load the saved session, a missing or corrupt session file keeps its message and also exits 3. 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
82 lines
2.7 KiB
TypeScript
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",
|
|
});
|
|
});
|
|
});
|