@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/.vscode/launch.json +0 -1
- package/CHANGELOG.md +8 -0
- package/README.md +7 -0
- package/compile.js +2 -0
- package/package-lock.json +749 -769
- package/package.json +2 -2
- package/src/cache.js +275 -0
- package/src/client.js +47 -16
- package/src/methodCache.js +21 -2
- package/src/optionCache.js +5 -1
- package/src/soap.js +2 -1
- package/src/util.js +0 -229
- package/src/xtkEntityCache.js +19 -3
- package/test/caches.test.js +148 -2
- package/test/client.test.js +108 -4
- package/test/mock.js +24 -2
- package/test/util.test.js +2 -1
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
|
})();
|
package/src/xtkEntityCache.js
CHANGED
|
@@ -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('./
|
|
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
|
/**
|
package/test/caches.test.js
CHANGED
|
@@ -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/
|
|
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
|
});
|
package/test/client.test.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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'>
|
|
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'
|
|
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
|
|
20
|
+
const { Util } = require('../src/util.js');
|
|
21
|
+
const { SafeStorage, Cache } = require('../src/cache.js');
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
describe('Util', function() {
|