Line Sender

This commit is contained in:
chk
2026-06-15 09:22:41 +02:00
parent b7d742b9a4
commit 635b8bd1f3
14 changed files with 1050 additions and 217 deletions

View File

@@ -1,9 +1,12 @@
jest.mock('../src/driverClient');
const os = require('os');
const path = require('path');
const fsp = require('fs/promises');
const cfg = require('../src/config');
const store = require('../src/store/fileStore');
const units = require('../src/gcode/units');
const driverClient = require('../src/driverClient');
const { ActiveState } = require('../src/active/activeState');
let tmp;
@@ -126,3 +129,67 @@ test('FPoint ohne aktives Programm → auto-lädt Default-Programm (log)', async
expect(r.index).toBe(0);
expect(a.programId).toBeTruthy(); // Default-Programm wurde geladen
});
// ─── Senden-Modus (execute=true) ─────────────────────────────────────────────
describe('Senden-Modus (execute=true)', () => {
const M114 = { position: { x: 10, y: 300, z: 0, a: 0 }, motorCounts: {} };
beforeEach(() => {
driverClient.send.mockReset();
driverClient.send.mockResolvedValue(M114);
});
async function makeActive() {
await store.write('step_1', {
name: 'Step',
lines: [
'G90 G1 x0 y300 z0 a90.00 b-90.00 c0.00 e0.00 f1000 ;1',
'G90 G1 x10 y300 z0 a0.00 b-90.00 c0.00 e0.00 f1000 ;2',
],
});
const a = new ActiveState();
await a.load('step_1');
return a;
}
test('next(true) ruft driverClient.send mit der G-Code-Zeile auf', async () => {
const a = await makeActive();
await a.first(); // cursor → 0
await a.next(true); // cursor → 1, sendet Zeile 1
expect(driverClient.send).toHaveBeenCalledTimes(1);
const sentLine = driverClient.send.mock.calls[0][0];
// Zeile muss Radian-Werte enthalten (a ≈ 0) und kein Semikolon (kein Kommentar)
expect(sentLine).toContain('G90 G1');
expect(sentLine).not.toContain(';');
});
test('next(true) gibt { cursor, line, driverPos } zurück', async () => {
const a = await makeActive();
await a.first();
const result = await a.next(true);
expect(result.cursor).toBe(1);
expect(result.line).toContain('G90 G1');
expect(result.driverPos).toEqual(M114);
});
test('next(true) wirft Fehler mit driverCode wenn driverClient.send ablehnt', async () => {
const driverErr = Object.assign(new Error('inverse kinematics failed'), { driverCode: 'GCODE_ERROR' });
driverClient.send.mockRejectedValueOnce(driverErr);
const a = await makeActive();
await a.first();
await expect(a.next(true)).rejects.toMatchObject({ driverCode: 'GCODE_ERROR' });
});
test('next(false) ruft driverClient.send NICHT auf', async () => {
const a = await makeActive();
await a.first();
await a.next(false); // Navigieren-Modus
expect(driverClient.send).not.toHaveBeenCalled();
});
});

136
test/driverClient.test.js Normal file
View File

@@ -0,0 +1,136 @@
// Tests für src/driverClient.js
// Mockt das 'ws'-Modul vollständig — keine echte Netzwerkverbindung.
jest.mock('ws');
function makeMockWs(readyState = 1 /*OPEN*/) {
return {
readyState,
send: jest.fn(),
once: jest.fn(),
on: jest.fn(),
};
}
describe('driverClient', () => {
// Nach jedem resetModules() zeigt 'WebSocket' auf die frische Auto-Mock-Instanz.
// Deshalb wird MockWebSocket in beforeEach neu geladen, nicht oben auf dem Modul.
let MockWebSocket;
let cfg;
let dc;
beforeEach(() => {
jest.resetModules();
jest.clearAllMocks();
MockWebSocket = require('ws');
MockWebSocket.OPEN = 1;
MockWebSocket.CLOSED = 3;
cfg = require('../src/config');
cfg.driverWsUrl = 'wss://test-driver:9999';
cfg.driverTimeoutMs = 500;
dc = require('../src/driverClient');
});
// ─── Fehler: nicht konfiguriert ────────────────────────────────────────────
test('send() rejects mit NOT_CONFIGURED wenn DRIVER_WS_URL leer ist', async () => {
cfg.driverWsUrl = '';
await expect(dc.send('G90 G1 x0')).rejects.toMatchObject({
driverCode: 'NOT_CONFIGURED',
});
});
// ─── Erfolg: M114-Antwort ──────────────────────────────────────────────────
test('send() resolved mit M114-Objekt bei Erfolg', async () => {
const mockWs = makeMockWs();
MockWebSocket.mockImplementation(() => mockWs);
let capturedHandler;
mockWs.once.mockImplementation((event, handler) => {
if (event === 'message') capturedHandler = handler;
});
const promise = dc.send('G90 G1 x10');
const m114 = { position: { x: 10, y: 300, z: 0, a: 0 }, motorCounts: { x: 1 } };
capturedHandler(Buffer.from(JSON.stringify(m114)));
const result = await promise;
expect(result).toMatchObject({ position: { x: 10 } });
expect(mockWs.send).toHaveBeenCalledWith('G90 G1 x10');
});
// ─── Fehler: Driver meldet GCODE_ERROR ────────────────────────────────────
test('send() rejects mit driverCode=GCODE_ERROR wenn Driver Fehler schickt', async () => {
const mockWs = makeMockWs();
MockWebSocket.mockImplementation(() => mockWs);
let capturedHandler;
mockWs.once.mockImplementation((event, handler) => {
if (event === 'message') capturedHandler = handler;
});
const promise = dc.send('UNGUELTIG');
const errMsg = { type: 'error', code: 'GCODE_ERROR', message: 'inverse kinematics failed', input: 'UNGUELTIG' };
capturedHandler(Buffer.from(JSON.stringify(errMsg)));
await expect(promise).rejects.toMatchObject({
message: 'inverse kinematics failed',
driverCode: 'GCODE_ERROR',
});
});
// ─── Fehler: Timeout ───────────────────────────────────────────────────────
test('send() rejects mit TIMEOUT wenn Driver nicht antwortet', async () => {
jest.useFakeTimers();
const mockWs = makeMockWs();
MockWebSocket.mockImplementation(() => mockWs);
mockWs.once.mockImplementation(() => {}); // handler nie aufrufen → Timeout tritt ein
cfg.driverTimeoutMs = 5000;
const promise = dc.send('G90 G1 x0');
// Fake-Timer vorschiessen damit der Timeout feuert
jest.runAllTimers();
await expect(promise).rejects.toMatchObject({ driverCode: 'TIMEOUT' });
jest.useRealTimers();
});
// ─── Unbekanntes Antwort-Format → trotzdem Erfolg ────────────────────────
test('send() resolved auch bei unbekanntem (nicht-JSON) Antwort-Format', async () => {
const mockWs = makeMockWs();
MockWebSocket.mockImplementation(() => mockWs);
let capturedHandler;
mockWs.once.mockImplementation((event, handler) => {
if (event === 'message') capturedHandler = handler;
});
const promise = dc.send('G28');
capturedHandler(Buffer.from('ok'));
const result = await promise;
expect(result).toMatchObject({ raw: 'ok' });
});
// ─── configured() ──────────────────────────────────────────────────────────
test('configured() ist true wenn DRIVER_WS_URL gesetzt', () => {
expect(dc.configured()).toBe(true);
});
test('configured() ist false wenn DRIVER_WS_URL leer', () => {
cfg.driverWsUrl = '';
expect(dc.configured()).toBe(false);
});
});