Kleine Arbeiten
This commit is contained in:
@@ -117,7 +117,13 @@ describe('InfoServer', () => {
|
||||
reconnectAttempt: 0,
|
||||
reconnectTimer: false,
|
||||
health: 'ok',
|
||||
reason: undefined
|
||||
reason: undefined,
|
||||
grblState: null,
|
||||
machinePosition: null,
|
||||
plannerBlocksFree: null,
|
||||
rxBytesFree: null,
|
||||
lastError: null,
|
||||
lastReportAt: null
|
||||
},
|
||||
{
|
||||
name: 'Hand',
|
||||
@@ -129,7 +135,13 @@ describe('InfoServer', () => {
|
||||
reconnectAttempt: 0,
|
||||
reconnectTimer: false,
|
||||
health: 'disconnected',
|
||||
reason: 'no active socket connection'
|
||||
reason: 'no active socket connection',
|
||||
grblState: null,
|
||||
machinePosition: null,
|
||||
plannerBlocksFree: null,
|
||||
rxBytesFree: null,
|
||||
lastError: null,
|
||||
lastReportAt: null
|
||||
}
|
||||
]);
|
||||
});
|
||||
@@ -175,7 +187,13 @@ describe('InfoServer', () => {
|
||||
reconnectAttempt: 2,
|
||||
reconnectTimer: true,
|
||||
health: 'warning',
|
||||
reason: undefined
|
||||
reason: undefined,
|
||||
grblState: null,
|
||||
machinePosition: null,
|
||||
plannerBlocksFree: null,
|
||||
rxBytesFree: null,
|
||||
lastError: null,
|
||||
lastReportAt: null
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
const TelnetSender = require('../robot/TelnetSenderGRBL');
|
||||
const MotorPosition = require('../robot/RobotMotorPosition');
|
||||
const Robot = require('../robot/kinematics/Arm3SegmentLinearX');
|
||||
// Produktiv-Funktion unter Test (vorher inline in dieser Datei, jetzt extrahiert).
|
||||
const { motorStateFromPorts } = require('../robot/portInverse');
|
||||
|
||||
const D = 180 / Math.PI;
|
||||
|
||||
@@ -27,18 +29,9 @@ function buildSenders() {
|
||||
};
|
||||
}
|
||||
|
||||
/* ---------- Die Umkehrung: GRBL-Readings → 7 Motorwerte ---------- */
|
||||
// r = { base:{x,y,z}, elbow:{x}, hand:{x,y,z} } (GRBL-Achswerte, Grad bzw. mm)
|
||||
function motorStateFromPorts(r) {
|
||||
const xMotor = r.base.x; // x-Port = xMotor (mm, direkt)
|
||||
const alpha = r.base.y / D; // y-Port = alpha·D
|
||||
const beta = (r.base.z + r.base.y) / D; // z-Port = (beta-alpha)·D ⇒ beta = z/D + alpha
|
||||
const a = r.elbow.x / D; // Elbow x-Port = a·D
|
||||
const b = r.hand.z / D; // Hand z-Port = b·D
|
||||
const c = (r.hand.x + r.hand.z) / D; // Hand x-Port = (c-b)·D ⇒ c = x/D + b
|
||||
const eMotor = r.hand.y / D; // Hand y-Port = eMotor·D
|
||||
return { xMotor, alpha, beta, a, b, c, eMotor };
|
||||
}
|
||||
/* ---------- Die Umkehrung kommt jetzt aus robot/portInverse.js ----------
|
||||
* (vorher hier inline; extrahiert für ToDo_9 Paket 4, damit Produktivcode und
|
||||
* dieser Verifikations-Test dieselbe Funktion nutzen.) */
|
||||
|
||||
/* ---------- Hilfen ---------- */
|
||||
// {x,y,z,a,b,c,e}-pos aus Motorwerten (Feld-Mapping der RobotMotorPosition)
|
||||
|
||||
141
test/RobotController.sync.test.js
Normal file
141
test/RobotController.sync.test.js
Normal file
@@ -0,0 +1,141 @@
|
||||
'use strict';
|
||||
// Tests für ToDo_9 Paket 4: Hardware-Sync-Command (M114 R).
|
||||
// - syncFromHardware liest MPos aller 3 Controller → Motorwerte → Pose
|
||||
// - bewegt den Roboter NICHT (kein sendCommand)
|
||||
// - robust gegen fehlende Rolle / ungültige/fehlende MPos
|
||||
|
||||
const RobotController = require('../robot/RobotController');
|
||||
const { motorStateFromPorts, D } = require('../robot/portInverse');
|
||||
const Robot = require('../robot/kinematics/Arm3SegmentLinearX');
|
||||
|
||||
// ── Fake-Sender mit Rolle und vorgegebenem MPos-Snapshot ─────────────────────
|
||||
function fakeSender(role, machinePosition, { fail } = {}) {
|
||||
return {
|
||||
controllerRole: role,
|
||||
requestStatusReport: jest.fn(() =>
|
||||
fail
|
||||
? Promise.reject(new Error(`${role}: timeout`))
|
||||
: Promise.resolve({ grblState: 'Idle', machinePosition, machinePositionType: 'MPos' })
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/** Roboter mit den drei Sendern, deren MPos die gegebenen Motorwerte kodieren. */
|
||||
function robotWithMotors(m) {
|
||||
const robot = new Robot(250, 264, 100);
|
||||
// Produktiv-Verkabelung: base[x,y,z]=[xMotor, alpha·D, (beta−alpha)·D],
|
||||
// elbow[x]=a·D, hand[x,y,z]=[(c−b)·D, eMotor·D, b·D]
|
||||
const base = [m.xMotor, m.alpha * D, (m.beta - m.alpha) * D];
|
||||
const elbow = [m.a * D];
|
||||
const hand = [(m.c - m.b) * D, m.eMotor * D, m.b * D];
|
||||
robot.cmdReceivers = [
|
||||
fakeSender('base', base),
|
||||
fakeSender('elbow', elbow),
|
||||
fakeSender('hand', hand),
|
||||
];
|
||||
return robot;
|
||||
}
|
||||
|
||||
describe('RobotController.syncFromHardware (ToDo_9 Paket 4)', () => {
|
||||
|
||||
beforeAll(() => { jest.spyOn(console, 'log').mockImplementation(() => {}); });
|
||||
afterAll(() => { jest.restoreAllMocks(); });
|
||||
|
||||
test('rekonstruiert Motorwerte aus MPos und übernimmt sie', async () => {
|
||||
const motors = { xMotor: 100, alpha: 0.30, beta: 0.50, a: 0.20, b: 0.40, c: 0.90, eMotor: 1.0 };
|
||||
const robot = robotWithMotors(motors);
|
||||
|
||||
await RobotController.syncFromHardware(robot);
|
||||
|
||||
expect(robot.xMotor).toBeCloseTo(motors.xMotor, 6);
|
||||
expect(robot.alpha).toBeCloseTo(motors.alpha, 6);
|
||||
expect(robot.beta).toBeCloseTo(motors.beta, 6);
|
||||
expect(robot.a).toBeCloseTo(motors.a, 6);
|
||||
expect(robot.b).toBeCloseTo(motors.b, 6);
|
||||
expect(robot.c).toBeCloseTo(motors.c, 6);
|
||||
expect(robot.eMotor).toBeCloseTo(motors.eMotor, 6);
|
||||
});
|
||||
|
||||
test('rechnet die Pose vor und gibt sie zurück (gleiche wie Referenz-Roboter)', async () => {
|
||||
const motors = { xMotor: 42, alpha: 0.10, beta: 0.10, a: 0.05, b: 1.20, c: 1.25, eMotor: 0.0 };
|
||||
const robot = robotWithMotors(motors);
|
||||
|
||||
const pose = await RobotController.syncFromHardware(robot);
|
||||
|
||||
const ref = new Robot(250, 264, 100);
|
||||
Object.assign(ref, motors);
|
||||
ref.calculatePositionFromMotorAngles();
|
||||
|
||||
for (const k of ['x', 'y', 'z', 'phi', 'theta', 'psi']) {
|
||||
expect(pose[k]).toBeCloseTo(ref[k], 6);
|
||||
}
|
||||
});
|
||||
|
||||
test('bewegt den Roboter NICHT (kein execCommand auf den Sendern)', async () => {
|
||||
const robot = robotWithMotors({ xMotor: 10, alpha: 0.1, beta: 0.2, a: 0.1, b: 0.2, c: 0.3, eMotor: 0 });
|
||||
// execCommand-Spy auf alle Sender; darf nie aufgerufen werden
|
||||
robot.cmdReceivers.forEach(s => { s.execCommand = jest.fn(); });
|
||||
|
||||
await RobotController.syncFromHardware(robot);
|
||||
|
||||
robot.cmdReceivers.forEach(s => expect(s.execCommand).not.toHaveBeenCalled());
|
||||
});
|
||||
|
||||
test('setzt motorPosition zurück (nächster Move rechnet von echter Position)', async () => {
|
||||
const robot = robotWithMotors({ xMotor: 5, alpha: 0, beta: 0, a: 0, b: 0, c: 0, eMotor: 0 });
|
||||
robot.motorPosition = { dummy: true };
|
||||
robot.motorPositionOld = { dummy: true };
|
||||
|
||||
await RobotController.syncFromHardware(robot);
|
||||
|
||||
expect(robot.motorPosition).toBeNull();
|
||||
expect(robot.motorPositionOld).toBeNull();
|
||||
});
|
||||
|
||||
test('fragt alle drei Controller aktiv ab (requestStatusReport)', async () => {
|
||||
const robot = robotWithMotors({ xMotor: 0, alpha: 0, beta: 0, a: 0, b: 0, c: 0, eMotor: 0 });
|
||||
await RobotController.syncFromHardware(robot);
|
||||
robot.cmdReceivers.forEach(s => expect(s.requestStatusReport).toHaveBeenCalledTimes(1));
|
||||
});
|
||||
|
||||
test('wirft, wenn eine Controller-Rolle fehlt', async () => {
|
||||
const robot = new Robot(250, 264, 100);
|
||||
robot.cmdReceivers = [fakeSender('base', [0, 0, 0]), fakeSender('hand', [0, 0, 0])]; // elbow fehlt
|
||||
await expect(RobotController.syncFromHardware(robot)).rejects.toThrow(/elbow/);
|
||||
});
|
||||
|
||||
test('wirft bei ungültiger MPos (zu wenige Achsen)', async () => {
|
||||
const robot = new Robot(250, 264, 100);
|
||||
robot.cmdReceivers = [
|
||||
fakeSender('base', [1, 2]), // nur 2 statt 3 Werte
|
||||
fakeSender('elbow', [0]),
|
||||
fakeSender('hand', [0, 0, 0]),
|
||||
];
|
||||
await expect(RobotController.syncFromHardware(robot)).rejects.toThrow(/base/);
|
||||
});
|
||||
|
||||
test('wirft, wenn ein Controller nicht antwortet (Timeout propagiert)', async () => {
|
||||
const robot = new Robot(250, 264, 100);
|
||||
robot.cmdReceivers = [
|
||||
fakeSender('base', [0, 0, 0]),
|
||||
fakeSender('elbow', [0], { fail: true }),
|
||||
fakeSender('hand', [0, 0, 0]),
|
||||
];
|
||||
await expect(RobotController.syncFromHardware(robot)).rejects.toThrow(/timeout/);
|
||||
});
|
||||
|
||||
test('Dispatch: applyCommand(M114 R) liefert ein Promise', () => {
|
||||
const robot = robotWithMotors({ xMotor: 0, alpha: 0, beta: 0, a: 0, b: 0, c: 0, eMotor: 0 });
|
||||
const result = RobotController.applyCommand(robot, { command: 'M114', params: { R: true } });
|
||||
expect(result && typeof result.then).toBe('function');
|
||||
return result; // sauber auflösen lassen
|
||||
});
|
||||
|
||||
test('receive(): bare M114 ohne R löst keinen Sync aus (synchron, undefined)', () => {
|
||||
const robot = robotWithMotors({ xMotor: 0, alpha: 0, beta: 0, a: 0, b: 0, c: 0, eMotor: 0 });
|
||||
robot.cmdReceivers.forEach(s => s.requestStatusReport.mockClear());
|
||||
const result = RobotController.receive(robot, 'M114');
|
||||
expect(result).toBeUndefined();
|
||||
robot.cmdReceivers.forEach(s => expect(s.requestStatusReport).not.toHaveBeenCalled());
|
||||
});
|
||||
});
|
||||
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