n8n/packages/nodes-base/nodes/Zammad/test/Zammad.node.test.ts

1040 lines
29 KiB
TypeScript

import type {
ICredentialsDecrypted,
ICredentialTestFunctions,
IExecuteFunctions,
ILoadOptionsFunctions,
} from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow';
import * as GenericFunctions from '../GenericFunctions';
import { Zammad } from '../Zammad.node';
jest.mock('../GenericFunctions', () => ({
...jest.requireActual('../GenericFunctions'),
zammadApiRequest: jest.fn(),
zammadApiRequestAllItems: jest.fn(),
getAllFields: jest.fn(),
getGroupCustomFields: jest.fn(),
getOrganizationCustomFields: jest.fn(),
getUserCustomFields: jest.fn(),
getTicketCustomFields: jest.fn(),
getGroupFields: jest.fn(),
getOrganizationFields: jest.fn(),
getTicketFields: jest.fn(),
getUserFields: jest.fn(),
doesNotBelongToZammad: jest.fn(),
fieldToLoadOption: jest.fn(),
isCustomer: jest.fn(),
isNotZammadFoundation: jest.fn(),
throwOnEmptyUpdate: jest.fn(),
}));
describe('Zammad Node', () => {
let node: Zammad;
let mockExecuteFunctions: Partial<IExecuteFunctions>;
let mockLoadOptionsFunctions: Partial<ILoadOptionsFunctions>;
let mockCredentialTestFunctions: Partial<ICredentialTestFunctions>;
beforeEach(() => {
node = new Zammad();
jest.clearAllMocks();
mockExecuteFunctions = {
getInputData: jest.fn().mockReturnValue([{}]),
getNodeParameter: jest.fn(),
getNode: jest.fn().mockReturnValue({ id: 'test-node', name: 'Test Node', type: 'zammad' }),
continueOnFail: jest.fn().mockReturnValue(false),
helpers: {
constructExecutionMetaData: jest
.fn()
.mockImplementation((data: any, meta: any) =>
data.map((item: any) => ({ json: item, itemData: meta.itemData })),
),
returnJsonArray: jest
.fn()
.mockImplementation((data: any) => (Array.isArray(data) ? data : [data])),
},
} as unknown as Partial<IExecuteFunctions>;
mockLoadOptionsFunctions = {
getCredentials: jest.fn(),
getNode: jest.fn(),
} as Partial<ILoadOptionsFunctions>;
mockCredentialTestFunctions = {
helpers: {
request: jest.fn(),
},
} as unknown as Partial<ICredentialTestFunctions>;
});
describe('Node Description', () => {
it('should have correct basic properties', () => {
expect(node.description.displayName).toBe('Zammad');
expect(node.description.name).toBe('zammad');
expect(node.description.version).toBe(1);
expect(node.description.description).toBe('Consume the Zammad API');
});
it('should support correct authentication methods', () => {
const authProperty = node.description.properties?.find((p) => p.name === 'authentication');
expect(authProperty?.options).toEqual([
{ name: 'Basic Auth', value: 'basicAuth' },
{ name: 'Token Auth', value: 'tokenAuth' },
]);
expect(authProperty?.default).toBe('tokenAuth');
});
it('should support correct resources', () => {
const resourceProperty = node.description.properties?.find((p) => p.name === 'resource');
expect(resourceProperty?.options).toEqual([
{ name: 'Group', value: 'group' },
{ name: 'Organization', value: 'organization' },
{ name: 'Ticket', value: 'ticket' },
{ name: 'User', value: 'user' },
]);
expect(resourceProperty?.default).toBe('user');
});
});
describe('Credential Tests', () => {
it('should test basic auth credentials successfully', async () => {
const credentials: ICredentialsDecrypted = {
id: 'test',
name: 'test',
type: 'zammadBasicAuthApi',
data: {
baseUrl: 'https://test.zammad.com',
username: 'testuser',
password: 'testpass',
allowUnauthorizedCerts: false,
},
};
(mockCredentialTestFunctions.helpers!.request as jest.Mock).mockResolvedValue({
success: true,
});
const result = await node.methods.credentialTest.zammadBasicAuthApiTest.call(
mockCredentialTestFunctions as ICredentialTestFunctions,
credentials,
);
expect(result).toEqual({
status: 'OK',
message: 'Authentication successful',
});
expect(mockCredentialTestFunctions.helpers!.request).toHaveBeenCalledWith({
method: 'GET',
uri: 'https://test.zammad.com/api/v1/users/me',
json: true,
rejectUnauthorized: true,
auth: {
user: 'testuser',
pass: 'testpass',
},
});
});
it('should test token auth credentials successfully', async () => {
const credentials: ICredentialsDecrypted = {
id: 'test',
name: 'test',
type: 'zammadTokenAuthApi',
data: {
baseUrl: 'https://test.zammad.com/',
accessToken: 'test-token',
allowUnauthorizedCerts: true,
},
};
(mockCredentialTestFunctions.helpers!.request as jest.Mock).mockResolvedValue({
success: true,
});
const result = await node.methods.credentialTest.zammadTokenAuthApiTest.call(
mockCredentialTestFunctions as ICredentialTestFunctions,
credentials,
);
expect(result).toEqual({
status: 'OK',
message: 'Authentication successful',
});
expect(mockCredentialTestFunctions.helpers!.request).toHaveBeenCalledWith({
method: 'GET',
uri: 'https://test.zammad.com/api/v1/users/me',
json: true,
rejectUnauthorized: false,
headers: {
Authorization: 'Token token=test-token',
},
});
});
it('should handle credential test errors', async () => {
const credentials: ICredentialsDecrypted = {
id: 'test',
name: 'test',
type: 'zammadBasicAuthApi',
data: {
baseUrl: 'https://test.zammad.com',
username: 'wronguser',
password: 'wrongpass',
allowUnauthorizedCerts: false,
},
};
const error = new Error('Unauthorized');
(mockCredentialTestFunctions.helpers!.request as jest.Mock).mockRejectedValue(error);
const result = await node.methods.credentialTest.zammadBasicAuthApiTest.call(
mockCredentialTestFunctions as ICredentialTestFunctions,
credentials,
);
expect(result).toEqual({
status: 'Error',
message: 'Unauthorized',
});
});
});
describe('Load Options', () => {
beforeEach(() => {
(GenericFunctions.getAllFields as jest.Mock).mockResolvedValue([
{ id: 1, name: 'Test Field', object: 'User' },
{ id: 2, name: 'Custom Field', object: 'User' },
]);
});
it('should load group custom fields', async () => {
(GenericFunctions.getGroupCustomFields as jest.Mock).mockReturnValue([
{ id: 1, name: 'Group Custom Field' },
]);
(GenericFunctions.fieldToLoadOption as jest.Mock).mockReturnValue({
name: 'Group Custom Field',
value: 1,
});
const result = await node.methods.loadOptions.loadGroupCustomFields.call(
mockLoadOptionsFunctions as ILoadOptionsFunctions,
);
expect(GenericFunctions.getAllFields).toHaveBeenCalled();
expect(GenericFunctions.getGroupCustomFields).toHaveBeenCalled();
expect(result).toEqual([{ name: 'Group Custom Field', value: 1 }]);
});
it('should load ticket states', async () => {
const mockStates = [
{ id: 1, name: 'Open' },
{ id: 2, name: 'Closed' },
];
(GenericFunctions.zammadApiRequest as jest.Mock).mockResolvedValue(mockStates);
const result = await node.methods.loadOptions.loadTicketStates.call(
mockLoadOptionsFunctions as ILoadOptionsFunctions,
);
expect(GenericFunctions.zammadApiRequest).toHaveBeenCalledWith('GET', '/ticket_states');
expect(result).toEqual([
{ name: 'Open', value: 1 },
{ name: 'Closed', value: 2 },
]);
});
it('should load groups', async () => {
const mockGroups = [
{ id: 1, name: 'Support' },
{ id: 2, name: 'Sales' },
];
(GenericFunctions.zammadApiRequest as jest.Mock).mockResolvedValue(mockGroups);
const result = await node.methods.loadOptions.loadGroups.call(
mockLoadOptionsFunctions as ILoadOptionsFunctions,
);
expect(GenericFunctions.zammadApiRequest).toHaveBeenCalledWith('GET', '/groups');
expect(result).toEqual([
{ name: 'Support', value: 1 },
{ name: 'Sales', value: 2 },
]);
});
it('should load group names', async () => {
const mockGroups = [
{ id: 1, name: 'Support' },
{ id: 2, name: 'Sales' },
];
(GenericFunctions.zammadApiRequest as jest.Mock).mockResolvedValue(mockGroups);
const result = await node.methods.loadOptions.loadGroupNames.call(
mockLoadOptionsFunctions as ILoadOptionsFunctions,
);
expect(GenericFunctions.zammadApiRequest).toHaveBeenCalledWith('GET', '/groups');
expect(result).toEqual([
{ name: 'Support', value: 'Support' },
{ name: 'Sales', value: 'Sales' },
]);
});
it('should load organization names', async () => {
const mockOrganizations = [
{ id: 1, name: 'Acme Corp' },
{ id: 2, name: 'Beta Inc' },
{ id: 3, name: 'Zammad Foundation' }, // This should be filtered out
];
(GenericFunctions.zammadApiRequest as jest.Mock).mockResolvedValue(mockOrganizations);
(GenericFunctions.isNotZammadFoundation as jest.Mock).mockImplementation(
(org) => org.name !== 'Zammad Foundation',
);
const result = await node.methods.loadOptions.loadOrganizationNames.call(
mockLoadOptionsFunctions as ILoadOptionsFunctions,
);
expect(GenericFunctions.zammadApiRequest).toHaveBeenCalledWith('GET', '/organizations');
expect(result).toEqual([
{ name: 'Acme Corp', value: 'Acme Corp' },
{ name: 'Beta Inc', value: 'Beta Inc' },
]);
});
it('should load customer emails', async () => {
const mockUsers = [
{ id: 1, email: 'customer1@example.com', role: 'customer' },
{ id: 2, email: 'agent@example.com', role: 'agent' },
{ id: 3, email: 'customer2@example.com', role: 'customer' },
];
(GenericFunctions.zammadApiRequest as jest.Mock).mockResolvedValue(mockUsers);
(GenericFunctions.isCustomer as jest.Mock).mockImplementation(
(user) => user.role === 'customer',
);
const result = await node.methods.loadOptions.loadCustomerEmails.call(
mockLoadOptionsFunctions as ILoadOptionsFunctions,
);
expect(GenericFunctions.zammadApiRequest).toHaveBeenCalledWith('GET', '/users');
expect(result).toEqual([
{ name: 'customer1@example.com', value: 'customer1@example.com' },
{ name: 'customer2@example.com', value: 'customer2@example.com' },
]);
});
});
describe('Execute - User Operations', () => {
beforeEach(() => {
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation(
(paramName, _itemIndex) => {
if (paramName === 'resource') return 'user';
if (paramName === 'operation') return 'create';
return null;
},
);
});
it('should create a user', async () => {
const mockUser = { id: 1, firstname: 'John', lastname: 'Doe' };
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation(
(paramName, _itemIndex) => {
switch (paramName) {
case 'resource':
return 'user';
case 'operation':
return 'create';
case 'firstname':
return 'John';
case 'lastname':
return 'Doe';
case 'additionalFields':
return {};
default:
return null;
}
},
);
(GenericFunctions.zammadApiRequest as jest.Mock).mockResolvedValue(mockUser);
const result = await node.execute.call(mockExecuteFunctions as IExecuteFunctions);
expect(GenericFunctions.zammadApiRequest).toHaveBeenCalledWith('POST', '/users', {
firstname: 'John',
lastname: 'Doe',
});
expect(result).toEqual([[{ json: mockUser, itemData: { item: 0 } }]]);
});
it('should update a user', async () => {
const mockUser = { id: 1, firstname: 'John', lastname: 'Smith' };
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation(
(paramName, _itemIndex) => {
switch (paramName) {
case 'resource':
return 'user';
case 'operation':
return 'update';
case 'id':
return '1';
case 'updateFields':
return { lastname: 'Smith' };
default:
return null;
}
},
);
(GenericFunctions.zammadApiRequest as jest.Mock).mockResolvedValue(mockUser);
const result = await node.execute.call(mockExecuteFunctions as IExecuteFunctions);
expect(GenericFunctions.zammadApiRequest).toHaveBeenCalledWith('PUT', '/users/1', {
lastname: 'Smith',
});
expect(result).toEqual([[{ json: mockUser, itemData: { item: 0 } }]]);
});
it('should throw error on empty update', async () => {
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation(
(paramName, _itemIndex) => {
switch (paramName) {
case 'resource':
return 'user';
case 'operation':
return 'update';
case 'id':
return '1';
case 'updateFields':
return {};
default:
return null;
}
},
);
(GenericFunctions.throwOnEmptyUpdate as jest.Mock).mockImplementation(() => {
throw new NodeOperationError(
mockExecuteFunctions.getNode!(),
'Please enter at least one field to update for the user resource',
);
});
await expect(node.execute.call(mockExecuteFunctions as IExecuteFunctions)).rejects.toThrow(
'Please enter at least one field to update for the user resource',
);
expect(GenericFunctions.throwOnEmptyUpdate).toHaveBeenCalledWith('user');
});
it('should delete a user', async () => {
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation(
(paramName, _itemIndex) => {
switch (paramName) {
case 'resource':
return 'user';
case 'operation':
return 'delete';
case 'id':
return '1';
default:
return null;
}
},
);
(GenericFunctions.zammadApiRequest as jest.Mock).mockResolvedValue({});
const result = await node.execute.call(mockExecuteFunctions as IExecuteFunctions);
expect(GenericFunctions.zammadApiRequest).toHaveBeenCalledWith('DELETE', '/users/1');
expect(result).toEqual([[{ json: { success: true }, itemData: { item: 0 } }]]);
});
it('should get a user', async () => {
const mockUser = { id: 1, firstname: 'John', lastname: 'Doe' };
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation(
(paramName, _itemIndex) => {
switch (paramName) {
case 'resource':
return 'user';
case 'operation':
return 'get';
case 'id':
return '1';
default:
return null;
}
},
);
(GenericFunctions.zammadApiRequest as jest.Mock).mockResolvedValue(mockUser);
const result = await node.execute.call(mockExecuteFunctions as IExecuteFunctions);
expect(GenericFunctions.zammadApiRequest).toHaveBeenCalledWith('GET', '/users/1');
expect(result).toEqual([[{ json: mockUser, itemData: { item: 0 } }]]);
});
it('should get all users', async () => {
const mockUsers = [
{ id: 1, firstname: 'John', lastname: 'Doe', _preferences: {} },
{ id: 2, firstname: 'Jane', lastname: 'Smith', _preferences: {} },
];
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation(
(paramName, _itemIndex) => {
switch (paramName) {
case 'resource':
return 'user';
case 'operation':
return 'getAll';
case 'filters':
return {};
case 'returnAll':
return true;
default:
return null;
}
},
);
(GenericFunctions.zammadApiRequestAllItems as jest.Mock).mockResolvedValue(mockUsers);
const result = await node.execute.call(mockExecuteFunctions as IExecuteFunctions);
expect(GenericFunctions.zammadApiRequestAllItems).toHaveBeenCalledWith(
'GET',
'/users/search',
{},
{ query: '' },
0,
);
const expectedUsers = mockUsers.map(({ _preferences, ...user }) => user);
expect(result).toEqual([
expectedUsers.map((user) => ({ json: user, itemData: { item: 0 } })),
]);
});
it('should get current user', async () => {
const mockUser = { id: 1, firstname: 'Current', lastname: 'User' };
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation(
(paramName, _itemIndex) => {
switch (paramName) {
case 'resource':
return 'user';
case 'operation':
return 'getSelf';
default:
return null;
}
},
);
(GenericFunctions.zammadApiRequest as jest.Mock).mockResolvedValue(mockUser);
const result = await node.execute.call(mockExecuteFunctions as IExecuteFunctions);
expect(GenericFunctions.zammadApiRequest).toHaveBeenCalledWith('GET', '/users/me');
expect(result).toEqual([[{ json: mockUser, itemData: { item: 0 } }]]);
});
});
describe('Execute - Ticket Operations', () => {
beforeEach(() => {
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation(
(paramName, _itemIndex) => {
if (paramName === 'resource') return 'ticket';
return null;
},
);
});
it('should create a ticket with custom fields if specified', async () => {
const mockTicket = { id: 1, title: 'Test Ticket' };
const mockArticles = [{ id: 1, body: 'Test article' }];
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation(
(paramName, _itemIndex) => {
switch (paramName) {
case 'resource':
return 'ticket';
case 'operation':
return 'create';
case 'title':
return 'Test Ticket';
case 'group':
return 'Support';
case 'customer':
return 'test@example.com';
case 'article':
return {
articleDetails: {
body: 'Test article',
type: 'note',
visibility: 'public',
},
};
case 'additionalFields':
return {
customFieldsUi: {
// eslint-disable-next-line
customFieldPairs: [{ name: 'linear', value: '1234' }],
},
};
default:
return null;
}
},
);
(GenericFunctions.zammadApiRequest as jest.Mock)
.mockResolvedValueOnce(mockTicket)
.mockResolvedValueOnce(mockArticles);
await node.execute.call(mockExecuteFunctions as IExecuteFunctions);
expect(GenericFunctions.zammadApiRequest).toHaveBeenCalledWith('POST', '/tickets', {
article: {
body: 'Test article',
type: 'note',
internal: false,
},
title: 'Test Ticket',
group: 'Support',
customer: 'test@example.com',
linear: '1234',
});
});
it('should include to and cc for email articles', async () => {
const mockTicket = { id: 2, title: 'Email Ticket' };
const mockArticles = [{ id: 2, body: 'Email body' }];
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation(
(paramName, _itemIndex) => {
switch (paramName) {
case 'resource':
return 'ticket';
case 'operation':
return 'create';
case 'title':
return 'Email Ticket';
case 'group':
return 'Support';
case 'customer':
return 'customer@example.com';
case 'article':
return {
articleDetails: {
subject: 'Hello',
body: 'Email body',
type: 'email',
visibility: 'external',
to: 'recipient@example.com',
cc: 'other@example.com',
},
};
case 'additionalFields':
return {};
default:
return null;
}
},
);
(GenericFunctions.zammadApiRequest as jest.Mock)
.mockResolvedValueOnce(mockTicket)
.mockResolvedValueOnce(mockArticles);
await node.execute.call(mockExecuteFunctions as IExecuteFunctions);
expect(GenericFunctions.zammadApiRequest).toHaveBeenCalledWith('POST', '/tickets', {
article: {
subject: 'Hello',
body: 'Email body',
type: 'email',
internal: false,
to: 'recipient@example.com',
cc: 'other@example.com',
},
title: 'Email Ticket',
group: 'Support',
customer: 'customer@example.com',
});
});
it('should throw error when creating ticket without article', async () => {
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation(
(paramName, _itemIndex) => {
switch (paramName) {
case 'resource':
return 'ticket';
case 'operation':
return 'create';
case 'title':
return 'Test Ticket';
case 'group':
return 'Support';
case 'customer':
return 'test@example.com';
case 'article':
return {};
case 'additionalFields':
return {};
default:
return null;
}
},
);
await expect(node.execute.call(mockExecuteFunctions as IExecuteFunctions)).rejects.toThrow(
NodeOperationError,
);
});
it('should update a ticket`s note', async () => {
const mockTicket = { id: 1, title: 'Updated Ticket' };
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation(
(paramName, _itemIndex) => {
switch (paramName) {
case 'resource':
return 'ticket';
case 'operation':
return 'update';
case 'id':
return '1';
case 'updateFields':
return {
title: 'Updated Ticket',
note: 'Adding a note',
};
default:
return null;
}
},
);
(GenericFunctions.zammadApiRequest as jest.Mock).mockResolvedValue(mockTicket);
const result = await node.execute.call(mockExecuteFunctions as IExecuteFunctions);
expect(GenericFunctions.zammadApiRequest).toHaveBeenCalledWith('PUT', '/tickets/1', {
title: 'Updated Ticket',
article: {
body: 'Adding a note',
internal: true,
type: 'note',
content_type: 'text/html',
},
});
expect(result).toEqual([[{ json: mockTicket, itemData: { item: 0 } }]]);
});
it('should handle pending_time removal in ticket update', async () => {
const mockTicket = { id: 1, title: 'Test Ticket' };
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation(
(paramName, _itemIndex) => {
switch (paramName) {
case 'resource':
return 'ticket';
case 'operation':
return 'update';
case 'id':
return '1';
case 'updateFields':
return {
title: 'Test Ticket',
pending_time: '',
};
default:
return null;
}
},
);
(GenericFunctions.zammadApiRequest as jest.Mock).mockResolvedValue(mockTicket);
await node.execute.call(mockExecuteFunctions as IExecuteFunctions);
expect(GenericFunctions.zammadApiRequest).toHaveBeenCalledWith('PUT', '/tickets/1', {
title: 'Test Ticket',
});
});
});
describe('Execute - Organization Operations', () => {
beforeEach(() => {
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation(
(paramName, _itemIndex) => {
if (paramName === 'resource') return 'organization';
return null;
},
);
});
it('should create an organization', async () => {
const mockOrg = { id: 1, name: 'Test Org' };
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation(
(paramName, _itemIndex) => {
switch (paramName) {
case 'resource':
return 'organization';
case 'operation':
return 'create';
case 'name':
return 'Test Org';
case 'additionalFields':
return {};
default:
return null;
}
},
);
(GenericFunctions.zammadApiRequest as jest.Mock).mockResolvedValue(mockOrg);
const result = await node.execute.call(mockExecuteFunctions as IExecuteFunctions);
expect(GenericFunctions.zammadApiRequest).toHaveBeenCalledWith('POST', '/organizations', {
name: 'Test Org',
});
expect(result).toEqual([[{ json: mockOrg, itemData: { item: 0 } }]]);
});
it('should get all organizations', async () => {
const mockOrgs = [
{ id: 1, name: 'Org 1' },
{ id: 2, name: 'Org 2' },
];
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation(
(paramName, _itemIndex) => {
switch (paramName) {
case 'resource':
return 'organization';
case 'operation':
return 'getAll';
case 'returnAll':
return false;
case 'limit':
return 10;
default:
return null;
}
},
);
(GenericFunctions.zammadApiRequestAllItems as jest.Mock).mockResolvedValue(mockOrgs);
const result = await node.execute.call(mockExecuteFunctions as IExecuteFunctions);
expect(GenericFunctions.zammadApiRequestAllItems).toHaveBeenCalledWith(
'GET',
'/organizations',
{},
{},
10,
);
expect(result).toEqual([mockOrgs.map((org) => ({ json: org, itemData: { item: 0 } }))]);
});
});
describe('Execute - Group Operations', () => {
beforeEach(() => {
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation(
(paramName, _itemIndex) => {
if (paramName === 'resource') return 'group';
return null;
},
);
});
it('should create a group', async () => {
const mockGroup = { id: 1, name: 'Test Group' };
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation(
(paramName, _itemIndex) => {
switch (paramName) {
case 'resource':
return 'group';
case 'operation':
return 'create';
case 'name':
return 'Test Group';
case 'additionalFields':
return {};
default:
return null;
}
},
);
(GenericFunctions.zammadApiRequest as jest.Mock).mockResolvedValue(mockGroup);
const result = await node.execute.call(mockExecuteFunctions as IExecuteFunctions);
expect(GenericFunctions.zammadApiRequest).toHaveBeenCalledWith('POST', '/groups', {
name: 'Test Group',
});
expect(result).toEqual([[{ json: mockGroup, itemData: { item: 0 } }]]);
});
it('should get all groups with limit', async () => {
const mockGroups = [
{ id: 1, name: 'Group 1' },
{ id: 2, name: 'Group 2' },
];
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation(
(paramName, _itemIndex) => {
switch (paramName) {
case 'resource':
return 'group';
case 'operation':
return 'getAll';
case 'returnAll':
return false;
case 'limit':
return 5;
default:
return null;
}
},
);
(GenericFunctions.zammadApiRequestAllItems as jest.Mock).mockResolvedValue(mockGroups);
const result = await node.execute.call(mockExecuteFunctions as IExecuteFunctions);
expect(GenericFunctions.zammadApiRequestAllItems).toHaveBeenCalledWith(
'GET',
'/groups',
{},
{},
5,
);
expect(result).toEqual([mockGroups.map((group) => ({ json: group, itemData: { item: 0 } }))]);
});
});
describe('Error Handling', () => {
beforeEach(() => {
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation(
(paramName, _itemIndex) => {
if (paramName === 'resource') return 'user';
if (paramName === 'operation') return 'get';
if (paramName === 'id') return '999';
return null;
},
);
});
it('should handle API errors and continue on fail', async () => {
const error = new Error('User not found');
(GenericFunctions.zammadApiRequest as jest.Mock).mockRejectedValue(error);
(mockExecuteFunctions.continueOnFail as jest.Mock).mockReturnValue(true);
const result = await node.execute.call(mockExecuteFunctions as IExecuteFunctions);
expect(result).toEqual([[{ json: { error: 'User not found' } }]]);
});
it('should throw error when continue on fail is false', async () => {
const error = new Error('User not found');
(GenericFunctions.zammadApiRequest as jest.Mock).mockRejectedValue(error);
(mockExecuteFunctions.continueOnFail as jest.Mock).mockReturnValue(false);
await expect(node.execute.call(mockExecuteFunctions as IExecuteFunctions)).rejects.toThrow(
'User not found',
);
});
});
describe('Custom Fields Handling', () => {
it('should handle custom fields in user creation', async () => {
const mockUser = { id: 1, firstname: 'John', lastname: 'Doe', custom_field: 'value' };
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation(
(paramName, _itemIndex) => {
switch (paramName) {
case 'resource':
return 'user';
case 'operation':
return 'create';
case 'firstname':
return 'John';
case 'lastname':
return 'Doe';
case 'additionalFields':
return {
customFieldsUi: {
customFieldPairs: [{ name: 'Custom_field', value: 'value' }],
},
};
default:
return null;
}
},
);
(GenericFunctions.zammadApiRequest as jest.Mock).mockResolvedValue(mockUser);
const result = await node.execute.call(mockExecuteFunctions as IExecuteFunctions);
expect(GenericFunctions.zammadApiRequest).toHaveBeenCalledWith('POST', '/users', {
firstname: 'John',
lastname: 'Doe',
Custom_field: 'value',
});
expect(result).toEqual([[{ json: mockUser, itemData: { item: 0 } }]]);
});
it('should handle custom fields in ticket update', async () => {
const mockTicket = { id: 1, title: 'Test Ticket', custom_priority: 'high' };
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation(
(paramName, _itemIndex) => {
switch (paramName) {
case 'resource':
return 'ticket';
case 'operation':
return 'update';
case 'id':
return '1';
case 'updateFields':
return {
customFieldsUi: {
customFieldPairs: [{ name: 'Custom_priority', value: 'high' }],
},
};
default:
return null;
}
},
);
(GenericFunctions.zammadApiRequest as jest.Mock).mockResolvedValue(mockTicket);
const result = await node.execute.call(mockExecuteFunctions as IExecuteFunctions);
expect(GenericFunctions.zammadApiRequest).toHaveBeenCalledWith('PUT', '/tickets/1', {
Custom_priority: 'high',
});
expect(result).toEqual([[{ json: mockTicket, itemData: { item: 0 } }]]);
});
});
});