From ef6c61c824aeb281cdc377dac29a2de124dfa0d0 Mon Sep 17 00:00:00 2001 From: michelleyeoh Date: Fri, 27 Feb 2026 17:42:30 -0800 Subject: [PATCH 1/8] update cached client --- app/(api)/_utils/mongodb/mongoClient.mjs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/(api)/_utils/mongodb/mongoClient.mjs b/app/(api)/_utils/mongodb/mongoClient.mjs index 33bcfaaf5..4c199b937 100644 --- a/app/(api)/_utils/mongodb/mongoClient.mjs +++ b/app/(api)/_utils/mongodb/mongoClient.mjs @@ -2,13 +2,19 @@ import { MongoClient } from 'mongodb'; const uri = process.env.MONGODB_URI; let cachedClient = null; +let cachedPromise = null; export async function getClient() { if (cachedClient) { return cachedClient; } - const client = new MongoClient(uri); - cachedClient = client; + + if (!cachedPromise) { + const client = new MongoClient(uri); + cachedPromise = client.connect(); + } + + cachedClient = await cachedPromise; return cachedClient; } From 252d2d08a89bf668dda9b154eefcd3a93a6dcdd4 Mon Sep 17 00:00:00 2001 From: michelleyeoh Date: Fri, 27 Feb 2026 18:12:06 -0800 Subject: [PATCH 2/8] updated mongoclient and test --- __tests__/mongoClient.test.ts | 31 ++++++++++++++++++++++++ app/(api)/_utils/mongodb/mongoClient.mjs | 26 +++++++++++++++++--- 2 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 __tests__/mongoClient.test.ts diff --git a/__tests__/mongoClient.test.ts b/__tests__/mongoClient.test.ts new file mode 100644 index 000000000..847ca5d81 --- /dev/null +++ b/__tests__/mongoClient.test.ts @@ -0,0 +1,31 @@ +import { getClient, resetClient } from '@utils/mongodb/mongoClient.mjs'; +import { MongoClient } from 'mongodb'; + +describe('getClient', () => { + beforeEach(() => { + resetClient(); + jest.clearAllMocks(); + }); + + it('should return the same instance on multiple calls', async () => { + const mockConnect = jest.fn().mockResolvedValue({}); + MongoClient.prototype.connect = mockConnect; + + const c1 = await getClient(); + const c2 = await getClient(); + + expect(c1).toBe(c2); + expect(mockConnect).toHaveBeenCalledTimes(1); + }); + + it('should retry after a failed connection', async () => { + MongoClient.prototype.connect = jest + .fn() + .mockRejectedValueOnce(new Error('Network Fail')) + .mockResolvedValueOnce({}); + + await expect(getClient()).rejects.toThrow('Network Fail'); + + await expect(getClient()).resolves.toBeDefined(); + }); +}); diff --git a/app/(api)/_utils/mongodb/mongoClient.mjs b/app/(api)/_utils/mongodb/mongoClient.mjs index 4c199b937..e50968c31 100644 --- a/app/(api)/_utils/mongodb/mongoClient.mjs +++ b/app/(api)/_utils/mongodb/mongoClient.mjs @@ -1,6 +1,11 @@ import { MongoClient } from 'mongodb'; const uri = process.env.MONGODB_URI; + +if (!uri) { + throw new Error('Missing MONGODB_URI environment variable.'); +} + let cachedClient = null; let cachedPromise = null; @@ -11,11 +16,26 @@ export async function getClient() { if (!cachedPromise) { const client = new MongoClient(uri); - cachedPromise = client.connect(); + cachedPromise = client + .connect() + .then((connectedClient) => { + cachedClient = connectedClient; + return connectedClient; + }) + .catch((error) => { + cachedPromise = null; + cachedClient = null; + throw error; + }); } - cachedClient = await cachedPromise; - return cachedClient; + return cachedPromise; +} + +// Helper function for testing +export function resetClient() { + cachedClient = null; + cachedPromise = null; } export async function getDatabase() { From 617711f22414873e30d0a7a4042185ade7671698 Mon Sep 17 00:00:00 2001 From: michelleyeoh Date: Fri, 27 Feb 2026 18:27:20 -0800 Subject: [PATCH 3/8] addec lient close --- __tests__/mongoClient.test.ts | 20 +++++++++++++------- app/(api)/_utils/mongodb/mongoClient.mjs | 9 +++++---- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/__tests__/mongoClient.test.ts b/__tests__/mongoClient.test.ts index 847ca5d81..6ddd81748 100644 --- a/__tests__/mongoClient.test.ts +++ b/__tests__/mongoClient.test.ts @@ -1,31 +1,37 @@ +/** @jest-environment node */ import { getClient, resetClient } from '@utils/mongodb/mongoClient.mjs'; import { MongoClient } from 'mongodb'; describe('getClient', () => { beforeEach(() => { resetClient(); - jest.clearAllMocks(); + jest.restoreAllMocks(); }); it('should return the same instance on multiple calls', async () => { - const mockConnect = jest.fn().mockResolvedValue({}); - MongoClient.prototype.connect = mockConnect; + const mockDb = { db: jest.fn() }; + const spy = jest + .spyOn(MongoClient.prototype, 'connect') + .mockResolvedValue(mockDb as any); const c1 = await getClient(); const c2 = await getClient(); expect(c1).toBe(c2); - expect(mockConnect).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledTimes(1); }); it('should retry after a failed connection', async () => { - MongoClient.prototype.connect = jest - .fn() + const spy = jest + .spyOn(MongoClient.prototype, 'connect') .mockRejectedValueOnce(new Error('Network Fail')) - .mockResolvedValueOnce({}); + .mockResolvedValueOnce({ db: jest.fn() } as any); await expect(getClient()).rejects.toThrow('Network Fail'); + resetClient(); + await expect(getClient()).resolves.toBeDefined(); + expect(spy).toHaveBeenCalledTimes(2); }); }); diff --git a/app/(api)/_utils/mongodb/mongoClient.mjs b/app/(api)/_utils/mongodb/mongoClient.mjs index e50968c31..a4e705749 100644 --- a/app/(api)/_utils/mongodb/mongoClient.mjs +++ b/app/(api)/_utils/mongodb/mongoClient.mjs @@ -2,14 +2,14 @@ import { MongoClient } from 'mongodb'; const uri = process.env.MONGODB_URI; -if (!uri) { - throw new Error('Missing MONGODB_URI environment variable.'); -} - let cachedClient = null; let cachedPromise = null; export async function getClient() { + if (!uri) { + throw new Error('Missing MONGODB_URI environment variable.'); + } + if (cachedClient) { return cachedClient; } @@ -23,6 +23,7 @@ export async function getClient() { return connectedClient; }) .catch((error) => { + client.close().catch(() => {}); cachedPromise = null; cachedClient = null; throw error; From 1a3ca772c00323a660c7ac1356440cfa86cd2a55 Mon Sep 17 00:00:00 2001 From: michelleyeoh Date: Fri, 27 Feb 2026 21:10:19 -0800 Subject: [PATCH 4/8] moved uri --- __tests__/mongoClient.test.ts | 8 ++++++++ app/(api)/_utils/mongodb/mongoClient.mjs | 3 +-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/__tests__/mongoClient.test.ts b/__tests__/mongoClient.test.ts index 6ddd81748..5a4e12b8c 100644 --- a/__tests__/mongoClient.test.ts +++ b/__tests__/mongoClient.test.ts @@ -8,6 +8,14 @@ describe('getClient', () => { jest.restoreAllMocks(); }); + beforeAll(() => { + process.env.MONGODB_URI = 'mongodb://localhost:27017/test'; + }); + + afterAll(() => { + delete process.env.MONGODB_URI; + }); + it('should return the same instance on multiple calls', async () => { const mockDb = { db: jest.fn() }; const spy = jest diff --git a/app/(api)/_utils/mongodb/mongoClient.mjs b/app/(api)/_utils/mongodb/mongoClient.mjs index a4e705749..0bb4852fa 100644 --- a/app/(api)/_utils/mongodb/mongoClient.mjs +++ b/app/(api)/_utils/mongodb/mongoClient.mjs @@ -1,11 +1,10 @@ import { MongoClient } from 'mongodb'; -const uri = process.env.MONGODB_URI; - let cachedClient = null; let cachedPromise = null; export async function getClient() { + const uri = process.env.MONGODB_URI; if (!uri) { throw new Error('Missing MONGODB_URI environment variable.'); } From 4a0af12e4658902b351440d6dd71905028ead0aa Mon Sep 17 00:00:00 2001 From: michelleyeoh Date: Fri, 27 Feb 2026 21:21:38 -0800 Subject: [PATCH 5/8] added more tests --- __tests__/mongoClient.test.ts | 25 ++++++++++++++++++++++-- app/(api)/_utils/mongodb/mongoClient.mjs | 2 +- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/__tests__/mongoClient.test.ts b/__tests__/mongoClient.test.ts index 5a4e12b8c..b2d1fd295 100644 --- a/__tests__/mongoClient.test.ts +++ b/__tests__/mongoClient.test.ts @@ -16,6 +16,19 @@ describe('getClient', () => { delete process.env.MONGODB_URI; }); + it('should throw an error if MONGODB_URI is missing', async () => { + const originalUri = process.env.MONGODB_URI; + delete process.env.MONGODB_URI; + + resetClient(); + + await expect(getClient()).rejects.toThrow( + 'Missing MONGODB_URI environment variable.' + ); + + process.env.MONGODB_URI = originalUri; + }); + it('should return the same instance on multiple calls', async () => { const mockDb = { db: jest.fn() }; const spy = jest @@ -29,6 +42,16 @@ describe('getClient', () => { expect(spy).toHaveBeenCalledTimes(1); }); + it('should dedupe concurrent callers using cachedPromise', async () => { + const mockDb = { db: jest.fn() }; + const spy = jest + .spyOn(MongoClient.prototype, 'connect') + .mockResolvedValue(mockDb as any); + const [c1, c2] = await Promise.all([getClient(), getClient()]); + expect(c1).toBe(c2); + expect(spy).toHaveBeenCalledTimes(1); + }); + it('should retry after a failed connection', async () => { const spy = jest .spyOn(MongoClient.prototype, 'connect') @@ -37,8 +60,6 @@ describe('getClient', () => { await expect(getClient()).rejects.toThrow('Network Fail'); - resetClient(); - await expect(getClient()).resolves.toBeDefined(); expect(spy).toHaveBeenCalledTimes(2); }); diff --git a/app/(api)/_utils/mongodb/mongoClient.mjs b/app/(api)/_utils/mongodb/mongoClient.mjs index 0bb4852fa..615b4bca5 100644 --- a/app/(api)/_utils/mongodb/mongoClient.mjs +++ b/app/(api)/_utils/mongodb/mongoClient.mjs @@ -33,7 +33,7 @@ export async function getClient() { } // Helper function for testing -export function resetClient() { +export async function resetClient() { cachedClient = null; cachedPromise = null; } From 80350feb598a603e574e74141042c03cedec437c Mon Sep 17 00:00:00 2001 From: michelleyeoh Date: Fri, 27 Feb 2026 21:34:42 -0800 Subject: [PATCH 6/8] removed mock uri --- __tests__/mongoClient.test.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/__tests__/mongoClient.test.ts b/__tests__/mongoClient.test.ts index b2d1fd295..c6231c848 100644 --- a/__tests__/mongoClient.test.ts +++ b/__tests__/mongoClient.test.ts @@ -8,14 +8,6 @@ describe('getClient', () => { jest.restoreAllMocks(); }); - beforeAll(() => { - process.env.MONGODB_URI = 'mongodb://localhost:27017/test'; - }); - - afterAll(() => { - delete process.env.MONGODB_URI; - }); - it('should throw an error if MONGODB_URI is missing', async () => { const originalUri = process.env.MONGODB_URI; delete process.env.MONGODB_URI; From b7185b4ed892c12043574e2d4ccde829da281940 Mon Sep 17 00:00:00 2001 From: michelleyeoh Date: Fri, 27 Feb 2026 21:45:00 -0800 Subject: [PATCH 7/8] fixed async resetClient --- __tests__/mongoClient.test.ts | 6 +++--- app/(api)/_utils/mongodb/mongoClient.mjs | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/__tests__/mongoClient.test.ts b/__tests__/mongoClient.test.ts index c6231c848..67b49bd6d 100644 --- a/__tests__/mongoClient.test.ts +++ b/__tests__/mongoClient.test.ts @@ -3,8 +3,8 @@ import { getClient, resetClient } from '@utils/mongodb/mongoClient.mjs'; import { MongoClient } from 'mongodb'; describe('getClient', () => { - beforeEach(() => { - resetClient(); + beforeEach(async () => { + await resetClient(); jest.restoreAllMocks(); }); @@ -12,7 +12,7 @@ describe('getClient', () => { const originalUri = process.env.MONGODB_URI; delete process.env.MONGODB_URI; - resetClient(); + await resetClient(); await expect(getClient()).rejects.toThrow( 'Missing MONGODB_URI environment variable.' diff --git a/app/(api)/_utils/mongodb/mongoClient.mjs b/app/(api)/_utils/mongodb/mongoClient.mjs index 615b4bca5..6a4d5fa6e 100644 --- a/app/(api)/_utils/mongodb/mongoClient.mjs +++ b/app/(api)/_utils/mongodb/mongoClient.mjs @@ -34,6 +34,9 @@ export async function getClient() { // Helper function for testing export async function resetClient() { + if (cachedClient) { + await cachedClient.close().catch(() => {}); + } cachedClient = null; cachedPromise = null; } From 81a98ee7ffc4f4b52cfe32f6333f271c4e129cd1 Mon Sep 17 00:00:00 2001 From: michelleyeoh Date: Fri, 27 Feb 2026 21:49:23 -0800 Subject: [PATCH 8/8] reset resetClient --- app/(api)/_utils/mongodb/mongoClient.mjs | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/(api)/_utils/mongodb/mongoClient.mjs b/app/(api)/_utils/mongodb/mongoClient.mjs index 6a4d5fa6e..615b4bca5 100644 --- a/app/(api)/_utils/mongodb/mongoClient.mjs +++ b/app/(api)/_utils/mongodb/mongoClient.mjs @@ -34,9 +34,6 @@ export async function getClient() { // Helper function for testing export async function resetClient() { - if (cachedClient) { - await cachedClient.close().catch(() => {}); - } cachedClient = null; cachedPromise = null; }