Heartbeat
This commit is contained in:
147
test/Sender.Telnet.heartbeat.test.js
Normal file
147
test/Sender.Telnet.heartbeat.test.js
Normal file
@@ -0,0 +1,147 @@
|
||||
'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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user