UNPKG

n8n-nodes-pytenable

Version:

Un nodo de n8n para interactuar con la API de Tenable usando Pytenable en un sandbox de Docker.

156 lines (126 loc) 5.92 kB
import {jest, describe, it, expect, beforeEach} from '@jest/globals'; import type { IExecuteFunctions, INodeExecutionData, INodeType } from 'n8n-workflow'; import { PassThrough } from 'stream'; const mockAttach = jest.fn(); const mockStart = jest.fn(); const mockContainer = { attach: mockAttach, start: mockStart, }; const mockCreateContainer = jest.fn().mockResolvedValue(mockContainer); jest.mock('dockerode', () => { return jest.fn().mockImplementation(() => { return { createContainer: mockCreateContainer, }; }); }); // A helper to create the mock Docker stream header for multiplexed streams const createDockerStreamChunk = (channel: 1 | 2, data: string): Buffer => { const header = Buffer.alloc(8); header.writeUInt8(channel, 0); // 1 for stdout, 2 for stderr header.writeUInt32BE(data.length, 4); return Buffer.concat([header, Buffer.from(data)]); }; describe('Pytenable Node with Docker', () => { let executeFunctions: IExecuteFunctions; let Pytenable: new () => INodeType; beforeEach(async () => { const module = await import('./Pytenable.node.js'); Pytenable = module.Pytenable; jest.clearAllMocks(); mockAttach.mockClear(); mockStart.mockClear(); mockCreateContainer.mockClear(); jest.setTimeout(30000); executeFunctions = { getNode: jest.fn().mockReturnValue({ getContext: jest.fn().mockReturnValue({ executionId: 'test-execution', node: { name: 'Pytenable', type: 'Pytenable', id: 'test-node-id' }, }), }), getNodeParameter: jest.fn(), getCredentials: jest.fn().mockResolvedValue({}), getInputData: jest.fn(), helpers: { returnJsonArray: jest.fn(data => data as INodeExecutionData[]), constructExecutionMetaData: jest.fn((data, options) => data.map((item: Record<string, unknown>) => ({ json: item, pairedItem: options.pairedItem })) ), }, } as unknown as IExecuteFunctions; }); it('should execute for all items at once successfully', async () => { // Arrange (executeFunctions.getNodeParameter as jest.Mock) .mockReturnValueOnce('runOnceForAllItems') .mockReturnValueOnce('return [{"status": "ok"}]'); (executeFunctions.getInputData as jest.Mock).mockReturnValue([{ json: { id: 1 } }]); (executeFunctions.helpers.constructExecutionMetaData as jest.Mock).mockImplementation((data) => data.map((item: Record<string, unknown>) => ({json: item}))); const mockStream = new PassThrough(); (mockStream.end as jest.Mock) = jest.fn(); mockAttach.mockResolvedValue(mockStream); mockStart.mockImplementation((cb: (err: Error | null) => void) => cb(null)); // Act const executionPromise = new Pytenable().execute.call(executeFunctions); // Simulate container output and end the stream manually for the node's listener await new Promise(resolve => setImmediate(resolve)); // Wait for listeners to attach const output = JSON.stringify([{ status: 'ok' }]); mockStream.write(createDockerStreamChunk(1, output)); mockStream.emit('end'); const result = await executionPromise; // Assert expect(result[0]).toEqual([expect.objectContaining({ json: { status: 'ok' } })]); expect(mockCreateContainer).toHaveBeenCalledTimes(1); }); it('should execute for each item individually and pair results', async () => { // Arrange (executeFunctions.getNodeParameter as jest.Mock) .mockReturnValueOnce('runOnceForEachItem') .mockReturnValueOnce('return [{"id": _item["id"] * 2}]'); (executeFunctions.getInputData as jest.Mock).mockReturnValue([{ json: { id: 1 } }, { json: { id: 2 } }]); (executeFunctions.helpers.returnJsonArray as jest.Mock).mockImplementation(data => data); (executeFunctions.helpers.constructExecutionMetaData as jest.Mock).mockImplementation((data) => data.map((item: Record<string, unknown>) => ({ json: item }))); const mockStreams = [new PassThrough(), new PassThrough()]; mockStreams.forEach(s => { (s.end as jest.Mock) = jest.fn(); }); mockAttach .mockResolvedValueOnce(mockStreams[0]) .mockResolvedValueOnce(mockStreams[1]); mockStart.mockImplementation((cb: (err: Error | null) => void) => cb(null)); // Act const executionPromise = new Pytenable().execute.call(executeFunctions); // Simulate responses for each item await new Promise(resolve => setImmediate(resolve)); mockStreams[0].write(createDockerStreamChunk(1, JSON.stringify([{ id: 2 }]))); mockStreams[0].emit('end'); await new Promise(resolve => setImmediate(resolve)); mockStreams[1].write(createDockerStreamChunk(1, JSON.stringify([{ id: 4 }]))); mockStreams[1].emit('end'); const result = await executionPromise; // Assert const returnData = result[0]; expect(returnData.length).toBe(2); expect(returnData[0]).toEqual({ json: { id: 2 }, pairedItem: { item: 0 } }); expect(returnData[1]).toEqual({ json: { id: 4 }, pairedItem: { item: 1 } }); }); it('should handle Python script error from stderr', async () => { // Arrange (executeFunctions.getNodeParameter as jest.Mock) .mockReturnValueOnce('runOnceForAllItems') .mockReturnValueOnce('raise Exception("Boom!")'); (executeFunctions.getInputData as jest.Mock).mockReturnValue([{}]); const mockStream = new PassThrough(); (mockStream.end as jest.Mock) = jest.fn(); mockAttach.mockResolvedValue(mockStream); mockStart.mockImplementation((cb: (err: Error | null) => void) => cb(null)); // Act const executionPromise = new Pytenable().execute.call(executeFunctions); await new Promise(resolve => setImmediate(resolve)); const errorOutput = "Traceback... Exception: Boom!"; mockStream.write(createDockerStreamChunk(2, errorOutput)); mockStream.emit('end'); // Assert await expect(executionPromise).rejects.toThrow('Error en el contenedor de Python: Traceback... Exception: Boom!'); }); });