cassproject 5.0.2 → 5.0.4
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 +8 -0
- package/package.json +11 -11
- package/src/org/cass/exporter/CSVExport.js +11 -1
- package/src/org/cass/importer/CTDLASNCSVConceptImport.js +178 -9
- package/src/org/cass/importer/CTDLASNCSVImport.js +173 -14
- package/src/test/1.EcAesCtrAsync.test.js +20 -20
- package/src/test/1.EcAesCtrAsyncWorker.test.js +19 -19
- package/src/test/1.EcRepository.l0.test.js +103 -103
- package/src/test/1.EcRepository.l1.test.js +93 -93
- package/src/test/1.EcRepository.l2.test.js +120 -120
package/README.md
CHANGED
|
@@ -50,6 +50,14 @@ Development unit tests presume you have a CaSS Repository running on `localhost:
|
|
|
50
50
|
|
|
51
51
|
# Changelog
|
|
52
52
|
|
|
53
|
+
## 5.0.4
|
|
54
|
+
* Updates to CEASN CSV Import validation
|
|
55
|
+
* Security updates.
|
|
56
|
+
|
|
57
|
+
## 5.0.3
|
|
58
|
+
* Updates to CEASN import/export.
|
|
59
|
+
* Security updates.
|
|
60
|
+
|
|
53
61
|
## 5.0.2
|
|
54
62
|
* Cosmetic and security updates (to dev dependencies)
|
|
55
63
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cassproject",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.4",
|
|
4
4
|
"description": "Competency and Skills Service",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
"multitest": "concurrently --kill-others-on-fail \"npm run test15\" \"npm run test14\" \"npm run test13\" \"npm run test12\"",
|
|
11
11
|
"testCassTest": "npm run testkill && docker run -d --platform linux/amd64 --network cass-net --name cass-test -p80:80 -e CASS_LOOPBACK cass-test && wait-on http://localhost/api/ping && npm run testNode18 && npm run testNode18Fips && npm run testNode16 && npm run testNode15 && npm run testNode14 && npm run testNode13 && npm run testNode12 && npm run testCypressEdge && npm run testCypress && npm run testkill",
|
|
12
12
|
"testDevHttps": "npm run testkill && docker run -d --platform linux/amd64 --network cass-net --name cass-test -p443:80 -e CASS_LOOPBACK -e HTTPS=true cassproject/cass:dev && wait-on https://localhost/api/ping && npm run testNode18HttpsFips && npm run testNode18Https && npm run testNode16Https && npm run testNode15Https && npm run testNode14Https && npm run testNode13Https && npm run testNode12Https && npm run testCypressEdgeHttps && npm run testCypressHttps",
|
|
13
|
-
"test16HttpsFips": "export CASS_LOOPBACK=https://cass-testsf16/api/|| set CASS_LOOPBACK=https://cass-testsf16/api/&& npm run testkillsf16 && docker run -d --platform linux/amd64 --network cass-net --name cass-testsf16 -p450:443 -e CASS_LOOPBACK -e HTTPS=true cassproject/cass:1.6.
|
|
14
|
-
"test16Https11Fips": "export CASS_LOOPBACK=https://cass-testsf1116/api/|| set CASS_LOOPBACK=https://cass-testsf1116/api/&& npm run testkillsf1116 && docker run -d --platform linux/amd64 --network cass-net --name cass-testsf1116 -p449:443 -e CASS_LOOPBACK -e HTTPS=true -e HTTP2=false cassproject/cass:1.6.
|
|
15
|
-
"test16Https": "export CASS_LOOPBACK=https://cass-tests16/api/|| set CASS_LOOPBACK=https://cass-tests16/api/&& npm run testkills16 && docker run -d --platform linux/amd64 --network cass-net --name cass-tests16 -p448:443 -e CASS_LOOPBACK -e HTTPS=true cassproject/cass:1.6.
|
|
16
|
-
"test16Https11": "export CASS_LOOPBACK=https://cass-tests1116/api/|| set CASS_LOOPBACK=https://cass-tests1116/api/&& npm run testkills1116 && docker run -d --platform linux/amd64 --network cass-net --name cass-tests1116 -p451:443 -e CASS_LOOPBACK -e HTTPS=true -e HTTP2=false cassproject/cass:1.6.
|
|
17
|
-
"test16Fips": "export CASS_LOOPBACK=http://cass-testf16/api/|| set CASS_LOOPBACK=http://cass-testf16/api/&& npm run testkillf16 && docker run -d --platform linux/amd64 --network cass-net -e CASS_LOOPBACK --name cass-testf16 -p83:80 cassproject/cass:1.6.
|
|
18
|
-
"test16": "export CASS_LOOPBACK=http://cass-test16/api/|| set CASS_LOOPBACK=http://cass-test16/api/&& npm run testkill16 && docker run -d --platform linux/amd64 --network cass-net -e CASS_LOOPBACK --name cass-test16 -p82:80 cassproject/cass:1.6.
|
|
13
|
+
"test16HttpsFips": "export CASS_LOOPBACK=https://cass-testsf16/api/|| set CASS_LOOPBACK=https://cass-testsf16/api/&& npm run testkillsf16 && docker run -d --platform linux/amd64 --network cass-net --name cass-testsf16 -p450:443 -e CASS_LOOPBACK -e HTTPS=true cassproject/cass:1.6.7 && wait-on https://localhost:450/api/ping && concurrently --kill-others-on-fail \"npm run testCypressHttps\" \"npm run testNode24Https\" \"npm run testNode24HttpsFips\" \"npm run testNode24HttpsForceFips\" \"npm run testNode22Https\" \"npm run testNode22HttpsFips\" \"npm run testNode22HttpsForceFips\" \"npm run testNode20Https\" \"npm run testNode20HttpsFips\" \"npm run testNode20HttpsForceFips\" \"npm run testNode18Https\" && npm run testkillsf16",
|
|
14
|
+
"test16Https11Fips": "export CASS_LOOPBACK=https://cass-testsf1116/api/|| set CASS_LOOPBACK=https://cass-testsf1116/api/&& npm run testkillsf1116 && docker run -d --platform linux/amd64 --network cass-net --name cass-testsf1116 -p449:443 -e CASS_LOOPBACK -e HTTPS=true -e HTTP2=false cassproject/cass:1.6.7 && wait-on https://localhost:449/api/ping && concurrently --kill-others-on-fail \"npm run testCypressHttps\" \"npm run testNode24Https\" \"npm run testNode24HttpsFips\" \"npm run testNode24HttpsForceFips\" \"npm run testNode22Https\" \"npm run testNode22HttpsFips\" \"npm run testNode22HttpsForceFips\" \"npm run testNode20Https\" \"npm run testNode20HttpsFips\" \"npm run testNode20HttpsForceFips\" \"npm run testNode18Https\" && npm run testkillsf1116",
|
|
15
|
+
"test16Https": "export CASS_LOOPBACK=https://cass-tests16/api/|| set CASS_LOOPBACK=https://cass-tests16/api/&& npm run testkills16 && docker run -d --platform linux/amd64 --network cass-net --name cass-tests16 -p448:443 -e CASS_LOOPBACK -e HTTPS=true cassproject/cass:1.6.7 && wait-on https://localhost:448/api/ping && concurrently --kill-others-on-fail \"npm run testCypressHttps\" \"npm run testNode24Https\" \"npm run testNode24HttpsFips\" \"npm run testNode22Https\" \"npm run testNode22HttpsFips\" \"npm run testNode20Https\" \"npm run testNode20HttpsFips\" \"npm run testNode18Https\" && npm run testkills16",
|
|
16
|
+
"test16Https11": "export CASS_LOOPBACK=https://cass-tests1116/api/|| set CASS_LOOPBACK=https://cass-tests1116/api/&& npm run testkills1116 && docker run -d --platform linux/amd64 --network cass-net --name cass-tests1116 -p451:443 -e CASS_LOOPBACK -e HTTPS=true -e HTTP2=false cassproject/cass:1.6.7 && wait-on https://localhost:451/api/ping && concurrently --kill-others-on-fail \"npm run testCypressHttps\" \"npm run testNode24Https\" \"npm run testNode24HttpsFips\" \"npm run testNode22Https\" \"npm run testNode22HttpsFips\" \"npm run testNode20Https\" \"npm run testNode20HttpsFips\" \"npm run testNode18Https\" && npm run testkills1116",
|
|
17
|
+
"test16Fips": "export CASS_LOOPBACK=http://cass-testf16/api/|| set CASS_LOOPBACK=http://cass-testf16/api/&& npm run testkillf16 && docker run -d --platform linux/amd64 --network cass-net -e CASS_LOOPBACK --name cass-testf16 -p83:80 cassproject/cass:1.6.7 && wait-on http://localhost:83/api/ping && concurrently --kill-others-on-fail \"npm run testCypress\" \"npm run testNode24\" \"npm run testNode24Fips\" \"npm run testNode24ForceFips\" \"npm run testNode22\" \"npm run testNode22Fips\" \"npm run testNode22ForceFips\" \"npm run testNode20\" \"npm run testNode20Fips\" \"npm run testNode20ForceFips\" \"npm run testNode18\" && npm run testkillf16",
|
|
18
|
+
"test16": "export CASS_LOOPBACK=http://cass-test16/api/|| set CASS_LOOPBACK=http://cass-test16/api/&& npm run testkill16 && docker run -d --platform linux/amd64 --network cass-net -e CASS_LOOPBACK --name cass-test16 -p82:80 cassproject/cass:1.6.7 && wait-on http://localhost:82/api/ping && concurrently --kill-others-on-fail \"npm run testCypress\" \"npm run testNode24\" \"npm run testNode24Fips\" \"npm run testNode22\" \"npm run testNode22Fips\" \"npm run testNode20\" \"npm run testNode20Fips\" \"npm run testNode18\" && npm run testkill16",
|
|
19
19
|
"test15HttpsFips": "export TESTLEVEL=15|| set TESTLEVEL=15&& export CASS_LOOPBACK=https://cass-testsf15/api/|| set CASS_LOOPBACK=https://cass-testsf15/api/&& npm run testkillsf15 && docker run -d --platform linux/amd64 --network cass-net --name cass-testsf15 -p446:443 -e CASS_LOOPBACK -e HTTPS=true cassproject/cass:1.5.75 && wait-on https://localhost:446/api/ping && concurrently --kill-others-on-fail \"npm run testCypressHttps\" \"npm run testNode24Https\" \"npm run testNode24HttpsFips\" \"npm run testNode22Https\" \"npm run testNode22HttpsFips\" \"npm run testNode22HttpsForceFips\" \"npm run testNode20Https\" \"npm run testNode20HttpsFips\" \"npm run testNode20HttpsForceFips\" \"npm run testNode18Https\" && npm run testkillsf15",
|
|
20
20
|
"test15Https11Fips": "export TESTLEVEL=15|| set TESTLEVEL=15&& export CASS_LOOPBACK=https://cass-testsf1115/api/|| set CASS_LOOPBACK=https://cass-testsf1115/api/&& npm run testkillsf1115 && docker run -d --platform linux/amd64 --network cass-net --name cass-testsf1115 -p447:443 -e CASS_LOOPBACK -e HTTPS=true -e HTTP2=false cassproject/cass:1.5.75 && wait-on https://localhost:447/api/ping && concurrently --kill-others-on-fail \"npm run testCypressHttps\" \"npm run testNode24Https\" \"npm run testNode24HttpsFips\" \"npm run testNode24HttpsForceFips\" \"npm run testNode22Https\" \"npm run testNode22HttpsFips\" \"npm run testNode22HttpsForceFips\" \"npm run testNode20Https\" \"npm run testNode20HttpsFips\" \"npm run testNode20HttpsForceFips\" \"npm run testNode18Https\" && npm run testkillsf1115",
|
|
21
21
|
"test15Https": "export TESTLEVEL=15|| set TESTLEVEL=15&& export CASS_LOOPBACK=https://cass-tests15/api/|| set CASS_LOOPBACK=https://cass-tests15/api/&& npm run testkills15 && docker run -d --platform linux/amd64 --network cass-net --name cass-tests15 -p444:443 -e CASS_LOOPBACK -e HTTPS=true cassproject/cass:1.5.75 && wait-on https://localhost:444/api/ping && concurrently --kill-others-on-fail \"npm run testCypressHttps\" \"npm run testNode24Https\" \"npm run testNode24HttpsFips\" \"npm run testNode22Https\" \"npm run testNode22HttpsFips\" \"npm run testNode20Https\" \"npm run testNode20HttpsFips\" \"npm run testNode18Https\" && npm run testkills15",
|
|
@@ -188,10 +188,10 @@
|
|
|
188
188
|
"chai": "4.5.0",
|
|
189
189
|
"concurrently": "^9.2.1",
|
|
190
190
|
"convert-hrtime": "^5.0.0",
|
|
191
|
-
"cypress": "^15.
|
|
191
|
+
"cypress": "^15.5.0",
|
|
192
192
|
"cypress-fail-fast": "^7.1.1",
|
|
193
|
-
"eslint": "^9.
|
|
194
|
-
"fake-indexeddb": "^6.2.
|
|
193
|
+
"eslint": "^9.39.1",
|
|
194
|
+
"fake-indexeddb": "^6.2.4",
|
|
195
195
|
"mocha": "^10.8.2",
|
|
196
196
|
"node-polyfill-webpack-plugin": "^4.1.0",
|
|
197
197
|
"nodemon": "^3.1.10",
|
|
@@ -199,7 +199,7 @@
|
|
|
199
199
|
"sinon": "^19.0.5",
|
|
200
200
|
"url-polyfill": "^1.1.14",
|
|
201
201
|
"wait-on": "^8.0.5",
|
|
202
|
-
"webpack": "^5.
|
|
202
|
+
"webpack": "^5.102.1",
|
|
203
203
|
"webpack-cli": "^5.1.4"
|
|
204
204
|
},
|
|
205
205
|
"packageManager": "npm@11.3.0+sha512.96eb611483f49c55f7fa74df61b588de9e213f80a256728e6798ddc67176c7b07e4a1cfc7de8922422cbce02543714367037536955221fa451b0c4fefaf20c66"
|
|
@@ -23,6 +23,7 @@ module.exports = class CSVExport extends Exporter {
|
|
|
23
23
|
compExport.downloadCSV(fileName);
|
|
24
24
|
}
|
|
25
25
|
static exportCTDLASN(json, name) {
|
|
26
|
+
console.log('export CTDL');
|
|
26
27
|
let objects = [];
|
|
27
28
|
CSVExport.findGraphs(json, objects);
|
|
28
29
|
CSVExport.exportObjects(objects, name + ".csv", true);
|
|
@@ -202,7 +203,16 @@ module.exports = class CSVExport extends Exporter {
|
|
|
202
203
|
display += props2[prop2][prop3] + "|";
|
|
203
204
|
}
|
|
204
205
|
} else {
|
|
205
|
-
|
|
206
|
+
if (prop === 'ceterms:versionIdentifier') {
|
|
207
|
+
if (props2[prop2]['ceterms:identifierValueCode'])
|
|
208
|
+
display += props2[prop2]['ceterms:identifierValueCode'] + "~";
|
|
209
|
+
if (props2[prop2]['ceterms:identifierTypeName'])
|
|
210
|
+
display += Object.values(props2[prop2]['ceterms:identifierTypeName'])[0] + "~";
|
|
211
|
+
if (props2[prop2]['ceterms:identifierType'])
|
|
212
|
+
display += props2[prop2]['ceterms:identifierType'] + "|";
|
|
213
|
+
} else {
|
|
214
|
+
display += props2[prop2] + "|";
|
|
215
|
+
}
|
|
206
216
|
}
|
|
207
217
|
}
|
|
208
218
|
display = display.substring(0, display.length - 1);
|
|
@@ -109,7 +109,8 @@ module.exports = class CTDLASNCSVConceptImport {
|
|
|
109
109
|
ceo,
|
|
110
110
|
endpoint,
|
|
111
111
|
eim,
|
|
112
|
-
progressionsFlag
|
|
112
|
+
progressionsFlag,
|
|
113
|
+
validationRules
|
|
113
114
|
) {
|
|
114
115
|
if (eim === undefined || eim == null)
|
|
115
116
|
eim = EcIdentityManager.default;
|
|
@@ -123,7 +124,7 @@ module.exports = class CTDLASNCSVConceptImport {
|
|
|
123
124
|
failure("Invalid file type");
|
|
124
125
|
}
|
|
125
126
|
if (progressionsFlag) {
|
|
126
|
-
return this.importProgressions(repo, file, success, failure, ceo, endpoint, eim);
|
|
127
|
+
return this.importProgressions(repo, file, success, failure, ceo, endpoint, eim, validationRules);
|
|
127
128
|
}
|
|
128
129
|
Papa.parse(file, {
|
|
129
130
|
header: true,
|
|
@@ -139,21 +140,54 @@ module.exports = class CTDLASNCSVConceptImport {
|
|
|
139
140
|
} catch (e) {
|
|
140
141
|
console.error('Error trimming data', e);
|
|
141
142
|
}
|
|
143
|
+
|
|
144
|
+
// Validate hierarchy before processing (only if validation rules provided)
|
|
145
|
+
if (validationRules && validationRules.validateHierarchy !== false) {
|
|
146
|
+
const hierarchyError = CTDLASNCSVConceptImport.validateConceptHierarchy(
|
|
147
|
+
tabularData,
|
|
148
|
+
validationRules.hierarchyRules
|
|
149
|
+
);
|
|
150
|
+
if (hierarchyError) {
|
|
151
|
+
failure(hierarchyError);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
142
155
|
const terms = JSON.parse(JSON.stringify((await EcRemote.getExpectingObject("https://schema.cassproject.org/0.4/jsonld1.1/ceasn2cassConceptsTerms"))));
|
|
143
156
|
let schemeArray = [];
|
|
144
157
|
let concepts = [];
|
|
145
158
|
for (let each = 0; each < tabularData.length; each++) {
|
|
146
159
|
let pretranslatedE = tabularData[each];
|
|
147
|
-
//
|
|
148
|
-
if (!pretranslatedE["@
|
|
160
|
+
// Skip extra lines if found in file
|
|
161
|
+
if (!pretranslatedE || !pretranslatedE["@type"]) {
|
|
149
162
|
continue;
|
|
150
163
|
}
|
|
164
|
+
if (!pretranslatedE["@id"]) {
|
|
165
|
+
failure(`Row ${each + 2}: is missing an @id`);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (!pretranslatedE["@id"].startsWith('http') && !pretranslatedE["@id"].startsWith('ce-')) {
|
|
169
|
+
failure(`row ${i + 2}: @id must be a valid URI or start with 'ce-'`)
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
151
172
|
if (
|
|
152
173
|
pretranslatedE["@type"].toLowerCase().startsWith('sample') || pretranslatedE["@type"].toLowerCase().startsWith('instruction')
|
|
153
174
|
) {
|
|
154
175
|
continue;
|
|
155
176
|
}
|
|
156
177
|
if (pretranslatedE["@type"] == "skos:ConceptScheme") {
|
|
178
|
+
// Validate required properties (only if validation rules provided)
|
|
179
|
+
if (validationRules && validationRules.requiredProps) {
|
|
180
|
+
const validationError = CTDLASNCSVConceptImport.validateRequiredProperties(
|
|
181
|
+
pretranslatedE,
|
|
182
|
+
"skos:ConceptScheme",
|
|
183
|
+
each + 2,
|
|
184
|
+
validationRules.requiredProps
|
|
185
|
+
);
|
|
186
|
+
if (validationError) {
|
|
187
|
+
failure(validationError);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
157
191
|
let translator = new EcLinkedData(null, null);
|
|
158
192
|
translator.copyFrom(pretranslatedE);
|
|
159
193
|
CTDLASNCSVImport.cleanUpTranslator(
|
|
@@ -223,6 +257,19 @@ module.exports = class CTDLASNCSVConceptImport {
|
|
|
223
257
|
setVersionIdentifier(f);
|
|
224
258
|
schemeArray.push(f);
|
|
225
259
|
} else if (pretranslatedE["@type"] == "skos:Concept") {
|
|
260
|
+
// Validate required properties (only if validation rules provided)
|
|
261
|
+
if (validationRules && validationRules.requiredProps) {
|
|
262
|
+
const validationError = CTDLASNCSVConceptImport.validateRequiredProperties(
|
|
263
|
+
pretranslatedE,
|
|
264
|
+
"skos:Concept",
|
|
265
|
+
each + 2,
|
|
266
|
+
validationRules.requiredProps
|
|
267
|
+
);
|
|
268
|
+
if (validationError) {
|
|
269
|
+
failure(validationError);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
226
273
|
let translator = new EcLinkedData(null, null);
|
|
227
274
|
translator.copyFrom(pretranslatedE);
|
|
228
275
|
CTDLASNCSVImport.cleanUpTranslator(
|
|
@@ -251,6 +298,7 @@ module.exports = class CTDLASNCSVConceptImport {
|
|
|
251
298
|
let f = new EcConcept();
|
|
252
299
|
f.copyFrom(e);
|
|
253
300
|
if (e["id"] == null) {
|
|
301
|
+
failure(`Row ${each + 2}: Concept is missing an id`);
|
|
254
302
|
return;
|
|
255
303
|
}
|
|
256
304
|
if (
|
|
@@ -364,10 +412,11 @@ module.exports = class CTDLASNCSVConceptImport {
|
|
|
364
412
|
pretranslatedE["@type"] == null ||
|
|
365
413
|
pretranslatedE["@type"] == ""
|
|
366
414
|
) {
|
|
415
|
+
failure(`Row ${each + 2}: Missing or empty @type`);
|
|
367
416
|
return;
|
|
368
417
|
} else {
|
|
369
|
-
|
|
370
|
-
|
|
418
|
+
failure(
|
|
419
|
+
`Row ${each + 2}: Found unknown type: ` + pretranslatedE["@type"]
|
|
371
420
|
);
|
|
372
421
|
return;
|
|
373
422
|
}
|
|
@@ -384,7 +433,8 @@ module.exports = class CTDLASNCSVConceptImport {
|
|
|
384
433
|
failure,
|
|
385
434
|
ceo,
|
|
386
435
|
endpoint,
|
|
387
|
-
eim
|
|
436
|
+
eim,
|
|
437
|
+
validationRules
|
|
388
438
|
) {
|
|
389
439
|
Papa.parse(file, {
|
|
390
440
|
header: true,
|
|
@@ -400,17 +450,50 @@ module.exports = class CTDLASNCSVConceptImport {
|
|
|
400
450
|
} catch (e) {
|
|
401
451
|
console.error('Error trimming data', e);
|
|
402
452
|
}
|
|
453
|
+
|
|
454
|
+
// Validate hierarchy before processing (only if validation rules provided)
|
|
455
|
+
if (validationRules && validationRules.validateHierarchy !== false) {
|
|
456
|
+
const hierarchyError = CTDLASNCSVConceptImport.validateConceptHierarchy(
|
|
457
|
+
tabularData,
|
|
458
|
+
validationRules.hierarchyRules
|
|
459
|
+
);
|
|
460
|
+
if (hierarchyError) {
|
|
461
|
+
failure(hierarchyError);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
403
465
|
const terms = JSON.parse(JSON.stringify((await EcRemote.getExpectingObject("https://schema.cassproject.org/0.4/jsonld1.1/ceasn2cassConceptsTerms"))));
|
|
404
466
|
let schemeArray = [];
|
|
405
467
|
let concepts = [];
|
|
406
468
|
for (let each = 0; each < tabularData.length; each++) {
|
|
407
469
|
let pretranslatedE = tabularData[each];
|
|
470
|
+
// Skip extra lines if found in file
|
|
471
|
+
if (!pretranslatedE || !pretranslatedE["@type"]) {
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
if (!pretranslatedE["@id"]) {
|
|
475
|
+
failure(`Row ${each + 2}: is missing an @id`);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
408
478
|
if (
|
|
409
479
|
pretranslatedE["@type"].toLowerCase().startsWith('sample') || pretranslatedE["@type"].toLowerCase().startsWith('instruction')
|
|
410
480
|
) {
|
|
411
481
|
continue;
|
|
412
482
|
}
|
|
413
483
|
if (pretranslatedE["@type"] == "asn:ProgressionModel") {
|
|
484
|
+
// Validate required properties (only if validation rules provided)
|
|
485
|
+
if (validationRules && validationRules.requiredProps) {
|
|
486
|
+
const validationError = CTDLASNCSVConceptImport.validateRequiredProperties(
|
|
487
|
+
pretranslatedE,
|
|
488
|
+
"asn:ProgressionModel",
|
|
489
|
+
each + 2,
|
|
490
|
+
validationRules.requiredProps
|
|
491
|
+
);
|
|
492
|
+
if (validationError) {
|
|
493
|
+
failure(validationError);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
414
497
|
let translator = new EcLinkedData(null, null);
|
|
415
498
|
translator.copyFrom(pretranslatedE);
|
|
416
499
|
CTDLASNCSVImport.cleanUpTranslator(
|
|
@@ -476,6 +559,19 @@ module.exports = class CTDLASNCSVConceptImport {
|
|
|
476
559
|
f.subType = "Progression";
|
|
477
560
|
schemeArray.push(f);
|
|
478
561
|
} else if (pretranslatedE["@type"] == "asn:ProgressionLevel") {
|
|
562
|
+
// Validate required properties (only if validation rules provided)
|
|
563
|
+
if (validationRules && validationRules.requiredProps) {
|
|
564
|
+
const validationError = CTDLASNCSVConceptImport.validateRequiredProperties(
|
|
565
|
+
pretranslatedE,
|
|
566
|
+
"asn:ProgressionLevel",
|
|
567
|
+
each + 2,
|
|
568
|
+
validationRules.requiredProps
|
|
569
|
+
);
|
|
570
|
+
if (validationError) {
|
|
571
|
+
failure(validationError);
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
479
575
|
let translator = new EcLinkedData(null, null);
|
|
480
576
|
translator.copyFrom(pretranslatedE);
|
|
481
577
|
CTDLASNCSVImport.cleanUpTranslator(
|
|
@@ -504,6 +600,7 @@ module.exports = class CTDLASNCSVConceptImport {
|
|
|
504
600
|
let f = new EcConcept();
|
|
505
601
|
f.copyFrom(e);
|
|
506
602
|
if (e["id"] == null) {
|
|
603
|
+
failure(`Row ${each + 2}: Concept is missing an id`);
|
|
507
604
|
return;
|
|
508
605
|
}
|
|
509
606
|
if (
|
|
@@ -583,10 +680,11 @@ module.exports = class CTDLASNCSVConceptImport {
|
|
|
583
680
|
pretranslatedE["@type"] == null ||
|
|
584
681
|
pretranslatedE["@type"] == ""
|
|
585
682
|
) {
|
|
683
|
+
failure(`Row ${each + 2}: Missing or empty @type`);
|
|
586
684
|
return;
|
|
587
685
|
} else {
|
|
588
|
-
|
|
589
|
-
|
|
686
|
+
failure(
|
|
687
|
+
`Row ${each + 2}: Found unknown type: ` + pretranslatedE["@type"]
|
|
590
688
|
);
|
|
591
689
|
return;
|
|
592
690
|
}
|
|
@@ -596,4 +694,75 @@ module.exports = class CTDLASNCSVConceptImport {
|
|
|
596
694
|
error: failure
|
|
597
695
|
});
|
|
598
696
|
}
|
|
697
|
+
|
|
698
|
+
static validateRequiredProperties(obj, type, rowIndex, requiredProps) {
|
|
699
|
+
// Use provided validation rules or fall back to defaults
|
|
700
|
+
if (!requiredProps) {
|
|
701
|
+
return 'Required properties not defined.'
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
const props = requiredProps[type];
|
|
705
|
+
if (!props) return null;
|
|
706
|
+
|
|
707
|
+
const missing = [];
|
|
708
|
+
for (const prop of props) {
|
|
709
|
+
if (!obj[prop] || (typeof obj[prop] === 'string' && obj[prop].trim() === '')) {
|
|
710
|
+
missing.push(prop);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
if (missing.length > 0) {
|
|
715
|
+
return `Row ${rowIndex}: Missing required properties for ${type}: ${missing.join(', ')}`;
|
|
716
|
+
}
|
|
717
|
+
return null;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
static validateConceptHierarchy(tabularData, hierarchyRules) {
|
|
721
|
+
// Use provided hierarchy rules or fall back to defaults
|
|
722
|
+
if (!hierarchyRules) {
|
|
723
|
+
return 'Hierarchy rules not defined.'
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// Check each type defined in hierarchy rules
|
|
727
|
+
for (const [type, rules] of Object.entries(hierarchyRules)) {
|
|
728
|
+
const hasType = tabularData.some(r => r && r["@type"] === type);
|
|
729
|
+
if (!hasType) continue;
|
|
730
|
+
|
|
731
|
+
let hasRequiredProp = false;
|
|
732
|
+
let hasChildProp = false;
|
|
733
|
+
|
|
734
|
+
for (const row of tabularData) {
|
|
735
|
+
if (!row || !row["@type"]) continue;
|
|
736
|
+
|
|
737
|
+
// Check if this row has any of the required properties
|
|
738
|
+
if (row["@type"] === type && rules.requiredProperties) {
|
|
739
|
+
for (const prop of rules.requiredProperties) {
|
|
740
|
+
if (row[prop]) {
|
|
741
|
+
hasRequiredProp = true;
|
|
742
|
+
break;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// Check if any row has child properties
|
|
748
|
+
if (rules.childProperties) {
|
|
749
|
+
for (const prop of rules.childProperties) {
|
|
750
|
+
if (row[prop]) {
|
|
751
|
+
hasChildProp = true;
|
|
752
|
+
break;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
if (hasRequiredProp && hasChildProp) break;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// Validate that at least one of the hierarchy properties exists
|
|
761
|
+
if (!hasRequiredProp && !hasChildProp) {
|
|
762
|
+
return rules.errorMessage || `CSV must contain hierarchy properties for ${type}`;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
return null;
|
|
767
|
+
}
|
|
599
768
|
};
|