@natlibfi/melinda-rest-api-commons 6.1.0-alpha.0 → 6.2.0-alpha.1

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 (92) hide show
  1. package/.github/workflows/melinda-node-tests-and-publish.yml +4 -2
  2. package/README.md +211 -14
  3. package/dist/amqp.d.ts +41 -0
  4. package/dist/amqp.d.ts.map +1 -0
  5. package/dist/amqp.js +251 -235
  6. package/dist/amqp.js.map +1 -7
  7. package/dist/constants.d.ts +49 -0
  8. package/dist/constants.d.ts.map +1 -0
  9. package/dist/constants.js +38 -38
  10. package/dist/constants.js.map +1 -7
  11. package/dist/helpers/conversion.d.ts +8 -0
  12. package/dist/helpers/conversion.d.ts.map +1 -0
  13. package/dist/helpers/conversion.js +70 -64
  14. package/dist/helpers/conversion.js.map +1 -7
  15. package/dist/helpers/fix-constants.d.ts +76 -0
  16. package/dist/helpers/fix-constants.d.ts.map +1 -0
  17. package/dist/helpers/fix-constants.js +41 -41
  18. package/dist/helpers/fix-constants.js.map +1 -7
  19. package/dist/helpers/fix-handle-tempurns.d.ts +3 -0
  20. package/dist/helpers/fix-handle-tempurns.d.ts.map +1 -0
  21. package/dist/helpers/fix-handle-tempurns.js +73 -71
  22. package/dist/helpers/fix-handle-tempurns.js.map +1 -7
  23. package/dist/helpers/fix-strip-f884s.d.ts +10 -0
  24. package/dist/helpers/fix-strip-f884s.d.ts.map +1 -0
  25. package/dist/helpers/fix-strip-f884s.js +161 -163
  26. package/dist/helpers/fix-strip-f884s.js.map +1 -7
  27. package/dist/helpers/fix.d.ts +6 -0
  28. package/dist/helpers/fix.d.ts.map +1 -0
  29. package/dist/helpers/fix.js +133 -121
  30. package/dist/helpers/fix.js.map +1 -7
  31. package/dist/helpers/validation.d.ts +9 -0
  32. package/dist/helpers/validation.d.ts.map +1 -0
  33. package/dist/helpers/validation.js +14 -16
  34. package/dist/helpers/validation.js.map +1 -7
  35. package/dist/index.d.ts +13 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +1 -1
  38. package/dist/index.js.map +1 -7
  39. package/dist/mongo.d.ts +165 -0
  40. package/dist/mongo.d.ts.map +1 -0
  41. package/dist/mongo.js +595 -547
  42. package/dist/mongo.js.map +1 -7
  43. package/dist/mongoError.d.ts +2 -0
  44. package/dist/mongoError.d.ts.map +1 -0
  45. package/dist/mongoError.js +19 -0
  46. package/dist/mongoError.js.map +1 -0
  47. package/dist/mongoLog.d.ts +50 -0
  48. package/dist/mongoLog.d.ts.map +1 -0
  49. package/dist/mongoLog.js +274 -242
  50. package/dist/mongoLog.js.map +1 -7
  51. package/dist/utils.d.ts +36 -0
  52. package/dist/utils.d.ts.map +1 -0
  53. package/dist/utils.js +112 -110
  54. package/dist/utils.js.map +1 -7
  55. package/eslint.config.ts +66 -0
  56. package/package.json +17 -11
  57. package/{tests/amqp-error-branches.test.js → test/amqp-error-branches.test.ts} +135 -61
  58. package/{tests/amqp.healthtest.js → test/amqp.healthtest.ts} +19 -11
  59. package/{tests/amqp.tests.localtest.js → test/amqp.tests.localtest.ts} +54 -35
  60. package/{tests/helpers/conversion.test.js → test/helpers/conversion.test.ts} +26 -14
  61. package/{tests/helpers/fix-edge.test.js → test/helpers/fix-edge.test.ts} +16 -12
  62. package/{tests/helpers/fix.test.js → test/helpers/fix.test.ts} +14 -8
  63. package/{tests/helpers/validation.test.js → test/helpers/validation.test.ts} +7 -6
  64. package/{tests/mongo-add-blob-size.test.js → test/mongo-add-blob-size.test.ts} +14 -15
  65. package/{tests/mongo-check-and-check-state.test.js → test/mongo-check-and-check-state.test.ts} +6 -8
  66. package/{tests/mongo-check-and-set-import-job-state.test.js → test/mongo-check-and-set-import-job-state.test.ts} +6 -9
  67. package/{tests/mongo-check-timeout.test.js → test/mongo-check-timeout.test.ts} +6 -9
  68. package/{tests/mongo-create-bulk.test.js → test/mongo-create-bulk.test.ts} +6 -7
  69. package/{tests/mongo-create-prio.test.js → test/mongo-create-prio.test.ts} +6 -6
  70. package/{tests/mongo-error-branches.test.js → test/mongo-error-branches.test.ts} +166 -65
  71. package/{tests/mongo-get-one.test.js → test/mongo-get-one.test.ts} +6 -6
  72. package/{tests/mongo-get-stream.test.js → test/mongo-get-stream.test.ts} +6 -8
  73. package/{tests/mongo-push-ids.test.js → test/mongo-push-ids.test.ts} +6 -6
  74. package/{tests/mongo-push-messages.test.js → test/mongo-push-messages.test.ts} +6 -6
  75. package/{tests/mongo-query-by-id.test.js → test/mongo-query-by-id.test.ts} +6 -9
  76. package/{tests/mongo-query.test.js → test/mongo-query.test.ts} +6 -7
  77. package/{tests/mongo-read-content.test.js → test/mongo-read-content.test.ts} +6 -9
  78. package/{tests/mongo-remove.test.js → test/mongo-remove.test.ts} +6 -8
  79. package/{tests/mongo-set-blob-size.test.js → test/mongo-set-blob-size.test.ts} +6 -6
  80. package/{tests/mongo-set-import-job-state.test.js → test/mongo-set-import-job-state.test.ts} +6 -6
  81. package/{tests/mongo-set-operation.test.js → test/mongo-set-operation.test.ts} +6 -6
  82. package/{tests/mongo-set-operations.test.js → test/mongo-set-operations.test.ts} +6 -6
  83. package/{tests/mongo-set-state.test.js → test/mongo-set-state.test.ts} +6 -6
  84. package/{tests/mongoLog-protect.test.js → test/mongoLog-protect.test.ts} +18 -17
  85. package/{tests/mongoLog.test.js → test/mongoLog.test.ts} +8 -8
  86. package/{tests/testUtils.js → test/testUtils.ts} +49 -15
  87. package/{tests/utils.test.js → test/utils.test.ts} +5 -5
  88. package/tsconfig.json +62 -0
  89. package/tsconfig.test.json +17 -0
  90. package/dist/helpers/fix-utils.js +0 -39
  91. package/dist/helpers/fix-utils.js.map +0 -7
  92. package/eslint.config.js +0 -53
@@ -1,17 +1,19 @@
1
-
2
1
  import assert from 'node:assert';
3
2
  import {test} from 'node:test';
4
3
  import {Readable, PassThrough} from 'node:stream';
5
4
  import httpStatus from 'http-status';
6
5
  import {MongoDriverError} from 'mongodb';
7
6
  import {Error as ApiError} from '@natlibfi/melinda-commons';
8
- import createMongo from '../src/mongo.js';
9
- import createMongoLog from '../src/mongoLog.js';
7
+ import createMongo from '../src/mongo.ts';
8
+ import createMongoLog from '../src/mongoLog.ts';
10
9
 
11
10
  const DUMMY_URI = 'mongodb://stub:27017/rest-api';
12
11
  const COLLECTION = 'foobar';
13
12
 
14
- function makeOperator(overrides = {}) {
13
+ // the deps seam takes unknown-typed dependencies, so plain stub objects
14
+ // are fine to inject (the driver types are never exercised here)
15
+
16
+ function makeOperator(overrides: Record<string, any> = {}) {
15
17
  return {
16
18
  insertOne: async () => ({acknowledged: true, insertedId: 1}),
17
19
  deleteOne: async () => ({acknowledged: true, deletedCount: 1}),
@@ -19,7 +21,7 @@ function makeOperator(overrides = {}) {
19
21
  };
20
22
  }
21
23
 
22
- function makeGridFSBucket(overrides = {}) {
24
+ function makeGridFSBucket(overrides: Record<string, any> = {}) {
23
25
  return {
24
26
  find: () => ({toArray: async () => []}),
25
27
  openDownloadStreamByName: () => {
@@ -29,10 +31,18 @@ function makeGridFSBucket(overrides = {}) {
29
31
  };
30
32
  }
31
33
 
32
- async function makeMongo(deps) {
34
+ /**
35
+ * @param deps the injected stub operator/gridFSBucket
36
+ */
37
+ async function makeMongo(deps: Record<string, any>) {
33
38
  return createMongo(DUMMY_URI, COLLECTION, 'rest-api', deps);
34
39
  }
35
40
 
41
+ // The 400-guard tests pass intentionally malformed inputs (non-string
42
+ // correlationId, non-array ids, ...) that break the operator param types on
43
+ // purpose; this helper bypasses the type check for those objects
44
+ const asMalformed = <T>(params: any): T => params as T;
45
+
36
46
  test('createPrio: insertOne with acknowledged=false throws ApiError 500', async () => {
37
47
  const mongo = await makeMongo({
38
48
  operator: makeOperator({insertOne: async () => ({acknowledged: false})}),
@@ -60,7 +70,7 @@ test('createPrio: insertOne rejecting throws ApiError 500 with "Mongo errored" m
60
70
  (error) => {
61
71
  assert.ok(error instanceof ApiError);
62
72
  assert.equal(error.status, httpStatus.INTERNAL_SERVER_ERROR);
63
- assert.ok(error.payload.includes('Mongo errored'));
73
+ assert.ok((error.payload as string).includes('Mongo errored'));
64
74
  return true;
65
75
  }
66
76
  );
@@ -105,13 +115,12 @@ test('remove: MongoDriverError "File not found" deletes queueItem and reports wh
105
115
  });
106
116
 
107
117
  test('remove: returns {removed: true, deletedContent: true} when the queueItem and its GridFS file both exist', async () => {
108
- const deletedFiles = [];
118
+ const deletedFiles: any[] = [];
109
119
  const mongo = await makeMongo({
110
120
  operator: makeOperator({deleteOne: async () => ({acknowledged: true, deletedCount: 1})}),
111
121
  gridFSBucket: makeGridFSBucket({
112
122
  find: () => ({toArray: async () => [{_id: 'file-1', filename: 'FOO'}]}),
113
- delete: async (id) => {
114
- // eslint-disable-next-line functional/immutable-data
123
+ delete: async (id: unknown) => {
115
124
  deletedFiles.push(id);
116
125
  return true;
117
126
  }
@@ -147,7 +156,7 @@ test('remove: MongoDriverError with other message throws ApiError 500', async ()
147
156
  (error) => {
148
157
  assert.ok(error instanceof ApiError);
149
158
  assert.equal(error.status, httpStatus.INTERNAL_SERVER_ERROR);
150
- assert.ok(error.payload.includes('some other driver error'));
159
+ assert.ok((error.payload as string).includes('some other driver error'));
151
160
  return true;
152
161
  }
153
162
  );
@@ -190,7 +199,7 @@ test('getStream rejects with ApiError 500 when openDownloadStreamByName throws',
190
199
  (error) => {
191
200
  assert.ok(error instanceof ApiError);
192
201
  assert.equal(error.status, httpStatus.INTERNAL_SERVER_ERROR);
193
- assert.ok(error.payload.includes('Mongo errored'));
202
+ assert.ok((error.payload as string).includes('Mongo errored'));
194
203
  return true;
195
204
  }
196
205
  );
@@ -276,22 +285,27 @@ test('createBulk (stream) returns the normalized {correlationId, acknowledged, q
276
285
  });
277
286
 
278
287
  test('checkTimeOut honours MONGO_TIMEOUT_MINUTES (older item than the window is aborted)', async () => {
279
- process.env.MONGO_TIMEOUT_MINUTES = '5';
280
- const oldDate = new Date(Date.now() - 4 * 60 * 1000); // 4 min old: fine at 5m, not at 1m
281
- const mongo = await makeMongo({
282
- operator: makeOperator({
283
- findOne: async () => ({
284
- correlationId: 'FOO',
285
- queueItemState: 'VALIDATING',
286
- modificationTime: oldDate,
287
- importJobState: {CREATE: 'DONE', UPDATE: 'DONE', FIX: 'DONE'}
288
- })
289
- }),
290
- gridFSBucket: makeGridFSBucket()
291
- });
292
- const result = await mongo.checkTimeOut({correlationId: 'FOO'});
293
- delete process.env.MONGO_TIMEOUT_MINUTES;
294
- assert.equal(result, true);
288
+ process.env['MONGO_TIMEOUT_MINUTES'] = '5';
289
+ try {
290
+ const oldDate = new Date(Date.now() - 4 * 60 * 1000); // 4 min old: fine at 5m, not at 1m
291
+ const mongo = await makeMongo({
292
+ operator: makeOperator({
293
+ findOne: async () => ({
294
+ correlationId: 'FOO',
295
+ queueItemState: 'VALIDATING',
296
+ modificationTime: oldDate,
297
+ importJobState: {CREATE: 'DONE', UPDATE: 'DONE', FIX: 'DONE'}
298
+ })
299
+ }),
300
+ gridFSBucket: makeGridFSBucket()
301
+ });
302
+ const result = await mongo.checkTimeOut({correlationId: 'FOO'});
303
+ assert.equal(result, true);
304
+ } finally {
305
+ // restore the env even on failure so a later operator does not inherit a
306
+ // 5-minute timeout window
307
+ delete process.env['MONGO_TIMEOUT_MINUTES'];
308
+ }
295
309
  });
296
310
 
297
311
  // createBulk stream path used to have no error handling on the GridFS
@@ -299,7 +313,7 @@ test('checkTimeOut honours MONGO_TIMEOUT_MINUTES (older item than the window is
299
313
  // (orphan partial chunks). Both are now wired to reject + cleanup.
300
314
 
301
315
  test('createBulk rejects with ApiError 500 when input stream errors mid-upload, and cleans up the partial file', async () => {
302
- const deletedFiles = [];
316
+ const deletedFiles: any[] = [];
303
317
  // errors itself once flowing mode starts
304
318
  const input = new Readable({
305
319
  read() {
@@ -311,8 +325,7 @@ test('createBulk rejects with ApiError 500 when input stream errors mid-upload,
311
325
  gridFSBucket: makeGridFSBucket({
312
326
  openUploadStream: () => new PassThrough(),
313
327
  find: () => ({toArray: async () => [{_id: 'partial-1', filename: 'FOO'}]}),
314
- delete: async (id) => {
315
- // eslint-disable-next-line functional/immutable-data
328
+ delete: async (id: unknown) => {
316
329
  deletedFiles.push(id);
317
330
  return true;
318
331
  }
@@ -331,7 +344,7 @@ test('createBulk rejects with ApiError 500 when input stream errors mid-upload,
331
344
  (error) => {
332
345
  assert.ok(error instanceof ApiError);
333
346
  assert.equal(error.status, httpStatus.INTERNAL_SERVER_ERROR);
334
- assert.ok(error.payload.includes('input stream boom'));
347
+ assert.ok((error.payload as string).includes('input stream boom'));
335
348
  return true;
336
349
  }
337
350
  );
@@ -363,7 +376,7 @@ test('createBulk rejects with ApiError 500 when the GridFS output stream errors
363
376
  (error) => {
364
377
  assert.ok(error instanceof ApiError);
365
378
  assert.equal(error.status, httpStatus.INTERNAL_SERVER_ERROR);
366
- assert.ok(error.payload.includes('disk on fire'));
379
+ assert.ok((error.payload as string).includes('disk on fire'));
367
380
  return true;
368
381
  }
369
382
  );
@@ -375,7 +388,8 @@ test('createBulk rejects with ApiError 500 when the GridFS output stream errors
375
388
  test('pushIds rejects with ApiError 400 when handledIds is not an array', async () => {
376
389
  const mongo = await makeMongo({operator: makeOperator(), gridFSBucket: makeGridFSBucket()});
377
390
  await assert.rejects(
378
- () => mongo.pushIds({correlationId: 'FOO', handledIds: 'nope', rejectedIds: []}),
391
+ // intentionally malformed input: handledIds must be string[]
392
+ () => mongo.pushIds(asMalformed({correlationId: 'FOO', handledIds: 'nope', rejectedIds: []})),
379
393
  (error) => {
380
394
  assert.ok(error instanceof ApiError);
381
395
  assert.equal(error.status, httpStatus.BAD_REQUEST);
@@ -387,7 +401,8 @@ test('pushIds rejects with ApiError 400 when handledIds is not an array', async
387
401
  test('pushIds rejects with ApiError 400 when rejectedIds is not an array', async () => {
388
402
  const mongo = await makeMongo({operator: makeOperator(), gridFSBucket: makeGridFSBucket()});
389
403
  await assert.rejects(
390
- () => mongo.pushIds({correlationId: 'FOO', handledIds: [], rejectedIds: undefined}),
404
+ // intentionally malformed input: rejectedIds must be string[]
405
+ () => mongo.pushIds(asMalformed({correlationId: 'FOO', handledIds: [], rejectedIds: undefined})),
391
406
  (error) => {
392
407
  assert.ok(error instanceof ApiError);
393
408
  assert.equal(error.status, httpStatus.BAD_REQUEST);
@@ -408,8 +423,8 @@ test('pushIds rejects with ApiError 500 "Mongo errored" when updateOne throws',
408
423
  (error) => {
409
424
  assert.ok(error instanceof ApiError);
410
425
  assert.equal(error.status, httpStatus.INTERNAL_SERVER_ERROR);
411
- assert.ok(error.payload.includes('Mongo errored'));
412
- assert.ok(error.payload.includes('pushIds boom'));
426
+ assert.ok((error.payload as string).includes('Mongo errored'));
427
+ assert.ok((error.payload as string).includes('pushIds boom'));
413
428
  return true;
414
429
  }
415
430
  );
@@ -418,7 +433,8 @@ test('pushIds rejects with ApiError 500 "Mongo errored" when updateOne throws',
418
433
  test('pushMessages rejects with ApiError 400 when messages is not an array', async () => {
419
434
  const mongo = await makeMongo({operator: makeOperator(), gridFSBucket: makeGridFSBucket()});
420
435
  await assert.rejects(
421
- () => mongo.pushMessages({correlationId: 'FOO', messages: 'nope'}),
436
+ // intentionally malformed input: messages must be an array
437
+ () => mongo.pushMessages(asMalformed({correlationId: 'FOO', messages: 'nope'})),
422
438
  (error) => {
423
439
  assert.ok(error instanceof ApiError);
424
440
  assert.equal(error.status, httpStatus.BAD_REQUEST);
@@ -439,8 +455,8 @@ test('pushMessages rejects with ApiError 500 "Mongo errored" when updateOne thro
439
455
  (error) => {
440
456
  assert.ok(error instanceof ApiError);
441
457
  assert.equal(error.status, httpStatus.INTERNAL_SERVER_ERROR);
442
- assert.ok(error.payload.includes('Mongo errored'));
443
- assert.ok(error.payload.includes('pushMessages boom'));
458
+ assert.ok((error.payload as string).includes('Mongo errored'));
459
+ assert.ok((error.payload as string).includes('pushMessages boom'));
444
460
  return true;
445
461
  }
446
462
  );
@@ -452,7 +468,8 @@ test('pushMessages rejects with ApiError 500 "Mongo errored" when updateOne thro
452
468
  test('checkAndSetState rejects with ApiError 400 when correlationId is not a string', async () => {
453
469
  const mongo = await makeMongo({operator: makeOperator(), gridFSBucket: makeGridFSBucket()});
454
470
  await assert.rejects(
455
- () => mongo.checkAndSetState({correlationId: {'$ne': ''}, state: 'VALIDATING'}),
471
+ // intentionally malformed input: correlationId must be a string
472
+ () => mongo.checkAndSetState(asMalformed({correlationId: {'$ne': ''}, state: 'VALIDATING'})),
456
473
  (error) => {
457
474
  assert.ok(error instanceof ApiError);
458
475
  assert.equal(error.status, httpStatus.BAD_REQUEST);
@@ -464,7 +481,8 @@ test('checkAndSetState rejects with ApiError 400 when correlationId is not a str
464
481
  test('getOne rejects with ApiError 400 when queueItemState is not a string', async () => {
465
482
  const mongo = await makeMongo({operator: makeOperator({findOne: async () => null}), gridFSBucket: makeGridFSBucket()});
466
483
  await assert.rejects(
467
- () => mongo.getOne({queueItemState: {'$ne': ''}}),
484
+ // intentionally malformed input: queueItemState must be a string
485
+ () => mongo.getOne(asMalformed({queueItemState: {'$ne': ''}})),
468
486
  (error) => {
469
487
  assert.ok(error instanceof ApiError);
470
488
  assert.equal(error.status, httpStatus.BAD_REQUEST);
@@ -476,7 +494,8 @@ test('getOne rejects with ApiError 400 when queueItemState is not a string', asy
476
494
  test('getOne rejects with ApiError 400 when importJobState is not an object', async () => {
477
495
  const mongo = await makeMongo({operator: makeOperator({findOne: async () => null}), gridFSBucket: makeGridFSBucket()});
478
496
  await assert.rejects(
479
- () => mongo.getOne({importJobState: 'IN_QUEUE'}),
497
+ // intentionally malformed input: importJobState must be an object
498
+ () => mongo.getOne(asMalformed({importJobState: 'IN_QUEUE'})),
480
499
  (error) => {
481
500
  assert.ok(error instanceof ApiError);
482
501
  assert.equal(error.status, httpStatus.BAD_REQUEST);
@@ -510,10 +529,9 @@ test('getOne rejects with ApiError 400 when an importJobState value is not a str
510
529
  });
511
530
 
512
531
  test('getOne queries findOne with a valid importJobState filter', async () => {
513
- const filters = [];
532
+ const filters: any[] = [];
514
533
  const mongo = await makeMongo({
515
- operator: makeOperator({findOne: async (filter) => {
516
- // eslint-disable-next-line functional/immutable-data
534
+ operator: makeOperator({findOne: async (filter: unknown) => {
517
535
  filters.push(filter);
518
536
  return null;
519
537
  }}),
@@ -527,7 +545,8 @@ test('getOne queries findOne with a valid importJobState filter', async () => {
527
545
  test('setBlobSize rejects with ApiError 400 when blobSize is not a number', async () => {
528
546
  const mongo = await makeMongo({operator: makeOperator(), gridFSBucket: makeGridFSBucket()});
529
547
  await assert.rejects(
530
- () => mongo.setBlobSize({correlationId: 'FOO', blobSize: '7'}),
548
+ // intentionally malformed input: blobSize must be a number
549
+ () => mongo.setBlobSize(asMalformed({correlationId: 'FOO', blobSize: '7'})),
531
550
  (error) => {
532
551
  assert.ok(error instanceof ApiError);
533
552
  assert.equal(error.status, httpStatus.BAD_REQUEST);
@@ -537,10 +556,9 @@ test('setBlobSize rejects with ApiError 400 when blobSize is not a number', asyn
537
556
  });
538
557
 
539
558
  test('setBlobSize stores a real number when blobSize is a number', async () => {
540
- const updates = [];
559
+ const updates: any[] = [];
541
560
  const mongo = await makeMongo({
542
- operator: makeOperator({findOneAndUpdate: async (filter, update) => {
543
- // eslint-disable-next-line functional/immutable-data
561
+ operator: makeOperator({findOneAndUpdate: async (filter: unknown, update: unknown) => {
544
562
  updates.push({filter, update});
545
563
  return {correlationId: 'FOO', blobSize: 7};
546
564
  }}),
@@ -555,7 +573,8 @@ test('setBlobSize stores a real number when blobSize is a number', async () => {
555
573
  test('pushMessages rejects with ApiError 400 when messageField is not a string', async () => {
556
574
  const mongo = await makeMongo({operator: makeOperator(), gridFSBucket: makeGridFSBucket()});
557
575
  await assert.rejects(
558
- () => mongo.pushMessages({correlationId: 'FOO', messages: ['m1'], messageField: 42}),
576
+ // intentionally malformed input: messageField must be a string
577
+ () => mongo.pushMessages(asMalformed({correlationId: 'FOO', messages: ['m1'], messageField: 42})),
559
578
  (error) => {
560
579
  assert.ok(error instanceof ApiError);
561
580
  assert.equal(error.status, httpStatus.BAD_REQUEST);
@@ -588,7 +607,7 @@ test('createBulk (stream): insertOne with acknowledged=false throws ApiError 500
588
607
  (error) => {
589
608
  assert.ok(error instanceof ApiError);
590
609
  assert.equal(error.status, httpStatus.INTERNAL_SERVER_ERROR);
591
- assert.ok(error.payload.includes('Mongo errored'));
610
+ assert.ok((error.payload as string).includes('Mongo errored'));
592
611
  return true;
593
612
  }
594
613
  );
@@ -625,8 +644,8 @@ test('createBulk (stream): a failing GridFS cleanup does not mask the original u
625
644
  (error) => {
626
645
  assert.ok(error instanceof ApiError);
627
646
  assert.equal(error.status, httpStatus.INTERNAL_SERVER_ERROR);
628
- assert.ok(error.payload.includes('input stream boom'));
629
- assert.ok(!error.payload.includes('gridfs cleanup boom'));
647
+ assert.ok((error.payload as string).includes('input stream boom'));
648
+ assert.ok(!(error.payload as string).includes('gridfs cleanup boom'));
630
649
  return true;
631
650
  }
632
651
  );
@@ -644,7 +663,7 @@ test('remove: non-MongoDriverError from deleteOne throws ApiError 500 with the e
644
663
  (error) => {
645
664
  assert.ok(error instanceof ApiError);
646
665
  assert.equal(error.status, httpStatus.INTERNAL_SERVER_ERROR);
647
- assert.ok(error.payload.includes('plain delete boom'));
666
+ assert.ok((error.payload as string).includes('plain delete boom'));
648
667
  return true;
649
668
  }
650
669
  );
@@ -663,18 +682,62 @@ test('setOperation rejects with ApiError 404 when findOneAndUpdate returns null'
663
682
  (error) => {
664
683
  assert.ok(error instanceof ApiError);
665
684
  assert.equal(error.status, httpStatus.NOT_FOUND);
666
- assert.ok(error.payload.message.includes('not found'));
685
+ assert.ok((error.payload as {message: string}).message.includes('not found'));
667
686
  return true;
668
687
  }
669
688
  );
670
689
  });
671
690
 
691
+ // Q-3: previously-unwrapped public methods now reject with ApiError 500
692
+ // ("Mongo errored: ...") when the driver fails, instead of leaking the raw
693
+ // MongoDriverError. Non-driver errors (400/404 ApiErrors) still pass
694
+ // through unchanged.
695
+
696
+ test('mongo query: MongoDriverError from find rejects with ApiError 500 "Mongo errored"', async () => {
697
+ const mongo = await makeMongo({
698
+ operator: makeOperator({find: () => {
699
+ throw new MongoDriverError('query driver boom');
700
+ }}),
701
+ gridFSBucket: makeGridFSBucket()
702
+ });
703
+ await assert.rejects(
704
+ () => mongo.query({}),
705
+ mongoErrored500('query driver boom')
706
+ );
707
+ });
708
+
709
+ test('mongo queryById: MongoDriverError from findOne rejects with ApiError 500 "Mongo errored"', async () => {
710
+ const mongo = await makeMongo({
711
+ operator: makeOperator({findOne: async () => {
712
+ throw new MongoDriverError('queryById driver boom');
713
+ }}),
714
+ gridFSBucket: makeGridFSBucket()
715
+ });
716
+ await assert.rejects(
717
+ () => mongo.queryById({correlationId: 'FOO'}),
718
+ mongoErrored500('queryById driver boom')
719
+ );
720
+ });
721
+
722
+ test('mongo checkTimeOut: MongoDriverError from findOne rejects with ApiError 500 "Mongo errored"', async () => {
723
+ const mongo = await makeMongo({
724
+ operator: makeOperator({findOne: async () => {
725
+ throw new MongoDriverError('checkTimeOut driver boom');
726
+ }}),
727
+ gridFSBucket: makeGridFSBucket()
728
+ });
729
+ await assert.rejects(
730
+ () => mongo.checkTimeOut({correlationId: 'FOO'}),
731
+ mongoErrored500('checkTimeOut driver boom')
732
+ );
733
+ });
734
+
672
735
  // mongoLog: same deps-seam convention as mongo (factory last param). When
673
736
  // deps.collection is provided no real connection is made and every
674
737
  // db.collection('logs') call routes to the injected object. These tests
675
738
  // cover the catch blocks that re-wrap driver failures as ApiError 500.
676
739
 
677
- function makeLogCollection(overrides = {}) {
740
+ function makeLogCollection(overrides: Record<string, any> = {}) {
678
741
  return {
679
742
  insertOne: async () => ({acknowledged: true, insertedId: 1}),
680
743
  updateMany: async () => ({acknowledged: true, matchedCount: 0, modifiedCount: 0}),
@@ -683,16 +746,19 @@ function makeLogCollection(overrides = {}) {
683
746
  };
684
747
  }
685
748
 
686
- async function makeMongoLog(deps) {
749
+ /**
750
+ * @param deps the injected stub collection
751
+ */
752
+ async function makeMongoLog(deps: Record<string, any>) {
687
753
  return createMongoLog(DUMMY_URI, 'rest-api', deps);
688
754
  }
689
755
 
690
- function mongoErrored500(text) {
691
- return (error) => {
756
+ function mongoErrored500(text: string) {
757
+ return (error: unknown) => {
692
758
  assert.ok(error instanceof ApiError);
693
759
  assert.equal(error.status, httpStatus.INTERNAL_SERVER_ERROR);
694
- assert.ok(error.payload.includes('Mongo errored'));
695
- assert.ok(error.payload.includes(text));
760
+ assert.ok((error.payload as string).includes('Mongo errored'));
761
+ assert.ok((error.payload as string).includes(text));
696
762
  return true;
697
763
  };
698
764
  }
@@ -723,6 +789,42 @@ test('mongoLog addLogItem: insertOne rejecting throws ApiError 500 with "Mongo e
723
789
  );
724
790
  });
725
791
 
792
+ test('mongoLog query: MongoDriverError from find rejects with ApiError 500 "Mongo errored"', async () => {
793
+ const mongoLog = await makeMongoLog({
794
+ collection: makeLogCollection({find: () => {
795
+ throw new MongoDriverError('mongoLog query driver boom');
796
+ }})
797
+ });
798
+ await assert.rejects(
799
+ () => mongoLog.query({}),
800
+ mongoErrored500('mongoLog query driver boom')
801
+ );
802
+ });
803
+
804
+ test('mongoLog queryById: MongoDriverError from find rejects with ApiError 500 "Mongo errored"', async () => {
805
+ const mongoLog = await makeMongoLog({
806
+ collection: makeLogCollection({find: () => {
807
+ throw new MongoDriverError('mongoLog queryById driver boom');
808
+ }})
809
+ });
810
+ await assert.rejects(
811
+ () => mongoLog.queryById('FOO'),
812
+ mongoErrored500('mongoLog queryById driver boom')
813
+ );
814
+ });
815
+
816
+ test('mongoLog getListOfCatalogers: MongoDriverError from distinct rejects with ApiError 500 "Mongo errored"', async () => {
817
+ const mongoLog = await makeMongoLog({
818
+ collection: makeLogCollection({distinct: async () => {
819
+ throw new MongoDriverError('mongoLog distinct driver boom');
820
+ }})
821
+ });
822
+ await assert.rejects(
823
+ () => mongoLog.getListOfCatalogers(),
824
+ mongoErrored500('mongoLog distinct driver boom')
825
+ );
826
+ });
827
+
726
828
  test('mongoLog protect: updateMany rejecting throws ApiError 500 with "Mongo errored" message', async () => {
727
829
  const mongoLog = await makeMongoLog({
728
830
  collection: makeLogCollection({updateMany: async () => {
@@ -772,11 +874,10 @@ test('mongoLog removeBySequences: deleteMany rejecting throws ApiError 500 with
772
874
  });
773
875
 
774
876
  test('mongoLog protect: injected collection routes the update and returns its result', async () => {
775
- const filters = [];
877
+ const filters: any[] = [];
776
878
  const result = {acknowledged: true, matchedCount: 1, modifiedCount: 1};
777
879
  const mongoLog = await makeMongoLog({
778
- collection: makeLogCollection({updateMany: async (filter) => {
779
- // eslint-disable-next-line functional/immutable-data
880
+ collection: makeLogCollection({updateMany: async (filter: unknown) => {
780
881
  filters.push(filter);
781
882
  return result;
782
883
  }})
@@ -4,10 +4,10 @@ import mongoFixturesFactory from '@natlibfi/fixura-mongo';
4
4
  import generateTests from '@natlibfi/fixugen';
5
5
  import createDebugLogger from 'debug';
6
6
  //import {handleError, compareToFirstDbEntry, compareToDbEntry, formatQueueItem, streamToString} from './testUtils.js';
7
- import {getMongoOperator, handleError} from './testUtils.js';
7
+ import {getMongoOperator, handleError} from './testUtils.ts';
8
8
 
9
9
 
10
- let mongoFixtures; // eslint-disable-line functional/no-let
10
+ let mongoFixtures: any;
11
11
  const debug = createDebugLogger('@natlibfi/melinda-rest-api-commons/mongo:get-one:test');
12
12
 
13
13
  generateTests({
@@ -41,14 +41,14 @@ generateTests({
41
41
 
42
42
  async function initMongofixtures() {
43
43
  mongoFixtures = await mongoFixturesFactory({
44
- recurse: false,
45
44
  rootPath: [import.meta.dirname, '..', 'test-fixtures', 'mongo', 'get-one'],
46
45
  gridFS: {bucketName: 'foobar'},
47
46
  useObjectId: true
48
47
  });
49
48
  }
50
49
 
51
- async function callback({
50
+ async function callback(callbackParams: Record<string, any>) {
51
+ const {
52
52
  getFixture,
53
53
  functionName,
54
54
  params,
@@ -57,14 +57,14 @@ async function callback({
57
57
  expectedErrorMessage = '',
58
58
  expectedErrorStatus = '',
59
59
  expectedOpResult = undefined
60
- }) {
60
+ } = callbackParams;
61
61
 
62
62
  const mongoOperator = await getMongoOperator(mongoFixtures);
63
63
  //const expectedResult = await getFixture('expectedResult.json');
64
64
 
65
65
  await doPreFillDb(preFillDb);
66
66
 
67
- async function doPreFillDb(preFillDb) {
67
+ async function doPreFillDb(preFillDb: boolean) {
68
68
  if (preFillDb) {
69
69
  await mongoFixtures.populate(getFixture('dbContents.json'));
70
70
  return;
@@ -4,9 +4,9 @@ import mongoFixturesFactory from '@natlibfi/fixura-mongo';
4
4
  import generateTests from '@natlibfi/fixugen';
5
5
  import createDebugLogger from 'debug';
6
6
  //import {handleError, compareToFirstDbEntry, compareToDbEntry, formatQueueItem, streamToString} from './testUtils.js';
7
- import {getMongoOperator, handleError, streamToString} from './testUtils.js';
7
+ import {getMongoOperator, handleError, streamToString} from './testUtils.ts';
8
8
 
9
- let mongoFixtures; // eslint-disable-line functional/no-let
9
+ let mongoFixtures: any;
10
10
  const debug = createDebugLogger('@natlibfi/melinda-rest-api-commons/mongo:get-stream:test');
11
11
 
12
12
  generateTests({
@@ -40,15 +40,14 @@ generateTests({
40
40
 
41
41
  async function initMongofixtures() {
42
42
  mongoFixtures = await mongoFixturesFactory({
43
- recurse: false,
44
43
  rootPath: [import.meta.dirname, '..', 'test-fixtures', 'mongo', 'get-stream'],
45
44
  gridFS: {bucketName: 'foobar'},
46
45
  useObjectId: true
47
46
  });
48
47
  }
49
48
 
50
- // eslint-disable-next-line max-statements
51
- async function callback({
49
+ async function callback(callbackParams: Record<string, any>) {
50
+ const {
52
51
  getFixture,
53
52
  functionName,
54
53
  params,
@@ -58,14 +57,14 @@ async function callback({
58
57
  expectedErrorStatus = '',
59
58
  contentStream = false,
60
59
  createBulkParams = undefined
61
- }) {
60
+ } = callbackParams;
62
61
 
63
62
  const mongoOperator = await getMongoOperator(mongoFixtures);
64
63
  //const expectedResult = await getFixture('expectedResult.json');
65
64
 
66
65
  await doPreFillDb(preFillDb);
67
66
 
68
- async function doPreFillDb(preFillDb) {
67
+ async function doPreFillDb(preFillDb: boolean) {
69
68
  if (preFillDb) {
70
69
  await mongoFixtures.populate(getFixture('dbContents.json'));
71
70
  return;
@@ -80,7 +79,6 @@ async function callback({
80
79
  debug(JSON.stringify(params));
81
80
  //correlationId
82
81
 
83
- // eslint-disable-next-line functional/no-conditional-statements
84
82
  if (createBulkParams) {
85
83
  const stream = contentStream ? await getFixture({components: ['contentStream'], reader: READERS.STREAM}) : createBulkParams.stream;
86
84
  //debug(stream);
@@ -3,10 +3,10 @@ import mongoFixturesFactory from '@natlibfi/fixura-mongo';
3
3
  import generateTests from '@natlibfi/fixugen';
4
4
  import createDebugLogger from 'debug';
5
5
  //import {handleError, compareToFirstDbEntry, compareToDbEntry, formatQueueItem, streamToString} from './testUtils.js';
6
- import {getMongoOperator, handleError, compareToFirstDbEntry} from './testUtils.js';
6
+ import {getMongoOperator, handleError, compareToFirstDbEntry} from './testUtils.ts';
7
7
 
8
8
 
9
- let mongoFixtures; // eslint-disable-line functional/no-let
9
+ let mongoFixtures: any;
10
10
  const debug = createDebugLogger('@natlibfi/melinda-rest-api-commons/mongo:push-ids:test');
11
11
 
12
12
  generateTests({
@@ -40,14 +40,14 @@ generateTests({
40
40
 
41
41
  async function initMongofixtures() {
42
42
  mongoFixtures = await mongoFixturesFactory({
43
- recurse: false,
44
43
  rootPath: [import.meta.dirname, '..', 'test-fixtures', 'mongo', 'push-ids'],
45
44
  gridFS: {bucketName: 'foobar'},
46
45
  useObjectId: true
47
46
  });
48
47
  }
49
48
 
50
- async function callback({
49
+ async function callback(callbackParams: Record<string, any>) {
50
+ const {
51
51
  getFixture,
52
52
  functionName,
53
53
  params,
@@ -56,14 +56,14 @@ async function callback({
56
56
  expectedToThrow = false,
57
57
  expectedErrorMessage = '',
58
58
  expectedErrorStatus = ''
59
- }) {
59
+ } = callbackParams;
60
60
 
61
61
  const mongoOperator = await getMongoOperator(mongoFixtures);
62
62
  const expectedResult = await getFixture('expectedResult.json');
63
63
 
64
64
  await doPreFillDb(preFillDb);
65
65
 
66
- async function doPreFillDb(preFillDb) {
66
+ async function doPreFillDb(preFillDb: boolean) {
67
67
  if (preFillDb) {
68
68
  await mongoFixtures.populate(getFixture('dbContents.json'));
69
69
  return;
@@ -4,9 +4,9 @@ import mongoFixturesFactory from '@natlibfi/fixura-mongo';
4
4
  import generateTests from '@natlibfi/fixugen';
5
5
  import createDebugLogger from 'debug';
6
6
  //import {handleError, compareToFirstDbEntry, compareToDbEntry, formatQueueItem, streamToString} from './testUtils.js';
7
- import {getMongoOperator, handleError, compareToFirstDbEntry} from './testUtils.js';
7
+ import {getMongoOperator, handleError, compareToFirstDbEntry} from './testUtils.ts';
8
8
 
9
- let mongoFixtures; // eslint-disable-line functional/no-let
9
+ let mongoFixtures: any;
10
10
  const debug = createDebugLogger('@natlibfi/melinda-rest-api-commons/mongo:push-messages:test');
11
11
 
12
12
  generateTests({
@@ -40,14 +40,14 @@ generateTests({
40
40
 
41
41
  async function initMongofixtures() {
42
42
  mongoFixtures = await mongoFixturesFactory({
43
- recurse: false,
44
43
  rootPath: [import.meta.dirname, '..', 'test-fixtures', 'mongo', 'push-messages'],
45
44
  gridFS: {bucketName: 'foobar'},
46
45
  useObjectId: true
47
46
  });
48
47
  }
49
48
 
50
- async function callback({
49
+ async function callback(callbackParams: Record<string, any>) {
50
+ const {
51
51
  getFixture,
52
52
  functionName,
53
53
  params,
@@ -56,14 +56,14 @@ async function callback({
56
56
  expectedToThrow = false,
57
57
  expectedErrorMessage = '',
58
58
  expectedErrorStatus = ''
59
- }) {
59
+ } = callbackParams;
60
60
 
61
61
  const mongoOperator = await getMongoOperator(mongoFixtures);
62
62
  const expectedResult = await getFixture('expectedResult.json');
63
63
 
64
64
  await doPreFillDb(preFillDb);
65
65
 
66
- async function doPreFillDb(preFillDb) {
66
+ async function doPreFillDb(preFillDb: boolean) {
67
67
  if (preFillDb) {
68
68
  await mongoFixtures.populate(getFixture('dbContents.json'));
69
69
  return;