From 28b7ef2593c37a71195ab6a89faabb99388dfea8 Mon Sep 17 00:00:00 2001 From: "n8n-cat-bot[bot]" <283985454+n8n-cat-bot[bot]@users.noreply.github.com> Date: Mon, 25 May 2026 18:09:42 +0100 Subject: [PATCH] test(Harvest Node): Add unit tests for User resource (#31083) Co-authored-by: n8n-cat-bot[bot] Co-authored-by: Claude Opus 4.7 Co-authored-by: Declan Carroll --- .../nodes/Harvest/test/Harvest.node.test.ts | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 packages/nodes-base/nodes/Harvest/test/Harvest.node.test.ts diff --git a/packages/nodes-base/nodes/Harvest/test/Harvest.node.test.ts b/packages/nodes-base/nodes/Harvest/test/Harvest.node.test.ts new file mode 100644 index 00000000000..791e3d15db0 --- /dev/null +++ b/packages/nodes-base/nodes/Harvest/test/Harvest.node.test.ts @@ -0,0 +1,151 @@ +import type { MockProxy } from 'jest-mock-extended'; +import { mock } from 'jest-mock-extended'; +import type { + IDataObject, + IExecuteFunctions, + INode, + INodeExecutionData, + IPairedItemData, +} from 'n8n-workflow'; +import { NodeApiError } from 'n8n-workflow'; + +import * as GenericFunctions from '../GenericFunctions'; +import { Harvest } from '../Harvest.node'; + +jest.mock('../GenericFunctions'); + +describe('Harvest Node', () => { + let harvest: Harvest; + let mockExecuteFunctions: MockProxy; + + const mockHarvestApiRequest = GenericFunctions.harvestApiRequest as jest.MockedFunction< + typeof GenericFunctions.harvestApiRequest + >; + const mockGetAllResource = GenericFunctions.getAllResource as jest.MockedFunction< + typeof GenericFunctions.getAllResource + >; + + const mockNode: INode = { + id: 'test-node-id', + name: 'Harvest', + type: 'n8n-nodes-base.harvest', + typeVersion: 1, + position: [0, 0], + parameters: {}, + }; + + beforeEach(() => { + harvest = new Harvest(); + mockExecuteFunctions = mock({ + helpers: { + constructExecutionMetaData: jest.fn( + ( + data: INodeExecutionData[], + options: { itemData: IPairedItemData | IPairedItemData[] }, + ) => { + const itemIndex = + (Array.isArray(options?.itemData) + ? options.itemData[0]?.item + : options?.itemData?.item) ?? 0; + return data.map((item) => ({ ...item, pairedItem: { item: itemIndex } })); + }, + ), + returnJsonArray: jest.fn((data: IDataObject | IDataObject[]) => + Array.isArray(data) ? data.map((d) => ({ json: d })) : [{ json: data }], + ), + }, + }); + + jest.clearAllMocks(); + + mockExecuteFunctions.getInputData.mockReturnValue([{ json: {} }]); + mockExecuteFunctions.getNode.mockReturnValue(mockNode); + mockExecuteFunctions.continueOnFail.mockReturnValue(false); + }); + + describe('User resource', () => { + it('should return all users on getAll', async () => { + mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => { + const params: Record = { + resource: 'user', + operation: 'getAll', + }; + return params[paramName]; + }); + + const mockUsers = [ + { id: 1, first_name: 'Alice', last_name: 'Anderson', email: 'alice@example.com' }, + { id: 2, first_name: 'Bob', last_name: 'Brown', email: 'bob@example.com' }, + ]; + mockGetAllResource.mockResolvedValue(mockUsers); + + const result = await harvest.execute.call(mockExecuteFunctions); + + expect(mockGetAllResource).toHaveBeenCalledWith('users', 0); + expect(result).toEqual([ + [ + { json: mockUsers[0], pairedItem: { item: 0 } }, + { json: mockUsers[1], pairedItem: { item: 0 } }, + ], + ]); + }); + + it('should call the users/{id} endpoint on get', async () => { + mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => { + const params: Record = { + resource: 'user', + operation: 'get', + id: '42', + }; + return params[paramName]; + }); + + const mockUser = { id: 42, first_name: 'Carol', last_name: 'Clarke' }; + mockHarvestApiRequest.mockResolvedValue(mockUser); + + const result = await harvest.execute.call(mockExecuteFunctions); + + expect(mockHarvestApiRequest).toHaveBeenCalledWith('GET', {}, 'users/42'); + expect(result).toEqual([[{ json: mockUser, pairedItem: { item: 0 } }]]); + }); + }); + + describe('Error handling', () => { + it('should propagate a NodeApiError from the API request', async () => { + mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => { + const params: Record = { + resource: 'user', + operation: 'get', + id: '999', + }; + return params[paramName]; + }); + + const apiError = new NodeApiError(mockNode, { + message: 'Unauthorized', + httpCode: '401', + }); + mockHarvestApiRequest.mockRejectedValue(apiError); + + await expect(harvest.execute.call(mockExecuteFunctions)).rejects.toThrow(NodeApiError); + }); + + it('should surface the error on the item when continueOnFail is true', async () => { + mockExecuteFunctions.continueOnFail.mockReturnValue(true); + mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => { + const params: Record = { + resource: 'user', + operation: 'get', + id: '999', + }; + return params[paramName]; + }); + + mockHarvestApiRequest.mockRejectedValue(new Error('User not found')); + + const result = await harvest.execute.call(mockExecuteFunctions); + + expect(result).toEqual([[{ json: { error: 'User not found' }, pairedItem: { item: 0 } }]]); + }); + }); +});