UNPKG

@ledgerhq/live-common

Version:
394 lines (323 loc) • 12.9 kB
/* eslint-env jest */ import { TransportStatusError } from "@ledgerhq/errors"; import { CompleteExchangeError, convertTransportError, getErrorDetails, getErrorName, getErrorMessage, getSwapStepFromError, } from "./error"; describe("exchange/error", () => { describe("getErrorName", () => { it("should return name from error object", () => { const error = { name: "ValidationError" }; expect(getErrorName(error)).toBe("ValidationError"); }); it("should return name from cause when error has no direct name", () => { const error = { cause: { name: "NetworkError" } }; expect(getErrorName(error)).toBe("NetworkError"); }); it("should prioritize direct name over cause.name", () => { const error = { name: "OuterError", cause: { name: "InnerError" }, }; expect(getErrorName(error)).toBe("OuterError"); }); it("should return undefined for string error", () => { expect(getErrorName("error string")).toBeUndefined(); }); it("should return undefined for null", () => { expect(getErrorName(null)).toBeUndefined(); }); it("should return undefined for undefined", () => { expect(getErrorName(undefined)).toBeUndefined(); }); it("should return undefined for object without name or cause", () => { const error = { code: "ERR001", message: "Something failed" }; expect(getErrorName(error)).toBeUndefined(); }); it("should return undefined when cause exists but has no name", () => { const error = { cause: { message: "nested error" } }; expect(getErrorName(error)).toBeUndefined(); }); it("should prefer cause.name when top-level name is generic 'Error'", () => { const error = new Error("something"); Object.assign(error, { cause: { name: "SpecificError" } }); expect(getErrorName(error)).toBe("SpecificError"); }); }); describe("getErrorMessage", () => { it("should return string error as-is", () => { expect(getErrorMessage("error string")).toBe("error string"); }); it("should return message from error object", () => { const error = { message: "Validation failed" }; expect(getErrorMessage(error)).toBe("Validation failed"); }); it("should return message from cause when error has no direct message", () => { const error = { cause: { message: "Network timeout" } }; expect(getErrorMessage(error)).toBe("Network timeout"); }); it("should prioritize direct message over cause.message", () => { const error = { message: "Outer error", cause: { message: "Inner error" }, }; expect(getErrorMessage(error)).toBe("Outer error"); }); it("should return 'Unknown error' for null and empty string", () => { expect(getErrorMessage(null)).toBe("Unknown error"); expect(getErrorMessage("")).toBe("Unknown error"); }); it("should return 'Unknown error' for undefined", () => { expect(getErrorMessage(undefined)).toBe("Unknown error"); }); it("should return 'Unknown error' for number", () => { expect(getErrorMessage(42)).toBe("Unknown error"); }); it("should return 'Unknown error' for boolean", () => { expect(getErrorMessage(false)).toBe("Unknown error"); }); it("should fall back to name for object without message or cause", () => { const error = { code: "ERR001", name: "CustomError" }; expect(getErrorMessage(error)).toBe("CustomError"); }); it("should return 'Unknown error' for object when cause has no message or name", () => { const error = { cause: { code: "E500" } }; expect(getErrorMessage(error)).toBe("Unknown error"); }); it("should return 'Unknown error' when message is explicitly empty", () => { const error = { message: "" }; expect(getErrorMessage(error)).toBe("Unknown error"); }); it("should handle Error instances", () => { const error = new Error("Standard error"); expect(getErrorMessage(error)).toBe("Standard error"); }); it("should handle nested Error instances", () => { const innerError = new Error("Inner error"); const error = { cause: innerError }; expect(getErrorMessage(error)).toBe("Inner error"); }); }); describe("getErrorDetails", () => { it("should return 'Unknown error' for null", () => { expect(getErrorDetails(null)).toEqual({ message: "Unknown error" }); }); it("should return 'Unknown error' for undefined", () => { expect(getErrorDetails(undefined)).toEqual({ message: "Unknown error" }); }); it("should return string error as message", () => { expect(getErrorDetails("something broke")).toEqual({ message: "something broke" }); }); it("should return 'Unknown error' for empty string", () => { expect(getErrorDetails("")).toEqual({ message: "Unknown error" }); }); it("should extract name/message and cause details when present", () => { const error = { name: "OuterError", cause: { name: "InnerError", message: "Inner message", swapCode: "swap001", }, }; expect(getErrorDetails(error)).toEqual({ name: "OuterError", message: "Inner message", cause: { name: "InnerError", message: "Inner message", swapCode: "swap001", }, }); }); it("should fall back to cause name for Error instances with generic top-level name", () => { const error = new Error("Drawer closed"); Object.assign(error, { cause: { name: "DrawerClosedError" } }); expect(getErrorDetails(error)).toEqual({ name: "DrawerClosedError", message: "Drawer closed", cause: { name: "DrawerClosedError", }, }); }); it("should keep specific top-level name over cause name for Error instances", () => { const error = new Error("Drawer closed"); error.name = "CustomTopLevelError"; Object.assign(error, { cause: { name: "DrawerClosedError" } }); expect(getErrorDetails(error)).toEqual({ name: "CustomTopLevelError", message: "Drawer closed", cause: { name: "DrawerClosedError", }, }); }); it("should extract cause details when Error instance has object cause", () => { const error = new Error("Top-level message"); Object.assign(error, { cause: { name: "NestedObjectCause", message: "Nested cause message", swapCode: "swap005", }, }); expect(getErrorDetails(error)).toEqual({ name: "NestedObjectCause", message: "Top-level message", cause: { name: "NestedObjectCause", message: "Nested cause message", swapCode: "swap005", }, }); }); it("should extract cause details when Error instance has Error as cause", () => { const error = new Error("Top-level message"); const nestedError = new Error("Nested error message"); nestedError.name = "NestedErrorCause"; Object.assign(error, { cause: nestedError }); expect(getErrorDetails(error)).toEqual({ name: "NestedErrorCause", message: "Top-level message", cause: { name: "NestedErrorCause", message: "Nested error message", }, }); }); it("should fall back to effectiveName when no message or causeMessage", () => { const error = { name: "SomeError" }; expect(getErrorDetails(error)).toEqual({ name: "SomeError", message: "SomeError", }); }); it("should return 'Unknown error' for object with no useful fields", () => { const error = { code: 123 }; expect(getErrorDetails(error)).toEqual({ message: "Unknown error" }); }); }); describe("convertTransportError", () => { it("should convert TransportStatusError to CompleteExchangeError", () => { const transportError = new TransportStatusError(0x6a80); const result = convertTransportError("INIT", transportError); expect(result).toBeInstanceOf(CompleteExchangeError); expect((result as CompleteExchangeError).step).toBe("INIT"); }); it("should use INVALID_ADDRESS error code for CHECK_REFUND_ADDRESS step", () => { const transportError = new TransportStatusError(0x6a80); const result = convertTransportError("CHECK_REFUND_ADDRESS", transportError); expect(result).toBeInstanceOf(CompleteExchangeError); expect((result as CompleteExchangeError).step).toBe("CHECK_REFUND_ADDRESS"); // The error code should be overridden to INVALID_ADDRESS for this step }); it("should preserve error for non-TransportStatusError", () => { const error = new Error("Regular error"); const result = convertTransportError("INIT", error); expect(result).toBe(error); expect(result).not.toBeInstanceOf(CompleteExchangeError); }); it("should preserve unknown error types", () => { const error = "string error"; const result = convertTransportError("PROCESS_TRANSACTION", error); expect(result).toBe(error); }); it("should handle null error", () => { const result = convertTransportError("INIT", null); expect(result).toBeNull(); }); it("should handle undefined error", () => { const result = convertTransportError("INIT", undefined); expect(result).toBeUndefined(); }); it("should work with all CompleteExchangeStep types", () => { const steps = [ "INIT", "SET_PARTNER_KEY", "CHECK_PARTNER", "PROCESS_TRANSACTION", "CHECK_TRANSACTION_SIGNATURE", "CHECK_PAYOUT_ADDRESS", "CHECK_REFUND_ADDRESS", "SIGN_COIN_TRANSACTION", ] as const; steps.forEach(step => { const transportError = new TransportStatusError(0x6a80); const result = convertTransportError(step, transportError); expect(result).toBeInstanceOf(CompleteExchangeError); expect((result as CompleteExchangeError).step).toBe(step); }); }); }); describe("getSwapStepFromError", () => { it("should return step from CompleteExchangeError", () => { const error = new CompleteExchangeError("CHECK_PARTNER", "Partner check failed"); expect(getSwapStepFromError(error)).toBe("CHECK_PARTNER"); }); it("should return SIGN_COIN_TRANSACTION for DisabledTransactionBroadcastError", () => { const error = new Error("Broadcast disabled"); error.name = "DisabledTransactionBroadcastError"; expect(getSwapStepFromError(error)).toBe("SIGN_COIN_TRANSACTION"); }); it("should return UNKNOWN_STEP for regular Error", () => { const error = new Error("Generic error"); expect(getSwapStepFromError(error)).toBe("UNKNOWN_STEP"); }); it("should return UNKNOWN_STEP for Error with different name", () => { const error = new Error("Custom error"); error.name = "CustomError"; expect(getSwapStepFromError(error)).toBe("UNKNOWN_STEP"); }); it("should handle all CompleteExchangeStep types", () => { const steps = [ "INIT", "SET_PARTNER_KEY", "CHECK_PARTNER", "PROCESS_TRANSACTION", "CHECK_TRANSACTION_SIGNATURE", "CHECK_PAYOUT_ADDRESS", "CHECK_REFUND_ADDRESS", "SIGN_COIN_TRANSACTION", ] as const; steps.forEach(step => { const error = new CompleteExchangeError(step); expect(getSwapStepFromError(error)).toBe(step); }); }); }); describe("CompleteExchangeError", () => { it("should create error with step, title, and message", () => { const error = new CompleteExchangeError( "CHECK_PARTNER", "Partner Verification Failed", "The partner key is invalid", ); expect(error).toBeInstanceOf(Error); expect(error.name).toBe("CompleteExchangeError"); expect(error.step).toBe("CHECK_PARTNER"); expect(error.title).toBe("Partner Verification Failed"); expect(error.message).toBe("The partner key is invalid"); }); it("should create error with only step", () => { const error = new CompleteExchangeError("INIT"); expect(error.name).toBe("CompleteExchangeError"); expect(error.step).toBe("INIT"); expect(error.title).toBeUndefined(); expect(error.message).toBe(""); }); it("should create error with step and title but no message", () => { const error = new CompleteExchangeError( "PROCESS_TRANSACTION", "Transaction Processing Error", ); expect(error.step).toBe("PROCESS_TRANSACTION"); expect(error.title).toBe("Transaction Processing Error"); expect(error.message).toBe(""); }); }); });