cassproject 5.0.17 → 5.0.19
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/package.json +5 -5
- package/src/com/eduworks/ec/remote/EcRemote.js +22 -2
- package/src/org/cass/importer/CTDLASNCSVConceptImport.js +56 -0
- package/src/org/cass/importer/CTDLASNCSVImport.js +98 -0
- package/src/org/cassproject/ebac/identity/remote/EcRemoteIdentityManager.js +1 -0
- package/src/org/cassproject/ebac/repository/EcRepository.js +1 -4
- package/src/org/cassproject/schema/general/EcRemoteLinkedData.js +14 -0
- package/src/test/1.CTDLASNCSVImport.validateRegistryUrls.test.js +100 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cassproject",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.19",
|
|
4
4
|
"description": "Competency and Skills Service",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -184,20 +184,20 @@
|
|
|
184
184
|
},
|
|
185
185
|
"homepage": "https://github.com/cassproject/cass-npm#readme",
|
|
186
186
|
"devDependencies": {
|
|
187
|
-
"@cypress/webpack-preprocessor": "^7.1.
|
|
187
|
+
"@cypress/webpack-preprocessor": "^7.1.2",
|
|
188
188
|
"chai": "^4.5.0",
|
|
189
189
|
"concurrently": "^10.0.4",
|
|
190
190
|
"cypress": "15.19.0",
|
|
191
191
|
"cypress-fail-fast": "^8.1.0",
|
|
192
|
-
"eslint": "^10.8.
|
|
192
|
+
"eslint": "^10.8.1",
|
|
193
193
|
"fake-indexeddb": "^6.2.5",
|
|
194
|
-
"mocha": "^11.
|
|
194
|
+
"mocha": "^11.8.0",
|
|
195
195
|
"node-polyfill-webpack-plugin": "^4.1.0",
|
|
196
196
|
"nodemon": "^3.1.14",
|
|
197
197
|
"nyc": "^18.0.0",
|
|
198
198
|
"sinon": "^21.1.2",
|
|
199
199
|
"wait-on": "^9.1.0",
|
|
200
|
-
"webpack": "^5.109.
|
|
200
|
+
"webpack": "^5.109.2"
|
|
201
201
|
},
|
|
202
202
|
"packageManager": "npm@11.3.0+sha512.96eb611483f49c55f7fa74df61b588de9e213f80a256728e6798ddc67176c7b07e4a1cfc7de8922422cbce02543714367037536955221fa451b0c4fefaf20c66"
|
|
203
203
|
}
|
|
@@ -10,16 +10,27 @@ if (typeof process === 'object') {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
|
+
let httpFormData = typeof FormData !== 'undefined' ? FormData : undefined;
|
|
13
14
|
if (isNode)
|
|
14
|
-
{
|
|
15
|
-
let undici =
|
|
15
|
+
{
|
|
16
|
+
let undici = null;
|
|
17
|
+
try {
|
|
18
|
+
undici = eval("require('undici');");
|
|
19
|
+
} catch (ex) {
|
|
20
|
+
//undici is optional. Fall back to the platform's fetch and FormData.
|
|
21
|
+
}
|
|
16
22
|
if (undici != null)
|
|
17
23
|
{
|
|
18
24
|
var {setGlobalDispatcher,Agent,fetch} = undici; //NOSONAR - Intentional use of var to allow for global scope.
|
|
25
|
+
//fetch and FormData must come from the same implementation. undici's fetch
|
|
26
|
+
//does not recognize the platform's FormData and serializes it as text/plain.
|
|
27
|
+
httpFormData = undici.FormData;
|
|
19
28
|
setGlobalDispatcher(new Agent({
|
|
20
29
|
allowH2: process.env.HTTP2 != null ? process.env.HTTP2.trim() == 'true' : true
|
|
21
30
|
}))
|
|
22
31
|
}
|
|
32
|
+
else if (typeof globalThis.fetch === 'function')
|
|
33
|
+
var fetch = globalThis.fetch; //NOSONAR - Intentional use of var; the destructuring above hoists `fetch` to module scope, which would otherwise shadow the global with undefined.
|
|
23
34
|
}
|
|
24
35
|
if (typeof window !== 'undefined' && window.fetch != null)
|
|
25
36
|
var fetch = window.fetch; //NOSONAR - Intentional use of var to allow for global scope.
|
|
@@ -50,6 +61,15 @@ const { cassPromisify } = require("../promises/helpers");
|
|
|
50
61
|
* @module com.eduworks.ec
|
|
51
62
|
*/
|
|
52
63
|
module.exports = class EcRemote {
|
|
64
|
+
/**
|
|
65
|
+
* The FormData implementation paired with the fetch implementation EcRemote uses.
|
|
66
|
+
* Always construct multi-part bodies destined for EcRemote.post* with this,
|
|
67
|
+
* not the platform global, or bodies will be serialized as text/plain.
|
|
68
|
+
*
|
|
69
|
+
* @property FormData
|
|
70
|
+
* @static
|
|
71
|
+
*/
|
|
72
|
+
static FormData = httpFormData;
|
|
53
73
|
//See https://github.com/nodejs/node/issues/47130 -- This is a workaround for a bug that causes http 1.1 keep-alive from throwing an error.
|
|
54
74
|
static leTired() {
|
|
55
75
|
if (isNode)
|
|
@@ -207,6 +207,20 @@ module.exports = class CTDLASNCSVConceptImport {
|
|
|
207
207
|
continue;
|
|
208
208
|
}
|
|
209
209
|
}
|
|
210
|
+
// Validate registry URL fields (only if url rules provided)
|
|
211
|
+
if (validationRules && validationRules.urlRules) {
|
|
212
|
+
const urlErrors = CTDLASNCSVImport.validateRegistryUrls(
|
|
213
|
+
pretranslatedE,
|
|
214
|
+
"skos:ConceptScheme",
|
|
215
|
+
each + 2,
|
|
216
|
+
validationRules.urlRules
|
|
217
|
+
);
|
|
218
|
+
if (urlErrors) {
|
|
219
|
+
errors.push(...urlErrors);
|
|
220
|
+
rowsWithErrors.add(each);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
210
224
|
let translator = new EcLinkedData(null, null);
|
|
211
225
|
translator.copyFrom(pretranslatedE);
|
|
212
226
|
CTDLASNCSVImport.cleanUpTranslator(
|
|
@@ -294,6 +308,20 @@ module.exports = class CTDLASNCSVConceptImport {
|
|
|
294
308
|
continue;
|
|
295
309
|
}
|
|
296
310
|
}
|
|
311
|
+
// Validate registry URL fields (only if url rules provided)
|
|
312
|
+
if (validationRules && validationRules.urlRules) {
|
|
313
|
+
const urlErrors = CTDLASNCSVImport.validateRegistryUrls(
|
|
314
|
+
pretranslatedE,
|
|
315
|
+
"skos:Concept",
|
|
316
|
+
each + 2,
|
|
317
|
+
validationRules.urlRules
|
|
318
|
+
);
|
|
319
|
+
if (urlErrors) {
|
|
320
|
+
errors.push(...urlErrors);
|
|
321
|
+
rowsWithErrors.add(each);
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
297
325
|
let translator = new EcLinkedData(null, null);
|
|
298
326
|
translator.copyFrom(pretranslatedE);
|
|
299
327
|
CTDLASNCSVImport.cleanUpTranslator(
|
|
@@ -583,6 +611,20 @@ module.exports = class CTDLASNCSVConceptImport {
|
|
|
583
611
|
continue;
|
|
584
612
|
}
|
|
585
613
|
}
|
|
614
|
+
// Validate registry URL fields (only if url rules provided)
|
|
615
|
+
if (validationRules && validationRules.urlRules) {
|
|
616
|
+
const urlErrors = CTDLASNCSVImport.validateRegistryUrls(
|
|
617
|
+
pretranslatedE,
|
|
618
|
+
"asn:ProgressionModel",
|
|
619
|
+
each + 2,
|
|
620
|
+
validationRules.urlRules
|
|
621
|
+
);
|
|
622
|
+
if (urlErrors) {
|
|
623
|
+
errors.push(...urlErrors);
|
|
624
|
+
rowsWithErrors.add(each);
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
586
628
|
let translator = new EcLinkedData(null, null);
|
|
587
629
|
translator.copyFrom(pretranslatedE);
|
|
588
630
|
CTDLASNCSVImport.cleanUpTranslator(
|
|
@@ -666,6 +708,20 @@ module.exports = class CTDLASNCSVConceptImport {
|
|
|
666
708
|
continue;
|
|
667
709
|
}
|
|
668
710
|
}
|
|
711
|
+
// Validate registry URL fields (only if url rules provided)
|
|
712
|
+
if (validationRules && validationRules.urlRules) {
|
|
713
|
+
const urlErrors = CTDLASNCSVImport.validateRegistryUrls(
|
|
714
|
+
pretranslatedE,
|
|
715
|
+
"asn:ProgressionLevel",
|
|
716
|
+
each + 2,
|
|
717
|
+
validationRules.urlRules
|
|
718
|
+
);
|
|
719
|
+
if (urlErrors) {
|
|
720
|
+
errors.push(...urlErrors);
|
|
721
|
+
rowsWithErrors.add(each);
|
|
722
|
+
continue;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
669
725
|
let translator = new EcLinkedData(null, null);
|
|
670
726
|
translator.copyFrom(pretranslatedE);
|
|
671
727
|
CTDLASNCSVImport.cleanUpTranslator(
|
|
@@ -255,6 +255,20 @@ module.exports = class CTDLASNCSVImport {
|
|
|
255
255
|
continue;
|
|
256
256
|
}
|
|
257
257
|
}
|
|
258
|
+
// Validate registry URL fields (only if url rules provided)
|
|
259
|
+
if (validationRules && validationRules.urlRules) {
|
|
260
|
+
const urlErrors = CTDLASNCSVImport.validateRegistryUrls(
|
|
261
|
+
pretranslatedE,
|
|
262
|
+
"ceasn:CompetencyFramework",
|
|
263
|
+
i + 2,
|
|
264
|
+
validationRules.urlRules
|
|
265
|
+
);
|
|
266
|
+
if (urlErrors) {
|
|
267
|
+
errors.push(...urlErrors);
|
|
268
|
+
rowsWithErrors.add(i);
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
258
272
|
|
|
259
273
|
let translator = new EcLinkedData(null, null);
|
|
260
274
|
translator.copyFrom(pretranslatedE);
|
|
@@ -404,6 +418,20 @@ module.exports = class CTDLASNCSVImport {
|
|
|
404
418
|
continue;
|
|
405
419
|
}
|
|
406
420
|
}
|
|
421
|
+
// Validate registry URL fields (only if url rules provided)
|
|
422
|
+
if (validationRules && validationRules.urlRules) {
|
|
423
|
+
const urlErrors = CTDLASNCSVImport.validateRegistryUrls(
|
|
424
|
+
pretranslatedE,
|
|
425
|
+
"ceasn:Competency",
|
|
426
|
+
i + 2,
|
|
427
|
+
validationRules.urlRules
|
|
428
|
+
);
|
|
429
|
+
if (urlErrors) {
|
|
430
|
+
errors.push(...urlErrors);
|
|
431
|
+
rowsWithErrors.add(i);
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
407
435
|
|
|
408
436
|
let translator = new EcLinkedData(null, null);
|
|
409
437
|
translator.copyFrom(pretranslatedE);
|
|
@@ -850,6 +878,20 @@ module.exports = class CTDLASNCSVImport {
|
|
|
850
878
|
continue;
|
|
851
879
|
}
|
|
852
880
|
}
|
|
881
|
+
// Validate registry URL fields (only if url rules provided)
|
|
882
|
+
if (validationRules && validationRules.urlRules) {
|
|
883
|
+
const urlErrors = CTDLASNCSVImport.validateRegistryUrls(
|
|
884
|
+
pretranslatedE,
|
|
885
|
+
"ceterms:Collection",
|
|
886
|
+
i + 2,
|
|
887
|
+
validationRules.urlRules
|
|
888
|
+
);
|
|
889
|
+
if (urlErrors) {
|
|
890
|
+
errors.push(...urlErrors);
|
|
891
|
+
rowsWithErrors.add(i);
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
853
895
|
let translator = new EcLinkedData(null, null);
|
|
854
896
|
translator.copyFrom(pretranslatedE);
|
|
855
897
|
CTDLASNCSVImport.cleanUpTranslator(
|
|
@@ -939,6 +981,20 @@ module.exports = class CTDLASNCSVImport {
|
|
|
939
981
|
continue;
|
|
940
982
|
}
|
|
941
983
|
}
|
|
984
|
+
// Validate registry URL fields (only if url rules provided)
|
|
985
|
+
if (validationRules && validationRules.urlRules) {
|
|
986
|
+
const urlErrors = CTDLASNCSVImport.validateRegistryUrls(
|
|
987
|
+
pretranslatedE,
|
|
988
|
+
"ceasn:Competency",
|
|
989
|
+
i + 2,
|
|
990
|
+
validationRules.urlRules
|
|
991
|
+
);
|
|
992
|
+
if (urlErrors) {
|
|
993
|
+
errors.push(...urlErrors);
|
|
994
|
+
rowsWithErrors.add(i);
|
|
995
|
+
continue;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
942
998
|
let translator = new EcLinkedData(null, null);
|
|
943
999
|
translator.copyFrom(pretranslatedE);
|
|
944
1000
|
CTDLASNCSVImport.cleanUpTranslator(
|
|
@@ -1447,6 +1503,48 @@ module.exports = class CTDLASNCSVImport {
|
|
|
1447
1503
|
return null;
|
|
1448
1504
|
}
|
|
1449
1505
|
|
|
1506
|
+
/*
|
|
1507
|
+
* Validates that reference fields contain either a CTID (ce-...) or a
|
|
1508
|
+
* credential registry URL whose origin is allowed for this environment.
|
|
1509
|
+
* CTIDs are resolved by the CTID pipeline (getIdFromCtid); any other value
|
|
1510
|
+
* must be an http(s) URL with an origin listed in urlRules.allowedOrigins.
|
|
1511
|
+
*
|
|
1512
|
+
* urlRules = {
|
|
1513
|
+
* fields: { "<@type>": ["ceasn:creator", ...], ... },
|
|
1514
|
+
* allowedOrigins: ["https://sandbox.credentialengineregistry.org", ...]
|
|
1515
|
+
* }
|
|
1516
|
+
*
|
|
1517
|
+
* Returns an array of error strings, or null when everything passes.
|
|
1518
|
+
*/
|
|
1519
|
+
static validateRegistryUrls(obj, type, rowIndex, urlRules) {
|
|
1520
|
+
if (!urlRules) return null;
|
|
1521
|
+
const fields = urlRules.fields ? urlRules.fields[type] : null;
|
|
1522
|
+
const allowedOrigins = urlRules.allowedOrigins;
|
|
1523
|
+
if (!fields || !allowedOrigins || allowedOrigins.length === 0) return null;
|
|
1524
|
+
|
|
1525
|
+
const ctidPattern = /^ce-[0-9a-fA-F-]+$/;
|
|
1526
|
+
const errors = [];
|
|
1527
|
+
for (const field of fields) {
|
|
1528
|
+
const raw = obj[field];
|
|
1529
|
+
if (raw == null) continue;
|
|
1530
|
+
const values = Array.isArray(raw) ? raw : String(raw).split("|");
|
|
1531
|
+
for (let value of values) {
|
|
1532
|
+
value = String(value).trim();
|
|
1533
|
+
if (value === "" || ctidPattern.test(value)) continue;
|
|
1534
|
+
let origin = null;
|
|
1535
|
+
try {
|
|
1536
|
+
origin = new URL(value).origin;
|
|
1537
|
+
} catch (e) {
|
|
1538
|
+
// Not parseable as a URL; falls through to the error below.
|
|
1539
|
+
}
|
|
1540
|
+
if (origin == null || !allowedOrigins.includes(origin)) {
|
|
1541
|
+
errors.push(`Row ${rowIndex}: ${field} must be a CTID or a credential registry URL for this environment (got: ${value})`);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
return errors.length > 0 ? errors : null;
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1450
1548
|
static validateFrameworkHierarchy(tabularData, hierarchyRules) {
|
|
1451
1549
|
// Use provided hierarchy rules or fall back to defaults
|
|
1452
1550
|
if (!hierarchyRules) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
const {cassPromisify} = require("../../../../../com/eduworks/ec/promises/helpers");
|
|
3
3
|
const EcRemote = require("../../../../../com/eduworks/ec/remote/EcRemote");
|
|
4
|
+
const FormData = EcRemote.FormData;
|
|
4
5
|
const EbacCredentialCommit = require("../../../../../com/eduworks/schema/ebac/EbacCredentialCommit");
|
|
5
6
|
const EbacCredentialRequest = require("../../../../../com/eduworks/schema/ebac/EbacCredentialRequest");
|
|
6
7
|
const EcContact = require("../EcContact");
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
if (typeof process !== 'undefined' && process.version && process.version.startsWith("v16")) {
|
|
2
|
-
console.log("Loading polyfill for FormData.");
|
|
3
|
-
FormData = eval("require('undici').FormData");
|
|
4
|
-
}
|
|
5
1
|
const EcObject = require("../../../../com/eduworks/ec/array/EcObject");
|
|
6
2
|
const EcEncryptedValue = require("./EcEncryptedValue");
|
|
7
3
|
const EcIdentityManager = require("../identity/EcIdentityManager");
|
|
8
4
|
const EcRekeyRequest = require("../identity/EcRekeyRequest");
|
|
9
5
|
const EcArray = require("../../../../com/eduworks/ec/array/EcArray");
|
|
10
6
|
const EcRemote = require("../../../../com/eduworks/ec/remote/EcRemote");
|
|
7
|
+
const FormData = EcRemote.FormData;
|
|
11
8
|
const { cassPromisify, cassReturnAsPromise, cassReturnNullAsPromise } = require("../../../../com/eduworks/ec/promises/helpers");
|
|
12
9
|
const EcRemoteLinkedData = require("../../schema/general/EcRemoteLinkedData");
|
|
13
10
|
const EcCrypto = require("../../../../com/eduworks/ec/crypto/EcCrypto");
|
|
@@ -6,6 +6,7 @@ const EcPpkFacade = require("../../../../com/eduworks/ec/crypto/EcPpkFacade.js")
|
|
|
6
6
|
const EcRsaOaepAsyncWorker = require("../../../../com/eduworks/ec/crypto/EcRsaOaepAsyncWorker.js");
|
|
7
7
|
const EcLinkedData = require("../../../json/ld/EcLinkedData.js");
|
|
8
8
|
const Ebac = require("./Ebac.js");
|
|
9
|
+
const FormData = require("../../../../com/eduworks/ec/remote/EcRemote.js").FormData;
|
|
9
10
|
require("../../general/AuditLogger.js")
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -540,6 +541,19 @@ module.exports = class EcRemoteLinkedData extends EcLinkedData {
|
|
|
540
541
|
types[i].substring(lastSlash + 1) +
|
|
541
542
|
'")';
|
|
542
543
|
}
|
|
544
|
+
//Replace http with https in schema or visa versa and then do it again
|
|
545
|
+
for (let i = 0; i < types.length; i++) {
|
|
546
|
+
if (result != "") result += " OR ";
|
|
547
|
+
let lastSlash = types[i].lastIndexOf("/");
|
|
548
|
+
let context = types[i].substring(0, lastSlash + 1);
|
|
549
|
+
if (context.startsWith("http://")) {
|
|
550
|
+
let httpsContext = context.replace("http://", "https://");
|
|
551
|
+
result += '(@context:"' + httpsContext + '" AND @type:"' + types[i].substring(lastSlash + 1) + '")';
|
|
552
|
+
} else if (context.startsWith("https://")) {
|
|
553
|
+
let httpContext = context.replace("https://", "http://");
|
|
554
|
+
result += '(@context:"' + httpContext + '" AND @type:"' + types[i].substring(lastSlash + 1) + '")';
|
|
555
|
+
}
|
|
556
|
+
}
|
|
543
557
|
return "(" + result + ")";
|
|
544
558
|
}
|
|
545
559
|
asRdfXml(success, failure, signatureSheet) {
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
let chai = require("chai");
|
|
2
|
+
const CTDLASNCSVImport = require("../org/cass/importer/CTDLASNCSVImport.js");
|
|
3
|
+
|
|
4
|
+
let expect = chai.expect;
|
|
5
|
+
|
|
6
|
+
// Unit tests for the registry URL validation added for cassproject/cass-editor#1423:
|
|
7
|
+
// CSV reference fields must contain either a CTID (ce-...) or a credential
|
|
8
|
+
// registry URL whose origin matches the configured environment.
|
|
9
|
+
describe("CTDLASNCSVImport.validateRegistryUrls", () => {
|
|
10
|
+
const urlRules = {
|
|
11
|
+
fields: {
|
|
12
|
+
"ceasn:CompetencyFramework": ["ceasn:creator", "ceasn:publisher", "asn:hasProgressionModel", "ceasn:hasTopChild"],
|
|
13
|
+
"ceasn:Competency": ["ceasn:creator", "ceterms:hasTask"]
|
|
14
|
+
},
|
|
15
|
+
allowedOrigins: ["https://sandbox.credentialengineregistry.org"]
|
|
16
|
+
};
|
|
17
|
+
const type = "ceasn:CompetencyFramework";
|
|
18
|
+
|
|
19
|
+
it("accepts a CTID value", () => {
|
|
20
|
+
const obj = { "ceasn:publisher": "ce-11111111-2222-3333-4444-555555555555" };
|
|
21
|
+
expect(CTDLASNCSVImport.validateRegistryUrls(obj, type, 2, urlRules)).to.equal(null);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("accepts a registry URL with an allowed origin", () => {
|
|
25
|
+
const obj = { "ceasn:publisher": "https://sandbox.credentialengineregistry.org/resources/ce-1111" };
|
|
26
|
+
expect(CTDLASNCSVImport.validateRegistryUrls(obj, type, 2, urlRules)).to.equal(null);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("rejects a URL from a different origin", () => {
|
|
30
|
+
const obj = { "ceasn:publisher": "https://credentialengineregistry.org/resources/ce-1111" };
|
|
31
|
+
const errors = CTDLASNCSVImport.validateRegistryUrls(obj, type, 2, urlRules);
|
|
32
|
+
expect(errors).to.be.an("array").with.lengthOf(1);
|
|
33
|
+
expect(errors[0]).to.contain("Row 2");
|
|
34
|
+
expect(errors[0]).to.contain("ceasn:publisher");
|
|
35
|
+
expect(errors[0]).to.contain("https://credentialengineregistry.org/resources/ce-1111");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("rejects an arbitrary non-registry URL", () => {
|
|
39
|
+
const obj = { "ceasn:creator": "https://example.com/x" };
|
|
40
|
+
const errors = CTDLASNCSVImport.validateRegistryUrls(obj, type, 3, urlRules);
|
|
41
|
+
expect(errors).to.be.an("array").with.lengthOf(1);
|
|
42
|
+
expect(errors[0]).to.contain("ceasn:creator");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("rejects a value that is neither CTID nor URL", () => {
|
|
46
|
+
const obj = { "ceasn:publisher": "Some Publisher Name" };
|
|
47
|
+
const errors = CTDLASNCSVImport.validateRegistryUrls(obj, type, 4, urlRules);
|
|
48
|
+
expect(errors).to.be.an("array").with.lengthOf(1);
|
|
49
|
+
expect(errors[0]).to.contain("Some Publisher Name");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("reports only the bad part of a pipe-delimited value", () => {
|
|
53
|
+
const obj = { "ceasn:hasTopChild": "ce-11111111-2222-3333-4444-555555555555|https://evil.example.com/resources/ce-2222" };
|
|
54
|
+
const errors = CTDLASNCSVImport.validateRegistryUrls(obj, type, 5, urlRules);
|
|
55
|
+
expect(errors).to.be.an("array").with.lengthOf(1);
|
|
56
|
+
expect(errors[0]).to.contain("https://evil.example.com/resources/ce-2222");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("validates array values", () => {
|
|
60
|
+
const obj = { "ceasn:creator": ["https://sandbox.credentialengineregistry.org/resources/ce-1", "https://example.org/nope"] };
|
|
61
|
+
const errors = CTDLASNCSVImport.validateRegistryUrls(obj, type, 6, urlRules);
|
|
62
|
+
expect(errors).to.be.an("array").with.lengthOf(1);
|
|
63
|
+
expect(errors[0]).to.contain("https://example.org/nope");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("collects errors across multiple fields", () => {
|
|
67
|
+
const obj = {
|
|
68
|
+
"ceasn:creator": "https://example.com/a",
|
|
69
|
+
"ceasn:publisher": "https://example.com/b"
|
|
70
|
+
};
|
|
71
|
+
const errors = CTDLASNCSVImport.validateRegistryUrls(obj, type, 7, urlRules);
|
|
72
|
+
expect(errors).to.be.an("array").with.lengthOf(2);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("ignores fields not configured for the type", () => {
|
|
76
|
+
const obj = { "ceasn:description": "https://example.com/not-checked" };
|
|
77
|
+
expect(CTDLASNCSVImport.validateRegistryUrls(obj, type, 8, urlRules)).to.equal(null);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("is inert for types with no configured fields", () => {
|
|
81
|
+
const obj = { "ceasn:publisher": "https://example.com/x" };
|
|
82
|
+
expect(CTDLASNCSVImport.validateRegistryUrls(obj, "skos:Concept", 9, urlRules)).to.equal(null);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("is inert when urlRules is absent", () => {
|
|
86
|
+
const obj = { "ceasn:publisher": "https://example.com/x" };
|
|
87
|
+
expect(CTDLASNCSVImport.validateRegistryUrls(obj, type, 10, null)).to.equal(null);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("is inert when allowedOrigins is empty", () => {
|
|
91
|
+
const obj = { "ceasn:publisher": "https://example.com/x" };
|
|
92
|
+
const rules = { fields: urlRules.fields, allowedOrigins: [] };
|
|
93
|
+
expect(CTDLASNCSVImport.validateRegistryUrls(obj, type, 11, rules)).to.equal(null);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("skips empty and null values", () => {
|
|
97
|
+
const obj = { "ceasn:publisher": "", "ceasn:creator": null };
|
|
98
|
+
expect(CTDLASNCSVImport.validateRegistryUrls(obj, type, 12, urlRules)).to.equal(null);
|
|
99
|
+
});
|
|
100
|
+
});
|