Skip to main content
Glama
error-enhanced.test.js11 kB
/** * Enhanced tests for error handling utilities */ import { vi } from "vitest"; // ESM-friendly module mocking: must occur before dynamic imports vi.mock("../../dist/utils/logger.js", () => { const mockLogger = { warn: vi.fn(), error: vi.fn(), debug: vi.fn(), info: vi.fn(), child: vi.fn(() => mockLogger), }; return { Logger: vi.fn(() => mockLogger), LoggerFactory: { server: vi.fn(() => mockLogger), api: vi.fn(() => mockLogger), cache: vi.fn(() => mockLogger), tool: vi.fn(() => mockLogger), }, }; }); let getErrorMessage; let isError; let logAndReturn; let handleToolError; let validateRequired; let validateSite; let __errorUtilsLogger; beforeAll(async () => { await import("../../dist/utils/logger.js"); ({ getErrorMessage, isError, logAndReturn, handleToolError, validateRequired, validateSite, __errorUtilsLogger } = await import("../../dist/utils/error.js")); }); describe("Enhanced Error Utilities", () => { beforeEach(() => { vi.clearAllMocks(); }); describe("getErrorMessage", () => { it("should extract message from Error object", () => { const error = new Error("Test error message"); expect(getErrorMessage(error)).toBe("Test error message"); }); it("should return string as-is", () => { expect(getErrorMessage("String error")).toBe("String error"); }); it("should extract message from object with message property", () => { const errorLike = { message: "Object error", code: 500 }; expect(getErrorMessage(errorLike)).toBe("Object error"); }); it("should handle null and undefined", () => { expect(getErrorMessage(null)).toBe("Unknown error occurred"); expect(getErrorMessage(undefined)).toBe("Unknown error occurred"); }); it("should handle non-standard objects", () => { expect(getErrorMessage({ error: "No message prop" })).toBe("Unknown error occurred"); expect(getErrorMessage(123)).toBe("Unknown error occurred"); expect(getErrorMessage(true)).toBe("Unknown error occurred"); }); it("should convert non-string message properties", () => { expect(getErrorMessage({ message: 404 })).toBe("404"); expect(getErrorMessage({ message: null })).toBe("null"); expect(getErrorMessage({ message: undefined })).toBe("undefined"); }); }); describe("isError", () => { it("should identify Error instances", () => { expect(isError(new Error())).toBe(true); expect(isError(new TypeError())).toBe(true); expect(isError(new RangeError())).toBe(true); }); it("should reject non-Error objects", () => { expect(isError("error")).toBe(false); expect(isError({ message: "error" })).toBe(false); expect(isError(null)).toBe(false); expect(isError(undefined)).toBe(false); expect(isError(123)).toBe(false); }); }); describe("logAndReturn", () => { it("should log warning and return default value", () => { const result = logAndReturn(new Error("Test error"), "default"); expect(result).toBe("default"); expect(__errorUtilsLogger.warn).toHaveBeenCalledWith( "Error occurred - returning default value", expect.objectContaining({ error: "Test error" }), ); }); it("should handle non-Error objects", () => { const result = logAndReturn("String error", 42); expect(result).toBe(42); expect(__errorUtilsLogger.warn).toHaveBeenCalledWith( "Error occurred - returning default value", expect.objectContaining({ error: "String error" }), ); }); it("should work with complex default values", () => { const defaultObj = { status: "ok", data: [] }; const result = logAndReturn(new Error(), defaultObj); expect(result).toBe(defaultObj); }); }); describe("handleToolError", () => { it("should throw formatted error for connection issues", () => { expect(() => { handleToolError(new Error("ECONNREFUSED"), "fetch posts"); }).toThrow("Connection failed during fetch posts. Please check your WordPress site URL and network connection."); expect(() => { handleToolError(new Error("ENOTFOUND"), "update post"); }).toThrow("Connection failed during update post. Please check your WordPress site URL and network connection."); }); it("should throw formatted error for authentication issues", () => { expect(() => { handleToolError(new Error("401 Unauthorized"), "create post"); }).toThrow("Authentication failed during create post. Please check your WordPress credentials."); expect(() => { handleToolError(new Error("Request failed with status 401"), "delete post"); }).toThrow("Authentication failed during delete post. Please check your WordPress credentials."); }); it("should throw formatted error for permission issues", () => { expect(() => { handleToolError(new Error("403 Forbidden"), "publish post"); }).toThrow("Permission denied during publish post. Please check your user permissions."); expect(() => { handleToolError(new Error("Status: 403"), "moderate comment"); }).toThrow("Permission denied during moderate comment. Please check your user permissions."); }); it("should throw formatted error for rate limiting", () => { expect(() => { handleToolError(new Error("429 Too Many Requests"), "batch update"); }).toThrow("Rate limit exceeded during batch update. Please try again later."); expect(() => { handleToolError(new Error("Too Many Requests"), "bulk delete"); }).toThrow("Rate limit exceeded during bulk delete. Please try again later."); }); it("should throw generic error for unknown issues", () => { expect(() => { handleToolError(new Error("Something went wrong"), "process data"); }).toThrow("Failed to process data: Something went wrong"); }); it("should log error details", () => { try { handleToolError(new Error("Test error"), "test operation", { id: 123 }); } catch (_e) { // expected throw } expect(__errorUtilsLogger.error).toHaveBeenCalledWith( "Error in test operation", expect.objectContaining({ error: "Test error", context: { id: 123 } }), ); }); it("should log stack trace for Error objects", () => { const error = new Error("Test"); error.stack = "Error: Test\n at test.js:1:1"; try { handleToolError(error, "test"); } catch (_e) { // expected throw } expect(__errorUtilsLogger.debug).toHaveBeenCalledWith( "Error stack trace", expect.objectContaining({ stack: error.stack }), ); }); }); describe("validateRequired", () => { it("should pass when all required fields are present", () => { expect(() => { validateRequired({ name: "John", email: "john@example.com", age: 30 }, ["name", "email"]); }).not.toThrow(); }); it("should throw when required fields are missing", () => { expect(() => { validateRequired({ name: "John" }, ["name", "email", "phone"]); }).toThrow("Missing required parameters: email, phone"); }); it("should throw when required fields are null or undefined", () => { expect(() => { validateRequired({ name: "John", email: null, phone: undefined }, ["name", "email", "phone"]); }).toThrow("Missing required parameters: email, phone"); }); it("should pass when required fields are empty strings", () => { expect(() => { validateRequired({ name: "", email: "" }, ["name", "email"]); }).not.toThrow(); }); it("should pass when required fields are 0 or false", () => { expect(() => { validateRequired({ count: 0, enabled: false }, ["count", "enabled"]); }).not.toThrow(); }); it("should handle empty required array", () => { expect(() => { validateRequired({ anything: "value" }, []); }).not.toThrow(); }); }); describe("validateSite", () => { it("should return the only site when site param is undefined", () => { const result = validateSite(undefined, ["site1"]); expect(result).toBe("site1"); }); it("should throw when site param is undefined with multiple sites", () => { expect(() => { validateSite(undefined, ["site1", "site2", "site3"]); }).toThrow("Site parameter is required when multiple sites are configured. Available sites: site1, site2, site3"); }); it("should return valid site when specified", () => { const result = validateSite("site2", ["site1", "site2", "site3"]); expect(result).toBe("site2"); }); it("should throw when site is not in available sites", () => { expect(() => { validateSite("invalid", ["site1", "site2"]); }).toThrow("Site 'invalid' not found. Available sites: site1, site2"); }); it("should handle empty string site param", () => { expect(() => { validateSite("", ["site1", "site2"]); }).toThrow("Site parameter is required when multiple sites are configured. Available sites: site1, site2"); }); it("should work with single site even when site param provided", () => { const result = validateSite("site1", ["site1"]); expect(result).toBe("site1"); }); it("should throw when wrong site provided with single available site", () => { expect(() => { validateSite("site2", ["site1"]); }).toThrow("Site 'site2' not found. Available sites: site1"); }); }); describe("Edge Cases", () => { it("should handle circular references in error objects", () => { const circular = { message: "Error" }; circular.self = circular; expect(getErrorMessage(circular)).toBe("Error"); }); it("should handle very long error messages", () => { const longMessage = "A".repeat(10000); expect(getErrorMessage(longMessage)).toBe(longMessage); }); it("should handle errors with no stack trace", () => { const error = new Error("No stack"); delete error.stack; try { handleToolError(error, "test"); } catch (_e) { // expected throw } expect(__errorUtilsLogger.debug).not.toHaveBeenCalled(); }); it("should handle validateRequired with non-object params", () => { expect(() => { validateRequired(null, ["field"]); }).toThrow(); expect(() => { validateRequired(undefined, ["field"]); }).toThrow(); expect(() => { validateRequired("string", ["field"]); }).toThrow(); }); it("should handle validateSite with empty available sites", () => { expect(() => { validateSite("site1", []); }).toThrow("Site 'site1' not found. Available sites: "); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/docdyhr/mcp-wordpress'

If you have feedback or need assistance with the MCP directory API, please join our Discord server