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