From 388a83dfbdc6ac301e4df704666df9f09fb7d0b3 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 18 Dec 2024 10:55:40 +0000 Subject: [PATCH] fix(Elasticsearch Node): Fix issue stopping search queries being sent (#11464) --- .../ElasticsearchApi.credentials.ts | 2 +- .../Elasticsearch/Elasticsearch.node.ts | 4 +- .../Elastic/Elasticsearch/GenericFunctions.ts | 4 +- .../tests/GenericFunctions.test.ts | 112 ++++++++++++++++++ 4 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 packages/nodes-base/nodes/Elastic/Elasticsearch/tests/GenericFunctions.test.ts diff --git a/packages/nodes-base/credentials/ElasticsearchApi.credentials.ts b/packages/nodes-base/credentials/ElasticsearchApi.credentials.ts index b487ce894f..25811fba80 100644 --- a/packages/nodes-base/credentials/ElasticsearchApi.credentials.ts +++ b/packages/nodes-base/credentials/ElasticsearchApi.credentials.ts @@ -56,7 +56,7 @@ export class ElasticsearchApi implements ICredentialType { test: ICredentialTestRequest = { request: { - baseURL: '={{$credentials.baseUrl}}', + baseURL: '={{$credentials.baseUrl}}'.replace(/\/$/, ''), url: '/_xpack?human=false', skipSslCertificateValidation: '={{$credentials.ignoreSSLIssues}}', }, diff --git a/packages/nodes-base/nodes/Elastic/Elasticsearch/Elasticsearch.node.ts b/packages/nodes-base/nodes/Elastic/Elasticsearch/Elasticsearch.node.ts index d13d237514..ca5906d6ec 100644 --- a/packages/nodes-base/nodes/Elastic/Elasticsearch/Elasticsearch.node.ts +++ b/packages/nodes-base/nodes/Elastic/Elasticsearch/Elasticsearch.node.ts @@ -172,7 +172,7 @@ export class Elasticsearch implements INodeType { } else { responseData = await elasticsearchApiRequest.call( this, - 'GET', + 'POST', `/${indexId}/_search`, body, qs, @@ -184,7 +184,7 @@ export class Elasticsearch implements INodeType { responseData = await elasticsearchApiRequest.call( this, - 'GET', + 'POST', `/${indexId}/_search`, body, qs, diff --git a/packages/nodes-base/nodes/Elastic/Elasticsearch/GenericFunctions.ts b/packages/nodes-base/nodes/Elastic/Elasticsearch/GenericFunctions.ts index 333a412a02..9bfce49d80 100644 --- a/packages/nodes-base/nodes/Elastic/Elasticsearch/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Elastic/Elasticsearch/GenericFunctions.ts @@ -20,7 +20,7 @@ export async function elasticsearchBulkApiRequest(this: IExecuteFunctions, body: method: 'POST', headers: { 'Content-Type': 'application/x-ndjson' }, body: bulkBody, - url: `${baseUrl}/_bulk`, + url: `${baseUrl.replace(/\/$/, '')}/_bulk`, skipSslCertificateValidation: ignoreSSLIssues, returnFullResponse: true, ignoreHttpStatusErrors: true, @@ -66,7 +66,7 @@ export async function elasticsearchApiRequest( method, body, qs, - url: `${baseUrl}${endpoint}`, + url: `${baseUrl.replace(/\/$/, '')}${endpoint}`, json: true, skipSslCertificateValidation: ignoreSSLIssues, }; diff --git a/packages/nodes-base/nodes/Elastic/Elasticsearch/tests/GenericFunctions.test.ts b/packages/nodes-base/nodes/Elastic/Elasticsearch/tests/GenericFunctions.test.ts new file mode 100644 index 0000000000..a578344410 --- /dev/null +++ b/packages/nodes-base/nodes/Elastic/Elasticsearch/tests/GenericFunctions.test.ts @@ -0,0 +1,112 @@ +import { type IExecuteFunctions, NodeApiError } from 'n8n-workflow'; + +import { elasticsearchApiRequest } from '../GenericFunctions'; + +describe('Elasticsearch -> elasticsearchApiRequest', () => { + let mockExecuteFunctions: IExecuteFunctions; + + const mockHttpRequestWithAuthentication = jest.fn(); + + const setupMockFunctions = () => { + mockExecuteFunctions = { + getCredentials: jest.fn().mockResolvedValue({ + baseUrl: 'https://example.com', + ignoreSSLIssues: false, + }), + helpers: { + httpRequestWithAuthentication: mockHttpRequestWithAuthentication, + }, + getNode: jest.fn().mockReturnValue({}), + } as unknown as IExecuteFunctions; + jest.clearAllMocks(); + }; + + beforeEach(() => { + setupMockFunctions(); + mockHttpRequestWithAuthentication.mockClear(); + }); + + const response = { success: true }; + + it('should make a successful GET API request', async () => { + mockHttpRequestWithAuthentication.mockResolvedValue(response); + + const result = await elasticsearchApiRequest.call( + mockExecuteFunctions, + 'GET', + '/test-endpoint', + ); + + expect(result).toEqual(response); + expect(mockExecuteFunctions.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith( + 'elasticsearchApi', + expect.objectContaining({ + method: 'GET', + url: 'https://example.com/test-endpoint', + json: true, + skipSslCertificateValidation: false, + }), + ); + }); + + it('should make a successful POST API request', async () => { + const body = { key: 'value' }; + + mockHttpRequestWithAuthentication.mockResolvedValue(response); + + const result = await elasticsearchApiRequest.call( + mockExecuteFunctions, + 'POST', + '/test-endpoint', + body, + ); + + expect(result).toEqual(response); + expect(mockExecuteFunctions.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith( + 'elasticsearchApi', + expect.objectContaining({ + body, + method: 'POST', + url: 'https://example.com/test-endpoint', + json: true, + skipSslCertificateValidation: false, + }), + ); + }); + + it('should handle API request errors', async () => { + const errorResponse = { message: 'Error occurred' }; + mockHttpRequestWithAuthentication.mockRejectedValue(errorResponse); + + await expect( + elasticsearchApiRequest.call(mockExecuteFunctions, 'GET', '/test-endpoint'), + ).rejects.toThrow(NodeApiError); + + expect(mockExecuteFunctions.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith( + 'elasticsearchApi', + expect.objectContaining({ + method: 'GET', + url: 'https://example.com/test-endpoint', + json: true, + skipSslCertificateValidation: false, + }), + ); + }); + + it('should ignore trailing slashes in the base URL', async () => { + mockHttpRequestWithAuthentication.mockResolvedValue(response); + + mockExecuteFunctions.getCredentials = jest.fn().mockResolvedValue({ + baseUrl: 'https://elastic.domain.com/', + ignoreSSLIssues: false, + }); + await elasticsearchApiRequest.call(mockExecuteFunctions, 'GET', '/test-endpoint'); + + expect(mockExecuteFunctions.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith( + 'elasticsearchApi', + expect.objectContaining({ + url: 'https://elastic.domain.com/test-endpoint', + }), + ); + }); +});