Files
au_whisper_web/tests/errors.test.ts
T
2026-07-29 07:59:16 +02:00

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");
});
});