@smartsoft001/mongo 1.1.91 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/.eslintrc +13 -0
  2. package/jest.config.ts +27 -0
  3. package/package.json +2 -26
  4. package/project.json +48 -0
  5. package/src/index.ts +3 -0
  6. package/src/lib/mongo.config.ts +10 -0
  7. package/src/lib/mongo.module.ts +28 -0
  8. package/src/lib/mongo.unitofwork.spec.ts +84 -0
  9. package/src/lib/mongo.unitofwork.ts +58 -0
  10. package/src/lib/mongo.utils.spec.ts +54 -0
  11. package/src/lib/mongo.utils.ts +17 -0
  12. package/src/lib/repositories/attachment.repository.spec.ts +249 -0
  13. package/src/lib/repositories/attachment.repository.ts +113 -0
  14. package/src/lib/repositories/interfaces.ts +28 -0
  15. package/src/lib/repositories/item.repository.spec.ts +1269 -0
  16. package/src/lib/repositories/item.repository.ts +582 -0
  17. package/tsconfig.json +13 -0
  18. package/tsconfig.lib.json +11 -0
  19. package/tsconfig.spec.json +20 -0
  20. package/src/index.d.ts +0 -3
  21. package/src/index.js +0 -7
  22. package/src/index.js.map +0 -1
  23. package/src/lib/mongo.config.d.ts +0 -9
  24. package/src/lib/mongo.config.js +0 -7
  25. package/src/lib/mongo.config.js.map +0 -1
  26. package/src/lib/mongo.module.d.ts +0 -5
  27. package/src/lib/mongo.module.js +0 -25
  28. package/src/lib/mongo.module.js.map +0 -1
  29. package/src/lib/mongo.unitofwork.d.ts +0 -12
  30. package/src/lib/mongo.unitofwork.js +0 -56
  31. package/src/lib/mongo.unitofwork.js.map +0 -1
  32. package/src/lib/mongo.utils.d.ts +0 -2
  33. package/src/lib/mongo.utils.js +0 -13
  34. package/src/lib/mongo.utils.js.map +0 -1
  35. package/src/lib/repositories/attachment.repository.d.ts +0 -28
  36. package/src/lib/repositories/attachment.repository.js +0 -88
  37. package/src/lib/repositories/attachment.repository.js.map +0 -1
  38. package/src/lib/repositories/item.repository.d.ts +0 -51
  39. package/src/lib/repositories/item.repository.js +0 -411
  40. package/src/lib/repositories/item.repository.js.map +0 -1
  41. package/test-setup.js +0 -1
  42. package/test-setup.js.map +0 -1
  43. /package/{test-setup.d.ts → test-setup.ts} +0 -0
@@ -0,0 +1,1269 @@
1
+ import { IItemRepositoryOptions } from '@smartsoft001/domain-core';
2
+ import { MongoConfig, MongoItemRepository } from '@smartsoft001/mongo';
3
+ import { IUser } from '@smartsoft001/users';
4
+
5
+ import { IMongoTransaction } from '../mongo.unitofwork';
6
+
7
+ describe('shared-mongo: MongoItemRepository create function', () => {
8
+ let repository: MongoItemRepository<any>;
9
+ let mockCollection: any;
10
+ let mockLogChange: jest.Mock;
11
+
12
+ beforeEach(() => {
13
+ mockCollection = {
14
+ insertOne: jest.fn(),
15
+ };
16
+
17
+ mockLogChange = jest.fn().mockResolvedValue(undefined);
18
+
19
+ repository = new MongoItemRepository(new MongoConfig());
20
+ jest
21
+ .spyOn(repository as any, 'collectionContext')
22
+ .mockImplementation((callback: any) => {
23
+ return callback(mockCollection);
24
+ });
25
+ jest
26
+ .spyOn(repository as any, 'logChange')
27
+ .mockImplementation(mockLogChange);
28
+ jest
29
+ .spyOn(repository as any, 'getModelToCreate')
30
+ .mockImplementation((item: Record<string, any>, user: IUser) => ({
31
+ ...item,
32
+ _id: item.id,
33
+ __info: { create: { username: user.username, date: new Date() } },
34
+ }));
35
+ });
36
+
37
+ it('should call insertOne with the transformed item', async () => {
38
+ const item = { id: '123', name: 'test item' };
39
+ const user = { username: 'testuser', permissions: [''] };
40
+
41
+ await repository.create(item, user);
42
+
43
+ expect(mockCollection.insertOne).toHaveBeenCalledWith(
44
+ {
45
+ id: '123',
46
+ name: 'test item',
47
+ _id: '123',
48
+ __info: {
49
+ create: {
50
+ username: 'testuser',
51
+ date: expect.any(Date),
52
+ },
53
+ },
54
+ },
55
+ { session: undefined },
56
+ );
57
+ });
58
+
59
+ it('should call logChange with correct parameters on success', async () => {
60
+ const item = { id: '123', name: 'test item' };
61
+ const user = { username: 'testuser', permissions: [''] };
62
+ const repoOptions = {
63
+ transaction: { session: 'mockSession', connection: 'mockConnection' },
64
+ };
65
+
66
+ await repository.create(item, user, repoOptions);
67
+
68
+ expect(mockLogChange).toHaveBeenCalledWith(
69
+ 'create',
70
+ item,
71
+ repoOptions,
72
+ user,
73
+ null,
74
+ );
75
+ });
76
+
77
+ it('should call logChange with error on failure and rethrow the error', async () => {
78
+ const item = { id: '123', name: 'test item' };
79
+ const user = { username: 'testuser', permissions: [''] };
80
+ const error = new Error('Insert failed');
81
+ mockCollection.insertOne.mockRejectedValue(error);
82
+
83
+ await expect(repository.create(item, user)).rejects.toThrow(error);
84
+
85
+ expect(mockLogChange).toHaveBeenCalledWith(
86
+ 'create',
87
+ item,
88
+ undefined,
89
+ user,
90
+ error,
91
+ );
92
+ });
93
+ });
94
+
95
+ describe('shared-mongo: MongoItemRepository clear function', () => {
96
+ let repository: MongoItemRepository<any>;
97
+ let mockCollection: any;
98
+ let mockLogChange: jest.Mock;
99
+
100
+ beforeEach(() => {
101
+ mockCollection = {
102
+ deleteMany: jest.fn(),
103
+ };
104
+
105
+ mockLogChange = jest.fn().mockResolvedValue(undefined);
106
+
107
+ repository = new MongoItemRepository<any>(null as any);
108
+
109
+ // Mock collectionContext
110
+ jest
111
+ .spyOn(repository as any, 'collectionContext')
112
+ .mockImplementation(async (callback: any) => {
113
+ return callback(mockCollection);
114
+ });
115
+
116
+ // Mock logChange
117
+ jest
118
+ .spyOn(repository as any, 'logChange')
119
+ .mockImplementation(mockLogChange);
120
+ });
121
+
122
+ it('should call deleteMany and logChange on success', async () => {
123
+ const mockUser: IUser = { username: 'testUser', permissions: [''] }; // Replace with actual IUser fields
124
+ const mockRepoOptions: IItemRepositoryOptions = {
125
+ transaction: { session: {} } as IMongoTransaction,
126
+ };
127
+
128
+ mockCollection.deleteMany.mockResolvedValueOnce({});
129
+
130
+ await repository.clear(mockUser, mockRepoOptions);
131
+
132
+ expect(mockCollection.deleteMany).toHaveBeenCalledWith(
133
+ {},
134
+ { session: (mockRepoOptions.transaction as IMongoTransaction).session },
135
+ );
136
+ expect(mockLogChange).toHaveBeenCalledWith(
137
+ 'clear',
138
+ null,
139
+ mockRepoOptions,
140
+ mockUser,
141
+ null,
142
+ );
143
+ });
144
+
145
+ it('should call logChange with error if deleteMany fails', async () => {
146
+ const mockUser: IUser = { username: 'testUser', permissions: [''] };
147
+ const mockRepoOptions: IItemRepositoryOptions = {
148
+ transaction: { session: {} } as IMongoTransaction,
149
+ };
150
+ const mockError = new Error('DeleteMany failed');
151
+
152
+ mockCollection.deleteMany.mockRejectedValueOnce(mockError);
153
+
154
+ await expect(repository.clear(mockUser, mockRepoOptions)).rejects.toThrow(
155
+ mockError,
156
+ );
157
+
158
+ expect(mockCollection.deleteMany).toHaveBeenCalledWith(
159
+ {},
160
+ { session: (mockRepoOptions.transaction as IMongoTransaction).session },
161
+ );
162
+ expect(mockLogChange).toHaveBeenCalledWith(
163
+ 'clear',
164
+ null,
165
+ mockRepoOptions,
166
+ mockUser,
167
+ mockError,
168
+ );
169
+ });
170
+
171
+ it('should work without repoOptions', async () => {
172
+ const mockUser: IUser = { username: 'testUser', permissions: [''] };
173
+
174
+ mockCollection.deleteMany.mockResolvedValueOnce({});
175
+
176
+ await repository.clear(mockUser);
177
+
178
+ expect(mockCollection.deleteMany).toHaveBeenCalledWith(
179
+ {},
180
+ { session: undefined },
181
+ );
182
+ expect(mockLogChange).toHaveBeenCalledWith(
183
+ 'clear',
184
+ null,
185
+ undefined,
186
+ mockUser,
187
+ null,
188
+ );
189
+ });
190
+ });
191
+
192
+ describe('shared-mongo: MongoItemRepository createMany function', () => {
193
+ let repository: MongoItemRepository<any>;
194
+ let mockCollection: any;
195
+ let mockLogChange: jest.Mock;
196
+ let mockGetModelToCreate: jest.Mock;
197
+
198
+ beforeEach(() => {
199
+ mockCollection = {
200
+ insertMany: jest.fn(),
201
+ };
202
+
203
+ mockLogChange = jest.fn().mockResolvedValue(undefined);
204
+ mockGetModelToCreate = jest.fn((item, user) => ({
205
+ ...item,
206
+ createdBy: user.username,
207
+ }));
208
+
209
+ repository = new MongoItemRepository<any>(null as any);
210
+
211
+ // Mock collectionContext
212
+ jest
213
+ .spyOn(repository as any, 'collectionContext')
214
+ .mockImplementation(async (callback: any) => {
215
+ return callback(mockCollection);
216
+ });
217
+
218
+ // Mock logChange
219
+ jest
220
+ .spyOn(repository as any, 'logChange')
221
+ .mockImplementation(mockLogChange);
222
+
223
+ // Mock getModelToCreate
224
+ jest
225
+ .spyOn(repository as any, 'getModelToCreate')
226
+ .mockImplementation(mockGetModelToCreate);
227
+ });
228
+
229
+ it('should call insertMany and logChange on success', async () => {
230
+ const mockUser: IUser = { username: 'testUser', permissions: [''] };
231
+ const mockRepoOptions: IItemRepositoryOptions = {
232
+ transaction: { session: {} } as IMongoTransaction,
233
+ };
234
+ const mockList = [{ id: 1 }, { id: 2 }];
235
+
236
+ mockCollection.insertMany.mockResolvedValueOnce({});
237
+
238
+ await repository.createMany(mockList, mockUser, mockRepoOptions);
239
+
240
+ expect(mockCollection.insertMany).toHaveBeenCalledWith(
241
+ mockList.map((item) => ({ ...item, createdBy: mockUser.username })),
242
+ { session: (mockRepoOptions.transaction as IMongoTransaction).session },
243
+ );
244
+ expect(mockLogChange).toHaveBeenCalledWith(
245
+ 'createMany',
246
+ null,
247
+ mockRepoOptions,
248
+ mockUser,
249
+ null,
250
+ );
251
+ });
252
+
253
+ it('should call logChange with error if insertMany fails', async () => {
254
+ const mockUser: IUser = { username: 'testUser', permissions: [''] };
255
+ const mockRepoOptions: IItemRepositoryOptions = {
256
+ transaction: { session: {} } as IMongoTransaction,
257
+ };
258
+ const mockList = [{ id: 1 }, { id: 2 }];
259
+ const mockError = new Error('InsertMany failed');
260
+
261
+ mockCollection.insertMany.mockRejectedValueOnce(mockError);
262
+
263
+ await expect(
264
+ repository.createMany(mockList, mockUser, mockRepoOptions),
265
+ ).rejects.toThrow(mockError);
266
+
267
+ expect(mockCollection.insertMany).toHaveBeenCalledWith(
268
+ mockList.map((item) => ({ ...item, createdBy: mockUser.username })),
269
+ { session: (mockRepoOptions.transaction as IMongoTransaction).session },
270
+ );
271
+ expect(mockLogChange).toHaveBeenCalledWith(
272
+ 'createMany',
273
+ null,
274
+ mockRepoOptions,
275
+ mockUser,
276
+ mockError,
277
+ );
278
+ });
279
+
280
+ it('should work without repoOptions', async () => {
281
+ const mockUser: IUser = { username: 'testUser', permissions: [''] };
282
+ const mockList = [{ id: 1 }, { id: 2 }];
283
+
284
+ mockCollection.insertMany.mockResolvedValueOnce({});
285
+
286
+ await repository.createMany(mockList, mockUser);
287
+
288
+ expect(mockCollection.insertMany).toHaveBeenCalledWith(
289
+ mockList.map((item) => ({ ...item, createdBy: mockUser.username })),
290
+ { session: undefined },
291
+ );
292
+ expect(mockLogChange).toHaveBeenCalledWith(
293
+ 'createMany',
294
+ null,
295
+ undefined,
296
+ mockUser,
297
+ null,
298
+ );
299
+ });
300
+
301
+ it('should handle an empty list without calling insertMany', async () => {
302
+ const mockUser: IUser = { username: 'testUser', permissions: [''] };
303
+
304
+ await repository.createMany([], mockUser);
305
+
306
+ expect(mockLogChange).toHaveBeenCalledWith(
307
+ 'createMany',
308
+ null,
309
+ undefined,
310
+ mockUser,
311
+ null,
312
+ );
313
+ });
314
+ });
315
+
316
+ describe('shared-mongo: MongoItemRepository update function', () => {
317
+ let repository: MongoItemRepository<any>;
318
+ let mockCollection: any;
319
+ let mockLogChange: jest.Mock;
320
+ let mockGetInfo: jest.Mock;
321
+ let mockGetModelToUpdate: jest.Mock;
322
+
323
+ beforeEach(() => {
324
+ mockCollection = {
325
+ replaceOne: jest.fn(),
326
+ };
327
+
328
+ mockLogChange = jest.fn().mockResolvedValue(undefined);
329
+ mockGetInfo = jest.fn().mockResolvedValue({ existingData: 'value' });
330
+ mockGetModelToUpdate = jest.fn((item, user, info) => ({
331
+ ...item,
332
+ updatedBy: user.username,
333
+ previousInfo: info,
334
+ }));
335
+
336
+ repository = new MongoItemRepository<any>(null as any);
337
+
338
+ // Mock collectionContext
339
+ jest
340
+ .spyOn(repository as any, 'collectionContext')
341
+ .mockImplementation(async (callback: any) => {
342
+ return callback(mockCollection);
343
+ });
344
+
345
+ // Mock logChange
346
+ jest
347
+ .spyOn(repository as any, 'logChange')
348
+ .mockImplementation(mockLogChange);
349
+
350
+ // Mock getInfo
351
+ jest.spyOn(repository as any, 'getInfo').mockImplementation(mockGetInfo);
352
+
353
+ // Mock getModelToUpdate
354
+ jest
355
+ .spyOn(repository as any, 'getModelToUpdate')
356
+ .mockImplementation(mockGetModelToUpdate);
357
+ });
358
+
359
+ it('should call replaceOne and logChange on success', async () => {
360
+ const mockUser: IUser = { username: 'testUser', permissions: [''] };
361
+ const mockRepoOptions: IItemRepositoryOptions = {
362
+ transaction: { session: {} } as IMongoTransaction,
363
+ };
364
+ const mockItem = { id: 'item1', name: 'Updated Item' };
365
+
366
+ mockCollection.replaceOne.mockResolvedValueOnce({});
367
+
368
+ await repository.update(mockItem, mockUser, mockRepoOptions);
369
+
370
+ expect(mockGetInfo).toHaveBeenCalledWith(mockItem.id, mockCollection);
371
+ expect(mockCollection.replaceOne).toHaveBeenCalledWith(
372
+ { _id: mockItem.id },
373
+ {
374
+ ...mockItem,
375
+ updatedBy: mockUser.username,
376
+ previousInfo: { existingData: 'value' },
377
+ },
378
+ { session: (mockRepoOptions.transaction as IMongoTransaction).session },
379
+ );
380
+ expect(mockLogChange).toHaveBeenCalledWith(
381
+ 'update',
382
+ mockItem,
383
+ mockRepoOptions,
384
+ mockUser,
385
+ null,
386
+ );
387
+ });
388
+
389
+ it('should call logChange with error if replaceOne fails', async () => {
390
+ const mockUser: IUser = { username: 'testUser', permissions: [''] };
391
+ const mockRepoOptions: IItemRepositoryOptions = {
392
+ transaction: { session: {} } as IMongoTransaction,
393
+ };
394
+ const mockItem = { id: 'item1', name: 'Updated Item' };
395
+ const mockError = new Error('ReplaceOne failed');
396
+
397
+ mockCollection.replaceOne.mockRejectedValueOnce(mockError);
398
+
399
+ await expect(
400
+ repository.update(mockItem, mockUser, mockRepoOptions),
401
+ ).rejects.toThrow(mockError);
402
+
403
+ expect(mockGetInfo).toHaveBeenCalledWith(mockItem.id, mockCollection);
404
+ expect(mockCollection.replaceOne).toHaveBeenCalledWith(
405
+ { _id: mockItem.id },
406
+ {
407
+ ...mockItem,
408
+ updatedBy: mockUser.username,
409
+ previousInfo: { existingData: 'value' },
410
+ },
411
+ { session: (mockRepoOptions.transaction as IMongoTransaction).session },
412
+ );
413
+ expect(mockLogChange).toHaveBeenCalledWith(
414
+ 'update',
415
+ mockItem,
416
+ mockRepoOptions,
417
+ mockUser,
418
+ mockError,
419
+ );
420
+ });
421
+
422
+ it('should work without repoOptions', async () => {
423
+ const mockUser: IUser = { username: 'testUser', permissions: [''] };
424
+ const mockItem = { id: 'item1', name: 'Updated Item' };
425
+
426
+ mockCollection.replaceOne.mockResolvedValueOnce({});
427
+
428
+ await repository.update(mockItem, mockUser);
429
+
430
+ expect(mockGetInfo).toHaveBeenCalledWith(mockItem.id, mockCollection);
431
+ expect(mockCollection.replaceOne).toHaveBeenCalledWith(
432
+ { _id: mockItem.id },
433
+ {
434
+ ...mockItem,
435
+ updatedBy: mockUser.username,
436
+ previousInfo: { existingData: 'value' },
437
+ },
438
+ { session: undefined },
439
+ );
440
+ expect(mockLogChange).toHaveBeenCalledWith(
441
+ 'update',
442
+ mockItem,
443
+ undefined,
444
+ mockUser,
445
+ null,
446
+ );
447
+ });
448
+
449
+ it('should throw if getInfo fails', async () => {
450
+ const mockUser: IUser = { username: 'testUser', permissions: [''] };
451
+ const mockItem = { id: 'item1', name: 'Updated Item' };
452
+ const mockError = new Error('GetInfo failed');
453
+
454
+ mockGetInfo.mockRejectedValueOnce(mockError);
455
+
456
+ await expect(repository.update(mockItem, mockUser)).rejects.toThrow(
457
+ mockError,
458
+ );
459
+
460
+ expect(mockGetInfo).toHaveBeenCalledWith(mockItem.id, mockCollection);
461
+ expect(mockCollection.replaceOne).not.toHaveBeenCalled();
462
+ expect(mockLogChange).toHaveBeenCalledWith(
463
+ 'update',
464
+ mockItem,
465
+ undefined,
466
+ mockUser,
467
+ mockError,
468
+ );
469
+ });
470
+ });
471
+
472
+ describe('shared-mongo: MongoItemRepository updatePartial function', () => {
473
+ let repository: MongoItemRepository<any>;
474
+ let mockCollection: any;
475
+ let mockLogChange: jest.Mock;
476
+ let mockGetInfo: jest.Mock;
477
+ let mockGetModelToUpdate: jest.Mock;
478
+
479
+ beforeEach(() => {
480
+ mockCollection = {
481
+ updateOne: jest.fn(),
482
+ };
483
+
484
+ mockLogChange = jest.fn().mockResolvedValue(undefined);
485
+ mockGetInfo = jest.fn().mockResolvedValue({ existingData: 'value' });
486
+ mockGetModelToUpdate = jest.fn((item, user, info) => ({
487
+ ...item,
488
+ updatedBy: user.username,
489
+ previousInfo: info,
490
+ }));
491
+
492
+ repository = new MongoItemRepository<any>(null as any);
493
+
494
+ // Mock collectionContext
495
+ jest
496
+ .spyOn(repository as any, 'collectionContext')
497
+ .mockImplementation(async (callback: any) => {
498
+ return callback(mockCollection);
499
+ });
500
+
501
+ // Mock logChange
502
+ jest
503
+ .spyOn(repository as any, 'logChange')
504
+ .mockImplementation(mockLogChange);
505
+
506
+ // Mock getInfo
507
+ jest.spyOn(repository as any, 'getInfo').mockImplementation(mockGetInfo);
508
+
509
+ // Mock getModelToUpdate
510
+ jest
511
+ .spyOn(repository as any, 'getModelToUpdate')
512
+ .mockImplementation(mockGetModelToUpdate);
513
+ });
514
+
515
+ it('should call updateOne and logChange on success', async () => {
516
+ const mockUser: IUser = { username: 'testUser', permissions: [''] };
517
+ const mockRepoOptions: IItemRepositoryOptions = {
518
+ transaction: { session: {} } as IMongoTransaction,
519
+ };
520
+ const mockItem = { id: 'item1', name: 'Partial Update' };
521
+
522
+ mockCollection.updateOne.mockResolvedValueOnce({});
523
+
524
+ await repository.updatePartial(mockItem, mockUser, mockRepoOptions);
525
+
526
+ expect(mockGetInfo).toHaveBeenCalledWith(mockItem.id, mockCollection);
527
+ expect(mockCollection.updateOne).toHaveBeenCalledWith(
528
+ { _id: mockItem.id },
529
+ {
530
+ $set: {
531
+ ...mockItem,
532
+ updatedBy: mockUser.username,
533
+ previousInfo: { existingData: 'value' },
534
+ },
535
+ },
536
+ { session: (mockRepoOptions.transaction as IMongoTransaction).session },
537
+ );
538
+ expect(mockLogChange).toHaveBeenCalledWith(
539
+ 'updatePartial',
540
+ mockItem,
541
+ mockRepoOptions,
542
+ mockUser,
543
+ null,
544
+ );
545
+ });
546
+
547
+ it('should call logChange with error if updateOne fails', async () => {
548
+ const mockUser: IUser = { username: 'testUser', permissions: [''] };
549
+ const mockRepoOptions: IItemRepositoryOptions = {
550
+ transaction: { session: {} } as IMongoTransaction,
551
+ };
552
+ const mockItem = { id: 'item1', name: 'Partial Update' };
553
+ const mockError = new Error('UpdateOne failed');
554
+
555
+ mockCollection.updateOne.mockRejectedValueOnce(mockError);
556
+
557
+ await expect(
558
+ repository.updatePartial(mockItem, mockUser, mockRepoOptions),
559
+ ).rejects.toThrow(mockError);
560
+
561
+ expect(mockGetInfo).toHaveBeenCalledWith(mockItem.id, mockCollection);
562
+ expect(mockCollection.updateOne).toHaveBeenCalledWith(
563
+ { _id: mockItem.id },
564
+ {
565
+ $set: {
566
+ ...mockItem,
567
+ updatedBy: mockUser.username,
568
+ previousInfo: { existingData: 'value' },
569
+ },
570
+ },
571
+ { session: (mockRepoOptions.transaction as IMongoTransaction).session },
572
+ );
573
+ expect(mockLogChange).toHaveBeenCalledWith(
574
+ 'updatePartial',
575
+ mockItem,
576
+ mockRepoOptions,
577
+ mockUser,
578
+ mockError,
579
+ );
580
+ });
581
+
582
+ it('should work without repoOptions', async () => {
583
+ const mockUser: IUser = { username: 'testUser', permissions: [''] };
584
+ const mockItem = { id: 'item1', name: 'Partial Update' };
585
+
586
+ mockCollection.updateOne.mockResolvedValueOnce({});
587
+
588
+ await repository.updatePartial(mockItem, mockUser);
589
+
590
+ expect(mockGetInfo).toHaveBeenCalledWith(mockItem.id, mockCollection);
591
+ expect(mockCollection.updateOne).toHaveBeenCalledWith(
592
+ { _id: mockItem.id },
593
+ {
594
+ $set: {
595
+ ...mockItem,
596
+ updatedBy: mockUser.username,
597
+ previousInfo: { existingData: 'value' },
598
+ },
599
+ },
600
+ { session: undefined },
601
+ );
602
+ expect(mockLogChange).toHaveBeenCalledWith(
603
+ 'updatePartial',
604
+ mockItem,
605
+ undefined,
606
+ mockUser,
607
+ null,
608
+ );
609
+ });
610
+
611
+ it('should throw if getInfo fails', async () => {
612
+ const mockUser: IUser = { username: 'testUser', permissions: [''] };
613
+ const mockItem = { id: 'item1', name: 'Partial Update' };
614
+ const mockError = new Error('GetInfo failed');
615
+
616
+ mockGetInfo.mockRejectedValueOnce(mockError);
617
+
618
+ await expect(repository.updatePartial(mockItem, mockUser)).rejects.toThrow(
619
+ mockError,
620
+ );
621
+
622
+ expect(mockGetInfo).toHaveBeenCalledWith(mockItem.id, mockCollection);
623
+ expect(mockCollection.updateOne).not.toHaveBeenCalled();
624
+ expect(mockLogChange).toHaveBeenCalledWith(
625
+ 'updatePartial',
626
+ mockItem,
627
+ undefined,
628
+ mockUser,
629
+ mockError,
630
+ );
631
+ });
632
+ });
633
+
634
+ describe('shared-mongo: MongoItemRepository updatePartialManyByCriteria function', () => {
635
+ let repository: MongoItemRepository<any>;
636
+ let mockCollection: any;
637
+ let mockLogChange: jest.Mock;
638
+ let mockConvertIdInCriteria: jest.Mock;
639
+
640
+ beforeEach(() => {
641
+ mockCollection = {
642
+ updateMany: jest.fn(),
643
+ };
644
+
645
+ mockLogChange = jest.fn().mockResolvedValue(undefined);
646
+ mockConvertIdInCriteria = jest.fn((criteria) => criteria);
647
+
648
+ repository = new MongoItemRepository<any>(null as any);
649
+
650
+ // Mock collectionContext
651
+ jest
652
+ .spyOn(repository as any, 'collectionContext')
653
+ .mockImplementation(async (callback: any) => {
654
+ return callback(mockCollection);
655
+ });
656
+
657
+ // Mock logChange
658
+ jest
659
+ .spyOn(repository as any, 'logChange')
660
+ .mockImplementation(mockLogChange);
661
+
662
+ // Mock convertIdInCriteria
663
+ jest
664
+ .spyOn(repository as any, 'convertIdInCriteria')
665
+ .mockImplementation(mockConvertIdInCriteria);
666
+ });
667
+
668
+ it('should call updateMany and logChange on success', async () => {
669
+ const mockUser: IUser = { username: 'testUser', permissions: [''] };
670
+ const mockRepoOptions: IItemRepositoryOptions = {
671
+ transaction: { session: {} } as IMongoTransaction,
672
+ };
673
+ const mockCriteria = { status: 'pending' };
674
+ const mockSet = { fieldToUpdate: 'newValue' };
675
+
676
+ mockCollection.updateMany.mockResolvedValueOnce({});
677
+
678
+ await repository.updatePartialManyByCriteria(
679
+ mockCriteria,
680
+ mockSet,
681
+ mockUser,
682
+ mockRepoOptions,
683
+ );
684
+
685
+ expect(mockConvertIdInCriteria).toHaveBeenCalledWith(mockCriteria);
686
+ expect(mockCollection.updateMany).toHaveBeenCalledWith(
687
+ mockCriteria,
688
+ {
689
+ $set: {
690
+ ...mockSet,
691
+ '__info.update': {
692
+ username: mockUser.username,
693
+ date: expect.any(Date),
694
+ },
695
+ },
696
+ },
697
+ { session: (mockRepoOptions.transaction as IMongoTransaction).session },
698
+ );
699
+ expect(mockLogChange).toHaveBeenCalledWith(
700
+ 'updatePartialManyByCriteria',
701
+ { ...mockCriteria, set: mockSet },
702
+ mockRepoOptions,
703
+ mockUser,
704
+ null,
705
+ );
706
+ });
707
+
708
+ it('should call logChange with error if updateMany fails', async () => {
709
+ const mockUser: IUser = { username: 'testUser', permissions: [''] };
710
+ const mockRepoOptions: IItemRepositoryOptions = {
711
+ transaction: { session: {} } as IMongoTransaction,
712
+ };
713
+ const mockCriteria = { status: 'pending' };
714
+ const mockSet = { fieldToUpdate: 'newValue' };
715
+ const mockError = new Error('updateMany failed');
716
+
717
+ mockCollection.updateMany.mockRejectedValueOnce(mockError);
718
+
719
+ await expect(
720
+ repository.updatePartialManyByCriteria(
721
+ mockCriteria,
722
+ mockSet,
723
+ mockUser,
724
+ mockRepoOptions,
725
+ ),
726
+ ).rejects.toThrow(mockError);
727
+
728
+ expect(mockConvertIdInCriteria).toHaveBeenCalledWith(mockCriteria);
729
+ expect(mockCollection.updateMany).toHaveBeenCalledWith(
730
+ mockCriteria,
731
+ {
732
+ $set: {
733
+ ...mockSet,
734
+ '__info.update': {
735
+ username: mockUser.username,
736
+ date: expect.any(Date),
737
+ },
738
+ },
739
+ },
740
+ { session: (mockRepoOptions.transaction as IMongoTransaction).session },
741
+ );
742
+ expect(mockLogChange).toHaveBeenCalledWith(
743
+ 'updatePartialManyByCriteria',
744
+ { ...mockCriteria, set: mockSet },
745
+ mockRepoOptions,
746
+ mockUser,
747
+ mockError,
748
+ );
749
+ });
750
+
751
+ it('should work without repoOptions', async () => {
752
+ const mockUser: IUser = { username: 'testUser', permissions: [''] };
753
+ const mockCriteria = { status: 'pending' };
754
+ const mockSet = { fieldToUpdate: 'newValue' };
755
+
756
+ mockCollection.updateMany.mockResolvedValueOnce({});
757
+
758
+ await repository.updatePartialManyByCriteria(
759
+ mockCriteria,
760
+ mockSet,
761
+ mockUser,
762
+ );
763
+
764
+ expect(mockConvertIdInCriteria).toHaveBeenCalledWith(mockCriteria);
765
+ expect(mockCollection.updateMany).toHaveBeenCalledWith(
766
+ mockCriteria,
767
+ {
768
+ $set: {
769
+ ...mockSet,
770
+ '__info.update': {
771
+ username: mockUser.username,
772
+ date: expect.any(Date),
773
+ },
774
+ },
775
+ },
776
+ { session: undefined },
777
+ );
778
+ expect(mockLogChange).toHaveBeenCalledWith(
779
+ 'updatePartialManyByCriteria',
780
+ { ...mockCriteria, set: mockSet },
781
+ undefined,
782
+ mockUser,
783
+ null,
784
+ );
785
+ });
786
+
787
+ it('should throw if convertIdInCriteria fails', async () => {
788
+ const mockUser: IUser = { username: 'testUser', permissions: [''] };
789
+ const mockCriteria = { status: 'pending' };
790
+ const mockSet = { fieldToUpdate: 'newValue' };
791
+ const mockError = new Error('convertIdInCriteria failed');
792
+
793
+ mockConvertIdInCriteria.mockImplementationOnce(() => {
794
+ throw mockError;
795
+ });
796
+
797
+ await expect(
798
+ repository.updatePartialManyByCriteria(mockCriteria, mockSet, mockUser),
799
+ ).rejects.toThrow(mockError);
800
+
801
+ expect(mockConvertIdInCriteria).toHaveBeenCalledWith(mockCriteria);
802
+ expect(mockCollection.updateMany).not.toHaveBeenCalled();
803
+ expect(mockLogChange).toHaveBeenCalledWith(
804
+ 'updatePartialManyByCriteria',
805
+ { ...mockCriteria, set: mockSet },
806
+ undefined,
807
+ mockUser,
808
+ mockError,
809
+ );
810
+ });
811
+ });
812
+
813
+ describe('shared-mongo: MongoItemRepository delete function', () => {
814
+ let repository: MongoItemRepository<any>;
815
+ let mockCollection: any;
816
+ let mockLogChange: jest.Mock;
817
+
818
+ beforeEach(() => {
819
+ mockCollection = {
820
+ deleteOne: jest.fn(),
821
+ };
822
+
823
+ mockLogChange = jest.fn().mockResolvedValue(undefined);
824
+
825
+ repository = new MongoItemRepository<any>(null as any);
826
+
827
+ jest
828
+ .spyOn(repository as any, 'collectionContext')
829
+ .mockImplementation(async (callback: any) => {
830
+ return callback(mockCollection);
831
+ });
832
+
833
+ jest
834
+ .spyOn(repository as any, 'logChange')
835
+ .mockImplementation(mockLogChange);
836
+ });
837
+
838
+ it('should call deleteOne and logChange on success', async () => {
839
+ const mockUser: IUser = { username: 'testUser', permissions: [''] };
840
+ const mockRepoOptions: IItemRepositoryOptions = {
841
+ transaction: { session: {} } as IMongoTransaction,
842
+ };
843
+ const mockId = '123';
844
+
845
+ mockCollection.deleteOne.mockResolvedValueOnce({});
846
+
847
+ await repository.delete(mockId, mockUser, mockRepoOptions);
848
+
849
+ expect(mockCollection.deleteOne).toHaveBeenCalledWith(
850
+ { _id: mockId },
851
+ { session: (mockRepoOptions.transaction as IMongoTransaction).session },
852
+ );
853
+ expect(mockLogChange).toHaveBeenCalledWith(
854
+ 'delete',
855
+ { id: mockId },
856
+ mockRepoOptions,
857
+ mockUser,
858
+ null,
859
+ );
860
+ });
861
+
862
+ it('should call logChange with error if deleteOne fails', async () => {
863
+ const mockUser: IUser = { username: 'testUser', permissions: [''] };
864
+ const mockRepoOptions: IItemRepositoryOptions = {
865
+ transaction: { session: {} } as IMongoTransaction,
866
+ };
867
+ const mockId = '123';
868
+ const mockError = new Error('deleteOne failed');
869
+
870
+ mockCollection.deleteOne.mockRejectedValueOnce(mockError);
871
+
872
+ await expect(
873
+ repository.delete(mockId, mockUser, mockRepoOptions),
874
+ ).rejects.toThrow(mockError);
875
+
876
+ expect(mockCollection.deleteOne).toHaveBeenCalledWith(
877
+ { _id: mockId },
878
+ { session: (mockRepoOptions.transaction as IMongoTransaction).session },
879
+ );
880
+ expect(mockLogChange).toHaveBeenCalledWith(
881
+ 'delete',
882
+ { id: mockId },
883
+ mockRepoOptions,
884
+ mockUser,
885
+ mockError,
886
+ );
887
+ });
888
+
889
+ it('should work without repoOptions', async () => {
890
+ const mockUser: IUser = { username: 'testUser', permissions: [''] };
891
+ const mockId = '123';
892
+
893
+ mockCollection.deleteOne.mockResolvedValueOnce({});
894
+
895
+ await repository.delete(mockId, mockUser);
896
+
897
+ expect(mockCollection.deleteOne).toHaveBeenCalledWith(
898
+ { _id: mockId },
899
+ { session: undefined },
900
+ );
901
+ expect(mockLogChange).toHaveBeenCalledWith(
902
+ 'delete',
903
+ { id: mockId },
904
+ undefined,
905
+ mockUser,
906
+ null,
907
+ );
908
+ });
909
+ });
910
+
911
+ describe('shared-mongo: MongoItemRepository getById function', () => {
912
+ let repository: MongoItemRepository<any>;
913
+ let mockCollection: any;
914
+
915
+ beforeEach(() => {
916
+ mockCollection = {
917
+ findOne: jest.fn(),
918
+ };
919
+
920
+ repository = new MongoItemRepository<any>(null as any);
921
+
922
+ jest
923
+ .spyOn(repository as any, 'collectionContext')
924
+ .mockImplementation(async (callback: any) => {
925
+ return callback(mockCollection);
926
+ });
927
+
928
+ jest
929
+ .spyOn(repository as any, 'getModelToResult')
930
+ .mockImplementation((item: any) => item);
931
+ });
932
+
933
+ it('should call findOne and return the result', async () => {
934
+ const mockId = '123';
935
+ const mockRepoOptions: IItemRepositoryOptions = {
936
+ transaction: { session: {} } as IMongoTransaction,
937
+ };
938
+ const mockItem = { _id: mockId, name: 'testItem' };
939
+
940
+ mockCollection.findOne.mockResolvedValueOnce(mockItem);
941
+
942
+ const result = await repository.getById(mockId, mockRepoOptions);
943
+
944
+ expect(mockCollection.findOne).toHaveBeenCalledWith(
945
+ { _id: mockId },
946
+ { session: (mockRepoOptions.transaction as IMongoTransaction).session },
947
+ );
948
+ expect(result).toEqual(mockItem);
949
+ });
950
+
951
+ it('should return null if no item is found', async () => {
952
+ const mockId = '123';
953
+ const mockRepoOptions: IItemRepositoryOptions = {
954
+ transaction: { session: {} } as IMongoTransaction,
955
+ };
956
+
957
+ mockCollection.findOne.mockResolvedValueOnce(null);
958
+
959
+ const result = await repository.getById(mockId, mockRepoOptions);
960
+
961
+ expect(mockCollection.findOne).toHaveBeenCalledWith(
962
+ { _id: mockId },
963
+ { session: (mockRepoOptions.transaction as IMongoTransaction).session },
964
+ );
965
+ expect(result).toBeNull();
966
+ });
967
+
968
+ it('should work without repoOptions', async () => {
969
+ const mockId = '123';
970
+ const mockItem = { _id: mockId, name: 'testItem' };
971
+
972
+ mockCollection.findOne.mockResolvedValueOnce(mockItem);
973
+
974
+ const result = await repository.getById(mockId);
975
+
976
+ expect(mockCollection.findOne).toHaveBeenCalledWith(
977
+ { _id: mockId },
978
+ { session: undefined },
979
+ );
980
+ expect(result).toEqual(mockItem);
981
+ });
982
+ });
983
+
984
+ describe('shared-mongo: MongoItemRepository getByCriteria function', () => {
985
+ let repository: MongoItemRepository<any>;
986
+ let mockCollection: any;
987
+
988
+ beforeEach(() => {
989
+ mockCollection = {
990
+ aggregate: jest.fn(),
991
+ };
992
+
993
+ repository = new MongoItemRepository<any>(null as any);
994
+
995
+ jest
996
+ .spyOn(repository as any, 'collectionContext')
997
+ .mockImplementation(async (callback: any) => {
998
+ return callback(mockCollection);
999
+ });
1000
+
1001
+ jest
1002
+ .spyOn(repository as any, 'convertIdInCriteria')
1003
+ .mockImplementation(jest.fn());
1004
+ jest
1005
+ .spyOn(repository as any, 'generateSearch')
1006
+ .mockImplementation(jest.fn());
1007
+ jest.spyOn(repository as any, 'getCount').mockResolvedValue(42);
1008
+ jest
1009
+ .spyOn(repository as any, 'getModelToResult')
1010
+ .mockImplementation((item: any) => item);
1011
+ });
1012
+
1013
+ it('should aggregate data based on criteria and options', async () => {
1014
+ const criteria = { field: 'value' };
1015
+ const options = { sort: { field: 1 }, skip: 10, limit: 5 };
1016
+ const mockData = [{ id: 1 }, { id: 2 }];
1017
+
1018
+ mockCollection.aggregate.mockReturnValueOnce({
1019
+ toArray: jest.fn().mockResolvedValueOnce(mockData),
1020
+ });
1021
+
1022
+ const result = await repository.getByCriteria(criteria, options);
1023
+
1024
+ expect(repository['convertIdInCriteria']).toHaveBeenCalledWith(criteria);
1025
+ expect(repository['generateSearch']).toHaveBeenCalledWith(criteria);
1026
+ expect(repository['getCount']).toHaveBeenCalledWith(
1027
+ criteria,
1028
+ mockCollection,
1029
+ );
1030
+
1031
+ expect(mockCollection.aggregate).toHaveBeenCalledWith(
1032
+ [
1033
+ { $match: criteria },
1034
+ { $sort: options.sort },
1035
+ { $skip: options.skip },
1036
+ { $limit: options.limit },
1037
+ ],
1038
+ { allowDiskUse: undefined, session: undefined },
1039
+ );
1040
+
1041
+ expect(result).toEqual({ data: mockData, totalCount: 42 });
1042
+ });
1043
+
1044
+ it('should handle no criteria or options', async () => {
1045
+ const mockData = [{ id: 1 }];
1046
+
1047
+ mockCollection.aggregate.mockReturnValueOnce({
1048
+ toArray: jest.fn().mockResolvedValueOnce(mockData),
1049
+ });
1050
+
1051
+ const result = await repository.getByCriteria(undefined, undefined);
1052
+
1053
+ expect(repository['convertIdInCriteria']).toHaveBeenCalledWith(undefined);
1054
+ expect(repository['generateSearch']).toHaveBeenCalledWith(undefined);
1055
+ expect(repository['getCount']).toHaveBeenCalledWith(
1056
+ undefined,
1057
+ mockCollection,
1058
+ );
1059
+
1060
+ expect(mockCollection.aggregate).toHaveBeenCalledWith([], {
1061
+ allowDiskUse: undefined,
1062
+ session: undefined,
1063
+ });
1064
+
1065
+ expect(result).toEqual({ data: mockData, totalCount: 42 });
1066
+ });
1067
+
1068
+ it('should include additional pipeline stages when options are provided', async () => {
1069
+ const criteria = { field: 'value' };
1070
+ const options = {
1071
+ project: { field: 1 },
1072
+ group: { _id: '$field', count: { $sum: 1 } },
1073
+ };
1074
+ const mockData = [{ _id: 'field1', count: 10 }];
1075
+
1076
+ mockCollection.aggregate.mockReturnValueOnce({
1077
+ toArray: jest.fn().mockResolvedValueOnce(mockData),
1078
+ });
1079
+
1080
+ const result = await repository.getByCriteria(criteria, options);
1081
+
1082
+ expect(mockCollection.aggregate).toHaveBeenCalledWith(
1083
+ [
1084
+ { $match: criteria },
1085
+ { $project: options.project },
1086
+ { $group: options.group },
1087
+ ],
1088
+ { allowDiskUse: undefined, session: undefined },
1089
+ );
1090
+
1091
+ expect(result).toEqual({ data: mockData, totalCount: 42 });
1092
+ });
1093
+ });
1094
+
1095
+ // Mock MongoDB components
1096
+ // jest.mock('mongodb', () => {
1097
+ // const originalModule = jest.requireActual('mongodb');
1098
+ //
1099
+ // const mockGridFSBucket = jest.fn().mockImplementation(() => ({}));
1100
+ // const mockDb = jest.fn().mockReturnValue({
1101
+ // GridFSBucket: mockGridFSBucket,
1102
+ // });
1103
+ // const mockConnect = jest.fn().mockResolvedValue({
1104
+ // db: jest.fn(() => mockDb()),
1105
+ // });
1106
+ //
1107
+ // return {
1108
+ // ...originalModule,
1109
+ // MongoClient: {
1110
+ // connect: mockConnect,
1111
+ // },
1112
+ // };
1113
+ // });
1114
+
1115
+ // describe('shared-mongo: MongoItemRepository changesByCriteria function', () => {
1116
+ // let mongoItemRepository: MongoItemRepository<any>;
1117
+ // let mockClient: any;
1118
+ // let mockDb: any;
1119
+ // let mockCollection: any;
1120
+ // let mockChangeStream: any;
1121
+ // const mockMongoConfig: MongoConfig = {
1122
+ // database: 'testDb',
1123
+ // collection: 'testCollection',
1124
+ // type: null,
1125
+ // host: 'host',
1126
+ // port: 4200
1127
+ // };
1128
+ //
1129
+ // const mongodbMock = jest.requireMock("mongodb");
1130
+ // const mockConnect = mongodbMock.MongoClient.connect;
1131
+ //
1132
+ // beforeEach(() => {
1133
+ // mockClient = { db: jest.fn().mockReturnThis(), close: jest.fn() };
1134
+ // mockDb = { collection: jest.fn().mockReturnThis() };
1135
+ // mockCollection = { watch: jest.fn() };
1136
+ // mockChangeStream = { on: jest.fn() };
1137
+ // mockClient.db.mockReturnValue(mockDb);
1138
+ // mockDb.collection.mockReturnValue(mockCollection);
1139
+ // mockCollection.watch.mockReturnValue(mockChangeStream);
1140
+ // mockConnect.mockResolvedValue(mockClient);
1141
+ //
1142
+ // mongoItemRepository = new MongoItemRepository(mockMongoConfig);
1143
+ // });
1144
+ //
1145
+ // afterEach(() => {
1146
+ // jest.clearAllMocks();
1147
+ // });
1148
+ //
1149
+ // it('should establish a MongoDB connection and emit changes', (done) => {
1150
+ // const mockResult = {
1151
+ // 'documentKey': { '_id': '123' },
1152
+ // 'operationType': 'insert',
1153
+ // 'fullDocument': { id: '123', name: 'Item 1' },
1154
+ // };
1155
+ //
1156
+ // mockChangeStream.on.mockImplementationOnce((event, callback) => {
1157
+ // if (event === 'change') {
1158
+ // callback(mockResult); // Simulate a change event
1159
+ // }
1160
+ // });
1161
+ //
1162
+ // const criteria = { id: '123' };
1163
+ //
1164
+ // mongoItemRepository.changesByCriteria(criteria).subscribe({
1165
+ // next: (itemChangedData: ItemChangedData) => {
1166
+ // expect(itemChangedData).toEqual({
1167
+ // id: '123',
1168
+ // type: 'create', // 'insert' should be mapped to 'create'
1169
+ // data: mockResult.fullDocument,
1170
+ // });
1171
+ // done();
1172
+ // },
1173
+ // error: done.fail,
1174
+ // });
1175
+ //
1176
+ // expect(mockConnect).toHaveBeenCalledTimes(1);
1177
+ // expect(mockCollection.watch).toHaveBeenCalledWith([
1178
+ // { $match: { 'documentKey._id': '123' } },
1179
+ // ]);
1180
+ // });
1181
+ //
1182
+ // it('should establish a MongoDB connection and emit changes for general updates', (done) => {
1183
+ // const mockResult = {
1184
+ // 'documentKey': { '_id': '123' },
1185
+ // 'operationType': 'update',
1186
+ // 'updateDescription': { updatedFields: { name: 'Updated Item' } },
1187
+ // };
1188
+ //
1189
+ // mockChangeStream.on.mockImplementationOnce((event, callback) => {
1190
+ // if (event === 'change') {
1191
+ // callback(mockResult); // Simulate a change event
1192
+ // }
1193
+ // });
1194
+ //
1195
+ // const criteria = { id: '123' };
1196
+ //
1197
+ // mongoItemRepository.changesByCriteria(criteria).subscribe({
1198
+ // next: (itemChangedData: ItemChangedData) => {
1199
+ // expect(itemChangedData).toEqual({
1200
+ // id: '123',
1201
+ // type: 'update', // 'update' should be mapped to 'update'
1202
+ // data: mockResult.updateDescription,
1203
+ // });
1204
+ // done();
1205
+ // },
1206
+ // error: done.fail,
1207
+ // });
1208
+ //
1209
+ // expect(mockConnect).toHaveBeenCalledTimes(1);
1210
+ // expect(mockCollection.watch).toHaveBeenCalledWith([
1211
+ // { $match: { 'documentKey._id': '123' } },
1212
+ // ]);
1213
+ // });
1214
+ //
1215
+ // it('should handle errors properly and close the connection', (done) => {
1216
+ // const mockError = new Error('Connection error');
1217
+ //
1218
+ // mockChangeStream.on.mockImplementationOnce((event, callback) => {
1219
+ // if (event === 'change') {
1220
+ // callback(mockError); // Simulate an error during the change event
1221
+ // }
1222
+ // });
1223
+ //
1224
+ // const criteria = { id: '123' };
1225
+ //
1226
+ // mongoItemRepository.changesByCriteria(criteria).subscribe({
1227
+ // next: () => {},
1228
+ // error: (error) => {
1229
+ // expect(error).toEqual(mockError);
1230
+ // expect(mockConnect).toHaveBeenCalledTimes(1);
1231
+ // expect(mockClient.close).toHaveBeenCalledTimes(1);
1232
+ // done();
1233
+ // },
1234
+ // });
1235
+ // });
1236
+ //
1237
+ // it('should finalize the stream and close the connection', (done) => {
1238
+ // const mockResult = {
1239
+ // 'documentKey': { '_id': '123' },
1240
+ // 'operationType': 'insert',
1241
+ // 'fullDocument': { id: '123', name: 'Item 1' },
1242
+ // };
1243
+ //
1244
+ // mockChangeStream.on.mockImplementationOnce((event, callback) => {
1245
+ // if (event === 'change') {
1246
+ // callback(mockResult); // Simulate a change event
1247
+ // }
1248
+ // });
1249
+ //
1250
+ // const criteria = { id: '123' };
1251
+ //
1252
+ // const subscription = mongoItemRepository.changesByCriteria(criteria).subscribe({
1253
+ // next: (itemChangedData: ItemChangedData) => {
1254
+ // expect(itemChangedData).toEqual({
1255
+ // id: '123',
1256
+ // type: 'create',
1257
+ // data: mockResult.fullDocument,
1258
+ // });
1259
+ // subscription.unsubscribe(); // Unsubscribe to finalize the stream
1260
+ // done();
1261
+ // },
1262
+ // error: done.fail,
1263
+ // });
1264
+ //
1265
+ // subscription.add(() => {
1266
+ // expect(mockClient.close).toHaveBeenCalledTimes(1); // Ensure the connection is closed after unsubscribe
1267
+ // });
1268
+ // });
1269
+ // });