Skip to content

Commit c5b565d

Browse files
Merge pull request #147 from microsoftgraph/RedirectHandler
Implementation of Redirect handler and Redirect handler options
2 parents a03b177 + f421111 commit c5b565d

13 files changed

+891
-105
lines changed

spec/DummyHTTPMessageHandler.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,33 @@ import { Middleware } from "../src/middleware/IMiddleware";
1818
* Class representing DummyHTTPMessageHandler
1919
*/
2020
export class DummyHTTPMessageHandler implements Middleware {
21+
/**
22+
* @private
23+
* A member holding the array of response objects
24+
*/
25+
private responses: Response[];
26+
27+
/**
28+
* @public
29+
* @constructor
30+
* To create an instance of DummyHTTPMessageHandler
31+
* @param {Response[]} [responses = []] - The array of response objects
32+
* @returns An instance of DummyHTTPMessageHandler
33+
*/
34+
public constructor(responses: Response[] = []) {
35+
this.responses = responses;
36+
}
37+
38+
/**
39+
* @public
40+
* To set the array of responses
41+
* @param {Response[]} response - The array of responses
42+
* @returns Nothing
43+
*/
44+
public setResponses(responses: Response[]): void {
45+
this.responses = responses;
46+
}
47+
2148
/**
2249
* @public
2350
* @async
@@ -26,6 +53,7 @@ export class DummyHTTPMessageHandler implements Middleware {
2653
* @returns A promise that resolves to nothing
2754
*/
2855
public async execute(context: Context) {
56+
context.response = this.responses.shift();
2957
return;
3058
}
3159
}

spec/middleware/MiddlewareUtil.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
import { assert } from "chai";
99

1010
import { FetchOptions } from "../../src/IFetchOptions";
11-
import { getRequestHeader, setRequestHeader } from "../../src/middleware/MiddlewareUtil";
11+
import { cloneRequestWithNewUrl, getRequestHeader, setRequestHeader } from "../../src/middleware/MiddlewareUtil";
1212

13-
describe("MiddlewareUtil.ts", () => {
13+
describe("MiddlewareUtil.ts", async () => {
1414
describe("setRequestHeader", () => {
1515
const key: string = "Content-Type";
1616
const value: string = "application/json";

spec/middleware/RedirectHandler.ts

Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
/**
2+
* -------------------------------------------------------------------------------------------
3+
* Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License.
4+
* See License in the project root for license information.
5+
* -------------------------------------------------------------------------------------------
6+
*/
7+
8+
import { assert } from "chai";
9+
10+
import { Context } from "../../src/IContext";
11+
import { MiddlewareControl } from "../../src/middleware/MiddlewareControl";
12+
import { RedirectHandlerOptions } from "../../src/middleware/options/RedirectHandlerOptions";
13+
import { RedirectHandler } from "../../src/middleware/RedirectHandler";
14+
import { DummyHTTPMessageHandler } from "../DummyHTTPMessageHandler";
15+
16+
const redirectHandlerOptions = new RedirectHandlerOptions();
17+
const redirectHandler = new RedirectHandler();
18+
describe("RedirectHandler.ts", () => {
19+
/* tslint:disable: no-string-literal */
20+
describe("constructor", () => {
21+
it("Should create an instance with given options", () => {
22+
const handler = new RedirectHandler(redirectHandlerOptions);
23+
assert.isDefined(handler["options"]);
24+
});
25+
26+
it("Should create an instance with default set of options", () => {
27+
const handler = new RedirectHandler();
28+
assert.isDefined(handler["options"]);
29+
});
30+
});
31+
32+
describe("isRedirect", () => {
33+
it("Should return true for response having 301 status code", () => {
34+
const response = new Response("Dummy", {
35+
status: 301,
36+
});
37+
assert.isTrue(redirectHandler["isRedirect"](response));
38+
});
39+
40+
it("Should return true for response having 302 status code", () => {
41+
const response = new Response("Dummy", {
42+
status: 302,
43+
});
44+
assert.isTrue(redirectHandler["isRedirect"](response));
45+
});
46+
47+
it("Should return true for response having 303 status code", () => {
48+
const response = new Response("Dummy", {
49+
status: 303,
50+
});
51+
assert.isTrue(redirectHandler["isRedirect"](response));
52+
});
53+
54+
it("Should return true for response having 307 status code", () => {
55+
const response = new Response("Dummy", {
56+
status: 307,
57+
});
58+
assert.isTrue(redirectHandler["isRedirect"](response));
59+
});
60+
61+
it("Should return true for response having 308 status code", () => {
62+
const response = new Response("Dummy", {
63+
status: 308,
64+
});
65+
assert.isTrue(redirectHandler["isRedirect"](response));
66+
});
67+
68+
it("Should return false for non redirect status codes", () => {
69+
const response = new Response("Dummy", {
70+
status: 200,
71+
});
72+
assert.isFalse(redirectHandler["isRedirect"](response));
73+
});
74+
});
75+
76+
describe("hasLocationHeader", () => {
77+
it("Should return true for response with location header", () => {
78+
const res = new Response("Dummy", {
79+
status: 301,
80+
headers: {
81+
location: "https://dummylocation.microsoft.com",
82+
},
83+
});
84+
assert.isTrue(redirectHandler["hasLocationHeader"](res));
85+
});
86+
87+
it("Should return false for response without location header", () => {
88+
const res = new Response("Dummy", {
89+
status: 301,
90+
});
91+
assert.isFalse(redirectHandler["hasLocationHeader"](res));
92+
});
93+
});
94+
95+
describe("getLocationHeader", () => {
96+
it("Should return location from response", () => {
97+
const location = "https://dummylocation.microsoft.com";
98+
const res = new Response("Dummy", {
99+
status: 301,
100+
headers: {
101+
location,
102+
},
103+
});
104+
assert.equal(redirectHandler["getLocationHeader"](res), location);
105+
});
106+
107+
it("Should return null for response without location header", () => {
108+
const res = new Response("Dummy", {
109+
status: 301,
110+
});
111+
assert.equal(redirectHandler["getLocationHeader"](res), null);
112+
});
113+
});
114+
115+
describe("isRelativeURL", () => {
116+
it("Should return true for a relative url", () => {
117+
const url = "/graphproxy/me";
118+
assert.isTrue(redirectHandler["isRelativeURL"](url));
119+
});
120+
121+
it("Should return false for a absolute url", () => {
122+
const url = "https://graph.microsoft.com/v1.0/graphproxy/me";
123+
assert.isFalse(redirectHandler["isRelativeURL"](url));
124+
});
125+
});
126+
127+
describe("shouldDropAuthorizationHeader", () => {
128+
it("Should return true for urls with different domain", () => {
129+
const requestUrl = "https://graph.microsoft.com/v1.0/me";
130+
const redirectedUrl = "https://graphredirection.microsoft.com/v1.0/me";
131+
assert.isTrue(redirectHandler["shouldDropAuthorizationHeader"](requestUrl, redirectedUrl));
132+
});
133+
134+
it("Should return true for urls with different domain and one without path", () => {
135+
const requestUrl = "https://graph.microsoft.com/v1.0/me";
136+
const redirectedUrl = "https://graphredirection.microsoft.com/";
137+
assert.isTrue(redirectHandler["shouldDropAuthorizationHeader"](requestUrl, redirectedUrl));
138+
});
139+
140+
it("Should return true for urls with different domain without path", () => {
141+
const requestUrl = "https://graph.microsoft.com/";
142+
const redirectedUrl = "https://graphredirection.microsoft.com";
143+
assert.isTrue(redirectHandler["shouldDropAuthorizationHeader"](requestUrl, redirectedUrl));
144+
});
145+
146+
it("Should return false relative urls", () => {
147+
const requestUrl = "/graph/me/";
148+
const redirectedUrl = "/graphRedirection/me";
149+
assert.isFalse(redirectHandler["shouldDropAuthorizationHeader"](requestUrl, redirectedUrl));
150+
});
151+
152+
it("Should return false redirect url is relative", () => {
153+
const requestUrl = "https://graph.microsoft.com/v1.0/me";
154+
const redirectedUrl = "/graphRedirection";
155+
assert.isFalse(redirectHandler["shouldDropAuthorizationHeader"](requestUrl, redirectedUrl));
156+
});
157+
158+
it("Should return false for urls with same domain", () => {
159+
const requestUrl = "https://graph.microsoft.com/v1.0/me";
160+
const redirectedUrl = "https://graph.microsoft.com/v2.0/me";
161+
assert.isFalse(redirectHandler["shouldDropAuthorizationHeader"](requestUrl, redirectedUrl));
162+
});
163+
});
164+
165+
describe("getOptions", () => {
166+
it("Should return the options in the context object", () => {
167+
const maxRedirects = 10;
168+
const shouldRedirect = () => false;
169+
const options = new RedirectHandlerOptions(maxRedirects, shouldRedirect);
170+
const cxt: Context = {
171+
request: "url",
172+
middlewareControl: new MiddlewareControl([options]),
173+
};
174+
const o = redirectHandler["getOptions"](cxt);
175+
assert.equal(o.maxRedirects, maxRedirects);
176+
assert.equal(o.shouldRedirect, shouldRedirect);
177+
});
178+
179+
it("Should return the default set of options in the middleware", () => {
180+
const cxt: Context = {
181+
request: "url",
182+
};
183+
const o = redirectHandler["getOptions"](cxt);
184+
assert.equal(o.maxRedirects, redirectHandler["options"].maxRedirects);
185+
assert.equal(o.shouldRedirect, redirectHandler["options"].shouldRedirect);
186+
});
187+
});
188+
189+
describe("executeWithRedirect", async () => {
190+
const context: Context = {
191+
request: "/me",
192+
options: {
193+
method: "GET",
194+
},
195+
};
196+
const dummyHTTPHandler = new DummyHTTPMessageHandler();
197+
const handler = new RedirectHandler();
198+
handler.setNext(dummyHTTPHandler);
199+
it("Should not redirect for the redirect count equal to maxRedirects", async () => {
200+
const maxRedirect = 1;
201+
const options = new RedirectHandlerOptions(maxRedirect);
202+
dummyHTTPHandler.setResponses([new Response("", { status: 301 }), new Response("ok", { status: 200 })]);
203+
await handler["executeWithRedirect"](context, maxRedirect, options);
204+
assert.equal(context.response.status, 301);
205+
});
206+
207+
it("Should not redirect for the non redirect response", async () => {
208+
const options = new RedirectHandlerOptions();
209+
dummyHTTPHandler.setResponses([new Response("", { status: 200 })]);
210+
await handler["executeWithRedirect"](context, 0, options);
211+
assert.equal(context.response.status, 200);
212+
});
213+
214+
it("Should not redirect for the redirect response without location header", async () => {
215+
const options = new RedirectHandlerOptions();
216+
dummyHTTPHandler.setResponses([new Response("", { status: 301 }), new Response("ok", { status: 200 })]);
217+
await handler["executeWithRedirect"](context, 0, options);
218+
assert.equal(context.response.status, 301);
219+
});
220+
221+
it("Should not redirect for shouldRedirect callback returning false", async () => {
222+
const options = new RedirectHandlerOptions(undefined, () => false);
223+
dummyHTTPHandler.setResponses([new Response("", { status: 301 }), new Response("ok", { status: 200 })]);
224+
await handler["executeWithRedirect"](context, 0, options);
225+
assert.equal(context.response.status, 301);
226+
});
227+
228+
it("Should drop body and change method to get for SEE_OTHER status code", async () => {
229+
const options = new RedirectHandlerOptions();
230+
const cxt: Context = {
231+
request: "/me",
232+
options: {
233+
method: "POST",
234+
body: "dummy body",
235+
},
236+
};
237+
dummyHTTPHandler.setResponses([
238+
new Response("", {
239+
status: 303,
240+
headers: {
241+
[RedirectHandler["LOCATION_HEADER"]]: "/location",
242+
},
243+
}),
244+
new Response("ok", { status: 200 }),
245+
]);
246+
await handler["executeWithRedirect"](context, 0, options);
247+
assert.isUndefined(context.options.body);
248+
assert.equal(context.options.method, "GET");
249+
assert.equal(context.response.status, 200);
250+
});
251+
252+
it("Should not drop Authorization header for relative url redirect", async () => {
253+
const options = new RedirectHandlerOptions();
254+
const cxt: Context = {
255+
request: "/me",
256+
options: {
257+
method: "POST",
258+
body: "dummy body",
259+
headers: {
260+
[RedirectHandler["AUTHORIZATION_HEADER"]]: "Bearer TEST",
261+
},
262+
},
263+
};
264+
dummyHTTPHandler.setResponses([
265+
new Response("", {
266+
status: 301,
267+
headers: {
268+
[RedirectHandler["LOCATION_HEADER"]]: "/location",
269+
},
270+
}),
271+
new Response("ok", { status: 200 }),
272+
]);
273+
await handler["executeWithRedirect"](cxt, 0, options);
274+
assert.isDefined(cxt.options.headers[RedirectHandler["AUTHORIZATION_HEADER"]]);
275+
assert.equal(cxt.response.status, 200);
276+
});
277+
278+
it("Should not drop Authorization header for same authority redirect url", async () => {
279+
const options = new RedirectHandlerOptions();
280+
const cxt: Context = {
281+
request: "https://graph.microsoft.com/v1.0/me",
282+
options: {
283+
method: "POST",
284+
body: "dummy body",
285+
headers: {
286+
[RedirectHandler["AUTHORIZATION_HEADER"]]: "Bearer TEST",
287+
},
288+
},
289+
};
290+
dummyHTTPHandler.setResponses([
291+
new Response("", {
292+
status: 301,
293+
headers: {
294+
[RedirectHandler["LOCATION_HEADER"]]: "https://graph.microsoft.com/v2.0/me",
295+
},
296+
}),
297+
new Response("ok", { status: 200 }),
298+
]);
299+
await handler["executeWithRedirect"](cxt, 0, options);
300+
assert.isDefined(cxt.options.headers[RedirectHandler["AUTHORIZATION_HEADER"]]);
301+
assert.equal(cxt.response.status, 200);
302+
});
303+
304+
it("Should return success response after successful redirect", async () => {
305+
const options = new RedirectHandlerOptions();
306+
const cxt: Context = {
307+
request: "https://graph.microsoft.com/v1.0/me",
308+
options: {
309+
method: "POST",
310+
body: "dummy body",
311+
},
312+
};
313+
dummyHTTPHandler.setResponses([
314+
new Response(null, {
315+
status: 301,
316+
headers: {
317+
[RedirectHandler["LOCATION_HEADER"]]: "https://graphredirect.microsoft.com/v1.0/me",
318+
},
319+
}),
320+
new Response("ok", { status: 200 }),
321+
]);
322+
await handler["executeWithRedirect"](cxt, 0, options);
323+
assert.equal(cxt.response.status, 200);
324+
});
325+
});
326+
327+
describe("execute", async () => {
328+
it("Should set the redirect value in options to manual", async () => {
329+
const context: Context = {
330+
request: "/me",
331+
options: {
332+
method: "GET",
333+
},
334+
};
335+
const dummyHTTPHandler = new DummyHTTPMessageHandler();
336+
const handler = new RedirectHandler();
337+
handler.setNext(dummyHTTPHandler);
338+
dummyHTTPHandler.setResponses([new Response("", { status: 200 })]);
339+
await handler.execute(context);
340+
assert.equal(context.options.redirect, RedirectHandler["MANUAL_REDIRECT"]);
341+
});
342+
});
343+
/* tslint:enable: no-string-literal */
344+
});

0 commit comments

Comments
 (0)