|
| 1 | +import { describe, expect, it } from "vitest"; |
| 2 | +import { Prisma } from "../generated/prisma"; |
| 3 | +import { |
| 4 | + isInfrastructureError, |
| 5 | + isRetryableInfrastructureError, |
| 6 | + looksLikeConnectivityError, |
| 7 | +} from "./infraError"; |
| 8 | + |
| 9 | +const known = (code: string, message = "") => |
| 10 | + new Prisma.PrismaClientKnownRequestError(message, { code, clientVersion: "6.14.0" }); |
| 11 | + |
| 12 | +describe("isInfrastructureError", () => { |
| 13 | + it("treats connection-level Prisma codes as infrastructure errors", () => { |
| 14 | + for (const code of ["P1001", "P1002", "P1008", "P1017"]) { |
| 15 | + expect(isInfrastructureError(known(code, "boom"))).toBe(true); |
| 16 | + } |
| 17 | + }); |
| 18 | + |
| 19 | + it("does not treat query/validation errors as infrastructure errors", () => { |
| 20 | + expect(isInfrastructureError(known("P2025", "record not found"))).toBe(false); |
| 21 | + expect(isInfrastructureError(known("P2002", "unique constraint"))).toBe(false); |
| 22 | + }); |
| 23 | + |
| 24 | + it("treats P2010 as infrastructure only when the message looks like connectivity loss", () => { |
| 25 | + expect(isInfrastructureError(known("P2010", "Connection terminated unexpectedly"))).toBe(true); |
| 26 | + expect(isInfrastructureError(known("P2010", "syntax error at or near"))).toBe(false); |
| 27 | + }); |
| 28 | + |
| 29 | + it("treats init / panic / unknown request errors as infrastructure errors", () => { |
| 30 | + expect( |
| 31 | + isInfrastructureError(new Prisma.PrismaClientInitializationError("no db", "6.14.0")) |
| 32 | + ).toBe(true); |
| 33 | + }); |
| 34 | + |
| 35 | + it("recognises raw connectivity errno / messages", () => { |
| 36 | + expect(isInfrastructureError({ code: "ECONNRESET" })).toBe(true); |
| 37 | + expect(isInfrastructureError(new Error("server has closed the connection"))).toBe(true); |
| 38 | + expect(isInfrastructureError(new Error("column does not exist"))).toBe(false); |
| 39 | + }); |
| 40 | +}); |
| 41 | + |
| 42 | +describe("looksLikeConnectivityError", () => { |
| 43 | + it("matches known errno codes and message fragments", () => { |
| 44 | + expect(looksLikeConnectivityError({ code: "EHOSTUNREACH" })).toBe(true); |
| 45 | + expect(looksLikeConnectivityError(new Error("Can't reach database server"))).toBe(true); |
| 46 | + expect(looksLikeConnectivityError(new Error("relation does not exist"))).toBe(false); |
| 47 | + }); |
| 48 | +}); |
| 49 | + |
| 50 | +describe("isRetryableInfrastructureError", () => { |
| 51 | + it("retries connection-level codes and connectivity errnos/messages", () => { |
| 52 | + for (const code of ["P1001", "P1002", "P1008", "P1017"]) { |
| 53 | + expect(isRetryableInfrastructureError(known(code, "boom"))).toBe(true); |
| 54 | + } |
| 55 | + expect(isRetryableInfrastructureError({ code: "ECONNRESET" })).toBe(true); |
| 56 | + expect(isRetryableInfrastructureError(new Error("server has closed the connection"))).toBe( |
| 57 | + true |
| 58 | + ); |
| 59 | + }); |
| 60 | + |
| 61 | + it("does not retry query/validation errors", () => { |
| 62 | + expect(isRetryableInfrastructureError(known("P2025", "record not found"))).toBe(false); |
| 63 | + expect(isRetryableInfrastructureError(new Error("column does not exist"))).toBe(false); |
| 64 | + }); |
| 65 | + |
| 66 | + it("retries an init error only with a connectivity signal (not a permanent one)", () => { |
| 67 | + expect( |
| 68 | + isRetryableInfrastructureError( |
| 69 | + new Prisma.PrismaClientInitializationError("Can't reach database server", "6.14.0", "P1001") |
| 70 | + ) |
| 71 | + ).toBe(true); |
| 72 | + expect( |
| 73 | + isRetryableInfrastructureError( |
| 74 | + new Prisma.PrismaClientInitializationError( |
| 75 | + "Authentication failed against database server", |
| 76 | + "6.14.0", |
| 77 | + "P1000" |
| 78 | + ) |
| 79 | + ) |
| 80 | + ).toBe(false); |
| 81 | + }); |
| 82 | + |
| 83 | + it("never retries a Rust-engine panic", () => { |
| 84 | + expect( |
| 85 | + isRetryableInfrastructureError(new Prisma.PrismaClientRustPanicError("panic", "6.14.0")) |
| 86 | + ).toBe(false); |
| 87 | + }); |
| 88 | + |
| 89 | + it("never retries pool exhaustion (P2024), even though its message looks like connectivity", () => { |
| 90 | + const poolMsg = "Timed out fetching a new connection from the connection pool"; |
| 91 | + expect(isRetryableInfrastructureError(known("P2024", poolMsg))).toBe(false); |
| 92 | + expect(isRetryableInfrastructureError(new Error(poolMsg))).toBe(false); |
| 93 | + // The broad classifier still flags it (used for logging, not retry). |
| 94 | + expect(isInfrastructureError(new Error(poolMsg))).toBe(true); |
| 95 | + }); |
| 96 | + |
| 97 | + it("retries an unknown-request error only with a connectivity signal", () => { |
| 98 | + expect( |
| 99 | + isRetryableInfrastructureError( |
| 100 | + new Prisma.PrismaClientUnknownRequestError("connection terminated unexpectedly", { |
| 101 | + clientVersion: "6.14.0", |
| 102 | + }) |
| 103 | + ) |
| 104 | + ).toBe(true); |
| 105 | + expect( |
| 106 | + isRetryableInfrastructureError( |
| 107 | + new Prisma.PrismaClientUnknownRequestError("unexpected engine failure", { |
| 108 | + clientVersion: "6.14.0", |
| 109 | + }) |
| 110 | + ) |
| 111 | + ).toBe(false); |
| 112 | + }); |
| 113 | +}); |
0 commit comments