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.

1,125 lines 153 kB
/** * @module Semaphore */ import { vi, } from "vitest"; import {} from "../../../event-bus/contracts/_module.js"; import { LimitReachedSemaphoreError, SEMAPHORE_EVENTS, FailedReleaseSemaphoreError, FailedRefreshSemaphoreError, SEMAPHORE_STATE, } from "../../../semaphore/contracts/_module.js"; import {} from "../../../serde/contracts/_module.js"; import { createIsTimeSpanEqualityTester } from "../../../test-utilities/_module.js"; import { TO_MILLISECONDS, } from "../../../time-span/contracts/_module.js"; import { TimeSpan } from "../../../time-span/implementations/_module.js"; import { delay } from "../../../utilities/_module.js"; /** * The `semaphoreFactoryTestSuite` function simplifies the process of testing your custom implementation of {@link ISemaphore | `ISemaphore`} with `vitest`. * * IMPORT_PATH: `"@daiso-tech/core/semaphore/test-utilities"` * @group Utilities * @example * ```ts * import { describe, expect, test, beforeEach } from "vitest"; * import { MemorySemaphoreAdapter } from "@daiso-tech/core/semaphore/memory-semaphore-adapter"; * import { SemaphoreFactory } from "@daiso-tech/core/semaphore"; * import { EventBus } from "@daiso-tech/core/event-bus"; * import { MemoryEventBusAdapter } from "@daiso-tech/core/event-bus/memory-event-bus-adapter"; * import { semaphoreFactoryTestSuite } from "@daiso-tech/core/semaphore/test-utilities"; * import { Serde } from "@daiso-tech/core/serde"; * import { SuperJsonSerdeAdapter } from "@daiso-tech/core/serde/super-json-serde-adapter"; * import type { ISemaphoreData } from "@daiso-tech/core/semaphore/contracts"; * * describe("class: SemaphoreFactory", () => { * semaphoreFactoryTestSuite({ * createSemaphoreFactory: () => { * const serde = new Serde(new SuperJsonSerdeAdapter()); * const semaphoreFactory = new SemaphoreFactory({ * serde, * adapter: new MemorySemaphoreAdapter(), * }); * return { semaphoreFactory, serde }; * }, * beforeEach, * describe, * expect, * test, * serde, * }); * }); * ``` */ export function semaphoreFactoryTestSuite(settings) { const { expect, test, createSemaphoreFactory, describe, beforeEach: beforeEach_, includeEventTests = true, includeSerdeTests = true, delayBuffer = TimeSpan.fromMilliseconds(10), timeSpanEqualityBuffer = TimeSpan.fromMilliseconds(10), eventDispatchWaitTime = TimeSpan.fromMilliseconds(10), } = settings; let semaphoreFactory; let serde; const waitForSettings = { interval: TimeSpan.fromTimeSpan(eventDispatchWaitTime).toMilliseconds(), timeout: TimeSpan.fromTimeSpan(eventDispatchWaitTime) .multiply(3) .toMilliseconds(), }; async function delayWithBuffer(ttl) { await delay(TimeSpan.fromTimeSpan(ttl).addTimeSpan(delayBuffer)); } const RETURN_VALUE = "RETURN_VALUE"; describe("ISemaphoreFactory tests:", () => { beforeEach_(async () => { const { semaphoreFactory: semaphoreFactory_, serde: serde_ } = await createSemaphoreFactory(); semaphoreFactory = semaphoreFactory_; serde = serde_; }); describe("Api tests:", () => { describe("method: runOrFail", () => { test("Should call acquireOrFail method", async () => { const key = "a"; const ttl = null; const limit = 1; const semaphore = semaphoreFactory.create(key, { ttl, limit, }); const acquireOrFailSpy = vi.spyOn(semaphore, "acquireOrFail"); await semaphore.runOrFail(() => { return Promise.resolve(RETURN_VALUE); }); expect(acquireOrFailSpy).toHaveBeenCalledTimes(1); }); test("Should call acquireOrFail before release method", async () => { const key = "a"; const ttl = null; const limit = 1; const semaphore = semaphoreFactory.create(key, { ttl, limit, }); const acquireOrFailSpy = vi.spyOn(semaphore, "acquireOrFail"); const releaseSpy = vi.spyOn(semaphore, "release"); await semaphore.runOrFail(() => { return Promise.resolve(RETURN_VALUE); }); expect(acquireOrFailSpy).toHaveBeenCalledBefore(releaseSpy); }); test("Should call release method", async () => { const key = "a"; const ttl = null; const limit = 1; const semaphore = semaphoreFactory.create(key, { ttl, limit, }); const releaseSpy = vi.spyOn(semaphore, "release"); await semaphore.runOrFail(() => { return Promise.resolve(RETURN_VALUE); }); expect(releaseSpy).toHaveBeenCalledTimes(1); }); test("Should call release after acquireOrFail method", async () => { const key = "a"; const ttl = null; const limit = 1; const semaphore = semaphoreFactory.create(key, { ttl, limit, }); const releaseSpy = vi.spyOn(semaphore, "release"); const acquireOrFailSpy = vi.spyOn(semaphore, "acquireOrFail"); await semaphore.runOrFail(() => { return Promise.resolve(RETURN_VALUE); }); expect(releaseSpy).toHaveBeenCalledAfter(acquireOrFailSpy); }); test("Should call release when an error is thrown", async () => { const key = "a"; const ttl = null; const limit = 1; const semaphore = semaphoreFactory.create(key, { ttl, limit, }); const releaseSpy = vi.spyOn(semaphore, "release"); class UnexpectedError extends Error { } try { await semaphore.runOrFail(() => { return Promise.reject(new UnexpectedError()); }); } catch (error) { if (!(error instanceof UnexpectedError)) { throw error; } } expect(releaseSpy).toHaveBeenCalledTimes(1); }); test("Should propagate thrown error", async () => { const key = "a"; const ttl = null; const limit = 1; const semaphore = semaphoreFactory.create(key, { ttl, limit, }); class UnexpectedErrorA extends Error { } const error = semaphore.runOrFail(() => { return Promise.reject(new UnexpectedErrorA()); }); await expect(error).rejects.toBeInstanceOf(UnexpectedErrorA); }); test("Should call handler function when key doesnt exists", async () => { const key = "a"; const ttl = null; const limit = 1; const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); await semaphoreFactory .create(key, { ttl, limit, }) .runOrFail(handlerFn); expect(handlerFn).toHaveBeenCalledTimes(1); }); test("Should call handler function when slot is expired", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const limit = 1; await semaphoreFactory .create(key, { ttl, limit, }) .acquire(); await delayWithBuffer(ttl); const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); await semaphoreFactory .create(key, { ttl, limit, }) .runOrFail(handlerFn); expect(handlerFn).toHaveBeenCalledTimes(1); }); test("Should not call handler function when slot is unexpireable", async () => { const key = "a"; const ttl = null; const limit = 1; const semaphore = semaphoreFactory.create(key, { ttl, limit, }); await semaphore.acquire(); const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); try { await semaphore.runOrFail(handlerFn); } catch (error) { if (!(error instanceof LimitReachedSemaphoreError)) { throw error; } } await delay(delayBuffer); expect(handlerFn).not.toHaveBeenCalled(); }); test("Should not call handler function when slot is unexpired", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const limit = 1; const semaphore = semaphoreFactory.create(key, { ttl, limit, }); await semaphore.acquire(); const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); try { await semaphore.runOrFail(handlerFn); } catch (error) { if (!(error instanceof LimitReachedSemaphoreError)) { throw error; } } await delay(delayBuffer); expect(handlerFn).not.toHaveBeenCalled(); }); test("Should not call handler function when slot is unexpireable", async () => { const key = "a"; const ttl = null; const limit = 1; await semaphoreFactory .create(key, { ttl, limit, }) .acquire(); const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); try { await semaphoreFactory .create(key, { ttl, limit, }) .runOrFail(handlerFn); } catch (error) { if (!(error instanceof LimitReachedSemaphoreError)) { throw error; } } await delay(delayBuffer); expect(handlerFn).not.toHaveBeenCalled(); }); test("Should not call handler function when slot is unexpired", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const limit = 1; await semaphoreFactory .create(key, { ttl, limit, }) .acquire(); const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); try { await semaphoreFactory .create(key, { ttl, limit, }) .runOrFail(handlerFn); } catch (error) { if (!(error instanceof LimitReachedSemaphoreError)) { throw error; } } await delay(delayBuffer); expect(handlerFn).not.toHaveBeenCalled(); }); test("Should return value when key doesnt exists", async () => { const key = "a"; const ttl = null; const limit = 1; const result = await semaphoreFactory .create(key, { ttl, limit, }) .runOrFail(() => { return Promise.resolve(RETURN_VALUE); }); expect(result).toBe(RETURN_VALUE); }); test("Should return value when slot is expired", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const limit = 1; await semaphoreFactory .create(key, { ttl, limit, }) .acquire(); await delayWithBuffer(ttl); const result = await semaphoreFactory .create(key, { ttl, limit, }) .runOrFail(() => { return Promise.resolve(RETURN_VALUE); }); expect(result).toBe(RETURN_VALUE); }); test("Should throw LimitReachedSemaphoreError when slot is unexpireable", async () => { const key = "a"; const ttl = null; const limit = 1; const semaphore = semaphoreFactory.create(key, { ttl, limit, }); await semaphore.acquire(); const result = semaphore.runOrFail(() => { return Promise.resolve(RETURN_VALUE); }); await expect(result).rejects.toBeInstanceOf(LimitReachedSemaphoreError); }); test("Should throw LimitReachedSemaphoreError when slot is unexpired", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const limit = 1; const semaphore = semaphoreFactory.create(key, { ttl, limit, }); await semaphore.acquire(); const result = semaphore.runOrFail(() => { return Promise.resolve(RETURN_VALUE); }); await expect(result).rejects.toBeInstanceOf(LimitReachedSemaphoreError); }); test("Should throw LimitReachedSemaphoreError when slot is unexpireable", async () => { const key = "a"; const ttl = null; const limit = 1; await semaphoreFactory .create(key, { ttl, limit, }) .acquire(); const result = semaphoreFactory .create(key, { ttl, limit, }) .runOrFail(() => { return Promise.resolve(RETURN_VALUE); }); await expect(result).rejects.toBeInstanceOf(LimitReachedSemaphoreError); }); test("Should throw LimitReachedSemaphoreError when slot is unexpired", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const limit = 1; await semaphoreFactory .create(key, { ttl, limit, }) .acquire(); const result = semaphoreFactory .create(key, { ttl, limit, }) .runOrFail(() => { return Promise.resolve(RETURN_VALUE); }); await expect(result).rejects.toBeInstanceOf(LimitReachedSemaphoreError); }); }); describe("method: acquire", () => { test("Should return true when key doesnt exists", async () => { const key = "a"; const limit = 2; const ttl = null; const result = await semaphoreFactory .create(key, { limit, ttl, }) .acquire(); expect(result).toBe(true); }); test("Should return true when key exists and slot is expired", async () => { const key = "a"; const limit = 2; const ttl = TimeSpan.fromMilliseconds(50); const semaphore = semaphoreFactory.create(key, { limit, ttl, }); await semaphore.acquire(); await delayWithBuffer(ttl); const result = await semaphore.acquire(); expect(result).toBe(true); }); test("Should return true when limit is not reached", async () => { const key = "a"; const limit = 2; const ttl = null; const semaphore1 = semaphoreFactory.create(key, { limit, ttl, }); await semaphore1.acquire(); const semaphore2 = semaphoreFactory.create(key, { limit, ttl, }); const result = await semaphore2.acquire(); expect(result).toBe(true); }); test("Should return false when limit is reached", async () => { const key = "a"; const limit = 2; const ttl = null; const semaphore1 = semaphoreFactory.create(key, { limit, ttl, }); await semaphore1.acquire(); const semaphore2 = semaphoreFactory.create(key, { limit, ttl, }); await semaphore2.acquire(); const semaphore3 = semaphoreFactory.create(key, { limit, ttl, }); const result = await semaphore3.acquire(); expect(result).toBe(false); }); test("Should return true when one slot is expired", async () => { const key = "a"; const limit = 2; const ttl1 = null; const semaphore1 = semaphoreFactory.create(key, { limit, ttl: ttl1, }); await semaphore1.acquire(); const ttl2 = TimeSpan.fromMilliseconds(50); const semaphore2 = semaphoreFactory.create(key, { limit, ttl: ttl2, }); await semaphore2.acquire(); await delayWithBuffer(ttl2); const ttl3 = null; const semaphore3 = semaphoreFactory.create(key, { ttl: ttl3, limit, }); const result = await semaphore3.acquire(); expect(result).toBe(true); }); test("Should return true when slot exists, is unexpireable and acquired multiple times", async () => { const key = "a"; const limit = 2; const ttl = null; const semaphore = semaphoreFactory.create(key, { limit, ttl, }); await semaphore.acquire(); const result = await semaphore.acquire(); expect(result).toBe(true); }); test("Should return true when slot exists, is unexpired and acquired multiple times", async () => { const key = "a"; const limit = 2; const ttl = TimeSpan.fromMilliseconds(50); const semaphore = semaphoreFactory.create(key, { limit, ttl, }); await semaphore.acquire(); const result = await semaphore.acquire(); expect(result).toBe(true); }); test("Should not acquire a slot when slot exists, is unexpireable and acquired multiple times", async () => { const key = "a"; const limit = 2; const ttl = null; const semaphore1 = semaphoreFactory.create(key, { limit, ttl, }); await semaphore1.acquire(); await semaphore1.acquire(); const semaphore2 = semaphoreFactory.create(key, { limit, ttl, }); const result = await semaphore2.acquire(); expect(result).toBe(true); }); test("Should not acquire a slot when slot exists, is unexpired and acquired multiple times", async () => { const key = "a"; const limit = 2; const ttl = TimeSpan.fromMilliseconds(50); const semaphore1 = semaphoreFactory.create(key, { limit, ttl, }); await semaphore1.acquire(); await semaphore1.acquire(); const semaphore2 = semaphoreFactory.create(key, { ttl, limit, }); const result = await semaphore2.acquire(); expect(result).toBe(true); }); test("Should not update limit when slot count is more than 0", async () => { const key = "a"; const limit = 2; const ttl = null; const semaphore1 = semaphoreFactory.create(key, { limit, ttl, }); await semaphore1.acquire(); const newLimit = 3; const semaphore2 = semaphoreFactory.create(key, { limit: newLimit, ttl, }); await semaphore2.acquire(); const semaphore3 = semaphoreFactory.create(key, { limit: newLimit, ttl, }); const result1 = await semaphore3.acquire(); expect(result1).toBe(false); const state = (await semaphore3.getState()); expect(state.limit).toBe(limit); }); }); describe("method: acquireOrFail", () => { test("Should not throw error when key doesnt exists", async () => { const key = "a"; const limit = 2; const ttl = null; const result = semaphoreFactory .create(key, { limit, ttl, }) .acquireOrFail(); await expect(result).resolves.toBeUndefined(); }); test("Should not throw error when key exists and slot is expired", async () => { const key = "a"; const limit = 2; const ttl = TimeSpan.fromMilliseconds(50); const semaphore = semaphoreFactory.create(key, { limit, ttl, }); await semaphore.acquire(); await delayWithBuffer(ttl); const result = semaphore.acquireOrFail(); await expect(result).resolves.toBeUndefined(); }); test("Should not throw error when limit is not reached", async () => { const key = "a"; const limit = 2; const ttl = null; const semaphore1 = semaphoreFactory.create(key, { limit, ttl, }); await semaphore1.acquire(); const semaphore2 = semaphoreFactory.create(key, { limit, ttl, }); const result = semaphore2.acquireOrFail(); await expect(result).resolves.toBeUndefined(); }); test("Should throw LimitReachedSemaphoreError when limit is reached", async () => { const key = "a"; const limit = 2; const ttl = null; const semaphore1 = semaphoreFactory.create(key, { limit, ttl, }); await semaphore1.acquire(); const semaphore2 = semaphoreFactory.create(key, { limit, ttl, }); await semaphore2.acquire(); const semaphore3 = semaphoreFactory.create(key, { limit, ttl, }); const result = semaphore3.acquireOrFail(); await expect(result).rejects.toBeInstanceOf(LimitReachedSemaphoreError); }); test("Should not throw error when one slot is expired", async () => { const key = "a"; const limit = 2; const ttl1 = null; const semaphore1 = semaphoreFactory.create(key, { limit, ttl: ttl1, }); await semaphore1.acquire(); const ttl2 = TimeSpan.fromMilliseconds(50); const semaphore2 = semaphoreFactory.create(key, { limit, ttl: ttl2, }); await semaphore2.acquire(); await delayWithBuffer(ttl2); const ttl3 = null; const semaphore3 = semaphoreFactory.create(key, { ttl: ttl3, limit, }); const result = semaphore3.acquireOrFail(); await expect(result).resolves.toBeUndefined(); }); test("Should not throw error when slot exists, is unexpireable and acquired multiple times", async () => { const key = "a"; const limit = 2; const ttl = null; const semaphore = semaphoreFactory.create(key, { limit, ttl, }); await semaphore.acquire(); const result = semaphore.acquireOrFail(); await expect(result).resolves.toBeUndefined(); }); test("Should not throw error when slot exists, is unexpired and acquired multiple times", async () => { const key = "a"; const limit = 2; const ttl = TimeSpan.fromMilliseconds(50); const semaphore = semaphoreFactory.create(key, { limit, ttl, }); await semaphore.acquire(); const result = semaphore.acquireOrFail(); await expect(result).resolves.toBeUndefined(); }); test("Should not acquire a slot when slot exists, is unexpireable and acquired multiple times", async () => { const key = "a"; const limit = 2; const ttl = null; const semaphore1 = semaphoreFactory.create(key, { limit, ttl, }); await semaphore1.acquire(); await semaphore1.acquire(); const semaphore2 = semaphoreFactory.create(key, { limit, ttl, }); const result = semaphore2.acquireOrFail(); await expect(result).resolves.toBeUndefined(); }); test("Should not acquire a slot when slot exists, is unexpired and acquired multiple times", async () => { const key = "a"; const limit = 2; const ttl = TimeSpan.fromMilliseconds(50); const semaphore1 = semaphoreFactory.create(key, { limit, ttl, }); await semaphore1.acquire(); await semaphore1.acquire(); const semaphore2 = semaphoreFactory.create(key, { ttl, limit, }); const result = semaphore2.acquireOrFail(); await expect(result).resolves.toBeUndefined(); }); test("Should not update limit when slot count is more than 0", async () => { const key = "a"; const limit = 2; const ttl = null; const semaphore1 = semaphoreFactory.create(key, { limit, ttl, }); await semaphore1.acquire(); const newLimit = 3; const semaphore2 = semaphoreFactory.create(key, { limit: newLimit, ttl, }); await semaphore2.acquire(); const semaphore3 = semaphoreFactory.create(key, { limit: newLimit, ttl, }); const result1 = semaphore3.acquireOrFail(); await expect(result1).rejects.toBeInstanceOf(LimitReachedSemaphoreError); const state = (await semaphore3.getState()); expect(state.limit).toBe(limit); }); }); describe("method: release", () => { test("Should return false when key doesnt exists", async () => { const key = "a"; const limit = 2; const ttl = null; await semaphoreFactory .create(key, { limit, ttl, }) .acquire(); const noneExistingKey = "c"; const result = await semaphoreFactory .create(noneExistingKey, { limit, ttl, }) .release(); expect(result).toBe(false); }); test("Should return false when slot doesnt exists", async () => { const key = "a"; const limit = 2; const ttl = null; await semaphoreFactory .create(key, { limit, ttl, }) .acquire(); const noneExistingSlotId = "1"; const result = await semaphoreFactory .create(key, { limit, ttl, slotId: noneExistingSlotId, }) .release(); expect(result).toBe(false); }); test("Should return false when slot is expired", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const limit = 2; const semaphore = semaphoreFactory.create(key, { ttl, limit, }); await semaphore.acquire(); await delayWithBuffer(ttl); const result = await semaphore.release(); expect(result).toBe(false); }); test("Should return true when slot exists and is unexpired", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const limit = 2; const semaphore = semaphoreFactory.create(key, { ttl, limit, }); await semaphore.acquire(); const result = await semaphore.release(); expect(result).toBe(true); }); test("Should return true when slot exists and is unexpireable", async () => { const key = "a"; const ttl = null; const limit = 2; const semaphore = semaphoreFactory.create(key, { ttl, limit, }); await semaphore.acquire(); const result = await semaphore.release(); expect(result).toBe(true); }); test("Should update limit when slot count is 0", async () => { const key = "a"; const limit = 2; const ttl = null; const semaphore1 = semaphoreFactory.create(key, { ttl, limit, }); await semaphore1.acquire(); const semaphore2 = semaphoreFactory.create(key, { ttl, limit, }); await semaphore2.acquire(); await semaphore1.release(); await semaphore2.release(); const newLimit = 3; const semaphore3 = semaphoreFactory.create(key, { ttl, limit: newLimit, }); await semaphore3.acquire(); const semaphore4 = semaphoreFactory.create(key, { ttl, limit: newLimit, }); await semaphore4.acquire(); const semaphore5 = semaphoreFactory.create(key, { ttl, limit: newLimit, }); const result1 = await semaphore5.acquire(); expect(result1).toBe(true); const state = (await semaphore5.getState()); expect(state.limit).toBe(newLimit); const semaphore6 = semaphoreFactory.create(key, { ttl, limit: newLimit, }); const result3 = await semaphore6.acquire(); expect(result3).toBe(false); }); test("Should decrement slot count when one slot is released", async () => { const key = "a"; const limit = 2; const ttl = null; const semaphore1 = semaphoreFactory.create(key, { ttl, limit, }); await semaphore1.acquire(); const semaphore2 = semaphoreFactory.create(key, { ttl, limit, }); await semaphore2.acquire(); await semaphore1.release(); await semaphore2.release(); const semaphore3 = semaphoreFactory.create(key, { ttl, limit, }); const result1 = await semaphore3.acquire(); expect(result1).toBe(true); const semaphore4 = semaphoreFactory.create(key, { ttl, limit, }); const result2 = await semaphore4.acquire(); expect(result2).toBe(true); }); }); describe("method: releaseOrFail", () => { test("Should throw FailedReleaseSemaphoreError when key doesnt exists", async () => { const key = "a"; const limit = 2; const ttl = null; await semaphoreFactory .create(key, { limit, ttl, }) .acquire(); const noneExistingKey = "c"; const result = semaphoreFactory .create(noneExistingKey, { limit, ttl, }) .releaseOrFail(); await expect(result).rejects.toBeInstanceOf(FailedReleaseSemaphoreError); }); test("Should throw FailedReleaseSemaphoreError when slot doesnt exists", async () => { const key = "a"; const limit = 2; const ttl = null; await semaphoreFactory .create(key, { limit, ttl, }) .acquire(); const noneExistingSlotId = "1"; const result = semaphoreFactory .create(key, { limit, ttl, slotId: noneExistingSlotId, }) .releaseOrFail(); await expect(result).rejects.toBeInstanceOf(FailedReleaseSemaphoreError); }); test("Should throw FailedReleaseSemaphoreError when slot is expired", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const limit = 2; const semaphore1 = semaphoreFactory.create(key, { ttl, limit, }); await semaphore1.acquire(); await delayWithBuffer(ttl); const result = semaphoreFactory .create(key, { ttl, limit, }) .releaseOrFail(); await expect(result).rejects.toBeInstanceOf(FailedReleaseSemaphoreError); }); test("Should throw FailedReleaseSemaphoreError when slot exists, is expired", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const limit = 2; const semaphore = semaphoreFactory.create(key, { ttl, limit, }); await semaphore.acquire(); await delayWithBuffer(ttl); const result = semaphore.releaseOrFail(); await expect(result).rejects.toBeInstanceOf(FailedReleaseSemaphoreError); }); test("Should not throw error when slot exists and is unexpired", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const limit = 2; const semaphore = semaphoreFactory.create(key, { ttl, limit, }); await semaphore.acquire(); const result = semaphore.releaseOrFail(); await expect(result).resolves.toBeUndefined(); }); test("Should not throw error when slot exists and is unexpireable", async () => { const key = "a"; const ttl = null; const limit = 2; const semaphore = semaphoreFactory.create(key, { ttl, limit, }); await semaphore.acquire(); const result = semaphore.releaseOrFail(); await expect(result).resolves.toBeUndefined(); }); test("Should update limit when slot count is 0", async () => { const key = "a"; const limit = 2; const ttl = null; const semaphore1 = semaphoreFactory.create(key, { ttl, limit, }); await semaphore1.acquire(); const semaphore2 = semaphoreFactory.create(key, { ttl, limit, }); await semaphore2.acquire(); await semaphore1.release(); await semaphore2.releaseOrFail(); const newLimit = 3; const semaphore3 = semaphoreFactory.create(key, { ttl, limit: newLimit, }); await semaphore3.acquire(); const semaphore4 = semaphoreFactory.create(key, { ttl, limit: newLimit, }); await semaphore4.acquire(); const semaphore5 = semaphoreFactory.create(key, { ttl, limit: newLimit, }); const result1 = await semaphore5.acquire(); expect(result1).toBe(true); const state = (await semaphore5.getState()); expect(state.limit).toBe(newLimit); const semaphore6 = semaphoreFactory.create(key, { ttl, limit: newLimit, }); const result3 = await semaphore6.acquire(); expect(result3).toBe(false); }); test("Should decrement slot count when one slot is released", async () => { const key = "a"; const limit = 2; const ttl = null; const semaphore1 = semaphoreFactory.create(key, { ttl, limit, }); await semaphore1.acquire(); const semaphore2 = semaphoreFactory.create(key, { ttl, limit, }); await semaphore2.acquire(); await semaphore1.release(); await semaphore2.releaseOrFail(); const semaphore3 = semaphoreFactory.create(key, { ttl, limit, }); const result1 = await semaphore3.acquire(); expect(result1).toBe(true); const semaphore4 = semaphoreFactory.create(key, { ttl, limit, }); const result2 = await semaphore4.acquire(); expect(result2).toBe(true); }); }); describe("method: forceReleaseAll", () => { test("Should return false when key doesnt exists", async () => { const key = "a"; const limit = 2; const ttl = null; const semaphore1 = semaphoreFactory.create(key, { limit, ttl, }); await semaphore1.acquire(); const noneExistingKey = "c"; const semaphore2 = semaphoreFactory.create(noneExistingKey, { limit, ttl, }); const result = await semaphore2.forceReleaseAll(); expect(result).toBe(false); }); test("Should return false when slot is expired", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const limit = 2; const semaphore = semaphoreFactory.create(key, { limit, ttl, }); await semaphore.acquire(); await delayWithBuffer(ttl); const result = await semaphore.forceReleaseAll(); expect(result).toBe(false); }); test("Should return false when no slots are acquired", async () => { const key = "a"; const ttl = null; const limit = 2; const semaphore1 = semaphoreFactory.create(key, { limit, ttl, }); await semaphore1.acquire(); const semaphore2 = semaphoreFactory.create(key, { limit, ttl, }); await semaphore2.acquire(); await semaphore1.release(); await semaphore2.release(); const result = await semaphore1.forceReleaseAll(); expect(result).toBe(false); }); test("Should return true when at least 1 slot is acquired", async () => { const key = "a"; const ttl = null; const limit = 2; const semaphore = semaphoreFactory.create(key, { limit, ttl, }); awai