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(); 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'); }); }); });