import { describe, expect, test } from "bun:test"; import { RPC_ERROR_CODES, ServiceError, failure, isRetryableStatus, success, } from "../src/errors.ts"; describe("rpc errors", () => { test("success and failure build the result shape", () => { expect(success(42)).toEqual({ ok: true, value: 42 }); const f = failure(RPC_ERROR_CODES.denied, "Forbidden"); expect(f.ok).toBe(false); if (!f.ok) { expect(f.code).toBe("RPC_DENIED"); expect(f.retryable).toBe(false); } }); test("only transport failures are retryable", () => { // 5xx and 429 are the callee saying "try again"; everything else is final. expect(isRetryableStatus(500)).toBe(true); expect(isRetryableStatus(503)).toBe(true); expect(isRetryableStatus(429)).toBe(true); expect(isRetryableStatus(400)).toBe(false); expect(isRetryableStatus(403)).toBe(false); expect(isRetryableStatus(404)).toBe(false); expect(isRetryableStatus(200)).toBe(false); }); test("a denial is never retryable", () => { const f = failure(RPC_ERROR_CODES.denied, "Forbidden"); if (!f.ok) expect(f.retryable).toBe(false); }); test("ServiceError carries a code and does not leak a cause into its message", () => { const error = new ServiceError(RPC_ERROR_CODES.handler, "Something failed"); expect(error.name).toBe("ServiceError"); expect(error.code).toBe("RPC_HANDLER"); expect(error.message).toBe("Something failed"); expect(error.retryable).toBe(false); }); test("toResult produces an opaque failure", () => { const error = new ServiceError(RPC_ERROR_CODES.handler, "db password is hunter2"); const result = error.toResult({ exposeMessage: false }); if (!result.ok) { expect(result.message).toBe("Internal error"); expect(result.message).not.toContain("hunter2"); } }); });