test(websocket-handler): copri i rami di errore su send/terminate/applyAction
Aggiunge scenari in cui ws.send o ws.terminate lanciano eccezioni e in cui applyAction fallisce a metà azione: verifica che l'errore venga loggato e lo stato precedente ripristinato senza propagare l'eccezione al chiamante.
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
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')
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user