'use strict'; // Tests für TelnetSenderGRBL.emergencyStop() und alarmUnlock() const { EventEmitter } = require('events'); const TelnetSenderGRBL = require('../robot/TelnetSenderGRBL'); function makeTelnetSocket() { const em = new EventEmitter(); return Object.assign(em, { write: jest.fn(), end: jest.fn(), destroy: jest.fn(), }); } /** Sender im Test-Modus (URL "test.test") — tSocket ist gesetzt. */ function makeConnectedSender() { const sender = new TelnetSenderGRBL('test.test'); sender.tSocket = makeTelnetSocket(); return sender; } /** Sender ohne tSocket (disconnected). */ function makeDisconnectedSender() { const sender = new TelnetSenderGRBL('test.test'); sender.tSocket = null; return sender; } // ── emergencyStop() ────────────────────────────────────────────────────────── describe('TelnetSenderGRBL.emergencyStop()', () => { test('sendet "!" wenn verbunden', async () => { const sender = makeConnectedSender(); const result = await sender.emergencyStop(); expect(result.ok).toBe(true); expect(sender.tSocket.write).toHaveBeenCalledWith('!'); }); test('sendet nur "!" — kein Zeilenende (FluidNC Realtime-Byte)', async () => { const sender = makeConnectedSender(); await sender.emergencyStop(); const arg = sender.tSocket.write.mock.calls[0][0]; expect(arg).toBe('!'); expect(arg).not.toContain('\r\n'); }); test('gibt {ok:false} zurück wenn nicht verbunden', async () => { const sender = makeDisconnectedSender(); const result = await sender.emergencyStop(); expect(result.ok).toBe(false); expect(result.error).toBe('not connected'); }); test('gibt {ok:false} zurück wenn write() wirft', async () => { const sender = makeConnectedSender(); sender.tSocket.write.mockImplementation(() => { throw new Error('socket closed'); }); const result = await sender.emergencyStop(); expect(result.ok).toBe(false); expect(result.error).toBe('socket closed'); }); }); // ── alarmUnlock() ──────────────────────────────────────────────────────────── describe('TelnetSenderGRBL.alarmUnlock()', () => { test('sendet "$X\\r\\n" wenn verbunden', async () => { const sender = makeConnectedSender(); const result = await sender.alarmUnlock(); expect(result.ok).toBe(true); expect(sender.tSocket.write).toHaveBeenCalledWith('$X\r\n'); }); test('gibt {ok:false} zurück wenn nicht verbunden', async () => { const sender = makeDisconnectedSender(); const result = await sender.alarmUnlock(); expect(result.ok).toBe(false); expect(result.error).toBe('not connected'); }); test('gibt {ok:false} zurück wenn write() wirft', async () => { const sender = makeConnectedSender(); sender.tSocket.write.mockImplementation(() => { throw new Error('write error'); }); const result = await sender.alarmUnlock(); expect(result.ok).toBe(false); expect(result.error).toBe('write error'); }); }); // ── SenderInterface-Defaults ───────────────────────────────────────────────── describe('SenderInterface — Default-Implementierungen (no-op)', () => { test('SenderInterface.emergencyStop() gibt {ok:true, skipped:true}', async () => { const SenderInterface = require('../robot/SenderInterface'); // Subclass, die nur emergencyStop von der Basis erbt class MinimalSender extends SenderInterface { async connect() { return this; } send() { return false; } getStatus() { return {}; } disconnect() {} } const s = new MinimalSender(); await expect(s.emergencyStop()).resolves.toEqual({ ok: true, skipped: true }); }); test('SenderInterface.alarmUnlock() gibt {ok:true, skipped:true}', async () => { const SenderInterface = require('../robot/SenderInterface'); class MinimalSender extends SenderInterface { async connect() { return this; } send() { return false; } getStatus() { return {}; } disconnect() {} } const s = new MinimalSender(); await expect(s.alarmUnlock()).resolves.toEqual({ ok: true, skipped: true }); }); });