@natlibfi/melinda-rest-api-commons 6.1.0-alpha.0 → 6.2.0-alpha.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 (92) hide show
  1. package/.github/workflows/melinda-node-tests-and-publish.yml +4 -2
  2. package/README.md +53 -5
  3. package/dist/amqp.d.ts +41 -0
  4. package/dist/amqp.d.ts.map +1 -0
  5. package/dist/amqp.js +237 -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 +15 -11
  57. package/{tests/amqp-error-branches.test.js → test/amqp-error-branches.test.ts} +68 -52
  58. package/{tests/amqp.healthtest.js → test/amqp.healthtest.ts} +7 -3
  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} +147 -51
  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
@@ -3,24 +3,47 @@ import {test} from 'node:test';
3
3
  import {execFile} from 'node:child_process';
4
4
  import {fileURLToPath} from 'node:url';
5
5
  import {Error as ApiError} from '@natlibfi/melinda-commons';
6
- import createAmqp from '../src/amqp.js';
6
+ import type {Message} from 'amqplib';
7
+ import createAmqp, {type QueueItemMessagePayload, type QueueItemRecordPayload} from '../src/amqp.ts';
7
8
 
8
- const AMQP_MODULE_PATH = fileURLToPath(new URL('../src/amqp.js', import.meta.url));
9
+ const AMQP_MODULE_PATH = fileURLToPath(new URL('../src/amqp.ts', import.meta.url));
9
10
 
10
11
  const DUMMY_URL = 'amqp://stub:5672/';
11
12
  const QUEUE = 'TESTQUEUE';
12
13
 
13
- function makeMessage(correlationId, deliveryTag) {
14
+ /**
15
+ * A minimal stub for an amqplib message. The fields beyond deliveryTag
16
+ * match the shape of a real amqplib message (the tests only exercise
17
+ * deliveryTag, correlationId and the message body).
18
+ */
19
+ function makeMessage(correlationId: string, deliveryTag: number): Message {
14
20
  const data = {leader: '00000nam a22000000i 4500', fields: [{tag: '001', value: '123'}]};
15
21
  const content = JSON.stringify({data});
16
22
  return {
17
23
  content: Buffer.from(content),
18
- properties: {correlationId, headers: {from: 'TEST'}},
19
- fields: {deliveryTag}
24
+ // only correlationId and headers are exercised by these tests; the
25
+ // remaining message properties stay undefined
26
+ properties: {
27
+ contentType: undefined,
28
+ contentEncoding: undefined,
29
+ headers: {from: 'TEST'},
30
+ deliveryMode: undefined,
31
+ priority: undefined,
32
+ correlationId,
33
+ replyTo: undefined,
34
+ expiration: undefined,
35
+ messageId: undefined,
36
+ timestamp: undefined,
37
+ type: undefined,
38
+ userId: undefined,
39
+ appId: undefined,
40
+ clusterId: undefined
41
+ },
42
+ fields: {deliveryTag, redelivered: false, exchange: '', routingKey: ''}
20
43
  };
21
44
  }
22
45
 
23
- function makeChannel(overrides = {}) {
46
+ function makeChannel(overrides: Record<string, unknown> = {}) {
24
47
  return {
25
48
  assertQueue: async () => ({messageCount: 0}),
26
49
  checkQueue: async () => ({messageCount: 0}),
@@ -34,14 +57,17 @@ function makeChannel(overrides = {}) {
34
57
  };
35
58
  }
36
59
 
37
- async function makeOperator(channelOverrides) {
60
+ /**
61
+ * Create an operator with the given channel stub overrides injected via deps
62
+ */
63
+ async function makeOperator(channelOverrides?: Record<string, unknown>) {
38
64
  const channel = makeChannel(channelOverrides);
39
65
  const connection = {createChannel: async () => channel, close: async () => {}};
40
66
  return createAmqp(DUMMY_URL, false, {connection, channel});
41
67
  }
42
68
 
43
69
  test('consumeChunk returns false when queue drains mid-pump', async () => {
44
- // (src/amqp.js, getData/pump):
70
+ // (src/amqp.ts, getData/pump):
45
71
  // FIXED: pump now bails out (returns results) when channel.get -> null,
46
72
  // and consumeChunk maps an empty pump result to false, matching the
47
73
  // empty-queue contract of checkQueue. Previously this crashed with
@@ -67,14 +93,14 @@ test('consumeChunk returns partial chunk when queue drains after first message',
67
93
  return first;
68
94
  }
69
95
  });
70
- const {records, messages} = await amqpOperator.consumeChunk(QUEUE, true);
96
+ const {records, messages} = (await amqpOperator.consumeChunk(QUEUE, true)) as QueueItemRecordPayload;
71
97
  assert.equal(messages.length, 1);
72
98
  assert.equal(records.length, 1);
73
99
  });
74
100
 
75
101
 
76
102
  test('consumeChunk deduplicates messages with same correlationId + deliveryTag', async () => {
77
- // (src/amqp.js, getData/pump):
103
+ // (src/amqp.ts, getData/pump):
78
104
  // FIXED: dedup now compares correlationId + deliveryTag field-by-field,
79
105
  // so duplicate messages are filtered out. (Previously the includes()
80
106
  // check compared object references and never triggered.)
@@ -83,7 +109,7 @@ test('consumeChunk deduplicates messages with same correlationId + deliveryTag',
83
109
  checkQueue: async () => ({messageCount: 2}),
84
110
  get: async () => duplicate
85
111
  });
86
- const {records, messages} = await amqpOperator.consumeChunk(QUEUE, true);
112
+ const {records, messages} = (await amqpOperator.consumeChunk(QUEUE, true)) as QueueItemRecordPayload;
87
113
  assert.equal(messages.length, 1);
88
114
  assert.equal(records.length, 1);
89
115
  });
@@ -96,21 +122,20 @@ test('same deliveryTag with different correlationIds is NOT deduplicated', async
96
122
  checkQueue: async () => ({messageCount: 2}),
97
123
  get: async () => messages.shift()
98
124
  });
99
- const {messages: got} = await amqpOperator.consumeChunk(QUEUE, true);
125
+ const {messages: got} = (await amqpOperator.consumeChunk(QUEUE, true)) as QueueItemRecordPayload;
100
126
  assert.equal(got.length, 2);
101
127
  });
102
128
 
103
129
  test('removeQueue closes the temp channel even when deleteQueue throws', async () => {
104
- // (src/amqp.js, removeQueue):
130
+ // (src/amqp.ts, removeQueue):
105
131
  // FIXED: the throwaway tempChannel is now closed in a finally block, so a
106
132
  // missing queue (deleteQueue throws) no longer leaks a channel.
107
- const closedChannels = [];
133
+ const closedChannels: any[] = [];
108
134
  const tempChannel = {
109
135
  deleteQueue: async () => {
110
136
  throw new Error('NOT_FOUND - queue missing');
111
137
  },
112
138
  close: async () => {
113
- // eslint-disable-next-line functional/immutable-data
114
139
  closedChannels.push('temp');
115
140
  }
116
141
  };
@@ -123,13 +148,12 @@ test('removeQueue closes the temp channel even when deleteQueue throws', async (
123
148
  });
124
149
 
125
150
  test('removeQueue closes the temp channel on success too', async () => {
126
- const closedChannels = [];
151
+ const closedChannels: any[] = [];
127
152
  const amqpOperator = await createAmqp(DUMMY_URL, false, {
128
153
  connection: {
129
154
  createChannel: async () => ({
130
155
  deleteQueue: async () => {},
131
156
  close: async () => {
132
- // eslint-disable-next-line functional/immutable-data
133
157
  closedChannels.push('temp');
134
158
  }
135
159
  }),
@@ -142,7 +166,7 @@ test('removeQueue closes the temp channel on success too', async () => {
142
166
  });
143
167
 
144
168
  test('healthCheck loop stops after closeChannel and does not keep firing or throw', async () => {
145
- // (src/amqp.js, healthCheck):
169
+ // (src/amqp.ts, healthCheck):
146
170
  // FIXED: closeChannel/closeConnection cancel the recursive healthCheck loop,
147
171
  // so the ~200 ms chain short-circuits instead of running until the closed
148
172
  // channel throws (which used to make graceful shutdown flaky).
@@ -151,9 +175,8 @@ test('healthCheck loop stops after closeChannel and does not keep firing or thro
151
175
  let closed = false;
152
176
  let healthCheckCalls = 0;
153
177
  const channel = {
154
- assertQueue: async (name) => {
178
+ assertQueue: async (name: string) => {
155
179
  if (name === 'HEALTHCHECK') {
156
- // eslint-disable-next-line functional/immutable-data
157
180
  healthCheckCalls += 1;
158
181
  if (closed) {
159
182
  throw new Error('channel closed');
@@ -167,7 +190,6 @@ test('healthCheck loop stops after closeChannel and does not keep firing or thro
167
190
  nack: () => {},
168
191
  purgeQueue: async () => {},
169
192
  close: async () => {
170
- // eslint-disable-next-line functional/immutable-data
171
193
  closed = true;
172
194
  }
173
195
  };
@@ -194,7 +216,7 @@ test('happy path: consumeChunk returns two distinct messages', async () => {
194
216
  checkQueue: async () => ({messageCount: messages.length}),
195
217
  get: async () => messages.shift()
196
218
  });
197
- const {headers, records, messages: got} = await amqpOperator.consumeChunk(QUEUE, true);
219
+ const {headers, records, messages: got} = (await amqpOperator.consumeChunk(QUEUE, true)) as QueueItemRecordPayload;
198
220
  assert.equal(got.length, 2);
199
221
  assert.equal(records.length, 2);
200
222
  assert.deepStrictEqual(headers, {from: 'TEST'});
@@ -218,7 +240,7 @@ test('checkQueue style one returns the {headers, records, messages} form when to
218
240
  assertQueue: async () => ({messageCount: 1}),
219
241
  get: async () => message
220
242
  });
221
- const {headers, records, messages} = await amqpOperator.checkQueue({queue: QUEUE, style: 'one', toRecord: true});
243
+ const {headers, records, messages} = (await amqpOperator.checkQueue({queue: QUEUE, style: 'one', toRecord: true})) as QueueItemRecordPayload;
222
244
  assert.deepStrictEqual(headers, {from: 'TEST'});
223
245
  assert.equal(records.length, 1);
224
246
  assert.strictEqual(messages.length, 1);
@@ -257,7 +279,7 @@ test('checkQueue style basic returns a chunk of records and messages when the qu
257
279
  checkQueue: async () => ({messageCount: messages.length}),
258
280
  get: async () => messages.shift()
259
281
  });
260
- const {headers, records, messages: got} = await amqpOperator.checkQueue({queue: QUEUE, style: 'basic', toRecord: true});
282
+ const {headers, records, messages: got} = (await amqpOperator.checkQueue({queue: QUEUE, style: 'basic', toRecord: true})) as QueueItemRecordPayload;
261
283
  assert.deepStrictEqual(headers, {from: 'TEST'});
262
284
  assert.equal(records.length, 2);
263
285
  assert.equal(got.length, 2);
@@ -276,17 +298,15 @@ test('checkQueue rejects with a 500 ApiError for an unknown style', async () =>
276
298
  });
277
299
 
278
300
  test('checkQueue purges the queue before returning the style result', async () => {
279
- const purgeCalls = [];
301
+ const purgeCalls: any[] = [];
280
302
  let assertCalls = 0;
281
303
  const amqpOperator = await makeOperator({
282
304
  assertQueue: async () => {
283
- // eslint-disable-next-line functional/immutable-data
284
305
  assertCalls += 1;
285
306
  // first call (before the purge) reports 3 pending, after the purge 0
286
307
  return assertCalls === 1 ? {messageCount: 3} : {messageCount: 0};
287
308
  },
288
- purgeQueue: async (name) => {
289
- // eslint-disable-next-line functional/immutable-data
309
+ purgeQueue: async (name: string) => {
290
310
  purgeCalls.push(name);
291
311
  }
292
312
  });
@@ -309,7 +329,7 @@ test('consumeOne returns the plain message when toRecord is false', async () =>
309
329
  test('consumeOne returns the {headers, records, messages} form when toRecord is true', async () => {
310
330
  const message = makeMessage('c1', 1);
311
331
  const amqpOperator = await makeOperator({get: async () => message});
312
- const {headers, records, messages} = await amqpOperator.consumeOne(QUEUE, true);
332
+ const {headers, records, messages} = (await amqpOperator.consumeOne(QUEUE, true)) as QueueItemRecordPayload;
313
333
  assert.deepStrictEqual(headers, {from: 'TEST'});
314
334
  assert.equal(records.length, 1);
315
335
  assert.strictEqual(messages.length, 1);
@@ -341,7 +361,7 @@ test('consumeChunk returns {headers, messages} without records when toRecord is
341
361
  checkQueue: async () => ({messageCount: messages.length}),
342
362
  get: async () => messages.shift()
343
363
  });
344
- const {headers, messages: got} = await amqpOperator.consumeChunk(QUEUE, false);
364
+ const {headers, messages: got} = (await amqpOperator.consumeChunk(QUEUE, false)) as QueueItemMessagePayload;
345
365
  assert.deepStrictEqual(headers, {from: 'TEST'});
346
366
  assert.equal(got.length, 2);
347
367
  });
@@ -360,11 +380,10 @@ test('consumeChunk rejects when the amqplib checkQueue throws a plain Error', as
360
380
  // ---------------------------------------------------------------------------
361
381
 
362
382
  test('ackMessages acks every message on the channel', async () => {
363
- const acked = [];
383
+ const acked: Message[] = [];
364
384
  const messages = [makeMessage('c1', 1), makeMessage('c2', 2)];
365
385
  const amqpOperator = await makeOperator({
366
- ack: (message) => {
367
- // eslint-disable-next-line functional/immutable-data
386
+ ack: (message: Message) => {
368
387
  acked.push(message);
369
388
  }
370
389
  });
@@ -373,11 +392,10 @@ test('ackMessages acks every message on the channel', async () => {
373
392
  });
374
393
 
375
394
  test('nackMessages nacks every message on the channel', async () => {
376
- const nacked = [];
395
+ const nacked: Message[] = [];
377
396
  const messages = [makeMessage('c1', 1), makeMessage('c2', 2)];
378
397
  const amqpOperator = await makeOperator({
379
- nack: (message) => {
380
- // eslint-disable-next-line functional/immutable-data
398
+ nack: (message: Message) => {
381
399
  nacked.push(message);
382
400
  }
383
401
  });
@@ -390,10 +408,9 @@ test('nackMessages nacks every message on the channel', async () => {
390
408
  // ---------------------------------------------------------------------------
391
409
 
392
410
  test('sendToQueue sends the JSON body with correlationId, persistent and headers options', async () => {
393
- const sent = [];
411
+ const sent: {queue: string, content: Buffer, options: Record<string, unknown>}[] = [];
394
412
  const amqpOperator = await makeOperator({
395
- sendToQueue: async (queue, content, options) => {
396
- // eslint-disable-next-line functional/immutable-data
413
+ sendToQueue: async (queue: string, content: Buffer, options: Record<string, unknown>) => {
397
414
  sent.push({queue, content, options});
398
415
  }
399
416
  });
@@ -405,10 +422,10 @@ test('sendToQueue sends the JSON body with correlationId, persistent and headers
405
422
  data
406
423
  });
407
424
  assert.equal(sent.length, 1);
408
- assert.equal(sent[0].queue, QUEUE);
409
- assert.ok(Buffer.isBuffer(sent[0].content));
410
- assert.deepStrictEqual(JSON.parse(sent[0].content.toString()), {data});
411
- assert.deepStrictEqual(sent[0].options, {correlationId: 'cid-1', persistent: true, headers: {h: 1}});
425
+ assert.equal(sent[0]?.queue, QUEUE);
426
+ assert.ok(sent[0] && Buffer.isBuffer(sent[0].content));
427
+ assert.deepStrictEqual(JSON.parse(sent[0]?.content.toString() ?? ''), {data});
428
+ assert.deepStrictEqual(sent[0]?.options, {correlationId: 'cid-1', persistent: true, headers: {h: 1}});
412
429
  });
413
430
 
414
431
  test('sendToQueue rejects with a 500 ApiError when the queue is undefined', async () => {
@@ -442,13 +459,12 @@ test('checkQueue rejects with a 500 ApiError when the queue is an empty string',
442
459
  // ---------------------------------------------------------------------------
443
460
 
444
461
  test('closeConnection closes the connection and the operator resolves', async () => {
445
- const closed = [];
462
+ const closed: any[] = [];
446
463
  const channel = makeChannel();
447
464
  const amqpOperator = await createAmqp(DUMMY_URL, false, {
448
465
  connection: {
449
466
  createChannel: async () => channel,
450
467
  close: async () => {
451
- // eslint-disable-next-line functional/immutable-data
452
468
  closed.push('connection');
453
469
  }
454
470
  },
@@ -459,16 +475,14 @@ test('closeConnection closes the connection and the operator resolves', async ()
459
475
  });
460
476
 
461
477
  test('healthCheck error arriving after cancel does not rethrow and stops the loop', async () => {
462
- let rejectAssert;
478
+ let rejectAssert: ((reason?: unknown) => void) | undefined;
463
479
  const pending = new Promise((_, reject) => {
464
- // eslint-disable-next-line functional/immutable-data
465
480
  rejectAssert = reject;
466
481
  });
467
482
  let healthCheckCalls = 0;
468
483
  const channel = makeChannel({
469
- assertQueue: async (name) => {
484
+ assertQueue: async (name: string) => {
470
485
  if (name === 'HEALTHCHECK') {
471
- // eslint-disable-next-line functional/immutable-data
472
486
  healthCheckCalls += 1;
473
487
  return pending;
474
488
  }
@@ -482,7 +496,9 @@ test('healthCheck error arriving after cancel does not rethrow and stops the loo
482
496
  // by now the loop is suspended on the pending assertQueue call;
483
497
  // cancel first, then let the error arrive so the catch sees the flag already cleared
484
498
  await amqpOperator.closeChannel();
485
- rejectAssert(new Error('amqp down after close'));
499
+ if (rejectAssert) {
500
+ rejectAssert(new Error('amqp down after close'));
501
+ }
486
502
  await new Promise((resolve) => setTimeout(resolve, 50));
487
503
  assert.equal(healthCheckCalls, 1, 'no further health checks after cancel');
488
504
  });
@@ -497,7 +513,7 @@ test('healthCheck error while running rethrows into the un-awaited loop promise'
497
513
  const script = `
498
514
  import createAmqp from '${AMQP_MODULE_PATH}';
499
515
  const channel = {
500
- assertQueue: async (name) => {
516
+ assertQueue: /** @param {*} name */ async (name) => {
501
517
  if (name === 'HEALTHCHECK') {
502
518
  throw new Error('amqp down');
503
519
  }
@@ -523,7 +539,7 @@ test('healthCheck error while running rethrows into the un-awaited loop promise'
523
539
  process.stdout.write(ok ? 'OK' : 'FAIL:' + JSON.stringify(captured.map((e) => String(e && e.message))));
524
540
  process.exit(ok ? 0 : 1);
525
541
  `;
526
- const {code, stdout} = await new Promise((resolve) => {
542
+ const {code, stdout} = await new Promise<{code: number | string, stdout: string}>((resolve) => {
527
543
  const child = execFile(process.execPath, ['--input-type=module', '-e', script], (err, out) => {
528
544
  resolve({code: err ? (err.code ?? 1) : 0, stdout: out ?? ''});
529
545
  });
@@ -1,7 +1,8 @@
1
1
  import {describe, it} from 'node:test';
2
2
  import assert from 'node:assert';
3
3
  import createDebugLogger from 'debug';
4
- import {amqpFactory} from '../src/index.js';
4
+ import {amqpFactory} from '../src/index.ts';
5
+ import type {AmqpOperator} from '../src/amqp.ts';
5
6
 
6
7
 
7
8
  const debug = createDebugLogger('@natlibfi/melinda-rest-api-commons:amqp:test');
@@ -35,7 +36,11 @@ describe('HealthCheck: amqpOperator should error if channel or connection fails'
35
36
  debug(`Closing connection`);
36
37
  await amqpOperator.closeConnection();
37
38
 
38
- async function awaitTime(amqpOperator, close) {
39
+ /**
40
+ * @param amqpOperator
41
+ * @param close
42
+ */
43
+ async function awaitTime(amqpOperator: AmqpOperator, close: boolean): Promise<void> {
39
44
  if (close) {
40
45
  // wrap the wait so any error thrown by the (cancelled) loop fails the test
41
46
  await amqpOperator.closeChannel();
@@ -53,4 +58,3 @@ describe('HealthCheck: amqpOperator should error if channel or connection fails'
53
58
 
54
59
  });
55
60
  });
56
-
@@ -3,8 +3,11 @@ import createDebugLogger from 'debug';
3
3
  import {promisify} from 'util';
4
4
  import {READERS} from '@natlibfi/fixura';
5
5
  import generateTests from '@natlibfi/fixugen';
6
+ import type {CallbackArgs} from '@natlibfi/fixugen';
6
7
  import {Error as ApiError} from '@natlibfi/melinda-commons';
7
- import {amqpFactory} from '../src/index.js';
8
+ import type {GetMessage, Message} from 'amqplib';
9
+ import {amqpFactory} from '../src/index.ts';
10
+ import type {AmqpOperator, QueueItemMessagePayload, QueueItemRecordPayload, SendToQueueParams} from '../src/amqp.ts';
8
11
 
9
12
  const setTimeoutPromise = promisify(setTimeout);
10
13
  const debug = createDebugLogger('@natlibfi/melinda-rest-api-commons:amqp:test');
@@ -18,8 +21,7 @@ const testData = {
18
21
 
19
22
  debug(`Testing amqp against a local AMQP instance - we'll need some kind of mockup for this`);
20
23
  const amqpUrl = 'amqp://127.0.0.1:5672/';
21
- // eslint-disable-next-line functional/no-let
22
- let amqpOperator;
24
+ let amqpOperator: AmqpOperator;
23
25
 
24
26
  generateTests({
25
27
  callback,
@@ -48,8 +50,15 @@ generateTests({
48
50
  }
49
51
  });
50
52
 
51
- // eslint-disable-next-line max-statements
52
- async function callback({
53
+ /**
54
+ * One fixture case. See the fixture metadata files in test-fixtures/amqp
55
+ * for the possible keys (getFixture, expectToFail, expectedErrorStatus,
56
+ * ackMessages, nackMessages, checkNextMessage, checkQueueStyle, queue,
57
+ * sentToQueue, expectedMessageCount, removeQueue, expectedErrorMessage,
58
+ * expectApiError, expectFalseCheckResult).
59
+ */
60
+ async function callback(callbackParams: CallbackArgs) {
61
+ const {
53
62
  getFixture,
54
63
  expectToFail = false,
55
64
  expectedErrorStatus = '',
@@ -57,14 +66,16 @@ async function callback({
57
66
  nackMessages = false,
58
67
  checkNextMessage = false,
59
68
  checkQueueStyle = false,
60
- queue = testData.queue,
69
+ queue: queueFromFixture = testData.queue,
61
70
  sentToQueue,
62
71
  expectedMessageCount,
63
72
  removeQueue = false,
64
73
  expectedErrorMessage = undefined,
65
74
  expectApiError = false,
66
75
  expectFalseCheckResult = false
67
- }) {
76
+ } = callbackParams;
77
+ // the fixture contract passes the queue name as a string
78
+ const queue = queueFromFixture as string;
68
79
  try {
69
80
  await sendMessagesToQueue();
70
81
 
@@ -79,7 +90,7 @@ async function callback({
79
90
  assert.equal(messageCount, expectedMessageCount);
80
91
 
81
92
  if (checkNextMessage) {
82
- const {headers, records} = await queueCheckNextMessage();
93
+ const {headers, records} = (await queueCheckNextMessage()) as QueueItemRecordPayload;
83
94
  const nextMessage = {headers, records};
84
95
  const expectedNextMessage = getFixture('expectedMessage.json');
85
96
  assert.deepStrictEqual(
@@ -97,15 +108,19 @@ async function callback({
97
108
  testErrors(error);
98
109
  }
99
110
 
100
- function testErrors(error) {
111
+ /**
112
+ * @param error
113
+ */
114
+ function testErrors(error: unknown) {
101
115
  const isApiError = error instanceof ApiError;
102
116
  if (expectApiError) {
103
117
  assert.equal(isApiError, true);
104
118
  debug(`We expected and got an ApiError`);
105
- assert.equal(error.status, expectedErrorStatus);
119
+ const apiError = error as ApiError;
120
+ assert.equal(apiError.status, expectedErrorStatus);
106
121
 
107
122
  if (expectedErrorMessage) {
108
- assert.equal(error.payload, expectedErrorMessage);
123
+ assert.equal(apiError.payload, expectedErrorMessage);
109
124
  return;
110
125
  }
111
126
 
@@ -113,26 +128,26 @@ async function callback({
113
128
  }
114
129
  assert.equal(isApiError, false);
115
130
  debug(`We expected and got an non-ApiError`);
116
- debug(`Error is a ${error.constructor.name}`);
131
+ const maybeError = error as Error;
132
+ debug(`Error is a ${maybeError.constructor.name}`);
117
133
  }
118
134
 
119
135
  function queueRemoval() {
120
136
  if (removeQueue) {
121
- return amqpOperator.removeQueue(removeQueue);
137
+ return amqpOperator.removeQueue(removeQueue as string);
122
138
  }
123
139
 
124
140
  return;
125
141
  }
126
142
 
127
- function queueCheckNextMessage() {
143
+ function queueCheckNextMessage(): Promise<QueueItemRecordPayload> | false {
128
144
  if (checkNextMessage) {
129
- return amqpOperator.consumeOne(testData.queue, true);
145
+ return amqpOperator.consumeOne(testData.queue, true) as Promise<QueueItemRecordPayload>;
130
146
  }
131
147
 
132
148
  return false;
133
149
  }
134
150
 
135
- // eslint-disable-next-line max-statements
136
151
  async function checkQueue() {
137
152
  debug(`checkQueueStyle: ${checkQueueStyle}`);
138
153
 
@@ -144,18 +159,19 @@ async function callback({
144
159
  return;
145
160
  }
146
161
 
147
- assert.equal(Object.hasOwn(result, 'headers'), true);
148
- assert.equal(Object.hasOwn(result, 'records'), true);
149
- assert.equal(Object.hasOwn(result, 'messages'), true);
150
- assert.equal(result.records.length, 1);
151
- assert.equal(result.messages.length, 1);
162
+ const recordResult = result as QueueItemRecordPayload;
163
+ assert.equal(Object.hasOwn(recordResult, 'headers'), true);
164
+ assert.equal(Object.hasOwn(recordResult, 'records'), true);
165
+ assert.equal(Object.hasOwn(recordResult, 'messages'), true);
166
+ assert.equal(recordResult.records.length, 1);
167
+ assert.equal(recordResult.messages.length, 1);
152
168
 
153
169
  return;
154
170
  }
155
171
 
156
172
  if (checkQueueStyle === 'oneNotRecord') {
157
173
  const result = await amqpOperator.checkQueue({queue, style: 'one', toRecord: false, purge: false});
158
- const {fields} = result;
174
+ const {fields} = result as GetMessage;
159
175
 
160
176
  if (expectFalseCheckResult) {
161
177
  assert.strictEqual(result, false);
@@ -178,14 +194,15 @@ async function callback({
178
194
  return;
179
195
  }
180
196
 
181
- assert.equal(Object.hasOwn(result, 'headers'), true);
182
- assert.equal(Object.hasOwn(result, 'records'), true);
183
- assert.equal(Object.hasOwn(result, 'messages'), true);
184
- assert.equal(result.records.length, 3);
185
- assert.equal(result.messages.length, 3);
197
+ const recordResult = result as QueueItemRecordPayload;
198
+ assert.equal(Object.hasOwn(recordResult, 'headers'), true);
199
+ assert.equal(Object.hasOwn(recordResult, 'records'), true);
200
+ assert.equal(Object.hasOwn(recordResult, 'messages'), true);
201
+ assert.equal(recordResult.records.length, 3);
202
+ assert.equal(recordResult.messages.length, 3);
186
203
  const expectedResult = getFixture('expectedResult.json');
187
204
  assert.deepStrictEqual(
188
- JSON.parse(JSON.stringify(result.records)),
205
+ JSON.parse(JSON.stringify(recordResult.records)),
189
206
  expectedResult,
190
207
  );
191
208
 
@@ -200,11 +217,13 @@ async function callback({
200
217
  return;
201
218
  }
202
219
 
203
- assert.equal(Object.hasOwn(result, 'headers'), true);
204
- assert.equal(Object.hasOwn(result, 'messages'), true);
205
- assert.equal(result.messages.length, 3);
220
+ const messageResult = result as QueueItemMessagePayload;
221
+ assert.equal(Object.hasOwn(messageResult, 'headers'), true);
222
+ assert.equal(Object.hasOwn(messageResult, 'messages'), true);
223
+ assert.equal(messageResult.messages.length, 3);
206
224
 
207
- const [{fields}] = result.messages;
225
+ const [firstMessage] = messageResult.messages as [Message, ...Message[]];
226
+ const {fields} = firstMessage;
208
227
 
209
228
  assert.equal(typeof fields.deliveryTag, 'number');
210
229
  assert.equal(typeof fields.messageCount, 'number');
@@ -228,14 +247,14 @@ async function callback({
228
247
  async function ackOrNackMessages() {
229
248
  if (ackMessages) {
230
249
  const message = await amqpOperator.consumeOne(testData.queue, false);
231
- await amqpOperator.ackMessages([message]);
250
+ await amqpOperator.ackMessages([message as GetMessage]);
232
251
 
233
252
  return setTimeoutPromise(10);
234
253
  }
235
254
 
236
255
  if (nackMessages) {
237
256
  const message = await amqpOperator.consumeOne(testData.queue, false);
238
- await amqpOperator.nackMessages([message]);
257
+ await amqpOperator.nackMessages([message as GetMessage]);
239
258
 
240
259
  return setTimeoutPromise(10);
241
260
  }
@@ -245,7 +264,7 @@ async function callback({
245
264
 
246
265
  async function sendMessagesToQueue() {
247
266
  if (sentToQueue) {
248
- const messages = getFixture('input.json');
267
+ const messages = getFixture('input.json') as SendToQueueParams[];
249
268
  // {queue, correlationId, headers, data}
250
269
 
251
270
  const promises = messages.map(message => amqpOperator.sendToQueue(message));