155 lines
4.5 KiB
TypeScript
155 lines
4.5 KiB
TypeScript
/**
|
|
* Tests for circuit breaker functionality
|
|
*/
|
|
|
|
import {
|
|
CircuitBreaker,
|
|
getCircuitBreaker,
|
|
} from "../../src/lib/circuitBreaker";
|
|
|
|
describe("Circuit Breaker Module", () => {
|
|
let circuitBreaker: CircuitBreaker;
|
|
|
|
beforeEach(() => {
|
|
circuitBreaker = new CircuitBreaker();
|
|
});
|
|
|
|
describe("CircuitBreaker", () => {
|
|
it("should start in CLOSED state", () => {
|
|
const state = circuitBreaker.getState();
|
|
expect(state.state).toBe("CLOSED");
|
|
expect(state.failureCount).toBe(0);
|
|
expect(state.successCount).toBe(0);
|
|
});
|
|
|
|
it("should execute successful operations", async () => {
|
|
const mockOperation = jest.fn().mockResolvedValue("success");
|
|
|
|
const result = await circuitBreaker.execute(mockOperation);
|
|
|
|
expect(result).toBe("success");
|
|
expect(mockOperation).toHaveBeenCalledTimes(1);
|
|
|
|
const state = circuitBreaker.getState();
|
|
expect(state.successCount).toBe(0);
|
|
});
|
|
|
|
it("should handle failed operations", async () => {
|
|
const mockOperation = jest.fn().mockRejectedValue(new Error("failure"));
|
|
|
|
await expect(circuitBreaker.execute(mockOperation)).rejects.toThrow(
|
|
"failure"
|
|
);
|
|
|
|
const state = circuitBreaker.getState();
|
|
expect(state.failureCount).toBe(1);
|
|
});
|
|
|
|
it("should open circuit after failure threshold", async () => {
|
|
const mockOperation = jest.fn().mockRejectedValue(new Error("failure"));
|
|
|
|
// Trigger multiple failures to open circuit
|
|
for (let i = 0; i < 5; i++) {
|
|
try {
|
|
await circuitBreaker.execute(mockOperation);
|
|
} catch (e) {
|
|
// Expected to fail
|
|
}
|
|
}
|
|
|
|
const state = circuitBreaker.getState();
|
|
expect(state.state).toBe("OPEN");
|
|
});
|
|
|
|
it("should reject requests when circuit is OPEN", async () => {
|
|
const mockOperation = jest.fn().mockRejectedValue(new Error("failure"));
|
|
|
|
// Open the circuit
|
|
for (let i = 0; i < 5; i++) {
|
|
try {
|
|
await circuitBreaker.execute(mockOperation);
|
|
} catch (e) {
|
|
// Expected to fail
|
|
}
|
|
}
|
|
|
|
// Now circuit should be open and reject immediately
|
|
const mockNewOperation = jest.fn().mockResolvedValue("success");
|
|
|
|
await expect(circuitBreaker.execute(mockNewOperation)).rejects.toThrow(
|
|
"Circuit breaker is OPEN"
|
|
);
|
|
expect(mockNewOperation).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("should transition to HALF_OPEN after timeout", async () => {
|
|
const mockOperation = jest.fn().mockRejectedValue(new Error("failure"));
|
|
|
|
// Open the circuit
|
|
for (let i = 0; i < 5; i++) {
|
|
try {
|
|
await circuitBreaker.execute(mockOperation);
|
|
} catch (e) {
|
|
// Expected to fail
|
|
}
|
|
}
|
|
|
|
// Manually set last failure time to simulate timeout
|
|
(circuitBreaker as any).state.lastFailureTime = Date.now() - 61000; // 61 seconds ago
|
|
|
|
const mockNewOperation = jest.fn().mockResolvedValue("success");
|
|
const result = await circuitBreaker.execute(mockNewOperation);
|
|
|
|
expect(result).toBe("success");
|
|
const state = circuitBreaker.getState();
|
|
expect(state.state).toBe("HALF_OPEN");
|
|
expect(state.successCount).toBe(1);
|
|
});
|
|
|
|
it("should reset failure count on success", async () => {
|
|
const mockFailOperation = jest
|
|
.fn()
|
|
.mockRejectedValue(new Error("failure"));
|
|
const mockSuccessOperation = jest.fn().mockResolvedValue("success");
|
|
|
|
// Cause some failures
|
|
try {
|
|
await circuitBreaker.execute(mockFailOperation);
|
|
} catch (e) {
|
|
// Expected
|
|
}
|
|
|
|
let state = circuitBreaker.getState();
|
|
expect(state.failureCount).toBe(1);
|
|
|
|
// Now succeed
|
|
await circuitBreaker.execute(mockSuccessOperation);
|
|
|
|
state = circuitBreaker.getState();
|
|
expect(state.failureCount).toBe(0);
|
|
expect(state.successCount).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe("getCircuitBreaker", () => {
|
|
it("should return same instance for same proxy URL", () => {
|
|
const cb1 = getCircuitBreaker("https://proxy1.com");
|
|
const cb2 = getCircuitBreaker("https://proxy1.com");
|
|
|
|
expect(cb1).toBe(cb2);
|
|
});
|
|
|
|
it("should return different instances for different proxy URLs", () => {
|
|
const cb1 = getCircuitBreaker("https://proxy1.com");
|
|
const cb2 = getCircuitBreaker("https://proxy2.com");
|
|
|
|
expect(cb1).not.toBe(cb2);
|
|
});
|
|
|
|
it("should handle invalid URLs", () => {
|
|
const cb = getCircuitBreaker("invalid-url");
|
|
expect(cb).toBeInstanceOf(CircuitBreaker);
|
|
});
|
|
});
|
|
});
|