/**
* 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: ");
});
});
});