n8n-nodes-innotes 2.0.16 → 2.0.18

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