3d54d162e6
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.
135 lines
5.8 KiB
JavaScript
135 lines
5.8 KiB
JavaScript
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')
|
|
})
|
|
})
|
|
})
|