import { describe, it, expect, beforeEach } from 'vitest' import { acquire, clearLocks } from '../../server/utils/asyncLock.js' describe('asyncLock', () => { beforeEach(() => { clearLocks() }) it('executes callback immediately when no lock exists', async () => { const executed = { value: false } await acquire('test', async () => { executed.value = true return 42 }) expect(executed.value).toBe(true) }) it('returns callback result', async () => { const result = await acquire('test', async () => { return { value: 123 } }) expect(result).toEqual({ value: 123 }) }) it('serializes concurrent operations on same key', async () => { const results = [] const promises = Array.from({ length: 5 }, (_, i) => acquire('same-key', async () => { results.push(`start-${i}`) await new Promise(resolve => setTimeout(resolve, 10)) results.push(`end-${i}`) return i }), ) await Promise.all(promises) // Operations should be serialized: start-end pairs should not interleave expect(results.length).toBe(10) Array.from({ length: 5 }, (_, i) => { expect(results[i * 2]).toBe(`start-${i}`) expect(results[i * 2 + 1]).toBe(`end-${i}`) }) }) it('allows parallel operations on different keys', async () => { const results = [] const promises = Array.from({ length: 5 }, (_, i) => acquire(`key-${i}`, async () => { results.push(`start-${i}`) await new Promise(resolve => setTimeout(resolve, 10)) results.push(`end-${i}`) return i }), ) await Promise.all(promises) // Different keys can run in parallel expect(results.length).toBe(10) // All starts should come before all ends (parallel execution) const starts = results.filter(r => r.startsWith('start')) const ends = results.filter(r => r.startsWith('end')) expect(starts.length).toBe(5) expect(ends.length).toBe(5) }) it('handles errors and releases lock', async () => { const callCount = { value: 0 } try { await acquire('error-key', async () => { callCount.value++ throw new Error('Test error') }) } catch (error) { expect(error.message).toBe('Test error') } // Lock should be released, next operation should execute await acquire('error-key', async () => { callCount.value++ return 'success' }) expect(callCount.value).toBe(2) }) it('maintains lock ordering', async () => { const order = [] const promises = Array.from({ length: 3 }, (_, i) => acquire('ordered', async () => { order.push(`before-${i}`) await new Promise(resolve => setTimeout(resolve, 5)) order.push(`after-${i}`) }), ) await Promise.all(promises) // Should execute in order: before-0, after-0, before-1, after-1, before-2, after-2 expect(order).toEqual(['before-0', 'after-0', 'before-1', 'after-1', 'before-2', 'after-2']) }) })