sfmc-sdk 0.5.0 → 0.6.1
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/lib/auth.js +6 -5
- package/lib/rest.js +20 -8
- package/lib/soap.js +20 -35
- package/lib/util.js +76 -0
- package/package.json +48 -44
- package/test/auth.test.js +19 -19
- package/test/resources/auth.json +1 -1
- package/test/resources/rest.json +5 -0
- package/test/rest.test.js +103 -16
- package/test/soap.test.js +20 -19
- package/test/utils.js +1 -1
package/lib/auth.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const axios = require('axios');
|
|
4
|
-
const { isConnectionError } = require('./util');
|
|
4
|
+
const { isConnectionError, RestError } = require('./util');
|
|
5
5
|
const AVAIALABLE_SCOPES = [
|
|
6
6
|
'accounts_read',
|
|
7
7
|
'accounts_write',
|
|
@@ -154,21 +154,21 @@ module.exports = class Auth {
|
|
|
154
154
|
}
|
|
155
155
|
if (Boolean(forceRefresh) || _isExpired(this.authObject)) {
|
|
156
156
|
try {
|
|
157
|
+
remainingAttempts--;
|
|
157
158
|
this.authObject = await _requestToken(this.authObject);
|
|
158
159
|
} catch (ex) {
|
|
159
160
|
if (
|
|
160
161
|
this.options.retryOnConnectionError &&
|
|
161
|
-
remainingAttempts &&
|
|
162
|
+
remainingAttempts > 0 &&
|
|
162
163
|
isConnectionError(ex.code)
|
|
163
164
|
) {
|
|
164
165
|
if (this.options?.eventHandlers?.onConnectionError) {
|
|
165
166
|
this.options.eventHandlers.onConnectionError(ex, remainingAttempts);
|
|
166
167
|
}
|
|
167
|
-
remainingAttempts--;
|
|
168
168
|
return this.getAccessToken(forceRefresh, remainingAttempts);
|
|
169
|
-
} else {
|
|
170
|
-
throw ex;
|
|
171
169
|
}
|
|
170
|
+
|
|
171
|
+
throw new RestError(ex);
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
if (this.options?.eventHandlers?.onRefresh) {
|
|
@@ -180,6 +180,7 @@ module.exports = class Auth {
|
|
|
180
180
|
|
|
181
181
|
/**
|
|
182
182
|
* Helper to get back list of scopes supported by SDK
|
|
183
|
+
*
|
|
183
184
|
* @returns {Array[String]} array of potential scopes
|
|
184
185
|
*/
|
|
185
186
|
getSupportedScopes() {
|
package/lib/rest.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const axios = require('axios');
|
|
4
|
-
const { isObject, isConnectionError } = require('./util');
|
|
4
|
+
const { isObject, isPayload, isConnectionError, RestError } = require('./util');
|
|
5
5
|
const pLimit = require('p-limit');
|
|
6
6
|
|
|
7
7
|
module.exports = class Rest {
|
|
@@ -175,23 +175,35 @@ module.exports = class Rest {
|
|
|
175
175
|
requestOptions.headers = {
|
|
176
176
|
Authorization: `Bearer ` + this.auth.authObject.access_token,
|
|
177
177
|
};
|
|
178
|
-
|
|
179
|
-
|
|
178
|
+
remainingAttempts--;
|
|
179
|
+
if (this.options?.eventHandlers?.logRequest) {
|
|
180
|
+
this.options.eventHandlers.logRequest(requestOptions);
|
|
181
|
+
}
|
|
182
|
+
const response = await axios(requestOptions);
|
|
183
|
+
if (this.options?.eventHandlers?.logResponse) {
|
|
184
|
+
this.options.eventHandlers.logResponse({
|
|
185
|
+
data: response.data,
|
|
186
|
+
status: response.status,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
return response.data;
|
|
180
190
|
} catch (ex) {
|
|
181
191
|
if (
|
|
182
192
|
this.options.retryOnConnectionError &&
|
|
183
|
-
remainingAttempts &&
|
|
193
|
+
remainingAttempts > 0 &&
|
|
184
194
|
isConnectionError(ex.code)
|
|
185
195
|
) {
|
|
186
|
-
|
|
196
|
+
if (this.options?.eventHandlers?.onConnectionError) {
|
|
197
|
+
this.options.eventHandlers.onConnectionError(ex, remainingAttempts);
|
|
198
|
+
}
|
|
187
199
|
return this._apiRequest(requestOptions, remainingAttempts);
|
|
188
200
|
} else if (ex.response && ex.response.status === 401 && remainingAttempts) {
|
|
189
201
|
// force refresh due to url related issue
|
|
190
202
|
await this.auth.getAccessToken(true);
|
|
191
203
|
//only retry once on refresh since there should be no reason for this token to be invalid
|
|
192
|
-
return this._apiRequest(requestOptions,
|
|
204
|
+
return this._apiRequest(requestOptions, 1);
|
|
193
205
|
} else {
|
|
194
|
-
throw ex;
|
|
206
|
+
throw new RestError(ex);
|
|
195
207
|
}
|
|
196
208
|
}
|
|
197
209
|
}
|
|
@@ -202,7 +214,7 @@ module.exports = class Rest {
|
|
|
202
214
|
* @param {object} options API request opptions
|
|
203
215
|
*/
|
|
204
216
|
function _checkPayload(options) {
|
|
205
|
-
if (!
|
|
217
|
+
if (!isPayload(options.data)) {
|
|
206
218
|
throw new Error(`${options.method} requests require a payload in options.data`);
|
|
207
219
|
}
|
|
208
220
|
}
|
package/lib/soap.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
const axios = require('axios');
|
|
3
3
|
const { XMLBuilder, XMLParser } = require('fast-xml-parser');
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
const { isObject, isConnectionError, SOAPError } = require('./util');
|
|
5
6
|
|
|
6
7
|
module.exports = class Soap {
|
|
7
8
|
/**
|
|
@@ -96,6 +97,8 @@ module.exports = class Soap {
|
|
|
96
97
|
}
|
|
97
98
|
status = resultsBatch.OverallStatus;
|
|
98
99
|
if (status === 'MoreDataAvailable') {
|
|
100
|
+
//as requestParams is by default optional, ensure object exists in this case
|
|
101
|
+
requestParams = requestParams || {};
|
|
99
102
|
requestParams.continueRequest = resultsBatch.RequestID;
|
|
100
103
|
if (this.options?.eventHandlers?.onLoop) {
|
|
101
104
|
this.options.eventHandlers.onLoop(type, resultsBulk);
|
|
@@ -367,22 +370,25 @@ module.exports = class Soap {
|
|
|
367
370
|
this.options.eventHandlers.logRequest(requestOptions);
|
|
368
371
|
}
|
|
369
372
|
let response;
|
|
373
|
+
remainingAttempts--;
|
|
370
374
|
try {
|
|
371
375
|
response = await axios(requestOptions);
|
|
372
376
|
} catch (ex) {
|
|
373
377
|
if (
|
|
374
378
|
this.options.retryOnConnectionError &&
|
|
375
|
-
remainingAttempts &&
|
|
379
|
+
remainingAttempts > 0 &&
|
|
376
380
|
isConnectionError(ex.code)
|
|
377
381
|
) {
|
|
378
|
-
|
|
382
|
+
if (this.options?.eventHandlers?.onConnectionError) {
|
|
383
|
+
this.options.eventHandlers.onConnectionError(ex, remainingAttempts);
|
|
384
|
+
}
|
|
379
385
|
return this._apiRequest(options, remainingAttempts);
|
|
380
386
|
} else if (ex.response) {
|
|
381
387
|
// if the response is received, then continue parsing and check for errors later
|
|
382
388
|
response = ex.response;
|
|
383
389
|
} else {
|
|
384
390
|
// if no response, then throw
|
|
385
|
-
throw ex;
|
|
391
|
+
throw new SOAPError(ex);
|
|
386
392
|
}
|
|
387
393
|
}
|
|
388
394
|
if (this.options?.eventHandlers?.logResponse) {
|
|
@@ -399,13 +405,17 @@ module.exports = class Soap {
|
|
|
399
405
|
// need to wait as it may error
|
|
400
406
|
return await _parseResponse(response, options.key);
|
|
401
407
|
} catch (ex) {
|
|
402
|
-
if (ex.
|
|
408
|
+
if (ex.message === 'Token Expired' && remainingAttempts) {
|
|
403
409
|
// force refresh due to url related issue
|
|
404
410
|
await this.auth.getAccessToken(true);
|
|
405
411
|
// set to no more retries as after token refresh it should always work
|
|
406
|
-
return this._apiRequest(options,
|
|
407
|
-
} else {
|
|
412
|
+
return this._apiRequest(options, 1);
|
|
413
|
+
} else if (ex instanceof SOAPError) {
|
|
414
|
+
//rethrow as is already handled/parsed
|
|
408
415
|
throw ex;
|
|
416
|
+
} else {
|
|
417
|
+
//unknown error
|
|
418
|
+
throw new SOAPError(ex, response, null);
|
|
409
419
|
}
|
|
410
420
|
}
|
|
411
421
|
}
|
|
@@ -483,11 +493,12 @@ async function _parseResponse(response, key) {
|
|
|
483
493
|
}
|
|
484
494
|
// checks overall status error
|
|
485
495
|
if (['Error', 'Has Errors'].includes(soapBody[key].OverallStatus)) {
|
|
486
|
-
throw new SOAPError(response, soapBody[key]);
|
|
496
|
+
throw new SOAPError(null, response, soapBody[key]);
|
|
487
497
|
}
|
|
488
498
|
return soapBody[key];
|
|
489
499
|
}
|
|
490
|
-
|
|
500
|
+
// something else went wrong but payload parsed
|
|
501
|
+
throw new SOAPError(null, response, soapBody);
|
|
491
502
|
}
|
|
492
503
|
/**
|
|
493
504
|
* Method checks options object for validity
|
|
@@ -514,29 +525,3 @@ function validateOptions(options, additional) {
|
|
|
514
525
|
}
|
|
515
526
|
}
|
|
516
527
|
}
|
|
517
|
-
|
|
518
|
-
class SOAPError extends Error {
|
|
519
|
-
constructor(response, soapBody) {
|
|
520
|
-
// Content Error
|
|
521
|
-
if (soapBody && ['Error', 'Has Errors'].includes(soapBody.OverallStatus)) {
|
|
522
|
-
super('One or more errors in the Results');
|
|
523
|
-
}
|
|
524
|
-
// Payload Error
|
|
525
|
-
else if (soapBody && soapBody['soap:Fault']) {
|
|
526
|
-
super('Error in SOAP Payload');
|
|
527
|
-
const fault = soapBody['soap:Fault'];
|
|
528
|
-
this.errorCode = fault.faultcode;
|
|
529
|
-
this.errorMessage = fault.faultstring;
|
|
530
|
-
}
|
|
531
|
-
// Request Error
|
|
532
|
-
else if (response.status > 299) {
|
|
533
|
-
super('Error with SOAP Request');
|
|
534
|
-
}
|
|
535
|
-
// Fallback Error
|
|
536
|
-
else {
|
|
537
|
-
super('Unknown Error or Unhandled Request');
|
|
538
|
-
}
|
|
539
|
-
this.response = response;
|
|
540
|
-
this.JSON = soapBody;
|
|
541
|
-
}
|
|
542
|
-
}
|
package/lib/util.js
CHANGED
|
@@ -6,6 +6,15 @@
|
|
|
6
6
|
*/
|
|
7
7
|
module.exports.isObject = (obj) => Object.prototype.toString.call(obj) === '[object Object]';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Method to check if Object passed is a valid payload for API calls
|
|
11
|
+
*
|
|
12
|
+
* @param {object} obj Object to check
|
|
13
|
+
* @returns {boolean} true if is a valid payload
|
|
14
|
+
*/
|
|
15
|
+
module.exports.isPayload = (obj) =>
|
|
16
|
+
Object.prototype.toString.call(obj) === '[object Object]' || Array.isArray(obj);
|
|
17
|
+
|
|
9
18
|
/**
|
|
10
19
|
* Method to check if it is a connection error
|
|
11
20
|
*
|
|
@@ -14,3 +23,70 @@ module.exports.isObject = (obj) => Object.prototype.toString.call(obj) === '[obj
|
|
|
14
23
|
*/
|
|
15
24
|
module.exports.isConnectionError = (code) =>
|
|
16
25
|
code && ['ETIMEDOUT', 'EHOSTUNREACH', 'ENOTFOUND', 'ECONNRESET', 'ECONNABORTED'].includes(code);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* CustomError type for handling REST (including Auth) based errors
|
|
29
|
+
*
|
|
30
|
+
* @class RestError
|
|
31
|
+
* @augments {Error}
|
|
32
|
+
*/
|
|
33
|
+
module.exports.RestError = class RestError extends Error {
|
|
34
|
+
constructor(ex) {
|
|
35
|
+
// Expired Error
|
|
36
|
+
if (ex.response?.data?.message) {
|
|
37
|
+
super(ex.response.data.message);
|
|
38
|
+
this.code = ex.response.data.errorcode || ex.code;
|
|
39
|
+
}
|
|
40
|
+
// Unauthenticated
|
|
41
|
+
else if (ex.response?.data?.error_description) {
|
|
42
|
+
super(ex.response.data.error_description);
|
|
43
|
+
this.code = ex.response.data.error || ex.code;
|
|
44
|
+
} else {
|
|
45
|
+
super(ex.message);
|
|
46
|
+
this.code = ex.code;
|
|
47
|
+
}
|
|
48
|
+
this.response = ex.response;
|
|
49
|
+
this.name = this.constructor.name;
|
|
50
|
+
if (Error.captureStackTrace) {
|
|
51
|
+
Error.captureStackTrace(this, RestError);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* CustomError type for handling SOAP based errors
|
|
58
|
+
*
|
|
59
|
+
* @class SOAPError
|
|
60
|
+
* @augments {Error}
|
|
61
|
+
*/
|
|
62
|
+
module.exports.SOAPError = class SOAPError extends Error {
|
|
63
|
+
constructor(ex, response, soapBody) {
|
|
64
|
+
// Content Error
|
|
65
|
+
if (soapBody && ['Error', 'Has Errors'].includes(soapBody.OverallStatus)) {
|
|
66
|
+
super('One or more errors in the Results');
|
|
67
|
+
this.code = soapBody.OverallStatus;
|
|
68
|
+
}
|
|
69
|
+
// Payload Error
|
|
70
|
+
else if (soapBody && soapBody['soap:Fault']) {
|
|
71
|
+
const fault = soapBody['soap:Fault'];
|
|
72
|
+
super(fault.faultstring);
|
|
73
|
+
this.code = fault.faultcode;
|
|
74
|
+
}
|
|
75
|
+
// Request Error
|
|
76
|
+
else if (response?.status > 299) {
|
|
77
|
+
super('Error with SOAP Request');
|
|
78
|
+
this.code = response?.status;
|
|
79
|
+
}
|
|
80
|
+
// Fallback Error
|
|
81
|
+
else {
|
|
82
|
+
super(ex.message);
|
|
83
|
+
this.code = ex.code;
|
|
84
|
+
}
|
|
85
|
+
this.response = response;
|
|
86
|
+
this.json = soapBody;
|
|
87
|
+
this.name = this.constructor.name;
|
|
88
|
+
if (Error.captureStackTrace) {
|
|
89
|
+
Error.captureStackTrace(this, SOAPError);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
package/package.json
CHANGED
|
@@ -1,46 +1,50 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
2
|
+
"name": "sfmc-sdk",
|
|
3
|
+
"version": "0.6.1",
|
|
4
|
+
"description": "Libarary to simplify SFMC requests with updated dependencies and less overhead",
|
|
5
|
+
"main": "./lib/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "nyc --reporter=text mocha",
|
|
8
|
+
"lint": "eslint ./lib && eslint ./test",
|
|
9
|
+
"lint:fix": "eslint ./lib --fix && eslint ./test --fix"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/DougMidgley/SFMC-SDK.git"
|
|
14
|
+
},
|
|
15
|
+
"author": "Doug Midgley <douglasmidgley@gmail.com>",
|
|
16
|
+
"license": "BSD-3-Clause",
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"axios": "^0.27.2",
|
|
19
|
+
"fast-xml-parser": "4.0.8",
|
|
20
|
+
"p-limit": "3.1.0"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"fuel",
|
|
24
|
+
"exacttarget",
|
|
25
|
+
"salesforce",
|
|
26
|
+
"marketing",
|
|
27
|
+
"cloud",
|
|
28
|
+
"soap",
|
|
29
|
+
"rest",
|
|
30
|
+
"auth",
|
|
31
|
+
"sdk"
|
|
32
|
+
],
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"assert": "2.0.0",
|
|
35
|
+
"axios-mock-adapter": "1.21.1",
|
|
36
|
+
"chai": "4.3.6",
|
|
37
|
+
"eslint-config-prettier": "8.5.0",
|
|
38
|
+
"eslint-plugin-jsdoc": "39.3.3",
|
|
39
|
+
"eslint-plugin-mocha": "10.0.5",
|
|
40
|
+
"eslint-plugin-prettier": "4.0.0",
|
|
41
|
+
"mocha": "10.0.0",
|
|
42
|
+
"nyc": "15.1.0",
|
|
43
|
+
"prettier-eslint": "15.0.1",
|
|
44
|
+
"sinon": "14.0.0"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"npm": ">=6.14.4",
|
|
48
|
+
"node": ">=14.0.0"
|
|
49
|
+
}
|
|
46
50
|
}
|
package/test/auth.test.js
CHANGED
|
@@ -4,11 +4,11 @@ const { defaultSdk, mock } = require('./utils.js');
|
|
|
4
4
|
const resources = require('./resources/auth.json');
|
|
5
5
|
const { isConnectionError } = require('../lib/util');
|
|
6
6
|
|
|
7
|
-
describe('auth', ()
|
|
8
|
-
afterEach(()
|
|
7
|
+
describe('auth', function () {
|
|
8
|
+
afterEach(function () {
|
|
9
9
|
mock.reset();
|
|
10
10
|
});
|
|
11
|
-
it('should return an auth payload with token', async ()
|
|
11
|
+
it('should return an auth payload with token', async function () {
|
|
12
12
|
//given
|
|
13
13
|
const { success } = resources;
|
|
14
14
|
|
|
@@ -20,7 +20,7 @@ describe('auth', () => {
|
|
|
20
20
|
assert.lengthOf(mock.history.post, 1);
|
|
21
21
|
return;
|
|
22
22
|
});
|
|
23
|
-
it('should return an auth payload with previous token and one request', async ()
|
|
23
|
+
it('should return an auth payload with previous token and one request', async function () {
|
|
24
24
|
//given
|
|
25
25
|
const { success } = resources;
|
|
26
26
|
mock.onPost(success.url).reply(success.status, success.response);
|
|
@@ -33,7 +33,7 @@ describe('auth', () => {
|
|
|
33
33
|
assert.lengthOf(mock.history.post, 1);
|
|
34
34
|
return;
|
|
35
35
|
});
|
|
36
|
-
it('should return an unauthorized error', async ()
|
|
36
|
+
it('should return an unauthorized error', async function () {
|
|
37
37
|
//given
|
|
38
38
|
const { unauthorized } = resources;
|
|
39
39
|
mock.onPost(unauthorized.url).reply(unauthorized.status, unauthorized.response);
|
|
@@ -49,10 +49,10 @@ describe('auth', () => {
|
|
|
49
49
|
|
|
50
50
|
return;
|
|
51
51
|
});
|
|
52
|
-
it('should return an incorrect account_id error', async ()
|
|
52
|
+
it('should return an incorrect account_id error', async function () {
|
|
53
53
|
try {
|
|
54
54
|
//given
|
|
55
|
-
|
|
55
|
+
new SDK({
|
|
56
56
|
client_id: 'XXXXX',
|
|
57
57
|
client_secret: 'YYYYYY',
|
|
58
58
|
auth_url: 'https://mct0l7nxfq2r988t1kxfy8sc47ma.auth.marketingcloudapis.com/',
|
|
@@ -68,10 +68,10 @@ describe('auth', () => {
|
|
|
68
68
|
}
|
|
69
69
|
return;
|
|
70
70
|
});
|
|
71
|
-
it('should return an incorrect auth_url error', async ()
|
|
71
|
+
it('should return an incorrect auth_url error', async function () {
|
|
72
72
|
try {
|
|
73
73
|
//given
|
|
74
|
-
|
|
74
|
+
new SDK({
|
|
75
75
|
client_id: 'XXXXX',
|
|
76
76
|
client_secret: 'YYYYYY',
|
|
77
77
|
auth_url: 'https://x.auth.marketingcloudapis.com/',
|
|
@@ -87,10 +87,10 @@ describe('auth', () => {
|
|
|
87
87
|
}
|
|
88
88
|
return;
|
|
89
89
|
});
|
|
90
|
-
it('should return an incorrect client_id error', async ()
|
|
90
|
+
it('should return an incorrect client_id error', async function () {
|
|
91
91
|
try {
|
|
92
92
|
//given
|
|
93
|
-
|
|
93
|
+
new SDK({
|
|
94
94
|
client_id: '',
|
|
95
95
|
client_secret: 'YYYYYY',
|
|
96
96
|
auth_url: 'https://mct0l7nxfq2r988t1kxfy8sc47ma.auth.marketingcloudapis.com/',
|
|
@@ -103,10 +103,10 @@ describe('auth', () => {
|
|
|
103
103
|
}
|
|
104
104
|
return;
|
|
105
105
|
});
|
|
106
|
-
it('should return an incorrect client_key error', async ()
|
|
106
|
+
it('should return an incorrect client_key error', async function () {
|
|
107
107
|
try {
|
|
108
108
|
//given
|
|
109
|
-
|
|
109
|
+
new SDK({
|
|
110
110
|
client_id: 'XXXXX',
|
|
111
111
|
client_secret: '',
|
|
112
112
|
auth_url: 'https://mct0l7nxfq2r988t1kxfy8sc47ma.auth.marketingcloudapis.com/',
|
|
@@ -119,10 +119,10 @@ describe('auth', () => {
|
|
|
119
119
|
}
|
|
120
120
|
return;
|
|
121
121
|
});
|
|
122
|
-
it('should return an invalid scope error', async ()
|
|
122
|
+
it('should return an invalid scope error', async function () {
|
|
123
123
|
try {
|
|
124
124
|
//given
|
|
125
|
-
|
|
125
|
+
new SDK({
|
|
126
126
|
client_id: 'XXXXX',
|
|
127
127
|
client_secret: 'YYYYYY',
|
|
128
128
|
auth_url: 'https://mct0l7nxfq2r988t1kxfy8sc47ma.auth.marketingcloudapis.com/',
|
|
@@ -136,10 +136,10 @@ describe('auth', () => {
|
|
|
136
136
|
}
|
|
137
137
|
return;
|
|
138
138
|
});
|
|
139
|
-
it('should return an invalid scope type error', async ()
|
|
139
|
+
it('should return an invalid scope type error', async function () {
|
|
140
140
|
try {
|
|
141
141
|
//given
|
|
142
|
-
|
|
142
|
+
new SDK({
|
|
143
143
|
client_id: 'XXXXX',
|
|
144
144
|
client_secret: 'YYYYYY',
|
|
145
145
|
auth_url: 'https://mct0l7nxfq2r988t1kxfy8sc47ma.auth.marketingcloudapis.com/',
|
|
@@ -154,7 +154,7 @@ describe('auth', () => {
|
|
|
154
154
|
return;
|
|
155
155
|
});
|
|
156
156
|
|
|
157
|
-
it('RETRY: should return an success, after a connection issues', async ()
|
|
157
|
+
it('RETRY: should return an success, after a connection issues', async function () {
|
|
158
158
|
//given
|
|
159
159
|
const { success } = resources;
|
|
160
160
|
|
|
@@ -169,7 +169,7 @@ describe('auth', () => {
|
|
|
169
169
|
assert.lengthOf(mock.history.post, 2);
|
|
170
170
|
return;
|
|
171
171
|
});
|
|
172
|
-
it('FAILED RETRY: should return an error, after multiple connection issues', async ()
|
|
172
|
+
it('FAILED RETRY: should return an error, after multiple connection issues', async function () {
|
|
173
173
|
//given
|
|
174
174
|
const { success } = resources;
|
|
175
175
|
|
package/test/resources/auth.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"success": {
|
|
3
3
|
"url": "https://mct0l7nxfq2r988t1kxfy8sc47ma.auth.marketingcloudapis.com/v2/token",
|
|
4
4
|
"response": {
|
|
5
|
-
"access_token": "
|
|
5
|
+
"access_token": "TESTTOKEN",
|
|
6
6
|
"token_type": "Bearer",
|
|
7
7
|
"expires_in": 1079,
|
|
8
8
|
"scope": "offline documents_and_images_read documents_and_images_write saved_content_read saved_content_write automations_execute automations_read automations_write journeys_execute journeys_read journeys_write email_read email_send email_write push_read push_send push_write sms_read sms_send sms_write social_post social_publish social_read social_write web_publish web_read web_write audiences_read audiences_write list_and_subscribers_read list_and_subscribers_write data_extensions_read data_extensions_write file_locations_read file_locations_write tracking_events_read calendar_read calendar_write campaign_read campaign_write accounts_read accounts_write users_read users_write webhooks_read webhooks_write workflows_write approvals_write tags_write approvals_read tags_read workflows_read ott_chat_messaging_read ott_chat_messaging_send ott_channels_read ott_channels_write marketing_cloud_connect_read marketing_cloud_connect_write marketing_cloud_connect_send event_notification_callback_create event_notification_callback_read event_notification_callback_update event_notification_callback_delete event_notification_subscription_create event_notification_subscription_read event_notification_subscription_update event_notification_subscription_delete tracking_events_write key_manage_view key_manage_rotate key_manage_revoke dfu_configure journeys_aspr journeys_delete package_manager_package package_manager_deploy deep_linking_asset_read deep_linking_asset_write deep_linking_asset_delete deep_linking_settings_read deep_linking_settings_write",
|
package/test/resources/rest.json
CHANGED
|
@@ -356,6 +356,11 @@
|
|
|
356
356
|
"serviceMessageID": "36f9ab27-aaee-46d5-b41f-4b7a61fc645a"
|
|
357
357
|
}
|
|
358
358
|
},
|
|
359
|
+
"dataExtensionUpsert": {
|
|
360
|
+
"status": 200,
|
|
361
|
+
"url": "https://mct0l7nxfq2r988t1kxfy8sc47ma.rest.marketingcloudapis.com/hub/v1/dataevents/key:key/rowset",
|
|
362
|
+
"response": [{ "keys": { "primaryKey": 1 }, "values": { "name": "test" } }]
|
|
363
|
+
},
|
|
359
364
|
"campaignDelete": {
|
|
360
365
|
"status": 200,
|
|
361
366
|
"url": "https://mct0l7nxfq2r988t1kxfy8sc47ma.rest.marketingcloudapis.com/hub/v1/campaigns/12656",
|
package/test/rest.test.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
const assert = require('chai').assert;
|
|
2
2
|
const { defaultSdk, mock } = require('./utils.js');
|
|
3
|
+
const SDK = require('../lib');
|
|
3
4
|
const resources = require('./resources/rest.json');
|
|
4
5
|
const authResources = require('./resources/auth.json');
|
|
5
6
|
const { isConnectionError } = require('../lib/util');
|
|
6
7
|
|
|
7
|
-
describe('rest', ()
|
|
8
|
-
beforeEach(()
|
|
8
|
+
describe('rest', function () {
|
|
9
|
+
beforeEach(function () {
|
|
9
10
|
mock.onPost(authResources.success.url).reply(
|
|
10
11
|
authResources.success.status,
|
|
11
12
|
authResources.success.response
|
|
12
13
|
);
|
|
13
14
|
});
|
|
14
|
-
afterEach(()
|
|
15
|
+
afterEach(function () {
|
|
15
16
|
mock.reset();
|
|
16
17
|
});
|
|
17
|
-
|
|
18
|
-
it('GET Bulk: should return 6 journey items', async () => {
|
|
18
|
+
it('GET Bulk: should return 6 journey items', async function () {
|
|
19
19
|
//given
|
|
20
20
|
const { journeysPage1, journeysPage2 } = resources;
|
|
21
21
|
mock.onGet(journeysPage1.url).reply(journeysPage1.status, journeysPage1.response);
|
|
@@ -28,7 +28,7 @@ describe('rest', () => {
|
|
|
28
28
|
assert.lengthOf(mock.history.get, 2);
|
|
29
29
|
return;
|
|
30
30
|
});
|
|
31
|
-
it('GET: should return 5 journey items', async ()
|
|
31
|
+
it('GET: should return 5 journey items', async function () {
|
|
32
32
|
//given
|
|
33
33
|
const { journeysPage1 } = resources;
|
|
34
34
|
mock.onGet(journeysPage1.url).reply(journeysPage1.status, journeysPage1.response);
|
|
@@ -42,7 +42,7 @@ describe('rest', () => {
|
|
|
42
42
|
assert.lengthOf(mock.history.get, 1);
|
|
43
43
|
return;
|
|
44
44
|
});
|
|
45
|
-
it('GETCOLLECTION: should return 2 identical payloads', async ()
|
|
45
|
+
it('GETCOLLECTION: should return 2 identical payloads', async function () {
|
|
46
46
|
//given
|
|
47
47
|
const { journeysPage1 } = resources;
|
|
48
48
|
mock.onGet(journeysPage1.url).reply(journeysPage1.status, journeysPage1.response);
|
|
@@ -58,7 +58,7 @@ describe('rest', () => {
|
|
|
58
58
|
assert.lengthOf(mock.history.get, 2);
|
|
59
59
|
return;
|
|
60
60
|
});
|
|
61
|
-
it('POST: should create Event Definition', async ()
|
|
61
|
+
it('POST: should create Event Definition', async function () {
|
|
62
62
|
//given
|
|
63
63
|
const { eventcreate } = resources;
|
|
64
64
|
mock.onPost(eventcreate.url).reply(eventcreate.status, eventcreate.response);
|
|
@@ -91,7 +91,23 @@ describe('rest', () => {
|
|
|
91
91
|
assert.lengthOf(mock.history.post, 2);
|
|
92
92
|
return;
|
|
93
93
|
});
|
|
94
|
-
it('
|
|
94
|
+
it('POST: should add an entry to a Data Extension', async function () {
|
|
95
|
+
//given
|
|
96
|
+
const { dataExtensionUpsert } = resources;
|
|
97
|
+
mock.onPost(dataExtensionUpsert.url).reply(
|
|
98
|
+
dataExtensionUpsert.status,
|
|
99
|
+
dataExtensionUpsert.response
|
|
100
|
+
);
|
|
101
|
+
// when
|
|
102
|
+
const payload = await defaultSdk().rest.post('hub/v1/dataevents/key:key/rowset', [
|
|
103
|
+
{ keys: { primaryKey: 1 }, values: { name: 'test' } },
|
|
104
|
+
]);
|
|
105
|
+
// then
|
|
106
|
+
assert.deepEqual(payload, dataExtensionUpsert.response);
|
|
107
|
+
assert.lengthOf(mock.history.post, 2);
|
|
108
|
+
return;
|
|
109
|
+
});
|
|
110
|
+
it('PUT: should update Event Definition', async function () {
|
|
95
111
|
//given
|
|
96
112
|
const { eventupdate } = resources;
|
|
97
113
|
mock.onPut(eventupdate.url).reply(eventupdate.status, eventupdate.response);
|
|
@@ -114,7 +130,7 @@ describe('rest', () => {
|
|
|
114
130
|
assert.lengthOf(mock.history.put, 1);
|
|
115
131
|
return;
|
|
116
132
|
});
|
|
117
|
-
it('PATCH: should update Contact', async ()
|
|
133
|
+
it('PATCH: should update Contact', async function () {
|
|
118
134
|
//given
|
|
119
135
|
const { contactPatch } = resources;
|
|
120
136
|
mock.onPatch(contactPatch.url).reply(contactPatch.status, contactPatch.response);
|
|
@@ -164,7 +180,7 @@ describe('rest', () => {
|
|
|
164
180
|
assert.lengthOf(mock.history.patch, 1);
|
|
165
181
|
return;
|
|
166
182
|
});
|
|
167
|
-
it('DELETE: should delete Campaign', async ()
|
|
183
|
+
it('DELETE: should delete Campaign', async function () {
|
|
168
184
|
//given
|
|
169
185
|
const { campaignDelete } = resources;
|
|
170
186
|
mock.onDelete(campaignDelete.url).reply(campaignDelete.status, campaignDelete.response);
|
|
@@ -176,7 +192,7 @@ describe('rest', () => {
|
|
|
176
192
|
assert.lengthOf(mock.history.delete, 1);
|
|
177
193
|
return;
|
|
178
194
|
});
|
|
179
|
-
it('should retry auth one time on first failure then work', async ()
|
|
195
|
+
it('should retry auth one time on first failure then work', async function () {
|
|
180
196
|
//given
|
|
181
197
|
mock.reset(); // needed to avoid before hook being used
|
|
182
198
|
const { expired, success } = authResources;
|
|
@@ -195,7 +211,7 @@ describe('rest', () => {
|
|
|
195
211
|
assert.lengthOf(mock.history.delete, 1);
|
|
196
212
|
return;
|
|
197
213
|
});
|
|
198
|
-
it('should retry auth one time on first failure then fail', async ()
|
|
214
|
+
it('should retry auth one time on first failure then fail', async function () {
|
|
199
215
|
//given
|
|
200
216
|
mock.reset(); // needed to avoid before hook being used
|
|
201
217
|
const { unauthorized } = authResources;
|
|
@@ -214,7 +230,7 @@ describe('rest', () => {
|
|
|
214
230
|
}
|
|
215
231
|
return;
|
|
216
232
|
});
|
|
217
|
-
it('should fail to delete campaign', async ()
|
|
233
|
+
it('should fail to delete campaign', async function () {
|
|
218
234
|
//given
|
|
219
235
|
const { campaignFailed } = resources;
|
|
220
236
|
mock.onDelete(campaignFailed.url).reply(campaignFailed.status, campaignFailed.response);
|
|
@@ -233,7 +249,7 @@ describe('rest', () => {
|
|
|
233
249
|
assert.lengthOf(mock.history.delete, 1);
|
|
234
250
|
return;
|
|
235
251
|
});
|
|
236
|
-
it('RETRY: should return 5 journey items, after a connection error', async ()
|
|
252
|
+
it('RETRY: should return 5 journey items, after a connection error', async function () {
|
|
237
253
|
//given
|
|
238
254
|
const { journeysPage1 } = resources;
|
|
239
255
|
mock.onGet(journeysPage1.url)
|
|
@@ -250,7 +266,7 @@ describe('rest', () => {
|
|
|
250
266
|
assert.lengthOf(mock.history.get, 2);
|
|
251
267
|
return;
|
|
252
268
|
});
|
|
253
|
-
it('FAILED RETRY: should return error, after 2 connection errors', async ()
|
|
269
|
+
it('FAILED RETRY: should return error, after 2 connection timeout errors', async function () {
|
|
254
270
|
//given
|
|
255
271
|
const { journeysPage1 } = resources;
|
|
256
272
|
mock.onGet(journeysPage1.url).timeout();
|
|
@@ -267,4 +283,75 @@ describe('rest', () => {
|
|
|
267
283
|
|
|
268
284
|
return;
|
|
269
285
|
});
|
|
286
|
+
it('FAILED RETRY: should return error, after 2 ECONNRESET errors', async function () {
|
|
287
|
+
//given
|
|
288
|
+
const { journeysPage1 } = resources;
|
|
289
|
+
|
|
290
|
+
mock.onGet(journeysPage1.url).reply(() => {
|
|
291
|
+
const connectionError = new Error();
|
|
292
|
+
connectionError.code = 'ECONNRESET';
|
|
293
|
+
throw connectionError;
|
|
294
|
+
});
|
|
295
|
+
// when
|
|
296
|
+
try {
|
|
297
|
+
await defaultSdk().rest.get('interaction/v1/interactions?$pageSize=5&$page=1');
|
|
298
|
+
assert.fail();
|
|
299
|
+
} catch (ex) {
|
|
300
|
+
// then
|
|
301
|
+
assert.isTrue(isConnectionError(ex.code));
|
|
302
|
+
}
|
|
303
|
+
assert.lengthOf(mock.history.post, 1);
|
|
304
|
+
assert.lengthOf(mock.history.get, 2);
|
|
305
|
+
|
|
306
|
+
return;
|
|
307
|
+
});
|
|
308
|
+
it('LogRequest & Response: should run middleware for logging ', async function () {
|
|
309
|
+
//given
|
|
310
|
+
const { journeysPage1 } = resources;
|
|
311
|
+
mock.onGet(journeysPage1.url).reply(journeysPage1.status, journeysPage1.response);
|
|
312
|
+
// when
|
|
313
|
+
let expectedRequest;
|
|
314
|
+
let expectedResponse;
|
|
315
|
+
const sdk = new SDK(
|
|
316
|
+
{
|
|
317
|
+
client_id: 'XXXXX',
|
|
318
|
+
client_secret: 'YYYYYY',
|
|
319
|
+
auth_url: 'https://mct0l7nxfq2r988t1kxfy8sc47ma.auth.marketingcloudapis.com/',
|
|
320
|
+
account_id: 1111111,
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
eventHandlers: {
|
|
324
|
+
logRequest: (reqObj) => {
|
|
325
|
+
expectedRequest = reqObj;
|
|
326
|
+
},
|
|
327
|
+
|
|
328
|
+
logResponse: (resObj) => {
|
|
329
|
+
expectedResponse = resObj;
|
|
330
|
+
},
|
|
331
|
+
onConnectionError: () => {
|
|
332
|
+
return;
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
retryOnConnectionError: true,
|
|
336
|
+
requestAttempts: 2,
|
|
337
|
+
}
|
|
338
|
+
);
|
|
339
|
+
// when
|
|
340
|
+
await sdk.rest.get('interaction/v1/interactions?$pageSize=5&$page=1');
|
|
341
|
+
// then
|
|
342
|
+
assert.deepEqual(
|
|
343
|
+
{
|
|
344
|
+
method: 'GET',
|
|
345
|
+
url: 'interaction/v1/interactions?$pageSize=5&$page=1',
|
|
346
|
+
baseURL: 'https://mct0l7nxfq2r988t1kxfy8sc47ma.rest.marketingcloudapis.com/',
|
|
347
|
+
headers: {
|
|
348
|
+
Authorization: 'Bearer TESTTOKEN',
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
expectedRequest
|
|
352
|
+
);
|
|
353
|
+
assert.equal(200, expectedResponse.status);
|
|
354
|
+
assert.equal(5, expectedResponse.data.items.length);
|
|
355
|
+
return;
|
|
356
|
+
});
|
|
270
357
|
});
|
package/test/soap.test.js
CHANGED
|
@@ -19,18 +19,18 @@ const addHandler = (metadata) => {
|
|
|
19
19
|
).reply(metadata.status, metadata.response);
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
describe('soap', ()
|
|
23
|
-
beforeEach(()
|
|
22
|
+
describe('soap', function () {
|
|
23
|
+
beforeEach(function () {
|
|
24
24
|
mock.onPost(authResources.success.url).reply(
|
|
25
25
|
authResources.success.status,
|
|
26
26
|
authResources.success.response
|
|
27
27
|
);
|
|
28
28
|
});
|
|
29
|
-
afterEach(()
|
|
29
|
+
afterEach(function () {
|
|
30
30
|
mock.reset();
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
-
it('retrieve: should return 1 data extension', async ()
|
|
33
|
+
it('retrieve: should return 1 data extension', async function () {
|
|
34
34
|
//given
|
|
35
35
|
addHandler(resources.retrieveDataExtension);
|
|
36
36
|
// when
|
|
@@ -56,7 +56,7 @@ describe('soap', () => {
|
|
|
56
56
|
assert.lengthOf(mock.history.post, 2);
|
|
57
57
|
return;
|
|
58
58
|
});
|
|
59
|
-
it('retrieveBulk: should return 2 data extensions', async ()
|
|
59
|
+
it('retrieveBulk: should return 2 data extensions', async function () {
|
|
60
60
|
//given
|
|
61
61
|
mock.onPost(
|
|
62
62
|
'/Service.asmx',
|
|
@@ -102,7 +102,7 @@ describe('soap', () => {
|
|
|
102
102
|
assert.lengthOf(mock.history.post, 3);
|
|
103
103
|
return;
|
|
104
104
|
});
|
|
105
|
-
it('failed: should fail to create 1 subscriber', async ()
|
|
105
|
+
it('failed: should fail to create 1 subscriber', async function () {
|
|
106
106
|
//given
|
|
107
107
|
addHandler(resources.subscriberFailed);
|
|
108
108
|
// when
|
|
@@ -122,13 +122,13 @@ describe('soap', () => {
|
|
|
122
122
|
// then
|
|
123
123
|
assert.fail();
|
|
124
124
|
} catch (ex) {
|
|
125
|
-
assert.deepEqual(ex.
|
|
125
|
+
assert.deepEqual(ex.json, resources.subscriberFailed.parsed);
|
|
126
126
|
assert.lengthOf(mock.history.post, 2);
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
return;
|
|
130
130
|
});
|
|
131
|
-
it('create: should create 1 subscriber', async ()
|
|
131
|
+
it('create: should create 1 subscriber', async function () {
|
|
132
132
|
//given
|
|
133
133
|
addHandler(resources.subscriberCreated);
|
|
134
134
|
// when
|
|
@@ -151,7 +151,7 @@ describe('soap', () => {
|
|
|
151
151
|
|
|
152
152
|
return;
|
|
153
153
|
});
|
|
154
|
-
it('update: should update 1 subscriber', async ()
|
|
154
|
+
it('update: should update 1 subscriber', async function () {
|
|
155
155
|
//given
|
|
156
156
|
addHandler(resources.subscriberUpdated);
|
|
157
157
|
// when
|
|
@@ -174,7 +174,7 @@ describe('soap', () => {
|
|
|
174
174
|
|
|
175
175
|
return;
|
|
176
176
|
});
|
|
177
|
-
it('expired: should return an error of expired token', async ()
|
|
177
|
+
it('expired: should return an error of expired token', async function () {
|
|
178
178
|
//given
|
|
179
179
|
addHandler(resources.expiredToken);
|
|
180
180
|
// when
|
|
@@ -185,13 +185,14 @@ describe('soap', () => {
|
|
|
185
185
|
});
|
|
186
186
|
} catch (ex) {
|
|
187
187
|
// then
|
|
188
|
-
assert.equal(ex.
|
|
188
|
+
assert.equal(ex.message, 'Token Expired');
|
|
189
189
|
assert.lengthOf(mock.history.post, 4);
|
|
190
|
+
|
|
190
191
|
return;
|
|
191
192
|
}
|
|
192
193
|
assert.fail();
|
|
193
194
|
});
|
|
194
|
-
it('bad Request: should return an error of bad request', async ()
|
|
195
|
+
it('bad Request: should return an error of bad request', async function () {
|
|
195
196
|
//given
|
|
196
197
|
addHandler(resources.badRequest);
|
|
197
198
|
// when
|
|
@@ -207,7 +208,7 @@ describe('soap', () => {
|
|
|
207
208
|
}
|
|
208
209
|
assert.fail();
|
|
209
210
|
});
|
|
210
|
-
it('Delete: should delete a subscriber', async ()
|
|
211
|
+
it('Delete: should delete a subscriber', async function () {
|
|
211
212
|
//given
|
|
212
213
|
addHandler(resources.subscriberDeleted);
|
|
213
214
|
// when
|
|
@@ -219,7 +220,7 @@ describe('soap', () => {
|
|
|
219
220
|
assert.lengthOf(mock.history.post, 2);
|
|
220
221
|
return;
|
|
221
222
|
});
|
|
222
|
-
it('Describe: should describe the subscriber type', async ()
|
|
223
|
+
it('Describe: should describe the subscriber type', async function () {
|
|
223
224
|
//given
|
|
224
225
|
addHandler(resources.subscriberDescribed);
|
|
225
226
|
// when
|
|
@@ -229,7 +230,7 @@ describe('soap', () => {
|
|
|
229
230
|
assert.lengthOf(mock.history.post, 2);
|
|
230
231
|
return;
|
|
231
232
|
});
|
|
232
|
-
it('Execute: should unsubscribe subscriber', async ()
|
|
233
|
+
it('Execute: should unsubscribe subscriber', async function () {
|
|
233
234
|
//given
|
|
234
235
|
addHandler(resources.subscribeUnsub);
|
|
235
236
|
// when
|
|
@@ -242,7 +243,7 @@ describe('soap', () => {
|
|
|
242
243
|
assert.lengthOf(mock.history.post, 2);
|
|
243
244
|
return;
|
|
244
245
|
});
|
|
245
|
-
it('Perform: should unsubscribe subscriber', async ()
|
|
246
|
+
it('Perform: should unsubscribe subscriber', async function () {
|
|
246
247
|
//given
|
|
247
248
|
addHandler(resources.queryPerform);
|
|
248
249
|
// when
|
|
@@ -254,7 +255,7 @@ describe('soap', () => {
|
|
|
254
255
|
assert.lengthOf(mock.history.post, 2);
|
|
255
256
|
return;
|
|
256
257
|
});
|
|
257
|
-
it('Schedule: should schedule an Automation', async ()
|
|
258
|
+
it('Schedule: should schedule an Automation', async function () {
|
|
258
259
|
//given
|
|
259
260
|
addHandler(resources.automationSchedule);
|
|
260
261
|
// when
|
|
@@ -279,7 +280,7 @@ describe('soap', () => {
|
|
|
279
280
|
assert.lengthOf(mock.history.post, 2);
|
|
280
281
|
return;
|
|
281
282
|
});
|
|
282
|
-
it('RETRY: should return 1 data extension, after a connection error', async ()
|
|
283
|
+
it('RETRY: should return 1 data extension, after a connection error', async function () {
|
|
283
284
|
//given
|
|
284
285
|
|
|
285
286
|
mock.onPost('/Service.asmx')
|
|
@@ -322,7 +323,7 @@ describe('soap', () => {
|
|
|
322
323
|
assert.lengthOf(mock.history.post, 3);
|
|
323
324
|
return;
|
|
324
325
|
});
|
|
325
|
-
it('FAILED RETRY: should return error, after multiple connection error', async ()
|
|
326
|
+
it('FAILED RETRY: should return error, after multiple connection error', async function () {
|
|
326
327
|
//given
|
|
327
328
|
|
|
328
329
|
mock.onPost('/Service.asmx').timeout();
|