diff --git a/spec/development/workload/PageIterator.ts b/spec/development/workload/PageIterator.ts new file mode 100644 index 000000000..12e9063e1 --- /dev/null +++ b/spec/development/workload/PageIterator.ts @@ -0,0 +1,123 @@ +/** + * ------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. + * See License in the project root for license information. + * ------------------------------------------------------------------------------------------- + */ + +import { assert } from "chai"; +import { Event } from "microsoft-graph"; + +import { PageIterator, PageIteratorCallback, GraphRequestOptions, PageCollection } from "../../../src/tasks/PageIterator"; +import { getClient } from "../test-helper"; +import { ChaosHandler } from "../../../src/middleware/ChaosHandler"; +import { ChaosHandlerOptions } from "../../../src/middleware/options/ChaosHandlerOptions"; +import { ChaosStrategy } from "../../../src/middleware/options/ChaosStrategy"; +import { Client, ClientOptions } from "../../../src"; + +const client = getClient(); +describe("PageIterator", function() { + const pstHeader = { Prefer: 'outlook.timezone= "pacific standard time"' }; + const utc = "UTC"; + const pst = "Pacific Standard Time"; + const testURL = "/me/events"; + + before(async function() { + this.timeout(20000); + + const response = await client.api(testURL).get(); + const numberOfEvents = 4; + const existingEventsCount = response.value.length; + + if (existingEventsCount >= numberOfEvents) { + return; + } + const eventSubject = '"subject": "Test event '; + const eventTimeZone = '"timeZone": "UTC"'; + const eventStartDateTime = '"start": { "dateTime":"' + new Date().toISOString() + '",' + eventTimeZone + "}"; + const eventEndDateTime = '"end": { "dateTime":"' + new Date().toISOString() + '",' + eventTimeZone + "}"; + + for (let i = 1; i <= numberOfEvents - existingEventsCount; i++) { + const eventBody = "{" + eventSubject + "" + 1 + '",' + eventStartDateTime + "," + eventEndDateTime + "}"; + const response = await client.api(testURL).post(eventBody); + if (response.error) { + throw response.error; + } + } + }); + + it("same headers passed with pageIterator", async () => { + const response = await client + .api(`${testURL}?$top=2`) + .headers(pstHeader) + .select("id,start,end") + .get(); + + const callback: PageIteratorCallback = (eventResponse) => { + const event = eventResponse as Event; + assert.equal(event.start.timeZone, pst); + return true; + }; + var requestOptions: GraphRequestOptions = { options: { headers: pstHeader } }; + if (response["@odata.nextLink"]) { + const pageIterator = new PageIterator(client, response, callback, requestOptions); + await pageIterator.iterate(); + assert.isTrue(pageIterator.isComplete()); + } + }).timeout(30 * 1000); + + it("different headers passed with pageIterator", async () => { + const response = await client + .api(`${testURL}?$top=2`) + .headers({ Prefer: `outlook.timezone= "${utc}"` }) + .select("id,start,end") + .get(); + + let counter = 0; + const callback: PageIteratorCallback = (eventResponse) => { + const event = eventResponse as Event; + if (counter < 2) { + assert.equal(event.start.timeZone, utc); + counter++; + } else { + assert.equal(event.start.timeZone, pst); + } + return true; + }; + + var requestOptions = { headers: pstHeader }; + if (response["@odata.nextLink"]) { + const pageIterator = new PageIterator(client, response, callback, requestOptions); + await pageIterator.iterate(); + assert.isTrue(pageIterator.isComplete()); + } + }).timeout(30 * 1000); + + it("setting middleware with pageIterator", async () => { + const middleware = new ChaosHandler(); + const getPageCollection = () => { + return { + value: [], + "@odata.nextLink": "nextURL", + additionalContent: "additional content", + }; + }; + const clientOptions: ClientOptions = { + middleware, + }; + const responseBody = { value: [{ event1: "value1" }, { event2: "value2" }] }; + let counter = 1; + const callback: PageIteratorCallback = (data) => { + assert.equal(data["event" + counter], "value" + counter); + counter++; + return true; + }; + + const middlewareOptions = [new ChaosHandlerOptions(ChaosStrategy.MANUAL, 200, "middleware options for pageIterator", 0, responseBody)]; + const requestOptions = { middlewareOptions }; + + const client = Client.initWithMiddleware(clientOptions); + const pageIterator = new PageIterator(client, getPageCollection(), callback, requestOptions); + await pageIterator.iterate(); + }); +}); diff --git a/src/GraphRequest.ts b/src/GraphRequest.ts index c83f22399..237266359 100644 --- a/src/GraphRequest.ts +++ b/src/GraphRequest.ts @@ -79,9 +79,7 @@ export class GraphRequest { * @private * A member to hold custom header options for a request */ - private _headers: { - [key: string]: string; - }; + private _headers: HeadersInit; /** * @private @@ -428,10 +426,10 @@ export class GraphRequest { /** * @public * Sets the custom headers for a request - * @param {KeyValuePairObjectStringNumber} headers - The headers key value pair object + * @param {KeyValuePairObjectStringNumber | HeadersInit} headers - The request headers * @returns The same GraphRequest instance that is being called with */ - public headers(headers: KeyValuePairObjectStringNumber): GraphRequest { + public headers(headers: KeyValuePairObjectStringNumber | HeadersInit): GraphRequest { for (const key in headers) { if (headers.hasOwnProperty(key)) { this._headers[key] = headers[key] as string; diff --git a/src/middleware/ChaosHandler.ts b/src/middleware/ChaosHandler.ts index c79252cd0..5944cad79 100644 --- a/src/middleware/ChaosHandler.ts +++ b/src/middleware/ChaosHandler.ts @@ -91,15 +91,19 @@ export class ChaosHandler implements Middleware { * @param {string} statusMessage - the status message to be returned for the request * @param {string} requestID - request id * @param {string} requestDate - date of the request + * @param {any?} requestBody - the request body to be returned for the request * @returns response body */ - private createResponseBody(statusCode: number, statusMessage: string, requestID: string, requestDate: string) { - let responseBody: any; + private createResponseBody(statusCode: number, statusMessage: string, requestID: string, requestDate: string, responseBody?: any) { + if (responseBody) { + return responseBody; + } + let body: any; if (statusCode >= 400) { const codeMessage: string = httpStatusCode[statusCode]; const errMessage: string = statusMessage; - responseBody = { + body = { error: { code: codeMessage, message: errMessage, @@ -110,9 +114,9 @@ export class ChaosHandler implements Middleware { }, }; } else { - responseBody = {}; + body = {}; } - return responseBody; + return body; } /** @@ -132,7 +136,7 @@ export class ChaosHandler implements Middleware { requestID = generateUUID(); requestDate = new Date(); responseHeader = this.createResponseHeaders(chaosHandlerOptions.statusCode, requestID, requestDate.toString()); - responseBody = this.createResponseBody(chaosHandlerOptions.statusCode, chaosHandlerOptions.statusMessage, requestID, requestDate.toString()); + responseBody = this.createResponseBody(chaosHandlerOptions.statusCode, chaosHandlerOptions.statusMessage, requestID, requestDate.toString(), chaosHandlerOptions.responseBody); const init: any = { url: requestURL, status: chaosHandlerOptions.statusCode, statusText: chaosHandlerOptions.statusMessage, headers: responseHeader }; context.response = new Response(responseBody, init); } catch (error) { diff --git a/src/middleware/options/ChaosHandlerOptions.ts b/src/middleware/options/ChaosHandlerOptions.ts index c74fe76cb..1f3c0f6b1 100644 --- a/src/middleware/options/ChaosHandlerOptions.ts +++ b/src/middleware/options/ChaosHandlerOptions.ts @@ -20,7 +20,7 @@ import { MiddlewareOptions } from "./IMiddlewareOptions"; */ export class ChaosHandlerOptions implements MiddlewareOptions { /** - * Specifies the startegy used for the Testing Handler -> RAMDOM/MANUAL + * Specifies the startegy used for the Testing Handler -> RANDOM/MANUAL * * @public */ @@ -48,20 +48,30 @@ export class ChaosHandlerOptions implements MiddlewareOptions { */ public chaosPercentage: number; + /** + * The response body to be returned in the response + * + * @public + */ + public responseBody: any; + /** * @public * @constructor * To create an instance of Testing Handler Options * @param {ChaosStrategy} ChaosStrategy - Specifies the startegy used for the Testing Handler -> RAMDOM/MANUAL - * @param {number?} statusCode - The Message to be returned in the response - * @param {string} - The Message to be returned in the response + * @param {number?} statusCode - The statusCode to be returned in the response + * @param {string} statusMessage - The Message to be returned in the response + * @param {number?} chaosPercentage - The percentage of randomness/chaos in the handler + * @param {any?} responseBody - The response body to be returned in the response * @returns An instance of ChaosHandlerOptions */ - public constructor(chaosStrategy: ChaosStrategy = ChaosStrategy.RANDOM, statusCode?: number, statusMessage: string = "Some error Happened", chaosPercentage?: number) { + public constructor(chaosStrategy: ChaosStrategy = ChaosStrategy.RANDOM, statusCode?: number, statusMessage: string = "Some error Happened", chaosPercentage?: number, responseBody?: any) { this.chaosStrategy = chaosStrategy; this.statusCode = statusCode; this.statusMessage = statusMessage; this.chaosPercentage = chaosPercentage !== undefined ? chaosPercentage : 10; + this.responseBody = responseBody; if (this.chaosPercentage > 100) { throw new Error("Error Pecentage can not be more than 100"); } diff --git a/src/tasks/PageIterator.ts b/src/tasks/PageIterator.ts index 3fa90e4f8..b23cc68a1 100644 --- a/src/tasks/PageIterator.ts +++ b/src/tasks/PageIterator.ts @@ -9,7 +9,10 @@ * @module PageIterator */ +import { FetchOptions } from "../IFetchOptions"; import { Client } from "../index"; +import { MiddlewareOptions } from "../middleware/options/IMiddlewareOptions"; +import { ResponseType } from "../ResponseType"; /** * Signature representing PageCollection @@ -25,6 +28,19 @@ export interface PageCollection { [Key: string]: any; } +/** + * Signature to define the request options to be sent during request. + * The values of the GraphRequestOptions properties are passed to the Graph Request object. + * @property {HeadersInit} headers - the header options for the request + * @property {MiddlewareOptions[]} middlewareoptions - The middleware options for the request + * @property {FetchOptions} options - The fetch options for the request + */ +export interface GraphRequestOptions { + headers?: HeadersInit; + middlewareOptions?: MiddlewareOptions[]; + options?: FetchOptions; +} + /** * Signature representing callback for page iterator * @property {Function} callback - The callback function which should return boolean to continue the continue/stop the iteration. @@ -73,6 +89,11 @@ export class PageIterator { */ private complete: boolean; + /** + * Information to be added to the request + */ + private requestOptions: GraphRequestOptions; + /** * @public * @constructor @@ -80,15 +101,17 @@ export class PageIterator { * @param {Client} client - The graph client instance * @param {PageCollection} pageCollection - The page collection object * @param {PageIteratorCallback} callBack - The callback function + * @param {GraphRequestOptions} requestOptions - The request options * @returns An instance of a PageIterator */ - public constructor(client: Client, pageCollection: PageCollection, callback: PageIteratorCallback) { + public constructor(client: Client, pageCollection: PageCollection, callback: PageIteratorCallback, requestOptions?: GraphRequestOptions) { this.client = client; this.collection = pageCollection.value; this.nextLink = pageCollection["@odata.nextLink"]; this.deltaLink = pageCollection["@odata.deltaLink"]; this.callback = callback; this.complete = false; + this.requestOptions = requestOptions; } /** @@ -116,7 +139,20 @@ export class PageIterator { */ private async fetchAndUpdateNextPageData(): Promise { try { - const response: PageCollection = await this.client.api(this.nextLink).get(); + let graphRequest = this.client.api(this.nextLink); + if (this.requestOptions) { + if (this.requestOptions.headers) { + graphRequest = graphRequest.headers(this.requestOptions.headers); + } + if (this.requestOptions.middlewareOptions) { + graphRequest = graphRequest.middlewareOptions(this.requestOptions.middlewareOptions); + } + if (this.requestOptions.options) { + graphRequest = graphRequest.options(this.requestOptions.options); + } + } + + const response: PageCollection = await graphRequest.get(); this.collection = response.value; this.nextLink = response["@odata.nextLink"]; this.deltaLink = response["@odata.deltaLink"];