diff --git a/packages/nodes-base/nodes/LinkedIn/LinkedIn.node.test.ts b/packages/nodes-base/nodes/LinkedIn/LinkedIn.node.test.ts new file mode 100644 index 00000000000..69ae94baa62 --- /dev/null +++ b/packages/nodes-base/nodes/LinkedIn/LinkedIn.node.test.ts @@ -0,0 +1,69 @@ +import type { IExecuteFunctions, INode } from 'n8n-workflow'; +import type { MockProxy } from 'vitest-mock-extended'; +import { mock } from 'vitest-mock-extended'; + +import * as GenericFunctions from './GenericFunctions'; +import { LinkedIn } from './LinkedIn.node'; + +vi.mock('./GenericFunctions'); + +describe('LinkedIn node', () => { + const node = mock({ + id: 'linked-in-node', + name: 'LinkedIn', + type: 'n8n-nodes-base.linkedIn', + typeVersion: 1, + }); + const linkedInApiRequest = vi.mocked(GenericFunctions.linkedInApiRequest); + let executeFunctions: MockProxy; + + beforeEach(() => { + vi.clearAllMocks(); + executeFunctions = mock({ + helpers: { + constructExecutionMetaData: vi.fn(() => []), + returnJsonArray: vi.fn(() => []), + }, + }); + executeFunctions.getInputData.mockReturnValue([{ json: {} }]); + executeFunctions.getNode.mockReturnValue(node); + executeFunctions.getNodeParameter.calledWith('resource', 0).mockReturnValue('post'); + executeFunctions.getNodeParameter.calledWith('operation', 0).mockReturnValue('create'); + executeFunctions.getNodeParameter.calledWith('shareMediaCategory', 0).mockReturnValue('NONE'); + executeFunctions.getNodeParameter.calledWith('postAs', 0).mockReturnValue('person'); + executeFunctions.getNodeParameter.calledWith('additionalFields', 0).mockReturnValue({}); + executeFunctions.getNodeParameter.calledWith('person', 0).mockReturnValue('person-id'); + executeFunctions.continueOnFail.mockReturnValue(false); + linkedInApiRequest.mockResolvedValue({ urn: 'post-id' }); + }); + + it('escapes special characters in post text', async () => { + executeFunctions.getNodeParameter.calledWith('text', 0).mockReturnValue('Hello @world'); + + await new LinkedIn().execute.call(executeFunctions); + + expect(linkedInApiRequest).toHaveBeenCalledWith( + 'POST', + '/posts', + expect.objectContaining({ commentary: 'Hello \\@world' }), + ); + }); + + it('rejects undefined post text before making an API request', async () => { + executeFunctions.getNodeParameter.calledWith('text', 0).mockReturnValue(undefined); + + await expect(new LinkedIn().execute.call(executeFunctions)).rejects.toThrow( + 'Parameter "text" is not string', + ); + expect(linkedInApiRequest).not.toHaveBeenCalled(); + }); + + it('rejects empty post text before making an API request', async () => { + executeFunctions.getNodeParameter.calledWith('text', 0).mockReturnValue(''); + + await expect(new LinkedIn().execute.call(executeFunctions)).rejects.toThrow( + 'The Text field is empty. Enter text to publish the post.', + ); + expect(linkedInApiRequest).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/nodes-base/nodes/LinkedIn/LinkedIn.node.ts b/packages/nodes-base/nodes/LinkedIn/LinkedIn.node.ts index ad6ebe3f211..eb830581599 100644 --- a/packages/nodes-base/nodes/LinkedIn/LinkedIn.node.ts +++ b/packages/nodes-base/nodes/LinkedIn/LinkedIn.node.ts @@ -7,7 +7,7 @@ import type { INodeType, INodeTypeDescription, } from 'n8n-workflow'; -import { NodeConnectionTypes } from 'n8n-workflow'; +import { assertParamIsString, NodeConnectionTypes, NodeOperationError } from 'n8n-workflow'; import { linkedInApiRequest } from './GenericFunctions'; import { postFields, postOperations } from './PostDescription'; @@ -133,7 +133,15 @@ export class LinkedIn implements INodeType { try { if (resource === 'post') { if (operation === 'create') { - let text = this.getNodeParameter('text', i) as string; + let text = this.getNodeParameter('text', i); + assertParamIsString('text', text, this.getNode()); + if (text.trim() === '') { + throw new NodeOperationError( + this.getNode(), + 'The Text field is empty. Enter text to publish the post.', + { itemIndex: i, level: 'info' }, + ); + } const shareMediaCategory = this.getNodeParameter('shareMediaCategory', i) as string; const postAs = this.getNodeParameter('postAs', i) as string; const additionalFields = this.getNodeParameter('additionalFields', i);