cassproject 5.0.20 → 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 (107) hide show
  1. package/package.json +49 -52
  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/forgeAsync.js +1 -9
  13. package/src/com/eduworks/ec/crypto/forgeAsyncNode.js +8 -8
  14. package/src/com/eduworks/ec/graph/EcDirectedGraph.js +1 -2
  15. package/src/com/eduworks/ec/graph/EcFrameworkGraph.js +18 -24
  16. package/src/com/eduworks/ec/promises/helpers.js +1 -3
  17. package/src/com/eduworks/ec/remote/EcRemote.js +9 -9
  18. package/src/com/eduworks/ec/task/EcAsyncTask.js +5 -20
  19. package/src/com/eduworks/ec/task/EcAsyncTaskManager.js +3 -4
  20. package/src/com/eduworks/ec/task/EcAsyncTaskWorker.js +9 -11
  21. package/src/com/eduworks/schema/cfd/competency/CfdFramework.js +36 -37
  22. package/src/com/eduworks/schema/ebac/EbacContact.js +3 -9
  23. package/src/com/eduworks/schema/ebac/EbacContactGrant.js +3 -9
  24. package/src/com/eduworks/schema/ebac/EbacCredential.js +3 -9
  25. package/src/com/eduworks/schema/ebac/EbacCredentialCommit.js +3 -9
  26. package/src/com/eduworks/schema/ebac/EbacCredentialRequest.js +3 -9
  27. package/src/com/eduworks/schema/ebac/EbacCredentials.js +3 -9
  28. package/src/com/eduworks/schema/ebac/EbacEncryptedSecret.js +3 -9
  29. package/src/com/eduworks/schema/ebac/EbacEncryptedValue.js +5 -12
  30. package/src/com/eduworks/schema/ebac/EbacSignature.js +1 -6
  31. package/src/index/env.js +6 -3
  32. package/src/index/polyfills.js +13 -27
  33. package/src/org/angles/schema/angles/VideoStory.js +1 -1
  34. package/src/org/cass/competency/EcCompetency.js +1 -2
  35. package/src/org/cass/competency/EcDirectory.js +1 -2
  36. package/src/org/cass/competency/EcFramework.js +9 -12
  37. package/src/org/cass/exporter/CSVExport.js +12 -13
  38. package/src/org/cass/importer/ASNImport.js +3 -6
  39. package/src/org/cass/importer/CSVImport.js +9 -17
  40. package/src/org/cass/importer/CTDLASNCSVConceptImport.js +25 -121
  41. package/src/org/cass/importer/CTDLASNCSVImport.js +88 -133
  42. package/src/org/cass/importer/FrameworkImport.js +16 -30
  43. package/src/org/cass/importer/MedbiqImport.js +3 -4
  44. package/src/org/cass/importer/TabStructuredImport.js +6 -6
  45. package/src/org/cass/profile/EcAssertion.js +40 -51
  46. package/src/org/cassproject/ebac/identity/EcContactGrant.js +2 -2
  47. package/src/org/cassproject/ebac/identity/EcIdentityManager.js +56 -62
  48. package/src/org/cassproject/ebac/identity/EcRekeyRequest.js +3 -2
  49. package/src/org/cassproject/ebac/identity/remote/EcRemoteIdentityManager.js +51 -62
  50. package/src/org/cassproject/ebac/identity/remote/OAuth2FileBasedRemoteIdentityManager.js +56 -70
  51. package/src/org/cassproject/ebac/identity/remote/RemoteIdentityManagerInterface.js +3 -3
  52. package/src/org/cassproject/ebac/repository/EcEncryptedValue.js +51 -44
  53. package/src/org/cassproject/ebac/repository/EcRepository.js +228 -301
  54. package/src/org/cassproject/general/repository/GeneralFile.js +8 -9
  55. package/src/org/cassproject/schema/cass/competency/Competency.js +10 -11
  56. package/src/org/cassproject/schema/cass/competency/Framework.js +10 -11
  57. package/src/org/cassproject/schema/cass/competency/Level.js +10 -11
  58. package/src/org/cassproject/schema/cass/competency/Relation.js +10 -11
  59. package/src/org/cassproject/schema/cass/competency/RollupRule.js +7 -7
  60. package/src/org/cassproject/schema/cass/profile/Assertion.js +20 -27
  61. package/src/org/cassproject/schema/cass/profile/AssertionEnvelope.js +3 -5
  62. package/src/org/cassproject/schema/general/EcRemoteLinkedData.js +41 -44
  63. package/src/org/json/ld/EcLinkedData.js +10 -12
  64. package/src/org/schema/EcOrganization.js +10 -10
  65. package/src/org/schema/EcQuestion.js +8 -8
  66. package/src/org/w3/skos/Concept.js +6 -6
  67. package/src/org/w3/skos/ConceptScheme.js +6 -6
  68. package/src/org/w3/skos/EcConcept.js +1 -2
  69. package/src/org/w3/skos/EcConceptScheme.js +1 -2
  70. package/src/test/0.start.test.js +1 -1
  71. package/src/test/0.testLibraries.test.js +172 -0
  72. package/src/test/1.ASNImport.test.js +122 -0
  73. package/src/test/1.CSVExport.test.js +57 -0
  74. package/src/test/1.CSVImport.test.js +185 -0
  75. package/src/test/1.CTDLASNCSVConceptImport.test.js +348 -0
  76. package/src/test/1.CTDLASNCSVImport.import.test.js +497 -0
  77. package/src/test/1.Ebac.schema.test.js +107 -0
  78. package/src/test/1.EcAssertion.decrypt.test.js +98 -0
  79. package/src/test/1.EcAssertion.pinned.test.js +160 -0
  80. package/src/test/1.EcCrypto.formats.test.js +293 -0
  81. package/src/test/1.EcCrypto.generateUUID.test.js +47 -0
  82. package/src/test/1.EcEncryptedValue.helpers.test.js +236 -0
  83. package/src/test/1.EcHelpers.misc.test.js +259 -0
  84. package/src/test/1.EcIdentityManager.keys.test.js +439 -0
  85. package/src/test/1.EcOrganization.rekey.test.js +68 -0
  86. package/src/test/1.EcRemote.responses.test.js +155 -0
  87. package/src/test/1.EcRemoteIdentityManager.flows.test.js +326 -0
  88. package/src/test/1.EcRemoteLinkedData.pinned.test.js +473 -0
  89. package/src/test/1.EcRepository.l0.test.js +14 -5
  90. package/src/test/1.EcRepository.l1.test.js +16 -7
  91. package/src/test/1.EcRepository.l2.test.js +20 -11
  92. package/src/test/1.EcRepository.unit.test.js +961 -0
  93. package/src/test/1.EcRsaOaep.test.js +2 -12
  94. package/src/test/1.EcRsaOaepAsync.test.js +2 -12
  95. package/src/test/1.EcRsaOaepAsyncWorker.test.js +2 -12
  96. package/src/test/1.EcRsaOaepAsyncWorker.workers.test.js +147 -0
  97. package/src/test/1.FrameworkImport.test.js +170 -0
  98. package/src/test/1.MedbiqImport.test.js +92 -0
  99. package/src/test/1.OAuth2FileBasedRemoteIdentityManager.test.js +399 -0
  100. package/src/test/1.TabStructuredImport.test.js +61 -0
  101. package/src/test/1.polyfills.test.js +89 -0
  102. package/src/test/1.schema.competency.pinned.test.js +384 -0
  103. package/src/test/3.EcFrameworkGraph.test.js +3 -2
  104. package/src/test/4.schema.test.js +33 -28
  105. package/src/test/4.skyId.test.js +4 -2
  106. package/src/test/cypressValue.js +9 -0
  107. package/src/test/2.EcCompetency.test.js +0 -17
@@ -0,0 +1,473 @@
1
+ require("../../index.js");
2
+ const { expect } = require("chai");
3
+ const sinon = require("sinon");
4
+ const EcCrypto = require("../com/eduworks/ec/crypto/EcCrypto.js");
5
+ const EcPk = require("../com/eduworks/ec/crypto/EcPk.js");
6
+ const EcPpk = require("../com/eduworks/ec/crypto/EcPpk.js");
7
+ const EcRsaOaepAsyncWorker = require("../com/eduworks/ec/crypto/EcRsaOaepAsyncWorker.js");
8
+
9
+ describe("EcRemoteLinkedData / EcLinkedData (pinned behaviour)", () => {
10
+ afterEach(() => sinon.restore());
11
+
12
+ describe("trimVersionFromUrl", () => {
13
+ let saved;
14
+ beforeEach(() => {
15
+ saved = EcRemoteLinkedData.caching;
16
+ });
17
+ afterEach(() => {
18
+ EcRemoteLinkedData.caching = saved;
19
+ EcRemoteLinkedData.tvfuCache = {};
20
+ });
21
+ let cases = [
22
+ [null, null],
23
+ ["http://x/api/data/a.b/guid/123", "http://x/api/data/a.b/guid"],
24
+ ["http://x/api/custom/data/a/guid/123", "http://x/api/custom/data/a/guid"],
25
+ ["http://x/api/data/a/guid", "http://x/api/data/a/guid"],
26
+ ["http://x/api/data/a/guid/12a", "http://x/api/data/a/guid/12a"],
27
+ ["http://x/api/data/a//123", "http://x/api/data/a"],
28
+ ["http://x/other/guid/123", "http://x/other/guid/123"],
29
+ ["http://x/api/data/123", "http://x/api/data"],
30
+ ["", ""],
31
+ ];
32
+ for (let caching of [false, true]) {
33
+ for (let [id, expected] of cases) {
34
+ it(`${JSON.stringify(id)} -> ${JSON.stringify(expected)} (caching ${caching})`, () => {
35
+ EcRemoteLinkedData.caching = caching;
36
+ EcRemoteLinkedData.tvfuCache = {};
37
+ expect(EcRemoteLinkedData.trimVersionFromUrl(id)).to.equal(expected);
38
+ if (caching && id != null)
39
+ expect(EcRemoteLinkedData.tvfuCache[id]).to.equal(expected);
40
+ else
41
+ expect(EcRemoteLinkedData.tvfuCache).to.deep.equal({});
42
+ expect(EcRemoteLinkedData.trimVersionFromUrl(id)).to.equal(expected);
43
+ });
44
+ }
45
+ }
46
+ it("returns a cached value when caching", () => {
47
+ EcRemoteLinkedData.caching = true;
48
+ EcRemoteLinkedData.tvfuCache = { "http://x/api/data/a/1": "cached" };
49
+ expect(EcRemoteLinkedData.trimVersionFromUrl("http://x/api/data/a/1")).to.equal("cached");
50
+ });
51
+ });
52
+
53
+ describe("ids and timestamps", () => {
54
+ let clock;
55
+ beforeEach(() => {
56
+ clock = sinon.useFakeTimers({ now: 1234567 });
57
+ sinon.stub(EcCrypto, "generateUUID").returns("UUID");
58
+ });
59
+ afterEach(() => clock.restore());
60
+ let thing = () => new EcRemoteLinkedData("https://schema.cassproject.org/0.4/a/b", "Thing");
61
+ it("getDottedType replaces every slash", () => {
62
+ expect(thing().getDottedType()).to.equal("schema.cassproject.org.0.4.a.b.Thing");
63
+ let h = new EcRemoteLinkedData("http://x.org", "T");
64
+ expect(h.getDottedType()).to.equal("x.org.T");
65
+ });
66
+ it("generateId appends data/type/uuid/now", () => {
67
+ let t = thing();
68
+ t.generateId("http://s/api/");
69
+ expect(t.id).to.equal("http://s/api/data/schema.cassproject.org.0.4.a.b.Thing/UUID/1234567");
70
+ t.generateId("http://s/api");
71
+ expect(t.id).to.equal("http://s/api/data/schema.cassproject.org.0.4.a.b.Thing/UUID/1234567");
72
+ t.generateId("http://s/ce-");
73
+ expect(t.id).to.equal("http://s/ce-data/schema.cassproject.org.0.4.a.b.Thing/UUID/1234567");
74
+ });
75
+ it("assignId appends data/type/identifier/now", () => {
76
+ let t = thing();
77
+ t.assignId("http://s/api", "my-id");
78
+ expect(t.id).to.equal("http://s/api/data/schema.cassproject.org.0.4.a.b.Thing/my-id/1234567");
79
+ t.assignId("http://s/api/", "my-id");
80
+ expect(t.id).to.equal("http://s/api/data/schema.cassproject.org.0.4.a.b.Thing/my-id/1234567");
81
+ });
82
+ it("updateTimestamp replaces the version with now", () => {
83
+ let t = thing();
84
+ t.id = "http://s/api/data/T/guid/1";
85
+ t.updateTimestamp();
86
+ expect(t.id).to.equal("http://s/api/data/T/guid/1234567");
87
+ t.id = "http://s/api/data/T/guid";
88
+ t.updateTimestamp();
89
+ expect(t.id).to.equal("http://s/api/data/T/guid");
90
+ t.id = "http://s/api/data/T//5";
91
+ t.updateTimestamp();
92
+ expect(t.id).to.equal("http://s/api/data/T/1234567");
93
+ t.id = "5";
94
+ t.updateTimestamp();
95
+ expect(t.id).to.equal("/1234567");
96
+ });
97
+ });
98
+
99
+ describe("addOwner", () => {
100
+ it("adds a pem once and clears the signature only when it adds", () => {
101
+ let t = new EcRemoteLinkedData("https://schema.cassproject.org/0.4", "Thing");
102
+ t.signature = ["s"];
103
+ t.addOwner({ toPem: () => "A" });
104
+ expect(t.owner).to.deep.equal(["A"]);
105
+ expect(t.signature).to.equal(null);
106
+ t.signature = ["s"];
107
+ t.addOwner({ toPem: () => "A" });
108
+ expect(t.owner).to.deep.equal(["A"]);
109
+ expect(t.signature).to.deep.equal(["s"]);
110
+ t.addOwner({ toPem: () => "B" });
111
+ expect(t.owner).to.deep.equal(["A", "B"]);
112
+ });
113
+ });
114
+
115
+ describe("invalid", () => {
116
+ it("requires id, context and an http(s) type", () => {
117
+ let t = new EcRemoteLinkedData("https://schema.cassproject.org/0.4", "Thing");
118
+ expect(t.invalid()).to.equal(true);
119
+ t.id = "x";
120
+ expect(t.invalid()).to.equal(false);
121
+ t.context = "http://a.org";
122
+ expect(t.invalid()).to.equal(false);
123
+ t.context = "urn:a";
124
+ expect(t.invalid()).to.equal(true);
125
+ t.context = null;
126
+ expect(t.invalid()).to.equal(true);
127
+ t.context = "x";
128
+ t.type = "https://y/Thing";
129
+ expect(t.invalid()).to.equal(false);
130
+ });
131
+ });
132
+
133
+ describe("getSearchStringByType", () => {
134
+ it("builds the full query", () => {
135
+ let r = new EcRemoteLinkedData("https://schema.cassproject.org/0.4", "Thing");
136
+ r.getTypes = () => ["http://a.org/x/T1", "https://b.org/T2", "urn:c"];
137
+ expect(r.getSearchStringByType()).to.equal(
138
+ '(@type:"http://a.org/x/T1" OR (@context:"http://a.org/x/" AND @type:"T1") OR @type:"https://b.org/T2" OR (@context:"https://b.org/" AND @type:"T2") OR @type:"urn:c" OR (@context:"" AND @type:"urn:c")' +
139
+ ' OR \\*encryptedType:"http://a.org/x/T1" OR (@context:"https://schema.cassproject.org/kbac/0.4" AND \\*encryptedType:"T1")' +
140
+ ' OR \\*encryptedType:"https://b.org/T2" OR (@context:"https://schema.cassproject.org/kbac/0.4" AND \\*encryptedType:"T2")' +
141
+ ' OR \\*encryptedType:"urn:c" OR (@context:"https://schema.cassproject.org/kbac/0.4" AND \\*encryptedType:"urn:c")' +
142
+ ' OR (@context:"https://a.org/x/" AND @type:"T1") OR (@context:"http://b.org/" AND @type:"T2") OR )'
143
+ );
144
+ });
145
+ it("returns () with no types", () => {
146
+ let r = new EcRemoteLinkedData("https://schema.cassproject.org/0.4", "Thing");
147
+ r.getTypes = () => [];
148
+ expect(r.getSearchStringByType()).to.equal("()");
149
+ });
150
+ it("for a real class", () => {
151
+ let s = new EcCompetency().getSearchStringByType();
152
+ expect(s.startsWith('(@type:"https://schema.cassproject.org/0.4/Competency" OR (@context:"https://schema.cassproject.org/0.4/" AND @type:"Competency")')).to.equal(true);
153
+ expect(s).to.include(' OR \\*encryptedType:"https://schema.cassproject.org/0.4/Competency" OR (@context:"https://schema.cassproject.org/kbac/0.4" AND \\*encryptedType:"Competency")');
154
+ expect(s.endsWith(' OR (@context:"https://schema.eduworks.com/cass/0.1/" AND @type:"competency"))')).to.equal(true);
155
+ });
156
+ });
157
+
158
+ describe("upgrade", () => {
159
+ it("copies @-prefixed KBAC fields to their plain names", () => {
160
+ let t = new EcRemoteLinkedData("https://schema.cassproject.org/0.4", "Thing");
161
+ sinon.stub(t, "handleForwarding");
162
+ t["@owner"] = ["o"];
163
+ t["@reader"] = ["r"];
164
+ t["@signature"] = ["s"];
165
+ t["@signatureSha256"] = ["s2"];
166
+ t["@encryptedType"] = "et";
167
+ t["@encryptedContext"] = "ec";
168
+ t.upgrade();
169
+ expect([t.owner, t.reader, t.signature, t.signatureSha256, t.encryptedType, t.encryptedContext])
170
+ .to.deep.equal([["o"], ["r"], ["s"], ["s2"], "et", "ec"]);
171
+ expect(t.handleForwarding.calledOnce).to.equal(true);
172
+ let u = new EcRemoteLinkedData("https://schema.cassproject.org/0.4", "Thing");
173
+ u.owner = ["keep"];
174
+ sinon.stub(u, "handleForwarding");
175
+ u.upgrade();
176
+ expect(u.owner).to.deep.equal(["keep"]);
177
+ expect(u.reader).to.equal(null);
178
+ });
179
+ });
180
+
181
+ describe("verify", () => {
182
+ let t, verify, verify256;
183
+ beforeEach(() => {
184
+ t = new EcRemoteLinkedData("https://schema.cassproject.org/0.4", "Thing");
185
+ t.id = "http://x/1";
186
+ sinon.stub(EcPk, "fromPem").callsFake((p) => "PK:" + p);
187
+ verify = sinon.stub(EcRsaOaepAsyncWorker, "verify");
188
+ verify256 = sinon.stub(EcRsaOaepAsyncWorker, "verifySha256");
189
+ sinon.stub(console, "log");
190
+ });
191
+ it("returns false without signatures", async () => {
192
+ expect(await t.verify()).to.equal(false);
193
+ });
194
+ for (let [result, expected] of [[true, true], [false, false], [1, true], ["1", true], ["true", false], [null, false], [undefined, false]]) {
195
+ it(`treats a verify result of ${JSON.stringify(result)} as ${expected}`, async () => {
196
+ t.owner = ["o1"];
197
+ t.signature = ["sig"];
198
+ verify.resolves(result);
199
+ expect(await t.verify()).to.equal(expected);
200
+ delete t.signature;
201
+ t.signatureSha256 = ["sig"];
202
+ verify256.resolves(result);
203
+ expect(await t.verify()).to.equal(expected);
204
+ });
205
+ }
206
+ it("treats a thrown verification as not verified and tries the next owner", async () => {
207
+ t.owner = ["o1", "o2", "o3"];
208
+ t.signature = ["sig"];
209
+ verify.onFirstCall().rejects(new Error("bad"));
210
+ verify.onSecondCall().resolves(true);
211
+ expect(await t.verify()).to.equal(true);
212
+ expect(verify.callCount).to.equal(2);
213
+ expect(verify.firstCall.args[0]).to.equal("PK:o1");
214
+ expect(verify.secondCall.args[0]).to.equal("PK:o2");
215
+ expect(verify.firstCall.args[2]).to.equal("sig");
216
+ });
217
+ it("requires every signature to verify", async () => {
218
+ t.owner = ["o1"];
219
+ t.signature = ["a", "b"];
220
+ verify.withArgs("PK:o1", sinon.match.any, "a").resolves(true);
221
+ verify.withArgs("PK:o1", sinon.match.any, "b").resolves(false);
222
+ expect(await t.verify()).to.equal(false);
223
+ });
224
+ it("returns false with signatures but no owners", async () => {
225
+ t.signature = ["a"];
226
+ expect(await t.verify()).to.equal(false);
227
+ delete t.signature;
228
+ t.signatureSha256 = ["a"];
229
+ expect(await t.verify()).to.equal(false);
230
+ });
231
+ it("needs both kinds to verify when both are present", async () => {
232
+ t.owner = ["o1"];
233
+ t.signature = ["a"];
234
+ t.signatureSha256 = ["b"];
235
+ verify.resolves(true);
236
+ verify256.rejects(new Error("x"));
237
+ expect(await t.verify()).to.equal(false);
238
+ verify256.resolves(true);
239
+ expect(await t.verify()).to.equal(true);
240
+ expect(verify256.lastCall.args[1]).to.equal(t.toSignableJson());
241
+ });
242
+ it("returns false for empty signature arrays", async () => {
243
+ t.signature = [];
244
+ t.signatureSha256 = [];
245
+ expect(await t.verify()).to.equal(false);
246
+ });
247
+ });
248
+
249
+ describe("EcLinkedData", () => {
250
+ it("getFullType", () => {
251
+ let l = new EcLinkedData(null, "T");
252
+ expect(l.getFullType()).to.equal("T");
253
+ l = new EcLinkedData("https://a.org", "T");
254
+ expect(l.getFullType()).to.equal("https://a.org/T");
255
+ l.type = "https://b.org/U";
256
+ expect(l.getFullType()).to.equal("https://b.org/U");
257
+ l.type = "xhttpx";
258
+ expect(l.getFullType()).to.equal("xhttpx");
259
+ l.context = { ns: "https://c.org", "@vocab": "https://v.org/" };
260
+ l.type = "ns:V";
261
+ expect(l.getFullType()).to.equal("https://c.org/V");
262
+ l.type = "W";
263
+ expect(l.getFullType()).to.equal("https://v.org/W");
264
+ });
265
+ describe("toSignableJson", () => {
266
+ let mk = (ctx, type) => {
267
+ let t = new EcRemoteLinkedData(ctx, "x");
268
+ t.type = type;
269
+ t.id = "http://x/1";
270
+ t.owner = ["o"];
271
+ t.reader = ["r"];
272
+ t.signature = ["s"];
273
+ t.signatureSha256 = ["s2"];
274
+ t.name = "n";
275
+ return t;
276
+ };
277
+ it("strips owner/reader/signatures for eduworks 0.1 types", () => {
278
+ let t = mk("http://schema.eduworks.com/cass/0.1", "http://schema.eduworks.com/cass/0.1/competency");
279
+ expect(t.toSignableJson()).to.equal('{"@context":"http://schema.eduworks.com/cass/0.1","@type":"http://schema.eduworks.com/cass/0.1/competency","name":"n","signatureSha256":["s2"]}');
280
+ });
281
+ it("strips only signatures and id for other types", () => {
282
+ let t = mk("http://schema.eduworks.com/cass/0.2", "http://schema.eduworks.com/cass/0.2/competency");
283
+ expect(t.toSignableJson()).to.equal('{"@context":"http://schema.eduworks.com/cass/0.2","@type":"http://schema.eduworks.com/cass/0.2/competency","name":"n","owner":["o"],"reader":["r"]}');
284
+ let u = mk("https://schema.cassproject.org/0.1", "https://schema.cassproject.org/0.1/Competency");
285
+ expect(u.toSignableJson()).to.equal('{"@context":"https://schema.cassproject.org/0.1","@type":"https://schema.cassproject.org/0.1/Competency","name":"n","owner":["o"],"reader":["r"]}');
286
+ });
287
+ });
288
+ describe("recast", () => {
289
+ let report;
290
+ beforeEach(() => {
291
+ report = sinon.stub(global.auditLogger, "report");
292
+ });
293
+ it("expands, compacts into the target context and copies the result", async () => {
294
+ let t = new EcLinkedData("https://a.org", "T");
295
+ t.name = "old";
296
+ sinon.stub(jsonld, "expand").resolves(["EXPANDED"]);
297
+ let compact = sinon.stub(jsonld, "compact").resolves({ "@context": "https://a.org", "@type": "T", name: "new" });
298
+ let loader = () => { };
299
+ let r = await t.recast("TRANS", "TARGET", loader);
300
+ expect(r).to.equal(t);
301
+ expect(t.name).to.equal("new");
302
+ expect(t["@context"]).to.equal("TARGET");
303
+ expect(jsonld.expand.firstCall.args[0]["@context"]).to.equal("TRANS");
304
+ expect(jsonld.expand.firstCall.args[1]).to.deep.equal({ documentLoader: loader });
305
+ expect(compact.firstCall.args).to.deep.equal([["EXPANDED"], "TARGET", { documentLoader: loader }]);
306
+ });
307
+ it("defaults the target context to the object's own @context", async () => {
308
+ let t = new EcLinkedData("https://a.org", "T");
309
+ sinon.stub(jsonld, "expand").resolves([]);
310
+ let compact = sinon.stub(jsonld, "compact").resolves({ "@context": "https://a.org", "@type": "T" });
311
+ await t.recast("TRANS");
312
+ expect(compact.firstCall.args).to.deep.equal([[], "https://a.org", undefined]);
313
+ expect(t["@context"]).to.equal("https://a.org");
314
+ });
315
+ it("reports and returns undefined when expand fails", async () => {
316
+ let t = new EcLinkedData("https://a.org", "T");
317
+ sinon.stub(jsonld, "expand").rejects(new Error("boom"));
318
+ let compact = sinon.stub(jsonld, "compact");
319
+ expect(await t.recast("TRANS", "TARGET")).to.equal(undefined);
320
+ expect(report.firstCall.args.slice(2)).to.deep.equal(["EcLinkedDataRecast", "boom"]);
321
+ expect(compact.called).to.equal(false);
322
+ });
323
+ it("reports the error object and returns undefined when compact fails", async () => {
324
+ let t = new EcLinkedData("https://a.org", "T");
325
+ t.name = "keep";
326
+ let err = new Error("compact");
327
+ sinon.stub(jsonld, "expand").resolves([]);
328
+ sinon.stub(jsonld, "compact").rejects(err);
329
+ expect(await t.recast("TRANS", "TARGET")).to.equal(undefined);
330
+ expect(report.firstCall.args.slice(2)).to.deep.equal(["EcLinkedDataRecast", err]);
331
+ expect(t.name).to.equal("keep");
332
+ });
333
+ });
334
+ describe("compact", () => {
335
+ it("copies the compacted form and calls success with this", async () => {
336
+ let t = new EcLinkedData("https://a.org", "T");
337
+ sinon.stub(jsonld, "compact").resolves({ "@context": "https://a.org", "@type": "T", name: "c" });
338
+ let success = sinon.spy(), failure = sinon.spy();
339
+ let before = t.toJson();
340
+ await t.compact("CTX", success, failure);
341
+ expect(jsonld.compact.firstCall.args).to.deep.equal([before, "CTX"]);
342
+ expect(t.name).to.equal("c");
343
+ expect(success.calledOnceWithExactly(t)).to.equal(true);
344
+ expect(failure.called).to.equal(false);
345
+ });
346
+ it("calls failure on error", async () => {
347
+ let t = new EcLinkedData("https://a.org", "T");
348
+ let err = new Error("x");
349
+ sinon.stub(jsonld, "compact").rejects(err);
350
+ let success = sinon.spy(), failure = sinon.spy();
351
+ await t.compact("CTX", success, failure);
352
+ expect(failure.calledOnceWithExactly(err)).to.equal(true);
353
+ expect(success.called).to.equal(false);
354
+ });
355
+ });
356
+ });
357
+
358
+ describe("EcQuestion", () => {
359
+ let q = (props) => Object.assign(new EcQuestion(), props);
360
+ it("getQuestionType", () => {
361
+ expect(q({}).getQuestionType({})).to.equal(EcQuestion.HAND_GRADED_ESSAY);
362
+ expect(q({ acceptedAnswer: [] }).getQuestionType({})).to.equal(EcQuestion.HAND_GRADED_ESSAY);
363
+ expect(q({ acceptedAnswer: ["a"] }).getQuestionType({})).to.equal(EcQuestion.SHORT_ANSWER);
364
+ expect(q({ acceptedAnswer: ["a"], text: "fill __ in" }).getQuestionType({})).to.equal(EcQuestion.FILL_IN_THE_BLANK);
365
+ expect(q({ acceptedAnswer: ["a"], text: "fill _ in" }).getQuestionType({})).to.equal(EcQuestion.SHORT_ANSWER);
366
+ expect(q({ acceptedAnswer: ["a"], text: ["__"] }).getQuestionType({})).to.equal(EcQuestion.FILL_IN_THE_BLANK);
367
+ expect(q({ acceptedAnswer: ["a"], text: ["a__"] }).getQuestionType({})).to.equal(EcQuestion.SHORT_ANSWER);
368
+ expect(q({ acceptedAnswer: ["a"], suggestedAnswer: [] }).getQuestionType({})).to.equal(EcQuestion.SHORT_ANSWER);
369
+ expect(q({ acceptedAnswer: ["a"], suggestedAnswer: [], text: "__" }).getQuestionType({})).to.equal(EcQuestion.FILL_IN_THE_BLANK);
370
+ expect(q({ acceptedAnswer: ["a"], suggestedAnswer: [], text: "_" }).getQuestionType({})).to.equal(EcQuestion.SHORT_ANSWER);
371
+ expect(q({ acceptedAnswer: ["a", "b"], suggestedAnswer: ["c"] }).getQuestionType({})).to.equal(EcQuestion.MULTIPLE_SELECT);
372
+ expect(q({ acceptedAnswer: ["a"], suggestedAnswer: ["c"] }).getQuestionType({})).to.equal(EcQuestion.MULTIPLE_CHOICE);
373
+ expect(() => q({ acceptedAnswer: ["a"], text: {} }).getQuestionType({})).to.throw(TypeError);
374
+ });
375
+ it("cementAnswerId replaces matching versions", () => {
376
+ let o = q({ acceptedAnswer: ["http://x/api/data/a/1/1", "b"], suggestedAnswer: ["http://x/api/data/a/1/2", "c"] });
377
+ o.cementAnswerId("http://x/api/data/a/1/9");
378
+ expect(o.acceptedAnswer).to.deep.equal(["http://x/api/data/a/1/9", "b"]);
379
+ expect(o.suggestedAnswer).to.deep.equal(["http://x/api/data/a/1/9", "c"]);
380
+ q({}).cementAnswerId("x");
381
+ });
382
+ it("add/remove answers", () => {
383
+ let o = q({});
384
+ o.addAcceptedAnswer({ id: "http://x/api/data/a/g1/1" });
385
+ o.addSuggestedAnswer({ id: "http://x/api/data/b/g1/1" });
386
+ expect(o.acceptedAnswers()).to.deep.equal(["http://x/api/data/a/g1/1"]);
387
+ expect(o.suggestedAnswers()).to.deep.equal(["http://x/api/data/b/g1/1"]);
388
+ o.removeAcceptedAnswerById("http://x/api/data/a/g1");
389
+ o.removeSuggestedAnswerById("http://x/api/data/b/g1/5");
390
+ expect(o.acceptedAnswer).to.deep.equal([]);
391
+ expect(o.suggestedAnswer).to.deep.equal([]);
392
+ q({}).removeAcceptedAnswerById("x");
393
+ q({}).removeSuggestedAnswerById("x");
394
+ });
395
+ let throwing = [
396
+ ["cementAnswerId", { acceptedAnswer: "x" }, "Accepted Answer is not Array"],
397
+ ["cementAnswerId", { suggestedAnswer: "x" }, "Suggested Answer is not Array"],
398
+ ["addAcceptedAnswer", { acceptedAnswer: "x" }, "Accepted Answer is not Array"],
399
+ ["addSuggestedAnswer", { suggestedAnswer: "x" }, "Suggested Answer is not Array"],
400
+ ["removeSuggestedAnswerById", { suggestedAnswer: "x" }, "Suggested Answer is not Array"],
401
+ ["removeAcceptedAnswerById", { acceptedAnswer: "x" }, "Accepted Answer is not Array"],
402
+ ];
403
+ for (let [method, props, msg] of throwing) {
404
+ it(`${method} throws "${msg}" for a non-array`, () => {
405
+ let err = null;
406
+ try {
407
+ q(props)[method]({ id: "a" });
408
+ } catch (e) {
409
+ err = e;
410
+ }
411
+ expect(err).to.be.instanceOf(Error);
412
+ expect(err.constructor).to.equal(Error);
413
+ expect(err.toString()).to.equal("Error: " + msg);
414
+ expect(err.message).to.equal(msg);
415
+ });
416
+ }
417
+ });
418
+
419
+ describe("EcOrganization", () => {
420
+ let person = (id) => ({ shortId: () => id });
421
+ it("addEmployee adds a person's short id once", () => {
422
+ let o = new EcOrganization();
423
+ o.addEmployee(person("p1"));
424
+ o.addEmployee(person("p2"));
425
+ o.addEmployee(person("p1"));
426
+ expect(o.employee).to.deep.equal(["p1", "p2"]);
427
+ o.employee = "x";
428
+ expect(() => o.addEmployee(person("p1"))).to.throw("Employee is not Array");
429
+ });
430
+ it("movePersonMembersToEmployee moves ids containing 'person' (case-insensitive), skipping the element after each move", () => {
431
+ let o = new EcOrganization();
432
+ o.member = ["http://x/Person/1", "http://x/PERSON/2", "http://x/org/3", "http://x/person/4", "http://x/person/1"];
433
+ o.employee = ["http://x/person/4"];
434
+ o.movePersonMembersToEmployee();
435
+ expect(o.employee).to.deep.equal(["http://x/person/4", "http://x/Person/1"]);
436
+ expect(o.member).to.deep.equal(["http://x/PERSON/2", "http://x/org/3", "http://x/person/1"]);
437
+ let p = new EcOrganization();
438
+ p.movePersonMembersToEmployee();
439
+ expect(p.employee).to.equal(undefined);
440
+ p.member = "x";
441
+ p.movePersonMembersToEmployee();
442
+ expect(p.employee).to.deep.equal([]);
443
+ });
444
+ it("ppkListToPemArrayString", () => {
445
+ let o = new EcOrganization();
446
+ expect(o.ppkListToPemArrayString(null)).to.equal("[]");
447
+ expect(o.ppkListToPemArrayString([])).to.equal("[]");
448
+ expect(o.ppkListToPemArrayString([{ toPem: () => "a" }, { toPem: () => "b" }])).to.equal('["a","b"]');
449
+ });
450
+ it("rekeyAndSave without a repository fails or reports", async () => {
451
+ let o = new EcOrganization();
452
+ let failure = sinon.spy();
453
+ expect(await o.rekeyAndSave(null, failure, null, {})).to.equal(undefined);
454
+ expect(failure.args).to.deep.equal([["Repository cannot be null for a rekey operation"]]);
455
+ let report = sinon.stub(global.auditLogger, "report");
456
+ expect(await o.rekeyAndSave(null, null, null, {})).to.equal(undefined);
457
+ expect(report.firstCall.args.slice(2)).to.deep.equal(["EcOrgRekeyAndSave", "Repository cannot be null for a rekey operation"]);
458
+ });
459
+ it("getOrgKeys decrypts the encrypted key set in order", async () => {
460
+ let o = new EcOrganization();
461
+ o[EcOrganization.ORG_PPK_SET_KEY] = { "@type": "EncryptedValue", "@context": Ebac.context, payload: "p" };
462
+ sinon.stub(EcEncryptedValue.prototype, "decryptIntoString").resolves('["k1","k2"]');
463
+ sinon.stub(EcPpk, "fromPem").callsFake((p) => "PPK:" + p);
464
+ expect(await o.getOrgKeys()).to.deep.equal(["PPK:k1", "PPK:k2"]);
465
+ expect(await o.getCurrentOrgKey()).to.equal("PPK:k2");
466
+ o[EcOrganization.ORG_PPK_SET_KEY] = ["plain"];
467
+ expect(await o.getOrgKeys()).to.deep.equal(["plain"]);
468
+ delete o[EcOrganization.ORG_PPK_SET_KEY];
469
+ expect(await o.getOrgKeys()).to.deep.equal([]);
470
+ expect(await o.getCurrentOrgKey()).to.equal(null);
471
+ });
472
+ });
473
+ });
@@ -1,3 +1,4 @@
1
+ const cypressValue = require('./cypressValue.js');
1
2
  const fs = require('fs');
2
3
  const https = require('https');
3
4
 
@@ -91,10 +92,10 @@ describe("EcRepository (L0 Cache)", () => {
91
92
  EcRepository.caching = false;
92
93
  EcRepository.cachingL2 = false;
93
94
  EcIdentityManager.default.clearIdentities();
94
- if ((typeof Cypress !== 'undefined') && Cypress != null && Cypress.env != null)
95
- process.env.CASS_LOOPBACK = Cypress.env('CASS_LOOPBACK');
96
- if ((typeof Cypress !== 'undefined') && Cypress != null && Cypress.env != null)
97
- process.env.TESTLEVEL = Cypress.env('TESTLEVEL');
95
+ if ((typeof Cypress !== 'undefined') && Cypress != null)
96
+ process.env.CASS_LOOPBACK = cypressValue('CASS_LOOPBACK');
97
+ if ((typeof Cypress !== 'undefined') && Cypress != null)
98
+ process.env.TESTLEVEL = cypressValue('TESTLEVEL');
98
99
  console.log(process.env.CASS_LOOPBACK);
99
100
  await repo.init(process.env.CASS_LOOPBACK || "http://localhost/api/", null, null, console.log);
100
101
  if (EcIdentityManager.default.ids.length > 0)
@@ -203,6 +204,8 @@ describe("EcRepository (L0 Cache)", () => {
203
204
  rld.addOwner(newId1.ppk.toPk());
204
205
  rld.setName("Some Thing");
205
206
  rld.setDescription("Some Description");
207
+ assert.equal(rld.getName(), "Some Thing");
208
+ assert.isTrue(rld.hasOwner(newId1.ppk.toPk()));
206
209
  });
207
210
  it('save (ecrepository)', async () => {
208
211
  await changeNameAndSaveAndCheckRepo(rld);
@@ -265,6 +268,8 @@ describe("EcRepository (L0 Cache)", () => {
265
268
  rld.addOwner(newId1.ppk.toPk());
266
269
  rld.setName("Some Thing");
267
270
  rld.setDescription("Some Description");
271
+ assert.equal(rld.getName(), "Some Thing");
272
+ assert.isTrue(rld.hasOwner(newId1.ppk.toPk()));
268
273
  });
269
274
  it('save (multiput)', async () => {
270
275
  await changeNameAndSaveAndCheckMultiput(rld);
@@ -327,6 +332,8 @@ describe("EcRepository (L0 Cache)", () => {
327
332
  rld.addOwner(newId1.ppk.toPk());
328
333
  rld.setName("Some Thing");
329
334
  rld.setDescription("Some Description");
335
+ assert.equal(rld.getName(), "Some Thing");
336
+ assert.isTrue(rld.hasOwner(newId1.ppk.toPk()));
330
337
  });
331
338
  it('registered save (to)', async () => {
332
339
  await changeNameAndSaveAndCheck(rld);
@@ -379,6 +386,8 @@ describe("EcRepository (L0 Cache)", () => {
379
386
  rld.addOwner(newId1.ppk.toPk());
380
387
  rld.setName("Some Thing");
381
388
  rld.setDescription("Some Description");
389
+ assert.equal(rld.getName(), "Some Thing");
390
+ assert.isTrue(rld.hasOwner(newId1.ppk.toPk()));
382
391
  });
383
392
  it('registered save (multiput)', async () => {
384
393
  await changeNameAndSaveAndCheckMultiput(rld);
@@ -547,7 +556,7 @@ describe("EcRepository (L0 Cache)", () => {
547
556
  results2 = await EcRepository.get(rld6.shortId(), null, null, repo);
548
557
  assert.equal(results2, null);
549
558
  });
550
- it('Turn off caching', async () => {
559
+ after(() => {
551
560
  EcRepository.caching = false;
552
561
  EcRepository.cachingL2 = false;
553
562
  });
@@ -1,3 +1,4 @@
1
+ const cypressValue = require('./cypressValue.js');
1
2
  const fs = require('fs');
2
3
  const https = require('https');
3
4
 
@@ -88,10 +89,10 @@ describe("EcRepository (L1 Cache)", () => {
88
89
  EcRepository.caching = true;
89
90
  EcRepository.cachingL2 = false;
90
91
  EcIdentityManager.default.clearIdentities();
91
- if ((typeof Cypress !== 'undefined') && Cypress != null && Cypress.env != null)
92
- process.env.CASS_LOOPBACK = Cypress.env('CASS_LOOPBACK');
93
- if ((typeof Cypress !== 'undefined') && Cypress != null && Cypress.env != null)
94
- process.env.TESTLEVEL = Cypress.env('TESTLEVEL');
92
+ if ((typeof Cypress !== 'undefined') && Cypress != null)
93
+ process.env.CASS_LOOPBACK = cypressValue('CASS_LOOPBACK');
94
+ if ((typeof Cypress !== 'undefined') && Cypress != null)
95
+ process.env.TESTLEVEL = cypressValue('TESTLEVEL');
95
96
  console.log(process.env.CASS_LOOPBACK);
96
97
  await repo.init(process.env.CASS_LOOPBACK || "http://localhost/api/", null, null, console.log);
97
98
  if (EcIdentityManager.default.ids.length > 0)
@@ -202,6 +203,8 @@ describe("EcRepository (L1 Cache)", () => {
202
203
  rld.addOwner(newId1.ppk.toPk());
203
204
  rld.setName("Some Thing");
204
205
  rld.setDescription("Some Description");
206
+ assert.equal(rld.getName(), "Some Thing");
207
+ assert.isTrue(rld.hasOwner(newId1.ppk.toPk()));
205
208
  });
206
209
  it('save (ecrepository)', async () => {
207
210
  await changeNameAndSaveAndCheckRepo(rld);
@@ -264,6 +267,8 @@ describe("EcRepository (L1 Cache)", () => {
264
267
  rld.addOwner(newId1.ppk.toPk());
265
268
  rld.setName("Some Thing");
266
269
  rld.setDescription("Some Description");
270
+ assert.equal(rld.getName(), "Some Thing");
271
+ assert.isTrue(rld.hasOwner(newId1.ppk.toPk()));
267
272
  });
268
273
  it('save (multiput)', async () => {
269
274
  await changeNameAndSaveAndCheckMultiput(rld);
@@ -326,6 +331,8 @@ describe("EcRepository (L1 Cache)", () => {
326
331
  rld.addOwner(newId1.ppk.toPk());
327
332
  rld.setName("Some Thing");
328
333
  rld.setDescription("Some Description");
334
+ assert.equal(rld.getName(), "Some Thing");
335
+ assert.isTrue(rld.hasOwner(newId1.ppk.toPk()));
329
336
  });
330
337
  it('registered save (to)', async () => {
331
338
  await changeNameAndSaveAndCheck(rld);
@@ -378,6 +385,8 @@ describe("EcRepository (L1 Cache)", () => {
378
385
  rld.addOwner(newId1.ppk.toPk());
379
386
  rld.setName("Some Thing");
380
387
  rld.setDescription("Some Description");
388
+ assert.equal(rld.getName(), "Some Thing");
389
+ assert.isTrue(rld.hasOwner(newId1.ppk.toPk()));
381
390
  });
382
391
  it('registered save (multiput)', async () => {
383
392
  await changeNameAndSaveAndCheckMultiput(rld);
@@ -438,8 +447,8 @@ describe("EcRepository (L1 Cache)", () => {
438
447
  let results2 = await EcRepository.get(rld.shortId(), null, null, repo);
439
448
  assert.equal(results2, null);
440
449
  });
441
- it('multidelete', async () => {
442
- if (process.env.TESTLEVEL == 15 || process.env.TESTLEVEL?.trim() == "15") return;
450
+ it('multidelete', async function () {
451
+ if (process.env.TESTLEVEL == 15 || process.env.TESTLEVEL?.trim() == "15") this.skip();
443
452
  rld = new schema.Thing();
444
453
  rld.generateId(repo.selectedServer);
445
454
  rld.addOwner(newId1.ppk.toPk());
@@ -545,7 +554,7 @@ describe("EcRepository (L1 Cache)", () => {
545
554
  results2 = await EcRepository.get(rld6.shortId(), null, null, repo);
546
555
  assert.equal(results2, null);
547
556
  });
548
- it('Turn off caching', async () => {
557
+ after(() => {
549
558
  EcRepository.caching = false;
550
559
  EcRepository.cachingL2 = false;
551
560
  });