2026-05-11 19:44:09 +02:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
// Phase 10 rewrite: drives only the public BaseNodeAdapter surface.
|
|
|
|
|
// A child.register / registerChild msg with an unknown id should resolve
|
|
|
|
|
// to no-op (the handler logs warn, no throw) and still call done.
|
|
|
|
|
|
2026-02-19 17:37:42 +01:00
|
|
|
const test = require('node:test');
|
|
|
|
|
const assert = require('node:assert/strict');
|
|
|
|
|
|
2026-05-11 19:44:09 +02:00
|
|
|
const nodeClass = require('../../src/nodeClass');
|
|
|
|
|
const { makeUiConfig } = require('../helpers/factories');
|
2026-02-19 17:37:42 +01:00
|
|
|
|
2026-05-11 19:44:09 +02:00
|
|
|
function makeRED(nodeMap = {}) {
|
|
|
|
|
return { nodes: { getNode: (id) => nodeMap[id] || null } };
|
|
|
|
|
}
|
2026-02-19 17:37:42 +01:00
|
|
|
|
2026-05-11 19:44:09 +02:00
|
|
|
function makeNode(id = 'reactor-node-1') {
|
|
|
|
|
const sends = [];
|
|
|
|
|
const statuses = [];
|
|
|
|
|
const handlers = {};
|
|
|
|
|
return {
|
|
|
|
|
id, sends, statuses, handlers,
|
|
|
|
|
send(arr) { sends.push(arr); },
|
|
|
|
|
status(b) { statuses.push(b); },
|
|
|
|
|
on(ev, fn) { handlers[ev] = fn; },
|
|
|
|
|
warn() {}, error() {},
|
2026-02-19 17:37:42 +01:00
|
|
|
};
|
2026-05-11 19:44:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function closeNode(node) {
|
|
|
|
|
if (node.handlers.close) node.handlers.close(() => {});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test('registerChild alias with unknown id is ignored without throwing', async () => {
|
|
|
|
|
const node = makeNode();
|
|
|
|
|
new nodeClass(makeUiConfig(), makeRED(), node, 'reactor');
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
let done = 0;
|
|
|
|
|
await assert.doesNotReject(async () => {
|
|
|
|
|
await node.handlers.input(
|
|
|
|
|
{ topic: 'registerChild', payload: 'missing-child', positionVsParent: 'upstream' },
|
|
|
|
|
() => {},
|
|
|
|
|
() => { done += 1; },
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
assert.equal(done, 1);
|
|
|
|
|
} finally {
|
|
|
|
|
closeNode(node);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('child.register canonical topic with unknown id is ignored without throwing', async () => {
|
|
|
|
|
const node = makeNode();
|
|
|
|
|
new nodeClass(makeUiConfig(), makeRED(), node, 'reactor');
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
let done = 0;
|
|
|
|
|
await assert.doesNotReject(async () => {
|
|
|
|
|
await node.handlers.input(
|
|
|
|
|
{ topic: 'child.register', payload: 'missing-child', positionVsParent: 'upstream' },
|
|
|
|
|
() => {},
|
|
|
|
|
() => { done += 1; },
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
assert.equal(done, 1);
|
|
|
|
|
} finally {
|
|
|
|
|
closeNode(node);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('child.register with a child that has no .source is ignored without throwing', async () => {
|
|
|
|
|
const node = makeNode();
|
|
|
|
|
// The looked-up RED node exists but lacks a `.source` — the handler
|
|
|
|
|
// guards against this and logs warn.
|
|
|
|
|
new nodeClass(makeUiConfig(), makeRED({ orphan: {} }), node, 'reactor');
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
let done = 0;
|
|
|
|
|
await assert.doesNotReject(async () => {
|
|
|
|
|
await node.handlers.input(
|
|
|
|
|
{ topic: 'child.register', payload: 'orphan', positionVsParent: 'upstream' },
|
|
|
|
|
() => {},
|
|
|
|
|
() => { done += 1; },
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
assert.equal(done, 1);
|
|
|
|
|
} finally {
|
|
|
|
|
closeNode(node);
|
|
|
|
|
}
|
2026-02-19 17:37:42 +01:00
|
|
|
});
|