@natlibfi/melinda-rest-api-commons 6.0.1-alpha.1 → 6.1.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.
- package/README.md +34 -0
- package/dist/amqp.js +37 -15
- package/dist/amqp.js.map +2 -2
- package/dist/helpers/conversion.js +1 -2
- package/dist/helpers/conversion.js.map +2 -2
- package/dist/helpers/fix-strip-f884s.js +18 -4
- package/dist/helpers/fix-strip-f884s.js.map +2 -2
- package/dist/helpers/fix.js +12 -8
- package/dist/helpers/fix.js.map +2 -2
- package/dist/helpers/validation.js +1 -1
- package/dist/helpers/validation.js.map +1 -1
- package/dist/mongo.js +200 -109
- package/dist/mongo.js.map +3 -3
- package/dist/mongoLog.js +113 -35
- package/dist/mongoLog.js.map +3 -3
- package/dist/utils.js +33 -4
- package/dist/utils.js.map +2 -2
- package/package.json +11 -17
- package/tests/amqp-error-branches.test.js +535 -0
- package/tests/amqp.healthtest.js +12 -4
- package/tests/amqp.tests.localtest.js +13 -18
- package/tests/helpers/conversion.test.js +16 -0
- package/tests/helpers/fix-edge.test.js +197 -0
- package/tests/helpers/fix.test.js +20 -2
- package/tests/helpers/validation.test.js +6 -2
- package/tests/mongo-check-and-check-state.test.js +3 -1
- package/tests/mongo-check-and-set-import-job-state.test.js +3 -1
- package/tests/mongo-error-branches.test.js +787 -0
- package/tests/mongo-get-stream.test.js +4 -1
- package/tests/mongo-read-content.test.js +10 -0
- package/tests/mongoLog-protect.test.js +181 -0
- package/tests/mongoLog.test.js +113 -6
- package/tests/utils.test.js +360 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import {test} from 'node:test';
|
|
4
|
+
import httpStatus from 'http-status';
|
|
5
|
+
import {Error as ApiError} from '@natlibfi/melinda-commons';
|
|
6
|
+
import {
|
|
7
|
+
logError,
|
|
8
|
+
createImportJobState,
|
|
9
|
+
createRecordResponseItem,
|
|
10
|
+
addRecordResponseItem,
|
|
11
|
+
addRecordResponseItems
|
|
12
|
+
} from '../src/utils.js';
|
|
13
|
+
|
|
14
|
+
test('createImportJobState should return state object for valid operation and state', () => {
|
|
15
|
+
const result = createImportJobState('CREATE', 'EMPTY');
|
|
16
|
+
assert.deepStrictEqual(result, {CREATE: 'EMPTY'});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('createImportJobState should return query object when queryImportJobState is true', () => {
|
|
20
|
+
const result = createImportJobState('CREATE', 'EMPTY', true);
|
|
21
|
+
assert.deepStrictEqual(result, {'importJobState.CREATE': 'EMPTY'});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('createImportJobState should throw for invalid state', () => {
|
|
25
|
+
assert.throws(() => {
|
|
26
|
+
createImportJobState('CREATE', 'INVALID_STATE');
|
|
27
|
+
}, new Error('Invalid IMPORT_JOB_STATE'));
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('createImportJobState should throw for invalid operation', () => {
|
|
31
|
+
assert.throws(() => {
|
|
32
|
+
createImportJobState('INVALID_OPERATION', 'EMPTY');
|
|
33
|
+
}, new Error('Invalid operation'));
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('createRecordResponseItem should use message from responsePayload.message', () => {
|
|
37
|
+
const result = createRecordResponseItem({
|
|
38
|
+
responsePayload: {message: 'payload message'},
|
|
39
|
+
responseStatus: 'UPDATED'
|
|
40
|
+
});
|
|
41
|
+
assert.equal(result.message, 'payload message');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('createRecordResponseItem should use string responsePayload as message', () => {
|
|
45
|
+
const result = createRecordResponseItem({
|
|
46
|
+
responsePayload: 'string payload',
|
|
47
|
+
responseStatus: 'UPDATED'
|
|
48
|
+
});
|
|
49
|
+
assert.equal(result.message, 'string payload');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('createRecordResponseItem should use empty message when payload has no message', () => {
|
|
53
|
+
const result = createRecordResponseItem({
|
|
54
|
+
responsePayload: {ids: ['123']},
|
|
55
|
+
responseStatus: 'UPDATED'
|
|
56
|
+
});
|
|
57
|
+
assert.equal(result.message, '');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('createRecordResponseItem should default databaseId when id is omitted', () => {
|
|
61
|
+
const result = createRecordResponseItem({
|
|
62
|
+
responsePayload: {message: 'ok'},
|
|
63
|
+
responseStatus: 'UPDATED'
|
|
64
|
+
});
|
|
65
|
+
assert.equal(result.databaseId, '000000000');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('createRecordResponseItem should set databaseId from id when provided', () => {
|
|
69
|
+
const result = createRecordResponseItem({
|
|
70
|
+
responsePayload: {message: 'ok'},
|
|
71
|
+
responseStatus: 'UPDATED',
|
|
72
|
+
id: '12345'
|
|
73
|
+
});
|
|
74
|
+
assert.equal(result.databaseId, '12345');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('createRecordResponseItem should map UPDATED, CREATED and INVALID statuses', () => {
|
|
78
|
+
for (const responseStatus of ['UPDATED', 'CREATED', 'INVALID']) {
|
|
79
|
+
const result = createRecordResponseItem({
|
|
80
|
+
responsePayload: {message: 'msg'},
|
|
81
|
+
responseStatus
|
|
82
|
+
});
|
|
83
|
+
assert.equal(result.recordStatus, responseStatus);
|
|
84
|
+
assert.equal(result.detailedRecordStatus, responseStatus);
|
|
85
|
+
assert.equal(result.message, 'msg');
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('createRecordResponseItem should map SKIPPED status', () => {
|
|
90
|
+
const result = createRecordResponseItem({
|
|
91
|
+
responsePayload: {message: 'skipped'},
|
|
92
|
+
responseStatus: 'SKIPPED'
|
|
93
|
+
});
|
|
94
|
+
assert.equal(result.recordStatus, 'SKIPPED');
|
|
95
|
+
assert.equal(result.detailedRecordStatus, 'SKIPPED');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('createRecordResponseItem should map SKIPPED_UPDATE to recordStatus SKIPPED', () => {
|
|
99
|
+
const result = createRecordResponseItem({
|
|
100
|
+
responsePayload: {message: 'skipped update'},
|
|
101
|
+
responseStatus: 'SKIPPED_UPDATE'
|
|
102
|
+
});
|
|
103
|
+
assert.equal(result.recordStatus, 'SKIPPED');
|
|
104
|
+
assert.equal(result.detailedRecordStatus, 'SKIPPED_UPDATE');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('createRecordResponseItem should map CONFLICT duplicate message to DUPLICATE', () => {
|
|
108
|
+
const result = createRecordResponseItem({
|
|
109
|
+
responsePayload: {
|
|
110
|
+
message: 'Duplicates in database: 123',
|
|
111
|
+
ids: ['123', '456']
|
|
112
|
+
},
|
|
113
|
+
responseStatus: httpStatus.CONFLICT,
|
|
114
|
+
id: '123'
|
|
115
|
+
});
|
|
116
|
+
assert.equal(result.recordStatus, 'DUPLICATE');
|
|
117
|
+
assert.equal(result.detailedRecordStatus, 'DUPLICATE');
|
|
118
|
+
assert.deepStrictEqual(result.ids, ['123', '456']);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('createRecordResponseItem should map CONFLICT MatchValidation message to ERROR', () => {
|
|
122
|
+
const result = createRecordResponseItem({
|
|
123
|
+
responsePayload: {
|
|
124
|
+
message: 'MatchValidation for all: something',
|
|
125
|
+
ids: ['123']
|
|
126
|
+
},
|
|
127
|
+
responseStatus: 'CONFLICT',
|
|
128
|
+
id: '123'
|
|
129
|
+
});
|
|
130
|
+
assert.equal(result.recordStatus, 'ERROR');
|
|
131
|
+
assert.equal(result.detailedRecordStatus, 'CONFLICT');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('createRecordResponseItem should attach ids to CONFLICT when id does not match', () => {
|
|
135
|
+
const result = createRecordResponseItem({
|
|
136
|
+
responsePayload: {
|
|
137
|
+
message: 'conflict',
|
|
138
|
+
ids: ['789']
|
|
139
|
+
},
|
|
140
|
+
responseStatus: httpStatus.CONFLICT,
|
|
141
|
+
id: '123'
|
|
142
|
+
});
|
|
143
|
+
assert.equal(result.recordStatus, 'ERROR');
|
|
144
|
+
assert.equal(result.detailedRecordStatus, 'CONFLICT');
|
|
145
|
+
assert.deepStrictEqual(result.ids, ['789']);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('createRecordResponseItem should omit ids from CONFLICT when ids matches id', () => {
|
|
149
|
+
const result = createRecordResponseItem({
|
|
150
|
+
responsePayload: {
|
|
151
|
+
message: 'conflict',
|
|
152
|
+
ids: ['123']
|
|
153
|
+
},
|
|
154
|
+
responseStatus: httpStatus.CONFLICT,
|
|
155
|
+
id: '123'
|
|
156
|
+
});
|
|
157
|
+
assert.equal(result.recordStatus, 'ERROR');
|
|
158
|
+
assert.equal(result.detailedRecordStatus, 'CONFLICT');
|
|
159
|
+
assert.equal(Object.hasOwn(result, 'ids'), false);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// a missing (undefined/null) responsePayload is rejected with a clear
|
|
163
|
+
// message; objects and strings remain the valid payload forms
|
|
164
|
+
test('createRecordResponseItem should throw with a missing responsePayload', () => {
|
|
165
|
+
assert.throws(
|
|
166
|
+
() => createRecordResponseItem({responsePayload: undefined, responseStatus: 'UPDATED'}),
|
|
167
|
+
/responsePayload must be an object or a string!/
|
|
168
|
+
);
|
|
169
|
+
assert.throws(
|
|
170
|
+
() => createRecordResponseItem({responsePayload: null, responseStatus: 'UPDATED'}),
|
|
171
|
+
/responsePayload must be an object or a string!/
|
|
172
|
+
);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// a string payload is a documented valid form and must not be rejected by the guard
|
|
176
|
+
test('createRecordResponseItem accepts a string responsePayload', () => {
|
|
177
|
+
const item = createRecordResponseItem({responsePayload: 'Something went wrong', responseStatus: 422, id: '000000001'});
|
|
178
|
+
assert.equal(item.message, 'Something went wrong');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// for CONFLICT with an empty ids array, no `ids` field is
|
|
182
|
+
// attached to the response item (previously the empty array was attached
|
|
183
|
+
// because `ids[0] !== id` was true for an empty array).
|
|
184
|
+
test('createRecordResponseItem should omit ids on CONFLICT when ids is empty', () => {
|
|
185
|
+
const result = createRecordResponseItem({
|
|
186
|
+
responsePayload: {
|
|
187
|
+
message: 'conflict',
|
|
188
|
+
ids: []
|
|
189
|
+
},
|
|
190
|
+
responseStatus: httpStatus.CONFLICT,
|
|
191
|
+
id: '123'
|
|
192
|
+
});
|
|
193
|
+
assert.equal(result.recordStatus, 'ERROR');
|
|
194
|
+
assert.equal(result.detailedRecordStatus, 'CONFLICT');
|
|
195
|
+
assert.equal(Object.hasOwn(result, 'ids'), false);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test('createRecordResponseItem should map UNPROCESSABLE_ENTITY to INVALID', () => {
|
|
199
|
+
const result = createRecordResponseItem({
|
|
200
|
+
responsePayload: {
|
|
201
|
+
message: 'unprocessable',
|
|
202
|
+
ids: ['456']
|
|
203
|
+
},
|
|
204
|
+
responseStatus: httpStatus.UNPROCESSABLE_ENTITY,
|
|
205
|
+
id: '123'
|
|
206
|
+
});
|
|
207
|
+
assert.equal(result.recordStatus, 'INVALID');
|
|
208
|
+
assert.equal(result.detailedRecordStatus, 'UNPROCESSABLE_ENTITY');
|
|
209
|
+
assert.deepStrictEqual(result.ids, ['456']);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test('createRecordResponseItem should map UNPROCESSABLE_ENTITY without ids when matching', () => {
|
|
213
|
+
const result = createRecordResponseItem({
|
|
214
|
+
responsePayload: {
|
|
215
|
+
message: 'unprocessable',
|
|
216
|
+
ids: ['123']
|
|
217
|
+
},
|
|
218
|
+
responseStatus: 'UNPROCESSABLE_ENTITY',
|
|
219
|
+
id: '123'
|
|
220
|
+
});
|
|
221
|
+
assert.equal(result.recordStatus, 'INVALID');
|
|
222
|
+
assert.equal(result.detailedRecordStatus, 'UNPROCESSABLE_ENTITY');
|
|
223
|
+
assert.equal(Object.hasOwn(result, 'ids'), false);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test('createRecordResponseItem should map NOT_FOUND to ERROR', () => {
|
|
227
|
+
const result = createRecordResponseItem({
|
|
228
|
+
responsePayload: {message: 'not found'},
|
|
229
|
+
responseStatus: httpStatus.NOT_FOUND
|
|
230
|
+
});
|
|
231
|
+
assert.equal(result.recordStatus, 'ERROR');
|
|
232
|
+
assert.equal(result.detailedRecordStatus, 'NOT_FOUND');
|
|
233
|
+
assert.equal(result.message, 'not found');
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test('createRecordResponseItem should map FORBIDDEN to ERROR', () => {
|
|
237
|
+
const result = createRecordResponseItem({
|
|
238
|
+
responsePayload: {message: 'forbidden'},
|
|
239
|
+
responseStatus: httpStatus.FORBIDDEN
|
|
240
|
+
});
|
|
241
|
+
assert.equal(result.recordStatus, 'ERROR');
|
|
242
|
+
assert.equal(result.detailedRecordStatus, 'FORBIDDEN');
|
|
243
|
+
assert.equal(result.message, 'forbidden');
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test('createRecordResponseItem should map unknown status to ERROR with appended message', () => {
|
|
247
|
+
const result = createRecordResponseItem({
|
|
248
|
+
responsePayload: {message: 'some message'},
|
|
249
|
+
responseStatus: 'SOMETHING_ELSE'
|
|
250
|
+
});
|
|
251
|
+
assert.equal(result.recordStatus, 'ERROR');
|
|
252
|
+
assert.equal(result.detailedRecordStatus, 'SOMETHING_ELSE');
|
|
253
|
+
assert.equal(result.message, 'some message - Unknown recordStatus: SOMETHING_ELSE');
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test('createRecordResponseItem should map UNKNOWN status to ERROR with ids', () => {
|
|
257
|
+
const result = createRecordResponseItem({
|
|
258
|
+
responsePayload: {
|
|
259
|
+
message: 'unknown',
|
|
260
|
+
ids: ['42']
|
|
261
|
+
},
|
|
262
|
+
responseStatus: 'UNKNOWN'
|
|
263
|
+
});
|
|
264
|
+
assert.equal(result.recordStatus, 'ERROR');
|
|
265
|
+
assert.equal(result.detailedRecordStatus, 'UNKNOWN');
|
|
266
|
+
assert.deepStrictEqual(result.ids, ['42']);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test('addRecordResponseItem should push the recordResponseItem via mongoOperator', async () => {
|
|
270
|
+
let captured;
|
|
271
|
+
const mongoOperator = {
|
|
272
|
+
pushMessages: async (args) => {
|
|
273
|
+
captured = args;
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
const recordResponseItem = {databaseId: '000000000'};
|
|
277
|
+
const result = await addRecordResponseItem({
|
|
278
|
+
recordResponseItem,
|
|
279
|
+
correlationId: 'corr-1',
|
|
280
|
+
mongoOperator
|
|
281
|
+
});
|
|
282
|
+
assert.equal(result, true);
|
|
283
|
+
assert.deepStrictEqual(captured, {
|
|
284
|
+
correlationId: 'corr-1',
|
|
285
|
+
messages: [recordResponseItem],
|
|
286
|
+
messageField: 'records'
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test('addRecordResponseItem should throw for non-object input', async () => {
|
|
291
|
+
const mongoOperator = {
|
|
292
|
+
pushMessages: async () => {}
|
|
293
|
+
};
|
|
294
|
+
for (const badInput of [undefined, null, 'not-an-object', 42, [{databaseId: '1'}]]) {
|
|
295
|
+
await assert.rejects(
|
|
296
|
+
addRecordResponseItem({
|
|
297
|
+
recordResponseItem: badInput,
|
|
298
|
+
correlationId: 'corr-42',
|
|
299
|
+
mongoOperator
|
|
300
|
+
}),
|
|
301
|
+
new Error('RecordResponseItem is not an object!')
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test('addRecordResponseItems should push all items in one call', async () => {
|
|
307
|
+
let captured;
|
|
308
|
+
let callCount = 0;
|
|
309
|
+
const mongoOperator = {
|
|
310
|
+
pushMessages: async (args) => {
|
|
311
|
+
callCount += 1;
|
|
312
|
+
captured = args;
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
const items = [
|
|
316
|
+
{databaseId: '1'},
|
|
317
|
+
{databaseId: '2'}
|
|
318
|
+
];
|
|
319
|
+
const result = await addRecordResponseItems({
|
|
320
|
+
recordResponseItems: items,
|
|
321
|
+
correlationId: 'corr-2',
|
|
322
|
+
mongoOperator
|
|
323
|
+
});
|
|
324
|
+
assert.equal(result, true);
|
|
325
|
+
assert.equal(callCount, 1);
|
|
326
|
+
assert.deepStrictEqual(captured, {
|
|
327
|
+
correlationId: 'corr-2',
|
|
328
|
+
messages: items,
|
|
329
|
+
messageField: 'records'
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
test('addRecordResponseItems should throw for non-array input', async () => {
|
|
334
|
+
const mongoOperator = {
|
|
335
|
+
pushMessages: async () => {}
|
|
336
|
+
};
|
|
337
|
+
await assert.rejects(
|
|
338
|
+
addRecordResponseItems({
|
|
339
|
+
recordResponseItems: 'not-an-array',
|
|
340
|
+
correlationId: 'corr-3',
|
|
341
|
+
mongoOperator
|
|
342
|
+
}),
|
|
343
|
+
new Error('RecordResponseItems is not an array!')
|
|
344
|
+
);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
test('logError should not throw for ApiError, SIGINT and regular errors', () => {
|
|
348
|
+
assert.doesNotThrow(() => {
|
|
349
|
+
logError(new ApiError(404, 'Not found'));
|
|
350
|
+
});
|
|
351
|
+
assert.doesNotThrow(() => {
|
|
352
|
+
logError('SIGINT');
|
|
353
|
+
});
|
|
354
|
+
assert.doesNotThrow(() => {
|
|
355
|
+
logError(new Error('some error'));
|
|
356
|
+
});
|
|
357
|
+
assert.doesNotThrow(() => {
|
|
358
|
+
logError('plain string without stack');
|
|
359
|
+
});
|
|
360
|
+
});
|