mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
530 lines
15 KiB
TypeScript
530 lines
15 KiB
TypeScript
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
|
import { NodeApiError, NodeOperationError } from 'n8n-workflow';
|
|
import nock from 'nock';
|
|
|
|
import { Github } from '../../Github.node';
|
|
|
|
describe('Test Github Node', () => {
|
|
describe('Workflow Dispatch', () => {
|
|
const now = 1683028800000;
|
|
const owner = 'testOwner';
|
|
const repository = 'testRepository';
|
|
const workflowId = 147025216;
|
|
const usersResponse = {
|
|
total_count: 12,
|
|
items: [
|
|
{
|
|
login: 'testOwner',
|
|
id: 1,
|
|
},
|
|
],
|
|
};
|
|
const repositoriesResponse = {
|
|
total_count: 40,
|
|
items: [
|
|
{
|
|
id: 3081286,
|
|
name: 'testRepository',
|
|
},
|
|
],
|
|
};
|
|
const workflowsResponse = {
|
|
total_count: 2,
|
|
workflows: [
|
|
{
|
|
id: workflowId,
|
|
node_id: 'MDg6V29ya2Zsb3cxNjEzMzU=',
|
|
name: 'CI',
|
|
path: '.github/workflows/blank.yaml',
|
|
state: 'active',
|
|
created_at: '2020-01-08T23:48:37.000-08:00',
|
|
updated_at: '2020-01-08T23:50:21.000-08:00',
|
|
url: 'https://api.github.com/repos/octo-org/octo-repo/actions/workflows/161335',
|
|
html_url: 'https://github.com/octo-org/octo-repo/blob/master/.github/workflows/161335',
|
|
badge_url: 'https://github.com/octo-org/octo-repo/workflows/CI/badge.svg',
|
|
},
|
|
{
|
|
id: 269289,
|
|
node_id: 'MDE4OldvcmtmbG93IFNlY29uZGFyeTI2OTI4OQ==',
|
|
name: 'Linter',
|
|
path: '.github/workflows/linter.yaml',
|
|
state: 'active',
|
|
created_at: '2020-01-08T23:48:37.000-08:00',
|
|
updated_at: '2020-01-08T23:50:21.000-08:00',
|
|
url: 'https://api.github.com/repos/octo-org/octo-repo/actions/workflows/269289',
|
|
html_url: 'https://github.com/octo-org/octo-repo/blob/master/.github/workflows/269289',
|
|
badge_url: 'https://github.com/octo-org/octo-repo/workflows/Linter/badge.svg',
|
|
},
|
|
],
|
|
};
|
|
|
|
beforeAll(async () => {
|
|
jest.useFakeTimers({ doNotFake: ['nextTick'], now });
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
const baseUrl = 'https://api.github.com';
|
|
nock(baseUrl)
|
|
.persist()
|
|
.defaultReplyHeaders({ 'Content-Type': 'application/json' })
|
|
.get('/search/users')
|
|
.query(true)
|
|
.reply(200, usersResponse)
|
|
.get('/search/repositories')
|
|
.query(true)
|
|
.reply(200, repositoriesResponse)
|
|
.get(`/repos/${owner}/${repository}/actions/workflows`)
|
|
.reply(200, workflowsResponse)
|
|
.post(`/repos/${owner}/${repository}/actions/workflows/${workflowId}/dispatches`, {
|
|
ref: 'main',
|
|
inputs: {},
|
|
})
|
|
.reply(200, {});
|
|
});
|
|
|
|
new NodeTestHarness().setupTests({
|
|
workflowFiles: ['GithubTestWorkflow.json'],
|
|
});
|
|
});
|
|
|
|
describe('Error Handling', () => {
|
|
let githubNode: Github;
|
|
let mockExecutionContext: any;
|
|
|
|
beforeEach(() => {
|
|
githubNode = new Github();
|
|
mockExecutionContext = {
|
|
getNode: jest.fn().mockReturnValue({ name: 'Github' }),
|
|
getNodeParameter: jest.fn(),
|
|
getInputData: jest.fn().mockReturnValue([{ json: {} }]),
|
|
continueOnFail: jest.fn().mockReturnValue(false),
|
|
putExecutionToWait: jest.fn(),
|
|
getCredentials: jest.fn().mockResolvedValue({
|
|
server: 'https://api.github.com',
|
|
user: 'test',
|
|
accessToken: 'test',
|
|
}),
|
|
helpers: {
|
|
returnJsonArray: jest.fn().mockReturnValue([{ json: {} }]),
|
|
httpRequest: jest.fn(),
|
|
httpRequestWithAuthentication: jest.fn(),
|
|
requestWithAuthentication: jest
|
|
.fn()
|
|
.mockImplementation(async (_credentialType, options) => {
|
|
if (options.uri.includes('dispatches') && options.method === 'POST') {
|
|
const error: any = new Error('Not Found');
|
|
error.statusCode = 404;
|
|
error.message = 'Not Found';
|
|
throw error;
|
|
}
|
|
return {};
|
|
}),
|
|
request: jest.fn(),
|
|
constructExecutionMetaData: jest.fn().mockReturnValue([{ json: {} }]),
|
|
assertBinaryData: jest.fn(),
|
|
prepareBinaryData: jest.fn(),
|
|
},
|
|
getWorkflowDataProxy: jest.fn().mockReturnValue({
|
|
$execution: {
|
|
resumeUrl: 'https://example.com/webhook',
|
|
},
|
|
}),
|
|
};
|
|
});
|
|
|
|
it('should throw NodeOperationError for invalid JSON inputs', async () => {
|
|
mockExecutionContext.getNodeParameter.mockImplementation((parameterName: string) => {
|
|
if (parameterName === 'inputs') {
|
|
return 'invalid json';
|
|
}
|
|
if (parameterName === 'resource') {
|
|
return 'workflow';
|
|
}
|
|
if (parameterName === 'operation') {
|
|
return 'dispatchAndWait';
|
|
}
|
|
if (parameterName === 'authentication') {
|
|
return 'accessToken';
|
|
}
|
|
return '';
|
|
});
|
|
|
|
await expect(async () => {
|
|
await githubNode.execute.call(mockExecutionContext);
|
|
}).rejects.toThrow(NodeOperationError);
|
|
});
|
|
|
|
it('should throw NodeOperationError for 404 errors when dispatching a workflow', async () => {
|
|
const owner = 'testOwner';
|
|
const repository = 'testRepository';
|
|
const workflowId = 147025216;
|
|
|
|
mockExecutionContext.helpers.requestWithAuthentication.mockRejectedValueOnce({
|
|
statusCode: 404,
|
|
message: 'Not Found',
|
|
});
|
|
|
|
mockExecutionContext.getNodeParameter.mockImplementation((parameterName: string) => {
|
|
if (parameterName === 'owner') {
|
|
return owner;
|
|
}
|
|
if (parameterName === 'repository') {
|
|
return repository;
|
|
}
|
|
if (parameterName === 'workflowId') {
|
|
return workflowId;
|
|
}
|
|
if (parameterName === 'inputs') {
|
|
return '{}';
|
|
}
|
|
if (parameterName === 'ref') {
|
|
return 'main';
|
|
}
|
|
if (parameterName === 'resource') {
|
|
return 'workflow';
|
|
}
|
|
if (parameterName === 'operation') {
|
|
return 'dispatchAndWait';
|
|
}
|
|
if (parameterName === 'authentication') {
|
|
return 'accessToken';
|
|
}
|
|
return '';
|
|
});
|
|
|
|
await expect(async () => {
|
|
await githubNode.execute.call(mockExecutionContext);
|
|
}).rejects.toThrow(/The workflow to dispatch could not be found/);
|
|
});
|
|
|
|
it('should throw NodeApiError for general API errors', async () => {
|
|
const owner = 'testOwner';
|
|
const repository = 'testRepository';
|
|
const workflowId = 147025216;
|
|
|
|
mockExecutionContext.helpers.requestWithAuthentication.mockRejectedValueOnce({
|
|
statusCode: 500,
|
|
message: 'Internal Server Error',
|
|
});
|
|
|
|
mockExecutionContext.getNodeParameter.mockImplementation((parameterName: string) => {
|
|
if (parameterName === 'owner') {
|
|
return owner;
|
|
}
|
|
if (parameterName === 'repository') {
|
|
return repository;
|
|
}
|
|
if (parameterName === 'workflowId') {
|
|
return workflowId;
|
|
}
|
|
if (parameterName === 'inputs') {
|
|
return '{}';
|
|
}
|
|
if (parameterName === 'ref') {
|
|
return 'main';
|
|
}
|
|
if (parameterName === 'resource') {
|
|
return 'workflow';
|
|
}
|
|
if (parameterName === 'operation') {
|
|
return 'dispatch';
|
|
}
|
|
if (parameterName === 'authentication') {
|
|
return 'accessToken';
|
|
}
|
|
return '';
|
|
});
|
|
|
|
await expect(async () => {
|
|
await githubNode.execute.call(mockExecutionContext);
|
|
}).rejects.toThrow();
|
|
});
|
|
|
|
it('should throw NodeApiError for general API errors in dispatchAndWait operation', async () => {
|
|
const owner = 'testOwner';
|
|
const repository = 'testRepository';
|
|
const workflowId = 147025216;
|
|
|
|
mockExecutionContext.getWorkflowDataProxy = jest.fn().mockReturnValue({
|
|
$execution: {
|
|
resumeUrl: 'https://example.com/webhook',
|
|
},
|
|
});
|
|
|
|
mockExecutionContext.helpers.requestWithAuthentication.mockRejectedValueOnce({
|
|
statusCode: 500,
|
|
message: 'Internal Server Error',
|
|
});
|
|
|
|
mockExecutionContext.getNodeParameter.mockImplementation((parameterName: string) => {
|
|
if (parameterName === 'owner') {
|
|
return owner;
|
|
}
|
|
if (parameterName === 'repository') {
|
|
return repository;
|
|
}
|
|
if (parameterName === 'workflowId') {
|
|
return workflowId;
|
|
}
|
|
if (parameterName === 'inputs') {
|
|
return '{}';
|
|
}
|
|
if (parameterName === 'ref') {
|
|
return 'main';
|
|
}
|
|
if (parameterName === 'resource') {
|
|
return 'workflow';
|
|
}
|
|
if (parameterName === 'operation') {
|
|
return 'dispatchAndWait';
|
|
}
|
|
if (parameterName === 'authentication') {
|
|
return 'accessToken';
|
|
}
|
|
return '';
|
|
});
|
|
|
|
await expect(async () => {
|
|
await githubNode.execute.call(mockExecutionContext);
|
|
}).rejects.toThrow(NodeApiError);
|
|
|
|
expect(mockExecutionContext.helpers.requestWithAuthentication).toHaveBeenCalledWith(
|
|
expect.any(String),
|
|
expect.objectContaining({
|
|
method: 'POST',
|
|
uri: expect.stringContaining(
|
|
`/repos/${owner}/${repository}/actions/workflows/${workflowId}/dispatches`,
|
|
),
|
|
body: expect.objectContaining({
|
|
ref: 'main',
|
|
inputs: expect.objectContaining({
|
|
resumeUrl: 'https://example.com/webhook',
|
|
}),
|
|
}),
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('Workflow Operations', () => {
|
|
let githubNode: Github;
|
|
let mockExecutionContext: any;
|
|
|
|
beforeEach(() => {
|
|
githubNode = new Github();
|
|
mockExecutionContext = {
|
|
getNode: jest.fn().mockReturnValue({ name: 'Github' }),
|
|
getNodeParameter: jest.fn(),
|
|
getInputData: jest.fn().mockReturnValue([{ json: {} }]),
|
|
continueOnFail: jest.fn().mockReturnValue(false),
|
|
getCredentials: jest.fn().mockResolvedValue({
|
|
server: 'https://api.github.com',
|
|
user: 'test',
|
|
accessToken: 'test',
|
|
}),
|
|
helpers: {
|
|
returnJsonArray: jest.fn().mockReturnValue([{ json: {} }]),
|
|
requestWithAuthentication: jest.fn().mockResolvedValue({}),
|
|
constructExecutionMetaData: jest.fn().mockReturnValue([{ json: {} }]),
|
|
},
|
|
};
|
|
});
|
|
|
|
it('should use extractValue for workflowId in disable operation', async () => {
|
|
const owner = 'testOwner';
|
|
const repository = 'testRepository';
|
|
const workflowId = 147025216;
|
|
|
|
mockExecutionContext.getNodeParameter.mockImplementation(
|
|
(parameterName: string, _itemIndex: number, defaultValue: string, options?: any) => {
|
|
if (parameterName === 'owner') {
|
|
return owner;
|
|
}
|
|
if (parameterName === 'repository') {
|
|
return repository;
|
|
}
|
|
if (parameterName === 'workflowId') {
|
|
expect(options).toBeDefined();
|
|
expect(options.extractValue).toBe(true);
|
|
return workflowId;
|
|
}
|
|
if (parameterName === 'resource') {
|
|
return 'workflow';
|
|
}
|
|
if (parameterName === 'operation') {
|
|
return 'disable';
|
|
}
|
|
if (parameterName === 'authentication') {
|
|
return 'accessToken';
|
|
}
|
|
return defaultValue;
|
|
},
|
|
);
|
|
|
|
await githubNode.execute.call(mockExecutionContext);
|
|
|
|
expect(mockExecutionContext.helpers.requestWithAuthentication).toHaveBeenCalledWith(
|
|
expect.any(String),
|
|
expect.objectContaining({
|
|
method: 'PUT',
|
|
uri: `https://api.github.com/repos/${owner}/${repository}/actions/workflows/${workflowId}/disable`,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('should use extractValue for workflowId in enable operation', async () => {
|
|
const owner = 'testOwner';
|
|
const repository = 'testRepository';
|
|
const workflowId = 147025216;
|
|
|
|
mockExecutionContext.getNodeParameter.mockImplementation(
|
|
(parameterName: string, _itemIndex: number, defaultValue: string, options?: any) => {
|
|
if (parameterName === 'owner') {
|
|
return owner;
|
|
}
|
|
if (parameterName === 'repository') {
|
|
return repository;
|
|
}
|
|
if (parameterName === 'workflowId') {
|
|
expect(options).toBeDefined();
|
|
expect(options.extractValue).toBe(true);
|
|
return workflowId;
|
|
}
|
|
if (parameterName === 'resource') {
|
|
return 'workflow';
|
|
}
|
|
if (parameterName === 'operation') {
|
|
return 'enable';
|
|
}
|
|
if (parameterName === 'authentication') {
|
|
return 'accessToken';
|
|
}
|
|
return defaultValue;
|
|
},
|
|
);
|
|
|
|
await githubNode.execute.call(mockExecutionContext);
|
|
|
|
expect(mockExecutionContext.helpers.requestWithAuthentication).toHaveBeenCalledWith(
|
|
expect.any(String),
|
|
expect.objectContaining({
|
|
method: 'PUT',
|
|
uri: `https://api.github.com/repos/${owner}/${repository}/actions/workflows/${workflowId}/enable`,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('should use extractValue for workflowId in get operation', async () => {
|
|
const owner = 'testOwner';
|
|
const repository = 'testRepository';
|
|
const workflowId = 147025216;
|
|
|
|
mockExecutionContext.getNodeParameter.mockImplementation(
|
|
(parameterName: string, _itemIndex: number, defaultValue: string, options?: any) => {
|
|
if (parameterName === 'owner') {
|
|
return owner;
|
|
}
|
|
if (parameterName === 'repository') {
|
|
return repository;
|
|
}
|
|
if (parameterName === 'workflowId') {
|
|
expect(options).toBeDefined();
|
|
expect(options.extractValue).toBe(true);
|
|
return workflowId;
|
|
}
|
|
if (parameterName === 'resource') {
|
|
return 'workflow';
|
|
}
|
|
if (parameterName === 'operation') {
|
|
return 'get';
|
|
}
|
|
if (parameterName === 'authentication') {
|
|
return 'accessToken';
|
|
}
|
|
return defaultValue;
|
|
},
|
|
);
|
|
|
|
await githubNode.execute.call(mockExecutionContext);
|
|
|
|
expect(mockExecutionContext.helpers.requestWithAuthentication).toHaveBeenCalledWith(
|
|
expect.any(String),
|
|
expect.objectContaining({
|
|
method: 'GET',
|
|
uri: `https://api.github.com/repos/${owner}/${repository}/actions/workflows/${workflowId}`,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('should use extractValue for workflowId in getUsage operation', async () => {
|
|
const owner = 'testOwner';
|
|
const repository = 'testRepository';
|
|
const workflowId = 147025216;
|
|
|
|
mockExecutionContext.getNodeParameter.mockImplementation(
|
|
(parameterName: string, _itemIndex: number, defaultValue: string, options?: any) => {
|
|
if (parameterName === 'owner') {
|
|
return owner;
|
|
}
|
|
if (parameterName === 'repository') {
|
|
return repository;
|
|
}
|
|
if (parameterName === 'workflowId') {
|
|
expect(options).toBeDefined();
|
|
expect(options.extractValue).toBe(true);
|
|
return workflowId;
|
|
}
|
|
if (parameterName === 'resource') {
|
|
return 'workflow';
|
|
}
|
|
if (parameterName === 'operation') {
|
|
return 'getUsage';
|
|
}
|
|
if (parameterName === 'authentication') {
|
|
return 'accessToken';
|
|
}
|
|
return defaultValue;
|
|
},
|
|
);
|
|
|
|
await githubNode.execute.call(mockExecutionContext);
|
|
|
|
expect(mockExecutionContext.helpers.requestWithAuthentication).toHaveBeenCalledWith(
|
|
expect.any(String),
|
|
expect.objectContaining({
|
|
method: 'GET',
|
|
uri: `https://api.github.com/repos/${owner}/${repository}/actions/workflows/${workflowId}/timing`,
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('Parameter Extraction', () => {
|
|
it('should use extractValue for workflowId parameter', () => {
|
|
const githubNode = new Github();
|
|
const description = githubNode.description;
|
|
|
|
const workflowIdParam = description.properties.find((prop) => prop.name === 'workflowId');
|
|
|
|
expect(workflowIdParam).toBeDefined();
|
|
expect(workflowIdParam?.type).toBe('resourceLocator');
|
|
|
|
const workflowOperations = description.properties.find(
|
|
(prop) =>
|
|
prop.name === 'operation' && prop.displayOptions?.show?.resource?.includes('workflow'),
|
|
);
|
|
|
|
expect(workflowOperations).toBeDefined();
|
|
expect(workflowOperations?.options).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({ value: 'disable' }),
|
|
expect.objectContaining({ value: 'dispatch' }),
|
|
expect.objectContaining({ value: 'enable' }),
|
|
expect.objectContaining({ value: 'get' }),
|
|
expect.objectContaining({ value: 'getUsage' }),
|
|
]),
|
|
);
|
|
});
|
|
});
|
|
});
|