n8n-nodes-pytenable
Version:
Un nodo de n8n para interactuar con la API de Tenable usando Pytenable en un sandbox de Docker.
125 lines (124 loc) • 6.45 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const Pytenable_node_1 = require("./Pytenable.node");
const dockerode_1 = __importDefault(require("dockerode"));
const stream_1 = require("stream");
// Mock the dockerode library
jest.mock('dockerode');
// A helper to create the mock Docker stream header for multiplexed streams
const createDockerStreamChunk = (channel, data) => {
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;
beforeEach(() => {
jest.clearAllMocks();
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),
constructExecutionMetaData: jest.fn((data, options) => data.map((item) => ({ json: item, pairedItem: options.pairedItem }))),
},
};
});
it('should execute for all items at once successfully', async () => {
// Arrange
executeFunctions.getNodeParameter
.mockReturnValueOnce('runOnceForAllItems')
.mockReturnValueOnce('return [{"status": "ok"}]');
executeFunctions.getInputData.mockReturnValue([{ json: { id: 1 } }]);
executeFunctions.helpers.constructExecutionMetaData.mockImplementation((data, _) => data.map((item) => ({ json: item })));
const mockStream = new stream_1.PassThrough();
// Prevent the node's call to stream.end() from closing our mock stream
mockStream.end = jest.fn();
const mockContainer = {
attach: jest.fn().mockResolvedValue(mockStream),
start: jest.fn().mockImplementation(cb => cb(null)),
};
const mockDocker = { createContainer: jest.fn().mockResolvedValue(mockContainer) };
dockerode_1.default.mockReturnValue(mockDocker);
// Act
const executionPromise = new Pytenable_node_1.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(mockDocker.createContainer).toHaveBeenCalledTimes(1);
});
it('should execute for each item individually and pair results', async () => {
// Arrange
executeFunctions.getNodeParameter
.mockReturnValueOnce('runOnceForEachItem')
.mockReturnValueOnce('return [{"id": _item["id"] * 2}]');
executeFunctions.getInputData.mockReturnValue([{ json: { id: 1 } }, { json: { id: 2 } }]);
executeFunctions.helpers.returnJsonArray.mockImplementation(data => data);
executeFunctions.helpers.constructExecutionMetaData.mockImplementation((data, _) => data.map((item) => ({ json: item })));
const mockStreams = [new stream_1.PassThrough(), new stream_1.PassThrough()];
mockStreams.forEach(s => { s.end = jest.fn(); }); // Disable end on both streams
const mockContainer = {
attach: jest.fn()
.mockResolvedValueOnce(mockStreams[0])
.mockResolvedValueOnce(mockStreams[1]),
start: jest.fn().mockImplementation(cb => cb(null)),
};
const mockDocker = { createContainer: jest.fn().mockResolvedValue(mockContainer) };
dockerode_1.default.mockReturnValue(mockDocker);
// Act
const executionPromise = new Pytenable_node_1.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
.mockReturnValueOnce('runOnceForAllItems')
.mockReturnValueOnce('raise Exception("Boom!")');
executeFunctions.getInputData.mockReturnValue([{}]);
const mockStream = new stream_1.PassThrough();
mockStream.end = jest.fn();
const mockContainer = {
attach: jest.fn().mockResolvedValue(mockStream),
start: jest.fn().mockImplementation(cb => cb(null)),
};
const mockDocker = { createContainer: jest.fn().mockResolvedValue(mockContainer) };
dockerode_1.default.mockReturnValue(mockDocker);
// Act
const executionPromise = new Pytenable_node_1.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!');
});
});