test(cli): aggiunge suite unit per il terminal controller
32 test che coprono registrazione WebSocket, parsing di tutti i comandi (shortcut inclusi), validazioni input, conferma reset e gestione uscita.
This commit is contained in:
303
tests/unit/cli.test.js
Normal file
303
tests/unit/cli.test.js
Normal file
@@ -0,0 +1,303 @@
|
||||
import { vi, describe, it, expect, beforeAll, beforeEach } from 'vitest'
|
||||
|
||||
// vi.hoisted garantisce che refs sia disponibile nelle factory dei mock,
|
||||
// che vengono hoistate prima degli import statici.
|
||||
const refs = vi.hoisted(() => ({ ws: null, rl: null }))
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mock: ws
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
vi.mock('ws', async () => {
|
||||
const { EventEmitter } = await import('events')
|
||||
|
||||
class WebSocket extends EventEmitter {
|
||||
static OPEN = 1
|
||||
constructor() {
|
||||
super()
|
||||
this.readyState = 1
|
||||
this.send = vi.fn()
|
||||
this.close = vi.fn()
|
||||
refs.ws = this
|
||||
}
|
||||
}
|
||||
|
||||
return { WebSocket }
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mock: readline
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
vi.mock('readline', async () => {
|
||||
const { EventEmitter } = await import('events')
|
||||
|
||||
return {
|
||||
default: {
|
||||
createInterface: vi.fn(() => {
|
||||
const rl = new EventEmitter()
|
||||
rl.prompt = vi.fn()
|
||||
rl.question = vi.fn()
|
||||
rl.close = vi.fn()
|
||||
refs.rl = rl
|
||||
return rl
|
||||
}),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Silenzia output e blocca process.exit durante i test
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
vi.spyOn(process, 'exit').mockImplementation(() => {})
|
||||
vi.spyOn(process.stdout, 'write').mockReturnValue(true)
|
||||
vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Importa cli.js — esegue gli effetti collaterali con le dipendenze mockate
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
beforeAll(async () => {
|
||||
await import('../../cli.js')
|
||||
refs.ws.emit('open')
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function sendLine(line) {
|
||||
refs.rl.emit('line', line)
|
||||
}
|
||||
|
||||
function lastSent() {
|
||||
const calls = refs.ws.send.mock.calls
|
||||
return calls.length ? JSON.parse(calls[calls.length - 1][0]) : null
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('CLI — Registrazione', () => {
|
||||
it('invia register con ruolo controller all\'apertura del WebSocket', () => {
|
||||
const call = refs.ws.send.mock.calls.find((c) => {
|
||||
try { return JSON.parse(c[0]).type === 'register' } catch { return false }
|
||||
})
|
||||
expect(call).toBeDefined()
|
||||
expect(JSON.parse(call[0])).toEqual({ type: 'register', role: 'controller' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('CLI — Ricezione messaggi', () => {
|
||||
beforeEach(() => {
|
||||
refs.rl.prompt.mockClear()
|
||||
vi.mocked(console.error).mockClear()
|
||||
})
|
||||
|
||||
it('ripristina il prompt dopo un messaggio "state"', () => {
|
||||
const state = {
|
||||
sp: { nomi: { home: 'A', guest: 'B' }, punt: { home: 0, guest: 0 }, set: { home: 0, guest: 0 }, servHome: true },
|
||||
modalitaPartita: '3/5',
|
||||
}
|
||||
refs.ws.emit('message', JSON.stringify({ type: 'state', state }))
|
||||
expect(refs.rl.prompt).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('mostra un errore alla ricezione di un messaggio "error"', () => {
|
||||
refs.ws.emit('message', JSON.stringify({ type: 'error', message: 'azione non valida' }))
|
||||
expect(console.error).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('CLI — Comandi punteggio', () => {
|
||||
beforeEach(() => refs.ws.send.mockClear())
|
||||
|
||||
it('"+" → incPunt home', () => {
|
||||
sendLine('+')
|
||||
expect(lastSent()).toMatchObject({ type: 'action', action: { type: 'incPunt', team: 'home' } })
|
||||
})
|
||||
|
||||
it('"pc" → incPunt home (shortcut)', () => {
|
||||
sendLine('pc')
|
||||
expect(lastSent()).toMatchObject({ type: 'action', action: { type: 'incPunt', team: 'home' } })
|
||||
})
|
||||
|
||||
it('"-" → incPunt guest', () => {
|
||||
sendLine('-')
|
||||
expect(lastSent()).toMatchObject({ type: 'action', action: { type: 'incPunt', team: 'guest' } })
|
||||
})
|
||||
|
||||
it('"po" → incPunt guest (shortcut)', () => {
|
||||
sendLine('po')
|
||||
expect(lastSent()).toMatchObject({ type: 'action', action: { type: 'incPunt', team: 'guest' } })
|
||||
})
|
||||
|
||||
it('"punto casa" → incPunt home', () => {
|
||||
sendLine('punto casa')
|
||||
expect(lastSent()).toMatchObject({ type: 'action', action: { type: 'incPunt', team: 'home' } })
|
||||
})
|
||||
|
||||
it('"punto ospite" → incPunt guest', () => {
|
||||
sendLine('punto ospite')
|
||||
expect(lastSent()).toMatchObject({ type: 'action', action: { type: 'incPunt', team: 'guest' } })
|
||||
})
|
||||
|
||||
it('"punto" senza squadra → errore, nessun invio', () => {
|
||||
sendLine('punto')
|
||||
expect(refs.ws.send).not.toHaveBeenCalled()
|
||||
expect(console.error).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('"undo" → decPunt', () => {
|
||||
sendLine('undo')
|
||||
expect(lastSent()).toMatchObject({ type: 'action', action: { type: 'decPunt' } })
|
||||
})
|
||||
|
||||
it('"u" → decPunt (shortcut)', () => {
|
||||
sendLine('u')
|
||||
expect(lastSent()).toMatchObject({ type: 'action', action: { type: 'decPunt' } })
|
||||
})
|
||||
})
|
||||
|
||||
describe('CLI — Comandi set', () => {
|
||||
beforeEach(() => refs.ws.send.mockClear())
|
||||
|
||||
it('"set casa" → incSet home', () => {
|
||||
sendLine('set casa')
|
||||
expect(lastSent()).toMatchObject({ type: 'action', action: { type: 'incSet', team: 'home' } })
|
||||
})
|
||||
|
||||
it('"set ospite" → incSet guest', () => {
|
||||
sendLine('set ospite')
|
||||
expect(lastSent()).toMatchObject({ type: 'action', action: { type: 'incSet', team: 'guest' } })
|
||||
})
|
||||
|
||||
it('"set" senza squadra → errore, nessun invio', () => {
|
||||
sendLine('set')
|
||||
expect(refs.ws.send).not.toHaveBeenCalled()
|
||||
expect(console.error).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('CLI — Comandi partita', () => {
|
||||
beforeEach(() => refs.ws.send.mockClear())
|
||||
|
||||
it('"serv" → cambiaPalla', () => {
|
||||
sendLine('serv')
|
||||
expect(lastSent()).toMatchObject({ type: 'action', action: { type: 'cambiaPalla' } })
|
||||
})
|
||||
|
||||
it('"nomi <casa> <ospite>" → setNomi con entrambi i nomi', () => {
|
||||
sendLine('nomi Antoniana Riviera')
|
||||
expect(lastSent()).toMatchObject({
|
||||
type: 'action',
|
||||
action: { type: 'setNomi', home: 'Antoniana', guest: 'Riviera' },
|
||||
})
|
||||
})
|
||||
|
||||
it('"nomi <casa>" → setNomi con solo nome casa', () => {
|
||||
sendLine('nomi Antoniana')
|
||||
const sent = lastSent()
|
||||
expect(sent).toMatchObject({ type: 'action', action: { type: 'setNomi', home: 'Antoniana' } })
|
||||
expect(sent.action.guest).toBeUndefined()
|
||||
})
|
||||
|
||||
it('"nomi" senza argomenti → errore, nessun invio', () => {
|
||||
sendLine('nomi')
|
||||
expect(refs.ws.send).not.toHaveBeenCalled()
|
||||
expect(console.error).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('"modalita 2/3" → setModalita 2/3', () => {
|
||||
sendLine('modalita 2/3')
|
||||
expect(lastSent()).toMatchObject({ type: 'action', action: { type: 'setModalita', modalita: '2/3' } })
|
||||
})
|
||||
|
||||
it('"modalita 3/5" → setModalita 3/5', () => {
|
||||
sendLine('modalita 3/5')
|
||||
expect(lastSent()).toMatchObject({ type: 'action', action: { type: 'setModalita', modalita: '3/5' } })
|
||||
})
|
||||
|
||||
it('"modalita" con valore non valido → errore, nessun invio', () => {
|
||||
sendLine('modalita 4/7')
|
||||
expect(refs.ws.send).not.toHaveBeenCalled()
|
||||
expect(console.error).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('CLI — Comando reset (con conferma)', () => {
|
||||
beforeEach(() => {
|
||||
refs.ws.send.mockClear()
|
||||
refs.rl.question.mockClear()
|
||||
})
|
||||
|
||||
it('conferma "s" → invia resetta', () => {
|
||||
refs.rl.question.mockImplementationOnce((_msg, cb) => cb('s'))
|
||||
sendLine('reset')
|
||||
expect(lastSent()).toMatchObject({ type: 'action', action: { type: 'resetta' } })
|
||||
})
|
||||
|
||||
it('risposta "n" → non invia nulla', () => {
|
||||
refs.rl.question.mockImplementationOnce((_msg, cb) => cb('n'))
|
||||
sendLine('reset')
|
||||
expect(refs.ws.send).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('risposta vuota → non invia nulla', () => {
|
||||
refs.rl.question.mockImplementationOnce((_msg, cb) => cb(''))
|
||||
sendLine('reset')
|
||||
expect(refs.ws.send).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('CLI — Comandi informativi', () => {
|
||||
beforeEach(() => {
|
||||
refs.ws.send.mockClear()
|
||||
vi.mocked(console.log).mockClear()
|
||||
})
|
||||
|
||||
it('"help" → stampa aiuto, nessun invio', () => {
|
||||
sendLine('help')
|
||||
expect(console.log).toHaveBeenCalled()
|
||||
expect(refs.ws.send).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('"stato" → nessun invio', () => {
|
||||
sendLine('stato')
|
||||
expect(refs.ws.send).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('comando sconosciuto → messaggio di errore, nessun invio', () => {
|
||||
sendLine('xyzzy')
|
||||
expect(refs.ws.send).not.toHaveBeenCalled()
|
||||
expect(console.error).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('riga vuota → nessun invio', () => {
|
||||
refs.rl.emit('line', ' ')
|
||||
expect(refs.ws.send).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('CLI — Uscita', () => {
|
||||
it('"exit" → chiude il WebSocket', () => {
|
||||
refs.ws.close.mockClear()
|
||||
sendLine('exit')
|
||||
expect(refs.ws.close).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('"q" → chiude il WebSocket (shortcut)', () => {
|
||||
refs.ws.close.mockClear()
|
||||
sendLine('q')
|
||||
expect(refs.ws.close).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('chiusura readline → chiude il WebSocket', () => {
|
||||
refs.ws.close.mockClear()
|
||||
refs.rl.emit('close')
|
||||
expect(refs.ws.close).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user