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,062 lines 323 kB
/** * @module SharedLock */ import { vi, } from "vitest"; import {} from "../../../event-bus/contracts/_module.js"; import {} from "../../../serde/contracts/_module.js"; import { FailedAcquireWriterLockError, FailedRefreshReaderSemaphoreError, FailedRefreshWriterLockError, FailedReleaseReaderSemaphoreError, FailedReleaseWriterLockError, LimitReachedReaderSemaphoreError, SHARED_LOCK_EVENTS, SHARED_LOCK_STATE, } from "../../../shared-lock/contracts/_module.js"; import { createIsTimeSpanEqualityTester } from "../../../test-utilities/_module.js"; import {} from "../../../time-span/contracts/_module.js"; import { TimeSpan } from "../../../time-span/implementations/_module.js"; import { delay } from "../../../utilities/_module.js"; /** * The `sharedLockFactoryTestSuite` function simplifies the process of testing your custom implementation of {@link ISharedLock | `ISharedLock`} with `vitest`. * * IMPORT_PATH: `"@daiso-tech/core/shared-lock/test-utilities"` * @group Utilities * @example * ```ts * import { describe, expect, test, beforeEach } from "vitest"; * import { MemorySharedLockAdapter } from "@daiso-tech/core/shared-lock/memory-shared-lock-adapter"; * import { SharedLockFactory } from "@daiso-tech/core/shared-lock"; * import { EventBus } from "@daiso-tech/core/event-bus"; * import { MemoryEventBusAdapter } from "@daiso-tech/core/event-bus/memory-event-bus-adapter"; * import { sharedLockFactoryTestSuite } from "@daiso-tech/core/shared-lock/test-utilities"; * import { Serde } from "@daiso-tech/core/serde"; * import { SuperJsonSerdeAdapter } from "@daiso-tech/core/serde/super-json-serde-adapter"; * import type { ISharedLockData } from "@daiso-tech/core/shared-lock/contracts"; * * describe("class: SharedLockFactory", () => { * sharedLockFactoryTestSuite({ * createSharedLockFactory: () => { * const serde = new Serde(new SuperJsonSerdeAdapter()); * const sharedLockFactory = new SharedLockFactory({ * serde, * adapter: new MemorySharedLockAdapter(), * }); * return { sharedLockFactory, serde }; * }, * beforeEach, * describe, * expect, * test, * serde, * }); * }); * ``` */ export function sharedLockFactoryTestSuite(settings) { const { expect, test, createSharedLockFactory, describe, beforeEach: beforeEach_, excludeEventTests = false, excludeSerdeTests = false, retry = 0, delayBuffer = TimeSpan.fromMilliseconds(10), timeSpanEqualityBuffer = TimeSpan.fromMilliseconds(10), eventDispatchWaitTime = TimeSpan.fromMilliseconds(10), } = settings; let sharedLockFactory; let serde; async function delayWithBuffer(ttl) { await delay(TimeSpan.fromTimeSpan(ttl).addTimeSpan(delayBuffer)); } class UnexpectedErrorA extends Error { } const waitForSettings = { interval: TimeSpan.fromTimeSpan(eventDispatchWaitTime).toMilliseconds(), timeout: TimeSpan.fromTimeSpan(eventDispatchWaitTime) .multiply(3) .toMilliseconds(), }; const RETURN_VALUE = "RETURN_VALUE"; describe("ISharedLockFactory tests:", () => { beforeEach_(async () => { const { sharedLockFactory: sharedLockFactory_, serde: serde_ } = await createSharedLockFactory(); sharedLockFactory = sharedLockFactory_; serde = serde_; }); describe("Api tests:", () => { describe("method: runWriterOrFail", () => { test("Should call acquireReaderOrFail method", async () => { const key = "a"; const ttl = null; const limit = 4; const sharedLock = sharedLockFactory.create(key, { ttl, limit, }); const acquireSpy = vi.spyOn(sharedLock, "acquireWriterOrFail"); await sharedLock.runWriterOrFail(() => { return Promise.resolve(RETURN_VALUE); }); expect(acquireSpy).toHaveBeenCalledTimes(1); }); test("Should call acquireReaderOrFail before release method", async () => { const key = "a"; const ttl = null; const limit = 4; const sharedLock = sharedLockFactory.create(key, { ttl, limit, }); const acquireSpy = vi.spyOn(sharedLock, "acquireWriterOrFail"); const releaseSpy = vi.spyOn(sharedLock, "releaseWriter"); await sharedLock.runWriterOrFail(() => { return Promise.resolve(RETURN_VALUE); }); expect(acquireSpy).toHaveBeenCalledBefore(releaseSpy); }); test("Should call release method", async () => { const key = "a"; const ttl = null; const limit = 4; const sharedLock = sharedLockFactory.create(key, { ttl, limit, }); const releaseSpy = vi.spyOn(sharedLock, "releaseWriter"); await sharedLock.runWriterOrFail(() => { return Promise.resolve(RETURN_VALUE); }); expect(releaseSpy).toHaveBeenCalledTimes(1); }); test("Should call release after acquireReaderOrFail method", async () => { const key = "a"; const ttl = null; const limit = 4; const sharedLock = sharedLockFactory.create(key, { ttl, limit, }); const releaseSpy = vi.spyOn(sharedLock, "releaseWriter"); const acquireSpy = vi.spyOn(sharedLock, "acquireWriterOrFail"); await sharedLock.runWriterOrFail(() => { return Promise.resolve(RETURN_VALUE); }); expect(releaseSpy).toHaveBeenCalledAfter(acquireSpy); }); test("Should call release when an error is thrown", async () => { const key = "a"; const ttl = null; const limit = 4; const sharedLock = sharedLockFactory.create(key, { ttl, limit, }); const releaseSpy = vi.spyOn(sharedLock, "releaseWriter"); try { await sharedLock.runWriterOrFail(() => { return Promise.reject(new UnexpectedErrorA()); }); } catch (error) { if (!(error instanceof UnexpectedErrorA)) { throw error; } } expect(releaseSpy).toHaveBeenCalledTimes(1); }); test("Should propagate thrown error", async () => { const key = "a"; const ttl = null; const limit = 4; const sharedLock = sharedLockFactory.create(key, { ttl, limit, }); const error = sharedLock.runWriterOrFail(() => { 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 limit = 4; const ttl = null; const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); await sharedLockFactory .create(key, { ttl, limit, }) .runWriterOrFail(handlerFn); expect(handlerFn).toHaveBeenCalledTimes(1); }); test("Should call handler function when key is expired", async () => { const key = "a"; const limit = 4; const ttl = TimeSpan.fromMilliseconds(50); await sharedLockFactory .create(key, { ttl, limit, }) .acquireWriter(); await delayWithBuffer(ttl); const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); await sharedLockFactory .create(key, { ttl, limit, }) .runWriterOrFail(handlerFn); expect(handlerFn).toHaveBeenCalledTimes(1); }); test("Should call handler function when key is unexpireable and acquired by same shared-lock-id", async () => { const key = "a"; const limit = 4; const ttl = null; const sharedLock = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock.acquireWriter(); const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); await sharedLock.runWriterOrFail(handlerFn); expect(handlerFn).toHaveBeenCalledTimes(1); }); test("Should call handler function when key is unexpired and acquired by same shared-lock-id", async () => { const key = "a"; const limit = 4; const ttl = TimeSpan.fromMilliseconds(50); const sharedLock = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock.acquireWriter(); const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); await sharedLock.runWriterOrFail(handlerFn); expect(handlerFn).toHaveBeenCalledTimes(1); }); test("Should not call handler function when key is unexpireable and acquired by different shared-lock-id", async () => { const key = "a"; const limit = 4; const ttl = null; await sharedLockFactory .create(key, { ttl, limit, }) .acquireWriter(); const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); try { await sharedLockFactory .create(key, { ttl, limit, }) .runWriterOrFail(handlerFn); } catch (error) { if (!(error instanceof FailedAcquireWriterLockError)) { throw error; } } await delay(delayBuffer); expect(handlerFn).not.toHaveBeenCalled(); }); test("Should not call handler function when key is unexpired and acquired by different shared-lock-id", async () => { const key = "a"; const limit = 4; const ttl = TimeSpan.fromMilliseconds(50); await sharedLockFactory .create(key, { ttl, limit, }) .acquireWriter(); const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); try { await sharedLockFactory .create(key, { ttl, limit, }) .runWriterOrFail(handlerFn); } catch (error) { if (!(error instanceof FailedAcquireWriterLockError)) { throw error; } } await delay(delayBuffer); expect(handlerFn).not.toHaveBeenCalled(); }); test("Should return value when key doesnt exists", async () => { const key = "a"; const limit = 4; const ttl = null; const result = await sharedLockFactory .create(key, { ttl, limit, }) .runWriterOrFail(() => { return Promise.resolve(RETURN_VALUE); }); expect(result).toBe(RETURN_VALUE); }); test("Should return value when key is expired", async () => { const key = "a"; const limit = 4; const ttl = TimeSpan.fromMilliseconds(50); await sharedLockFactory .create(key, { ttl, limit, }) .acquireWriter(); await delayWithBuffer(ttl); const result = await sharedLockFactory .create(key, { ttl, limit, }) .runWriterOrFail(() => { return Promise.resolve(RETURN_VALUE); }); expect(result).toBe(RETURN_VALUE); }); test("Should not throw error when key is unexpireable and acquired by same shared-lock-id", async () => { const key = "a"; const limit = 4; const ttl = null; const sharedLock = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock.acquireWriter(); const result = await sharedLock.runWriterOrFail(() => { return Promise.resolve(RETURN_VALUE); }); expect(result).toBe(RETURN_VALUE); }); test("Should not throw error when key is unexpired and acquired by same shared-lock-id", async () => { const key = "a"; const limit = 4; const ttl = TimeSpan.fromMilliseconds(50); const sharedLock = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock.acquireWriter(); const result = await sharedLock.runWriterOrFail(() => { return Promise.resolve(RETURN_VALUE); }); expect(result).toBe(RETURN_VALUE); }); test("Should throw FailedAcquireWriterLockError when key is unexpireable and acquired by different shared-lock-id", async () => { const key = "a"; const limit = 4; const ttl = null; await sharedLockFactory .create(key, { ttl, limit, }) .acquireWriter(); const result = sharedLockFactory .create(key, { ttl, limit, }) .runWriterOrFail(() => { return Promise.resolve(RETURN_VALUE); }); await expect(result).rejects.toBeInstanceOf(FailedAcquireWriterLockError); }); test("Should throw FailedAcquireWriterLockError when key is unexpired and acquired by different shared-lock-id", async () => { const key = "a"; const limit = 4; const ttl = TimeSpan.fromMilliseconds(50); await sharedLockFactory .create(key, { ttl, limit, }) .acquireWriter(); const result = sharedLockFactory .create(key, { ttl, limit, }) .runWriterOrFail(() => { return Promise.resolve(RETURN_VALUE); }); await expect(result).rejects.toBeInstanceOf(FailedAcquireWriterLockError); }); }); describe("method: acquireWriter", () => { test("Should return true when key doesnt exists", async () => { const key = "a"; const ttl = null; const limit = 4; const result = await sharedLockFactory .create(key, { limit, ttl, }) .acquireWriter(); expect(result).toBe(true); }); test("Should return true when key is expired", async () => { const key = "a"; const limit = 4; const ttl = TimeSpan.fromMilliseconds(50); const sharedLock = sharedLockFactory.create(key, { limit, ttl, }); await sharedLock.acquireWriter(); await delayWithBuffer(ttl); const result = await sharedLock.acquireWriter(); expect(result).toBe(true); }); test("Should return true when key is unexpireable and acquired by same shared-lock-id", async () => { const key = "a"; const limit = 4; const ttl = null; const sharedLock = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock.acquireWriter(); const result = await sharedLock.acquireWriter(); expect(result).toBe(true); }); test("Should return true when key is unexpired and acquired by same shared-lock-id", async () => { const key = "a"; const limit = 4; const ttl = TimeSpan.fromMilliseconds(50); const sharedLock = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock.acquireWriter(); const result = await sharedLock.acquireWriter(); expect(result).toBe(true); }); test("Should return false when key is unexpireable and acquired by different shared-lock-id", async () => { const key = "a"; const ttl = null; const limit = 4; const sharedLock1 = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock1.acquireWriter(); const sharedLock2 = sharedLockFactory.create(key, { ttl, limit, }); const result = await sharedLock2.acquireWriter(); expect(result).toBe(false); }); test("Should return false when key is unexpired and acquired by different shared-lock-id", async () => { const key = "a"; const limit = 4; const ttl = TimeSpan.fromMilliseconds(50); const sharedLock1 = sharedLockFactory.create(key, { limit, ttl, }); await sharedLock1.acquireWriter(); const sharedLock2 = sharedLockFactory.create(key, { limit, ttl, }); const result = await sharedLock2.acquireWriter(); expect(result).toBe(false); }); test("Should return false when key is acquired as reader", async () => { const key = "a"; const limit = 2; const ttl = null; const sharedLock = sharedLockFactory.create(key, { limit, ttl, }); await sharedLock.acquireReader(); const result = await sharedLock.acquireWriter(); expect(result).toBe(false); }); test("Should not update state when key is acquired as reader", async () => { const key = "a"; const limit = 3; const ttl = null; const sharedLock = sharedLockFactory.create(key, { limit, ttl, }); await sharedLock.acquireReader(); await sharedLock.acquireWriter(); const state = await sharedLock.getState(); expect(state).toEqual({ type: SHARED_LOCK_STATE.READER_ACQUIRED, limit, remainingTime: ttl, freeSlotsCount: 2, acquiredSlotsCount: 1, acquiredSlots: [sharedLock.id], }); }); }); describe("method: acquireWriterOrFail", () => { test("Should not throw error when key doesnt exists", async () => { const key = "a"; const ttl = null; const limit = 4; const result = sharedLockFactory .create(key, { limit, ttl, }) .acquireWriterOrFail(); await expect(result).resolves.toBeUndefined(); }); test("Should not throw error when key is expired", async () => { const key = "a"; const limit = 4; const ttl = TimeSpan.fromMilliseconds(50); const sharedLock = sharedLockFactory.create(key, { limit, ttl, }); await sharedLock.acquireWriterOrFail(); await delayWithBuffer(ttl); const result = sharedLock.acquireWriterOrFail(); await expect(result).resolves.toBeUndefined(); }); test("Should not throw error when key is unexpireable and acquired by same shared-lock-id", async () => { const key = "a"; const limit = 4; const ttl = null; const sharedLock = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock.acquireWriterOrFail(); const result = sharedLock.acquireWriterOrFail(); await expect(result).resolves.toBeUndefined(); }); test("Should not throw error when key is unexpired and acquired by same shared-lock-id", async () => { const key = "a"; const limit = 4; const ttl = TimeSpan.fromMilliseconds(50); const sharedLock = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock.acquireWriterOrFail(); const result = sharedLock.acquireWriterOrFail(); await expect(result).resolves.toBeUndefined(); }); test("Should throw FailedAcquireWriterLockError when key is unexpireable and acquired by different shared-lock-id", async () => { const key = "a"; const ttl = null; const limit = 4; const sharedLock1 = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock1.acquireWriterOrFail(); const sharedLock2 = sharedLockFactory.create(key, { ttl, limit, }); const result = sharedLock2.acquireWriterOrFail(); await expect(result).rejects.toBeInstanceOf(FailedAcquireWriterLockError); }); test("Should throw FailedAcquireWriterLockError when key is unexpired and acquired by different shared-lock-id", async () => { const key = "a"; const limit = 4; const ttl = TimeSpan.fromMilliseconds(50); const sharedLock1 = sharedLockFactory.create(key, { limit, ttl, }); await sharedLock1.acquireWriterOrFail(); const sharedLock2 = sharedLockFactory.create(key, { limit, ttl, }); const result = sharedLock2.acquireWriterOrFail(); await expect(result).rejects.toBeInstanceOf(FailedAcquireWriterLockError); }); test("Should throw FailedAcquireWriterLockError when key is acquired as reader", async () => { const key = "a"; const limit = 2; const ttl = null; const sharedLock = sharedLockFactory.create(key, { limit, ttl, }); await sharedLock.acquireReader(); const result = sharedLock.acquireWriterOrFail(); await expect(result).rejects.toBeInstanceOf(FailedAcquireWriterLockError); }); test("Should not update state when key is acquired as reader", async () => { const key = "a"; const limit = 3; const ttl = null; const sharedLock = sharedLockFactory.create(key, { limit, ttl, }); await sharedLock.acquireReader(); try { await sharedLock.acquireWriterOrFail(); } catch (error) { if (!(error instanceof FailedAcquireWriterLockError)) { throw error; } } const state = await sharedLock.getState(); expect(state).toEqual({ type: SHARED_LOCK_STATE.READER_ACQUIRED, limit, remainingTime: ttl, freeSlotsCount: 2, acquiredSlotsCount: 1, acquiredSlots: [sharedLock.id], }); }); }); describe("method: releaseWriter", () => { test("Should return false when key doesnt exists", async () => { const key = "a"; const ttl = null; const limit = 4; const sharedLock = sharedLockFactory.create(key, { ttl, limit, }); const result = await sharedLock.releaseWriter(); expect(result).toBe(false); }); test("Should return false when key is unexpireable and released by different shared-lock-id", async () => { const key = "a"; const ttl = null; const limit = 4; const sharedLock1 = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock1.acquireWriter(); const sharedLock2 = sharedLockFactory.create(key, { ttl, limit, }); const result = await sharedLock2.releaseWriter(); expect(result).toBe(false); }); test("Should return false when key is unexpired and released by different shared-lock-id", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const limit = 4; const sharedLock1 = sharedLockFactory.create(key, { limit, ttl, }); await sharedLock1.acquireWriter(); const sharedLock2 = sharedLockFactory.create(key, { limit, ttl, }); const result = await sharedLock2.releaseWriter(); expect(result).toBe(false); }); test("Should return false when key is expired and released by different shared-lock-id", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const limit = 4; const sharedLock1 = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock1.acquireWriter(); await delayWithBuffer(ttl); const sharedLock2 = sharedLockFactory.create(key, { ttl, limit, }); const result = await sharedLock2.releaseWriter(); expect(result).toBe(false); }); test("Should return false when key is expired and released by same shared-lock-id", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const limit = 4; const sharedLock = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock.acquireWriter(); await delayWithBuffer(ttl); const result = await sharedLock.releaseWriter(); expect(result).toBe(false); }); test("Should return true when key is unexpireable and released by same shared-lock-id", async () => { const key = "a"; const ttl = null; const limit = 4; const sharedLock = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock.acquireWriter(); const result = await sharedLock.releaseWriter(); expect(result).toBe(true); }); test("Should return true when key is unexpired and released by same shared-lock-id", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const limit = 4; const sharedLock = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock.acquireWriter(); const result = await sharedLock.releaseWriter(); expect(result).toBe(true); }); test("Should not be reacquirable when key is unexpireable and released by different shared-lock-id", async () => { const key = "a"; const ttl = null; const limit = 4; const sharedLock1 = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock1.acquireWriter(); const sharedLock2 = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock2.releaseWriter(); const result = await sharedLock2.acquireWriter(); expect(result).toBe(false); }); test("Should not be reacquirable when key is unexpired and released by different shared-lock-id", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const limit = 4; const sharedLock1 = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock1.acquireWriter(); const sharedLock2 = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock2.releaseWriter(); const result = await sharedLock2.acquireWriter(); expect(result).toBe(false); }); test("Should be reacquirable when key is unexpireable and released by same shared-lock-id", async () => { const key = "a"; const ttl = null; const limit = 4; const sharedLock1 = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock1.acquireWriter(); await sharedLock1.releaseWriter(); const sharedLock2 = sharedLockFactory.create(key, { ttl, limit, }); const result = await sharedLock2.acquireWriter(); expect(result).toBe(true); }); test("Should be reacquirable when key is unexpired and released by same shared-lock-id", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const limit = 4; const sharedLock1 = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock1.acquireWriter(); await sharedLock1.releaseWriter(); const sharedLock2 = sharedLockFactory.create(key, { ttl, limit, }); const result = await sharedLock2.acquireWriter(); expect(result).toBe(true); }); test("Should return false when key is acquired as reader", async () => { const key = "a"; const limit = 2; const ttl = TimeSpan.fromSeconds(10); const sharedLock = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock.acquireReader(); const result = await sharedLock.releaseWriter(); expect(result).toBe(false); }); test("Should not update state when key is acquired as reader", async () => { const key = "a"; const limit = 3; const ttl = null; const sharedLock = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock.acquireReader(); await sharedLock.releaseWriter(); const state = await sharedLock.getState(); expect(state).toEqual({ type: SHARED_LOCK_STATE.READER_ACQUIRED, limit, remainingTime: ttl, freeSlotsCount: 2, acquiredSlotsCount: 1, acquiredSlots: [sharedLock.id], }); }); }); describe("method: releaseWriterOrFail", () => { test("Should throw FailedReleaseWriterLockError when key doesnt exists", async () => { const key = "a"; const ttl = null; const limit = 4; const sharedLock = sharedLockFactory.create(key, { ttl, limit, }); const result = sharedLock.releaseWriterOrFail(); await expect(result).rejects.toBeInstanceOf(FailedReleaseWriterLockError); }); test("Should throw FailedReleaseWriterLockError when key is unexpireable and released by different shared-lock-id", async () => { const key = "a"; const ttl = null; const limit = 4; const sharedLock1 = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock1.acquireWriter(); const sharedLock2 = sharedLockFactory.create(key, { ttl, limit, }); const result = sharedLock2.releaseWriterOrFail(); await expect(result).rejects.toBeInstanceOf(FailedReleaseWriterLockError); }); test("Should throw FailedReleaseWriterLockError when key is unexpired and released by different shared-lock-id", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const limit = 4; const sharedLock1 = sharedLockFactory.create(key, { limit, ttl, }); await sharedLock1.acquireWriter(); const sharedLock2 = sharedLockFactory.create(key, { limit, ttl, }); const result = sharedLock2.releaseWriterOrFail(); await expect(result).rejects.toBeInstanceOf(FailedReleaseWriterLockError); }); test("Should throw FailedReleaseWriterLockError when key is expired and released by different shared-lock-id", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const limit = 4; const sharedLock1 = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock1.acquireWriter(); await delayWithBuffer(ttl); const sharedLock2 = sharedLockFactory.create(key, { ttl, limit, }); const result = sharedLock2.releaseWriterOrFail(); await expect(result).rejects.toBeInstanceOf(FailedReleaseWriterLockError); }); test("Should throw FailedReleaseWriterLockError when key is expired and released by same shared-lock-id", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const limit = 4; const sharedLock = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock.acquireWriter(); await delayWithBuffer(ttl); const result = sharedLock.releaseWriterOrFail(); await expect(result).rejects.toBeInstanceOf(FailedReleaseWriterLockError); }); test("Should not throw error when key is unexpireable and released by same shared-lock-id", async () => { const key = "a"; const ttl = null; const limit = 4; const sharedLock = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock.acquireWriter(); const result = sharedLock.releaseWriterOrFail(); await expect(result).resolves.toBeUndefined(); }); test("Should not throw error when key is unexpired and released by same shared-lock-id", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const limit = 4; const sharedLock = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock.acquireWriter(); const result = sharedLock.releaseWriterOrFail(); await expect(result).resolves.toBeUndefined(); }); test("Should not be reacquirable when key is unexpireable and released by different shared-lock-id", async () => { const key = "a"; const ttl = null; const limit = 4; const sharedLock1 = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock1.acquireWriter(); const sharedLock2 = sharedLockFactory.create(key, { ttl, limit, }); try { await sharedLock2.releaseWriterOrFail(); } catch (error) { if (!(error instanceof FailedReleaseWriterLockError)) { throw error; } } const result = await sharedLock2.acquireWriter(); expect(result).toBe(false); }); test("Should not be reacquirable when key is unexpired and released by different shared-lock-id", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const limit = 4; const sharedLock1 = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock1.acquireWriter(); const sharedLock2 = sharedLockFactory.create(key, { ttl, limit, }); try { await sharedLock2.releaseWriterOrFail(); } catch (error) { if (!(error instanceof FailedReleaseWriterLockError)) { throw error; } } const result = await sharedLock2.acquireWriter(); expect(result).toBe(false); }); test("Should be reacquirable when key is unexpireable and released by same shared-lock-id", async () => { const key = "a"; const ttl = null; const limit = 4; const sharedLock1 = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock1.acquireWriter(); await sharedLock1.releaseWriterOrFail(); const sharedLock2 = sharedLockFactory.create(key, { ttl, limit, }); const result = await sharedLock2.acquireWriter(); expect(result).toBe(true); }); test("Should be reacquirable when key is unexpired and released by same shared-lock-id", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const limit = 4; const sharedLock1 = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock1.acquireWriter(); await sharedLock1.releaseWriterOrFail(); const sharedLock2 = sharedLockFactory.create(key, { ttl, limit, }); const result = await sharedLock2.acquireWriter(); expect(result).toBe(true); }); test("Should throw FailedReleaseWriterLockError when key is acquired as reader", async () => { const key = "a"; const limit = 2; const ttl = TimeSpan.fromSeconds(10); const sharedLock = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock.acquireReader(); const result = sharedLock.releaseWriterOrFail(); await expect(result).rejects.toBeInstanceOf(FailedReleaseWriterLockError); }); test("Should not update state when key is acquired as reader", async () => { const key = "a"; const limit = 3; const ttl = null; const sharedLock = sharedLockFactory.create(key, { ttl, limit, }); await sharedLock.acquireReader(); try { await sharedLock.releaseWriterOrFail(); } catch (error) { if (!(error instanceof FailedReleaseWriterLockError)) { throw error; } } const state = await sharedLock.getState(); expect(state).toEqual({ type: SHARED_LOCK_STATE.READER_ACQUIRED, limit, remainingTime: ttl, freeSlotsCount: 2, acquiredSlotsCount: 1, acquiredSlots: [sharedLock.id], }); }); }); describe("method: refreshWriter", () => { test("Should return false when key doesnt exists", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(25); const limit = 4; const sharedLock = sharedLockFactory.create(key, { ttl, limit, }); const newTtl = TimeSpan.fromMinutes(1); const result = await sharedLock.refreshWriter(newTtl); expect(result).toBe(false); }); test("Should return false when key is unexpireable and refreshed by different share