n8n/packages/nodes-base/nodes/EditImage/test/EditImage.node.test.ts
Dimitri Lavrenük b649eea01d
fix(EditImage Node): Fix orientation when modifying images (#28970)
Co-authored-by: RomanDavydchuk <roman.davydchuk@n8n.io>
2026-04-23 14:02:43 +00:00

1546 lines
48 KiB
TypeScript

import { mockDeep } from 'jest-mock-extended';
import type { IExecuteFunctions, INode, INodeExecutionData } from 'n8n-workflow';
import { EditImage } from '../EditImage.node';
const createTestBuffer = () =>
Buffer.from([
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1f, 0x15, 0xc4,
0x89, 0x00, 0x00, 0x00, 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x63, 0x00, 0x01, 0x00, 0x00,
0x05, 0x00, 0x01, 0x0d, 0x0a, 0x2d, 0xb4, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae,
0x42, 0x60, 0x82,
]);
const mockGmInstance: any = {
background: jest.fn(function (this: any) {
return this;
}),
blur: jest.fn(function (this: any) {
return this;
}),
borderColor: jest.fn(function (this: any) {
return this;
}),
border: jest.fn(function (this: any) {
return this;
}),
compose: jest.fn(function (this: any) {
return this;
}),
geometry: jest.fn(function (this: any) {
return this;
}),
composite: jest.fn(function (this: any) {
return this;
}),
crop: jest.fn(function (this: any) {
return this;
}),
drawCircle: jest.fn(function (this: any) {
return this;
}),
drawLine: jest.fn(function (this: any) {
return this;
}),
drawRectangle: jest.fn(function (this: any) {
return this;
}),
fill: jest.fn(function (this: any) {
return this;
}),
font: jest.fn(function (this: any) {
return this;
}),
fontSize: jest.fn(function (this: any) {
return this;
}),
drawText: jest.fn(function (this: any) {
return this;
}),
identify: jest.fn(function (this: any, callback: any) {
callback(null, { width: 100, height: 100, format: 'PNG' });
return this;
}),
quality: jest.fn(function (this: any) {
return this;
}),
resize: jest.fn(function (this: any) {
return this;
}),
rotate: jest.fn(function (this: any) {
return this;
}),
setFormat: jest.fn(function (this: any) {
return this;
}),
shear: jest.fn(function (this: any) {
return this;
}),
stream: jest.fn(function (this: any) {
return this;
}),
transparent: jest.fn(function (this: any) {
return this;
}),
toBuffer: jest.fn(function (this: any, callback: any) {
callback(null, createTestBuffer());
return this;
}),
autoOrient: jest.fn(function (this: any) {
return this;
}),
out: jest.fn(function (this: any) {
return this;
}),
};
jest.mock('gm', () => jest.fn(() => mockGmInstance));
describe('EditImage Node', () => {
let editImageNode: EditImage;
const mockExecuteFunctions = mockDeep<IExecuteFunctions>();
let mockNode: INode;
beforeEach(() => {
jest.clearAllMocks();
editImageNode = new EditImage();
mockNode = {
id: 'test-node-id',
name: 'EditImage',
type: 'n8n-nodes-base.editImage',
typeVersion: 1,
position: [0, 0],
parameters: {},
};
mockExecuteFunctions.getNode.mockReturnValue(mockNode);
mockExecuteFunctions.continueOnFail.mockReturnValue(false);
mockExecuteFunctions.helpers.assertBinaryData.mockReturnValue(undefined as any);
});
describe('dataPropertyName parameter', () => {
it('should handle IBinaryData type', async () => {
const testBuffer = createTestBuffer();
const binaryData = {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
};
const items: INodeExecutionData[] = [
{
json: {},
binary: {
data: binaryData,
},
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'blur';
if (paramName === 'dataPropertyName') return binaryData;
if (paramName === 'blur') return 5;
if (paramName === 'sigma') return 2;
return {};
});
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(testBuffer);
mockExecuteFunctions.helpers.prepareBinaryData.mockResolvedValue({
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
});
const result = await editImageNode.execute.call(mockExecuteFunctions);
expect(result[0]).toHaveLength(1);
expect(result[0][0].binary).toHaveProperty('data');
expect(mockExecuteFunctions.helpers.assertBinaryData).toHaveBeenCalledWith(0, binaryData);
});
it('should handle string type for custom property', async () => {
const testBuffer = createTestBuffer();
const items: INodeExecutionData[] = [
{
json: {},
binary: {
customImageProperty: {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
},
},
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'information';
if (paramName === 'dataPropertyName') return 'customImageProperty';
return {};
});
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(testBuffer);
await editImageNode.execute.call(mockExecuteFunctions);
expect(mockExecuteFunctions.helpers.assertBinaryData).toHaveBeenCalledWith(
0,
'customImageProperty',
);
expect(mockExecuteFunctions.helpers.getBinaryDataBuffer).toHaveBeenCalledWith(
0,
'customImageProperty',
);
});
it('should handle string type for default "data" property', async () => {
const testBuffer = createTestBuffer();
const items: INodeExecutionData[] = [
{
json: {},
binary: {
data: {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
},
},
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'information';
if (paramName === 'dataPropertyName') return 'data';
return {};
});
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(testBuffer);
await editImageNode.execute.call(mockExecuteFunctions);
expect(mockExecuteFunctions.helpers.assertBinaryData).toHaveBeenCalledWith(0, 'data');
expect(mockExecuteFunctions.helpers.getBinaryDataBuffer).toHaveBeenCalledWith(0, 'data');
});
it('should throw error when binary data is missing', async () => {
const items: INodeExecutionData[] = [
{
json: {},
binary: {},
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'blur';
if (paramName === 'dataPropertyName') return 'missingProperty';
if (paramName === 'blur') return 5;
if (paramName === 'sigma') return 2;
return {};
});
const error = new Error('Binary data missing');
mockExecuteFunctions.helpers.assertBinaryData.mockImplementation(() => {
throw error;
});
await expect(editImageNode.execute.call(mockExecuteFunctions)).rejects.toThrow();
});
});
describe('destinationKey option', () => {
it('should use destinationKey for output property', async () => {
const testBuffer = createTestBuffer();
const items: INodeExecutionData[] = [
{
json: { id: 1 },
binary: {
data: {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
},
},
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string, _: number) => {
if (paramName === 'operation') return 'blur';
if (paramName === 'dataPropertyName') return 'data';
if (paramName === 'blur') return 5;
if (paramName === 'sigma') return 2;
if (paramName === 'options') {
return {
destinationKey: 'processedImage',
};
}
return {};
});
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(testBuffer);
mockExecuteFunctions.helpers.prepareBinaryData.mockResolvedValue({
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
});
const result = await editImageNode.execute.call(mockExecuteFunctions);
expect(result[0]).toHaveLength(1);
expect(result[0][0].binary).toHaveProperty('processedImage');
expect(result[0][0].json).toEqual({ id: 1 });
});
it('should default output to input property name when destinationKey not specified', async () => {
const testBuffer = createTestBuffer();
const items: INodeExecutionData[] = [
{
json: { id: 1 },
binary: {
imageData: {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
},
},
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'blur';
if (paramName === 'dataPropertyName') return 'imageData';
if (paramName === 'blur') return 5;
if (paramName === 'sigma') return 2;
if (paramName === 'options') return {};
return {};
});
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(testBuffer);
mockExecuteFunctions.helpers.prepareBinaryData.mockResolvedValue({
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
});
const result = await editImageNode.execute.call(mockExecuteFunctions);
expect(result[0]).toHaveLength(1);
expect(result[0][0].binary).toHaveProperty('imageData');
});
});
describe('data preservation', () => {
it('should preserve existing binary data when processing', async () => {
const testBuffer = createTestBuffer();
const items: INodeExecutionData[] = [
{
json: { id: 1 },
binary: {
data: {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
},
otherFile: {
data: 'other-data',
mimeType: 'text/plain',
fileName: 'other.txt',
},
},
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'blur';
if (paramName === 'dataPropertyName') return 'data';
if (paramName === 'blur') return 5;
if (paramName === 'sigma') return 2;
if (paramName === 'options') return {};
return {};
});
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(testBuffer);
mockExecuteFunctions.helpers.prepareBinaryData.mockResolvedValue({
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
});
const result = await editImageNode.execute.call(mockExecuteFunctions);
expect(result[0]).toHaveLength(1);
expect(result[0][0].binary).toHaveProperty('data');
expect(result[0][0].binary).toHaveProperty('otherFile');
expect(result[0][0].binary?.otherFile).toEqual({
data: 'other-data',
mimeType: 'text/plain',
fileName: 'other.txt',
});
});
it('should preserve JSON data when processing binary', async () => {
const testBuffer = createTestBuffer();
const items: INodeExecutionData[] = [
{
json: { id: 1, name: 'test', metadata: { processed: false } },
binary: {
data: {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
},
},
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'blur';
if (paramName === 'dataPropertyName') return 'data';
if (paramName === 'blur') return 5;
if (paramName === 'sigma') return 2;
if (paramName === 'options') return {};
return {};
});
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(testBuffer);
mockExecuteFunctions.helpers.prepareBinaryData.mockResolvedValue({
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
});
const result = await editImageNode.execute.call(mockExecuteFunctions);
expect(result[0]).toHaveLength(1);
expect(result[0][0].json).toEqual({ id: 1, name: 'test', metadata: { processed: false } });
});
});
describe('create operation', () => {
it('should create image without input binary', async () => {
const items: INodeExecutionData[] = [
{
json: {},
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'create';
if (paramName === 'dataPropertyName') return 'data';
if (paramName === 'backgroundColor') return '#ffffff';
if (paramName === 'width') return 100;
if (paramName === 'height') return 100;
if (paramName === 'options') {
return {
format: 'png',
};
}
return {};
});
const testBuffer = createTestBuffer();
mockExecuteFunctions.helpers.prepareBinaryData.mockResolvedValue({
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'image.png',
});
const result = await editImageNode.execute.call(mockExecuteFunctions);
expect(result[0]).toHaveLength(1);
expect(result[0][0].binary).toHaveProperty('data');
expect(mockExecuteFunctions.helpers.assertBinaryData).not.toHaveBeenCalled();
});
});
describe('information operation', () => {
it('should return image information', async () => {
const testBuffer = createTestBuffer();
const items: INodeExecutionData[] = [
{
json: {},
binary: {
data: {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
},
},
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'information';
if (paramName === 'dataPropertyName') return 'data';
return {};
});
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(testBuffer);
const result = await editImageNode.execute.call(mockExecuteFunctions);
expect(result[0]).toHaveLength(1);
expect(result[0][0].json).toBeDefined();
expect(result[0][0].json).toHaveProperty('width');
expect(result[0][0].json).toHaveProperty('height');
expect(result[0][0].json).toHaveProperty('format');
});
});
describe('multiple items', () => {
it('should process multiple items with different binary property names', async () => {
const testBuffer = createTestBuffer();
const items: INodeExecutionData[] = [
{
json: { id: 1 },
binary: {
image1: {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test1.png',
},
},
},
{
json: { id: 2 },
binary: {
image2: {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test2.png',
},
},
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation(
(paramName: string, itemIndex: number) => {
if (paramName === 'operation') return 'blur';
if (paramName === 'dataPropertyName') {
return itemIndex === 0 ? 'image1' : 'image2';
}
if (paramName === 'blur') return 5;
if (paramName === 'sigma') return 2;
if (paramName === 'options') return {};
return {};
},
);
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(testBuffer);
mockExecuteFunctions.helpers.prepareBinaryData.mockResolvedValue({
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
});
const result = await editImageNode.execute.call(mockExecuteFunctions);
expect(result[0]).toHaveLength(2);
expect(result[0][0].binary).toHaveProperty('image1');
expect(result[0][1].binary).toHaveProperty('image2');
expect(mockExecuteFunctions.helpers.assertBinaryData).toHaveBeenCalledWith(0, 'image1');
expect(mockExecuteFunctions.helpers.assertBinaryData).toHaveBeenCalledWith(1, 'image2');
});
});
describe('format and quality options', () => {
it('should apply format option to output binary data', async () => {
const testBuffer = createTestBuffer();
const items: INodeExecutionData[] = [
{
json: {},
binary: {
data: {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
},
},
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'blur';
if (paramName === 'dataPropertyName') return 'data';
if (paramName === 'blur') return 5;
if (paramName === 'sigma') return 2;
if (paramName === 'options') {
return {
format: 'jpeg',
quality: 80,
};
}
return {};
});
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(testBuffer);
mockExecuteFunctions.helpers.prepareBinaryData.mockResolvedValue({
data: testBuffer.toString('base64'),
mimeType: 'image/jpeg',
fileExtension: 'jpeg',
fileName: 'test.jpeg',
});
const result = await editImageNode.execute.call(mockExecuteFunctions);
expect(result[0]).toHaveLength(1);
expect(result[0][0].binary?.data?.mimeType).toBe('image/jpeg');
expect(result[0][0].binary?.data?.fileExtension).toBe('jpeg');
});
it('should apply custom fileName option', async () => {
const testBuffer = createTestBuffer();
const items: INodeExecutionData[] = [
{
json: {},
binary: {
data: {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
},
},
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'blur';
if (paramName === 'dataPropertyName') return 'data';
if (paramName === 'blur') return 5;
if (paramName === 'sigma') return 2;
if (paramName === 'options') {
return {
fileName: 'custom-output.png',
};
}
return {};
});
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(testBuffer);
mockExecuteFunctions.helpers.prepareBinaryData.mockResolvedValue({
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'custom-output.png',
});
const result = await editImageNode.execute.call(mockExecuteFunctions);
expect(result[0]).toHaveLength(1);
expect(result[0][0].binary?.data?.fileName).toBe('custom-output.png');
});
});
describe('error handling', () => {
it('should return error in json when continueOnFail is true', async () => {
const items: INodeExecutionData[] = [
{
json: {},
binary: {},
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.continueOnFail.mockReturnValue(true);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'blur';
if (paramName === 'dataPropertyName') return 'missingProperty';
if (paramName === 'blur') return 5;
if (paramName === 'sigma') return 2;
return {};
});
const error = new Error('Binary data missing');
mockExecuteFunctions.helpers.assertBinaryData.mockImplementation(() => {
throw error;
});
const result = await editImageNode.execute.call(mockExecuteFunctions);
expect(result[0]).toHaveLength(1);
expect(result[0][0].json).toHaveProperty('error');
expect(result[0][0].json.error).toBe('Binary data missing');
expect(result[0][0].pairedItem).toEqual({ item: 0 });
});
it('should throw error when continueOnFail is false', async () => {
const items: INodeExecutionData[] = [
{
json: {},
binary: {},
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.continueOnFail.mockReturnValue(false);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'blur';
if (paramName === 'dataPropertyName') return 'missingProperty';
if (paramName === 'blur') return 5;
if (paramName === 'sigma') return 2;
return {};
});
const error = new Error('Binary data missing');
mockExecuteFunctions.helpers.assertBinaryData.mockImplementation(() => {
throw error;
});
await expect(editImageNode.execute.call(mockExecuteFunctions)).rejects.toThrow(
'Binary data missing',
);
});
});
describe('operations with IBinaryData', () => {
it('should handle information operation with IBinaryData', async () => {
const testBuffer = createTestBuffer();
const binaryData = {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
};
const items: INodeExecutionData[] = [
{
json: {},
binary: {
data: binaryData,
},
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'information';
if (paramName === 'dataPropertyName') return binaryData;
return {};
});
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(testBuffer);
const result = await editImageNode.execute.call(mockExecuteFunctions);
expect(result[0]).toHaveLength(1);
expect(result[0][0].json).toHaveProperty('width');
expect(result[0][0].json).toHaveProperty('height');
expect(mockExecuteFunctions.helpers.assertBinaryData).toHaveBeenCalledWith(0, binaryData);
expect(mockExecuteFunctions.helpers.getBinaryDataBuffer).toHaveBeenCalledWith(0, binaryData);
});
it('should handle crop operation with IBinaryData', async () => {
const testBuffer = createTestBuffer();
const binaryData = {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
};
const items: INodeExecutionData[] = [
{
json: {},
binary: {
data: binaryData,
},
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'crop';
if (paramName === 'dataPropertyName') return binaryData;
if (paramName === 'width') return 100;
if (paramName === 'height') return 100;
if (paramName === 'positionX') return 0;
if (paramName === 'positionY') return 0;
if (paramName === 'options') return {};
return {};
});
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(testBuffer);
mockExecuteFunctions.helpers.prepareBinaryData.mockResolvedValue({
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
});
const result = await editImageNode.execute.call(mockExecuteFunctions);
expect(result[0]).toHaveLength(1);
expect(result[0][0].binary).toHaveProperty('data');
expect(mockExecuteFunctions.helpers.assertBinaryData).toHaveBeenCalledWith(0, binaryData);
});
it('should handle text operation with IBinaryData', async () => {
const testBuffer = createTestBuffer();
const binaryData = {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
};
const items: INodeExecutionData[] = [
{
json: {},
binary: {
data: binaryData,
},
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'text';
if (paramName === 'dataPropertyName') return binaryData;
if (paramName === 'text') return 'Hello';
if (paramName === 'fontSize') return 18;
if (paramName === 'fontColor') return '#000000';
if (paramName === 'positionX') return 10;
if (paramName === 'positionY') return 10;
if (paramName === 'lineLength') return 80;
if (paramName === 'options') return { font: 'Arial' };
return {};
});
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(testBuffer);
mockExecuteFunctions.helpers.prepareBinaryData.mockResolvedValue({
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
});
const result = await editImageNode.execute.call(mockExecuteFunctions);
expect(result[0]).toHaveLength(1);
expect(result[0][0].binary).toHaveProperty('data');
expect(mockExecuteFunctions.helpers.assertBinaryData).toHaveBeenCalledWith(0, binaryData);
});
it('should use destinationKey with IBinaryData', async () => {
const testBuffer = createTestBuffer();
const binaryData = {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
};
const items: INodeExecutionData[] = [
{
json: {},
binary: {
data: binaryData,
},
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'blur';
if (paramName === 'dataPropertyName') return binaryData;
if (paramName === 'blur') return 5;
if (paramName === 'sigma') return 2;
if (paramName === 'options') return { destinationKey: 'output' };
return {};
});
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(testBuffer);
mockExecuteFunctions.helpers.prepareBinaryData.mockResolvedValue({
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
});
const result = await editImageNode.execute.call(mockExecuteFunctions);
expect(result[0]).toHaveLength(1);
expect(result[0][0].binary).toHaveProperty('output');
});
it('should read from both main and composite binary properties', async () => {
const testBuffer = createTestBuffer();
const items: INodeExecutionData[] = [
{
json: {},
binary: {
mainImage: {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'main.png',
},
overlayImage: {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'overlay.png',
},
},
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'composite';
if (paramName === 'dataPropertyName') return 'mainImage';
if (paramName === 'dataPropertyNameComposite') return 'overlayImage';
if (paramName === 'operator') return 'Over';
if (paramName === 'positionX') return 0;
if (paramName === 'positionY') return 0;
if (paramName === 'options') return {};
return {};
});
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(testBuffer);
mockExecuteFunctions.helpers.prepareBinaryData.mockResolvedValue({
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
});
await editImageNode.execute.call(mockExecuteFunctions);
expect(mockExecuteFunctions.helpers.assertBinaryData).toHaveBeenCalledWith(0, 'mainImage');
expect(mockExecuteFunctions.helpers.assertBinaryData).toHaveBeenCalledWith(0, 'overlayImage');
});
});
describe('all operations with string and IBinaryData', () => {
it('should handle border operation with string dataPropertyName', async () => {
const testBuffer = createTestBuffer();
const items: INodeExecutionData[] = [
{
json: {},
binary: {
data: {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
},
},
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'border';
if (paramName === 'dataPropertyName') return 'data';
if (paramName === 'borderColor') return '#000000';
if (paramName === 'borderWidth') return 10;
if (paramName === 'borderHeight') return 10;
if (paramName === 'options') return {};
return {};
});
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(testBuffer);
mockExecuteFunctions.helpers.prepareBinaryData.mockResolvedValue({
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
});
const result = await editImageNode.execute.call(mockExecuteFunctions);
expect(result[0]).toHaveLength(1);
expect(result[0][0].binary).toHaveProperty('data');
expect(mockExecuteFunctions.helpers.assertBinaryData).toHaveBeenCalledWith(0, 'data');
});
it('should handle border operation with IBinaryData dataPropertyName', async () => {
const testBuffer = createTestBuffer();
const binaryData = {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
};
const items: INodeExecutionData[] = [
{
json: {},
binary: { data: binaryData },
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'border';
if (paramName === 'dataPropertyName') return binaryData;
if (paramName === 'borderColor') return '#000000';
if (paramName === 'borderWidth') return 10;
if (paramName === 'borderHeight') return 10;
if (paramName === 'options') return {};
return {};
});
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(testBuffer);
mockExecuteFunctions.helpers.prepareBinaryData.mockResolvedValue({
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
});
const result = await editImageNode.execute.call(mockExecuteFunctions);
expect(result[0]).toHaveLength(1);
expect(result[0][0].binary).toHaveProperty('data');
expect(mockExecuteFunctions.helpers.assertBinaryData).toHaveBeenCalledWith(0, binaryData);
});
it('should handle draw operation with string dataPropertyName', async () => {
const testBuffer = createTestBuffer();
const items: INodeExecutionData[] = [
{
json: {},
binary: {
imageFile: {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
},
},
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'draw';
if (paramName === 'dataPropertyName') return 'imageFile';
if (paramName === 'primitive') return 'rectangle';
if (paramName === 'color') return '#ff0000';
if (paramName === 'startPositionX') return 10;
if (paramName === 'startPositionY') return 10;
if (paramName === 'endPositionX') return 100;
if (paramName === 'endPositionY') return 100;
if (paramName === 'cornerRadius') return 5;
if (paramName === 'options') return {};
return {};
});
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(testBuffer);
mockExecuteFunctions.helpers.prepareBinaryData.mockResolvedValue({
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
});
const result = await editImageNode.execute.call(mockExecuteFunctions);
expect(result[0]).toHaveLength(1);
expect(result[0][0].binary).toHaveProperty('imageFile');
expect(mockExecuteFunctions.helpers.assertBinaryData).toHaveBeenCalledWith(0, 'imageFile');
});
it('should handle draw operation with IBinaryData dataPropertyName', async () => {
const testBuffer = createTestBuffer();
const binaryData = {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
};
const items: INodeExecutionData[] = [
{
json: {},
binary: { data: binaryData },
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'draw';
if (paramName === 'dataPropertyName') return binaryData;
if (paramName === 'primitive') return 'circle';
if (paramName === 'color') return '#0000ff';
if (paramName === 'startPositionX') return 50;
if (paramName === 'startPositionY') return 50;
if (paramName === 'endPositionX') return 75;
if (paramName === 'endPositionY') return 75;
if (paramName === 'options') return {};
return {};
});
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(testBuffer);
mockExecuteFunctions.helpers.prepareBinaryData.mockResolvedValue({
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
});
const result = await editImageNode.execute.call(mockExecuteFunctions);
expect(result[0]).toHaveLength(1);
expect(result[0][0].binary).toHaveProperty('data');
expect(mockExecuteFunctions.helpers.assertBinaryData).toHaveBeenCalledWith(0, binaryData);
});
it('should handle rotate operation with string dataPropertyName', async () => {
const testBuffer = createTestBuffer();
const items: INodeExecutionData[] = [
{
json: {},
binary: {
photo: {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
},
},
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'rotate';
if (paramName === 'dataPropertyName') return 'photo';
if (paramName === 'rotate') return 45;
if (paramName === 'backgroundColor') return '#ffffff';
if (paramName === 'options') return {};
return {};
});
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(testBuffer);
mockExecuteFunctions.helpers.prepareBinaryData.mockResolvedValue({
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
});
const result = await editImageNode.execute.call(mockExecuteFunctions);
expect(result[0]).toHaveLength(1);
expect(result[0][0].binary).toHaveProperty('photo');
expect(mockExecuteFunctions.helpers.assertBinaryData).toHaveBeenCalledWith(0, 'photo');
});
it('should handle rotate operation with IBinaryData dataPropertyName', async () => {
const testBuffer = createTestBuffer();
const binaryData = {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
};
const items: INodeExecutionData[] = [
{
json: {},
binary: { data: binaryData },
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'rotate';
if (paramName === 'dataPropertyName') return binaryData;
if (paramName === 'rotate') return 90;
if (paramName === 'backgroundColor') return '#000000';
if (paramName === 'options') return {};
return {};
});
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(testBuffer);
mockExecuteFunctions.helpers.prepareBinaryData.mockResolvedValue({
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
});
const result = await editImageNode.execute.call(mockExecuteFunctions);
expect(result[0]).toHaveLength(1);
expect(result[0][0].binary).toHaveProperty('data');
expect(mockExecuteFunctions.helpers.assertBinaryData).toHaveBeenCalledWith(0, binaryData);
});
it('should handle resize operation with string dataPropertyName', async () => {
const testBuffer = createTestBuffer();
const items: INodeExecutionData[] = [
{
json: {},
binary: {
image: {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
},
},
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'resize';
if (paramName === 'dataPropertyName') return 'image';
if (paramName === 'width') return 200;
if (paramName === 'height') return 200;
if (paramName === 'resizeOption') return 'maximumArea';
if (paramName === 'options') return {};
return {};
});
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(testBuffer);
mockExecuteFunctions.helpers.prepareBinaryData.mockResolvedValue({
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
});
const result = await editImageNode.execute.call(mockExecuteFunctions);
expect(result[0]).toHaveLength(1);
expect(result[0][0].binary).toHaveProperty('image');
expect(mockExecuteFunctions.helpers.assertBinaryData).toHaveBeenCalledWith(0, 'image');
});
it('should handle resize operation with IBinaryData dataPropertyName', async () => {
const testBuffer = createTestBuffer();
const binaryData = {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
};
const items: INodeExecutionData[] = [
{
json: {},
binary: { data: binaryData },
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'resize';
if (paramName === 'dataPropertyName') return binaryData;
if (paramName === 'width') return 150;
if (paramName === 'height') return 150;
if (paramName === 'resizeOption') return 'ignoreAspectRatio';
if (paramName === 'options') return {};
return {};
});
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(testBuffer);
mockExecuteFunctions.helpers.prepareBinaryData.mockResolvedValue({
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
});
const result = await editImageNode.execute.call(mockExecuteFunctions);
expect(result[0]).toHaveLength(1);
expect(result[0][0].binary).toHaveProperty('data');
expect(mockExecuteFunctions.helpers.assertBinaryData).toHaveBeenCalledWith(0, binaryData);
});
it('should handle shear operation with string dataPropertyName', async () => {
const testBuffer = createTestBuffer();
const items: INodeExecutionData[] = [
{
json: {},
binary: {
myImage: {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
},
},
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'shear';
if (paramName === 'dataPropertyName') return 'myImage';
if (paramName === 'degreesX') return 20;
if (paramName === 'degreesY') return 10;
if (paramName === 'options') return {};
return {};
});
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(testBuffer);
mockExecuteFunctions.helpers.prepareBinaryData.mockResolvedValue({
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
});
const result = await editImageNode.execute.call(mockExecuteFunctions);
expect(result[0]).toHaveLength(1);
expect(result[0][0].binary).toHaveProperty('myImage');
expect(mockExecuteFunctions.helpers.assertBinaryData).toHaveBeenCalledWith(0, 'myImage');
});
it('should handle shear operation with IBinaryData dataPropertyName', async () => {
const testBuffer = createTestBuffer();
const binaryData = {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
};
const items: INodeExecutionData[] = [
{
json: {},
binary: { data: binaryData },
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'shear';
if (paramName === 'dataPropertyName') return binaryData;
if (paramName === 'degreesX') return 15;
if (paramName === 'degreesY') return 5;
if (paramName === 'options') return {};
return {};
});
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(testBuffer);
mockExecuteFunctions.helpers.prepareBinaryData.mockResolvedValue({
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
});
const result = await editImageNode.execute.call(mockExecuteFunctions);
expect(result[0]).toHaveLength(1);
expect(result[0][0].binary).toHaveProperty('data');
expect(mockExecuteFunctions.helpers.assertBinaryData).toHaveBeenCalledWith(0, binaryData);
});
it('should handle transparent operation with string dataPropertyName', async () => {
const testBuffer = createTestBuffer();
const items: INodeExecutionData[] = [
{
json: {},
binary: {
picture: {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
},
},
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'transparent';
if (paramName === 'dataPropertyName') return 'picture';
if (paramName === 'color') return '#ffffff';
if (paramName === 'options') return {};
return {};
});
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(testBuffer);
mockExecuteFunctions.helpers.prepareBinaryData.mockResolvedValue({
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
});
const result = await editImageNode.execute.call(mockExecuteFunctions);
expect(result[0]).toHaveLength(1);
expect(result[0][0].binary).toHaveProperty('picture');
expect(mockExecuteFunctions.helpers.assertBinaryData).toHaveBeenCalledWith(0, 'picture');
});
it('should handle transparent operation with IBinaryData dataPropertyName', async () => {
const testBuffer = createTestBuffer();
const binaryData = {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
};
const items: INodeExecutionData[] = [
{
json: {},
binary: { data: binaryData },
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'transparent';
if (paramName === 'dataPropertyName') return binaryData;
if (paramName === 'color') return '#00ff00';
if (paramName === 'options') return {};
return {};
});
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(testBuffer);
mockExecuteFunctions.helpers.prepareBinaryData.mockResolvedValue({
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
});
const result = await editImageNode.execute.call(mockExecuteFunctions);
expect(result[0]).toHaveLength(1);
expect(result[0][0].binary).toHaveProperty('data');
expect(mockExecuteFunctions.helpers.assertBinaryData).toHaveBeenCalledWith(0, binaryData);
});
});
describe('multiStep operation', () => {
it('should process multiple operations in sequence', async () => {
const testBuffer = createTestBuffer();
const items: INodeExecutionData[] = [
{
json: {},
binary: {
data: {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
},
},
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'multiStep';
if (paramName === 'dataPropertyName') return 'data';
if (paramName === 'operations') {
return {
operations: [
{
operation: 'blur',
blur: 5,
sigma: 2,
},
{
operation: 'rotate',
rotate: 90,
backgroundColor: '#ffffff',
},
],
};
}
if (paramName === 'options') return {};
return {};
});
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(testBuffer);
mockExecuteFunctions.helpers.prepareBinaryData.mockResolvedValue({
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
});
const result = await editImageNode.execute.call(mockExecuteFunctions);
expect(result[0]).toHaveLength(1);
expect(result[0][0].binary).toHaveProperty('data');
});
});
describe('autoOrient', () => {
it('should call autoOrient when loading an existing image', async () => {
const testBuffer = createTestBuffer();
const items: INodeExecutionData[] = [
{
json: {},
binary: {
data: {
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
fileName: 'test.png',
},
},
},
];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'blur';
if (paramName === 'dataPropertyName') return 'data';
if (paramName === 'blur') return 5;
if (paramName === 'sigma') return 2;
return {};
});
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(testBuffer);
mockExecuteFunctions.helpers.prepareBinaryData.mockResolvedValue({
data: testBuffer.toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
});
await editImageNode.execute.call(mockExecuteFunctions);
expect(mockGmInstance.autoOrient).toHaveBeenCalled();
expect(mockGmInstance.out).toHaveBeenCalledWith('-orient', 'TopLeft');
});
it('should not call autoOrient for create operation', async () => {
const items: INodeExecutionData[] = [{ json: {} }];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'create';
if (paramName === 'dataPropertyName') return 'data';
if (paramName === 'width') return 100;
if (paramName === 'height') return 100;
if (paramName === 'backgroundColor') return 'white';
return {};
});
mockExecuteFunctions.helpers.prepareBinaryData.mockResolvedValue({
data: createTestBuffer().toString('base64'),
mimeType: 'image/png',
fileExtension: 'png',
});
await editImageNode.execute.call(mockExecuteFunctions);
expect(mockGmInstance.autoOrient).not.toHaveBeenCalled();
expect(mockGmInstance.out).not.toHaveBeenCalledWith('-orient', 'TopLeft');
});
});
});