import { describe, it, expect } from "vitest"; import { isTransientError } from "../utils/errors.js"; /** Create an Error with a `.code` property (like Node/pg errors) */ function makeError(opts: { code?: string; message?: string }): Error { const err = new Error(opts.message || ""); if (opts.code) (err as Error & { code: string }).code = opts.code; return err; } describe("isTransientError", () => { describe("null/undefined/empty input", () => { it("returns false for null", () => { expect(isTransientError(null)).toBe(false); }); it("returns false for undefined", () => { expect(isTransientError(undefined)).toBe(false); }); it("returns false for empty object (not an Error)", () => { expect(isTransientError({})).toBe(false); }); it("returns false for plain string", () => { expect(isTransientError("ECONNRESET")).toBe(false); }); }); describe("error codes from TRANSIENT_ERRORS set", () => { it("returns true for ECONNRESET", () => { expect(isTransientError(makeError({ code: "ECONNRESET" }))).toBe(true); }); it("returns true for ECONNREFUSED", () => { expect(isTransientError(makeError({ code: "ECONNREFUSED" }))).toBe(true); }); it("returns true for EPIPE", () => { expect(isTransientError(makeError({ code: "EPIPE" }))).toBe(true); }); it("returns true for ETIMEDOUT", () => { expect(isTransientError(makeError({ code: "ETIMEDOUT" }))).toBe(true); }); it("returns true for CONNECTION_LOST", () => { expect(isTransientError(makeError({ code: "CONNECTION_LOST" }))).toBe(true); }); it("returns true for 57P01 (admin_shutdown)", () => { expect(isTransientError(makeError({ code: "57P01" }))).toBe(true); }); it("returns true for 57P02 (crash_shutdown)", () => { expect(isTransientError(makeError({ code: "57P02" }))).toBe(true); }); it("returns true for 57P03 (cannot_connect_now)", () => { expect(isTransientError(makeError({ code: "57P03" }))).toBe(true); }); it("returns true for 08006 (connection_failure)", () => { expect(isTransientError(makeError({ code: "08006" }))).toBe(true); }); it("returns true for 08003 (connection_does_not_exist)", () => { expect(isTransientError(makeError({ code: "08003" }))).toBe(true); }); it("returns true for 08001 (sqlclient_unable_to_establish_sqlconnection)", () => { expect(isTransientError(makeError({ code: "08001" }))).toBe(true); }); }); describe("message substring matching", () => { it("returns true for 'no available server'", () => { expect(isTransientError(new Error("no available server"))).toBe(true); }); it("returns true for 'connection terminated'", () => { expect(isTransientError(new Error("connection terminated unexpectedly"))).toBe(true); }); it("returns true for 'connection refused'", () => { expect(isTransientError(new Error("connection refused by server"))).toBe(true); }); it("returns true for 'server closed the connection'", () => { expect(isTransientError(new Error("server closed the connection unexpectedly"))).toBe(true); }); it("returns true for 'timeout expired'", () => { expect(isTransientError(new Error("timeout expired waiting for connection"))).toBe(true); }); }); describe("case-insensitive message matching", () => { it("returns true for 'No Available Server' (mixed case)", () => { expect(isTransientError(new Error("No Available Server"))).toBe(true); }); it("returns true for 'CONNECTION TERMINATED' (uppercase)", () => { expect(isTransientError(new Error("CONNECTION TERMINATED"))).toBe(true); }); it("returns true for 'Connection Refused' (title case)", () => { expect(isTransientError(new Error("Connection Refused"))).toBe(true); }); it("returns true for 'SERVER CLOSED THE CONNECTION' (uppercase)", () => { expect(isTransientError(new Error("SERVER CLOSED THE CONNECTION"))).toBe(true); }); it("returns true for 'Timeout Expired' (title case)", () => { expect(isTransientError(new Error("Timeout Expired"))).toBe(true); }); }); describe("non-transient errors", () => { it("returns false for syntax error", () => { expect(isTransientError(makeError({ code: "42601", message: "syntax error at or near SELECT" }))).toBe(false); }); it("returns false for unique constraint violation", () => { expect(isTransientError(makeError({ code: "23505", message: "duplicate key value violates unique constraint" }))).toBe(false); }); it("returns false for foreign key violation", () => { expect(isTransientError(makeError({ code: "23503", message: "foreign key constraint violation" }))).toBe(false); }); it("returns false for not null violation", () => { expect(isTransientError(makeError({ code: "23502", message: "null value in column violates not-null constraint" }))).toBe(false); }); it("returns false for permission denied", () => { expect(isTransientError(makeError({ code: "42501", message: "permission denied for table users" }))).toBe(false); }); }); describe("unrelated codes and messages", () => { it("returns false for unrelated error code", () => { expect(isTransientError(makeError({ code: "UNKNOWN_ERROR" }))).toBe(false); }); it("returns false for unrelated error message", () => { expect(isTransientError(new Error("Something went wrong"))).toBe(false); }); it("returns false for generic database error", () => { expect(isTransientError(makeError({ code: "P0001", message: "Database operation failed" }))).toBe(false); }); it("returns false for application error", () => { expect(isTransientError(new Error("Invalid user input"))).toBe(false); }); }); describe("edge cases", () => { it("returns true when both code and message match", () => { expect(isTransientError(makeError({ code: "ECONNRESET", message: "connection terminated" }))).toBe(true); }); it("returns true when only code matches", () => { expect(isTransientError(makeError({ code: "ETIMEDOUT", message: "some other message" }))).toBe(true); }); it("returns true when only message matches", () => { expect(isTransientError(makeError({ code: "SOME_CODE", message: "no available server to connect" }))).toBe(true); }); it("returns false for error with only unrelated code", () => { expect(isTransientError(makeError({ code: "NOTFOUND" }))).toBe(false); }); it("returns false for Error with empty message", () => { expect(isTransientError(new Error(""))).toBe(false); }); }); });