mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-31 08:46:58 +02:00
1040 lines
29 KiB
TypeScript
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 } }]]);
|
|
});
|
|
});
|
|
});
|