Skip to content

Commit 978500d

Browse files
add additional tests; refactor timeout events
1 parent dad0c00 commit 978500d

File tree

8 files changed

+347
-73
lines changed

8 files changed

+347
-73
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ includeAnnotations | String | Defaults Prefer header with value "odata.include-a
193193
maxPageSize | Number | Defaults the odata.maxpagesize preference. Use to set the number of entities returned in the response.
194194
onTokenRefresh | Function | A callback function that triggered when DynamicsWebApi requests a new OAuth token. (At this moment it is done before each call to Dynamics 365, as [recommended by Microsoft](https://msdn.microsoft.com/en-ca/library/gg327838.aspx#Anchor_2)).
195195
returnRepresentation | Boolean | Defaults Prefer header with value "return=representation". Use this property to return just created or updated entity in a single request.
196+
timeout | Number | Sets a number of milliseconds before a request times out.
196197
useEntityNames | Boolean | `v.1.4.0+` Indicates whether to use entity logical names instead of collection logical names during requests.
197198
webApiUrl | String | A complete URL string to Web API. Example of the URL: "https://myorg.api.crm.dynamics.com/api/data/v8.2/". If it is specified then webApiVersion property will not be used even if it is not empty.
198199
webApiVersion | String | Version of the Web API. Default version is "8.0".

lib/requests/http.js

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,6 @@ var parseResponse = require('./helpers/parseResponse');
66
/**
77
* Sends a request to given URL with given parameters
88
*
9-
* @param {string} method - Method of the request.
10-
* @param {string} uri - Request URI.
11-
* @param {any} responseParams - parameters for parsing the response
12-
* @param {Function} successCallback - A callback called on success of the request.
13-
* @param {Function} errorCallback - A callback called when a request failed.
14-
* @param {string} [data] - Data to send in the request.
15-
* @param {Object} [additionalHeaders] - Additional headers. IMPORTANT! This object does not contain default headers needed for every request.
16-
* @param {number} [timeout] - socket timeout for the http request.
179
*/
1810
var httpRequest = function (options) {
1911
var method = options.method;
@@ -123,14 +115,20 @@ var httpRequest = function (options) {
123115
});
124116
});
125117

126-
request.on('timeout', function (error) {
127-
request.abort();
128-
});
118+
if (options.timeout) {
119+
request.setTimeout(options.timeout, function () {
120+
request.abort();
121+
});
122+
}
123+
124+
//request.on('timeout', function () {
125+
// request.abort();
126+
//});
129127

130128
request.on('error', function (error) {
131-
if (request.aborted) {
132-
error = new Error('Request timed out: ' + error);
133-
}
129+
//if (request.aborted) {
130+
// error = new Error('Request timed out: ' + error);
131+
//}
134132
responseParams.length = 0;
135133
errorCallback(error);
136134
});

lib/requests/sendRequest.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ function sendRequest(method, path, config, data, additionalHeaders, responsePara
212212
successCallback: successCallback,
213213
errorCallback: errorCallback,
214214
isAsync: isAsync,
215-
timeout: config.timeout,
215+
timeout: config.timeout
216216
});
217217
};
218218

lib/requests/xhr.js

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,6 @@ var parseResponseHeaders = require('./helpers/parseResponseHeaders');
44
/**
55
* Sends a request to given URL with given parameters
66
*
7-
* @param {string} method - Method of the request.
8-
* @param {string} uri - Request URI.
9-
* @param {string} [data] - Data to send in the request.
10-
* @param {Object} [additionalHeaders] - Object with headers. IMPORTANT! This object does not contain default headers needed for every request.
11-
* @param {any} responseParams - parameters for parsing the response
12-
* @param {Function} successCallback - A callback called on success of the request.
13-
* @param {Function} errorCallback - A callback called when a request failed.
14-
* @param {boolean} [isAsync] - Indicates if the request needs to be synchronous
157
*/
168
var xhrRequest = function (options) {
179
var method = options.method;
@@ -74,13 +66,21 @@ var xhrRequest = function (options) {
7466
};
7567

7668
request.onerror = function () {
77-
errorCallback({ message: "Network Error" });
69+
errorCallback({
70+
status: request.status,
71+
statusText: request.statusText,
72+
message: request.responseText || "Network Error"
73+
});
7874
responseParams.length = 0;
7975
request = null;
8076
};
8177

82-
request.ontimeout = function (error) {
83-
errorCallback({ message: "Request Timed Out" });
78+
request.ontimeout = function () {
79+
errorCallback({
80+
status: request.status,
81+
statusText: request.statusText,
82+
message: request.responseText || "Request Timed Out"
83+
});
8484
responseParams.length = 0;
8585
request = null;
8686
};

tests/common-tests.js

Lines changed: 222 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -13,65 +13,153 @@ var dateReviver = require('../lib/requests/helpers/dateReviver');
1313
var Request = require('../lib/requests/sendRequest');
1414
var parseResponse = require('../lib/requests/helpers/parseResponse');
1515

16-
describe("Utility.buildFunctionParameters - ", function () {
17-
it("no parameters", function () {
18-
var result = Utility.buildFunctionParameters();
19-
expect(result).to.equal("()");
20-
});
21-
it("1 parameter", function () {
22-
var result = Utility.buildFunctionParameters({ param1: "value1" });
23-
expect(result).to.equal("(param1=@p1)?@p1='value1'");
24-
});
25-
it("2 parameters", function () {
26-
var result = Utility.buildFunctionParameters({ param1: "value1", param2: 2 });
27-
expect(result).to.equal("(param1=@p1,param2=@p2)?@p1='value1'&@p2=2");
16+
describe("Utility.", function () {
17+
describe("buildFunctionParameters - ", function () {
18+
it("no parameters", function () {
19+
var result = Utility.buildFunctionParameters();
20+
expect(result).to.equal("()");
21+
});
22+
it("1 parameter", function () {
23+
var result = Utility.buildFunctionParameters({ param1: "value1" });
24+
expect(result).to.equal("(param1=@p1)?@p1='value1'");
25+
});
26+
it("2 parameters", function () {
27+
var result = Utility.buildFunctionParameters({ param1: "value1", param2: 2 });
28+
expect(result).to.equal("(param1=@p1,param2=@p2)?@p1='value1'&@p2=2");
29+
});
30+
it("3 parameters", function () {
31+
var result = Utility.buildFunctionParameters({ param1: "value1", param2: 2, param3: "value2" });
32+
expect(result).to.equal("(param1=@p1,param2=@p2,param3=@p3)?@p1='value1'&@p2=2&@p3='value2'");
33+
});
34+
it("object parameter", function () {
35+
var result = Utility.buildFunctionParameters({ param1: { test1: "value", '@odata.type': 'account' } });
36+
expect(result).to.equal("(param1=@p1)?@p1={\"test1\":\"value\",\"@odata.type\":\"account\"}");
37+
});
2838
});
29-
it("3 parameters", function () {
30-
var result = Utility.buildFunctionParameters({ param1: "value1", param2: 2, param3: "value2" });
31-
expect(result).to.equal("(param1=@p1,param2=@p2,param3=@p3)?@p1='value1'&@p2=2&@p3='value2'");
39+
40+
describe("getFetchXmlPagingCookie - ", function () {
41+
it("pagingCookie is empty", function () {
42+
var result = Utility.getFetchXmlPagingCookie("", 2);
43+
expect(result).to.deep.equal({
44+
cookie: "",
45+
page: 2,
46+
nextPage: 3
47+
});
48+
});
49+
50+
it("pagingCookie is null or undefined", function () {
51+
var result = Utility.getFetchXmlPagingCookie(null, 2);
52+
expect(result).to.deep.equal({
53+
cookie: "",
54+
page: 2,
55+
nextPage: 3
56+
});
57+
58+
result = Utility.getFetchXmlPagingCookie();
59+
expect(result).to.deep.equal({
60+
cookie: "",
61+
page: 1,
62+
nextPage: 2
63+
});
64+
});
65+
66+
it("pagingCookie is normal", function () {
67+
var result = Utility.getFetchXmlPagingCookie(mocks.data.fetchXmls.cookiePage2, 2);
68+
expect(result).to.deep.equal(mocks.data.fetchXmls.fetchXmlResultPage2Cookie.PagingInfo);
69+
70+
result = Utility.getFetchXmlPagingCookie(mocks.data.fetchXmls.cookiePage1, 2);
71+
expect(result).to.deep.equal(mocks.data.fetchXmls.fetchXmlResultPage1Cookie.PagingInfo);
72+
73+
result = Utility.getFetchXmlPagingCookie(mocks.data.fetchXmls.cookiePage2);
74+
expect(result).to.deep.equal(mocks.data.fetchXmls.fetchXmlResultPage2Cookie.PagingInfo);
75+
76+
});
3277
});
33-
it("object parameter", function () {
34-
var result = Utility.buildFunctionParameters({ param1: { test1: "value", '@odata.type': 'account' } });
35-
expect(result).to.equal("(param1=@p1)?@p1={\"test1\":\"value\",\"@odata.type\":\"account\"}");
78+
79+
describe("getXrmContext - GetGlobalContext", function () {
80+
before(function () {
81+
global.GetGlobalContext = function () {
82+
return "Global Context";
83+
};
84+
});
85+
86+
after(function () {
87+
global.GetGlobalContext = undefined;
88+
});
89+
90+
it("returns a correct object", function () {
91+
var result = Utility.getXrmContext();
92+
93+
expect(result).to.be.eq("Global Context");
94+
});
3695
});
37-
});
3896

39-
describe("Utility.getFetchXmlPagingCookie -", function () {
40-
it("pagingCookie is empty", function () {
41-
var result = Utility.getFetchXmlPagingCookie("", 2);
42-
expect(result).to.deep.equal({
43-
cookie: "",
44-
page: 2,
45-
nextPage: 3
97+
describe("getXrmContext - Xrm.Utility.getGlobalContext", function () {
98+
before(function () {
99+
global.Xrm.Utility = {
100+
getGlobalContext: function () {
101+
return {
102+
getClientUrl: function () {
103+
return "Xrm.Utility";
104+
}
105+
};
106+
}
107+
};
108+
});
109+
110+
after(function () {
111+
global.Xrm.Utility = undefined;
112+
});
113+
114+
it("returns a correct object", function () {
115+
var result = Utility.getXrmContext().getClientUrl();
116+
117+
expect(result).to.be.eq("Xrm.Utility");
46118
});
47119
});
48120

49-
it("pagingCookie is null or undefined", function () {
50-
var result = Utility.getFetchXmlPagingCookie(null, 2);
51-
expect(result).to.deep.equal({
52-
cookie: "",
53-
page: 2,
54-
nextPage: 3
121+
describe("getXrmContext - Form context does not exist", function () {
122+
before(function () {
123+
global.Xrm = undefined;
55124
});
56125

57-
result = Utility.getFetchXmlPagingCookie();
58-
expect(result).to.deep.equal({
59-
cookie: "",
60-
page: 1,
61-
nextPage: 2
126+
after(function () {
127+
global.Xrm = {
128+
Page: {
129+
context: {
130+
getClientUrl: function () {
131+
return "http://testorg.crm.dynamics.com";
132+
}
133+
}
134+
}
135+
};
136+
});
137+
138+
it("throws an error", function () {
139+
expect(function () {
140+
Utility.getXrmContext();
141+
}).to.throw();
62142
});
63143
});
64144

65-
it("pagingCookie is normal", function () {
66-
var result = Utility.getFetchXmlPagingCookie(mocks.data.fetchXmls.cookiePage2, 2);
67-
expect(result).to.deep.equal(mocks.data.fetchXmls.fetchXmlResultPage2Cookie.PagingInfo);
145+
describe("getClientUrl - removes a slash at the end", function () {
146+
before(function () {
147+
Xrm.Page.context.getClientUrl = function () {
148+
return "http://testorg.crm.dynamics.com/";
149+
};
150+
});
68151

69-
result = Utility.getFetchXmlPagingCookie(mocks.data.fetchXmls.cookiePage1, 2);
70-
expect(result).to.deep.equal(mocks.data.fetchXmls.fetchXmlResultPage1Cookie.PagingInfo);
152+
after(function () {
153+
Xrm.Page.context.getClientUrl = function () {
154+
return "http://testorg.crm.dynamics.com";
155+
};
156+
});
71157

72-
result = Utility.getFetchXmlPagingCookie(mocks.data.fetchXmls.cookiePage2);
73-
expect(result).to.deep.equal(mocks.data.fetchXmls.fetchXmlResultPage2Cookie.PagingInfo);
158+
it("returns a correct string", function () {
159+
var result = Utility.getClientUrl();
74160

161+
expect(result).to.be.eq("http://testorg.crm.dynamics.com");
162+
});
75163
});
76164
});
77165

@@ -1772,6 +1860,96 @@ describe("Request.sendRequest", function () {
17721860
expect(scope.isDone()).to.be.true;
17731861
});
17741862
});
1863+
1864+
describe("request error", function () {
1865+
var scope;
1866+
var url = 'test';
1867+
before(function () {
1868+
scope = nock(mocks.webApiUrl + 'test')
1869+
.post("", mocks.data.testEntity)
1870+
.replyWithError({ code: 'Error' });
1871+
});
1872+
1873+
after(function () {
1874+
nock.cleanAll();
1875+
});
1876+
1877+
it("returns a correct response", function (done) {
1878+
Request.sendRequest('POST', url, { webApiUrl: mocks.webApiUrl }, mocks.data.testEntityAdditionalAttributes, null, null, function (object) {
1879+
expect(object).to.be.undefined;
1880+
done(object);
1881+
}, function (object) {
1882+
expect(object).to.be.deep.equal({ code: "Error" });
1883+
done();
1884+
});
1885+
});
1886+
1887+
it("all requests have been made", function () {
1888+
expect(scope.isDone()).to.be.true;
1889+
});
1890+
});
1891+
1892+
describe("timeout - socket", function () {
1893+
var scope;
1894+
var url = 'test';
1895+
before(function () {
1896+
var response = mocks.responses.basicEmptyResponseSuccess;
1897+
scope = nock(mocks.webApiUrl + 'test')
1898+
.post("", mocks.data.testEntity)
1899+
.socketDelay(1000)
1900+
.reply(response.status, response.responseText, response.responseHeaders);
1901+
});
1902+
1903+
after(function () {
1904+
nock.cleanAll();
1905+
});
1906+
1907+
it("returns a correct response", function (done) {
1908+
Request.sendRequest('POST', url, { webApiUrl: mocks.webApiUrl, timeout: 500 }, mocks.data.testEntityAdditionalAttributes, null, null, function (object) {
1909+
expect(object).to.be.undefined;
1910+
done(object);
1911+
}, function (error) {
1912+
expect(error.message).to.be.eq("socket hang up");
1913+
expect(error.code).to.be.eq("ECONNRESET");
1914+
done();
1915+
});
1916+
});
1917+
1918+
it("all requests have been made", function () {
1919+
expect(scope.isDone()).to.be.true;
1920+
});
1921+
});
1922+
1923+
describe("timeout - connection delay", function () {
1924+
var scope;
1925+
var url = 'test';
1926+
before(function () {
1927+
var response = mocks.responses.basicEmptyResponseSuccess;
1928+
scope = nock(mocks.webApiUrl + 'test')
1929+
.post("", mocks.data.testEntity)
1930+
.delayConnection(1000)
1931+
.reply(response.status, response.responseText, response.responseHeaders);
1932+
});
1933+
1934+
after(function () {
1935+
nock.cleanAll();
1936+
});
1937+
1938+
it("returns a correct response", function (done) {
1939+
Request.sendRequest('POST', url, { webApiUrl: mocks.webApiUrl, timeout: 500 }, mocks.data.testEntityAdditionalAttributes, null, null, function (object) {
1940+
expect(object).to.be.undefined;
1941+
done(object);
1942+
}, function (error) {
1943+
expect(error.message).to.be.eq("socket hang up");
1944+
expect(error.code).to.be.eq("ECONNRESET");
1945+
done();
1946+
});
1947+
});
1948+
1949+
it("all requests have been made", function () {
1950+
expect(scope.isDone()).to.be.true;
1951+
});
1952+
});
17751953
});
17761954

17771955
describe("parseResponse", function () {

0 commit comments

Comments
 (0)