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,111 lines (1,110 loc) 145 kB
/** * @module Lock */ import { vi, } from "vitest"; import { KeyAlreadyAcquiredLockError, UnownedRefreshLockError, UnownedReleaseLockError, LOCK_EVENTS, UnrefreshableKeyLockError, } from "../../../lock/contracts/_module-exports.js"; import { RESULT, resultSuccess, } from "../../../utilities/_module-exports.js"; import { TimeSpan } from "../../../utilities/_module-exports.js"; import { NoOpSerdeAdapter } from "../../../serde/implementations/adapters/_module-exports.js"; import { Serde } from "../../../serde/implementations/derivables/_module-exports.js"; import { LazyPromise } from "../../../async/_module-exports.js"; /** * The `lockProviderTestSuite` function simplifies the process of testing your custom implementation of {@link ILock | `ILock`} with `vitest`. * * IMPORT_PATH: `"@daiso-tech/core/lock/test-utilities"` * @group Utilities * @example * ```ts * import { describe, expect, test, beforeEach } from "vitest"; * import { MemoryLockAdapter } from "@daiso-tech/core/lock/adapters"; * import { LockProvider } from "@daiso-tech/core/lock"; * import { EventBus } from "@daiso-tech/core/event-bus"; * import { MemoryEventBusAdapter } from "@daiso-tech/core/event-bus/adapters"; * import { lockProviderTestSuite } from "@daiso-tech/core/lock/test-utilities"; * import { Serde } from "@daiso-tech/core/serde"; * import { SuperJsonSerdeAdapter } from "@daiso-tech/core/serde/adapters"; * import type { ILockData } from "@daiso-tech/core/lock/contracts"; * * describe("class: LockProvider", () => { * const serde = new Serde(new SuperJsonSerdeAdapter()); * let map: Map<string, ILockData>; * lockProviderTestSuite({ * createLockProvider: () => { * return new LockProvider({ * serde, * adapter: new MemoryLockAdapter(), * }); * }, * beforeEach, * describe, * expect, * test, * serde, * }); * }); * ``` */ export function lockProviderTestSuite(settings) { const { expect, test, createLockProvider, describe, beforeEach, serde = new Serde(new NoOpSerdeAdapter()), } = settings; let lockProvider; beforeEach(async () => { lockProvider = await createLockProvider(); }); async function delay(time) { await LazyPromise.delay(time.addMilliseconds(10)); } const RETURN_VALUE = "RETURN_VALUE"; describe("Api tests:", () => { describe("method: run", () => { test("Should call acquire method", async () => { const key = "a"; const ttl = null; const lock = lockProvider.create(key, { ttl, }); const acquireSpy = vi.spyOn(lock, "acquire"); await lock.run(() => { return Promise.resolve(RETURN_VALUE); }); expect(acquireSpy).toHaveBeenCalledTimes(1); }); test("Should call acquire before release method", async () => { const key = "a"; const ttl = null; const lock = lockProvider.create(key, { ttl, }); const acquireSpy = vi.spyOn(lock, "acquire"); const releaseSpy = vi.spyOn(lock, "release"); await lock.run(() => { return Promise.resolve(RETURN_VALUE); }); expect(acquireSpy).toHaveBeenCalledBefore(releaseSpy); }); test("Should call release method", async () => { const key = "a"; const ttl = null; const lock = lockProvider.create(key, { ttl, }); const releaseSpy = vi.spyOn(lock, "release"); await lock.run(() => { return Promise.resolve(RETURN_VALUE); }); expect(releaseSpy).toHaveBeenCalledTimes(1); }); test("Should call release after acquire method", async () => { const key = "a"; const ttl = null; const lock = lockProvider.create(key, { ttl, }); const releaseSpy = vi.spyOn(lock, "release"); const acquireSpy = vi.spyOn(lock, "acquire"); await lock.run(() => { 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 lock = lockProvider.create(key, { ttl, }); const releaseSpy = vi.spyOn(lock, "release"); try { await lock.run(() => { return Promise.reject(new Error()); }); } catch { /* EMPTY */ } expect(releaseSpy).toHaveBeenCalledTimes(1); }); test("Should propagate thrown error", async () => { const key = "a"; const ttl = null; const lock = lockProvider.create(key, { ttl, }); class CustomError extends Error { } const error = lock.run(() => { return Promise.reject(new CustomError()); }); await expect(error).rejects.toBeInstanceOf(CustomError); }); test("Should call handler function when key doesnt exists", async () => { const key = "a"; const ttl = null; const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); await lockProvider .create(key, { ttl, }) .run(handlerFn); expect(handlerFn).toHaveBeenCalledTimes(1); }); test("Should call handler function when key is expired", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); await lockProvider.create(key, { ttl }).acquire(); await delay(ttl); const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); await lockProvider.create(key, { ttl }).run(handlerFn); expect(handlerFn).toHaveBeenCalledTimes(1); }); test("Should not call handler function when key is unexpireable and acquired by same owner", async () => { const key = "a"; const ttl = null; const lock = lockProvider.create(key, { ttl }); await lock.acquire(); const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); await lock.run(handlerFn); expect(handlerFn).toHaveBeenCalledTimes(0); }); test("Should not call handler function when key is unexpired and acquired by same owner", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const lock = lockProvider.create(key, { ttl, }); await lock.acquire(); const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); await lock.run(handlerFn); expect(handlerFn).toHaveBeenCalledTimes(0); }); test("Should not call handler function when key is unexpireable and acquired by different owner", async () => { const key = "a"; const ttl = null; await lockProvider.create(key, { ttl }).acquire(); const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); await lockProvider.create(key, { ttl }).run(handlerFn); expect(handlerFn).not.toHaveBeenCalled(); }); test("Should not call handler function when key is unexpired and acquired by different owner", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); await lockProvider.create(key, { ttl }).acquire(); const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); await lockProvider.create(key, { ttl }).run(handlerFn); expect(handlerFn).not.toHaveBeenCalled(); }); test("Should return ResultSuccess<string> when key doesnt exists", async () => { const key = "a"; const ttl = null; const result = await lockProvider .create(key, { ttl, }) .run(() => { return Promise.resolve(RETURN_VALUE); }); expect(result).toEqual(resultSuccess(RETURN_VALUE)); }); test("Should return ResultSuccess<string> when key is expired", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); await lockProvider.create(key, { ttl }).acquire(); await delay(ttl); const result = await lockProvider .create(key, { ttl }) .run(() => { return Promise.resolve(RETURN_VALUE); }); expect(result).toEqual(resultSuccess(RETURN_VALUE)); }); test("Should return ResultFailure<KeyAlreadyAcquiredLockError> when key is unexpireable and acquired by same owner", async () => { const key = "a"; const ttl = null; const lock = lockProvider.create(key, { ttl }); await lock.acquire(); const result = await lock.run(() => { return Promise.resolve(RETURN_VALUE); }); expect(result.type).toBe(RESULT.FAILURE); expect(result.error).toBeInstanceOf(KeyAlreadyAcquiredLockError); }); test("Should return ResultFailure<KeyAlreadyAcquiredLockError> when key is unexpired and acquired by same owner", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const lock = lockProvider.create(key, { ttl, }); await lock.acquire(); const result = await lock.run(() => { return Promise.resolve(RETURN_VALUE); }); expect(result.type).toBe(RESULT.FAILURE); expect(result.error).toBeInstanceOf(KeyAlreadyAcquiredLockError); }); test("Should return ResultFailure<KeyAlreadyAcquiredLockError> when key is unexpireable and acquired by different owner", async () => { const key = "a"; const ttl = null; await lockProvider.create(key, { ttl }).acquire(); const result = await lockProvider .create(key, { ttl }) .run(() => { return Promise.resolve(RETURN_VALUE); }); expect(result.type).toBe(RESULT.FAILURE); expect(result.error).toBeInstanceOf(KeyAlreadyAcquiredLockError); }); test("Should return ResultFailure<KeyAlreadyAcquiredLockError> when key is unexpired and acquired by different owner", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); await lockProvider.create(key, { ttl }).acquire(); const result = await lockProvider .create(key, { ttl }) .run(() => { return Promise.resolve(RETURN_VALUE); }); expect(result.type).toBe(RESULT.FAILURE); expect(result.error).toBeInstanceOf(KeyAlreadyAcquiredLockError); }); }); describe("method: runOrFail", () => { test("Should call acquireOrFail method", async () => { const key = "a"; const ttl = null; const lock = lockProvider.create(key, { ttl, }); const acquireSpy = vi.spyOn(lock, "acquireOrFail"); await lock.runOrFail(() => { return Promise.resolve(RETURN_VALUE); }); expect(acquireSpy).toHaveBeenCalledTimes(1); }); test("Should call acquireOrFail before release method", async () => { const key = "a"; const ttl = null; const lock = lockProvider.create(key, { ttl, }); const acquireSpy = vi.spyOn(lock, "acquireOrFail"); const releaseSpy = vi.spyOn(lock, "release"); await lock.runOrFail(() => { return Promise.resolve(RETURN_VALUE); }); expect(acquireSpy).toHaveBeenCalledBefore(releaseSpy); }); test("Should call release method", async () => { const key = "a"; const ttl = null; const lock = lockProvider.create(key, { ttl, }); const releaseSpy = vi.spyOn(lock, "release"); await lock.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 lock = lockProvider.create(key, { ttl, }); const releaseSpy = vi.spyOn(lock, "release"); const acquireSpy = vi.spyOn(lock, "acquireOrFail"); await lock.runOrFail(() => { 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 lock = lockProvider.create(key, { ttl, }); const releaseSpy = vi.spyOn(lock, "release"); try { await lock.runOrFail(() => { return Promise.reject(new Error()); }); } catch { /* EMPTY */ } expect(releaseSpy).toHaveBeenCalledTimes(1); }); test("Should propagate thrown error", async () => { const key = "a"; const ttl = null; const lock = lockProvider.create(key, { ttl, }); class CustomError extends Error { } const error = lock.runOrFail(() => { return Promise.reject(new CustomError()); }); await expect(error).rejects.toBeInstanceOf(CustomError); }); test("Should call handler function when key doesnt exists", async () => { const key = "a"; const ttl = null; const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); await lockProvider .create(key, { ttl, }) .runOrFail(handlerFn); expect(handlerFn).toHaveBeenCalledTimes(1); }); test("Should call handler function when key is expired", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); await lockProvider.create(key, { ttl }).acquire(); await delay(ttl); const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); await lockProvider.create(key, { ttl }).runOrFail(handlerFn); expect(handlerFn).toHaveBeenCalledTimes(1); }); test("Should not call handler function when key is unexpireable and acquired by same owner", async () => { const key = "a"; const ttl = null; const lock = lockProvider.create(key, { ttl }); await lock.acquire(); const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); try { await lock.runOrFail(handlerFn); } catch { /* EMPTY */ } expect(handlerFn).toHaveBeenCalledTimes(0); }); test("Should not call handler function when key is unexpired and acquired by same owner", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const lock = lockProvider.create(key, { ttl, }); await lock.acquire(); const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); try { await lock.runOrFail(handlerFn); } catch { /* EMPTY */ } expect(handlerFn).toHaveBeenCalledTimes(0); }); test("Should not call handler function when key is unexpireable and acquired by different owner", async () => { const key = "a"; const ttl = null; await lockProvider.create(key, { ttl }).acquire(); const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); try { await lockProvider .create(key, { ttl }) .runOrFail(handlerFn); } catch { /* EMPTY */ } expect(handlerFn).not.toHaveBeenCalled(); }); test("Should not call handler function when key is unexpired and acquired by different owner", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); await lockProvider.create(key, { ttl }).acquire(); const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); try { await lockProvider .create(key, { ttl }) .runOrFail(handlerFn); } catch { /* EMPTY */ } expect(handlerFn).not.toHaveBeenCalled(); }); test("Should return value when key doesnt exists", async () => { const key = "a"; const ttl = null; const result = await lockProvider .create(key, { ttl, }) .runOrFail(() => { return Promise.resolve(RETURN_VALUE); }); expect(result).toBe(RETURN_VALUE); }); test("Should return value when key is expired", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); await lockProvider.create(key, { ttl }).acquire(); await delay(ttl); const result = await lockProvider .create(key, { ttl }) .runOrFail(() => { return Promise.resolve(RETURN_VALUE); }); expect(result).toBe(RETURN_VALUE); }); test("Should throw KeyAlreadyAcquiredLockError when key is unexpireable and acquired by same owner", async () => { const key = "a"; const ttl = null; const lock = lockProvider.create(key, { ttl }); await lock.acquire(); const result = lock.runOrFail(() => { return Promise.resolve(RETURN_VALUE); }); await expect(result).rejects.toBeInstanceOf(KeyAlreadyAcquiredLockError); }); test("Should throw KeyAlreadyAcquiredLockError when key is unexpired and acquired by same owner", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const lock = lockProvider.create(key, { ttl, }); await lock.acquire(); const result = lock.runOrFail(() => { return Promise.resolve(RETURN_VALUE); }); await expect(result).rejects.toBeInstanceOf(KeyAlreadyAcquiredLockError); }); test("Should throw KeyAlreadyAcquiredLockError when key is unexpireable and acquired by different owner", async () => { const key = "a"; const ttl = null; await lockProvider.create(key, { ttl }).acquire(); const result = lockProvider .create(key, { ttl }) .runOrFail(() => { return Promise.resolve(RETURN_VALUE); }); await expect(result).rejects.toBeInstanceOf(KeyAlreadyAcquiredLockError); }); test("Should throw KeyAlreadyAcquiredLockError when key is unexpired and acquired by different owner", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); await lockProvider.create(key, { ttl }).acquire(); const result = lockProvider .create(key, { ttl }) .runOrFail(() => { return Promise.resolve(RETURN_VALUE); }); await expect(result).rejects.toBeInstanceOf(KeyAlreadyAcquiredLockError); }); }); describe("method: runBlocking", () => { test("Should call acquireBlocking method", async () => { const key = "a"; const ttl = null; const lock = lockProvider.create(key, { ttl, }); const acquireSpy = vi.spyOn(lock, "acquireBlocking"); await lock.runBlocking(() => { return Promise.resolve(RETURN_VALUE); }, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); expect(acquireSpy).toHaveBeenCalledTimes(1); }); test("Should call acquireBlocking before release method", async () => { const key = "a"; const ttl = null; const lock = lockProvider.create(key, { ttl, }); const acquireSpy = vi.spyOn(lock, "acquireBlocking"); const releaseSpy = vi.spyOn(lock, "release"); await lock.runBlocking(() => { return Promise.resolve(RETURN_VALUE); }, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); expect(acquireSpy).toHaveBeenCalledBefore(releaseSpy); }); test("Should call release method", async () => { const key = "a"; const ttl = null; const lock = lockProvider.create(key, { ttl, }); const releaseSpy = vi.spyOn(lock, "release"); await lock.runBlocking(() => { return Promise.resolve(RETURN_VALUE); }, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); expect(releaseSpy).toHaveBeenCalledTimes(1); }); test("Should call release after acquireBlocking method", async () => { const key = "a"; const ttl = null; const lock = lockProvider.create(key, { ttl, }); const releaseSpy = vi.spyOn(lock, "release"); const acquireSpy = vi.spyOn(lock, "acquireBlocking"); await lock.runBlocking(() => { return Promise.resolve(RETURN_VALUE); }, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); expect(releaseSpy).toHaveBeenCalledAfter(acquireSpy); }); test("Should call release when an error is thrown", async () => { const key = "a"; const ttl = null; const lock = lockProvider.create(key, { ttl, }); const releaseSpy = vi.spyOn(lock, "release"); try { await lock.runBlocking(() => { return Promise.reject(new Error()); }, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); } catch { /* EMPTY */ } expect(releaseSpy).toHaveBeenCalledTimes(1); }); test("Should propagate thrown error", async () => { const key = "a"; const ttl = null; const lock = lockProvider.create(key, { ttl, }); class CustomError extends Error { } const error = lock.runBlocking(() => { return Promise.reject(new CustomError()); }, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); await expect(error).rejects.toBeInstanceOf(CustomError); }); test("Should call handler function when key doesnt exists", async () => { const key = "a"; const ttl = null; const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); await lockProvider .create(key, { ttl, }) .runBlocking(handlerFn, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); expect(handlerFn).toHaveBeenCalledTimes(1); }); test("Should call handler function when key is expired", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); await lockProvider.create(key, { ttl }).acquire(); await delay(ttl); const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); await lockProvider.create(key, { ttl }).runBlocking(handlerFn, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); expect(handlerFn).toHaveBeenCalledTimes(1); }); test("Should not call handler function when key is unexpireable and acquired by same owner", async () => { const key = "a"; const ttl = null; const lock = lockProvider.create(key, { ttl }); await lock.acquire(); const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); await lock.runBlocking(handlerFn, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); expect(handlerFn).toHaveBeenCalledTimes(0); }); test("Should not call handler function when key is unexpired and acquired by same owner", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const lock = lockProvider.create(key, { ttl, }); await lock.acquire(); const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); await lock.runBlocking(handlerFn, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); expect(handlerFn).toHaveBeenCalledTimes(0); }); test("Should not call handler function when key is unexpireable and acquired by different owner", async () => { const key = "a"; const ttl = null; await lockProvider.create(key, { ttl }).acquire(); const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); await lockProvider.create(key, { ttl }).runBlocking(handlerFn, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); expect(handlerFn).not.toHaveBeenCalled(); }); test("Should not call handler function when key is unexpired and acquired by different owner", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); await lockProvider.create(key, { ttl }).acquire(); const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); await lockProvider.create(key, { ttl }).runBlocking(handlerFn, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); expect(handlerFn).not.toHaveBeenCalled(); }); test("Should return ResultSuccess<string> when key doesnt exists", async () => { const key = "a"; const ttl = null; const result = await lockProvider .create(key, { ttl, }) .runBlocking(() => { return Promise.resolve(RETURN_VALUE); }, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); expect(result).toEqual(resultSuccess(RETURN_VALUE)); }); test("Should return ResultSuccess<string> when key is expired", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); await lockProvider.create(key, { ttl }).acquire(); await delay(ttl); const result = await lockProvider .create(key, { ttl }) .runBlocking(() => { return Promise.resolve(RETURN_VALUE); }, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); expect(result).toEqual(resultSuccess(RETURN_VALUE)); }); test("Should return ResultFailure<KeyAlreadyAcquiredLockError> when key is unexpireable and acquired by same owner", async () => { const key = "a"; const ttl = null; const lock = lockProvider.create(key, { ttl }); await lock.acquire(); const result = await lock.runBlocking(() => { return Promise.resolve(RETURN_VALUE); }, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); expect(result.type).toBe(RESULT.FAILURE); expect(result.error).toBeInstanceOf(KeyAlreadyAcquiredLockError); }); test("Should return ResultFailure<KeyAlreadyAcquiredLockError> when key is unexpired and acquired by same owner", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const lock = lockProvider.create(key, { ttl, }); await lock.acquire(); const result = await lock.runBlocking(() => { return Promise.resolve(RETURN_VALUE); }, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); expect(result.type).toBe(RESULT.FAILURE); expect(result.error).toBeInstanceOf(KeyAlreadyAcquiredLockError); }); test("Should return ResultFailure<KeyAlreadyAcquiredLockError> when key is unexpireable and acquired by different owner", async () => { const key = "a"; const ttl = null; await lockProvider.create(key, { ttl }).acquire(); const result = await lockProvider .create(key, { ttl }) .runBlocking(() => { return Promise.resolve(RETURN_VALUE); }, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); expect(result.type).toBe(RESULT.FAILURE); expect(result.error).toBeInstanceOf(KeyAlreadyAcquiredLockError); }); test("Should return ResultFailure<KeyAlreadyAcquiredLockError> when key is unexpired and acquired by different owner", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); await lockProvider.create(key, { ttl }).acquire(); const result = await lockProvider .create(key, { ttl }) .runBlocking(() => { return Promise.resolve(RETURN_VALUE); }, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); expect(result.type).toBe(RESULT.FAILURE); expect(result.error).toBeInstanceOf(KeyAlreadyAcquiredLockError); }); test("Should retry acquire the lock", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const lock1 = lockProvider.create(key, { ttl, }); await lock1.acquire(); let index = 0; await lockProvider.addListener(LOCK_EVENTS.UNAVAILABLE, (_event) => { index++; }); const lock2 = lockProvider.create(key, { ttl, }); await lock2.runBlocking(() => { return Promise.resolve(RETURN_VALUE); }, { time: TimeSpan.fromMilliseconds(55), interval: TimeSpan.fromMilliseconds(5), }); expect(index).toBeGreaterThan(1); }); }); describe("method: runBlockingOrFail", () => { test("Should call acquireBlockingOrFail method", async () => { const key = "a"; const ttl = null; const lock = lockProvider.create(key, { ttl, }); const acquireSpy = vi.spyOn(lock, "acquireBlockingOrFail"); await lock.runBlockingOrFail(() => { return Promise.resolve(RETURN_VALUE); }, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); expect(acquireSpy).toHaveBeenCalledTimes(1); }); test("Should call acquireBlockingOrFail before release method", async () => { const key = "a"; const ttl = null; const lock = lockProvider.create(key, { ttl, }); const acquireSpy = vi.spyOn(lock, "acquireBlockingOrFail"); const releaseSpy = vi.spyOn(lock, "release"); await lock.runBlockingOrFail(() => { return Promise.resolve(RETURN_VALUE); }, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); expect(acquireSpy).toHaveBeenCalledBefore(releaseSpy); }); test("Should call release method", async () => { const key = "a"; const ttl = null; const lock = lockProvider.create(key, { ttl, }); const releaseSpy = vi.spyOn(lock, "release"); await lock.runBlockingOrFail(() => { return Promise.resolve(RETURN_VALUE); }, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); expect(releaseSpy).toHaveBeenCalledTimes(1); }); test("Should call release after acquireBlockingOrFail method", async () => { const key = "a"; const ttl = null; const lock = lockProvider.create(key, { ttl, }); const releaseSpy = vi.spyOn(lock, "release"); const acquireSpy = vi.spyOn(lock, "acquireBlockingOrFail"); await lock.runBlockingOrFail(() => { return Promise.resolve(RETURN_VALUE); }, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); expect(releaseSpy).toHaveBeenCalledAfter(acquireSpy); }); test("Should call release when an error is thrown", async () => { const key = "a"; const ttl = null; const lock = lockProvider.create(key, { ttl, }); const releaseSpy = vi.spyOn(lock, "release"); try { await lock.runBlockingOrFail(() => { return Promise.reject(new Error()); }, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); } catch { /* EMPTY */ } expect(releaseSpy).toHaveBeenCalledTimes(1); }); test("Should propagate thrown error", async () => { const key = "a"; const ttl = null; const lock = lockProvider.create(key, { ttl, }); class CustomError extends Error { } const error = lock.runBlockingOrFail(() => { return Promise.reject(new CustomError()); }, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); await expect(error).rejects.toBeInstanceOf(CustomError); }); test("Should call handler function when key doesnt exists", async () => { const key = "a"; const ttl = null; const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); await lockProvider .create(key, { ttl, }) .runBlockingOrFail(handlerFn, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); expect(handlerFn).toHaveBeenCalledTimes(1); }); test("Should call handler function when key is expired", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); await lockProvider.create(key, { ttl }).acquire(); await delay(ttl); const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); await lockProvider .create(key, { ttl }) .runBlockingOrFail(handlerFn, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); expect(handlerFn).toHaveBeenCalledTimes(1); }); test("Should not call handler function when key is unexpireable and acquired by same owner", async () => { const key = "a"; const ttl = null; const lock = lockProvider.create(key, { ttl }); await lock.acquire(); const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); try { await lock.runBlockingOrFail(handlerFn, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); } catch { /* EMPTY */ } expect(handlerFn).toHaveBeenCalledTimes(0); }); test("Should not call handler function when key is unexpired and acquired by same owner", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const lock = lockProvider.create(key, { ttl, }); await lock.acquire(); const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); try { await lock.runBlockingOrFail(handlerFn, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); } catch { /* EMPTY */ } expect(handlerFn).toHaveBeenCalledTimes(0); }); test("Should not call handler function when key is unexpireable and acquired by different owner", async () => { const key = "a"; const ttl = null; await lockProvider.create(key, { ttl }).acquire(); const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); try { await lockProvider .create(key, { ttl }) .runBlockingOrFail(handlerFn, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); } catch { /* EMPTY */ } expect(handlerFn).not.toHaveBeenCalled(); }); test("Should not call handler function when key is unexpired and acquired by different owner", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); await lockProvider.create(key, { ttl }).acquire(); const handlerFn = vi.fn(() => { return Promise.resolve(RETURN_VALUE); }); try { await lockProvider .create(key, { ttl }) .runBlockingOrFail(handlerFn, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); } catch { /* EMPTY */ } expect(handlerFn).not.toHaveBeenCalled(); }); test("Should return value when key doesnt exists", async () => { const key = "a"; const ttl = null; const result = await lockProvider .create(key, { ttl, }) .runBlockingOrFail(() => { return Promise.resolve(RETURN_VALUE); }, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); expect(result).toBe(RETURN_VALUE); }); test("Should return value when key is expired", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); await lockProvider.create(key, { ttl }).acquire(); await delay(ttl); const result = await lockProvider .create(key, { ttl }) .runBlockingOrFail(() => { return Promise.resolve(RETURN_VALUE); }, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); expect(result).toBe(RETURN_VALUE); }); test("Should throw KeyAlreadyAcquiredLockError when key is unexpireable and acquired by same owner", async () => { const key = "a"; const ttl = null; const lock = lockProvider.create(key, { ttl }); await lock.acquire(); const result = lock.runBlockingOrFail(() => { return Promise.resolve(RETURN_VALUE); }, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); await expect(result).rejects.toBeInstanceOf(KeyAlreadyAcquiredLockError); }); test("Should throw KeyAlreadyAcquiredLockError when key is unexpired and acquired by same owner", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); const lock = lockProvider.create(key, { ttl, }); await lock.acquire(); const result = lock.runBlockingOrFail(() => { return Promise.resolve(RETURN_VALUE); }, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); await expect(result).rejects.toBeInstanceOf(KeyAlreadyAcquiredLockError); }); test("Should throw KeyAlreadyAcquiredLockError when key is unexpireable and acquired by different owner", async () => { const key = "a"; const ttl = null; await lockProvider.create(key, { ttl }).acquire(); const result = lockProvider .create(key, { ttl }) .runBlockingOrFail(() => { return Promise.resolve(RETURN_VALUE); }, { time: TimeSpan.fromMilliseconds(5), interval: TimeSpan.fromMilliseconds(5), }); await expect(result).rejects.toBeInstanceOf(KeyAlreadyAcquiredLockError); }); test("Should throw KeyAlreadyAcquiredLockError when key is unexpired and acquired by different owner", async () => { const key = "a"; const ttl = TimeSpan.fromMilliseconds(50); await lockProvider.create(key, { ttl }).acquire(); const result = lockProvider .create(key, { ttl }) .runBlockingOrFail(() => { return Promise.resolve(RETURN_VALUE);