feat(dashboardapi): manual regen via msg.topic == regenerate-dashboard (#41)
Adds an explicit topic for operators (and the dashboardAPI v2 manual escape hatch from PRD F-12). On `regenerate-dashboard`, dashboardAPI iterates every child source cached by prior `child.register` messages and re-emits Grafana upsert messages — bypassing the diff-skip predicate from #36. - src/specificClass.js: light state cache (recordChild / cachedChildSources). - src/commands/handlers.js: refactor shared emit path; emitDashboardsFor() used by both child.register and regenerateDashboard; meta.trigger distinguishes the two for downstream filtering. - src/commands/index.js: register 'regenerate-dashboard' (alias 'regen'). - CONTRACT.md: document the new topic. - test/basic/slice41-manual-regen.basic.test.js: 5 cases covering cache semantics, no-op for empty cache, bypass-predicate, trigger stamp on both paths, registry exposure. Closes #41
This commit is contained in:
75
test/basic/slice41-manual-regen.basic.test.js
Normal file
75
test/basic/slice41-manual-regen.basic.test.js
Normal file
@@ -0,0 +1,75 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const DashboardApi = require('../../src/specificClass.js');
|
||||
const handlers = require('../../src/commands/handlers.js');
|
||||
|
||||
function makeCtx(sends, nodeId = 'dApi-1') {
|
||||
return {
|
||||
node: { id: nodeId },
|
||||
RED: { nodes: { getNode: () => null } },
|
||||
send: (m) => sends.push(m),
|
||||
logger: null,
|
||||
};
|
||||
}
|
||||
|
||||
function makeChildPayload(id, softwareType = 'measurement') {
|
||||
return {
|
||||
config: {
|
||||
general: { id, name: id },
|
||||
functionality: { softwareType, positionVsParent: 'downstream' },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test('recordChild caches child source by id; subsequent ones replace by id', () => {
|
||||
const api = new DashboardApi({});
|
||||
api.recordChild(makeChildPayload('a'));
|
||||
api.recordChild(makeChildPayload('b'));
|
||||
api.recordChild(makeChildPayload('a')); // replace
|
||||
assert.equal(api.cachedChildSources().length, 2);
|
||||
});
|
||||
|
||||
test('regenerate-dashboard with no cached children is a no-op (no msgs emitted)', () => {
|
||||
const api = new DashboardApi({});
|
||||
const sends = [];
|
||||
handlers.regenerateDashboard(api, { topic: 'regenerate-dashboard', payload: {} }, makeCtx(sends));
|
||||
assert.equal(sends.length, 0);
|
||||
});
|
||||
|
||||
test('regenerate-dashboard re-emits for each cached child, bypassing diff', () => {
|
||||
const api = new DashboardApi({});
|
||||
// Pre-populate cache as if two children had registered.
|
||||
api.recordChild(makeChildPayload('m-1'));
|
||||
api.recordChild(makeChildPayload('m-2'));
|
||||
|
||||
// Set a diff that says nothing changed — registerChild would skip, but
|
||||
// regenerateDashboard should ignore the predicate.
|
||||
api.lastFlowsStartedDiff = { added: [], changed: [], removed: [], rewired: [] };
|
||||
|
||||
const sends = [];
|
||||
handlers.regenerateDashboard(api, { topic: 'regenerate-dashboard', payload: {} }, makeCtx(sends));
|
||||
// Each child yields at least one dashboard message (the root for the child's view).
|
||||
assert.ok(sends.length >= 2, `expected ≥2 emitted msgs, got ${sends.length}`);
|
||||
// Every emitted msg carries trigger: 'manual' in meta.
|
||||
for (const m of sends) assert.equal(m.meta?.trigger, 'manual');
|
||||
});
|
||||
|
||||
test('child.register stamps trigger: child.register in emitted msg meta', () => {
|
||||
const api = new DashboardApi({});
|
||||
api.lastFlowsStartedDiff = null; // cold-start → always regen
|
||||
const sends = [];
|
||||
handlers.registerChild(api, { topic: 'child.register', payload: makeChildPayload('m-3') }, makeCtx(sends));
|
||||
assert.ok(sends.length >= 1);
|
||||
for (const m of sends) assert.equal(m.meta?.trigger, 'child.register');
|
||||
});
|
||||
|
||||
test('command registry exposes regenerate-dashboard with regen alias', () => {
|
||||
const registry = require('../../src/commands/index.js');
|
||||
const entry = registry.find((e) => e.topic === 'regenerate-dashboard');
|
||||
assert.ok(entry, 'topic registered');
|
||||
assert.deepEqual(entry.aliases, ['regen']);
|
||||
assert.equal(typeof entry.handler, 'function');
|
||||
});
|
||||
Reference in New Issue
Block a user