import { afterEach, expect, test, vi } from "vitest" import { CoordinationAbortError, CoordinationTimeoutError, Semaphore, } from "#Source/orchestration/index.ts" afterEach(() => { vi.clearAllTimers() vi.useRealTimers() vi.restoreAllMocks() }) test("Semaphore tracks capacity and grants permits in FIFO order", async () => { const semaphore = new Semaphore(2) expect(semaphore.getMaxConcurrency()).toBe(2) expect(semaphore.getActiveCount()).toBe(0) expect(semaphore.getAvailableCount()).toBe(2) expect(semaphore.isSaturated()).toBe(false) const firstPermit = semaphore.tryAcquire() const secondPermit = semaphore.tryAcquire() expect(firstPermit).toBeDefined() expect(secondPermit).toBeDefined() expect(semaphore.isSaturated()).toBe(true) expect(semaphore.getActiveCount()).toBe(2) expect(semaphore.getAvailableCount()).toBe(0) const thirdPermitPromise = semaphore.acquire() const fourthPermitPromise = semaphore.acquire() expect(semaphore.getPendingCount()).toBe(2) expect(semaphore.tryAcquire()).toBeUndefined() firstPermit?.release() const thirdPermit = await thirdPermitPromise let fourthResolved = false void fourthPermitPromise.then(() => { fourthResolved = true }) await Promise.resolve() expect(semaphore.getPendingCount()).toBe(1) expect(fourthResolved).toBe(false) secondPermit?.release() const fourthPermit = await fourthPermitPromise expect(semaphore.getPendingCount()).toBe(0) thirdPermit.release() fourthPermit.release() expect(semaphore.getActiveCount()).toBe(0) expect(semaphore.getAvailableCount()).toBe(2) expect(semaphore.isSaturated()).toBe(false) }) test("Semaphore acquire rejects on timeout and abort without leaking queue entries", async () => { vi.useFakeTimers() const semaphore = new Semaphore(1) const blockingPermit = semaphore.tryAcquire() expect(blockingPermit).toBeDefined() const timeoutPromise = semaphore.acquire({ timeout: 15 }) const timeoutExpectation = expect(timeoutPromise).rejects.toBeInstanceOf(CoordinationTimeoutError) expect(semaphore.getPendingCount()).toBe(1) await vi.advanceTimersByTimeAsync(15) await timeoutExpectation expect(semaphore.getPendingCount()).toBe(0) const abortController = new AbortController() const abortPromise = semaphore.acquire({ abortSignal: abortController.signal }) const abortExpectation = expect(abortPromise).rejects.toBeInstanceOf(CoordinationAbortError) expect(semaphore.getPendingCount()).toBe(1) abortController.abort("stopped") await abortExpectation expect(semaphore.getPendingCount()).toBe(0) blockingPermit?.release() }) test("Semaphore runExclusive releases capacity after callback failure", async () => { const semaphore = new Semaphore(1) const error = new Error("boom") await expect(semaphore.runExclusive(() => 1)).resolves.toBe(1) expect(semaphore.getActiveCount()).toBe(0) await expect( semaphore.runExclusive(() => { throw error }), ).rejects.toThrow(error) expect(semaphore.getActiveCount()).toBe(0) expect(semaphore.isSaturated()).toBe(false) }) test("Semaphore duplicate release stays silent by default and uses custom handler when provided", () => { const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => { // no-op to silence warnings during test }) const silentSemaphore = new Semaphore(1) const silentPermit = silentSemaphore.tryAcquire() expect(silentPermit).toBeDefined() silentPermit?.release() silentPermit?.release() silentPermit?.release() expect(silentSemaphore.getActiveCount()).toBe(0) expect(warnSpy).not.toHaveBeenCalled() const onDuplicateRelease = vi.fn() const customSemaphore = new Semaphore(1, { onDuplicateRelease }) const customPermit = customSemaphore.tryAcquire() expect(customPermit).toBeDefined() customPermit?.release() customPermit?.release() customPermit?.release() expect(customSemaphore.getActiveCount()).toBe(0) expect(onDuplicateRelease).toHaveBeenCalledTimes(2) expect(onDuplicateRelease).toHaveBeenCalledWith( "Semaphore permit release was called more than once.", ) expect(warnSpy).not.toHaveBeenCalled() })