printhive/test/printerLive.test.js
dlawler489 88988237ae Don't let a busy printer's reports hold back spool sync; hide placeholder chamber temps
A printer mid-print reports every second, and each report restarted the
five-second wait, so a printing printer's spools never synced. Only a change
in what's loaded restarts it now. Printers without a chamber sensor report
5 °C, which is no longer shown.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 23:50:54 +10:00

97 lines
4.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const test = require('node:test');
const assert = require('node:assert/strict');
const { EventEmitter } = require('node:events');
// A stand-in for the MQTT client, so no network is involved.
const clients = [];
const live = require('../lib/printerLive');
live.setConnector((url, options) => {
const c = new EventEmitter();
Object.assign(c, {
url, options, connected: false, reconnecting: false, published: [], ended: false,
subscribe: (_topic, cb) => cb(null),
publish: (topic, body) => c.published.push({ topic, body: JSON.parse(body) }),
end: () => { c.ended = true; },
reconnect: () => {},
});
clients.push(c);
return c;
});
const report = (print) => Buffer.from(JSON.stringify({ print }));
const printerRow = { id: 1, host: '10.0.0.5', serial: 'SER1', access_code: 'code1' };
const tray = (id, type, colour) => ({ id: String(id), tray_type: type, tray_color: colour, tray_uuid: type ? `U${id}` : '0' });
test('live status merges partial reports, and a tray change syncs spools once', async (t) => {
t.after(() => live.stopAll());
const synced = [];
live.setTraysHandler(async (id, trays) => { synced.push({ id, trays }); return { updated: trays.length }; });
live.reconcile([printerRow, { id: 2, host: '', serial: 'X', access_code: 'y' }]);
assert.equal(clients.length, 1, 'a printer missing its address isn’t connected');
const c = clients[0];
assert.equal(c.options.password, 'code1');
c.connected = true;
c.emit('connect');
assert.equal(c.published[0].topic, 'device/SER1/request');
assert.equal(c.published[0].body.pushing.command, 'pushall');
c.emit('message', 'device/SER1/report', report({
gcode_state: 'RUNNING', mc_percent: 10, nozzle_temper: 220,
ams: { ams: [{ id: '0', tray: [tray(0, 'PLA', 'FF0000FF'), tray(1, '', '')] }] },
}));
c.emit('message', 'device/SER1/report', report({ mc_percent: 11 }));
const snap = live.snapshot(1);
assert.equal(snap.connected, true);
assert.equal(snap.status.state, 'RUNNING');
assert.equal(snap.status.progress, 11);
assert.equal(snap.status.nozzle, 220, 'kept from the earlier report');
assert.deepEqual(snap.loaded.map((l) => [l.slot, l.empty, l.colour, l.tagged]),
[[0, false, '#FF0000', true], [1, true, null, false]]);
assert.equal(JSON.stringify(snap).includes('code1'), false, 'no access code in a snapshot');
// "Test & sync" asks again and syncs straight away.
const pending = live.refresh(1, 3000);
c.emit('message', 'device/SER1/report', report({ ams: { ams: [{ id: '0', tray: [tray(0, 'PLA', 'FF0000FF'), tray(1, '', '')] }] } }));
const result = await pending;
assert.equal(result.connected, true);
assert.equal(result.sync.updated, 2);
assert.equal(synced.length, 1);
assert.equal(c.published.at(-1).body.pushing.command, 'pushall');
});
test('a busy printer’s steady reports don’t hold back a spool sync', async (t) => {
t.mock.timers.enable({ apis: ['setTimeout'] });
t.after(() => live.stopAll());
clients.length = 0;
const synced = [];
live.setTraysHandler(async (id) => { synced.push(id); return {}; });
live.reconcile([{ ...printerRow, id: 5, serial: 'SER5' }]);
const c = clients[0];
c.connected = true;
c.emit('connect');
c.emit('message', 't', report({ ams: { ams: [{ id: '0', tray: [tray(0, 'PLA', 'FF0000FF')] }] } }));
for (let i = 0; i < 10; i += 1) {
t.mock.timers.tick(1000);
c.emit('message', 't', report({ mc_percent: i }));
}
assert.equal(synced.length, 1, 'synced once, five seconds after the change');
// The same trays again change nothing.
c.emit('message', 't', report({ ams: { ams: [{ id: '0', tray: [tray(0, 'PLA', 'FF0000FF')] }] } }));
t.mock.timers.tick(6000);
assert.equal(synced.length, 1);
});
test('changing a printer’s code reopens its connection; removing it closes it', () => {
clients.length = 0;
live.reconcile([printerRow]);
live.reconcile([printerRow]);
assert.equal(clients.length, 1, 'no change, no new connection');
live.reconcile([{ ...printerRow, access_code: 'code2' }]);
assert.equal(clients.length, 2);
assert.equal(clients[0].ended, true);
live.reconcile([]);
assert.equal(clients[1].ended, true);
assert.deepEqual(live.snapshot(1), { live: false });
});