зеркало из https://github.com/Azure/ms-rest-js.git
Fix default HTTP client tests (#321)
* Enable CORS * Add empty line * Add mock http proxy * Fix few tests * Remove axios workaround that was fixed in current release * Fix all tests except download/upload progress * Fix stream HTTP tests * Delete testserver * Remove reporting skipped tests as warning * Remove .only * Add nodeIt * Fix Constants.ts * Remove it.skip()
This commit is contained in:
Родитель
2bc787a678
Коммит
4c2b1c5390
|
@ -2,11 +2,8 @@ import { checkEverything } from "@ts-common/azure-js-dev-tools";
|
||||||
import { checkConstantsVersion } from "./checkConstantsVersion";
|
import { checkConstantsVersion } from "./checkConstantsVersion";
|
||||||
|
|
||||||
checkEverything({
|
checkEverything({
|
||||||
checkForSkipCallsOptions: {
|
|
||||||
skipIsWarning: true
|
|
||||||
},
|
|
||||||
additionalChecks: {
|
additionalChecks: {
|
||||||
name: "Constants.ts Version",
|
name: "Constants.ts Version",
|
||||||
check: checkConstantsVersion
|
check: checkConstantsVersion
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -49,9 +49,13 @@ module.exports = function (config: any) {
|
||||||
concurrency: Infinity,
|
concurrency: Infinity,
|
||||||
|
|
||||||
customLaunchers: {
|
customLaunchers: {
|
||||||
|
ChromeNoSecurity: {
|
||||||
|
base: "ChromeHeadless",
|
||||||
|
flags: ["--disable-web-security"]
|
||||||
|
},
|
||||||
ChromeDebugging: {
|
ChromeDebugging: {
|
||||||
base: "Chrome",
|
base: "Chrome",
|
||||||
flags: [`http://localhost:${defaults.port}/debug.html`, "--auto-open-devtools-for-tabs"]
|
flags: [`http://localhost:${defaults.port}/debug.html`, "--auto-open-devtools-for-tabs", "--disable-web-security"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,9 +12,7 @@ import { RestError } from "./restError";
|
||||||
import { WebResource, HttpRequestBody } from "./webResource";
|
import { WebResource, HttpRequestBody } from "./webResource";
|
||||||
import { ProxySettings } from "./serviceClient";
|
import { ProxySettings } from "./serviceClient";
|
||||||
|
|
||||||
const axiosClient = axios.create();
|
export const axiosClient = axios.create();
|
||||||
// Workaround for https://github.com/axios/axios/issues/1158
|
|
||||||
axiosClient.interceptors.request.use(config => ({ ...config, method: config.method && config.method.toUpperCase() as any }));
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A HttpClient implementation that uses axios to send HTTP requests.
|
* A HttpClient implementation that uses axios to send HTTP requests.
|
||||||
|
|
|
@ -8,7 +8,7 @@ export const Constants = {
|
||||||
* @const
|
* @const
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
msRestVersion: "1.5.0",
|
msRestVersion: "1.5.1",
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies HTTP.
|
* Specifies HTTP.
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
{
|
{
|
||||||
"reporterEnabled": "list, mocha-junit-reporter"
|
"reporterEnabled": "list, mocha-junit-reporter"
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
"email": "azsdkteam@microsoft.com",
|
"email": "azsdkteam@microsoft.com",
|
||||||
"url": "https://github.com/Azure/ms-rest-js"
|
"url": "https://github.com/Azure/ms-rest-js"
|
||||||
},
|
},
|
||||||
"version": "1.5.0",
|
"version": "1.5.1",
|
||||||
"description": "Isomorphic client Runtime for Typescript/node.js/browser javascript client libraries generated using AutoRest",
|
"description": "Isomorphic client Runtime for Typescript/node.js/browser javascript client libraries generated using AutoRest",
|
||||||
"tags": [
|
"tags": [
|
||||||
"isomorphic",
|
"isomorphic",
|
||||||
|
@ -73,6 +73,7 @@
|
||||||
"@types/xhr-mock": "^2.0.0",
|
"@types/xhr-mock": "^2.0.0",
|
||||||
"@types/xml2js": "^0.4.3",
|
"@types/xml2js": "^0.4.3",
|
||||||
"abortcontroller-polyfill": "^1.1.9",
|
"abortcontroller-polyfill": "^1.1.9",
|
||||||
|
"axios-mock-adapter": "^1.16.0",
|
||||||
"chai": "^4.2.0",
|
"chai": "^4.2.0",
|
||||||
"express": "^4.16.3",
|
"express": "^4.16.3",
|
||||||
"glob": "^7.1.2",
|
"glob": "^7.1.2",
|
||||||
|
@ -111,6 +112,7 @@
|
||||||
"webpack": "^4.27.1",
|
"webpack": "^4.27.1",
|
||||||
"webpack-cli": "^3.1.2",
|
"webpack-cli": "^3.1.2",
|
||||||
"webpack-dev-middleware": "^3.1.2",
|
"webpack-dev-middleware": "^3.1.2",
|
||||||
|
"xhr-mock": "^2.4.1",
|
||||||
"yarn": "^1.6.0"
|
"yarn": "^1.6.0"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/Azure/ms-rest-js",
|
"homepage": "https://github.com/Azure/ms-rest-js",
|
||||||
|
@ -132,7 +134,7 @@
|
||||||
"test": "run-p test:tslint test:unit test:karma",
|
"test": "run-p test:tslint test:unit test:karma",
|
||||||
"test:tslint": "tslint -p . -c tslint.json --exclude \"./test/**/*.ts\"",
|
"test:tslint": "tslint -p . -c tslint.json --exclude \"./test/**/*.ts\"",
|
||||||
"test:unit": "nyc mocha",
|
"test:unit": "nyc mocha",
|
||||||
"test:karma": "npm run build:test-browser && node ./node_modules/karma/bin/karma start karma.conf.ts --single-run",
|
"test:karma": "npm run build:test-browser && node ./node_modules/karma/bin/karma start karma.conf.ts --browsers ChromeNoSecurity --single-run ",
|
||||||
"test:karma:debug": "npm run build:test-browser && node ./node_modules/karma/bin/karma start karma.conf.ts --log-level debug --browsers ChromeDebugging --debug --auto-watch",
|
"test:karma:debug": "npm run build:test-browser && node ./node_modules/karma/bin/karma start karma.conf.ts --log-level debug --browsers ChromeDebugging --debug --auto-watch",
|
||||||
"publish-preview": "mocha --no-colors && shx rm -rf dist/test && node ./.scripts/publish",
|
"publish-preview": "mocha --no-colors && shx rm -rf dist/test && node ./.scripts/publish",
|
||||||
"local": "ts-node ./.scripts/local.ts",
|
"local": "ts-node ./.scripts/local.ts",
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
|
||||||
import { assert, AssertionError } from "chai";
|
import { assert, AssertionError } from "chai";
|
||||||
import "chai/register-should";
|
import "chai/register-should";
|
||||||
import { createReadStream } from "fs";
|
import { createReadStream } from "fs";
|
||||||
import { join } from "path";
|
|
||||||
|
|
||||||
import { DefaultHttpClient } from "../lib/defaultHttpClient";
|
import { DefaultHttpClient } from "../lib/defaultHttpClient";
|
||||||
import { RestError } from "../lib/restError";
|
import { RestError } from "../lib/restError";
|
||||||
import { isNode } from "../lib/util/utils";
|
import { isNode } from "../lib/util/utils";
|
||||||
import { WebResource, HttpRequestBody } from "../lib/webResource";
|
import { WebResource, HttpRequestBody, TransferProgressEvent } from "../lib/webResource";
|
||||||
|
import { getHttpMock } from "./mockHttp";
|
||||||
|
|
||||||
|
const nodeIt = isNode ? it : it.skip;
|
||||||
|
const httpMock = getHttpMock();
|
||||||
|
|
||||||
function getAbortController(): AbortController {
|
function getAbortController(): AbortController {
|
||||||
let controller: AbortController;
|
let controller: AbortController;
|
||||||
|
@ -21,11 +25,229 @@ function getAbortController(): AbortController {
|
||||||
return controller;
|
return controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseURL = "https://example.com";
|
describe("defaultHttpClient", function () {
|
||||||
|
function sleep(ms: number): Promise<void> {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => httpMock.setup());
|
||||||
|
afterEach(() => httpMock.teardown());
|
||||||
|
|
||||||
|
it("should return a response instead of throwing for awaited 404", async function () {
|
||||||
|
const resourceUrl = "/nonexistent/";
|
||||||
|
|
||||||
|
httpMock.get(resourceUrl, async () => {
|
||||||
|
return { status: 404 };
|
||||||
|
});
|
||||||
|
|
||||||
|
const request = new WebResource(resourceUrl, "GET");
|
||||||
|
const httpClient = new DefaultHttpClient();
|
||||||
|
|
||||||
|
const response = await httpClient.sendRequest(request);
|
||||||
|
response.status.should.equal(404);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should allow canceling requests", async function () {
|
||||||
|
const resourceUrl = `/fileupload`;
|
||||||
|
httpMock.post(resourceUrl, async () => {
|
||||||
|
await sleep(10000);
|
||||||
|
assert.fail();
|
||||||
|
return { status: 201 };
|
||||||
|
});
|
||||||
|
const controller = getAbortController();
|
||||||
|
const veryBigPayload = "very long string";
|
||||||
|
const request = new WebResource(resourceUrl, "POST", veryBigPayload, undefined, undefined, true, undefined, controller.signal);
|
||||||
|
const client = new DefaultHttpClient();
|
||||||
|
const promise = client.sendRequest(request);
|
||||||
|
controller.abort();
|
||||||
|
try {
|
||||||
|
await promise;
|
||||||
|
assert.fail("");
|
||||||
|
} catch (err) {
|
||||||
|
err.should.not.be.instanceof(AssertionError);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
nodeIt("should not overwrite a user-provided cookie (nodejs only)", async function () {
|
||||||
|
// Cookie is only allowed to be set by the browser based on an actual response Set-Cookie header
|
||||||
|
httpMock.get("http://my.fake.domain/set-cookie", {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
"Set-Cookie": "data=123456"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
httpMock.get("http://my.fake.domain/cookie", async (_url, _method, _body, headers) => {
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
headers: headers
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const client = new DefaultHttpClient();
|
||||||
|
|
||||||
|
const request1 = new WebResource("http://my.fake.domain/set-cookie");
|
||||||
|
const response1 = await client.sendRequest(request1);
|
||||||
|
response1.headers.get("Set-Cookie")!.should.equal("data=123456");
|
||||||
|
|
||||||
|
const request2 = new WebResource("http://my.fake.domain/cookie");
|
||||||
|
const response2 = await client.sendRequest(request2);
|
||||||
|
response2.headers.get("Cookie")!.should.equal("data=123456");
|
||||||
|
|
||||||
|
const request3 = new WebResource("http://my.fake.domain/cookie", "GET", undefined, undefined, { Cookie: "data=abcdefg" });
|
||||||
|
const response3 = await client.sendRequest(request3);
|
||||||
|
response3.headers.get("Cookie")!.should.equal("data=abcdefg");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should allow canceling multiple requests with one token", async function () {
|
||||||
|
httpMock.post("/fileupload", async () => {
|
||||||
|
await sleep(1000);
|
||||||
|
assert.fail();
|
||||||
|
return { status: 201 };
|
||||||
|
});
|
||||||
|
|
||||||
|
const controller = getAbortController();
|
||||||
|
const buf = "Very large string";
|
||||||
|
const requests = [
|
||||||
|
new WebResource("/fileupload", "POST", buf, undefined, undefined, true, undefined, controller.signal),
|
||||||
|
new WebResource("/fileupload", "POST", buf, undefined, undefined, true, undefined, controller.signal)
|
||||||
|
];
|
||||||
|
const client = new DefaultHttpClient();
|
||||||
|
const promises = requests.map(r => client.sendRequest(r));
|
||||||
|
controller.abort();
|
||||||
|
// Ensure each promise is individually rejected
|
||||||
|
for (const promise of promises) {
|
||||||
|
try {
|
||||||
|
await promise;
|
||||||
|
assert.fail();
|
||||||
|
} catch (err) {
|
||||||
|
err.should.not.be.instanceof(AssertionError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("should report upload and download progress", () => {
|
||||||
|
type Notified = { notified: boolean };
|
||||||
|
const listener = (operationStatus: Notified, ev: TransferProgressEvent) => {
|
||||||
|
operationStatus.notified = true;
|
||||||
|
if (typeof ProgressEvent !== "undefined") {
|
||||||
|
ev.should.not.be.instanceof(ProgressEvent);
|
||||||
|
}
|
||||||
|
ev.loadedBytes.should.be.a("Number");
|
||||||
|
};
|
||||||
|
|
||||||
|
it("for simple bodies", async function () {
|
||||||
|
httpMock.post("/fileupload", async (_url, _method, body) => {
|
||||||
|
return { status: 201, body: body, headers: { "Content-Length": "200" } };
|
||||||
|
});
|
||||||
|
|
||||||
|
const upload: Notified = { notified: false };
|
||||||
|
const download: Notified = { notified: false };
|
||||||
|
|
||||||
|
const body = "Very large string to upload";
|
||||||
|
const request = new WebResource("/fileupload", "POST", body, undefined, undefined, false, undefined, undefined, 0,
|
||||||
|
ev => listener(upload, ev),
|
||||||
|
ev => listener(download, ev));
|
||||||
|
|
||||||
|
const client = new DefaultHttpClient();
|
||||||
|
const response = await client.sendRequest(request);
|
||||||
|
response.should.exist;
|
||||||
|
upload.notified.should.be.true;
|
||||||
|
download.notified.should.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("for blob or stream bodies", async function () {
|
||||||
|
let payload: HttpRequestBody;
|
||||||
|
if (isNode) {
|
||||||
|
payload = () => createReadStream(__filename);
|
||||||
|
} else {
|
||||||
|
payload = new Blob([new Uint8Array(1024 * 1024)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const size = isNode ? payload.toString().length : undefined;
|
||||||
|
|
||||||
|
httpMock.post("/fileupload", async (_url, _method, _body) => {
|
||||||
|
return { status: 201, body: payload, headers: { "Content-Type": "text/javascript", "Content-length": size } };
|
||||||
|
});
|
||||||
|
|
||||||
|
const upload: Notified = { notified: false };
|
||||||
|
const download: Notified = { notified: false };
|
||||||
|
|
||||||
|
const request = new WebResource("/fileupload", "POST", payload, undefined, undefined, true, undefined, undefined, 0,
|
||||||
|
ev => listener(upload, ev),
|
||||||
|
ev => listener(download, ev));
|
||||||
|
|
||||||
|
const client = new DefaultHttpClient();
|
||||||
|
const response = await client.sendRequest(request);
|
||||||
|
if (response.blobBody) {
|
||||||
|
await response.blobBody;
|
||||||
|
} else if ((typeof response.readableStreamBody === "function")) {
|
||||||
|
const streamBody = (response.readableStreamBody as Function)();
|
||||||
|
streamBody.on("data", () => { });
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
streamBody.on("end", resolve);
|
||||||
|
streamBody.on("error", reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
upload.notified.should.be.true;
|
||||||
|
download.notified.should.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should honor request timeouts", async function () {
|
||||||
|
httpMock.timeout("GET", "/slow");
|
||||||
|
|
||||||
|
const request = new WebResource("/slow", "GET", undefined, undefined, undefined, false, false, undefined, 100);
|
||||||
|
const client = new DefaultHttpClient();
|
||||||
|
try {
|
||||||
|
await client.sendRequest(request);
|
||||||
|
throw new Error("request did not fail as expected");
|
||||||
|
} catch (err) {
|
||||||
|
err.message.should.match(/timeout/);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should give a graceful error for nonexistent hosts", async function () {
|
||||||
|
const requestUrl = "http://foo.notawebsite/";
|
||||||
|
httpMock.passThrough(requestUrl);
|
||||||
|
const request = new WebResource(requestUrl);
|
||||||
|
const client = new DefaultHttpClient();
|
||||||
|
try {
|
||||||
|
await client.sendRequest(request);
|
||||||
|
throw new Error("request did not fail as expected");
|
||||||
|
} catch (err) {
|
||||||
|
err.should.be.instanceof(RestError);
|
||||||
|
err.code.should.equal("REQUEST_SEND_ERROR");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should interpret undefined as an empty body", async function () {
|
||||||
|
const requestUrl = "/expect-empty";
|
||||||
|
httpMock.put(requestUrl, async (_url, _method, body, _headers) => {
|
||||||
|
if (!body) {
|
||||||
|
return {
|
||||||
|
status: 200
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
status: 400,
|
||||||
|
body: `Expected empty body but got "${JSON.stringify(body)}"`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const request = new WebResource(requestUrl, "PUT");
|
||||||
|
const client = new DefaultHttpClient();
|
||||||
|
const response = await client.sendRequest(request);
|
||||||
|
response.status.should.equal(200, response.bodyAsText!);
|
||||||
|
});
|
||||||
|
|
||||||
describe.skip("defaultHttpClient", function () {
|
|
||||||
it("should send HTTP requests", async function () {
|
it("should send HTTP requests", async function () {
|
||||||
const request = new WebResource("https://example.com/", "GET");
|
httpMock.passThrough();
|
||||||
|
const request = new WebResource("https://example.com", "GET");
|
||||||
|
request.headers.set("Access-Control-Allow-Headers", "Content-Type");
|
||||||
|
request.headers.set("Access-Control-Allow-Methods", "GET");
|
||||||
request.headers.set("Access-Control-Allow-Origin", "https://example.com");
|
request.headers.set("Access-Control-Allow-Origin", "https://example.com");
|
||||||
const httpClient = new DefaultHttpClient();
|
const httpClient = new DefaultHttpClient();
|
||||||
|
|
||||||
|
@ -33,8 +255,6 @@ describe.skip("defaultHttpClient", function () {
|
||||||
assert.deepEqual(response.request, request);
|
assert.deepEqual(response.request, request);
|
||||||
assert.strictEqual(response.status, 200);
|
assert.strictEqual(response.status, 200);
|
||||||
assert(response.headers);
|
assert(response.headers);
|
||||||
// content-length varies based on OS line endings
|
|
||||||
assert.strictEqual(response.headers.get("content-length"), response.bodyAsText!.length.toString());
|
|
||||||
assert.strictEqual(response.headers.get("content-type")!.split(";")[0], "text/html");
|
assert.strictEqual(response.headers.get("content-type")!.split(";")[0], "text/html");
|
||||||
const responseBody: string | null | undefined = response.bodyAsText;
|
const responseBody: string | null | undefined = response.bodyAsText;
|
||||||
const expectedResponseBody =
|
const expectedResponseBody =
|
||||||
|
@ -93,168 +313,4 @@ describe.skip("defaultHttpClient", function () {
|
||||||
responseBody && responseBody.replace(/\s/g, ""),
|
responseBody && responseBody.replace(/\s/g, ""),
|
||||||
expectedResponseBody.replace(/\s/g, ""));
|
expectedResponseBody.replace(/\s/g, ""));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return a response instead of throwing for awaited 404", async function () {
|
|
||||||
const resourceUrl = `${baseURL}/nonexistent`;
|
|
||||||
const request = new WebResource(resourceUrl, "GET");
|
|
||||||
const httpClient = new DefaultHttpClient();
|
|
||||||
|
|
||||||
const response = await httpClient.sendRequest(request);
|
|
||||||
response.should.exist;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should allow canceling requests", async function () {
|
|
||||||
const controller = getAbortController();
|
|
||||||
const veryBigPayload = "very long string";
|
|
||||||
const request = new WebResource(`${baseURL}/fileupload`, "POST", veryBigPayload, undefined, undefined, true, undefined, controller.signal);
|
|
||||||
const client = new DefaultHttpClient();
|
|
||||||
const promise = client.sendRequest(request);
|
|
||||||
controller.abort();
|
|
||||||
try {
|
|
||||||
await promise;
|
|
||||||
assert.fail("");
|
|
||||||
} catch (err) {
|
|
||||||
err.should.not.be.instanceof(AssertionError);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not overwrite a user-provided cookie (nodejs only)", async function () {
|
|
||||||
// Cookie is only allowed to be set by the browser based on an actual response Set-Cookie header
|
|
||||||
if (!isNode) {
|
|
||||||
this.skip();
|
|
||||||
}
|
|
||||||
|
|
||||||
const client = new DefaultHttpClient();
|
|
||||||
|
|
||||||
const request1 = new WebResource(`${baseURL}/set-cookie`);
|
|
||||||
await client.sendRequest(request1);
|
|
||||||
|
|
||||||
const request2 = new WebResource(`${baseURL}/cookie`);
|
|
||||||
const response2 = await client.sendRequest(request2);
|
|
||||||
response2.headers.get("Cookie")!.should.equal("data=123456");
|
|
||||||
|
|
||||||
const request3 = new WebResource(`${baseURL}/cookie`, "GET", undefined, undefined, { Cookie: "data=abcdefg" });
|
|
||||||
const response3 = await client.sendRequest(request3);
|
|
||||||
response3.headers.get("Cookie")!.should.equal("data=abcdefg");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should allow canceling multiple requests with one token", async function () {
|
|
||||||
const controller = getAbortController();
|
|
||||||
const buf = new Uint8Array(1024 * 1024 * 1);
|
|
||||||
const requests = [
|
|
||||||
new WebResource(`${baseURL}/fileupload`, "POST", buf, undefined, undefined, true, undefined, controller.signal),
|
|
||||||
new WebResource(`${baseURL}/fileupload`, "POST", buf, undefined, undefined, true, undefined, controller.signal)
|
|
||||||
];
|
|
||||||
const client = new DefaultHttpClient();
|
|
||||||
const promises = requests.map(r => client.sendRequest(r));
|
|
||||||
controller.abort();
|
|
||||||
// Ensure each promise is individually rejected
|
|
||||||
for (const promise of promises) {
|
|
||||||
try {
|
|
||||||
await promise;
|
|
||||||
assert.fail("");
|
|
||||||
} catch (err) {
|
|
||||||
err.should.not.be.instanceof(AssertionError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should report upload and download progress for simple bodies", async function () {
|
|
||||||
let uploadNotified = false;
|
|
||||||
let downloadNotified = false;
|
|
||||||
|
|
||||||
const body = isNode ? Buffer.alloc(1024 * 1024) : new Uint8Array(1024 * 1024);
|
|
||||||
const request = new WebResource(`${baseURL}/fileupload`, "POST", body, undefined, undefined, false, undefined, undefined, 0,
|
|
||||||
ev => {
|
|
||||||
uploadNotified = true;
|
|
||||||
if (typeof ProgressEvent !== "undefined") {
|
|
||||||
ev.should.not.be.instanceof(ProgressEvent);
|
|
||||||
}
|
|
||||||
ev.loadedBytes.should.be.a("Number");
|
|
||||||
},
|
|
||||||
ev => {
|
|
||||||
downloadNotified = true;
|
|
||||||
if (typeof ProgressEvent !== "undefined") {
|
|
||||||
ev.should.not.be.instanceof(ProgressEvent);
|
|
||||||
}
|
|
||||||
ev.loadedBytes.should.be.a("Number");
|
|
||||||
});
|
|
||||||
|
|
||||||
const client = new DefaultHttpClient();
|
|
||||||
await client.sendRequest(request);
|
|
||||||
assert(uploadNotified);
|
|
||||||
assert(downloadNotified);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should report upload and download progress for blob or stream bodies", async function () {
|
|
||||||
let uploadNotified = false;
|
|
||||||
let downloadNotified = false;
|
|
||||||
|
|
||||||
let body: HttpRequestBody;
|
|
||||||
if (isNode) {
|
|
||||||
body = () => createReadStream(join(__dirname, "..", "resources", "example-index.html"));
|
|
||||||
} else {
|
|
||||||
body = new Blob([new Uint8Array(1024 * 1024)]);
|
|
||||||
}
|
|
||||||
const request = new WebResource(`${baseURL}/fileupload`, "POST", body, undefined, undefined, true, undefined, undefined, 0,
|
|
||||||
ev => {
|
|
||||||
uploadNotified = true;
|
|
||||||
if (typeof ProgressEvent !== "undefined") {
|
|
||||||
ev.should.not.be.instanceof(ProgressEvent);
|
|
||||||
}
|
|
||||||
ev.loadedBytes.should.be.a("Number");
|
|
||||||
},
|
|
||||||
ev => {
|
|
||||||
downloadNotified = true;
|
|
||||||
if (typeof ProgressEvent !== "undefined") {
|
|
||||||
ev.should.not.be.instanceof(ProgressEvent);
|
|
||||||
}
|
|
||||||
ev.loadedBytes.should.be.a("Number");
|
|
||||||
});
|
|
||||||
|
|
||||||
const client = new DefaultHttpClient();
|
|
||||||
const response = await client.sendRequest(request);
|
|
||||||
const streamBody = response.readableStreamBody;
|
|
||||||
if (response.blobBody) {
|
|
||||||
await response.blobBody;
|
|
||||||
} else if (streamBody) {
|
|
||||||
streamBody.on("data", () => { });
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
streamBody.on("end", resolve);
|
|
||||||
streamBody.on("error", reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
assert(uploadNotified);
|
|
||||||
assert(downloadNotified);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should honor request timeouts", async function () {
|
|
||||||
const request = new WebResource(`${baseURL}/slow`, "GET", undefined, undefined, undefined, false, false, undefined, 100);
|
|
||||||
const client = new DefaultHttpClient();
|
|
||||||
try {
|
|
||||||
await client.sendRequest(request);
|
|
||||||
throw new Error("request did not fail as expected");
|
|
||||||
} catch (err) {
|
|
||||||
err.message.should.match(/timeout/);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should give a graceful error for nonexistent hosts", async function () {
|
|
||||||
const request = new WebResource(`http://foo.notawebsite/`);
|
|
||||||
const client = new DefaultHttpClient();
|
|
||||||
try {
|
|
||||||
await client.sendRequest(request);
|
|
||||||
throw new Error("request did not fail as expected");
|
|
||||||
} catch (err) {
|
|
||||||
err.should.be.instanceof(RestError);
|
|
||||||
err.code.should.equal("REQUEST_SEND_ERROR");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should interpret undefined as an empty body", async function () {
|
|
||||||
const request = new WebResource(`${baseURL}/expect-empty`, "PUT");
|
|
||||||
const client = new DefaultHttpClient();
|
|
||||||
const response = await client.sendRequest(request);
|
|
||||||
response.status.should.equal(200, response.bodyAsText!);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,132 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
import xhrMock, { proxy } from "xhr-mock";
|
||||||
|
import MockAdapter from "axios-mock-adapter";
|
||||||
|
import { isNode, HttpMethods } from "../lib/msRest";
|
||||||
|
import { AxiosRequestConfig } from "axios";
|
||||||
|
|
||||||
|
export type UrlFilter = string | RegExp;
|
||||||
|
|
||||||
|
export type MockResponseData = {
|
||||||
|
status?: number;
|
||||||
|
body?: any;
|
||||||
|
headers?: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MockResponseFunction = (url?: string, method?: string, body?: any, headers?: any) => Promise<MockResponseData>;
|
||||||
|
|
||||||
|
export type MockResponse = MockResponseData | MockResponseFunction;
|
||||||
|
|
||||||
|
interface HttpMockFacade {
|
||||||
|
setup(): void;
|
||||||
|
teardown(): void;
|
||||||
|
passThrough(url?: UrlFilter): void;
|
||||||
|
timeout(method: HttpMethods, url?: UrlFilter): void;
|
||||||
|
mockHttpMethod(method: HttpMethods, url: UrlFilter, response: MockResponse): void;
|
||||||
|
get(url: UrlFilter, response: MockResponse): void;
|
||||||
|
post(url: UrlFilter, response: MockResponse): void;
|
||||||
|
put(url: UrlFilter, response: MockResponse): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getHttpMock(): HttpMockFacade {
|
||||||
|
return (isNode ? new NodeHttpMock() : new BrowserHttpMock());
|
||||||
|
}
|
||||||
|
|
||||||
|
class NodeHttpMock implements HttpMockFacade {
|
||||||
|
private _mockAdapter: MockAdapter;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
const axiosClient = require("../lib/axiosHttpClient").axiosClient;
|
||||||
|
this._mockAdapter = new MockAdapter(axiosClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
setup(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
mockHttpMethod(method: HttpMethods, url: UrlFilter, response: MockResponse): void {
|
||||||
|
const methodName = "on" + method.charAt(0) + method.slice(1).toLowerCase();
|
||||||
|
const mockCall: { reply: (statusOrCallback: number | Function, data?: any, headers?: any) => MockAdapter } = (this._mockAdapter as any)[methodName](url);
|
||||||
|
|
||||||
|
if (typeof response === "function") {
|
||||||
|
mockCall.reply(async (config: AxiosRequestConfig) => {
|
||||||
|
const result = await response(config.url, config.method, config.data, config.headers);
|
||||||
|
return [result.status, result.body, result.headers];
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
mockCall.reply(response.status || 200, response.body || {}, response.headers || {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get(url: UrlFilter, response: MockResponse): void {
|
||||||
|
return this.mockHttpMethod("GET", url, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
post(url: UrlFilter, response: MockResponse): void {
|
||||||
|
return this.mockHttpMethod("POST", url, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
put(url: UrlFilter, response: MockResponse): void {
|
||||||
|
return this.mockHttpMethod("PUT", url, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
passThrough(url?: UrlFilter): void {
|
||||||
|
this._mockAdapter.onAny(url).passThrough();
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout(_method: HttpMethods, url?: UrlFilter): void {
|
||||||
|
this._mockAdapter.onAny(url).timeout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BrowserHttpMock implements HttpMockFacade {
|
||||||
|
setup(): void {
|
||||||
|
xhrMock.setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown(): void {
|
||||||
|
xhrMock.teardown();
|
||||||
|
}
|
||||||
|
|
||||||
|
mockHttpMethod(method: HttpMethods, url: UrlFilter, response: MockResponse): void {
|
||||||
|
if (typeof response === "function") {
|
||||||
|
xhrMock.use(method, url, async (req, res) => {
|
||||||
|
const result = await response(req.url().toString(), req.method().toString(), req.body(), req.headers());
|
||||||
|
return res.status(result.status || 200).body(result.body || {}).headers(result.headers || {});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
xhrMock.use(method, url, {
|
||||||
|
status: response.status,
|
||||||
|
body: response.body
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get(url: UrlFilter, response: MockResponse): void {
|
||||||
|
this.mockHttpMethod("GET", url, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
post(url: UrlFilter, response: MockResponse): void {
|
||||||
|
this.mockHttpMethod("POST", url, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
put(url: UrlFilter, response: MockResponse): void {
|
||||||
|
this.mockHttpMethod("PUT", url, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
passThrough(url?: UrlFilter): void {
|
||||||
|
if (url) {
|
||||||
|
console.warn("Browser mock doesn't support filtered passThrough calls.");
|
||||||
|
}
|
||||||
|
|
||||||
|
xhrMock.use(proxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout(method: HttpMethods, url: UrlFilter): void {
|
||||||
|
return this.mockHttpMethod(method, url, () => new Promise(() => { }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
import path = require("path");
|
|
||||||
import webpackMiddleware = require("webpack-dev-middleware");
|
|
||||||
import webpack = require("webpack");
|
|
||||||
import express = require("express");
|
|
||||||
import testconfig = require("../webpack.testconfig");
|
|
||||||
|
|
||||||
const port = parseInt(process.env.PORT!) || 3001;
|
|
||||||
const app = express();
|
|
||||||
|
|
||||||
if (process.argv.indexOf("--no-webpack") === -1) {
|
|
||||||
app.use(webpackMiddleware(webpack(testconfig), {
|
|
||||||
publicPath: "/"
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
app.use(express.static(path.join(__dirname, "../")));
|
|
||||||
app.use(express.static(path.join(__dirname, "../test/resources/")));
|
|
||||||
|
|
||||||
app.post("/fileupload", function(req, res) {
|
|
||||||
req.pipe(res);
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get("/set-cookie", function(_req, res) {
|
|
||||||
res.setHeader("Set-Cookie", "data=123456");
|
|
||||||
res.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get("/cookie", function(req, res) {
|
|
||||||
res.setHeader("Cookie", req.header("Cookie")!);
|
|
||||||
res.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get("/slow", function(_req, res) {
|
|
||||||
setTimeout(() => {
|
|
||||||
res.status(200);
|
|
||||||
res.end();
|
|
||||||
}, 2000);
|
|
||||||
});
|
|
||||||
|
|
||||||
app.put("/expect-empty", function (req, res) {
|
|
||||||
let bufs: Buffer[] = [];
|
|
||||||
req.on('data', (data: Buffer) => {
|
|
||||||
bufs.push(data);
|
|
||||||
});
|
|
||||||
req.on('end', () => {
|
|
||||||
const buf = Buffer.concat(bufs);
|
|
||||||
if (buf.length === 0) {
|
|
||||||
res.status(200);
|
|
||||||
res.end();
|
|
||||||
} else {
|
|
||||||
res.status(400);
|
|
||||||
res.end("Expected empty body but got " + buf.toString('utf-8'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
req.on('error', err => {
|
|
||||||
res.status(500);
|
|
||||||
res.end(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.listen(port, function() {
|
|
||||||
console.log(`ms-rest-js testserver (${process.pid}) listening on port ${port}...`);
|
|
||||||
});
|
|
|
@ -35,4 +35,4 @@
|
||||||
"./samples/**/*.ts",
|
"./samples/**/*.ts",
|
||||||
"./test/**/*.ts"
|
"./test/**/*.ts"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,22 @@ const config: webpack.Configuration = {
|
||||||
entry: glob.sync(path.join(__dirname, "test/**/*.ts")),
|
entry: glob.sync(path.join(__dirname, "test/**/*.ts")),
|
||||||
mode: "development",
|
mode: "development",
|
||||||
devtool: "source-map",
|
devtool: "source-map",
|
||||||
|
stats: {
|
||||||
|
colors: true,
|
||||||
|
hash: false,
|
||||||
|
version: false,
|
||||||
|
timings: false,
|
||||||
|
assets: false,
|
||||||
|
chunks: false,
|
||||||
|
modules: false,
|
||||||
|
reasons: false,
|
||||||
|
children: false,
|
||||||
|
source: false,
|
||||||
|
errors: true,
|
||||||
|
errorDetails: false,
|
||||||
|
warnings: false,
|
||||||
|
publicPath: false
|
||||||
|
},
|
||||||
output: {
|
output: {
|
||||||
filename: "msRest.browser.test.js",
|
filename: "msRest.browser.test.js",
|
||||||
path: path.resolve(__dirname, "test")
|
path: path.resolve(__dirname, "test")
|
||||||
|
@ -31,7 +47,7 @@ const config: webpack.Configuration = {
|
||||||
},
|
},
|
||||||
node: {
|
node: {
|
||||||
fs: "empty",
|
fs: "empty",
|
||||||
net: false,
|
net: "empty",
|
||||||
path: "empty",
|
path: "empty",
|
||||||
dns: false,
|
dns: false,
|
||||||
tls: false,
|
tls: false,
|
||||||
|
|
Загрузка…
Ссылка в новой задаче