Files
optima/src/lib/optima-api/errorHandler.spec.ts
T

155 lines
5.3 KiB
TypeScript

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