UNPKG

n8n-nodes-awx

Version:

n8n node to interact with Ansible AWX/Tower with improved type safety

497 lines 22.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const src_1 = require("../src"); const debug = (message, ...args) => { const timestamp = new Date().toISOString(); console.log(`[${timestamp}] [DEBUG] ${message}`, ...args); }; jest.setTimeout(10000); beforeAll(() => { jest.useFakeTimers(); }); afterAll(() => { jest.useRealTimers(); jest.clearAllTimers(); }); const mockApiClient = { get: jest.fn(), post: jest.fn(), put: jest.fn(), delete: jest.fn(), }; jest.mock('../src/ApiClient', () => { return jest.fn().mockImplementation(() => mockApiClient); }); const createMockJobTemplate = (overrides = {}) => ({ id: 42, name: 'Deploy Web Application', description: 'Deploy the web application to production', job_type: 'run', inventory: 15, project: 10, playbook: 'deploy.yml', ask_variables_on_launch: true, ask_limit_on_launch: true, ask_tags_on_launch: true, ask_skip_tags_on_launch: true, ask_job_type_on_launch: false, ask_inventory_on_launch: false, ask_credential_on_launch: false, survey_enabled: true, become_enabled: true, diff_mode: false, allow_simultaneous: false, status: 'successful', last_job_run: '2023-04-15T10:30:00Z', summary_fields: { inventory: { id: 15, name: 'Production Servers' }, project: { id: 10, name: 'Web App Project' }, last_job: { id: 100, status: 'successful' }, created_by: { username: 'admin', id: 1 }, credentials: [ { id: 5, name: 'AWS Production', credential_type: 1 } ] }, ...overrides }); describe('JobTemplateResources', () => { let jobTemplateResources; afterEach(async () => { debug('Cleaning up after test'); jest.clearAllMocks(); jest.clearAllTimers(); }); afterAll(() => { debug('All tests completed'); jest.restoreAllMocks(); jest.resetModules(); jest.useRealTimers(); }); beforeEach(() => { debug('Setting up test'); jest.clearAllMocks(); jobTemplateResources = new src_1.JobTemplateResources(mockApiClient); mockApiClient.get.mockImplementation((url) => { debug(`Mock GET called with URL: ${url}`); if (url.includes('/api/v2/job_templates/')) { if (url.includes('name=')) { return Promise.resolve({ data: { count: 1, next: null, previous: null, results: [createMockJobTemplate()] } }); } else if (url.match(/\/api\/v2\/job_templates\/\d+\/$/)) { return Promise.resolve({ data: createMockJobTemplate() }); } return Promise.resolve({ data: { count: 1, next: null, previous: null, results: [createMockJobTemplate()] } }); } return Promise.resolve({ data: createMockJobTemplate() }); }); mockApiClient.post.mockImplementation((url) => { if (url.endsWith('/launch/')) { return Promise.resolve({ data: { id: 100, status: 'pending', job_template: 42, summary_fields: { job_template: { id: 42, name: 'Deploy Web Application' } } } }); } return Promise.resolve({ data: createMockJobTemplate() }); }); mockApiClient.put.mockImplementation(() => Promise.resolve({ data: createMockJobTemplate() })); mockApiClient.delete.mockImplementation(() => Promise.resolve()); }); describe('list', () => { it('should return a list of job templates', async () => { const expectedTemplate = createMockJobTemplate(); const jobTemplates = await jobTemplateResources.list(); expect(mockApiClient.get).toHaveBeenCalledTimes(1); expect(mockApiClient.get).toHaveBeenCalledWith('/api/v2/job_templates/'); expect(jobTemplates).toEqual([expectedTemplate]); expect(Array.isArray(jobTemplates)).toBe(true); expect(jobTemplates.length).toBe(1); const firstTemplate = jobTemplates[0]; expect(firstTemplate).toHaveProperty('id', expectedTemplate.id); expect(firstTemplate).toHaveProperty('name', expectedTemplate.name); expect(firstTemplate).toHaveProperty('job_type', expectedTemplate.job_type); expect(firstTemplate).toHaveProperty('playbook', expectedTemplate.playbook); expect(firstTemplate.summary_fields).toBeDefined(); if (firstTemplate.summary_fields && expectedTemplate.summary_fields) { expect(firstTemplate.summary_fields.inventory).toEqual(expectedTemplate.summary_fields.inventory); expect(firstTemplate.summary_fields.project).toEqual(expectedTemplate.summary_fields.project); } }); it('should pass query parameters when provided', async () => { debug('Starting test: should pass query parameters when provided'); const params = { name: 'Deploy', page_size: 10 }; const mockResponse = { data: { count: 1, next: null, previous: null, results: [createMockJobTemplate()] } }; debug('Mock response set up:', JSON.stringify(mockResponse, null, 2)); mockApiClient.get.mockResolvedValueOnce(mockResponse); const result = await jobTemplateResources.list(params); expect(mockApiClient.get).toHaveBeenCalledTimes(1); expect(mockApiClient.get).toHaveBeenCalledWith('/api/v2/job_templates/?name=Deploy&page_size=10'); expect(Array.isArray(result)).toBe(true); expect(result).toHaveLength(1); debug('Test completed successfully'); }); it('should handle empty results', async () => { debug('Starting test: should handle empty results'); const emptyResponse = { data: { count: 0, next: null, previous: null, results: [] } }; debug('Setting up empty response mock'); mockApiClient.get.mockResolvedValueOnce(emptyResponse); const result = await jobTemplateResources.list(); expect(Array.isArray(result)).toBe(true); expect(result).toHaveLength(0); expect(mockApiClient.get).toHaveBeenCalledTimes(1); expect(mockApiClient.get).toHaveBeenCalledWith('/api/v2/job_templates/'); debug('Test completed successfully'); }); }); describe('get', () => { it('should return a job template by id', async () => { const expectedTemplate = createMockJobTemplate(); const jobTemplate = await jobTemplateResources.get(42); expect(mockApiClient.get).toHaveBeenCalledTimes(1); expect(mockApiClient.get).toHaveBeenCalledWith('/api/v2/job_templates/42/'); expect(jobTemplate).toEqual(expectedTemplate); }); it('should throw an error when id is not provided', async () => { await expect(jobTemplateResources.get(undefined)).rejects.toThrow('Job Template ID is required'); expect(mockApiClient.get).not.toHaveBeenCalled(); }); }); describe('getByName', () => { it('should return a job template by name', async () => { const templateName = 'Deploy Web Application'; mockApiClient.get.mockResolvedValueOnce({ data: { count: 1, results: [createMockJobTemplate()] } }); const jobTemplate = await jobTemplateResources.getByName(templateName); expect(mockApiClient.get).toHaveBeenCalledTimes(1); expect(mockApiClient.get).toHaveBeenCalledWith(`/api/v2/job_templates/?name=${encodeURIComponent(templateName)}`); expect(jobTemplate).toBeDefined(); expect(jobTemplate === null || jobTemplate === void 0 ? void 0 : jobTemplate.id).toBe(42); expect(jobTemplate === null || jobTemplate === void 0 ? void 0 : jobTemplate.name).toBe('Deploy Web Application'); }); it('should return null when job template is not found', async () => { mockApiClient.get.mockResolvedValueOnce({ data: { count: 0, next: null, previous: null, results: [] } }); const jobTemplate = await jobTemplateResources.getByName('Non-existent Template'); expect(jobTemplate).toBeNull(); }); it('should throw an error when multiple job templates with the same name are found', async () => { mockApiClient.get.mockResolvedValueOnce({ data: { count: 2, results: [createMockJobTemplate(), createMockJobTemplate({ id: 43 })] } }); await expect(jobTemplateResources.getByName('Duplicate Template')).rejects.toThrow('Multiple job templates found with name: Duplicate Template'); }); }); describe('create', () => { it('should create a new job template', async () => { const newTemplate = { name: 'New Job Template', job_type: 'run', project: 10, playbook: 'deploy.yml', inventory: 15 }; const result = await jobTemplateResources.create(newTemplate); expect(mockApiClient.post).toHaveBeenCalledTimes(1); expect(mockApiClient.post).toHaveBeenCalledWith('/api/v2/job_templates/', newTemplate); expect(result).toBeDefined(); expect(result.name).toBe('Deploy Web Application'); }); it('should throw an error when required fields are missing', async () => { const invalidData = { description: 'This should fail' }; await expect(jobTemplateResources.create(invalidData)).rejects.toThrowError('Missing required fields: name, job_type, inventory, project, playbook'); expect(mockApiClient.post).not.toHaveBeenCalled(); }); }); describe('update', () => { it('should update an existing job template', async () => { const updates = { name: 'Updated Job Template', description: 'Updated description' }; const result = await jobTemplateResources.update(42, updates); expect(mockApiClient.put).toHaveBeenCalledTimes(1); expect(mockApiClient.put).toHaveBeenCalledWith('/api/v2/job_templates/42/', updates); expect(result).toBeDefined(); expect(result.id).toBe(42); }); it('should throw an error when id is not provided', async () => { await expect(jobTemplateResources.update(undefined, {})).rejects.toThrow('Job Template ID is required'); expect(mockApiClient.put).not.toHaveBeenCalled(); }); }); describe('delete', () => { it('should delete a job template', async () => { await jobTemplateResources.delete(42); expect(mockApiClient.delete).toHaveBeenCalledTimes(1); expect(mockApiClient.delete).toHaveBeenCalledWith('/api/v2/job_templates/42/'); }); it('should throw an error when id is not provided', async () => { await expect(jobTemplateResources.delete(undefined)).rejects.toThrow('Job Template ID is required'); expect(mockApiClient.delete).not.toHaveBeenCalled(); }); }); describe('launch', () => { it('should launch a job template', async () => { const launchParams = { extra_vars: { environment: 'production' }, limit: 'web_servers', tags: 'deploy,web' }; const job = await jobTemplateResources.launch(42, launchParams); expect(mockApiClient.post).toHaveBeenCalledTimes(1); expect(mockApiClient.post).toHaveBeenCalledWith('/api/v2/job_templates/42/launch/', launchParams); expect(job).toBeDefined(); expect(job.id).toBe(100); expect(job.status).toBe('pending'); expect(job.job_template).toBe(42); }); it('should launch with default parameters when none provided', async () => { const job = await jobTemplateResources.launch(42); expect(mockApiClient.post).toHaveBeenCalledTimes(1); expect(mockApiClient.post).toHaveBeenCalledWith('/api/v2/job_templates/42/launch/', {}); expect(job).toBeDefined(); }); it('should throw an error when id is not provided', async () => { await expect(jobTemplateResources.launch(undefined, {})).rejects.toThrow('Job Template ID is required'); expect(mockApiClient.post).not.toHaveBeenCalled(); }); }); describe('getByName', () => { it('should return a job template by name', async () => { const expectedTemplate = createMockJobTemplate(); const templateName = 'Deploy Web Application'; mockApiClient.get.mockResolvedValueOnce({ data: { count: 1, results: [expectedTemplate] } }); const result = await jobTemplateResources.getByName(templateName); expect(mockApiClient.get).toHaveBeenCalledTimes(1); expect(mockApiClient.get).toHaveBeenCalledWith(`/api/v2/job_templates/?name=${encodeURIComponent(templateName)}`); expect(result).toEqual(expectedTemplate); }); it('should return null when job template is not found', async () => { mockApiClient.get.mockResolvedValueOnce({ data: { count: 0, results: [] } }); const result = await jobTemplateResources.getByName('Non-existent Template'); expect(result).toBeNull(); }); it('should throw an error when multiple job templates with the same name are found', async () => { const template1 = createMockJobTemplate({ id: 1 }); const template2 = createMockJobTemplate({ id: 2 }); mockApiClient.get.mockResolvedValueOnce({ data: { count: 2, results: [template1, template2] } }); await expect(jobTemplateResources.getByName('Duplicate Template')).rejects.toThrow('Multiple job templates found with name: Duplicate Template'); }); }); describe('create', () => { it('should create a new job template', async () => { const newTemplateData = { name: 'New Job Template', job_type: 'run', project: 10, playbook: 'deploy.yml', inventory: 15 }; const expectedTemplate = createMockJobTemplate({ ...newTemplateData, id: 100 }); mockApiClient.post.mockResolvedValueOnce({ data: expectedTemplate }); const result = await jobTemplateResources.create(newTemplateData); expect(mockApiClient.post).toHaveBeenCalledTimes(1); expect(mockApiClient.post).toHaveBeenCalledWith('/api/v2/job_templates/', newTemplateData); expect(result).toEqual(expectedTemplate); }); it('should throw an error when required fields are missing', async () => { const originalPost = mockApiClient.post; mockApiClient.post.mockImplementationOnce(() => { throw new Error('Required fields are missing'); }); const invalidTemplate = { description: 'This template is missing required fields' }; await expect(jobTemplateResources.create(invalidTemplate)).rejects.toThrow('Missing required fields: name, job_type, inventory, project, playbook'); expect(mockApiClient.post).not.toHaveBeenCalled(); mockApiClient.post = originalPost; }); }); describe('update', () => { it('should update an existing job template', async () => { const updates = { name: 'Updated Job Template', description: 'Updated description' }; const expectedTemplate = createMockJobTemplate({ ...updates, id: 42 }); mockApiClient.put.mockResolvedValueOnce({ data: expectedTemplate }); const result = await jobTemplateResources.update(42, updates); expect(mockApiClient.put).toHaveBeenCalledTimes(1); expect(mockApiClient.put).toHaveBeenCalledWith('/api/v2/job_templates/42/', updates); expect(result).toEqual(expectedTemplate); }); it('should throw an error when id is not provided', async () => { await expect(jobTemplateResources.update(undefined, {})).rejects.toThrow('Job Template ID is required'); }); }); describe('delete', () => { it('should delete a job template', async () => { await jobTemplateResources.delete(42); expect(mockApiClient.delete).toHaveBeenCalledTimes(1); expect(mockApiClient.delete).toHaveBeenCalledWith('/api/v2/job_templates/42/'); }); it('should throw an error when id is not provided', async () => { await expect(jobTemplateResources.delete(undefined)).rejects.toThrow('Job Template ID is required'); }); }); describe('launch', () => { it('should launch a job template', async () => { const launchParams = { extra_vars: { environment: 'production' }, limit: 'web_servers', tags: 'deploy,web' }; const launchResponse = { id: 100, type: 'job', url: '/api/v2/jobs/100/', related: { job_template: '/api/v2/job_templates/42/' }, job_template: 42, status: 'pending', name: 'Deploy Web Application', started: null, finished: null, elapsed: 0 }; mockApiClient.post.mockResolvedValueOnce({ data: launchResponse }); const result = await jobTemplateResources.launch(42, launchParams); expect(mockApiClient.post).toHaveBeenCalledTimes(1); expect(mockApiClient.post).toHaveBeenCalledWith('/api/v2/job_templates/42/launch/', launchParams); expect(result).toEqual(launchResponse); }); it('should throw an error when id is not provided', async () => { await expect(jobTemplateResources.launch(undefined, {})).rejects.toThrow('Job Template ID is required'); expect(mockApiClient.post).not.toHaveBeenCalled(); }); }); describe('buildUrl', () => { const testBuildUrl = (jobTemplateResources, path, params) => { const resourcesWithPrivate = jobTemplateResources; return resourcesWithPrivate.buildUrl(path, params); }; it('should return the path when no params are provided', () => { const result = testBuildUrl(jobTemplateResources, '/api/v2/job_templates/'); expect(result).toBe('/api/v2/job_templates/'); }); it('should handle empty params object', () => { const result = testBuildUrl(jobTemplateResources, '/api/v2/job_templates/', {}); expect(result).toBe('/api/v2/job_templates/'); }); it('should handle null params', () => { const result = testBuildUrl(jobTemplateResources, '/api/v2/job_templates/', null); expect(result).toBe('/api/v2/job_templates/'); }); it('should append query parameters correctly', () => { const result = testBuildUrl(jobTemplateResources, '/api/v2/job_templates/', { name: 'test job template', page: 1, page_size: 10 }); expect(result).toContain('name=test%20job%20template'); expect(result).toContain('page=1'); expect(result).toContain('page_size=10'); expect(result).toMatch(/^\/api\/v2\/job_templates\/\?.*/); }); it('should handle special characters in parameters', () => { const result = testBuildUrl(jobTemplateResources, '/api/v2/job_templates/', { search: 'job@template', filter: 'inventory=1&name=test' }); expect(result).toContain('search=job%40template'); expect(result).toContain('filter=inventory%3D1%26name%3Dtest'); }); it('should handle undefined and null parameter values', () => { const result = testBuildUrl(jobTemplateResources, '/api/v2/job_templates/', { name: 'test', status: undefined, filter: null }); expect(result).toContain('name=test'); expect(result).not.toContain('status='); expect(result).not.toContain('filter='); }); }); }); //# sourceMappingURL=JobTemplateResources.test.js.map