From 0ab07f04788e9d4f82516939da7e83d3b3dbef44 Mon Sep 17 00:00:00 2001 From: Vic A <127297567+vicalca@users.noreply.github.com> Date: Mon, 17 Nov 2025 12:11:23 +0100 Subject: [PATCH] fix(Embeddings Azure OpenAi Node): Add proxy agent (#18663) Co-authored-by: RomanDavydchuk --- .../EmbeddingsAzureOpenAi.node.ts | 8 ++ .../test/EmbeddingsAzureOpenAi.test.ts | 97 +++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 packages/@n8n/nodes-langchain/nodes/embeddings/test/EmbeddingsAzureOpenAi.test.ts diff --git a/packages/@n8n/nodes-langchain/nodes/embeddings/EmbeddingsAzureOpenAi/EmbeddingsAzureOpenAi.node.ts b/packages/@n8n/nodes-langchain/nodes/embeddings/EmbeddingsAzureOpenAi/EmbeddingsAzureOpenAi.node.ts index dd1fe503930..8a279e9d62c 100644 --- a/packages/@n8n/nodes-langchain/nodes/embeddings/EmbeddingsAzureOpenAi/EmbeddingsAzureOpenAi.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/embeddings/EmbeddingsAzureOpenAi/EmbeddingsAzureOpenAi.node.ts @@ -7,6 +7,7 @@ import { type SupplyData, } from 'n8n-workflow'; +import { getProxyAgent } from '@utils/httpProxyAgent'; import { logWrapper } from '@utils/logWrapper'; import { getConnectionHintNoticeField } from '@utils/sharedFields'; @@ -153,6 +154,13 @@ export class EmbeddingsAzureOpenAi implements INodeType { azureOpenAIBasePath: credentials.endpoint ? `${credentials.endpoint}/openai/deployments` : undefined, + configuration: { + fetchOptions: { + dispatcher: getProxyAgent( + credentials.endpoint ?? `https://${credentials.resourceName}.openai.azure.com`, + ), + }, + }, ...options, }); diff --git a/packages/@n8n/nodes-langchain/nodes/embeddings/test/EmbeddingsAzureOpenAi.test.ts b/packages/@n8n/nodes-langchain/nodes/embeddings/test/EmbeddingsAzureOpenAi.test.ts new file mode 100644 index 00000000000..247d975b165 --- /dev/null +++ b/packages/@n8n/nodes-langchain/nodes/embeddings/test/EmbeddingsAzureOpenAi.test.ts @@ -0,0 +1,97 @@ +/* eslint-disable n8n-nodes-base/node-filename-against-convention */ +/* eslint-disable @typescript-eslint/unbound-method */ +import { AzureOpenAIEmbeddings } from '@langchain/openai'; +import { createMockExecuteFunction } from 'n8n-nodes-base/test/nodes/Helpers'; +import type { INode, ISupplyDataFunctions } from 'n8n-workflow'; + +import { EmbeddingsAzureOpenAi } from '../EmbeddingsAzureOpenAi/EmbeddingsAzureOpenAi.node'; + +jest.mock('@langchain/openai'); + +class MockProxyAgent {} + +jest.mock('@utils/httpProxyAgent', () => ({ + getProxyAgent: jest.fn().mockImplementation(() => new MockProxyAgent()), +})); + +const MockedAzureOpenAIEmbeddings = jest.mocked(AzureOpenAIEmbeddings); + +describe('AzureOpenAIEmbeddings', () => { + let embeddingsAzureOpenAi: EmbeddingsAzureOpenAi; + let mockContext: jest.Mocked; + + const mockNode: INode = { + id: '1', + name: 'Embeddings Azure OpenAI', + typeVersion: 1, + type: '@n8n/n8n-nodes-langchain.embeddingsAzureOpenAi', + position: [0, 0], + parameters: {}, + }; + + const setupMockContext = (nodeOverrides: Partial = {}) => { + const node = { ...mockNode, ...nodeOverrides }; + mockContext = createMockExecuteFunction( + {}, + node, + ) as jest.Mocked; + + // Setup default mocks + mockContext.getCredentials = jest.fn().mockResolvedValue({ + apiKey: 'test-api-key', + }); + mockContext.getNode = jest.fn().mockReturnValue(node); + mockContext.getNodeParameter = jest.fn(); + mockContext.logger = { + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }; + return mockContext; + }; + + beforeEach(() => { + embeddingsAzureOpenAi = new EmbeddingsAzureOpenAi(); + jest.clearAllMocks(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('supplyData', () => { + it('dispatcher should get proxy agent', async () => { + const mockContext = setupMockContext(); + + mockContext.getCredentials.mockResolvedValue({ + apiKey: 'test-api-key', + endpoint: 'https://test-resource-name.openai.azure.com', + apiVersion: 'v1', + }); + + mockContext.getNodeParameter = jest.fn().mockImplementation((paramName: string) => { + if (paramName === 'model') return 'text-embedding-3-large'; + if (paramName === 'options') return {}; + return undefined; + }); + + await embeddingsAzureOpenAi.supplyData.call(mockContext, 0); + + expect(MockedAzureOpenAIEmbeddings).toHaveBeenCalledWith( + expect.objectContaining({ + azureOpenAIApiDeploymentName: 'text-embedding-3-large', + azureOpenAIApiInstanceName: undefined, + azureOpenAIApiKey: 'test-api-key', + azureOpenAIApiVersion: 'v1', + azureOpenAIBasePath: 'https://test-resource-name.openai.azure.com/openai/deployments', + configuration: { + fetchOptions: { + dispatcher: expect.any(MockProxyAgent), + }, + }, + }), + ); + }); + }); +});