Skip to content

Commit ac353de

Browse files
authored
Merge pull request finos#1356 from jescalada/improve-test-coverage
test: improve test coverage
2 parents 3c43652 + 698e39f commit ac353de

14 files changed

Lines changed: 2061 additions & 20 deletions

src/service/routes/auth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ router.get('/profile', async (req: Request, res: Response) => {
144144

145145
const userVal = await db.findUser((req.user as User).username);
146146
if (!userVal) {
147-
res.status(404).send('User not found').end();
147+
res.status(404).send({ message: 'User not found' }).end();
148148
return;
149149
}
150150

test/db/mongo/helper.test.ts

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest';
2+
import { MongoClient } from 'mongodb';
3+
4+
const mockCollection = {
5+
find: vi.fn(),
6+
findOne: vi.fn(),
7+
};
8+
9+
const mockDb = {
10+
collection: vi.fn(() => mockCollection),
11+
};
12+
13+
const mockClient = {
14+
connect: vi.fn().mockResolvedValue(undefined),
15+
db: vi.fn(() => mockDb),
16+
};
17+
18+
const mockToArray = vi.fn();
19+
20+
vi.mock('mongodb', async () => {
21+
const actual = await vi.importActual('mongodb');
22+
return {
23+
...actual,
24+
MongoClient: vi.fn(() => mockClient),
25+
};
26+
});
27+
28+
const mockGetDatabase = vi.fn();
29+
30+
vi.mock('../../../src/config', () => ({
31+
getDatabase: mockGetDatabase,
32+
}));
33+
34+
const mockFromNodeProviderChain = vi.fn();
35+
36+
vi.mock('@aws-sdk/credential-providers', () => ({
37+
fromNodeProviderChain: mockFromNodeProviderChain,
38+
}));
39+
40+
const mockMongoDBStore = vi.fn();
41+
42+
vi.mock('connect-mongo', () => ({
43+
default: mockMongoDBStore,
44+
}));
45+
46+
describe('MongoDB Helper', () => {
47+
beforeEach(() => {
48+
vi.clearAllMocks();
49+
mockCollection.find.mockReturnValue({ toArray: mockToArray });
50+
51+
// Clear cached db
52+
vi.resetModules();
53+
});
54+
55+
afterEach(() => {
56+
vi.restoreAllMocks();
57+
});
58+
59+
describe('connect', () => {
60+
it('should connect to MongoDB and return collection', async () => {
61+
mockGetDatabase.mockReturnValue({
62+
connectionString: 'mongodb://localhost:27017/testdb',
63+
options: {},
64+
});
65+
66+
const { connect } = await import('../../../src/db/mongo/helper');
67+
68+
const result = await connect('testCollection');
69+
70+
expect(MongoClient).toHaveBeenCalledWith('mongodb://localhost:27017/testdb', {});
71+
expect(mockClient.connect).toHaveBeenCalledTimes(1);
72+
expect(mockClient.db).toHaveBeenCalledTimes(1);
73+
expect(mockDb.collection).toHaveBeenCalledWith('testCollection');
74+
expect(result).toBe(mockCollection);
75+
});
76+
77+
it('should reuse existing connection', async () => {
78+
mockGetDatabase.mockReturnValue({
79+
connectionString: 'mongodb://localhost:27017/testdb',
80+
options: {},
81+
});
82+
83+
const { connect } = await import('../../../src/db/mongo/helper');
84+
85+
const result = await connect('collection1');
86+
87+
expect(MongoClient).toHaveBeenCalledWith('mongodb://localhost:27017/testdb', {});
88+
expect(mockClient.connect).toHaveBeenCalledTimes(1);
89+
expect(mockClient.db).toHaveBeenCalledTimes(1);
90+
expect(mockDb.collection).toHaveBeenCalledWith('collection1');
91+
expect(result).toBe(mockCollection);
92+
93+
// Accessing a different collection should reuse the existing db connection
94+
await connect('collection2');
95+
expect(MongoClient).toHaveBeenCalledTimes(1);
96+
expect(mockClient.connect).toHaveBeenCalledTimes(1);
97+
expect(mockClient.db).toHaveBeenCalledTimes(1);
98+
expect(mockDb.collection).toHaveBeenCalledWith('collection2');
99+
});
100+
101+
it('should throw error when connection string is not provided', async () => {
102+
mockGetDatabase.mockReturnValue({
103+
connectionString: '',
104+
options: {},
105+
});
106+
107+
const { connect } = await import('../../../src/db/mongo/helper');
108+
109+
await expect(connect('testCollection')).rejects.toThrow(
110+
'MongoDB connection string is not provided',
111+
);
112+
});
113+
114+
it('should throw error when connection string is undefined', async () => {
115+
mockGetDatabase.mockReturnValue({
116+
connectionString: undefined,
117+
options: {},
118+
});
119+
120+
const { connect } = await import('../../../src/db/mongo/helper');
121+
122+
await expect(connect('testCollection')).rejects.toThrow(
123+
'MongoDB connection string is not provided',
124+
);
125+
});
126+
127+
it('should handle AWS credential provider', async () => {
128+
const mockCredentialProvider = vi.fn();
129+
mockFromNodeProviderChain.mockReturnValue(mockCredentialProvider);
130+
131+
mockGetDatabase.mockReturnValue({
132+
connectionString: 'mongodb://localhost:27017/testdb',
133+
options: {
134+
authMechanismProperties: {
135+
AWS_CREDENTIAL_PROVIDER: 'placeholder',
136+
},
137+
},
138+
});
139+
140+
const { connect } = await import('../../../src/db/mongo/helper');
141+
142+
await connect('testCollection');
143+
144+
expect(mockFromNodeProviderChain).toHaveBeenCalled();
145+
expect(MongoClient).toHaveBeenCalledWith(
146+
'mongodb://localhost:27017/testdb',
147+
expect.objectContaining({
148+
authMechanismProperties: {
149+
AWS_CREDENTIAL_PROVIDER: mockCredentialProvider,
150+
},
151+
}),
152+
);
153+
});
154+
155+
it('should pass options to MongoClient', async () => {
156+
const options = {
157+
maxPoolSize: 10,
158+
minPoolSize: 5,
159+
serverSelectionTimeoutMS: 5000,
160+
};
161+
162+
mockGetDatabase.mockReturnValue({
163+
connectionString: 'mongodb://localhost:27017/testdb',
164+
options,
165+
});
166+
167+
const { connect } = await import('../../../src/db/mongo/helper');
168+
169+
await connect('testCollection');
170+
171+
expect(MongoClient).toHaveBeenCalledWith('mongodb://localhost:27017/testdb', options);
172+
});
173+
});
174+
175+
describe('findDocuments', () => {
176+
beforeEach(async () => {
177+
mockGetDatabase.mockReturnValue({
178+
connectionString: 'mongodb://localhost:27017/testdb',
179+
options: {},
180+
});
181+
});
182+
183+
it('should find documents with default filter and options', async () => {
184+
const mockDocs = [
185+
{ id: 1, name: 'test1' },
186+
{ id: 2, name: 'test2' },
187+
];
188+
mockToArray.mockResolvedValue(mockDocs);
189+
190+
const { findDocuments } = await import('../../../src/db/mongo/helper');
191+
192+
const result = await findDocuments('testCollection');
193+
194+
expect(mockDb.collection).toHaveBeenCalledWith('testCollection');
195+
expect(mockCollection.find).toHaveBeenCalledWith({}, {});
196+
expect(mockToArray).toHaveBeenCalled();
197+
expect(result).toEqual(mockDocs);
198+
});
199+
200+
it('should find documents with custom filter', async () => {
201+
const mockDocs = [{ id: 1, name: 'test1' }];
202+
mockToArray.mockResolvedValue(mockDocs);
203+
204+
const { findDocuments } = await import('../../../src/db/mongo/helper');
205+
206+
const filter = { name: 'test1' };
207+
const result = await findDocuments('testCollection', filter);
208+
209+
expect(mockCollection.find).toHaveBeenCalledWith(filter, {});
210+
expect(result).toEqual(mockDocs);
211+
});
212+
213+
it('should find documents with custom options', async () => {
214+
const mockDocs = [{ id: 1, name: 'test1' }];
215+
mockToArray.mockResolvedValue(mockDocs);
216+
217+
const { findDocuments } = await import('../../../src/db/mongo/helper');
218+
219+
const filter = { name: 'test1' };
220+
const options = { projection: { _id: 0, name: 1 }, limit: 10 };
221+
const result = await findDocuments('testCollection', filter, options);
222+
223+
expect(mockCollection.find).toHaveBeenCalledWith(filter, options);
224+
expect(result).toEqual(mockDocs);
225+
});
226+
227+
it('should return empty array when no documents found', async () => {
228+
mockToArray.mockResolvedValue([]);
229+
230+
const { findDocuments } = await import('../../../src/db/mongo/helper');
231+
232+
const result = await findDocuments('testCollection');
233+
234+
expect(result).toEqual([]);
235+
});
236+
});
237+
238+
describe('findOneDocument', () => {
239+
beforeEach(async () => {
240+
mockGetDatabase.mockReturnValue({
241+
connectionString: 'mongodb://localhost:27017/testdb',
242+
options: {},
243+
});
244+
});
245+
246+
it('should find one document with default filter and options', async () => {
247+
const mockDoc = { id: 1, name: 'test1' };
248+
mockCollection.findOne.mockResolvedValue(mockDoc);
249+
250+
const { findOneDocument } = await import('../../../src/db/mongo/helper');
251+
252+
const result = await findOneDocument('testCollection');
253+
254+
expect(mockDb.collection).toHaveBeenCalledWith('testCollection');
255+
expect(mockCollection.findOne).toHaveBeenCalledWith({}, {});
256+
expect(result).toEqual(mockDoc);
257+
});
258+
259+
it('should find one document with custom filter', async () => {
260+
const mockDoc = { id: 1, name: 'test1' };
261+
mockCollection.findOne.mockResolvedValue(mockDoc);
262+
263+
const { findOneDocument } = await import('../../../src/db/mongo/helper');
264+
265+
const filter = { id: 1 };
266+
const result = await findOneDocument('testCollection', filter);
267+
268+
expect(mockCollection.findOne).toHaveBeenCalledWith(filter, {});
269+
expect(result).toEqual(mockDoc);
270+
});
271+
272+
it('should find one document with custom options', async () => {
273+
const mockDoc = { id: 1, name: 'test1' };
274+
mockCollection.findOne.mockResolvedValue(mockDoc);
275+
276+
const { findOneDocument } = await import('../../../src/db/mongo/helper');
277+
278+
const filter = { id: 1 };
279+
const options = { projection: { _id: 0, name: 1 } };
280+
const result = await findOneDocument('testCollection', filter, options);
281+
282+
expect(mockCollection.findOne).toHaveBeenCalledWith(filter, options);
283+
expect(result).toEqual(mockDoc);
284+
});
285+
286+
it('should return null when document not found', async () => {
287+
mockCollection.findOne.mockResolvedValue(null);
288+
289+
const { findOneDocument } = await import('../../../src/db/mongo/helper');
290+
291+
const result = await findOneDocument('testCollection', { id: 999 });
292+
293+
expect(result).toBeNull();
294+
});
295+
});
296+
297+
describe('getSessionStore', () => {
298+
it('should create MongoDBStore with connection string and options', async () => {
299+
const connectionString = 'mongodb://localhost:27017/testdb';
300+
const options = { maxPoolSize: 10 };
301+
302+
mockGetDatabase.mockReturnValue({
303+
connectionString,
304+
options,
305+
});
306+
307+
const { getSessionStore } = await import('../../../src/db/mongo/helper');
308+
309+
const result = getSessionStore();
310+
311+
expect(result).toBeDefined();
312+
expect(mockMongoDBStore).toHaveBeenCalledWith({
313+
mongoUrl: connectionString,
314+
collectionName: 'user_session',
315+
mongoOptions: options,
316+
});
317+
});
318+
});
319+
});

0 commit comments

Comments
 (0)