diff --git a/packages/nodes-base/nodes/SpreadsheetFile/test/SpreadsheetFile.test.ts b/packages/nodes-base/nodes/SpreadsheetFile/test/SpreadsheetFile.test.ts new file mode 100644 index 0000000000..9fae97cd52 --- /dev/null +++ b/packages/nodes-base/nodes/SpreadsheetFile/test/SpreadsheetFile.test.ts @@ -0,0 +1,171 @@ +import * as Helpers from '../../../test/nodes/Helpers'; +import type { WorkflowTestData } from '../../../test/nodes/types'; + +import { executeWorkflow } from '../../../test/nodes/ExecuteWorkflow'; +import path from 'path'; + +describe('Execute Spreadsheet File Node', () => { + beforeEach(async () => { + await Helpers.initBinaryDataManager(); + }); + + // replace workflow json 'Read Binary File' node's filePath to local file + const workflow = Helpers.readJsonFileSync('nodes/SpreadsheetFile/test/workflow.json'); + const node = workflow.nodes.find((n: any) => n.name === 'Read Binary File'); + node.parameters.filePath = path.join(__dirname, 'spreadsheet.csv'); + + const tests: WorkflowTestData[] = [ + { + description: 'execute workflow.json', + input: { + workflowData: workflow, + }, + output: { + nodeData: { + 'Read From File': [ + [ + { + json: { A: 1, B: 2, C: 3 }, + }, + { + json: { A: 4, B: 5, C: 6 }, + }, + ], + ], + 'Read From File Range': [ + [ + { + json: { '1': 4, '2': 5 }, + }, + ], + ], + 'Read From File no Header Row': [ + [ + { + json: { + row: ['A', 'B', 'C'], + }, + }, + { + json: { + row: [1, 2, 3], + }, + }, + { + json: { + row: [4, 5, 6], + }, + }, + ], + ], + 'Read From File Raw Data': [ + [ + { + json: { A: '1', B: '2', C: '3' }, + }, + { + json: { A: '4', B: '5', C: '6' }, + }, + ], + ], + 'Read From File Read as String': [ + [ + { + json: { A: 1, B: 2, C: 3 }, + }, + { + json: { A: 4, B: 5, C: 6 }, + }, + ], + ], + 'Write To File CSV': [ + [ + { + json: {}, + binary: { + data: { + mimeType: 'text/csv', + fileType: 'text', + fileExtension: 'csv', + data: '77u/QSxCLEMKMSwyLDMKNCw1LDYK', + fileName: 'spreadsheet.csv', + fileSize: '21 B', + }, + }, + }, + ], + ], + 'Write To File HTML': [ + [ + { + json: {}, + binary: { + data: { + mimeType: 'text/html', + fileType: 'text', + fileExtension: 'html', + data: 'PGh0bWw+PGhlYWQ+PG1ldGEgY2hhcnNldD0idXRmLTgiLz48dGl0bGU+U2hlZXRKUyBUYWJsZSBFeHBvcnQ8L3RpdGxlPjwvaGVhZD48Ym9keT48dGFibGU+PHRyPjx0ZCBkYXRhLXQ9InMiIGRhdGEtdj0iQSIgaWQ9InNqcy1BMSI+QTwvdGQ+PHRkIGRhdGEtdD0icyIgZGF0YS12PSJCIiBpZD0ic2pzLUIxIj5CPC90ZD48dGQgZGF0YS10PSJzIiBkYXRhLXY9IkMiIGlkPSJzanMtQzEiPkM8L3RkPjwvdHI+PHRyPjx0ZCBkYXRhLXQ9Im4iIGRhdGEtdj0iMSIgaWQ9InNqcy1BMiI+MTwvdGQ+PHRkIGRhdGEtdD0ibiIgZGF0YS12PSIyIiBpZD0ic2pzLUIyIj4yPC90ZD48dGQgZGF0YS10PSJuIiBkYXRhLXY9IjMiIGlkPSJzanMtQzIiPjM8L3RkPjwvdHI+PHRyPjx0ZCBkYXRhLXQ9Im4iIGRhdGEtdj0iNCIgaWQ9InNqcy1BMyI+NDwvdGQ+PHRkIGRhdGEtdD0ibiIgZGF0YS12PSI1IiBpZD0ic2pzLUIzIj41PC90ZD48dGQgZGF0YS10PSJuIiBkYXRhLXY9IjYiIGlkPSJzanMtQzMiPjY8L3RkPjwvdHI+PC90YWJsZT48L2JvZHk+PC9odG1sPg==', + fileName: 'spreadsheet.html', + fileSize: '535 B', + }, + }, + }, + ], + ], + // ODS file has slight differences every time it's created + // + 'Write To File RTF': [ + [ + { + json: {}, + binary: { + data: { + mimeType: 'application/rtf', + fileExtension: 'rtf', + data: 'e1xydGYxXGFuc2lcdHJvd2RcdHJhdXRvZml0MVxjZWxseDFcY2VsbHgyXGNlbGx4M1xwYXJkXGludGJsIEFcY2VsbCBCXGNlbGwgQ1xjZWxsXHBhcmRcaW50Ymxccm93XHRyb3dkXHRyYXV0b2ZpdDFcY2VsbHgxXGNlbGx4MlxjZWxseDNccGFyZFxpbnRibCAxXGNlbGwgMlxjZWxsIDNcY2VsbFxwYXJkXGludGJsXHJvd1x0cm93ZFx0cmF1dG9maXQxXGNlbGx4MVxjZWxseDJcY2VsbHgzXHBhcmRcaW50YmwgNFxjZWxsIDVcY2VsbCA2XGNlbGxccGFyZFxpbnRibFxyb3d9', + fileName: 'spreadsheet.rtf', + fileSize: '267 B', + }, + }, + }, + ], + ], + 'Write To File XLS': [ + [ + { + json: {}, + binary: { + data: { + mimeType: 'application/vnd.ms-excel', + fileExtension: 'xls', + data: '0M8R4KGxGuEAAAAAAAAAAAAAAAAAAAAAPgADAP7/CQAGAAAAAAAAAAAAAAABAAAAAgAAAAAAAAAAEAAAAQAAAAEAAAD+////AAAAAAAAAAD////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9/////v////7///8EAAAABQAAAP7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7///8CAAAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACQAAAAoAAAALAAAADAAAAA0AAAAOAAAADwAAABAAAAARAAAAEgAAABMAAAD+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7////+////UgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQABQH//////////wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAUAAAAAAAABAFMAaAAzADMAdABKADUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEgACAf////8CAAAA/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAFcAbwByAGsA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', + fileName: 'spreadsheet.xls', + fileSize: '3.58 kB', + }, + }, + }, + ], + ], + }, + }, + }, + ]; + + const nodeTypes = Helpers.setup(tests); + + for (const testData of tests) { + // eslint-disable-next-line @typescript-eslint/no-loop-func + test(testData.description, async () => { + // execute workflow + const { result } = await executeWorkflow(testData, nodeTypes); + + // check if result node data matches expected test data + const resultNodeData = Helpers.getResultNodeData(result, testData); + resultNodeData.forEach(({ nodeName, resultData }) => + expect(resultData).toEqual(testData.output.nodeData[nodeName]), + ); + + expect(result.finished).toEqual(true); + }); + } +}); diff --git a/packages/nodes-base/nodes/SpreadsheetFile/test/spreadsheet.csv b/packages/nodes-base/nodes/SpreadsheetFile/test/spreadsheet.csv new file mode 100644 index 0000000000..803fa76475 --- /dev/null +++ b/packages/nodes-base/nodes/SpreadsheetFile/test/spreadsheet.csv @@ -0,0 +1,3 @@ +A,B,C +1,2,3 +4,5,6 \ No newline at end of file diff --git a/packages/nodes-base/nodes/SpreadsheetFile/test/workflow.json b/packages/nodes-base/nodes/SpreadsheetFile/test/workflow.json new file mode 100644 index 0000000000..b52c7918bd --- /dev/null +++ b/packages/nodes-base/nodes/SpreadsheetFile/test/workflow.json @@ -0,0 +1,253 @@ +{ + "meta": { + "instanceId": "104a4d08d8897b8bdeb38aaca515021075e0bd8544c983c2bb8c86e6a8e6081c" + }, + "nodes": [ + { + "parameters": {}, + "id": "087277cc-297d-4912-bd11-86626eff2d71", + "name": "When clicking \"Execute Workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 620, + 640 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "f55bc21c-c9a8-43af-bbc8-e4bdd30f0ce9", + "name": "Read From File", + "type": "n8n-nodes-base.spreadsheetFile", + "typeVersion": 1, + "position": [ + 1260, + 640 + ] + }, + { + "parameters": { + "filePath": "C:\\Users\\spech\\Documents\\GitHub\\n8n-master\\packages\\nodes-base\\nodes\\SpreadsheetFile\\test\\spreadsheet.csv" + }, + "id": "d7620053-eb3d-43dd-b2cd-d60d9a08a9cc", + "name": "Read Binary File", + "type": "n8n-nodes-base.readBinaryFile", + "typeVersion": 1, + "position": [ + 840, + 640 + ] + }, + { + "parameters": { + "operation": "toFile", + "fileFormat": "csv", + "options": {} + }, + "id": "21bc49fe-1e6b-46d6-a04d-cb474d138e02", + "name": "Write To File CSV", + "type": "n8n-nodes-base.spreadsheetFile", + "typeVersion": 1, + "position": [ + 1580, + 280 + ] + }, + { + "parameters": { + "operation": "toFile", + "fileFormat": "html", + "options": {} + }, + "id": "a4c2c717-5a9d-4fd6-8450-6bb78e233c05", + "name": "Write To File HTML", + "type": "n8n-nodes-base.spreadsheetFile", + "typeVersion": 1, + "position": [ + 1580, + 460 + ] + }, + { + "parameters": { + "operation": "toFile", + "fileFormat": "ods", + "options": {} + }, + "id": "58e5a423-0477-44df-a505-6b8a40dbf275", + "name": "Write To File ODS", + "type": "n8n-nodes-base.spreadsheetFile", + "typeVersion": 1, + "position": [ + 1580, + 640 + ] + }, + { + "parameters": { + "operation": "toFile", + "fileFormat": "rtf", + "options": {} + }, + "id": "3ae6e9c5-bc0a-44e4-959e-cfc572f4179f", + "name": "Write To File RTF", + "type": "n8n-nodes-base.spreadsheetFile", + "typeVersion": 1, + "position": [ + 1580, + 820 + ] + }, + { + "parameters": { + "operation": "toFile", + "options": {} + }, + "id": "7e6db847-d24c-4094-907d-92ffec626f68", + "name": "Write To File XLS", + "type": "n8n-nodes-base.spreadsheetFile", + "typeVersion": 1, + "position": [ + 1580, + 1020 + ] + }, + { + "parameters": { + "options": { + "range": "A2:B3" + } + }, + "id": "48934f0d-ac10-4862-ae0c-2ea591b111e3", + "name": "Read From File Range", + "type": "n8n-nodes-base.spreadsheetFile", + "typeVersion": 1, + "position": [ + 1060, + 520 + ] + }, + { + "parameters": { + "options": { + "headerRow": false + } + }, + "id": "dea6f3f6-f2fb-472e-97b2-8a0c0a36bc4d", + "name": "Read From File no Header Row", + "type": "n8n-nodes-base.spreadsheetFile", + "typeVersion": 1, + "position": [ + 1060, + 320 + ] + }, + { + "parameters": { + "options": { + "rawData": true + } + }, + "id": "38ed33fc-6906-4b09-8376-937ef3ca99be", + "name": "Read From File Raw Data", + "type": "n8n-nodes-base.spreadsheetFile", + "typeVersion": 1, + "position": [ + 1060, + 740 + ] + }, + { + "parameters": { + "options": { + "readAsString": true + } + }, + "id": "ffe09dc8-9b7a-4baf-bd03-bd65bbce2590", + "name": "Read From File Read as String", + "type": "n8n-nodes-base.spreadsheetFile", + "typeVersion": 1, + "position": [ + 1060, + 940 + ] + } + ], + "connections": { + "When clicking \"Execute Workflow\"": { + "main": [ + [ + { + "node": "Read Binary File", + "type": "main", + "index": 0 + } + ] + ] + }, + "Read From File": { + "main": [ + [ + { + "node": "Write To File CSV", + "type": "main", + "index": 0 + }, + { + "node": "Write To File HTML", + "type": "main", + "index": 0 + }, + { + "node": "Write To File ODS", + "type": "main", + "index": 0 + }, + { + "node": "Write To File RTF", + "type": "main", + "index": 0 + }, + { + "node": "Write To File XLS", + "type": "main", + "index": 0 + } + ] + ] + }, + "Read Binary File": { + "main": [ + [ + { + "node": "Read From File", + "type": "main", + "index": 0 + }, + { + "node": "Read From File Range", + "type": "main", + "index": 0 + }, + { + "node": "Read From File no Header Row", + "type": "main", + "index": 0 + }, + { + "node": "Read From File Raw Data", + "type": "main", + "index": 0 + }, + { + "node": "Read From File Read as String", + "type": "main", + "index": 0 + } + ] + ] + } + } +} \ No newline at end of file diff --git a/packages/nodes-base/test/nodes/Airtable/Airtable.node.test.ts b/packages/nodes-base/test/nodes/Airtable/Airtable.node.test.ts index 706ac41d95..02f91ebdd7 100644 --- a/packages/nodes-base/test/nodes/Airtable/Airtable.node.test.ts +++ b/packages/nodes-base/test/nodes/Airtable/Airtable.node.test.ts @@ -34,7 +34,7 @@ describe('Execute Airtable Node', () => { }, output: { nodeData: { - Airtable: [[...records]], + Airtable: [[...records.map((r) => ({ json: r }))]], }, }, }, diff --git a/packages/nodes-base/test/nodes/Helpers.ts b/packages/nodes-base/test/nodes/Helpers.ts index 5b9bf78069..721cd52614 100644 --- a/packages/nodes-base/test/nodes/Helpers.ts +++ b/packages/nodes-base/test/nodes/Helpers.ts @@ -1,5 +1,5 @@ -import { readFileSync, readdirSync } from 'fs'; -import { Credentials, loadClassInIsolation } from 'n8n-core'; +import { readFileSync, readdirSync, mkdtempSync } from 'fs'; +import { BinaryDataManager, Credentials, loadClassInIsolation } from 'n8n-core'; import { ICredentialDataDecryptedObject, ICredentialsHelper, @@ -27,6 +27,8 @@ import { import { executeWorkflow } from './ExecuteWorkflow'; import { WorkflowTestData } from './types'; import path from 'path'; +import { tmpdir } from 'os'; +import { isEmpty } from 'lodash'; export class CredentialsHelper extends ICredentialsHelper { async authenticate( @@ -156,6 +158,17 @@ const loadKnownNodes = (): Record => { return knownNodes!; }; +export async function initBinaryDataManager(mode: 'default' | 'filesystem' = 'default') { + const temporaryDir = mkdtempSync(path.join(tmpdir(), 'n8n')); + await BinaryDataManager.init({ + mode, + availableModes: mode, + localStoragePath: temporaryDir, + binaryDataTTL: 1, + persistedBinaryDataTTL: 1, + }); +} + export function setup(testData: Array | WorkflowTestData) { if (!Array.isArray(testData)) { testData = [testData]; @@ -204,7 +217,11 @@ export function getResultNodeData(result: IRun, testData: WorkflowTestData) { if (nodeData.data === undefined) { return null; } - return nodeData.data.main[0]!.map((entry) => entry.json); + return nodeData.data.main[0]!.map((entry) => { + if (entry.binary && isEmpty(entry.binary)) delete entry.binary; + delete entry.pairedItem; + return entry; + }); }); return { nodeName, @@ -241,7 +258,7 @@ export const workflowToTests = (workflowFiles: string[]) => { } const nodeData = Object.keys(workflowData.pinData).reduce( (acc, key) => { - const data = (workflowData.pinData[key] as IDataObject[]).map((item) => item.json); + const data = workflowData.pinData[key] as IDataObject[]; acc[key] = [data as IDataObject[]]; return acc; },