@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,125 lines • 153 kB
JavaScript
/**
* @module Semaphore
*/
import { vi, } from "vitest";
import {} from "../../../event-bus/contracts/_module.js";
import { LimitReachedSemaphoreError, SEMAPHORE_EVENTS, FailedReleaseSemaphoreError, FailedRefreshSemaphoreError, SEMAPHORE_STATE, } from "../../../semaphore/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 `semaphoreFactoryTestSuite` function simplifies the process of testing your custom implementation of {@link ISemaphore | `ISemaphore`} with `vitest`.
*
* IMPORT_PATH: `"@daiso-tech/core/semaphore/test-utilities"`
* @group Utilities
* @example
* ```ts
* import { describe, expect, test, beforeEach } from "vitest";
* import { MemorySemaphoreAdapter } from "@daiso-tech/core/semaphore/memory-semaphore-adapter";
* import { SemaphoreFactory } from "@daiso-tech/core/semaphore";
* import { EventBus } from "@daiso-tech/core/event-bus";
* import { MemoryEventBusAdapter } from "@daiso-tech/core/event-bus/memory-event-bus-adapter";
* import { semaphoreFactoryTestSuite } from "@daiso-tech/core/semaphore/test-utilities";
* import { Serde } from "@daiso-tech/core/serde";
* import { SuperJsonSerdeAdapter } from "@daiso-tech/core/serde/super-json-serde-adapter";
* import type { ISemaphoreData } from "@daiso-tech/core/semaphore/contracts";
*
* describe("class: SemaphoreFactory", () => {
* semaphoreFactoryTestSuite({
* createSemaphoreFactory: () => {
* const serde = new Serde(new SuperJsonSerdeAdapter());
* const semaphoreFactory = new SemaphoreFactory({
* serde,
* adapter: new MemorySemaphoreAdapter(),
* });
* return { semaphoreFactory, serde };
* },
* beforeEach,
* describe,
* expect,
* test,
* serde,
* });
* });
* ```
*/
export function semaphoreFactoryTestSuite(settings) {
const { expect, test, createSemaphoreFactory, describe, beforeEach: beforeEach_, includeEventTests = true, includeSerdeTests = true, delayBuffer = TimeSpan.fromMilliseconds(10), timeSpanEqualityBuffer = TimeSpan.fromMilliseconds(10), eventDispatchWaitTime = TimeSpan.fromMilliseconds(10), } = settings;
let semaphoreFactory;
let serde;
const waitForSettings = {
interval: TimeSpan.fromTimeSpan(eventDispatchWaitTime).toMilliseconds(),
timeout: TimeSpan.fromTimeSpan(eventDispatchWaitTime)
.multiply(3)
.toMilliseconds(),
};
async function delayWithBuffer(ttl) {
await delay(TimeSpan.fromTimeSpan(ttl).addTimeSpan(delayBuffer));
}
const RETURN_VALUE = "RETURN_VALUE";
describe("ISemaphoreFactory tests:", () => {
beforeEach_(async () => {
const { semaphoreFactory: semaphoreFactory_, serde: serde_ } = await createSemaphoreFactory();
semaphoreFactory = semaphoreFactory_;
serde = serde_;
});
describe("Api tests:", () => {
describe("method: runOrFail", () => {
test("Should call acquireOrFail method", async () => {
const key = "a";
const ttl = null;
const limit = 1;
const semaphore = semaphoreFactory.create(key, {
ttl,
limit,
});
const acquireOrFailSpy = vi.spyOn(semaphore, "acquireOrFail");
await semaphore.runOrFail(() => {
return Promise.resolve(RETURN_VALUE);
});
expect(acquireOrFailSpy).toHaveBeenCalledTimes(1);
});
test("Should call acquireOrFail before release method", async () => {
const key = "a";
const ttl = null;
const limit = 1;
const semaphore = semaphoreFactory.create(key, {
ttl,
limit,
});
const acquireOrFailSpy = vi.spyOn(semaphore, "acquireOrFail");
const releaseSpy = vi.spyOn(semaphore, "release");
await semaphore.runOrFail(() => {
return Promise.resolve(RETURN_VALUE);
});
expect(acquireOrFailSpy).toHaveBeenCalledBefore(releaseSpy);
});
test("Should call release method", async () => {
const key = "a";
const ttl = null;
const limit = 1;
const semaphore = semaphoreFactory.create(key, {
ttl,
limit,
});
const releaseSpy = vi.spyOn(semaphore, "release");
await semaphore.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 limit = 1;
const semaphore = semaphoreFactory.create(key, {
ttl,
limit,
});
const releaseSpy = vi.spyOn(semaphore, "release");
const acquireOrFailSpy = vi.spyOn(semaphore, "acquireOrFail");
await semaphore.runOrFail(() => {
return Promise.resolve(RETURN_VALUE);
});
expect(releaseSpy).toHaveBeenCalledAfter(acquireOrFailSpy);
});
test("Should call release when an error is thrown", async () => {
const key = "a";
const ttl = null;
const limit = 1;
const semaphore = semaphoreFactory.create(key, {
ttl,
limit,
});
const releaseSpy = vi.spyOn(semaphore, "release");
class UnexpectedError extends Error {
}
try {
await semaphore.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 limit = 1;
const semaphore = semaphoreFactory.create(key, {
ttl,
limit,
});
class UnexpectedErrorA extends Error {
}
const error = semaphore.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 limit = 1;
const handlerFn = vi.fn(() => {
return Promise.resolve(RETURN_VALUE);
});
await semaphoreFactory
.create(key, {
ttl,
limit,
})
.runOrFail(handlerFn);
expect(handlerFn).toHaveBeenCalledTimes(1);
});
test("Should call handler function when slot is expired", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const limit = 1;
await semaphoreFactory
.create(key, {
ttl,
limit,
})
.acquire();
await delayWithBuffer(ttl);
const handlerFn = vi.fn(() => {
return Promise.resolve(RETURN_VALUE);
});
await semaphoreFactory
.create(key, {
ttl,
limit,
})
.runOrFail(handlerFn);
expect(handlerFn).toHaveBeenCalledTimes(1);
});
test("Should not call handler function when slot is unexpireable", async () => {
const key = "a";
const ttl = null;
const limit = 1;
const semaphore = semaphoreFactory.create(key, {
ttl,
limit,
});
await semaphore.acquire();
const handlerFn = vi.fn(() => {
return Promise.resolve(RETURN_VALUE);
});
try {
await semaphore.runOrFail(handlerFn);
}
catch (error) {
if (!(error instanceof LimitReachedSemaphoreError)) {
throw error;
}
}
await delay(delayBuffer);
expect(handlerFn).not.toHaveBeenCalled();
});
test("Should not call handler function when slot is unexpired", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const limit = 1;
const semaphore = semaphoreFactory.create(key, {
ttl,
limit,
});
await semaphore.acquire();
const handlerFn = vi.fn(() => {
return Promise.resolve(RETURN_VALUE);
});
try {
await semaphore.runOrFail(handlerFn);
}
catch (error) {
if (!(error instanceof LimitReachedSemaphoreError)) {
throw error;
}
}
await delay(delayBuffer);
expect(handlerFn).not.toHaveBeenCalled();
});
test("Should not call handler function when slot is unexpireable", async () => {
const key = "a";
const ttl = null;
const limit = 1;
await semaphoreFactory
.create(key, {
ttl,
limit,
})
.acquire();
const handlerFn = vi.fn(() => {
return Promise.resolve(RETURN_VALUE);
});
try {
await semaphoreFactory
.create(key, {
ttl,
limit,
})
.runOrFail(handlerFn);
}
catch (error) {
if (!(error instanceof LimitReachedSemaphoreError)) {
throw error;
}
}
await delay(delayBuffer);
expect(handlerFn).not.toHaveBeenCalled();
});
test("Should not call handler function when slot is unexpired", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const limit = 1;
await semaphoreFactory
.create(key, {
ttl,
limit,
})
.acquire();
const handlerFn = vi.fn(() => {
return Promise.resolve(RETURN_VALUE);
});
try {
await semaphoreFactory
.create(key, {
ttl,
limit,
})
.runOrFail(handlerFn);
}
catch (error) {
if (!(error instanceof LimitReachedSemaphoreError)) {
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 limit = 1;
const result = await semaphoreFactory
.create(key, {
ttl,
limit,
})
.runOrFail(() => {
return Promise.resolve(RETURN_VALUE);
});
expect(result).toBe(RETURN_VALUE);
});
test("Should return value when slot is expired", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const limit = 1;
await semaphoreFactory
.create(key, {
ttl,
limit,
})
.acquire();
await delayWithBuffer(ttl);
const result = await semaphoreFactory
.create(key, {
ttl,
limit,
})
.runOrFail(() => {
return Promise.resolve(RETURN_VALUE);
});
expect(result).toBe(RETURN_VALUE);
});
test("Should throw LimitReachedSemaphoreError when slot is unexpireable", async () => {
const key = "a";
const ttl = null;
const limit = 1;
const semaphore = semaphoreFactory.create(key, {
ttl,
limit,
});
await semaphore.acquire();
const result = semaphore.runOrFail(() => {
return Promise.resolve(RETURN_VALUE);
});
await expect(result).rejects.toBeInstanceOf(LimitReachedSemaphoreError);
});
test("Should throw LimitReachedSemaphoreError when slot is unexpired", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const limit = 1;
const semaphore = semaphoreFactory.create(key, {
ttl,
limit,
});
await semaphore.acquire();
const result = semaphore.runOrFail(() => {
return Promise.resolve(RETURN_VALUE);
});
await expect(result).rejects.toBeInstanceOf(LimitReachedSemaphoreError);
});
test("Should throw LimitReachedSemaphoreError when slot is unexpireable", async () => {
const key = "a";
const ttl = null;
const limit = 1;
await semaphoreFactory
.create(key, {
ttl,
limit,
})
.acquire();
const result = semaphoreFactory
.create(key, {
ttl,
limit,
})
.runOrFail(() => {
return Promise.resolve(RETURN_VALUE);
});
await expect(result).rejects.toBeInstanceOf(LimitReachedSemaphoreError);
});
test("Should throw LimitReachedSemaphoreError when slot is unexpired", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const limit = 1;
await semaphoreFactory
.create(key, {
ttl,
limit,
})
.acquire();
const result = semaphoreFactory
.create(key, {
ttl,
limit,
})
.runOrFail(() => {
return Promise.resolve(RETURN_VALUE);
});
await expect(result).rejects.toBeInstanceOf(LimitReachedSemaphoreError);
});
});
describe("method: acquire", () => {
test("Should return true when key doesnt exists", async () => {
const key = "a";
const limit = 2;
const ttl = null;
const result = await semaphoreFactory
.create(key, {
limit,
ttl,
})
.acquire();
expect(result).toBe(true);
});
test("Should return true when key exists and slot is expired", async () => {
const key = "a";
const limit = 2;
const ttl = TimeSpan.fromMilliseconds(50);
const semaphore = semaphoreFactory.create(key, {
limit,
ttl,
});
await semaphore.acquire();
await delayWithBuffer(ttl);
const result = await semaphore.acquire();
expect(result).toBe(true);
});
test("Should return true when limit is not reached", async () => {
const key = "a";
const limit = 2;
const ttl = null;
const semaphore1 = semaphoreFactory.create(key, {
limit,
ttl,
});
await semaphore1.acquire();
const semaphore2 = semaphoreFactory.create(key, {
limit,
ttl,
});
const result = await semaphore2.acquire();
expect(result).toBe(true);
});
test("Should return false when limit is reached", async () => {
const key = "a";
const limit = 2;
const ttl = null;
const semaphore1 = semaphoreFactory.create(key, {
limit,
ttl,
});
await semaphore1.acquire();
const semaphore2 = semaphoreFactory.create(key, {
limit,
ttl,
});
await semaphore2.acquire();
const semaphore3 = semaphoreFactory.create(key, {
limit,
ttl,
});
const result = await semaphore3.acquire();
expect(result).toBe(false);
});
test("Should return true when one slot is expired", async () => {
const key = "a";
const limit = 2;
const ttl1 = null;
const semaphore1 = semaphoreFactory.create(key, {
limit,
ttl: ttl1,
});
await semaphore1.acquire();
const ttl2 = TimeSpan.fromMilliseconds(50);
const semaphore2 = semaphoreFactory.create(key, {
limit,
ttl: ttl2,
});
await semaphore2.acquire();
await delayWithBuffer(ttl2);
const ttl3 = null;
const semaphore3 = semaphoreFactory.create(key, {
ttl: ttl3,
limit,
});
const result = await semaphore3.acquire();
expect(result).toBe(true);
});
test("Should return true when slot exists, is unexpireable and acquired multiple times", async () => {
const key = "a";
const limit = 2;
const ttl = null;
const semaphore = semaphoreFactory.create(key, {
limit,
ttl,
});
await semaphore.acquire();
const result = await semaphore.acquire();
expect(result).toBe(true);
});
test("Should return true when slot exists, is unexpired and acquired multiple times", async () => {
const key = "a";
const limit = 2;
const ttl = TimeSpan.fromMilliseconds(50);
const semaphore = semaphoreFactory.create(key, {
limit,
ttl,
});
await semaphore.acquire();
const result = await semaphore.acquire();
expect(result).toBe(true);
});
test("Should not acquire a slot when slot exists, is unexpireable and acquired multiple times", async () => {
const key = "a";
const limit = 2;
const ttl = null;
const semaphore1 = semaphoreFactory.create(key, {
limit,
ttl,
});
await semaphore1.acquire();
await semaphore1.acquire();
const semaphore2 = semaphoreFactory.create(key, {
limit,
ttl,
});
const result = await semaphore2.acquire();
expect(result).toBe(true);
});
test("Should not acquire a slot when slot exists, is unexpired and acquired multiple times", async () => {
const key = "a";
const limit = 2;
const ttl = TimeSpan.fromMilliseconds(50);
const semaphore1 = semaphoreFactory.create(key, {
limit,
ttl,
});
await semaphore1.acquire();
await semaphore1.acquire();
const semaphore2 = semaphoreFactory.create(key, {
ttl,
limit,
});
const result = await semaphore2.acquire();
expect(result).toBe(true);
});
test("Should not update limit when slot count is more than 0", async () => {
const key = "a";
const limit = 2;
const ttl = null;
const semaphore1 = semaphoreFactory.create(key, {
limit,
ttl,
});
await semaphore1.acquire();
const newLimit = 3;
const semaphore2 = semaphoreFactory.create(key, {
limit: newLimit,
ttl,
});
await semaphore2.acquire();
const semaphore3 = semaphoreFactory.create(key, {
limit: newLimit,
ttl,
});
const result1 = await semaphore3.acquire();
expect(result1).toBe(false);
const state = (await semaphore3.getState());
expect(state.limit).toBe(limit);
});
});
describe("method: acquireOrFail", () => {
test("Should not throw error when key doesnt exists", async () => {
const key = "a";
const limit = 2;
const ttl = null;
const result = semaphoreFactory
.create(key, {
limit,
ttl,
})
.acquireOrFail();
await expect(result).resolves.toBeUndefined();
});
test("Should not throw error when key exists and slot is expired", async () => {
const key = "a";
const limit = 2;
const ttl = TimeSpan.fromMilliseconds(50);
const semaphore = semaphoreFactory.create(key, {
limit,
ttl,
});
await semaphore.acquire();
await delayWithBuffer(ttl);
const result = semaphore.acquireOrFail();
await expect(result).resolves.toBeUndefined();
});
test("Should not throw error when limit is not reached", async () => {
const key = "a";
const limit = 2;
const ttl = null;
const semaphore1 = semaphoreFactory.create(key, {
limit,
ttl,
});
await semaphore1.acquire();
const semaphore2 = semaphoreFactory.create(key, {
limit,
ttl,
});
const result = semaphore2.acquireOrFail();
await expect(result).resolves.toBeUndefined();
});
test("Should throw LimitReachedSemaphoreError when limit is reached", async () => {
const key = "a";
const limit = 2;
const ttl = null;
const semaphore1 = semaphoreFactory.create(key, {
limit,
ttl,
});
await semaphore1.acquire();
const semaphore2 = semaphoreFactory.create(key, {
limit,
ttl,
});
await semaphore2.acquire();
const semaphore3 = semaphoreFactory.create(key, {
limit,
ttl,
});
const result = semaphore3.acquireOrFail();
await expect(result).rejects.toBeInstanceOf(LimitReachedSemaphoreError);
});
test("Should not throw error when one slot is expired", async () => {
const key = "a";
const limit = 2;
const ttl1 = null;
const semaphore1 = semaphoreFactory.create(key, {
limit,
ttl: ttl1,
});
await semaphore1.acquire();
const ttl2 = TimeSpan.fromMilliseconds(50);
const semaphore2 = semaphoreFactory.create(key, {
limit,
ttl: ttl2,
});
await semaphore2.acquire();
await delayWithBuffer(ttl2);
const ttl3 = null;
const semaphore3 = semaphoreFactory.create(key, {
ttl: ttl3,
limit,
});
const result = semaphore3.acquireOrFail();
await expect(result).resolves.toBeUndefined();
});
test("Should not throw error when slot exists, is unexpireable and acquired multiple times", async () => {
const key = "a";
const limit = 2;
const ttl = null;
const semaphore = semaphoreFactory.create(key, {
limit,
ttl,
});
await semaphore.acquire();
const result = semaphore.acquireOrFail();
await expect(result).resolves.toBeUndefined();
});
test("Should not throw error when slot exists, is unexpired and acquired multiple times", async () => {
const key = "a";
const limit = 2;
const ttl = TimeSpan.fromMilliseconds(50);
const semaphore = semaphoreFactory.create(key, {
limit,
ttl,
});
await semaphore.acquire();
const result = semaphore.acquireOrFail();
await expect(result).resolves.toBeUndefined();
});
test("Should not acquire a slot when slot exists, is unexpireable and acquired multiple times", async () => {
const key = "a";
const limit = 2;
const ttl = null;
const semaphore1 = semaphoreFactory.create(key, {
limit,
ttl,
});
await semaphore1.acquire();
await semaphore1.acquire();
const semaphore2 = semaphoreFactory.create(key, {
limit,
ttl,
});
const result = semaphore2.acquireOrFail();
await expect(result).resolves.toBeUndefined();
});
test("Should not acquire a slot when slot exists, is unexpired and acquired multiple times", async () => {
const key = "a";
const limit = 2;
const ttl = TimeSpan.fromMilliseconds(50);
const semaphore1 = semaphoreFactory.create(key, {
limit,
ttl,
});
await semaphore1.acquire();
await semaphore1.acquire();
const semaphore2 = semaphoreFactory.create(key, {
ttl,
limit,
});
const result = semaphore2.acquireOrFail();
await expect(result).resolves.toBeUndefined();
});
test("Should not update limit when slot count is more than 0", async () => {
const key = "a";
const limit = 2;
const ttl = null;
const semaphore1 = semaphoreFactory.create(key, {
limit,
ttl,
});
await semaphore1.acquire();
const newLimit = 3;
const semaphore2 = semaphoreFactory.create(key, {
limit: newLimit,
ttl,
});
await semaphore2.acquire();
const semaphore3 = semaphoreFactory.create(key, {
limit: newLimit,
ttl,
});
const result1 = semaphore3.acquireOrFail();
await expect(result1).rejects.toBeInstanceOf(LimitReachedSemaphoreError);
const state = (await semaphore3.getState());
expect(state.limit).toBe(limit);
});
});
describe("method: release", () => {
test("Should return false when key doesnt exists", async () => {
const key = "a";
const limit = 2;
const ttl = null;
await semaphoreFactory
.create(key, {
limit,
ttl,
})
.acquire();
const noneExistingKey = "c";
const result = await semaphoreFactory
.create(noneExistingKey, {
limit,
ttl,
})
.release();
expect(result).toBe(false);
});
test("Should return false when slot doesnt exists", async () => {
const key = "a";
const limit = 2;
const ttl = null;
await semaphoreFactory
.create(key, {
limit,
ttl,
})
.acquire();
const noneExistingSlotId = "1";
const result = await semaphoreFactory
.create(key, {
limit,
ttl,
slotId: noneExistingSlotId,
})
.release();
expect(result).toBe(false);
});
test("Should return false when slot is expired", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const limit = 2;
const semaphore = semaphoreFactory.create(key, {
ttl,
limit,
});
await semaphore.acquire();
await delayWithBuffer(ttl);
const result = await semaphore.release();
expect(result).toBe(false);
});
test("Should return true when slot exists and is unexpired", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const limit = 2;
const semaphore = semaphoreFactory.create(key, {
ttl,
limit,
});
await semaphore.acquire();
const result = await semaphore.release();
expect(result).toBe(true);
});
test("Should return true when slot exists and is unexpireable", async () => {
const key = "a";
const ttl = null;
const limit = 2;
const semaphore = semaphoreFactory.create(key, {
ttl,
limit,
});
await semaphore.acquire();
const result = await semaphore.release();
expect(result).toBe(true);
});
test("Should update limit when slot count is 0", async () => {
const key = "a";
const limit = 2;
const ttl = null;
const semaphore1 = semaphoreFactory.create(key, {
ttl,
limit,
});
await semaphore1.acquire();
const semaphore2 = semaphoreFactory.create(key, {
ttl,
limit,
});
await semaphore2.acquire();
await semaphore1.release();
await semaphore2.release();
const newLimit = 3;
const semaphore3 = semaphoreFactory.create(key, {
ttl,
limit: newLimit,
});
await semaphore3.acquire();
const semaphore4 = semaphoreFactory.create(key, {
ttl,
limit: newLimit,
});
await semaphore4.acquire();
const semaphore5 = semaphoreFactory.create(key, {
ttl,
limit: newLimit,
});
const result1 = await semaphore5.acquire();
expect(result1).toBe(true);
const state = (await semaphore5.getState());
expect(state.limit).toBe(newLimit);
const semaphore6 = semaphoreFactory.create(key, {
ttl,
limit: newLimit,
});
const result3 = await semaphore6.acquire();
expect(result3).toBe(false);
});
test("Should decrement slot count when one slot is released", async () => {
const key = "a";
const limit = 2;
const ttl = null;
const semaphore1 = semaphoreFactory.create(key, {
ttl,
limit,
});
await semaphore1.acquire();
const semaphore2 = semaphoreFactory.create(key, {
ttl,
limit,
});
await semaphore2.acquire();
await semaphore1.release();
await semaphore2.release();
const semaphore3 = semaphoreFactory.create(key, {
ttl,
limit,
});
const result1 = await semaphore3.acquire();
expect(result1).toBe(true);
const semaphore4 = semaphoreFactory.create(key, {
ttl,
limit,
});
const result2 = await semaphore4.acquire();
expect(result2).toBe(true);
});
});
describe("method: releaseOrFail", () => {
test("Should throw FailedReleaseSemaphoreError when key doesnt exists", async () => {
const key = "a";
const limit = 2;
const ttl = null;
await semaphoreFactory
.create(key, {
limit,
ttl,
})
.acquire();
const noneExistingKey = "c";
const result = semaphoreFactory
.create(noneExistingKey, {
limit,
ttl,
})
.releaseOrFail();
await expect(result).rejects.toBeInstanceOf(FailedReleaseSemaphoreError);
});
test("Should throw FailedReleaseSemaphoreError when slot doesnt exists", async () => {
const key = "a";
const limit = 2;
const ttl = null;
await semaphoreFactory
.create(key, {
limit,
ttl,
})
.acquire();
const noneExistingSlotId = "1";
const result = semaphoreFactory
.create(key, {
limit,
ttl,
slotId: noneExistingSlotId,
})
.releaseOrFail();
await expect(result).rejects.toBeInstanceOf(FailedReleaseSemaphoreError);
});
test("Should throw FailedReleaseSemaphoreError when slot is expired", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const limit = 2;
const semaphore1 = semaphoreFactory.create(key, {
ttl,
limit,
});
await semaphore1.acquire();
await delayWithBuffer(ttl);
const result = semaphoreFactory
.create(key, {
ttl,
limit,
})
.releaseOrFail();
await expect(result).rejects.toBeInstanceOf(FailedReleaseSemaphoreError);
});
test("Should throw FailedReleaseSemaphoreError when slot exists, is expired", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const limit = 2;
const semaphore = semaphoreFactory.create(key, {
ttl,
limit,
});
await semaphore.acquire();
await delayWithBuffer(ttl);
const result = semaphore.releaseOrFail();
await expect(result).rejects.toBeInstanceOf(FailedReleaseSemaphoreError);
});
test("Should not throw error when slot exists and is unexpired", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const limit = 2;
const semaphore = semaphoreFactory.create(key, {
ttl,
limit,
});
await semaphore.acquire();
const result = semaphore.releaseOrFail();
await expect(result).resolves.toBeUndefined();
});
test("Should not throw error when slot exists and is unexpireable", async () => {
const key = "a";
const ttl = null;
const limit = 2;
const semaphore = semaphoreFactory.create(key, {
ttl,
limit,
});
await semaphore.acquire();
const result = semaphore.releaseOrFail();
await expect(result).resolves.toBeUndefined();
});
test("Should update limit when slot count is 0", async () => {
const key = "a";
const limit = 2;
const ttl = null;
const semaphore1 = semaphoreFactory.create(key, {
ttl,
limit,
});
await semaphore1.acquire();
const semaphore2 = semaphoreFactory.create(key, {
ttl,
limit,
});
await semaphore2.acquire();
await semaphore1.release();
await semaphore2.releaseOrFail();
const newLimit = 3;
const semaphore3 = semaphoreFactory.create(key, {
ttl,
limit: newLimit,
});
await semaphore3.acquire();
const semaphore4 = semaphoreFactory.create(key, {
ttl,
limit: newLimit,
});
await semaphore4.acquire();
const semaphore5 = semaphoreFactory.create(key, {
ttl,
limit: newLimit,
});
const result1 = await semaphore5.acquire();
expect(result1).toBe(true);
const state = (await semaphore5.getState());
expect(state.limit).toBe(newLimit);
const semaphore6 = semaphoreFactory.create(key, {
ttl,
limit: newLimit,
});
const result3 = await semaphore6.acquire();
expect(result3).toBe(false);
});
test("Should decrement slot count when one slot is released", async () => {
const key = "a";
const limit = 2;
const ttl = null;
const semaphore1 = semaphoreFactory.create(key, {
ttl,
limit,
});
await semaphore1.acquire();
const semaphore2 = semaphoreFactory.create(key, {
ttl,
limit,
});
await semaphore2.acquire();
await semaphore1.release();
await semaphore2.releaseOrFail();
const semaphore3 = semaphoreFactory.create(key, {
ttl,
limit,
});
const result1 = await semaphore3.acquire();
expect(result1).toBe(true);
const semaphore4 = semaphoreFactory.create(key, {
ttl,
limit,
});
const result2 = await semaphore4.acquire();
expect(result2).toBe(true);
});
});
describe("method: forceReleaseAll", () => {
test("Should return false when key doesnt exists", async () => {
const key = "a";
const limit = 2;
const ttl = null;
const semaphore1 = semaphoreFactory.create(key, {
limit,
ttl,
});
await semaphore1.acquire();
const noneExistingKey = "c";
const semaphore2 = semaphoreFactory.create(noneExistingKey, {
limit,
ttl,
});
const result = await semaphore2.forceReleaseAll();
expect(result).toBe(false);
});
test("Should return false when slot is expired", async () => {
const key = "a";
const ttl = TimeSpan.fromMilliseconds(50);
const limit = 2;
const semaphore = semaphoreFactory.create(key, {
limit,
ttl,
});
await semaphore.acquire();
await delayWithBuffer(ttl);
const result = await semaphore.forceReleaseAll();
expect(result).toBe(false);
});
test("Should return false when no slots are acquired", async () => {
const key = "a";
const ttl = null;
const limit = 2;
const semaphore1 = semaphoreFactory.create(key, {
limit,
ttl,
});
await semaphore1.acquire();
const semaphore2 = semaphoreFactory.create(key, {
limit,
ttl,
});
await semaphore2.acquire();
await semaphore1.release();
await semaphore2.release();
const result = await semaphore1.forceReleaseAll();
expect(result).toBe(false);
});
test("Should return true when at least 1 slot is acquired", async () => {
const key = "a";
const ttl = null;
const limit = 2;
const semaphore = semaphoreFactory.create(key, {
limit,
ttl,
});
awai