From 226ca3a1d2fe4b0c9a133d579dca919205f86707 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 Jan 2026 02:58:33 +0000 Subject: [PATCH 1/2] Initial plan From d60068caa85f49e6c6edcb8ec1a29603c0931b5b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 Jan 2026 03:15:07 +0000 Subject: [PATCH 2/2] Add POST LRO exportBinary operation with bytes response type Co-authored-by: msyyc <70930885+msyyc@users.noreply.github.com> --- ...-specs-add-export-binary-lro-2026-01-19.md | 7 + packages/azure-http-specs/assets/image.png | Bin 0 -> 2992 bytes packages/azure-http-specs/spec-summary.md | 568 +++++++++++++++--- .../operation-templates/lro.tsp | 87 +++ .../operation-templates/mockapi.ts | 115 ++++ packages/azure-http-specs/specs/helper.ts | 7 + 6 files changed, 706 insertions(+), 78 deletions(-) create mode 100644 .chronus/changes/azure-http-specs-add-export-binary-lro-2026-01-19.md create mode 100644 packages/azure-http-specs/assets/image.png create mode 100644 packages/azure-http-specs/specs/helper.ts diff --git a/.chronus/changes/azure-http-specs-add-export-binary-lro-2026-01-19.md b/.chronus/changes/azure-http-specs-add-export-binary-lro-2026-01-19.md new file mode 100644 index 0000000000..6354c7b120 --- /dev/null +++ b/.chronus/changes/azure-http-specs-add-export-binary-lro-2026-01-19.md @@ -0,0 +1,7 @@ +--- +changeKind: feature +packages: + - "@azure-tools/azure-http-specs" +--- + +Add POST LRO test case `exportBinary` whose response type is `bytes` in the Azure Resource Manager operation templates spec. diff --git a/packages/azure-http-specs/assets/image.png b/packages/azure-http-specs/assets/image.png new file mode 100644 index 0000000000000000000000000000000000000000..42fe8dc14560b0046bf0f3f00b7a471fa0346e36 GIT binary patch literal 2992 zcmaJ>2UJtr5=}ynfPe^6#i)oP2~A3b_#j9P<c=;9z@urf$wEd&r0Smmc~ z4b}n)eX~PAprkO6@Q)ZL0M}MJAZwiOD16}zNDSD?0g_k*{=o$NMUbCNY?07U9S+2S zEZop`b^yBJDZ#-+YAA`O;C(9uFi4PXy{I6N!uB-*+qrI=1#0z#xnpTqM+_2ABESQJ zNP)p{I)S{F2V_P^0xBVx7N9{V941ncbaU9Z2qd7dnGrCJZy~e@a~RgqMFUNu1Zx<< zb>X@&6jVb)!;BJi2rs64KWLg-BsIiteAdnPEGl#*}3jJK)-!qsV_Nx++`r}x@ z0TF8+gdSWM@iR9-HCwYHUBc+WhrO)A2tdz(8Yn{}vv1-5tZHzM{I(Zu9;;%|=s zCVEhVDQFS_C`d#7x~w1M-<>~*W{9LqLSH=UG`#0SvISvT&z^Cv#7@xJSx2aa-|9tm}-z6Hc>u#KDm+I4F>M3G9RmW+_?M2)1;gWo3xcVR@W8PHPH4h8*{++h z8}Y5F9`(=WPeZXH<+|Y0gJnwP5YL?0zo!3|^aN{T{s{OKE?O45QM0I0;1yDN`SND{ zyOg5mp)N5c(u0Esp-E=kfllR0C&k1s@i0GrZRfs5<7!+x0s^%ugM8%*ef;#PiEVR~ zk~180fRXw!|2qMhFy2QQNA)X#rKKLKFql@YS0DB+8Y~M0L6l6MBxAd5adGjcF9c8t zo3^yHG!i%{(t~?+_KjG%t?wg9W@hGn@>d^2zlX4eDsq69+{GOYhow*ZZ9YFY+;qgj z#AWZ^z2xho`YaYJ$mptuez>fhT&9eIf;49^mEC)&XN9ajcTsqPhD7C%d`TOUID=@6 zW~%vaGf31w{D~F5LO*H9Ul5b;M$_L~@5Rf{$YXmij@GSqZ($ztNsv;Z`i@;wgI)yL z`tVD3-D4YatT^h!2`{f7xgFBk(KVv6D=iyOe>q)Zxtu3f1vhZeoRRfQZ3s>DQ~|{c zhLZ7j^pqm{@Kbe|+)KiSAs)dZlW4Rb|0>x!mf7eaT?yRGZFm;h)5lP}0s z)MR+lRu=;7XofSxFZS5kB(Io?U<4$)=j+Ujv-f1z7}jT(U8oBF@c2?YjHZlX3}y?2 zB}I!j)%5LcxOt457x{YKwWQLqjkl*P!m*TD-H`cTuN{_G`e(Occ-dLa=;vA%@*AOg(a(z}6t_crT&EO-&+m4gF>N0h;BXG0?{Bi7 z`CMs0?IQEIN_yrvkTId}4tcQlLFDsm2){A5T)eb!$m;U*9bpNnS{u9Nur=c6{IY zKC28T*@~?E=+fvG1A<0jF;cm0Q(bSl;8d`tTUme#<>4g zLGy3~(+z4auzTu_$wj7=5_bmr4M3i>XprZ8-8A{ z^S%T(i(@^t7G<(wYTc~P^uFuDm}iOCg0RJbylL{6l)E9&xd*ipG|e`)b?b`n)|`3$ z>k`idS@_lp+*>nk7_W2qvLHrYsnN9VjcdkaYxRqWdf5!mZ8vsD^2e%lG>7`oaP1~( z91eF(@?7&4^jE^(0w>-(Xig#TL8J;KX+2YL&fdmGjCA1!I<+uYch0=ri`kM?G=Uz= zYe(%m$g6gpfYRA}M3Wh=PjV`BA|f`Ajz1QY-KfS$+mLC&=*M@CqU0@-843#@s!tp+ z`hXG_$T+w$%ZRc;oA!hozBo{U_AgR)cc2=ist%Tf1!goyZ`H}XSr9&UY zJ^9hCDY9E_U|L$j^3{t+O$rDP^WqxkQ)&F;Eh@Hsx^<};S#~>%t}R_YRkG7OjOT7s&BTd5F;H{&a{d!BS5Vz@RgwkT{dwhcQ zPGno6OlTdu0KU{d<)>5lyi9d5Rz$1ros^mpW_coXd{r;;5w~?!YUHbE$=hL2`F`si x55d?S>E1-idha0G3zgVNNXgr$|KG8qZJn`L2pQ~%2Sy6WZoiY&ZA<_7e*r@k8!Z3; literal 0 HcmV?d00001 diff --git a/packages/azure-http-specs/spec-summary.md b/packages/azure-http-specs/spec-summary.md index ef9310d6ba..96ed063739 100644 --- a/packages/azure-http-specs/spec-summary.md +++ b/packages/azure-http-specs/spec-summary.md @@ -161,6 +161,72 @@ Example input: Output: None (204/empty response) +### Azure_ClientGenerator_Core_ClientDefaultValue_getHeaderParameter + +- Endpoint: `get /azure/client-generator-core/client-default-value/header-parameter` + +Test case 4: `@clientDefaultValue` for header parameters. +This scenario tests that client default values are correctly applied to header parameters. + +Expected header parameters: +Accept: "application/json;odata.metadata=none" (default) +x-custom-header: "default-value" (default) + +Expected response: 204 No Content + +### Azure_ClientGenerator_Core_ClientDefaultValue_getOperationParameter + +- Endpoint: `get /azure/client-generator-core/client-default-value/operation-parameter` + +Test case 2: `@clientDefaultValue` for operation parameter. +This scenario tests that client default values are correctly applied to operation parameters. + +Expected query parameter: +name: "test" +pageSize: 10 (default) +format: "json" (default) + +Expected response: 204 No Content + +### Azure_ClientGenerator_Core_ClientDefaultValue_getPathParameter + +- Endpoint: `get /azure/client-generator-core/client-default-value/path-parameter/{segment1}/{segment2}` + +Test case 3: `@clientDefaultValue` for first path segment. +This scenario has 2 path segments and tests client default value on the first segment. + +Expected path parameters: +segment1: "default-segment1" (default) +segment2: "segment2" + +Expected response: 204 No Content + +### Azure_ClientGenerator_Core_ClientDefaultValue_putModelProperty + +- Endpoint: `put /azure/client-generator-core/client-default-value/model-property` + +Test case 1: `@clientDefaultValue` for model property. +This scenario tests that client default values are correctly applied to model properties. + +Expected input body: + +```json +{ + "name": "test" +} +``` + +Expected response body: + +```json +{ + "name": "test", + "timeout": 30, + "tier": "standard", + "retry": true +} +``` + ### Azure_ClientGenerator_Core_ClientInitialization_HeaderParam - Endpoints: @@ -316,75 +382,9 @@ client.getStandalone(); client.deleteStandalone(); ``` -### Azure_ClientGenerator_Core_ClientDefaultValue_getOperationParameter - -- Endpoint: `get /azure/client-generator-core/client-default-value/operation-parameter` - -Test case 2: `@clientDefaultValue` for operation parameter. -This scenario tests that client default values are correctly applied to operation parameters. - -Expected query parameter: -name: "test" -pageSize: 10 (default) -format: "json" (default) +### Azure_ClientGenerator_Core_ClientLocation_MoveMethodParameterToClient_BlobOperations -Expected response: 204 No Content - -### Azure_ClientGenerator_Core_ClientDefaultValue_getHeaderParameter - -- Endpoint: `get /azure/client-generator-core/client-default-value/header-parameter` - -Test case 4: `@clientDefaultValue` for header parameters. -This scenario tests that client default values are correctly applied to header parameters. - -Expected header parameters: -Accept: "application/json;odata.metadata=none" (default) -x-custom-header: "default-value" (default) - -Expected response: 204 No Content - -### Azure_ClientGenerator_Core_ClientDefaultValue_getPathParameter - -- Endpoint: `get /azure/client-generator-core/client-default-value/path-parameter/{segment1}/{segment2}` - -Test case 3: `@clientDefaultValue` for first path segment. -This scenario has 2 path segments and tests client default value on the first segment. - -Expected path parameters: -segment1: "default-segment1" (default) -segment2: "segment2" - -Expected response: 204 No Content - -### Azure_ClientGenerator_Core_ClientDefaultValue_putModelProperty - -- Endpoint: `put /azure/client-generator-core/client-default-value/model-property` - -Test case 1: `@clientDefaultValue` for model property. -This scenario tests that client default values are correctly applied to model properties. - -Expected input body: - -```json -{ - "name": "test" -} -``` - -Expected response body: - -```json -{ - "name": "test", - "timeout": 30, - "tier": "standard", - "retry": true -} -``` - -### Azure_ClientGenerator_Core_ClientLocation_MoveMethodParameterToClient - -- Endpoint: `get /azure/client-generator-core/client-location/blob` +- Endpoint: `get /azure/client-generator-core/client-location/move-method-parameter-to-client/blob` Test moving a method parameter to client. @@ -399,12 +399,11 @@ Expected response: - Status: 200 - Body: {"id": "blob-001", "name": "testblob.txt", "size": 1024, "path": "/testcontainer/testblob.txt"} -### Azure_ClientGenerator_Core_ClientLocation_MoveToExistingSubClient +### Azure_ClientGenerator_Core_ClientLocation_MoveToExistingSubClient_UserOperations - Endpoints: - - `get /azure/client-generator-core/client-location/admin` - - `get /azure/client-generator-core/client-location/user` - - `get /azure/client-generator-core/client-location/user` + - `get /azure/client-generator-core/client-location/move-to-existing-sub-client/user` + - `get /azure/client-generator-core/client-location/move-to-existing-sub-client/user` Test moving an operation from one sub client to another existing sub client. @@ -415,11 +414,11 @@ Expected client structure: - Interface UserOperations should contain only operation `getUser` - Interface AdminOperations should contain operations `getAdminInfo` and `deleteUser` (moved from UserOperations) -### Azure_ClientGenerator_Core_ClientLocation_MoveToNewSubClient +### Azure_ClientGenerator_Core_ClientLocation_MoveToNewSubClient_ProductOperations - Endpoints: - - `get /azure/client-generator-core/client-location/products` - - `get /azure/client-generator-core/client-location/products/archive` + - `get /azure/client-generator-core/client-location/move-to-new-sub-client/products` + - `get /azure/client-generator-core/client-location/move-to-new-sub-client/products/archive` Test moving an operation to a new sub client specified by string name. @@ -430,11 +429,11 @@ Expected client structure: - Interface ProductOperations should contain only operation `listProducts` - A new sub client "ArchiveOperations" should be created containing operation `archiveProduct` -### Azure_ClientGenerator_Core_ClientLocation_MoveToRootClient +### Azure_ClientGenerator_Core_ClientLocation_MoveToRootClient_ResourceOperations - Endpoints: - - `get /azure/client-generator-core/client-location/resource` - - `get /azure/client-generator-core/client-location/health` + - `get /azure/client-generator-core/client-location/move-to-root-client/resource` + - `get /azure/client-generator-core/client-location/move-to-root-client/health` Test moving an operation to the root client. @@ -2168,6 +2167,280 @@ Expected response body: } ``` +### Azure_ResourceManager_MultiServiceOlderVersions_Compute_VirtualMachines_createOrUpdate + +- Endpoint: `put https://management.azure.com` + +Test that a client can expose operations from multiple services using older API versions. This operation should be called like this: `client.virtualMachines.createOrUpdate(...)`. + +PUT (create or update) a Virtual Machine. +Expected path: /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Compute/virtualMachinesOld/vm-old1 +Expected query parameter: api-version=2024-11-01 +Expected request body: + +```json +{ + "location": "eastus", + "properties": { + "size": "Standard_D2s_v3" + } +} +``` + +Expected response body: + +```json +{ + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Compute/virtualMachinesOld/vm-old1", + "name": "vm-old1", + "type": "Microsoft.Compute/virtualMachinesOld", + "location": "eastus", + "properties": { + "provisioningState": "Succeeded", + "size": "Standard_D2s_v3" + } +} +``` + +### Azure_ResourceManager_MultiServiceOlderVersions_Compute_VirtualMachines_get + +- Endpoint: `get https://management.azure.com` + +Test that a client can expose operations from multiple services using older API versions. This operation should be called like this: `client.virtualMachines.get(...)`. + +GET a Virtual Machine. +Expected path: /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Compute/virtualMachinesOld/vm-old1 +Expected query parameter: api-version=2024-11-01 + +Expected response body: + +```json +{ + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Compute/virtualMachinesOld/vm-old1", + "name": "vm-old1", + "type": "Microsoft.Compute/virtualMachinesOld", + "location": "eastus", + "properties": { + "provisioningState": "Succeeded", + "size": "Standard_D2s_v3" + } +} +``` + +### Azure_ResourceManager_MultiServiceOlderVersions_ComputeDisk_Disks_createOrUpdate + +- Endpoint: `put https://management.azure.com` + +Test that a client can expose operations from multiple services using older API versions. This operation should be called like this: `client.disks.createOrUpdate(...)`. + +PUT (create or update) a Disk resource. +Expected path: /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Compute/disksOld/disk-old1 +Expected query parameter: api-version=2024-03-02 +Expected request body: + +```json +{ + "location": "eastus", + "properties": { + "diskSizeGB": 128 + } +} +``` + +Expected response body: + +```json +{ + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Compute/disksOld/disk-old1", + "name": "disk-old1", + "type": "Microsoft.Compute/disksOld", + "location": "eastus", + "properties": { + "provisioningState": "Succeeded", + "diskSizeGB": 128 + } +} +``` + +### Azure_ResourceManager_MultiServiceOlderVersions_ComputeDisk_Disks_get + +- Endpoint: `get https://management.azure.com` + +Test that a client can expose operations from multiple services using older API versions. This operation should be called like this: `client.disks.get(...)`. + +GET a Disk resource. +Expected path: /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Compute/disksOld/disk-old1 +Expected query parameter: api-version=2024-03-02 + +Expected response body: + +```json +{ + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Compute/disksOld/disk-old1", + "name": "disk-old1", + "type": "Microsoft.Compute/disksOld", + "location": "eastus", + "properties": { + "provisioningState": "Succeeded", + "diskSizeGB": 128 + } +} +``` + +### Azure_ResourceManager_MultiServiceSharedModels_Compute_VirtualMachines_createOrUpdate + +- Endpoint: `put https://management.azure.com` + +Test that a client can expose operations from multiple services with shared models. This operation should be called like this: `client.virtualMachines.createOrUpdate(...)`. + +PUT (create or update) a Virtual Machine. +Expected path: /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Compute/virtualMachinesShared/vm-shared1 +Expected query parameter: api-version=2025-05-01 +Expected request body: + +```json +{ + "location": "eastus", + "properties": { + "metadata": { + "createdBy": "user@example.com", + "tags": { + "environment": "production" + } + } + } +} +``` + +Expected response body: + +```json +{ + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Compute/virtualMachinesShared/vm-shared1", + "name": "vm-shared1", + "type": "Microsoft.Compute/virtualMachinesShared", + "location": "eastus", + "properties": { + "provisioningState": "Succeeded", + "metadata": { + "createdAt": "2025-01-01T00:00:00Z", + "createdBy": "user@example.com", + "tags": { + "environment": "production" + } + } + } +} +``` + +### Azure_ResourceManager_MultiServiceSharedModels_Compute_VirtualMachines_get + +- Endpoint: `get https://management.azure.com` + +Test that a client can expose operations from multiple services with shared models. This operation should be called like this: `client.virtualMachines.get(...)`. + +GET a Virtual Machine. +Expected path: /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Compute/virtualMachinesShared/vm-shared1 +Expected query parameter: api-version=2025-05-01 + +Expected response body: + +```json +{ + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Compute/virtualMachinesShared/vm-shared1", + "name": "vm-shared1", + "type": "Microsoft.Compute/virtualMachinesShared", + "location": "eastus", + "properties": { + "provisioningState": "Succeeded", + "metadata": { + "createdAt": "2025-01-01T00:00:00Z", + "createdBy": "user@example.com", + "tags": { + "environment": "production" + } + } + } +} +``` + +### Azure_ResourceManager_MultiServiceSharedModels_Storage_StorageAccounts_createOrUpdate + +- Endpoint: `put https://management.azure.com` + +Test that a client can expose operations from multiple services with shared models. This operation should be called like this: `client.storageAccounts.createOrUpdate(...)`. + +PUT (create or update) a Storage Account. +Expected path: /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Storage/storageAccounts/account1 +Expected query parameter: api-version=2025-02-01 +Expected request body: + +```json +{ + "location": "westus", + "properties": { + "metadata": { + "createdBy": "admin@example.com", + "tags": { + "department": "engineering" + } + } + } +} +``` + +Expected response body: + +```json +{ + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Storage/storageAccounts/account1", + "name": "account1", + "type": "Microsoft.Storage/storageAccounts", + "location": "westus", + "properties": { + "provisioningState": "Succeeded", + "metadata": { + "createdAt": "2025-01-02T00:00:00Z", + "createdBy": "admin@example.com", + "tags": { + "department": "engineering" + } + } + } +} +``` + +### Azure_ResourceManager_MultiServiceSharedModels_Storage_StorageAccounts_get + +- Endpoint: `get https://management.azure.com` + +Test that a client can expose operations from multiple services with shared models. This operation should be called like this: `client.storageAccounts.get(...)`. + +GET a Storage Account. +Expected path: /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Storage/storageAccounts/account1 +Expected query parameter: api-version=2025-02-01 + +Expected response body: + +```json +{ + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Storage/storageAccounts/account1", + "name": "account1", + "type": "Microsoft.Storage/storageAccounts", + "location": "westus", + "properties": { + "provisioningState": "Succeeded", + "metadata": { + "createdAt": "2025-01-02T00:00:00Z", + "createdBy": "admin@example.com", + "tags": { + "department": "engineering" + } + } + } +} +``` + ### Azure_ResourceManager_NonResource_NonResourceOperations_create - Endpoint: `put https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.NonResource/locations/{location}/otherParameters/{parameter}` @@ -2506,6 +2779,145 @@ Expected response body: } ``` +### Azure_ResourceManager_OperationTemplates_Lro_exportBinary + +- Endpoint: `post https://management.azure.com` + +Resource POST operation that returns binary data. +Service returns both Location and Azure-AsyncOperation header on initial request. +final-state-via: location + +Expected verb: POST +Expected path: /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Azure.ResourceManager.OperationTemplates/orders/order1/exportBinary +Expected query parameter: api-version=2023-12-01-preview +Expected request body: + +```json +{ + "sourceId": "source1" +} +``` + +Expected response status code: 202 +Expected response headers: + +- Azure-AsyncOperation={endpoint}/subscriptions/00000000-0000-0000-0000-000000000000/providers/Azure.ResourceManager.OperationTemplates/locations/eastus/operations/lro_post_binary_aao +- Location={endpoint}/subscriptions/00000000-0000-0000-0000-000000000000/providers/Azure.ResourceManager.OperationTemplates/locations/eastus/operations/lro_post_binary_location + Expected no response body + +Whether you do polling through AAO, Location or combined, first one will respond with provisioning state "InProgress", second one with "Succeeded". + +AAO first poll. +Expected verb: GET +Expected URL: {endpoint}/subscriptions/00000000-0000-0000-0000-000000000000/providers/Azure.ResourceManager.OperationTemplates/locations/eastus/operations/lro_post_binary_aao +Expected status code: 200 +Expected response body: + +```json +{ + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Azure.ResourceManager.OperationTemplates/locations/eastus/operations/lro_post_binary_aao", + "name": "lro_post_binary_aao", + "status": "InProgress", + "startTime": "2024-11-08T01:41:53.5508583+00:00" +} +``` + +AAO second poll. +Expected verb: GET +Expected URL: {endpoint}/subscriptions/00000000-0000-0000-0000-000000000000/providers/Azure.ResourceManager.OperationTemplates/locations/eastus/operations/lro_post_binary_aao +Expected status code: 200 +Expected response body: + +```json +{ + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Azure.ResourceManager.OperationTemplates/locations/eastus/operations/lro_post_binary_aao", + "name": "lro_post_binary_aao", + "status": "Succeeded", + "startTime": "2024-11-08T01:41:53.5508583+00:00", + "endTime": "2024-11-08T01:42:41.5354192+00:00" +} +``` + +Location first poll. +Expected verb: GET +Expected URL: {endpoint}/subscriptions/00000000-0000-0000-0000-000000000000/providers/Azure.ResourceManager.OperationTemplates/locations/eastus/operations/lro_post_binary_location +Expected status code: 202 +Expected no response body + +Location second poll. +Expected verb: GET +Expected URL: {endpoint}/subscriptions/00000000-0000-0000-0000-000000000000/providers/Azure.ResourceManager.OperationTemplates/locations/eastus/operations/lro_post_binary_location +Expected status code: 200 +Expected response header: Content-Type=application/octet-stream +Expected response body: binary content (image.png file) + +### Azure_ResourceManager_OperationTemplates_LroPaging_postPagingLro + +- Endpoint: `post https://management.azure.com` + +Resource POST operation that returns a LRO with paging. + +Step 1: Initial Request +Expected verb: POST +Expected path: /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Azure.ResourceManager.OperationTemplates/products/default/postPagingLro +Expected query parameter: api-version=2023-12-01-preview +Expected response: 202 Accepted with Location and Retry-After headers. + +Step 2: Polling Request +Expected verb: GET +Expected path: /subscriptions/00000000-0000-0000-0000-000000000000/providers/Azure.ResourceManager.OperationTemplates/locations/eastus/operations/lro_paging_post_location +Expected query parameter: api-version=2023-12-01-preview +Expected response: 202 Accepted with Location and Retry-After headers. + +Step 3: Final Result Request +Expected verb: GET +Expected path: /subscriptions/00000000-0000-0000-0000-000000000000/providers/Azure.ResourceManager.OperationTemplates/locations/eastus/operations/lro_paging_post_location +Expected query parameter: api-version=2023-12-01-preview +Expected response: 200 OK with a paged result. The response body contains a "nextLink" field. +Expected response body: + +```json +{ + "value": [ + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Azure.ResourceManager.OperationTemplates/products/product1", + "name": "product1", + "type": "Azure.ResourceManager.OperationTemplates/products", + "location": "eastus", + "properties": { + "provisioningState": "Succeeded", + "productId": "product1" + } + } + ], + "nextLink": "..." +} +``` + +Step 4: Next Page Request +Expected verb: GET +Expected path: /subscriptions/00000000-0000-0000-0000-000000000000/providers/Azure.ResourceManager.OperationTemplates/locations/eastus/operations/lro_paging_post_location/nextPage +Expected query parameter: api-version=2023-12-01-preview +Expected response: 200 OK with the second page of results. +Expected response body: + +```json +{ + "value": [ + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Azure.ResourceManager.OperationTemplates/products/product2", + "name": "product2", + "type": "Azure.ResourceManager.OperationTemplates/products", + "location": "eastus", + "properties": { + "provisioningState": "Succeeded", + "productId": "product2" + } + } + ] +} +``` + ### Azure_ResourceManager_OperationTemplates_OptionalBody_get - Endpoint: `get https://management.azure.com` diff --git a/packages/azure-http-specs/specs/azure/resource-manager/operation-templates/lro.tsp b/packages/azure-http-specs/specs/azure/resource-manager/operation-templates/lro.tsp index 6d5d6f2187..a43db5f352 100644 --- a/packages/azure-http-specs/specs/azure/resource-manager/operation-templates/lro.tsp +++ b/packages/azure-http-specs/specs/azure/resource-manager/operation-templates/lro.tsp @@ -4,6 +4,7 @@ import "@azure-tools/typespec-azure-core"; import "@azure-tools/typespec-azure-resource-manager"; import "@typespec/spector"; +using Http; using Rest; using Spector; @@ -36,6 +37,19 @@ model ExportResult { content: string; } +model ExportBinaryRequest { + @doc("Source identifier for binary export.") + sourceId: string; +} + +model ExportBinaryResult { + @header contentType: "application/octet-stream"; + + @doc("Binary content of the exported data.") + @body + data: bytes; +} + @armResourceOperations interface Lro { @scenario @@ -218,6 +232,79 @@ interface Lro { Azure.Core.Foundations.RetryAfterHeader >; + @scenario + @scenarioDoc(""" + Resource POST operation that returns binary data. + Service returns both Location and Azure-AsyncOperation header on initial request. + final-state-via: location + + Expected verb: POST + Expected path: /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Azure.ResourceManager.OperationTemplates/orders/order1/exportBinary + Expected query parameter: api-version=2023-12-01-preview + Expected request body: + ```json + { + "sourceId": "source1" + } + ``` + Expected response status code: 202 + Expected response headers: + - Azure-AsyncOperation={endpoint}/subscriptions/00000000-0000-0000-0000-000000000000/providers/Azure.ResourceManager.OperationTemplates/locations/eastus/operations/lro_post_binary_aao + - Location={endpoint}/subscriptions/00000000-0000-0000-0000-000000000000/providers/Azure.ResourceManager.OperationTemplates/locations/eastus/operations/lro_post_binary_location + Expected no response body + + Whether you do polling through AAO, Location or combined, first one will respond with provisioning state "InProgress", second one with "Succeeded". + + AAO first poll. + Expected verb: GET + Expected URL: {endpoint}/subscriptions/00000000-0000-0000-0000-000000000000/providers/Azure.ResourceManager.OperationTemplates/locations/eastus/operations/lro_post_binary_aao + Expected status code: 200 + Expected response body: + ```json + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Azure.ResourceManager.OperationTemplates/locations/eastus/operations/lro_post_binary_aao", + "name": "lro_post_binary_aao", + "status" : "InProgress", + "startTime": "2024-11-08T01:41:53.5508583+00:00" + } + ``` + + AAO second poll. + Expected verb: GET + Expected URL: {endpoint}/subscriptions/00000000-0000-0000-0000-000000000000/providers/Azure.ResourceManager.OperationTemplates/locations/eastus/operations/lro_post_binary_aao + Expected status code: 200 + Expected response body: + ```json + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Azure.ResourceManager.OperationTemplates/locations/eastus/operations/lro_post_binary_aao", + "name": "lro_post_binary_aao", + "status" : "Succeeded", + "startTime": "2024-11-08T01:41:53.5508583+00:00", + "endTime": "2024-11-08T01:42:41.5354192+00:00" + } + ``` + + Location first poll. + Expected verb: GET + Expected URL: {endpoint}/subscriptions/00000000-0000-0000-0000-000000000000/providers/Azure.ResourceManager.OperationTemplates/locations/eastus/operations/lro_post_binary_location + Expected status code: 202 + Expected no response body + + Location second poll. + Expected verb: GET + Expected URL: {endpoint}/subscriptions/00000000-0000-0000-0000-000000000000/providers/Azure.ResourceManager.OperationTemplates/locations/eastus/operations/lro_post_binary_location + Expected status code: 200 + Expected response header: Content-Type=application/octet-stream + Expected response body: binary content (image.png file) + """) + exportBinary is ArmResourceActionAsync< + Order, + ExportBinaryRequest, + ExportBinaryResult, + LroHeaders = ArmCombinedLroHeaders & + Azure.Core.Foundations.RetryAfterHeader + >; + @scenario @scenarioDoc(""" Resource DELETE operation. diff --git a/packages/azure-http-specs/specs/azure/resource-manager/operation-templates/mockapi.ts b/packages/azure-http-specs/specs/azure/resource-manager/operation-templates/mockapi.ts index 4a623cc3fb..0dbe72ca74 100644 --- a/packages/azure-http-specs/specs/azure/resource-manager/operation-templates/mockapi.ts +++ b/packages/azure-http-specs/specs/azure/resource-manager/operation-templates/mockapi.ts @@ -7,6 +7,7 @@ import { ScenarioMockApi, withServiceKeys, } from "@typespec/spec-api"; +import { pngFile } from "../../../helper.js"; export const Scenarios: Record = {}; @@ -80,6 +81,7 @@ const validProductListResultPage2 = { }; let createOrReplacePollCount = 0; let postPollCount = 0; +let postBinaryPollCount = 0; let deletePollCount = 0; // operation list @@ -381,6 +383,119 @@ Scenarios.Azure_ResourceManager_OperationTemplates_Lro_export = passOnSuccess([ }, ]); +Scenarios.Azure_ResourceManager_OperationTemplates_Lro_exportBinary = passOnSuccess([ + { + // LRO POST binary initial request + uri: "/subscriptions/:subscriptionId/resourceGroups/:resourceGroup/providers/Azure.ResourceManager.OperationTemplates/orders/:orderName/exportBinary", + method: "post", + request: { + pathParams: { + subscriptionId: SUBSCRIPTION_ID_EXPECTED, + resourceGroup: RESOURCE_GROUP_EXPECTED, + orderName: "order1", + }, + query: { + "api-version": "2023-12-01-preview", + }, + body: json({ + sourceId: "source1", + }), + }, + response: { + status: 202, + headers: { + location: dyn`${dynItem("baseUrl")}/subscriptions/${SUBSCRIPTION_ID_EXPECTED}/providers/Azure.ResourceManager.OperationTemplates/locations/eastus/operations/lro_post_binary_location`, + "azure-asyncoperation": dyn`${dynItem("baseUrl")}/subscriptions/${SUBSCRIPTION_ID_EXPECTED}/providers/Azure.ResourceManager.OperationTemplates/locations/eastus/operations/lro_post_binary_aao`, + }, + }, + handler: (req: MockRequest) => { + postBinaryPollCount = 0; + return { + status: 202, + headers: { + location: `${req.baseUrl}/subscriptions/${SUBSCRIPTION_ID_EXPECTED}/providers/Azure.ResourceManager.OperationTemplates/locations/eastus/operations/lro_post_binary_location`, + "azure-asyncoperation": `${req.baseUrl}/subscriptions/${SUBSCRIPTION_ID_EXPECTED}/providers/Azure.ResourceManager.OperationTemplates/locations/eastus/operations/lro_post_binary_aao`, + }, + }; + }, + kind: "MockApiDefinition", + }, + { + // LRO POST binary poll intermediate/get final result - Location Header + uri: "/subscriptions/:subscriptionId/providers/Azure.ResourceManager.OperationTemplates/locations/eastus/operations/lro_post_binary_location", + method: "get", + request: { + pathParams: { + subscriptionId: SUBSCRIPTION_ID_EXPECTED, + }, + query: { + "api-version": "2023-12-01-preview", + }, + }, + response: { + status: 200, + }, + handler: (req: MockRequest) => { + const response = + // first status will be 202, second and forward be 200 with binary content + postBinaryPollCount > 0 + ? { + status: 200, + body: { + contentType: "application/octet-stream", + rawContent: pngFile, + }, + } + : { status: 202 }; + + postBinaryPollCount += 1; + return response; + }, + kind: "MockApiDefinition", + }, + { + // LRO POST binary poll intermediate/get final result - Azure-AsyncOperation Header + uri: "/subscriptions/:subscriptionId/providers/Azure.ResourceManager.OperationTemplates/locations/eastus/operations/lro_post_binary_aao", + method: "get", + request: { + pathParams: { + subscriptionId: SUBSCRIPTION_ID_EXPECTED, + }, + query: { + "api-version": "2023-12-01-preview", + }, + }, + response: { + status: 200, + }, + handler: (req: MockRequest) => { + const aaoResponse = { + id: `/subscriptions/${SUBSCRIPTION_ID_EXPECTED}/providers/Azure.ResourceManager.OperationTemplates/locations/eastus/operations/lro_post_binary_aao`, + name: "lro_post_binary_aao", + startTime: "2024-11-08T01:41:53.5508583+00:00", + }; + // first provisioningState will be "InProgress", second and forward be "Succeeded" + const responseBody = + postBinaryPollCount > 0 + ? { + ...aaoResponse, + status: "Succeeded", + endTime: "2024-11-08T01:42:41.5354192+00:00", + } + : { ...aaoResponse, status: "InProgress" }; + + const response = { + status: 200, // aao always returns 200 with response body + body: json(responseBody), + }; + + postBinaryPollCount += 1; + return response; + }, + kind: "MockApiDefinition", + }, +]); + Scenarios.Azure_ResourceManager_OperationTemplates_Lro_delete = passOnSuccess([ { // LRO DELETE initial request diff --git a/packages/azure-http-specs/specs/helper.ts b/packages/azure-http-specs/specs/helper.ts new file mode 100644 index 0000000000..5ea4745396 --- /dev/null +++ b/packages/azure-http-specs/specs/helper.ts @@ -0,0 +1,7 @@ +import { resolvePath } from "@typespec/compiler"; +import { readFileSync } from "fs"; +import { fileURLToPath } from "url"; + +const root = resolvePath(fileURLToPath(import.meta.url), "../../../"); + +export const pngFile = readFileSync(resolvePath(root, "assets/image.png"));