import { beforeEach, describe, expect, it, vi } from "vitest"; const { mockError, mockRedirect } = vi.hoisted(() => ({ mockError: vi.fn(), mockRedirect: vi.fn(), })); vi.mock("@sveltejs/kit", () => ({ error: mockError, redirect: mockRedirect, })); import { ApiError, handleApiError, isInvalidSignatureError, isNetworkError, isUnauthorizedError, isForbiddenError, isNotFoundError, } from "./errorHandler"; describe("errorHandler", () => { beforeEach(() => { vi.clearAllMocks(); vi.spyOn(console, "error").mockImplementation(() => {}); vi.spyOn(console, "warn").mockImplementation(() => {}); }); // ── ApiError ────────────────────────────────────────────────────────── it("ApiError stores statusCode, message, and details", () => { const err = new ApiError(422, "Validation failed", { field: "name" }); expect(err).toBeInstanceOf(Error); expect(err.name).toBe("ApiError"); expect(err.statusCode).toBe(422); expect(err.message).toBe("Validation failed"); expect(err.details).toEqual({ field: "name" }); }); // ── isInvalidSignatureError ─────────────────────────────────────────── it("detects 'invalid signature' in response data message", () => { const err = { response: { data: { message: "Token has invalid signature" } }, }; expect(isInvalidSignatureError(err)).toBe(true); }); it("detects 'jwt malformed' in response data error field", () => { const err = { response: { data: { error: "jwt malformed" } }, }; expect(isInvalidSignatureError(err)).toBe(true); }); it("detects 'invalid token' in Error message", () => { const err = new Error("Invalid token received"); expect(isInvalidSignatureError(err)).toBe(true); }); it("returns false for unrelated errors", () => { expect(isInvalidSignatureError(new Error("network down"))).toBe(false); expect(isInvalidSignatureError(null)).toBe(false); expect(isInvalidSignatureError("string error")).toBe(false); }); // ── handleApiError ──────────────────────────────────────────────────── it("redirects to /logout on invalid signature error", () => { mockRedirect.mockImplementation(() => { throw new Error("REDIRECT"); }); const err = { response: { data: { message: "invalid signature" } }, }; expect(() => handleApiError(err)).toThrow("REDIRECT"); expect(mockRedirect).toHaveBeenCalledWith(303, "/logout"); }); it("throws SvelteKit error for ApiError", () => { mockError.mockImplementation(() => { throw new Error("HTTP_ERROR"); }); const err = new ApiError(404, "Not found", "extra"); expect(() => handleApiError(err)).toThrow("HTTP_ERROR"); expect(mockError).toHaveBeenCalledWith(404, { message: "Not found", details: "extra", }); }); it("throws 500 error for generic Error", () => { mockError.mockImplementation(() => { throw new Error("HTTP_ERROR"); }); const err = new Error("Something broke"); expect(() => handleApiError(err)).toThrow("HTTP_ERROR"); expect(mockError).toHaveBeenCalledWith(500, { message: "Something broke", details: expect.any(String), }); }); it("throws 500 error for non-Error values", () => { mockError.mockImplementation(() => { throw new Error("HTTP_ERROR"); }); expect(() => handleApiError("string error")).toThrow("HTTP_ERROR"); expect(mockError).toHaveBeenCalledWith(500, { message: "An unexpected error occurred", details: "string error", }); }); // ── isNetworkError ──────────────────────────────────────────────────── it("identifies Network errors", () => { expect(isNetworkError(new Error("Network request failed"))).toBe(true); expect(isNetworkError(new Error("fetch failed"))).toBe(true); expect(isNetworkError(new Error("ECONNREFUSED"))).toBe(true); }); it("returns false for non-network errors", () => { expect(isNetworkError(new Error("validation error"))).toBe(false); expect(isNetworkError("not an error")).toBe(false); }); // ── status code helpers ─────────────────────────────────────────────── it("isUnauthorizedError returns true for 401 ApiError", () => { expect(isUnauthorizedError(new ApiError(401, "Unauthorized"))).toBe(true); expect(isUnauthorizedError(new ApiError(403, "Forbidden"))).toBe(false); expect(isUnauthorizedError(new Error("nope"))).toBe(false); }); it("isForbiddenError returns true for 403 ApiError", () => { expect(isForbiddenError(new ApiError(403, "Forbidden"))).toBe(true); expect(isForbiddenError(new ApiError(401, "Unauthorized"))).toBe(false); }); it("isNotFoundError returns true for 404 ApiError", () => { expect(isNotFoundError(new ApiError(404, "Not Found"))).toBe(true); expect(isNotFoundError(new ApiError(500, "Server Error"))).toBe(false); }); });