148 lines
5.4 KiB
JavaScript
148 lines
5.4 KiB
JavaScript
'use strict';
|
|
// Heartbeat-Tests für TelnetSenderGRBL:
|
|
// Erkennung toter Verbindungen nach NotAus / Stromausfall beim Roboter.
|
|
|
|
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(),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Erstellt einen Sender mit gemockten Abhängigkeiten und simuliert
|
|
* einen erfolgreichen Verbindungsaufbau.
|
|
*/
|
|
function setup({ heartbeatInterval = 1000, deadTimeout = 5000 } = {}) {
|
|
const rawSocket = makeRawSocket();
|
|
const telnetSock = makeTelnetSocket();
|
|
|
|
// Intervall-Verwaltung: injiziert, damit kein echter setInterval läuft.
|
|
const activeIntervals = new Map();
|
|
let nextId = 1;
|
|
const setIntervalFn = jest.fn((cb) => { const id = nextId++; activeIntervals.set(id, cb); return id; });
|
|
const clearIntervalFn = jest.fn((id) => { activeIntervals.delete(id); });
|
|
|
|
// Timeout-Mock: verhindert echte setTimeout-Aufrufe bei Reconnect-Scheduling.
|
|
const setTimeoutFn = jest.fn(() => 99);
|
|
const clearTimeoutFn = jest.fn();
|
|
|
|
const sender = new TelnetSenderGRBL(
|
|
'robot.local', 5000,
|
|
'x', 'y', 'z', null, null, null, null,
|
|
{
|
|
netModule: { createConnection: jest.fn(() => rawSocket) },
|
|
TelnetSocketClass: jest.fn(() => telnetSock),
|
|
setIntervalFn,
|
|
clearIntervalFn,
|
|
setTimeoutFn,
|
|
clearTimeoutFn,
|
|
heartbeatInterval,
|
|
deadTimeout,
|
|
autoConnect: false,
|
|
}
|
|
);
|
|
|
|
// Verbindungsaufbau simulieren
|
|
sender.connect();
|
|
rawSocket.emit('connect');
|
|
|
|
/** Heartbeat-Callback manuell auslösen. */
|
|
const fireHeartbeat = () => {
|
|
const entries = [...activeIntervals.values()];
|
|
if (entries.length > 0) entries[0]();
|
|
};
|
|
|
|
return { sender, rawSocket, telnetSock, activeIntervals, fireHeartbeat };
|
|
}
|
|
|
|
// ── Tests ────────────────────────────────────────────────────────────────────
|
|
|
|
describe('TelnetSenderGRBL — Heartbeat / NotAus-Erkennung', () => {
|
|
|
|
test('TCP-Keepalive wird beim Verbinden aktiviert', () => {
|
|
const { rawSocket } = setup();
|
|
expect(rawSocket.setKeepAlive).toHaveBeenCalledWith(true, 2000);
|
|
});
|
|
|
|
test('Heartbeat-Timer wird nach Verbindung gestartet', () => {
|
|
const { activeIntervals } = setup();
|
|
expect(activeIntervals.size).toBe(1);
|
|
});
|
|
|
|
test('Heartbeat sendet ? an den Controller', () => {
|
|
const { telnetSock, fireHeartbeat } = setup();
|
|
fireHeartbeat();
|
|
expect(telnetSock.write).toHaveBeenCalledWith('?');
|
|
});
|
|
|
|
test('Socket wird zerstört wenn deadTimeout überschritten', () => {
|
|
const { sender, rawSocket, fireHeartbeat } = setup({ deadTimeout: 5000 });
|
|
sender._lastDataAt = Date.now() - 20_000; // älter als deadTimeout → tot
|
|
fireHeartbeat();
|
|
expect(rawSocket.destroy).toHaveBeenCalled();
|
|
});
|
|
|
|
test('Heartbeat-Timer wird nach Timeout-Erkennung gestoppt', () => {
|
|
const { sender, activeIntervals, fireHeartbeat } = setup({ deadTimeout: 5000 });
|
|
sender._lastDataAt = Date.now() - 20_000;
|
|
fireHeartbeat();
|
|
expect(activeIntervals.size).toBe(0);
|
|
});
|
|
|
|
test('Kein Destroy wenn Daten innerhalb deadTimeout empfangen wurden', () => {
|
|
const { sender, rawSocket, telnetSock, fireHeartbeat } = setup({ deadTimeout: 5000 });
|
|
sender._lastDataAt = Date.now(); // soeben empfangen → kein Timeout
|
|
fireHeartbeat();
|
|
expect(rawSocket.destroy).not.toHaveBeenCalled();
|
|
expect(telnetSock.write).toHaveBeenCalledWith('?');
|
|
});
|
|
|
|
test('Eingehende Daten aktualisieren _lastDataAt', () => {
|
|
const { sender, rawSocket } = setup();
|
|
const before = sender._lastDataAt;
|
|
rawSocket.emit('data', Buffer.from('<Idle|MPos:0,0,0>'));
|
|
expect(sender._lastDataAt).toBeGreaterThanOrEqual(before);
|
|
});
|
|
|
|
test('Heartbeat-Timer wird bei socket close gestoppt', () => {
|
|
const { activeIntervals, telnetSock } = setup();
|
|
expect(activeIntervals.size).toBe(1);
|
|
telnetSock.emit('close');
|
|
expect(activeIntervals.size).toBe(0);
|
|
});
|
|
|
|
test('Heartbeat-Timer wird bei disconnect() gestoppt', () => {
|
|
const { sender, activeIntervals } = setup();
|
|
expect(activeIntervals.size).toBe(1);
|
|
sender.disconnect();
|
|
expect(activeIntervals.size).toBe(0);
|
|
});
|
|
|
|
test('Heartbeat stoppt sich selbst wenn tSocket null ist', () => {
|
|
const { sender, rawSocket, telnetSock, activeIntervals, fireHeartbeat } = setup();
|
|
sender.tSocket = null;
|
|
fireHeartbeat();
|
|
expect(rawSocket.destroy).not.toHaveBeenCalled();
|
|
expect(telnetSock.write).not.toHaveBeenCalled();
|
|
expect(activeIntervals.size).toBe(0);
|
|
});
|
|
});
|