import { describe, it, expect, beforeEach, vi } from 'vitest' import { createSession, getLiveSession, updateLiveSession, 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', () => { const testState = { sessionId: null, } beforeEach(async () => { clearSessions() const session = await createSession('test-user', 'Test Session') testState.sessionId = session.id }) it('creates a session with WebRTC fields', () => { const session = getLiveSession(testState.sessionId) expect(session).toBeDefined() expect(session.id).toBe(testState.sessionId) expect(session.userId).toBe('test-user') expect(session.label).toBe('Test Session') expect(session.routerId).toBeNull() expect(session.producerId).toBeNull() expect(session.transportId).toBeNull() }) it('updates location', async () => { await updateLiveSession(testState.sessionId, { lat: 37.7, lng: -122.4 }) const session = getLiveSession(testState.sessionId) expect(session.lat).toBe(37.7) expect(session.lng).toBe(-122.4) }) it('updates WebRTC fields', async () => { await updateLiveSession(testState.sessionId, { routerId: 'router-1', producerId: 'producer-1', transportId: 'transport-1' }) const session = getLiveSession(testState.sessionId) expect(session.routerId).toBe('router-1') expect(session.producerId).toBe('producer-1') expect(session.transportId).toBe('transport-1') }) it('returns hasStream instead of hasSnapshot', async () => { await updateLiveSession(testState.sessionId, { producerId: 'producer-1' }) const active = await getActiveSessions() const session = active.find(s => s.id === testState.sessionId) expect(session).toBeDefined() expect(session.hasStream).toBe(true) }) it('returns hasStream false when no producer', async () => { const active = await getActiveSessions() const session = active.find(s => s.id === testState.sessionId) expect(session).toBeDefined() expect(session.hasStream).toBe(false) }) it('deletes a session', async () => { await deleteLiveSession(testState.sessionId) const session = getLiveSession(testState.sessionId) expect(session).toBeUndefined() }) it('getActiveSessionByUserId returns session for same user when active', async () => { const found = await getActiveSessionByUserId('test-user') expect(found).toBeDefined() expect(found.id).toBe(testState.sessionId) }) it('getActiveSessionByUserId returns undefined for unknown user', async () => { const found = await getActiveSessionByUserId('other-user') expect(found).toBeUndefined() }) it('getActiveSessionByUserId returns undefined for expired session', async () => { const session = getLiveSession(testState.sessionId) session.updatedAt = Date.now() - 120_000 const found = await getActiveSessionByUserId('test-user') expect(found).toBeUndefined() }) it('getActiveSessions removes expired sessions', async () => { const session = getLiveSession(testState.sessionId) session.updatedAt = Date.now() - 120_000 const active = await getActiveSessions() expect(active.find(s => s.id === testState.sessionId)).toBeUndefined() expect(getLiveSession(testState.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(testState.sessionId, { producerId: 'p1', transportId: 't1', routerId: 'r1' }) const session = getLiveSession(testState.sessionId) session.updatedAt = Date.now() - 120_000 const active = await getActiveSessions() expect(active.find(s => s.id === testState.sessionId)).toBeUndefined() expect(mockProducer.close).toHaveBeenCalled() expect(mockTransport.close).toHaveBeenCalled() expect(closeRouter).toHaveBeenCalledWith(testState.sessionId) }) it('getOrCreateSession returns existing active session', async () => { const session = await getOrCreateSession('test-user', 'New Label') expect(session.id).toBe(testState.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 = Array.from({ length: 5 }, () => getOrCreateSession('concurrent-user', 'Concurrent'), ) const sessions = await Promise.all(promises) const uniqueIds = new Set(sessions.map(s => s.id)) expect(uniqueIds.size).toBe(1) }) })