@adobe/acc-js-sdk 1.1.2 → 1.1.3

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.
@@ -16,7 +16,7 @@ jobs:
16
16
  node-version: 10.0.0
17
17
  - name: Publish if version has been updated
18
18
  #uses: pascalgn/npm-publish-action@06e0830ea83eea10ed4a62654eeaedafb8bf50fc
19
- uses: mkiki/npm-publish-action@c4315ef5790b7bcec2cbb75b34e37681a409d78d
19
+ uses: mkiki/npm-publish-action@52879298d00c0a02781e1f02c9e917e01cff1bc7
20
20
  with: # All of theses inputs are optional
21
21
  tag_name: "v%s"
22
22
  tag_message: "v%s"
package/CHANGELOG.md CHANGED
@@ -5,6 +5,17 @@ This is a node.js SDK for Campaign API. It exposes the Campaign API exactly like
5
5
 
6
6
  # Changelog
7
7
 
8
+ ## Version 1.1.3
9
+ _2022/05/30_
10
+
11
+ * Fix a bug in client.hasPackage which was returning an incorrect result when passed a single parameter (it would always return false). Fixed the corresponding unit test too.
12
+ * Fix a bug causing API calls having a input parameter of type "int" to fail. Usually the type is described as "long", but sometimes "int" is used instead, such as, for instance, in the nms:extAccount#UpdateMCSynchWkf method.
13
+ * When using XML representations and DOMDocument method parameter type, the SDK expects to be passed an actual DOM document. Now it supports being passed a DOM element too. This is a common case when using the nms:delivery#createFromModel API followed by a xtk:session#Write API call.
14
+ * Avoid the error 'Cannot transform entity to xml because no XML root name was given' by using SOAP method parameter name as the default for XML document root when no other root is available
15
+ * Document how to set the password of an external account
16
+ * By default, SDK will send additional HTTP headers to help troubleshooting and usage tracking
17
+ * Add the ability to pass extra HTTP headers to API calls, either globally (to all HTTP headers), or locally, i.e. for a specific method
18
+
8
19
  ## Version 1.1.2
9
20
  _2022/03/22_
10
21
 
package/README.md CHANGED
@@ -130,7 +130,9 @@ noStorage|false|De-activate using of local storage
130
130
  storage|localStorage|Overrides the local storage for caches
131
131
  refreshClient|undefined|Async callback to run when the session token is expired
132
132
  charset|UTF-8|The charset encoding used for http requests. In version 1.1.1 and above, the default will be UTF-8. It's possible to override (including setting an empty character set) with this option.
133
-
133
+ extraHttpHeaders|[string]:string|An optional dictionary (key/value pairs) of extra HTTP headers to pass to all API calls.
134
+ clientApp|string|An optional string describing the name and version of the SDK client application. It will be passed to the server in the ACC-SDK-Client-App HTTP header
135
+ noSDKHeaders|boolean|Can be set to disable passing ACC-SDK-* HTTP headers to the server
134
136
  ```js
135
137
  const connectionParameters = sdk.ConnectionParameters.ofUserAndPassword(
136
138
  "https://myInstance.campaign.adobe.com",
@@ -576,6 +578,49 @@ const document = DomUtil.fromJSON(json, "BadgerFish");
576
578
  const json = DomUtil.toJSON(documentOrElement, "BadgerFish");
577
579
  ```
578
580
 
581
+ ## Passing XML parameters
582
+ Many Campaign APIs take arguments which are DOM documents or DOM elements. For example, the nms:delivery#DeployTriggerMessages first argument is a DOMElement which is supposed to be a `<where>` clause used as a condition to select Message Center deliveries to publish.
583
+
584
+ ```xml
585
+ <method name="DeployTriggerMessages" static="true">
586
+ <parameters>
587
+ <param inout="in" name="deliveries" type="DOMElement"/>
588
+ <param inout="in" name="localPublish" type="boolean"/>
589
+ </parameters>
590
+ </method>
591
+ ```
592
+
593
+ For example, one would want to use the following condition to republish a particular delivery
594
+
595
+ ```js
596
+ await client.NLWS.nmsDelivery.DeployTriggerMessages({
597
+ condition: [ {
598
+ expr: "@internalName='DM23'"
599
+ }]
600
+ }, false);
601
+ ```
602
+
603
+ The JSON object corresponds to the following XML
604
+ ```xml
605
+ <where>
606
+ <condition expr="@internalName='DM23'"/>
607
+ </where>
608
+ ```
609
+
610
+ Note that in XML, unlike JSON, the root element `<where>` is explicitely named "where". When converting JSON to XML, there is no way for the SDK to know which tag name to used for the root XML element. The SDK contains some code to set it for the most common situation, but will rely on the user to specify, when necessary, the name of the root elment. This can be done using the `xtkschema` (all case insensitive) attribute as follows:
611
+ ```js
612
+ await client.NLWS.nmsDelivery.DeployTriggerMessages({
613
+ xtkschema: 'xtk:where',
614
+ condition: [ {
615
+ expr: "@internalName='DM23'"
616
+ }]
617
+ }, false);
618
+ ```
619
+
620
+ When the `xtkschema` attribute is set, the part after the colon (i.e. "where" in this example) will be used as the root element, effectively generating the right XML.
621
+
622
+ In our example, the `DeployTriggerMessages` will work properly regardless of the XML root of its `deliveries` parameter, so it's not needed to actually set the `xtkschema` attribute, but it's a best practice to do so, because some APIs will actually depend on receiving the right tag name.
623
+
579
624
  ## Error Management
580
625
 
581
626
  If an API call fails (SOAP fault or HTTP error), a `CampaignException` object is thrown. This object contains the following attributes
@@ -654,6 +699,64 @@ const password = cipher.decryptPassword(encryptedPassword);
654
699
 
655
700
  > **warning** This function is deprecated in version 1.0.0 of the SDK because it may break as we deploy Vault.
656
701
 
702
+ In order to set the password of an external account, you can use the `encryptPassword` API as follow
703
+
704
+ ```js
705
+ const password = await this._client.NLWS.xtkSession.encryptPassword('password');
706
+ const account = {
707
+ xtkschema: "nms:extAccount",
708
+ name: name,
709
+ account: 'admin',
710
+ server: server,
711
+ password: password,
712
+ };
713
+ await this._client.NLWS.xtkSession.Write(account);
714
+ ```
715
+
716
+
717
+ ## HTTP Headers
718
+
719
+ ### Out-of-the-box headers
720
+ In version 1.1.3 and above, the SDK will pass additional HTTP headers automatically
721
+
722
+ Header | Description
723
+ -------|------------
724
+ SOAPAction| name of the schema and SOAP method (ex: xtk:query#ExecuteQuery)
725
+ ACC-SDK-Version| Version of the SDK making the scores
726
+ ACC-SDK-Auth| Authentification type and ACC user
727
+ ACC-SDK-Client-App| Name of the application calling the client SDK
728
+ ACC-SDK-Call-RetryCount| In case an API call is retried, indicates the number of retries
729
+ ACC-SDK-Call-Internal| Indicates that an API call is performed byt the SDK for its own purpose
730
+
731
+ The `ACC-SDK` headers can be removed using the connection parameter `noSDKHeaders`.
732
+
733
+ ### Custom HTTP headers
734
+ In version 1.1.3 and above, it is possible to pass additional HTTP headers or override HTTP headers set by the SDK. This can be done globally (i.e. for all API calls), or locally, i.e. just for a particular call, or both.
735
+
736
+ Http headers are passed through an object whose keys represent the header name and values the corresponding header value. Nothing particular is done in term of case sensitivity, headers will be passed as passed.
737
+
738
+ To set global HTTP headers for all API calls of a client, pass an http headers array in the connection parameters
739
+ ```js
740
+ const connectionParameters = sdk.ConnectionParameters.ofUserAndPassword(
741
+ "https://myInstance.campaign.adobe.com",
742
+ "admin", "admin",
743
+ { extraHttpHeaders: {
744
+ "X-ACC-JS-SDK-LBSAFE": "1",
745
+ "X-ACC-WEBUI-VERSION: "1.2"
746
+ } });
747
+ ```
748
+
749
+ Subsequent API calls will have the corresponding headers set.
750
+
751
+ To set more HTTP headers for a particular API call, use the "headers" method of the NLWS object.
752
+
753
+ ```js
754
+ const query = client.NLWS
755
+ .headers({'X-Test': 'hello'})
756
+ .xtkQueryDef.create(queryDef);
757
+ await query.executeQuery();
758
+ ```
759
+
657
760
 
658
761
 
659
762
  # Samples
@@ -1144,6 +1247,17 @@ const folder = {
1144
1247
  await NLWS.xtkSession.write(folder);
1145
1248
  ````
1146
1249
 
1250
+ Some objects, such as deliveries are created from templates. The `createFromModel` API is preferred in this case. Given a template name, and a patch object, it will return an object created from the template and the patch, applying all sort of business rules and default values. This object can be inserted using a writer.
1251
+
1252
+ In this example, an email delivery is created from the "mail" delivery template and it's label is set to "Hello".
1253
+
1254
+ Note the xtkschema attribute in the second parameter of the `createFromModel` API call which is needed for the SDK to perform the proper JSON to XML transformation.
1255
+
1256
+ ```js
1257
+ const mail = await client.NLWS.nmsDelivery.createFromModel('mail', { xtkschema:'nms:delivery', label:'Hello'});
1258
+ await client.NLWS.xtkSession.write(mail);
1259
+ ````
1260
+
1147
1261
  # Workflow API
1148
1262
 
1149
1263
  Start and stop wotkflows, passing either an id or workflow internal name
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@adobe/acc-js-sdk",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@adobe/acc-js-sdk",
9
- "version": "1.1.2",
9
+ "version": "1.1.3",
10
10
  "license": "ISC",
11
11
  "dependencies": {
12
12
  "axios": "^0.25.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/acc-js-sdk",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "ACC Javascript SDK",
5
5
  "main": "src/index.js",
6
6
  "homepage": "https://github.com/adobe/acc-js-sdk#readme",
package/samples/utils.js CHANGED
@@ -47,7 +47,9 @@ async function run(main) {
47
47
  */
48
48
  async function logon(callback) {
49
49
  var main = async () => {
50
- const connectionParameters = sdk.ConnectionParameters.ofUserAndPassword(url, user, password);
50
+ const connectionParameters = sdk.ConnectionParameters.ofUserAndPassword(url, user, password, {
51
+ clientApp: 'acc-js-sdk sample'
52
+ });
51
53
  const client = await sdk.init(connectionParameters);
52
54
  const NLWS = client.NLWS;
53
55
 
package/src/client.js CHANGED
@@ -105,16 +105,35 @@ const xtkObjectHandler = {
105
105
  * @private
106
106
  * @memberof Campaign
107
107
  */
108
- const clientHandler = (representation) => {
108
+ const clientHandler = (representation, headers) => {
109
109
  return {
110
110
  get: function(client, namespace) {
111
+
111
112
  // Force XML or JSON representation (NLWS.xml or NLWS.json)
112
- if (namespace == "xml") return new Proxy(client, clientHandler("xml"));
113
- if (namespace == "json") return new Proxy(client, clientHandler("SimpleJson"));
113
+ if (namespace == "xml") return new Proxy(client, clientHandler("xml", headers));
114
+ if (namespace == "json") return new Proxy(client, clientHandler("SimpleJson", headers));
115
+
116
+ // Override HTTP headers (NLWS.headers({...}))
117
+ // Unlike NLWS.xml or NLWS.json, NLWS.headers returns a function. This function takes key/value
118
+ // pairs of headers, and, when called, returns a proxy object which will remember the headers
119
+ // and be able to pass them to subsequent SOAP call context
120
+ if (namespace == "headers") return (methodHeaders) => {
121
+ // Build of copy of the http headers and append new headers in order to accomodate
122
+ // chained calls, such as NLWS.headers(...).headers(...)
123
+ const newHeaders = {};
124
+ if (headers) for (let h in headers) newHeaders[h] = headers[h];
125
+ if (methodHeaders) for (let h in methodHeaders) newHeaders[h] = methodHeaders[h];
126
+ return new Proxy(client, clientHandler(representation, newHeaders));
127
+ };
114
128
 
115
129
  return new Proxy({ client:client, namespace:namespace}, {
116
130
  get: function(callContext, methodName) {
117
131
  callContext.representation = representation;
132
+ callContext.headers = callContext.headers || client._connectionParameters._options.extraHttpHeaders;
133
+ if (headers) {
134
+ for (let h in headers) callContext.headers[h] = headers[h];
135
+ }
136
+
118
137
  if (methodName == ".") return callContext;
119
138
 
120
139
  // get Schema id from namespace (find first upper case letter)
@@ -248,6 +267,9 @@ class Credentials {
248
267
  * @property {Storage} storage - Overrides the storage interface (i.e. LocalStorage)
249
268
  * @property {function} refreshClient - An async callback function with the SDK client as parameter, which will be called when the ACC session is expired
250
269
  * @property {string} charset - The charset encoding used for http requests. Defaults to UTF-8 since SDK version 1.1.1
270
+ * @property {{ name:string, value:string}} extraHttpHeaders - optional key/value pair of HTTP header (will override any other headers)
271
+ * @property {string} clientApp - optional name/version of the application client of the SDK. This will be passed in HTTP headers for troubleshooting
272
+ * @property {boolean} noSDKHeaders - set to disable "ACC-SDK" HTTP headers
251
273
  * @memberOf Campaign
252
274
  */
253
275
 
@@ -312,6 +334,12 @@ class ConnectionParameters {
312
334
  this._options._storage = storage;
313
335
  this._options.refreshClient = options.refreshClient;
314
336
  this._options.charset = options.charset === undefined ? "UTF-8": options.charset;
337
+ this._options.extraHttpHeaders = {};
338
+ if (options.extraHttpHeaders) {
339
+ for (let h in options.extraHttpHeaders) this._options.extraHttpHeaders[h] = options.extraHttpHeaders[h];
340
+ }
341
+ this._options.clientApp = options.clientApp;
342
+ this._options.noSDKHeaders = !!options.noSDKHeaders;
315
343
  }
316
344
 
317
345
  /**
@@ -684,13 +712,15 @@ class Client {
684
712
  * @private
685
713
  * @param {string} urn is the API name space, usually the schema. For instance xtk:session
686
714
  * @param {string} method is the method to call, for instance Logon
715
+ * @param {boolean} internal is a boolean indicating whether the SOAP call is performed by the SDK (internal = true) or on behalf of a user
687
716
  * @return {SOAP.SoapMethodCall} a SoapMethodCall which have been initialized with security tokens... and to which the method
688
717
  * parameters should be set
689
718
  */
690
- _prepareSoapCall(urn, method, internal) {
719
+ _prepareSoapCall(urn, method, internal, extraHttpHeaders) {
691
720
  const soapCall = new SoapMethodCall(this._transport, urn, method,
692
721
  this._sessionToken, this._securityToken,
693
- this._getUserAgentString(), this._connectionParameters._options.charset);
722
+ this._getUserAgentString(), this._connectionParameters._options.charset,
723
+ extraHttpHeaders);
694
724
  soapCall.internal = !!internal;
695
725
  return soapCall;
696
726
  }
@@ -704,6 +734,7 @@ class Client {
704
734
  */
705
735
  async _retrySoapCall(soapCall) {
706
736
  soapCall.retry = false;
737
+ soapCall._retryCount = soapCall._retryCount + 1;
707
738
  var newClient = await this._refreshClient(this);
708
739
  soapCall.finalize(newClient._soapEndPoint(), newClient);
709
740
  if (this._traceAPICalls) {
@@ -778,6 +809,18 @@ class Client {
778
809
  this._securityToken = "";
779
810
  const credentials = this._connectionParameters._credentials;
780
811
 
812
+ const sdkVersion = this.sdk.getSDKVersion();
813
+ const version = `${sdkVersion.name} ${sdkVersion.version}`;
814
+ const clientApp = this._connectionParameters._options.clientApp;
815
+ if (!this._connectionParameters._options.noSDKHeaders) {
816
+ this._connectionParameters._options.extraHttpHeaders["ACC-SDK-Version"] = version;
817
+ this._connectionParameters._options.extraHttpHeaders["ACC-SDK-Auth"] = `${credentials._type}`;
818
+ if (clientApp)
819
+ this._connectionParameters._options.extraHttpHeaders["ACC-SDK-Client-App"] = clientApp;
820
+ }
821
+ // See NEO-35259
822
+ this._connectionParameters._options.extraHttpHeaders['X-Query-Source'] = `${version}${clientApp? "," + clientApp : ""}`;
823
+
781
824
  // Clear session token cookie to ensure we're not inheriting an expired cookie. See NEO-26589
782
825
  if (credentials._type != "SecurityToken" && typeof document != "undefined") {
783
826
  document.cookie = '__sessiontoken=;path=/;';
@@ -790,8 +833,8 @@ class Client {
790
833
  that.application = new Application(that);
791
834
  return Promise.resolve();
792
835
  }
793
- else if (credentials._type == "SecurityToken") {
794
- that._sessionInfo = undefined;
836
+ else if (credentials._type == "SecurityToken") {
837
+ that._sessionInfo = undefined;
795
838
  that._installedPackages = {};
796
839
  that._sessionToken = "";
797
840
  that._securityToken = credentials._securityToken;
@@ -799,12 +842,14 @@ class Client {
799
842
  return Promise.resolve();
800
843
  }
801
844
  else if (credentials._type == "UserPassword" || credentials._type == "BearerToken") {
802
- const soapCall = that._prepareSoapCall("xtk:session", credentials._type === "UserPassword" ? "Logon" : "BearerTokenLogon");
845
+ const soapCall = that._prepareSoapCall("xtk:session", credentials._type === "UserPassword" ? "Logon" : "BearerTokenLogon", false, this._connectionParameters._options.extraHttpHeaders);
803
846
  // No retry for logon SOAP methods
804
847
  soapCall.retry = false;
805
848
  if (credentials._type == "UserPassword") {
806
849
  const user = credentials._getUser();
807
850
  const password = credentials._getPassword();
851
+ if (!this._connectionParameters._options.noSDKHeaders)
852
+ this._connectionParameters._options.extraHttpHeaders["ACC-SDK-Auth"] = `${credentials._type} ${user}`;
808
853
  soapCall.writeString("login", user);
809
854
  soapCall.writeString("password", password);
810
855
  var parameters = null;
@@ -872,7 +917,7 @@ class Client {
872
917
  if (!that.isLogged()) return;
873
918
  const credentials = this._connectionParameters._credentials;
874
919
  if (credentials._type != "SessionToken" && credentials._type != "AnonymousUser") {
875
- var soapCall = that._prepareSoapCall("xtk:session", "Logoff");
920
+ var soapCall = that._prepareSoapCall("xtk:session", "Logoff", false, this._connectionParameters._options.extraHttpHeaders);
876
921
  return this._makeSoapCall(soapCall).then(function() {
877
922
  that._sessionToken = "";
878
923
  that._securityToken = "";
@@ -977,7 +1022,7 @@ class Client {
977
1022
  * @returns {boolean} a boolean indicating if the package is installed or not
978
1023
  */
979
1024
  hasPackage(packageId, optionalName) {
980
- if (optionalName === undefined)
1025
+ if (optionalName !== undefined)
981
1026
  packageId = `${packageId}:${optionalName}`;
982
1027
  if (!this.isLogged())
983
1028
  throw CampaignException.NOT_LOGGED_IN(undefined, `Cannot call hasPackage: session not connected`);
@@ -991,7 +1036,7 @@ class Client {
991
1036
  * @private
992
1037
  * @deprecated since version 1.0.0
993
1038
  */
994
- async _getSecretKeyCipher() {
1039
+ async _getSecretKeyCipher() {
995
1040
  var that = this;
996
1041
  if (this._secretKeyCipher) return this._secretKeyCipher;
997
1042
  return that.getOption("XtkSecretKey").then(function(secretKey) {
@@ -1014,7 +1059,7 @@ class Client {
1014
1059
  */
1015
1060
  async getEntityIfMoreRecent(entityType, fullName, representation, internal) {
1016
1061
  const that = this;
1017
- const soapCall = this._prepareSoapCall("xtk:persist", "GetEntityIfMoreRecent", internal);
1062
+ const soapCall = this._prepareSoapCall("xtk:persist", "GetEntityIfMoreRecent", internal, this._connectionParameters._options.extraHttpHeaders);
1018
1063
  soapCall.writeString("pk", entityType + "|" + fullName);
1019
1064
  soapCall.writeString("md5", "");
1020
1065
  soapCall.writeBoolean("mustExist", false);
@@ -1117,7 +1162,7 @@ class Client {
1117
1162
  // console.log(method.toXMLString());
1118
1163
 
1119
1164
  var urn = that._methodCache.getSoapUrn(schemaId, methodName);
1120
- var soapCall = that._prepareSoapCall(urn, methodName);
1165
+ var soapCall = that._prepareSoapCall(urn, methodName, false, callContext.headers);
1121
1166
 
1122
1167
  // If method is called with one parameter which is a function, then we assume it's a hook: the function will return
1123
1168
  // the actual list of parameters
@@ -1158,6 +1203,8 @@ class Client {
1158
1203
  soapCall.writeByte(paramName, XtkCaster.asByte(paramValue));
1159
1204
  else if (type == "short")
1160
1205
  soapCall.writeShort(paramName, XtkCaster.asShort(paramValue));
1206
+ else if (type == "int")
1207
+ soapCall.writeLong(paramName, XtkCaster.asLong(paramValue));
1161
1208
  else if (type == "long")
1162
1209
  soapCall.writeLong(paramName, XtkCaster.asLong(paramValue));
1163
1210
  else if (type == "int64")
@@ -1182,6 +1229,7 @@ class Client {
1182
1229
  const index = xtkschema.indexOf(":");
1183
1230
  docName = xtkschema.substr(index+1);
1184
1231
  }
1232
+ if (!docName) docName = paramName; // Use te parameter name as the XML root element
1185
1233
  var xmlValue = that._fromRepresentation(docName, paramValue, callContext.representation);
1186
1234
  if (type == "DOMDocument")
1187
1235
  soapCall.writeDocument(paramName, xmlValue);
@@ -1321,8 +1369,11 @@ class Client {
1321
1369
  */
1322
1370
  async test() {
1323
1371
  const request = {
1324
- url: `${this._connectionParameters._endpoint}/r/test`
1372
+ url: `${this._connectionParameters._endpoint}/r/test`,
1373
+ headers: {}
1325
1374
  };
1375
+ for (let h in this._connectionParameters._options.extraHttpHeaders)
1376
+ request.headers[h] = this._connectionParameters._options.extraHttpHeaders[h];
1326
1377
  const body = await this._makeHttpCall(request);
1327
1378
  const xml = DomUtil.parse(body);
1328
1379
  const result = this._toRepresentation(xml);
@@ -1342,6 +1393,8 @@ class Client {
1342
1393
  'Cookie': '__sessiontoken=' + this._sessionToken
1343
1394
  }
1344
1395
  };
1396
+ for (let h in this._connectionParameters._options.extraHttpHeaders)
1397
+ request.headers[h] = this._connectionParameters._options.extraHttpHeaders[h];
1345
1398
  const body = await this._makeHttpCall(request);
1346
1399
  const lines = body.split('\n');
1347
1400
  const doc = DomUtil.newDocument("ping");
@@ -1371,6 +1424,8 @@ class Client {
1371
1424
  'Cookie': '__sessiontoken=' + this._sessionToken
1372
1425
  }
1373
1426
  };
1427
+ for (let h in this._connectionParameters._options.extraHttpHeaders)
1428
+ request.headers[h] = this._connectionParameters._options.extraHttpHeaders[h];
1374
1429
  const body = await this._makeHttpCall(request);
1375
1430
  const lines = body.split('\n');
1376
1431
  const doc = DomUtil.newDocument("ping");
package/src/soap.js CHANGED
@@ -79,11 +79,12 @@ const NS_XSD = "http://www.w3.org/2001/XMLSchema";
79
79
  * @param {string} securityToken Campaign security token
80
80
  * @param {string} userAgentString The user agent string to use for HTTP requests
81
81
  * @param {string} charset The charset encoding used for http requests, usually UTF-8
82
+ * @param {{ name:string, value:string}} extraHttpHeaders key/value pair of HTTP header (will override any other headers)
82
83
  * @memberof SOAP
83
84
  */
84
85
  class SoapMethodCall {
85
86
 
86
- constructor(transport, urn, methodName, sessionToken, securityToken, userAgentString, charset) {
87
+ constructor(transport, urn, methodName, sessionToken, securityToken, userAgentString, charset, extraHttpHeaders) {
87
88
  this.request = undefined; // The HTTP request (object litteral passed to the transport layer)
88
89
  this.response = undefined; // The HTTP response object (in case of success)
89
90
 
@@ -96,11 +97,13 @@ class SoapMethodCall {
96
97
  this.internal = false;
97
98
  // Enable soap retry
98
99
  this.retry = true;
100
+ this._retryCount = 0;
99
101
 
100
102
  this._sessionToken = sessionToken || "";
101
103
  this._securityToken = securityToken || "";
102
104
  this._userAgentString = userAgentString;
103
105
  this._charset = charset || "";
106
+ this._extraHttpHeaders = extraHttpHeaders || {};
104
107
 
105
108
  // THe SOAP call being built
106
109
  this._doc = undefined; // XML document for SOAP call
@@ -213,7 +216,7 @@ class SoapMethodCall {
213
216
  * @param {string} tag the parameter name
214
217
  * @param {*} value the parameter value, which will be casted to a int32 according to xtk rules
215
218
  */
216
- writeLong(tag, value) {
219
+ writeLong(tag, value) {
217
220
  value = XtkCaster.asLong(value);
218
221
  this._addNode(tag, "xsd:int", XtkCaster.asString(value), SOAP_ENCODING_NATIVE);
219
222
  }
@@ -299,7 +302,8 @@ class SoapMethodCall {
299
302
  writeDocument(tag, document) {
300
303
  const node = this._addNode(tag, "", null, SOAP_ENCODING_XML);
301
304
  if (document !== null && document !== undefined) {
302
- const child = this._doc.importNode(document.documentElement, true);
305
+ const element = document.nodeType === 1 ? document : document.documentElement;
306
+ const child = this._doc.importNode(element, true);
303
307
  node.appendChild(child);
304
308
  }
305
309
  }
@@ -515,20 +519,36 @@ class SoapMethodCall {
515
519
  */
516
520
  _createHTTPRequest(url) {
517
521
 
522
+ const headers = {
523
+ 'Content-type': `application/soap+xml${this._charset ? ";charset=" + this._charset : ""}`,
524
+ 'SoapAction': `${this.urn}#${this.methodName}`,
525
+ 'X-Security-Token': this._securityToken
526
+ };
527
+
528
+ // Add HTTP headers specific to the SOAP call for better tracing/troubleshooting
529
+ if (this._extraHttpHeaders && this._extraHttpHeaders['ACC-SDK-Version']) {
530
+ // "this.retry" means that the call can be retried, not that it is being retried. The HTTP header howerver, indicates that this
531
+ // is actually a retry of a previously failed call (expired token)
532
+ if (this._retryCount > 0) headers["ACC-SDK-Call-RetryCount"] = `${this._retryCount}`;
533
+ if (this.internal) headers["ACC-SDK-Call-Internal"] = "1";
534
+ }
535
+
518
536
  const options = {
519
537
  url: url,
520
538
  method: 'POST',
521
- headers: {
522
- 'Content-type': `application/soap+xml${this._charset ? ";charset=" + this._charset : ""}`,
523
- 'SoapAction': `${this.urn}#${this.methodName}`,
524
- 'X-Security-Token': this._securityToken
525
- },
539
+ headers: headers,
526
540
  data: DomUtil.toXMLString(this._doc)
527
541
  };
528
542
  if (this._sessionToken)
529
543
  options.headers.Cookie = '__sessiontoken=' + this._sessionToken;
530
544
  if (this._userAgentString)
531
545
  options.headers['User-Agent'] = this._userAgentString;
546
+
547
+ // Override http headers with custom headers
548
+ for (let h in this._extraHttpHeaders) {
549
+ options.headers[h] = this._extraHttpHeaders[h];
550
+ }
551
+
532
552
  return options;
533
553
  }
534
554
 
@@ -56,12 +56,12 @@ describe('ACC Client (has package)', () => {
56
56
  client._transport.mockReturnValueOnce(LOGON_RESPONSE);
57
57
  await client.NLWS.xtkSession.logon();
58
58
 
59
- expect(client.hasPackage("nms:campaign"));
60
- expect(client.hasPackage("nms", "campaign"));
61
- expect(!client.hasPackage("nms:mrm"));
62
- expect(!client.hasPackage("nms", "mrm"));
63
- expect(client.hasPackage("nms:core"));
64
- expect(client.hasPackage("nms", "core"));
59
+ expect(client.hasPackage("nms:campaign")).toBeTruthy();
60
+ expect(client.hasPackage("nms", "campaign")).toBeTruthy();
61
+ expect(client.hasPackage("nms:mrm")).toBeFalsy();
62
+ expect(client.hasPackage("nms", "mrm")).toBeFalsy();
63
+ expect(client.hasPackage("nms:core")).toBeTruthy();
64
+ expect(client.hasPackage("nms", "core")).toBeTruthy();
65
65
  });
66
66
 
67
67
  it('should fail when unlogged', async () => {
@@ -2283,15 +2283,15 @@ describe('ACC Client', function () {
2283
2283
  it("Should ignore protocol for local storage root key", async () => {
2284
2284
  var connectionParameters = sdk.ConnectionParameters.ofUserAndPassword("http://acc-sdk:8080", "admin", "admin", {});
2285
2285
  var client = await sdk.init(connectionParameters);
2286
- expect(client._optionCache._storage._rootKey).toBe("acc.js.sdk.1.1.2.acc-sdk:8080.cache.OptionCache$");
2286
+ expect(client._optionCache._storage._rootKey).toBe("acc.js.sdk.1.1.3.acc-sdk:8080.cache.OptionCache$");
2287
2287
 
2288
2288
  connectionParameters = sdk.ConnectionParameters.ofUserAndPassword("https://acc-sdk:8080", "admin", "admin", {});
2289
2289
  client = await sdk.init(connectionParameters);
2290
- expect(client._optionCache._storage._rootKey).toBe("acc.js.sdk.1.1.2.acc-sdk:8080.cache.OptionCache$");
2290
+ expect(client._optionCache._storage._rootKey).toBe("acc.js.sdk.1.1.3.acc-sdk:8080.cache.OptionCache$");
2291
2291
 
2292
2292
  connectionParameters = sdk.ConnectionParameters.ofUserAndPassword("acc-sdk:8080", "admin", "admin", {});
2293
2293
  client = await sdk.init(connectionParameters);
2294
- expect(client._optionCache._storage._rootKey).toBe("acc.js.sdk.1.1.2.acc-sdk:8080.cache.OptionCache$");
2294
+ expect(client._optionCache._storage._rootKey).toBe("acc.js.sdk.1.1.3.acc-sdk:8080.cache.OptionCache$");
2295
2295
  })
2296
2296
 
2297
2297
  it("Should support no storage", async () => {
@@ -2474,4 +2474,333 @@ describe('ACC Client', function () {
2474
2474
  expect(json).toBe('{"#text":[],"extAccount":[{"id":"1816","name":"defaultPopAccount"},{"id":"1818","name":"defaultOther"},{"id":"1849","name":"billingReport"},{"id":"12070","name":"TST_EXT_ACCOUNT_POSTGRESQL"},{"id":"1817","name":"defaultEmailBulk"},{"id":"2087","name":"ffda"},{"id":"2088","name":"defaultEmailMid"}]}');
2475
2475
  });
2476
2476
  });
2477
+
2478
+ describe('Support for int type parameters such as nms:extAccount#UpdateMCSynchWkf', () => {
2479
+ it("Should call nms:extAccount#UpdateMCSynchWkf", async () => {
2480
+ const client = await Mock.makeClient();
2481
+ client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
2482
+ await client.NLWS.xtkSession.logon();
2483
+
2484
+ client._transport.mockReturnValueOnce(Promise.resolve(`<?xml version='1.0'?>
2485
+ <SOAP-ENV:Envelope xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:ns='urn:wpp:default' xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/'>
2486
+ <SOAP-ENV:Body>
2487
+ <GetEntityIfMoreRecentResponse xmlns='urn:wpp:default' SOAP-ENV:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'>
2488
+ <pdomDoc xsi:type='ns:Element' SOAP-ENV:encodingStyle='http://xml.apache.org/xml-soap/literalxml'>
2489
+ <schema name="extAccount" namespace="nms" xtkschema="xtk:schema">
2490
+ <element name="extAccount"></element>
2491
+ <methods>
2492
+ <method library="nms:messageCenter.js" name="UpdateMCSynchWkf" static="true" hidden="true">
2493
+ <parameters>
2494
+ <param name="extAccountId" type="int" desc="Message Center external account identifier"/>
2495
+ </parameters>
2496
+ </method>
2497
+ </methods>
2498
+ </schema>
2499
+ </pdomDoc>
2500
+ </GetEntityIfMoreRecentResponse>
2501
+ </SOAP-ENV:Body>
2502
+ </SOAP-ENV:Envelope>`));
2503
+
2504
+ client._transport.mockReturnValueOnce(Promise.resolve(`<?xml version='1.0'?>
2505
+ <SOAP-ENV:Envelope xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:ns='urn:nms:extAccount' xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/'>
2506
+ <SOAP-ENV:Body>
2507
+ <UpdateMCSynchWkfResponse xmlns='urn:nms:extAccount' SOAP-ENV:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'>
2508
+ </UpdateMCSynchWkfResponse>
2509
+ </SOAP-ENV:Body>
2510
+ </SOAP-ENV:Envelope>`));
2511
+
2512
+ await client.NLWS.nmsExtAccount.updateMCSynchWkf(1);
2513
+ })
2514
+ });
2515
+
2516
+ describe("Method-level HTTP headers", () => {
2517
+ it("Should set header", async () => {
2518
+ const client = await Mock.makeClient();
2519
+ client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
2520
+ await client.NLWS.xtkSession.logon();
2521
+ client._transport.mockReturnValueOnce(Mock.GET_XTK_QUERY_SCHEMA_RESPONSE);
2522
+ client._transport.mockReturnValueOnce(Mock.GET_QUERY_EXECUTE_RESPONSE);
2523
+ const queryDef = {
2524
+ "schema": "nms:extAccount",
2525
+ "operation": "select",
2526
+ "select": {
2527
+ "node": [
2528
+ { "expr": "@id" },
2529
+ { "expr": "@name" }
2530
+ ]
2531
+ }
2532
+ };
2533
+ const query = client.NLWS.headers({'X-Test': 'hello'}).xtkQueryDef.create(queryDef);
2534
+
2535
+ let headers = {};
2536
+ client.registerObserver({
2537
+ onSOAPCall: (soapCall) => {
2538
+ const request = soapCall.request;
2539
+ headers = request.headers;
2540
+ }
2541
+ });
2542
+ await query.executeQuery();
2543
+ expect(headers).toMatchObject({
2544
+ "SoapAction": "xtk:queryDef#ExecuteQuery",
2545
+ "X-Test": "hello"
2546
+ });
2547
+ });
2548
+
2549
+ it("Should support global and method-level http headers", async () => {
2550
+ const client = await Mock.makeClient({
2551
+ extraHttpHeaders: {
2552
+ "X-Test": "world",
2553
+ "X-Test-Global": "global"
2554
+ }
2555
+ });
2556
+ client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
2557
+ await client.NLWS.xtkSession.logon();
2558
+ client._transport.mockReturnValueOnce(Mock.GET_XTK_QUERY_SCHEMA_RESPONSE);
2559
+ client._transport.mockReturnValueOnce(Mock.GET_QUERY_EXECUTE_RESPONSE);
2560
+ const queryDef = {
2561
+ "schema": "nms:extAccount",
2562
+ "operation": "select",
2563
+ "select": {
2564
+ "node": [
2565
+ { "expr": "@id" },
2566
+ { "expr": "@name" }
2567
+ ]
2568
+ }
2569
+ };
2570
+ const query = client.NLWS.headers({'X-Test': 'hello'}).xtkQueryDef.create(queryDef);
2571
+
2572
+ let headers = {};
2573
+ client.registerObserver({
2574
+ onSOAPCall: (soapCall) => {
2575
+ const request = soapCall.request;
2576
+ headers = request.headers;
2577
+ }
2578
+ });
2579
+ await query.executeQuery();
2580
+ expect(headers).toMatchObject({
2581
+ "SoapAction": "xtk:queryDef#ExecuteQuery",
2582
+ "X-Test": "hello",
2583
+ "X-Test-Global": "global"
2584
+ });
2585
+ })
2586
+
2587
+ it("Should support undefined method headers", async () => {
2588
+ const client = await Mock.makeClient({
2589
+ extraHttpHeaders: {
2590
+ "X-Test": "world",
2591
+ "X-Test-Global": "global"
2592
+ }
2593
+ });
2594
+ client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
2595
+ await client.NLWS.xtkSession.logon();
2596
+ client._transport.mockReturnValueOnce(Mock.GET_XTK_QUERY_SCHEMA_RESPONSE);
2597
+ client._transport.mockReturnValueOnce(Mock.GET_QUERY_EXECUTE_RESPONSE);
2598
+ const queryDef = {
2599
+ "schema": "nms:extAccount",
2600
+ "operation": "select",
2601
+ "select": {
2602
+ "node": [
2603
+ { "expr": "@id" },
2604
+ { "expr": "@name" }
2605
+ ]
2606
+ }
2607
+ };
2608
+ // missing headers
2609
+ const query = client.NLWS.headers().xtkQueryDef.create(queryDef);
2610
+
2611
+ let headers = {};
2612
+ client.registerObserver({
2613
+ onSOAPCall: (soapCall) => {
2614
+ const request = soapCall.request;
2615
+ headers = request.headers;
2616
+ }
2617
+ });
2618
+ await query.executeQuery();
2619
+ expect(headers).toMatchObject({
2620
+ "SoapAction": "xtk:queryDef#ExecuteQuery",
2621
+ "X-Test": "world",
2622
+ "X-Test-Global": "global"
2623
+ });
2624
+ })
2625
+
2626
+ it("Should set http headers with an xml representation", async () => {
2627
+ const client = await Mock.makeClient();
2628
+ client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
2629
+ await client.NLWS.xtkSession.logon();
2630
+ client._transport.mockReturnValueOnce(Mock.GET_XTK_QUERY_SCHEMA_RESPONSE);
2631
+ client._transport.mockReturnValueOnce(Mock.GET_QUERY_EXECUTE_RESPONSE);
2632
+ const queryDef = DomUtil.parse(`
2633
+ <queryDef schema="nms:extAccount" operation="select">
2634
+ <select>
2635
+ <node expr="@id"/>
2636
+ <node expr="@name"/>
2637
+ </select>
2638
+ </queryDef>
2639
+ `);
2640
+ const query = client.NLWS
2641
+ .headers({'X-Test': 'hello', 'X-Test-Before': 'before'})
2642
+ .xml
2643
+ .headers({'X-Test': 'world', 'X-Test-After': 'after'})
2644
+ .xtkQueryDef.create(queryDef);
2645
+ let headers = {};
2646
+ client.registerObserver({
2647
+ onSOAPCall: (soapCall) => {
2648
+ const request = soapCall.request;
2649
+ headers = request.headers;
2650
+ }
2651
+ });
2652
+ await query.executeQuery();
2653
+ console.log(headers);
2654
+ expect(headers).toMatchObject({
2655
+ "SoapAction": "xtk:queryDef#ExecuteQuery",
2656
+ "X-Test": "world",
2657
+ "X-Test-Before": "before",
2658
+ "X-Test-After": "after"
2659
+ });
2660
+ });
2661
+ });
2662
+
2663
+ describe("ACC-SDK HTTP headers", () => {
2664
+
2665
+ const collectHeaders = async (client, callback) => {
2666
+ let headers = {};
2667
+ client.registerObserver({
2668
+ onSOAPCall: (soapCall) => {
2669
+ const request = soapCall.request;
2670
+ headers = request.headers;
2671
+ },
2672
+ onHTTPCall: (request) => {
2673
+ headers = request.headers;
2674
+ }
2675
+ });
2676
+ await callback();
2677
+ return headers;
2678
+ };
2679
+
2680
+ it("Should set headers by default", async () => {
2681
+ const client = await Mock.makeClient();
2682
+ client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
2683
+ await client.NLWS.xtkSession.logon();
2684
+ client._transport.mockReturnValueOnce(Mock.GET_XTK_QUERY_SCHEMA_RESPONSE);
2685
+ client._transport.mockReturnValueOnce(Mock.GET_QUERY_EXECUTE_RESPONSE);
2686
+ const queryDef = {
2687
+ "schema": "nms:extAccount",
2688
+ "operation": "select",
2689
+ "select": {
2690
+ "node": [
2691
+ { "expr": "@id" },
2692
+ { "expr": "@name" }
2693
+ ]
2694
+ }
2695
+ };
2696
+ const query = client.NLWS.xtkQueryDef.create(queryDef);
2697
+
2698
+ const headers = await collectHeaders(client, async() => {
2699
+ await query.executeQuery();
2700
+ });
2701
+
2702
+ expect(headers).toMatchObject({
2703
+ "ACC-SDK-Version": `${sdk.getSDKVersion().name} ${sdk.getSDKVersion().version}`,
2704
+ "ACC-SDK-Auth": "UserPassword admin",
2705
+ "X-Query-Source": `${sdk.getSDKVersion().name} ${sdk.getSDKVersion().version}`,
2706
+ });
2707
+ // This header is only set if "clientApp" connection parameter is set
2708
+ expect(headers["ACC-SDK-Client-App"]).toBeUndefined();
2709
+ });
2710
+
2711
+ it("Should disable ACC-SDK headers", async () => {
2712
+ const client = await Mock.makeClient({
2713
+ noSDKHeaders: true
2714
+ });
2715
+ client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
2716
+ await client.NLWS.xtkSession.logon();
2717
+ client._transport.mockReturnValueOnce(Mock.GET_XTK_QUERY_SCHEMA_RESPONSE);
2718
+ client._transport.mockReturnValueOnce(Mock.GET_QUERY_EXECUTE_RESPONSE);
2719
+ const queryDef = {
2720
+ "schema": "nms:extAccount",
2721
+ "operation": "select",
2722
+ "select": {
2723
+ "node": [
2724
+ { "expr": "@id" },
2725
+ { "expr": "@name" }
2726
+ ]
2727
+ }
2728
+ };
2729
+ const query = client.NLWS.xtkQueryDef.create(queryDef);
2730
+
2731
+ const headers = await collectHeaders(client, async() => {
2732
+ await query.executeQuery();
2733
+ });
2734
+ expect(headers["ACC-SDK-Version"]).toBeUndefined();
2735
+ expect(headers["ACC-SDK-Auth"]).toBeUndefined();
2736
+ expect(headers["X-Query-Source"]).toBe(`${sdk.getSDKVersion().name} ${sdk.getSDKVersion().version}`);
2737
+ });
2738
+
2739
+ it("Should support ACC-SDK-Client-App header", async () => {
2740
+ const client = await Mock.makeClient({
2741
+ clientApp: 'Test client app'
2742
+ });
2743
+ client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
2744
+ await client.NLWS.xtkSession.logon();
2745
+ client._transport.mockReturnValueOnce(Mock.GET_XTK_QUERY_SCHEMA_RESPONSE);
2746
+ client._transport.mockReturnValueOnce(Mock.GET_QUERY_EXECUTE_RESPONSE);
2747
+ const queryDef = {
2748
+ "schema": "nms:extAccount",
2749
+ "operation": "select",
2750
+ "select": {
2751
+ "node": [
2752
+ { "expr": "@id" },
2753
+ { "expr": "@name" }
2754
+ ]
2755
+ }
2756
+ };
2757
+ const query = client.NLWS.xtkQueryDef.create(queryDef);
2758
+
2759
+ const headers = await collectHeaders(client, async() => {
2760
+ await query.executeQuery();
2761
+ });
2762
+ expect(headers).toMatchObject({
2763
+ "ACC-SDK-Version": `${sdk.getSDKVersion().name} ${sdk.getSDKVersion().version}`,
2764
+ "ACC-SDK-Auth": "UserPassword admin",
2765
+ "ACC-SDK-Client-App": "Test client app",
2766
+ "X-Query-Source": `${sdk.getSDKVersion().name} ${sdk.getSDKVersion().version},Test client app`,
2767
+ });
2768
+ });
2769
+
2770
+ it("Should set ACC-SDK headers on ping JSP", async () => {
2771
+ const client = await Mock.makeClient({
2772
+ clientApp: 'Test client app'
2773
+ });
2774
+ client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
2775
+ await client.NLWS.xtkSession.logon();
2776
+ const headers = await collectHeaders(client, async() => {
2777
+ client._transport.mockReturnValueOnce(Mock.PING);
2778
+ await client.ping();
2779
+ });
2780
+ expect(headers).toMatchObject({
2781
+ "ACC-SDK-Version": `${sdk.getSDKVersion().name} ${sdk.getSDKVersion().version}`,
2782
+ "ACC-SDK-Auth": "UserPassword admin",
2783
+ "ACC-SDK-Client-App": "Test client app",
2784
+ "X-Query-Source": `${sdk.getSDKVersion().name} ${sdk.getSDKVersion().version},Test client app`,
2785
+ });
2786
+ });
2787
+
2788
+ it("Should set ACC-SDK headers on mcping JSP", async () => {
2789
+ const client = await Mock.makeClient({
2790
+ clientApp: 'Test client app'
2791
+ });
2792
+ client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
2793
+ await client.NLWS.xtkSession.logon();
2794
+ const headers = await collectHeaders(client, async() => {
2795
+ client._transport.mockReturnValueOnce(Mock.MC_PING);
2796
+ await client.mcPing();
2797
+ });
2798
+ expect(headers).toMatchObject({
2799
+ "ACC-SDK-Version": `${sdk.getSDKVersion().name} ${sdk.getSDKVersion().version}`,
2800
+ "ACC-SDK-Auth": "UserPassword admin",
2801
+ "ACC-SDK-Client-App": "Test client app",
2802
+ "X-Query-Source": `${sdk.getSDKVersion().name} ${sdk.getSDKVersion().version},Test client app`,
2803
+ });
2804
+ });
2805
+ });
2477
2806
  });
package/test/soap.test.js CHANGED
@@ -25,8 +25,8 @@ const sdk = require('../src/index.js');
25
25
 
26
26
  const URL = "https://soap-test/nl/jsp/soaprouter.jsp";
27
27
 
28
- function makeSoapMethodCall(transport, urn, methodName, sessionToken, securityToken, userAgentString, optionalCharset) {
29
- const call = new SoapMethodCall(transport, urn, methodName, sessionToken, securityToken, userAgentString, optionalCharset);
28
+ function makeSoapMethodCall(transport, urn, methodName, sessionToken, securityToken, userAgentString, optionalCharset, charset, extraHttpHeaders) {
29
+ const call = new SoapMethodCall(transport, urn, methodName, sessionToken, securityToken, userAgentString, optionalCharset, charset, extraHttpHeaders);
30
30
  return call;
31
31
  }
32
32
 
@@ -388,6 +388,22 @@ describe('SOAP', function() {
388
388
  expect(actualElement.getAttribute("att")).toBe("Hello");
389
389
  });
390
390
 
391
+ it('Should support passing DOM elements instead of document parameters', function() {
392
+ const xml = '<root att="Hello"><child/></root>';
393
+ const doc = DomUtil.parse(xml);
394
+
395
+ const call = makeSoapMethodCall(undefined, "xtk:session", "Document", "$session$", "$security$");
396
+ call.writeDocument("p", doc.documentElement);
397
+ const request = call._createHTTPRequest(URL);
398
+ const env = DomUtil.parse(request.data).documentElement;
399
+ const body = hasChildElement(env, "SOAP-ENV:Body");
400
+ const method = hasChildElement(body, "m:Document");
401
+ const param = hasChildElement(method, "p");
402
+ const actualElement = hasChildElement(param, "root");
403
+ expect(actualElement).toBeTruthy();
404
+ expect(actualElement.getAttribute("att")).toBe("Hello");
405
+ });
406
+
391
407
  it('Should write null document', function() {
392
408
  const call = makeSoapMethodCall(undefined, "xtk:session", "Document", "$session$", "$security$");
393
409
  call.writeDocument("p", null);
@@ -850,5 +866,23 @@ describe("Campaign exception", () => {
850
866
  expect(req.headers["X-Security-Token"].indexOf("$security$")).toBe(-1);
851
867
  })
852
868
 
869
+ describe('Extra Http headers', () => {
870
+ it("Should take additional headers", () => {
871
+ const call = makeSoapMethodCall(undefined, "xtk:session", "Date", "$session$", "$security$", "My User Agent", undefined, { 'X-ACC-UI-Version': '1.0' });
872
+ const request = call._createHTTPRequest(URL);
873
+ expect(request.headers['User-Agent']).toBe("My User Agent");
874
+ expect(request.headers['X-ACC-UI-Version']).toBe("1.0");
875
+ expect(request.headers['SoapAction']).toBe("xtk:session#Date");
876
+ });
877
+
878
+ it("Should override default headers headers", () => {
879
+ const call = makeSoapMethodCall(undefined, "xtk:session", "Date", "$session$", "$security$", "My User Agent", undefined, { 'X-ACC-UI-Version': '1.0', 'SoapAction': 'My soap action' });
880
+ const request = call._createHTTPRequest(URL);
881
+ expect(request.headers['User-Agent']).toBe("My User Agent");
882
+ expect(request.headers['X-ACC-UI-Version']).toBe("1.0");
883
+ expect(request.headers['SoapAction']).toBe("My soap action");
884
+ });
885
+ });
886
+
853
887
  });
854
888