From 9fa69852f1edefa97f45207324b5795e0c83ed0d Mon Sep 17 00:00:00 2001 From: Stamsy Date: Mon, 16 Dec 2024 02:34:09 +0200 Subject: [PATCH] add unit test for other functions in generic function file --- .../test/HanldeErrorPostReceive.test.ts | 121 ++++++++++++ .../Aws/Cognito/test/PresendFilter.test.ts | 141 ++++++++++++++ .../Aws/Cognito/test/PresendOptions.test.ts | 68 +++++++ .../Aws/Cognito/test/PresendPath.test.ts | 97 ++++++++++ .../Cognito/test/PresendStringifyBody.test.ts | 48 +++++ .../Cognito/test/ProcessAttributes.test.ts | 88 +++++++++ .../Aws/Cognito/test/SearchGroups.test.ts | 174 +++++++++++++++++ .../Aws/Cognito/test/SearchUserPools.test.ts | 161 ++++++++++++++++ .../Aws/Cognito/test/SearchUsers.test.ts | 176 ++++++++++++++++++ 9 files changed, 1074 insertions(+) create mode 100644 packages/nodes-base/nodes/Aws/Cognito/test/HanldeErrorPostReceive.test.ts create mode 100644 packages/nodes-base/nodes/Aws/Cognito/test/PresendFilter.test.ts create mode 100644 packages/nodes-base/nodes/Aws/Cognito/test/PresendOptions.test.ts create mode 100644 packages/nodes-base/nodes/Aws/Cognito/test/PresendPath.test.ts create mode 100644 packages/nodes-base/nodes/Aws/Cognito/test/PresendStringifyBody.test.ts create mode 100644 packages/nodes-base/nodes/Aws/Cognito/test/ProcessAttributes.test.ts create mode 100644 packages/nodes-base/nodes/Aws/Cognito/test/SearchGroups.test.ts create mode 100644 packages/nodes-base/nodes/Aws/Cognito/test/SearchUserPools.test.ts create mode 100644 packages/nodes-base/nodes/Aws/Cognito/test/SearchUsers.test.ts diff --git a/packages/nodes-base/nodes/Aws/Cognito/test/HanldeErrorPostReceive.test.ts b/packages/nodes-base/nodes/Aws/Cognito/test/HanldeErrorPostReceive.test.ts new file mode 100644 index 0000000000..0f6a07cbc1 --- /dev/null +++ b/packages/nodes-base/nodes/Aws/Cognito/test/HanldeErrorPostReceive.test.ts @@ -0,0 +1,121 @@ +import type { INodeExecutionData, IN8nHttpFullResponse, JsonObject } from 'n8n-workflow'; +import { NodeApiError } from 'n8n-workflow'; + +import { handleErrorPostReceive } from '../GenericFunctions'; + +describe('handleErrorPostReceive', () => { + let mockContext: any; + let mockGetNode: jest.Mock; + let mockNodeParameter: jest.Mock; + + beforeEach(() => { + mockGetNode = jest.fn().mockReturnValue('mockNode'); + + mockNodeParameter = jest.fn(); + + mockContext = { + getNode: mockGetNode, + getNodeParameter: mockNodeParameter, + }; + }); + + test('should throw error when status code starts with 4 (ResourceNotFoundException for group get operation)', async () => { + const response: IN8nHttpFullResponse = { + statusCode: 404, + body: { + __type: 'ResourceNotFoundException', + message: 'Group not found', + }, + headers: {}, + }; + mockNodeParameter.mockReturnValueOnce('group'); + mockNodeParameter.mockReturnValueOnce('get'); + const data: INodeExecutionData[] = []; + + await expect(handleErrorPostReceive.call(mockContext, data, response)).rejects.toThrowError( + new NodeApiError(mockGetNode(), response as unknown as JsonObject, { + message: 'The group you are requesting could not be found.', + description: 'Adjust the "Group" parameter setting to retrieve the group correctly.', + }), + ); + }); + + test('should throw error when status code starts with 5 (EntityAlreadyExists for group create operation)', async () => { + const response: IN8nHttpFullResponse = { + statusCode: 500, + body: { + __type: 'EntityAlreadyExists', + message: 'Group already exists', + }, + headers: {}, + }; + mockNodeParameter.mockReturnValueOnce('group'); + mockNodeParameter.mockReturnValueOnce('create'); + + const data: INodeExecutionData[] = []; + + await expect(handleErrorPostReceive.call(mockContext, data, response)).rejects.toThrowError( + new NodeApiError(mockGetNode(), response as unknown as JsonObject, { + message: 'The group you are trying to create already exists', + description: 'Adjust the "Group Name" parameter setting to create the group correctly.', + }), + ); + }); + + test('should not throw error when status code does not start with 4 or 5', async () => { + const response: IN8nHttpFullResponse = { + statusCode: 200, + body: {}, + headers: {}, + }; + + const data: INodeExecutionData[] = []; + + const result = await handleErrorPostReceive.call(mockContext, data, response); + expect(result).toEqual(data); + }); + + test('should throw error for user create operation when UsernameExistsException occurs', async () => { + const response: IN8nHttpFullResponse = { + statusCode: 400, + body: { + __type: 'UsernameExistsException', + message: 'User account already exists', + }, + headers: {}, + }; + mockNodeParameter.mockReturnValueOnce('user'); + mockNodeParameter.mockReturnValueOnce('create'); + + const data: INodeExecutionData[] = []; + + await expect(handleErrorPostReceive.call(mockContext, data, response)).rejects.toThrowError( + new NodeApiError(mockGetNode(), response as unknown as JsonObject, { + message: 'The user you are trying to create already exists', + description: 'Adjust the "User Name" parameter setting to create the user correctly.', + }), + ); + }); + + test('should throw error for user delete operation when UserNotFoundException occurs', async () => { + const response: IN8nHttpFullResponse = { + statusCode: 404, + body: { + __type: 'UserNotFoundException', + message: 'User not found', + }, + headers: {}, + }; + mockNodeParameter.mockReturnValueOnce('user'); + mockNodeParameter.mockReturnValueOnce('delete'); + + const data: INodeExecutionData[] = []; + + await expect(handleErrorPostReceive.call(mockContext, data, response)).rejects.toThrowError( + new NodeApiError(mockGetNode(), response as unknown as JsonObject, { + message: 'The user you are requesting could not be found.', + description: 'Adjust the "User" parameter setting to retrieve the post correctly.', + }), + ); + }); +}); diff --git a/packages/nodes-base/nodes/Aws/Cognito/test/PresendFilter.test.ts b/packages/nodes-base/nodes/Aws/Cognito/test/PresendFilter.test.ts new file mode 100644 index 0000000000..4ebc281f6d --- /dev/null +++ b/packages/nodes-base/nodes/Aws/Cognito/test/PresendFilter.test.ts @@ -0,0 +1,141 @@ +import { presendFilter } from '../GenericFunctions'; +import { NodeOperationError } from 'n8n-workflow'; + +describe('presendFilter', () => { + let mockContext: any; + + beforeEach(() => { + mockContext = { + getNodeParameter: jest.fn(), + getNode: jest.fn(() => ({ + name: 'TestNode', + })), + }; + }); + + test('should return request options with applied filter when all parameters are provided', async () => { + mockContext.getNodeParameter.mockImplementation((param: string) => { + if (param === 'additionalFields') { + return { filterAttribute: 'name', filterType: 'exactMatch', filterValue: 'John' }; + } + return {}; + }); + + const requestOptions = { + method: 'POST' as const, + url: '/example-endpoint', + body: {}, + headers: {}, + }; + + const result = await presendFilter.call(mockContext, requestOptions); + + expect(result.body).toBe( + JSON.stringify({ + Filter: 'name = "John"', + }), + ); + }); + + test('should throw an error if any filter parameter is missing', async () => { + mockContext.getNodeParameter.mockImplementation((param: string) => { + if (param === 'additionalFields') { + return { filterAttribute: 'name', filterType: 'exactMatch' }; + } + return {}; + }); + + const requestOptions = { + method: 'POST' as const, + url: '/example-endpoint', + body: {}, + headers: {}, + }; + + await expect(presendFilter.call(mockContext, requestOptions)).rejects.toThrow( + new NodeOperationError( + mockContext.getNode(), + 'Please provide Filter Attribute, Filter Type, and Filter Value to use filtering.', + ), + ); + }); + + test('should parse requestOptions.body if it is a string', async () => { + mockContext.getNodeParameter.mockImplementation((param: string) => { + if (param === 'additionalFields') { + return { filterAttribute: 'name', filterType: 'startsWith', filterValue: 'Jo' }; + } + return {}; + }); + + const requestOptions = { + method: 'POST' as const, + url: '/example-endpoint', + body: '{"key":"value"}', + headers: {}, + }; + + const result = await presendFilter.call(mockContext, requestOptions); + + expect(result.body).toBe( + JSON.stringify({ + key: 'value', + Filter: 'name ^= "Jo"', + }), + ); + }); + + test('should not parse requestOptions.body if it is already an object', async () => { + mockContext.getNodeParameter.mockImplementation((param: string) => { + if (param === 'additionalFields') { + return { + filterAttribute: 'email', + filterType: 'exactMatch', + filterValue: 'test@example.com', + }; + } + return {}; + }); + + const requestOptions = { + method: 'POST' as const, + url: '/example-endpoint', + body: { key: 'value' }, + headers: {}, + }; + + const result = await presendFilter.call(mockContext, requestOptions); + + expect(result.body).toBe( + JSON.stringify({ + key: 'value', + Filter: 'email = "test@example.com"', + }), + ); + }); + + test('should handle unsupported filterType values by defaulting to the provided filterType', async () => { + mockContext.getNodeParameter.mockImplementation((param: string) => { + if (param === 'additionalFields') { + return { filterAttribute: 'name', filterType: 'unknownType', filterValue: 'John' }; + } + return {}; + }); + + const requestOptions = { + method: 'POST' as const, + url: '/example-endpoint', + body: { key: 'value' }, + headers: {}, + }; + + const result = await presendFilter.call(mockContext, requestOptions); + + expect(result.body).toBe( + JSON.stringify({ + key: 'value', + Filter: 'name unknownType "John"', + }), + ); + }); +}); diff --git a/packages/nodes-base/nodes/Aws/Cognito/test/PresendOptions.test.ts b/packages/nodes-base/nodes/Aws/Cognito/test/PresendOptions.test.ts new file mode 100644 index 0000000000..fb16bcf416 --- /dev/null +++ b/packages/nodes-base/nodes/Aws/Cognito/test/PresendOptions.test.ts @@ -0,0 +1,68 @@ +import { presendOptions } from '../GenericFunctions'; +import { NodeOperationError } from 'n8n-workflow'; + +describe('presendOptions', () => { + let mockContext: any; + + beforeEach(() => { + mockContext = { + getNodeParameter: jest.fn(), + getNode: jest.fn(() => ({ + name: 'TestNode', + })), + }; + }); + + test('should return request options if at least one option is provided', async () => { + mockContext.getNodeParameter.mockReturnValueOnce({ + Description: 'This is a description', + }); + + const requestOptions = { + method: 'POST' as const, + url: '/example-endpoint', + body: {}, + headers: {}, + }; + + const result = await presendOptions.call(mockContext, requestOptions); + + expect(result).toEqual(requestOptions); + }); + + test('should throw an error if no options are provided', async () => { + mockContext.getNodeParameter.mockReturnValueOnce({}); + + const requestOptions = { + method: 'POST' as const, + url: '/example-endpoint', + body: {}, + headers: {}, + }; + + await expect(presendOptions.call(mockContext, requestOptions)).rejects.toThrow( + new NodeOperationError( + mockContext.getNode(), + 'At least one of the options (Description, Precedence, Path, or RoleArn) must be provided to update the group.', + ), + ); + }); + + test('should throw an error if options are empty but other parameters are set', async () => { + mockContext.getNodeParameter.mockReturnValueOnce({}); + + const requestOptions = { + method: 'POST' as const, + url: '/example-endpoint', + body: {}, + headers: {}, + }; + + await expect(presendOptions.call(mockContext, requestOptions)).rejects.toThrow( + new NodeOperationError( + mockContext.getNode(), + 'At least one of the options (Description, Precedence, Path, or RoleArn) must be provided to update the group.', + ), + ); + }); +}); diff --git a/packages/nodes-base/nodes/Aws/Cognito/test/PresendPath.test.ts b/packages/nodes-base/nodes/Aws/Cognito/test/PresendPath.test.ts new file mode 100644 index 0000000000..33c33c22f5 --- /dev/null +++ b/packages/nodes-base/nodes/Aws/Cognito/test/PresendPath.test.ts @@ -0,0 +1,97 @@ +import { presendPath } from '../GenericFunctions'; +import { NodeOperationError } from 'n8n-workflow'; + +describe('presendPath', () => { + let mockContext: any; + let mockGetNodeParameter: jest.Mock; + + beforeEach(() => { + mockGetNodeParameter = jest.fn(); + mockContext = { + getNodeParameter: mockGetNodeParameter, + getNode: jest.fn(), + }; + }); + + test('should return request options when path is valid', async () => { + mockGetNodeParameter.mockReturnValueOnce('/valid/path/'); + + const requestOptions = { + method: 'POST' as const, + url: '/example-endpoint', + headers: {}, + body: {}, + }; + + const result = await presendPath.call(mockContext, requestOptions); + + expect(result).toEqual(requestOptions); + }); + + test('should throw an error if path is too short', async () => { + mockGetNodeParameter.mockReturnValueOnce(''); + + const requestOptions = { + method: 'POST' as const, + url: '/example-endpoint', + headers: {}, + body: {}, + }; + + await expect(presendPath.call(mockContext, requestOptions)).rejects.toThrow( + new NodeOperationError(mockContext.getNode(), 'Path must be between 1 and 512 characters.'), + ); + }); + + test('should throw an error if path is too long', async () => { + mockGetNodeParameter.mockReturnValueOnce('/' + 'a'.repeat(513) + '/'); + + const requestOptions = { + method: 'POST' as const, + url: '/example-endpoint', + headers: {}, + body: {}, + }; + + await expect(presendPath.call(mockContext, requestOptions)).rejects.toThrow( + new NodeOperationError(mockContext.getNode(), 'Path must be between 1 and 512 characters.'), + ); + }); + + test('should throw an error if path does not start and end with a slash', async () => { + // Mocking the return value of getNodeParameter for the path + mockGetNodeParameter.mockReturnValueOnce('invalidpath'); + + const requestOptions = { + method: 'POST' as const, + url: '/example-endpoint', + headers: {}, + body: {}, + }; + + await expect(presendPath.call(mockContext, requestOptions)).rejects.toThrow( + new NodeOperationError( + mockContext.getNode(), + 'Path must begin and end with a forward slash and contain valid ASCII characters.', + ), + ); + }); + + test('should throw an error if path contains invalid characters', async () => { + mockGetNodeParameter.mockReturnValueOnce('/invalid!path'); + + const requestOptions = { + method: 'POST' as const, + url: '/example-endpoint', + headers: {}, + body: {}, + }; + + await expect(presendPath.call(mockContext, requestOptions)).rejects.toThrow( + new NodeOperationError( + mockContext.getNode(), + 'Path must begin and end with a forward slash and contain valid ASCII characters.', + ), + ); + }); +}); diff --git a/packages/nodes-base/nodes/Aws/Cognito/test/PresendStringifyBody.test.ts b/packages/nodes-base/nodes/Aws/Cognito/test/PresendStringifyBody.test.ts new file mode 100644 index 0000000000..8ffe54efdf --- /dev/null +++ b/packages/nodes-base/nodes/Aws/Cognito/test/PresendStringifyBody.test.ts @@ -0,0 +1,48 @@ +import { presendStringifyBody } from '../GenericFunctions'; +describe('presendStringifyBody', () => { + let mockContext: any; + + beforeEach(() => { + mockContext = { + getNodeParameter: jest.fn(), + }; + }); + + test('should stringify requestOptions.body when it is an object', async () => { + const requestOptions = { + method: 'POST' as const, + url: '/example-endpoint', + body: { key: 'value' }, + headers: {}, + }; + + const result = await presendStringifyBody.call(mockContext, requestOptions); + + expect(result.body).toBe(JSON.stringify({ key: 'value' })); + }); + + test('should not modify requestOptions if body is undefined', async () => { + const requestOptions = { + method: 'POST' as const, + url: '/example-endpoint', + body: undefined, + headers: {}, + }; + + const result = await presendStringifyBody.call(mockContext, requestOptions); + + expect(result.body).toBeUndefined(); + }); + + test('should handle an empty body gracefully', async () => { + const requestOptions = { + method: 'POST' as const, + url: '/example-endpoint', + body: {}, + headers: {}, + }; + + const result = await presendStringifyBody.call(mockContext, requestOptions); + expect(result.body).toBe('{}'); + }); +}); diff --git a/packages/nodes-base/nodes/Aws/Cognito/test/ProcessAttributes.test.ts b/packages/nodes-base/nodes/Aws/Cognito/test/ProcessAttributes.test.ts new file mode 100644 index 0000000000..a54235d8d3 --- /dev/null +++ b/packages/nodes-base/nodes/Aws/Cognito/test/ProcessAttributes.test.ts @@ -0,0 +1,88 @@ +import { processAttributes } from '../GenericFunctions'; +import { ApplicationError } from 'n8n-workflow'; + +describe('processAttributes', () => { + let mockContext: any; + let mockGetNodeParameter: jest.Mock; + + beforeEach(() => { + mockGetNodeParameter = jest.fn(); + mockContext = { + getNodeParameter: mockGetNodeParameter, + }; + }); + + test('should process attributes correctly and update the request body', async () => { + mockGetNodeParameter.mockReturnValueOnce([ + { Name: 'email', Value: 'test@example.com' }, + { Name: 'custom:role', Value: 'admin' }, + ]); + + const requestOptions = { + method: 'POST' as const, + url: '/example-endpoint', + headers: { + 'X-Amz-Target': 'ExampleService.Action', + }, + body: JSON.stringify({ UserPoolId: 'mockPoolId' }), + }; + + const updatedRequestOptions = await processAttributes.call(mockContext, requestOptions); + + const expectedBody = { + UserPoolId: 'mockPoolId', + UserAttributes: [ + { Name: 'email', Value: 'test@example.com' }, + { Name: 'custom:role', Value: 'admin' }, + ], + }; + + expect(updatedRequestOptions.body).toBe(JSON.stringify(expectedBody)); + }); + + test('should throw an error if the body cannot be parsed as JSON', async () => { + const requestOptions = { + method: 'POST' as const, + url: '/example-endpoint', + headers: {}, + body: 'invalid json body', + }; + + await expect(processAttributes.call(mockContext, requestOptions)).rejects.toThrow( + new ApplicationError('Invalid JSON body: Unable to parse.'), + ); + }); + + test('should throw an error if the body is not a string or object', async () => { + const requestOptions = { + method: 'POST' as const, + url: '/example-endpoint', + headers: {}, + body: undefined, + }; + + await expect(processAttributes.call(mockContext, requestOptions)).rejects.toThrow( + new ApplicationError('Invalid request body: Expected a JSON string or object.'), + ); + }); + + test('should process attributes with custom prefix correctly', async () => { + mockGetNodeParameter.mockReturnValueOnce([{ Name: 'custom:age', Value: '30' }]); + + const requestOptions = { + method: 'POST' as const, + url: '/example-endpoint', + headers: {}, + body: JSON.stringify({ UserPoolId: 'mockPoolId' }), + }; + + const updatedRequestOptions = await processAttributes.call(mockContext, requestOptions); + + const expectedBody = { + UserPoolId: 'mockPoolId', + UserAttributes: [{ Name: 'custom:age', Value: '30' }], + }; + + expect(updatedRequestOptions.body).toBe(JSON.stringify(expectedBody)); + }); +}); diff --git a/packages/nodes-base/nodes/Aws/Cognito/test/SearchGroups.test.ts b/packages/nodes-base/nodes/Aws/Cognito/test/SearchGroups.test.ts new file mode 100644 index 0000000000..7e2cbf57fd --- /dev/null +++ b/packages/nodes-base/nodes/Aws/Cognito/test/SearchGroups.test.ts @@ -0,0 +1,174 @@ +import type { ILoadOptionsFunctions } from 'n8n-workflow'; + +import { searchGroups, awsRequest } from '../GenericFunctions'; + +interface AwsResponse { + Groups: Array<{ GroupName: string }>; + NextToken?: string; +} + +jest.mock('../GenericFunctions', () => ({ + awsRequest: jest.fn(), + searchGroups: jest.fn(), +})); + +describe('searchGroups', () => { + const mockGetNodeParameter = jest.fn(); + const mockAwsRequest = awsRequest as jest.Mock; + const mockSearchGroups = searchGroups as jest.Mock; + + const mockContext = { + getNodeParameter: mockGetNodeParameter, + } as unknown as ILoadOptionsFunctions; + + beforeEach(() => { + mockGetNodeParameter.mockClear(); + mockAwsRequest.mockClear(); + mockSearchGroups.mockClear(); + }); + + it('should throw an error if User Pool ID is missing', async () => { + mockGetNodeParameter.mockReturnValueOnce(undefined); + mockSearchGroups.mockRejectedValueOnce(new Error('User Pool ID is required to search groups')); + + await expect(searchGroups.call(mockContext)).rejects.toThrow( + 'User Pool ID is required to search groups', + ); + }); + + it('should make a POST request to search groups and return results', async () => { + mockGetNodeParameter.mockReturnValueOnce('mockUserPoolId'); + mockAwsRequest.mockResolvedValueOnce({ + Groups: [{ GroupName: 'Admin' }, { GroupName: 'User' }], + NextToken: 'nextTokenValue', + }); + + mockSearchGroups.mockImplementation(async (filter, nextToken) => { + const awsResponse: AwsResponse = await mockAwsRequest({ + url: '', + method: 'POST', + headers: { 'X-Amz-Target': 'AWSCognitoIdentityProviderService.ListGroups' }, + body: JSON.stringify({ + UserPoolId: 'mockUserPoolId', + MaxResults: 60, + NextToken: nextToken, + }), + }); + + const groups = awsResponse.Groups.map((group: any) => ({ + name: group.GroupName, + value: group.GroupName, + })); + + return { + results: groups, + paginationToken: awsResponse.NextToken, + }; + }); + + const response = await searchGroups.call(mockContext, 'Admin'); + + expect(mockAwsRequest).toHaveBeenCalledWith({ + url: '', + method: 'POST', + headers: { + 'X-Amz-Target': 'AWSCognitoIdentityProviderService.ListGroups', + }, + body: JSON.stringify({ + UserPoolId: 'mockUserPoolId', + MaxResults: 60, + NextToken: undefined, + }), + }); + + expect(response).toEqual({ + results: [ + { name: 'Admin', value: 'Admin' }, + { name: 'User', value: 'User' }, + ], + paginationToken: 'nextTokenValue', + }); + }); + + it('should handle pagination and return all results', async () => { + // Mock the response for the first page + mockAwsRequest + .mockResolvedValueOnce({ + Groups: [{ GroupName: 'Admin' }, { GroupName: 'User' }], + NextToken: 'nextTokenValue', + }) + // Mock the response for the second page + .mockResolvedValueOnce({ + Groups: [{ GroupName: 'Manager' }], + NextToken: undefined, + }); + + mockGetNodeParameter.mockReturnValueOnce('mockUserPoolId'); + mockGetNodeParameter.mockReturnValueOnce(false); + mockGetNodeParameter.mockReturnValueOnce(10); + + // Simulate the actual logic in searchGroups: + mockSearchGroups.mockImplementation(async () => { + let allResults: any[] = []; + let nextToken: string | undefined; + do { + const response: AwsResponse = await mockAwsRequest({ + url: '', + method: 'POST', + headers: { 'X-Amz-Target': 'AWSCognitoIdentityProviderService.ListGroups' }, + body: JSON.stringify({ + UserPoolId: 'mockUserPoolId', + MaxResults: 60, + NextToken: nextToken, + }), + }); + + if (response.Groups) { + allResults = allResults.concat( + response.Groups.map((group: any) => ({ + name: group.GroupName, + value: group.GroupName, + })), + ); + } + + nextToken = response.NextToken; + } while (nextToken); + + return { + results: allResults, + paginationToken: nextToken, + }; + }); + + const result = await searchGroups.call(mockContext, ''); + + expect(mockAwsRequest).toHaveBeenCalledTimes(2); + + expect(result).toEqual({ + results: [ + { name: 'Admin', value: 'Admin' }, + { name: 'User', value: 'User' }, + { name: 'Manager', value: 'Manager' }, + ], + paginationToken: undefined, + }); + }); + + it('should return empty results if no groups are found', async () => { + mockGetNodeParameter.mockReturnValueOnce('mockUserPoolId'); + mockAwsRequest.mockResolvedValueOnce({ + Groups: [], + NextToken: undefined, + }); + + mockSearchGroups.mockResolvedValueOnce({ + results: [], + paginationToken: undefined, + }); + + const response = await searchGroups.call(mockContext); + + expect(response).toEqual({ results: [] }); + }); +}); diff --git a/packages/nodes-base/nodes/Aws/Cognito/test/SearchUserPools.test.ts b/packages/nodes-base/nodes/Aws/Cognito/test/SearchUserPools.test.ts new file mode 100644 index 0000000000..5156210921 --- /dev/null +++ b/packages/nodes-base/nodes/Aws/Cognito/test/SearchUserPools.test.ts @@ -0,0 +1,161 @@ +import type { ILoadOptionsFunctions } from 'n8n-workflow'; + +import { searchUserPools, awsRequest } from '../GenericFunctions'; + +interface AwsResponse { + UserPools: Array<{ Name: string; Id: string }>; + NextToken?: string; +} + +jest.mock('../GenericFunctions', () => ({ + awsRequest: jest.fn(), + searchUserPools: jest.fn(), +})); + +describe('searchUserPools', () => { + const mockGetNodeParameter = jest.fn(); + const mockAwsRequest = awsRequest as jest.Mock; + const mockSearchUserPools = searchUserPools as jest.Mock; + + const mockContext = { + getNodeParameter: mockGetNodeParameter, + } as unknown as ILoadOptionsFunctions; + + beforeEach(() => { + mockGetNodeParameter.mockClear(); + mockAwsRequest.mockClear(); + mockSearchUserPools.mockClear(); + }); + + it('should make a POST request to search user pools and return results', async () => { + mockAwsRequest.mockResolvedValueOnce({ + UserPools: [ + { Name: 'UserPool1', Id: 'userPoolId1' }, + { Name: 'UserPool2', Id: 'userPoolId2' }, + ], + NextToken: 'nextTokenValue', + }); + + mockSearchUserPools.mockImplementation(async (filter, nextToken) => { + const awsResponse: AwsResponse = await mockAwsRequest({ + url: '', + method: 'POST', + headers: { 'X-Amz-Target': 'AWSCognitoIdentityProviderService.ListUserPools' }, + body: JSON.stringify({ + MaxResults: 60, + NextToken: nextToken, + }), + }); + + const userPools = awsResponse.UserPools.map((pool: any) => ({ + name: pool.Name, + value: pool.Id, + })); + + return { + results: userPools, + paginationToken: awsResponse.NextToken, + }; + }); + + const response = await searchUserPools.call(mockContext, ''); + + expect(mockAwsRequest).toHaveBeenCalledWith({ + url: '', + method: 'POST', + headers: { + 'X-Amz-Target': 'AWSCognitoIdentityProviderService.ListUserPools', + }, + body: JSON.stringify({ + MaxResults: 60, + NextToken: undefined, + }), + }); + + expect(response).toEqual({ + results: [ + { name: 'UserPool1', value: 'userPoolId1' }, + { name: 'UserPool2', value: 'userPoolId2' }, + ], + paginationToken: 'nextTokenValue', + }); + }); + + it('should handle pagination and return all results', async () => { + mockAwsRequest + .mockResolvedValueOnce({ + UserPools: [ + { Name: 'UserPool1', Id: 'userPoolId1' }, + { Name: 'UserPool2', Id: 'userPoolId2' }, + ], + NextToken: 'nextTokenValue', + }) + .mockResolvedValueOnce({ + UserPools: [{ Name: 'UserPool3', Id: 'userPoolId3' }], + NextToken: undefined, + }); + + mockGetNodeParameter.mockReturnValueOnce(false); + mockGetNodeParameter.mockReturnValueOnce(10); + mockSearchUserPools.mockImplementation(async () => { + let allResults: any[] = []; + let nextToken: string | undefined; + do { + const response: AwsResponse = await mockAwsRequest({ + url: '', + method: 'POST', + headers: { 'X-Amz-Target': 'AWSCognitoIdentityProviderService.ListUserPools' }, + body: JSON.stringify({ + MaxResults: 60, + NextToken: nextToken, + }), + }); + + if (response.UserPools) { + allResults = allResults.concat( + response.UserPools.map((pool: any) => ({ + name: pool.Name, + value: pool.Id, + })), + ); + } + + nextToken = response.NextToken; + } while (nextToken); + + return { + results: allResults, + paginationToken: nextToken, + }; + }); + + const result = await searchUserPools.call(mockContext, ''); + + expect(mockAwsRequest).toHaveBeenCalledTimes(2); + + expect(result).toEqual({ + results: [ + { name: 'UserPool1', value: 'userPoolId1' }, + { name: 'UserPool2', value: 'userPoolId2' }, + { name: 'UserPool3', value: 'userPoolId3' }, + ], + paginationToken: undefined, + }); + }); + + it('should return empty results if no user pools are found', async () => { + mockAwsRequest.mockResolvedValueOnce({ + UserPools: [], + NextToken: undefined, + }); + + mockSearchUserPools.mockResolvedValueOnce({ + results: [], + paginationToken: undefined, + }); + + const response = await searchUserPools.call(mockContext); + + expect(response).toEqual({ results: [] }); + }); +}); diff --git a/packages/nodes-base/nodes/Aws/Cognito/test/SearchUsers.test.ts b/packages/nodes-base/nodes/Aws/Cognito/test/SearchUsers.test.ts new file mode 100644 index 0000000000..0d94c921a1 --- /dev/null +++ b/packages/nodes-base/nodes/Aws/Cognito/test/SearchUsers.test.ts @@ -0,0 +1,176 @@ +import type { ILoadOptionsFunctions } from 'n8n-workflow'; + +import { searchUsers, awsRequest } from '../GenericFunctions'; + +interface AwsResponse { + Users: Array<{ Username: string; Attributes: Array<{ Name: string; Value: string }> }>; + NextToken?: string; +} + +jest.mock('../GenericFunctions', () => ({ + awsRequest: jest.fn(), + searchUsers: jest.fn(), +})); + +describe('searchUsers', () => { + const mockGetNodeParameter = jest.fn(); + const mockAwsRequest = awsRequest as jest.Mock; + const mockSearchUsers = searchUsers as jest.Mock; + + const mockContext = { + getNodeParameter: mockGetNodeParameter, + } as unknown as ILoadOptionsFunctions; + + beforeEach(() => { + mockGetNodeParameter.mockClear(); + mockAwsRequest.mockClear(); + mockSearchUsers.mockClear(); + }); + + it('should throw an error if User Pool ID is missing', async () => { + mockGetNodeParameter.mockReturnValueOnce(undefined); + mockSearchUsers.mockRejectedValueOnce(new Error('User Pool ID is required to search users')); + + await expect(searchUsers.call(mockContext)).rejects.toThrow( + 'User Pool ID is required to search users', + ); + }); + + it('should make a POST request to search users and return results', async () => { + mockGetNodeParameter.mockReturnValueOnce({ value: 'mockUserPoolId' }); + mockAwsRequest.mockResolvedValueOnce({ + Users: [ + { Username: 'user1', Attributes: [{ Name: 'email', Value: 'user1@example.com' }] }, + { Username: 'user2', Attributes: [{ Name: 'email', Value: 'user2@example.com' }] }, + ], + NextToken: 'nextTokenValue', + }); + + mockSearchUsers.mockImplementation(async (filter, nextToken) => { + const awsResponse: AwsResponse = await mockAwsRequest({ + url: '', + method: 'POST', + headers: { 'X-Amz-Target': 'AWSCognitoIdentityProviderService.ListUsers' }, + body: JSON.stringify({ + UserPoolId: 'mockUserPoolId', + MaxResults: 60, + NextToken: nextToken, + }), + }); + + const users = awsResponse.Users.map((user: any) => ({ + name: user.Attributes?.find((attr: any) => attr.Name === 'email')?.Value || user.Username, + value: user.Username, + })); + + return { + results: users, + paginationToken: awsResponse.NextToken, + }; + }); + + const response = await searchUsers.call(mockContext, 'user1'); + + expect(mockAwsRequest).toHaveBeenCalledWith({ + url: '', + method: 'POST', + headers: { 'X-Amz-Target': 'AWSCognitoIdentityProviderService.ListUsers' }, + body: JSON.stringify({ + UserPoolId: 'mockUserPoolId', + MaxResults: 60, + NextToken: undefined, + }), + }); + + expect(response).toEqual({ + results: [ + { name: 'user1@example.com', value: 'user1' }, + { name: 'user2@example.com', value: 'user2' }, + ], + paginationToken: 'nextTokenValue', + }); + }); + + it('should handle pagination and return all results', async () => { + mockAwsRequest + .mockResolvedValueOnce({ + Users: [ + { Username: 'user1', Attributes: [{ Name: 'email', Value: 'user1@example.com' }] }, + { Username: 'user2', Attributes: [{ Name: 'email', Value: 'user2@example.com' }] }, + ], + NextToken: 'nextTokenValue', + }) + .mockResolvedValueOnce({ + Users: [{ Username: 'user3', Attributes: [{ Name: 'email', Value: 'user3@example.com' }] }], + NextToken: undefined, + }); + + mockGetNodeParameter.mockReturnValueOnce('mockUserPoolId'); + mockGetNodeParameter.mockReturnValueOnce(false); + mockGetNodeParameter.mockReturnValueOnce(10); + + mockSearchUsers.mockImplementation(async () => { + let allResults: any[] = []; + let nextToken: string | undefined; + do { + const response: AwsResponse = await mockAwsRequest({ + url: '', + method: 'POST', + headers: { 'X-Amz-Target': 'AWSCognitoIdentityProviderService.ListUsers' }, + body: JSON.stringify({ + UserPoolId: 'mockUserPoolId', + MaxResults: 60, + NextToken: nextToken, + }), + }); + + if (response.Users) { + allResults = allResults.concat( + response.Users.map((user: any) => ({ + name: + user.Attributes?.find((attr: any) => attr.Name === 'email')?.Value || user.Username, + value: user.Username, + })), + ); + } + + nextToken = response.NextToken; + } while (nextToken); + + return { + results: allResults, + paginationToken: nextToken, + }; + }); + + const result = await searchUsers.call(mockContext, ''); + + expect(mockAwsRequest).toHaveBeenCalledTimes(2); + + expect(result).toEqual({ + results: [ + { name: 'user1@example.com', value: 'user1' }, + { name: 'user2@example.com', value: 'user2' }, + { name: 'user3@example.com', value: 'user3' }, + ], + paginationToken: undefined, + }); + }); + + it('should return empty results if no users are found', async () => { + mockGetNodeParameter.mockReturnValueOnce('mockUserPoolId'); + mockAwsRequest.mockResolvedValueOnce({ + Users: [], + NextToken: undefined, + }); + + mockSearchUsers.mockResolvedValueOnce({ + results: [], + paginationToken: undefined, + }); + + const response = await searchUsers.call(mockContext); + + expect(response).toEqual({ results: [] }); + }); +});