UNPKG

n8n-nodes-innotes

Version:

N8N node for InNotes CRM API integration

1,532 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',
                    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