mirror of
https://github.com/n8n-io/n8n
synced 2026-04-21 15:47:20 +00:00
fix(Form Node): Update mime-types package to handle x-zip-compressed (#21492)
Co-authored-by: Roman Davydchuk <roman.davydchuk@n8n.io>
This commit is contained in:
parent
2a623eacf3
commit
8a935aa5c1
7 changed files with 638 additions and 41 deletions
|
|
@ -163,7 +163,7 @@
|
|||
"@types/cheerio": "^0.22.15",
|
||||
"@types/html-to-text": "^9.0.1",
|
||||
"@types/json-schema": "^7.0.15",
|
||||
"@types/mime-types": "^2.1.0",
|
||||
"@types/mime-types": "catalog:",
|
||||
"@types/pg": "^8.11.6",
|
||||
"@types/sanitize-html": "^2.11.0",
|
||||
"@types/temp": "^0.9.1",
|
||||
|
|
@ -228,7 +228,7 @@
|
|||
"langchain": "0.3.33",
|
||||
"lodash": "catalog:",
|
||||
"mammoth": "1.11.0",
|
||||
"mime-types": "2.1.35",
|
||||
"mime-types": "catalog:",
|
||||
"mongodb": "6.11.0",
|
||||
"n8n-nodes-base": "workspace:*",
|
||||
"n8n-workflow": "workspace:*",
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
"@types/express": "catalog:",
|
||||
"@types/jsonwebtoken": "catalog:",
|
||||
"@types/lodash": "catalog:",
|
||||
"@types/mime-types": "^2.1.0",
|
||||
"@types/mime-types": "catalog:",
|
||||
"@types/proxy-from-env": "^1.0.4",
|
||||
"@types/uuid": "catalog:",
|
||||
"@types/xml2js": "catalog:"
|
||||
|
|
@ -63,7 +63,7 @@
|
|||
"jsonwebtoken": "catalog:",
|
||||
"lodash": "catalog:",
|
||||
"luxon": "catalog:",
|
||||
"mime-types": "2.1.35",
|
||||
"mime-types": "catalog:",
|
||||
"n8n-workflow": "workspace:*",
|
||||
"nanoid": "catalog:",
|
||||
"oauth-1.0a": "2.2.6",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import * as fflate from 'fflate';
|
|||
import * as mime from 'mime-types';
|
||||
import {
|
||||
NodeConnectionTypes,
|
||||
NodeOperationError,
|
||||
type IBinaryKeyData,
|
||||
type IExecuteFunctions,
|
||||
type INodeExecutionData,
|
||||
|
|
@ -276,8 +277,16 @@ export class Compression implements INodeType {
|
|||
for (const [index, binaryPropertyName] of binaryPropertyNames.entries()) {
|
||||
const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName);
|
||||
const binaryDataBuffer = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName);
|
||||
const fileExtension = binaryData.fileExtension?.toLowerCase();
|
||||
|
||||
if (binaryData.fileExtension?.toLowerCase() === 'zip') {
|
||||
if (!fileExtension) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
`File extension not found for binary data ${binaryPropertyName}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (fileExtension === 'zip') {
|
||||
const files = await unzip(binaryDataBuffer);
|
||||
|
||||
for (const key of Object.keys(files)) {
|
||||
|
|
@ -293,7 +302,7 @@ export class Compression implements INodeType {
|
|||
|
||||
binaryObject[`${outputPrefix}${zipIndex++}`] = data;
|
||||
}
|
||||
} else if (['gz', 'gzip'].includes(binaryData.fileExtension?.toLowerCase() as string)) {
|
||||
} else if (['gz', 'gzip'].includes(fileExtension)) {
|
||||
const file = await gunzip(binaryDataBuffer);
|
||||
|
||||
const fileName = binaryData.fileName?.split('.')[0];
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -871,7 +871,7 @@
|
|||
"@types/lodash": "catalog:",
|
||||
"@types/lossless-json": "^1.0.0",
|
||||
"@types/mailparser": "^3.4.4",
|
||||
"@types/mime-types": "^2.1.0",
|
||||
"@types/mime-types": "catalog:",
|
||||
"@types/mssql": "^9.1.5",
|
||||
"@types/nodemailer": "^7.0.3",
|
||||
"@types/oracledb": "^6.9.1",
|
||||
|
|
@ -925,6 +925,7 @@
|
|||
"lossless-json": "1.0.5",
|
||||
"luxon": "catalog:",
|
||||
"mailparser": "3.6.7",
|
||||
"mime-types": "catalog:",
|
||||
"minifaker": "1.34.1",
|
||||
"moment-timezone": "0.5.48",
|
||||
"mongodb": "6.11.0",
|
||||
|
|
|
|||
|
|
@ -39,6 +39,9 @@ catalogs:
|
|||
'@types/lodash':
|
||||
specifier: 4.17.17
|
||||
version: 4.17.17
|
||||
'@types/mime-types':
|
||||
specifier: 3.0.1
|
||||
version: 3.0.1
|
||||
'@types/uuid':
|
||||
specifier: ^10.0.0
|
||||
version: 10.0.0
|
||||
|
|
@ -87,6 +90,9 @@ catalogs:
|
|||
luxon:
|
||||
specifier: 3.4.4
|
||||
version: 3.4.4
|
||||
mime-types:
|
||||
specifier: 3.0.1
|
||||
version: 3.0.1
|
||||
mysql2:
|
||||
specifier: 3.15.0
|
||||
version: 3.15.0
|
||||
|
|
@ -1211,8 +1217,8 @@ importers:
|
|||
specifier: 1.11.0
|
||||
version: 1.11.0
|
||||
mime-types:
|
||||
specifier: 2.1.35
|
||||
version: 2.1.35
|
||||
specifier: 'catalog:'
|
||||
version: 3.0.1
|
||||
mongodb:
|
||||
specifier: 6.11.0
|
||||
version: 6.11.0(@aws-sdk/credential-providers@3.808.0)(gcp-metadata@5.3.0)(socks@2.8.3)
|
||||
|
|
@ -1278,8 +1284,8 @@ importers:
|
|||
specifier: ^7.0.15
|
||||
version: 7.0.15
|
||||
'@types/mime-types':
|
||||
specifier: ^2.1.0
|
||||
version: 2.1.1
|
||||
specifier: 'catalog:'
|
||||
version: 3.0.1
|
||||
'@types/pg':
|
||||
specifier: ^8.11.6
|
||||
version: 8.11.6
|
||||
|
|
@ -1926,8 +1932,8 @@ importers:
|
|||
specifier: 'catalog:'
|
||||
version: 3.4.4
|
||||
mime-types:
|
||||
specifier: 2.1.35
|
||||
version: 2.1.35
|
||||
specifier: 'catalog:'
|
||||
version: 3.0.1
|
||||
n8n-workflow:
|
||||
specifier: workspace:*
|
||||
version: link:../workflow
|
||||
|
|
@ -1984,8 +1990,8 @@ importers:
|
|||
specifier: 'catalog:'
|
||||
version: 4.17.17
|
||||
'@types/mime-types':
|
||||
specifier: ^2.1.0
|
||||
version: 2.1.1
|
||||
specifier: 'catalog:'
|
||||
version: 3.0.1
|
||||
'@types/proxy-from-env':
|
||||
specifier: ^1.0.4
|
||||
version: 1.0.4
|
||||
|
|
@ -2999,6 +3005,9 @@ importers:
|
|||
mailparser:
|
||||
specifier: 3.6.7
|
||||
version: 3.6.7
|
||||
mime-types:
|
||||
specifier: 'catalog:'
|
||||
version: 3.0.1
|
||||
minifaker:
|
||||
specifier: 1.34.1
|
||||
version: 1.34.1(patch_hash=bc707e2c34a2464da2c9c93ead33e80fd9883a00434ef64907ddc45208a08b33)
|
||||
|
|
@ -3148,8 +3157,8 @@ importers:
|
|||
specifier: ^3.4.4
|
||||
version: 3.4.4
|
||||
'@types/mime-types':
|
||||
specifier: ^2.1.0
|
||||
version: 2.1.1
|
||||
specifier: 'catalog:'
|
||||
version: 3.0.1
|
||||
'@types/mssql':
|
||||
specifier: ^9.1.5
|
||||
version: 9.1.5
|
||||
|
|
@ -8317,8 +8326,8 @@ packages:
|
|||
'@types/methods@1.1.4':
|
||||
resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==}
|
||||
|
||||
'@types/mime-types@2.1.1':
|
||||
resolution: {integrity: sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==}
|
||||
'@types/mime-types@3.0.1':
|
||||
resolution: {integrity: sha512-xRMsfuQbnRq1Ef+C+RKaENOxXX87Ygl38W1vDfPHRku02TgQr+Qd8iivLtAMcR0KF5/29xlnFihkTlbqFrGOVQ==}
|
||||
|
||||
'@types/mime@1.3.5':
|
||||
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
|
||||
|
|
@ -24492,7 +24501,7 @@ snapshots:
|
|||
|
||||
'@types/methods@1.1.4': {}
|
||||
|
||||
'@types/mime-types@2.1.1': {}
|
||||
'@types/mime-types@3.0.1': {}
|
||||
|
||||
'@types/mime@1.3.5': {}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,24 +5,19 @@ packages:
|
|||
- packages/extensions/**
|
||||
- packages/testing/**
|
||||
|
||||
minimumReleaseAge: 2880 # 2 days
|
||||
minimumReleaseAgeExclude:
|
||||
- '@n8n/*'
|
||||
- '@n8n_io/*'
|
||||
- eslint-plugin-storybook
|
||||
|
||||
catalog:
|
||||
'@n8n/typeorm': 0.3.20-15
|
||||
'@n8n_io/ai-assistant-sdk': 1.17.0
|
||||
'@langchain/core': 0.3.68
|
||||
'@langchain/openai': 0.6.16
|
||||
'@langchain/anthropic': 0.3.26
|
||||
'@langchain/community': 0.3.50
|
||||
'@langchain/core': 0.3.68
|
||||
'@langchain/openai': 0.6.16
|
||||
'@n8n/typeorm': 0.3.20-15
|
||||
'@n8n_io/ai-assistant-sdk': 1.17.0
|
||||
'@sentry/node': ^9.42.1
|
||||
'@types/basic-auth': ^1.1.3
|
||||
'@types/express': ^5.0.1
|
||||
'@types/jsonwebtoken': ^9.0.9
|
||||
'@types/lodash': 4.17.17
|
||||
'@types/mime-types': 3.0.1
|
||||
'@types/uuid': ^10.0.0
|
||||
'@types/xml2js': ^0.4.14
|
||||
'@vitest/coverage-v8': 3.2.4
|
||||
|
|
@ -30,6 +25,7 @@ catalog:
|
|||
basic-auth: 2.0.1
|
||||
callsites: 3.1.0
|
||||
chokidar: 4.0.3
|
||||
eslint: 9.29.0
|
||||
fast-glob: 3.2.12
|
||||
fastest-levenshtein: 1.0.16
|
||||
flatted: 3.2.7
|
||||
|
|
@ -37,17 +33,22 @@ catalog:
|
|||
http-proxy-agent: 7.0.2
|
||||
https-proxy-agent: 7.0.6
|
||||
iconv-lite: 0.6.3
|
||||
jsonwebtoken: 9.0.2
|
||||
js-base64: 3.7.2
|
||||
jsonwebtoken: 9.0.2
|
||||
lodash: 4.17.21
|
||||
luxon: 3.4.4
|
||||
mime-types: 3.0.1
|
||||
mysql2: 3.15.0
|
||||
nanoid: 3.3.8
|
||||
nodemailer: 7.0.10
|
||||
picocolors: 1.0.1
|
||||
reflect-metadata: 0.2.2
|
||||
rimraf: 6.0.1
|
||||
run-script-os: 1.1.6
|
||||
simple-git: 3.28.0
|
||||
tsdown: ^0.15.6
|
||||
tsx: ^4.19.3
|
||||
simple-git: 3.28.0
|
||||
typescript: 5.9.2
|
||||
uuid: 10.0.0
|
||||
vite: npm:rolldown-vite@latest
|
||||
vite-plugin-dts: ^4.5.4
|
||||
|
|
@ -57,11 +58,6 @@ catalog:
|
|||
xss: 1.0.15
|
||||
zod: 3.25.67
|
||||
zod-to-json-schema: 3.23.3
|
||||
typescript: 5.9.2
|
||||
eslint: 9.29.0
|
||||
mysql2: 3.15.0
|
||||
run-script-os: 1.1.6
|
||||
nodemailer: 7.0.10
|
||||
|
||||
catalogs:
|
||||
frontend:
|
||||
|
|
@ -69,16 +65,23 @@ catalogs:
|
|||
'@testing-library/jest-dom': ^6.6.3
|
||||
'@testing-library/user-event': ^14.6.1
|
||||
'@testing-library/vue': ^8.1.0
|
||||
'@vitejs/plugin-vue': ^5.2.4
|
||||
'@vue/test-utils': ^2.4.6
|
||||
'@vue/tsconfig': ^0.7.0
|
||||
'@vueuse/core': ^10.11.0
|
||||
'@vitejs/plugin-vue': ^5.2.4
|
||||
element-plus: 2.4.3
|
||||
highlight.js: ^11.8.0
|
||||
pinia: ^2.2.4
|
||||
unplugin-icons: ^0.19.0
|
||||
vue: ^3.5.13
|
||||
vue-i18n: ^11.1.2
|
||||
vue-markdown-render: ^2.2.1
|
||||
vue-router: ^4.5.0
|
||||
vue-tsc: ^2.2.8
|
||||
vue-markdown-render: ^2.2.1
|
||||
highlight.js: ^11.8.0
|
||||
element-plus: 2.4.3
|
||||
|
||||
minimumReleaseAge: 2880
|
||||
|
||||
minimumReleaseAgeExclude:
|
||||
- '@n8n/*'
|
||||
- '@n8n_io/*'
|
||||
- eslint-plugin-storybook
|
||||
|
|
|
|||
Loading…
Reference in a new issue