UNPKG

@seqera/node-red-seqera

Version:

Node-RED nodes for interacting with the Seqera Platform API

664 lines (584 loc) 19.4 kB
/** * Tests for nodes/workflow-monitor.js (seqera-workflow-monitor node) * * Tests the workflow monitoring functionality including: * - Polling workflow status at configurable intervals * - Three outputs: Active (running), Succeeded, Failed/Cancelled * - Status color mapping * - Interval cleanup on completion or close */ const { helper, nock, DEFAULT_BASE_URL, DEFAULT_WORKSPACE_ID, createConfigNode, createCredentials, createWorkflowResponse, } = require("./helper"); const { expect } = require("chai"); const sinon = require("sinon"); const workflowMonitorNode = require("../nodes/workflow-monitor"); const configNode = require("../nodes/config"); describe("seqera-workflow-monitor Node", function () { let clock; beforeEach(function (done) { helper.startServer(done); }); afterEach(function (done) { if (clock) { clock.restore(); clock = null; } helper.unload(); helper.stopServer(done); nock.cleanAll(); }); describe("node loading", function () { it("should be loaded with correct type", function (done) { const flow = [ createConfigNode(), { id: "monitor1", type: "seqera-workflow-monitor", name: "Test Monitor", seqera: "config-node-1", workflowId: "workflowId", workflowIdType: "msg", poll: 5, pollType: "num", keepPolling: true, wires: [[], [], []], }, ]; helper.load([configNode, workflowMonitorNode], flow, createCredentials(), function () { const n1 = helper.getNode("monitor1"); try { expect(n1).to.exist; expect(n1.type).to.equal("seqera-workflow-monitor"); expect(n1.name).to.equal("Test Monitor"); done(); } catch (err) { done(err); } }); }); it("should store poll interval configuration", function (done) { const flow = [ createConfigNode(), { id: "monitor1", type: "seqera-workflow-monitor", name: "Test Monitor", seqera: "config-node-1", poll: 10, pollType: "num", keepPolling: true, wires: [[], [], []], }, ]; helper.load([configNode, workflowMonitorNode], flow, createCredentials(), function () { const n1 = helper.getNode("monitor1"); try { expect(n1.pollIntervalProp).to.equal(10); expect(n1.keepPolling).to.be.true; done(); } catch (err) { done(err); } }); }); it("should default keepPolling to true when not specified", function (done) { const flow = [ createConfigNode(), { id: "monitor1", type: "seqera-workflow-monitor", name: "Test Monitor", seqera: "config-node-1", wires: [[], [], []], }, ]; helper.load([configNode, workflowMonitorNode], flow, createCredentials(), function () { const n1 = helper.getNode("monitor1"); try { expect(n1.keepPolling).to.be.true; done(); } catch (err) { done(err); } }); }); }); describe("status polling", function () { it("should fetch workflow status on input", function (done) { const flow = [ createConfigNode(), { id: "monitor1", type: "seqera-workflow-monitor", name: "Test Monitor", seqera: "config-node-1", workflowId: "workflowId", workflowIdType: "msg", poll: 5, pollType: "num", keepPolling: false, wires: [["helper1"], ["helper2"], ["helper3"]], }, { id: "helper1", type: "helper" }, { id: "helper2", type: "helper" }, { id: "helper3", type: "helper" }, ]; nock(DEFAULT_BASE_URL) .get("/workflow/wf-123") .query(true) .reply(200, createWorkflowResponse({ id: "wf-123", status: "running" })); helper.load([configNode, workflowMonitorNode], flow, createCredentials(), function () { const monitorNode = helper.getNode("monitor1"); const helper1 = helper.getNode("helper1"); helper1.on("input", function (msg) { try { expect(msg.workflowId).to.equal("wf-123"); expect(msg.payload.workflow.status).to.equal("running"); done(); } catch (err) { done(err); } }); monitorNode.receive({ workflowId: "wf-123" }); }); }); it("should report error when workflowId not provided", function (done) { const flow = [ createConfigNode(), { id: "monitor1", type: "seqera-workflow-monitor", name: "Test Monitor", seqera: "config-node-1", workflowId: "workflowId", workflowIdType: "msg", keepPolling: false, wires: [[], [], []], }, ]; helper.load([configNode, workflowMonitorNode], flow, createCredentials(), function () { const monitorNode = helper.getNode("monitor1"); monitorNode.on("call:error", function (call) { try { expect(call.firstArg).to.include("workflowId not provided"); done(); } catch (err) { done(err); } }); monitorNode.receive({ payload: {} }); }); }); }); describe("output routing", function () { it("should send to output 1 (active) when status is running", function (done) { const flow = [ createConfigNode(), { id: "monitor1", type: "seqera-workflow-monitor", name: "Test Monitor", seqera: "config-node-1", workflowId: "workflowId", workflowIdType: "msg", keepPolling: false, wires: [["helper1"], ["helper2"], ["helper3"]], }, { id: "helper1", type: "helper" }, { id: "helper2", type: "helper" }, { id: "helper3", type: "helper" }, ]; nock(DEFAULT_BASE_URL) .get("/workflow/wf-123") .query(true) .reply(200, createWorkflowResponse({ status: "running" })); helper.load([configNode, workflowMonitorNode], flow, createCredentials(), function () { const monitorNode = helper.getNode("monitor1"); const helper1 = helper.getNode("helper1"); const helper2 = helper.getNode("helper2"); const helper3 = helper.getNode("helper3"); let receivedOnHelper1 = false; helper1.on("input", function (msg) { receivedOnHelper1 = true; }); helper2.on("input", function () { done(new Error("Should not receive on output 2 for running status")); }); helper3.on("input", function () { done(new Error("Should not receive on output 3 for running status")); }); monitorNode.receive({ workflowId: "wf-123" }); setTimeout(function () { try { expect(receivedOnHelper1).to.be.true; done(); } catch (err) { done(err); } }, 100); }); }); it("should send to output 1 (active) when status is submitted", function (done) { const flow = [ createConfigNode(), { id: "monitor1", type: "seqera-workflow-monitor", name: "Test Monitor", seqera: "config-node-1", workflowId: "workflowId", workflowIdType: "msg", keepPolling: false, wires: [["helper1"], ["helper2"], ["helper3"]], }, { id: "helper1", type: "helper" }, { id: "helper2", type: "helper" }, { id: "helper3", type: "helper" }, ]; nock(DEFAULT_BASE_URL) .get("/workflow/wf-123") .query(true) .reply(200, createWorkflowResponse({ status: "submitted" })); helper.load([configNode, workflowMonitorNode], flow, createCredentials(), function () { const monitorNode = helper.getNode("monitor1"); const helper1 = helper.getNode("helper1"); helper1.on("input", function (msg) { try { expect(msg.payload.workflow.status).to.equal("submitted"); done(); } catch (err) { done(err); } }); monitorNode.receive({ workflowId: "wf-123" }); }); }); it("should send to output 2 (succeeded) when status is succeeded", function (done) { const flow = [ createConfigNode(), { id: "monitor1", type: "seqera-workflow-monitor", name: "Test Monitor", seqera: "config-node-1", workflowId: "workflowId", workflowIdType: "msg", keepPolling: false, wires: [["helper1"], ["helper2"], ["helper3"]], }, { id: "helper1", type: "helper" }, { id: "helper2", type: "helper" }, { id: "helper3", type: "helper" }, ]; nock(DEFAULT_BASE_URL) .get("/workflow/wf-123") .query(true) .reply(200, createWorkflowResponse({ status: "succeeded" })); helper.load([configNode, workflowMonitorNode], flow, createCredentials(), function () { const monitorNode = helper.getNode("monitor1"); const helper2 = helper.getNode("helper2"); helper2.on("input", function (msg) { try { expect(msg.payload.workflow.status).to.equal("succeeded"); done(); } catch (err) { done(err); } }); monitorNode.receive({ workflowId: "wf-123" }); }); }); it("should send to output 3 (failed) when status is failed", function (done) { const flow = [ createConfigNode(), { id: "monitor1", type: "seqera-workflow-monitor", name: "Test Monitor", seqera: "config-node-1", workflowId: "workflowId", workflowIdType: "msg", keepPolling: false, wires: [["helper1"], ["helper2"], ["helper3"]], }, { id: "helper1", type: "helper" }, { id: "helper2", type: "helper" }, { id: "helper3", type: "helper" }, ]; nock(DEFAULT_BASE_URL) .get("/workflow/wf-123") .query(true) .reply(200, createWorkflowResponse({ status: "failed" })); helper.load([configNode, workflowMonitorNode], flow, createCredentials(), function () { const monitorNode = helper.getNode("monitor1"); const helper3 = helper.getNode("helper3"); helper3.on("input", function (msg) { try { expect(msg.payload.workflow.status).to.equal("failed"); done(); } catch (err) { done(err); } }); monitorNode.receive({ workflowId: "wf-123" }); }); }); it("should send to output 3 when status is cancelled", function (done) { const flow = [ createConfigNode(), { id: "monitor1", type: "seqera-workflow-monitor", name: "Test Monitor", seqera: "config-node-1", workflowId: "workflowId", workflowIdType: "msg", keepPolling: false, wires: [["helper1"], ["helper2"], ["helper3"]], }, { id: "helper1", type: "helper" }, { id: "helper2", type: "helper" }, { id: "helper3", type: "helper" }, ]; nock(DEFAULT_BASE_URL) .get("/workflow/wf-123") .query(true) .reply(200, createWorkflowResponse({ status: "cancelled" })); helper.load([configNode, workflowMonitorNode], flow, createCredentials(), function () { const monitorNode = helper.getNode("monitor1"); const helper3 = helper.getNode("helper3"); helper3.on("input", function (msg) { try { expect(msg.payload.workflow.status).to.equal("cancelled"); done(); } catch (err) { done(err); } }); monitorNode.receive({ workflowId: "wf-123" }); }); }); }); describe("polling behavior", function () { it("should stop polling when workflow reaches terminal state", function (done) { this.timeout(5000); const flow = [ createConfigNode(), { id: "monitor1", type: "seqera-workflow-monitor", name: "Test Monitor", seqera: "config-node-1", workflowId: "workflowId", workflowIdType: "msg", poll: 1, pollType: "num", keepPolling: true, wires: [["helper1"], ["helper2"], ["helper3"]], }, { id: "helper1", type: "helper" }, { id: "helper2", type: "helper" }, { id: "helper3", type: "helper" }, ]; let pollCount = 0; // First call: running, second call: succeeded nock(DEFAULT_BASE_URL) .get("/workflow/wf-123") .query(true) .reply(200, function () { pollCount++; if (pollCount === 1) { return createWorkflowResponse({ status: "running" }); } return createWorkflowResponse({ status: "succeeded" }); }) .persist(); helper.load([configNode, workflowMonitorNode], flow, createCredentials(), function () { const monitorNode = helper.getNode("monitor1"); const helper2 = helper.getNode("helper2"); helper2.on("input", function (msg) { // Once we receive on helper2 (succeeded), polling should stop setTimeout(function () { try { // pollCount should be 2 (running, then succeeded) // It shouldn't continue polling after succeeded expect(pollCount).to.equal(2); done(); } catch (err) { done(err); } }, 2000); // Wait to ensure no more polls happen }); monitorNode.receive({ workflowId: "wf-123" }); }); }); it("should stop polling when keepPolling is false", function (done) { const flow = [ createConfigNode(), { id: "monitor1", type: "seqera-workflow-monitor", name: "Test Monitor", seqera: "config-node-1", workflowId: "workflowId", workflowIdType: "msg", poll: 1, pollType: "num", keepPolling: false, wires: [["helper1"], [], []], }, { id: "helper1", type: "helper" }, ]; let pollCount = 0; nock(DEFAULT_BASE_URL) .get("/workflow/wf-123") .query(true) .reply(200, function () { pollCount++; return createWorkflowResponse({ status: "running" }); }) .persist(); helper.load([configNode, workflowMonitorNode], flow, createCredentials(), function () { const monitorNode = helper.getNode("monitor1"); const helper1 = helper.getNode("helper1"); helper1.on("input", function (msg) { // Should only poll once since keepPolling is false setTimeout(function () { try { expect(pollCount).to.equal(1); done(); } catch (err) { done(err); } }, 1500); }); monitorNode.receive({ workflowId: "wf-123" }); }); }); }); describe("message passthrough", function () { it("should preserve custom message properties", function (done) { const flow = [ createConfigNode(), { id: "monitor1", type: "seqera-workflow-monitor", name: "Test Monitor", seqera: "config-node-1", workflowId: "workflowId", workflowIdType: "msg", keepPolling: false, wires: [["helper1"], [], []], }, { id: "helper1", type: "helper" }, ]; nock(DEFAULT_BASE_URL) .get("/workflow/wf-123") .query(true) .reply(200, createWorkflowResponse({ status: "running" })); helper.load([configNode, workflowMonitorNode], flow, createCredentials(), function () { const monitorNode = helper.getNode("monitor1"); const helper1 = helper.getNode("helper1"); helper1.on("input", function (msg) { try { expect(msg._context).to.equal("test-context"); expect(msg.correlationId).to.equal("corr-456"); expect(msg.workflowId).to.exist; done(); } catch (err) { done(err); } }); monitorNode.receive({ workflowId: "wf-123", _context: "test-context", correlationId: "corr-456", }); }); }); }); describe("error handling", function () { it("should set error status and stop polling on API error", function (done) { const flow = [ createConfigNode(), { id: "monitor1", type: "seqera-workflow-monitor", name: "Test Monitor", seqera: "config-node-1", workflowId: "workflowId", workflowIdType: "msg", keepPolling: true, wires: [[], [], []], }, ]; nock(DEFAULT_BASE_URL).get("/workflow/wf-123").query(true).reply(500, { error: "Server error" }); helper.load([configNode, workflowMonitorNode], flow, createCredentials(), function () { const monitorNode = helper.getNode("monitor1"); monitorNode.on("call:error", function (call) { try { expect(call.firstArg).to.include("Seqera API request failed"); done(); } catch (err) { done(err); } }); monitorNode.receive({ workflowId: "wf-123" }); }); }); }); describe("cleanup on close", function () { it("should clear interval when node is closed", function (done) { const flow = [ createConfigNode(), { id: "monitor1", type: "seqera-workflow-monitor", name: "Test Monitor", seqera: "config-node-1", workflowId: "workflowId", workflowIdType: "msg", poll: 1, pollType: "num", keepPolling: true, wires: [["helper1"], [], []], }, { id: "helper1", type: "helper" }, ]; let pollCount = 0; nock(DEFAULT_BASE_URL) .get("/workflow/wf-123") .query(true) .reply(200, function () { pollCount++; return createWorkflowResponse({ status: "running" }); }) .persist(); helper.load([configNode, workflowMonitorNode], flow, createCredentials(), function () { const monitorNode = helper.getNode("monitor1"); const helper1 = helper.getNode("helper1"); helper1.on("input", function (msg) { // After first message, close the node monitorNode.close(); // Wait and verify no more polls happened setTimeout(function () { try { expect(pollCount).to.equal(1); done(); } catch (err) { done(err); } }, 2000); }); monitorNode.receive({ workflowId: "wf-123" }); }); }); }); });