196 lines
6.3 KiB
JavaScript
196 lines
6.3 KiB
JavaScript
const { describe, test, expect, beforeEach } = require("@jest/globals");
|
|
|
|
// Mock config
|
|
jest.mock("../../config", () => ({
|
|
errorHandling: {
|
|
maxRetries: 3,
|
|
},
|
|
}));
|
|
|
|
// Mock logger
|
|
jest.mock("../../lib/logger", () => ({
|
|
emailFailed: jest.fn(),
|
|
emailRetry: jest.fn(),
|
|
emailPermanentFailure: jest.fn(),
|
|
}));
|
|
|
|
const errorHandler = require("../../lib/errorHandler");
|
|
|
|
describe("ErrorHandler", () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
errorHandler.clearRetries();
|
|
});
|
|
|
|
describe("classifyError", () => {
|
|
test("should classify authentication errors", () => {
|
|
const error = new Error("Invalid login credentials");
|
|
const result = errorHandler.classifyError(error);
|
|
expect(result).toBe("AUTH_ERROR");
|
|
});
|
|
|
|
test("should classify rate limit errors", () => {
|
|
const error = new Error("Rate limit exceeded");
|
|
const result = errorHandler.classifyError(error);
|
|
expect(result).toBe("RATE_LIMIT");
|
|
});
|
|
|
|
test("should classify network errors", () => {
|
|
const error = new Error("Connection timeout");
|
|
const result = errorHandler.classifyError(error);
|
|
expect(result).toBe("NETWORK_ERROR");
|
|
});
|
|
|
|
test("should classify recipient errors", () => {
|
|
const error = new Error("Invalid recipient address");
|
|
const result = errorHandler.classifyError(error);
|
|
expect(result).toBe("RECIPIENT_ERROR");
|
|
});
|
|
|
|
test("should classify message errors", () => {
|
|
const error = new Error("Message too large");
|
|
const result = errorHandler.classifyError(error);
|
|
expect(result).toBe("MESSAGE_ERROR");
|
|
});
|
|
|
|
test("should classify unknown errors", () => {
|
|
const error = new Error("Something unexpected happened");
|
|
const result = errorHandler.classifyError(error);
|
|
expect(result).toBe("UNKNOWN_ERROR");
|
|
});
|
|
});
|
|
|
|
describe("isRetryable", () => {
|
|
test("should mark retryable errors as retryable", () => {
|
|
expect(errorHandler.isRetryable("RATE_LIMIT")).toBe(true);
|
|
expect(errorHandler.isRetryable("NETWORK_ERROR")).toBe(true);
|
|
expect(errorHandler.isRetryable("UNKNOWN_ERROR")).toBe(true);
|
|
});
|
|
|
|
test("should mark non-retryable errors as non-retryable", () => {
|
|
expect(errorHandler.isRetryable("AUTH_ERROR")).toBe(false);
|
|
expect(errorHandler.isRetryable("RECIPIENT_ERROR")).toBe(false);
|
|
expect(errorHandler.isRetryable("MESSAGE_ERROR")).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("getRetryDelay", () => {
|
|
test("should calculate exponential backoff delay", () => {
|
|
const delay1 = errorHandler.getRetryDelay(1);
|
|
const delay2 = errorHandler.getRetryDelay(2);
|
|
const delay3 = errorHandler.getRetryDelay(3);
|
|
|
|
// Each delay should be roughly double the previous (with jitter)
|
|
expect(delay1).toBeGreaterThan(45000); // ~1 minute with jitter
|
|
expect(delay1).toBeLessThan(90000);
|
|
|
|
expect(delay2).toBeGreaterThan(90000); // ~2 minutes with jitter
|
|
expect(delay2).toBeLessThan(180000);
|
|
|
|
expect(delay3).toBeGreaterThan(180000); // ~4 minutes with jitter
|
|
expect(delay3).toBeLessThan(360000);
|
|
});
|
|
|
|
test("should cap delay at maximum", () => {
|
|
const delay = errorHandler.getRetryDelay(10); // Very high attempt number
|
|
expect(delay).toBeLessThanOrEqual(30 * 60 * 1000); // 30 minutes max
|
|
});
|
|
});
|
|
|
|
describe("handleError", () => {
|
|
test("should schedule retry for retryable errors", async () => {
|
|
const email = { subject: "Test", firmName: "Test Firm" };
|
|
const recipient = "test@example.com";
|
|
const error = new Error("Network timeout");
|
|
const transporter = {};
|
|
|
|
const result = await errorHandler.handleError(
|
|
email,
|
|
recipient,
|
|
error,
|
|
transporter
|
|
);
|
|
|
|
expect(result).toBe(true); // Indicates retry scheduled
|
|
|
|
const stats = errorHandler.getRetryStats();
|
|
expect(stats.totalFailed).toBe(1);
|
|
});
|
|
|
|
test("should not schedule retry for non-retryable errors", async () => {
|
|
const email = { subject: "Test", firmName: "Test Firm" };
|
|
const recipient = "test@example.com";
|
|
const error = new Error("Invalid login");
|
|
const transporter = {};
|
|
|
|
const result = await errorHandler.handleError(
|
|
email,
|
|
recipient,
|
|
error,
|
|
transporter
|
|
);
|
|
|
|
expect(result).toBe(false); // Indicates permanent failure
|
|
|
|
const stats = errorHandler.getRetryStats();
|
|
expect(stats.totalFailed).toBe(0);
|
|
});
|
|
|
|
test("should not retry after max attempts reached", async () => {
|
|
const email = { subject: "Test", firmName: "Test Firm" };
|
|
const recipient = "test@example.com";
|
|
const error = new Error("Network timeout");
|
|
const transporter = {};
|
|
|
|
// Schedule maximum retries
|
|
await errorHandler.handleError(email, recipient, error, transporter);
|
|
await errorHandler.handleError(email, recipient, error, transporter);
|
|
await errorHandler.handleError(email, recipient, error, transporter);
|
|
|
|
// This should be a permanent failure
|
|
const result = await errorHandler.handleError(
|
|
email,
|
|
recipient,
|
|
error,
|
|
transporter
|
|
);
|
|
|
|
expect(result).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("getRetryStats", () => {
|
|
test("should return correct retry statistics", () => {
|
|
// Initially no retries
|
|
let stats = errorHandler.getRetryStats();
|
|
expect(stats.totalFailed).toBe(0);
|
|
expect(stats.pendingRetries).toBe(0);
|
|
expect(stats.readyToRetry).toBe(0);
|
|
|
|
// Add a retry item
|
|
errorHandler.failedEmails.push({
|
|
recipient: "test@example.com",
|
|
retryAt: Date.now() + 60000, // 1 minute from now
|
|
});
|
|
|
|
stats = errorHandler.getRetryStats();
|
|
expect(stats.totalFailed).toBe(1);
|
|
expect(stats.pendingRetries).toBe(1);
|
|
expect(stats.readyToRetry).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe("clearRetries", () => {
|
|
test("should clear all retry data", () => {
|
|
// Add some retry data
|
|
errorHandler.failedEmails.push({ recipient: "test@example.com" });
|
|
errorHandler.retryAttempts.set("test-key", 2);
|
|
|
|
errorHandler.clearRetries();
|
|
|
|
expect(errorHandler.failedEmails).toHaveLength(0);
|
|
expect(errorHandler.retryAttempts.size).toBe(0);
|
|
});
|
|
});
|
|
});
|