@adobe/acc-js-sdk 1.1.25 → 1.1.27

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/acc-js-sdk",
3
- "version": "1.1.25",
3
+ "version": "1.1.27",
4
4
  "description": "ACC Javascript SDK",
5
5
  "main": "src/index.js",
6
6
  "homepage": "https://github.com/adobe/acc-js-sdk#readme",
@@ -95,7 +95,8 @@ class SchemaCache {
95
95
  if (schema === undefined) {
96
96
  schema = await this._client.application._getSchema(schemaId);
97
97
  if (!schema) schema = null; // null = not found
98
- this._schemas[schemaId] = schema;
98
+ if (!schemaId.startsWith("temp:group:"))
99
+ this._schemas[schemaId] = schema;
99
100
  }
100
101
  return schema;
101
102
  }
package/src/client.js CHANGED
@@ -11,7 +11,7 @@ governing permissions and limitations under the License.
11
11
  */
12
12
  (function() {
13
13
  "use strict";
14
-
14
+ /*jshint sub:true*/
15
15
 
16
16
  /**********************************************************************************
17
17
  *
@@ -244,12 +244,12 @@ class Credentials {
244
244
  */
245
245
  constructor(type, sessionToken, securityToken) {
246
246
  if (type != "UserPassword" && type != "ImsServiceToken" && type != "SessionToken" &&
247
- type != "AnonymousUser" && type != "SecurityToken" && type != "BearerToken")
247
+ type != "AnonymousUser" && type != "SecurityToken" && type != "BearerToken" && type != "ImsBearerToken")
248
248
  throw CampaignException.INVALID_CREDENTIALS_TYPE(type);
249
249
  this._type = type;
250
250
  this._sessionToken = sessionToken || "";
251
251
  this._securityToken = securityToken || "";
252
- if (type == "BearerToken") {
252
+ if (type == "BearerToken" || type === "ImsBearerToken") {
253
253
  this._bearerToken = sessionToken || "";
254
254
  this._sessionToken = "";
255
255
  }
@@ -397,8 +397,13 @@ class ConnectionParameters {
397
397
  }
398
398
 
399
399
  /**
400
- * Creates connection parameters for a Campaign instance from bearer token
401
- *
400
+ * Creates connection parameters for a Campaign instance from bearer token.
401
+ * This authentication method uses an IMS Bearer token and calls the xtk:session#BearerTokenLogin method
402
+ * which will exchange the IMS bearer token with Campaign session and security tokens.
403
+ * This is a legacy method. Campaign 8.5 will has an authentication method which allows to directly
404
+ * use the IMS bearer token and which is recommended.
405
+ * To avoid an extra call to "BearerTokenLogin", use the "ofImsBearerToken" method.
406
+ *
402
407
  * @param {string} endpoint The campaign endpoint (URL)
403
408
  * @param {string} bearerToken IMS bearer token
404
409
  * @param {*} options connection options
@@ -408,6 +413,23 @@ class ConnectionParameters {
408
413
  const credentials = new Credentials("BearerToken", bearerToken);
409
414
  return new ConnectionParameters(endpoint, credentials, options);
410
415
  }
416
+
417
+ /**
418
+ * Creates connection parameters for a Campaign instance from a IMS bearer token.
419
+ * This authentication method does not require exchange the IMS token with session and security tokens
420
+ * and only works in ACC 8.5 and above.
421
+ * For older version of ACC or to use session/seurity tokens, use the "ofBearerToken" method.
422
+ *
423
+ * @param {string} endpoint The campaign endpoint (URL)
424
+ * @param {string} bearerToken IMS bearer token
425
+ * @param {*} options connection options
426
+ * @returns {ConnectionParameters} a ConnectionParameters object which can be used to create a Client
427
+ */
428
+ static ofImsBearerToken(endpoint, bearerToken, options) {
429
+ const credentials = new Credentials("ImsBearerToken", bearerToken);
430
+ return new ConnectionParameters(endpoint, credentials, options);
431
+ }
432
+
411
433
  /**
412
434
  * Creates connection parameters for a Campaign instance, using an IMS service token and a user name (the user to impersonate)
413
435
  *
@@ -557,15 +579,13 @@ const fileUploader = (client) => {
557
579
  }
558
580
  const data = new FormData();
559
581
  data.append('file_noMd5', file);
582
+ const headers = client._getAuthHeaders(false);
560
583
  client._makeHttpCall({
561
584
  url: `${client._connectionParameters._endpoint}/nl/jsp/uploadFile.jsp`,
562
585
  processData: false,
563
586
  method: 'POST',
564
587
  data: data,
565
- headers: {
566
- 'X-Security-Token': client._securityToken,
567
- 'X-Session-Token': client._sessionToken,
568
- }
588
+ headers: headers
569
589
  }).then((okay) => {
570
590
  if (!okay.startsWith('Ok')) {
571
591
  throw okay;
@@ -645,13 +665,28 @@ class Client {
645
665
  */
646
666
  constructor(sdk, connectionParameters) {
647
667
  this.sdk = sdk;
668
+ this.reinit(connectionParameters);
669
+
670
+ this._observers = [];
671
+ this._cacheChangeListeners = [];
672
+ }
673
+
674
+ /**
675
+ * Re-initialize a client with new connection parameters.
676
+ * Typically called from the refreshClient callback after a connection expires.
677
+ * Conserves observers
678
+ *
679
+ * @param {Campaign.ConnectionParameters} user user name, for instance admin
680
+ */
681
+ reinit(connectionParameters) {
648
682
  this._connectionParameters = connectionParameters; // ## TODO security concern (password kept in memory)
649
683
  this._representation = connectionParameters._options.representation;
650
684
 
651
685
  this._sessionInfo = undefined;
652
686
  this._sessionToken = undefined;
653
687
  this._securityToken = undefined;
654
- this._installedPackages = {}; // package set (key and value = package id, ex: "nms:amp")
688
+ this._bearerToken = undefined; // set when using Bearer authentication and "ImsBearer" credential type
689
+ this._installedPackages = {}; // package set (key and value = package id, ex: "nms:amp")
655
690
 
656
691
  this._secretKeyCipher = undefined;
657
692
 
@@ -667,7 +702,7 @@ class Client {
667
702
  const rootKeyType = connectionParameters._options.cacheRootKey;
668
703
  let rootKey = "";
669
704
  if (!rootKeyType || rootKeyType === "default")
670
- rootKey = `acc.js.sdk.${sdk.getSDKVersion().version}.${instanceKey}.cache.`;
705
+ rootKey = `acc.js.sdk.${this.sdk.getSDKVersion().version}.${instanceKey}.cache.`;
671
706
 
672
707
  // Clear storage cache if the sdk versions or the instances are different
673
708
  if (this._storage && typeof this._storage.removeItem === 'function') {
@@ -687,8 +722,6 @@ class Client {
687
722
 
688
723
  this._transport = connectionParameters._options.transport;
689
724
  this._traceAPICalls = connectionParameters._options.traceAPICalls;
690
- this._observers = [];
691
- this._cacheChangeListeners = [];
692
725
  this._refreshClient = connectionParameters._options.refreshClient;
693
726
 
694
727
  // expose utilities
@@ -749,6 +782,26 @@ class Client {
749
782
  return `${version.name}/${version.version} ${version.description}`;
750
783
  }
751
784
 
785
+ /**
786
+ * Get HTTP authentication headers
787
+ * @param
788
+ * @returns {Object} the headers
789
+ */
790
+ _getAuthHeaders(setCookie) {
791
+ const headers = {};
792
+ if (this._bearerToken) {
793
+ headers['Authorization'] = `Bearer ${this._bearerToken}`;
794
+ }
795
+ else {
796
+ headers['X-Security-Token'] = this._securityToken;
797
+ headers['X-Session-Token'] = this._sessionToken;
798
+ if (setCookie) {
799
+ headers['Cookie'] = '__sessiontoken=' + this._sessionToken;
800
+ }
801
+ }
802
+ return headers;
803
+ }
804
+
752
805
  /**
753
806
  * Convert an XML object into a representation
754
807
  *
@@ -929,6 +982,9 @@ class Client {
929
982
  const credentialsType = this._connectionParameters._credentials._type;
930
983
  if (credentialsType == "AnonymousUser")
931
984
  return true;
985
+ else if (credentialsType == "ImsBearerToken") {
986
+ return !!this._bearerToken;
987
+ }
932
988
 
933
989
  // When using bearer token authentication we are considered logged only after
934
990
  // the bearer token has been converted into session token and security token
@@ -967,7 +1023,8 @@ class Client {
967
1023
  this._sessionToken, this._securityToken,
968
1024
  this._getUserAgentString(),
969
1025
  Object.assign({}, this._connectionParameters._options, pushDownOptions),
970
- extraHttpHeaders);
1026
+ extraHttpHeaders,
1027
+ this._bearerToken);
971
1028
  soapCall.internal = !!internal;
972
1029
  soapCall.isStatic = isStatic;
973
1030
  return soapCall;
@@ -1324,6 +1381,7 @@ class Client {
1324
1381
  this.application = null;
1325
1382
  this._sessionToken = "";
1326
1383
  this._securityToken = "";
1384
+ this._bearerToken = undefined;
1327
1385
  const credentials = this._connectionParameters._credentials;
1328
1386
 
1329
1387
  const sdkVersion = this.sdk.getSDKVersion();
@@ -1349,6 +1407,7 @@ class Client {
1349
1407
  that._installedPackages = {};
1350
1408
  that._sessionToken = credentials._sessionToken;
1351
1409
  that._securityToken = "";
1410
+ that._bearerToken = undefined;
1352
1411
  that._onLogon();
1353
1412
  return Promise.resolve();
1354
1413
  }
@@ -1357,6 +1416,16 @@ class Client {
1357
1416
  that._installedPackages = {};
1358
1417
  that._sessionToken = "";
1359
1418
  that._securityToken = credentials._securityToken;
1419
+ that._bearerToken = undefined;
1420
+ that._onLogon();
1421
+ return Promise.resolve();
1422
+ }
1423
+ else if (credentials._type == "ImsBearerToken") {
1424
+ that._sessionInfo = undefined;
1425
+ that._installedPackages = {};
1426
+ that._sessionToken = "";
1427
+ that._securityToken = "";
1428
+ that._bearerToken = credentials._bearerToken;
1360
1429
  that._onLogon();
1361
1430
  return Promise.resolve();
1362
1431
  }
@@ -1407,6 +1476,7 @@ class Client {
1407
1476
  // store member variables after all parameters are decode the ensure atomicity
1408
1477
  that._sessionToken = sessionToken;
1409
1478
  that._securityToken = securityToken;
1479
+ that._bearerToken = undefined;
1410
1480
 
1411
1481
  that._onLogon();
1412
1482
  });
@@ -1444,6 +1514,7 @@ class Client {
1444
1514
  return this._makeSoapCall(soapCall).then(function() {
1445
1515
  that._sessionToken = "";
1446
1516
  that._securityToken = "";
1517
+ that._bearerToken = undefined;
1447
1518
  that.application = null;
1448
1519
  soapCall.checkNoMoreArgs();
1449
1520
  });
@@ -1451,6 +1522,7 @@ class Client {
1451
1522
  else {
1452
1523
  that._sessionToken = "";
1453
1524
  that._securityToken = "";
1525
+ that._bearerToken = undefined;
1454
1526
  that.application = null;
1455
1527
  }
1456
1528
  } finally {
@@ -1668,16 +1740,55 @@ class Client {
1668
1740
  async getSchema(schemaId, representation, internal) {
1669
1741
  var entity = await this._entityCache.get("xtk:schema", schemaId);
1670
1742
  if (!entity) {
1671
- entity = await this.getEntityIfMoreRecent("xtk:schema", schemaId, "xml", internal);
1672
- if (entity) {
1673
- const impls = DomUtil.getAttributeAsString(entity, "implements");
1674
- if (impls === "xtk:persist" && schemaId !== "xtk:session" && schemaId !== "xtk:persist") {
1675
- // Ensure xtk:persist is present by loading the xtk:session schema
1676
- await this.getSchema("xtk:session", "xml", true);
1743
+ // special case of "temp:group:*" schemas for nms:group
1744
+ // Schema "temp:group:*" is not cached because life cycle of this kind of schema is not the same as the others schemas
1745
+ if (schemaId.startsWith("temp:group:")) {
1746
+ const parts = schemaId.split(":");
1747
+ let queryDef = {
1748
+ "schema": "nms:group",
1749
+ "operation": "get",
1750
+ "select": {
1751
+ "node": [
1752
+ { "expr": "@id" },
1753
+ { "expr": "extension" }
1754
+ ]
1755
+ },
1756
+ "where": {
1757
+ "condition": [
1758
+ { "expr": "@id=" + XtkCaster.asLong(parts[2]) }
1759
+ ]
1760
+ }
1761
+ };
1762
+ // Convert to current representation
1763
+ queryDef = this._convertToRepresentation(queryDef, "SimpleJson", "xml");
1764
+ const query = this.NLWS.xml.xtkQueryDef.create(queryDef);
1765
+ try {
1766
+ const groupSchema = await query.executeQuery();
1767
+ const extension = DomUtil.findElement(groupSchema, "extension");
1768
+ if (extension) {
1769
+ entity = extension;
1770
+ } else {
1771
+ entity = null;
1772
+ }
1773
+ } catch (ex) {
1774
+ if (ex.name == 'CampaignException' && ex.errorCode == 'SOP-330011') {
1775
+ entity = null;
1776
+ } else {
1777
+ throw ex;
1778
+ }
1779
+ }
1780
+ } else {
1781
+ entity = await this.getEntityIfMoreRecent("xtk:schema", schemaId, "xml", internal);
1782
+ if (entity) {
1783
+ const impls = DomUtil.getAttributeAsString(entity, "implements");
1784
+ if (impls === "xtk:persist" && schemaId !== "xtk:session" && schemaId !== "xtk:persist") {
1785
+ // Ensure xtk:persist is present by loading the xtk:session schema
1786
+ await this.getSchema("xtk:session", "xml", true);
1787
+ }
1788
+ await this._entityCache.put("xtk:schema", schemaId, entity);
1789
+ await this._methodCache.put(entity);
1790
+ }
1677
1791
  }
1678
- await this._entityCache.put("xtk:schema", schemaId, entity);
1679
- await this._methodCache.put(entity);
1680
- }
1681
1792
  }
1682
1793
  entity = this._toRepresentation(entity, representation);
1683
1794
  return entity;
@@ -1914,13 +2025,10 @@ class Client {
1914
2025
  * @returns {Campaign.PingStatus} an object describing the server status
1915
2026
  */
1916
2027
  async ping() {
2028
+ const headers = this._getAuthHeaders(true);
1917
2029
  const request = {
1918
2030
  url: `${this._connectionParameters._endpoint}/nl/jsp/ping.jsp`,
1919
- headers: {
1920
- 'X-Security-Token': this._securityToken,
1921
- 'X-Session-Token': this._sessionToken,
1922
- 'Cookie': '__sessiontoken=' + this._sessionToken
1923
- }
2031
+ headers: headers,
1924
2032
  };
1925
2033
  for (let h in this._connectionParameters._options.extraHttpHeaders)
1926
2034
  request.headers[h] = this._connectionParameters._options.extraHttpHeaders[h];
@@ -1953,14 +2061,12 @@ class Client {
1953
2061
  callContext.formData.ctx = DomUtil.toXMLString(xmlCtx);
1954
2062
  }
1955
2063
  const selectionCount = callContext.selection.split(',').length;
1956
-
2064
+
2065
+ const headers = this._getAuthHeaders(false);
2066
+ headers['Content-Type'] = 'application/x-www-form-urlencoded';
1957
2067
  const request = {
1958
2068
  url: `${this._connectionParameters._endpoint}/report/${callContext.reportName}?${encodeURI(`_noRender=true&_schema=${callContext.schema}&_context=${callContext.context}&_selection=${callContext.selection}`)}&_selectionCount=${selectionCount}`,
1959
- headers: {
1960
- 'X-Security-Token': this._securityToken,
1961
- 'X-Session-Token': this._sessionToken,
1962
- 'Content-Type': 'application/x-www-form-urlencoded'
1963
- },
2069
+ headers: headers,
1964
2070
  method: 'POST',
1965
2071
  data : qsStringify(callContext.formData)
1966
2072
  };
@@ -1985,13 +2091,10 @@ class Client {
1985
2091
  * @returns {Campaign.McPingStatus} an object describing Message Center server status
1986
2092
  */
1987
2093
  async mcPing() {
2094
+ const headers = this._getAuthHeaders(true);
1988
2095
  const request = {
1989
2096
  url: `${this._connectionParameters._endpoint}/nl/jsp/mcPing.jsp`,
1990
- headers: {
1991
- 'X-Security-Token': this._securityToken,
1992
- 'X-Session-Token': this._sessionToken,
1993
- 'Cookie': '__sessiontoken=' + this._sessionToken
1994
- }
2097
+ headers: headers
1995
2098
  };
1996
2099
  for (let h in this._connectionParameters._options.extraHttpHeaders)
1997
2100
  request.headers[h] = this._connectionParameters._options.extraHttpHeaders[h];
package/src/domUtil.js CHANGED
@@ -341,20 +341,18 @@ class DomUtil {
341
341
  xmlElement.textContent = value;
342
342
  xmlRoot.appendChild(xmlElement);
343
343
  }
344
- else if (t == "object") {
345
- if (value.length !== undefined && value.length !== null) {
346
- for (var i=0; i<value.length; i++) {
347
- const xmlElement = doc.createElement(att);
348
- this._fromJSON(doc, xmlElement, value[i], flavor);
349
- xmlRoot.appendChild(xmlElement);
350
- }
351
- }
352
- else {
344
+ else if (Util.isArray(value)) {
345
+ for (var i=0; i<value.length; i++) {
353
346
  const xmlElement = doc.createElement(att);
354
- this._fromJSON(doc, xmlElement, value, flavor);
347
+ this._fromJSON(doc, xmlElement, value[i], flavor);
355
348
  xmlRoot.appendChild(xmlElement);
356
349
  }
357
350
  }
351
+ else if (t == "object") {
352
+ const xmlElement = doc.createElement(att);
353
+ this._fromJSON(doc, xmlElement, value, flavor);
354
+ xmlRoot.appendChild(xmlElement);
355
+ }
358
356
  else
359
357
  throw new DomException(`Cannot cast JSON to XML: element '${att}' type '${t}' is unknown or not supported yet`);
360
358
  }
package/src/soap.js CHANGED
@@ -11,7 +11,7 @@ governing permissions and limitations under the License.
11
11
  */
12
12
  (function() {
13
13
  "use strict";
14
-
14
+ /*jshint sub:true*/
15
15
 
16
16
  /**********************************************************************************
17
17
  *
@@ -80,11 +80,12 @@ const NS_XSD = "http://www.w3.org/2001/XMLSchema";
80
80
  * @param {string} userAgentString The user agent string to use for HTTP requests
81
81
  * @param {string} pushDownOptions Options to push down to the request (comes from connectionParameters._options)
82
82
  * @param {{ name:string, value:string}} extraHttpHeaders key/value pair of HTTP header (will override any other headers)
83
+ * @param {string} bearerToken The bearer token to use for HTTP requests. Only required for ImsBearerToken authentication
83
84
  * @memberof SOAP
84
85
  */
85
86
  class SoapMethodCall {
86
87
 
87
- constructor(transport, urn, methodName, sessionToken, securityToken, userAgentString, pushDownOptions, extraHttpHeaders) {
88
+ constructor(transport, urn, methodName, sessionToken, securityToken, userAgentString, pushDownOptions, extraHttpHeaders, bearerToken) {
88
89
  this.request = undefined; // The HTTP request (object literal passed to the transport layer)
89
90
  this.requestOptions = undefined;
90
91
  this.response = undefined; // The HTTP response object (in case of success)
@@ -103,6 +104,7 @@ class SoapMethodCall {
103
104
 
104
105
  this._sessionToken = sessionToken || "";
105
106
  this._securityToken = securityToken || "";
107
+ this._bearerToken = bearerToken; // may be undefined if not using bearer token authentication
106
108
  this._userAgentString = userAgentString;
107
109
  this._pushDownOptions = pushDownOptions || {};
108
110
  this._charset = this._pushDownOptions.charset || '';
@@ -538,9 +540,14 @@ class SoapMethodCall {
538
540
  const headers = {
539
541
  'Content-type': `application/soap+xml${this._charset ? ";charset=" + this._charset : ""}`,
540
542
  'SoapAction': `${this.urn}#${this.methodName}`,
541
- 'X-Security-Token': this._securityToken,
542
- 'X-Session-Token': this._sessionToken,
543
543
  };
544
+ if (this._bearerToken) {
545
+ headers['Authorization'] = `Bearer ${this._bearerToken}`;
546
+ }
547
+ else {
548
+ headers['X-Security-Token'] = this._securityToken;
549
+ headers['X-Session-Token'] = this._sessionToken;
550
+ }
544
551
 
545
552
  // Add HTTP headers specific to the SOAP call for better tracing/troubleshooting
546
553
  if (this._extraHttpHeaders && this._extraHttpHeaders['ACC-SDK-Version']) {
@@ -579,6 +586,7 @@ class SoapMethodCall {
579
586
  if (client) {
580
587
  this._sessionToken = client._sessionToken;
581
588
  this._securityToken = client._securityToken;
589
+ this._bearerToken = client._bearerToken;
582
590
  }
583
591
 
584
592
  var cookieHeader = DomUtil.findElement(this._header, "Cookie");
package/src/util.js CHANGED
@@ -51,18 +51,7 @@ class Util {
51
51
  * @returns {boolean} true if the object is an array
52
52
  */
53
53
  static isArray(obj) {
54
- if (obj === null || obj === undefined) return false;
55
- // JavaScript arrays are objects
56
- if (typeof obj != "object") return false;
57
- // They also have a length property. But checking the length is not enough
58
- // since, it can also be an object literal with a "length" property. Campaign
59
- // schema attributes typically have a "length" attribute and are not arrays
60
- if (obj.length === undefined || obj.length === null) return false;
61
- // So check for a "push" function
62
- if (obj.push === undefined || obj.push === null) return false;
63
- if (typeof obj.push != "function")
64
- return false;
65
- return true;
54
+ return Array.isArray(obj);
66
55
  }
67
56
 
68
57
  // Helper function for trim() to replace text between 2 indices
@@ -1499,6 +1499,66 @@ describe('Application', () => {
1499
1499
  const isoA3 = await recipient.root.findNode("country/@isoA3");
1500
1500
  expect(isoA3).toBeFalsy();
1501
1501
  });
1502
+
1503
+ it("Should not cache temp:group: schemas", async () => {
1504
+ const client = await Mock.makeClient();
1505
+ client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
1506
+ await client.NLWS.xtkSession.logon();
1507
+
1508
+ client._transport.mockReturnValueOnce(Mock.GET_XTK_QUERY_SCHEMA_RESPONSE);
1509
+ client._transport.mockReturnValueOnce(Promise.resolve(`<?xml version='1.0'?>
1510
+ <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/'>
1511
+ <SOAP-ENV:Body>
1512
+ <ExecuteQueryResponse xmlns='urn:xtk:queryDef' SOAP-ENV:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'>
1513
+ <pdomOutput xsi:type='ns:Element' SOAP-ENV:encodingStyle='http://xml.apache.org/xml-soap/literalxml'>
1514
+ <group expirationDate="" folder-id="1199" id="2200" label="testlist" name="LST260" schema="nms:recipient" type="1">
1515
+ <extension label="email is not empty" mappingType="sql" name="query" namespace="temp">
1516
+ <element advanced="false" dataSource="nms:extAccount:ffda" label="email is not empty" name="query" pkSequence="" sqltable="grp2200" unbound="false">
1517
+ <compute-string expr=""/>
1518
+ <key internal="true" name="internal">
1519
+ <keyfield xpath="@id"/>
1520
+ </key>
1521
+ <attribute advanced="false" belongsTo="@id" label="Primary key" length="0" name="id" notNull="false" sql="true" sqlname="uId" type="uuid" xml="false"/>
1522
+ <element advanced="false" externalJoin="true" label="Targeting dimension" name="target" revLink="" target="nms:recipient" type="link" unbound="false">
1523
+ <join xpath-dst="@id" xpath-src="@id"/>
1524
+ </element>
1525
+ </element>
1526
+ </extension>
1527
+ </group>
1528
+ </pdomOutput>
1529
+ </ExecuteQueryResponse>
1530
+ </SOAP-ENV:Body>
1531
+ </SOAP-ENV:Envelope>`));
1532
+ const group = await client.application.getSchema('temp:group:2200');
1533
+ expect(group.label).toBe("email is not empty");
1534
+
1535
+ // return updated schema with label changed
1536
+ client._transport.mockReturnValueOnce(Promise.resolve(`<?xml version='1.0'?>
1537
+ <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/'>
1538
+ <SOAP-ENV:Body>
1539
+ <ExecuteQueryResponse xmlns='urn:xtk:queryDef' SOAP-ENV:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'>
1540
+ <pdomOutput xsi:type='ns:Element' SOAP-ENV:encodingStyle='http://xml.apache.org/xml-soap/literalxml'>
1541
+ <group expirationDate="" folder-id="1199" id="2200" label="testlist" name="LST260" schema="nms:recipient" type="1">
1542
+ <extension label="email is empty" mappingType="sql" name="query" namespace="temp">
1543
+ <element advanced="false" dataSource="nms:extAccount:ffda" label="email is empty" name="query" pkSequence="" sqltable="grp2200" unbound="false">
1544
+ <compute-string expr=""/>
1545
+ <key internal="true" name="internal">
1546
+ <keyfield xpath="@id"/>
1547
+ </key>
1548
+ <attribute advanced="false" belongsTo="@id" label="Primary key" length="0" name="id" notNull="false" sql="true" sqlname="uId" type="uuid" xml="false"/>
1549
+ <element advanced="false" externalJoin="true" label="Targeting dimension" name="target" revLink="" target="nms:recipient" type="link" unbound="false">
1550
+ <join xpath-dst="@id" xpath-src="@id"/>
1551
+ </element>
1552
+ </element>
1553
+ </extension>
1554
+ </group>
1555
+ </pdomOutput>
1556
+ </ExecuteQueryResponse>
1557
+ </SOAP-ENV:Body>
1558
+ </SOAP-ENV:Envelope>`));
1559
+ const group2 = await client.application.getSchema('temp:group:2200');
1560
+ expect(group2.label).toBe("email is empty");
1561
+ });
1502
1562
  });
1503
1563
 
1504
1564
  describe("Ref nodes", () => {