2023-04-12 05:58:05 -07:00
import nock from 'nock' ;
2022-09-11 07:42:09 -07:00
import { join } from 'path' ;
import { tmpdir } from 'os' ;
import { readFileSync , mkdtempSync } from 'fs' ;
2023-04-12 05:58:05 -07:00
import { mock } from 'jest-mock-extended' ;
import type {
IBinaryData ,
INode ,
ITaskDataConnections ,
IWorkflowExecuteAdditionalData ,
Workflow ,
WorkflowHooks ,
} from 'n8n-workflow' ;
2022-11-09 06:25:00 -08:00
import { BinaryDataManager } from '@/BinaryDataManager' ;
2023-04-12 05:58:05 -07:00
import {
setBinaryDataBuffer ,
getBinaryDataBuffer ,
proxyRequestToAxios ,
} from '@/NodeExecuteFunctions' ;
2023-06-21 00:38:28 -07:00
import { initLogger } from './helpers/utils' ;
2022-09-11 07:42:09 -07:00
const temporaryDir = mkdtempSync ( join ( tmpdir ( ) , 'n8n' ) ) ;
describe ( 'NodeExecuteFunctions' , ( ) = > {
2023-04-12 05:58:05 -07:00
describe ( 'test binary data helper methods' , ( ) = > {
2022-09-11 07:42:09 -07:00
// Reset BinaryDataManager for each run. This is a dirty operation, as individual managers are not cleaned.
beforeEach ( ( ) = > {
BinaryDataManager . instance = undefined ;
} ) ;
2023-05-02 01:37:19 -07:00
test ( "test getBinaryDataBuffer(...) & setBinaryDataBuffer(...) methods in 'default' mode" , async ( ) = > {
2022-09-11 07:42:09 -07:00
// Setup a 'default' binary data manager instance
await BinaryDataManager . init ( {
mode : 'default' ,
availableModes : 'default' ,
localStoragePath : temporaryDir ,
binaryDataTTL : 1 ,
persistedBinaryDataTTL : 1 ,
} ) ;
// Set our binary data buffer
2023-05-02 01:37:19 -07:00
const inputData : Buffer = Buffer . from ( 'This is some binary data' , 'utf8' ) ;
const setBinaryDataBufferResponse : IBinaryData = await setBinaryDataBuffer (
2022-09-11 07:42:09 -07:00
{
mimeType : 'txt' ,
data : 'This should be overwritten by the actual payload in the response' ,
} ,
inputData ,
'executionId' ,
) ;
// Expect our return object to contain the base64 encoding of the input data, as it should be stored in memory.
expect ( setBinaryDataBufferResponse . data ) . toEqual ( inputData . toString ( 'base64' ) ) ;
// Now, re-fetch our data.
// An ITaskDataConnections object is used to share data between nodes. The top level property, 'main', represents the successful output object from a previous node.
2023-05-02 01:37:19 -07:00
const taskDataConnectionsInput : ITaskDataConnections = {
2022-09-11 07:42:09 -07:00
main : [ ] ,
} ;
// We add an input set, with one item at index 0, to this input. It contains an empty json payload and our binary data.
taskDataConnectionsInput . main . push ( [
{
json : { } ,
binary : {
data : setBinaryDataBufferResponse ,
} ,
} ,
] ) ;
// Now, lets fetch our data! The item will be item index 0.
2023-05-02 01:37:19 -07:00
const getBinaryDataBufferResponse : Buffer = await getBinaryDataBuffer (
2022-09-11 07:42:09 -07:00
taskDataConnectionsInput ,
0 ,
'data' ,
0 ,
) ;
expect ( getBinaryDataBufferResponse ) . toEqual ( inputData ) ;
} ) ;
2023-05-02 01:37:19 -07:00
test ( "test getBinaryDataBuffer(...) & setBinaryDataBuffer(...) methods in 'filesystem' mode" , async ( ) = > {
2022-09-11 07:42:09 -07:00
// Setup a 'filesystem' binary data manager instance
await BinaryDataManager . init ( {
mode : 'filesystem' ,
availableModes : 'filesystem' ,
localStoragePath : temporaryDir ,
binaryDataTTL : 1 ,
persistedBinaryDataTTL : 1 ,
} ) ;
// Set our binary data buffer
2023-05-02 01:37:19 -07:00
const inputData : Buffer = Buffer . from ( 'This is some binary data' , 'utf8' ) ;
const setBinaryDataBufferResponse : IBinaryData = await setBinaryDataBuffer (
2022-09-11 07:42:09 -07:00
{
mimeType : 'txt' ,
data : 'This should be overwritten with the name of the configured data manager' ,
} ,
inputData ,
'executionId' ,
) ;
// Expect our return object to contain the name of the configured data manager.
expect ( setBinaryDataBufferResponse . data ) . toEqual ( 'filesystem' ) ;
// Ensure that the input data was successfully persisted to disk.
expect (
readFileSync (
` ${ temporaryDir } / ${ setBinaryDataBufferResponse . id ? . replace ( 'filesystem:' , '' ) } ` ,
) ,
) . toEqual ( inputData ) ;
// Now, re-fetch our data.
// An ITaskDataConnections object is used to share data between nodes. The top level property, 'main', represents the successful output object from a previous node.
2023-05-02 01:37:19 -07:00
const taskDataConnectionsInput : ITaskDataConnections = {
2022-09-11 07:42:09 -07:00
main : [ ] ,
} ;
// We add an input set, with one item at index 0, to this input. It contains an empty json payload and our binary data.
taskDataConnectionsInput . main . push ( [
{
json : { } ,
binary : {
data : setBinaryDataBufferResponse ,
} ,
} ,
] ) ;
// Now, lets fetch our data! The item will be item index 0.
2023-05-02 01:37:19 -07:00
const getBinaryDataBufferResponse : Buffer = await getBinaryDataBuffer (
2022-09-11 07:42:09 -07:00
taskDataConnectionsInput ,
0 ,
'data' ,
0 ,
) ;
expect ( getBinaryDataBufferResponse ) . toEqual ( inputData ) ;
} ) ;
} ) ;
2023-04-12 05:58:05 -07:00
describe ( 'proxyRequestToAxios' , ( ) = > {
const baseUrl = 'http://example.de' ;
const workflow = mock < Workflow > ( ) ;
const hooks = mock < WorkflowHooks > ( ) ;
const additionalData = mock < IWorkflowExecuteAdditionalData > ( { hooks } ) ;
const node = mock < INode > ( ) ;
beforeEach ( ( ) = > {
initLogger ( ) ;
hooks . executeHookFunctions . mockClear ( ) ;
} ) ;
test ( 'should not throw if the response status is 200' , async ( ) = > {
nock ( baseUrl ) . get ( '/test' ) . reply ( 200 ) ;
await proxyRequestToAxios ( workflow , additionalData , node , ` ${ baseUrl } /test ` ) ;
expect ( hooks . executeHookFunctions ) . toHaveBeenCalledWith ( 'nodeFetchedData' , [
workflow . id ,
node ,
] ) ;
} ) ;
test ( 'should throw if the response status is 403' , async ( ) = > {
const headers = { 'content-type' : 'text/plain' } ;
nock ( baseUrl ) . get ( '/test' ) . reply ( 403 , 'Forbidden' , headers ) ;
try {
await proxyRequestToAxios ( workflow , additionalData , node , ` ${ baseUrl } /test ` ) ;
} catch ( error ) {
expect ( error . statusCode ) . toEqual ( 403 ) ;
expect ( error . request ) . toBeUndefined ( ) ;
expect ( error . response ) . toMatchObject ( { headers , status : 403 } ) ;
expect ( error . options ) . toMatchObject ( {
headers : { Accept : '*/*' } ,
method : 'get' ,
url : 'http://example.de/test' ,
} ) ;
expect ( error . config ) . toBeUndefined ( ) ;
expect ( error . message ) . toEqual ( '403 - "Forbidden"' ) ;
}
expect ( hooks . executeHookFunctions ) . not . toHaveBeenCalled ( ) ;
} ) ;
test ( 'should not throw if the response status is 404, but `simple` option is set to `false`' , async ( ) = > {
nock ( baseUrl ) . get ( '/test' ) . reply ( 404 , 'Not Found' ) ;
const response = await proxyRequestToAxios ( workflow , additionalData , node , {
url : ` ${ baseUrl } /test ` ,
simple : false ,
} ) ;
expect ( response ) . toEqual ( 'Not Found' ) ;
expect ( hooks . executeHookFunctions ) . toHaveBeenCalledWith ( 'nodeFetchedData' , [
workflow . id ,
node ,
] ) ;
} ) ;
test ( 'should return full response when `resolveWithFullResponse` is set to true' , async ( ) = > {
nock ( baseUrl ) . get ( '/test' ) . reply ( 404 , 'Not Found' ) ;
const response = await proxyRequestToAxios ( workflow , additionalData , node , {
url : ` ${ baseUrl } /test ` ,
resolveWithFullResponse : true ,
simple : false ,
} ) ;
expect ( response ) . toMatchObject ( {
body : 'Not Found' ,
headers : { } ,
statusCode : 404 ,
statusMessage : null ,
} ) ;
expect ( hooks . executeHookFunctions ) . toHaveBeenCalledWith ( 'nodeFetchedData' , [
workflow . id ,
node ,
] ) ;
} ) ;
} ) ;
2022-09-11 07:42:09 -07:00
} ) ;