cassproject 5.0.22 → 5.0.23

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.
Files changed (105) hide show
  1. package/package.json +47 -50
  2. package/src/com/eduworks/ec/array/EcArray.js +1 -1
  3. package/src/com/eduworks/ec/array/EcAsyncHelper.js +1 -1
  4. package/src/com/eduworks/ec/array/EcObject.js +1 -1
  5. package/src/com/eduworks/ec/crypto/EcAes.js +1 -1
  6. package/src/com/eduworks/ec/crypto/EcAesCtrAsync.js +20 -22
  7. package/src/com/eduworks/ec/crypto/EcCrypto.js +8 -4
  8. package/src/com/eduworks/ec/crypto/EcPk.js +7 -7
  9. package/src/com/eduworks/ec/crypto/EcPpk.js +7 -7
  10. package/src/com/eduworks/ec/crypto/EcRsaOaepAsync.js +20 -19
  11. package/src/com/eduworks/ec/crypto/EcRsaOaepAsyncWorker.js +7 -6
  12. package/src/com/eduworks/ec/crypto/forgeAsyncNode.js +8 -8
  13. package/src/com/eduworks/ec/graph/EcDirectedGraph.js +1 -2
  14. package/src/com/eduworks/ec/graph/EcFrameworkGraph.js +18 -24
  15. package/src/com/eduworks/ec/promises/helpers.js +1 -3
  16. package/src/com/eduworks/ec/remote/EcRemote.js +9 -9
  17. package/src/com/eduworks/ec/task/EcAsyncTask.js +5 -20
  18. package/src/com/eduworks/ec/task/EcAsyncTaskManager.js +3 -4
  19. package/src/com/eduworks/ec/task/EcAsyncTaskWorker.js +9 -11
  20. package/src/com/eduworks/schema/cfd/competency/CfdFramework.js +36 -37
  21. package/src/com/eduworks/schema/ebac/EbacContact.js +3 -9
  22. package/src/com/eduworks/schema/ebac/EbacContactGrant.js +3 -9
  23. package/src/com/eduworks/schema/ebac/EbacCredential.js +3 -9
  24. package/src/com/eduworks/schema/ebac/EbacCredentialCommit.js +3 -9
  25. package/src/com/eduworks/schema/ebac/EbacCredentialRequest.js +3 -9
  26. package/src/com/eduworks/schema/ebac/EbacCredentials.js +3 -9
  27. package/src/com/eduworks/schema/ebac/EbacEncryptedSecret.js +3 -9
  28. package/src/com/eduworks/schema/ebac/EbacEncryptedValue.js +5 -12
  29. package/src/com/eduworks/schema/ebac/EbacSignature.js +1 -6
  30. package/src/index/env.js +6 -3
  31. package/src/index/polyfills.js +13 -27
  32. package/src/org/angles/schema/angles/VideoStory.js +1 -1
  33. package/src/org/cass/competency/EcCompetency.js +1 -2
  34. package/src/org/cass/competency/EcDirectory.js +1 -2
  35. package/src/org/cass/competency/EcFramework.js +9 -12
  36. package/src/org/cass/exporter/CSVExport.js +12 -13
  37. package/src/org/cass/importer/ASNImport.js +3 -6
  38. package/src/org/cass/importer/CSVImport.js +9 -17
  39. package/src/org/cass/importer/CTDLASNCSVConceptImport.js +25 -121
  40. package/src/org/cass/importer/CTDLASNCSVImport.js +88 -133
  41. package/src/org/cass/importer/FrameworkImport.js +16 -30
  42. package/src/org/cass/importer/MedbiqImport.js +3 -4
  43. package/src/org/cass/importer/TabStructuredImport.js +6 -6
  44. package/src/org/cass/profile/EcAssertion.js +40 -51
  45. package/src/org/cassproject/ebac/identity/EcContactGrant.js +2 -2
  46. package/src/org/cassproject/ebac/identity/EcIdentityManager.js +56 -62
  47. package/src/org/cassproject/ebac/identity/remote/EcRemoteIdentityManager.js +51 -62
  48. package/src/org/cassproject/ebac/identity/remote/OAuth2FileBasedRemoteIdentityManager.js +56 -70
  49. package/src/org/cassproject/ebac/identity/remote/RemoteIdentityManagerInterface.js +3 -3
  50. package/src/org/cassproject/ebac/repository/EcEncryptedValue.js +51 -44
  51. package/src/org/cassproject/ebac/repository/EcRepository.js +228 -301
  52. package/src/org/cassproject/general/repository/GeneralFile.js +8 -9
  53. package/src/org/cassproject/schema/cass/competency/Competency.js +10 -11
  54. package/src/org/cassproject/schema/cass/competency/Framework.js +10 -11
  55. package/src/org/cassproject/schema/cass/competency/Level.js +10 -11
  56. package/src/org/cassproject/schema/cass/competency/Relation.js +10 -11
  57. package/src/org/cassproject/schema/cass/competency/RollupRule.js +7 -7
  58. package/src/org/cassproject/schema/cass/profile/Assertion.js +20 -27
  59. package/src/org/cassproject/schema/cass/profile/AssertionEnvelope.js +3 -5
  60. package/src/org/cassproject/schema/general/EcRemoteLinkedData.js +41 -44
  61. package/src/org/json/ld/EcLinkedData.js +10 -12
  62. package/src/org/schema/EcOrganization.js +10 -10
  63. package/src/org/schema/EcQuestion.js +8 -8
  64. package/src/org/w3/skos/Concept.js +6 -6
  65. package/src/org/w3/skos/ConceptScheme.js +6 -6
  66. package/src/org/w3/skos/EcConcept.js +1 -2
  67. package/src/org/w3/skos/EcConceptScheme.js +1 -2
  68. package/src/test/0.start.test.js +1 -1
  69. package/src/test/0.testLibraries.test.js +172 -0
  70. package/src/test/1.ASNImport.test.js +122 -0
  71. package/src/test/1.CSVExport.test.js +57 -0
  72. package/src/test/1.CSVImport.test.js +185 -0
  73. package/src/test/1.CTDLASNCSVConceptImport.test.js +348 -0
  74. package/src/test/1.CTDLASNCSVImport.import.test.js +497 -0
  75. package/src/test/1.Ebac.schema.test.js +107 -0
  76. package/src/test/1.EcAssertion.decrypt.test.js +98 -0
  77. package/src/test/1.EcAssertion.pinned.test.js +160 -0
  78. package/src/test/1.EcCrypto.formats.test.js +293 -0
  79. package/src/test/1.EcCrypto.generateUUID.test.js +47 -0
  80. package/src/test/1.EcEncryptedValue.helpers.test.js +236 -0
  81. package/src/test/1.EcHelpers.misc.test.js +259 -0
  82. package/src/test/1.EcIdentityManager.keys.test.js +439 -0
  83. package/src/test/1.EcOrganization.rekey.test.js +68 -0
  84. package/src/test/1.EcRemote.responses.test.js +155 -0
  85. package/src/test/1.EcRemoteIdentityManager.flows.test.js +326 -0
  86. package/src/test/1.EcRemoteLinkedData.pinned.test.js +473 -0
  87. package/src/test/1.EcRepository.l0.test.js +14 -5
  88. package/src/test/1.EcRepository.l1.test.js +16 -7
  89. package/src/test/1.EcRepository.l2.test.js +20 -11
  90. package/src/test/1.EcRepository.unit.test.js +961 -0
  91. package/src/test/1.EcRsaOaep.test.js +2 -12
  92. package/src/test/1.EcRsaOaepAsync.test.js +2 -12
  93. package/src/test/1.EcRsaOaepAsyncWorker.test.js +2 -12
  94. package/src/test/1.EcRsaOaepAsyncWorker.workers.test.js +147 -0
  95. package/src/test/1.FrameworkImport.test.js +170 -0
  96. package/src/test/1.MedbiqImport.test.js +92 -0
  97. package/src/test/1.OAuth2FileBasedRemoteIdentityManager.test.js +399 -0
  98. package/src/test/1.TabStructuredImport.test.js +61 -0
  99. package/src/test/1.polyfills.test.js +89 -0
  100. package/src/test/1.schema.competency.pinned.test.js +384 -0
  101. package/src/test/3.EcFrameworkGraph.test.js +3 -2
  102. package/src/test/4.schema.test.js +33 -28
  103. package/src/test/4.skyId.test.js +4 -2
  104. package/src/test/cypressValue.js +9 -0
  105. package/src/test/2.EcCompetency.test.js +0 -17
@@ -68,18 +68,8 @@ describe("EcRsaOaep", () => {
68
68
  assert.isTrue(elapsed < 1);
69
69
  assert.isTrue(randomString == decrypted);
70
70
  });
71
- it('signing then verifying', () => {
72
- console.log("Deprecated. See https://github.com/Lomilar/node-fips-rsassa-pkcs1-15-sha1/tree/main");
73
- return;
74
- });
75
- it('signing then verifying (utf-8)', () => {
76
- console.log("Deprecated. See https://github.com/Lomilar/node-fips-rsassa-pkcs1-15-sha1/tree/main");
77
- return;
78
- });
79
- it('signing then verifying w/caching', () => {
80
- console.log("Deprecated. See https://github.com/Lomilar/node-fips-rsassa-pkcs1-15-sha1/tree/main");
81
- return;
82
- });
71
+ // SHA-1 signing then verifying (plain, utf-8 and w/caching) is no longer tested here.
72
+ // See https://github.com/Lomilar/node-fips-rsassa-pkcs1-15-sha1/tree/main
83
73
  it('signing then verifying (sha256)', () => {
84
74
  let randomString = EcAes.newIv(256*4);
85
75
  let ppk = EcPpk.fromPem(
@@ -71,18 +71,8 @@ describe("EcRsaOaepAsync", () => {
71
71
  assert.isTrue(elapsed < 1);
72
72
  assert.isTrue(randomString == decrypted);
73
73
  });
74
- it('signing then verifying', async () => {
75
- console.log("Deprecated. See https://github.com/Lomilar/node-fips-rsassa-pkcs1-15-sha1/tree/main");
76
- return;
77
- });
78
- it('signing then verifying (utf-8)', async () => {
79
- console.log("Deprecated. See https://github.com/Lomilar/node-fips-rsassa-pkcs1-15-sha1/tree/main");
80
- return;
81
- });
82
- it('signing then verifying w/caching', async () => {
83
- console.log("Deprecated. See https://github.com/Lomilar/node-fips-rsassa-pkcs1-15-sha1/tree/main");
84
- return;
85
- });
74
+ // SHA-1 signing then verifying (plain, utf-8 and w/caching) is no longer tested here.
75
+ // See https://github.com/Lomilar/node-fips-rsassa-pkcs1-15-sha1/tree/main
86
76
  it('signing then verifying (sha256)', async () => {
87
77
  let randomString = EcAes.newIv(256*4);
88
78
  let ppk = EcPpk.fromPem(
@@ -74,18 +74,8 @@ describe("EcRsaOaepAsyncWorker", () => {
74
74
  assert.isTrue(elapsed < 1);
75
75
  assert.isTrue(randomString == decrypted);
76
76
  });
77
- it('signing then verifying', async () => {
78
- console.log("Deprecated. See https://github.com/Lomilar/node-fips-rsassa-pkcs1-15-sha1/tree/main");
79
- return;
80
- });
81
- it('signing then verifying (utf-8)', async () => {
82
- console.log("Deprecated. See https://github.com/Lomilar/node-fips-rsassa-pkcs1-15-sha1/tree/main");
83
- return;
84
- });
85
- it('signing then verifying w/caching', async () => {
86
- console.log("Deprecated. See https://github.com/Lomilar/node-fips-rsassa-pkcs1-15-sha1/tree/main");
87
- return;
88
- });
77
+ // SHA-1 signing then verifying (plain, utf-8 and w/caching) is no longer tested here.
78
+ // See https://github.com/Lomilar/node-fips-rsassa-pkcs1-15-sha1/tree/main
89
79
  it('signing then verifying (sha256)', async () => {
90
80
  let randomString = EcAes.newIv(256 * 4);
91
81
  let ppk = EcPpk.fromPem(
@@ -0,0 +1,147 @@
1
+ // Checks that, under Node, the crypto "worker" classes really run on web-worker
2
+ // threads. The other worker tests only check results, which would also pass if
3
+ // creating the workers failed and every call fell back to the main thread.
4
+ require('../../index.js');
5
+ const chai = require('chai');
6
+ const sinon = require('sinon');
7
+ const EcRsaOaepAsyncWorker = require('../com/eduworks/ec/crypto/EcRsaOaepAsyncWorker.js');
8
+ const EcAesCtrAsyncWorker = require('../com/eduworks/ec/crypto/EcAesCtrAsyncWorker.js');
9
+ const EcRsaOaepAsync = require('../com/eduworks/ec/crypto/EcRsaOaepAsync.js');
10
+ const EcAesCtrAsync = require('../com/eduworks/ec/crypto/EcAesCtrAsync.js');
11
+ const EcRsaOaep = require('../com/eduworks/ec/crypto/EcRsaOaep.js');
12
+ const EcAesCtr = require('../com/eduworks/ec/crypto/EcAesCtr.js');
13
+ const EcAes = require('../com/eduworks/ec/crypto/EcAes.js');
14
+ const EcPpk = require('../com/eduworks/ec/crypto/EcPpk.js');
15
+ const EcCrypto = require('../com/eduworks/ec/crypto/EcCrypto.js');
16
+
17
+ const assert = chai.assert;
18
+
19
+ describe('EcRsaOaepAsyncWorker threads (Node)', function () {
20
+ this.timeout(60000);
21
+ let ppk, pk, posted, caching;
22
+ const fallbacks = [];
23
+
24
+ before(async function () {
25
+ if (typeof window !== 'undefined') this.skip();
26
+ // Cached decryptions never reach a worker.
27
+ caching = EcCrypto.caching;
28
+ EcCrypto.caching = false;
29
+ EcRsaOaepAsyncWorker.teardown();
30
+ ppk = await EcPpk.generateKeyAsync();
31
+ pk = ppk.toPk();
32
+ });
33
+
34
+ beforeEach(() => {
35
+ // Any use of the main-thread implementations means the worker path was skipped.
36
+ fallbacks.length = 0;
37
+ for (const [cls, names] of [[EcRsaOaepAsync, ['encrypt', 'decrypt', 'sign', 'signSha256', 'verify', 'verifySha256']], [EcAesCtrAsync, ['encrypt', 'decrypt']]])
38
+ for (const n of names) fallbacks.push(sinon.spy(cls, n));
39
+ posted = [];
40
+ });
41
+
42
+ afterEach(() => sinon.restore());
43
+ after(() => {
44
+ EcRsaOaepAsyncWorker.teardown();
45
+ if (caching !== undefined) EcCrypto.caching = caching;
46
+ });
47
+
48
+ function watchWorkers() {
49
+ EcRsaOaepAsyncWorker.initWorker();
50
+ for (const w of EcRsaOaepAsyncWorker.w)
51
+ if (!w.postMessage.restore)
52
+ sinon.stub(w, 'postMessage').callsFake(function (msg, origin) {
53
+ posted.push(msg.cmd);
54
+ return w.postMessage.wrappedMethod.call(w, msg, origin);
55
+ });
56
+ }
57
+
58
+ function assertNoFallback() {
59
+ for (const f of fallbacks)
60
+ assert.isFalse(f.called, `fell back to ${f.toString()}`);
61
+ }
62
+
63
+ it('provides a Worker implementation', () => {
64
+ assert.isFunction(global.Worker);
65
+ });
66
+
67
+ it('starts one worker per rotation without logging errors', () => {
68
+ const report = sinon.spy(global.auditLogger, 'report');
69
+ EcRsaOaepAsyncWorker.initWorker();
70
+ assert.lengthOf(EcRsaOaepAsyncWorker.w, EcRsaOaepAsyncWorker.rotations);
71
+ for (const w of EcRsaOaepAsyncWorker.w) {
72
+ assert.isNotNull(w);
73
+ assert.isFunction(w.postMessage);
74
+ assert.isFunction(w._worker.terminate);
75
+ }
76
+ assert.isFalse(report.calledWith(sinon.match.any, sinon.match.any, 'EcRsaOaepAsyncWorker'), 'worker creation logged an error');
77
+ });
78
+
79
+ // ASCII interoperates with the main-thread classes. Non-ASCII text only round-trips
80
+ // worker to worker: the worker path encodes UTF-8 differently.
81
+ it('encrypts and decrypts on a worker', async () => {
82
+ watchWorkers();
83
+ const text = 'Hello, workers';
84
+ assert.equal(EcRsaOaep.decrypt(ppk, await EcRsaOaepAsyncWorker.encrypt(pk, text)), text);
85
+ assert.equal(await EcRsaOaepAsyncWorker.decrypt(ppk, EcRsaOaep.encrypt(pk, text)), text);
86
+ const utf8 = 'Hello, workers ✓';
87
+ assert.equal(await EcRsaOaepAsyncWorker.decrypt(ppk, await EcRsaOaepAsyncWorker.encrypt(pk, utf8)), utf8);
88
+ assert.equal(await EcRsaOaepAsyncWorker.decrypt(ppk, EcRsaOaep.encrypt(pk, utf8)), utf8);
89
+ assert.deepEqual(posted, ['encryptRsaOaep', 'decryptRsaOaep', 'encryptRsaOaep', 'decryptRsaOaep', 'decryptRsaOaep']);
90
+ assertNoFallback();
91
+ });
92
+
93
+ it('signs and verifies on a worker', async () => {
94
+ watchWorkers();
95
+ const text = 'Sign me';
96
+ const sha1 = await EcRsaOaepAsyncWorker.sign(ppk, text);
97
+ const sha256 = await EcRsaOaepAsyncWorker.signSha256(ppk, text);
98
+ assert.equal(sha256, EcRsaOaep.signSha256(ppk, text));
99
+ assert.isTrue(await EcRsaOaepAsyncWorker.verify(pk, text, sha1));
100
+ assert.isTrue(await EcRsaOaepAsyncWorker.verifySha256(pk, text, sha256));
101
+ assert.isFalse(await EcRsaOaepAsyncWorker.verifySha256(pk, text + '!', sha256));
102
+ const utf8 = 'Sign me ✓';
103
+ assert.isTrue(await EcRsaOaepAsyncWorker.verifySha256(pk, utf8, await EcRsaOaepAsyncWorker.signSha256(ppk, utf8)));
104
+ assert.deepEqual(posted, ['signRsaOaep', 'signSha256RsaOaep', 'verifyRsaOaep', 'verifyRsaOaepSha256', 'verifyRsaOaepSha256', 'signSha256RsaOaep', 'verifyRsaOaepSha256']);
105
+ assertNoFallback();
106
+ });
107
+
108
+ it('runs AES-CTR on a worker', async () => {
109
+ watchWorkers();
110
+ const secret = EcAes.newSecret(32), iv = EcAes.newIv(16), text = 'AES on a worker';
111
+ assert.equal(EcAesCtr.decrypt(await EcAesCtrAsyncWorker.encrypt(text, secret, iv), secret, iv), text);
112
+ assert.equal(await EcAesCtrAsyncWorker.decrypt(EcAesCtr.encrypt(text, secret, iv), secret, iv), text);
113
+ const utf8 = 'AES on a worker ✓';
114
+ assert.equal(await EcAesCtrAsyncWorker.decrypt(await EcAesCtrAsyncWorker.encrypt(utf8, secret, iv), secret, iv), utf8);
115
+ assert.deepEqual(posted, ['encryptAesCtr', 'decryptAesCtr', 'encryptAesCtr', 'decryptAesCtr']);
116
+ assertNoFallback();
117
+ });
118
+
119
+ it('rotates work across all workers', async () => {
120
+ watchWorkers();
121
+ const used = new Set();
122
+ EcRsaOaepAsyncWorker.w.forEach((w, i) => w.postMessage.callsFake(function (msg, origin) {
123
+ used.add(i);
124
+ return w.postMessage.wrappedMethod.call(w, msg, origin);
125
+ }));
126
+ const text = 'rotate';
127
+ await Promise.all(Array.from({ length: EcRsaOaepAsyncWorker.rotations }, () => EcRsaOaepAsyncWorker.encrypt(pk, text)));
128
+ assert.equal(used.size, EcRsaOaepAsyncWorker.rotations);
129
+ });
130
+
131
+ it('resolves the string "null" when the worker cannot decrypt', async () => {
132
+ watchWorkers();
133
+ assert.strictEqual(await EcRsaOaepAsyncWorker.decrypt(ppk, 'bm90IGNpcGhlcnRleHQ='), 'null');
134
+ assert.deepEqual(posted, ['decryptRsaOaep']);
135
+ });
136
+
137
+ it('terminates the workers on teardown and starts new ones afterwards', async () => {
138
+ EcRsaOaepAsyncWorker.initWorker();
139
+ const old = EcRsaOaepAsyncWorker.w.map((w) => sinon.spy(w._worker, 'terminate'));
140
+ EcRsaOaepAsyncWorker.teardown();
141
+ assert.isNull(EcRsaOaepAsyncWorker.w);
142
+ for (const t of old) assert.isTrue(t.calledOnce);
143
+ watchWorkers();
144
+ assert.equal(await EcRsaOaepAsyncWorker.decrypt(ppk, EcRsaOaep.encrypt(pk, 'again')), 'again');
145
+ assertNoFallback();
146
+ });
147
+ });
@@ -0,0 +1,170 @@
1
+ require('../../index.js');
2
+ const chai = require('chai');
3
+ const sinon = require('sinon');
4
+ const EcRepository = require('../org/cassproject/ebac/repository/EcRepository.js');
5
+
6
+ const assert = chai.assert;
7
+
8
+ const SERVER = "http://repo.test/api/";
9
+ const SRC = "http://src.test/api/data/";
10
+
11
+ function fixtures() {
12
+ const store = {};
13
+ const comp = (n) => {
14
+ const c = new EcCompetency();
15
+ c.id = SRC + "c" + n;
16
+ c.name = "Comp " + n;
17
+ store[c.id] = c;
18
+ return c;
19
+ };
20
+ const c1 = comp(1);
21
+ const c2 = comp(2);
22
+ const r1 = new EcAlignment();
23
+ r1.id = SRC + "r1";
24
+ r1.source = c2.shortId();
25
+ r1.target = c1.shortId();
26
+ r1.relationType = "narrows";
27
+ store[r1.id] = r1;
28
+ const source = new EcFramework();
29
+ source.id = SRC + "fw";
30
+ source.name = "Source";
31
+ source.competency = [c1.id, c2.id];
32
+ source.relation = [r1.id];
33
+ const target = new EcFramework();
34
+ target.id = SERVER + "fw";
35
+ target.name = "Target";
36
+ return { store, source, target };
37
+ }
38
+
39
+ describe('FrameworkImport.importCompetencies', () => {
40
+ let store;
41
+ let gets;
42
+ beforeEach(() => {
43
+ gets = [];
44
+ sinon.stub(EcRepository, 'getAs').callsFake((id, obj, success, failure) => {
45
+ gets.push(id);
46
+ if (store[id] == null) return failure("missing " + id);
47
+ obj.copyFrom(JSON.parse(store[id].toJson()));
48
+ success(obj);
49
+ });
50
+ });
51
+ afterEach(() => sinon.restore());
52
+
53
+ const run = (source, target, copy, serverUrl, repo) => new Promise((resolve, reject) => {
54
+ FrameworkImport.importCompetencies(source, target, copy, serverUrl, null, (c, r) => resolve({ c, r }), reject, repo);
55
+ });
56
+ const savingRepo = (saved, selectedServer) => ({ selectedServer: selectedServer || SERVER, saveTo: (o, s) => { saved.push(o); s("ok"); } });
57
+
58
+ it('reports missing source, target and competencies', () => {
59
+ const failures = [];
60
+ FrameworkImport.importCompetencies(null, null, false, SERVER, null, null, (e) => failures.push(e));
61
+ FrameworkImport.importCompetencies(new EcFramework(), null, false, SERVER, null, null, (e) => failures.push(e));
62
+ FrameworkImport.importCompetencies(new EcFramework(), new EcFramework(), false, SERVER, null, null, (e) => failures.push(e));
63
+ assert.deepEqual(failures, ["Source Framework not set", "Target Framework not Set", "Source Has No Competencies"]);
64
+ });
65
+
66
+ describe('copy', () => {
67
+ it('copies competencies and relations with versioned ids and remapped endpoints', async () => {
68
+ const f = fixtures();
69
+ store = f.store;
70
+ const saved = [];
71
+ const { c, r } = await run(f.source, f.target, true, SERVER, savingRepo(saved));
72
+ assert.lengthOf(c, 2);
73
+ assert.lengthOf(r, 1);
74
+ for (const x of c.concat(r)) {
75
+ assert.isTrue(x.id.startsWith(SERVER));
76
+ assert.notEqual(x.id, x.shortId());
77
+ }
78
+ assert.deepEqual(c.map(x => x.name), ["Comp 1", "Comp 2"]);
79
+ assert.equal(r[0].source, c[1].shortId());
80
+ assert.equal(r[0].target, c[0].shortId());
81
+ assert.deepEqual(f.target.competency.slice().sort(), c.map(x => x.shortId()).sort());
82
+ assert.deepEqual(f.target.relation, [r[0].shortId()]);
83
+ assert.deepEqual(gets, [SRC + "c1", SRC + "c2", SRC + "r1"]);
84
+ });
85
+
86
+ it('copies with short ids when the repository server does not contain the server url', async () => {
87
+ const f = fixtures();
88
+ store = f.store;
89
+ const { c, r } = await run(f.source, f.target, true, "http://other.test/", savingRepo([]));
90
+ for (const x of c.concat(r)) {
91
+ assert.isTrue(x.id.startsWith("http://other.test/"));
92
+ assert.equal(x.id, x.shortId());
93
+ }
94
+ });
95
+
96
+ it('copies with versioned ids through EcRepository when there is no repository', async () => {
97
+ const f = fixtures();
98
+ store = f.store;
99
+ const saved = [];
100
+ sinon.stub(EcRepository, 'save').callsFake((o, s) => { saved.push(o); s("ok"); });
101
+ const { c, r } = await run(f.source, f.target, true, SERVER, null);
102
+ for (const x of c.concat(r))
103
+ assert.notEqual(x.id, x.shortId());
104
+ assert.lengthOf(saved, 5);
105
+ });
106
+ });
107
+
108
+ describe('link', () => {
109
+ it('links every competency and relation when the target has none', async () => {
110
+ const f = fixtures();
111
+ store = f.store;
112
+ const saved = [];
113
+ const { c, r } = await run(f.source, f.target, false, SERVER, savingRepo(saved));
114
+ assert.deepEqual(c.map(x => x.id), [SRC + "c1", SRC + "c2"]);
115
+ assert.deepEqual(r.map(x => x.id), [SRC + "r1"]);
116
+ assert.deepEqual(f.target.competency, [SRC + "c1", SRC + "c2"]);
117
+ assert.deepEqual(f.target.relation, [SRC + "r1"]);
118
+ assert.deepEqual(saved, [f.target, f.target]);
119
+ });
120
+
121
+ it('skips competencies already in the target by exact or unversioned id', async () => {
122
+ const f = fixtures();
123
+ store = f.store;
124
+ f.source.competency = [SRC + "c1", SRC + "c2/123"];
125
+ store[SRC + "c2/123"] = store[SRC + "c2"];
126
+ f.target.competency = [SRC + "c1", SRC + "c2"];
127
+ let done = false;
128
+ FrameworkImport.importCompetencies(f.source, f.target, false, SERVER, null, () => { done = true; }, () => { done = true; }, savingRepo([]));
129
+ await new Promise(r => setTimeout(r, 20));
130
+ assert.deepEqual(gets, []);
131
+ // Nothing was linked, so the completion check never fires.
132
+ assert.isFalse(done);
133
+ });
134
+
135
+ it('only fetches the competencies the target is missing, and then never completes', async () => {
136
+ const f = fixtures();
137
+ store = f.store;
138
+ f.target.competency = [SRC + "c1"];
139
+ const saved = [];
140
+ let done = false;
141
+ FrameworkImport.importCompetencies(f.source, f.target, false, SERVER, null, () => { done = true; }, () => { done = true; }, savingRepo(saved));
142
+ await new Promise(r => setTimeout(r, 20));
143
+ assert.deepEqual(gets, [SRC + "c2"]);
144
+ assert.deepEqual(f.target.competency, [SRC + "c1", SRC + "c2"]);
145
+ assert.deepEqual(saved, []);
146
+ assert.isFalse(done);
147
+ });
148
+
149
+ it('skips a relation whose id is in the target, or whose same-index competency id is in the target relations', async () => {
150
+ const f = fixtures();
151
+ store = f.store;
152
+ f.target.relation = [SRC + "r1"];
153
+ let done = false;
154
+ FrameworkImport.importCompetencies(f.source, f.target, false, SERVER, null, () => { done = true; }, () => { done = true; }, savingRepo([]));
155
+ await new Promise(r => setTimeout(r, 20));
156
+ assert.deepEqual(gets, [SRC + "c1", SRC + "c2"]);
157
+ assert.isFalse(done);
158
+
159
+ const g = fixtures();
160
+ store = g.store;
161
+ gets.length = 0;
162
+ // The relation check compares against source.competency[i], not source.relation[i].
163
+ g.target.relation = [SRC + "c1"];
164
+ FrameworkImport.importCompetencies(g.source, g.target, false, SERVER, null, () => { done = true; }, () => { done = true; }, savingRepo([]));
165
+ await new Promise(r => setTimeout(r, 20));
166
+ assert.deepEqual(gets, [SRC + "c1", SRC + "c2"]);
167
+ assert.isFalse(done);
168
+ });
169
+ });
170
+ });
@@ -0,0 +1,92 @@
1
+ require('../../index.js');
2
+ const chai = require('chai');
3
+ const sinon = require('sinon');
4
+ const EcRepository = require('../org/cassproject/ebac/repository/EcRepository.js');
5
+
6
+ const assert = chai.assert;
7
+
8
+ const SERVER = "http://repo.test/api/";
9
+
10
+ const competencyObject = (title, extra) => ({ lom: { general: Object.assign({ title: { string: title } }, extra) } });
11
+
12
+ describe('MedbiqImport', () => {
13
+ afterEach(() => {
14
+ sinon.restore();
15
+ MedbiqImport.medbiqXmlCompetencies = null;
16
+ });
17
+
18
+ describe('medbiqXmlLookForCompetencyObject', () => {
19
+ it('collects competency objects that have lom.general and skips the rest', () => {
20
+ MedbiqImport.medbiqXmlCompetencies = [];
21
+ MedbiqImport.medbiqXmlLookForCompetencyObject({
22
+ root: {
23
+ CompetencyObject: [
24
+ competencyObject("One", { description: { string: "Desc" }, identifier: { entry: "urn:1" } }),
25
+ competencyObject("Two"),
26
+ { lom: { other: 1 } },
27
+ { lom: null },
28
+ { nothing: true }
29
+ ],
30
+ nested: { CompetencyObject: competencyObject("Three") }
31
+ }
32
+ });
33
+ const found = MedbiqImport.medbiqXmlCompetencies;
34
+ assert.deepEqual(found.map(c => c.name), ["One", "Two", "Three"]);
35
+ assert.equal(found[0].description, "Desc");
36
+ assert.equal(found[0].url, "urn:1");
37
+ assert.equal(found[1].description, "");
38
+ assert.isUndefined(found[1].url);
39
+ });
40
+ });
41
+
42
+ describe('importCompetencies', () => {
43
+ const prepare = (names) => {
44
+ MedbiqImport.medbiqXmlCompetencies = names.map((n) => {
45
+ const c = new EcCompetency();
46
+ c.name = n;
47
+ return c;
48
+ });
49
+ };
50
+ const run = (serverUrl, repo) => new Promise((resolve, reject) => {
51
+ const progress = [];
52
+ MedbiqImport.importCompetencies(serverUrl, null, (comps) => resolve({ comps, progress }), reject, (p) => progress.push(JSON.parse(JSON.stringify(p))), repo);
53
+ });
54
+
55
+ it('generates versioned ids when the repository server contains the server url', async () => {
56
+ prepare(["a", "b"]);
57
+ const saved = [];
58
+ const repo = { selectedServer: SERVER + "extra", saveTo: (o, s) => { saved.push(o); s("ok"); } };
59
+ const { comps, progress } = await run(SERVER, repo);
60
+ assert.lengthOf(comps, 2);
61
+ assert.lengthOf(saved, 2);
62
+ for (const c of comps) {
63
+ assert.notEqual(c.id, c.shortId());
64
+ assert.isTrue(c.id.startsWith(SERVER));
65
+ }
66
+ assert.deepEqual(progress, [{ competencies: 2 }]);
67
+ });
68
+
69
+ it('generates short ids when the repository server does not contain the server url', async () => {
70
+ prepare(["a"]);
71
+ const repo = { selectedServer: SERVER, saveTo: (o, s) => s("ok") };
72
+ const { comps } = await run("http://other.test/", repo);
73
+ assert.equal(comps[0].id, comps[0].shortId());
74
+ assert.isTrue(comps[0].id.startsWith("http://other.test/"));
75
+ });
76
+
77
+ it('generates versioned ids and saves through EcRepository when there is no repository', async () => {
78
+ prepare(["a"]);
79
+ const save = sinon.stub(EcRepository, 'save').callsFake((o, s) => s("ok"));
80
+ const { comps } = await run(SERVER, null);
81
+ assert.notEqual(comps[0].id, comps[0].shortId());
82
+ assert.isTrue(save.calledOnce);
83
+ });
84
+
85
+ it('reports progress every INCREMENTAL_STEP saves and at the end', async () => {
86
+ prepare(["a", "b", "c", "d", "e", "f"]);
87
+ const repo = { selectedServer: SERVER, saveTo: (o, s) => s("ok") };
88
+ const { progress } = await run(SERVER, repo);
89
+ assert.deepEqual(progress, [{ competencies: 5 }, { competencies: 6 }]);
90
+ });
91
+ });
92
+ });