Skip to content

Commit 03e530c

Browse files
Changes:
* Added isComplete method for PageIterator * Updated delay to be added with exponential backoff value from second retry atttemp onwords * Updated readme with details of dependencies * Added proper handling for responses other than 2XX
1 parent 50be590 commit 03e530c

File tree

12 files changed

+191
-131
lines changed

12 files changed

+191
-131
lines changed

README.md

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,18 @@ import `@microsoft/microsoft-graph-client` into your module.
2222
import { Client } from "@microsoft/microsoft-graph-client";
2323
```
2424

25-
In case your environment have support for or have polyfill for [Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) [[support](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API#Browser_compatibility)] and [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) [[support](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#Browser_compatibility)], import `./node_modules/@microsoft/microsoft-graph-client/lib/src/core/index` into your module which doesn't have polyfills for these.
26-
27-
```typescript
28-
import { Client } from "./node_modules/@microsoft/microsoft-graph-client/lib/src/core/index";
29-
```
30-
3125
### Via Script Tag
3226

33-
Include `lib/graph-js-sdk-core.js` in your page.
27+
Include `lib/graph-js-sdk-web.js` in your page.
3428

3529
```HTML
36-
<script type="text/javascript" src="graph-js-sdk-core.js"></script>
30+
<script type="text/javascript" src="graph-js-sdk-web.js"></script>
3731
```
3832

39-
In case your browser doesn't have support for [Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) [[support](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API#Browser_compatibility)] and [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) [[support](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#Browser_compatibility)], you can polyfill them and include as above or you can use `lib/graph-js-sdk-web.js` which includes polyfills.
33+
Incase if your application ships with [es6-promise](https://www.npmjs.com/package/es6-promise) and [isomorphic-fetch](https://www.npmjs.com/package/isomorphic-fetch) just use `lib/graph-js-sdk-core.js`
4034

4135
```HTML
42-
<script type="text/javascript" src="graph-js-sdk-web.js"></script>
36+
<script type="text/javascript" src="graph-js-sdk-core.js"></script>
4337
```
4438

4539
## Getting started

docs/tasks/PageIterator.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,10 @@ async function customSize() {
4747
pageIterator.iterate();
4848

4949
// Resuming will do start from where it left off and iterate for next 1000 entities.
50-
// Resume is likely to be called in any user interaction requiring to load more data.
51-
pageIterator.resume();
50+
// Check and resume is likely to be called in any user interaction requiring to load more data.
51+
if (!pageIterator.isComplete()) {
52+
pageIterator.resume();
53+
}
5254
} catch (e) {
5355
throw e;
5456
}

spec/core/GraphErrorHandler.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,29 @@ describe("GraphErrorHandler.ts", () => {
3030
});
3131
});
3232

33+
describe("constructErrorFromRawResponse", async () => {
34+
it("Should parse error from raw response", async () => {
35+
const body = "unauthorized";
36+
const statusCode = 401;
37+
const errorResponse = new Response(body, {
38+
status: statusCode,
39+
});
40+
const gError = await GraphErrorHandler["constructErrorFromRawResponse"](errorResponse, statusCode);
41+
assert.equal(gError.statusCode, statusCode);
42+
assert.equal(gError.body, body);
43+
});
44+
45+
it("Should parse error without body", async () => {
46+
const statusCode = 401;
47+
const errorResponse = new Response(undefined, {
48+
status: statusCode,
49+
});
50+
const gError = await GraphErrorHandler["constructErrorFromRawResponse"](errorResponse, statusCode);
51+
assert.equal(gError.statusCode, statusCode);
52+
assert.isNull(gError.body);
53+
});
54+
});
55+
3356
describe("constructErrorFromResponse", () => {
3457
const statusCode = 400;
3558
const error: any = {
@@ -69,8 +92,8 @@ describe("GraphErrorHandler.ts", () => {
6992
});
7093
/* tslint:enable: no-string-literal */
7194

72-
describe("getError", () => {
73-
it("Should construct error from response", () => {
95+
describe("getError", async () => {
96+
it("Should construct error from response", async () => {
7497
const errorResponse = {
7598
error: {
7699
code: "500",
@@ -80,23 +103,23 @@ describe("GraphErrorHandler.ts", () => {
80103
},
81104
},
82105
};
83-
const gError = GraphErrorHandler.getError(errorResponse);
106+
const gError = await GraphErrorHandler.getError(errorResponse);
84107
assert.equal(gError.requestId, "some random id");
85108
assert.equal(gError.code, "500");
86109
assert.equal(gError.message, "Internal Server Error");
87110
});
88111

89-
it("Should construct error from error object", () => {
112+
it("Should construct error from error object", async () => {
90113
const error = new Error("Some Error");
91114
error.name = "InvalidError";
92-
const gError = GraphErrorHandler.getError(error);
115+
const gError = await GraphErrorHandler.getError(error);
93116
assert.equal(gError.requestId, null);
94117
assert.equal(gError.message, "Some Error");
95118
assert.equal(gError.code, "InvalidError");
96119
});
97120

98-
it("Should construct some default error", () => {
99-
const gError = GraphErrorHandler.getError();
121+
it("Should construct some default error", async () => {
122+
const gError = await GraphErrorHandler.getError();
100123
assert.equal(gError.statusCode, -1);
101124
assert.equal(gError.code, null);
102125
assert.equal(gError.message, null);

spec/core/GraphResponseHandler.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,6 @@ describe("GraphResponseHandler.ts", () => {
5858
assert.equal(responseValue, htmlString);
5959
});
6060

61-
it("Should return a raw response", async () => {
62-
const response = new Response(htmlString, status200);
63-
const responseValue = await GraphResponseHandler["convertResponse"](response, ResponseType.RAW);
64-
assert.isDefined(responseValue);
65-
assert.isTrue(responseValue instanceof Response);
66-
});
67-
6861
it("Should return response value as text for text/html return type", async () => {
6962
const response = new Response(htmlString, status200);
7063
const responseValue = await GraphResponseHandler["convertResponse"](response, ResponseType.DOCUMENT);
@@ -92,20 +85,28 @@ describe("GraphResponseHandler.ts", () => {
9285
/* tslint:enable: no-string-literal */
9386

9487
describe("getResponse", () => {
88+
it("Should return a raw response", async () => {
89+
const response = new Response(htmlString, status200);
90+
const responseValue = await GraphResponseHandler.getResponse(response, ResponseType.RAW);
91+
assert.isDefined(responseValue);
92+
assert.isTrue(responseValue instanceof Response);
93+
});
94+
9595
it("Should return valid 200 OK response", async () => {
9696
const response = new Response(htmlString, status200);
9797
const responseValue = await GraphResponseHandler.getResponse(response, ResponseType.TEXT);
9898
assert.isDefined(responseValue);
9999
});
100100

101-
it("Should throw error for NOT OK response", async () => {
101+
it("Should parse from raw response for NOT OK response", async () => {
102102
try {
103103
const response = new Response("NOT OK", status500);
104104
const responseValue = await GraphResponseHandler.getResponse(response, ResponseType.TEXT);
105105
throw new Error("Something wrong with validating OK response");
106106
} catch (error) {
107107
assert.isDefined(error);
108-
assert.equal(error, "NOT OK");
108+
assert.isTrue(error instanceof Response);
109+
assert.equal(error.status, 500);
109110
}
110111
});
111112
});

spec/middleware/RetryHandler.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -166,24 +166,34 @@ describe("RetryHandler.ts", function() {
166166
assert.isDefined(delay);
167167
});
168168

169+
it("Should return delay without exponential backoff", () => {
170+
const delay = retryHandler["getDelay"](gatewayTimeoutResponse, 1, 10);
171+
assert.isAbove(delay, 10);
172+
assert.isBelow(delay, 11);
173+
});
174+
175+
it("Should return delay with exponential backoff", () => {
176+
const delay = retryHandler["getDelay"](gatewayTimeoutResponse, 2, 10);
177+
assert.isAbove(delay, 12);
178+
assert.isBelow(delay, 13);
179+
});
180+
169181
it("Should return max delay for if the calculated delay is more", () => {
170-
const delay = retryHandler["getDelay"](gatewayTimeoutResponse, 0, 1);
171-
assert.isAbove(delay, 0);
172-
assert.isBelow(delay, 1);
182+
const delay = retryHandler["getDelay"](gatewayTimeoutResponse, 10, 100);
183+
assert.isAbove(delay, 180);
184+
assert.isBelow(delay, 181);
173185
});
174186
});
175187

176188
describe("getExponentialBackOffTime", () => {
177189
it("Should return 0 delay for 0th attempt i.e for a fresh request", () => {
178190
const time = retryHandler["getExponentialBackOffTime"](0);
179-
assert.isAbove(time, 0);
180-
assert.isBelow(time, 1);
191+
assert.equal(time, 0);
181192
});
182193

183194
it("Should return attempt time", () => {
184195
const time = retryHandler["getExponentialBackOffTime"](1);
185-
assert.isAbove(time, 1);
186-
assert.isBelow(time, 2);
196+
assert.equal(time, 1);
187197
});
188198
});
189199

spec/tasks/PageIterator.ts

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,6 @@ const truthyCallback: PageIteratorCallback = (data) => {
4747
return true;
4848
};
4949

50-
let truthyCallbackCounter = 5;
51-
const truthyCallbackWithCounter: PageIteratorCallback = (data) => {
52-
truthyCallbackCounter--;
53-
return true;
54-
};
55-
5650
let halfWayCallbackCounter = 5;
5751
const halfWayCallback: PageIteratorCallback = (data) => {
5852
halfWayCallbackCounter--;
@@ -78,11 +72,10 @@ describe("PageIterator.ts", () => {
7872

7973
describe("iterate", () => {
8074
it("Should iterate over a complete collection without nextLink", async () => {
81-
truthyCallbackCounter = 10;
82-
const pageIterator = new PageIterator(client, getPageCollection(), truthyCallbackWithCounter);
75+
const pageIterator = new PageIterator(client, getPageCollection(), truthyCallback);
8376
try {
8477
await pageIterator.iterate();
85-
assert.equal(truthyCallbackCounter, 0);
78+
assert.isTrue(pageIterator.isComplete());
8679
} catch (error) {
8780
throw error;
8881
}
@@ -104,7 +97,7 @@ describe("PageIterator.ts", () => {
10497
halfWayCallbackCounter = 5;
10598
try {
10699
await pageIterator.iterate();
107-
assert.equal(halfWayCallbackCounter, 0);
100+
assert.isFalse(pageIterator.isComplete());
108101
} catch (error) {
109102
throw error;
110103
}
@@ -123,21 +116,43 @@ describe("PageIterator.ts", () => {
123116
}
124117
});
125118
});
126-
/* tslint:enable: no-string-literal */
127119

128120
describe("resume", () => {
129121
it("Should start from the place where it left the iteration", async () => {
130122
const pageIterator = new PageIterator(client, getPageCollection(), halfWayCallback);
131123
halfWayCallbackCounter = 5;
132124
try {
133125
await pageIterator.iterate();
134-
assert.equal(halfWayCallbackCounter, 0);
135-
halfWayCallbackCounter = 5;
126+
assert.isFalse(pageIterator.isComplete());
136127
await pageIterator.resume();
137-
assert.equal(halfWayCallbackCounter, 0);
128+
assert.isTrue(pageIterator.isComplete());
129+
} catch (error) {
130+
throw error;
131+
}
132+
});
133+
});
134+
135+
describe("isComplete", () => {
136+
it("Should return false for incomplete iteration", async () => {
137+
const pageIterator = new PageIterator(client, getPageCollection(), halfWayCallback);
138+
halfWayCallbackCounter = 5;
139+
try {
140+
await pageIterator.iterate();
141+
assert.isFalse(pageIterator.isComplete());
142+
} catch (error) {
143+
throw error;
144+
}
145+
});
146+
147+
it("Should return true for complete iteration", async () => {
148+
const pageIterator = new PageIterator(client, getPageCollection(), truthyCallback);
149+
try {
150+
await pageIterator.iterate();
151+
assert.isTrue(pageIterator.isComplete());
138152
} catch (error) {
139153
throw error;
140154
}
141155
});
142156
});
157+
/* tslint:enable: no-string-literal */
143158
});

src/GraphErrorHandler.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,30 @@ export class GraphErrorHandler {
4040
/**
4141
* @private
4242
* @static
43+
* @async
44+
* To construct error from the raw response
45+
* @param {Response} error - The error response
46+
* @param {number} statusCode - The status code of the response
47+
* @returns A promise that resolves to GraphError instance
48+
*/
49+
private static async constructErrorFromRawResponse(error: Response, statusCode: number): Promise<GraphError> {
50+
const gError = new GraphError(statusCode);
51+
try {
52+
gError.body = await error.text();
53+
} catch (error) {
54+
// tslint:disable-line: no-empty
55+
}
56+
return gError;
57+
}
58+
59+
/**
60+
* @private
61+
* @static
62+
* @async
4363
* Populates the GraphError instance from the Error returned by graph service
4464
* @param {any} error - The error returned by graph service or some native error
4565
* @param {number} statusCode - The status code of the response
46-
* @returns The GraphError instance
66+
* @returns A promise that resolves to GraphError instance
4767
*
4868
* Example error for https://graph.microsoft.com/v1.0/me/events?$top=3&$search=foo
4969
* {
@@ -77,15 +97,18 @@ export class GraphErrorHandler {
7797
/**
7898
* @public
7999
* @static
100+
* @async
80101
* To get the GraphError object
81102
* @param {any} [error = null] - The error returned by graph service or some native error
82103
* @param {number} [statusCode = -1] - The status code of the response
83104
* @param {GraphRequestCallback} [callback] - The graph request callback function
84-
* @returns The GraphError instance
105+
* @returns A promise that resolves to GraphError instance
85106
*/
86-
public static getError(error: any = null, statusCode: number = -1, callback?: GraphRequestCallback): GraphError {
107+
public static async getError(error: any = null, statusCode: number = -1, callback?: GraphRequestCallback): Promise<GraphError> {
87108
let gError: GraphError;
88-
if (error && error.error) {
109+
if (error instanceof Response) {
110+
gError = await GraphErrorHandler.constructErrorFromRawResponse(error, statusCode);
111+
} else if (error && error.error) {
89112
gError = GraphErrorHandler.constructErrorFromResponse(error, statusCode);
90113
} else if (error instanceof Error) {
91114
gError = GraphErrorHandler.constructError(error, statusCode);

src/GraphRequest.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ export class GraphRequest {
302302
if (typeof rawResponse !== "undefined") {
303303
statusCode = rawResponse.status;
304304
}
305-
const gError: GraphError = GraphErrorHandler.getError(error, statusCode, callback);
305+
const gError: GraphError = await GraphErrorHandler.getError(error, statusCode, callback);
306306
throw gError;
307307
}
308308
}

0 commit comments

Comments
 (0)