From 9346463c6bd353e50ed769a8fc633042b434b85f Mon Sep 17 00:00:00 2001 From: RomanDavydchuk Date: Mon, 23 Jun 2025 13:30:42 +0300 Subject: [PATCH] fix(MongoDB Atlas Vector Store Node): Old credentials used even after credentials are updated/changed (#16471) --- .../VectorStoreMongoDBAtlas.node.test.ts | 78 +++++++++++++++++++ .../VectorStoreMongoDBAtlas.node.ts | 23 ++++-- 2 files changed, 94 insertions(+), 7 deletions(-) create mode 100644 packages/@n8n/nodes-langchain/nodes/vector_store/VectorStoreMongoDBAtlas/VectorStoreMongoDBAtlas.node.test.ts diff --git a/packages/@n8n/nodes-langchain/nodes/vector_store/VectorStoreMongoDBAtlas/VectorStoreMongoDBAtlas.node.test.ts b/packages/@n8n/nodes-langchain/nodes/vector_store/VectorStoreMongoDBAtlas/VectorStoreMongoDBAtlas.node.test.ts new file mode 100644 index 0000000000..ba3dda5eea --- /dev/null +++ b/packages/@n8n/nodes-langchain/nodes/vector_store/VectorStoreMongoDBAtlas/VectorStoreMongoDBAtlas.node.test.ts @@ -0,0 +1,78 @@ +import { MongoClient } from 'mongodb'; + +import { getMongoClient, mongoConfig } from './VectorStoreMongoDBAtlas.node'; + +jest.mock('mongodb', () => ({ + MongoClient: jest.fn(), +})); + +describe('VectorStoreMongoDBAtlas -> getMongoClient', () => { + const mockContext = { + getCredentials: jest.fn(), + }; + const mockClient1 = { + connect: jest.fn().mockResolvedValue(undefined), + close: jest.fn().mockResolvedValue(undefined), + }; + const mockClient2 = { + connect: jest.fn().mockResolvedValue(undefined), + close: jest.fn().mockResolvedValue(undefined), + }; + const MockMongoClient = MongoClient as jest.MockedClass; + + beforeEach(() => { + jest.resetAllMocks(); + mongoConfig.client = null; + mongoConfig.connectionString = ''; + }); + + it('should reuse the same client when connection string is unchanged', async () => { + MockMongoClient.mockImplementation(() => mockClient1 as unknown as MongoClient); + mockContext.getCredentials.mockResolvedValue({ + connectionString: 'mongodb://localhost:27017', + }); + + const client1 = await getMongoClient(mockContext); + const client2 = await getMongoClient(mockContext); + + expect(MockMongoClient).toHaveBeenCalledTimes(1); + expect(MockMongoClient).toHaveBeenCalledWith('mongodb://localhost:27017', { + appName: 'devrel.integration.n8n_vector_integ', + }); + expect(mockClient1.connect).toHaveBeenCalledTimes(1); + expect(mockClient1.close).not.toHaveBeenCalled(); + expect(mockClient2.connect).not.toHaveBeenCalled(); + expect(client1).toBe(mockClient1); + expect(client2).toBe(mockClient1); + }); + + it('should create new client when connection string changes', async () => { + MockMongoClient.mockImplementationOnce( + () => mockClient1 as unknown as MongoClient, + ).mockImplementationOnce(() => mockClient2 as unknown as MongoClient); + mockContext.getCredentials + .mockResolvedValueOnce({ + connectionString: 'mongodb://localhost:27017', + }) + .mockResolvedValueOnce({ + connectionString: 'mongodb://different-host:27017', + }); + + const client1 = await getMongoClient(mockContext); + const client2 = await getMongoClient(mockContext); + + expect(MockMongoClient).toHaveBeenCalledTimes(2); + expect(MockMongoClient).toHaveBeenNthCalledWith(1, 'mongodb://localhost:27017', { + appName: 'devrel.integration.n8n_vector_integ', + }); + expect(MockMongoClient).toHaveBeenNthCalledWith(2, 'mongodb://different-host:27017', { + appName: 'devrel.integration.n8n_vector_integ', + }); + expect(mockClient1.connect).toHaveBeenCalledTimes(1); + expect(mockClient1.close).toHaveBeenCalledTimes(1); + expect(mockClient2.connect).toHaveBeenCalledTimes(1); + expect(mockClient2.close).not.toHaveBeenCalled(); + expect(client1).toBe(mockClient1); + expect(client2).toBe(mockClient2); + }); +}); diff --git a/packages/@n8n/nodes-langchain/nodes/vector_store/VectorStoreMongoDBAtlas/VectorStoreMongoDBAtlas.node.ts b/packages/@n8n/nodes-langchain/nodes/vector_store/VectorStoreMongoDBAtlas/VectorStoreMongoDBAtlas.node.ts index 0918a08977..fb016226f0 100644 --- a/packages/@n8n/nodes-langchain/nodes/vector_store/VectorStoreMongoDBAtlas/VectorStoreMongoDBAtlas.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/vector_store/VectorStoreMongoDBAtlas/VectorStoreMongoDBAtlas.node.ts @@ -103,17 +103,26 @@ const insertFields: INodeProperties[] = [ }, ]; -let mongoClient: MongoClient | null = null; +export const mongoConfig = { + client: null as MongoClient | null, + connectionString: '', +}; -async function getMongoClient(context: any) { - if (!mongoClient) { - const credentials = await context.getCredentials('mongoDb'); - mongoClient = new MongoClient(credentials.connectionString as string, { +export async function getMongoClient(context: any) { + const credentials = await context.getCredentials('mongoDb'); + const connectionString = credentials.connectionString as string; + if (!mongoConfig.client || mongoConfig.connectionString !== connectionString) { + if (mongoConfig.client) { + await mongoConfig.client.close(); + } + + mongoConfig.connectionString = connectionString; + mongoConfig.client = new MongoClient(connectionString, { appName: 'devrel.integration.n8n_vector_integ', }); - await mongoClient.connect(); + await mongoConfig.client.connect(); } - return mongoClient; + return mongoConfig.client; } async function mongoClientAndDatabase(context: any) {