@adobe/acc-js-sdk 1.0.3 → 1.0.7

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.
@@ -19,61 +19,65 @@ governing permissions and limitations under the License.
19
19
 
20
20
  const assert = require('assert');
21
21
  const crypto = require('../src/crypto.js');
22
+ const { Mock } = require('./mock.js');
22
23
  const Cipher = crypto.Cipher;
23
24
 
24
25
  describe('crypto', function() {
25
26
 
26
27
  it("Should decrypt password", function() {
27
- const cipher = new Cipher("HMLmn6uvWr8wu1Akt8UORr07YbC64u1FVW7ENAxNjpo=");
28
- var decrypted = cipher.decryptPassword("@57QS5VHMb9BCsojLVrKI/Q==");
28
+ const cipher = new Cipher(Mock.makeKey());
29
+ const encrypted = cipher.encryptPassword("mid");
30
+ var decrypted = cipher.decryptPassword(encrypted);
29
31
  assert.equal(decrypted, "mid");
30
32
  });
31
33
 
32
34
  it("Should fail on invalid encrypted string", function() {
33
- const cipher = new Cipher("HMLmn6uvWr8wu1Akt8UORr07YbC64u1FVW7ENAxNjpo=");
35
+ const cipher = new Cipher(Mock.makeKey());
34
36
  assert.throws(function() {
35
37
  cipher.decryptPassword("Hello");
36
38
  });
37
39
  });
38
40
 
39
41
  it("Should decrypt password twice", function() {
40
- const cipher = new Cipher("HMLmn6uvWr8wu1Akt8UORr07YbC64u1FVW7ENAxNjpo=");
41
- var decrypted = cipher.decryptPassword("@57QS5VHMb9BCsojLVrKI/Q==");
42
+ const cipher = new Cipher(Mock.makeKey());
43
+ const encrypted = cipher.encryptPassword("mid");
44
+ var decrypted = cipher.decryptPassword(encrypted);
42
45
  assert.equal(decrypted, "mid");
43
- decrypted = cipher.decryptPassword("@57QS5VHMb9BCsojLVrKI/Q==");
46
+ decrypted = cipher.decryptPassword(encrypted);
44
47
  assert.equal(decrypted, "mid");
45
48
  });
46
49
 
47
50
  it("Should decrypt password after failure", function() {
48
- const cipher = new Cipher("HMLmn6uvWr8wu1Akt8UORr07YbC64u1FVW7ENAxNjpo=");
51
+ const cipher = new Cipher(Mock.makeKey());
49
52
  assert.throws(function() {
50
53
  cipher.decryptPassword("@Hello");
51
54
  });
52
55
  // make sure state is valid after failure
53
- var decrypted = cipher.decryptPassword("@57QS5VHMb9BCsojLVrKI/Q==");
56
+ const encrypted = cipher.encryptPassword("mid");
57
+ var decrypted = cipher.decryptPassword(encrypted);
54
58
  assert.equal(decrypted, "mid");
55
59
  });
56
60
 
57
61
  it("Should handle plain text passwords", function() {
58
- const cipher = new Cipher("llL97E5mAvLTxgT1fsAH2kjLqZXKCGHfDyR9q0C6Ivs=");
62
+ const cipher = new Cipher(Mock.makeKey());
59
63
  var decrypted = cipher.decryptPassword("__PLAINTEXT__pass");
60
64
  assert.equal(decrypted, "pass");
61
65
  });
62
66
 
63
67
  it("Should fail if no marker", function() {
64
- const cipher = new Cipher("HMLmn6uvWr8wu1Akt8UORr07YbC64u1FVW7ENAxNjpo=");
68
+ const cipher = new Cipher(Mock.makeKey());
65
69
  expect(() => { cipher.decryptPassword("57QS5VHMb9BCsojLVrKI/Q=="); }).toThrow("SDK-000011");
66
70
  });
67
71
 
68
72
  it("Should support empty passwords", function() {
69
- const cipher = new Cipher("HMLmn6uvWr8wu1Akt8UORr07YbC64u1FVW7ENAxNjpo=");
73
+ const cipher = new Cipher(Mock.makeKey());
70
74
  assert.equal(cipher.decryptPassword(""), "");
71
75
  assert.equal(cipher.decryptPassword(null), "");
72
76
  assert.equal(cipher.decryptPassword(undefined), "");
73
77
  });
74
78
 
75
79
  it("Encrypt - decrypt", function() {
76
- const cipher = new Cipher("HMLmn6uvWr8wu1Akt8UORr07YbC64u1FVW7ENAxNjpo=");
80
+ const cipher = new Cipher(Mock.makeKey());
77
81
  expect(cipher.decryptPassword(cipher.encryptPassword(""))).toBe("");
78
82
  expect(cipher.decryptPassword(cipher.encryptPassword("1"))).toBe("1");
79
83
  expect(cipher.decryptPassword(cipher.encryptPassword("Hello"))).toBe("Hello");
@@ -523,4 +523,27 @@ describe('DomUtil', function() {
523
523
  });
524
524
  });
525
525
 
526
+ describe("Text and CDATA nodes", () => {
527
+ it("Should handle cdata node", () => {
528
+ const xml = DomUtil.parse(`<workflow-collection><workflow id="1840" internalName="cleanup" label="Database cleanup"><desc><![CDATA[Ensure that obsolete data are deleted from the database.]]></desc></workflow></workflow-collection>`);
529
+ const json = DomUtil.toJSON(xml);
530
+ expect(json.workflow[0]["$desc"]).toBe("Ensure that obsolete data are deleted from the database.");
531
+ expect(json.workflow[0]["desc"]).toBeUndefined();
532
+ });
533
+
534
+ it("Should handle text node", () => {
535
+ const xml = DomUtil.parse(`<workflow-collection><workflow id="1840" internalName="cleanup" label="Database cleanup"><desc>Ensure that obsolete data are deleted from the database.</desc></workflow></workflow-collection>`);
536
+ const json = DomUtil.toJSON(xml);
537
+ expect(json.workflow[0]["$desc"]).toBe("Ensure that obsolete data are deleted from the database.");
538
+ expect(json.workflow[0]["desc"]).toBeUndefined();
539
+ });
540
+
541
+ it("Should handle empty node", () => {
542
+ const xml = DomUtil.parse(`<workflow-collection><workflow id="1840" internalName="cleanup" label="Database cleanup"><desc/></workflow></workflow-collection>`);
543
+ const json = DomUtil.toJSON(xml);
544
+ expect(json.workflow[0]["$desc"]).toBeUndefined();
545
+ expect(json.workflow[0]["desc"]).toStrictEqual({});
546
+ });
547
+ });
548
+
526
549
  });
package/test/mock.js CHANGED
@@ -17,18 +17,31 @@ governing permissions and limitations under the License.
17
17
  *
18
18
  *********************************************************************************/
19
19
  const sdk = require('../src/index.js');
20
+ const crypto = require("crypto");
21
+
22
+ const makeKey = () => {
23
+ const a = [];
24
+ for (let i=0; i<32; i++) {
25
+ a.push(Math.floor(crypto.randomInt(0, 256)));
26
+ }
27
+ const buffer = Buffer.from(a);
28
+ const s = buffer.toString('base64');
29
+ return s;
30
+ }
20
31
 
21
32
  async function makeAnonymousClient(options) {
22
33
  const connectionParameters = sdk.ConnectionParameters.ofAnonymousUser("http://acc-sdk:8080", options);
23
34
  const client = await sdk.init(connectionParameters);
24
- client._transport = jest.fn();
35
+ if (!options || !options.transport) // allow tests to explicitely set the transport
36
+ client._transport = jest.fn();
25
37
  return client;
26
38
  }
27
39
 
28
40
  async function makeClient(options) {
29
41
  const connectionParameters = sdk.ConnectionParameters.ofUserAndPassword("http://acc-sdk:8080", "admin", "admin", options);
30
42
  const client = await sdk.init(connectionParameters);
31
- client._transport = jest.fn();
43
+ if (!options || !options.transport) // allow tests to explicitely set the transport
44
+ client._transport = jest.fn();
32
45
  return client;
33
46
  }
34
47
 
@@ -61,7 +74,7 @@ const LOGON_RESPONSE = Promise.resolve(`<?xml version='1.0'?>
61
74
  <SOAP-ENV:Envelope xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:ns='urn:xtk:session' xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/'>
62
75
  <SOAP-ENV:Body>
63
76
  <LogonResponse xmlns='urn:xtk:session' SOAP-ENV:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'>
64
- <pstrSessionToken xsi:type='xsd:string'>___C8B4A541-48DC-4C97-95AD-066930FD3892</pstrSessionToken>
77
+ <pstrSessionToken xsi:type='xsd:string'>___$session_token$</pstrSessionToken>
65
78
  <pSessionInfo xsi:type='ns:Element' SOAP-ENV:encodingStyle='http://xml.apache.org/xml-soap/literalxml'>
66
79
  <sessionInfo>
67
80
  <serverInfo advisedClientBuildNumber="0" allowSQL="false" buildNumber="9219" commitId="f5f3ec3" databaseId="uFE80000000000000F1FA913DD7CC7C480041161C" defaultNameSpace="cus" fohVersion="2" instanceName="ffdamkt" majNumber="6" minClientBuildNumber="8969" minNumber="7" minNumberTechnical="0" releaseName="20.3" securityTimeOut="86400" serverDate="2020-07-05 14:11:31.986Z" servicePack="0" sessionTimeOut="86400" useVault="false"/>
@@ -73,11 +86,32 @@ const LOGON_RESPONSE = Promise.resolve(`<?xml version='1.0'?>
73
86
  </userInfo>
74
87
  </sessionInfo>
75
88
  </pSessionInfo>
76
- <pstrSecurityToken xsi:type='xsd:string'>@mMBSMLXIpQd56agsZ5X7OGXWz8Q476qMq6FimwqCdT1wByRDq3pQtaYSY4uJnAbCgXIvpXA5TrxHu-3YjUad5g==</pstrSecurityToken>
89
+ <pstrSecurityToken xsi:type='xsd:string'>@$security_token$==</pstrSecurityToken>
77
90
  </LogonResponse>
78
91
  </SOAP-ENV:Body>
79
92
  </SOAP-ENV:Envelope>`);
80
93
 
94
+ const BEARER_LOGON_RESPONSE = Promise.resolve(`<?xml version='1.0'?>
95
+ <SOAP-ENV:Envelope xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:ns='urn:xtk:session' xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/'>
96
+ <SOAP-ENV:Body>
97
+ <BearerTokenLogonResponse xmlns='urn:xtk:session' SOAP-ENV:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'>
98
+ <pstrSessionToken xsi:type='xsd:string'>___$session_token$</pstrSessionToken>
99
+ <pSessionInfo xsi:type='ns:Element' SOAP-ENV:encodingStyle='http://xml.apache.org/xml-soap/literalxml'>
100
+ <sessionInfo>
101
+ <serverInfo advisedClientBuildNumber="0" allowSQL="false" buildNumber="9219" commitId="f5f3ec3" databaseId="uFE80000000000000F1FA913DD7CC7C480041161C" defaultNameSpace="cus" fohVersion="2" instanceName="ffdamkt" majNumber="6" minClientBuildNumber="8969" minNumber="7" minNumberTechnical="0" releaseName="20.3" securityTimeOut="86400" serverDate="2020-07-05 14:11:31.986Z" servicePack="0" sessionTimeOut="86400" useVault="false"/>
102
+ <userInfo datakitInDatabase="true" homeDir="" instanceLocale="en" locale="en" login="admin" loginCS="Administrator (admin)" loginId="1059" noConsoleCnx="false" orgUnitId="0" theme="" timezone="Europe/Paris">
103
+ <login-group id="1060"/>
104
+ <login-right right="admin"/>
105
+ <installed-package name="campaign" namespace="nms"/>
106
+ <installed-package name="core" namespace="nms"/>
107
+ </userInfo>
108
+ </sessionInfo>
109
+ </pSessionInfo>
110
+ <pstrSecurityToken xsi:type='xsd:string'>@$security_token$==</pstrSecurityToken>
111
+ </BearerTokenLogonResponse>
112
+ </SOAP-ENV:Body>
113
+ </SOAP-ENV:Envelope>`);
114
+
81
115
  const LOGON_RESPONSE_NO_USERINFO = Promise.resolve(`<?xml version='1.0'?>
82
116
  <SOAP-ENV:Envelope xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:ns='urn:xtk:session' xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/'>
83
117
  <SOAP-ENV:Body>
@@ -257,37 +291,43 @@ const GET_XTK_QUERY_SCHEMA_RESPONSE = Promise.resolve(`<?xml version='1.0'?>
257
291
  </SOAP-ENV:Body>
258
292
  </SOAP-ENV:Envelope>`);
259
293
 
260
- const GET_MID_EXT_ACCOUNT_RESPONSE = Promise.resolve(`<?xml version='1.0'?>
294
+ const GET_MID_EXT_ACCOUNT_RESPONSE = (encryptedPassword) => {
295
+ return Promise.resolve(`<?xml version='1.0'?>
261
296
  <SOAP-ENV:Envelope xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:ns='urn:xtk:queryDef' xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/'>
262
297
  <SOAP-ENV:Body>
263
298
  <ExecuteQueryResponse xmlns='urn:xtk:queryDef' SOAP-ENV:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'>
264
299
  <pdomOutput xsi:type='ns:Element' SOAP-ENV:encodingStyle='http://xml.apache.org/xml-soap/literalxml'>
265
- <extAccount account="mid" id="2088" name="defaultEmailMid" password="@57QS5VHMb9BCsojLVrKI/Q==" server="http://ffdamid:8080" type="3"/>
300
+ <extAccount account="mid" id="2088" name="defaultEmailMid" password="${encryptedPassword}" server="http://ffdamid:8080" type="3"/>
266
301
  </pdomOutput>
267
302
  </ExecuteQueryResponse>
268
303
  </SOAP-ENV:Body>
269
304
  </SOAP-ENV:Envelope>`);
305
+ }
270
306
 
271
- const GET_BAD_EXT_ACCOUNT_RESPONSE = Promise.resolve(`<?xml version='1.0'?>
307
+ const GET_BAD_EXT_ACCOUNT_RESPONSE = (encryptedPassword) => {
308
+ return Promise.resolve(`<?xml version='1.0'?>
272
309
  <SOAP-ENV:Envelope xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:ns='urn:xtk:queryDef' xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/'>
273
310
  <SOAP-ENV:Body>
274
311
  <ExecuteQueryResponse xmlns='urn:xtk:queryDef' SOAP-ENV:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'>
275
312
  <pdomOutput xsi:type='ns:Element' SOAP-ENV:encodingStyle='http://xml.apache.org/xml-soap/literalxml'>
276
- <extAccount account="bad" id="2088" name="bad" password="@57QS5VHMb9BCsojLVrKI/Q==" server="http://zz:8080" type="999"/>
313
+ <extAccount account="bad" id="2088" name="bad" password="${encryptedPassword}" server="http://zz:8080" type="999"/>
277
314
  </pdomOutput>
278
315
  </ExecuteQueryResponse>
279
316
  </SOAP-ENV:Body>
280
317
  </SOAP-ENV:Envelope>`);
318
+ }
281
319
 
282
- const GET_SECRET_KEY_OPTION_RESPONSE = Promise.resolve(`<?xml version='1.0'?>
320
+ const GET_SECRET_KEY_OPTION_RESPONSE = (key) => {
321
+ return Promise.resolve(`<?xml version='1.0'?>
283
322
  <SOAP-ENV:Envelope xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:ns='urn:xtk:session' xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/'>
284
323
  <SOAP-ENV:Body>
285
324
  <GetOptionResponse xmlns='urn:xtk:session' SOAP-ENV:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'>
286
- <pstrValue xsi:type='xsd:string'>HMLmn6uvWr8wu1Akt8UORr07YbC64u1FVW7ENAxNjpo=</pstrValue>
325
+ <pstrValue xsi:type='xsd:string'>${key}</pstrValue>
287
326
  <pbtType xsi:type='xsd:byte'>6</pbtType>
288
327
  </GetOptionResponse>
289
328
  </SOAP-ENV:Body>
290
329
  </SOAP-ENV:Envelope>`);
330
+ }
291
331
 
292
332
  const GET_LOGON_MID_RESPONSE = Promise.resolve(`<?xml version='1.0'?>
293
333
  <SOAP-ENV:Envelope xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:ns='urn:xtk:session' xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/'>
@@ -571,11 +611,13 @@ exports.Mock = {
571
611
  makeClient: makeClient,
572
612
  makeAnonymousClient: makeAnonymousClient,
573
613
  withMockConsole: withMockConsole,
614
+ makeKey: makeKey,
574
615
  R_TEST: R_TEST,
575
616
  PING: PING,
576
617
  MC_PING: MC_PING,
577
618
  MC_PING_ERROR: MC_PING_ERROR,
578
619
  LOGON_RESPONSE: LOGON_RESPONSE,
620
+ BEARER_LOGON_RESPONSE: BEARER_LOGON_RESPONSE,
579
621
  LOGON_RESPONSE_NO_SESSIONTOKEN: LOGON_RESPONSE_NO_SESSIONTOKEN,
580
622
  LOGON_RESPONSE_NO_SECURITYTOKEN: LOGON_RESPONSE_NO_SECURITYTOKEN,
581
623
  LOGOFF_RESPONSE: LOGOFF_RESPONSE,
package/test/soap.test.js CHANGED
@@ -154,11 +154,8 @@ describe('SOAP', function() {
154
154
  const env = DomUtil.parse(request.data).documentElement;
155
155
  const header = hasChildElement(env, "SOAP-ENV:Header");
156
156
  expect(DomUtil.findElement(header, "Cookie")).toBeFalsy();
157
- hasChildElement(header, "X-Security-Token");
158
157
  const body = hasChildElement(env, "SOAP-ENV:Body");
159
158
  const method = hasChildElement(body, "m:Empty", undefined, "xmlns:m", "urn:xtk:session", "SOAP-ENV:encodingStyle", "http://schemas.xmlsoap.org/soap/encoding/");
160
- // sessiontoken is always required as first parameter, even if not set
161
- expect(DomUtil.findElement(method, "sessiontoken")).toBeTruthy();
162
159
  });
163
160
 
164
161
  it('Should have set authentication tokens', function() {
@@ -168,11 +165,8 @@ describe('SOAP', function() {
168
165
  assert.equal(request.headers["Cookie"], "__sessiontoken=$session$", "Session token matches");
169
166
  const env = DomUtil.parse(request.data).documentElement;
170
167
  const header = hasChildElement(env, "SOAP-ENV:Header");
171
- hasChildElement(header, "Cookie", "__sessiontoken=$session$");
172
- hasChildElement(header, "X-Security-Token", "$security$");
173
168
  const body = hasChildElement(env, "SOAP-ENV:Body");
174
169
  const method = hasChildElement(body, "m:Empty");
175
- hasChildElement(method, "sessiontoken", "$session$", "xsi:type", "xsd:string");
176
170
  });
177
171
 
178
172
  it('Should set boolean parameters', function() {
@@ -792,5 +786,18 @@ describe("Campaign exception", () => {
792
786
  })
793
787
  })
794
788
 
789
+ it("CampaignException should hide sensitive information", () => {
790
+ const call = makeSoapMethodCall(undefined, "xtk:session", "Date", "$session$", "$security$", "My User Agent");
791
+ call.finalize("http://ffdamkt:8080/nl/jsp/soaprouter.jsp")
792
+ const ex = makeCampaignException(call, new Error("Failed"));
793
+ const req = ex.methodCall.request;
794
+ expect(req.data.indexOf("$session$")).toBe(-1);
795
+ expect(req.data.indexOf("$security$")).toBe(-1);
796
+ expect(req.headers.Cookie.indexOf("$session$")).toBe(-1);
797
+ expect(req.headers.Cookie.indexOf("$security$")).toBe(-1);
798
+ expect(req.headers["X-Security-Token"].indexOf("$session$")).toBe(-1);
799
+ expect(req.headers["X-Security-Token"].indexOf("$security$")).toBe(-1);
800
+ })
801
+
795
802
  });
796
803
 
package/test/util.test.js CHANGED
@@ -17,7 +17,8 @@ governing permissions and limitations under the License.
17
17
  *
18
18
  *********************************************************************************/
19
19
 
20
- const { Util } = require('../src/util.js');
20
+ const { Util } = require('../src/util.js');
21
+ const { SafeStorage, Cache } = require('../src/cache.js');
21
22
 
22
23
 
23
24
  describe('Util', function() {
@@ -80,6 +81,155 @@ describe('Util', function() {
80
81
  expect(Util.trim({hello:'Lead<sessiontoken xsi:type="xsd:string">Stuff</sessiontoken>Trail'})).toStrictEqual({hello:'Lead<sessiontoken xsi:type="xsd:string">***</sessiontoken>Trail'});
81
82
  })
82
83
 
84
+ it("Should remove password", () => {
85
+ expect(Util.trim({hello:`<sessiontoken xsi:type="xsd:string"/><login xsi:type="xsd:string">admin</login><password xsi:type="xsd:string">password</password><parameters xsi:type="ns:Element" SOAP-ENV:encodingStyle="http://xml.apache.org/xml-soap/literalxml"/>`})).toStrictEqual({hello:`<sessiontoken xsi:type="xsd:string"/><login xsi:type="xsd:string">admin</login><password xsi:type="xsd:string">***</password><parameters xsi:type="ns:Element" SOAP-ENV:encodingStyle="http://xml.apache.org/xml-soap/literalxml"/>`});
86
+ })
87
+
88
+ it("Should hide X-Security-Token properties", () => {
89
+ expect(Util.trim({"x-security-token": "Hello"})).toMatchObject({"x-security-token": "***"});
90
+ expect(Util.trim({"X-Security-Token": "Hello"})).toMatchObject({"X-Security-Token": "***"});
91
+ })
92
+
93
+ it("Should remove session tokens from cookies", () => {
94
+ expect(Util.trim({"Cookie": "__sessiontoken=ABC"})).toMatchObject({"Cookie": "__sessiontoken=***"});
95
+ expect(Util.trim({"Cookie": "__sessionToken=ABC"})).toMatchObject({"Cookie": "__sessionToken=***"});
96
+ expect(Util.trim({"Cookie": "__sessiontoken=ABC;"})).toMatchObject({"Cookie": "__sessiontoken=***;"});
97
+ expect(Util.trim({"Cookie": "__sessiontoken =ABC"})).toMatchObject({"Cookie": "__sessiontoken =***"});
98
+ expect(Util.trim({"Cookie": "__sessiontoken ABC"})).toMatchObject({"Cookie": "__sessiontoken ABC"}); // no = sign => no token value
99
+ expect(Util.trim({"Cookie": "a=b; __sessiontoken =ABC"})).toMatchObject({"Cookie": "a=b; __sessiontoken =***"});
100
+ expect(Util.trim({"Cookie": "a=b; __sessiontoken =ABC; c=d"})).toMatchObject({"Cookie": "a=b; __sessiontoken =***; c=d"});
101
+ expect(Util.trim({"Cookie": "a=b; __token =ABC; c=d"})).toMatchObject({"Cookie": "a=b; __token =ABC; c=d"});
102
+ })
103
+ })
104
+
105
+
106
+ describe("Safe storage", () => {
107
+ it("Should support undefined delegate", () => {
108
+ const storage = new SafeStorage();
109
+ expect(storage.getItem("Hello")).toBeUndefined();
110
+ storage.setItem("Hello", { text: "World" });
111
+ expect(storage.getItem("Hello")).toBeUndefined();
112
+ storage.setItem("Hello", "World"); // value should be JSON but errors are ignored
113
+ expect(storage.getItem("Hello")).toBeUndefined();
114
+ storage.removeItem("Hello");
115
+ expect(storage.getItem("Hello")).toBeUndefined();
116
+ })
117
+
118
+ it("Should handle map", () => {
119
+ const map = {};
120
+ const delegate = {
121
+ getItem: (key) => map[key],
122
+ setItem: (key, value) => { map[key] = value },
123
+ removeItem: (key) => { delete map[key] }
124
+ };
125
+ const storage = new SafeStorage(delegate);
126
+ expect(storage.getItem("Hello")).toBeUndefined();
127
+ storage.setItem("Hello", { text: "World" });
128
+ expect(map["Hello"]).toStrictEqual(JSON.stringify({text: "World"}));
129
+ expect(storage.getItem("Hello")).toStrictEqual({"text": "World"});
130
+ storage.setItem("Hello", "World"); // value should be JSON but errors are ignored
131
+ expect(storage.getItem("Hello")).toBeUndefined();
132
+ storage.setItem("Hello", { text: "World" });
133
+ storage.removeItem("Hello");
134
+ expect(storage.getItem("Hello")).toBeUndefined();
135
+ })
136
+
137
+ it("Should handle root key", () => {
138
+ const map = {};
139
+ const delegate = {
140
+ getItem: (key) => map[key],
141
+ setItem: (key, value) => { map[key] = value },
142
+ removeItem: (key) => { delete map[key] }
143
+ };
144
+ const storage = new SafeStorage(delegate, "root");
145
+ expect(storage.getItem("Hello")).toBeUndefined();
146
+ storage.setItem("Hello", { text: "World" });
147
+ expect(map["root$Hello"]).toStrictEqual(JSON.stringify({text: "World"}));
148
+ expect(storage.getItem("Hello")).toStrictEqual({"text": "World"});
149
+ storage.setItem("Hello", "World"); // value should be JSON but errors are ignored
150
+ expect(map["root$Hello"]).toBeUndefined();
151
+ expect(storage.getItem("Hello")).toBeUndefined();
152
+ storage.setItem("Hello", { text: "World" });
153
+ storage.removeItem("Hello");
154
+ expect(map["root$Hello"]).toBeUndefined();
155
+ expect(storage.getItem("Hello")).toBeUndefined();
156
+ })
157
+
158
+ it("Edge cases", () => {
159
+ expect(new SafeStorage()._storage).toBeUndefined();
160
+ expect(new SafeStorage()._rootKey).toBe("");
161
+ expect(new SafeStorage(null)._storage).toBeUndefined();
162
+ expect(new SafeStorage(null)._rootKey).toBe("");
163
+ expect(new SafeStorage(null, "")._storage).toBeUndefined();
164
+ expect(new SafeStorage(null, "")._rootKey).toBe("");
165
+ })
166
+
167
+ it("Should remove invalid items on get", () => {
168
+ const map = {};
169
+ const delegate = {
170
+ getItem: (key) => map[key],
171
+ setItem: (key, value) => { map[key] = value },
172
+ removeItem: (key) => { delete map[key] }
173
+ };
174
+ const storage = new SafeStorage(delegate, "root");
175
+ // value is not valid because not a JSON serialized string
176
+ map["root$Hello"] = "Invalid";
177
+ expect(storage.getItem("Hello")).toBeUndefined();
178
+ // Get should have removed invalid value
179
+ expect(map["root$Hello"]).toBeUndefined()
180
+ })
181
+
182
+ it("Should handle cache last cleared", () => {
183
+ const map = {};
184
+ const delegate = {
185
+ getItem: (key) => map[key],
186
+ setItem: (key, value) => { map[key] = value },
187
+ removeItem: (key) => { delete map[key] }
188
+ };
189
+ const cache = new Cache(delegate, "root");
190
+ cache.put("Hello", "World");
191
+ expect(JSON.parse(map["root$Hello"])).toMatchObject({ value:"World" });
192
+ expect(cache.get("Hello")).toBe("World");
193
+ cache.clear();
194
+ // Clear could not remove the item from the map
195
+ expect(JSON.parse(map["root$Hello"])).toMatchObject({ value:"World" });
196
+ // But get from cache will
197
+ expect(cache.get("Hello")).toBeUndefined();
198
+ })
199
+ })
200
+
201
+ it("Should preserve last cleared", () => {
202
+ const map = {};
203
+ const delegate = {
204
+ getItem: (key) => map[key],
205
+ setItem: (key, value) => { map[key] = value },
206
+ removeItem: (key) => { delete map[key] }
207
+ };
208
+ const cache = new Cache(delegate, "root");
209
+ expect(cache._lastCleared).toBeUndefined();
210
+ cache.put("Hello", "World");
211
+ cache.clear();
212
+ const lastCleared = cache._lastCleared;
213
+ expect(lastCleared).not.toBeUndefined();
214
+ expect(cache.get("Hello")).toBeUndefined();
215
+ expect(map["root$lastCleared"]).toBe(JSON.stringify({timestamp:lastCleared}));
216
+ // New cache with same delegate storage should preserve lastCleared date
217
+ const cache2 = new Cache(delegate, "root");
218
+ expect(cache2._lastCleared).toBe(lastCleared);
219
+ })
220
+
221
+ it("Should cache in memory value which is in local storage", () => {
222
+ const map = {};
223
+ const delegate = {
224
+ getItem: (key) => map[key],
225
+ setItem: (key, value) => { map[key] = value },
226
+ removeItem: (key) => { delete map[key] }
227
+ };
228
+ const cache = new Cache(delegate, "root");
229
+ map["root$Hello"] = JSON.stringify({ value: "World", cachedAt:Date.now() + 99999999 });
230
+ const value = cache.get("Hello");
231
+ expect(value).toBe("World");
232
+ expect(cache._cache["Hello"].value).toBe("World");
83
233
  })
84
234
 
85
235
  });
@@ -696,14 +696,22 @@ describe('XtkCaster', function() {
696
696
  expect(XtkCaster._variantStorageAttribute(6)).toBe("stringValue");
697
697
  expect(XtkCaster._variantStorageAttribute("string")).toBe("stringValue");
698
698
  expect(XtkCaster._variantStorageAttribute("int64")).toBe("stringValue");
699
+ expect(XtkCaster._variantStorageAttribute("uuid")).toBe("stringValue");
699
700
  expect(XtkCaster._variantStorageAttribute(12)).toBe("memoValue");
700
701
  expect(XtkCaster._variantStorageAttribute(13)).toBe("memoValue");
701
702
  expect(XtkCaster._variantStorageAttribute("memo")).toBe("memoValue");
702
703
  expect(XtkCaster._variantStorageAttribute("CDATA")).toBe("memoValue");
704
+ expect(XtkCaster._variantStorageAttribute("blob")).toBe("memoValue");
705
+ expect(XtkCaster._variantStorageAttribute("html")).toBe("memoValue");
703
706
  expect(XtkCaster._variantStorageAttribute(1)).toBe("longValue");
704
707
  expect(XtkCaster._variantStorageAttribute(2)).toBe("longValue");
705
708
  expect(XtkCaster._variantStorageAttribute(3)).toBe("longValue");
706
709
  expect(XtkCaster._variantStorageAttribute(15)).toBe("longValue");
710
+ expect(XtkCaster._variantStorageAttribute("byte")).toBe("longValue");
711
+ expect(XtkCaster._variantStorageAttribute("short")).toBe("longValue");
712
+ expect(XtkCaster._variantStorageAttribute("long")).toBe("longValue");
713
+ expect(XtkCaster._variantStorageAttribute("int")).toBe("longValue");
714
+ expect(XtkCaster._variantStorageAttribute("boolean")).toBe("longValue");
707
715
  expect(XtkCaster._variantStorageAttribute(4)).toBe("doubleValue");
708
716
  expect(XtkCaster._variantStorageAttribute(5)).toBe("doubleValue");
709
717
  expect(XtkCaster._variantStorageAttribute("float")).toBe("doubleValue");
@@ -716,4 +724,93 @@ describe('XtkCaster', function() {
716
724
  expect(XtkCaster._variantStorageAttribute("date")).toBe("timeStampValue");
717
725
  expect(() => { XtkCaster._variantStorageAttribute(777); }).toThrow("Cannot get variant storage");
718
726
  });
727
+
728
+ describe("Array tests", () => {
729
+ it("Should return array", () => {
730
+ expect(XtkCaster.asArray(null)).toStrictEqual([]);
731
+ expect(XtkCaster.asArray(undefined)).toStrictEqual([]);
732
+ expect(XtkCaster.asArray(false)).toStrictEqual([false]);
733
+ expect(XtkCaster.asArray("Hello")).toStrictEqual(["Hello"]);
734
+ expect(XtkCaster.asArray([])).toStrictEqual([]);
735
+ expect(XtkCaster.asArray([null])).toStrictEqual([null]);
736
+ })
737
+
738
+ it("Should support arrays", () => {
739
+ expect(XtkCaster.as(null, "array")).toStrictEqual([]);
740
+ expect(XtkCaster.as(undefined, "array")).toStrictEqual([]);
741
+ expect(XtkCaster.as(false, "array")).toStrictEqual([false]);
742
+ expect(XtkCaster.as("Hello", "array")).toStrictEqual(["Hello"]);
743
+ expect(XtkCaster.as([], "array")).toStrictEqual([]);
744
+ expect(XtkCaster.as([null], "array")).toStrictEqual([null]);
745
+ });
746
+ });
747
+
748
+ describe("Timespan test", () => {
749
+ it("Should return timespan", () => {
750
+ expect(XtkCaster.asTimespan(null)).toStrictEqual(0);
751
+ expect(XtkCaster.asTimespan(undefined)).toStrictEqual(0);
752
+ expect(XtkCaster.asTimespan(false)).toStrictEqual(0);
753
+ expect(XtkCaster.asTimespan("Hello")).toStrictEqual(0);
754
+ expect(XtkCaster.asTimespan([])).toStrictEqual(0);
755
+ expect(XtkCaster.asTimespan([null])).toStrictEqual(0);
756
+ expect(XtkCaster.asTimespan(NaN)).toStrictEqual(0);
757
+ expect(XtkCaster.asTimespan(Number.POSITIVE_INFINITY)).toStrictEqual(0);
758
+ expect(XtkCaster.asTimespan(Number.NEGATIVE_INFINITY)).toStrictEqual(0);
759
+ expect(XtkCaster.asTimespan("86400")).toStrictEqual(86400);
760
+ expect(XtkCaster.asTimespan(86400)).toStrictEqual(86400);
761
+ })
762
+
763
+ it("As should support 'timspan'", () => {
764
+ expect(XtkCaster.as(null, "timespan")).toStrictEqual(0);
765
+ expect(XtkCaster.as(undefined, "timespan")).toStrictEqual(0);
766
+ expect(XtkCaster.as(false, "timespan")).toStrictEqual(0);
767
+ expect(XtkCaster.as("Hello", "timespan")).toStrictEqual(0);
768
+ expect(XtkCaster.as([], "timespan")).toStrictEqual(0);
769
+ expect(XtkCaster.as([null], "timespan")).toStrictEqual(0);
770
+ expect(XtkCaster.as("86400", "timespan")).toStrictEqual(86400);
771
+ expect(XtkCaster.as(86400, "timespan")).toStrictEqual(86400);
772
+ });
773
+
774
+ it("As should support type 14", () => {
775
+ expect(XtkCaster.as(null, 14)).toStrictEqual(0);
776
+ expect(XtkCaster.as(undefined, 14)).toStrictEqual(0);
777
+ expect(XtkCaster.as(false, 14)).toStrictEqual(0);
778
+ expect(XtkCaster.as("Hello", 14)).toStrictEqual(0);
779
+ expect(XtkCaster.as([], 14)).toStrictEqual(0);
780
+ expect(XtkCaster.as([null], 14)).toStrictEqual(0);
781
+ expect(XtkCaster.as("86400", 14)).toStrictEqual(86400);
782
+ expect(XtkCaster.as(86400, 14)).toStrictEqual(86400);
783
+ });
784
+ })
785
+
786
+ describe("Other string types", () => {
787
+ it("Type 'html'", () => {
788
+ expect(XtkCaster.as(null, "html")).toStrictEqual("");
789
+ expect(XtkCaster.as(undefined, "html")).toStrictEqual("");
790
+ expect(XtkCaster.as("Hello", "html")).toStrictEqual("Hello");
791
+ expect(XtkCaster.as("0", "html")).toStrictEqual("0");
792
+ });
793
+
794
+ it("Type 'uuid'", () => {
795
+ expect(XtkCaster.as(null, "uuid")).toStrictEqual("");
796
+ expect(XtkCaster.as(undefined, "uuid")).toStrictEqual("");
797
+ expect(XtkCaster.as("Hello", "uuid")).toStrictEqual("Hello");
798
+ expect(XtkCaster.as("0", "uuid")).toStrictEqual("0");
799
+ });
800
+
801
+ it("Type 'blob'", () => {
802
+ expect(XtkCaster.as(null, "blob")).toStrictEqual("");
803
+ expect(XtkCaster.as(undefined, "blob")).toStrictEqual("");
804
+ expect(XtkCaster.as("Hello", "blob")).toStrictEqual("Hello");
805
+ expect(XtkCaster.as("0", "blob")).toStrictEqual("0");
806
+ });
807
+
808
+ it("Type 'int'", () => {
809
+ expect(XtkCaster.as(null, "int")).toStrictEqual(0);
810
+ expect(XtkCaster.as(undefined, "int")).toStrictEqual(0);
811
+ expect(XtkCaster.as("42", "int")).toStrictEqual(42);
812
+ expect(XtkCaster.as("0", "int")).toStrictEqual(0);
813
+ });
814
+ })
815
+
719
816
  });