Fileservice
This commit is contained in:
151
test/FCodeClient.test.js
Normal file
151
test/FCodeClient.test.js
Normal file
@@ -0,0 +1,151 @@
|
||||
const FCodeClient = require('../robot/FCodeClient');
|
||||
|
||||
const robot = {
|
||||
x: 0, y: 300, z: 0,
|
||||
phi: Math.PI / 2, theta: -Math.PI / 2, psi: 0, e: 0,
|
||||
feedrate: 1000,
|
||||
};
|
||||
|
||||
function mockOk(data) {
|
||||
global.fetch = jest.fn().mockResolvedValue({
|
||||
ok: true, status: 200, statusText: 'OK',
|
||||
json: () => Promise.resolve(data),
|
||||
});
|
||||
}
|
||||
|
||||
function mockErr(status, code, message) {
|
||||
global.fetch = jest.fn().mockResolvedValue({
|
||||
ok: false, status, statusText: 'Error',
|
||||
json: () => Promise.resolve({ code, message }),
|
||||
});
|
||||
}
|
||||
|
||||
afterEach(() => { delete global.fetch; });
|
||||
|
||||
/* ---------- isFCode ---------- */
|
||||
|
||||
describe('isFCode', () => {
|
||||
test('erkennt alle bekannten FCodes', () => {
|
||||
for (const f of ['FList','FShow','FLoad demo','FSave Name','FClear','FPoint','FPlus','FMinus','FFirst','FLast','FGoto 3','FPlay','FStop']) {
|
||||
expect(FCodeClient.isFCode(f)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
test('erkennt Nicht-FCodes nicht', () => {
|
||||
expect(FCodeClient.isFCode('G1 X100')).toBe(false);
|
||||
expect(FCodeClient.isFCode('F1000')).toBe(false);
|
||||
expect(FCodeClient.isFCode('M114')).toBe(false);
|
||||
expect(FCodeClient.isFCode('Ping')).toBe(false);
|
||||
expect(FCodeClient.isFCode('')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
/* ---------- handle: Listing / Info ---------- */
|
||||
|
||||
test('FList → GET /api/programs, type list', async () => {
|
||||
mockOk([{ id: 'a', name: 'A' }]);
|
||||
const result = await FCodeClient.handle(robot, 'FList');
|
||||
expect(result.type).toBe('list');
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
expect.stringContaining('/api/programs'),
|
||||
expect.objectContaining({ method: 'GET' })
|
||||
);
|
||||
expect(JSON.parse(result.data)).toEqual([{ id: 'a', name: 'A' }]);
|
||||
});
|
||||
|
||||
test('FShow ohne id → GET /api/active', async () => {
|
||||
mockOk({ programId: 'x', cursor: 0 });
|
||||
const result = await FCodeClient.handle(robot, 'FShow');
|
||||
expect(global.fetch).toHaveBeenCalledWith(expect.stringContaining('/api/active'), expect.anything());
|
||||
expect(result.type).toBe('show');
|
||||
});
|
||||
|
||||
test('FShow mit id → GET /api/programs/:id', async () => {
|
||||
mockOk({ id: 'demo', name: 'Demo' });
|
||||
await FCodeClient.handle(robot, 'FShow demo');
|
||||
expect(global.fetch).toHaveBeenCalledWith(expect.stringContaining('/api/programs/demo'), expect.anything());
|
||||
});
|
||||
|
||||
/* ---------- handle: Teaching ---------- */
|
||||
|
||||
test('FPoint → POST /api/active/points mit Roboterpose (Radian)', async () => {
|
||||
mockOk({ index: 0, line: 'G90 G1 x0 y300 z0 a1.5708 b-1.5708 c0 e0 f1000' });
|
||||
const result = await FCodeClient.handle(robot, 'FPoint');
|
||||
expect(result.type).toBe('point');
|
||||
const call = global.fetch.mock.calls[0];
|
||||
expect(call[1].method).toBe('POST');
|
||||
const body = JSON.parse(call[1].body);
|
||||
expect(body.pose.a).toBeCloseTo(Math.PI / 2, 4);
|
||||
expect(body.pose.b).toBeCloseTo(-Math.PI / 2, 4);
|
||||
expect(body.feedrate).toBe(1000);
|
||||
});
|
||||
|
||||
/* ---------- handle: Program management ---------- */
|
||||
|
||||
test('FLoad → PUT /api/active mit id', async () => {
|
||||
mockOk({ programId: 'mein_prog', cursor: 0, lineCount: 5 });
|
||||
const result = await FCodeClient.handle(robot, 'FLoad mein_prog');
|
||||
expect(result.type).toBe('ok');
|
||||
const body = JSON.parse(global.fetch.mock.calls[0][1].body);
|
||||
expect(body).toEqual({ id: 'mein_prog' });
|
||||
});
|
||||
|
||||
test('FSave → POST /api/programs mit name und fromActive:true', async () => {
|
||||
mockOk({ id: 'test_prog', name: 'Test Prog' });
|
||||
await FCodeClient.handle(robot, 'FSave Test Prog');
|
||||
const body = JSON.parse(global.fetch.mock.calls[0][1].body);
|
||||
expect(body.name).toBe('Test Prog');
|
||||
expect(body.fromActive).toBe(true);
|
||||
});
|
||||
|
||||
test('FClear → POST /api/active/clear', async () => {
|
||||
mockOk({ ok: true });
|
||||
await FCodeClient.handle(robot, 'FClear');
|
||||
expect(global.fetch).toHaveBeenCalledWith(expect.stringContaining('/api/active/clear'), expect.objectContaining({ method: 'POST' }));
|
||||
});
|
||||
|
||||
/* ---------- handle: Stepping → type step ---------- */
|
||||
|
||||
test('FPlus → POST /api/active/next → type step mit Zeile', async () => {
|
||||
mockOk({ cursor: 1, line: 'G90 G1 x10 y300 z0 a1.5708 b-1.5708 c0 e0 f1000' });
|
||||
const result = await FCodeClient.handle(robot, 'FPlus');
|
||||
expect(result.type).toBe('step');
|
||||
expect(result.line).toContain('G1');
|
||||
});
|
||||
|
||||
test('FMinus → POST /api/active/prev', async () => {
|
||||
mockOk({ cursor: 0, line: 'G90 G1 x0 y300 z0 a0 b0 c0 e0 f1000' });
|
||||
const result = await FCodeClient.handle(robot, 'FMinus');
|
||||
expect(result.type).toBe('step');
|
||||
});
|
||||
|
||||
test('FFirst / FLast', async () => {
|
||||
mockOk({ cursor: 0, line: 'G90 G1 x0 y0 z0 a0 b0 c0 e0 f1000' });
|
||||
const r1 = await FCodeClient.handle(robot, 'FFirst');
|
||||
expect(r1.type).toBe('step');
|
||||
|
||||
mockOk({ cursor: 4, line: 'G90 G1 x5 y0 z0 a0 b0 c0 e0 f1000' });
|
||||
const r2 = await FCodeClient.handle(robot, 'FLast');
|
||||
expect(r2.type).toBe('step');
|
||||
});
|
||||
|
||||
test('FGoto N → POST /api/active/goto mit index', async () => {
|
||||
mockOk({ cursor: 3, line: 'G90 G1 x3 y0 z0 a0 b0 c0 e0 f1000' });
|
||||
await FCodeClient.handle(robot, 'FGoto 3');
|
||||
const body = JSON.parse(global.fetch.mock.calls[0][1].body);
|
||||
expect(body.index).toBe(3);
|
||||
});
|
||||
|
||||
/* ---------- Fehlerbehandlung ---------- */
|
||||
|
||||
test('Fileservice-Fehler → wirft mit code und status', async () => {
|
||||
mockErr(404, 'PROGRAM_NOT_FOUND', 'not found');
|
||||
await expect(FCodeClient.handle(robot, 'FLoad nichtda')).rejects.toMatchObject({
|
||||
code: 'PROGRAM_NOT_FOUND',
|
||||
status: 404,
|
||||
});
|
||||
});
|
||||
|
||||
test('Unbekannter FCode → wirft', async () => {
|
||||
await expect(FCodeClient.handle(robot, 'FUnbekannt')).rejects.toThrow('Unbekannter FCode');
|
||||
});
|
||||
@@ -1,6 +1,26 @@
|
||||
const GCode = require('../robot/GCode.js');
|
||||
|
||||
test('GCode - FPoint', () => {
|
||||
var x = GCode.ContainsFilesCommand("FPoint") ;
|
||||
expect(x).toBe(true);
|
||||
});
|
||||
// Datei-Operationen liegen jetzt in appRobotFileservice.
|
||||
// GCode kennt keine ContainsFilesCommand mehr — FCodeClient.isFCode übernimmt die Erkennung.
|
||||
const FCodeClient = require('../robot/FCodeClient');
|
||||
|
||||
test('isFCode erkennt FPoint', () => {
|
||||
expect(FCodeClient.isFCode('FPoint')).toBe(true);
|
||||
});
|
||||
|
||||
test('isFCode erkennt FList', () => {
|
||||
expect(FCodeClient.isFCode('FList')).toBe(true);
|
||||
});
|
||||
|
||||
test('isFCode erkennt FPlus / FMinus / FFirst / FLast', () => {
|
||||
expect(FCodeClient.isFCode('FPlus')).toBe(true);
|
||||
expect(FCodeClient.isFCode('FMinus')).toBe(true);
|
||||
expect(FCodeClient.isFCode('FFirst')).toBe(true);
|
||||
expect(FCodeClient.isFCode('FLast')).toBe(true);
|
||||
});
|
||||
|
||||
test('isFCode: feedrate F1000 ist kein FCode', () => {
|
||||
expect(FCodeClient.isFCode('F1000')).toBe(false);
|
||||
});
|
||||
|
||||
test('isFCode: G1-Befehl ist kein FCode', () => {
|
||||
expect(FCodeClient.isFCode('G1 X100')).toBe(false);
|
||||
});
|
||||
|
||||
95
test/InputWS.fcode.test.js
Normal file
95
test/InputWS.fcode.test.js
Normal file
@@ -0,0 +1,95 @@
|
||||
// Testet das FCode-Routing in InputWS (FCodeClient wird gemockt).
|
||||
jest.mock('../robot/FCodeClient', () => ({
|
||||
isFCode: jest.fn((msg) => /^F[A-Z][a-z]+/.test(String(msg).trim())),
|
||||
handle: jest.fn(),
|
||||
}));
|
||||
|
||||
const http = require('http');
|
||||
const WebSocket = require('ws');
|
||||
const FCodeClient = require('../robot/FCodeClient');
|
||||
const initInputWS = require('../server/InputWS');
|
||||
const GCode = require('../robot/GCode');
|
||||
const createDummyRobot = require('./helpers/createDummyRobot');
|
||||
|
||||
/* ---------- helpers ---------- */
|
||||
|
||||
function listen(server) {
|
||||
return new Promise((resolve, reject) => {
|
||||
server.listen(0, () => {
|
||||
const { port } = server.address();
|
||||
port ? resolve(port) : reject(new Error('no port'));
|
||||
});
|
||||
server.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
function connect(port) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const ws = new WebSocket(`ws://127.0.0.1:${port}`);
|
||||
ws.on('open', () => resolve(ws));
|
||||
ws.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
function nextMessage(ws, timeoutMs = 2000) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const t = setTimeout(() => reject(new Error('Timeout')), timeoutMs);
|
||||
ws.once('message', (d) => { clearTimeout(t); resolve(d.toString()); });
|
||||
});
|
||||
}
|
||||
|
||||
/* ---------- suite ---------- */
|
||||
|
||||
describe('InputWS FCode-Routing', () => {
|
||||
let server, ws;
|
||||
|
||||
beforeEach(() => {
|
||||
FCodeClient.handle.mockReset();
|
||||
FCodeClient.isFCode.mockClear();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (ws) { ws.close(); ws = null; }
|
||||
if (server) { await new Promise(r => server.close(r)); server = null; }
|
||||
});
|
||||
|
||||
async function setup(robot) {
|
||||
server = http.createServer();
|
||||
initInputWS(server, robot || createDummyRobot(), GCode, { connectedClients: [], lastCommands: [], lastPings: [] });
|
||||
const port = await listen(server);
|
||||
ws = await connect(port);
|
||||
return ws;
|
||||
}
|
||||
|
||||
test('FList → broadcastet die Antwortdaten vom Fileservice', async () => {
|
||||
FCodeClient.handle.mockResolvedValue({ type: 'list', data: JSON.stringify([{ id: 'a' }]) });
|
||||
await setup();
|
||||
const replyP = nextMessage(ws);
|
||||
ws.send('FList');
|
||||
expect(JSON.parse(await replyP)).toEqual([{ id: 'a' }]);
|
||||
});
|
||||
|
||||
test('FPlus (step) → GCode-Zeile ausführen + M114 broadcasten', async () => {
|
||||
const line = 'G90 G1 x10 y300 z0 a1.5708 b-1.5708 c0 e0 f1000';
|
||||
FCodeClient.handle.mockResolvedValue({ type: 'step', line });
|
||||
const robot = createDummyRobot();
|
||||
await setup(robot);
|
||||
const replyP = nextMessage(ws);
|
||||
ws.send('FPlus');
|
||||
const msg = JSON.parse(await replyP);
|
||||
expect(msg.position).toBeDefined();
|
||||
expect(robot.sendCommand).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('Fileservice-Fehler → machine-readable FILE_ERROR an Sender', async () => {
|
||||
const err = Object.assign(new Error('not found'), { code: 'PROGRAM_NOT_FOUND' });
|
||||
FCodeClient.handle.mockRejectedValue(err);
|
||||
await setup();
|
||||
const replyP = nextMessage(ws);
|
||||
ws.send('FLoad nichtda');
|
||||
const msg = JSON.parse(await replyP);
|
||||
expect(msg.type).toBe('error');
|
||||
expect(msg.code).toBe('FILE_ERROR');
|
||||
expect(msg.input).toBe('FLoad nichtda');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user