mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 10:02:05 +00:00
fix(GitHub Node): Tolerate trailing slash in file path (#15517)
This commit is contained in:
@@ -25,6 +25,7 @@ import {
|
|||||||
validateJSON,
|
validateJSON,
|
||||||
} from './GenericFunctions';
|
} from './GenericFunctions';
|
||||||
import { getRefs, getRepositories, getUsers, getWorkflows } from './SearchFunctions';
|
import { getRefs, getRepositories, getUsers, getWorkflows } from './SearchFunctions';
|
||||||
|
import { removeTrailingSlash } from '../../utils/utilities';
|
||||||
import { defaultWebhookDescription } from '../Webhook/description';
|
import { defaultWebhookDescription } from '../Webhook/description';
|
||||||
|
|
||||||
export class Github implements INodeType {
|
export class Github implements INodeType {
|
||||||
@@ -2250,7 +2251,7 @@ export class Github implements INodeType {
|
|||||||
|
|
||||||
requestMethod = 'PUT';
|
requestMethod = 'PUT';
|
||||||
|
|
||||||
const filePath = this.getNodeParameter('filePath', i);
|
const filePath = removeTrailingSlash(this.getNodeParameter('filePath', i));
|
||||||
|
|
||||||
const additionalParameters = this.getNodeParameter(
|
const additionalParameters = this.getNodeParameter(
|
||||||
'additionalParameters',
|
'additionalParameters',
|
||||||
@@ -2326,7 +2327,7 @@ export class Github implements INodeType {
|
|||||||
body.branch = (additionalParameters.branch as IDataObject).branch;
|
body.branch = (additionalParameters.branch as IDataObject).branch;
|
||||||
}
|
}
|
||||||
|
|
||||||
const filePath = this.getNodeParameter('filePath', i);
|
const filePath = removeTrailingSlash(this.getNodeParameter('filePath', i));
|
||||||
body.message = this.getNodeParameter('commitMessage', i) as string;
|
body.message = this.getNodeParameter('commitMessage', i) as string;
|
||||||
|
|
||||||
body.sha = await getFileSha.call(
|
body.sha = await getFileSha.call(
|
||||||
@@ -2341,7 +2342,7 @@ export class Github implements INodeType {
|
|||||||
} else if (operation === 'get') {
|
} else if (operation === 'get') {
|
||||||
requestMethod = 'GET';
|
requestMethod = 'GET';
|
||||||
|
|
||||||
const filePath = this.getNodeParameter('filePath', i);
|
const filePath = removeTrailingSlash(this.getNodeParameter('filePath', i));
|
||||||
const additionalParameters = this.getNodeParameter(
|
const additionalParameters = this.getNodeParameter(
|
||||||
'additionalParameters',
|
'additionalParameters',
|
||||||
i,
|
i,
|
||||||
@@ -2354,7 +2355,7 @@ export class Github implements INodeType {
|
|||||||
endpoint = `/repos/${owner}/${repository}/contents/${encodeURIComponent(filePath)}`;
|
endpoint = `/repos/${owner}/${repository}/contents/${encodeURIComponent(filePath)}`;
|
||||||
} else if (operation === 'list') {
|
} else if (operation === 'list') {
|
||||||
requestMethod = 'GET';
|
requestMethod = 'GET';
|
||||||
const filePath = this.getNodeParameter('filePath', i);
|
const filePath = removeTrailingSlash(this.getNodeParameter('filePath', i));
|
||||||
endpoint = `/repos/${owner}/${repository}/contents/${encodeURIComponent(filePath)}`;
|
endpoint = `/repos/${owner}/${repository}/contents/${encodeURIComponent(filePath)}`;
|
||||||
}
|
}
|
||||||
} else if (resource === 'issue') {
|
} else if (resource === 'issue') {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
|||||||
import { NodeApiError, NodeOperationError } from 'n8n-workflow';
|
import { NodeApiError, NodeOperationError } from 'n8n-workflow';
|
||||||
import nock from 'nock';
|
import nock from 'nock';
|
||||||
|
|
||||||
|
import * as utilities from '../../../../utils/utilities';
|
||||||
import { Github } from '../../Github.node';
|
import { Github } from '../../Github.node';
|
||||||
|
|
||||||
describe('Test Github Node', () => {
|
describe('Test Github Node', () => {
|
||||||
@@ -62,6 +63,73 @@ describe('Test Github Node', () => {
|
|||||||
jest.useFakeTimers({ doNotFake: ['nextTick'], now });
|
jest.useFakeTimers({ doNotFake: ['nextTick'], now });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('removeTrailingSlash Function', () => {
|
||||||
|
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: {} }]),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
jest.spyOn(utilities, 'removeTrailingSlash');
|
||||||
|
jest.mock('../../../../utils/utilities', () => ({
|
||||||
|
...jest.requireActual('../../../../utils/utilities'),
|
||||||
|
getFileSha: jest.fn().mockResolvedValue('mockedSHA'),
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call remove trailing slash', async () => {
|
||||||
|
mockExecutionContext.getNodeParameter.mockImplementation((parameterName: string) => {
|
||||||
|
if (parameterName === 'operation') {
|
||||||
|
return 'list';
|
||||||
|
}
|
||||||
|
if (parameterName === 'resource') {
|
||||||
|
return 'file';
|
||||||
|
}
|
||||||
|
if (parameterName === 'filePath') {
|
||||||
|
return 'path/to/file/';
|
||||||
|
}
|
||||||
|
if (parameterName === 'owner') {
|
||||||
|
return 'me';
|
||||||
|
}
|
||||||
|
if (parameterName === 'repository') {
|
||||||
|
return 'repo';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
|
await githubNode.execute.call(mockExecutionContext);
|
||||||
|
|
||||||
|
expect(utilities.removeTrailingSlash).toHaveBeenCalledWith('path/to/file/');
|
||||||
|
expect(mockExecutionContext.helpers.requestWithAuthentication).toHaveBeenCalledWith(
|
||||||
|
'githubOAuth2Api',
|
||||||
|
{
|
||||||
|
body: {},
|
||||||
|
headers: { 'User-Agent': 'n8n' },
|
||||||
|
json: true,
|
||||||
|
method: 'GET',
|
||||||
|
qs: {},
|
||||||
|
uri: 'https://api.github.com/repos/me/repo/contents/path%2Fto%2Ffile',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const baseUrl = 'https://api.github.com';
|
const baseUrl = 'https://api.github.com';
|
||||||
nock(baseUrl)
|
nock(baseUrl)
|
||||||
|
|||||||
@@ -11,12 +11,7 @@ import type {
|
|||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { NodeApiError } from 'n8n-workflow';
|
import { NodeApiError } from 'n8n-workflow';
|
||||||
|
|
||||||
export const removeTrailingSlash = (url: string) => {
|
import { removeTrailingSlash } from '../../utils/utilities';
|
||||||
if (url.endsWith('/')) {
|
|
||||||
return url.slice(0, -1);
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function strapiApiRequest(
|
export async function strapiApiRequest(
|
||||||
this: IExecuteFunctions | ILoadOptionsFunctions | IHookFunctions | IWebhookFunctions,
|
this: IExecuteFunctions | ILoadOptionsFunctions | IHookFunctions | IWebhookFunctions,
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ import { NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';
|
|||||||
import { entryFields, entryOperations } from './EntryDescription';
|
import { entryFields, entryOperations } from './EntryDescription';
|
||||||
import {
|
import {
|
||||||
getToken,
|
getToken,
|
||||||
removeTrailingSlash,
|
|
||||||
strapiApiRequest,
|
strapiApiRequest,
|
||||||
strapiApiRequestAllItems,
|
strapiApiRequestAllItems,
|
||||||
validateJSON,
|
validateJSON,
|
||||||
} from './GenericFunctions';
|
} from './GenericFunctions';
|
||||||
|
import { removeTrailingSlash } from '../../utils/utilities';
|
||||||
|
|
||||||
export class Strapi implements INodeType {
|
export class Strapi implements INodeType {
|
||||||
description: INodeTypeDescription = {
|
description: INodeTypeDescription = {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
fuzzyCompare,
|
fuzzyCompare,
|
||||||
getResolvables,
|
getResolvables,
|
||||||
keysToLowercase,
|
keysToLowercase,
|
||||||
|
removeTrailingSlash,
|
||||||
shuffleArray,
|
shuffleArray,
|
||||||
sortItemKeysByPriorityList,
|
sortItemKeysByPriorityList,
|
||||||
wrapData,
|
wrapData,
|
||||||
@@ -312,3 +313,13 @@ describe('sortItemKeysByPriorityList', () => {
|
|||||||
expect(Object.keys(result[0].json)).toEqual(['a', 'b', 'd']);
|
expect(Object.keys(result[0].json)).toEqual(['a', 'b', 'd']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('removeTrailingSlash', () => {
|
||||||
|
it('removes trailing slash', () => {
|
||||||
|
expect(removeTrailingSlash('https://example.com/')).toBe('https://example.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not change a URL without trailing slash', () => {
|
||||||
|
expect(removeTrailingSlash('https://example.com')).toBe('https://example.com');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -472,3 +472,10 @@ export function createUtmCampaignLink(nodeType: string, instanceId?: string) {
|
|||||||
nodeType,
|
nodeType,
|
||||||
)}${instanceId ? '_' + instanceId : ''}`;
|
)}${instanceId ? '_' + instanceId : ''}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const removeTrailingSlash = (url: string) => {
|
||||||
|
if (url.endsWith('/')) {
|
||||||
|
return url.slice(0, -1);
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user