Files
kestrelos/test/unit/asyncLock.spec.js
Madison Grubb b0e8dd7ad9
Some checks failed
ci/woodpecker/pr/pr Pipeline failed
make kestrel a tak server, so that it can send and receive pois as cots data
2026-02-17 10:42:53 -05:00

118 lines
3.1 KiB
JavaScript

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 () => {
let executed = false
await acquire('test', async () => {
executed = true
return 42
})
expect(executed).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 = []
for (let i = 0; i < 5; i++) {
promises.push(
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)
for (let i = 0; i < 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 = []
for (let i = 0; i < 5; i++) {
promises.push(
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 () => {
let callCount = 0
try {
await acquire('error-key', async () => {
callCount++
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++
return 'success'
})
expect(callCount).toBe(2)
})
it('maintains lock ordering', async () => {
const order = []
const promises = []
for (let i = 0; i < 3; i++) {
const idx = i
promises.push(
acquire('ordered', async () => {
order.push(`before-${idx}`)
await new Promise(resolve => setTimeout(resolve, 5))
order.push(`after-${idx}`)
}),
)
}
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'])
})
})