@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,102 lines • 399 kB
JavaScript
/**
* @module SharedLock
*/
import { vi, } from "vitest";
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 { Task } from "../../../task/implementations/_module.js";
import { TimeSpan } from "../../../time-span/implementations/_module.js";
import {} from "../../../utilities/_module.js";
/**
* The `sharedLockProviderTestSuite` 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 { SharedLockProvider } 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 { sharedLockProviderTestSuite } 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: SharedLockProvider", () => {
* sharedLockProviderTestSuite({
* createSharedLockProvider: () => {
* const serde = new Serde(new SuperJsonSerdeAdapter());
* const sharedLockProvider = new SharedLockProvider({
* serde,
* adapter: new MemorySharedLockAdapter(),
* });
* return { sharedLockProvider, serde };
* },
* beforeEach,
* describe,
* expect,
* test,
* serde,
* });
* });
* ```
*/
export function sharedLockProviderTestSuite(settings) {
const { expect, test, createSharedLockProvider, describe, beforeEach, excludeEventTests = false, excludeSerdeTests = false, } = settings;
let sharedLockProvider;
let serde;
async function delay(time) {
await Task.delay(time.addMilliseconds(10));
}
const RETURN_VALUE = "RETURN_VALUE";
describe("Reusable tests:", () => {
beforeEach(async () => {
const { sharedLockProvider: sharedLockProvider_, serde: serde_ } = await createSharedLockProvider();
sharedLockProvider = sharedLockProvider_;
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 = sharedLockProvider.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 = sharedLockProvider.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 = sharedLockProvider.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 = sharedLockProvider.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 = sharedLockProvider.create(key, {
ttl,
limit,
});
const releaseSpy = vi.spyOn(sharedLock, "releaseWriter");
try {
await sharedLock.runWriterOrFail(() => {
return Promise.reject(new Error());
});
}
catch {
/* EMPTY */
}
expect(releaseSpy).toHaveBeenCalledTimes(1);
});
test("Should propagate thrown error", async () => {
const key = "a";
const ttl = null;
const limit = 4;
const sharedLock = sharedLockProvider.create(key, {
ttl,
limit,
});
class CustomError extends Error {
}
const error = sharedLock.runWriterOrFail(() => {
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 limit = 4;
const ttl = null;
const handlerFn = vi.fn(() => {
return Promise.resolve(RETURN_VALUE);
});
await sharedLockProvider
.create(key, {
ttl,
limit,
})
.runWriterOrFail(handlerFn);
expect(handlerFn).toHaveBeenCalledTimes(1);
});
test("Should call handler function when key is expired", {
retry: 10,
}, async () => {
const key = "a";
const limit = 4;
const ttl = TimeSpan.fromMilliseconds(50);
await sharedLockProvider
.create(key, {
ttl,
limit,
})
.acquireWriter();
await delay(ttl);
const handlerFn = vi.fn(() => {
return Promise.resolve(RETURN_VALUE);
});
await sharedLockProvider
.create(key, {
ttl,
limit,
})
.runWriterOrFail(handlerFn);
expect(handlerFn).toHaveBeenCalledTimes(1);
});
test("Should call handler function when key is unexpireable and acquired by same lockId", async () => {
const key = "a";
const limit = 4;
const ttl = null;
const sharedLock = sharedLockProvider.create(key, {
ttl,
limit,
});
await sharedLock.acquireWriter();
const handlerFn = vi.fn(() => {
return Promise.resolve(RETURN_VALUE);
});
try {
await sharedLock.runWriterOrFail(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 limit = 4;
const ttl = TimeSpan.fromMilliseconds(50);
const sharedLock = sharedLockProvider.create(key, {
ttl,
limit,
});
await sharedLock.acquireWriter();
const handlerFn = vi.fn(() => {
return Promise.resolve(RETURN_VALUE);
});
try {
await sharedLock.runWriterOrFail(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 limit = 4;
const ttl = null;
await sharedLockProvider
.create(key, {
ttl,
limit,
})
.acquireWriter();
const handlerFn = vi.fn(() => {
return Promise.resolve(RETURN_VALUE);
});
try {
await sharedLockProvider
.create(key, {
ttl,
limit,
})
.runWriterOrFail(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 limit = 4;
const ttl = TimeSpan.fromMilliseconds(50);
await sharedLockProvider
.create(key, {
ttl,
limit,
})
.acquireWriter();
const handlerFn = vi.fn(() => {
return Promise.resolve(RETURN_VALUE);
});
try {
await sharedLockProvider
.create(key, {
ttl,
limit,
})
.runWriterOrFail(handlerFn);
}
catch {
/* EMPTY */
}
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 sharedLockProvider
.create(key, {
ttl,
limit,
})
.runWriterOrFail(() => {
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 limit = 4;
const ttl = TimeSpan.fromMilliseconds(50);
await sharedLockProvider
.create(key, {
ttl,
limit,
})
.acquireWriter();
await delay(ttl);
const result = await sharedLockProvider
.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 lockId", async () => {
const key = "a";
const limit = 4;
const ttl = null;
const sharedLock = sharedLockProvider.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 lockId", async () => {
const key = "a";
const limit = 4;
const ttl = TimeSpan.fromMilliseconds(50);
const sharedLock = sharedLockProvider.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 lockId", async () => {
const key = "a";
const limit = 4;
const ttl = null;
await sharedLockProvider
.create(key, {
ttl,
limit,
})
.acquireWriter();
const result = sharedLockProvider
.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 lockId", async () => {
const key = "a";
const limit = 4;
const ttl = TimeSpan.fromMilliseconds(50);
await sharedLockProvider
.create(key, {
ttl,
limit,
})
.acquireWriter();
const result = sharedLockProvider
.create(key, {
ttl,
limit,
})
.runWriterOrFail(() => {
return Promise.resolve(RETURN_VALUE);
});
await expect(result).rejects.toBeInstanceOf(FailedAcquireWriterLockError);
});
});
describe("method: runWriterBlockingOrFail", () => {
test("Should call acquireWriterBlockingOrFail method", async () => {
const key = "a";
const ttl = null;
const limit = 4;
const sharedLock = sharedLockProvider.create(key, {
ttl,
limit,
});
const acquireSpy = vi.spyOn(sharedLock, "acquireWriterBlockingOrFail");
await sharedLock.runWriterBlockingOrFail(() => {
return Promise.resolve(RETURN_VALUE);
}, {
time: TimeSpan.fromMilliseconds(5),
interval: TimeSpan.fromMilliseconds(5),
});
expect(acquireSpy).toHaveBeenCalledTimes(1);
});
test("Should call acquireWriterBlockingOrFail before release method", async () => {
const key = "a";
const ttl = null;
const limit = 4;
const sharedLock = sharedLockProvider.create(key, {
ttl,
limit,
});
const acquireSpy = vi.spyOn(sharedLock, "acquireWriterBlockingOrFail");
const releaseSpy = vi.spyOn(sharedLock, "releaseWriter");
await sharedLock.runWriterBlockingOrFail(() => {
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 limit = 4;
const sharedLock = sharedLockProvider.create(key, {
ttl,
limit,
});
const releaseSpy = vi.spyOn(sharedLock, "releaseWriter");
await sharedLock.runWriterBlockingOrFail(() => {
return Promise.resolve(RETURN_VALUE);
}, {
time: TimeSpan.fromMilliseconds(5),
interval: TimeSpan.fromMilliseconds(5),
});
expect(releaseSpy).toHaveBeenCalledTimes(1);
});
test("Should call release after acquireWriterBlockingOrFail method", async () => {
const key = "a";
const ttl = null;
const limit = 4;
const sharedLock = sharedLockProvider.create(key, {
ttl,
limit,
});
const releaseSpy = vi.spyOn(sharedLock, "releaseWriter");
const acquireSpy = vi.spyOn(sharedLock, "acquireWriterBlockingOrFail");
await sharedLock.runWriterBlockingOrFail(() => {
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 limit = 4;
const sharedLock = sharedLockProvider.create(key, {
ttl,
limit,
});
const releaseSpy = vi.spyOn(sharedLock, "releaseWriter");
try {
await sharedLock.runWriterBlockingOrFail(() => {
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 limit = 4;
const sharedLock = sharedLockProvider.create(key, {
ttl,
limit,
});
class CustomError extends Error {
}
const error = sharedLock.runWriterBlockingOrFail(() => {
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 limit = 4;
const handlerFn = vi.fn(() => {
return Promise.resolve(RETURN_VALUE);
});
await sharedLockProvider
.create(key, {
ttl,
limit,
})
.runWriterBlockingOrFail(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);
const limit = 4;
await sharedLockProvider
.create(key, {
ttl,
limit,
})
.acquireWriter();
await delay(ttl);
const handlerFn = vi.fn(() => {
return Promise.resolve(RETURN_VALUE);
});
await sharedLockProvider
.create(key, {
ttl,
limit,
})
.runWriterBlockingOrFail(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 limit = 4;
const sharedLock = sharedLockProvider.create(key, {
ttl,
limit,
});
await sharedLock.acquireWriter();
const handlerFn = vi.fn(() => {
return Promise.resolve(RETURN_VALUE);
});
try {
await sharedLock.runWriterBlockingOrFail(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 limit = 4;
const sharedLock = sharedLockProvider.create(key, {
ttl,
limit,
});
await sharedLock.acquireWriter();
const handlerFn = vi.fn(() => {
return Promise.resolve(RETURN_VALUE);
});
try {
await sharedLock.runWriterBlockingOrFail(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;
const limit = 4;
await sharedLockProvider
.create(key, {
ttl,
limit,
})
.acquireWriter();
const handlerFn = vi.fn(() => {
return Promise.resolve(RETURN_VALUE);
});
try {
await sharedLockProvider
.create(key, {
ttl,
limit,
})
.runWriterBlockingOrFail(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);
const limit = 4;
await sharedLockProvider
.create(key, {
ttl,
limit,
})
.acquireWriter();
const handlerFn = vi.fn(() => {
return Promise.resolve(RETURN_VALUE);
});
try {
await sharedLockProvider
.create(key, {
ttl,
limit,
})
.runWriterBlockingOrFail(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 limit = 4;
const result = await sharedLockProvider
.create(key, {
ttl,
limit,
})
.runWriterBlockingOrFail(() => {
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", {
retry: 10,
}, async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const limit = 4;
await sharedLockProvider
.create(key, {
ttl,
limit,
})
.acquireWriter();
await delay(ttl);
const result = await sharedLockProvider
.create(key, {
ttl,
limit,
})
.runWriterBlockingOrFail(() => {
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 unexpireable and acquired by same lockId", async () => {
const key = "a";
const ttl = null;
const limit = 4;
const sharedLock = sharedLockProvider.create(key, {
ttl,
limit,
});
await sharedLock.acquireWriter();
const result = await sharedLock.runWriterBlockingOrFail(() => {
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 unexpired and acquired by same lockId", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const limit = 4;
const sharedLock = sharedLockProvider.create(key, {
ttl,
limit,
});
await sharedLock.acquireWriter();
const result = await sharedLock.runWriterBlockingOrFail(() => {
return Promise.resolve(RETURN_VALUE);
}, {
time: TimeSpan.fromMilliseconds(5),
interval: TimeSpan.fromMilliseconds(5),
});
expect(result).toBe(RETURN_VALUE);
});
test("Should throw FailedAcquireWriterLockError when key is unexpireable and acquired by different lockId", async () => {
const key = "a";
const ttl = null;
const limit = 4;
await sharedLockProvider
.create(key, {
ttl,
limit,
})
.acquireWriter();
const result = sharedLockProvider
.create(key, {
ttl,
limit,
})
.runWriterBlockingOrFail(() => {
return Promise.resolve(RETURN_VALUE);
}, {
time: TimeSpan.fromMilliseconds(5),
interval: TimeSpan.fromMilliseconds(5),
});
await expect(result).rejects.toBeInstanceOf(FailedAcquireWriterLockError);
});
test("Should throw FailedAcquireWriterLockError when key is unexpired and acquired by different lockId", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const limit = 4;
await sharedLockProvider
.create(key, {
ttl,
limit,
})
.acquireWriter();
const result = sharedLockProvider
.create(key, {
ttl,
limit,
})
.runWriterBlockingOrFail(() => {
return Promise.resolve(RETURN_VALUE);
}, {
time: TimeSpan.fromMilliseconds(5),
interval: TimeSpan.fromMilliseconds(5),
});
await expect(result).rejects.toBeInstanceOf(FailedAcquireWriterLockError);
});
test("Should retry acquire the shared lock when blocked by a writer", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const limit = 4;
const sharedLock1 = sharedLockProvider.create(key, {
ttl,
limit,
});
await sharedLock1.acquireWriter();
const handlerFn = vi.fn(() => { });
await sharedLockProvider.addListener(SHARED_LOCK_EVENTS.UNAVAILABLE, handlerFn);
const sharedLock2 = sharedLockProvider.create(key, {
ttl,
limit,
});
try {
await sharedLock2.runWriterBlockingOrFail(() => {
return Promise.resolve(RETURN_VALUE);
}, {
time: TimeSpan.fromMilliseconds(55),
interval: TimeSpan.fromMilliseconds(5),
});
}
catch {
/* EMPTY */
}
expect(handlerFn.mock.calls.length).toBeGreaterThan(1);
});
test("Should retry acquire the shared lock when blocked by a reader", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const limit = 4;
const sharedLock1 = sharedLockProvider.create(key, {
ttl,
limit,
});
await sharedLock1.acquireReader();
const handlerFn = vi.fn(() => { });
await sharedLockProvider.addListener(SHARED_LOCK_EVENTS.UNAVAILABLE, handlerFn);
const sharedLock2 = sharedLockProvider.create(key, {
ttl,
limit,
});
try {
await sharedLock2.runWriterBlockingOrFail(() => {
return Promise.resolve(RETURN_VALUE);
}, {
time: TimeSpan.fromMilliseconds(55),
interval: TimeSpan.fromMilliseconds(5),
});
}
catch {
/* EMPTY */
}
expect(handlerFn.mock.calls.length).toBeGreaterThan(1);
});
});
describe("method: acquireWriter", () => {
test("Should return true when key doesnt exists", async () => {
const key = "a";
const ttl = null;
const limit = 4;
const result = await sharedLockProvider
.create(key, {
limit,
ttl,
})
.acquireWriter();
expect(result).toBe(true);
});
test("Should return true when key is expired", {
retry: 10,
}, async () => {
const key = "a";
const limit = 4;
const ttl = TimeSpan.fromMilliseconds(50);
const sharedLock = sharedLockProvider.create(key, {
limit,
ttl,
});
await sharedLock.acquireWriter();
await delay(ttl);
const result = await sharedLock.acquireWriter();
expect(result).toBe(true);
});
test("Should return true when key is unexpireable and acquired by same owner", async () => {
const key = "a";
const limit = 4;
const ttl = null;
const sharedLock = sharedLockProvider.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 owner", async () => {
const key = "a";
const limit = 4;
const ttl = TimeSpan.fromMilliseconds(50);
const sharedLock = sharedLockProvider.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 owner", async () => {
const key = "a";
const ttl = null;
const limit = 4;
const sharedLock1 = sharedLockProvider.create(key, {
ttl,
limit,
});
await sharedLock1.acquireWriter();
const sharedLock2 = sharedLockProvider.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 owner", async () => {
const key = "a";
const limit = 4;
const ttl = TimeSpan.fromMilliseconds(50);
const sharedLock1 = sharedLockProvider.create(key, {
limit,
ttl,
});
await sharedLock1.acquireWriter();
const sharedLock2 = sharedLockProvider.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 = sharedLockProvider.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 = sharedLockProvider.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: null,
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 = sharedLockProvider
.create(key, {
limit,
ttl,
})
.acquireWriterOrFail();
await expect(result).resolves.toBeUndefined();
});
test("Should not throw error when key is expired", {
retry: 10,
}, async () => {
const key = "a";
const limit = 4;
const ttl = TimeSpan.fromMilliseconds(50);
const sharedLock = sharedLockProvider.create(key, {
limit,
ttl,
});
await sharedLock.acquireWriterOrFail();
await delay(ttl);
const result = sharedLock.acquireWriterOrFail();
await expect(result).resolves.toBeUndefined();
});
test("Should not throw error when key is unexpireable and acquired by same owner", async () => {
const key = "a";
const limit = 4;
const ttl = null;
const sharedLock = sharedLockProvider.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 owner", async () => {
const key = "a";
const limit = 4;
const ttl = TimeSpan.fromMilliseconds(50);
const sharedLock = sharedLockProvider.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 owner", async () => {
const key = "a";
const ttl = null;
const limit = 4;
const sharedLock1 = sharedLockProvider.create(key, {
ttl,
limit,
});
await sharedLock1.acquireWriterOrFail();
const sharedLock2 = sharedLockProvider.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 owner", async () => {
const key = "a";
const limit = 4;
const ttl = TimeSpan.fromMilliseconds(50);
const sharedLock1 = sharedLockProvider.create(key, {
limit,
ttl,
});
await sharedLock1.acquireWriterOrFail();
const sharedLock2 = sharedLockProvider.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 = sharedLockProvider.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 = sharedLockProvider.create(key, {
limit,
ttl,
});
await sharedLock.acquireReader();
try {
await sharedLock.acquireWriterOrFail();
}
catch {
/* EMPTY */
}
const state = await sharedLock.getState();
expect(state).toEqual({
type: SHARED_LOCK_STATE.READER_ACQUIRED,
limit,
remainingTime: null,
freeSlotsCount: 2,
acquiredSlotsCount: 1,
acquiredSlots: [sharedLock.id],
});
});
});
describe("method: acquireWriterBlocking", () => {
test("Should return true when key doesnt exists", async () => {
const key = "a";
const ttl = null;
const limit = 4;
const result = await sharedLockProvider
.create(key, {
limit,
ttl,
})
.acquireWriterBlocking({
time: TimeSpan.fromMilliseconds(5),
interval: TimeSpan.fromMilliseconds(5),
});
expect(result).toBe(true);
});
test("Should return true when key is expired", {
retry: 10,
}, async () => {
const key = "a";
const limit = 4;
const ttl = TimeSpan.fromMilliseconds(50);
const sharedLock = sharedLockProvider.create(key, {
limit,
ttl,
});
await sharedLock.acquireWriter();
await delay(ttl);
const result = await sharedLock.acquireWriterBlocking({
time: TimeSpan.fromMilliseconds(5),
interval: TimeSpan.fromMilliseconds(5),
});
expect(result).t