n8n-nodes-awx
Version:
n8n node to interact with Ansible AWX/Tower with improved type safety
497 lines • 22.5 kB
JavaScript
"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