fix: 修复TypeScript配置错误并更新项目文档
详细说明: - 修复了@n8n/config包的TypeScript配置错误 - 移除了不存在的jest-expect-message类型引用 - 清理了所有TypeScript构建缓存 - 更新了可行性分析文档,添加了技术实施方案 - 更新了Agent prompt文档 - 添加了会展策划工作流文档 - 包含了n8n-chinese-translation子项目 - 添加了exhibition-demo展示系统框架
This commit is contained in:
@@ -1,60 +0,0 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"httpMethod": "POST",
|
||||
"path": "test",
|
||||
"options": {
|
||||
"binaryData": false
|
||||
}
|
||||
},
|
||||
"id": "ec188f16-b2c5-44e3-bd83-259a94f15c94",
|
||||
"name": "Webhook",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 1,
|
||||
"webhookId": "a59a3be7-6d43-47e7-951d-86b8172c2006"
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Webhook": {
|
||||
"main": [[]]
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"mode": "webhook",
|
||||
"input": {
|
||||
"json": {
|
||||
"headers": {
|
||||
"host": "localhost:5678",
|
||||
"user-agent": "curl/8.2.0",
|
||||
"accept": "*/*",
|
||||
"content-length": "137",
|
||||
"content-type": "multipart/form-data; boundary=--boundary"
|
||||
},
|
||||
"params": { "path": "test" },
|
||||
"query": {},
|
||||
"body": { "a": ["b"] }
|
||||
}
|
||||
}
|
||||
},
|
||||
"pinData": {
|
||||
"Webhook": [
|
||||
{
|
||||
"json": {
|
||||
"headers": {
|
||||
"host": "localhost:5678",
|
||||
"user-agent": "curl/8.2.0",
|
||||
"accept": "*/*",
|
||||
"content-length": "137",
|
||||
"content-type": "multipart/form-data; boundary=--boundary"
|
||||
},
|
||||
"params": { "path": "test" },
|
||||
"query": {},
|
||||
"body": {
|
||||
"a": ["b"]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import type { Request, Response } from 'express';
|
||||
import fs from 'fs/promises';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import type { IWebhookFunctions } from 'n8n-workflow';
|
||||
|
||||
import { Webhook } from '../Webhook.node';
|
||||
|
||||
jest.mock('fs/promises');
|
||||
const mockFs = jest.mocked(fs);
|
||||
|
||||
describe('Test Webhook Node', () => {
|
||||
new NodeTestHarness().setupTests();
|
||||
|
||||
describe('handleFormData', () => {
|
||||
const node = new Webhook();
|
||||
const context = mock<IWebhookFunctions>({
|
||||
nodeHelpers: mock(),
|
||||
});
|
||||
context.getNodeParameter.calledWith('options').mockReturnValue({});
|
||||
context.getNode.calledWith().mockReturnValue({
|
||||
type: 'n8n-nodes-base.webhook',
|
||||
typeVersion: 1.1,
|
||||
} as any);
|
||||
const req = mock<Request>();
|
||||
req.contentType = 'multipart/form-data';
|
||||
context.getRequestObject.mockReturnValue(req);
|
||||
|
||||
it('should handle when no files are present', async () => {
|
||||
req.body = {
|
||||
files: {},
|
||||
};
|
||||
const returnData = await node.webhook(context);
|
||||
expect(returnData.workflowData?.[0][0].binary).toBeUndefined();
|
||||
expect(context.nodeHelpers.copyBinaryFile).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle when files are present', async () => {
|
||||
req.body = {
|
||||
files: { file1: { filepath: '/tmp/test.txt' } },
|
||||
};
|
||||
const returnData = await node.webhook(context);
|
||||
expect(returnData.workflowData?.[0][0].binary).not.toBeUndefined();
|
||||
expect(context.nodeHelpers.copyBinaryFile).toHaveBeenCalled();
|
||||
expect(mockFs.rm).toHaveBeenCalledWith('/tmp/test.txt', { force: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe('streaming response mode', () => {
|
||||
const node = new Webhook();
|
||||
const context = mock<IWebhookFunctions>({
|
||||
nodeHelpers: mock(),
|
||||
});
|
||||
const req = mock<Request>();
|
||||
const res = mock<Response>();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
context.getRequestObject.mockReturnValue(req);
|
||||
context.getResponseObject.mockReturnValue(res);
|
||||
context.getChildNodes.mockReturnValue([]);
|
||||
context.getNode.mockReturnValue({
|
||||
type: 'n8n-nodes-base.webhook',
|
||||
typeVersion: 2,
|
||||
name: 'Webhook',
|
||||
} as any);
|
||||
context.getNodeParameter.mockImplementation((paramName: string) => {
|
||||
if (paramName === 'options') return {};
|
||||
if (paramName === 'responseMode') return 'streaming';
|
||||
return undefined;
|
||||
});
|
||||
req.headers = {};
|
||||
req.params = {};
|
||||
req.query = {};
|
||||
req.body = { message: 'test' };
|
||||
Object.defineProperty(req, 'ips', { value: [], configurable: true });
|
||||
Object.defineProperty(req, 'ip', { value: '127.0.0.1', configurable: true });
|
||||
res.writeHead.mockImplementation(() => res);
|
||||
res.flushHeaders.mockImplementation(() => undefined);
|
||||
});
|
||||
|
||||
it('should enable streaming when responseMode is "streaming"', async () => {
|
||||
const result = await node.webhook(context);
|
||||
|
||||
// Verify streaming headers are set
|
||||
expect(res.writeHead).toHaveBeenCalledWith(200, {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
'Transfer-Encoding': 'chunked',
|
||||
'Cache-Control': 'no-cache',
|
||||
Connection: 'keep-alive',
|
||||
});
|
||||
expect(res.flushHeaders).toHaveBeenCalled();
|
||||
|
||||
// Verify response structure for streaming
|
||||
expect(result).toEqual({
|
||||
noWebhookResponse: true,
|
||||
workflowData: expect.any(Array),
|
||||
});
|
||||
});
|
||||
|
||||
it('should not enable streaming when responseMode is not "streaming"', async () => {
|
||||
context.getNodeParameter.mockImplementation((paramName: string) => {
|
||||
if (paramName === 'options') return {};
|
||||
if (paramName === 'responseMode') return 'onReceived';
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const result = await node.webhook(context);
|
||||
|
||||
// Verify streaming headers are NOT set
|
||||
expect(res.writeHead).not.toHaveBeenCalled();
|
||||
expect(res.flushHeaders).not.toHaveBeenCalled();
|
||||
|
||||
// Verify normal response structure
|
||||
expect(result).toEqual({
|
||||
webhookResponse: undefined,
|
||||
workflowData: expect.any(Array),
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle multipart form data with streaming enabled', async () => {
|
||||
req.contentType = 'multipart/form-data';
|
||||
req.body = {
|
||||
data: { message: 'Hello' },
|
||||
files: {},
|
||||
};
|
||||
|
||||
const result = await node.webhook(context);
|
||||
|
||||
// For multipart form data, streaming is handled in handleFormData method
|
||||
// The current implementation returns normal workflowData for form data
|
||||
expect(result).toEqual({
|
||||
workflowData: expect.any(Array),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,474 +0,0 @@
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { ApplicationError, type IWebhookFunctions } from 'n8n-workflow';
|
||||
|
||||
import type { WebhookParameters } from '../utils';
|
||||
import {
|
||||
checkResponseModeConfiguration,
|
||||
configuredOutputs,
|
||||
getResponseCode,
|
||||
getResponseData,
|
||||
isIpWhitelisted,
|
||||
setupOutputConnection,
|
||||
validateWebhookAuthentication,
|
||||
} from '../utils';
|
||||
|
||||
jest.mock('jsonwebtoken', () => ({
|
||||
verify: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('Webhook Utils', () => {
|
||||
describe('getResponseCode', () => {
|
||||
it('should return the response code if it exists', () => {
|
||||
const parameters: WebhookParameters = {
|
||||
responseCode: 404,
|
||||
httpMethod: '',
|
||||
responseMode: '',
|
||||
responseData: '',
|
||||
};
|
||||
const responseCode = getResponseCode(parameters);
|
||||
expect(responseCode).toBe(404);
|
||||
});
|
||||
|
||||
it('should return the custom response code if it exists', () => {
|
||||
const parameters: WebhookParameters = {
|
||||
options: {
|
||||
responseCode: {
|
||||
values: {
|
||||
responseCode: 200,
|
||||
customCode: 201,
|
||||
},
|
||||
},
|
||||
},
|
||||
httpMethod: '',
|
||||
responseMode: '',
|
||||
responseData: '',
|
||||
};
|
||||
const responseCode = getResponseCode(parameters);
|
||||
expect(responseCode).toBe(201);
|
||||
});
|
||||
|
||||
it('should return the default response code if no response code is provided', () => {
|
||||
const parameters: WebhookParameters = {
|
||||
httpMethod: '',
|
||||
responseMode: '',
|
||||
responseData: '',
|
||||
};
|
||||
const responseCode = getResponseCode(parameters);
|
||||
expect(responseCode).toBe(200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getResponseData', () => {
|
||||
it('should return the response data if it exists', () => {
|
||||
const parameters: WebhookParameters = {
|
||||
responseData: 'Hello World',
|
||||
httpMethod: '',
|
||||
responseMode: '',
|
||||
};
|
||||
const responseData = getResponseData(parameters);
|
||||
expect(responseData).toBe('Hello World');
|
||||
});
|
||||
|
||||
it('should return the options response data if response mode is "onReceived"', () => {
|
||||
const parameters: WebhookParameters = {
|
||||
responseMode: 'onReceived',
|
||||
options: {
|
||||
responseData: 'Hello World',
|
||||
},
|
||||
httpMethod: '',
|
||||
responseData: '',
|
||||
};
|
||||
const responseData = getResponseData(parameters);
|
||||
expect(responseData).toBe('Hello World');
|
||||
});
|
||||
|
||||
it('should return "noData" if options noResponseBody is true', () => {
|
||||
const parameters: WebhookParameters = {
|
||||
responseMode: 'onReceived',
|
||||
options: {
|
||||
noResponseBody: true,
|
||||
},
|
||||
httpMethod: '',
|
||||
responseData: '',
|
||||
};
|
||||
const responseData = getResponseData(parameters);
|
||||
expect(responseData).toBe('noData');
|
||||
});
|
||||
|
||||
it('should return undefined if no response data is provided', () => {
|
||||
const parameters: WebhookParameters = {
|
||||
responseMode: 'onReceived',
|
||||
httpMethod: '',
|
||||
responseData: '',
|
||||
};
|
||||
const responseData = getResponseData(parameters);
|
||||
expect(responseData).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('configuredOutputs', () => {
|
||||
it('should return an array with a single output if httpMethod is not an array', () => {
|
||||
const parameters: WebhookParameters = {
|
||||
httpMethod: 'GET',
|
||||
responseMode: '',
|
||||
responseData: '',
|
||||
};
|
||||
const outputs = configuredOutputs(parameters);
|
||||
expect(outputs).toEqual([
|
||||
{
|
||||
type: 'main',
|
||||
displayName: 'GET',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return an array of outputs if httpMethod is an array', () => {
|
||||
const parameters: WebhookParameters = {
|
||||
httpMethod: ['GET', 'POST'],
|
||||
responseMode: '',
|
||||
responseData: '',
|
||||
};
|
||||
const outputs = configuredOutputs(parameters);
|
||||
expect(outputs).toEqual([
|
||||
{
|
||||
type: 'main',
|
||||
displayName: 'GET',
|
||||
},
|
||||
{
|
||||
type: 'main',
|
||||
displayName: 'POST',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setupOutputConnection', () => {
|
||||
it('should return a function that sets the webhookUrl and executionMode in the output data', () => {
|
||||
const ctx: Partial<IWebhookFunctions> = {
|
||||
getNodeParameter: jest.fn().mockReturnValue('GET'),
|
||||
getNodeWebhookUrl: jest.fn().mockReturnValue('https://example.com/webhook/'),
|
||||
getMode: jest.fn().mockReturnValue('manual'),
|
||||
};
|
||||
const method = 'GET';
|
||||
const additionalData = {
|
||||
jwtPayload: {
|
||||
userId: '123',
|
||||
},
|
||||
};
|
||||
const outputData = {
|
||||
json: {},
|
||||
};
|
||||
const setupOutput = setupOutputConnection(ctx as IWebhookFunctions, method, additionalData);
|
||||
const result = setupOutput(outputData);
|
||||
expect(result).toEqual([
|
||||
[
|
||||
{
|
||||
json: {
|
||||
webhookUrl: 'https://example.com/webhook-test/',
|
||||
executionMode: 'test',
|
||||
jwtPayload: { userId: '123' },
|
||||
},
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return a function that sets the webhookUrl and executionMode in the output data for multiple methods', () => {
|
||||
const ctx: Partial<IWebhookFunctions> = {
|
||||
getNodeParameter: jest.fn().mockReturnValue(['GET', 'POST']),
|
||||
getNodeWebhookUrl: jest.fn().mockReturnValue('https://example.com/webhook/'),
|
||||
getMode: jest.fn().mockReturnValue('manual'),
|
||||
};
|
||||
const method = 'POST';
|
||||
const additionalData = {
|
||||
jwtPayload: {
|
||||
userId: '123',
|
||||
},
|
||||
};
|
||||
const outputData = {
|
||||
json: {},
|
||||
};
|
||||
const setupOutput = setupOutputConnection(ctx as IWebhookFunctions, method, additionalData);
|
||||
const result = setupOutput(outputData);
|
||||
expect(result).toEqual([
|
||||
[],
|
||||
[
|
||||
{
|
||||
json: {
|
||||
webhookUrl: 'https://example.com/webhook-test/',
|
||||
executionMode: 'test',
|
||||
jwtPayload: { userId: '123' },
|
||||
},
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isIpWhitelisted', () => {
|
||||
it('should return true if whitelist is undefined', () => {
|
||||
expect(isIpWhitelisted(undefined, ['192.168.1.1'], '192.168.1.1')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true if whitelist is an empty string', () => {
|
||||
expect(isIpWhitelisted('', ['192.168.1.1'], '192.168.1.1')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true if ip is in the whitelist', () => {
|
||||
expect(isIpWhitelisted('192.168.1.1', ['192.168.1.2'], '192.168.1.1')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true if any ip in ips is in the whitelist', () => {
|
||||
expect(isIpWhitelisted('192.168.1.1', ['192.168.1.1', '192.168.1.2'])).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if ip and ips are not in the whitelist', () => {
|
||||
expect(isIpWhitelisted('192.168.1.3', ['192.168.1.1', '192.168.1.2'], '192.168.1.4')).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return true if any ip in ips matches any address in the whitelist array', () => {
|
||||
expect(isIpWhitelisted(['192.168.1.1', '192.168.1.2'], ['192.168.1.2', '192.168.1.3'])).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return true if ip matches any address in the whitelist array', () => {
|
||||
expect(isIpWhitelisted(['192.168.1.1', '192.168.1.2'], ['192.168.1.3'], '192.168.1.2')).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return false if ip and ips do not match any address in the whitelist array', () => {
|
||||
expect(
|
||||
isIpWhitelisted(
|
||||
['192.168.1.4', '192.168.1.5'],
|
||||
['192.168.1.1', '192.168.1.2'],
|
||||
'192.168.1.3',
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle comma-separated whitelist string', () => {
|
||||
expect(isIpWhitelisted('192.168.1.1, 192.168.1.2', ['192.168.1.3'], '192.168.1.2')).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('should trim whitespace in comma-separated whitelist string', () => {
|
||||
expect(isIpWhitelisted(' 192.168.1.1 , 192.168.1.2 ', ['192.168.1.3'], '192.168.1.2')).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkResponseModeConfiguration', () => {
|
||||
it('should throw an error if response mode is "responseNode" but no Respond to Webhook node is found', () => {
|
||||
const context: Partial<IWebhookFunctions> = {
|
||||
getNodeParameter: jest.fn().mockReturnValue('responseNode'),
|
||||
getChildNodes: jest.fn().mockReturnValue([]),
|
||||
getNode: jest.fn().mockReturnValue({ name: 'Webhook' }),
|
||||
};
|
||||
expect(() => {
|
||||
checkResponseModeConfiguration(context as IWebhookFunctions);
|
||||
}).toThrowError('No Respond to Webhook node found in the workflow');
|
||||
});
|
||||
|
||||
it('should throw an error if response mode is not "responseNode" but a Respond to Webhook node is found', () => {
|
||||
const context: Partial<IWebhookFunctions> = {
|
||||
getNodeParameter: jest.fn().mockReturnValue('onReceived'),
|
||||
getChildNodes: jest.fn().mockReturnValue([{ type: 'n8n-nodes-base.respondToWebhook' }]),
|
||||
getNode: jest.fn().mockReturnValue({ name: 'Webhook' }),
|
||||
};
|
||||
expect(() => {
|
||||
checkResponseModeConfiguration(context as IWebhookFunctions);
|
||||
}).toThrowError('Webhook node not correctly configured');
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateWebhookAuthentication', () => {
|
||||
it('should return early if authentication is "none"', async () => {
|
||||
const ctx: Partial<IWebhookFunctions> = {
|
||||
getNodeParameter: jest.fn().mockReturnValue('none'),
|
||||
};
|
||||
const authPropertyName = 'authentication';
|
||||
const result = await validateWebhookAuthentication(
|
||||
ctx as IWebhookFunctions,
|
||||
authPropertyName,
|
||||
);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should throw an error if basicAuth is enabled but no authentication data is defined on the node', async () => {
|
||||
const headers = {
|
||||
authorization: 'Basic some-token',
|
||||
};
|
||||
const ctx: Partial<IWebhookFunctions> = {
|
||||
getNodeParameter: jest.fn().mockReturnValue('basicAuth'),
|
||||
getCredentials: jest.fn().mockRejectedValue(new Error()),
|
||||
getRequestObject: jest.fn().mockReturnValue({
|
||||
headers,
|
||||
}),
|
||||
getHeaderData: jest.fn().mockReturnValue(headers),
|
||||
};
|
||||
const authPropertyName = 'authentication';
|
||||
await expect(
|
||||
validateWebhookAuthentication(ctx as IWebhookFunctions, authPropertyName),
|
||||
).rejects.toThrowError('No authentication data defined on node!');
|
||||
});
|
||||
|
||||
it('should throw an error if basicAuth is enabled but the provided authentication data is wrong', async () => {
|
||||
const headers = {
|
||||
authorization: 'Basic some-token',
|
||||
};
|
||||
const ctx: Partial<IWebhookFunctions> = {
|
||||
getNodeParameter: jest.fn().mockReturnValue('basicAuth'),
|
||||
getCredentials: jest.fn().mockResolvedValue({
|
||||
user: 'admin',
|
||||
password: 'password',
|
||||
}),
|
||||
getRequestObject: jest.fn().mockReturnValue({
|
||||
headers,
|
||||
}),
|
||||
getHeaderData: jest.fn().mockReturnValue(headers),
|
||||
};
|
||||
const authPropertyName = 'authentication';
|
||||
await expect(
|
||||
validateWebhookAuthentication(ctx as IWebhookFunctions, authPropertyName),
|
||||
).rejects.toThrowError('Authorization is required!');
|
||||
});
|
||||
|
||||
it('should throw an error if headerAuth is enabled but no authentication data is defined on the node', async () => {
|
||||
const ctx: Partial<IWebhookFunctions> = {
|
||||
getNodeParameter: jest.fn().mockReturnValue('headerAuth'),
|
||||
getCredentials: jest
|
||||
.fn()
|
||||
.mockRejectedValue(new Error('No authentication data defined on node!')),
|
||||
getRequestObject: jest.fn().mockReturnValue({
|
||||
headers: {},
|
||||
}),
|
||||
getHeaderData: jest.fn().mockReturnValue({}),
|
||||
};
|
||||
const authPropertyName = 'authentication';
|
||||
await expect(
|
||||
validateWebhookAuthentication(ctx as IWebhookFunctions, authPropertyName),
|
||||
).rejects.toThrowError('No authentication data defined on node!');
|
||||
});
|
||||
|
||||
it('should throw an error if headerAuth is enabled but the provided authentication data is wrong', async () => {
|
||||
const headers = {
|
||||
authorization: 'Bearer invalid-token',
|
||||
};
|
||||
const ctx: Partial<IWebhookFunctions> = {
|
||||
getNodeParameter: jest.fn().mockReturnValue('headerAuth'),
|
||||
getCredentials: jest.fn().mockResolvedValue({
|
||||
name: 'Authorization',
|
||||
value: 'Bearer token',
|
||||
}),
|
||||
getRequestObject: jest.fn().mockReturnValue({
|
||||
headers,
|
||||
}),
|
||||
getHeaderData: jest.fn().mockReturnValue(headers),
|
||||
};
|
||||
const authPropertyName = 'authentication';
|
||||
await expect(
|
||||
validateWebhookAuthentication(ctx as IWebhookFunctions, authPropertyName),
|
||||
).rejects.toThrowError('Authorization data is wrong!');
|
||||
});
|
||||
|
||||
it('should throw an error if jwtAuth is enabled but no authentication data is defined on the node', async () => {
|
||||
const ctx: Partial<IWebhookFunctions> = {
|
||||
getNodeParameter: jest.fn().mockReturnValue('jwtAuth'),
|
||||
getCredentials: jest
|
||||
.fn()
|
||||
.mockRejectedValue(new Error('No authentication data defined on node!')),
|
||||
getRequestObject: jest.fn().mockReturnValue({}),
|
||||
getHeaderData: jest.fn().mockReturnValue({}),
|
||||
};
|
||||
const authPropertyName = 'authentication';
|
||||
await expect(
|
||||
validateWebhookAuthentication(ctx as IWebhookFunctions, authPropertyName),
|
||||
).rejects.toThrowError('No authentication data defined on node!');
|
||||
});
|
||||
|
||||
it('should throw an error if jwtAuth is enabled but no token is provided', async () => {
|
||||
const ctx: Partial<IWebhookFunctions> = {
|
||||
getNodeParameter: jest.fn().mockReturnValue('jwtAuth'),
|
||||
getCredentials: jest.fn().mockResolvedValue({
|
||||
keyType: 'passphrase',
|
||||
publicKey: '',
|
||||
secret: 'secret',
|
||||
algorithm: 'HS256',
|
||||
}),
|
||||
getRequestObject: jest.fn().mockReturnValue({
|
||||
headers: {},
|
||||
}),
|
||||
getHeaderData: jest.fn().mockReturnValue({}),
|
||||
};
|
||||
const authPropertyName = 'authentication';
|
||||
await expect(
|
||||
validateWebhookAuthentication(ctx as IWebhookFunctions, authPropertyName),
|
||||
).rejects.toThrowError('No token provided');
|
||||
});
|
||||
|
||||
it('should throw an error if jwtAuth is enabled but the provided token is invalid', async () => {
|
||||
const headers = {
|
||||
authorization: 'Bearer invalid-token',
|
||||
};
|
||||
const ctx: Partial<IWebhookFunctions> = {
|
||||
getNodeParameter: jest.fn().mockReturnValue('jwtAuth'),
|
||||
getCredentials: jest.fn().mockResolvedValue({
|
||||
keyType: 'passphrase',
|
||||
publicKey: '',
|
||||
secret: 'secret',
|
||||
algorithm: 'HS256',
|
||||
}),
|
||||
getRequestObject: jest.fn().mockReturnValue({
|
||||
headers,
|
||||
}),
|
||||
getHeaderData: jest.fn().mockReturnValue(headers),
|
||||
};
|
||||
(jwt.verify as jest.Mock).mockImplementationOnce(() => {
|
||||
throw new ApplicationError('jwt malformed');
|
||||
});
|
||||
const authPropertyName = 'authentication';
|
||||
await expect(
|
||||
validateWebhookAuthentication(ctx as IWebhookFunctions, authPropertyName),
|
||||
).rejects.toThrowError('jwt malformed');
|
||||
});
|
||||
|
||||
it('should return the decoded JWT payload if jwtAuth is enabled and the token is valid', async () => {
|
||||
const decodedPayload = {
|
||||
sub: '1234567890',
|
||||
name: 'John Doe',
|
||||
iat: 1516239022,
|
||||
};
|
||||
(jwt.verify as jest.Mock).mockReturnValue(decodedPayload);
|
||||
const headers = {
|
||||
authorization:
|
||||
'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c',
|
||||
};
|
||||
const ctx: Partial<IWebhookFunctions> = {
|
||||
getNodeParameter: jest.fn().mockReturnValue('jwtAuth'),
|
||||
getCredentials: jest.fn().mockResolvedValue({
|
||||
keyType: 'passphrase',
|
||||
publicKey: '',
|
||||
secret: 'secret',
|
||||
algorithm: 'HS256',
|
||||
}),
|
||||
getRequestObject: jest.fn().mockReturnValue({
|
||||
headers,
|
||||
}),
|
||||
getHeaderData: jest.fn().mockReturnValue(headers),
|
||||
};
|
||||
const authPropertyName = 'authentication';
|
||||
|
||||
const result = await validateWebhookAuthentication(
|
||||
ctx as IWebhookFunctions,
|
||||
authPropertyName,
|
||||
);
|
||||
expect(result).toEqual(decodedPayload);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user