180 lines
5.2 KiB
JavaScript
180 lines
5.2 KiB
JavaScript
const {
|
|
enhanceError,
|
|
categorizeError,
|
|
isRetryableError,
|
|
createStandardError,
|
|
} = require("../../src/tui/utils/errorHandler");
|
|
const ErrorHandlingService = require("../../src/tui/services/ErrorHandlingService");
|
|
|
|
describe("TUI Error Handling", () => {
|
|
describe("Error Enhancement", () => {
|
|
test("should enhance basic error with troubleshooting", () => {
|
|
const originalError = new Error("Network timeout");
|
|
const enhanced = enhanceError(originalError, {
|
|
operation: "fetchTags",
|
|
screen: "TagAnalysisScreen",
|
|
});
|
|
|
|
expect(enhanced.troubleshooting).toBeDefined();
|
|
expect(enhanced.troubleshooting.length).toBeGreaterThan(0);
|
|
expect(enhanced.context.operation).toBe("fetchTags");
|
|
expect(enhanced.enhanced).toBe(true);
|
|
});
|
|
|
|
test("should not double-enhance errors", () => {
|
|
const originalError = new Error("Test error");
|
|
const enhanced1 = enhanceError(originalError);
|
|
const enhanced2 = enhanceError(enhanced1);
|
|
|
|
expect(enhanced2).toBe(enhanced1);
|
|
});
|
|
});
|
|
|
|
describe("Error Categorization", () => {
|
|
test("should categorize network errors correctly", () => {
|
|
const networkError = new Error("Connection timeout");
|
|
expect(categorizeError(networkError)).toBe("network");
|
|
|
|
const econnError = new Error("ECONNRESET");
|
|
econnError.code = "ECONNRESET";
|
|
expect(categorizeError(econnError)).toBe("network");
|
|
});
|
|
|
|
test("should categorize API errors correctly", () => {
|
|
const apiError = new Error("Shopify API rate limit exceeded");
|
|
expect(categorizeError(apiError)).toBe("api");
|
|
|
|
const httpError = new Error("HTTP 503 Service Unavailable");
|
|
httpError.code = "503";
|
|
expect(categorizeError(httpError)).toBe("api");
|
|
});
|
|
|
|
test("should categorize file errors correctly", () => {
|
|
const fileError = new Error("File not found");
|
|
fileError.code = "ENOENT";
|
|
expect(categorizeError(fileError)).toBe("file");
|
|
|
|
const permissionError = new Error("Permission denied");
|
|
permissionError.code = "EACCES";
|
|
expect(categorizeError(permissionError)).toBe("file");
|
|
});
|
|
|
|
test("should categorize validation errors correctly", () => {
|
|
const validationError = new Error("Invalid input format");
|
|
validationError.name = "ValidationError";
|
|
expect(categorizeError(validationError)).toBe("validation");
|
|
});
|
|
});
|
|
|
|
describe("Retry Logic", () => {
|
|
test("should identify retryable errors", () => {
|
|
const networkError = new Error("Connection timeout");
|
|
expect(isRetryableError(networkError)).toBe(true);
|
|
|
|
const rateLimitError = new Error("Rate limit exceeded");
|
|
expect(isRetryableError(rateLimitError)).toBe(true);
|
|
|
|
const validationError = new Error("Invalid input");
|
|
validationError.name = "ValidationError";
|
|
expect(isRetryableError(validationError)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("Standard Errors", () => {
|
|
test("should create file not found error", () => {
|
|
const error = createStandardError("fileNotFound", {
|
|
file: "schedules.json",
|
|
});
|
|
|
|
expect(error.message).toContain("schedules.json");
|
|
expect(error.troubleshooting).toBeDefined();
|
|
expect(error.troubleshooting.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
test("should create API connection error", () => {
|
|
const error = createStandardError("apiConnectionFailed");
|
|
|
|
expect(error.message).toContain("Shopify API");
|
|
expect(error.troubleshooting).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe("ErrorHandlingService", () => {
|
|
let errorHandler;
|
|
|
|
beforeEach(() => {
|
|
errorHandler = new ErrorHandlingService();
|
|
});
|
|
|
|
test("should execute operation successfully on first try", async () => {
|
|
const mockOperation = jest.fn().mockResolvedValue("success");
|
|
|
|
const result = await errorHandler.executeWithRetry(mockOperation);
|
|
|
|
expect(result).toBe("success");
|
|
expect(mockOperation).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test("should retry failed operations", async () => {
|
|
const mockOperation = jest
|
|
.fn()
|
|
.mockRejectedValueOnce(new Error("Network timeout"))
|
|
.mockResolvedValue("success");
|
|
|
|
const result = await errorHandler.executeWithRetry(mockOperation, {
|
|
maxRetries: 2,
|
|
});
|
|
|
|
expect(result).toBe("success");
|
|
expect(mockOperation).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
test("should fail after max retries", async () => {
|
|
const mockOperation = jest
|
|
.fn()
|
|
.mockRejectedValue(new Error("Persistent error"));
|
|
|
|
await expect(
|
|
errorHandler.executeWithRetry(mockOperation, {
|
|
maxRetries: 1, // Only 1 retry, so should be called once
|
|
})
|
|
).rejects.toThrow();
|
|
|
|
expect(mockOperation).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test("should handle graceful file reads", async () => {
|
|
const mockFileReader = jest
|
|
.fn()
|
|
.mockRejectedValue(
|
|
Object.assign(new Error("File not found"), { code: "ENOENT" })
|
|
);
|
|
|
|
const result = await errorHandler.gracefulFileRead(
|
|
mockFileReader,
|
|
"fallback"
|
|
);
|
|
|
|
expect(result).toBe("fallback");
|
|
});
|
|
|
|
test("should track error history", async () => {
|
|
const mockOperation = jest
|
|
.fn()
|
|
.mockRejectedValue(new Error("Test error"));
|
|
|
|
try {
|
|
await errorHandler.executeWithRetry(mockOperation, { maxRetries: 1 });
|
|
} catch (error) {
|
|
// Expected to fail
|
|
}
|
|
|
|
const history = errorHandler.getErrorHistory();
|
|
expect(history.length).toBeGreaterThan(0);
|
|
|
|
const stats = errorHandler.getErrorStats();
|
|
expect(stats.totalErrors).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
});
|