n8n-nodes-innotes
Version:
N8N node for InNotes CRM API integration
1,027 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'