101 lines
3.6 KiB
TypeScript
101 lines
3.6 KiB
TypeScript
import { describe, test, expect } from "bun:test";
|
|
import { classifyError } from "../src/lib/errors";
|
|
|
|
// Pure pattern-matching (M6 error taxonomy) — no browser needed.
|
|
|
|
describe("classifyError", () => {
|
|
test("decode-stage failures are always unsupported-file, regardless of message", () => {
|
|
const result = classifyError(new Error("Unable to decode audio data"), { stage: "decode" });
|
|
expect(result.kind).toBe("unsupported-file");
|
|
expect(result.retryable).toBe(false);
|
|
expect(result.suggestion).toMatch(/different file|convert/i);
|
|
});
|
|
|
|
test("recognizes out-of-memory messages", () => {
|
|
for (const message of [
|
|
"RuntimeError: Aborted(OOM)",
|
|
"Cannot enlarge memory arrays to size 123456, (OOM)",
|
|
"Failed to allocate memory for tensor",
|
|
"could not allocate buffer",
|
|
]) {
|
|
const result = classifyError(new Error(message));
|
|
expect(result.kind).toBe("out-of-memory");
|
|
expect(result.retryable).toBe(false);
|
|
expect(result.suggestion).toMatch(/smaller model/i);
|
|
}
|
|
});
|
|
|
|
test("a worker crash with an empty message is treated as out-of-memory (D-027)", () => {
|
|
const result = classifyError(new Error(""), { stage: "worker-crash" });
|
|
expect(result.kind).toBe("out-of-memory");
|
|
expect(result.message.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
test("a worker crash with a real message is not assumed to be OOM", () => {
|
|
const result = classifyError(new Error("TypeError: x is not a function"), {
|
|
stage: "worker-crash",
|
|
});
|
|
expect(result.kind).toBe("generic");
|
|
});
|
|
|
|
test("recognizes WebGPU device-lost messages", () => {
|
|
for (const message of [
|
|
"GPUDeviceLostInfo: device was lost",
|
|
"WebGPU device was lost: reason=destroyed",
|
|
"the GPU device has been lost",
|
|
]) {
|
|
const result = classifyError(new Error(message));
|
|
expect(result.kind).toBe("device-lost");
|
|
expect(result.retryable).toBe(true);
|
|
expect(result.suggestion).toMatch(/backend|try again/i);
|
|
}
|
|
});
|
|
|
|
test("recognizes network failures", () => {
|
|
for (const message of [
|
|
"TypeError: Failed to fetch",
|
|
"NetworkError when attempting to fetch resource.",
|
|
"Load failed",
|
|
]) {
|
|
const result = classifyError(new Error(message), { stage: "load" });
|
|
expect(result.kind).toBe("network");
|
|
expect(result.retryable).toBe(true);
|
|
}
|
|
});
|
|
|
|
test("offline context forces a network classification with a clear message", () => {
|
|
const result = classifyError(new Error("something obscure"), {
|
|
stage: "load",
|
|
offline: true,
|
|
});
|
|
expect(result.kind).toBe("network");
|
|
expect(result.retryable).toBe(true);
|
|
expect(result.message).toMatch(/offline/i);
|
|
expect(result.suggestion).toMatch(/restart/i);
|
|
});
|
|
|
|
test("load-stage network suggestion mentions the download restarting", () => {
|
|
const result = classifyError(new Error("Failed to fetch"), { stage: "load" });
|
|
expect(result.suggestion).toMatch(/restart/i);
|
|
});
|
|
|
|
test("transcribe-stage network suggestion is stage-agnostic", () => {
|
|
const result = classifyError(new Error("Failed to fetch"), { stage: "transcribe" });
|
|
expect(result.suggestion).not.toMatch(/restart/i);
|
|
});
|
|
|
|
test("falls back to a generic, non-retryable classification", () => {
|
|
const result = classifyError(new Error("Something went wrong doing the thing"));
|
|
expect(result).toEqual({
|
|
kind: "generic",
|
|
message: "Something went wrong doing the thing",
|
|
retryable: false,
|
|
});
|
|
});
|
|
|
|
test("accepts non-Error raw values", () => {
|
|
expect(classifyError("plain string failure").message).toBe("plain string failure");
|
|
expect(classifyError(42).message).toBe("42");
|
|
});
|
|
});
|