n8n-nodes-innotes
Version:
N8N node for InNotes CRM API integration
1,532 lines • 75.8 kB
JavaScript
"use strict";
/**
* Author: marco
* Last updated: 2025-02-14
* Comprehensive test coverage for InNotes n8n node
*/
Object.defineProperty(exports, "__esModule", { value: true });
const InNotes_node_1 = require("../nodes/InNotes/InNotes.node");
// ============================================================================
// Test Helpers
// ============================================================================
/**
* Creates a mock IExecuteFunctions object with customizable parameters
*/
function createMockExecuteFunctions(params = {}, credentials = {}) {
const defaultCredentials = {
baseUrl: 'https://test.innotes.com',
token: 'test-token',
callbackSecret: 'test-callback-secret',
...credentials,
};
return {
getInputData: jest.fn().mockReturnValue([{ json: {} }]),
getNodeParameter: jest.fn().mockImplementation((param, _index, defaultValue) => {
var _a;
return (_a = params[param]) !== null && _a !== void 0 ? _a : defaultValue;
}),
getCredentials: jest.fn().mockResolvedValue(defaultCredentials),
continueOnFail: jest.fn().mockReturnValue(false),
getNode: jest.fn().mockReturnValue({ name: 'InNotes' }),
helpers: {
httpRequestWithAuthentication: jest.fn(),
httpRequest: jest.fn(),
requestWithAuthenticationPaginated: jest.fn(),
request: jest.fn(),
requestWithAuthentication: jest.fn(),
requestOAuth2: jest.fn(),
requestOAuth1: jest.fn(),
returnJsonArray: jest.fn((data) => data.map((item) => ({ json: item }))),
},
};
}
/**
* Creates a mock ILoadOptionsFunctions object
*/
function createMockLoadOptionsFunctions(credentials = {}) {
const defaultCredentials = {
baseUrl: 'https://test.innotes.com',
token: 'test-token',
};
return {
getCredentials: jest.fn().mockResolvedValue({ ...defaultCredentials, ...credentials }),
getNode: jest.fn().mockReturnValue({ name: 'InNotes' }),
helpers: {
httpRequestWithAuthentication: jest.fn(),
},
};
}
// ============================================================================
// Tests
// ============================================================================
describe('InNotes Node', () => {
let node;
beforeEach(() => {
node = new InNotes_node_1.InNotes();
jest.clearAllMocks();
});
// ========================================================================
// Node Properties Tests
// ========================================================================
describe('Node Properties', () => {
it('should have correct description', () => {
expect(node.description.displayName).toBe('InNotes');
expect(node.description.name).toBe('inNotes');
expect(node.description.group).toEqual(['output']);
expect(node.description.version).toBe(1);
});
it('should have correct credentials', () => {
expect(node.description.credentials).toEqual([
{
name: 'inNotesApi',
required: true,
},
]);
});
it('should have correct inputs and outputs', () => {
expect(node.description.inputs).toBeDefined();
expect(node.description.outputs).toBeDefined();
});
it('should have all expected resources', () => {
var _a;
const resourceProperty = (_a = node.description.properties) === null || _a === void 0 ? void 0 : _a.find((prop) => prop.name === 'resource' && prop.type === 'options');
expect(resourceProperty).toBeDefined();
const options = resourceProperty === null || resourceProperty === void 0 ? void 0 : resourceProperty.options;
const resourceNames = options === null || options === void 0 ? void 0 : options.map((o) => o.value);
expect(resourceNames).toContain('automation');
expect(resourceNames).toContain('contact');
expect(resourceNames).toContain('note');
expect(resourceNames).toContain('job');
expect(resourceNames).toContain('status');
expect(resourceNames).toContain('tag');
expect(resourceNames).toContain('user');
});
});
// ========================================================================
// Load Options Tests
// ========================================================================
describe('Load Options', () => {
describe('jobStatuses', () => {
it('should load job statuses from API', async () => {
const mockLoadOptions = createMockLoadOptionsFunctions();
const mockStatuses = [
{ id: 1, name: 'Applied' },
{ id: 2, name: 'Interviewing' },
{ id: 3, name: 'Offer' },
];
mockLoadOptions.helpers.httpRequestWithAuthentication.mockResolvedValue(mockStatuses);
const result = await node.methods.loadOptions.jobStatuses.call(mockLoadOptions);
expect(result).toEqual([
{ name: 'Applied', value: 'Applied' },
{ name: 'Interviewing', value: 'Interviewing' },
{ name: 'Offer', value: 'Offer' },
]);
});
it('should handle single status response', async () => {
const mockLoadOptions = createMockLoadOptionsFunctions();
const mockStatus = { id: 1, name: 'Applied' };
mockLoadOptions.helpers.httpRequestWithAuthentication.mockResolvedValue(mockStatus);
const result = await node.methods.loadOptions.jobStatuses.call(mockLoadOptions);
expect(result).toEqual([{ name: 'Applied', value: 'Applied' }]);
});
it('should throw error on API failure', async () => {
const mockLoadOptions = createMockLoadOptionsFunctions();
mockLoadOptions.helpers.httpRequestWithAuthentication.mockRejectedValue(new Error('API Error'));
await expect(node.methods.loadOptions.jobStatuses.call(mockLoadOptions)).rejects.toThrow();
});
});
});
// ========================================================================
// Contact Operations Tests
// ========================================================================
describe('Contact Operations', () => {
describe('create', () => {
it('should create contact with required fields', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'contact',
operation: 'create',
options: {},
name: 'John Doe',
linkedin_key: 'johndoe123',
});
const mockResponse = { id: '1', name: 'John Doe' };
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
const result = await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
method: 'POST',
url: 'https://test.innotes.com/api/contacts',
body: expect.objectContaining({
name: 'John Doe',
linkedin_key: 'johndoe123',
}),
}));
expect(result[0]).toBeDefined();
});
it('should include optional fields when provided', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'contact',
operation: 'create',
options: {},
name: 'John Doe',
linkedin_key: 'johndoe123',
linkedin_user: 'john.doe',
location: 'New York',
current_company: 'Acme Inc',
picture_url: 'https://example.com/photo.jpg',
tags: 'vip,prospect',
status_id: '1',
});
const mockResponse = { id: '1', name: 'John Doe' };
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
body: expect.objectContaining({
name: 'John Doe',
linkedin_key: 'johndoe123',
linkedin_user: 'john.doe',
location: 'New York',
current_company: 'Acme Inc',
picture_url: 'https://example.com/photo.jpg',
tags: ['vip', 'prospect'],
status_id: '1',
}),
}));
});
});
describe('get', () => {
it('should get contact by ID', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'contact',
operation: 'get',
options: {},
contactId: '123',
});
const mockResponse = { id: '123', name: 'Test Contact' };
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
const result = await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
method: 'GET',
url: 'https://test.innotes.com/api/contacts/123',
}));
expect(result[0]).toBeDefined();
});
});
describe('getAll', () => {
it('should get all contacts with pagination', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'contact',
operation: 'getAll',
options: {},
returnAll: false,
limit: 10,
});
const mockResponse = [{ id: '1' }, { id: '2' }];
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
const result = await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
method: 'GET',
url: 'https://test.innotes.com/api/contacts',
qs: expect.objectContaining({
pageSize: 10,
page: 1,
}),
}));
expect(result[0]).toBeDefined();
});
it('should apply search and tag filters', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'contact',
operation: 'getAll',
options: {},
returnAll: false,
limit: 24,
search: 'john',
tags: 'vip',
});
const mockResponse = [{ id: '1' }];
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
qs: expect.objectContaining({
search: 'john',
tags: 'vip',
}),
}));
});
});
describe('update', () => {
it('should update contact fields', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'contact',
operation: 'update',
options: {},
contactId: '123',
updateFields: {
name: 'Updated Name',
location: 'Boston',
},
});
const mockResponse = { id: '123', name: 'Updated Name' };
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
method: 'PUT',
url: 'https://test.innotes.com/api/contacts/123',
body: {
name: 'Updated Name',
location: 'Boston',
},
}));
});
});
describe('delete', () => {
it('should delete contact and return success', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'contact',
operation: 'delete',
options: {},
contactId: '123',
});
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue({});
const result = await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
method: 'DELETE',
url: 'https://test.innotes.com/api/contacts/123',
}));
expect(result[0][0].json).toEqual({ success: true });
});
});
});
// ========================================================================
// Note Operations Tests
// ========================================================================
describe('Note Operations', () => {
describe('create', () => {
it('should create note with required fields', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'note',
operation: 'create',
options: {},
content: 'Test note content',
contactId: '123',
});
const mockResponse = { id: '1', text: 'Test note content' };
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
const result = await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
method: 'POST',
url: 'https://test.innotes.com/api/note',
body: expect.objectContaining({
content: 'Test note content',
contact_id: '123',
}),
}));
expect(result[0]).toBeDefined();
});
it('should include visibility and ext_table_name when provided', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'note',
operation: 'create',
options: {},
content: 'Test note',
contactId: '123',
visibility: 'public',
ext_table_name: 'jobs',
});
const mockResponse = { id: '1' };
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
body: expect.objectContaining({
visibility: 'public',
ext_table_name: 'jobs',
}),
}));
});
});
describe('get', () => {
it('should get note by ID', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'note',
operation: 'get',
options: {},
noteId: '456',
});
const mockResponse = { id: '456', text: 'Test note' };
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
method: 'GET',
url: 'https://test.innotes.com/api/note/456',
}));
});
});
describe('getAll', () => {
it('should get all notes for a contact', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'note',
operation: 'getAll',
options: {},
contactId: '123',
returnAll: false,
limit: 10,
});
const mockResponse = [{ id: '1' }, { id: '2' }];
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
method: 'GET',
url: 'https://test.innotes.com/api/note',
qs: expect.objectContaining({
contact_id: '123',
}),
}));
});
});
describe('update', () => {
it('should update note', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'note',
operation: 'update',
options: {},
noteId: '456',
updateFields: { text: 'Updated content' },
});
const mockResponse = { id: '456' };
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
method: 'PUT',
url: 'https://test.innotes.com/api/note/456',
}));
});
});
describe('delete', () => {
it('should delete note', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'note',
operation: 'delete',
options: {},
noteId: '456',
});
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue({});
const result = await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
method: 'DELETE',
url: 'https://test.innotes.com/api/note/456',
}));
expect(result[0][0].json).toEqual({ success: true });
});
});
});
// ========================================================================
// Job Operations Tests (HIGH PRIORITY - Bug Fix Verification)
// ========================================================================
describe('Job Operations', () => {
describe('create', () => {
it('should create job with status lookup and send status_id as integer', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'create',
options: {},
name: 'Software Engineer',
company_name: 'Tech Corp',
status: 'Interested',
});
// First call returns statuses, second creates job
mockExecute.helpers.httpRequestWithAuthentication
.mockResolvedValueOnce([
{ id: 100, name: 'Applied' },
{ id: 200, name: 'Interested' },
{ id: 300, name: 'Rejected' },
])
.mockResolvedValueOnce({ id: '1' });
await node.execute.call(mockExecute);
// Verify the job creation call
const calls = mockExecute.helpers.httpRequestWithAuthentication.mock.calls;
const createJobCall = calls.find((call) => call[1].method === 'POST' && call[1].url.includes('/api/job'));
expect(createJobCall).toBeDefined();
expect(createJobCall[1].body.status_id).toBe(200); // Should be integer, not string
expect(typeof createJobCall[1].body.status_id).toBe('number');
});
it('should NOT include status field in request body', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'create',
options: {},
name: 'Software Engineer',
company_name: 'Tech Corp',
status: 'Interested',
});
mockExecute.helpers.httpRequestWithAuthentication
.mockResolvedValueOnce([{ id: 200, name: 'Interested' }])
.mockResolvedValueOnce({ id: '1' });
await node.execute.call(mockExecute);
const calls = mockExecute.helpers.httpRequestWithAuthentication.mock.calls;
const createJobCall = calls.find((call) => call[1].method === 'POST' && call[1].url.includes('/api/job'));
expect(createJobCall[1].body).not.toHaveProperty('status');
});
it('should exclude picture_url when value is "Not Available"', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'create',
options: {},
name: 'Software Engineer',
company_name: 'Tech Corp',
picture_url: 'Not Available',
});
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue({ id: '1' });
await node.execute.call(mockExecute);
const calls = mockExecute.helpers.httpRequestWithAuthentication.mock.calls;
const createJobCall = calls.find((call) => call[1].method === 'POST');
expect(createJobCall[1].body).not.toHaveProperty('picture_url');
});
it('should include picture_url when value is valid URL', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'create',
options: {},
name: 'Software Engineer',
company_name: 'Tech Corp',
picture_url: 'https://example.com/logo.png',
});
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue({ id: '1' });
await node.execute.call(mockExecute);
const calls = mockExecute.helpers.httpRequestWithAuthentication.mock.calls;
const createJobCall = calls.find((call) => call[1].method === 'POST');
expect(createJobCall[1].body.picture_url).toBe('https://example.com/logo.png');
});
it('should handle tags as comma-separated string converted to array', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'create',
options: {},
name: 'Software Engineer',
company_name: 'Tech Corp',
tags: 'remote, senior, python',
});
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue({ id: '1' });
await node.execute.call(mockExecute);
const calls = mockExecute.helpers.httpRequestWithAuthentication.mock.calls;
const createJobCall = calls.find((call) => call[1].method === 'POST');
expect(createJobCall[1].body.tags).toEqual(['remote', 'senior', 'python']);
});
it('should throw error when status name not found', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'create',
options: {},
name: 'Software Engineer',
company_name: 'Tech Corp',
status: 'NonExistentStatus',
});
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue([
{ id: 1, name: 'Applied' },
{ id: 2, name: 'Rejected' },
]);
await expect(node.execute.call(mockExecute)).rejects.toThrow(/NonExistentStatus.*not found/);
});
it('should include all optional fields when provided', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'create',
options: {},
name: 'Software Engineer',
company_name: 'Tech Corp',
description: 'Great opportunity',
location: 'Remote',
remote_setting: 'Remote',
company_url: 'https://techcorp.com',
provider: 'linkedin',
url: 'https://linkedin.com/jobs/123',
ext_id: 'LI-123',
});
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue({ id: '1' });
await node.execute.call(mockExecute);
const calls = mockExecute.helpers.httpRequestWithAuthentication.mock.calls;
const createJobCall = calls.find((call) => call[1].method === 'POST');
expect(createJobCall[1].body).toEqual(expect.objectContaining({
name: 'Software Engineer',
company_name: 'Tech Corp',
description: 'Great opportunity',
location: 'Remote',
remote_setting: 'Remote',
company_url: 'https://techcorp.com',
provider: 'linkedin',
url: 'https://linkedin.com/jobs/123',
ext_id: 'LI-123',
}));
});
});
describe('get', () => {
it('should get job by ID', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'get',
options: {},
jobId: '123',
});
const mockResponse = { id: '123', name: 'Test Job' };
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
method: 'GET',
url: 'https://test.innotes.com/api/job/123',
}));
});
it('should handle job not found error', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'get',
options: {},
jobId: '999',
});
mockExecute.helpers.httpRequestWithAuthentication.mockRejectedValue(new Error('Job not found'));
await expect(node.execute.call(mockExecute)).rejects.toThrow();
});
});
describe('getAll', () => {
it('should get all jobs with pagination', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'getAll',
options: {},
returnAll: false,
limit: 10,
});
const mockResponse = [{ id: '1' }, { id: '2' }];
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
method: 'GET',
url: 'https://test.innotes.com/api/job',
qs: expect.objectContaining({
pageSize: 10,
page: 1,
}),
}));
});
it('should apply search filters', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'getAll',
options: {},
returnAll: false,
limit: 24,
search: 'engineer',
tags: 'remote',
});
const mockResponse = [{ id: '1' }];
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
qs: expect.objectContaining({
search: 'engineer',
tags: 'remote',
}),
}));
});
});
describe('update', () => {
it('should update job with status lookup as integer', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'update',
options: {},
jobId: '123',
updateFields: {
status: 'Interviewing',
},
});
mockExecute.helpers.httpRequestWithAuthentication
.mockResolvedValueOnce([
{ id: 100, name: 'Applied' },
{ id: 200, name: 'Interviewing' },
])
.mockResolvedValueOnce({ id: '123' });
await node.execute.call(mockExecute);
const calls = mockExecute.helpers.httpRequestWithAuthentication.mock.calls;
const updateCall = calls.find((call) => call[1].method === 'PUT');
expect(updateCall[1].body.status_id).toBe(200);
expect(typeof updateCall[1].body.status_id).toBe('number');
expect(updateCall[1].body).not.toHaveProperty('status');
});
it('should convert tags string to array', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'update',
options: {},
jobId: '123',
updateFields: {
tags: 'urgent, high-priority',
},
});
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue({ id: '123' });
await node.execute.call(mockExecute);
const calls = mockExecute.helpers.httpRequestWithAuthentication.mock.calls;
const updateCall = calls.find((call) => call[1].method === 'PUT');
expect(updateCall[1].body.tags).toEqual(['urgent', 'high-priority']);
});
});
describe('delete', () => {
it('should delete job and return success', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'delete',
options: {},
jobId: '123',
});
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue({});
const result = await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
method: 'DELETE',
url: 'https://test.innotes.com/api/job/123',
}));
expect(result[0][0].json).toEqual({ success: true });
});
});
describe('search', () => {
it('should search by general term', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'search',
options: {},
searchMethod: 'general',
searchQuery: 'software engineer',
searchOptions: {},
});
const mockResponse = [{ id: '1', name: 'Software Engineer' }];
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
qs: expect.objectContaining({
searchTerm: 'software engineer',
}),
}));
});
it('should search by ext_id', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'search',
options: {},
searchMethod: 'ext_id',
searchQuery: 'LI-123456',
searchOptions: {},
});
const mockResponse = [{ id: '1', ext_id: 'LI-123456' }];
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
qs: expect.objectContaining({
ext_id: 'LI-123456',
}),
}));
});
it('should search by specific field (title)', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'search',
options: {},
searchMethod: 'title',
searchQuery: 'manager',
searchOptions: {},
});
const mockResponse = [{ id: '1' }];
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
qs: expect.objectContaining({
searchTerm: 'manager',
searchField: 'title',
}),
}));
});
it('should apply additional filters', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'search',
options: {},
searchMethod: 'general',
searchQuery: 'developer',
searchOptions: {
remote_setting: 'remote',
status: 'Applied',
limit: 50,
},
});
const mockResponse = [];
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
qs: expect.objectContaining({
remote_setting: 'remote',
status: 'Applied',
pageSize: 50,
}),
}));
});
});
describe('exists', () => {
it('should return exists:true when found by job_id', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'exists',
options: {},
id: '123',
});
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue({ id: '123' });
const result = await node.execute.call(mockExecute);
expect(result[0][0].json).toEqual({
exists: true,
found_by: 'job_id',
id: '123',
});
});
it('should return exists:true when found by ext_id', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'exists',
options: {},
id: 'LI-123',
});
// First call (get by ID) fails, second call (search by ext_id) succeeds
mockExecute.helpers.httpRequestWithAuthentication
.mockRejectedValueOnce(new Error('Not found'))
.mockResolvedValueOnce([{ id: '456', ext_id: 'LI-123' }]);
const result = await node.execute.call(mockExecute);
expect(result[0][0].json).toEqual({
exists: true,
found_by: 'ext_id',
id: 'LI-123',
});
});
it('should return exists:false when not found', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'exists',
options: {},
id: 'nonexistent',
});
mockExecute.helpers.httpRequestWithAuthentication
.mockRejectedValueOnce(new Error('Not found'))
.mockResolvedValueOnce([]);
const result = await node.execute.call(mockExecute);
expect(result[0][0].json).toEqual({
exists: false,
id: 'nonexistent',
});
});
});
});
// ========================================================================
// Status Operations Tests
// ========================================================================
describe('Status Operations', () => {
describe('getAll', () => {
it('should get all statuses by type', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'status',
operation: 'getAll',
options: {},
type: 'job',
});
const mockResponse = [
{ id: 1, name: 'Applied' },
{ id: 2, name: 'Interviewing' },
];
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
method: 'GET',
url: 'https://test.innotes.com/api/statuses',
qs: { type: 'job' },
}));
});
});
describe('create', () => {
it('should create new status', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'status',
operation: 'create',
options: {},
name: 'New Status',
type: 'job',
color: '#ff0000',
});
const mockResponse = { id: 10, name: 'New Status' };
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
method: 'POST',
url: 'https://test.innotes.com/api/statuses',
body: expect.objectContaining({
name: 'New Status',
type: 'job',
color: '#ff0000',
}),
}));
});
});
describe('update', () => {
it('should update status', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'status',
operation: 'update',
options: {},
statusId: '5',
updateFields: { name: 'Updated Status' },
});
const mockResponse = { id: 5, name: 'Updated Status' };
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
method: 'PUT',
url: 'https://test.innotes.com/api/statuses/5',
}));
});
});
describe('delete', () => {
it('should delete status', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'status',
operation: 'delete',
options: {},
statusId: '5',
});
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue({});
const result = await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
method: 'DELETE',
url: 'https://test.innotes.com/api/statuses/5',
}));
expect(result[0][0].json).toEqual({ success: true });
});
});
});
// ========================================================================
// Tag Operations Tests
// ========================================================================
describe('Tag Operations', () => {
describe('getAll', () => {
it('should get all tags', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'tag',
operation: 'getAll',
options: {},
});
const mockResponse = [
{ value: 'urgent' },
{ value: 'follow-up' },
];
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
method: 'GET',
url: 'https://test.innotes.com/api/tags',
}));
});
});
});
// ========================================================================
// User Operations Tests
// ========================================================================
describe('User Operations', () => {
describe('get', () => {
it('should get current user', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'user',
operation: 'get',
options: {},
});
const mockResponse = { id: '1', email: 'user@example.com', name: 'Test User' };
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
method: 'GET',
url: 'https://test.innotes.com/api/user',
}));
});
});
describe('getCv', () => {
it('should get CV and parse job_preferences JSON', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'user',
operation: 'getCv',
options: {},
});
const mockResponse = {
cv: 'My resume content',
cv_updated_at: '2024-01-01T00:00:00Z',
job_preferences: JSON.stringify({
searchJobTitles: ['Software Engineer', 'Developer'],
locations: ['Remote', 'New York'],
blacklist: {
titleBlacklist: ['Sales'],
companyBlacklist: ['BadCorp'],
penalize: ['Consultant'],
},
}),
};
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
const result = await node.execute.call(mockExecute);
expect(result[0][0].json).toEqual(expect.objectContaining({
cv: 'My resume content',
searchJobTitles: 'Software Engineer, Developer',
locations: 'Remote, New York',
titleBlacklist: 'Sales',
companyBlacklist: 'BadCorp',
penalize: 'Consultant',
}));
});
it('should handle legacy blacklist structure', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'user',
operation: 'getCv',
options: {},
});
const mockResponse = {
cv: 'My resume',
job_preferences: JSON.stringify({
searchJobTitles: ['Engineer'],
locations: ['SF'],
titleBlacklist: ['Manager'],
companyBlacklist: ['OldCorp'],
}),
};
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
const result = await node.execute.call(mockExecute);
expect(result[0][0].json).toEqual(expect.objectContaining({
titleBlacklist: 'Manager',
companyBlacklist: 'OldCorp',
}));
});
it('should handle missing job_preferences', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'user',
operation: 'getCv',
options: {},
});
const mockResponse = {
cv: 'My resume',
cv_updated_at: null,
job_preferences: null,
};
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
const result = await node.execute.call(mockExecute);
expect(result[0][0].json).toEqual(expect.objectContaining({
cv: 'My resume',
searchJobTitles: '',
locations: '',
}));
});
});
describe('update', () => {
it('should update user fields', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'user',
operation: 'update',
options: {},
updateFields: { home_location: 'Boston, MA' },
});
const mockResponse = { id: '1', home_location: 'Boston, MA' };
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
method: 'PUT',
url: 'https://test.innotes.com/api/user',
body: { home_location: 'Boston, MA' },
}));
});
});
});
// ========================================================================
// Automation Operations Tests
// ========================================================================
describe('Automation Operations', () => {
describe('getConfig', () => {
it('should get automation config successfully', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'automation',
operation: 'getConfig',
options: {},
});
const mockResponse = {
models: {
low: 'gpt-4o-mini',
medium: 'gpt-5-mini',
high: 'gpt-5-mini',
},
};
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue(mockResponse);
const result = await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
method: 'GET',
url: 'https://test.innotes.com/api/automation/config',
}));
expect(result[0][0].json).toEqual(mockResponse);
});
it('should handle API errors in getConfig', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'automation',
operation: 'getConfig',
options: {},
});
mockExecute.helpers.httpRequestWithAuthentication.mockRejectedValue(new Error('Unauthorized'));
await expect(node.execute.call(mockExecute)).rejects.toThrow(/Failed to get automation config/i);
});
});
describe('reportJobCreated', () => {
it('should report with success status', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'automation',
operation: 'reportJobCreated',
options: {},
jobsAdded: 5,
status: 'success',
});
// First call gets user, second reports callback
mockExecute.helpers.httpRequestWithAuthentication
.mockResolvedValueOnce({ id: 'user-123' })
.mockResolvedValueOnce({ success: true });
const result = await node.execute.call(mockExecute);
const calls = mockExecute.helpers.httpRequestWithAuthentication.mock.calls;
const callbackCall = calls.find((call) => call[1].url.includes('/api/automation/callback'));
expect(callbackCall[1].body).toEqual(expect.objectContaining({
userId: 'user-123',
status: 'success',
jobsAdded: 5,
secret: 'test-callback-secret',
}));
expect(result[0][0].json).toEqual(expect.objectContaining({
success: true,
jobsAdded: 5,
status: 'success',
}));
});
it('should report with error status and message', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'automation',
operation: 'reportJobCreated',
options: {},
jobsAdded: 0,
status: 'error',
errorMessage: 'Failed to scrape jobs',
});
mockExecute.helpers.httpRequestWithAuthentication
.mockResolvedValueOnce({ id: 'user-123' })
.mockResolvedValueOnce({ success: true });
await node.execute.call(mockExecute);
const calls = mockExecute.helpers.httpRequestWithAuthentication.mock.calls;
const callbackCall = calls.find((call) => call[1].url.includes('/api/automation/callback'));
expect(callbackCall[1].body).toEqual(expect.objectContaining({
status: 'error',
error: 'Failed to scrape jobs',
}));
});
it('should throw when callbackSecret not configured', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'automation',
operation: 'reportJobCreated',
options: {},
jobsAdded: 1,
status: 'success',
}, { callbackSecret: '' });
await expect(node.execute.call(mockExecute)).rejects.toThrow(/callback secret/i);
});
});
});
// ========================================================================
// Batch Processing Tests
// ========================================================================
describe('Batch Processing', () => {
it('should process items in batches', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'contact',
operation: 'get',
options: { batchSize: 2, timeoutBetweenBatches: 10 },
contactId: '1',
});
// Simulate 3 input items
mockExecute.getInputData.mockReturnValue([
{ json: {} },
{ json: {} },
{ json: {} },
]);
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue({ id: '1' });
await node.execute.call(mockExecute);
// Should have made 3 API calls (one per item)
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledTimes(3);
});
it('should respect batchSize option', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'contact',
operation: 'get',
options: { batchSize: 5 },
contactId: '1',
});
mockExecute.getInputData.mockReturnValue([{ json: {} }]);
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue({ id: '1' });
await node.execute.call(mockExecute);
// Verify options were read
expect(mockExecute.getNodeParameter).toHaveBeenCalledWith('options', 0, {});
});
});
// ========================================================================
// Error Handling Tests
// ========================================================================
describe('Error Handling', () => {
it('should handle API errors gracefully', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'contact',
operation: 'get',
options: {},
contactId: '999',
});
mockExecute.helpers.httpRequestWithAuthentication.mockRejectedValue(new Error('Not found'));
await expect(node.execute.call(mockExecute)).rejects.toThrow();
});
it('should continue on fail when enabled', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'contact',
operation: 'get',
options: {},
contactId: '999',
});
mockExecute.continueOnFail.mockReturnValue(true);
mockExecute.helpers.httpRequestWithAuthentication.mockRejectedValue(new Error('API Error'));
const result = await node.execute.call(mockExecute);
// Should return error in result instead of throwing
expect(result[0][0].json).toEqual({ error: 'API Error' });
});
it('should include request parameters in job creation error messages', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'create',
options: {},
name: 'Test Job',
company_name: 'Test Corp',
});
mockExecute.helpers.httpRequestWithAuthentication.mockRejectedValue(new Error('Server error'));
await expect(node.execute.call(mockExecute)).rejects.toThrow(/Test Job|Test Corp/);
});
});
// ========================================================================
// Unsupported Operations Tests
// ========================================================================
describe('Unsupported Operations', () => {
it('should handle unsupported resource gracefully', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'unsupported',
operation: 'get',
options: {},
});
const result = await node.execute.call(mockExecute);
expect(result).toBeDefined();
expect(Array.isArray(result)).toBe(true);
});
it('should throw error for unsupported contact operation', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'contact',
operation: 'unsupported_op',
options: {},
});
await expect(node.execute.call(mockExecute)).rejects.toThrow(/not supported.*contact/i);
});
it('should throw error for unsupported note operation', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'note',
operation: 'unsupported_op',
options: {},
});
await expect(node.execute.call(mockExecute)).rejects.toThrow(/not supported.*note/i);
});
it('should throw error for unsupported job operation', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'unsupported_op',
options: {},
});
await expect(node.execute.call(mockExecute)).rejects.toThrow(/not supported.*job/i);
});
it('should throw error for unsupported status operation', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'status',
operation: 'unsupported_op',
options: {},
});
await expect(node.execute.call(mockExecute)).rejects.toThrow(/not supported.*status/i);
});
it('should throw error for unsupported tag operation', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'tag',
operation: 'unsupported_op',
options: {},
});
await expect(node.execute.call(mockExecute)).rejects.toThrow(/not supported.*tag/i);
});
it('should throw error for unsupported user operation', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'user',
operation: 'unsupported_op',
options: {},
});
await expect(node.execute.call(mockExecute)).rejects.toThrow(/not supported.*user/i);
});
it('should throw error for unsupported automation operation', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'automation',
operation: 'unsupported_op',
options: {},
});
await expect(node.execute.call(mockExecute)).rejects.toThrow(/not supported.*automation/i);
});
});
// ========================================================================
// Additional Error Handling Tests for Branch Coverage
// ========================================================================
describe('Additional Error Handling', () => {
describe('Contact Operations', () => {
it('should include request params in contact create error', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'contact',
operation: 'create',
options: {},
name: 'Test Contact',
linkedin_key: 'test-key',
linkedin_user: 'test-user',
location: 'New York',
current_company: 'Test Corp',
picture_url: 'http://example.com/pic.jpg',
tags: 'tag1,tag2',
status_id: '5',
});
mockExecute.helpers.httpRequestWithAuthentication.mockRejectedValue(new Error('Validation error'));
await expect(node.execute.call(mockExecute)).rejects.toThrow(/Test Contact|Validation error/);
});
});
describe('Automation Operations', () => {
it('should throw when getting user ID fails', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'automation',
operation: 'reportJobCreated',
options: {},
jobsAdded: 5,
status: 'success',
});
mockExecute.helpers.httpRequestWithAuthentication.mockRejectedValue(new Error('User not found'));
await expect(node.execute.call(mockExecute)).rejects.toThrow(/Failed to get user ID/i);
});
it('should throw when callback request fails', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'automation',
operation: 'reportJobCreated',
options: {},
jobsAdded: 5,
status: 'success',
});
mockExecute.helpers.httpRequestWithAuthentication
.mockResolvedValueOnce({ id: 'user-123' }) // User request succeeds
.mockRejectedValueOnce(new Error('Callback failed')); // Callback fails
await expect(node.execute.call(mockExecute)).rejects.toThrow(/Failed to report job created/i);
});
});
describe('Job Operations', () => {
it('should throw when status lookup fails in job create', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'create',
options: {},
name: 'Test Job',
company_name: 'Test Corp',
status: 'Applied',
});
// Status lookup fails with network error
mockExecute.helpers.httpRequestWithAuthentication.mockRejectedValue(new Error('Network error'));
await expect(node.execute.call(mockExecute)).rejects.toThrow(/Failed to lookup status/i);
});
it('should throw when status not found in job update', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'update',
options: {},
jobId: '123',
updateFields: { status: 'NonExistentStatus' },
});
// Return empty statuses array
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValueOnce([
{ id: '1', name: 'Applied' },
{ id: '2', name: 'Interviewing' },
]);
await expect(node.execute.call(mockExecute)).rejects.toThrow(/NonExistentStatus.*not found/i);
});
it('should throw when status lookup fails in job update', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'update',
options: {},
jobId: '123',
updateFields: { status: 'Applied' },
});
// Status lookup fails with network error
mockExecute.helpers.httpRequestWithAuthentication.mockRejectedValue(new Error('Network error'));
await expect(node.execute.call(mockExecute)).rejects.toThrow(/Failed to lookup status/i);
});
it('should return exists:false with error when search throws', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'exists',
options: {},
existsType: 'byJobId',
id: '123',
});
mockExecute.helpers.httpRequestWithAuthentication.mockRejectedValue(new Error('Search failed'));
const result = await node.execute.call(mockExecute);
expect(result[0][0].json).toEqual(expect.objectContaining({
exists: false,
id: '123',
error: 'Search failed',
}));
});
});
describe('Job Search Types', () => {
it('should search by company field', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'search',
options: {},
searchMethod: 'company',
searchQuery: 'Google',
searchOptions: { limit: 50 },
});
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue([]);
await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
qs: expect.objectContaining({
searchTerm: 'Google',
searchField: 'company',
}),
}));
});
it('should search by location field', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'search',
options: {},
searchMethod: 'location',
searchQuery: 'San Francisco',
searchOptions: { limit: 50 },
});
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue([]);
await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
qs: expect.objectContaining({
searchTerm: 'San Francisco',
searchField: 'location',
}),
}));
});
it('should search by description field', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'search',
options: {},
searchMethod: 'description',
searchQuery: 'React',
searchOptions: { limit: 50 },
});
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue([]);
await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
qs: expect.objectContaining({
searchTerm: 'React',
searchField: 'description',
}),
}));
});
it('should use default search when method is unknown', async () => {
const mockExecute = createMockExecuteFunctions({
resource: 'job',
operation: 'search',
options: {},
searchMethod: 'unknown_method',
searchQuery: 'test query',
searchOptions: { limit: 50 },
});
mockExecute.helpers.httpRequestWithAuthentication.mockResolvedValue([]);
await node.execute.call(mockExecute);
expect(mockExecute.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('inNotesApi', expect.objectContaining({
qs: expect.objectContaining({
searchTerm: 'test query',
}),
}));
});
});
});
});
// ============================================================================
// Phone & Email — the fixedCollection the UI produces vs the array the API takes
// ============================================================================
describe('contactDetailsFromCollection', () => {
it('turns the collection n8n hands over into the API array', () => {
expect((0, InNotes_node_1.contactDetailsFromCollection)({
entry: [
{ type: 'email', value: ' ada@example.com ', label: ' work ' },
{ type: 'phone', value: '+41 79 123 45 67', label: '' },
],
})).toEqual([
{ type: 'email', value: 'ada@example.com', label: 'work' },
{ type: 'phone', value: '+41 79 123 45 67' },
]);
});
it('leaves the stored recapiti alone when the field was never used', () => {
// undefined, never [] — an empty array is a request to DELETE every number
// the contact has, and the update operation posts this collection straight
// through as the request body, so [] would empty a live contact and
// answer 200.
expect((0, InNotes_node_1.contactDetailsFromCollection)(undefined)).toBeUndefined();
expect((0, InNotes_node_1.contactDetailsFromCollection)({})).toBeUndefined();
expect((0, InNotes_node_1.contactDetailsFromCollection)({ entry: [] })).toBeUndefined();
});
it('raises on UPDATE when rows were configured and none of them can be used', () => {
// The alternative is deleting the field from the body, writing nothing and
// answering 200 — telling an operator who filled in three rows that it
// worked.
expect(() => (0, InNotes_node_1.contactDetailsFromCollection)({ entry: [{ type: 'phone', value: ' ' }] }, true)).toThrow(/no usable entry/);
expect(() => (0, InNotes_node_1.contactDetailsFromCollection)({ entry: [{ type: 'phone' }] }, true)).toThrow(/no usable entry/);
});
it('does NOT raise on create, where the contact is the point and recapiti are optional', () => {
// Raising here would abort the create: importing 100 contacts would skip
// the 30 whose phone expression resolved to "".
expect((0, InNotes_node_1.contactDetailsFromCollection)({ entry: [{ type: 'phone', value: ' ' }] })).toBeUndefined();
});
it('accepts the API shape an expression produces, not only the collection', () => {
// `{{ $json.contact_details }}` arrives as a bare array; reading only
// `.entry` there returns undefined and writes nothing under a 200.
expect((0, InNotes_node_1.contactDetailsFromCollection)([{ type: 'phone', value: '+41 79 123 45 67' }])).toEqual([
{ type: 'phone', value: '+41 79 123 45 67' },
]);
});
it('coerces a value an expression resolved to a number', () => {
// `{{ $json.phone }}` yielding 41791234567 is a filled-in row, not a blank.
expect((0, InNotes_node_1.contactDetailsFromCollection)({ entry: [{ type: 'phone', value: 41791234567 }] })).toEqual([
{ type: 'phone', value: '41791234567' },
]);
});
it('drops a row whose type is neither email nor phone', () => {
// `type` is a dropdown but expression-settable, so a workflow can resolve
// it to 'mobile'. Sending it as an email would earn a 400 complaining
// about the value, which names neither the row nor the real cause. When
// it is the ONLY row, dropping it would write nothing under a 200, so
// that case raises instead.
expect(() => (0, InNotes_node_1.contactDetailsFromCollection)({ entry: [{ type: 'fax', value: '123456' }] }, true)).toThrow(/no usable entry/);
expect((0, InNotes_node_1.contactDetailsFromCollection)({
entry: [
{ type: 'mobile', value: '+41 79 123 45 67' },
{ type: 'phone', value: '+41 44 555 66 77' },
],
})).toEqual([{ type: 'phone', value: '+41 44 555 66 77' }]);
});
});
//# sourceMappingURL=InNotes.node.test.js.map