mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 01:56:46 +00:00
422 lines
13 KiB
TypeScript
422 lines
13 KiB
TypeScript
import type { IHttpRequestOptions } from 'n8n-workflow';
|
|
import { NodeOperationError } from 'n8n-workflow';
|
|
|
|
import {
|
|
preprocessTags,
|
|
deleteGroupMembers,
|
|
validatePath,
|
|
validateUserPath,
|
|
removeUserFromGroups,
|
|
findUsersForGroup,
|
|
simplifyGetGroupsResponse,
|
|
simplifyGetAllGroupsResponse,
|
|
simplifyGetAllUsersResponse,
|
|
validateName,
|
|
validatePermissionsBoundary,
|
|
encodeBodyAsFormUrlEncoded,
|
|
} from '../../helpers/utils';
|
|
import { awsApiRequest } from '../../transport';
|
|
|
|
jest.mock('../../transport', () => ({
|
|
awsApiRequest: jest.fn(),
|
|
}));
|
|
|
|
describe('AWS IAM - Helper Functions', () => {
|
|
let mockNode: any;
|
|
|
|
beforeEach(() => {
|
|
mockNode = {
|
|
getNodeParameter: jest.fn(),
|
|
getNode: jest.fn(),
|
|
helpers: {
|
|
returnJsonArray: jest.fn((input: unknown[]) => input.map((i) => ({ json: i }))),
|
|
},
|
|
};
|
|
});
|
|
|
|
describe('encodeBodyAsFormUrlEncoded', () => {
|
|
it('should encode the body as application/x-www-form-urlencoded', async () => {
|
|
const requestOptions: IHttpRequestOptions = {
|
|
body: {
|
|
client_id: 'myClient',
|
|
client_secret: 'mySecret',
|
|
grant_type: 'client_credentials',
|
|
},
|
|
url: '',
|
|
headers: {},
|
|
};
|
|
|
|
const result = await encodeBodyAsFormUrlEncoded.call(mockNode, requestOptions);
|
|
|
|
expect(result.body).toBe(
|
|
'client_id=myClient&client_secret=mySecret&grant_type=client_credentials',
|
|
);
|
|
});
|
|
|
|
it('should return unchanged options if no body is present', async () => {
|
|
const requestOptions: IHttpRequestOptions = { url: '', headers: {} };
|
|
const result = await encodeBodyAsFormUrlEncoded.call(mockNode, requestOptions);
|
|
|
|
expect(result).toEqual({ url: '', headers: {} });
|
|
});
|
|
});
|
|
|
|
describe('findUsersForGroup', () => {
|
|
it('should return users for a valid groupName', async () => {
|
|
mockNode.getNodeParameter.mockReturnValue('groupName');
|
|
const mockResponse = {
|
|
GetGroupResponse: { GetGroupResult: { Users: [{ UserName: 'user1' }] } },
|
|
};
|
|
(awsApiRequest as jest.Mock).mockResolvedValue(mockResponse);
|
|
|
|
const result = await findUsersForGroup.call(mockNode, 'groupName');
|
|
expect(result).toEqual([{ UserName: 'user1' }]);
|
|
});
|
|
});
|
|
|
|
describe('preprocessTags', () => {
|
|
it('should preprocess tags correctly into request body', async () => {
|
|
mockNode.getNodeParameter.mockReturnValue({
|
|
tags: [
|
|
{ key: 'Department', value: 'Engineering' },
|
|
{ key: 'Role', value: 'Developer' },
|
|
],
|
|
});
|
|
|
|
const requestOptions = { body: '', headers: {}, url: '' };
|
|
const result = await preprocessTags.call(mockNode, requestOptions);
|
|
|
|
expect(result.body).toBe(
|
|
'Tags.member.1.Key=Department&Tags.member.1.Value=Engineering&Tags.member.2.Key=Role&Tags.member.2.Value=Developer',
|
|
);
|
|
});
|
|
|
|
it('should throw error if a tag is missing a key', async () => {
|
|
mockNode.getNodeParameter.mockReturnValue({
|
|
tags: [{ key: '', value: 'Engineering' }],
|
|
});
|
|
|
|
const requestOptions = { body: '', headers: {}, url: '' };
|
|
|
|
await expect(preprocessTags.call(mockNode, requestOptions)).rejects.toThrow(
|
|
NodeOperationError,
|
|
);
|
|
|
|
await expect(preprocessTags.call(mockNode, requestOptions)).rejects.toThrow(
|
|
"Tag at position 1 is missing 'Key'. Both 'Key' and 'Value' are required.",
|
|
);
|
|
});
|
|
|
|
it('should throw error if a tag is missing a value', async () => {
|
|
mockNode.getNodeParameter.mockReturnValue({
|
|
tags: [{ key: 'Department', value: '' }],
|
|
});
|
|
|
|
const requestOptions = { body: '', headers: {}, url: '' };
|
|
|
|
await expect(preprocessTags.call(mockNode, requestOptions)).rejects.toThrow(
|
|
NodeOperationError,
|
|
);
|
|
|
|
await expect(preprocessTags.call(mockNode, requestOptions)).rejects.toThrow(
|
|
"Tag at position 1 is missing 'Value'. Both 'Key' and 'Value' are required.",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('deleteGroupMembers', () => {
|
|
it('should attempt to remove users from a group', async () => {
|
|
mockNode.getNodeParameter.mockImplementation((param: string) => {
|
|
if (param === 'group') {
|
|
return 'groupName';
|
|
}
|
|
return null;
|
|
});
|
|
|
|
const mockUsers = [{ UserName: 'user1' }];
|
|
(awsApiRequest as jest.Mock).mockResolvedValue(mockUsers);
|
|
|
|
const requestOptions = { headers: {}, url: '' };
|
|
const result = await deleteGroupMembers.call(mockNode, requestOptions);
|
|
|
|
expect(result).toEqual(requestOptions);
|
|
|
|
expect(awsApiRequest).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
method: 'POST',
|
|
body: expect.stringContaining('GroupName=groupName'),
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('validatePath', () => {
|
|
it('should throw an error for invalid path length', async () => {
|
|
mockNode.getNodeParameter.mockReturnValue('');
|
|
await expect(validatePath.call(mockNode, { headers: {}, url: '' })).rejects.toThrowError(
|
|
NodeOperationError,
|
|
);
|
|
});
|
|
|
|
it('should throw an error for invalid path format', async () => {
|
|
mockNode.getNodeParameter.mockReturnValue('/invalidPath');
|
|
await expect(validatePath.call(mockNode, { url: '' })).rejects.toThrowError(
|
|
NodeOperationError,
|
|
);
|
|
});
|
|
|
|
it('should pass for a valid path', async () => {
|
|
mockNode.getNodeParameter.mockReturnValue('/valid/path/');
|
|
const result = await validatePath.call(mockNode, { headers: {}, url: '' });
|
|
expect(result).toEqual({ headers: {}, url: '' });
|
|
});
|
|
});
|
|
|
|
describe('validateUserPath', () => {
|
|
it('should throw an error for invalid path prefix', async () => {
|
|
mockNode.getNodeParameter.mockReturnValue('/invalidPrefix');
|
|
|
|
const mockResponse = {
|
|
ListUsersResponse: {
|
|
ListUsersResult: {
|
|
Users: [{ UserName: 'user1', Path: '/validPrefix/user1' }],
|
|
},
|
|
},
|
|
};
|
|
|
|
(awsApiRequest as jest.Mock).mockResolvedValue(mockResponse);
|
|
|
|
await expect(validateUserPath.call(mockNode, { headers: {}, url: '' })).rejects.toThrowError(
|
|
NodeOperationError,
|
|
);
|
|
});
|
|
|
|
it('should modify the request body with a valid path', async () => {
|
|
mockNode.getNodeParameter.mockReturnValue('/validPrefix/');
|
|
const requestOptions = { body: {}, headers: {}, url: '' };
|
|
|
|
const mockResponse = {
|
|
ListUsersResponse: {
|
|
ListUsersResult: {
|
|
Users: [{ UserName: 'user1', Path: '/validPrefix/user1' }],
|
|
},
|
|
},
|
|
};
|
|
|
|
(awsApiRequest as jest.Mock).mockResolvedValue(mockResponse);
|
|
|
|
const result = await validateUserPath.call(mockNode, requestOptions);
|
|
expect(result.body).toHaveProperty('PathPrefix', '/validPrefix/');
|
|
});
|
|
});
|
|
|
|
describe('validateName', () => {
|
|
const requestOptions: IHttpRequestOptions = { body: {}, url: '' };
|
|
|
|
it('should throw an error if userName contains spaces', async () => {
|
|
mockNode.getNodeParameter.mockImplementation((param: string) => {
|
|
if (param === 'resource') return 'user';
|
|
if (param === 'userName') return 'John Doe';
|
|
return '';
|
|
});
|
|
|
|
await expect(validateName.call(mockNode, requestOptions)).rejects.toThrowError(
|
|
new NodeOperationError(mockNode.getNode(), 'User name should not contain spaces.'),
|
|
);
|
|
});
|
|
|
|
it('should throw an error if userName contains invalid characters', async () => {
|
|
mockNode.getNodeParameter.mockImplementation((param: string) => {
|
|
if (param === 'resource') return 'user';
|
|
if (param === 'userName') return 'John@Doe';
|
|
return '';
|
|
});
|
|
|
|
await expect(validateName.call(mockNode, requestOptions)).rejects.toThrowError(
|
|
new NodeOperationError(
|
|
mockNode.getNode(),
|
|
'User name can have up to 64 characters. Valid characters: letters, numbers, hyphens (-), and underscores (_).',
|
|
),
|
|
);
|
|
});
|
|
|
|
it('should pass validation for valid userName', async () => {
|
|
mockNode.getNodeParameter.mockImplementation((param: string) => {
|
|
if (param === 'resource') return 'user';
|
|
if (param === 'userName') return 'John_Doe123';
|
|
return '';
|
|
});
|
|
|
|
await expect(validateName.call(mockNode, requestOptions)).resolves.toEqual(requestOptions);
|
|
});
|
|
|
|
it('should throw an error if groupName contains spaces', async () => {
|
|
mockNode.getNodeParameter.mockImplementation((param: string) => {
|
|
if (param === 'resource') return 'group';
|
|
if (param === 'groupName') return 'Group Name';
|
|
return '';
|
|
});
|
|
|
|
await expect(validateName.call(mockNode, requestOptions)).rejects.toThrowError(
|
|
new NodeOperationError(mockNode.getNode(), 'Group name should not contain spaces.'),
|
|
);
|
|
});
|
|
|
|
it('should throw an error if groupName contains invalid characters', async () => {
|
|
mockNode.getNodeParameter.mockImplementation((param: string) => {
|
|
if (param === 'resource') return 'group';
|
|
if (param === 'groupName') return 'Group@Name';
|
|
return '';
|
|
});
|
|
|
|
await expect(validateName.call(mockNode, requestOptions)).rejects.toThrowError(
|
|
new NodeOperationError(
|
|
mockNode.getNode(),
|
|
'Group name can have up to 128 characters. Valid characters: letters, numbers, hyphens (-), and underscores (_).',
|
|
),
|
|
);
|
|
});
|
|
|
|
it('should pass validation for valid groupName', async () => {
|
|
mockNode.getNodeParameter.mockImplementation((param: string) => {
|
|
if (param === 'resource') return 'group';
|
|
if (param === 'groupName') return 'Group_Name-123';
|
|
return '';
|
|
});
|
|
|
|
await expect(validateName.call(mockNode, requestOptions)).resolves.toEqual(requestOptions);
|
|
});
|
|
});
|
|
|
|
describe('validatePermissionsBoundary', () => {
|
|
const requestOptions: IHttpRequestOptions = { body: {}, url: '' };
|
|
|
|
it('should return the request options unchanged if no permissions boundary is set', async () => {
|
|
mockNode.getNodeParameter.mockReturnValue(undefined);
|
|
|
|
const result = await validatePermissionsBoundary.call(mockNode, requestOptions);
|
|
|
|
expect(result).toEqual(requestOptions);
|
|
});
|
|
|
|
it('should add a valid permissions boundary to the request body', async () => {
|
|
const validArn = 'arn:aws:iam::123456789012:policy/ExamplePolicy';
|
|
mockNode.getNodeParameter.mockReturnValue(validArn);
|
|
|
|
const result = await validatePermissionsBoundary.call(mockNode, requestOptions);
|
|
|
|
expect(result.body).toEqual({ PermissionsBoundary: validArn });
|
|
});
|
|
|
|
it('should throw an error for invalid permissions boundary format', async () => {
|
|
const invalidArn = 'invalid:arn:format';
|
|
mockNode.getNodeParameter.mockReturnValue(invalidArn);
|
|
|
|
await expect(
|
|
validatePermissionsBoundary.call(mockNode, { body: {}, url: '', headers: {} }),
|
|
).rejects.toThrow(NodeOperationError);
|
|
});
|
|
});
|
|
|
|
describe('simplifyGetGroupsResponse', () => {
|
|
it('should return group data', async () => {
|
|
mockNode.getNodeParameter.mockReturnValue(false);
|
|
const mockResponse = {
|
|
body: { GetGroupResponse: { GetGroupResult: { Group: { GroupName: 'TestGroup' } } } },
|
|
headers: {},
|
|
url: '',
|
|
statusCode: 200,
|
|
};
|
|
|
|
const result = await simplifyGetGroupsResponse.call(mockNode, [], mockResponse);
|
|
|
|
expect(result).toEqual([{ json: { GroupName: 'TestGroup' } }]);
|
|
});
|
|
|
|
it('should include users if "includeUsers" is true', async () => {
|
|
mockNode.getNodeParameter.mockReturnValue(true);
|
|
const mockResponse = {
|
|
body: {
|
|
GetGroupResponse: {
|
|
GetGroupResult: { Group: { GroupName: 'TestGroup' }, Users: [{ UserName: 'user1' }] },
|
|
},
|
|
},
|
|
headers: {},
|
|
url: '',
|
|
statusCode: 200,
|
|
};
|
|
|
|
const result = await simplifyGetGroupsResponse.call(mockNode, [], mockResponse);
|
|
|
|
expect(result).toEqual([
|
|
{ json: { GroupName: 'TestGroup', Users: [{ UserName: 'user1' }] } },
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('simplifyGetAllGroupsResponse', () => {
|
|
it('should return groups without users if "includeUsers" is false', async () => {
|
|
mockNode.getNodeParameter.mockReturnValue(false);
|
|
const mockResponse = {
|
|
body: {
|
|
ListGroupsResponse: { ListGroupsResult: { Groups: [{ GroupName: 'TestGroup' }] } },
|
|
},
|
|
headers: {},
|
|
url: '',
|
|
statusCode: 200,
|
|
};
|
|
|
|
const result = await simplifyGetAllGroupsResponse.call(mockNode, [], mockResponse);
|
|
|
|
expect(result).toEqual([{ json: { GroupName: 'TestGroup' } }]);
|
|
});
|
|
|
|
it('should return groups with users if "includeUsers" is true', async () => {
|
|
mockNode.getNodeParameter.mockReturnValue(true);
|
|
const mockResponse = {
|
|
body: {
|
|
ListGroupsResponse: { ListGroupsResult: { Groups: [{ GroupName: 'TestGroup' }] } },
|
|
},
|
|
headers: {},
|
|
url: '',
|
|
statusCode: 200,
|
|
};
|
|
|
|
const mockUsers = [{ UserName: 'user1' }];
|
|
(awsApiRequest as jest.Mock).mockResolvedValueOnce({
|
|
GetGroupResponse: { GetGroupResult: { Users: mockUsers } },
|
|
});
|
|
|
|
const result = await simplifyGetAllGroupsResponse.call(mockNode, [], mockResponse);
|
|
|
|
expect(result).toEqual([{ json: { GroupName: 'TestGroup', Users: mockUsers } }]);
|
|
});
|
|
});
|
|
|
|
describe('simplifyGetAllUsersResponse', () => {
|
|
it('should return all users', async () => {
|
|
const mockResponse = {
|
|
body: { ListUsersResponse: { ListUsersResult: { Users: [{ UserName: 'user1' }] } } },
|
|
headers: {},
|
|
url: '',
|
|
statusCode: 200,
|
|
};
|
|
const result = await simplifyGetAllUsersResponse.call(mockNode, [], mockResponse);
|
|
expect(result).toEqual([{ json: { UserName: 'user1' } }]);
|
|
});
|
|
});
|
|
|
|
describe('removeUserFromGroups', () => {
|
|
it('should remove a user from all groups', async () => {
|
|
mockNode.getNodeParameter.mockReturnValue('user1');
|
|
const mockUserGroups = { results: [{ value: 'group1' }, { value: 'group2' }] };
|
|
(awsApiRequest as jest.Mock).mockResolvedValue(mockUserGroups);
|
|
|
|
const requestOptions = { headers: {}, url: '' };
|
|
const result = await removeUserFromGroups.call(mockNode, requestOptions);
|
|
|
|
expect(result).toEqual(requestOptions);
|
|
});
|
|
});
|
|
});
|