'use strict'; // Output-coverage tests per .claude/rules/output-coverage.md and // test/_output-manifest.md. Every output is exercised in both populated // and degraded states. 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 makeChild(id, name = id, softwareType = 'measurement') { return { config: { general: { id, name }, functionality: { softwareType, positionVsParent: 'downstream' }, }, }; } function makeCtx(nodeId = 'dApi-1') { const sends = []; const logs = []; return { sends, logs, ctx: { node: { id: nodeId }, RED: { nodes: { getNode: () => null } }, send: (m) => sends.push(m), logger: null, }, }; } // ── Port 0 message shape: populated ──────────────────────────────────── test('Port 0 emit has all required keys when token + folderUid configured', async () => { const api = new DashboardApi({ grafanaConnector: { protocol: 'http', host: 'grafana', port: 3000, bearerToken: 'tok', folderUid: 'rnd-folder' }, }); api.lastFlowsStartedDiff = null; // cold start const { sends, ctx } = makeCtx(); await handlers.registerChild(api, { topic: 'child.register', payload: makeChild('m-1', 'FT-001') }, ctx); assert.ok(sends.length >= 1); const m = sends[0]; assert.equal(m.topic, 'create'); assert.equal(m.method, 'POST'); assert.equal(m.headers['Accept'], 'application/json'); assert.equal(m.headers['Content-Type'], 'application/json'); assert.equal(m.headers.Authorization, 'Bearer tok'); assert.match(m.url, /^http:\/\/grafana:3000\/api\/dashboards\/db$/); assert.equal(m.payload.overwrite, true); assert.ok(m.payload.dashboard, 'dashboard JSON present'); assert.equal(m.payload.folderUid, 'rnd-folder'); // meta assert.equal(m.meta.nodeId, 'm-1'); assert.equal(m.meta.softwareType, 'measurement'); assert.equal(typeof m.meta.uid, 'string'); assert.equal(m.meta.title, 'FT-001'); assert.equal(m.meta.trigger, 'child.register'); }); // ── Port 0 degraded: token absent, folderUid absent ─────────────────── test('Port 0 emit omits Authorization header when no bearerToken configured', async () => { const api = new DashboardApi({}); // no creds api.lastFlowsStartedDiff = null; const { sends, ctx } = makeCtx(); await handlers.registerChild(api, { topic: 'child.register', payload: makeChild('m-2') }, ctx); const m = sends[0]; assert.equal(m.headers.Authorization, undefined, 'Authorization should be absent (not empty string, not null)'); assert.equal(m.payload.folderUid, undefined, 'folderUid should be absent when empty'); assert.equal('folderId' in m.payload, false, 'folderId should also be absent (not 0)'); }); // ── Port 0 degraded: no template for softwareType ───────────────────── test('Port 0 emits no message when child softwareType has no template', async () => { const api = new DashboardApi({}); api.lastFlowsStartedDiff = null; const { sends, ctx } = makeCtx(); // 'nonexistent' has no config/<>.json file await handlers.registerChild(api, { topic: 'child.register', payload: makeChild('m-3', 'm-3', 'nonexistent') }, ctx); assert.equal(sends.length, 0, 'no upsert message should be emitted when template missing'); }); // ── Diff-skip path: no emission, logged outcome:no-diff ─────────────── test('Diff-skip suppresses Port 0 emission AND records the skip in source.logger', async () => { const api = new DashboardApi({}); // Set diff so the predicate returns false (no overlap with subtree). api.lastFlowsStartedDiff = { added: ['unrelated'], changed: [], removed: [], rewired: [] }; // Stub logger to capture const captured = []; api.logger = { info: (e) => captured.push(e), debug: () => {} }; const { sends, ctx } = makeCtx('dApi-1'); await handlers.registerChild(api, { topic: 'child.register', payload: makeChild('m-4') }, ctx); assert.equal(sends.length, 0, 'no upsert emitted when subtree unchanged'); const skipLog = captured.find((e) => e.event === 'regen-skipped'); assert.ok(skipLog, 'skip log emitted'); assert.equal(skipLog.outcome, 'no-diff'); assert.equal(skipLog.trigger, 'child.register'); assert.equal(skipLog.dashboardApiId, 'dApi-1'); assert.equal(skipLog.childId, 'm-4'); }); // ── Successful regen logs structured fields per N-4 ─────────────────── test('Successful regen logs event=regen-emitted with N-4 fields', async () => { const api = new DashboardApi({}); api.lastFlowsStartedDiff = null; // cold start → always regen const captured = []; api.logger = { info: (e) => captured.push(e), debug: () => {} }; const { ctx } = makeCtx('dApi-1'); await handlers.registerChild(api, { topic: 'child.register', payload: makeChild('m-5') }, ctx); const emitLog = captured.find((e) => e.event === 'regen-emitted'); assert.ok(emitLog, 'regen-emitted log present'); assert.equal(emitLog.trigger, 'child.register'); assert.equal(emitLog.dashboardApiId, 'dApi-1'); assert.equal(emitLog.childId, 'm-5'); assert.equal(typeof emitLog.dashboardCount, 'number'); }); // ── Manual regen logs manual-regen-requested + emits with trigger:manual ─ test('Manual regen logs manual-regen-requested and stamps trigger=manual', async () => { const api = new DashboardApi({}); api.recordChild(makeChild('m-6')); const captured = []; api.logger = { info: (e) => captured.push(e), debug: () => {} }; const { sends, ctx } = makeCtx(); await handlers.regenerateDashboard(api, { topic: 'regenerate-dashboard', payload: {} }, ctx); const reqLog = captured.find((e) => e.event === 'manual-regen-requested'); assert.ok(reqLog, 'manual-regen-requested log present'); assert.equal(reqLog.cachedChildCount, 1); if (sends.length > 0) { assert.equal(sends[0].meta.trigger, 'manual'); } });