import { afterEach, expect, test, vi } from "vitest" import { CoordinationAbortError, CoordinationTimeoutError, Mutex, } from "#Source/orchestration/index.ts" afterEach(() => { vi.clearAllTimers() vi.useRealTimers() vi.restoreAllMocks() }) test("Mutex reports lock state and wakes queued acquirers in FIFO order", async () => { const mutex = new Mutex() expect(mutex.isLocked()).toBe(false) expect(mutex.getPendingCount()).toBe(0) const firstPermit = mutex.tryAcquire() expect(firstPermit).toBeDefined() expect(mutex.isLocked()).toBe(true) const secondPermitPromise = mutex.acquire() const thirdPermitPromise = mutex.acquire() expect(mutex.getPendingCount()).toBe(2) expect(mutex.tryAcquire()).toBeUndefined() firstPermit?.release() const secondPermit = await secondPermitPromise let thirdResolved = false void thirdPermitPromise.then(() => { thirdResolved = true }) await Promise.resolve() expect(mutex.isLocked()).toBe(true) expect(mutex.getPendingCount()).toBe(1) expect(thirdResolved).toBe(false) secondPermit.release() const thirdPermit = await thirdPermitPromise expect(mutex.getPendingCount()).toBe(0) expect(mutex.isLocked()).toBe(true) thirdPermit.release() expect(mutex.isLocked()).toBe(false) }) test("Mutex acquire rejects on timeout and abort while cleaning pending state", async () => { vi.useFakeTimers() const mutex = new Mutex() const blockingPermit = mutex.tryAcquire() expect(blockingPermit).toBeDefined() const timeoutPromise = mutex.acquire({ timeout: 10 }) const timeoutExpectation = expect(timeoutPromise).rejects.toBeInstanceOf(CoordinationTimeoutError) expect(mutex.getPendingCount()).toBe(1) await vi.advanceTimersByTimeAsync(10) await timeoutExpectation expect(mutex.getPendingCount()).toBe(0) const abortController = new AbortController() const abortPromise = mutex.acquire({ abortSignal: abortController.signal }) const abortExpectation = expect(abortPromise).rejects.toBeInstanceOf(CoordinationAbortError) expect(mutex.getPendingCount()).toBe(1) abortController.abort("cancelled") await abortExpectation expect(mutex.getPendingCount()).toBe(0) blockingPermit?.release() }) test("Mutex runExclusive releases the lock after callback success or failure", async () => { const mutex = new Mutex() await expect(mutex.runExclusive(() => "done")).resolves.toBe("done") expect(mutex.isLocked()).toBe(false) const error = new Error("boom") await expect( mutex.runExclusive(() => { throw error }), ).rejects.toThrow(error) expect(mutex.isLocked()).toBe(false) }) test("Mutex is silent by default when release is called more than once", () => { const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => { // no-op to silence warnings during test }) const mutex = new Mutex() const permit = mutex.tryAcquire() expect(permit).toBeDefined() permit?.release() permit?.release() permit?.release() expect(warnSpy).not.toHaveBeenCalled() }) test("Mutex uses custom duplicate-release handler when provided", () => { const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => { // no-op to silence warnings during test }) const onDuplicateRelease = vi.fn() const mutex = new Mutex({ onDuplicateRelease }) const permit = mutex.tryAcquire() expect(permit).toBeDefined() permit?.release() permit?.release() permit?.release() expect(onDuplicateRelease).toHaveBeenCalledTimes(2) expect(onDuplicateRelease).toHaveBeenCalledWith("Mutex permit release was called more than once.") expect(warnSpy).not.toHaveBeenCalled() })