UNPKG

@daiso-tech/core

Version:

The library offers flexible, framework-agnostic solutions for modern web applications, built on adaptable components that integrate seamlessly with popular frameworks like Next Js.

457 lines 25.2 kB
/** * @module CircuitBreaker */ import {} from "vitest"; import { BACKOFFS, } from "../../../backoff-policies/implementations/_module.js"; import { CIRCUIT_BREAKER_STATE, } from "../../../circuit-breaker/contracts/_module.js"; import { BREAKER_POLICIES, } from "../../../circuit-breaker/implementations/policies/_module.js"; import {} from "../../../execution-context/contracts/_module.js"; import { NoOpExecutionContextAdapter } from "../../../execution-context/implementations/adapters/no-op-execution-context-adapter/_module.js"; import { ExecutionContext } from "../../../execution-context/implementations/derivables/_module.js"; import {} from "../../../time-span/contracts/_module.js"; import { TimeSpan } from "../../../time-span/implementations/_module.js"; import { delay } from "../../../utilities/_module.js"; /** * @group TestUtilities */ const circuitBreakerPolicySettings = { type: BREAKER_POLICIES.CONSECUTIVE, failureThreshold: 5, successThreshold: 5, }; /** * @group TestUtilities */ const backoffPolicySettings = { type: BACKOFFS.CONSTANT, delay: TimeSpan.fromMilliseconds(50), jitter: null, }; /** * IMPORT_PATH: `"@daiso-tech/core/circuit-breaker/test-utilities"` * @group TestUtilities * * @example * ```ts * import { beforeEach, describe, expect, test } from "vitest"; * import { DatabaseCircuitBreakerAdapter } from "@daiso-tech/core/circuit-breaker/database-circuit-breaker-adapter"; * import { ConsecutiveBreaker } from "@daiso-tech/core/circuit-breaker/policies"; * import { consecutiveBreakerTestSuite } from "@daiso-tech/core/circuit-breaker/test-utilities"; * import { constantBackoff } from "@daiso-tech/core/backoff-policies"; * import { MemoryCircuitBreakerStorageAdapter } from "@daiso-tech/core/circuit-breaker/memory-circuit-breaker-storage-adapter"; * * describe("consecutive-breaker class: DatabaseCircuitBreakerAdapter", () => { * consecutiveBreakerTestSuite({ * createAdapter: () => { * const adapter = new DatabaseCircuitBreakerAdapter({ * adapter: new MemoryCircuitBreakerStorageAdapter(), * backoffPolicy: constantBackoff( * consecutiveBreakerTestSuite.backoffPolicySettings, * ), * circuitBreakerPolicy: new ConsecutiveBreaker( * consecutiveBreakerTestSuite.circuitBreakerPolicySettings, * ), * }); * return adapter; * }, * beforeEach, * describe, * expect, * test, * }); * }); * ``` */ export function consecutiveBreakerTestSuite(settings) { const { expect, test, createAdapter, describe, beforeEach: beforeEach_, delayBuffer = TimeSpan.fromMilliseconds(10), context = new ExecutionContext(new NoOpExecutionContextAdapter()), } = settings; let adapter; const waitTime = TimeSpan.fromTimeSpan(backoffPolicySettings.delay); describe("consecutive-breaker ICircuitBreakerAdapter tests:", () => { beforeEach_(async () => { adapter = await createAdapter(); }); const KEY = "a"; async function delayWithBuffer(timeSpan) { await delay(TimeSpan.fromTimeSpan(timeSpan).addTimeSpan(delayBuffer)); } describe("method: getState", () => { test("Should return CIRCUIT_BREAKER_STATE.CLOSED as initial state", async () => { const state = await adapter.getState(context, KEY); expect(state).toBe(CIRCUIT_BREAKER_STATE.CLOSED); }); test("Should return CIRCUIT_BREAKER_STATE.CLOSED when in ClosedState", async () => { await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); const state = await adapter.getState(context, KEY); expect(state).toBe(CIRCUIT_BREAKER_STATE.CLOSED); }); test("Should return CIRCUIT_BREAKER_STATE.OPEN when in OpenedState", async () => { await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); const state = await adapter.getState(context, KEY); expect(state).toBe(CIRCUIT_BREAKER_STATE.OPEN); }); test("Should return CIRCUIT_BREAKER_STATE.HALF_OPEN when in HalfOpenState", async () => { await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await delayWithBuffer(waitTime); await adapter.updateState(context, KEY); const state = await adapter.getState(context, KEY); expect(state).toBe(CIRCUIT_BREAKER_STATE.HALF_OPEN); }); test("Should return CIRCUIT_BREAKER_STATE.ISOLATED when in IsolatedState", async () => { await adapter.isolate(context, KEY); const state = await adapter.getState(context, KEY); expect(state).toBe(CIRCUIT_BREAKER_STATE.ISOLATED); }); }); describe("method: updateState / trackFailure / trackSuccess", () => { test("Should transition ClosedState -> ClosedState when 1 failure has occurred", async () => { await adapter.trackFailure(context, KEY); const transition = await adapter.updateState(context, KEY); expect(transition).toEqual({ from: CIRCUIT_BREAKER_STATE.CLOSED, to: CIRCUIT_BREAKER_STATE.CLOSED, }); }); test("Should transition ClosedState -> ClosedState when 4 consecutive failures has occurred", async () => { await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); const transition = await adapter.updateState(context, KEY); expect(transition).toEqual({ from: CIRCUIT_BREAKER_STATE.CLOSED, to: CIRCUIT_BREAKER_STATE.CLOSED, }); }); test("Should transition ClosedState -> OpenState when 5 consecutive failures has occurred", async () => { await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); const transition = await adapter.updateState(context, KEY); expect(transition).toEqual({ from: CIRCUIT_BREAKER_STATE.CLOSED, to: CIRCUIT_BREAKER_STATE.OPEN, }); }); test("Should transition ClosedState -> ClosedState when 4 consecutive failures, 1 success and 1 failure has occurred", async () => { await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackSuccess(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); const transition = await adapter.updateState(context, KEY); expect(transition).toEqual({ from: CIRCUIT_BREAKER_STATE.CLOSED, to: CIRCUIT_BREAKER_STATE.CLOSED, }); }); test("Should transition ClosedState -> OpenState -> OpenState when 5 consecutive failures has occurred and wait time is not reached", async () => { await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await delayWithBuffer(waitTime.divide(2)); const transition = await adapter.updateState(context, KEY); expect(transition).toEqual({ from: CIRCUIT_BREAKER_STATE.OPEN, to: CIRCUIT_BREAKER_STATE.OPEN, }); }); test("Should transition ClosedState -> OpenState -> HalfOpenState when 5 consecutive failures has occurred and wait time is reached", async () => { await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await delayWithBuffer(waitTime); const transition = await adapter.updateState(context, KEY); expect(transition).toEqual({ from: CIRCUIT_BREAKER_STATE.OPEN, to: CIRCUIT_BREAKER_STATE.HALF_OPEN, }); }); test("Should transition ClosedState -> OpenState -> HalfOpenState -> HalfOpenState when 5 consecutive failures, wait time is reached and 1 consecutive successes has occurred", async () => { await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await delayWithBuffer(waitTime); await adapter.updateState(context, KEY); await adapter.trackSuccess(context, KEY); const transition = await adapter.updateState(context, KEY); expect(transition).toEqual({ from: CIRCUIT_BREAKER_STATE.HALF_OPEN, to: CIRCUIT_BREAKER_STATE.HALF_OPEN, }); }); test("Should transition ClosedState -> OpenState -> HalfOpenState -> HalfOpenState when 5 consecutive failures, wait time is reached and 4 consecutive successes has occurred", async () => { await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await delayWithBuffer(waitTime); await adapter.updateState(context, KEY); await adapter.trackSuccess(context, KEY); await adapter.updateState(context, KEY); await adapter.trackSuccess(context, KEY); await adapter.updateState(context, KEY); await adapter.trackSuccess(context, KEY); await adapter.updateState(context, KEY); await adapter.trackSuccess(context, KEY); const transition = await adapter.updateState(context, KEY); expect(transition).toEqual({ from: CIRCUIT_BREAKER_STATE.HALF_OPEN, to: CIRCUIT_BREAKER_STATE.HALF_OPEN, }); }); test("Should transition ClosedState -> OpenState -> HalfOpenState -> ClosedState when 5 consecutive failures, wait time is reached and 5 consecutive successes has occurred", async () => { await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await delayWithBuffer(waitTime); await adapter.updateState(context, KEY); await adapter.trackSuccess(context, KEY); await adapter.updateState(context, KEY); await adapter.trackSuccess(context, KEY); await adapter.updateState(context, KEY); await adapter.trackSuccess(context, KEY); await adapter.updateState(context, KEY); await adapter.trackSuccess(context, KEY); await adapter.updateState(context, KEY); await adapter.trackSuccess(context, KEY); const transition = await adapter.updateState(context, KEY); expect(transition).toEqual({ from: CIRCUIT_BREAKER_STATE.HALF_OPEN, to: CIRCUIT_BREAKER_STATE.CLOSED, }); }); test("Should transition ClosedState -> OpenState -> HalfOpenState -> OpenState when 5 consecutive failures, wait time is reached and 1 failure has occurred", async () => { await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await delayWithBuffer(waitTime); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); const transition = await adapter.updateState(context, KEY); expect(transition).toEqual({ from: CIRCUIT_BREAKER_STATE.HALF_OPEN, to: CIRCUIT_BREAKER_STATE.OPEN, }); }); test("Should transition ClosedState -> OpenState -> HalfOpenState -> OpenState when 5 consecutive failures, wait time is reached, 4 consecutive successes and 1 failure has occurred", async () => { await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await delayWithBuffer(waitTime); await adapter.updateState(context, KEY); await adapter.trackSuccess(context, KEY); await adapter.updateState(context, KEY); await adapter.trackSuccess(context, KEY); await adapter.updateState(context, KEY); await adapter.trackSuccess(context, KEY); await adapter.updateState(context, KEY); await adapter.trackSuccess(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); const transition = await adapter.updateState(context, KEY); expect(transition).toEqual({ from: CIRCUIT_BREAKER_STATE.HALF_OPEN, to: CIRCUIT_BREAKER_STATE.OPEN, }); }); }); describe("method: updateState / trackFailure / isolate / getState", () => { test("Should transition to IsolatedState when in ClosedState", async () => { await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.isolate(context, KEY); const state = await adapter.getState(context, KEY); expect(state).toBe(CIRCUIT_BREAKER_STATE.ISOLATED); }); test("Should transition to IsolatedState when in OpenedState", async () => { await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.isolate(context, KEY); const state = await adapter.getState(context, KEY); expect(state).toBe(CIRCUIT_BREAKER_STATE.ISOLATED); }); test("Should transition to IsolatedState when in HalfOpenState", async () => { await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await delayWithBuffer(waitTime); await adapter.updateState(context, KEY); await adapter.isolate(context, KEY); const state = await adapter.getState(context, KEY); expect(state).toBe(CIRCUIT_BREAKER_STATE.ISOLATED); }); }); describe("method: updateState / trackFailure / reset", () => { test("Should reset when in ClosedState", async () => { await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.reset(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); const state = await adapter.getState(context, KEY); expect(state).toBe(CIRCUIT_BREAKER_STATE.CLOSED); }); test("Should reset when in OpenedState", async () => { await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.reset(context, KEY); const state = await adapter.getState(context, KEY); expect(state).toBe(CIRCUIT_BREAKER_STATE.CLOSED); }); test("Should reset when in HalfOpenState", async () => { await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); await delayWithBuffer(waitTime); await adapter.updateState(context, KEY); await adapter.reset(context, KEY); const state = await adapter.getState(context, KEY); expect(state).toBe(CIRCUIT_BREAKER_STATE.CLOSED); }); test("Should reset when in IsolatedState", async () => { await adapter.isolate(context, KEY); await adapter.reset(context, KEY); const state = await adapter.getState(context, KEY); expect(state).toBe(CIRCUIT_BREAKER_STATE.CLOSED); }); }); test.skip("TEST", async () => { await adapter.trackFailure(context, KEY); await adapter.updateState(context, KEY); }); }); } consecutiveBreakerTestSuite.circuitBreakerPolicySettings = circuitBreakerPolicySettings; consecutiveBreakerTestSuite.backoffPolicySettings = backoffPolicySettings; //# sourceMappingURL=consecutive-breaker.test-suite.js.map