add unit test for other functions in generic function file

This commit is contained in:
Stamsy 2024-12-16 02:34:09 +02:00
parent c667116c4b
commit 9fa69852f1
9 changed files with 1074 additions and 0 deletions

View file

@ -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.',
}),
);
});
});

View file

@ -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"',
}),
);
});
});

View file

@ -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.',
),
);
});
});

View file

@ -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.',
),
);
});
});

View file

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

View file

@ -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));
});
});

View file

@ -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: [] });
});
});

View file

@ -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: [] });
});
});

View file

@ -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: [] });
});
});