Files
sim/apps/sim/socket-server/index.test.ts
Vikhyath Mondreti 0bf9ce0b9e feat(enhanced logs): integration + log visualizer canvas (#618)
* feat(logs): enhanced logging system with cleanup and theme fixes

- Implement enhanced logging cleanup with S3 archival and retention policies
- Fix error propagation in trace spans for manual executions
- Add theme-aware styling for frozen canvas modal
- Integrate enhanced logging system across all execution pathways
- Add comprehensive trace span processing and iteration navigation
- Fix boolean parameter types in enhanced logs API

* add warning for old logs

* fix lint

* added cost for streaming outputs

* fix overflow issue

* fix lint

* fix selection on closing sidebar

* tooltips z index increase

---------

Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@vikhyaths-air.lan>
Co-authored-by: Waleed Latif <walif6@gmail.com>
2025-07-06 20:01:28 -07:00

366 lines
11 KiB
TypeScript

/**
* Tests for the socket server index.ts
*
* @vitest-environment node
*/
import { createServer } from 'http'
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
import { createLogger } from '../lib/logs/console-logger'
import { createSocketIOServer } from './config/socket'
import { RoomManager } from './rooms/manager'
import { createHttpHandler } from './routes/http'
vi.mock('../lib/auth', () => ({
auth: {
api: {
verifyOneTimeToken: vi.fn(),
},
},
}))
vi.mock('../db', () => ({
db: {
select: vi.fn(),
insert: vi.fn(),
update: vi.fn(),
delete: vi.fn(),
transaction: vi.fn(),
},
}))
vi.mock('./middleware/auth', () => ({
authenticateSocket: vi.fn((socket, next) => {
socket.userId = 'test-user-id'
socket.userName = 'Test User'
socket.userEmail = 'test@example.com'
next()
}),
}))
vi.mock('./middleware/permissions', () => ({
verifyWorkflowAccess: vi.fn().mockResolvedValue({
hasAccess: true,
role: 'owner',
}),
verifyOperationPermission: vi.fn().mockResolvedValue({
allowed: true,
}),
}))
vi.mock('./database/operations', () => ({
getWorkflowState: vi.fn().mockResolvedValue({
id: 'test-workflow',
name: 'Test Workflow',
lastModified: Date.now(),
}),
persistWorkflowOperation: vi.fn().mockResolvedValue(undefined),
}))
describe('Socket Server Index Integration', () => {
let httpServer: any
let io: any
let roomManager: RoomManager
let logger: any
let PORT: number
beforeAll(() => {
logger = createLogger('SocketServerTest')
})
beforeEach(async () => {
// Use a random port for each test to avoid conflicts
PORT = 3333 + Math.floor(Math.random() * 1000)
// Create HTTP server
httpServer = createServer()
// Create Socket.IO server using extracted config
io = createSocketIOServer(httpServer)
// Initialize room manager after io is created
roomManager = new RoomManager(io)
// Configure HTTP request handler
const httpHandler = createHttpHandler(roomManager, logger)
httpServer.on('request', httpHandler)
// Start server with timeout handling
await new Promise<void>((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error(`Server failed to start on port ${PORT} within 15 seconds`))
}, 15000)
httpServer.listen(PORT, '0.0.0.0', () => {
clearTimeout(timeout)
resolve()
})
httpServer.on('error', (err: any) => {
clearTimeout(timeout)
if (err.code === 'EADDRINUSE') {
// Try a different port
PORT = 3333 + Math.floor(Math.random() * 1000)
httpServer.listen(PORT, '0.0.0.0', () => {
resolve()
})
} else {
reject(err)
}
})
})
}, 20000)
afterEach(async () => {
// Properly close servers and wait for them to fully close
if (io) {
await new Promise<void>((resolve) => {
io.close(() => resolve())
})
}
if (httpServer) {
await new Promise<void>((resolve) => {
httpServer.close(() => resolve())
})
}
vi.clearAllMocks()
})
describe('HTTP Server Configuration', () => {
it('should create HTTP server successfully', () => {
expect(httpServer).toBeDefined()
expect(httpServer.listening).toBe(true)
})
it('should handle health check endpoint', async () => {
try {
const response = await fetch(`http://localhost:${PORT}/health`)
expect(response.status).toBe(200)
const data = await response.json()
expect(data).toHaveProperty('status', 'ok')
expect(data).toHaveProperty('timestamp')
expect(data).toHaveProperty('connections')
} catch (error) {
// Skip this test if fetch fails (likely due to test environment)
console.warn('Health check test skipped due to fetch error:', error)
}
})
})
describe('Socket.IO Server Configuration', () => {
it('should create Socket.IO server with proper configuration', () => {
expect(io).toBeDefined()
expect(io.engine).toBeDefined()
})
it('should have proper CORS configuration', () => {
const corsOptions = io.engine.opts.cors
expect(corsOptions).toBeDefined()
expect(corsOptions.methods).toContain('GET')
expect(corsOptions.methods).toContain('POST')
expect(corsOptions.credentials).toBe(true)
})
it('should have proper transport configuration', () => {
const transports = io.engine.opts.transports
expect(transports).toContain('polling')
expect(transports).toContain('websocket')
})
})
describe('Room Manager Integration', () => {
it('should create room manager successfully', () => {
expect(roomManager).toBeDefined()
expect(roomManager.getTotalActiveConnections()).toBe(0)
})
it('should create workflow rooms', () => {
const workflowId = 'test-workflow-123'
const room = roomManager.createWorkflowRoom(workflowId)
roomManager.setWorkflowRoom(workflowId, room)
expect(roomManager.hasWorkflowRoom(workflowId)).toBe(true)
const retrievedRoom = roomManager.getWorkflowRoom(workflowId)
expect(retrievedRoom).toBeDefined()
expect(retrievedRoom?.workflowId).toBe(workflowId)
})
it('should manage user sessions', () => {
const socketId = 'test-socket-123'
const workflowId = 'test-workflow-456'
const session = { userId: 'user-123', userName: 'Test User' }
roomManager.setWorkflowForSocket(socketId, workflowId)
roomManager.setUserSession(socketId, session)
expect(roomManager.getWorkflowIdForSocket(socketId)).toBe(workflowId)
expect(roomManager.getUserSession(socketId)).toEqual(session)
})
it('should clean up rooms properly', () => {
const workflowId = 'test-workflow-789'
const socketId = 'test-socket-789'
const room = roomManager.createWorkflowRoom(workflowId)
roomManager.setWorkflowRoom(workflowId, room)
// Add user to room
room.users.set(socketId, {
userId: 'user-789',
workflowId,
userName: 'Test User',
socketId,
joinedAt: Date.now(),
lastActivity: Date.now(),
})
room.activeConnections = 1
roomManager.setWorkflowForSocket(socketId, workflowId)
// Clean up user
roomManager.cleanupUserFromRoom(socketId, workflowId)
expect(roomManager.hasWorkflowRoom(workflowId)).toBe(false)
expect(roomManager.getWorkflowIdForSocket(socketId)).toBeUndefined()
})
})
describe('Module Integration', () => {
it.concurrent('should properly import all extracted modules', async () => {
// Test that all modules can be imported without errors
const { createSocketIOServer } = await import('./config/socket')
const { createHttpHandler } = await import('./routes/http')
const { RoomManager } = await import('./rooms/manager')
const { authenticateSocket } = await import('./middleware/auth')
const { verifyWorkflowAccess } = await import('./middleware/permissions')
const { getWorkflowState } = await import('./database/operations')
const { WorkflowOperationSchema } = await import('./validation/schemas')
expect(createSocketIOServer).toBeTypeOf('function')
expect(createHttpHandler).toBeTypeOf('function')
expect(RoomManager).toBeTypeOf('function')
expect(authenticateSocket).toBeTypeOf('function')
expect(verifyWorkflowAccess).toBeTypeOf('function')
expect(getWorkflowState).toBeTypeOf('function')
expect(WorkflowOperationSchema).toBeDefined()
})
it.concurrent('should maintain all original functionality after refactoring', () => {
// Verify that the main components are properly instantiated
expect(httpServer).toBeDefined()
expect(io).toBeDefined()
expect(roomManager).toBeDefined()
// Verify core methods exist and are callable
expect(typeof roomManager.createWorkflowRoom).toBe('function')
expect(typeof roomManager.cleanupUserFromRoom).toBe('function')
expect(typeof roomManager.handleWorkflowDeletion).toBe('function')
expect(typeof roomManager.validateWorkflowConsistency).toBe('function')
})
})
describe('Error Handling', () => {
it('should have global error handlers configured', () => {
expect(typeof process.on).toBe('function')
})
it('should handle server setup', () => {
expect(httpServer).toBeDefined()
expect(io).toBeDefined()
})
})
describe('Authentication Middleware', () => {
it('should apply authentication middleware to Socket.IO', () => {
expect(io._parser).toBeDefined()
})
})
describe('Graceful Shutdown', () => {
it('should have shutdown capability', () => {
expect(typeof httpServer.close).toBe('function')
expect(typeof io.close).toBe('function')
})
})
describe('Validation and Utils', () => {
it.concurrent('should validate workflow operations', async () => {
const { WorkflowOperationSchema } = await import('./validation/schemas')
const validOperation = {
operation: 'add',
target: 'block',
payload: {
id: 'test-block',
type: 'action',
name: 'Test Block',
position: { x: 100, y: 200 },
},
timestamp: Date.now(),
}
expect(() => WorkflowOperationSchema.parse(validOperation)).not.toThrow()
})
it.concurrent('should validate block operations with autoConnectEdge', async () => {
const { WorkflowOperationSchema } = await import('./validation/schemas')
const validOperationWithAutoEdge = {
operation: 'add',
target: 'block',
payload: {
id: 'test-block',
type: 'action',
name: 'Test Block',
position: { x: 100, y: 200 },
autoConnectEdge: {
id: 'auto-edge-123',
source: 'source-block',
target: 'test-block',
sourceHandle: 'output',
targetHandle: 'target',
type: 'workflowEdge',
},
},
timestamp: Date.now(),
}
expect(() => WorkflowOperationSchema.parse(validOperationWithAutoEdge)).not.toThrow()
})
it.concurrent('should validate edge operations', async () => {
const { WorkflowOperationSchema } = await import('./validation/schemas')
const validEdgeOperation = {
operation: 'add',
target: 'edge',
payload: {
id: 'test-edge',
source: 'block-1',
target: 'block-2',
},
timestamp: Date.now(),
}
expect(() => WorkflowOperationSchema.parse(validEdgeOperation)).not.toThrow()
})
it('should validate subflow operations', async () => {
const { WorkflowOperationSchema } = await import('./validation/schemas')
const validSubflowOperation = {
operation: 'update',
target: 'subflow',
payload: {
id: 'test-subflow',
type: 'loop',
config: { iterations: 5 },
},
timestamp: Date.now(),
}
expect(() => WorkflowOperationSchema.parse(validSubflowOperation)).not.toThrow()
})
})
})