Files
appRobotDriver/test/InputWS.api.test.js
2026-06-25 18:58:55 +02:00

185 lines
4.8 KiB
JavaScript

const http = require('http');
const WebSocket = require('ws');
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 address = server.address();
if (address && address.port) resolve(address.port);
else reject(new Error('Failed to get server 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) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(new Error('Timeout waiting for message')), 2000);
ws.once('message', (data) => {
clearTimeout(timer);
resolve(data.toString());
});
});
}
/** Resolves to true if NO message arrives within `ms`, false otherwise. */
function expectSilence(ws, ms = 200) {
return new Promise((resolve) => {
let gotMessage = false;
const onMessage = () => { gotMessage = true; };
ws.on('message', onMessage);
setTimeout(() => {
ws.off('message', onMessage);
resolve(!gotMessage);
}, ms);
});
}
/* ---------- tests ---------- */
describe('InputWS API response routing', () => {
let server;
function startServer(robot, gcode = GCode) {
server = http.createServer();
const sharedState = { connectedClients: [], lastCommands: [], lastPings: [] };
initInputWS(server, robot, gcode, sharedState);
return { sharedState };
}
afterEach(async () => {
if (server) {
await new Promise((resolve) => server.close(resolve));
server = null;
}
});
test('Ping is answered to the requester only (not broadcast)', async () => {
startServer(createDummyRobot());
const port = await listen(server);
const a = await connect(port);
const b = await connect(port);
const aReply = nextMessage(a);
const bSilent = expectSilence(b);
a.send('Ping');
expect(await aReply).toBe('Ping');
expect(await bSilent).toBe(true);
a.close();
b.close();
});
test('M114 status query is answered to the requester only', async () => {
const robot = createDummyRobot();
robot.x = 5; robot.y = 6; robot.z = 7;
startServer(robot);
const port = await listen(server);
const a = await connect(port);
const b = await connect(port);
const aReply = nextMessage(a);
const bSilent = expectSilence(b);
a.send('M114');
const parsed = JSON.parse(await aReply);
expect(parsed.position).toEqual({ x: 5, y: 6, z: 7, a: 0, b: 0, c: 0, e: 0 });
expect(await bSilent).toBe(true);
a.close();
b.close();
});
test('G-code motion command broadcasts the new position to all clients', async () => {
const robot = createDummyRobot();
startServer(robot);
const port = await listen(server);
const a = await connect(port);
const b = await connect(port);
const aReply = nextMessage(a);
const bReply = nextMessage(b);
a.send('G1 X1 Y2 Z3');
const aParsed = JSON.parse(await aReply);
const bParsed = JSON.parse(await bReply);
expect(aParsed.position).toEqual({ x: 1, y: 2, z: 3, a: 0, b: 0, c: 0, e: 0 });
expect(bParsed.position).toEqual({ x: 1, y: 2, z: 3, a: 0, b: 0, c: 0, e: 0 });
expect(robot.sendCommand).toHaveBeenCalled();
a.close();
b.close();
});
test('unknown input returns a machine-readable error to the sender only', async () => {
startServer(createDummyRobot());
const port = await listen(server);
const a = await connect(port);
const b = await connect(port);
const aReply = nextMessage(a);
const bSilent = expectSilence(b);
a.send('TOTALLY_UNKNOWN');
const err = JSON.parse(await aReply);
expect(err).toEqual({
type: 'error',
code: 'UNKNOWN_COMMAND',
message: 'Unrecognized input',
input: 'TOTALLY_UNKNOWN'
});
expect(await bSilent).toBe(true);
a.close();
b.close();
});
test('G-code processing errors are reported as a machine-readable error', async () => {
const throwingGCode = {
containsCommand: () => true,
ContainsFilesCommand: () => false,
receiveGCode: () => { throw new Error('boom'); },
getM114: () => '{}'
};
startServer(createDummyRobot(), throwingGCode);
const port = await listen(server);
const a = await connect(port);
const aReply = nextMessage(a);
a.send('G1 X1');
const err = JSON.parse(await aReply);
expect(err).toMatchObject({
type: 'error',
code: 'GCODE_ERROR',
message: 'boom',
input: 'G1 X1'
});
a.close();
});
});