From ce1a1ab70d6da06cac1d47225d320602cc382c16 Mon Sep 17 00:00:00 2001 From: Deyaaeldeen Almahallawi Date: Thu, 3 Mar 2022 17:35:18 -0800 Subject: [PATCH 1/9] [core-lro] merge azureAsync and location strategies --- sdk/core/core-lro/package.json | 2 +- .../src/lroEngine/azureAsyncPolling.ts | 80 ------------------ .../core-lro/src/lroEngine/locationPolling.ts | 81 +++++++++++++++++-- sdk/core/core-lro/src/lroEngine/models.ts | 2 +- .../core-lro/src/lroEngine/requestUtils.ts | 26 +++--- .../core-lro/src/lroEngine/stateMachine.ts | 8 +- sdk/core/core-lro/test/engine.spec.ts | 8 +- .../test/utils/router/routesProcesses.ts | 15 +++- .../core-lro/test/utils/router/routesTable.ts | 10 ++- 9 files changed, 118 insertions(+), 114 deletions(-) delete mode 100644 sdk/core/core-lro/src/lroEngine/azureAsyncPolling.ts diff --git a/sdk/core/core-lro/package.json b/sdk/core/core-lro/package.json index fb1a3508d7b3..9b44a32696c1 100644 --- a/sdk/core/core-lro/package.json +++ b/sdk/core/core-lro/package.json @@ -75,7 +75,7 @@ "clean": "rimraf dist dist-* types *.log browser statistics.html coverage src/**/*.js test/**/*.js", "execute:samples": "echo skipped", "extract-api": "tsc -p . && api-extractor run --local", - "format": "prettier --write --config ../../../.prettierrc.json --ignore-path ../../../.prettierignore \"src/**/*.ts\" \"test/**/*.ts\" \"samples-dev/**/*.ts\" \"*.{js,json}\"", + "format": "prettier --write --config ../../../.prettierrc.json --ignore-path ../../../.prettierignore \"src/**/*.ts\" \"test/**/*.ts\" \"*.{js,json}\"", "integration-test:browser": "echo skipped", "integration-test:node": "echo skipped", "integration-test": "npm run integration-test:node && npm run integration-test:browser", diff --git a/sdk/core/core-lro/src/lroEngine/azureAsyncPolling.ts b/sdk/core/core-lro/src/lroEngine/azureAsyncPolling.ts deleted file mode 100644 index 08c56e62c030..000000000000 --- a/sdk/core/core-lro/src/lroEngine/azureAsyncPolling.ts +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import { - LongRunningOperation, - LroBody, - LroResourceLocationConfig, - LroResponse, - LroStatus, - RawResponse, - failureStates, - successStates, -} from "./models"; -import { isUnexpectedPollingResponse } from "./requestUtils"; - -function getResponseStatus(rawResponse: RawResponse): string { - const { status } = (rawResponse.body as LroBody) ?? {}; - return typeof status === "string" ? status.toLowerCase() : "succeeded"; -} - -function isAzureAsyncPollingDone(rawResponse: RawResponse): boolean { - const state = getResponseStatus(rawResponse); - if (isUnexpectedPollingResponse(rawResponse) || failureStates.includes(state)) { - throw new Error(`The long running operation has failed. The provisioning state: ${state}.`); - } - return successStates.includes(state); -} - -/** - * Sends a request to the URI of the provisioned resource if needed. - */ -async function sendFinalRequest( - lro: LongRunningOperation, - resourceLocation: string, - lroResourceLocationConfig?: LroResourceLocationConfig -): Promise | undefined> { - switch (lroResourceLocationConfig) { - case "original-uri": - return lro.sendPollRequest(lro.requestPath); - case "azure-async-operation": - return undefined; - case "location": - default: - return lro.sendPollRequest(resourceLocation ?? lro.requestPath); - } -} - -export function processAzureAsyncOperationResult( - lro: LongRunningOperation, - resourceLocation?: string, - lroResourceLocationConfig?: LroResourceLocationConfig -): (response: LroResponse) => LroStatus { - return (response: LroResponse): LroStatus => { - if (isAzureAsyncPollingDone(response.rawResponse)) { - if (resourceLocation === undefined) { - return { ...response, done: true }; - } else { - return { - ...response, - done: false, - next: async () => { - const finalResponse = await sendFinalRequest( - lro, - resourceLocation, - lroResourceLocationConfig - ); - return { - ...(finalResponse ?? response), - done: true, - }; - }, - }; - } - } - return { - ...response, - done: false, - }; - }; -} diff --git a/sdk/core/core-lro/src/lroEngine/locationPolling.ts b/sdk/core/core-lro/src/lroEngine/locationPolling.ts index 3c504a6ca47c..c5ee8f50bb73 100644 --- a/sdk/core/core-lro/src/lroEngine/locationPolling.ts +++ b/sdk/core/core-lro/src/lroEngine/locationPolling.ts @@ -1,18 +1,83 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { LroResponse, LroStatus, RawResponse } from "./models"; +import { + LongRunningOperation, + LroBody, + LroResourceLocationConfig, + LroResponse, + LroStatus, + RawResponse, + failureStates, + successStates, +} from "./models"; import { isUnexpectedPollingResponse } from "./requestUtils"; -function isLocationPollingDone(rawResponse: RawResponse): boolean { - return !isUnexpectedPollingResponse(rawResponse) && rawResponse.statusCode !== 202; +function getResponseStatus(rawResponse: RawResponse): string { + const { status } = (rawResponse.body as LroBody) ?? {}; + return typeof status === "string" ? status.toLowerCase() : "succeeded"; +} + +function isPollingDone(rawResponse: RawResponse): boolean { + if (isUnexpectedPollingResponse(rawResponse) || rawResponse.statusCode === 202) { + return false; + } + const state = getResponseStatus(rawResponse); + if (isUnexpectedPollingResponse(rawResponse) || failureStates.includes(state)) { + throw new Error(`The long running operation has failed. The provisioning state: ${state}.`); + } + return successStates.includes(state); +} + +/** + * Sends a request to the URI of the provisioned resource if needed. + */ +async function sendFinalRequest( + lro: LongRunningOperation, + resourceLocation: string, + lroResourceLocationConfig?: LroResourceLocationConfig +): Promise | undefined> { + switch (lroResourceLocationConfig) { + case "original-uri": + return lro.sendPollRequest(lro.requestPath); + case "azure-async-operation": + return undefined; + case "location": + default: + return lro.sendPollRequest(resourceLocation ?? lro.requestPath); + } } export function processLocationPollingOperationResult( - response: LroResponse -): LroStatus { - return { - ...response, - done: isLocationPollingDone(response.rawResponse), + lro: LongRunningOperation, + resourceLocation?: string, + lroResourceLocationConfig?: LroResourceLocationConfig +): (response: LroResponse) => LroStatus { + return (response: LroResponse): LroStatus => { + if (isPollingDone(response.rawResponse)) { + if (resourceLocation === undefined) { + return { ...response, done: true }; + } else { + return { + ...response, + done: false, + next: async () => { + const finalResponse = await sendFinalRequest( + lro, + resourceLocation, + lroResourceLocationConfig + ); + return { + ...(finalResponse ?? response), + done: true, + }; + }, + }; + } + } + return { + ...response, + done: false, + }; }; } diff --git a/sdk/core/core-lro/src/lroEngine/models.ts b/sdk/core/core-lro/src/lroEngine/models.ts index 96981c53e70f..dcd3d26a2dcf 100644 --- a/sdk/core/core-lro/src/lroEngine/models.ts +++ b/sdk/core/core-lro/src/lroEngine/models.ts @@ -83,7 +83,7 @@ export interface LroResponse { } /** The type of which LRO implementation being followed by a specific API. */ -export type LroMode = "AzureAsync" | "Location" | "Body"; +export type LroMode = "Location" | "Body"; /** * The configuration of a LRO to determine how to perform polling and checking whether the operation has completed. diff --git a/sdk/core/core-lro/src/lroEngine/requestUtils.ts b/sdk/core/core-lro/src/lroEngine/requestUtils.ts index bc25db6ef873..1e01fca2f76c 100644 --- a/sdk/core/core-lro/src/lroEngine/requestUtils.ts +++ b/sdk/core/core-lro/src/lroEngine/requestUtils.ts @@ -12,8 +12,8 @@ import { LroConfig, RawResponse } from "./models"; export function getPollingUrl(rawResponse: RawResponse, defaultPath: string): string { return ( getAzureAsyncOperation(rawResponse) ?? - getLocation(rawResponse) ?? getOperationLocation(rawResponse) ?? + getLocation(rawResponse) ?? defaultPath ); } @@ -35,23 +35,21 @@ export function inferLroMode( requestMethod: string, rawResponse: RawResponse ): LroConfig { - if (getAzureAsyncOperation(rawResponse) !== undefined) { + const hasAzureAsync = getAzureAsyncOperation(rawResponse) !== undefined; + const hasLocation = getLocation(rawResponse) !== undefined; + const hasOpLocation = getOperationLocation(rawResponse) !== undefined; + if (hasAzureAsync || hasLocation || hasOpLocation) { return { - mode: "AzureAsync", + mode: "Location", resourceLocation: - requestMethod === "PUT" - ? requestPath - : requestMethod === "POST" || requestMethod === "PATCH" - ? getLocation(rawResponse) + hasAzureAsync || hasOpLocation + ? requestMethod === "PUT" + ? requestPath + : requestMethod === "POST" || requestMethod === "PATCH" + ? getLocation(rawResponse) + : undefined : undefined, }; - } else if ( - getLocation(rawResponse) !== undefined || - getOperationLocation(rawResponse) !== undefined - ) { - return { - mode: "Location", - }; } else if (["PUT", "PATCH"].includes(requestMethod)) { return { mode: "Body", diff --git a/sdk/core/core-lro/src/lroEngine/stateMachine.ts b/sdk/core/core-lro/src/lroEngine/stateMachine.ts index 44cc9b068bd3..bb4d17102448 100644 --- a/sdk/core/core-lro/src/lroEngine/stateMachine.ts +++ b/sdk/core/core-lro/src/lroEngine/stateMachine.ts @@ -14,7 +14,6 @@ import { import { getPollingUrl, inferLroMode, isUnexpectedInitialResponse } from "./requestUtils"; import { isBodyPollingDone, processBodyPollingOperationResult } from "./bodyPolling"; import { logger } from "./logger"; -import { processAzureAsyncOperationResult } from "./azureAsyncPolling"; import { processLocationPollingOperationResult } from "./locationPolling"; import { processPassthroughOperationResult } from "./passthrough"; @@ -27,16 +26,13 @@ export function createGetLroStatusFromResponse( lroResourceLocationConfig?: LroResourceLocationConfig ): GetLroStatusFromResponse { switch (config.mode) { - case "AzureAsync": { - return processAzureAsyncOperationResult( + case "Location": { + return processLocationPollingOperationResult( lroPrimitives, config.resourceLocation, lroResourceLocationConfig ); } - case "Location": { - return processLocationPollingOperationResult; - } case "Body": { return processBodyPollingOperationResult; } diff --git a/sdk/core/core-lro/test/engine.spec.ts b/sdk/core/core-lro/test/engine.spec.ts index f5dc6aed5320..43f567fcd7aa 100644 --- a/sdk/core/core-lro/test/engine.spec.ts +++ b/sdk/core/core-lro/test/engine.spec.ts @@ -152,6 +152,12 @@ describe("Lro Engine", function () { const result = await runMockedLro("DELETE", "/delete/provisioning/202/deleting/200/canceled"); assert.equal(result.properties?.provisioningState, "Canceled"); }); + + it("should handle postOperationDoubleHeadersFinalLocationGet", async () => { + const result = await runMockedLro("POST", "/LROPostOperationDoubleHeadersFinalLocationGet"); + assert.equal(result.id, "100"); + assert.equal(result.name, "foo"); + }); }); describe("Passthrough strategy", () => { @@ -162,7 +168,7 @@ describe("Lro Engine", function () { }); describe("Azure Async Operation Strategy", () => { - it("should handle postDoubleHeadersFinalLocationGet", async () => { + it("should handle postAsyncDoubleHeadersFinalLocationGet", async () => { const result = await runMockedLro("POST", "/LROPostDoubleHeadersFinalLocationGet"); assert.equal(result.id, "100"); assert.equal(result.name, "foo"); diff --git a/sdk/core/core-lro/test/utils/router/routesProcesses.ts b/sdk/core/core-lro/test/utils/router/routesProcesses.ts index cbb72ce39408..76313e7236a9 100644 --- a/sdk/core/core-lro/test/utils/router/routesProcesses.ts +++ b/sdk/core/core-lro/test/utils/router/routesProcesses.ts @@ -77,7 +77,7 @@ export function delete204Succeeded(request: PipelineRequest): PipelineResponse { return buildResponse(request, 204); } -export function postDoubleHeadersFinalLocationGet(request: PipelineRequest): PipelineResponse { +export function postAsyncDoubleHeadersFinalLocationGet(request: PipelineRequest): PipelineResponse { return { request: request, status: 202, @@ -88,6 +88,19 @@ export function postDoubleHeadersFinalLocationGet(request: PipelineRequest): Pip }; } +export function postOperationDoubleHeadersFinalLocationGet( + request: PipelineRequest +): PipelineResponse { + return { + request: request, + status: 202, + headers: createHttpHeaders({ + "Operation-Location": `/LROPostDoubleHeadersFinalLocationGet/asyncOperationUrl`, + Location: `/LROPostDoubleHeadersFinalLocationGet/location`, + }), + }; +} + export function postDoubleHeadersFinalLocationGetAsyncOperationUrl( request: PipelineRequest ): PipelineResponse { diff --git a/sdk/core/core-lro/test/utils/router/routesTable.ts b/sdk/core/core-lro/test/utils/router/routesTable.ts index d8e4de19c3cc..edec7709fb33 100644 --- a/sdk/core/core-lro/test/utils/router/routesTable.ts +++ b/sdk/core/core-lro/test/utils/router/routesTable.ts @@ -63,7 +63,7 @@ import { postDoubleHeadersFinalAzureHeaderGetAsyncOperationUrl, postDoubleHeadersFinalAzureHeaderGetDefault, postDoubleHeadersFinalAzureHeaderGetLocation, - postDoubleHeadersFinalLocationGet, + postAsyncDoubleHeadersFinalLocationGet, postDoubleHeadersFinalLocationGetAsyncOperationUrl, postDoubleHeadersFinalLocationGetLocation, postList, @@ -85,6 +85,7 @@ import { putSubresourceasyncOperationresults123, putasyncNoheader201200, putasyncNoheaderOperationresults123, + postOperationDoubleHeadersFinalLocationGet, } from "./routesProcesses"; interface LroRoute { @@ -127,7 +128,12 @@ export const routes: LroRoute[] = [ { method: "POST", path: "/LROPostDoubleHeadersFinalLocationGet", - process: postDoubleHeadersFinalLocationGet, + process: postAsyncDoubleHeadersFinalLocationGet, + }, + { + method: "POST", + path: "/LROPostOperationDoubleHeadersFinalLocationGet", + process: postOperationDoubleHeadersFinalLocationGet, }, { method: "GET", From 9668ada2894d26e2783547467f69f72940ff5554 Mon Sep 17 00:00:00 2001 From: Deyaaeldeen Almahallawi Date: Thu, 3 Mar 2022 18:54:34 -0800 Subject: [PATCH 2/9] add changelog entry --- sdk/core/core-lro/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sdk/core/core-lro/CHANGELOG.md b/sdk/core/core-lro/CHANGELOG.md index 005746aa304d..a457574f8665 100644 --- a/sdk/core/core-lro/CHANGELOG.md +++ b/sdk/core/core-lro/CHANGELOG.md @@ -8,6 +8,8 @@ ### Bugs Fixed +- Fix polling so that resources created in a different URL will be retrieved once polling is done. [PR #20656](https://github.com/Azure/azure-sdk-for-js/pull/20656) + ### Other Changes ## 2.2.3 (2022-01-06) From 9baa81d175989598b5785e762741dd1d71dd3d80 Mon Sep 17 00:00:00 2001 From: Deyaaeldeen Almahallawi Date: Thu, 3 Mar 2022 19:20:26 -0800 Subject: [PATCH 3/9] fix linting --- sdk/core/core-lro/test/utils/router/routesTable.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/core/core-lro/test/utils/router/routesTable.ts b/sdk/core/core-lro/test/utils/router/routesTable.ts index edec7709fb33..397b2f4bc777 100644 --- a/sdk/core/core-lro/test/utils/router/routesTable.ts +++ b/sdk/core/core-lro/test/utils/router/routesTable.ts @@ -59,14 +59,15 @@ import { nonretryerrorPutasyncRetryFailedOperationResults400, patchAsync202200, patchAsyncOperationresults123, + postAsyncDoubleHeadersFinalLocationGet, postDoubleHeadersFinalAzureHeaderGet, postDoubleHeadersFinalAzureHeaderGetAsyncOperationUrl, postDoubleHeadersFinalAzureHeaderGetDefault, postDoubleHeadersFinalAzureHeaderGetLocation, - postAsyncDoubleHeadersFinalLocationGet, postDoubleHeadersFinalLocationGetAsyncOperationUrl, postDoubleHeadersFinalLocationGetLocation, postList, + postOperationDoubleHeadersFinalLocationGet, postPayload200, put200Succeeded, put201Succeeded, @@ -85,7 +86,6 @@ import { putSubresourceasyncOperationresults123, putasyncNoheader201200, putasyncNoheaderOperationresults123, - postOperationDoubleHeadersFinalLocationGet, } from "./routesProcesses"; interface LroRoute { From 1f291675cd9e3963663847b51e227396c7fd46b7 Mon Sep 17 00:00:00 2001 From: Deyaaeldeen Almahallawi Date: Thu, 3 Mar 2022 19:21:00 -0800 Subject: [PATCH 4/9] remove unneeded isDone logic from TA --- .../ai-text-analytics/src/analyzeLro.ts | 15 --------------- .../ai-text-analytics/src/textAnalyticsClient.ts | 2 -- 2 files changed, 17 deletions(-) diff --git a/sdk/textanalytics/ai-text-analytics/src/analyzeLro.ts b/sdk/textanalytics/ai-text-analytics/src/analyzeLro.ts index ddc629d980c8..3f4f6d9d4c0d 100644 --- a/sdk/textanalytics/ai-text-analytics/src/analyzeLro.ts +++ b/sdk/textanalytics/ai-text-analytics/src/analyzeLro.ts @@ -171,21 +171,6 @@ export class AnalyzeLro implements LongRunningOperation Date: Fri, 4 Mar 2022 11:07:21 -0800 Subject: [PATCH 5/9] adding a couple more tests --- sdk/core/core-lro/test/engine.spec.ts | 21 ++++- .../test/utils/router/routesProcesses.ts | 87 ++++++++++--------- .../core-lro/test/utils/router/routesTable.ts | 26 ++++-- 3 files changed, 85 insertions(+), 49 deletions(-) diff --git a/sdk/core/core-lro/test/engine.spec.ts b/sdk/core/core-lro/test/engine.spec.ts index 43f567fcd7aa..8150f774c25c 100644 --- a/sdk/core/core-lro/test/engine.spec.ts +++ b/sdk/core/core-lro/test/engine.spec.ts @@ -154,10 +154,29 @@ describe("Lro Engine", function () { }); it("should handle postOperationDoubleHeadersFinalLocationGet", async () => { - const result = await runMockedLro("POST", "/LROPostOperationDoubleHeadersFinalLocationGet"); + const result = await runMockedLro("POST", "/LROPostLocationDoubleHeadersFinalLocationGet"); assert.equal(result.id, "100"); assert.equal(result.name, "foo"); }); + + it("should handle postDoubleHeadersFinalAzureHeaderGet", async () => { + const result = await runMockedLro( + "POST", + "/LROLocationPostDoubleHeadersFinalAzureHeaderGet", + undefined, + "azure-async-operation" + ); + assert.equal(result.id, "100"); + }); + + it("should handle postDoubleHeadersFinalAzureHeaderGetDefault", async () => { + const result = await runMockedLro( + "POST", + "/LROLocationPostDoubleHeadersFinalAzureHeaderGetDefault" + ); + assert.equal(result.id, "100"); + assert.equal(result.statusCode, 200); + }); }); describe("Passthrough strategy", () => { diff --git a/sdk/core/core-lro/test/utils/router/routesProcesses.ts b/sdk/core/core-lro/test/utils/router/routesProcesses.ts index 76313e7236a9..403add209514 100644 --- a/sdk/core/core-lro/test/utils/router/routesProcesses.ts +++ b/sdk/core/core-lro/test/utils/router/routesProcesses.ts @@ -77,29 +77,26 @@ export function delete204Succeeded(request: PipelineRequest): PipelineResponse { return buildResponse(request, 204); } -export function postAsyncDoubleHeadersFinalLocationGet(request: PipelineRequest): PipelineResponse { - return { - request: request, - status: 202, - headers: createHttpHeaders({ - "Azure-AsyncOperation": `/LROPostDoubleHeadersFinalLocationGet/asyncOperationUrl`, +function postDoubleHeadersFinalLocationGet( + headerName: string +): (request: PipelineRequest) => PipelineResponse { + return function (request: PipelineRequest): PipelineResponse { + const headers = createHttpHeaders({ Location: `/LROPostDoubleHeadersFinalLocationGet/location`, - }), + }); + headers.set(headerName, `/LROPostDoubleHeadersFinalLocationGet/asyncOperationUrl`); + return { + request: request, + status: 202, + headers: headers, + }; }; } -export function postOperationDoubleHeadersFinalLocationGet( - request: PipelineRequest -): PipelineResponse { - return { - request: request, - status: 202, - headers: createHttpHeaders({ - "Operation-Location": `/LROPostDoubleHeadersFinalLocationGet/asyncOperationUrl`, - Location: `/LROPostDoubleHeadersFinalLocationGet/location`, - }), - }; -} +export const postAsyncDoubleHeadersFinalLocationGet = + postDoubleHeadersFinalLocationGet("Azure-AsyncOperation"); +export const postLocationDoubleHeadersFinalLocationGet = + postDoubleHeadersFinalLocationGet("Operation-Location"); export function postDoubleHeadersFinalLocationGetAsyncOperationUrl( request: PipelineRequest @@ -113,18 +110,23 @@ export function postDoubleHeadersFinalLocationGetLocation( return buildResponse(request, 200, `{ "id": "100", "name": "foo" }`); } -export function postDoubleHeadersFinalAzureHeaderGet(request: PipelineRequest): PipelineResponse { - return buildResponse( - request, - 202, - "", - createHttpHeaders({ - "Azure-AsyncOperation": `/LROPostDoubleHeadersFinalAzureHeaderGet/asyncOperationUrl`, - Location: `/LROPostDoubleHeadersFinalAzureHeaderGet/location`, - }) - ); +function postDoubleHeadersFinalAzureHeaderGet( + headerName: string +): (request: PipelineRequest) => PipelineResponse { + const headers = createHttpHeaders({ + Location: `/LROPostDoubleHeadersFinalAzureHeaderGet/location`, + }); + headers.set(headerName, `/LROPostDoubleHeadersFinalAzureHeaderGet/asyncOperationUrl`); + return function (request: PipelineRequest): PipelineResponse { + return buildResponse(request, 202, "", headers); + }; } +export const postAsyncDoubleHeadersFinalAzureHeaderGet = + postDoubleHeadersFinalAzureHeaderGet("Azure-AsyncOperation"); +export const postLocationDoubleHeadersFinalAzureHeaderGet = + postDoubleHeadersFinalAzureHeaderGet("Operation-Location"); + export function postDoubleHeadersFinalAzureHeaderGetAsyncOperationUrl( request: PipelineRequest ): PipelineResponse { @@ -152,20 +154,23 @@ export function getPayload200(request: PipelineRequest): PipelineResponse { return buildResponse(request, 200, `{"id":"1", "name":"product"}`); } -export function postDoubleHeadersFinalAzureHeaderGetDefault( - request: PipelineRequest -): PipelineResponse { - return buildResponse( - request, - 202, - "", - createHttpHeaders({ - Location: "/LROPostDoubleHeadersFinalAzureHeaderGetDefault/location", - "Azure-AsyncOperation": "/LROPostDoubleHeadersFinalAzureHeaderGetDefault/asyncOperationUrl", - }) - ); +function postDoubleHeadersFinalAzureHeaderGetDefault( + headerName: string +): (request: PipelineRequest) => PipelineResponse { + const headers = createHttpHeaders({ + Location: "/LROPostDoubleHeadersFinalAzureHeaderGetDefault/location", + }); + headers.set(headerName, "/LROPostDoubleHeadersFinalAzureHeaderGetDefault/asyncOperationUrl"); + return function (request: PipelineRequest): PipelineResponse { + return buildResponse(request, 202, "", headers); + }; } +export const postAsyncDoubleHeadersFinalAzureHeaderGetDefault = + postDoubleHeadersFinalAzureHeaderGetDefault("Azure-AsyncOperation"); +export const postLocationDoubleHeadersFinalAzureHeaderGetDefault = + postDoubleHeadersFinalAzureHeaderGetDefault("Operation-Location"); + export function getDoubleHeadersFinalAzureHeaderGetDefaultAsyncOperationUrl( request: PipelineRequest ): PipelineResponse { diff --git a/sdk/core/core-lro/test/utils/router/routesTable.ts b/sdk/core/core-lro/test/utils/router/routesTable.ts index 397b2f4bc777..0c521961af4c 100644 --- a/sdk/core/core-lro/test/utils/router/routesTable.ts +++ b/sdk/core/core-lro/test/utils/router/routesTable.ts @@ -59,15 +59,17 @@ import { nonretryerrorPutasyncRetryFailedOperationResults400, patchAsync202200, patchAsyncOperationresults123, + postAsyncDoubleHeadersFinalAzureHeaderGet, + postAsyncDoubleHeadersFinalAzureHeaderGetDefault, postAsyncDoubleHeadersFinalLocationGet, - postDoubleHeadersFinalAzureHeaderGet, postDoubleHeadersFinalAzureHeaderGetAsyncOperationUrl, - postDoubleHeadersFinalAzureHeaderGetDefault, postDoubleHeadersFinalAzureHeaderGetLocation, postDoubleHeadersFinalLocationGetAsyncOperationUrl, postDoubleHeadersFinalLocationGetLocation, postList, - postOperationDoubleHeadersFinalLocationGet, + postLocationDoubleHeadersFinalAzureHeaderGet, + postLocationDoubleHeadersFinalAzureHeaderGetDefault, + postLocationDoubleHeadersFinalLocationGet, postPayload200, put200Succeeded, put201Succeeded, @@ -132,8 +134,8 @@ export const routes: LroRoute[] = [ }, { method: "POST", - path: "/LROPostOperationDoubleHeadersFinalLocationGet", - process: postOperationDoubleHeadersFinalLocationGet, + path: "/LROPostLocationDoubleHeadersFinalLocationGet", + process: postLocationDoubleHeadersFinalLocationGet, }, { method: "GET", @@ -148,7 +150,12 @@ export const routes: LroRoute[] = [ { method: "POST", path: "/LROPostDoubleHeadersFinalAzureHeaderGet", - process: postDoubleHeadersFinalAzureHeaderGet, + process: postAsyncDoubleHeadersFinalAzureHeaderGet, + }, + { + method: "POST", + path: "/LROLocationPostDoubleHeadersFinalAzureHeaderGet", + process: postLocationDoubleHeadersFinalAzureHeaderGet, }, { method: "GET", @@ -165,7 +172,12 @@ export const routes: LroRoute[] = [ { method: "POST", path: "/LROPostDoubleHeadersFinalAzureHeaderGetDefault", - process: postDoubleHeadersFinalAzureHeaderGetDefault, + process: postAsyncDoubleHeadersFinalAzureHeaderGetDefault, + }, + { + method: "POST", + path: "/LROLocationPostDoubleHeadersFinalAzureHeaderGetDefault", + process: postLocationDoubleHeadersFinalAzureHeaderGetDefault, }, { method: "GET", From b3ed1e948300d76fa50ebe856e5032821dd0fd2f Mon Sep 17 00:00:00 2001 From: Deyaaeldeen Almahallawi Date: Fri, 4 Mar 2022 12:12:43 -0800 Subject: [PATCH 6/9] address feedback --- .../core-lro/src/lroEngine/locationPolling.ts | 8 ++---- .../core-lro/src/lroEngine/requestUtils.ts | 25 +++++++++++++++---- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/sdk/core/core-lro/src/lroEngine/locationPolling.ts b/sdk/core/core-lro/src/lroEngine/locationPolling.ts index c5ee8f50bb73..b4e7c92cda45 100644 --- a/sdk/core/core-lro/src/lroEngine/locationPolling.ts +++ b/sdk/core/core-lro/src/lroEngine/locationPolling.ts @@ -13,16 +13,12 @@ import { } from "./models"; import { isUnexpectedPollingResponse } from "./requestUtils"; -function getResponseStatus(rawResponse: RawResponse): string { - const { status } = (rawResponse.body as LroBody) ?? {}; - return typeof status === "string" ? status.toLowerCase() : "succeeded"; -} - function isPollingDone(rawResponse: RawResponse): boolean { if (isUnexpectedPollingResponse(rawResponse) || rawResponse.statusCode === 202) { return false; } - const state = getResponseStatus(rawResponse); + const { status } = (rawResponse.body as LroBody) ?? {}; + const state = typeof status === "string" ? status.toLowerCase() : "succeeded"; if (isUnexpectedPollingResponse(rawResponse) || failureStates.includes(state)) { throw new Error(`The long running operation has failed. The provisioning state: ${state}.`); } diff --git a/sdk/core/core-lro/src/lroEngine/requestUtils.ts b/sdk/core/core-lro/src/lroEngine/requestUtils.ts index 1e01fca2f76c..bdc4f01bd402 100644 --- a/sdk/core/core-lro/src/lroEngine/requestUtils.ts +++ b/sdk/core/core-lro/src/lroEngine/requestUtils.ts @@ -30,6 +30,25 @@ function getAzureAsyncOperation(rawResponse: RawResponse): string | undefined { return rawResponse.headers["azure-asyncoperation"]; } +function findResourceLocation( + requestMethod: string, + rawResponse: RawResponse, + requestPath: string +): string | undefined { + switch (requestMethod) { + case "PUT": { + return requestPath; + } + case "POST": + case "PATCH": { + return getLocation(rawResponse); + } + default: { + return undefined; + } + } +} + export function inferLroMode( requestPath: string, requestMethod: string, @@ -43,11 +62,7 @@ export function inferLroMode( mode: "Location", resourceLocation: hasAzureAsync || hasOpLocation - ? requestMethod === "PUT" - ? requestPath - : requestMethod === "POST" || requestMethod === "PATCH" - ? getLocation(rawResponse) - : undefined + ? findResourceLocation(requestMethod, rawResponse, requestPath) : undefined, }; } else if (["PUT", "PATCH"].includes(requestMethod)) { From 468fbc743620d5faae8c02a40444c3fd23042fcb Mon Sep 17 00:00:00 2001 From: Deyaaeldeen Almahallawi Date: Fri, 4 Mar 2022 14:51:34 -0800 Subject: [PATCH 7/9] address feedback --- sdk/core/core-lro/src/lroEngine/requestUtils.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/sdk/core/core-lro/src/lroEngine/requestUtils.ts b/sdk/core/core-lro/src/lroEngine/requestUtils.ts index bdc4f01bd402..c1a90a5f8bea 100644 --- a/sdk/core/core-lro/src/lroEngine/requestUtils.ts +++ b/sdk/core/core-lro/src/lroEngine/requestUtils.ts @@ -54,16 +54,17 @@ export function inferLroMode( requestMethod: string, rawResponse: RawResponse ): LroConfig { - const hasAzureAsync = getAzureAsyncOperation(rawResponse) !== undefined; - const hasLocation = getLocation(rawResponse) !== undefined; - const hasOpLocation = getOperationLocation(rawResponse) !== undefined; - if (hasAzureAsync || hasLocation || hasOpLocation) { + if ( + getAzureAsyncOperation(rawResponse) !== undefined || + getOperationLocation(rawResponse) !== undefined + ) { + return { + mode: "Location", + resourceLocation: findResourceLocation(requestMethod, rawResponse, requestPath), + }; + } else if (getLocation(rawResponse) !== undefined) { return { mode: "Location", - resourceLocation: - hasAzureAsync || hasOpLocation - ? findResourceLocation(requestMethod, rawResponse, requestPath) - : undefined, }; } else if (["PUT", "PATCH"].includes(requestMethod)) { return { From d8d592d8b79f8f5be7234b2715f24d78e352b685 Mon Sep 17 00:00:00 2001 From: Deyaaeldeen Almahallawi Date: Fri, 4 Mar 2022 17:21:58 -0800 Subject: [PATCH 8/9] merge tests for operation location and asyncoperation --- sdk/core/core-lro/package.json | 1 + sdk/core/core-lro/test/engine.spec.ts | 421 +++++++++--------- .../core-lro/test/utils/router/paramRoutes.ts | 67 ++- .../test/utils/router/routesProcesses.ts | 144 +++--- .../core-lro/test/utils/router/routesTable.ts | 23 +- 5 files changed, 354 insertions(+), 302 deletions(-) diff --git a/sdk/core/core-lro/package.json b/sdk/core/core-lro/package.json index 9b44a32696c1..7d7057f57d2c 100644 --- a/sdk/core/core-lro/package.json +++ b/sdk/core/core-lro/package.json @@ -101,6 +101,7 @@ "@azure/core-rest-pipeline": "^1.1.0", "@azure/eslint-plugin-azure-sdk": "^3.0.0", "@azure/dev-tool": "^1.0.0", + "@azure/test-utils": "^1.0.0", "@microsoft/api-extractor": "^7.18.11", "@types/chai": "^4.1.6", "@types/mocha": "^7.0.2", diff --git a/sdk/core/core-lro/test/engine.spec.ts b/sdk/core/core-lro/test/engine.spec.ts index 8150f774c25c..d31873ed0b7d 100644 --- a/sdk/core/core-lro/test/engine.spec.ts +++ b/sdk/core/core-lro/test/engine.spec.ts @@ -4,67 +4,22 @@ import { mockedPoller, runMockedLro } from "./utils/router"; import { RawResponse } from "../src/lroEngine/models"; import { assert } from "chai"; +import { matrix } from "@azure/test-utils"; describe("Lro Engine", function () { - it("put201Succeeded", async function () { - const result = await runMockedLro("PUT", "/put/201/succeeded"); - assert.equal(result.id, "100"); - assert.equal(result.name, "foo"); - assert.equal(result.properties?.provisioningState, "Succeeded"); - }); - - describe("BodyPolling Strategy", () => { - it("put200Succeeded", async function () { - const result = await runMockedLro("PUT", "/put/200/succeeded"); - assert.equal(result.properties?.provisioningState, "Succeeded"); - }); - - it("should handle initial response with terminal state without provisioning State", async () => { - const result = await runMockedLro("PUT", "/put/200/succeeded/nostate"); - assert.deepEqual(result.id, "100"); - assert.deepEqual(result.name, "foo"); - }); - - it("should handle initial response creating followed by success through an Azure Resource", async () => { - const result = await runMockedLro("PUT", "/put/201/creating/succeeded/200"); - assert.deepEqual(result.properties?.provisioningState, "Succeeded"); - assert.deepEqual(result.id, "100"); - assert.deepEqual(result.name, "foo"); - }); - - it("should handle put200Acceptedcanceled200", async () => { - try { - await runMockedLro("PUT", "/put/200/accepted/canceled/200"); - throw new Error("should have thrown instead"); - } catch (e) { - assert.equal( - e.message, - "The long running operation has failed. The provisioning state: canceled." - ); - } - }); - - it("should handle put200UpdatingSucceeded204", async () => { - const result = await runMockedLro("PUT", "/put/200/updating/succeeded/200"); - assert.deepEqual(result.properties?.provisioningState, "Succeeded"); - assert.deepEqual(result.id, "100"); - assert.deepEqual(result.name, "foo"); + describe("No polling", () => { + it("should handle delete204Succeeded", async () => { + const response = await runMockedLro("DELETE", "/delete/204/succeeded"); + assert.equal(response.statusCode, 204); }); - it("should handle put201CreatingFailed200", async () => { - try { - await runMockedLro("PUT", "/put/201/created/failed/200"); - throw new Error("should have thrown instead"); - } catch (e) { - assert.equal( - e.message, - "The long running operation has failed. The provisioning state: failed." - ); - } + it("put201Succeeded", async function () { + const result = await runMockedLro("PUT", "/put/201/succeeded"); + assert.equal(result.id, "100"); + assert.equal(result.name, "foo"); + assert.equal(result.properties?.provisioningState, "Succeeded"); }); - }); - describe("Location Strategy", () => { it("should handle post202Retry200", async () => { const response = await runMockedLro("POST", "/post/202/retry/200"); assert.equal(response.statusCode, 200); @@ -152,82 +107,30 @@ describe("Lro Engine", function () { const result = await runMockedLro("DELETE", "/delete/provisioning/202/deleting/200/canceled"); assert.equal(result.properties?.provisioningState, "Canceled"); }); - - it("should handle postOperationDoubleHeadersFinalLocationGet", async () => { - const result = await runMockedLro("POST", "/LROPostLocationDoubleHeadersFinalLocationGet"); - assert.equal(result.id, "100"); - assert.equal(result.name, "foo"); - }); - - it("should handle postDoubleHeadersFinalAzureHeaderGet", async () => { - const result = await runMockedLro( - "POST", - "/LROLocationPostDoubleHeadersFinalAzureHeaderGet", - undefined, - "azure-async-operation" - ); - assert.equal(result.id, "100"); - }); - - it("should handle postDoubleHeadersFinalAzureHeaderGetDefault", async () => { - const result = await runMockedLro( - "POST", - "/LROLocationPostDoubleHeadersFinalAzureHeaderGetDefault" - ); - assert.equal(result.id, "100"); - assert.equal(result.statusCode, 200); - }); }); - describe("Passthrough strategy", () => { - it("should handle delete204Succeeded", async () => { - const response = await runMockedLro("DELETE", "/delete/204/succeeded"); - assert.equal(response.statusCode, 204); - }); - }); - - describe("Azure Async Operation Strategy", () => { - it("should handle postAsyncDoubleHeadersFinalLocationGet", async () => { - const result = await runMockedLro("POST", "/LROPostDoubleHeadersFinalLocationGet"); - assert.equal(result.id, "100"); - assert.equal(result.name, "foo"); - }); - - it("should handle postDoubleHeadersFinalAzureHeaderGet", async () => { - const result = await runMockedLro( - "POST", - "/LROPostDoubleHeadersFinalAzureHeaderGet", - undefined, - "azure-async-operation" - ); - assert.equal(result.id, "100"); - }); - - it("should handle post200WithPayload", async () => { - const result = await runMockedLro("POST", "/post/payload/200"); - assert.equal(result.id, "1"); - assert.equal(result.name, "product"); - }); - - it("should handle postDoubleHeadersFinalAzureHeaderGetDefault", async () => { - const result = await runMockedLro("POST", "/LROPostDoubleHeadersFinalAzureHeaderGetDefault"); - assert.equal(result.id, "100"); - assert.equal(result.statusCode, 200); + describe("Polling from body", () => { + it("put200Succeeded", async function () { + const result = await runMockedLro("PUT", "/put/200/succeeded"); + assert.equal(result.properties?.provisioningState, "Succeeded"); }); - it("should handle deleteAsyncRetrySucceeded", async () => { - const response = await runMockedLro("DELETE", "/deleteasync/retry/succeeded"); - assert.equal(response.statusCode, 200); + it("should handle initial response with terminal state without provisioning State", async () => { + const result = await runMockedLro("PUT", "/put/200/succeeded/nostate"); + assert.deepEqual(result.id, "100"); + assert.deepEqual(result.name, "foo"); }); - it("should handle deleteAsyncNoRetrySucceeded", async () => { - const response = await runMockedLro("DELETE", "/deleteasync/noretry/succeeded"); - assert.equal(response.statusCode, 200); + it("should handle initial response creating followed by success through an Azure Resource", async () => { + const result = await runMockedLro("PUT", "/put/201/creating/succeeded/200"); + assert.deepEqual(result.properties?.provisioningState, "Succeeded"); + assert.deepEqual(result.id, "100"); + assert.deepEqual(result.name, "foo"); }); - it("should handle deleteAsyncRetrycanceled", async () => { + it("should handle put200Acceptedcanceled200", async () => { try { - await runMockedLro("DELETE", "/deleteasync/retry/canceled"); + await runMockedLro("PUT", "/put/200/accepted/canceled/200"); throw new Error("should have thrown instead"); } catch (e) { assert.equal( @@ -237,9 +140,16 @@ describe("Lro Engine", function () { } }); - it("should handle DeleteAsyncRetryFailed", async () => { + it("should handle put200UpdatingSucceeded204", async () => { + const result = await runMockedLro("PUT", "/put/200/updating/succeeded/200"); + assert.deepEqual(result.properties?.provisioningState, "Succeeded"); + assert.deepEqual(result.id, "100"); + assert.deepEqual(result.name, "foo"); + }); + + it("should handle put201CreatingFailed200", async () => { try { - await runMockedLro("DELETE", "/deleteasync/retry/failed"); + await runMockedLro("PUT", "/put/201/created/failed/200"); throw new Error("should have thrown instead"); } catch (e) { assert.equal( @@ -249,119 +159,192 @@ describe("Lro Engine", function () { } }); - it("should handle putAsyncRetrySucceeded", async () => { - const result = await runMockedLro("PUT", "/putasync/noretry/succeeded"); - assert.equal(result.id, "100"); - assert.equal(result.name, "foo"); - assert.equal(result.properties?.provisioningState, "Succeeded"); + it("should handle post200WithPayload", async () => { + const result = await runMockedLro("POST", "/post/payload/200"); + assert.equal(result.id, "1"); + assert.equal(result.name, "product"); }); + }); - it("should handle put201Succeeded", async () => { - const result = await runMockedLro("PUT", "/put/201/succeeded"); - assert.equal(result.id, "100"); - assert.equal(result.name, "foo"); - assert.equal(result.properties?.provisioningState, "Succeeded"); - }); + matrix([["async", "location"]] as const, async function (rootPrefix: string) { + describe(`Polling from ${ + rootPrefix === "async" ? "Azure-AsyncOperation" : "Operation-Location" + }`, function () { + const rootPrefix1 = + rootPrefix === "location" ? rootPrefix.charAt(0).toUpperCase() + rootPrefix.slice(1) : ""; + + it("should handle postDoubleHeadersFinalLocationGet", async () => { + const result = await runMockedLro( + "POST", + `/LROPost${rootPrefix1}DoubleHeadersFinalLocationGet` + ); + assert.equal(result.id, "100"); + assert.equal(result.name, "foo"); + }); - it("should handle post202List", async () => { - const result = await runMockedLro("POST", "/list"); - assert.equal((result as any)[0].id, "100"); - assert.equal((result as any)[0].name, "foo"); - }); + it("should handle postDoubleHeadersFinalAzureHeaderGet", async () => { + const result = await runMockedLro( + "POST", + `/LRO${rootPrefix1}PostDoubleHeadersFinalAzureHeaderGet`, + undefined, + "azure-async-operation" + ); + assert.equal(result.id, "100"); + }); - it("should handle putAsyncRetryFailed", async () => { - try { - await runMockedLro("PUT", "/putasync/retry/failed"); - throw new Error("should have thrown instead"); - } catch (e) { - assert.equal( - e.message, - "The long running operation has failed. The provisioning state: failed." + it("should handle postDoubleHeadersFinalAzureHeaderGetDefault", async () => { + const result = await runMockedLro( + "POST", + `/LRO${rootPrefix1}PostDoubleHeadersFinalAzureHeaderGetDefault` ); - } - }); + assert.equal(result.id, "100"); + assert.equal(result.statusCode, 200); + }); - it("should handle putAsyncNonResource", async () => { - const result = await runMockedLro("PUT", "/putnonresourceasync/202/200"); - assert.equal(result.name, "sku"); - assert.equal(result.id, "100"); - }); + it("should handle deleteAsyncRetrySucceeded", async () => { + const response = await runMockedLro("DELETE", `/delete${rootPrefix}/retry/succeeded`); + assert.equal(response.statusCode, 200); + }); - it("should handle patchAsync", async () => { - const result = await runMockedLro("PATCH", "/patchasync/202/200"); - assert.equal(result.name, "sku"); - assert.equal(result.id, "100"); - }); + it("should handle deleteAsyncNoRetrySucceeded", async () => { + const response = await runMockedLro("DELETE", `/delete${rootPrefix}/noretry/succeeded`); + assert.equal(response.statusCode, 200); + }); - it("should handle putAsyncNoHeaderInRetry", async () => { - const result = await runMockedLro("PUT", "/putasync/noheader/201/200"); - assert.equal(result.name, "foo"); - assert.equal(result.id, "100"); - assert.deepEqual(result.properties?.provisioningState, "Succeeded"); - }); + it("should handle deleteAsyncRetrycanceled", async () => { + try { + await runMockedLro("DELETE", `/delete${rootPrefix}/retry/canceled`); + throw new Error("should have thrown instead"); + } catch (e) { + assert.equal( + e.message, + "The long running operation has failed. The provisioning state: canceled." + ); + } + }); - it("should handle putAsyncNoRetrySucceeded", async () => { - const result = await runMockedLro("PUT", "/putasync/noretry/succeeded"); - assert.equal(result.name, "foo"); - assert.equal(result.id, "100"); - }); + it("should handle DeleteAsyncRetryFailed", async () => { + try { + await runMockedLro("DELETE", `/delete${rootPrefix}/retry/failed`); + throw new Error("should have thrown instead"); + } catch (e) { + assert.equal( + e.message, + "The long running operation has failed. The provisioning state: failed." + ); + } + }); - it("should handle putAsyncNoRetrycanceled", async () => { - try { - await runMockedLro("PUT", "/putasync/noretry/canceled"); - throw new Error("should have thrown instead"); - } catch (e) { - assert.equal( - e.message, - "The long running operation has failed. The provisioning state: canceled." + it("should handle putAsyncRetrySucceeded", async () => { + const result = await runMockedLro("PUT", `/put${rootPrefix}/noretry/succeeded`); + assert.equal(result.id, "100"); + assert.equal(result.name, "foo"); + assert.equal(result.properties?.provisioningState, "Succeeded"); + }); + + it("should handle post202List", async () => { + const result = await runMockedLro( + "POST", + `/list${rootPrefix === "location" ? "location" : ""}` ); - } - }); + assert.equal((result as any)[0].id, "100"); + assert.equal((result as any)[0].name, "foo"); + }); - it("should handle putAsyncSubResource", async () => { - const result = await runMockedLro("PUT", "/putsubresourceasync/202/200"); - assert.equal(result.id, "100"); - assert.equal(result.properties?.provisioningState, "Succeeded"); - }); + it("should handle putAsyncRetryFailed", async () => { + try { + await runMockedLro("PUT", `/put${rootPrefix}/retry/failed`); + throw new Error("should have thrown instead"); + } catch (e) { + assert.equal( + e.message, + "The long running operation has failed. The provisioning state: failed." + ); + } + }); - it("should handle deleteAsyncNoHeaderInRetry", async () => { - const response = await runMockedLro("DELETE", "/deleteasync/noheader/202/204"); - assert.equal(response.statusCode, 200); - }); + it("should handle putAsyncNonResource", async () => { + const result = await runMockedLro("PUT", `/putnonresource${rootPrefix}/202/200`); + assert.equal(result.name, "sku"); + assert.equal(result.id, "100"); + }); - it("should handle postAsyncNoRetrySucceeded", async () => { - const result = await runMockedLro("POST", "/postasync/noretry/succeeded"); - assert.deepInclude(result, { id: "100", name: "foo" }); - }); + it("should handle patchAsync", async () => { + const result = await runMockedLro("PATCH", `/patch${rootPrefix}/202/200`); + assert.equal(result.name, "sku"); + assert.equal(result.id, "100"); + }); - it("should handle postAsyncRetryFailed", async () => { - try { - await runMockedLro("POST", "/postasync/retry/failed"); - throw new Error("should have thrown instead"); - } catch (e) { - assert.equal( - e.message, - "The long running operation has failed. The provisioning state: failed." - ); - } - }); + it("should handle putAsyncNoHeaderInRetry", async () => { + const result = await runMockedLro("PUT", `/put${rootPrefix}/noheader/201/200`); + assert.equal(result.name, "foo"); + assert.equal(result.id, "100"); + assert.deepEqual(result.properties?.provisioningState, "Succeeded"); + }); - it("should handle postAsyncRetrySucceeded", async () => { - const result = await runMockedLro("POST", "/postasync/retry/succeeded"); + it("should handle putAsyncNoRetrySucceeded", async () => { + const result = await runMockedLro("PUT", `/put${rootPrefix}/noretry/succeeded`); + assert.equal(result.name, "foo"); + assert.equal(result.id, "100"); + }); - assert.deepInclude(result, { id: "100", name: "foo" }); - }); + it("should handle putAsyncNoRetrycanceled", async () => { + try { + await runMockedLro("PUT", `/put${rootPrefix}/noretry/canceled`); + throw new Error("should have thrown instead"); + } catch (e) { + assert.equal( + e.message, + "The long running operation has failed. The provisioning state: canceled." + ); + } + }); - it("should handle postAsyncRetrycanceled", async () => { - try { - await runMockedLro("POST", "/postasync/retry/canceled"); - throw new Error("should have thrown instead"); - } catch (e) { - assert.equal( - e.message, - "The long running operation has failed. The provisioning state: canceled." - ); - } + it("should handle putAsyncSubResource", async () => { + const result = await runMockedLro("PUT", `/putsubresource${rootPrefix}/202/200`); + assert.equal(result.id, "100"); + assert.equal(result.properties?.provisioningState, "Succeeded"); + }); + + it("should handle deleteAsyncNoHeaderInRetry", async () => { + const response = await runMockedLro("DELETE", `/delete${rootPrefix}/noheader/202/204`); + assert.equal(response.statusCode, 200); + }); + + it("should handle postAsyncNoRetrySucceeded", async () => { + const result = await runMockedLro("POST", `/post${rootPrefix}/noretry/succeeded`); + assert.deepInclude(result, { id: "100", name: "foo" }); + }); + + it("should handle postAsyncRetryFailed", async () => { + try { + await runMockedLro("POST", `/post${rootPrefix}/retry/failed`); + throw new Error("should have thrown instead"); + } catch (e) { + assert.equal( + e.message, + "The long running operation has failed. The provisioning state: failed." + ); + } + }); + + it("should handle postAsyncRetrySucceeded", async () => { + const result = await runMockedLro("POST", `/post${rootPrefix}/retry/succeeded`); + + assert.deepInclude(result, { id: "100", name: "foo" }); + }); + + it("should handle postAsyncRetrycanceled", async () => { + try { + await runMockedLro("POST", `/post${rootPrefix}/retry/canceled`); + throw new Error("should have thrown instead"); + } catch (e) { + assert.equal( + e.message, + "The long running operation has failed. The provisioning state: canceled." + ); + } + }); }); }); diff --git a/sdk/core/core-lro/test/utils/router/paramRoutes.ts b/sdk/core/core-lro/test/utils/router/paramRoutes.ts index 5c1f5d6b1754..1e90467aa058 100644 --- a/sdk/core/core-lro/test/utils/router/paramRoutes.ts +++ b/sdk/core/core-lro/test/utils/router/paramRoutes.ts @@ -8,6 +8,11 @@ export interface Processor { process: (request: PipelineRequest) => PipelineResponse | undefined; } +interface Options { + headerName: string; + rootPrefix: string; +} + function createPutBody(): Processor { return { process, @@ -194,7 +199,7 @@ function createDeleteProvisioning(): Processor { } } -function createDeleteAsyncRetry(): Processor { +function createDeleteAsyncRetry(options: Options): Processor { let internalCounter = 1; return { process, @@ -206,19 +211,20 @@ function createDeleteAsyncRetry(): Processor { (finalState === "succeeded" || finalState === "canceled" || finalState === "failed") ); } + const root = `delete${options.rootPrefix}`; const pieces = parseUri(request.url); - if (pieces[0] !== "deleteasync") { + if (pieces[0] !== root) { return undefined; } const retry = pieces[1]; const finalState = pieces[2]; if (!isValidRequest(retry, finalState)) return undefined; if (pieces.length === 3) { - const pollingUri = `/deleteasync/${retry}/${finalState.toLowerCase()}/operationResults/200/`; + const pollingUri = `/${root}/${retry}/${finalState.toLowerCase()}/operationResults/200/`; const headers = createHttpHeaders({ - "Azure-AsyncOperation": pollingUri, Location: pollingUri, }); + headers.set(options.headerName, pollingUri); if (retry === "retry") { headers.set("Retry-After", "0"); } @@ -230,11 +236,11 @@ function createDeleteAsyncRetry(): Processor { return buildResponse(request, finalCode, `{ "status": "${getPascalCase(finalState)}"}`); } else { --internalCounter; - const pollingUri = `/deleteasync/${retry}/${finalState.toLowerCase()}/operationResults/${finalCode}`; + const pollingUri = `/${root}/${retry}/${finalState.toLowerCase()}/operationResults/${finalCode}`; const headers = createHttpHeaders({ - "Azure-AsyncOperation": pollingUri, Location: pollingUri, }); + headers.set(options.headerName, pollingUri); if (retry === "retry") { headers.set("Retry-After", "0"); } @@ -248,7 +254,7 @@ function createDeleteAsyncRetry(): Processor { } } -function createPutAsyncRetry(): Processor { +function createPutAsyncRetry(options: Options): Processor { let internalCounter = 1; return { process, @@ -261,7 +267,8 @@ function createPutAsyncRetry(): Processor { ); } const pieces = parseUri(request.url); - if (pieces[0] !== "putasync") { + const root = `put${options.rootPrefix}`; + if (pieces[0] !== root) { return undefined; } const retry = pieces[1]; @@ -278,11 +285,11 @@ function createPutAsyncRetry(): Processor { )}"}, "id": "100", "name": "foo" }` ); } else if (method === "PUT") { - const pollingUri = `/putasync/${retry}/${finalState.toLowerCase()}/operationResults/200/`; + const pollingUri = `/${root}/${retry}/${finalState.toLowerCase()}/operationResults/200/`; const headers = createHttpHeaders({ - "Azure-AsyncOperation": pollingUri, Location: pollingUri, }); + headers.set(options.headerName, pollingUri); if (retry === "retry") { headers.set("Retry-After", "0"); } @@ -300,11 +307,11 @@ function createPutAsyncRetry(): Processor { return buildResponse(request, finalCode, `{ "status": "${getPascalCase(finalState)}"}`); } else { --internalCounter; - const pollingUri = `/putasync/${retry}/${finalState.toLowerCase()}/operationResults/${finalCode}`; + const pollingUri = `/${root}/${retry}/${finalState.toLowerCase()}/operationResults/${finalCode}`; const headers = createHttpHeaders({ - "Azure-AsyncOperation": pollingUri, Location: pollingUri, }); + headers.set(options.headerName, pollingUri); if (retry === "retry") { headers.set("Retry-After", "0"); } @@ -318,7 +325,7 @@ function createPutAsyncRetry(): Processor { } } -function createPostasyncRetry(): Processor { +function createPostasyncRetry(options: Options): Processor { let internalCounter = 1; return { process, @@ -331,7 +338,8 @@ function createPostasyncRetry(): Processor { ); } const pieces = parseUri(request.url); - if (pieces[0] !== "postasync") { + const root = `post${options.rootPrefix}`; + if (pieces[0] !== root) { return undefined; } const retry = pieces[1]; @@ -349,9 +357,12 @@ function createPostasyncRetry(): Processor { ); } else if (method === "POST") { const headers = createHttpHeaders({ - "Azure-AsyncOperation": `/postasync/${retry}/${finalState.toLowerCase()}/operationResults/200/`, - Location: `/postasync/${retry}/succeeded/operationResults/foo/200/`, + Location: `/${root}/${retry}/succeeded/operationResults/foo/200/`, }); + headers.set( + options.headerName, + `/${root}/${retry}/${finalState.toLowerCase()}/operationResults/200/` + ); if (retry === "retry") { headers.set("Retry-After", "0"); } @@ -379,9 +390,12 @@ function createPostasyncRetry(): Processor { } else { --internalCounter; const headers = createHttpHeaders({ - "Azure-AsyncOperation": `/postasync/${retry}/${finalState.toLowerCase()}/operationResults/foo/${finalCode}`, - Location: `/postasync/${retry}/succeeded/operationResults/foo/200/`, + Location: `/${root}/${retry}/succeeded/operationResults/foo/200/`, }); + headers.set( + options.headerName, + `/${root}/${retry}/${finalState.toLowerCase()}/operationResults/foo/${finalCode}` + ); if (retry === "retry") { headers.set("Retry-After", "0"); } @@ -395,11 +409,22 @@ function createPostasyncRetry(): Processor { } } +const asyncOptions = { + headerName: "Azure-AsyncOperation", + rootPrefix: "async", +}; +const locationsOptions = { + headerName: "Operation-Location", + rootPrefix: "location", +}; export const paramRoutes = [ createPutBody(), createRetries(), createDeleteProvisioning(), - createDeleteAsyncRetry(), - createPutAsyncRetry(), - createPostasyncRetry(), + createDeleteAsyncRetry(asyncOptions), + createPutAsyncRetry(asyncOptions), + createPostasyncRetry(asyncOptions), + createDeleteAsyncRetry(locationsOptions), + createPutAsyncRetry(locationsOptions), + createPostasyncRetry(locationsOptions), ]; diff --git a/sdk/core/core-lro/test/utils/router/routesProcesses.ts b/sdk/core/core-lro/test/utils/router/routesProcesses.ts index 403add209514..681f827769ca 100644 --- a/sdk/core/core-lro/test/utils/router/routesProcesses.ts +++ b/sdk/core/core-lro/test/utils/router/routesProcesses.ts @@ -183,18 +183,19 @@ export function getDoubleHeadersFinalAzureHeaderGetDefaultLocation( return buildResponse(request, 200, `{ "id": "100", "name": "foo" }`); } -export function postList(request: PipelineRequest): PipelineResponse { - return buildResponse( - request, - 200, - undefined, - createHttpHeaders({ - "Azure-AsyncOperation": `/list/pollingGet`, - Location: `/list/finalGet`, - }) - ); +function createPostList(headerName: string): (request: PipelineRequest) => PipelineResponse { + const headers = createHttpHeaders({ + Location: `/list/finalGet`, + }); + headers.set(headerName, `/list/pollingGet`); + return function postList(request: PipelineRequest): PipelineResponse { + return buildResponse(request, 200, undefined, headers); + }; } +export const postListAsync = createPostList("Azure-AsyncOperation"); +export const postListLocation = createPostList("Operation-Location"); + export function getListPollingGet(request: PipelineRequest): PipelineResponse { return buildResponse(request, 200, `{ "status": "Succeeded" }`); } @@ -203,34 +204,38 @@ export function getListFinalGet(request: PipelineRequest): PipelineResponse { return buildResponse(request, 200, `[{ "id": "100", "name": "foo" }]`); } -export function putNonresourceAsync202200(request: PipelineRequest): PipelineResponse { - return buildResponse( - request, - 202, - undefined, - createHttpHeaders({ - "Azure-AsyncOperation": `/putnonresourceasync/operationresults/123`, - Location: `somethingBadWhichShouldNotBeUsed`, - }) - ); +function createPutNonresource202200( + headerName: string +): (request: PipelineRequest) => PipelineResponse { + const headers = createHttpHeaders({ + Location: `somethingBadWhichShouldNotBeUsed`, + }); + headers.set(headerName, `/putnonresourceasync/operationresults/123`); + return function (request: PipelineRequest): PipelineResponse { + return buildResponse(request, 202, undefined, headers); + }; } +export const putNonresourceAsync202200 = createPutNonresource202200("Azure-AsyncOperation"); +export const putNonresourceLocation202200 = createPutNonresource202200("Azure-AsyncOperation"); + export function getNonresourceAsync202200(request: PipelineRequest): PipelineResponse { return buildResponse(request, 200, `{ "name": "sku" , "id": "100" }`); } -export function patchAsync202200(request: PipelineRequest): PipelineResponse { - return buildResponse( - request, - 202, - undefined, - createHttpHeaders({ - "Azure-AsyncOperation": `/patchasync/operationresults/123`, - Location: `/patchasync/succeeded`, - }) - ); +function createPatch202200(headerName: string): (request: PipelineRequest) => PipelineResponse { + const headers = createHttpHeaders({ + Location: `/patchasync/succeeded`, + }); + headers.set(headerName, `/patchasync/operationresults/123`); + return function (request: PipelineRequest): PipelineResponse { + return buildResponse(request, 202, undefined, headers); + }; } +export const patchAsync202200 = createPatch202200("Azure-AsyncOperation"); +export const patchLocation202200 = createPatch202200("Operation-Location"); + export function getPatchAsyncSucceeded(request: PipelineRequest): PipelineResponse { return buildResponse(request, 200, `{ "name": "sku" , "id": "100" }`); } @@ -253,18 +258,26 @@ export const patchAsyncOperationresults123 = buildProcessMultipleRequests( (req) => buildResponse(req, 200, `{ "status": "Succeeded"}`) ); -export function putasyncNoheader201200(request: PipelineRequest): PipelineResponse { - return buildResponse( - request, - 201, - `{ "properties": { "provisioningState": "Accepted"}, "id": "100", "name": "foo" }`, - createHttpHeaders({ - "Azure-AsyncOperation": `/putasync/noheader/operationresults/123`, - Location: `somethingBadWhichShouldNotBeUsed`, - }) - ); +export function createPutasyncNoheader201200( + headerName: string +): (request: PipelineRequest) => PipelineResponse { + const headers = createHttpHeaders({ + Location: `somethingBadWhichShouldNotBeUsed`, + }); + headers.set(headerName, `/putasync/noheader/operationresults/123`); + return function (request: PipelineRequest): PipelineResponse { + return buildResponse( + request, + 201, + `{ "properties": { "provisioningState": "Accepted"}, "id": "100", "name": "foo" }`, + headers + ); + }; } +export const putasyncNoheader201200 = createPutasyncNoheader201200("Azure-AsyncOperation"); +export const putlocationNoheader201200 = createPutasyncNoheader201200("Operation-Location"); + export function getasyncNoheader201200(request: PipelineRequest): PipelineResponse { return buildResponse( request, @@ -278,18 +291,26 @@ export const putasyncNoheaderOperationresults123 = buildProcessMultipleRequests( (req) => buildResponse(req, 200, `{ "status": "Succeeded"}`) ); -export function putSubresourceAsync202200(request: PipelineRequest): PipelineResponse { - return buildResponse( - request, - 202, - `{ "properties": { "provisioningState": "Accepted"}, "id": "100", "subresource": "sub1" }`, - createHttpHeaders({ - "Azure-AsyncOperation": `/putsubresourceasync/operationresults/123`, - Location: `somethingBadWhichShouldNotBeUsed`, - }) - ); +function createPutSubresource202200( + headerName: string +): (request: PipelineRequest) => PipelineResponse { + const headers = createHttpHeaders({ + Location: `somethingBadWhichShouldNotBeUsed`, + }); + headers.set(headerName, `/putsubresourceasync/operationresults/123`); + return function putSubresourceAsync202200(request: PipelineRequest): PipelineResponse { + return buildResponse( + request, + 202, + `{ "properties": { "provisioningState": "Accepted"}, "id": "100", "subresource": "sub1" }`, + headers + ); + }; } +export const putSubresourceAsync202200 = createPutSubresource202200("Azure-AsyncOperation"); +export const putSubresourceLocation202200 = createPutSubresource202200("Operation-Location"); + export function getSubresourceAsync202200(request: PipelineRequest): PipelineResponse { return buildResponse( request, @@ -303,18 +324,21 @@ export const putSubresourceasyncOperationresults123 = buildProcessMultipleReques (req) => buildResponse(req, 200, `{ "status": "Succeeded"}`) ); -export function deleteasyncNoheader202204(request: PipelineRequest): PipelineResponse { - return buildResponse( - request, - 202, - undefined, - createHttpHeaders({ - "Azure-AsyncOperation": `/deleteasync/noheader/operationresults/123`, - Location: `somethingBadWhichShouldNotBeUsed`, - }) - ); +function createDeleteasyncNoheader202204( + headerName: string +): (request: PipelineRequest) => PipelineResponse { + const headers = createHttpHeaders({ + Location: `somethingBadWhichShouldNotBeUsed`, + }); + headers.set(headerName, `/deleteasync/noheader/operationresults/123`); + return function (request: PipelineRequest): PipelineResponse { + return buildResponse(request, 202, undefined, headers); + }; } +export const deleteasyncNoheader202204 = createDeleteasyncNoheader202204("Azure-AsyncOperation"); +export const deletelocationNoheader202204 = createDeleteasyncNoheader202204("Operation-Location"); + export const deleteNoHeaderOperationResults = buildProcessMultipleRequests( (req) => buildResponse(req, 202), (req) => buildResponse(req, 204) diff --git a/sdk/core/core-lro/test/utils/router/routesTable.ts b/sdk/core/core-lro/test/utils/router/routesTable.ts index 0c521961af4c..b0ca04c473b7 100644 --- a/sdk/core/core-lro/test/utils/router/routesTable.ts +++ b/sdk/core/core-lro/test/utils/router/routesTable.ts @@ -8,6 +8,7 @@ import { deleteNoHeaderOperationResults, deleteasyncNoheader202204, deleteasyncNoheaderOperationresults123, + deletelocationNoheader202204, errorDelete202RetryInvalidheader, errorDelete204nolocation, errorDeleteasyncRetryFailedOperationResultsInvalidjsonpolling, @@ -59,6 +60,7 @@ import { nonretryerrorPutasyncRetryFailedOperationResults400, patchAsync202200, patchAsyncOperationresults123, + patchLocation202200, postAsyncDoubleHeadersFinalAzureHeaderGet, postAsyncDoubleHeadersFinalAzureHeaderGetDefault, postAsyncDoubleHeadersFinalLocationGet, @@ -66,7 +68,8 @@ import { postDoubleHeadersFinalAzureHeaderGetLocation, postDoubleHeadersFinalLocationGetAsyncOperationUrl, postDoubleHeadersFinalLocationGetLocation, - postList, + postListAsync, + postListLocation, postLocationDoubleHeadersFinalAzureHeaderGet, postLocationDoubleHeadersFinalAzureHeaderGetDefault, postLocationDoubleHeadersFinalLocationGet, @@ -81,13 +84,16 @@ import { putNonresource20200, putNonresourceAsync202200, putNonresourceAsyncOperationresults123, + putNonresourceLocation202200, putNonresourceOperationResults, putSubresource202200, putSubresourceAsync202200, + putSubresourceLocation202200, putSubresourceOperationResults, putSubresourceasyncOperationresults123, putasyncNoheader201200, putasyncNoheaderOperationresults123, + putlocationNoheader201200, } from "./routesProcesses"; interface LroRoute { @@ -189,17 +195,21 @@ export const routes: LroRoute[] = [ path: "/LROPostDoubleHeadersFinalAzureHeaderGetDefault/location", process: getDoubleHeadersFinalAzureHeaderGetDefaultLocation, }, - { method: "POST", path: "/list", process: postList }, + { method: "POST", path: "/list", process: postListAsync }, + { method: "POST", path: "/listlocation", process: postListLocation }, { method: "GET", path: "/list/pollingGet", process: getListPollingGet }, { method: "GET", path: "/list/finalGet", process: getListFinalGet }, { method: "PUT", path: "/putnonresourceasync/202/200", process: putNonresourceAsync202200 }, + { method: "PUT", path: "/putnonresourcelocation/202/200", process: putNonresourceLocation202200 }, { method: "GET", path: "/putnonresourceasync/operationresults/123", process: putNonresourceAsyncOperationresults123, }, { method: "GET", path: "/putnonresourceasync/202/200", process: getNonresourceAsync202200 }, + { method: "GET", path: "/putnonresourcelocation/202/200", process: getNonresourceAsync202200 }, { method: "PATCH", path: "/patchasync/202/200", process: patchAsync202200 }, + { method: "PATCH", path: "/patchlocation/202/200", process: patchLocation202200 }, { method: "GET", path: "/patchasync/operationresults/123", @@ -208,6 +218,8 @@ export const routes: LroRoute[] = [ { method: "GET", path: "/patchasync/succeeded", process: getPatchAsyncSucceeded }, { method: "PUT", path: "/putasync/noheader/201/200", process: putasyncNoheader201200 }, { method: "GET", path: "/putasync/noheader/201/200", process: getasyncNoheader201200 }, + { method: "PUT", path: "/putlocation/noheader/201/200", process: putlocationNoheader201200 }, + { method: "GET", path: "/putlocation/noheader/201/200", process: getasyncNoheader201200 }, { method: "GET", path: "/putasync/noheader/operationresults/123", @@ -215,12 +227,19 @@ export const routes: LroRoute[] = [ }, { method: "PUT", path: "/putsubresourceasync/202/200", process: putSubresourceAsync202200 }, { method: "GET", path: "/putsubresourceasync/202/200", process: getSubresourceAsync202200 }, + { method: "PUT", path: "/putsubresourcelocation/202/200", process: putSubresourceLocation202200 }, + { method: "GET", path: "/putsubresourcelocation/202/200", process: getSubresourceAsync202200 }, { method: "GET", path: "/putsubresourceasync/operationresults/123", process: putSubresourceasyncOperationresults123, }, { method: "DELETE", path: "/deleteasync/noheader/202/204", process: deleteasyncNoheader202204 }, + { + method: "DELETE", + path: "/deletelocation/noheader/202/204", + process: deletelocationNoheader202204, + }, { method: "GET", path: "/deleteasync/noheader/operationresults/123", From 300d6c6e0965f29d1308a26b2cabad6ae73b7895 Mon Sep 17 00:00:00 2001 From: Deyaaeldeen Almahallawi Date: Fri, 4 Mar 2022 17:24:36 -0800 Subject: [PATCH 9/9] remove unnecessary export --- sdk/core/core-lro/test/utils/router/routesProcesses.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/core/core-lro/test/utils/router/routesProcesses.ts b/sdk/core/core-lro/test/utils/router/routesProcesses.ts index 681f827769ca..0c777bc060ec 100644 --- a/sdk/core/core-lro/test/utils/router/routesProcesses.ts +++ b/sdk/core/core-lro/test/utils/router/routesProcesses.ts @@ -258,7 +258,7 @@ export const patchAsyncOperationresults123 = buildProcessMultipleRequests( (req) => buildResponse(req, 200, `{ "status": "Succeeded"}`) ); -export function createPutasyncNoheader201200( +function createPutasyncNoheader201200( headerName: string ): (request: PipelineRequest) => PipelineResponse { const headers = createHttpHeaders({