@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
|
@@ -90,7 +90,10 @@ async function callback({
|
|
|
90
90
|
}
|
|
91
91
|
//const {correlationId} = params;
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
// getStream is now async (it pre-checks file existence and
|
|
94
|
+
// throws 404 for a missing file instead of returning a stream that
|
|
95
|
+
// emits an unhandled 'error' event)
|
|
96
|
+
const opResult = await mongoOperator.getStream(params);
|
|
94
97
|
const opResultString = await streamToString(opResult);
|
|
95
98
|
|
|
96
99
|
if (contentStream) {
|
|
@@ -69,6 +69,16 @@ async function callback({
|
|
|
69
69
|
async function doPreFillDb(preFillDb) {
|
|
70
70
|
if (preFillDb) {
|
|
71
71
|
await mongoFixtures.populate(getFixture('dbContents.json'));
|
|
72
|
+
// optional gridFSFiles.json uploads GridFS files (filename -> content)
|
|
73
|
+
let gridFSFiles = undefined;
|
|
74
|
+
try {
|
|
75
|
+
gridFSFiles = getFixture('gridFSFiles.json');
|
|
76
|
+
} catch {
|
|
77
|
+
// no GridFS files for this fixture
|
|
78
|
+
}
|
|
79
|
+
if (gridFSFiles) {
|
|
80
|
+
await mongoFixtures.populateFiles(gridFSFiles);
|
|
81
|
+
}
|
|
72
82
|
return;
|
|
73
83
|
}
|
|
74
84
|
return;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import {test, before, after} from 'node:test';
|
|
3
|
+
import mongoFixturesFactory from '@natlibfi/fixura-mongo';
|
|
4
|
+
import {Error as ApiError} from '@natlibfi/melinda-commons';
|
|
5
|
+
import createMongoLogOperator from '../src/mongoLog.js';
|
|
6
|
+
|
|
7
|
+
let mongoFixtures; // eslint-disable-line functional/no-let
|
|
8
|
+
let mongoLogOperator; // eslint-disable-line functional/no-let
|
|
9
|
+
|
|
10
|
+
async function initMongofixtures() {
|
|
11
|
+
mongoFixtures = await mongoFixturesFactory({
|
|
12
|
+
rootPath: [import.meta.dirname, '..', 'test-fixtures', 'mongoLog'],
|
|
13
|
+
gridFS: {bucketName: 'blobs'},
|
|
14
|
+
useObjectId: true,
|
|
15
|
+
format: {
|
|
16
|
+
logs: {
|
|
17
|
+
creationTime: v => new Date(v),
|
|
18
|
+
modificationTime: v => new Date(v)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
const mongoUri = await mongoFixtures.getUri();
|
|
23
|
+
mongoLogOperator = await createMongoLogOperator(mongoUri, '');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function cleanup() {
|
|
27
|
+
await mongoFixtures.clear();
|
|
28
|
+
await mongoFixtures.close();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
before(async () => {
|
|
32
|
+
await initMongofixtures();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
after(async () => {
|
|
36
|
+
await cleanup();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('protect is idempotent: protecting an already protected log keeps it protected', async () => {
|
|
40
|
+
const logItem = {
|
|
41
|
+
logItemType: 'MERGE_LOG',
|
|
42
|
+
cataloger: 'IMP_TEST1',
|
|
43
|
+
correlationId: '22222222-2222-4222-8222-222222222222',
|
|
44
|
+
blobSequence: 1
|
|
45
|
+
};
|
|
46
|
+
await mongoLogOperator.addLogItem(logItem);
|
|
47
|
+
|
|
48
|
+
await mongoLogOperator.protect(logItem.correlationId, logItem.blobSequence);
|
|
49
|
+
|
|
50
|
+
// a second call must not toggle the log back to unprotected
|
|
51
|
+
await mongoLogOperator.protect(logItem.correlationId, logItem.blobSequence);
|
|
52
|
+
|
|
53
|
+
const {logs} = await mongoFixtures.dump();
|
|
54
|
+
const own = logs.filter(doc => doc.correlationId === logItem.correlationId);
|
|
55
|
+
assert.equal(own.length, 1);
|
|
56
|
+
assert.equal(own[0].protected, true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('unprotect sets protected to false after protect', async () => {
|
|
60
|
+
const logItem = {
|
|
61
|
+
logItemType: 'MERGE_LOG',
|
|
62
|
+
cataloger: 'IMP_TEST1',
|
|
63
|
+
correlationId: '33333333-3333-4333-8333-333333333333',
|
|
64
|
+
blobSequence: 2
|
|
65
|
+
};
|
|
66
|
+
await mongoLogOperator.addLogItem(logItem);
|
|
67
|
+
|
|
68
|
+
await mongoLogOperator.protect(logItem.correlationId, logItem.blobSequence);
|
|
69
|
+
const result = await mongoLogOperator.unprotect(logItem.correlationId, logItem.blobSequence);
|
|
70
|
+
assert.equal(result.modifiedCount, 1);
|
|
71
|
+
|
|
72
|
+
const {logs} = await mongoFixtures.dump();
|
|
73
|
+
const own = logs.filter(doc => doc.correlationId === logItem.correlationId);
|
|
74
|
+
assert.equal(own.length, 1);
|
|
75
|
+
assert.equal(own[0].protected, false);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('unprotect without blobSequence unprotects all logs of the correlationId', async () => {
|
|
79
|
+
const correlationId = '44444444-4444-4444-8444-444444444444';
|
|
80
|
+
await mongoLogOperator.addLogItem({logItemType: 'MERGE_LOG', cataloger: 'IMP_TEST1', correlationId, blobSequence: 1});
|
|
81
|
+
await mongoLogOperator.addLogItem({logItemType: 'MATCH_LOG', cataloger: 'IMP_TEST1', correlationId, blobSequence: 2});
|
|
82
|
+
|
|
83
|
+
await mongoLogOperator.protect(correlationId);
|
|
84
|
+
const result = await mongoLogOperator.unprotect(correlationId);
|
|
85
|
+
assert.equal(result.modifiedCount, 2);
|
|
86
|
+
|
|
87
|
+
const {logs} = await mongoFixtures.dump();
|
|
88
|
+
const own = logs.filter(doc => doc.correlationId === correlationId);
|
|
89
|
+
assert.equal(own.length, 2);
|
|
90
|
+
assert.ok(own.every(doc => doc.protected === false));
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('getExpandedListOfLogs throws ApiError when logItemTypes is provided but is not an array', async () => {
|
|
94
|
+
await assert.rejects(
|
|
95
|
+
() => mongoLogOperator.getExpandedListOfLogs({logItemTypes: 'MERGE_LOG'}),
|
|
96
|
+
(error) => {
|
|
97
|
+
assert.ok(error instanceof ApiError);
|
|
98
|
+
assert.strictEqual(error.status, 400);
|
|
99
|
+
assert.ok(error.payload.includes('logItemTypes must be an array'));
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('getExpandedListOfLogs throws ApiError when logItemTypes contains an unknown element', async () => {
|
|
106
|
+
await assert.rejects(
|
|
107
|
+
() => mongoLogOperator.getExpandedListOfLogs({logItemTypes: ['MERGE_LOG', 'BOGUS_LOG']}),
|
|
108
|
+
(error) => {
|
|
109
|
+
assert.ok(error instanceof ApiError);
|
|
110
|
+
assert.strictEqual(error.status, 400);
|
|
111
|
+
assert.ok(error.payload.includes('Unknown logItemType'));
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('getExpandedListOfLogs uses Date instances for dateBefore and dateAfter as-is', async () => {
|
|
118
|
+
const logItem = {
|
|
119
|
+
logItemType: 'MERGE_LOG',
|
|
120
|
+
cataloger: 'IMP_TEST1',
|
|
121
|
+
correlationId: '66666666-6666-4666-8666-666666666666',
|
|
122
|
+
blobSequence: 1
|
|
123
|
+
};
|
|
124
|
+
await mongoLogOperator.addLogItem(logItem);
|
|
125
|
+
// both bounds as Date instances: the match must use them as-is (no UTC
|
|
126
|
+
// midnight anchoring), so the freshly created log is inside the window
|
|
127
|
+
const result = await mongoLogOperator.getExpandedListOfLogs({
|
|
128
|
+
logItemTypes: ['MERGE_LOG'],
|
|
129
|
+
catalogers: ['IMP_TEST1'],
|
|
130
|
+
dateBefore: new Date(Date.now() + 60 * 60 * 1000),
|
|
131
|
+
dateAfter: new Date(Date.now() - 60 * 60 * 1000)
|
|
132
|
+
});
|
|
133
|
+
const own = result.filter(entry => entry.correlationId === logItem.correlationId);
|
|
134
|
+
assert.equal(own.length, 1);
|
|
135
|
+
assert.equal(own[0].logCount, 1);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('getListOfLogs throws ApiError 500 for an unknown logItemType', async () => {
|
|
139
|
+
await assert.rejects(
|
|
140
|
+
() => mongoLogOperator.getListOfLogs('BOGUS_LOG'),
|
|
141
|
+
(error) => {
|
|
142
|
+
assert.ok(error instanceof ApiError);
|
|
143
|
+
assert.strictEqual(error.status, 500);
|
|
144
|
+
assert.ok(error.payload.includes('Unknown logItemType'));
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('addLogItem with an unknown logItemType does not throw (warn path)', async () => {
|
|
151
|
+
const logItem = {
|
|
152
|
+
logItemType: 'BOGUS_LOG',
|
|
153
|
+
cataloger: 'IMP_TEST1',
|
|
154
|
+
correlationId: '77777777-7777-4777-8777-777777777777',
|
|
155
|
+
blobSequence: 1
|
|
156
|
+
};
|
|
157
|
+
// checkLogItemType is called with errorUnknown=false for addLogItem, so an
|
|
158
|
+
// unknown type must only warn, not throw, and the log must be stored
|
|
159
|
+
await mongoLogOperator.addLogItem(logItem);
|
|
160
|
+
|
|
161
|
+
const {logs} = await mongoFixtures.dump();
|
|
162
|
+
const own = logs.filter(doc => doc.correlationId === logItem.correlationId);
|
|
163
|
+
assert.equal(own.length, 1);
|
|
164
|
+
assert.equal(own[0].logItemType, 'BOGUS_LOG');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('getExpandedListOfLogs with a valid logItemTypes array still works', async () => {
|
|
168
|
+
const logItem = {
|
|
169
|
+
logItemType: 'MERGE_LOG',
|
|
170
|
+
cataloger: 'IMP_TEST1',
|
|
171
|
+
correlationId: '55555555-5555-4555-8555-555555555555',
|
|
172
|
+
blobSequence: 1
|
|
173
|
+
};
|
|
174
|
+
await mongoLogOperator.addLogItem(logItem);
|
|
175
|
+
const result = await mongoLogOperator.getExpandedListOfLogs({logItemTypes: ['MERGE_LOG'], catalogers: ['IMP_TEST1'], dateBefore: new Date(Date.now() + 60 * 60 * 1000)});
|
|
176
|
+
const own = result.filter(entry => entry.correlationId === logItem.correlationId);
|
|
177
|
+
assert.equal(own.length, 1);
|
|
178
|
+
assert.equal(own[0].logItemType, 'MERGE_LOG');
|
|
179
|
+
assert.equal(own[0].cataloger, 'IMP_TEST1');
|
|
180
|
+
assert.equal(own[0].logCount, 1);
|
|
181
|
+
});
|
package/tests/mongoLog.test.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
2
|
import {READERS} from '@natlibfi/fixura';
|
|
3
|
+
import {Error as ApiError} from '@natlibfi/melinda-commons';
|
|
3
4
|
import generateTests from '@natlibfi/fixugen';
|
|
4
5
|
import mongoFixturesFactory from '@natlibfi/fixura-mongo';
|
|
5
6
|
import createMongoLogOperator from '../src/mongoLog.js';
|
|
6
7
|
|
|
7
|
-
let mongoFixtures;
|
|
8
|
+
let mongoFixtures;
|
|
8
9
|
|
|
9
10
|
generateTests({
|
|
10
11
|
callback,
|
|
@@ -32,9 +33,16 @@ async function initMongofixtures() {
|
|
|
32
33
|
rootPath: [import.meta.dirname, '..', 'test-fixtures', 'mongoLog'],
|
|
33
34
|
gridFS: {bucketName: 'blobs'},
|
|
34
35
|
useObjectId: true,
|
|
36
|
+
// fixura-mongo format is keyed by COLLECTION name, so the old
|
|
37
|
+
// {creationTime, modificationTime} form never applied and the fixtures
|
|
38
|
+
// stored creationTime as an ISO string (production addLogItem stores a
|
|
39
|
+
// Date). Format under the 'logs' collection so tests see the real
|
|
40
|
+
// production shape.
|
|
35
41
|
format: {
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
logs: {
|
|
43
|
+
creationTime: v => new Date(v),
|
|
44
|
+
modificationTime: v => new Date(v)
|
|
45
|
+
}
|
|
38
46
|
}
|
|
39
47
|
});
|
|
40
48
|
}
|
|
@@ -43,11 +51,40 @@ async function callback({
|
|
|
43
51
|
getFixture,
|
|
44
52
|
functionName,
|
|
45
53
|
params,
|
|
46
|
-
preFillDb = false
|
|
54
|
+
preFillDb = false,
|
|
55
|
+
expectError = false,
|
|
56
|
+
expectedErrorStatus = 500
|
|
47
57
|
}) {
|
|
48
58
|
const mongoUri = await mongoFixtures.getUri();
|
|
49
59
|
const mongoLogOperator = await createMongoLogOperator(mongoUri, '');
|
|
50
|
-
const expectedResult = await getFixture('expectedResult.json');
|
|
60
|
+
const expectedResult = expectError ? undefined : await getFixture('expectedResult.json');
|
|
61
|
+
|
|
62
|
+
if (expectError) {
|
|
63
|
+
// assert the throw (like the removeBySequences branch below)
|
|
64
|
+
if (functionName === 'protect') {
|
|
65
|
+
const [correlationId, blobSequence] = params;
|
|
66
|
+
return assert.rejects(
|
|
67
|
+
() => mongoLogOperator.protect(correlationId, blobSequence),
|
|
68
|
+
error => error instanceof ApiError && error.status === expectedErrorStatus
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
if (functionName === 'remove') {
|
|
72
|
+
const [correlationId, force] = params;
|
|
73
|
+
return assert.rejects(
|
|
74
|
+
() => mongoLogOperator.remove(correlationId, force),
|
|
75
|
+
error => error instanceof ApiError && error.status === expectedErrorStatus
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
if (functionName === 'removeBySequences') {
|
|
79
|
+
const [correlationId, sequences, force] = params;
|
|
80
|
+
return assert.rejects(
|
|
81
|
+
() => mongoLogOperator.removeBySequences(correlationId, sequences, force),
|
|
82
|
+
error => error instanceof ApiError && error.status === expectedErrorStatus
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
throw new Error(`Unknown functionName with expectError: ${functionName}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
51
88
|
// console.log(typeof mongoUri); // eslint-disable-line
|
|
52
89
|
|
|
53
90
|
if (preFillDb) {
|
|
@@ -71,7 +108,7 @@ async function callback({
|
|
|
71
108
|
const result = await mongoLogOperator.getExpandedListOfLogs(params);
|
|
72
109
|
// console.log(result); // eslint-disable-line
|
|
73
110
|
|
|
74
|
-
return assert.deepEqual(result, expectedResult);
|
|
111
|
+
return assert.deepEqual(normalizeDates(result), expectedResult);
|
|
75
112
|
}
|
|
76
113
|
|
|
77
114
|
if (functionName === 'getListOfLogs') {
|
|
@@ -79,6 +116,76 @@ async function callback({
|
|
|
79
116
|
// console.log(result); // eslint-disable-line
|
|
80
117
|
return assert.deepEqual(result, expectedResult);
|
|
81
118
|
}
|
|
119
|
+
|
|
120
|
+
if (functionName === 'query') {
|
|
121
|
+
const result = normalizeDates(await mongoLogOperator.query(params));
|
|
122
|
+
// console.log(result); // eslint-disable-line
|
|
123
|
+
return assert.deepEqual(result, expectedResult);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (functionName === 'queryById') {
|
|
127
|
+
const [correlationId, logItemType, skip, limit] = params;
|
|
128
|
+
const result = normalizeDates(await mongoLogOperator.queryById(correlationId, logItemType, skip, limit));
|
|
129
|
+
// console.log(result); // eslint-disable-line
|
|
130
|
+
return assert.deepEqual(result, expectedResult);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (functionName === 'getListOfCorrelationIds') {
|
|
134
|
+
const result = await mongoLogOperator.getListOfCorrelationIds();
|
|
135
|
+
// console.log(result); // eslint-disable-line
|
|
136
|
+
return assert.deepEqual(result, expectedResult);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (functionName === 'protect') {
|
|
140
|
+
const [correlationId, blobSequence] = params;
|
|
141
|
+
const result = await mongoLogOperator.protect(correlationId, blobSequence);
|
|
142
|
+
assert.deepEqual(result, expectedResult);
|
|
143
|
+
const remaining = (await dumpRemaining()).map(doc => {
|
|
144
|
+
// protect() sets modificationTime to the current time, so it can't be
|
|
145
|
+
// compared against a static fixture
|
|
146
|
+
const fixed = {...doc};
|
|
147
|
+
delete fixed.modificationTime;
|
|
148
|
+
return fixed;
|
|
149
|
+
});
|
|
150
|
+
return assert.deepEqual(remaining, getFixture('expectedRemaining.json'));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (functionName === 'remove') {
|
|
154
|
+
const [correlationId, force] = params;
|
|
155
|
+
const result = await mongoLogOperator.remove(correlationId, force);
|
|
156
|
+
assert.deepEqual(result, expectedResult);
|
|
157
|
+
return assert.deepEqual(await dumpRemaining(), getFixture('expectedRemaining.json'));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (functionName === 'removeBySequences') {
|
|
161
|
+
const [correlationId, sequences, force] = params;
|
|
162
|
+
const result = await mongoLogOperator.removeBySequences(correlationId, sequences, force);
|
|
163
|
+
assert.deepEqual(result, expectedResult);
|
|
164
|
+
return assert.deepEqual(await dumpRemaining(), getFixture('expectedRemaining.json'));
|
|
165
|
+
}
|
|
166
|
+
|
|
82
167
|
throw new Error(`Unknown functionName: ${functionName}`);
|
|
83
168
|
}
|
|
84
169
|
|
|
170
|
+
// Dump the surviving logs, normalized for comparison against a fixture
|
|
171
|
+
async function dumpRemaining() {
|
|
172
|
+
return normalizeDates((await mongoFixtures.dump()).logs);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Convert BSON Dates in the result to ISO strings and drop _id, so the result
|
|
176
|
+
// can be compared with the plain strings in expectedResult.json /
|
|
177
|
+
// expectedRemaining.json (fixura-mongo dump also strips _id)
|
|
178
|
+
function normalizeDates(docs) {
|
|
179
|
+
return docs.map(doc => {
|
|
180
|
+
const fixed = {...doc};
|
|
181
|
+
delete fixed._id;
|
|
182
|
+
if (fixed.creationTime instanceof Date) {
|
|
183
|
+
fixed.creationTime = fixed.creationTime.toISOString();
|
|
184
|
+
}
|
|
185
|
+
if (fixed.modificationTime instanceof Date) {
|
|
186
|
+
fixed.modificationTime = fixed.modificationTime.toISOString();
|
|
187
|
+
}
|
|
188
|
+
return fixed;
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|