diff --git a/packages/backend-core/src/redis/tests/redlockImpl.spec.ts b/packages/backend-core/src/redis/tests/redlockImpl.spec.ts index 0661b8f7cc..b3dd572ba9 100644 --- a/packages/backend-core/src/redis/tests/redlockImpl.spec.ts +++ b/packages/backend-core/src/redis/tests/redlockImpl.spec.ts @@ -1,32 +1,98 @@ -import { LockName, LockType } from "@budibase/types" +import { LockName, LockType, LockOptions } from "@budibase/types" +import tk from "timekeeper" import { doWithLock } from "../redlockImpl" -import { DBTestConfiguration } from "../../../tests" +import { DBTestConfiguration, generator } from "../../../tests" + +tk.reset() describe("redlockImpl", () => { describe("doWithLock", () => { - it("should execute the task and return the result", async () => { - const mockTask = jest.fn().mockResolvedValue("mockResult") + const config = new DBTestConfiguration() + const lockTtl = 30 - // Define test options - const testOpts = { - name: LockName.PERSIST_WRITETHROUGH, - type: LockType.AUTO_EXTEND, - ttl: 5, - } - - // Call the function with the mock lock and task - const config = new DBTestConfiguration() - const result = await config.doInTenant(() => - doWithLock(testOpts, async () => { - await new Promise(r => setTimeout(() => r(), 10)) - return mockTask() + function runLockWithExecutionTime({ + opts, + task, + executionTimeMs, + }: { + opts: LockOptions + task: () => Promise + executionTimeMs: number + }) { + return config.doInTenant(() => + doWithLock(opts, async () => { + await new Promise(r => setTimeout(() => r(), executionTimeMs)) + return task() }) ) + } + + it.each(Object.values(LockType))( + "should return the task value", + async (lockType: LockType) => { + const expectedResult = generator.guid() + const mockTask = jest.fn().mockResolvedValue(expectedResult) + + const opts = { + name: LockName.PERSIST_WRITETHROUGH, + type: lockType, + ttl: lockTtl, + } + + const result = await runLockWithExecutionTime({ + opts, + task: mockTask, + executionTimeMs: 0, + }) + + expect(result.executed).toBe(true) + expect(result.executed && result.result).toBe(expectedResult) + expect(mockTask).toHaveBeenCalledTimes(1) + } + ) + + it("should extend when type is autoextend", async () => { + const expectedResult = generator.guid() + const mockTask = jest.fn().mockResolvedValue(expectedResult) + + const opts = { + name: LockName.PERSIST_WRITETHROUGH, + type: LockType.AUTO_EXTEND, + ttl: lockTtl, + } + + const result = await runLockWithExecutionTime({ + opts, + task: mockTask, + executionTimeMs: lockTtl * 2, + }) - // Assert the result and verify function calls expect(result.executed).toBe(true) - expect(result.executed && result.result).toBe("mockResult") + expect(result.executed && result.result).toBe(expectedResult) expect(mockTask).toHaveBeenCalledTimes(1) }) + + it.each(Object.values(LockType).filter(t => t !== LockType.AUTO_EXTEND))( + "should timeout when type is %s", + async (lockType: LockType) => { + const mockTask = jest.fn().mockResolvedValue("mockResult") + + const opts = { + name: LockName.PERSIST_WRITETHROUGH, + type: lockType, + ttl: lockTtl, + } + + await expect( + runLockWithExecutionTime({ + opts, + task: mockTask, + executionTimeMs: lockTtl * 2, + }) + ).rejects.toThrowError( + `Unable to fully release the lock on resource \"lock:${config.tenantId}_persist_writethrough\".` + ) + } + ) }) })