@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,048 lines • 172 kB
JavaScript
/**
* @module Lock
*/
import { vi, } from "vitest";
import { FailedAcquireLockError, FailedReleaseLockError, LOCK_EVENTS, FailedRefreshLockError, LOCK_STATE, } from "../../../lock/contracts/_module-exports.js";
import { RESULT, resultSuccess, } from "../../../utilities/_module-exports.js";
import { Task } from "../../../task/_module-exports.js";
import { TimeSpan } from "../../../time-span/implementations/_module-exports.js";
import { TO_MILLISECONDS } from "../../../time-span/contracts/_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/memory-lock-adapter";
* import { LockProvider } from "@daiso-tech/core/lock";
* import { EventBus } from "@daiso-tech/core/event-bus";
* import { MemoryEventBusAdapter } from "@daiso-tech/core/event-bus/memory-event-bus-adapter";
* import { lockProviderTestSuite } from "@daiso-tech/core/lock/test-utilities";
* import { Serde } from "@daiso-tech/core/serde";
* import { SuperJsonSerdeAdapter } from "@daiso-tech/core/serde/super-json-serde-adapter";
* import type { ILockData } from "@daiso-tech/core/lock/contracts";
*
* describe("class: LockProvider", () => {
* lockProviderTestSuite({
* createLockProvider: () => {
* const serde = new Serde(new SuperJsonSerdeAdapter());
* const lockProvider = new LockProvider({
* serde,
* adapter: new MemoryLockAdapter(),
* });
* return { lockProvider, serde };
* },
* beforeEach,
* describe,
* expect,
* test,
* serde,
* });
* });
* ```
*/
export function lockProviderTestSuite(settings) {
const { expect, test, createLockProvider, describe, beforeEach, excludeEventTests = false, excludeSerdeTests = false, } = settings;
let lockProvider;
let serde;
async function delay(time) {
await Task.delay(time.addMilliseconds(10));
}
const RETURN_VALUE = "RETURN_VALUE";
describe("Reusable tests:", () => {
beforeEach(async () => {
const { lockProvider: lockProvider_, serde: serde_ } = await createLockProvider();
lockProvider = lockProvider_;
serde = serde_;
});
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", {
retry: 10,
}, 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 call handler function when key is unexpireable and acquired by same lockId", 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(1);
});
test("Should call handler function when key is unexpired and acquired by same lockId", 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(1);
});
test("Should not call handler function when key is unexpireable and acquired by different lockId", 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 lockId", 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", {
retry: 10,
}, 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 ResultSuccess<string> when key is unexpireable and acquired by same lockId", 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).toEqual(resultSuccess(RETURN_VALUE));
});
test("Should return ResultSuccess<string> when key is unexpired and acquired by same lockId", 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).toEqual(resultSuccess(RETURN_VALUE));
});
test("Should return ResultFailure<FailedAcquireLockError> when key is unexpireable and acquired by different lockId", 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).toEqual(expect.objectContaining({
type: RESULT.FAILURE,
error: expect.any(FailedAcquireLockError),
}));
});
test("Should return ResultFailure<FailedAcquireLockError> when key is unexpired and acquired by different lockId", 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).toEqual(expect.objectContaining({
type: RESULT.FAILURE,
error: expect.any(FailedAcquireLockError),
}));
});
});
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", {
retry: 10,
}, 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 call handler function when key is unexpireable and acquired by same lockId", 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(1);
});
test("Should call handler function when key is unexpired and acquired by same lockId", 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(1);
});
test("Should not call handler function when key is unexpireable and acquired by different lockId", 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 lockId", 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", {
retry: 10,
}, 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 not throw error when key is unexpireable and acquired by same lockId", async () => {
const key = "a";
const ttl = null;
const lock = lockProvider.create(key, { ttl });
await lock.acquire();
const result = await lock.runOrFail(() => {
return Promise.resolve(RETURN_VALUE);
});
expect(result).toBe(RETURN_VALUE);
});
test("Should not throw error when key is unexpired and acquired by same lockId", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const lock = lockProvider.create(key, {
ttl,
});
await lock.acquire();
const result = await lock.runOrFail(() => {
return Promise.resolve(RETURN_VALUE);
});
expect(result).toBe(RETURN_VALUE);
});
test("Should throw FailedAcquireLockError when key is unexpireable and acquired by different lockId", 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(FailedAcquireLockError);
});
test("Should throw FailedAcquireLockError when key is unexpired and acquired by different lockId", 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(FailedAcquireLockError);
});
});
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", {
retry: 10,
}, 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 call handler function when key is unexpireable and acquired by same lockId", 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(1);
});
test("Should call handler function when key is unexpired and acquired by same lockId", 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(1);
});
test("Should not call handler function when key is unexpireable and acquired by different lockId", 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 lockId", 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", {
retry: 10,
}, 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 ResultSuccess<string> when key is unexpireable and acquired by same lockId", 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).toEqual(resultSuccess(RETURN_VALUE));
});
test("Should return ResultSuccess<string> when key is unexpired and acquired by same lockId", 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).toEqual(resultSuccess(RETURN_VALUE));
});
test("Should return ResultFailure<FailedAcquireLockError> when key is unexpireable and acquired by different lockId", 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).toEqual(expect.objectContaining({
type: RESULT.FAILURE,
error: expect.any(FailedAcquireLockError),
}));
});
test("Should return ResultFailure<FailedAcquireLockError> when key is unexpired and acquired by different lockId", 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).toEqual(expect.objectContaining({
type: RESULT.FAILURE,
error: expect.any(FailedAcquireLockError),
}));
});
test("Should retry acquire the lock", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const lock1 = lockProvider.create(key, {
ttl,
});
await lock1.acquire();
const handlerFn = vi.fn(() => { });
await lockProvider.addListener(LOCK_EVENTS.UNAVAILABLE, handlerFn);
const lock2 = lockProvider.create(key, {
ttl,
});
await lock2.runBlocking(() => {
return Promise.resolve(RETURN_VALUE);
}, {
time: TimeSpan.fromMilliseconds(55),
interval: TimeSpan.fromMilliseconds(5),
});
expect(handlerFn.mock.calls.length).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", {
retry: 10,
}, 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 call handler function when key is unexpireable and acquired by same lockId", 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(1);
});
test("Should call handler function when key is unexpired and acquired by same lockId", 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(1);
});
test("Should not call handler function when key is unexpireable and acquired by different lockId", 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 lockId", 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 })
.