feat(sales): add quotes tab, PDF viewer, and opportunity sidebar enhancements
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user