n8n-nodes-innotes 1.0.19 → 1.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/descriptions/AutomationDescription.js +7 -1
- package/dist/descriptions/AutomationDescription.js.map +1 -1
- package/dist/descriptions/JobDescription.js +21 -0
- package/dist/descriptions/JobDescription.js.map +1 -1
- package/dist/nodes/InNotes/InNotes.node.js +72 -17
- package/dist/nodes/InNotes/InNotes.node.js.map +1 -1
- package/dist/package.json +7 -8
- package/dist/test/InNotes.node.test.js +1425 -100
- package/dist/test/InNotes.node.test.js.map +1 -1
- package/dist/test/setup.js +4 -2
- package/dist/test/setup.js.map +1 -1
- package/package.json +7 -8
|
@@ -1,39 +1,80 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* Author: marco
|
|
4
|
-
* Last updated: 2025-
|
|
4
|
+
* Last updated: 2025-02-14
|
|
5
|
+
* Comprehensive test coverage for InNotes n8n node
|
|
5
6
|
*/
|
|
6
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
8
|
const InNotes_node_1 = require("../nodes/InNotes/InNotes.node");
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Test Helpers
|
|
11
|
+
// ============================================================================
|
|
12
|
+
/**
|
|
13
|
+
* Creates a mock IExecuteFunctions object with customizable parameters
|
|
14
|
+
*/
|
|
15
|
+
function createMockExecuteFunctions(params = {}, credentials = {}) {
|
|
16
|
+
const defaultCredentials = {
|
|
17
|
+
baseUrl: 'https://test.innotes.com',
|
|
18
|
+
authMethod: 'token',
|
|
19
|
+
token: 'test-token',
|
|
20
|
+
callbackSecret: 'test-callback-secret',
|
|
21
|
+
...credentials,
|
|
22
|
+
};
|
|
23
|
+
return {
|
|
24
|
+
getInputData: jest.fn().mockReturnValue([{ json: {} }]),
|
|
25
|
+
getNodeParameter: jest.fn().mockImplementation((param, _index, defaultValue) => {
|
|
26
|
+
var _a;
|
|
27
|
+
return (_a = params[param]) !== null && _a !== void 0 ? _a : defaultValue;
|
|
28
|
+
}),
|
|
29
|
+
getCredentials: jest.fn().mockResolvedValue(defaultCredentials),
|
|
30
|
+
continueOnFail: jest.fn().mockReturnValue(false),
|
|
31
|
+
getNode: jest.fn().mockReturnValue({ name: 'InNotes' }),
|
|
32
|
+
helpers: {
|
|
33
|
+
httpRequestWithAuthentication: jest.fn(),
|
|
34
|
+
httpRequest: jest.fn(),
|
|
35
|
+
requestWithAuthenticationPaginated: jest.fn(),
|
|
36
|
+
request: jest.fn(),
|
|
37
|
+
requestWithAuthentication: jest.fn(),
|
|
38
|
+
requestOAuth2: jest.fn(),
|
|
39
|
+
requestOAuth1: jest.fn(),
|
|
40
|
+
returnJsonArray: jest.fn((data) => data.map((item) => ({ json: item }))),
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Creates a mock ILoadOptionsFunctions object
|
|
46
|
+
*/
|
|
47
|
+
function createMockLoadOptionsFunctions(credentials = {}) {
|
|
48
|
+
const defaultCredentials = {
|
|
49
|
+
baseUrl: 'https://test.innotes.com',
|
|
50
|
+
authMethod: 'token',
|
|
51
|
+
token: 'test-token',
|
|
52
|
+
};
|
|
53
|
+
return {
|
|
54
|
+
getCredentials: jest.fn().mockResolvedValue({ ...defaultCredentials, ...credentials }),
|
|
55
|
+
getNode: jest.fn().mockReturnValue({ name: 'InNotes' }),
|
|
56
|
+
helpers: {
|
|
57
|
+
httpRequestWithAuthentication: jest.fn(),
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
// ============================================================================
|
|
62
|
+
// Tests
|
|
63
|
+
// ============================================================================
|
|
8
64
|
describe('InNotes Node', () => {
|
|
9
65
|
let node;
|
|
10
|
-
let mockExecuteFunctions;
|
|
11
66
|
beforeEach(() => {
|
|
12
67
|
node = new InNotes_node_1.InNotes();
|
|
13
|
-
|
|
14
|
-
getInputData: jest.fn().mockReturnValue([{ json: {} }]),
|
|
15
|
-
getNodeParameter: jest.fn(),
|
|
16
|
-
getCredentials: jest.fn().mockResolvedValue({
|
|
17
|
-
username: 'test@example.com',
|
|
18
|
-
password: 'testpassword',
|
|
19
|
-
baseUrl: 'https://test.innotes.com',
|
|
20
|
-
}),
|
|
21
|
-
helpers: {
|
|
22
|
-
httpRequestWithAuthentication: jest.fn(),
|
|
23
|
-
httpRequest: jest.fn(),
|
|
24
|
-
requestWithAuthenticationPaginated: jest.fn(),
|
|
25
|
-
request: jest.fn(),
|
|
26
|
-
requestWithAuthentication: jest.fn(),
|
|
27
|
-
requestOAuth2: jest.fn(),
|
|
28
|
-
requestOAuth1: jest.fn(),
|
|
29
|
-
},
|
|
30
|
-
};
|
|
68
|
+
jest.clearAllMocks();
|
|
31
69
|
});
|
|
70
|
+
// ========================================================================
|
|
71
|
+
// Node Properties Tests
|
|
72
|
+
// ========================================================================
|
|
32
73
|
describe('Node Properties', () => {
|
|
33
74
|
it('should have correct description', () => {
|
|
34
75
|
expect(node.description.displayName).toBe('InNotes');
|
|
35
76
|
expect(node.description.name).toBe('inNotes');
|
|
36
|
-
expect(node.description.group).toEqual(['
|
|
77
|
+
expect(node.description.group).toEqual(['output']);
|
|
37
78
|
expect(node.description.version).toBe(1);
|
|
38
79
|
});
|
|
39
80
|
it('should have correct credentials', () => {
|
|
@@ -45,98 +86,1382 @@ describe('InNotes Node', () => {
|
|
|
45
86
|
]);
|
|
46
87
|
});
|
|
47
88
|
it('should have correct inputs and outputs', () => {
|
|
48
|
-
expect(node.description.inputs).
|
|
49
|
-
expect(node.description.outputs).
|
|
89
|
+
expect(node.description.inputs).toBeDefined();
|
|
90
|
+
expect(node.description.outputs).toBeDefined();
|
|
91
|
+
});
|
|
92
|
+
it('should have all expected resources', () => {
|
|
93
|
+
var _a;
|
|
94
|
+
const resourceProperty = (_a = node.description.properties) === null || _a === void 0 ? void 0 : _a.find((prop) => prop.name === 'resource' && prop.type === 'options');
|
|
95
|
+
expect(resourceProperty).toBeDefined();
|
|
96
|
+
const options = resourceProperty === null || resourceProperty === void 0 ? void 0 : resourceProperty.options;
|
|
97
|
+
const resourceNames = options === null || options === void 0 ? void 0 : options.map((o) => o.value);
|
|
98
|
+
expect(resourceNames).toContain('automation');
|
|
99
|
+
expect(resourceNames).toContain('contact');
|
|
100
|
+
expect(resourceNames).toContain('note');
|
|
101
|
+
expect(resourceNames).toContain('job');
|
|
102
|
+
expect(resourceNames).toContain('status');
|
|
103
|
+
expect(resourceNames).toContain('tag');
|
|
104
|
+
expect(resourceNames).toContain('user');
|
|
50
105
|
});
|
|
51
106
|
});
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
107
|
+
// ========================================================================
|
|
108
|
+
// Load Options Tests
|
|
109
|
+
// ========================================================================
|
|
110
|
+
describe('Load Options', () => {
|
|
111
|
+
describe('jobStatuses', () => {
|
|
112
|
+
it('should load job statuses from API', async () => {
|
|
113
|
+
const mockLoadOptions = createMockLoadOptionsFunctions();
|
|
114
|
+
const mockStatuses = [
|
|
115
|
+
{ id: 1, name: 'Applied' },
|
|
116
|
+
{ id: 2, name: 'Interviewing' },
|
|
117
|
+
{ id: 3, name: 'Offer' },
|
|
118
|
+
];
|
|
119
|
+
mockLoadOptions.helpers.httpRequestWithAuthentication.mockResolvedValue(mockStatuses);
|
|
120
|
+
const result = await node.methods.loadOptions.jobStatuses.call(mockLoadOptions);
|
|
121
|
+
expect(result).toEqual([
|
|
122
|
+
{ name: 'Applied', value: 'Applied' },
|
|
123
|
+
{ name: 'Interviewing', value: 'Interviewing' },
|
|
124
|
+
{ name: 'Offer', value: 'Offer' },
|
|
125
|
+
]);
|
|
126
|
+
});
|
|
127
|
+
it('should handle single status response', async () => {
|
|
128
|
+
const mockLoadOptions = createMockLoadOptionsFunctions();
|
|
129
|
+
const mockStatus = { id: 1, name: 'Applied' };
|
|
130
|
+
mockLoadOptions.helpers.httpRequestWithAuthentication.mockResolvedValue(mockStatus);
|
|
131
|
+
const result = await node.methods.loadOptions.jobStatuses.call(mockLoadOptions);
|
|
132
|
+
expect(result).toEqual([{ name: 'Applied', value: 'Applied' }]);
|
|
133
|
+
});
|
|
134
|
+
it('should throw error on API failure', async () => {
|
|
135
|
+
const mockLoadOptions = createMockLoadOptionsFunctions();
|
|
136
|
+
mockLoadOptions.helpers.httpRequestWithAuthentication.mockRejectedValue(new Error('API Error'));
|
|
137
|
+
await expect(node.methods.loadOptions.jobStatuses.call(mockLoadOptions)).rejects.toThrow();
|
|
138
|
+
});
|
|
68
139
|
});
|
|
69
140
|
});
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
141
|
+
// ========================================================================
|
|
142
|
+
// Contact Operations Tests
|
|
143
|
+
// ========================================================================
|
|
144
|
+
describe('Contact Operations', () => {
|
|
145
|
+
describe('create', () => {
|
|
146
|
+
it('should create contact with required fields', async () => {
|
|
147
|
+
const mockExecute = createMockExecuteFunctions({
|
|
148
|
+
resource: 'contact',
|
|
149
|
+
operation: 'create',
|
|
150
|
+
options: {},
|
|
151
|
+
name: 'John Doe',
|
|
152
|
+
linkedin_key: 'johndoe123',
|
|
153
|
+
});
|
|
154
|
+
const mockResponse = { id: '1', name: 'John Doe' };
|
|
155
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
|
|
156
|
+
const result = await node.execute.call(mockExecute);
|
|
157
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
158
|
+
method: 'POST',
|
|
159
|
+
url: 'https://test.innotes.com/api/contacts',
|
|
160
|
+
body: expect.objectContaining({
|
|
161
|
+
name: 'John Doe',
|
|
162
|
+
linkedin_key: 'johndoe123',
|
|
163
|
+
}),
|
|
164
|
+
}));
|
|
165
|
+
expect(result[0]).toBeDefined();
|
|
78
166
|
});
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
167
|
+
it('should include optional fields when provided', async () => {
|
|
168
|
+
const mockExecute = createMockExecuteFunctions({
|
|
169
|
+
resource: 'contact',
|
|
170
|
+
operation: 'create',
|
|
171
|
+
options: {},
|
|
172
|
+
name: 'John Doe',
|
|
173
|
+
linkedin_key: 'johndoe123',
|
|
174
|
+
linkedin_user: 'john.doe',
|
|
175
|
+
location: 'New York',
|
|
176
|
+
current_company: 'Acme Inc',
|
|
177
|
+
picture_url: 'https://example.com/photo.jpg',
|
|
178
|
+
tags: 'vip,prospect',
|
|
179
|
+
status_id: '1',
|
|
180
|
+
});
|
|
181
|
+
const mockResponse = { id: '1', name: 'John Doe' };
|
|
182
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
|
|
183
|
+
await node.execute.call(mockExecute);
|
|
184
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
185
|
+
body: expect.objectContaining({
|
|
186
|
+
name: 'John Doe',
|
|
187
|
+
linkedin_key: 'johndoe123',
|
|
188
|
+
linkedin_user: 'john.doe',
|
|
189
|
+
location: 'New York',
|
|
190
|
+
current_company: 'Acme Inc',
|
|
191
|
+
picture_url: 'https://example.com/photo.jpg',
|
|
192
|
+
tags: ['vip', 'prospect'],
|
|
193
|
+
status_id: '1',
|
|
194
|
+
}),
|
|
195
|
+
}));
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
describe('get', () => {
|
|
199
|
+
it('should get contact by ID', async () => {
|
|
200
|
+
const mockExecute = createMockExecuteFunctions({
|
|
201
|
+
resource: 'contact',
|
|
202
|
+
operation: 'get',
|
|
203
|
+
options: {},
|
|
204
|
+
contactId: '123',
|
|
205
|
+
});
|
|
206
|
+
const mockResponse = { id: '123', name: 'Test Contact' };
|
|
207
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
|
|
208
|
+
const result = await node.execute.call(mockExecute);
|
|
209
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
210
|
+
method: 'GET',
|
|
211
|
+
url: 'https://test.innotes.com/api/contacts/123',
|
|
212
|
+
}));
|
|
213
|
+
expect(result[0]).toBeDefined();
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
describe('getAll', () => {
|
|
217
|
+
it('should get all contacts with pagination', async () => {
|
|
218
|
+
const mockExecute = createMockExecuteFunctions({
|
|
219
|
+
resource: 'contact',
|
|
220
|
+
operation: 'getAll',
|
|
221
|
+
options: {},
|
|
222
|
+
returnAll: false,
|
|
223
|
+
limit: 10,
|
|
224
|
+
});
|
|
225
|
+
const mockResponse = [{ id: '1' }, { id: '2' }];
|
|
226
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
|
|
227
|
+
const result = await node.execute.call(mockExecute);
|
|
228
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
229
|
+
method: 'GET',
|
|
230
|
+
url: 'https://test.innotes.com/api/contacts',
|
|
231
|
+
qs: expect.objectContaining({
|
|
232
|
+
pageSize: 10,
|
|
233
|
+
page: 1,
|
|
234
|
+
}),
|
|
235
|
+
}));
|
|
236
|
+
expect(result[0]).toBeDefined();
|
|
237
|
+
});
|
|
238
|
+
it('should apply search and tag filters', async () => {
|
|
239
|
+
const mockExecute = createMockExecuteFunctions({
|
|
240
|
+
resource: 'contact',
|
|
241
|
+
operation: 'getAll',
|
|
242
|
+
options: {},
|
|
243
|
+
returnAll: false,
|
|
244
|
+
limit: 24,
|
|
245
|
+
search: 'john',
|
|
246
|
+
tags: 'vip',
|
|
247
|
+
});
|
|
248
|
+
const mockResponse = [{ id: '1' }];
|
|
249
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
|
|
250
|
+
await node.execute.call(mockExecute);
|
|
251
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
252
|
+
qs: expect.objectContaining({
|
|
253
|
+
search: 'john',
|
|
254
|
+
tags: 'vip',
|
|
255
|
+
}),
|
|
256
|
+
}));
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
describe('update', () => {
|
|
260
|
+
it('should update contact fields', async () => {
|
|
261
|
+
const mockExecute = createMockExecuteFunctions({
|
|
262
|
+
resource: 'contact',
|
|
263
|
+
operation: 'update',
|
|
264
|
+
options: {},
|
|
265
|
+
contactId: '123',
|
|
266
|
+
updateFields: {
|
|
267
|
+
name: 'Updated Name',
|
|
268
|
+
location: 'Boston',
|
|
97
269
|
},
|
|
98
|
-
|
|
99
|
-
|
|
270
|
+
});
|
|
271
|
+
const mockResponse = { id: '123', name: 'Updated Name' };
|
|
272
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
|
|
273
|
+
await node.execute.call(mockExecute);
|
|
274
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
275
|
+
method: 'PUT',
|
|
276
|
+
url: 'https://test.innotes.com/api/contacts/123',
|
|
277
|
+
body: {
|
|
278
|
+
name: 'Updated Name',
|
|
279
|
+
location: 'Boston',
|
|
280
|
+
},
|
|
281
|
+
}));
|
|
282
|
+
});
|
|
100
283
|
});
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
284
|
+
describe('delete', () => {
|
|
285
|
+
it('should delete contact and return success', async () => {
|
|
286
|
+
const mockExecute = createMockExecuteFunctions({
|
|
287
|
+
resource: 'contact',
|
|
288
|
+
operation: 'delete',
|
|
289
|
+
options: {},
|
|
290
|
+
contactId: '123',
|
|
291
|
+
});
|
|
292
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue({});
|
|
293
|
+
const result = await node.execute.call(mockExecute);
|
|
294
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
295
|
+
method: 'DELETE',
|
|
296
|
+
url: 'https://test.innotes.com/api/contacts/123',
|
|
297
|
+
}));
|
|
298
|
+
expect(result[0][0].json).toEqual({ success: true });
|
|
108
299
|
});
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
// ========================================================================
|
|
303
|
+
// Note Operations Tests
|
|
304
|
+
// ========================================================================
|
|
305
|
+
describe('Note Operations', () => {
|
|
306
|
+
describe('create', () => {
|
|
307
|
+
it('should create note with required fields', async () => {
|
|
308
|
+
const mockExecute = createMockExecuteFunctions({
|
|
309
|
+
resource: 'note',
|
|
310
|
+
operation: 'create',
|
|
311
|
+
options: {},
|
|
120
312
|
content: 'Test note content',
|
|
121
|
-
contactId:
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
313
|
+
contactId: '123',
|
|
314
|
+
});
|
|
315
|
+
const mockResponse = { id: '1', text: 'Test note content' };
|
|
316
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
|
|
317
|
+
const result = await node.execute.call(mockExecute);
|
|
318
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
319
|
+
method: 'POST',
|
|
320
|
+
url: 'https://test.innotes.com/api/note',
|
|
321
|
+
body: expect.objectContaining({
|
|
322
|
+
content: 'Test note content',
|
|
323
|
+
contact_id: '123',
|
|
324
|
+
}),
|
|
325
|
+
}));
|
|
326
|
+
expect(result[0]).toBeDefined();
|
|
327
|
+
});
|
|
328
|
+
it('should include visibility and ext_table_name when provided', async () => {
|
|
329
|
+
const mockExecute = createMockExecuteFunctions({
|
|
330
|
+
resource: 'note',
|
|
331
|
+
operation: 'create',
|
|
332
|
+
options: {},
|
|
333
|
+
content: 'Test note',
|
|
334
|
+
contactId: '123',
|
|
335
|
+
visibility: 'public',
|
|
336
|
+
ext_table_name: 'jobs',
|
|
337
|
+
});
|
|
338
|
+
const mockResponse = { id: '1' };
|
|
339
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
|
|
340
|
+
await node.execute.call(mockExecute);
|
|
341
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
342
|
+
body: expect.objectContaining({
|
|
343
|
+
visibility: 'public',
|
|
344
|
+
ext_table_name: 'jobs',
|
|
345
|
+
}),
|
|
346
|
+
}));
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
describe('get', () => {
|
|
350
|
+
it('should get note by ID', async () => {
|
|
351
|
+
const mockExecute = createMockExecuteFunctions({
|
|
352
|
+
resource: 'note',
|
|
353
|
+
operation: 'get',
|
|
354
|
+
options: {},
|
|
355
|
+
noteId: '456',
|
|
356
|
+
});
|
|
357
|
+
const mockResponse = { id: '456', text: 'Test note' };
|
|
358
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
|
|
359
|
+
await node.execute.call(mockExecute);
|
|
360
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
361
|
+
method: 'GET',
|
|
362
|
+
url: 'https://test.innotes.com/api/note/456',
|
|
363
|
+
}));
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
describe('getAll', () => {
|
|
367
|
+
it('should get all notes for a contact', async () => {
|
|
368
|
+
const mockExecute = createMockExecuteFunctions({
|
|
369
|
+
resource: 'note',
|
|
370
|
+
operation: 'getAll',
|
|
371
|
+
options: {},
|
|
372
|
+
contactId: '123',
|
|
373
|
+
returnAll: false,
|
|
374
|
+
limit: 10,
|
|
375
|
+
});
|
|
376
|
+
const mockResponse = [{ id: '1' }, { id: '2' }];
|
|
377
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
|
|
378
|
+
await node.execute.call(mockExecute);
|
|
379
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
380
|
+
method: 'GET',
|
|
381
|
+
url: 'https://test.innotes.com/api/note',
|
|
382
|
+
qs: expect.objectContaining({
|
|
383
|
+
contact_id: '123',
|
|
384
|
+
}),
|
|
385
|
+
}));
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
describe('update', () => {
|
|
389
|
+
it('should update note', async () => {
|
|
390
|
+
const mockExecute = createMockExecuteFunctions({
|
|
391
|
+
resource: 'note',
|
|
392
|
+
operation: 'update',
|
|
393
|
+
options: {},
|
|
394
|
+
noteId: '456',
|
|
395
|
+
updateFields: { text: 'Updated content' },
|
|
396
|
+
});
|
|
397
|
+
const mockResponse = { id: '456' };
|
|
398
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
|
|
399
|
+
await node.execute.call(mockExecute);
|
|
400
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
401
|
+
method: 'PUT',
|
|
402
|
+
url: 'https://test.innotes.com/api/note/456',
|
|
403
|
+
}));
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
describe('delete', () => {
|
|
407
|
+
it('should delete note', async () => {
|
|
408
|
+
const mockExecute = createMockExecuteFunctions({
|
|
409
|
+
resource: 'note',
|
|
410
|
+
operation: 'delete',
|
|
411
|
+
options: {},
|
|
412
|
+
noteId: '456',
|
|
413
|
+
});
|
|
414
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue({});
|
|
415
|
+
const result = await node.execute.call(mockExecute);
|
|
416
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
417
|
+
method: 'DELETE',
|
|
418
|
+
url: 'https://test.innotes.com/api/note/456',
|
|
419
|
+
}));
|
|
420
|
+
expect(result[0][0].json).toEqual({ success: true });
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
// ========================================================================
|
|
425
|
+
// Job Operations Tests (HIGH PRIORITY - Bug Fix Verification)
|
|
426
|
+
// ========================================================================
|
|
427
|
+
describe('Job Operations', () => {
|
|
428
|
+
describe('create', () => {
|
|
429
|
+
it('should create job with status lookup and send status_id as integer', async () => {
|
|
430
|
+
const mockExecute = createMockExecuteFunctions({
|
|
431
|
+
resource: 'job',
|
|
432
|
+
operation: 'create',
|
|
433
|
+
options: {},
|
|
434
|
+
name: 'Software Engineer',
|
|
435
|
+
company_name: 'Tech Corp',
|
|
436
|
+
status: 'Interested',
|
|
437
|
+
});
|
|
438
|
+
// First call returns statuses, second creates job
|
|
439
|
+
mockExecute.helpers.httpRequestWithAuthentication
|
|
440
|
+
.mockResolvedValueOnce([
|
|
441
|
+
{ id: 100, name: 'Applied' },
|
|
442
|
+
{ id: 200, name: 'Interested' },
|
|
443
|
+
{ id: 300, name: 'Rejected' },
|
|
444
|
+
])
|
|
445
|
+
.mockResolvedValueOnce({ id: '1' });
|
|
446
|
+
await node.execute.call(mockExecute);
|
|
447
|
+
// Verify the job creation call
|
|
448
|
+
const calls = mockExecute.helpers.httpRequestWithAuthentication.mock.calls;
|
|
449
|
+
const createJobCall = calls.find((call) => call[1].method === 'POST' && call[1].url.includes('/api/job'));
|
|
450
|
+
expect(createJobCall).toBeDefined();
|
|
451
|
+
expect(createJobCall[1].body.status_id).toBe(200); // Should be integer, not string
|
|
452
|
+
expect(typeof createJobCall[1].body.status_id).toBe('number');
|
|
453
|
+
});
|
|
454
|
+
it('should NOT include status field in request body', async () => {
|
|
455
|
+
const mockExecute = createMockExecuteFunctions({
|
|
456
|
+
resource: 'job',
|
|
457
|
+
operation: 'create',
|
|
458
|
+
options: {},
|
|
459
|
+
name: 'Software Engineer',
|
|
460
|
+
company_name: 'Tech Corp',
|
|
461
|
+
status: 'Interested',
|
|
462
|
+
});
|
|
463
|
+
mockExecute.helpers.httpRequestWithAuthentication
|
|
464
|
+
.mockResolvedValueOnce([{ id: 200, name: 'Interested' }])
|
|
465
|
+
.mockResolvedValueOnce({ id: '1' });
|
|
466
|
+
await node.execute.call(mockExecute);
|
|
467
|
+
const calls = mockExecute.helpers.httpRequestWithAuthentication.mock.calls;
|
|
468
|
+
const createJobCall = calls.find((call) => call[1].method === 'POST' && call[1].url.includes('/api/job'));
|
|
469
|
+
expect(createJobCall[1].body).not.toHaveProperty('status');
|
|
470
|
+
});
|
|
471
|
+
it('should exclude picture_url when value is "Not Available"', async () => {
|
|
472
|
+
const mockExecute = createMockExecuteFunctions({
|
|
473
|
+
resource: 'job',
|
|
474
|
+
operation: 'create',
|
|
475
|
+
options: {},
|
|
476
|
+
name: 'Software Engineer',
|
|
477
|
+
company_name: 'Tech Corp',
|
|
478
|
+
picture_url: 'Not Available',
|
|
479
|
+
});
|
|
480
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue({ id: '1' });
|
|
481
|
+
await node.execute.call(mockExecute);
|
|
482
|
+
const calls = mockExecute.helpers.httpRequestWithAuthentication.mock.calls;
|
|
483
|
+
const createJobCall = calls.find((call) => call[1].method === 'POST');
|
|
484
|
+
expect(createJobCall[1].body).not.toHaveProperty('picture_url');
|
|
485
|
+
});
|
|
486
|
+
it('should include picture_url when value is valid URL', async () => {
|
|
487
|
+
const mockExecute = createMockExecuteFunctions({
|
|
488
|
+
resource: 'job',
|
|
489
|
+
operation: 'create',
|
|
490
|
+
options: {},
|
|
491
|
+
name: 'Software Engineer',
|
|
492
|
+
company_name: 'Tech Corp',
|
|
493
|
+
picture_url: 'https://example.com/logo.png',
|
|
494
|
+
});
|
|
495
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue({ id: '1' });
|
|
496
|
+
await node.execute.call(mockExecute);
|
|
497
|
+
const calls = mockExecute.helpers.httpRequestWithAuthentication.mock.calls;
|
|
498
|
+
const createJobCall = calls.find((call) => call[1].method === 'POST');
|
|
499
|
+
expect(createJobCall[1].body.picture_url).toBe('https://example.com/logo.png');
|
|
500
|
+
});
|
|
501
|
+
it('should handle tags as comma-separated string converted to array', async () => {
|
|
502
|
+
const mockExecute = createMockExecuteFunctions({
|
|
503
|
+
resource: 'job',
|
|
504
|
+
operation: 'create',
|
|
505
|
+
options: {},
|
|
506
|
+
name: 'Software Engineer',
|
|
507
|
+
company_name: 'Tech Corp',
|
|
508
|
+
tags: 'remote, senior, python',
|
|
509
|
+
});
|
|
510
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue({ id: '1' });
|
|
511
|
+
await node.execute.call(mockExecute);
|
|
512
|
+
const calls = mockExecute.helpers.httpRequestWithAuthentication.mock.calls;
|
|
513
|
+
const createJobCall = calls.find((call) => call[1].method === 'POST');
|
|
514
|
+
expect(createJobCall[1].body.tags).toEqual(['remote', 'senior', 'python']);
|
|
515
|
+
});
|
|
516
|
+
it('should throw error when status name not found', async () => {
|
|
517
|
+
const mockExecute = createMockExecuteFunctions({
|
|
518
|
+
resource: 'job',
|
|
519
|
+
operation: 'create',
|
|
520
|
+
options: {},
|
|
521
|
+
name: 'Software Engineer',
|
|
522
|
+
company_name: 'Tech Corp',
|
|
523
|
+
status: 'NonExistentStatus',
|
|
524
|
+
});
|
|
525
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue([
|
|
526
|
+
{ id: 1, name: 'Applied' },
|
|
527
|
+
{ id: 2, name: 'Rejected' },
|
|
528
|
+
]);
|
|
529
|
+
await expect(node.execute.call(mockExecute)).rejects.toThrow(/NonExistentStatus.*not found/);
|
|
530
|
+
});
|
|
531
|
+
it('should include all optional fields when provided', async () => {
|
|
532
|
+
const mockExecute = createMockExecuteFunctions({
|
|
533
|
+
resource: 'job',
|
|
534
|
+
operation: 'create',
|
|
535
|
+
options: {},
|
|
536
|
+
name: 'Software Engineer',
|
|
537
|
+
company_name: 'Tech Corp',
|
|
538
|
+
description: 'Great opportunity',
|
|
539
|
+
location: 'Remote',
|
|
540
|
+
remote_setting: 'Remote',
|
|
541
|
+
company_url: 'https://techcorp.com',
|
|
542
|
+
provider: 'linkedin',
|
|
543
|
+
url: 'https://linkedin.com/jobs/123',
|
|
544
|
+
ext_id: 'LI-123',
|
|
545
|
+
});
|
|
546
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue({ id: '1' });
|
|
547
|
+
await node.execute.call(mockExecute);
|
|
548
|
+
const calls = mockExecute.helpers.httpRequestWithAuthentication.mock.calls;
|
|
549
|
+
const createJobCall = calls.find((call) => call[1].method === 'POST');
|
|
550
|
+
expect(createJobCall[1].body).toEqual(expect.objectContaining({
|
|
551
|
+
name: 'Software Engineer',
|
|
552
|
+
company_name: 'Tech Corp',
|
|
553
|
+
description: 'Great opportunity',
|
|
554
|
+
location: 'Remote',
|
|
555
|
+
remote_setting: 'Remote',
|
|
556
|
+
company_url: 'https://techcorp.com',
|
|
557
|
+
provider: 'linkedin',
|
|
558
|
+
url: 'https://linkedin.com/jobs/123',
|
|
559
|
+
ext_id: 'LI-123',
|
|
560
|
+
}));
|
|
561
|
+
});
|
|
562
|
+
});
|
|
563
|
+
describe('get', () => {
|
|
564
|
+
it('should get job by ID', async () => {
|
|
565
|
+
const mockExecute = createMockExecuteFunctions({
|
|
566
|
+
resource: 'job',
|
|
567
|
+
operation: 'get',
|
|
568
|
+
options: {},
|
|
569
|
+
jobId: '123',
|
|
570
|
+
});
|
|
571
|
+
const mockResponse = { id: '123', name: 'Test Job' };
|
|
572
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
|
|
573
|
+
await node.execute.call(mockExecute);
|
|
574
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
575
|
+
method: 'GET',
|
|
576
|
+
url: 'https://test.innotes.com/api/job/123',
|
|
577
|
+
}));
|
|
578
|
+
});
|
|
579
|
+
it('should handle job not found error', async () => {
|
|
580
|
+
const mockExecute = createMockExecuteFunctions({
|
|
581
|
+
resource: 'job',
|
|
582
|
+
operation: 'get',
|
|
583
|
+
options: {},
|
|
584
|
+
jobId: '999',
|
|
585
|
+
});
|
|
586
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockRejectedValue(new Error('Job not found'));
|
|
587
|
+
await expect(node.execute.call(mockExecute)).rejects.toThrow();
|
|
588
|
+
});
|
|
589
|
+
});
|
|
590
|
+
describe('getAll', () => {
|
|
591
|
+
it('should get all jobs with pagination', async () => {
|
|
592
|
+
const mockExecute = createMockExecuteFunctions({
|
|
593
|
+
resource: 'job',
|
|
594
|
+
operation: 'getAll',
|
|
595
|
+
options: {},
|
|
596
|
+
returnAll: false,
|
|
597
|
+
limit: 10,
|
|
598
|
+
});
|
|
599
|
+
const mockResponse = [{ id: '1' }, { id: '2' }];
|
|
600
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
|
|
601
|
+
await node.execute.call(mockExecute);
|
|
602
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
603
|
+
method: 'GET',
|
|
604
|
+
url: 'https://test.innotes.com/api/job',
|
|
605
|
+
qs: expect.objectContaining({
|
|
606
|
+
pageSize: 10,
|
|
607
|
+
page: 1,
|
|
608
|
+
}),
|
|
609
|
+
}));
|
|
610
|
+
});
|
|
611
|
+
it('should apply search filters', async () => {
|
|
612
|
+
const mockExecute = createMockExecuteFunctions({
|
|
613
|
+
resource: 'job',
|
|
614
|
+
operation: 'getAll',
|
|
615
|
+
options: {},
|
|
616
|
+
returnAll: false,
|
|
617
|
+
limit: 24,
|
|
618
|
+
search: 'engineer',
|
|
619
|
+
tags: 'remote',
|
|
620
|
+
});
|
|
621
|
+
const mockResponse = [{ id: '1' }];
|
|
622
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
|
|
623
|
+
await node.execute.call(mockExecute);
|
|
624
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
625
|
+
qs: expect.objectContaining({
|
|
626
|
+
search: 'engineer',
|
|
627
|
+
tags: 'remote',
|
|
628
|
+
}),
|
|
629
|
+
}));
|
|
630
|
+
});
|
|
631
|
+
});
|
|
632
|
+
describe('update', () => {
|
|
633
|
+
it('should update job with status lookup as integer', async () => {
|
|
634
|
+
const mockExecute = createMockExecuteFunctions({
|
|
635
|
+
resource: 'job',
|
|
636
|
+
operation: 'update',
|
|
637
|
+
options: {},
|
|
638
|
+
jobId: '123',
|
|
639
|
+
updateFields: {
|
|
640
|
+
status: 'Interviewing',
|
|
641
|
+
},
|
|
642
|
+
});
|
|
643
|
+
mockExecute.helpers.httpRequestWithAuthentication
|
|
644
|
+
.mockResolvedValueOnce([
|
|
645
|
+
{ id: 100, name: 'Applied' },
|
|
646
|
+
{ id: 200, name: 'Interviewing' },
|
|
647
|
+
])
|
|
648
|
+
.mockResolvedValueOnce({ id: '123' });
|
|
649
|
+
await node.execute.call(mockExecute);
|
|
650
|
+
const calls = mockExecute.helpers.httpRequestWithAuthentication.mock.calls;
|
|
651
|
+
const updateCall = calls.find((call) => call[1].method === 'PUT');
|
|
652
|
+
expect(updateCall[1].body.status_id).toBe(200);
|
|
653
|
+
expect(typeof updateCall[1].body.status_id).toBe('number');
|
|
654
|
+
expect(updateCall[1].body).not.toHaveProperty('status');
|
|
655
|
+
});
|
|
656
|
+
it('should convert tags string to array', async () => {
|
|
657
|
+
const mockExecute = createMockExecuteFunctions({
|
|
658
|
+
resource: 'job',
|
|
659
|
+
operation: 'update',
|
|
660
|
+
options: {},
|
|
661
|
+
jobId: '123',
|
|
662
|
+
updateFields: {
|
|
663
|
+
tags: 'urgent, high-priority',
|
|
664
|
+
},
|
|
665
|
+
});
|
|
666
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue({ id: '123' });
|
|
667
|
+
await node.execute.call(mockExecute);
|
|
668
|
+
const calls = mockExecute.helpers.httpRequestWithAuthentication.mock.calls;
|
|
669
|
+
const updateCall = calls.find((call) => call[1].method === 'PUT');
|
|
670
|
+
expect(updateCall[1].body.tags).toEqual(['urgent', 'high-priority']);
|
|
671
|
+
});
|
|
672
|
+
});
|
|
673
|
+
describe('delete', () => {
|
|
674
|
+
it('should delete job and return success', async () => {
|
|
675
|
+
const mockExecute = createMockExecuteFunctions({
|
|
676
|
+
resource: 'job',
|
|
677
|
+
operation: 'delete',
|
|
678
|
+
options: {},
|
|
679
|
+
jobId: '123',
|
|
680
|
+
});
|
|
681
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue({});
|
|
682
|
+
const result = await node.execute.call(mockExecute);
|
|
683
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
684
|
+
method: 'DELETE',
|
|
685
|
+
url: 'https://test.innotes.com/api/job/123',
|
|
686
|
+
}));
|
|
687
|
+
expect(result[0][0].json).toEqual({ success: true });
|
|
688
|
+
});
|
|
689
|
+
});
|
|
690
|
+
describe('search', () => {
|
|
691
|
+
it('should search by general term', async () => {
|
|
692
|
+
const mockExecute = createMockExecuteFunctions({
|
|
693
|
+
resource: 'job',
|
|
694
|
+
operation: 'search',
|
|
695
|
+
options: {},
|
|
696
|
+
searchMethod: 'general',
|
|
697
|
+
searchQuery: 'software engineer',
|
|
698
|
+
searchOptions: {},
|
|
699
|
+
});
|
|
700
|
+
const mockResponse = [{ id: '1', name: 'Software Engineer' }];
|
|
701
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
|
|
702
|
+
await node.execute.call(mockExecute);
|
|
703
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
704
|
+
qs: expect.objectContaining({
|
|
705
|
+
searchTerm: 'software engineer',
|
|
706
|
+
}),
|
|
707
|
+
}));
|
|
708
|
+
});
|
|
709
|
+
it('should search by ext_id', async () => {
|
|
710
|
+
const mockExecute = createMockExecuteFunctions({
|
|
711
|
+
resource: 'job',
|
|
712
|
+
operation: 'search',
|
|
713
|
+
options: {},
|
|
714
|
+
searchMethod: 'ext_id',
|
|
715
|
+
searchQuery: 'LI-123456',
|
|
716
|
+
searchOptions: {},
|
|
717
|
+
});
|
|
718
|
+
const mockResponse = [{ id: '1', ext_id: 'LI-123456' }];
|
|
719
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
|
|
720
|
+
await node.execute.call(mockExecute);
|
|
721
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
722
|
+
qs: expect.objectContaining({
|
|
723
|
+
ext_id: 'LI-123456',
|
|
724
|
+
}),
|
|
725
|
+
}));
|
|
726
|
+
});
|
|
727
|
+
it('should search by specific field (title)', async () => {
|
|
728
|
+
const mockExecute = createMockExecuteFunctions({
|
|
729
|
+
resource: 'job',
|
|
730
|
+
operation: 'search',
|
|
731
|
+
options: {},
|
|
732
|
+
searchMethod: 'title',
|
|
733
|
+
searchQuery: 'manager',
|
|
734
|
+
searchOptions: {},
|
|
735
|
+
});
|
|
736
|
+
const mockResponse = [{ id: '1' }];
|
|
737
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
|
|
738
|
+
await node.execute.call(mockExecute);
|
|
739
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
740
|
+
qs: expect.objectContaining({
|
|
741
|
+
searchTerm: 'manager',
|
|
742
|
+
searchField: 'title',
|
|
743
|
+
}),
|
|
744
|
+
}));
|
|
745
|
+
});
|
|
746
|
+
it('should apply additional filters', async () => {
|
|
747
|
+
const mockExecute = createMockExecuteFunctions({
|
|
748
|
+
resource: 'job',
|
|
749
|
+
operation: 'search',
|
|
750
|
+
options: {},
|
|
751
|
+
searchMethod: 'general',
|
|
752
|
+
searchQuery: 'developer',
|
|
753
|
+
searchOptions: {
|
|
754
|
+
remote_setting: 'remote',
|
|
755
|
+
status: 'Applied',
|
|
756
|
+
limit: 50,
|
|
757
|
+
},
|
|
758
|
+
});
|
|
759
|
+
const mockResponse = [];
|
|
760
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
|
|
761
|
+
await node.execute.call(mockExecute);
|
|
762
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
763
|
+
qs: expect.objectContaining({
|
|
764
|
+
remote_setting: 'remote',
|
|
765
|
+
status: 'Applied',
|
|
766
|
+
pageSize: 50,
|
|
767
|
+
}),
|
|
768
|
+
}));
|
|
769
|
+
});
|
|
770
|
+
});
|
|
771
|
+
describe('exists', () => {
|
|
772
|
+
it('should return exists:true when found by job_id', async () => {
|
|
773
|
+
const mockExecute = createMockExecuteFunctions({
|
|
774
|
+
resource: 'job',
|
|
775
|
+
operation: 'exists',
|
|
776
|
+
options: {},
|
|
777
|
+
id: '123',
|
|
778
|
+
});
|
|
779
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue({ id: '123' });
|
|
780
|
+
const result = await node.execute.call(mockExecute);
|
|
781
|
+
expect(result[0][0].json).toEqual({
|
|
782
|
+
exists: true,
|
|
783
|
+
found_by: 'job_id',
|
|
784
|
+
id: '123',
|
|
785
|
+
});
|
|
786
|
+
});
|
|
787
|
+
it('should return exists:true when found by ext_id', async () => {
|
|
788
|
+
const mockExecute = createMockExecuteFunctions({
|
|
789
|
+
resource: 'job',
|
|
790
|
+
operation: 'exists',
|
|
791
|
+
options: {},
|
|
792
|
+
id: 'LI-123',
|
|
793
|
+
});
|
|
794
|
+
// First call (get by ID) fails, second call (search by ext_id) succeeds
|
|
795
|
+
mockExecute.helpers.httpRequestWithAuthentication
|
|
796
|
+
.mockRejectedValueOnce(new Error('Not found'))
|
|
797
|
+
.mockResolvedValueOnce([{ id: '456', ext_id: 'LI-123' }]);
|
|
798
|
+
const result = await node.execute.call(mockExecute);
|
|
799
|
+
expect(result[0][0].json).toEqual({
|
|
800
|
+
exists: true,
|
|
801
|
+
found_by: 'ext_id',
|
|
802
|
+
id: 'LI-123',
|
|
803
|
+
});
|
|
804
|
+
});
|
|
805
|
+
it('should return exists:false when not found', async () => {
|
|
806
|
+
const mockExecute = createMockExecuteFunctions({
|
|
807
|
+
resource: 'job',
|
|
808
|
+
operation: 'exists',
|
|
809
|
+
options: {},
|
|
810
|
+
id: 'nonexistent',
|
|
811
|
+
});
|
|
812
|
+
mockExecute.helpers.httpRequestWithAuthentication
|
|
813
|
+
.mockRejectedValueOnce(new Error('Not found'))
|
|
814
|
+
.mockResolvedValueOnce([]);
|
|
815
|
+
const result = await node.execute.call(mockExecute);
|
|
816
|
+
expect(result[0][0].json).toEqual({
|
|
817
|
+
exists: false,
|
|
818
|
+
id: 'nonexistent',
|
|
819
|
+
});
|
|
820
|
+
});
|
|
821
|
+
});
|
|
822
|
+
});
|
|
823
|
+
// ========================================================================
|
|
824
|
+
// Status Operations Tests
|
|
825
|
+
// ========================================================================
|
|
826
|
+
describe('Status Operations', () => {
|
|
827
|
+
describe('getAll', () => {
|
|
828
|
+
it('should get all statuses by type', async () => {
|
|
829
|
+
const mockExecute = createMockExecuteFunctions({
|
|
830
|
+
resource: 'status',
|
|
831
|
+
operation: 'getAll',
|
|
832
|
+
options: {},
|
|
833
|
+
type: 'job',
|
|
834
|
+
});
|
|
835
|
+
const mockResponse = [
|
|
836
|
+
{ id: 1, name: 'Applied' },
|
|
837
|
+
{ id: 2, name: 'Interviewing' },
|
|
838
|
+
];
|
|
839
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
|
|
840
|
+
await node.execute.call(mockExecute);
|
|
841
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
842
|
+
method: 'GET',
|
|
843
|
+
url: 'https://test.innotes.com/api/statuses',
|
|
844
|
+
qs: { type: 'job' },
|
|
845
|
+
}));
|
|
846
|
+
});
|
|
847
|
+
});
|
|
848
|
+
describe('create', () => {
|
|
849
|
+
it('should create new status', async () => {
|
|
850
|
+
const mockExecute = createMockExecuteFunctions({
|
|
851
|
+
resource: 'status',
|
|
852
|
+
operation: 'create',
|
|
853
|
+
options: {},
|
|
854
|
+
name: 'New Status',
|
|
855
|
+
type: 'job',
|
|
856
|
+
color: '#ff0000',
|
|
857
|
+
});
|
|
858
|
+
const mockResponse = { id: 10, name: 'New Status' };
|
|
859
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
|
|
860
|
+
await node.execute.call(mockExecute);
|
|
861
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
862
|
+
method: 'POST',
|
|
863
|
+
url: 'https://test.innotes.com/api/statuses',
|
|
864
|
+
body: expect.objectContaining({
|
|
865
|
+
name: 'New Status',
|
|
866
|
+
type: 'job',
|
|
867
|
+
color: '#ff0000',
|
|
868
|
+
}),
|
|
869
|
+
}));
|
|
870
|
+
});
|
|
871
|
+
});
|
|
872
|
+
describe('update', () => {
|
|
873
|
+
it('should update status', async () => {
|
|
874
|
+
const mockExecute = createMockExecuteFunctions({
|
|
875
|
+
resource: 'status',
|
|
876
|
+
operation: 'update',
|
|
877
|
+
options: {},
|
|
878
|
+
statusId: '5',
|
|
879
|
+
updateFields: { name: 'Updated Status' },
|
|
880
|
+
});
|
|
881
|
+
const mockResponse = { id: 5, name: 'Updated Status' };
|
|
882
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
|
|
883
|
+
await node.execute.call(mockExecute);
|
|
884
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
885
|
+
method: 'PUT',
|
|
886
|
+
url: 'https://test.innotes.com/api/statuses/5',
|
|
887
|
+
}));
|
|
888
|
+
});
|
|
889
|
+
});
|
|
890
|
+
describe('delete', () => {
|
|
891
|
+
it('should delete status', async () => {
|
|
892
|
+
const mockExecute = createMockExecuteFunctions({
|
|
893
|
+
resource: 'status',
|
|
894
|
+
operation: 'delete',
|
|
895
|
+
options: {},
|
|
896
|
+
statusId: '5',
|
|
897
|
+
});
|
|
898
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue({});
|
|
899
|
+
const result = await node.execute.call(mockExecute);
|
|
900
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
901
|
+
method: 'DELETE',
|
|
902
|
+
url: 'https://test.innotes.com/api/statuses/5',
|
|
903
|
+
}));
|
|
904
|
+
expect(result[0][0].json).toEqual({ success: true });
|
|
905
|
+
});
|
|
906
|
+
});
|
|
907
|
+
});
|
|
908
|
+
// ========================================================================
|
|
909
|
+
// Tag Operations Tests
|
|
910
|
+
// ========================================================================
|
|
911
|
+
describe('Tag Operations', () => {
|
|
912
|
+
describe('getAll', () => {
|
|
913
|
+
it('should get all tags', async () => {
|
|
914
|
+
const mockExecute = createMockExecuteFunctions({
|
|
915
|
+
resource: 'tag',
|
|
916
|
+
operation: 'getAll',
|
|
917
|
+
options: {},
|
|
918
|
+
});
|
|
919
|
+
const mockResponse = [
|
|
920
|
+
{ value: 'urgent' },
|
|
921
|
+
{ value: 'follow-up' },
|
|
922
|
+
];
|
|
923
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
|
|
924
|
+
await node.execute.call(mockExecute);
|
|
925
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
926
|
+
method: 'GET',
|
|
927
|
+
url: 'https://test.innotes.com/api/tags',
|
|
928
|
+
}));
|
|
929
|
+
});
|
|
930
|
+
});
|
|
931
|
+
});
|
|
932
|
+
// ========================================================================
|
|
933
|
+
// User Operations Tests
|
|
934
|
+
// ========================================================================
|
|
935
|
+
describe('User Operations', () => {
|
|
936
|
+
describe('get', () => {
|
|
937
|
+
it('should get current user', async () => {
|
|
938
|
+
const mockExecute = createMockExecuteFunctions({
|
|
939
|
+
resource: 'user',
|
|
940
|
+
operation: 'get',
|
|
941
|
+
options: {},
|
|
942
|
+
});
|
|
943
|
+
const mockResponse = { id: '1', email: 'user@example.com', name: 'Test User' };
|
|
944
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
|
|
945
|
+
await node.execute.call(mockExecute);
|
|
946
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
947
|
+
method: 'GET',
|
|
948
|
+
url: 'https://test.innotes.com/api/user',
|
|
949
|
+
}));
|
|
950
|
+
});
|
|
951
|
+
});
|
|
952
|
+
describe('getCv', () => {
|
|
953
|
+
it('should get CV and parse job_preferences JSON', async () => {
|
|
954
|
+
const mockExecute = createMockExecuteFunctions({
|
|
955
|
+
resource: 'user',
|
|
956
|
+
operation: 'getCv',
|
|
957
|
+
options: {},
|
|
958
|
+
});
|
|
959
|
+
const mockResponse = {
|
|
960
|
+
cv: 'My resume content',
|
|
961
|
+
cv_updated_at: '2024-01-01T00:00:00Z',
|
|
962
|
+
job_preferences: JSON.stringify({
|
|
963
|
+
searchJobTitles: ['Software Engineer', 'Developer'],
|
|
964
|
+
locations: ['Remote', 'New York'],
|
|
965
|
+
blacklist: {
|
|
966
|
+
titleBlacklist: ['Sales'],
|
|
967
|
+
companyBlacklist: ['BadCorp'],
|
|
968
|
+
penalize: ['Consultant'],
|
|
131
969
|
},
|
|
970
|
+
}),
|
|
971
|
+
};
|
|
972
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
|
|
973
|
+
const result = await node.execute.call(mockExecute);
|
|
974
|
+
expect(result[0][0].json).toEqual(expect.objectContaining({
|
|
975
|
+
cv: 'My resume content',
|
|
976
|
+
searchJobTitles: 'Software Engineer, Developer',
|
|
977
|
+
locations: 'Remote, New York',
|
|
978
|
+
titleBlacklist: 'Sales',
|
|
979
|
+
companyBlacklist: 'BadCorp',
|
|
980
|
+
penalize: 'Consultant',
|
|
981
|
+
}));
|
|
982
|
+
});
|
|
983
|
+
it('should handle legacy blacklist structure', async () => {
|
|
984
|
+
const mockExecute = createMockExecuteFunctions({
|
|
985
|
+
resource: 'user',
|
|
986
|
+
operation: 'getCv',
|
|
987
|
+
options: {},
|
|
988
|
+
});
|
|
989
|
+
const mockResponse = {
|
|
990
|
+
cv: 'My resume',
|
|
991
|
+
job_preferences: JSON.stringify({
|
|
992
|
+
searchJobTitles: ['Engineer'],
|
|
993
|
+
locations: ['SF'],
|
|
994
|
+
titleBlacklist: ['Manager'],
|
|
995
|
+
companyBlacklist: ['OldCorp'],
|
|
996
|
+
}),
|
|
997
|
+
};
|
|
998
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
|
|
999
|
+
const result = await node.execute.call(mockExecute);
|
|
1000
|
+
expect(result[0][0].json).toEqual(expect.objectContaining({
|
|
1001
|
+
titleBlacklist: 'Manager',
|
|
1002
|
+
companyBlacklist: 'OldCorp',
|
|
1003
|
+
}));
|
|
1004
|
+
});
|
|
1005
|
+
it('should handle missing job_preferences', async () => {
|
|
1006
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1007
|
+
resource: 'user',
|
|
1008
|
+
operation: 'getCv',
|
|
1009
|
+
options: {},
|
|
1010
|
+
});
|
|
1011
|
+
const mockResponse = {
|
|
1012
|
+
cv: 'My resume',
|
|
1013
|
+
cv_updated_at: null,
|
|
1014
|
+
job_preferences: null,
|
|
1015
|
+
};
|
|
1016
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
|
|
1017
|
+
const result = await node.execute.call(mockExecute);
|
|
1018
|
+
expect(result[0][0].json).toEqual(expect.objectContaining({
|
|
1019
|
+
cv: 'My resume',
|
|
1020
|
+
searchJobTitles: '',
|
|
1021
|
+
locations: '',
|
|
1022
|
+
}));
|
|
1023
|
+
});
|
|
1024
|
+
});
|
|
1025
|
+
describe('update', () => {
|
|
1026
|
+
it('should update user fields', async () => {
|
|
1027
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1028
|
+
resource: 'user',
|
|
1029
|
+
operation: 'update',
|
|
1030
|
+
options: {},
|
|
1031
|
+
updateFields: { home_location: 'Boston, MA' },
|
|
1032
|
+
});
|
|
1033
|
+
const mockResponse = { id: '1', home_location: 'Boston, MA' };
|
|
1034
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
|
|
1035
|
+
await node.execute.call(mockExecute);
|
|
1036
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
1037
|
+
method: 'PUT',
|
|
1038
|
+
url: 'https://test.innotes.com/api/user',
|
|
1039
|
+
body: { home_location: 'Boston, MA' },
|
|
1040
|
+
}));
|
|
1041
|
+
});
|
|
1042
|
+
});
|
|
1043
|
+
});
|
|
1044
|
+
// ========================================================================
|
|
1045
|
+
// Automation Operations Tests
|
|
1046
|
+
// ========================================================================
|
|
1047
|
+
describe('Automation Operations', () => {
|
|
1048
|
+
describe('getConfig', () => {
|
|
1049
|
+
it('should get automation config successfully', async () => {
|
|
1050
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1051
|
+
resource: 'automation',
|
|
1052
|
+
operation: 'getConfig',
|
|
1053
|
+
options: {},
|
|
1054
|
+
});
|
|
1055
|
+
const mockResponse = {
|
|
1056
|
+
models: {
|
|
1057
|
+
low: 'gpt-4o-mini',
|
|
1058
|
+
medium: 'gpt-5-mini',
|
|
1059
|
+
high: 'gpt-5-mini',
|
|
132
1060
|
},
|
|
133
|
-
|
|
1061
|
+
};
|
|
1062
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
|
|
1063
|
+
const result = await node.execute.call(mockExecute);
|
|
1064
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
1065
|
+
method: 'GET',
|
|
1066
|
+
url: 'https://test.innotes.com/api/automation/config',
|
|
1067
|
+
}));
|
|
1068
|
+
expect(result[0][0].json).toEqual(mockResponse);
|
|
1069
|
+
});
|
|
1070
|
+
it('should handle API errors in getConfig', async () => {
|
|
1071
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1072
|
+
resource: 'automation',
|
|
1073
|
+
operation: 'getConfig',
|
|
1074
|
+
options: {},
|
|
1075
|
+
});
|
|
1076
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockRejectedValue(new Error('Unauthorized'));
|
|
1077
|
+
await expect(node.execute.call(mockExecute)).rejects.toThrow(/Failed to get automation config/i);
|
|
1078
|
+
});
|
|
1079
|
+
});
|
|
1080
|
+
describe('reportJobCreated', () => {
|
|
1081
|
+
it('should report with success status', async () => {
|
|
1082
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1083
|
+
resource: 'automation',
|
|
1084
|
+
operation: 'reportJobCreated',
|
|
1085
|
+
options: {},
|
|
1086
|
+
jobsAdded: 5,
|
|
1087
|
+
status: 'success',
|
|
1088
|
+
});
|
|
1089
|
+
// First call gets user, second reports callback
|
|
1090
|
+
mockExecute.helpers.httpRequestWithAuthentication
|
|
1091
|
+
.mockResolvedValueOnce({ id: 'user-123' })
|
|
1092
|
+
.mockResolvedValueOnce({ success: true });
|
|
1093
|
+
const result = await node.execute.call(mockExecute);
|
|
1094
|
+
const calls = mockExecute.helpers.httpRequestWithAuthentication.mock.calls;
|
|
1095
|
+
const callbackCall = calls.find((call) => call[1].url.includes('/api/automation/callback'));
|
|
1096
|
+
expect(callbackCall[1].body).toEqual(expect.objectContaining({
|
|
1097
|
+
userId: 'user-123',
|
|
1098
|
+
status: 'success',
|
|
1099
|
+
jobsAdded: 5,
|
|
1100
|
+
secret: 'test-callback-secret',
|
|
1101
|
+
}));
|
|
1102
|
+
expect(result[0][0].json).toEqual(expect.objectContaining({
|
|
1103
|
+
success: true,
|
|
1104
|
+
jobsAdded: 5,
|
|
1105
|
+
status: 'success',
|
|
1106
|
+
}));
|
|
1107
|
+
});
|
|
1108
|
+
it('should report with error status and message', async () => {
|
|
1109
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1110
|
+
resource: 'automation',
|
|
1111
|
+
operation: 'reportJobCreated',
|
|
1112
|
+
options: {},
|
|
1113
|
+
jobsAdded: 0,
|
|
1114
|
+
status: 'error',
|
|
1115
|
+
errorMessage: 'Failed to scrape jobs',
|
|
1116
|
+
});
|
|
1117
|
+
mockExecute.helpers.httpRequestWithAuthentication
|
|
1118
|
+
.mockResolvedValueOnce({ id: 'user-123' })
|
|
1119
|
+
.mockResolvedValueOnce({ success: true });
|
|
1120
|
+
await node.execute.call(mockExecute);
|
|
1121
|
+
const calls = mockExecute.helpers.httpRequestWithAuthentication.mock.calls;
|
|
1122
|
+
const callbackCall = calls.find((call) => call[1].url.includes('/api/automation/callback'));
|
|
1123
|
+
expect(callbackCall[1].body).toEqual(expect.objectContaining({
|
|
1124
|
+
status: 'error',
|
|
1125
|
+
error: 'Failed to scrape jobs',
|
|
1126
|
+
}));
|
|
1127
|
+
});
|
|
1128
|
+
it('should throw when callbackSecret not configured', async () => {
|
|
1129
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1130
|
+
resource: 'automation',
|
|
1131
|
+
operation: 'reportJobCreated',
|
|
1132
|
+
options: {},
|
|
1133
|
+
jobsAdded: 1,
|
|
1134
|
+
status: 'success',
|
|
1135
|
+
}, { callbackSecret: '' });
|
|
1136
|
+
await expect(node.execute.call(mockExecute)).rejects.toThrow(/callback secret/i);
|
|
1137
|
+
});
|
|
1138
|
+
});
|
|
1139
|
+
});
|
|
1140
|
+
// ========================================================================
|
|
1141
|
+
// Batch Processing Tests
|
|
1142
|
+
// ========================================================================
|
|
1143
|
+
describe('Batch Processing', () => {
|
|
1144
|
+
it('should process items in batches', async () => {
|
|
1145
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1146
|
+
resource: 'contact',
|
|
1147
|
+
operation: 'get',
|
|
1148
|
+
options: { batchSize: 2, timeoutBetweenBatches: 10 },
|
|
1149
|
+
contactId: '1',
|
|
1150
|
+
});
|
|
1151
|
+
// Simulate 3 input items
|
|
1152
|
+
mockExecute.getInputData.mockReturnValue([
|
|
1153
|
+
{ json: {} },
|
|
1154
|
+
{ json: {} },
|
|
1155
|
+
{ json: {} },
|
|
134
1156
|
]);
|
|
1157
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue({ id: '1' });
|
|
1158
|
+
await node.execute.call(mockExecute);
|
|
1159
|
+
// Should have made 3 API calls (one per item)
|
|
1160
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledTimes(3);
|
|
1161
|
+
});
|
|
1162
|
+
it('should respect batchSize option', async () => {
|
|
1163
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1164
|
+
resource: 'contact',
|
|
1165
|
+
operation: 'get',
|
|
1166
|
+
options: { batchSize: 5 },
|
|
1167
|
+
contactId: '1',
|
|
1168
|
+
});
|
|
1169
|
+
mockExecute.getInputData.mockReturnValue([{ json: {} }]);
|
|
1170
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue({ id: '1' });
|
|
1171
|
+
await node.execute.call(mockExecute);
|
|
1172
|
+
// Verify options were read
|
|
1173
|
+
expect(mockExecute.getNodeParameter).toHaveBeenCalledWith('options', 0, {});
|
|
1174
|
+
});
|
|
1175
|
+
});
|
|
1176
|
+
// ========================================================================
|
|
1177
|
+
// Error Handling Tests
|
|
1178
|
+
// ========================================================================
|
|
1179
|
+
describe('Error Handling', () => {
|
|
1180
|
+
it('should handle API errors gracefully', async () => {
|
|
1181
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1182
|
+
resource: 'contact',
|
|
1183
|
+
operation: 'get',
|
|
1184
|
+
options: {},
|
|
1185
|
+
contactId: '999',
|
|
1186
|
+
});
|
|
1187
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockRejectedValue(new Error('Not found'));
|
|
1188
|
+
await expect(node.execute.call(mockExecute)).rejects.toThrow();
|
|
1189
|
+
});
|
|
1190
|
+
it('should continue on fail when enabled', async () => {
|
|
1191
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1192
|
+
resource: 'contact',
|
|
1193
|
+
operation: 'get',
|
|
1194
|
+
options: {},
|
|
1195
|
+
contactId: '999',
|
|
1196
|
+
});
|
|
1197
|
+
mockExecute.continueOnFail.mockReturnValue(true);
|
|
1198
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockRejectedValue(new Error('API Error'));
|
|
1199
|
+
const result = await node.execute.call(mockExecute);
|
|
1200
|
+
// Should return error in result instead of throwing
|
|
1201
|
+
expect(result[0][0].json).toEqual({ error: 'API Error' });
|
|
135
1202
|
});
|
|
136
|
-
it('should
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
1203
|
+
it('should include request parameters in job creation error messages', async () => {
|
|
1204
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1205
|
+
resource: 'job',
|
|
1206
|
+
operation: 'create',
|
|
1207
|
+
options: {},
|
|
1208
|
+
name: 'Test Job',
|
|
1209
|
+
company_name: 'Test Corp',
|
|
1210
|
+
});
|
|
1211
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockRejectedValue(new Error('Server error'));
|
|
1212
|
+
await expect(node.execute.call(mockExecute)).rejects.toThrow(/Test Job|Test Corp/);
|
|
1213
|
+
});
|
|
1214
|
+
});
|
|
1215
|
+
// ========================================================================
|
|
1216
|
+
// Unsupported Operations Tests
|
|
1217
|
+
// ========================================================================
|
|
1218
|
+
describe('Unsupported Operations', () => {
|
|
1219
|
+
it('should handle unsupported resource gracefully', async () => {
|
|
1220
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1221
|
+
resource: 'unsupported',
|
|
1222
|
+
operation: 'get',
|
|
1223
|
+
options: {},
|
|
1224
|
+
});
|
|
1225
|
+
const result = await node.execute.call(mockExecute);
|
|
1226
|
+
expect(result).toBeDefined();
|
|
1227
|
+
expect(Array.isArray(result)).toBe(true);
|
|
1228
|
+
});
|
|
1229
|
+
it('should throw error for unsupported contact operation', async () => {
|
|
1230
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1231
|
+
resource: 'contact',
|
|
1232
|
+
operation: 'unsupported_op',
|
|
1233
|
+
options: {},
|
|
1234
|
+
});
|
|
1235
|
+
await expect(node.execute.call(mockExecute)).rejects.toThrow(/not supported.*contact/i);
|
|
1236
|
+
});
|
|
1237
|
+
it('should throw error for unsupported note operation', async () => {
|
|
1238
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1239
|
+
resource: 'note',
|
|
1240
|
+
operation: 'unsupported_op',
|
|
1241
|
+
options: {},
|
|
1242
|
+
});
|
|
1243
|
+
await expect(node.execute.call(mockExecute)).rejects.toThrow(/not supported.*note/i);
|
|
1244
|
+
});
|
|
1245
|
+
it('should throw error for unsupported job operation', async () => {
|
|
1246
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1247
|
+
resource: 'job',
|
|
1248
|
+
operation: 'unsupported_op',
|
|
1249
|
+
options: {},
|
|
1250
|
+
});
|
|
1251
|
+
await expect(node.execute.call(mockExecute)).rejects.toThrow(/not supported.*job/i);
|
|
1252
|
+
});
|
|
1253
|
+
it('should throw error for unsupported status operation', async () => {
|
|
1254
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1255
|
+
resource: 'status',
|
|
1256
|
+
operation: 'unsupported_op',
|
|
1257
|
+
options: {},
|
|
1258
|
+
});
|
|
1259
|
+
await expect(node.execute.call(mockExecute)).rejects.toThrow(/not supported.*status/i);
|
|
1260
|
+
});
|
|
1261
|
+
it('should throw error for unsupported tag operation', async () => {
|
|
1262
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1263
|
+
resource: 'tag',
|
|
1264
|
+
operation: 'unsupported_op',
|
|
1265
|
+
options: {},
|
|
1266
|
+
});
|
|
1267
|
+
await expect(node.execute.call(mockExecute)).rejects.toThrow(/not supported.*tag/i);
|
|
1268
|
+
});
|
|
1269
|
+
it('should throw error for unsupported user operation', async () => {
|
|
1270
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1271
|
+
resource: 'user',
|
|
1272
|
+
operation: 'unsupported_op',
|
|
1273
|
+
options: {},
|
|
1274
|
+
});
|
|
1275
|
+
await expect(node.execute.call(mockExecute)).rejects.toThrow(/not supported.*user/i);
|
|
1276
|
+
});
|
|
1277
|
+
it('should throw error for unsupported automation operation', async () => {
|
|
1278
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1279
|
+
resource: 'automation',
|
|
1280
|
+
operation: 'unsupported_op',
|
|
1281
|
+
options: {},
|
|
1282
|
+
});
|
|
1283
|
+
await expect(node.execute.call(mockExecute)).rejects.toThrow(/not supported.*automation/i);
|
|
1284
|
+
});
|
|
1285
|
+
});
|
|
1286
|
+
// ========================================================================
|
|
1287
|
+
// Additional Error Handling Tests for Branch Coverage
|
|
1288
|
+
// ========================================================================
|
|
1289
|
+
describe('Additional Error Handling', () => {
|
|
1290
|
+
describe('Contact Operations', () => {
|
|
1291
|
+
it('should include request params in contact create error', async () => {
|
|
1292
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1293
|
+
resource: 'contact',
|
|
1294
|
+
operation: 'create',
|
|
1295
|
+
options: {},
|
|
1296
|
+
name: 'Test Contact',
|
|
1297
|
+
linkedin_key: 'test-key',
|
|
1298
|
+
linkedin_user: 'test-user',
|
|
1299
|
+
location: 'New York',
|
|
1300
|
+
current_company: 'Test Corp',
|
|
1301
|
+
picture_url: 'http://example.com/pic.jpg',
|
|
1302
|
+
tags: 'tag1,tag2',
|
|
1303
|
+
status_id: '5',
|
|
1304
|
+
});
|
|
1305
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockRejectedValue(new Error('Validation error'));
|
|
1306
|
+
await expect(node.execute.call(mockExecute)).rejects.toThrow(/Test Contact|Validation error/);
|
|
1307
|
+
});
|
|
1308
|
+
});
|
|
1309
|
+
describe('Automation Operations', () => {
|
|
1310
|
+
it('should throw when getting user ID fails', async () => {
|
|
1311
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1312
|
+
resource: 'automation',
|
|
1313
|
+
operation: 'reportJobCreated',
|
|
1314
|
+
options: {},
|
|
1315
|
+
jobsAdded: 5,
|
|
1316
|
+
status: 'success',
|
|
1317
|
+
});
|
|
1318
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockRejectedValue(new Error('User not found'));
|
|
1319
|
+
await expect(node.execute.call(mockExecute)).rejects.toThrow(/Failed to get user ID/i);
|
|
1320
|
+
});
|
|
1321
|
+
it('should throw when callback request fails', async () => {
|
|
1322
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1323
|
+
resource: 'automation',
|
|
1324
|
+
operation: 'reportJobCreated',
|
|
1325
|
+
options: {},
|
|
1326
|
+
jobsAdded: 5,
|
|
1327
|
+
status: 'success',
|
|
1328
|
+
});
|
|
1329
|
+
mockExecute.helpers.httpRequestWithAuthentication
|
|
1330
|
+
.mockResolvedValueOnce({ id: 'user-123' }) // User request succeeds
|
|
1331
|
+
.mockRejectedValueOnce(new Error('Callback failed')); // Callback fails
|
|
1332
|
+
await expect(node.execute.call(mockExecute)).rejects.toThrow(/Failed to report job created/i);
|
|
1333
|
+
});
|
|
1334
|
+
});
|
|
1335
|
+
describe('Job Operations', () => {
|
|
1336
|
+
it('should throw when status lookup fails in job create', async () => {
|
|
1337
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1338
|
+
resource: 'job',
|
|
1339
|
+
operation: 'create',
|
|
1340
|
+
options: {},
|
|
1341
|
+
name: 'Test Job',
|
|
1342
|
+
company_name: 'Test Corp',
|
|
1343
|
+
status: 'Applied',
|
|
1344
|
+
});
|
|
1345
|
+
// Status lookup fails with network error
|
|
1346
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockRejectedValue(new Error('Network error'));
|
|
1347
|
+
await expect(node.execute.call(mockExecute)).rejects.toThrow(/Failed to lookup status/i);
|
|
1348
|
+
});
|
|
1349
|
+
it('should throw when status not found in job update', async () => {
|
|
1350
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1351
|
+
resource: 'job',
|
|
1352
|
+
operation: 'update',
|
|
1353
|
+
options: {},
|
|
1354
|
+
jobId: '123',
|
|
1355
|
+
updateFields: { status: 'NonExistentStatus' },
|
|
1356
|
+
});
|
|
1357
|
+
// Return empty statuses array
|
|
1358
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValueOnce([
|
|
1359
|
+
{ id: '1', name: 'Applied' },
|
|
1360
|
+
{ id: '2', name: 'Interviewing' },
|
|
1361
|
+
]);
|
|
1362
|
+
await expect(node.execute.call(mockExecute)).rejects.toThrow(/NonExistentStatus.*not found/i);
|
|
1363
|
+
});
|
|
1364
|
+
it('should throw when status lookup fails in job update', async () => {
|
|
1365
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1366
|
+
resource: 'job',
|
|
1367
|
+
operation: 'update',
|
|
1368
|
+
options: {},
|
|
1369
|
+
jobId: '123',
|
|
1370
|
+
updateFields: { status: 'Applied' },
|
|
1371
|
+
});
|
|
1372
|
+
// Status lookup fails with network error
|
|
1373
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockRejectedValue(new Error('Network error'));
|
|
1374
|
+
await expect(node.execute.call(mockExecute)).rejects.toThrow(/Failed to lookup status/i);
|
|
1375
|
+
});
|
|
1376
|
+
it('should return exists:false with error when search throws', async () => {
|
|
1377
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1378
|
+
resource: 'job',
|
|
1379
|
+
operation: 'exists',
|
|
1380
|
+
options: {},
|
|
1381
|
+
existsType: 'byJobId',
|
|
1382
|
+
id: '123',
|
|
1383
|
+
});
|
|
1384
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockRejectedValue(new Error('Search failed'));
|
|
1385
|
+
const result = await node.execute.call(mockExecute);
|
|
1386
|
+
expect(result[0][0].json).toEqual(expect.objectContaining({
|
|
1387
|
+
exists: false,
|
|
1388
|
+
id: '123',
|
|
1389
|
+
error: 'Search failed',
|
|
1390
|
+
}));
|
|
1391
|
+
});
|
|
1392
|
+
});
|
|
1393
|
+
describe('Job Search Types', () => {
|
|
1394
|
+
it('should search by company field', async () => {
|
|
1395
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1396
|
+
resource: 'job',
|
|
1397
|
+
operation: 'search',
|
|
1398
|
+
options: {},
|
|
1399
|
+
searchMethod: 'company',
|
|
1400
|
+
searchQuery: 'Google',
|
|
1401
|
+
searchOptions: { limit: 50 },
|
|
1402
|
+
});
|
|
1403
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue([]);
|
|
1404
|
+
await node.execute.call(mockExecute);
|
|
1405
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
1406
|
+
qs: expect.objectContaining({
|
|
1407
|
+
searchTerm: 'Google',
|
|
1408
|
+
searchField: 'company',
|
|
1409
|
+
}),
|
|
1410
|
+
}));
|
|
1411
|
+
});
|
|
1412
|
+
it('should search by location field', async () => {
|
|
1413
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1414
|
+
resource: 'job',
|
|
1415
|
+
operation: 'search',
|
|
1416
|
+
options: {},
|
|
1417
|
+
searchMethod: 'location',
|
|
1418
|
+
searchQuery: 'San Francisco',
|
|
1419
|
+
searchOptions: { limit: 50 },
|
|
1420
|
+
});
|
|
1421
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue([]);
|
|
1422
|
+
await node.execute.call(mockExecute);
|
|
1423
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
1424
|
+
qs: expect.objectContaining({
|
|
1425
|
+
searchTerm: 'San Francisco',
|
|
1426
|
+
searchField: 'location',
|
|
1427
|
+
}),
|
|
1428
|
+
}));
|
|
1429
|
+
});
|
|
1430
|
+
it('should search by description field', async () => {
|
|
1431
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1432
|
+
resource: 'job',
|
|
1433
|
+
operation: 'search',
|
|
1434
|
+
options: {},
|
|
1435
|
+
searchMethod: 'description',
|
|
1436
|
+
searchQuery: 'React',
|
|
1437
|
+
searchOptions: { limit: 50 },
|
|
1438
|
+
});
|
|
1439
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue([]);
|
|
1440
|
+
await node.execute.call(mockExecute);
|
|
1441
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
1442
|
+
qs: expect.objectContaining({
|
|
1443
|
+
searchTerm: 'React',
|
|
1444
|
+
searchField: 'description',
|
|
1445
|
+
}),
|
|
1446
|
+
}));
|
|
1447
|
+
});
|
|
1448
|
+
it('should use default search when method is unknown', async () => {
|
|
1449
|
+
const mockExecute = createMockExecuteFunctions({
|
|
1450
|
+
resource: 'job',
|
|
1451
|
+
operation: 'search',
|
|
1452
|
+
options: {},
|
|
1453
|
+
searchMethod: 'unknown_method',
|
|
1454
|
+
searchQuery: 'test query',
|
|
1455
|
+
searchOptions: { limit: 50 },
|
|
1456
|
+
});
|
|
1457
|
+
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue([]);
|
|
1458
|
+
await node.execute.call(mockExecute);
|
|
1459
|
+
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
|
|
1460
|
+
qs: expect.objectContaining({
|
|
1461
|
+
searchTerm: 'test query',
|
|
1462
|
+
}),
|
|
1463
|
+
}));
|
|
1464
|
+
});
|
|
140
1465
|
});
|
|
141
1466
|
});
|
|
142
1467
|
});
|