const test = require('node:test'); const assert = require('node:assert/strict'); const { planSync, slotKey } = require('../lib/spoolSync'); const { paletteMatch } = require('../lib/filaments'); const palette = require('../db/bambu-filaments.json').filaments; const SERIAL = 'PRINTER1'; const tray = (slot, hex, uuid, extra = {}) => ({ unit: 0, slot, empty: false, material: 'PLA', type: 'Basic', colour: hex, brand: 'PLA Basic', uuid, tagUid: uuid ? `TAG${slot}` : null, idName: null, weight: 1000, ...extra, }); test('a tagged spool the shelf has never seen is created and named from the filament list', () => { const { actions, untracked } = planSync([], [tray(0, '#C12E1F', 'U-RED', { idName: 'A00-R0' })], SERIAL, palette); assert.equal(untracked.length, 0); assert.equal(actions.length, 1); assert.equal(actions[0].op, 'create'); assert.equal(actions[0].slot, 'PRINTER1/0/0'); assert.equal(actions[0].spool.colour, 'Red'); assert.equal(actions[0].spool.code, '10200'); assert.equal(actions[0].spool.colour_hex, '#C12E1F'); assert.equal(actions[0].spool.product_code, 'A00-R0'); assert.equal(actions[0].spool.total_g, 1000); assert.equal(actions[0].spool.remaining_g, null, 'an AMS lite can’t say how much is left'); }); test('a spool the shelf already knows is moved, not added again', () => { const existing = [{ id: 7, material: 'PLA', colour_hex: '#000000', rfid_uuid: 'U-BLACK', printer_serial: SERIAL, ams_slot: 'PRINTER1/0/3' }]; const { actions } = planSync(existing, [tray(1, '#000000', 'U-BLACK')], SERIAL, palette); assert.deepEqual(actions, [{ op: 'update', id: 7, slot: 'PRINTER1/0/1', printerSerial: SERIAL, remaining: null }]); }); test('a spool added by hand is adopted by the printer rather than duplicated', () => { // The Jade White spool added before the printer was connected. const existing = [{ id: 1, material: 'PLA', colour_hex: '#ffffff', rfid_uuid: null, printer_serial: null, ams_slot: null }]; const { actions } = planSync(existing, [tray(3, '#FFFFFF', 'U-WHITE', { idName: 'A00-W1' })], SERIAL, palette); assert.equal(actions.length, 1); assert.equal(actions[0].op, 'adopt'); assert.equal(actions[0].id, 1); assert.equal(actions[0].uuid, 'U-WHITE'); assert.equal(actions[0].code, '10100'); }); test('one hand-added spool is adopted once, even with two matching trays', () => { const existing = [{ id: 1, material: 'PLA', colour_hex: '#FFFFFF', rfid_uuid: null }]; const { actions } = planSync(existing, [tray(0, '#FFFFFF', 'U-W1'), tray(1, '#FFFFFF', 'U-W2')], SERIAL, palette); assert.deepEqual(actions.map((a) => a.op), ['adopt', 'create']); }); test('a hand-added spool of another colour or material is left alone', () => { const existing = [ { id: 1, material: 'PLA', colour_hex: '#FF0000', rfid_uuid: null }, // wrong colour { id: 2, material: 'PETG', colour_hex: '#FFFFFF', rfid_uuid: null }, // wrong material ]; const { actions } = planSync(existing, [tray(0, '#FFFFFF', 'U-W')], SERIAL, palette); assert.deepEqual(actions.map((a) => a.op), ['create']); }); test('an untagged spool is listed, not guessed at', () => { const { actions, untracked } = planSync([], [tray(2, '#123456', null)], SERIAL, palette); assert.deepEqual(actions, []); assert.deepEqual(untracked, [{ slot: 'PRINTER1/0/2', material: 'PLA', colour: '#123456' }]); }); test('a spool this printer no longer reports is unloaded; other printers’ spools are not', () => { const existing = [ { id: 5, material: 'PLA', colour_hex: '#000000', rfid_uuid: 'U-GONE', printer_serial: SERIAL, ams_slot: 'PRINTER1/0/0' }, { id: 6, material: 'PLA', colour_hex: '#000000', rfid_uuid: 'U-ELSEWHERE', printer_serial: 'OTHER', ams_slot: 'OTHER/0/0' }, ]; const { actions } = planSync(existing, [], SERIAL, palette); assert.deepEqual(actions, [{ op: 'unload', id: 5 }]); }); test('empty trays are ignored, and the external holder has its own slot key', () => { assert.deepEqual(planSync([], [{ empty: true, unit: 0, slot: 0 }], SERIAL, palette).actions, []); assert.equal(slotKey(SERIAL, { unit: null, slot: 254 }), 'PRINTER1/ext/254'); }); test('palette matching names a colour within its product line, and doesn’t guess', () => { assert.deepEqual(paletteMatch(palette, 'PLA', 'Basic', '#FFFFFF'), { name: 'Jade White', code: '10100', type: 'Basic' }); assert.equal(paletteMatch(palette, 'PLA', 'Matte', '#FFFFFF').name, 'Ivory White'); assert.equal(paletteMatch(palette, 'PLA', 'Basic', '#123456'), null); }); test('an AMS that measures fills in grams left; an AMS lite never does', () => { const trays = [tray(0, '#C12E1F', 'U-RED', { remain: 42 })]; const lite = planSync([], trays, SERIAL, palette, { measures: false }); assert.equal(lite.actions[0].spool.remaining_g, null); const pro = planSync([], trays, SERIAL, palette, { measures: true }); assert.equal(pro.actions[0].spool.remaining_g, 420); const existing = [{ id: 7, material: 'PLA', colour_hex: '#C12E1F', rfid_uuid: 'U-RED', printer_serial: SERIAL, ams_slot: 'PRINTER1/0/0' }]; assert.equal(planSync(existing, trays, SERIAL, palette, { measures: true }).actions[0].remaining, 420); // No reading at all leaves the stored amount alone. const unknown = [tray(0, '#C12E1F', 'U-RED', { remain: null })]; assert.equal(planSync(existing, unknown, SERIAL, palette, { measures: true }).actions[0].remaining, null); });