Merge pull request #42 from Azure/daschult/lro
Restructure LRO polling into different polling strategies
This commit is contained in:
Коммит
5a65b73d31
|
@ -1,14 +1,14 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import * as msRest from "ms-rest-js";
|
||||
import PollingState from "./pollingState";
|
||||
import Constants, { LongRunningOperationStates as LroStates } from "./util/constants";
|
||||
import { HttpOperationResponse, RequestOptionsBase, RequestPrepareOptions, ServiceClient, ServiceClientCredentials, ServiceClientOptions, WebResource } from "ms-rest-js";
|
||||
import { createLROPollStrategy, LROPollStrategy } from "./lroPollStrategy";
|
||||
import * as Constants from "./util/constants";
|
||||
|
||||
/**
|
||||
* Options to be provided while creating the client.
|
||||
*/
|
||||
export interface AzureServiceClientOptions extends msRest.ServiceClientOptions {
|
||||
export interface AzureServiceClientOptions extends ServiceClientOptions {
|
||||
/**
|
||||
* @property {string} [options.acceptLanguage] - Gets or sets the preferred language for the response. Default value is: "en-US".
|
||||
*/
|
||||
|
@ -30,11 +30,14 @@ export interface AzureServiceClientOptions extends msRest.ServiceClientOptions {
|
|||
* UserTokenCredentials object used for authentication.
|
||||
* @param {AzureServiceClientOptions} options - The parameter options used by AzureServiceClient
|
||||
*/
|
||||
export class AzureServiceClient extends msRest.ServiceClient {
|
||||
acceptLanguage: string = Constants.DEFAULT_LANGUAGE;
|
||||
longRunningOperationRetryTimeout = 30;
|
||||
export class AzureServiceClient extends ServiceClient {
|
||||
public acceptLanguage: string = Constants.DEFAULT_LANGUAGE;
|
||||
/**
|
||||
* The retry timeout in seconds for Long Running Operations. Default value is 30.
|
||||
*/
|
||||
public longRunningOperationRetryTimeout?: number;
|
||||
|
||||
constructor(credentials: msRest.ServiceClientCredentials, options?: AzureServiceClientOptions) {
|
||||
constructor(credentials: ServiceClientCredentials, options?: AzureServiceClientOptions) {
|
||||
super(credentials, options = updateOptionsWithDefaultValues(options));
|
||||
|
||||
if (options.acceptLanguage != undefined) {
|
||||
|
@ -51,57 +54,30 @@ export class AzureServiceClient extends msRest.ServiceClient {
|
|||
/**
|
||||
* Provides a mechanism to make a request that will poll and provide the final result.
|
||||
* @param {msRest.RequestPrepareOptions|msRest.WebResource} request - The request object
|
||||
* @param {msRest.RequestOptionsBase} [options] Additional options to be sent while making the request
|
||||
* @param {AzureRequestOptionsBase} [options] Additional options to be sent while making the request
|
||||
* @returns {Promise<msRest.HttpOperationResponse>} The HttpOperationResponse containing the final polling request, response and the responseBody.
|
||||
*/
|
||||
sendLongRunningRequest(request: msRest.RequestPrepareOptions | msRest.WebResource, options?: msRest.RequestOptionsBase): Promise<msRest.HttpOperationResponse> {
|
||||
return this.sendRequest(request).then(response => this.getLongRunningOperationResult(response, options));
|
||||
async sendLongRunningRequest(request: RequestPrepareOptions | WebResource, options?: RequestOptionsBase): Promise<HttpOperationResponse> {
|
||||
return this.sendRequest(request).then((response: HttpOperationResponse) => this.getLongRunningOperationResult(response, options));
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll Azure long running PUT, PATCH, POST or DELETE operations.
|
||||
* @param {msRest.HttpOperationResponse} resultOfInitialRequest - result/response of the initial request which is a part of the asynchronous polling operation.
|
||||
* @param {msRest.RequestOptionsBase} [options] - custom request options.
|
||||
* @returns {Promise<msRest.HttpOperationResponse>} result - The final response after polling is complete.
|
||||
* @param {HttpOperationResponse} initialResponse - response of the initial request which is a part of the asynchronous polling operation.
|
||||
* @param {AzureRequestOptionsBase} [options] - custom request options.
|
||||
* @returns {Promise<HttpOperationResponse>} The final response after polling is complete.
|
||||
*/
|
||||
async getLongRunningOperationResult(resultOfInitialRequest: msRest.HttpOperationResponse, options?: msRest.RequestOptionsBase): Promise<msRest.HttpOperationResponse> {
|
||||
const initialRequestMethod: msRest.HttpMethods = resultOfInitialRequest.request.method;
|
||||
|
||||
if (checkResponseStatusCodeFailed(resultOfInitialRequest)) {
|
||||
throw new Error(`Unexpected polling status code from long running operation ` +
|
||||
`"${resultOfInitialRequest.status}" for method "${initialRequestMethod}".`);
|
||||
}
|
||||
const pollingState = new PollingState(resultOfInitialRequest, this.longRunningOperationRetryTimeout);
|
||||
pollingState.optionsOfInitialRequest = options as msRest.RequestOptionsBase;
|
||||
|
||||
const resourceUrl: string = resultOfInitialRequest.request.url;
|
||||
while (terminalStates.indexOf(pollingState.status) === -1) {
|
||||
await msRest.delay(pollingState.getTimeout());
|
||||
if (pollingState.azureAsyncOperationHeaderLink) {
|
||||
await updateStateFromAzureAsyncOperationHeader(this, pollingState);
|
||||
} else if (pollingState.locationHeaderLink) {
|
||||
await updateStateFromLocationHeader(this, initialRequestMethod, pollingState);
|
||||
} else if (initialRequestMethod === "PUT") {
|
||||
await updateStateFromGetResourceOperation(this, resourceUrl, pollingState);
|
||||
} else {
|
||||
throw new Error("Location header is missing from long running operation.");
|
||||
}
|
||||
}
|
||||
|
||||
if (pollingState.status === "Succeeded") {
|
||||
if ((pollingState.azureAsyncOperationHeaderLink || !pollingState.resource) &&
|
||||
(initialRequestMethod === "PUT" || initialRequestMethod === "PATCH")) {
|
||||
await updateStateFromGetResourceOperation(this, resourceUrl, pollingState);
|
||||
}
|
||||
return pollingState.getOperationResponse();
|
||||
async getLongRunningOperationResult(initialResponse: HttpOperationResponse, options?: RequestOptionsBase): Promise<HttpOperationResponse> {
|
||||
const lroPollStrategy: LROPollStrategy = createLROPollStrategy(initialResponse, this, options);
|
||||
const succeeded: boolean = await lroPollStrategy.pollUntilFinished();
|
||||
if (succeeded) {
|
||||
return lroPollStrategy.getOperationResponse();
|
||||
} else {
|
||||
throw pollingState.getRestError();
|
||||
throw lroPollStrategy.getRestError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const terminalStates: LroStates[] = ["Succeeded", "Failed", "Canceled"];
|
||||
|
||||
export function updateOptionsWithDefaultValues(options?: AzureServiceClientOptions): AzureServiceClientOptions {
|
||||
if (!options) {
|
||||
options = {};
|
||||
|
@ -110,133 +86,4 @@ export function updateOptionsWithDefaultValues(options?: AzureServiceClientOptio
|
|||
options.generateClientRequestIdHeader = true;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verified whether an unexpected polling status code for long running operation was received for the response of the initial request.
|
||||
* @param {msRest.HttpOperationResponse} initialResponse - Response to the initial request that was sent as a part of the asynchronous operation.
|
||||
*/
|
||||
export function checkResponseStatusCodeFailed(initialResponse: msRest.HttpOperationResponse): boolean {
|
||||
const statusCode = initialResponse.status;
|
||||
const method = initialResponse.request.method;
|
||||
if (statusCode === 200 || statusCode === 202 ||
|
||||
(statusCode === 201 && method === "PUT") ||
|
||||
(statusCode === 204 && (method === "DELETE" || method === "POST"))) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve operation status by polling from "azure-asyncoperation" header.
|
||||
* @param {PollingState} pollingState - The object to persist current operation state.
|
||||
* @param {boolean} inPostOrDelete - Invoked by Post Or Delete operation.
|
||||
*/
|
||||
export function updateStateFromAzureAsyncOperationHeader(client: AzureServiceClient, pollingState: PollingState): Promise<void> {
|
||||
return getStatus(client, pollingState.azureAsyncOperationHeaderLink as string, pollingState.optionsOfInitialRequest).then(result => {
|
||||
const parsedResponse = result.parsedBody as { [key: string]: any };
|
||||
if (!parsedResponse) {
|
||||
throw new Error("The response from long running operation does not contain a body.");
|
||||
} else if (!parsedResponse.status) {
|
||||
throw new Error(`The response "${result.bodyAsText}" from long running operation does not contain the status property.`);
|
||||
}
|
||||
pollingState.status = parsedResponse.status;
|
||||
pollingState.error = parsedResponse.error;
|
||||
pollingState.updateResponse(result);
|
||||
pollingState.request = result.request;
|
||||
pollingState.resource = undefined;
|
||||
pollingState.resource = result.parsedBody;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve PUT operation status by polling from "location" header.
|
||||
* @param {string} method - The HTTP method.
|
||||
* @param {PollingState} pollingState - The object to persist current operation state.
|
||||
*/
|
||||
export function updateStateFromLocationHeader(client: AzureServiceClient, method: msRest.HttpMethods, pollingState: PollingState): Promise<void> {
|
||||
return getStatus(client, pollingState.locationHeaderLink as string, pollingState.optionsOfInitialRequest).then(result => {
|
||||
const parsedResponse = result.parsedBody as { [key: string]: any };
|
||||
|
||||
pollingState.updateResponse(result);
|
||||
pollingState.request = result.request;
|
||||
const statusCode = result.status;
|
||||
if (statusCode === 202) {
|
||||
pollingState.status = "InProgress";
|
||||
} else if (statusCode === 200 ||
|
||||
(statusCode === 201 && (method === "PUT" || method === "PATCH")) ||
|
||||
(statusCode === 204 && (method === "DELETE" || method === "POST"))) {
|
||||
pollingState.status = "Succeeded";
|
||||
pollingState.resource = parsedResponse;
|
||||
// we might not throw an error, but initialize here just in case.
|
||||
pollingState.error = new msRest.RestError(`Long running operation failed with status "${pollingState.status}".`);
|
||||
pollingState.error.code = pollingState.status;
|
||||
} else {
|
||||
throw new Error(`The response with status code ${statusCode} from polling for ` +
|
||||
`long running operation url "${pollingState.locationHeaderLink}" is not valid.`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Polling for resource status.
|
||||
* @param {string} resourceUrl - The url of resource.
|
||||
* @param {PollingState} pollingState - The object to persist current operation state.
|
||||
*/
|
||||
export function updateStateFromGetResourceOperation(client: AzureServiceClient, resourceUrl: string, pollingState: PollingState): Promise<void> {
|
||||
return getStatus(client, resourceUrl, pollingState.optionsOfInitialRequest).then(result => {
|
||||
if (!result.parsedBody) {
|
||||
throw new Error("The response from long running operation does not contain a body.");
|
||||
}
|
||||
|
||||
const parsedResponse = result.parsedBody as { [key: string]: any };
|
||||
pollingState.status = "Succeeded";
|
||||
if (parsedResponse && parsedResponse.properties && parsedResponse.properties.provisioningState) {
|
||||
pollingState.status = parsedResponse.properties.provisioningState;
|
||||
}
|
||||
pollingState.updateResponse(result);
|
||||
pollingState.request = result.request;
|
||||
pollingState.resource = parsedResponse;
|
||||
// we might not throw an error, but initialize here just in case.
|
||||
pollingState.error = new msRest.RestError(`Long running operation failed with status "${pollingState.status}".`);
|
||||
pollingState.error.code = pollingState.status;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves operation status by querying the operation URL.
|
||||
* @param {string} operationUrl - URL used to poll operation result.
|
||||
* @param {object} options - Options that can be set on the request object
|
||||
*/
|
||||
export function getStatus(client: AzureServiceClient, operationUrl: string, options?: msRest.RequestOptionsBase): Promise<msRest.HttpOperationResponse> {
|
||||
// Construct URL
|
||||
const requestUrl = operationUrl.replace(" ", "%20");
|
||||
// Create HTTP request object
|
||||
const httpRequest: msRest.RequestPrepareOptions = {
|
||||
method: "GET",
|
||||
url: requestUrl,
|
||||
headers: {}
|
||||
};
|
||||
if (options && options.customHeaders) {
|
||||
const customHeaders = options.customHeaders;
|
||||
for (const headerName of Object.keys(customHeaders)) {
|
||||
httpRequest.headers![headerName] = customHeaders[headerName];
|
||||
}
|
||||
}
|
||||
return client.sendRequest(httpRequest).then(operationResponse => {
|
||||
const statusCode = operationResponse.status;
|
||||
const responseBody = operationResponse.parsedBody;
|
||||
if (statusCode !== 200 && statusCode !== 201 && statusCode !== 202 && statusCode !== 204) {
|
||||
const error = new msRest.RestError(`Invalid status code with response body "${operationResponse.bodyAsText}" occurred ` +
|
||||
`when polling for operation status.`);
|
||||
error.statusCode = statusCode;
|
||||
error.request = msRest.stripRequest(operationResponse.request);
|
||||
error.response = operationResponse;
|
||||
error.body = responseBody;
|
||||
throw error;
|
||||
}
|
||||
|
||||
return operationResponse;
|
||||
});
|
||||
}
|
|
@ -0,0 +1,354 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import { delay, HttpMethods, HttpOperationResponse, RequestOptionsBase, RequestPrepareOptions, RestError, stripRequest, WebResource } from "ms-rest-js";
|
||||
import { AzureServiceClient } from "./azureServiceClient";
|
||||
import { LongRunningOperationStates } from "./util/constants";
|
||||
|
||||
/**
|
||||
* A long-running operation polling strategy base class that other polling strategies should extend.
|
||||
*/
|
||||
export abstract class LROPollStrategy {
|
||||
private readonly _initialRequestUrl: string;
|
||||
protected readonly _initialRequestMethod: HttpMethods;
|
||||
protected _status: LongRunningOperationStates;
|
||||
protected _mostRecentRequest: WebResource;
|
||||
protected _response: HttpOperationResponse;
|
||||
protected _resource: any;
|
||||
|
||||
constructor(initialResponse: HttpOperationResponse, private readonly _azureServiceClient: AzureServiceClient, private readonly _options?: RequestOptionsBase) {
|
||||
this._response = initialResponse;
|
||||
|
||||
this._mostRecentRequest = initialResponse.request;
|
||||
this._initialRequestUrl = this._mostRecentRequest.url;
|
||||
this._initialRequestMethod = this._mostRecentRequest.method;
|
||||
|
||||
this._resource = getResponseBody(initialResponse);
|
||||
this._status = getStatusFromResponse(initialResponse, this._resource);
|
||||
this._resource = getResponseBody(initialResponse);
|
||||
}
|
||||
|
||||
public async pollUntilFinished(): Promise<boolean> {
|
||||
while (!isFinished(this._status)) {
|
||||
const delayInSeconds: number = getDelayInSeconds(this._azureServiceClient, this._response);
|
||||
await delay(delayInSeconds * 1000);
|
||||
|
||||
await this.sendPollRequest();
|
||||
}
|
||||
return this._status === "Succeeded";
|
||||
}
|
||||
|
||||
protected abstract sendPollRequest(): Promise<void>;
|
||||
|
||||
protected shouldDoFinalGetResourceRequest(): boolean {
|
||||
return !this._resource && (this._initialRequestMethod === "PUT" || this._initialRequestMethod === "PATCH");
|
||||
}
|
||||
|
||||
public async getOperationResponse(): Promise<HttpOperationResponse> {
|
||||
if (this.shouldDoFinalGetResourceRequest()) {
|
||||
await this.updateStateFromGetResourceOperation();
|
||||
}
|
||||
const result: HttpOperationResponse = {
|
||||
...this._response,
|
||||
headers: this._response.headers.clone()
|
||||
};
|
||||
if (this._resource && typeof this._resource.valueOf() === "string") {
|
||||
result.bodyAsText = this._resource;
|
||||
result.parsedBody = JSON.parse(this._resource);
|
||||
} else {
|
||||
result.bodyAsText = JSON.stringify(this._resource);
|
||||
result.parsedBody = this._resource;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public getRestError(): RestError {
|
||||
const error = new RestError("");
|
||||
error.request = stripRequest(this._mostRecentRequest);
|
||||
error.response = this._response;
|
||||
error.message = `Long running operation failed with status: "${this._status}".`;
|
||||
error.body = this._resource;
|
||||
if (error.body) {
|
||||
const innerError: any = error.body.error;
|
||||
if (innerError) {
|
||||
if (innerError.message) {
|
||||
error.message = `Long running operation failed with error: "${innerError.message}".`;
|
||||
}
|
||||
if (innerError.code) {
|
||||
error.code = innerError.code;
|
||||
}
|
||||
}
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
protected updateStateFromGetResourceOperation(): Promise<void> {
|
||||
return this.getStatus(this._initialRequestUrl).then(result => {
|
||||
if (!result.parsedBody) {
|
||||
throw new Error("The response from long running operation does not contain a body.");
|
||||
}
|
||||
|
||||
this._status = getProvisioningState(result.parsedBody) || "Succeeded";
|
||||
this._response = result;
|
||||
this._mostRecentRequest = result.request;
|
||||
this._resource = getResponseBody(result);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves operation status by querying the operation URL.
|
||||
* @param {string} statusUrl URL used to poll operation result.
|
||||
*/
|
||||
protected getStatus(statusUrl: string): Promise<HttpOperationResponse> {
|
||||
const requestUrl: string = statusUrl.replace(" ", "%20");
|
||||
const httpRequest: RequestPrepareOptions = {
|
||||
method: "GET",
|
||||
url: requestUrl,
|
||||
headers: {}
|
||||
};
|
||||
if (this._options && this._options.customHeaders) {
|
||||
const customHeaders = this._options.customHeaders;
|
||||
for (const headerName of Object.keys(customHeaders)) {
|
||||
httpRequest.headers![headerName] = customHeaders[headerName];
|
||||
}
|
||||
}
|
||||
return this._azureServiceClient.sendRequest(httpRequest).then(operationResponse => {
|
||||
const statusCode: number = operationResponse.status;
|
||||
const responseBody: any = operationResponse.parsedBody;
|
||||
if (statusCode !== 200 && statusCode !== 201 && statusCode !== 202 && statusCode !== 204) {
|
||||
const error = new RestError(`Invalid status code with response body "${operationResponse.bodyAsText}" occurred when polling for operation status.`);
|
||||
error.statusCode = statusCode;
|
||||
error.request = stripRequest(operationResponse.request);
|
||||
error.response = operationResponse;
|
||||
error.body = responseBody;
|
||||
throw error;
|
||||
}
|
||||
|
||||
return operationResponse;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function getDelayInSeconds(azureServiceClient: AzureServiceClient, previousResponse: HttpOperationResponse): number {
|
||||
let delayInSeconds = 30;
|
||||
if (azureServiceClient.longRunningOperationRetryTimeout != undefined) {
|
||||
delayInSeconds = azureServiceClient.longRunningOperationRetryTimeout;
|
||||
} else {
|
||||
const retryAfterHeaderValue: string | undefined = previousResponse.headers.get("retry-after");
|
||||
if (retryAfterHeaderValue) {
|
||||
const retryAfterDelayInSeconds: number = parseInt(retryAfterHeaderValue);
|
||||
if (!Number.isNaN(retryAfterDelayInSeconds)) {
|
||||
delayInSeconds = retryAfterDelayInSeconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
return delayInSeconds;
|
||||
}
|
||||
|
||||
function getProvisioningState(responseBody: any): LongRunningOperationStates | undefined {
|
||||
return responseBody && responseBody.properties && responseBody.properties.provisioningState;
|
||||
}
|
||||
|
||||
function getResponseBody(response: HttpOperationResponse): any {
|
||||
let result: any;
|
||||
try {
|
||||
if (response.bodyAsText && response.bodyAsText.length > 0) {
|
||||
result = JSON.parse(response.bodyAsText);
|
||||
} else {
|
||||
result = response.parsedBody;
|
||||
}
|
||||
} catch (error) {
|
||||
const deserializationError = new RestError(`Error "${error}" occurred in parsing the responseBody " +
|
||||
"while creating the PollingState for Long Running Operation- "${response.bodyAsText}"`);
|
||||
deserializationError.request = response.request;
|
||||
deserializationError.response = response;
|
||||
throw deserializationError;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function getStatusFromResponse(response: HttpOperationResponse, responseBody?: any): LongRunningOperationStates {
|
||||
if (responseBody == undefined) {
|
||||
responseBody = getResponseBody(response);
|
||||
}
|
||||
|
||||
let result: LongRunningOperationStates;
|
||||
switch (response.status) {
|
||||
case 202:
|
||||
result = "InProgress";
|
||||
break;
|
||||
|
||||
case 204:
|
||||
result = "Succeeded";
|
||||
break;
|
||||
|
||||
case 201:
|
||||
result = getProvisioningState(responseBody) || "InProgress";
|
||||
break;
|
||||
|
||||
case 200:
|
||||
result = getProvisioningState(responseBody) || "Succeeded";
|
||||
break;
|
||||
|
||||
default:
|
||||
result = "Failed";
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const terminalStates: LongRunningOperationStates[] = ["Succeeded", "Failed", "Canceled"];
|
||||
|
||||
/**
|
||||
* Get whether or not a long-running operation with the provided status is finished.
|
||||
* @param status The current status of a long-running operation.
|
||||
* @returns Whether or not a long-running operation with the provided status is finished.
|
||||
*/
|
||||
function isFinished(status: LongRunningOperationStates): boolean {
|
||||
return terminalStates.indexOf(status) !== -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new long-running operation polling strategy based on the provided initial response.
|
||||
* @param initialResponse The initial response to the long-running operation's initial request.
|
||||
* @param azureServiceClient The AzureServiceClient that was used to send the initial request.
|
||||
* @param options Any options that were provided to the initial request.
|
||||
*/
|
||||
export function createLROPollStrategy(initialResponse: HttpOperationResponse, azureServiceClient: AzureServiceClient, options?: RequestOptionsBase): LROPollStrategy {
|
||||
if (checkResponseStatusCodeFailed(initialResponse)) {
|
||||
throw new Error(`Unexpected polling status code from long running operation ` +
|
||||
`"${initialResponse.status}" for method "${initialResponse.request.method}".`);
|
||||
}
|
||||
|
||||
let result: LROPollStrategy;
|
||||
|
||||
if (getAzureAsyncOperationHeaderValue(initialResponse)) {
|
||||
result = new AzureAsyncOperationLROPollStrategy(initialResponse, azureServiceClient, options);
|
||||
} else if (getLocationHeaderValue(initialResponse)) {
|
||||
result = new LocationLROPollStrategy(initialResponse, azureServiceClient, options);
|
||||
} else if (initialResponse.request.method === "PUT" || isFinished(getStatusFromResponse(initialResponse))) {
|
||||
result = new GetResourceLROPollStrategy(initialResponse, azureServiceClient, options);
|
||||
} else {
|
||||
throw new Error("Can't determine long running operation polling strategy from initial response.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verified whether an unexpected polling status code for long running operation was received for the response of the initial request.
|
||||
* @param {msRest.HttpOperationResponse} initialResponse - Response to the initial request that was sent as a part of the asynchronous operation.
|
||||
*/
|
||||
function checkResponseStatusCodeFailed(initialResponse: HttpOperationResponse): boolean {
|
||||
const statusCode = initialResponse.status;
|
||||
const method: HttpMethods = initialResponse.request.method;
|
||||
if (statusCode === 200 || statusCode === 202 ||
|
||||
(statusCode === 201 && method === "PUT") ||
|
||||
(statusCode === 204 && (method === "DELETE" || method === "POST"))) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function getLocationHeaderValue(response: HttpOperationResponse): string | undefined {
|
||||
return response.headers.get("location");
|
||||
}
|
||||
|
||||
/**
|
||||
* A long-running operation polling strategy that is based on the location header.
|
||||
*/
|
||||
class LocationLROPollStrategy extends LROPollStrategy {
|
||||
private _locationHeaderValue?: string;
|
||||
|
||||
constructor(initialResponse: HttpOperationResponse, azureServiceClient: AzureServiceClient, options?: RequestOptionsBase) {
|
||||
super(initialResponse, azureServiceClient, options);
|
||||
|
||||
this._locationHeaderValue = getLocationHeaderValue(initialResponse)!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve PUT operation status by polling from "location" header.
|
||||
* @param {string} method - The HTTP method.
|
||||
* @param {PollingState} pollingState - The object to persist current operation state.
|
||||
*/
|
||||
protected sendPollRequest(): Promise<void> {
|
||||
return this.getStatus(this._locationHeaderValue!).then((result: HttpOperationResponse) => {
|
||||
const locationHeaderValue: string | undefined = getLocationHeaderValue(result);
|
||||
if (locationHeaderValue) {
|
||||
this._locationHeaderValue = locationHeaderValue;
|
||||
}
|
||||
|
||||
this._response = result;
|
||||
this._mostRecentRequest = result.request;
|
||||
|
||||
const statusCode: number = result.status;
|
||||
if (statusCode === 202) {
|
||||
this._status = "InProgress";
|
||||
} else if (statusCode === 200 ||
|
||||
(statusCode === 201 && (this._initialRequestMethod === "PUT" || this._initialRequestMethod === "PATCH")) ||
|
||||
(statusCode === 204 && (this._initialRequestMethod === "DELETE" || this._initialRequestMethod === "POST"))) {
|
||||
this._status = "Succeeded";
|
||||
this._resource = getResponseBody(result);
|
||||
} else {
|
||||
throw new Error(`The response with status code ${statusCode} from polling for long running operation url "${this._locationHeaderValue}" is not valid.`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getAzureAsyncOperationHeaderValue(response: HttpOperationResponse): string | undefined {
|
||||
return response.headers.get("azure-asyncoperation");
|
||||
}
|
||||
|
||||
/**
|
||||
* A long-running operation polling strategy that is based on the azure-asyncoperation header.
|
||||
*/
|
||||
class AzureAsyncOperationLROPollStrategy extends LROPollStrategy {
|
||||
private _azureAsyncOperationHeaderValue: string;
|
||||
|
||||
public constructor(initialResponse: HttpOperationResponse, azureServiceClient: AzureServiceClient, options?: RequestOptionsBase) {
|
||||
super(initialResponse, azureServiceClient, options);
|
||||
|
||||
this._azureAsyncOperationHeaderValue = getAzureAsyncOperationHeaderValue(initialResponse)!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve operation status by polling from "azure-asyncoperation" header.
|
||||
* @param {PollingState} pollingState - The object to persist current operation state.
|
||||
* @param {boolean} inPostOrDelete - Invoked by Post Or Delete operation.
|
||||
*/
|
||||
protected sendPollRequest(): Promise<void> {
|
||||
return this.getStatus(this._azureAsyncOperationHeaderValue!).then((response: HttpOperationResponse) => {
|
||||
const parsedResponse: any = response.parsedBody;
|
||||
if (!parsedResponse) {
|
||||
throw new Error("The response from long running operation does not contain a body.");
|
||||
} else if (!parsedResponse.status) {
|
||||
throw new Error(`The response "${response.bodyAsText}" from long running operation does not contain the status property.`);
|
||||
}
|
||||
|
||||
const azureAsyncOperationHeaderValue: string | undefined = getAzureAsyncOperationHeaderValue(response);
|
||||
if (azureAsyncOperationHeaderValue) {
|
||||
this._azureAsyncOperationHeaderValue = azureAsyncOperationHeaderValue;
|
||||
}
|
||||
|
||||
this._status = parsedResponse.status;
|
||||
this._response = response;
|
||||
this._mostRecentRequest = response.request;
|
||||
this._resource = getResponseBody(response);
|
||||
});
|
||||
}
|
||||
|
||||
protected shouldDoFinalGetResourceRequest(): boolean {
|
||||
return this._initialRequestMethod === "PUT" || this._initialRequestMethod === "PATCH";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A long-running operation polling strategy that is based on the resource's provisioning state.
|
||||
*/
|
||||
class GetResourceLROPollStrategy extends LROPollStrategy {
|
||||
protected sendPollRequest(): Promise<void> {
|
||||
return this.updateStateFromGetResourceOperation();
|
||||
}
|
||||
}
|
|
@ -1,9 +1,8 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import { AzureServiceClientOptions, AzureServiceClient } from "./azureServiceClient";
|
||||
import Constants from "./util/constants";
|
||||
import { CloudError, CloudErrorMapper } from "./cloudError";
|
||||
import { BaseResource, BaseResourceMapper } from "./baseResource";
|
||||
import { CognitiveServicesCredentials } from "./credentials/cognitiveServicesCredentials";
|
||||
export { AzureServiceClient, AzureServiceClientOptions, Constants, CloudError, CloudErrorMapper, BaseResource, BaseResourceMapper, CognitiveServicesCredentials };
|
||||
export { AzureServiceClient, AzureServiceClientOptions } from "./azureServiceClient";
|
||||
export { BaseResource, BaseResourceMapper } from "./baseResource";
|
||||
export { CloudError, CloudErrorMapper } from "./cloudError";
|
||||
export { CognitiveServicesCredentials } from "./credentials/cognitiveServicesCredentials";
|
||||
export { DEFAULT_LANGUAGE, LongRunningOperationStates, msRestAzureVersion } from "./util/constants";
|
||||
|
|
|
@ -1,194 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import { LongRunningOperationStates as LroStates } from "./util/constants";
|
||||
import * as msRest from "ms-rest-js";
|
||||
|
||||
/**
|
||||
* @class
|
||||
* Initializes a new instance of the PollingState class.
|
||||
*/
|
||||
export default class PollingState {
|
||||
/**
|
||||
* @param {msRest.HttpOperationResponse} [response] - Response of the initial request that was made as a part of the asynchronous operation.
|
||||
*/
|
||||
resultOfInitialRequest: msRest.HttpOperationResponse;
|
||||
/**
|
||||
* @param {msRest.RequestOptionsBase} [optionsOfInitialRequest] - Request options that were provided as a part of the initial request.
|
||||
*/
|
||||
optionsOfInitialRequest!: msRest.RequestOptionsBase;
|
||||
/**
|
||||
* @param {msRest.WebResource} [request] - provides information about the request made for polling.
|
||||
*/
|
||||
request: msRest.WebResource;
|
||||
/**
|
||||
* @param {Response} [response] - The response object to extract longrunning operation status.
|
||||
*/
|
||||
response!: msRest.HttpOperationResponse;
|
||||
/**
|
||||
* @param {any} [resource] - Provides information about the response body received in the polling request. Particularly useful when polling via provisioningState.
|
||||
*/
|
||||
resource: any;
|
||||
/**
|
||||
* @param {number} [retryTimeout] - The timeout in seconds to retry on intermediate operation results. Default Value is 30.
|
||||
*/
|
||||
retryTimeout = 30;
|
||||
/**
|
||||
* @param {string} [azureAsyncOperationHeaderLink] - The url that is present in "azure-asyncoperation" response header.
|
||||
*/
|
||||
azureAsyncOperationHeaderLink?: string;
|
||||
/**
|
||||
* @param {string} [locationHeaderLink] - The url that is present in "Location" response header.
|
||||
*/
|
||||
locationHeaderLink?: string;
|
||||
/**
|
||||
* @param {string} [status] - The status of polling. "Succeeded, Failed, Cancelled, Updating, Creating, etc."
|
||||
*/
|
||||
status: LroStates;
|
||||
/**
|
||||
* @param {msRest.RestError} [error] - Provides information about the error that happened while polling.
|
||||
*/
|
||||
error?: msRest.RestError;
|
||||
|
||||
constructor(resultOfInitialRequest: msRest.HttpOperationResponse, retryTimeout: number) {
|
||||
this.resultOfInitialRequest = resultOfInitialRequest;
|
||||
this.retryTimeout = retryTimeout;
|
||||
this.updateResponse(resultOfInitialRequest);
|
||||
this.request = resultOfInitialRequest.request;
|
||||
// Parse response.body & assign it as the resource.
|
||||
try {
|
||||
if (resultOfInitialRequest.bodyAsText && resultOfInitialRequest.bodyAsText.length > 0) {
|
||||
this.resource = JSON.parse(resultOfInitialRequest.bodyAsText);
|
||||
} else {
|
||||
this.resource = resultOfInitialRequest.parsedBody;
|
||||
}
|
||||
} catch (error) {
|
||||
const deserializationError = new msRest.RestError(`Error "${error}" occurred in parsing the responseBody " +
|
||||
"while creating the PollingState for Long Running Operation- "${resultOfInitialRequest.bodyAsText}"`);
|
||||
deserializationError.request = resultOfInitialRequest.request;
|
||||
deserializationError.response = resultOfInitialRequest;
|
||||
throw deserializationError;
|
||||
}
|
||||
const resource = this.resource;
|
||||
let status: LroStates;
|
||||
switch (this.response.status) {
|
||||
case 202:
|
||||
status = "InProgress";
|
||||
break;
|
||||
|
||||
case 204:
|
||||
status = "Succeeded";
|
||||
break;
|
||||
|
||||
case 201:
|
||||
if (resource && resource.properties && resource.properties.provisioningState) {
|
||||
status = resource.properties.provisioningState;
|
||||
} else {
|
||||
status = "InProgress";
|
||||
}
|
||||
break;
|
||||
|
||||
case 200:
|
||||
if (resource && resource.properties && resource.properties.provisioningState) {
|
||||
status = resource.properties.provisioningState;
|
||||
} else {
|
||||
status = "Succeeded";
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
status = "Failed";
|
||||
break;
|
||||
}
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update cached data using the provided response object
|
||||
* @param {Response} [response] - provider response object.
|
||||
*/
|
||||
updateResponse(response: msRest.HttpOperationResponse) {
|
||||
this.response = response;
|
||||
if (response && response.headers) {
|
||||
const asyncOperationHeader: string | undefined = response.headers.get("azure-asyncoperation");
|
||||
const locationHeader: string | undefined = response.headers.get("location");
|
||||
if (asyncOperationHeader) {
|
||||
this.azureAsyncOperationHeaderLink = asyncOperationHeader;
|
||||
}
|
||||
|
||||
if (locationHeader) {
|
||||
this.locationHeaderLink = locationHeader;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets timeout in milliseconds.
|
||||
* @returns {number} timeout
|
||||
*/
|
||||
getTimeout() {
|
||||
const retryTimeout = this.retryTimeout;
|
||||
if (retryTimeout || retryTimeout === 0) {
|
||||
return retryTimeout * 1000;
|
||||
}
|
||||
if (this.response) {
|
||||
const retryAfter: string | undefined = this.response.headers.get("retry-after");
|
||||
if (retryAfter) {
|
||||
return parseInt(retryAfter) * 1000;
|
||||
}
|
||||
}
|
||||
return 30 * 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns long running operation result.
|
||||
* @returns {msRest.HttpOperationResponse} HttpOperationResponse
|
||||
*/
|
||||
getOperationResponse(): msRest.HttpOperationResponse {
|
||||
const result = { ...this.response, headers: this.response.headers.clone() };
|
||||
const resource = this.resource;
|
||||
if (resource && typeof resource.valueOf() === "string") {
|
||||
result.bodyAsText = resource;
|
||||
result.parsedBody = JSON.parse(resource);
|
||||
} else {
|
||||
result.parsedBody = resource;
|
||||
result.bodyAsText = JSON.stringify(resource);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Error on operation failure.
|
||||
* @param {Error} err - The error object.
|
||||
* @returns {msRest.RestError} The RestError defined in the runtime.
|
||||
*/
|
||||
getRestError(err?: Error): msRest.RestError {
|
||||
let errMsg: string;
|
||||
let errCode: string | undefined = undefined;
|
||||
|
||||
const error = new msRest.RestError("");
|
||||
error.request = msRest.stripRequest(this.request);
|
||||
error.response = this.response;
|
||||
const parsedResponse = this.resource as { [key: string]: any };
|
||||
|
||||
if (err && err.message) {
|
||||
errMsg = `Long running operation failed with error: "${err.message}".`;
|
||||
} else {
|
||||
errMsg = `Long running operation failed with status: "${this.status}".`;
|
||||
}
|
||||
|
||||
if (parsedResponse) {
|
||||
if (parsedResponse.error && parsedResponse.error.message) {
|
||||
errMsg = `Long running operation failed with error: "${parsedResponse.error.message}".`;
|
||||
}
|
||||
if (parsedResponse.error && parsedResponse.error.code) {
|
||||
errCode = parsedResponse.error.code as string;
|
||||
}
|
||||
}
|
||||
|
||||
error.message = errMsg;
|
||||
if (errCode) error.code = errCode;
|
||||
error.body = parsedResponse;
|
||||
return error;
|
||||
}
|
||||
}
|
|
@ -10,22 +10,17 @@
|
|||
*/
|
||||
export type LongRunningOperationStates = "InProgress" | "Succeeded" | "Failed" | "Canceled";
|
||||
|
||||
const Constants = {
|
||||
/**
|
||||
* The default language in the request header.
|
||||
*
|
||||
* @const
|
||||
* @type {string}
|
||||
*/
|
||||
export const DEFAULT_LANGUAGE = "en-us";
|
||||
|
||||
/**
|
||||
* The default language in the request header.
|
||||
*
|
||||
* @const
|
||||
* @type {string}
|
||||
*/
|
||||
DEFAULT_LANGUAGE: "en-us",
|
||||
|
||||
/**
|
||||
* The ms-rest-azure version.
|
||||
* @const
|
||||
* @type {string}
|
||||
*/
|
||||
msRestAzureVersion: "0.1.0"
|
||||
};
|
||||
|
||||
export default Constants;
|
||||
/**
|
||||
* The ms-rest-azure version.
|
||||
* @const
|
||||
* @type {string}
|
||||
*/
|
||||
export const msRestAzureVersion = "0.1.0";
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "ms-rest-azure-js",
|
||||
"version": "0.11.101",
|
||||
"version": "0.11.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@ -2938,9 +2938,9 @@
|
|||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"ms-rest-js": {
|
||||
"version": "0.16.371",
|
||||
"resolved": "https://registry.npmjs.org/ms-rest-js/-/ms-rest-js-0.16.371.tgz",
|
||||
"integrity": "sha1-iQHQQc8HynVA0YzfmmwCe2Z7ImQ=",
|
||||
"version": "0.16.372",
|
||||
"resolved": "https://registry.npmjs.org/ms-rest-js/-/ms-rest-js-0.16.372.tgz",
|
||||
"integrity": "sha1-8xchzTHDXqv9piaAG/fJRSqJfJE=",
|
||||
"requires": {
|
||||
"@types/express": "^4.11.1",
|
||||
"@types/form-data": "^2.2.1",
|
||||
|
@ -6306,9 +6306,9 @@
|
|||
}
|
||||
},
|
||||
"tsutils": {
|
||||
"version": "2.28.0",
|
||||
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.28.0.tgz",
|
||||
"integrity": "sha512-bh5nAtW0tuhvOJnx1GLRn5ScraRLICGyJV5wJhtRWOLsxW70Kk5tZtpK3O/hW6LDnqKS9mlUMPZj9fEMJ0gxqA==",
|
||||
"version": "2.29.0",
|
||||
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz",
|
||||
"integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "^1.8.1"
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
"types": "./typings/lib/msRestAzure.d.ts",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms-rest-js": "~0.16.371",
|
||||
"ms-rest-js": "~0.16.372",
|
||||
"tslib": "^1.9.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import * as assert from "assert";
|
||||
import * as msRest from "ms-rest-js";
|
||||
import { HttpHeaders, HttpOperationResponse, TokenCredentials, WebResource } from "ms-rest-js";
|
||||
import { HttpHeaders, HttpOperationResponse, RequestOptionsBase, RestError, TokenCredentials, WebResource } from "ms-rest-js";
|
||||
import { AzureServiceClient, AzureServiceClientOptions, updateOptionsWithDefaultValues } from "../lib/azureServiceClient";
|
||||
import * as msAssert from "./msAssert";
|
||||
|
||||
|
@ -12,14 +11,14 @@ describe("AzureServiceClient", () => {
|
|||
it("with no options provided", () => {
|
||||
const client = new AzureServiceClient(new TokenCredentials("my-fake-token"));
|
||||
assert.strictEqual(client.acceptLanguage, "en-us");
|
||||
assert.strictEqual(client.longRunningOperationRetryTimeout, 30);
|
||||
assert.strictEqual(client.longRunningOperationRetryTimeout, undefined);
|
||||
assert.deepStrictEqual(client.userAgentInfo, { value: ["ms-rest-js/0.1.0", "ms-rest-azure/0.1.0"] });
|
||||
});
|
||||
|
||||
it("with acceptLanguage provided", () => {
|
||||
const client = new AzureServiceClient(new TokenCredentials("my-fake-token"), { acceptLanguage: "my-fake-language" });
|
||||
assert.strictEqual(client.acceptLanguage, "my-fake-language");
|
||||
assert.strictEqual(client.longRunningOperationRetryTimeout, 30);
|
||||
assert.strictEqual(client.longRunningOperationRetryTimeout, undefined);
|
||||
assert.deepStrictEqual(client.userAgentInfo, { value: ["ms-rest-js/0.1.0", "ms-rest-azure/0.1.0"] });
|
||||
});
|
||||
|
||||
|
@ -49,7 +48,7 @@ describe("AzureServiceClient", () => {
|
|||
}
|
||||
]);
|
||||
const httpRequest = new WebResource("https://fake.azure.com/longRunningOperation", "GET");
|
||||
const error: msRest.RestError = await msAssert.throwsAsync(serviceClient.sendLongRunningRequest(httpRequest));
|
||||
const error: RestError = await msAssert.throwsAsync(serviceClient.sendLongRunningRequest(httpRequest));
|
||||
assert.strictEqual(error.message, `Error "SyntaxError: Unexpected token < in JSON at position 0" occurred while parsing the response body - <responseBody>hello</responseBody>.`);
|
||||
assert.strictEqual(error.request!.headers.get("authorization"), "Bearer my-fake-token");
|
||||
});
|
||||
|
@ -166,7 +165,7 @@ describe("AzureServiceClient", () => {
|
|||
{ status: 200, body: {} }
|
||||
]);
|
||||
const httpRequest = new WebResource("https://fake.azure.com/longRunningOperation", "PUT");
|
||||
const options: msRest.RequestOptionsBase = {
|
||||
const options: RequestOptionsBase = {
|
||||
customHeaders: {
|
||||
a: "1"
|
||||
}
|
||||
|
@ -184,7 +183,7 @@ describe("AzureServiceClient", () => {
|
|||
{ status: 200, body: { properties: { provisioningState: "Failed" } } }
|
||||
]);
|
||||
const httpRequest = new WebResource("https://fake.azure.com/longRunningOperation", "PUT");
|
||||
const error: msRest.RestError = await msAssert.throwsAsync(serviceClient.sendLongRunningRequest(httpRequest));
|
||||
const error: RestError = await msAssert.throwsAsync(serviceClient.sendLongRunningRequest(httpRequest));
|
||||
assert.strictEqual(error.message, `Long running operation failed with status: "Failed".`);
|
||||
assert.strictEqual(error.code, undefined);
|
||||
});
|
||||
|
@ -210,7 +209,7 @@ describe("AzureServiceClient", () => {
|
|||
const httpRequest = new WebResource("https://fake.azure.com/longRunningOperation", "GET");
|
||||
await msAssert.throwsAsync(
|
||||
serviceClient.sendLongRunningRequest(httpRequest),
|
||||
new Error(`Location header is missing from long running operation.`));
|
||||
new Error(`Can't determine long running operation polling strategy from initial response.`));
|
||||
});
|
||||
|
||||
it("with 202 status and PATCH method", async () => {
|
||||
|
@ -218,7 +217,7 @@ describe("AzureServiceClient", () => {
|
|||
const httpRequest = new WebResource("https://fake.azure.com/longRunningOperation", "PATCH");
|
||||
await msAssert.throwsAsync(
|
||||
serviceClient.sendLongRunningRequest(httpRequest),
|
||||
new Error(`Location header is missing from long running operation.`));
|
||||
new Error(`Can't determine long running operation polling strategy from initial response.`));
|
||||
});
|
||||
|
||||
it("with 202 status, PUT method, and undefined final response body", async () => {
|
||||
|
@ -237,7 +236,7 @@ describe("AzureServiceClient", () => {
|
|||
const httpRequest = new WebResource("https://fake.azure.com/longRunningOperation", "POST");
|
||||
await msAssert.throwsAsync(
|
||||
serviceClient.sendLongRunningRequest(httpRequest),
|
||||
new Error(`Location header is missing from long running operation.`));
|
||||
new Error(`Can't determine long running operation polling strategy from initial response.`));
|
||||
});
|
||||
|
||||
it("with 202 status, POST method, azure-asyncoperation header, and undefined final response body", async () => {
|
||||
|
@ -478,7 +477,7 @@ describe("AzureServiceClient", () => {
|
|||
}
|
||||
]);
|
||||
const httpRequest = new WebResource("https://fake.azure.com/longRunningOperation", "PUT");
|
||||
const error: msRest.RestError = await msAssert.throwsAsync(serviceClient.sendLongRunningRequest(httpRequest));
|
||||
const error: RestError = await msAssert.throwsAsync(serviceClient.sendLongRunningRequest(httpRequest));
|
||||
assert.strictEqual(error.message, `Invalid status code with response body "undefined" occurred when polling for operation status.`);
|
||||
assert.strictEqual(error.statusCode, 404);
|
||||
assert.strictEqual(error.request!.headers.contains("authorization"), false);
|
||||
|
@ -490,7 +489,7 @@ describe("AzureServiceClient", () => {
|
|||
const httpRequest = new WebResource("https://fake.azure.com/longRunningOperation", "DELETE");
|
||||
await msAssert.throwsAsync(
|
||||
serviceClient.sendLongRunningRequest(httpRequest),
|
||||
new Error(`Location header is missing from long running operation.`));
|
||||
new Error(`Can't determine long running operation polling strategy from initial response.`));
|
||||
});
|
||||
|
||||
it("with 204 status and GET method", async () => {
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import * as assert from "assert";
|
||||
import { WebResource } from "ms-rest-js";
|
||||
import { CognitiveServicesCredentials } from "../../lib/credentials/cognitiveServicesCredentials";
|
||||
import * as msAssert from "../msAssert";
|
||||
|
||||
describe("CognitiveServicesCredentials", () => {
|
||||
describe("constructor()", () => {
|
||||
it("with undefined subscription key", () => {
|
||||
msAssert.throws(() => new CognitiveServicesCredentials(undefined as any),
|
||||
new Error("subscriptionKey cannot be null or undefined and must be of type string."));
|
||||
});
|
||||
|
||||
it("with null subscription key", () => {
|
||||
// tslint:disable-next-line:no-null-keyword
|
||||
msAssert.throws(() => new CognitiveServicesCredentials(null as any),
|
||||
new Error("subscriptionKey cannot be null or undefined and must be of type string."));
|
||||
});
|
||||
|
||||
it("with number subscription key", () => {
|
||||
msAssert.throws(() => new CognitiveServicesCredentials(50 as any),
|
||||
new Error("subscriptionKey cannot be null or undefined and must be of type string."));
|
||||
});
|
||||
|
||||
it("with empty subscription key", () => {
|
||||
msAssert.throws(() => new CognitiveServicesCredentials(""),
|
||||
new Error("subscriptionKey cannot be null or undefined and must be of type string."));
|
||||
});
|
||||
|
||||
it("with non-empty subscription key", async () => {
|
||||
const credentials = new CognitiveServicesCredentials("fake-subscription-key");
|
||||
const httpRequest = new WebResource();
|
||||
const signedHttpRequest: WebResource = await credentials.signRequest(httpRequest);
|
||||
assert.strictEqual(signedHttpRequest, httpRequest);
|
||||
assert.deepEqual(signedHttpRequest.headers.rawHeaders(), {
|
||||
"Ocp-Apim-Subscription-Key": "fake-subscription-key",
|
||||
"X-BingApis-SDK-Client": "node-SDK"
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import * as assert from "assert";
|
||||
import { HttpHeaders, HttpOperationResponse, TokenCredentials, WebResource } from "ms-rest-js";
|
||||
import { AzureServiceClient } from "../lib/azureServiceClient";
|
||||
import { getDelayInSeconds } from "../lib/lroPollStrategy";
|
||||
|
||||
describe("LROPollStrategy", () => {
|
||||
describe("getDelayInMilliseconds()", () => {
|
||||
it("with no AzureServiceClient.longRunningOperationRetryTimeout value and no retry-after header", () => {
|
||||
const azureServiceClient = new AzureServiceClient(new TokenCredentials("my-fake-token"));
|
||||
const previousResponse: HttpOperationResponse = {
|
||||
request: new WebResource(),
|
||||
status: 200,
|
||||
headers: new HttpHeaders()
|
||||
};
|
||||
assert.strictEqual(getDelayInSeconds(azureServiceClient, previousResponse), 30);
|
||||
});
|
||||
|
||||
it("with 11 AzureServiceClient.longRunningOperationRetryTimeout and no retry-after header", () => {
|
||||
const azureServiceClient = new AzureServiceClient(new TokenCredentials("my-fake-token"), { longRunningOperationRetryTimeout: 11 });
|
||||
const previousResponse: HttpOperationResponse = {
|
||||
request: new WebResource(),
|
||||
status: 200,
|
||||
headers: new HttpHeaders()
|
||||
};
|
||||
assert.strictEqual(getDelayInSeconds(azureServiceClient, previousResponse), 11);
|
||||
});
|
||||
|
||||
it("with no AzureServiceClient.longRunningOperationRetryTimeout value and 12 retry-after header", () => {
|
||||
const azureServiceClient = new AzureServiceClient(new TokenCredentials("my-fake-token"));
|
||||
const previousResponse: HttpOperationResponse = {
|
||||
request: new WebResource(),
|
||||
status: 200,
|
||||
headers: new HttpHeaders({ "retry-after": "12" })
|
||||
};
|
||||
assert.strictEqual(getDelayInSeconds(azureServiceClient, previousResponse), 12);
|
||||
});
|
||||
|
||||
it("with no AzureServiceClient.longRunningOperationRetryTimeout value and spam retry-after header", () => {
|
||||
const azureServiceClient = new AzureServiceClient(new TokenCredentials("my-fake-token"));
|
||||
const previousResponse: HttpOperationResponse = {
|
||||
request: new WebResource(),
|
||||
status: 200,
|
||||
headers: new HttpHeaders({ "retry-after": "spam" })
|
||||
};
|
||||
assert.strictEqual(getDelayInSeconds(azureServiceClient, previousResponse), 30);
|
||||
});
|
||||
|
||||
it("with 11 AzureServiceClient.longRunningOperationRetryTimeout and 12 retry-after header", () => {
|
||||
const azureServiceClient = new AzureServiceClient(new TokenCredentials("my-fake-token"), { longRunningOperationRetryTimeout: 11 });
|
||||
const previousResponse: HttpOperationResponse = {
|
||||
request: new WebResource(),
|
||||
status: 200,
|
||||
headers: new HttpHeaders({ "retry-after": "12" })
|
||||
};
|
||||
assert.strictEqual(getDelayInSeconds(azureServiceClient, previousResponse), 11);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -4,10 +4,37 @@
|
|||
import * as assert from "assert";
|
||||
|
||||
/**
|
||||
* Assert that the provided asyncAction throws an Error. If the expectedError is undefined, then
|
||||
* Assert that the provided syncFunction throws an Error. If the expectedError is undefined, then
|
||||
* this function will just assert that an Error was thrown. If the expectedError is defined, then
|
||||
* this function will assert that the Error that was thrown is equal to the provided expectedError.
|
||||
* @param asyncFunction The async function that is expected to thrown an Error.
|
||||
* @param syncFunction The synchronous function that is expected to thrown an Error.
|
||||
* @param expectedError The Error that is expected to be thrown.
|
||||
*/
|
||||
export function throws(syncFunction: () => void, expectedError?: ((error: Error) => void) | Error): Error {
|
||||
let thrownError: Error | undefined;
|
||||
|
||||
try {
|
||||
syncFunction();
|
||||
} catch (error) {
|
||||
thrownError = error;
|
||||
}
|
||||
|
||||
if (!thrownError) {
|
||||
assert.throws(() => { });
|
||||
} else if (expectedError instanceof Error) {
|
||||
assert.deepStrictEqual(thrownError, expectedError);
|
||||
} else if (expectedError) {
|
||||
expectedError(thrownError);
|
||||
}
|
||||
|
||||
return thrownError!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the provided asyncFunction throws an Error. If the expectedError is undefined, then
|
||||
* this function will just assert that an Error was thrown. If the expectedError is defined, then
|
||||
* this function will assert that the Error that was thrown is equal to the provided expectedError.
|
||||
* @param asyncFunction The asynchronous function that is expected to thrown an Error.
|
||||
* @param expectedError The Error that is expected to be thrown.
|
||||
*/
|
||||
export async function throwsAsync<T>(asyncFunction: (() => Promise<T>) | Promise<T>, expectedError?: ((error: Error) => void) | Error): Promise<Error> {
|
||||
|
|
Загрузка…
Ссылка в новой задаче