make kestrel a tak server, so that it can send and receive pois as cots data
Some checks failed
ci/woodpecker/pr/pr Pipeline failed

This commit is contained in:
Madison Grubb
2026-02-17 10:42:53 -05:00
parent b18283d3b3
commit b0e8dd7ad9
96 changed files with 5767 additions and 500 deletions

View File

@@ -1,4 +1,4 @@
import { describe, it, expect, beforeEach } from 'vitest'
import { describe, it, expect, beforeEach, vi } from 'vitest'
import {
createSession,
getLiveSession,
@@ -6,15 +6,23 @@ import {
deleteLiveSession,
getActiveSessions,
getActiveSessionByUserId,
getOrCreateSession,
clearSessions,
} from '../../../server/utils/liveSessions.js'
vi.mock('../../../server/utils/mediasoup.js', () => ({
getProducer: vi.fn().mockReturnValue(null),
getTransport: vi.fn().mockReturnValue(null),
closeRouter: vi.fn().mockResolvedValue(undefined),
}))
describe('liveSessions', () => {
let sessionId
beforeEach(() => {
beforeEach(async () => {
clearSessions()
sessionId = createSession('test-user', 'Test Session').id
const session = await createSession('test-user', 'Test Session')
sessionId = session.id
})
it('creates a session with WebRTC fields', () => {
@@ -28,15 +36,15 @@ describe('liveSessions', () => {
expect(session.transportId).toBeNull()
})
it('updates location', () => {
updateLiveSession(sessionId, { lat: 37.7, lng: -122.4 })
it('updates location', async () => {
await updateLiveSession(sessionId, { lat: 37.7, lng: -122.4 })
const session = getLiveSession(sessionId)
expect(session.lat).toBe(37.7)
expect(session.lng).toBe(-122.4)
})
it('updates WebRTC fields', () => {
updateLiveSession(sessionId, { routerId: 'router-1', producerId: 'producer-1', transportId: 'transport-1' })
it('updates WebRTC fields', async () => {
await updateLiveSession(sessionId, { routerId: 'router-1', producerId: 'producer-1', transportId: 'transport-1' })
const session = getLiveSession(sessionId)
expect(session.routerId).toBe('router-1')
expect(session.producerId).toBe('producer-1')
@@ -44,7 +52,7 @@ describe('liveSessions', () => {
})
it('returns hasStream instead of hasSnapshot', async () => {
updateLiveSession(sessionId, { producerId: 'producer-1' })
await updateLiveSession(sessionId, { producerId: 'producer-1' })
const active = await getActiveSessions()
const session = active.find(s => s.id === sessionId)
expect(session).toBeDefined()
@@ -58,27 +66,27 @@ describe('liveSessions', () => {
expect(session.hasStream).toBe(false)
})
it('deletes a session', () => {
deleteLiveSession(sessionId)
it('deletes a session', async () => {
await deleteLiveSession(sessionId)
const session = getLiveSession(sessionId)
expect(session).toBeUndefined()
})
it('getActiveSessionByUserId returns session for same user when active', () => {
const found = getActiveSessionByUserId('test-user')
it('getActiveSessionByUserId returns session for same user when active', async () => {
const found = await getActiveSessionByUserId('test-user')
expect(found).toBeDefined()
expect(found.id).toBe(sessionId)
})
it('getActiveSessionByUserId returns undefined for unknown user', () => {
const found = getActiveSessionByUserId('other-user')
it('getActiveSessionByUserId returns undefined for unknown user', async () => {
const found = await getActiveSessionByUserId('other-user')
expect(found).toBeUndefined()
})
it('getActiveSessionByUserId returns undefined for expired session', () => {
it('getActiveSessionByUserId returns undefined for expired session', async () => {
const session = getLiveSession(sessionId)
session.updatedAt = Date.now() - 120_000
const found = getActiveSessionByUserId('test-user')
const found = await getActiveSessionByUserId('test-user')
expect(found).toBeUndefined()
})
@@ -89,4 +97,43 @@ describe('liveSessions', () => {
expect(active.find(s => s.id === sessionId)).toBeUndefined()
expect(getLiveSession(sessionId)).toBeUndefined()
})
it('getActiveSessions runs cleanup for expired session with producer and transport', async () => {
const { getProducer, getTransport, closeRouter } = await import('../../../server/utils/mediasoup.js')
const mockProducer = { close: vi.fn() }
const mockTransport = { close: vi.fn() }
getProducer.mockReturnValue(mockProducer)
getTransport.mockReturnValue(mockTransport)
closeRouter.mockResolvedValue(undefined)
await updateLiveSession(sessionId, { producerId: 'p1', transportId: 't1', routerId: 'r1' })
const session = getLiveSession(sessionId)
session.updatedAt = Date.now() - 120_000
const active = await getActiveSessions()
expect(active.find(s => s.id === sessionId)).toBeUndefined()
expect(mockProducer.close).toHaveBeenCalled()
expect(mockTransport.close).toHaveBeenCalled()
expect(closeRouter).toHaveBeenCalledWith(sessionId)
})
it('getOrCreateSession returns existing active session', async () => {
const session = await getOrCreateSession('test-user', 'New Label')
expect(session.id).toBe(sessionId)
expect(session.userId).toBe('test-user')
})
it('getOrCreateSession creates new session when none exists', async () => {
const session = await getOrCreateSession('new-user', 'New Session')
expect(session.userId).toBe('new-user')
expect(session.label).toBe('New Session')
})
it('getOrCreateSession handles concurrent calls atomically', async () => {
const promises = []
for (let i = 0; i < 5; i++) {
promises.push(getOrCreateSession('concurrent-user', 'Concurrent'))
}
const sessions = await Promise.all(promises)
const uniqueIds = new Set(sessions.map(s => s.id))
expect(uniqueIds.size).toBe(1)
})
})