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