UNPKG

n8n-nodes-innotes

Version:

N8N node for InNotes CRM API integration

1,027 lines 75.8 kB
"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'