/** * TASK-0102 DoD: "Retry/backoff + fatal classification proven." The * retryable-vs-fatal matrix is a pure function (errors.classifyError) and is * pinned here: network/S3-5xx → retryable (BullMQ backoff); corrupt object / * missing row / disk guard / bad request → fatal (FAILED, no retry). */ import { describe, expect, it } from "vitest"; import { S3Error } from "../../../lib/s3.js"; import { classifyError, FatalError, RetryableError } from "../errors.js"; describe("files pipeline error classification (TASK-0102 §1)", () => { it("S3 5xx → retryable (transient server fault)", () => { for (const code of [500, 502, 503, 504]) { const e = classifyError(new S3Error("InternalError", "boom", code)); expect(e, `S3 ${code}`).toBeInstanceOf(RetryableError); } }); it("S3 404 → fatal 'upload_not_found' (the object is gone, retry can't fix it)", () => { const e = classifyError(new S3Error("NoSuchKey", "no such key", 404)); expect(e).toBeInstanceOf(FatalError); expect((e as FatalError).code).toBe("upload_not_found"); }); it("S3 400/401/403 → fatal 's3_client_error' (bad request/auth, not transient)", () => { for (const code of [400, 401, 403]) { const e = classifyError(new S3Error("AccessDenied", "denied", code)); expect(e, `S3 ${code}`).toBeInstanceOf(FatalError); } }); it("network / fetch failures → retryable", () => { expect(classifyError(new TypeError("fetch failed"))).toBeInstanceOf(RetryableError); expect(classifyError(new Error("connect ECONNREFUSED 127.0.0.1:9000"))).toBeInstanceOf( RetryableError ); expect(classifyError(new Error("socket hang up ECONNRESET"))).toBeInstanceOf(RetryableError); expect(classifyError(new Error("UND_ERR_SOCKET socket error"))).toBeInstanceOf(RetryableError); }); it("redis blips → retryable", () => { expect(classifyError(new Error("Connection is closed."))).toBeInstanceOf(RetryableError); expect(classifyError(new Error("Ready check failed"))).toBeInstanceOf(RetryableError); }); it("pre-classified errors pass through unchanged", () => { const r = new RetryableError("x"); expect(classifyError(r)).toBe(r); const f = new FatalError("x", "corrupt_object"); expect(classifyError(f)).toBe(f); }); it("anything else is fatal (a deterministic unknown failure is not worth a retry)", () => { const e = classifyError(new Error("mupdf: cannot decode")); expect(e).toBeInstanceOf(FatalError); const c = classifyError(new Error("corrupt")); expect(c).toBeInstanceOf(FatalError); expect((c as FatalError).code).toBe("unclassified"); }); });