@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.
988 lines • 115 kB
JavaScript
/**
* @module Lock
*/
import { vi, } from "vitest";
import {} from "../../../event-bus/contracts/_module.js";
import { FailedAcquireLockError, FailedReleaseLockError, LOCK_EVENTS, FailedRefreshLockError, LOCK_STATE, } from "../../../lock/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 `lockFactoryTestSuite` 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 { LockFactory } 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 { lockFactoryTestSuite } 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: LockFactory", () => {
* lockFactoryTestSuite({
* createLockFactory: () => {
* const serde = new Serde(new SuperJsonSerdeAdapter());
* const lockFactory = new LockFactory({
* serde,
* adapter: new MemoryLockAdapter(),
* });
* return { lockFactory, serde };
* },
* beforeEach,
* describe,
* expect,
* test,
* serde,
* });
* });
* ```
*/
export function lockFactoryTestSuite(settings) {
const { expect, test, createLockFactory, describe, beforeEach: beforeEach_, excludeEventTests = false, excludeSerdeTests = false, delayBuffer = TimeSpan.fromMilliseconds(10), timeSpanEqualityBuffer = TimeSpan.fromMilliseconds(10), eventDispatchWaitTime = TimeSpan.fromMilliseconds(10), } = settings;
let lockFactory;
let serde;
async function delayWithBuffer(ttl) {
await delay(TimeSpan.fromTimeSpan(ttl).addTimeSpan(delayBuffer));
}
const waitForSettings = {
interval: TimeSpan.fromTimeSpan(eventDispatchWaitTime).toMilliseconds(),
timeout: TimeSpan.fromTimeSpan(eventDispatchWaitTime)
.multiply(3)
.toMilliseconds(),
};
const RETURN_VALUE = "RETURN_VALUE";
describe("ILockFactory tests:", () => {
beforeEach_(async () => {
const { lockFactory: lockFactory_, serde: serde_ } = await createLockFactory();
lockFactory = lockFactory_;
serde = serde_;
});
describe("Api tests:", () => {
describe("method: runOrFail", () => {
test("Should call acquireOrFail method", async () => {
const key = "a";
const ttl = null;
const lock = lockFactory.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 = lockFactory.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 = lockFactory.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 = lockFactory.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 = lockFactory.create(key, {
ttl,
});
const releaseSpy = vi.spyOn(lock, "release");
class UnexpectedError extends Error {
}
try {
await lock.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 lock = lockFactory.create(key, {
ttl,
});
class UnexpectedErrorA extends Error {
}
const error = lock.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 handlerFn = vi.fn(() => {
return Promise.resolve(RETURN_VALUE);
});
await lockFactory
.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 lockFactory.create(key, { ttl }).acquire();
await delayWithBuffer(ttl);
const handlerFn = vi.fn(() => {
return Promise.resolve(RETURN_VALUE);
});
await lockFactory.create(key, { ttl }).runOrFail(handlerFn);
expect(handlerFn).toHaveBeenCalledTimes(1);
});
test("Should call handler function when key is unexpireable and acquired by same lock-id", async () => {
const key = "a";
const ttl = null;
const lock = lockFactory.create(key, { ttl });
await lock.acquire();
const handlerFn = vi.fn(() => {
return Promise.resolve(RETURN_VALUE);
});
await lock.runOrFail(handlerFn);
expect(handlerFn).toHaveBeenCalledTimes(1);
});
test("Should call handler function when key is unexpired and acquired by same lock-id", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const lock = lockFactory.create(key, {
ttl,
});
await lock.acquire();
const handlerFn = vi.fn(() => {
return Promise.resolve(RETURN_VALUE);
});
await lock.runOrFail(handlerFn);
expect(handlerFn).toHaveBeenCalledTimes(1);
});
test("Should not call handler function when key is unexpireable and acquired by different lock-id", async () => {
const key = "a";
const ttl = null;
await lockFactory.create(key, { ttl }).acquire();
const handlerFn = vi.fn(() => {
return Promise.resolve(RETURN_VALUE);
});
try {
await lockFactory
.create(key, { ttl })
.runOrFail(handlerFn);
}
catch (error) {
if (!(error instanceof FailedAcquireLockError)) {
throw error;
}
}
await delay(delayBuffer);
expect(handlerFn).not.toHaveBeenCalled();
});
test("Should not call handler function when key is unexpired and acquired by different lock-id", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
await lockFactory.create(key, { ttl }).acquire();
const handlerFn = vi.fn(() => {
return Promise.resolve(RETURN_VALUE);
});
try {
await lockFactory
.create(key, { ttl })
.runOrFail(handlerFn);
}
catch (error) {
if (!(error instanceof FailedAcquireLockError)) {
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 result = await lockFactory
.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 lockFactory.create(key, { ttl }).acquire();
await delayWithBuffer(ttl);
const result = await lockFactory
.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 lock-id", async () => {
const key = "a";
const ttl = null;
const lock = lockFactory.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 lock-id", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const lock = lockFactory.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 lock-id", async () => {
const key = "a";
const ttl = null;
await lockFactory.create(key, { ttl }).acquire();
const result = lockFactory
.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 lock-id", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
await lockFactory.create(key, { ttl }).acquire();
const result = lockFactory
.create(key, { ttl })
.runOrFail(() => {
return Promise.resolve(RETURN_VALUE);
});
await expect(result).rejects.toBeInstanceOf(FailedAcquireLockError);
});
});
describe("method: acquire", () => {
test("Should return true when key doesnt exists", async () => {
const key = "a";
const ttl = null;
const result = await lockFactory
.create(key, {
ttl,
})
.acquire();
expect(result).toBe(true);
});
test("Should return true when key is expired", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
await lockFactory.create(key, { ttl }).acquire();
await delayWithBuffer(ttl);
const result = await lockFactory
.create(key, { ttl })
.acquire();
expect(result).toBe(true);
});
test("Should return true when key is unexpireable and acquired by same lock-id", async () => {
const key = "a";
const ttl = null;
const lock = lockFactory.create(key, { ttl });
await lock.acquire();
const result = await lock.acquire();
expect(result).toBe(true);
});
test("Should return true when key is unexpired and acquired by same lock-id", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const lock = lockFactory.create(key, {
ttl,
});
await lock.acquire();
const result = await lock.acquire();
expect(result).toBe(true);
});
test("Should return false when key is unexpireable and acquired by different lock-id", async () => {
const key = "a";
const ttl = null;
await lockFactory.create(key, { ttl }).acquire();
const result = await lockFactory
.create(key, { ttl })
.acquire();
expect(result).toBe(false);
});
test("Should return false when key is unexpired and acquired by different lock-id", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
await lockFactory.create(key, { ttl }).acquire();
const result = await lockFactory
.create(key, { ttl })
.acquire();
expect(result).toBe(false);
});
});
describe("method: acquireOrFail", () => {
test("Should not throw error when key doesnt exists", async () => {
const key = "a";
const ttl = null;
const result = lockFactory
.create(key, {
ttl,
})
.acquireOrFail();
await expect(result).resolves.toBeUndefined();
});
test("Should not throw error when key is expired", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
await lockFactory.create(key, { ttl }).acquireOrFail();
await delayWithBuffer(ttl);
const result = lockFactory
.create(key, { ttl })
.acquireOrFail();
await expect(result).resolves.toBeUndefined();
});
test("Should not throw error when key is unexpireable and acquired by same lock-id", async () => {
const key = "a";
const ttl = null;
const lock = lockFactory.create(key, { ttl });
await lock.acquireOrFail();
const result = lock.acquireOrFail();
await expect(result).resolves.toBeUndefined();
});
test("Should not throw error when key is unexpired and acquired by same lock-id", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const lock = lockFactory.create(key, {
ttl,
});
await lock.acquireOrFail();
const result = lock.acquireOrFail();
await expect(result).resolves.toBeUndefined();
});
test("Should throw FailedAcquireLockError when key is unexpireable and acquired by different lock-id", async () => {
const key = "a";
const ttl = null;
await lockFactory.create(key, { ttl }).acquireOrFail();
const result = lockFactory
.create(key, { ttl })
.acquireOrFail();
await expect(result).rejects.toBeInstanceOf(FailedAcquireLockError);
});
test("Should throw FailedAcquireLockError when key is unexpired and acquired by different lock-id", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
await lockFactory.create(key, { ttl }).acquireOrFail();
const result = lockFactory
.create(key, { ttl })
.acquireOrFail();
await expect(result).rejects.toBeInstanceOf(FailedAcquireLockError);
});
});
describe("method: release", () => {
test("Should return false when key doesnt exists", async () => {
const key = "a";
const ttl = null;
const result = await lockFactory
.create(key, {
ttl,
})
.release();
expect(result).toBe(false);
});
test("Should return false when key is unexpireable and released by different lock-id", async () => {
const key = "a";
const ttl = null;
await lockFactory.create(key, { ttl }).acquire();
const result = await lockFactory
.create(key, { ttl })
.release();
expect(result).toBe(false);
});
test("Should return false when key is unexpired and released by different lock-id", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
await lockFactory.create(key, { ttl }).acquire();
const result = await lockFactory
.create(key, { ttl })
.release();
expect(result).toBe(false);
});
test("Should return false when key is expired and released by different lock-id", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
await lockFactory.create(key, { ttl }).acquire();
await delayWithBuffer(ttl);
const result = await lockFactory
.create(key, { ttl })
.release();
expect(result).toBe(false);
});
test("Should return false when key is expired and released by same lock-id", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const lock = lockFactory.create(key, { ttl });
await lock.acquire();
await delayWithBuffer(ttl);
const result = await lock.release();
expect(result).toBe(false);
});
test("Should return true when key is unexpireable and released by same lock-id", async () => {
const key = "a";
const ttl = null;
const lock = lockFactory.create(key, { ttl });
await lock.acquire();
const result = await lock.release();
expect(result).toBe(true);
});
test("Should return true when key is unexpired and released by same lock-id", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const lock = lockFactory.create(key, { ttl });
await lock.acquire();
const result = await lock.release();
expect(result).toBe(true);
});
test("Should not be reacquirable when key is unexpireable and released by different lock-id", async () => {
const key = "a";
const ttl = null;
const lock1 = lockFactory.create(key, { ttl });
await lock1.acquire();
const lock2 = lockFactory.create(key, { ttl });
await lock2.release();
const result = await lock2.acquire();
expect(result).toBe(false);
});
test("Should not be reacquirable when key is unexpired and released by different lock-id", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const lock1 = lockFactory.create(key, { ttl });
await lock1.acquire();
const lock2 = lockFactory.create(key, { ttl });
await lock2.release();
const result = await lock2.acquire();
expect(result).toBe(false);
});
test("Should be reacquirable when key is unexpireable and released by same lock-id", async () => {
const key = "a";
const ttl = null;
const lock1 = lockFactory.create(key, { ttl });
await lock1.acquire();
await lock1.release();
const lock2 = lockFactory.create(key, { ttl });
const result = await lock2.acquire();
expect(result).toBe(true);
});
test("Should be reacquirable when key is unexpired and released by same lock-id", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const lock1 = lockFactory.create(key, { ttl });
await lock1.acquire();
await lock1.release();
const lock2 = lockFactory.create(key, { ttl });
const result = await lock2.acquire();
expect(result).toBe(true);
});
});
describe("method: releaseOrFail", () => {
test("Should throw FailedReleaseLockError when key doesnt exists", async () => {
const key = "a";
const ttl = null;
const result = lockFactory
.create(key, {
ttl,
})
.releaseOrFail();
await expect(result).rejects.toBeInstanceOf(FailedReleaseLockError);
});
test("Should throw FailedReleaseLockError when key is unexpireable and released by different lock-id", async () => {
const key = "a";
const ttl = null;
await lockFactory.create(key, { ttl }).acquire();
const result = lockFactory
.create(key, { ttl })
.releaseOrFail();
await expect(result).rejects.toBeInstanceOf(FailedReleaseLockError);
});
test("Should throw FailedReleaseLockError when key is unexpired and released by different lock-id", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
await lockFactory.create(key, { ttl }).acquire();
const result = lockFactory
.create(key, { ttl })
.releaseOrFail();
await expect(result).rejects.toBeInstanceOf(FailedReleaseLockError);
});
test("Should throw FailedReleaseLockError when key is expired and released by different lock-id", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
await lockFactory.create(key, { ttl }).acquire();
await delayWithBuffer(ttl);
const result = lockFactory
.create(key, { ttl })
.releaseOrFail();
await expect(result).rejects.toBeInstanceOf(FailedReleaseLockError);
});
test("Should throw FailedReleaseLockError when key is expired and released by same lock-id", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const lock = lockFactory.create(key, { ttl });
await lock.acquire();
await delayWithBuffer(ttl);
const result = lock.releaseOrFail();
await expect(result).rejects.toBeInstanceOf(FailedReleaseLockError);
});
test("Should not throw error when key is unexpireable and released by same lock-id", async () => {
const key = "a";
const ttl = null;
const lock = lockFactory.create(key, { ttl });
await lock.acquire();
const result = lock.releaseOrFail();
await expect(result).resolves.toBeUndefined();
});
test("Should not throw error when key is unexpired and released by same lock-id", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const lock = lockFactory.create(key, { ttl });
await lock.acquire();
const result = lock.releaseOrFail();
await expect(result).resolves.toBeUndefined();
});
test("Should not be reacquirable when key is unexpireable and released by different lock-id", async () => {
const key = "a";
const ttl = null;
const lock1 = lockFactory.create(key, { ttl });
await lock1.acquire();
const lock2 = lockFactory.create(key, { ttl });
try {
await lock2.releaseOrFail();
}
catch (error) {
if (!(error instanceof FailedReleaseLockError)) {
throw error;
}
}
const result = await lock2.acquire();
expect(result).toBe(false);
});
test("Should not be reacquirable when key is unexpired and released by different lock-id", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const lock1 = lockFactory.create(key, { ttl });
await lock1.acquire();
const lock2 = lockFactory.create(key, { ttl });
try {
await lock2.releaseOrFail();
}
catch (error) {
if (!(error instanceof FailedReleaseLockError)) {
throw error;
}
}
const result = await lock2.acquire();
expect(result).toBe(false);
});
test("Should be reacquirable when key is unexpireable and released by same lock-id", async () => {
const key = "a";
const ttl = null;
const lock1 = lockFactory.create(key, { ttl });
await lock1.acquire();
await lock1.releaseOrFail();
const lock2 = lockFactory.create(key, { ttl });
const result = await lock2.acquire();
expect(result).toBe(true);
});
test("Should be reacquirable when key is unexpired and released by same lock-id", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const lock1 = lockFactory.create(key, { ttl });
await lock1.acquire();
await lock1.releaseOrFail();
const lock2 = lockFactory.create(key, { ttl });
const result = await lock2.acquire();
expect(result).toBe(true);
});
});
describe("method: forceRelease", () => {
test("Should return false when key doesnt exists", async () => {
const key = "a";
const ttl = null;
const result = await lockFactory
.create(key, {
ttl,
})
.forceRelease();
expect(result).toBe(false);
});
test("Should return false when key is expired", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const lock = lockFactory.create(key, {
ttl,
});
await lock.acquire();
await delayWithBuffer(ttl);
const result = await lock.forceRelease();
expect(result).toBe(false);
});
test("Should return true when key is unexpired", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const lock = lockFactory.create(key, { ttl });
await lock.acquire();
const result = await lock.forceRelease();
expect(result).toBe(true);
});
test("Should return true when key is unexpireable", async () => {
const key = "a";
const ttl = null;
const lock = lockFactory.create(key, { ttl });
await lock.acquire();
const result = await lock.forceRelease();
expect(result).toBe(true);
});
test("Should be reacquirable when key is unexpired", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const lock = lockFactory.create(key, { ttl });
await lock.acquire();
await lock.forceRelease();
const result = await lock.acquire();
expect(result).toBe(true);
});
test("Should be reacquirable when key is unexpireable", async () => {
const key = "a";
const ttl = null;
const lock = lockFactory.create(key, { ttl });
await lock.acquire();
await lock.forceRelease();
const result = await lock.acquire();
expect(result).toBe(true);
});
});
describe("method: refresh", () => {
test("Should return false when key doesnt exists", async () => {
const key = "a";
const ttl = null;
const newTtl = TimeSpan.fromMinutes(1);
const result = await lockFactory
.create(key, {
ttl,
})
.refresh(newTtl);
expect(result).toBe(false);
});
test("Should return false when key is unexpireable and refreshed by different lock-id", async () => {
const key = "a";
const ttl = null;
const lock1 = lockFactory.create(key, { ttl });
await lock1.acquire();
const newTtl = TimeSpan.fromMinutes(1);
const lock2 = lockFactory.create(key, { ttl });
const result = await lock2.refresh(newTtl);
expect(result).toBe(false);
});
test("Should return false when key is unexpired and refreshed by different lock-id", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const lock1 = lockFactory.create(key, { ttl });
await lock1.acquire();
const newTtl = TimeSpan.fromMinutes(1);
const lock2 = lockFactory.create(key, { ttl });
const result = await lock2.refresh(newTtl);
expect(result).toBe(false);
});
test("Should return false when key is expired and refreshed by different lock-id", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const lock1 = lockFactory.create(key, { ttl });
await lock1.acquire();
await delayWithBuffer(ttl);
const newTtl = TimeSpan.fromMinutes(1);
const lock2 = lockFactory.create(key, { ttl });
const result = await lock2.refresh(newTtl);
expect(result).toBe(false);
});
test("Should return false when key is expired and refreshed by same lock-id", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const lock = lockFactory.create(key, {
ttl,
});
await lock.acquire();
await delayWithBuffer(ttl);
const newTtl = TimeSpan.fromMinutes(1);
const result = await lock.refresh(newTtl);
expect(result).toBe(false);
});
test("Should return false when key is unexpireable and refreshed by same lock-id", async () => {
const key = "a";
const ttl = null;
const lock = lockFactory.create(key, { ttl });
await lock.acquire();
const newTtl = TimeSpan.fromMinutes(1);
const result = await lock.refresh(newTtl);
expect(result).toBe(false);
});
test("Should return true when key is unexpired and refreshed by same lock-id", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const lock = lockFactory.create(key, { ttl });
await lock.acquire();
const newTtl = TimeSpan.fromMinutes(1);
const result = await lock.refresh(newTtl);
expect(result).toBe(true);
});
test("Should not update expiration when key is unexpireable and refreshed by same lock-id", async () => {
const key = "a";
const ttl = null;
const lock1 = lockFactory.create(key, { ttl });
await lock1.acquire();
const newTtl = TimeSpan.fromMilliseconds(50);
await lock1.refresh(newTtl);
await delayWithBuffer(newTtl);
const lock2 = lockFactory.create(key, { ttl });
const result = await lock2.acquire();
expect(result).toBe(false);
});
test("Should update expiration when key is unexpired and refreshed by same lock-id", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const lock1 = lockFactory.create(key, { ttl });
await lock1.acquire();
const newTtl = TimeSpan.fromMilliseconds(100);
await lock1.refresh(newTtl);
await delayWithBuffer(newTtl.divide(2));
const lock2 = lockFactory.create(key, { ttl });
const result1 = await lock2.acquire();
expect(result1).toBe(false);
await delayWithBuffer(newTtl.divide(2));
const result2 = await lock2.acquire();
expect(result2).toBe(true);
});
});
describe("method: refreshOrFail", () => {
test("Should throw FailedRefreshLockError when key doesnt exists", async () => {
const key = "a";
const ttl = null;
const newTtl = TimeSpan.fromMinutes(1);
const result = lockFactory
.create(key, {
ttl,
})
.refreshOrFail(newTtl);
await expect(result).rejects.toBeInstanceOf(FailedRefreshLockError);
});
test("Should throw FailedRefreshLockError when key is unexpireable and refreshed by different lock-id", async () => {
const key = "a";
const ttl = null;
const lock1 = lockFactory.create(key, { ttl });
await lock1.acquire();
const newTtl = TimeSpan.fromMinutes(1);
const lock2 = lockFactory.create(key, { ttl });
const result = lock2.refreshOrFail(newTtl);
await expect(result).rejects.toBeInstanceOf(FailedRefreshLockError);
});
test("Should throw FailedRefreshLockError when key is unexpired and refreshed by different lock-id", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const lock1 = lockFactory.create(key, { ttl });
await lock1.acquire();
const newTtl = TimeSpan.fromMinutes(1);
const lock2 = lockFactory.create(key, { ttl });
const result = lock2.refreshOrFail(newTtl);
await expect(result).rejects.toBeInstanceOf(FailedRefreshLockError);
});
test("Should throw FailedRefreshLockError when key is expired and refreshed by different lock-id", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const lock1 = lockFactory.create(key, { ttl });
await lock1.acquire();
await delayWithBuffer(ttl);
const newTtl = TimeSpan.fromMinutes(1);
const lock2 = lockFactory.create(key, { ttl });
const result = lock2.refreshOrFail(newTtl);
await expect(result).rejects.toBeInstanceOf(FailedRefreshLockError);
});
test("Should throw FailedRefreshLockError when key is expired and refreshed by same lock-id", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const lock = lockFactory.create(key, {
ttl,
});
await lock.acquire();
await delayWithBuffer(ttl);
const newTtl = TimeSpan.fromMinutes(1);
const result = lock.refreshOrFail(newTtl);
await expect(result).rejects.toBeInstanceOf(FailedRefreshLockError);
});
test("Should throw FailedRefreshLockError when key is unexpireable and refreshed by same lock-id", async () => {
const key = "a";
const ttl = null;
const lock = lockFactory.create(key, { ttl });
await lock.acquire();
const newTtl = TimeSpan.fromMinutes(1);
const result = lock.refreshOrFail(newTtl);
await expect(result).rejects.toBeInstanceOf(FailedRefreshLockError);
});
test("Should not throw error when key is unexpired and refreshed by same lock-id", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const lock = lockFactory.create(key, { ttl });
await lock.acquire();
const newTtl = TimeSpan.fromMinutes(1);
const result = lock.refreshOrFail(newTtl);
await expect(result).resolves.toBeUndefined();
});
test("Should not update expiration when key is unexpireable and refreshed by same lock-id", async () => {
const key = "a";
const ttl = null;
const lock1 = lockFactory.create(key, { ttl });
await lock1.acquire();
const newTtl = TimeSpan.fromMilliseconds(50);
try {
await lock1.refreshOrFail(newTtl);
}
catch (error) {
if (!(error instanceof FailedRefreshLockError)) {
throw error;
}
}
await delayWithBuffer(newTtl);
const lock2 = lockFactory.create(key, { ttl });
const result = await lock2.acquire();
expect(result).toBe(false);
});
test("Should update expiration when key is unexpired and refreshed by same lock-id", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const lock1 = lockFactory.create(key, { ttl });
await lock1.acquire();
const newTtl = TimeSpan.fromMilliseconds(100);
await lock1.refreshOrFail(newTtl);
await delayWithBuffer(newTtl.divide(2));
const lock2 = lockFactory.create(key, { ttl });
const result1 = await lock2.acquire();
expect(result1).toBe(false);
await delayWithBuffer(newTtl.divide(2));
const result2 = await lock2.acquire();
expect(result2).toBe(true);
});
});
describe("method: id", () => {
test("Should return lock id of ILock instance", () => {
const key = "a";
const lockId = "1";
const lock = lockFactory.create(key, {
lockId,
});
expect(lock.id).toBe(lockId);
});
test("Should return lock id of ILock instance implicitly", () => {
const key = "a";
const ttl = null;
const lock = lockFactory.create(key, {
ttl,
});
expect(lock.id).toBeTypeOf("string");
expect(lock.id.length).toBeGreaterThan(0);
});
});
describe("method: ttl", () => {
test("Should return null when given null ttl", () => {
const key = "a";
const ttl = null;
const lock = lockFactory.create(key, {
ttl,
});
expect(lock.ttl).toBeNull();
});
test("Should return TimeSpan when given TimeSpan", () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(100);
const lock = lockFactory.create(key, {
ttl,
});
expect(lock.ttl).toBeInstanceOf(TimeSpan);
expect(lock.ttl?.[TO_MILLISECONDS]()).toBe(ttl.toMilliseconds());
});
});
describe("method: getState", () => {
test("Should return ILockExpiredState when key doesnt exists", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const lock = lockFactory.create(key, {
ttl,
});
const result = await lock.getState();
expect(result).toEqual({
type: LOCK_STATE.EXPIRED,
});
});
test("Should return ILockExpiredState when key is expired", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const lock = lockFactory.create(key, {
ttl,
});
await lock.acquire();
await delayWithBuffer(ttl);
const result = await lock.getState();
expect(result).toEqual({
type: LOCK_STATE.EXPIRED,
});
});
test("Should return ILockExpiredState when key is released with forceRelease method", async () => {
const key = "a";
const ttl1 = null;
const lock1 = lockFactory.create(key, {
ttl: ttl1,
});
await lock1.acquire();
const ttl2 = null;
const lock2 = lockFactory.c