UNPKG

node-red-contrib-testmonitor

Version:

A comprehensive Node-RED wrapper for TestMonitor API providing test case management, test runs, milestones, and test result operations for test automation workflows

442 lines (392 loc) 16.3 kB
module.exports = function (RED) { "use strict"; const NodeCache = require('node-cache'); function TestMonitorTestRunNode(config) { RED.nodes.createNode(this, config); const node = this; // Configuration node.operation = config.operation || "get"; node.testRunId = config.testRunId || ""; node.milestoneId = config.milestoneId || ""; node.name = config.name || ""; node.field = config.field || "payload"; node.fieldType = config.fieldType || "msg"; node.enableCaching = config.enableCaching !== false; node.cacheDuration = parseInt(config.cacheDuration) || 300; // 5 minutes default node.timeout = parseInt(config.timeout) || 30000; node.credentials = RED.nodes.getNode(config.credentials); // Initialize cache const cache = node.enableCaching ? new NodeCache({ stdTTL: node.cacheDuration, checkperiod: 60 }) : null; // Status tracking function updateStatus(text, color = "grey") { node.status({ fill: color, shape: "dot", text: text }); } // Generate cache key function generateCacheKey(operation, testRunId, additionalParams = {}) { const keyData = { operation, testRunId, ...additionalParams }; return `testrun_${JSON.stringify(keyData)}`; } // Validate required credentials if (!node.credentials) { updateStatus("Missing credentials", "red"); node.error("TestMonitor credentials configuration is required"); return; } // TestRun operations const testRunOperations = { get: async (params) => { if (!params.testRunId) { throw new Error("Test Run ID is required for get operation"); } const endpoint = `/test-runs/${params.testRunId}`; return new Promise((resolve, reject) => { node.credentials.get(endpoint, {}, (error, result) => { if (error) { reject(error); } else { resolve(result.data); } }); }); }, list: async (params) => { const queryParams = { project_id: node.credentials.projectId }; if (params.milestoneId) { queryParams['milestone'] = params.milestoneId; } const endpoint = '/test-runs'; return new Promise((resolve, reject) => { node.credentials.get(endpoint, queryParams, (error, result) => { if (error) { reject(error); } else { resolve(result.data); } }); }); }, create: async (params) => { if (!params.milestoneId) { throw new Error("Milestone ID is required for create operation"); } if (!params.name) { throw new Error("Name is required for create operation"); } const testRunData = { milestone_id: params.milestoneId, name: params.name, draft: params.draft !== undefined ? params.draft : true, priority: params.priority, starts_at: params.starts_at, ends_at: params.ends_at, users: params.users || [], test_cases: params.test_cases || [], tags: params.tags || [], subscribers: params.subscribers || [], test_environment_id: params.test_environment_id, custom_fields: params.custom_fields || [] }; // Remove null/undefined values Object.keys(testRunData).forEach(key => { if (testRunData[key] === null || testRunData[key] === undefined) { delete testRunData[key]; } }); const endpoint = '/test-runs'; return new Promise((resolve, reject) => { node.credentials.post(endpoint, testRunData, (error, result) => { if (error) { reject(error); } else { resolve(result.data); } }); }); }, update: async (params) => { if (!params.testRunId) { throw new Error("Test Run ID is required for update operation"); } const updateData = {}; // Only include fields that are provided if (params.name !== undefined) updateData.name = params.name; if (params.milestoneId !== undefined) updateData.milestone_id = params.milestoneId; if (params.draft !== undefined) updateData.draft = params.draft; if (params.priority !== undefined) updateData.priority = params.priority; if (params.starts_at !== undefined) updateData.starts_at = params.starts_at; if (params.ends_at !== undefined) updateData.ends_at = params.ends_at; if (params.users !== undefined) updateData.users = params.users; if (params.test_cases !== undefined) updateData.test_cases = params.test_cases; if (params.tags !== undefined) updateData.tags = params.tags; if (params.subscribers !== undefined) updateData.subscribers = params.subscribers; if (params.test_environment_id !== undefined) updateData.test_environment_id = params.test_environment_id; if (params.custom_fields !== undefined) updateData.custom_fields = params.custom_fields; const endpoint = `/test-runs/${params.testRunId}`; return new Promise((resolve, reject) => { node.credentials.put(endpoint, updateData, (error, result) => { if (error) { reject(error); } else { resolve(result.data); } }); }); }, delete: async (params) => { if (!params.testRunId) { throw new Error("Test Run ID is required for delete operation"); } const endpoint = `/test-runs/${params.testRunId}`; return new Promise((resolve, reject) => { node.credentials.delete(endpoint, (error, result) => { if (error) { reject(error); } else { resolve({ success: true, message: `Test run ${params.testRunId} deleted successfully` }); } }); }); }, getTestCases: async (params) => { if (!params.testRunId) { throw new Error("Test Run ID is required for getTestCases operation"); } const endpoint = `/test-run/${params.testRunId}/test-cases`; const queryParams = { project_id: node.credentials.projectId, 'filter[has_test_cases]': true }; return new Promise((resolve, reject) => { node.credentials.get(endpoint, queryParams, (error, result) => { if (error) { reject(error); } else { resolve(result.data); } }); }); }, getTestResults: async (params) => { if (!params.testRunId) { throw new Error("Test Run ID is required for getTestResults operation"); } const endpoint = `/test-run/${params.testRunId}/test-results`; const queryParams = { project_id: node.credentials.projectId }; return new Promise((resolve, reject) => { node.credentials.get(endpoint, queryParams, (error, result) => { if (error) { reject(error); } else { resolve(result.data); } }); }); }, addTestCases: async (params) => { if (!params.testRunId) { throw new Error("Test Run ID is required for addTestCases operation"); } if (!params.test_case_ids || !Array.isArray(params.test_case_ids)) { throw new Error("test_case_ids array is required for addTestCases operation"); } const endpoint = `/test-runs/${params.testRunId}`; const updateData = { test_case_ids: params.test_case_ids }; return new Promise((resolve, reject) => { node.credentials.put(endpoint, updateData, (error, result) => { if (error) { reject(error); } else { resolve(result.data); } }); }); }, openTestRun: async (params) => { if (!params.testRunId) { throw new Error("Test Run ID is required for openTestRun operation"); } const endpoint = `/test-runs/${params.testRunId}`; const updateData = { draft: false }; return new Promise((resolve, reject) => { node.credentials.put(endpoint, updateData, (error, result) => { if (error) { reject(error); } else { resolve(result.data); } }); }); }, closeTestRun: async (params) => { if (!params.testRunId) { throw new Error("Test Run ID is required for closeTestRun operation"); } const endpoint = `/test-runs/${params.testRunId}`; const updateData = { draft: true }; return new Promise((resolve, reject) => { node.credentials.put(endpoint, updateData, (error, result) => { if (error) { reject(error); } else { resolve(result.data); } }); }); } }; // Main message handler node.on('input', async function (msg, send, done) { send = send || function () { node.send.apply(node, arguments); }; try { updateStatus("Processing...", "blue"); // Extract parameters from message or node configuration const inputData = RED.util.getMessageProperty(msg, node.field); const params = { testRunId: inputData?.testRunId || msg.testRunId || node.testRunId, operation: inputData?.operation || msg.operation || node.operation, milestoneId: inputData?.milestoneId || msg.milestoneId || node.milestoneId, name: inputData?.name || msg.name || node.name, draft: inputData?.draft !== undefined ? inputData.draft : msg.draft, priority: inputData?.priority !== undefined ? inputData.priority : msg.priority, starts_at: inputData?.starts_at || msg.starts_at, ends_at: inputData?.ends_at || msg.ends_at, users: inputData?.users || msg.users, test_cases: inputData?.test_cases || msg.test_cases, test_case_ids: inputData?.test_case_ids || msg.test_case_ids, tags: inputData?.tags || msg.tags, subscribers: inputData?.subscribers || msg.subscribers, test_environment_id: inputData?.test_environment_id || msg.test_environment_id, custom_fields: inputData?.custom_fields || msg.custom_fields }; // Check cache for read operations const cacheKey = generateCacheKey(params.operation, params.testRunId, params); if (cache && ['get', 'list', 'getTestCases', 'getTestResults'].includes(params.operation)) { const cachedResult = cache.get(cacheKey); if (cachedResult) { updateStatus("From cache", "green"); msg.payload = cachedResult; send(msg); done(); return; } } // Validate operation if (!testRunOperations[params.operation]) { throw new Error(`Unknown operation: ${params.operation}`); } // Execute operation const result = await testRunOperations[params.operation](params); // Cache read operations if (cache && ['get', 'list', 'getTestCases', 'getTestResults'].includes(params.operation)) { cache.set(cacheKey, result); } // Set result in message msg.payload = result; msg.testMonitor = { operation: params.operation, testRunId: params.testRunId, milestoneId: params.milestoneId, timestamp: new Date().toISOString() }; updateStatus(`${params.operation} completed`, "green"); send(msg); done(); } catch (error) { updateStatus(`Error: ${error.message}`, "red"); node.error(error.message, msg); done(error); } }); updateStatus("Ready"); } RED.nodes.registerType("testmonitor-testrun", TestMonitorTestRunNode); };