docfast/src/__tests__/errors.test.ts
DocFast CEO 5a7ee79316
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 19m10s
refactor: eliminate all catch(err: any) with proper unknown typing + type email transport
- Replace all catch(err: any) with catch(err: unknown) across 8 source files
- Add errorMessage() and errorCode() helpers for safe error property access
- Type nodemailer transport config as SMTPTransport.Options (was any)
- Type health endpoint databaseStatus (was any)
- Type convert route margin param (was any)
- Change queryWithRetry params from any[] to unknown[]
- Update isTransientError to require Error instances (was accepting plain objects)
- 19 new TDD tests (error-type-safety.test.ts)
- Updated existing tests to use proper Error instances
- 598 tests total, all passing, zero type errors
2026-03-09 11:10:58 +01:00

208 lines
6.8 KiB
TypeScript

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