@adobe/acc-js-sdk 1.0.5 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/util.js CHANGED
@@ -135,236 +135,7 @@ class Util {
135
135
  }
136
136
  }
137
137
 
138
-
139
- /**********************************************************************************
140
- *
141
- * A simple cache for XtkEntities, options, etc.
142
- *
143
- *********************************************************************************/
144
-
145
- /**
146
- * @private
147
- * @class
148
- * @constructor
149
- * @memberof Utils
150
- */
151
- class SafeStorage {
152
-
153
- /**
154
- * A wrapper to the Storage interface (LocalStorage, etc.) which is "safe", i.e.
155
- *
156
- * - it will never throw / support local stroage to be undefined or not accessible
157
- * - Handle the notion of "root key", i.e. prefix
158
- * - Set/get values as JSON only and not as strings
159
- * - Silently caches all exceptions
160
- * - Automatically remove from storage cached values which are not valid, expired, or cannot be parsed
161
- *
162
- * SafeStorage objects are created automatically by Caches
163
- *
164
- * @param {Storage} delegate an optional delegate options, confomring to the Storage interface (getItem, setItem, removeItem)
165
- * @param {string} rootKey an optional prefix which will be prepend to all keys
166
- */
167
- constructor(delegate, rootKey) {
168
- this._delegate = delegate;
169
- this._rootKey = rootKey ? `${rootKey}$` : "";
170
- }
171
-
172
- /**
173
- * Get an item from storage
174
- * @param {string} key the item key (relative to the root key)
175
- * @returns {Object} the cached JSON object, or undefined if not found
176
- */
177
- getItem(key) {
178
- if (!this._delegate || this._rootKey === undefined || this._rootKey === null)
179
- return;
180
- const itemKey = `${this._rootKey}${key}`;
181
- const raw = this._delegate.getItem(itemKey);
182
- if (!raw)
183
- return undefined;
184
- try {
185
- return JSON.parse(raw);
186
- } catch(ex) {
187
- this.removeItem(key);
188
- }
189
- }
190
-
191
- /**
192
- * Put an item into storage
193
- * @param {string} key the item key (relative to the root key)
194
- * @param {Object} json the value to cache
195
- */
196
- setItem(key, json) {
197
- if (!this._delegate || this._rootKey === undefined || this._rootKey === null)
198
- return;
199
- try {
200
- if (json && typeof json === "object") {
201
- const raw = JSON.stringify(json);
202
- this._delegate.setItem(`${this._rootKey}${key}`, raw);
203
- return;
204
- }
205
- } catch(ex) { /* Ignore errors in safe class */
206
- }
207
- this.removeItem(key);
208
- }
209
-
210
- /**
211
- * Removes an item from the storage
212
- * @param {string} key the item key (relative to the root key)
213
- */
214
- removeItem(key) {
215
- if (!this._delegate || this._rootKey === undefined || this._rootKey === null)
216
- return;
217
- try {
218
- this._delegate.removeItem(`${this._rootKey}${key}`);
219
- } catch(ex) { /* Ignore errors in safe class */
220
- }
221
- }
222
- }
223
-
224
- /**
225
- * @private
226
- * @class
227
- * @constructor
228
- * @memberof Utils
229
- */
230
- class CachedObject {
231
-
232
- /**
233
- * An object in the cache, i.e. a wrapped to a cached value and additional metadata to manage the cache.
234
- * Do not create such objects directly, they are 100% managed by the Cache object
235
- *
236
- * @param {*} value the cached value
237
- * @param {number} cachedAt the timestamp at which the value was cached
238
- * @param {number} expiresAt the timestamp at which the cached value expires
239
- */
240
- constructor(value, cachedAt, expiresAt) {
241
- this.value = value;
242
- this.cachedAt = cachedAt;
243
- this.expiresAt = expiresAt;
244
- }
245
- }
246
-
247
- /**
248
- * @private
249
- * @class
250
- * @constructor
251
- * @memberof Utils
252
- */
253
- class Cache {
254
-
255
- /**
256
- * A general purpose in-memory cache with TTL. In addition to caching in memory, the cache has the ability to delegate the caching
257
- * to a persistent cache, such as the browser localStorage. The interface is 100% synchronous.
258
- *
259
- * By default, caches take a single parameter for the key. It is possible however to use a more complex scenario by setting a makeKeyFn.
260
- * When set, such a function will take 1 or more arguments and will be responsible to create a primitive key (a string) from the arguments.
261
- * The cache public APIs : get and put therefore can take a variable number of key arguments, which will be combined into the actual primitive key.
262
- *
263
- * @param {Storage} storage is an optional Storage object, such as localStorage or sessionStorage. This object will be wrapped into a SafeStorage object to ensure access is safe and will not throw any exceptions
264
- * @param {string} rootKey is an optional root key to use for the storage object
265
- * @param {number} ttl is the TTL for objects in ms. Defaults to 5 mins
266
- * @param {function} makeKeyFn is an optional function which will generate a key for objects in the cache. It's passed the arguments of the cache 'get' function
267
- */
268
- constructor(storage, rootKey, ttl, makeKeyFn) {
269
- this._storage = new SafeStorage(storage, rootKey);
270
- this._ttl = ttl || 1000*300;
271
- this._makeKeyFn = makeKeyFn || ((x) => x);
272
- this._cache = {};
273
- // timestamp at which the cache was last cleared
274
- this._lastCleared = this._loadLastCleared();
275
- }
276
-
277
- // Load timestamp at which the cache was last cleared
278
- _loadLastCleared() {
279
- const json = this._storage.getItem("lastCleared");
280
- return json ? json.timestamp : undefined;
281
- }
282
-
283
- _saveLastCleared() {
284
- const now = Date.now();
285
- this._lastCleared = now;
286
- this._storage.setItem("lastCleared", { timestamp: now});
287
- }
288
-
289
- // Load from local storage
290
- _load(key) {
291
- const json = this._storage.getItem(key);
292
- if (!json || !json.cachedAt || json.cachedAt <= this._lastCleared) {
293
- this._storage.removeItem(key);
294
- return;
295
- }
296
- return json;
297
- }
298
-
299
- // Save to local storage
300
- _save(key, cached) {
301
- this._storage.setItem(key, cached);
302
- }
303
-
304
- // Remove from local storage
305
- _remove(key) {
306
- this._storage.removeItem(key);
307
- }
308
-
309
- _getIfActive(key) {
310
- // In memory cache?
311
- var cached = this._cache[key];
312
- // Local storage ?
313
- if (!cached) {
314
- cached = this._load(key);
315
- this._cache[key] = cached;
316
- }
317
- if (!cached)
318
- return undefined;
319
- if (cached.expiresAt <= Date.now()) {
320
- delete this._cache[key];
321
- this._remove(key);
322
- return undefined;
323
- }
324
- return cached.value;
325
- }
326
-
327
- /**
328
- * Get a value from the cache
329
- * @param {*} key the key or keys of the value to retreive
330
- * @returns {*} the cached value, or undefined if not found
331
- */
332
- get() {
333
- const key = this._makeKeyFn.apply(this, arguments);
334
- const cached = this._getIfActive(key);
335
- return cached;
336
- }
337
-
338
- /**
339
- * Put a value from the cache
340
- * @param {*} key the key or keys of the value to retreive
341
- * @param {*} value the value to cache
342
- * @returns {CachedObject} a cached object containing the cached value
343
- */
344
- put() {
345
- const value = arguments[arguments.length -1];
346
- const key = this._makeKeyFn.apply(this, arguments);
347
- const now = Date.now();
348
- const expiresAt = now + this._ttl;
349
- const cached = new CachedObject(value, now, expiresAt);
350
- this._cache[key] = cached;
351
- this._save(key, cached);
352
- return cached;
353
- }
354
-
355
- /**
356
- * Removes everything from the cache. It does not directly removes data from persistent storage if there is, but it marks the cache
357
- * as cleared so that subsequent get operation will not actually return any data cached in persistent storage
358
- */
359
- clear() {
360
- this._cache = {};
361
- this._saveLastCleared();
362
- }
363
- }
364
-
365
138
  // Public expots
366
139
  exports.Util = Util;
367
- exports.SafeStorage = SafeStorage;
368
- exports.Cache = Cache;
369
140
 
370
141
  })();
@@ -13,7 +13,7 @@ governing permissions and limitations under the License.
13
13
  "use strict";
14
14
 
15
15
  const DomUtil = require('./domUtil.js').DomUtil;
16
- const { Cache } = require('./util.js');
16
+ const { Cache } = require('./cache.js');
17
17
 
18
18
 
19
19
  /**********************************************************************************
@@ -32,14 +32,30 @@ class XtkEntityCache extends Cache {
32
32
 
33
33
  /**
34
34
  * A in-memory cache for xtk entities. Not intended to be used directly,
35
- * but an internal cache for the Campaign.Client object
35
+ * but an internal cache for the Campaign.Client object.
36
+ *
37
+ * Cached object are made of
38
+ * - the key is a string in the form <entityType>|<entityName>, such as "xtk:schema|nms:recipient"
39
+ * - the value is a DOM element corresponding to the entity. It's always a DOM element, regardless of the client representation
36
40
  *
37
41
  * @param {Storage} storage is an optional Storage object, such as localStorage or sessionStorage
38
42
  * @param {string} rootKey is an optional root key to use for the storage object
39
43
  * @param {number} ttl is the TTL for objects in ms. Defaults to 5 mins
40
44
  */
41
45
  constructor(storage, rootKey, ttl) {
42
- super(storage, rootKey, ttl, (entityType, entityFullName) => entityType + "|" + entityFullName);
46
+ super(storage, rootKey, ttl, (entityType, entityFullName) => entityType + "|" + entityFullName, (item, serDeser) => {
47
+ if (serDeser) {
48
+ if (!item || !item.value) throw Error(`Cannot serialize falsy cached item`);
49
+ const value = Object.assign({}, item);
50
+ value.value = DomUtil.toXMLString(item.value);
51
+ return JSON.stringify(value);
52
+ }
53
+ else {
54
+ const json = JSON.parse(item);
55
+ json.value = DomUtil.parse(json.value).documentElement;
56
+ return json;
57
+ }
58
+ });
43
59
  }
44
60
 
45
61
  /**
@@ -18,7 +18,7 @@ governing permissions and limitations under the License.
18
18
  *********************************************************************************/
19
19
 
20
20
  const assert = require('assert');
21
- const { Cache } = require('../src/util.js');
21
+ const { Cache, SafeStorage } = require('../src/cache.js');
22
22
  const OptionCache = require('../src/optionCache.js').OptionCache;
23
23
  const MethodCache = require('../src/methodCache.js').MethodCache;
24
24
  const XtkEntityCache = require('../src/xtkEntityCache.js').XtkEntityCache;
@@ -205,7 +205,17 @@ describe('Caches', function() {
205
205
  assert.equal(found.nodeName, "method");
206
206
  assert.equal(found.getAttribute("name"), "Create");
207
207
  })
208
-
208
+
209
+ it("Deserialized method should be a DOM element", () => {
210
+ const cache = new MethodCache();
211
+ const serDeser = cache._storage._serDeser;
212
+ const cached = {value: { x:3, method:DomUtil.parse("<hello/>")} };
213
+ const serialized = serDeser(cached, true);
214
+ const deserialized = serDeser(serialized, false);
215
+ const method = deserialized.value.method;
216
+ // should be a DOM element, not a DOM document
217
+ expect(method.nodeType).toBe(1);
218
+ })
209
219
  });
210
220
 
211
221
  describe("Method cache for interfaces", function() {
@@ -280,4 +290,140 @@ describe('Caches', function() {
280
290
  assert.strictEqual(urn, "xtk:session");
281
291
  });
282
292
  });
293
+
294
+ describe("SafeStorage", () => {
295
+
296
+ describe("JSON safe storage", () => {
297
+
298
+ it("Should find mock json from the cache", () => {
299
+ const map = {};
300
+ const delegate = {
301
+ getItem: jest.fn((key) => map[key]),
302
+ setItem: jest.fn((key, value) => map[key] = value)
303
+ }
304
+ const storage = new SafeStorage(delegate, "");
305
+ expect(storage.getItem("not_found")).toBeUndefined();
306
+ map["k1"] = `{ "hello": "world" }`;
307
+ expect(storage.getItem("k1")).toMatchObject({ hello: "world" });
308
+ map["k2"] = `{ "value": { "hello": "world" } }`;
309
+ expect(storage.getItem("k2")).toMatchObject({ value: { hello: "world" } });
310
+ });
311
+ });
312
+
313
+ describe("XML safe storage", () => {
314
+
315
+ const xmlSerDeser = (item, serDeser) => {
316
+ if (serDeser) {
317
+ const xml = DomUtil.toXMLString(item.value);
318
+ const value = {...item, value: xml };
319
+ return JSON.stringify(value);
320
+ }
321
+ else {
322
+ const json = JSON.parse(item);
323
+ const dom = DomUtil.parse(json.value);
324
+ return {...json, value:dom.documentElement};
325
+ }
326
+ };
327
+
328
+ it("Should find mock xml from the cache", () => {
329
+ const map = {};
330
+ const delegate = {
331
+ getItem: jest.fn((key) => map[key]),
332
+ setItem: jest.fn((key, value) => map[key] = value)
333
+ }
334
+ const storage = new SafeStorage(delegate, "", xmlSerDeser);
335
+ expect(storage.getItem("not_found")).toBeUndefined();
336
+ map["k1"] = `{ "hello": "world" }`;
337
+ expect(storage.getItem("k1")).toBeUndefined(); // k1 cached object does not have "value" attribute containing serialized XML
338
+ map["k1"] = `{ "hello": "world", "value": "" }`;
339
+ expect(storage.getItem("k1")).toBeUndefined(); // k1 cached object does not have "value" attribute containing serialized XML
340
+ map["k1"] = `{ "value": { "hello": "world" } }`;
341
+ expect(storage.getItem("k2")).toBeUndefined(); // k1 cached object does not have "value" attribute but it's not valid XML
342
+ map["k1"] = `{ "value": "" } }`;
343
+ expect(storage.getItem("k1")).toBeUndefined(); // k1 cached object does not have "value" attribute but it's not valid XML
344
+ map["k1"] = `{ "value": "bad" } }`;
345
+ expect(storage.getItem("k1")).toBeUndefined(); // k1 cached object does not have "value" attribute but it's not valid XML
346
+ map["k2"] = `{ "value": "<hello/>" }`;
347
+ expect(storage.getItem("k2").value.tagName).toBe("hello");
348
+ });
349
+ });
350
+ });
351
+
352
+ describe("Cache seralizers", () => {
353
+ it("Should serialize json", () => {
354
+ const cache = new OptionCache();
355
+ const serDeser = cache._storage._serDeser;
356
+ expect(serDeser({ hello: "World" }, true)).toBe('{"hello":"World"}');
357
+ expect(serDeser({ }, true)).toBe('{}');
358
+ expect(() => {serDeser(null, true)}).toThrow("Cannot serialize");
359
+ expect(() => {serDeser(undefined, true)}).toThrow("Cannot serialize");
360
+ expect(() => {serDeser("", true)}).toThrow("Cannot serialize");
361
+ expect(() => {serDeser("Hello", true)}).toThrow("Cannot serialize");
362
+ })
363
+
364
+ it("Should deserialize json", () => {
365
+ const cache = new OptionCache();
366
+ const serDeser = cache._storage._serDeser;
367
+ expect(serDeser('{"hello":"World"}', false)).toMatchObject({ hello: "World" });
368
+ expect(serDeser('{}', false)).toMatchObject({ });
369
+ expect(() => {serDeser(null, false)}).toThrow("Cannot deserialize");
370
+ expect(() => {serDeser(undefined, false)}).toThrow("Cannot deserialize");
371
+ expect(() => {serDeser("", false)}).toThrow("Cannot deserialize");
372
+ expect(() => {serDeser("Hello", false)}).toThrow("Unexpected token");
373
+ })
374
+
375
+ it("Should serialize XML entity", () => {
376
+ const cache = new XtkEntityCache();
377
+ const serDeser = cache._storage._serDeser;
378
+ expect(serDeser({value: DomUtil.parse("<hello/>")}, true)).toBe('{"value":"<hello/>"}')
379
+ expect(() => { serDeser({}, true); }).toThrow();
380
+ expect(() => { serDeser(null, true); }).toThrow();
381
+ expect(() => { serDeser(undefined, true); }).toThrow();
382
+ expect(() => { serDeser("", true); }).toThrow();
383
+ expect(() => { serDeser("Hello", true); }).toThrow();
384
+ })
385
+
386
+ it("Should deserialize XML entity", () => {
387
+ const cache = new XtkEntityCache();
388
+ const serDeser = cache._storage._serDeser;
389
+ expect(DomUtil.toXMLString(serDeser(`{"value":"<hello/>"}`, false).value)).toBe("<hello/>");
390
+ expect(() => {serDeser(null, false)}).toThrow();
391
+ expect(() => {serDeser(undefined, false)}).toThrow();
392
+ expect(() => {serDeser("", false)}).toThrow();
393
+ expect(() => {serDeser("Hello", false)}).toThrow();
394
+ })
395
+
396
+ it("Should serialize methods", () => {
397
+ const cache = new MethodCache();
398
+ const serDeser = cache._storage._serDeser;
399
+ expect(serDeser({value: { x:3, method:DomUtil.parse("<hello/>")} }, true)).toBe('{"value":{"x":3,"method":"<hello/>"}}')
400
+ expect(() => { serDeser({value: { x:3 }}, true); }).toThrow();
401
+ expect(() => { serDeser({}, true); }).toThrow();
402
+ expect(() => { serDeser(null, true); }).toThrow();
403
+ expect(() => { serDeser(undefined, true); }).toThrow();
404
+ expect(() => { serDeser("", true); }).toThrow();
405
+ expect(() => { serDeser("Hello", true); }).toThrow();
406
+ })
407
+
408
+ it("Should deserialize methods", () => {
409
+ const cache = new MethodCache();
410
+ const serDeser = cache._storage._serDeser;
411
+ expect(DomUtil.toXMLString(serDeser('{"value":{"x":3,"method":"<hello/>"}}', false).value.method)).toBe("<hello/>");
412
+ expect(() => { serDeser('{"value":{"x":3}}', false); }).toThrow();
413
+ expect(() => { serDeser({}, false); }).toThrow();
414
+ expect(() => { serDeser(null, false); }).toThrow();
415
+ expect(() => { serDeser(undefined, false); }).toThrow();
416
+ expect(() => { serDeser("", false); }).toThrow();
417
+ expect(() => { serDeser("Hello", false); }).toThrow();
418
+ })
419
+
420
+ it("Method serialization should not change initial object", () => {
421
+ const cache = new MethodCache();
422
+ const serDeser = cache._storage._serDeser;
423
+ const cached = {value: { x:3, method:DomUtil.parse("<hello/>")} };
424
+ serDeser(cached, true); // make sure this call does not change the input parameter "cached"
425
+ expect(cached.value.x).toBe(3);
426
+ expect(cached.value.method.documentElement.tagName).toBe("hello");
427
+ })
428
+ })
283
429
  });
@@ -27,7 +27,6 @@ const { HttpError } = require('../src/transport.js');
27
27
  describe('ACC Client', function () {
28
28
 
29
29
  describe('Init', function () {
30
-
31
30
  it('Should create client', async function () {
32
31
  const client = await Mock.makeClient();
33
32
  const NLWS = client.NLWS;
@@ -1897,6 +1896,58 @@ describe('ACC Client', function () {
1897
1896
  })
1898
1897
  })
1899
1898
 
1899
+ describe("Bearer token authentication", () => {
1900
+ // Bearer token authentication is used when embedding IMS for authentication
1901
+ it("Should create logged client", async() => {
1902
+ const connectionParameters = sdk.ConnectionParameters.ofBearerToken("http://acc-sdk:8080", "$token$");
1903
+ const client = await sdk.init(connectionParameters);
1904
+ client._transport = jest.fn();
1905
+ client._transport.mockReturnValueOnce(Mock.BEARER_LOGON_RESPONSE);
1906
+ expect(client.isLogged()).toBeFalsy();
1907
+ await client.logon();
1908
+ expect(client.isLogged()).toBeTruthy();
1909
+ const transport = client._transport.mockReturnValueOnce(Mock.LOGOFF_RESPONSE);
1910
+ await client.logoff();
1911
+ expect(client.isLogged()).toBeFalsy();
1912
+ // Ensure logoff has been called
1913
+ expect(transport.mock.calls.length).toBe(2);
1914
+ })
1915
+
1916
+ it("Call SAOP method", async () => {
1917
+ const connectionParameters = sdk.ConnectionParameters.ofBearerToken("http://acc-sdk:8080", "$token$");
1918
+ const client = await sdk.init(connectionParameters);
1919
+ client._transport = jest.fn();
1920
+ client._transport.mockReturnValueOnce(Mock.BEARER_LOGON_RESPONSE);
1921
+ await client.logon();
1922
+ client._transport.mockReturnValueOnce(Mock.GET_XTK_QUERY_SCHEMA_RESPONSE);
1923
+ var queryDef = {
1924
+ "schema": "nms:extAccount",
1925
+ "operation": "select",
1926
+ "select": {
1927
+ "node": [
1928
+ { "expr": "@id" },
1929
+ { "expr": "@name" }
1930
+ ]
1931
+ }
1932
+ };
1933
+
1934
+ client._transport.mockReturnValueOnce(Promise.resolve(`<?xml version='1.0'?>
1935
+ <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/'>
1936
+ <SOAP-ENV:Body>
1937
+ <ExecuteQueryResponse xmlns='urn:xtk:queryDef' SOAP-ENV:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'>
1938
+ <pdomOutput xsi:type='ns:Element' SOAP-ENV:encodingStyle='http://xml.apache.org/xml-soap/literalxml'>
1939
+ <extAccount-collection/>
1940
+ </pdomOutput></ExecuteQueryResponse>
1941
+ </SOAP-ENV:Body>
1942
+ </SOAP-ENV:Envelope>`));
1943
+
1944
+ // Select should return empty array
1945
+ var query = client.NLWS.xtkQueryDef.create(queryDef);
1946
+ var extAccount = await query.executeQuery();
1947
+ expect(extAccount).toEqual({ extAccount: [] });
1948
+ });
1949
+ })
1950
+
1900
1951
  describe("Logon should always return a promise", () => {
1901
1952
 
1902
1953
  it("Should return a promise with UserPassword", async () => {
@@ -1909,6 +1960,16 @@ describe('ACC Client', function () {
1909
1960
  await result;
1910
1961
  })
1911
1962
 
1963
+ it("Should return a promise with bearer token", async () => {
1964
+ const connectionParameters = sdk.ConnectionParameters.ofBearerToken("http://acc-sdk:8080", "$token$");
1965
+ const client = await sdk.init(connectionParameters);
1966
+ client._transport = jest.fn();
1967
+ client._transport.mockReturnValueOnce(Mock.BEARER_LOGON_RESPONSE);
1968
+ const result = client.logon();
1969
+ expect(result instanceof Promise).toBe(true);
1970
+ await result;
1971
+ })
1972
+
1912
1973
  it("Should return a promise with UserAndServiceToken", async () => {
1913
1974
  const connectionParameters = sdk.ConnectionParameters.ofUserAndServiceToken("http://acc-sdk:8080", "$user$", "$service_token$");
1914
1975
  const client = await sdk.init(connectionParameters);
@@ -2069,15 +2130,15 @@ describe('ACC Client', function () {
2069
2130
  it("Should ignore protocol for local storage root key", async () => {
2070
2131
  var connectionParameters = sdk.ConnectionParameters.ofUserAndPassword("http://acc-sdk:8080", "admin", "admin", {});
2071
2132
  var client = await sdk.init(connectionParameters);
2072
- expect(client._optionCache._storage._rootKey).toBe("acc.js.sdk.1.0.5.acc-sdk:8080.cache.OptionCache$");
2133
+ expect(client._optionCache._storage._rootKey).toBe("acc.js.sdk.1.0.6.acc-sdk:8080.cache.OptionCache$");
2073
2134
 
2074
2135
  connectionParameters = sdk.ConnectionParameters.ofUserAndPassword("https://acc-sdk:8080", "admin", "admin", {});
2075
2136
  client = await sdk.init(connectionParameters);
2076
- expect(client._optionCache._storage._rootKey).toBe("acc.js.sdk.1.0.5.acc-sdk:8080.cache.OptionCache$");
2137
+ expect(client._optionCache._storage._rootKey).toBe("acc.js.sdk.1.0.6.acc-sdk:8080.cache.OptionCache$");
2077
2138
 
2078
2139
  connectionParameters = sdk.ConnectionParameters.ofUserAndPassword("acc-sdk:8080", "admin", "admin", {});
2079
2140
  client = await sdk.init(connectionParameters);
2080
- expect(client._optionCache._storage._rootKey).toBe("acc.js.sdk.1.0.5.acc-sdk:8080.cache.OptionCache$");
2141
+ expect(client._optionCache._storage._rootKey).toBe("acc.js.sdk.1.0.6.acc-sdk:8080.cache.OptionCache$");
2081
2142
  })
2082
2143
 
2083
2144
  it("Should support no storage", async () => {
@@ -2094,5 +2155,48 @@ describe('ACC Client', function () {
2094
2155
  expect(value).toBe('uFE80000000000000F1FA913DD7CC7C480041161C');
2095
2156
  expect(storage.getItem.mock.calls.length).toBe(0); // storage is disabled and should not have been called
2096
2157
  })
2158
+
2159
+ it("Should cache XML in storage", async () => {
2160
+ const map = {};
2161
+ const storage = {
2162
+ getItem: jest.fn((key) => map[key]),
2163
+ setItem: jest.fn((key, value) => map[key] = value)
2164
+ }
2165
+ let client = await Mock.makeClient({ storage: storage });
2166
+ client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
2167
+ await client.NLWS.xtkSession.logon();
2168
+ client._transport.mockReturnValueOnce(Mock.GET_NMS_EXTACCOUNT_SCHEMA_RESPONSE);
2169
+ await client.getSchema("nms:extAccount");
2170
+ // Schema should have been cached to local storage
2171
+ expect(storage.setItem.mock.calls.length).toBe(1);
2172
+ expect(storage.setItem.mock.calls[0][0]).toMatch("cache.XtkEntityCache$xtk:schema|nms:extAccount");
2173
+ // Value is the cached object, it should not be an empty object
2174
+ const cached = JSON.parse(storage.setItem.mock.calls[0][1]);
2175
+ expect(Object.keys(cached.value).length).toBeGreaterThan(0);
2176
+ expect(cached.value).toMatch("<schema");
2177
+
2178
+ // Now simulate reusing the local storage. We need a new client to make sure we do not reuse
2179
+ // the in-memory cache of the client.
2180
+ client = await Mock.makeClient({ storage: storage });
2181
+ client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
2182
+ await client.NLWS.xtkSession.logon();
2183
+ client._transport.mockReturnValueOnce(Mock.GET_NMS_EXTACCOUNT_SCHEMA_RESPONSE);
2184
+ await client.getSchema("nms:extAccount");
2185
+ })
2097
2186
  })
2187
+
2188
+ describe("Get Schema, cache and representations", () => {
2189
+ it("Should get schema with no cache", async () => {
2190
+ const client = await Mock.makeClient();
2191
+ client.clearAllCaches();
2192
+ client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
2193
+ await client.NLWS.xtkSession.logon();
2194
+
2195
+ client._transport.mockReturnValueOnce(Mock.GET_NMS_EXTACCOUNT_SCHEMA_RESPONSE);
2196
+ var schema = await client.getSchema("nms:extAccount");
2197
+ expect(schema["namespace"]).toBe("nms");
2198
+ expect(schema["name"]).toBe("extAccount");
2199
+ });
2200
+
2201
+ });
2098
2202
  });
package/test/mock.js CHANGED
@@ -63,7 +63,7 @@ const LOGON_RESPONSE = Promise.resolve(`<?xml version='1.0'?>
63
63
  <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/'>
64
64
  <SOAP-ENV:Body>
65
65
  <LogonResponse xmlns='urn:xtk:session' SOAP-ENV:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'>
66
- <pstrSessionToken xsi:type='xsd:string'>___C8B4A541-48DC-4C97-95AD-066930FD3892</pstrSessionToken>
66
+ <pstrSessionToken xsi:type='xsd:string'>___$session_token$</pstrSessionToken>
67
67
  <pSessionInfo xsi:type='ns:Element' SOAP-ENV:encodingStyle='http://xml.apache.org/xml-soap/literalxml'>
68
68
  <sessionInfo>
69
69
  <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"/>
@@ -75,11 +75,32 @@ const LOGON_RESPONSE = Promise.resolve(`<?xml version='1.0'?>
75
75
  </userInfo>
76
76
  </sessionInfo>
77
77
  </pSessionInfo>
78
- <pstrSecurityToken xsi:type='xsd:string'>@mMBSMLXIpQd56agsZ5X7OGXWz8Q476qMq6FimwqCdT1wByRDq3pQtaYSY4uJnAbCgXIvpXA5TrxHu-3YjUad5g==</pstrSecurityToken>
78
+ <pstrSecurityToken xsi:type='xsd:string'>@$security_token$==</pstrSecurityToken>
79
79
  </LogonResponse>
80
80
  </SOAP-ENV:Body>
81
81
  </SOAP-ENV:Envelope>`);
82
82
 
83
+ const BEARER_LOGON_RESPONSE = Promise.resolve(`<?xml version='1.0'?>
84
+ <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/'>
85
+ <SOAP-ENV:Body>
86
+ <BearerTokenLogonResponse xmlns='urn:xtk:session' SOAP-ENV:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'>
87
+ <pstrSessionToken xsi:type='xsd:string'>___$session_token$</pstrSessionToken>
88
+ <pSessionInfo xsi:type='ns:Element' SOAP-ENV:encodingStyle='http://xml.apache.org/xml-soap/literalxml'>
89
+ <sessionInfo>
90
+ <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"/>
91
+ <userInfo datakitInDatabase="true" homeDir="" instanceLocale="en" locale="en" login="admin" loginCS="Administrator (admin)" loginId="1059" noConsoleCnx="false" orgUnitId="0" theme="" timezone="Europe/Paris">
92
+ <login-group id="1060"/>
93
+ <login-right right="admin"/>
94
+ <installed-package name="campaign" namespace="nms"/>
95
+ <installed-package name="core" namespace="nms"/>
96
+ </userInfo>
97
+ </sessionInfo>
98
+ </pSessionInfo>
99
+ <pstrSecurityToken xsi:type='xsd:string'>@$security_token$==</pstrSecurityToken>
100
+ </BearerTokenLogonResponse>
101
+ </SOAP-ENV:Body>
102
+ </SOAP-ENV:Envelope>`);
103
+
83
104
  const LOGON_RESPONSE_NO_USERINFO = Promise.resolve(`<?xml version='1.0'?>
84
105
  <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/'>
85
106
  <SOAP-ENV:Body>
@@ -578,6 +599,7 @@ exports.Mock = {
578
599
  MC_PING: MC_PING,
579
600
  MC_PING_ERROR: MC_PING_ERROR,
580
601
  LOGON_RESPONSE: LOGON_RESPONSE,
602
+ BEARER_LOGON_RESPONSE: BEARER_LOGON_RESPONSE,
581
603
  LOGON_RESPONSE_NO_SESSIONTOKEN: LOGON_RESPONSE_NO_SESSIONTOKEN,
582
604
  LOGON_RESPONSE_NO_SECURITYTOKEN: LOGON_RESPONSE_NO_SECURITYTOKEN,
583
605
  LOGOFF_RESPONSE: LOGOFF_RESPONSE,
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, SafeStorage, Cache } = 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() {