import { SuaveplanError } from "@suaveplan/error"; import { describe, expect, it } from "@suaveplan/testing/runner"; import { FALLBACK_URI_CODES, FallbackUriError, } from "../error-codes/error-codes.js"; import { FallbackPolicySchema } from "../types/policy.schemas.js"; import type { FallbackPolicy } from "../types/policy.types.js"; import { classify } from "./classify.js"; const POLICY: FallbackPolicy = FallbackPolicySchema.parse({}); const ANY_POLICY: FallbackPolicy = FallbackPolicySchema.parse({ errorClass: "any", }); class TestErr extends SuaveplanError { public constructor(code: string) { super(code, "test"); } } describe("classify", () => { it("classifies NOT_FOUND as fall-through-eligible", { timeout: 5000, }, () => { expect(classify(new TestErr("STORAGE_URI.NOT_FOUND"), POLICY)).toBe( "transient" ); }); it("classifies UNKNOWN_SCHEME as semantic under default", { timeout: 5000, }, () => { expect( classify(new TestErr("STORAGE_URI.UNKNOWN_SCHEME"), POLICY) ).toBe("semantic"); }); it("classifies UNKNOWN_SCHEME as transient under errorClass=any", { timeout: 5000, }, () => { expect( classify(new TestErr("STORAGE_URI.UNKNOWN_SCHEME"), ANY_POLICY) ).toBe("transient"); }); it("treats MAX_DEPTH_EXCEEDED as semantic regardless of policy", { timeout: 5000, }, () => { const err = new FallbackUriError( FALLBACK_URI_CODES.MAX_DEPTH_EXCEEDED, "x" ); expect(classify(err, POLICY)).toBe("semantic"); expect(classify(err, ANY_POLICY)).toBe("semantic"); }); it("treats CIRCULAR_REFERENCE as semantic regardless of policy", { timeout: 5000, }, () => { const err = new FallbackUriError( FALLBACK_URI_CODES.CIRCULAR_REFERENCE, "x" ); expect(classify(err, ANY_POLICY)).toBe("semantic"); }); it("classifies ABORTED codes as abort", { timeout: 5000 }, () => { expect( classify( new FallbackUriError(FALLBACK_URI_CODES.ABORTED, "x"), POLICY ) ).toBe("abort"); expect(classify(new TestErr("STORAGE_URI.ABORTED"), POLICY)).toBe( "abort" ); }); it("classifies ADAPTER_ERROR as transient", { timeout: 5000 }, () => { expect(classify(new TestErr("STORAGE_URI.ADAPTER_ERROR"), POLICY)).toBe( "transient" ); }); it("classifies generic semantic codes as semantic", { timeout: 5000, }, () => { expect(classify(new TestErr("STORAGE_URI.INVALID_URI"), POLICY)).toBe( "semantic" ); expect( classify(new TestErr("STORAGE_URI.WRITE_UNSUPPORTED"), POLICY) ).toBe("semantic"); expect( classify(new TestErr("STORAGE_URI.LIST_UNSUPPORTED"), POLICY) ).toBe("semantic"); expect( classify(new TestErr("STORAGE_URI.SCHEME_CONFLICT"), POLICY) ).toBe("semantic"); }); it("classifies unknown namespaced codes per policy", { timeout: 5000, }, () => { expect(classify(new TestErr("FOO.BAR"), POLICY)).toBe("semantic"); expect(classify(new TestErr("FOO.BAR"), ANY_POLICY)).toBe("transient"); }); it("classifies AbortError name as abort", { timeout: 5000 }, () => { const e = new Error("aborted"); e.name = "AbortError"; expect(classify(e, POLICY)).toBe("abort"); }); it("classifies unknown thrown values as transient", { timeout: 5000, }, () => { expect(classify("boom", POLICY)).toBe("transient"); expect(classify(new Error("plain"), POLICY)).toBe("transient"); }); });