first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled

This commit is contained in:
2026-03-17 16:22:57 +03:30
commit 3d5eaf9445
15349 changed files with 2847338 additions and 0 deletions
@@ -0,0 +1,55 @@
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
import type { WorkflowTestData } from 'n8n-workflow';
import os from 'node:os';
import path from 'path';
if (os.platform() !== 'win32') {
describe('Execute Compression Node', () => {
const testHarness = new NodeTestHarness();
const workflowData = testHarness.readWorkflowJSON('workflow.compression.json');
const node = workflowData.nodes.find((n) => n.name === 'Read Binary File')!;
node.parameters.filePath = path.join(__dirname, 'lorem.txt');
const tests: WorkflowTestData[] = [
{
description: 'nodes/Compression/test/node/workflow.compression.json',
input: {
workflowData,
},
output: {
assertBinaryData: true,
nodeData: {
Compression1: [
[
{
json: {},
binary: {
file_0: {
mimeType: 'text/plain',
fileType: 'text',
fileExtension: 'txt',
data: 'TG9yZW0gSXBzdW0KIk5lcXVlIHBvcnJvIHF1aXNxdWFtIGVzdCBxdWkgZG9sb3JlbSBpcHN1bSBxdWlhIGRvbG9yIHNpdCBhbWV0LCBjb25zZWN0ZXR1ciwgYWRpcGlzY2kgdmVsaXQuLi4iCiJUaGVyZSBpcyBubyBvbmUgd2hvIGxvdmVzIHBhaW4gaXRzZWxmLCB3aG8gc2Vla3MgYWZ0ZXIgaXQgYW5kIHdhbnRzIHRvIGhhdmUgaXQsIHNpbXBseSBiZWNhdXNlIGl0IGlzIHBhaW4uLi4iCkxvcmVtIGlwc3VtIGRvbG9yIHNpdCBhbWV0LCBjb25zZWN0ZXR1ciBhZGlwaXNjaW5nIGVsaXQuIFZpdmFtdXMgZWZmaWNpdHVyIGF1Z3VlIGVnZXQgbGVvIGF1Y3RvciBpbnRlcmR1bS4gTWFlY2VuYXMgcGhhcmV0cmEgcGVsbGVudGVzcXVlIGp1c3RvIGF0IHBvcnR0aXRvci4gUGhhc2VsbHVzIHNvZGFsZXMgYWNjdW1zYW4gcG9zdWVyZS4gU2VkIHVybmEgYXVndWUsIG1hbGVzdWFkYSBldCBwbGFjZXJhdCBlZ2V0LCBhbGlxdWV0IHNlZCB0b3J0b3IuIFByYWVzZW50IGNvbW1vZG8sIGVyb3MgdmVsIGZlcm1lbnR1bSBhbGlxdWV0LCBkaWFtIGZlbGlzIHRlbXB1cyB1cm5hLCBuZWMgcG9zdWVyZSBsaWd1bGEgZWxpdCBxdWlzIGRpYW0uIFNlZCBub24gbG9yZW0gdm9sdXRwYXQsIG1vbGVzdGllIG5pYmggZWdldCwgcHVsdmluYXIgZWxpdC4gTWFlY2VuYXMgYWMgdmFyaXVzIGV4LCBxdWlzIHVsdHJpY2VzIHNhcGllbi4gVml2YW11cyBlZ2VzdGFzLCByaXN1cyBzaXQgYW1ldCBtYWxlc3VhZGEgZWxlaWZlbmQsIG1hc3NhIHRvcnRvciBlbGVtZW50dW0gdHVycGlzLCBpZCBzb2xsaWNpdHVkaW4gZXN0IGxpYmVybyBzZWQgZHVpLiBOdWxsYSBpZCBwdWx2aW5hciBzZW0sIG5lYyBoZW5kcmVyaXQgbGFjdXMuCgpWZXN0aWJ1bHVtIGFsaXF1YW0gZXQgcHVydXMgc2l0IGFtZXQgdWxsYW1jb3JwZXIuIE5hbSB2aXZlcnJhIHRyaXN0aXF1ZSBmaW5pYnVzLiBWaXZhbXVzIGRpZ25pc3NpbSB0aW5jaWR1bnQgZXN0LiBEb25lYyBqdXN0byB0dXJwaXMsIGZldWdpYXQgc29sbGljaXR1ZGluIHN1c2NpcGl0IGEsIGJsYW5kaXQgY3Vyc3VzIHF1YW0uIER1aXMgdml2ZXJyYSBzZW0gdXQgbGFjaW5pYSBzZW1wZXIuIEFlbmVhbiB1bHRyaWNlcyBhbnRlIGV0IG5pc2kgc2VtcGVyIGRhcGlidXMuIE1vcmJpIHF1aXMgZnJpbmdpbGxhIG1hZ25hLiBOdW5jIGxpZ3VsYSBhcmN1LCBhbGlxdWFtIHF1aXMgbGVvIGV0LCBjb21tb2RvIG1vbGVzdGllIGxlY3R1cy4gUHJvaW4gYWMgbGVvIGZlcm1lbnR1bSwgY29udmFsbGlzIGVyYXQgaW4sIHVsdHJpY2llcyBtYWduYS4gTW9yYmkgbG9yZW0gYXJjdSwgY29uZ3VlIHNlZCBkaWFtIGlkLCBtb2xlc3RpZSBhbGlxdWV0IGVyYXQuIEFsaXF1YW0gdXQgZHVpIHBvc3VlcmUsIHB1bHZpbmFyIHNhcGllbiBzZWQsIGludGVyZHVtIHNhcGllbi4gRG9uZWMgc29sbGljaXR1ZGluIGxpYmVybyBpbXBlcmRpZXQgYWxpcXVhbSBhbGlxdWFtLgoKRG9uZWMgbm9uIG9kaW8gbWFzc2EuIE1hZWNlbmFzIGV1IGludGVyZHVtIGlwc3VtLCBhdCBncmF2aWRhIHR1cnBpcy4gVmVzdGlidWx1bSBldSBtb2xsaXMgdHVycGlzLCBlZ2V0IGZhdWNpYnVzIGxpZ3VsYS4gVmVzdGlidWx1bSBhbnRlIGlwc3VtIHByaW1pcyBpbiBmYXVjaWJ1cyBvcmNpIGx1Y3R1cyBldCB1bHRyaWNlcyBwb3N1ZXJlIGN1YmlsaWEgY3VyYWU7IEFlbmVhbiB0cmlzdGlxdWUgZXggbmVjIHJpc3VzIGJpYmVuZHVtLCBldSB1bGxhbWNvcnBlciBlcm9zIHBoYXJldHJhLiBVdCBmaW5pYnVzIGp1c3RvIGxlY3R1cywgbm9uIHZhcml1cyBhbnRlIHZvbHV0cGF0IGV1LiBQZWxsZW50ZXNxdWUgdHVycGlzIGVyb3MsIG1vbGVzdGllIGVnZXQgZW5pbSBuZWMsIGVsZW1lbnR1bSBwb3N1ZXJlIHJpc3VzLiBQaGFzZWxsdXMgZGljdHVtLCBlc3Qgdml0YWUgdml2ZXJyYSBlZ2VzdGFzLCBlbGl0IGVyb3MgcG9zdWVyZSBsb3JlbSwgaWQgZmluaWJ1cyBzYXBpZW4gbnVsbGEgc2l0IGFtZXQgYW50ZS4gRXRpYW0gaW4gcHVydXMgaWQgdGVsbHVzIHBvcnR0aXRvciBwb3N1ZXJlLgoKRG9uZWMgbm9uIGx1Y3R1cyBlbmltLiBWaXZhbXVzIHNvbGxpY2l0dWRpbiB0dXJwaXMgcXVpcyBxdWFtIGZlcm1lbnR1bSBwb3J0dGl0b3IuIFByb2luIGlkIHRpbmNpZHVudCBlcmF0LiBBZW5lYW4gaGVuZHJlcml0IHNpdCBhbWV0IGFyY3UgdmVuZW5hdGlzIHZ1bHB1dGF0ZS4gUGVsbGVudGVzcXVlIG5vbiBlcm9zIHZvbHV0cGF0LCB2ZXN0aWJ1bHVtIG1pIG5vbiwgdm9sdXRwYXQgYXJjdS4gSW4gYWMgdWxsYW1jb3JwZXIgc2FwaWVuLiBVdCBzaXQgYW1ldCB1cm5hIGFjIG51bmMgYWNjdW1zYW4gYXVjdG9yLiBGdXNjZSBhY2N1bXNhbiBsaWJlcm8gdmVsIHByZXRpdW0gaWFjdWxpcy4gUHJvaW4gc2VkIHZlaGljdWxhIGVyYXQuIEludGVnZXIgZmF1Y2lidXMgYXVndWUgbnVuYywgbmVjIGJsYW5kaXQgYXJjdSBsdWN0dXMgZGlnbmlzc2ltLiBNYWVjZW5hcyBhbGlxdWFtIHNvbGxpY2l0dWRpbiBkdWksIGF0IHBsYWNlcmF0IGVyYXQgbWFsZXN1YWRhIHV0LiBTdXNwZW5kaXNzZSBub24gcnV0cnVtIG9kaW8sIHV0IHVsdHJpY2llcyBtYXNzYS4KCk1vcmJpIHNpdCBhbWV0IHJpc3VzIHZlc3RpYnVsdW0sIHZlaGljdWxhIGVsaXQgdmVsLCB1bGxhbWNvcnBlciBhbnRlLiBBZW5lYW4gcnV0cnVtIHBlbGxlbnRlc3F1ZSBmZWxpcywgbmVjIHBoYXJldHJhIGlwc3VtIGNvbnNlY3RldHVyIHF1aXMuIE1hdXJpcyBpbiBlbGl0IGV1IG51bGxhIGRhcGlidXMgZmV1Z2lhdC4gSW4gdmVzdGlidWx1bSBtYXNzYSBpZCBsYW9yZWV0IHNvZGFsZXMuIFN1c3BlbmRpc3NlIG9ybmFyZSBjb25ndWUgbWV0dXMsIGluIHVsdHJpY2llcyBuZXF1ZSBjb25zZXF1YXQgbmVjLiBJbnRlcmR1bSBldCBtYWxlc3VhZGEgZmFtZXMgYWMgYW50ZSBpcHN1bSBwcmltaXMgaW4gZmF1Y2lidXMuIE1hZWNlbmFzIHNlZCBsdWN0dXMgdmVsaXQuIFF1aXNxdWUgaW4gbnVuYyBwZWxsZW50ZXNxdWUsIGFsaXF1ZXQgcXVhbSBpbiwgYWNjdW1zYW4gbGVjdHVzLiBWZXN0aWJ1bHVtIGRhcGlidXMgdG9ydG9yIGFjIG1ldHVzIGZldWdpYXQgY3Vyc3VzLiBTZWQgbmVjIHNjZWxlcmlzcXVlIGFyY3UuCg==',
fileName: 'lorem.txt',
fileSize: '3.07 kB',
},
},
},
],
],
},
},
},
];
for (const testData of tests) {
testHarness.setupTest(testData);
}
});
} else {
describe('Execute Compression Node', () => {
it('Skipped because compression results are different on platform win32', () => {
expect(true).toBe(true);
});
});
}
@@ -0,0 +1,575 @@
import { mockDeep } from 'jest-mock-extended';
import type { IExecuteFunctions, INode, IBinaryData } from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow';
import * as fflate from 'fflate';
import { Compression } from '../../Compression.node';
jest.mock('fflate');
const mockFflate = (
method: 'unzip' | 'gunzip' | 'zip' | 'gunzip',
data: any,
error: any = null,
) => {
jest.mocked(fflate[method]).mockImplementation((_, callback) => {
callback(error, data);
return () => {};
});
};
describe('Compression Node - Decompress Operation', () => {
let compression: Compression;
let mockExecuteFunctions: jest.Mocked<IExecuteFunctions>;
const mockNode: INode = {
id: 'test-node',
name: 'Compression',
type: 'n8n-nodes-base.compression',
typeVersion: 1.1,
position: [0, 0],
parameters: {},
};
beforeEach(() => {
compression = new Compression();
mockExecuteFunctions = mockDeep<IExecuteFunctions>();
jest.clearAllMocks();
mockExecuteFunctions.getNode.mockReturnValue(mockNode);
mockExecuteFunctions.getInputData.mockReturnValue([{ json: { test: 'data' } }]);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
const params: Record<string, string> = {
operation: 'decompress',
binaryPropertyName: 'data',
outputPrefix: 'file_',
};
return params[paramName];
});
mockExecuteFunctions.continueOnFail.mockReturnValue(false);
});
afterEach(() => {
jest.resetAllMocks();
});
describe('Zip Decompression', () => {
it('should decompress a zip file successfully', async () => {
const mockBinaryData: IBinaryData = {
data: 'base64data',
mimeType: 'application/zip',
fileName: 'test.zip',
fileExtension: 'zip',
};
const mockZipContents = {
file1_txt: new Uint8Array([72, 101, 108, 108, 111]),
file2_txt: new Uint8Array([87, 111, 114, 108, 100]),
};
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
jest
.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer)
.mockResolvedValue(Buffer.from('mock zip data'));
mockFflate('unzip', mockZipContents);
jest.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockImplementation(
async (buffer, fileName) =>
({
data: buffer.toString('base64'),
mimeType: 'text/plain',
fileName: fileName ?? 'file',
fileExtension: 'txt',
}) as IBinaryData,
);
const result = await compression.execute.call(mockExecuteFunctions);
expect(result).toHaveLength(1);
expect(result[0]).toHaveLength(1);
expect(result[0][0].json).toEqual({ test: 'data' });
expect(result[0][0].binary).toBeDefined();
expect(result[0][0].binary?.file_0).toBeDefined();
expect(result[0][0].binary?.file_1).toBeDefined();
expect(result[0][0].pairedItem).toEqual({ item: 0 });
expect(fflate.unzip).toHaveBeenCalledTimes(1);
});
it('should skip __MACOSX files when decompressing zip', async () => {
const mockBinaryData: IBinaryData = {
data: 'base64data',
mimeType: 'application/zip',
fileName: 'test.zip',
fileExtension: 'zip',
};
const mockZipContents = {
file1_txt: new Uint8Array([72, 101, 108, 108, 111]),
__MACOSX_file1_txt: new Uint8Array([0, 0, 0]),
file2_txt: new Uint8Array([87, 111, 114, 108, 100]),
};
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
jest
.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer)
.mockResolvedValue(Buffer.from('mock zip data'));
mockFflate('unzip', mockZipContents);
jest.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockImplementation(
async (buffer, fileName) =>
({
data: buffer.toString('base64'),
mimeType: 'text/plain',
fileName: fileName ?? 'file',
fileExtension: 'txt',
}) as IBinaryData,
);
const result = await compression.execute.call(mockExecuteFunctions);
expect(result[0][0].binary?.file_0).toBeDefined();
expect(result[0][0].binary?.file_1).toBeDefined();
expect(jest.mocked(mockExecuteFunctions.helpers.prepareBinaryData)).toHaveBeenCalledTimes(2);
});
it('should process multiple zip files from comma-separated binary properties', async () => {
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
const params: Record<string, string> = {
operation: 'decompress',
binaryPropertyName: 'data1,data2',
outputPrefix: 'file_',
};
return params[paramName];
});
const mockBinaryData: IBinaryData = {
data: 'base64data',
mimeType: 'application/zip',
fileName: 'test.zip',
fileExtension: 'zip',
};
const mockZipContents = {
file_txt: new Uint8Array([72, 101, 108, 108, 111]),
};
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
jest
.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer)
.mockResolvedValue(Buffer.from('mock zip data'));
mockFflate('unzip', mockZipContents);
jest.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockImplementation(
async (buffer, fileName) =>
({
data: buffer.toString('base64'),
mimeType: 'text/plain',
fileName: fileName ?? 'file',
fileExtension: 'txt',
}) as IBinaryData,
);
await compression.execute.call(mockExecuteFunctions);
expect(fflate.unzip).toHaveBeenCalledTimes(2);
expect(jest.mocked(mockExecuteFunctions.helpers.assertBinaryData)).toHaveBeenCalledWith(
0,
'data1',
);
expect(jest.mocked(mockExecuteFunctions.helpers.assertBinaryData)).toHaveBeenCalledWith(
0,
'data2',
);
});
});
describe('Gzip Decompression', () => {
it('should decompress a gzip file successfully', async () => {
const mockBinaryData: IBinaryData = {
data: 'base64data',
mimeType: 'application/gzip',
fileName: 'test.txt.gz',
fileExtension: 'gz',
};
const mockGunzipData = new Uint8Array([72, 101, 108, 108, 111]);
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
jest
.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer)
.mockResolvedValue(Buffer.from('mock gzip data'));
mockFflate('gunzip', mockGunzipData);
jest.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockResolvedValue({
data: 'SGVsbG8=',
mimeType: 'text/plain',
fileName: 'test.txt',
fileExtension: 'txt',
} as IBinaryData);
const result = await compression.execute.call(mockExecuteFunctions);
expect(result).toHaveLength(1);
expect(result[0]).toHaveLength(1);
expect(result[0][0].binary?.file_0).toBeDefined();
expect(result[0][0].binary?.file_0?.fileName).toBe('test.txt');
expect(result[0][0].binary?.file_0?.fileExtension).toBe('txt');
expect(result[0][0].binary?.file_0?.mimeType).toBe('text/plain');
expect(fflate.gunzip).toHaveBeenCalledTimes(1);
});
it('should handle gzip file with .gzip extension', async () => {
const mockBinaryData: IBinaryData = {
data: 'base64data',
mimeType: 'application/gzip',
fileName: 'test.txt.gzip',
fileExtension: 'gzip',
};
const mockGunzipData = new Uint8Array([72, 101, 108, 108, 111]);
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
jest
.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer)
.mockResolvedValue(Buffer.from('mock gzip data'));
mockFflate('gunzip', mockGunzipData);
jest.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockResolvedValue({
data: 'SGVsbG8=',
mimeType: 'text/plain',
fileName: 'test.txt',
fileExtension: 'txt',
} as IBinaryData);
const result = await compression.execute.call(mockExecuteFunctions);
expect(result[0][0].binary?.file_0).toBeDefined();
expect(fflate.gunzip).toHaveBeenCalledTimes(1);
});
it('should determine mime type and file extension for gzip file', async () => {
const mockBinaryData: IBinaryData = {
data: 'base64data',
mimeType: 'application/gzip',
fileName: 'data.json.gz',
fileExtension: 'gz',
};
const mockGunzipData = new Uint8Array([123, 125]);
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
jest
.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer)
.mockResolvedValue(Buffer.from('mock gzip data'));
mockFflate('gunzip', mockGunzipData);
let callCount = 0;
jest
.mocked(mockExecuteFunctions.helpers.prepareBinaryData)
.mockImplementation(async (buffer, fileName, mimeType) => {
callCount++;
if (callCount === 1) {
return {
data: buffer.toString('base64'),
mimeType: 'application/json',
fileName: fileName ?? 'data',
fileExtension: 'json',
} as IBinaryData;
}
return {
data: buffer.toString('base64'),
mimeType: mimeType ?? 'application/json',
fileName: fileName ?? 'data',
fileExtension: 'json',
} as IBinaryData;
});
const result = await compression.execute.call(mockExecuteFunctions);
expect(result[0][0].binary?.file_0?.fileName).toBe('data.json');
expect(result[0][0].binary?.file_0?.fileExtension).toBe('json');
expect(result[0][0].binary?.file_0?.mimeType).toBe('application/json');
});
it('should process multiple gzip files', async () => {
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
const params: Record<string, string> = {
operation: 'decompress',
binaryPropertyName: 'data1,data2',
outputPrefix: 'file_',
};
return params[paramName];
});
const mockBinaryData: IBinaryData = {
data: 'base64data',
mimeType: 'application/gzip',
fileName: 'test.txt.gz',
fileExtension: 'gz',
};
const mockGunzipData = new Uint8Array([72, 101, 108, 108, 111]);
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
jest
.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer)
.mockResolvedValue(Buffer.from('mock gzip data'));
mockFflate('gunzip', mockGunzipData);
jest.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockResolvedValue({
data: 'SGVsbG8=',
mimeType: 'text/plain',
fileName: 'test.txt',
fileExtension: 'txt',
} as IBinaryData);
const result = await compression.execute.call(mockExecuteFunctions);
expect(result[0][0].binary?.file_0).toBeDefined();
expect(result[0][0].binary?.file_1).toBeDefined();
expect(fflate.gunzip).toHaveBeenCalledTimes(2);
});
});
describe('Error Handling', () => {
it('should throw error when file extension is not found', async () => {
const mockBinaryData: IBinaryData = {
data: 'base64data',
mimeType: 'application/octet-stream',
fileName: undefined,
fileExtension: undefined,
};
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
await expect(compression.execute.call(mockExecuteFunctions)).rejects.toThrow(
NodeOperationError,
);
});
it('should handle decompression errors with continueOnFail', async () => {
const mockBinaryData: IBinaryData = {
data: 'base64data',
mimeType: 'application/zip',
fileName: 'test.zip',
fileExtension: 'zip',
};
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
jest
.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer)
.mockResolvedValue(Buffer.from('invalid zip data'));
mockFflate('unzip', null, new Error('Invalid zip file'));
mockExecuteFunctions.continueOnFail.mockReturnValue(true);
const result = await compression.execute.call(mockExecuteFunctions);
expect(result[0]).toHaveLength(1);
expect(result[0][0].json).toHaveProperty('error', 'Invalid zip file');
expect(result[0][0].pairedItem).toEqual({ item: 0 });
});
it('should throw error when continueOnFail is false', async () => {
const mockBinaryData: IBinaryData = {
data: 'base64data',
mimeType: 'application/zip',
fileName: 'test.zip',
fileExtension: 'zip',
};
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
jest
.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer)
.mockResolvedValue(Buffer.from('invalid zip data'));
mockFflate('unzip', null, new Error('Invalid zip file'));
mockExecuteFunctions.continueOnFail.mockReturnValue(false);
await expect(compression.execute.call(mockExecuteFunctions)).rejects.toThrow(
'Invalid zip file',
);
});
it('should handle gunzip errors with continueOnFail', async () => {
const mockBinaryData: IBinaryData = {
data: 'base64data',
mimeType: 'application/gzip',
fileName: 'test.txt.gz',
fileExtension: 'gz',
};
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
jest
.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer)
.mockResolvedValue(Buffer.from('invalid gzip data'));
mockFflate('gunzip', null, new Error('Invalid gzip file'));
mockExecuteFunctions.continueOnFail.mockReturnValue(true);
const result = await compression.execute.call(mockExecuteFunctions);
expect(result[0][0].json).toHaveProperty('error', 'Invalid gzip file');
});
it('should throw when fileExtension is missing', async () => {
const mockBinaryData: IBinaryData = {
data: 'base64data',
mimeType: 'application/zip',
fileName: 'test.zip',
};
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
jest
.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer)
.mockResolvedValue(Buffer.from('mock zip data'));
await expect(compression.execute.call(mockExecuteFunctions)).rejects.toThrow(
NodeOperationError,
);
});
});
describe('Multiple Items Processing', () => {
it('should process multiple input items', async () => {
mockExecuteFunctions.getInputData.mockReturnValue([
{ json: { item: 1 } },
{ json: { item: 2 } },
]);
const mockBinaryData: IBinaryData = {
data: 'base64data',
mimeType: 'application/gzip',
fileName: 'test.txt.gz',
fileExtension: 'gz',
};
const mockGunzipData = new Uint8Array([72, 101, 108, 108, 111]);
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
jest
.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer)
.mockResolvedValue(Buffer.from('mock gzip data'));
mockFflate('gunzip', mockGunzipData);
jest.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockResolvedValue({
data: 'SGVsbG8=',
mimeType: 'text/plain',
fileName: 'test.txt',
fileExtension: 'txt',
} as IBinaryData);
const result = await compression.execute.call(mockExecuteFunctions);
expect(result[0]).toHaveLength(2);
expect(result[0][0].json).toEqual({ item: 1 });
expect(result[0][1].json).toEqual({ item: 2 });
expect(result[0][0].pairedItem).toEqual({ item: 0 });
expect(result[0][1].pairedItem).toEqual({ item: 1 });
});
});
describe('Edge Cases', () => {
it('should use custom output prefix', async () => {
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
const params: Record<string, string> = {
operation: 'decompress',
binaryPropertyName: 'data',
outputPrefix: 'extracted_',
};
return params[paramName];
});
const mockBinaryData: IBinaryData = {
data: 'base64data',
mimeType: 'application/zip',
fileName: 'test.zip',
fileExtension: 'zip',
};
const mockZipContents = {
file1_txt: new Uint8Array([72, 101, 108, 108, 111]),
};
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
jest
.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer)
.mockResolvedValue(Buffer.from('mock zip data'));
mockFflate('unzip', mockZipContents);
jest.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockResolvedValue({
data: 'SGVsbG8=',
mimeType: 'text/plain',
fileName: 'file1.txt',
fileExtension: 'txt',
} as IBinaryData);
const result = await compression.execute.call(mockExecuteFunctions);
expect(result[0][0].binary?.extracted_0).toBeDefined();
});
it('should handle empty zip archive', async () => {
const mockBinaryData: IBinaryData = {
data: 'base64data',
mimeType: 'application/zip',
fileName: 'empty.zip',
fileExtension: 'zip',
};
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
jest
.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer)
.mockResolvedValue(Buffer.from('mock zip data'));
mockFflate('unzip', {});
const result = await compression.execute.call(mockExecuteFunctions);
expect(result[0][0].binary).toEqual({});
expect(result[0][0].json).toEqual({ test: 'data' });
});
it('should trim whitespace from binary property names', async () => {
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
const params: Record<string, string> = {
operation: 'decompress',
binaryPropertyName: ' data1 , data2 ',
outputPrefix: 'file_',
};
return params[paramName];
});
const mockBinaryData: IBinaryData = {
data: 'base64data',
mimeType: 'application/gzip',
fileName: 'test.txt.gz',
fileExtension: 'gz',
};
const mockGunzipData = new Uint8Array([72, 101, 108, 108, 111]);
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
jest
.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer)
.mockResolvedValue(Buffer.from('mock gzip data'));
mockFflate('gunzip', mockGunzipData);
jest.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockResolvedValue({
data: 'SGVsbG8=',
mimeType: 'text/plain',
fileName: 'test.txt',
fileExtension: 'txt',
} as IBinaryData);
const result = await compression.execute.call(mockExecuteFunctions);
expect(jest.mocked(mockExecuteFunctions.helpers.assertBinaryData)).toHaveBeenCalledWith(
0,
'data1',
);
expect(jest.mocked(mockExecuteFunctions.helpers.assertBinaryData)).toHaveBeenCalledWith(
0,
'data2',
);
expect(result[0][0].binary?.file_0).toBeDefined();
expect(result[0][0].binary?.file_1).toBeDefined();
});
});
});
@@ -0,0 +1,12 @@
Lorem Ipsum
"Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..."
"There is no one who loves pain itself, who seeks after it and wants to have it, simply because it is pain..."
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus efficitur augue eget leo auctor interdum. Maecenas pharetra pellentesque justo at porttitor. Phasellus sodales accumsan posuere. Sed urna augue, malesuada et placerat eget, aliquet sed tortor. Praesent commodo, eros vel fermentum aliquet, diam felis tempus urna, nec posuere ligula elit quis diam. Sed non lorem volutpat, molestie nibh eget, pulvinar elit. Maecenas ac varius ex, quis ultrices sapien. Vivamus egestas, risus sit amet malesuada eleifend, massa tortor elementum turpis, id sollicitudin est libero sed dui. Nulla id pulvinar sem, nec hendrerit lacus.
Vestibulum aliquam et purus sit amet ullamcorper. Nam viverra tristique finibus. Vivamus dignissim tincidunt est. Donec justo turpis, feugiat sollicitudin suscipit a, blandit cursus quam. Duis viverra sem ut lacinia semper. Aenean ultrices ante et nisi semper dapibus. Morbi quis fringilla magna. Nunc ligula arcu, aliquam quis leo et, commodo molestie lectus. Proin ac leo fermentum, convallis erat in, ultricies magna. Morbi lorem arcu, congue sed diam id, molestie aliquet erat. Aliquam ut dui posuere, pulvinar sapien sed, interdum sapien. Donec sollicitudin libero imperdiet aliquam aliquam.
Donec non odio massa. Maecenas eu interdum ipsum, at gravida turpis. Vestibulum eu mollis turpis, eget faucibus ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Aenean tristique ex nec risus bibendum, eu ullamcorper eros pharetra. Ut finibus justo lectus, non varius ante volutpat eu. Pellentesque turpis eros, molestie eget enim nec, elementum posuere risus. Phasellus dictum, est vitae viverra egestas, elit eros posuere lorem, id finibus sapien nulla sit amet ante. Etiam in purus id tellus porttitor posuere.
Donec non luctus enim. Vivamus sollicitudin turpis quis quam fermentum porttitor. Proin id tincidunt erat. Aenean hendrerit sit amet arcu venenatis vulputate. Pellentesque non eros volutpat, vestibulum mi non, volutpat arcu. In ac ullamcorper sapien. Ut sit amet urna ac nunc accumsan auctor. Fusce accumsan libero vel pretium iaculis. Proin sed vehicula erat. Integer faucibus augue nunc, nec blandit arcu luctus dignissim. Maecenas aliquam sollicitudin dui, at placerat erat malesuada ut. Suspendisse non rutrum odio, ut ultricies massa.
Morbi sit amet risus vestibulum, vehicula elit vel, ullamcorper ante. Aenean rutrum pellentesque felis, nec pharetra ipsum consectetur quis. Mauris in elit eu nulla dapibus feugiat. In vestibulum massa id laoreet sodales. Suspendisse ornare congue metus, in ultricies neque consequat nec. Interdum et malesuada fames ac ante ipsum primis in faucibus. Maecenas sed luctus velit. Quisque in nunc pellentesque, aliquet quam in, accumsan lectus. Vestibulum dapibus tortor ac metus feugiat cursus. Sed nec scelerisque arcu.
@@ -0,0 +1,87 @@
{
"name": "Compression test",
"nodes": [
{
"parameters": {},
"id": "abf9ed91-0b05-49df-82d3-1bafb6e6f698",
"name": "When clicking \"Execute Workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [400, 400]
},
{
"parameters": {
"operation": "compress",
"outputFormat": "zip",
"fileName": "data.zip"
},
"id": "a84b995a-bd44-42a0-b5e0-a8ff4e879814",
"name": "Compression",
"type": "n8n-nodes-base.compression",
"typeVersion": 1,
"position": [800, 400]
},
{
"parameters": {
"filePath": "/home/me/Desktop/lorem.txt"
},
"id": "2fdbf11d-e1ae-4091-b15f-e0eed69e87c3",
"name": "Read Binary File",
"type": "n8n-nodes-base.readBinaryFile",
"typeVersion": 1,
"position": [600, 400]
},
{
"parameters": {},
"id": "959fa107-f3e6-444e-8fd4-1c893b8942db",
"name": "Compression1",
"type": "n8n-nodes-base.compression",
"typeVersion": 1,
"position": [1000, 400]
}
],
"pinData": {},
"connections": {
"When clicking \"Execute Workflow\"": {
"main": [
[
{
"node": "Read Binary File",
"type": "main",
"index": 0
}
]
]
},
"Read Binary File": {
"main": [
[
{
"node": "Compression",
"type": "main",
"index": 0
}
]
]
},
"Compression": {
"main": [
[
{
"node": "Compression1",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {},
"versionId": "9868d72c-88bc-4bcc-a85b-586ae1b6b023",
"id": "112",
"meta": {
"instanceId": "36203ea1ce3cef713fa25999bd9874ae26b9e4c2c3a90a365f2882a154d031d0"
},
"tags": []
}