Files

127 lines
4.6 KiB
TypeScript

import { ExitCode } from "bailian-cli-core";
import { expect, test } from "vite-plus/test";
import { handleError } from "../src/error-handler.ts";
import { ConfirmationRequiredError } from "../src/confirm.ts";
test("handleError: fetch failed JSON includes cause.code from errno", () => {
const previousOutput = process.env.DASHSCOPE_OUTPUT;
process.env.DASHSCOPE_OUTPUT = "json";
let stderr = "";
const originalWrite = process.stderr.write.bind(process.stderr);
const originalExit = process.exit.bind(process);
process.stderr.write = ((chunk: string | Uint8Array) => {
stderr += String(chunk);
return true;
}) as typeof process.stderr.write;
process.exit = ((code?: number) => {
throw new Error(`process.exit:${code ?? 0}`);
}) as typeof process.exit;
const root = Object.assign(new Error("getaddrinfo ENOTFOUND example.invalid"), {
code: "ENOTFOUND",
});
const fetchFailed = new TypeError("fetch failed", { cause: root });
try {
expect(() => handleError(fetchFailed, "bl")).toThrow(
new RegExp(`process\\.exit:${ExitCode.NETWORK}`),
);
expect(stderr).toMatch(/^\{\n {2}"error": \{\n/);
const payload = JSON.parse(stderr.trim()) as {
error: { code: number; message: string; cause?: { message: string; code?: string } };
};
expect(payload.error.code).toBe(ExitCode.NETWORK);
expect(payload.error.message).toMatch(/ENOTFOUND/);
expect(payload.error.cause).toEqual({
message: root.message,
code: "ENOTFOUND",
});
} finally {
process.stderr.write = originalWrite;
process.exit = originalExit;
if (previousOutput === undefined) {
delete process.env.DASHSCOPE_OUTPUT;
} else {
process.env.DASHSCOPE_OUTPUT = previousOutput;
}
}
});
test("handleError: fetch failed without nested cause still maps to NETWORK", () => {
const previousOutput = process.env.DASHSCOPE_OUTPUT;
process.env.DASHSCOPE_OUTPUT = "json";
let stderr = "";
const originalWrite = process.stderr.write.bind(process.stderr);
const originalExit = process.exit.bind(process);
process.stderr.write = ((chunk: string | Uint8Array) => {
stderr += String(chunk);
return true;
}) as typeof process.stderr.write;
process.exit = ((code?: number) => {
throw new Error(`process.exit:${code ?? 0}`);
}) as typeof process.exit;
const fetchFailed = new TypeError("fetch failed");
try {
expect(() => handleError(fetchFailed, "bl")).toThrow(
new RegExp(`process\\.exit:${ExitCode.NETWORK}`),
);
const payload = JSON.parse(stderr.trim()) as {
error: { code: number; message: string; cause?: { message: string; code?: string } };
};
expect(payload.error.code).toBe(ExitCode.NETWORK);
expect(payload.error.message).toMatch(/unknown cause/);
expect(payload.error.cause).toEqual({ message: "fetch failed" });
} finally {
process.stderr.write = originalWrite;
process.exit = originalExit;
if (previousOutput === undefined) {
delete process.env.DASHSCOPE_OUTPUT;
} else {
process.env.DASHSCOPE_OUTPUT = previousOutput;
}
}
});
test("handleError: confirmation text uses the standard message and hint layout", () => {
const previousOutput = process.env.DASHSCOPE_OUTPUT;
delete process.env.DASHSCOPE_OUTPUT;
let stderr = "";
const originalWrite = process.stderr.write.bind(process.stderr);
const originalExit = process.exit.bind(process);
process.stderr.write = ((chunk: string | Uint8Array) => {
stderr += String(chunk);
return true;
}) as typeof process.stderr.write;
process.exit = ((code?: number) => {
throw new Error(`process.exit:${code ?? 0}`);
}) as typeof process.exit;
const confirmation = new ConfirmationRequiredError({
message: "This permanently deletes the selected documents and all of their chunks.",
hint: "This command performs a high-risk operation. To continue, add --yes to the original command and re-run it.",
});
try {
expect(() => handleError(confirmation, "bl")).toThrow(
new RegExp(`process\\.exit:${ExitCode.CONFIRMATION_REQUIRED}`),
);
expect(stderr).toContain(
"This command performs a high-risk operation. To continue, add --yes to the original command and re-run it.",
);
expect(stderr).not.toContain("bl knowledge doc delete");
expect(stderr).not.toContain("Risk:");
expect(stderr).not.toContain("Action:");
expect(stderr).not.toContain("Note:");
} finally {
process.stderr.write = originalWrite;
process.exit = originalExit;
if (previousOutput === undefined) delete process.env.DASHSCOPE_OUTPUT;
else process.env.DASHSCOPE_OUTPUT = previousOutput;
}
});