diff --git a/sdk/core/core-rest-pipeline/src/defaultHttpClient.browser.ts b/sdk/core/core-rest-pipeline/src/defaultHttpClient.browser.ts index d9708b80da71..3dd3f30404f2 100644 --- a/sdk/core/core-rest-pipeline/src/defaultHttpClient.browser.ts +++ b/sdk/core/core-rest-pipeline/src/defaultHttpClient.browser.ts @@ -2,11 +2,13 @@ // Licensed under the MIT license. import { HttpClient } from "./interfaces"; +import { createFetchHttpClient } from "./fetchHttpClient"; import { createXhrHttpClient } from "./xhrHttpClient"; /** * Create the correct HttpClient for the current environment. */ export function createDefaultHttpClient(): HttpClient { - return createXhrHttpClient(); + const isFetch = typeof self.fetch !== "undefined"; + return isFetch ? createFetchHttpClient() : createXhrHttpClient(); } diff --git a/sdk/identity/identity/test/httpRequests.browser.ts b/sdk/identity/identity/test/httpRequests.browser.ts index aff7111df446..9106beaee350 100644 --- a/sdk/identity/identity/test/httpRequests.browser.ts +++ b/sdk/identity/identity/test/httpRequests.browser.ts @@ -2,12 +2,14 @@ // Licensed under the MIT license. import * as sinon from "sinon"; -import { setLogLevel, AzureLogger, getLogLevel, AzureLogLevel } from "@azure/logger"; -import { RestError } from "@azure/core-rest-pipeline"; + import { AccessToken, GetTokenOptions, TokenCredential } from "@azure/core-auth"; -import { getError } from "./authTestUtils"; +import { AzureLogLevel, AzureLogger, getLogLevel, setLogLevel } from "@azure/logger"; import { IdentityTestContextInterface, RawTestResponse, TestResponse } from "./httpRequestsCommon"; +import { RestError } from "@azure/core-rest-pipeline"; +import { getError } from "./authTestUtils"; + /** * Helps specify a different number of responses for Node and for the browser. * In Node, this method will return an array that will have a response @@ -35,6 +37,14 @@ export function prepareMSALResponses(): RawTestResponse[] { * that may expect more than one response (or error) from more than one endpoint. * @internal */ + +type TrackedRequest = { + url: string; + body: string; + method: string; + headers: Record; +}; + export class IdentityTestContext implements IdentityTestContextInterface { public sandbox: sinon.SinonSandbox; public clock: sinon.SinonFakeTimers; @@ -42,7 +52,11 @@ export class IdentityTestContext implements IdentityTestContextInterface { public oldLogger: any; public logMessages: string[]; public server: sinon.SinonFakeServer; - public requests: sinon.SinonFakeXMLHttpRequest[]; + public requests: TrackedRequest[]; + public fetch: sinon.SinonStub< + [input: RequestInfo, init?: RequestInit | undefined], + Promise + >; public responses: RawTestResponse[]; constructor({ replaceLogger, logLevel }: { replaceLogger?: boolean; logLevel?: AzureLogLevel }) { @@ -51,6 +65,7 @@ export class IdentityTestContext implements IdentityTestContextInterface { this.oldLogLevel = getLogLevel(); this.oldLogger = AzureLogger.log; this.logMessages = []; + this.fetch = this.sandbox.stub(self, "fetch"); /** * Browser specific code. @@ -71,6 +86,22 @@ export class IdentityTestContext implements IdentityTestContextInterface { } } + private trackRequest(url: RequestInfo, request?: RequestInit) { + const headers = new Headers(request?.headers); + const rawHeaders: Record = {}; + + headers.forEach((key, value) => { + rawHeaders[key] = value; + }); + + this.requests.push({ + url: url.toString(), + body: request?.body?.toString() ?? "", + method: request?.method ?? "GET", + headers: rawHeaders, + }); + } + async restore(): Promise { this.sandbox.restore(); AzureLogger.log = this.oldLogger; @@ -89,9 +120,13 @@ export class IdentityTestContext implements IdentityTestContextInterface { * Both keeps track of the outgoing requests, * and ensures each request answers with each received response, in order. */ - this.server.respondWith((xhr) => { - this.requests.push(xhr); - xhr.respond(response.statusCode, response.headers, response.body); + this.fetch.callsFake(async (url, request) => { + this.trackRequest(url, request); + + return new Response(response.body, { + headers: response.headers, + status: response.statusCode, + }); }); const promise = sendPromise(); this.server.respond(); @@ -135,16 +170,22 @@ export class IdentityTestContext implements IdentityTestContextInterface { }[]; }> { this.responses.push(...[...insecureResponses, ...secureResponses]); - this.server.respondWith((xhr) => { - this.requests.push(xhr); + this.fetch.callsFake(async (url, request) => { + this.trackRequest(url, request); if (!this.responses.length) { throw new Error("No responses to send"); } const { response, error } = this.responses.shift()!; if (response) { - xhr.respond(response.statusCode, response.headers, response.body); + return new Response(response.body, { + headers: response.headers, + status: response.statusCode, + }); } else if (error) { - xhr.respond(error.statusCode!, {}, error.message); + return new Response(error.message, { + headers: {}, + status: error.statusCode, + }); } else { throw new Error("No response or error to send"); } @@ -168,14 +209,7 @@ export class IdentityTestContext implements IdentityTestContextInterface { return { result, error, - requests: this.requests.map((request) => { - return { - url: request.url, - body: request.requestBody, - method: request.method, - headers: request.requestHeaders, - }; - }), + requests: this.requests, }; } } diff --git a/sdk/test-utils/recorder/test/testProxyTests.spec.ts b/sdk/test-utils/recorder/test/testProxyTests.spec.ts index 5a7c239621b8..d6f675d821b6 100644 --- a/sdk/test-utils/recorder/test/testProxyTests.spec.ts +++ b/sdk/test-utils/recorder/test/testProxyTests.spec.ts @@ -74,12 +74,12 @@ import { getTestServerUrl, makeRequestAndVerifyResponse, setTestMode } from "./u await makeRequestAndVerifyResponse( client, { - path: `/sample_response`, + path: `/post_sample_response`, body, - method: "GET", + method: "POST", headers: [{ headerName: "Content-Type", value: "text/plain" }], }, - { val: "abc" } + { val: "post_abc" } ); }); @@ -95,12 +95,12 @@ import { getTestServerUrl, makeRequestAndVerifyResponse, setTestMode } from "./u await makeRequestAndVerifyResponse( client, { - path: `/sample_response`, + path: `/post_sample_response`, body: "body", - method: "GET", + method: "POST", headers: [{ headerName: "Content-Type", value: "text/plain" }, testHeader], }, - { val: "abc" } + { val: "post_abc" } ); }); }); diff --git a/sdk/test-utils/recorder/test/utils/server.ts b/sdk/test-utils/recorder/test/utils/server.ts index bfb58761e31e..d38cec530ca2 100644 --- a/sdk/test-utils/recorder/test/utils/server.ts +++ b/sdk/test-utils/recorder/test/utils/server.ts @@ -19,6 +19,10 @@ app.get("/sample_response", (_, res) => { res.send({ val: "abc" }); }); +app.post("/post_sample_response", (_, res) => { + res.send({ val: "post_abc" }); +}); + app.get(`/sample_response/:secret_info`, (_, res) => { res.send({ val: "I am the answer!" }); });