Kleine Arbeiten
This commit is contained in:
260
test/Sender.Telnet.responseParsing.test.js
Normal file
260
test/Sender.Telnet.responseParsing.test.js
Normal file
@@ -0,0 +1,260 @@
|
||||
'use strict';
|
||||
// Tests für ToDo_9 Paket 1/3:
|
||||
// Paket 1 — eingehende GRBL/FluidNC-Antworten lesen und klassifizieren
|
||||
// Paket 3 — opt-in Auto-Report-Konfiguration ($10=3, $Report/Interval)
|
||||
|
||||
const { EventEmitter } = require('events');
|
||||
const TelnetSenderGRBL = require('../robot/TelnetSenderGRBL');
|
||||
|
||||
// ── Hilfsfunktionen ──────────────────────────────────────────────────────────
|
||||
|
||||
function makeRawSocket() {
|
||||
const em = new EventEmitter();
|
||||
return Object.assign(em, {
|
||||
write: jest.fn(),
|
||||
end: jest.fn(),
|
||||
destroy: jest.fn(),
|
||||
setKeepAlive: jest.fn(),
|
||||
});
|
||||
}
|
||||
|
||||
function makeTelnetSocket() {
|
||||
const em = new EventEmitter();
|
||||
return Object.assign(em, {
|
||||
write: jest.fn(),
|
||||
end: jest.fn(),
|
||||
destroy: jest.fn(),
|
||||
});
|
||||
}
|
||||
|
||||
/** Sender mit gemockten Abhängigkeiten; simuliert erfolgreichen Verbindungsaufbau. */
|
||||
function setup(extraOptions = {}) {
|
||||
const rawSocket = makeRawSocket();
|
||||
const telnetSock = makeTelnetSocket();
|
||||
|
||||
const sender = new TelnetSenderGRBL(
|
||||
'robot.local', 5000,
|
||||
'x', 'y', 'z', null, null, null, null,
|
||||
{
|
||||
netModule: { createConnection: jest.fn(() => rawSocket) },
|
||||
TelnetSocketClass: jest.fn(() => telnetSock),
|
||||
setIntervalFn: jest.fn(() => 1),
|
||||
clearIntervalFn: jest.fn(),
|
||||
setTimeoutFn: jest.fn(() => 99),
|
||||
clearTimeoutFn: jest.fn(),
|
||||
autoConnect: false,
|
||||
...extraOptions,
|
||||
}
|
||||
);
|
||||
|
||||
sender.connect();
|
||||
rawSocket.emit('connect');
|
||||
|
||||
/** Simuliert vom Controller eingehende Bytes. */
|
||||
const recv = (str) => telnetSock.emit('data', Buffer.from(str, 'utf8'));
|
||||
|
||||
return { sender, rawSocket, telnetSock, recv };
|
||||
}
|
||||
|
||||
// ── Tests ────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('TelnetSenderGRBL — Antworten lesen (ToDo_9 Paket 1)', () => {
|
||||
|
||||
beforeAll(() => { jest.spyOn(console, 'log').mockImplementation(() => {}); });
|
||||
afterAll(() => { jest.restoreAllMocks(); });
|
||||
|
||||
test('parst Statusreport: State, MPos und Bf', () => {
|
||||
const { sender, recv } = setup();
|
||||
recv('<Idle|MPos:151.000,149.000,-1.000|Bf:15,128>\r\n');
|
||||
|
||||
expect(sender.grblState).toBe('Idle');
|
||||
expect(sender.machinePosition).toEqual([151.0, 149.0, -1.0]);
|
||||
expect(sender.machinePositionType).toBe('MPos');
|
||||
expect(sender.plannerBlocksFree).toBe(15);
|
||||
expect(sender.rxBytesFree).toBe(128);
|
||||
});
|
||||
|
||||
test('parst Run-Zustand', () => {
|
||||
const { sender, recv } = setup();
|
||||
recv('<Run|MPos:1,2,3|FS:500,0>\r\n');
|
||||
expect(sender.grblState).toBe('Run');
|
||||
expect(sender.machinePosition).toEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
test('nutzt WPos, wenn kein MPos vorhanden', () => {
|
||||
const { sender, recv } = setup();
|
||||
recv('<Idle|WPos:10.5,20.5,30.5>\r\n');
|
||||
expect(sender.machinePosition).toEqual([10.5, 20.5, 30.5]);
|
||||
expect(sender.machinePositionType).toBe('WPos');
|
||||
});
|
||||
|
||||
test('puffert über fragmentierte data-Events', () => {
|
||||
const { sender, recv } = setup();
|
||||
recv('<Idle|MPos:1.0');
|
||||
// Noch keine vollständige Zeile → Zustand unverändert
|
||||
expect(sender.machinePosition).toBeNull();
|
||||
|
||||
recv('00,2.000,3.000|Bf:10,120>\r\n');
|
||||
expect(sender.machinePosition).toEqual([1.0, 2.0, 3.0]);
|
||||
expect(sender.plannerBlocksFree).toBe(10);
|
||||
});
|
||||
|
||||
test('verarbeitet mehrere Zeilen in einem Chunk', () => {
|
||||
const { sender, recv } = setup();
|
||||
recv('ok\r\nok\r\n<Idle|MPos:0,0,0>\r\n');
|
||||
expect(sender.grblState).toBe('Idle');
|
||||
expect(sender.lastResponse).toBe('<Idle|MPos:0,0,0>');
|
||||
expect(sender.lastOk).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('erkennt "ok" und setzt lastOk', () => {
|
||||
const { sender, recv } = setup();
|
||||
expect(sender.lastOk).toBe(0);
|
||||
recv('ok\r\n');
|
||||
expect(sender.lastOk).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('erkennt error:-Zeile und legt sie in lastError ab', () => {
|
||||
const { sender, recv } = setup();
|
||||
recv('error:9\r\n');
|
||||
expect(sender.lastError).toBe('error:9');
|
||||
});
|
||||
|
||||
test('erkennt ALARM-Zeile', () => {
|
||||
const { sender, recv } = setup();
|
||||
recv('ALARM:1\r\n');
|
||||
expect(sender.lastError).toBe('ALARM:1');
|
||||
});
|
||||
|
||||
test('ignoriert fremde/unbekannte Zeilen ohne zu werfen (Cross-Channel)', () => {
|
||||
const { sender, recv } = setup();
|
||||
expect(() => recv('[MSG:some banner]\r\n')).not.toThrow();
|
||||
expect(sender.grblState).toBeNull();
|
||||
expect(sender.lastError).toBeNull();
|
||||
// lastResponse wird trotzdem gesetzt
|
||||
expect(sender.lastResponse).toBe('[MSG:some banner]');
|
||||
});
|
||||
|
||||
test('zerstörter Report wirft nicht und lässt alten Zustand bestehen', () => {
|
||||
const { sender, recv } = setup();
|
||||
recv('<Idle|MPos:1,2,3>\r\n');
|
||||
expect(() => recv('<Run|MPos:nope,bad,vals>\r\n')).not.toThrow();
|
||||
// State wird übernommen, kaputte Position aber nicht
|
||||
expect(sender.grblState).toBe('Run');
|
||||
expect(sender.machinePosition).toEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
test('Zeilen-Puffer wird bei Reconnect zurückgesetzt', () => {
|
||||
const { sender, rawSocket, telnetSock, recv } = setup();
|
||||
recv('<Idle|MPos:1.0'); // unvollständig im Puffer
|
||||
expect(sender._rxBuffer.length).toBeGreaterThan(0);
|
||||
|
||||
// Reconnect: neuer Socket, connect erneut
|
||||
telnetSock.emit('close');
|
||||
rawSocket.emit('connect');
|
||||
expect(sender._rxBuffer).toBe('');
|
||||
});
|
||||
|
||||
test('getStatus() liefert die Hardware-Feedback-Felder', () => {
|
||||
const { sender, recv } = setup();
|
||||
recv('<Hold|MPos:5,6,7|Bf:12,100>\r\n');
|
||||
const st = sender.getStatus();
|
||||
expect(st.grblState).toBe('Hold');
|
||||
expect(st.machinePosition).toEqual([5, 6, 7]);
|
||||
expect(st.plannerBlocksFree).toBe(12);
|
||||
expect(st.rxBytesFree).toBe(100);
|
||||
expect(st.autoReport).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('TelnetSenderGRBL — Auto-Report Opt-in (ToDo_9 Paket 3)', () => {
|
||||
|
||||
beforeAll(() => { jest.spyOn(console, 'log').mockImplementation(() => {}); });
|
||||
afterAll(() => { jest.restoreAllMocks(); });
|
||||
|
||||
test('Default AUS: sendet beim Connect keine Settings-Kommandos', () => {
|
||||
const { telnetSock } = setup(); // autoReport nicht gesetzt
|
||||
expect(telnetSock.write).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('Opt-in EIN: sendet $10=3 und $Report/Interval beim Connect', () => {
|
||||
const { telnetSock } = setup({ autoReport: true, reportInterval: 150 });
|
||||
expect(telnetSock.write).toHaveBeenCalledWith('$10=3\r\n');
|
||||
expect(telnetSock.write).toHaveBeenCalledWith('$Report/Interval=150\r\n');
|
||||
});
|
||||
|
||||
test('Opt-in EIN: nutzt Default-Intervall 200, wenn nicht angegeben', () => {
|
||||
const { telnetSock } = setup({ autoReport: true });
|
||||
expect(telnetSock.write).toHaveBeenCalledWith('$Report/Interval=200\r\n');
|
||||
});
|
||||
});
|
||||
|
||||
describe('TelnetSenderGRBL — requestStatusReport (ToDo_9 Paket 4)', () => {
|
||||
|
||||
beforeAll(() => { jest.spyOn(console, 'log').mockImplementation(() => {}); });
|
||||
afterAll(() => { jest.restoreAllMocks(); });
|
||||
|
||||
// Setup mit erfassbarem Timeout-Callback (für den Timeout-Pfad).
|
||||
function setupTimed() {
|
||||
const rawSocket = makeRawSocket();
|
||||
const telnetSock = makeTelnetSocket();
|
||||
let timeoutCb = null;
|
||||
const sender = new TelnetSenderGRBL(
|
||||
'robot.local', 5000, 'x', 'y', 'z', null, null, null, null,
|
||||
{
|
||||
netModule: { createConnection: jest.fn(() => rawSocket) },
|
||||
TelnetSocketClass: jest.fn(() => telnetSock),
|
||||
setIntervalFn: jest.fn(() => 1),
|
||||
clearIntervalFn: jest.fn(),
|
||||
setTimeoutFn: jest.fn((cb) => { timeoutCb = cb; return 42; }),
|
||||
clearTimeoutFn: jest.fn(),
|
||||
autoConnect: false,
|
||||
}
|
||||
);
|
||||
sender.connect();
|
||||
rawSocket.emit('connect');
|
||||
const recv = (str) => telnetSock.emit('data', Buffer.from(str, 'utf8'));
|
||||
return { sender, rawSocket, telnetSock, recv, fireTimeout: () => timeoutCb && timeoutCb() };
|
||||
}
|
||||
|
||||
test('sendet ? und löst beim nächsten Report auf', async () => {
|
||||
const { sender, telnetSock, recv } = setupTimed();
|
||||
const p = sender.requestStatusReport(1000);
|
||||
expect(telnetSock.write).toHaveBeenCalledWith('?');
|
||||
|
||||
recv('<Idle|MPos:1,2,3|Bf:15,128>\r\n');
|
||||
const snap = await p;
|
||||
expect(snap.grblState).toBe('Idle');
|
||||
expect(snap.machinePosition).toEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
test('wirft bei Timeout ohne Report', async () => {
|
||||
const { sender, fireTimeout } = setupTimed();
|
||||
const p = sender.requestStatusReport(1000);
|
||||
fireTimeout();
|
||||
await expect(p).rejects.toThrow(/Timeout/);
|
||||
});
|
||||
|
||||
test('wirft, wenn nicht verbunden', async () => {
|
||||
const { sender } = setupTimed();
|
||||
sender.tSocket = null;
|
||||
await expect(sender.requestStatusReport(1000)).rejects.toThrow(/not connected/);
|
||||
});
|
||||
|
||||
test('bricht offene Anfrage bei Verbindungsverlust ab', async () => {
|
||||
const { sender, telnetSock } = setupTimed();
|
||||
const p = sender.requestStatusReport(1000);
|
||||
telnetSock.emit('close');
|
||||
await expect(p).rejects.toThrow(/geschlossen/);
|
||||
});
|
||||
|
||||
test('mehrere gleichzeitige Anfragen werden alle vom selben Report aufgelöst', async () => {
|
||||
const { sender, recv } = setupTimed();
|
||||
const p1 = sender.requestStatusReport(1000);
|
||||
const p2 = sender.requestStatusReport(1000);
|
||||
recv('<Run|MPos:4,5,6>\r\n');
|
||||
const [s1, s2] = await Promise.all([p1, p2]);
|
||||
expect(s1.machinePosition).toEqual([4, 5, 6]);
|
||||
expect(s2.machinePosition).toEqual([4, 5, 6]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user