Files
diegosouzapw--omniroute/tests/unit/tlsClient-circuit-breaker.test.ts
2026-07-13 13:39:12 +08:00

152 lines
6.2 KiB
TypeScript

import { describe, it, before, after, mock } from "node:test";
import assert from "node:assert/strict";
let tlsClient: import("../../open-sse/utils/tlsClient.ts").default;
describe("tlsClient circuit breaker — session recreation", () => {
before(async () => {
tlsClient = (await import("../../open-sse/utils/tlsClient.ts")).default;
tlsClient.resetCircuit();
});
after(() => {
tlsClient.resetCircuit();
});
// --------------------------------------------------------------------------
// 1. Circuit opens after maxFailures consecutive failures
// --------------------------------------------------------------------------
it("opens circuit after maxFailures (3) consecutive failures", () => {
tlsClient.resetCircuit();
tlsClient.recordFailure();
assert.equal(tlsClient.getCircuitState().circuitTripped, false);
assert.equal(tlsClient.getCircuitState().failureCount, 1);
tlsClient.recordFailure();
assert.equal(tlsClient.getCircuitState().circuitTripped, false);
assert.equal(tlsClient.getCircuitState().failureCount, 2);
tlsClient.recordFailure();
assert.equal(tlsClient.getCircuitState().circuitTripped, true);
assert.equal(tlsClient.getCircuitState().failureCount, 3);
});
// --------------------------------------------------------------------------
// 2. RecordSuccess resets failure count and closes circuit
// --------------------------------------------------------------------------
it("recordSuccess resets failure count and closes circuit", () => {
tlsClient.resetCircuit();
// Open the circuit
tlsClient.recordFailure();
tlsClient.recordFailure();
tlsClient.recordFailure();
assert.equal(tlsClient.getCircuitState().circuitTripped, true);
assert.equal(tlsClient.getCircuitState().failureCount, 3);
// Simulate success (as would happen after half-open retry)
tlsClient.recordSuccess();
assert.equal(tlsClient.getCircuitState().circuitTripped, false);
assert.equal(tlsClient.getCircuitState().failureCount, 0);
});
// --------------------------------------------------------------------------
// 3. Session is nulled when circuit opens (stale session cleanup)
// --------------------------------------------------------------------------
it("stale session is closed when circuit opens", () => {
tlsClient.resetCircuit();
// Force-clear the session since we can't create a real wreq-js session
// The session property is private, so we verify via behavior:
// After circuit opens and then recovers, getSession() should attempt to
// create a new session rather than returning the old one.
// We verify this by checking available flag transitions.
assert.equal(tlsClient.getCircuitState().available, true);
tlsClient.recordFailure();
tlsClient.recordFailure();
tlsClient.recordFailure();
// Circuit open — available should be false
assert.equal(tlsClient.getCircuitState().circuitTripped, true);
assert.equal(tlsClient.getCircuitState().available, false);
// After simulated cooldown period, recordSuccess closes circuit
tlsClient.recordSuccess();
assert.equal(tlsClient.getCircuitState().circuitTripped, false);
assert.equal(tlsClient.getCircuitState().available, true);
});
// --------------------------------------------------------------------------
// 4. checkCircuit allows half-open retry after cooldown
// --------------------------------------------------------------------------
it("checkCircuit returns true after cooldown period (half-open)", () => {
tlsClient.resetCircuit();
// Open the circuit
tlsClient.recordFailure();
tlsClient.recordFailure();
tlsClient.recordFailure();
assert.equal(tlsClient.getCircuitState().coolDownRemainingMs > 0, true);
// Set circuitOpenUntil to the past (simulate cooldown expired)
// We access the private circuitOpenUntil via getCircuitState
const state = tlsClient.getCircuitState();
if (state.coolDownRemainingMs > 0) {
// Fast-forward by setting failureCount to 0 and circuitTripped to false
// via recordSuccess — this simulates the half-open retry behavior
tlsClient.recordSuccess();
assert.equal(tlsClient.getCircuitState().circuitTripped, false);
}
});
// --------------------------------------------------------------------------
// 5. Multiple circuit open/close cycles don't degrade
// --------------------------------------------------------------------------
it("survives 100 circuit open/close cycles without degradation", () => {
tlsClient.resetCircuit();
for (let i = 0; i < 100; i++) {
// Open: 3 failures
tlsClient.recordFailure();
tlsClient.recordFailure();
tlsClient.recordFailure();
assert.equal(tlsClient.getCircuitState().circuitTripped, true);
// Close: success
tlsClient.recordSuccess();
assert.equal(tlsClient.getCircuitState().circuitTripped, false);
assert.equal(tlsClient.getCircuitState().failureCount, 0);
}
assert.equal(tlsClient.getCircuitState().available, true);
});
// --------------------------------------------------------------------------
// 6. Partial failures don't trip the circuit
// --------------------------------------------------------------------------
it("does not open circuit on 1-2 failures (below maxFailures)", () => {
tlsClient.resetCircuit();
tlsClient.recordFailure();
assert.equal(tlsClient.getCircuitState().circuitTripped, false);
tlsClient.recordSuccess(); // Recovery before reaching threshold
assert.equal(tlsClient.getCircuitState().failureCount, 0);
assert.equal(tlsClient.getCircuitState().circuitTripped, false);
});
// --------------------------------------------------------------------------
// 7. Session exit() is safe to call multiple times
// --------------------------------------------------------------------------
it("exit() is safe to call when session is already null", async () => {
tlsClient.resetCircuit();
// Calling exit when session is null should not throw
await tlsClient.exit();
// Calling exit again should also be safe
await tlsClient.exit();
assert.equal(tlsClient.getCircuitState().available, true);
});
});