import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { EventEmitter } from 'events' // Mock parziale di una WebSocket e del Server, analogo a websocket.test.js ma // con send/terminate che possono essere forzati a lanciare per esercitare i // rami di gestione errori altrimenti irraggiungibili. class MockWebSocket extends EventEmitter { constructor() { super() this.readyState = 1 // OPEN } send = vi.fn() terminate = vi.fn() } class MockWebSocketServer extends EventEmitter { clients = new Set() } describe('websocket-handler.js — rami di errore', () => { afterEach(() => { vi.restoreAllMocks() vi.resetModules() vi.doUnmock('../../src/gameState.js') }) describe('errori di invio (ws.send che lancia)', () => { let wss, setupWebSocketHandler, consoleSpy beforeEach(async () => { vi.resetModules() ;({ setupWebSocketHandler } = await import('../../src/websocket-handler.js')) wss = new MockWebSocketServer() consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) }) it('handleRegister: se ws.send lancia durante l\'invio dello stato iniziale, non deve propagare l\'errore', () => { setupWebSocketHandler(wss) const ws = new MockWebSocket() ws.send.mockImplementation(() => { throw new Error('socket chiuso a metà') }) wss.emit('connection', ws) wss.clients.add(ws) expect(() => { ws.emit('message', JSON.stringify({ type: 'register', role: 'display' })) }).not.toThrow() expect(consoleSpy).toHaveBeenCalledWith('Error sending initial state:', expect.any(Error)) }) it('sendError: se ws.send lancia rispondendo a un JSON non valido, non deve propagare l\'errore', () => { setupWebSocketHandler(wss) const ws = new MockWebSocket() ws.send.mockImplementation(() => { throw new Error('socket chiuso a metà') }) wss.emit('connection', ws) wss.clients.add(ws) // JSON non valido → handleMessage va nel catch → sendError prova a rispondere → ws.send lancia expect(() => { ws.emit('message', 'non è JSON {{{') }).not.toThrow() expect(consoleSpy).toHaveBeenCalledWith('Failed to send error message:', expect.any(Error)) }) it('sendError: se ws.send lancia rispondendo a un\'azione invalida, non deve propagare l\'errore', () => { setupWebSocketHandler(wss) const controller = new MockWebSocket() wss.emit('connection', controller) wss.clients.add(controller) controller.emit('message', JSON.stringify({ type: 'register', role: 'controller' })) controller.send.mockImplementation(() => { throw new Error('socket chiuso a metà') }) expect(() => { controller.emit('message', JSON.stringify({ type: 'action' })) }).not.toThrow() expect(consoleSpy).toHaveBeenCalledWith('Failed to send error message:', expect.any(Error)) }) }) describe('handleError: ws.terminate che lancia', () => { it('non deve propagare l\'errore se la chiusura forzata fallisce', async () => { vi.resetModules() const { setupWebSocketHandler } = await import('../../src/websocket-handler.js') const wss = new MockWebSocketServer() setupWebSocketHandler(wss) const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) const ws = new MockWebSocket() ws.terminate.mockImplementation(() => { throw new Error('già chiusa') }) wss.emit('connection', ws) const err = new Error('Invalid UTF8') err.code = 'WS_ERR_INVALID_UTF8' expect(() => ws.emit('error', err)).not.toThrow() expect(consoleSpy).toHaveBeenCalledWith('Error closing connection:', expect.any(Error)) }) }) describe('handleAction: applyAction che lancia', () => { it('non deve propagare l\'errore, ripristina lo stato precedente e notifica il client', async () => { vi.resetModules() vi.doMock('../../src/gameState.js', async (importOriginal) => { const actual = await importOriginal() return { ...actual, applyAction: vi.fn((state, action) => { if (action.type === 'boom') throw new Error('regola di gioco rotta') return actual.applyAction(state, action) }), } }) const { setupWebSocketHandler } = await import('../../src/websocket-handler.js') const wss = new MockWebSocketServer() const handler = setupWebSocketHandler(wss) const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) const controller = new MockWebSocket() wss.emit('connection', controller) wss.clients.add(controller) controller.emit('message', JSON.stringify({ type: 'register', role: 'controller' })) const statoPrecedente = handler.getState() controller.send.mockClear() expect(() => { controller.emit('message', JSON.stringify({ type: 'action', action: { type: 'boom' } })) }).not.toThrow() expect(consoleSpy).toHaveBeenCalledWith('Error applying action:', expect.any(Error)) expect(handler.getState()).toEqual(statoPrecedente) const msg = JSON.parse(controller.send.mock.calls.at(-1)[0]) expect(msg.type).toBe('error') expect(msg.message).toContain('Failed to apply action') }) }) })