mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 15:07:55 -05:00
* fix(rce): add isolate to list of trusted deps * updated error enchancer in RCE * fixed * fix build * fix failing test * fix build * fix build * remove extraneous comment
380 lines
11 KiB
TypeScript
380 lines
11 KiB
TypeScript
/**
|
|
* Tests for the socket server index.ts
|
|
*
|
|
* @vitest-environment node
|
|
*/
|
|
import { createServer, request as httpRequest } from 'http'
|
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { createLogger } from '@/lib/logs/console/logger'
|
|
import { createSocketIOServer } from '@/socket-server/config/socket'
|
|
import { RoomManager } from '@/socket-server/rooms/manager'
|
|
import { createHttpHandler } from '@/socket-server/routes/http'
|
|
|
|
vi.mock('@/lib/auth', () => ({
|
|
auth: {
|
|
api: {
|
|
verifyOneTimeToken: vi.fn(),
|
|
},
|
|
},
|
|
}))
|
|
|
|
vi.mock('@sim/db', () => ({
|
|
db: {
|
|
select: vi.fn(),
|
|
insert: vi.fn(),
|
|
update: vi.fn(),
|
|
delete: vi.fn(),
|
|
transaction: vi.fn(),
|
|
},
|
|
}))
|
|
|
|
vi.mock('@/socket-server/middleware/auth', () => ({
|
|
authenticateSocket: vi.fn((socket, next) => {
|
|
socket.userId = 'test-user-id'
|
|
socket.userName = 'Test User'
|
|
socket.userEmail = 'test@example.com'
|
|
next()
|
|
}),
|
|
}))
|
|
|
|
vi.mock('@/socket-server/middleware/permissions', () => ({
|
|
verifyWorkflowAccess: vi.fn().mockResolvedValue({
|
|
hasAccess: true,
|
|
role: 'admin',
|
|
}),
|
|
checkRolePermission: vi.fn().mockReturnValue({
|
|
allowed: true,
|
|
}),
|
|
}))
|
|
|
|
vi.mock('@/socket-server/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 () => {
|
|
PORT = 3333 + Math.floor(Math.random() * 1000)
|
|
|
|
httpServer = createServer()
|
|
|
|
io = createSocketIOServer(httpServer)
|
|
|
|
roomManager = new RoomManager(io)
|
|
|
|
const httpHandler = createHttpHandler(roomManager, logger)
|
|
httpServer.on('request', httpHandler)
|
|
|
|
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') {
|
|
PORT = 3333 + Math.floor(Math.random() * 1000)
|
|
httpServer.close(() => {
|
|
httpServer.listen(PORT, '0.0.0.0', () => {
|
|
resolve()
|
|
})
|
|
})
|
|
} else {
|
|
reject(err)
|
|
}
|
|
})
|
|
})
|
|
}, 20000)
|
|
|
|
afterEach(async () => {
|
|
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 () => {
|
|
const data = await new Promise<{ status: string; timestamp: number; connections: number }>(
|
|
(resolve, reject) => {
|
|
const req = httpRequest(
|
|
{
|
|
hostname: 'localhost',
|
|
port: PORT,
|
|
path: '/health',
|
|
method: 'GET',
|
|
},
|
|
(res) => {
|
|
expect(res.statusCode).toBe(200)
|
|
|
|
let body = ''
|
|
res.on('data', (chunk) => {
|
|
body += chunk
|
|
})
|
|
res.on('end', () => {
|
|
try {
|
|
resolve(JSON.parse(body))
|
|
} catch (e) {
|
|
reject(e)
|
|
}
|
|
})
|
|
}
|
|
)
|
|
|
|
req.on('error', reject)
|
|
req.end()
|
|
}
|
|
)
|
|
|
|
expect(data).toHaveProperty('status', 'ok')
|
|
expect(data).toHaveProperty('timestamp')
|
|
expect(data).toHaveProperty('connections')
|
|
})
|
|
})
|
|
|
|
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(),
|
|
role: 'admin',
|
|
})
|
|
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 () => {
|
|
const { createSocketIOServer } = await import('@/socket-server/config/socket')
|
|
const { createHttpHandler } = await import('@/socket-server/routes/http')
|
|
const { RoomManager } = await import('@/socket-server/rooms/manager')
|
|
const { authenticateSocket } = await import('@/socket-server/middleware/auth')
|
|
const { verifyWorkflowAccess } = await import('@/socket-server/middleware/permissions')
|
|
const { getWorkflowState } = await import('@/socket-server/database/operations')
|
|
const { WorkflowOperationSchema } = await import('@/socket-server/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', () => {
|
|
expect(httpServer).toBeDefined()
|
|
expect(io).toBeDefined()
|
|
expect(roomManager).toBeDefined()
|
|
|
|
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('@/socket-server/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('@/socket-server/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('@/socket-server/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('@/socket-server/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()
|
|
})
|
|
})
|
|
})
|