@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,197 @@
|
|
|
1
|
+
// Edge-case tests for the MARC fix helpers.
|
|
2
|
+
|
|
3
|
+
import assert from 'node:assert';
|
|
4
|
+
import {test} from 'node:test';
|
|
5
|
+
import {MarcRecord} from '@natlibfi/marc-record';
|
|
6
|
+
import {fixRecord} from '../../src/helpers/fix.js';
|
|
7
|
+
import {BIB_STRIP_F884S_SETTINGS, BIB_F035_TO_SID} from '../../src/helpers/fix-constants.js';
|
|
8
|
+
|
|
9
|
+
// a Melinda f884 (tag 884, $5 MELINDA, $a + $g present) that
|
|
10
|
+
// has NO $k subfield. findSource/findHash used to call `sfK.replace(...)` on
|
|
11
|
+
// undefined and crash the whole fixRecord call. Now a missing $k is treated
|
|
12
|
+
// as an empty string: the field gets NO_SOURCE (source) and the all-zero
|
|
13
|
+
// sentinel (hash), and the merged date range is applied to its $g.
|
|
14
|
+
test('fixRecord handles Melinda f884 without $k subfield without crashing', () => {
|
|
15
|
+
const record = new MarcRecord({
|
|
16
|
+
leader: '03085cam a22006858i 4500',
|
|
17
|
+
fields: [
|
|
18
|
+
{tag: '001', value: '000000001'},
|
|
19
|
+
{tag: '003', value: 'FI-MELINDA'},
|
|
20
|
+
{tag: '005', value: '20220818041047.0'},
|
|
21
|
+
{
|
|
22
|
+
// Melinda f884 WITHOUT $k
|
|
23
|
+
tag: '884',
|
|
24
|
+
ind1: ' ',
|
|
25
|
+
ind2: ' ',
|
|
26
|
+
subfields: [
|
|
27
|
+
{code: 'a', value: 'ONIX3 to MARC transformation'},
|
|
28
|
+
{code: 'g', value: '20220101'},
|
|
29
|
+
{code: '5', value: 'MELINDA'}
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
// A second Melinda f884 with $k so that the
|
|
34
|
+
// "not enough Melinda f884s" early return does not trigger
|
|
35
|
+
tag: '884',
|
|
36
|
+
ind1: ' ',
|
|
37
|
+
ind2: ' ',
|
|
38
|
+
subfields: [
|
|
39
|
+
{code: 'a', value: 'ONIX3 to MARC transformation'},
|
|
40
|
+
{code: 'g', value: '20000718'},
|
|
41
|
+
{
|
|
42
|
+
code: 'k',
|
|
43
|
+
value: 'SRC_4:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
|
44
|
+
},
|
|
45
|
+
{code: '5', value: 'MELINDA'}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
}, {subfieldValues: false});
|
|
50
|
+
|
|
51
|
+
// Fixed behaviour: fixRecord must not throw
|
|
52
|
+
const result = fixRecord(record, BIB_STRIP_F884S_SETTINGS);
|
|
53
|
+
|
|
54
|
+
// Both Melinda f884s survive (they are not identical, so uniq keeps both)
|
|
55
|
+
const f884s = result.fields.filter((f) => f.tag === '884');
|
|
56
|
+
assert.equal(f884s.length, 2);
|
|
57
|
+
|
|
58
|
+
// The field WITHOUT $k still has its $a, $g and $5 MELINDA
|
|
59
|
+
const noK = f884s.find((f) => !f.subfields.some((s) => s.code === 'k'));
|
|
60
|
+
assert.ok(noK, 'field without $k should survive');
|
|
61
|
+
assert.deepEqual(
|
|
62
|
+
noK.subfields.map((s) => s.code).sort(),
|
|
63
|
+
['5', 'a', 'g']
|
|
64
|
+
);
|
|
65
|
+
const gNoK = noK.subfields.find((s) => s.code === 'g').value;
|
|
66
|
+
const aNoK = noK.subfields.find((s) => s.code === 'a').value;
|
|
67
|
+
assert.equal(aNoK, 'ONIX3 to MARC transformation');
|
|
68
|
+
assert.equal(gNoK, '20220101');
|
|
69
|
+
|
|
70
|
+
// The field WITH $k keeps its $a, $g, $k and $5 MELINDA
|
|
71
|
+
const withK = f884s.find((f) => f.subfields.some((s) => s.code === 'k'));
|
|
72
|
+
assert.ok(withK, 'field with $k should survive');
|
|
73
|
+
assert.deepEqual(
|
|
74
|
+
withK.subfields.map((s) => s.code).sort(),
|
|
75
|
+
['5', 'a', 'g', 'k']
|
|
76
|
+
);
|
|
77
|
+
assert.equal(withK.subfields.find((s) => s.code === 'g').value, '20000718');
|
|
78
|
+
assert.equal(withK.subfields.find((s) => s.code === 'k').value,
|
|
79
|
+
'SRC_4:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// stripF884s early return: fewer than 2 Melinda f884s (0 or 1) means the
|
|
83
|
+
// record is returned unchanged (the merge/uniq pipeline never runs).
|
|
84
|
+
test('stripF884s returns the record unchanged when it has no Melinda f884s', () => {
|
|
85
|
+
const record = new MarcRecord({
|
|
86
|
+
leader: '03085cam a22006858i 4500',
|
|
87
|
+
fields: [
|
|
88
|
+
{tag: '001', value: '000000001'},
|
|
89
|
+
{tag: '035', ind1: ' ', ind2: ' ', subfields: [{code: 'a', value: '(FI-BTJ)12345'}]}
|
|
90
|
+
]
|
|
91
|
+
}, {subfieldValues: false});
|
|
92
|
+
|
|
93
|
+
const result = fixRecord(record, BIB_STRIP_F884S_SETTINGS);
|
|
94
|
+
assert.deepStrictEqual(result, record.toObject());
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('stripF884s returns the record unchanged when it has a single Melinda f884', () => {
|
|
98
|
+
const record = new MarcRecord({
|
|
99
|
+
leader: '03085cam a22006858i 4500',
|
|
100
|
+
fields: [
|
|
101
|
+
{tag: '001', value: '000000001'},
|
|
102
|
+
{
|
|
103
|
+
// a single Melinda f884: fewer than 2, so the field must survive
|
|
104
|
+
// untouched (no date-range merge, no removeFields/insertFields)
|
|
105
|
+
tag: '884',
|
|
106
|
+
ind1: ' ',
|
|
107
|
+
ind2: ' ',
|
|
108
|
+
subfields: [
|
|
109
|
+
{code: 'a', value: 'ONIX3 to MARC transformation'},
|
|
110
|
+
{code: 'g', value: '20220101'},
|
|
111
|
+
{
|
|
112
|
+
code: 'k',
|
|
113
|
+
value: 'SRC_4:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
|
114
|
+
},
|
|
115
|
+
{code: '5', value: 'MELINDA'}
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
]
|
|
119
|
+
}, {subfieldValues: false});
|
|
120
|
+
|
|
121
|
+
const result = fixRecord(record, BIB_STRIP_F884S_SETTINGS);
|
|
122
|
+
assert.deepStrictEqual(result, record.toObject());
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// generateMissingSIDs: an existing SID field is only counted when its $b is
|
|
126
|
+
// in fSIDFilters AND it has a $c. Anything else maps to undefined and is
|
|
127
|
+
// filtered out, so the SID is (re)generated from the f035.
|
|
128
|
+
test('fixRecord ignores an existing SID field whose $b is not in fSIDFilters and generates the SID from the f035', () => {
|
|
129
|
+
const record = new MarcRecord({
|
|
130
|
+
leader: '03085cam a22006858i 4500',
|
|
131
|
+
fields: [
|
|
132
|
+
{tag: '001', value: '000000001'},
|
|
133
|
+
{tag: '035', ind1: ' ', ind2: ' ', subfields: [{code: 'a', value: '(FI-BTJ)12345'}]},
|
|
134
|
+
{
|
|
135
|
+
// $b 'FI-KV' is NOT in fSIDFilters -> the field must be ignored,
|
|
136
|
+
// so the f035 (FI-BTJ)12345 still yields a generated SID
|
|
137
|
+
tag: 'SID',
|
|
138
|
+
ind1: ' ',
|
|
139
|
+
ind2: ' ',
|
|
140
|
+
subfields: [
|
|
141
|
+
{code: 'c', value: '12345'},
|
|
142
|
+
{code: 'b', value: 'FI-KV'}
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
}, {subfieldValues: false});
|
|
147
|
+
|
|
148
|
+
const result = fixRecord(record, {generateMissingSIDs: BIB_F035_TO_SID});
|
|
149
|
+
const sids = result.fields.filter((f) => f.tag === 'SID');
|
|
150
|
+
assert.equal(sids.length, 2);
|
|
151
|
+
const generated = sids.find((f) => f.subfields.some((s) => s.code === 'b' && s.value === 'FI-BTJ'));
|
|
152
|
+
assert.ok(generated, 'a new SID with $b FI-BTJ should have been generated');
|
|
153
|
+
assert.equal(generated.subfields.find((s) => s.code === 'c').value, '12345');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('fixRecord ignores an existing SID field with a matching $b but missing $c and generates the SID from the f035', () => {
|
|
157
|
+
const record = new MarcRecord({
|
|
158
|
+
leader: '03085cam a22006858i 4500',
|
|
159
|
+
fields: [
|
|
160
|
+
{tag: '001', value: '000000001'},
|
|
161
|
+
{tag: '035', ind1: ' ', ind2: ' ', subfields: [{code: 'a', value: '(FI-BTJ)12345'}]},
|
|
162
|
+
{
|
|
163
|
+
// $b 'FI-BTJ' IS in fSIDFilters, but there is no $c -> the field
|
|
164
|
+
// must be ignored, so the f035 still yields a generated SID
|
|
165
|
+
tag: 'SID',
|
|
166
|
+
ind1: ' ',
|
|
167
|
+
ind2: ' ',
|
|
168
|
+
subfields: [
|
|
169
|
+
{code: 'b', value: 'FI-BTJ'}
|
|
170
|
+
]
|
|
171
|
+
}
|
|
172
|
+
]
|
|
173
|
+
}, {subfieldValues: false});
|
|
174
|
+
|
|
175
|
+
const result = fixRecord(record, {generateMissingSIDs: BIB_F035_TO_SID});
|
|
176
|
+
const sids = result.fields.filter((f) => f.tag === 'SID');
|
|
177
|
+
assert.equal(sids.length, 2);
|
|
178
|
+
const generated = sids.find((f) => f.subfields.some((s) => s.code === 'c'));
|
|
179
|
+
assert.ok(generated, 'a generated SID with $c should exist');
|
|
180
|
+
assert.equal(generated.subfields.find((s) => s.code === 'b').value, 'FI-BTJ');
|
|
181
|
+
assert.equal(generated.subfields.find((s) => s.code === 'c').value, '12345');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test('fixRecord adds no SID when the f035 $a matches no f035Filter', () => {
|
|
185
|
+
const record = new MarcRecord({
|
|
186
|
+
leader: '03085cam a22006858i 4500',
|
|
187
|
+
fields: [
|
|
188
|
+
{tag: '001', value: '000000001'},
|
|
189
|
+
// $a does not match any f035Filter in BIB_F035_TO_SID, so
|
|
190
|
+
// f035ToSidInfo maps the field to undefined and nothing is generated
|
|
191
|
+
{tag: '035', ind1: ' ', ind2: ' ', subfields: [{code: 'a', value: '(NOT-IN-FILTERS)999'}]}
|
|
192
|
+
]
|
|
193
|
+
}, {subfieldValues: false});
|
|
194
|
+
|
|
195
|
+
const result = fixRecord(record, {generateMissingSIDs: BIB_F035_TO_SID});
|
|
196
|
+
assert.equal(result.fields.filter((f) => f.tag === 'SID').length, 0);
|
|
197
|
+
});
|
|
@@ -22,10 +22,28 @@ generateTests({
|
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
function callback({getFixture, settings = undefined}) {
|
|
25
|
-
if (
|
|
25
|
+
if (
|
|
26
|
+
settings !== undefined
|
|
27
|
+
&& typeof settings !== 'object'
|
|
28
|
+
&& fixSettings[settings] === undefined
|
|
29
|
+
) {
|
|
26
30
|
throw new Error(`There are no settings ${settings} defined!`);
|
|
27
31
|
}
|
|
28
|
-
|
|
32
|
+
let runSettings = fixSettings[settings] || settings;
|
|
33
|
+
// metadata.json is plain JSON, so f035Filters may contain string
|
|
34
|
+
// patterns which fix.js cannot use as-is; convert them to RegExp
|
|
35
|
+
if (runSettings && runSettings.generateMissingSIDs) {
|
|
36
|
+
runSettings = {
|
|
37
|
+
...runSettings,
|
|
38
|
+
generateMissingSIDs: {
|
|
39
|
+
...runSettings.generateMissingSIDs,
|
|
40
|
+
f035Filters: (runSettings.generateMissingSIDs.f035Filters || []).map(
|
|
41
|
+
filter => (typeof filter === 'string' ? new RegExp(filter) : filter)
|
|
42
|
+
),
|
|
43
|
+
fSIDFilters: runSettings.generateMissingSIDs.fSIDFilters
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
29
47
|
|
|
30
48
|
const record = new MarcRecord(getFixture('record.json'), {subfieldValues: false});
|
|
31
49
|
const expectedRecord = getFixture('expectedRecord.json');
|
|
@@ -31,7 +31,9 @@ describe('services/validate', () => {
|
|
|
31
31
|
]
|
|
32
32
|
});
|
|
33
33
|
const {_validationOptions, ...expectedRecordData} = expectedRecord;
|
|
34
|
-
|
|
34
|
+
const {record: actualRecord, ...actualRest} = result;
|
|
35
|
+
assert.deepStrictEqual(actualRest, rest);
|
|
36
|
+
assert.deepStrictEqual(actualRecord.toObject(), new MarcRecord(expectedRecordData, _validationOptions).toObject());
|
|
35
37
|
assert.equal(result.failed, false);
|
|
36
38
|
});
|
|
37
39
|
});
|
|
@@ -53,7 +55,9 @@ describe('services/validate', () => {
|
|
|
53
55
|
]
|
|
54
56
|
});
|
|
55
57
|
const {_validationOptions, ...expectedRecordData} = expectedRecord;
|
|
56
|
-
|
|
58
|
+
const {record: actualRecord, ...actualRest} = result;
|
|
59
|
+
assert.deepStrictEqual(actualRest, rest);
|
|
60
|
+
assert.deepStrictEqual(actualRecord.toObject(), new MarcRecord(expectedRecordData, _validationOptions).toObject());
|
|
57
61
|
assert.equal(result.failed, true);
|
|
58
62
|
});
|
|
59
63
|
});
|
|
@@ -89,9 +89,11 @@ async function callback({
|
|
|
89
89
|
const opResult = await mongoOperator.checkAndSetState(params);
|
|
90
90
|
debug(`checkAndSetState result: ${JSON.stringify(opResult)} (it should be: ${JSON.stringify(expectedOpResult)})}`);
|
|
91
91
|
|
|
92
|
+
// checkAndSetState now returns a discriminated result
|
|
93
|
+
// {status: 'updated' | 'timeout' | 'unchanged', queueItem} - compare status
|
|
92
94
|
// eslint-disable-next-line functional/no-conditional-statements
|
|
93
95
|
if (expectedOpResult !== undefined) {
|
|
94
|
-
assert.deepStrictEqual(opResult, expectedOpResult);
|
|
96
|
+
assert.deepStrictEqual(opResult.status, expectedOpResult);
|
|
95
97
|
}
|
|
96
98
|
await compareToFirstDbEntry({mongoFixtures, expectedResult, expectModificationTime, formatDates: true});
|
|
97
99
|
|
|
@@ -89,9 +89,11 @@ async function callback({
|
|
|
89
89
|
const opResult = await mongoOperator.checkAndSetImportJobState(params);
|
|
90
90
|
debug(`checkAndSetImportJobState result: ${JSON.stringify(opResult)} (it should be: ${JSON.stringify(expectedOpResult)})}`);
|
|
91
91
|
|
|
92
|
+
// checkAndSetImportJobState now returns a discriminated result
|
|
93
|
+
// {status: 'updated' | 'timeout' | 'unchanged', queueItem} - compare status
|
|
92
94
|
// eslint-disable-next-line functional/no-conditional-statements
|
|
93
95
|
if (expectedOpResult !== undefined) {
|
|
94
|
-
assert.deepStrictEqual(opResult, expectedOpResult);
|
|
96
|
+
assert.deepStrictEqual(opResult.status, expectedOpResult);
|
|
95
97
|
}
|
|
96
98
|
await compareToFirstDbEntry({mongoFixtures, expectedResult, expectModificationTime, formatDates: true});
|
|
97
99
|
} catch (error) {
|