Line Sender
This commit is contained in:
@@ -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
136
test/driverClient.test.js
Normal 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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user